UNPKG

@equinor/esv-intersection

Version:

Intersection component package with testing and automatic documentation.

156 lines (140 loc) 5.73 kB
import Vector2 from '@equinor/videx-vector2'; import { clamp } from '@equinor/videx-math'; import { CurveInterpolator } from 'curve-interpolator'; import { Vector } from 'curve-interpolator/dist/src/core/interfaces'; import { CurveInterpolatorOptions } from 'curve-interpolator/dist/src/curve-interpolator'; import { RootFinder } from '../utils/root-finder'; import { ArcLength } from '../utils/arc-length'; import { BinarySearch } from '../utils/binary-search'; export class ExtendedCurveInterpolator extends CurveInterpolator { arcLengthLookup: number[] = []; constructor(points: Vector[], options?: CurveInterpolatorOptions) { super(points, options); this.findTForArcLength = this.findTForArcLength.bind(this); this.findTByRootForArcLength = this.findTByRootForArcLength.bind(this); this.findApproxTForArcLength = this.findApproxTForArcLength.bind(this); this.findTQuickForArcLength = this.findTQuickForArcLength.bind(this); this.generateArcLengthLookup = this.generateArcLengthLookup.bind(this); this.getArcLength = this.getArcLength.bind(this); this.getQuickArcLength = this.getQuickArcLength.bind(this); this.getPointAtArcLength = this.getPointAtArcLength.bind(this); this.getPointAt = this.getPointAt.bind(this); } /** * Function which finds t value for arc length * @param {Number} arcLength Target arc length * @param {Number} tolerance Tolerance for result * @param {Number} iterations Max number of iterations to use */ findTForArcLength(arcLength: number, options?: { approxT?: boolean; quickT?: boolean; normalizedLength?: number }): number { // TODO: Ideally the CurveInterpolator should be able to provide t for curve length let t; if (options?.approxT) { t = this.findApproxTForArcLength(arcLength, options?.normalizedLength); } else if (options?.quickT) { t = this.findTQuickForArcLength(arcLength); } else { t = this.findTByRootForArcLength(arcLength); } return t; } /** * Function which finds t value for arc length by finding root * @param {Number} arcLength Target arc length * @param {Number} tolerance Tolerance for result * @param {Number} iterations Max number of iterations to use */ findTByRootForArcLength(arcLength: number, tolerance = 0.01, iterations = 100): number { if (arcLength <= 0) { return 0.0; } if (arcLength >= this.length) { return 1.0; } const t = RootFinder.findRoot((x) => arcLength - this.getQuickArcLength(0, x), tolerance, iterations, arcLength / this.length); return t; } /** * Function which finds t value for arc length by simple approximation * @param {Number} arcLength Target arclength */ findApproxTForArcLength(arcLength: number, normalizedLength?: number): number { const t = arcLength / (normalizedLength || this.length); return t; } /** * Function which finds t value for arc length using lookup table * @param {Number} arcLength Target arclength */ findTQuickForArcLength(arcLength: number): number { if (this.arcLengthLookup.length === 0) { this.generateArcLengthLookup(); } const index = BinarySearch.search(this.arcLengthLookup, arcLength); const v1 = this.arcLengthLookup[index]!; const v2 = this.arcLengthLookup[index + 1]!; const t = (index + (arcLength - v1) / (v2 - v1)) / this.arcLengthLookup.length; return t; } generateArcLengthLookup(segments = 1000): void { let lastPos = this.getPointAt(0); let length = 0; for (let i = 0; i < segments; i++) { const pos = this.getPointAt(i / (segments - 1)); const delta = Vector2.distance(lastPos as number[], pos as number[]); length += delta; this.arcLengthLookup.push(length); lastPos = pos; } } /** * Function calculating length along curve using interpolator. * @param {Number} from t at start (default = 0) * @param {Number} to t at end (default = 1) */ getArcLength(from = 0, to = 1): number { if (from === 0 && to === 1) { return this.length; } const tolerance = 0.002; return ArcLength.bisect((t: number) => this.getPointAt(t), from, to, tolerance); } /** * Function calculating length along curve using interpolator. * @param {Number} from t at start (default = 0) * @param {Number} to t at end (default = 1) */ getQuickArcLength(from = 0, to = 1): number { let fromLength = 0; let toLength = this.length; if (this.arcLengthLookup.length === 0) { this.generateArcLengthLookup(); } if (from !== 0) { const fromIndex = Math.floor(from * this.arcLengthLookup.length); const fromLl = this.arcLengthLookup[fromIndex]!; const fromLh = this.arcLengthLookup[fromIndex + 1]!; fromLength = fromLl + ((from * this.arcLengthLookup.length) % this.arcLengthLookup.length) * (fromLh - fromLl); } if (to !== 1) { const toIndex = Math.floor(to * this.arcLengthLookup.length); const toLl = this.arcLengthLookup[toIndex]!; const toLh = this.arcLengthLookup[toIndex + 1]!; toLength = toLl + ((from * this.arcLengthLookup.length) % this.arcLengthLookup.length) * (toLh - toLl); } const totalLength = toLength - fromLength; return totalLength; } /** * Function getting a point at curve length. * @param {Number} arcLength */ getPointAtArcLength(arcLength: number, options?: { approxT?: boolean; quickT?: boolean; normalizedLength?: number }): Vector { const t = this.findTForArcLength(arcLength, options); return this.getPointAt(t); } override getPointAt(t: number): Vector { const tl = clamp(t, 0, 1); return super.getPointAt(tl); } }