UNPKG

polygonjs-engine

Version:

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

353 lines (332 loc) 11.2 kB
import {Number2, Number3, Number4} from '../../types/GlobalTypes'; import {Vector2} from 'three/src/math/Vector2'; import {Vector3} from 'three/src/math/Vector3'; import {Vector4} from 'three/src/math/Vector4'; import {Quaternion} from 'three/src/math/Quaternion'; import {Object3D} from 'three/src/core/Object3D'; import {TimelineBuilder, Operation} from './TimelineBuilder'; import {PropertyTarget} from './PropertyTarget'; import {PolyScene} from '../../engine/scene/PolyScene'; import {BaseNodeType} from '../../engine/nodes/_Base'; import {BaseParamType} from '../../engine/params/_Base'; import {ParamType} from '../../engine/poly/ParamType'; import {FloatParam} from '../../engine/params/Float'; import {Vector2Param} from '../../engine/params/Vector2'; import {Vector3Param} from '../../engine/params/Vector3'; import {Vector4Param} from '../../engine/params/Vector4'; import {TypeAssert} from '../../engine/poly/Assert'; import {AnimNodeEasing} from './Constant'; import {Poly} from '../../engine/Poly'; import {CoreType} from '../Type'; export type AnimPropertyTargetValue = number | Vector2 | Vector3 | Vector4 | Quaternion; interface Object3DProps { target_property: AnimPropertyTargetValue; to_target: object; property_names: string[]; } const PROPERTY_SEPARATOR = '.'; export class TimelineBuilderProperty { private _property_name: string | undefined; private _target_value: AnimPropertyTargetValue | undefined; constructor() {} setName(name: string) { this._property_name = name; } set_target_value(value: AnimPropertyTargetValue) { this._target_value = value; } name() { return this._property_name; } target_value() { return this._target_value; } clone() { const cloned = new TimelineBuilderProperty(); if (this._property_name) { cloned.setName(this._property_name); } if (this._target_value != null) { const new_target_value = CoreType.isNumber(this._target_value) ? this._target_value : this._target_value.clone(); cloned.set_target_value(new_target_value); } return cloned; } add_to_timeline( timeline_builder: TimelineBuilder, scene: PolyScene, timeline: gsap.core.Timeline, target: PropertyTarget ) { const objects = target.objects(scene); if (objects) { this._populate_with_objects(objects, timeline_builder, timeline); } else { const node = target.node(scene); if (node) { this._populate_with_node(node, timeline_builder, timeline); } } } private _populate_with_objects( objects: Object3D[], timeline_builder: TimelineBuilder, timeline: gsap.core.Timeline ) { if (!this._property_name) { Poly.warn('no property name given'); return; } if (this._target_value == null) { Poly.warn('no target value given'); return; } const operation = timeline_builder.operation(); const update_callback = timeline_builder.update_callback(); for (let object3d of objects) { // const target_property = (object3d as any)[this._property_name as any] as TargetValue; // let to_target: object | null = null; const props = this._scene_graph_props(object3d, this._property_name); if (props) { let {target_property, to_target, property_names} = props; const vars = this._common_vars(timeline_builder); // add update_matrix if (update_callback && update_callback.update_matrix()) { const old_matrix_auto_update = object3d.matrixAutoUpdate; vars.onStart = () => { object3d.matrixAutoUpdate = true; }; vars.onComplete = () => { object3d.matrixAutoUpdate = old_matrix_auto_update; }; } // handle quaternions as a special case if (target_property instanceof Quaternion && this._target_value instanceof Quaternion) { const proxy = {value: 0}; const qTarget = target_property; const qStart = new Quaternion().copy(target_property); const qEnd = this._target_value; vars.onUpdate = () => { Quaternion.slerp(qStart, qEnd, qTarget, proxy.value); }; to_target = proxy; vars.value = 1; } if (CoreType.isNumber(this._target_value)) { if (CoreType.isNumber(target_property)) { for (let property_name of property_names) { vars[property_name] = this.with_op(target_property, this._target_value, operation); } } } else { if (!CoreType.isNumber(target_property)) { for (let property_name of property_names) { vars[property_name] = this.with_op( target_property[property_name as 'x'], this._target_value[property_name as 'x'], operation ); } } } if (to_target) { this._start_timeline(timeline_builder, timeline, vars, to_target); } } } } private _scene_graph_props(object: object, property_name: string): Object3DProps | undefined { const elements = property_name.split(PROPERTY_SEPARATOR); if (elements.length > 1) { const first_element = elements.shift() as string; const sub_object = (object as any)[first_element as any] as object; if (sub_object) { const sub_property_name = elements.join(PROPERTY_SEPARATOR); return this._scene_graph_props(sub_object, sub_property_name); } } else { const target_property = (object as any)[property_name as any] as AnimPropertyTargetValue; let to_target: object | null = null; const property_names: string[] = []; if (CoreType.isNumber(target_property)) { to_target = object; property_names.push(property_name); } else { to_target = target_property; if (this._target_value instanceof Vector2) { property_names.push('x', 'y'); } if (this._target_value instanceof Vector3) { property_names.push('x', 'y', 'z'); } if (this._target_value instanceof Vector4) { property_names.push('x', 'y', 'z', 'w'); } if (this._target_value instanceof Quaternion) { // is_quaternion = true; } } return { target_property: target_property, to_target: to_target, property_names: property_names, }; } } private _populate_with_node(node: BaseNodeType, timeline_builder: TimelineBuilder, timeline: gsap.core.Timeline) { const target_param = node.p[this._property_name as any] as BaseParamType; if (!target_param) { Poly.warn(`${this._property_name} not found on node ${node.fullPath()}`); return; } if (target_param) { this._populate_vars_for_param(target_param, timeline_builder, timeline); } } private _populate_vars_for_param( param: BaseParamType, timeline_builder: TimelineBuilder, timeline: gsap.core.Timeline ) { switch (param.type()) { case ParamType.FLOAT: { this._populate_vars_for_param_float(param as FloatParam, timeline_builder, timeline); } case ParamType.VECTOR2: { this._populate_vars_for_param_vector2(param as Vector2Param, timeline_builder, timeline); } case ParamType.VECTOR3: { this._populate_vars_for_param_vector3(param as Vector3Param, timeline_builder, timeline); } case ParamType.VECTOR4: { this._populate_vars_for_param_vector4(param as Vector4Param, timeline_builder, timeline); } } } private _populate_vars_for_param_float( param: FloatParam, timeline_builder: TimelineBuilder, timeline: gsap.core.Timeline ) { if (!CoreType.isNumber(this._target_value)) { Poly.warn('value is not a numbber', this._target_value); return; } const vars = this._common_vars(timeline_builder); const proxy = {num: param.value}; vars.onUpdate = () => { param.set(proxy.num); }; const operation = timeline_builder.operation(); vars.num = this.with_op(param.value, this._target_value, operation); this._start_timeline(timeline_builder, timeline, vars, proxy); } private _populate_vars_for_param_vector2( param: Vector2Param, timeline_builder: TimelineBuilder, timeline: gsap.core.Timeline ) { if (!(this._target_value instanceof Vector2)) { return; } const vars = this._common_vars(timeline_builder); const proxy = param.value.clone(); const proxy_array: Number2 = [0, 0]; vars.onUpdate = () => { proxy.toArray(proxy_array); param.set(proxy_array); }; const operation = timeline_builder.operation(); vars.x = this.with_op(param.value.x, this._target_value.x, operation); vars.y = this.with_op(param.value.y, this._target_value.y, operation); this._start_timeline(timeline_builder, timeline, vars, proxy); } private _populate_vars_for_param_vector3( param: Vector3Param, timeline_builder: TimelineBuilder, timeline: gsap.core.Timeline ) { if (!(this._target_value instanceof Vector3)) { return; } const vars = this._common_vars(timeline_builder); const proxy = param.value.clone(); const proxy_array: Number3 = [0, 0, 0]; vars.onUpdate = () => { proxy.toArray(proxy_array); param.set(proxy_array); }; const operation = timeline_builder.operation(); vars.x = this.with_op(param.value.x, this._target_value.x, operation); vars.y = this.with_op(param.value.y, this._target_value.y, operation); vars.z = this.with_op(param.value.z, this._target_value.z, operation); this._start_timeline(timeline_builder, timeline, vars, proxy); } private _populate_vars_for_param_vector4( param: Vector4Param, timeline_builder: TimelineBuilder, timeline: gsap.core.Timeline ) { if (!(this._target_value instanceof Vector4)) { return; } const vars = this._common_vars(timeline_builder); const proxy = param.value.clone(); const proxy_array: Number4 = [0, 0, 0, 0]; vars.onUpdate = () => { proxy.toArray(proxy_array); param.set(proxy_array); }; const operation = timeline_builder.operation(); vars.x = this.with_op(param.value.x, this._target_value.x, operation); vars.y = this.with_op(param.value.y, this._target_value.y, operation); vars.z = this.with_op(param.value.z, this._target_value.z, operation); vars.w = this.with_op(param.value.w, this._target_value.w, operation); this._start_timeline(timeline_builder, timeline, vars, proxy); } private with_op(current_value: number, value: number, operation: Operation) { switch (operation) { case Operation.SET: return value; case Operation.ADD: return current_value + value; case Operation.SUBSTRACT: return current_value - value; } TypeAssert.unreachable(operation); } private _common_vars(timeline_builder: TimelineBuilder) { const duration = timeline_builder.duration(); const vars: gsap.TweenVars = {duration: duration}; // easing const easing = timeline_builder.easing() || AnimNodeEasing.NONE; if (easing) { vars.ease = easing; } // delay const delay = timeline_builder.delay(); if (delay != null) { vars.delay = delay; } // repeat const repeat_params = timeline_builder.repeat_params(); if (repeat_params) { vars.repeat = repeat_params.count; vars.repeatDelay = repeat_params.delay; vars.yoyo = repeat_params.yoyo; } return vars; } private _start_timeline( timeline_builder: TimelineBuilder, timeline: gsap.core.Timeline, vars: gsap.TweenVars, target: object ) { const position = timeline_builder.position(); const position_param = position ? position.to_parameter() : undefined; timeline.to(target, vars, position_param); } }