s3m-three 0.4.24 → 0.4.26

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 (83) hide show
  1. package/dist/browser/assets/{s3mWorker-DvAUA8NJ.js → s3mWorker-DHOVIrAU.js} +1 -1
  2. package/dist/browser/s3m-three.iife.ext-three.js +107 -36
  3. package/dist/browser/s3m-three.iife.js +107 -36
  4. package/dist/core/CustomAttributes.d.ts +7 -0
  5. package/dist/core/CustomAttributes.d.ts.map +1 -0
  6. package/dist/core/CustomAttributes.js +48 -0
  7. package/dist/core/CustomAttributes.js.map +1 -0
  8. package/dist/core/DXTTextureDecode.d.ts +5 -2
  9. package/dist/core/DXTTextureDecode.d.ts.map +1 -1
  10. package/dist/core/DXTTextureDecode.js +17 -7
  11. package/dist/core/DXTTextureDecode.js.map +1 -1
  12. package/dist/core/Draco.d.ts.map +1 -1
  13. package/dist/core/Draco.js +149 -2
  14. package/dist/core/Draco.js.map +1 -1
  15. package/dist/core/MeshOpt.d.ts.map +1 -1
  16. package/dist/core/MeshOpt.js +2 -0
  17. package/dist/core/MeshOpt.js.map +1 -1
  18. package/dist/core/S3M1Parser.d.ts.map +1 -1
  19. package/dist/core/S3M1Parser.js +114 -27
  20. package/dist/core/S3M1Parser.js.map +1 -1
  21. package/dist/core/S3MBParser.d.ts +5 -2
  22. package/dist/core/S3MBParser.d.ts.map +1 -1
  23. package/dist/core/S3MBParser.js +27 -12
  24. package/dist/core/S3MBParser.js.map +1 -1
  25. package/dist/core/TileGeometry.d.ts +1 -0
  26. package/dist/core/TileGeometry.d.ts.map +1 -1
  27. package/dist/core/TileGeometry.js +25 -7
  28. package/dist/core/TileGeometry.js.map +1 -1
  29. package/dist/core/types.d.ts +1 -0
  30. package/dist/core/types.d.ts.map +1 -1
  31. package/dist/index.d.ts +4 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/render/GeometryBuilder.d.ts.map +1 -1
  36. package/dist/render/GeometryBuilder.js +36 -2
  37. package/dist/render/GeometryBuilder.js.map +1 -1
  38. package/dist/render/MotionBlurPass.d.ts +130 -0
  39. package/dist/render/MotionBlurPass.d.ts.map +1 -0
  40. package/dist/render/MotionBlurPass.js +394 -0
  41. package/dist/render/MotionBlurPass.js.map +1 -0
  42. package/dist/render/S3MViewer.d.ts +27 -0
  43. package/dist/render/S3MViewer.d.ts.map +1 -1
  44. package/dist/render/S3MViewer.js +76 -4
  45. package/dist/render/S3MViewer.js.map +1 -1
  46. package/dist/render/viewer/PickService.d.ts +2 -0
  47. package/dist/render/viewer/PickService.d.ts.map +1 -1
  48. package/dist/render/viewer/PickService.js +1 -1
  49. package/dist/render/viewer/PickService.js.map +1 -1
  50. package/dist/render/viewer/types.d.ts +22 -0
  51. package/dist/render/viewer/types.d.ts.map +1 -1
  52. package/dist/render/viewer/types.js.map +1 -1
  53. package/dist/tileset/S3MTileset.d.ts +19 -0
  54. package/dist/tileset/S3MTileset.d.ts.map +1 -1
  55. package/dist/tileset/S3MTileset.js +71 -4
  56. package/dist/tileset/S3MTileset.js.map +1 -1
  57. package/dist/worker/S3MWorkerClient.d.ts +1 -1
  58. package/dist/worker/S3MWorkerClient.d.ts.map +1 -1
  59. package/dist/worker/S3MWorkerClient.js +2 -2
  60. package/dist/worker/S3MWorkerClient.js.map +1 -1
  61. package/dist/worker/s3mWorker.js +792 -636
  62. package/dist/worker/s3mWorker.js.map +1 -1
  63. package/docs/API.md +18 -5
  64. package/docs/FORMAT-NOTES.md +10 -0
  65. package/package.json +1 -1
  66. package/src/core/CustomAttributes.ts +53 -0
  67. package/src/core/DXTTextureDecode.ts +17 -7
  68. package/src/core/Draco.ts +168 -6
  69. package/src/core/MeshOpt.ts +2 -0
  70. package/src/core/S3M1Parser.ts +102 -22
  71. package/src/core/S3MBParser.ts +33 -14
  72. package/src/core/TileGeometry.ts +34 -7
  73. package/src/core/types.ts +1 -0
  74. package/src/index.ts +4 -2
  75. package/src/render/GeometryBuilder.ts +37 -2
  76. package/src/render/MotionBlurPass.ts +453 -0
  77. package/src/render/S3MViewer.ts +78 -4
  78. package/src/render/viewer/PickService.ts +1 -1
  79. package/src/render/viewer/types.ts +22 -0
  80. package/src/tileset/S3MTileset.ts +74 -3
  81. package/src/vendor/draco_decoder.module.d.ts +15 -1
  82. package/src/worker/S3MWorkerClient.ts +7 -2
  83. package/src/worker/s3mWorker.ts +6 -2
