diff --git a/docs/chapters/control/lerp.js b/docs/chapters/control/lerp.js
index 553b110e..30f4f93a 100644
--- a/docs/chapters/control/lerp.js
+++ b/docs/chapters/control/lerp.js
@@ -33,7 +33,7 @@ setup() {
// and capturing the resulting Shape object that yields. We'll draw
// those in the draw() function.
this.shapes = this.interpolationFunctions.map(f => plot(f, 0, 1, degree*5) );
-
+ noGrid();
setSlider(`.slide-control`, `position`, 0)
}
diff --git a/docs/images/chapters/control/1b8c5e574dc67bfb0afc3fb0a8727378.png b/docs/images/chapters/control/1b8c5e574dc67bfb0afc3fb0a8727378.png
new file mode 100644
index 00000000..39aaa2f1
Binary files /dev/null and b/docs/images/chapters/control/1b8c5e574dc67bfb0afc3fb0a8727378.png differ
diff --git a/docs/images/chapters/control/8332e5d34b7344bbee2a2e1f4521ce46.png b/docs/images/chapters/control/8332e5d34b7344bbee2a2e1f4521ce46.png
new file mode 100644
index 00000000..29e2128f
Binary files /dev/null and b/docs/images/chapters/control/8332e5d34b7344bbee2a2e1f4521ce46.png differ
diff --git a/docs/images/chapters/control/c26d2655e8741ef7e2eeb4f6554fc7a5.png b/docs/images/chapters/control/c26d2655e8741ef7e2eeb4f6554fc7a5.png
new file mode 100644
index 00000000..9894b8c7
Binary files /dev/null and b/docs/images/chapters/control/c26d2655e8741ef7e2eeb4f6554fc7a5.png differ
diff --git a/docs/index.html b/docs/index.html
index 9c0de75e..68e6ac6f 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -38,7 +38,7 @@
-
+
@@ -1008,7 +1008,7 @@ function Bezier(3,t):
Scripts are disabled. Showing fallback image.
-
+
@@ -1016,7 +1016,7 @@ function Bezier(3,t):
Scripts are disabled. Showing fallback image.
-
+
@@ -1024,7 +1024,7 @@ function Bezier(3,t):
Scripts are disabled. Showing fallback image.
-
+
diff --git a/docs/ja-JP/index.html b/docs/ja-JP/index.html
index 5853d12f..4feda82c 100644
--- a/docs/ja-JP/index.html
+++ b/docs/ja-JP/index.html
@@ -41,7 +41,7 @@
-
+
@@ -974,7 +974,7 @@ function Bezier(3,t):
>
Scripts are disabled. Showing fallback image.
-
+
@@ -990,7 +990,7 @@ function Bezier(3,t):
>
Scripts are disabled. Showing fallback image.
-
+
@@ -1006,7 +1006,7 @@ function Bezier(3,t):
>
Scripts are disabled. Showing fallback image.
-
+
diff --git a/docs/js/custom-element/api/graphics-api.js b/docs/js/custom-element/api/graphics-api.js
index 373d5146..48b4c1c0 100644
--- a/docs/js/custom-element/api/graphics-api.js
+++ b/docs/js/custom-element/api/graphics-api.js
@@ -12,6 +12,7 @@ const MOUSE_PRECISION_ZONE = 5;
const TOUCH_PRECISION_ZONE = 30;
let CURRENT_HUE = 0;
+let CURRENT_CURSOR = `pointer`;
/**
* Our Graphics API, which is the "public" side of the API.
@@ -276,11 +277,34 @@ class GraphicsAPI extends BaseAPI {
}
}
+ /**
+ * Set a (CSS) margin around the canvas
+ */
+ setMargin(width = 0) {
+ this.canvas.style.marginTop = `${width}px`;
+ this.canvas.style.marginBottom = `${width}px`;
+ }
+
+ /**
+ * Hide the cursor in a way that we can restore later.
+ */
+ hideCursor() {
+ this.canvas.style.cursor = `none`;
+ }
+
+ /**
+ * Rebind the cursor to what it should be.
+ */
+ showCursor() {
+ this.canvas.style.cursor = CURRENT_CURSOR;
+ }
+
/**
* Set the cursor type while the cursor is over the canvas
*/
setCursor(type) {
- this.canvas.style.cursor = type;
+ CURRENT_CURSOR = type;
+ this.showCursor();
}
/**
diff --git a/docs/js/custom-element/api/impart-slider-logic.js b/docs/js/custom-element/api/impart-slider-logic.js
index b026b94f..f9d902e7 100644
--- a/docs/js/custom-element/api/impart-slider-logic.js
+++ b/docs/js/custom-element/api/impart-slider-logic.js
@@ -14,7 +14,7 @@ export default function impartSliderLogic(GraphicsAPI) {
slider.setAttribute(`value`, value);
slider.setAttribute(`class`, classes);
this.element.append(slider);
- this.setSlider(slider, propname, value, transform);
+ return this.setSlider(slider, propname, value, transform);
}
};
diff --git a/docs/js/custom-element/graphics-element.js b/docs/js/custom-element/graphics-element.js
index 8ad5758f..c11db1fc 100644
--- a/docs/js/custom-element/graphics-element.js
+++ b/docs/js/custom-element/graphics-element.js
@@ -78,7 +78,7 @@ class GraphicsElement extends CustomElement {
:host([hidden]) { display: none; }
:host { max-width: calc(2em + ${this.getAttribute(`width`)}px); }
:host style { display: none; }
- :host .top-title { display: flex; flex-direction: row; justify-content: space-between; }
+ :host .top-title { display: flex; flex-direction: row-reverse; justify-content: space-between; }
:host canvas { position: relative; z-index: 1; display: block; margin: auto; border-radius: 0; box-sizing: content-box!important; border: 1px solid lightgrey; }
:host canvas:focus { border: 1px solid red; }
:host a.view-source { font-size: 60%; text-decoration: none; }
@@ -204,6 +204,8 @@ class GraphicsElement extends CustomElement {
return `${main} "${modulebase}${group}"`;
});
+ this.linkableCode = code;
+
// Then, step 2: split up the code into "global" vs. "class" code.
const split = splitCodeSections(code);
const globalCode = split.quasiGlobal;
@@ -328,19 +330,21 @@ class GraphicsElement extends CustomElement {
const toptitle = document.createElement(`div`);
toptitle.classList.add(`top-title`);
- const a = document.createElement(`a`);
- a.classList.add(`view-source`);
- a.textContent = this.getAttribute(`viewSource`) || `view source`;
- a.href = this.src;
- a.target = `_blank`;
- toptitle.append(a);
-
const r = document.createElement(`button`);
r.classList.add(`reset`);
r.textContent = this.getAttribute(`reset`) || `reset`;
r.addEventListener(`click`, () => this.reset());
toptitle.append(r);
+ if (this.src) {
+ const a = document.createElement(`a`);
+ a.classList.add(`view-source`);
+ a.textContent = this.getAttribute(`viewSource`) || `view source`;
+ a.href = this.src;
+ a.target = `_blank`;
+ toptitle.append(a);
+ }
+
if (this.label) slotParent.insertBefore(toptitle, this.canvas);
}
}
diff --git a/docs/js/custom-element/lib/bezierjs/bezier.js b/docs/js/custom-element/lib/bezierjs/bezier.js
index 8d7d3f4f..1bf1e3c3 100644
--- a/docs/js/custom-element/lib/bezierjs/bezier.js
+++ b/docs/js/custom-element/lib/bezierjs/bezier.js
@@ -1,3 +1,1208 @@
+// 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 || m > e) {
+ s += tau;
+ }
+ if (s > e) {
+ _ = e;
+ e = s;
+ s = _;
+ }
+ } else {
+ // if e 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();
+}
+
+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 = normalizePath(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);
+}
+
/**
A javascript Bezier curve library by Pomax.
@@ -6,15 +1211,9 @@
This code is MIT licensed.
**/
-import { utils } from "./utils.js";
-import { PolyBezier } from "./poly-bezier.js";
-import { convertPath } from "./svg-to-beziers.js";
-
// math-inlining.
-const { abs, min, max, cos, sin, acos, sqrt } = Math;
-const pi = Math.PI;
-// a zero coordinate, which is surprisingly useful
-const ZERO = { x: 0, y: 0, z: 0 };
+const { abs: abs$1, min, max, cos: cos$1, sin: sin$1, acos: acos$1, sqrt: sqrt$1 } = Math;
+const pi$1 = Math.PI;
/**
* Bezier curve constructor.
@@ -77,7 +1276,7 @@ class Bezier {
this.dimlen = dims.length;
const aligned = utils.align(points, { p1: points[0], p2: points[order] });
- this._linear = !aligned.some((p) => abs(p.y) > 0.0001);
+ this._linear = !aligned.some((p) => abs$1(p.y) > 0.0001);
this._lut = [];
@@ -292,6 +1491,7 @@ class Bezier {
ft = t;
}
}
+ ft = ft < 0 ? 0 : ft > 1 ? 1 : ft;
p = this.compute(ft);
p.t = ft;
p.d = mdist;
@@ -356,7 +1556,7 @@ class Bezier {
__normal2(t) {
const d = this.derivative(t);
- const q = sqrt(d.x * d.x + d.y * d.y);
+ const q = sqrt$1(d.x * d.x + d.y * d.y);
return { x: -d.y / q, y: d.x / q };
}
@@ -364,8 +1564,8 @@ class Bezier {
// see http://stackoverflow.com/questions/25453159
const r1 = this.derivative(t),
r2 = this.derivative(t + 0.01),
- q1 = sqrt(r1.x * r1.x + r1.y * r1.y + r1.z * r1.z),
- q2 = sqrt(r2.x * r2.x + r2.y * r2.y + r2.z * r2.z);
+ q1 = sqrt$1(r1.x * r1.x + r1.y * r1.y + r1.z * r1.z),
+ q2 = sqrt$1(r2.x * r2.x + r2.y * r2.y + r2.z * r2.z);
r1.x /= q1;
r1.y /= q1;
r1.z /= q1;
@@ -378,7 +1578,7 @@ class Bezier {
y: r2.z * r1.x - r2.x * r1.z,
z: r2.x * r1.y - r2.y * r1.x,
};
- const m = sqrt(c.x * c.x + c.y * c.y + c.z * c.z);
+ const m = sqrt$1(c.x * c.x + c.y * c.y + c.z * c.z);
c.x /= m;
c.y /= m;
c.z /= m;
@@ -545,7 +1745,7 @@ class Bezier {
if (this._3d) {
s += n1.z * n2.z;
}
- return abs(acos(s)) < pi / 3;
+ return abs$1(acos$1(s)) < pi$1 / 3;
}
reduce() {
@@ -584,7 +1784,7 @@ class Bezier {
segment = p1.split(t1, t2);
if (!segment.simple()) {
t2 -= step;
- if (abs(t1 - t2) < step) {
+ if (abs$1(t1 - t2) < step) {
// we can never form a reduction
return [];
}
@@ -662,7 +1862,7 @@ class Bezier {
};
var rc = distanceFn ? distanceFn((t + 1) / order) : d;
if (distanceFn && !clockwise) rc = -rc;
- var m = sqrt(ov.x * ov.x + ov.y * ov.y);
+ var m = sqrt$1(ov.x * ov.x + ov.y * ov.y);
ov.x /= m;
ov.y /= m;
np[t + 1] = {
@@ -697,7 +1897,7 @@ class Bezier {
// form curve oulines
reduced.forEach(function (segment) {
- slen = segment.length();
+ const slen = segment.length();
if (graduated) {
fcurves.push(segment.scale(linearDistanceFunction(d1, d3, tlen, alen, slen)));
bcurves.push(segment.scale(linearDistanceFunction(-d2, -d4, tlen, alen, slen)));
@@ -728,8 +1928,7 @@ class Bezier {
be = bcurves[0].points[0],
ls = utils.makeline(bs, fs),
le = utils.makeline(fe, be),
- segments = [ls].concat(fcurves).concat([le]).concat(bcurves),
- slen = segments.length;
+ segments = [ls].concat(fcurves).concat([le]).concat(bcurves);
return new PolyBezier(segments);
}
@@ -782,7 +1981,7 @@ class Bezier {
left = reduced.slice(i, i + 1);
right = reduced.slice(i + 2);
result = this.curveintersects(left, right, curveIntersectionThreshold);
- results = results.concat(result);
+ results.push(...result);
}
return results;
}
@@ -820,7 +2019,7 @@ class Bezier {
ref = utils.dist(pc, np1),
d1 = utils.dist(pc, c1),
d2 = utils.dist(pc, c2);
- return abs(d1 - ref) + abs(d2 - ref);
+ return abs$1(d1 - ref) + abs$1(d2 - ref);
}
_iterate(errorThreshold, circles) {
@@ -848,15 +2047,13 @@ class Bezier {
// numbers:
let t_m = t_e,
- prev_e = 1,
- step = 0;
+ prev_e = 1;
// step 2: find the best possible arc
do {
prev_good = curr_good;
prev_arc = arc;
t_m = (t_s + t_e) / 2;
- step++;
np2 = this.get(t_m);
np3 = this.get(t_e);
@@ -886,8 +2083,8 @@ class Bezier {
// the arc's end angle is correct with respect to the bezier end point.
if (t_e > 1) {
let d = {
- x: arc.x + arc.r * cos(arc.e),
- y: arc.y + arc.r * sin(arc.e),
+ x: arc.x + arc.r * cos$1(arc.e),
+ y: arc.y + arc.r * sin$1(arc.e),
};
arc.e += utils.angle({ x: arc.x, y: arc.y }, d, this.get(1));
}
diff --git a/docs/news/2020-09-18.html b/docs/news/2020-09-18.html
index 7095f9a6..f03a136a 100644
--- a/docs/news/2020-09-18.html
+++ b/docs/news/2020-09-18.html
@@ -34,7 +34,7 @@
-
+
diff --git a/docs/news/index.html b/docs/news/index.html
index 6a05df53..9d99f65e 100644
--- a/docs/news/index.html
+++ b/docs/news/index.html
@@ -33,7 +33,7 @@
-
+
diff --git a/docs/news/rss.xml b/docs/news/rss.xml
index 5691b203..cd832592 100644
--- a/docs/news/rss.xml
+++ b/docs/news/rss.xml
@@ -6,7 +6,7 @@
News updates for the primer on Bézier Curves by Pomax
en-GB
- Fri Oct 30 2020 20:22:30 +00:00
+ Sun Nov 01 2020 14:42:49 +00:00
https://pomax.github.io/bezierinfo/images/og-image.png
A Primer on Bézier Curves
diff --git a/docs/uk-UA/index.html b/docs/uk-UA/index.html
index 1206eb9c..4f6e905c 100644
--- a/docs/uk-UA/index.html
+++ b/docs/uk-UA/index.html
@@ -39,7 +39,7 @@
-
+
@@ -1039,7 +1039,7 @@ function Bezier(3,t):
>
Скрипти вимкнено. показує резервний.
-
+
@@ -1055,7 +1055,7 @@ function Bezier(3,t):
>
Скрипти вимкнено. показує резервний.
-
+
@@ -1071,7 +1071,7 @@ function Bezier(3,t):
>
Скрипти вимкнено. показує резервний.
-
+
diff --git a/docs/zh-CN/index.html b/docs/zh-CN/index.html
index c4e91b36..431be9ea 100644
--- a/docs/zh-CN/index.html
+++ b/docs/zh-CN/index.html
@@ -41,7 +41,7 @@
-
+
@@ -946,7 +946,7 @@ function Bezier(3,t):
>
Scripts are disabled. Showing fallback image.
-
+
@@ -962,7 +962,7 @@ function Bezier(3,t):
>
Scripts are disabled. Showing fallback image.
-
+
@@ -978,7 +978,7 @@ function Bezier(3,t):
>
Scripts are disabled. Showing fallback image.
-
+