Make dispiro results cachable

This commit is contained in:
be5invis 2021-07-11 18:30:29 -07:00
parent f892a56532
commit 7bf55f2682
6 changed files with 211 additions and 73 deletions

View file

@ -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 = 10; const Edition = 11;
const MAX_AGE = 5; const MAX_AGE = 5;
class GfEntry { class GfEntry {

View file

@ -3,9 +3,9 @@
const SpiroJs = require("spiro"); const SpiroJs = require("spiro");
const CurveUtil = require("../support/curve-util"); const CurveUtil = require("../support/curve-util");
const Transform = require("../support/transform"); const Transform = require("../support/transform");
const { SpiroExpansionContext1, SpiroExpansionContext2 } = require("../support/spiro-expand"); const { BiKnotCollector } = require("../support/spiro-expand");
const { fallback, mix, bez2, bez3 } = require("../support/utils"); const { fallback, mix, bez2, bez3 } = require("../support/utils");
const { SpiroGeometry, CombineGeometry } = require("../support/geometry"); const { SpiroGeometry, DiSpiroGeometry, CombineGeometry } = require("../support/geometry");
exports.SetupBuilders = function (bindings) { exports.SetupBuilders = function (bindings) {
const { Contrast, GlobalTransform, Stroke, Superness } = bindings; const { Contrast, GlobalTransform, Stroke, Superness } = bindings;
@ -279,15 +279,34 @@ exports.SetupBuilders = function (bindings) {
knots = flatten(s, knots); knots = flatten(s, knots);
return { knots, closed }; return { knots, closed };
} }
function iterateNormals(s, closed) {
let knotsP2 = s.getPass2Knots(closed, fallback(s.contrast, Contrast)); class DiSpiroProxy {
let s2 = new SpiroExpansionContext2(s.controlKnots, s.gizmo); constructor(closed, collector, origKnots) {
return SpiroJs.spiroToArcsOnContext(knotsP2, closed, s2); this.geometry = new DiSpiroGeometry(
collector.gizmo,
collector.contrast,
closed,
collector.controlKnots
);
this.m_origKnots = origKnots;
}
get knots() {
return this.m_origKnots;
}
get lhsKnots() {
return this.geometry.expand().lhs;
}
get rhsKnots() {
return this.geometry.expand().rhs;
}
} }
function dispiro(...args) { function dispiro(...args) {
return function () { return function () {
let s = new SpiroExpansionContext1(this.gizmo || GlobalTransform); const gizmo = this.gizmo || GlobalTransform;
let { knots, closed } = prepareSpiroKnots([].slice.call(args, 0), s); const collector = new BiKnotCollector(gizmo, Contrast);
const { knots, closed } = prepareSpiroKnots([].slice.call(args, 0), collector);
for (const knot of knots) { for (const knot of knots) {
const ty = knot.type; const ty = knot.type;
const af = knot.af; const af = knot.af;
@ -296,26 +315,10 @@ exports.SetupBuilders = function (bindings) {
return af ? af.apply(this, args) : void 0; return af ? af.apply(this, args) : void 0;
}; };
} }
SpiroJs.spiroToArcsOnContext(knots, closed, collector);
SpiroJs.spiroToArcsOnContext(knots, closed, s); const dsp = new DiSpiroProxy(closed, collector, knots);
iterateNormals(s, closed); this.includeGeometry(dsp.geometry);
iterateNormals(s, closed); return dsp;
const { lhs, rhs } = s.expand(fallback(s.contrast, Contrast));
if (closed) {
this.includeGeometry(
new CombineGeometry([
new SpiroGeometry(lhs.slice(0, -1), closed, Transform.Id()),
new SpiroGeometry(rhs.reverse().slice(0, -1), closed, Transform.Id())
])
);
} else {
lhs[0].type = lhs[lhs.length - 1].type = "corner";
rhs[0].type = rhs[rhs.length - 1].type = "corner";
const allKnots = lhs.concat(rhs.reverse());
this.includeGeometry(new SpiroGeometry(allKnots, true, Transform.Id()));
}
return { knots, lhsKnots: lhs, rhsKnots: rhs };
}; };
} }
@ -324,7 +327,7 @@ exports.SetupBuilders = function (bindings) {
const gizmo = this.gizmo || GlobalTransform; const gizmo = this.gizmo || GlobalTransform;
const g = new CurveUtil.BezToContoursSink(gizmo); const g = new CurveUtil.BezToContoursSink(gizmo);
const { knots, closed } = prepareSpiroKnots(args, g); const { knots, closed } = prepareSpiroKnots(args, g);
return this.includeGeometry(new SpiroGeometry(knots, closed, gizmo)); return this.includeGeometry(new SpiroGeometry(gizmo, closed, knots));
}; };
} }
return { return {

View file

@ -1,3 +1,5 @@
"use strict";
const { mix } = require("./utils"); const { mix } = require("./utils");
class Box { class Box {

View file

@ -0,0 +1,29 @@
"use strict";
function struct(leader, ...items) {
return "" + leader + "(" + items.join(";") + ")";
}
function tuple(...items) {
return "(" + items.join(";") + ")";
}
function list(items) {
return "{" + items.join(";") + "}";
}
function n(x) {
return String(Math.round(x * 0x10000));
}
function typedPoint(z) {
return tuple(z.type, n(z.x), n(z.y));
}
function gizmo(g) {
return tuple(n(g.xx), n(g.xy), n(g.yx), n(g.yy), n(g.x), n(g.y));
}
exports.struct = struct;
exports.tuple = tuple;
exports.list = list;
exports.n = n;
exports.typedPoint = typedPoint;
exports.gizmo = gizmo;

View file

@ -7,6 +7,8 @@ const SpiroJs = require("spiro");
const Point = require("./point"); const Point = require("./point");
const Transform = require("./transform"); const Transform = require("./transform");
const CurveUtil = require("./curve-util"); const CurveUtil = require("./curve-util");
const { SpiroExpander } = require("./spiro-expand");
const Format = require("./formatter");
class GeometryBase { class GeometryBase {
asContours() { asContours() {
@ -62,17 +64,12 @@ class ContourGeometry extends GeometryBase {
return this.m_points.length; return this.m_points.length;
} }
toShapeStringOrNull() { toShapeStringOrNull() {
let s = "ContourGeometry{"; return Format.struct(`ContourGeometry`, Format.list(this.m_points.map(Format.typedPoint)));
for (const z of this.m_points) {
s += `(${z.type};${formatN(z.x)};${formatN(z.y)})`;
}
s += "}";
return s;
} }
} }
class SpiroGeometry extends GeometryBase { class SpiroGeometry extends GeometryBase {
constructor(knots, closed, gizmo) { constructor(gizmo, closed, knots) {
super(); super();
this.m_knots = []; this.m_knots = [];
for (const k of knots) { for (const k of knots) {
@ -105,18 +102,86 @@ class SpiroGeometry extends GeometryBase {
return this.m_knots.length; return this.m_knots.length;
} }
toShapeStringOrNull() { toShapeStringOrNull() {
let s = "SpiroGeometry{{"; return Format.struct(
for (const k of this.m_knots) { "SpiroGeometry",
s += `(${k.type};${formatN(k.x)};${formatN(k.y)})`; Format.gizmo(this.m_gizmo),
this.m_closed,
Format.list(this.m_knots.map(Format.typedPoint))
);
}
}
class DiSpiroGeometry extends GeometryBase {
constructor(gizmo, contrast, closed, biKnots) {
super();
this.m_biKnots = [];
for (const k of biKnots) this.m_biKnots.push(k.clone());
this.m_closed = closed;
this.m_gizmo = gizmo;
this.m_contrast = contrast;
this.m_cachedExpansionResults = null;
this.m_cachedContours = null;
}
asContours() {
if (this.m_cachedContours) return this.m_cachedContours;
const { lhs, rhs } = this.expand();
let rawGeometry;
if (this.m_closed) {
rawGeometry = new CombineGeometry([
new SpiroGeometry(Transform.Id(), this.m_closed, lhs.slice(0, -1)),
new SpiroGeometry(Transform.Id(), this.m_closed, rhs.reverse().slice(0, -1))
]);
} else {
lhs[0].type = lhs[lhs.length - 1].type = "corner";
rhs[0].type = rhs[rhs.length - 1].type = "corner";
const allKnots = lhs.concat(rhs.reverse());
rawGeometry = new SpiroGeometry(Transform.Id(), true, allKnots);
} }
s += "};"; this.m_cachedContours = rawGeometry.asContours();
s += `${this.m_closed};`; return this.m_cachedContours;
s += }
`;{${formatN(this.m_gizmo.xx)},${formatN(this.m_gizmo.xy)},` + expand() {
`${formatN(this.m_gizmo.yx)},${formatN(this.m_gizmo.yy)},` + if (this.m_cachedExpansionResults) return this.m_cachedExpansionResults;
`${formatN(this.m_gizmo.x)},${formatN(this.m_gizmo.y)}}`; const expander = new SpiroExpander(
s += "}"; this.m_gizmo,
return s; this.m_contrast,
this.m_closed,
this.m_biKnots.map(k => k.clone())
);
expander.iterateNormals();
expander.iterateNormals();
this.m_cachedExpansionResults = expander.expand();
return this.m_cachedExpansionResults;
}
asReferences() {
return null;
}
filterTag(fn) {
return this;
}
isEmpty() {
return !this.m_biKnots.length;
}
measureComplexity() {
for (const z of this.m_biKnots) {
if (!isFinite(z.x) || !isFinite(z.y)) return 0xffff;
}
return this.m_biKnots.length;
}
toShapeStringOrNull() {
return Format.struct(
Format.gizmo(this.m_gizmo),
Format.n(this.m_contrast),
this.m_closed,
Format.list(this.m_biKnots.map(z => z.toShapeString()))
);
} }
} }
@ -159,7 +224,7 @@ class ReferenceGeometry extends GeometryBase {
toShapeStringOrNull() { toShapeStringOrNull() {
let sTarget = this.m_glyph.geometry.toShapeStringOrNull(); let sTarget = this.m_glyph.geometry.toShapeStringOrNull();
if (!sTarget) return null; if (!sTarget) return null;
return `ReferenceGeometry{${sTarget};${formatN(this.m_x)};${formatN(this.m_y)}}`; return Format.struct("ReferenceGeometry", sTarget, Format.n(this.m_x), Format.n(this.m_y));
} }
} }
@ -252,13 +317,7 @@ class TransformedGeometry extends GeometryBase {
toShapeStringOrNull() { toShapeStringOrNull() {
const sTarget = this.m_geom.toShapeStringOrNull(); const sTarget = this.m_geom.toShapeStringOrNull();
if (!sTarget) return null; if (!sTarget) return null;
return ( return Format.struct(TransformedGeometry, sTarget, Format.gizmo(this.m_transform));
`TransformedGeometry{${sTarget};` +
`${formatN(this.m_transform.xx)},${formatN(this.m_transform.xy)},` +
`${formatN(this.m_transform.yx)},${formatN(this.m_transform.yy)},` +
`${formatN(this.m_transform.x)},${formatN(this.m_transform.y)}` +
`}`
);
} }
} }
@ -330,7 +389,7 @@ class CombineGeometry extends GeometryBase {
if (!sPart) return null; if (!sPart) return null;
sParts.push(sPart); sParts.push(sPart);
} }
return `CombineGeometry{${sParts.join(",")}}`; return Format.struct("CombineGeometry", Format.list(sParts));
} }
} }
@ -407,7 +466,7 @@ class BooleanGeometry extends GeometryBase {
if (!sPart) return null; if (!sPart) return null;
sParts.push(sPart); sParts.push(sPart);
} }
return `BooleanGeometry{${this.m_operator};${sParts.join(",")}}`; return Format.struct("BooleanGeometry", this.m_operator, Format.list(sParts));
} }
} }
@ -425,12 +484,9 @@ function combineWith(a, b) {
} }
} }
function formatN(x) {
return `${Math.round(x * 0x10000)}`;
}
exports.GeometryBase = GeometryBase; exports.GeometryBase = GeometryBase;
exports.SpiroGeometry = SpiroGeometry; exports.SpiroGeometry = SpiroGeometry;
exports.DiSpiroGeometry = DiSpiroGeometry;
exports.ContourGeometry = ContourGeometry; exports.ContourGeometry = ContourGeometry;
exports.ReferenceGeometry = ReferenceGeometry; exports.ReferenceGeometry = ReferenceGeometry;
exports.TaggedGeometry = TaggedGeometry; exports.TaggedGeometry = TaggedGeometry;

