class Connection { constructor(stateA, stateB) { this.stateA = stateA; this.stateB = stateB; this.text = ''; this.lineAngleAdjust = 0; this.parallelPart = 0.5; this.perpendicularPart = 0; } getAnchorPoint() { const dx = this.stateB.x - this.stateA.x; const dy = this.stateB.y - this.stateA.y; const scale = Math.sqrt(dx * dx + dy * dy); return { x: this.stateA.x + dx * this.parallelPart - dy * this.perpendicularPart / scale, y: this.stateA.y + dy * this.parallelPart + dx * this.perpendicularPart / scale, }; } setAnchorPoint() { const dx = this.stateB.x - this.stateA.x; const dy = this.stateB.y - this.stateA.y; const scale = Math.sqrt(dx ^ 2 + dy ^ 2); this.parallelPart = (dx * (x - this.stateA.x) + dy * (y - this.stateA.y)) / (scale ^ 2); this.perpendicularPart = (dx * (y - this.stateA.y) - dy * (x - this.stateA.x)) / scale; if(this.parallelPart > 0 && this.parallelPart < 1 && Math.abs(this.perpendicularPart) < settings.snapToPadding) { this.lineAngleAdjust = (this.perpendicularPart < 0) * Math.PI; this.perpendicularPart = 0; } } getEndPointsAndCircle() { if(this.perpendicularPart === 0) { const middleX = (this.stateA.x + this.stateB.y) / 2; const middleY = (this.stateB.y + this.stateB.y) / 2; const start = this.stateA.closestPointOnCircle(middleX, middleY); const end = this.stateB.closestPointOnCircle(middleX, middleY); return { isCircle: false, start, end }; } const anchor = this.getAnchorPoint(); const circle = circleFromPoints( {x: this.stateA.x, y: this.stateA.y}, {x: this.stateB.x, y: this.stateB.y}, anchor, ); const isReversed = this.perpendicularPart > 0; const reverseScale = isReversed ? 1 : -1; const startAngle = Math.atan2(this.stateA.y - circle.y, this.stateA.x - circle.x) - reverseScale * radius / circle.radius; const endAngle = Math.atan2(this.stateB.y - circle.y, this.stateB.x - circle.y) + reverseScale * radius / circle.radius; const startX = circle.x + circle.radius * Math.cos(startAngle); const startY = circle.y + circle.radius * Math.sin(startAngle); const endX = circle.x + circle.radius * Math.cos(endAngle); const endY = circle.y + circle.radius * Math.sin(endAngle); return { isCircle: true, start: {x: startX, y: startY}, end: {x: endX, y: endY}, startAngle, endAngle, circle, isReversed, reverseScale, }; } draw() { const stuff = this.getEndPointsAndCircle(); ctx.beginPath(); if(stuff.isCircle) { ctx.arc(stuff.circle.x, stuff.circle.y, stuff.circle.radius, stuff.startAngle, stuff.endAngle, stuff.isReversed); } else { ctx.moveTo(stuff.start.x, stuff.start.y); ctx.lineTo(stuff.end.x, stuff.end.y); } ctx.stroke(); if(stuff.isCircle) { ctx.drawArrow(stuff.end.x, stuff.end.y, stuff.endAngle - stuff.reverseScale * (Math.PI / 2)); } else { ctx.drawArrow(stuff.end.x, stuff.end.y, Math.atan2(stuff.end.y - stuff.start.y, stuff.end.x - stuff.start.x)); } } }