dada-datav-vue3
Version:
dataV组件vue3版本
311 lines (235 loc) • 10.6 kB
JavaScript
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