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