@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.
1,331 lines (1,314 loc) • 3.73 MB
JavaScript
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const MergedStore = {};
const DecoratorInitialStore = {};
/** @internal */
function GetDirectStore(target) {
const classKey = target.getClassName();
if (!DecoratorInitialStore[classKey]) {
DecoratorInitialStore[classKey] = {};
}
return DecoratorInitialStore[classKey];
}
/**
* @returns the list of properties flagged as serializable
* @param target host object
*/
function GetMergedStore(target) {
const classKey = target.getClassName();
if (MergedStore[classKey]) {
return MergedStore[classKey];
}
MergedStore[classKey] = {};
const store = MergedStore[classKey];
let currentTarget = target;
let currentKey = classKey;
while (currentKey) {
const initialStore = DecoratorInitialStore[currentKey];
for (const property in initialStore) {
store[property] = initialStore[property];
}
let parent;
let done = false;
do {
parent = Object.getPrototypeOf(currentTarget);
if (!parent.getClassName) {
done = true;
break;
}
if (parent.getClassName() !== currentKey) {
break;
}
currentTarget = parent;
} while (parent);
if (done) {
break;
}
currentKey = parent.getClassName();
currentTarget = parent;
}
return store;
}
function generateSerializableMember(type, sourceName) {
return (target, propertyKey) => {
const classStore = GetDirectStore(target);
if (!classStore[propertyKey]) {
classStore[propertyKey] = { type: type, sourceName: sourceName };
}
};
}
function generateExpandMember(setCallback, targetKey = null) {
return (target, propertyKey) => {
const key = targetKey || "_" + propertyKey;
Object.defineProperty(target, propertyKey, {
get: function () {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this[key];
},
set: function (value) {
// does this object (i.e. vector3) has an equals function? use it!
// Note - not using "with epsilon" here, it is expected te behave like the internal cache does.
if (typeof this[key]?.equals === "function") {
if (this[key].equals(value)) {
return;
}
}
if (this[key] === value) {
return;
}
this[key] = value;
target[setCallback].apply(this);
},
enumerable: true,
configurable: true,
});
};
}
function expandToProperty(callback, targetKey = null) {
return generateExpandMember(callback, targetKey);
}
function serialize(sourceName) {
return generateSerializableMember(0, sourceName); // value member
}
function serializeAsTexture(sourceName) {
return generateSerializableMember(1, sourceName); // texture member
}
function serializeAsColor3(sourceName) {
return generateSerializableMember(2, sourceName); // color3 member
}
function serializeAsFresnelParameters(sourceName) {
return generateSerializableMember(3, sourceName); // fresnel parameters member
}
function serializeAsVector2(sourceName) {
return generateSerializableMember(4, sourceName); // vector2 member
}
function serializeAsVector3(sourceName) {
return generateSerializableMember(5, sourceName); // vector3 member
}
function serializeAsMeshReference(sourceName) {
return generateSerializableMember(6, sourceName); // mesh reference member
}
function serializeAsColorCurves(sourceName) {
return generateSerializableMember(7, sourceName); // color curves
}
function serializeAsColor4(sourceName) {
return generateSerializableMember(8, sourceName); // color 4
}
function serializeAsImageProcessingConfiguration(sourceName) {
return generateSerializableMember(9, sourceName); // image processing
}
function serializeAsQuaternion(sourceName) {
return generateSerializableMember(10, sourceName); // quaternion member
}
function serializeAsMatrix(sourceName) {
return generateSerializableMember(12, sourceName); // matrix member
}
/**
* Decorator used to redirect a function to a native implementation if available.
* @internal
*/
function nativeOverride(target, propertyKey, descriptor, predicate) {
// Cache the original JS function for later.
const jsFunc = descriptor.value;
// Override the JS function to check for a native override on first invocation. Setting descriptor.value overrides the function at the early stage of code being loaded/imported.
descriptor.value = (...params) => {
// Assume the resolved function will be the original JS function, then we will check for the Babylon Native context.
let func = jsFunc;
// Check if we are executing in a Babylon Native context (e.g. check the presence of the _native global property) and if so also check if a function override is available.
if (typeof _native !== "undefined" && _native[propertyKey]) {
const nativeFunc = _native[propertyKey];
// If a predicate was provided, then we'll need to invoke the predicate on each invocation of the underlying function to determine whether to call the native function or the JS function.
if (predicate) {
// The resolved function will execute the predicate and then either execute the native function or the JS function.
func = (...params) => (predicate(...params) ? nativeFunc(...params) : jsFunc(...params));
}
else {
// The resolved function will directly execute the native function.
func = nativeFunc;
}
}
// Override the JS function again with the final resolved target function.
target[propertyKey] = func;
// The JS function has now been overridden based on whether we're executing in the context of Babylon Native, but we still need to invoke that function.
// Future invocations of the function will just directly invoke the final overridden function, not any of the decorator setup logic above.
return func(...params);
};
}
/**
* Decorator factory that applies the nativeOverride decorator, but determines whether to redirect to the native implementation based on a filter function that evaluates the function arguments.
* @param predicate
* @example @nativeOverride.filter((...[arg1]: Parameters<typeof someClass.someMethod>) => arg1.length > 20)
* public someMethod(arg1: string, arg2: number): string {
* @internal
*/
nativeOverride.filter = function (predicate) {
return (target, propertyKey, descriptor) => nativeOverride(target, propertyKey, descriptor, predicate);
};
const IsWeakRefSupported = typeof WeakRef !== "undefined";
/**
* A class serves as a medium between the observable and its observers
*/
class EventState {
/**
* Create a new EventState
* @param mask defines the mask associated with this state
* @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
* @param target defines the original target of the state
* @param currentTarget defines the current target of the state
*/
constructor(mask, skipNextObservers = false, target, currentTarget) {
this.initialize(mask, skipNextObservers, target, currentTarget);
}
/**
* Initialize the current event state
* @param mask defines the mask associated with this state
* @param skipNextObservers defines a flag which will instruct the observable to skip following observers when set to true
* @param target defines the original target of the state
* @param currentTarget defines the current target of the state
* @returns the current event state
*/
initialize(mask, skipNextObservers = false, target, currentTarget) {
this.mask = mask;
this.skipNextObservers = skipNextObservers;
this.target = target;
this.currentTarget = currentTarget;
return this;
}
}
/**
* Represent an observer registered to a given Observable object.
*/
class Observer {
/**
* Creates a new observer
* @param callback defines the callback to call when the observer is notified
* @param mask defines the mask of the observer (used to filter notifications)
* @param scope defines the current scope used to restore the JS context
*/
constructor(
/**
* Defines the callback to call when the observer is notified
*/
callback,
/**
* Defines the mask of the observer (used to filter notifications)
*/
mask,
/**
* [null] Defines the current scope used to restore the JS context
*/
scope = null) {
this.callback = callback;
this.mask = mask;
this.scope = scope;
/** @internal */
this._willBeUnregistered = false;
/**
* Gets or sets a property defining that the observer as to be unregistered after the next notification
*/
this.unregisterOnNextCall = false;
/**
* this function can be used to remove the observer from the observable.
* It will be set by the observable that the observer belongs to.
* @internal
*/
this._remove = null;
}
/**
* Remove the observer from its observable
* This can be used instead of using the observable's remove function.
* @param defer if true, the removal will be deferred to avoid callback skipping (default: false)
*/
remove(defer = false) {
if (this._remove) {
this._remove(defer);
}
}
}
/**
* The Observable class is a simple implementation of the Observable pattern.
*
* There's one slight particularity though: a given Observable can notify its observer using a particular mask value, only the Observers registered with this mask value will be notified.
* This enable a more fine grained execution without having to rely on multiple different Observable objects.
* For instance you may have a given Observable that have four different types of notifications: Move (mask = 0x01), Stop (mask = 0x02), Turn Right (mask = 0X04), Turn Left (mask = 0X08).
* A given observer can register itself with only Move and Stop (mask = 0x03), then it will only be notified when one of these two occurs and will never be for Turn Left/Right.
*/
class Observable {
/**
* Create an observable from a Promise.
* @param promise a promise to observe for fulfillment.
* @param onErrorObservable an observable to notify if a promise was rejected.
* @returns the new Observable
*/
static FromPromise(promise, onErrorObservable) {
const observable = new Observable();
promise
// eslint-disable-next-line github/no-then
.then((ret) => {
observable.notifyObservers(ret);
})
// eslint-disable-next-line github/no-then
.catch((err) => {
if (onErrorObservable) {
onErrorObservable.notifyObservers(err);
}
else {
throw err;
}
});
return observable;
}
/**
* Gets the list of observers
* Note that observers that were recently deleted may still be present in the list because they are only really deleted on the next javascript tick!
*/
get observers() {
return this._observers;
}
/**
* Creates a new observable
* @param onObserverAdded defines a callback to call when a new observer is added
* @param notifyIfTriggered If set to true the observable will notify when an observer was added if the observable was already triggered.
*/
constructor(onObserverAdded,
/**
* [false] If set to true the observable will notify when an observer was added if the observable was already triggered.
* This is helpful to single-state observables like the scene onReady or the dispose observable.
*/
notifyIfTriggered = false) {
this.notifyIfTriggered = notifyIfTriggered;
this._observers = new Array();
this._numObserversMarkedAsDeleted = 0;
this._hasNotified = false;
this._eventState = new EventState(0);
if (onObserverAdded) {
this._onObserverAdded = onObserverAdded;
}
}
add(callback, mask = -1, insertFirst = false, scope = null, unregisterOnFirstCall = false) {
if (!callback) {
return null;
}
const observer = new Observer(callback, mask, scope);
observer.unregisterOnNextCall = unregisterOnFirstCall;
if (insertFirst) {
this._observers.unshift(observer);
}
else {
this._observers.push(observer);
}
if (this._onObserverAdded) {
this._onObserverAdded(observer);
}
// If the observable was already triggered and the observable is set to notify if triggered, notify the new observer
if (this._hasNotified && this.notifyIfTriggered) {
if (this._lastNotifiedValue !== undefined) {
this.notifyObserver(observer, this._lastNotifiedValue);
}
}
// attach the remove function to the observer
const observableWeakRef = IsWeakRefSupported ? new WeakRef(this) : { deref: () => this };
observer._remove = (defer = false) => {
const observable = observableWeakRef.deref();
if (observable) {
defer ? observable.remove(observer) : observable._remove(observer);
}
};
return observer;
}
addOnce(callback) {
return this.add(callback, undefined, undefined, undefined, true);
}
/**
* Remove an Observer from the Observable object
* @param observer the instance of the Observer to remove
* @returns false if it doesn't belong to this Observable
*/
remove(observer) {
if (!observer) {
return false;
}
observer._remove = null;
const index = this._observers.indexOf(observer);
if (index !== -1) {
this._deferUnregister(observer);
return true;
}
return false;
}
/**
* Remove a callback from the Observable object
* @param callback the callback to remove
* @param scope optional scope. If used only the callbacks with this scope will be removed
* @returns false if it doesn't belong to this Observable
*/
removeCallback(callback, scope) {
for (let index = 0; index < this._observers.length; index++) {
const observer = this._observers[index];
if (observer._willBeUnregistered) {
continue;
}
if (observer.callback === callback && (!scope || scope === observer.scope)) {
this._deferUnregister(observer);
return true;
}
}
return false;
}
/**
* @internal
*/
_deferUnregister(observer) {
if (observer._willBeUnregistered) {
return;
}
this._numObserversMarkedAsDeleted++;
observer.unregisterOnNextCall = false;
observer._willBeUnregistered = true;
setTimeout(() => {
this._remove(observer);
}, 0);
}
// This should only be called when not iterating over _observers to avoid callback skipping.
// Removes an observer from the _observer Array.
_remove(observer, updateCounter = true) {
if (!observer) {
return false;
}
const index = this._observers.indexOf(observer);
if (index !== -1) {
if (updateCounter) {
this._numObserversMarkedAsDeleted--;
}
this._observers.splice(index, 1);
return true;
}
return false;
}
/**
* Moves the observable to the top of the observer list making it get called first when notified
* @param observer the observer to move
*/
makeObserverTopPriority(observer) {
this._remove(observer, false);
this._observers.unshift(observer);
}
/**
* Moves the observable to the bottom of the observer list making it get called last when notified
* @param observer the observer to move
*/
makeObserverBottomPriority(observer) {
this._remove(observer, false);
this._observers.push(observer);
}
/**
* Notify all Observers by calling their respective callback with the given data
* Will return true if all observers were executed, false if an observer set skipNextObservers to true, then prevent the subsequent ones to execute
* @param eventData defines the data to send to all observers
* @param mask defines the mask of the current notification (observers with incompatible mask (ie mask & observer.mask === 0) will not be notified)
* @param target defines the original target of the state
* @param currentTarget defines the current target of the state
* @param userInfo defines any user info to send to observers
* @returns false if the complete observer chain was not processed (because one observer set the skipNextObservers to true)
*/
notifyObservers(eventData, mask = -1, target, currentTarget, userInfo) {
// this prevents potential memory leaks - if an object is disposed but the observable doesn't get cleared.
if (this.notifyIfTriggered) {
this._hasNotified = true;
this._lastNotifiedValue = eventData;
}
if (!this._observers.length) {
return true;
}
const state = this._eventState;
state.mask = mask;
state.target = target;
state.currentTarget = currentTarget;
state.skipNextObservers = false;
state.lastReturnValue = eventData;
state.userInfo = userInfo;
for (const obs of this._observers) {
if (obs._willBeUnregistered) {
continue;
}
if (obs.mask & mask) {
if (obs.unregisterOnNextCall) {
this._deferUnregister(obs);
}
if (obs.scope) {
state.lastReturnValue = obs.callback.apply(obs.scope, [eventData, state]);
}
else {
state.lastReturnValue = obs.callback(eventData, state);
}
}
if (state.skipNextObservers) {
return false;
}
}
return true;
}
/**
* Notify a specific observer
* @param observer defines the observer to notify
* @param eventData defines the data to be sent to each callback
* @param mask is used to filter observers defaults to -1
*/
notifyObserver(observer, eventData, mask = -1) {
// this prevents potential memory leaks - if an object is disposed but the observable doesn't get cleared.
if (this.notifyIfTriggered) {
this._hasNotified = true;
this._lastNotifiedValue = eventData;
}
if (observer._willBeUnregistered) {
return;
}
const state = this._eventState;
state.mask = mask;
state.skipNextObservers = false;
if (observer.unregisterOnNextCall) {
this._deferUnregister(observer);
}
observer.callback(eventData, state);
}
/**
* Gets a boolean indicating if the observable has at least one observer
* @returns true is the Observable has at least one Observer registered
*/
hasObservers() {
return this._observers.length - this._numObserversMarkedAsDeleted > 0;
}
/**
* Clear the list of observers
*/
clear() {
while (this._observers.length) {
const o = this._observers.pop();
if (o) {
o._remove = null;
}
}
this._onObserverAdded = null;
this._numObserversMarkedAsDeleted = 0;
this.cleanLastNotifiedState();
}
/**
* Clean the last notified state - both the internal last value and the has-notified flag
*/
cleanLastNotifiedState() {
this._hasNotified = false;
this._lastNotifiedValue = undefined;
}
/**
* Clone the current observable
* @returns a new observable
*/
clone() {
const result = new Observable();
result._observers = this._observers.slice(0);
return result;
}
/**
* Does this observable handles observer registered with a given mask
* @param mask defines the mask to be tested
* @returns whether or not one observer registered with the given mask is handled
**/
hasSpecificMask(mask = -1) {
for (const obs of this._observers) {
if (obs.mask & mask || obs.mask === mask) {
return true;
}
}
return false;
}
}
/**
* Constant used to convert a value to gamma space
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
const ToGammaSpace = 1 / 2.2;
/**
* Constant used to convert a value to linear space
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
const ToLinearSpace = 2.2;
/**
* Constant used to define the minimal number value in Babylon.js
* @ignorenaming
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
const Epsilon = 0.001;
/* eslint-disable @typescript-eslint/naming-convention */
/**
* Returns an array of the given size filled with elements built from the given constructor and the parameters.
* @param size the number of element to construct and put in the array.
* @param itemBuilder a callback responsible for creating new instance of item. Called once per array entry.
* @returns a new array filled with new objects.
*/
function BuildArray(size, itemBuilder) {
const a = [];
for (let i = 0; i < size; ++i) {
a.push(itemBuilder());
}
return a;
}
/**
* Returns a tuple of the given size filled with elements built from the given constructor and the parameters.
* @param size he number of element to construct and put in the tuple.
* @param itemBuilder a callback responsible for creating new instance of item. Called once per tuple entry.
* @returns a new tuple filled with new objects.
*/
function BuildTuple(size, itemBuilder) {
return BuildArray(size, itemBuilder);
}
/**
* Observes a function and calls the given callback when it is called.
* @param object Defines the object the function to observe belongs to.
* @param functionName Defines the name of the function to observe.
* @param callback Defines the callback to call when the function is called.
* @returns A function to call to stop observing
*/
function ObserveArrayFunction(object, functionName, callback) {
// Finds the function to observe
const oldFunction = object[functionName];
if (typeof oldFunction !== "function") {
return null;
}
// Creates a new function that calls the callback and the old function
const newFunction = function () {
const previousLength = object.length;
const returnValue = newFunction.previous.apply(object, arguments);
callback(functionName, previousLength);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return returnValue;
};
// Doublishly links the new function and the old function
oldFunction.next = newFunction;
newFunction.previous = oldFunction;
// Replaces the old function with the new function
object[functionName] = newFunction;
// Returns a function to disable the hook
return () => {
// Only unhook if the function is still hooked
const previous = newFunction.previous;
if (!previous) {
return;
}
// Finds the ref to the next function in the chain
const next = newFunction.next;
// If in the middle of the chain, link the previous and next functions
if (next) {
previous.next = next;
next.previous = previous;
}
// If at the end of the chain, remove the reference to the previous function
// and restore the previous function
else {
previous.next = undefined;
object[functionName] = previous;
}
// Lose reference to the previous and next functions
newFunction.next = undefined;
newFunction.previous = undefined;
};
}
/**
* Defines the list of functions to proxy when observing an array.
* The scope is currently reduced to the common functions used in the render target render list and the scene cameras.
*/
const observedArrayFunctions = ["push", "splice", "pop", "shift", "unshift"];
/**
* Observes an array and notifies the given observer when the array is modified.
* @param array Defines the array to observe
* @param callback Defines the function to call when the array is modified (in the limit of the observed array functions)
* @returns A function to call to stop observing the array
* @internal
*/
function _ObserveArray(array, callback) {
// Observes all the required array functions and stores the unhook functions
const unObserveFunctions = observedArrayFunctions.map((name) => {
return ObserveArrayFunction(array, name, callback);
});
// Returns a function that unhook all the observed functions
return () => {
for (const unObserveFunction of unObserveFunctions) {
unObserveFunction?.();
}
};
}
/** @internal */
// eslint-disable-next-line @typescript-eslint/naming-convention
const RegisteredTypes = {};
/**
* @internal
*/
function RegisterClass(className, type) {
RegisteredTypes[className] = type;
}
/**
* @internal
*/
function GetClass(fqdn) {
return RegisteredTypes[fqdn];
}
/** @internal */
class PerformanceConfigurator {
/**
* @internal
*/
static SetMatrixPrecision(use64bits) {
PerformanceConfigurator.MatrixTrackPrecisionChange = false;
if (use64bits && !PerformanceConfigurator.MatrixUse64Bits) {
if (PerformanceConfigurator.MatrixTrackedMatrices) {
for (let m = 0; m < PerformanceConfigurator.MatrixTrackedMatrices.length; ++m) {
const matrix = PerformanceConfigurator.MatrixTrackedMatrices[m];
const values = matrix._m;
matrix._m = new Array(16);
for (let i = 0; i < 16; ++i) {
matrix._m[i] = values[i];
}
}
}
}
PerformanceConfigurator.MatrixUse64Bits = use64bits;
PerformanceConfigurator.MatrixCurrentType = PerformanceConfigurator.MatrixUse64Bits ? Array : Float32Array;
PerformanceConfigurator.MatrixTrackedMatrices = null; // reclaim some memory, as we don't need _TrackedMatrices anymore
}
}
/** @internal */
PerformanceConfigurator.MatrixUse64Bits = false;
/** @internal */
PerformanceConfigurator.MatrixTrackPrecisionChange = true;
/** @internal */
PerformanceConfigurator.MatrixCurrentType = Float32Array;
/** @internal */
PerformanceConfigurator.MatrixTrackedMatrices = [];
/**
* The engine store class is responsible to hold all the instances of Engine and Scene created
* during the life time of the application.
*/
class EngineStore {
/**
* Gets the latest created engine
*/
static get LastCreatedEngine() {
if (this.Instances.length === 0) {
return null;
}
return this.Instances[this.Instances.length - 1];
}
/**
* Gets the latest created scene
*/
static get LastCreatedScene() {
return this._LastCreatedScene;
}
}
/** Gets the list of created engines */
EngineStore.Instances = [];
/**
* Notifies when an engine was disposed.
* Mainly used for static/cache cleanup
*/
EngineStore.OnEnginesDisposedObservable = new Observable();
/** @internal */
EngineStore._LastCreatedScene = null;
/**
* Gets or sets a global variable indicating if fallback texture must be used when a texture cannot be loaded
*/
EngineStore.UseFallbackTexture = true;
/**
* Texture content used if a texture cannot loaded
*/
EngineStore.FallbackTexture = "";
/**
* Extract int value
* @param value number value
* @returns int value
*/
function ExtractAsInt$1(value) {
return parseInt(value.toString().replace(/\W/g, ""));
}
/**
* Boolean : true if the absolute difference between a and b is lower than epsilon (default = 1.401298E-45)
* @param a number
* @param b number
* @param epsilon (default = 1.401298E-45)
* @returns true if the absolute difference between a and b is lower than epsilon (default = 1.401298E-45)
*/
function WithinEpsilon(a, b, epsilon = 1.401298e-45) {
return Math.abs(a - b) <= epsilon;
}
/**
* Boolean : true if the number is outside a range
* @param num number
* @param min min value
* @param max max value
* @param epsilon (default = Number.EPSILON)
* @returns true if the number is between min and max values
*/
function OutsideRange(num, min, max, epsilon = 1.401298e-45) {
return num < min - epsilon || num > max + epsilon;
}
/**
* Returns a random float number between and min and max values
* @param min min value of random
* @param max max value of random
* @returns random value
*/
function RandomRange(min, max) {
if (min === max) {
return min;
}
return Math.random() * (max - min) + min;
}
/**
* Creates a new scalar with values linearly interpolated of "amount" between the start scalar and the end scalar.
* @param start start value
* @param end target value
* @param amount amount to lerp between
* @returns the lerped value
*/
function Lerp(start, end, amount) {
return start + (end - start) * amount;
}
/**
* Same as Lerp but makes sure the values interpolate correctly when they wrap around 360 degrees.
* The parameter t is clamped to the range [0, 1]. Variables a and b are assumed to be in degrees.
* @param start start value
* @param end target value
* @param amount amount to lerp between
* @returns the lerped value
*/
function LerpAngle(start, end, amount) {
let num = Repeat(end - start, 360.0);
if (num > 180.0) {
num -= 360.0;
}
return start + num * Clamp(amount);
}
/**
* Calculates the linear parameter t that produces the interpolant value within the range [a, b].
* @param a start value
* @param b target value
* @param value value between a and b
* @returns the inverseLerp value
*/
function InverseLerp(a, b, value) {
let result = 0;
if (a != b) {
result = Clamp((value - a) / (b - a));
}
else {
result = 0.0;
}
return result;
}
/**
* Returns a new scalar located for "amount" (float) on the Hermite spline defined by the scalars "value1", "value3", "tangent1", "tangent2".
* @see http://mathworld.wolfram.com/HermitePolynomial.html
* @param value1 defines the first control point
* @param tangent1 defines the first tangent
* @param value2 defines the second control point
* @param tangent2 defines the second tangent
* @param amount defines the amount on the interpolation spline (between 0 and 1)
* @returns hermite result
*/
function Hermite(value1, tangent1, value2, tangent2, amount) {
const squared = amount * amount;
const cubed = amount * squared;
const part1 = 2.0 * cubed - 3.0 * squared + 1.0;
const part2 = -2 * cubed + 3.0 * squared;
const part3 = cubed - 2.0 * squared + amount;
const part4 = cubed - squared;
return value1 * part1 + value2 * part2 + tangent1 * part3 + tangent2 * part4;
}
/**
* Returns a new scalar which is the 1st derivative of the Hermite spline defined by the scalars "value1", "value2", "tangent1", "tangent2".
* @param value1 defines the first control point
* @param tangent1 defines the first tangent
* @param value2 defines the second control point
* @param tangent2 defines the second tangent
* @param time define where the derivative must be done
* @returns 1st derivative
*/
function Hermite1stDerivative(value1, tangent1, value2, tangent2, time) {
const t2 = time * time;
return (t2 - time) * 6 * value1 + (3 * t2 - 4 * time + 1) * tangent1 + (-t2 + time) * 6 * value2 + (3 * t2 - 2 * time) * tangent2;
}
/**
* Returns the value itself if it's between min and max.
* Returns min if the value is lower than min.
* Returns max if the value is greater than max.
* @param value the value to clmap
* @param min the min value to clamp to (default: 0)
* @param max the max value to clamp to (default: 1)
* @returns the clamped value
*/
function Clamp(value, min = 0, max = 1) {
return Math.min(max, Math.max(min, value));
}
/**
* Returns the angle converted to equivalent value between -Math.PI and Math.PI radians.
* @param angle The angle to normalize in radian.
* @returns The converted angle.
*/
function NormalizeRadians(angle) {
// More precise but slower version kept for reference.
// angle = angle % Tools.TwoPi;
// angle = (angle + Tools.TwoPi) % Tools.TwoPi;
//if (angle > Math.PI) {
// angle -= Tools.TwoPi;
//}
angle -= Math.PI * 2 * Math.floor((angle + Math.PI) / (Math.PI * 2));
return angle;
}
/**
* Returns a string : the upper case translation of the number i to hexadecimal.
* @param i number
* @returns the upper case translation of the number i to hexadecimal.
*/
function ToHex(i) {
const str = i.toString(16);
if (i <= 15) {
return ("0" + str).toUpperCase();
}
return str.toUpperCase();
}
/**
* the floor part of a log2 value.
* @param value the value to compute log2 of
* @returns the log2 of value.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
function ILog2(value) {
if (Math.log2) {
return Math.floor(Math.log2(value));
}
if (value < 0) {
return NaN;
}
else if (value === 0) {
return -Infinity;
}
let n = 0;
if (value < 1) {
while (value < 1) {
n++;
value = value * 2;
}
n = -n;
}
else if (value > 1) {
while (value > 1) {
n++;
value = Math.floor(value / 2);
}
}
return n;
}
/**
* Loops the value, so that it is never larger than length and never smaller than 0.
*
* This is similar to the modulo operator but it works with floating point numbers.
* For example, using 3.0 for t and 2.5 for length, the result would be 0.5.
* With t = 5 and length = 2.5, the result would be 0.0.
* Note, however, that the behaviour is not defined for negative numbers as it is for the modulo operator
* @param value the value
* @param length the length
* @returns the looped value
*/
function Repeat(value, length) {
return value - Math.floor(value / length) * length;
}
/**
* Normalize the value between 0.0 and 1.0 using min and max values
* @param value value to normalize
* @param min max to normalize between
* @param max min to normalize between
* @returns the normalized value
*/
function Normalize(value, min, max) {
return (value - min) / (max - min);
}
/**
* Denormalize the value from 0.0 and 1.0 using min and max values
* @param normalized value to denormalize
* @param min max to denormalize between
* @param max min to denormalize between
* @returns the denormalized value
*/
function Denormalize(normalized, min, max) {
return normalized * (max - min) + min;
}
/**
* Calculates the shortest difference between two given angles given in degrees.
* @param current current angle in degrees
* @param target target angle in degrees
* @returns the delta
*/
function DeltaAngle(current, target) {
let num = Repeat(target - current, 360.0);
if (num > 180.0) {
num -= 360.0;
}
return num;
}
/**
* PingPongs the value t, so that it is never larger than length and never smaller than 0.
* @param tx value
* @param length length
* @returns The returned value will move back and forth between 0 and length
*/
function PingPong(tx, length) {
const t = Repeat(tx, length * 2.0);
return length - Math.abs(t - length);
}
/**
* Interpolates between min and max with smoothing at the limits.
*
* This function interpolates between min and max in a similar way to Lerp. However, the interpolation will gradually speed up
* from the start and slow down toward the end. This is useful for creating natural-looking animation, fading and other transitions.
* @param from from
* @param to to
* @param tx value
* @returns the smooth stepped value
*/
function SmoothStep(from, to, tx) {
let t = Clamp(tx);
t = -2 * t * t * t + 3.0 * t * t;
return to * t + from * (1.0 - t);
}
/**
* Moves a value current towards target.
*
* This is essentially the same as Mathf.Lerp but instead the function will ensure that the speed never exceeds maxDelta.
* Negative values of maxDelta pushes the value away from target.
* @param current current value
* @param target target value
* @param maxDelta max distance to move
* @returns resulting value
*/
function MoveTowards(current, target, maxDelta) {
let result = 0;
if (Math.abs(target - current) <= maxDelta) {
result = target;
}
else {
result = current + Math.sign(target - current) * maxDelta;
}
return result;
}
/**
* Same as MoveTowards but makes sure the values interpolate correctly when they wrap around 360 degrees.
*
* Variables current and target are assumed to be in degrees. For optimization reasons, negative values of maxDelta
* are not supported and may cause oscillation. To push current away from a target angle, add 180 to that angle instead.
* @param current current value
* @param target target value
* @param maxDelta max distance to move
* @returns resulting angle
*/
function MoveTowardsAngle(current, target, maxDelta) {
const num = DeltaAngle(current, target);
let result = 0;
if (-maxDelta < num && num < maxDelta) {
result = target;
}
else {
target = current + num;
result = MoveTowards(current, target, maxDelta);
}
return result;
}
/**
* This function returns percentage of a number in a given range.
*
* RangeToPercent(40,20,60) will return 0.5 (50%)
* RangeToPercent(34,0,100) will return 0.34 (34%)
* @param number to convert to percentage
* @param min min range
* @param max max range
* @returns the percentage
*/
function RangeToPercent(number, min, max) {
return (number - min) / (max - min);
}
/**
* This function returns number that corresponds to the percentage in a given range.
*
* PercentToRange(0.34,0,100) will return 34.
* @param percent to convert to number
* @param min min range
* @param max max range
* @returns the number
*/
function PercentToRange(percent, min, max) {
return (max - min) * percent + min;
}
/**
* Returns the highest common factor of two integers.
* @param a first parameter
* @param b second parameter
* @returns HCF of a and b
*/
function HighestCommonFactor(a, b) {
const r = a % b;
if (r === 0) {
return b;
}
return HighestCommonFactor(b, r);
}
var functions = /*#__PURE__*/Object.freeze({
__proto__: null,
Clamp: Clamp,
DeltaAngle: DeltaAngle,
Denormalize: Denormalize,
ExtractAsInt: ExtractAsInt$1,
Hermite: Hermite,
Hermite1stDerivative: Hermite1stDerivative,
HighestCommonFactor: HighestCommonFactor,
ILog2: ILog2,
InverseLerp: InverseLerp,
Lerp: Lerp,
LerpAngle: LerpAngle,
MoveTowards: MoveTowards,
MoveTowardsAngle: MoveTowardsAngle,
Normalize: Normalize,
NormalizeRadians: NormalizeRadians,
OutsideRange: OutsideRange,
PercentToRange: PercentToRange,
PingPong: PingPong,
RandomRange: RandomRange,
RangeToPercent: RangeToPercent,
Repeat: Repeat,
SmoothStep: SmoothStep,
ToHex: ToHex,
WithinEpsilon: WithinEpsilon
});
/** @internal */
class MatrixManagement {
}
/** @internal */
MatrixManagement._UpdateFlagSeed = 0;
/**
* Multiplies two matrices and stores the result in the target array.
* @param a defines the first matrix
* @param b defines the second matrix
* @param output defines the target array
* @param offset defines the offset in the target array where to store the result (0 by default)
*/
function MultiplyMatricesToArray(a, b, output, offset = 0) {
const m = a.asArray();
const otherM = b.asArray();
const tm0 = m[0], tm1 = m[1], tm2 = m[2], tm3 = m[3];
const tm4 = m[4], tm5 = m[5], tm6 = m[6], tm7 = m[7];
const tm8 = m[8], tm9 = m[9], tm10 = m[10], tm11 = m[11];
const tm12 = m[12], tm13 = m[13], tm14 = m[14], tm15 = m[15];
const om0 = otherM[0], om1 = otherM[1], om2 = otherM[2], om3 = otherM[3];
const om4 = otherM[4], om5 = otherM[5], om6 = otherM[6], om7 = otherM[7];
const om8 = otherM[8], om9 = otherM[9], om10 = otherM[10], om11 = otherM[11];
const om12 = otherM[12], om13 = otherM[13], om14 = otherM[14], om15 = otherM[15];
output[offset] = tm0 * om0 + tm1 * om4 + tm2 * om8 + tm3 * om12;
output[offset + 1] = tm0 * om1 + tm1 * om5 + tm2 * om9 + tm3 * om13;
output[offset + 2] = tm0 * om2 + tm1 * om6 + tm2 * om10 + tm3 * om14;
output[offset + 3] = tm0 * om3 + tm1 * om7 + tm2 * om11 + tm3 * om15;
output[offset + 4] = tm4 * om0 + tm5 * om4 + tm6 * om8 + tm7 * om12;
output[offset + 5] = tm4 * om1 + tm5 * om5 + tm6 * om9 + tm7 * om13;
output[offset + 6] = tm4 * om2 + tm5 * om6 + tm6 * om10 + tm7 * om14;
output[offset + 7] = tm4 * om3 + tm5 * om7 + tm6 * om11 + tm7 * om15;
output[offset + 8] = tm8 * om0 + tm9 * om4 + tm10 * om8 + tm11 * om12;
output[offset + 9] = tm8 * om1 + tm9 * om5 + tm10 * om9 + tm11 * om13;
output[offset + 10] = tm8 * om2 + tm9 * om6 + tm10 * om10 + tm11 * om14;
output[offset + 11] = tm8 * om3 + tm9 * om7 + tm10 * om11 + tm11 * om15;
output[offset + 12] = tm12 * om0 + tm13 * om4 + tm14 * om8 + tm15 * om12;
output[offset + 13] = tm12 * om1 + tm13 * om5 + tm14 * om9 + tm15 * om13;
output[offset + 14] = tm12 * om2 + tm13 * om6 + tm14 * om10 + tm15 * om14;
output[offset + 15] = tm12 * om3 + tm13 * om7 + tm14 * om11 + tm15 * om15;
}
/**
* Populates the given array from the starting index with the current matrix values
* @param matrix defines the source matrix
* @param array defines the target array
* @param offset defines the offset in the target array where to start storing values
*/
function CopyMatrixToArray(matrix, array, offset = 0) {
const source = matrix.asArray();
array[offset] = source[0];
array[offset + 1] = source[1];
array[offset + 2] = source[2];
array[offset + 3] = source[3];
array[offset + 4] = source[4];
array[offset + 5] = source[5];
array[offset + 6] = source[6];
array[offset + 7] = source[7];
array[offset + 8] = source[8];
array[offset + 9] = source[9];
array[offset + 10] = source[10];
array[offset + 11] = source[11];
array[offset + 12] = source[12];
array[offset + 13] = source[13];
array[offset + 14] = source[14];
array[offset + 15] = source[15];
}
/**
* Inverts the given matrix and stores the result in the target array
* @param source defines the source matrix
* @param target defines the target array
* @returns true if the matrix was inverted successfully, false otherwise
*/
function InvertMatrixToArray(source, target) {
// the inverse of a matrix is the transpose of cofactor matrix divided by the determinant
const m = source.asArray();
const m00 = m[0], m01 = m[1], m02 = m[2], m03 = m[3];
const m10 = m[4], m11 = m[5], m12 = m[6], m13 = m[7];
const m20 = m[8], m21 = m[9], m22 = m[10], m23 = m[11];
const m30 = m[12], m31 = m[13], m32 = m[14], m33 = m[15];
const det_22_33 = m22 * m33 - m32 * m23;
const det_21_33 = m21 * m33 - m31 * m23;
const det_21_32 = m21 * m32 - m31 * m22;
const det_20_33 = m20 * m33 - m30 * m23;
const det_20_32 = m20 * m32 - m22 * m30;
const det_20_31 = m20 * m31 - m30 * m21;
const cofact_00 = +(m11 * det_22_33 - m12 * det_21_33 + m13 * det_21_32);
const cofact_01 = -(m10 * det_22_33 - m12 * det_20_33 + m13 * det_20_32);
const cofact_02 = +(m10 * det_21_33 - m11 * det_20_33 + m13 * det_20_31);
const cofact_03 = -(m10 * det_21_32 - m11 * det_20_32 + m12 * det_20_31);
const det = m00 * cofact_00 + m01 * cofact_01 + m02 * cofact_02 + m03 * cofact_03;
if (det === 0) {
// Not invertible
return false;
}
const detInv = 1 / det;
const det_12_33 = m12 * m33 - m32 * m13;
const det_11_33 = m11 * m33 - m31 * m13;
const det_11_32 = m11 * m32 - m31 * m12;
const det_10_33 = m10 * m33 - m30 * m13;
const det_10_32 = m10 * m32 - m30 * m12;
const det_10_31 = m10 * m31 - m30 * m11;
const det_12_23 = m12 * m23 - m22 * m13;
const det_11_23 = m11 * m23 - m21 * m13;
const det_11_22 = m11 * m22 - m21 * m12;
const det_10_23 = m10 * m23 - m20 * m13;
const det_10_22 = m10 * m22 - m20 * m12;
const det_10_21 = m10 * m21 - m20 * m11;
const cofact_10 = -(m01 * det_22_33 - m02 * det_21_33 + m03 * det_21_32);
const cofact_11 = +(m00 * det_22_33 - m02 * det_20_33 + m03 * det_20_32);
const cofact_12 = -(m00 * det_21_33 - m01 * det_20_33 + m03 * det_20_31);
const cofact_13 = +(m00 * det_21_32 - m01 * det_20_32 + m02 * det_20_31);
const cofact_20 = +(m01 * det_12_33 - m02 * det_11_33 + m03 * det_11_32);
const cofact_21 = -(m00 * det_12_33 - m02 * det_10_33 + m03 * det_10_32);
const cofact_22 = +(m00 * det_11_33 - m01 * det_10_33 + m03 * det_10_31);
const cofact_23 = -(m00 * det_11_32 - m01 * det_10_32 + m02 * det_10_31);
const cofact_30 = -(m01 * det_12_23 - m02 * det_11_23 + m03 * det_11_22);
const cofact_31 = +(m00 * det_12_23 - m02 * det_10_23 + m03 * det_10_22);
const cofact_32 = -(m00 * det_11_23 - m01 * det_10_23 + m03 * det_10_21);
const cofact_33 = +(m00 * det_11_22 - m01 * det_10_22 + m02 * det_10_21);
target[0] = cofact_00 * detInv;
target[1] = cofact_10 * detInv;
target[2] = cofact_20 * detInv;
target[3] = cofact_30 * detInv;
target[4] = cofact_01 * detInv;
target[5] = cofact_11 * detInv;
target[6] = cofact_21 * detInv;
target[7] = cofact_31 * detInv;
target[8] = cofact_02 * detInv;
target[9] = cofact_12 * detInv;
target[10] = cofact_22 * detInv;
target[11] = cofact_32 * detInv;
target[12] = cofact_03 * detInv;
target[13] = cofact_13 * detInv;
target[14] = cofact_23 * detInv;
target[15] = cofact_33 * detInv;
return true;
}
/* eslint-disable @typescript-eslint/naming-convention */
// eslint-disable-next-line @typescript-eslint/naming-convention
const ExtractAsInt = (value) => {
return parseInt(value.toString().replace(/\W/g, ""));
};
/**
* Class representing a vector containing 2 coordinates
* Example Playground - Overview - https://playground.babylonjs.com/#QYBWV4#9
*/
class Vector2 {
/**
* Creates a new Vector2 from the given x and y coordinates
* @param x defines the first coordinate
* @param y defines the second coordinate
*/
constructor(
/** [0] defines the first coordinate */
x = 0,
/** [0] defines the second coordinate */
y = 0) {
this.x = x;
this.y = y;
}
/**
* Gets a string with the Vector2 coordinates
* @returns a string with the Vector2 coordinates
*/
toString() {
return `{X: ${this.x} Y: ${this.y}}`;
}
/**
* Gets class name
* @returns the string "Vector2"
*/
getClassName() {
return "Vector2";
}
/**
* Gets current vector hash code
* @returns the Vector2 hash code as a number
*/
getHashCode() {
const x = ExtractAsInt(this.x);
const y = ExtractAsInt(this.y);
let hash = x;
hash = (hash * 397) ^ y;
return hash;
}
// Ope