Repository

js/Mobilizing/renderer/3D/three/texture/ImageSequenceTexture.js

import Component from "../../../../core/Component";
import Timer from "../../../../time/Timer";
import * as debug from "../../../../core/util/Debug";

/**
* The ImageSequenceTexture represents a special kind of 2D Texture that is built from a sequence of pictures that you can play at a given rate and with a given play mode.
*
* @class ImageSequenceTexture
*/
const MODE_FORWARD = "forward";
const MODE_BACKWARD = "backward";
const MODE_PINGPONG = "pingpong";
const MODE_RANDOM = "random";

export default class ImageSequenceTexture extends Component {
    /**
    @param {Object} params Parameters object, given by the constructor.
    @param {Number} params.fps the frame per second of this sequence
    @param {String} params.sequenceMode the mode of playing for this sequence. One of "forward", "backward", "pingpong", "random".
    @param {Number} params.direction the direction of the play head. 1 for forward, -1 for backward
    @param {Number} params.lapsToDo the number of loop to do before to stop the animation
    @param {Array <Texture>} params.textures the texture array that make the image sequence
    */
    constructor({
        fps = 10,
        sequenceMode = MODE_FORWARD,
        direction = 1,
        lapsToDo = -1,
        textures = [],
    } = {}) {
        super(...arguments);

        this.sequenceMode = sequenceMode;
        this.direction = direction;
        this.textures = textures;
        this.frameRate = fps;
        this.lapsToDo = lapsToDo;

        this.frameCount = this.textures.length - 1;
        this.playing = false;
        this.laps = 0;
        this.currentFrame = 0;

        this.timer = new Timer({
            "interval": 1000 / this.frameRate,
            "callback": this.updateSequence.bind(this),
        });
        this.chain(this.timer); //chainning system
    }

    setup() {
        super.setup();

        this.enterFrame = 0;
        this.currentFrame = 0;
        this.outFrame = this.frameCount;

        this.timer.on();
        this.timer.start();
    }

    /**
    Set the starting frame of the current loaded sequence.
    @param {Integer} frame starting frame.
    */
    setEnterFrame(frame) {
        if (frame >= 0 && frame < this.frameCount) {
            this.enterFrame = frame;
            this.currentFrame = this.enterFrame;
        }
        else {
            this.enterFrame = 0;
            this.currentFrame = 0;
            debug.log("ERROR enterFrame %i is out of sequence bounds - back to 0", frame);
        }
    }

    /**
    Set the ending frame of the current loaded sequence.
    @param {Integer} frame ending frame.
    */
    setOutFrame(frame) {
        if (frame >= this.enterFrame && frame < this.frameCount) {
            this.outFrame = frame;
        }
        else {
            this.outFrame = this.frameCount - 1;
            debug.log("ERROR outFrame %i is out of sequence bounds - back to frameCount", frame);
        }
    }

    /**
    Set the current frame to force the play head, can be used on pause to manually control the play head
    @event frameUpdate triggered by this component's inner events
    */
    setCurrentFrame(frame) {
        this.currentFrame = frame;
        this.events.trigger("frameUpdate");
    }

    /**
    Set the sequence playing mode.
    @param {String} mode playing mode : "forward", "backward", "pingpong", "random".
    */
    setMode(mode) {
        this.sequenceMode = mode;
    }

    /**
    update the Image sequence. You have to call this once per frame for the Image sequence Texture to update and run its own logic.
    @event frameUpdate triggered by this component's inner events
    */
    updateSequence() {
        if (!this.playing) {
            return;
        }

        if (this.sequenceMode === MODE_FORWARD) {
            if (this.laps >= 0 && this.laps < this.lapsToDo) {
                this.currentFrame++;
                this.currentFrame %= this.outFrame + 1;

                if (this.currentFrame === 0) {
                    this.currentFrame = this.enterFrame;
                }

                if (this.currentFrame === this.outFrame) {
                    this.laps++;
                }
            }
            else if (this.lapsToDo < 0) {
                this.currentFrame++;
                this.currentFrame %= this.outFrame + 1;
                if (this.currentFrame === 0) {
                    this.currentFrame = this.enterFrame;
                }
            }
            if (this.lapsToDo === this.laps) {
                this.stop();
            }
            debug.log(`currentFrame = ${this.currentFrame}`);
        }
        else if (this.sequenceMode === MODE_BACKWARD) {
            if (this.laps >= 0 && this.laps < this.lapsToDo) {
                this.currentFrame--;

                if (this.currentFrame < this.enterFrame) {
                    this.currentFrame = this.outFrame;
                }
                if (this.currentFrame === this.enterFrame) {
                    this.laps++;
                }

            }
            else if (this.lapsToDo < 0) {
                this.currentFrame--;

                if (this.currentFrame < this.enterFrame) {
                    this.currentFrame = this.outFrame;
                }
            }

            if (this.lapsToDo === this.laps) {
                this.stop();
            }
            debug.log(`currentFrame = ${this.currentFrame}`);
        }
        else if (this.sequenceMode === MODE_PINGPONG) {

            if (this.laps >= 0 && this.laps < this.lapsToDo) {
                this.currentFrame += this.direction;

                //protection against border over
                if (this.currentFrame < 0) {
                    this.currentFrame = 0;
                }
                if (this.currentFrame > this.outFrame) {
                    this.currentFrame = this.outFrame;
                }

                if (this.currentFrame >= this.outFrame) {
                    this.direction = -this.direction;
                    this.laps++;
                }
                if (this.currentFrame <= this.enterFrame) {
                    this.direction = -this.direction;
                    this.laps++;
                }
            }
            else if (this.lapsToDo < 0) {
                this.currentFrame += this.direction;
                if (this.currentFrame >= this.outFrame) {
                    this.direction = -this.direction;
                }
                if (this.currentFrame <= this.enterFrame) {
                    this.direction = -this.direction;
                }
            }

            if (this.lapsToDo === this.laps) {
                this.stop();
            }
            debug.log(`currentFrame = ${this.currentFrame}`);
        }
        else if (this.sequenceMode === MODE_RANDOM) {
            this.currentFrame = Math.floor(Math.random() * (this.outFrame - this.enterFrame) + this.enterFrame);
            debug.log(`currentFrame = ${this.currentFrame}`);
        }

        this.events.trigger("frameUpdate");
    }

    /**
    Get the current Image Sequence frame Texture
    @return {Texture} the texture
    */
    getCurrentTexture() {
        return this.textures[this.currentFrame];
    }

    /**
    play the Image Sequence.
    */
    play() {
        this.playing = true;
    }

    /**
    pause the Image Sequence.
    */
    pause() {
        this.playing = false;
    }

    /**
    stop the Image Sequence. The image frame is resetted.
    */
    stop() {
        this.currentFrame = 0;
        this.playing = false;
        this.laps = 0;
    }

    /**
    Loop the image sequence for a given number of iterations (once play() is called).
    @param {Number} count : -1 means infinity.
    */
    loop(count) {
        this.lapsToDo = count;
    }
}