s3m-three 0.4.18 → 0.4.20

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.
Files changed (169) hide show
  1. package/dist/browser/assets/s3mWorker-DvAUA8NJ.js +1 -0
  2. package/dist/browser/s3m-three.iife.ext-three.js +619 -304
  3. package/dist/browser/s3m-three.iife.js +647 -332
  4. package/dist/core/Draco.d.ts.map +1 -1
  5. package/dist/core/Draco.js +0 -1
  6. package/dist/core/Draco.js.map +1 -1
  7. package/dist/core/GaussianSplat.d.ts +64 -0
  8. package/dist/core/GaussianSplat.d.ts.map +1 -0
  9. package/dist/core/GaussianSplat.js +139 -0
  10. package/dist/core/GaussianSplat.js.map +1 -0
  11. package/dist/core/S3M1Parser.d.ts +1 -1
  12. package/dist/core/S3M1Parser.d.ts.map +1 -1
  13. package/dist/core/S3M1Parser.js +320 -25
  14. package/dist/core/S3M1Parser.js.map +1 -1
  15. package/dist/core/S3MBParser.d.ts.map +1 -1
  16. package/dist/core/S3MBParser.js +13 -0
  17. package/dist/core/S3MBParser.js.map +1 -1
  18. package/dist/core/S3MBlockParser.d.ts.map +1 -1
  19. package/dist/core/S3MBlockParser.js +83 -28
  20. package/dist/core/S3MBlockParser.js.map +1 -1
  21. package/dist/core/SplatLod.d.ts +36 -0
  22. package/dist/core/SplatLod.d.ts.map +1 -0
  23. package/dist/core/SplatLod.js +54 -0
  24. package/dist/core/SplatLod.js.map +1 -0
  25. package/dist/core/SplatProjection.d.ts +75 -0
  26. package/dist/core/SplatProjection.d.ts.map +1 -0
  27. package/dist/core/SplatProjection.js +162 -0
  28. package/dist/core/SplatProjection.js.map +1 -0
  29. package/dist/core/SplatSort.d.ts +26 -0
  30. package/dist/core/SplatSort.d.ts.map +1 -0
  31. package/dist/core/SplatSort.js +61 -0
  32. package/dist/core/SplatSort.js.map +1 -0
  33. package/dist/core/TexCoordZ.d.ts +94 -0
  34. package/dist/core/TexCoordZ.d.ts.map +1 -0
  35. package/dist/core/TexCoordZ.js +125 -0
  36. package/dist/core/TexCoordZ.js.map +1 -0
  37. package/dist/core/TextureDecode.d.ts +19 -0
  38. package/dist/core/TextureDecode.d.ts.map +1 -1
  39. package/dist/core/TextureDecode.js +53 -11
  40. package/dist/core/TextureDecode.js.map +1 -1
  41. package/dist/core/TileGeometry.d.ts +39 -0
  42. package/dist/core/TileGeometry.d.ts.map +1 -1
  43. package/dist/core/TileGeometry.js +133 -14
  44. package/dist/core/TileGeometry.js.map +1 -1
  45. package/dist/core/degradations.d.ts +37 -0
  46. package/dist/core/degradations.d.ts.map +1 -0
  47. package/dist/core/degradations.js +32 -0
  48. package/dist/core/degradations.js.map +1 -0
  49. package/dist/core/types.d.ts +36 -0
  50. package/dist/core/types.d.ts.map +1 -1
  51. package/dist/index.d.ts +7 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +4 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/render/DatasetManager.d.ts +11 -0
  56. package/dist/render/DatasetManager.d.ts.map +1 -1
  57. package/dist/render/DatasetManager.js +2 -0
  58. package/dist/render/DatasetManager.js.map +1 -1
  59. package/dist/render/FeatureStyleManager.d.ts.map +1 -1
  60. package/dist/render/FeatureStyleManager.js +7 -5
  61. package/dist/render/FeatureStyleManager.js.map +1 -1
  62. package/dist/render/GeometryBuilder.d.ts +31 -0
  63. package/dist/render/GeometryBuilder.d.ts.map +1 -1
  64. package/dist/render/GeometryBuilder.js +229 -6
  65. package/dist/render/GeometryBuilder.js.map +1 -1
  66. package/dist/render/Picker.d.ts.map +1 -1
  67. package/dist/render/Picker.js +19 -5
  68. package/dist/render/Picker.js.map +1 -1
  69. package/dist/render/S3MViewer.d.ts +106 -379
  70. package/dist/render/S3MViewer.d.ts.map +1 -1
  71. package/dist/render/S3MViewer.js +324 -1494
  72. package/dist/render/S3MViewer.js.map +1 -1
  73. package/dist/render/SplatMaterial.d.ts +103 -0
  74. package/dist/render/SplatMaterial.d.ts.map +1 -0
  75. package/dist/render/SplatMaterial.js +388 -0
  76. package/dist/render/SplatMaterial.js.map +1 -0
  77. package/dist/render/SplatSorter.d.ts +14 -0
  78. package/dist/render/SplatSorter.d.ts.map +1 -0
  79. package/dist/render/SplatSorter.js +66 -0
  80. package/dist/render/SplatSorter.js.map +1 -0
  81. package/dist/render/StyleMaterial.d.ts +48 -0
  82. package/dist/render/StyleMaterial.d.ts.map +1 -1
  83. package/dist/render/StyleMaterial.js +292 -69
  84. package/dist/render/StyleMaterial.js.map +1 -1
  85. package/dist/render/TextureFactory.d.ts +10 -0
  86. package/dist/render/TextureFactory.d.ts.map +1 -1
  87. package/dist/render/TextureFactory.js +35 -5
  88. package/dist/render/TextureFactory.js.map +1 -1
  89. package/dist/render/lightingPreset.d.ts +53 -0
  90. package/dist/render/lightingPreset.d.ts.map +1 -0
  91. package/dist/render/lightingPreset.js +36 -0
  92. package/dist/render/lightingPreset.js.map +1 -0
  93. package/dist/render/materialAlpha.d.ts +51 -0
  94. package/dist/render/materialAlpha.d.ts.map +1 -0
  95. package/dist/render/materialAlpha.js +68 -0
  96. package/dist/render/materialAlpha.js.map +1 -0
  97. package/dist/render/viewer/EdgeDecorator.d.ts +206 -0
  98. package/dist/render/viewer/EdgeDecorator.d.ts.map +1 -0
  99. package/dist/render/viewer/EdgeDecorator.js +628 -0
  100. package/dist/render/viewer/EdgeDecorator.js.map +1 -0
  101. package/dist/render/viewer/HeightIndex.d.ts +36 -0
  102. package/dist/render/viewer/HeightIndex.d.ts.map +1 -0
  103. package/dist/render/viewer/HeightIndex.js +98 -0
  104. package/dist/render/viewer/HeightIndex.js.map +1 -0
  105. package/dist/render/viewer/LightRig.d.ts +88 -0
  106. package/dist/render/viewer/LightRig.d.ts.map +1 -0
  107. package/dist/render/viewer/LightRig.js +229 -0
  108. package/dist/render/viewer/LightRig.js.map +1 -0
  109. package/dist/render/viewer/PickService.d.ts +109 -0
  110. package/dist/render/viewer/PickService.d.ts.map +1 -0
  111. package/dist/render/viewer/PickService.js +591 -0
  112. package/dist/render/viewer/PickService.js.map +1 -0
  113. package/dist/render/viewer/SelectionDecorator.d.ts +84 -0
  114. package/dist/render/viewer/SelectionDecorator.d.ts.map +1 -0
  115. package/dist/render/viewer/SelectionDecorator.js +339 -0
  116. package/dist/render/viewer/SelectionDecorator.js.map +1 -0
  117. package/dist/render/viewer/SplatLayer.d.ts +26 -0
  118. package/dist/render/viewer/SplatLayer.d.ts.map +1 -0
  119. package/dist/render/viewer/SplatLayer.js +44 -0
  120. package/dist/render/viewer/SplatLayer.js.map +1 -0
  121. package/dist/render/viewer/types.d.ts +250 -0
  122. package/dist/render/viewer/types.d.ts.map +1 -0
  123. package/dist/render/viewer/types.js +3 -0
  124. package/dist/render/viewer/types.js.map +1 -0
  125. package/dist/tileset/S3MTileset.d.ts +42 -1
  126. package/dist/tileset/S3MTileset.d.ts.map +1 -1
  127. package/dist/tileset/S3MTileset.js +52 -2
  128. package/dist/tileset/S3MTileset.js.map +1 -1
  129. package/dist/vendor/browser-node-stub.js +10 -0
  130. package/dist/worker/s3mWorker.js +1166 -979
  131. package/dist/worker/s3mWorker.js.map +1 -1
  132. package/docs/API.md +55 -1
  133. package/docs/FORMAT-NOTES.md +362 -0
  134. package/package.json +1 -1
  135. package/src/core/Draco.ts +1 -2
  136. package/src/core/GaussianSplat.ts +175 -0
  137. package/src/core/S3M1Parser.ts +330 -24
  138. package/src/core/S3MBParser.ts +18 -0
  139. package/src/core/S3MBlockParser.ts +83 -27
  140. package/src/core/SplatLod.ts +83 -0
  141. package/src/core/SplatProjection.ts +231 -0
  142. package/src/core/SplatSort.ts +92 -0
  143. package/src/core/TexCoordZ.ts +160 -0
  144. package/src/core/TextureDecode.ts +63 -11
  145. package/src/core/TileGeometry.ts +151 -14
  146. package/src/core/degradations.ts +74 -0
  147. package/src/core/types.ts +32 -0
  148. package/src/index.ts +8 -1
  149. package/src/render/DatasetManager.ts +10 -0
  150. package/src/render/FeatureStyleManager.ts +8 -6
  151. package/src/render/GeometryBuilder.ts +271 -7
  152. package/src/render/Picker.ts +21 -5
  153. package/src/render/S3MViewer.ts +358 -1727
  154. package/src/render/SplatMaterial.ts +433 -0
  155. package/src/render/SplatSorter.ts +66 -0
  156. package/src/render/StyleMaterial.ts +339 -69
  157. package/src/render/TextureFactory.ts +39 -5
  158. package/src/render/lightingPreset.ts +76 -0
  159. package/src/render/materialAlpha.ts +75 -0
  160. package/src/render/viewer/EdgeDecorator.ts +722 -0
  161. package/src/render/viewer/HeightIndex.ts +94 -0
  162. package/src/render/viewer/LightRig.ts +267 -0
  163. package/src/render/viewer/PickService.ts +651 -0
  164. package/src/render/viewer/SelectionDecorator.ts +377 -0
  165. package/src/render/viewer/SplatLayer.ts +44 -0
  166. package/src/render/viewer/types.ts +258 -0
  167. package/src/tileset/S3MTileset.ts +78 -2
  168. package/src/vendor/browser-node-stub.js +10 -0
  169. package/dist/browser/assets/s3mWorker-BjPOUgjg.js +0 -1
