@babylonjs/loaders
Version:
For usage documentation please visit https://doc.babylonjs.com/features/featuresDeepDive/importers/loadingFileTypes/.
151 lines • 6.61 kB
JavaScript
import { FlowGraphCoordinator } from "@babylonjs/core/FlowGraph/flowGraphCoordinator.js";
import { ParseFlowGraphAsync } from "@babylonjs/core/FlowGraph/flowGraphParser.js";
import { registerGLTFExtension, unregisterGLTFExtension } from "../glTFLoaderExtensionRegistry.js";
import { AddObjectAccessorToKey, GetPathToObjectConverter } from "./objectModelMapping.js";
import { InteractivityGraphToFlowGraphParser } from "./KHR_interactivity/interactivityGraphParser.js";
import { addToBlockFactory } from "@babylonjs/core/FlowGraph/Blocks/flowGraphBlockFactory.js";
import { Quaternion, Vector3 } from "@babylonjs/core/Maths/math.vector.js";
const NAME = "KHR_interactivity";
/**
* Loader extension for KHR_interactivity
*/
export class KHR_interactivity {
/**
* @internal
* @param _loader
*/
constructor(_loader) {
this._loader = _loader;
/**
* The name of this extension.
*/
this.name = NAME;
this.enabled = this._loader.isExtensionUsed(NAME);
this._pathConverter = GetPathToObjectConverter(this._loader.gltf);
// avoid starting animations automatically.
_loader._skipStartAnimationStep = true;
// Update object model with new pointers
const scene = _loader.babylonScene;
if (scene) {
_AddInteractivityObjectModel(scene);
}
}
dispose() {
this._loader = null;
delete this._pathConverter;
}
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-misused-promises
async onReady() {
if (!this._loader.babylonScene || !this._pathConverter) {
return;
}
const scene = this._loader.babylonScene;
const interactivityDefinition = this._loader.gltf.extensions?.KHR_interactivity;
if (!interactivityDefinition) {
// This can technically throw, but it's not a critical error
return;
}
const coordinator = new FlowGraphCoordinator({ scene });
coordinator.dispatchEventsSynchronously = false; // glTF interactivity dispatches events asynchronously
const graphs = interactivityDefinition.graphs.map((graph) => {
const parser = new InteractivityGraphToFlowGraphParser(graph, this._loader.gltf, this._loader.parent.targetFps);
return parser.serializeToFlowGraph();
});
// parse each graph async
await Promise.all(graphs.map(async (graph) => await ParseFlowGraphAsync(graph, { coordinator, pathConverter: this._pathConverter })));
coordinator.start();
}
}
/**
* @internal
* populates the object model with the interactivity extension
*/
export function _AddInteractivityObjectModel(scene) {
// Note - all of those are read-only, as per the specs!
// active camera rotation
AddObjectAccessorToKey("/extensions/KHR_interactivity/?/activeCamera/rotation", {
get: () => {
if (!scene.activeCamera) {
return new Quaternion(NaN, NaN, NaN, NaN);
}
const quat = Quaternion.FromRotationMatrix(scene.activeCamera.getWorldMatrix()).normalize();
if (!scene.useRightHandedSystem) {
quat.w *= -1; // glTF uses right-handed system, while babylon uses left-handed
quat.x *= -1; // glTF uses right-handed system, while babylon uses left-handed
}
return quat;
},
type: "Quaternion",
getTarget: () => scene.activeCamera,
});
// activeCamera position
AddObjectAccessorToKey("/extensions/KHR_interactivity/?/activeCamera/position", {
get: () => {
if (!scene.activeCamera) {
return new Vector3(NaN, NaN, NaN);
}
const pos = scene.activeCamera.getWorldMatrix().getTranslation(); // not global position
if (!scene.useRightHandedSystem) {
pos.x *= -1; // glTF uses right-handed system, while babylon uses left-handed
}
return pos;
},
type: "Vector3",
getTarget: () => scene.activeCamera,
});
// /animations/{} pointers:
AddObjectAccessorToKey("/animations/{}/extensions/KHR_interactivity/isPlaying", {
get: (animation) => {
return animation._babylonAnimationGroup?.isPlaying ?? false;
},
type: "boolean",
getTarget: (animation) => {
return animation._babylonAnimationGroup;
},
});
AddObjectAccessorToKey("/animations/{}/extensions/KHR_interactivity/minTime", {
get: (animation) => {
return (animation._babylonAnimationGroup?.from ?? 0) / 60; // fixed factor for duration-to-frames conversion
},
type: "number",
getTarget: (animation) => {
return animation._babylonAnimationGroup;
},
});
AddObjectAccessorToKey("/animations/{}/extensions/KHR_interactivity/maxTime", {
get: (animation) => {
return (animation._babylonAnimationGroup?.to ?? 0) / 60; // fixed factor for duration-to-frames conversion
},
type: "number",
getTarget: (animation) => {
return animation._babylonAnimationGroup;
},
});
// playhead
AddObjectAccessorToKey("/animations/{}/extensions/KHR_interactivity/playhead", {
get: (animation) => {
return (animation._babylonAnimationGroup?.getCurrentFrame() ?? 0) / 60; // fixed factor for duration-to-frames conversion
},
type: "number",
getTarget: (animation) => {
return animation._babylonAnimationGroup;
},
});
//virtualPlayhead - TODO, do we support this property in our animations? getCurrentFrame is the only method we have for this.
AddObjectAccessorToKey("/animations/{}/extensions/KHR_interactivity/virtualPlayhead", {
get: (animation) => {
return (animation._babylonAnimationGroup?.getCurrentFrame() ?? 0) / 60; // fixed factor for duration-to-frames conversion
},
type: "number",
getTarget: (animation) => {
return animation._babylonAnimationGroup;
},
});
}
// Register flow graph blocks. Do it here so they are available when the extension is enabled.
addToBlockFactory(NAME, "FlowGraphGLTFDataProvider", async () => {
return (await import("./KHR_interactivity/flowGraphGLTFDataProvider.js")).FlowGraphGLTFDataProvider;
});
unregisterGLTFExtension(NAME);
registerGLTFExtension(NAME, true, (loader) => new KHR_interactivity(loader));
//# sourceMappingURL=KHR_interactivity.js.map