s3m-three 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +202 -202
  2. package/dist/render/DatasetManager.d.ts +10 -3
  3. package/dist/render/DatasetManager.d.ts.map +1 -1
  4. package/dist/render/DatasetManager.js +14 -5
  5. package/dist/render/DatasetManager.js.map +1 -1
  6. package/dist/render/EdgeLines.js +46 -46
  7. package/dist/render/GroundManager.d.ts +9 -0
  8. package/dist/render/GroundManager.d.ts.map +1 -1
  9. package/dist/render/GroundManager.js +13 -1
  10. package/dist/render/GroundManager.js.map +1 -1
  11. package/dist/render/Picker.js +66 -66
  12. package/dist/render/S3MViewer.d.ts +11 -3
  13. package/dist/render/S3MViewer.d.ts.map +1 -1
  14. package/dist/render/S3MViewer.js +18 -3
  15. package/dist/render/S3MViewer.js.map +1 -1
  16. package/dist/render/StyleMaterial.js +265 -265
  17. package/dist/vendor/meshopt_decoder.module.js +118 -118
  18. package/docs/API.md +20 -0
  19. package/package.json +1 -1
  20. package/src/core/BinaryReader.ts +116 -116
  21. package/src/core/DXTTextureDecode.ts +260 -260
  22. package/src/core/FeatureInfo.ts +150 -150
  23. package/src/core/MeshOpt.ts +401 -401
  24. package/src/core/S3MBParser.ts +590 -590
  25. package/src/core/types.ts +222 -222
  26. package/src/render/DatasetManager.ts +17 -6
  27. package/src/render/EdgeLines.ts +343 -343
  28. package/src/render/GeometryBuilder.ts +840 -840
  29. package/src/render/GroundManager.ts +14 -1
  30. package/src/render/Picker.ts +293 -293
  31. package/src/render/S3MViewer.ts +18 -3
  32. package/src/render/StyleMaterial.ts +602 -602
  33. package/src/render/TextureFactory.ts +134 -134
  34. package/src/tileset/S3MTileset.ts +552 -552
  35. package/src/vendor/meshopt_decoder.module.d.ts +31 -31
  36. package/src/vendor/meshopt_decoder.module.js +118 -118
  37. package/dist/render/CoordinateHelper.d.ts +0 -90
  38. package/dist/render/CoordinateHelper.d.ts.map +0 -1
  39. package/dist/render/CoordinateHelper.js +0 -85
  40. package/dist/render/CoordinateHelper.js.map +0 -1
  41. package/dist/render/S3MLayer.d.ts +0 -457
  42. package/dist/render/S3MLayer.d.ts.map +0 -1
  43. package/dist/render/S3MLayer.js +0 -1860
  44. package/dist/render/S3MLayer.js.map +0 -1
@@ -1,150 +1,150 @@
1
- /**
2
- * 单体属性提取
3
- *
4
- * S3M 的 `pickInfo` 是「SMID → batchId + 顶点区间」的映射,本身不带业务属性
5
- * (OSGB 转 S3M 的数据通常没有属性表)。所以这里做两件事:
6
- * 1. 反查 SMID —— 这是和 iServer 侧数据对齐的唯一标识;
7
- * 2. 从几何直接算出可信的派生属性(高度、基底高程、外接盒、三角面数、楼层估算)。
8
- *
9
- * 派生属性是「算出来的」而不是「查出来的」,所以每一项都标注了计算口径,
10
- * 避免使用者误以为是业务数据。
11
- */
12
- import type { BufferGeometry } from 'three';
13
- import type { FeatureAttributes, GeoPackageEntry, PickEntry, PickSegment, SegmentBox } from './types.js';
14
-
15
- /** 楼层估算用的默认层高(米) */
16
- export const DEFAULT_FLOOR_HEIGHT = 3.2;
17
-
18
- /**
19
- * 单体的控制位。
20
- *
21
- * 顶点区间型单体用 `batchId`(写进 `aBatchId`),实例化型用 `instanceBatchId`
22
- * (写进 `aInstanceBatchId`)。两者都走「每网格一张状态纹理」,所以对上层是同一个概念。
23
- */
24
- export function pickEntryId(entry?: PickEntry | null): number {
25
- if (!entry) return -1;
26
- if (entry.batchId != null) return entry.batchId;
27
- if (entry.instanceBatchId != null) return entry.instanceBatchId;
28
- return -1;
29
- }
30
-
31
- /** 实例化几何里「实例序号 → 单体」的反查表 */
32
- export interface InstanceFeature {
33
- smid: number;
34
- /** 控制位(状态纹理下标) */
35
- batchId: number;
36
- }
37
-
38
- export function buildInstanceLookup(
39
- pickInfo?: Record<number, PickEntry> | null
40
- ): Map<number, InstanceFeature> {
41
- const map = new Map<number, InstanceFeature>();
42
- if (!pickInfo) return map;
43
- for (const [key, entry] of Object.entries(pickInfo)) {
44
- if (!entry?.instanceIds?.length) continue;
45
- const smid = Number(key);
46
- if (!Number.isFinite(smid)) continue;
47
- const batchId = pickEntryId(entry);
48
- for (const instanceIndex of entry.instanceIds) map.set(instanceIndex, { smid, batchId });
49
- }
50
- return map;
51
- }
52
-
53
- export function resolveFeature(
54
- geo: GeoPackageEntry | undefined,
55
- batchId: number
56
- ): { smid: number | null; segments: PickSegment[] } {
57
- const pickInfo = geo?.pickInfo;
58
- if (!pickInfo || batchId < 0) return { smid: null, segments: [] };
59
-
60
- for (const [id, entry] of Object.entries(pickInfo)) {
61
- if (pickEntryId(entry) === batchId) {
62
- return { smid: Number(id), segments: entry.segments ?? [] };
63
- }
64
- }
65
- return { smid: null, segments: [] };
66
- }
67
-
68
- /**
69
- * 统计单体顶点区间包围盒 + 该单体自身的三角面数。
70
- */
71
- export function measureSegments(geometry: BufferGeometry | undefined, segments: PickSegment[]): SegmentBox | null {
72
- const pos = geometry?.getAttribute('position');
73
- if (!pos || !segments.length) return null;
74
-
75
- let minX = Infinity;
76
- let minY = Infinity;
77
- let minZ = Infinity;
78
- let maxX = -Infinity;
79
- let maxY = -Infinity;
80
- let maxZ = -Infinity;
81
- let count = 0;
82
-
83
- for (const seg of segments) {
84
- const end = Math.min(seg.vertexOffset + seg.vertexCount, pos.count);
85
- for (let i = seg.vertexOffset; i < end; i++) {
86
- const x = pos.getX(i);
87
- const y = pos.getY(i);
88
- const z = pos.getZ(i);
89
- if (x < minX) minX = x;
90
- if (x > maxX) maxX = x;
91
- if (y < minY) minY = y;
92
- if (y > maxY) maxY = y;
93
- if (z < minZ) minZ = z;
94
- if (z > maxZ) maxZ = z;
95
- count++;
96
- }
97
- }
98
-
99
- if (!count) return null;
100
-
101
- // 单体三角面数:索引三元组的三个顶点都落在本单体区间内才算,
102
- // 直接用整个网格的 index.count 会把整片街区都算进去。
103
- let triangles = 0;
104
- const index = geometry!.index;
105
- if (index) {
106
- const mask = new Uint8Array(pos.count);
107
- for (const seg of segments) {
108
- const end = Math.min(seg.vertexOffset + seg.vertexCount, pos.count);
109
- mask.fill(1, seg.vertexOffset, end);
110
- }
111
- const arr = index.array as ArrayLike<number>;
112
- for (let i = 0; i < arr.length; i += 3) {
113
- if (mask[arr[i]] && mask[arr[i + 1]] && mask[arr[i + 2]]) triangles++;
114
- }
115
- }
116
-
117
- return { minX, minY, minZ, maxX, maxY, maxZ, count, triangles };
118
- }
119
-
120
- /** 组装给 UI 展示的属性对象 */
121
- export function buildAttributes(args: {
122
- smid: number | null;
123
- box: SegmentBox | null;
124
- triangles: number;
125
- floorHeight?: number;
126
- }): FeatureAttributes {
127
- const { smid, box, triangles, floorHeight = DEFAULT_FLOOR_HEIGHT } = args;
128
- if (!box) return { smid, triangles };
129
-
130
- const height = box.maxZ - box.minZ;
131
- return {
132
- smid,
133
- height: round(height, 1),
134
- baseElevation: round(box.minZ, 1),
135
- size: [round(box.maxX - box.minX, 1), round(box.maxY - box.minY, 1)],
136
- bboxArea: round((box.maxX - box.minX) * (box.maxY - box.minY), 1),
137
- floors: Math.max(1, Math.round(height / floorHeight)),
138
- triangles: box.triangles ?? triangles,
139
- vertices: box.count,
140
- bbox: [
141
- [round(box.minX, 1), round(box.minY, 1), round(box.minZ, 1)],
142
- [round(box.maxX, 1), round(box.maxY, 1), round(box.maxZ, 1)],
143
- ],
144
- };
145
- }
146
-
147
- function round(v: number, d: number): number {
148
- const p = 10 ** d;
149
- return Math.round(v * p) / p;
150
- }
1
+ /**
2
+ * 单体属性提取
3
+ *
4
+ * S3M 的 `pickInfo` 是「SMID → batchId + 顶点区间」的映射,本身不带业务属性
5
+ * (OSGB 转 S3M 的数据通常没有属性表)。所以这里做两件事:
6
+ * 1. 反查 SMID —— 这是和 iServer 侧数据对齐的唯一标识;
7
+ * 2. 从几何直接算出可信的派生属性(高度、基底高程、外接盒、三角面数、楼层估算)。
8
+ *
9
+ * 派生属性是「算出来的」而不是「查出来的」,所以每一项都标注了计算口径,
10
+ * 避免使用者误以为是业务数据。
11
+ */
12
+ import type { BufferGeometry } from 'three';
13
+ import type { FeatureAttributes, GeoPackageEntry, PickEntry, PickSegment, SegmentBox } from './types.js';
14
+
15
+ /** 楼层估算用的默认层高(米) */
16
+ export const DEFAULT_FLOOR_HEIGHT = 3.2;
17
+
18
+ /**
19
+ * 单体的控制位。
20
+ *
21
+ * 顶点区间型单体用 `batchId`(写进 `aBatchId`),实例化型用 `instanceBatchId`
22
+ * (写进 `aInstanceBatchId`)。两者都走「每网格一张状态纹理」,所以对上层是同一个概念。
23
+ */
24
+ export function pickEntryId(entry?: PickEntry | null): number {
25
+ if (!entry) return -1;
26
+ if (entry.batchId != null) return entry.batchId;
27
+ if (entry.instanceBatchId != null) return entry.instanceBatchId;
28
+ return -1;
29
+ }
30
+
31
+ /** 实例化几何里「实例序号 → 单体」的反查表 */
32
+ export interface InstanceFeature {
33
+ smid: number;
34
+ /** 控制位(状态纹理下标) */
35
+ batchId: number;
36
+ }
37
+
38
+ export function buildInstanceLookup(
39
+ pickInfo?: Record<number, PickEntry> | null
40
+ ): Map<number, InstanceFeature> {
41
+ const map = new Map<number, InstanceFeature>();
42
+ if (!pickInfo) return map;
43
+ for (const [key, entry] of Object.entries(pickInfo)) {
44
+ if (!entry?.instanceIds?.length) continue;
45
+ const smid = Number(key);
46
+ if (!Number.isFinite(smid)) continue;
47
+ const batchId = pickEntryId(entry);
48
+ for (const instanceIndex of entry.instanceIds) map.set(instanceIndex, { smid, batchId });
49
+ }
50
+ return map;
51
+ }
52
+
53
+ export function resolveFeature(
54
+ geo: GeoPackageEntry | undefined,
55
+ batchId: number
56
+ ): { smid: number | null; segments: PickSegment[] } {
57
+ const pickInfo = geo?.pickInfo;
58
+ if (!pickInfo || batchId < 0) return { smid: null, segments: [] };
59
+
60
+ for (const [id, entry] of Object.entries(pickInfo)) {
61
+ if (pickEntryId(entry) === batchId) {
62
+ return { smid: Number(id), segments: entry.segments ?? [] };
63
+ }
64
+ }
65
+ return { smid: null, segments: [] };
66
+ }
67
+
68
+ /**
69
+ * 统计单体顶点区间包围盒 + 该单体自身的三角面数。
70
+ */
71
+ export function measureSegments(geometry: BufferGeometry | undefined, segments: PickSegment[]): SegmentBox | null {
72
+ const pos = geometry?.getAttribute('position');
73
+ if (!pos || !segments.length) return null;
74
+
75
+ let minX = Infinity;
76
+ let minY = Infinity;
77
+ let minZ = Infinity;
78
+ let maxX = -Infinity;
79
+ let maxY = -Infinity;
80
+ let maxZ = -Infinity;
81
+ let count = 0;
82
+
83
+ for (const seg of segments) {
84
+ const end = Math.min(seg.vertexOffset + seg.vertexCount, pos.count);
85
+ for (let i = seg.vertexOffset; i < end; i++) {
86
+ const x = pos.getX(i);
87
+ const y = pos.getY(i);
88
+ const z = pos.getZ(i);
89
+ if (x < minX) minX = x;
90
+ if (x > maxX) maxX = x;
91
+ if (y < minY) minY = y;
92
+ if (y > maxY) maxY = y;
93
+ if (z < minZ) minZ = z;
94
+ if (z > maxZ) maxZ = z;
95
+ count++;
96
+ }
97
+ }
98
+
99
+ if (!count) return null;
100
+
101
+ // 单体三角面数:索引三元组的三个顶点都落在本单体区间内才算,
102
+ // 直接用整个网格的 index.count 会把整片街区都算进去。
103
+ let triangles = 0;
104
+ const index = geometry!.index;
105
+ if (index) {
106
+ const mask = new Uint8Array(pos.count);
107
+ for (const seg of segments) {
108
+ const end = Math.min(seg.vertexOffset + seg.vertexCount, pos.count);
109
+ mask.fill(1, seg.vertexOffset, end);
110
+ }
111
+ const arr = index.array as ArrayLike<number>;
112
+ for (let i = 0; i < arr.length; i += 3) {
113
+ if (mask[arr[i]] && mask[arr[i + 1]] && mask[arr[i + 2]]) triangles++;
114
+ }
115
+ }
116
+
117
+ return { minX, minY, minZ, maxX, maxY, maxZ, count, triangles };
118
+ }
119
+
120
+ /** 组装给 UI 展示的属性对象 */
121
+ export function buildAttributes(args: {
122
+ smid: number | null;
123
+ box: SegmentBox | null;
124
+ triangles: number;
125
+ floorHeight?: number;
126
+ }): FeatureAttributes {
127
+ const { smid, box, triangles, floorHeight = DEFAULT_FLOOR_HEIGHT } = args;
128
+ if (!box) return { smid, triangles };
129
+
130
+ const height = box.maxZ - box.minZ;
131
+ return {
132
+ smid,
133
+ height: round(height, 1),
134
+ baseElevation: round(box.minZ, 1),
135
+ size: [round(box.maxX - box.minX, 1), round(box.maxY - box.minY, 1)],
136
+ bboxArea: round((box.maxX - box.minX) * (box.maxY - box.minY), 1),
137
+ floors: Math.max(1, Math.round(height / floorHeight)),
138
+ triangles: box.triangles ?? triangles,
139
+ vertices: box.count,
140
+ bbox: [
141
+ [round(box.minX, 1), round(box.minY, 1), round(box.minZ, 1)],
142
+ [round(box.maxX, 1), round(box.maxY, 1), round(box.maxZ, 1)],
143
+ ],
144
+ };
145
+ }
146
+
147
+ function round(v: number, d: number): number {
148
+ const p = 10 ** d;
149
+ return Math.round(v * p) / p;
150
+ }