import './monotonic-interpolate' as smooth import './transform' as : Transform && [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) define-macro xytransform : syntax-rules `[xytransform @tfm @x @y] : begin set [env.declarations.get [formOf x]].isParameter 0 set [env.declarations.get [formOf y]].isParameter 0 let [t : env.newt] `[begin \\ set @t @x set @x : @x * @tfm.xx + @y * @tfm.yx + @tfm.x set @y : @t * @tfm.xy + @y * @tfm.yy + @tfm.y ] define [normalY angle] : Math.sin angle define [normalX angle vex] : [Math.cos angle] * vex export all : class SpiroExpansionContext public [new] : begin set this.gizmo [Transform.Id] set this.controlKnots {} set this.defaultd1 0 set this.defaultd2 0 public [moveTo x y unimportant] : begin if unimportant : return nothing # Transform incoming knots using gizmo xytransform this.gizmo x y this.controlKnots.push {.x x .y y .type 'g4' .d1 this.defaultd1 .d2 this.defaultd2} public [lineTo x y unimportant] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) xytransform this.gizmo x 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 public [cubicTo x1 y1 x2 y2 x y unimportant] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) xytransform this.gizmo x1 y1 xytransform this.gizmo x2 y2 xytransform this.gizmo x 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 public [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 public [heads-to direction] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) if lastKnot : begin lastKnot.proposedNormal = direction public [set-type type] : begin local lastKnot this.controlKnots.(this.controlKnots.length - 1) if lastKnot : begin lastKnot.type = type public [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}