Reference Source

src/viewer/scene/camera/CameraPath.js

import {Component} from "../Component.js"
import {SplineCurve} from "../paths/SplineCurve.js"
import {math} from "../math/math.js";

const tempVec3a = math.vec3();

/**
 * @desc Defines a sequence of frames along which a {@link CameraPathAnimation} can animate a {@link Camera}.
 *
 * See {@link CameraPathAnimation} for usage.
 */
class CameraPath extends Component {

    /**
     * Returns "CameraPath".
     *
     * @private
     *
     * @returns {string} "CameraPath"
     */
    get type() {
        return "CameraPath"
    }

    /**
     * @constructor
     * @param {Component} [owner]  Owner component. When destroyed, the owner will destroy this CameraPath as well.
     * @param [cfg] {*} Configuration
     * @param {String} [cfg.id]  Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.
     * @param {{t:Number, eye:Object, look:Object, up: Object}[]} [cfg.frames] Initial sequence of frames.
     */
    constructor(owner, cfg = {}) {

        super(owner, cfg);

        this._frames = [];

        this._eyeCurve = new SplineCurve(this);
        this._lookCurve = new SplineCurve(this);
        this._upCurve = new SplineCurve(this);

        if (cfg.frames) {
            this.addFrames(cfg.frames);
            this.smoothFrameTimes(1);
        }
    }

    /**
     * Gets the camera frames in this CameraPath.
     *
     * @returns {{t:Number, eye:Object, look:Object, up: Object}[]} The frames on this CameraPath.
     */
    get frames() {
        return this._frames;
    }

    /**
     * Gets the {@link SplineCurve} along which {@link Camera#eye} travels.
     * @returns {SplineCurve} The SplineCurve for {@link Camera#eye}.
     */
    get eyeCurve() {
        return this._eyeCurve;
    }

    /**
     * Gets the {@link SplineCurve} along which {@link Camera#look} travels.
     * @returns {SplineCurve} The SplineCurve for {@link Camera#look}.
     */
    get lookCurve() {
        return this._lookCurve;
    }

    /**
     * Gets the {@link SplineCurve} along which {@link Camera#up} travels.
     * @returns {SplineCurve} The SplineCurve for {@link Camera#up}.
     */
    get upCurve() {
        return this._upCurve;
    }

    /**
     * Adds a frame to this CameraPath, given as the current position of the {@link Camera}.
     *
     * @param {Number} t Time instant for the new frame.
     */
    saveFrame(t) {
        const camera = this.scene.camera;
        this.addFrame(t, camera.eye, camera.look, camera.up);
    }

    /**
     * Adds a frame to this CameraPath, specified as values for eye, look and up vectors at a given time instant.
     *
     * @param {Number} t Time instant for the new frame.
     * @param {Number[]} eye A three-element vector specifying the eye position for the new frame.
     * @param {Number[]} look A three-element vector specifying the look position for the new frame.
     * @param {Number[]} up A three-element vector specifying the up vector for the new frame.
     */
    addFrame(t, eye, look, up) {
        const frame = {
            t: t,
            eye: eye.slice(0),
            look: look.slice(0),
            up: up.slice(0)
        };
        this._frames.push(frame);
        this._eyeCurve.points.push(frame.eye);
        this._lookCurve.points.push(frame.look);
        this._upCurve.points.push(frame.up);
    }

    /**
     * Adds multiple frames to this CameraPath, each frame specified as a set of values for eye, look and up vectors at a given time instant.
     *
     * @param {{t:Number, eye:Object, look:Object, up: Object}[]} frames Frames to add to this CameraPath.
     */
    addFrames(frames) {
        let frame;
        for (let i = 0, len = frames.length; i < len; i++) {
            frame = frames[i];
            this.addFrame(frame.t || 0, frame.eye, frame.look, frame.up);
        }
    }

    /**
     * Sets the position of the {@link Camera} to a position interpolated within this CameraPath at the given time instant.
     *
     * @param {Number} t Time instant.
     */
    loadFrame(t) {

        const camera = this.scene.camera;

        t = t / (this._frames[this._frames.length - 1].t - this._frames[0].t);
        t = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);

        camera.eye = this._eyeCurve.getPoint(t, tempVec3a);
        camera.look = this._lookCurve.getPoint(t, tempVec3a);
        camera.up = this._upCurve.getPoint(t, tempVec3a);
    }

    /**
     * Gets eye, look and up vectors on this CameraPath at a given instant.
     *
     * @param {Number} t Time instant.
     * @param {Number[]} eye The eye position to update.
     * @param {Number[]} look The look position to update.
     * @param {Number[]} up The up vector to update.
     */
    sampleFrame(t, eye, look, up) {
        t = t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
        this._eyeCurve.getPoint(t, eye);
        this._lookCurve.getPoint(t, look);
        this._upCurve.getPoint(t, up);
    }

    /**
     * Given a total duration (in seconds) for this CameraPath, recomputes the time instant at each frame so that,
     * when animated by {@link CameraPathAnimation}, the {@link Camera} will move along the path at a constant rate.
     *
     * @param {Number} duration The total duration for this CameraPath.
     */
    smoothFrameTimes(duration) {
        const numFrames = this._frames.length;
        if (numFrames === 0) {
            return;
        }
        const vec = math.vec3();
        var totalLen = 0;
        this._frames[0].t = 0;
        const lens = [];
        for (let i = 1, len = this._frames.length; i < len; i++) {
            var lenVec = math.lenVec3(math.subVec3(this._frames[i].eye, this._frames[i - 1].eye, vec));
            lens[i] = lenVec;
            totalLen += lenVec;
        }
        for (let i = 1, len = this._frames.length; i < len; i++) {
            const interFrameRate = (lens[i] / totalLen) * duration;
            this._frames[i].t = this._frames[i-1].t + interFrameRate;
        }
    }

    /**
     * Removes all frames from this CameraPath.
     */
    clearFrames() {
        this._frames = [];
        this._eyeCurve.points = [];
        this._lookCurve.points = [];
        this._upCurve.points = [];
    }
}

export {CameraPath}