@lijuhong1981/three.instancedsprite 1.0.0 → 1.1.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.
@@ -1,406 +1,425 @@
1
- import Check from "@lijuhong1981/jscheck/src/Check.js";
2
- import { BufferAttribute, InstancedBufferAttribute, InstancedBufferGeometry, Mesh, Raycaster, Texture } from "three";
3
- import InstancedSprite from "./InstancedSprite.js";
4
- import InstancedSpriteMaterial from "./InstancedSpriteMaterial.js";
5
- import InstancedSpriteNodeMaterial from "./InstancedSpriteNodeMaterial.js";
6
-
7
- /**
8
- * @import InstancedSpriteCollection from "./InstancedSpriteCollection.js";
9
- */
10
-
11
- /**
12
- * 顶点数据,定义了一个单位正方形的四个顶点位置和UV坐标,以及组成两个三角形的索引
13
- * @ignore
14
- */
15
- const positions = new Float32Array([
16
- -0.5, -0.5, 0,
17
- 0.5, -0.5, 0,
18
- 0.5, 0.5, 0,
19
- -0.5, 0.5, 0,
20
- ]);
21
- const uvs = new Float32Array([
22
- 0, 0,
23
- 1, 0,
24
- 1, 1,
25
- 0, 1,
26
- ]);
27
- const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
28
- const geometry = new InstancedBufferGeometry();
29
- geometry.setAttribute("position", new BufferAttribute(positions, 3));
30
- geometry.setAttribute("uv", new BufferAttribute(uvs, 2));
31
- geometry.setIndex(new BufferAttribute(indices, 1));
32
- // geometry.computeVertexNormals();
33
-
34
- // --- 定义InstancedSpriteMaterial使用的attribute名称常量 ---
35
- const aPositionAndShow = 'aPositionAndShow';
36
- const aCenterAndSize = 'aCenterAndSize';
37
- const aScaleAndRotationAndSizeAttenuation = 'aScaleAndRotationAndSizeAttenuation';
38
- const aColorAndOpacity = 'aColorAndOpacity';
39
- /**
40
- * InstancedSpriteMaterial使用的attribute名称常量与itemSize大小,InstancedSpriteMesh会根据名称和itemSize创建InstancedBufferAttribute
41
- * @type {Object<string, number>}
42
- * @readonly
43
- * @ignore
44
- */
45
- const AttributesItemSize = Object.freeze({
46
- aPositionAndShow: 4,
47
- aCenterAndSize: 4,
48
- aScaleAndRotationAndSizeAttenuation: 3,
49
- aColorAndOpacity: 4,
50
- });
51
- /**
52
- * 检查并更新一个InstancedSprite实例的数据到对应的InstancedBufferAttribute位置
53
- * @param {InstancedSpriteMesh} mesh
54
- * @param {InstancedSprite} sprite
55
- * @ignore
56
- */
57
- function checkAndUpdateInstancedSprite(mesh, sprite) {
58
- const attributesData = mesh._attributesData;
59
- const index = sprite._instanceIndex;
60
-
61
- const positionChanged = (sprite._position.equals(sprite.position) === false);
62
- const showChanged = (sprite._show !== sprite.show);
63
- const centerChanged = (sprite._center.equals(sprite.center) === false);
64
- const imageSizeChanged = (sprite._imageSize.equals(sprite.imageSize) === false);
65
- const scaleChanged = (sprite._scale !== sprite.scale);
66
- const rotationChanged = (sprite._rotation !== sprite.rotation);
67
- const sizeAttenuationChanged = (sprite._sizeAttenuation !== sprite.sizeAttenuation);
68
- const colorChanged = (sprite._color.equals(sprite.color) === false);
69
- const opacityChanged = (sprite._opacity !== sprite.opacity);
70
-
71
- let idx = 0;
72
-
73
- if (positionChanged || showChanged) {
74
- idx = index * AttributesItemSize.aPositionAndShow;
75
- if (positionChanged) {
76
- sprite._position.copy(sprite.position);
77
- attributesData.aPositionAndShow[idx] = sprite._position.x;
78
- attributesData.aPositionAndShow[idx + 1] = sprite._position.y;
79
- attributesData.aPositionAndShow[idx + 2] = sprite._position.z;
80
- }
81
- if (showChanged) {
82
- sprite._show = sprite.show;
83
- attributesData.aPositionAndShow[idx + 3] = sprite._show ? 1 : 0;
84
- }
85
- mesh.geometry.attributes.aPositionAndShow.needsUpdate = true;
86
- }
87
-
88
- if (centerChanged || imageSizeChanged) {
89
- idx = index * AttributesItemSize.aCenterAndSize;
90
- if (centerChanged) {
91
- sprite._center.copy(sprite.center);
92
- attributesData.aCenterAndSize[idx] = sprite._center.x;
93
- attributesData.aCenterAndSize[idx + 1] = sprite._center.y;
94
- }
95
- if (imageSizeChanged) {
96
- sprite._imageSize.copy(sprite.imageSize);
97
- attributesData.aCenterAndSize[idx + 2] = sprite._imageSize.x;
98
- attributesData.aCenterAndSize[idx + 3] = sprite._imageSize.y;
99
- }
100
- mesh.geometry.attributes.aCenterAndSize.needsUpdate = true;
101
- }
102
-
103
- if (scaleChanged || rotationChanged || sizeAttenuationChanged) {
104
- idx = index * AttributesItemSize.aScaleAndRotationAndSizeAttenuation;
105
- if (scaleChanged) {
106
- sprite._scale = sprite.scale;
107
- attributesData.aScaleAndRotationAndSizeAttenuation[idx] = sprite._scale;
108
- }
109
- if (rotationChanged) {
110
- sprite._rotation = sprite.rotation;
111
- attributesData.aScaleAndRotationAndSizeAttenuation[idx + 1] = sprite._rotation;
112
- }
113
- if (sizeAttenuationChanged) {
114
- sprite._sizeAttenuation = sprite.sizeAttenuation;
115
- attributesData.aScaleAndRotationAndSizeAttenuation[idx + 2] = sprite._sizeAttenuation ? 1 : 0;
116
- }
117
- mesh.geometry.attributes.aScaleAndRotationAndSizeAttenuation.needsUpdate = true;
118
- }
119
-
120
- if (colorChanged || opacityChanged) {
121
- idx = index * AttributesItemSize.aColorAndOpacity;
122
- if (colorChanged) {
123
- sprite._color.copy(sprite.color);
124
- attributesData.aColorAndOpacity[idx] = sprite._color.r;
125
- attributesData.aColorAndOpacity[idx + 1] = sprite._color.g;
126
- attributesData.aColorAndOpacity[idx + 2] = sprite._color.b;
127
- }
128
- if (opacityChanged) {
129
- sprite._opacity = sprite.opacity;
130
- attributesData.aColorAndOpacity[idx + 3] = sprite._opacity;
131
- }
132
- mesh.geometry.attributes.aColorAndOpacity.needsUpdate = true;
133
- }
134
- };
135
- /**
136
- * 添加一个InstancedSprite实例的数据到InstancedBufferAttribute中对应的位置
137
- * @param {InstancedSpriteMesh} mesh
138
- * @param {InstancedSprite} sprite
139
- * @param {number} index
140
- * @ignore
141
- */
142
- function addInstancedSprite(mesh, sprite, index) {
143
- const attributesData = mesh._attributesData;
144
- sprite._instanceIndex = index;
145
- let idx = 0;
146
-
147
- idx = index * AttributesItemSize.aPositionAndShow;
148
- sprite._position.copy(sprite.position);
149
- attributesData.aPositionAndShow[idx] = sprite._position.x;
150
- attributesData.aPositionAndShow[idx + 1] = sprite._position.y;
151
- attributesData.aPositionAndShow[idx + 2] = sprite._position.z;
152
- sprite._show = sprite.show;
153
- attributesData.aPositionAndShow[idx + 3] = sprite._show ? 1 : 0;
154
- mesh.geometry.attributes.aPositionAndShow.needsUpdate = true;
155
-
156
- idx = index * AttributesItemSize.aCenterAndSize;
157
- sprite._center.copy(sprite.center);
158
- attributesData.aCenterAndSize[idx] = sprite._center.x;
159
- attributesData.aCenterAndSize[idx + 1] = sprite._center.y;
160
- sprite._imageSize.copy(sprite.imageSize);
161
- attributesData.aCenterAndSize[idx + 2] = sprite._imageSize.x;
162
- attributesData.aCenterAndSize[idx + 3] = sprite._imageSize.y;
163
- mesh.geometry.attributes.aCenterAndSize.needsUpdate = true;
164
-
165
- idx = index * AttributesItemSize.aScaleAndRotationAndSizeAttenuation;
166
- sprite._scale = sprite.scale;
167
- attributesData.aScaleAndRotationAndSizeAttenuation[idx] = sprite._scale;
168
- sprite._rotation = sprite.rotation;
169
- attributesData.aScaleAndRotationAndSizeAttenuation[idx + 1] = sprite._rotation;
170
- sprite._sizeAttenuation = sprite.sizeAttenuation;
171
- attributesData.aScaleAndRotationAndSizeAttenuation[idx + 2] = sprite._sizeAttenuation ? 1 : 0;
172
- mesh.geometry.attributes.aScaleAndRotationAndSizeAttenuation.needsUpdate = true;
173
-
174
- idx = index * AttributesItemSize.aColorAndOpacity;
175
- sprite._color.copy(sprite.color);
176
- attributesData.aColorAndOpacity[idx] = sprite._color.r;
177
- attributesData.aColorAndOpacity[idx + 1] = sprite._color.g;
178
- attributesData.aColorAndOpacity[idx + 2] = sprite._color.b;
179
- sprite._opacity = sprite.opacity;
180
- attributesData.aColorAndOpacity[idx + 3] = sprite._opacity;
181
- mesh.geometry.attributes.aColorAndOpacity.needsUpdate = true;
182
- };
183
-
184
- /**
185
- * InstancedSpriteMesh类,基于InstancedBufferGeometry实现的高性能InstancedSprite渲染组件
186
- *
187
- * @extends {Mesh}
188
- */
189
- class InstancedSpriteMesh extends Mesh {
190
- /**
191
- * @param {InstancedSpriteCollection} collection - 所属的InstancedSpriteCollection实例,必填
192
- * @param {Texture} texture - 材质图像纹理,必填
193
- * @constructor
194
- */
195
- constructor(collection, texture) {
196
- Check.defined('collection', collection);
197
- Check.defined('texture', texture);
198
- super(geometry.clone(), collection.useNodeMaterial ? new InstancedSpriteNodeMaterial() : new InstancedSpriteMaterial());
199
- this._collection = collection; //所属的InstancedSpriteCollection实例
200
- this.material.texture = texture;
201
- /**
202
- * 对象类型标识
203
- * @type {string}
204
- * @readonly
205
- */
206
- this.type = "InstancedSpriteMesh";
207
- /**
208
- * @type {Array<InstancedSprite>}
209
- * @ignore
210
- */
211
- this._instancedSprites = [];
212
- /**
213
- * @type {Array<InstancedSprite>}
214
- * @ignore
215
- */
216
- this._addInstancedSprites = [];
217
- /**
218
- * @type {Array<InstancedSprite>}
219
- * @ignore
220
- */
221
- this._removeInstancedSprites = [];
222
- /**
223
- * InstancedBufferAttribute数据存储对象,key为属性名称,value为对应的Float32Array数组
224
- * @type {Object<string, Float32Array>}
225
- * @ignore
226
- */
227
- this._attributesData = {};
228
- /**
229
- * 当前能容纳的最大InstancedSprite数量,初始为0,InstancedSpriteMesh会根据需要动态扩容
230
- * @type {number}
231
- * @ignore
232
- */
233
- this._maxCapacity = 0;
234
- this._enaureCapacity(64);
235
- }
236
- /**
237
- * InstancedSpriteMesh对象标识
238
- * @type {boolean}
239
- * @readonly
240
- */
241
- get isInstancedSpriteMesh() { return true; }
242
- /**
243
- * 深度测试开关,默认为true,开启后会进行深度测试以正确处理遮挡关系,但可能会有性能影响;如果关闭则所有InstancedSprite都会被渲染在最前面,适合需要始终显示的UI元素等场景
244
- * @type {boolean}
245
- * @default true
246
- */
247
- get depthTest() { return this.material.depthTest; }
248
- set depthTest(value) {
249
- Check.typeOf.boolean('depthTest', value);
250
- this.material.depthTest = value;
251
- }
252
- /**
253
- * InstancedSprite对象数组
254
- * @type {Array<InstancedSprite>}
255
- * @readonly
256
- */
257
- get instancedSprites() { return this._instancedSprites; }
258
- /**
259
- * 确保InstancedBufferAttribute的大小能够容纳指定数量的InstancedSprite实例,如果当前最大容量不足,则进行扩容
260
- * @param {number} required - 需要容纳的InstancedSprite数量
261
- * @private
262
- */
263
- _enaureCapacity(required) {
264
- if (required < this._maxCapacity) return;
265
-
266
- const newCapacity = Math.max(required, this._maxCapacity + 64);
267
-
268
- for (const [name, itemSize] of Object.entries(AttributesItemSize)) {
269
- const newArray = new Float32Array(newCapacity * itemSize);
270
- const oldArray = this._attributesData[name];
271
- if (oldArray) {
272
- const copyLen = Math.min(oldArray.length, newArray.length);
273
- newArray.set(oldArray.subarray(0, copyLen));
274
- }
275
- this._attributesData[name] = newArray;
276
- }
277
-
278
- // 创建 InstancedBufferAttribute 对象
279
- for (const [name, itemSize] of Object.entries(AttributesItemSize)) {
280
- const attr = new InstancedBufferAttribute(this._attributesData[name], itemSize);
281
- this.geometry.setAttribute(name, attr);
282
- }
283
-
284
- this.geometry.instanceCount = required;
285
- this._maxCapacity = newCapacity;
286
- }
287
- /**
288
- * 添加一个InstancedSprite
289
- * @param {InstancedSprite} sprite - 要添加的InstancedSprite实例
290
- * @returns {InstancedSpriteMesh}
291
- */
292
- add(sprite) {
293
- sprite._mesh = this;
294
- this._addInstancedSprites.push(sprite);
295
- return this;
296
- }
297
- /**
298
- * 移除一个InstancedSprite
299
- * @param {InstancedSprite} sprite - 要移除的InstancedSprite实例
300
- * @returns {InstancedSpriteMesh}
301
- */
302
- remove(sprite) {
303
- if (this._instancedSprites.includes(sprite) && !this._removeInstancedSprites.includes(sprite))
304
- this._removeInstancedSprites.push(sprite);
305
- else
306
- console.warn('InstancedSprite not found in InstancedSpriteMesh or already marked for removal');
307
- return this;
308
- }
309
- /**
310
- * 移除所有InstancedSprite
311
- * @returns {InstancedSpriteMesh}
312
- */
313
- clear() {
314
- this._addInstancedSprites.length = 0;
315
- this._removeInstancedSprites.length = 0;
316
- this._instancedSprites.length = 0;
317
- this.geometry.instanceCount = 0;
318
- return this;
319
- }
320
- /**
321
- * Computes intersection points between a casted ray and this sprite.
322
- *
323
- * @param {Raycaster} raycaster - The raycaster.
324
- * @param {Array<Object>} intersects - The target array that holds the intersection points.
325
- */
326
- raycast(raycaster, intersects) {
327
-
328
- if (raycaster.camera === null) {
329
-
330
- console.error('InstancedSpriteMesh: "Raycaster.camera" needs to be set in order to raycast against sprites.');
331
- return;
332
-
333
- }
334
-
335
- const sprites = this._instancedSprites;
336
- for (const sprite of sprites) {
337
- sprite.raycast(raycaster, intersects, raycaster.camera.matrixWorldInverse);
338
- }
339
- }
340
- /**
341
- * 每帧更新
342
- * @private
343
- */
344
- update() {
345
- if (!this.visible) return;
346
-
347
- const removeInstancedSprites = this._removeInstancedSprites;
348
- const addInstancedSprites = this._addInstancedSprites;
349
- const instancedSprites = this._instancedSprites;
350
-
351
- // 计算最终需要的容量
352
- const requiredCapacity = instancedSprites.length + addInstancedSprites.length - removeInstancedSprites.length;
353
- this._enaureCapacity(requiredCapacity);
354
-
355
- if (removeInstancedSprites.length > 0) {
356
- // 如果有需要移除的InstancedSprite,先从instancedSprites数组中移除
357
- for (const sprite of removeInstancedSprites) {
358
- const index = instancedSprites.indexOf(sprite);
359
- if (index !== -1) {
360
- instancedSprites.splice(index, 1); //从instancedSprites数组中移除
361
- sprite._mesh = undefined;
362
- sprite._instanceIndex = -1;
363
- }
364
- // else
365
- // console.warn('InstancedSprite not found in InstancedSpriteMesh during removal');
366
- }
367
- removeInstancedSprites.length = 0;
368
-
369
- // 如果有需要添加的InstancedSprite,则加入instancedSprites数组中
370
- if (addInstancedSprites.length > 0) {
371
- instancedSprites.push(...addInstancedSprites);
372
- addInstancedSprites.length = 0;
373
- }
374
-
375
- // 重新写入所有InstancedSprite的属性数据到InstancedBufferAttribute中
376
- const length = instancedSprites.length;
377
- for (let i = 0; i < length; i++) {
378
- const sprite = instancedSprites[i];
379
- addInstancedSprite(this, sprite, i);
380
- }
381
- } else {
382
- // 检查并更新属性值有变化的InstancedSprite
383
- for (const sprite of instancedSprites) {
384
- checkAndUpdateInstancedSprite(this, sprite);
385
- }
386
-
387
- // 添加InstancedSprite
388
- while (addInstancedSprites.length > 0) {
389
- const sprite = addInstancedSprites.shift();
390
- addInstancedSprite(this, sprite, instancedSprites.length);
391
- instancedSprites.push(sprite);
392
- }
393
- }
394
-
395
- this.geometry.instanceCount = instancedSprites.length; //更新instanceCount
396
- }
397
- dispose() {
398
- this.clear();
399
- this.geometry.dispose();
400
- this.material.dispose();
401
- }
402
- };
403
-
404
- export default InstancedSpriteMesh;
405
- export { InstancedSpriteMesh };
406
-
1
+ import Check from "@lijuhong1981/jscheck/src/Check.js";
2
+ import { BufferAttribute, InstancedBufferAttribute, InstancedBufferGeometry, Mesh, Raycaster, Texture } from "three";
3
+ import InstancedSprite from "./InstancedSprite.js";
4
+ import InstancedSpriteMaterial from "./InstancedSpriteMaterial.js";
5
+ import InstancedSpriteNodeMaterial from "./InstancedSpriteNodeMaterial.js";
6
+
7
+ /**
8
+ * @import InstancedSpriteCollection from "./InstancedSpriteCollection.js";
9
+ */
10
+
11
+ /**
12
+ * 顶点数据,定义了一个单位正方形的四个顶点位置和UV坐标,以及组成两个三角形的索引
13
+ * @ignore
14
+ */
15
+ const positions = new Float32Array([
16
+ -0.5, -0.5, 0,
17
+ 0.5, -0.5, 0,
18
+ 0.5, 0.5, 0,
19
+ -0.5, 0.5, 0,
20
+ ]);
21
+ const uvs = new Float32Array([
22
+ 0, 0,
23
+ 1, 0,
24
+ 1, 1,
25
+ 0, 1,
26
+ ]);
27
+ const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
28
+ const geometry = new InstancedBufferGeometry();
29
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
30
+ geometry.setAttribute("uv", new BufferAttribute(uvs, 2));
31
+ geometry.setIndex(new BufferAttribute(indices, 1));
32
+ // geometry.computeVertexNormals();
33
+
34
+ // --- 定义InstancedSpriteMaterial使用的attribute名称常量 ---
35
+ const aPositionAndShow = 'aPositionAndShow';
36
+ const aCenterAndSize = 'aCenterAndSize';
37
+ const aScaleAndRotationAndSizeAttenuation = 'aScaleAndRotationAndSizeAttenuation';
38
+ const aColorAndOpacity = 'aColorAndOpacity';
39
+ const aPickColorAndEnabled = 'aPickColorAndEnabled';
40
+ /**
41
+ * InstancedSpriteMaterial使用的attribute名称常量与itemSize大小,InstancedSpriteMesh会根据名称和itemSize创建InstancedBufferAttribute
42
+ * @type {Object<string, number>}
43
+ * @readonly
44
+ * @ignore
45
+ */
46
+ const AttributesItemSize = Object.freeze({
47
+ aPositionAndShow: 4,
48
+ aCenterAndSize: 4,
49
+ aScaleAndRotationAndSizeAttenuation: 3,
50
+ aColorAndOpacity: 4,
51
+ aPickColorAndEnabled: 4,
52
+ });
53
+ /**
54
+ * 检查并更新一个InstancedSprite实例的数据到对应的InstancedBufferAttribute位置
55
+ * @param {InstancedSpriteMesh} mesh
56
+ * @param {InstancedSprite} sprite
57
+ * @ignore
58
+ */
59
+ function checkAndUpdateInstancedSprite(mesh, sprite) {
60
+ const attributesData = mesh._attributesData;
61
+ const index = sprite._instanceIndex;
62
+
63
+ const positionChanged = (sprite._position.equals(sprite.position) === false);
64
+ const showChanged = (sprite._show !== sprite.show);
65
+ const centerChanged = (sprite._center.equals(sprite.center) === false);
66
+ const imageSizeChanged = (sprite._imageSize.equals(sprite.imageSize) === false);
67
+ const scaleChanged = (sprite._scale !== sprite.scale);
68
+ const rotationChanged = (sprite._rotation !== sprite.rotation);
69
+ const sizeAttenuationChanged = (sprite._sizeAttenuation !== sprite.sizeAttenuation);
70
+ const colorChanged = (sprite._color.equals(sprite.color) === false);
71
+ const opacityChanged = (sprite._opacity !== sprite.opacity);
72
+ const pickColorChanged = (sprite._pickColor.equals(sprite.pickColor) === false);
73
+ const enablePickColorChanged = (sprite._enablePickColor !== sprite.enablePickColor);
74
+
75
+ let idx = 0;
76
+
77
+ if (positionChanged || showChanged) {
78
+ idx = index * AttributesItemSize.aPositionAndShow;
79
+ if (positionChanged) {
80
+ sprite._position.copy(sprite.position);
81
+ attributesData.aPositionAndShow[idx] = sprite._position.x;
82
+ attributesData.aPositionAndShow[idx + 1] = sprite._position.y;
83
+ attributesData.aPositionAndShow[idx + 2] = sprite._position.z;
84
+ }
85
+ if (showChanged) {
86
+ sprite._show = sprite.show;
87
+ attributesData.aPositionAndShow[idx + 3] = sprite._show ? 1 : 0;
88
+ }
89
+ mesh.geometry.attributes.aPositionAndShow.needsUpdate = true;
90
+ }
91
+
92
+ if (centerChanged || imageSizeChanged) {
93
+ idx = index * AttributesItemSize.aCenterAndSize;
94
+ if (centerChanged) {
95
+ sprite._center.copy(sprite.center);
96
+ attributesData.aCenterAndSize[idx] = sprite._center.x;
97
+ attributesData.aCenterAndSize[idx + 1] = sprite._center.y;
98
+ }
99
+ if (imageSizeChanged) {
100
+ sprite._imageSize.copy(sprite.imageSize);
101
+ attributesData.aCenterAndSize[idx + 2] = sprite._imageSize.x;
102
+ attributesData.aCenterAndSize[idx + 3] = sprite._imageSize.y;
103
+ }
104
+ mesh.geometry.attributes.aCenterAndSize.needsUpdate = true;
105
+ }
106
+
107
+ if (scaleChanged || rotationChanged || sizeAttenuationChanged) {
108
+ idx = index * AttributesItemSize.aScaleAndRotationAndSizeAttenuation;
109
+ if (scaleChanged) {
110
+ sprite._scale = sprite.scale;
111
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx] = sprite._scale;
112
+ }
113
+ if (rotationChanged) {
114
+ sprite._rotation = sprite.rotation;
115
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx + 1] = sprite._rotation;
116
+ }
117
+ if (sizeAttenuationChanged) {
118
+ sprite._sizeAttenuation = sprite.sizeAttenuation;
119
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx + 2] = sprite._sizeAttenuation ? 1 : 0;
120
+ }
121
+ mesh.geometry.attributes.aScaleAndRotationAndSizeAttenuation.needsUpdate = true;
122
+ }
123
+
124
+ if (colorChanged || opacityChanged) {
125
+ idx = index * AttributesItemSize.aColorAndOpacity;
126
+ if (colorChanged) {
127
+ sprite._color.copy(sprite.color);
128
+ attributesData.aColorAndOpacity[idx] = sprite._color.r;
129
+ attributesData.aColorAndOpacity[idx + 1] = sprite._color.g;
130
+ attributesData.aColorAndOpacity[idx + 2] = sprite._color.b;
131
+ }
132
+ if (opacityChanged) {
133
+ sprite._opacity = sprite.opacity;
134
+ attributesData.aColorAndOpacity[idx + 3] = sprite._opacity;
135
+ }
136
+ mesh.geometry.attributes.aColorAndOpacity.needsUpdate = true;
137
+ }
138
+
139
+ if (pickColorChanged || enablePickColorChanged) {
140
+ idx = index * AttributesItemSize.aPickColorAndEnabled;
141
+ if (pickColorChanged) {
142
+ sprite._pickColor.copy(sprite.pickColor);
143
+ attributesData.aPickColorAndEnabled[idx] = sprite._pickColor.r;
144
+ attributesData.aPickColorAndEnabled[idx + 1] = sprite._pickColor.g;
145
+ attributesData.aPickColorAndEnabled[idx + 2] = sprite._pickColor.b;
146
+ }
147
+ if (enablePickColorChanged) {
148
+ sprite._enablePickColor = sprite.enablePickColor;
149
+ attributesData.aPickColorAndEnabled[idx + 3] = sprite._enablePickColor ? 1 : 0;
150
+ }
151
+ mesh.geometry.attributes.aPickColorAndEnabled.needsUpdate = true;
152
+ }
153
+ };
154
+ /**
155
+ * 添加一个InstancedSprite实例的数据到InstancedBufferAttribute中对应的位置
156
+ * @param {InstancedSpriteMesh} mesh
157
+ * @param {InstancedSprite} sprite
158
+ * @param {number} index
159
+ * @ignore
160
+ */
161
+ function addInstancedSprite(mesh, sprite, index) {
162
+ const attributesData = mesh._attributesData;
163
+ sprite._instanceIndex = index;
164
+ let idx = 0;
165
+
166
+ idx = index * AttributesItemSize.aPositionAndShow;
167
+ sprite._position.copy(sprite.position);
168
+ attributesData.aPositionAndShow[idx] = sprite._position.x;
169
+ attributesData.aPositionAndShow[idx + 1] = sprite._position.y;
170
+ attributesData.aPositionAndShow[idx + 2] = sprite._position.z;
171
+ sprite._show = sprite.show;
172
+ attributesData.aPositionAndShow[idx + 3] = sprite._show ? 1 : 0;
173
+ mesh.geometry.attributes.aPositionAndShow.needsUpdate = true;
174
+
175
+ idx = index * AttributesItemSize.aCenterAndSize;
176
+ sprite._center.copy(sprite.center);
177
+ attributesData.aCenterAndSize[idx] = sprite._center.x;
178
+ attributesData.aCenterAndSize[idx + 1] = sprite._center.y;
179
+ sprite._imageSize.copy(sprite.imageSize);
180
+ attributesData.aCenterAndSize[idx + 2] = sprite._imageSize.x;
181
+ attributesData.aCenterAndSize[idx + 3] = sprite._imageSize.y;
182
+ mesh.geometry.attributes.aCenterAndSize.needsUpdate = true;
183
+
184
+ idx = index * AttributesItemSize.aScaleAndRotationAndSizeAttenuation;
185
+ sprite._scale = sprite.scale;
186
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx] = sprite._scale;
187
+ sprite._rotation = sprite.rotation;
188
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx + 1] = sprite._rotation;
189
+ sprite._sizeAttenuation = sprite.sizeAttenuation;
190
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx + 2] = sprite._sizeAttenuation ? 1 : 0;
191
+ mesh.geometry.attributes.aScaleAndRotationAndSizeAttenuation.needsUpdate = true;
192
+
193
+ idx = index * AttributesItemSize.aColorAndOpacity;
194
+ sprite._color.copy(sprite.color);
195
+ attributesData.aColorAndOpacity[idx] = sprite._color.r;
196
+ attributesData.aColorAndOpacity[idx + 1] = sprite._color.g;
197
+ attributesData.aColorAndOpacity[idx + 2] = sprite._color.b;
198
+ sprite._opacity = sprite.opacity;
199
+ attributesData.aColorAndOpacity[idx + 3] = sprite._opacity;
200
+ mesh.geometry.attributes.aColorAndOpacity.needsUpdate = true;
201
+ };
202
+
203
+ /**
204
+ * InstancedSpriteMesh类,基于InstancedBufferGeometry实现的高性能InstancedSprite渲染组件
205
+ *
206
+ * @extends {Mesh}
207
+ */
208
+ class InstancedSpriteMesh extends Mesh {
209
+ /**
210
+ * @param {InstancedSpriteCollection} collection - 所属的InstancedSpriteCollection实例,必填
211
+ * @param {Texture} texture - 材质图像纹理,必填
212
+ * @constructor
213
+ */
214
+ constructor(collection, texture) {
215
+ Check.defined('collection', collection);
216
+ Check.defined('texture', texture);
217
+ super(geometry.clone(), collection.useNodeMaterial ? new InstancedSpriteNodeMaterial() : new InstancedSpriteMaterial());
218
+ this._collection = collection; //所属的InstancedSpriteCollection实例
219
+ this.material.texture = texture;
220
+ /**
221
+ * 对象类型标识
222
+ * @type {string}
223
+ * @readonly
224
+ */
225
+ this.type = "InstancedSpriteMesh";
226
+ /**
227
+ * @type {Array<InstancedSprite>}
228
+ * @ignore
229
+ */
230
+ this._instancedSprites = [];
231
+ /**
232
+ * @type {Array<InstancedSprite>}
233
+ * @ignore
234
+ */
235
+ this._addInstancedSprites = [];
236
+ /**
237
+ * @type {Array<InstancedSprite>}
238
+ * @ignore
239
+ */
240
+ this._removeInstancedSprites = [];
241
+ /**
242
+ * InstancedBufferAttribute数据存储对象,key为属性名称,value为对应的Float32Array数组
243
+ * @type {Object<string, Float32Array>}
244
+ * @ignore
245
+ */
246
+ this._attributesData = {};
247
+ /**
248
+ * 当前能容纳的最大InstancedSprite数量,初始为0,InstancedSpriteMesh会根据需要动态扩容
249
+ * @type {number}
250
+ * @ignore
251
+ */
252
+ this._maxCapacity = 0;
253
+ this._enaureCapacity(64);
254
+ }
255
+ /**
256
+ * InstancedSpriteMesh对象标识
257
+ * @type {boolean}
258
+ * @readonly
259
+ */
260
+ get isInstancedSpriteMesh() { return true; }
261
+ /**
262
+ * 深度测试开关,默认为true,开启后会进行深度测试以正确处理遮挡关系,但可能会有性能影响;如果关闭则所有InstancedSprite都会被渲染在最前面,适合需要始终显示的UI元素等场景
263
+ * @type {boolean}
264
+ * @default true
265
+ */
266
+ get depthTest() { return this.material.depthTest; }
267
+ set depthTest(value) {
268
+ Check.typeOf.boolean('depthTest', value);
269
+ this.material.depthTest = value;
270
+ }
271
+ /**
272
+ * InstancedSprite对象数组
273
+ * @type {Array<InstancedSprite>}
274
+ * @readonly
275
+ */
276
+ get instancedSprites() { return this._instancedSprites; }
277
+ /**
278
+ * 确保InstancedBufferAttribute的大小能够容纳指定数量的InstancedSprite实例,如果当前最大容量不足,则进行扩容
279
+ * @param {number} required - 需要容纳的InstancedSprite数量
280
+ * @private
281
+ */
282
+ _enaureCapacity(required) {
283
+ if (required < this._maxCapacity) return;
284
+
285
+ const newCapacity = Math.max(required, this._maxCapacity + 64);
286
+
287
+ for (const [name, itemSize] of Object.entries(AttributesItemSize)) {
288
+ const newArray = new Float32Array(newCapacity * itemSize);
289
+ const oldArray = this._attributesData[name];
290
+ if (oldArray) {
291
+ const copyLen = Math.min(oldArray.length, newArray.length);
292
+ newArray.set(oldArray.subarray(0, copyLen));
293
+ }
294
+ this._attributesData[name] = newArray;
295
+ }
296
+
297
+ // 创建 InstancedBufferAttribute 对象
298
+ for (const [name, itemSize] of Object.entries(AttributesItemSize)) {
299
+ const attr = new InstancedBufferAttribute(this._attributesData[name], itemSize);
300
+ this.geometry.setAttribute(name, attr);
301
+ }
302
+
303
+ this.geometry.instanceCount = required;
304
+ this._maxCapacity = newCapacity;
305
+ }
306
+ /**
307
+ * 添加一个InstancedSprite
308
+ * @param {InstancedSprite} sprite - 要添加的InstancedSprite实例
309
+ * @returns {InstancedSpriteMesh}
310
+ */
311
+ add(sprite) {
312
+ sprite._mesh = this;
313
+ this._addInstancedSprites.push(sprite);
314
+ return this;
315
+ }
316
+ /**
317
+ * 移除一个InstancedSprite
318
+ * @param {InstancedSprite} sprite - 要移除的InstancedSprite实例
319
+ * @returns {InstancedSpriteMesh}
320
+ */
321
+ remove(sprite) {
322
+ if (this._instancedSprites.includes(sprite) && !this._removeInstancedSprites.includes(sprite))
323
+ this._removeInstancedSprites.push(sprite);
324
+ else
325
+ console.warn('InstancedSprite not found in InstancedSpriteMesh or already marked for removal');
326
+ return this;
327
+ }
328
+ /**
329
+ * 移除所有InstancedSprite
330
+ * @returns {InstancedSpriteMesh}
331
+ */
332
+ clear() {
333
+ this._addInstancedSprites.length = 0;
334
+ this._removeInstancedSprites.length = 0;
335
+ this._instancedSprites.length = 0;
336
+ this.geometry.instanceCount = 0;
337
+ return this;
338
+ }
339
+ /**
340
+ * Computes intersection points between a casted ray and this sprite.
341
+ *
342
+ * @param {Raycaster} raycaster - The raycaster.
343
+ * @param {Array<Object>} intersects - The target array that holds the intersection points.
344
+ */
345
+ raycast(raycaster, intersects) {
346
+
347
+ if (raycaster.camera === null) {
348
+
349
+ console.error('InstancedSpriteMesh: "Raycaster.camera" needs to be set in order to raycast against sprites.');
350
+ return;
351
+
352
+ }
353
+
354
+ const sprites = this._instancedSprites;
355
+ for (const sprite of sprites) {
356
+ sprite.raycast(raycaster, intersects, raycaster.camera.matrixWorldInverse);
357
+ }
358
+ }
359
+ /**
360
+ * 每帧更新
361
+ * @private
362
+ */
363
+ update() {
364
+ if (!this.visible) return;
365
+
366
+ const removeInstancedSprites = this._removeInstancedSprites;
367
+ const addInstancedSprites = this._addInstancedSprites;
368
+ const instancedSprites = this._instancedSprites;
369
+
370
+ // 计算最终需要的容量
371
+ const requiredCapacity = instancedSprites.length + addInstancedSprites.length - removeInstancedSprites.length;
372
+ this._enaureCapacity(requiredCapacity);
373
+
374
+ if (removeInstancedSprites.length > 0) {
375
+ // 如果有需要移除的InstancedSprite,先从instancedSprites数组中移除
376
+ for (const sprite of removeInstancedSprites) {
377
+ const index = instancedSprites.indexOf(sprite);
378
+ if (index !== -1) {
379
+ instancedSprites.splice(index, 1); //从instancedSprites数组中移除
380
+ sprite._mesh = undefined;
381
+ sprite._instanceIndex = -1;
382
+ }
383
+ // else
384
+ // console.warn('InstancedSprite not found in InstancedSpriteMesh during removal');
385
+ }
386
+ removeInstancedSprites.length = 0;
387
+
388
+ // 如果有需要添加的InstancedSprite,则加入instancedSprites数组中
389
+ if (addInstancedSprites.length > 0) {
390
+ instancedSprites.push(...addInstancedSprites);
391
+ addInstancedSprites.length = 0;
392
+ }
393
+
394
+ // 重新写入所有InstancedSprite的属性数据到InstancedBufferAttribute中
395
+ const length = instancedSprites.length;
396
+ for (let i = 0; i < length; i++) {
397
+ const sprite = instancedSprites[i];
398
+ addInstancedSprite(this, sprite, i);
399
+ }
400
+ } else {
401
+ // 检查并更新属性值有变化的InstancedSprite
402
+ for (const sprite of instancedSprites) {
403
+ checkAndUpdateInstancedSprite(this, sprite);
404
+ }
405
+
406
+ // 添加InstancedSprite
407
+ while (addInstancedSprites.length > 0) {
408
+ const sprite = addInstancedSprites.shift();
409
+ addInstancedSprite(this, sprite, instancedSprites.length);
410
+ instancedSprites.push(sprite);
411
+ }
412
+ }
413
+
414
+ this.geometry.instanceCount = instancedSprites.length; //更新instanceCount
415
+ }
416
+ dispose() {
417
+ this.clear();
418
+ this.geometry.dispose();
419
+ this.material.dispose();
420
+ }
421
+ };
422
+
423
+ export default InstancedSpriteMesh;
424
+ export { InstancedSpriteMesh };
425
+