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.
Files changed (93) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +348 -0
  3. package/actionengine/3rdparty/goblin/goblin.js +9609 -0
  4. package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
  5. package/actionengine/camera/actioncamera.js +90 -0
  6. package/actionengine/camera/cameracollisionhandler.js +69 -0
  7. package/actionengine/character/actioncharacter.js +360 -0
  8. package/actionengine/character/actioncharacter3D.js +61 -0
  9. package/actionengine/core/app.js +430 -0
  10. package/actionengine/debug/basedebugpanel.js +858 -0
  11. package/actionengine/display/canvasmanager.js +75 -0
  12. package/actionengine/display/gl/programmanager.js +570 -0
  13. package/actionengine/display/gl/shaders/lineshader.js +118 -0
  14. package/actionengine/display/gl/shaders/objectshader.js +1756 -0
  15. package/actionengine/display/gl/shaders/particleshader.js +43 -0
  16. package/actionengine/display/gl/shaders/shadowshader.js +319 -0
  17. package/actionengine/display/gl/shaders/spriteshader.js +100 -0
  18. package/actionengine/display/gl/shaders/watershader.js +67 -0
  19. package/actionengine/display/graphics/actionmodel3D.js +191 -0
  20. package/actionengine/display/graphics/actionsprite3D.js +230 -0
  21. package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
  22. package/actionengine/display/graphics/lighting/actionlight.js +211 -0
  23. package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
  24. package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
  25. package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
  26. package/actionengine/display/graphics/renderableobject.js +44 -0
  27. package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
  28. package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
  29. package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
  30. package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
  31. package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
  32. package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
  33. package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
  34. package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
  35. package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
  36. package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
  37. package/actionengine/display/graphics/texture/texturemanager.js +242 -0
  38. package/actionengine/display/graphics/texture/textureregistry.js +177 -0
  39. package/actionengine/input/actionscrollablearea.js +1405 -0
  40. package/actionengine/input/inputhandler.js +1647 -0
  41. package/actionengine/math/geometry/geometrybuilder.js +161 -0
  42. package/actionengine/math/geometry/glbexporter.js +364 -0
  43. package/actionengine/math/geometry/glbloader.js +722 -0
  44. package/actionengine/math/geometry/modelcodegenerator.js +97 -0
  45. package/actionengine/math/geometry/triangle.js +33 -0
  46. package/actionengine/math/geometry/triangleutils.js +34 -0
  47. package/actionengine/math/mathutils.js +25 -0
  48. package/actionengine/math/matrix4.js +785 -0
  49. package/actionengine/math/physics/actionphysics.js +108 -0
  50. package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
  51. package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
  52. package/actionengine/math/physics/actionraycast.js +129 -0
  53. package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
  54. package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
  55. package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
  56. package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
  57. package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
  58. package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
  59. package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
  60. package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
  61. package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
  62. package/actionengine/math/quaternion.js +61 -0
  63. package/actionengine/math/vector2.js +277 -0
  64. package/actionengine/math/vector3.js +318 -0
  65. package/actionengine/math/viewfrustum.js +136 -0
  66. package/actionengine/network/ACTIONNETREADME.md +810 -0
  67. package/actionengine/network/client/ActionNetManager.js +802 -0
  68. package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
  69. package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
  70. package/actionengine/network/client/SyncSystem.js +422 -0
  71. package/actionengine/network/p2p/ActionNetPeer.js +142 -0
  72. package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
  73. package/actionengine/network/p2p/DataConnection.js +282 -0
  74. package/actionengine/network/p2p/README.md +510 -0
  75. package/actionengine/network/p2p/example.html +502 -0
  76. package/actionengine/network/server/ActionNetServer.js +577 -0
  77. package/actionengine/network/server/ActionNetServerSSL.js +579 -0
  78. package/actionengine/network/server/ActionNetServerUtils.js +458 -0
  79. package/actionengine/network/server/SERVERREADME.md +314 -0
  80. package/actionengine/network/server/package-lock.json +35 -0
  81. package/actionengine/network/server/package.json +13 -0
  82. package/actionengine/network/server/start.bat +27 -0
  83. package/actionengine/network/server/start.sh +25 -0
  84. package/actionengine/network/server/startwss.bat +27 -0
  85. package/actionengine/sound/audiomanager.js +1589 -0
  86. package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
  87. package/actionengine/sound/soundfont/actionparser.js +718 -0
  88. package/actionengine/sound/soundfont/actionreverb.js +252 -0
  89. package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
  90. package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
  91. package/actionengine/sound/soundfont/soundfont.js +2 -0
  92. package/dist/action-engine.min.js +328 -0
  93. 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
+ }