@babylonjs/loaders
Version:
For usage documentation please visit https://doc.babylonjs.com/features/featuresDeepDive/importers/loadingFileTypes/.
220 lines • 10.2 kB
JavaScript
import { Vector3 } from "@babylonjs/core/Maths/math.vector.js";
import { Tools } from "@babylonjs/core/Misc/tools.js";
import { AnimationEvent } from "@babylonjs/core/Animations/animationEvent.js";
import { Sound } from "@babylonjs/core/Audio/sound.js";
import { WeightedSound } from "@babylonjs/core/Audio/weightedsound.js";
import { GLTFLoader, ArrayItem } from "../glTFLoader.js";
import { registerGLTFExtension, unregisterGLTFExtension } from "../glTFLoaderExtensionRegistry.js";
import "@babylonjs/core/Audio/audioSceneComponent.js";
const NAME = "MSFT_audio_emitter";
/**
* [Specification](https://github.com/najadojo/glTF/blob/MSFT_audio_emitter/extensions/2.0/Vendor/MSFT_audio_emitter/README.md)
* !!! Experimental Extension Subject to Changes !!!
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export class MSFT_audio_emitter {
/**
* @internal
*/
constructor(loader) {
/**
* The name of this extension.
*/
this.name = NAME;
this._loader = loader;
this.enabled = this._loader.isExtensionUsed(NAME);
}
/** @internal */
dispose() {
this._loader = null;
this._clips = null;
this._emitters = null;
}
/** @internal */
onLoading() {
const extensions = this._loader.gltf.extensions;
if (extensions && extensions[this.name]) {
const extension = extensions[this.name];
this._clips = extension.clips;
this._emitters = extension.emitters;
ArrayItem.Assign(this._clips);
ArrayItem.Assign(this._emitters);
}
}
/**
* @internal
*/
loadSceneAsync(context, scene) {
return GLTFLoader.LoadExtensionAsync(context, scene, this.name, (extensionContext, extension) => {
const promises = new Array();
promises.push(this._loader.loadSceneAsync(context, scene));
for (const emitterIndex of extension.emitters) {
const emitter = ArrayItem.Get(`${extensionContext}/emitters`, this._emitters, emitterIndex);
if (emitter.refDistance != undefined ||
emitter.maxDistance != undefined ||
emitter.rolloffFactor != undefined ||
emitter.distanceModel != undefined ||
emitter.innerAngle != undefined ||
emitter.outerAngle != undefined) {
throw new Error(`${extensionContext}: Direction or Distance properties are not allowed on emitters attached to a scene`);
}
promises.push(this._loadEmitterAsync(`${extensionContext}/emitters/${emitter.index}`, emitter));
}
return Promise.all(promises).then(() => { });
});
}
/**
* @internal
*/
loadNodeAsync(context, node, assign) {
return GLTFLoader.LoadExtensionAsync(context, node, this.name, (extensionContext, extension) => {
const promises = new Array();
return this._loader
.loadNodeAsync(extensionContext, node, (babylonMesh) => {
for (const emitterIndex of extension.emitters) {
const emitter = ArrayItem.Get(`${extensionContext}/emitters`, this._emitters, emitterIndex);
promises.push(this._loadEmitterAsync(`${extensionContext}/emitters/${emitter.index}`, emitter).then(() => {
for (const sound of emitter._babylonSounds) {
sound.attachToMesh(babylonMesh);
if (emitter.innerAngle != undefined || emitter.outerAngle != undefined) {
sound.setLocalDirectionToMesh(Vector3.Forward());
sound.setDirectionalCone(2 * Tools.ToDegrees(emitter.innerAngle == undefined ? Math.PI : emitter.innerAngle), 2 * Tools.ToDegrees(emitter.outerAngle == undefined ? Math.PI : emitter.outerAngle), 0);
}
}
}));
}
assign(babylonMesh);
})
.then((babylonMesh) => {
return Promise.all(promises).then(() => {
return babylonMesh;
});
});
});
}
/**
* @internal
*/
loadAnimationAsync(context, animation) {
return GLTFLoader.LoadExtensionAsync(context, animation, this.name, (extensionContext, extension) => {
return this._loader.loadAnimationAsync(context, animation).then((babylonAnimationGroup) => {
const promises = new Array();
ArrayItem.Assign(extension.events);
for (const event of extension.events) {
promises.push(this._loadAnimationEventAsync(`${extensionContext}/events/${event.index}`, context, animation, event, babylonAnimationGroup));
}
return Promise.all(promises).then(() => {
return babylonAnimationGroup;
});
});
});
}
_loadClipAsync(context, clip) {
if (clip._objectURL) {
return clip._objectURL;
}
let promise;
if (clip.uri) {
promise = this._loader.loadUriAsync(context, clip, clip.uri);
}
else {
const bufferView = ArrayItem.Get(`${context}/bufferView`, this._loader.gltf.bufferViews, clip.bufferView);
promise = this._loader.loadBufferViewAsync(`/bufferViews/${bufferView.index}`, bufferView);
}
clip._objectURL = promise.then((data) => {
return URL.createObjectURL(new Blob([data], { type: clip.mimeType }));
});
return clip._objectURL;
}
_loadEmitterAsync(context, emitter) {
emitter._babylonSounds = emitter._babylonSounds || [];
if (!emitter._babylonData) {
const clipPromises = new Array();
const name = emitter.name || `emitter${emitter.index}`;
const options = {
loop: false,
autoplay: false,
volume: emitter.volume == undefined ? 1 : emitter.volume,
};
for (let i = 0; i < emitter.clips.length; i++) {
const clipContext = `/extensions/${this.name}/clips`;
const clip = ArrayItem.Get(clipContext, this._clips, emitter.clips[i].clip);
clipPromises.push(this._loadClipAsync(`${clipContext}/${emitter.clips[i].clip}`, clip).then((objectURL) => {
const sound = (emitter._babylonSounds[i] = new Sound(name, objectURL, this._loader.babylonScene, null, options));
sound.refDistance = emitter.refDistance || 1;
sound.maxDistance = emitter.maxDistance || 256;
sound.rolloffFactor = emitter.rolloffFactor || 1;
sound.distanceModel = emitter.distanceModel || "exponential";
}));
}
const promise = Promise.all(clipPromises).then(() => {
const weights = emitter.clips.map((clip) => {
return clip.weight || 1;
});
const weightedSound = new WeightedSound(emitter.loop || false, emitter._babylonSounds, weights);
if (emitter.innerAngle) {
weightedSound.directionalConeInnerAngle = 2 * Tools.ToDegrees(emitter.innerAngle);
}
if (emitter.outerAngle) {
weightedSound.directionalConeOuterAngle = 2 * Tools.ToDegrees(emitter.outerAngle);
}
if (emitter.volume) {
weightedSound.volume = emitter.volume;
}
emitter._babylonData.sound = weightedSound;
});
emitter._babylonData = {
loaded: promise,
};
}
return emitter._babylonData.loaded;
}
_getEventAction(context, sound, action, time, startOffset) {
switch (action) {
case "play" /* IMSFTAudioEmitter_AnimationEventAction.play */: {
return (currentFrame) => {
const frameOffset = (startOffset || 0) + (currentFrame - time);
sound.play(frameOffset);
};
}
case "stop" /* IMSFTAudioEmitter_AnimationEventAction.stop */: {
return () => {
sound.stop();
};
}
case "pause" /* IMSFTAudioEmitter_AnimationEventAction.pause */: {
return () => {
sound.pause();
};
}
default: {
throw new Error(`${context}: Unsupported action ${action}`);
}
}
}
_loadAnimationEventAsync(context, animationContext, animation, event, babylonAnimationGroup) {
if (babylonAnimationGroup.targetedAnimations.length == 0) {
return Promise.resolve();
}
const babylonAnimation = babylonAnimationGroup.targetedAnimations[0];
const emitterIndex = event.emitter;
const emitter = ArrayItem.Get(`/extensions/${this.name}/emitters`, this._emitters, emitterIndex);
return this._loadEmitterAsync(context, emitter).then(() => {
const sound = emitter._babylonData.sound;
if (sound) {
const babylonAnimationEvent = new AnimationEvent(event.time, this._getEventAction(context, sound, event.action, event.time, event.startOffset));
babylonAnimation.animation.addEvent(babylonAnimationEvent);
// Make sure all started audio stops when this animation is terminated.
babylonAnimationGroup.onAnimationGroupEndObservable.add(() => {
sound.stop();
});
babylonAnimationGroup.onAnimationGroupPauseObservable.add(() => {
sound.pause();
});
}
});
}
}
unregisterGLTFExtension(NAME);
registerGLTFExtension(NAME, true, (loader) => new MSFT_audio_emitter(loader));
//# sourceMappingURL=MSFT_audio_emitter.js.map