mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-08-28 10:40:52 +02:00
renamed graphics-element dir
This commit is contained in:
2115
docs/js/graphics-element/lib/bezierjs/bezier.js
Normal file
2115
docs/js/graphics-element/lib/bezierjs/bezier.js
Normal file
File diff suppressed because it is too large
Load Diff
226
docs/js/graphics-element/lib/bezierjs/normalise-svg.js
Normal file
226
docs/js/graphics-element/lib/bezierjs/normalise-svg.js
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Normalise an SVG path to absolute coordinates
|
||||
* and full commands, rather than relative coordinates
|
||||
* and/or shortcut commands.
|
||||
*/
|
||||
export default function normalizePath(d) {
|
||||
// preprocess "d" so that we have spaces between values
|
||||
d = d
|
||||
.replace(/,/g, " ") // replace commas with spaces
|
||||
.replace(/-/g, " - ") // add spacing around minus signs
|
||||
.replace(/-\s+/g, "-") // remove spacing to the right of minus signs.
|
||||
.replace(/([a-zA-Z])/g, " $1 ");
|
||||
|
||||
// set up the variables used in this function
|
||||
const instructions = d.replace(/([a-zA-Z])\s?/g, "|$1").split("|"),
|
||||
instructionLength = instructions.length;
|
||||
|
||||
let i,
|
||||
instruction,
|
||||
op,
|
||||
lop,
|
||||
args = [],
|
||||
alen,
|
||||
a,
|
||||
sx = 0,
|
||||
sy = 0,
|
||||
x = 0,
|
||||
y = 0,
|
||||
cx = 0,
|
||||
cy = 0,
|
||||
cx2 = 0,
|
||||
cy2 = 0,
|
||||
rx = 0,
|
||||
ry = 0,
|
||||
xrot = 0,
|
||||
lflag = 0,
|
||||
sweep = 0,
|
||||
normalized = "";
|
||||
|
||||
// we run through the instruction list starting at 1, not 0,
|
||||
// because we split up "|M x y ...." so the first element will
|
||||
// always be an empty string. By design.
|
||||
for (i = 1; i < instructionLength; i++) {
|
||||
// which instruction is this?
|
||||
instruction = instructions[i];
|
||||
op = instruction.substring(0, 1);
|
||||
lop = op.toLowerCase();
|
||||
|
||||
// what are the arguments? note that we need to convert
|
||||
// all strings into numbers, or + will do silly things.
|
||||
args = instruction.replace(op, "").trim().split(" ");
|
||||
args = args
|
||||
.filter(function (v) {
|
||||
return v !== "";
|
||||
})
|
||||
.map(parseFloat);
|
||||
alen = args.length;
|
||||
|
||||
// we could use a switch, but elaborate code in a "case" with
|
||||
// fallthrough is just horrid to read. So let's use ifthen
|
||||
// statements instead.
|
||||
|
||||
// moveto command (plus possible lineto)
|
||||
if (lop === "m") {
|
||||
normalized += "M ";
|
||||
if (op === "m") {
|
||||
x += args[0];
|
||||
y += args[1];
|
||||
} else {
|
||||
x = args[0];
|
||||
y = args[1];
|
||||
}
|
||||
// records start position, for dealing
|
||||
// with the shape close operator ('Z')
|
||||
sx = x;
|
||||
sy = y;
|
||||
normalized += x + " " + y + " ";
|
||||
if (alen > 2) {
|
||||
for (a = 0; a < alen; a += 2) {
|
||||
if (op === "m") {
|
||||
x += args[a];
|
||||
y += args[a + 1];
|
||||
} else {
|
||||
x = args[a];
|
||||
y = args[a + 1];
|
||||
}
|
||||
normalized += "L " + x + " " + y + " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lineto commands
|
||||
else if (lop === "l") {
|
||||
for (a = 0; a < alen; a += 2) {
|
||||
if (op === "l") {
|
||||
x += args[a];
|
||||
y += args[a + 1];
|
||||
} else {
|
||||
x = args[a];
|
||||
y = args[a + 1];
|
||||
}
|
||||
normalized += "L " + x + " " + y + " ";
|
||||
}
|
||||
} else if (lop === "h") {
|
||||
for (a = 0; a < alen; a++) {
|
||||
if (op === "h") {
|
||||
x += args[a];
|
||||
} else {
|
||||
x = args[a];
|
||||
}
|
||||
normalized += "L " + x + " " + y + " ";
|
||||
}
|
||||
} else if (lop === "v") {
|
||||
for (a = 0; a < alen; a++) {
|
||||
if (op === "v") {
|
||||
y += args[a];
|
||||
} else {
|
||||
y = args[a];
|
||||
}
|
||||
normalized += "L " + x + " " + y + " ";
|
||||
}
|
||||
}
|
||||
|
||||
// quadratic curveto commands
|
||||
else if (lop === "q") {
|
||||
for (a = 0; a < alen; a += 4) {
|
||||
if (op === "q") {
|
||||
cx = x + args[a];
|
||||
cy = y + args[a + 1];
|
||||
x += args[a + 2];
|
||||
y += args[a + 3];
|
||||
} else {
|
||||
cx = args[a];
|
||||
cy = args[a + 1];
|
||||
x = args[a + 2];
|
||||
y = args[a + 3];
|
||||
}
|
||||
normalized += "Q " + cx + " " + cy + " " + x + " " + y + " ";
|
||||
}
|
||||
} else if (lop === "t") {
|
||||
for (a = 0; a < alen; a += 2) {
|
||||
// reflect previous cx/cy over x/y
|
||||
cx = x + (x - cx);
|
||||
cy = y + (y - cy);
|
||||
// then get real end point
|
||||
if (op === "t") {
|
||||
x += args[a];
|
||||
y += args[a + 1];
|
||||
} else {
|
||||
x = args[a];
|
||||
y = args[a + 1];
|
||||
}
|
||||
normalized += "Q " + cx + " " + cy + " " + x + " " + y + " ";
|
||||
}
|
||||
}
|
||||
|
||||
// cubic curveto commands
|
||||
else if (lop === "c") {
|
||||
for (a = 0; a < alen; a += 6) {
|
||||
if (op === "c") {
|
||||
cx = x + args[a];
|
||||
cy = y + args[a + 1];
|
||||
cx2 = x + args[a + 2];
|
||||
cy2 = y + args[a + 3];
|
||||
x += args[a + 4];
|
||||
y += args[a + 5];
|
||||
} else {
|
||||
cx = args[a];
|
||||
cy = args[a + 1];
|
||||
cx2 = args[a + 2];
|
||||
cy2 = args[a + 3];
|
||||
x = args[a + 4];
|
||||
y = args[a + 5];
|
||||
}
|
||||
normalized += "C " + cx + " " + cy + " " + cx2 + " " + cy2 + " " + x + " " + y + " ";
|
||||
}
|
||||
} else if (lop === "s") {
|
||||
for (a = 0; a < alen; a += 4) {
|
||||
// reflect previous cx2/cy2 over x/y
|
||||
cx = x + (x - cx2);
|
||||
cy = y + (y - cy2);
|
||||
// then get real control and end point
|
||||
if (op === "s") {
|
||||
cx2 = x + args[a];
|
||||
cy2 = y + args[a + 1];
|
||||
x += args[a + 2];
|
||||
y += args[a + 3];
|
||||
} else {
|
||||
cx2 = args[a];
|
||||
cy2 = args[a + 1];
|
||||
x = args[a + 2];
|
||||
y = args[a + 3];
|
||||
}
|
||||
normalized += "C " + cx + " " + cy + " " + cx2 + " " + cy2 + " " + x + " " + y + " ";
|
||||
}
|
||||
}
|
||||
|
||||
// rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
||||
// a 25,25 -30 0, 1 50,-25
|
||||
|
||||
// arc command
|
||||
else if (lop === "a") {
|
||||
for (a = 0; a < alen; a += 7) {
|
||||
rx = args[a];
|
||||
ry = args[a + 1];
|
||||
xrot = args[a + 2];
|
||||
lflag = args[a + 3];
|
||||
sweep = args[a + 4];
|
||||
if (op === "a") {
|
||||
x += args[a + 5];
|
||||
y += args[a + 6];
|
||||
} else {
|
||||
x = args[a + 5];
|
||||
y = args[a + 6];
|
||||
}
|
||||
normalized += "A " + rx + " " + ry + " " + xrot + " " + lflag + " " + sweep + " " + x + " " + y + " ";
|
||||
}
|
||||
} else if (lop === "z") {
|
||||
normalized += "Z ";
|
||||
// not unimportant: path closing changes the current x/y coordinate
|
||||
x = sx;
|
||||
y = sy;
|
||||
}
|
||||
}
|
||||
return normalized.trim();
|
||||
}
|
70
docs/js/graphics-element/lib/bezierjs/poly-bezier.js
Normal file
70
docs/js/graphics-element/lib/bezierjs/poly-bezier.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { utils } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Poly Bezier
|
||||
* @param {[type]} curves [description]
|
||||
*/
|
||||
class PolyBezier {
|
||||
constructor(curves) {
|
||||
this.curves = [];
|
||||
this._3d = false;
|
||||
if (!!curves) {
|
||||
this.curves = curves;
|
||||
this._3d = this.curves[0]._3d;
|
||||
}
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
toString() {
|
||||
return (
|
||||
"[" +
|
||||
this.curves
|
||||
.map(function (curve) {
|
||||
return utils.pointsToString(curve.points);
|
||||
})
|
||||
.join(", ") +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
addCurve(curve) {
|
||||
this.curves.push(curve);
|
||||
this._3d = this._3d || curve._3d;
|
||||
}
|
||||
|
||||
length() {
|
||||
return this.curves
|
||||
.map(function (v) {
|
||||
return v.length();
|
||||
})
|
||||
.reduce(function (a, b) {
|
||||
return a + b;
|
||||
});
|
||||
}
|
||||
|
||||
curve(idx) {
|
||||
return this.curves[idx];
|
||||
}
|
||||
|
||||
bbox() {
|
||||
const c = this.curves;
|
||||
var bbox = c[0].bbox();
|
||||
for (var i = 1; i < c.length; i++) {
|
||||
utils.expandbox(bbox, c[i].bbox());
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
offset(d) {
|
||||
const offset = [];
|
||||
this.curves.forEach(function (v) {
|
||||
offset = offset.concat(v.offset(d));
|
||||
});
|
||||
return new PolyBezier(offset);
|
||||
}
|
||||
}
|
||||
|
||||
export { PolyBezier };
|
45
docs/js/graphics-element/lib/bezierjs/svg-to-beziers.js
Normal file
45
docs/js/graphics-element/lib/bezierjs/svg-to-beziers.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import normalise from "./normalise-svg.js";
|
||||
|
||||
let M = { x: false, y: false };
|
||||
|
||||
/**
|
||||
* ...
|
||||
*/
|
||||
function makeBezier(Bezier, term, values) {
|
||||
if (term === "Z") return;
|
||||
if (term === "M") {
|
||||
M = { x: values[0], y: values[1] };
|
||||
return;
|
||||
}
|
||||
const curve = new Bezier(M.x, M.y, ...values);
|
||||
const last = values.slice(-2);
|
||||
M = { x: last[0], y: last[1] };
|
||||
return curve;
|
||||
}
|
||||
|
||||
/**
|
||||
* ...
|
||||
*/
|
||||
function convertPath(Bezier, d) {
|
||||
const terms = normalise(d).split(" "),
|
||||
matcher = new RegExp("[MLCQZ]", "");
|
||||
|
||||
let term,
|
||||
segment,
|
||||
values,
|
||||
segments = [],
|
||||
ARGS = { C: 6, Q: 4, L: 2, M: 2 };
|
||||
|
||||
while (terms.length) {
|
||||
term = terms.splice(0, 1)[0];
|
||||
if (matcher.test(term)) {
|
||||
values = terms.splice(0, ARGS[term]).map(parseFloat);
|
||||
segment = makeBezier(Bezier, term, values);
|
||||
if (segment) segments.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return new Bezier.PolyBezier(segments);
|
||||
}
|
||||
|
||||
export { convertPath };
|
872
docs/js/graphics-element/lib/bezierjs/utils.js
Normal file
872
docs/js/graphics-element/lib/bezierjs/utils.js
Normal file
@@ -0,0 +1,872 @@
|
||||
import { Bezier } from "./bezier.js";
|
||||
|
||||
// math-inlining.
|
||||
const { abs, cos, sin, acos, atan2, sqrt, pow } = Math;
|
||||
|
||||
// cube root function yielding real roots
|
||||
function crt(v) {
|
||||
return v < 0 ? -pow(-v, 1 / 3) : pow(v, 1 / 3);
|
||||
}
|
||||
|
||||
// trig constants
|
||||
const pi = Math.PI,
|
||||
tau = 2 * pi,
|
||||
quart = pi / 2,
|
||||
// float precision significant decimal
|
||||
epsilon = 0.000001,
|
||||
// extremas used in bbox calculation and similar algorithms
|
||||
nMax = Number.MAX_SAFE_INTEGER || 9007199254740991,
|
||||
nMin = Number.MIN_SAFE_INTEGER || -9007199254740991,
|
||||
// a zero coordinate, which is surprisingly useful
|
||||
ZERO = { x: 0, y: 0, z: 0 };
|
||||
|
||||
// Bezier utility functions
|
||||
const utils = {
|
||||
// Legendre-Gauss abscissae with n=24 (x_i values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x))
|
||||
Tvalues: [
|
||||
-0.0640568928626056260850430826247450385909,
|
||||
0.0640568928626056260850430826247450385909,
|
||||
-0.1911188674736163091586398207570696318404,
|
||||
0.1911188674736163091586398207570696318404,
|
||||
-0.3150426796961633743867932913198102407864,
|
||||
0.3150426796961633743867932913198102407864,
|
||||
-0.4337935076260451384870842319133497124524,
|
||||
0.4337935076260451384870842319133497124524,
|
||||
-0.5454214713888395356583756172183723700107,
|
||||
0.5454214713888395356583756172183723700107,
|
||||
-0.6480936519369755692524957869107476266696,
|
||||
0.6480936519369755692524957869107476266696,
|
||||
-0.7401241915785543642438281030999784255232,
|
||||
0.7401241915785543642438281030999784255232,
|
||||
-0.8200019859739029219539498726697452080761,
|
||||
0.8200019859739029219539498726697452080761,
|
||||
-0.8864155270044010342131543419821967550873,
|
||||
0.8864155270044010342131543419821967550873,
|
||||
-0.9382745520027327585236490017087214496548,
|
||||
0.9382745520027327585236490017087214496548,
|
||||
-0.9747285559713094981983919930081690617411,
|
||||
0.9747285559713094981983919930081690617411,
|
||||
-0.9951872199970213601799974097007368118745,
|
||||
0.9951872199970213601799974097007368118745,
|
||||
],
|
||||
|
||||
// Legendre-Gauss weights with n=24 (w_i values, defined by a function linked to in the Bezier primer article)
|
||||
Cvalues: [
|
||||
0.1279381953467521569740561652246953718517,
|
||||
0.1279381953467521569740561652246953718517,
|
||||
0.1258374563468282961213753825111836887264,
|
||||
0.1258374563468282961213753825111836887264,
|
||||
0.121670472927803391204463153476262425607,
|
||||
0.121670472927803391204463153476262425607,
|
||||
0.1155056680537256013533444839067835598622,
|
||||
0.1155056680537256013533444839067835598622,
|
||||
0.1074442701159656347825773424466062227946,
|
||||
0.1074442701159656347825773424466062227946,
|
||||
0.0976186521041138882698806644642471544279,
|
||||
0.0976186521041138882698806644642471544279,
|
||||
0.086190161531953275917185202983742667185,
|
||||
0.086190161531953275917185202983742667185,
|
||||
0.0733464814110803057340336152531165181193,
|
||||
0.0733464814110803057340336152531165181193,
|
||||
0.0592985849154367807463677585001085845412,
|
||||
0.0592985849154367807463677585001085845412,
|
||||
0.0442774388174198061686027482113382288593,
|
||||
0.0442774388174198061686027482113382288593,
|
||||
0.0285313886289336631813078159518782864491,
|
||||
0.0285313886289336631813078159518782864491,
|
||||
0.0123412297999871995468056670700372915759,
|
||||
0.0123412297999871995468056670700372915759,
|
||||
],
|
||||
|
||||
arcfn: function (t, derivativeFn) {
|
||||
const d = derivativeFn(t);
|
||||
let l = d.x * d.x + d.y * d.y;
|
||||
if (typeof d.z !== "undefined") {
|
||||
l += d.z * d.z;
|
||||
}
|
||||
return sqrt(l);
|
||||
},
|
||||
|
||||
compute: function (t, points, _3d) {
|
||||
// shortcuts
|
||||
if (t === 0) {
|
||||
points[0].t = 0;
|
||||
return points[0];
|
||||
}
|
||||
|
||||
const order = points.length - 1;
|
||||
|
||||
if (t === 1) {
|
||||
points[order].t = 1;
|
||||
return points[order];
|
||||
}
|
||||
|
||||
const mt = 1 - t;
|
||||
let p = points;
|
||||
|
||||
// constant?
|
||||
if (order === 0) {
|
||||
points[0].t = t;
|
||||
return points[0];
|
||||
}
|
||||
|
||||
// linear?
|
||||
if (order === 1) {
|
||||
const ret = {
|
||||
x: mt * p[0].x + t * p[1].x,
|
||||
y: mt * p[0].y + t * p[1].y,
|
||||
t: t,
|
||||
};
|
||||
if (_3d) {
|
||||
ret.z = mt * p[0].z + t * p[1].z;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// quadratic/cubic curve?
|
||||
if (order < 4) {
|
||||
let mt2 = mt * mt,
|
||||
t2 = t * t,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d = 0;
|
||||
if (order === 2) {
|
||||
p = [p[0], p[1], p[2], ZERO];
|
||||
a = mt2;
|
||||
b = mt * t * 2;
|
||||
c = t2;
|
||||
} else if (order === 3) {
|
||||
a = mt2 * mt;
|
||||
b = mt2 * t * 3;
|
||||
c = mt * t2 * 3;
|
||||
d = t * t2;
|
||||
}
|
||||
const ret = {
|
||||
x: a * p[0].x + b * p[1].x + c * p[2].x + d * p[3].x,
|
||||
y: a * p[0].y + b * p[1].y + c * p[2].y + d * p[3].y,
|
||||
t: t,
|
||||
};
|
||||
if (_3d) {
|
||||
ret.z = a * p[0].z + b * p[1].z + c * p[2].z + d * p[3].z;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// higher order curves: use de Casteljau's computation
|
||||
const dCpts = JSON.parse(JSON.stringify(points));
|
||||
while (dCpts.length > 1) {
|
||||
for (let i = 0; i < dCpts.length - 1; i++) {
|
||||
dCpts[i] = {
|
||||
x: dCpts[i].x + (dCpts[i + 1].x - dCpts[i].x) * t,
|
||||
y: dCpts[i].y + (dCpts[i + 1].y - dCpts[i].y) * t,
|
||||
};
|
||||
if (typeof dCpts[i].z !== "undefined") {
|
||||
dCpts[i] = dCpts[i].z + (dCpts[i + 1].z - dCpts[i].z) * t;
|
||||
}
|
||||
}
|
||||
dCpts.splice(dCpts.length - 1, 1);
|
||||
}
|
||||
dCpts[0].t = t;
|
||||
return dCpts[0];
|
||||
},
|
||||
|
||||
computeWithRatios: function (t, points, ratios, _3d) {
|
||||
const mt = 1 - t,
|
||||
r = ratios,
|
||||
p = points;
|
||||
|
||||
let f1 = r[0],
|
||||
f2 = r[1],
|
||||
f3 = r[2],
|
||||
f4 = r[3],
|
||||
d;
|
||||
|
||||
// spec for linear
|
||||
f1 *= mt;
|
||||
f2 *= t;
|
||||
|
||||
if (p.length === 2) {
|
||||
d = f1 + f2;
|
||||
return {
|
||||
x: (f1 * p[0].x + f2 * p[1].x) / d,
|
||||
y: (f1 * p[0].y + f2 * p[1].y) / d,
|
||||
z: !_3d ? false : (f1 * p[0].z + f2 * p[1].z) / d,
|
||||
t: t,
|
||||
};
|
||||
}
|
||||
|
||||
// upgrade to quadratic
|
||||
f1 *= mt;
|
||||
f2 *= 2 * mt;
|
||||
f3 *= t * t;
|
||||
|
||||
if (p.length === 3) {
|
||||
d = f1 + f2 + f3;
|
||||
return {
|
||||
x: (f1 * p[0].x + f2 * p[1].x + f3 * p[2].x) / d,
|
||||
y: (f1 * p[0].y + f2 * p[1].y + f3 * p[2].y) / d,
|
||||
z: !_3d ? false : (f1 * p[0].z + f2 * p[1].z + f3 * p[2].z) / d,
|
||||
t: t,
|
||||
};
|
||||
}
|
||||
|
||||
// upgrade to cubic
|
||||
f1 *= mt;
|
||||
f2 *= 1.5 * mt;
|
||||
f3 *= 3 * mt;
|
||||
f4 *= t * t * t;
|
||||
|
||||
if (p.length === 4) {
|
||||
d = f1 + f2 + f3 + f4;
|
||||
return {
|
||||
x: (f1 * p[0].x + f2 * p[1].x + f3 * p[2].x + f4 * p[3].x) / d,
|
||||
y: (f1 * p[0].y + f2 * p[1].y + f3 * p[2].y + f4 * p[3].y) / d,
|
||||
z: !_3d ? false : (f1 * p[0].z + f2 * p[1].z + f3 * p[2].z + f4 * p[3].z) / d,
|
||||
t: t,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
derive: function (points, _3d) {
|
||||
const dpoints = [];
|
||||
for (let p = points, d = p.length, c = d - 1; d > 1; d--, c--) {
|
||||
const list = [];
|
||||
for (let j = 0, dpt; j < c; j++) {
|
||||
dpt = {
|
||||
x: c * (p[j + 1].x - p[j].x),
|
||||
y: c * (p[j + 1].y - p[j].y),
|
||||
};
|
||||
if (_3d) {
|
||||
dpt.z = c * (p[j + 1].z - p[j].z);
|
||||
}
|
||||
list.push(dpt);
|
||||
}
|
||||
dpoints.push(list);
|
||||
p = list;
|
||||
}
|
||||
return dpoints;
|
||||
},
|
||||
|
||||
between: function (v, m, M) {
|
||||
return (m <= v && v <= M) || utils.approximately(v, m) || utils.approximately(v, M);
|
||||
},
|
||||
|
||||
approximately: function (a, b, precision) {
|
||||
return abs(a - b) <= (precision || epsilon);
|
||||
},
|
||||
|
||||
length: function (derivativeFn) {
|
||||
const z = 0.5,
|
||||
len = utils.Tvalues.length;
|
||||
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0, t; i < len; i++) {
|
||||
t = z * utils.Tvalues[i] + z;
|
||||
sum += utils.Cvalues[i] * utils.arcfn(t, derivativeFn);
|
||||
}
|
||||
return z * sum;
|
||||
},
|
||||
|
||||
map: function (v, ds, de, ts, te) {
|
||||
const d1 = de - ds,
|
||||
d2 = te - ts,
|
||||
v2 = v - ds,
|
||||
r = v2 / d1;
|
||||
return ts + d2 * r;
|
||||
},
|
||||
|
||||
lerp: function (r, v1, v2) {
|
||||
const ret = {
|
||||
x: v1.x + r * (v2.x - v1.x),
|
||||
y: v1.y + r * (v2.y - v1.y),
|
||||
};
|
||||
if (!!v1.z && !!v2.z) {
|
||||
ret.z = v1.z + r * (v2.z - v1.z);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
|
||||
pointToString: function (p) {
|
||||
let s = p.x + "/" + p.y;
|
||||
if (typeof p.z !== "undefined") {
|
||||
s += "/" + p.z;
|
||||
}
|
||||
return s;
|
||||
},
|
||||
|
||||
pointsToString: function (points) {
|
||||
return "[" + points.map(utils.pointToString).join(", ") + "]";
|
||||
},
|
||||
|
||||
copy: function (obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
},
|
||||
|
||||
angle: function (o, v1, v2) {
|
||||
const dx1 = v1.x - o.x,
|
||||
dy1 = v1.y - o.y,
|
||||
dx2 = v2.x - o.x,
|
||||
dy2 = v2.y - o.y,
|
||||
cross = dx1 * dy2 - dy1 * dx2,
|
||||
dot = dx1 * dx2 + dy1 * dy2;
|
||||
return atan2(cross, dot);
|
||||
},
|
||||
|
||||
// round as string, to avoid rounding errors
|
||||
round: function (v, d) {
|
||||
const s = "" + v;
|
||||
const pos = s.indexOf(".");
|
||||
return parseFloat(s.substring(0, pos + 1 + d));
|
||||
},
|
||||
|
||||
dist: function (p1, p2) {
|
||||
const dx = p1.x - p2.x,
|
||||
dy = p1.y - p2.y;
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
},
|
||||
|
||||
closest: function (LUT, point) {
|
||||
let mdist = pow(2, 63),
|
||||
mpos,
|
||||
d;
|
||||
LUT.forEach(function (p, idx) {
|
||||
d = utils.dist(point, p);
|
||||
if (d < mdist) {
|
||||
mdist = d;
|
||||
mpos = idx;
|
||||
}
|
||||
});
|
||||
return { mdist: mdist, mpos: mpos };
|
||||
},
|
||||
|
||||
abcratio: function (t, n) {
|
||||
// see ratio(t) note on http://pomax.github.io/bezierinfo/#abc
|
||||
if (n !== 2 && n !== 3) {
|
||||
return false;
|
||||
}
|
||||
if (typeof t === "undefined") {
|
||||
t = 0.5;
|
||||
} else if (t === 0 || t === 1) {
|
||||
return t;
|
||||
}
|
||||
const bottom = pow(t, n) + pow(1 - t, n),
|
||||
top = bottom - 1;
|
||||
return abs(top / bottom);
|
||||
},
|
||||
|
||||
projectionratio: function (t, n) {
|
||||
// see u(t) note on http://pomax.github.io/bezierinfo/#abc
|
||||
if (n !== 2 && n !== 3) {
|
||||
return false;
|
||||
}
|
||||
if (typeof t === "undefined") {
|
||||
t = 0.5;
|
||||
} else if (t === 0 || t === 1) {
|
||||
return t;
|
||||
}
|
||||
const top = pow(1 - t, n),
|
||||
bottom = pow(t, n) + top;
|
||||
return top / bottom;
|
||||
},
|
||||
|
||||
lli8: function (x1, y1, x2, y2, x3, y3, x4, y4) {
|
||||
const nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
|
||||
ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
|
||||
d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
||||
if (d == 0) {
|
||||
return false;
|
||||
}
|
||||
return { x: nx / d, y: ny / d };
|
||||
},
|
||||
|
||||
lli4: function (p1, p2, p3, p4) {
|
||||
const x1 = p1.x,
|
||||
y1 = p1.y,
|
||||
x2 = p2.x,
|
||||
y2 = p2.y,
|
||||
x3 = p3.x,
|
||||
y3 = p3.y,
|
||||
x4 = p4.x,
|
||||
y4 = p4.y;
|
||||
return utils.lli8(x1, y1, x2, y2, x3, y3, x4, y4);
|
||||
},
|
||||
|
||||
lli: function (v1, v2) {
|
||||
return utils.lli4(v1, v1.c, v2, v2.c);
|
||||
},
|
||||
|
||||
makeline: function (p1, p2) {
|
||||
const x1 = p1.x,
|
||||
y1 = p1.y,
|
||||
x2 = p2.x,
|
||||
y2 = p2.y,
|
||||
dx = (x2 - x1) / 3,
|
||||
dy = (y2 - y1) / 3;
|
||||
return new Bezier(x1, y1, x1 + dx, y1 + dy, x1 + 2 * dx, y1 + 2 * dy, x2, y2);
|
||||
},
|
||||
|
||||
findbbox: function (sections) {
|
||||
let mx = nMax,
|
||||
my = nMax,
|
||||
MX = nMin,
|
||||
MY = nMin;
|
||||
sections.forEach(function (s) {
|
||||
const bbox = s.bbox();
|
||||
if (mx > bbox.x.min) mx = bbox.x.min;
|
||||
if (my > bbox.y.min) my = bbox.y.min;
|
||||
if (MX < bbox.x.max) MX = bbox.x.max;
|
||||
if (MY < bbox.y.max) MY = bbox.y.max;
|
||||
});
|
||||
return {
|
||||
x: { min: mx, mid: (mx + MX) / 2, max: MX, size: MX - mx },
|
||||
y: { min: my, mid: (my + MY) / 2, max: MY, size: MY - my },
|
||||
};
|
||||
},
|
||||
|
||||
shapeintersections: function (s1, bbox1, s2, bbox2, curveIntersectionThreshold) {
|
||||
if (!utils.bboxoverlap(bbox1, bbox2)) return [];
|
||||
const intersections = [];
|
||||
const a1 = [s1.startcap, s1.forward, s1.back, s1.endcap];
|
||||
const a2 = [s2.startcap, s2.forward, s2.back, s2.endcap];
|
||||
a1.forEach(function (l1) {
|
||||
if (l1.virtual) return;
|
||||
a2.forEach(function (l2) {
|
||||
if (l2.virtual) return;
|
||||
const iss = l1.intersects(l2, curveIntersectionThreshold);
|
||||
if (iss.length > 0) {
|
||||
iss.c1 = l1;
|
||||
iss.c2 = l2;
|
||||
iss.s1 = s1;
|
||||
iss.s2 = s2;
|
||||
intersections.push(iss);
|
||||
}
|
||||
});
|
||||
});
|
||||
return intersections;
|
||||
},
|
||||
|
||||
makeshape: function (forward, back, curveIntersectionThreshold) {
|
||||
const bpl = back.points.length;
|
||||
const fpl = forward.points.length;
|
||||
const start = utils.makeline(back.points[bpl - 1], forward.points[0]);
|
||||
const end = utils.makeline(forward.points[fpl - 1], back.points[0]);
|
||||
const shape = {
|
||||
startcap: start,
|
||||
forward: forward,
|
||||
back: back,
|
||||
endcap: end,
|
||||
bbox: utils.findbbox([start, forward, back, end]),
|
||||
};
|
||||
shape.intersections = function (s2) {
|
||||
return utils.shapeintersections(shape, shape.bbox, s2, s2.bbox, curveIntersectionThreshold);
|
||||
};
|
||||
return shape;
|
||||
},
|
||||
|
||||
getminmax: function (curve, d, list) {
|
||||
if (!list) return { min: 0, max: 0 };
|
||||
let min = nMax,
|
||||
max = nMin,
|
||||
t,
|
||||
c;
|
||||
if (list.indexOf(0) === -1) {
|
||||
list = [0].concat(list);
|
||||
}
|
||||
if (list.indexOf(1) === -1) {
|
||||
list.push(1);
|
||||
}
|
||||
for (let i = 0, len = list.length; i < len; i++) {
|
||||
t = list[i];
|
||||
c = curve.get(t);
|
||||
if (c[d] < min) {
|
||||
min = c[d];
|
||||
}
|
||||
if (c[d] > max) {
|
||||
max = c[d];
|
||||
}
|
||||
}
|
||||
return { min: min, mid: (min + max) / 2, max: max, size: max - min };
|
||||
},
|
||||
|
||||
align: function (points, line) {
|
||||
const tx = line.p1.x,
|
||||
ty = line.p1.y,
|
||||
a = -atan2(line.p2.y - ty, line.p2.x - tx),
|
||||
d = function (v) {
|
||||
return {
|
||||
x: (v.x - tx) * cos(a) - (v.y - ty) * sin(a),
|
||||
y: (v.x - tx) * sin(a) + (v.y - ty) * cos(a),
|
||||
};
|
||||
};
|
||||
return points.map(d);
|
||||
},
|
||||
|
||||
roots: function (points, line) {
|
||||
line = line || { p1: { x: 0, y: 0 }, p2: { x: 1, y: 0 } };
|
||||
|
||||
const order = points.length - 1;
|
||||
const aligned = utils.align(points, line);
|
||||
const reduce = function (t) {
|
||||
return 0 <= t && t <= 1;
|
||||
};
|
||||
|
||||
if (order === 2) {
|
||||
const a = aligned[0].y,
|
||||
b = aligned[1].y,
|
||||
c = aligned[2].y,
|
||||
d = a - 2 * b + c;
|
||||
if (d !== 0) {
|
||||
const m1 = -sqrt(b * b - a * c),
|
||||
m2 = -a + b,
|
||||
v1 = -(m1 + m2) / d,
|
||||
v2 = -(-m1 + m2) / d;
|
||||
return [v1, v2].filter(reduce);
|
||||
} else if (b !== c && d === 0) {
|
||||
return [(2 * b - c) / (2 * b - 2 * c)].filter(reduce);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// see http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm
|
||||
const pa = aligned[0].y,
|
||||
pb = aligned[1].y,
|
||||
pc = aligned[2].y,
|
||||
pd = aligned[3].y;
|
||||
|
||||
let d = -pa + 3 * pb - 3 * pc + pd,
|
||||
a = 3 * pa - 6 * pb + 3 * pc,
|
||||
b = -3 * pa + 3 * pb,
|
||||
c = pa;
|
||||
|
||||
if (utils.approximately(d, 0)) {
|
||||
// this is not a cubic curve.
|
||||
if (utils.approximately(a, 0)) {
|
||||
// in fact, this is not a quadratic curve either.
|
||||
if (utils.approximately(b, 0)) {
|
||||
// in fact in fact, there are no solutions.
|
||||
return [];
|
||||
}
|
||||
// linear solution:
|
||||
return [-c / b].filter(reduce);
|
||||
}
|
||||
// quadratic solution:
|
||||
const q = sqrt(b * b - 4 * a * c),
|
||||
a2 = 2 * a;
|
||||
return [(q - b) / a2, (-b - q) / a2].filter(reduce);
|
||||
}
|
||||
|
||||
// at this point, we know we need a cubic solution:
|
||||
|
||||
a /= d;
|
||||
b /= d;
|
||||
c /= d;
|
||||
|
||||
const p = (3 * b - a * a) / 3,
|
||||
p3 = p / 3,
|
||||
q = (2 * a * a * a - 9 * a * b + 27 * c) / 27,
|
||||
q2 = q / 2,
|
||||
discriminant = q2 * q2 + p3 * p3 * p3;
|
||||
|
||||
let u1, v1, x1, x2, x3;
|
||||
if (discriminant < 0) {
|
||||
const mp3 = -p / 3,
|
||||
mp33 = mp3 * mp3 * mp3,
|
||||
r = sqrt(mp33),
|
||||
t = -q / (2 * r),
|
||||
cosphi = t < -1 ? -1 : t > 1 ? 1 : t,
|
||||
phi = acos(cosphi),
|
||||
crtr = crt(r),
|
||||
t1 = 2 * crtr;
|
||||
x1 = t1 * cos(phi / 3) - a / 3;
|
||||
x2 = t1 * cos((phi + tau) / 3) - a / 3;
|
||||
x3 = t1 * cos((phi + 2 * tau) / 3) - a / 3;
|
||||
return [x1, x2, x3].filter(reduce);
|
||||
} else if (discriminant === 0) {
|
||||
u1 = q2 < 0 ? crt(-q2) : -crt(q2);
|
||||
x1 = 2 * u1 - a / 3;
|
||||
x2 = -u1 - a / 3;
|
||||
return [x1, x2].filter(reduce);
|
||||
} else {
|
||||
const sd = sqrt(discriminant);
|
||||
u1 = crt(-q2 + sd);
|
||||
v1 = crt(q2 + sd);
|
||||
return [u1 - v1 - a / 3].filter(reduce);
|
||||
}
|
||||
},
|
||||
|
||||
droots: function (p) {
|
||||
// quadratic roots are easy
|
||||
if (p.length === 3) {
|
||||
const a = p[0],
|
||||
b = p[1],
|
||||
c = p[2],
|
||||
d = a - 2 * b + c;
|
||||
if (d !== 0) {
|
||||
const m1 = -sqrt(b * b - a * c),
|
||||
m2 = -a + b,
|
||||
v1 = -(m1 + m2) / d,
|
||||
v2 = -(-m1 + m2) / d;
|
||||
return [v1, v2];
|
||||
} else if (b !== c && d === 0) {
|
||||
return [(2 * b - c) / (2 * (b - c))];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// linear roots are even easier
|
||||
if (p.length === 2) {
|
||||
const a = p[0],
|
||||
b = p[1];
|
||||
if (a !== b) {
|
||||
return [a / (a - b)];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
curvature: function (t, d1, d2, _3d, kOnly) {
|
||||
let num,
|
||||
dnm,
|
||||
adk,
|
||||
dk,
|
||||
k = 0,
|
||||
r = 0;
|
||||
|
||||
//
|
||||
// We're using the following formula for curvature:
|
||||
//
|
||||
// x'y" - y'x"
|
||||
// k(t) = ------------------
|
||||
// (x'² + y'²)^(3/2)
|
||||
//
|
||||
// from https://en.wikipedia.org/wiki/Radius_of_curvature#Definition
|
||||
//
|
||||
// With it corresponding 3D counterpart:
|
||||
//
|
||||
// sqrt( (y'z" - y"z')² + (z'x" - z"x')² + (x'y" - x"y')²)
|
||||
// k(t) = -------------------------------------------------------
|
||||
// (x'² + y'² + z'²)^(3/2)
|
||||
//
|
||||
|
||||
const d = utils.compute(t, d1);
|
||||
const dd = utils.compute(t, d2);
|
||||
const qdsum = d.x * d.x + d.y * d.y;
|
||||
|
||||
if (_3d) {
|
||||
num = sqrt(pow(d.y * dd.z - dd.y * d.z, 2) + pow(d.z * dd.x - dd.z * d.x, 2) + pow(d.x * dd.y - dd.x * d.y, 2));
|
||||
dnm = pow(qdsum + d.z * d.z, 3 / 2);
|
||||
} else {
|
||||
num = d.x * dd.y - d.y * dd.x;
|
||||
dnm = pow(qdsum, 3 / 2);
|
||||
}
|
||||
|
||||
if (num === 0 || dnm === 0) {
|
||||
return { k: 0, r: 0 };
|
||||
}
|
||||
|
||||
k = num / dnm;
|
||||
r = dnm / num;
|
||||
|
||||
// We're also computing the derivative of kappa, because
|
||||
// there is value in knowing the rate of change for the
|
||||
// curvature along the curve. And we're just going to
|
||||
// ballpark it based on an epsilon.
|
||||
if (!kOnly) {
|
||||
// compute k'(t) based on the interval before, and after it,
|
||||
// to at least try to not introduce forward/backward pass bias.
|
||||
const pk = utils.curvature(t - 0.001, d1, d2, _3d, true).k;
|
||||
const nk = utils.curvature(t + 0.001, d1, d2, _3d, true).k;
|
||||
dk = (nk - k + (k - pk)) / 2;
|
||||
adk = (abs(nk - k) + abs(k - pk)) / 2;
|
||||
}
|
||||
|
||||
return { k: k, r: r, dk: dk, adk: adk };
|
||||
},
|
||||
|
||||
inflections: function (points) {
|
||||
if (points.length < 4) return [];
|
||||
|
||||
// FIXME: TODO: add in inflection abstraction for quartic+ curves?
|
||||
|
||||
const p = utils.align(points, { p1: points[0], p2: points.slice(-1)[0] }),
|
||||
a = p[2].x * p[1].y,
|
||||
b = p[3].x * p[1].y,
|
||||
c = p[1].x * p[2].y,
|
||||
d = p[3].x * p[2].y,
|
||||
v1 = 18 * (-3 * a + 2 * b + 3 * c - d),
|
||||
v2 = 18 * (3 * a - b - 3 * c),
|
||||
v3 = 18 * (c - a);
|
||||
|
||||
if (utils.approximately(v1, 0)) {
|
||||
if (!utils.approximately(v2, 0)) {
|
||||
let t = -v3 / v2;
|
||||
if (0 <= t && t <= 1) return [t];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const trm = v2 * v2 - 4 * v1 * v3,
|
||||
sq = Math.sqrt(trm),
|
||||
d2 = 2 * v1;
|
||||
|
||||
if (utils.approximately(d2, 0)) return [];
|
||||
|
||||
return [(sq - v2) / d2, -(v2 + sq) / d2].filter(function (r) {
|
||||
return 0 <= r && r <= 1;
|
||||
});
|
||||
},
|
||||
|
||||
bboxoverlap: function (b1, b2) {
|
||||
const dims = ["x", "y"],
|
||||
len = dims.length;
|
||||
|
||||
for (let i = 0, dim, l, t, d; i < len; i++) {
|
||||
dim = dims[i];
|
||||
l = b1[dim].mid;
|
||||
t = b2[dim].mid;
|
||||
d = (b1[dim].size + b2[dim].size) / 2;
|
||||
if (abs(l - t) >= d) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
expandbox: function (bbox, _bbox) {
|
||||
if (_bbox.x.min < bbox.x.min) {
|
||||
bbox.x.min = _bbox.x.min;
|
||||
}
|
||||
if (_bbox.y.min < bbox.y.min) {
|
||||
bbox.y.min = _bbox.y.min;
|
||||
}
|
||||
if (_bbox.z && _bbox.z.min < bbox.z.min) {
|
||||
bbox.z.min = _bbox.z.min;
|
||||
}
|
||||
if (_bbox.x.max > bbox.x.max) {
|
||||
bbox.x.max = _bbox.x.max;
|
||||
}
|
||||
if (_bbox.y.max > bbox.y.max) {
|
||||
bbox.y.max = _bbox.y.max;
|
||||
}
|
||||
if (_bbox.z && _bbox.z.max > bbox.z.max) {
|
||||
bbox.z.max = _bbox.z.max;
|
||||
}
|
||||
bbox.x.mid = (bbox.x.min + bbox.x.max) / 2;
|
||||
bbox.y.mid = (bbox.y.min + bbox.y.max) / 2;
|
||||
if (bbox.z) {
|
||||
bbox.z.mid = (bbox.z.min + bbox.z.max) / 2;
|
||||
}
|
||||
bbox.x.size = bbox.x.max - bbox.x.min;
|
||||
bbox.y.size = bbox.y.max - bbox.y.min;
|
||||
if (bbox.z) {
|
||||
bbox.z.size = bbox.z.max - bbox.z.min;
|
||||
}
|
||||
},
|
||||
|
||||
pairiteration: function (c1, c2, curveIntersectionThreshold) {
|
||||
const c1b = c1.bbox(),
|
||||
c2b = c2.bbox(),
|
||||
r = 100000,
|
||||
threshold = curveIntersectionThreshold || 0.5;
|
||||
|
||||
if (c1b.x.size + c1b.y.size < threshold && c2b.x.size + c2b.y.size < threshold) {
|
||||
return [(((r * (c1._t1 + c1._t2)) / 2) | 0) / r + "/" + (((r * (c2._t1 + c2._t2)) / 2) | 0) / r];
|
||||
}
|
||||
|
||||
let cc1 = c1.split(0.5),
|
||||
cc2 = c2.split(0.5),
|
||||
pairs = [
|
||||
{ left: cc1.left, right: cc2.left },
|
||||
{ left: cc1.left, right: cc2.right },
|
||||
{ left: cc1.right, right: cc2.right },
|
||||
{ left: cc1.right, right: cc2.left },
|
||||
];
|
||||
|
||||
pairs = pairs.filter(function (pair) {
|
||||
return utils.bboxoverlap(pair.left.bbox(), pair.right.bbox());
|
||||
});
|
||||
|
||||
let results = [];
|
||||
|
||||
if (pairs.length === 0) return results;
|
||||
|
||||
pairs.forEach(function (pair) {
|
||||
results = results.concat(utils.pairiteration(pair.left, pair.right, threshold));
|
||||
});
|
||||
|
||||
results = results.filter(function (v, i) {
|
||||
return results.indexOf(v) === i;
|
||||
});
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
getccenter: function (p1, p2, p3) {
|
||||
const dx1 = p2.x - p1.x,
|
||||
dy1 = p2.y - p1.y,
|
||||
dx2 = p3.x - p2.x,
|
||||
dy2 = p3.y - p2.y,
|
||||
dx1p = dx1 * cos(quart) - dy1 * sin(quart),
|
||||
dy1p = dx1 * sin(quart) + dy1 * cos(quart),
|
||||
dx2p = dx2 * cos(quart) - dy2 * sin(quart),
|
||||
dy2p = dx2 * sin(quart) + dy2 * cos(quart),
|
||||
// chord midpoints
|
||||
mx1 = (p1.x + p2.x) / 2,
|
||||
my1 = (p1.y + p2.y) / 2,
|
||||
mx2 = (p2.x + p3.x) / 2,
|
||||
my2 = (p2.y + p3.y) / 2,
|
||||
// midpoint offsets
|
||||
mx1n = mx1 + dx1p,
|
||||
my1n = my1 + dy1p,
|
||||
mx2n = mx2 + dx2p,
|
||||
my2n = my2 + dy2p,
|
||||
// intersection of these lines:
|
||||
arc = utils.lli8(mx1, my1, mx1n, my1n, mx2, my2, mx2n, my2n),
|
||||
r = utils.dist(arc, p1);
|
||||
|
||||
// arc start/end values, over mid point:
|
||||
let s = atan2(p1.y - arc.y, p1.x - arc.x),
|
||||
m = atan2(p2.y - arc.y, p2.x - arc.x),
|
||||
e = atan2(p3.y - arc.y, p3.x - arc.x),
|
||||
_;
|
||||
|
||||
// determine arc direction (cw/ccw correction)
|
||||
if (s < e) {
|
||||
// if s<m<e, arc(s, e)
|
||||
// if m<s<e, arc(e, s + tau)
|
||||
// if s<e<m, arc(e, s + tau)
|
||||
if (s > m || m > e) {
|
||||
s += tau;
|
||||
}
|
||||
if (s > e) {
|
||||
_ = e;
|
||||
e = s;
|
||||
s = _;
|
||||
}
|
||||
} else {
|
||||
// if e<m<s, arc(e, s)
|
||||
// if m<e<s, arc(s, e + tau)
|
||||
// if e<s<m, arc(s, e + tau)
|
||||
if (e < m && m < s) {
|
||||
_ = e;
|
||||
e = s;
|
||||
s = _;
|
||||
} else {
|
||||
e += tau;
|
||||
}
|
||||
}
|
||||
// assign and done.
|
||||
arc.s = s;
|
||||
arc.e = e;
|
||||
arc.r = r;
|
||||
return arc;
|
||||
},
|
||||
|
||||
numberSort: function (a, b) {
|
||||
return a - b;
|
||||
},
|
||||
};
|
||||
|
||||
export { utils };
|
Reference in New Issue
Block a user