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:
@@ -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
|
||||
Reference in New Issue
Block a user