Skip to content

Commit a7b2df1

Browse files
committed
Support targeting morph target weights in glTF animation pointer and interactivity
1 parent b21a969 commit a7b2df1

File tree

6 files changed

+58
-68
lines changed

6 files changed

+58
-68
lines changed

packages/dev/core/src/FlowGraph/Blocks/Data/Transformers/flowGraphJsonPointerParserBlock.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphA
8585
}
8686

8787
public override _doOperation(context: FlowGraphContext): P {
88-
const accessorContainer = this.templateComponent.getAccessor(this.config.pathConverter, context);
89-
const value = accessorContainer.info.get(accessorContainer.object) as P;
90-
const object = accessorContainer.info.getTarget?.(accessorContainer.object);
91-
const propertyName = accessorContainer.info.getPropertyName?.[0](accessorContainer.object);
88+
const accessor = this.templateComponent.getAccessor(this.config.pathConverter, context);
89+
const value = accessor.info.get(accessor.object, accessor.index) as P;
90+
const object = accessor.info.getTarget?.(accessor.object, accessor.index);
91+
const propertyName = accessor.info.getPropertyName?.[0](accessor.object);
9292
if (!object) {
9393
throw new Error("Object is undefined");
9494
} else {
@@ -101,18 +101,18 @@ export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphA
101101
}
102102

103103
private _setPropertyValue(_target: O, _propertyName: string, value: P, context: FlowGraphContext): void {
104-
const accessorContainer = this.templateComponent.getAccessor(this.config.pathConverter, context);
105-
const type = accessorContainer.info.type;
104+
const accessor = this.templateComponent.getAccessor(this.config.pathConverter, context);
105+
const type = accessor.info.type;
106106
if (type.startsWith("Color")) {
107107
value = ToColor(value as Vector4, type) as unknown as P;
108108
}
109-
accessorContainer.info.set?.(value, accessorContainer.object);
109+
accessor.info.set?.(value, accessor.object, accessor.index);
110110
}
111111

112112
private _getPropertyValue(_target: O, _propertyName: string, context: FlowGraphContext): P | undefined {
113-
const accessorContainer = this.templateComponent.getAccessor(this.config.pathConverter, context);
114-
const type = accessorContainer.info.type;
115-
const value = accessorContainer.info.get(accessorContainer.object);
113+
const accessor = this.templateComponent.getAccessor(this.config.pathConverter, context);
114+
const type = accessor.info.type;
115+
const value = accessor.info.get(accessor.object, accessor.index);
116116
if (type.startsWith("Color")) {
117117
return FromColor(value as Color3 | Color4) as unknown as P;
118118
}
@@ -124,11 +124,11 @@ export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphA
124124
_propertyName: string,
125125
context: FlowGraphContext
126126
): (keys: any[], fps: number, animationType: number, easingFunction?: EasingFunction) => Animation[] {
127-
const accessorContainer = this.templateComponent.getAccessor(this.config.pathConverter, context);
127+
const accessor = this.templateComponent.getAccessor(this.config.pathConverter, context);
128128
return (keys: any[], fps: number, animationType: number, easingFunction?: EasingFunction) => {
129129
const animations: Animation[] = [];
130130
// make sure keys are of the right type (in case of float3 color/vector)
131-
const type = accessorContainer.info.type;
131+
const type = accessor.info.type;
132132
if (type.startsWith("Color")) {
133133
keys = keys.map((key) => {
134134
return {
@@ -137,8 +137,8 @@ export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphA
137137
};
138138
});
139139
}
140-
accessorContainer.info.interpolation?.forEach((info, index) => {
141-
const name = accessorContainer.info.getPropertyName?.[index](accessorContainer.object) || "Animation-interpolation-" + index;
140+
accessor.info.interpolation?.forEach((info, index) => {
141+
const name = accessor.info.getPropertyName?.[index](accessor.object) || "Animation-interpolation-" + index;
142142
// generate the keys based on interpolation info
143143
let newKeys: any[] = keys;
144144
if (animationType !== info.type) {
@@ -150,7 +150,7 @@ export class FlowGraphJsonPointerParserBlock<P extends any, O extends FlowGraphA
150150
};
151151
});
152152
}
153-
const animationData = info.buildAnimations(accessorContainer.object, name, 60, newKeys);
153+
const animationData = info.buildAnimations(accessor.object, name, 60, newKeys);
154154
for (const animation of animationData) {
155155
if (easingFunction) {
156156
animation.babylonAnimation.setEasingFunction(easingFunction);

packages/dev/core/src/ObjectModel/objectModelInterfaces.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
/**
22
* A container with an original object and information about that object.
3-
* on some other object.
43
*/
54
export interface IObjectInfo<T, O = any> {
65
/**
76
* The original object.
87
*/
98
object: O;
9+
/**
10+
* The index of the array in question when applicable.
11+
*/
12+
index?: number;
1013
/**
1114
* Information about the object.
1215
*/

packages/dev/loaders/src/glTF/2.0/Extensions/KHR_interactivity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class KHR_interactivity implements IGLTFLoaderExtension {
8383
return parser.serializeToFlowGraph();
8484
});
8585
// parse each graph async
86-
await Promise.all(graphs.map(async (graph) => await ParseFlowGraphAsync(graph, { coordinator, pathConverter: this._pathConverter })));
86+
await Promise.all(graphs.map((graph) => ParseFlowGraphAsync(graph, { coordinator, pathConverter: this._pathConverter })));
8787

8888
coordinator.start();
8989
}

packages/dev/loaders/src/glTF/2.0/Extensions/gltfPathToObjectConverter.ts

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,6 @@ import type { IObjectInfo, IPathToObjectConverter } from "core/ObjectModel/objec
22
import type { IGLTF } from "../glTFLoaderInterfaces";
33
import type { IObjectAccessor } from "core/FlowGraph/typeDefinitions";
44

5-
/**
6-
* Adding an exception here will break traversing through the glTF object tree.
7-
* This is used for properties that might not be in the glTF object model, but are optional and have a default value.
8-
* For example, the path /nodes/\{\}/extensions/KHR_node_visibility/visible is optional - the object can be deferred without the object fully existing.
9-
*/
10-
export const OptionalPathExceptionsList: {
11-
regex: RegExp;
12-
}[] = [
13-
{
14-
// get the node as object when reading an extension
15-
regex: new RegExp(`^/nodes/\\d+/extensions/`),
16-
},
17-
];
18-
195
/**
206
* A converter that takes a glTF Object Model JSON Pointer
217
* and transforms it into an ObjectAccessorContainer, allowing
@@ -42,7 +28,7 @@ export class GLTFPathToObjectConverter<T, BabylonType, BabylonValue> implements
4228
*
4329
* Examples:
4430
* - "/nodes/0/rotation"
45-
* - "/nodes.length"
31+
* - "/nodes.length"
4632
* - "/materials/2/emissiveFactor"
4733
* - "/materials/2/pbrMetallicRoughness/baseColorFactor"
4834
* - "/materials/2/extensions/KHR_materials_emissive_strength/emissiveStrength"
@@ -61,51 +47,40 @@ export class GLTFPathToObjectConverter<T, BabylonType, BabylonValue> implements
6147
const parts = path.split("/");
6248
parts.shift();
6349

64-
//if the last part has ".length" in it, separate that as an extra part
65-
if (parts[parts.length - 1].includes(".length")) {
50+
// If the last part ends with `.length`, separate that as an extra part
51+
if (parts[parts.length - 1].endsWith(".length")) {
6652
const lastPart = parts[parts.length - 1];
6753
const split = lastPart.split(".");
6854
parts.pop();
6955
parts.push(...split);
7056
}
7157

72-
let ignoreObjectTree = false;
58+
let arrayIndex: number | undefined = undefined;
7359

7460
for (const part of parts) {
75-
const isLength = part === "length";
76-
if (isLength && !infoTree.__array__) {
77-
throw new Error(`Path ${path} is invalid`);
78-
}
79-
if (infoTree.__ignoreObjectTree__) {
80-
ignoreObjectTree = true;
81-
}
82-
if (infoTree.__array__ && !isLength) {
83-
infoTree = infoTree.__array__;
84-
} else {
61+
if (infoTree[part]) {
8562
infoTree = infoTree[part];
86-
if (!infoTree) {
87-
throw new Error(`Path ${path} is invalid`);
63+
} else if (infoTree.__array__) {
64+
infoTree = infoTree.__array__;
65+
if (target) {
66+
arrayIndex = parseInt(part, 10);
8867
}
68+
} else {
69+
throw new Error(`Path ${path} is invalid`);
8970
}
90-
if (!ignoreObjectTree) {
91-
if (objectTree === undefined) {
92-
// check if the path is in the exception list. If it is, break and return the last object that was found
93-
const exception = OptionalPathExceptionsList.find((e) => e.regex.test(path));
94-
if (!exception) {
95-
throw new Error(`Path ${path} is invalid`);
96-
}
97-
} else if (!isLength) {
98-
objectTree = objectTree?.[part];
99-
}
71+
72+
if (!target) {
73+
objectTree = objectTree[part];
10074
}
10175

102-
if (infoTree.__target__ || isLength) {
76+
if (infoTree.__target__) {
10377
target = objectTree;
10478
}
10579
}
10680

10781
return {
10882
object: target,
83+
index: arrayIndex,
10984
info: infoTree,
11085
};
11186
}

packages/dev/loaders/src/glTF/2.0/Extensions/objectModelMapping.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export interface IGLTFObjectModelTreeNodesObject<GLTFTargetType = INode, Babylon
4444
globalMatrix: IObjectAccessor<GLTFTargetType, BabylonTargetType, Matrix>;
4545
weights: {
4646
length: IObjectAccessor<GLTFTargetType, BabylonTargetType, number>;
47-
__array__: { __target__: boolean } & IObjectAccessor<GLTFTargetType, BabylonTargetType, number>;
47+
__array__: IObjectAccessor<GLTFTargetType, BabylonTargetType, number>;
4848
} & IObjectAccessor<GLTFTargetType, BabylonTargetType, number[]>;
4949
extensions: {
5050
EXT_lights_ies?: {
@@ -304,21 +304,33 @@ const nodesTree: IGLTFObjectModelTreeNodesObject = {
304304
type: "number",
305305
get: (node: INode) => node._numMorphTargets,
306306
getTarget: (node: INode) => node._babylonTransformNode,
307-
getPropertyName: [() => "influence"],
308307
},
309308
__array__: {
310-
__target__: true,
311309
type: "number",
312-
get: (node: INode, index?: number) => (index !== undefined ? node._primitiveBabylonMeshes?.[0].morphTargetManager?.getTarget(index).influence : undefined),
313-
// set: (value: number, node: INode, index?: number) => node._babylonTransformNode?.getMorphTargetManager()?.getTarget(index)?.setInfluence(value),
310+
get: (node: INode, arrayIndex?: number) => node._primitiveBabylonMeshes![0].morphTargetManager!.getTarget(arrayIndex!).influence,
311+
set: (value: number, node: INode, arrayIndex?: number) => {
312+
for (const primitive of node._primitiveBabylonMeshes!) {
313+
const target = primitive.morphTargetManager!.getTarget(arrayIndex!);
314+
if (target) {
315+
target.influence = value;
316+
}
317+
}
318+
},
314319
getTarget: (node: INode) => node._babylonTransformNode,
315-
getPropertyName: [() => "influence"],
316320
},
317321
type: "number[]",
318-
get: (node: INode, index?: number) => [0], // TODO: get the weights correctly
319-
// set: (value: number, node: INode, index?: number) => node._babylonTransformNode?.getMorphTargetManager()?.getTarget(index)?.setInfluence(value),
322+
get: (node: INode) => Array.from({ length: node._numMorphTargets! }, (_, index) => node._primitiveBabylonMeshes![0].morphTargetManager!.getTarget(index).influence!),
323+
set: (value: number[], node: INode) => {
324+
for (const primitive of node._primitiveBabylonMeshes!) {
325+
for (let i = 0; i < value.length; i++) {
326+
const target = primitive.morphTargetManager!.getTarget(i);
327+
if (target) {
328+
target.influence = value[i];
329+
}
330+
}
331+
}
332+
},
320333
getTarget: (node: INode) => node._babylonTransformNode,
321-
getPropertyName: [() => "influence"],
322334
},
323335
// readonly!
324336
matrix: {

packages/public/glTF2Interface/babylon.glTF2Interface.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ declare module BABYLON.GLTF2 {
691691
*/
692692
mode?: MeshPrimitiveMode;
693693
/**
694-
* An array of Morph Targets, each Morph Target is a dictionary mapping attributes (only POSITION, NORMAL, and TANGENT supported) to their deviations in the Morph Target
694+
* An array of morph targets, each morph target is a dictionary mapping attributes (only POSITION, NORMAL, and TANGENT supported) to their deviations in the morph target
695695
*/
696696
targets?: {
697697
[name: string]: number;

0 commit comments

Comments
 (0)