Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AnimationCurve #8409

Merged
merged 35 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
11cf750
AnimationCurve
shrinktofit Jun 30, 2021
5c93696
Mark curve frame values as uniquelyReferenced
shrinktofit Jun 30, 2021
08d90f1
Adjust modules
shrinktofit Jul 1, 2021
d7c5513
Update
shrinktofit Jul 1, 2021
8d66265
Manually implements KeyframeCurve.iterator
shrinktofit Jul 1, 2021
bd1f2b6
TargetPath
shrinktofit Jul 1, 2021
75157cd
Update
shrinktofit Jul 2, 2021
9f02855
Fix BUG
shrinktofit Jul 2, 2021
8e1c329
Fix
shrinktofit Jul 2, 2021
9a0d2d1
Add size track
shrinktofit Jul 12, 2021
154c1a3
Update
shrinktofit Jul 12, 2021
e579f84
Fix size; unit tests
shrinktofit Jul 12, 2021
b756cd6
fix split animation (#24)
gameall3d Jul 14, 2021
79fdb28
CCONB animation clip; Remove integer track/channel/curve
shrinktofit Jul 16, 2021
0ef7819
Optimize key shared curves
shrinktofit Jul 16, 2021
e8a3b03
Remove keyframecurve.empty
shrinktofit Jul 17, 2021
14de4ea
Exotic animation
shrinktofit Jul 17, 2021
b53f4dd
Fix unit tests
shrinktofit Jul 19, 2021
29e268e
start/end -> left/right
shrinktofit Jul 19, 2021
04af667
Curve range rename
shrinktofit Jul 19, 2021
3dd06b7
Easing methods
shrinktofit Jul 19, 2021
28019de
Fix untyped track
shrinktofit Jul 20, 2021
bef3e82
Fix
shrinktofit Jul 20, 2021
a954431
fix upgradUnTypedTracks (#26)
gameall3d Jul 20, 2021
36a315c
Rename
shrinktofit Jul 21, 2021
273e501
Update pre/post extrap
shrinktofit Jul 21, 2021
5c75516
Optimize-1
shrinktofit Jul 21, 2021
dc2f9ce
Optimize-2
shrinktofit Jul 21, 2021
7e7b18f
Eliminate for-of
shrinktofit Jul 21, 2021
d0f8145
Mark deprecateds
shrinktofit Jul 22, 2021
d7c22a0
Hide RealKeyframeValue/QuatKeyframeValue Constructor
shrinktofit Jul 22, 2021
0dece5d
Fix quat keyframe value name
shrinktofit Jul 22, 2021
83dd1f9
Error ID
shrinktofit Jul 22, 2021
b95b68b
Fix RealKeyframeValue editor extras
shrinktofit Jul 22, 2021
744aa4f
Elimanate for-of forEach
shrinktofit Jul 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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