Source: math/curve3.js

import { Vector3 } from './vector3.js';

/**
 * Default segment count for curve sampling.
 *
 * @type {number}
 */
const DEFAULT_SAMPLE_SEGMENTS = 32;

/**
 * Minimum allowed segment count.
 *
 * @type {number}
 */
const MIN_SAMPLE_SEGMENTS = 1;

/**
 * Start value for the sampling loop.
 *
 * @type {number}
 */
const SAMPLE_START_INDEX = 0;

/**
 * Step to increment the sample index.
 *
 * @type {number}
 */
const SAMPLE_INDEX_INCREMENT = 1;

/**
 * First sample value.
 *
 * @type {number}
 */
const SAMPLE_START_PARAMETER = 0;

/**
 * End sample value.
 *
 * @type {number}
 */
const SAMPLE_END_PARAMETER = 1;

/**
 * Base class for 3D curves.
 */
export class Curve3 {

    /**
     * Abstract curve base.
     *
     * @throws {Error} When instantiated directly.
     */
    constructor() {
        if (new.target === Curve3) {
            throw new Error('`Curve3` is abstract and must be subclassed.');
        }
    }

    /* eslint-disable */

    /**
     * Computes a point on the curve at normalized parameter.
     *
     * @param {number} normalizedParameter - Parameter in [0..1].
     * @param {Vector3} [out]              - Optional output vector.
     * @returns {Vector3}
     * @throws {Error} When not implemented by subclass.
     */
    getPoint(normalizedParameter, out = new Vector3()) {
        throw new Error('`Curve3.getPoint` must be implemented in subclasses.');
    }

    /**
     * Computes a tangent on the curve at normalized parameter.
     *
     * @param {number} normalizedParameter - Parameter in [0..1].
     * @param {Vector3} [out]              - Optional output vector.
     * @returns {Vector3}
     * @throws {Error} When not implemented by subclass.
     */
    getTangent(normalizedParameter, out = new Vector3()) {
        throw new Error('`Curve3.getTangent` must be implemented in subclasses.');
    }

    /* eslint-enable */

    /**
     * Returns a point on the curve at normalized parameter.
     *
     * @param {number} normalizedParameter - Parameter in [0..1].
     * @param {Vector3} [out]              - Optional output vector.
     * @returns {Vector3}
     * @throws {Error} When not implemented by subclass.
     */
    getPointAt(normalizedParameter, out = new Vector3()) {
        return this.getPoint(normalizedParameter, out);
    }

    /**
     * Returns a tangent on the curve at normalized parameter.
     *
     * @param {number} normalizedParameter - Parameter in [0..1].
     * @param {Vector3} [out]              - Optional output vector.
     * @returns {Vector3}
     * @throws {Error} When not implemented by subclass.
     */
    getTangentAt(normalizedParameter, out = new Vector3()) {
        return this.getTangent(normalizedParameter, out);
    }

    /**
     * Samples the curve into the polyline.
     *
     * @param {number} [segments = 32] - Sample segment count.
     * @returns {Vector3[]}
     * @throws {TypeError}  When segments is not a finite number.
     * @throws {RangeError} When segments is not an `integer >= 1`.
     */
    getPoints(segments = DEFAULT_SAMPLE_SEGMENTS) {
        if (typeof segments !== 'number' || !Number.isFinite(segments)) {
            throw new TypeError('`Curve3.getPoints` expects `segments` as a finite number.');
        }

        if (!Number.isInteger(segments) || segments < MIN_SAMPLE_SEGMENTS) {
            throw new RangeError('`Curve3.getPoints` expects `segments` as an integer >= 1.');
        }

        const points       = [];
        const segmentCount = segments;
        const maxIndex     = segmentCount;

        for (let index = SAMPLE_START_INDEX; index <= maxIndex; index += SAMPLE_INDEX_INCREMENT) {
            const sampleParameter = SAMPLE_START_PARAMETER + (SAMPLE_END_PARAMETER - SAMPLE_START_PARAMETER) * (index / segmentCount);
            points.push(this.getPoint(sampleParameter, new Vector3()));
        }

        return points;
    }
}