s3m-three 0.4.26 → 0.4.27
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-DHOVIrAU.js → s3mWorker-BfSBNvF5.js} +1 -1
- package/dist/browser/s3m-three.iife.ext-three.js +52 -40
- package/dist/browser/s3m-three.iife.js +52 -40
- package/dist/core/BakeMercator.d.ts +54 -0
- package/dist/core/BakeMercator.d.ts.map +1 -0
- package/dist/core/BakeMercator.js +65 -0
- package/dist/core/BakeMercator.js.map +1 -0
- package/dist/core/TileGeometry.d.ts +7 -0
- package/dist/core/TileGeometry.d.ts.map +1 -1
- package/dist/core/TileGeometry.js +20 -0
- package/dist/core/TileGeometry.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +27 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +18 -4
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/GeoAdapter.d.ts +49 -24
- package/dist/render/GeoAdapter.d.ts.map +1 -1
- package/dist/render/GeoAdapter.js +96 -8
- package/dist/render/GeoAdapter.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +17 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +31 -2
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +72 -2
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +269 -9
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +5 -0
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +21 -0
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/tilePlacement.d.ts +84 -0
- package/dist/render/tilePlacement.d.ts.map +1 -0
- package/dist/render/tilePlacement.js +71 -0
- package/dist/render/tilePlacement.js.map +1 -0
- package/dist/render/viewer/EdgeDecorator.d.ts +1 -0
- package/dist/render/viewer/EdgeDecorator.d.ts.map +1 -1
- package/dist/render/viewer/EdgeDecorator.js +3 -0
- package/dist/render/viewer/EdgeDecorator.js.map +1 -1
- package/dist/render/viewer/PickService.d.ts +13 -0
- package/dist/render/viewer/PickService.d.ts.map +1 -1
- package/dist/render/viewer/PickService.js +24 -15
- package/dist/render/viewer/PickService.js.map +1 -1
- package/dist/render/viewer/types.d.ts +53 -0
- package/dist/render/viewer/types.d.ts.map +1 -1
- package/dist/render/viewer/types.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +43 -1
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +202 -2
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/worker/s3mWorker.js +115 -62
- package/dist/worker/s3mWorker.js.map +1 -1
- package/docs/API.md +18 -0
- package/package.json +1 -1
- package/src/core/BakeMercator.ts +96 -0
- package/src/core/TileGeometry.ts +26 -0
- package/src/index.ts +11 -1
- package/src/render/DatasetManager.ts +43 -5
- package/src/render/GeoAdapter.ts +139 -11
- package/src/render/GeometryBuilder.ts +53 -2
- package/src/render/S3MViewer.ts +306 -13
- package/src/render/StyleMaterial.ts +26 -0
- package/src/render/tilePlacement.ts +121 -0
- package/src/render/viewer/EdgeDecorator.ts +3 -0
- package/src/render/viewer/PickService.ts +44 -16
- package/src/render/viewer/types.ts +51 -0
- package/src/tileset/S3MTileset.ts +226 -3
package/docs/API.md
CHANGED
|
@@ -170,7 +170,11 @@ new S3MViewer(options?: S3MViewerOptions)
|
|
|
170
170
|
| (随 `useWorker`)纹理解码 | `boolean` | `true` | DXT / CRN / WEBP 纹理解码与 `.s3mb` 解析共用同一个 Worker(`core/TextureDecode.ts` 三方共用同一份实现,WEBP 走可 transfer 的 ImageBitmap);失败自动回主线程,产物逐字节一致 |
|
|
171
171
|
| (随 `useWorker`)几何组装 | `boolean` | `true` | 顶点解交错 / 法线计算 / 同材质合批合并(`core/TileGeometry.ts`)随解析在 Worker 完成,主线程只做 three 对象包装;Worker 不可用时主线程跑同一份实现,产物一致 |
|
|
172
172
|
| (随 `useWorker`)DXT 压缩直传 | 自动 | 自动 | 宿主 GL 同时支持 `WEBGL_compressed_texture_s3tc`(+srgb) 时,dxt1/dxt5(含 CRN 转码产物)不解码直接 `CompressedTexture` 上传,显存与上传带宽降为 1/4~1/8;块长与 4×4 对齐公式不符、flipY 图集、移动端无扩展时自动回退 RGBA 解码 |
|
|
173
|
+
| `perTilePlacement` | `boolean` | `true` | 瓦片级切平面放置与拾取反解(**默认开启**)。每块瓦片用自身中心纬度计算墨卡托比例与反解拾取,消除「全场景共用一个常量切平面」在大范围(距场景锚点 20 km 处 0.26% 误差,约 26 m;200 km 处 2.8 km)下的累积尺度误差与拾取偏移。逆矩阵自动缓存在瓦片 group 上避免拾取性能损耗。可显式传 `false` 回退旧版单一全场景切平面(下游若缓存了旧坐标或与旧数据对齐时使用) |
|
|
174
|
+
| `bakeMercator` | `boolean` | `undefined`(自动) | 全球大尺度下逐顶点烘焙精确 Web 墨卡托偏移(Issue #15)。消除大范围单瓦片内因墨卡托纬度非线性缩放导致的视差游动与呼吸效应。默认 `undefined`:当瓦片集根节点包围球半径 > 25 km 时自动启用;<= 25 km 时保持关闭(顶点 buffer 逐字节与旧版相同)。传 `true` / `false` 强制开关 |
|
|
173
175
|
| `lodRangeScale` | `number` | `1.0` | LOD 切换距离缩放系数,> 1 更倾向保留粗模,< 1 提前细化 |
|
|
176
|
+
| `lodTransition` | `'binary' \| 'incremental'` | `'binary'` | LOD 细化过渡策略。`'binary'`(默认):整节点二值切换,所有子瓦片全部就绪或失败后整块替换;`'incremental'`:增量细化(primary / fallback 双列表 + LEQUAL 深度推移),子瓦片就绪一个显示一个,未就绪区域由最近父瓦片(fallback)垫底填充,消除整块跳变与慢 child 阻塞,极端角度接缝无缝衔接且不出洞 |
|
|
177
|
+
| `fallbackOffset` | `{ factor?: number; units?: number }` | `{ factor: 1, units: 2 }` | 增量细化模式下 fallback 兜底瓦片的材质深度偏移系数,向远处推移深度配合 gl.LEQUAL 消除共面 z-fighting |
|
|
174
178
|
| `lodPixelRatioCap` | `number` | `Infinity` | LOD 判定屏幕高度的 **DPR 上限**(不截断)。默认按 `canvasHeight` 物理像素算,与官方 `calcPixFromCam` 同口径;3x DPR 手机上由此会多细化、多拉瓦片,传 `2` 即按「CSS 高度 × 2」截断 |
|
|
175
179
|
| `anisotropyCap` | `number` | `Infinity` | 各向异性过滤上限(不封顶 = 取 GPU 最大值,通常 16)。移动端封到 4 / 8 肉眼通常无差,采样成本明显下降。**图层级**:运行时 `setAnisotropyCap()` 会立刻应用到已上传纹理(改采样参数 + 重传,不重解码、不重载瓦片),图集纹理恒为 1 |
|
|
176
180
|
| `maxTextureSize` | `number` | `Infinity` | 纹理长边像素上限(不限制)。非图集的 RGBA / WebP 产物按 2× 盒式滤波减半到上限内;DXT 压缩直传与图集不受影响。**图层级**,但只影响之后解码的纹理(已解码的不重解) |
|
|
@@ -178,6 +182,7 @@ new S3MViewer(options?: S3MViewerOptions)
|
|
|
178
182
|
| `materialQuality` | `'pbr' \| 'lambert'` | `'pbr'` | 光照模型。`lambert` 用 `MeshLambertMaterial` 替换 PBR,去掉 GGX 与环境球谐,片元约省一半;白模 / 纯色 / 按高度着色几乎无差。**图层级**:同页多个图层各用各的,互不覆盖;不传则跟随模块级默认(`setMaterialQuality`)。只影响之后创建的材质 |
|
|
179
183
|
| `colorPipeline` | `'linear' \| 'direct'` | `'linear'` | 颜色管线。`direct` = 官方 iClient3D 的 sRGB 直乘(同时关掉纹理解码),`lightingPreset: 'supermap'` 会选它。**图层级**:同页多图层各用各的;已加载瓦片可运行时 `setColorPipeline(q, { duration })` 走 fade-through 平滑切过去(重建材质 + 重传纹理,不重下瓦片) |
|
|
180
184
|
| `motionBlur` | `{ mode?: 'afterimage' \| 'velocity'; damp?: number \| 'auto'; maxOffset?: number }` | 不传 = 关闭 | 可选的运动模糊后处理(**默认关闭**,不传时渲染路径与旧版逐字一致)。低端 / 移动设备低帧率下,用帧间残影在感知层面填补卡顿与边缘闪烁。`afterimage`(缺省)= 当前帧 + 上一帧全屏混合,几乎零边际 GPU 成本;`velocity` = 按上一帧 ↔ 当前帧相机矩阵逐像素反推屏幕速度、沿速度方向 8 tap 采样(方向感知,只覆盖相机运动)。`damp` 为拖尾长度(仅 afterimage),`'auto'`(缺省)按实时 FPS 收敛(`0.55^(fps/60)` 封顶 0.92),数值钳 [0, 0.98];`maxOffset` 为拖影上限(仅 velocity),速度向量的最大屏幕偏移按绘图缓冲宽度的百分比计,缺省 `0.05`,数值钳 [0, 1]。运行时 `setMotionBlur(null)` 关闭并释放 RT。仅 WebGL2(WebGL1 静默回退直渲);已知限制:拖影只在当前帧几何覆盖范围内(合成 quad 依赖本帧深度保住遮挡正确性);RT 用 HalfFloatType 存线性值(高光不被 clamp,代价是三张全屏纹理显存 ×2) |
|
|
185
|
+
| `depthMode` | `'scene' \| 'clear' \| 'auto'` | `'scene'` | 本层绘制前深度缓冲处理策略。`'scene'`(默认):保留宿主深度,建筑/管线等离散对象受地形正常遮挡;`'clear'`:先清深度再画,贴地倾斜摄影/开挖地表/大范围 DEM 化模型消除共面 z-fighting;`'auto'`:按 ground 覆盖或数据集类型自动推断 |
|
|
181
186
|
| `buildTile` | `BuildTileFn \| null` | — | 自定义瓦片构建器(测试 / 服务端渲染时注入) |
|
|
182
187
|
| `attributes` | `AttributeQueryOptions` | — | 可选的 iServer 数据服务配置,配了才能用 `colorByField()` 等便利方法;不配就自己取数 + `setFeaturesStyle()`,见[业务字段](#业务字段锚点在库内对应关系在外部) |
|
|
183
188
|
| `onRequestRepaint` | `() => void` | — | 宿主请求重画的信号:纯 three 宿主接自己的渲染循环 |
|
|
@@ -208,6 +213,15 @@ prewarmShading(): Promise<void> // 手动预热另一种 shading 变体
|
|
|
208
213
|
|
|
209
214
|
`geo` 是可写的 `GeoAdapter | null`:`null` 用内置的墨卡托兜底(`worldFromLngLat` / `lngLatFromWorld`),地图宿主注入适配器以支持任意 CRS / 自定义投影。
|
|
210
215
|
|
|
216
|
+
内置兜底使用高精度 **WGS84 椭球模型**($a=6378137, f=1/298.257223563$):
|
|
217
|
+
- `worldFromLngLat` 返回带有各向异性尺度的对象:`{ x, y, z, meterInMercatorEast, meterInMercatorNorth, meterInMercatorUp, meterInMercator, altitude }`
|
|
218
|
+
- 东西向尺度对应卯酉圈半径 $N(\varphi) = a / \sqrt{1 - e^2 \sin^2\varphi}$:$1 / (2\pi N \cos\varphi)$
|
|
219
|
+
- 南北向尺度对应子午圈半径 $M(\varphi) = a(1 - e^2) / (1 - e^2 \sin^2\varphi)^{3/2}$:$1 / (2\pi M \cos\varphi)$
|
|
220
|
+
- 竖直向高程尺度 `meterInMercatorUp` 取东向尺度(局部正圆保角)
|
|
221
|
+
- `meterInMercator` 保留作为向前兼容属性,取值从旧版球面近似 $1/(2\pi R\cos\varphi)$ 升级为真实的 WGS84 椭球东向尺度(属于精度修正,默认生效且更精准)
|
|
222
|
+
- 彻底消除传统单标量模型在南北/东西方向上 0.4%~0.67% 的各向异性拉伸与米级系统残差。
|
|
223
|
+
- 包导出了高精度大地测量工具:`primeVerticalRadius`, `meridianRadius`, `enuToLngLat`, `lngLatToEnu` 及 `WGS84_A`, `WGS84_E2` 常量。
|
|
224
|
+
|
|
211
225
|
### 数据集管理
|
|
212
226
|
|
|
213
227
|
```ts
|
|
@@ -516,6 +530,8 @@ type ClipOptions =
|
|
|
516
530
|
```ts
|
|
517
531
|
setLodRangeScale(scale: number, datasetId?: string): void
|
|
518
532
|
readonly lodRangeScale: number
|
|
533
|
+
readonly lodTransition: 'binary' | 'incremental'
|
|
534
|
+
setLodTransition(mode: 'binary' | 'incremental'): void
|
|
519
535
|
setLodPixelRatioCap(cap: number): void // Infinity = 不截断(默认)
|
|
520
536
|
readonly lodPixelRatioCap: number
|
|
521
537
|
setMaxTriangles(n: number, datasetId?: string): void // Infinity = 不限制(默认)
|
|
@@ -527,6 +543,8 @@ readonly anisotropyCap: number
|
|
|
527
543
|
setMaxTextureSize(n: number): void // Infinity = 不限制(默认)
|
|
528
544
|
readonly maxTextureSize: number
|
|
529
545
|
readonly maxImageCache: number
|
|
546
|
+
setDepthMode(mode: DepthMode): void // 运行时调整深度处理策略('scene' | 'clear' | 'auto')
|
|
547
|
+
readonly depthMode: DepthMode // 当前深度策略
|
|
530
548
|
setMotionBlur(cfg: MotionBlurOptions | null): void // 运行时开关运动模糊;null = 关闭并释放 RT 显存
|
|
531
549
|
readonly motionBlur: MotionBlurOptions | null // 当前配置(归一化后的副本)
|
|
532
550
|
readonly stats: Stats
|
package/package.json
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全球大尺度精确 Web 墨卡托烘焙(issue #15)。
|
|
3
|
+
*
|
|
4
|
+
* ## 背景与原理
|
|
5
|
+
*
|
|
6
|
+
* S3M 场景通常只有一个切平面:`_enuMatrix` 用场景锚点纬度的常量 `meterInMercator`
|
|
7
|
+
* 把整个场景缩放进 Web 墨卡托。但在跨度超过几十公里的全球或大区域场景中,
|
|
8
|
+
* 墨卡托投影比例随纬度变化(k = 1 / cos(φ))。
|
|
9
|
+
* 即使有 `perTilePlacement`(issue #12)按瓦片重置了原点与缩放,瓦片内部仍然存在
|
|
10
|
+
* 线性切平面与真实墨卡托投影之间的微小残差。
|
|
11
|
+
*
|
|
12
|
+
* 烘焙方案:
|
|
13
|
+
* 1. 预先或在组装瓦片几何时,逐顶点计算其精确的 Web 墨卡托坐标;
|
|
14
|
+
* 2. 减去瓦片中心的精确墨卡托坐标,得到相对于瓦片中心的相对偏移量 `aMercatorOffset`(float32);
|
|
15
|
+
* 3. 为什么是相对偏移:
|
|
16
|
+
* Web 墨卡托全局坐标在 0..1 之间,若直接用 float32(24 位尾数)存储绝对坐标,
|
|
17
|
+
* 分辨率约为 40000000 / 2^24 ≈ 2.38 米;
|
|
18
|
+
* 若改为相对瓦片中心(瓦片半径通常 <= 100 km),float32 尾数精度可达:
|
|
19
|
+
* 100000 / 2^24 ≈ 0.0059 米(小于 6 毫米),常规 2 km 瓦片下更是达到 0.1 毫米以内。
|
|
20
|
+
* 4. 着色器重构:
|
|
21
|
+
* 顶点着色器在局部对象空间中还原出抵消瓦片尺度与中心平移后的坐标:
|
|
22
|
+
* `transformed = uTilePivot + vec3(aMercatorOffset.x, -aMercatorOffset.y, aMercatorOffset.z) * uInvTileScale;`
|
|
23
|
+
* 其中 `uInvTileScale = 1 / (sScene * k) = 1 / sTile`。
|
|
24
|
+
* 经过瓦片矩阵变换与 `_enuMatrix` 缩放平移后,刚好恒等于真实的世界墨卡托坐标,误差 < 1 cm。
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
EARTH_RADIUS,
|
|
29
|
+
enuToLngLat as geoEnuToLngLat,
|
|
30
|
+
mercatorFromLngLat,
|
|
31
|
+
} from '../render/GeoAdapter.js';
|
|
32
|
+
|
|
33
|
+
/** 超过此半径(米)的瓦片在未指定参数时自动开启墨卡托烘焙(25 km) */
|
|
34
|
+
export const AUTO_BAKE_RADIUS_THRESHOLD = 25000;
|
|
35
|
+
|
|
36
|
+
export interface TileAnchorLike {
|
|
37
|
+
lon: number;
|
|
38
|
+
lat: number;
|
|
39
|
+
height?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TileCenterLike {
|
|
43
|
+
x: number;
|
|
44
|
+
y: number;
|
|
45
|
+
z?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 局部 ENU 偏移(东, 北 米)→ 经纬度(对接 GeoAdapter 的 WGS84 高精度模型)。
|
|
50
|
+
*/
|
|
51
|
+
export function enuToLngLat(anchor: TileAnchorLike, east: number, north: number): [number, number] {
|
|
52
|
+
return geoEnuToLngLat(anchor.lon, anchor.lat, east, north);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 逐顶点计算 Web 墨卡托相对于瓦片中心的偏移量。
|
|
57
|
+
*
|
|
58
|
+
* @param positions 顶点位置数组(x, y, z...)
|
|
59
|
+
* @param vertexCount 顶点数
|
|
60
|
+
* @param tileAnchor 数据集/瓦片地理锚点
|
|
61
|
+
* @param tileCenter 瓦片中心在数据集局部 ENU 中的位置(米)
|
|
62
|
+
* @param stride 每个顶点的分量数(默认为 3)
|
|
63
|
+
* @param target 可选的复用 Float32Array
|
|
64
|
+
* @returns 长度为 vertexCount * 3 的 Float32Array,存 [dx, dy, dz, ...]
|
|
65
|
+
*/
|
|
66
|
+
export function bakeMercatorOffsets(
|
|
67
|
+
positions: ArrayLike<number>,
|
|
68
|
+
vertexCount: number,
|
|
69
|
+
tileAnchor: TileAnchorLike,
|
|
70
|
+
tileCenter: TileCenterLike,
|
|
71
|
+
stride = 3,
|
|
72
|
+
target?: Float32Array
|
|
73
|
+
): Float32Array {
|
|
74
|
+
const out = target && target.length >= vertexCount * 3 ? target : new Float32Array(vertexCount * 3);
|
|
75
|
+
|
|
76
|
+
const centerAlt = (tileAnchor.height ?? 0) + (tileCenter.z ?? 0);
|
|
77
|
+
const [centerLon, centerLat] = enuToLngLat(tileAnchor, tileCenter.x, tileCenter.y);
|
|
78
|
+
const cMerc = mercatorFromLngLat(centerLon, centerLat, centerAlt);
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
81
|
+
const idx = i * stride;
|
|
82
|
+
const vx = positions[idx];
|
|
83
|
+
const vy = positions[idx + 1];
|
|
84
|
+
const vz = positions[idx + 2] ?? 0;
|
|
85
|
+
|
|
86
|
+
const vAlt = (tileAnchor.height ?? 0) + vz;
|
|
87
|
+
const [vLon, vLat] = enuToLngLat(tileAnchor, vx, vy);
|
|
88
|
+
const vMerc = mercatorFromLngLat(vLon, vLat, vAlt);
|
|
89
|
+
|
|
90
|
+
out[i * 3 + 0] = vMerc.x - cMerc.x;
|
|
91
|
+
out[i * 3 + 1] = vMerc.y - cMerc.y;
|
|
92
|
+
out[i * 3 + 2] = vMerc.z - cMerc.z;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return out;
|
|
96
|
+
}
|
package/src/core/TileGeometry.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
VertexAttribute,
|
|
22
22
|
} from './types.js';
|
|
23
23
|
import { applyCustomAttributeSemantics } from './CustomAttributes.js';
|
|
24
|
+
import { bakeMercatorOffsets, type TileAnchorLike, type TileCenterLike } from './BakeMercator.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* S3M 规范里 `DrawPrimitiveType` 的两个特殊值(原住 GeometryBuilder,随组装层
|
|
@@ -249,6 +250,8 @@ export interface PrimitiveAttributes {
|
|
|
249
250
|
* 只有存在该流时才产生;解码语义见 core/TexCoordZ.ts。
|
|
250
251
|
*/
|
|
251
252
|
aTexCoordZ?: PlainAttribute;
|
|
253
|
+
/** 逐顶点精确 Web 墨卡托偏移(Issue #15) */
|
|
254
|
+
aMercatorOffset?: PlainAttribute;
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
export interface PrimitiveData {
|
|
@@ -298,6 +301,10 @@ export interface AssembleOptions {
|
|
|
298
301
|
* `useWorker: false` 或 Worker 崩溃降级时它落在主线程上。
|
|
299
302
|
*/
|
|
300
303
|
dedupeCoincident?: boolean;
|
|
304
|
+
/** 是否烘焙精确 Web 墨卡托偏移(Issue #15) */
|
|
305
|
+
bakeMercator?: boolean;
|
|
306
|
+
tileAnchor?: TileAnchorLike;
|
|
307
|
+
tileCenter?: TileCenterLike;
|
|
301
308
|
vertexAttributeDescript?: unknown;
|
|
302
309
|
}
|
|
303
310
|
|
|
@@ -905,5 +912,24 @@ export function assembleTileGeometry(tile: ParsedTile, options: AssembleOptions
|
|
|
905
912
|
}
|
|
906
913
|
}
|
|
907
914
|
|
|
915
|
+
// 逐顶点烘焙精确 Web 墨卡托(Issue #15)
|
|
916
|
+
if (options.bakeMercator && options.tileAnchor) {
|
|
917
|
+
for (const p of primitives) {
|
|
918
|
+
const positions = p.attributes.position?.array;
|
|
919
|
+
if (!positions || p.vertexCount === 0) continue;
|
|
920
|
+
const center = options.tileCenter ?? {
|
|
921
|
+
x: p.bounds.sphereCenter[0],
|
|
922
|
+
y: p.bounds.sphereCenter[1],
|
|
923
|
+
z: p.bounds.sphereCenter[2],
|
|
924
|
+
};
|
|
925
|
+
const offsets = bakeMercatorOffsets(positions, p.vertexCount, options.tileAnchor, center);
|
|
926
|
+
p.attributes.aMercatorOffset = {
|
|
927
|
+
array: offsets,
|
|
928
|
+
components: 3,
|
|
929
|
+
normalized: false,
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
908
934
|
return { primitives, stats };
|
|
909
935
|
}
|
package/src/index.ts
CHANGED
|
@@ -51,13 +51,21 @@ export type {
|
|
|
51
51
|
|
|
52
52
|
// —— 地理坐标适配(宿主无关接口 + 墨卡托兜底)
|
|
53
53
|
export {
|
|
54
|
+
WGS84_A,
|
|
55
|
+
WGS84_B,
|
|
56
|
+
WGS84_F,
|
|
57
|
+
WGS84_E2,
|
|
54
58
|
EARTH_RADIUS,
|
|
59
|
+
primeVerticalRadius,
|
|
60
|
+
meridianRadius,
|
|
61
|
+
enuToLngLat,
|
|
62
|
+
lngLatToEnu,
|
|
55
63
|
mercatorFromLngLat,
|
|
56
64
|
lngLatFromMercator,
|
|
57
65
|
worldFromLngLat,
|
|
58
66
|
lngLatFromWorld,
|
|
59
67
|
} from './render/GeoAdapter.js';
|
|
60
|
-
export type { GeoAdapter } from './render/GeoAdapter.js';
|
|
68
|
+
export type { GeoAdapter, WorldCoordinateResult } from './render/GeoAdapter.js';
|
|
61
69
|
|
|
62
70
|
// —— 瓦片层
|
|
63
71
|
export { S3MTileset, parseConfigText, classifyDatasetKind } from './tileset/S3MTileset.js';
|
|
@@ -110,8 +118,10 @@ export type {
|
|
|
110
118
|
GroundOptions,
|
|
111
119
|
LightOptions,
|
|
112
120
|
PathFollowOptions,
|
|
121
|
+
DepthMode,
|
|
113
122
|
} from './render/S3MViewer.js';
|
|
114
123
|
|
|
124
|
+
export { normalizeDepthMode } from './render/S3MViewer.js';
|
|
115
125
|
export { LIGHT_PRESETS, DEFAULT_LIGHT_PRESET, SUPERMAP_LIGHT_PRESET, resolveLightingPreset } from './render/lightingPreset.js';
|
|
116
126
|
export type { LightingPreset, PresetLightIntensities } from './render/lightingPreset.js';
|
|
117
127
|
|
|
@@ -14,6 +14,7 @@ import type { Anchor } from '../core/types.js';
|
|
|
14
14
|
import type { UnsupportedPolicy } from '../core/degradations.js';
|
|
15
15
|
import { worldFromLngLat, type GeoAdapter } from './GeoAdapter.js';
|
|
16
16
|
import type { TextureCreateOptions } from './TextureFactory.js';
|
|
17
|
+
import type { DepthMode } from './viewer/types.js';
|
|
17
18
|
|
|
18
19
|
export type DatasetAlignment = 'origin' | 'bottom' | 'top' | 'center';
|
|
19
20
|
|
|
@@ -42,6 +43,11 @@ export interface S3MDatasetOptions {
|
|
|
42
43
|
heightOffset?: number;
|
|
43
44
|
/** 是否用 Web Worker 解析经典 .s3mb 瓦片(默认开,崩溃自动降级主线程),见 S3MTilesetOptions.useWorker */
|
|
44
45
|
useWorker?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* 本数据集推荐的深度模式('scene' | 'clear' | 'auto')。
|
|
48
|
+
* 当图层/Viewer 的 depthMode 为 'auto' 时,若任一可见数据集标注为 'clear',则整层以 'clear' 绘制。
|
|
49
|
+
*/
|
|
50
|
+
depthMode?: DepthMode;
|
|
45
51
|
/** 三角面材质深度偏移(共面摩尔纹 / 重合面归属),见 BuildTileOptions.polygonOffset */
|
|
46
52
|
polygonOffset?: { factor: number; units: number };
|
|
47
53
|
/**
|
|
@@ -49,6 +55,7 @@ export interface S3MDatasetOptions {
|
|
|
49
55
|
* 只影响该数据集**之后创建**的材质。
|
|
50
56
|
*/
|
|
51
57
|
materialQuality?: 'pbr' | 'lambert';
|
|
58
|
+
bakeMercator?: boolean;
|
|
52
59
|
/**
|
|
53
60
|
* 是否对该数据集生成几何棱线(CPU 端 EdgesGeometry)。
|
|
54
61
|
* - 不填:跟随全局 `style.edges`
|
|
@@ -65,6 +72,13 @@ export interface S3MDatasetOptions {
|
|
|
65
72
|
* 目前只有 3DGS:按点渲染,做不出泼溅。见 `S3MTilesetOptions.unsupported`。
|
|
66
73
|
*/
|
|
67
74
|
unsupported?: UnsupportedPolicy;
|
|
75
|
+
/**
|
|
76
|
+
* LOD 细化过渡策略:
|
|
77
|
+
* - 'binary'(默认):整节点二值切换
|
|
78
|
+
* - 'incremental':增量细化(primary / fallback 双列表 + LEQUAL 深度推移)
|
|
79
|
+
*/
|
|
80
|
+
lodTransition?: 'binary' | 'incremental';
|
|
81
|
+
fallbackOffset?: { factor?: number; units?: number };
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
export interface S3MDatasetEntry {
|
|
@@ -97,13 +111,32 @@ export interface DatasetManagerCallbacks {
|
|
|
97
111
|
|
|
98
112
|
export class DatasetManager {
|
|
99
113
|
readonly datasets = new Map<string, S3MDatasetEntry>();
|
|
100
|
-
sceneAnchor: {
|
|
114
|
+
sceneAnchor: {
|
|
115
|
+
x: number;
|
|
116
|
+
y: number;
|
|
117
|
+
z: number;
|
|
118
|
+
meterInMercatorEast?: number;
|
|
119
|
+
meterInMercatorNorth?: number;
|
|
120
|
+
meterInMercatorUp?: number;
|
|
121
|
+
meterInMercator: number;
|
|
122
|
+
altitude?: number;
|
|
123
|
+
} | null = null;
|
|
101
124
|
totalMaxTiles: number = 0;
|
|
102
125
|
/**
|
|
103
126
|
* 图层级默认光照模型档位(`undefined` = 跟随模块级默认)。
|
|
104
127
|
* 数据集自己指定了 `materialQuality` 就用自己的。
|
|
105
128
|
*/
|
|
106
129
|
defaultMaterialQuality: 'pbr' | 'lambert' | undefined = undefined;
|
|
130
|
+
defaultLodTransition: 'binary' | 'incremental' = 'binary';
|
|
131
|
+
defaultFallbackOffset?: { factor?: number; units?: number };
|
|
132
|
+
|
|
133
|
+
setDefaultLodTransition(mode: 'binary' | 'incremental'): void {
|
|
134
|
+
this.defaultLodTransition = mode;
|
|
135
|
+
for (const entry of this.datasets.values()) {
|
|
136
|
+
entry.tileset?.setLodTransition(mode);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
defaultBakeMercator: boolean | undefined = undefined;
|
|
107
140
|
|
|
108
141
|
/**
|
|
109
142
|
* 图层档位变更:同步给已存在的数据集(它们之后加载的瓦片才用新档位;
|
|
@@ -264,6 +297,8 @@ export class DatasetManager {
|
|
|
264
297
|
maxMemory: ds.maxMemory ?? maxMemory,
|
|
265
298
|
maxImageCache: ds.maxImageCache ?? maxImageCache,
|
|
266
299
|
lodRangeScale: ds.lodRangeScale ?? defaultLodRangeScale,
|
|
300
|
+
lodTransition: ds.lodTransition ?? this.defaultLodTransition,
|
|
301
|
+
fallbackOffset: ds.fallbackOffset ?? this.defaultFallbackOffset,
|
|
267
302
|
buildTile: buildTile ?? null,
|
|
268
303
|
polygonOffset: ds.polygonOffset,
|
|
269
304
|
// 数据集 > 图层 > 模块级默认(undefined 一路传下去,由 createStyledMaterial 回落)
|
|
@@ -272,6 +307,7 @@ export class DatasetManager {
|
|
|
272
307
|
colorPipeline: this.defaultColorPipeline,
|
|
273
308
|
textureOptions: this.defaultTextureOptions,
|
|
274
309
|
useWorker: ds.useWorker,
|
|
310
|
+
bakeMercator: ds.bakeMercator ?? this.defaultBakeMercator,
|
|
275
311
|
authenticate: ds.authenticate ?? authenticate,
|
|
276
312
|
unsupported: ds.unsupported,
|
|
277
313
|
uniforms: uniforms,
|
|
@@ -359,12 +395,14 @@ export class DatasetManager {
|
|
|
359
395
|
if (!this.sceneAnchor) {
|
|
360
396
|
this.sceneAnchor = worldFromLngLat(geo, entry.anchor.lon, entry.anchor.lat, entry.anchor.height ?? 0);
|
|
361
397
|
}
|
|
362
|
-
const
|
|
398
|
+
const sEast = this.sceneAnchor.meterInMercatorEast ?? this.sceneAnchor.meterInMercator;
|
|
399
|
+
const sNorth = this.sceneAnchor.meterInMercatorNorth ?? this.sceneAnchor.meterInMercator;
|
|
400
|
+
const sUp = sEast;
|
|
363
401
|
const a = worldFromLngLat(geo, entry.anchor.lon, entry.anchor.lat, entry.anchor.height ?? 0);
|
|
364
402
|
entry.offset = [
|
|
365
|
-
(a.x - this.sceneAnchor.x) /
|
|
366
|
-
-(a.y - this.sceneAnchor.y) /
|
|
367
|
-
(a.z - this.sceneAnchor.z) /
|
|
403
|
+
(a.x - this.sceneAnchor.x) / sEast,
|
|
404
|
+
-(a.y - this.sceneAnchor.y) / sNorth,
|
|
405
|
+
(a.z - this.sceneAnchor.z) / sUp + (entry.heightOffset ?? 0),
|
|
368
406
|
];
|
|
369
407
|
entry.root.position.set(entry.offset[0], entry.offset[1], entry.offset[2]);
|
|
370
408
|
entry.root.updateMatrixWorld(true);
|
package/src/render/GeoAdapter.ts
CHANGED
|
@@ -6,39 +6,151 @@
|
|
|
6
6
|
* 这个文件只定义接口与默认实现,不依赖任何宿主:
|
|
7
7
|
*
|
|
8
8
|
* - `GeoAdapter` —— 宿主需要提供的两个转换函数
|
|
9
|
-
* - `mercatorFromLngLat` / `lngLatFromMercator` —— 标准 Web
|
|
9
|
+
* - `mercatorFromLngLat` / `lngLatFromMercator` —— 标准 Web 墨卡托换算(WGS84 椭球)
|
|
10
10
|
* - `worldFromLngLat` / `lngLatFromWorld` —— 带兜底的封装:
|
|
11
11
|
* 有 adapter 用 adapter(自动适配 MapLibre v5+ 任意 CRS / 自定义投影),
|
|
12
12
|
* 没有就平滑回退到标准 Web 墨卡托。
|
|
13
|
+
* - `enuToLngLat` / `lngLatToEnu` —— WGS84 椭球高精度局部坐标互转
|
|
13
14
|
*
|
|
14
15
|
* MapLibre / Mapbox 的 adapter 实现见 s3m-maplibre 包的 MapLibreGeoAdapter.ts。
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
/** WGS84 椭球长半轴 a(米) */
|
|
19
|
+
export const WGS84_A = 6378137.0;
|
|
20
|
+
/** WGS84 椭球扁率 f */
|
|
21
|
+
export const WGS84_F = 1 / 298.257223563;
|
|
22
|
+
/** WGS84 椭球短半轴 b(米) */
|
|
23
|
+
export const WGS84_B = WGS84_A * (1 - WGS84_F);
|
|
24
|
+
/** WGS84 椭球第一偏心率平方 e² = 2f - f² ≈ 0.0066943799901413165 */
|
|
25
|
+
export const WGS84_E2 = 2 * WGS84_F - WGS84_F * WGS84_F;
|
|
26
|
+
|
|
27
|
+
/** 保持向下兼容:EARTH_RADIUS 对应 WGS84 长半轴 */
|
|
28
|
+
export const EARTH_RADIUS = WGS84_A;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 卯酉圈曲率半径 N(φ) = a / √(1 - e² sin²φ)(米)
|
|
32
|
+
*/
|
|
33
|
+
export function primeVerticalRadius(latRad: number): number {
|
|
34
|
+
const sin = Math.sin(latRad);
|
|
35
|
+
return WGS84_A / Math.sqrt(1 - WGS84_E2 * sin * sin);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 子午圈曲率半径 M(φ) = a(1 - e²) / (1 - e² sin²φ)^(3/2)(米)
|
|
40
|
+
*/
|
|
41
|
+
export function meridianRadius(latRad: number): number {
|
|
42
|
+
const sin = Math.sin(latRad);
|
|
43
|
+
const w2 = 1 - WGS84_E2 * sin * sin;
|
|
44
|
+
return (WGS84_A * (1 - WGS84_E2)) / (w2 * Math.sqrt(w2));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 局部 ENU 米坐标 (east, north) → 经纬度 [lon, lat] (度)
|
|
49
|
+
* 基于 WGS84 椭球中点曲率半径反解,在 100 km 范围内误差 < 1 cm。
|
|
50
|
+
*/
|
|
51
|
+
export function enuToLngLat(
|
|
52
|
+
anchorLon: number,
|
|
53
|
+
anchorLat: number,
|
|
54
|
+
east: number,
|
|
55
|
+
north: number
|
|
56
|
+
): [number, number] {
|
|
57
|
+
const lat0 = (anchorLat * Math.PI) / 180;
|
|
58
|
+
const M0 = meridianRadius(lat0);
|
|
59
|
+
// 中点迭代一步:在数百公里尺度即可将纬度子午线积分误差压到亚毫米
|
|
60
|
+
const dLatApprox = north / M0;
|
|
61
|
+
const latMid = lat0 + dLatApprox * 0.5;
|
|
62
|
+
const M_mid = meridianRadius(latMid);
|
|
63
|
+
const latRad = lat0 + north / M_mid;
|
|
64
|
+
|
|
65
|
+
const N_lat = primeVerticalRadius(latRad);
|
|
66
|
+
const cosLat = Math.cos(latRad);
|
|
67
|
+
const dLon = Math.abs(cosLat) < 1e-12 ? 0 : east / (N_lat * cosLat);
|
|
68
|
+
|
|
69
|
+
return [anchorLon + (dLon * 180) / Math.PI, (latRad * 180) / Math.PI];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 经纬度 (lon, lat) → 局部 ENU 米坐标 [east, north]
|
|
74
|
+
* enuToLngLat 的逆运算。
|
|
75
|
+
*/
|
|
76
|
+
export function lngLatToEnu(
|
|
77
|
+
anchorLon: number,
|
|
78
|
+
anchorLat: number,
|
|
79
|
+
lon: number,
|
|
80
|
+
lat: number
|
|
81
|
+
): [number, number] {
|
|
82
|
+
const lat0 = (anchorLat * Math.PI) / 180;
|
|
83
|
+
const lat1 = (lat * Math.PI) / 180;
|
|
84
|
+
const latMid = (lat0 + lat1) * 0.5;
|
|
85
|
+
const north = (lat1 - lat0) * meridianRadius(latMid);
|
|
86
|
+
|
|
87
|
+
const N_lat = primeVerticalRadius(lat1);
|
|
88
|
+
const cosLat = Math.cos(lat1);
|
|
89
|
+
const dLon = ((lon - anchorLon) * Math.PI) / 180;
|
|
90
|
+
const east = dLon * N_lat * cosLat;
|
|
91
|
+
|
|
92
|
+
return [east, north];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 坐标转换结果类型 */
|
|
96
|
+
export interface WorldCoordinateResult {
|
|
97
|
+
x: number;
|
|
98
|
+
y: number;
|
|
99
|
+
z: number;
|
|
100
|
+
/** 东向:1 米 = 多少世界单位 (Web Mercator 东西向尺度 1 / (2π N cosφ)) */
|
|
101
|
+
meterInMercatorEast: number;
|
|
102
|
+
/** 北向:1 米 = 多少世界单位 (Web Mercator 南北向尺度 1 / (2π M cosφ)) */
|
|
103
|
+
meterInMercatorNorth: number;
|
|
104
|
+
/** 竖向(高程):1 米 = 多少世界单位(当前取东向尺度 meterInMercatorEast,以保持局部长方体水平与竖直比例) */
|
|
105
|
+
meterInMercatorUp: number;
|
|
106
|
+
/** 兼容旧代码:等同于 meterInMercatorEast。注意:从原先的正球近似更新为更精准的 WGS84 椭球东向尺度 */
|
|
107
|
+
meterInMercator: number;
|
|
108
|
+
altitude: number;
|
|
109
|
+
}
|
|
18
110
|
|
|
19
111
|
/** 宿主地理坐标系能力:S3M 渲染层用到的就只有这两个方向 */
|
|
20
112
|
export interface GeoAdapter {
|
|
21
|
-
/** 经纬度 + 高程 → 归一化世界坐标,并给出「1 米 =
|
|
113
|
+
/** 经纬度 + 高程 → 归一化世界坐标,并给出「1 米 = 多少世界单位」(含各向异性 East / North) */
|
|
22
114
|
worldFromLngLat(
|
|
23
115
|
lng: number,
|
|
24
116
|
lat: number,
|
|
25
117
|
altitude?: number
|
|
26
|
-
):
|
|
118
|
+
): WorldCoordinateResult;
|
|
27
119
|
/** 归一化世界坐标 → 经纬度 [lng, lat] */
|
|
28
120
|
lngLatFromWorld(x: number, y: number): [number, number];
|
|
29
121
|
}
|
|
30
122
|
|
|
31
|
-
/** 经纬度 → 墨卡托归一化坐标(世界为 1×1)——
|
|
123
|
+
/** 经纬度 → 墨卡托归一化坐标(世界为 1×1)—— 默认墨卡托兜底(WGS84 椭球曲率半径与各向异性尺度) */
|
|
32
124
|
export function mercatorFromLngLat(
|
|
33
125
|
lon: number,
|
|
34
126
|
lat: number,
|
|
35
127
|
altitude = 0
|
|
36
|
-
):
|
|
37
|
-
const
|
|
128
|
+
): WorldCoordinateResult {
|
|
129
|
+
const latRad = (lat * Math.PI) / 180;
|
|
130
|
+
const sinLat = Math.sin(latRad);
|
|
131
|
+
const cosLat = Math.cos(latRad);
|
|
132
|
+
const safeCosLat = Math.max(Math.abs(cosLat), 1e-12);
|
|
133
|
+
|
|
38
134
|
const x = (180 + lon) / 360;
|
|
39
135
|
const y = 0.5 - (0.25 * Math.log((1 + sinLat) / (1 - sinLat))) / Math.PI;
|
|
40
|
-
|
|
41
|
-
|
|
136
|
+
|
|
137
|
+
const N = primeVerticalRadius(latRad);
|
|
138
|
+
const M = meridianRadius(latRad);
|
|
139
|
+
|
|
140
|
+
const meterInMercatorEast = 1 / (2 * Math.PI * N * safeCosLat);
|
|
141
|
+
const meterInMercatorNorth = 1 / (2 * Math.PI * M * safeCosLat);
|
|
142
|
+
const meterInMercatorUp = meterInMercatorEast;
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
x,
|
|
146
|
+
y,
|
|
147
|
+
z: altitude * meterInMercatorUp,
|
|
148
|
+
meterInMercatorEast,
|
|
149
|
+
meterInMercatorNorth,
|
|
150
|
+
meterInMercatorUp,
|
|
151
|
+
meterInMercator: meterInMercatorEast,
|
|
152
|
+
altitude,
|
|
153
|
+
};
|
|
42
154
|
}
|
|
43
155
|
|
|
44
156
|
/** 墨卡托归一化坐标 → 经纬度 —— 默认墨卡托兜底 */
|
|
@@ -58,8 +170,24 @@ export function worldFromLngLat(
|
|
|
58
170
|
lon: number,
|
|
59
171
|
lat: number,
|
|
60
172
|
altitude = 0
|
|
61
|
-
):
|
|
62
|
-
if (adapter)
|
|
173
|
+
): WorldCoordinateResult {
|
|
174
|
+
if (adapter) {
|
|
175
|
+
const res = adapter.worldFromLngLat(lon, lat, altitude);
|
|
176
|
+
const east = res.meterInMercatorEast ?? res.meterInMercator;
|
|
177
|
+
const north = res.meterInMercatorNorth ?? res.meterInMercator ?? east;
|
|
178
|
+
const up = res.meterInMercatorUp ?? east;
|
|
179
|
+
const legacy = res.meterInMercator ?? east;
|
|
180
|
+
return {
|
|
181
|
+
x: res.x,
|
|
182
|
+
y: res.y,
|
|
183
|
+
z: res.z,
|
|
184
|
+
meterInMercatorEast: east,
|
|
185
|
+
meterInMercatorNorth: north,
|
|
186
|
+
meterInMercatorUp: up,
|
|
187
|
+
meterInMercator: legacy,
|
|
188
|
+
altitude: res.altitude,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
63
191
|
return mercatorFromLngLat(lon, lat, altitude);
|
|
64
192
|
}
|
|
65
193
|
|
|
@@ -18,6 +18,8 @@ import { createSharedUniforms, createStyledMaterial } from './StyleMaterial.js';
|
|
|
18
18
|
import { isTransparentMaterial, minVertexAlpha } from './materialAlpha.js';
|
|
19
19
|
import type { StyledMaterial } from './StyleMaterial.js';
|
|
20
20
|
import { assembleTileGeometry, LINE_PRIMITIVE, POINT_PRIMITIVE } from '../core/TileGeometry.js';
|
|
21
|
+
import { bakeMercatorOffsets } from '../core/BakeMercator.js';
|
|
22
|
+
import { mercatorFromLngLat } from './GeoAdapter.js';
|
|
21
23
|
import type { TileGeometryData, PrimitiveData } from '../core/TileGeometry.js';
|
|
22
24
|
import { decodeScale, decodeRotation } from '../core/GaussianSplat.js';
|
|
23
25
|
import { createSplatMaterial, createQuadGeometry, splatRaycast } from './SplatMaterial.js';
|
|
@@ -378,6 +380,15 @@ export interface BuildTileOptions {
|
|
|
378
380
|
* 不传 = 各自读模块级默认。同样由图层的渲染 profile 带下来。
|
|
379
381
|
*/
|
|
380
382
|
textureOptions?: TextureCreateOptions;
|
|
383
|
+
/**
|
|
384
|
+
* 是否烘焙精确 Web 墨卡托偏移属性(消除全球尺度下的视差游动)。
|
|
385
|
+
* 透传给 assembleTileGeometry 和 createStyledMaterial。
|
|
386
|
+
*/
|
|
387
|
+
bakeMercator?: boolean;
|
|
388
|
+
/** 瓦片锚点(经纬度),烘焙墨卡托时使用 */
|
|
389
|
+
tileAnchor?: { lon: number; lat: number; height?: number };
|
|
390
|
+
/** 瓦片中心偏移(ENU 米),烘焙墨卡托时使用 */
|
|
391
|
+
tileCenter?: { x: number; y: number; z?: number };
|
|
381
392
|
}
|
|
382
393
|
|
|
383
394
|
export interface BuildTileResult {
|
|
@@ -475,7 +486,8 @@ export async function buildTileObjects(
|
|
|
475
486
|
code?: string,
|
|
476
487
|
hasAtlasRect = false,
|
|
477
488
|
hasTexCoordZ = false,
|
|
478
|
-
vertexAlphaMin = 1
|
|
489
|
+
vertexAlphaMin = 1,
|
|
490
|
+
hasMercator = false
|
|
479
491
|
): StyledMaterial => {
|
|
480
492
|
const entry = materialList.find((e) => materialName(unwrapMaterial(e)) === code);
|
|
481
493
|
const m = unwrapMaterial(entry);
|
|
@@ -546,6 +558,19 @@ export async function buildTileObjects(
|
|
|
546
558
|
? Math.max((m?.alphaCutoff as number) ?? 0, 0.01)
|
|
547
559
|
: 0;
|
|
548
560
|
|
|
561
|
+
let initialInvScale: [number, number, number] | undefined;
|
|
562
|
+
if (options.tileAnchor) {
|
|
563
|
+
const tileMerc = mercatorFromLngLat(
|
|
564
|
+
options.tileAnchor.lon,
|
|
565
|
+
options.tileAnchor.lat,
|
|
566
|
+
options.tileAnchor.height
|
|
567
|
+
);
|
|
568
|
+
const sEast = tileMerc.meterInMercatorEast ?? tileMerc.meterInMercator;
|
|
569
|
+
const sNorth = tileMerc.meterInMercatorNorth ?? tileMerc.meterInMercator;
|
|
570
|
+
const sUp = tileMerc.meterInMercatorUp ?? sEast;
|
|
571
|
+
initialInvScale = [1.0 / sEast, 1.0 / sNorth, 1.0 / sUp];
|
|
572
|
+
}
|
|
573
|
+
|
|
549
574
|
const mat = createStyledMaterial({
|
|
550
575
|
uniforms,
|
|
551
576
|
map,
|
|
@@ -562,6 +587,9 @@ export async function buildTileObjects(
|
|
|
562
587
|
// 图层 / 数据集的档位;undefined 时 createStyledMaterial 自己回落到模块级默认
|
|
563
588
|
materialQuality: options.materialQuality,
|
|
564
589
|
colorPipeline: options.colorPipeline,
|
|
590
|
+
bakeMercator: hasMercator || options.bakeMercator,
|
|
591
|
+
tilePivot: options.tileCenter ? new THREE.Vector3(options.tileCenter.x, options.tileCenter.y, options.tileCenter.z) : undefined,
|
|
592
|
+
invTileScale: initialInvScale ? new THREE.Vector3(...initialInvScale) : undefined,
|
|
565
593
|
});
|
|
566
594
|
// 用户配置的深度偏移(共面摩尔纹 / 重合面归属的人工定序,见 BuildTileOptions 注释)
|
|
567
595
|
if (options.polygonOffset) {
|
|
@@ -574,11 +602,32 @@ export async function buildTileObjects(
|
|
|
574
602
|
|
|
575
603
|
// 3) 纯数据几何组装:Worker 解析时已顺带产出就直接用,否则主线程现场组装
|
|
576
604
|
// (core/TileGeometry 的同一份实现,两条路径产物一致)
|
|
605
|
+
if (options.assembled && options.bakeMercator && options.tileAnchor) {
|
|
606
|
+
for (const prim of options.assembled.primitives) {
|
|
607
|
+
if (!prim.attributes.aMercatorOffset && prim.attributes.position) {
|
|
608
|
+
const offsets = bakeMercatorOffsets(
|
|
609
|
+
prim.attributes.position.array,
|
|
610
|
+
prim.vertexCount,
|
|
611
|
+
options.tileAnchor,
|
|
612
|
+
options.tileCenter ?? { x: 0, y: 0, z: 0 }
|
|
613
|
+
|
|
614
|
+
);
|
|
615
|
+
prim.attributes.aMercatorOffset = {
|
|
616
|
+
array: offsets,
|
|
617
|
+
components: 3,
|
|
618
|
+
normalized: false,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
577
623
|
const assembled =
|
|
578
624
|
options.assembled ??
|
|
579
625
|
assembleTileGeometry(tile, {
|
|
580
626
|
batching: options.batching !== false,
|
|
581
627
|
dedupeCoincident: options.dedupeCoincident,
|
|
628
|
+
bakeMercator: options.bakeMercator,
|
|
629
|
+
tileAnchor: options.tileAnchor,
|
|
630
|
+
tileCenter: options.tileCenter,
|
|
582
631
|
});
|
|
583
632
|
stats.vertices = assembled.stats.vertices;
|
|
584
633
|
stats.edgeLines = assembled.stats.edgeLines;
|
|
@@ -610,11 +659,13 @@ export async function buildTileObjects(
|
|
|
610
659
|
const geometry = packPrimitive(prim);
|
|
611
660
|
const geoRef = tile.geoPackage[prim.geoNames[0]] ?? null;
|
|
612
661
|
const vp = geoRef?.vertexPackage;
|
|
662
|
+
const hasMercator = !!prim.attributes.aMercatorOffset;
|
|
613
663
|
const material = makeMaterial(
|
|
614
664
|
prim.passNames[0],
|
|
615
665
|
!!prim.attributes.aAtlasRect,
|
|
616
666
|
!!prim.attributes.aTexCoordZ,
|
|
617
|
-
minVertexAlpha(prim.attributes.aColor)
|
|
667
|
+
minVertexAlpha(prim.attributes.aColor),
|
|
668
|
+
hasMercator
|
|
618
669
|
);
|
|
619
670
|
const matrix =
|
|
620
671
|
prim.matrix && prim.matrix.length === 16 ? new THREE.Matrix4().fromArray(prim.matrix) : null;
|