@@ -14,13 +14,18 @@
14
14
  import * as THREE from 'three';
15
15
  import { createTexture } from './TextureFactory.js';
16
16
  import { createSharedUniforms, createStyledMaterial } from './StyleMaterial.js';
17
+ import { isTransparentMaterial, minVertexAlpha } from './materialAlpha.js';
17
18
  import type { StyledMaterial } from './StyleMaterial.js';
18
19
  import { assembleTileGeometry, LINE_PRIMITIVE, POINT_PRIMITIVE } from '../core/TileGeometry.js';
19
20
  import type { TileGeometryData, PrimitiveData } from '../core/TileGeometry.js';
21
+ import { decodeScale, decodeRotation } from '../core/GaussianSplat.js';
22
+ import { createSplatMaterial, createQuadGeometry, splatRaycast } from './SplatMaterial.js';
23
+ import { SPLAT_SIGMA } from '../core/SplatProjection.js';
20
24
  import type {
21
25
  GeoPackageEntry,
22
26
  ParsedTile,
23
27
  StyleUniforms,
28
+ VertexPackage,
24
29
  } from '../core/types.js';
25
30
 
26
31
  // 组装层下沉 core 后的对外 API 兼容:常量原住本文件
@@ -35,6 +40,146 @@ function isPointPrimitive(primitiveType: number): boolean {
35
40
  return primitiveType === POINT_PRIMITIVE;
36
41
  }
