@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
295 lines (277 loc) • 10 kB
text/typescript
import {Object3D,Quaternion,Color,Vector2,Vector3,Vector4} from 'three';
import {} from 'three';
import {BaseNodeType} from '../../engine/nodes/_Base';
import {BaseParamType} from '../../engine/params/_Base';
import {ParamType} from '../../engine/poly/ParamType';
import {Vector2Param} from '../../engine/params/Vector2';
import {Vector3Param} from '../../engine/params/Vector3';
import {Vector4Param} from '../../engine/params/Vector4';
import {Poly} from '../../engine/Poly';
import {isNumber,isEuler,isVector,isColor,isQuaternion} from '../Type';
import {ColorParam} from '../../engine/params/Color';
import {IntegerParam} from '../../engine/params/Integer';
import {RegisterableProperty} from './AnimatedPropertiesRegister';
import {animBuilderCommonVars} from './vars/Common';
import {AddToTimelineOptions, Operation} from './vars/AnimBuilderTypes';
import {animBuilderStartTimeline} from './vars/StartTimeline';
import {populateVarsForParamVector4} from './vars/type/Vector4';
import {populateVarsForColor, populateVarsForParamColor} from './vars/type/Color';
import {populateVarsForParamVector3} from './vars/type/Vector3';
import {populateVarsForParamVector2} from './vars/type/Vector2';
import {populateVarsForNumber, populateVarsForSingleNumber} from './vars/type/Number';
import {populateVarsForVector} from './vars/type/Vector';
import {populateVarsAndCreateProxyForQuaternion} from './vars/type/Quaternion';
import {populateVarsForEuler} from './vars/type/Euler';
import type {GsapTweenVars} from '../thirdParty/gsap/gsapFactory';
export type AnimPropertyTargetValue = number | Vector2 | Vector3 | Color | Vector4 | Quaternion;
interface Object3DProps {
targetProperty: AnimPropertyTargetValue;
toTarget: object;
propertyNames: string[];
}
interface PopulateVarsOptions {
vars: GsapTweenVars;
targetValue: AnimPropertyTargetValue;
targetProperty: AnimPropertyTargetValue;
propertyNames: string[];
operation: Operation;
}
const PROPERTY_SEPARATOR = '.';
export class TimelineBuilderProperty {
private _propertyName: string | undefined;
private _targetValue: AnimPropertyTargetValue | undefined;
constructor() {}
setName(name: string) {
this._propertyName = name;
}
setTargetValue(value: AnimPropertyTargetValue) {
this._targetValue = value;
}
name() {
return this._propertyName;
}
targetValue() {
return this._targetValue;
}
private _debug = false;
setDebug(debug: boolean) {
this._debug = debug;
}
private _printDebug(message: any) {
if (!this._debug) {
return;
}
console.log(message);
}
clone() {
const cloned = new TimelineBuilderProperty();
if (this._propertyName) {
cloned.setName(this._propertyName);
}
if (this._targetValue != null) {
const newTargetValue = isNumber(this._targetValue) ? this._targetValue : this._targetValue.clone();
cloned.setTargetValue(newTargetValue);
}
return cloned;
}
addToTimeline(options: AddToTimelineOptions) {
const target = options.propertyTarget || options.target;
const objects = target.objects() as Object3D[];
const node = target.node();
this._printDebug(['addToTimeline', target, objects, node]);
if (objects) {
this._populateWithObjects(objects, options);
}
if (node) {
this._populateWithNode(node, options);
}
}
private _populateWithObjects(objects: Object3D[], options: AddToTimelineOptions) {
const {timelineBuilder} = options;
this._printDebug(['_populateWithObjects', objects]);
if (!this._propertyName) {
Poly.warn('no property name given');
return;
}
if (this._targetValue == null) {
Poly.warn('no target value given');
return;
}
const operation = timelineBuilder.operation();
const updateCallback = timelineBuilder.updateCallback();
for (const 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._sceneGraphProps(object3d, this._propertyName);
if (props) {
const registerableProp: RegisterableProperty = {
object: object3d,
propertyName: this._propertyName,
};
let {targetProperty, toTarget, propertyNames} = props;
const vars = animBuilderCommonVars(timelineBuilder);
// add update_matrix
if (updateCallback && updateCallback.updateMatrix()) {
const oldMatrixAutoUpdate = object3d.matrixAutoUpdate;
// matrixAutoUpdate should be set to true during onUpdate and not onStart
// in case another timeline completes and sets it to false
vars.onUpdate = () => {
object3d.matrixAutoUpdate = true;
};
vars.onComplete = () => {
object3d.matrixAutoUpdate = oldMatrixAutoUpdate;
// we still need to update the matrix manually here,
// as (it is specially noticeable on short animations)
// the last step of the timeline will not have the matrix updated
// and the object will therefore look like it has not fully completed
// its animation.
if (!object3d.matrixAutoUpdate) {
object3d.updateMatrix();
}
};
}
// handle quaternions as a special case
if (targetProperty instanceof Quaternion && this._targetValue instanceof Quaternion) {
toTarget = populateVarsAndCreateProxyForQuaternion({
targetValue: this._targetValue,
vars,
targetProperty,
});
}
this._populateVarsForObjectProperty({
targetValue: this._targetValue,
vars,
targetProperty,
propertyNames,
operation,
});
if (toTarget) {
animBuilderStartTimeline({...options, vars, target: toTarget, registerableProp});
}
}
}
}
private _populateVarsForObjectProperty(options: PopulateVarsOptions) {
const {vars, targetValue, targetProperty, propertyNames, operation} = options;
function warnMismatch(expectedType: string) {
Poly.warn(
`mismatch between targetValue and targetProperty (expected ${expectedType})`,
targetValue,
targetProperty
);
}
// number
if (isNumber(targetProperty)) {
if (isNumber(targetValue)) {
return populateVarsForNumber({targetValue, vars, targetProperty, propertyNames, operation});
}
return warnMismatch('number');
}
// euler (needs to be positioned before the isVector, as it would other be caught in the isVector )
// note that for euler, we first test targetProperty, as otherwise a 'position' property would test true here
if (isEuler(targetProperty)) {
if (targetValue instanceof Vector3) {
return populateVarsForEuler({targetValue, vars, targetProperty, propertyNames, operation});
}
return warnMismatch('euler');
}
// vector
if (isVector(targetProperty)) {
if (isVector(targetValue)) {
return populateVarsForVector({targetValue, vars, targetProperty, propertyNames, operation});
}
return warnMismatch('vector');
}
// color
if (isColor(targetProperty)) {
if (isColor(targetValue)) {
return populateVarsForColor({targetValue, vars, targetProperty, propertyNames, operation});
}
return warnMismatch('color');
}
if (isQuaternion(targetProperty)) {
//
}
Poly.warn(`targetValue and targetProp are not recognized types`, targetValue, targetProperty);
}
private _sceneGraphProps(object: object, propertyName: string): Object3DProps | undefined {
const elements = propertyName.split(PROPERTY_SEPARATOR);
if (elements.length > 1) {
const firstElement = elements.shift() as string;
const subObject = (object as any)[firstElement as any] as object;
if (subObject) {
const subPropertyName = elements.join(PROPERTY_SEPARATOR);
return this._sceneGraphProps(subObject, subPropertyName);
} else {
Poly.warn(`property ${firstElement} not found on object`, object);
}
} else {
const targetProperty = (object as any)[propertyName as any] as AnimPropertyTargetValue;
let toTarget: object | null = null;
const propertyNames: string[] = [];
if (isNumber(targetProperty)) {
toTarget = object;
propertyNames.push(propertyName);
} else {
toTarget = targetProperty;
if (this._targetValue instanceof Vector2) {
propertyNames.push('x', 'y');
}
if (this._targetValue instanceof Vector3) {
propertyNames.push('x', 'y', 'z');
}
if (this._targetValue instanceof Vector4) {
propertyNames.push('x', 'y', 'z', 'w');
}
if (this._targetValue instanceof Color) {
propertyNames.push('r', 'g', 'b');
}
if (this._targetValue instanceof Quaternion) {
// is_quaternion = true;
}
}
return {
targetProperty: targetProperty,
toTarget: toTarget,
propertyNames: propertyNames,
};
}
}
private _populateWithNode(node: BaseNodeType, options: AddToTimelineOptions) {
this._printDebug(['_populateWithNode', node]);
const targetParam = node.p[this._propertyName as any] as BaseParamType;
this._printDebug(['targetParam', targetParam]);
if (!targetParam) {
Poly.warn(`${this._propertyName} not found on node ${node.path()}`);
return;
}
if (targetParam) {
this._populateVarsForParam(targetParam, options);
}
}
private _populateVarsForParam(param: BaseParamType, options: AddToTimelineOptions) {
this._printDebug(['_populateVarsForParam', param]);
if (this._targetValue == null) {
return;
}
switch (param.type()) {
case ParamType.INTEGER:
case ParamType.FLOAT: {
return populateVarsForSingleNumber(param as IntegerParam, this._targetValue, options);
}
case ParamType.VECTOR2: {
return populateVarsForParamVector2(param as Vector2Param, this._targetValue, options);
}
case ParamType.VECTOR3: {
return populateVarsForParamVector3(param as Vector3Param, this._targetValue, options);
}
case ParamType.COLOR: {
return populateVarsForParamColor(param as ColorParam, this._targetValue, options);
}
case ParamType.VECTOR4: {
return populateVarsForParamVector4(param as Vector4Param, this._targetValue, options);
}
}
Poly.warn(`param type cannot be animated (yet): '${param.type()}' '${param.path()}'`);
}
}