151 lines
5.6 KiB
Plaintext
151 lines
5.6 KiB
Plaintext
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} |