@@ -195,6 +195,10 @@ export function parseConfigText(text: string): Record<string, unknown> {
195
195
 
196
196
  return {
197
197
  fileType: tagVal('FileType') ?? undefined,
198
+ // 地形/影像缓存的判别字段(见 classifyDatasetKind):FileExtentName=Terrainz 是地形,
199
+ // <sml:Type>Image</sml:Type> 是影像(影像的 FileExtentName 是 jpg_png 这种扩展名样式,不可靠)
200
+ fileExtentName: tagVal('FileExtentName') ?? undefined,
201
+ cacheType: tagVal('Type') ?? undefined,
198
202
  position: { x: posX, y: posY, z: posZ, units: 'Degree' },
199
203
  geoBounds: { left, right, top, bottom },
200
204
  heightRange: Number.isFinite(minH) || Number.isFinite(maxH) ? { min: minH, max: maxH } : undefined,
@@ -205,6 +209,53 @@ export function parseConfigText(text: string): Record<string, unknown> {
205
209
  return JSON.parse(trimmed) as Record<string, unknown>;
206
210
  }
207
211
 
212
+ /** config 能认出的数据集类别(判别规则见 classifyDatasetKind) */
213
+ export type DatasetKind = 'model' | 'terrain' | 'imagery';
214
+
215
+ /**
216
+ * 按 config 的类型标记识别数据集类别。
217
+ *
218
+ * 同一个 `…/datas/<名>/config` URL 底下可能是模型、地形、影像三种完全不同的缓存
219
+ * (FORMAT-NOTES 第二章)。地形/影像是按 Level0 金字塔寻址的栅格缓存,**没有瓦片清单**,
220
+ * 当模型加载只会一路走到「配置中缺少 root tile」,把「类型不支持」伪装成「配置缺字段」
221
+ * (issue #7:混合服务第一个数据集 beijingdem 是 Terrainz 地形缓存)。
222
+ *
223
+ * 判别字段取自实测(supermapol 3D-OlympicGreen_Plan,2026-10-30):
224
+ * 地形 XML: FileExtentName = Terrainz
225
+ * 影像 XML: FileExtentName = jpg_png(扩展名样式,不可靠)+ Type = Image
226
+ * 模型 XML: FileType = OSGBFile / OSGBCacheFile,无 FileExtentName / Type
227
+ *
228
+ * 保守原则:只有认出确凿的非模型标记才判 terrain / imagery,其余(含 JSON 配置、
229
+ * 无标记的 XML)一律 'model' —— 保证「认不出」时行为与旧版一致(不变式 ①)。
230
+ */
231
+ export function classifyDatasetKind(config: Record<string, unknown> | null | undefined): DatasetKind {
232
+ if (!config || typeof config !== 'object') return 'model';
233
+ if (String(config.fileExtentName ?? '').trim().toLowerCase() === 'terrainz') return 'terrain';
234
+ if (String(config.cacheType ?? '').trim().toLowerCase() === 'image') return 'imagery';
235
+ return 'model';
236
+ }
237
+
238
+ /** 拼一句能区分 config 形态的上下文(FileExtentName / Type / fileType / dataType),供报错使用 */
239
+ function configTypeHint(config: Record<string, unknown>): string {
240
+ const parts: string[] = [];
241
+ if (config.fileExtentName) parts.push(`FileExtentName=${config.fileExtentName}`);
242
+ if (config.cacheType) parts.push(`Type=${config.cacheType}`);
243
+ if (config.fileType) parts.push(`fileType=${config.fileType}`);
244
+ if (config.dataType) parts.push(`dataType=${config.dataType}`);
245
+ return parts.length ? parts.join(', ') : '无类型标记';
246
+ }
247
+
248
+ /** 从 configUrl 里抠出数据集名(…/datas/<名>/config);抠不出就原样返回,报错时能对上服务里的数据集 */
249
+ function datasetNameOf(configUrl: string): string {
250
+ const m = /\/datas\/([^/?#]+)/i.exec(configUrl);
251
+ if (!m) return configUrl;
252
+ try {
253
+ return decodeURIComponent(m[1]!);
254
+ } catch {
255
+ return m[1]!;
256
+ }
257
+ }
258
+
208
259
  export interface TileNode {
209
260
  url: string;
210
261
  tile: ParsedTile;
@@ -539,11 +590,31 @@ export class S3MTileset {
539
590
  const text = await res.text();
540
591
  const config = parseConfigText(text);
541
592
  this.config = config;
593
+
594
+ // 地形/影像缓存(金字塔栅格,无瓦片清单)不能当模型加载:走到下面「缺少 root tile」
595
+ // 那步只会得到一句把「类型不支持」伪装成「配置缺字段」的报错(issue #7),
596
+ // 所以在锚点解析之前先按类型标记拒掉,把病因说清楚。
597
+ const kind = classifyDatasetKind(config);
598
+ if (kind !== 'model') {
599
+ const err = new Error(
600
+ `数据集 ${datasetNameOf(this.configUrl)} 是${kind === 'terrain' ? '地形' : '影像'}缓存(${configTypeHint(config)}),本层仅支持模型类 S3M 数据集`
601
+ ) as Error & { datasetKind: DatasetKind };
602
+ // 程序化判别用:调用方(如数据集列表 UI)可以据此标注/跳过该数据集
603
+ err.datasetKind = kind;
604
+ throw err;
605
+ }
606
+
542
607
  this.anchor = this._resolveAnchor(config);
543
608
 
544
609
  // iServer 的 realspace 配置用 rootTiles(可多棵树),桌面导出的用 tiles
545
610
  const roots = (config.rootTiles ?? config.tiles ?? []) as Array<{ url: string }>;
546
- if (!roots.length) throw new Error('配置中缺少 root tile');
611
+ if (!roots.length) {
612
+ // 带上 config 的类型标记:地形/影像缓存(本应在前一步被拒)与「configUrl 误指向
613
+ // …/datas 清单」「桌面导出漏瓦片清单」是三种不同的病因,文案必须能区分
614
+ throw new Error(
615
+ `配置中缺少 root tile(${configTypeHint(config)})——若是地形/影像数据集,或 configUrl 误指向 …/datas 数据集清单,本层仅支持模型类 S3M 数据集`
616
+ );
617
+ }
547
618
 
548
619
  this.rootUrls.length = 0;
549
620
  for (const r of roots) {
@@ -941,7 +1012,7 @@ export class S3MTileset {
941
1012
  }
942
1013
  if (this._workerClient) {
943
1014
  try {
944
- return await this._workerClient.parseTile(buffer, true);
1015
+ return await this._workerClient.parseTile(buffer, true, true, this.config?.vertexAttributeDescript);
945
1016
  } catch (e) {
946
1017
  if (e instanceof WorkerDeadError) {
947
1018
  // 脚本加载失败 / 崩溃属于环境问题,永久退回主线程;瓦片本身的问题照常抛
@@ -951,7 +1022,7 @@ export class S3MTileset {
951
1022
  }
952
1023
  }
953
1024
  }
954
- return { tile: await parseS3MB(buffer) };
1025
+ return { tile: await parseS3MB(buffer, { vertexAttributeDescript: this.config?.vertexAttributeDescript }) };
955
1026
  }
956
1027
 
957
1028
  /**
@@ -1,4 +1,4 @@
1
- /**
1
+ /**
2
2
  * Draco 几何解码器(google/draco,wasm 内联)的类型声明
3
3
  *
4
4
  * 只声明 S3M 用到的部分。两个坑:
@@ -41,6 +41,11 @@ export interface DracoPointCloud {
41
41
  __destroy__(): void;
42
42
  }
43
43
 
44
+
45
+ /** draco::Mesh - inherits from PointCloud, additionally provides face info (used for TRIANGULAR_MESH branch) */
46
+ export interface DracoMesh extends DracoPointCloud {
47
+ num_faces(): number;
48
+ }
44
49
  export interface DracoStatus {
45
50
  ok(): boolean;
46
51
  error_msg(): string;
@@ -54,6 +59,14 @@ export interface DracoDecoder {
54
59
  byteLength: number,
55
60
  out: DracoPointCloud
56
61
  ): DracoStatus;
62
+ /** 解码三角网格(TRIANGULAR_MESH 分支) */
63
+ DecodeArrayToMesh(
64
+ data: Uint8Array,
65
+ byteLength: number,
66
+ out: DracoMesh
67
+ ): DracoStatus;
68
+ /** 从已解码的 Mesh 取第 faceIndex 个面的 3 个顶点索引到 DracoInt32Array */
69
+ GetFaceFromMesh(mesh: DracoMesh, faceIndex: number, out: DracoInt32Array): boolean;
57
70
  /** 按属性在几何里的序号取 */
58
71
  GetAttribute(pc: DracoPointCloud, index: number): DracoPointAttribute;
59
72
  /** 按 unique_id 取;不存在时返回 null */
@@ -113,6 +126,7 @@ export interface DracoUInt32Array {
113
126
  export interface DracoModule {
114
127
  Decoder: new () => DracoDecoder;
115
128
  PointCloud: new () => DracoPointCloud;
129
+ Mesh: new () => DracoMesh;
116
130
  DracoFloat32Array: new () => DracoFloat32Array;
117
131
  DracoInt8Array: new () => DracoInt8Array;
118
132
  DracoUInt8Array: new () => DracoUInt8Array;
@@ -88,12 +88,17 @@ export class S3MWorkerClient {
88
88
  * 解析一块瓦片。buffer 会被 structured clone(不 transfer),主线程保留原 buffer 以便崩溃后降级重解。
89
89
  * assemble = true 时顺带在 Worker 里做纯数据几何组装,返回 { tile, geometry } 一次带回。
90
90
  */
91
- parseTile(buffer: ArrayBuffer, assemble = false, batching = true): Promise<{ tile: ParsedTile; geometry?: TileGeometryData }> {
91
+ parseTile(
92
+ buffer: ArrayBuffer,
93
+ assemble = false,
94
+ batching = true,
95
+ vertexAttributeDescript?: unknown
96
+ ): Promise<{ tile: ParsedTile; geometry?: TileGeometryData }> {
92
97
  if (this.dead) return Promise.reject(new WorkerDeadError('s3m 解析 Worker 已终止'));
93
98
  const id = ++this._seq;
94
99
  return new Promise<{ tile: ParsedTile; geometry?: TileGeometryData }>((resolve, reject) => {
95
100
  this._pending.set(id, { resolve: resolve as (v: unknown) => void, reject });
96
- this._worker.postMessage({ type: 'parse', id, buffer, assemble, batching });
101
+ this._worker.postMessage({ type: 'parse', id, buffer, assemble, batching, vertexAttributeDescript });
97
102
  });
98
103
  }
99
104
 
@@ -28,6 +28,7 @@ type ParseRequest = {
28
28
  /** 解析后顺带做纯数据几何组装(解交错 / 法线 / 合批),一次往返带回 */
29
29
  assemble?: boolean;
30
30
  batching?: boolean;
31
+ vertexAttributeDescript?: unknown;
31
32
  };
32
33
  type TexRequest = { type: 'tex'; id: number; tex: S3MTexture; compressed?: boolean; maxSize?: number };
33
34
  type Request = ParseRequest | TexRequest;
@@ -52,10 +53,13 @@ ctx.onmessage = async (e: MessageEvent<Request>) => {
52
53
  const { id } = msg;
53
54
  try {
54
55
  if (msg.type === 'parse') {
55
- const tile = await parseS3MB(msg.buffer);
56
+ const tile = await parseS3MB(msg.buffer, { vertexAttributeDescript: msg.vertexAttributeDescript });
56
57
  if (msg.assemble) {
57
58
  // 解析 + 组装一起做,避免 tile 回主线程后再整块克隆回来一次
58
- const geometry = assembleTileGeometry(tile, { batching: msg.batching });
59
+ const geometry = assembleTileGeometry(tile, {
60
+ batching: msg.batching,
61
+ vertexAttributeDescript: msg.vertexAttributeDescript,
62
+ });
59
63
  ctx.postMessage(
60
64
  { id, ok: true, tile, geometry } satisfies ParseResponse,
61
65
  [...collectTransferables(tile), ...collectTransferables(geometry)]