polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
353 lines (332 loc) • 11.2 kB
text/typescript
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);
}
}