Further cleanup and optimize spiro expansion.
This commit is contained in:
parent
e86890e16a
commit
e1e8144f1c
12 changed files with 234 additions and 224 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -44,8 +44,8 @@ snapshot/iosevka*
|
||||||
snapshot/index.css
|
snapshot/index.css
|
||||||
|
|
||||||
# Generated scripts
|
# Generated scripts
|
||||||
support/*.js
|
|
||||||
gen/build-glyphs.js
|
gen/build-glyphs.js
|
||||||
|
gen/kits/*.js
|
||||||
meta/*.js
|
meta/*.js
|
||||||
otl/*.js
|
otl/*.js
|
||||||
glyphs/*.js
|
glyphs/*.js
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import '../support/glyph' as Glyph
|
import '../support/glyph' as Glyph
|
||||||
import '../support/point' as Point
|
import '../support/point' as Point
|
||||||
import '../support/spiro-kit' as spirokit
|
import './kits/spiro-kit' as spirokit
|
||||||
import '../support/boole-kit' as BooleKit
|
import './kits/boole-kit' as BooleKit
|
||||||
import '../support/anchor' as Anchor
|
import '../support/anchor' as Anchor
|
||||||
import '../support/monotonic-interpolate' as smoothreg
|
import '../support/monotonic-interpolate' as smoothreg
|
||||||
|
|
||||||
|
|
|
@ -125,8 +125,8 @@ class FairizedShapeSink {
|
||||||
this.lastContour.pop();
|
this.lastContour.pop();
|
||||||
}
|
}
|
||||||
this.contours.push(this.lastContour);
|
this.contours.push(this.lastContour);
|
||||||
this.lastContour = [];
|
|
||||||
}
|
}
|
||||||
|
this.lastContour = [];
|
||||||
}
|
}
|
||||||
moveTo(x, y) {
|
moveTo(x, y) {
|
||||||
this.endShape();
|
this.endShape();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'typo-geom' as TypoGeom
|
import 'typo-geom' as TypoGeom
|
||||||
import './curve-util' as CurveUtil
|
import '../../support/curve-util' as CurveUtil
|
||||||
import './spiroexpand' as [object SpiroContourContext]
|
|
||||||
|
|
||||||
export : define [SetupBuilders args] : begin
|
export : define [SetupBuilders args] : begin
|
||||||
define [object Glyph globalTransform] args
|
define [object Glyph globalTransform] args
|
||||||
|
@ -17,7 +16,7 @@ export : define [SetupBuilders args] : begin
|
||||||
g1.include item
|
g1.include item
|
||||||
set g1.contours : g1.contours.map CurveUtil.convertContourToCubic
|
set g1.contours : g1.contours.map CurveUtil.convertContourToCubic
|
||||||
local c1 : TypoGeom.Boolean.combine operator [CurveUtil.convertShapeToArcs g.contours] [CurveUtil.convertShapeToArcs g1.contours] TypoGeom.Boolean.PolyFillType.pftNonZero TypoGeom.Boolean.PolyFillType.pftNonZero 16384
|
local c1 : TypoGeom.Boolean.combine operator [CurveUtil.convertShapeToArcs g.contours] [CurveUtil.convertShapeToArcs g1.contours] TypoGeom.Boolean.PolyFillType.pftNonZero TypoGeom.Boolean.PolyFillType.pftNonZero 16384
|
||||||
local ctx : new SpiroContourContext
|
local ctx : new CurveUtil.ArcFlattener
|
||||||
TypoGeom.transferBezArcShape c1 ctx
|
TypoGeom.transferBezArcShape c1 ctx
|
||||||
set g.contours ctx.contours
|
set g.contours ctx.contours
|
||||||
this.includeGlyph g
|
this.includeGlyph g
|
|
@ -1,13 +1,8 @@
|
||||||
import 'spiro' as SpiroJs
|
import 'spiro' as SpiroJs
|
||||||
import './spiroexpand' as [object SpiroExpansionContext SpiroContourContext]
|
import '../../support/spiro-expand' as SpiroExpansionContext
|
||||||
import './curve-util' as CurveUtil
|
import '../../support/curve-util' as CurveUtil
|
||||||
|
import '../../support/transform' as Transform
|
||||||
import './transform' as Transform
|
import '../../support/utils' as [object fallback mix bez2 bez3]
|
||||||
|
|
||||||
define [fallback] : for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)
|
|
||||||
define [mix a b p] : a + (b - a) * p
|
|
||||||
define [bez2 a b c t] : (1 - t) * (1 - t) * a + 2 * (1 - t) * t * b + t * t * c
|
|
||||||
define [bez3 a b c d t] : (1 - t) * (1 - t) * (1 - t) * a + 3 * (1 - t) * (1 - t) * t * b + 3 * t * t * (1 - t) * c + t * t * t * d
|
|
||||||
|
|
||||||
export : define [SetupBuilders args] : begin
|
export : define [SetupBuilders args] : begin
|
||||||
define [object para Glyph Contrast globalTransform Stroke Superness] args
|
define [object para Glyph Contrast globalTransform Stroke Superness] args
|
||||||
|
@ -166,12 +161,12 @@ export : define [SetupBuilders args] : begin
|
||||||
set knot.af : lambda [] : begin
|
set knot.af : lambda [] : begin
|
||||||
this.setType ty
|
this.setType ty
|
||||||
if af : af.apply this args
|
if af : af.apply this args
|
||||||
SpiroJs.spiroToBezierOnContext knots closed s
|
SpiroJs.spiroToArcsOnContext knots closed s
|
||||||
foreach af [items-of lastafs] : if af : af.call s
|
foreach af [items-of lastafs] : if af : af.call s
|
||||||
|
|
||||||
local {.lhs lhs .rhs rhs} : s.expand [fallback s.contrast Contrast]
|
local {.lhs lhs .rhs rhs} : s.expand [fallback s.contrast Contrast]
|
||||||
if closed : then
|
if closed : then
|
||||||
local g : new SpiroContourContext
|
local g : new CurveUtil.ArcFlattener
|
||||||
SpiroJs.spiroToBezierOnContext [lhs.slice 0 (-1)] true g QUAD PRECISION
|
SpiroJs.spiroToBezierOnContext [lhs.slice 0 (-1)] true g QUAD PRECISION
|
||||||
local lhsContour g.contours.0
|
local lhsContour g.contours.0
|
||||||
set g.contours {}
|
set g.contours {}
|
||||||
|
@ -179,7 +174,7 @@ export : define [SetupBuilders args] : begin
|
||||||
local rhsContour g.contours.0
|
local rhsContour g.contours.0
|
||||||
set g.contours {[lhsContour.concat rhsContour]}
|
set g.contours {[lhsContour.concat rhsContour]}
|
||||||
: else : begin
|
: else : begin
|
||||||
local g : new SpiroContourContext
|
local g : new CurveUtil.ArcFlattener
|
||||||
lhs.0.type = rhs.0.type = lhs.(lhs.length - 1).type = rhs.(rhs.length - 1).type = 'corner'
|
lhs.0.type = rhs.0.type = lhs.(lhs.length - 1).type = rhs.(rhs.length - 1).type = 'corner'
|
||||||
SpiroJs.spiroToBezierOnContext [lhs.concat : rhs.reverse] true g QUAD PRECISION
|
SpiroJs.spiroToBezierOnContext [lhs.concat : rhs.reverse] true g QUAD PRECISION
|
||||||
|
|
||||||
|
@ -190,7 +185,7 @@ export : define [SetupBuilders args] : begin
|
||||||
return g
|
return g
|
||||||
|
|
||||||
define [spiro-outline] : let [k : {}.slice.call arguments 0] : lambda [dontinc] : begin
|
define [spiro-outline] : let [k : {}.slice.call arguments 0] : lambda [dontinc] : begin
|
||||||
local g : new SpiroContourContext (this.gizmo || globalTransform)
|
local g : new CurveUtil.ArcFlattener (this.gizmo || globalTransform)
|
||||||
local {.knots knots .closed closed .lastafs lastafs} : prepareSpiroKnots k g
|
local {.knots knots .closed closed .lastafs lastafs} : prepareSpiroKnots k g
|
||||||
SpiroJs.spiroToBezierOnContext knots closed g QUAD PRECISION
|
SpiroJs.spiroToBezierOnContext knots closed g QUAD PRECISION
|
||||||
foreach af [items-of lastafs] : if af : af.call g
|
foreach af [items-of lastafs] : if af : af.call g
|
|
@ -14,7 +14,7 @@
|
||||||
"otfcc-ttcize": "^0.10.1",
|
"otfcc-ttcize": "^0.10.1",
|
||||||
"patel": "^0.33.1",
|
"patel": "^0.33.1",
|
||||||
"semver": "^7.1.3",
|
"semver": "^7.1.3",
|
||||||
"spiro": "^1.1.0",
|
"spiro": "^2.0.0",
|
||||||
"stylus": "^0.54.7",
|
"stylus": "^0.54.7",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"topsort": "^0.0.2",
|
"topsort": "^0.0.2",
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
const TypoGeom = require("typo-geom");
|
const TypoGeom = require("typo-geom");
|
||||||
const Point = require("./point");
|
const Point = require("./point");
|
||||||
const { mix } = require("./utils");
|
const { mix } = require("./utils");
|
||||||
|
const Transform = require("./transform");
|
||||||
|
|
||||||
exports.OffsetCurve = class OffsetCurve {
|
exports.OffsetCurve = class OffsetCurve {
|
||||||
constructor(bone, offset, contrast) {
|
constructor(bone, offset, contrast) {
|
||||||
|
@ -195,3 +196,34 @@ exports.convertContourToCubicRev = convertContourToCubicRev;
|
||||||
exports.autoCubify = autoCubify;
|
exports.autoCubify = autoCubify;
|
||||||
exports.fixedCubify = fixedCubify;
|
exports.fixedCubify = fixedCubify;
|
||||||
exports.convertShapeToArcs = convertShapeToArcs;
|
exports.convertShapeToArcs = convertShapeToArcs;
|
||||||
|
|
||||||
|
exports.ArcFlattener = class ArcFlattener {
|
||||||
|
constructor(gizmo) {
|
||||||
|
this.gizmo = gizmo || Transform.Id();
|
||||||
|
this.contours = [];
|
||||||
|
this.lastContour = [];
|
||||||
|
}
|
||||||
|
beginShape() {}
|
||||||
|
endShape() {
|
||||||
|
if (this.lastContour.length) {
|
||||||
|
this.contours.push(this.lastContour);
|
||||||
|
}
|
||||||
|
this.lastContour = [];
|
||||||
|
}
|
||||||
|
moveTo(x, y) {
|
||||||
|
this.endShape();
|
||||||
|
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||||
|
}
|
||||||
|
lineTo(x, y) {
|
||||||
|
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||||
|
}
|
||||||
|
curveTo(xc, yc, x, y) {
|
||||||
|
this.lastContour.push(Point.transformedXY(this.gizmo, xc, yc, false, false));
|
||||||
|
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||||
|
}
|
||||||
|
cubicTo(x1, y1, x2, y2, x, y) {
|
||||||
|
this.lastContour.push(Point.transformedXY(this.gizmo, x1, y1, false, true));
|
||||||
|
this.lastContour.push(Point.transformedXY(this.gizmo, x2, y2, false, true));
|
||||||
|
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
132
support/spiro-expand.js
Normal file
132
support/spiro-expand.js
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
const Transform = require("./transform");
|
||||||
|
const { linreg } = require("./utils");
|
||||||
|
|
||||||
|
module.exports = class SpiroExpansionContext {
|
||||||
|
constructor() {
|
||||||
|
this.gizmo = Transform.Id();
|
||||||
|
this.controlKnots = [];
|
||||||
|
this.defaultD1 = 0;
|
||||||
|
this.defaultD2 = 0;
|
||||||
|
}
|
||||||
|
beginShape() {}
|
||||||
|
endShape() {}
|
||||||
|
moveTo(x, y, unimportant) {
|
||||||
|
if (unimportant) return;
|
||||||
|
this.controlKnots.push({
|
||||||
|
type: "g4",
|
||||||
|
d1: this.defaultD1,
|
||||||
|
d2: this.defaultD2,
|
||||||
|
...this.gizmo.apply({ x, y })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
arcTo(arc, x, y) {
|
||||||
|
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||||
|
if (!k0) throw new Error("Unreachable: lineTo called before moveTo");
|
||||||
|
if (k0.normalAngle == null) {
|
||||||
|
const tfDerive0 = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
||||||
|
k0.normalAngle = Math.PI / 2 + Math.atan2(tfDerive0.y, tfDerive0.x);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const tfDerive1 = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
||||||
|
this.controlKnots.push({
|
||||||
|
type: "g4",
|
||||||
|
d1: k0.d1,
|
||||||
|
d2: k0.d2,
|
||||||
|
...this.gizmo.apply({ x, y }),
|
||||||
|
normalAngle: Math.PI / 2 + Math.atan2(tfDerive1.y, tfDerive1.x)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setWidth(l, r) {
|
||||||
|
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||||
|
if (k0) {
|
||||||
|
(k0.d1 = l), (k0.d2 = r);
|
||||||
|
} else {
|
||||||
|
(this.defaultD1 = l), (this.defaultD2 = r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headsTo(direction) {
|
||||||
|
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||||
|
if (k0) k0.proposedNormal = direction;
|
||||||
|
}
|
||||||
|
setType(type) {
|
||||||
|
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||||
|
if (k0) k0.type = type;
|
||||||
|
}
|
||||||
|
expand(contrast) {
|
||||||
|
if (contrast == null) contrast = 1 / 0.9;
|
||||||
|
const lhs = [],
|
||||||
|
rhs = [];
|
||||||
|
|
||||||
|
// Create important knots
|
||||||
|
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||||
|
const knot = this.controlKnots[j];
|
||||||
|
if (knot.unimportant) continue;
|
||||||
|
|
||||||
|
let dx = 0,
|
||||||
|
dy = 0;
|
||||||
|
if (knot.proposedNormal) {
|
||||||
|
dx = knot.proposedNormal.x - normalX(knot.normalAngle, contrast);
|
||||||
|
dy = knot.proposedNormal.y - normalY(knot.normalAngle, contrast);
|
||||||
|
}
|
||||||
|
lhs[j] = {
|
||||||
|
type: knot.type,
|
||||||
|
x: knot.x + knot.d1 * (dx + normalX(knot.normalAngle, contrast)),
|
||||||
|
y: knot.y + knot.d1 * (dy + normalY(knot.normalAngle, contrast))
|
||||||
|
};
|
||||||
|
rhs[j] = {
|
||||||
|
type: reverseKnotType(knot.type),
|
||||||
|
x: knot.x - knot.d2 * (dx + normalX(knot.normalAngle, contrast)),
|
||||||
|
y: knot.y - knot.d2 * (dy + normalY(knot.normalAngle, contrast))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.interpolateUnimportantKnots(lhs, rhs);
|
||||||
|
return { lhs, rhs };
|
||||||
|
}
|
||||||
|
interpolateUnimportantKnots(lhs, rhs) {
|
||||||
|
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||||
|
const knot = this.controlKnots[j];
|
||||||
|
if (!knot.unimportant) continue;
|
||||||
|
let jBefore, jAfter;
|
||||||
|
for (jBefore = j - 1; this.controlKnots[jBefore].unimportant; jBefore--);
|
||||||
|
for (jAfter = j + 1; this.controlKnots[jAfter].unimportant; jAfter++);
|
||||||
|
|
||||||
|
const knotBefore = this.gizmo.unapply(this.controlKnots[jBefore]),
|
||||||
|
knotAfter = this.gizmo.unapply(this.controlKnots[jAfter]),
|
||||||
|
ref = this.gizmo.unapply(knot),
|
||||||
|
lhsBefore = this.gizmo.unapply(lhs[jBefore]),
|
||||||
|
lhsAfter = this.gizmo.unapply(lhs[jAfter]),
|
||||||
|
rhsBefore = this.gizmo.unapply(rhs[jBefore]),
|
||||||
|
rhsAfter = this.gizmo.unapply(rhs[jAfter]);
|
||||||
|
|
||||||
|
lhs[j] = {
|
||||||
|
type: knot.type,
|
||||||
|
...this.gizmo.apply({
|
||||||
|
x: linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
||||||
|
y: linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
rhs[j] = {
|
||||||
|
type: reverseKnotType(knot.type),
|
||||||
|
...this.gizmo.apply({
|
||||||
|
x: linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x),
|
||||||
|
y: linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function zeroes(n) {
|
||||||
|
let a = new Array(n);
|
||||||
|
for (let i = 0; i < n; ++i) a[i] = 0;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
function normalX(angle, contrast) {
|
||||||
|
return Math.cos(angle) * contrast;
|
||||||
|
}
|
||||||
|
function normalY(angle) {
|
||||||
|
return Math.sin(angle);
|
||||||
|
}
|
||||||
|
function reverseKnotType(ty) {
|
||||||
|
return ty === "left" ? "right" : ty === "right" ? "left" : ty;
|
||||||
|
}
|
|
@ -1,191 +0,0 @@
|
||||||
import './monotonic-interpolate' as smooth
|
|
||||||
import './transform' as Transform
|
|
||||||
import './point' as Point
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
class SpiroExpansionContext
|
|
||||||
public [new] : begin
|
|
||||||
set this.gizmo [Transform.Id]
|
|
||||||
set this.controlKnots {}
|
|
||||||
set this.defaultd1 0
|
|
||||||
set this.defaultd2 0
|
|
||||||
|
|
||||||
public [beginShape] : begin
|
|
||||||
public [endShape] : begin
|
|
||||||
|
|
||||||
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 [setWidth 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 [headsTo direction] : begin
|
|
||||||
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
|
|
||||||
if lastKnot : begin
|
|
||||||
lastKnot.proposedNormal = direction
|
|
||||||
|
|
||||||
public [setType 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
|
|
||||||
|
|
||||||
# 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 : this.gizmo.unapply this.controlKnots.(jBefore)
|
|
||||||
local knotAfter : this.gizmo.unapply this.controlKnots.(jAfter)
|
|
||||||
local ref : this.gizmo.unapply knot
|
|
||||||
local lhsBefore : this.gizmo.unapply lhs.(jBefore)
|
|
||||||
local lhsAfter : this.gizmo.unapply lhs.(jAfter)
|
|
||||||
local rhsBefore : this.gizmo.unapply rhs.(jBefore)
|
|
||||||
local rhsAfter : this.gizmo.unapply rhs.(jAfter)
|
|
||||||
|
|
||||||
local kLHS : this.gizmo.apply : 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 : this.gizmo.apply : 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}
|
|
||||||
|
|
||||||
class SpiroContourContext
|
|
||||||
public [new gizmo] : begin
|
|
||||||
set this.gizmo (gizmo || [Transform.Id])
|
|
||||||
set this.contours { }
|
|
||||||
set this.defaultTag null
|
|
||||||
|
|
||||||
public [beginShape] : begin
|
|
||||||
|
|
||||||
public [endShape] : begin
|
|
||||||
|
|
||||||
public [moveTo x y] : begin
|
|
||||||
local contour {[Point.transformedXY this.gizmo x y true]}
|
|
||||||
set contour.tag this.defaultTag
|
|
||||||
this.contours.push contour
|
|
||||||
return this
|
|
||||||
|
|
||||||
public [lineTo x y] : begin
|
|
||||||
this.contours.((this.contours.length - 1)).push
|
|
||||||
Point.transformedXY this.gizmo x y true
|
|
||||||
return this
|
|
||||||
|
|
||||||
public [curveTo xc yc x y] : begin
|
|
||||||
this.contours.((this.contours.length - 1)).push
|
|
||||||
Point.transformedXY this.gizmo xc yc false
|
|
||||||
Point.transformedXY this.gizmo x y true
|
|
||||||
return this
|
|
||||||
|
|
||||||
public [cubicTo x1 y1 x2 y2 x y] : begin
|
|
||||||
this.contours.((this.contours.length - 1)).push
|
|
||||||
Point.transformedXY this.gizmo x1 y1 false true
|
|
||||||
Point.transformedXY this.gizmo x2 y2 false true
|
|
||||||
Point.transformedXY this.gizmo x y true
|
|
||||||
return this
|
|
||||||
|
|
||||||
export SpiroExpansionContext
|
|
||||||
export SpiroContourContext
|
|
|
@ -20,6 +20,12 @@ module.exports = class Transform {
|
||||||
y: pt.x * this.xy + pt.y * this.yy + this.y
|
y: pt.x * this.xy + pt.y * this.yy + this.y
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
applyOffset(delta) {
|
||||||
|
return {
|
||||||
|
x: delta.x * this.xx + delta.y * this.yx,
|
||||||
|
y: delta.x * this.xy + delta.y * this.yy
|
||||||
|
};
|
||||||
|
}
|
||||||
unapply(pt) {
|
unapply(pt) {
|
||||||
const xx = pt.x - this.x;
|
const xx = pt.x - this.x;
|
||||||
const yy = pt.y - this.y;
|
const yy = pt.y - this.y;
|
||||||
|
|
49
support/utils.js
Normal file
49
support/utils.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function mix(a, b, p) {
|
||||||
|
return a + (b - a) * p;
|
||||||
|
}
|
||||||
|
function ratio(l, r, m) {
|
||||||
|
return l === r ? 0 : (m - l) / (r - l);
|
||||||
|
}
|
||||||
|
function barmixL(l, r, b, p) {
|
||||||
|
return l > r ? barmixL(r, l, b, p) : l + b + p * (r - l - b * 3);
|
||||||
|
}
|
||||||
|
function barmixM(l, r, b, p) {
|
||||||
|
return barmixL(l, r, b, p) + b / 2;
|
||||||
|
}
|
||||||
|
function barMixR(l, r, b, p) {
|
||||||
|
return barMixR(l, r, b, p) + b;
|
||||||
|
}
|
||||||
|
function linreg(x0, y0, x1, y1, x) {
|
||||||
|
return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
|
||||||
|
}
|
||||||
|
function clamp(l, h, x) {
|
||||||
|
return x < l ? l : x > h ? h : x;
|
||||||
|
}
|
||||||
|
function fallback(...args) {
|
||||||
|
for (const item of args) if (item !== void 0) return item;
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
function bez2(a, b, c, t) {
|
||||||
|
return (1 - t) * (1 - t) * a + 2 * (1 - t) * t * b + t * t * c;
|
||||||
|
}
|
||||||
|
function bez3(a, b, c, d, t) {
|
||||||
|
return (
|
||||||
|
(1 - t) * (1 - t) * (1 - t) * a +
|
||||||
|
3 * (1 - t) * (1 - t) * t * b +
|
||||||
|
3 * t * t * (1 - t) * c +
|
||||||
|
t * t * t * d
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.mix = mix;
|
||||||
|
exports.ratio = ratio;
|
||||||
|
exports.barmixL = barmixL;
|
||||||
|
exports.barmixM = barmixM;
|
||||||
|
exports.barmixR = barMixR;
|
||||||
|
exports.linreg = linreg;
|
||||||
|
exports.clamp = clamp;
|
||||||
|
exports.fallback = fallback;
|
||||||
|
exports.bez2 = bez2;
|
||||||
|
exports.bez3 = bez3;
|
|
@ -1,12 +0,0 @@
|
||||||
import './anchor' as Anchor
|
|
||||||
|
|
||||||
export : define [mix a b p] : a + (b - a) * p
|
|
||||||
export : define [ratio l r m] : if [l === r] 0 ((m - l) / (r - l))
|
|
||||||
export : define [barmixL l r b p] : if (l > r) [barmixL r l b p] (l + b + p * (r - l - b * 3))
|
|
||||||
export : define [barmixM l r b p] : [barmixL l r b p] + b / 2
|
|
||||||
export : define [barmixR l r b p] : [barmixR l r b p] + b
|
|
||||||
export : define [linreg x0 y0 x1 y1 x] : y0 + (x - x0) * (y1 - y0) / (x1 - x0)
|
|
||||||
export : define [clamp l h x] : if (x < l) l : if (x > h) h x
|
|
||||||
export : define [fallback] : begin
|
|
||||||
for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)
|
|
||||||
return nothing
|
|
Loading…
Add table
Add a link
Reference in a new issue