37
42
 
43
+ /* ------------------------------------------------------------ 高斯泼溅 */
44
+
45
+ let splatMaterial: THREE.ShaderMaterial | null = null;
46
+
47
+ /** 泼溅材质全瓦片共享:同一套 program / uniform,不为每块瓦片单独编译 */
48
+ function getSplatMaterial(): THREE.ShaderMaterial {
49
+ splatMaterial ??= createSplatMaterial();
50
+ return splatMaterial;
51
+ }
52
+
53
+ /**
54
+ * 每帧同步绘图缓冲尺寸:顶点着色器的最小像素半径 / 抗锯齿低通都要拿它
55
+ * 把 ndc 换算成像素。还没建过泼溅材质时什么都不做(不为了设置而创建)。
56
+ */
57
+ export function setSplatViewport(width: number, height: number): void {
58
+ if (splatMaterial) (splatMaterial.uniforms.uViewport!.value as THREE.Vector2).set(width, height);
59
+ }
60
+
61
+ /**
62
+ * 点云包 → 实例化四边形(每个高斯一片)。
63
+ *
64
+ * 返回 `null` 表示这不是泼溅数据(缺 gsScale / gsRotation),调用方退回 `THREE.Points`。
65
+ * 这样「有泼溅参数就画泼溅、没有就当点云」的分流只由数据决定,不靠外部开关。
66
+ */
67
+ function buildSplatGeometry(
68
+ prim: PrimitiveData,
69
+ vp: VertexPackage
70
+ ): { geo: THREE.InstancedBufferGeometry; sigma: number } | null {
71
+ const scaleAttr = vp.vertexAttributes.find((a) => a.semantic === 'gsScale');
72
+ const rotAttr = vp.vertexAttributes.find((a) => a.semantic === 'gsRotation');
73
+ const posAttr = prim.attributes.position;
74
+ if (!scaleAttr || !rotAttr || !posAttr) return null;
75
+
76
+ // 泼溅参数是逐骨架的,合批不会发生在点云包上;取两者较小值兜住异常数据,
77
+ // 宁可少画几个高斯,也不要读到越界内存
78
+ const count = Math.min(
79
+ vp.verticesCount,
80
+ Math.floor(posAttr.array.length / posAttr.components)
81
+ );
82
+ if (count <= 0) return null;
83
+
84
+ const scales = decodeScale(scaleAttr.data, scaleAttr.stride, scaleAttr.offsetInBytes, count);
85
+ const quats = decodeRotation(rotAttr.data, rotAttr.stride, rotAttr.offsetInBytes, count);
86
+
87
+ // 代表性半轴:抽样的最长轴中位数(米)。LOD 判据要拿它估「屏幕上高斯有多大」,
88
+ // 见 core/SplatLod.ts。抽样上限 512,解码已经付过代价,这里不再逐点扫。
89
+ const sigma = medianLongAxis(scales, count);
90
+
91
+ const geo = createQuadGeometry();
92
+ geo.instanceCount = count;
93
+ geo.setAttribute(
94
+ 'iCenter',
95
+ new THREE.InstancedBufferAttribute(posAttr.array as Float32Array, 3)
96
+ );
97
+ geo.setAttribute('iScale', new THREE.InstancedBufferAttribute(scales, 3));
98
+ geo.setAttribute('iQuat', new THREE.InstancedBufferAttribute(quats, 4));
99
+
100
+ // 颜色优先取 vp 的原始 color 流(u8×4,含 alpha)。
101
+ // prim.attributes.aColor 现在也是 RGBA,但点云这条路径本来就要原始字节
102
+ // (避免多一轮 float 转换),保持原样即可。
103
+ const rgba = new Float32Array(count * 4);
104
+ rgba.fill(1);
105
+ const colorAttr = vp.vertexAttributes.find((a) => a.semantic === 'color');
106
+ if (colorAttr && colorAttr.dtype === 'u8') {
107
+ const comp = colorAttr.components ?? 4;
108
+ // 异常包里 color 流可能比顶点数短:读不到的点保持默认白,不越界
109
+ const n = Math.min(count, Math.floor(colorAttr.data.length / colorAttr.stride));
110
+ for (let i = 0; i < n; i++) {
111
+ const o = i * colorAttr.stride + colorAttr.offsetInBytes;
112
+ rgba[i * 4] = colorAttr.data[o]! / 255;
113
+ rgba[i * 4 + 1] = colorAttr.data[o + 1]! / 255;
114
+ rgba[i * 4 + 2] = colorAttr.data[o + 2]! / 255;
115
+ if (comp >= 4) rgba[i * 4 + 3] = colorAttr.data[o + 3]! / 255;
116
+ }
117
+ } else if (prim.attributes.aColor) {
118
+ // 兜底:只有组装后的 RGB(0~1 float),alpha 补 1
119
+ const src = prim.attributes.aColor;
120
+ const comp = src.components ?? 4;
121
+ for (let i = 0; i < count; i++) {
122
+ for (let k = 0; k < Math.min(comp, 4); k++) rgba[i * 4 + k] = src.array[i * comp + k]!;
123
+ }
124
+ }
125
+ geo.setAttribute('iColor', new THREE.InstancedBufferAttribute(rgba, 4));
126
+
127
+ // 真实包围球:拾取要先用它做「射线有没有碰到这一块」的快速排除。
128
+ // createQuadGeometry 里给的是空球(渲染靠 frustumCulled = false,不看它),
129
+ // 留成 Infinity 的话每点一次要把所有瓦片的每个高斯都算一遍。
130
+ //
131
+ // 包围盒和最长轴在同一遍里扫完:10 万点的瓦片,多一遍就是几毫秒。
132
+ const pos = posAttr.array;
133
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
134
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
135
+ let maxAxis = 0;
136
+ for (let i = 0; i < count; i++) {
137
+ const x = pos[i * 3]!;
138
+ const y = pos[i * 3 + 1]!;
139
+ const z = pos[i * 3 + 2]!;
140
+ if (x < minX) minX = x;
141
+ if (x > maxX) maxX = x;
142
+ if (y < minY) minY = y;
143
+ if (y > maxY) maxY = y;
144
+ if (z < minZ) minZ = z;
145
+ if (z > maxZ) maxZ = z;
146
+ const sx = scales[i * 3]!;
147
+ const sy = scales[i * 3 + 1]!;
148
+ const sz = scales[i * 3 + 2]!;
149
+ if (sx > maxAxis) maxAxis = sx;
150
+ if (sy > maxAxis) maxAxis = sy;
151
+ if (sz > maxAxis) maxAxis = sz;
152
+ }
153
+ const box = new THREE.Box3();
154
+ if (count > 0) {
155
+ box.min.set(minX, minY, minZ);
156
+ box.max.set(maxX, maxY, maxZ);
157
+ }
158
+ const sphere = new THREE.Sphere(new THREE.Vector3(), 0);
159
+ if (!box.isEmpty()) box.getBoundingSphere(sphere);
160
+ sphere.radius += SPLAT_SIGMA * maxAxis;
161
+ geo.boundingSphere = sphere;
162
+
163
+ return { geo, sigma };
164
+ }
165
+
166
+ /**
167
+ * 最长轴的中位长度(米)。抽样上限 512 个,够稳且不拖慢瓦片构建。
168
+ * 全部非正 / 非有限值时返回 0,调用方据此退回通用 LOD 判据。
169
+ */
170
+ function medianLongAxis(scales: Float32Array, count: number): number {
171
+ const step = Math.max(1, Math.floor(count / 512));
172
+ const xs: number[] = [];
173
+ for (let i = 0; i < count; i += step) {
174
+ const m = Math.max(scales[i * 3]!, scales[i * 3 + 1]!, scales[i * 3 + 2]!);
175
+ if (Number.isFinite(m) && m > 0) xs.push(m);
176
+ }
177
+ if (!xs.length) return 0;
178
+ xs.sort((a, b) => a - b);
179
+ return xs[xs.length >> 1]!;
180
+ }
181
+
182
+
38
183
  /* ------------------------------------------------------------------ 图集 UV */
