Skip to content

Commit a386e27

Browse files
authored
Add import support for EXT_lights_area (#17146)
Added importing support for first version of EXT_light_area extension. KhronosGroup/glTF#2525
1 parent eabe759 commit a386e27

File tree

4 files changed

+177
-0
lines changed

4 files changed

+177
-0
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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));

packages/dev/loaders/src/glTF/2.0/Extensions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-restricted-imports */
22
export * from "./objectModelMapping";
3+
export * from "./EXT_lights_area";
34
export * from "./EXT_lights_image_based";
45
export * from "./EXT_mesh_gpu_instancing";
56
export * from "./EXT_meshopt_compression";

packages/dev/loaders/src/glTF/2.0/glTFLoaderInterfaces.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,10 @@ export interface IEXTLightsIES_Light extends GLTF2.IEXTLightsIES_Light, IArrayIt
312312
/** @hidden */
313313
_babylonLight?: Light;
314314
}
315+
316+
/** @internal */
317+
// eslint-disable-next-line @typescript-eslint/naming-convention
318+
export interface IEXTLightsArea_Light extends GLTF2.IEXTLightsArea_Light, IArrayItem {
319+
/** @hidden */
320+
_babylonLight?: Light;
321+
}

packages/public/glTF2Interface/babylon.glTF2Interface.d.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,37 @@ declare module BABYLON.GLTF2 {
13881388
filter?: "NONE" | "OCTAHEDRAL" | "QUATERNION" | "EXPONENTIAL";
13891389
}
13901390

1391+
/**
1392+
* Interfaces from the EXT_lights_area extension
1393+
*/
1394+
1395+
/** @internal */
1396+
const enum EXTLightsArea_LightShape {
1397+
RECT = "rect",
1398+
DISK = "disk"
1399+
}
1400+
1401+
/** @internal */
1402+
interface IEXTLightsArea_LightReference {
1403+
light: number;
1404+
}
1405+
1406+
/** @internal */
1407+
interface IEXTLightsArea_Light extends IChildRootProperty {
1408+
shape: EXTLightsArea_LightShape;
1409+
color?: number[];
1410+
intensity?: number;
1411+
type?: "area";
1412+
width?: number;
1413+
height?: number;
1414+
radius?: number;
1415+
}
1416+
1417+
/** @internal */
1418+
interface IEXTLightsArea {
1419+
lights: IEXTLightsArea_Light[];
1420+
}
1421+
13911422
/**
13921423
* Interfaces for the KHR_interactivity extension
13931424
*/

0 commit comments

Comments
 (0)