UNPKG

polygonjs-engine

Version:

node-based webgl 3D engine https://polygonjs.com

172 lines (159 loc) 5.47 kB
import {Number2} from '../../../types/GlobalTypes'; import {PolyScene} from '../PolyScene'; import {CoreGraphNode} from '../../../core/graph/CoreGraphNode'; import {SceneEvent} from '../../poly/SceneEvent'; import {SceneEventType} from './events/SceneEventsController'; import {EventContext} from './events/_BaseEventsController'; type FrameRange = Number2; // ensure that FPS remains a float // to have divisions and multiplications also give a float const FPS = 60.0; export class TimeController { private _frame: number = 1; private _time: number = 0; private _prev_performance_now: number = 0; private _graph_node: CoreGraphNode; private _frame_range: FrameRange = [1, 600]; private _realtimeState = true; private _frameRangeLocked: [boolean, boolean] = [true, true]; private _playing: boolean = false; private _PLAY_EVENT_CONTEXT: EventContext<Event> | undefined; private _PAUSE_EVENT_CONTEXT: EventContext<Event> | undefined; private _TICK_EVENT_CONTEXT: EventContext<Event> | undefined; get PLAY_EVENT_CONTEXT() { return (this._PLAY_EVENT_CONTEXT = this._PLAY_EVENT_CONTEXT || {event: new Event(SceneEventType.PLAY)}); } get PAUSE_EVENT_CONTEXT() { return (this._PAUSE_EVENT_CONTEXT = this._PAUSE_EVENT_CONTEXT || {event: new Event(SceneEventType.PAUSE)}); } get TICK_EVENT_CONTEXT() { return (this._TICK_EVENT_CONTEXT = this._TICK_EVENT_CONTEXT || {event: new Event(SceneEventType.TICK)}); } constructor(private scene: PolyScene) { this._graph_node = new CoreGraphNode(scene, 'time controller'); } get graph_node() { return this._graph_node; } get frame(): number { return this._frame; } get time(): number { return this._time; } get frame_range(): FrameRange { return this._frame_range; } get frameRangeLocked(): [boolean, boolean] { return this._frameRangeLocked; } get realtimeState() { return this._realtimeState; } setFrameRange(start_frame: number, end_frame: number) { this._frame_range[0] = Math.floor(start_frame); this._frame_range[1] = Math.floor(end_frame); this.scene.dispatchController.dispatch(this._graph_node, SceneEvent.FRAME_RANGE_UPDATED); } setFrameRange_locked(start_locked: boolean, end_locked: boolean) { this._frameRangeLocked[0] = start_locked; this._frameRangeLocked[1] = end_locked; this.scene.dispatchController.dispatch(this._graph_node, SceneEvent.FRAME_RANGE_UPDATED); } set_realtimeState(state: boolean) { this._realtimeState = state; this.scene.dispatchController.dispatch(this._graph_node, SceneEvent.REALTIME_STATUS_UPDATED); } // set_fps(fps: number) { // this._fps = Math.floor(fps); // this._frame_interval = 1000 / this._fps; // this.scene.events_controller.dispatch(this._graph_node, SceneEvent.FRAME_RANGE_UPDATED); // } setTime(time: number, update_frame = true) { if (time != this._time) { this._time = time; if (update_frame) { const new_frame = Math.floor(this._time * FPS); const bounded_frame = this._ensure_frame_within_bounds(new_frame); if (new_frame != bounded_frame) { this.setFrame(bounded_frame, true); } else { this._frame = new_frame; } } // update time dependents this.scene.dispatchController.dispatch(this._graph_node, SceneEvent.FRAME_UPDATED); this.scene.uniforms_controller.update_time_dependent_uniform_owners(); // we block updates here, so that dependent nodes only cook once this.scene.cooker.block(); this.graph_node.setSuccessorsDirty(); this.scene.cooker.unblock(); // dispatch events after nodes have cooked this.scene.eventsDispatcher.sceneEventsController.processEvent(this.TICK_EVENT_CONTEXT); } } setFrame(frame: number, update_time = true) { if (frame != this._frame) { frame = this._ensure_frame_within_bounds(frame); if (frame != this._frame) { this._frame = frame; if (update_time) { this.setTime(this._frame / FPS, false); } } } } increment_time_if_playing() { if (this._playing) { if (!this.scene.root().are_children_cooking()) { this.increment_time(); } } } increment_time() { if (this._realtimeState) { const performance_now = performance.now(); const delta = (performance_now - this._prev_performance_now) / 1000.0; const new_time = this._time + delta; this._prev_performance_now = performance_now; this.setTime(new_time); } else { this.setFrame(this.frame + 1); } } _ensure_frame_within_bounds(frame: number): number { if (this._frameRangeLocked[0] && frame < this._frame_range[0]) { return this._frame_range[1]; } if (this._frameRangeLocked[1] && frame > this._frame_range[1]) { return this._frame_range[0]; } return frame; } get playing() { return this._playing === true; } pause() { if (this._playing == true) { this._playing = false; // TODO: try and unify the dispatch controller and events dispatcher this.scene.dispatchController.dispatch(this._graph_node, SceneEvent.PLAY_STATE_UPDATED); this.scene.eventsDispatcher.sceneEventsController.processEvent(this.PAUSE_EVENT_CONTEXT); } } play() { if (this._playing !== true) { this._playing = true; this._prev_performance_now = performance.now(); this.scene.dispatchController.dispatch(this._graph_node, SceneEvent.PLAY_STATE_UPDATED); this.scene.eventsDispatcher.sceneEventsController.processEvent(this.PLAY_EVENT_CONTEXT); } } toggle_play_pause() { if (this.playing) { this.pause(); } else { this.play(); } } }