UNPKG

@turbox3d/math

Version:

Large-scale graphics application math library

300 lines (266 loc) 8.33 kB
import { MathUtils } from '../MathUtils'; import { Tolerance } from './Tolerance'; export class Interval { static invalid(): Interval { return new Interval(NaN, NaN); } /** * interval mul by number * @param alpha * @returns */ static mul(interval: Interval, alpha: number): Interval { if (!interval) { return Interval.invalid(); } return new Interval(interval.start * alpha, interval.end * alpha); } /** * Get the intersect intervals of two list of intervals. * @param intervals1 * @param intervals2 * @returns List<Interval> */ static intersect(intervals1: Interval[], intervals2: Interval[]): Interval[] { if (!intervals1 || intervals1.length === 0) { return []; } if (!intervals2 || intervals2.length === 0) { return []; } let sortedIntervals1: Interval[] = Interval.union(intervals1); let sortedIntervals2: Interval[] = Interval.union(intervals2); const resultIntervals: Interval[] = []; sortedIntervals1 = sortedIntervals1.sort((n1, n2) => n1.start - n2.start); sortedIntervals2 = sortedIntervals2.sort((n1, n2) => n1.start - n2.start); let index1 = 0; let index2 = 0; while (index1 < sortedIntervals1.length && index2 < sortedIntervals2.length) { if (sortedIntervals1[index1].intersected(sortedIntervals2[index2], true)) { resultIntervals.push(sortedIntervals1[index1].intersection(sortedIntervals2[index2])); } if (sortedIntervals1[index1].end < sortedIntervals2[index2].end) { index1 += 1; } else { index2 += 1; } } return resultIntervals; } /** * Combine the intervals.Return the list of intervals after unioned * @param intervals * @returns {Interval[]} */ static union(intervals: Interval[]): Interval[] { if (!intervals || intervals.length === 0) { return []; } const sortedIntervals: Interval[] = intervals.sort((n1, n2) => n1.start - n2.start); const resultIntervals: Interval[] = []; let temp = sortedIntervals[0]; for (const interval of sortedIntervals) { if (!temp.intersected(interval, true)) { resultIntervals.push(temp); temp = new Interval(interval.start, interval.end); } else { temp = temp.includeInterval(interval); } } resultIntervals.push(temp); return resultIntervals; } private _start: number; private _end: number; /** * constructor * @param start * @param end */ constructor(start: number, end: number) { this._start = start; this._end = end; } /** * Get interval's start */ get start(): number { return this._start; } /** * Get interval's end */ get end(): number { return this._end; } /** * Returns true if this interval is equal to another within specified tolerance. * @param other * @param numericTolerance * @return */ isEqual(other: Interval, numTol?: number): boolean { const tolerance = numTol || Tolerance.global.numTol; return (MathUtils.isEqual(this._start, other.start, tolerance) && MathUtils.isEqual(this._end, other.end, tolerance)); } /** * Get the middle number * @return */ get middle(): number { return 0.5 * (this._start + this._end); } /** * Get the cloned object of this Interval. */ clone(): Interval { return new Interval(this._start, this._end); } /** * Get interval's length * @return */ get length(): number { return Math.abs(this._end - this._start); } /** * interval string * @return */ toString() { return `Interval{start: ${this._start}, end: ${this._end}}`; } /** * Indicates whether the Interval is Valid * @return */ isValid() { return !Number.isNaN(this._start) && !Number.isNaN(this._end); } /** * interval mul by number * @param alpha * @returns */ mul(alpha: number): Interval { return new Interval(this._start * alpha, this._end * alpha); } /** * interval add by number * @param alpha * @returns */ add(interval: Interval): Interval { if (!interval) { return new Interval(this._start, this._end); } return new Interval(this._start + interval.start, this._end + interval.end); } addByNumber(alpha: number): Interval { return new Interval(this._start + alpha, this._end + alpha); } /** * interval sub by Interval * @param interval * @returns */ sub(interval: Interval): Interval { if (!interval) { return new Interval(this._start, this._end); } return new Interval(this._start - interval.start, this._end - interval.end); } /** * Interval interpolate * @param alpha * @returns */ interpolate(alpha: number): number { return (1 - alpha) * this._start + alpha * this._end; } /** * Interval expand * @param length * @returns */ expand(length: number): Interval { return new Interval(this._start - length, this._end + length); } /** * Indicates whether the given value is included in this interval. * @param {number} point * @param {boolean} includeEnds - Indicates whether to check the ends. * @param {number} [tolerance] - Tolerance to check on the ends. * @returns {boolean} */ contains(point: number, includeEnds: boolean, tolerance: number = Tolerance.global.distTol): boolean { if (includeEnds) { return point <= this._end + tolerance && point >= this._start - tolerance; } return point < this._end - tolerance && point > this._start + tolerance; } /** * Indicates whether the given interval is included in this interval. * @param {Interval} interval * @param {boolean} includeEnds - Indicates whether to check the ends. * @param {number} [tolerance] - Tolerance to check on the ends. * @returns {boolean} */ containsInterval(interval: Interval, includeEnds: boolean, tolerance: number = Tolerance.global.distTol): boolean { if (includeEnds) { return interval._start >= this._start - tolerance && interval._end <= this._end + tolerance; } return interval._start > this._start + tolerance && interval._end < this._end - tolerance; } /** * Indicates whether the given interval is intersected with this interval. * @param {Interval} interval * @param {boolean} includeEnds - Indicates whether to check the ends. * @param {number} [tolerance] - Intersection tolerance. * @returns {boolean} */ intersects(interval: Interval, includeEnds: boolean, tolerance: number = Tolerance.global.distTol): boolean { if (includeEnds) { return !(this._start > interval._end + tolerance || this._end < interval._start - tolerance); } return !(this._start >= interval._end - tolerance || this._end <= interval._start + tolerance); } /** * Indicates whether the given interval is intersected with this interval. * @param {Interval} interval * @param {boolean} includeEnds - Indicates whether to check the ends. * @param {number} [tolerance] - Intersection tolerance. * @returns {boolean} */ intersected(interval: Interval, includeEnds: boolean, tolerance: number = Tolerance.global.distTol): boolean { if (includeEnds) { return !(this._start > interval._end + tolerance || this._end < interval._start - tolerance); } return !(this._start >= interval._end - tolerance || this._end <= interval._start + tolerance); } /** * Get the intersect part with interval. If the intervals are not intersectant, we will return an invalid interval * @param interval * @returns {Interval} | null */ intersection(interval: Interval): Interval { if (!interval || !this.intersected(interval, true)) { return Interval.invalid(); } return new Interval(Math.max(interval.start, this.start), Math.min(interval.end, this.end)); } /** * Combine with interval. If the intervals are not intersectant, we will return an invalid interval * @param interval * @returns */ includeInterval(interval: Interval): Interval { if (!interval || !this.intersected(interval, true)) { return Interval.invalid(); } return new Interval(Math.min(interval.start, this.start), Math.max(interval.end, this.end)); } }