s3m-three 0.4.18 → 0.4.19
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-4iTAmWIu.js +1 -0
- package/dist/browser/s3m-three.iife.ext-three.js +406 -91
- package/dist/browser/s3m-three.iife.js +434 -119
- package/dist/core/GaussianSplat.d.ts +64 -0
- package/dist/core/GaussianSplat.d.ts.map +1 -0
- package/dist/core/GaussianSplat.js +139 -0
- package/dist/core/GaussianSplat.js.map +1 -0
- package/dist/core/S3M1Parser.d.ts +1 -1
- package/dist/core/S3M1Parser.d.ts.map +1 -1
- package/dist/core/S3M1Parser.js +320 -25
- package/dist/core/S3M1Parser.js.map +1 -1
- package/dist/core/S3MBParser.d.ts.map +1 -1
- package/dist/core/S3MBParser.js +13 -0
- package/dist/core/S3MBParser.js.map +1 -1
- package/dist/core/S3MBlockParser.d.ts.map +1 -1
- package/dist/core/S3MBlockParser.js +56 -14
- package/dist/core/S3MBlockParser.js.map +1 -1
- package/dist/core/SplatLod.d.ts +36 -0
- package/dist/core/SplatLod.d.ts.map +1 -0
- package/dist/core/SplatLod.js +54 -0
- package/dist/core/SplatLod.js.map +1 -0
- package/dist/core/SplatProjection.d.ts +75 -0
- package/dist/core/SplatProjection.d.ts.map +1 -0
- package/dist/core/SplatProjection.js +162 -0
- package/dist/core/SplatProjection.js.map +1 -0
- package/dist/core/SplatSort.d.ts +13 -0
- package/dist/core/SplatSort.d.ts.map +1 -0
- package/dist/core/SplatSort.js +14 -0
- package/dist/core/SplatSort.js.map +1 -0
- package/dist/core/TexCoordZ.d.ts +94 -0
- package/dist/core/TexCoordZ.d.ts.map +1 -0
- package/dist/core/TexCoordZ.js +125 -0
- package/dist/core/TexCoordZ.js.map +1 -0
- package/dist/core/TextureDecode.d.ts +19 -0
- package/dist/core/TextureDecode.d.ts.map +1 -1
- package/dist/core/TextureDecode.js +53 -11
- package/dist/core/TextureDecode.js.map +1 -1
- package/dist/core/TileGeometry.d.ts +29 -0
- package/dist/core/TileGeometry.d.ts.map +1 -1
- package/dist/core/TileGeometry.js +130 -14
- package/dist/core/TileGeometry.js.map +1 -1
- package/dist/core/degradations.d.ts +37 -0
- package/dist/core/degradations.d.ts.map +1 -0
- package/dist/core/degradations.js +32 -0
- package/dist/core/degradations.js.map +1 -0
- package/dist/core/types.d.ts +36 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +11 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +2 -0
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/FeatureStyleManager.d.ts.map +1 -1
- package/dist/render/FeatureStyleManager.js +7 -5
- package/dist/render/FeatureStyleManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +24 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +196 -5
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/Picker.d.ts.map +1 -1
- package/dist/render/Picker.js +19 -5
- package/dist/render/Picker.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +69 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +222 -12
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/SplatMaterial.d.ts +103 -0
- package/dist/render/SplatMaterial.d.ts.map +1 -0
- package/dist/render/SplatMaterial.js +388 -0
- package/dist/render/SplatMaterial.js.map +1 -0
- package/dist/render/SplatSorter.d.ts +14 -0
- package/dist/render/SplatSorter.d.ts.map +1 -0
- package/dist/render/SplatSorter.js +59 -0
- package/dist/render/SplatSorter.js.map +1 -0
- package/dist/render/StyleMaterial.d.ts +48 -0
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +292 -69
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +10 -0
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +35 -5
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/render/lightingPreset.d.ts +53 -0
- package/dist/render/lightingPreset.d.ts.map +1 -0
- package/dist/render/lightingPreset.js +36 -0
- package/dist/render/lightingPreset.js.map +1 -0
- package/dist/render/materialAlpha.d.ts +51 -0
- package/dist/render/materialAlpha.d.ts.map +1 -0
- package/dist/render/materialAlpha.js +68 -0
- package/dist/render/materialAlpha.js.map +1 -0
- package/dist/tileset/S3MTileset.d.ts +42 -0
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +52 -1
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/vendor/browser-node-stub.js +10 -0
- package/dist/worker/s3mWorker.js +1166 -979
- package/dist/worker/s3mWorker.js.map +1 -1
- package/docs/API.md +55 -1
- package/docs/FORMAT-NOTES.md +338 -0
- package/package.json +1 -1
- package/src/core/GaussianSplat.ts +175 -0
- package/src/core/S3M1Parser.ts +330 -24
- package/src/core/S3MBParser.ts +18 -0
- package/src/core/S3MBlockParser.ts +56 -11
- package/src/core/SplatLod.ts +83 -0
- package/src/core/SplatProjection.ts +231 -0
- package/src/core/SplatSort.ts +30 -0
- package/src/core/TexCoordZ.ts +160 -0
- package/src/core/TextureDecode.ts +63 -11
- package/src/core/TileGeometry.ts +138 -14
- package/src/core/degradations.ts +74 -0
- package/src/core/types.ts +32 -0
- package/src/index.ts +8 -1
- package/src/render/DatasetManager.ts +10 -0
- package/src/render/FeatureStyleManager.ts +7 -5
- package/src/render/GeometryBuilder.ts +239 -6
- package/src/render/Picker.ts +21 -5
- package/src/render/S3MViewer.ts +255 -11
- package/src/render/SplatMaterial.ts +433 -0
- package/src/render/SplatSorter.ts +59 -0
- package/src/render/StyleMaterial.ts +339 -69
- package/src/render/TextureFactory.ts +39 -5
- package/src/render/lightingPreset.ts +76 -0
- package/src/render/materialAlpha.ts +75 -0
- package/src/tileset/S3MTileset.ts +78 -1
- package/src/vendor/browser-node-stub.js +10 -0
- package/dist/browser/assets/s3mWorker-BjPOUgjg.js +0 -1
package/src/render/S3MViewer.ts
CHANGED
|
@@ -24,8 +24,12 @@ import * as THREE from 'three';
|
|
|
24
24
|
import { S3MTileset, usableGeoBounds } from '../tileset/S3MTileset.js';
|
|
25
25
|
import type { BuildTileFn, TileNode } from '../tileset/S3MTileset.js';
|
|
26
26
|
import { authFetch, type AuthenticateFn } from '../core/AuthFetch.js';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
27
|
+
import { sortSplatMesh } from './SplatSorter.js';
|
|
28
|
+
import { splatCenterBounds } from './SplatMaterial.js';
|
|
29
|
+
import { setSplatViewport } from './GeometryBuilder.js';
|
|
30
|
+
import { setMaxAnisotropy, setAnisotropyCap, setCompressedTexturesSupported, setTextureOffThreadDecode, setMaxTextureSize as setTextureSizeCap, setSrgbTextureDecode } from './TextureFactory.js';
|
|
31
|
+
import { getMaterialQuality, setMaterialQuality, setColorPipeline } from './StyleMaterial.js';
|
|
32
|
+
import { resolveLightingPreset, type LightingPreset } from './lightingPreset.js';
|
|
29
33
|
import { decodeTextureWithWorker } from '../worker/S3MWorkerClient.js';
|
|
30
34
|
import {
|
|
31
35
|
STYLE,
|
|
@@ -37,6 +41,7 @@ import {
|
|
|
37
41
|
createOutlineMaterial,
|
|
38
42
|
resolveEdgeModes,
|
|
39
43
|
sameEdgeModes,
|
|
44
|
+
restoreBaseBlendState,
|
|
40
45
|
} from './StyleMaterial.js';
|
|
41
46
|
import type {
|
|
42
47
|
OutlineMaterial,
|
|
@@ -51,6 +56,7 @@ import type {
|
|
|
51
56
|
MaterialQuality,
|
|
52
57
|
} from './StyleMaterial.js';
|
|
53
58
|
import { applyPointCloudStyle, isOwnedPointCloudMaterial } from './PointCloudMaterial.js';
|
|
59
|
+
import type { DegradationKind, UnsupportedPolicy } from '../core/degradations.js';
|
|
54
60
|
import { Picker } from './Picker.js';
|
|
55
61
|
import { GpuEdgePass } from './GpuEdgePass.js';
|
|
56
62
|
import { Animator } from '../anim/Animator.js';
|
|
@@ -209,8 +215,21 @@ export interface LightOptions {
|
|
|
209
215
|
}
|
|
210
216
|
|
|
211
217
|
export interface S3MViewerOptions {
|
|
218
|
+
/**
|
|
219
|
+
* 光照预设:
|
|
220
|
+
* - `'default'`(默认):现有 PBR 打光,亮部线性总量 ≈ 2.55;
|
|
221
|
+
* - `'supermap'`:对齐 iClient3D 观感(暗部 0.46 / 亮部 2.44),远处不发灰、饱和度更高。
|
|
222
|
+
* 只改光源,不动材质与色调映射;`light` 里的显式字段仍然优先生效。
|
|
223
|
+
*/
|
|
224
|
+
lightingPreset?: LightingPreset;
|
|
212
225
|
/** 光照参数配置(强度、颜色、朝向模式、太阳方位角/仰角等) */
|
|
213
226
|
light?: LightOptions;
|
|
227
|
+
/**
|
|
228
|
+
* 三角面材质深度偏移(共面摩尔纹 / 重合面归属的人工定序)。
|
|
229
|
+
* 例:factor -1 / units -2 把配置材质的片元深度略微拉近。
|
|
230
|
+
* 这是 mesh 粒度开关,见 BuildTileOptions.polygonOffset 的说明。
|
|
231
|
+
*/
|
|
232
|
+
polygonOffset?: { factor: number; units: number };
|
|
214
233
|
/**
|
|
215
234
|
* 色调映射模式(默认 THREE.NoToneMapping,避免 ACESFilmic 压暗中暗部及导致底图与建筑色彩失真;
|
|
216
235
|
* 可传入 THREE.ACESFilmicToneMapping、THREE.LinearToneMapping、THREE.ReinhardToneMapping 等)
|
|
@@ -232,6 +251,11 @@ export interface S3MViewerOptions {
|
|
|
232
251
|
maxTiles?: number;
|
|
233
252
|
/** 每个数据集并发请求数 */
|
|
234
253
|
maxConcurrent?: number;
|
|
254
|
+
/**
|
|
255
|
+
* 图层级默认的降级处置策略(默认 `'warn'`),数据集可用 `unsupported` 单独覆盖。
|
|
256
|
+
* 见 `S3MTilesetOptions.unsupported`。
|
|
257
|
+
*/
|
|
258
|
+
unsupported?: UnsupportedPolicy;
|
|
235
259
|
/** 是否用 Web Worker 解析经典 .s3mb 瓦片(默认开,崩溃自动降级主线程),见 S3MTilesetOptions.useWorker */
|
|
236
260
|
useWorker?: boolean;
|
|
237
261
|
/** LOD 切换范围缩放系数(默认 1.0),> 1 更倾向保留粗模,< 1 提前换细模 */
|
|
@@ -395,6 +419,8 @@ const STYLE_SCALAR_KEYS = [
|
|
|
395
419
|
'texMix',
|
|
396
420
|
'roofThreshold',
|
|
397
421
|
'ambientGain',
|
|
422
|
+
'metalness',
|
|
423
|
+
'roughness',
|
|
398
424
|
'edgeWidth',
|
|
399
425
|
'edgeOpacity',
|
|
400
426
|
'edgeOffset',
|
|
@@ -534,6 +560,9 @@ export class S3MViewer {
|
|
|
534
560
|
private readonly _nativeEdgeLines = new Set<THREE.LineSegments>();
|
|
535
561
|
/** 点云对象集合:透明度动画时只刷它们,避免每帧遍历全部 pickables */
|
|
536
562
|
private readonly _pointClouds = new Set<THREE.Points>();
|
|
563
|
+
/** 泼溅排序状态:相机不动且没有新瓦片时,排序结果可以直接复用 */
|
|
564
|
+
private readonly _lastSplatSortView = new THREE.Matrix4();
|
|
565
|
+
private _splatsSorted = false;
|
|
537
566
|
/** 当前材质是否处于 transparent 状态,用来避免逐帧重复切换 program */
|
|
538
567
|
private _opacityTransparent = false;
|
|
539
568
|
|
|
@@ -560,6 +589,8 @@ export class S3MViewer {
|
|
|
560
589
|
private _hemiLight!: THREE.HemisphereLight;
|
|
561
590
|
private _keyLight!: THREE.DirectionalLight;
|
|
562
591
|
private _fillLight!: THREE.DirectionalLight;
|
|
592
|
+
/** 环境贴图的 PMREM 渲染目标(setEnvironment 建的,unmount 时释放) */
|
|
593
|
+
private _envRT: THREE.WebGLRenderTarget | null = null;
|
|
563
594
|
private readonly _lightOptions: Required<Omit<LightOptions, 'mainIntensity' | 'mainColor'>> = {
|
|
564
595
|
ambientIntensity: 0.4,
|
|
565
596
|
ambientColor: 0xffffff,
|
|
@@ -583,6 +614,8 @@ export class S3MViewer {
|
|
|
583
614
|
/** 鉴权适配(全局默认),构造时注入 */
|
|
584
615
|
private readonly _authenticate: AuthenticateFn | undefined;
|
|
585
616
|
private _lodRangeScale = 1.0;
|
|
617
|
+
/** 图层级降级策略,数据集可覆盖,见 `S3MViewerOptions.unsupported` */
|
|
618
|
+
private readonly _unsupported: UnsupportedPolicy;
|
|
586
619
|
/** LOD 判定屏幕高度的 DPR 上限。Infinity = 不截断(默认,行为同旧版) */
|
|
587
620
|
private _lodPixelRatioCap = Infinity;
|
|
588
621
|
|
|
@@ -599,9 +632,14 @@ export class S3MViewer {
|
|
|
599
632
|
this.options = options;
|
|
600
633
|
this.pickable = options.pickable !== false;
|
|
601
634
|
this._lodRangeScale = options.lodRangeScale ?? 1.0;
|
|
635
|
+
this._unsupported = options.unsupported ?? 'warn';
|
|
602
636
|
this._lodPixelRatioCap = options.lodPixelRatioCap ?? Infinity;
|
|
603
637
|
setTextureSizeCap(options.maxTextureSize ?? Infinity);
|
|
604
638
|
setMaterialQuality(options.materialQuality ?? 'pbr');
|
|
639
|
+
if (options.lightingPreset && options.lightingPreset !== 'default') {
|
|
640
|
+
Object.assign(this._lightOptions, resolveLightingPreset(options.lightingPreset));
|
|
641
|
+
this._applyColorPipeline(options.lightingPreset);
|
|
642
|
+
}
|
|
605
643
|
if (options.light) {
|
|
606
644
|
Object.assign(this._lightOptions, options.light);
|
|
607
645
|
}
|
|
@@ -780,6 +818,8 @@ export class S3MViewer {
|
|
|
780
818
|
this.groundManager.dispose();
|
|
781
819
|
this.picker?.dispose();
|
|
782
820
|
this.picker = null;
|
|
821
|
+
this._envRT?.dispose();
|
|
822
|
+
this._envRT = null;
|
|
783
823
|
this.outlineMaterial.dispose();
|
|
784
824
|
this._gpuEdge?.dispose();
|
|
785
825
|
this._gpuEdge = null;
|
|
@@ -843,6 +883,34 @@ export class S3MViewer {
|
|
|
843
883
|
}
|
|
844
884
|
}
|
|
845
885
|
|
|
886
|
+
/**
|
|
887
|
+
* 运行时切换光照预设:覆盖四个强度,颜色/朝向保持不变。
|
|
888
|
+
* 之后再调 `setLight({...})` 仍然可以逐项微调。
|
|
889
|
+
*/
|
|
890
|
+
/**
|
|
891
|
+
* supermap 完整版 = 光照强度 + 颜色管线一起切:
|
|
892
|
+
* direct 管线下纹理不做 sRGB 解码、片元按官方常数 Lambert 直乘(见 lightingPreset.ts)。
|
|
893
|
+
* 两个都是模块级全局态,只影响之后创建的材质 / 纹理 —— 已加载瓦片要重载才换。
|
|
894
|
+
*/
|
|
895
|
+
private _applyColorPipeline(preset: LightingPreset): void {
|
|
896
|
+
const direct = preset === 'supermap';
|
|
897
|
+
setColorPipeline(direct ? 'direct' : 'linear');
|
|
898
|
+
setSrgbTextureDecode(!direct);
|
|
899
|
+
// 官方不开各向异性:远处直接采高 mip 平均成纯色("灰色滤镜"的一部分)。
|
|
900
|
+
// 我们默认 Infinity(倾斜摄影立面清晰度优先),supermap 模式必须收回。
|
|
901
|
+
setAnisotropyCap(direct ? 1 : Infinity);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
setLightingPreset(preset: LightingPreset): void {
|
|
905
|
+
Object.assign(this._lightOptions, resolveLightingPreset(preset));
|
|
906
|
+
this._applyColorPipeline(preset);
|
|
907
|
+
this.options.lightingPreset = preset;
|
|
908
|
+
this._applyLightOptions();
|
|
909
|
+
this._updateLights();
|
|
910
|
+
this._needsRepaint = true;
|
|
911
|
+
this.triggerRepaint();
|
|
912
|
+
}
|
|
913
|
+
|
|
846
914
|
/**
|
|
847
915
|
* 动态设置光照配置
|
|
848
916
|
*/
|
|
@@ -909,22 +977,31 @@ export class S3MViewer {
|
|
|
909
977
|
};
|
|
910
978
|
place(this._keyLight, this._lightOptions.azimuth, this._lightOptions.altitude);
|
|
911
979
|
place(this._fillLight, this._lightOptions.azimuth + 65, Math.max(10, this._lightOptions.altitude - 20));
|
|
980
|
+
// direct 管线的官方 Lambert 用主光方向(共享 uniform,所有材质同步生效)
|
|
981
|
+
if (this.uniforms?.uSunDirection && this._keyLight) {
|
|
982
|
+
(this.uniforms.uSunDirection.value as THREE.Vector3).copy(this._keyLight.position).normalize();
|
|
983
|
+
}
|
|
912
984
|
}
|
|
913
985
|
|
|
914
986
|
/* ---------------------------------------------------------------- 数据集 */
|
|
915
987
|
|
|
916
988
|
private _normalizeDatasets(options: S3MViewerOptions): S3MDatasetOptions[] {
|
|
917
|
-
if (Array.isArray(options.datasets))
|
|
989
|
+
if (Array.isArray(options.datasets))
|
|
990
|
+
return options.datasets
|
|
991
|
+
.filter((d) => !!d?.configUrl)
|
|
992
|
+
.map((d) => ({ ...d, polygonOffset: d.polygonOffset ?? options.polygonOffset }));
|
|
918
993
|
if (options.configUrl)
|
|
919
|
-
return [{ id: 'default', name: '默认数据集', configUrl: options.configUrl, useWorker: options.useWorker }];
|
|
994
|
+
return [{ id: 'default', name: '默认数据集', configUrl: options.configUrl, useWorker: options.useWorker, polygonOffset: options.polygonOffset }];
|
|
920
995
|
return [];
|
|
921
996
|
}
|
|
922
997
|
|
|
923
998
|
/** 追加一个数据集。图层 addLayer 之前调用会抛错。 */
|
|
924
999
|
async addDataset(ds: S3MDatasetOptions): Promise<S3MDatasetEntry> {
|
|
925
1000
|
if (!this.scene) throw new Error('[s3m] 图层尚未 addLayer,不能添加数据集');
|
|
1001
|
+
// 图层级默认值:数据集自己指定了就用自己的,见 `S3MViewerOptions.unsupported`
|
|
1002
|
+
const merged: S3MDatasetOptions = { ...ds, unsupported: ds.unsupported ?? this._unsupported };
|
|
926
1003
|
const entry = await this._datasetManager.addDataset(
|
|
927
|
-
|
|
1004
|
+
merged,
|
|
928
1005
|
this.scene,
|
|
929
1006
|
this.geo,
|
|
930
1007
|
this.uniforms,
|
|
@@ -1851,6 +1928,8 @@ export class S3MViewer {
|
|
|
1851
1928
|
const mesh = child as THREE.Mesh;
|
|
1852
1929
|
if (!mesh.isMesh && !isPoints) continue;
|
|
1853
1930
|
if (isPoints) this._pointClouds.add(child as THREE.Points);
|
|
1931
|
+
// 新瓦片里的泼溅实例还没按当前视角排过,下一帧要重排
|
|
1932
|
+
if (isPoints) this._splatsSorted = false;
|
|
1854
1933
|
const ud = mesh.userData as MeshUserData;
|
|
1855
1934
|
ud.pickId = this._allocPickId();
|
|
1856
1935
|
ud.datasetId = entry.id;
|
|
@@ -1882,9 +1961,11 @@ export class S3MViewer {
|
|
|
1882
1961
|
if (mat) {
|
|
1883
1962
|
const o = clampOpacity(this.style.opacity);
|
|
1884
1963
|
// 不写 mat.opacity:全局透明度走共享 uOpacity,材质自身 opacity 保留模型自带 alpha
|
|
1885
|
-
|
|
1964
|
+
// 混合开关不能写成 `o < 0.999` 一刀切:材质自身可能是 blend / 顶点 alpha
|
|
1965
|
+
// 透明(s3mOpts.transparent,如 SMID 4421),注册时就被这句踩掉,
|
|
1966
|
+
// 表现为「刚进去没透明度,切换材质质感重建后才恢复」。
|
|
1886
1967
|
const keepDepth = this.style.depthWrite !== false;
|
|
1887
|
-
mat
|
|
1968
|
+
restoreBaseBlendState(mat, o < 0.999, keepDepth);
|
|
1888
1969
|
// shading 走共享 uniform(uFlatShading),不写 mat.flatShading / needsUpdate——
|
|
1889
1970
|
// transparent / depthWrite 也不是 program 决定项,整套样式继承零重编译
|
|
1890
1971
|
if (mat.userData.uFlatShading) {
|
|
@@ -1940,9 +2021,10 @@ export class S3MViewer {
|
|
|
1940
2021
|
(mesh.userData as MeshUserData).tileFadeKey = undefined;
|
|
1941
2022
|
this.animator.unregister(key);
|
|
1942
2023
|
// 淡入期间强制开了 transparent + depthWrite,结束时不能自己拍板关掉——
|
|
1943
|
-
//
|
|
2024
|
+
// 先恢复「材质自身语义 || 图层透明度」的基础状态(材质可能是 blend /
|
|
2025
|
+
// 顶点 alpha 透明,如 SMID 4421),再让单体淡化流程有需要时重新打开。
|
|
2026
|
+
restoreBaseBlendState(mat, this._opacityTransparent, this.style.depthWrite !== false);
|
|
1944
2027
|
this._featureStyleManager.applyOverridesToMesh(mesh);
|
|
1945
|
-
mat.depthWrite = this.style.depthWrite !== false;
|
|
1946
2028
|
this._requestRepaint();
|
|
1947
2029
|
},
|
|
1948
2030
|
});
|
|
@@ -2146,6 +2228,52 @@ export class S3MViewer {
|
|
|
2146
2228
|
return { ...this._styleTarget, modeLabel: STYLE_LABELS[this._styleTarget.mode] };
|
|
2147
2229
|
}
|
|
2148
2230
|
|
|
2231
|
+
/**
|
|
2232
|
+
* 挂 / 摘环境贴图。
|
|
2233
|
+
*
|
|
2234
|
+
* 金属度(`setStyle({ metalness })`)**必须**有环境图才看得出质感:
|
|
2235
|
+
* PBR 的金属几乎没有漫反射,画面全靠 `scene.environment` 里的环境反射,
|
|
2236
|
+
* 没有它时 metalness 一高就整片发黑,只剩两盏平行灯打成的小高光点。
|
|
2237
|
+
*
|
|
2238
|
+
* @param source 等距柱状图(.hdr / .jpg / .png,2:1)地址,或已加载的 `THREE.Texture`;
|
|
2239
|
+
* `null` = 关掉环境图
|
|
2240
|
+
* @param intensity 环境光强度(`scene.environmentIntensity`,默认 1)
|
|
2241
|
+
*
|
|
2242
|
+
* 传入 URL 时纹理由内部加载并转成 PMREM(预滤波 mipmap);传 Texture 时不会接管它的生命周期。
|
|
2243
|
+
*/
|
|
2244
|
+
async setEnvironment(
|
|
2245
|
+
source: string | THREE.Texture | null,
|
|
2246
|
+
intensity = 1
|
|
2247
|
+
): Promise<void> {
|
|
2248
|
+
if (!this.renderer || !this.scene) return;
|
|
2249
|
+
this.scene.environmentIntensity = Math.max(0, intensity);
|
|
2250
|
+
|
|
2251
|
+
if (source == null) {
|
|
2252
|
+
this.scene.environment = null;
|
|
2253
|
+
this._envRT?.dispose();
|
|
2254
|
+
this._envRT = null;
|
|
2255
|
+
this._requestRepaint();
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
const fromUrl = typeof source === 'string';
|
|
2260
|
+
const tex = fromUrl ? await new THREE.TextureLoader().loadAsync(source) : source;
|
|
2261
|
+
// PMREM 只认等距柱状图;普通贴图也按 2:1 全景处理
|
|
2262
|
+
tex.mapping = THREE.EquirectangularReflectionMapping;
|
|
2263
|
+
|
|
2264
|
+
const pmrem = new THREE.PMREMGenerator(this.renderer);
|
|
2265
|
+
pmrem.compileEquirectangularShader();
|
|
2266
|
+
const rt = pmrem.fromEquirectangular(tex);
|
|
2267
|
+
pmrem.dispose();
|
|
2268
|
+
if (fromUrl) tex.dispose();
|
|
2269
|
+
|
|
2270
|
+
this.scene.environment = rt.texture;
|
|
2271
|
+
// 上一张(同一个 viewer 反复换)要释放,否则显存里会一直堆
|
|
2272
|
+
this._envRT?.dispose();
|
|
2273
|
+
this._envRT = rt;
|
|
2274
|
+
this._requestRepaint();
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2149
2277
|
/** 把某个样式键的取值转成动画通道能吃的形状(标量 or 线性 RGB 三元组) */
|
|
2150
2278
|
private _animTargetOf(key: string, patch: Partial<S3MStyle>): number | number[] {
|
|
2151
2279
|
const value = (patch as Record<string, unknown>)[key];
|
|
@@ -2327,6 +2455,9 @@ export class S3MViewer {
|
|
|
2327
2455
|
if (u.uAmbientGain && s.ambientGain != null) {
|
|
2328
2456
|
u.uAmbientGain.value = s.ambientGain;
|
|
2329
2457
|
}
|
|
2458
|
+
// 金属度 / 粗糙度:负值 = 不覆盖,保持材质(数据)自身的值
|
|
2459
|
+
if (u.uMetalness) u.uMetalness.value = s.metalness ?? -1;
|
|
2460
|
+
if (u.uRoughness) u.uRoughness.value = s.roughness ?? -1;
|
|
2330
2461
|
if (u.uOpacity && s.opacity != null) {
|
|
2331
2462
|
u.uOpacity.value = clampOpacity(s.opacity);
|
|
2332
2463
|
}
|
|
@@ -2368,8 +2499,9 @@ export class S3MViewer {
|
|
|
2368
2499
|
// 有单体淡化 / 正在淡入的材质,transparent 由各自的流程管,这里不要抢
|
|
2369
2500
|
if (m.userData.uBatchTexActive?.value) continue;
|
|
2370
2501
|
if ((m.userData.uTileFade?.value ?? 1) < 1) continue;
|
|
2371
|
-
|
|
2372
|
-
|
|
2502
|
+
// 材质自身可能是 blend / 顶点 alpha 透明(s3mOpts.transparent),
|
|
2503
|
+
// 关全局混合时不能把它一刀切成不透明
|
|
2504
|
+
restoreBaseBlendState(m, transparent, keepDepth);
|
|
2373
2505
|
}
|
|
2374
2506
|
}
|
|
2375
2507
|
|
|
@@ -3059,6 +3191,9 @@ export class S3MViewer {
|
|
|
3059
3191
|
ray?: THREE.Ray
|
|
3060
3192
|
): PickResult | null {
|
|
3061
3193
|
const ud = ref.mesh.userData as MeshUserData;
|
|
3194
|
+
// 泼溅走独立分支:它既没有 instanceMatrix,也没有 geoRef.pickInfo 里的单体表,
|
|
3195
|
+
// 走下面那段会在 `_instanceFeature()` 拿到 null 直接返回 —— 表现是「点得中但拿不到结果」。
|
|
3196
|
+
if (ud.isGaussianSplat) return this._buildSplatPickResult(hit, ref, exactPoint, ray);
|
|
3062
3197
|
let smid: number | null = null;
|
|
3063
3198
|
let batchId = hit.batchId;
|
|
3064
3199
|
let instanceIndex = hit.instanceIndex;
|
|
@@ -3206,6 +3341,85 @@ export class S3MViewer {
|
|
|
3206
3341
|
};
|
|
3207
3342
|
}
|
|
3208
3343
|
|
|
3344
|
+
/**
|
|
3345
|
+
* 泼溅的拾取结果。
|
|
3346
|
+
*
|
|
3347
|
+
* 整块泼溅瓦片当一个单体(没有单体级 smid 可报),命中点由 `splatRaycast` 给出 ——
|
|
3348
|
+
* 它按每个高斯的 3σ 球求交,比拾取那一趟为了命中率摊开的 4px 方块准得多。
|
|
3349
|
+
* 包围盒必须由 `iCenter` 算:geometry 的 position 只是 [-1,1]² 的四边形角点。
|
|
3350
|
+
*/
|
|
3351
|
+
private _buildSplatPickResult(
|
|
3352
|
+
hit: { objectId: number; batchId: number; instanceIndex?: number },
|
|
3353
|
+
ref: PickableRef,
|
|
3354
|
+
exactPoint?: THREE.Vector3,
|
|
3355
|
+
ray?: THREE.Ray
|
|
3356
|
+
): PickResult | null {
|
|
3357
|
+
const anchor = this.sceneAnchor;
|
|
3358
|
+
if (!anchor) return null;
|
|
3359
|
+
const mesh = ref.mesh;
|
|
3360
|
+
|
|
3361
|
+
let hitPoint = exactPoint ?? null;
|
|
3362
|
+
if (!hitPoint && ray) hitPoint = this._intersectMeshAtRay(mesh, ray);
|
|
3363
|
+
|
|
3364
|
+
const local = splatCenterBounds(mesh.geometry);
|
|
3365
|
+
const box: SegmentBox | null = local
|
|
3366
|
+
? {
|
|
3367
|
+
minX: local.minX,
|
|
3368
|
+
minY: local.minY,
|
|
3369
|
+
minZ: local.minZ,
|
|
3370
|
+
maxX: local.maxX,
|
|
3371
|
+
maxY: local.maxY,
|
|
3372
|
+
maxZ: local.maxZ,
|
|
3373
|
+
count: local.count,
|
|
3374
|
+
triangles: 0,
|
|
3375
|
+
}
|
|
3376
|
+
: null;
|
|
3377
|
+
|
|
3378
|
+
const attributes = buildAttributes({
|
|
3379
|
+
smid: null,
|
|
3380
|
+
box,
|
|
3381
|
+
triangles: 0,
|
|
3382
|
+
floorHeight: this.style.floorHeight,
|
|
3383
|
+
});
|
|
3384
|
+
|
|
3385
|
+
const s = anchor.meterInMercator;
|
|
3386
|
+
let point: [number, number, number] | null = null;
|
|
3387
|
+
let lngLat: [number, number] | null = null;
|
|
3388
|
+
let altitude: number | null = null;
|
|
3389
|
+
let centerLngLat: [number, number] | null = null;
|
|
3390
|
+
|
|
3391
|
+
if (hitPoint) {
|
|
3392
|
+
point = [hitPoint.x, hitPoint.y, hitPoint.z];
|
|
3393
|
+
lngLat = this._lngLatFromWorld(hitPoint.x * s + anchor.x, -hitPoint.y * s + anchor.y);
|
|
3394
|
+
altitude = hitPoint.z + (anchor.altitude ?? 0);
|
|
3395
|
+
// 泼溅命中的就是一个高斯,没有「单体中心」可言,用命中点当中心
|
|
3396
|
+
centerLngLat = lngLat;
|
|
3397
|
+
} else if (box) {
|
|
3398
|
+
const v = new THREE.Vector3(
|
|
3399
|
+
(box.minX + box.maxX) / 2,
|
|
3400
|
+
(box.minY + box.maxY) / 2,
|
|
3401
|
+
(box.minZ + box.maxZ) / 2
|
|
3402
|
+
).applyMatrix4(mesh.matrixWorld);
|
|
3403
|
+
centerLngLat = this._lngLatFromWorld(v.x * s + anchor.x, -v.y * s + anchor.y);
|
|
3404
|
+
altitude = v.z + (anchor.altitude ?? 0);
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
return {
|
|
3408
|
+
datasetId: ref.datasetId,
|
|
3409
|
+
datasetName: this.datasets.get(ref.datasetId)?.name ?? ref.datasetId,
|
|
3410
|
+
objectId: hit.objectId,
|
|
3411
|
+
batchId: -1,
|
|
3412
|
+
instanceIndex: undefined,
|
|
3413
|
+
skeletonName: ref.skeletonName,
|
|
3414
|
+
centerLngLat,
|
|
3415
|
+
point,
|
|
3416
|
+
lngLat,
|
|
3417
|
+
altitude,
|
|
3418
|
+
...attributes,
|
|
3419
|
+
mesh,
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3209
3423
|
/** 按 canvas 左上角的 CSS 像素坐标拾取。 */
|
|
3210
3424
|
pick(x: number, y: number): PickResult | null {
|
|
3211
3425
|
if (!this.picker || !this.scene || !this.camera || !this.sceneAnchor) return null;
|
|
@@ -3906,6 +4120,23 @@ export class S3MViewer {
|
|
|
3906
4120
|
this._requestRepaint();
|
|
3907
4121
|
}
|
|
3908
4122
|
|
|
4123
|
+
/**
|
|
4124
|
+
* 泼溅实例按视深 back-to-front 重排:alpha 混合是顺序相关的,
|
|
4125
|
+
* 乱序混合就是满屏彩色噪点。相机没动且没有新瓦片时整个跳过;
|
|
4126
|
+
* 单个瓦片顺序本来就对时 sortSplatMesh 内部也会跳过重写。
|
|
4127
|
+
*/
|
|
4128
|
+
private _sortSplats(): void {
|
|
4129
|
+
const cam = this.camera;
|
|
4130
|
+
if (!cam) return;
|
|
4131
|
+
if (this._splatsSorted && this._lastSplatSortView.equals(cam.matrixWorldInverse)) return;
|
|
4132
|
+
for (const pc of this._pointClouds) {
|
|
4133
|
+
if (!(pc.userData as { isGaussianSplat?: boolean }).isGaussianSplat) continue;
|
|
4134
|
+
sortSplatMesh(pc as unknown as THREE.Mesh, cam.matrixWorldInverse);
|
|
4135
|
+
}
|
|
4136
|
+
this._lastSplatSortView.copy(cam.matrixWorldInverse);
|
|
4137
|
+
this._splatsSorted = true;
|
|
4138
|
+
}
|
|
4139
|
+
|
|
3909
4140
|
/**
|
|
3910
4141
|
* 渲染一帧。所有宿主相关量都由调用方喂进来,viewer 不碰任何地图 API:
|
|
3911
4142
|
*
|
|
@@ -3997,12 +4228,15 @@ export class S3MViewer {
|
|
|
3997
4228
|
|
|
3998
4229
|
// 等宽棱线按设备像素撑开,分辨率每帧同步一次
|
|
3999
4230
|
this._updateLineResolution(canvas);
|
|
4231
|
+
// 泼溅的最小像素半径 / 抗锯齿低通同样要把 ndc 换算成像素
|
|
4232
|
+
setSplatViewport(canvas.width, canvas.height);
|
|
4000
4233
|
|
|
4001
4234
|
this.renderer.setViewport(0, 0, canvas.width, canvas.height);
|
|
4002
4235
|
this.renderer.resetState();
|
|
4003
4236
|
// 完全透明时什么都不画:透明物体照样写深度,画了会在底图上挖出一个洞。
|
|
4004
4237
|
// 这里跳过的是「本层的绘制」,宿主自己的清屏 / 底图照旧。
|
|
4005
4238
|
if ((this.uniforms.uOpacity?.value ?? 1) > OPACITY_EPS) {
|
|
4239
|
+
this._sortSplats();
|
|
4006
4240
|
this.renderer.render(this.scene, this.camera);
|
|
4007
4241
|
// GPU 描边必须画在主场景之后:它的全屏 quad 参与宿主深度测试,
|
|
4008
4242
|
// 依赖本帧已经写好的深度(被地形挡住的地方不该描边)
|
|
@@ -4053,7 +4287,9 @@ export class S3MViewer {
|
|
|
4053
4287
|
programs: 0,
|
|
4054
4288
|
textures: 0,
|
|
4055
4289
|
geometries: 0,
|
|
4290
|
+
degradations: [],
|
|
4056
4291
|
};
|
|
4292
|
+
let degradations: DegradationKind[] | null = null;
|
|
4057
4293
|
for (const entry of this.datasets.values()) {
|
|
4058
4294
|
const s = entry.tileset?.stats;
|
|
4059
4295
|
if (!s) continue;
|
|
@@ -4063,7 +4299,15 @@ export class S3MViewer {
|
|
|
4063
4299
|
total.meshes += s.meshes;
|
|
4064
4300
|
total.vertices += s.vertices;
|
|
4065
4301
|
total.triangles += s.triangles;
|
|
4302
|
+
// 降级项跨数据集去重:同一类只报一次给 UI
|
|
4303
|
+
if (s.degradations?.length) {
|
|
4304
|
+
degradations ??= [];
|
|
4305
|
+
for (const d of s.degradations) {
|
|
4306
|
+
if (!degradations.includes(d)) degradations.push(d);
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4066
4309
|
}
|
|
4310
|
+
if (degradations) total.degradations = degradations;
|
|
4067
4311
|
// GPU 侧预算观测:draw call / program / 显存驻留。没有渲染器(未 mount /
|
|
4068
4312
|
// 纯解析场景)时保持 0,不抛错。
|
|
4069
4313
|
const info = this.renderer?.info;
|