Skip to content

Commit

Permalink
feat(snapping): snap bendpoints on connection path
Browse files Browse the repository at this point in the history
  • Loading branch information
marstamm authored and fake-join[bot] committed May 23, 2022
1 parent 95312b4 commit e49eace
Show file tree
Hide file tree
Showing 5 changed files with 412 additions and 11 deletions.
50 changes: 41 additions & 9 deletions lib/features/bendpoints/BendpointSnapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {

import { setSnapped } from '../snapping/SnapUtil';

import { getClosestPointOnConnection } from './BendpointUtil';

var abs = Math.abs,
round = Math.round;

Expand Down Expand Up @@ -38,7 +40,12 @@ export default function BendpointSnapping(eventBus) {
return value;
}

function mid(element) {
function getSnapPoint(element, event) {

if (element.waypoints) {
return getClosestPointOnConnection(event, element);
}

if (element.width) {
return {
x: round(element.width / 2 + element.x),
Expand All @@ -49,9 +56,10 @@ export default function BendpointSnapping(eventBus) {

// connection segment snapping //////////////////////

function getConnectionSegmentSnaps(context) {
function getConnectionSegmentSnaps(event) {

var snapPoints = context.snapPoints,
var context = event.context,
snapPoints = context.snapPoints,
connection = context.connection,
waypoints = connection.waypoints,
segmentStart = context.segmentStart,
Expand All @@ -72,11 +80,11 @@ export default function BendpointSnapping(eventBus) {
];

if (segmentStartIndex < 2) {
referenceWaypoints.unshift(mid(connection.source));
referenceWaypoints.unshift(getSnapPoint(connection.source, event));
}

if (segmentEndIndex > waypoints.length - 3) {
referenceWaypoints.unshift(mid(connection.target));
referenceWaypoints.unshift(getSnapPoint(connection.target, event));
}

context.snapPoints = snapPoints = { horizontal: [] , vertical: [] };
Expand All @@ -102,8 +110,7 @@ export default function BendpointSnapping(eventBus) {
}

eventBus.on('connectionSegment.move.move', 1500, function(event) {
var context = event.context,
snapPoints = getConnectionSegmentSnaps(context),
var snapPoints = getConnectionSegmentSnaps(event),
x = event.x,
y = event.y,
sx, sy;
Expand Down Expand Up @@ -171,13 +178,31 @@ export default function BendpointSnapping(eventBus) {
return snapPoints;
}

// Snap Endpoint of new connection
eventBus.on([
'connect.hover',
'connect.move',
'connect.end'
], 1500, function(event) {
var context = event.context,
hover = context.hover,
hoverMid = hover && getSnapPoint(hover, event);

// only snap on connections, elements can have multiple connect endpoints
if (!isConnection(hover) || !hoverMid || !hoverMid.x || !hoverMid.y) {
return;
}

setSnapped(event, 'x', hoverMid.x);
setSnapped(event, 'y', hoverMid.y);
});

eventBus.on([ 'bendpoint.move.move', 'bendpoint.move.end' ], 1500, function(event) {

var context = event.context,
snapPoints = getBendpointSnaps(context),
hover = context.hover,
hoverMid = hover && mid(hover),
hoverMid = hover && getSnapPoint(hover, event),
x = event.x,
y = event.y,
sx, sy;
Expand Down Expand Up @@ -214,4 +239,11 @@ export default function BendpointSnapping(eventBus) {
}


BendpointSnapping.$inject = [ 'eventBus' ];
BendpointSnapping.$inject = [ 'eventBus' ];


// helpers //////////////////////

function isConnection(element) {
return element && !!element.waypoints;
}
36 changes: 36 additions & 0 deletions lib/features/bendpoints/BendpointUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
getApproxIntersection
} from '../../util/LineIntersection';

import { getDistancePointLine, perpendicularFoot } from './GeometricUtil';

export var BENDPOINT_CLS = 'djs-bendpoint';
export var SEGMENT_DRAGGER_CLS = 'djs-segment-dragger';

Expand Down Expand Up @@ -156,6 +158,20 @@ export function calculateSegmentMoveRegion(segmentLength) {
return Math.abs(Math.round(segmentLength * 2 / 3));
}

/**
* Returns the point with the closest distance that is on the connection path.
*
* @param {Point} position
* @param {djs.Base.Connection} connection
* @returns {Point}
*/
export function getClosestPointOnConnection(position, connection) {
var segment = getClosestSegment(position, connection);

return perpendicularFoot(position, segment);
}


// helper //////////

function calculateHitWidth(segmentStart, segmentEnd, alignment) {
Expand All @@ -166,3 +182,23 @@ function calculateHitWidth(segmentStart, segmentEnd, alignment) {
calculateSegmentMoveRegion(segmentLengthXAxis) :
calculateSegmentMoveRegion(segmentLengthYAxis);
}

function getClosestSegment(position, connection) {
var waypoints = connection.waypoints;

var minDistance = Infinity,
segmentIndex;

for (var i = 0; i < waypoints.length - 1; i++) {
var start = waypoints[i],
end = waypoints[i + 1],
distance = getDistancePointLine(position, [ start, end ]);

if (distance < minDistance) {
minDistance = distance;
segmentIndex = i;
}
}

return [ waypoints[segmentIndex], waypoints[segmentIndex + 1] ];
}
122 changes: 122 additions & 0 deletions lib/features/bendpoints/GeometricUtil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@

/**
* Returns the length of a vector
*
* @param {Vector}
* @return {Float}
*/
export function vectorLength(v) {
return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2));
}


/**
* Calculates the angle between a line a the yAxis
*
* @param {Array}
* @return {Float}
*/
export function getAngle(line) {

// return value is between 0, 180 and -180, -0
// @janstuemmel: maybe replace return a/b with b/a
return Math.atan((line[1].y - line[0].y) / (line[1].x - line[0].x));
}


/**
* Rotates a vector by a given angle
*
* @param {Vector}
* @param {Float} Angle in radians
* @return {Vector}
*/
export function rotateVector(vector, angle) {
return (!angle) ? vector : {
x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y,
y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y
};
}


/**
* Solves a 2D equation system
* a + r*b = c, where a,b,c are 2D vectors
*
* @param {Vector}
* @param {Vector}
* @param {Vector}
* @return {Float}
*/
function solveLambaSystem(a, b, c) {

// the 2d system
var system = [
{ n: a[0] - c[0], lambda: b[0] },
{ n: a[1] - c[1], lambda: b[1] }
];

// solve
var n = system[0].n * b[0] + system[1].n * b[1],
l = system[0].lambda * b[0] + system[1].lambda * b[1];

return -n / l;
}


/**
* Position of perpendicular foot
*
* @param {Point}
* @param [ {Point}, {Point} ] line defined through two points
* @return {Point} the perpendicular foot position
*/
export function perpendicularFoot(point, line) {

var a = line[0], b = line[1];

// relative position of b from a
var bd = { x: b.x - a.x, y: b.y - a.y };

// solve equation system to the parametrized vectors param real value
var r = solveLambaSystem([ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ]);

return { x: a.x + r * bd.x, y: a.y + r * bd.y };
}


/**
* Calculates the distance between a point and a line
*
* @param {Point}
* @param [ {Point}, {Point} ] line defined through two points
* @return {Float} distance
*/
export function getDistancePointLine(point, line) {

var pfPoint = perpendicularFoot(point, line);

// distance vector
var connectionVector = {
x: pfPoint.x - point.x,
y: pfPoint.y - point.y
};

return vectorLength(connectionVector);
}


/**
* Calculates the distance between two points
*
* @param {Point}
* @param {Point}
* @return {Float} distance
*/
export function getDistancePointPoint(point1, point2) {

return vectorLength({
x: point1.x - point2.x,
y: point1.y - point2.y
});
}
Loading

0 comments on commit e49eace

Please sign in to comment.