action-engine-js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -0
- package/README.md +348 -0
- package/actionengine/3rdparty/goblin/goblin.js +9609 -0
- package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
- package/actionengine/camera/actioncamera.js +90 -0
- package/actionengine/camera/cameracollisionhandler.js +69 -0
- package/actionengine/character/actioncharacter.js +360 -0
- package/actionengine/character/actioncharacter3D.js +61 -0
- package/actionengine/core/app.js +430 -0
- package/actionengine/debug/basedebugpanel.js +858 -0
- package/actionengine/display/canvasmanager.js +75 -0
- package/actionengine/display/gl/programmanager.js +570 -0
- package/actionengine/display/gl/shaders/lineshader.js +118 -0
- package/actionengine/display/gl/shaders/objectshader.js +1756 -0
- package/actionengine/display/gl/shaders/particleshader.js +43 -0
- package/actionengine/display/gl/shaders/shadowshader.js +319 -0
- package/actionengine/display/gl/shaders/spriteshader.js +100 -0
- package/actionengine/display/gl/shaders/watershader.js +67 -0
- package/actionengine/display/graphics/actionmodel3D.js +191 -0
- package/actionengine/display/graphics/actionsprite3D.js +230 -0
- package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
- package/actionengine/display/graphics/lighting/actionlight.js +211 -0
- package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
- package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
- package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
- package/actionengine/display/graphics/renderableobject.js +44 -0
- package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
- package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
- package/actionengine/display/graphics/texture/texturemanager.js +242 -0
- package/actionengine/display/graphics/texture/textureregistry.js +177 -0
- package/actionengine/input/actionscrollablearea.js +1405 -0
- package/actionengine/input/inputhandler.js +1647 -0
- package/actionengine/math/geometry/geometrybuilder.js +161 -0
- package/actionengine/math/geometry/glbexporter.js +364 -0
- package/actionengine/math/geometry/glbloader.js +722 -0
- package/actionengine/math/geometry/modelcodegenerator.js +97 -0
- package/actionengine/math/geometry/triangle.js +33 -0
- package/actionengine/math/geometry/triangleutils.js +34 -0
- package/actionengine/math/mathutils.js +25 -0
- package/actionengine/math/matrix4.js +785 -0
- package/actionengine/math/physics/actionphysics.js +108 -0
- package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
- package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
- package/actionengine/math/physics/actionraycast.js +129 -0
- package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
- package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
- package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
- package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
- package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
- package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
- package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
- package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
- package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
- package/actionengine/math/quaternion.js +61 -0
- package/actionengine/math/vector2.js +277 -0
- package/actionengine/math/vector3.js +318 -0
- package/actionengine/math/viewfrustum.js +136 -0
- package/actionengine/network/ACTIONNETREADME.md +810 -0
- package/actionengine/network/client/ActionNetManager.js +802 -0
- package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
- package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
- package/actionengine/network/client/SyncSystem.js +422 -0
- package/actionengine/network/p2p/ActionNetPeer.js +142 -0
- package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
- package/actionengine/network/p2p/DataConnection.js +282 -0
- package/actionengine/network/p2p/README.md +510 -0
- package/actionengine/network/p2p/example.html +502 -0
- package/actionengine/network/server/ActionNetServer.js +577 -0
- package/actionengine/network/server/ActionNetServerSSL.js +579 -0
- package/actionengine/network/server/ActionNetServerUtils.js +458 -0
- package/actionengine/network/server/SERVERREADME.md +314 -0
- package/actionengine/network/server/package-lock.json +35 -0
- package/actionengine/network/server/package.json +13 -0
- package/actionengine/network/server/start.bat +27 -0
- package/actionengine/network/server/start.sh +25 -0
- package/actionengine/network/server/startwss.bat +27 -0
- package/actionengine/sound/audiomanager.js +1589 -0
- package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
- package/actionengine/sound/soundfont/actionparser.js +718 -0
- package/actionengine/sound/soundfont/actionreverb.js +252 -0
- package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
- package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
- package/actionengine/sound/soundfont/soundfont.js +2 -0
- package/dist/action-engine.min.js +328 -0
- package/package.json +35 -0
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
// actionengine/display/graphics/lighting/lightmanager.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LightManager handles creation, management, and rendering of multiple light types
|
|
5
|
+
* This class serves as a central registry for all lights in the scene
|
|
6
|
+
*/
|
|
7
|
+
class LightManager {
|
|
8
|
+
/**
|
|
9
|
+
* Constructor for the light manager
|
|
10
|
+
* @param {WebGLRenderingContext} gl - The WebGL rendering context
|
|
11
|
+
* @param {boolean} isWebGL2 - Flag indicating if WebGL2 is available
|
|
12
|
+
* @param {ProgramManager} programManager - Reference to the program manager for shader access
|
|
13
|
+
*/
|
|
14
|
+
constructor(gl, isWebGL2, programManager) {
|
|
15
|
+
this.gl = gl;
|
|
16
|
+
this.isWebGL2 = isWebGL2;
|
|
17
|
+
this.programManager = programManager;
|
|
18
|
+
|
|
19
|
+
// Reference to lighting constants
|
|
20
|
+
this.constants = lightingConstants;
|
|
21
|
+
|
|
22
|
+
// Storage for different light types
|
|
23
|
+
this.directionalLights = [];
|
|
24
|
+
this.pointLights = [];
|
|
25
|
+
this.spotLights = [];
|
|
26
|
+
|
|
27
|
+
// Light data textures
|
|
28
|
+
this.directionalLightDataTexture = null;
|
|
29
|
+
this.pointLightDataTexture = null;
|
|
30
|
+
this.spotLightDataTexture = null;
|
|
31
|
+
|
|
32
|
+
// Flag to track if light data textures need updating
|
|
33
|
+
this.lightDataDirty = true;
|
|
34
|
+
|
|
35
|
+
// The main directional light (sun) is optional
|
|
36
|
+
// It's not created by default, but can be created if needed
|
|
37
|
+
this.mainDirectionalLightEnabled = true; // Flag to track whether directional light should be enabled
|
|
38
|
+
if (this.mainDirectionalLightEnabled) {
|
|
39
|
+
this.createMainDirectionalLight();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Frame counter for updates
|
|
43
|
+
this.frameCount = 0;
|
|
44
|
+
|
|
45
|
+
// Initialize the light data textures
|
|
46
|
+
this.initializeLightDataTextures();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create the main directional light (sun) with default settings
|
|
51
|
+
* @returns {ActionDirectionalShadowLight} - The created light or null if directional light is disabled
|
|
52
|
+
*/
|
|
53
|
+
createMainDirectionalLight() {
|
|
54
|
+
// If directional light is disabled, return null
|
|
55
|
+
if (!this.mainDirectionalLightEnabled) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const mainLight = new ActionDirectionalShadowLight(this.gl, this.isWebGL2, this.programManager);
|
|
59
|
+
|
|
60
|
+
// Set initial properties from constants
|
|
61
|
+
mainLight.setPosition(
|
|
62
|
+
new Vector3(
|
|
63
|
+
this.constants.LIGHT_POSITION.x,
|
|
64
|
+
this.constants.LIGHT_POSITION.y,
|
|
65
|
+
this.constants.LIGHT_POSITION.z
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
mainLight.setDirection(
|
|
70
|
+
new Vector3(
|
|
71
|
+
this.constants.LIGHT_DIRECTION.x,
|
|
72
|
+
this.constants.LIGHT_DIRECTION.y,
|
|
73
|
+
this.constants.LIGHT_DIRECTION.z
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
mainLight.setIntensity(this.constants.LIGHT_INTENSITY.value);
|
|
78
|
+
|
|
79
|
+
// Add to the list of directional lights
|
|
80
|
+
this.directionalLights.push(mainLight);
|
|
81
|
+
|
|
82
|
+
return mainLight;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the main directional light (sun)
|
|
87
|
+
* @returns {ActionDirectionalShadowLight|null} - The main directional light or null if none exists
|
|
88
|
+
*/
|
|
89
|
+
getMainDirectionalLight() {
|
|
90
|
+
return this.directionalLights[0] || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Enable or disable the main directional light
|
|
95
|
+
* @param {boolean} enabled - Whether the directional light should be enabled
|
|
96
|
+
*/
|
|
97
|
+
setMainDirectionalLightEnabled(enabled) {
|
|
98
|
+
this.mainDirectionalLightEnabled = enabled;
|
|
99
|
+
|
|
100
|
+
// When enabling the light, make sure the intensity in constants is non-zero
|
|
101
|
+
if (enabled) {
|
|
102
|
+
// Make sure the intensity in lighting constants is not 0
|
|
103
|
+
if (this.constants.LIGHT_INTENSITY.value <= 0.001) {
|
|
104
|
+
// Set to a reasonable default if it was zero
|
|
105
|
+
this.constants.LIGHT_INTENSITY.value = 100.0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If no directional light exists, create one
|
|
109
|
+
if (this.directionalLights.length === 0) {
|
|
110
|
+
const light = this.createMainDirectionalLight();
|
|
111
|
+
|
|
112
|
+
// Force update light from constants to make sure it has the right properties
|
|
113
|
+
if (light) {
|
|
114
|
+
light.setIntensity(this.constants.LIGHT_INTENSITY.value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// If light already exists, make sure its properties match the constants
|
|
118
|
+
else if (this.directionalLights.length > 0) {
|
|
119
|
+
const light = this.directionalLights[0];
|
|
120
|
+
if (light) {
|
|
121
|
+
light.setIntensity(this.constants.LIGHT_INTENSITY.value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// If disabling and directional light exists, remove it
|
|
126
|
+
else if (!enabled && this.directionalLights.length > 0) {
|
|
127
|
+
// Store a reference to the light before removal
|
|
128
|
+
const light = this.directionalLights[0];
|
|
129
|
+
|
|
130
|
+
// Remove the light from the array first
|
|
131
|
+
this.directionalLights.splice(0, 1);
|
|
132
|
+
|
|
133
|
+
// Then dispose of its resources
|
|
134
|
+
if (light) {
|
|
135
|
+
light.dispose();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if the main directional light is enabled
|
|
142
|
+
* @returns {boolean} - Whether the directional light is enabled
|
|
143
|
+
*/
|
|
144
|
+
isMainDirectionalLightEnabled() {
|
|
145
|
+
return this.mainDirectionalLightEnabled;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a new directional light
|
|
150
|
+
* @param {Vector3} position - Initial position
|
|
151
|
+
* @param {Vector3} direction - Initial direction
|
|
152
|
+
* @param {Vector3} color - Light color (RGB, values 0-1)
|
|
153
|
+
* @param {number} intensity - Light intensity
|
|
154
|
+
* @param {boolean} castsShadows - Whether this light should cast shadows
|
|
155
|
+
* @returns {ActionDirectionalShadowLight} - The created light
|
|
156
|
+
*/
|
|
157
|
+
createDirectionalLight(position, direction, color, intensity, castsShadows = true) {
|
|
158
|
+
const light = new ActionDirectionalShadowLight(this.gl, this.isWebGL2, this.programManager);
|
|
159
|
+
|
|
160
|
+
light.setPosition(position);
|
|
161
|
+
light.setDirection(direction);
|
|
162
|
+
|
|
163
|
+
if (color) {
|
|
164
|
+
light.setColor(color);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
light.setIntensity(intensity);
|
|
168
|
+
light.setShadowsEnabled(castsShadows);
|
|
169
|
+
|
|
170
|
+
this.directionalLights.push(light);
|
|
171
|
+
this.lightDataDirty = true; // Mark light data as needing update
|
|
172
|
+
|
|
173
|
+
return light;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Create a new omnidirectional point light
|
|
178
|
+
* @param {Vector3} position - Initial position
|
|
179
|
+
* @param {Vector3} color - Light color (RGB, values 0-1)
|
|
180
|
+
* @param {number} intensity - Light intensity
|
|
181
|
+
* @param {number} radius - Light radius (affects attenuation)
|
|
182
|
+
* @param {boolean} castsShadows - Whether this light should cast shadows
|
|
183
|
+
* @returns {ActionOmnidirectionalShadowLight} - The created light
|
|
184
|
+
*/
|
|
185
|
+
createPointLight(position, color, intensity, radius = 100.0, castsShadows = false) {
|
|
186
|
+
// Remove logging to reduce console spam
|
|
187
|
+
|
|
188
|
+
const light = new ActionOmnidirectionalShadowLight(this.gl, this.isWebGL2, this.programManager);
|
|
189
|
+
|
|
190
|
+
light.setPosition(position);
|
|
191
|
+
|
|
192
|
+
if (color) {
|
|
193
|
+
light.setColor(color);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
light.setIntensity(intensity);
|
|
197
|
+
light.setRadius(radius);
|
|
198
|
+
light.setShadowsEnabled(castsShadows);
|
|
199
|
+
|
|
200
|
+
this.pointLights.push(light);
|
|
201
|
+
this.lightDataDirty = true; // Mark light data as needing update
|
|
202
|
+
|
|
203
|
+
return light;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Remove a light from the manager
|
|
208
|
+
* @param {ActionLight} light - The light to remove
|
|
209
|
+
* @returns {boolean} - True if the light was removed, false if not found
|
|
210
|
+
*/
|
|
211
|
+
removeLight(light) {
|
|
212
|
+
if (!light) return false;
|
|
213
|
+
|
|
214
|
+
// Check each light type
|
|
215
|
+
const directionalIndex = this.directionalLights.indexOf(light);
|
|
216
|
+
if (directionalIndex !== -1) {
|
|
217
|
+
// Allow removing the main light if it's the main directional light
|
|
218
|
+
// and it matches the light parameter
|
|
219
|
+
if (directionalIndex === 0) {
|
|
220
|
+
// Only allow removal if we're explicitly disabling the main light
|
|
221
|
+
if (!this.mainDirectionalLightEnabled) {
|
|
222
|
+
light.dispose();
|
|
223
|
+
this.directionalLights.splice(directionalIndex, 1);
|
|
224
|
+
this.lightDataDirty = true; // Mark light data as needing update
|
|
225
|
+
return true;
|
|
226
|
+
} else {
|
|
227
|
+
console.warn(
|
|
228
|
+
"Cannot remove main directional light while it's enabled. Use setMainDirectionalLightEnabled(false) first."
|
|
229
|
+
);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
light.dispose();
|
|
234
|
+
this.directionalLights.splice(directionalIndex, 1);
|
|
235
|
+
this.lightDataDirty = true; // Mark light data as needing update
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const pointIndex = this.pointLights.indexOf(light);
|
|
240
|
+
if (pointIndex !== -1) {
|
|
241
|
+
light.dispose();
|
|
242
|
+
this.pointLights.splice(pointIndex, 1);
|
|
243
|
+
this.lightDataDirty = true; // Mark light data as needing update
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const spotIndex = this.spotLights.indexOf(light);
|
|
248
|
+
if (spotIndex !== -1) {
|
|
249
|
+
light.dispose();
|
|
250
|
+
this.spotLights.splice(spotIndex, 1);
|
|
251
|
+
this.lightDataDirty = true; // Mark light data as needing update
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Update all lights
|
|
260
|
+
* @returns {boolean} - Whether any lights changed this frame
|
|
261
|
+
*/
|
|
262
|
+
update() {
|
|
263
|
+
this.frameCount++;
|
|
264
|
+
let changed = false;
|
|
265
|
+
|
|
266
|
+
// Only update every few frames for performance
|
|
267
|
+
if (this.frameCount % 5 !== 0) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Update directional lights
|
|
272
|
+
for (const light of this.directionalLights) {
|
|
273
|
+
const lightChanged = light.update();
|
|
274
|
+
changed = changed || lightChanged;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Update point lights
|
|
278
|
+
for (const light of this.pointLights) {
|
|
279
|
+
const lightChanged = light.update();
|
|
280
|
+
changed = changed || lightChanged;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Update spot lights (future)
|
|
284
|
+
for (const light of this.spotLights) {
|
|
285
|
+
const lightChanged = light.update();
|
|
286
|
+
changed = changed || lightChanged;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// If any light has changed, mark light data as needing update
|
|
290
|
+
if (changed) {
|
|
291
|
+
this.lightDataDirty = true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return changed;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Sync the main directional light with lighting constants
|
|
299
|
+
* This maintains compatibility with the existing debug panel
|
|
300
|
+
*/
|
|
301
|
+
syncWithConstants() {
|
|
302
|
+
const mainLight = this.getMainDirectionalLight();
|
|
303
|
+
if (mainLight) {
|
|
304
|
+
mainLight.syncWithConstants();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get light configuration for the main directional light
|
|
310
|
+
* This maintains compatibility with existing code
|
|
311
|
+
*/
|
|
312
|
+
getLightConfig() {
|
|
313
|
+
const mainLight = this.getMainDirectionalLight();
|
|
314
|
+
if (!mainLight) return null;
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
POSITION: {
|
|
318
|
+
x: mainLight.position.x,
|
|
319
|
+
y: mainLight.position.y,
|
|
320
|
+
z: mainLight.position.z
|
|
321
|
+
},
|
|
322
|
+
DIRECTION: {
|
|
323
|
+
x: mainLight.direction.x,
|
|
324
|
+
y: mainLight.direction.y,
|
|
325
|
+
z: mainLight.direction.z
|
|
326
|
+
},
|
|
327
|
+
INTENSITY: mainLight.intensity,
|
|
328
|
+
MATERIAL: {
|
|
329
|
+
ROUGHNESS: this.constants.MATERIAL.ROUGHNESS.value,
|
|
330
|
+
METALLIC: this.constants.MATERIAL.METALLIC.value,
|
|
331
|
+
BASE_REFLECTIVITY: this.constants.MATERIAL.BASE_REFLECTIVITY.value
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get the light space matrix from the main directional light
|
|
338
|
+
* @returns {Float32Array|null} - The light space matrix or null if no directional light exists
|
|
339
|
+
*/
|
|
340
|
+
getLightSpaceMatrix() {
|
|
341
|
+
const mainLight = this.getMainDirectionalLight();
|
|
342
|
+
return mainLight ? mainLight.getLightSpaceMatrix() : null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get the direction vector from the main directional light
|
|
347
|
+
* @returns {Vector3|null} - The direction vector or null if no directional light exists
|
|
348
|
+
*/
|
|
349
|
+
getLightDir() {
|
|
350
|
+
const mainLight = this.getMainDirectionalLight();
|
|
351
|
+
return mainLight ? mainLight.getDirection() : null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Render shadow maps for all shadow-casting lights
|
|
356
|
+
* @param {Array} objects - Array of objects to render to shadow maps
|
|
357
|
+
*/
|
|
358
|
+
renderShadowMaps(objects) {
|
|
359
|
+
// Early exit if no objects
|
|
360
|
+
if (!objects || objects.length === 0) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Filter objects that actually have triangles once
|
|
365
|
+
const validObjects = objects.filter(obj => obj && obj.triangles && obj.triangles.length > 0);
|
|
366
|
+
|
|
367
|
+
if (validObjects.length === 0) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Render directional light shadow maps
|
|
372
|
+
for (const light of this.directionalLights) {
|
|
373
|
+
if (light.getShadowsEnabled()) {
|
|
374
|
+
light.beginShadowPass();
|
|
375
|
+
|
|
376
|
+
// Render objects to shadow map
|
|
377
|
+
for (const object of validObjects) {
|
|
378
|
+
light.renderObjectToShadowMap(object);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
light.endShadowPass();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Render point light shadow maps
|
|
386
|
+
for (let lightIndex = 0; lightIndex < this.pointLights.length; lightIndex++) {
|
|
387
|
+
const light = this.pointLights[lightIndex];
|
|
388
|
+
if (light.getShadowsEnabled()) {
|
|
389
|
+
// For omnidirectional lights, we need to render the shadow map for each face (6 faces)
|
|
390
|
+
for (let faceIndex = 0; faceIndex < 6; faceIndex++) {
|
|
391
|
+
light.beginShadowPass(faceIndex, lightIndex);
|
|
392
|
+
|
|
393
|
+
// Render objects to shadow map for this face
|
|
394
|
+
for (const object of validObjects) {
|
|
395
|
+
light.renderObjectToShadowMap(object);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
light.endShadowPass();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Reset state after shadow rendering
|
|
404
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
|
405
|
+
this.gl.useProgram(null);
|
|
406
|
+
|
|
407
|
+
// Render spot light shadow maps (future)
|
|
408
|
+
// ...
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Apply all lights to the given shader program
|
|
413
|
+
* @param {WebGLProgram} program - The shader program to apply lights to
|
|
414
|
+
*/
|
|
415
|
+
applyLightsToShader(program) {
|
|
416
|
+
const gl = this.gl;
|
|
417
|
+
|
|
418
|
+
// Make sure light data textures are up-to-date
|
|
419
|
+
this.updateLightDataTextures();
|
|
420
|
+
|
|
421
|
+
// -- Set Light Counts --
|
|
422
|
+
// Set directional light count
|
|
423
|
+
const dirLightCountLoc = gl.getUniformLocation(program, "uDirectionalLightCount");
|
|
424
|
+
if (dirLightCountLoc !== null) {
|
|
425
|
+
gl.uniform1i(dirLightCountLoc, this.directionalLights.length);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Set point light count
|
|
429
|
+
const pointLightCountLoc = gl.getUniformLocation(program, "uPointLightCount");
|
|
430
|
+
if (pointLightCountLoc !== null) {
|
|
431
|
+
gl.uniform1i(pointLightCountLoc, this.pointLights.length);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Set spot light count
|
|
435
|
+
const spotLightCountLoc = gl.getUniformLocation(program, "uSpotLightCount");
|
|
436
|
+
if (spotLightCountLoc !== null) {
|
|
437
|
+
gl.uniform1i(spotLightCountLoc, this.spotLights.length);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// -- Set Texture Sizes --
|
|
441
|
+
const dirLightTextureSizeLoc = gl.getUniformLocation(program, "uDirectionalLightTextureSize");
|
|
442
|
+
if (dirLightTextureSizeLoc !== null) {
|
|
443
|
+
const pixelsPerLight = 3; // Each light takes 3 pixels in the texture
|
|
444
|
+
const textureWidth = Math.max(1, this.directionalLights.length * pixelsPerLight);
|
|
445
|
+
gl.uniform2f(dirLightTextureSizeLoc, textureWidth, 1);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const pointLightTextureSizeLoc = gl.getUniformLocation(program, "uPointLightTextureSize");
|
|
449
|
+
if (pointLightTextureSizeLoc !== null) {
|
|
450
|
+
const pixelsPerLight = 3; // Each light takes 3 pixels in the texture
|
|
451
|
+
const textureWidth = Math.max(1, this.pointLights.length * pixelsPerLight);
|
|
452
|
+
gl.uniform2f(pointLightTextureSizeLoc, textureWidth, 1);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// -- Textures are bound by ObjectRenderer3D --
|
|
456
|
+
// The actual texture binding happens in ObjectRenderer3D.drawObject
|
|
457
|
+
// to avoid sampler conflicts and ensure proper WebGL texture units
|
|
458
|
+
|
|
459
|
+
// -- Apply Legacy Light Uniforms for Backward Compatibility --
|
|
460
|
+
// Main directional light shadow
|
|
461
|
+
const mainLight = this.getMainDirectionalLight();
|
|
462
|
+
if (mainLight) {
|
|
463
|
+
// Still call applyToShader for uniforms that aren't in textures yet
|
|
464
|
+
mainLight.applyToShader(program, 0);
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
// Make sure shader knows there's no main directional light
|
|
468
|
+
const shadowsEnabledLoc = gl.getUniformLocation(program, "uShadowsEnabled");
|
|
469
|
+
if (shadowsEnabledLoc !== null) {
|
|
470
|
+
gl.uniform1i(shadowsEnabledLoc, 0); // 0 = false
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Apply shadow maps for point lights (up to 4 with shadow mapping)
|
|
475
|
+
// This sets the light-specific uniforms in the shader
|
|
476
|
+
for (let i = 0; i < Math.min(this.pointLights.length, 4); i++) {
|
|
477
|
+
const light = this.pointLights[i];
|
|
478
|
+
if (light) {
|
|
479
|
+
// Remove logging to reduce console spam
|
|
480
|
+
light.applyToShader(program, i);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Apply shadow quality preset to all shadow-casting lights
|
|
488
|
+
* @param {number} presetIndex - Index of the preset to apply
|
|
489
|
+
*/
|
|
490
|
+
setShadowQuality(presetIndex) {
|
|
491
|
+
// Apply to all directional lights
|
|
492
|
+
for (const light of this.directionalLights) {
|
|
493
|
+
if (light.getShadowsEnabled()) {
|
|
494
|
+
light.setQualityPreset(presetIndex);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Apply to all point lights
|
|
499
|
+
for (const light of this.pointLights) {
|
|
500
|
+
if (light.getShadowsEnabled()) {
|
|
501
|
+
light.setQualityPreset(presetIndex);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get shadow map size from the main directional light
|
|
508
|
+
* @returns {number} - The shadow map size
|
|
509
|
+
*/
|
|
510
|
+
getShadowMapSize() {
|
|
511
|
+
const mainLight = this.getMainDirectionalLight();
|
|
512
|
+
return mainLight ? mainLight.shadowMapSize : this.constants.SHADOW_MAP.SIZE.value;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Get shadow bias from the main directional light
|
|
517
|
+
* @returns {number} - The shadow bias
|
|
518
|
+
*/
|
|
519
|
+
getShadowBias() {
|
|
520
|
+
const mainLight = this.getMainDirectionalLight();
|
|
521
|
+
return mainLight ? mainLight.shadowBias : this.constants.SHADOW_MAP.BIAS.value;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Cleanup and dispose of all lights
|
|
526
|
+
*/
|
|
527
|
+
/**
|
|
528
|
+
* Initialize light data textures for all light types
|
|
529
|
+
*/
|
|
530
|
+
initializeLightDataTextures() {
|
|
531
|
+
const gl = this.gl;
|
|
532
|
+
|
|
533
|
+
// Create a texture for directional light data
|
|
534
|
+
this.directionalLightDataTexture = gl.createTexture();
|
|
535
|
+
gl.bindTexture(gl.TEXTURE_2D, this.directionalLightDataTexture);
|
|
536
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
537
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
538
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
539
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
540
|
+
|
|
541
|
+
// Create initial empty texture data (will be updated later)
|
|
542
|
+
this.createEmptyFloatTexture(this.directionalLightDataTexture, 1, 1);
|
|
543
|
+
|
|
544
|
+
// Create a texture for point light data
|
|
545
|
+
this.pointLightDataTexture = gl.createTexture();
|
|
546
|
+
gl.bindTexture(gl.TEXTURE_2D, this.pointLightDataTexture);
|
|
547
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
548
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
549
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
550
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
551
|
+
|
|
552
|
+
// Create initial empty texture data (will be updated later)
|
|
553
|
+
this.createEmptyFloatTexture(this.pointLightDataTexture, 1, 1);
|
|
554
|
+
|
|
555
|
+
// Unbind texture
|
|
556
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Helper method to create an empty float texture with fallbacks for various WebGL implementations
|
|
561
|
+
* @param {WebGLTexture} texture - The texture object to initialize
|
|
562
|
+
* @param {number} width - Width of the texture
|
|
563
|
+
* @param {number} height - Height of the texture
|
|
564
|
+
* @param {Float32Array} data - Optional data to fill the texture with
|
|
565
|
+
* @returns {boolean} - Whether the texture was created successfully
|
|
566
|
+
*/
|
|
567
|
+
createEmptyFloatTexture(texture, width, height, data = null) {
|
|
568
|
+
const gl = this.gl;
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
// Try different approaches based on WebGL version and capabilities
|
|
572
|
+
if (this.isWebGL2) {
|
|
573
|
+
// Try high precision internal formats for WebGL2 first
|
|
574
|
+
try {
|
|
575
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, width, height, 0, gl.RGBA, gl.FLOAT, data);
|
|
576
|
+
// No logging to reduce console spam
|
|
577
|
+
return true;
|
|
578
|
+
} catch (e) {
|
|
579
|
+
// If RGBA32F fails, try RGBA16F
|
|
580
|
+
try {
|
|
581
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, width, height, 0, gl.RGBA, gl.FLOAT, data);
|
|
582
|
+
// No logging to reduce console spam
|
|
583
|
+
return true;
|
|
584
|
+
} catch (e2) {
|
|
585
|
+
// Last resort for WebGL2 - try standard RGBA format
|
|
586
|
+
console.warn('[LightManager] High precision formats not supported, falling back to standard RGBA with gl.FLOAT');
|
|
587
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, data);
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
// For WebGL1, we need OES_texture_float extension
|
|
593
|
+
const ext = gl.getExtension('OES_texture_float');
|
|
594
|
+
if (ext) {
|
|
595
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, data);
|
|
596
|
+
// No logging to reduce console spam
|
|
597
|
+
return true;
|
|
598
|
+
} else {
|
|
599
|
+
// If float textures aren't supported, fall back to UNSIGNED_BYTE
|
|
600
|
+
console.warn('[LightManager] Float textures not supported, falling back to UNSIGNED_BYTE');
|
|
601
|
+
|
|
602
|
+
// If data was provided, convert it to UNSIGNED_BYTE
|
|
603
|
+
if (data) {
|
|
604
|
+
const byteData = new Uint8Array(data.length);
|
|
605
|
+
for (let i = 0; i < data.length; i++) {
|
|
606
|
+
// Scale float values (typically 0-1) to byte range (0-255)
|
|
607
|
+
byteData[i] = Math.min(255, Math.max(0, Math.floor(data[i] * 255)));
|
|
608
|
+
}
|
|
609
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, byteData);
|
|
610
|
+
} else {
|
|
611
|
+
// Just create an empty texture
|
|
612
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
613
|
+
}
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
} catch (err) {
|
|
618
|
+
// Final fallback if everything else fails
|
|
619
|
+
console.error('[LightManager] Error creating float texture:', err, 'Using UNSIGNED_BYTE fallback');
|
|
620
|
+
|
|
621
|
+
// Convert to UNSIGNED_BYTE as last resort
|
|
622
|
+
try {
|
|
623
|
+
if (data) {
|
|
624
|
+
const byteData = new Uint8Array(data.length);
|
|
625
|
+
for (let i = 0; i < data.length; i++) {
|
|
626
|
+
byteData[i] = Math.min(255, Math.max(0, Math.floor(data[i] * 255)));
|
|
627
|
+
}
|
|
628
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, byteData);
|
|
629
|
+
} else {
|
|
630
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
631
|
+
}
|
|
632
|
+
return false;
|
|
633
|
+
} catch (e) {
|
|
634
|
+
console.error('[LightManager] Critical error creating texture:', e);
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Update the directional light data texture
|
|
642
|
+
*/
|
|
643
|
+
updateDirectionalLightDataTexture() {
|
|
644
|
+
const gl = this.gl;
|
|
645
|
+
|
|
646
|
+
// Skip if there are no directional lights
|
|
647
|
+
if (this.directionalLights.length === 0) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Each light needs multiple pixels for all its data
|
|
652
|
+
// Position: RGBA (xyz, enabled)
|
|
653
|
+
// Direction: RGBA (xyz, shadowEnabled)
|
|
654
|
+
// Color+Intensity: RGBA (rgb, intensity)
|
|
655
|
+
// We'll use 3 horizontal pixels per light
|
|
656
|
+
|
|
657
|
+
const pixelsPerLight = 3;
|
|
658
|
+
const textureWidth = Math.max(1, this.directionalLights.length * pixelsPerLight);
|
|
659
|
+
const textureHeight = 1; // Just one row needed
|
|
660
|
+
|
|
661
|
+
// Create a Float32Array to hold all light data
|
|
662
|
+
const data = new Float32Array(textureWidth * textureHeight * 4); // 4 components per pixel (RGBA)
|
|
663
|
+
|
|
664
|
+
// Fill the data array with light properties
|
|
665
|
+
for (let i = 0; i < this.directionalLights.length; i++) {
|
|
666
|
+
const light = this.directionalLights[i];
|
|
667
|
+
const baseIndex = i * pixelsPerLight * 4; // Each pixel has 4 components (RGBA)
|
|
668
|
+
|
|
669
|
+
// First pixel: Position (xyz) + enabled flag
|
|
670
|
+
data[baseIndex] = light.position.x;
|
|
671
|
+
data[baseIndex + 1] = light.position.y;
|
|
672
|
+
data[baseIndex + 2] = light.position.z;
|
|
673
|
+
data[baseIndex + 3] = 1.0; // Enabled
|
|
674
|
+
|
|
675
|
+
// Second pixel: Direction (xyz) + shadow enabled flag
|
|
676
|
+
data[baseIndex + 4] = light.direction.x;
|
|
677
|
+
data[baseIndex + 5] = light.direction.y;
|
|
678
|
+
data[baseIndex + 6] = light.direction.z;
|
|
679
|
+
data[baseIndex + 7] = light.getShadowsEnabled() ? 1.0 : 0.0;
|
|
680
|
+
|
|
681
|
+
// Third pixel: Color (rgb) + intensity
|
|
682
|
+
const color = light.getColor();
|
|
683
|
+
data[baseIndex + 8] = color.x;
|
|
684
|
+
data[baseIndex + 9] = color.y;
|
|
685
|
+
data[baseIndex + 10] = color.z;
|
|
686
|
+
data[baseIndex + 11] = light.intensity;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Upload data to the texture
|
|
690
|
+
gl.bindTexture(gl.TEXTURE_2D, this.directionalLightDataTexture);
|
|
691
|
+
this.createEmptyFloatTexture(this.directionalLightDataTexture, textureWidth, textureHeight, data);
|
|
692
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Update the point light data texture
|
|
697
|
+
*/
|
|
698
|
+
updatePointLightDataTexture() {
|
|
699
|
+
const gl = this.gl;
|
|
700
|
+
|
|
701
|
+
// Skip if there are no point lights
|
|
702
|
+
if (this.pointLights.length === 0) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Each light needs multiple pixels for all its data
|
|
707
|
+
// Position: RGBA (xyz, enabled)
|
|
708
|
+
// Color+Intensity: RGBA (rgb, intensity)
|
|
709
|
+
// Radius+Shadow: RGBA (radius, shadowEnabled, 0, 0)
|
|
710
|
+
// We'll use 3 horizontal pixels per light
|
|
711
|
+
|
|
712
|
+
const pixelsPerLight = 3;
|
|
713
|
+
const textureWidth = Math.max(1, this.pointLights.length * pixelsPerLight);
|
|
714
|
+
const textureHeight = 1; // Just one row needed
|
|
715
|
+
|
|
716
|
+
// Create a Float32Array to hold all light data
|
|
717
|
+
const data = new Float32Array(textureWidth * textureHeight * 4); // 4 components per pixel (RGBA)
|
|
718
|
+
|
|
719
|
+
// Fill the data array with light properties
|
|
720
|
+
for (let i = 0; i < this.pointLights.length; i++) {
|
|
721
|
+
const light = this.pointLights[i];
|
|
722
|
+
const baseIndex = i * pixelsPerLight * 4; // Each pixel has 4 components (RGBA)
|
|
723
|
+
|
|
724
|
+
// First pixel: Position (xyz) + enabled flag
|
|
725
|
+
data[baseIndex] = light.position.x;
|
|
726
|
+
data[baseIndex + 1] = light.position.y;
|
|
727
|
+
data[baseIndex + 2] = light.position.z;
|
|
728
|
+
data[baseIndex + 3] = 1.0; // Enabled
|
|
729
|
+
|
|
730
|
+
// Second pixel: Color (rgb) + intensity
|
|
731
|
+
const color = light.getColor();
|
|
732
|
+
data[baseIndex + 4] = color.x;
|
|
733
|
+
data[baseIndex + 5] = color.y;
|
|
734
|
+
data[baseIndex + 6] = color.z;
|
|
735
|
+
data[baseIndex + 7] = light.intensity;
|
|
736
|
+
|
|
737
|
+
// Third pixel: Radius + shadow enabled flag + padding
|
|
738
|
+
data[baseIndex + 8] = light.radius;
|
|
739
|
+
data[baseIndex + 9] = light.getShadowsEnabled() ? 1.0 : 0.0;
|
|
740
|
+
data[baseIndex + 10] = 0.0; // Padding
|
|
741
|
+
data[baseIndex + 11] = 0.0; // Padding
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Upload data to the texture
|
|
745
|
+
gl.bindTexture(gl.TEXTURE_2D, this.pointLightDataTexture);
|
|
746
|
+
this.createEmptyFloatTexture(this.pointLightDataTexture, textureWidth, textureHeight, data);
|
|
747
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Update all light data textures if needed
|
|
752
|
+
*/
|
|
753
|
+
updateLightDataTextures() {
|
|
754
|
+
if (this.lightDataDirty) {
|
|
755
|
+
this.updateDirectionalLightDataTexture();
|
|
756
|
+
this.updatePointLightDataTexture();
|
|
757
|
+
this.lightDataDirty = false;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
dispose() {
|
|
762
|
+
// Clean up all lights
|
|
763
|
+
for (const light of this.directionalLights) {
|
|
764
|
+
light.dispose();
|
|
765
|
+
}
|
|
766
|
+
this.directionalLights = [];
|
|
767
|
+
|
|
768
|
+
for (const light of this.pointLights) {
|
|
769
|
+
light.dispose();
|
|
770
|
+
}
|
|
771
|
+
this.pointLights = [];
|
|
772
|
+
|
|
773
|
+
for (const light of this.spotLights) {
|
|
774
|
+
light.dispose();
|
|
775
|
+
}
|
|
776
|
+
this.spotLights = [];
|
|
777
|
+
|
|
778
|
+
// Clean up light data textures
|
|
779
|
+
const gl = this.gl;
|
|
780
|
+
if (this.directionalLightDataTexture) {
|
|
781
|
+
gl.deleteTexture(this.directionalLightDataTexture);
|
|
782
|
+
this.directionalLightDataTexture = null;
|
|
783
|
+
}
|
|
784
|
+
if (this.pointLightDataTexture) {
|
|
785
|
+
gl.deleteTexture(this.pointLightDataTexture);
|
|
786
|
+
this.pointLightDataTexture = null;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|