@babylonjs/viewer
Version:
The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.
750 lines (738 loc) • 25.6 kB
JavaScript
import { _ as _AbstractAudioSubNode, a as _WebAudioBaseSubGraph, b as _GetVolumeAudioSubNode } from './webAudioBaseSubGraph-Cl2P5hh_.esm.js';
import { V as Vector3, Q as Quaternion, M as Matrix } from './index-D8dpgsp6.esm.js';
import { _ as _SpatialAudioAttacherComponent, a as _WebAudioParameterComponent, b as _SpatialWebAudioUpdaterComponent } from './audioEngine-DOe3dmI5.esm.js';
const _SpatialAudioDefaults = {
coneInnerAngle: 6.28318530718,
coneOuterAngle: 6.28318530718,
coneOuterVolume: 0,
distanceModel: "linear",
maxDistance: 10000,
minDistance: 1,
panningModel: "equalpower",
position: Vector3.Zero(),
rolloffFactor: 1,
rotation: Vector3.Zero(),
rotationQuaternion: new Quaternion(),
};
/**
* @param options The spatial audio options to check.
* @returns `true` if spatial audio options are defined, otherwise `false`.
*/
function _HasSpatialAudioOptions(options) {
return (options.spatialEnabled ||
options.spatialAutoUpdate !== undefined ||
options.spatialConeInnerAngle !== undefined ||
options.spatialConeOuterAngle !== undefined ||
options.spatialConeOuterVolume !== undefined ||
options.spatialDistanceModel !== undefined ||
options.spatialMaxDistance !== undefined ||
options.spatialMinDistance !== undefined ||
options.spatialMinUpdateTime !== undefined ||
options.spatialPanningModel !== undefined ||
options.spatialPosition !== undefined ||
options.spatialRolloffFactor !== undefined ||
options.spatialRotation !== undefined ||
options.spatialRotationQuaternion !== undefined);
}
/**
* Abstract class representing the `spatial` audio property on a sound or audio bus.
*
* @see {@link AudioEngineV2.listener}
*/
class AbstractSpatialAudio {
}
const _StereoAudioDefaults = {
pan: 0,
};
/**
* @param options The stereo audio options to check.
* @returns `true` if stereo audio options are defined, otherwise `false`.
*/
function _HasStereoAudioOptions(options) {
return options.stereoEnabled || options.stereoPan !== undefined;
}
/**
* Abstract class representing the `stereo` audio property on a sound or audio bus.
*
* @see {@link AudioEngineV2.listener}
*/
class AbstractStereoAudio {
}
/** @internal */
class _StereoAudioSubNode extends _AbstractAudioSubNode {
constructor(engine) {
super("Stereo" /* AudioSubNode.STEREO */, engine);
}
/** @internal */
setOptions(options) {
this.pan = options.stereoPan ?? _StereoAudioDefaults.pan;
}
}
/** @internal */
function _GetStereoAudioSubNode(subGraph) {
return subGraph.getSubNode("Stereo" /* AudioSubNode.STEREO */);
}
/** @internal */
function _SetStereoAudioProperty(subGraph, property, value) {
subGraph.callOnSubNode("Stereo" /* AudioSubNode.STEREO */, (node) => {
node[property] = value;
});
}
/** @internal */
class _StereoAudio extends AbstractStereoAudio {
/** @internal */
constructor(subGraph) {
super();
this._pan = _StereoAudioDefaults.pan;
this._subGraph = subGraph;
}
/** @internal */
get pan() {
return this._pan;
}
set pan(value) {
this._pan = value;
_SetStereoAudioProperty(this._subGraph, "pan", value);
}
}
/** @internal */
class _SpatialAudioSubNode extends _AbstractAudioSubNode {
constructor(engine) {
super("Spatial" /* AudioSubNode.SPATIAL */, engine);
this._attacherComponent = null;
}
/** @internal */
get isAttached() {
return this._attacherComponent !== null && this._attacherComponent.isAttached;
}
/** @internal */
attach(sceneNode, useBoundingBox, attachmentType) {
this.detach();
if (!this._attacherComponent) {
this._attacherComponent = new _SpatialAudioAttacherComponent(this);
}
this._attacherComponent.attach(sceneNode, useBoundingBox, attachmentType);
}
/** @internal */
detach() {
this._attacherComponent?.detach();
}
/** @internal */
dispose() {
super.dispose();
this._attacherComponent?.dispose();
this._attacherComponent = null;
}
/** @internal */
setOptions(options) {
this.coneInnerAngle = options.spatialConeInnerAngle ?? _SpatialAudioDefaults.coneInnerAngle;
this.coneOuterAngle = options.spatialConeOuterAngle ?? _SpatialAudioDefaults.coneOuterAngle;
this.coneOuterVolume = options.spatialConeOuterVolume ?? _SpatialAudioDefaults.coneOuterVolume;
this.distanceModel = options.spatialDistanceModel ?? _SpatialAudioDefaults.distanceModel;
this.maxDistance = options.spatialMaxDistance ?? _SpatialAudioDefaults.maxDistance;
this.minDistance = options.spatialMinDistance ?? _SpatialAudioDefaults.minDistance;
this.panningModel = options.spatialPanningModel ?? _SpatialAudioDefaults.panningModel;
this.rolloffFactor = options.spatialRolloffFactor ?? _SpatialAudioDefaults.rolloffFactor;
if (options.spatialPosition) {
this.position = options.spatialPosition.clone();
}
if (options.spatialRotationQuaternion) {
this.rotationQuaternion = options.spatialRotationQuaternion.clone();
}
else if (options.spatialRotation) {
this.rotation = options.spatialRotation.clone();
}
else {
this.rotationQuaternion = _SpatialAudioDefaults.rotationQuaternion.clone();
}
this.update();
}
/** @internal */
update() {
if (this.isAttached) {
this._attacherComponent?.update();
}
else {
this._updatePosition();
this._updateRotation();
}
}
}
/** @internal */
function _GetSpatialAudioSubNode(subGraph) {
return subGraph.getSubNode("Spatial" /* AudioSubNode.SPATIAL */);
}
/** @internal */
function _SetSpatialAudioProperty(subGraph, property, value) {
subGraph.callOnSubNode("Spatial" /* AudioSubNode.SPATIAL */, (node) => {
node[property] = value;
});
}
const TmpMatrix = Matrix.Zero();
const TmpQuaternion = new Quaternion();
const TmpVector = Vector3.Zero();
function D2r(degrees) {
return (degrees * Math.PI) / 180;
}
function R2d(radians) {
return (radians * 180) / Math.PI;
}
/** @internal */
// eslint-disable-next-line @typescript-eslint/require-await
async function _CreateSpatialAudioSubNodeAsync(engine) {
return new _SpatialWebAudioSubNode(engine);
}
/** @internal */
class _SpatialWebAudioSubNode extends _SpatialAudioSubNode {
/** @internal */
constructor(engine) {
super(engine);
this._lastPosition = Vector3.Zero();
this._lastRotation = Vector3.Zero();
this._lastRotationQuaternion = new Quaternion();
/** @internal */
this.position = _SpatialAudioDefaults.position.clone();
/** @internal */
this.rotation = _SpatialAudioDefaults.rotation.clone();
/** @internal */
this.rotationQuaternion = _SpatialAudioDefaults.rotationQuaternion.clone();
this.node = new PannerNode(engine._audioContext);
this._orientationX = new _WebAudioParameterComponent(engine, this.node.orientationX);
this._orientationY = new _WebAudioParameterComponent(engine, this.node.orientationY);
this._orientationZ = new _WebAudioParameterComponent(engine, this.node.orientationZ);
this._positionX = new _WebAudioParameterComponent(engine, this.node.positionX);
this._positionY = new _WebAudioParameterComponent(engine, this.node.positionY);
this._positionZ = new _WebAudioParameterComponent(engine, this.node.positionZ);
}
/** @internal */
dispose() {
super.dispose();
this._orientationX.dispose();
this._orientationY.dispose();
this._orientationZ.dispose();
this._positionX.dispose();
this._positionY.dispose();
this._positionZ.dispose();
this.node.disconnect();
}
/** @internal */
get coneInnerAngle() {
return D2r(this.node.coneInnerAngle);
}
set coneInnerAngle(value) {
this.node.coneInnerAngle = R2d(value);
}
/** @internal */
get coneOuterAngle() {
return D2r(this.node.coneOuterAngle);
}
set coneOuterAngle(value) {
this.node.coneOuterAngle = R2d(value);
}
/** @internal */
get coneOuterVolume() {
return this.node.coneOuterGain;
}
set coneOuterVolume(value) {
this.node.coneOuterGain = value;
}
/** @internal */
get distanceModel() {
return this.node.distanceModel;
}
set distanceModel(value) {
this.node.distanceModel = value;
// Wiggle the max distance to make the change take effect.
const maxDistance = this.node.maxDistance;
this.node.maxDistance = maxDistance + 0.001;
this.node.maxDistance = maxDistance;
}
/** @internal */
get minDistance() {
return this.node.refDistance;
}
set minDistance(value) {
this.node.refDistance = value;
}
/** @internal */
get maxDistance() {
return this.node.maxDistance;
}
set maxDistance(value) {
this.node.maxDistance = value;
}
/** @internal */
get panningModel() {
return this.node.panningModel;
}
set panningModel(value) {
this.node.panningModel = value;
}
/** @internal */
get rolloffFactor() {
return this.node.rolloffFactor;
}
set rolloffFactor(value) {
this.node.rolloffFactor = value;
}
/** @internal */
get _inNode() {
return this.node;
}
/** @internal */
get _outNode() {
return this.node;
}
/** @internal */
_updatePosition() {
if (this._lastPosition.equalsWithEpsilon(this.position)) {
return;
}
// If attached and there is a ramp in progress, we assume another update is coming soon that we can wait for.
// We don't do this for unattached nodes because there may not be another update coming.
if (this.isAttached && (this._positionX.isRamping || this._positionY.isRamping || this._positionZ.isRamping)) {
return;
}
this._positionX.targetValue = this.position.x;
this._positionY.targetValue = this.position.y;
this._positionZ.targetValue = this.position.z;
this._lastPosition.copyFrom(this.position);
}
/** @internal */
_updateRotation() {
// If attached and there is a ramp in progress, we assume another update is coming soon that we can wait for.
// We don't do this for unattached nodes because there may not be another update coming.
if (this.isAttached && (this._orientationX.isRamping || this._orientationY.isRamping || this._orientationZ.isRamping)) {
return;
}
if (!this._lastRotationQuaternion.equalsWithEpsilon(this.rotationQuaternion)) {
TmpQuaternion.copyFrom(this.rotationQuaternion);
this._lastRotationQuaternion.copyFrom(this.rotationQuaternion);
}
else if (!this._lastRotation.equalsWithEpsilon(this.rotation)) {
Quaternion.FromEulerAnglesToRef(this.rotation.x, this.rotation.y, this.rotation.z, TmpQuaternion);
this._lastRotation.copyFrom(this.rotation);
}
else {
return;
}
Matrix.FromQuaternionToRef(TmpQuaternion, TmpMatrix);
Vector3.TransformNormalToRef(Vector3.RightReadOnly, TmpMatrix, TmpVector);
this._orientationX.targetValue = TmpVector.x;
this._orientationY.targetValue = TmpVector.y;
this._orientationZ.targetValue = TmpVector.z;
}
_connect(node) {
const connected = super._connect(node);
if (!connected) {
return false;
}
// If the wrapped node is not available now, it will be connected later by the subgraph.
if (node._inNode) {
this.node.connect(node._inNode);
}
return true;
}
_disconnect(node) {
const disconnected = super._disconnect(node);
if (!disconnected) {
return false;
}
if (node._inNode) {
this.node.disconnect(node._inNode);
}
return true;
}
/** @internal */
getClassName() {
return "_SpatialWebAudioSubNode";
}
}
/** @internal */
// eslint-disable-next-line @typescript-eslint/require-await
async function _CreateStereoAudioSubNodeAsync(engine) {
return new _StereoWebAudioSubNode(engine);
}
/** @internal */
class _StereoWebAudioSubNode extends _StereoAudioSubNode {
/** @internal */
constructor(engine) {
super(engine);
this.node = new StereoPannerNode(engine._audioContext);
this._pan = new _WebAudioParameterComponent(engine, this.node.pan);
}
/** @internal */
dispose() {
super.dispose();
this._pan.dispose();
}
/** @internal */
get pan() {
return this._pan.targetValue;
}
/** @internal */
set pan(value) {
this._pan.targetValue = value;
}
/** @internal */
get _inNode() {
return this.node;
}
/** @internal */
get _outNode() {
return this.node;
}
/** @internal */
getClassName() {
return "_StereoWebAudioSubNode";
}
_connect(node) {
const connected = super._connect(node);
if (!connected) {
return false;
}
// If the wrapped node is not available now, it will be connected later by the subgraph.
if (node._inNode) {
this.node.connect(node._inNode);
}
return true;
}
_disconnect(node) {
const disconnected = super._disconnect(node);
if (!disconnected) {
return false;
}
if (node._inNode) {
this.node.disconnect(node._inNode);
}
return true;
}
}
/** @internal */
class _WebAudioBusAndSoundSubGraph extends _WebAudioBaseSubGraph {
constructor() {
super(...arguments);
this._rootNode = null;
this._inputNode = null;
}
/** @internal */
async initAsync(options) {
await super.initAsync(options);
let hasSpatialOptions = false;
let hasStereoOptions = false;
if ((hasSpatialOptions = _HasSpatialAudioOptions(options))) {
await this.createAndAddSubNodeAsync("Spatial" /* AudioSubNode.SPATIAL */);
}
if ((hasStereoOptions = _HasStereoAudioOptions(options))) {
await this.createAndAddSubNodeAsync("Stereo" /* AudioSubNode.STEREO */);
}
await this._createSubNodePromisesResolvedAsync();
if (hasSpatialOptions) {
_GetSpatialAudioSubNode(this)?.setOptions(options);
}
if (hasStereoOptions) {
_GetStereoAudioSubNode(this)?.setOptions(options);
}
}
/** @internal */
get _inNode() {
return this._inputNode;
}
// eslint-disable-next-line @typescript-eslint/promise-function-async
_createSubNode(name) {
try {
const node = super._createSubNode(name);
return node;
}
catch (e) { }
switch (name) {
case "Spatial" /* AudioSubNode.SPATIAL */:
return _CreateSpatialAudioSubNodeAsync(this._owner.engine);
case "Stereo" /* AudioSubNode.STEREO */:
return _CreateStereoAudioSubNodeAsync(this._owner.engine);
default:
throw new Error(`Unknown subnode name: ${name}`);
}
}
_onSubNodesChanged() {
super._onSubNodesChanged();
const spatialNode = _GetSpatialAudioSubNode(this);
const stereoNode = _GetStereoAudioSubNode(this);
const volumeNode = _GetVolumeAudioSubNode(this);
if (spatialNode && spatialNode.getClassName() !== "_SpatialWebAudioSubNode") {
throw new Error("Not a WebAudio subnode.");
}
if (stereoNode && stereoNode.getClassName() !== "_StereoWebAudioSubNode") {
throw new Error("Not a WebAudio subnode.");
}
if (volumeNode && volumeNode.getClassName() !== "_VolumeWebAudioSubNode") {
throw new Error("Not a WebAudio subnode.");
}
if (spatialNode) {
spatialNode.disconnectAll();
if (volumeNode) {
spatialNode.connect(volumeNode);
}
}
if (stereoNode) {
stereoNode.disconnectAll();
if (volumeNode) {
stereoNode.connect(volumeNode);
}
}
if (spatialNode && stereoNode) {
this._rootNode = new GainNode(this._owner.engine._audioContext);
this._rootNode.connect(spatialNode._outNode);
this._rootNode.connect(stereoNode._outNode);
}
else {
this._rootNode?.disconnect();
this._rootNode = null;
}
let inSubNode = null;
let inNode = null;
if (this._rootNode) {
inNode = this._rootNode;
}
else {
if (spatialNode) {
inSubNode = spatialNode;
}
else if (stereoNode) {
inSubNode = stereoNode;
}
else if (volumeNode) {
inSubNode = volumeNode;
}
inNode = inSubNode?.node ?? null;
}
if (this._inputNode !== inNode) {
// Disconnect the wrapped upstream WebAudio nodes from the old wrapped WebAudio node.
// The wrapper nodes are unaware of this change.
if (this._inputNode && this._upstreamNodes) {
const it = this._upstreamNodes.values();
for (let next = it.next(); !next.done; next = it.next()) {
next.value._outNode?.disconnect(this._inputNode);
}
}
this._inputNode = inNode;
// Connect the wrapped upstream WebAudio nodes to the new wrapped WebAudio node.
// The wrapper nodes are unaware of this change.
if (inNode && this._upstreamNodes) {
const it = this._upstreamNodes.values();
for (let next = it.next(); !next.done; next = it.next()) {
next.value._outNode?.connect(inNode);
}
}
}
}
}
/** @internal */
class _SpatialAudio extends AbstractSpatialAudio {
/** @internal */
constructor(subGraph) {
super();
this._coneInnerAngle = _SpatialAudioDefaults.coneInnerAngle;
this._coneOuterAngle = _SpatialAudioDefaults.coneOuterAngle;
this._coneOuterVolume = _SpatialAudioDefaults.coneOuterVolume;
this._distanceModel = _SpatialAudioDefaults.distanceModel;
this._maxDistance = _SpatialAudioDefaults.maxDistance;
this._minDistance = _SpatialAudioDefaults.minDistance;
this._panningModel = _SpatialAudioDefaults.panningModel;
this._rolloffFactor = _SpatialAudioDefaults.rolloffFactor;
const subNode = _GetSpatialAudioSubNode(subGraph);
if (subNode) {
this._position = subNode.position.clone();
this._rotation = subNode.rotation.clone();
this._rotationQuaternion = subNode.rotationQuaternion.clone();
}
else {
this._position = _SpatialAudioDefaults.position.clone();
this._rotation = _SpatialAudioDefaults.rotation.clone();
this._rotationQuaternion = _SpatialAudioDefaults.rotationQuaternion.clone();
// eslint-disable-next-line @typescript-eslint/no-floating-promises
subGraph.createAndAddSubNodeAsync("Spatial" /* AudioSubNode.SPATIAL */);
}
this._subGraph = subGraph;
}
/** @internal */
get coneInnerAngle() {
return this._coneInnerAngle;
}
set coneInnerAngle(value) {
this._coneInnerAngle = value;
_SetSpatialAudioProperty(this._subGraph, "coneInnerAngle", value);
}
/** @internal */
get coneOuterAngle() {
return this._coneOuterAngle;
}
set coneOuterAngle(value) {
this._coneOuterAngle = value;
_SetSpatialAudioProperty(this._subGraph, "coneOuterAngle", value);
}
/** @internal */
get coneOuterVolume() {
return this._coneOuterVolume;
}
set coneOuterVolume(value) {
this._coneOuterVolume = value;
_SetSpatialAudioProperty(this._subGraph, "coneOuterVolume", value);
}
/** @internal */
get distanceModel() {
return this._distanceModel;
}
set distanceModel(value) {
this._distanceModel = value;
_SetSpatialAudioProperty(this._subGraph, "distanceModel", value);
}
/** @internal */
get isAttached() {
return this._subGraph.getSubNode("Spatial" /* AudioSubNode.SPATIAL */)?.isAttached ?? false;
}
/** @internal */
get maxDistance() {
return this._maxDistance;
}
set maxDistance(value) {
if (value <= 0) {
value = 0.000001;
}
this._maxDistance = value;
_SetSpatialAudioProperty(this._subGraph, "maxDistance", value);
}
/** @internal */
get minDistance() {
return this._minDistance;
}
set minDistance(value) {
this._minDistance = value;
_SetSpatialAudioProperty(this._subGraph, "minDistance", value);
}
/** @internal */
get panningModel() {
return this._panningModel;
}
set panningModel(value) {
this._panningModel = value;
_SetSpatialAudioProperty(this._subGraph, "panningModel", value);
}
/** @internal */
get position() {
return this._position;
}
set position(value) {
this._position = value;
this._updatePosition();
}
/** @internal */
get rolloffFactor() {
return this._rolloffFactor;
}
set rolloffFactor(value) {
this._rolloffFactor = value;
_SetSpatialAudioProperty(this._subGraph, "rolloffFactor", value);
}
/** @internal */
get rotation() {
return this._rotation;
}
set rotation(value) {
this._rotation = value;
this._updateRotation();
}
/** @internal */
get rotationQuaternion() {
return this._rotationQuaternion;
}
set rotationQuaternion(value) {
this._rotationQuaternion = value;
this._updateRotation();
}
/**
* Attaches to a scene node.
*
* Detaches automatically before attaching to the given scene node.
* If `sceneNode` is `null` it is the same as calling `detach()`.
*
* @param sceneNode The scene node to attach to, or `null` to detach.
* @param useBoundingBox Whether to use the bounding box of the node for positioning. Defaults to `false`.
* @param attachmentType Whether to attach to the node's position and/or rotation. Defaults to `PositionAndRotation`.
*/
attach(sceneNode, useBoundingBox = false, attachmentType = 3 /* SpatialAudioAttachmentType.PositionAndRotation */) {
_GetSpatialAudioSubNode(this._subGraph)?.attach(sceneNode, useBoundingBox, attachmentType);
}
/**
* Detaches from the scene node if attached.
*/
detach() {
_GetSpatialAudioSubNode(this._subGraph)?.detach();
}
/** @internal */
update() {
const subNode = _GetSpatialAudioSubNode(this._subGraph);
if (!subNode) {
return;
}
if (subNode.isAttached) {
subNode.update();
}
else {
this._updatePosition(subNode);
this._updateRotation(subNode);
}
}
_updatePosition(subNode = null) {
if (!subNode) {
subNode = _GetSpatialAudioSubNode(this._subGraph);
if (!subNode) {
return;
}
}
const position = subNode.position;
if (!position.equalsWithEpsilon(this._position)) {
subNode.position.copyFrom(this._position);
subNode._updatePosition();
}
}
_updateRotation(subNode = null) {
if (!subNode) {
subNode = _GetSpatialAudioSubNode(this._subGraph);
if (!subNode) {
return;
}
}
if (!subNode.rotationQuaternion.equalsWithEpsilon(this._rotationQuaternion)) {
subNode.rotationQuaternion.copyFrom(this._rotationQuaternion);
subNode._updateRotation();
}
else if (!subNode.rotation.equalsWithEpsilon(this._rotation)) {
subNode.rotation.copyFrom(this._rotation);
subNode._updateRotation();
}
}
}
/** @internal */
class _SpatialWebAudio extends _SpatialAudio {
/** @internal */
constructor(subGraph, autoUpdate, minUpdateTime) {
super(subGraph);
this._updaterComponent = new _SpatialWebAudioUpdaterComponent(this, autoUpdate, minUpdateTime);
}
/** @internal */
get minUpdateTime() {
return this._updaterComponent.minUpdateTime;
}
/** @internal */
set minUpdateTime(value) {
this._updaterComponent.minUpdateTime = value;
}
/** @internal */
dispose() {
this._updaterComponent.dispose();
this._updaterComponent = null;
}
}
export { _HasSpatialAudioOptions as _, _StereoAudio as a, _SpatialWebAudio as b, _WebAudioBusAndSoundSubGraph as c };
//# sourceMappingURL=spatialWebAudio-BoQhANFp.esm.js.map