s3m-three 0.4.22 → 0.4.24
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/analysis/clipping.d.ts +63 -0
- package/dist/analysis/clipping.d.ts.map +1 -0
- package/dist/analysis/clipping.js +69 -0
- package/dist/analysis/clipping.js.map +1 -0
- package/dist/analysis/depth.d.ts +55 -0
- package/dist/analysis/depth.d.ts.map +1 -0
- package/dist/analysis/depth.js +99 -0
- package/dist/analysis/depth.js.map +1 -0
- package/dist/analysis/index.d.ts +21 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +17 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/lineOfSight.d.ts +60 -0
- package/dist/analysis/lineOfSight.d.ts.map +1 -0
- package/dist/analysis/lineOfSight.js +56 -0
- package/dist/analysis/lineOfSight.js.map +1 -0
- package/dist/analysis/measure.d.ts +56 -0
- package/dist/analysis/measure.d.ts.map +1 -0
- package/dist/analysis/measure.js +95 -0
- package/dist/analysis/measure.js.map +1 -0
- package/dist/analysis/skyline.d.ts +55 -0
- package/dist/analysis/skyline.d.ts.map +1 -0
- package/dist/analysis/skyline.js +78 -0
- package/dist/analysis/skyline.js.map +1 -0
- package/dist/analysis/viewshed.d.ts +48 -0
- package/dist/analysis/viewshed.d.ts.map +1 -0
- package/dist/analysis/viewshed.js +115 -0
- package/dist/analysis/viewshed.js.map +1 -0
- package/dist/browser/s3m-three.iife.ext-three.js +52 -35
- package/dist/browser/s3m-three.iife.js +47 -30
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +13 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +20 -0
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +11 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +2 -1
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +159 -5
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +331 -13
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +10 -1
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +9 -1
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +23 -1
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +32 -8
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/render/analysis/DepthPass.d.ts +78 -0
- package/dist/render/analysis/DepthPass.d.ts.map +1 -0
- package/dist/render/analysis/DepthPass.js +209 -0
- package/dist/render/analysis/DepthPass.js.map +1 -0
- package/dist/render/viewer/LightRig.d.ts +21 -7
- package/dist/render/viewer/LightRig.d.ts.map +1 -1
- package/dist/render/viewer/LightRig.js +11 -13
- package/dist/render/viewer/LightRig.js.map +1 -1
- package/dist/render/viewer/PickService.d.ts +15 -0
- package/dist/render/viewer/PickService.d.ts.map +1 -1
- package/dist/render/viewer/PickService.js +41 -0
- package/dist/render/viewer/PickService.js.map +1 -1
- package/dist/render/viewer/types.d.ts +12 -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 +19 -0
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +16 -0
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/docs/API.md +124 -3
- package/package.json +1 -1
- package/src/analysis/clipping.ts +135 -0
- package/src/analysis/depth.ts +126 -0
- package/src/analysis/index.ts +47 -0
- package/src/analysis/lineOfSight.ts +110 -0
- package/src/analysis/measure.ts +119 -0
- package/src/analysis/skyline.ts +125 -0
- package/src/analysis/viewshed.ts +160 -0
- package/src/index.ts +5 -0
- package/src/render/DatasetManager.ts +21 -0
- package/src/render/GeometryBuilder.ts +13 -1
- package/src/render/S3MViewer.ts +401 -14
- package/src/render/StyleMaterial.ts +16 -2
- package/src/render/TextureFactory.ts +50 -8
- package/src/render/analysis/DepthPass.ts +278 -0
- package/src/render/viewer/LightRig.ts +23 -13
- package/src/render/viewer/PickService.ts +47 -0
- package/src/render/viewer/types.ts +12 -0
- package/src/tileset/S3MTileset.ts +24 -0
|
@@ -31,6 +31,43 @@ export function setMaxAnisotropy(n: number, cap = Infinity): void {
|
|
|
31
31
|
anisotropyCap = Number.isFinite(cap) && cap > 0 ? Math.max(1, cap) : Infinity;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 单次创建的纹理参数。三项都可缺省 —— 缺省时读模块级默认(`setSrgbTextureDecode`
|
|
36
|
+
* / `setMaxTextureSize` / `setAnisotropyCap`),所以老调用方行为逐字不变。
|
|
37
|
+
*
|
|
38
|
+
* 图层级隔离靠这条:`S3MLayer` 把自己的渲染 profile 透传进来,不再共享模块级状态。
|
|
39
|
+
*/
|
|
40
|
+
export interface TextureCreateOptions {
|
|
41
|
+
/** 是否做 sRGB 解码(默认读 `getSrgbTextureDecode()`)。`'direct'` 管线传 false */
|
|
42
|
+
srgbDecode?: boolean;
|
|
43
|
+
/** 长边像素上限(默认读 `setMaxTextureSize` 的值)。图集恒不缩放 */
|
|
44
|
+
maxSize?: number;
|
|
45
|
+
/** 各向异性上限(默认读 `setAnisotropyCap` 的值)。Infinity = 取 GPU 最大值 */
|
|
46
|
+
anisotropyCap?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 把「上限」解析成实际的 anisotropy 值(GPU 最大值与 cap 取小) */
|
|
50
|
+
function resolveAnisotropy(cap: number): number {
|
|
51
|
+
return Number.isFinite(cap) && cap > 0 ? Math.min(maxAnisotropy, Math.max(1, cap)) : maxAnisotropy;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 把各向异性上限**即时应用**到一批已上传的纹理。
|
|
56
|
+
*
|
|
57
|
+
* `anisotropy` 是采样参数,不需要重新解码 —— 改值 + `needsUpdate` 让 three 重传一次
|
|
58
|
+
* 即可,代价远小于重载瓦片。图集纹理(`configureAtlas` 打过 `s3mNoAniso` 标记)
|
|
59
|
+
* 保持 1:它关了 mip、靠子区域 rect 自己算层级,开各向异性会把相邻子区域混进来。
|
|
60
|
+
*/
|
|
61
|
+
export function applyAnisotropyCap(textures: Iterable<THREE.Texture>, cap: number): void {
|
|
62
|
+
const n = resolveAnisotropy(cap);
|
|
63
|
+
for (const tex of textures) {
|
|
64
|
+
if (!tex || (tex.userData as { s3mNoAniso?: boolean })?.s3mNoAniso) continue;
|
|
65
|
+
if (tex.anisotropy === n) continue;
|
|
66
|
+
tex.anisotropy = n;
|
|
67
|
+
tex.needsUpdate = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
34
71
|
/**
|
|
35
72
|
* 单独调各向异性上限(只影响之后创建的纹理)。
|
|
36
73
|
* supermap 预设会设成 1 —— 官方 iClient3D 不开各向异性,斜视角直接采高 mip
|
|
@@ -114,15 +151,15 @@ export function getSrgbTextureDecode(): boolean {
|
|
|
114
151
|
return srgbTextureDecode;
|
|
115
152
|
}
|
|
116
153
|
|
|
117
|
-
function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
154
|
+
function configure<T extends THREE.Texture>(tex: T, srgb = true, opts: TextureCreateOptions = {}): T {
|
|
118
155
|
tex.wrapS = THREE.RepeatWrapping;
|
|
119
156
|
tex.wrapT = THREE.RepeatWrapping;
|
|
120
157
|
tex.magFilter = THREE.LinearFilter;
|
|
121
158
|
tex.minFilter = THREE.LinearMipmapLinearFilter;
|
|
122
159
|
tex.generateMipmaps = true;
|
|
123
160
|
// 各向异性过滤对斜视角立面清晰度的提升比提高分辨率更直接
|
|
124
|
-
tex.anisotropy = anisotropyCap
|
|
125
|
-
if (srgb && srgbTextureDecode) tex.colorSpace = THREE.SRGBColorSpace;
|
|
161
|
+
tex.anisotropy = resolveAnisotropy(opts.anisotropyCap ?? anisotropyCap);
|
|
162
|
+
if (srgb && (opts.srgbDecode ?? srgbTextureDecode)) tex.colorSpace = THREE.SRGBColorSpace;
|
|
126
163
|
tex.flipY = false;
|
|
127
164
|
tex.needsUpdate = true;
|
|
128
165
|
return tex;
|
|
@@ -136,18 +173,23 @@ function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
|
136
173
|
* 自动选的层级会把相邻子区域混进来,远处就是一糊串色 —— 实测照抄公式但留着 GPU mip
|
|
137
174
|
* 时,画面反而比不改更糟,就是因为这两个变量叠在一起了。
|
|
138
175
|
*/
|
|
139
|
-
function configureAtlas<T extends THREE.Texture>(tex: T): T {
|
|
140
|
-
configure(tex);
|
|
176
|
+
function configureAtlas<T extends THREE.Texture>(tex: T, opts: TextureCreateOptions = {}): T {
|
|
177
|
+
configure(tex, true, opts);
|
|
141
178
|
tex.wrapS = THREE.ClampToEdgeWrapping;
|
|
142
179
|
tex.wrapT = THREE.ClampToEdgeWrapping;
|
|
143
180
|
tex.minFilter = THREE.LinearFilter;
|
|
144
181
|
tex.generateMipmaps = false;
|
|
145
182
|
tex.anisotropy = 1;
|
|
183
|
+
// 标记给 applyAnisotropyCap:图集不吃各向异性上限(见该函数注释)
|
|
184
|
+
(tex.userData as { s3mNoAniso?: boolean }).s3mNoAniso = true;
|
|
146
185
|
tex.needsUpdate = true;
|
|
147
186
|
return tex;
|
|
148
187
|
}
|
|
149
188
|
|
|
150
|
-
export async function createTexture(
|
|
189
|
+
export async function createTexture(
|
|
190
|
+
tex: S3MTexture | null | undefined,
|
|
191
|
+
opts: TextureCreateOptions = {}
|
|
192
|
+
): Promise<DecodedTexture> {
|
|
151
193
|
if (!tex || !tex.width || !tex.height) return { texture: null, decoded: 'empty' };
|
|
152
194
|
|
|
153
195
|
// 图集纹理(flipY 的原始 DXT 图集 / AtlasPack 拼出来的 RGBA 大图 / leaf 组合画布)都必须
|
|
@@ -157,7 +199,7 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
157
199
|
// 压缩直传只在「宿主 GL 支持 + 非图集」时开:图集要 flipY / 无 mip,压缩上传做不了翻行
|
|
158
200
|
const wantCompressed = compressedTexturesSupported && !isAtlas;
|
|
159
201
|
// 尺寸上限同样跳过图集:UV 已重映射到子区域,缩放会让整体错位
|
|
160
|
-
const sizeCap = isAtlas ? Infinity : maxTextureSize;
|
|
202
|
+
const sizeCap = isAtlas ? Infinity : (opts.maxSize ?? maxTextureSize);
|
|
161
203
|
const data = await decodeTexture(tex, wantCompressed, sizeCap);
|
|
162
204
|
|
|
163
205
|
if (!data.rgba && !data.blocks && !data.bitmap) {
|
|
@@ -194,7 +236,7 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
194
236
|
texture = dt;
|
|
195
237
|
}
|
|
196
238
|
texture.needsUpdate = true;
|
|
197
|
-
const configured = isAtlas ? configureAtlas(texture) : configure(texture);
|
|
239
|
+
const configured = isAtlas ? configureAtlas(texture, opts) : configure(texture, true, opts);
|
|
198
240
|
if (data.blocks) {
|
|
199
241
|
configured.generateMipmaps = false;
|
|
200
242
|
// 带链 → 三线性取链上高层级(官方同款平均效果);纯 level0 才退回 Linear 兜底
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 分析用的离屏深度采集(P1 的 GPU 部分)。
|
|
3
|
+
*
|
|
4
|
+
* 这里只干一件事:把场景渲染成「一张能回读的深度图」,判断逻辑全在
|
|
5
|
+
* `analysis/skyline.ts` 与 `analysis/viewshed.ts` 里(纯数学、有单测)。
|
|
6
|
+
* 这样 GPU 侧出错只可能是「图不对」(demo 里一眼看得出),
|
|
7
|
+
* 而不会把归约逻辑一起搅进去。
|
|
8
|
+
*
|
|
9
|
+
* 两个采集器:
|
|
10
|
+
* - `captureSkylineDepth`:用当前相机视角渲染一次,给天际线用(three 的 RGBADepthPacking);
|
|
11
|
+
* - `captureRadialCube`:从观察点朝 6 个方向各渲染一次,存「到观察点的距离」,
|
|
12
|
+
* 给 360° 可视域用。
|
|
13
|
+
*
|
|
14
|
+
* 三条硬约束(都是踩过的坑):
|
|
15
|
+
* 1. **必须 `resetState()`**:MapLibre 在上一次 draw 之后改过 GL 状态,
|
|
16
|
+
* three 的缓存还停在上一次自己的 render 上,不重置会出现深度测试失效。
|
|
17
|
+
* 2. **清屏成白色**:`packDepthToRGBA(1)` 返回全白,清屏也用白,
|
|
18
|
+
* 于是「背景」和「远平面」解包后都是 1.0,背景判定只需一个阈值。
|
|
19
|
+
* 3. **回读是行倒序的**:WebGL 的 `readPixels` 原点在左下,这里翻成正序再交出去。
|
|
20
|
+
*/
|
|
21
|
+
import * as THREE from 'three';
|
|
22
|
+
import type { Vec3 } from '../../analysis/measure.js';
|
|
23
|
+
|
|
24
|
+
export interface DepthCaptureContext {
|
|
25
|
+
renderer: () => THREE.WebGLRenderer | null;
|
|
26
|
+
scene: () => THREE.Scene | null;
|
|
27
|
+
camera: () => THREE.Camera | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 参与深度采集的**额外几何**(分析代理)。
|
|
32
|
+
*
|
|
33
|
+
* 用途:分析只看得见 S3M 场景里的东西。要让 MapLibre 的 MVT 建筑 / 地形也参与
|
|
34
|
+
* 遮挡,外围得自己把它们挤出成几何,通过这个参数临时挂进场景 —— 只在这一趟
|
|
35
|
+
* 深度渲染里存在,**不进主渲染、不影响画面**,渲染完立刻摘掉。
|
|
36
|
+
*
|
|
37
|
+
* 注意:
|
|
38
|
+
* - 传进来的对象不要已经有 parent(会被临时 add 到 viewer 的场景上再移除);
|
|
39
|
+
* - 代理几何的材质会被 `overrideMaterial` 覆盖,所以给什么材质都行;
|
|
40
|
+
* - 代理和 MapLibre 实际画出来的可能有细微偏差(表达式插值、terrain 抬升等),
|
|
41
|
+
* 这是「复制一份几何」这条路固有的代价。
|
|
42
|
+
*/
|
|
43
|
+
export interface DepthCaptureInclude {
|
|
44
|
+
include?: readonly THREE.Object3D[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 临时把代理几何挂进场景,返回「摘掉」的函数 */
|
|
48
|
+
function attachProxy(scene: THREE.Scene, include?: readonly THREE.Object3D[]): () => void {
|
|
49
|
+
if (!include?.length) return () => {};
|
|
50
|
+
const added = include.filter((o) => o && !o.parent);
|
|
51
|
+
for (const o of added) scene.add(o);
|
|
52
|
+
return () => {
|
|
53
|
+
for (const o of added) scene.remove(o);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ------------------------------------------------------------------ 渲染器状态 */
|
|
58
|
+
|
|
59
|
+
interface RendererState {
|
|
60
|
+
target: THREE.WebGLRenderTarget | null;
|
|
61
|
+
toneMapping: THREE.ToneMapping;
|
|
62
|
+
colorSpace: THREE.ColorSpace;
|
|
63
|
+
autoClear: boolean;
|
|
64
|
+
clearColor: THREE.Color;
|
|
65
|
+
clearAlpha: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function saveState(r: THREE.WebGLRenderer): RendererState {
|
|
69
|
+
return {
|
|
70
|
+
target: r.getRenderTarget(),
|
|
71
|
+
toneMapping: r.toneMapping,
|
|
72
|
+
colorSpace: r.outputColorSpace,
|
|
73
|
+
autoClear: r.autoClear,
|
|
74
|
+
clearColor: r.getClearColor(new THREE.Color()),
|
|
75
|
+
clearAlpha: r.getClearAlpha(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function restoreState(r: THREE.WebGLRenderer, s: RendererState): void {
|
|
80
|
+
r.setRenderTarget(s.target);
|
|
81
|
+
r.toneMapping = s.toneMapping;
|
|
82
|
+
r.outputColorSpace = s.colorSpace;
|
|
83
|
+
r.autoClear = s.autoClear;
|
|
84
|
+
r.setClearColor(s.clearColor, s.clearAlpha);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 回读并把像素翻成正序(行 0 = 画面顶部) */
|
|
88
|
+
function readFlipped(r: THREE.WebGLRenderer, rt: THREE.WebGLRenderTarget, width: number, height: number): Uint8Array {
|
|
89
|
+
const raw = new Uint8Array(width * height * 4);
|
|
90
|
+
r.readRenderTargetPixels(rt, 0, 0, width, height, raw);
|
|
91
|
+
const out = new Uint8Array(raw.length);
|
|
92
|
+
const rowBytes = width * 4;
|
|
93
|
+
for (let y = 0; y < height; y++) {
|
|
94
|
+
out.set(raw.subarray((height - 1 - y) * rowBytes, (height - y) * rowBytes), y * rowBytes);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function makeTarget(w: number, h: number): THREE.WebGLRenderTarget {
|
|
100
|
+
return new THREE.WebGLRenderTarget(w, h, {
|
|
101
|
+
format: THREE.RGBAFormat,
|
|
102
|
+
type: THREE.UnsignedByteType,
|
|
103
|
+
minFilter: THREE.NearestFilter,
|
|
104
|
+
magFilter: THREE.NearestFilter,
|
|
105
|
+
depthBuffer: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ------------------------------------------------------------------ 天际线 */
|
|
110
|
+
|
|
111
|
+
export interface SkylineCapture {
|
|
112
|
+
rgba: Uint8Array;
|
|
113
|
+
width: number;
|
|
114
|
+
height: number;
|
|
115
|
+
inverseViewProj: Float32Array;
|
|
116
|
+
cameraPos: Vec3;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 用当前相机渲染一张深度图(RGBADepthPacking)。
|
|
121
|
+
*
|
|
122
|
+
* @param width 采集分辨率。精度由它决定:1280 宽 ≈ 0.28°/列;320 宽 ≈ 1.1°/列。
|
|
123
|
+
*/
|
|
124
|
+
export function captureSkylineDepth(
|
|
125
|
+
ctx: DepthCaptureContext,
|
|
126
|
+
width: number,
|
|
127
|
+
height: number,
|
|
128
|
+
include?: readonly THREE.Object3D[]
|
|
129
|
+
): SkylineCapture | null {
|
|
130
|
+
const r = ctx.renderer();
|
|
131
|
+
const scene = ctx.scene();
|
|
132
|
+
const camera = ctx.camera();
|
|
133
|
+
if (!r || !scene || !camera) return null;
|
|
134
|
+
|
|
135
|
+
const w = Math.max(1, Math.floor(width));
|
|
136
|
+
const h = Math.max(1, Math.floor(height));
|
|
137
|
+
const rt = makeTarget(w, h);
|
|
138
|
+
const depthMat = new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking });
|
|
139
|
+
|
|
140
|
+
const prevOverride = scene.overrideMaterial;
|
|
141
|
+
const state = saveState(r);
|
|
142
|
+
const detach = attachProxy(scene, include);
|
|
143
|
+
let rgba: Uint8Array | null = null;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
r.resetState();
|
|
147
|
+
r.setRenderTarget(rt);
|
|
148
|
+
r.toneMapping = THREE.NoToneMapping;
|
|
149
|
+
r.outputColorSpace = THREE.LinearSRGBColorSpace;
|
|
150
|
+
r.autoClear = true;
|
|
151
|
+
r.setClearColor(0xffffff, 1);
|
|
152
|
+
r.clear();
|
|
153
|
+
|
|
154
|
+
scene.overrideMaterial = depthMat;
|
|
155
|
+
camera.updateMatrixWorld();
|
|
156
|
+
r.render(scene, camera);
|
|
157
|
+
rgba = readFlipped(r, rt, w, h);
|
|
158
|
+
} finally {
|
|
159
|
+
detach();
|
|
160
|
+
scene.overrideMaterial = prevOverride;
|
|
161
|
+
restoreState(r, state);
|
|
162
|
+
depthMat.dispose();
|
|
163
|
+
rt.dispose();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const vp = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
167
|
+
const inv = new Float32Array(vp.clone().invert().elements);
|
|
168
|
+
const pos = new THREE.Vector3().setFromMatrixPosition(camera.matrixWorld);
|
|
169
|
+
|
|
170
|
+
return { rgba, width: w, height: h, inverseViewProj: inv, cameraPos: [pos.x, pos.y, pos.z] };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ------------------------------------------------------------------ 360° 可视域 */
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 6 个面的朝向。**与 `analysis/viewshed.ts` 的采样器约定必须逐字一致**,
|
|
177
|
+
* 否则可视域会镜像到错误的一侧(而且看起来还挺像真的)。
|
|
178
|
+
*
|
|
179
|
+
* 约定:`right = forward × up`;采样器里 `u = dot(dir, right) / dot(dir, forward)`,
|
|
180
|
+
* `v = dot(dir, up) / dot(dir, forward)`,像素 = `((u + 1) / 2 * size, (v + 1) / 2 * size)`。
|
|
181
|
+
*/
|
|
182
|
+
export const CUBE_FACES: readonly { forward: Vec3; up: Vec3 }[] = [
|
|
183
|
+
{ forward: [1, 0, 0], up: [0, -1, 0] },
|
|
184
|
+
{ forward: [-1, 0, 0], up: [0, -1, 0] },
|
|
185
|
+
{ forward: [0, 1, 0], up: [0, 0, 1] },
|
|
186
|
+
{ forward: [0, -1, 0], up: [0, 0, -1] },
|
|
187
|
+
{ forward: [0, 0, 1], up: [0, -1, 0] },
|
|
188
|
+
{ forward: [0, 0, -1], up: [0, -1, 0] },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
/** 按上面的约定造 6 台 90° 相机;抽出来是为了能脱离 WebGL 单测朝向 */
|
|
192
|
+
export function buildCubeCameras(origin: Vec3, near: number, far: number): THREE.PerspectiveCamera[] {
|
|
193
|
+
return CUBE_FACES.map(({ forward, up }) => {
|
|
194
|
+
const cam = new THREE.PerspectiveCamera(90, 1, near, far);
|
|
195
|
+
cam.up.set(up[0], up[1], up[2]);
|
|
196
|
+
cam.position.set(origin[0], origin[1], origin[2]);
|
|
197
|
+
cam.lookAt(origin[0] + forward[0], origin[1] + forward[1], origin[2] + forward[2]);
|
|
198
|
+
cam.updateMatrixWorld(true);
|
|
199
|
+
return cam;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 从观察点朝 6 个方向渲染「到观察点的距离」,归一化到 [0,1] 后按
|
|
205
|
+
* three 的 `packDepthToRGBA` 打包。
|
|
206
|
+
*
|
|
207
|
+
* ⚠️ 用 `overrideMaterial` 换材质,**实例化网格(InstancedMesh)不会被正确变换** ——
|
|
208
|
+
* 简化着色器不处理 `instanceMatrix`。点云 / 树木这类实例化内容不参与遮挡。
|
|
209
|
+
*/
|
|
210
|
+
export function captureRadialCube(
|
|
211
|
+
ctx: DepthCaptureContext,
|
|
212
|
+
origin: Vec3,
|
|
213
|
+
size: number,
|
|
214
|
+
maxDist: number,
|
|
215
|
+
include?: readonly THREE.Object3D[]
|
|
216
|
+
): Uint8Array[] | null {
|
|
217
|
+
const r = ctx.renderer();
|
|
218
|
+
const scene = ctx.scene();
|
|
219
|
+
if (!r || !scene) return null;
|
|
220
|
+
|
|
221
|
+
const s = Math.max(8, Math.floor(size));
|
|
222
|
+
const rt = makeTarget(s, s);
|
|
223
|
+
const mat = new THREE.ShaderMaterial({
|
|
224
|
+
uniforms: {
|
|
225
|
+
uOrigin: { value: new THREE.Vector3(origin[0], origin[1], origin[2]) },
|
|
226
|
+
uMaxDist: { value: maxDist },
|
|
227
|
+
},
|
|
228
|
+
vertexShader: /* glsl */ `
|
|
229
|
+
varying vec3 vWorld;
|
|
230
|
+
void main() {
|
|
231
|
+
vec4 world = modelMatrix * vec4( position, 1.0 );
|
|
232
|
+
vWorld = world.xyz;
|
|
233
|
+
gl_Position = projectionMatrix * viewMatrix * world;
|
|
234
|
+
}
|
|
235
|
+
`,
|
|
236
|
+
fragmentShader: /* glsl */ `
|
|
237
|
+
#include <common>
|
|
238
|
+
#include <packing>
|
|
239
|
+
uniform vec3 uOrigin;
|
|
240
|
+
uniform float uMaxDist;
|
|
241
|
+
varying vec3 vWorld;
|
|
242
|
+
void main() {
|
|
243
|
+
float d = clamp( length( vWorld - uOrigin ) / uMaxDist, 0.0, 1.0 );
|
|
244
|
+
gl_FragColor = packDepthToRGBA( d );
|
|
245
|
+
}
|
|
246
|
+
`,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const prevOverride = scene.overrideMaterial;
|
|
250
|
+
const state = saveState(r);
|
|
251
|
+
const detach = attachProxy(scene, include);
|
|
252
|
+
const cameras = buildCubeCameras(origin, 0.1, maxDist);
|
|
253
|
+
const faces: Uint8Array[] = [];
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
r.resetState();
|
|
257
|
+
r.setRenderTarget(rt);
|
|
258
|
+
r.toneMapping = THREE.NoToneMapping;
|
|
259
|
+
r.outputColorSpace = THREE.LinearSRGBColorSpace;
|
|
260
|
+
r.autoClear = true;
|
|
261
|
+
r.setClearColor(0xffffff, 1); // 白 = 该方向没有几何(与 pack(1) 一致)
|
|
262
|
+
scene.overrideMaterial = mat;
|
|
263
|
+
|
|
264
|
+
for (const cam of cameras) {
|
|
265
|
+
r.clear();
|
|
266
|
+
r.render(scene, cam);
|
|
267
|
+
faces.push(readFlipped(r, rt, s, s));
|
|
268
|
+
}
|
|
269
|
+
} finally {
|
|
270
|
+
detach();
|
|
271
|
+
scene.overrideMaterial = prevOverride;
|
|
272
|
+
restoreState(r, state);
|
|
273
|
+
mat.dispose();
|
|
274
|
+
rt.dispose();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return faces;
|
|
278
|
+
}
|
|
@@ -4,17 +4,16 @@
|
|
|
4
4
|
* 从 `S3MViewer` 里整体搬出来的第一块 —— 它只依赖 `scene` / `renderer` / 共享
|
|
5
5
|
* uniform / 宿主方位角,不碰几何、瓦片、拾取,是耦合最低的一块。
|
|
6
6
|
*
|
|
7
|
-
* ##
|
|
7
|
+
* ## 颜色管线是图层级的
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* 颜色管线 / 纹理解码 / 各向异性三者一起决定「官方 supermap 观感」,都由
|
|
10
|
+
* `ctx.applyRenderProfile` 落到本图层(见 `S3MViewer._applyRenderProfile`),
|
|
11
|
+
* 不再写模块级全局 —— 同页两个 viewer 用不同预设互不影响,已加载瓦片也会
|
|
12
|
+
* 当场重建材质、重传纹理,不用重载。
|
|
13
13
|
*/
|
|
14
14
|
import * as THREE from 'three';
|
|
15
15
|
import { resolveLightingPreset, type LightingPreset } from '../lightingPreset.js';
|
|
16
|
-
import {
|
|
17
|
-
import { setAnisotropyCap, setSrgbTextureDecode } from '../TextureFactory.js';
|
|
16
|
+
import type { ColorPipeline } from '../StyleMaterial.js';
|
|
18
17
|
import type { LightOptions, S3MViewerOptions } from '../S3MViewer.js';
|
|
19
18
|
import type { StyleUniforms } from '../../core/types.js';
|
|
20
19
|
|
|
@@ -49,6 +48,14 @@ export interface LightRigContext {
|
|
|
49
48
|
getBearing?: () => number | undefined;
|
|
50
49
|
/** 宿主需要重画的信号 */
|
|
51
50
|
repaint: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* 应用图层级渲染 profile(颜色管线 + 各向异性)。
|
|
53
|
+
*
|
|
54
|
+
* `initial = true` 是构造期:那时还没有瓦片与材质,落地方只记状态即可。
|
|
55
|
+
* 换掉以前的 `setColorPipeline` / `setSrgbTextureDecode` / `setAnisotropyCap`
|
|
56
|
+
* 三个模块级全局调用 —— 它们会让同页多图层互相污染。
|
|
57
|
+
*/
|
|
58
|
+
applyRenderProfile: (p: { colorPipeline: ColorPipeline; anisotropyCap: number }, initial?: boolean) => void;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
export class LightRig {
|
|
@@ -74,7 +81,7 @@ export class LightRig {
|
|
|
74
81
|
const options = ctx.options;
|
|
75
82
|
if (options.lightingPreset && options.lightingPreset !== 'default') {
|
|
76
83
|
Object.assign(this._state, resolveLightingPreset(options.lightingPreset));
|
|
77
|
-
this.applyColorPipeline(options.lightingPreset);
|
|
84
|
+
this.applyColorPipeline(options.lightingPreset, true);
|
|
78
85
|
}
|
|
79
86
|
if (options.light) Object.assign(this._state, options.light);
|
|
80
87
|
}
|
|
@@ -155,15 +162,18 @@ export class LightRig {
|
|
|
155
162
|
/**
|
|
156
163
|
* supermap 完整版 = 光照强度 + 颜色管线一起切:
|
|
157
164
|
* direct 管线下纹理不做 sRGB 解码、片元按官方常数 Lambert 直乘(见 lightingPreset.ts)。
|
|
158
|
-
*
|
|
165
|
+
*
|
|
166
|
+
* 这三样(管线 / 解码 / 各向异性)以前都是模块级全局,同页两个图层用不同预设
|
|
167
|
+
* 会互相污染;现在交给 `ctx.applyRenderProfile`,由图层自己持有并透传给材质与纹理。
|
|
159
168
|
*/
|
|
160
|
-
applyColorPipeline(preset: LightingPreset): void {
|
|
169
|
+
applyColorPipeline(preset: LightingPreset, initial = false): void {
|
|
161
170
|
const direct = preset === 'supermap';
|
|
162
|
-
setColorPipeline(direct ? 'direct' : 'linear');
|
|
163
|
-
setSrgbTextureDecode(!direct);
|
|
164
171
|
// 官方不开各向异性:远处直接采高 mip 平均成纯色("灰色滤镜"的一部分)。
|
|
165
172
|
// 我们默认 Infinity(倾斜摄影立面清晰度优先),supermap 模式必须收回。
|
|
166
|
-
|
|
173
|
+
this.ctx.applyRenderProfile(
|
|
174
|
+
{ colorPipeline: direct ? 'direct' : 'linear', anisotropyCap: direct ? 1 : Infinity },
|
|
175
|
+
initial
|
|
176
|
+
);
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
setLightingPreset(preset: LightingPreset): void {
|
|
@@ -407,6 +407,53 @@ export class PickService {
|
|
|
407
407
|
return null;
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
/**
|
|
411
|
+
* 沿给定射线求**最近**交点距离(通视分析用)。
|
|
412
|
+
*
|
|
413
|
+
* 与 `pick` 不同:不走 GPU、不认批次,只回答「这条线上最先撞到什么」,
|
|
414
|
+
* 所以一次查询的代价是 O(已加载三角面),几十万面的场景在几十毫秒量级,
|
|
415
|
+
* 适合点击式查询;要逐帧连续更新得换深度图方案(见 `docs/API.md`)。
|
|
416
|
+
*
|
|
417
|
+
* @param origin 起点(场景 ENU 米)
|
|
418
|
+
* @param dir 单位方向
|
|
419
|
+
* @param maxDistance 只关心这个距离内的交点
|
|
420
|
+
*/
|
|
421
|
+
raycastDistance(
|
|
422
|
+
origin: readonly [number, number, number],
|
|
423
|
+
dir: readonly [number, number, number],
|
|
424
|
+
maxDistance: number
|
|
425
|
+
): { distance: number; point: [number, number, number] } | null {
|
|
426
|
+
if (!this.host.scene) return null;
|
|
427
|
+
const meshes = [...this.pickables.values()]
|
|
428
|
+
.map((p) => p.mesh)
|
|
429
|
+
.filter((m) => {
|
|
430
|
+
// 被隐藏的单体所在网格、以及整层隐藏的都要排除,否则会「撞到看不见的墙」
|
|
431
|
+
let curr: THREE.Object3D | null = m;
|
|
432
|
+
while (curr) {
|
|
433
|
+
if (!curr.visible) return false;
|
|
434
|
+
curr = curr.parent;
|
|
435
|
+
}
|
|
436
|
+
return true;
|
|
437
|
+
});
|
|
438
|
+
if (!meshes.length) return null;
|
|
439
|
+
|
|
440
|
+
const raycaster = new THREE.Raycaster();
|
|
441
|
+
raycaster.set(
|
|
442
|
+
new THREE.Vector3(origin[0], origin[1], origin[2]),
|
|
443
|
+
new THREE.Vector3(dir[0], dir[1], dir[2]).normalize()
|
|
444
|
+
);
|
|
445
|
+
raycaster.near = 0;
|
|
446
|
+
raycaster.far = maxDistance;
|
|
447
|
+
|
|
448
|
+
const hits = raycaster.intersectObjects(meshes, false);
|
|
449
|
+
if (!hits.length) return null;
|
|
450
|
+
const h = hits[0];
|
|
451
|
+
return {
|
|
452
|
+
distance: h.distance,
|
|
453
|
+
point: [h.point.x, h.point.y, h.point.z],
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
410
457
|
/**
|
|
411
458
|
* 沿视线方向穿透拾取多个单体,按由近至远排序返回。
|
|
412
459
|
*/
|
|
@@ -174,6 +174,18 @@ export interface S3MViewerOptions {
|
|
|
174
174
|
* `setMaterialQuality()` 原位重建。
|
|
175
175
|
*/
|
|
176
176
|
materialQuality?: 'pbr' | 'lambert';
|
|
177
|
+
/**
|
|
178
|
+
* 颜色管线(默认 `'linear'`)。
|
|
179
|
+
*
|
|
180
|
+
* - `'linear'`:纹理 sRGB 解码 → 线性 PBR 光照 → linear→sRGB 输出(three 标准姿势)
|
|
181
|
+
* - `'direct'`:官方 iClient3D 的 sRGB 直乘管线,同时把纹理解码关掉(字节值原样进
|
|
182
|
+
* shader)。`lightingPreset: 'supermap'` 会选它
|
|
183
|
+
*
|
|
184
|
+
* **图层级**:只定这一层,同页多图层各用各的;不传 = 跟随模块级默认。
|
|
185
|
+
* 已加载瓦片可以在运行时用 `setColorPipeline(q, { duration })` 平滑切过去
|
|
186
|
+
* (重建材质 + 重传纹理,不重新下载瓦片)。
|
|
187
|
+
*/
|
|
188
|
+
colorPipeline?: 'linear' | 'direct';
|
|
177
189
|
/** 自定义瓦片构建器(测试 / 服务端渲染时注入) */
|
|
178
190
|
buildTile?: BuildTileFn | null;
|
|
179
191
|
/**
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
} from '../core/S3MBlockParser.js';
|
|
33
33
|
import type { Anchor, ParsedTile, Stats, StyleUniforms } from '../core/types.js';
|
|
34
34
|
import type { TileGeometryData } from '../core/TileGeometry.js';
|
|
35
|
+
import type { TextureCreateOptions } from '../render/TextureFactory.js';
|
|
35
36
|
import type { Group, Texture } from 'three';
|
|
36
37
|
|
|
37
38
|
function dirOf(url: string): string {
|
|
@@ -248,6 +249,10 @@ export type BuildTileFn = (
|
|
|
248
249
|
polygonOffset?: { factor: number; units: number };
|
|
249
250
|
/** 光照模型档位(透传给 buildTileObjects),见 BuildTileOptions.materialQuality */
|
|
250
251
|
materialQuality?: 'pbr' | 'lambert';
|
|
252
|
+
/** 颜色管线(透传给 buildTileObjects),见 BuildTileOptions.colorPipeline */
|
|
253
|
+
colorPipeline?: 'linear' | 'direct';
|
|
254
|
+
/** 纹理创建参数(透传给 buildTileObjects → createTexture) */
|
|
255
|
+
textureOptions?: TextureCreateOptions;
|
|
251
256
|
}
|
|
252
257
|
) => Promise<{ group: Group; stats: Partial<Stats> }>;
|
|
253
258
|
|
|
@@ -265,6 +270,10 @@ export interface S3MTilesetOptions {
|
|
|
265
270
|
* 由 DatasetManager 从图层 / 数据集设置带下来。
|
|
266
271
|
*/
|
|
267
272
|
materialQuality?: 'pbr' | 'lambert';
|
|
273
|
+
/** 颜色管线,透传给 buildTileObjects。不传 = 读模块级默认。由图层的渲染 profile 带下来 */
|
|
274
|
+
colorPipeline?: 'linear' | 'direct';
|
|
275
|
+
/** 纹理创建参数(sRGB 解码 / 尺寸上限 / 各向异性上限),透传给 createTexture */
|
|
276
|
+
textureOptions?: TextureCreateOptions;
|
|
268
277
|
/**
|
|
269
278
|
* 是否用 Web Worker 解析经典 `.s3mb` 瓦片(默认开)。
|
|
270
279
|
*
|
|
@@ -479,6 +488,8 @@ export class S3MTileset {
|
|
|
479
488
|
private _buildTile: BuildTileFn | null;
|
|
480
489
|
private readonly _polygonOffset: { factor: number; units: number } | undefined;
|
|
481
490
|
private _materialQuality: 'pbr' | 'lambert' | undefined;
|
|
491
|
+
private _colorPipeline: 'linear' | 'direct' | undefined;
|
|
492
|
+
private _textureOptions: TextureCreateOptions | undefined;
|
|
482
493
|
private readonly _uniforms: StyleUniforms | null;
|
|
483
494
|
/** 经典 .s3mb 的 Worker 解析客户端;undefined = 尚未尝试创建,null = 不可用/已降级 */
|
|
484
495
|
private _workerClient: S3MWorkerClient | null | undefined; private readonly _useWorker: boolean;
|
|
@@ -500,6 +511,8 @@ export class S3MTileset {
|
|
|
500
511
|
this._buildTile = options.buildTile ?? null;
|
|
501
512
|
this._polygonOffset = options.polygonOffset;
|
|
502
513
|
this._materialQuality = options.materialQuality;
|
|
514
|
+
this._colorPipeline = options.colorPipeline;
|
|
515
|
+
this._textureOptions = options.textureOptions;
|
|
503
516
|
// 着色器共享 uniform,由 S3MLayer 持有并传进来
|
|
504
517
|
this._uniforms = options.uniforms ?? null;
|
|
505
518
|
this._useWorker = options.useWorker !== false;
|
|
@@ -740,6 +753,15 @@ export class S3MTileset {
|
|
|
740
753
|
this._materialQuality = q;
|
|
741
754
|
}
|
|
742
755
|
|
|
756
|
+
/**
|
|
757
|
+
* 运行时改本数据集的渲染 profile(颜色管线 / 纹理参数)。
|
|
758
|
+
* 只影响**之后**构建的瓦片材质与纹理;场上已有的由 `S3MViewer` 统一重建 / 重传。
|
|
759
|
+
*/
|
|
760
|
+
setRenderProfile(p: { colorPipeline?: 'linear' | 'direct'; textureOptions?: TextureCreateOptions }): void {
|
|
761
|
+
if (p.colorPipeline !== undefined) this._colorPipeline = p.colorPipeline;
|
|
762
|
+
if (p.textureOptions !== undefined) this._textureOptions = p.textureOptions;
|
|
763
|
+
}
|
|
764
|
+
|
|
743
765
|
/**
|
|
744
766
|
* 当前数据集驻留的显存与内存总字节数(几何缓冲精确大小 + 纹理显存估计)
|
|
745
767
|
*/
|
|
@@ -1264,6 +1286,8 @@ export class S3MTileset {
|
|
|
1264
1286
|
assembled,
|
|
1265
1287
|
polygonOffset: this._polygonOffset,
|
|
1266
1288
|
materialQuality: this._materialQuality,
|
|
1289
|
+
colorPipeline: this._colorPipeline,
|
|
1290
|
+
textureOptions: this._textureOptions,
|
|
1267
1291
|
});
|
|
1268
1292
|
const { group, stats } = buildRes;
|
|
1269
1293
|
const texKeys = (buildRes as any).textureKeys ?? (group.userData?.textureKeys as string[] | undefined) ?? Object.keys(tile.textures ?? {});
|