diff --git a/e2e/case/animator-referenceAndProperty.ts b/e2e/case/animator-referenceAndProperty.ts new file mode 100644 index 0000000000..3b0c7e4727 --- /dev/null +++ b/e2e/case/animator-referenceAndProperty.ts @@ -0,0 +1,121 @@ +/** + * @title Animation Reference And Property + * @category Animation + */ + +import { + AnimationClip, + AnimationColorCurve, + AnimationFloatCurve, + AnimationRefCurve, + Animator, + AnimatorController, + AnimatorControllerLayer, + BlinnPhongMaterial, + Camera, + Color, + DirectLight, + Keyframe, + Logger, + Material, + MeshRenderer, + PBRMaterial, + PrimitiveMesh, + RenderFace, + Transform, + Vector3, + WebGLEngine +} from "@galacean/engine"; +import { OrbitControl } from "@galacean/engine-toolkit"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(2); + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + + // camera + const cameraEntity = rootEntity.createChild("camera_node"); + cameraEntity.transform.position = new Vector3(0, 1, 5); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.addComponent(OrbitControl).target = new Vector3(0, 1, 0); + + const lightNode = rootEntity.createChild("light_node"); + lightNode.addComponent(DirectLight).intensity = 0.6; + lightNode.transform.lookAt(new Vector3(0, 0, 1)); + lightNode.transform.rotate(new Vector3(0, 90, 0)); + + const material = new BlinnPhongMaterial(engine); + material.renderFace = RenderFace.Double; + material.baseColor = new Color(1, 0, 0, 1); + + const material2 = new PBRMaterial(engine); + material2.renderFace = RenderFace.Double; + material2.baseColor = new Color(0, 1, 0, 1); + + const entity = rootEntity.createChild("mesh"); + const { transform } = entity; + transform.setPosition(0, 1, 0); + transform.setRotation(0, 0, 0); + const meshRenderer = entity.addComponent(MeshRenderer); + meshRenderer.mesh = PrimitiveMesh.createCuboid(engine); + meshRenderer.setMaterial(material); + + const animator = entity.addComponent(Animator); + const controller = new AnimatorController(engine); + const layer = new AnimatorControllerLayer("base"); + controller.addLayer(layer); + const stateMachine = layer.stateMachine; + const cubeState = stateMachine.addState("material"); + const cubeClip = (cubeState.clip = new AnimationClip("material")); + + const materialCurve = new AnimationRefCurve(); + const key1 = new Keyframe(); + key1.time = 0; + key1.value = material; + const key2 = new Keyframe(); + key2.time = 1; + key2.value = material2; + const key3 = new Keyframe(); + key3.time = 2; + key3.value = material; + materialCurve.addKey(key1); + materialCurve.addKey(key2); + materialCurve.addKey(key3); + + const colorCurve = new AnimationColorCurve(); + const key6 = new Keyframe(); + key6.time = 0; + key6.value = new Color(1, 0, 0, 1); + const key7 = new Keyframe(); + key7.time = 1; + key7.value = new Color(0.5, 0.5, 0.5, 1); + const key8 = new Keyframe(); + key8.time = 2; + key8.value = new Color(1, 0, 0, 1); + colorCurve.addKey(key6); + colorCurve.addKey(key7); + colorCurve.addKey(key8); + + const rotateCurve = new AnimationFloatCurve(); + const key9 = new Keyframe(); + key9.time = 0; + key9.value = 0; + const key10 = new Keyframe(); + key10.time = 2; + key10.value = 360; + rotateCurve.addKey(key9); + rotateCurve.addKey(key10); + + cubeClip.addCurveBinding("", MeshRenderer, "getMaterial().baseColor", colorCurve); + cubeClip.addCurveBinding("", MeshRenderer, "setMaterial($value)", "getMaterial()", materialCurve); + cubeClip.addCurveBinding("", Transform, "rotation.y", rotateCurve); + animator.animatorController = controller; + animator.play("material"); + animator.speed = 0.5; + + updateForE2E(engine, 130); + + initScreenshot(engine, camera); +}); diff --git a/e2e/config.ts b/e2e/config.ts index 67a1095ec3..5f1c373d0b 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -69,6 +69,11 @@ export const E2E_CONFIG = { category: "Animator", caseFileName: "animator-stateMachine", threshold: 0.1 + }, + referenceAndProperty: { + category: "Animator", + caseFileName: "animator-referenceAndProperty", + threshold: 0.1 } }, GLTF: { diff --git a/e2e/fixtures/originImage/Animator_animator-referenceAndProperty.jpg b/e2e/fixtures/originImage/Animator_animator-referenceAndProperty.jpg new file mode 100644 index 0000000000..84bc523d6b --- /dev/null +++ b/e2e/fixtures/originImage/Animator_animator-referenceAndProperty.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d8ff1307881657731eb7e0abd6f1e721f3086b27480c9dfb7145cf4b1f4cfb5 +size 20528 diff --git a/packages/core/src/animation/AnimationClip.ts b/packages/core/src/animation/AnimationClip.ts index 79982db526..fb44b02dc1 100644 --- a/packages/core/src/animation/AnimationClip.ts +++ b/packages/core/src/animation/AnimationClip.ts @@ -6,12 +6,15 @@ import { AnimationClipCurveBinding } from "./AnimationClipCurveBinding"; import { AnimationCurve } from "./animationCurve/AnimationCurve"; import { AnimationEvent } from "./AnimationEvent"; import { AnimationCurveOwner } from "./internal/animationCurveOwner/AnimationCurveOwner"; +import { AnimationPropertyReferenceManager } from "./internal/AnimationPropertyReferenceManager"; import { KeyframeValueType } from "./Keyframe"; /** * Stores keyframe based animations. */ export class AnimationClip extends EngineObject { + private static _tempReferenceManager: AnimationPropertyReferenceManager; + /** @internal */ _curveBindings: AnimationClipCurveBinding[] = []; @@ -193,7 +196,20 @@ export class AnimationClip extends EngineObject { } this._length = Math.max(this._length, curveBinding.curve.length); - this._curveBindings.push(curveBinding); + + const curveBindings = this._curveBindings; + const count = curveBindings.length; + const propertyPathLength = curveBinding.property.split(".").length; + curveBinding._pathLength = propertyPathLength; + + const maxPropertyPathLength = count ? curveBindings[count - 1]._pathLength : 0; + if (propertyPathLength > maxPropertyPathLength) { + curveBindings.push(curveBinding); + } else { + let index = count; + while (--index >= 0 && propertyPathLength < curveBindings[index]._pathLength); + curveBindings.splice(index + 1, 0, curveBinding); + } } /** @@ -211,6 +227,12 @@ export class AnimationClip extends EngineObject { * @param time - The time to sample an animation */ _sampleAnimation(entity: Entity, time: number): void { + if (!AnimationClip._tempReferenceManager) { + AnimationClip._tempReferenceManager = new AnimationPropertyReferenceManager(); + } + + AnimationClip._tempReferenceManager.clear(); + const { _curveBindings: curveBindings } = this; for (let i = curveBindings.length - 1; i >= 0; i--) { const curve = curveBindings[i]; @@ -223,7 +245,7 @@ export class AnimationClip extends EngineObject { if (!component) { continue; } - const curveOwner = curve._getTempCurveOwner(targetEntity, component); + const curveOwner = curve._getTempCurveOwner(AnimationClip._tempReferenceManager, targetEntity, component); if (curveOwner && curve.curve.keys.length) { const value = curveOwner.evaluateValue(curve.curve, time, false); curveOwner.applyValue(value, 1, false); diff --git a/packages/core/src/animation/AnimationClipCurveBinding.ts b/packages/core/src/animation/AnimationClipCurveBinding.ts index dff0241c95..a8e03ab284 100644 --- a/packages/core/src/animation/AnimationClipCurveBinding.ts +++ b/packages/core/src/animation/AnimationClipCurveBinding.ts @@ -4,6 +4,7 @@ import { KeyframeValueType } from "./Keyframe"; import { AnimationCurve } from "./animationCurve"; import { IAnimationCurveCalculator } from "./animationCurve/interfaces/IAnimationCurveCalculator"; import { AnimationCurveLayerOwner } from "./internal/AnimationCurveLayerOwner"; +import { AnimationPropertyReferenceManager } from "./internal/AnimationPropertyReferenceManager"; import { AnimationCurveOwner } from "./internal/animationCurveOwner/AnimationCurveOwner"; /** @@ -33,14 +34,28 @@ export class AnimationClipCurveBinding { /** The animation curve. */ curve: AnimationCurve; + /** @internal */ + _pathLength: number; private _tempCurveOwner: Record> = {}; /** * @internal */ - _createCurveOwner(entity: Entity, component: Component): AnimationCurveOwner { + _createCurveOwner( + referenceManager: AnimationPropertyReferenceManager, + entity: Entity, + component: Component + ): AnimationCurveOwner { const curveType = (this.curve.constructor) as IAnimationCurveCalculator; - const owner = new AnimationCurveOwner(entity, this.type, component, this.property, this.getProperty, curveType); + const owner = new AnimationCurveOwner( + referenceManager, + entity, + this.type, + component, + this.property, + this.getProperty, + curveType + ); curveType._initializeOwner(owner); owner.saveDefaultValue(); return owner; @@ -62,10 +77,14 @@ export class AnimationClipCurveBinding { /** * @internal */ - _getTempCurveOwner(entity: Entity, component: Component): AnimationCurveOwner { + _getTempCurveOwner( + referenceManager: AnimationPropertyReferenceManager, + entity: Entity, + component: Component + ): AnimationCurveOwner { const { instanceId } = entity; if (!this._tempCurveOwner[instanceId]) { - this._tempCurveOwner[instanceId] = this._createCurveOwner(entity, component); + this._tempCurveOwner[instanceId] = this._createCurveOwner(referenceManager, entity, component); } return this._tempCurveOwner[instanceId]; } diff --git a/packages/core/src/animation/Animator.ts b/packages/core/src/animation/Animator.ts index 02cc02684f..ad1f977797 100644 --- a/packages/core/src/animation/Animator.ts +++ b/packages/core/src/animation/Animator.ts @@ -25,6 +25,8 @@ import { AnimatorLayerData } from "./internal/AnimatorLayerData"; import { AnimatorStateData } from "./internal/AnimatorStateData"; import { AnimatorStatePlayData } from "./internal/AnimatorStatePlayData"; import { AnimationCurveOwner } from "./internal/animationCurveOwner/AnimationCurveOwner"; +import { AnimationClipCurveBinding } from "./AnimationClipCurveBinding"; +import { AnimationPropertyReferenceManager } from "./internal/AnimationPropertyReferenceManager"; /** * The controller of the animation system. @@ -58,12 +60,12 @@ export class Animator extends Component { private _animationEventHandlerPool = new ClearableObjectPool(AnimationEventHandler); @ignoreClone private _parametersValueMap = >Object.create(null); - @ignoreClone private _tempAnimatorStateInfo: IAnimatorStateInfo = { layerIndex: -1, state: null }; - @ignoreClone private _controlledRenderers = new Array(); + @ignoreClone + private _animationPropertyReferenceManager = new AnimationPropertyReferenceManager(); /** * All layers from the AnimatorController which belongs this Animator. @@ -319,6 +321,7 @@ export class Animator extends Component { this._curveOwnerPool = Object.create(null); this._parametersValueMap = Object.create(null); this._animationEventHandlerPool.clear(); + this._animationPropertyReferenceManager.clear(); if (this._controllerUpdateFlag) { this._controllerUpdateFlag.flag = false; @@ -397,37 +400,41 @@ export class Animator extends Component { const { entity, _curveOwnerPool: curveOwnerPool } = this; let { mask } = this._animatorController.layers[layerIndex]; const { curveLayerOwner } = animatorStateData; - const { _curveBindings: curves } = animatorState.clip; - + const { _curveBindings: curveBindings } = animatorState.clip; const { curveOwnerPool: layerCurveOwnerPool } = animatorLayerData; - for (let i = curves.length - 1; i >= 0; i--) { - const curve = curves[i]; - const { relativePath } = curve; - const targetEntity = curve.relativePath === "" ? entity : entity.findByPath(curve.relativePath); + for (let i = 0, n = curveBindings.length; i < n; i++) { + const curveBinding = curveBindings[i]; + const { relativePath } = curveBinding; + const targetEntity = curveBinding.relativePath === "" ? entity : entity.findByPath(curveBinding.relativePath); + if (targetEntity) { const component = - curve.typeIndex > 0 - ? targetEntity.getComponents(curve.type, AnimationCurveOwner._components)[curve.typeIndex] - : targetEntity.getComponent(curve.type); + curveBinding.typeIndex > 0 + ? targetEntity.getComponents(curveBinding.type, AnimationCurveOwner._components)[curveBinding.typeIndex] + : targetEntity.getComponent(curveBinding.type); if (!component) { continue; } - const { property } = curve; + const { property } = curveBinding; const { instanceId } = component; // Get owner const propertyOwners = (curveOwnerPool[instanceId] ||= >>( Object.create(null) )); - const owner = (propertyOwners[property] ||= curve._createCurveOwner(targetEntity, component)); + const owner = (propertyOwners[property] ||= curveBinding._createCurveOwner( + this._animationPropertyReferenceManager, + targetEntity, + component + )); // Get layer owner const layerPropertyOwners = (layerCurveOwnerPool[instanceId] ||= >( Object.create(null) )); - const layerOwner = (layerPropertyOwners[property] ||= curve._createCurveLayerOwner(owner)); + const layerOwner = (layerPropertyOwners[property] ||= curveBinding._createCurveLayerOwner(owner)); if (mask && mask.pathMasks.length) { layerOwner.isActive = mask.getPathMask(relativePath)?.active ?? true; @@ -436,7 +443,7 @@ export class Animator extends Component { curveLayerOwner[i] = layerOwner; } else { curveLayerOwner[i] = null; - Logger.warn(`The entity don\'t have the child entity which path is ${curve.relativePath}.`); + Logger.warn(`The entity don\'t have the child entity which path is ${relativePath}.`); } } } @@ -505,7 +512,7 @@ export class Animator extends Component { const { crossLayerOwnerCollection } = animatorLayerData; // Save current cross curve data owner fixed pose - for (let i = crossLayerOwnerCollection.length - 1; i >= 0; i--) { + for (let i = 0, n = crossLayerOwnerCollection.length; i < n; i++) { const layerOwner = crossLayerOwnerCollection[i]; if (!layerOwner) continue; layerOwner.curveOwner.saveFixedPoseValue(); @@ -518,7 +525,7 @@ export class Animator extends Component { private _prepareSrcCrossData(animatorLayerData: AnimatorLayerData, saveFixed: boolean): void { const { curveLayerOwner } = animatorLayerData.srcPlayData.stateData; - for (let i = curveLayerOwner.length - 1; i >= 0; i--) { + for (let i = 0, n = curveLayerOwner.length; i < n; i++) { const layerOwner = curveLayerOwner[i]; if (!layerOwner) continue; layerOwner.crossCurveMark = animatorLayerData.crossCurveMark; @@ -529,7 +536,7 @@ export class Animator extends Component { private _prepareDestCrossData(animatorLayerData: AnimatorLayerData, saveFixed: boolean): void { const { curveLayerOwner } = animatorLayerData.destPlayData.stateData; - for (let i = curveLayerOwner.length - 1; i >= 0; i--) { + for (let i = 0, n = curveLayerOwner.length; i < n; i++) { const layerOwner = curveLayerOwner[i]; if (!layerOwner) continue; if (layerOwner.crossCurveMark === animatorLayerData.crossCurveMark) { @@ -692,7 +699,7 @@ export class Animator extends Component { if (aniUpdate || finished) { const curveLayerOwner = playData.stateData.curveLayerOwner; - for (let i = curveBindings.length - 1; i >= 0; i--) { + for (let i = 0, n = curveBindings.length; i < n; i++) { const layerOwner = curveLayerOwner[i]; const owner = layerOwner?.curveOwner; @@ -817,7 +824,7 @@ export class Animator extends Component { const finished = destPlayData.playState === AnimatorStatePlayState.Finished; if (aniUpdate || finished) { - for (let i = crossLayerOwnerCollection.length - 1; i >= 0; i--) { + for (let i = 0, n = crossLayerOwnerCollection.length; i < n; i++) { const layerOwner = crossLayerOwnerCollection[i]; const owner = layerOwner?.curveOwner; @@ -929,7 +936,7 @@ export class Animator extends Component { // When the animator is culled (aniUpdate=false), if the play state has finished, the final value needs to be calculated and saved to be applied directly if (aniUpdate || finished) { - for (let i = crossLayerOwnerCollection.length - 1; i >= 0; i--) { + for (let i = 0, n = crossLayerOwnerCollection.length; i < n; i++) { const layerOwner = crossLayerOwnerCollection[i]; const owner = layerOwner?.curveOwner; @@ -1009,7 +1016,7 @@ export class Animator extends Component { const { curveLayerOwner } = playData.stateData; const { _curveBindings: curveBindings } = playData.state.clip; - for (let i = curveBindings.length - 1; i >= 0; i--) { + for (let i = 0, n = curveBindings.length; i < n; i++) { const layerOwner = curveLayerOwner[i]; const owner = layerOwner?.curveOwner; @@ -1037,13 +1044,13 @@ export class Animator extends Component { const srcPlayData = layerData.srcPlayData; if (srcPlayData.state !== playState) { const { curveLayerOwner } = srcPlayData.stateData; - for (let i = curveLayerOwner.length - 1; i >= 0; i--) { + for (let i = 0, n = curveLayerOwner.length; i < n; i++) { curveLayerOwner[i]?.curveOwner.revertDefaultValue(); } } } else { const { crossLayerOwnerCollection } = layerData; - for (let i = crossLayerOwnerCollection.length - 1; i >= 0; i--) { + for (let i = 0, n = crossLayerOwnerCollection.length; i < n; i++) { crossLayerOwnerCollection[i].curveOwner.revertDefaultValue(); } } diff --git a/packages/core/src/animation/animationCurve/AnimationFloatArrayCurve.ts b/packages/core/src/animation/animationCurve/AnimationFloatArrayCurve.ts index 3360eeb00d..3100532bab 100644 --- a/packages/core/src/animation/animationCurve/AnimationFloatArrayCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationFloatArrayCurve.ts @@ -19,7 +19,7 @@ export class AnimationFloatArrayCurve extends AnimationCurve { * @internal */ static _initializeOwner(owner: AnimationCurveOwner): void { - const size = owner.referenceTargetValue.length; + const size = owner._assembler.getTargetValue().length; owner.defaultValue = new Float32Array(size); owner.fixedPoseValue = new Float32Array(size); owner.baseEvaluateData.value = new Float32Array(size); @@ -30,7 +30,7 @@ export class AnimationFloatArrayCurve extends AnimationCurve { * @internal */ static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { - const size = (owner.curveOwner.referenceTargetValue).length; + const size = (owner.curveOwner._assembler.getTargetValue()).length; owner.finalValue = new Float32Array(size); } diff --git a/packages/core/src/animation/internal/AnimationPropertyReference.ts b/packages/core/src/animation/internal/AnimationPropertyReference.ts new file mode 100644 index 0000000000..6947203734 --- /dev/null +++ b/packages/core/src/animation/internal/AnimationPropertyReference.ts @@ -0,0 +1,177 @@ +import { AnimationPropertyReferenceManager } from "./AnimationPropertyReferenceManager"; + +/** + * @internal + */ +export abstract class AnimationPropertyReference { + manager: AnimationPropertyReferenceManager; + parseFlag: MountedParseFlag; + invDependencies = new Array(); + value: any; + index: number; + + private _parent: AnimationPropertyReference; + private _dirty = true; + + get dirty(): boolean { + return this._dirty; + } + + set dirty(value: boolean) { + this._dirty = value; + if (value) { + for (let i = 0; i < this.invDependencies.length; i++) { + const index = this.invDependencies[i]; + const reference = this.manager.animationPropertyReferences[index]; + reference.dirty = true; + } + } + } + + get parent(): AnimationPropertyReference { + return this._parent; + } + + set parent(dependence: AnimationPropertyReference) { + this._parent = dependence; + dependence.invDependencies.push(this.index); + } + + abstract getValue(): any; + abstract setValue(value: any): void; +} + +/** + * @internal + */ +export class ComponentReference extends AnimationPropertyReference { + getValue() { + return this.value; + } + + setValue(value: any) { + this.value = value; + } +} + +/** + * @internal + */ +export class PropertyReference extends AnimationPropertyReference { + property: string; + + constructor(propertyStr: string) { + super(); + this.property = propertyStr; + } + + getValue() { + const dependence = this.parent; + + if (dependence.dirty) { + dependence.getValue(); + } + + if (this.dirty) { + this.value = dependence.value[this.property]; + this.dirty = false; + } + return this.value; + } + + setValue(value: any) { + const dependence = this.parent; + if (dependence.dirty) { + dependence.getValue(); + } + + this.value = dependence.value[this.property] = value; + this.dirty = true; + } +} + +/** + * @internal + */ +export class MethodReference extends AnimationPropertyReference { + methodName: string; + args: any[]; + replaceValueIndex: number; + + constructor(propertyStr: string) { + super(); + this.methodName = propertyStr.slice(0, propertyStr.indexOf("(")); + this.args = propertyStr + .match(/\w+\(([^)]*)\)/)[1] + .split(",") + .map((arg) => arg.trim().replace(/['"]+/g, "")) + .filter((arg) => arg !== ""); + this.replaceValueIndex = this.args.indexOf("$value"); + } + + getValue() { + const dependence = this.parent; + if (dependence.dirty) { + dependence.getValue(); + } + this.value = dependence.value[this.methodName].apply(dependence.value, this.args); + this.dirty = false; + return this.value; + } + + setValue(value: any) { + const dependence = this.parent; + if (dependence.dirty) { + dependence.getValue(); + } + const args = this.args; + if (this.replaceValueIndex >= 0) { + args[this.replaceValueIndex] = value; + } + dependence.value[this.methodName].apply(dependence.value, args); + this.dirty = true; + } +} + +/** + * @internal + */ +export class ArrayReference extends AnimationPropertyReference { + property: string; + arrayIndex: number; + + constructor(propertyStr: string) { + super(); + const indexPos = propertyStr.indexOf("["); + this.property = propertyStr.slice(0, indexPos); + this.arrayIndex = parseInt(propertyStr.slice(indexPos + 1, -1)); + } + + getValue() { + const dependence = this.parent; + if (dependence.dirty) { + dependence.getValue(); + } + this.value = dependence.value[this.property][this.arrayIndex]; + this.dirty = false; + return this.value; + } + + setValue(value: any) { + const dependence = this.parent; + if (dependence.dirty) { + dependence.getValue(); + } + dependence.value[this.property][this.arrayIndex] = value; + this.dirty = true; + } +} + +/** + * @internal + */ +export enum MountedParseFlag { + Get = 0x1, + Set = 0x2, + Both = 0x3 +} diff --git a/packages/core/src/animation/internal/AnimationPropertyReferenceManager.ts b/packages/core/src/animation/internal/AnimationPropertyReferenceManager.ts new file mode 100644 index 0000000000..64c47ced98 --- /dev/null +++ b/packages/core/src/animation/internal/AnimationPropertyReferenceManager.ts @@ -0,0 +1,83 @@ +import { Component } from "../../Component"; +import { + AnimationPropertyReference, + ArrayReference, + ComponentReference, + MethodReference, + MountedParseFlag, + PropertyReference +} from "./AnimationPropertyReference"; + +/** + * @internal + */ +export class AnimationPropertyReferenceManager { + animationPropertyReferences: AnimationPropertyReference[] = []; + + private _referenceIndexMap: Map = new Map(); + private _subProperties = new Array(); + + addReference(component: Component, propertyStr: string, parseFlag: MountedParseFlag): AnimationPropertyReference { + const instanceId = component.instanceId; + + let reference: AnimationPropertyReference; + + const existedReference = this._referenceIndexMap.get(instanceId); + if (existedReference) { + reference = existedReference; + } else { + reference = new ComponentReference(); + reference.manager = this; + reference.value = component; + this._referenceIndexMap.set(instanceId, reference); + } + + const properties = propertyStr.split("."); + const endIndex = properties.length - 1; + + const subProperties = this._subProperties; + subProperties.length = 0; + + for (let i = 0; i <= endIndex; i++) { + const property = properties[i]; + subProperties.push(property); + const uniqueKey = `${instanceId}-${subProperties.join(".")}`; + const existReference = this._referenceIndexMap.get(uniqueKey); + if (existReference) { + reference = existReference; + continue; + } + + const parent = reference; + + if (property.indexOf("[") > -1) { + // is array + reference = new ArrayReference(property); + } else if (property.endsWith(")")) { + // is method + reference = new MethodReference(property); + } else { + // is property + reference = new PropertyReference(property); + } + + reference.manager = this; + reference.index = this.animationPropertyReferences.length; + reference.parent = parent; + + // Get the value once when initializing to improve runtime performance + if (parseFlag & MountedParseFlag.Get) { + reference.getValue(); + } + this._referenceIndexMap.set(uniqueKey, reference); + this.animationPropertyReferences.push(reference); + } + + return reference; + } + + clear() { + this.animationPropertyReferences.length = 0; + this._referenceIndexMap.clear(); + } +} diff --git a/packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts b/packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts index 4bd51e0424..8f630f1c8a 100644 --- a/packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts +++ b/packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts @@ -3,6 +3,7 @@ import { Entity } from "../../../Entity"; import { AnimationCurve } from "../../animationCurve/AnimationCurve"; import { IAnimationCurveCalculator } from "../../animationCurve/interfaces/IAnimationCurveCalculator"; import { KeyframeValueType } from "../../Keyframe"; +import { AnimationPropertyReferenceManager } from "../AnimationPropertyReferenceManager"; import { IAnimationCurveOwnerAssembler } from "./assembler/IAnimationCurveOwnerAssembler"; import { UniversalAnimationCurveOwnerAssembler } from "./assembler/UniversalAnimationCurveOwnerAssembler"; @@ -34,18 +35,20 @@ export class AnimationCurveOwner { readonly property: string; readonly getProperty?: string; readonly component: Component; + readonly referenceManager: AnimationPropertyReferenceManager; defaultValue: V; fixedPoseValue: V; baseEvaluateData: IEvaluateData = { curKeyframeIndex: 0, value: null }; crossEvaluateData: IEvaluateData = { curKeyframeIndex: 0, value: null }; - referenceTargetValue: V; cureType: IAnimationCurveCalculator; updateMark: number = 0; - private _assembler: IAnimationCurveOwnerAssembler; + /** @internal */ + _assembler: IAnimationCurveOwnerAssembler; constructor( + referenceManager: AnimationPropertyReferenceManager, target: Entity, type: new (entity: Entity) => Component, component: Component, @@ -53,6 +56,7 @@ export class AnimationCurveOwner { getProperty: string, cureType: IAnimationCurveCalculator ) { + this.referenceManager = referenceManager; this.target = target; this.property = property; this.getProperty = getProperty; @@ -62,10 +66,6 @@ export class AnimationCurveOwner { const assemblerType = AnimationCurveOwner.getAssemblerType(type, property); this._assembler = >new assemblerType(); this._assembler.initialize(this); - - if (cureType._isCopyMode) { - this.referenceTargetValue = this._assembler.getTargetValue(); - } } evaluateValue(curve: AnimationCurve, time: number, additive: boolean): KeyframeValueType { @@ -147,7 +147,7 @@ export class AnimationCurveOwner { saveDefaultValue(): void { if (this.cureType._isCopyMode) { - this.cureType._setValue(this.referenceTargetValue, this.defaultValue); + this.cureType._setValue(this._assembler.getTargetValue(), this.defaultValue); } else { this.defaultValue = this._assembler.getTargetValue(); } @@ -155,7 +155,7 @@ export class AnimationCurveOwner { saveFixedPoseValue(): void { if (this.cureType._isCopyMode) { - this.cureType._setValue(this.referenceTargetValue, this.fixedPoseValue); + this.cureType._setValue(this._assembler.getTargetValue(), this.fixedPoseValue); } else { this.fixedPoseValue = this._assembler.getTargetValue(); } @@ -163,12 +163,13 @@ export class AnimationCurveOwner { applyValue(value: V, weight: number, additive: boolean): void { const cureType = this.cureType; + const referenceTargetValue = cureType._isCopyMode ? this._assembler.getTargetValue() : null; if (additive) { const assembler = this._assembler; if (cureType._isCopyMode) { - cureType._additiveValue(value, weight, this.referenceTargetValue); + cureType._additiveValue(value, weight, referenceTargetValue); } else { const originValue = assembler.getTargetValue(); const additiveValue = cureType._additiveValue(value, weight, originValue); @@ -177,14 +178,13 @@ export class AnimationCurveOwner { } else { if (weight === 1.0) { if (cureType._isCopyMode) { - cureType._setValue(value, this.referenceTargetValue); + cureType._setValue(value, referenceTargetValue); } else { this._assembler.setTargetValue(value); } } else { if (cureType._isCopyMode) { - const targetValue = this.referenceTargetValue; - cureType._lerpValue(targetValue, value, weight, targetValue); + cureType._lerpValue(referenceTargetValue, value, weight, referenceTargetValue); } else { const originValue = this._assembler.getTargetValue(); const lerpValue = cureType._lerpValue(originValue, value, weight); diff --git a/packages/core/src/animation/internal/animationCurveOwner/assembler/UniversalAnimationCurveOwnerAssembler.ts b/packages/core/src/animation/internal/animationCurveOwner/assembler/UniversalAnimationCurveOwnerAssembler.ts index 3893a61b58..71b2393fa9 100644 --- a/packages/core/src/animation/internal/animationCurveOwner/assembler/UniversalAnimationCurveOwnerAssembler.ts +++ b/packages/core/src/animation/internal/animationCurveOwner/assembler/UniversalAnimationCurveOwnerAssembler.ts @@ -1,4 +1,6 @@ import { KeyframeValueType } from "../../../Keyframe"; +import { AnimationPropertyReference, MountedParseFlag } from "../../AnimationPropertyReference"; +import { AnimationPropertyReferenceManager } from "../../AnimationPropertyReferenceManager"; import { AnimationCurveOwner } from "../AnimationCurveOwner"; import { IAnimationCurveOwnerAssembler } from "./IAnimationCurveOwnerAssembler"; @@ -6,142 +8,30 @@ import { IAnimationCurveOwnerAssembler } from "./IAnimationCurveOwnerAssembler"; * @internal */ export class UniversalAnimationCurveOwnerAssembler implements IAnimationCurveOwnerAssembler { - private _getMounted: Record; - private _setMounted: Record; - - private _getType: HandleType; - private _setType: HandleType; - - private _getValueName: string; - private _setValueName: string; - - private _getArgs: any[]; - private _setArgs: any[]; - private _replaceValueIndex: number; - - private _getArrayIndex: number; - private _setArrayIndex: number; + private _getReference: AnimationPropertyReference; + private _setReference: AnimationPropertyReference; initialize(owner: AnimationCurveOwner): void { - let mounted = owner.component; - - const setProperties = owner.property.split("."); + const { referenceManager } = owner; if (owner.getProperty) { - const getProperties = owner.getProperty.split("."); - this._initializeMounted(mounted, getProperties, MountedParseFlag.Get); - this._initializeMounted(mounted, setProperties, MountedParseFlag.Set); + this._getReference = referenceManager.addReference(owner.component, owner.getProperty, MountedParseFlag.Get); + this._setReference = referenceManager.addReference(owner.component, owner.property, MountedParseFlag.Set); + this._setReference.invDependencies.push(this._getReference.index); } else { - this._initializeMounted(mounted, setProperties, MountedParseFlag.Both); + this._getReference = this._setReference = referenceManager.addReference( + owner.component, + owner.property, + MountedParseFlag.Both + ); } } getTargetValue(): KeyframeValueType { - switch (this._getType) { - case HandleType.Array: - return this._getMounted[this._getArrayIndex] as KeyframeValueType; - case HandleType.Method: - return (this._getMounted[this._getValueName] as Function).apply(this._getMounted, this._getArgs); - case HandleType.Property: - return this._getMounted[this._getValueName] as KeyframeValueType; - } + return this._getReference.getValue(); } setTargetValue(value: KeyframeValueType): void { - switch (this._setType) { - case HandleType.Array: - this._setMounted[this._setArrayIndex] = value; - break; - case HandleType.Method: - const args = this._setArgs; - args[this._replaceValueIndex] = value; - (this._setMounted[this._setValueName] as Function).apply(this._setMounted, args); - break; - case HandleType.Property: - this._setMounted[this._setValueName] = value; - break; - } - } - - private _initializeMounted(mounted: any, properties: string[], parseFlag: MountedParseFlag): void { - const endIndex = properties.length - 1; - for (let i = 0; i < endIndex; i++) { - const property = properties[i]; - if (property.indexOf("[") > -1) { - // is array - const indexPos = property.indexOf("["); - mounted = mounted[property.slice(0, indexPos)]; - mounted = mounted[parseInt(property.slice(indexPos + 1, -1))]; - } else if (property.endsWith(")")) { - // is method - const methodName = property.slice(0, property.indexOf("(")); - const args = property - .match(/\w+\(([^)]*)\)/)[1] - .split(",") - .map((arg) => arg.trim().replace(/['"]+/g, "")) - .filter((arg) => arg !== ""); - mounted = mounted[methodName].apply(mounted, args); - } else { - // is property - mounted = mounted[property]; - } - } - - const property = properties[endIndex]; - - let handleType: HandleType; - let arrayIndex: number; - let methodName: string; - let args: any[]; - - if (property.indexOf("[") > -1) { - const indexPos = property.indexOf("["); - handleType = HandleType.Array; - mounted = mounted[property.slice(0, indexPos)]; - arrayIndex = parseInt(property.slice(indexPos + 1, -1)); - } else if (property.endsWith(")")) { - methodName = property.slice(0, property.indexOf("(")); - args = property - .match(/\w+\(([^)]*)\)/)[1] - .split(",") - .map((arg) => arg.trim().replace(/['"]+/g, "")) - .filter((arg) => arg !== ""); - handleType = HandleType.Method; - if (parseFlag & MountedParseFlag.Set) { - const index = args.indexOf("$value"); - this._replaceValueIndex = index > -1 ? index : args.length; - } - } else { - handleType = HandleType.Property; - } - - if (parseFlag & MountedParseFlag.Set) { - this._setMounted = mounted; - this._setType = handleType; - this._setArrayIndex = arrayIndex; - this._setValueName = property; - methodName && (this._setValueName = methodName); - this._setArgs = args; - } - if (parseFlag & MountedParseFlag.Get) { - this._getMounted = mounted; - this._getType = handleType; - this._getArrayIndex = arrayIndex; - this._getValueName = property; - methodName && (this._getValueName = methodName); - this._getArgs = args; - } + this._setReference.setValue(value); } } - -enum HandleType { - Property, - Method, - Array -} - -enum MountedParseFlag { - Get = 0x1, - Set = 0x2, - Both = 0x3 -} diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts index 9e9533915e..3f6d9f365e 100644 --- a/tests/src/core/Animator.test.ts +++ b/tests/src/core/Animator.test.ts @@ -137,7 +137,7 @@ describe("Animator test", function () { animator.update(5); const curveOwner = srcPlayData.stateData.curveLayerOwner[0].curveOwner; const initValue = curveOwner.defaultValue; - const currentValue = curveOwner.referenceTargetValue; + const currentValue = curveOwner._assembler.getTargetValue(); expect(Quaternion.equals(initValue, currentValue)).to.eq(true); animator.cullingMode = 0;