Make dispiro results cachable
This commit is contained in:
parent
f892a56532
commit
7bf55f2682
6 changed files with 211 additions and 73 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 = 10;
|
const Edition = 11;
|
||||||
const MAX_AGE = 5;
|
const MAX_AGE = 5;
|
||||||
|
|
||||||
class GfEntry {
|
class GfEntry {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
const { mix } = require("./utils");
|
const { mix } = require("./utils");
|
||||||
|
|
||||||
class Box {
|
class Box {
|
||||||
|
|
29
font-src/support/formatter.js
Normal file
29
font-src/support/formatter.js
Normal 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;
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue