s3m-three 0.4.18 → 0.4.19
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-4iTAmWIu.js +1 -0
- package/dist/browser/s3m-three.iife.ext-three.js +406 -91
- package/dist/browser/s3m-three.iife.js +434 -119
- package/dist/core/GaussianSplat.d.ts +64 -0
- package/dist/core/GaussianSplat.d.ts.map +1 -0
- package/dist/core/GaussianSplat.js +139 -0
- package/dist/core/GaussianSplat.js.map +1 -0
- package/dist/core/S3M1Parser.d.ts +1 -1
- package/dist/core/S3M1Parser.d.ts.map +1 -1
- package/dist/core/S3M1Parser.js +320 -25
- package/dist/core/S3M1Parser.js.map +1 -1
- package/dist/core/S3MBParser.d.ts.map +1 -1
- package/dist/core/S3MBParser.js +13 -0
- package/dist/core/S3MBParser.js.map +1 -1
- package/dist/core/S3MBlockParser.d.ts.map +1 -1
- package/dist/core/S3MBlockParser.js +56 -14
- package/dist/core/S3MBlockParser.js.map +1 -1
- package/dist/core/SplatLod.d.ts +36 -0
- package/dist/core/SplatLod.d.ts.map +1 -0
- package/dist/core/SplatLod.js +54 -0
- package/dist/core/SplatLod.js.map +1 -0
- package/dist/core/SplatProjection.d.ts +75 -0
- package/dist/core/SplatProjection.d.ts.map +1 -0
- package/dist/core/SplatProjection.js +162 -0
- package/dist/core/SplatProjection.js.map +1 -0
- package/dist/core/SplatSort.d.ts +13 -0
- package/dist/core/SplatSort.d.ts.map +1 -0
- package/dist/core/SplatSort.js +14 -0
- package/dist/core/SplatSort.js.map +1 -0
- package/dist/core/TexCoordZ.d.ts +94 -0
- package/dist/core/TexCoordZ.d.ts.map +1 -0
- package/dist/core/TexCoordZ.js +125 -0
- package/dist/core/TexCoordZ.js.map +1 -0
- package/dist/core/TextureDecode.d.ts +19 -0
- package/dist/core/TextureDecode.d.ts.map +1 -1
- package/dist/core/TextureDecode.js +53 -11
- package/dist/core/TextureDecode.js.map +1 -1
- package/dist/core/TileGeometry.d.ts +29 -0
- package/dist/core/TileGeometry.d.ts.map +1 -1
- package/dist/core/TileGeometry.js +130 -14
- package/dist/core/TileGeometry.js.map +1 -1
- package/dist/core/degradations.d.ts +37 -0
- package/dist/core/degradations.d.ts.map +1 -0
- package/dist/core/degradations.js +32 -0
- package/dist/core/degradations.js.map +1 -0
- package/dist/core/types.d.ts +36 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +11 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +2 -0
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/FeatureStyleManager.d.ts.map +1 -1
- package/dist/render/FeatureStyleManager.js +7 -5
- package/dist/render/FeatureStyleManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +24 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +196 -5
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/Picker.d.ts.map +1 -1
- package/dist/render/Picker.js +19 -5
- package/dist/render/Picker.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +69 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +222 -12
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/SplatMaterial.d.ts +103 -0
- package/dist/render/SplatMaterial.d.ts.map +1 -0
- package/dist/render/SplatMaterial.js +388 -0
- package/dist/render/SplatMaterial.js.map +1 -0
- package/dist/render/SplatSorter.d.ts +14 -0
- package/dist/render/SplatSorter.d.ts.map +1 -0
- package/dist/render/SplatSorter.js +59 -0
- package/dist/render/SplatSorter.js.map +1 -0
- package/dist/render/StyleMaterial.d.ts +48 -0
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +292 -69
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +10 -0
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +35 -5
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/render/lightingPreset.d.ts +53 -0
- package/dist/render/lightingPreset.d.ts.map +1 -0
- package/dist/render/lightingPreset.js +36 -0
- package/dist/render/lightingPreset.js.map +1 -0
- package/dist/render/materialAlpha.d.ts +51 -0
- package/dist/render/materialAlpha.d.ts.map +1 -0
- package/dist/render/materialAlpha.js +68 -0
- package/dist/render/materialAlpha.js.map +1 -0
- package/dist/tileset/S3MTileset.d.ts +42 -0
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +52 -1
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/vendor/browser-node-stub.js +10 -0
- package/dist/worker/s3mWorker.js +1166 -979
- package/dist/worker/s3mWorker.js.map +1 -1
- package/docs/API.md +55 -1
- package/docs/FORMAT-NOTES.md +338 -0
- package/package.json +1 -1
- package/src/core/GaussianSplat.ts +175 -0
- package/src/core/S3M1Parser.ts +330 -24
- package/src/core/S3MBParser.ts +18 -0
- package/src/core/S3MBlockParser.ts +56 -11
- package/src/core/SplatLod.ts +83 -0
- package/src/core/SplatProjection.ts +231 -0
- package/src/core/SplatSort.ts +30 -0
- package/src/core/TexCoordZ.ts +160 -0
- package/src/core/TextureDecode.ts +63 -11
- package/src/core/TileGeometry.ts +138 -14
- package/src/core/degradations.ts +74 -0
- package/src/core/types.ts +32 -0
- package/src/index.ts +8 -1
- package/src/render/DatasetManager.ts +10 -0
- package/src/render/FeatureStyleManager.ts +7 -5
- package/src/render/GeometryBuilder.ts +239 -6
- package/src/render/Picker.ts +21 -5
- package/src/render/S3MViewer.ts +255 -11
- package/src/render/SplatMaterial.ts +433 -0
- package/src/render/SplatSorter.ts +59 -0
- package/src/render/StyleMaterial.ts +339 -69
- package/src/render/TextureFactory.ts +39 -5
- package/src/render/lightingPreset.ts +76 -0
- package/src/render/materialAlpha.ts +75 -0
- package/src/tileset/S3MTileset.ts +78 -1
- package/src/vendor/browser-node-stub.js +10 -0
- package/dist/browser/assets/s3mWorker-BjPOUgjg.js +0 -1
package/src/core/TileGeometry.ts
CHANGED
|
@@ -106,8 +106,16 @@ export function deinterleave(attr: VertexAttribute): PlainAttribute {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/** 把 u8 顶点色转成 float vec3,供着色器直接相乘(原 toColorAttribute) */
|
|
109
|
+
/**
|
|
110
|
+
* 顶点色解成 RGBA。
|
|
111
|
+
*
|
|
112
|
+
* **必须带 alpha**:BIM 数据里"材质不透明、靠顶点色 alpha 表达半透明"是常见做法。
|
|
113
|
+
* 实测 `3D-BIMbuilding-2` 的 SMID 4421(`WhiteMaterial_1`,无纹理、`diffuse.a=1`)
|
|
114
|
+
* 顶点色是 u8 `(0,128,192,63)`,alpha = 0.247 —— 之前只取 RGB,这一片就变成实心青色。
|
|
115
|
+
* 官方片元里 `baseColorWithAlpha = vColor` 是 vec4,走的就是这条路。
|
|
116
|
+
*/
|
|
109
117
|
function colorData(attr: VertexAttribute | null, vertexCount: number): Float32Array {
|
|
110
|
-
const out = new Float32Array(vertexCount *
|
|
118
|
+
const out = new Float32Array(vertexCount * 4);
|
|
111
119
|
if (!attr) {
|
|
112
120
|
out.fill(1);
|
|
113
121
|
return out;
|
|
@@ -115,11 +123,14 @@ function colorData(attr: VertexAttribute | null, vertexCount: number): Float32Ar
|
|
|
115
123
|
const src = attr.data;
|
|
116
124
|
const comp = attr.components ?? 4;
|
|
117
125
|
const scale = attr.dtype === 'u8' ? 1 / 255 : 1;
|
|
126
|
+
// 只有 RGB 的旧数据补 alpha = 1,避免留下 0(那会整片全透明)
|
|
127
|
+
const hasAlpha = comp >= 4;
|
|
118
128
|
for (let i = 0; i < vertexCount; i++) {
|
|
119
129
|
const o = i * comp;
|
|
120
|
-
out[i *
|
|
121
|
-
out[i *
|
|
122
|
-
out[i *
|
|
130
|
+
out[i * 4] = (src[o] ?? 255) * scale;
|
|
131
|
+
out[i * 4 + 1] = (src[o + 1] ?? 255) * scale;
|
|
132
|
+
out[i * 4 + 2] = (src[o + 2] ?? 255) * scale;
|
|
133
|
+
out[i * 4 + 3] = hasAlpha ? (src[o + 3] ?? 255) * scale : 1;
|
|
123
134
|
}
|
|
124
135
|
return out;
|
|
125
136
|
}
|
|
@@ -232,6 +243,11 @@ export interface PrimitiveAttributes {
|
|
|
232
243
|
aAtlasRect?: PlainAttribute;
|
|
233
244
|
aAtlasRect1?: PlainAttribute;
|
|
234
245
|
aSecUv?: PlainAttribute;
|
|
246
|
+
/**
|
|
247
|
+
* uv 第三分量(文件里的 `custom0`,官方 `aTextureCoordMatrix`)。
|
|
248
|
+
* 只有存在该流时才产生;解码语义见 core/TexCoordZ.ts。
|
|
249
|
+
*/
|
|
250
|
+
aTexCoordZ?: PlainAttribute;
|
|
235
251
|
}
|
|
236
252
|
|
|
237
253
|
export interface PrimitiveData {
|
|
@@ -258,6 +274,8 @@ export interface TileGeometryStats {
|
|
|
258
274
|
edgeLines: number;
|
|
259
275
|
/** 合批前独立网格数(batching 生效时 > meshes) */
|
|
260
276
|
originalMeshes?: number;
|
|
277
|
+
/** 被剔除的「同单体共面重复」三角形数(数据自重叠,见 dropCoincidentTriangles) */
|
|
278
|
+
deduped?: number;
|
|
261
279
|
skipped: string[];
|
|
262
280
|
}
|
|
263
281
|
|
|
@@ -355,6 +373,7 @@ function mergeCandidates(candidates: Candidate[]): PrimitiveData {
|
|
|
355
373
|
let hasUv = false;
|
|
356
374
|
let hasUv1 = false;
|
|
357
375
|
let hasAtlas = false;
|
|
376
|
+
let hasTexZ = false;
|
|
358
377
|
|
|
359
378
|
for (const c of candidates) {
|
|
360
379
|
totalVertices += c.vertexCount;
|
|
@@ -363,17 +382,19 @@ function mergeCandidates(candidates: Candidate[]): PrimitiveData {
|
|
|
363
382
|
if (c.attrs.uv) hasUv = true;
|
|
364
383
|
if (c.attrs.uv1) hasUv1 = true;
|
|
365
384
|
if (c.attrs.aAtlasRect) hasAtlas = true;
|
|
385
|
+
if (c.attrs.aTexCoordZ) hasTexZ = true;
|
|
366
386
|
}
|
|
367
387
|
|
|
368
388
|
const mergedPos: PlainAttribute = { array: new Float32Array(totalVertices * 3), components: 3, normalized: false };
|
|
369
389
|
const mergedNorm = hasNormal ? { array: new Float32Array(totalVertices * 3), components: 3, normalized: false } : null;
|
|
370
390
|
const mergedUv = hasUv ? { array: new Float32Array(totalVertices * 2), components: 2, normalized: false } : null;
|
|
371
391
|
const mergedUv1 = hasUv1 ? { array: new Float32Array(totalVertices * 2), components: 2, normalized: false } : null;
|
|
372
|
-
const mergedColor: PlainAttribute = { array: new Float32Array(totalVertices *
|
|
392
|
+
const mergedColor: PlainAttribute = { array: new Float32Array(totalVertices * 4), components: 4, normalized: false };
|
|
373
393
|
const mergedBatchId: PlainAttribute = { array: new Float32Array(totalVertices), components: 1, normalized: false };
|
|
374
394
|
const mergedRect0 = hasAtlas ? { array: new Float32Array(totalVertices * 4), components: 4, normalized: false } : null;
|
|
375
395
|
const mergedRect1 = hasAtlas ? { array: new Float32Array(totalVertices * 4), components: 4, normalized: false } : null;
|
|
376
396
|
const mergedSecUv = hasAtlas ? { array: new Float32Array(totalVertices * 2), components: 2, normalized: false } : null;
|
|
397
|
+
const mergedTexZ = hasTexZ ? { array: new Float32Array(totalVertices), components: 1, normalized: false } : null;
|
|
377
398
|
const mergedIndices = (totalVertices > 65535 ? new Uint32Array(totalIndices) : new Uint16Array(totalIndices)) as Uint16Array | Uint32Array;
|
|
378
399
|
|
|
379
400
|
const tmp = [0, 0, 0];
|
|
@@ -389,6 +410,8 @@ function mergeCandidates(candidates: Candidate[]): PrimitiveData {
|
|
|
389
410
|
const normArr = attrs.normal?.array;
|
|
390
411
|
const uvArr = attrs.uv?.array;
|
|
391
412
|
const uv1Arr = attrs.uv1?.array;
|
|
413
|
+
const tzArr = attrs.aTexCoordZ?.array;
|
|
414
|
+
const tzOut = mergedTexZ?.array as Float32Array | undefined;
|
|
392
415
|
const colArr = attrs.aColor.array;
|
|
393
416
|
const bArr = attrs.aBatchId.array;
|
|
394
417
|
const posOut = mergedPos.array as Float32Array;
|
|
@@ -433,15 +456,19 @@ function mergeCandidates(candidates: Candidate[]): PrimitiveData {
|
|
|
433
456
|
uv1Out[dst * 2] = uv1Arr[i * 2];
|
|
434
457
|
uv1Out[dst * 2 + 1] = uv1Arr[i * 2 + 1];
|
|
435
458
|
}
|
|
436
|
-
//
|
|
459
|
+
// aTexCoordZ(uv 第三分量):缺这条流的候选补 0 = 单位变换
|
|
460
|
+
if (tzOut) tzOut[dst] = tzArr ? tzArr[i]! : 0;
|
|
461
|
+
// aColor(RGBA:alpha 是 4421 这类「材质不透明、顶点半透明」的唯一来源)
|
|
437
462
|
if (colArr) {
|
|
438
|
-
colOut[dst *
|
|
439
|
-
colOut[dst *
|
|
440
|
-
colOut[dst *
|
|
463
|
+
colOut[dst * 4] = colArr[i * 4];
|
|
464
|
+
colOut[dst * 4 + 1] = colArr[i * 4 + 1];
|
|
465
|
+
colOut[dst * 4 + 2] = colArr[i * 4 + 2];
|
|
466
|
+
colOut[dst * 4 + 3] = colArr[i * 4 + 3] ?? 1;
|
|
441
467
|
} else {
|
|
442
|
-
colOut[dst *
|
|
443
|
-
colOut[dst *
|
|
444
|
-
colOut[dst *
|
|
468
|
+
colOut[dst * 4] = 1;
|
|
469
|
+
colOut[dst * 4 + 1] = 1;
|
|
470
|
+
colOut[dst * 4 + 2] = 1;
|
|
471
|
+
colOut[dst * 4 + 3] = 1;
|
|
445
472
|
}
|
|
446
473
|
// aBatchId
|
|
447
474
|
bOut[dst] = bArr ? bArr[i] : -1;
|
|
@@ -501,6 +528,7 @@ function mergeCandidates(candidates: Candidate[]): PrimitiveData {
|
|
|
501
528
|
aAtlasRect: mergedRect0 ?? undefined,
|
|
502
529
|
aAtlasRect1: mergedRect1 ?? undefined,
|
|
503
530
|
aSecUv: mergedSecUv ?? undefined,
|
|
531
|
+
aTexCoordZ: mergedTexZ ?? undefined,
|
|
504
532
|
},
|
|
505
533
|
index: mergedIndices,
|
|
506
534
|
bounds,
|
|
@@ -529,7 +557,7 @@ function assembleSkeleton(name: string, geo: GeoPackageEntry, tile: ParsedTile,
|
|
|
529
557
|
const vertexCount = vp.verticesCount || 0;
|
|
530
558
|
const attrs: PrimitiveAttributes = {
|
|
531
559
|
position: deinterleave(positionAttr),
|
|
532
|
-
aColor: { array: new Float32Array(0), components:
|
|
560
|
+
aColor: { array: new Float32Array(0), components: 4, normalized: false },
|
|
533
561
|
aBatchId: { array: new Float32Array(0), components: 1, normalized: false },
|
|
534
562
|
};
|
|
535
563
|
let colorAttr: VertexAttribute | null = null;
|
|
@@ -537,6 +565,7 @@ function assembleSkeleton(name: string, geo: GeoPackageEntry, tile: ParsedTile,
|
|
|
537
565
|
let normalData: PlainAttribute | null = null;
|
|
538
566
|
let uvData: PlainAttribute | null = null;
|
|
539
567
|
let uv1Data: PlainAttribute | null = null;
|
|
568
|
+
let texCoordZData: PlainAttribute | null = null;
|
|
540
569
|
|
|
541
570
|
for (const attr of vp.vertexAttributes) {
|
|
542
571
|
if (attr.divisor !== undefined) continue; // 实例属性不进顶点流
|
|
@@ -546,15 +575,22 @@ function assembleSkeleton(name: string, geo: GeoPackageEntry, tile: ParsedTile,
|
|
|
546
575
|
else if (attr.semantic === 'normal') normalData = deinterleave(attr);
|
|
547
576
|
else if (attr.semantic === 'uv') uvData = deinterleave(attr);
|
|
548
577
|
else if (attr.semantic === 'uv2') uv1Data = deinterleave(attr);
|
|
578
|
+
// custom0 = uv 的第三分量,官方 aTextureCoordMatrix:决定「这张贴图用不用、
|
|
579
|
+
// 用图集的哪个子区域、按 2 的几次幂缩放」。BIM/OSGB 转出来的数据依赖它,
|
|
580
|
+
// 丢掉就会把本该纯色的墙面贴上整张材质图。语义见 core/TexCoordZ.ts。
|
|
581
|
+
else if (attr.semantic === 'custom0' && (attr.components ?? 1) >= 1) {
|
|
582
|
+
texCoordZData = deinterleave(attr);
|
|
583
|
+
}
|
|
549
584
|
}
|
|
550
585
|
|
|
551
|
-
attrs.aColor = { array: colorData(colorAttr, vertexCount), components:
|
|
586
|
+
attrs.aColor = { array: colorData(colorAttr, vertexCount), components: 4, normalized: false };
|
|
552
587
|
attrs.aBatchId = batchAttr
|
|
553
588
|
? { array: deinterleave(batchAttr).array, components: 1, normalized: false }
|
|
554
589
|
: { array: new Float32Array(vertexCount).fill(-1), components: 1, normalized: false };
|
|
555
590
|
if (normalData) attrs.normal = normalData;
|
|
556
591
|
if (uvData) attrs.uv = uvData;
|
|
557
592
|
if (uv1Data) attrs.uv1 = uv1Data;
|
|
593
|
+
if (texCoordZData) attrs.aTexCoordZ = texCoordZData;
|
|
558
594
|
|
|
559
595
|
// S3MBLOCK 图集:逐顶点带子区域矩形;烘焙图用第二套 UV(aSecUv)
|
|
560
596
|
const rects = tile.atlasRects?.[name];
|
|
@@ -624,6 +660,77 @@ function primitiveOf(
|
|
|
624
660
|
};
|
|
625
661
|
}
|
|
626
662
|
|
|
663
|
+
/**
|
|
664
|
+
* 丢弃「同单体、同位置、同绕向」的重复三角形。
|
|
665
|
+
*
|
|
666
|
+
* 为什么需要:OSGB 转出来的 S3M 里确实存在**模型自重叠**。实测
|
|
667
|
+
* `3D-BIMbuilding-2` 的 `Tile_-85071_248749_0000` 中 geo E2F8F0 有 7156 个三角形,
|
|
668
|
+
* 其中 2276 个与前面某个三角形的三个顶点坐标完全相同(4 位小数,0.1 mm 内),
|
|
669
|
+
* `Tile_-85071_248750_0000` 的 geo 386340 是 58/156 —— 文件级统计,不是我们合批造出来的。
|
|
670
|
+
*
|
|
671
|
+
* 两个共面三角形各自光栅化时深度值只差最后几位,深度缓冲分不出谁在前,
|
|
672
|
+
* 屏幕上就是一片斜向摩尔纹(用户说的「模型重叠的斜线」);若两份材质不同,
|
|
673
|
+
* 还会随绘制顺序闪成另一种颜色。
|
|
674
|
+
*
|
|
675
|
+
* 只丢**同 batchId** 的重复:跨单体的重叠是数据本身两个单体压在一起,
|
|
676
|
+
* 丢掉任何一个都会让那个 SMID 变得不可见 / 不可拾取,只能留给业务层处理。
|
|
677
|
+
*
|
|
678
|
+
* @param quantum 顶点坐标量化步长(米)。1 mm 足以合并「同一条边」的重复,
|
|
679
|
+
* 又不会误伤真正相邻的两个面。
|
|
680
|
+
*/
|
|
681
|
+
export function dropCoincidentTriangles(
|
|
682
|
+
index: Uint16Array | Uint32Array,
|
|
683
|
+
positions: ArrayLike<number>,
|
|
684
|
+
batchIds: ArrayLike<number> | null,
|
|
685
|
+
quantum = 1e-3
|
|
686
|
+
): { index: Uint16Array | Uint32Array; dropped: number } {
|
|
687
|
+
const triCount = Math.floor(index.length / 3);
|
|
688
|
+
if (triCount === 0) return { index, dropped: 0 };
|
|
689
|
+
|
|
690
|
+
const inv = 1 / quantum;
|
|
691
|
+
const keyOf = (v: number): string =>
|
|
692
|
+
`${Math.round(positions[v * 3] * inv)},${Math.round(positions[v * 3 + 1] * inv)},${Math.round(positions[v * 3 + 2] * inv)}`;
|
|
693
|
+
|
|
694
|
+
const seen = new Set<string>();
|
|
695
|
+
const out = new (index instanceof Uint32Array ? Uint32Array : Uint16Array)(index.length);
|
|
696
|
+
let w = 0;
|
|
697
|
+
let dropped = 0;
|
|
698
|
+
for (let t = 0; t < triCount; t++) {
|
|
699
|
+
const a = index[t * 3];
|
|
700
|
+
const b = index[t * 3 + 1];
|
|
701
|
+
const c = index[t * 3 + 2];
|
|
702
|
+
const ka = keyOf(a);
|
|
703
|
+
const kb = keyOf(b);
|
|
704
|
+
const kc = keyOf(c);
|
|
705
|
+
// 把字典序最小的顶点转到首位,**保持绕向**:
|
|
706
|
+
// 绕向相同的两份才是「同一个面画了两遍」;正反两份是有意做的双面片,
|
|
707
|
+
// 它们共面且绕向相反,深度相等时后者覆盖前者、着色一致,不会出摩尔纹。
|
|
708
|
+
let k1 = ka;
|
|
709
|
+
let k2 = kb;
|
|
710
|
+
let k3 = kc;
|
|
711
|
+
if (kb <= ka && kb <= kc) {
|
|
712
|
+
k1 = kb;
|
|
713
|
+
k2 = kc;
|
|
714
|
+
k3 = ka;
|
|
715
|
+
} else if (kc <= ka && kc <= kb) {
|
|
716
|
+
k1 = kc;
|
|
717
|
+
k2 = ka;
|
|
718
|
+
k3 = kb;
|
|
719
|
+
}
|
|
720
|
+
const key = k1 + k2 + k3 + (batchIds ? `#${batchIds[a]}` : '');
|
|
721
|
+
if (seen.has(key)) {
|
|
722
|
+
dropped++;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
seen.add(key);
|
|
726
|
+
out[w++] = a;
|
|
727
|
+
out[w++] = b;
|
|
728
|
+
out[w++] = c;
|
|
729
|
+
}
|
|
730
|
+
if (dropped === 0) return { index, dropped: 0 };
|
|
731
|
+
return { index: out.slice(0, w) as Uint16Array | Uint32Array, dropped };
|
|
732
|
+
}
|
|
733
|
+
|
|
627
734
|
/**
|
|
628
735
|
* 瓦片几何组装入口。与 GeometryBuilder 的摆放/分组逻辑逐位对齐,
|
|
629
736
|
* 但只产出纯数据 —— three 包装在渲染层。
|
|
@@ -741,5 +848,22 @@ export function assembleTileGeometry(tile: ParsedTile, options: AssembleOptions
|
|
|
741
848
|
}
|
|
742
849
|
}
|
|
743
850
|
|
|
851
|
+
// 数据自重叠:同单体、同位置、同绕向的重复三角形会互相压深度,出斜向摩尔纹。
|
|
852
|
+
// 只在**单 geo** 的 primitive 上做:合批产物里多个 geo 的 batchId 各自从 0 起,
|
|
853
|
+
// 混在一起会把「不同单体恰好共面」误判成同一条。
|
|
854
|
+
for (const p of primitives) {
|
|
855
|
+
if (p.primitiveType !== 4 || !p.index) continue;
|
|
856
|
+
if (p.geoNames.length > 1) continue;
|
|
857
|
+
const positions = p.attributes.position?.array;
|
|
858
|
+
if (!positions) continue;
|
|
859
|
+
const batchIds = p.attributes.aBatchId?.array ?? null;
|
|
860
|
+
const { index, dropped } = dropCoincidentTriangles(p.index, positions, batchIds);
|
|
861
|
+
if (dropped > 0) {
|
|
862
|
+
p.index = index;
|
|
863
|
+
p.indexCount = index.length;
|
|
864
|
+
stats.deduped = (stats.deduped ?? 0) + dropped;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
744
868
|
return { primitives, stats };
|
|
745
869
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 降级上报:解析成功、但能力不完整的数据要让调用方看得见
|
|
3
|
+
*
|
|
4
|
+
* 分界:**「解析不了」应该抛错,「解析得了但画不出该有的样子」必须显式说明。**
|
|
5
|
+
* 前者是 fail loudly,后者最容易被写成静默降级 —— 用户看到一个能跑但不对的画面,
|
|
6
|
+
* 会去怀疑数据、怀疑网络、怀疑显卡,很难怀疑到「这个分支还没做完」。
|
|
7
|
+
*
|
|
8
|
+
* 目前唯一一类降级是 3DGS:数据里的 `gsScale` / `gsRotation` 已经保留成顶点属性,
|
|
9
|
+
* 但那 4 字节的量化格式没有标定,所以只能按 Points 渲染,画不出 EWA splatting。
|
|
10
|
+
* 详见 `docs/FORMAT-NOTES.md` 第九节。
|
|
11
|
+
*
|
|
12
|
+
* 策略:
|
|
13
|
+
* 'warn'(默认) —— 首次遇到告警一次,画照画。不传参时与旧版行为一致(旧版不抛)。
|
|
14
|
+
* 'throw' —— 直接拒绝这块瓦片,标 `permanent` 让调度器不要重试。
|
|
15
|
+
* 给「宁可不出图,也不要出一张错的图」的场景用。
|
|
16
|
+
* 'silent' —— 只记录到 `stats.degradations`,不告警。给已经自己接管提示的 UI 用。
|
|
17
|
+
*/
|
|
18
|
+
export type DegradationKind = 'gaussian-splatting-points';
|
|
19
|
+
|
|
20
|
+
export type UnsupportedPolicy = 'warn' | 'throw' | 'silent';
|
|
21
|
+
|
|
22
|
+
/** 告警文案要自己解释清楚「看到了什么」和「为什么不对」,不要只说一句 "unsupported" */
|
|
23
|
+
const MESSAGES: Record<DegradationKind, string> = {
|
|
24
|
+
'gaussian-splatting-points':
|
|
25
|
+
'[s3m] 数据声明是 3DGS(高斯泼溅),但骨架里没有 gsScale / gsRotation 这两条流,' +
|
|
26
|
+
'只能按点云渲染。这不是数据损坏,但也不是预期的泼溅效果 —— ' +
|
|
27
|
+
'若确认是导出配置问题,可以 unsupported: "throw" 让它直接报错。',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export interface DegradationPlan {
|
|
31
|
+
/** 本次新出现的降级项(调用方据此告警) */
|
|
32
|
+
fresh: DegradationKind[];
|
|
33
|
+
/** 合并后的已知清单,写回 `stats.degradations` */
|
|
34
|
+
known: DegradationKind[];
|
|
35
|
+
/** 需要输出的告警文案(`silent` 时为空) */
|
|
36
|
+
messages: string[];
|
|
37
|
+
/** `throw` 策略下要抛的错(已标 `permanent`,调度器不要重试) */
|
|
38
|
+
error?: Error;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 纯函数:给定「本次遇到的降级项」和「已经报过的」,算出要做什么。
|
|
43
|
+
*
|
|
44
|
+
* 去重是必须的:一个 3DGS 服务一上来几十块瓦片都是同一个原因,
|
|
45
|
+
* 不去重就是几十条一模一样的告警,把真正有用的日志冲掉。
|
|
46
|
+
*/
|
|
47
|
+
export function planDegradations(
|
|
48
|
+
incoming: readonly DegradationKind[],
|
|
49
|
+
known: readonly DegradationKind[],
|
|
50
|
+
policy: UnsupportedPolicy
|
|
51
|
+
): DegradationPlan {
|
|
52
|
+
const knownSet = new Set(known);
|
|
53
|
+
const fresh: DegradationKind[] = [];
|
|
54
|
+
for (const kind of incoming) {
|
|
55
|
+
if (!knownSet.has(kind)) {
|
|
56
|
+
knownSet.add(kind);
|
|
57
|
+
fresh.push(kind);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const messages = policy === 'warn' ? fresh.map((k) => MESSAGES[k] ?? `[s3m] 降级:${k}`) : [];
|
|
62
|
+
|
|
63
|
+
if (policy === 'throw' && fresh.length > 0) {
|
|
64
|
+
const err = new Error(
|
|
65
|
+
`[s3m] 数据包含当前实现不完整的特性,已按 unsupported: "throw" 拒绝:${fresh.join(', ')}。` +
|
|
66
|
+
`详见 docs/FORMAT-NOTES.md`
|
|
67
|
+
) as Error & { permanent?: boolean };
|
|
68
|
+
// 格式层面的能力缺失,重试一万次也不会变好
|
|
69
|
+
err.permanent = true;
|
|
70
|
+
return { fresh, known: [...knownSet], messages, error: err };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { fresh, known: [...knownSet], messages };
|
|
74
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 这里只放「跨模块共享」的结构定义。模块内部的私有类型各自就近声明。
|
|
5
5
|
*/
|
|
6
6
|
import type { Color } from 'three';
|
|
7
|
+
import type { DegradationKind } from './degradations.js';
|
|
7
8
|
|
|
8
9
|
/* ------------------------------------------------------------------ 数学 */
|
|
9
10
|
|
|
@@ -200,6 +201,14 @@ export interface ParsedTile {
|
|
|
200
201
|
materials: unknown;
|
|
201
202
|
pickInfo: unknown;
|
|
202
203
|
extensions?: unknown;
|
|
204
|
+
/**
|
|
205
|
+
* 解析成功、但渲染能力不完整的项(机器可读)。
|
|
206
|
+
*
|
|
207
|
+
* 空 / 不传 = 这份数据的每一项能力都实现了。目前只会由 3DGS 数据填上
|
|
208
|
+
* `'gaussian-splatting-points'` —— 那种数据能加载、能看出轮廓,但画不出泼溅。
|
|
209
|
+
* 处置策略见 `core/degradations.ts` 与 `S3MTilesetOptions.unsupported`。
|
|
210
|
+
*/
|
|
211
|
+
degradations?: DegradationKind[];
|
|
203
212
|
/** 实际消费的字节数,应与 bytesTotal 相等 */
|
|
204
213
|
bytesRead: number;
|
|
205
214
|
bytesTotal: number;
|
|
@@ -230,6 +239,20 @@ export interface Stats {
|
|
|
230
239
|
textures: number;
|
|
231
240
|
/** 显存里驻留的几何数(renderer.info.memory.geometries) */
|
|
232
241
|
geometries: number;
|
|
242
|
+
/**
|
|
243
|
+
* 泼溅瓦片里高斯最长轴的中位长度(米),用来估「屏幕上一个高斯多大」。
|
|
244
|
+
* 非泼溅数据为 undefined;顶点统计接口也会带上这个可选字段。
|
|
245
|
+
*/
|
|
246
|
+
splatSigma?: number;
|
|
247
|
+
/** 泼溅瓦片里的高斯实例数;非泼溅数据为 undefined */
|
|
248
|
+
splatCount?: number;
|
|
249
|
+
/**
|
|
250
|
+
* 当前场景里出现过的降级项(去重)。空数组 = 所有数据的能力都完整实现了。
|
|
251
|
+
*
|
|
252
|
+
* 这是给 UI 用的:状态栏 / 诊断面板可以直接读它,不必去翻 console。
|
|
253
|
+
* 语义与 `ParsedTile.degradations` 一致,只是从「单块瓦片」聚到「整个场景」。
|
|
254
|
+
*/
|
|
255
|
+
degradations: DegradationKind[];
|
|
233
256
|
}
|
|
234
257
|
|
|
235
258
|
/* ------------------------------------------------------------------ 单体属性 */
|
|
@@ -291,6 +314,15 @@ export interface StyleUniforms {
|
|
|
291
314
|
uTexMix?: { value: number };
|
|
292
315
|
/** 基础漫反射底色增益 / 直通自发光微调(0~1,默认 0.0) */
|
|
293
316
|
uAmbientGain?: { value: number };
|
|
317
|
+
/**
|
|
318
|
+
* 金属度覆盖(0~1,**负数 = 不覆盖**,保持材质自身的值)。
|
|
319
|
+
*
|
|
320
|
+
* PBR 材质才有意义:注入在 `metalnessmap_fragment` 之后直接改写 metalnessFactor,
|
|
321
|
+
* 不重建材质也就不会重编译 program。lambert 光照模型没有这个 chunk,会被忽略。
|
|
322
|
+
*/
|
|
323
|
+
uMetalness?: { value: number };
|
|
324
|
+
/** 粗糙度覆盖(0~1,负数 = 不覆盖)。同上,只有 pbr 生效。 */
|
|
325
|
+
uRoughness?: { value: number };
|
|
294
326
|
/** 双纹理交叉淡化插值因子(0~1,0 = 状态 A,1 = 状态 B) */
|
|
295
327
|
uBatchMix?: { value: number };
|
|
296
328
|
/** 图层整体透明度(共享 uniform,透明度动画靠它做到 O(1)) */
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ export { parseS3MB, S3MFormatError, S3M_VERSION, VertexTag, VertexTagV3 } from '
|
|
|
14
14
|
export { parseS3M1, isS3M1Buffer } from './core/S3M1Parser.js';
|
|
15
15
|
export type { ParseOptions, InflateFn } from './core/S3MBParser.js';
|
|
16
16
|
export { BinaryReader } from './core/BinaryReader.js';
|
|
17
|
+
export { planDegradations } from './core/degradations.js';
|
|
18
|
+
export type { DegradationKind, UnsupportedPolicy, DegradationPlan } from './core/degradations.js';
|
|
17
19
|
export { resolveFeature, measureSegments, buildAttributes, DEFAULT_FLOOR_HEIGHT } from './core/FeatureInfo.js';
|
|
18
20
|
export { AttributeQuery } from './core/AttributeQuery.js';
|
|
19
21
|
export type {
|
|
@@ -110,10 +112,15 @@ export type {
|
|
|
110
112
|
PathFollowOptions,
|
|
111
113
|
} from './render/S3MViewer.js';
|
|
112
114
|
|
|
115
|
+
export { LIGHT_PRESETS, DEFAULT_LIGHT_PRESET, SUPERMAP_LIGHT_PRESET, resolveLightingPreset } from './render/lightingPreset.js';
|
|
116
|
+
export type { LightingPreset, PresetLightIntensities } from './render/lightingPreset.js';
|
|
117
|
+
|
|
113
118
|
export { buildTileObjects } from './render/GeometryBuilder.js';
|
|
114
119
|
export type { BuildTileOptions, BuildTileResult, TileBuildStats } from './render/GeometryBuilder.js';
|
|
115
120
|
|
|
116
|
-
export { createTexture, PixelFormat, setMaxAnisotropy, setMaxTextureSize } from './render/TextureFactory.js';
|
|
121
|
+
export { createTexture, PixelFormat, setMaxAnisotropy, setMaxTextureSize, setSrgbTextureDecode } from './render/TextureFactory.js';
|
|
122
|
+
export { setColorPipeline, getColorPipeline } from './render/StyleMaterial.js';
|
|
123
|
+
export type { ColorPipeline } from './render/StyleMaterial.js';
|
|
117
124
|
export type { DecodedTexture } from './render/TextureFactory.js';
|
|
118
125
|
|
|
119
126
|
export { resolveTextureKind, dxtPixelFormatOf, TextureCompress } from './core/TextureFormat.js';
|
|
@@ -11,6 +11,7 @@ import * as THREE from 'three';
|
|
|
11
11
|
import { S3MTileset, usableGeoBounds } from '../tileset/S3MTileset.js';
|
|
12
12
|
import type { AuthenticateFn } from '../core/AuthFetch.js';
|
|
13
13
|
import type { Anchor } from '../core/types.js';
|
|
14
|
+
import type { UnsupportedPolicy } from '../core/degradations.js';
|
|
14
15
|
import { worldFromLngLat, type GeoAdapter } from './GeoAdapter.js';
|
|
15
16
|
|
|
16
17
|
export type DatasetAlignment = 'origin' | 'bottom' | 'top' | 'center';
|
|
@@ -38,6 +39,8 @@ export interface S3MDatasetOptions {
|
|
|
38
39
|
heightOffset?: number;
|
|
39
40
|
/** 是否用 Web Worker 解析经典 .s3mb 瓦片(默认开,崩溃自动降级主线程),见 S3MTilesetOptions.useWorker */
|
|
40
41
|
useWorker?: boolean;
|
|
42
|
+
/** 三角面材质深度偏移(共面摩尔纹 / 重合面归属),见 BuildTileOptions.polygonOffset */
|
|
43
|
+
polygonOffset?: { factor: number; units: number };
|
|
41
44
|
/**
|
|
42
45
|
* 是否对该数据集生成几何棱线(CPU 端 EdgesGeometry)。
|
|
43
46
|
* - 不填:跟随全局 `style.edges`
|
|
@@ -49,6 +52,11 @@ export interface S3MDatasetOptions {
|
|
|
49
52
|
* 适合多数据集走不同网关 / 不同 token 的场景。见 `AuthenticateFn`。
|
|
50
53
|
*/
|
|
51
54
|
authenticate?: AuthenticateFn;
|
|
55
|
+
/**
|
|
56
|
+
* 本数据集遇到「解析得了但画不出该有的样子」时怎么办(默认 `'warn'`)。
|
|
57
|
+
* 目前只有 3DGS:按点渲染,做不出泼溅。见 `S3MTilesetOptions.unsupported`。
|
|
58
|
+
*/
|
|
59
|
+
unsupported?: UnsupportedPolicy;
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
export interface S3MDatasetEntry {
|
|
@@ -211,8 +219,10 @@ export class DatasetManager {
|
|
|
211
219
|
maxImageCache: ds.maxImageCache ?? maxImageCache,
|
|
212
220
|
lodRangeScale: ds.lodRangeScale ?? defaultLodRangeScale,
|
|
213
221
|
buildTile: buildTile ?? null,
|
|
222
|
+
polygonOffset: ds.polygonOffset,
|
|
214
223
|
useWorker: ds.useWorker,
|
|
215
224
|
authenticate: ds.authenticate ?? authenticate,
|
|
225
|
+
unsupported: ds.unsupported,
|
|
216
226
|
uniforms: uniforms,
|
|
217
227
|
onTile: (node) => {
|
|
218
228
|
callbacks.onTile(node, entry);
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from '../core/FeatureInfo.js';
|
|
21
21
|
import type { AttributeQuery, AttributeQueryOptions, AttributeRow } from '../core/AttributeQuery.js';
|
|
22
22
|
import type { StyledMaterial } from './StyleMaterial.js';
|
|
23
|
+
import { restoreBaseBlendState } from './StyleMaterial.js';
|
|
23
24
|
|
|
24
25
|
export interface GPUPathOptions {
|
|
25
26
|
/** 绑定的路径索引 ID(0, 1, 2...) */
|
|
@@ -1011,8 +1012,9 @@ export class FeatureStyleManager {
|
|
|
1011
1012
|
if (mat.userData.uBatchTexActive) {
|
|
1012
1013
|
mat.userData.uBatchTexActive.value = 0;
|
|
1013
1014
|
}
|
|
1014
|
-
// 单体淡化清除后,把材质 transparent
|
|
1015
|
-
|
|
1015
|
+
// 单体淡化清除后,把材质 transparent 还原成「自身语义 || 全局透明度」
|
|
1016
|
+
// (只看全局透明度会把 alphaMode blend / 顶点 alpha 材质误关成不透明)
|
|
1017
|
+
if (mat.transparent && !this._isLayerTransparent()) restoreBaseBlendState(mat, false);
|
|
1016
1018
|
return;
|
|
1017
1019
|
}
|
|
1018
1020
|
|
|
@@ -1020,7 +1022,7 @@ export class FeatureStyleManager {
|
|
|
1020
1022
|
if (mat.userData.uBatchTexActive) {
|
|
1021
1023
|
mat.userData.uBatchTexActive.value = 0;
|
|
1022
1024
|
}
|
|
1023
|
-
if (mat.transparent && !this._isLayerTransparent()) mat
|
|
1025
|
+
if (mat.transparent && !this._isLayerTransparent()) restoreBaseBlendState(mat, false);
|
|
1024
1026
|
return;
|
|
1025
1027
|
}
|
|
1026
1028
|
|
|
@@ -1052,9 +1054,9 @@ export class FeatureStyleManager {
|
|
|
1052
1054
|
this.featureStyles
|
|
1053
1055
|
);
|
|
1054
1056
|
|
|
1055
|
-
//
|
|
1057
|
+
// 有淡化单体时整块材质要开混合才有透明效果;清除后还原为「自身语义 || 全局透明度」
|
|
1056
1058
|
if (hasFade && !mat.transparent) mat.transparent = true;
|
|
1057
|
-
else if (!hasFade && mat.transparent && !this._isLayerTransparent()) mat
|
|
1059
|
+
else if (!hasFade && mat.transparent && !this._isLayerTransparent()) restoreBaseBlendState(mat, false);
|
|
1058
1060
|
|
|
1059
1061
|
if (conflicts > 0 && !this._batchConflictWarned) {
|
|
1060
1062
|
this._batchConflictWarned = true;
|