Make first stage of dispiro expansion cachable
This commit is contained in:
parent
e7627df03c
commit
70f41352c1
9 changed files with 102 additions and 115 deletions
|
@ -4,7 +4,7 @@ const fs = require("fs-extra");
|
||||||
const zlib = require("zlib");
|
const zlib = require("zlib");
|
||||||
const { encode, decode } = require("@msgpack/msgpack");
|
const { encode, decode } = require("@msgpack/msgpack");
|
||||||
|
|
||||||
const Edition = 11;
|
const Edition = 12;
|
||||||
const MAX_AGE = 5;
|
const MAX_AGE = 5;
|
||||||
|
|
||||||
class GfEntry {
|
class GfEntry {
|
||||||
|
|
|
@ -565,13 +565,29 @@ glyph-block CommonShapes : begin
|
||||||
* keyKnot
|
* keyKnot
|
||||||
* segAfter
|
* segAfter
|
||||||
|
|
||||||
|
define [hookStartBlender before after args] : begin
|
||||||
|
return : HookShape after before true args.y args.tight args.sw args.swItalicAdj args.noAdjTerminalY
|
||||||
|
|
||||||
|
define [hookEndBlender before after args] : begin
|
||||||
|
return : HookShape before after false args.y args.tight args.sw args.swItalicAdj args.noAdjTerminalY
|
||||||
|
|
||||||
define [hookstart] : params [y tight sw swItalicAdj noAdjTerminalY] : return {
|
define [hookstart] : params [y tight sw swItalicAdj noAdjTerminalY] : return {
|
||||||
.type 'interpolate'
|
.type 'interpolate'
|
||||||
.af [lambda [before after] [HookShape after before true y tight sw swItalicAdj noAdjTerminalY]]
|
.blender hookStartBlender
|
||||||
|
.y y
|
||||||
|
.tight tight
|
||||||
|
.sw sw
|
||||||
|
.swItalicAdj swItalicAdj
|
||||||
|
.noAdjTerminalY noAdjTerminalY
|
||||||
}
|
}
|
||||||
define [hookend] : params [y tight sw swItalicAdj noAdjTerminalY] : return {
|
define [hookend] : params [y tight sw swItalicAdj noAdjTerminalY] : return {
|
||||||
.type 'interpolate'
|
.type 'interpolate'
|
||||||
.af [lambda [before after] [HookShape before after false y tight sw swItalicAdj noAdjTerminalY]]
|
.blender hookEndBlender
|
||||||
|
.y y
|
||||||
|
.tight tight
|
||||||
|
.sw sw
|
||||||
|
.swItalicAdj swItalicAdj
|
||||||
|
.noAdjTerminalY noAdjTerminalY
|
||||||
}
|
}
|
||||||
|
|
||||||
# Composite transformations
|
# Composite transformations
|
||||||
|
|
|
@ -123,7 +123,6 @@ glyph-block Letter-Latin-S : begin
|
||||||
hookend O (sw -- stroke) (swItalicAdj -- Stroke)
|
hookend O (sw -- stroke) (swItalicAdj -- Stroke)
|
||||||
g4 (SB - OX + SOBot) SHook
|
g4 (SB - OX + SOBot) SHook
|
||||||
|
|
||||||
glyph-block-export SStrokeAlt
|
|
||||||
define [SStrokeAlt] : params [top hook swStart swEnd oXLeftTop offsetLT offsetRB offsetC] : begin
|
define [SStrokeAlt] : params [top hook swStart swEnd oXLeftTop offsetLT offsetRB offsetC] : begin
|
||||||
define stroke : Math.max swStart swEnd
|
define stroke : Math.max swStart swEnd
|
||||||
define fine : Math.min swStart swEnd
|
define fine : Math.min swStart swEnd
|
||||||
|
|
|
@ -103,7 +103,8 @@ glyph-block Digits-Eight : begin
|
||||||
kty (Middle - CorrectionOMidS) (top - O) [widths.lhs stroke]
|
kty (Middle - CorrectionOMidS) (top - O) [widths.lhs stroke]
|
||||||
archv
|
archv
|
||||||
g4 [mix r l p] (top - [SmoothAOf (arch * p) Width])
|
g4 [mix r l p] (top - [SmoothAOf (arch * p) Width])
|
||||||
alsoThru.sNeck 0.96 0.3 stroke (2 / 3)
|
alsoThruThem { {0.34 0.45 (2 / 3)} {0.66 0.55 (1 / 3)} } : object
|
||||||
|
blend : function [rt] : widths (stroke * rt) (stroke * (1 - rt))
|
||||||
g4 r [SmoothAOf arch Width] [widths.rhs stroke]
|
g4 r [SmoothAOf arch Width] [widths.rhs stroke]
|
||||||
arcvh
|
arcvh
|
||||||
kty (Middle + CorrectionOMidS) (O)
|
kty (Middle + CorrectionOMidS) (O)
|
||||||
|
|
|
@ -136,31 +136,14 @@ exports.SetupBuilders = function (bindings) {
|
||||||
}
|
}
|
||||||
return innerKnots;
|
return innerKnots;
|
||||||
}
|
}
|
||||||
function afInterpolateSNeck(before, after, args) {
|
|
||||||
return [
|
|
||||||
g2(
|
|
||||||
mix(before.x, after.x, 1 / 2 - args.px / 6),
|
|
||||||
mix(before.y, after.y, 1 / 2 - args.py / 6),
|
|
||||||
widths(args.sw * args.ps, args.sw * (1 - args.ps))
|
|
||||||
),
|
|
||||||
g2(
|
|
||||||
mix(before.x, after.x, 1 / 2 + args.px / 6),
|
|
||||||
mix(before.y, after.y, 1 / 2 + args.py / 6),
|
|
||||||
widths(args.sw * (1 - args.ps), args.sw * args.ps)
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
function alsoThru(rx, ry, raf) {
|
function alsoThru(rx, ry, raf) {
|
||||||
return { type: "interpolate", rx, ry, raf, af: afInterpolate };
|
return { type: "interpolate", rx, ry, raf, blender: afInterpolate };
|
||||||
}
|
}
|
||||||
alsoThru.g2 = function (rx, ry, raf) {
|
alsoThru.g2 = function (rx, ry, raf) {
|
||||||
return { type: "interpolate", rx, ry, raf, af: afInterpolateG2 };
|
return { type: "interpolate", rx, ry, raf, blender: afInterpolateG2 };
|
||||||
};
|
|
||||||
alsoThru.sNeck = function (px, py, sw, ps) {
|
|
||||||
return { type: "interpolate", px, py, sw, ps, af: afInterpolateSNeck };
|
|
||||||
};
|
};
|
||||||
function alsoThruThem(es, raf, ty) {
|
function alsoThruThem(es, raf, ty) {
|
||||||
return { type: "interpolate", rs: es, raf, ty, af: afInterpolateThem };
|
return { type: "interpolate", rs: es, raf, ty, blender: afInterpolateThem };
|
||||||
}
|
}
|
||||||
function bezControlsImpl(x1, y1, x2, y2, samples, raf, ty) {
|
function bezControlsImpl(x1, y1, x2, y2, samples, raf, ty) {
|
||||||
let rs = [];
|
let rs = [];
|
||||||
|
@ -227,16 +210,6 @@ exports.SetupBuilders = function (bindings) {
|
||||||
arcvh.superness = function (s) {
|
arcvh.superness = function (s) {
|
||||||
return arcvh(DEFAULT_STEPS, s);
|
return arcvh(DEFAULT_STEPS, s);
|
||||||
};
|
};
|
||||||
function complexThru(...a) {
|
|
||||||
return {
|
|
||||||
type: "interpolate",
|
|
||||||
af: function (before, after, args) {
|
|
||||||
let ks = [];
|
|
||||||
for (const knot of a) ks.push(knot.af.call(this, before, after, knot));
|
|
||||||
return ks;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function flattenImpl(sink, knots) {
|
function flattenImpl(sink, knots) {
|
||||||
for (const p of knots) {
|
for (const p of knots) {
|
||||||
if (p instanceof Array) flattenImpl(sink, p);
|
if (p instanceof Array) flattenImpl(sink, p);
|
||||||
|
@ -254,7 +227,7 @@ exports.SetupBuilders = function (bindings) {
|
||||||
if (knots[j] && knots[j].type === "interpolate") {
|
if (knots[j] && knots[j].type === "interpolate") {
|
||||||
const kBefore = knots[nCyclic(j - 1, knots.length)];
|
const kBefore = knots[nCyclic(j - 1, knots.length)];
|
||||||
const kAfter = knots[nCyclic(j + 1, knots.length)];
|
const kAfter = knots[nCyclic(j + 1, knots.length)];
|
||||||
knots[j] = knots[j].af.call(s, kBefore, kAfter, knots[j]);
|
knots[j] = knots[j].blender(kBefore, kAfter, knots[j]);
|
||||||
unwrapped = true;
|
unwrapped = true;
|
||||||
}
|
}
|
||||||
if (unwrapped) return flatten(s, knots);
|
if (unwrapped) return flatten(s, knots);
|
||||||
|
@ -308,14 +281,10 @@ exports.SetupBuilders = function (bindings) {
|
||||||
const collector = new BiKnotCollector(gizmo, Contrast);
|
const collector = new BiKnotCollector(gizmo, Contrast);
|
||||||
const { knots, closed } = prepareSpiroKnots([].slice.call(args, 0), collector);
|
const { knots, closed } = prepareSpiroKnots([].slice.call(args, 0), collector);
|
||||||
for (const knot of knots) {
|
for (const knot of knots) {
|
||||||
const ty = knot.type;
|
collector.pushKnot(knot.type, knot.x, knot.y);
|
||||||
const af = knot.af;
|
if (knot.af) knot.af.call(collector);
|
||||||
knot.af = function () {
|
|
||||||
this.setType(ty);
|
|
||||||
return af ? af.apply(this, args) : void 0;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
SpiroJs.spiroToArcsOnContext(knots, closed, collector);
|
|
||||||
const dsp = new DiSpiroProxy(closed, collector, knots);
|
const dsp = new DiSpiroProxy(closed, collector, knots);
|
||||||
this.includeGeometry(dsp.geometry);
|
this.includeGeometry(dsp.geometry);
|
||||||
return dsp;
|
return dsp;
|
||||||
|
@ -350,7 +319,6 @@ exports.SetupBuilders = function (bindings) {
|
||||||
quadControls,
|
quadControls,
|
||||||
archv,
|
archv,
|
||||||
arcvh,
|
arcvh,
|
||||||
complexThru,
|
|
||||||
dispiro,
|
dispiro,
|
||||||
"spiro-outline": spiroOutline
|
"spiro-outline": spiroOutline
|
||||||
};
|
};
|
||||||
|
|
|
@ -222,7 +222,7 @@ define-macro glyph-block : syntax-rules
|
||||||
SmoothAdjust MidJutSide MidJutCenter compositeBaseAnchors YSmoothMidR YSmoothMidL]
|
SmoothAdjust MidJutSide MidJutCenter compositeBaseAnchors YSmoothMidR YSmoothMidL]
|
||||||
define spiroFnImports `[g4 g2 corner flat curl close end straight widths
|
define spiroFnImports `[g4 g2 corner flat curl close end straight widths
|
||||||
disable-contrast heading unimportant important alsoThru alsoThruThem bezControls
|
disable-contrast heading unimportant important alsoThru alsoThruThem bezControls
|
||||||
quadControls archv arcvh complexThru dispiro spiro-outline]
|
quadControls archv arcvh dispiro spiro-outline]
|
||||||
define booleFnImports `[union intersection difference]
|
define booleFnImports `[union intersection difference]
|
||||||
|
|
||||||
dirty `[$GlyphBlocks$.push : lambda [$Capture_Ext$] : begin \\
|
dirty `[$GlyphBlocks$.push : lambda [$Capture_Ext$] : begin \\
|
||||||
|
|
|
@ -153,6 +153,7 @@ class DiSpiroGeometry extends GeometryBase {
|
||||||
this.m_closed,
|
this.m_closed,
|
||||||
this.m_biKnots.map(k => k.clone())
|
this.m_biKnots.map(k => k.clone())
|
||||||
);
|
);
|
||||||
|
expander.initializeNormals();
|
||||||
expander.iterateNormals();
|
expander.iterateNormals();
|
||||||
expander.iterateNormals();
|
expander.iterateNormals();
|
||||||
this.m_cachedExpansionResults = expander.expand();
|
this.m_cachedExpansionResults = expander.expand();
|
||||||
|
|
|
@ -29,8 +29,8 @@ class BiKnot {
|
||||||
this.type,
|
this.type,
|
||||||
Format.n(this.x),
|
Format.n(this.x),
|
||||||
Format.n(this.y),
|
Format.n(this.y),
|
||||||
Format.n(this.d1),
|
this.d1 == null ? "" : Format.n(this.d1),
|
||||||
Format.n(this.d2),
|
this.d2 == null ? "" : Format.n(this.d2),
|
||||||
this.origTangent
|
this.origTangent
|
||||||
? Format.tuple(Format.n(this.origTangent.x), Format.n(this.origTangent.y))
|
? Format.tuple(Format.n(this.origTangent.x), Format.n(this.origTangent.y))
|
||||||
: "",
|
: "",
|
||||||
|
@ -52,29 +52,16 @@ class BiKnotCollector {
|
||||||
this.defaultD2 = 0;
|
this.defaultD2 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
beginShape() {}
|
pushKnot(type, x, y) {
|
||||||
endShape() {}
|
const tfZ = this.gizmo.applyXY(x, y);
|
||||||
moveTo(x, y, unimportant) {
|
|
||||||
if (unimportant) return;
|
|
||||||
if (!isFinite(x) || !isFinite(y)) throw new Error("NaN detected.");
|
|
||||||
const tfZ = this.gizmo.apply({ x, y });
|
|
||||||
this.controlKnots.push(new BiKnot("g2", tfZ.x, tfZ.y, this.defaultD1, this.defaultD2));
|
|
||||||
}
|
|
||||||
arcTo(arc, x, y) {
|
|
||||||
if (!isFinite(x) || !isFinite(y)) throw new Error("NaN detected.");
|
|
||||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||||
if (!k0) throw new Error("Unreachable: lineTo called before moveTo");
|
if (k0) {
|
||||||
if (k0.origTangent == null) {
|
this.controlKnots.push(new BiKnot(type, tfZ.x, tfZ.y, k0.d1, k0.d2));
|
||||||
k0.origTangent = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
} else {
|
||||||
}
|
this.controlKnots.push(new BiKnot(type, tfZ.x, tfZ.y, this.defaultD1, this.defaultD2));
|
||||||
{
|
|
||||||
const tfDerive1 = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
|
||||||
const tfZ = this.gizmo.apply({ x, y });
|
|
||||||
const bz = new BiKnot("g2", tfZ.x, tfZ.y, k0.d1, k0.d2);
|
|
||||||
bz.origTangent = tfDerive1;
|
|
||||||
this.controlKnots.push(bz);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setWidth(l, r) {
|
setWidth(l, r) {
|
||||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||||
if (k0) {
|
if (k0) {
|
||||||
|
@ -105,16 +92,20 @@ class SpiroExpander {
|
||||||
this.controlKnots = cks;
|
this.controlKnots = cks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeNormals() {
|
||||||
|
const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo);
|
||||||
|
SpiroJs.spiroToArcsOnContext(this.controlKnots, this.closed, normalRectifier);
|
||||||
|
}
|
||||||
|
|
||||||
iterateNormals() {
|
iterateNormals() {
|
||||||
const centerBone = this.getPass2Knots();
|
const centerBone = this.getPass2Knots();
|
||||||
const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo);
|
const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo);
|
||||||
SpiroJs.spiroToArcsOnContext(centerBone, this.closed, normalRectifier);
|
SpiroJs.spiroToArcsOnContext(centerBone, this.closed, normalRectifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPass2Knots() {
|
getPass2Knots() {
|
||||||
const expanded = this.expand(this.contrast);
|
const expanded = this.expand(this.contrast);
|
||||||
const middles = [];
|
const middles = [];
|
||||||
for (let j = 0; j + (this.closed ? 1 : 0) < this.controlKnots.length; j++) {
|
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||||
const lhs = this.gizmo.unapply(expanded.lhs[j]);
|
const lhs = this.gizmo.unapply(expanded.lhs[j]);
|
||||||
const rhs = this.gizmo.unapply(expanded.rhs[j]);
|
const rhs = this.gizmo.unapply(expanded.rhs[j]);
|
||||||
middles[j] = {
|
middles[j] = {
|
||||||
|
@ -130,6 +121,22 @@ class SpiroExpander {
|
||||||
expand() {
|
expand() {
|
||||||
const lhs = [],
|
const lhs = [],
|
||||||
rhs = [];
|
rhs = [];
|
||||||
|
// Initialize knots
|
||||||
|
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||||
|
const knot = this.controlKnots[j];
|
||||||
|
lhs[j] = {
|
||||||
|
type: knot.type,
|
||||||
|
unimportant: knot.unimportant,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
rhs[j] = {
|
||||||
|
type: reverseKnotType(knot.type),
|
||||||
|
unimportant: knot.unimportant,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Create important knots
|
// Create important knots
|
||||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||||
|
@ -144,52 +151,43 @@ class SpiroExpander {
|
||||||
dx = normalX(knot.origTangent, this.contrast);
|
dx = normalX(knot.origTangent, this.contrast);
|
||||||
dy = normalY(knot.origTangent, this.contrast);
|
dy = normalY(knot.origTangent, this.contrast);
|
||||||
}
|
}
|
||||||
lhs[j] = {
|
lhs[j].x = knot.x + knot.d1 * dx;
|
||||||
type: knot.type,
|
lhs[j].y = knot.y + knot.d1 * dy;
|
||||||
x: knot.x + knot.d1 * dx,
|
|
||||||
y: knot.y + knot.d1 * dy
|
rhs[j].x = knot.x - knot.d2 * dx;
|
||||||
};
|
rhs[j].y = knot.y - knot.d2 * dy;
|
||||||
rhs[j] = {
|
|
||||||
type: reverseKnotType(knot.type),
|
|
||||||
x: knot.x - knot.d2 * dx,
|
|
||||||
y: knot.y - knot.d2 * dy
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
this.interpolateUnimportantKnots(lhs, rhs);
|
this.interpolateUnimportantKnots(lhs, rhs);
|
||||||
return { lhs, rhs };
|
return { lhs, rhs };
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateUnimportantKnots(lhs, rhs) {
|
interpolateUnimportantKnots(lhs, rhs) {
|
||||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||||
const knot = this.controlKnots[j];
|
const knot = this.controlKnots[j];
|
||||||
if (!knot.unimportant) continue;
|
if (!knot.unimportant) continue;
|
||||||
let jBefore, jAfter;
|
let jBefore, jAfter;
|
||||||
for (jBefore = j - 1; this.controlKnots[jBefore].unimportant; jBefore--);
|
for (jBefore = j - 1; cyNth(this.controlKnots, jBefore).unimportant; jBefore--);
|
||||||
for (jAfter = j + 1; this.controlKnots[jAfter].unimportant; jAfter++);
|
for (jAfter = j + 1; cyNth(this.controlKnots, jAfter).unimportant; jAfter++);
|
||||||
|
|
||||||
const knotBefore = this.gizmo.unapply(this.controlKnots[jBefore]),
|
const knotBefore = this.gizmo.unapply(cyNth(this.controlKnots, jBefore)),
|
||||||
knotAfter = this.gizmo.unapply(this.controlKnots[jAfter]),
|
knotAfter = this.gizmo.unapply(cyNth(this.controlKnots, jAfter)),
|
||||||
ref = this.gizmo.unapply(knot),
|
ref = this.gizmo.unapply(knot),
|
||||||
lhsBefore = this.gizmo.unapply(lhs[jBefore]),
|
lhsBefore = this.gizmo.unapply(cyNth(lhs, jBefore)),
|
||||||
lhsAfter = this.gizmo.unapply(lhs[jAfter]),
|
lhsAfter = this.gizmo.unapply(cyNth(lhs, jAfter)),
|
||||||
rhsBefore = this.gizmo.unapply(rhs[jBefore]),
|
rhsBefore = this.gizmo.unapply(cyNth(rhs, jBefore)),
|
||||||
rhsAfter = this.gizmo.unapply(rhs[jAfter]);
|
rhsAfter = this.gizmo.unapply(cyNth(rhs, jAfter));
|
||||||
|
|
||||||
lhs[j] = {
|
const lhsTf = this.gizmo.applyXY(
|
||||||
unimportant: knot.unimportant,
|
linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
||||||
type: knot.type,
|
linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y)
|
||||||
...this.gizmo.apply({
|
);
|
||||||
x: linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
const rhsTf = this.gizmo.applyXY(
|
||||||
y: linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y)
|
linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x),
|
||||||
})
|
linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y)
|
||||||
};
|
);
|
||||||
rhs[j] = {
|
|
||||||
unimportant: knot.unimportant,
|
(lhs[j].x = lhsTf.x), (lhs[j].y = lhsTf.y);
|
||||||
type: reverseKnotType(knot.type),
|
(rhs[j].x = rhsTf.x), (rhs[j].y = rhsTf.y);
|
||||||
...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)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,18 +201,17 @@ class NormalRectifier {
|
||||||
|
|
||||||
beginShape() {}
|
beginShape() {}
|
||||||
endShape() {}
|
endShape() {}
|
||||||
moveTo(x, y, unimportant) {
|
moveTo(x, y) {
|
||||||
if (unimportant) return;
|
|
||||||
this.nKnotsProcessed += 1;
|
this.nKnotsProcessed += 1;
|
||||||
}
|
}
|
||||||
arcTo(arc, x, y) {
|
arcTo(arc, x, y) {
|
||||||
if (this.nKnotsProcessed === 1) {
|
if (this.nKnotsProcessed === 1) {
|
||||||
const d = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
const d = this.gizmo.applyOffsetXY(arc.deriveX0, arc.deriveY0);
|
||||||
if (isTangentValid(d)) this.controlKnots[0].origTangent = d;
|
if (isTangentValid(d)) this.controlKnots[0].origTangent = d;
|
||||||
else throw new Error("NaN angle detected.");
|
else throw new Error("NaN angle detected.");
|
||||||
}
|
}
|
||||||
{
|
if (this.controlKnots[this.nKnotsProcessed]) {
|
||||||
const d = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
const d = this.gizmo.applyOffsetXY(arc.deriveX1, arc.deriveY1);
|
||||||
if (isTangentValid(d)) this.controlKnots[this.nKnotsProcessed].origTangent = d;
|
if (isTangentValid(d)) this.controlKnots[this.nKnotsProcessed].origTangent = d;
|
||||||
else throw new Error("NaN angle detected.");
|
else throw new Error("NaN angle detected.");
|
||||||
}
|
}
|
||||||
|
@ -234,9 +231,8 @@ function normalY(tangent) {
|
||||||
function reverseKnotType(ty) {
|
function reverseKnotType(ty) {
|
||||||
return ty === "left" ? "right" : ty === "right" ? "left" : ty;
|
return ty === "left" ? "right" : ty === "right" ? "left" : ty;
|
||||||
}
|
}
|
||||||
function computeNormalAngle(gizmo, x, y) {
|
function cyNth(a, j) {
|
||||||
const tfd = gizmo.applyOffset({ x, y });
|
return a[j % a.length];
|
||||||
return Math.PI / 2 + Math.atan2(tfd.y, tfd.x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.BiKnotCollector = BiKnotCollector;
|
exports.BiKnotCollector = BiKnotCollector;
|
||||||
|
|
|
@ -19,15 +19,21 @@ module.exports = class Transform {
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(pt) {
|
apply(pt) {
|
||||||
|
return this.applyXY(pt.x, pt.y);
|
||||||
|
}
|
||||||
|
applyXY(x, y) {
|
||||||
return {
|
return {
|
||||||
x: pt.x * this.xx + pt.y * this.yx + this.x,
|
x: x * this.xx + y * this.yx + this.x,
|
||||||
y: pt.x * this.xy + pt.y * this.yy + this.y
|
y: x * this.xy + y * this.yy + this.y
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
applyOffset(delta) {
|
applyOffset(delta) {
|
||||||
|
return this.applyOffsetXY(delta.x, delta.y);
|
||||||
|
}
|
||||||
|
applyOffsetXY(deltaX, deltaY) {
|
||||||
return {
|
return {
|
||||||
x: delta.x * this.xx + delta.y * this.yx,
|
x: deltaX * this.xx + deltaY * this.yx,
|
||||||
y: delta.x * this.xy + delta.y * this.yy
|
y: deltaX * this.xy + deltaY * this.yy
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
unapply(pt) {
|
unapply(pt) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue