emeraldengine 2.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 (73) hide show
  1. package/README.md +968 -0
  2. package/dist/types/imports/BitmapText.d.ts +79 -0
  3. package/dist/types/imports/Color.d.ts +17 -0
  4. package/dist/types/imports/Drawable.d.ts +157 -0
  5. package/dist/types/imports/Emerald.d.ts +73 -0
  6. package/dist/types/imports/FPSCounter.d.ts +19 -0
  7. package/dist/types/imports/GLUtils.d.ts +6 -0
  8. package/dist/types/imports/Instance.d.ts +87 -0
  9. package/dist/types/imports/InstancedTexture.d.ts +230 -0
  10. package/dist/types/imports/Physics.d.ts +139 -0
  11. package/dist/types/imports/Scene.d.ts +39 -0
  12. package/dist/types/imports/Shaders.d.ts +4 -0
  13. package/dist/types/imports/Shapes.d.ts +25 -0
  14. package/dist/types/imports/Storage.d.ts +46 -0
  15. package/dist/types/imports/Texture.d.ts +19 -0
  16. package/dist/types/imports/Time.d.ts +22 -0
  17. package/dist/types/imports/Transform.d.ts +41 -0
  18. package/dist/types/imports/components/BoxCollider.d.ts +41 -0
  19. package/dist/types/imports/components/BoxColliderDebug.d.ts +19 -0
  20. package/dist/types/imports/components/CircleCollider.d.ts +39 -0
  21. package/dist/types/imports/components/CircleColliderDebug.d.ts +19 -0
  22. package/dist/types/imports/components/Collider.d.ts +53 -0
  23. package/dist/types/imports/components/GameObject.d.ts +72 -0
  24. package/dist/types/imports/components/RigidBody.d.ts +104 -0
  25. package/dist/types/imports/lights/DirectionalLight.d.ts +26 -0
  26. package/dist/types/imports/lights/PointLight.d.ts +18 -0
  27. package/dist/types/imports/managers/AudioManager.d.ts +60 -0
  28. package/dist/types/imports/managers/CameraManager.d.ts +35 -0
  29. package/dist/types/imports/managers/EventManager.d.ts +187 -0
  30. package/dist/types/imports/managers/GLManager.d.ts +47 -0
  31. package/dist/types/imports/managers/IDManager.d.ts +21 -0
  32. package/dist/types/imports/managers/SceneManager.d.ts +22 -0
  33. package/dist/types/imports/particlesystem/Particle.d.ts +42 -0
  34. package/dist/types/imports/particlesystem/ParticleSettings.d.ts +49 -0
  35. package/dist/types/imports/particlesystem/Particles.d.ts +71 -0
  36. package/dist/types/index.d.ts +32 -0
  37. package/imports/BitmapText.js +245 -0
  38. package/imports/Color.js +18 -0
  39. package/imports/Drawable.js +550 -0
  40. package/imports/Emerald.js +459 -0
  41. package/imports/FPSCounter.js +43 -0
  42. package/imports/GLUtils.js +67 -0
  43. package/imports/Instance.js +187 -0
  44. package/imports/InstancedTexture.js +856 -0
  45. package/imports/Physics.js +242 -0
  46. package/imports/Scene.js +79 -0
  47. package/imports/Shaders.js +165 -0
  48. package/imports/Shapes.js +129 -0
  49. package/imports/Storage.js +63 -0
  50. package/imports/Texture.js +67 -0
  51. package/imports/Time.js +28 -0
  52. package/imports/Transform.js +59 -0
  53. package/imports/components/BoxCollider.js +67 -0
  54. package/imports/components/BoxColliderDebug.js +38 -0
  55. package/imports/components/CircleCollider.js +67 -0
  56. package/imports/components/CircleColliderDebug.js +36 -0
  57. package/imports/components/Collider.js +51 -0
  58. package/imports/components/GameObject.js +160 -0
  59. package/imports/components/RigidBody.js +169 -0
  60. package/imports/lights/DirectionalLight.js +68 -0
  61. package/imports/lights/PointLight.js +17 -0
  62. package/imports/managers/AudioManager.js +146 -0
  63. package/imports/managers/CameraManager.js +60 -0
  64. package/imports/managers/EventManager.js +479 -0
  65. package/imports/managers/GLManager.js +65 -0
  66. package/imports/managers/IDManager.js +32 -0
  67. package/imports/managers/SceneManager.js +27 -0
  68. package/imports/managers/ShaderManager.js +133 -0
  69. package/imports/particlesystem/Particle.js +75 -0
  70. package/imports/particlesystem/ParticleSettings.js +49 -0
  71. package/imports/particlesystem/Particles.js +226 -0
  72. package/index.js +67 -0
  73. package/package.json +38 -0
@@ -0,0 +1,856 @@
1
+ import { initVertexBuffer, initInstancedBuffer } from "./GLUtils.js";
2
+ import Instance from "./Instance.js";
3
+ import Drawable from "./Drawable.js";
4
+ import { mat4 } from "gl-matrix";
5
+ import GLManager from "./managers/GLManager.js";
6
+ import RigidBody from "./components/RigidBody.js";
7
+ import Collider from "./components/Collider.js";
8
+
9
+ /**
10
+ * @class InstancedTexture
11
+ * @description Represents an instanced texture component
12
+ * @param {string} texturePath - The path to the texture
13
+ * @param {number} instanceCount - The number of instances
14
+ * @param {number} frameWidth - The width of the frame
15
+ * @param {number} frameHeight - The height of the frame
16
+ * @param {number} framesPerRow - The number of frames per row
17
+ * @param {number} totalFrames - The total number of frames
18
+ * @param {number} animationSpeed - The speed of the animation
19
+ * @param {boolean} autoPlay - Whether to auto play the animation
20
+ * @param {boolean} pixelart - Whether to use pixel art
21
+ * @param {boolean} useLighting - Whether to use lighting
22
+ */
23
+ class InstancedTexture extends Drawable {
24
+ constructor(
25
+ texturePath = {},
26
+ instanceCount = 1,
27
+ frameWidth = 0,
28
+ frameHeight = 0,
29
+ framesPerRow = 1,
30
+ totalFrames = 1,
31
+ animationSpeed = 1000,
32
+ autoPlay = true,
33
+ pixelart = true,
34
+ useLighting = true
35
+ ) {
36
+ const vertices = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];
37
+ const verticesBuffer = initVertexBuffer(GLManager.getGL(), vertices);
38
+ const textureCoordinates = [
39
+ 1.0,
40
+ 1.0, // Top-right
41
+ 0.0,
42
+ 1.0, // Top-left
43
+ 1.0,
44
+ 0.0, // Bottom-right
45
+ 0.0,
46
+ 0.0, // Bottom-left
47
+ ];
48
+ const texCoordBuffer = initVertexBuffer(
49
+ GLManager.getGL(),
50
+ textureCoordinates
51
+ );
52
+
53
+ super(
54
+ GLManager.getGL(),
55
+ GLManager.getProgramInfo(),
56
+ verticesBuffer,
57
+ texCoordBuffer,
58
+ vertices,
59
+ true,
60
+ texturePath,
61
+ frameWidth,
62
+ frameHeight,
63
+ framesPerRow,
64
+ totalFrames,
65
+ animationSpeed,
66
+ autoPlay,
67
+ pixelart,
68
+ useLighting
69
+ );
70
+
71
+ this.instanceCount = instanceCount;
72
+
73
+ this.instances = [];
74
+ this.instanceMatrices = new Float32Array(instanceCount * 16); // 4x4 matrix for each instance
75
+ this.instanceTexCoords = new Float32Array(instanceCount * 8); // 4 vertices * 2 coords per instance
76
+
77
+ // Create instanced buffer for matrices
78
+ this.instanceMatrixBuffer = initInstancedBuffer(
79
+ GLManager.getGL(),
80
+ this.instanceMatrices,
81
+ 16
82
+ );
83
+
84
+ // Create instanced buffer for texture coordinates
85
+ this.instanceTexCoordBuffer = initInstancedBuffer(
86
+ GLManager.getGL(),
87
+ this.instanceTexCoords,
88
+ 8
89
+ );
90
+
91
+ // Event handling for instances
92
+ this.instanceClickListeners = new Map();
93
+ this.instanceHoverListeners = new Map();
94
+ }
95
+
96
+ /**
97
+ * @method addInstance
98
+ * @description Adds an instance to the instanced texture
99
+ * @param {Instance} instance - The instance to add
100
+ */
101
+ addInstance(instance) {
102
+ if (!(instance instanceof Instance)) {
103
+ console.error("Instance must be an instance of the Instance class.");
104
+ return;
105
+ }
106
+
107
+ if (this.instances.length < this.instanceCount) {
108
+ this.instances.push(instance);
109
+ instance.setParent(this);
110
+ } else {
111
+ console.warn("Max instance count reached.");
112
+ }
113
+ }
114
+
115
+ /**
116
+ * @method removeInstance
117
+ * @description Removes an instance from the instanced texture
118
+ * @param {string} instanceID - The ID of the instance to remove
119
+ */
120
+ removeInstance(instanceID) {
121
+ const index = this.instances.findIndex(
122
+ (instance) => instance.id === instanceID
123
+ );
124
+ if (index !== -1) {
125
+ this.instances.splice(index, 1);
126
+
127
+ // Clean up event listeners for this instance
128
+ this.instanceClickListeners.delete(instanceID);
129
+ this.instanceHoverListeners.delete(instanceID);
130
+
131
+ this.instanceMatrices = new Float32Array(this.instanceCount * 16);
132
+ this.instanceTexCoords = new Float32Array(this.instanceCount * 8);
133
+
134
+ // Update instance matrices and texture coordinates
135
+ this.updateAllInstanceMatrices();
136
+ this.updateAllInstanceTexCoords();
137
+ } else {
138
+ console.warn("Instance not found:", instanceID);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @method clearInstances
144
+ * @description Clears all instances from the instanced texture
145
+ */
146
+ clearInstances() {
147
+ this.instances = [];
148
+
149
+ // Clear all event listeners
150
+ this.instanceClickListeners.clear();
151
+ this.instanceHoverListeners.clear();
152
+
153
+ this.instanceMatrices = new Float32Array(this.instanceCount * 16); // Reset matrices
154
+ this.instanceTexCoords = new Float32Array(this.instanceCount * 8); // Reset texture coordinates
155
+
156
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceMatrixBuffer);
157
+ this.gl.bufferData(
158
+ this.gl.ARRAY_BUFFER,
159
+ this.instanceMatrices,
160
+ this.gl.DYNAMIC_DRAW
161
+ );
162
+
163
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceTexCoordBuffer);
164
+ this.gl.bufferData(
165
+ this.gl.ARRAY_BUFFER,
166
+ this.instanceTexCoords,
167
+ this.gl.DYNAMIC_DRAW
168
+ );
169
+ }
170
+
171
+ /**
172
+ * @method updateInstanceCount
173
+ * @description Updates the instance count of the instanced texture
174
+ * @param {number} newCount - The new instance count
175
+ */
176
+ updateInstanceCount(newCount) {
177
+ this.instanceCount = newCount;
178
+ this.instanceMatrices = new Float32Array(newCount * 16);
179
+ this.instanceTexCoords = new Float32Array(newCount * 8);
180
+ this.instanceMatrixBuffer = initInstancedBuffer(
181
+ this.gl,
182
+ this.instanceMatrices,
183
+ 16
184
+ );
185
+ this.instanceTexCoordBuffer = initInstancedBuffer(
186
+ this.gl,
187
+ this.instanceTexCoords,
188
+ 8
189
+ );
190
+ }
191
+
192
+ /**
193
+ * @method updateInstanceMatrix
194
+ * @description Updates the transformation matrix for a specific instance
195
+ * @param {number} index - The index of the instance to update
196
+ */
197
+ updateInstanceMatrix(index) {
198
+ if (index >= 0 && index < this.instanceCount) {
199
+ const matrix = mat4.create();
200
+ const position = [
201
+ this.instances[index].transform.position.x,
202
+ this.instances[index].transform.position.y,
203
+ this.instances[index].transform.position.z,
204
+ ];
205
+ const scale = [
206
+ this.instances[index].transform.scale.x,
207
+ this.instances[index].transform.scale.y,
208
+ 1,
209
+ ];
210
+ const rotation = this.instances[index].transform.rotation;
211
+
212
+ mat4.translate(matrix, matrix, position);
213
+ mat4.rotate(matrix, matrix, 0, [1, 0, 0]);
214
+ mat4.rotate(matrix, matrix, 0, [0, 1, 0]);
215
+ mat4.rotate(matrix, matrix, rotation, [0, 0, 1]);
216
+ mat4.scale(matrix, matrix, scale);
217
+
218
+ // Copy matrix values to the appropriate place in instanceMatrices
219
+ const offset = index * 16;
220
+ for (let i = 0; i < 16; i++) {
221
+ this.instanceMatrices[offset + i] = matrix[i];
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * @method updateAllInstanceMatrices
228
+ * @description Updates all instance matrices
229
+ */
230
+ updateAllInstanceMatrices() {
231
+ for (let i = 0; i < this.instances.length; i++) {
232
+ this.updateInstanceMatrix(i);
233
+ }
234
+
235
+ // Update the instance buffer with new matrices
236
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceMatrixBuffer);
237
+ this.gl.bufferData(
238
+ this.gl.ARRAY_BUFFER,
239
+ this.instanceMatrices,
240
+ this.gl.DYNAMIC_DRAW
241
+ );
242
+
243
+ // Also update texture coordinates
244
+ this.updateAllInstanceTexCoords();
245
+ }
246
+
247
+ /**
248
+ * @method updateInstanceTexCoords
249
+ * @description Updates the texture coordinates for a specific instance
250
+ * @param {number} index - The index of the instance to update
251
+ */
252
+ updateInstanceTexCoords(index) {
253
+ if (
254
+ index >= 0 &&
255
+ index < this.instanceCount &&
256
+ index < this.instances.length
257
+ ) {
258
+ const frame = this.instances[index].frame;
259
+ const texCoords = this.getFrameTexCoords(frame);
260
+
261
+ const offset = index * 8;
262
+ for (let i = 0; i < 8; i++) {
263
+ this.instanceTexCoords[offset + i] = texCoords[i];
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * @method updateAllInstanceTexCoords
270
+ * @description Updates all instance texture coordinates
271
+ */
272
+ updateAllInstanceTexCoords() {
273
+ for (let i = 0; i < this.instances.length; i++) {
274
+ this.updateInstanceTexCoords(i);
275
+ }
276
+
277
+ // Update the instance texture coordinate buffer
278
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceTexCoordBuffer);
279
+ this.gl.bufferData(
280
+ this.gl.ARRAY_BUFFER,
281
+ this.instanceTexCoords,
282
+ this.gl.DYNAMIC_DRAW
283
+ );
284
+ }
285
+
286
+ /**
287
+ * @method getInstanceWithId
288
+ * @description Gets an instance by its ID
289
+ * @param {string} id - The ID of the instance to get
290
+ * @returns {Instance} - The instance
291
+ */
292
+ getInstanceWithId(id) {
293
+ return this.instances.find((instance) => instance.id === id);
294
+ }
295
+
296
+ /**
297
+ * @method update
298
+ * @description Updates the instanced texture
299
+ * @param {number} currentTime - The current time
300
+ */
301
+ update(currentTime) {
302
+ let needsUpdate = false;
303
+
304
+ // Update animations for all instances
305
+ for (let i = 0; i < this.instances.length; i++) {
306
+ const instance = this.instances[i];
307
+ if (instance.updateAnimation(currentTime)) {
308
+ needsUpdate = true;
309
+ }
310
+ }
311
+
312
+ // If any instance frame changed, update texture coordinates
313
+ if (needsUpdate) {
314
+ this.updateAllInstanceTexCoords();
315
+ }
316
+ }
317
+
318
+ /**
319
+ * @method animateInstance
320
+ * @description Starts animation on a specific instance
321
+ * @param {string} instanceId - The ID of the instance to animate
322
+ * @param {Array} frames - The frames to animate
323
+ * @param {number} speed - The speed of the animation
324
+ */
325
+ animateInstance(instanceId, frames, speed = 1000) {
326
+ const instance = this.getInstanceWithId(instanceId);
327
+ if (instance) {
328
+ instance.playAnimation(frames, speed);
329
+ } else {
330
+ console.warn(`Instance with ID ${instanceId} not found`);
331
+ }
332
+ }
333
+
334
+ /**
335
+ * @method stopInstanceAnimation
336
+ * @description Stops animation on a specific instance
337
+ * @param {string} instanceId - The ID of the instance to stop animation
338
+ * @param {boolean} revertToOriginal - Whether to revert to the original frame
339
+ */
340
+ stopInstanceAnimation(instanceId, revertToOriginal = false) {
341
+ const instance = this.getInstanceWithId(instanceId);
342
+ if (instance) {
343
+ instance.stopAnimation(revertToOriginal);
344
+ } else {
345
+ console.warn(`Instance with ID ${instanceId} not found`);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * @method addClickEventToAllInstances
351
+ * @description Adds a click event to all instances
352
+ * @param {function} func - The function to call when the instance is clicked
353
+ */
354
+ addClickEventToAllInstances(func) {
355
+ this.instances.forEach((instance) => {
356
+ this.addInstanceClickEvent(instance.id, func);
357
+ });
358
+ }
359
+
360
+ /**
361
+ * @method addHoverEventToAllInstances
362
+ * @description Adds a hover event to all instances
363
+ * @param {function} enterFunc - The function to call when the instance is hovered
364
+ * @param {function} leaveFunc - The function to call when the instance is no longer hovered
365
+ */
366
+ addHoverEventToAllInstances(enterFunc, leaveFunc) {
367
+ this.instances.forEach((instance) => {
368
+ this.addInstanceHoverEvent(instance.id, enterFunc, leaveFunc);
369
+ });
370
+ }
371
+
372
+ /**
373
+ * @method removeClickEventFromAllInstances
374
+ * @description Removes a click event from all instances
375
+ * @param {function} func - The function to remove
376
+ */
377
+ removeClickEventFromAllInstances(func) {
378
+ this.instances.forEach((instance) => {
379
+ this.removeInstanceClickEvent(instance.id, func);
380
+ });
381
+ }
382
+
383
+ /**
384
+ * @method removeHoverEventFromAllInstances
385
+ * @description Removes a hover event from all instances
386
+ * @param {function} enterFunc - The function to remove
387
+ * @param {function} leaveFunc - The function to remove
388
+ */
389
+ removeHoverEventFromAllInstances(enterFunc, leaveFunc) {
390
+ this.instances.forEach((instance) => {
391
+ this.removeInstanceHoverEvent(instance.id, enterFunc, leaveFunc);
392
+ });
393
+ }
394
+
395
+ /**
396
+ * @method getFrameTexCoords
397
+ * @description Calculates the texture coordinates for a specific frame
398
+ * @param {number} frame - The frame to calculate the texture coordinates for
399
+ * @returns {Array} - The texture coordinates
400
+ */
401
+ getFrameTexCoords(frame) {
402
+ let texLeft, texRight, texTop, texBottom;
403
+
404
+ if (this.frameWidth > 0 && this.frameHeight > 0) {
405
+ // Spritesheet animation
406
+ const col = frame % this.framesPerRow;
407
+ const row = Math.floor(frame / this.framesPerRow);
408
+
409
+ texLeft = ((col + 1) * this.frameWidth) / this.textureWidth;
410
+ texRight = (col * this.frameWidth) / this.textureWidth;
411
+ texTop =
412
+ (this.textureHeight - row * this.frameHeight - this.frameHeight) /
413
+ this.textureHeight;
414
+ texBottom =
415
+ (this.textureHeight - row * this.frameHeight) / this.textureHeight;
416
+ } else {
417
+ // Static image
418
+ texLeft = 1.0;
419
+ texRight = 0.0;
420
+ texTop = 0.0;
421
+ texBottom = 1.0;
422
+ }
423
+
424
+ // Return texture coordinates (considering mirroring)
425
+ return this.mirrored
426
+ ? [
427
+ texRight,
428
+ texBottom,
429
+ texLeft,
430
+ texBottom,
431
+ texRight,
432
+ texTop,
433
+ texLeft,
434
+ texTop,
435
+ ]
436
+ : [
437
+ texLeft,
438
+ texBottom,
439
+ texRight,
440
+ texBottom,
441
+ texLeft,
442
+ texTop,
443
+ texRight,
444
+ texTop,
445
+ ];
446
+ }
447
+
448
+ /**
449
+ * @method draw
450
+ * @description Draws the instanced texture
451
+ */
452
+ draw() {
453
+ this.updateAllInstanceMatrices();
454
+ this.update(performance.now());
455
+ for (let i = 0; i < this.instances.length; i++) {
456
+ let instance = this.instances[i];
457
+ let rigidBody = instance.getComponent(RigidBody);
458
+ let collider = instance.getComponent(Collider);
459
+
460
+ if (rigidBody) {
461
+ if (rigidBody.getType() === "dynamic") {
462
+ const updatedPos = rigidBody.getBody().getPosition();
463
+ const rigidBodyOffset = rigidBody.getOffset();
464
+ instance.transform.position.x =
465
+ updatedPos.x * rigidBody.getPhysics().getScale() -
466
+ rigidBodyOffset.x;
467
+ instance.transform.position.y =
468
+ updatedPos.y * rigidBody.getPhysics().getScale() -
469
+ rigidBodyOffset.y;
470
+ if (collider) {
471
+ collider.debugShape.gameObject.transform.position.x =
472
+ instance.transform.position.x;
473
+ collider.debugShape.gameObject.transform.position.y =
474
+ instance.transform.position.y;
475
+ }
476
+ }
477
+ }
478
+ }
479
+ this.gl.uniform1i(
480
+ this.programInfo.uniformLocations.useTexture,
481
+ this.useTexture
482
+ );
483
+
484
+ this.gl.uniform1i(this.programInfo.uniformLocations.useInstances, true);
485
+
486
+ // Set lighting uniform
487
+ this.gl.uniform1i(
488
+ this.programInfo.uniformLocations.useLighting,
489
+ this.useLighting
490
+ );
491
+
492
+ // Always set the color uniform for tinting
493
+ this.gl.uniform4fv(this.programInfo.uniformLocations.color, this.color);
494
+
495
+ // Set per-GameObject opacity if available (defaults to 1.0)
496
+ const ownerGameObject = this.getParent();
497
+ const ownerOpacity =
498
+ ownerGameObject && typeof ownerGameObject.opacity === "number"
499
+ ? ownerGameObject.opacity
500
+ : 1.0;
501
+ if (this.programInfo.uniformLocations.uOpacity) {
502
+ this.gl.uniform1f(
503
+ this.programInfo.uniformLocations.uOpacity,
504
+ ownerOpacity
505
+ );
506
+ }
507
+
508
+ // Bind and set vertex position buffer
509
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.verticesBuffer);
510
+ this.gl.vertexAttribPointer(
511
+ this.programInfo.attribLocations.vertexPosition,
512
+ 2,
513
+ this.gl.FLOAT,
514
+ false,
515
+ 0,
516
+ 0
517
+ );
518
+ this.gl.enableVertexAttribArray(
519
+ this.programInfo.attribLocations.vertexPosition
520
+ );
521
+
522
+ // Bind and set texture coordinate buffer (for non-instanced rendering)
523
+ if (this.useTexture) {
524
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
525
+ this.gl.vertexAttribPointer(
526
+ this.programInfo.attribLocations.aTexCoord,
527
+ 2,
528
+ this.gl.FLOAT,
529
+ false,
530
+ 0,
531
+ 0
532
+ );
533
+ this.gl.enableVertexAttribArray(
534
+ this.programInfo.attribLocations.aTexCoord
535
+ );
536
+
537
+ if (this.texture) {
538
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
539
+ }
540
+
541
+ // Set up per-instance texture coordinate attributes
542
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceTexCoordBuffer);
543
+
544
+ // Each instance has 4 texture coordinates (for 4 vertices)
545
+ const texCoordAttribs = [
546
+ this.programInfo.attribLocations.instanceTexCoord0,
547
+ this.programInfo.attribLocations.instanceTexCoord1,
548
+ this.programInfo.attribLocations.instanceTexCoord2,
549
+ this.programInfo.attribLocations.instanceTexCoord3,
550
+ ];
551
+
552
+ for (let i = 0; i < 4; i++) {
553
+ const loc = texCoordAttribs[i];
554
+ if (loc !== -1) {
555
+ this.gl.enableVertexAttribArray(loc);
556
+ this.gl.vertexAttribPointer(
557
+ loc,
558
+ 2,
559
+ this.gl.FLOAT,
560
+ false,
561
+ 8 * 4, // 8 floats per instance (4 vertices * 2 coords)
562
+ i * 2 * 4 // offset for each vertex
563
+ );
564
+ this.gl.vertexAttribDivisor(loc, 1); // This makes it instanced
565
+ }
566
+ }
567
+ }
568
+
569
+ // Set up instance matrix attributes (4 vec4s for each matrix)
570
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instanceMatrixBuffer);
571
+
572
+ const matrixLoc = this.programInfo.attribLocations.instanceMatrix;
573
+ for (let i = 0; i < 4; i++) {
574
+ const loc = matrixLoc + i;
575
+ this.gl.enableVertexAttribArray(loc);
576
+ this.gl.vertexAttribPointer(
577
+ loc,
578
+ 4,
579
+ this.gl.FLOAT,
580
+ false,
581
+ 16 * 4,
582
+ i * 4 * 4
583
+ );
584
+ this.gl.vertexAttribDivisor(loc, 1); // This makes it instanced
585
+ }
586
+
587
+ // Draw all instances
588
+ const drawMode =
589
+ this.vertices.length === 8 ? this.gl.TRIANGLE_STRIP : this.gl.TRIANGLES;
590
+ this.gl.drawArraysInstanced(
591
+ drawMode,
592
+ 0,
593
+ this.vertices.length / 2,
594
+ this.instances.length // Use actual instance count, not max
595
+ );
596
+
597
+ // Reset attribute divisors
598
+ for (let i = 0; i < 4; i++) {
599
+ this.gl.vertexAttribDivisor(matrixLoc + i, 0);
600
+ this.gl.disableVertexAttribArray(matrixLoc + i);
601
+ }
602
+
603
+ // Reset texture coordinate attribute divisors
604
+ if (this.useTexture) {
605
+ const texCoordAttribs = [
606
+ this.programInfo.attribLocations.instanceTexCoord0,
607
+ this.programInfo.attribLocations.instanceTexCoord1,
608
+ this.programInfo.attribLocations.instanceTexCoord2,
609
+ this.programInfo.attribLocations.instanceTexCoord3,
610
+ ];
611
+
612
+ for (let i = 0; i < 4; i++) {
613
+ const loc = texCoordAttribs[i];
614
+ if (loc !== -1) {
615
+ this.gl.vertexAttribDivisor(loc, 0);
616
+ this.gl.disableVertexAttribArray(loc);
617
+ }
618
+ }
619
+ }
620
+
621
+ this.gl.uniform1i(this.programInfo.uniformLocations.useInstances, false);
622
+ }
623
+
624
+ /**
625
+ * @method setInstanceFrame
626
+ * @description Updates the frame of a specific instance
627
+ * @param {string} instanceId - The ID of the instance to update
628
+ * @param {number} frame - The frame to set
629
+ */
630
+ setInstanceFrame(instanceId, frame) {
631
+ const instance = this.getInstanceWithId(instanceId);
632
+ if (instance) {
633
+ instance.frame = frame;
634
+ const index = this.instances.indexOf(instance);
635
+ if (index !== -1) {
636
+ this.updateInstanceTexCoords(index);
637
+ }
638
+ }
639
+ }
640
+
641
+ /**
642
+ * @method isPointInInstance
643
+ * @description Checks if a point is inside a specific instance
644
+ * @param {number} x - The x coordinate of the point
645
+ * @param {number} y - The y coordinate of the point
646
+ * @param {Instance} instance - The instance to check
647
+ * @returns {boolean} - Whether the point is inside the instance
648
+ */
649
+ isPointInInstance(x, y, instance) {
650
+ const left = instance.transform.position.x - instance.transform.scale.x;
651
+ const right = instance.transform.position.x + instance.transform.scale.x;
652
+ const top = instance.transform.position.y + instance.transform.scale.y;
653
+ const bottom = instance.transform.position.y - instance.transform.scale.y;
654
+ return x >= left && x <= right && y >= bottom && y <= top;
655
+ }
656
+
657
+ /**
658
+ * @method getInstanceAtPosition
659
+ * @description Gets an instance at a specific position
660
+ * @param {Vector3} position - The position to check
661
+ * @param {number} tolerance - The tolerance for the position
662
+ * @returns {Instance} - The instance at the position
663
+ */
664
+ getInstanceAtPosition(position, tolerance = 0.1) {
665
+ return this.instances.find((instance) => {
666
+ const instancePosition = instance.transform.position;
667
+ return (
668
+ Math.abs(instancePosition.x - position.x) < tolerance &&
669
+ Math.abs(instancePosition.y - position.y) < tolerance &&
670
+ Math.abs(instancePosition.z - position.z) < tolerance
671
+ );
672
+ });
673
+ }
674
+
675
+ /**
676
+ * @method getInstanceByFrame
677
+ * @description Gets an instance by its frame index
678
+ * @param {number} frameIndex - The frame index to get
679
+ * @returns {Instance} - The instance at the frame index
680
+ */
681
+ getInstanceByFrame(frameIndex) {
682
+ return this.instances.find((instance) => instance.frame === frameIndex);
683
+ }
684
+
685
+ /**
686
+ * @method getInstanceAtPoint
687
+ * @description Gets an instance at a specific point
688
+ * @param {number} x - The x coordinate of the point
689
+ * @param {number} y - The y coordinate of the point
690
+ * @returns {Instance} - The instance at the point
691
+ */
692
+ getInstanceAtPoint(x, y) {
693
+ for (let i = 0; i < this.instances.length; i++) {
694
+ const instance = this.instances[i];
695
+ if (this.isPointInInstance(x, y, instance)) {
696
+ return instance;
697
+ }
698
+ }
699
+ return null;
700
+ }
701
+
702
+ /**
703
+ * @method addInstanceClickEvent
704
+ * @description Adds a click event listener to a specific instance
705
+ * @param {string} instanceId - The ID of the instance to add the click event listener to
706
+ * @param {function} func - The function to call when the instance is clicked
707
+ */
708
+ addInstanceClickEvent(instanceId, func) {
709
+ if (!this.instanceClickListeners.has(instanceId)) {
710
+ this.instanceClickListeners.set(instanceId, new Set());
711
+ }
712
+ this.instanceClickListeners.get(instanceId).add(func);
713
+ }
714
+
715
+ /**
716
+ * @method removeInstanceClickEvent
717
+ * @description Removes a click event listener from a specific instance
718
+ * @param {string} instanceId - The ID of the instance to remove the click event listener from
719
+ * @param {function} func - The function to remove
720
+ */
721
+ removeInstanceClickEvent(instanceId, func) {
722
+ if (this.instanceClickListeners.has(instanceId)) {
723
+ this.instanceClickListeners.get(instanceId).delete(func);
724
+ if (this.instanceClickListeners.get(instanceId).size === 0) {
725
+ this.instanceClickListeners.delete(instanceId);
726
+ }
727
+ }
728
+ }
729
+
730
+ /**
731
+ * @method addInstanceHoverEvent
732
+ * @description Adds a hover event listener to a specific instance
733
+ * @param {string} instanceId - The ID of the instance to add the hover event listener to
734
+ * @param {function} enterFunc - The function to call when the instance is hovered
735
+ * @param {function} leaveFunc - The function to call when the instance is no longer hovered
736
+ */
737
+ addInstanceHoverEvent(instanceId, enterFunc, leaveFunc) {
738
+ if (!this.instanceHoverListeners.has(instanceId)) {
739
+ this.instanceHoverListeners.set(instanceId, {
740
+ enter: new Set(),
741
+ leave: new Set(),
742
+ });
743
+ }
744
+ this.instanceHoverListeners.get(instanceId).enter.add(enterFunc);
745
+ this.instanceHoverListeners.get(instanceId).leave.add(leaveFunc);
746
+ }
747
+
748
+ /**
749
+ * @method removeInstanceHoverEvent
750
+ * @description Removes a hover event listener from a specific instance
751
+ * @param {string} instanceId - The ID of the instance to remove the hover event listener from
752
+ * @param {function} enterFunc - The function to remove
753
+ * @param {function} leaveFunc - The function to remove
754
+ */
755
+ removeInstanceHoverEvent(instanceId, enterFunc, leaveFunc) {
756
+ if (this.instanceHoverListeners.has(instanceId)) {
757
+ const listeners = this.instanceHoverListeners.get(instanceId);
758
+ listeners.enter.delete(enterFunc);
759
+ listeners.leave.delete(leaveFunc);
760
+ if (listeners.enter.size === 0 && listeners.leave.size === 0) {
761
+ this.instanceHoverListeners.delete(instanceId);
762
+ }
763
+ }
764
+ }
765
+
766
+ /**
767
+ * @method handleInstanceClick
768
+ * @description Handles a click event for a specific instance
769
+ * @param {Event} event - The event to handle
770
+ * @param {number} x - The x coordinate of the click
771
+ * @param {number} y - The y coordinate of the click
772
+ */
773
+ handleInstanceClick(event, x, y) {
774
+ const clickedInstance = this.getInstanceAtPoint(x, y);
775
+ if (clickedInstance) {
776
+ const listeners = this.instanceClickListeners.get(clickedInstance.id);
777
+ if (listeners) {
778
+ listeners.forEach((func) => func(event, clickedInstance));
779
+ }
780
+ return true; // Event was handled
781
+ }
782
+ return false; // No instance was clicked
783
+ }
784
+
785
+ /**
786
+ * @method handleInstanceHover
787
+ * @description Handles a hover event for a specific instance
788
+ * @param {Event} event - The event to handle
789
+ * @param {number} x - The x coordinate of the hover
790
+ * @param {number} y - The y coordinate of the hover
791
+ * @param {Instance} lastHoveredInstance - The last hovered instance
792
+ */
793
+ handleInstanceHover(event, x, y, lastHoveredInstance) {
794
+ const hoveredInstance = this.getInstanceAtPoint(x, y);
795
+
796
+ if (hoveredInstance !== lastHoveredInstance) {
797
+ // Handle leave event for previous instance
798
+ if (lastHoveredInstance) {
799
+ const leaveListeners = this.instanceHoverListeners.get(
800
+ lastHoveredInstance.id
801
+ );
802
+ if (leaveListeners) {
803
+ leaveListeners.leave.forEach((func) =>
804
+ func(event, lastHoveredInstance)
805
+ );
806
+ }
807
+ }
808
+
809
+ // Handle enter event for new instance
810
+ if (hoveredInstance) {
811
+ const enterListeners = this.instanceHoverListeners.get(
812
+ hoveredInstance.id
813
+ );
814
+ if (enterListeners) {
815
+ enterListeners.enter.forEach((func) => func(event, hoveredInstance));
816
+ }
817
+ }
818
+ }
819
+
820
+ return hoveredInstance;
821
+ }
822
+
823
+ /**
824
+ * @method playAnimation
825
+ * @description Plays an animation
826
+ * @param {Array} frames - The frames to play
827
+ * @param {number} speed - The speed of the animation
828
+ */
829
+ playAnimation(frames, speed = 1000) {
830
+ // Override with empty implementation
831
+ console.warn("playAnimation is not implemented for InstancedTexture");
832
+ }
833
+
834
+ /**
835
+ * @method playAnimationOnce
836
+ * @description Plays an animation once
837
+ * @param {Array} frames - The frames to play
838
+ * @param {number} speed - The speed of the animation
839
+ */
840
+ playAnimationOnce(frames, speed = 1000) {
841
+ // Override with empty implementation
842
+ console.warn("playAnimationOnce is not implemented for InstancedTexture");
843
+ }
844
+
845
+ /**
846
+ * @method getAnimation
847
+ * @description Gets the animation
848
+ * @returns {Array} - The animation
849
+ */
850
+ getAnimation() {
851
+ // Override with empty implementation
852
+ console.warn("getAnimation is not implemented for InstancedTexture");
853
+ }
854
+ }
855
+
856
+ export default InstancedTexture;