action-engine-js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -0
- package/README.md +348 -0
- package/actionengine/3rdparty/goblin/goblin.js +9609 -0
- package/actionengine/3rdparty/goblin/goblin.min.js +5 -0
- package/actionengine/camera/actioncamera.js +90 -0
- package/actionengine/camera/cameracollisionhandler.js +69 -0
- package/actionengine/character/actioncharacter.js +360 -0
- package/actionengine/character/actioncharacter3D.js +61 -0
- package/actionengine/core/app.js +430 -0
- package/actionengine/debug/basedebugpanel.js +858 -0
- package/actionengine/display/canvasmanager.js +75 -0
- package/actionengine/display/gl/programmanager.js +570 -0
- package/actionengine/display/gl/shaders/lineshader.js +118 -0
- package/actionengine/display/gl/shaders/objectshader.js +1756 -0
- package/actionengine/display/gl/shaders/particleshader.js +43 -0
- package/actionengine/display/gl/shaders/shadowshader.js +319 -0
- package/actionengine/display/gl/shaders/spriteshader.js +100 -0
- package/actionengine/display/gl/shaders/watershader.js +67 -0
- package/actionengine/display/graphics/actionmodel3D.js +191 -0
- package/actionengine/display/graphics/actionsprite3D.js +230 -0
- package/actionengine/display/graphics/lighting/actiondirectionalshadowlight.js +864 -0
- package/actionengine/display/graphics/lighting/actionlight.js +211 -0
- package/actionengine/display/graphics/lighting/actionomnidirectionalshadowlight.js +862 -0
- package/actionengine/display/graphics/lighting/lightingconstants.js +263 -0
- package/actionengine/display/graphics/lighting/lightmanager.js +789 -0
- package/actionengine/display/graphics/renderableobject.js +44 -0
- package/actionengine/display/graphics/renderers/actionrenderer2D.js +341 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/actionrenderer3D.js +655 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/canvasmanager3D.js +82 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/debugrenderer3D.js +493 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/objectrenderer3D.js +790 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/spriteRenderer3D.js +266 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/sunrenderer3D.js +140 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/waterrenderer3D.js +173 -0
- package/actionengine/display/graphics/renderers/actionrenderer3D/weatherrenderer3D.js +87 -0
- package/actionengine/display/graphics/texture/proceduraltexture.js +192 -0
- package/actionengine/display/graphics/texture/texturemanager.js +242 -0
- package/actionengine/display/graphics/texture/textureregistry.js +177 -0
- package/actionengine/input/actionscrollablearea.js +1405 -0
- package/actionengine/input/inputhandler.js +1647 -0
- package/actionengine/math/geometry/geometrybuilder.js +161 -0
- package/actionengine/math/geometry/glbexporter.js +364 -0
- package/actionengine/math/geometry/glbloader.js +722 -0
- package/actionengine/math/geometry/modelcodegenerator.js +97 -0
- package/actionengine/math/geometry/triangle.js +33 -0
- package/actionengine/math/geometry/triangleutils.js +34 -0
- package/actionengine/math/mathutils.js +25 -0
- package/actionengine/math/matrix4.js +785 -0
- package/actionengine/math/physics/actionphysics.js +108 -0
- package/actionengine/math/physics/actionphysicsobject3D.js +164 -0
- package/actionengine/math/physics/actionphysicsworld3D.js +238 -0
- package/actionengine/math/physics/actionraycast.js +129 -0
- package/actionengine/math/physics/shapes/actionphysicsbox3D.js +158 -0
- package/actionengine/math/physics/shapes/actionphysicscapsule3D.js +200 -0
- package/actionengine/math/physics/shapes/actionphysicscompoundshape3D.js +147 -0
- package/actionengine/math/physics/shapes/actionphysicscone3D.js +126 -0
- package/actionengine/math/physics/shapes/actionphysicsconvexshape3D.js +72 -0
- package/actionengine/math/physics/shapes/actionphysicscylinder3D.js +117 -0
- package/actionengine/math/physics/shapes/actionphysicsmesh3D.js +74 -0
- package/actionengine/math/physics/shapes/actionphysicsplane3D.js +100 -0
- package/actionengine/math/physics/shapes/actionphysicssphere3D.js +95 -0
- package/actionengine/math/quaternion.js +61 -0
- package/actionengine/math/vector2.js +277 -0
- package/actionengine/math/vector3.js +318 -0
- package/actionengine/math/viewfrustum.js +136 -0
- package/actionengine/network/ACTIONNETREADME.md +810 -0
- package/actionengine/network/client/ActionNetManager.js +802 -0
- package/actionengine/network/client/ActionNetManagerGUI.js +1709 -0
- package/actionengine/network/client/ActionNetManagerP2P.js +1537 -0
- package/actionengine/network/client/SyncSystem.js +422 -0
- package/actionengine/network/p2p/ActionNetPeer.js +142 -0
- package/actionengine/network/p2p/ActionNetTrackerClient.js +623 -0
- package/actionengine/network/p2p/DataConnection.js +282 -0
- package/actionengine/network/p2p/README.md +510 -0
- package/actionengine/network/p2p/example.html +502 -0
- package/actionengine/network/server/ActionNetServer.js +577 -0
- package/actionengine/network/server/ActionNetServerSSL.js +579 -0
- package/actionengine/network/server/ActionNetServerUtils.js +458 -0
- package/actionengine/network/server/SERVERREADME.md +314 -0
- package/actionengine/network/server/package-lock.json +35 -0
- package/actionengine/network/server/package.json +13 -0
- package/actionengine/network/server/start.bat +27 -0
- package/actionengine/network/server/start.sh +25 -0
- package/actionengine/network/server/startwss.bat +27 -0
- package/actionengine/sound/audiomanager.js +1589 -0
- package/actionengine/sound/soundfont/ACTIONSOUNDFONT_README.md +205 -0
- package/actionengine/sound/soundfont/actionparser.js +718 -0
- package/actionengine/sound/soundfont/actionreverb.js +252 -0
- package/actionengine/sound/soundfont/actionsoundfont.js +543 -0
- package/actionengine/sound/soundfont/sf2playerlicence.txt +29 -0
- package/actionengine/sound/soundfont/soundfont.js +2 -0
- package/dist/action-engine.min.js +328 -0
- package/package.json +35 -0
|
@@ -0,0 +1,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
|
+
}
|