import './monotonic-interpolate' as smooth import './transform' as [object [transformPoint tp] [untransform utp] inverse] define [fallback] : for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j) define [linreg x0 y0 x1 y1 x] : y0 + (x - x0) * (y1 - y0) / (x1 - x0) export default : define [SpiroExpansionContext] : begin set this.gizmo {.xx 1 .yy 1 .xy 0 .yy 0 .x 0 .y 0} set this.controlKnots {} set this.defaultd1 0 set this.defaultd2 0 return nothing define [SpiroExpansionContext.prototype.moveTo x y unimportant] : begin if unimportant : return nothing # Transform incoming knots using gizmo set {.x x .y y} : tp this.gizmo {.x x .y y} this.controlKnots.push {.x x .y y .type 'g4' .d1 this.defaultd1 .d2 this.defaultd2} define [SpiroExpansionContext.prototype.lineTo x y unimportant] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) set {.x x .y y} : tp this.gizmo {.x x .y y} local thisKnot {.x x .y y .type 'g4' .d1 lastKnot.d1 .d2 lastKnot.d2} if lastKnot : begin local normalAngle : Math.PI / 2 + [Math.atan2 (y - lastKnot.y) (x - lastKnot.x)] set thisKnot.normalAngle normalAngle if (lastKnot.normalAngle === nothing) : set lastKnot.normalAngle normalAngle if [not unimportant] : this.controlKnots.push thisKnot define [SpiroExpansionContext.prototype.cubicTo x1 y1 x2 y2 x y unimportant] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) set {.x x1 .y y1} : tp this.gizmo {.x x1 .y y1} set {.x x2 .y y2} : tp this.gizmo {.x x2 .y y2} set {.x x .y y} : tp this.gizmo {.x x .y y} local thisKnot {.x x .y y .type 'g4' .d1 lastKnot.d1 .d2 lastKnot.d2} if (lastKnot && lastKnot.normalAngle === nothing) : begin local normalAngle : Math.PI / 2 + [Math.atan2 (y1 - lastKnot.y) (x1 - lastKnot.x)] if (lastKnot.normalAngle === nothing) : set lastKnot.normalAngle normalAngle if [not unimportant] : begin local normalAngle : Math.PI / 2 + [Math.atan2 (y - y2) (x - x2)] set thisKnot.normalAngle normalAngle this.controlKnots.push thisKnot define [SpiroExpansionContext.prototype.set-width l r] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) if lastKnot : then lastKnot.d1 = l; lastKnot.d2 = r : else this.defaultd1 = l; this.defaultd2 = r define [SpiroExpansionContext.prototype.heads-to direction] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) if lastKnot : begin lastKnot.proposedNormal = direction define [SpiroExpansionContext.prototype.set-type type] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) if lastKnot : begin lastKnot.type = type define [shortestAngle end start] : begin local a : (end - start) % (Math.PI * 2) if (a > Math.PI / 2) : a = a - Math.PI return a define [normalY angle] : Math.sin angle define [normalX angle vex] : [Math.cos angle] * vex define [SpiroExpansionContext.prototype.expand contrast] : begin local lhs {} local rhs {} local contrast : fallback contrast (1 / 0.9) local d1s {} local d2s {} local dxs {} local dys {} local js {} foreach j [range 0 this.controlKnots.length] : if [not this.controlKnots.(j).unimportant] : begin local knot this.controlKnots.(j) js.push j d1s.push knot.d1 d2s.push knot.d2 if knot.proposedNormal : then dxs.push : knot.proposedNormal.x - [normalX knot.normalAngle contrast] dys.push : knot.proposedNormal.y - [normalY knot.normalAngle contrast] : else dxs.push 0; dys.push 0 local fd1 : smooth js d1s local fd2 : smooth js d2s local fdx : smooth js dxs local fdy : smooth js dys # console.log deltaAngles # interpolate important knots foreach j [range 0 this.controlKnots.length] : begin local knot this.controlKnots.(j) if [not knot.unimportant] : begin set lhs.(j) : object x : knot.x + ([fdx j] + [normalX knot.normalAngle contrast]) * [fd1 j] y : knot.y + ([fdy j] + [normalY knot.normalAngle contrast]) * [fd1 j] type knot.type set rhs.(j) : object x : knot.x - ([fdx j] + [normalX knot.normalAngle contrast]) * [fd2 j] y : knot.y - ([fdy j] + [normalY knot.normalAngle contrast]) * [fd2 j] type : match knot.type "left" "right" "right" "left" type type # interpolate unimportant knots referencing their original position relationship foreach j [range 0 this.controlKnots.length] : begin local knot this.controlKnots.(j) if knot.unimportant : begin local jBefore (j - 1) while this.controlKnots.(jBefore).unimportant : dec jBefore local jAfter (j + 1) while this.controlKnots.(jAfter).unimportant : inc jAfter local knotBefore : utp this.gizmo this.controlKnots.(jBefore) local knotAfter : utp this.gizmo this.controlKnots.(jAfter) local ref : utp this.gizmo knot local lhsBefore : utp this.gizmo lhs.(jBefore) local lhsAfter : utp this.gizmo lhs.(jAfter) local rhsBefore : utp this.gizmo rhs.(jBefore) local rhsAfter : utp this.gizmo rhs.(jAfter) local kLHS : tp this.gizmo : object x : linreg knotBefore.x lhsBefore.x knotAfter.x lhsAfter.x ref.x y : linreg knotBefore.y lhsBefore.y knotAfter.y lhsAfter.y ref.y local kRHS : tp this.gizmo : object x : linreg knotBefore.x rhsBefore.x knotAfter.x rhsAfter.x ref.x y : linreg knotBefore.y rhsBefore.y knotAfter.y rhsAfter.y ref.y set lhs.(j) : object x kLHS.x y kLHS.y type knot.type set rhs.(j) : object x kRHS.x y kRHS.y type : match knot.type "left" "right" "right" "left" type type return {.lhs lhs .rhs rhs}