s3m-three 0.3.13 → 0.4.15
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-DLsGBSaV.js → s3mWorker-BjPOUgjg.js} +1 -1
- package/dist/browser/s3m-three.iife.ext-three.js +21 -21
- package/dist/browser/s3m-three.iife.js +21 -21
- package/dist/core/TextureDecode.d.ts +9 -0
- package/dist/core/TextureDecode.d.ts.map +1 -1
- package/dist/core/TextureDecode.js +66 -0
- package/dist/core/TextureDecode.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +5 -1
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +3 -1
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/OrthoProjection.d.ts +22 -0
- package/dist/render/OrthoProjection.d.ts.map +1 -1
- package/dist/render/OrthoProjection.js +60 -0
- package/dist/render/OrthoProjection.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +96 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +129 -6
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +15 -2
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +31 -4
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +3 -2
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +26 -6
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +26 -1
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +29 -2
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/worker/S3MWorkerClient.d.ts +6 -3
- package/dist/worker/S3MWorkerClient.d.ts.map +1 -1
- package/dist/worker/S3MWorkerClient.js +8 -5
- package/dist/worker/S3MWorkerClient.js.map +1 -1
- package/dist/worker/s3mWorker.js +123 -71
- package/dist/worker/s3mWorker.js.map +1 -1
- package/docs/API.md +66 -0
- package/package.json +1 -1
- package/src/core/TextureDecode.ts +87 -0
- package/src/index.ts +4 -2
- package/src/render/DatasetManager.ts +9 -1
- package/src/render/OrthoProjection.ts +58 -0
- package/src/render/S3MViewer.ts +181 -9
- package/src/render/StyleMaterial.ts +40 -5
- package/src/render/TextureFactory.ts +35 -8
- package/src/tileset/S3MTileset.ts +41 -2
- package/src/worker/S3MWorkerClient.ts +9 -5
- package/src/worker/s3mWorker.ts +2 -2
|
@@ -168,7 +168,33 @@ export const DEFAULT_STYLE: S3MStyle = {
|
|
|
168
168
|
fadeIn: 0,
|
|
169
169
|
};
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
/** 光照模型:pbr(默认,MeshStandardMaterial)| lambert(省 GGX 与环境球谐) */
|
|
172
|
+
export type MaterialQuality = 'pbr' | 'lambert';
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 当前光照模型。模块级全局 —— 与 anisotropy / maxTextureSize 同一套设计,
|
|
176
|
+
* 同页多个图层会互相覆盖,后创建的那个决定全局。只影响**之后创建**的材质。
|
|
177
|
+
*/
|
|
178
|
+
let materialQuality: MaterialQuality = 'pbr';
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 切换光照模型。
|
|
182
|
+
*
|
|
183
|
+
* 'pbr'(默认)= `MeshStandardMaterial`,GGX + 环境球谐,质感最好也最贵。
|
|
184
|
+
* 'lambert' = `MeshLambertMaterial`,只保留漫反射 + 平行光,片元开销约省一半;
|
|
185
|
+
* 白模 / 纯色 / 按高度着色这类不需要高光的模式,肉眼差别很小。
|
|
186
|
+
*
|
|
187
|
+
* 注意:只影响之后创建的材质 —— 已加载的瓦片要重新加载才会换。
|
|
188
|
+
*/
|
|
189
|
+
export function setMaterialQuality(q: MaterialQuality): void {
|
|
190
|
+
materialQuality = q === 'lambert' ? 'lambert' : 'pbr';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function getMaterialQuality(): MaterialQuality {
|
|
194
|
+
return materialQuality;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export type StyledMaterial = (THREE.MeshStandardMaterial | THREE.MeshLambertMaterial) & {
|
|
172
198
|
userData: {
|
|
173
199
|
uSelBatch: { value: number };
|
|
174
200
|
uSelWhole: { value: number };
|
|
@@ -785,11 +811,11 @@ export interface StyledMaterialOptions {
|
|
|
785
811
|
*/
|
|
786
812
|
export function createStyledMaterial(opts: StyledMaterialOptions): StyledMaterial {
|
|
787
813
|
const uniforms = opts.uniforms;
|
|
788
|
-
|
|
814
|
+
// 两种光照模型共用的参数。注入的 chunk 只动 diffuseColor / normal / transformed,
|
|
815
|
+
// 在 Standard 与 Lambert 的 shader 里都存在同名 include,所以模板不用分支。
|
|
816
|
+
const baseParams = {
|
|
789
817
|
color: opts.color ?? new THREE.Color(0xffffff),
|
|
790
818
|
map: opts.map ?? null,
|
|
791
|
-
roughness: opts.roughness ?? 0.95,
|
|
792
|
-
metalness: 0,
|
|
793
819
|
side: opts.side ?? THREE.DoubleSide,
|
|
794
820
|
transparent: !!opts.transparent,
|
|
795
821
|
opacity: opts.opacity ?? 1,
|
|
@@ -800,7 +826,16 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
800
826
|
// 顶点色、贴图全部在自定义 chunk 里处理,不让 three 插手
|
|
801
827
|
vertexColors: false,
|
|
802
828
|
alphaTest: opts.alphaTest ?? 0,
|
|
803
|
-
}
|
|
829
|
+
};
|
|
830
|
+
const mat = (
|
|
831
|
+
materialQuality === 'lambert'
|
|
832
|
+
? new THREE.MeshLambertMaterial(baseParams)
|
|
833
|
+
: new THREE.MeshStandardMaterial({
|
|
834
|
+
...baseParams,
|
|
835
|
+
roughness: opts.roughness ?? 0.95,
|
|
836
|
+
metalness: 0,
|
|
837
|
+
})
|
|
838
|
+
) as StyledMaterial;
|
|
804
839
|
|
|
805
840
|
// 选中单体编号:每个 Mesh 独立,默认 -1 表示无选中
|
|
806
841
|
const uSelBatch = { value: -1 };
|
|
@@ -17,8 +17,28 @@ export { PixelFormat } from '../core/TextureFormat.js';
|
|
|
17
17
|
/** 由 S3MLayer 在拿到 GL 上下文后注入,用于斜视角下保持纹理清晰 */
|
|
18
18
|
let maxAnisotropy = 1;
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* 各向异性过滤的**上限**(默认 Infinity = 不封顶,取 GPU 报的最大值)。
|
|
22
|
+
*
|
|
23
|
+
* 各向异性对斜视角立面清晰度的提升比提高分辨率更直接,所以默认全量启用;
|
|
24
|
+
* 但它每像素要多采样,移动端 GPU 上封到 4 或 8 通常肉眼无差、成本明显下降。
|
|
25
|
+
* 由 `setMaxAnisotropy(gpuMax, cap)` 一起注入,接入方通过 `anisotropyCap` 配置。
|
|
26
|
+
*/
|
|
27
|
+
let anisotropyCap = Infinity;
|
|
28
|
+
|
|
29
|
+
export function setMaxAnisotropy(n: number, cap = Infinity): void {
|
|
21
30
|
maxAnisotropy = Math.max(1, n | 0);
|
|
31
|
+
anisotropyCap = Number.isFinite(cap) && cap > 0 ? Math.max(1, cap) : Infinity;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 纹理长边像素上限(默认 Infinity = 不限制)。
|
|
36
|
+
* 只作用于非图集的 RGBA / WebP 产物,见 `core/TextureDecode.ts` 的 `maxSize`。
|
|
37
|
+
*/
|
|
38
|
+
let maxTextureSize = Infinity;
|
|
39
|
+
|
|
40
|
+
export function setMaxTextureSize(n: number): void {
|
|
41
|
+
maxTextureSize = Number.isFinite(n) && n >= 1 ? n : Infinity;
|
|
22
42
|
}
|
|
23
43
|
|
|
24
44
|
/**
|
|
@@ -42,23 +62,28 @@ export interface DecodedTexture {
|
|
|
42
62
|
/**
|
|
43
63
|
* 注入「离线解码」实现(S3MViewer 装配:走共享解析 Worker)。
|
|
44
64
|
* 返回 null = Worker 不可用 / 崩溃,createTexture 自动退回主线程 decodeTextureData。
|
|
65
|
+
* `maxSize` 一并带过去,缩放也在 Worker 里做(同样是 CPU 活)。
|
|
45
66
|
*/
|
|
46
67
|
let offThreadDecode:
|
|
47
|
-
| ((tex: S3MTexture, compressed: boolean) => Promise<DecodedTextureData | null>)
|
|
68
|
+
| ((tex: S3MTexture, compressed: boolean, maxSize?: number) => Promise<DecodedTextureData | null>)
|
|
48
69
|
| null = null;
|
|
49
70
|
|
|
50
71
|
export function setTextureOffThreadDecode(
|
|
51
|
-
fn: ((tex: S3MTexture, compressed: boolean) => Promise<DecodedTextureData | null>) | null
|
|
72
|
+
fn: ((tex: S3MTexture, compressed: boolean, maxSize?: number) => Promise<DecodedTextureData | null>) | null
|
|
52
73
|
): void {
|
|
53
74
|
offThreadDecode = fn;
|
|
54
75
|
}
|
|
55
76
|
|
|
56
|
-
async function decodeTexture(
|
|
77
|
+
async function decodeTexture(
|
|
78
|
+
tex: S3MTexture,
|
|
79
|
+
compressed: boolean,
|
|
80
|
+
maxSize: number
|
|
81
|
+
): Promise<DecodedTextureData> {
|
|
57
82
|
if (offThreadDecode) {
|
|
58
|
-
const viaWorker = await offThreadDecode(tex, compressed);
|
|
83
|
+
const viaWorker = await offThreadDecode(tex, compressed, maxSize);
|
|
59
84
|
if (viaWorker) return viaWorker;
|
|
60
85
|
}
|
|
61
|
-
return decodeTextureData(tex, { compressed });
|
|
86
|
+
return decodeTextureData(tex, { compressed, maxSize });
|
|
62
87
|
}
|
|
63
88
|
|
|
64
89
|
function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
@@ -68,7 +93,7 @@ function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
|
68
93
|
tex.minFilter = THREE.LinearMipmapLinearFilter;
|
|
69
94
|
tex.generateMipmaps = true;
|
|
70
95
|
// 各向异性过滤对斜视角立面清晰度的提升比提高分辨率更直接
|
|
71
|
-
tex.anisotropy = maxAnisotropy;
|
|
96
|
+
tex.anisotropy = anisotropyCap === Infinity ? maxAnisotropy : Math.min(maxAnisotropy, anisotropyCap);
|
|
72
97
|
if (srgb) tex.colorSpace = THREE.SRGBColorSpace;
|
|
73
98
|
tex.flipY = false;
|
|
74
99
|
tex.needsUpdate = true;
|
|
@@ -103,7 +128,9 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
103
128
|
|
|
104
129
|
// 压缩直传只在「宿主 GL 支持 + 非图集」时开:图集要 flipY / 无 mip,压缩上传做不了翻行
|
|
105
130
|
const wantCompressed = compressedTexturesSupported && !isAtlas;
|
|
106
|
-
|
|
131
|
+
// 尺寸上限同样跳过图集:UV 已重映射到子区域,缩放会让整体错位
|
|
132
|
+
const sizeCap = isAtlas ? Infinity : maxTextureSize;
|
|
133
|
+
const data = await decodeTexture(tex, wantCompressed, sizeCap);
|
|
107
134
|
|
|
108
135
|
if (!data.rgba && !data.blocks && !data.bitmap) {
|
|
109
136
|
if (data.error) {
|
|
@@ -261,6 +261,16 @@ export interface S3MTilesetOptions {
|
|
|
261
261
|
maxRetries?: number;
|
|
262
262
|
/** LOD 切换范围缩放系数(默认 1.0),> 1 更倾向保留粗模,< 1 提前换细模 */
|
|
263
263
|
lodRangeScale?: number;
|
|
264
|
+
/**
|
|
265
|
+
* 本数据集**驻留三角形总数**的软上限(默认不限制)。
|
|
266
|
+
*
|
|
267
|
+
* `maxTiles` 只数瓦片个数:一块精模瓦片几十万面,64 块照样能把差设备压垮,
|
|
268
|
+
* 所以在瓦片数之外再给一条按面数的刹车。超了走同一套 make-before-break 淘汰
|
|
269
|
+
* (只踢「当前不显示」或「位置已被可见子瓦接管」的瓦片),不在画面上开洞。
|
|
270
|
+
*
|
|
271
|
+
* 不传 = 不限制,行为与旧版完全一致。运行时可用 `setMaxTriangles()` 调整。
|
|
272
|
+
*/
|
|
273
|
+
maxTriangles?: number;
|
|
264
274
|
/**
|
|
265
275
|
* 是否请求 `.texblock` 子图集(S3MBLOCK 根图集的清晰版)。**默认关**。
|
|
266
276
|
*
|
|
@@ -315,6 +325,11 @@ export class S3MTileset {
|
|
|
315
325
|
|
|
316
326
|
maxConcurrent: number;
|
|
317
327
|
maxTiles: number;
|
|
328
|
+
/**
|
|
329
|
+
* 驻留三角形总数的软上限。`Infinity` = 不限制(默认),此时 `_trimBudget` 的
|
|
330
|
+
* 判据与旧版逐字一致,不会多算一次面数。
|
|
331
|
+
*/
|
|
332
|
+
maxTriangles: number;
|
|
318
333
|
lodRangeScale: number;
|
|
319
334
|
|
|
320
335
|
/** 数据集配置声明的高度范围(米),若配置未声明则返回 null */
|
|
@@ -369,6 +384,7 @@ export class S3MTileset {
|
|
|
369
384
|
// 并发与总量限制:iServer 公开服务上别把人家打爆
|
|
370
385
|
this.maxConcurrent = options.maxConcurrent ?? 4;
|
|
371
386
|
this.maxTiles = options.maxTiles ?? 64;
|
|
387
|
+
this.maxTriangles = options.maxTriangles ?? Infinity;
|
|
372
388
|
this.maxImageCache = options.maxImageCache ?? 128;
|
|
373
389
|
// 硬上限 = 软上限 + 余量。淘汰只在「新瓦片已经落地并接管显示」之后做(make-before-break),
|
|
374
390
|
// 所以中间态允许临时超出 maxTiles;硬上限只用来挡住失控的请求量,不用于挤掉正在显示的内容。
|
|
@@ -566,17 +582,40 @@ export class S3MTileset {
|
|
|
566
582
|
return true;
|
|
567
583
|
}
|
|
568
584
|
|
|
585
|
+
/** 当前驻留瓦片的三角形总数(只算已落地的;在途瓦片还没构建,统计不到) */
|
|
586
|
+
private _residentTriangles(): number {
|
|
587
|
+
let total = 0;
|
|
588
|
+
for (const node of this.nodes.values()) total += node.stats?.triangles ?? 0;
|
|
589
|
+
return total;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* 运行时调整面数预算。`Infinity` = 不限制(默认)。
|
|
594
|
+
* 收紧后不会立刻掉内容,要等下一次 `_trimBudget`(下一帧 update)才生效。
|
|
595
|
+
*/
|
|
596
|
+
setMaxTriangles(n: number): void {
|
|
597
|
+
this.maxTriangles = n > 0 ? n : Infinity;
|
|
598
|
+
}
|
|
599
|
+
|
|
569
600
|
/**
|
|
570
601
|
* 预算裁剪 —— 必须在可见性算完之后调用(make-before-break):
|
|
571
602
|
* 新瓦片已经落地、也已经决定由谁负责显示,这时候才把没人依赖的那块裁掉。
|
|
572
603
|
* 只踢「当前不显示」或「位置已被可见子瓦接手」的瓦片;挑不出安全候选时允许
|
|
573
|
-
*
|
|
604
|
+
* 暂时超出预算,等下一帧再裁 —— 宁可多占一会儿内存,也不在画面上开洞。
|
|
605
|
+
*
|
|
606
|
+
* 两条预算并行:瓦片数(`maxTiles`)与三角形总数(`maxTriangles`,默认不限制)。
|
|
607
|
+
* `maxTriangles` 为 Infinity 时短路,行为与只有瓦片数预算时完全一致。
|
|
574
608
|
*/
|
|
575
609
|
private _trimBudget(): boolean {
|
|
576
610
|
let removed = false;
|
|
577
611
|
let guard = 0;
|
|
578
612
|
// 在途的也算进"预期驻留数":不然一批瓦片同时落地时会先把画面撑爆再疯狂裁
|
|
579
|
-
|
|
613
|
+
const overTiles = () => this.nodes.size + this.pending.size > this.maxTiles;
|
|
614
|
+
const overTriangles = () =>
|
|
615
|
+
Number.isFinite(this.maxTriangles) && this._residentTriangles() > this.maxTriangles;
|
|
616
|
+
// 面数预算生效时可能要连踢多块,迭代上限跟着驻留规模走;不生效时根本进不了循环
|
|
617
|
+
const iterationCap = this.nodes.size + this.maxTiles + 16;
|
|
618
|
+
while ((overTiles() || overTriangles()) && guard++ <= iterationCap) {
|
|
580
619
|
if (!this._evictOne(this.nodes.size + this.pending.size > this.hardCap)) break;
|
|
581
620
|
removed = true;
|
|
582
621
|
}
|
|
@@ -97,13 +97,16 @@ export class S3MWorkerClient {
|
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
/**
|
|
101
|
-
|
|
100
|
+
/**
|
|
101
|
+
* 解码一张纹理(DXT / CRN / WEBP / RGB 系)。tex.data 走 structured clone,主线程保留原数据。
|
|
102
|
+
* `maxSize` 交给 Worker 侧收口——缩放本身也是 CPU 活,放在 Worker 里主线程零成本。
|
|
103
|
+
*/
|
|
104
|
+
decodeTexture(tex: S3MTexture, compressed = false, maxSize?: number): Promise<DecodedTextureData> {
|
|
102
105
|
if (this.dead) return Promise.reject(new WorkerDeadError('s3m 解析 Worker 已终止'));
|
|
103
106
|
const id = ++this._seq;
|
|
104
107
|
return new Promise<DecodedTextureData>((resolve, reject) => {
|
|
105
108
|
this._pending.set(id, { resolve: resolve as (v: unknown) => void, reject });
|
|
106
|
-
this._worker.postMessage({ type: 'tex', id, tex, compressed });
|
|
109
|
+
this._worker.postMessage({ type: 'tex', id, tex, compressed, maxSize });
|
|
107
110
|
});
|
|
108
111
|
}
|
|
109
112
|
|
|
@@ -147,12 +150,13 @@ export function getSharedS3MWorker(): S3MWorkerClient | null {
|
|
|
147
150
|
*/
|
|
148
151
|
export async function decodeTextureWithWorker(
|
|
149
152
|
tex: S3MTexture,
|
|
150
|
-
compressed: boolean
|
|
153
|
+
compressed: boolean,
|
|
154
|
+
maxSize?: number
|
|
151
155
|
): Promise<DecodedTextureData | null> {
|
|
152
156
|
const client = getSharedS3MWorker();
|
|
153
157
|
if (!client) return null;
|
|
154
158
|
try {
|
|
155
|
-
return await client.decodeTexture(tex, compressed);
|
|
159
|
+
return await client.decodeTexture(tex, compressed, maxSize);
|
|
156
160
|
} catch {
|
|
157
161
|
return null;
|
|
158
162
|
}
|
package/src/worker/s3mWorker.ts
CHANGED
|
@@ -29,7 +29,7 @@ type ParseRequest = {
|
|
|
29
29
|
assemble?: boolean;
|
|
30
30
|
batching?: boolean;
|
|
31
31
|
};
|
|
32
|
-
type TexRequest = { type: 'tex'; id: number; tex: S3MTexture; compressed?: boolean };
|
|
32
|
+
type TexRequest = { type: 'tex'; id: number; tex: S3MTexture; compressed?: boolean; maxSize?: number };
|
|
33
33
|
type Request = ParseRequest | TexRequest;
|
|
34
34
|
|
|
35
35
|
type ParseResponse =
|
|
@@ -65,7 +65,7 @@ ctx.onmessage = async (e: MessageEvent<Request>) => {
|
|
|
65
65
|
ctx.postMessage({ id, ok: true, tile } satisfies ParseResponse, collectTransferables(tile));
|
|
66
66
|
}
|
|
67
67
|
} else {
|
|
68
|
-
const data = await decodeTextureData(msg.tex, { compressed: msg.compressed });
|
|
68
|
+
const data = await decodeTextureData(msg.tex, { compressed: msg.compressed, maxSize: msg.maxSize });
|
|
69
69
|
ctx.postMessage({ id, ok: true, data } satisfies TexResponse, texTransferList(data));
|
|
70
70
|
}
|
|
71
71
|
} catch (err) {
|