View file

@ -1,4 +1,9 @@
"use strict";
const SpiroJs = require("spiro");
const { linreg } = require("./utils"); const { linreg } = require("./utils");
const Format = require("./formatter");
class BiKnot { class BiKnot {
constructor(type, x, y, d1, d2) { constructor(type, x, y, d1, d2) {
@ -11,15 +16,42 @@ class BiKnot {
this.proposedNormal = null; this.proposedNormal = null;
this.unimportant = 0; this.unimportant = 0;
} }
clone() {
const k1 = new BiKnot(this.type, this.x, this.y, this.d1, this.d2);
k1.origTangent = this.origTangent;
k1.proposedNormal = this.proposedNormal;
k1.unimportant = this.unimportant;
return k1;
}
toShapeString() {
return Format.tuple(
this.type,
Format.n(this.x),
Format.n(this.y),
Format.n(this.d1),
Format.n(this.d2),
this.origTangent
? Format.tuple(Format.n(this.origTangent.x), Format.n(this.origTangent.y))
: "",
this.proposedNormal
? Format.tuple(Format.n(this.proposedNormal.x), Format.n(this.proposedNormal.y))
: "",
this.unimportant
);
}
} }
class SpiroExpansionContext1 { class BiKnotCollector {
constructor(gizmo) { constructor(gizmo, contrast) {
this.gizmo = gizmo; this.gizmo = gizmo;
this.contrast = contrast;
this.controlKnots = []; this.controlKnots = [];
this.defaultD1 = 0; this.defaultD1 = 0;
this.defaultD2 = 0; this.defaultD2 = 0;
} }
beginShape() {} beginShape() {}
endShape() {} endShape() {}
moveTo(x, y, unimportant) { moveTo(x, y, unimportant) {
@ -63,11 +95,26 @@ class SpiroExpansionContext1 {
const k0 = this.controlKnots[this.controlKnots.length - 1]; const k0 = this.controlKnots[this.controlKnots.length - 1];
if (k0) k0.unimportant = 1; if (k0) k0.unimportant = 1;
} }
}
getPass2Knots(closed, contrast) { class SpiroExpander {
const expanded = this.expand(contrast); constructor(gizmo, contrast, closed, cks) {
this.gizmo = gizmo;
this.contrast = contrast;
this.closed = closed;
this.controlKnots = cks;
}
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 = []; const middles = [];
for (let j = 0; j + (closed ? 1 : 0) < this.controlKnots.length; j++) { for (let j = 0; j + (this.closed ? 1 : 0) < 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] = {
@ -79,7 +126,8 @@ class SpiroExpansionContext1 {
} }
return middles; return middles;
} }
expand(contrast) {
expand() {
const lhs = [], const lhs = [],
rhs = []; rhs = [];
@ -93,8 +141,8 @@ class SpiroExpansionContext1 {
dx = knot.proposedNormal.x; dx = knot.proposedNormal.x;
dy = knot.proposedNormal.y; dy = knot.proposedNormal.y;
} else { } else {
dx = normalX(knot.origTangent, contrast); dx = normalX(knot.origTangent, this.contrast);
dy = normalY(knot.origTangent, contrast); dy = normalY(knot.origTangent, this.contrast);
} }
lhs[j] = { lhs[j] = {
type: knot.type, type: knot.type,
@ -146,7 +194,7 @@ class SpiroExpansionContext1 {
} }
} }
class SpiroExpansionContext2 { class NormalRectifier {
constructor(stage1ControlKnots, gizmo) { constructor(stage1ControlKnots, gizmo) {
this.gizmo = gizmo; this.gizmo = gizmo;
this.controlKnots = stage1ControlKnots; this.controlKnots = stage1ControlKnots;
@ -191,5 +239,5 @@ function computeNormalAngle(gizmo, x, y) {
return Math.PI / 2 + Math.atan2(tfd.y, tfd.x); return Math.PI / 2 + Math.atan2(tfd.y, tfd.x);
} }
exports.SpiroExpansionContext1 = SpiroExpansionContext1; exports.BiKnotCollector = BiKnotCollector;
exports.SpiroExpansionContext2 = SpiroExpansionContext2; exports.SpiroExpander = SpiroExpander;