Skip to content

Commit 4a5d193

Browse files
committed
Support morph target weights in glTF animation pointer and interactivity
1 parent f74b7b2 commit 4a5d193

File tree

10 files changed

+121
-111
lines changed

10 files changed

+121
-111
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_animation_pointer.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,26 +97,29 @@ export class KHR_animation_pointer implements IGLTFLoaderExtension {
9797
throw new Error(`${extensionContext}: Pointer is missing`);
9898
}
9999

100+
let obj: { object: any; info: any };
100101
try {
101-
const obj = this._pathToObjectConverter.convert(pointer);
102-
if (!obj.info.interpolation) {
103-
throw new Error(`${extensionContext}/pointer: Interpolation is missing`);
104-
}
105-
return this._loader._loadAnimationChannelFromTargetInfoAsync(
106-
context,
107-
animationContext,
108-
animation,
109-
channel,
110-
{
111-
object: obj.object,
112-
info: obj.info.interpolation,
113-
},
114-
onLoad
115-
);
102+
obj = this._pathToObjectConverter.convert(pointer);
116103
} catch (e) {
117104
Logger.Warn(`${extensionContext}/pointer: Invalid pointer (${pointer}) skipped`);
118105
return null;
119106
}
107+
108+
if (!obj.info.interpolation) {
109+
throw new Error(`${extensionContext}/pointer: Interpolation is missing`);
110+
}
111+
112+
return this._loader._loadAnimationChannelFromTargetInfoAsync(
113+
context,
114+
animationContext,
115+
animation,
116+
channel,
117+
{
118+
object: obj.object,
119+
info: obj.info.interpolation,
120+
},
121+
onLoad
122+
);
120123
}
121124
}
122125

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

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { InteractivityGraphToFlowGraphParser } from "./KHR_interactivity/interac
1111
import { addToBlockFactory } from "core/FlowGraph/Blocks/flowGraphBlockFactory";
1212
import { Quaternion, Vector3 } from "core/Maths/math.vector";
1313
import type { Scene } from "core/scene";
14-
import type { IAnimation } from "../glTFLoaderInterfaces";
14+
import type { IAnimation, IScene } from "../glTFLoaderInterfaces";
15+
import { Nullable } from "core/types";
1516

1617
const NAME = "KHR_interactivity";
1718

@@ -39,13 +40,17 @@ export class KHR_interactivity implements IGLTFLoaderExtension {
3940
*/
4041
public enabled: boolean;
4142

42-
private _pathConverter?: GLTFPathToObjectConverter<any, any, any>;
43+
private readonly _pathConverter: GLTFPathToObjectConverter<any, any, any>;
44+
45+
private _loader?: GLTFLoader;
46+
private _coordinator?: FlowGraphCoordinator;
4347

4448
/**
4549
* @internal
4650
* @param _loader
4751
*/
48-
constructor(private _loader: GLTFLoader) {
52+
constructor(_loader: GLTFLoader) {
53+
this._loader = _loader;
4954
this.enabled = this._loader.isExtensionUsed(NAME);
5055
this._pathConverter = GetPathToObjectConverter(this._loader.gltf);
5156
// avoid starting animations automatically.
@@ -60,32 +65,47 @@ export class KHR_interactivity implements IGLTFLoaderExtension {
6065
}
6166

6267
public dispose() {
63-
(this._loader as any) = null;
64-
delete this._pathConverter;
68+
delete this._loader;
69+
delete this._coordinator;
6570
}
6671

67-
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-misused-promises
68-
public async onReady(): Promise<void> {
69-
if (!this._loader.babylonScene || !this._pathConverter) {
70-
return;
72+
public onReady(): void {
73+
if (this._coordinator) {
74+
this._coordinator.start();
7175
}
72-
const scene = this._loader.babylonScene;
73-
const interactivityDefinition = this._loader.gltf.extensions?.KHR_interactivity as IKHRInteractivity;
74-
if (!interactivityDefinition) {
75-
// This can technically throw, but it's not a critical error
76-
return;
76+
}
77+
78+
public loadSceneAsync(context: string, scene: IScene): Nullable<Promise<void>> {
79+
if (!this._loader) {
80+
return null;
7781
}
7882

79-
const coordinator = new FlowGraphCoordinator({ scene });
80-
coordinator.dispatchEventsSynchronously = false; // glTF interactivity dispatches events asynchronously
81-
const graphs = interactivityDefinition.graphs.map((graph) => {
82-
const parser = new InteractivityGraphToFlowGraphParser(graph, this._loader.gltf, this._loader.parent.targetFps);
83-
return parser.serializeToFlowGraph();
84-
});
85-
// parse each graph async
86-
await Promise.all(graphs.map(async (graph) => await ParseFlowGraphAsync(graph, { coordinator, pathConverter: this._pathConverter })));
83+
return this._loader.loadSceneAsync(context, scene).then(() => {
84+
if (!this._loader) {
85+
return;
86+
}
8787

88-
coordinator.start();
88+
const scene = this._loader.babylonScene;
89+
const gltf = this._loader.gltf;
90+
const targetFps = this._loader.parent.targetFps;
91+
const interactivityDefinition = gltf.extensions?.KHR_interactivity as IKHRInteractivity;
92+
if (!interactivityDefinition) {
93+
// This can technically throw, but it's not a critical error
94+
return;
95+
}
96+
97+
const coordinator = new FlowGraphCoordinator({ scene });
98+
coordinator.dispatchEventsSynchronously = false; // glTF interactivity dispatches events asynchronously
99+
100+
const graphs = interactivityDefinition.graphs.map((graph) => {
101+
const parser = new InteractivityGraphToFlowGraphParser(graph, gltf, targetFps);
102+
return parser.serializeToFlowGraph();
103+
});
104+
105+
return Promise.all(graphs.map((graph) => ParseFlowGraphAsync(graph, { coordinator, pathConverter: this._pathConverter }))).then(() => {
106+
this._coordinator = coordinator;
107+
});
108+
});
89109
}
90110
}
91111

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,7 @@ export class KHR_node_hoverability implements IGLTFLoaderExtension {
199199
this.enabled = loader.isExtensionUsed(NAME);
200200
}
201201

202-
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-misused-promises
203-
public async onReady(): Promise<void> {
202+
public onReady(): void {
204203
this._loader.gltf.nodes?.forEach((node) => {
205204
// default is true, so only apply if false
206205
if (node.extensions?.KHR_node_hoverability && node.extensions?.KHR_node_hoverability.hoverable === false) {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,7 @@ export class KHR_node_selectability implements IGLTFLoaderExtension {
132132
this.enabled = loader.isExtensionUsed(NAME);
133133
}
134134

135-
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-misused-promises
136-
public async onReady(): Promise<void> {
135+
public onReady(): void {
137136
this._loader.gltf.nodes?.forEach((node) => {
138137
if (node.extensions?.KHR_node_selectability && node.extensions?.KHR_node_selectability.selectable === false) {
139138
node._babylonTransformNode?.getChildMeshes().forEach((mesh) => {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ export class KHR_node_visibility implements IGLTFLoaderExtension {
6767
this.enabled = loader.isExtensionUsed(NAME);
6868
}
6969

70-
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-misused-promises
71-
public async onReady(): Promise<void> {
70+
public onReady(): void {
7271
this._loader.gltf.nodes?.forEach((node) => {
7372
node._primitiveBabylonMeshes?.forEach((mesh) => {
7473
mesh.inheritVisibility = true;

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 index: 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+
index = 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: index,
10984
info: infoTree,
11085
};
11186
}

0 commit comments

Comments
 (0)