action-engine-js 1.0.3 → 1.0.4

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