|
| 1 | +/* eslint-disable @typescript-eslint/naming-convention */ |
| 2 | +import type { Nullable } from "core/types"; |
| 3 | +import { Vector3, Quaternion } from "core/Maths/math.vector"; |
| 4 | +import { Color3 } from "core/Maths/math.color"; |
| 5 | +import { Light } from "core/Lights/light"; |
| 6 | +import { RectAreaLight } from "core/Lights/rectAreaLight"; |
| 7 | +import type { TransformNode } from "core/Meshes/transformNode"; |
| 8 | +import { TransformNode as BabylonTransformNode } from "core/Meshes/transformNode"; |
| 9 | + |
| 10 | +import type { IEXTLightsArea_LightReference } from "babylonjs-gltf2interface"; |
| 11 | +import { EXTLightsArea_LightShape } from "babylonjs-gltf2interface"; |
| 12 | +import type { INode, IEXTLightsArea_Light } from "../glTFLoaderInterfaces"; |
| 13 | +import type { IGLTFLoaderExtension } from "../glTFLoaderExtension"; |
| 14 | +import { GLTFLoader, ArrayItem } from "../glTFLoader"; |
| 15 | +import { registerGLTFExtension, unregisterGLTFExtension } from "../glTFLoaderExtensionRegistry"; |
| 16 | + |
| 17 | +const NAME = "EXT_lights_area"; |
| 18 | + |
| 19 | +declare module "../../glTFFileLoader" { |
| 20 | + // eslint-disable-next-line jsdoc/require-jsdoc, @typescript-eslint/naming-convention |
| 21 | + export interface GLTFLoaderExtensionOptions { |
| 22 | + /** |
| 23 | + * Defines options for the EXT_lights_area extension. |
| 24 | + */ |
| 25 | + // NOTE: Don't use NAME here as it will break the UMD type declarations. |
| 26 | + ["EXT_lights_area"]: {}; |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_lights_area/README.md) |
| 32 | + */ |
| 33 | +// eslint-disable-next-line @typescript-eslint/naming-convention |
| 34 | +export class EXT_lights_area implements IGLTFLoaderExtension { |
| 35 | + /** |
| 36 | + * The name of this extension. |
| 37 | + */ |
| 38 | + public readonly name = NAME; |
| 39 | + |
| 40 | + /** |
| 41 | + * Defines whether this extension is enabled. |
| 42 | + */ |
| 43 | + public enabled: boolean; |
| 44 | + |
| 45 | + /** hidden */ |
| 46 | + private _loader: GLTFLoader; |
| 47 | + private _lights?: IEXTLightsArea_Light[]; |
| 48 | + |
| 49 | + /** |
| 50 | + * @internal |
| 51 | + */ |
| 52 | + constructor(loader: GLTFLoader) { |
| 53 | + this._loader = loader; |
| 54 | + this.enabled = this._loader.isExtensionUsed(NAME); |
| 55 | + } |
| 56 | + |
| 57 | + /** @internal */ |
| 58 | + public dispose() { |
| 59 | + (this._loader as any) = null; |
| 60 | + delete this._lights; |
| 61 | + } |
| 62 | + |
| 63 | + /** @internal */ |
| 64 | + public onLoading(): void { |
| 65 | + const extensions = this._loader.gltf.extensions; |
| 66 | + if (extensions && extensions[this.name]) { |
| 67 | + const extension = extensions[this.name]; |
| 68 | + this._lights = extension.lights; |
| 69 | + ArrayItem.Assign(this._lights); |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * @internal |
| 75 | + */ |
| 76 | + // eslint-disable-next-line no-restricted-syntax |
| 77 | + public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>> { |
| 78 | + return GLTFLoader.LoadExtensionAsync<IEXTLightsArea_LightReference, TransformNode>(context, node, this.name, async (extensionContext, extension) => { |
| 79 | + this._loader._allMaterialsDirtyRequired = true; |
| 80 | + |
| 81 | + return await this._loader.loadNodeAsync(context, node, (babylonMesh) => { |
| 82 | + let babylonLight: Light; |
| 83 | + |
| 84 | + const light = ArrayItem.Get(extensionContext, this._lights, extension.light); |
| 85 | + const name = light.name || babylonMesh.name; |
| 86 | + |
| 87 | + this._loader.babylonScene._blockEntityCollection = !!this._loader._assetContainer; |
| 88 | + |
| 89 | + switch (light.shape) { |
| 90 | + case EXTLightsArea_LightShape.RECT: { |
| 91 | + const width = light.width !== undefined ? light.width : 1.0; |
| 92 | + const height = light.height !== undefined ? light.height : 1.0; |
| 93 | + const babylonRectAreaLight = new RectAreaLight(name, Vector3.Zero(), width, height, this._loader.babylonScene); |
| 94 | + babylonLight = babylonRectAreaLight; |
| 95 | + break; |
| 96 | + } |
| 97 | + case EXTLightsArea_LightShape.DISK: { |
| 98 | + // For disk lights, we'll use RectAreaLight with equal width and height to approximate a square area |
| 99 | + // In the future, this could be extended to support actual disk area lights |
| 100 | + const radius = light.radius !== undefined ? light.radius : 0.5; |
| 101 | + const size = radius * 2; // Convert radius to square size |
| 102 | + const babylonRectAreaLight = new RectAreaLight(name, Vector3.Zero(), size, size, this._loader.babylonScene); |
| 103 | + babylonLight = babylonRectAreaLight; |
| 104 | + break; |
| 105 | + } |
| 106 | + default: { |
| 107 | + this._loader.babylonScene._blockEntityCollection = false; |
| 108 | + throw new Error(`${extensionContext}: Invalid area light shape (${light.shape})`); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + babylonLight._parentContainer = this._loader._assetContainer; |
| 113 | + this._loader.babylonScene._blockEntityCollection = false; |
| 114 | + light._babylonLight = babylonLight; |
| 115 | + |
| 116 | + babylonLight.falloffType = Light.FALLOFF_GLTF; |
| 117 | + babylonLight.diffuse = light.color ? Color3.FromArray(light.color) : Color3.White(); |
| 118 | + babylonLight.intensity = light.intensity == undefined ? 1 : light.intensity; |
| 119 | + |
| 120 | + // glTF EXT_lights_area specifies lights face down -Z, but Babylon.js area lights face down +Z |
| 121 | + // Create a parent transform node with 180-degree rotation around Y axis to flip the direction |
| 122 | + const lightParentNode = new BabylonTransformNode(`${name}_orientation`, this._loader.babylonScene); |
| 123 | + lightParentNode.rotationQuaternion = Quaternion.RotationAxis(Vector3.Up(), Math.PI); |
| 124 | + lightParentNode.parent = babylonMesh; |
| 125 | + babylonLight.parent = lightParentNode; |
| 126 | + |
| 127 | + this._loader._babylonLights.push(babylonLight); |
| 128 | + |
| 129 | + GLTFLoader.AddPointerMetadata(babylonLight, extensionContext); |
| 130 | + |
| 131 | + assign(babylonMesh); |
| 132 | + }); |
| 133 | + }); |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +unregisterGLTFExtension(NAME); |
| 138 | +registerGLTFExtension(NAME, true, (loader) => new EXT_lights_area(loader)); |
0 commit comments