Skip to content

Commit

Permalink
AnimationCurve (cocos#8409)
Browse files Browse the repository at this point in the history
* AnimationCurve

* Mark curve frame values as uniquelyReferenced

* Adjust modules

* Update

* Manually implements KeyframeCurve.iterator

* TargetPath

* Update

* Fix BUG

* Fix

* Add size track

* Update

* Fix size; unit tests

* fix split animation (#24)

* CCONB animation clip; Remove integer track/channel/curve

* Optimize key shared curves

* Remove keyframecurve.empty

* Exotic animation

* Fix unit tests

* start/end -> left/right

* Curve range rename

* Easing methods

* Fix untyped track

* Fix

* fix upgradUnTypedTracks (#26)

* Rename

.interpMode -> .interpolationMode
RealInterpMode -> RealInterpolationMode
QuaternionInterpMode -> QuaternionInterpolationMode
ExtrapMode -> ExtrapolationMode
.preExtrap -> .preExtrapolation
.postExtrap -> .postExtrapolation
QuaternionCurve -> QuatCurve
QuaternionKeyframeValue -> QuatKeyframeValue

* Update pre/post extrap

* Optimize-1

* Optimize-2

* Eliminate for-of

* Mark deprecateds

* Hide RealKeyframeValue/QuatKeyframeValue Constructor

* Fix quat keyframe value name

* Error ID

* Fix RealKeyframeValue editor extras

* Elimanate for-of forEach

Co-authored-by: zheng han <sunstar198941@gmail.com>
  • Loading branch information
shrinktofit and gameall3d committed Jul 23, 2021
1 parent eb06667 commit 6d10f68
Show file tree
Hide file tree
Showing 61 changed files with 7,644 additions and 1,236 deletions.
2 changes: 2 additions & 0 deletions .vscode/cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
"Chukong",
"clampf",
"COCOSPLAY",
"coeff",
"deinterleave",
"deserialization",
"deserialize",
"deserializes",
"earcut",
"emscripten",
"endregion",
"eventify",
"eventified",
"forin",
Expand Down
64 changes: 64 additions & 0 deletions EngineErrorMap.md
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,70 @@ animation not added or already removed

already-playing

### 3920

Current context does not allow root motion.

### 3921

You provided a ill-formed track path. The last component of track path should be property key, or the setter should not be empty.

### 3922

Seems like we have animation for %s but are missing its parent joint %s in animation?

### 3923

Root motion is ignored since root bone could not be located in animation.

### 3924

Root motion is ignored since the root bone could not be located in scene.

### 3925

Target of hierarchy path should be of type Node.

### 3926

Node "%s" has no path "%s".

### 3927

Target of component path should be of type Node.

### 3928

Node "%s" has no component "%s".

### 3929

Target object has no property "%s".

### 3930

Can not decide type for untyped track: runtime binding does not provide a getter.

### 3931

Can not decide type for untyped track: got a unsupported value from runtime binding.

### 3932

Common targets should only target Vectors/`Size`/`Color`.

### 3933

Each curve that has common target should be numeric curve and targets string property.

### 3934

Misconfigured legacy curve: the first keyframe value is number but others aren't.

### 3935

We don't currently support conversion of \`CubicSplineQuatValue\`.

### 4000

<!-- DEPRECATED -->
Expand Down
9 changes: 4 additions & 5 deletions cocos/3d/skeletal-animation/skeletal-animation-blending.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@

import { Vec3, Quat } from '../../core/math';
import { Node } from '../../core/scene-graph';
import { AnimationState } from '../../core/animation/animation-state';
import { IBoundTarget } from '../../core/animation/bound-target';
import { RuntimeBinding } from '../../core/animation/tracks/track';

export class BlendStateBuffer {
private _nodeBlendStates: Map<Node, NodeBlendState> = new Map();
Expand All @@ -41,7 +40,7 @@ export class BlendStateBuffer {
property: P,
host: BlendStateWriterHost,
constants: boolean,
): Omit<BlendStateWriterInternal<P>, 'node' | 'property'> {
): BlendStateWriter<P> {
const propertyBlendState = this.ref(node, property);
return new BlendStateWriterInternal<P>(
node,
Expand Down Expand Up @@ -89,7 +88,7 @@ export interface BlendStateWriterHost {
readonly weight: number;
}

class BlendStateWriterInternal<P extends BlendingProperty> implements IBoundTarget {
class BlendStateWriterInternal<P extends BlendingProperty> implements RuntimeBinding {
constructor (
private _node: Node,
private _property: P,
Expand Down Expand Up @@ -124,7 +123,7 @@ class BlendStateWriterInternal<P extends BlendingProperty> implements IBoundTarg

export type BlendStateWriter<P extends BlendingProperty> = Omit<BlendStateWriterInternal<P>, 'node' | 'property'>;

type BlendingProperty = keyof NodeBlendState['_properties'];
export type BlendingProperty = keyof NodeBlendState['_properties'];

type BlendingPropertyValue<P extends BlendingProperty> = NonNullable<NodeBlendState['_properties'][P]>['blendedValue'];

Expand Down
128 changes: 9 additions & 119 deletions cocos/3d/skeletal-animation/skeletal-animation-data-hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,41 +27,25 @@
* @packageDocumentation
* @module animation
*/

import {
clamp01, Mat4, Quat, Vec3,
} from '../../core/math';
import { DataPoolManager } from './data-pool-manager';
import { AnimationClip, IObjectCurveData } from '../../core/animation/animation-clip';
import { HierarchyPath, isCustomPath, isPropertyPath } from '../../core/animation/target-path';
import type { AnimationClip } from '../../core/animation/animation-clip';
import { legacyCC } from '../../core/global-exports';
import { BAKE_SKELETON_CURVE_SYMBOL } from '../../core/animation/internal-symbols';

type CurveData = Vec3[] | Quat[] | Mat4[];
type ConvertedProps = Record<string, IPropertyCurve>;

interface IPropertyCurve {
keys: number;
values: CurveData;
}
interface ISkeletalCurveInfo {
frames: number;
sample: number;
}
interface IConvertedData {
info: ISkeletalCurveInfo;
data: Record<string, ConvertedProps>;
}
type BakeData = ReturnType<AnimationClip[typeof BAKE_SKELETON_CURVE_SYMBOL]>;

/**
* 骨骼动画数据转换中心。
*/
export class SkelAnimDataHub {
public static getOrExtract (clip: AnimationClip) {
public static getOrExtract (clip: AnimationClip): BakeData {
let data = SkelAnimDataHub.pool.get(clip);
if (!data || data.info.sample !== clip.sample) {
if (!data || data.samples !== clip.sample) {
// release outdated render data
if (data) { (legacyCC.director.root.dataPoolManager as DataPoolManager).releaseAnimationClip(clip); }
data = convertToSkeletalCurves(clip);
const frames = Math.ceil(clip.sample * clip.duration) + 1;
const step = clip.sample;
data = clip[BAKE_SKELETON_CURVE_SYMBOL](0, step, frames);
SkelAnimDataHub.pool.set(clip, data);
}
return data;
Expand All @@ -71,99 +55,5 @@ export class SkelAnimDataHub {
SkelAnimDataHub.pool.delete(clip);
}

protected static pool = new Map<AnimationClip, IConvertedData>();
}

function convertToSkeletalCurves (clip: AnimationClip): IConvertedData {
const data: Record<string, ConvertedProps> = {};
clip.curves.forEach((curve) => {
if (!curve.valueAdapter
&& isCustomPath(curve.modifiers[0], HierarchyPath)
&& isPropertyPath(curve.modifiers[1])) {
const { path } = curve.modifiers[0];
let cs = data[path];
if (!cs) { cs = data[path] = {}; }
const property = curve.modifiers[1] as string;
cs[property] = { values: curve.data.values, keys: curve.data.keys }; // don't use curve.data directly
}
});
const frames = Math.ceil(clip.sample * clip.duration) + 1;
// lazy eval the conversion due to memory-heavy ops
// many animation paths may not be actually in-use
for (const path of Object.keys(data)) {
const props = data[path];
if (!props) { continue; }
Object.defineProperty(props, 'worldMatrix', {
get: () => {
if (!props._worldMatrix) {
const { position, rotation, scale } = props;
// fixed step pre-sample
convertToUniformSample(clip, position, frames);
convertToUniformSample(clip, rotation, frames);
convertToUniformSample(clip, scale, frames);
// transform to world space
convertToWorldSpace(data, path, props);
}
return props._worldMatrix;
},
});
}
const info: ISkeletalCurveInfo = {
frames,
sample: clip.sample,
};
return { info, data };
}

function convertToUniformSample (clip: AnimationClip, curve: IPropertyCurve, frames: number) {
const keys = clip.keys[curve.keys];
const values: CurveData = [];
if (!keys || keys.length === 1) {
for (let i = 0; i < frames; i++) {
values[i] = curve.values[0].clone(); // never forget to clone
}
} else {
const isQuat = curve.values[0] instanceof Quat;
for (let i = 0, idx = 0; i < frames; i++) {
let time = i / clip.sample;
while (keys[idx] <= time) { idx++; }
if (idx > keys.length - 1) { idx = keys.length - 1; time = keys[idx]; } else if (idx === 0) { idx = 1; }
const from = curve.values[idx - 1].clone();
const denom = keys[idx] - keys[idx - 1];
const ratio = denom ? clamp01((time - keys[idx - 1]) / denom) : 1;
if (isQuat) {
(from as Quat).slerp(curve.values[idx] as Quat, ratio);
} else {
(from as Vec3).lerp(curve.values[idx] as Vec3, ratio);
}
values[i] = from;
}
}
curve.values = values;
}

function convertToWorldSpace (convertedProps: Record<string, ConvertedProps>, path: string, props: IObjectCurveData) {
const oPos = props.position.values;
const oRot = props.rotation.values;
const oScale = props.scale.values;
const matrix = oPos.map(() => new Mat4());
const idx = path.lastIndexOf('/');
let pMatrix: Mat4[] | null = null;
if (idx > 0) {
const name = path.substring(0, idx);
const data = convertedProps[name];
if (!data) { console.warn('no data for parent bone?'); return; }
pMatrix = data.worldMatrix.values as Mat4[];
}
// all props should have the same length now
for (let i = 0; i < oPos.length; i++) {
const oT = oPos[i];
const oR = oRot[i];
const oS = oScale[i];
const m = matrix[i];
Mat4.fromRTS(m, oR, oT, oS);
if (pMatrix) { Mat4.multiply(m, pMatrix[i], m); }
}
Object.keys(props).forEach((k) => delete props[k]);
props._worldMatrix = { keys: 0, interpolate: false, values: matrix };
private static pool = new Map<AnimationClip, BakeData>();
}
24 changes: 12 additions & 12 deletions cocos/3d/skeletal-animation/skeletal-animation-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { SkinnedMeshRenderer } from '../skinned-mesh-renderer';
import { Mat4, Quat, Vec3 } from '../../core/math';
import { IAnimInfo, JointAnimationInfo } from './skeletal-animation-utils';
import { Node } from '../../core/scene-graph/node';
import { AnimationClip, IRuntimeCurve } from '../../core/animation/animation-clip';
import { AnimationClip } from '../../core/animation/animation-clip';
import { AnimationState } from '../../core/animation/animation-state';
import { SkeletalAnimation, Socket } from './skeletal-animation';
import { SkelAnimDataHub } from './skeletal-animation-data-hub';
Expand All @@ -53,8 +53,6 @@ interface ISocketData {
frames: ITransform[];
}

const noCurves: IRuntimeCurve[] = [];

export class SkeletalAnimationState extends AnimationState {
protected _frames = 1;

Expand Down Expand Up @@ -89,12 +87,13 @@ export class SkeletalAnimationState extends AnimationState {
}
this._parent = root.getComponent('cc.SkeletalAnimation') as SkeletalAnimation;
const baked = this._parent.useBakedAnimation;
super.initialize(root, baked ? noCurves : undefined);
this._doNotCreateEval = baked;
super.initialize(root);
this._curvesInited = !baked;
const { info } = SkelAnimDataHub.getOrExtract(this.clip);
this._frames = info.frames - 1;
const { frames, samples } = SkelAnimDataHub.getOrExtract(this.clip);
this._frames = frames - 1;
this._animInfo = this._animInfoMgr.getData(root.uuid);
this._bakedDuration = this._frames / info.sample; // last key
this._bakedDuration = this._frames / samples; // last key
}

public onPlay () {
Expand Down Expand Up @@ -128,13 +127,13 @@ export class SkeletalAnimationState extends AnimationState {
if (!socket.target) { continue; }
const clipData = SkelAnimDataHub.getOrExtract(this.clip);
let animPath = socket.path;
let source = clipData.data[animPath];
let source = clipData.joints[animPath];
let animNode = targetNode;
let downstream: Mat4 | undefined;
while (!source) {
const idx = animPath.lastIndexOf('/');
animPath = animPath.substring(0, idx);
source = clipData.data[animPath];
source = clipData.joints[animPath];
if (animNode) {
if (!downstream) { downstream = Mat4.identity(m4_2); }
Mat4.fromRTS(m4_1, animNode.rotation, animNode.position, animNode.scale);
Expand All @@ -143,8 +142,8 @@ export class SkeletalAnimationState extends AnimationState {
}
if (idx < 0) { break; }
}
const curveData: Mat4[] | undefined = source && source.worldMatrix.values as Mat4[];
const { frames } = clipData.info;
const curveData: Mat4[] | undefined = source && source.transforms;
const { frames } = clipData;
const transforms: ITransform[] = [];
for (let f = 0; f < frames; f++) {
let mat: Mat4;
Expand Down Expand Up @@ -176,7 +175,8 @@ export class SkeletalAnimationState extends AnimationState {
}
}

private _sampleCurvesBaked (ratio: number) {
private _sampleCurvesBaked (time: number) {
const ratio = time / this.duration;
const info = this._animInfo!;
const curFrame = (ratio * this._frames + 0.5) | 0;
if (curFrame === info.data[0]) { return; }
Expand Down
8 changes: 4 additions & 4 deletions cocos/3d/skeletal-animation/skeletal-animation-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ export class JointTexturePool {
if (texture && texture.bounds.has(mesh.hash)) { texture.refCount++; return texture; }
const { joints, bindposes } = skeleton;
const clipData = SkelAnimDataHub.getOrExtract(clip);
const { frames } = clipData.info;
const { frames } = clipData;
let textureBuffer: Float32Array = null!; let buildTexture = false;
const jointCount = joints.length;
if (!texture) {
Expand Down Expand Up @@ -394,14 +394,14 @@ export class JointTexturePool {
const clipData = SkelAnimDataHub.getOrExtract(clip);
for (let j = 0; j < jointCount; j++) {
let animPath = joints[j];
let source = clipData.data[animPath];
let source = clipData.joints[animPath];
let animNode = skinningRoot.getChildByPath(animPath);
let downstream: Mat4 | undefined;
let correctionPath: string | undefined;
while (!source) {
const idx = animPath.lastIndexOf('/');
animPath = animPath.substring(0, idx);
source = clipData.data[animPath];
source = clipData.joints[animPath];
if (animNode) {
if (!downstream) { downstream = new Mat4(); }
Mat4.fromRTS(m4_1, animNode.rotation, animNode.position, animNode.scale);
Expand Down Expand Up @@ -447,7 +447,7 @@ export class JointTexturePool {
}
}
animInfos.push({
curveData: source && source.worldMatrix.values as Mat4[], downstream, bindposeIdx, bindposeCorrection,
curveData: source && source.transforms, downstream, bindposeIdx, bindposeCorrection,
});
}
return animInfos;
Expand Down
2 changes: 1 addition & 1 deletion cocos/3d/skeletal-animation/skeletal-animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export class SkeletalAnimation extends Animation {
}

public querySockets () {
const animPaths = (this._defaultClip && Object.keys(SkelAnimDataHub.getOrExtract(this._defaultClip).data).sort()
const animPaths = (this._defaultClip && Object.keys(SkelAnimDataHub.getOrExtract(this._defaultClip).joints).sort()
.reduce((acc, cur) => (cur.startsWith(acc[acc.length - 1]) ? acc : (acc.push(cur), acc)), [] as string[])) || [];
if (!animPaths.length) { return ['please specify a valid default animation clip first']; }
const out: string[] = [];
Expand Down
Loading

0 comments on commit 6d10f68

Please sign in to comment.