define smooth [require './monotonic-interpolate'].createInterpolant define tp [require './transform'].transformPoint define utp [require './transform'].untransform 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] 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 [SpiroExpansionContext.prototype.expand] : begin { local lhs () local rhs () 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 - [Math.cos knot.normalAngle] dys.push : knot.proposedNormal.y - [Math.sin knot.normalAngle] } 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] + [Math.cos knot.normalAngle]] * [fd1 j] y : knot.y + [[fdy j] + [Math.sin knot.normalAngle]] * [fd1 j] type knot.type } set rhs.(j) : object { x : knot.x - [[fdx j] + [Math.cos knot.normalAngle]] * [fd2 j] y : knot.y - [[fdy j] + [Math.sin knot.normalAngle]] * [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 } } } } lhs.0.type = rhs.0.type = lhs.[lhs.length - 1].type = rhs.[rhs.length - 1].type = 'corner' return : lhs.concat [rhs.reverse] } exports.SpiroExpansionContext = SpiroExpansionContext