123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- 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
|