s3m-three 0.4.17 → 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/AttributeQuery.d.ts +13 -0
- package/dist/core/AttributeQuery.d.ts.map +1 -1
- package/dist/core/AttributeQuery.js +20 -3
- package/dist/core/AttributeQuery.js.map +1 -1
- package/dist/core/AuthFetch.d.ts +52 -0
- package/dist/core/AuthFetch.d.ts.map +1 -0
- package/dist/core/AuthFetch.js +44 -0
- package/dist/core/AuthFetch.js.map +1 -0
- 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 +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +18 -1
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +4 -1
- 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/HostRenderer.d.ts +105 -0
- package/dist/render/HostRenderer.d.ts.map +1 -0
- package/dist/render/HostRenderer.js +170 -0
- package/dist/render/HostRenderer.js.map +1 -0
- 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 +79 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +235 -16
- 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 +50 -0
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +60 -5
- 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/AttributeQuery.ts +26 -3
- package/src/core/AuthFetch.ts +82 -0
- 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 +12 -1
- package/src/render/DatasetManager.ts +19 -1
- package/src/render/FeatureStyleManager.ts +7 -5
- package/src/render/GeometryBuilder.ts +239 -6
- package/src/render/HostRenderer.ts +222 -0
- package/src/render/Picker.ts +21 -5
- package/src/render/S3MViewer.ts +277 -15
- 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 +91 -5
- package/src/vendor/browser-node-stub.js +10 -0
- package/dist/browser/assets/s3mWorker-BjPOUgjg.js +0 -1
package/src/render/S3MViewer.ts
CHANGED
|
@@ -23,8 +23,13 @@
|
|
|
23
23
|
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
|
-
import {
|
|
27
|
-
import {
|
|
26
|
+
import { authFetch, type AuthenticateFn } from '../core/AuthFetch.js';
|
|
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';
|
|
28
33
|
import { decodeTextureWithWorker } from '../worker/S3MWorkerClient.js';
|
|
29
34
|
import {
|
|
30
35
|
STYLE,
|
|
@@ -36,6 +41,7 @@ import {
|
|
|
36
41
|
createOutlineMaterial,
|
|
37
42
|
resolveEdgeModes,
|
|
38
43
|
sameEdgeModes,
|
|
44
|
+
restoreBaseBlendState,
|
|
39
45
|
} from './StyleMaterial.js';
|
|
40
46
|
import type {
|
|
41
47
|
OutlineMaterial,
|
|
@@ -50,6 +56,7 @@ import type {
|
|
|
50
56
|
MaterialQuality,
|
|
51
57
|
} from './StyleMaterial.js';
|
|
52
58
|
import { applyPointCloudStyle, isOwnedPointCloudMaterial } from './PointCloudMaterial.js';
|
|
59
|
+
import type { DegradationKind, UnsupportedPolicy } from '../core/degradations.js';
|
|
53
60
|
import { Picker } from './Picker.js';
|
|
54
61
|
import { GpuEdgePass } from './GpuEdgePass.js';
|
|
55
62
|
import { Animator } from '../anim/Animator.js';
|
|
@@ -208,8 +215,21 @@ export interface LightOptions {
|
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
export interface S3MViewerOptions {
|
|
218
|
+
/**
|
|
219
|
+
* 光照预设:
|
|
220
|
+
* - `'default'`(默认):现有 PBR 打光,亮部线性总量 ≈ 2.55;
|
|
221
|
+
* - `'supermap'`:对齐 iClient3D 观感(暗部 0.46 / 亮部 2.44),远处不发灰、饱和度更高。
|
|
222
|
+
* 只改光源,不动材质与色调映射;`light` 里的显式字段仍然优先生效。
|
|
223
|
+
*/
|
|
224
|
+
lightingPreset?: LightingPreset;
|
|
211
225
|
/** 光照参数配置(强度、颜色、朝向模式、太阳方位角/仰角等) */
|
|
212
226
|
light?: LightOptions;
|
|
227
|
+
/**
|
|
228
|
+
* 三角面材质深度偏移(共面摩尔纹 / 重合面归属的人工定序)。
|
|
229
|
+
* 例:factor -1 / units -2 把配置材质的片元深度略微拉近。
|
|
230
|
+
* 这是 mesh 粒度开关,见 BuildTileOptions.polygonOffset 的说明。
|
|
231
|
+
*/
|
|
232
|
+
polygonOffset?: { factor: number; units: number };
|
|
213
233
|
/**
|
|
214
234
|
* 色调映射模式(默认 THREE.NoToneMapping,避免 ACESFilmic 压暗中暗部及导致底图与建筑色彩失真;
|
|
215
235
|
* 可传入 THREE.ACESFilmicToneMapping、THREE.LinearToneMapping、THREE.ReinhardToneMapping 等)
|
|
@@ -231,6 +251,11 @@ export interface S3MViewerOptions {
|
|
|
231
251
|
maxTiles?: number;
|
|
232
252
|
/** 每个数据集并发请求数 */
|
|
233
253
|
maxConcurrent?: number;
|
|
254
|
+
/**
|
|
255
|
+
* 图层级默认的降级处置策略(默认 `'warn'`),数据集可用 `unsupported` 单独覆盖。
|
|
256
|
+
* 见 `S3MTilesetOptions.unsupported`。
|
|
257
|
+
*/
|
|
258
|
+
unsupported?: UnsupportedPolicy;
|
|
234
259
|
/** 是否用 Web Worker 解析经典 .s3mb 瓦片(默认开,崩溃自动降级主线程),见 S3MTilesetOptions.useWorker */
|
|
235
260
|
useWorker?: boolean;
|
|
236
261
|
/** LOD 切换范围缩放系数(默认 1.0),> 1 更倾向保留粗模,< 1 提前换细模 */
|
|
@@ -290,6 +315,13 @@ export interface S3MViewerOptions {
|
|
|
290
315
|
onRequestRepaint?: () => void;
|
|
291
316
|
/** 地图朝向(弧度),用于让灯光跟着视角走。不传则灯光方向固定 */
|
|
292
317
|
getBearing?: () => number;
|
|
318
|
+
/**
|
|
319
|
+
* 鉴权适配函数(全局默认,数据集可用自己的 `authenticate` 覆盖)。
|
|
320
|
+
* 内核主线程发出的全部请求(配置 scp / 瓦片 / 子图集 / 棱线缓存 / 属性查询)
|
|
321
|
+
* 发出前都过一遍,可改写 URL(加 `?token=`)或附加请求头。
|
|
322
|
+
* 不传 = 行为与旧版完全一致。见 `AuthenticateFn`。
|
|
323
|
+
*/
|
|
324
|
+
authenticate?: AuthenticateFn;
|
|
293
325
|
onStats?: (stats: Stats) => void;
|
|
294
326
|
onReady?: (entry: S3MDatasetEntry) => void;
|
|
295
327
|
onPick?: (result: PickResult | null) => void;
|
|
@@ -387,6 +419,8 @@ const STYLE_SCALAR_KEYS = [
|
|
|
387
419
|
'texMix',
|
|
388
420
|
'roofThreshold',
|
|
389
421
|
'ambientGain',
|
|
422
|
+
'metalness',
|
|
423
|
+
'roughness',
|
|
390
424
|
'edgeWidth',
|
|
391
425
|
'edgeOpacity',
|
|
392
426
|
'edgeOffset',
|
|
@@ -526,6 +560,9 @@ export class S3MViewer {
|
|
|
526
560
|
private readonly _nativeEdgeLines = new Set<THREE.LineSegments>();
|
|
527
561
|
/** 点云对象集合:透明度动画时只刷它们,避免每帧遍历全部 pickables */
|
|
528
562
|
private readonly _pointClouds = new Set<THREE.Points>();
|
|
563
|
+
/** 泼溅排序状态:相机不动且没有新瓦片时,排序结果可以直接复用 */
|
|
564
|
+
private readonly _lastSplatSortView = new THREE.Matrix4();
|
|
565
|
+
private _splatsSorted = false;
|
|
529
566
|
/** 当前材质是否处于 transparent 状态,用来避免逐帧重复切换 program */
|
|
530
567
|
private _opacityTransparent = false;
|
|
531
568
|
|
|
@@ -552,6 +589,8 @@ export class S3MViewer {
|
|
|
552
589
|
private _hemiLight!: THREE.HemisphereLight;
|
|
553
590
|
private _keyLight!: THREE.DirectionalLight;
|
|
554
591
|
private _fillLight!: THREE.DirectionalLight;
|
|
592
|
+
/** 环境贴图的 PMREM 渲染目标(setEnvironment 建的,unmount 时释放) */
|
|
593
|
+
private _envRT: THREE.WebGLRenderTarget | null = null;
|
|
555
594
|
private readonly _lightOptions: Required<Omit<LightOptions, 'mainIntensity' | 'mainColor'>> = {
|
|
556
595
|
ambientIntensity: 0.4,
|
|
557
596
|
ambientColor: 0xffffff,
|
|
@@ -572,7 +611,11 @@ export class S3MViewer {
|
|
|
572
611
|
/** 宿主重画信号与地图朝向,构造时注入 */
|
|
573
612
|
private _onRequestRepaint: () => void = () => {};
|
|
574
613
|
private _getBearing: (() => number) | undefined;
|
|
614
|
+
/** 鉴权适配(全局默认),构造时注入 */
|
|
615
|
+
private readonly _authenticate: AuthenticateFn | undefined;
|
|
575
616
|
private _lodRangeScale = 1.0;
|
|
617
|
+
/** 图层级降级策略,数据集可覆盖,见 `S3MViewerOptions.unsupported` */
|
|
618
|
+
private readonly _unsupported: UnsupportedPolicy;
|
|
576
619
|
/** LOD 判定屏幕高度的 DPR 上限。Infinity = 不截断(默认,行为同旧版) */
|
|
577
620
|
private _lodPixelRatioCap = Infinity;
|
|
578
621
|
|
|
@@ -589,9 +632,14 @@ export class S3MViewer {
|
|
|
589
632
|
this.options = options;
|
|
590
633
|
this.pickable = options.pickable !== false;
|
|
591
634
|
this._lodRangeScale = options.lodRangeScale ?? 1.0;
|
|
635
|
+
this._unsupported = options.unsupported ?? 'warn';
|
|
592
636
|
this._lodPixelRatioCap = options.lodPixelRatioCap ?? Infinity;
|
|
593
637
|
setTextureSizeCap(options.maxTextureSize ?? Infinity);
|
|
594
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
|
+
}
|
|
595
643
|
if (options.light) {
|
|
596
644
|
Object.assign(this._lightOptions, options.light);
|
|
597
645
|
}
|
|
@@ -600,6 +648,7 @@ export class S3MViewer {
|
|
|
600
648
|
}
|
|
601
649
|
this._onRequestRepaint = options.onRequestRepaint ?? (() => {});
|
|
602
650
|
this._getBearing = options.getBearing;
|
|
651
|
+
this._authenticate = options.authenticate;
|
|
603
652
|
this.onStats = options.onStats ?? (() => {});
|
|
604
653
|
this.onReady = options.onReady ?? (() => {});
|
|
605
654
|
this.onPick = options.onPick ?? (() => {});
|
|
@@ -670,7 +719,11 @@ export class S3MViewer {
|
|
|
670
719
|
|
|
671
720
|
this._fps.last = performance.now();
|
|
672
721
|
this._datasetQueue = this._normalizeDatasets(options);
|
|
673
|
-
if (options.attributes)
|
|
722
|
+
if (options.attributes)
|
|
723
|
+
this.attributeQuery = new AttributeQuery({
|
|
724
|
+
...options.attributes,
|
|
725
|
+
authenticate: options.attributes.authenticate ?? this._authenticate,
|
|
726
|
+
});
|
|
674
727
|
}
|
|
675
728
|
|
|
676
729
|
/* ---------------------------------------------------------------- 生命周期 */
|
|
@@ -765,6 +818,8 @@ export class S3MViewer {
|
|
|
765
818
|
this.groundManager.dispose();
|
|
766
819
|
this.picker?.dispose();
|
|
767
820
|
this.picker = null;
|
|
821
|
+
this._envRT?.dispose();
|
|
822
|
+
this._envRT = null;
|
|
768
823
|
this.outlineMaterial.dispose();
|
|
769
824
|
this._gpuEdge?.dispose();
|
|
770
825
|
this._gpuEdge = null;
|
|
@@ -828,6 +883,34 @@ export class S3MViewer {
|
|
|
828
883
|
}
|
|
829
884
|
}
|
|
830
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
|
+
|
|
831
914
|
/**
|
|
832
915
|
* 动态设置光照配置
|
|
833
916
|
*/
|
|
@@ -894,22 +977,31 @@ export class S3MViewer {
|
|
|
894
977
|
};
|
|
895
978
|
place(this._keyLight, this._lightOptions.azimuth, this._lightOptions.altitude);
|
|
896
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
|
+
}
|
|
897
984
|
}
|
|
898
985
|
|
|
899
986
|
/* ---------------------------------------------------------------- 数据集 */
|
|
900
987
|
|
|
901
988
|
private _normalizeDatasets(options: S3MViewerOptions): S3MDatasetOptions[] {
|
|
902
|
-
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 }));
|
|
903
993
|
if (options.configUrl)
|
|
904
|
-
return [{ id: 'default', name: '默认数据集', configUrl: options.configUrl, useWorker: options.useWorker }];
|
|
994
|
+
return [{ id: 'default', name: '默认数据集', configUrl: options.configUrl, useWorker: options.useWorker, polygonOffset: options.polygonOffset }];
|
|
905
995
|
return [];
|
|
906
996
|
}
|
|
907
997
|
|
|
908
998
|
/** 追加一个数据集。图层 addLayer 之前调用会抛错。 */
|
|
909
999
|
async addDataset(ds: S3MDatasetOptions): Promise<S3MDatasetEntry> {
|
|
910
1000
|
if (!this.scene) throw new Error('[s3m] 图层尚未 addLayer,不能添加数据集');
|
|
1001
|
+
// 图层级默认值:数据集自己指定了就用自己的,见 `S3MViewerOptions.unsupported`
|
|
1002
|
+
const merged: S3MDatasetOptions = { ...ds, unsupported: ds.unsupported ?? this._unsupported };
|
|
911
1003
|
const entry = await this._datasetManager.addDataset(
|
|
912
|
-
|
|
1004
|
+
merged,
|
|
913
1005
|
this.scene,
|
|
914
1006
|
this.geo,
|
|
915
1007
|
this.uniforms,
|
|
@@ -950,7 +1042,8 @@ export class S3MViewer {
|
|
|
950
1042
|
},
|
|
951
1043
|
},
|
|
952
1044
|
this.options.maxTriangles ?? Infinity,
|
|
953
|
-
this.options.maxImageCache ?? 128
|
|
1045
|
+
this.options.maxImageCache ?? 128,
|
|
1046
|
+
this._authenticate
|
|
954
1047
|
);
|
|
955
1048
|
return entry;
|
|
956
1049
|
}
|
|
@@ -1616,7 +1709,9 @@ export class S3MViewer {
|
|
|
1616
1709
|
/* ---------------------------------------------------------------- 按业务字段着色 */
|
|
1617
1710
|
|
|
1618
1711
|
setAttributeSource(options: AttributeQueryOptions | null): void {
|
|
1619
|
-
this.attributeQuery = options
|
|
1712
|
+
this.attributeQuery = options
|
|
1713
|
+
? new AttributeQuery({ ...options, authenticate: options.authenticate ?? this._authenticate })
|
|
1714
|
+
: null;
|
|
1620
1715
|
}
|
|
1621
1716
|
|
|
1622
1717
|
getLoadedSmids(): number[] {
|
|
@@ -1833,6 +1928,8 @@ export class S3MViewer {
|
|
|
1833
1928
|
const mesh = child as THREE.Mesh;
|
|
1834
1929
|
if (!mesh.isMesh && !isPoints) continue;
|
|
1835
1930
|
if (isPoints) this._pointClouds.add(child as THREE.Points);
|
|
1931
|
+
// 新瓦片里的泼溅实例还没按当前视角排过,下一帧要重排
|
|
1932
|
+
if (isPoints) this._splatsSorted = false;
|
|
1836
1933
|
const ud = mesh.userData as MeshUserData;
|
|
1837
1934
|
ud.pickId = this._allocPickId();
|
|
1838
1935
|
ud.datasetId = entry.id;
|
|
@@ -1864,9 +1961,11 @@ export class S3MViewer {
|
|
|
1864
1961
|
if (mat) {
|
|
1865
1962
|
const o = clampOpacity(this.style.opacity);
|
|
1866
1963
|
// 不写 mat.opacity:全局透明度走共享 uOpacity,材质自身 opacity 保留模型自带 alpha
|
|
1867
|
-
|
|
1964
|
+
// 混合开关不能写成 `o < 0.999` 一刀切:材质自身可能是 blend / 顶点 alpha
|
|
1965
|
+
// 透明(s3mOpts.transparent,如 SMID 4421),注册时就被这句踩掉,
|
|
1966
|
+
// 表现为「刚进去没透明度,切换材质质感重建后才恢复」。
|
|
1868
1967
|
const keepDepth = this.style.depthWrite !== false;
|
|
1869
|
-
mat
|
|
1968
|
+
restoreBaseBlendState(mat, o < 0.999, keepDepth);
|
|
1870
1969
|
// shading 走共享 uniform(uFlatShading),不写 mat.flatShading / needsUpdate——
|
|
1871
1970
|
// transparent / depthWrite 也不是 program 决定项,整套样式继承零重编译
|
|
1872
1971
|
if (mat.userData.uFlatShading) {
|
|
@@ -1922,9 +2021,10 @@ export class S3MViewer {
|
|
|
1922
2021
|
(mesh.userData as MeshUserData).tileFadeKey = undefined;
|
|
1923
2022
|
this.animator.unregister(key);
|
|
1924
2023
|
// 淡入期间强制开了 transparent + depthWrite,结束时不能自己拍板关掉——
|
|
1925
|
-
//
|
|
2024
|
+
// 先恢复「材质自身语义 || 图层透明度」的基础状态(材质可能是 blend /
|
|
2025
|
+
// 顶点 alpha 透明,如 SMID 4421),再让单体淡化流程有需要时重新打开。
|
|
2026
|
+
restoreBaseBlendState(mat, this._opacityTransparent, this.style.depthWrite !== false);
|
|
1926
2027
|
this._featureStyleManager.applyOverridesToMesh(mesh);
|
|
1927
|
-
mat.depthWrite = this.style.depthWrite !== false;
|
|
1928
2028
|
this._requestRepaint();
|
|
1929
2029
|
},
|
|
1930
2030
|
});
|
|
@@ -2128,6 +2228,52 @@ export class S3MViewer {
|
|
|
2128
2228
|
return { ...this._styleTarget, modeLabel: STYLE_LABELS[this._styleTarget.mode] };
|
|
2129
2229
|
}
|
|
2130
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
|
+
|
|
2131
2277
|
/** 把某个样式键的取值转成动画通道能吃的形状(标量 or 线性 RGB 三元组) */
|
|
2132
2278
|
private _animTargetOf(key: string, patch: Partial<S3MStyle>): number | number[] {
|
|
2133
2279
|
const value = (patch as Record<string, unknown>)[key];
|
|
@@ -2309,6 +2455,9 @@ export class S3MViewer {
|
|
|
2309
2455
|
if (u.uAmbientGain && s.ambientGain != null) {
|
|
2310
2456
|
u.uAmbientGain.value = s.ambientGain;
|
|
2311
2457
|
}
|
|
2458
|
+
// 金属度 / 粗糙度:负值 = 不覆盖,保持材质(数据)自身的值
|
|
2459
|
+
if (u.uMetalness) u.uMetalness.value = s.metalness ?? -1;
|
|
2460
|
+
if (u.uRoughness) u.uRoughness.value = s.roughness ?? -1;
|
|
2312
2461
|
if (u.uOpacity && s.opacity != null) {
|
|
2313
2462
|
u.uOpacity.value = clampOpacity(s.opacity);
|
|
2314
2463
|
}
|
|
@@ -2350,8 +2499,9 @@ export class S3MViewer {
|
|
|
2350
2499
|
// 有单体淡化 / 正在淡入的材质,transparent 由各自的流程管,这里不要抢
|
|
2351
2500
|
if (m.userData.uBatchTexActive?.value) continue;
|
|
2352
2501
|
if ((m.userData.uTileFade?.value ?? 1) < 1) continue;
|
|
2353
|
-
|
|
2354
|
-
|
|
2502
|
+
// 材质自身可能是 blend / 顶点 alpha 透明(s3mOpts.transparent),
|
|
2503
|
+
// 关全局混合时不能把它一刀切成不透明
|
|
2504
|
+
restoreBaseBlendState(m, transparent, keepDepth);
|
|
2355
2505
|
}
|
|
2356
2506
|
}
|
|
2357
2507
|
|
|
@@ -2683,7 +2833,7 @@ export class S3MViewer {
|
|
|
2683
2833
|
* @param url 棱线缓存地址(二进制 Float32 序列:x,y,z,x,y,z,… 成对成段)
|
|
2684
2834
|
*/
|
|
2685
2835
|
async loadCreases(url: string): Promise<void> {
|
|
2686
|
-
const resp = await
|
|
2836
|
+
const resp = await authFetch(url, this._authenticate, 'creases');
|
|
2687
2837
|
if (!resp.ok) throw new Error(`[s3m] 棱线缓存加载失败: ${resp.status} ${url}`);
|
|
2688
2838
|
const buf = await resp.arrayBuffer();
|
|
2689
2839
|
const arr = new Float32Array(buf);
|
|
@@ -3041,6 +3191,9 @@ export class S3MViewer {
|
|
|
3041
3191
|
ray?: THREE.Ray
|
|
3042
3192
|
): PickResult | null {
|
|
3043
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);
|
|
3044
3197
|
let smid: number | null = null;
|
|
3045
3198
|
let batchId = hit.batchId;
|
|
3046
3199
|
let instanceIndex = hit.instanceIndex;
|
|
@@ -3188,6 +3341,85 @@ export class S3MViewer {
|
|
|
3188
3341
|
};
|
|
3189
3342
|
}
|
|
3190
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
|
+
|
|
3191
3423
|
/** 按 canvas 左上角的 CSS 像素坐标拾取。 */
|
|
3192
3424
|
pick(x: number, y: number): PickResult | null {
|
|
3193
3425
|
if (!this.picker || !this.scene || !this.camera || !this.sceneAnchor) return null;
|
|
@@ -3888,6 +4120,23 @@ export class S3MViewer {
|
|
|
3888
4120
|
this._requestRepaint();
|
|
3889
4121
|
}
|
|
3890
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
|
+
|
|
3891
4140
|
/**
|
|
3892
4141
|
* 渲染一帧。所有宿主相关量都由调用方喂进来,viewer 不碰任何地图 API:
|
|
3893
4142
|
*
|
|
@@ -3979,12 +4228,15 @@ export class S3MViewer {
|
|
|
3979
4228
|
|
|
3980
4229
|
// 等宽棱线按设备像素撑开,分辨率每帧同步一次
|
|
3981
4230
|
this._updateLineResolution(canvas);
|
|
4231
|
+
// 泼溅的最小像素半径 / 抗锯齿低通同样要把 ndc 换算成像素
|
|
4232
|
+
setSplatViewport(canvas.width, canvas.height);
|
|
3982
4233
|
|
|
3983
4234
|
this.renderer.setViewport(0, 0, canvas.width, canvas.height);
|
|
3984
4235
|
this.renderer.resetState();
|
|
3985
4236
|
// 完全透明时什么都不画:透明物体照样写深度,画了会在底图上挖出一个洞。
|
|
3986
4237
|
// 这里跳过的是「本层的绘制」,宿主自己的清屏 / 底图照旧。
|
|
3987
4238
|
if ((this.uniforms.uOpacity?.value ?? 1) > OPACITY_EPS) {
|
|
4239
|
+
this._sortSplats();
|
|
3988
4240
|
this.renderer.render(this.scene, this.camera);
|
|
3989
4241
|
// GPU 描边必须画在主场景之后:它的全屏 quad 参与宿主深度测试,
|
|
3990
4242
|
// 依赖本帧已经写好的深度(被地形挡住的地方不该描边)
|
|
@@ -4035,7 +4287,9 @@ export class S3MViewer {
|
|
|
4035
4287
|
programs: 0,
|
|
4036
4288
|
textures: 0,
|
|
4037
4289
|
geometries: 0,
|
|
4290
|
+
degradations: [],
|
|
4038
4291
|
};
|
|
4292
|
+
let degradations: DegradationKind[] | null = null;
|
|
4039
4293
|
for (const entry of this.datasets.values()) {
|
|
4040
4294
|
const s = entry.tileset?.stats;
|
|
4041
4295
|
if (!s) continue;
|
|
@@ -4045,7 +4299,15 @@ export class S3MViewer {
|
|
|
4045
4299
|
total.meshes += s.meshes;
|
|
4046
4300
|
total.vertices += s.vertices;
|
|
4047
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
|
+
}
|
|
4048
4309
|
}
|
|
4310
|
+
if (degradations) total.degradations = degradations;
|
|
4049
4311
|
// GPU 侧预算观测:draw call / program / 显存驻留。没有渲染器(未 mount /
|
|
4050
4312
|
// 纯解析场景)时保持 0,不抛错。
|
|
4051
4313
|
const info = this.renderer?.info;
|