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,790 @@
1
+ // actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js
2
+ class ObjectRenderer3D {
3
+ constructor(renderer, gl, programManager, lightManager) {
4
+ this.renderer = renderer;
5
+ this.gl = gl;
6
+ this.programManager = programManager;
7
+ this.lightManager = lightManager;
8
+
9
+ // Check if WebGL2 is available for 32-bit indices
10
+ this.isWebGL2 = this.gl instanceof WebGL2RenderingContext;
11
+
12
+ // Store the index element type for later use
13
+ this.indexType = this.isWebGL2 ? this.gl.UNSIGNED_INT : this.gl.UNSIGNED_SHORT;
14
+
15
+ // Create buffer for each renderable object - support textures for all objects
16
+ this.buffers = {
17
+ position: this.gl.createBuffer(),
18
+ normal: this.gl.createBuffer(),
19
+ color: this.gl.createBuffer(),
20
+ uv: this.gl.createBuffer(), // Add texture coordinate buffer
21
+ textureIndex: this.gl.createBuffer(), // Add texture index buffer
22
+ useTexture: this.gl.createBuffer(), // Add use texture flag buffer
23
+ indices: this.gl.createBuffer()
24
+ };
25
+
26
+ // Create view frustum for culling
27
+ this.viewFrustum = new ViewFrustum();
28
+
29
+ // Frustum culling is enabled by default
30
+ this.enableFrustumCulling = false;
31
+
32
+ // Add state tracking to avoid redundant texture bindings
33
+ this._currentTextureUnit = -1;
34
+ this._currentBoundTexture = null;
35
+ this._currentBoundTextureType = null;
36
+
37
+ // Cache for pre-computed uniform values
38
+ this._uniformCache = {
39
+ frame: -1, // Current frame number for cache validation
40
+ shaderProgram: null, // Current shader program
41
+ camera: null, // Current camera reference
42
+ lightConfig: null, // Cached light configuration
43
+ matrices: { // Cached matrices
44
+ projection: Matrix4.create(),
45
+ view: Matrix4.create(),
46
+ model: Matrix4.create(),
47
+ lightSpace: null
48
+ }
49
+ };
50
+
51
+ // Simple statistics
52
+ this.stats = {
53
+ objectsTotal: 0,
54
+ objectsCulled: 0,
55
+ uniformSetCount: 0 // Track how many uniform sets we perform
56
+ };
57
+ }
58
+
59
+ queue(object, camera, currentTime) {
60
+ // Skip rendering if object is invalid
61
+ if (!object) {
62
+ console.warn('Attempted to render null or undefined object');
63
+ return;
64
+ }
65
+
66
+ // Initialize the object renderer for the current frame if needed
67
+ if (!this._frameInitialized) {
68
+ // Reset stats
69
+ this.stats.objectsTotal = 0;
70
+ this.stats.objectsCulled = 0;
71
+ this.stats.uniformSetCount = 0;
72
+
73
+ // Track all objects in the current frame
74
+ this._frameObjects = [];
75
+ this._totalTriangles = 0;
76
+ this._frameInitialized = true;
77
+ this._currentFrameTime = performance.now();
78
+
79
+ // Store camera for batch rendering
80
+ this._camera = camera;
81
+
82
+ // Create persistent texture cache
83
+ if (!this._textureCache) {
84
+ this._textureCache = new Map();
85
+ }
86
+
87
+ // Update the view frustum with the current camera
88
+ this.viewFrustum.updateFromCamera(camera);
89
+
90
+ // Reset the frame counter for uniform cache
91
+ this._frameCount = (this._frameCount || 0) + 1;
92
+ }
93
+
94
+ // Update statistics
95
+ this.stats.objectsTotal++;
96
+
97
+ // Perform frustum culling if enabled
98
+ if (this.enableFrustumCulling) {
99
+ if (!this.viewFrustum.isVisible(object)) {
100
+ this.stats.objectsCulled++;
101
+ return; // Skip this object as it's outside the frustum
102
+ }
103
+ }
104
+
105
+ // Ensure object's visual geometry is up-to-date with its physics state
106
+ if (typeof object.updateVisual === 'function') {
107
+ object.updateVisual();
108
+ }
109
+
110
+ const triangles = object.triangles;
111
+
112
+ // Validate triangles exist
113
+ if (!triangles || triangles.length === 0) {
114
+ return; // Skip silently, this is a common case
115
+ }
116
+
117
+ const triangleCount = triangles.length;
118
+
119
+ // Add this object to our frame tracking
120
+ this._frameObjects.push(object);
121
+ this._totalTriangles += triangleCount;
122
+ }
123
+
124
+ render() {
125
+ if (this._frameObjects && this._frameObjects.length > 0) {
126
+ this.drawObjects(this._camera);
127
+ this._frameInitialized = false;
128
+ this._frameObjects = [];
129
+ this._totalTriangles = 0;
130
+ }
131
+ }
132
+
133
+ drawObjects(camera) {
134
+ // If we have no objects to render, just return
135
+ if (!this._frameObjects || this._frameObjects.length === 0) {
136
+ return;
137
+ }
138
+
139
+ // Calculate total vertex and index counts
140
+ const totalVertexCount = this._totalTriangles * 9;
141
+ const totalIndexCount = this._totalTriangles * 3;
142
+ const totalUvCount = this._totalTriangles * 6;
143
+ const totalFlagCount = this._totalTriangles * 3;
144
+
145
+ // Check if we'd exceed the 16-bit index limit
146
+ const exceeds16BitLimit = totalIndexCount > 65535;
147
+
148
+ // WebGL1 can't handle more than 65535 indices (16-bit limit)
149
+ if (exceeds16BitLimit && !this.isWebGL2) {
150
+ console.warn(`This scene has ${this._totalTriangles} triangles which exceeds the WebGL1 index limit.`);
151
+ console.warn('Using WebGL2 with Uint32 indices would greatly improve performance.');
152
+ }
153
+
154
+ // Allocate or resize buffers if needed
155
+ if (!this.cachedArrays || this.cachedArrays.positions.length < totalVertexCount) {
156
+ // Choose correct index array type based on WebGL version
157
+ const IndexArrayType = this.isWebGL2 ? Uint32Array : Uint16Array;
158
+
159
+ this.cachedArrays = {
160
+ positions: new Float32Array(totalVertexCount),
161
+ normals: new Float32Array(totalVertexCount),
162
+ colors: new Float32Array(totalVertexCount),
163
+ indices: new IndexArrayType(totalIndexCount)
164
+ };
165
+ }
166
+
167
+ // Initialize texture arrays if we need them
168
+ if (!this.textureArrays || this.textureArrays.uvs.length < totalUvCount) {
169
+ this.textureArrays = {
170
+ uvs: new Float32Array(totalUvCount),
171
+ textureIndices: new Float32Array(totalFlagCount),
172
+ useTextureFlags: new Float32Array(totalFlagCount)
173
+ };
174
+ this.textureArrays.useTextureFlags.fill(0);
175
+ }
176
+
177
+ const { positions, normals, colors, indices } = this.cachedArrays;
178
+
179
+ // Track offset for placing objects in buffer
180
+ let triangleOffset = 0;
181
+ let indexOffset = 0;
182
+
183
+ // Process all objects in the frame
184
+ for (const object of this._frameObjects) {
185
+ const triangles = object.triangles;
186
+ const triangleCount = triangles.length;
187
+
188
+ // Process geometry data for this object
189
+ // Use local variables for faster access
190
+ const tri = triangles;
191
+ const normal = new Float32Array(3);
192
+ let r, g, b;
193
+
194
+ for (let i = 0; i < triangleCount; i++) {
195
+ const triangle = tri[i];
196
+ const baseIndex = (triangleOffset + i) * 9; // Offset by triangles of previous objects
197
+
198
+ // Cache color conversion (only once per triangle)
199
+ const color = triangle.color;
200
+ if (color !== triangle.lastColor) {
201
+ // Use integer operations instead of substring for better performance
202
+ const hexColor = parseInt(color.slice(1), 16);
203
+ r = ((hexColor >> 16) & 255) / 255;
204
+ g = ((hexColor >> 8) & 255) / 255;
205
+ b = (hexColor & 255) / 255;
206
+
207
+ // Cache the parsed color
208
+ triangle.cachedColor = { r, g, b };
209
+ triangle.lastColor = color;
210
+ } else {
211
+ // Use cached color
212
+ r = triangle.cachedColor.r;
213
+ g = triangle.cachedColor.g;
214
+ b = triangle.cachedColor.b;
215
+ }
216
+
217
+ // Cache normal values once per triangle
218
+ normal[0] = triangle.normal.x;
219
+ normal[1] = triangle.normal.y;
220
+ normal[2] = triangle.normal.z;
221
+
222
+ // Process all vertices of this triangle in one batch
223
+ // Unroll the loop for better performance
224
+ // Vertex 0
225
+ const v0 = triangle.vertices[0];
226
+ const vo0 = baseIndex;
227
+ positions[vo0] = v0.x;
228
+ positions[vo0 + 1] = v0.y;
229
+ positions[vo0 + 2] = v0.z;
230
+ normals[vo0] = normal[0];
231
+ normals[vo0 + 1] = normal[1];
232
+ normals[vo0 + 2] = normal[2];
233
+ colors[vo0] = r;
234
+ colors[vo0 + 1] = g;
235
+ colors[vo0 + 2] = b;
236
+
237
+ // Vertex 1
238
+ const v1 = triangle.vertices[1];
239
+ const vo1 = baseIndex + 3;
240
+ positions[vo1] = v1.x;
241
+ positions[vo1 + 1] = v1.y;
242
+ positions[vo1 + 2] = v1.z;
243
+ normals[vo1] = normal[0];
244
+ normals[vo1 + 1] = normal[1];
245
+ normals[vo1 + 2] = normal[2];
246
+ colors[vo1] = r;
247
+ colors[vo1 + 1] = g;
248
+ colors[vo1 + 2] = b;
249
+
250
+ // Vertex 2
251
+ const v2 = triangle.vertices[2];
252
+ const vo2 = baseIndex + 6;
253
+ positions[vo2] = v2.x;
254
+ positions[vo2 + 1] = v2.y;
255
+ positions[vo2 + 2] = v2.z;
256
+ normals[vo2] = normal[0];
257
+ normals[vo2 + 1] = normal[1];
258
+ normals[vo2 + 2] = normal[2];
259
+ colors[vo2] = r;
260
+ colors[vo2 + 1] = g;
261
+ colors[vo2 + 2] = b;
262
+
263
+ // Set up indices with correct offsets for each object
264
+ const indexBaseOffset = (triangleOffset + i) * 3;
265
+ // Every vertex needs its own index in WebGL
266
+ indices[indexBaseOffset] = (triangleOffset + i) * 3;
267
+ indices[indexBaseOffset + 1] = (triangleOffset + i) * 3 + 1;
268
+ indices[indexBaseOffset + 2] = (triangleOffset + i) * 3 + 2;
269
+
270
+ // Check if this triangle has texture
271
+ if (triangle.texture) {
272
+ // First texture encountered in this object or frame
273
+ this._hasTextures = true;
274
+
275
+ const baseUVIndex = (triangleOffset + i) * 6;
276
+ const baseFlagIndex = (triangleOffset + i) * 3;
277
+ const { uvs, textureIndices, useTextureFlags } = this.textureArrays;
278
+
279
+ // Handle UVs
280
+ if (triangle.uvs) {
281
+ for (let j = 0; j < 3; j++) {
282
+ const uv = triangle.uvs[j];
283
+ uvs[baseUVIndex + j * 2] = uv.u;
284
+ uvs[baseUVIndex + j * 2 + 1] = uv.v;
285
+ }
286
+ } else {
287
+ // Default UVs
288
+ uvs[baseUVIndex] = 0;
289
+ uvs[baseUVIndex + 1] = 0;
290
+ uvs[baseUVIndex + 2] = 1;
291
+ uvs[baseUVIndex + 3] = 0;
292
+ uvs[baseUVIndex + 4] = 0.5;
293
+ uvs[baseUVIndex + 5] = 1;
294
+ }
295
+
296
+ // Set texture index - use cached value if possible
297
+ let textureIndex = this._textureCache.get(triangle.texture);
298
+ if (textureIndex === undefined) {
299
+ textureIndex = this.getTextureIndexForProceduralTexture(triangle.texture);
300
+ this._textureCache.set(triangle.texture, textureIndex);
301
+ }
302
+
303
+ textureIndices[baseFlagIndex] = textureIndex;
304
+ textureIndices[baseFlagIndex + 1] = textureIndex;
305
+ textureIndices[baseFlagIndex + 2] = textureIndex;
306
+
307
+ useTextureFlags[baseFlagIndex] = 1;
308
+ useTextureFlags[baseFlagIndex + 1] = 1;
309
+ useTextureFlags[baseFlagIndex + 2] = 1;
310
+ }
311
+ }
312
+
313
+ // Update triangle offset for next object
314
+ triangleOffset += triangleCount;
315
+ }
316
+
317
+ // Cache GL context and commonly used values
318
+ const gl = this.gl;
319
+ const ARRAY_BUFFER = gl.ARRAY_BUFFER;
320
+ // Use DYNAMIC_DRAW for buffers that change every frame
321
+ const DYNAMIC_DRAW = gl.DYNAMIC_DRAW;
322
+
323
+ // Update GL buffers with all object data
324
+ const bufferUpdates = [
325
+ { buffer: this.buffers.position, data: positions },
326
+ { buffer: this.buffers.normal, data: normals },
327
+ { buffer: this.buffers.color, data: colors }
328
+ ];
329
+
330
+ for (const { buffer, data } of bufferUpdates) {
331
+ gl.bindBuffer(ARRAY_BUFFER, buffer);
332
+ gl.bufferData(ARRAY_BUFFER, data, DYNAMIC_DRAW);
333
+ }
334
+
335
+ // Always update texture buffers to ensure consistent behavior
336
+ const { uvs, textureIndices, useTextureFlags } = this.textureArrays;
337
+ const textureBufferUpdates = [
338
+ { buffer: this.buffers.uv, data: uvs },
339
+ { buffer: this.buffers.textureIndex, data: textureIndices },
340
+ { buffer: this.buffers.useTexture, data: useTextureFlags }
341
+ ];
342
+
343
+ for (const { buffer, data } of textureBufferUpdates) {
344
+ gl.bindBuffer(ARRAY_BUFFER, buffer);
345
+ gl.bufferData(ARRAY_BUFFER, data, DYNAMIC_DRAW);
346
+ }
347
+
348
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers.indices);
349
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, DYNAMIC_DRAW);
350
+
351
+ // PRE-COMPUTE ALL MATRICES AND UNIFORMS ONCE PER FRAME
352
+ this.updateUniformCache(camera);
353
+
354
+ // Setup shader and draw - use the object shader from program manager
355
+ const program = this.programManager.getObjectProgram();
356
+ const locations = this.programManager.getObjectLocations();
357
+ gl.useProgram(program);
358
+ this.setupObjectShader(locations, camera);
359
+
360
+ // Draw all objects in one batch
361
+ this.drawObject(locations, totalIndexCount);
362
+
363
+ // Reset frame tracking for next frame
364
+ this._frameInitialized = false;
365
+ this._frameObjects = [];
366
+ this._totalTriangles = 0;
367
+ }
368
+
369
+ // Pre-compute all uniform values once per frame
370
+ updateUniformCache(camera) {
371
+ // Get the current shader program from program manager
372
+ const program = this.programManager.getObjectProgram();
373
+
374
+ // Check if we already computed values for this frame
375
+ if (this._uniformCache.frame === this._frameCount &&
376
+ this._uniformCache.shaderProgram === program &&
377
+ this._uniformCache.camera === camera) {
378
+ return; // Cache is valid, no need to update
379
+ }
380
+
381
+ // Update cache validation
382
+ this._uniformCache.frame = this._frameCount;
383
+ this._uniformCache.shaderProgram = program;
384
+ this._uniformCache.camera = camera;
385
+
386
+ // Pre-compute projection matrix
387
+ Matrix4.perspective(
388
+ this._uniformCache.matrices.projection,
389
+ camera.fov,
390
+ Game.WIDTH / Game.HEIGHT,
391
+ 0.1,
392
+ 10000.0
393
+ );
394
+
395
+ // Pre-compute view matrix
396
+ Matrix4.lookAt(
397
+ this._uniformCache.matrices.view,
398
+ camera.position.toArray(),
399
+ camera.target.toArray(),
400
+ camera.up.toArray()
401
+ );
402
+
403
+ // Identity model matrix
404
+ Matrix4.identity(this._uniformCache.matrices.model);
405
+
406
+ // Only cache light configuration if directional light is actually enabled
407
+ if (this.lightManager.isMainDirectionalLightEnabled() && this.lightManager.getMainDirectionalLight()) {
408
+ // Get real light data from the light manager - no sneaky default values
409
+ this._uniformCache.lightConfig = this.lightManager.getLightConfig();
410
+ this._uniformCache.lightDir = this.lightManager.getLightDir();
411
+
412
+ // Get the actual light space matrix from the light manager
413
+ this._uniformCache.matrices.lightSpace = this.lightManager.getLightSpaceMatrix();
414
+ } else {
415
+ // If directional light is disabled, explicitly set these to null
416
+ // to indicate there's no directional light present
417
+ this._uniformCache.lightConfig = null;
418
+ this._uniformCache.lightDir = null;
419
+ this._uniformCache.matrices.lightSpace = null;
420
+ }
421
+
422
+ // Cache other commonly used values
423
+ const materialConfig = this.lightManager.constants.MATERIAL;
424
+ this._uniformCache.roughness = materialConfig.ROUGHNESS.value;
425
+ this._uniformCache.metallic = materialConfig.METALLIC.value;
426
+ this._uniformCache.baseReflectivity = materialConfig.BASE_REFLECTIVITY.value;
427
+
428
+ // Save that we've updated the cache
429
+ this._cacheUpdated = true;
430
+ }
431
+
432
+ setupObjectShader(locations, camera) {
433
+ const gl = this.gl;
434
+
435
+ // Use pre-computed values from the uniform cache
436
+ gl.uniformMatrix4fv(locations.projectionMatrix, false, this._uniformCache.matrices.projection);
437
+ gl.uniformMatrix4fv(locations.viewMatrix, false, this._uniformCache.matrices.view);
438
+ gl.uniformMatrix4fv(locations.modelMatrix, false, this._uniformCache.matrices.model);
439
+
440
+ // Set camera position if the shader uses it
441
+ if (locations.cameraPos !== -1 && locations.cameraPos !== null) {
442
+ gl.uniform3fv(locations.cameraPos, camera.position.toArray());
443
+ }
444
+
445
+ // Only set light uniforms if we actually have a directional light
446
+ // Otherwise the shader will skip directional light calculations entirely
447
+ const config = this._uniformCache.lightConfig;
448
+ const mainLightEnabled = this.lightManager.isMainDirectionalLightEnabled() && this.lightManager.getMainDirectionalLight() !== null;
449
+
450
+ // If directional light is enabled, make sure shadows are also enabled
451
+ if (mainLightEnabled && locations.shadowsEnabled !== -1 && locations.shadowsEnabled !== null) {
452
+ gl.uniform1i(locations.shadowsEnabled, 1); // 1 = true
453
+ }
454
+
455
+ // Only set light position if light is enabled - no sneaky default values
456
+ if (locations.lightPos !== -1 && locations.lightPos !== null && mainLightEnabled && config && config.POSITION) {
457
+ gl.uniform3fv(locations.lightPos, [config.POSITION.x, config.POSITION.y, config.POSITION.z]);
458
+ }
459
+
460
+ // Only set light direction if light is enabled - no sneaky default values
461
+ if (locations.lightDir !== -1 && locations.lightDir !== null && mainLightEnabled && this._uniformCache.lightDir) {
462
+ gl.uniform3fv(locations.lightDir, this._uniformCache.lightDir.toArray());
463
+ }
464
+
465
+ // Only set intensity if light is enabled - no sneaky default values
466
+ if (locations.lightIntensity !== -1 && locations.lightIntensity !== null && mainLightEnabled && config && config.INTENSITY !== undefined) {
467
+ gl.uniform1f(locations.lightIntensity, config.INTENSITY);
468
+ }
469
+
470
+ // Set intensity factor for default shader
471
+ if (locations.intensityFactor !== -1 && locations.intensityFactor !== null) {
472
+ // Get the current shader name
473
+ const currentVariant = this.programManager.getCurrentVariant();
474
+
475
+ // Only apply the factor to the default shader
476
+ if (currentVariant === "default") {
477
+ const factor = this.lightManager.constants.OBJECT_SHADER_DEFAULT_VARIANT_INTENSITY_FACTOR.value;
478
+ gl.uniform1f(locations.intensityFactor, factor);
479
+ } else {
480
+ // For non-default shaders, use 1.0 (no scaling)
481
+ gl.uniform1f(locations.intensityFactor, 1.0);
482
+ }
483
+ }
484
+
485
+ // Set PBR material properties if they are defined in the shader
486
+ if (locations.roughness !== -1 && locations.roughness !== null) {
487
+ gl.uniform1f(locations.roughness, this._uniformCache.roughness);
488
+ }
489
+ if (locations.metallic !== -1 && locations.metallic !== null) {
490
+ gl.uniform1f(locations.metallic, this._uniformCache.metallic);
491
+ }
492
+ if (locations.baseReflectivity !== -1 && locations.baseReflectivity !== null) {
493
+ gl.uniform1f(locations.baseReflectivity, this._uniformCache.baseReflectivity);
494
+ }
495
+
496
+ // Set per-texture material properties uniform
497
+ if (locations.usePerTextureMaterials !== -1 && locations.usePerTextureMaterials !== null) {
498
+ // Get material settings from texture manager
499
+ const usePerTextureMaterials = this.renderer.textureManager?.usePerTextureMaterials || false;
500
+ gl.uniform1i(locations.usePerTextureMaterials, usePerTextureMaterials ? 1 : 0);
501
+ }
502
+
503
+ // Bind material properties texture if available
504
+ if (locations.materialPropertiesTexture !== -1 && locations.materialPropertiesTexture !== null) {
505
+ const materialPropertiesTexture = this.renderer.textureManager?.materialPropertiesTexture;
506
+ if (materialPropertiesTexture) {
507
+ // Only change texture binding if needed (texture unit 2 for material properties)
508
+ // Always bind the material properties texture to ensure it's up to date
509
+ // This is needed to support real-time changes in the debug panel
510
+ {
511
+
512
+ // Use texture unit 2 for material properties
513
+ this.gl.activeTexture(this.gl.TEXTURE2);
514
+ this.gl.bindTexture(this.gl.TEXTURE_2D, materialPropertiesTexture);
515
+ this.gl.uniform1i(locations.materialPropertiesTexture, 2);
516
+
517
+ // Update state tracking
518
+ this._currentTextureUnit = 2;
519
+ this._currentBoundTexture = materialPropertiesTexture;
520
+ this._currentBoundTextureType = this.gl.TEXTURE_2D;
521
+ }
522
+ }
523
+ }
524
+
525
+ // Set shadow map uniforms if they exist in the shader
526
+ if (locations.shadowsEnabled !== -1 && locations.shadowsEnabled !== null) {
527
+ // Set by renderer during shadow map binding
528
+ const shadowsEnabled = this.renderer.shadowsEnabled ? 1 : 0;
529
+ gl.uniform1i(locations.shadowsEnabled, shadowsEnabled);
530
+ }
531
+
532
+ // Set shadow bias if available
533
+ if (locations.shadowBias !== -1 && locations.shadowBias !== null) {
534
+ const shadowBias = this._uniformCache.shadowBias || 0.05;
535
+ gl.uniform1f(locations.shadowBias, shadowBias);
536
+ }
537
+
538
+ // Only set light space matrix if directional light is actually enabled
539
+ // We don't want to do sneaky calculations with default matrices
540
+ if (locations.lightSpaceMatrix !== -1 && locations.lightSpaceMatrix !== null &&
541
+ this._uniformCache.matrices.lightSpace &&
542
+ this.lightManager.isMainDirectionalLightEnabled()) {
543
+
544
+ // Apply the actual light space matrix from the light
545
+ gl.uniformMatrix4fv(locations.lightSpaceMatrix, false, this._uniformCache.matrices.lightSpace);
546
+ }
547
+
548
+ // Track how many uniform sets we've performed
549
+ this.stats.uniformSetCount++;
550
+ }
551
+
552
+ drawObject(locations, indexCount) {
553
+ // Cache commonly used values
554
+ const gl = this.gl;
555
+ const ARRAY_BUFFER = gl.ARRAY_BUFFER;
556
+
557
+ // Position attribute
558
+ gl.bindBuffer(ARRAY_BUFFER, this.buffers.position);
559
+ gl.vertexAttribPointer(locations.position, 3, gl.FLOAT, false, 0, 0);
560
+ gl.enableVertexAttribArray(locations.position);
561
+
562
+ // Normal attribute
563
+ gl.bindBuffer(ARRAY_BUFFER, this.buffers.normal);
564
+ gl.vertexAttribPointer(locations.normal, 3, gl.FLOAT, false, 0, 0);
565
+ gl.enableVertexAttribArray(locations.normal);
566
+
567
+ // Color attribute
568
+ if (locations.color !== -1) {
569
+ gl.bindBuffer(ARRAY_BUFFER, this.buffers.color);
570
+ gl.vertexAttribPointer(locations.color, 3, gl.FLOAT, false, 0, 0);
571
+ gl.enableVertexAttribArray(locations.color);
572
+ }
573
+
574
+ // Always set up texture attributes for consistent behavior
575
+ // Set up texture coordinates
576
+ if (locations.texCoord !== -1) {
577
+ gl.bindBuffer(ARRAY_BUFFER, this.buffers.uv);
578
+ gl.vertexAttribPointer(locations.texCoord, 2, gl.FLOAT, false, 0, 0);
579
+ gl.enableVertexAttribArray(locations.texCoord);
580
+ }
581
+
582
+ // Set up texture index
583
+ if (locations.textureIndex !== -1) {
584
+ gl.bindBuffer(ARRAY_BUFFER, this.buffers.textureIndex);
585
+ gl.vertexAttribPointer(locations.textureIndex, 1, gl.FLOAT, false, 0, 0);
586
+ gl.enableVertexAttribArray(locations.textureIndex);
587
+ }
588
+
589
+ // Set up use texture flag
590
+ if (locations.useTexture !== -1) {
591
+ gl.bindBuffer(ARRAY_BUFFER, this.buffers.useTexture);
592
+ gl.vertexAttribPointer(locations.useTexture, 1, gl.FLOAT, false, 0, 0);
593
+ gl.enableVertexAttribArray(locations.useTexture);
594
+ }
595
+
596
+ // Performance optimization: Cache shader information and texture binding
597
+ if (!this._currentShaderVariant) {
598
+ this._currentShaderVariant = "unknown";
599
+ }
600
+
601
+ // TEXTURE BINDING STRATEGY:
602
+ // Group 1: 2D textures (0-7)
603
+ // Group 2: Cubemap textures (10-19)
604
+ // Group 3: 2D array textures (20-29)
605
+
606
+ // --- GROUP 1: 2D TEXTURES ---
607
+ // Bind material properties (2D texture)
608
+ if (locations.materialPropertiesTexture !== -1 && locations.materialPropertiesTexture !== null) {
609
+ const materialPropertiesTexture = this.renderer?.textureManager?.materialPropertiesTexture;
610
+ if (materialPropertiesTexture) {
611
+ gl.activeTexture(gl.TEXTURE0);
612
+ gl.bindTexture(gl.TEXTURE_2D, materialPropertiesTexture);
613
+ gl.uniform1i(locations.materialPropertiesTexture, 0);
614
+ }
615
+ }
616
+
617
+ // Bind light data textures (2D textures)
618
+ if (locations.directionalLightData !== -1 && locations.directionalLightData !== null &&
619
+ this.lightManager.directionalLightDataTexture) {
620
+ gl.activeTexture(gl.TEXTURE1);
621
+ gl.bindTexture(gl.TEXTURE_2D, this.lightManager.directionalLightDataTexture);
622
+ gl.uniform1i(locations.directionalLightData, 1);
623
+ }
624
+
625
+ if (locations.pointLightData !== -1 && locations.pointLightData !== null &&
626
+ this.lightManager.pointLightDataTexture) {
627
+ gl.activeTexture(gl.TEXTURE2);
628
+ gl.bindTexture(gl.TEXTURE_2D, this.lightManager.pointLightDataTexture);
629
+ gl.uniform1i(locations.pointLightData, 2);
630
+ }
631
+
632
+ // Bind directional shadow map (2D texture)
633
+ if (locations.shadowMap !== -1 && locations.shadowMap !== null) {
634
+ const mainLight = this.lightManager.getMainDirectionalLight();
635
+ if (mainLight && mainLight.getShadowsEnabled()) {
636
+ const shadowMap = mainLight.shadowTexture;
637
+ if (shadowMap) {
638
+ gl.activeTexture(gl.TEXTURE3);
639
+ gl.bindTexture(gl.TEXTURE_2D, shadowMap);
640
+ gl.uniform1i(locations.shadowMap, 3);
641
+ }
642
+ }
643
+ }
644
+
645
+ // --- GROUP 2: CUBEMAP TEXTURES (must be separate from 2D textures) ---
646
+ // Bind shadow maps for multiple point lights
647
+ // Each point light gets assigned a texture unit starting from 10
648
+
649
+ // Define the mapping of point light index to shadow map properties
650
+ const pointLightShadowMaps = [
651
+ { locationName: 'pointShadowMap', textureUnit: 10, enabledName: 'pointShadowsEnabled' },
652
+ { locationName: 'pointShadowMap1', textureUnit: 11, enabledName: 'pointShadowsEnabled1' },
653
+ { locationName: 'pointShadowMap2', textureUnit: 12, enabledName: 'pointShadowsEnabled2' },
654
+ { locationName: 'pointShadowMap3', textureUnit: 13, enabledName: 'pointShadowsEnabled3' }
655
+ ];
656
+
657
+ // Bind shadow maps for up to 4 point lights
658
+ for (let i = 0; i < Math.min(this.lightManager.pointLights.length, pointLightShadowMaps.length); i++) {
659
+ const pointLight = this.lightManager.pointLights[i];
660
+ const shadowMapDef = pointLightShadowMaps[i];
661
+
662
+ // Get uniform locations
663
+ const shadowMapLoc = locations[shadowMapDef.locationName];
664
+ const shadowEnabledLoc = locations[shadowMapDef.enabledName];
665
+
666
+ // Skip if any required uniform doesn't exist
667
+ if (shadowMapLoc === -1 || shadowMapLoc === null || shadowEnabledLoc === -1 || shadowEnabledLoc === null) {
668
+ continue;
669
+ }
670
+
671
+ // Check if this light casts shadows
672
+ if (pointLight && pointLight.getShadowsEnabled()) {
673
+ const pointShadowMap = pointLight.shadowTexture;
674
+ if (pointShadowMap) {
675
+ // Bind the shadow map
676
+ gl.activeTexture(gl.TEXTURE0 + shadowMapDef.textureUnit);
677
+ if (this.isWebGL2 && pointLight.isWebGL2) {
678
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, pointShadowMap);
679
+ } else {
680
+ gl.bindTexture(gl.TEXTURE_2D, pointShadowMap);
681
+ }
682
+ gl.uniform1i(shadowMapLoc, shadowMapDef.textureUnit);
683
+
684
+ // Set that this light's shadows are enabled
685
+ gl.uniform1i(shadowEnabledLoc, 1); // 1 = true
686
+ } else {
687
+ // No shadow map, disable shadows
688
+ gl.uniform1i(shadowEnabledLoc, 0); // 0 = false
689
+ }
690
+ } else {
691
+ // Light doesn't cast shadows, disable them
692
+ gl.uniform1i(shadowEnabledLoc, 0); // 0 = false
693
+ }
694
+ }
695
+
696
+ // Disable shadows for any additional shadow map uniforms that exist
697
+ for (let i = this.lightManager.pointLights.length; i < pointLightShadowMaps.length; i++) {
698
+ const shadowMapDef = pointLightShadowMaps[i];
699
+ const shadowEnabledLoc = locations[shadowMapDef.enabledName];
700
+
701
+ if (shadowEnabledLoc !== -1 && shadowEnabledLoc !== null) {
702
+ gl.uniform1i(shadowEnabledLoc, 0); // 0 = false
703
+ }
704
+ }
705
+
706
+ // --- GROUP 3: TEXTURE ARRAYS ---
707
+ // Bind texture array (2D array texture)
708
+ if (locations.textureArray !== -1 && locations.textureArray !== null) {
709
+ const textureArray = this.renderer?.textureArray;
710
+ if (textureArray) {
711
+ // Determine which shader variant to use
712
+ if (!this._lastCheckedVariant || this._lastCheckedVariant !== this.programManager.getCurrentVariant()) {
713
+ this._lastCheckedVariant = this.programManager.getCurrentVariant();
714
+ this._currentShaderVariant = this._lastCheckedVariant === "pbr" ? "pbr" : "other";
715
+ }
716
+
717
+ // Use unit 20 for standard shader, 21 for PBR shader
718
+ const targetUnit = this._currentShaderVariant === "pbr" ? 21 : 20;
719
+
720
+ // Only change binding if needed
721
+ if (this._currentTextureUnit !== targetUnit ||
722
+ this._currentBoundTexture !== textureArray ||
723
+ this._currentBoundTextureType !== gl.TEXTURE_2D_ARRAY) {
724
+
725
+ gl.activeTexture(gl.TEXTURE0 + targetUnit);
726
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, textureArray);
727
+ gl.uniform1i(locations.textureArray, targetUnit);
728
+
729
+ // Update state tracking
730
+ this._currentTextureUnit = targetUnit;
731
+ this._currentBoundTexture = textureArray;
732
+ this._currentBoundTextureType = gl.TEXTURE_2D_ARRAY;
733
+ }
734
+
735
+ // Always update material properties texture, no dirty flag check
736
+ if (this.renderer?.textureManager) {
737
+ this.renderer.textureManager.updateMaterialPropertiesTexture();
738
+ }
739
+ }
740
+ }
741
+
742
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers.indices);
743
+ gl.drawElements(gl.TRIANGLES, indexCount, this.indexType, 0);
744
+ }
745
+
746
+ // Helper method to get texture index - works for any object with textures
747
+ getTextureIndexForProceduralTexture(proceduralTexture) {
748
+ // If textureRegistry doesn't exist or isn't accessible, return 0
749
+ if (typeof textureRegistry === 'undefined') {
750
+ console.warn('textureRegistry is not defined - textures will not work correctly');
751
+ return 0;
752
+ }
753
+
754
+ // Initialize the texture cache once if needed
755
+ if (!this._textureIndexCache) {
756
+ this._textureIndexCache = new WeakMap();
757
+
758
+ // Pre-populate cache with texture information - only need to do this once since textures don't change
759
+ textureRegistry.textureList.forEach((name, index) => {
760
+ const texture = textureRegistry.get(name);
761
+ if (texture) {
762
+ this._textureIndexCache.set(texture, index);
763
+ }
764
+ });
765
+
766
+ // Set lastCacheUpdate to infinity to prevent unnecessary refreshes
767
+ this._lastCacheUpdate = Infinity;
768
+ }
769
+
770
+ // Get from cache with O(1) lookup
771
+ const indexFromCache = this._textureIndexCache.get(proceduralTexture);
772
+ if (indexFromCache !== undefined) {
773
+ return indexFromCache;
774
+ }
775
+
776
+ // If texture wasn't in cache, this is a texture we haven't seen before
777
+ // Instead of refreshing the whole cache, just add this one texture
778
+ const textureName = proceduralTexture.name;
779
+ if (textureName) {
780
+ const textureIndex = textureRegistry.textureList.indexOf(textureName);
781
+ if (textureIndex !== -1) {
782
+ // Add to cache for future lookups
783
+ this._textureIndexCache.set(proceduralTexture, textureIndex);
784
+ return textureIndex;
785
+ }
786
+ }
787
+
788
+ return 0; // Default to first texture if not found
789
+ }
790
+ }