354 lines
9.7 KiB
JavaScript
354 lines
9.7 KiB
JavaScript
"use strict";
|
|
|
|
const SpiroJs = require("spiro");
|
|
const CurveUtil = require("../support/curve-util");
|
|
const Transform = require("../support/transform");
|
|
const { SpiroExpansionContext1, SpiroExpansionContext2 } = require("../support/spiro-expand");
|
|
const { fallback, mix, bez2, bez3 } = require("../support/utils");
|
|
const { SpiroGeometry, CombineGeometry } = require("../support/geometry");
|
|
|
|
exports.SetupBuilders = function (bindings) {
|
|
const { Contrast, GlobalTransform, Stroke, Superness } = bindings;
|
|
const g4 = (x, y, f) => ({ x, y, type: "g4", af: f });
|
|
const g2 = (x, y, f) => ({ x, y, type: "g2", af: f });
|
|
const corner = (x, y, f) => ({ x, y, type: "corner", af: f });
|
|
const flat = (x, y, f) => ({ x, y, type: "left", af: f });
|
|
const curl = (x, y, f) => ({ x, y, type: "right", af: f });
|
|
const close = f => ({ type: "close", af: f });
|
|
const end = f => ({ type: "end", af: f });
|
|
|
|
const straight = { l: flat, r: curl };
|
|
{
|
|
let directions = [
|
|
{ name: "up", x: 0, y: 1 },
|
|
{ name: "down", x: 0, y: -1 },
|
|
{ name: "left", x: -1, y: 0 },
|
|
{ name: "right", x: 1, y: 0 }
|
|
];
|
|
let adhesions = [
|
|
{ name: "start", l: 0, r: 0.01 },
|
|
{ name: "mid", l: -0.005, r: 0.005 },
|
|
{ name: "end", l: -0.01, r: 0 }
|
|
];
|
|
let knotTypes = [
|
|
[g4, g4, g4],
|
|
[g2, g2, g2],
|
|
[corner, corner, corner],
|
|
[straight, flat, curl]
|
|
];
|
|
for (const [sink, kl, kr] of knotTypes) {
|
|
for (const d of directions) {
|
|
sink[d.name] = {};
|
|
for (const a of adhesions) {
|
|
sink[d.name][a.name] = (x, y, af) => [
|
|
kl(x + d.x * a.l, y + d.y * a.l, af),
|
|
kr(x + d.x * a.r, y + d.y * a.r, af)
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function widths(l, r) {
|
|
return function () {
|
|
return this.setWidth ? this.setWidth(l, r) : void 0;
|
|
};
|
|
}
|
|
widths.lhs = function (w) {
|
|
return widths(fallback(w, Stroke), 0);
|
|
};
|
|
widths.rhs = function (w) {
|
|
return widths(0, fallback(w, Stroke));
|
|
};
|
|
widths.center = function (w) {
|
|
return widths(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2);
|
|
};
|
|
|
|
function heading(d) {
|
|
return function () {
|
|
return this.headsTo ? this.headsTo(d) : void 0;
|
|
};
|
|
}
|
|
widths.heading = function (l, r, d) {
|
|
return function () {
|
|
if (this.setWidth) this.setWidth(l, r);
|
|
return this.headsTo ? this.headsTo(d) : void 0;
|
|
};
|
|
};
|
|
widths.lhs.heading = function (w, d) {
|
|
return function () {
|
|
if (this.setWidth) this.setWidth(fallback(w, Stroke), 0);
|
|
return this.headsTo ? this.headsTo(d) : void 0;
|
|
};
|
|
};
|
|
widths.rhs.heading = function (w, d) {
|
|
return function () {
|
|
if (this.setWidth) this.setWidth(0, fallback(w, Stroke));
|
|
return this.headsTo ? this.headsTo(d) : void 0;
|
|
};
|
|
};
|
|
widths.center.heading = function (w, d) {
|
|
return function () {
|
|
if (this.setWidth) this.setWidth(fallback(w, Stroke) / 2, fallback(w, Stroke) / 2);
|
|
return this.headsTo ? this.headsTo(d) : void 0;
|
|
};
|
|
};
|
|
|
|
function disableContrast() {
|
|
return function () {
|
|
return (this.contrast = 1);
|
|
};
|
|
}
|
|
function unimportant() {
|
|
return this.setUnimportant ? this.setUnimportant() : void 0;
|
|
}
|
|
function important() {
|
|
return void 0;
|
|
}
|
|
function afInterpolate(before, after, args) {
|
|
return g4(
|
|
mix(before.x, after.x, args.rx),
|
|
mix(before.y, after.y, args.ry),
|
|
fallback(args.raf, unimportant)
|
|
);
|
|
}
|
|
function afInterpolateG2(before, after, args) {
|
|
return g2(
|
|
mix(before.x, after.x, args.rx),
|
|
mix(before.y, after.y, args.ry),
|
|
fallback(args.raf, unimportant)
|
|
);
|
|
}
|
|
function afInterpolateThem(before, after, args) {
|
|
let innerKnots = [];
|
|
for (const [rx, ry, rt] of args.rs) {
|
|
innerKnots.push(
|
|
fallback(args.ty, g2)(
|
|
mix(before.x, after.x, rx),
|
|
mix(before.y, after.y, ry),
|
|
args.raf && args.raf.blend && rt !== void 0
|
|
? args.raf.blend(rt)
|
|
: args.raf
|
|
? args.raf
|
|
: unimportant
|
|
)
|
|
);
|
|
}
|
|
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) {
|
|
return { type: "interpolate", rx, ry, raf, af: afInterpolate };
|
|
}
|
|
alsoThru.g2 = function (rx, ry, raf) {
|
|
return { type: "interpolate", rx, ry, raf, af: afInterpolateG2 };
|
|
};
|
|
alsoThru.sNeck = function (px, py, sw, ps) {
|
|
return { type: "interpolate", px, py, sw, ps, af: afInterpolateSNeck };
|
|
};
|
|
function alsoThruThem(es, raf, ty) {
|
|
return { type: "interpolate", rs: es, raf, ty, af: afInterpolateThem };
|
|
}
|
|
function bezControlsImpl(x1, y1, x2, y2, samples, raf, ty) {
|
|
let rs = [];
|
|
for (let j = 1; j < samples; j = j + 1)
|
|
rs.push([
|
|
bez3(0, x1, x2, 1, j / samples),
|
|
bez3(0, y1, y2, 1, j / samples),
|
|
j / samples
|
|
]);
|
|
return alsoThruThem(rs, raf);
|
|
}
|
|
function bezControls(x1, y1, x2, y2, _samples, raf) {
|
|
return bezControlsImpl(x1, y1, x2, y2, fallback(_samples, 3), raf);
|
|
}
|
|
function quadControls(x1, y1, _samples, raf) {
|
|
return bezControlsImpl(
|
|
(x1 * 2) / 3,
|
|
(y1 * 2) / 3,
|
|
mix(1, x1, 2 / 3),
|
|
mix(1, y1, 2 / 3),
|
|
fallback(_samples, 3),
|
|
raf
|
|
);
|
|
}
|
|
|
|
let DEFAULT_STEPS = 6;
|
|
|
|
let [buildHV, buildVH] = (function (cache) {
|
|
function build(samples, _superness) {
|
|
const superness = fallback(_superness, Superness);
|
|
let hv = [];
|
|
let vh = [];
|
|
for (let j = 1; j < samples; j = j + 1) {
|
|
const theta = (((j + 1) / (samples + 2)) * Math.PI) / 2;
|
|
const c = Math.pow(Math.cos(theta), 2 / superness);
|
|
const s = Math.pow(Math.sin(theta), 2 / superness);
|
|
hv.push([s, 1 - c]);
|
|
vh.push([1 - c, s]);
|
|
}
|
|
return { hv, vh: vh };
|
|
}
|
|
function buildHVImpl(samples, _superness) {
|
|
if (_superness) return build(samples, _superness).hv;
|
|
if (!cache[samples]) cache[samples] = build(samples, _superness);
|
|
return cache[samples].hv;
|
|
}
|
|
function buildVHImpl(samples, _superness) {
|
|
if (_superness) return build(samples, _superness).vh;
|
|
if (!cache[samples]) cache[samples] = build(samples, _superness);
|
|
return cache[samples].vh;
|
|
}
|
|
return [buildHVImpl, buildVHImpl];
|
|
})([]);
|
|
|
|
function archv(samples, superness) {
|
|
return alsoThruThem(buildHV(fallback(samples, DEFAULT_STEPS), superness));
|
|
}
|
|
archv.superness = function (s) {
|
|
return archv(DEFAULT_STEPS, s);
|
|
};
|
|
function arcvh(samples, superness) {
|
|
return alsoThruThem(buildVH(fallback(samples, DEFAULT_STEPS), superness));
|
|
}
|
|
arcvh.superness = function (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) {
|
|
for (const p of knots) {
|
|
if (p instanceof Array) flattenImpl(sink, p);
|
|
else sink.push(p);
|
|
}
|
|
}
|
|
function nCyclic(p, n) {
|
|
return (p + n + n) % n;
|
|
}
|
|
function flatten(s, knots0) {
|
|
let knots = [];
|
|
flattenImpl(knots, knots0);
|
|
let unwrapped = false;
|
|
for (let j = 0; j < knots.length; j = j + 1)
|
|
if (knots[j] && knots[j].type === "interpolate") {
|
|
const kBefore = 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]);
|
|
unwrapped = true;
|
|
}
|
|
if (unwrapped) return flatten(s, knots);
|
|
return knots;
|
|
}
|
|
function dropTailKnot(knots) {
|
|
let last = knots[knots.length - 1];
|
|
if (last && (last.type === "close" || last.type === "end")) {
|
|
knots.length = knots.length - 1;
|
|
return last.type === "close";
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
function prepareSpiroKnots(_knots, s) {
|
|
let knots = _knots;
|
|
while (knots[0] && knots[0] instanceof Function) {
|
|
knots[0].call(s);
|
|
knots.splice(0, 1);
|
|
}
|
|
const closed = dropTailKnot(knots);
|
|
knots = flatten(s, knots);
|
|
return { knots, closed };
|
|
}
|
|
function iterateNormals(s, closed) {
|
|
let knotsP2 = s.getPass2Knots(closed, fallback(s.contrast, Contrast));
|
|
let s2 = new SpiroExpansionContext2(s.controlKnots, s.gizmo);
|
|
return SpiroJs.spiroToArcsOnContext(knotsP2, closed, s2);
|
|
}
|
|
function dispiro(...args) {
|
|
return function () {
|
|
let s = new SpiroExpansionContext1(this.gizmo || GlobalTransform);
|
|
let { knots, closed } = prepareSpiroKnots([].slice.call(args, 0), s);
|
|
for (const knot of knots) {
|
|
const ty = knot.type;
|
|
const af = knot.af;
|
|
knot.af = function () {
|
|
this.setType(ty);
|
|
return af ? af.apply(this, args) : void 0;
|
|
};
|
|
}
|
|
|
|
SpiroJs.spiroToArcsOnContext(knots, closed, s);
|
|
iterateNormals(s, closed);
|
|
iterateNormals(s, closed);
|
|
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 };
|
|
};
|
|
}
|
|
|
|
function spiroOutline(...args) {
|
|
return function () {
|
|
const gizmo = this.gizmo || GlobalTransform;
|
|
const g = new CurveUtil.BezToContoursSink(gizmo);
|
|
const { knots, closed } = prepareSpiroKnots(args, g);
|
|
return this.includeGeometry(new SpiroGeometry(knots, closed, gizmo));
|
|
};
|
|
}
|
|
return {
|
|
g4,
|
|
g2,
|
|
corner,
|
|
flat,
|
|
curl,
|
|
close,
|
|
end,
|
|
straight,
|
|
widths,
|
|
heading,
|
|
"disable-contrast": disableContrast,
|
|
unimportant,
|
|
important,
|
|
alsoThru,
|
|
alsoThruThem,
|
|
bezControls,
|
|
quadControls,
|
|
archv,
|
|
arcvh,
|
|
complexThru,
|
|
dispiro,
|
|
"spiro-outline": spiroOutline
|
|
};
|
|
};
|