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
|
@ -153,6 +153,7 @@ class DiSpiroGeometry extends GeometryBase {
|
|||
this.m_closed,
|
||||
this.m_biKnots.map(k => k.clone())
|
||||
);
|
||||
expander.initializeNormals();
|
||||
expander.iterateNormals();
|
||||
expander.iterateNormals();
|
||||
this.m_cachedExpansionResults = expander.expand();
|
||||
|
|
|
@ -29,8 +29,8 @@ class BiKnot {
|
|||
this.type,
|
||||
Format.n(this.x),
|
||||
Format.n(this.y),
|
||||
Format.n(this.d1),
|
||||
Format.n(this.d2),
|
||||
this.d1 == null ? "" : Format.n(this.d1),
|
||||
this.d2 == null ? "" : Format.n(this.d2),
|
||||
this.origTangent
|
||||
? Format.tuple(Format.n(this.origTangent.x), Format.n(this.origTangent.y))
|
||||
: "",
|
||||
|
@ -52,29 +52,16 @@ class BiKnotCollector {
|
|||
this.defaultD2 = 0;
|
||||
}
|
||||
|
||||
beginShape() {}
|
||||
endShape() {}
|
||||
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.");
|
||||
pushKnot(type, x, y) {
|
||||
const tfZ = this.gizmo.applyXY(x, y);
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (!k0) throw new Error("Unreachable: lineTo called before moveTo");
|
||||
if (k0.origTangent == null) {
|
||||
k0.origTangent = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
||||
}
|
||||
{
|
||||
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);
|
||||
if (k0) {
|
||||
this.controlKnots.push(new BiKnot(type, tfZ.x, tfZ.y, k0.d1, k0.d2));
|
||||
} else {
|
||||
this.controlKnots.push(new BiKnot(type, tfZ.x, tfZ.y, this.defaultD1, this.defaultD2));
|
||||
}
|
||||
}
|
||||
|
||||
setWidth(l, r) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (k0) {
|
||||
|
@ -105,16 +92,20 @@ class SpiroExpander {
|
|||
this.controlKnots = cks;
|
||||
}
|
||||
|
||||
initializeNormals() {
|
||||
const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo);
|
||||
SpiroJs.spiroToArcsOnContext(this.controlKnots, this.closed, normalRectifier);
|
||||
}
|
||||
|
||||
iterateNormals() {
|
||||
const centerBone = this.getPass2Knots();
|
||||
const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo);
|
||||
SpiroJs.spiroToArcsOnContext(centerBone, this.closed, normalRectifier);
|
||||
}
|
||||
|
||||
getPass2Knots() {
|
||||
const expanded = this.expand(this.contrast);
|
||||
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 rhs = this.gizmo.unapply(expanded.rhs[j]);
|
||||
middles[j] = {
|
||||
|
@ -130,6 +121,22 @@ class SpiroExpander {
|
|||
expand() {
|
||||
const lhs = [],
|
||||
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
|
||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||
|
@ -144,52 +151,43 @@ class SpiroExpander {
|
|||
dx = normalX(knot.origTangent, this.contrast);
|
||||
dy = normalY(knot.origTangent, this.contrast);
|
||||
}
|
||||
lhs[j] = {
|
||||
type: knot.type,
|
||||
x: knot.x + knot.d1 * dx,
|
||||
y: knot.y + knot.d1 * dy
|
||||
};
|
||||
rhs[j] = {
|
||||
type: reverseKnotType(knot.type),
|
||||
x: knot.x - knot.d2 * dx,
|
||||
y: knot.y - knot.d2 * dy
|
||||
};
|
||||
lhs[j].x = knot.x + knot.d1 * dx;
|
||||
lhs[j].y = knot.y + knot.d1 * dy;
|
||||
|
||||
rhs[j].x = knot.x - knot.d2 * dx;
|
||||
rhs[j].y = knot.y - knot.d2 * dy;
|
||||
}
|
||||
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++);
|
||||
for (jBefore = j - 1; cyNth(this.controlKnots, jBefore).unimportant; jBefore--);
|
||||
for (jAfter = j + 1; cyNth(this.controlKnots, jAfter).unimportant; jAfter++);
|
||||
|
||||
const knotBefore = this.gizmo.unapply(this.controlKnots[jBefore]),
|
||||
knotAfter = this.gizmo.unapply(this.controlKnots[jAfter]),
|
||||
const knotBefore = this.gizmo.unapply(cyNth(this.controlKnots, jBefore)),
|
||||
knotAfter = this.gizmo.unapply(cyNth(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]);
|
||||
lhsBefore = this.gizmo.unapply(cyNth(lhs, jBefore)),
|
||||
lhsAfter = this.gizmo.unapply(cyNth(lhs, jAfter)),
|
||||
rhsBefore = this.gizmo.unapply(cyNth(rhs, jBefore)),
|
||||
rhsAfter = this.gizmo.unapply(cyNth(rhs, jAfter));
|
||||
|
||||
lhs[j] = {
|
||||
unimportant: knot.unimportant,
|
||||
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] = {
|
||||
unimportant: knot.unimportant,
|
||||
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)
|
||||
})
|
||||
};
|
||||
const lhsTf = this.gizmo.applyXY(
|
||||
linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
||||
linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y)
|
||||
);
|
||||
const rhsTf = this.gizmo.applyXY(
|
||||
linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x),
|
||||
linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y)
|
||||
);
|
||||
|
||||
(lhs[j].x = lhsTf.x), (lhs[j].y = lhsTf.y);
|
||||
(rhs[j].x = rhsTf.x), (rhs[j].y = rhsTf.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,18 +201,17 @@ class NormalRectifier {
|
|||
|
||||
beginShape() {}
|
||||
endShape() {}
|
||||
moveTo(x, y, unimportant) {
|
||||
if (unimportant) return;
|
||||
moveTo(x, y) {
|
||||
this.nKnotsProcessed += 1;
|
||||
}
|
||||
arcTo(arc, x, y) {
|
||||
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;
|
||||
else throw new Error("NaN angle detected.");
|
||||
}
|
||||
{
|
||||
const d = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
||||
if (this.controlKnots[this.nKnotsProcessed]) {
|
||||
const d = this.gizmo.applyOffsetXY(arc.deriveX1, arc.deriveY1);
|
||||
if (isTangentValid(d)) this.controlKnots[this.nKnotsProcessed].origTangent = d;
|
||||
else throw new Error("NaN angle detected.");
|
||||
}
|
||||
|
@ -234,9 +231,8 @@ function normalY(tangent) {
|
|||
function reverseKnotType(ty) {
|
||||
return ty === "left" ? "right" : ty === "right" ? "left" : ty;
|
||||
}
|
||||
function computeNormalAngle(gizmo, x, y) {
|
||||
const tfd = gizmo.applyOffset({ x, y });
|
||||
return Math.PI / 2 + Math.atan2(tfd.y, tfd.x);
|
||||
function cyNth(a, j) {
|
||||
return a[j % a.length];
|
||||
}
|
||||
|
||||
exports.BiKnotCollector = BiKnotCollector;
|
||||
|
|
|
@ -19,15 +19,21 @@ module.exports = class Transform {
|
|||
}
|
||||
|
||||
apply(pt) {
|
||||
return this.applyXY(pt.x, pt.y);
|
||||
}
|
||||
applyXY(x, y) {
|
||||
return {
|
||||
x: pt.x * this.xx + pt.y * this.yx + this.x,
|
||||
y: pt.x * this.xy + pt.y * this.yy + this.y
|
||||
x: x * this.xx + y * this.yx + this.x,
|
||||
y: x * this.xy + y * this.yy + this.y
|
||||
};
|
||||
}
|
||||
applyOffset(delta) {
|
||||
return this.applyOffsetXY(delta.x, delta.y);
|
||||
}
|
||||
applyOffsetXY(deltaX, deltaY) {
|
||||
return {
|
||||
x: delta.x * this.xx + delta.y * this.yx,
|
||||
y: delta.x * this.xy + delta.y * this.yy
|
||||
x: deltaX * this.xx + deltaY * this.yx,
|
||||
y: deltaX * this.xy + deltaY * this.yy
|
||||
};
|
||||
}
|
||||
unapply(pt) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue