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.
- package/README.md +968 -0
- package/dist/types/imports/BitmapText.d.ts +79 -0
- package/dist/types/imports/Color.d.ts +17 -0
- package/dist/types/imports/Drawable.d.ts +157 -0
- package/dist/types/imports/Emerald.d.ts +73 -0
- package/dist/types/imports/FPSCounter.d.ts +19 -0
- package/dist/types/imports/GLUtils.d.ts +6 -0
- package/dist/types/imports/Instance.d.ts +87 -0
- package/dist/types/imports/InstancedTexture.d.ts +230 -0
- package/dist/types/imports/Physics.d.ts +139 -0
- package/dist/types/imports/Scene.d.ts +39 -0
- package/dist/types/imports/Shaders.d.ts +4 -0
- package/dist/types/imports/Shapes.d.ts +25 -0
- package/dist/types/imports/Storage.d.ts +46 -0
- package/dist/types/imports/Texture.d.ts +19 -0
- package/dist/types/imports/Time.d.ts +22 -0
- package/dist/types/imports/Transform.d.ts +41 -0
- package/dist/types/imports/components/BoxCollider.d.ts +41 -0
- package/dist/types/imports/components/BoxColliderDebug.d.ts +19 -0
- package/dist/types/imports/components/CircleCollider.d.ts +39 -0
- package/dist/types/imports/components/CircleColliderDebug.d.ts +19 -0
- package/dist/types/imports/components/Collider.d.ts +53 -0
- package/dist/types/imports/components/GameObject.d.ts +72 -0
- package/dist/types/imports/components/RigidBody.d.ts +104 -0
- package/dist/types/imports/lights/DirectionalLight.d.ts +26 -0
- package/dist/types/imports/lights/PointLight.d.ts +18 -0
- package/dist/types/imports/managers/AudioManager.d.ts +60 -0
- package/dist/types/imports/managers/CameraManager.d.ts +35 -0
- package/dist/types/imports/managers/EventManager.d.ts +187 -0
- package/dist/types/imports/managers/GLManager.d.ts +47 -0
- package/dist/types/imports/managers/IDManager.d.ts +21 -0
- package/dist/types/imports/managers/SceneManager.d.ts +22 -0
- package/dist/types/imports/particlesystem/Particle.d.ts +42 -0
- package/dist/types/imports/particlesystem/ParticleSettings.d.ts +49 -0
- package/dist/types/imports/particlesystem/Particles.d.ts +71 -0
- package/dist/types/index.d.ts +32 -0
- package/imports/BitmapText.js +245 -0
- package/imports/Color.js +18 -0
- package/imports/Drawable.js +550 -0
- package/imports/Emerald.js +459 -0
- package/imports/FPSCounter.js +43 -0
- package/imports/GLUtils.js +67 -0
- package/imports/Instance.js +187 -0
- package/imports/InstancedTexture.js +856 -0
- package/imports/Physics.js +242 -0
- package/imports/Scene.js +79 -0
- package/imports/Shaders.js +165 -0
- package/imports/Shapes.js +129 -0
- package/imports/Storage.js +63 -0
- package/imports/Texture.js +67 -0
- package/imports/Time.js +28 -0
- package/imports/Transform.js +59 -0
- package/imports/components/BoxCollider.js +67 -0
- package/imports/components/BoxColliderDebug.js +38 -0
- package/imports/components/CircleCollider.js +67 -0
- package/imports/components/CircleColliderDebug.js +36 -0
- package/imports/components/Collider.js +51 -0
- package/imports/components/GameObject.js +160 -0
- package/imports/components/RigidBody.js +169 -0
- package/imports/lights/DirectionalLight.js +68 -0
- package/imports/lights/PointLight.js +17 -0
- package/imports/managers/AudioManager.js +146 -0
- package/imports/managers/CameraManager.js +60 -0
- package/imports/managers/EventManager.js +479 -0
- package/imports/managers/GLManager.js +65 -0
- package/imports/managers/IDManager.js +32 -0
- package/imports/managers/SceneManager.js +27 -0
- package/imports/managers/ShaderManager.js +133 -0
- package/imports/particlesystem/Particle.js +75 -0
- package/imports/particlesystem/ParticleSettings.js +49 -0
- package/imports/particlesystem/Particles.js +226 -0
- package/index.js +67 -0
- 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;
|