UNPKG

svg-getpointatlength

Version:

alternative to native pointAtLength() and getTotalLength() method

309 lines (222 loc) 11.5 kB
import { lgVals, deg2rad, rad2deg, PI2, PI, PI_half } from './constants.js'; //import { PathLengthObject } from './PathLengthObject.js'; import { checkFlatnessByPolygonArea, svgArcToCenterParam, getAngle, getLegendreGaussValues, toParametricAngle, getEllipseLengthLG, pointAtT, getLength, getPointOnEllipse, getTangentAngle, rotatePoint, normalizeAngle, getArcExtemes_fromParametrized, getBezierExtremes } from './geometry.js'; import {roundPoint} from './rounding.js'; //import { renderPoint } from './visualize.js'; //import { getAreaData } from './getArea.js'; //import { normalizePathData, parse, parsePathDataNormalized, stringifyPathData } from './pathData_parse.js'; export function getPointAtLength(lookup, length = 0, getTangent = true, getSegment = true, decimals=-1) { let { segments, pathData, totalLength } = lookup; // disable tangents if no angles present in lookup if (!segments[0].angles.length) getSegment = false; // get control points for path splitting let getCpts = getSegment; // 1st segment let seg0 = segments[0]; let seglast = segments[segments.length - 1]; let M = seg0.points[0]; let angle0 = seg0.angles[0] angle0 = angle0 < 0 ? angle0 + Math.PI * 2 : angle0; let newT = 0; let foundSegment = false; let pt = { x: M.x, y: M.y }; // round - opional if(decimals>-1) pt = roundPoint(pt) // tangent angles for Arcs let tangentAngle, rx, ry, xAxisRotation, tangentAdjust = 0; if (getTangent) { pt.angle = angle0; if (seg0.type === 'A') { let { arcData } = seg0; ({ rx, ry, xAxisRotation, tangentAdjust } = !arcData.isEllipse ? arcData : seg0.arcData_param); if (rx !== ry) { // calulate tangent angle tangentAngle = normalizeAngle(getTangentAngle(rx, ry, angle0) - xAxisRotation + tangentAdjust); pt.angle = tangentAngle; } } } // return segment data if (getSegment) { pt.index = segments[0].index; pt.segIndex = 0; pt.com = segments[0].com; } // first or last point on path if (length === 0) { return pt; } //return last on-path point when length is larger or equals total length else if (length >= totalLength) { //console.log('last', length); let ptLast = seglast.points[seglast.points.length - 1] let angleLast = seglast.angles[seglast.angles.length - 1] pt.x = ptLast.x; pt.y = ptLast.y; if (getTangent) { pt.angle = angleLast; if (seglast.type === 'A') { let { arcData } = seglast; ({ rx, ry, xAxisRotation, tangentAdjust } = !arcData.isEllipse ? arcData : seglast.arcData_param); if (rx !== ry) { // calulate tangent angle tangentAngle = normalizeAngle(getTangentAngle(rx, ry, angleLast) - xAxisRotation + tangentAdjust); pt.angle = tangentAngle; } } } if (getSegment) { //pt.index = segments.length - 1; pt.index = pathData.length-1; pt.segIndex = segments.length-1; pt.com = segments[segments.length - 1].com; } if(decimals>-1) pt = roundPoint(pt) return pt; } //loop through segments for (let i = 0; i < segments.length && !foundSegment; i++) { let segment = segments[i]; let { type, lengths, points, total, angles, com } = segment; let end = lengths[lengths.length - 1]; let tStep = 1 / (lengths.length - 1); // find path segment if (end >= length) { foundSegment = true; let foundT = false; let diffLength; switch (type) { case 'L': diffLength = end - length; newT = 1 - (1 / total) * diffLength; pt = pointAtT(points, newT, getTangent, getCpts) pt.type = 'L' if (getTangent) pt.angle = angles[0]; break; case 'A': diffLength = end - length; let { arcData } = segment; //console.log('arcData', arcData); let { rx, ry, cx, cy, startAngle, endAngle, deltaAngle, xAxisRotation, tangentAdjust, sweep } = !arcData.isEllipse ? arcData : segment.arcData_param; // final on-path point let pt1 = segment.points[1] let xAxisRotation_deg = xAxisRotation * rad2deg; // is ellipse if (rx !== ry) { //console.log('ellipse', length); for (let i = 1; i < lengths.length && !foundT; i++) { let lengthN = lengths[i]; if (length < lengthN) { // length is in this range foundT = true; let lengthPrev = lengths[i - 1] let lengthSeg = lengthN - lengthPrev; let lengthDiff = lengthN - length; let rat = (1 / lengthSeg) * lengthDiff || 1; let anglePrev = angles[i - 1]; let angle = angles[i]; // interpolated angle let angleI = (anglePrev - angle) * rat + angle; // get point on ellipse pt = getPointOnEllipse(cx, cy, rx, ry, angleI, xAxisRotation, false, false); // calulate tangent angle tangentAngle = normalizeAngle(getTangentAngle(rx, ry, angleI) - xAxisRotation + tangentAdjust) // return angle pt.angle = tangentAngle; // segment info if (getSegment) { // recalculate large arc based on split length and new delta angles let delta1 = Math.abs(angleI - startAngle) let delta2 = Math.abs(endAngle - angleI) let largeArc1 = delta1 >= Math.PI ? 1 : 0 let largeArc2 = delta2 >= Math.PI ? 1 : 0 pt.commands = [ { type: 'A', values: [rx, ry, xAxisRotation_deg, largeArc1, sweep, pt.x, pt.y] }, { type: 'A', values: [rx, ry, xAxisRotation_deg, largeArc2, sweep, pt1.x, pt1.y] }, ] } } // is at end of segment else if(length === lengthN){ pt = pt1 tangentAngle = normalizeAngle( getTangentAngle(rx, ry, endAngle) - xAxisRotation + tangentAdjust) pt.angle = tangentAngle //console.log('last!!!', pt); foundT = true; } } } else { newT = 1 - (1 / total) * diffLength; let newAngle = -deltaAngle * newT; // rotate point let cosA = Math.cos(newAngle); let sinA = Math.sin(newAngle); let p0 = segment.points[0]; pt = { x: (cosA * (p0.x - cx)) + (sinA * (p0.y - cy)) + cx, y: (cosA * (p0.y - cy)) - (sinA * (p0.x - cx)) + cy } // angle if (getTangent) { let angleOff = deltaAngle > 0 ? PI_half : -PI_half; pt.angle = normalizeAngle(startAngle + (deltaAngle * newT) + angleOff) } // segment info if (getSegment) { let angleI = Math.abs(deltaAngle * newT); let delta1 = Math.abs(deltaAngle - angleI) let delta2 = Math.abs(deltaAngle - delta1) let largeArc1 = delta1 >= Math.PI ? 1 : 0 let largeArc2 = delta2 >= Math.PI ? 1 : 0 //console.log('angleI', angleI, 'deltaAngle', deltaAngle, 'delta1', delta1, newT); pt.commands = [ { type: 'A', values: [rx, ry, xAxisRotation_deg, largeArc1, sweep, pt.x, pt.y] }, { type: 'A', values: [rx, ry, xAxisRotation_deg, largeArc2, sweep, pt1.x, pt1.y] }, ] } } break; case 'C': case 'Q': // is curve for (let i = 0; i < lengths.length && !foundT; i++) { let lengthAtT = lengths[i]; if (getTangent) pt.angle = angles[0]; // first or last point in segment if (i === 0) { pt.x = com.p0.x pt.y = com.p0.y } else if (lengthAtT === length) { pt.x = points[points.length - 1].x pt.y = points[points.length - 1].y } // found length at t range else if (lengthAtT > length && i > 0) { foundT = true; let lengthAtTPrev = i > 0 ? lengths[i - 1] : lengths[i]; let t = tStep * i; // length between previous and current t let tSegLength = lengthAtT - lengthAtTPrev; let diffLength = lengthAtT - length; // ratio between segment length and difference let tScale = (1 / tSegLength) * diffLength || 0; newT = t - tStep * tScale || 0; // return point and optionally angle pt = pointAtT(points, newT, getTangent, getCpts) } } break; } pt.t = newT; } if (getSegment) { //pathdata index pt.index = segment.index; //segment index pt.segIndex = segment.segIndex; pt.com = segment.com; } } return pt; }