s3m-maplibre 0.2.4 → 0.2.5

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/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  **在 MapLibre GL JS 里原生渲染 SuperMap S3M 三维模型** —— 浏览器直接解析 `.s3mb` 瓦片,不需要预先转成 3D Tiles,也不需要 SuperMap 的 WebGL 客户端。
4
4
 
5
- [![npm](https://img.shields.io/badge/npm-0.1.0-blue)](https://www.npmjs.com/)
5
+ [![npm](https://img.shields.io/npm/v/s3m-maplibre?color=blue&label=npm)](https://www.npmjs.com/package/s3m-maplibre)
6
+ [![demo](https://img.shields.io/badge/demo-online-2ea44f)](https://joo1es.github.io/s3m-maplibre/demo/index.html)
6
7
  [![license](https://img.shields.io/badge/license-Apache--2.0-green)](./LICENSE)
7
8
  [![types](https://img.shields.io/badge/types-included-blue)](./src/index.ts)
8
9
 
@@ -39,6 +40,8 @@ iServer / 本地目录 → .scp 配置 + .s3mb 瓦片
39
40
  | 白膜 / 单色 | ✅ | |
40
41
  | 按高度 / 按楼层着色 | ✅ | 高度范围自动统计 |
41
42
  | 按单体着色 | ✅ | 基于 S3M 单体化信息 |
43
+ | 按业务字段着色 | ✅ | 走 iServer 数据服务查 SMID 属性,分类 / 色带 / 分级,见 [docs/API.md](./docs/API.md#按业务字段着色) |
44
+ | 按业务字段过滤 / 抬高 | ✅ | 区间 / 取值 / 谓词过滤,字段值映射成抬高米数,同样只改一张一维纹理 |
42
45
  | 动态扫描 | ✅ | 扫描线沿高度循环 |
43
46
  | 透明度 | ✅ | 实时,不重编译着色器 |
44
47
  | 选中高亮 + 描边 | ✅ | 单体级 |
@@ -48,10 +51,11 @@ iServer / 本地目录 → .scp 配置 + .s3mb 瓦片
48
51
  | 瓦片同材质合批 | ✅ | 倾斜摄影大瓦片自动合批,大幅降低 DrawCall |
49
52
  | 纹理变换与寻址 | ✅ | 支持 `texmodmatrix` 仿射变换与 `addressmode` |
50
53
  | DXT / WebP 纹理 | ✅ | |
51
- | Draco 压缩几何 | ❌ | 遇到会抛明确错误 |
52
- | KTX2 / CRN | ❌ | |
53
54
  | 骨骼动画 | ❌ | 已解析未驱动 |
54
55
  | 地形遮挡 | ❌ | |
56
+ | Draco 压缩几何 | ❌ | 能识别、不能解码,遇到抛明确错误;接法见[未支持的格式](#未支持的格式draco--crn--ktx2) |
57
+ | CRN 纹理(pixelFormat 26 / 27) | ❌ | 官方 SDK 是 crunch.js 转 DXT5 再解码,接口位置已留好 |
58
+ | KTX2 纹理 | — | S3M 官方 `pixelFormat` 枚举里没有这个值,属于「不会遇到」而不是「不支持」 |
55
59
 
56
60
  ---
57
61
 
@@ -61,7 +65,9 @@ iServer / 本地目录 → .scp 配置 + .s3mb 瓦片
61
65
  npm install s3m-maplibre three
62
66
  ```
63
67
 
64
- `three` 是 peer 依赖(`>=0.168.0`)。`maplibre-gl` 不是依赖——图层只是一个符合 `CustomLayerInterface` 的普通对象,MapLibre / Mapbox 都能挂。
68
+ 包在 npm 上是 [s3m-maplibre](https://www.npmjs.com/package/s3m-maplibre),当前 `0.2.5`。
69
+
70
+ `three` 是 peer 依赖(`>=0.168.0`)。`maplibre-gl` 不是依赖——图层只是一个符合 `CustomLayerInterface` 的普通对象,MapLibre / Mapbox 都能挂;不用地图时也能只用解析和瓦片层(见「纯 three.js 使用」)。
65
71
 
66
72
  类型随包发布(`dist/index.d.ts`,由源码直接生成)。three `>=0.168` 自带类型,更老的版本需要自己装 `@types/three`。
67
73
 
@@ -132,13 +138,90 @@ map.on('click', (e) => {
132
138
 
133
139
  ## 演示
134
140
 
135
- 仓库内 `demo/` 是一个完整示例,接了上海某区的真实 iServer 服务(5 个数据集叠加,45 万三角面):
141
+ 在线跑(GitHub Pages,数据随仓库一起发布):
142
+
143
+ - **MapLibre 版** — <https://joo1es.github.io/s3m-maplibre/demo/index.html>
144
+ - **纯 three.js 版** — <https://joo1es.github.io/s3m-maplibre/demo/three.html>
145
+
146
+ (<https://joo1es.github.io/s3m-maplibre/> 是入口页,两个示例都能进)
147
+
148
+ 本地跑:
136
149
 
137
150
  ```bash
138
- npm run demo # http://localhost:8123/s3m-maplibre/demo/index.html
151
+ npm run dev # 开发模式:tsc -w + 静态服务,保存源码后浏览器自动刷新
152
+ npm run demo # 一次性编译后起静态服务(不做监听)
153
+ ```
154
+
155
+ 两个命令都指向 http://localhost:8123/s3m-maplibre/demo/index.html ,`demo/three.html` 是同一台服务器上的纯 three 版。
156
+
157
+ 演示自带两份样例数据(北京 CBD 的 CCTV、comModel),也可以在地址栏输入框里填任意 iServer `realspace` 服务根地址、`datas` 集合或具体的 `.scp`,自动列出数据集并默认勾选第一个。
158
+
159
+ 演示包含:数据集叠加开关、着色模式、透明度、平面/平滑着色、选中描边(棱线 / 外扩两种画法,粗细可调)、全局棱线、点击属性卡;右侧面板还能填 iServer 数据服务地址,按业务字段(KEYID / SmMaxZ…)上色出图例、按区间过滤、按字段值抬高。
160
+
161
+ ---
162
+
163
+ ## 纯 three.js 使用(不用 MapLibre)
164
+
165
+ 可以。图层本身只是把三层粘到地图上,这三层都是宿主无关的:
166
+
167
+ | 层 | 需要什么 | 与地图的关系 |
168
+ |---|---|---|
169
+ | `core/` 解析 | 一个 `ArrayBuffer` | 完全无关,Node 里也能跑 |
170
+ | `tileset/` 瓦片树 | `fetch` + 一个 `three.Group` 当容器 | 只要喂相机位置算 LOD |
171
+ | `render/` 几何 | WebGL 上下文 | 只产出 `THREE.Group` / `Mesh` |
172
+
173
+ ```js
174
+ import * as THREE from 'three';
175
+ import { S3MTileset, createSharedUniforms, STYLE } from 's3m-maplibre';
176
+
177
+ const uniforms = createSharedUniforms(); // 材质 uniform 全局共享
178
+ uniforms.uMode.value = STYLE.TEXTURE; // 改这一行就是切样式
179
+
180
+ const root = new THREE.Group();
181
+ root.rotation.x = -Math.PI / 2; // S3M 顶点是 ENU(z 朝上),three 是 y 朝上
182
+ scene.add(root);
183
+
184
+ const tileset = new S3MTileset({
185
+ configUrl: '.../jingmo.scp',
186
+ uniforms,
187
+ onTile: (node) => root.add(node.group), // node.group 就是这片瓦片拼好的 Group(米制局部坐标)
188
+ });
189
+ await tileset.init(); // 返回时首棵瓦片已就绪
190
+
191
+ // 每帧:把相机位置换算回 ENU 喂给 LOD 调度,父子瓦片自己按需细分
192
+ function tick() {
193
+ const p = camera.position;
194
+ tileset.update([p.x, -p.z, p.y], THREE.MathUtils.degToRad(camera.fov), renderer.domElement.height);
195
+ renderer.render(scene, camera);
196
+ requestAnimationFrame(tick);
197
+ }
139
198
  ```
140
199
 
141
- 演示包含:数据集叠加开关、7 种着色模式、透明度、平面/平滑着色、选中描边、全局棱线、点击属性卡。
200
+ 完整可运行版本见 [`demo/three.js`](./demo/three.js)(一个文件,含轨道控制、切片统计、样式切换)。
201
+
202
+ 自己搭宿主时需要补的几件事,`S3MLayer` 不会代劳:
203
+
204
+ - **光照**:`MeshStandardMaterial` 的标准光照,自己加 `HemisphereLight` + `DirectionalLight`;
205
+ - **多数据集对齐**:每份数据有各自的锚点,`S3MLayer` 会按墨卡托算锚点差、写进数据集 `Group.position`(米),纯 three 里需要自己算一次;
206
+ - **贴地 / 经纬度**:不接地图就没有「贴在地球上」这一步,只有局部米制坐标;
207
+ - **拾取 / 选中描边 / 样式切换 UI**:`Picker`、等宽棱线、`setStyle` 都是 `S3MLayer` 上的,纯 three 用法需要直接操作 `uniforms` 与 Mesh。
208
+
209
+ ---
210
+
211
+ ## 未支持的格式:Draco / CRN / KTX2
212
+
213
+ 这三个常被问到,结论分别不同:
214
+
215
+ **Draco 压缩几何 —— 能解决,缺的是样本。**
216
+ 官方 SDK 里有 `S3MParser/S3MDracoDecode.js` + `draco_decode.module.js`(对 `DecodeBufferToPointCloud` 的封装,POSITION / NORMAL / TEX_COORD 三个属性,再按 `compressOptions` 分支处理)。接法与本项目的 MeshOpt 完全一致:内联 wasm 解码器、只在遇到 `VertexTag.DRACO` 时动态 `import`、解码后走同一套 attribute 组装 + 量化还原。真正的成本不在写代码,而在**标定容器**——MeshOpt 当时就是靠 60 个真实瓦片逐字节对齐才做对的,Draco 的 attribute 映射、`compressOptions` 与实际数据是否一致也必须拿样例验。仓库里没有 Draco 瓦片,所以现在是「识别到就抛明确错误」。
217
+
218
+ **CRN 纹理(`pixelFormat` 26 / 27)—— 能解决,纯接解码器。**
219
+ SuperMap 自己的做法是 `crunch.js` 把 CRN 转码成 DXT5 字节,再走普通 DXT 上传。本项目的 `TextureFactory` 在 DXT 那条路上已经完整(DXT1/3/4/5 都解到 RGBA8),所以只要在 `decodeTexture` 里加一个 CRN 分支:crunch 转 DXT5 → 复用现成解码。工作量约一个解码器文件 + 一个分支,同样需要一份 CRN 纹理样例来验字节。
220
+
221
+ **KTX2 —— 不是「不支持」,是 S3M 里基本不会出现。**
222
+ 官方 `S3MPixelFormat` 枚举(`1/2/3/4/5/6/7/10/11/12/13/14/17…21/25/26/27/28`)里没有 KTX2 / Basis 的条目,iServer 也不会发。真要支持,得自己引入 `basis_transcoder.wasm` 并把 KTX2 容器转成 RGBA32 或 DXT,但那是给「别人转成 KTX2 再塞进 S3M」的场景准备的,没有数据样本可测。
223
+
224
+ 两者的共同前提:**要有样例数据**。手头有 Draco 压缩的瓦片或带 CRN 纹理的服务地址,把 `.s3mb` 或服务地址给我,就能按 MeshOpt 那套方式标定 + 实现 + 回归。
142
225
 
143
226
  ---
144
227
 
@@ -228,9 +311,10 @@ S3M 顶点(数据集局部 ENU,米)
228
311
 
229
312
  ## 已知限制
230
313
 
231
- - **Draco 压缩几何**未实现。MeshOpt 已支持;遇到 Draco 会抛带说明的错误。
314
+ - **Draco 压缩几何 / CRN 纹理**未实现(可解,缺样例数据),见[未支持的格式](#未支持的格式draco--crn--ktx2)。
232
315
  - **地形遮挡**未实现:MapLibre 的地形深度没进 custom layer 的深度缓冲,建筑不会被地形裁掉。
233
316
  - 跨城市量级的数据集叠加会重新引入 float32 精度问题。
317
+ - MVT 式的「按需懒加载 + 卸载」没有做:已加载的瓦片常驻,只受 `maxTiles` 限制。
234
318
 
235
319
  ---
236
320
 
@@ -249,11 +333,14 @@ src/
249
333
  S3MLayer.ts 图层主体(多数据集 / 样式 / 拾取)
250
334
  GeometryBuilder.ts 几何与材质组装
251
335
  StyleMaterial.ts 着色器注入的着色系统
336
+ EdgeLines.ts 屏幕空间等宽棱线(选中描边 / 全局棱线)
252
337
  Picker.ts GPU 拾取
253
338
  TextureFactory.ts 纹理解码
254
339
  vendor/ meshoptimizer 解码器(wasm 内联,原样保留)
255
- demo/ 演示
256
- tools/ 自检脚本(解析校验、MeshOpt 回归、静态服务)
340
+ demo/
341
+ index.html / main.js MapLibre 完整示例(控制面板 + 属性卡)
342
+ three.html / three.js 纯 three.js 示例(无地图)
343
+ tools/ 自检脚本(解析校验、MeshOpt 回归、带热更新的静态服务)
257
344
  ```
258
345
 
259
346
  ---
@@ -0,0 +1,97 @@
1
+ /**
2
+ * iServer 数据服务属性查询
3
+ *
4
+ * S3M 瓦片里**只有单体 ID**(`MonomerIDInfo.id`,即 SmID)和顶点区间,没有任何业务字段;
5
+ * 业务属性在 iServer 的「数据服务」里:同一份数据源被发布成 `data-xxx/rest/data` 的那个服务。
6
+ *
7
+ * 这里的职责只有一件事:按 SmID 批量取字段值。字段表、分片、缓存、去重都在内部消化,
8
+ * 上层(`S3MLayer.colorByField`)只关心「拿到值之后怎么算颜色」。
9
+ *
10
+ * 实测(iServer 2026,GEOMODEL3D 数据集):
11
+ * - `queryOption: 'ATTRIBUTE'` 被忽略:放 body、放 query string、大小写都没用,
12
+ * 2D / 3D 数据集都照样返回几何;模型数据集还会给每条要素带上 `geometry.model`
13
+ * —— 那是 deflate 压缩后的 S3M 模型字节,约 2.5KB/条,占整个响应的 90%;
14
+ * - 没有「只取属性」的接口。想省带宽只能逐条 GET `/rest/data/feature/{a}-{b}-{idx}.json`
15
+ * (957B/条,model 为 null),但那是拿请求数换带宽:300 条并发 16 反而比一次 POST 慢;
16
+ * - `.fastjson` 比 `.json` 少 40%:model 用 hex(2 字符/字节)而不是数字数组(3~4 字符/字节)。
17
+ * 代价是它不是严格 JSON(`0.0D`、`1L`、`x'ABCD'` 都是 Java 字面量),要先清洗;
18
+ * - `maxFeatures` 生效,`fromIndex` / `toIndex` 不生效 —— 只能切前 N 条,不能翻页;
19
+ * - 同一份数据如果另外发布了二维数据源(`DataSource_erwei`),查它只有 1/5 的体积;
20
+ * - CORS 是开的(`Access-Control-Allow-Origin: *`),浏览器里可以直接 POST。
21
+ *
22
+ * 所以默认策略:`.fastjson` + 一次性把**全部字段**取回来(字段名很便宜,model 才是大头),
23
+ * 这样换专题字段时全部命中缓存,不再发请求。
24
+ */
25
+ export interface AttributeQueryOptions {
26
+ /** 数据服务根地址,形如 https://host/iserver/services/data-xxx/rest/data */
27
+ dataServiceUrl: string;
28
+ /** 数据集,支持 "DataSource:zhoubian" 或只写 "zhoubian"(默认补 DataSource:) */
29
+ datasetName: string;
30
+ /** 与 S3M 单体 ID 对应的字段,默认 SmID(对应 scp 里的 idFieldName) */
31
+ idField?: string;
32
+ /** 只取这些字段,默认取字段表里除几何/索引外的全部(推荐留空,全字段进缓存后换字段零请求) */
33
+ fields?: string[];
34
+ /** 每次请求带多少个 id,默认 200 */
35
+ chunkSize?: number;
36
+ /** 响应格式,默认 'auto':模型数据集用 fastjson(模型是 byte[],hex 比数字数组省 43%),
37
+ * 二维数据集用 json(坐标是 double,hex 反而更胖)。也可以强制指定 */
38
+ format?: 'auto' | 'fastjson' | 'json';
39
+ /** 便于测试时替换,默认用全局 fetch */
40
+ fetchImpl?: typeof fetch;
41
+ }
42
+ export interface AttributeField {
43
+ name: string;
44
+ type?: string;
45
+ alias?: string;
46
+ }
47
+ export type AttributeRow = Record<string, string | number>;
48
+ /** 一次查询的代价,用来在 UI 上展示「查了多大、发了几个请求」 */
49
+ export interface AttributeQueryStats {
50
+ requests: number;
51
+ bytes: number;
52
+ ms: number;
53
+ }
54
+ export declare class AttributeQuery {
55
+ readonly dataServiceUrl: string;
56
+ readonly datasourceName: string;
57
+ readonly datasetName: string;
58
+ readonly idField: string;
59
+ private readonly _fields;
60
+ private readonly _chunkSize;
61
+ private readonly _formatOption;
62
+ private readonly _fetch;
63
+ private readonly _cache;
64
+ private _schema;
65
+ private _resolvedFields;
66
+ private _format;
67
+ private _stats;
68
+ constructor(options: AttributeQueryOptions);
69
+ /** 数据集的 REST 路径:.../datasources/name/{ds}/datasets/name/{name} */
70
+ get datasetPath(): string;
71
+ /** 累计的请求代价(不清零,用来观察整体开销) */
72
+ get stats(): AttributeQueryStats;
73
+ /** 字段表(顺手也用来判断「这个数据集到底有没有属性表」) */
74
+ loadSchema(): Promise<AttributeField[]>;
75
+ /** 已经缓存过、不需要再请求的 id */
76
+ hasCached(smid: number): boolean;
77
+ clearCache(): void;
78
+ /**
79
+ * 按 SmID 批量取属性。自动跳过已缓存的、按 chunkSize 分片,并丢弃几何。
80
+ * 缓存的是**整行**(全部字段),所以换专题字段时不会再发请求。
81
+ */
82
+ queryBySmids(smids: number[]): Promise<Map<number, AttributeRow>>;
83
+ private _resolveFields;
84
+ /**
85
+ * 选响应格式。
86
+ *
87
+ * 模型数据集的体积几乎全是 `geometry.model`(byte[]),fastjson 用 hex 表示只要 2 字符/字节,
88
+ * json 是十进制数字数组(3~4 字符/字节),所以 fastjson 又快又小;
89
+ * 二维数据集的几何是坐标数组(double),hex 反而比十进制更胖(实测 3.9KB vs 2.4KB/条),
90
+ * 这种就留在 json。用 `features.json` 返回的 geometryType 判断,一次几十字节,跟着实例缓存。
91
+ */
92
+ private _resolveFormat;
93
+ private _queryChunk;
94
+ private _post;
95
+ private _parse;
96
+ }
97
+ //# sourceMappingURL=AttributeQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttributeQuery.d.ts","sourceRoot":"","sources":["../../src/core/AttributeQuery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;mDAC+C;IAC/C,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IACtC,0BAA0B;IAC1B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAE3D,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAwBD,qBAAa,cAAc;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA+B;IAC7D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,MAAM,CAAyD;gBAE3D,OAAO,EAAE,qBAAqB;IAgB1C,kEAAkE;IAClE,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,4BAA4B;IAC5B,IAAI,KAAK,IAAI,mBAAmB,CAE/B;IAED,kCAAkC;IAC5B,UAAU,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAU7C,uBAAuB;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIhC,UAAU,IAAI,IAAI;IAKlB;;;OAGG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;YAuBzD,cAAc;IAY5B;;;;;;;OAOG;YACW,cAAc;YAgBd,WAAW;YAsCX,KAAK;IAenB,OAAO,CAAC,MAAM;CAGf"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * iServer 数据服务属性查询
3
+ *
4
+ * S3M 瓦片里**只有单体 ID**(`MonomerIDInfo.id`,即 SmID)和顶点区间,没有任何业务字段;
5
+ * 业务属性在 iServer 的「数据服务」里:同一份数据源被发布成 `data-xxx/rest/data` 的那个服务。
6
+ *
7
+ * 这里的职责只有一件事:按 SmID 批量取字段值。字段表、分片、缓存、去重都在内部消化,
8
+ * 上层(`S3MLayer.colorByField`)只关心「拿到值之后怎么算颜色」。
9
+ *
10
+ * 实测(iServer 2026,GEOMODEL3D 数据集):
11
+ * - `queryOption: 'ATTRIBUTE'` 被忽略:放 body、放 query string、大小写都没用,
12
+ * 2D / 3D 数据集都照样返回几何;模型数据集还会给每条要素带上 `geometry.model`
13
+ * —— 那是 deflate 压缩后的 S3M 模型字节,约 2.5KB/条,占整个响应的 90%;
14
+ * - 没有「只取属性」的接口。想省带宽只能逐条 GET `/rest/data/feature/{a}-{b}-{idx}.json`
15
+ * (957B/条,model 为 null),但那是拿请求数换带宽:300 条并发 16 反而比一次 POST 慢;
16
+ * - `.fastjson` 比 `.json` 少 40%:model 用 hex(2 字符/字节)而不是数字数组(3~4 字符/字节)。
17
+ * 代价是它不是严格 JSON(`0.0D`、`1L`、`x'ABCD'` 都是 Java 字面量),要先清洗;
18
+ * - `maxFeatures` 生效,`fromIndex` / `toIndex` 不生效 —— 只能切前 N 条,不能翻页;
19
+ * - 同一份数据如果另外发布了二维数据源(`DataSource_erwei`),查它只有 1/5 的体积;
20
+ * - CORS 是开的(`Access-Control-Allow-Origin: *`),浏览器里可以直接 POST。
21
+ *
22
+ * 所以默认策略:`.fastjson` + 一次性把**全部字段**取回来(字段名很便宜,model 才是大头),
23
+ * 这样换专题字段时全部命中缓存,不再发请求。
24
+ */
25
+ const DEFAULT_ID_FIELD = 'SmID';
26
+ const DEFAULT_CHUNK_SIZE = 200;
27
+ /** 几何/索引字段取回来没用,还特别占带宽 */
28
+ const SKIP_FIELDS = new Set(['smgeometry', 'smindexkey', 'smgeoposition']);
29
+ /**
30
+ * fastjson 不是严格 JSON,服务端会把 Java 字面量直接吐出来:
31
+ * `"model":x'D7A34040...'`(byte[] 的 hex)、`"rotationX":0.0D`、`"id":423L`
32
+ * 这些在 JSON.parse 里全是语法错误。model 恰好是我们唯一不想要的东西(占 90% 体积),
33
+ * 直接在字符串上抹掉,连解析都省了。
34
+ */
35
+ function sanitizeFastjson(text) {
36
+ return text
37
+ .replace(/x'[0-9A-Fa-f]*'/g, 'null')
38
+ .replace(/(\d)[dDlLfF](?=\s*[,}\]])/g, '$1');
39
+ }
40
+ export class AttributeQuery {
41
+ dataServiceUrl;
42
+ datasourceName;
43
+ datasetName;
44
+ idField;
45
+ _fields;
46
+ _chunkSize;
47
+ _formatOption;
48
+ _fetch;
49
+ _cache = new Map();
50
+ _schema = null;
51
+ _resolvedFields = null;
52
+ _format = null;
53
+ _stats = { requests: 0, bytes: 0, ms: 0 };
54
+ constructor(options) {
55
+ if (!options?.dataServiceUrl)
56
+ throw new Error('[s3m] AttributeQuery 缺少 dataServiceUrl');
57
+ const full = options.datasetName?.includes(':') ? options.datasetName : `DataSource:${options.datasetName ?? ''}`;
58
+ const [ds, name] = full.split(':');
59
+ if (!name)
60
+ throw new Error(`[s3m] datasetName 格式应为 "数据源:数据集",收到 "${options.datasetName}"`);
61
+ this.dataServiceUrl = options.dataServiceUrl.replace(/\/+$/, '');
62
+ this.datasourceName = ds;
63
+ this.datasetName = name;
64
+ this.idField = options.idField ?? DEFAULT_ID_FIELD;
65
+ this._fields = options.fields?.length ? options.fields : null;
66
+ this._chunkSize = Math.max(1, options.chunkSize ?? DEFAULT_CHUNK_SIZE);
67
+ this._formatOption = options.format ?? 'auto';
68
+ this._fetch = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
69
+ }
70
+ /** 数据集的 REST 路径:.../datasources/name/{ds}/datasets/name/{name} */
71
+ get datasetPath() {
72
+ return `${this.dataServiceUrl}/datasources/name/${encodeURIComponent(this.datasourceName)}/datasets/name/${encodeURIComponent(this.datasetName)}`;
73
+ }
74
+ /** 累计的请求代价(不清零,用来观察整体开销) */
75
+ get stats() {
76
+ return { ...this._stats };
77
+ }
78
+ /** 字段表(顺手也用来判断「这个数据集到底有没有属性表」) */
79
+ async loadSchema() {
80
+ if (this._schema)
81
+ return this._schema;
82
+ const res = await this._fetch(`${this.datasetPath}/fields.json`);
83
+ this._stats.requests++;
84
+ if (!res.ok)
85
+ throw new Error(`[s3m] 读取字段表失败 ${res.status}:${this.datasetPath}/fields.json`);
86
+ const json = (await res.json());
87
+ this._schema = (json.fieldNames ?? []).map((name) => ({ name }));
88
+ return this._schema;
89
+ }
90
+ /** 已经缓存过、不需要再请求的 id */
91
+ hasCached(smid) {
92
+ return this._cache.has(smid);
93
+ }
94
+ clearCache() {
95
+ this._cache.clear();
96
+ this._stats = { requests: 0, bytes: 0, ms: 0 };
97
+ }
98
+ /**
99
+ * 按 SmID 批量取属性。自动跳过已缓存的、按 chunkSize 分片,并丢弃几何。
100
+ * 缓存的是**整行**(全部字段),所以换专题字段时不会再发请求。
101
+ */
102
+ async queryBySmids(smids) {
103
+ const ids = [...new Set(smids.filter((n) => Number.isFinite(n)))];
104
+ const result = new Map();
105
+ for (const id of ids) {
106
+ const hit = this._cache.get(id);
107
+ if (hit)
108
+ result.set(id, hit);
109
+ }
110
+ const todo = ids.filter((id) => !this._cache.has(id));
111
+ if (!todo.length)
112
+ return result;
113
+ const fields = await this._resolveFields();
114
+ for (let i = 0; i < todo.length; i += this._chunkSize) {
115
+ const chunk = todo.slice(i, i + this._chunkSize);
116
+ const rows = await this._queryChunk(chunk, fields);
117
+ for (const [id, row] of rows) {
118
+ this._cache.set(id, row);
119
+ result.set(id, row);
120
+ }
121
+ }
122
+ return result;
123
+ }
124
+ async _resolveFields() {
125
+ if (this._resolvedFields)
126
+ return this._resolvedFields;
127
+ if (this._fields) {
128
+ this._resolvedFields = this._fields;
129
+ return this._resolvedFields;
130
+ }
131
+ const schema = await this.loadSchema();
132
+ const fields = schema.map((f) => f.name).filter((n) => !SKIP_FIELDS.has(n.toLowerCase()));
133
+ this._resolvedFields = fields.length ? fields : [this.idField];
134
+ return this._resolvedFields;
135
+ }
136
+ /**
137
+ * 选响应格式。
138
+ *
139
+ * 模型数据集的体积几乎全是 `geometry.model`(byte[]),fastjson 用 hex 表示只要 2 字符/字节,
140
+ * json 是十进制数字数组(3~4 字符/字节),所以 fastjson 又快又小;
141
+ * 二维数据集的几何是坐标数组(double),hex 反而比十进制更胖(实测 3.9KB vs 2.4KB/条),
142
+ * 这种就留在 json。用 `features.json` 返回的 geometryType 判断,一次几十字节,跟着实例缓存。
143
+ */
144
+ async _resolveFormat() {
145
+ if (this._format)
146
+ return this._format;
147
+ if (this._formatOption !== 'auto')
148
+ return (this._format = this._formatOption);
149
+ try {
150
+ const res = await this._fetch(`${this.datasetPath}/features.json?fromIndex=0&toIndex=1`);
151
+ this._stats.requests++;
152
+ const json = (await res.json());
153
+ const isModel = String(json.geometryType ?? '').toUpperCase().includes('MODEL');
154
+ this._format = isModel ? 'fastjson' : 'json';
155
+ }
156
+ catch {
157
+ // 探测失败就当模型数据集处理:S3M 场景下这是绝大多数
158
+ this._format = 'fastjson';
159
+ }
160
+ return this._format;
161
+ }
162
+ async _queryChunk(ids, fields) {
163
+ const body = {
164
+ getFeatureMode: 'SQL',
165
+ datasetNames: [`${this.datasourceName}:${this.datasetName}`],
166
+ // 服务端会忽略它,但留着:将来遇到真的听话的版本就能省下全部几何
167
+ queryOption: 'ATTRIBUTE',
168
+ queryParameter: {
169
+ attributeFilter: `${this.idField} IN (${ids.join(',')})`,
170
+ fields,
171
+ },
172
+ };
173
+ const format = await this._resolveFormat();
174
+ const raw = await this._post(body, format);
175
+ let json;
176
+ try {
177
+ json = this._parse(raw);
178
+ }
179
+ catch (e) {
180
+ // fastjson 的方言没吃透时留一条退路,别让一次格式变更把功能整个弄挂
181
+ if (format !== 'fastjson')
182
+ throw e;
183
+ json = this._parse(await this._post(body, 'json'));
184
+ }
185
+ if (json.error)
186
+ throw new Error(`[s3m] 属性查询失败:${json.error.errorMsg ?? '未知错误'}`);
187
+ const out = new Map();
188
+ for (const f of json.features ?? []) {
189
+ const names = f.fieldNames ?? [];
190
+ const values = f.fieldValues ?? [];
191
+ const row = {};
192
+ for (let i = 0; i < names.length; i++)
193
+ row[names[i]] = values[i];
194
+ // 优先用 idField(SmID)关联,拿不到才退回要素 ID
195
+ const rawId = row[this.idField] ?? f.ID;
196
+ const id = Number(rawId);
197
+ if (Number.isFinite(id))
198
+ out.set(id, row);
199
+ }
200
+ return out;
201
+ }
202
+ async _post(body, format) {
203
+ const t0 = performance.now();
204
+ const res = await this._fetch(`${this.dataServiceUrl}/featureResults.${format}?returnContent=true`, {
205
+ method: 'POST',
206
+ headers: { 'Content-Type': 'application/json' },
207
+ body: JSON.stringify(body),
208
+ });
209
+ const text = await res.text();
210
+ this._stats.requests++;
211
+ this._stats.bytes += text.length;
212
+ this._stats.ms += Math.round(performance.now() - t0);
213
+ if (!res.ok)
214
+ throw new Error(`[s3m] 属性查询失败 ${res.status}:${text.slice(0, 200)}`);
215
+ return text;
216
+ }
217
+ _parse(text) {
218
+ return JSON.parse(text.includes("x'") || /\d[dDlL](?=\s*[,}\]])/.test(text) ? sanitizeFastjson(text) : text);
219
+ }
220
+ }
221
+ //# sourceMappingURL=AttributeQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttributeQuery.js","sourceRoot":"","sources":["../../src/core/AttributeQuery.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -12,11 +12,13 @@ export { parseS3MB, S3M_VERSION, VertexTag, VertexTagV3 } from './core/S3MBParse
12
12
  export type { ParseOptions, InflateFn } from './core/S3MBParser.js';
13
13
  export { BinaryReader } from './core/BinaryReader.js';
14
14
  export { resolveFeature, measureSegments, buildAttributes, DEFAULT_FLOOR_HEIGHT } from './core/FeatureInfo.js';
15
+ export { AttributeQuery } from './core/AttributeQuery.js';
16
+ export type { AttributeQueryOptions, AttributeField, AttributeRow, } from './core/AttributeQuery.js';
15
17
  export type { Vec3, BoundingBox, AttrDType, VertexAttribute, IndexPackage, VertexPackage, PickSegment, PickEntry, GeoPackageEntry, Geode, PageLOD, GroupNode, S3MTexture, ParsedTile, Anchor, Stats, FeatureAttributes, SegmentBox, StyleUniforms, } from './core/types.js';
16
18
  export { S3MTileset } from './tileset/S3MTileset.js';
17
19
  export type { TileNode, BuildTileFn, S3MTilesetOptions } from './tileset/S3MTileset.js';
18
20
  export { S3MLayer, DEFAULT_STYLE, STYLE, STYLE_LABELS, SELECT_MODE, SELECT_MODE_LABELS } from './render/S3MLayer.js';
19
- export type { S3MLayerOptions, S3MStyle, SelectMode, FeatureStyle, S3MFilter, S3MDatasetOptions, S3MDatasetEntry, PickResult, MapLike, MapMouseEventLike, } from './render/S3MLayer.js';
21
+ export type { S3MLayerOptions, S3MStyle, SelectMode, FeatureStyle, S3MFilter, S3MDatasetOptions, S3MDatasetEntry, PickResult, MapLike, MapMouseEventLike, ColorByFieldOptions, ThematicLegendEntry, ThematicResult, FieldFilterOptions, FieldFilterResult, FieldElevationOptions, FieldElevationResult, } from './render/S3MLayer.js';
20
22
  export { buildTileObjects } from './render/GeometryBuilder.js';
21
23
  export type { BuildTileOptions, BuildTileResult, TileBuildStats } from './render/GeometryBuilder.js';
22
24
  export { createTexture, PixelFormat, setMaxAnisotropy } from './render/TextureFactory.js';
@@ -25,5 +27,7 @@ export { createSharedUniforms, createStyledMaterial, createOutlineMaterial } fro
25
27
  export type { StyleMode, StyledMaterial, StyledMaterialOptions, OutlineMaterial } from './render/StyleMaterial.js';
26
28
  export { Picker } from './render/Picker.js';
27
29
  export type { PickHit } from './render/Picker.js';
30
+ export { createThickLineMaterial, createLineSharedUniforms, buildThickLineGeometry, buildFeatureEdgeSegments, } from './render/EdgeLines.js';
31
+ export type { ThickLineMaterial, LineSharedUniforms, EdgeVertexRange } from './render/EdgeLines.js';
28
32
  export { S3MLayer as default } from './render/S3MLayer.js';
29
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACtF,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAG/G,YAAY,EACV,IAAI,EACJ,WAAW,EACX,SAAS,EACT,eAAe,EACf,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,eAAe,EACf,KAAK,EACL,OAAO,EACP,SAAS,EACT,UAAU,EACV,UAAU,EACV,MAAM,EACN,KAAK,EACL,iBAAiB,EACjB,UAAU,EACV,aAAa,GACd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGxF,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrH,YAAY,EACV,eAAe,EACf,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,OAAO,EACP,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAErG,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC1F,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC9G,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEnH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,QAAQ,IAAI,OAAO,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACtF,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,YAAY,GACb,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EACV,IAAI,EACJ,WAAW,EACX,SAAS,EACT,eAAe,EACf,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,eAAe,EACf,KAAK,EACL,OAAO,EACP,SAAS,EACT,UAAU,EACV,UAAU,EACV,MAAM,EACN,KAAK,EACL,iBAAiB,EACjB,UAAU,EACV,aAAa,GACd,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGxF,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrH,YAAY,EACV,eAAe,EACf,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,OAAO,EACP,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAErG,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC1F,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC9G,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEnH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEpG,OAAO,EAAE,QAAQ,IAAI,OAAO,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@
12
12
  export { parseS3MB, S3M_VERSION, VertexTag, VertexTagV3 } from './core/S3MBParser.js';
13
13
  export { BinaryReader } from './core/BinaryReader.js';
14
14
  export { resolveFeature, measureSegments, buildAttributes, DEFAULT_FLOOR_HEIGHT } from './core/FeatureInfo.js';
15
+ export { AttributeQuery } from './core/AttributeQuery.js';
15
16
  // —— 瓦片层
16
17
  export { S3MTileset } from './tileset/S3MTileset.js';
17
18
  // —— 渲染层
@@ -20,5 +21,6 @@ export { buildTileObjects } from './render/GeometryBuilder.js';
20
21
  export { createTexture, PixelFormat, setMaxAnisotropy } from './render/TextureFactory.js';
21
22
  export { createSharedUniforms, createStyledMaterial, createOutlineMaterial } from './render/StyleMaterial.js';
22
23
  export { Picker } from './render/Picker.js';
24
+ export { createThickLineMaterial, createLineSharedUniforms, buildThickLineGeometry, buildFeatureEdgeSegments, } from './render/EdgeLines.js';
23
25
  export { S3MLayer as default } from './render/S3MLayer.js';
24
26
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,oCAAoC;AACpC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAyB/G,SAAS;AACT,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD,SAAS;AACT,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAcrH,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAG1F,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAG9G,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EAAE,QAAQ,IAAI,OAAO,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,oCAAoC;AACpC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC/G,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AA8B1D,SAAS;AACT,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD,SAAS;AACT,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAqBrH,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAG1F,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAG9G,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,QAAQ,IAAI,OAAO,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 屏幕空间等宽棱线
3
+ *
4
+ * 为什么不用 `LineBasicMaterial`:WebGL 下 `linewidth` 在绝大多数平台被忽略,
5
+ * 线宽恒定 1px,想「调粗细」是做不到的。所以要自己把每条线段摊成一条面向屏幕的四边形:
6
+ * 每条棱 4 个顶点,顶点着色器里把两端点投到屏幕空间,沿屏幕法线方向撑开 `uWidth` 个像素,
7
+ * 再沿线段方向各外延半线宽,把相邻棱线的接缝盖住,避免拐角处出现缺口。
8
+ *
9
+ * 顶点布局(每段 4 个顶点,2 个三角形):
10
+ * v0 = A 端, aSide = -1 v1 = A 端, aSide = +1
11
+ * v2 = B 端, aSide = +1 v3 = B 端, aSide = -1
12
+ * B 端的 side 之所以反号,是因为着色器里方向向量统一取「aOther → position」,
13
+ * B 端算出来的法线正好和 A 端相反,反号一次才能拼成同一个四边形。
14
+ */
15
+ import * as THREE from 'three';
16
+ /** 所有等宽线材质共享的分辨率 uniform(每帧更新一次即可) */
17
+ export interface LineSharedUniforms {
18
+ uResolution: {
19
+ value: THREE.Vector2;
20
+ };
21
+ uDpr: {
22
+ value: number;
23
+ };
24
+ }
25
+ export interface ThickLineMaterial extends THREE.ShaderMaterial {
26
+ uniforms: LineSharedUniforms & {
27
+ uColor: {
28
+ value: THREE.Color;
29
+ };
30
+ uOpacity: {
31
+ value: number;
32
+ };
33
+ uWidth: {
34
+ value: number;
35
+ };
36
+ uElevation: {
37
+ value: number;
38
+ };
39
+ [uniform: string]: THREE.IUniform;
40
+ };
41
+ }
42
+ export declare function createLineSharedUniforms(): LineSharedUniforms;
43
+ export declare function createThickLineMaterial(shared: LineSharedUniforms, opts?: {
44
+ color?: THREE.ColorRepresentation;
45
+ opacity?: number;
46
+ width?: number;
47
+ elevation?: number;
48
+ }): ThickLineMaterial;
49
+ /**
50
+ * 线段对(每 6 个 float:x1 y1 z1 x2 y2 z2)→ 可绘制等宽线的几何体。
51
+ */
52
+ export declare function buildThickLineGeometry(segments: ArrayLike<number>): THREE.BufferGeometry;
53
+ export interface EdgeVertexRange {
54
+ vertexOffset: number;
55
+ vertexCount: number;
56
+ }
57
+ /**
58
+ * 从几何里只挑出「指定顶点区间」的棱线,作为线段对返回。
59
+ *
60
+ * `ranges` 为空表示整块网格。判定规则与 `THREE.EdgesGeometry` 一致:
61
+ * - 只被一个三角形用到的边 → 边界边,保留;
62
+ * - 被两个三角形共用、且两面夹角大于 `creaseAngle` → 折边,保留;
63
+ * - 其余(共面内部边)→ 丢掉,否则平面会被切成三角网格。
64
+ *
65
+ * 非索引几何没有共享顶点,直接用顶点号去重会让每条边都被判成边界边。
66
+ * 所以这里先按位置量化把顶点归一化成 canonical id,再走同一套逻辑。
67
+ */
68
+ export declare function buildFeatureEdgeSegments(geometry: THREE.BufferGeometry, ranges?: EdgeVertexRange[] | null, creaseAngle?: number): Float32Array | null;
69
+ //# sourceMappingURL=EdgeLines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EdgeLines.d.ts","sourceRoot":"","sources":["../../src/render/EdgeLines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,sCAAsC;AACtC,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAA;KAAE,CAAC;IACtC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACzB;AAED,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc;IAC7D,QAAQ,EAAE,kBAAkB,GAAG;QAC7B,MAAM,EAAE;YAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAA;SAAE,CAAC;QAC/B,QAAQ,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5B,MAAM,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAC1B,UAAU,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAC9B,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC;KACnC,CAAC;CACH;AAED,wBAAgB,wBAAwB,IAAI,kBAAkB,CAK7D;AAkDD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,KAAK,CAAC,mBAAmB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACrG,iBAAiB,CAsBnB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,cAAc,CA8DxF;AAID,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,KAAK,CAAC,cAAc,EAC9B,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,IAAI,EACjC,WAAW,SAAK,GACf,YAAY,GAAG,IAAI,CAmIrB"}