@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.
478 lines (471 loc) • 24.9 kB
JavaScript
import { aY as Tools, l as __runInitializers, aL as Light, q as __esDecorate, aH as RawTexture, C as Constants, m as Texture, y as Logger, r as serializeAsVector3, V as Vector3, c8 as GenerateBase64StringFromTexture, c9 as GenerateBase64StringFromTextureAsync, s as serialize, i as Color3, bi as TransformNode, aD as Quaternion, bo as unregisterGLTFExtension, bp as registerGLTFExtension } from './index-MZPybX0H.esm.js';
import { A as ArrayItem, b as GLTFLoader } from './glTFLoader.pure-BaIedO7s.esm.js';
import './morphTargetManager-CK7B3gaT.esm.js';
import './bone.pure-CjsCww39.esm.js';
import './skeleton-D7Sw58cv.esm.js';
import './assetContainer-Bm0vsreJ.esm.js';
import './objectModelMapping-De5EKNEZ.esm.js';
import './spotLight.pure-CRdZC6Ci.esm.js';
/**
* Loads LTC texture data from Babylon.js CDN.
* @returns Promise with data for LTC1 and LTC2 textures for area lights.
*/
async function DecodeLTCTextureDataAsync() {
const ltc1 = new Uint16Array(64 * 64 * 4);
const ltc2 = new Uint16Array(64 * 64 * 4);
const file = await Tools.LoadFileAsync(Tools.GetAssetUrl("https://assets.babylonjs.com/core/areaLights/areaLightsLTC.bin"));
const ltcEncoded = new Uint16Array(file);
const pixelCount = ltcEncoded.length / 8;
for (let pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) {
ltc1[pixelIndex * 4] = ltcEncoded[pixelIndex * 8];
ltc1[pixelIndex * 4 + 1] = ltcEncoded[pixelIndex * 8 + 1];
ltc1[pixelIndex * 4 + 2] = ltcEncoded[pixelIndex * 8 + 2];
ltc1[pixelIndex * 4 + 3] = ltcEncoded[pixelIndex * 8 + 3];
ltc2[pixelIndex * 4] = ltcEncoded[pixelIndex * 8 + 4];
ltc2[pixelIndex * 4 + 1] = ltcEncoded[pixelIndex * 8 + 5];
ltc2[pixelIndex * 4 + 2] = ltcEncoded[pixelIndex * 8 + 6];
ltc2[pixelIndex * 4 + 3] = ltcEncoded[pixelIndex * 8 + 7];
}
return [ltc1, ltc2];
}
/** This file must only contain pure code and pure imports */
function CreateSceneLTCTextures(scene) {
const useDelayedTextureLoading = scene.useDelayedTextureLoading;
scene.useDelayedTextureLoading = false;
const previousState = scene._blockEntityCollection;
scene._blockEntityCollection = false;
scene._ltcTextures = {
LTC1: RawTexture.CreateRGBATexture(null, 64, 64, scene.getEngine(), false, false, Constants.TEXTURE_LINEAR_LINEAR, Constants.TEXTURETYPE_HALF_FLOAT, 0, false, true),
LTC2: RawTexture.CreateRGBATexture(null, 64, 64, scene.getEngine(), false, false, Constants.TEXTURE_LINEAR_LINEAR, Constants.TEXTURETYPE_HALF_FLOAT, 0, false, true),
};
scene._blockEntityCollection = previousState;
scene._ltcTextures.LTC1.wrapU = Texture.CLAMP_ADDRESSMODE;
scene._ltcTextures.LTC1.wrapV = Texture.CLAMP_ADDRESSMODE;
scene._ltcTextures.LTC2.wrapU = Texture.CLAMP_ADDRESSMODE;
scene._ltcTextures.LTC2.wrapV = Texture.CLAMP_ADDRESSMODE;
scene.useDelayedTextureLoading = useDelayedTextureLoading;
DecodeLTCTextureDataAsync()
// eslint-disable-next-line github/no-then
.then((textureData) => {
if (scene._ltcTextures) {
const ltc1 = scene._ltcTextures?.LTC1;
ltc1.update(textureData[0]);
const ltc2 = scene._ltcTextures?.LTC2;
ltc2.update(textureData[1]);
scene.onDisposeObservable.addOnce(() => {
scene._ltcTextures?.LTC1.dispose();
scene._ltcTextures?.LTC2.dispose();
});
}
})
// eslint-disable-next-line github/no-then
.catch((error) => {
Logger.Error(`Area Light fail to get LTC textures data. Error: ${error}`);
});
}
/**
* Abstract Area Light class that servers as parent for all Area Lights implementations.
* The light is emitted from the area in the -Z direction.
*/
let AreaLight = (() => {
var _a;
let _classSuper = Light;
let _position_decorators;
let _position_initializers = [];
let _position_extraInitializers = [];
return _a = class AreaLight extends _classSuper {
/**
* Creates a area light object.
* Documentation : https://doc.babylonjs.com/features/featuresDeepDive/lights/lights_introduction
* @param name The friendly name of the light
* @param position The position of the area light.
* @param scene The scene the light belongs to
* @param dontAddToScene True to not add the light to the scene
*/
constructor(name, position, scene, dontAddToScene) {
super(name, scene, dontAddToScene);
/**
* Area Light position.
*/
this.position = __runInitializers(this, _position_initializers, void 0);
__runInitializers(this, _position_extraInitializers);
this.position = position;
if (!this._scene._ltcTextures) {
CreateSceneLTCTextures(this._scene);
}
}
transferTexturesToEffect(effect, lightIndex) {
if (this._scene._ltcTextures) {
effect.setTexture("areaLightsLTC1Sampler", this._scene._ltcTextures.LTC1);
effect.setTexture("areaLightsLTC2Sampler", this._scene._ltcTextures.LTC2);
}
return this;
}
/**
* Prepares the list of defines specific to the light type.
* @param defines the list of defines
* @param lightIndex defines the index of the light for the effect
*/
prepareLightSpecificDefines(defines, lightIndex) {
defines["AREALIGHT" + lightIndex] = true;
defines["AREALIGHTUSED"] = true;
}
_isReady() {
if (this._scene._ltcTextures) {
return this._scene._ltcTextures.LTC1.isReady() && this._scene._ltcTextures.LTC2.isReady();
}
return false;
}
},
(() => {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
_position_decorators = [serializeAsVector3()];
__esDecorate(null, null, _position_decorators, { kind: "field", name: "position", static: false, private: false, access: { has: obj => "position" in obj, get: obj => obj.position, set: (obj, value) => { obj.position = value; } }, metadata: _metadata }, _position_initializers, _position_extraInitializers);
if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
})(),
_a;
})();
/** This file must only contain pure code and pure imports */
/**
* A rectangular area light defined by an unique point in world space, a width and a height.
* The light is emitted from the rectangular area in the -Z direction.
*/
let RectAreaLight = (() => {
var _a;
let _classSuper = AreaLight;
let _instanceExtraInitializers = [];
let _get_width_decorators;
let _get_height_decorators;
return _a = class RectAreaLight extends _classSuper {
/**
* Gets Rect Area Light emission texture. (Note: This texture needs pre-processing! Use AreaLightTextureTools to pre-process the texture).
*/
get emissionTexture() {
return this._emissionTextureTexture;
}
/**
* Sets Rect Area Light emission texture. (Note: This texture needs pre-processing! Use AreaLightTextureTools to pre-process the texture).
*/
set emissionTexture(value) {
if (this._emissionTextureTexture === value) {
return;
}
this._emissionTextureTexture = value;
if (this._emissionTextureTexture) {
this._emissionTextureTexture.wrapU = Constants.TEXTURE_CLAMP_ADDRESSMODE;
this._emissionTextureTexture.wrapV = Constants.TEXTURE_CLAMP_ADDRESSMODE;
}
if (this._emissionTextureTexture && _a._IsTexture(this._emissionTextureTexture)) {
this._emissionTextureTexture.onLoadObservable.addOnce(() => {
this._markMeshesAsLightDirty();
});
}
}
/**
* Rect Area Light width.
*/
get width() {
return this._width.x;
}
/**
* Rect Area Light width.
*/
set width(value) {
this._width.x = value;
}
/**
* Rect Area Light height.
*/
get height() {
return this._height.y;
}
/**
* Rect Area Light height.
*/
set height(value) {
this._height.y = value;
}
/**
* Creates a rectangular area light object.
* Documentation : https://doc.babylonjs.com/features/featuresDeepDive/lights/lights_introduction
* @param name The friendly name of the light
* @param position The position of the area light.
* @param width The width of the area light.
* @param height The height of the area light.
* @param scene The scene the light belongs to
* @param dontAddToScene True to not add the light to the scene
*/
constructor(name, position, width, height, scene, dontAddToScene) {
super(name, position, scene, dontAddToScene);
this._width = __runInitializers(this, _instanceExtraInitializers);
this._emissionTextureTexture = null;
this._width = new Vector3(width, 0, 0);
this._height = new Vector3(0, height, 0);
this._pointTransformedPosition = Vector3.Zero();
this._pointTransformedWidth = Vector3.Zero();
this._pointTransformedHeight = Vector3.Zero();
}
/**
* Returns the string "RectAreaLight"
* @returns the class name
*/
getClassName() {
return "RectAreaLight";
}
/**
* Returns the integer 4.
* @returns The light Type id as a constant defines in Light.LIGHTTYPEID_x
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
getTypeID() {
return Light.LIGHTTYPEID_RECT_AREALIGHT;
}
/**
* Serializes the rect area light into a serialization object.
* The emission texture is normally produced at runtime by AreaLightTextureTools and has no
* source URL, so its pixels are embedded as a base64 string to keep the scene self-contained.
* @returns the serialized object
*/
serialize() {
const serializationObject = super.serialize();
if (this._emissionTextureTexture) {
const serializedEmissionTexture = this._serializeEmissionTexture(this._emissionTextureTexture);
if (serializedEmissionTexture) {
serializationObject.emissionTexture = serializedEmissionTexture;
}
}
return serializationObject;
}
_serializeEmissionTexture(texture) {
// A regular URL-backed texture can be serialized by reference.
const referencedTexture = texture.serialize();
if (referencedTexture) {
return referencedTexture;
}
// The emission texture produced by AreaLightTextureTools is a runtime render-target texture
// with no source URL, so embed its pixels as a base64 PNG.
const internalTexture = texture.getInternalTexture();
if (!internalTexture) {
return null;
}
const invertY = internalTexture.invertY;
const hasAlpha = texture.hasAlpha;
// On engines with synchronous texture read (WebGL) the pixels are embedded immediately.
if (this._scene.getEngine()._features.supportSyncTextureRead) {
return _a._BuildEmbeddedEmissionTexture(GenerateBase64StringFromTexture(texture), invertY, hasAlpha);
}
// On WebGPU/Native (no synchronous texture read) a promise for the entire texture object is
// returned so it resolves to null (and is skipped on parse) if the pixels cannot be read.
// SceneSerializer.SerializeAsync resolves the promise before producing the final JSON.
return _a._BuildEmbeddedEmissionTextureAsync(GenerateBase64StringFromTextureAsync(texture), invertY, hasAlpha);
}
static async _BuildEmbeddedEmissionTextureAsync(base64Promise, invertY, hasAlpha) {
return _a._BuildEmbeddedEmissionTexture(await base64Promise, invertY, hasAlpha);
}
static _BuildEmbeddedEmissionTexture(base64String, invertY, hasAlpha) {
if (!base64String) {
return null;
}
return {
name: "areaLightEmissionTexture",
base64String: base64String,
invertY: invertY,
hasAlpha: hasAlpha,
};
}
/**
* Restores the emission texture from the serialized data after the base light has been parsed.
* @param parsedLight The JSON representation of the light
* @param scene The scene the light belongs to
* @param rootUrl The root url to use to load the emission texture when serialized by reference
*/
_onParsed(parsedLight, scene, rootUrl = "") {
super._onParsed(parsedLight, scene, rootUrl);
if (parsedLight.emissionTexture) {
this.emissionTexture = Texture.Parse(parsedLight.emissionTexture, scene, rootUrl);
}
}
_buildUniformLayout() {
this._uniformBuffer.addUniform("vLightData", 4);
this._uniformBuffer.addUniform("vLightDiffuse", 4);
this._uniformBuffer.addUniform("vLightSpecular", 4);
this._uniformBuffer.addUniform("vLightWidth", 4);
this._uniformBuffer.addUniform("vLightHeight", 4);
this._uniformBuffer.addUniform("shadowsInfo", 3);
this._uniformBuffer.addUniform("depthValues", 2);
this._uniformBuffer.create();
}
_computeTransformedInformation() {
if (this.parent && this.parent.getWorldMatrix) {
Vector3.TransformCoordinatesToRef(this.position, this.parent.getWorldMatrix(), this._pointTransformedPosition);
Vector3.TransformNormalToRef(this._width, this.parent.getWorldMatrix(), this._pointTransformedWidth);
Vector3.TransformNormalToRef(this._height, this.parent.getWorldMatrix(), this._pointTransformedHeight);
return true;
}
return false;
}
static _IsTexture(texture) {
return texture.onLoadObservable !== undefined;
}
/**
* Sets the passed Effect "effect" with the PointLight transformed position (or position, if none) and passed name (string).
* @param effect The effect to update
* @param lightIndex The index of the light in the effect to update
* @returns The point light
*/
transferToEffect(effect, lightIndex) {
const offset = this._scene.floatingOriginOffset;
if (this._computeTransformedInformation()) {
this._uniformBuffer.updateFloat4("vLightData", this._pointTransformedPosition.x - offset.x, this._pointTransformedPosition.y - offset.y, this._pointTransformedPosition.z - offset.z, 0, lightIndex);
this._uniformBuffer.updateFloat4("vLightWidth", this._pointTransformedWidth.x / 2, this._pointTransformedWidth.y / 2, this._pointTransformedWidth.z / 2, 0, lightIndex);
this._uniformBuffer.updateFloat4("vLightHeight", this._pointTransformedHeight.x / 2, this._pointTransformedHeight.y / 2, this._pointTransformedHeight.z / 2, 0, lightIndex);
}
else {
this._uniformBuffer.updateFloat4("vLightData", this.position.x - offset.x, this.position.y - offset.y, this.position.z - offset.z, 0, lightIndex);
this._uniformBuffer.updateFloat4("vLightWidth", this._width.x / 2, this._width.y / 2, this._width.z / 2, 0.0, lightIndex);
this._uniformBuffer.updateFloat4("vLightHeight", this._height.x / 2, this._height.y / 2, this._height.z / 2, 0.0, lightIndex);
}
return this;
}
transferTexturesToEffect(effect, lightIndex) {
super.transferTexturesToEffect(effect, lightIndex);
if (this._emissionTextureTexture && this._emissionTextureTexture.isReady()) {
effect.setTexture("rectAreaLightEmissionTexture" + lightIndex, this._emissionTextureTexture);
}
return this;
}
transferToNodeMaterialEffect(effect, lightDataUniformName) {
const offset = this._scene.floatingOriginOffset;
if (this._computeTransformedInformation()) {
effect.setFloat3(lightDataUniformName, this._pointTransformedPosition.x - offset.x, this._pointTransformedPosition.y - offset.y, this._pointTransformedPosition.z - offset.z);
}
else {
effect.setFloat3(lightDataUniformName, this.position.x - offset.x, this.position.y - offset.y, this.position.z - offset.z);
}
return this;
}
/**
* Prepares the list of defines specific to the light type.
* @param defines the list of defines
* @param lightIndex defines the index of the light for the effect
*/
prepareLightSpecificDefines(defines, lightIndex) {
super.prepareLightSpecificDefines(defines, lightIndex);
defines["RECTAREALIGHTEMISSIONTEXTURE" + lightIndex] = this._emissionTextureTexture && this._emissionTextureTexture.isReady() ? true : false;
}
},
(() => {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
_get_width_decorators = [serialize()];
_get_height_decorators = [serialize()];
__esDecorate(_a, null, _get_width_decorators, { kind: "getter", name: "width", static: false, private: false, access: { has: obj => "width" in obj, get: obj => obj.width }, metadata: _metadata }, null, _instanceExtraInitializers);
__esDecorate(_a, null, _get_height_decorators, { kind: "getter", name: "height", static: false, private: false, access: { has: obj => "height" in obj, get: obj => obj.height }, metadata: _metadata }, null, _instanceExtraInitializers);
if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
})(),
_a;
})();
const NAME = "EXT_lights_area";
/**
* [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_lights_area/README.md)
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
class EXT_lights_area {
/**
* @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;
delete this._lights;
}
/** @internal */
onLoading() {
const extensions = this._loader.gltf.extensions;
if (extensions && extensions[this.name]) {
const extension = extensions[this.name];
this._lights = extension.lights;
ArrayItem.Assign(this._lights);
}
}
/**
* @internal
*/
// eslint-disable-next-line no-restricted-syntax
loadNodeAsync(context, node, assign) {
return GLTFLoader.LoadExtensionAsync(context, node, this.name, async (extensionContext, extension) => {
this._loader._allMaterialsDirtyRequired = true;
return await this._loader.loadNodeAsync(context, node, (babylonMesh) => {
let babylonLight;
const light = ArrayItem.Get(extensionContext, this._lights, extension.light);
const name = light.name || babylonMesh.name;
this._loader.babylonScene._blockEntityCollection = !!this._loader._assetContainer;
const size = light.size !== undefined ? light.size : 1.0;
switch (light.type) {
case "rect" /* EXTLightsArea_LightType.RECT */: {
const width = light.rect?.aspect !== undefined ? light.rect.aspect * size : size;
const height = size;
const babylonRectAreaLight = new RectAreaLight(name, Vector3.Zero(), width, height, this._loader.babylonScene);
babylonLight = babylonRectAreaLight;
break;
}
case "disk" /* EXTLightsArea_LightType.DISK */: {
// For disk lights, we'll use a rectangle light with the same area to approximate the disk light
// In the future, this could be extended to support actual disk area lights
const newSize = Math.sqrt(size * size * 0.25 * Math.PI); // Area of the disk
const babylonRectAreaLight = new RectAreaLight(name, Vector3.Zero(), newSize, newSize, this._loader.babylonScene);
babylonLight = babylonRectAreaLight;
break;
}
default: {
this._loader.babylonScene._blockEntityCollection = false;
throw new Error(`${extensionContext}: Invalid area light type (${light.type})`);
}
}
babylonLight._parentContainer = this._loader._assetContainer;
this._loader.babylonScene._blockEntityCollection = false;
light._babylonLight = babylonLight;
babylonLight.falloffType = Light.FALLOFF_GLTF;
babylonLight.diffuse = light.color ? Color3.FromArray(light.color) : Color3.White();
babylonLight.intensity = light.intensity == undefined ? 1 : light.intensity;
// glTF EXT_lights_area specifies lights face down -Z, but Babylon.js area lights face down +Z
// Create a parent transform node with 180-degree rotation around Y axis to flip the direction
const lightParentNode = new TransformNode(`${name}_orientation`, this._loader.babylonScene);
lightParentNode.rotationQuaternion = Quaternion.RotationAxis(Vector3.Up(), Math.PI);
lightParentNode.parent = babylonMesh;
babylonLight.parent = lightParentNode;
this._loader._babylonLights.push(babylonLight);
GLTFLoader.AddPointerMetadata(babylonLight, extensionContext);
assign(babylonMesh);
});
});
}
}
let _Registered = false;
/**
* Registers the EXT_lights_area glTF loader extension.
* Safe to call multiple times; only the first call has an effect.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
function RegisterEXT_lights_area() {
if (_Registered) {
return;
}
_Registered = true;
unregisterGLTFExtension(NAME);
registerGLTFExtension(NAME, true, (loader) => new EXT_lights_area(loader));
}
/**
* Re-exports the pure implementation and applies the runtime registration side effect.
* Import "./EXT_lights_area.pure" for tree-shakeable, side-effect-free usage.
*/
RegisterEXT_lights_area();
export { EXT_lights_area, RegisterEXT_lights_area };
//# sourceMappingURL=EXT_lights_area-CLMq1oMA.esm.js.map