1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-06-12 21:29:31 +08:00

v1.0.0-beta 源码正式开源

This commit is contained in:
小莫唐尼
2022-12-06 15:08:29 +08:00
commit 636ae7b169
461 changed files with 116817 additions and 0 deletions
@@ -0,0 +1,310 @@
const { sqrt, pow, ceil, abs } = Math
// Initialize the number of points per curve
const defaultSegmentPointsNum = 50
/**
* @example data structure of bezierCurve
* bezierCurve = [
* // Starting point of the curve
* [10, 10],
* // BezierCurve segment data (controlPoint1, controlPoint2, endPoint)
* [
* [20, 20], [40, 20], [50, 10]
* ],
* ...
* ]
*/
/**
* @description Abstract the curve as a polyline consisting of N points
* @param {Array} bezierCurve bezierCurve data
* @param {Number} precision calculation accuracy. Recommended for 1-20. Default = 5
* @return {Object} Calculation results and related data
* @return {Array} Option.segmentPoints Point data that constitutes a polyline after calculation
* @return {Number} Option.cycles Number of iterations
* @return {Number} Option.rounds The number of recursions for the last iteration
*/
function abstractBezierCurveToPolyline (bezierCurve, precision = 5) {
const segmentsNum = bezierCurve.length - 1
const startPoint = bezierCurve[0]
const endPoint = bezierCurve[segmentsNum][2]
const segments = bezierCurve.slice(1)
const getSegmentTPointFuns = segments.map((seg, i) => {
let beginPoint = (i === 0) ? startPoint : segments[i - 1][2]
return createGetBezierCurveTPointFun(beginPoint, ...seg)
})
// Initialize the curve to a polyline
let segmentPointsNum = new Array(segmentsNum).fill(defaultSegmentPointsNum)
let segmentPoints = getSegmentPointsByNum(getSegmentTPointFuns, segmentPointsNum)
// Calculate uniformly distributed points by iteratively
const result = calcUniformPointsByIteration(segmentPoints, getSegmentTPointFuns, segments, precision)
result.segmentPoints.push(endPoint)
return result
}
/**
* @description Generate a method for obtaining corresponding point by t according to curve data
* @param {Array} beginPoint BezierCurve begin point. [x, y]
* @param {Array} controlPoint1 BezierCurve controlPoint1. [x, y]
* @param {Array} controlPoint2 BezierCurve controlPoint2. [x, y]
* @param {Array} endPoint BezierCurve end point. [x, y]
* @return {Function} Expected function
*/
function createGetBezierCurveTPointFun (beginPoint, controlPoint1, controlPoint2, endPoint) {
return function (t) {
const tSubed1 = 1 - t
const tSubed1Pow3 = pow(tSubed1, 3)
const tSubed1Pow2 = pow(tSubed1, 2)
const tPow3 = pow(t, 3)
const tPow2 = pow(t, 2)
return [
beginPoint[0] * tSubed1Pow3 + 3 * controlPoint1[0] * t * tSubed1Pow2 + 3 * controlPoint2[0] * tPow2 * tSubed1 + endPoint[0] * tPow3,
beginPoint[1] * tSubed1Pow3 + 3 * controlPoint1[1] * t * tSubed1Pow2 + 3 * controlPoint2[1] * tPow2 * tSubed1 + endPoint[1] * tPow3
]
}
}
/**
* @description Get the distance between two points
* @param {Array} point1 BezierCurve begin point. [x, y]
* @param {Array} point2 BezierCurve controlPoint1. [x, y]
* @return {Number} Expected distance
*/
function getTwoPointDistance ([ax, ay], [bx, by]) {
return sqrt(pow(ax - bx, 2) + pow(ay - by, 2))
}
/**
* @description Get the sum of the array of numbers
* @param {Array} nums An array of numbers
* @return {Number} Expected sum
*/
function getNumsSum (nums) {
return nums.reduce((sum, num) => sum + num, 0)
}
/**
* @description Get the distance of multiple sets of points
* @param {Array} segmentPoints Multiple sets of point data
* @return {Array} Distance of multiple sets of point data
*/
function getSegmentPointsDistance (segmentPoints) {
return segmentPoints.map((points, i) => {
return new Array(points.length - 1)
.fill(0)
.map((temp, j) => getTwoPointDistance(points[j], points[j + 1]))
})
}
/**
* @description Get the distance of multiple sets of points
* @param {Array} segmentPoints Multiple sets of point data
* @return {Array} Distance of multiple sets of point data
*/
function getSegmentPointsByNum (getSegmentTPointFuns, segmentPointsNum) {
return getSegmentTPointFuns.map((getSegmentTPointFun, i) => {
const tGap = 1 / segmentPointsNum[i]
return new Array(segmentPointsNum[i])
.fill('')
.map((foo, j) => getSegmentTPointFun(j * tGap))
})
}
/**
* @description Get the sum of deviations between line segment and the average length
* @param {Array} segmentPointsDistance Segment length of polyline
* @param {Number} avgLength Average length of the line segment
* @return {Number} Deviations
*/
function getAllDeviations (segmentPointsDistance, avgLength) {
return segmentPointsDistance
.map(seg => seg.map(s => abs(s - avgLength)))
.map(seg => getNumsSum(seg))
.reduce((total, v) => total + v, 0)
}
/**
* @description Calculate uniformly distributed points by iteratively
* @param {Array} segmentPoints Multiple setd of points that make up a polyline
* @param {Array} getSegmentTPointFuns Functions of get a point on the curve with t
* @param {Array} segments BezierCurve data
* @param {Number} precision Calculation accuracy
* @return {Object} Calculation results and related data
* @return {Array} Option.segmentPoints Point data that constitutes a polyline after calculation
* @return {Number} Option.cycles Number of iterations
* @return {Number} Option.rounds The number of recursions for the last iteration
*/
function calcUniformPointsByIteration (segmentPoints, getSegmentTPointFuns, segments, precision) {
// The number of loops for the current iteration
let rounds = 4
// Number of iterations
let cycles = 1
do {
// Recalculate the number of points per curve based on the last iteration data
let totalPointsNum = segmentPoints.reduce((total, seg) => total + seg.length, 0)
// Add last points of segment to calc exact segment length
segmentPoints.forEach((seg, i) => seg.push(segments[i][2]))
let segmentPointsDistance = getSegmentPointsDistance(segmentPoints)
let lineSegmentNum = segmentPointsDistance.reduce((total, seg) => total + seg.length, 0)
let segmentlength = segmentPointsDistance.map(seg => getNumsSum(seg))
let totalLength = getNumsSum(segmentlength)
let avgLength = totalLength / lineSegmentNum
// Check if precision is reached
let allDeviations = getAllDeviations(segmentPointsDistance, avgLength)
if (allDeviations <= precision) break
totalPointsNum = ceil(avgLength / precision * totalPointsNum * 1.1)
const segmentPointsNum = segmentlength.map(length => ceil(length / totalLength * totalPointsNum))
// Calculate the points after redistribution
segmentPoints = getSegmentPointsByNum(getSegmentTPointFuns, segmentPointsNum)
totalPointsNum = segmentPoints.reduce((total, seg) => total + seg.length, 0)
let segmentPointsForLength = JSON.parse(JSON.stringify(segmentPoints))
segmentPointsForLength.forEach((seg, i) => seg.push(segments[i][2]))
segmentPointsDistance = getSegmentPointsDistance(segmentPointsForLength)
lineSegmentNum = segmentPointsDistance.reduce((total, seg) => total + seg.length, 0)
segmentlength = segmentPointsDistance.map(seg => getNumsSum(seg))
totalLength = getNumsSum(segmentlength)
avgLength = totalLength / lineSegmentNum
const stepSize = 1 / totalPointsNum / 10
// Recursively for each segment of the polyline
getSegmentTPointFuns.forEach((getSegmentTPointFun, i) => {
const currentSegmentPointsNum = segmentPointsNum[i]
const t = new Array(currentSegmentPointsNum).fill('').map((foo, j) => j / segmentPointsNum[i])
// Repeated recursive offset
for (let r = 0; r < rounds; r++) {
let distance = getSegmentPointsDistance([segmentPoints[i]])[0]
const deviations = distance.map(d => d - avgLength)
let offset = 0
for (let j = 0; j < currentSegmentPointsNum; j++) {
if (j === 0) return
offset += deviations[j - 1]
t[j] -= stepSize * offset
if (t[j] > 1) t[j] = 1
if (t[j] < 0) t[j] = 0
segmentPoints[i][j] = getSegmentTPointFun(t[j])
}
}
})
rounds *= 4
cycles++
} while (rounds <= 1025)
segmentPoints = segmentPoints.reduce((all, seg) => all.concat(seg), [])
return {
segmentPoints,
cycles,
rounds
}
}
/**
* @description Get the polyline corresponding to the Bezier curve
* @param {Array} bezierCurve BezierCurve data
* @param {Number} precision Calculation accuracy. Recommended for 1-20. Default = 5
* @return {Array|Boolean} Point data that constitutes a polyline after calculation (Invalid input will return false)
*/
export function bezierCurveToPolyline (bezierCurve, precision = 5) {
if (!bezierCurve) {
console.error('bezierCurveToPolyline: Missing parameters!')
return false
}
if (!(bezierCurve instanceof Array)) {
console.error('bezierCurveToPolyline: Parameter bezierCurve must be an array!')
return false
}
if (typeof precision !== 'number') {
console.error('bezierCurveToPolyline: Parameter precision must be a number!')
return false
}
const { segmentPoints } = abstractBezierCurveToPolyline(bezierCurve, precision)
return segmentPoints
}
/**
* @description Get the bezier curve length
* @param {Array} bezierCurve bezierCurve data
* @param {Number} precision calculation accuracy. Recommended for 5-10. Default = 5
* @return {Number|Boolean} BezierCurve length (Invalid input will return false)
*/
export function getBezierCurveLength (bezierCurve, precision = 5) {
if (!bezierCurve) {
console.error('getBezierCurveLength: Missing parameters!')
return false
}
if (!(bezierCurve instanceof Array)) {
console.error('getBezierCurveLength: Parameter bezierCurve must be an array!')
return false
}
if (typeof precision !== 'number') {
console.error('getBezierCurveLength: Parameter precision must be a number!')
return false
}
const { segmentPoints } = abstractBezierCurveToPolyline(bezierCurve, precision)
// Calculate the total length of the points that make up the polyline
const pointsDistance = getSegmentPointsDistance([segmentPoints])[0]
const length = getNumsSum(pointsDistance)
return length
}
export default bezierCurveToPolyline
@@ -0,0 +1,465 @@
/**
* 点方法。
* 此库来自konva
* 移植:https://jx2d.cn
* tmzdy
*/
export function parsePathData(data) {
if (!data) {
return [];
}
var cs = data;
var cc = [
'm',
'M',
'l',
'L',
'v',
'V',
'h',
'H',
'z',
'Z',
'c',
'C',
'q',
'Q',
't',
'T',
's',
'S',
'a',
'A',
];
cs = cs.replace(new RegExp(' ', 'g'), ',');
for (var n = 0; n < cc.length; n++) {
cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
}
var arr = cs.split('|');
var ca = [];
var coords = [];
var cpx = 0;
var cpy = 0;
var re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi;
var match;
for (n = 1; n < arr.length; n++) {
var str = arr[n];
var c = str.charAt(0);
str = str.slice(1);
coords.length = 0;
while ((match = re.exec(str))) {
coords.push(match[0]);
}
var p = [];
for (var j = 0, jlen = coords.length; j < jlen; j++) {
if (coords[j] === '00') {
p.push(0, 0);
continue;
}
var parsed = parseFloat(coords[j]);
if (!isNaN(parsed)) {
p.push(parsed);
} else {
p.push(0);
}
}
while (p.length > 0) {
if (isNaN(p[0])) {
break;
}
var cmd = null;
var points = [];
var startX = cpx,
startY = cpy;
var prevCmd, ctlPtx, ctlPty;
var rx, ry, psi, fa, fs, x1, y1;
switch (c) {
case 'l':
cpx += p.shift();
cpy += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'L':
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'm':
var dx = p.shift();
var dy = p.shift();
cpx += dx;
cpy += dy;
cmd = 'M';
if (ca.length > 2 && ca[ca.length - 1].command === 'z') {
for (var idx = ca.length - 2; idx >= 0; idx--) {
if (ca[idx].command === 'M') {
cpx = ca[idx].points[0] + dx;
cpy = ca[idx].points[1] + dy;
break;
}
}
}
points.push(cpx, cpy);
c = 'l';
break;
case 'M':
cpx = p.shift();
cpy = p.shift();
cmd = 'M';
points.push(cpx, cpy);
c = 'L';
break;
case 'h':
cpx += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'H':
cpx = p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'v':
cpy += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'V':
cpy = p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'C':
points.push(p.shift(), p.shift(), p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'c':
points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 'S':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'C') {
ctlPtx = cpx + (cpx - prevCmd.points[2]);
ctlPty = cpy + (cpy - prevCmd.points[3]);
}
points.push(ctlPtx, ctlPty, p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 's':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'C') {
ctlPtx = cpx + (cpx - prevCmd.points[2]);
ctlPty = cpy + (cpy - prevCmd.points[3]);
}
points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 'Q':
points.push(p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'q':
points.push(cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'Q';
points.push(cpx, cpy);
break;
case 'T':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'Q') {
ctlPtx = cpx + (cpx - prevCmd.points[0]);
ctlPty = cpy + (cpy - prevCmd.points[1]);
}
cpx = p.shift();
cpy = p.shift();
cmd = 'Q';
points.push(ctlPtx, ctlPty, cpx, cpy);
break;
case 't':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'Q') {
ctlPtx = cpx + (cpx - prevCmd.points[0]);
ctlPty = cpy + (cpy - prevCmd.points[1]);
}
cpx += p.shift();
cpy += p.shift();
cmd = 'Q';
points.push(ctlPtx, ctlPty, cpx, cpy);
break;
case 'A':
rx = p.shift();
ry = p.shift();
psi = p.shift();
fa = p.shift();
fs = p.shift();
x1 = cpx;
y1 = cpy;
cpx = p.shift();
cpy = p.shift();
cmd = 'A';
points = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
break;
case 'a':
rx = p.shift();
ry = p.shift();
psi = p.shift();
fa = p.shift();
fs = p.shift();
x1 = cpx;
y1 = cpy;
cpx += p.shift();
cpy += p.shift();
cmd = 'A';
points = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
break;
}
ca.push({
command: cmd || c,
points: points,
start: {
x: startX,
y: startY,
},
pathLength: calcLength(startX, startY, cmd || c, points),
});
}
if (c === 'z' || c === 'Z') {
ca.push({
command: 'z',
points: [],
start: undefined,
pathLength: 0,
});
}
}
return ca;
}
export function calcLength(x, y, cmd, points) {
var len, p1, p2, t;
switch (cmd) {
case 'L':
return getLineLength(x, y, points[0], points[1]);
case 'C':
len = 0.0;
p1 = getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]);
for (t = 0.01; t <= 1; t += 0.01) {
p2 = getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[
5]);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
return len;
case 'Q':
len = 0.0;
p1 = getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]);
for (t = 0.01; t <= 1; t += 0.01) {
p2 = getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
return len;
case 'A':
len = 0.0;
var start = points[4];
var dTheta = points[5];
var end = points[4] + dTheta;
var inc = Math.PI / 180.0;
if (Math.abs(start - end) < inc) {
inc = Math.abs(start - end);
}
p1 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0);
if (dTheta < 0) {
for (t = start - inc; t > end; t -= inc) {
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
} else {
for (t = start + inc; t < end; t += inc) {
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
}
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
return len;
}
return 0;
}
export function convertEndpointToCenterParameterization(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) {
var psi = psiDeg * (Math.PI / 180.0);
var xp = (Math.cos(psi) * (x1 - x2)) / 2.0 + (Math.sin(psi) * (y1 - y2)) / 2.0;
var yp = (-1 * Math.sin(psi) * (x1 - x2)) / 2.0 +
(Math.cos(psi) * (y1 - y2)) / 2.0;
var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry);
if (lambda > 1) {
rx *= Math.sqrt(lambda);
ry *= Math.sqrt(lambda);
}
var f = Math.sqrt((rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) /
(rx * rx * (yp * yp) + ry * ry * (xp * xp)));
if (fa === fs) {
f *= -1;
}
if (isNaN(f)) {
f = 0;
}
var cxp = (f * rx * yp) / ry;
var cyp = (f * -ry * xp) / rx;
var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp;
var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp;
var vMag = function(v) {
return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
};
var vRatio = function(u, v) {
return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v));
};
var vAngle = function(u, v) {
return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v));
};
var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]);
var u = [(xp - cxp) / rx, (yp - cyp) / ry];
var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry];
var dTheta = vAngle(u, v);
if (vRatio(u, v) <= -1) {
dTheta = Math.PI;
}
if (vRatio(u, v) >= 1) {
dTheta = 0;
}
if (fs === 0 && dTheta > 0) {
dTheta = dTheta - 2 * Math.PI;
}
if (fs === 1 && dTheta < 0) {
dTheta = dTheta + 2 * Math.PI;
}
return [cx, cy, rx, ry, theta, dTheta, psi, fs];
}
export function getSelfRect() {
var points = [];
this.dataArray.forEach(function(data) {
if (data.command === 'A') {
var start = data.points[4];
var dTheta = data.points[5];
var end = data.points[4] + dTheta;
var inc = Math.PI / 180.0;
if (Math.abs(start - end) < inc) {
inc = Math.abs(start - end);
}
if (dTheta < 0) {
for (let t = start - inc; t > end; t -= inc) {
const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2],
data.points[3], t, 0);
points.push(point.x, point.y);
}
} else {
for (let t = start + inc; t < end; t += inc) {
const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2],
data.points[3], t, 0);
points.push(point.x, point.y);
}
}
} else if (data.command === 'C') {
for (let t = 0.0; t <= 1; t += 0.01) {
const point = Path.getPointOnCubicBezier(t, data.start.x, data.start.y, data.points[0], data
.points[1], data.points[2], data.points[3], data.points[4], data.points[5]);
points.push(point.x, point.y);
}
} else {
points = points.concat(data.points);
}
});
var minX = points[0];
var maxX = points[0];
var minY = points[1];
var maxY = points[1];
var x, y;
for (var i = 0; i < points.length / 2; i++) {
x = points[i * 2];
y = points[i * 2 + 1];
if (!isNaN(x)) {
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
}
if (!isNaN(y)) {
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
}
}
return {
x: Math.round(minX),
y: Math.round(minY),
width: Math.round(maxX - minX),
height: Math.round(maxY - minY),
};
}
export function expandPoints(p, tension) {
var len = p.length, allPoints = [], n, cp;
for (n = 2; n < len - 2; n += 2) {
cp = getControlPoints(p[n - 2], p[n - 1], p[n], p[n + 1], p[n + 2], p[n + 3], tension);
if (isNaN(cp[0])) {
continue;
}
allPoints.push(cp[0]);
allPoints.push(cp[1]);
allPoints.push(p[n]);
allPoints.push(p[n + 1]);
allPoints.push(cp[2]);
allPoints.push(cp[3]);
}
return allPoints;
}
export function getControlPoints(x0, y0, x1, y1, x2, y2, t) {
var d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)), d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), fa = (t * d01) / (d01 + d12), fb = (t * d12) / (d01 + d12), p1x = x1 - fa * (x2 - x0), p1y = y1 - fa * (y2 - y0), p2x = x1 + fb * (x2 - x0), p2y = y1 + fb * (y2 - y0);
return [p1x, p1y, p2x, p2y];
}
export function getTensionPointsClosed(points,tension) {
var p = points ,len = p.length, firstControlPoints = getControlPoints(p[len - 2], p[len - 1], p[0], p[1], p[2], p[3], tension), lastControlPoints = getControlPoints(p[len - 4], p[len - 3], p[len - 2], p[len - 1], p[0], p[1], tension), middle = expandPoints(p, tension), tp = [firstControlPoints[2], firstControlPoints[3]]
.concat(middle)
.concat([
lastControlPoints[0],
lastControlPoints[1],
p[len - 2],
p[len - 1],
lastControlPoints[2],
lastControlPoints[3],
firstControlPoints[0],
firstControlPoints[1],
p[0],
p[1],
]);
return tp;
}
@@ -0,0 +1,111 @@
/**
* @description Abstract the polyline formed by N points into a set of bezier curve
* @param {Array} polyline A set of points that make up a polyline
* @param {Boolean} close Closed curve
* @param {Number} offsetA Smoothness
* @param {Number} offsetB Smoothness
* @return {Array|Boolean} A set of bezier curve (Invalid input will return false)
*/
function polylineToBezierCurve (polyline, close = false, offsetA = 0.25, offsetB = 0.25) {
if (!(polyline instanceof Array)) {
console.error('polylineToBezierCurve: Parameter polyline must be an array!')
return false
}
if (polyline.length <= 2) {
console.error('polylineToBezierCurve: Converting to a curve requires at least 3 points!')
return false
}
const startPoint = polyline[0]
const bezierCurveLineNum = polyline.length - 1
const bezierCurvePoints = new Array(bezierCurveLineNum).fill(0).map((foo, i) =>
[...getBezierCurveLineControlPoints(polyline, i, close, offsetA, offsetB), polyline[i + 1]])
if (close) closeBezierCurve(bezierCurvePoints, startPoint)
bezierCurvePoints.unshift(polyline[0])
return bezierCurvePoints
}
/**
* @description Get the control points of the Bezier curve
* @param {Array} polyline A set of points that make up a polyline
* @param {Number} index The index of which get controls points's point in polyline
* @param {Boolean} close Closed curve
* @param {Number} offsetA Smoothness
* @param {Number} offsetB Smoothness
* @return {Array} Control points
*/
function getBezierCurveLineControlPoints (polyline, index, close = false, offsetA = 0.25, offsetB = 0.25) {
const pointNum = polyline.length
if (pointNum < 3 || index >= pointNum) return
let beforePointIndex = index - 1
if (beforePointIndex < 0) beforePointIndex = (close ? pointNum + beforePointIndex : 0)
let afterPointIndex = index + 1
if (afterPointIndex >= pointNum) afterPointIndex = (close ? afterPointIndex - pointNum : pointNum - 1)
let afterNextPointIndex = index + 2
if (afterNextPointIndex >= pointNum) afterNextPointIndex = (close ? afterNextPointIndex - pointNum : pointNum - 1)
const pointBefore = polyline[beforePointIndex]
const pointMiddle = polyline[index]
const pointAfter = polyline[afterPointIndex]
const pointAfterNext = polyline[afterNextPointIndex]
return [
[
pointMiddle[0] + offsetA * (pointAfter[0] - pointBefore[0]),
pointMiddle[1] + offsetA * (pointAfter[1] - pointBefore[1])
],
[
pointAfter[0] - offsetB * (pointAfterNext[0] - pointMiddle[0]),
pointAfter[1] - offsetB * (pointAfterNext[1] - pointMiddle[1])
]
]
}
/**
* @description Get the last curve of the closure
* @param {Array} bezierCurve A set of sub-curve
* @param {Array} startPoint Start point
* @return {Array} The last curve for closure
*/
function closeBezierCurve (bezierCurve, startPoint) {
const firstSubCurve = bezierCurve[0]
const lastSubCurve = bezierCurve.slice(-1)[0]
bezierCurve.push([
getSymmetryPoint(lastSubCurve[1], lastSubCurve[2]),
getSymmetryPoint(firstSubCurve[0], startPoint),
startPoint
])
return bezierCurve
}
/**
* @description Get the symmetry point
* @param {Array} point Symmetric point
* @param {Array} centerPoint Symmetric center
* @return {Array} Symmetric point
*/
function getSymmetryPoint (point, centerPoint) {
const [px, py] = point
const [cx, cy] = centerPoint
const minusX = cx - px
const minusY = cy - py
return [cx + minusX, cy + minusY]
}
export default polylineToBezierCurve
@@ -0,0 +1,11 @@
import { bezierCurveToPolyline, getBezierCurveLength } from './core/bezierCurveToPolyline'
import polylineToBezierCurve from './core/polylineToBezierCurve'
export { bezierCurveToPolyline, getBezierCurveLength } from './core/bezierCurveToPolyline'
export { default as polylineToBezierCurve } from './core/polylineToBezierCurve'
export default {
bezierCurveToPolyline,
getBezierCurveLength,
polylineToBezierCurve
}