s3m-three 0.4.26 → 0.4.28
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-B2u-xt54.js +1 -0
- 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/S3M1Parser.d.ts +19 -0
- package/dist/core/S3M1Parser.d.ts.map +1 -1
- package/dist/core/S3M1Parser.js +179 -8
- package/dist/core/S3M1Parser.js.map +1 -1
- 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 +80 -2
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +299 -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 +217 -2
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/worker/s3mWorker.js +691 -508
- 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/S3M1Parser.ts +179 -9
- 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 +335 -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 +240 -3
- package/dist/browser/assets/s3mWorker-DHOVIrAU.js +0 -1
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/S3M1Parser.ts
CHANGED
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
*/
|
|
63
63
|
import { BinaryReader } from './BinaryReader.js';
|
|
64
64
|
import type {
|
|
65
|
+
Geode,
|
|
65
66
|
GeoPackageEntry,
|
|
66
67
|
GroupNode,
|
|
67
68
|
IndexPackage,
|
|
@@ -124,7 +125,13 @@ interface XmlMaterial {
|
|
|
124
125
|
addressMode?: { u: number; v: number };
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
interface
|
|
128
|
+
export interface XmlGeode {
|
|
129
|
+
geoName: string;
|
|
130
|
+
matrix: number[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface XmlLod {
|
|
134
|
+
geodes?: XmlGeode[];
|
|
128
135
|
geoNames: string[];
|
|
129
136
|
matrix: number[];
|
|
130
137
|
childTile: string;
|
|
@@ -158,7 +165,7 @@ function rangeModeOf(text: string | null): number {
|
|
|
158
165
|
return 1;
|
|
159
166
|
}
|
|
160
167
|
|
|
161
|
-
function parseXmlLods(xml: string): XmlLod[] {
|
|
168
|
+
export function parseXmlLods(xml: string): XmlLod[] {
|
|
162
169
|
const blocks = [...xml.matchAll(/<(?:[A-Za-z0-9_-]+:)?PagedLOD[^>]*>([\s\S]*?)<\/(?:[A-Za-z0-9_-]+:)?PagedLOD>/gi)];
|
|
163
170
|
const out: XmlLod[] = [];
|
|
164
171
|
for (const b of blocks) {
|
|
@@ -180,6 +187,39 @@ function parseXmlLods(xml: string): XmlLod[] {
|
|
|
180
187
|
radius: num(tag(sphere, 'radius')),
|
|
181
188
|
});
|
|
182
189
|
}
|
|
190
|
+
// 兜底:S3M 1.x 叶子瓦片没有 <PagedLOD>,<PageLods> 内部直接是 <Geode>
|
|
191
|
+
if (out.length === 0) {
|
|
192
|
+
const pageLodsBlock = /<(?:[A-Za-z0-9_-]+:)?PageLods[^>]*>([\s\S]*?)<\/(?:[A-Za-z0-9_-]+:)?PageLods>/i.exec(xml);
|
|
193
|
+
const scope = pageLodsBlock ? pageLodsBlock[1] : xml;
|
|
194
|
+
const geodeBlocks = [...scope.matchAll(/<(?:[A-Za-z0-9_-]+:)?Geode[^>]*>([\s\S]*?)<\/(?:[A-Za-z0-9_-]+:)?Geode>/gi)];
|
|
195
|
+
if (geodeBlocks.length > 0) {
|
|
196
|
+
const geodes: XmlGeode[] = [];
|
|
197
|
+
for (const gb of geodeBlocks) {
|
|
198
|
+
const gs = gb[1];
|
|
199
|
+
const gName = tag(gs, "GeoName") ?? "";
|
|
200
|
+
const matStr = tag(gs, "GeoDeModMatrix");
|
|
201
|
+
const mat = (matStr ?? "1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1")
|
|
202
|
+
.split(",")
|
|
203
|
+
.map((x) => parseFloat(x.trim()))
|
|
204
|
+
.filter((x) => Number.isFinite(x));
|
|
205
|
+
geodes.push({
|
|
206
|
+
geoName: gName,
|
|
207
|
+
matrix: mat.length === 16 ? mat : [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
out.push({
|
|
211
|
+
geoNames: geodes.map((g) => g.geoName).filter(Boolean),
|
|
212
|
+
matrix: geodes[0]?.matrix ?? [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
|
213
|
+
geodes,
|
|
214
|
+
childTile: "",
|
|
215
|
+
rangeMode: rangeModeOf(null), // 1 = 屏幕像素
|
|
216
|
+
rangeValue: 0,
|
|
217
|
+
center: { x: 0, y: 0, z: 0 },
|
|
218
|
+
radius: 0,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
183
223
|
return out;
|
|
184
224
|
}
|
|
185
225
|
|
|
@@ -645,6 +685,108 @@ function readTextures(br: BinaryReader, texSize: number): Record<string, S3MText
|
|
|
645
685
|
let warnedV1Attrs = false;
|
|
646
686
|
let warnedQuantBox = false;
|
|
647
687
|
|
|
688
|
+
function computeLodBoundingSphere(
|
|
689
|
+
geodes: { matrix: number[]; skeletonNames: string[] }[],
|
|
690
|
+
meshes: MeshRecord[]
|
|
691
|
+
): { center: { x: number; y: number; z: number }; radius: number } | null {
|
|
692
|
+
const meshMap = new Map(meshes.map((m) => [`SuperMapCache#${m.geoName}`, m]));
|
|
693
|
+
let minX = Infinity, maxX = -Infinity;
|
|
694
|
+
let minY = Infinity, maxY = -Infinity;
|
|
695
|
+
let minZ = Infinity, maxZ = -Infinity;
|
|
696
|
+
let totalPoints = 0;
|
|
697
|
+
|
|
698
|
+
for (const geode of geodes) {
|
|
699
|
+
const mat = geode.matrix;
|
|
700
|
+
const isIdentity =
|
|
701
|
+
!mat ||
|
|
702
|
+
(mat[0] === 1 && mat[1] === 0 && mat[2] === 0 && mat[3] === 0 &&
|
|
703
|
+
mat[4] === 0 && mat[5] === 1 && mat[6] === 0 && mat[7] === 0 &&
|
|
704
|
+
mat[8] === 0 && mat[9] === 0 && mat[10] === 1 && mat[11] === 0 &&
|
|
705
|
+
mat[12] === 0 && mat[13] === 0 && mat[14] === 0 && mat[15] === 1);
|
|
706
|
+
|
|
707
|
+
for (const name of geode.skeletonNames) {
|
|
708
|
+
const mesh = meshMap.get(name);
|
|
709
|
+
if (!mesh || !mesh.position) continue;
|
|
710
|
+
const pos = mesh.position;
|
|
711
|
+
totalPoints += pos.length / 3;
|
|
712
|
+
|
|
713
|
+
if (isIdentity) {
|
|
714
|
+
for (let i = 0; i < pos.length; i += 3) {
|
|
715
|
+
const x = pos[i]!, y = pos[i + 1]!, z = pos[i + 2]!;
|
|
716
|
+
if (x < minX) minX = x; if (x > maxX) maxX = x;
|
|
717
|
+
if (y < minY) minY = y; if (y > maxY) maxY = y;
|
|
718
|
+
if (z < minZ) minZ = z; if (z > maxZ) maxZ = z;
|
|
719
|
+
}
|
|
720
|
+
} else {
|
|
721
|
+
const m0 = mat[0]!, m1 = mat[1]!, m2 = mat[2]!;
|
|
722
|
+
const m4 = mat[4]!, m5 = mat[5]!, m6 = mat[6]!;
|
|
723
|
+
const m8 = mat[8]!, m9 = mat[9]!, m10 = mat[10]!;
|
|
724
|
+
const m12 = mat[12]!, m13 = mat[13]!, m14 = mat[14]!;
|
|
725
|
+
for (let i = 0; i < pos.length; i += 3) {
|
|
726
|
+
const x = pos[i]!, y = pos[i + 1]!, z = pos[i + 2]!;
|
|
727
|
+
const tx = m0 * x + m4 * y + m8 * z + m12;
|
|
728
|
+
const ty = m1 * x + m5 * y + m9 * z + m13;
|
|
729
|
+
const tz = m2 * x + m6 * y + m10 * z + m14;
|
|
730
|
+
if (tx < minX) minX = tx; if (tx > maxX) maxX = tx;
|
|
731
|
+
if (ty < minY) minY = ty; if (ty > maxY) maxY = ty;
|
|
732
|
+
if (tz < minZ) minZ = tz; if (tz > maxZ) maxZ = tz;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (totalPoints === 0 || !Number.isFinite(minX)) return null;
|
|
739
|
+
|
|
740
|
+
const cx = (minX + maxX) * 0.5;
|
|
741
|
+
const cy = (minY + maxY) * 0.5;
|
|
742
|
+
const cz = (minZ + maxZ) * 0.5;
|
|
743
|
+
|
|
744
|
+
let maxR2 = 0;
|
|
745
|
+
for (const geode of geodes) {
|
|
746
|
+
const mat = geode.matrix;
|
|
747
|
+
const isIdentity =
|
|
748
|
+
!mat ||
|
|
749
|
+
(mat[0] === 1 && mat[1] === 0 && mat[2] === 0 && mat[3] === 0 &&
|
|
750
|
+
mat[4] === 0 && mat[5] === 1 && mat[6] === 0 && mat[7] === 0 &&
|
|
751
|
+
mat[8] === 0 && mat[9] === 0 && mat[10] === 1 && mat[11] === 0 &&
|
|
752
|
+
mat[12] === 0 && mat[13] === 0 && mat[14] === 0 && mat[15] === 1);
|
|
753
|
+
|
|
754
|
+
for (const name of geode.skeletonNames) {
|
|
755
|
+
const mesh = meshMap.get(name);
|
|
756
|
+
if (!mesh || !mesh.position) continue;
|
|
757
|
+
const pos = mesh.position;
|
|
758
|
+
|
|
759
|
+
if (isIdentity) {
|
|
760
|
+
for (let i = 0; i < pos.length; i += 3) {
|
|
761
|
+
const dx = pos[i]! - cx, dy = pos[i + 1]! - cy, dz = pos[i + 2]! - cz;
|
|
762
|
+
const r2 = dx * dx + dy * dy + dz * dz;
|
|
763
|
+
if (r2 > maxR2) maxR2 = r2;
|
|
764
|
+
}
|
|
765
|
+
} else {
|
|
766
|
+
const m0 = mat[0]!, m1 = mat[1]!, m2 = mat[2]!;
|
|
767
|
+
const m4 = mat[4]!, m5 = mat[5]!, m6 = mat[6]!;
|
|
768
|
+
const m8 = mat[8]!, m9 = mat[9]!, m10 = mat[10]!;
|
|
769
|
+
const m12 = mat[12]!, m13 = mat[13]!, m14 = mat[14]!;
|
|
770
|
+
for (let i = 0; i < pos.length; i += 3) {
|
|
771
|
+
const x = pos[i]!, y = pos[i + 1]!, z = pos[i + 2]!;
|
|
772
|
+
const tx = m0 * x + m4 * y + m8 * z + m12;
|
|
773
|
+
const ty = m1 * x + m5 * y + m9 * z + m13;
|
|
774
|
+
const tz = m2 * x + m6 * y + m10 * z + m14;
|
|
775
|
+
const dx = tx - cx, dy = ty - cy, dz = tz - cz;
|
|
776
|
+
const r2 = dx * dx + dy * dy + dz * dz;
|
|
777
|
+
if (r2 > maxR2) maxR2 = r2;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const radius = Math.sqrt(maxR2);
|
|
784
|
+
return {
|
|
785
|
+
center: { x: cx, y: cy, z: cz },
|
|
786
|
+
radius: radius > 0 ? radius : 0.001,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
648
790
|
export async function parseS3M1(arrayBuffer: ArrayBuffer, options: ParseS3M1Options = {}): Promise<ParsedTile> {
|
|
649
791
|
const inflate = options.inflate ?? defaultInflate;
|
|
650
792
|
const r = new BinaryReader(arrayBuffer);
|
|
@@ -770,17 +912,45 @@ export async function parseS3M1(arrayBuffer: ArrayBuffer, options: ParseS3M1Opti
|
|
|
770
912
|
const xmlLods = parseXmlLods(xml);
|
|
771
913
|
const pageLods: PageLOD[] = [];
|
|
772
914
|
xmlLods.forEach((lod, i) => {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
915
|
+
let geodes: Geode[];
|
|
916
|
+
if (lod.geodes && lod.geodes.length > 0) {
|
|
917
|
+
geodes = lod.geodes
|
|
918
|
+
.map((g, gi) => {
|
|
919
|
+
const key = keyOf.get(g.geoName);
|
|
920
|
+
const fallback = meshes[gi] ? [`SuperMapCache#${meshes[gi].geoName}`] : [];
|
|
921
|
+
const skeletonNames = key ? [key] : fallback;
|
|
922
|
+
return {
|
|
923
|
+
matrix: g.matrix,
|
|
924
|
+
skeletonNames,
|
|
925
|
+
};
|
|
926
|
+
})
|
|
927
|
+
.filter((g) => g.skeletonNames.length > 0);
|
|
928
|
+
} else {
|
|
929
|
+
const named = lod.geoNames.map((n) => keyOf.get(n)).filter((k): k is string => !!k);
|
|
930
|
+
// 名字对不上时退回位置匹配,并让上层有机会发现(宁可少一个 LOD 也不要挂错球)
|
|
931
|
+
const fallback = meshes[i] ? [`SuperMapCache#${meshes[i].geoName}`] : [];
|
|
932
|
+
const skeletonNames = named.length ? [...new Set(named)] : fallback;
|
|
933
|
+
geodes = skeletonNames.length ? [{ matrix: lod.matrix, skeletonNames }] : [];
|
|
934
|
+
}
|
|
935
|
+
if (!geodes.length) return;
|
|
936
|
+
|
|
937
|
+
let center = { ...lod.center };
|
|
938
|
+
let radius = lod.radius;
|
|
939
|
+
// 缺包围球(如叶子瓦片)时,从网格顶点实时计算真实几何包围球
|
|
940
|
+
if (radius <= 0) {
|
|
941
|
+
const computed = computeLodBoundingSphere(geodes, meshes);
|
|
942
|
+
if (computed) {
|
|
943
|
+
center = computed.center;
|
|
944
|
+
radius = computed.radius;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
778
948
|
pageLods.push({
|
|
779
949
|
rangeValue: lod.rangeValue,
|
|
780
950
|
rangeMode: lod.rangeMode,
|
|
781
|
-
boundingSphere: { center
|
|
951
|
+
boundingSphere: { center, radius },
|
|
782
952
|
childTile: lod.childTile,
|
|
783
|
-
geodes
|
|
953
|
+
geodes,
|
|
784
954
|
});
|
|
785
955
|
});
|
|
786
956
|
const groupNode: GroupNode = { pageLods };
|
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);
|