39
184
 
40
185
  /**
@@ -79,6 +224,18 @@ function getPointCloudMaterial(): THREE.PointsMaterial {
79
224
  /** SuperMap 内置水面(`103.water`)的基色:偏深的青蓝,配合低粗糙度出一点天光反射 */
80
225
  const WATER_COLOR = new THREE.Color(0x1d4e63);
81
226
 
227
+ /**
228
+ * 棱线 / 高斯点云的绘制顺序基准。
229
+ *
230
+ * 三角面网格的 renderOrder 被钉成「文件里的材质序号」(见 materialIndexOf),
231
+ * 是个从 0 开始的小整数;这两个覆盖层必须排在所有材质之后,
232
+ * 否则会被某块瓦片的材质序号插到前面去。
233
+ */
234
+ const EDGE_LINES_RENDER_ORDER = 1000;
235
+ const POINT_SPLAT_RENDER_ORDER = 1001;
236
+ /** 材质序号的上限,留出覆盖层的空间 */
237
+ const MAX_MATERIAL_RENDER_ORDER = 999;
238
+
82
239
  /* ------------------------------------------------------------------ 材质描述 */
83
240
 
84
241
  interface RawMaterialUnit {
@@ -158,6 +315,13 @@ export interface TileBuildStats {
158
315
  edgeLines: number;
159
316
  /** 合批前原有独立网格数 */
160
317
  originalMeshes?: number;
318
+ /**
319
+ * 该瓦片高斯的最长轴中位长度(米)。只有泼溅瓦片才有值,LOD 判据用它估
320
+ * 「屏幕上一个高斯多大」,见 core/SplatLod.ts。
321
+ */
322
+ splatSigma?: number;
323
+ /** 该瓦片的高斯实例数。泼溅瓦片才有值,LOD 判据用它算屏幕覆盖率 */
324
+ splatCount?: number;
161
325
  /** 被跳过的东西,加载完可以直接在控制台看 */
162
326
  skipped: string[];
163
327
  }
@@ -174,11 +338,27 @@ export interface BuildTileOptions {
174
338
  * 对倾斜摄影、大网格切片瓦片可减少 90%+ DrawCall,显著提高渲染帧率。
175
339
  */
176
340
  batching?: boolean;
341
+ /**
342
+ * 是否剔除同单体共面重复三角形(默认 true)。
343
+ *
344
+ * 见 `AssembleOptions.dedupeCoincident`。只在主线程现场组装时生效——
345
+ * 走 Worker 的路径在 Worker 里已经按默认值组装完了。
346
+ */
347
+ dedupeCoincident?: boolean;
177
348
  /**
178
349
  * Worker 侧已完成的纯数据几何组装(经典 .s3mb 路径由解析 Worker 顺带产出)。
179
350
  * 缺省时在主线程现场组装(core/TileGeometry 的同一份实现),两条路径产物一致。
180
351
  */
181
352
  assembled?: TileGeometryData;
353
+ /**
354
+ * 给三角面材质加 polygonOffset(深度偏移)。
355
+ *
356
+ * 用途:跨单体完全共面的重复面(如 `3D-BIMbuilding-2` 的 5136)深度值落在
357
+ * 深度缓冲分辨率边缘时出斜向摩尔纹;给其中一方一个小偏移(负值 = 拉向相机)
358
+ * 让每个像素稳定选边。这是**整个 mesh 粒度**的开关——该材质所有面都会偏移,
359
+ * 偏移量随斜率/距离缩放,量级在微米级,正常相邻面不受影响。
360
+ */
361
+ polygonOffset?: { factor: number; units: number };
182
362
  }
183
363
 
184
364
  export interface BuildTileResult {
@@ -256,7 +436,28 @@ export async function buildTileObjects(
256
436
  // 材质对象本身很轻(program 由 customProgramCacheKey 复用),一栋一个不心疼。
257
437
  const materialList = resolveMaterialList(tile.materials);
258
438
 
259
- const makeMaterial = (code?: string, hasAtlasRect = false): StyledMaterial => {
439
+ /**
440
+ * 材质在文件里的序号。
441
+ *
442
+ * 用途:完全重合的两份面(实测 `3D-BIMbuilding-2` 里跨单体共面 48 组,顶点偏移中位数
443
+ * 1 微米、最大 0.44 mm)深度相等,官方 `depthTest = LESS_OR_EQUAL`,
444
+ * 结果是**后画的那份赢**。官方的提交顺序就是文件里的材质顺序
445
+ * (每个 index package 一条 DrawCommand),所以这里把 renderOrder 钉成材质序号,
446
+ * 让"谁在上"与官方一致,而且不随材质重建而变。
447
+ *
448
+ * 不能指望 three 自己的排序:不透明队列按 `program.id → material.id → z` 排,
449
+ * 而 material.id 是创建顺序,`_rebuildStyledMaterials()` 换质感档位时会重新分配,
450
+ * 顺序可能整体翻转 —— 同一场景换个画质,重合面的颜色就换了一个。
451
+ */
452
+ const materialIndexOf = (code?: string): number =>
453
+ materialList.findIndex((e) => materialName(unwrapMaterial(e)) === code);
454
+
455
+ const makeMaterial = (
456
+ code?: string,
457
+ hasAtlasRect = false,
458
+ hasTexCoordZ = false,
459
+ vertexAlphaMin = 1
460
+ ): StyledMaterial => {
260
461
  const entry = materialList.find((e) => materialName(unwrapMaterial(e)) === code);
261
462
  const m = unwrapMaterial(entry);
262
463
  const units = materialUnits(m);
@@ -300,13 +501,19 @@ export async function buildTileObjects(
300
501
  }
301
502
 
302
503
  const diffuse = m?.diffuse ?? { r: 1, g: 1, b: 1, a: 1 };
303
- const alphaMode = m?.alphaMode;
304
504
  // SuperMap 的内置水面:纹理单元 id 是 `103.water`,块里**根本没有像素**
305
505
  // (实测 Waters 的 s3mb 只有 1081 字节,textures = 0,材质 diffuse 还是纯白)。
306
506
  // 以前当白色漫反射渲染,整片湖就是一块白板。这里按内置水面材质上色。
307
507
  const isWater = [unit0, unit1].some((u) => /\.water$/i.test(String(materialName(u) ?? u?.id ?? '')));
308
- const transparent =
309
- alphaMode === 'AM_BLEND' || (diffuse.a ?? 1) < 0.999 || m?.transparentsorting === true || (isWater && !map);
508
+ // 官方 isTransparent = (alphaMode === MASK || BLEND),MASK 我们交给 alphaTest;
509
+ // 另外顶点色 alpha < 1 也必须走混合(SMID 4421 就是这种)。见 render/materialAlpha.ts
510
+ const transparent = isTransparentMaterial({
511
+ alphaMode: m?.alphaMode,
512
+ diffuseAlpha: diffuse.a ?? 1,
513
+ transparentSorting: m?.transparentsorting === true,
514
+ vertexAlphaMin,
515
+ isWaterWithoutMap: isWater && !map,
516
+ });
310
517
 
311
518
  // S3M 的 shininess 是旧式 0~128 高光指数,粗略映射到粗糙度
312
519
  const roughness =
@@ -330,14 +537,27 @@ export async function buildTileObjects(
330
537
  roughness: isWater && !map ? 0.12 : roughness,
331
538
  alphaTest,
332
539
  atlas: hasAtlasRect && !!map,
540
+ // uv 第三分量(custom0):几何带了这条流,材质才走官方的「按 z 决定采样」分支
541
+ texCoordZ: hasTexCoordZ && !!map,
333
542
  secMap,
334
543
  });
544
+ // 用户配置的深度偏移(共面摩尔纹 / 重合面归属的人工定序,见 BuildTileOptions 注释)
545
+ if (options.polygonOffset) {
546
+ mat.polygonOffset = true;
547
+ mat.polygonOffsetFactor = options.polygonOffset.factor;
548
+ mat.polygonOffsetUnits = options.polygonOffset.units;
549
+ }
335
550
  return mat;
336
551
  };
337
552
 
338
553
  // 3) 纯数据几何组装:Worker 解析时已顺带产出就直接用,否则主线程现场组装
339
554
  // (core/TileGeometry 的同一份实现,两条路径产物一致)
340
- const assembled = options.assembled ?? assembleTileGeometry(tile, { batching: options.batching !== false });
555
+ const assembled =
556
+ options.assembled ??
557
+ assembleTileGeometry(tile, {
558
+ batching: options.batching !== false,
559
+ dedupeCoincident: options.dedupeCoincident,
560
+ });
341
561
  stats.vertices = assembled.stats.vertices;
342
562
  stats.edgeLines = assembled.stats.edgeLines;
343
563
  stats.instanced = assembled.stats.instanced;
@@ -368,7 +588,12 @@ export async function buildTileObjects(
368
588
  const geometry = packPrimitive(prim);
369
589
  const geoRef = tile.geoPackage[prim.geoNames[0]] ?? null;
370
590
  const vp = geoRef?.vertexPackage;
371
- const material = makeMaterial(prim.passNames[0], !!prim.attributes.aAtlasRect);
591
+ const material = makeMaterial(
592
+ prim.passNames[0],
593
+ !!prim.attributes.aAtlasRect,
594
+ !!prim.attributes.aTexCoordZ,
595
+ minVertexAlpha(prim.attributes.aColor)
596
+ );
372
597
  const matrix =
373
598
  prim.matrix && prim.matrix.length === 16 ? new THREE.Matrix4().fromArray(prim.matrix) : null;
374
599
 
@@ -376,7 +601,7 @@ export async function buildTileObjects(
376
601
  if (isLinePrimitive(prim.primitiveType)) {
377
602
  const lines = new THREE.LineSegments(geometry, edgeLineMaterial);
378
603
  lines.visible = options.edges === true;
379
- lines.renderOrder = 2;
604
+ lines.renderOrder = EDGE_LINES_RENDER_ORDER;
380
605
  lines.raycast = () => {};
381
606
  lines.userData.isEdgeLines = true;
382
607
  lines.userData.skeletonName = prim.skeletonName;
@@ -391,6 +616,37 @@ export async function buildTileObjects(
391
616
 
392
617
  // 点云包:顶点即点,按 THREE.Points 渲染。索引只是点序号,直接走 drawRange。
393
618
  if (isPointPrimitive(prim.primitiveType)) {
619
+ // 高斯泼溅:带 gsScale / gsRotation 的点云包不画点,画各向异性的高斯片。
620
+ // 这两条流的量化格式见 core/GaussianSplat.ts 的头部(从官方 worker 读出来的)。
621
+ const splat = vp ? buildSplatGeometry(prim, vp) : null;
622
+ if (splat) {
623
+ // 用 InstancedMesh 而不是裸 Mesh:它会正确设置 instanceCount 与
624
+ // `_maxInstanceCount`,后者缺失时 three 会算出 NaN 个实例、一个像素都不画。
625
+ // instanceMatrix 我们不用(位置全在 iCenter 里),但**不能**把 raycast 关掉:
626
+ // 拾取结果的精确命中点要靠它,关掉就只能退回原点那个 2×2 方块的包围盒。
627
+ const mesh = new THREE.InstancedMesh(splat.geo, getSplatMaterial(), splat.geo.instanceCount);
628
+ mesh.raycast = splatRaycast;
629
+ mesh.frustumCulled = false;
630
+ mesh.renderOrder = POINT_SPLAT_RENDER_ORDER;
631
+ // LOD 判据要用「这块瓦片的高斯有多大」,见 core/SplatLod.ts。
632
+ // 多骨架取最大值:宁可判得更细,也别停在稀的粗层。
633
+ if (splat.sigma > 0) stats.splatSigma = Math.max(stats.splatSigma ?? 0, splat.sigma);
634
+ stats.splatCount = (stats.splatCount ?? 0) + splat.geo.instanceCount;
635
+ // 与点云同样打标记:卸载 / 拾取 / 样式链路靠它放行
636
+ mesh.userData.isPointCloud = true;
637
+ mesh.userData.isGaussianSplat = true;
638
+ mesh.userData.skeletonName = prim.skeletonName;
639
+ mesh.userData.geoRef = geoRef;
640
+ mesh.userData.tileRef = tile;
641
+ mesh.userData.primitiveType = prim.primitiveType;
642
+ if (matrix && !matrix.equals(IDENTITY)) {
643
+ matrix.decompose(mesh.position, mesh.quaternion, mesh.scale);
644
+ }
645
+ group.add(mesh);
646
+ objects.push(mesh);
647
+ stats.meshes++;
648
+ continue;
649
+ }
394
650
  // PointsMaterial 的顶点色读内置 'color' 槽位,与 aColor 共享同一份 buffer
395
651
  if (!geometry.getAttribute('color')) {
396
652
  geometry.setAttribute('color', geometry.getAttribute('aColor')!);
@@ -480,6 +736,14 @@ export async function buildTileObjects(
480
736
  object.userData.geoRef = geoRef;
481
737
  object.userData.tileRef = tile;
482
738
  object.userData.primitiveType = prim.primitiveType;
739
+ // 绘制顺序 = 文件材质顺序(见 materialIndexOf 的说明)。只在拿到了序号时钉,
740
+ // 拿不到就保持 three 的默认 0,不影响其他数据。
741
+ // 透明材质例外:共面定序解决的是「不透明面深度相等谁赢」,而透明物体的正确
742
+ // 顺序是「远 → 近」,钉成材质序号反而会把近处玻璃画在远处之前。
743
+ const matIdx = materialIndexOf(prim.passNames[0]);
744
+ if (matIdx >= 0 && !material.transparent) {
745
+ object.renderOrder = Math.min(matIdx, MAX_MATERIAL_RENDER_ORDER);
746
+ }
483
747
  if (prim.batchedFrom != null && prim.batchedFrom > 1) {
484
748
  object.userData.isBatched = true;
485
749
  object.userData.batchedCount = prim.batchedFrom;
@@ -15,6 +15,7 @@
15
15
  * 的代价(移动端尤其明显)就没了。
16
16
  */
17
17
  import * as THREE from 'three';
18
+ import { createSplatPickMaterial } from './SplatMaterial.js';
18
19
 
19
20
  const PICK_VERT = /* glsl */ `
20
21
  attribute float aBatchId;
@@ -175,6 +176,8 @@ export interface PickHit {
175
176
  interface PickUserData {
176
177
  pickId?: number;
177
178
  pickMaterial?: THREE.Material;
179
+ /** 高斯泼溅实例网格:拾取要走专用材质,见 SplatMaterial.createSplatPickMaterial */
180
+ isGaussianSplat?: boolean;
178
181
  [key: string]: unknown;
179
182
  }
180
183
 
@@ -299,13 +302,26 @@ export class Picker {
299
302
  if (!mesh.isMesh && !isPointsObject(mesh)) return;
300
303
  const ud = mesh.userData as PickUserData;
301
304
  if (ud.pickId === undefined) return;
302
- if (!ud.pickMaterial) ud.pickMaterial = createPickMaterial(ud.pickId);
303
- if ((mesh as THREE.InstancedMesh).isInstancedMesh) instancedPickIds.add(ud.pickId);
304
-
305
- // 把当前的「单体状态纹理」抄给拾取材质:抬高 / 平移 / 姿态 / GPU 路径都是逐单体
306
- // 写在里面的,抄的是同一个纹理与同一批 uniform 对象,主材质那边更新后这里自动跟着变。
305
+ // 泼溅走自己的拾取材质:通用拾取着色器只读 position,不知道 iCenter,
306
+ // 交给它每个高斯都会塌在原点,点哪儿都没反应。见 SplatMaterial 里那段注释。
307
+ const isSplat = ud.isGaussianSplat === true;
308
+ if (!ud.pickMaterial) {
309
+ ud.pickMaterial = isSplat ? createSplatPickMaterial(ud.pickId) : createPickMaterial(ud.pickId);
310
+ }
311
+ // 泼溅**不登记为实例化单体**:它的实例号在 geoRef.pickInfo 里查不到(没有
312
+ // instanceMatrix 那套单体表),报成 instanceIndex 只会让上层 `_instanceFeature`
313
+ // 返回 null 把结果丢掉。泼溅整块瓦片当一个单体,命中点交给 splatRaycast。
314
+ if (!isSplat && (mesh as THREE.InstancedMesh).isInstancedMesh) instancedPickIds.add(ud.pickId);
307
315
  const prev = mesh.material;
308
316
  const pm = ud.pickMaterial as THREE.ShaderMaterial;
317
+ // 泼溅没有单体状态纹理 / GPU 路径那一套 uniform,直接跳过抄写
318
+ if (isSplat) {
319
+ swapped.push([mesh, prev]);
320
+ mesh.material = pm;
321
+ return;
322
+ }
323
+ // 把当前的「单体状态纹理」抄给拾取材质:抬高 / 平移 / 姿态 / GPU 路径都是逐单体
324
+ // 写在里面的,抄的是同一个纹理与同一批 uniform 对象,主材质那边更新后这里自动跟着变。
309
325
  const src = (Array.isArray(prev) ? prev[0] : prev) as THREE.Material & {
310
326
  userData?: Record<string, THREE.IUniform>;
311
327
  uniforms?: Record<string, THREE.IUniform>;