672 lines
21 KiB
JavaScript
672 lines
21 KiB
JavaScript
import Components from '../objects/components.js'
|
|
import selector from '../util/selector.js'
|
|
import numbers from '../interpolation/numbers.js'
|
|
|
|
import {toPathString,onStartCubicMorph} from './svgCubicMorphBase.js'
|
|
|
|
// const SVGMorph = { property : 'path', defaultValue: [], interpolators: {numbers} }, functions = { prepareStart, prepareProperty, onStart, crossCheck }
|
|
|
|
// Component Util
|
|
const INVALID_INPUT = 'Invalid path value'
|
|
|
|
/* Raphael.js - path (https://github.com/DmitryBaranovskiy/raphael)
|
|
* Copyright © 2008-2013 Dmitry Baranovskiy (http://dmitrybaranovskiy.github.io/raphael/)
|
|
* Copyright © 2008-2013 Sencha Labs (http://sencha.com)
|
|
* Licensed under the MIT (http://dmitrybaranovskiy.github.io/raphael/license.html) license.
|
|
* KUTE.js modifications
|
|
* - parsePathString is now moved outside of main functions
|
|
* - processPath moved outside pathToAbsolute function body
|
|
* - fixArc moved outside path2curve function body
|
|
* - fixM moved outside path2curve function body
|
|
* - minor fixes like "a != b" => "a !== b", various undefined/unused variables
|
|
*/
|
|
|
|
// http://schepers.cc/getting-to-the-point
|
|
function catmullRom2bezier(crp, z) {
|
|
const d = [];
|
|
for (let i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
|
|
const p = [
|
|
{x: +crp[i - 2], y: +crp[i - 1]},
|
|
{x: +crp[i], y: +crp[i + 1]},
|
|
{x: +crp[i + 2], y: +crp[i + 3]},
|
|
{x: +crp[i + 4], y: +crp[i + 5]}
|
|
];
|
|
if (z) {
|
|
if (!i) {
|
|
p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
|
|
} else if (iLen - 4 == i) {
|
|
p[3] = {x: +crp[0], y: +crp[1]};
|
|
} else if (iLen - 2 == i) {
|
|
p[2] = {x: +crp[0], y: +crp[1]};
|
|
p[3] = {x: +crp[2], y: +crp[3]};
|
|
}
|
|
} else {
|
|
if (iLen - 4 == i) {
|
|
p[3] = p[2];
|
|
} else if (!i) {
|
|
p[0] = {x: +crp[i], y: +crp[i + 1]};
|
|
}
|
|
}
|
|
d.push(["C",
|
|
(-p[0].x + 6 * p[1].x + p[2].x) / 6,
|
|
(-p[0].y + 6 * p[1].y + p[2].y) / 6,
|
|
(p[1].x + 6 * p[2].x - p[3].x) / 6,
|
|
(p[1].y + 6*p[2].y - p[3].y) / 6,
|
|
p[2].x,
|
|
p[2].y
|
|
])
|
|
}
|
|
return d
|
|
}
|
|
|
|
function ellipsePath(x, y, rx, ry, a) {
|
|
if (a == null && ry == null) {
|
|
ry = rx;
|
|
}
|
|
x = +x;
|
|
y = +y;
|
|
rx = +rx;
|
|
ry = +ry;
|
|
let res;
|
|
if (a != null) {
|
|
const rad = Math.PI / 180,
|
|
x1 = x + rx * Math.cos(-ry * rad),
|
|
x2 = x + rx * Math.cos(-a * rad),
|
|
y1 = y + rx * Math.sin(-ry * rad),
|
|
y2 = y + rx * Math.sin(-a * rad);
|
|
res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]];
|
|
} else {
|
|
res = [
|
|
["M", x, y],
|
|
["m", 0, -ry],
|
|
["a", rx, ry, 0, 1, 1, 0, 2 * ry],
|
|
["a", rx, ry, 0, 1, 1, 0, -2 * ry],
|
|
["z"]
|
|
];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
// Parses given path string into an array of arrays of path segments
|
|
function parsePathString(pathString) {
|
|
if (!pathString) {
|
|
return null;
|
|
}
|
|
if( pathString instanceof Array ) {
|
|
return pathString;
|
|
} else {
|
|
// tracer minifier cannot compute this string for some reason
|
|
// let spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029";
|
|
let spaces = `\\${("x09|x0a|x0b|x0c|x0d|x20|xa0|u1680|u180e|u2000|u2001|u2002|u2003|u2004|u2005|u2006|u2007|u2008|u2009|u200a|u202f|u205f|u3000|u2028|u2029").split('|').join('\\')}`,
|
|
pathCommand = new RegExp(`([a-z])[${spaces},]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[${spaces}]*,?[${spaces}]*)+)`, `ig`),
|
|
pathValues = new RegExp(`(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[${spaces}]*,?[${spaces}]*`, `ig`),
|
|
paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0},
|
|
data = [];
|
|
|
|
pathString.replace(pathCommand, (a, b, c) => {
|
|
let params = [], name = b.toLowerCase();
|
|
|
|
c.replace(pathValues, (a, b) => {
|
|
b && params.push(+b);
|
|
});
|
|
if (name == "m" && params.length > 2) {
|
|
data.push([b].concat(params.splice(0, 2)));
|
|
name = "l";
|
|
b = b == "m" ? "l" : "L";
|
|
}
|
|
if (name == "o" && params.length == 1) {
|
|
data.push([b, params[0]]);
|
|
}
|
|
if (name == "r") {
|
|
data.push([b].concat(params));
|
|
} else while (params.length >= paramCounts[name]) {
|
|
data.push([b].concat(params.splice(0, paramCounts[name])));
|
|
if (!paramCounts[name]) {
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
function pathToAbsolute(pathArray) {
|
|
pathArray = parsePathString(pathArray);
|
|
|
|
if (!pathArray || !pathArray.length) {
|
|
return [["M", 0, 0]];
|
|
}
|
|
let res = [], x = 0, y = 0, mx = 0, my = 0, start = 0, pa0;
|
|
if (pathArray[0][0] === "M") {
|
|
x = +pathArray[0][1];
|
|
y = +pathArray[0][2];
|
|
mx = x;
|
|
my = y;
|
|
start++;
|
|
res[0] = ["M", x, y];
|
|
}
|
|
const crz = pathArray.length === 3 &&
|
|
pathArray[0][0] === "M" &&
|
|
pathArray[1][0].toUpperCase() === "R" &&
|
|
pathArray[2][0].toUpperCase() === "Z";
|
|
for (let r, pa, i = start, ii = pathArray.length; i < ii; i++) {
|
|
res.push(r = []);
|
|
pa = pathArray[i];
|
|
pa0 = pa[0];
|
|
if (pa0 !== pa0.toUpperCase()) {
|
|
r[0] = pa0.toUpperCase();
|
|
switch (r[0]) {
|
|
case "A":
|
|
r[1] = pa[1];
|
|
r[2] = pa[2];
|
|
r[3] = pa[3];
|
|
r[4] = pa[4];
|
|
r[5] = pa[5];
|
|
r[6] = +pa[6] + x;
|
|
r[7] = +pa[7] + y;
|
|
break;
|
|
case "V":
|
|
r[1] = +pa[1] + y;
|
|
break;
|
|
case "H":
|
|
r[1] = +pa[1] + x;
|
|
break;
|
|
case "R":
|
|
var dots = [x, y].concat(pa.slice(1));
|
|
for (var j = 2, jj = dots.length; j < jj; j++) {
|
|
dots[j] = +dots[j] + x;
|
|
dots[++j] = +dots[j] + y;
|
|
}
|
|
res.pop();
|
|
res = res.concat(catmullRom2bezier(dots, crz));
|
|
break;
|
|
case "O":
|
|
res.pop();
|
|
dots = ellipsePath(x, y, pa[1], pa[2]);
|
|
dots.push(dots[0]);
|
|
res = res.concat(dots);
|
|
break;
|
|
case "U":
|
|
res.pop();
|
|
res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
|
|
r = ["U"].concat(res[res.length - 1].slice(-2));
|
|
break;
|
|
case "M":
|
|
mx = +pa[1] + x;
|
|
my = +pa[2] + y;
|
|
default:
|
|
for (j = 1, jj = pa.length; j < jj; j++) {
|
|
r[j] = +pa[j] + ((j % 2) ? x : y);
|
|
}
|
|
}
|
|
} else if (pa0 == "R") {
|
|
dots = [x, y].concat(pa.slice(1));
|
|
res.pop();
|
|
res = res.concat(catmullRom2bezier(dots, crz));
|
|
r = ["R"].concat(pa.slice(-2));
|
|
} else if (pa0 == "O") {
|
|
res.pop();
|
|
dots = ellipsePath(x, y, pa[1], pa[2]);
|
|
dots.push(dots[0]);
|
|
res = res.concat(dots);
|
|
} else if (pa0 == "U") {
|
|
res.pop();
|
|
res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
|
|
r = ["U"].concat(res[res.length - 1].slice(-2));
|
|
} else {
|
|
for (let k = 0, kk = pa.length; k < kk; k++) {
|
|
r[k] = pa[k];
|
|
}
|
|
}
|
|
pa0 = pa0.toUpperCase();
|
|
if (pa0 != "O") {
|
|
switch (r[0]) {
|
|
case "Z":
|
|
x = +mx;
|
|
y = +my;
|
|
break;
|
|
case "H":
|
|
x = r[1];
|
|
break;
|
|
case "V":
|
|
y = r[1];
|
|
break;
|
|
case "M":
|
|
mx = r[r.length - 2];
|
|
my = r[r.length - 1];
|
|
default:
|
|
x = r[r.length - 2];
|
|
y = r[r.length - 1];
|
|
}
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
function l2c(x1, y1, x2, y2) {
|
|
return [x1, y1, x2, y2, x2, y2];
|
|
}
|
|
function q2c(x1, y1, ax, ay, x2, y2) {
|
|
const _13 = 1 / 3;
|
|
const _23 = 2 / 3;
|
|
return [
|
|
_13 * x1 + _23 * ax,
|
|
_13 * y1 + _23 * ay,
|
|
_13 * x2 + _23 * ax,
|
|
_13 * y2 + _23 * ay,
|
|
x2,
|
|
y2
|
|
]
|
|
}
|
|
|
|
// for more information of where this math came from visit:
|
|
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
|
|
function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
|
|
const _120 = Math.PI * 120 / 180, rad = Math.PI / 180 * (+angle || 0);
|
|
let res = [], xy, f1, f2, cx, cy;
|
|
|
|
function rotateVector(x, y, rad) {
|
|
const X = x * Math.cos(rad) - y * Math.sin(rad),
|
|
Y = x * Math.sin(rad) + y * Math.cos(rad);
|
|
return {x: X, y: Y};
|
|
}
|
|
|
|
if (!recursive) {
|
|
xy = rotateVector(x1, y1, -rad);
|
|
x1 = xy.x;
|
|
y1 = xy.y;
|
|
xy = rotateVector(x2, y2, -rad);
|
|
x2 = xy.x;
|
|
y2 = xy.y;
|
|
// const cos = Math.cos(Math.PI / 180 * angle);
|
|
// const sin = Math.sin(Math.PI / 180 * angle);
|
|
let x = (x1 - x2) / 2, y = (y1 - y2) / 2, h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
|
|
if (h > 1) {
|
|
h = Math.sqrt(h);
|
|
rx = h * rx;
|
|
ry = h * ry;
|
|
}
|
|
let rx2 = rx * rx,
|
|
ry2 = ry * ry,
|
|
k = (large_arc_flag == sweep_flag ? -1 : 1)
|
|
* Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x)
|
|
/ (rx2 * y * y + ry2 * x * x)));
|
|
|
|
cx = k * rx * y / ry + (x1 + x2) / 2,
|
|
cy = k * -ry * x / rx + (y1 + y2) / 2;
|
|
|
|
f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
|
|
f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
|
|
|
|
f1 = x1 < cx ? Math.PI - f1 : f1;
|
|
f2 = x2 < cx ? Math.PI - f2 : f2;
|
|
f1 < 0 && (f1 = Math.PI * 2 + f1);
|
|
f2 < 0 && (f2 = Math.PI * 2 + f2);
|
|
if (sweep_flag && f1 > f2) {
|
|
f1 = f1 - Math.PI * 2;
|
|
}
|
|
if (!sweep_flag && f2 > f1) {
|
|
f2 = f2 - Math.PI * 2;
|
|
}
|
|
} else {
|
|
f1 = recursive[0];
|
|
f2 = recursive[1];
|
|
cx = recursive[2];
|
|
cy = recursive[3];
|
|
}
|
|
let df = f2 - f1;
|
|
if (Math.abs(df) > _120) {
|
|
const f2old = f2, x2old = x2, y2old = y2;
|
|
|
|
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
|
|
x2 = cx + rx * Math.cos(f2);
|
|
y2 = cy + ry * Math.sin(f2);
|
|
res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
|
|
}
|
|
df = f2 - f1;
|
|
const c1 = Math.cos(f1),
|
|
s1 = Math.sin(f1),
|
|
c2 = Math.cos(f2),
|
|
s2 = Math.sin(f2),
|
|
t = Math.tan(df / 4),
|
|
hx = 4 / 3 * rx * t,
|
|
hy = 4 / 3 * ry * t,
|
|
m1 = [x1, y1],
|
|
m2 = [x1 + hx * s1, y1 - hy * c1],
|
|
m3 = [x2 + hx * s2, y2 - hy * c2],
|
|
m4 = [x2, y2];
|
|
m2[0] = 2 * m1[0] - m2[0];
|
|
m2[1] = 2 * m1[1] - m2[1];
|
|
if (recursive) {
|
|
return [m2, m3, m4].concat(res);
|
|
} else {
|
|
res = [m2, m3, m4].concat(res).join().split(",");
|
|
const newres = [];
|
|
for (let i = 0, ii = res.length; i < ii; i++) {
|
|
newres[i] = i % 2 ? rotateVector(res[i - 1], res[i], rad).y : rotateVector(res[i], res[i + 1], rad).x;
|
|
}
|
|
return newres;
|
|
}
|
|
}
|
|
|
|
function processPath (path, d, pcom) {
|
|
let nx, ny;
|
|
if (!path) {
|
|
return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
|
|
}
|
|
!(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null);
|
|
switch (path[0]) {
|
|
case "M":
|
|
d.X = path[1];
|
|
d.Y = path[2];
|
|
break;
|
|
case "A":
|
|
path = ["C"].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
|
|
break;
|
|
case "S":
|
|
if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
|
|
nx = d.x * 2 - d.bx; // And reflect the previous
|
|
ny = d.y * 2 - d.by; // command's control point relative to the current point.
|
|
}
|
|
else { // or some else or nothing
|
|
nx = d.x;
|
|
ny = d.y;
|
|
}
|
|
path = ["C", nx, ny].concat(path.slice(1));
|
|
break;
|
|
case "T":
|
|
if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
|
|
d.qx = d.x * 2 - d.qx; // And make a reflection similar
|
|
d.qy = d.y * 2 - d.qy; // to case "S".
|
|
}
|
|
else { // or something else or nothing
|
|
d.qx = d.x;
|
|
d.qy = d.y;
|
|
}
|
|
path = ["C"].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
|
|
break;
|
|
case "Q":
|
|
d.qx = path[1];
|
|
d.qy = path[2];
|
|
path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
|
|
break;
|
|
case "L":
|
|
path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
|
|
break;
|
|
case "H":
|
|
path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
|
|
break;
|
|
case "V":
|
|
path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
|
|
break;
|
|
case "Z":
|
|
path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
|
|
break;
|
|
}
|
|
path.map((x,i)=>i?x.toFixed(3):x)
|
|
return path;
|
|
}
|
|
|
|
function fixM (path1, path2, a1, a2, i) {
|
|
if (path1 && path2 && path1[i][0] === "M" && path2[i][0] !== "M") {
|
|
path2.splice(i, 0, ["M", a2.x, a2.y]);
|
|
a1.bx = 0;
|
|
a1.by = 0;
|
|
a1.x = path1[i][1];
|
|
a1.y = path1[i][2];
|
|
// ii = Math.max(p.length, p2 && p2.length || 0);
|
|
}
|
|
}
|
|
|
|
function fixArc (p, p2, pcoms1, pcoms2, i) {
|
|
if (p[i].length > 7) {
|
|
p[i].shift();
|
|
const pi = p[i];
|
|
while (pi.length) {
|
|
pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved
|
|
p2 && (pcoms2[i] = "A"); // the same as above
|
|
p.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
|
|
}
|
|
p.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
function path2curve(path, path2) {
|
|
let p = pathToAbsolute(path), // holder for previous path command of original path
|
|
p2 = path2 && pathToAbsolute(path2),
|
|
// p2 = path2 ? pathToAbsolute(path2) : pathToAbsolute('M0,0L0,0'),
|
|
attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
|
|
attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null};
|
|
|
|
// path commands of original path p
|
|
// path commands of original path p2
|
|
// temporary holder for original path command
|
|
let pcoms1 = [], pcoms2 = [], pfirst = "", pcom = "";
|
|
|
|
for (let i = 0, ii = Math.max(p.length, p2 && p2.length || 0); i < ii; i++) {
|
|
p[i] && (pfirst = p[i][0]); // save current path command
|
|
|
|
if (pfirst !== "C") { // C is not saved yet, because it may be result of conversion
|
|
pcoms1[i] = pfirst; // Save current path command
|
|
i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom
|
|
}
|
|
p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
|
|
|
|
// A is the only command
|
|
// which may produce multiple C:s
|
|
// so we have to make sure that C is also C in original path
|
|
if (pcoms1[i] !== "A" && pfirst === "C") pcoms1[i] = "C";
|
|
|
|
// fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
|
|
fixArc(p, p2, pcoms1, pcoms2, i); // fixArc adds also the right amount of A:s to pcoms1 fixArc (p, p2, pcoms1, pcoms2, i) {
|
|
ii = Math.max(p.length, p2 && p2.length || 0);
|
|
|
|
if (p2) { // the same procedures is done to p2
|
|
p2[i] && (pfirst = p2[i][0]);
|
|
if (pfirst !== "C") {
|
|
pcoms2[i] = pfirst;
|
|
i && (pcom = pcoms2[i - 1]);
|
|
}
|
|
p2[i] = processPath(p2[i], attrs2, pcom);
|
|
|
|
if (pcoms2[i] !== "A" && pfirst === "C") {
|
|
pcoms2[i] = "C";
|
|
}
|
|
|
|
// fixArc(p2, i);
|
|
fixArc(p2, p, pcoms2, pcoms1, i); // fixArc (p, p2, pcoms1, pcoms2, i) {
|
|
ii = Math.max(p.length, p2 && p2.length || 0);
|
|
}
|
|
fixM(p, p2, attrs, attrs2, i);
|
|
p2 && fixM(p2, p, attrs2, attrs, i);
|
|
ii = Math.max(p.length, p2 && p2.length || 0);
|
|
|
|
let seg = p[i],
|
|
seg2 = p2 && p2[i],
|
|
seglen = seg.length,
|
|
seg2len = p2 && seg2.length;
|
|
attrs.x = seg[seglen - 2];
|
|
attrs.y = seg[seglen - 1];
|
|
attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
|
|
attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
|
|
attrs2.bx = p2 && (parseFloat(seg2[seg2len - 4]) || attrs2.x);
|
|
attrs2.by = p2 && (parseFloat(seg2[seg2len - 3]) || attrs2.y);
|
|
attrs2.x = p2 && seg2[seg2len - 2];
|
|
attrs2.y = p2 && seg2[seg2len - 1];
|
|
}
|
|
|
|
return p2 ? [p, p2] : p;
|
|
}
|
|
|
|
function createPath (path) { // create a <path> when glyph
|
|
const np = document.createElementNS('http://www.w3.org/2000/svg','path'),
|
|
d = path instanceof SVGElement ? path.getAttribute('d') : path
|
|
np.setAttribute('d',d);
|
|
return np
|
|
}
|
|
|
|
function getSegments(curveArray) {
|
|
// let result = []
|
|
// curveArray.map((seg, i) => {
|
|
// result[i] = {
|
|
// x: seg[seg[0] === 'M' ? 1 : 5],
|
|
// y: seg[seg[0] === 'M' ? 2 : 6],
|
|
// seg: seg
|
|
// }
|
|
// })
|
|
// return result
|
|
|
|
return curveArray.map(seg => {
|
|
return {
|
|
x: seg[seg[0] === 'M' ? 1 : 5],
|
|
y: seg[seg[0] === 'M' ? 2 : 6],
|
|
seg: seg
|
|
}
|
|
})
|
|
}
|
|
function reverseCurve(path){
|
|
let newSegments = [],
|
|
oldSegments = getSegments(path),
|
|
segsCount = oldSegments.length,
|
|
pointCount = segsCount - 1,
|
|
oldSegIdx = pointCount,
|
|
oldSegs = []
|
|
|
|
oldSegments.map((p,i)=>{
|
|
if (i === 0||oldSegments[oldSegIdx].seg[0] === 'M') {
|
|
newSegments[i] = ['M',oldSegments[oldSegIdx].x,oldSegments[oldSegIdx].y]
|
|
} else {
|
|
oldSegIdx = pointCount - i > 0 ? pointCount - i : pointCount;
|
|
oldSegs = oldSegments[oldSegIdx].seg
|
|
newSegments[i] = [oldSegs[0], oldSegs[5],oldSegs[6],oldSegs[3],oldSegs[4], oldSegs[1], oldSegs[2]]
|
|
}
|
|
})
|
|
|
|
return newSegments
|
|
}
|
|
|
|
function getRotationSegments(s,idx) {
|
|
let newSegments = [], segsCount = s.length, pointCount = segsCount - 1
|
|
|
|
s.map((p,i)=>{
|
|
let oldSegIdx = idx + i;
|
|
if (i===0 || s[oldSegIdx] && s[oldSegIdx].seg[0] === 'M') {
|
|
newSegments[i] = ['M',s[oldSegIdx].x,s[oldSegIdx].y]
|
|
} else {
|
|
if (oldSegIdx >= segsCount) oldSegIdx -= pointCount;
|
|
newSegments[i] = s[oldSegIdx].seg
|
|
}
|
|
})
|
|
return newSegments
|
|
}
|
|
|
|
function getRotations(a) {
|
|
let startSegments = getSegments(a), rotations = [];
|
|
startSegments.map((s,i)=>{rotations[i] = getRotationSegments(startSegments,i)})
|
|
return rotations
|
|
}
|
|
|
|
function getRotatedCurve(a,b) {
|
|
let startSegments = getSegments(a),
|
|
endSegments = getSegments(b),
|
|
segsCount = startSegments.length,
|
|
pointCount = segsCount - 1,
|
|
linePaths = [],
|
|
lineLengths = [],
|
|
rotations = getRotations(a);
|
|
|
|
rotations.map((r,i)=>{
|
|
let sumLensSqrd = 0, linePath = createPath('M0,0L0,0');
|
|
for (let j = 0; j < pointCount; j++) {
|
|
let linePt1 = startSegments[(i + j) % pointCount];
|
|
let linePt2 = endSegments[ j % pointCount];
|
|
let linePathStr = `M${linePt1.x},${linePt1.y}L${linePt2.x},${linePt2.y}`;
|
|
linePath.setAttribute('d',linePathStr);
|
|
sumLensSqrd += Math.pow(linePath.getTotalLength(),2);
|
|
linePaths[j] = linePath;
|
|
}
|
|
lineLengths[i] = sumLensSqrd
|
|
sumLensSqrd = 0
|
|
})
|
|
|
|
let computedIndex = lineLengths.indexOf(Math.min.apply(null,lineLengths)),
|
|
newPath = rotations[computedIndex];
|
|
return newPath
|
|
}
|
|
|
|
// Component Functions
|
|
function getCubicMorph(tweenProp){
|
|
return this.element.getAttribute('d');
|
|
}
|
|
function prepareCubicMorph(tweenProp,value){
|
|
// get path d attribute or create a path from string value
|
|
let pathObject = {},
|
|
el = value instanceof SVGElement ? value : /^\.|^\#/.test(value) ? selector(value) : null,
|
|
pathReg = new RegExp('\\n','ig'); // remove newlines, they break some JSON strings
|
|
|
|
try {
|
|
// make sure to return pre-processed values
|
|
if ( typeof(value) === 'object' && value.curve ) {
|
|
return value;
|
|
} else if ( el && /path|glyph/.test(el.tagName) ) {
|
|
pathObject.original = el.getAttribute('d').replace(pathReg,'');
|
|
// } else if (!el && /m|z|c|l|v|q|[0-9]|\,/gi.test(value)) { // maybe it's a string path already
|
|
} else if (!el && typeof(value) === 'string') { // maybe it's a string path already
|
|
pathObject.original = value.replace(pathReg,'');
|
|
}
|
|
return pathObject;
|
|
}
|
|
catch(e){
|
|
throw TypeError(`KUTE.js - ${INVALID_INPUT} ${e}`)
|
|
}
|
|
}
|
|
function crossCheckCubicMorph(tweenProp){
|
|
if (this.valuesEnd[tweenProp]) {
|
|
let pathCurve1 = this.valuesStart[tweenProp].curve,
|
|
pathCurve2 = this.valuesEnd[tweenProp].curve
|
|
|
|
if ( !pathCurve1 || !pathCurve2 || ( pathCurve1 && pathCurve2 && pathCurve1[0][0] === 'M' && pathCurve1.length !== pathCurve2.length) ) {
|
|
let path1 = this.valuesStart[tweenProp].original,
|
|
path2 = this.valuesEnd[tweenProp].original,
|
|
curves = path2curve(path1,path2)
|
|
|
|
let curve0 = this._reverseFirstPath ? reverseCurve.call(this,curves[0]) : curves[0],
|
|
curve1 = this._reverseSecondPath ? reverseCurve.call(this,curves[1]) : curves[1]
|
|
|
|
curve0 = getRotatedCurve.call(this,curve0,curve1)
|
|
this.valuesStart[tweenProp].curve = curve0;
|
|
this.valuesEnd[tweenProp].curve = curve1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// All Component Functions
|
|
const svgCubicMorphFunctions = {
|
|
prepareStart: getCubicMorph,
|
|
prepareProperty: prepareCubicMorph,
|
|
onStart: onStartCubicMorph,
|
|
crossCheck: crossCheckCubicMorph
|
|
}
|
|
|
|
// Component Full
|
|
const svgCubicMorph = {
|
|
component: 'svgCubicMorph',
|
|
property: 'path',
|
|
defaultValue: [],
|
|
Interpolate: {numbers,toPathString},
|
|
functions: svgCubicMorphFunctions,
|
|
// export utils to global for faster execution
|
|
Util: {
|
|
l2c, q2c, a2c, catmullRom2bezier, ellipsePath,
|
|
path2curve, pathToAbsolute, toPathString, parsePathString,
|
|
getRotatedCurve, getRotations,
|
|
getRotationSegments, reverseCurve, getSegments, createPath
|
|
}
|
|
}
|
|
|
|
export default svgCubicMorph
|
|
|
|
Components.SVGCubicMorph = svgCubicMorph
|