s3m-three 0.3.5 → 0.3.6
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-hZY5KqWF.js +1 -0
- package/dist/browser/s3m-three.iife.ext-three.js +26 -20
- package/dist/browser/s3m-three.iife.js +26 -20
- package/dist/core/TextureDecode.d.ts +18 -0
- package/dist/core/TextureDecode.d.ts.map +1 -0
- package/dist/core/TextureDecode.js +130 -0
- package/dist/core/TextureDecode.js.map +1 -0
- package/dist/core/TextureFormat.d.ts +22 -2
- package/dist/core/TextureFormat.d.ts.map +1 -1
- package/dist/core/TextureFormat.js +22 -2
- package/dist/core/TextureFormat.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +2 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +1 -0
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/FeatureStyleManager.d.ts +4 -2
- package/dist/render/FeatureStyleManager.d.ts.map +1 -1
- package/dist/render/FeatureStyleManager.js +33 -1
- package/dist/render/FeatureStyleManager.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +2 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +5 -2
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +6 -0
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +8 -19
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +39 -116
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +17 -0
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +32 -1
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/worker/S3MWorkerClient.d.ts +40 -0
- package/dist/worker/S3MWorkerClient.d.ts.map +1 -0
- package/dist/worker/S3MWorkerClient.js +120 -0
- package/dist/worker/S3MWorkerClient.js.map +1 -0
- package/dist/worker/s3mWorker.d.ts +2 -0
- package/dist/worker/s3mWorker.d.ts.map +1 -0
- package/dist/worker/s3mWorker.js +48 -0
- package/dist/worker/s3mWorker.js.map +1 -0
- package/dist/worker/transferables.d.ts +14 -0
- package/dist/worker/transferables.d.ts.map +1 -0
- package/dist/worker/transferables.js +61 -0
- package/dist/worker/transferables.js.map +1 -0
- package/docs/API.md +4 -0
- package/package.json +1 -1
- package/src/core/TextureDecode.ts +158 -0
- package/src/core/TextureFormat.ts +23 -2
- package/src/render/DatasetManager.ts +3 -0
- package/src/render/FeatureStyleManager.ts +31 -2
- package/src/render/S3MViewer.ts +8 -2
- package/src/render/StyleMaterial.ts +6 -0
- package/src/render/TextureFactory.ts +43 -122
- package/src/tileset/S3MTileset.ts +39 -1
- package/src/worker/S3MWorkerClient.ts +150 -0
- package/src/worker/s3mWorker.ts +58 -0
- package/src/worker/transferables.ts +58 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/** Worker 崩溃 / 不可用 —— 与「瓦片数据本身有问题」区分开,调用方据此决定降级还是重试 */
|
|
2
|
+
export class WorkerDeadError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'WorkerDeadError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class S3MWorkerClient {
|
|
9
|
+
_worker;
|
|
10
|
+
_seq = 0;
|
|
11
|
+
_pending = new Map();
|
|
12
|
+
/** true = 已崩溃 / 已终止,后续请求直接拒绝 */
|
|
13
|
+
dead = false;
|
|
14
|
+
/** 创建失败返回 null(不抛),调用方据此走主线程解析 */
|
|
15
|
+
static create() {
|
|
16
|
+
if (typeof Worker === 'undefined')
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
// 路径锚定本模块的编译产物(dist/worker/s3mWorker.js);
|
|
20
|
+
// Vite 打包时会把这个 pattern 转成独立 worker 资产,IIFE 直引场景由死锁降级兜底
|
|
21
|
+
const worker = new Worker(new URL('./s3mWorker.js', import.meta.url), { type: 'module' });
|
|
22
|
+
return new S3MWorkerClient(worker);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
constructor(worker) {
|
|
29
|
+
this._worker = worker;
|
|
30
|
+
worker.onmessage = (e) => {
|
|
31
|
+
const msg = e.data;
|
|
32
|
+
const waiter = this._pending.get(msg.id);
|
|
33
|
+
if (!waiter)
|
|
34
|
+
return;
|
|
35
|
+
this._pending.delete(msg.id);
|
|
36
|
+
if (!msg.ok) {
|
|
37
|
+
// 还原主线程 parseS3MB 的错误形状:permanent 标记驱动瓦片级的「永久失败」分支
|
|
38
|
+
const err = new Error(msg.message);
|
|
39
|
+
err.permanent = msg.permanent;
|
|
40
|
+
waiter.reject(err);
|
|
41
|
+
}
|
|
42
|
+
else if ('tile' in msg) {
|
|
43
|
+
waiter.resolve(msg.tile);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
waiter.resolve(msg.data);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
worker.onerror = () => this._die('s3m 解析 Worker 加载失败/崩溃');
|
|
50
|
+
worker.onmessageerror = () => this._die('s3m 解析 Worker 消息无法反序列化');
|
|
51
|
+
}
|
|
52
|
+
/** 解析一块瓦片。buffer 会被 structured clone(不 transfer),主线程保留原 buffer 以便崩溃后降级重解 */
|
|
53
|
+
parseTile(buffer) {
|
|
54
|
+
if (this.dead)
|
|
55
|
+
return Promise.reject(new WorkerDeadError('s3m 解析 Worker 已终止'));
|
|
56
|
+
const id = ++this._seq;
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
this._pending.set(id, { resolve: resolve, reject });
|
|
59
|
+
this._worker.postMessage({ type: 'parse', id, buffer });
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** 解码一张纹理(DXT / CRN / WEBP / RGB 系)。tex.data 走 structured clone,主线程保留原数据 */
|
|
63
|
+
decodeTexture(tex) {
|
|
64
|
+
if (this.dead)
|
|
65
|
+
return Promise.reject(new WorkerDeadError('s3m 解析 Worker 已终止'));
|
|
66
|
+
const id = ++this._seq;
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
this._pending.set(id, { resolve: resolve, reject });
|
|
69
|
+
this._worker.postMessage({ type: 'tex', id, tex });
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
dispose() {
|
|
73
|
+
this._die('dispose');
|
|
74
|
+
}
|
|
75
|
+
_die(reason) {
|
|
76
|
+
if (this.dead)
|
|
77
|
+
return;
|
|
78
|
+
this.dead = true;
|
|
79
|
+
try {
|
|
80
|
+
this._worker.terminate();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* 已死的 worker terminate 不会再抛 */
|
|
84
|
+
}
|
|
85
|
+
const err = new WorkerDeadError(`${reason},退回主线程解析`);
|
|
86
|
+
for (const waiter of this._pending.values())
|
|
87
|
+
waiter.reject(err);
|
|
88
|
+
this._pending.clear();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/* ------------------------------------------------------------------ 共享单例 */
|
|
92
|
+
/**
|
|
93
|
+
* 进程级共享的解析 Worker(解析 + 纹理解码共用一个线程,避免每个数据集 /
|
|
94
|
+
* 每个渲染层各起一个 Worker 把内存和调度打爆)。懒创建,崩溃后返回 null。
|
|
95
|
+
*/
|
|
96
|
+
let sharedClient;
|
|
97
|
+
export function getSharedS3MWorker() {
|
|
98
|
+
if (sharedClient === undefined)
|
|
99
|
+
sharedClient = S3MWorkerClient.create();
|
|
100
|
+
if (sharedClient && sharedClient.dead)
|
|
101
|
+
return null;
|
|
102
|
+
return sharedClient;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 纹理解码的「Worker 优先」入口,供 TextureFactory.setTextureOffThreadDecode 装配。
|
|
106
|
+
* 永不 reject:Worker 不可用 / 崩溃 / 单张解码失败都返回 null,调用方主线程兜底
|
|
107
|
+
* (解码失败本身在 decodeTextureData 里也不抛错,走 crn(failed) 之类的标记)。
|
|
108
|
+
*/
|
|
109
|
+
export async function decodeTextureWithWorker(tex) {
|
|
110
|
+
const client = getSharedS3MWorker();
|
|
111
|
+
if (!client)
|
|
112
|
+
return null;
|
|
113
|
+
try {
|
|
114
|
+
return await client.decodeTexture(tex);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=S3MWorkerClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"S3MWorkerClient.js","sourceRoot":"","sources":["../../src/worker/S3MWorkerClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3mWorker.d.ts","sourceRoot":"","sources":["../../src/worker/s3mWorker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3M 解析 / 纹理解码 Worker 入口。
|
|
3
|
+
*
|
|
4
|
+
* 职责:把 CPU 密集的解码搬离主线程——
|
|
5
|
+
* - parse:`.s3mb`(含 S3M 1.x / Draco / MeshOpt 内联 wasm 解码)整块解析;
|
|
6
|
+
* - tex: 纹理解码(DXT 解块 / CRN wasm 转码 / WEBP createImageBitmap),
|
|
7
|
+
* 与主线程共用 core/TextureDecode.ts 的同一份实现。
|
|
8
|
+
* 抓取(fetch)仍由 S3MTileset 在主线程做——重试、退避、content-type 检查这些
|
|
9
|
+
* 语义都留在原处不动,Worker 只收字节、吐纯数据结果。
|
|
10
|
+
*
|
|
11
|
+
* 协议:
|
|
12
|
+
* { type:'parse', id, buffer } → { id, ok:true, tile } | { id, ok:false, message, permanent }
|
|
13
|
+
* { type:'tex', id, tex } → { id, ok:true, data } | { id, ok:false, message }
|
|
14
|
+
*
|
|
15
|
+
* 线程安全:两个入口都无模块级可变状态;每次调用用独立的 reader / 解码器实例。
|
|
16
|
+
*/
|
|
17
|
+
import { parseS3MB } from '../core/S3MBParser.js';
|
|
18
|
+
import { decodeTextureData } from '../core/TextureDecode.js';
|
|
19
|
+
import { collectTransferables } from './transferables.js';
|
|
20
|
+
const ctx = self;
|
|
21
|
+
/** 纹理解码响应的传输清单:RGBA 像素 buffer + WEBP 的 ImageBitmap 都走零拷贝 */
|
|
22
|
+
function texTransferList(data) {
|
|
23
|
+
const list = collectTransferables(data);
|
|
24
|
+
if (data.bitmap)
|
|
25
|
+
list.push(data.bitmap);
|
|
26
|
+
return list;
|
|
27
|
+
}
|
|
28
|
+
ctx.onmessage = async (e) => {
|
|
29
|
+
const msg = e.data;
|
|
30
|
+
const { id } = msg;
|
|
31
|
+
try {
|
|
32
|
+
if (msg.type === 'parse') {
|
|
33
|
+
const tile = await parseS3MB(msg.buffer);
|
|
34
|
+
// 响应整体过线(零拷贝);请求 buffer 是主线程 structured clone 过来的副本,无需回传
|
|
35
|
+
ctx.postMessage({ id, ok: true, tile }, collectTransferables(tile));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const data = await decodeTextureData(msg.tex);
|
|
39
|
+
ctx.postMessage({ id, ok: true, data }, texTransferList(data));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const permanent = err?.permanent === true;
|
|
44
|
+
const message = String(err?.message ?? err);
|
|
45
|
+
ctx.postMessage({ id, ok: false, message, permanent });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=s3mWorker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3mWorker.js","sourceRoot":"","sources":["../../src/worker/s3mWorker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAY1D,MAAM,GAAG,GAAG,IAAyB,CAAC;AAEtC,4DAA4D;AAC5D,SAAS,eAAe,CAAC,IAAmD;IAC1E,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,IAAI,CAAC,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAgC,CAAC,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,GAAG,CAAC,SAAS,GAAG,KAAK,EAAE,CAAwB,EAAE,EAAE;IACjD,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC;IACnB,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC;IACnB,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACzC,yDAAyD;YACzD,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAA0B,EAAE,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9F,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9C,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAwB,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,SAAS,GAAI,GAAsC,EAAE,SAAS,KAAK,IAAI,CAAC;QAC9E,MAAM,OAAO,GAAG,MAAM,CAAE,GAAoB,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC;QAC9D,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IACzD,CAAC;AACH,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 传输清单收集:递归扫出对象图里所有可 Transfer 的 ArrayBuffer(去重)。
|
|
3
|
+
*
|
|
4
|
+
* 用于 Worker 往返的零拷贝:postMessage 的 transferList 传这个清单后,
|
|
5
|
+
* 线程间共享同一块内存(structured clone 只拷贝「壳」,不拷字节)。
|
|
6
|
+
*
|
|
7
|
+
* 安全性约定:**要么全转,要么不转** —— 同一块 ArrayBuffer 上可能挂着多个视图
|
|
8
|
+
* (positions / normals 常是同一块顶点缓冲的 subarray),transferList 里出现它一次
|
|
9
|
+
* 之后所有视图共享的底层数据整体过线,视图本身仍然有效;但如果只转一半视图、
|
|
10
|
+
* 另一半留在原地,留在原地的会因 detach 变成空数据。所以只允许「把整个对象图
|
|
11
|
+
* 的 buffer 全部收集」或「完全不收集」两种用法,不支持挑着转。
|
|
12
|
+
*/
|
|
13
|
+
export declare function collectTransferables(root: unknown): ArrayBuffer[];
|
|
14
|
+
//# sourceMappingURL=transferables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transferables.d.ts","sourceRoot":"","sources":["../../src/worker/transferables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,EAAE,CAyCjE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 传输清单收集:递归扫出对象图里所有可 Transfer 的 ArrayBuffer(去重)。
|
|
3
|
+
*
|
|
4
|
+
* 用于 Worker 往返的零拷贝:postMessage 的 transferList 传这个清单后,
|
|
5
|
+
* 线程间共享同一块内存(structured clone 只拷贝「壳」,不拷字节)。
|
|
6
|
+
*
|
|
7
|
+
* 安全性约定:**要么全转,要么不转** —— 同一块 ArrayBuffer 上可能挂着多个视图
|
|
8
|
+
* (positions / normals 常是同一块顶点缓冲的 subarray),transferList 里出现它一次
|
|
9
|
+
* 之后所有视图共享的底层数据整体过线,视图本身仍然有效;但如果只转一半视图、
|
|
10
|
+
* 另一半留在原地,留在原地的会因 detach 变成空数据。所以只允许「把整个对象图
|
|
11
|
+
* 的 buffer 全部收集」或「完全不收集」两种用法,不支持挑着转。
|
|
12
|
+
*/
|
|
13
|
+
/** 递归深度上限。ParsedTile 的层级是固定的(tile → geoPackage → geode → package),远用不到 12 层 */
|
|
14
|
+
const MAX_DEPTH = 12;
|
|
15
|
+
export function collectTransferables(root) {
|
|
16
|
+
const buffers = new Set();
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
const walk = (value, depth) => {
|
|
19
|
+
if (value == null || depth > MAX_DEPTH)
|
|
20
|
+
return;
|
|
21
|
+
if (ArrayBuffer.isView(value)) {
|
|
22
|
+
const b = value.buffer;
|
|
23
|
+
if (b instanceof ArrayBuffer)
|
|
24
|
+
buffers.add(b);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (value instanceof ArrayBuffer) {
|
|
28
|
+
buffers.add(value);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value !== 'object')
|
|
32
|
+
return;
|
|
33
|
+
const obj = value;
|
|
34
|
+
if (seen.has(obj))
|
|
35
|
+
return;
|
|
36
|
+
seen.add(obj);
|
|
37
|
+
if (Array.isArray(obj)) {
|
|
38
|
+
for (const item of obj)
|
|
39
|
+
walk(item, depth + 1);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (obj instanceof Map) {
|
|
43
|
+
for (const [k, v] of obj) {
|
|
44
|
+
walk(k, depth + 1);
|
|
45
|
+
walk(v, depth + 1);
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (obj instanceof Set) {
|
|
50
|
+
for (const item of obj)
|
|
51
|
+
walk(item, depth + 1);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const key of Object.keys(obj)) {
|
|
55
|
+
walk(obj[key], depth + 1);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
walk(root, 0);
|
|
59
|
+
return [...buffers];
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=transferables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transferables.js","sourceRoot":"","sources":["../../src/worker/transferables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,+EAA+E;AAC/E,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,MAAM,UAAU,oBAAoB,CAAC,IAAa;IAChD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAe,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,KAAa,EAAQ,EAAE;QACnD,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,SAAS;YAAE,OAAO;QAC/C,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAI,KAAyB,CAAC,MAAM,CAAC;YAC5C,IAAI,CAAC,YAAY,WAAW;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QACD,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO;QACT,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO;QACtC,MAAM,GAAG,GAAG,KAAe,CAAC;QAC5B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,MAAM,IAAI,IAAI,GAAG;gBAAE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QACD,IAAI,GAAG,YAAY,GAAG,EAAE,CAAC;YACvB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;gBACzB,IAAI,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBACnB,IAAI,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YACrB,CAAC;YACD,OAAO;QACT,CAAC;QACD,IAAI,GAAG,YAAY,GAAG,EAAE,CAAC;YACvB,KAAK,MAAM,IAAI,IAAI,GAAG;gBAAE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACnC,IAAI,CAAE,GAA+B,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACzD,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACd,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AACtB,CAAC"}
|
package/docs/API.md
CHANGED
|
@@ -163,6 +163,8 @@ new S3MViewer(options?: S3MViewerOptions)
|
|
|
163
163
|
| `totalMaxTiles` | `number` | — | 多数据集全局驻留瓦片总预算;开启后在可见数据集间动态均衡分配,防止叠加爆显存 |
|
|
164
164
|
| `light` | `LightOptions` | — | 场景光照配置,可配环境光与主平行光的颜色、强度、模式与方位角/高度角 |
|
|
165
165
|
| `maxConcurrent` | `number` | `4` | 每个数据集并发请求数 |
|
|
166
|
+
| `useWorker` | `boolean` | `true` | 用 Web Worker 解析经典 `.s3mb` 瓦片(inflate / Draco / MeshOpt 全在 Worker 跑,主线程只做 fetch 与几何组装);Worker 不可用或崩溃时自动永久降级主线程。S3MBLOCK 路径仍主线程解析 |
|
|
167
|
+
| (随 `useWorker`)纹理解码 | `boolean` | `true` | DXT / CRN / WEBP 纹理解码与 `.s3mb` 解析共用同一个 Worker(`core/TextureDecode.ts` 三方共用同一份实现,WEBP 走可 transfer 的 ImageBitmap);失败自动回主线程,产物逐字节一致 |
|
|
166
168
|
| `lodRangeScale` | `number` | `1.0` | LOD 切换距离缩放系数,> 1 更倾向保留粗模,< 1 提前细化 |
|
|
167
169
|
| `buildTile` | `BuildTileFn \| null` | — | 自定义瓦片构建器(测试 / 服务端渲染时注入) |
|
|
168
170
|
| `attributes` | `AttributeQueryOptions` | — | 业务属性查询配置,配了才能 `colorByField()`,见[按业务字段着色](#按业务字段着色) |
|
|
@@ -291,6 +293,8 @@ flushOverrides(): void // 手动刷脏标记(紧接着读纹理 / 截
|
|
|
291
293
|
getAllMeshes(): THREE.Mesh[] // 当前驻留的全部网格
|
|
292
294
|
```
|
|
293
295
|
|
|
296
|
+
`FeatureStyle.opacity` 支持单体独立透明度(batch 粒度):写入状态纹理第 2 行 alpha,片元里与全局透明度、贴图 alpha 叠乘。约定 `0 < opacity < 1` 生效,`opacity <= 0` 等价 `hidden`,`1` / 不传 = 不覆盖;有淡化单体时该块材质自动开混合,清除后还原为全局透明度语义。与 LOD / 拾取 / 描边兼容(淡化只是改像素 alpha,不参与深度剔除)。
|
|
297
|
+
|
|
294
298
|
### 字段着色 / 过滤 / 抬高
|
|
295
299
|
|
|
296
300
|
```ts
|
package/package.json
CHANGED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3M 纹理解码(three 无关,主线程 / Worker 共用同一份实现)
|
|
3
|
+
*
|
|
4
|
+
* 职责到「解出像素数据」为止:DXT 族解块成 RGBA8、CRN 先用 crunch wasm 转码回 DXT
|
|
5
|
+
* 再解块、WEBP 交给 createImageBitmap、RGB 系逐像素重排。产出是纯数据
|
|
6
|
+
* (DecodedTextureData),包成 three 对象由渲染层的 TextureFactory 负责——
|
|
7
|
+
* 这样整份文件可以原样搬进 Worker 跑。
|
|
8
|
+
*
|
|
9
|
+
* 该走哪条解码路径由 compressType 判定(见 core/TextureFormat.ts):
|
|
10
|
+
* v3.00 的 CRN 纹理 pixelFormat 是 13(ABGR)、行标的 CRN 是 32856(GL_RGBA8),
|
|
11
|
+
* 都不是 26 / 27,所以判定只能看 compressType,不能看 pixelFormat。
|
|
12
|
+
*/
|
|
13
|
+
import DXTTextureDecode from './DXTTextureDecode.js';
|
|
14
|
+
import { transcodeCRN } from './CRNDecode.js';
|
|
15
|
+
import { dxtPixelFormatOf, resolveTextureKind, PixelFormat } from './TextureFormat.js';
|
|
16
|
+
import type { S3MTexture } from './types.js';
|
|
17
|
+
|
|
18
|
+
/** 解码产物:rgba 与 bitmap 二选一,都为 null = 解码失败 / 格式不支持 */
|
|
19
|
+
export interface DecodedTextureData {
|
|
20
|
+
/** 实际走的解码路径('crn->dxt5' / 'dxt1' / 'raw' / 'webp' / 'crn(failed)' / 'unsupported(...)') */
|
|
21
|
+
kind: string;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
/** RGBA8 像素(已按需翻行) */
|
|
25
|
+
rgba: Uint8Array | null;
|
|
26
|
+
/** WEBP 路径的解码结果(ImageBitmap 可 transfer,主线程直接 new THREE.Texture 包) */
|
|
27
|
+
bitmap: ImageBitmap | null;
|
|
28
|
+
hasAlpha: boolean;
|
|
29
|
+
/** CRN 转码失败时的原始错误(渲染层负责打 warn) */
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 行序翻转(原地)。S3MBLOCK 图集按 GL 方向存储(v=0 在最后一行),解码后要翻一次行 */
|
|
34
|
+
function flipRows(rgba: Uint8Array, width: number, height: number): void {
|
|
35
|
+
const row = width * 4;
|
|
36
|
+
const tmp = new Uint8Array(row);
|
|
37
|
+
for (let y = 0; y < height >> 1; y++) {
|
|
38
|
+
const a = y * row;
|
|
39
|
+
const b = (height - 1 - y) * row;
|
|
40
|
+
tmp.set(rgba.subarray(a, a + row));
|
|
41
|
+
rgba.copyWithin(a, b, b + row);
|
|
42
|
+
rgba.set(tmp, b);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 该像素格式自带 alpha 通道(DXT1 的 1bit alpha 不算,历史上没开过 alphaTest) */
|
|
47
|
+
function hasAlphaChannel(pixelFormat: number): boolean {
|
|
48
|
+
return (
|
|
49
|
+
pixelFormat === PixelFormat.DXT3 ||
|
|
50
|
+
pixelFormat === PixelFormat.DXT4 ||
|
|
51
|
+
pixelFormat === PixelFormat.DXT5 ||
|
|
52
|
+
pixelFormat === PixelFormat.ARGB ||
|
|
53
|
+
pixelFormat === PixelFormat.ABGR ||
|
|
54
|
+
pixelFormat === PixelFormat.BGRA ||
|
|
55
|
+
pixelFormat === PixelFormat.RGBA
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 解 DXT 到 RGBA8。DXT5 是 1 字节/像素,DXT1 是 0.5 字节/像素。 */
|
|
60
|
+
/** DXT 解码只需要尺寸 / 格式 / 字节,CRN 转码出来的结果也走这条路 */
|
|
61
|
+
function decodeDXT(tex: Pick<S3MTexture, 'width' | 'height' | 'pixelFormat' | 'data' | 'flipY'>): {
|
|
62
|
+
rgba: Uint8Array;
|
|
63
|
+
hasAlpha: boolean;
|
|
64
|
+
} {
|
|
65
|
+
const { width, height, pixelFormat, data } = tex;
|
|
66
|
+
const out = new Uint8Array(width * height * 4);
|
|
67
|
+
DXTTextureDecode.decode(out, width, height, data, pixelFormat);
|
|
68
|
+
if (tex.flipY) flipRows(out, width, height);
|
|
69
|
+
return { rgba: out, hasAlpha: hasAlphaChannel(pixelFormat) };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function rawRGBA(tex: S3MTexture): { rgba: Uint8Array; hasAlpha: boolean } {
|
|
73
|
+
const { width, height, pixelFormat, data } = tex;
|
|
74
|
+
const out = new Uint8Array(width * height * 4);
|
|
75
|
+
// RGBA / BGRA / ARGB / ABGR 都是 4 字节,前两个是「尾字节是 alpha」,后两个是「首字节是 alpha」
|
|
76
|
+
const hasAlpha =
|
|
77
|
+
pixelFormat === PixelFormat.RGBA ||
|
|
78
|
+
pixelFormat === PixelFormat.BGRA ||
|
|
79
|
+
pixelFormat === PixelFormat.ARGB ||
|
|
80
|
+
pixelFormat === PixelFormat.ABGR;
|
|
81
|
+
const swapRB = pixelFormat === PixelFormat.BGR || pixelFormat === PixelFormat.BGRA;
|
|
82
|
+
const alphaFirst = pixelFormat === PixelFormat.ARGB || pixelFormat === PixelFormat.ABGR;
|
|
83
|
+
const src = hasAlpha ? 4 : 3;
|
|
84
|
+
for (let i = 0, s = 0, d = 0; i < width * height; i++, s += src, d += 4) {
|
|
85
|
+
const p = alphaFirst ? s + 1 : s;
|
|
86
|
+
out[d] = swapRB ? data[p + 2] : data[p];
|
|
87
|
+
out[d + 1] = data[p + 1];
|
|
88
|
+
out[d + 2] = swapRB ? data[p] : data[p + 2];
|
|
89
|
+
out[d + 3] = hasAlpha ? data[alphaFirst ? s : s + 3] : 255;
|
|
90
|
+
}
|
|
91
|
+
if (tex.flipY) flipRows(out, width, height);
|
|
92
|
+
return { rgba: out, hasAlpha: hasAlphaChannel(pixelFormat) };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 主线程 / Worker 共用的解码入口。不抛错:失败与不支持都体现在返回值里 */
|
|
96
|
+
export async function decodeTextureData(tex: S3MTexture): Promise<DecodedTextureData> {
|
|
97
|
+
const empty: DecodedTextureData = {
|
|
98
|
+
kind: 'unsupported(0/0)',
|
|
99
|
+
width: 0,
|
|
100
|
+
height: 0,
|
|
101
|
+
rgba: null,
|
|
102
|
+
bitmap: null,
|
|
103
|
+
hasAlpha: false,
|
|
104
|
+
};
|
|
105
|
+
if (!tex || !tex.width || !tex.height) return empty;
|
|
106
|
+
|
|
107
|
+
const kind = resolveTextureKind(tex.compressType ?? 0, tex.pixelFormat);
|
|
108
|
+
|
|
109
|
+
if (kind === 'crn') {
|
|
110
|
+
try {
|
|
111
|
+
const crn = await transcodeCRN(tex.data);
|
|
112
|
+
const decoded = decodeDXT({
|
|
113
|
+
width: crn.width,
|
|
114
|
+
height: crn.height,
|
|
115
|
+
pixelFormat: crn.dxtFormat === 1 ? PixelFormat.DXT1 : PixelFormat.DXT5,
|
|
116
|
+
data: crn.data,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
kind: `crn->dxt${crn.dxtFormat}`,
|
|
120
|
+
width: crn.width,
|
|
121
|
+
height: crn.height,
|
|
122
|
+
rgba: decoded.rgba,
|
|
123
|
+
bitmap: null,
|
|
124
|
+
hasAlpha: decoded.hasAlpha,
|
|
125
|
+
};
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// 一张纹理坏了不该带崩整个瓦片,如实上报并降级成无贴图
|
|
128
|
+
return {
|
|
129
|
+
kind: 'crn(failed)',
|
|
130
|
+
width: tex.width,
|
|
131
|
+
height: tex.height,
|
|
132
|
+
rgba: null,
|
|
133
|
+
bitmap: null,
|
|
134
|
+
hasAlpha: false,
|
|
135
|
+
error: String((e as Error | null)?.message ?? e),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (kind === 'dxt1' || kind === 'dxt3' || kind === 'dxt4' || kind === 'dxt5') {
|
|
141
|
+
const decoded = decodeDXT({ ...tex, pixelFormat: dxtPixelFormatOf(kind) });
|
|
142
|
+
return { kind, width: tex.width, height: tex.height, rgba: decoded.rgba, bitmap: null, hasAlpha: decoded.hasAlpha };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (kind === 'webp') {
|
|
146
|
+
const blob = new Blob([tex.data as BlobPart], { type: 'image/webp' });
|
|
147
|
+
const bitmap = await createImageBitmap(blob);
|
|
148
|
+
return { kind, width: bitmap.width, height: bitmap.height, rgba: null, bitmap, hasAlpha: true };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (kind === 'raw') {
|
|
152
|
+
const decoded = rawRGBA(tex);
|
|
153
|
+
return { kind, width: tex.width, height: tex.height, rgba: decoded.rgba, bitmap: null, hasAlpha: decoded.hasAlpha };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// KTX2 / ETC 需要额外解码器,先如实上报而不是静默出错
|
|
157
|
+
return { ...empty, kind: `unsupported(${tex.compressType}/${tex.pixelFormat})` };
|
|
158
|
+
}
|
|
@@ -18,8 +18,29 @@
|
|
|
18
18
|
* 真实服务里的 CRN 纹理会被当成未知格式整包丢掉(模型变成无贴图的灰模)。
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* S3M 像素 / 块格式枚举(iClient3D S3MPixelFormat)。
|
|
23
|
+
*
|
|
24
|
+
* 原本住在 render/TextureFactory,纹理解码下沉到 core(Worker 复用)后搬到这里;
|
|
25
|
+
* TextureFactory 保留同名 re-export,对外 API 不变。
|
|
26
|
+
*/
|
|
27
|
+
export const PixelFormat = {
|
|
28
|
+
RGB: 10,
|
|
29
|
+
BGR: 11,
|
|
30
|
+
ARGB: 12,
|
|
31
|
+
ABGR: 13,
|
|
32
|
+
BGRA: 14,
|
|
33
|
+
RGBA: 28,
|
|
34
|
+
WEBP: 25,
|
|
35
|
+
DXT1: 17,
|
|
36
|
+
DXT3: 19,
|
|
37
|
+
DXT4: 20,
|
|
38
|
+
DXT5: 21,
|
|
39
|
+
CRN_DXT5: 26,
|
|
40
|
+
STANDARD_CRN: 27,
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
/** S3M 3.01(行标)compressType 取值 */export const TextureCompress = {
|
|
23
44
|
NONE: 0,
|
|
24
45
|
DXT1_RGB: 33776,
|
|
25
46
|
DXT1_RGBA: 33777,
|
|
@@ -31,6 +31,8 @@ export interface S3MDatasetOptions {
|
|
|
31
31
|
align?: DatasetAlignment;
|
|
32
32
|
/** 垂直高度偏移(米),在 align 对齐基准上的额外偏移,正数抬升,负数下降,默认 0 */
|
|
33
33
|
heightOffset?: number;
|
|
34
|
+
/** 是否用 Web Worker 解析经典 .s3mb 瓦片(默认开,崩溃自动降级主线程),见 S3MTilesetOptions.useWorker */
|
|
35
|
+
useWorker?: boolean;
|
|
34
36
|
/**
|
|
35
37
|
* 是否对该数据集生成几何棱线(CPU 端 EdgesGeometry)。
|
|
36
38
|
* - 不填:跟随全局 `style.edges`
|
|
@@ -194,6 +196,7 @@ export class DatasetManager {
|
|
|
194
196
|
maxConcurrent: maxConcurrent,
|
|
195
197
|
lodRangeScale: ds.lodRangeScale ?? defaultLodRangeScale,
|
|
196
198
|
buildTile: buildTile ?? null,
|
|
199
|
+
useWorker: ds.useWorker,
|
|
197
200
|
uniforms: uniforms,
|
|
198
201
|
onTile: (node) => {
|
|
199
202
|
callbacks.onTile(node, entry);
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* 2. 专题图分析(按业务字段分段着色、连续插值着色、字段过滤、高度拉伸)
|
|
7
7
|
* 3. 2 行状态纹理(uBatchTex)的烘焙与 GPU 上传:
|
|
8
8
|
* - Row 0: [R, G, B, elevation / hidden]
|
|
9
|
-
* - Row 1: [translationX, translationY, translationZ,
|
|
9
|
+
* - Row 1: [translationX, translationY, translationZ, opacity]
|
|
10
|
+
* 约定:alpha 0 = 无单体透明覆盖(shader 不乘),(0,1) 开区间为实际透明度,
|
|
11
|
+
* opacity <= 0 等价 hidden(走 Row 0 的 -1 隐藏位)
|
|
10
12
|
* 4. 批次冲突审计(auditBatches)
|
|
11
13
|
*/
|
|
12
14
|
import * as THREE from 'three';
|
|
@@ -26,7 +28,7 @@ export interface FeatureStyle {
|
|
|
26
28
|
elevation?: number;
|
|
27
29
|
/** 自定义单体三维平移偏移量 [dx, dy, dz](米) */
|
|
28
30
|
translation?: [number, number, number];
|
|
29
|
-
/** 自定义单体透明度(0~1
|
|
31
|
+
/** 自定义单体透明度(0~1,batch 粒度;<= 0 等价 hidden,1 / 不传 = 不覆盖) */
|
|
30
32
|
opacity?: number;
|
|
31
33
|
/** 是否隐藏该单体 */
|
|
32
34
|
hidden?: boolean;
|
|
@@ -562,6 +564,8 @@ export class FeatureStyleManager {
|
|
|
562
564
|
if (mat.userData.uBatchTexActive) {
|
|
563
565
|
mat.userData.uBatchTexActive.value = 0;
|
|
564
566
|
}
|
|
567
|
+
// 单体淡化清除后,把材质 transparent 还原成全局透明度语义
|
|
568
|
+
if (mat.transparent && mat.opacity >= 0.999) mat.transparent = false;
|
|
565
569
|
return;
|
|
566
570
|
}
|
|
567
571
|
|
|
@@ -569,6 +573,7 @@ export class FeatureStyleManager {
|
|
|
569
573
|
if (mat.userData.uBatchTexActive) {
|
|
570
574
|
mat.userData.uBatchTexActive.value = 0;
|
|
571
575
|
}
|
|
576
|
+
if (mat.transparent && mat.opacity >= 0.999) mat.transparent = false;
|
|
572
577
|
return;
|
|
573
578
|
}
|
|
574
579
|
|
|
@@ -611,6 +616,7 @@ export class FeatureStyleManager {
|
|
|
611
616
|
const colorOfBatch = new Map<number, string>();
|
|
612
617
|
const elevOfBatch = new Map<number, number>();
|
|
613
618
|
const translationOfBatch = new Map<number, [number, number, number]>();
|
|
619
|
+
const opacityOfBatch = new Map<number, number>();
|
|
614
620
|
const ownersOfBatch = new Map<number, number>();
|
|
615
621
|
|
|
616
622
|
for (const [smidStr, entry] of Object.entries(pickInfo)) {
|
|
@@ -661,6 +667,16 @@ export class FeatureStyleManager {
|
|
|
661
667
|
continue;
|
|
662
668
|
}
|
|
663
669
|
|
|
670
|
+
// 单体独立透明度:<= 0 等价 hidden;(0,1) 写入状态纹理第 2 行 alpha;>= 1 不覆盖
|
|
671
|
+
const fade = st?.opacity;
|
|
672
|
+
if (fade != null) {
|
|
673
|
+
if (fade <= 0) {
|
|
674
|
+
hiddenBatches.add(bId);
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (fade < 1 && !opacityOfBatch.has(bId)) opacityOfBatch.set(bId, fade);
|
|
678
|
+
}
|
|
679
|
+
|
|
664
680
|
const col = st?.color ?? this.thematicColors.get(smid);
|
|
665
681
|
if (col && !colorOfBatch.has(bId)) colorOfBatch.set(bId, col);
|
|
666
682
|
|
|
@@ -704,6 +720,19 @@ export class FeatureStyleManager {
|
|
|
704
720
|
hasActive = true;
|
|
705
721
|
}
|
|
706
722
|
|
|
723
|
+
// 单体独立透明度写在第 2 行 alpha:shader 里 0 = 无覆盖,不会误伤未设置的单体
|
|
724
|
+
let hasFade = false;
|
|
725
|
+
for (const [bId, fade] of opacityOfBatch) {
|
|
726
|
+
if (hiddenBatches.has(bId)) continue;
|
|
727
|
+
data[(w + bId) * 4 + 3] = fade;
|
|
728
|
+
hasActive = true;
|
|
729
|
+
hasFade = true;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// 有淡化单体时整块材质要开混合才有透明效果;清除后还原为全局透明度语义
|
|
733
|
+
if (hasFade && !mat.transparent) mat.transparent = true;
|
|
734
|
+
else if (!hasFade && mat.transparent && mat.opacity >= 0.999) mat.transparent = false;
|
|
735
|
+
|
|
707
736
|
let conflicts = 0;
|
|
708
737
|
for (const [bId, owners] of ownersOfBatch) {
|
|
709
738
|
if (owners < 2) continue;
|
package/src/render/S3MViewer.ts
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
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 { setMaxAnisotropy } from './TextureFactory.js';
|
|
26
|
+
import { setMaxAnisotropy, setTextureOffThreadDecode } from './TextureFactory.js';
|
|
27
|
+
import { decodeTextureWithWorker } from '../worker/S3MWorkerClient.js';
|
|
27
28
|
import {
|
|
28
29
|
STYLE,
|
|
29
30
|
STYLE_LABELS,
|
|
@@ -193,6 +194,8 @@ export interface S3MViewerOptions {
|
|
|
193
194
|
maxTiles?: number;
|
|
194
195
|
/** 每个数据集并发请求数 */
|
|
195
196
|
maxConcurrent?: number;
|
|
197
|
+
/** 是否用 Web Worker 解析经典 .s3mb 瓦片(默认开,崩溃自动降级主线程),见 S3MTilesetOptions.useWorker */
|
|
198
|
+
useWorker?: boolean;
|
|
196
199
|
/** LOD 切换范围缩放系数(默认 1.0),> 1 更倾向保留粗模,< 1 提前换细模 */
|
|
197
200
|
lodRangeScale?: number;
|
|
198
201
|
/** 自定义瓦片构建器(测试 / 服务端渲染时注入) */
|
|
@@ -401,6 +404,8 @@ export class S3MViewer {
|
|
|
401
404
|
);
|
|
402
405
|
|
|
403
406
|
this.uniforms = createSharedUniforms();
|
|
407
|
+
// 纹理解码优先派给共享解析 Worker(崩溃自动回主线程,见 decodeTextureWithWorker)
|
|
408
|
+
setTextureOffThreadDecode(decodeTextureWithWorker);
|
|
404
409
|
this.style = { ...DEFAULT_STYLE, ...(options.style ?? {}) };
|
|
405
410
|
this.outlineMaterial = createOutlineMaterial(this.uniforms);
|
|
406
411
|
|
|
@@ -604,7 +609,8 @@ export class S3MViewer {
|
|
|
604
609
|
|
|
605
610
|
private _normalizeDatasets(options: S3MViewerOptions): S3MDatasetOptions[] {
|
|
606
611
|
if (Array.isArray(options.datasets)) return options.datasets.filter((d) => !!d?.configUrl);
|
|
607
|
-
if (options.configUrl)
|
|
612
|
+
if (options.configUrl)
|
|
613
|
+
return [{ id: 'default', name: '默认数据集', configUrl: options.configUrl, useWorker: options.useWorker }];
|
|
608
614
|
return [];
|
|
609
615
|
}
|
|
610
616
|
|
|
@@ -499,6 +499,12 @@ const FRAG_COLOR = /* glsl */ `
|
|
|
499
499
|
diffuseColor.rgb = bState.rgb;
|
|
500
500
|
hasCustomColor = true;
|
|
501
501
|
}
|
|
502
|
+
// 单体独立透明度(状态纹理第 2 行 alpha,0 = 无覆盖)。
|
|
503
|
+
// 与全局 uOpacity(diffuseColor.a 初值)和图集贴图自带 alpha 逐级叠乘
|
|
504
|
+
vec4 bOffset = texture2D( uBatchTex, vec2( ( s3mBid + 0.5 ) / uBatchTexWidth, 0.75 ) );
|
|
505
|
+
if ( bOffset.a > 0.0 ) {
|
|
506
|
+
diffuseColor.a *= bOffset.a;
|
|
507
|
+
}
|
|
502
508
|
}
|
|
503
509
|
|
|
504
510
|
// 3. 选中单体高亮 + 其余压暗
|