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/dist/render/S3MViewer.js
CHANGED
|
@@ -45,10 +45,19 @@ import { Path } from '../anim/Path.js';
|
|
|
45
45
|
import { GPUPathManager } from './GPUPathManager.js';
|
|
46
46
|
import { AttributeQuery } from '../core/AttributeQuery.js';
|
|
47
47
|
import { lngLatFromWorld, worldFromLngLat } from './GeoAdapter.js';
|
|
48
|
+
import { computeTilePlacement } from './tilePlacement.js';
|
|
48
49
|
import { DatasetManager, } from './DatasetManager.js';
|
|
49
50
|
import { FeatureStyleManager, extractEulerAngles, } from './FeatureStyleManager.js';
|
|
50
51
|
import { GroundManager, } from './GroundManager.js';
|
|
51
52
|
export { GroundManager, DatasetManager, FeatureStyleManager };
|
|
53
|
+
/** 稳健归一化 depthMode 入参,默认 'scene' */
|
|
54
|
+
export function normalizeDepthMode(mode) {
|
|
55
|
+
if (mode === 'clear')
|
|
56
|
+
return 'clear';
|
|
57
|
+
if (mode === 'auto')
|
|
58
|
+
return 'auto';
|
|
59
|
+
return 'scene';
|
|
60
|
+
}
|
|
52
61
|
function styledMaterialOf(mesh) {
|
|
53
62
|
const m = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
|
|
54
63
|
const styled = m;
|
|
@@ -71,6 +80,11 @@ function clampOpacity(v) {
|
|
|
71
80
|
}
|
|
72
81
|
/** 低于这个透明度就当作完全不可见:跳过绘制,避免全透明物体写深度 */
|
|
73
82
|
const OPACITY_EPS = 1e-3;
|
|
83
|
+
/**
|
|
84
|
+
* 拾取反解瓦片级放置时的临时矩阵。命中点→规范 ENU 是一次独立查询(鼠标事件),
|
|
85
|
+
* 复用一个实例避免每次分配;不可重入(`_toCanonicalScene` 内部不递归调用)。
|
|
86
|
+
*/
|
|
87
|
+
const _tilePlacementInverse = new THREE.Matrix4();
|
|
74
88
|
/* ------------------------------------------------------------------ 可动画的样式键 */
|
|
75
89
|
/**
|
|
76
90
|
* 能逐帧插值的标量样式键。
|
|
@@ -153,6 +167,8 @@ export class S3MViewer {
|
|
|
153
167
|
* 传了值就只认自己,多图层同页各用各的,互不覆盖。
|
|
154
168
|
*/
|
|
155
169
|
_materialQuality;
|
|
170
|
+
_lodTransition;
|
|
171
|
+
_fallbackOffset;
|
|
156
172
|
/**
|
|
157
173
|
* 本图层的颜色管线。
|
|
158
174
|
*
|
|
@@ -181,6 +197,7 @@ export class S3MViewer {
|
|
|
181
197
|
}
|
|
182
198
|
set sceneAnchor(val) {
|
|
183
199
|
this._datasetManager.sceneAnchor = val;
|
|
200
|
+
this._reapplyTilePlacements();
|
|
184
201
|
}
|
|
185
202
|
/** 拾取表 / pickId 池 / 射线求交(见 viewer/PickService.ts) */
|
|
186
203
|
_picks;
|
|
@@ -248,6 +265,13 @@ export class S3MViewer {
|
|
|
248
265
|
*/
|
|
249
266
|
_motionBlurCfg = null;
|
|
250
267
|
_motionBlur = null;
|
|
268
|
+
/**
|
|
269
|
+
* 瓦片级放置(默认关)。关时 `_registerTile` 完全不碰瓦片矩阵,代码路径与改动前
|
|
270
|
+
* 逐字一致;开时每块瓦片用自己的中心纬度算尺度,见 `tilePlacement.ts`。
|
|
271
|
+
*/
|
|
272
|
+
_perTilePlacement;
|
|
273
|
+
/** 本层绘制前深度缓冲的处理策略:'scene' | 'clear' | 'auto',默认 'scene' */
|
|
274
|
+
_depthMode = 'scene';
|
|
251
275
|
/** 宿主请求重画(MapLibre 传 triggerRepaint,纯 three 宿主传自己的渲染循环) */
|
|
252
276
|
_requestRepaint() {
|
|
253
277
|
this._onRequestRepaint();
|
|
@@ -261,6 +285,10 @@ export class S3MViewer {
|
|
|
261
285
|
this._lodRangeScale = options.lodRangeScale ?? 1.0;
|
|
262
286
|
this._unsupported = options.unsupported ?? 'warn';
|
|
263
287
|
this._lodPixelRatioCap = options.lodPixelRatioCap ?? Infinity;
|
|
288
|
+
// 瓦片级放置默认开启:旧行为是拿单一常量切平面拟合随纬度变化的墨卡托比例,属于 bug 修复,
|
|
289
|
+
// 所以默认值跟着改(见 AGENTS.md 不变式 ①)。显式传 false 仍可回退。
|
|
290
|
+
this._perTilePlacement = options.perTilePlacement !== false;
|
|
291
|
+
this._depthMode = normalizeDepthMode(options.depthMode);
|
|
264
292
|
setTextureSizeCap(options.maxTextureSize ?? Infinity);
|
|
265
293
|
// 运动模糊:默认关闭;开了也只在 renderFrame 里按需接管「场景→屏幕」这一步
|
|
266
294
|
this._motionBlurCfg = normalizeMotionBlur(options.motionBlur);
|
|
@@ -269,6 +297,11 @@ export class S3MViewer {
|
|
|
269
297
|
this._materialQuality = normalizeMaterialQuality(options.materialQuality);
|
|
270
298
|
// 之后 addDataset 的数据集按这个默认建材质(数据集自己指定则用自己的)
|
|
271
299
|
this._datasetManager.defaultMaterialQuality = this._materialQuality;
|
|
300
|
+
this._lodTransition = options.lodTransition ?? 'binary';
|
|
301
|
+
this._fallbackOffset = options.fallbackOffset;
|
|
302
|
+
this._datasetManager.defaultLodTransition = this._lodTransition;
|
|
303
|
+
this._datasetManager.defaultFallbackOffset = this._fallbackOffset;
|
|
304
|
+
this._datasetManager.defaultBakeMercator = options.bakeMercator;
|
|
272
305
|
if (options.totalMaxTiles) {
|
|
273
306
|
this._datasetManager.totalMaxTiles = options.totalMaxTiles;
|
|
274
307
|
}
|
|
@@ -343,6 +376,7 @@ export class S3MViewer {
|
|
|
343
376
|
return self._smidMeshes;
|
|
344
377
|
},
|
|
345
378
|
lngLatFromWorld: (x, y) => self._lngLatFromWorld(x, y),
|
|
379
|
+
toCanonicalScene: (p, mesh) => self._toCanonicalScene(p, mesh),
|
|
346
380
|
gpuPathPoseAt: (smid) => self._gpuPathPoseAt(smid),
|
|
347
381
|
isFeatureHidden: (smid, batchId) => self._isFeatureHidden(smid, batchId),
|
|
348
382
|
});
|
|
@@ -573,7 +607,15 @@ export class S3MViewer {
|
|
|
573
607
|
.filter((d) => !!d?.configUrl)
|
|
574
608
|
.map((d) => ({ ...d, polygonOffset: d.polygonOffset ?? options.polygonOffset }));
|
|
575
609
|
if (options.configUrl)
|
|
576
|
-
return [{
|
|
610
|
+
return [{
|
|
611
|
+
id: 'default',
|
|
612
|
+
name: '默认数据集',
|
|
613
|
+
configUrl: options.configUrl,
|
|
614
|
+
useWorker: options.useWorker,
|
|
615
|
+
polygonOffset: options.polygonOffset,
|
|
616
|
+
lodTransition: options.lodTransition,
|
|
617
|
+
fallbackOffset: options.fallbackOffset,
|
|
618
|
+
}];
|
|
577
619
|
return [];
|
|
578
620
|
}
|
|
579
621
|
/** 追加一个数据集。图层 addLayer 之前调用会抛错。 */
|
|
@@ -581,7 +623,12 @@ export class S3MViewer {
|
|
|
581
623
|
if (!this.scene)
|
|
582
624
|
throw new Error('[s3m] 图层尚未 addLayer,不能添加数据集');
|
|
583
625
|
// 图层级默认值:数据集自己指定了就用自己的,见 `S3MViewerOptions.unsupported`
|
|
584
|
-
const merged = {
|
|
626
|
+
const merged = {
|
|
627
|
+
...ds,
|
|
628
|
+
unsupported: ds.unsupported ?? this._unsupported,
|
|
629
|
+
lodTransition: ds.lodTransition ?? this._lodTransition,
|
|
630
|
+
fallbackOffset: ds.fallbackOffset ?? this._fallbackOffset,
|
|
631
|
+
};
|
|
585
632
|
const entry = await this._datasetManager.addDataset(merged, this.scene, this.geo, this.uniforms, this._lodRangeScale, this.options.buildTile, this.options.maxTiles ?? 64, this.options.maxConcurrent ?? 4, {
|
|
586
633
|
onTile: (node, e) => {
|
|
587
634
|
this._registerTile(node, e);
|
|
@@ -623,6 +670,8 @@ export class S3MViewer {
|
|
|
623
670
|
/** 当地图 CRS 投影切换或宿主 map 挂载后,重新计算场景锚点与各数据集 offset */
|
|
624
671
|
recomputeAnchors() {
|
|
625
672
|
this._datasetManager.recomputeAnchors(this.geo);
|
|
673
|
+
// 锚点变了 → 每块瓦片的 k 与 position 都要重算(关这项功能时是空转,一次遍历都不做)
|
|
674
|
+
this._reapplyTilePlacements();
|
|
626
675
|
this._heights.refreshAll();
|
|
627
676
|
this._requestRepaint();
|
|
628
677
|
}
|
|
@@ -1358,6 +1407,32 @@ export class S3MViewer {
|
|
|
1358
1407
|
}
|
|
1359
1408
|
/* ---------------------------------------------------------------- 瓦片注册 */
|
|
1360
1409
|
_registerTile(node, entry) {
|
|
1410
|
+
// 必须在 add / updateMatrixWorld 之前设置:后者会拿当前 matrix 算 matrixWorld 并缓存
|
|
1411
|
+
//
|
|
1412
|
+
// ★ 触发条件**不能**是「有没有包围球」。S3M 1.x 的叶子瓦片 XML 是
|
|
1413
|
+
// `<PageLods><Geode>`(外面没有 `<PagedLOD>` 包装),`parseXmlLods` 解析出来
|
|
1414
|
+
// pageLods 为空 → sphere 为 null。而 `entry.root` 上挂着 align 算出来的整体偏移
|
|
1415
|
+
// (索菲亚教堂是 heightRange.min = 119.84),那个偏移是靠 placement 里的
|
|
1416
|
+
// `- datasetOffset` 抵消的。少摆一块,这块就带着偏移显示 —— 实测 L23 整层
|
|
1417
|
+
// 因此沉到地下 120 m,且 `localZ - worldZ` 恒等于偏移量。
|
|
1418
|
+
// 缺球时用几何包围盒中心兜底;连几何都没有就退回旧口径。
|
|
1419
|
+
const center = node.sphere?.center ?? this._localCenterOf(node.group);
|
|
1420
|
+
if (this._perTilePlacement && center) {
|
|
1421
|
+
this._applyTilePlacement(node.group, center, entry);
|
|
1422
|
+
}
|
|
1423
|
+
// 上面没摆成(关掉本功能 / 缺锚点 / 几何为空)时 matrixAutoUpdate 仍是 true,走旧口径
|
|
1424
|
+
if (node.group.matrixAutoUpdate) {
|
|
1425
|
+
const sceneAnchor = this.sceneAnchor;
|
|
1426
|
+
if (sceneAnchor) {
|
|
1427
|
+
const sEast = sceneAnchor.meterInMercatorEast ?? sceneAnchor.meterInMercator;
|
|
1428
|
+
const sNorth = sceneAnchor.meterInMercatorNorth ?? sceneAnchor.meterInMercator;
|
|
1429
|
+
const sUp = sceneAnchor.meterInMercatorUp ?? sEast;
|
|
1430
|
+
const c = node.sphere
|
|
1431
|
+
? [node.sphere.center.x, node.sphere.center.y, node.sphere.center.z]
|
|
1432
|
+
: [0, 0, 0];
|
|
1433
|
+
this._updateTileUniforms(node.group, c, [1.0 / sEast, 1.0 / sNorth, 1.0 / sUp]);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1361
1436
|
entry.root.add(node.group);
|
|
1362
1437
|
node.group.updateMatrixWorld(true);
|
|
1363
1438
|
const hasNativeEdges = node.group.userData.hasNativeEdges === true;
|
|
@@ -1427,6 +1502,142 @@ export class S3MViewer {
|
|
|
1427
1502
|
this._sel.replayOnto(mesh);
|
|
1428
1503
|
}
|
|
1429
1504
|
}
|
|
1505
|
+
/**
|
|
1506
|
+
* 瓦片几何在**数据集局部 ENU** 里的包围盒中心,给没有包围球的瓦片兜底。
|
|
1507
|
+
*
|
|
1508
|
+
* 调用时机固定在 `entry.root.add()` 之前:此时 group 还没挂到 root 上、matrix 还是
|
|
1509
|
+
* 单位阵,`setFromObject` 得到的就是顶点自己的坐标系,正好是摆放要的枢轴。
|
|
1510
|
+
* 挂上 root 之后再算会带上 root 的 offset,枢轴就偏了。
|
|
1511
|
+
*/
|
|
1512
|
+
_localCenterOf(group) {
|
|
1513
|
+
if (!group.children.length)
|
|
1514
|
+
return null;
|
|
1515
|
+
const box = new THREE.Box3().setFromObject(group);
|
|
1516
|
+
if (box.isEmpty())
|
|
1517
|
+
return null;
|
|
1518
|
+
const c = box.getCenter(new THREE.Vector3());
|
|
1519
|
+
if (!Number.isFinite(c.x) || !Number.isFinite(c.y) || !Number.isFinite(c.z))
|
|
1520
|
+
return null;
|
|
1521
|
+
return { x: c.x, y: c.y, z: c.z };
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* 把一块瓦片摆到「用它自己中心纬度算出的尺度」上(`perTilePlacement` 开启时才走)。
|
|
1525
|
+
*
|
|
1526
|
+
* 矩阵是 `T(position) · S(k) · T(-pivot)`,展开后即 `v ↦ position + k·(v - pivot)`。
|
|
1527
|
+
* 直接用 `makeScale + setPosition` 一次写好,省掉两个临时矩阵:
|
|
1528
|
+
* `T(t)·S(k)·T(-c)` 的元素其实就是对角 `k` 加平移 `t - k·c`。
|
|
1529
|
+
*
|
|
1530
|
+
* 只写 `matrix`,不碰 `rotation / position / scale` —— three 在
|
|
1531
|
+
* `matrixAutoUpdate = false` 时直接使用 `matrix`,那些字段留着无害,
|
|
1532
|
+
* 但别让 `updateMatrix()` 有机会覆盖回来。
|
|
1533
|
+
*/
|
|
1534
|
+
_applyTilePlacement(group, center, entry) {
|
|
1535
|
+
const sceneAnchor = this.sceneAnchor;
|
|
1536
|
+
if (!sceneAnchor || !entry.anchor)
|
|
1537
|
+
return;
|
|
1538
|
+
const p = computeTilePlacement({
|
|
1539
|
+
center,
|
|
1540
|
+
datasetAnchor: entry.anchor,
|
|
1541
|
+
sceneAnchor,
|
|
1542
|
+
datasetOffset: entry.offset,
|
|
1543
|
+
geo: this.geo,
|
|
1544
|
+
});
|
|
1545
|
+
if (!p)
|
|
1546
|
+
return;
|
|
1547
|
+
const kEast = p.scaleEast ?? p.scale;
|
|
1548
|
+
const kNorth = p.scaleNorth ?? p.scale;
|
|
1549
|
+
const kUp = kEast;
|
|
1550
|
+
group.matrixAutoUpdate = false;
|
|
1551
|
+
group.matrix.makeScale(kEast, kNorth, kUp);
|
|
1552
|
+
group.matrix.setPosition(p.position[0] - kEast * p.pivot[0], p.position[1] - kNorth * p.pivot[1], p.position[2] - kUp * p.pivot[2]);
|
|
1553
|
+
group.matrixWorldNeedsUpdate = true;
|
|
1554
|
+
// 重锚点后要能重算:中心只属于注册那一刻,存到 group 上,不依赖 tileset 内部结构
|
|
1555
|
+
group.userData.tileCenter = {
|
|
1556
|
+
x: center.x,
|
|
1557
|
+
y: center.y,
|
|
1558
|
+
z: center.z,
|
|
1559
|
+
};
|
|
1560
|
+
// 逆矩阵缓存:_toCanonicalScene 常态路径无需每次拾取反复求逆,失效点仅在瓦片放置或重锚点
|
|
1561
|
+
const inv = (group.userData.placementInverse ??= new THREE.Matrix4());
|
|
1562
|
+
inv.copy(group.matrix).invert();
|
|
1563
|
+
const sSceneEast = sceneAnchor.meterInMercatorEast ?? sceneAnchor.meterInMercator;
|
|
1564
|
+
const sSceneNorth = sceneAnchor.meterInMercatorNorth ?? sceneAnchor.meterInMercator;
|
|
1565
|
+
const sSceneUp = sceneAnchor.meterInMercatorUp ?? sSceneEast;
|
|
1566
|
+
const invScale = p.invTileScale ?? [1.0 / (sSceneEast * kEast), 1.0 / (sSceneNorth * kNorth), 1.0 / (sSceneUp * kUp)];
|
|
1567
|
+
this._updateTileUniforms(group, p.pivot, invScale);
|
|
1568
|
+
}
|
|
1569
|
+
_updateTileUniforms(group, pivot, invTileScale) {
|
|
1570
|
+
group.traverse((obj) => {
|
|
1571
|
+
const mesh = obj;
|
|
1572
|
+
if ((obj.isMesh || obj.isPoints) && obj.material) {
|
|
1573
|
+
const mats = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
1574
|
+
for (const m of mats) {
|
|
1575
|
+
const uPivot = m.userData?.uTilePivot;
|
|
1576
|
+
const uScale = m.userData?.uInvTileScale;
|
|
1577
|
+
if (uPivot?.value) {
|
|
1578
|
+
uPivot.value.set(pivot[0], pivot[1], pivot[2]);
|
|
1579
|
+
}
|
|
1580
|
+
if (uScale?.value) {
|
|
1581
|
+
uScale.value.set(invTileScale[0], invTileScale[1], invTileScale[2]);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* 世界坐标 → **规范**场景 ENU:把瓦片级放置的缩放反解掉。
|
|
1589
|
+
*
|
|
1590
|
+
* `perTilePlacement` 开启后,瓦片几何是「绕自身中心缩放 k 倍、再摆到精确位置」的,
|
|
1591
|
+
* 所以命中点的世界坐标不再等于数据本身的 ENU。拾取要报的是**坐标本身**——
|
|
1592
|
+
* 一个屋顶的经纬度不该因为我们怎么缩放它而改变,否则同一个点在 20 km 外会比
|
|
1593
|
+
* 1 km 内偏出几米,量算、贴地、外部标注全都跟着漂。
|
|
1594
|
+
*
|
|
1595
|
+
* 反解只需要一次矩阵求逆:父级 `entry.root` 只有平移(`layoutDataset` 只写
|
|
1596
|
+
* `position`),所以世界坐标 `p` 对应的瓦片局部坐标是
|
|
1597
|
+
*
|
|
1598
|
+
* ```
|
|
1599
|
+
* v = group.matrix⁻¹ · (p − root.position)
|
|
1600
|
+
* canonical = root.position + v
|
|
1601
|
+
* ```
|
|
1602
|
+
*
|
|
1603
|
+
* 关掉本功能、或网格不在已放置的瓦片里时**原样返回同一个对象**,
|
|
1604
|
+
* 默认路径一次矩阵运算都不做。
|
|
1605
|
+
*/
|
|
1606
|
+
_toCanonicalScene(p, mesh) {
|
|
1607
|
+
if (!this._perTilePlacement || !mesh)
|
|
1608
|
+
return p;
|
|
1609
|
+
// 网格直接挂在瓦片 group 下(`_registerTile` 就是遍历 node.group.children);
|
|
1610
|
+
// 往上找到第一个带 tileCenter 的祖先,避免依赖层级深度
|
|
1611
|
+
let group = mesh.parent;
|
|
1612
|
+
while (group && !group.userData.tileCenter)
|
|
1613
|
+
group = group.parent;
|
|
1614
|
+
if (!group)
|
|
1615
|
+
return p;
|
|
1616
|
+
const root = group.parent;
|
|
1617
|
+
if (!root)
|
|
1618
|
+
return p;
|
|
1619
|
+
return p
|
|
1620
|
+
.clone()
|
|
1621
|
+
.sub(root.position)
|
|
1622
|
+
.applyMatrix4(group.userData.placementInverse ?? _tilePlacementInverse.copy(group.matrix).invert())
|
|
1623
|
+
.add(root.position);
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* 场景锚点变了(`recomputeAnchors`)之后,所有已注册瓦片的放置都要重算 ——
|
|
1627
|
+
* `k` 与 `position` 都依赖场景锚点。没存 `tileCenter` 的(未开启本功能时注册的)
|
|
1628
|
+
* 一律跳过,保证默认路径不产生任何副作用。
|
|
1629
|
+
*/
|
|
1630
|
+
_reapplyTilePlacements() {
|
|
1631
|
+
for (const entry of this._datasetManager.values()) {
|
|
1632
|
+
if (!entry.anchor)
|
|
1633
|
+
continue;
|
|
1634
|
+
for (const child of entry.root.children) {
|
|
1635
|
+
const c = child.userData.tileCenter;
|
|
1636
|
+
if (c)
|
|
1637
|
+
this._applyTilePlacement(child, c, entry);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1430
1641
|
/**
|
|
1431
1642
|
* 新瓦片淡入。
|
|
1432
1643
|
*
|
|
@@ -1881,6 +2092,16 @@ export class S3MViewer {
|
|
|
1881
2092
|
this._requestRepaint();
|
|
1882
2093
|
}
|
|
1883
2094
|
/** 当前材质质感档位(本图层指定了就用本图层的,否则是模块级默认) */
|
|
2095
|
+
get lodTransition() {
|
|
2096
|
+
return this._lodTransition;
|
|
2097
|
+
}
|
|
2098
|
+
setLodTransition(mode) {
|
|
2099
|
+
if (this._lodTransition === mode)
|
|
2100
|
+
return;
|
|
2101
|
+
this._lodTransition = mode;
|
|
2102
|
+
this._datasetManager.setDefaultLodTransition(mode);
|
|
2103
|
+
this.triggerRepaint();
|
|
2104
|
+
}
|
|
1884
2105
|
get materialQuality() {
|
|
1885
2106
|
return this._materialQuality ?? getMaterialQuality();
|
|
1886
2107
|
}
|
|
@@ -1901,6 +2122,55 @@ export class S3MViewer {
|
|
|
1901
2122
|
this.triggerRepaint();
|
|
1902
2123
|
}
|
|
1903
2124
|
/** 当前运动模糊配置(归一化后的副本);null = 关闭 */
|
|
2125
|
+
/**
|
|
2126
|
+
* 运行时修改深度处理策略('scene' | 'clear' | 'auto')。
|
|
2127
|
+
*/
|
|
2128
|
+
setDepthMode(mode) {
|
|
2129
|
+
const next = normalizeDepthMode(mode);
|
|
2130
|
+
if (this._depthMode !== next) {
|
|
2131
|
+
this._depthMode = next;
|
|
2132
|
+
this.triggerRepaint();
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
/** 当前深度策略('scene' | 'clear' | 'auto',默认 'scene') */
|
|
2136
|
+
get depthMode() {
|
|
2137
|
+
return this._depthMode;
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
2140
|
+
* 当前帧实际生效的深度处理模式('scene' | 'clear')。
|
|
2141
|
+
* - 'clear': 显式指定 clear,或 auto 模式下命中地面覆盖/倾斜摄影/DEM等地面类数据
|
|
2142
|
+
* - 'scene': 显式指定 scene,或 auto 模式下无地面类数据
|
|
2143
|
+
*/
|
|
2144
|
+
resolveDepthMode() {
|
|
2145
|
+
if (this._depthMode === 'clear')
|
|
2146
|
+
return 'clear';
|
|
2147
|
+
if (this._depthMode === 'scene')
|
|
2148
|
+
return 'scene';
|
|
2149
|
+
// 'auto': 按数据集与地面覆盖特征推断
|
|
2150
|
+
if (this.groundManager && this.groundManager.mode !== 'none') {
|
|
2151
|
+
return 'clear';
|
|
2152
|
+
}
|
|
2153
|
+
for (const entry of this._datasetManager.datasets.values()) {
|
|
2154
|
+
if (!entry.visible)
|
|
2155
|
+
continue;
|
|
2156
|
+
if (entry.options.depthMode === 'clear')
|
|
2157
|
+
return 'clear';
|
|
2158
|
+
if (entry.options.depthMode === 'scene')
|
|
2159
|
+
continue;
|
|
2160
|
+
if (entry.tileset?.isS3MV1)
|
|
2161
|
+
return 'clear';
|
|
2162
|
+
const cfg = entry.tileset?.config;
|
|
2163
|
+
if (cfg) {
|
|
2164
|
+
const typeStr = `${cfg.fileType ?? ''} ${cfg.dataType ?? ''} ${cfg.datasetType ?? ''} ${cfg.cacheType ?? ''} ${cfg.fileExtentName ?? ''}`;
|
|
2165
|
+
if (/osgb|oblique|ground|dem|tin|terrain/i.test(typeStr))
|
|
2166
|
+
return 'clear';
|
|
2167
|
+
}
|
|
2168
|
+
if (entry.options.configUrl && /osgb|oblique/i.test(entry.options.configUrl)) {
|
|
2169
|
+
return 'clear';
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
return 'scene';
|
|
2173
|
+
}
|
|
1904
2174
|
get motionBlur() {
|
|
1905
2175
|
return this._motionBlurCfg ? { ...this._motionBlurCfg } : null;
|
|
1906
2176
|
}
|
|
@@ -2194,10 +2464,11 @@ export class S3MViewer {
|
|
|
2194
2464
|
const anchor = this.sceneAnchor;
|
|
2195
2465
|
if (!anchor)
|
|
2196
2466
|
return [0, 0, 0];
|
|
2197
|
-
const
|
|
2467
|
+
const sEast = anchor.meterInMercatorEast ?? anchor.meterInMercator;
|
|
2468
|
+
const sNorth = anchor.meterInMercatorNorth ?? anchor.meterInMercator;
|
|
2198
2469
|
const w = worldFromLngLat(this.geo, p.lng, p.lat, p.altitude);
|
|
2199
2470
|
// 注意 y 取反:three 的 y 向上,墨卡托 y 向下(与 PickService.pickPosition 互逆)
|
|
2200
|
-
return [(w.x - anchor.x) /
|
|
2471
|
+
return [(w.x - anchor.x) / sEast, -(w.y - anchor.y) / sNorth, p.altitude - (anchor.altitude ?? 0)];
|
|
2201
2472
|
}
|
|
2202
2473
|
/**
|
|
2203
2474
|
* 经纬度 + 绝对高程 → 场景 ENU 米制坐标。
|
|
@@ -2218,8 +2489,9 @@ export class S3MViewer {
|
|
|
2218
2489
|
const anchor = this.sceneAnchor;
|
|
2219
2490
|
if (!anchor)
|
|
2220
2491
|
return { lng: 0, lat: 0, altitude: v[2] };
|
|
2221
|
-
const
|
|
2222
|
-
const
|
|
2492
|
+
const sEast = anchor.meterInMercatorEast ?? anchor.meterInMercator;
|
|
2493
|
+
const sNorth = anchor.meterInMercatorNorth ?? anchor.meterInMercator;
|
|
2494
|
+
const [lng, lat] = lngLatFromWorld(this.geo, v[0] * sEast + anchor.x, -v[1] * sNorth + anchor.y);
|
|
2223
2495
|
return { lng, lat, altitude: v[2] + (anchor.altitude ?? 0) };
|
|
2224
2496
|
}
|
|
2225
2497
|
/**
|
|
@@ -2645,9 +2917,11 @@ export class S3MViewer {
|
|
|
2645
2917
|
// 脏标记正常由微任务刷掉;这里再兜一次底,保证「本帧画出来的」一定是最新样式
|
|
2646
2918
|
this.flushOverrides();
|
|
2647
2919
|
const canvas = this.canvas;
|
|
2648
|
-
const
|
|
2920
|
+
const sEast = this.sceneAnchor.meterInMercatorEast ?? this.sceneAnchor.meterInMercator;
|
|
2921
|
+
const sNorth = this.sceneAnchor.meterInMercatorNorth ?? this.sceneAnchor.meterInMercator;
|
|
2922
|
+
const sUp = this.sceneAnchor.meterInMercatorUp ?? sEast;
|
|
2649
2923
|
// ENU → 世界坐标:x 向东,y 向北(墨卡托 y 朝南,所以取负),z 向上
|
|
2650
|
-
this._enuMatrix.set(
|
|
2924
|
+
this._enuMatrix.set(sEast, 0, 0, this.sceneAnchor.x, 0, -sNorth, 0, this.sceneAnchor.y, 0, 0, sUp, this.sceneAnchor.z, 0, 0, 0, 1);
|
|
2651
2925
|
// 正交模式由宿主在传入 mvp 前自行替换(见 s3m-maplibre 的 S3MLayer._applyOrtho),
|
|
2652
2926
|
// 视图矩阵是从宿主输入反推的(P⁻¹×MVP),不会和宿主的 pan/zoom/pitch 错位。
|
|
2653
2927
|
const mvp = this._frameMvp.fromArray(opts.mvp);
|
|
@@ -2694,6 +2968,19 @@ export class S3MViewer {
|
|
|
2694
2968
|
// 泼溅的最小像素半径 / 抗锯齿低通同样要把 ndc 换算成像素
|
|
2695
2969
|
setSplatViewport(canvas.width, canvas.height);
|
|
2696
2970
|
this.renderer.setViewport(0, 0, canvas.width, canvas.height);
|
|
2971
|
+
const effectiveDepthMode = (this.uniforms.uOpacity?.value ?? 1) > OPACITY_EPS
|
|
2972
|
+
? this.resolveDepthMode()
|
|
2973
|
+
: 'scene';
|
|
2974
|
+
if (effectiveDepthMode === 'clear' && gl) {
|
|
2975
|
+
// gl.clear 受 depthMask 约束。上一帧若以 depthWrite=false 的材质收尾(棱线、半透明
|
|
2976
|
+
// 地表覆盖、半透明点云、泼溅),GL 的 depthMask 会停在 false —— three 帧末不恢复它,
|
|
2977
|
+
// 而 resetState 排在 clear 之后,救不了这一步。不自己打开就是一次静默的空操作:
|
|
2978
|
+
// 画面上的 z-fighting 原样保留,且没有任何报错可查。
|
|
2979
|
+
gl.depthMask(true);
|
|
2980
|
+
// 只清深度,不清颜色 —— 颜色是 MapLibre 已经画好的底图
|
|
2981
|
+
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
2982
|
+
}
|
|
2983
|
+
// 清完之后必须让 three 知道状态被外部改过,否则它的状态缓存会认为自己仍然正确
|
|
2697
2984
|
this.renderer.resetState();
|
|
2698
2985
|
// 完全透明时什么都不画:透明物体照样写深度,画了会在底图上挖出一个洞。
|
|
2699
2986
|
// 这里跳过的是「本层的绘制」,宿主自己的清屏 / 底图照旧。
|
|
@@ -2707,7 +2994,10 @@ export class S3MViewer {
|
|
|
2707
2994
|
}
|
|
2708
2995
|
// GPU 描边必须画在主场景之后:它的全屏 quad 参与宿主深度测试,
|
|
2709
2996
|
// 依赖本帧已经写好的深度(被地形挡住的地方不该描边)
|
|
2710
|
-
|
|
2997
|
+
// clear 模式下宿主深度已清除,描边会穿透地形产生伪影,故只在非 clear 时画。
|
|
2998
|
+
if (effectiveDepthMode !== 'clear') {
|
|
2999
|
+
this._edges.renderGpuEdges(canvas);
|
|
3000
|
+
}
|
|
2711
3001
|
}
|
|
2712
3002
|
this._frame++;
|
|
2713
3003
|
const now = performance.now();
|