@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.
- package/API.md +213 -10
- package/README.md +146 -55
- package/index.js +6 -5
- package/package.json +11 -4
- package/src/InstancedSprite.js +14 -1
- package/src/InstancedSpriteCollection.js +205 -57
- package/src/InstancedSpriteMaterial.js +154 -147
- package/src/InstancedSpriteMesh.js +510 -425
- package/src/InstancedSpriteNodeMaterial.js +12 -5
- package/src/TextureAtlas.js +125 -0
- package/.claude/settings.local.json +0 -7
|
@@ -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
|
-
|
|
55
|
-
*
|
|
56
|
-
* @param {
|
|
57
|
-
* @
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
sprite.
|
|
128
|
-
attributesData.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
attributesData.
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
*
|
|
257
|
-
* @
|
|
258
|
-
* @
|
|
259
|
-
*/
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
this._maxCapacity =
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
*
|
|
318
|
-
* @
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
*
|
|
343
|
-
* @param {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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;
|