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,862 @@
1
+ // actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js
2
+
3
+ /**
4
+ * Omnidirectional light with shadow mapping capability
5
+ * This light type simulates light emitting in all directions from a point,
6
+ * like a lightbulb or torch, with appropriate shadow casting.
7
+ */
8
+ class ActionOmnidirectionalShadowLight extends ActionLight {
9
+ /**
10
+ * Constructor for an omnidirectional shadow light
11
+ * @param {WebGLRenderingContext} gl - The WebGL rendering context
12
+ * @param {boolean} isWebGL2 - Flag indicating if WebGL2 is available
13
+ * @param {ProgramManager} programManager - Reference to the program manager for shader access
14
+ */
15
+ constructor(gl, isWebGL2, programManager) {
16
+ super(gl, isWebGL2);
17
+
18
+ this.programManager = programManager;
19
+
20
+ // Point light specific properties
21
+ this.radius = 100.0; // Light radius - affects attenuation
22
+
23
+ // Enable shadows by default for omnidirectional lights
24
+ this.castsShadows = true;
25
+
26
+ // Shadow map settings from constants
27
+ this.shadowMapSize = this.constants.POINT_LIGHT_SHADOW_MAP.SIZE.value;
28
+ this.shadowBias = this.constants.POINT_LIGHT_SHADOW_MAP.BIAS.value;
29
+
30
+ // Create matrices for shadow calculations (one per cubemap face)
31
+ this.lightProjectionMatrix = Matrix4.create();
32
+ this.lightViewMatrices = [];
33
+ for (let i = 0; i < 6; i++) {
34
+ this.lightViewMatrices.push(Matrix4.create());
35
+ }
36
+ this.lightSpaceMatrices = [];
37
+ for (let i = 0; i < 6; i++) {
38
+ this.lightSpaceMatrices.push(Matrix4.create());
39
+ }
40
+
41
+ // For tracking position changes
42
+ this._lastPosition = undefined;
43
+
44
+ // Initialize shadow map resources and shader program
45
+ if (this.castsShadows) {
46
+ this.setupShadowMap();
47
+ this.setupShadowShaderProgram();
48
+ this.createReusableBuffers();
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Set the light radius (affects attenuation)
54
+ * @param {number} radius - The new radius value
55
+ */
56
+ setRadius(radius) {
57
+ this.radius = radius;
58
+ }
59
+
60
+ /**
61
+ * Get the light radius
62
+ * @returns {number} - The current radius
63
+ */
64
+ getRadius() {
65
+ return this.radius;
66
+ }
67
+
68
+ /**
69
+ * Override the update method to check for position changes
70
+ * @returns {boolean} - Whether any properties changed this frame
71
+ */
72
+ update() {
73
+ let changed = super.update();
74
+
75
+ // If any properties changed and shadows are enabled,
76
+ // update the light space matrices
77
+ if (changed && this.castsShadows) {
78
+ this.updateLightSpaceMatrices();
79
+ }
80
+
81
+ return changed;
82
+ }
83
+
84
+ /**
85
+ * Set up shadow map framebuffer and texture
86
+ * Creates a cubemap texture for omnidirectional shadows
87
+ * @param {number} lightIndex - Index of the light (for multiple lights)
88
+ */
89
+ setupShadowMap(lightIndex = 0) {
90
+ const gl = this.gl;
91
+
92
+ // Delete any existing shadow framebuffer and texture
93
+ if (this.shadowFramebuffer) {
94
+ gl.deleteFramebuffer(this.shadowFramebuffer);
95
+ }
96
+ if (this.shadowTexture) {
97
+ gl.deleteTexture(this.shadowTexture);
98
+ }
99
+
100
+ // Create and bind the framebuffer
101
+ this.shadowFramebuffer = gl.createFramebuffer();
102
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.shadowFramebuffer);
103
+
104
+ // For WebGL2, use a depth cubemap
105
+ if (this.isWebGL2) {
106
+ // Create the shadow cubemap texture
107
+ this.shadowTexture = gl.createTexture();
108
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.shadowTexture);
109
+
110
+ // Initialize each face of the cubemap
111
+ const faces = [
112
+ gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
113
+ gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
114
+ gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
115
+ ];
116
+
117
+ for (const face of faces) {
118
+ // Use RGBA format for compatibility with both WebGL1 and WebGL2
119
+ gl.texImage2D(
120
+ face,
121
+ 0,
122
+ gl.RGBA,
123
+ this.shadowMapSize,
124
+ this.shadowMapSize,
125
+ 0,
126
+ gl.RGBA,
127
+ gl.UNSIGNED_BYTE,
128
+ null
129
+ );
130
+ }
131
+
132
+ // Set up texture parameters
133
+ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
134
+ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
135
+ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
136
+ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
137
+ gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
138
+
139
+ // Create and attach a renderbuffer for depth (we're not reading this)
140
+ this.depthBuffer = gl.createRenderbuffer();
141
+ gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthBuffer);
142
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.shadowMapSize, this.shadowMapSize);
143
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.depthBuffer);
144
+ }
145
+ else {
146
+ // WebGL1 doesn't support cubemap rendering, so we'll use 6 separate textures
147
+ this.shadowTextures = [];
148
+ this.shadowFramebuffers = [];
149
+ this.depthBuffers = [];
150
+
151
+ // Create 6 separate framebuffers and textures (one for each face)
152
+ for (let i = 0; i < 6; i++) {
153
+ const fbo = gl.createFramebuffer();
154
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
155
+
156
+ const texture = gl.createTexture();
157
+ gl.bindTexture(gl.TEXTURE_2D, texture);
158
+
159
+ gl.texImage2D(
160
+ gl.TEXTURE_2D,
161
+ 0,
162
+ gl.RGBA,
163
+ this.shadowMapSize,
164
+ this.shadowMapSize,
165
+ 0,
166
+ gl.RGBA,
167
+ gl.UNSIGNED_BYTE,
168
+ null
169
+ );
170
+
171
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
172
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
173
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
174
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
175
+
176
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
177
+
178
+ // Create and attach a renderbuffer for depth
179
+ const depthBuffer = gl.createRenderbuffer();
180
+ gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
181
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.shadowMapSize, this.shadowMapSize);
182
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
183
+
184
+ this.shadowTextures.push(texture);
185
+ this.shadowFramebuffers.push(fbo);
186
+ this.depthBuffers.push(depthBuffer);
187
+ }
188
+ }
189
+
190
+ // Store the light index for later use
191
+ this.lightIndex = lightIndex;
192
+
193
+ // Unbind the framebuffer
194
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
195
+ }
196
+
197
+ /**
198
+ * Create reusable buffers for shadow rendering
199
+ */
200
+ createReusableBuffers() {
201
+ // OPTIMIZED: Create shared shadow geometry buffer for static triangles
202
+ this.maxShadowTriangles = 500000; // Increased to 500k triangles to handle large scenes
203
+ this.maxShadowVertices = this.maxShadowTriangles * 3;
204
+
205
+ // Create shared static geometry buffer
206
+ this.staticShadowGeometry = {
207
+ positions: new Float32Array(this.maxShadowVertices * 3),
208
+ indices: new Uint16Array(this.maxShadowVertices),
209
+ currentVertexOffset: 0,
210
+ currentIndexOffset: 0
211
+ };
212
+
213
+ // Create GL buffers for static geometry
214
+ this.shadowBuffers = {
215
+ position: this.gl.createBuffer(),
216
+ index: this.gl.createBuffer()
217
+ };
218
+
219
+ // Object geometry tracking (shared across all cubemap faces)
220
+ this.objectGeometry = new Map(); // object -> {vertexOffset, indexOffset, indexCount, originalTriangles}
221
+
222
+ console.log(`[ActionOmnidirectionalShadowLight] Initialized static shadow geometry system for ${this.maxShadowTriangles} triangles`);
223
+ }
224
+ /**
225
+ * Initialize static shadow geometry for an object (called once per object)
226
+ * This uploads the object's original triangles to the shared static geometry buffer
227
+ * @param {Object} object - The object to initialize
228
+ */
229
+ initializeObjectShadowGeometry(object) {
230
+ // Skip if already initialized or no triangles
231
+ if (this.objectGeometry.has(object) || !object.triangles || object.triangles.length === 0) {
232
+ return;
233
+ }
234
+
235
+ // Use original triangles if available (for transform via model matrix)
236
+ // Otherwise fall back to current triangles
237
+ let sourceTriangles;
238
+ if (object._originalTriangles && object._originalTriangles.length > 0) {
239
+ sourceTriangles = object._originalTriangles; // Use untransformed triangles for physics objects
240
+ } else if (object.characterModel && object.characterModel.triangles) {
241
+ sourceTriangles = object.characterModel.triangles; // Use character model triangles
242
+ } else {
243
+ sourceTriangles = object.triangles; // Fallback to current triangles
244
+ }
245
+
246
+ const triangleCount = sourceTriangles.length;
247
+ const vertexCount = triangleCount * 3;
248
+
249
+ // Check if we have space in the static buffer
250
+ if (this.staticShadowGeometry.currentVertexOffset + vertexCount > this.maxShadowVertices) {
251
+ console.warn(`[OmnidirectionalShadowLight] Not enough space in static shadow buffer for object with ${triangleCount} triangles. Using fallback rendering.`);
252
+
253
+ // Mark this object to use fallback rendering (old method)
254
+ this.objectGeometry.set(object, { useFallback: true });
255
+ return;
256
+ }
257
+
258
+ const gl = this.gl;
259
+ const geometry = this.staticShadowGeometry;
260
+
261
+ // Store geometry info for this object
262
+ const geometryInfo = {
263
+ vertexOffset: geometry.currentVertexOffset,
264
+ indexOffset: geometry.currentIndexOffset,
265
+ indexCount: vertexCount,
266
+ triangleCount: triangleCount,
267
+ needsModelMatrix: true // Flag indicating this object needs model matrix transforms
268
+ };
269
+
270
+ // Fill geometry arrays with original triangle data
271
+ for (let i = 0; i < triangleCount; i++) {
272
+ const triangle = sourceTriangles[i];
273
+
274
+ for (let j = 0; j < 3; j++) {
275
+ const vertex = triangle.vertices[j];
276
+ const vertexIndex = (geometry.currentVertexOffset + i * 3 + j) * 3;
277
+
278
+ // Store original vertex positions (before any transformations)
279
+ geometry.positions[vertexIndex] = vertex.x;
280
+ geometry.positions[vertexIndex + 1] = vertex.y;
281
+ geometry.positions[vertexIndex + 2] = vertex.z;
282
+
283
+ // Set up indices
284
+ geometry.indices[geometry.currentIndexOffset + i * 3 + j] = geometry.currentVertexOffset + i * 3 + j;
285
+ }
286
+ }
287
+
288
+ // Update offsets for next object
289
+ geometry.currentVertexOffset += vertexCount;
290
+ geometry.currentIndexOffset += vertexCount;
291
+
292
+ // Store geometry info
293
+ this.objectGeometry.set(object, geometryInfo);
294
+
295
+ console.log(`[OmnidirectionalShadowLight] Initialized shadow geometry for object: ${triangleCount} triangles at offset ${geometryInfo.indexOffset}`);
296
+
297
+ // Mark that we need to upload the updated geometry buffer
298
+ this._geometryBufferDirty = true;
299
+ }
300
+
301
+ /**
302
+ * Upload the static geometry buffer to GPU (called when geometry changes)
303
+ */
304
+ uploadStaticGeometry() {
305
+ if (!this._geometryBufferDirty) {
306
+ return;
307
+ }
308
+
309
+ const gl = this.gl;
310
+ const geometry = this.staticShadowGeometry;
311
+
312
+ // Upload position data
313
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.shadowBuffers.position);
314
+ gl.bufferData(gl.ARRAY_BUFFER, geometry.positions.subarray(0, geometry.currentVertexOffset * 3), gl.STATIC_DRAW);
315
+
316
+ // Upload index data
317
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.shadowBuffers.index);
318
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geometry.indices.subarray(0, geometry.currentIndexOffset), gl.STATIC_DRAW);
319
+
320
+ this._geometryBufferDirty = false;
321
+ console.log(`[OmnidirectionalShadowLight] Uploaded static shadow geometry: ${geometry.currentVertexOffset} vertices, ${geometry.currentIndexOffset} indices`);
322
+ }
323
+
324
+ /**
325
+ * Set up shadow shader program and get all necessary locations
326
+ */
327
+ setupShadowShaderProgram() {
328
+ try {
329
+ const shadowShader = new ShadowShader();
330
+
331
+ // Create shadow map program with a distinct program name
332
+ this.shadowProgram = this.programManager.createShaderProgram(
333
+ shadowShader.getOmniShadowVertexShader(this.isWebGL2),
334
+ shadowShader.getOmniShadowFragmentShader(this.isWebGL2),
335
+ "omnidirectional_shadow_pass" // Distinct name from directional shadows
336
+ );
337
+
338
+ // Get attribute and uniform locations
339
+ this.shadowLocations = {
340
+ position: this.gl.getAttribLocation(this.shadowProgram, "aPosition"),
341
+ lightSpaceMatrix: this.gl.getUniformLocation(this.shadowProgram, "uLightSpaceMatrix"),
342
+ modelMatrix: this.gl.getUniformLocation(this.shadowProgram, "uModelMatrix"),
343
+ lightPos: this.gl.getUniformLocation(this.shadowProgram, "uLightPos"),
344
+ farPlane: this.gl.getUniformLocation(this.shadowProgram, "uFarPlane"),
345
+ debugShadowMap: this.gl.getUniformLocation(this.shadowProgram, "uDebugShadowMap"),
346
+ forceShadowMapTest: this.gl.getUniformLocation(this.shadowProgram, "uForceShadowMapTest"),
347
+ shadowMapSize: this.gl.getUniformLocation(this.shadowProgram, "uShadowMapSize")
348
+ };
349
+ } catch (error) {
350
+ console.error("Error setting up shadow shader program:", error);
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Updates light space matrices for all cubemap faces based on light position
356
+ * This creates the view and projection matrices needed for shadow mapping
357
+ */
358
+ updateLightSpaceMatrices() {
359
+ // For point light, use perspective projection with 90-degree FOV for each cube face
360
+ const aspect = 1.0; // Always 1.0 for cubemap faces
361
+ const near = 0.1;
362
+ const far = 500.0; // Should be large enough for your scene
363
+
364
+ // Create light projection matrix (perspective for point light)
365
+ Matrix4.perspective(
366
+ this.lightProjectionMatrix,
367
+ Math.PI / 2.0, // 90 degrees in radians
368
+ aspect,
369
+ near,
370
+ far
371
+ );
372
+
373
+ // Define the 6 view directions for cubemap faces
374
+ const directions = [
375
+ { target: [ 1, 0, 0], up: [0, -1, 0] }, // +X
376
+ { target: [-1, 0, 0], up: [0, -1, 0] }, // -X
377
+ { target: [ 0, 1, 0], up: [0, 0, 1] }, // +Y
378
+ { target: [ 0, -1, 0], up: [0, 0, -1] }, // -Y
379
+ { target: [ 0, 0, 1], up: [0, -1, 0] }, // +Z
380
+ { target: [ 0, 0, -1], up: [0, -1, 0] } // -Z
381
+ ];
382
+
383
+ // Create view matrices for each direction
384
+ for (let i = 0; i < 6; i++) {
385
+ const target = [
386
+ this.position.x + directions[i].target[0],
387
+ this.position.y + directions[i].target[1],
388
+ this.position.z + directions[i].target[2]
389
+ ];
390
+
391
+ Matrix4.lookAt(
392
+ this.lightViewMatrices[i],
393
+ this.position.toArray(),
394
+ target,
395
+ directions[i].up
396
+ );
397
+
398
+ // Combine into light space matrix
399
+ Matrix4.multiply(
400
+ this.lightSpaceMatrices[i],
401
+ this.lightProjectionMatrix,
402
+ this.lightViewMatrices[i]
403
+ );
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Begin shadow map rendering pass for a specific face
409
+ * @param {number} faceIndex - Index of the cube face to render (0-5)
410
+ * @param {number} lightIndex - Index of the light (for multiple lights)
411
+ */
412
+ beginShadowPass(faceIndex, lightIndex = 0) {
413
+ const gl = this.gl;
414
+
415
+ // Save current viewport
416
+ if (!this._savedViewport) {
417
+ this._savedViewport = gl.getParameter(gl.VIEWPORT);
418
+ }
419
+
420
+ // Reset static geometry binding flag for this shadow pass
421
+ this._staticGeometryBound = false;
422
+
423
+ // Create shadow maps on demand if they don't exist for this light
424
+ if (!this.shadowFramebuffer && !this.shadowTexture) {
425
+ this.setupShadowMap(lightIndex);
426
+ }
427
+
428
+ if (this.isWebGL2) {
429
+ // Bind shadow framebuffer
430
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.shadowFramebuffer);
431
+
432
+ // Set the appropriate cubemap face as the color attachment
433
+ const faces = [
434
+ gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
435
+ gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
436
+ gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
437
+ ];
438
+
439
+ gl.framebufferTexture2D(
440
+ gl.FRAMEBUFFER,
441
+ gl.COLOR_ATTACHMENT0,
442
+ faces[faceIndex],
443
+ this.shadowTexture,
444
+ 0
445
+ );
446
+ } else {
447
+ // In WebGL1, use the corresponding framebuffer for this face
448
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.shadowFramebuffers[faceIndex]);
449
+ }
450
+
451
+ gl.viewport(0, 0, this.shadowMapSize, this.shadowMapSize);
452
+
453
+ // Clear the framebuffer
454
+ gl.clearColor(1.0, 1.0, 1.0, 1.0); // White (far depth)
455
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
456
+
457
+ // Use shadow mapping program
458
+ gl.useProgram(this.shadowProgram);
459
+
460
+ // Set light space matrix uniform for this face
461
+ gl.uniformMatrix4fv(this.shadowLocations.lightSpaceMatrix, false, this.lightSpaceMatrices[faceIndex]);
462
+
463
+ // Set light position uniform
464
+ gl.uniform3f(this.shadowLocations.lightPos, this.position.x, this.position.y, this.position.z);
465
+
466
+ // Set far plane uniform
467
+ gl.uniform1f(this.shadowLocations.farPlane, 500.0);
468
+
469
+ // Set debug shadow map uniform if available
470
+ if (this.shadowLocations.debugShadowMap !== null) {
471
+ const debugMode = this.constants.DEBUG.VISUALIZE_SHADOW_MAP ? 1 : 0;
472
+ gl.uniform1i(this.shadowLocations.debugShadowMap, debugMode);
473
+ }
474
+
475
+ // Set force shadow map test uniform if available
476
+ if (this.shadowLocations.forceShadowMapTest !== null) {
477
+ const forceTest = this.constants.DEBUG.FORCE_SHADOW_MAP_TEST ? 1 : 0;
478
+ gl.uniform1i(this.shadowLocations.forceShadowMapTest, forceTest);
479
+ }
480
+
481
+ // Set shadow map size uniform
482
+ if (this.shadowLocations.shadowMapSize !== null) {
483
+ gl.uniform1f(this.shadowLocations.shadowMapSize, this.shadowMapSize);
484
+ }
485
+ }
486
+
487
+ /**
488
+ * End shadow map rendering pass and restore previous state
489
+ */
490
+ endShadowPass() {
491
+ const gl = this.gl;
492
+
493
+ // Unbind shadow framebuffer
494
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
495
+
496
+ // Restore viewport
497
+ if (this._savedViewport) {
498
+ gl.viewport(this._savedViewport[0], this._savedViewport[1], this._savedViewport[2], this._savedViewport[3]);
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Helper method to fill batched shadow data
504
+ * @param {Array} validObjects - Array of valid objects with metadata
505
+ */
506
+ fillBatchedShadowData(validObjects) {
507
+ let vertexOffset = 0;
508
+
509
+ for (const { object, triangleCount } of validObjects) {
510
+ const triangles = object.triangles;
511
+
512
+ for (let i = 0; i < triangles.length; i++) {
513
+ const triangle = triangles[i];
514
+
515
+ for (let j = 0; j < 3; j++) {
516
+ const vertex = triangle.vertices[j];
517
+ const baseIndex = (vertexOffset + i * 3 + j) * 3;
518
+
519
+ this.persistentShadowArrays.positions[baseIndex] = vertex.x;
520
+ this.persistentShadowArrays.positions[baseIndex + 1] = vertex.y;
521
+ this.persistentShadowArrays.positions[baseIndex + 2] = vertex.z;
522
+
523
+ this.persistentShadowArrays.indices[vertexOffset + i * 3 + j] = vertexOffset + i * 3 + j;
524
+ }
525
+ }
526
+
527
+ vertexOffset += triangleCount * 3;
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Render a single object to the shadow map for a specific face
533
+ * @param {Object} object - Single object to render
534
+ */
535
+ renderObjectToShadowMap(object) {
536
+ const gl = this.gl;
537
+ const triangles = object.triangles;
538
+
539
+ // Skip if object has no triangles
540
+ if (!triangles || triangles.length === 0) {
541
+ return;
542
+ }
543
+
544
+ // Use identity model matrix since triangles are already in world space
545
+ const modelMatrix = Matrix4.create();
546
+ gl.uniformMatrix4fv(this.shadowLocations.modelMatrix, false, modelMatrix);
547
+
548
+ // Calculate total vertices and indices
549
+ const totalVertices = triangles.length * 3;
550
+
551
+ // Only allocate new arrays if needed or if size has changed
552
+ if (!this._positionsArray || this._positionsArray.length < totalVertices * 3) {
553
+ this._positionsArray = new Float32Array(totalVertices * 3);
554
+ }
555
+ if (!this._indicesArray || this._indicesArray.length < totalVertices) {
556
+ this._indicesArray = new Uint16Array(totalVertices);
557
+ }
558
+
559
+ // Fill position and index arrays
560
+ for (let i = 0; i < triangles.length; i++) {
561
+ const triangle = triangles[i];
562
+
563
+ // Process vertices
564
+ for (let j = 0; j < 3; j++) {
565
+ const vertex = triangle.vertices[j];
566
+ const baseIndex = (i * 3 + j) * 3;
567
+
568
+ this._positionsArray[baseIndex] = vertex.x;
569
+ this._positionsArray[baseIndex + 1] = vertex.y;
570
+ this._positionsArray[baseIndex + 2] = vertex.z;
571
+
572
+ // Set up indices
573
+ this._indicesArray[i * 3 + j] = i * 3 + j;
574
+ }
575
+ }
576
+
577
+ // Use buffer orphaning to avoid stalls
578
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.shadowBuffers.position);
579
+ gl.bufferData(gl.ARRAY_BUFFER, this._positionsArray.byteLength, gl.DYNAMIC_DRAW);
580
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._positionsArray.subarray(0, totalVertices * 3));
581
+
582
+ // Set up position attribute
583
+ gl.vertexAttribPointer(this.shadowLocations.position, 3, gl.FLOAT, false, 0, 0);
584
+ gl.enableVertexAttribArray(this.shadowLocations.position);
585
+
586
+ // Upload index data
587
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.shadowBuffers.index);
588
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this._indicesArray.byteLength, gl.DYNAMIC_DRAW);
589
+ gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, this._indicesArray.subarray(0, totalVertices));
590
+
591
+ // Draw object
592
+ gl.drawElements(gl.TRIANGLES, totalVertices, gl.UNSIGNED_SHORT, 0);
593
+ }
594
+ /**
595
+ * Get the model matrix for an object based on its current physics state
596
+ * @param {Object} object - The object to get matrix for
597
+ * @returns {Float32Array} - The model matrix
598
+ */
599
+ getObjectModelMatrix(object) {
600
+ const modelMatrix = Matrix4.create();
601
+
602
+ // For physics objects, use the body's current position and rotation
603
+ if (object.body) {
604
+ const pos = object.body.position;
605
+ const rot = object.body.rotation;
606
+
607
+ // Apply translation
608
+ Matrix4.translate(modelMatrix, modelMatrix, [pos.x, pos.y, pos.z]);
609
+
610
+ // Apply rotation from physics body quaternion
611
+ const rotationMatrix = Matrix4.create();
612
+ Matrix4.fromQuat(rotationMatrix, [rot.x, rot.y, rot.z, rot.w]);
613
+ Matrix4.multiply(modelMatrix, modelMatrix, rotationMatrix);
614
+ }
615
+ // For objects with manual position/rotation
616
+ else if (object.position) {
617
+ Matrix4.translate(modelMatrix, modelMatrix, [object.position.x, object.position.y, object.position.z]);
618
+
619
+ if (object.rotation !== undefined) {
620
+ Matrix4.rotateY(modelMatrix, modelMatrix, object.rotation);
621
+ }
622
+ }
623
+
624
+ return modelMatrix;
625
+ }
626
+ /**
627
+ * Fallback rendering method for objects that don't fit in static buffer
628
+ * Uses the original dynamic triangle upload approach
629
+ * @param {Object} object - The object to render
630
+ */
631
+ renderObjectToShadowMapFallback(object) {
632
+ const gl = this.gl;
633
+ const triangles = object.triangles;
634
+
635
+ // Skip if object has no triangles
636
+ if (!triangles || triangles.length === 0) {
637
+ return;
638
+ }
639
+
640
+ // Set model matrix for this object (identity since triangles are already transformed)
641
+ const modelMatrix = Matrix4.create();
642
+ gl.uniformMatrix4fv(this.shadowLocations.modelMatrix, false, modelMatrix);
643
+
644
+ // Calculate total vertices and indices
645
+ const totalVertices = triangles.length * 3;
646
+
647
+ // Only allocate new arrays if needed or if size has changed
648
+ if (!this._fallbackPositionsArray || this._fallbackPositionsArray.length < totalVertices * 3) {
649
+ this._fallbackPositionsArray = new Float32Array(totalVertices * 3);
650
+ }
651
+ if (!this._fallbackIndicesArray || this._fallbackIndicesArray.length < totalVertices) {
652
+ this._fallbackIndicesArray = new Uint16Array(totalVertices);
653
+ }
654
+
655
+ // Fill position and index arrays
656
+ for (let i = 0; i < triangles.length; i++) {
657
+ const triangle = triangles[i];
658
+
659
+ // Process vertices
660
+ for (let j = 0; j < 3; j++) {
661
+ const vertex = triangle.vertices[j];
662
+ const baseIndex = (i * 3 + j) * 3;
663
+
664
+ this._fallbackPositionsArray[baseIndex] = vertex.x;
665
+ this._fallbackPositionsArray[baseIndex + 1] = vertex.y;
666
+ this._fallbackPositionsArray[baseIndex + 2] = vertex.z;
667
+
668
+ // Set up indices
669
+ this._fallbackIndicesArray[i * 3 + j] = i * 3 + j;
670
+ }
671
+ }
672
+
673
+ // Create temporary buffers for fallback rendering
674
+ if (!this._fallbackBuffers) {
675
+ this._fallbackBuffers = {
676
+ position: gl.createBuffer(),
677
+ index: gl.createBuffer()
678
+ };
679
+ }
680
+
681
+ // Bind and upload position data to fallback buffer
682
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._fallbackBuffers.position);
683
+ gl.bufferData(gl.ARRAY_BUFFER, this._fallbackPositionsArray, gl.DYNAMIC_DRAW);
684
+
685
+ // Set up position attribute
686
+ gl.vertexAttribPointer(this.shadowLocations.position, 3, gl.FLOAT, false, 0, 0);
687
+ gl.enableVertexAttribArray(this.shadowLocations.position);
688
+
689
+ // Bind and upload index data to fallback buffer
690
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._fallbackBuffers.index);
691
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this._fallbackIndicesArray, gl.DYNAMIC_DRAW);
692
+
693
+ // Draw object using fallback method
694
+ gl.drawElements(gl.TRIANGLES, totalVertices, gl.UNSIGNED_SHORT, 0);
695
+
696
+ // Reset static geometry binding flag since we used different buffers
697
+ this._staticGeometryBound = false;
698
+ }
699
+
700
+
701
+ /**
702
+ * Get the light space matrix for a specific face
703
+ * @param {number} faceIndex - Index of the face (0-5)
704
+ * @returns {Float32Array} - The light space transformation matrix
705
+ */
706
+ getLightSpaceMatrix(faceIndex = 0) {
707
+ return this.lightSpaceMatrices[Math.min(faceIndex, 5)];
708
+ }
709
+
710
+ /**
711
+ * Apply this light's uniforms to a shader program
712
+ * @param {WebGLProgram} program - The shader program
713
+ * @param {number} index - Index of this light in an array of lights (for future multi-light support)
714
+ */
715
+ applyToShader(program, index = 0) {
716
+ const gl = this.gl;
717
+
718
+ // Use indexed uniform names for lights beyond the first one
719
+ const indexSuffix = index > 0 ? index.toString() : '';
720
+
721
+ // Select the right uniform names based on index
722
+ let posUniform, intensityUniform, radiusUniform, shadowMapUniform, shadowsEnabledUniform;
723
+
724
+ if (index === 0) {
725
+ // First light uses legacy names (no suffix)
726
+ posUniform = "uPointLightPos";
727
+ intensityUniform = "uPointLightIntensity";
728
+ radiusUniform = "uLightRadius"; // Different name for first light!
729
+ shadowMapUniform = "uPointShadowMap";
730
+ shadowsEnabledUniform = "uPointShadowsEnabled";
731
+ } else {
732
+ // Additional lights use indexed names
733
+ posUniform = `uPointLightPos${indexSuffix}`;
734
+ intensityUniform = `uPointLightIntensity${indexSuffix}`;
735
+ radiusUniform = `uPointLightRadius${indexSuffix}`;
736
+ shadowMapUniform = `uPointShadowMap${indexSuffix}`;
737
+ shadowsEnabledUniform = `uPointShadowsEnabled${indexSuffix}`;
738
+ }
739
+
740
+ // Get uniform locations
741
+ const lightPosLoc = gl.getUniformLocation(program, posUniform);
742
+ const lightIntensityLoc = gl.getUniformLocation(program, intensityUniform);
743
+ const lightRadiusLoc = gl.getUniformLocation(program, radiusUniform);
744
+ const shadowMapLoc = gl.getUniformLocation(program, shadowMapUniform);
745
+ const shadowsEnabledLoc = gl.getUniformLocation(program, shadowsEnabledUniform);
746
+ const shadowBiasLoc = gl.getUniformLocation(program, "uShadowBias");
747
+ const farPlaneLoc = gl.getUniformLocation(program, "uFarPlane");
748
+
749
+ // Detailed logs commented out to reduce console noise
750
+ /*
751
+ // Keep minimal logs for light setup
752
+ if (index === 0) {
753
+ console.log(`[PointLight:${index}] Setting up primary light`);
754
+ } else {
755
+ console.log(`[PointLight:${index}] Setting up additional light`);
756
+ }
757
+
758
+ console.log(`[PointLight:${index}] Setting up with: ${posUniform}, ${intensityUniform}, ${radiusUniform}`);
759
+ console.log(`Light position: (${this.position.x.toFixed(2)}, ${this.position.y.toFixed(2)}, ${this.position.z.toFixed(2)})`);
760
+ console.log(`Light intensity: ${this.intensity.toFixed(2)}, Light radius: ${this.radius.toFixed(2)}, Shadows: ${this.castsShadows}`);
761
+ */
762
+
763
+
764
+ // Set light position
765
+ if (lightPosLoc !== null) {
766
+ gl.uniform3f(lightPosLoc, this.position.x, this.position.y, this.position.z);
767
+ }
768
+
769
+ // Set light intensity
770
+ if (lightIntensityLoc !== null) {
771
+ gl.uniform1f(lightIntensityLoc, this.intensity);
772
+ }
773
+
774
+ // Set light radius
775
+ if (lightRadiusLoc !== null) {
776
+ gl.uniform1f(lightRadiusLoc, this.radius);
777
+ }
778
+
779
+ // Apply shadow mapping uniforms if shadows are enabled
780
+ if (this.castsShadows) {
781
+ //console.log(`[PointLight:${index}] Shadows enabled, shadowsEnabledLoc: ${shadowsEnabledLoc}, shadowMapLoc: ${shadowMapLoc}`);
782
+
783
+ // Set shadows enabled flag
784
+ if (shadowsEnabledLoc !== null) {
785
+ gl.uniform1i(shadowsEnabledLoc, 1); // 1 = true
786
+ //console.log(`[PointLight:${index}] Set ${shadowsEnabledUniform} to true`);
787
+ }
788
+
789
+ // Set shadow bias
790
+ if (shadowBiasLoc !== null) {
791
+ gl.uniform1f(shadowBiasLoc, this.shadowBias);
792
+ }
793
+
794
+ // Set far plane
795
+ if (farPlaneLoc !== null) {
796
+ gl.uniform1f(farPlaneLoc, 500.0);
797
+ }
798
+ } else if (shadowsEnabledLoc !== null) {
799
+ // Shadows are disabled for this light
800
+ gl.uniform1i(shadowsEnabledLoc, 0); // 0 = false
801
+ //console.log(`[PointLight:${index}] Shadows disabled, set ${shadowsEnabledUniform} to false`);
802
+ }
803
+ }
804
+
805
+ /**
806
+ * Cleanup resources used by this light
807
+ */
808
+ dispose() {
809
+ const gl = this.gl;
810
+
811
+ // Clean up shadow map resources
812
+ if (this.shadowFramebuffer) {
813
+ gl.deleteFramebuffer(this.shadowFramebuffer);
814
+ this.shadowFramebuffer = null;
815
+ }
816
+
817
+ if (this.shadowTexture) {
818
+ gl.deleteTexture(this.shadowTexture);
819
+ this.shadowTexture = null;
820
+ }
821
+
822
+ if (this.isWebGL2) {
823
+ if (this.depthBuffer) {
824
+ gl.deleteRenderbuffer(this.depthBuffer);
825
+ this.depthBuffer = null;
826
+ }
827
+ } else {
828
+ // Clean up WebGL1 resources (multiple framebuffers and textures)
829
+ if (this.shadowFramebuffers) {
830
+ for (const fbo of this.shadowFramebuffers) {
831
+ gl.deleteFramebuffer(fbo);
832
+ }
833
+ this.shadowFramebuffers = null;
834
+ }
835
+
836
+ if (this.shadowTextures) {
837
+ for (const texture of this.shadowTextures) {
838
+ gl.deleteTexture(texture);
839
+ }
840
+ this.shadowTextures = null;
841
+ }
842
+
843
+ if (this.depthBuffers) {
844
+ for (const depthBuffer of this.depthBuffers) {
845
+ gl.deleteRenderbuffer(depthBuffer);
846
+ }
847
+ this.depthBuffers = null;
848
+ }
849
+ }
850
+
851
+ // Clean up buffers
852
+ if (this.shadowBuffers) {
853
+ if (this.shadowBuffers.position) {
854
+ gl.deleteBuffer(this.shadowBuffers.position);
855
+ }
856
+ if (this.shadowBuffers.index) {
857
+ gl.deleteBuffer(this.shadowBuffers.index);
858
+ }
859
+ this.shadowBuffers = null;
860
+ }
861
+ }
862
+ }