@lijuhong1981/three.instancedsprite 1.0.1 → 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.
@@ -1,425 +1,510 @@
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
-
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
+ aUvRect: 4,
53
+ });
54
+ /**
55
+ * 将指定实例的属性片段标记为需要更新,仅上传该实例对应的数据范围(增量上传)
56
+ * @param {InstancedSpriteMesh} mesh
57
+ * @param {string} name - attribute名称
58
+ * @param {number} index - 实例索引
59
+ * @param {number} itemSize - 单个实例占用的float数量
60
+ * @ignore
61
+ */
62
+ function markUpdateRange(mesh, name, index, itemSize) {
63
+ const attribute = mesh.geometry.attributes[name];
64
+ attribute.addUpdateRange(index * itemSize, itemSize);
65
+ attribute.needsUpdate = true;
66
+ }
67
+ /**
68
+ * 将指定实例属性标记为全量更新(整块重传)
69
+ * @param {InstancedSpriteMesh} mesh
70
+ * @param {string} name - attribute名称
71
+ * @ignore
72
+ */
73
+ function markFullUpdate(mesh, name) {
74
+ const attribute = mesh.geometry.attributes[name];
75
+ attribute.clearUpdateRanges();
76
+ attribute.needsUpdate = true;
77
+ }
78
+ /**
79
+ * 检查并更新一个InstancedSprite实例的数据到对应的InstancedBufferAttribute位置
80
+ * @param {InstancedSpriteMesh} mesh
81
+ * @param {InstancedSprite} sprite
82
+ * @ignore
83
+ */
84
+ function checkAndUpdateInstancedSprite(mesh, sprite) {
85
+ const attributesData = mesh._attributesData;
86
+ const index = sprite._instanceIndex;
87
+
88
+ const positionChanged = (sprite._position.equals(sprite.position) === false);
89
+ const showChanged = (sprite._show !== sprite.show);
90
+ const centerChanged = (sprite._center.equals(sprite.center) === false);
91
+ const imageSizeChanged = (sprite._imageSize.equals(sprite.imageSize) === false);
92
+ const scaleChanged = (sprite._scale !== sprite.scale);
93
+ const rotationChanged = (sprite._rotation !== sprite.rotation);
94
+ const sizeAttenuationChanged = (sprite._sizeAttenuation !== sprite.sizeAttenuation);
95
+ const colorChanged = (sprite._color.equals(sprite.color) === false);
96
+ const opacityChanged = (sprite._opacity !== sprite.opacity);
97
+ const pickColorChanged = (sprite._pickColor.equals(sprite.pickColor) === false);
98
+ const enablePickColorChanged = (sprite._enablePickColor !== sprite.enablePickColor);
99
+ const uvRectChanged = (sprite._uvRect.equals(sprite.uvRect) === false);
100
+
101
+ let idx = 0;
102
+
103
+ if (positionChanged || showChanged) {
104
+ idx = index * AttributesItemSize.aPositionAndShow;
105
+ if (positionChanged) {
106
+ sprite._position.copy(sprite.position);
107
+ attributesData.aPositionAndShow[idx] = sprite._position.x;
108
+ attributesData.aPositionAndShow[idx + 1] = sprite._position.y;
109
+ attributesData.aPositionAndShow[idx + 2] = sprite._position.z;
110
+ }
111
+ if (showChanged) {
112
+ sprite._show = sprite.show;
113
+ attributesData.aPositionAndShow[idx + 3] = sprite._show ? 1 : 0;
114
+ }
115
+ markUpdateRange(mesh, 'aPositionAndShow', index, AttributesItemSize.aPositionAndShow);
116
+ }
117
+
118
+ if (centerChanged || imageSizeChanged) {
119
+ idx = index * AttributesItemSize.aCenterAndSize;
120
+ if (centerChanged) {
121
+ sprite._center.copy(sprite.center);
122
+ attributesData.aCenterAndSize[idx] = sprite._center.x;
123
+ attributesData.aCenterAndSize[idx + 1] = sprite._center.y;
124
+ }
125
+ if (imageSizeChanged) {
126
+ sprite._imageSize.copy(sprite.imageSize);
127
+ attributesData.aCenterAndSize[idx + 2] = sprite._imageSize.x;
128
+ attributesData.aCenterAndSize[idx + 3] = sprite._imageSize.y;
129
+ }
130
+ markUpdateRange(mesh, 'aCenterAndSize', index, AttributesItemSize.aCenterAndSize);
131
+ }
132
+
133
+ if (scaleChanged || rotationChanged || sizeAttenuationChanged) {
134
+ idx = index * AttributesItemSize.aScaleAndRotationAndSizeAttenuation;
135
+ if (scaleChanged) {
136
+ sprite._scale = sprite.scale;
137
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx] = sprite._scale;
138
+ }
139
+ if (rotationChanged) {
140
+ sprite._rotation = sprite.rotation;
141
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx + 1] = sprite._rotation;
142
+ }
143
+ if (sizeAttenuationChanged) {
144
+ sprite._sizeAttenuation = sprite.sizeAttenuation;
145
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx + 2] = sprite._sizeAttenuation ? 1 : 0;
146
+ }
147
+ markUpdateRange(mesh, 'aScaleAndRotationAndSizeAttenuation', index, AttributesItemSize.aScaleAndRotationAndSizeAttenuation);
148
+ }
149
+
150
+ if (colorChanged || opacityChanged) {
151
+ idx = index * AttributesItemSize.aColorAndOpacity;
152
+ if (colorChanged) {
153
+ sprite._color.copy(sprite.color);
154
+ attributesData.aColorAndOpacity[idx] = sprite._color.r;
155
+ attributesData.aColorAndOpacity[idx + 1] = sprite._color.g;
156
+ attributesData.aColorAndOpacity[idx + 2] = sprite._color.b;
157
+ }
158
+ if (opacityChanged) {
159
+ sprite._opacity = sprite.opacity;
160
+ attributesData.aColorAndOpacity[idx + 3] = sprite._opacity;
161
+ }
162
+ markUpdateRange(mesh, 'aColorAndOpacity', index, AttributesItemSize.aColorAndOpacity);
163
+ }
164
+
165
+ if (pickColorChanged || enablePickColorChanged) {
166
+ idx = index * AttributesItemSize.aPickColorAndEnabled;
167
+ if (pickColorChanged) {
168
+ sprite._pickColor.copy(sprite.pickColor);
169
+ attributesData.aPickColorAndEnabled[idx] = sprite._pickColor.r;
170
+ attributesData.aPickColorAndEnabled[idx + 1] = sprite._pickColor.g;
171
+ attributesData.aPickColorAndEnabled[idx + 2] = sprite._pickColor.b;
172
+ }
173
+ if (enablePickColorChanged) {
174
+ sprite._enablePickColor = sprite.enablePickColor;
175
+ attributesData.aPickColorAndEnabled[idx + 3] = sprite._enablePickColor ? 1 : 0;
176
+ }
177
+ markUpdateRange(mesh, 'aPickColorAndEnabled', index, AttributesItemSize.aPickColorAndEnabled);
178
+ }
179
+
180
+ if (uvRectChanged) {
181
+ idx = index * AttributesItemSize.aUvRect;
182
+ sprite._uvRect.copy(sprite.uvRect);
183
+ attributesData.aUvRect[idx] = sprite._uvRect.x;
184
+ attributesData.aUvRect[idx + 1] = sprite._uvRect.y;
185
+ attributesData.aUvRect[idx + 2] = sprite._uvRect.z;
186
+ attributesData.aUvRect[idx + 3] = sprite._uvRect.w;
187
+ markUpdateRange(mesh, 'aUvRect', index, AttributesItemSize.aUvRect);
188
+ }
189
+ };
190
+ /**
191
+ * 添加一个InstancedSprite实例的数据到InstancedBufferAttribute中对应的位置
192
+ * @param {InstancedSpriteMesh} mesh
193
+ * @param {InstancedSprite} sprite
194
+ * @param {number} index
195
+ * @ignore
196
+ */
197
+ function addInstancedSprite(mesh, sprite, index) {
198
+ const attributesData = mesh._attributesData;
199
+ sprite._instanceIndex = index;
200
+ let idx = 0;
201
+
202
+ idx = index * AttributesItemSize.aPositionAndShow;
203
+ sprite._position.copy(sprite.position);
204
+ attributesData.aPositionAndShow[idx] = sprite._position.x;
205
+ attributesData.aPositionAndShow[idx + 1] = sprite._position.y;
206
+ attributesData.aPositionAndShow[idx + 2] = sprite._position.z;
207
+ sprite._show = sprite.show;
208
+ attributesData.aPositionAndShow[idx + 3] = sprite._show ? 1 : 0;
209
+ markFullUpdate(mesh, 'aPositionAndShow');
210
+
211
+ idx = index * AttributesItemSize.aCenterAndSize;
212
+ sprite._center.copy(sprite.center);
213
+ attributesData.aCenterAndSize[idx] = sprite._center.x;
214
+ attributesData.aCenterAndSize[idx + 1] = sprite._center.y;
215
+ sprite._imageSize.copy(sprite.imageSize);
216
+ attributesData.aCenterAndSize[idx + 2] = sprite._imageSize.x;
217
+ attributesData.aCenterAndSize[idx + 3] = sprite._imageSize.y;
218
+ markFullUpdate(mesh, 'aCenterAndSize');
219
+
220
+ idx = index * AttributesItemSize.aScaleAndRotationAndSizeAttenuation;
221
+ sprite._scale = sprite.scale;
222
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx] = sprite._scale;
223
+ sprite._rotation = sprite.rotation;
224
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx + 1] = sprite._rotation;
225
+ sprite._sizeAttenuation = sprite.sizeAttenuation;
226
+ attributesData.aScaleAndRotationAndSizeAttenuation[idx + 2] = sprite._sizeAttenuation ? 1 : 0;
227
+ markFullUpdate(mesh, 'aScaleAndRotationAndSizeAttenuation');
228
+
229
+ idx = index * AttributesItemSize.aColorAndOpacity;
230
+ sprite._color.copy(sprite.color);
231
+ attributesData.aColorAndOpacity[idx] = sprite._color.r;
232
+ attributesData.aColorAndOpacity[idx + 1] = sprite._color.g;
233
+ attributesData.aColorAndOpacity[idx + 2] = sprite._color.b;
234
+ sprite._opacity = sprite.opacity;
235
+ attributesData.aColorAndOpacity[idx + 3] = sprite._opacity;
236
+ markFullUpdate(mesh, 'aColorAndOpacity');
237
+
238
+ idx = index * AttributesItemSize.aUvRect;
239
+ sprite._uvRect.copy(sprite.uvRect);
240
+ attributesData.aUvRect[idx] = sprite._uvRect.x;
241
+ attributesData.aUvRect[idx + 1] = sprite._uvRect.y;
242
+ attributesData.aUvRect[idx + 2] = sprite._uvRect.z;
243
+ attributesData.aUvRect[idx + 3] = sprite._uvRect.w;
244
+ markFullUpdate(mesh, 'aUvRect');
245
+ };
246
+
247
+ /**
248
+ * InstancedSpriteMesh类,基于InstancedBufferGeometry实现的高性能InstancedSprite渲染组件
249
+ *
250
+ * 一张图集纹理对应一个 InstancedSpriteMesh,所有使用该图集的 Sprite 由其统一绘制
251
+ *
252
+ * @extends {Mesh}
253
+ */
254
+ class InstancedSpriteMesh extends Mesh {
255
+ /**
256
+ * @param {InstancedSpriteCollection} collection - 所属的InstancedSpriteCollection实例,必填
257
+ * @param {Texture} texture - 图集纹理,必填
258
+ * @constructor
259
+ */
260
+ constructor(collection, texture) {
261
+ Check.defined('collection', collection);
262
+ Check.defined('texture', texture);
263
+ super(geometry.clone(), collection.useNodeMaterial ? new InstancedSpriteNodeMaterial() : new InstancedSpriteMaterial());
264
+ this._collection = collection; //所属的InstancedSpriteCollection实例
265
+ this.material.texture = texture;
266
+ /**
267
+ * 对象类型标识
268
+ * @type {string}
269
+ * @readonly
270
+ */
271
+ this.type = "InstancedSpriteMesh";
272
+ /**
273
+ * 实例化几何体的包围球基于单位四边形(位于原点),不包含实例位置,视锥剔除会把远离原点的实例整批误剔除,因此禁用
274
+ * @type {boolean}
275
+ * @default false
276
+ */
277
+ this.frustumCulled = false;
278
+ /**
279
+ * @type {Array<InstancedSprite>}
280
+ * @ignore
281
+ */
282
+ this._instancedSprites = [];
283
+ /**
284
+ * @type {Array<InstancedSprite>}
285
+ * @ignore
286
+ */
287
+ this._addInstancedSprites = [];
288
+ /**
289
+ * @type {Array<InstancedSprite>}
290
+ * @ignore
291
+ */
292
+ this._removeInstancedSprites = [];
293
+ /**
294
+ * InstancedBufferAttribute数据存储对象,key为属性名称,value为对应的Float32Array数组
295
+ * @type {Object<string, Float32Array>}
296
+ * @ignore
297
+ */
298
+ this._attributesData = {};
299
+ /**
300
+ * 当前能容纳的最大InstancedSprite数量,初始为0,InstancedSpriteMesh会根据需要动态扩容
301
+ * @type {number}
302
+ * @ignore
303
+ */
304
+ this._maxCapacity = 0;
305
+ this._ensureCapacity(1024);
306
+ // 初始无实例,绘制数量为0(_ensureCapacity 只负责扩容,不改变实例数量)
307
+ this.geometry.instanceCount = 0;
308
+ }
309
+ /**
310
+ * InstancedSpriteMesh对象标识
311
+ * @type {boolean}
312
+ * @readonly
313
+ */
314
+ get isInstancedSpriteMesh() { return true; }
315
+ /**
316
+ * 深度测试开关,默认为true,开启后会进行深度测试以正确处理遮挡关系,但可能会有性能影响;如果关闭则所有InstancedSprite都会被渲染在最前面,适合需要始终显示的UI元素等场景
317
+ * @type {boolean}
318
+ * @default true
319
+ */
320
+ get depthTest() { return this.material.depthTest; }
321
+ set depthTest(value) {
322
+ Check.typeOf.boolean('depthTest', value);
323
+ this.material.depthTest = value;
324
+ }
325
+ /**
326
+ * 深度写入开关,默认为true;多个半透明 Sprite 重叠时,开启深度写入可能导致排序瑕疵(后方 Sprite 被错误遮挡),关闭可缓解,适合半透明标签/粒子等场景
327
+ * @type {boolean}
328
+ * @default true
329
+ */
330
+ get depthWrite() { return this.material.depthWrite; }
331
+ set depthWrite(value) {
332
+ Check.typeOf.boolean('depthWrite', value);
333
+ this.material.depthWrite = value;
334
+ }
335
+ /**
336
+ * InstancedSprite对象数组
337
+ * @type {Array<InstancedSprite>}
338
+ * @readonly
339
+ */
340
+ get instancedSprites() { return this._instancedSprites; }
341
+ /**
342
+ * 确保InstancedBufferAttribute的大小能够容纳指定数量的InstancedSprite实例,如果当前最大容量不足,则进行扩容
343
+ * @param {number} required - 需要容纳的InstancedSprite数量
344
+ * @private
345
+ */
346
+ _ensureCapacity(required) {
347
+ if (required <= this._maxCapacity) return;
348
+
349
+ // 线性扩容(+1024)保证内存浪费有界;Three.js 不支持原地扩容 buffer,需新建数组与 attribute
350
+ const newCapacity = Math.max(required, this._maxCapacity + 1024);
351
+
352
+ for (const [name, itemSize] of Object.entries(AttributesItemSize)) {
353
+ const oldArray = this._attributesData[name];
354
+ const newArray = new Float32Array(newCapacity * itemSize);
355
+ if (oldArray) {
356
+ newArray.set(oldArray.subarray(0, Math.min(oldArray.length, newArray.length)));
357
+ }
358
+ this._attributesData[name] = newArray;
359
+
360
+ const attribute = new InstancedBufferAttribute(newArray, itemSize);
361
+ this.geometry.setAttribute(name, attribute);
362
+ }
363
+
364
+ this._maxCapacity = newCapacity;
365
+ }
366
+ /**
367
+ * 预分配容量,避免后续动态扩容(不改变当前实际绘制的实例数量)
368
+ * @param {number} capacity - 需要预留的实例数量
369
+ * @returns {InstancedSpriteMesh}
370
+ */
371
+ reserve(capacity) {
372
+ Check.typeOf.number('capacity', capacity);
373
+ this._ensureCapacity(capacity);
374
+ return this;
375
+ }
376
+ /**
377
+ * 添加一个InstancedSprite
378
+ * @param {InstancedSprite} sprite - 要添加的InstancedSprite实例
379
+ * @returns {InstancedSpriteMesh}
380
+ */
381
+ add(sprite) {
382
+ sprite._mesh = this;
383
+ this._addInstancedSprites.push(sprite);
384
+ return this;
385
+ }
386
+ /**
387
+ * 移除一个InstancedSprite
388
+ * @param {InstancedSprite} sprite - 要移除的InstancedSprite实例
389
+ * @returns {InstancedSpriteMesh}
390
+ */
391
+ remove(sprite) {
392
+ if (this._instancedSprites.includes(sprite) && !this._removeInstancedSprites.includes(sprite))
393
+ this._removeInstancedSprites.push(sprite);
394
+ else
395
+ console.warn('InstancedSprite not found in InstancedSpriteMesh or already marked for removal');
396
+ return this;
397
+ }
398
+ /**
399
+ * 移除所有InstancedSprite
400
+ * @returns {InstancedSpriteMesh}
401
+ */
402
+ clear() {
403
+ this._addInstancedSprites.length = 0;
404
+ this._removeInstancedSprites.length = 0;
405
+ this._instancedSprites.length = 0;
406
+ this.geometry.instanceCount = 0;
407
+ return this;
408
+ }
409
+ /**
410
+ * Computes intersection points between a casted ray and this sprite.
411
+ *
412
+ * @param {Raycaster} raycaster - The raycaster.
413
+ * @param {Array<Object>} intersects - The target array that holds the intersection points.
414
+ */
415
+ raycast(raycaster, intersects) {
416
+
417
+ if (raycaster.camera === null) {
418
+
419
+ console.error('InstancedSpriteMesh: "Raycaster.camera" needs to be set in order to raycast against sprites.');
420
+ return;
421
+
422
+ }
423
+
424
+ const sprites = this._instancedSprites;
425
+ for (const sprite of sprites) {
426
+ sprite.raycast(raycaster, intersects, raycaster.camera.matrixWorldInverse);
427
+ }
428
+ }
429
+ /**
430
+ * 每帧更新
431
+ * @private
432
+ */
433
+ update() {
434
+ if (!this.visible) return;
435
+
436
+ const removeInstancedSprites = this._removeInstancedSprites;
437
+ const addInstancedSprites = this._addInstancedSprites;
438
+ const instancedSprites = this._instancedSprites;
439
+
440
+ // 计算最终需要的容量
441
+ const requiredCapacity = instancedSprites.length + addInstancedSprites.length - removeInstancedSprites.length;
442
+ this._ensureCapacity(requiredCapacity);
443
+
444
+ if (removeInstancedSprites.length > 0) {
445
+ // 末位换入(swap-and-pop)移除:把最后一个 sprite 换到被移除的位置,O(k) 而非 O(n) 全量重写
446
+ const movedSprites = [];
447
+ for (const sprite of removeInstancedSprites) {
448
+ const index = sprite._instanceIndex;
449
+ if (index === -1)
450
+ continue; // 已被处理过(例如换入后又在本帧被移除)
451
+
452
+ const last = instancedSprites[instancedSprites.length - 1];
453
+ instancedSprites.pop();
454
+ if (last !== sprite) {
455
+ instancedSprites[index] = last;
456
+ last._instanceIndex = index; // 立即更新,避免后续删除用到陈旧索引
457
+ movedSprites.push(last);
458
+ }
459
+ sprite._instanceIndex = -1;
460
+ sprite._mesh = undefined;
461
+ }
462
+ removeInstancedSprites.length = 0;
463
+
464
+ // 重写被移动 sprite 的数据到新位置
465
+ for (const sprite of movedSprites) {
466
+ if (sprite._instanceIndex !== -1) {
467
+ addInstancedSprite(this, sprite, sprite._instanceIndex);
468
+ }
469
+ }
470
+
471
+ // 添加新 sprite
472
+ while (addInstancedSprites.length > 0) {
473
+ const sprite = addInstancedSprites.shift();
474
+ addInstancedSprite(this, sprite, instancedSprites.length);
475
+ instancedSprites.push(sprite);
476
+ }
477
+ } else {
478
+ // 检查并更新属性值有变化的InstancedSprite
479
+ for (const sprite of instancedSprites) {
480
+ checkAndUpdateInstancedSprite(this, sprite);
481
+ }
482
+
483
+ // 添加InstancedSprite
484
+ while (addInstancedSprites.length > 0) {
485
+ const sprite = addInstancedSprites.shift();
486
+ addInstancedSprite(this, sprite, instancedSprites.length);
487
+ instancedSprites.push(sprite);
488
+ }
489
+ }
490
+
491
+ this.geometry.instanceCount = instancedSprites.length; //更新instanceCount
492
+ }
493
+ /**
494
+ * 释放 GPU 资源
495
+ */
496
+ dispose() {
497
+ this.clear();
498
+ this.geometry.dispose();
499
+ this.material.dispose();
500
+ }
501
+ };
502
+
503
+ export default InstancedSpriteMesh;
504
+ export { InstancedSpriteMesh };
505
+ /**
506
+ * InstancedSpriteMesh 的别名。
507
+ * @class
508
+ * @name BillboardMesh
509
+ */
510
+ export const BillboardMesh = InstancedSpriteMesh;