Skip to content

Commit

Permalink
AnimationCurve
Browse files Browse the repository at this point in the history
  • Loading branch information
shrinktofit committed Apr 21, 2021
1 parent cf55464 commit f559776
Show file tree
Hide file tree
Showing 29 changed files with 2,728 additions and 698 deletions.
3 changes: 3 additions & 0 deletions .vscode/cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
"Chukong",
"clampf",
"COCOSPLAY",
"coeff",
"deinterleave",
"deserialize",
"earcut",
"emscripten",
"endregion",
"eventify",
"eventified",
"forin",
"glsl",
"grayscale",
"interp",
"IGFX",
"lerp",
"lerpable",
Expand Down
10 changes: 5 additions & 5 deletions cocos/3d/skeletal-animation/skeletal-animation-blending.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ import { Vec3, Quat } from '../../core/math';
import { Node } from '../../core/scene-graph';
import { IValueProxyFactory } from '../../core/animation/value-proxy';
import { assertIsNonNullable } from '../../core/data/utils/asserts';
import { AnimationState } from '../../core/animation/animation-state';
import { PoseOutput } from '../../core/animation/pose-output';

export class BlendStateBuffer {
private _nodeBlendStates: Map<Node, NodeBlendState> = new Map();
private _states = new Set<AnimationState>();
private _states = new Set<PoseOutput>();

public ref (node: Node, property: BlendingProperty) {
let nodeBlendState = this._nodeBlendStates.get(node);
Expand Down Expand Up @@ -118,11 +118,11 @@ export class BlendStateBuffer {
});
}

public bindState (state: AnimationState) {
public bindState (state: PoseOutput) {
this._states.add(state);
}

public unbindState (state: AnimationState) {
public unbindState (state: PoseOutput) {
this._states.delete(state);
}
}
Expand Down Expand Up @@ -197,7 +197,7 @@ function isVec3Property (property: BlendingProperty) {
return !isQuatProperty(property);
}

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

type BlendingPropertyValue<P extends BlendingProperty> = NonNullable<NodeBlendState['properties'][P]>['value'];

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>();
}
21 changes: 10 additions & 11 deletions cocos/3d/skeletal-animation/skeletal-animation-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,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 @@ -52,8 +52,6 @@ interface ISocketData {
frames: ITransform[];
}

const noCurves: IRuntimeCurve[] = [];

export class SkeletalAnimationState extends AnimationState {
protected _frames = 1;

Expand Down Expand Up @@ -88,12 +86,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 @@ -127,13 +126,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 @@ -142,8 +141,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
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
8 changes: 4 additions & 4 deletions cocos/core/algorithm/binary-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function binarySearch (array: number[], value: number) {
* otherwise, a negative number that is the bitwise complement of the index of the next element that is large than the searched value or,
* if there is no larger element(include the case that the array is empty), the bitwise complement of array's length.
*/
export function binarySearchEpsilon (array: number[], value: number, EPSILON = 1e-6) {
export function binarySearchEpsilon (array: readonly number[], value: number, EPSILON = 1e-6) {
let low = 0;
let high = array.length - 1;
let middle = high >>> 1;
Expand All @@ -71,15 +71,15 @@ export function binarySearchEpsilon (array: number[], value: number, EPSILON = 1
* otherwise, a negative number that is the bitwise complement of the index of the next element that is large than the searched value or,
* if there is no larger element(include the case that the array is empty), the bitwise complement of array's length.
*/
export function binarySearchBy<T> (array: T[], value: T, lessThan: (lhs: T, rhs: T) => boolean) {
export function binarySearchBy<T, U> (array: T[], value: U, lessThan: (lhs: T, rhs: U) => number) {
let low = 0;
let high = array.length - 1;
let middle = high >>> 1;
for (; low <= high; middle = (low + high) >>> 1) {
const test = array[middle];
if (lessThan(value, test)) {
if (lessThan(test, value) < 0) {
high = middle - 1;
} else if (lessThan(test, value)) {
} else if (lessThan(test, value) > 0) {
low = middle + 1;
} else {
return middle;
Expand Down
Loading

0 comments on commit f559776

Please sign in to comment.