s3m-three 0.3.5 → 0.3.7
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/dist/browser/assets/s3mWorker-D7AV6O30.js +1 -0
- package/dist/browser/s3m-three.iife.ext-three.js +26 -20
- package/dist/browser/s3m-three.iife.js +26 -20
- package/dist/core/TextureDecode.d.ts +39 -0
- package/dist/core/TextureDecode.d.ts.map +1 -0
- package/dist/core/TextureDecode.js +174 -0
- package/dist/core/TextureDecode.js.map +1 -0
- package/dist/core/TextureFormat.d.ts +22 -2
- package/dist/core/TextureFormat.d.ts.map +1 -1
- package/dist/core/TextureFormat.js +22 -2
- package/dist/core/TextureFormat.js.map +1 -1
- package/dist/core/TileGeometry.d.ts +92 -0
- package/dist/core/TileGeometry.d.ts.map +1 -0
- package/dist/core/TileGeometry.js +607 -0
- package/dist/core/TileGeometry.js.map +1 -0
- package/dist/render/DatasetManager.d.ts +2 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +1 -0
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/FeatureStyleManager.d.ts +4 -2
- package/dist/render/FeatureStyleManager.d.ts.map +1 -1
- package/dist/render/FeatureStyleManager.js +33 -1
- package/dist/render/FeatureStyleManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +8 -10
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +77 -555
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +4 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +13 -2
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +6 -0
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +9 -19
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +62 -115
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +21 -0
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +41 -2
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/worker/S3MWorkerClient.d.ts +48 -0
- package/dist/worker/S3MWorkerClient.d.ts.map +1 -0
- package/dist/worker/S3MWorkerClient.js +124 -0
- package/dist/worker/S3MWorkerClient.js.map +1 -0
- package/dist/worker/s3mWorker.d.ts +2 -0
- package/dist/worker/s3mWorker.d.ts.map +1 -0
- package/dist/worker/s3mWorker.js +56 -0
- package/dist/worker/s3mWorker.js.map +1 -0
- package/dist/worker/transferables.d.ts +14 -0
- package/dist/worker/transferables.d.ts.map +1 -0
- package/dist/worker/transferables.js +61 -0
- package/dist/worker/transferables.js.map +1 -0
- package/docs/API.md +6 -0
- package/package.json +1 -1
- package/src/core/TextureDecode.ts +224 -0
- package/src/core/TextureFormat.ts +23 -2
- package/src/core/TileGeometry.ts +745 -0
- package/src/render/DatasetManager.ts +3 -0
- package/src/render/FeatureStyleManager.ts +31 -2
- package/src/render/GeometryBuilder.ts +91 -617
- package/src/render/S3MViewer.ts +18 -4
- package/src/render/StyleMaterial.ts +6 -0
- package/src/render/TextureFactory.ts +78 -123
- package/src/tileset/S3MTileset.ts +55 -3
- package/src/worker/S3MWorkerClient.ts +159 -0
- package/src/worker/s3mWorker.ts +76 -0
- package/src/worker/transferables.ts +58 -0
|
@@ -15,28 +15,16 @@ import * as THREE from 'three';
|
|
|
15
15
|
import { createTexture } from './TextureFactory.js';
|
|
16
16
|
import { createSharedUniforms, createStyledMaterial } from './StyleMaterial.js';
|
|
17
17
|
import type { StyledMaterial } from './StyleMaterial.js';
|
|
18
|
+
import { assembleTileGeometry, LINE_PRIMITIVE, POINT_PRIMITIVE } from '../core/TileGeometry.js';
|
|
19
|
+
import type { TileGeometryData, PrimitiveData } from '../core/TileGeometry.js';
|
|
18
20
|
import type {
|
|
19
|
-
AttrDType,
|
|
20
21
|
GeoPackageEntry,
|
|
21
|
-
IndexPackage,
|
|
22
22
|
ParsedTile,
|
|
23
23
|
StyleUniforms,
|
|
24
|
-
VertexAttribute,
|
|
25
24
|
} from '../core/types.js';
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* S3M 规范里 `DrawPrimitiveType` 只到 10(多边形),但 iServer 实际发布的 v3 瓦片里
|
|
31
|
-
* 会出现 **13**:它是数据自带的「棱线包」—— 索引是线段对,不是三角形。
|
|
32
|
-
*
|
|
33
|
-
* 这条是踩过坑才认出来的:把 13 当三角形画,每条棱线都会被连成一根几十上百米的尖刺,
|
|
34
|
-
* 满屏都是放射状的三角面。按 LineSegments 画出来,正好是整片建筑轮廓线。
|
|
35
|
-
*/
|
|
36
|
-
export const LINE_PRIMITIVE = 13;
|
|
37
|
-
|
|
38
|
-
/** primitiveType = 1:点云包。索引是点序号,画成三角面就是满屏彩色尖刺。 */
|
|
39
|
-
export const POINT_PRIMITIVE = 1;
|
|
26
|
+
// 组装层下沉 core 后的对外 API 兼容:常量原住本文件
|
|
27
|
+
export { LINE_PRIMITIVE, POINT_PRIMITIVE };
|
|
40
28
|
|
|
41
29
|
function isLinePrimitive(primitiveType: number): boolean {
|
|
42
30
|
// 1 = 点,2 = 线段,3 = 折线,13 = 棱线包
|
|
@@ -86,103 +74,7 @@ function getPointCloudMaterial(): THREE.PointsMaterial {
|
|
|
86
74
|
|
|
87
75
|
/* ------------------------------------------------------------------ 顶点流 */
|
|
88
76
|
|
|
89
|
-
|
|
90
|
-
return dtype === 'u8' ? 1 : dtype === 'u16' ? 2 : 4;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function typedArray(dtype: AttrDType, buffer: ArrayBufferLike): Float32Array | Uint8Array | Uint16Array | Uint32Array {
|
|
94
|
-
switch (dtype) {
|
|
95
|
-
case 'u8':
|
|
96
|
-
return new Uint8Array(buffer);
|
|
97
|
-
case 'u16':
|
|
98
|
-
return new Uint16Array(buffer);
|
|
99
|
-
case 'u32':
|
|
100
|
-
return new Uint32Array(buffer);
|
|
101
|
-
default:
|
|
102
|
-
return new Float32Array(buffer);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function typedArrayOf(dtype: AttrDType, length: number): Float32Array | Uint8Array | Uint16Array | Uint32Array {
|
|
107
|
-
switch (dtype) {
|
|
108
|
-
case 'u8':
|
|
109
|
-
return new Uint8Array(length);
|
|
110
|
-
case 'u16':
|
|
111
|
-
return new Uint16Array(length);
|
|
112
|
-
case 'u32':
|
|
113
|
-
return new Uint32Array(length);
|
|
114
|
-
default:
|
|
115
|
-
return new Float32Array(length);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function toBufferAttribute(attr: VertexAttribute): THREE.BufferAttribute {
|
|
120
|
-
const { data, stride, offsetInBytes, dtype = 'f32' } = attr;
|
|
121
|
-
const components = attr.components ?? (attr as unknown as { comp?: number }).comp ?? 3;
|
|
122
|
-
const bpe = bytesPerElement(dtype);
|
|
123
|
-
const itemBytes = components * bpe;
|
|
124
|
-
const actualStride = stride ?? itemBytes;
|
|
125
|
-
const actualOffset = offsetInBytes ?? 0;
|
|
126
|
-
const normalized = dtype === 'u8';
|
|
127
|
-
|
|
128
|
-
// 非交错:整段直接铺开
|
|
129
|
-
if (actualStride === itemBytes && actualOffset === 0) {
|
|
130
|
-
const view = typedArray(dtype, data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
|
|
131
|
-
return new THREE.BufferAttribute(view, components, normalized);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 交错:逐元素抽取
|
|
135
|
-
const count = Math.floor(data.byteLength / actualStride);
|
|
136
|
-
const out = typedArrayOf(dtype, count * components);
|
|
137
|
-
const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
138
|
-
for (let i = 0; i < count; i++) {
|
|
139
|
-
const base = i * actualStride + actualOffset;
|
|
140
|
-
for (let c = 0; c < components; c++) {
|
|
141
|
-
const o = base + c * bpe;
|
|
142
|
-
out[i * components + c] =
|
|
143
|
-
dtype === 'u8'
|
|
144
|
-
? dv.getUint8(o)
|
|
145
|
-
: dtype === 'u16'
|
|
146
|
-
? dv.getUint16(o, true)
|
|
147
|
-
: dtype === 'u32'
|
|
148
|
-
? dv.getUint32(o, true)
|
|
149
|
-
: dv.getFloat32(o, true);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return new THREE.BufferAttribute(out, components, normalized);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function toIndex(indexPack: IndexPackage): THREE.BufferAttribute | null {
|
|
156
|
-
const { indices, indexType, indexCount } = indexPack;
|
|
157
|
-
if (!indices || indexCount === 0) return null;
|
|
158
|
-
// 以字节宽度为准:S3M 的 indexType 在 MeshOpt 包里恒为 0,靠它会读错一半索引。
|
|
159
|
-
const width = Math.floor(indices.byteLength / indexCount);
|
|
160
|
-
const wide = width === 4 || (width !== 2 && (indexType === 1 || indexType === 3));
|
|
161
|
-
const n = indexCount * (wide ? 4 : 2);
|
|
162
|
-
if (n > indices.byteLength) return null;
|
|
163
|
-
const buf = indices.buffer.slice(indices.byteOffset, indices.byteOffset + n);
|
|
164
|
-
const view = wide ? new Uint32Array(buf) : new Uint16Array(buf);
|
|
165
|
-
return new THREE.BufferAttribute(view, 1);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** 把 u8 顶点色转成 float vec3,供着色器直接相乘 */
|
|
169
|
-
function toColorAttribute(attr: VertexAttribute | null, vertexCount: number): THREE.BufferAttribute {
|
|
170
|
-
const out = new Float32Array(vertexCount * 3);
|
|
171
|
-
if (!attr) {
|
|
172
|
-
out.fill(1);
|
|
173
|
-
return new THREE.BufferAttribute(out, 3);
|
|
174
|
-
}
|
|
175
|
-
const src = attr.data;
|
|
176
|
-
const comp = attr.components ?? 4;
|
|
177
|
-
const scale = attr.dtype === 'u8' ? 1 / 255 : 1;
|
|
178
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
179
|
-
const o = i * comp;
|
|
180
|
-
out[i * 3] = (src[o] ?? 255) * scale;
|
|
181
|
-
out[i * 3 + 1] = (src[o + 1] ?? 255) * scale;
|
|
182
|
-
out[i * 3 + 2] = (src[o + 2] ?? 255) * scale;
|
|
183
|
-
}
|
|
184
|
-
return new THREE.BufferAttribute(out, 3);
|
|
185
|
-
}
|
|
77
|
+
/** 顶点流解交错 / 法线 / 合批已下沉 core/TileGeometry.ts(Worker / 主线程共用) */
|
|
186
78
|
|
|
187
79
|
/** SuperMap 内置水面(`103.water`)的基色:偏深的青蓝,配合低粗糙度出一点天光反射 */
|
|
188
80
|
const WATER_COLOR = new THREE.Color(0x1d4e63);
|
|
@@ -282,6 +174,11 @@ export interface BuildTileOptions {
|
|
|
282
174
|
* 对倾斜摄影、大网格切片瓦片可减少 90%+ DrawCall,显著提高渲染帧率。
|
|
283
175
|
*/
|
|
284
176
|
batching?: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Worker 侧已完成的纯数据几何组装(经典 .s3mb 路径由解析 Worker 顺带产出)。
|
|
179
|
+
* 缺省时在主线程现场组装(core/TileGeometry 的同一份实现),两条路径产物一致。
|
|
180
|
+
*/
|
|
181
|
+
assembled?: TileGeometryData;
|
|
285
182
|
}
|
|
286
183
|
|
|
287
184
|
export interface BuildTileResult {
|
|
@@ -291,16 +188,6 @@ export interface BuildTileResult {
|
|
|
291
188
|
textureKeys?: string[];
|
|
292
189
|
}
|
|
293
190
|
|
|
294
|
-
interface CachedGeometry {
|
|
295
|
-
geometry: THREE.BufferGeometry;
|
|
296
|
-
pack: IndexPackage;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
interface SkeletonInfo {
|
|
300
|
-
indexPackages: IndexPackage[];
|
|
301
|
-
instanced: boolean;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
191
|
export async function buildTileObjects(
|
|
305
192
|
tile: ParsedTile,
|
|
306
193
|
options: BuildTileOptions = {}
|
|
@@ -448,388 +335,62 @@ export async function buildTileObjects(
|
|
|
448
335
|
return mat;
|
|
449
336
|
};
|
|
450
337
|
|
|
451
|
-
// 3)
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
stats.skipped.push(`${name} 无 position`);
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const vertexCount = vp.verticesCount || 0;
|
|
464
|
-
const attributes: Record<string, THREE.BufferAttribute> = { position: toBufferAttribute(positionAttr) };
|
|
465
|
-
let colorAttr: VertexAttribute | null = null;
|
|
466
|
-
let batchAttr: VertexAttribute | null = null;
|
|
467
|
-
|
|
468
|
-
for (const attr of vp.vertexAttributes) {
|
|
469
|
-
if (attr.divisor !== undefined) continue; // 实例属性不进顶点流
|
|
470
|
-
if (attr.semantic === 'position') continue;
|
|
471
|
-
if (attr.semantic === 'color') colorAttr = attr;
|
|
472
|
-
else if (attr.semantic === 'batchId') batchAttr = attr;
|
|
473
|
-
else if (attr.semantic === 'normal') attributes.normal = toBufferAttribute(attr);
|
|
474
|
-
else if (attr.semantic === 'uv') attributes.uv = toBufferAttribute(attr);
|
|
475
|
-
else if (attr.semantic === 'uv2') attributes.uv1 = toBufferAttribute(attr);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// 着色器强依赖这两个 attribute,缺了就补默认值
|
|
479
|
-
attributes.aColor = toColorAttribute(colorAttr, vertexCount);
|
|
480
|
-
attributes.aBatchId = batchAttr
|
|
481
|
-
? toBufferAttribute(batchAttr)
|
|
482
|
-
: new THREE.BufferAttribute(new Float32Array(vertexCount).fill(-1), 1);
|
|
483
|
-
|
|
484
|
-
// S3MBLOCK 图集:逐顶点带子区域矩形,平铺的 fract 交给片元做(顶点上做会画反)。
|
|
485
|
-
// 烘焙图(材质 unit1)用第二套原始 UV,这里另开一个 aSecUv 槽位,不跟 three 的 uv1 语义纠缠。
|
|
486
|
-
const rects = tile.atlasRects?.[name];
|
|
487
|
-
if (rects) {
|
|
488
|
-
attributes.aAtlasRect = new THREE.BufferAttribute(rects.uv ?? new Float32Array(vertexCount * 4), 4);
|
|
489
|
-
// 没有第二套 UV 的骨架不做烘焙:给一个退化矩形,片元里据此跳过
|
|
490
|
-
const uv1 = attributes.uv1 as THREE.BufferAttribute | undefined;
|
|
491
|
-
if (rects.uv2 && uv1) {
|
|
492
|
-
attributes.aAtlasRect1 = new THREE.BufferAttribute(rects.uv2, 4);
|
|
493
|
-
attributes.aSecUv = uv1;
|
|
494
|
-
} else {
|
|
495
|
-
attributes.aAtlasRect1 = new THREE.BufferAttribute(new Float32Array(vertexCount * 4), 4);
|
|
496
|
-
attributes.aSecUv = (attributes.uv as THREE.BufferAttribute) ?? new THREE.BufferAttribute(new Float32Array(vertexCount * 2), 2);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
const instanced = vp.instanceIndex !== -1 && (vp.instanceMode === 16 || vp.instanceMode === 17);
|
|
501
|
-
// 注意:indexPackages 属于 Skeleton(geoPackage[name]),不在 vertexPackage 里
|
|
502
|
-
//
|
|
503
|
-
// 一个索引包都没有的骨架 = 纯点集:高斯泼溅(3DGaussianSplatting)和无索引点云
|
|
504
|
-
// 都是这种——顶点就是点,没有任何三角形可画。不补一个「顶点即点」的伪包,
|
|
505
|
-
// 下面就不会产出任何对象,整块瓦片最后落到「tile 无可用几何」被丢掉。
|
|
506
|
-
let packs = geo.indexPackages ?? [];
|
|
507
|
-
if (packs.length === 0 && vertexCount > 0) {
|
|
508
|
-
packs = [
|
|
509
|
-
{
|
|
510
|
-
indexCount: 0,
|
|
511
|
-
indexType: 0,
|
|
512
|
-
useIndex: 0,
|
|
513
|
-
primitiveType: POINT_PRIMITIVE,
|
|
514
|
-
indices: null,
|
|
515
|
-
passNames: [],
|
|
516
|
-
},
|
|
517
|
-
];
|
|
518
|
-
}
|
|
519
|
-
skeletonInfo.set(name, { indexPackages: packs, instanced });
|
|
520
|
-
stats.vertices += vertexCount;
|
|
521
|
-
|
|
522
|
-
packs.forEach((pack, pi) => {
|
|
523
|
-
let geometry = new THREE.BufferGeometry();
|
|
524
|
-
for (const [k, v] of Object.entries(attributes)) geometry.setAttribute(k, v);
|
|
525
|
-
const idx = toIndex(pack);
|
|
526
|
-
if (idx) geometry.setIndex(idx);
|
|
527
|
-
else geometry.setDrawRange(0, vertexCount);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
// 如果数据本身没有法线且是三角网格,必须在绑定了 index 之后计算法线
|
|
532
|
-
// 绝不能在未索引的 attributes.position 上算,否则非连续顶点会导致法线朝向错乱甚至越界 NaN 导致死黑面
|
|
533
|
-
// 线 / 点包不需要法线
|
|
534
|
-
if (
|
|
535
|
-
!isLinePrimitive(pack.primitiveType) &&
|
|
536
|
-
!isPointPrimitive(pack.primitiveType) &&
|
|
537
|
-
!geometry.getAttribute('normal')
|
|
538
|
-
) {
|
|
539
|
-
geometry.computeVertexNormals();
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
geometry.computeBoundingBox();
|
|
543
|
-
geometry.computeBoundingSphere();
|
|
544
|
-
geoCache.set(`${name}|${pi}`, { geometry, pack });
|
|
545
|
-
});
|
|
546
|
-
}
|
|
338
|
+
// 3) 纯数据几何组装:Worker 解析时已顺带产出就直接用,否则主线程现场组装
|
|
339
|
+
// (core/TileGeometry 的同一份实现,两条路径产物一致)
|
|
340
|
+
const assembled = options.assembled ?? assembleTileGeometry(tile, { batching: options.batching !== false });
|
|
341
|
+
stats.vertices = assembled.stats.vertices;
|
|
342
|
+
stats.edgeLines = assembled.stats.edgeLines;
|
|
343
|
+
stats.instanced = assembled.stats.instanced;
|
|
344
|
+
if (assembled.stats.originalMeshes != null) stats.originalMeshes = assembled.stats.originalMeshes;
|
|
345
|
+
stats.skipped.push(...assembled.stats.skipped);
|
|
547
346
|
|
|
548
347
|
const IDENTITY = new THREE.Matrix4();
|
|
549
348
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
matrix: THREE.Matrix4 | null;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const batchGroups = new Map<string, CandidateMesh[]>();
|
|
558
|
-
|
|
559
|
-
const mergeBatchGeometries = (
|
|
560
|
-
candidates: CandidateMesh[],
|
|
561
|
-
material: StyledMaterial,
|
|
562
|
-
tileRef: ParsedTile
|
|
563
|
-
): THREE.Mesh => {
|
|
564
|
-
let totalVertices = 0;
|
|
565
|
-
let totalIndices = 0;
|
|
566
|
-
let hasNormal = false;
|
|
567
|
-
let hasUv = false;
|
|
568
|
-
let hasUv1 = false;
|
|
569
|
-
let hasAColor = false;
|
|
570
|
-
let hasABatchId = false;
|
|
571
|
-
let hasAtlas = false;
|
|
572
|
-
|
|
573
|
-
for (const c of candidates) {
|
|
574
|
-
const g = c.cached.geometry;
|
|
575
|
-
const vCount = g.attributes.position?.count ?? 0;
|
|
576
|
-
totalVertices += vCount;
|
|
577
|
-
totalIndices += g.index ? g.index.count : vCount;
|
|
578
|
-
if (g.attributes.normal) hasNormal = true;
|
|
579
|
-
if (g.attributes.uv) hasUv = true;
|
|
580
|
-
if (g.attributes.uv1) hasUv1 = true;
|
|
581
|
-
if (g.attributes.aColor) hasAColor = true;
|
|
582
|
-
if (g.attributes.aBatchId) hasABatchId = true;
|
|
583
|
-
if (g.attributes.aAtlasRect) hasAtlas = true;
|
|
349
|
+
const packPrimitive = (prim: PrimitiveData): THREE.BufferGeometry => {
|
|
350
|
+
const geometry = new THREE.BufferGeometry();
|
|
351
|
+
for (const [k, a] of Object.entries(prim.attributes)) {
|
|
352
|
+
if (a) geometry.setAttribute(k, new THREE.BufferAttribute(a.array, a.components, a.normalized));
|
|
584
353
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
/** 逐顶点拷一个 n 分量的浮点属性;缺失时用 fallbackRect / 0 兜底 */
|
|
597
|
-
const copyAttr = (
|
|
598
|
-
dst: Float32Array,
|
|
599
|
-
attr: THREE.BufferAttribute | THREE.InterleavedBufferAttribute | undefined,
|
|
600
|
-
comp: number,
|
|
601
|
-
vCount: number,
|
|
602
|
-
vertexOffset: number,
|
|
603
|
-
fallback?: (i: number, out: number[]) => void
|
|
604
|
-
) => {
|
|
605
|
-
if (attr) {
|
|
606
|
-
const src = attr.array as ArrayLike<number>;
|
|
607
|
-
for (let i = 0; i < vCount; i++)
|
|
608
|
-
for (let k = 0; k < comp; k++) dst[(vertexOffset + i) * comp + k] = src[i * comp + k] ?? 0;
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
for (let i = 0; i < vCount; i++) {
|
|
612
|
-
const out: number[] = [];
|
|
613
|
-
fallback ? fallback(i, out) : out.push(...new Array(comp).fill(0));
|
|
614
|
-
for (let k = 0; k < comp; k++) dst[(vertexOffset + i) * comp + k] = out[k] ?? 0;
|
|
615
|
-
}
|
|
616
|
-
};
|
|
617
|
-
|
|
618
|
-
const IndexArrayType = totalVertices > 65535 ? Uint32Array : Uint16Array;
|
|
619
|
-
const mergedIndices = new IndexArrayType(totalIndices);
|
|
620
|
-
|
|
621
|
-
let vertexOffset = 0;
|
|
622
|
-
let indexOffset = 0;
|
|
623
|
-
|
|
624
|
-
const tmpVec = new THREE.Vector3();
|
|
625
|
-
const normalMatrix = new THREE.Matrix3();
|
|
626
|
-
const geoByBatchId = new Map<number, GeoPackageEntry>();
|
|
627
|
-
|
|
628
|
-
for (const c of candidates) {
|
|
629
|
-
const g = c.cached.geometry;
|
|
630
|
-
const vCount = g.attributes.position.count;
|
|
631
|
-
const m = c.matrix;
|
|
632
|
-
const hasTransform = m && !m.equals(IDENTITY);
|
|
633
|
-
if (hasTransform && hasNormal) {
|
|
634
|
-
normalMatrix.getNormalMatrix(m);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
if (c.geo.pickInfo) {
|
|
638
|
-
for (const entry of Object.values(c.geo.pickInfo)) {
|
|
639
|
-
if (typeof entry.batchId === 'number' && entry.batchId >= 0) {
|
|
640
|
-
geoByBatchId.set(entry.batchId, c.geo);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// 1. Position
|
|
646
|
-
const posArr = g.attributes.position.array as ArrayLike<number>;
|
|
647
|
-
for (let i = 0; i < vCount; i++) {
|
|
648
|
-
const srcIdx = i * 3;
|
|
649
|
-
const dstIdx = (vertexOffset + i) * 3;
|
|
650
|
-
if (hasTransform) {
|
|
651
|
-
tmpVec.set(posArr[srcIdx], posArr[srcIdx + 1], posArr[srcIdx + 2]).applyMatrix4(m);
|
|
652
|
-
mergedPos[dstIdx] = tmpVec.x;
|
|
653
|
-
mergedPos[dstIdx + 1] = tmpVec.y;
|
|
654
|
-
mergedPos[dstIdx + 2] = tmpVec.z;
|
|
655
|
-
} else {
|
|
656
|
-
mergedPos[dstIdx] = posArr[srcIdx];
|
|
657
|
-
mergedPos[dstIdx + 1] = posArr[srcIdx + 1];
|
|
658
|
-
mergedPos[dstIdx + 2] = posArr[srcIdx + 2];
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// 2. Normal
|
|
663
|
-
if (mergedNorm) {
|
|
664
|
-
const normAttr = g.attributes.normal;
|
|
665
|
-
if (normAttr) {
|
|
666
|
-
const normArr = normAttr.array as ArrayLike<number>;
|
|
667
|
-
for (let i = 0; i < vCount; i++) {
|
|
668
|
-
const srcIdx = i * 3;
|
|
669
|
-
const dstIdx = (vertexOffset + i) * 3;
|
|
670
|
-
if (hasTransform) {
|
|
671
|
-
tmpVec
|
|
672
|
-
.set(normArr[srcIdx], normArr[srcIdx + 1], normArr[srcIdx + 2])
|
|
673
|
-
.applyMatrix3(normalMatrix)
|
|
674
|
-
.normalize();
|
|
675
|
-
mergedNorm[dstIdx] = tmpVec.x;
|
|
676
|
-
mergedNorm[dstIdx + 1] = tmpVec.y;
|
|
677
|
-
mergedNorm[dstIdx + 2] = tmpVec.z;
|
|
678
|
-
} else {
|
|
679
|
-
mergedNorm[dstIdx] = normArr[srcIdx];
|
|
680
|
-
mergedNorm[dstIdx + 1] = normArr[srcIdx + 1];
|
|
681
|
-
mergedNorm[dstIdx + 2] = normArr[srcIdx + 2];
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// 3. UV
|
|
688
|
-
if (mergedUv) {
|
|
689
|
-
const uvAttr = g.attributes.uv;
|
|
690
|
-
if (uvAttr) {
|
|
691
|
-
const uvArr = uvAttr.array as ArrayLike<number>;
|
|
692
|
-
for (let i = 0; i < vCount; i++) {
|
|
693
|
-
mergedUv[(vertexOffset + i) * 2] = uvArr[i * 2];
|
|
694
|
-
mergedUv[(vertexOffset + i) * 2 + 1] = uvArr[i * 2 + 1];
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// 4. UV1
|
|
700
|
-
if (mergedUv1) {
|
|
701
|
-
const uv1Attr = g.attributes.uv1;
|
|
702
|
-
if (uv1Attr) {
|
|
703
|
-
const uvArr = uv1Attr.array as ArrayLike<number>;
|
|
704
|
-
for (let i = 0; i < vCount; i++) {
|
|
705
|
-
mergedUv1[(vertexOffset + i) * 2] = uvArr[i * 2];
|
|
706
|
-
mergedUv1[(vertexOffset + i) * 2 + 1] = uvArr[i * 2 + 1];
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
// 5. aColor
|
|
712
|
-
if (mergedColor) {
|
|
713
|
-
const colAttr = g.attributes.aColor;
|
|
714
|
-
if (colAttr) {
|
|
715
|
-
const colArr = colAttr.array as ArrayLike<number>;
|
|
716
|
-
for (let i = 0; i < vCount; i++) {
|
|
717
|
-
mergedColor[(vertexOffset + i) * 3] = colArr[i * 3];
|
|
718
|
-
mergedColor[(vertexOffset + i) * 3 + 1] = colArr[i * 3 + 1];
|
|
719
|
-
mergedColor[(vertexOffset + i) * 3 + 2] = colArr[i * 3 + 2];
|
|
720
|
-
}
|
|
721
|
-
} else {
|
|
722
|
-
for (let i = 0; i < vCount; i++) {
|
|
723
|
-
mergedColor[(vertexOffset + i) * 3] = 1;
|
|
724
|
-
mergedColor[(vertexOffset + i) * 3 + 1] = 1;
|
|
725
|
-
mergedColor[(vertexOffset + i) * 3 + 2] = 1;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// 6. aBatchId
|
|
731
|
-
if (mergedBatchId) {
|
|
732
|
-
const bAttr = g.attributes.aBatchId;
|
|
733
|
-
if (bAttr) {
|
|
734
|
-
const bArr = bAttr.array as ArrayLike<number>;
|
|
735
|
-
for (let i = 0; i < vCount; i++) {
|
|
736
|
-
mergedBatchId[vertexOffset + i] = bArr[i];
|
|
737
|
-
}
|
|
738
|
-
} else {
|
|
739
|
-
for (let i = 0; i < vCount; i++) {
|
|
740
|
-
mergedBatchId[vertexOffset + i] = -1;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// 7. 图集矩形(S3MBLOCK)
|
|
746
|
-
if (mergedRect0) {
|
|
747
|
-
copyAttr(mergedRect0, g.attributes.aAtlasRect, 4, vCount, vertexOffset, (_i, out) => out.push(0, 0, 0, 0));
|
|
748
|
-
copyAttr(mergedRect1!, g.attributes.aAtlasRect1, 4, vCount, vertexOffset, (_i, out) => out.push(0, 0, 0, 0));
|
|
749
|
-
copyAttr(mergedSecUv!, g.attributes.aSecUv, 2, vCount, vertexOffset);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// 8. Indices
|
|
753
|
-
if (g.index) {
|
|
754
|
-
const idxArr = g.index.array as ArrayLike<number>;
|
|
755
|
-
const iCount = g.index.count;
|
|
756
|
-
for (let k = 0; k < iCount; k++) {
|
|
757
|
-
mergedIndices[indexOffset + k] = idxArr[k] + vertexOffset;
|
|
758
|
-
}
|
|
759
|
-
indexOffset += iCount;
|
|
760
|
-
} else {
|
|
761
|
-
for (let k = 0; k < vCount; k++) {
|
|
762
|
-
mergedIndices[indexOffset + k] = k + vertexOffset;
|
|
763
|
-
}
|
|
764
|
-
indexOffset += vCount;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
vertexOffset += vCount;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
const mergedGeo = new THREE.BufferGeometry();
|
|
771
|
-
mergedGeo.setAttribute('position', new THREE.BufferAttribute(mergedPos, 3));
|
|
772
|
-
if (mergedNorm) mergedGeo.setAttribute('normal', new THREE.BufferAttribute(mergedNorm, 3));
|
|
773
|
-
if (mergedUv) mergedGeo.setAttribute('uv', new THREE.BufferAttribute(mergedUv, 2));
|
|
774
|
-
if (mergedUv1) mergedGeo.setAttribute('uv1', new THREE.BufferAttribute(mergedUv1, 2));
|
|
775
|
-
if (mergedColor) mergedGeo.setAttribute('aColor', new THREE.BufferAttribute(mergedColor, 3));
|
|
776
|
-
if (mergedBatchId) mergedGeo.setAttribute('aBatchId', new THREE.BufferAttribute(mergedBatchId, 1));
|
|
777
|
-
if (mergedRect0) {
|
|
778
|
-
mergedGeo.setAttribute('aAtlasRect', new THREE.BufferAttribute(mergedRect0, 4));
|
|
779
|
-
mergedGeo.setAttribute('aAtlasRect1', new THREE.BufferAttribute(mergedRect1!, 4));
|
|
780
|
-
mergedGeo.setAttribute('aSecUv', new THREE.BufferAttribute(mergedSecUv!, 2));
|
|
781
|
-
}
|
|
782
|
-
mergedGeo.setIndex(new THREE.BufferAttribute(mergedIndices, 1));
|
|
783
|
-
mergedGeo.computeBoundingBox();
|
|
784
|
-
mergedGeo.computeBoundingSphere();
|
|
785
|
-
|
|
786
|
-
const mesh = new THREE.Mesh(mergedGeo, material);
|
|
787
|
-
mesh.userData.skeletonName = candidates[0].name;
|
|
788
|
-
mesh.userData.geoRef = candidates[0].geo;
|
|
789
|
-
mesh.userData.tileRef = tileRef;
|
|
790
|
-
mesh.userData.primitiveType = 4;
|
|
791
|
-
mesh.userData.isBatched = true;
|
|
792
|
-
mesh.userData.batchedCount = candidates.length;
|
|
793
|
-
if (geoByBatchId.size > 0) {
|
|
794
|
-
mesh.userData.geoByBatchId = geoByBatchId;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
return mesh;
|
|
354
|
+
if (prim.index) geometry.setIndex(new THREE.BufferAttribute(prim.index, 1));
|
|
355
|
+
else geometry.setDrawRange(0, prim.vertexCount);
|
|
356
|
+
geometry.boundingBox = new THREE.Box3(
|
|
357
|
+
new THREE.Vector3(...prim.bounds.boxMin),
|
|
358
|
+
new THREE.Vector3(...prim.bounds.boxMax)
|
|
359
|
+
);
|
|
360
|
+
geometry.boundingSphere = new THREE.Sphere(
|
|
361
|
+
new THREE.Vector3(...prim.bounds.sphereCenter),
|
|
362
|
+
prim.bounds.sphereRadius
|
|
363
|
+
);
|
|
364
|
+
return geometry;
|
|
798
365
|
};
|
|
799
366
|
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
const material = makeMaterial(pack.passNames?.[0], !!geometry.getAttribute('aAtlasRect'));
|
|
808
|
-
let object: THREE.Mesh | THREE.InstancedMesh;
|
|
809
|
-
|
|
810
|
-
const info = skeletonInfo.get(name);
|
|
811
|
-
const vp = geo.vertexPackage;
|
|
367
|
+
for (const prim of assembled.primitives) {
|
|
368
|
+
const geometry = packPrimitive(prim);
|
|
369
|
+
const geoRef = tile.geoPackage[prim.geoNames[0]] ?? null;
|
|
370
|
+
const vp = geoRef?.vertexPackage;
|
|
371
|
+
const material = makeMaterial(prim.passNames[0], !!prim.attributes.aAtlasRect);
|
|
372
|
+
const matrix =
|
|
373
|
+
prim.matrix && prim.matrix.length === 16 ? new THREE.Matrix4().fromArray(prim.matrix) : null;
|
|
812
374
|
|
|
813
375
|
// 棱线包:线段,不是三角面。默认隐藏,等 setEdges(true) 再显示
|
|
814
|
-
if (isLinePrimitive(
|
|
376
|
+
if (isLinePrimitive(prim.primitiveType)) {
|
|
815
377
|
const lines = new THREE.LineSegments(geometry, edgeLineMaterial);
|
|
816
378
|
lines.visible = options.edges === true;
|
|
817
379
|
lines.renderOrder = 2;
|
|
818
380
|
lines.raycast = () => {};
|
|
819
381
|
lines.userData.isEdgeLines = true;
|
|
820
|
-
lines.userData.skeletonName =
|
|
382
|
+
lines.userData.skeletonName = prim.skeletonName;
|
|
821
383
|
if (matrix && !matrix.equals(IDENTITY)) {
|
|
822
384
|
matrix.decompose(lines.position, lines.quaternion, lines.scale);
|
|
823
385
|
}
|
|
824
386
|
group.add(lines);
|
|
825
387
|
objects.push(lines);
|
|
826
388
|
group.userData.hasNativeEdges = true;
|
|
827
|
-
|
|
828
|
-
return lines;
|
|
389
|
+
continue;
|
|
829
390
|
}
|
|
830
391
|
|
|
831
392
|
// 点云包:顶点即点,按 THREE.Points 渲染。索引只是点序号,直接走 drawRange。
|
|
832
|
-
if (isPointPrimitive(
|
|
393
|
+
if (isPointPrimitive(prim.primitiveType)) {
|
|
833
394
|
// PointsMaterial 的顶点色读内置 'color' 槽位,与 aColor 共享同一份 buffer
|
|
834
395
|
if (!geometry.getAttribute('color')) {
|
|
835
396
|
geometry.setAttribute('color', geometry.getAttribute('aColor')!);
|
|
@@ -837,47 +398,50 @@ export async function buildTileObjects(
|
|
|
837
398
|
const points = new THREE.Points(geometry, getPointCloudMaterial());
|
|
838
399
|
// 点云不是 Mesh:viewer 的样式 / 拾取 / 卸载链路都靠这个标记放行
|
|
839
400
|
points.userData.isPointCloud = true;
|
|
840
|
-
points.userData.skeletonName =
|
|
841
|
-
points.userData.geoRef =
|
|
401
|
+
points.userData.skeletonName = prim.skeletonName;
|
|
402
|
+
points.userData.geoRef = geoRef;
|
|
842
403
|
points.userData.tileRef = tile;
|
|
843
|
-
points.userData.primitiveType =
|
|
404
|
+
points.userData.primitiveType = prim.primitiveType;
|
|
844
405
|
if (matrix && !matrix.equals(IDENTITY)) {
|
|
845
406
|
matrix.decompose(points.position, points.quaternion, points.scale);
|
|
846
407
|
}
|
|
847
408
|
group.add(points);
|
|
848
409
|
objects.push(points);
|
|
849
410
|
stats.meshes++;
|
|
850
|
-
|
|
411
|
+
continue;
|
|
851
412
|
}
|
|
852
413
|
|
|
853
|
-
|
|
414
|
+
let object: THREE.Mesh | THREE.InstancedMesh;
|
|
415
|
+
const instanced =
|
|
416
|
+
!!vp &&
|
|
417
|
+
vp.instanceIndex !== -1 &&
|
|
418
|
+
(vp.instanceMode === 16 || vp.instanceMode === 17) &&
|
|
419
|
+
!!vp.instanceBuffer &&
|
|
420
|
+
(vp.instanceCount ?? 0) > 0;
|
|
421
|
+
|
|
422
|
+
if (instanced && vp!) {
|
|
854
423
|
// 逐实例的两条属性:
|
|
855
424
|
// aInstanceBatchId —— 单体控制位,着色器靠它查状态纹理(改色 / 隐藏 / 抬高)
|
|
856
425
|
// aInstanceIndex —— 实例序号,拾取时编码进颜色,CPU 侧再映射回 SMID
|
|
857
|
-
// 逐顶点属性 aBatchId 在这里没用(顶点数 ≠ 实例数),gpu 上按实例取值只能靠 instanced attribute。
|
|
858
426
|
if (!geometry.getAttribute('aInstanceBatchId')) {
|
|
859
|
-
const ids = vp
|
|
427
|
+
const ids = vp!.instanceBatchIds;
|
|
860
428
|
const batchIds =
|
|
861
|
-
ids && ids.length === vp
|
|
429
|
+
ids && ids.length === vp!.instanceCount ? ids.slice() : new Float32Array(vp!.instanceCount).fill(-1);
|
|
862
430
|
geometry.setAttribute('aInstanceBatchId', new THREE.InstancedBufferAttribute(batchIds, 1));
|
|
863
|
-
const index = new Float32Array(vp
|
|
431
|
+
const index = new Float32Array(vp!.instanceCount);
|
|
864
432
|
for (let i = 0; i < index.length; i++) index[i] = i;
|
|
865
433
|
geometry.setAttribute('aInstanceIndex', new THREE.InstancedBufferAttribute(index, 1));
|
|
866
434
|
}
|
|
867
435
|
|
|
868
|
-
const
|
|
869
|
-
|
|
436
|
+
const instancedMesh = new THREE.InstancedMesh(geometry, material, vp!.instanceCount);
|
|
437
|
+
instancedMesh.instanceMatrix.setUsage(THREE.StaticDrawUsage);
|
|
870
438
|
const m4 = new THREE.Matrix4();
|
|
871
439
|
const color = new THREE.Color();
|
|
872
|
-
const stride = vp
|
|
873
|
-
const f32 = new Float32Array(vp
|
|
874
|
-
for (let i = 0; i < vp
|
|
440
|
+
const stride = vp!.instanceMode;
|
|
441
|
+
const f32 = new Float32Array(vp!.instanceBuffer!.buffer, vp!.instanceBuffer!.byteOffset, vp!.instanceCount * stride);
|
|
442
|
+
for (let i = 0; i < vp!.instanceCount; i++) {
|
|
875
443
|
const o = i * stride;
|
|
876
|
-
// S3M 实例矩阵按 3x4 仿射矩阵(行主序)连续存储 12 个 float
|
|
877
|
-
// Row 0: [r00, r01, r02, tx]
|
|
878
|
-
// Row 1: [r10, r11, r12, ty]
|
|
879
|
-
// Row 2: [r20, r21, r22, tz]
|
|
880
|
-
// Row 3: [0, 0, 0, 1] 隐式补齐
|
|
444
|
+
// S3M 实例矩阵按 3x4 仿射矩阵(行主序)连续存储 12 个 float(Row3 隐式补齐),
|
|
881
445
|
// 后续紧随 4 个 float 的 RGBA 颜色(stride >= 16)及可选 uint32 的 SMID(stride == 17)。
|
|
882
446
|
// 若用 fromArray 直接按列主序填充 Matrix4,会将平移分量塞进底部的投影行 (w),
|
|
883
447
|
// 造成严重的透视奇点畸变(所有顶点拉扯成放射状尖锥)。
|
|
@@ -887,22 +451,21 @@ export async function buildTileObjects(
|
|
|
887
451
|
f32[o + 8], f32[o + 9], f32[o + 10], f32[o + 11],
|
|
888
452
|
0, 0, 0, 1
|
|
889
453
|
);
|
|
890
|
-
|
|
454
|
+
instancedMesh.setMatrixAt(i, m4);
|
|
891
455
|
if (stride >= 16) {
|
|
892
456
|
const r = f32[o + 12];
|
|
893
457
|
const g = f32[o + 13];
|
|
894
458
|
const b = f32[o + 14];
|
|
895
459
|
if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {
|
|
896
460
|
color.setRGB(r, g, b);
|
|
897
|
-
|
|
461
|
+
instancedMesh.setColorAt(i, color);
|
|
898
462
|
}
|
|
899
463
|
}
|
|
900
464
|
}
|
|
901
|
-
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
object = instanced;
|
|
465
|
+
instancedMesh.instanceMatrix.needsUpdate = true;
|
|
466
|
+
if (instancedMesh.instanceColor) instancedMesh.instanceColor.needsUpdate = true;
|
|
467
|
+
instancedMesh.frustumCulled = false;
|
|
468
|
+
object = instancedMesh;
|
|
906
469
|
} else {
|
|
907
470
|
object = new THREE.Mesh(geometry, material);
|
|
908
471
|
}
|
|
@@ -913,120 +476,31 @@ export async function buildTileObjects(
|
|
|
913
476
|
matrix.decompose(object.position, object.quaternion, object.scale);
|
|
914
477
|
}
|
|
915
478
|
|
|
916
|
-
object.userData.skeletonName =
|
|
917
|
-
object.userData.geoRef =
|
|
479
|
+
object.userData.skeletonName = prim.skeletonName;
|
|
480
|
+
object.userData.geoRef = geoRef;
|
|
918
481
|
object.userData.tileRef = tile;
|
|
919
|
-
object.userData.primitiveType =
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
for (const geode of pageLod.geodes ?? []) {
|
|
932
|
-
const m = geode.matrix;
|
|
933
|
-
// S3M 的 matrix4d 是列主序(OpenGL 约定),平移在第 12/13/14 位,
|
|
934
|
-
// 与 three 的 Matrix4.elements 布局一致 —— 必须用 fromArray 直接搬。
|
|
935
|
-
// 用 set() 按行主序喂进去等于转置,平移会被塞进投影行,模型直接飞上天。
|
|
936
|
-
const matrix = m && m.length === 16 ? new THREE.Matrix4().fromArray(m) : null;
|
|
937
|
-
|
|
938
|
-
for (const name of geode.skeletonNames ?? []) {
|
|
939
|
-
const info = skeletonInfo.get(name);
|
|
940
|
-
if (!info) continue;
|
|
941
|
-
const geo = tile.geoPackage[name];
|
|
942
|
-
if (!geo) continue;
|
|
943
|
-
const vp = geo.vertexPackage;
|
|
944
|
-
for (let pi = 0; pi < info.indexPackages.length; pi++) {
|
|
945
|
-
const cached = geoCache.get(`${name}|${pi}`);
|
|
946
|
-
if (!cached) continue;
|
|
947
|
-
|
|
948
|
-
// 棱线包或实例化网格:保持单独摆放
|
|
949
|
-
if (
|
|
950
|
-
isLinePrimitive(cached.pack.primitiveType) ||
|
|
951
|
-
(info.instanced && vp.instanceBuffer && vp.instanceCount > 0)
|
|
952
|
-
) {
|
|
953
|
-
placeObject(cached, name, geo, matrix);
|
|
954
|
-
placed++;
|
|
955
|
-
continue;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// 普通三角面网格:若开启合批,按材质进行分组收集
|
|
959
|
-
if (options.batching !== false && cached.pack.primitiveType === 4) {
|
|
960
|
-
const matKey = cached.pack.passNames?.[0] ?? '__default__';
|
|
961
|
-
let list = batchGroups.get(matKey);
|
|
962
|
-
if (!list) {
|
|
963
|
-
list = [];
|
|
964
|
-
batchGroups.set(matKey, list);
|
|
965
|
-
}
|
|
966
|
-
list.push({ cached, name, geo, matrix });
|
|
967
|
-
placed++;
|
|
968
|
-
continue;
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
placeObject(cached, name, geo, matrix);
|
|
972
|
-
placed++;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
// 兜底:没有被任何 geode 引用的骨架,按单位矩阵渲染,避免整块消失
|
|
979
|
-
if (placed === 0) {
|
|
980
|
-
for (const [name, info] of skeletonInfo) {
|
|
981
|
-
const geo = tile.geoPackage[name];
|
|
982
|
-
if (!geo) continue;
|
|
983
|
-
const vp = geo.vertexPackage;
|
|
984
|
-
for (let pi = 0; pi < info.indexPackages.length; pi++) {
|
|
985
|
-
const cached = geoCache.get(`${name}|${pi}`);
|
|
986
|
-
if (!cached) continue;
|
|
987
|
-
|
|
988
|
-
if (
|
|
989
|
-
isLinePrimitive(cached.pack.primitiveType) ||
|
|
990
|
-
(info.instanced && vp.instanceBuffer && vp.instanceCount > 0)
|
|
991
|
-
) {
|
|
992
|
-
placeObject(cached, name, geo, null);
|
|
993
|
-
continue;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
if (options.batching !== false && cached.pack.primitiveType === 4) {
|
|
997
|
-
const matKey = cached.pack.passNames?.[0] ?? '__default__';
|
|
998
|
-
let list = batchGroups.get(matKey);
|
|
999
|
-
if (!list) {
|
|
1000
|
-
list = [];
|
|
1001
|
-
batchGroups.set(matKey, list);
|
|
482
|
+
object.userData.primitiveType = prim.primitiveType;
|
|
483
|
+
if (prim.batchedFrom != null && prim.batchedFrom > 1) {
|
|
484
|
+
object.userData.isBatched = true;
|
|
485
|
+
object.userData.batchedCount = prim.batchedFrom;
|
|
486
|
+
// 合并产物按 batchId 反查 geoRef(拾取用):原实现在合并时顺手建,这里从 geoNames 重建
|
|
487
|
+
const geoByBatchId = new Map<number, GeoPackageEntry>();
|
|
488
|
+
for (const gname of prim.geoNames) {
|
|
489
|
+
const g = tile.geoPackage[gname];
|
|
490
|
+
if (!g?.pickInfo) continue;
|
|
491
|
+
for (const entry of Object.values(g.pickInfo)) {
|
|
492
|
+
if (typeof entry.batchId === 'number' && entry.batchId >= 0) {
|
|
493
|
+
geoByBatchId.set(entry.batchId, g);
|
|
1002
494
|
}
|
|
1003
|
-
list.push({ cached, name, geo, matrix: null });
|
|
1004
|
-
continue;
|
|
1005
495
|
}
|
|
1006
|
-
|
|
1007
|
-
placeObject(cached, name, geo, null);
|
|
1008
496
|
}
|
|
497
|
+
if (geoByBatchId.size > 0) object.userData.geoByBatchId = geoByBatchId;
|
|
1009
498
|
}
|
|
1010
|
-
|
|
499
|
+
group.add(object);
|
|
500
|
+
objects.push(object);
|
|
1011
501
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
for (const [matKey, list] of batchGroups) {
|
|
1015
|
-
stats.originalMeshes = (stats.originalMeshes ?? 0) + list.length;
|
|
1016
|
-
if (list.length === 1) {
|
|
1017
|
-
placeObject(list[0].cached, list[0].name, list[0].geo, list[0].matrix);
|
|
1018
|
-
} else {
|
|
1019
|
-
const hasAtlasRect = !!list[0].cached.geometry.getAttribute('aAtlasRect');
|
|
1020
|
-
const material = makeMaterial(matKey === '__default__' ? undefined : matKey, hasAtlasRect);
|
|
1021
|
-
const batchedMesh = mergeBatchGeometries(list, material, tile);
|
|
1022
|
-
group.add(batchedMesh);
|
|
1023
|
-
objects.push(batchedMesh);
|
|
1024
|
-
stats.meshes++;
|
|
1025
|
-
for (const item of list) {
|
|
1026
|
-
stats.triangles += (item.cached.pack.indexCount || 0) / 3;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
502
|
+
stats.meshes++;
|
|
503
|
+
if (prim.primitiveType === 4) stats.triangles += prim.indexCount / 3;
|
|
1030
504
|
}
|
|
1031
505
|
|
|
1032
506
|
if (!group.children.length) stats.skipped.push('tile 无可用几何');
|