bendy-line-thing-3.js

total 0
used 0
limit 0
/* title: Elbow Constraints category: constraints files: head point stroke ../point_src/point-content.js pointlist mouse dragging ../point_src/distances.js ../point_src/functions/clamp.js ../point_src/mirror.js ../point_src/stage.js ../point_src/text/label.js ../point_src/intersections.js ../point_src/cone.js ../point_src/easing.js ../point_src/arc.js ../point_src/constrain-distance.js --- An elbow contraint ensures a target point is _connected_ to another point, at a distance of the two radii. point.constraint.elbow(other) It's called an elbow, as there will always be an intersection at the max distance. Similar to rings bound at the edge. Synonymous to: let pA = this.legL let pB = this.primaryPoint pA.leash(pointB, (pB.radius + pA.radius) - .01) pA.avoid(pointB, Math.abs(pB.radius - pA.radius) + .01) */ const getContinuationArc = function(fromPoint, toPoint, centerPoint) { /* Return a plotted point on the continuation arc. The endpoint rotations define the tangent directions, while `centerPoint` acts as the landing-point hint and selects the correct sweep side. */ const dx = toPoint.x - fromPoint.x const dy = toPoint.y - fromPoint.y const distance = Math.hypot(dx, dy) if(distance === 0) { return { x: fromPoint.x , y: fromPoint.y } } const midpointX = (fromPoint.x + toPoint.x) * .5 const midpointY = (fromPoint.y + toPoint.y) * .5 const startTangent = fromPoint.radians == undefined ? degToRad(fromPoint.rotation + 180) : fromPoint.radians + Math.PI const endTangent = toPoint.radians == undefined ? degToRad(toPoint.rotation) : toPoint.radians const sweep = Math.atan2( Math.sin(endTangent - startTangent) , Math.cos(endTangent - startTangent) ) const minimumSweep = degToRad(.5) const absSweep = Math.max(Math.abs(sweep), minimumSweep) if(centerPoint != undefined && Math.abs(sweep) < degToRad(1)) { const projectionAmount = ( ((centerPoint.x - fromPoint.x) * dx) + ((centerPoint.y - fromPoint.y) * dy) ) / (distance * distance) return { x: fromPoint.x + (dx * projectionAmount) , y: fromPoint.y + (dy * projectionAmount) } } const offsetDistance = (distance * .5) / Math.tan(absSweep * .5) const unitPerpX = -dy / distance const unitPerpY = dx / distance const sideHint = centerPoint == undefined ? 1 : ((dx * (centerPoint.y - midpointY)) - (dy * (centerPoint.x - midpointX))) < 0 ? -1 : 1 const radiansDiff = function(a, b) { return Math.atan2(Math.sin(a - b), Math.cos(a - b)) } const getCandidate = function(direction) { const centerX = midpointX + (unitPerpX * offsetDistance * direction) const centerY = midpointY + (unitPerpY * offsetDistance * direction) const startRadiusAngle = Math.atan2(fromPoint.y - centerY, fromPoint.x - centerX) const endRadiusAngle = Math.atan2(toPoint.y - centerY, toPoint.x - centerX) const candidateSweep = radiansDiff(endRadiusAngle, startRadiusAngle) const tangentDirection = candidateSweep < 0 ? -1 : 1 const tangentAtStart = startRadiusAngle + (tangentDirection * Math.PI * .5) const tangentAtEnd = endRadiusAngle + (tangentDirection * Math.PI * .5) const tangentError = Math.abs(radiansDiff(tangentAtStart, startTangent)) + Math.abs(radiansDiff(tangentAtEnd, endTangent)) const sweepError = Math.abs(Math.abs(candidateSweep) - Math.min(absSweep, Math.PI)) const radius = Math.hypot(centerX - fromPoint.x, centerY - fromPoint.y) let pointX = midpointX let pointY = midpointY let hintError = direction == sideHint ? 0 : .001 if(centerPoint != undefined) { const hintDx = centerPoint.x - centerX const hintDy = centerPoint.y - centerY const hintDistance = Math.hypot(hintDx, hintDy) if(hintDistance > 0.000001) { pointX = centerX + (hintDx / hintDistance) * radius pointY = centerY + (hintDy / hintDistance) * radius hintError += Math.abs(hintDistance - radius) } } return { x: pointX , y: pointY , score: ((tangentError + sweepError) * distance) + (hintError / distance) } } const candidateA = getCandidate(-1) const candidateB = getCandidate(1) return candidateA.score < candidateB.score ? candidateA : candidateB } class MainStage extends Stage { canvas='playspace' mounted(){ console.log('main') this.a = new PointList( {x:180,y:360, radius:20} , {x:200,y:320, radius:20} , {x:240,y:290, radius:20} , {x:270,y:320, radius:20} , {x:300,y:350, radius:20} ).cast() this.dragging.addPoints(...this.a) } draw(ctx){ this.clear(ctx) /* hip leashes knee */ // this.a[2].constraint.leash(this.a[1], 100) /* Knee leashes foot */ // this.a[1].constraint.leash(this.a[0], 100) /* Knee looks away from hip*/ let orig = this.a; let lineCurve = this.genLine(orig) // a1.pen.indicator(ctx) // this.a.pen.indicator(ctx, {color: 'cyan'}) orig.pen.indicator(ctx, {color: '#444'}) let last; lineCurve.forEach(trip => { let sub = new PointList(...trip) if(last) { sub[0].pen.line(ctx, last, {color: 'cyan', width: 2}) } sub.pen.indicator(ctx, {color: 'orange'}) // sub.pen.quadCurve(ctx, {color: 'pink', width: 2, loop:false}) this.drawF(ctx, ...trip) last = sub.last() }) // lineCurve.pen.indicator(ctx, {color: 'orange'}) // lineCurve.pen.quadCurve(ctx, {color: 'red', width: 2, loop:true}) // lineCurve.pen.quadCurve(ctx, {color: 'pink', width: 2, loop:false}) // lineCurve.pen.line(ctx, {color: 'pink', width: 2, loop:false}) } drawF(ctx, fromPoint, toPoint, centerPoint){ // let cap = fromPoint.arc.to(toPoint, centerPoint) let cap = fromPoint.arc.to(centerPoint, toPoint) if(!cap) { return } ctx.beginPath(); ctx.arc(cap.cx, cap.cy, cap.radius, cap.startRadians, cap.toRadians, 0); ctx.stroke(); } genLine(orig){ let getPair = function(fromIndex=0, centerIndex=1, toIndex=2) { let r = orig[centerIndex].rotation orig[centerIndex].lookAt(orig[fromIndex]) let a1 = orig[centerIndex].project() orig[centerIndex].lookAt(orig[toIndex]) let a2 = orig[centerIndex].project() orig[centerIndex].rotation = r return [a1, a2] } let triple = function(fromIndex=0, centerIndex=1, toIndex=2, easingName) { let ab = getPair(fromIndex, centerIndex, toIndex) let arcPoint = orig[centerIndex].copy().update({radius:5}) arcPoint.update(getContinuationArc(ab[0], ab[1], orig[centerIndex])) // return [ab[0], orig[centerIndex], ab[1]] return [ab[0], arcPoint, ab[1]] } let tipA = function(beforeIndex, index, toTip=true, easingName) { let origR = orig[index].rotation // orig[index].lookAt(orig.last()) let a1 = orig[index].project() orig[index].lookAt(orig[beforeIndex]) let a2 = orig[index] if(toTip) { a2 = a2.project() } orig[index].rotation = origR if(!toTip) { return [a2, orig[index]] } let c = orig[index].copy() c.lookAt(a1.midpoint(a2)) let real = c.radius - (a1.distanceTo(a2)) * .5 c.radius = undefined c.update(getContinuationArc(a1, a2, orig[index])) return [a1, c, a2] } let tipB = function(beforeIndex, index, toTip=true, easingName) { let origR = orig[index].rotation orig[index].lookAt(orig[beforeIndex]) let a1 = orig[index] if(toTip) { a1 = a1.project() } orig[index].rotation = origR let a2 = orig[index].project() let c = orig[index].copy() // if(easingName) { // c.lookAt(a1.midpoint(a2)) // let real = c.radius - (a1.distanceTo(a2)) * .5 // real = c.radius * easingFunctions[easingName][easingType](real / c.radius) // c = c.project(real) // } c.lookAt(a1.midpoint(a2)) let real = c.radius - (a1.distanceTo(a2)) * .5 c.radius = undefined c.update(getContinuationArc(a1, a2, orig[index])) if(!toTip) { return [a1, orig[index]] } return [a1, c, a2] } let items = orig//.slice(1, stage.a.length-1) let toTip = true; let easingName = 'sine' let easingType = 'out' // orig[1].constraint.cone(orig[2], 90) let lineCurve = new PointList( tipA(1, 0, toTip, easingName) )//.cast() items.forEach((e,i, a) => { if(i == 0 || i == items.length-1) { return } lineCurve.push(triple(i-1, i, i+1, easingName)) }) lineCurve.push(tipB(items.length-2, items.length-1, toTip, easingName)); return lineCurve } } ;stage = MainStage.go();
Run
Meta Data
title Elbow Constraints
imports ()
files ('head', 'point', 'stroke', '../point_src/point-content.js', 'pointlist', 'mouse', 'dragging', '../point_src/distances.js', '../point_src/functions/clamp.js', '../point_src/mirror.js', '../point_src/stage.js', '../point_src/text/label.js', '../point_src/intersections.js', '../point_src/cone.js', '../point_src/easing.js', '../point_src/arc.js', '../point_src/constrain-distance.js')
unused_keys ()
unknown_keys ('category',)
category ['constraints']
filepath_exists True
path bendy-line-thing-3.js
filepath bendy-line-thing-3.js
clean_files ('../point_src/core/head.js', '../point_src/pointpen.js', '../point_src/compass.js', '../point_src/center.js', '../point_src/point-content.js', '../point_src/pointdraw.js', '../point_src/relative-xy.js', '../point_src/pointcast.js', '../point_src/point.js', '../point_src/setunset.js', '../point_src/stroke.js', '../point_src/pointlistdraw.js', '../point_src/pointlistgradient.js', '../point_src/pointlistshape.js', '../point_src/pointlistgenerator.js', '../point_src/unpack.js', '../point_src/pointlist.js', '../point_src/pointlistpen.js', '../point_src/events.js', '../point_src/automouse.js', '../point_src/functions/clamp.js', '../point_src/distances.js', '../point_src/protractor.js', '../point_src/text/beta.js', '../point_src/dragging.js', '../point_src/mirror.js', '../point_src/stage-hooks.js', '../point_src/functions/resolve.js', '../point_src/stage.js', '../point_src/text/label.js', '../point_src/intersections.js', '../point_src/cone.js', '../point_src/easing.js', '../point_src/arc.js', '../point_src/constrain-distance.js')
markdown {'html': "<p>An elbow contraint ensures a target point is <em>connected</em> to another point, at\na distance of the two radii.</p>\n<pre><code> point.constraint.elbow(other)\n</code></pre>\n<p>It's called an elbow, as there will always be an intersection at the max distance.\nSimilar to rings bound at the edge.</p>\n<p>Synonymous to:</p>\n<pre><code>let pA = this.legL\nlet pB = this.primaryPoint\n\npA.leash(pointB,\n (pB.radius + pA.radius) - .01)\npA.avoid(pointB,\n Math.abs(pB.radius - pA.radius) + .01)\n</code></pre>", 'content': "title: Elbow Constraints\ncategory: constraints\nfiles:\n head\n point\n stroke\n ../point_src/point-content.js\n pointlist\n mouse\n dragging\n ../point_src/distances.js\n ../point_src/functions/clamp.js\n ../point_src/mirror.js\n ../point_src/stage.js\n ../point_src/text/label.js\n ../point_src/intersections.js\n ../point_src/cone.js\n ../point_src/easing.js\n ../point_src/arc.js\n ../point_src/constrain-distance.js\n---\n\nAn elbow contraint ensures a target point is _connected_ to another point, at\na distance of the two radii.\n\n point.constraint.elbow(other)\n\nIt's called an elbow, as there will always be an intersection at the max distance.\nSimilar to rings bound at the edge.\n\nSynonymous to:\n\n let pA = this.legL\n let pB = this.primaryPoint\n\n pA.leash(pointB,\n (pB.radius + pA.radius) - .01)\n pA.avoid(pointB,\n Math.abs(pB.radius - pA.radius) + .01)"}