s3m-three 0.3.6 → 0.3.8
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/anim/Animator.d.ts +111 -0
- package/dist/anim/Animator.d.ts.map +1 -0
- package/dist/anim/Animator.js +382 -0
- package/dist/anim/Animator.js.map +1 -0
- package/dist/anim/Path.d.ts +30 -0
- package/dist/anim/Path.d.ts.map +1 -0
- package/dist/anim/Path.js +100 -0
- package/dist/anim/Path.js.map +1 -0
- package/dist/anim/easing.d.ts +70 -0
- package/dist/anim/easing.d.ts.map +1 -0
- package/dist/anim/easing.js +170 -0
- package/dist/anim/easing.js.map +1 -0
- package/dist/anim/index.d.ts +6 -0
- package/dist/anim/index.d.ts.map +1 -0
- package/dist/anim/index.js +4 -0
- package/dist/anim/index.js.map +1 -0
- package/dist/browser/assets/{s3mWorker-hZY5KqWF.js → s3mWorker-D7AV6O30.js} +1 -1
- package/dist/browser/s3m-three.iife.ext-three.js +60 -22
- package/dist/browser/s3m-three.iife.js +60 -22
- package/dist/core/TextureDecode.d.ts +22 -1
- package/dist/core/TextureDecode.d.ts.map +1 -1
- package/dist/core/TextureDecode.js +47 -3
- package/dist/core/TextureDecode.js.map +1 -1
- package/dist/core/TileGeometry.d.ts +92 -0
- package/dist/core/TileGeometry.d.ts.map +1 -0
- package/dist/core/TileGeometry.js +607 -0
- package/dist/core/TileGeometry.js.map +1 -0
- package/dist/core/types.d.ts +16 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/render/EdgeLines.d.ts +62 -14
- package/dist/render/EdgeLines.d.ts.map +1 -1
- package/dist/render/EdgeLines.js +97 -8
- package/dist/render/EdgeLines.js.map +1 -1
- package/dist/render/FeatureStyleManager.d.ts +17 -1
- package/dist/render/FeatureStyleManager.d.ts.map +1 -1
- package/dist/render/FeatureStyleManager.js +14 -4
- package/dist/render/FeatureStyleManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +8 -10
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +77 -555
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/MergeVertices.d.ts +7 -0
- package/dist/render/MergeVertices.d.ts.map +1 -1
- package/dist/render/MergeVertices.js +10 -1
- package/dist/render/MergeVertices.js.map +1 -1
- package/dist/render/S3MViewer.d.ts +218 -9
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +772 -68
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +19 -0
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +37 -0
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +2 -1
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +30 -6
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +6 -2
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +17 -7
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/worker/S3MWorkerClient.d.ts +12 -4
- package/dist/worker/S3MWorkerClient.d.ts.map +1 -1
- package/dist/worker/S3MWorkerClient.js +12 -8
- package/dist/worker/S3MWorkerClient.js.map +1 -1
- package/dist/worker/s3mWorker.js +11 -3
- package/dist/worker/s3mWorker.js.map +1 -1
- package/docs/ANIMATION.md +534 -0
- package/docs/API.md +4 -0
- package/package.json +1 -1
- package/src/anim/Animator.ts +460 -0
- package/src/anim/Path.ts +107 -0
- package/src/anim/easing.ts +195 -0
- package/src/anim/index.ts +10 -0
- package/src/core/TextureDecode.ts +69 -3
- package/src/core/TileGeometry.ts +745 -0
- package/src/core/types.ts +8 -0
- package/src/index.ts +10 -0
- package/src/render/EdgeLines.ts +131 -15
- package/src/render/FeatureStyleManager.ts +12 -4
- package/src/render/GeometryBuilder.ts +91 -617
- package/src/render/MergeVertices.ts +11 -1
- package/src/render/S3MViewer.ts +866 -67
- package/src/render/StyleMaterial.ts +54 -1
- package/src/render/TextureFactory.ts +42 -8
- package/src/tileset/S3MTileset.ts +25 -9
- package/src/worker/S3MWorkerClient.ts +18 -9
- package/src/worker/s3mWorker.ts +24 -6
|
@@ -115,8 +115,23 @@ export interface S3MStyle {
|
|
|
115
115
|
roofThreshold?: number;
|
|
116
116
|
/** 是否与模型原色贴图叠加混合(0 = 纯色覆盖,1 = 叠乘原色贴图) */
|
|
117
117
|
texMix?: number;
|
|
118
|
+
/**
|
|
119
|
+
* 基础漫反射底色增益 / 直通自发光微调(0~1,默认 0.0,建议开启 TOP_SIDE 时可设 0.15~0.3)。
|
|
120
|
+
* 绕过光照夹角直接补偿表面暗部底色,让 TOP_SIDE / 自定义等模式下的背光阴影面保持通透、不至于过黑。
|
|
121
|
+
*/
|
|
122
|
+
ambientGain?: number;
|
|
118
123
|
/** 半透明时是否保持深度写入(默认 true,保持写入可彻底遮挡背面与内部棱线,避免背面透出) */
|
|
119
124
|
depthWrite?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* 新瓦片首次出现时的淡入时长(毫秒,0 = 关闭)。
|
|
127
|
+
*
|
|
128
|
+
* 只影响「刚加载进来」的瓦片,已经显示的不会被重播。LOD 细化换瓦片时也会淡入,
|
|
129
|
+
* 视觉上比「啪」一下换掉柔和得多。
|
|
130
|
+
*
|
|
131
|
+
* 注意代价:淡入期间该瓦片会进透明渲染队列(混合 + 前后排序),首次加载
|
|
132
|
+
* 几百块瓦片同时淡入时会有一小段额外开销,所以默认关闭、按需开启。
|
|
133
|
+
*/
|
|
134
|
+
fadeIn?: number;
|
|
120
135
|
}
|
|
121
136
|
|
|
122
137
|
export const DEFAULT_STYLE: S3MStyle = {
|
|
@@ -147,7 +162,9 @@ export const DEFAULT_STYLE: S3MStyle = {
|
|
|
147
162
|
edgeThreshold: 55,
|
|
148
163
|
roofThreshold: 0.65,
|
|
149
164
|
texMix: 0,
|
|
165
|
+
ambientGain: 0,
|
|
150
166
|
depthWrite: true,
|
|
167
|
+
fadeIn: 0,
|
|
151
168
|
};
|
|
152
169
|
|
|
153
170
|
export interface StyledMaterial extends THREE.MeshStandardMaterial {
|
|
@@ -157,6 +174,8 @@ export interface StyledMaterial extends THREE.MeshStandardMaterial {
|
|
|
157
174
|
uBatchTex: { value: THREE.Texture };
|
|
158
175
|
uBatchTexWidth: { value: number };
|
|
159
176
|
uBatchTexActive: { value: number };
|
|
177
|
+
/** 单块瓦片的淡入系数(1 = 完全显示) */
|
|
178
|
+
uTileFade: { value: number };
|
|
160
179
|
isStyled: true;
|
|
161
180
|
[key: string]: unknown;
|
|
162
181
|
};
|
|
@@ -205,9 +224,25 @@ export function createSharedUniforms(): StyleUniforms {
|
|
|
205
224
|
uRoofThreshold: { value: 0.65 },
|
|
206
225
|
/** 与模型原色贴图混合强度:0 = 纯色覆盖,1 = 叠乘原色贴图 */
|
|
207
226
|
uTexMix: { value: 0 },
|
|
227
|
+
/** 基础底色增益 / 暗部补偿(0~1,默认 0.0) */
|
|
228
|
+
uAmbientGain: { value: 0 },
|
|
229
|
+
/**
|
|
230
|
+
* 图层整体透明度:在片元里乘进 diffuseColor.a。
|
|
231
|
+
*
|
|
232
|
+
* 为什么不做成 `material.opacity`:那是逐材质写入,几百块瓦片就是 O(meshes),
|
|
233
|
+
* 而且跨 0.999 改 `material.transparent` 会换 program(`opaque` 参与缓存键)。
|
|
234
|
+
* 放到共享 uniform 里,透明度动画就退化成一次 float 赋值。
|
|
235
|
+
*/
|
|
236
|
+
uOpacity: { value: 1 },
|
|
237
|
+
/** 棱线淡入淡出乘子:setEdges 过渡期间 0→1,新插入的瓦片自动继承 */
|
|
238
|
+
uEdgeFade: { value: 1 },
|
|
239
|
+
/**
|
|
240
|
+
* 整体压暗乘子:`mode` 这类不可插值的切换用它做 fade-through
|
|
241
|
+
* (压暗 → 换模式 → 亮回来),比硬切自然得多。
|
|
242
|
+
*/
|
|
243
|
+
uFade: { value: 1 },
|
|
208
244
|
};
|
|
209
245
|
}
|
|
210
|
-
|
|
211
246
|
const VERT_PARS = /* glsl */ `
|
|
212
247
|
attribute float aBatchId;
|
|
213
248
|
attribute vec3 aColor;
|
|
@@ -391,6 +426,10 @@ const FRAG_PARS = /* glsl */ `
|
|
|
391
426
|
uniform float uSelWhole;
|
|
392
427
|
uniform float uRoofThreshold;
|
|
393
428
|
uniform float uTexMix;
|
|
429
|
+
uniform float uAmbientGain;
|
|
430
|
+
uniform float uOpacity;
|
|
431
|
+
uniform float uTileFade;
|
|
432
|
+
uniform float uFade;
|
|
394
433
|
varying float vBatchId;
|
|
395
434
|
varying vec3 vVertColor;
|
|
396
435
|
varying float vWorldZ;
|
|
@@ -430,6 +469,8 @@ const FRAG_PARS = /* glsl */ `
|
|
|
430
469
|
const FRAG_COLOR = /* glsl */ `
|
|
431
470
|
float s3mH = clamp( ( vWorldZ - uHeightMin ) / max( uHeightMax - uHeightMin, 0.001 ), 0.0, 1.0 );
|
|
432
471
|
vec3 s3mBase = diffuseColor.rgb; // 已含 diffuse × map
|
|
472
|
+
// 图层整体透明度(共享 uniform)+ 单瓦片淡入系数(逐材质),逐帧改都不产生重编译
|
|
473
|
+
diffuseColor.a *= uOpacity * uTileFade;
|
|
433
474
|
// 所有按单体取值(配色 / 状态纹理 / 选中判定)都走取整后的 id,避免插值噪声
|
|
434
475
|
float s3mBid = s3mBatchId();
|
|
435
476
|
|
|
@@ -531,6 +572,10 @@ const FRAG_COLOR = /* glsl */ `
|
|
|
531
572
|
} else {
|
|
532
573
|
diffuseColor.rgb *= mix( 1.0, uDimFactor, uDim );
|
|
533
574
|
}
|
|
575
|
+
|
|
576
|
+
// 压暗乘子放在最后:mode 分支会整体覆写 diffuseColor.rgb,
|
|
577
|
+
// 放在前面会被覆盖掉。setMode 的 fade-through 就靠它。
|
|
578
|
+
diffuseColor.rgb *= uFade;
|
|
534
579
|
`;
|
|
535
580
|
|
|
536
581
|
const FRAG_EMISSIVE = /* glsl */ `
|
|
@@ -539,6 +584,10 @@ const FRAG_EMISSIVE = /* glsl */ `
|
|
|
539
584
|
if ( uSelMode != 3 ) {
|
|
540
585
|
totalEmissiveRadiance += uSelColor * s3mSelE * uSelMix * ( uSelEmissive * 0.4 );
|
|
541
586
|
}
|
|
587
|
+
// 直通自发光微调 / 环境光底色提升:补偿背光阴影面(尤其 TOP_SIDE 立面),避免光照阴影面过暗
|
|
588
|
+
if ( uAmbientGain > 0.001 ) {
|
|
589
|
+
totalEmissiveRadiance += diffuseColor.rgb * uAmbientGain;
|
|
590
|
+
}
|
|
542
591
|
`;
|
|
543
592
|
|
|
544
593
|
const FRAG_BAKE = /* glsl */ `
|
|
@@ -596,6 +645,8 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
596
645
|
const uBatchTex = { value: DUMMY_BATCH_TEX };
|
|
597
646
|
const uBatchTexWidth = { value: 1.0 };
|
|
598
647
|
const uBatchTexActive = { value: 0 };
|
|
648
|
+
/** 单块瓦片的淡入系数:逐材质独享(每块网格一份材质),默认 1 = 完全显示 */
|
|
649
|
+
const uTileFade = { value: 1 };
|
|
599
650
|
|
|
600
651
|
// 图集材质:只有几何确实带了 aAtlasRect 才开这个宏,否则缺属性会采到 (0,0) 的默认值
|
|
601
652
|
const atlas = !!opts.atlas && !!opts.map;
|
|
@@ -613,6 +664,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
613
664
|
uBatchTex,
|
|
614
665
|
uBatchTexWidth,
|
|
615
666
|
uBatchTexActive,
|
|
667
|
+
uTileFade,
|
|
616
668
|
uSecMap: { value: secMap },
|
|
617
669
|
uAtlasDim,
|
|
618
670
|
uAtlasDim1,
|
|
@@ -656,6 +708,7 @@ export function createStyledMaterial(opts: StyledMaterialOptions): StyledMateria
|
|
|
656
708
|
mat.userData.uBatchTex = uBatchTex;
|
|
657
709
|
mat.userData.uBatchTexWidth = uBatchTexWidth;
|
|
658
710
|
mat.userData.uBatchTexActive = uBatchTexActive;
|
|
711
|
+
mat.userData.uTileFade = uTileFade;
|
|
659
712
|
mat.userData.isStyled = true;
|
|
660
713
|
return mat;
|
|
661
714
|
}
|
|
@@ -21,6 +21,18 @@ export function setMaxAnisotropy(n: number): void {
|
|
|
21
21
|
maxAnisotropy = Math.max(1, n | 0);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* DXT 压缩直传开关(S3MViewer 在 mount / 上下文恢复时按 GL 能力注入):
|
|
26
|
+
* 需要 WEBGL_compressed_texture_s3tc 与 s3tc_srgb 两个扩展同时可用 ——
|
|
27
|
+
* 纹理是 SRGBColorSpace,缺 srgb 变体会出现贴图发白的颜色失配。
|
|
28
|
+
* 移动端大多没有 s3tc,自动回退 Worker 解码成 RGBA 上传,行为同旧版。
|
|
29
|
+
*/
|
|
30
|
+
let compressedTexturesSupported = false;
|
|
31
|
+
|
|
32
|
+
export function setCompressedTexturesSupported(v: boolean): void {
|
|
33
|
+
compressedTexturesSupported = v;
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
export interface DecodedTexture {
|
|
25
37
|
texture: THREE.Texture | null;
|
|
26
38
|
/** 实际走的解码路径,`unsupported(26)` 这类值会进 stats.skipped */
|
|
@@ -31,20 +43,22 @@ export interface DecodedTexture {
|
|
|
31
43
|
* 注入「离线解码」实现(S3MViewer 装配:走共享解析 Worker)。
|
|
32
44
|
* 返回 null = Worker 不可用 / 崩溃,createTexture 自动退回主线程 decodeTextureData。
|
|
33
45
|
*/
|
|
34
|
-
let offThreadDecode:
|
|
46
|
+
let offThreadDecode:
|
|
47
|
+
| ((tex: S3MTexture, compressed: boolean) => Promise<DecodedTextureData | null>)
|
|
48
|
+
| null = null;
|
|
35
49
|
|
|
36
50
|
export function setTextureOffThreadDecode(
|
|
37
|
-
fn: ((tex: S3MTexture) => Promise<DecodedTextureData | null>) | null
|
|
51
|
+
fn: ((tex: S3MTexture, compressed: boolean) => Promise<DecodedTextureData | null>) | null
|
|
38
52
|
): void {
|
|
39
53
|
offThreadDecode = fn;
|
|
40
54
|
}
|
|
41
55
|
|
|
42
|
-
async function decodeTexture(tex: S3MTexture): Promise<DecodedTextureData> {
|
|
56
|
+
async function decodeTexture(tex: S3MTexture, compressed: boolean): Promise<DecodedTextureData> {
|
|
43
57
|
if (offThreadDecode) {
|
|
44
|
-
const viaWorker = await offThreadDecode(tex);
|
|
58
|
+
const viaWorker = await offThreadDecode(tex, compressed);
|
|
45
59
|
if (viaWorker) return viaWorker;
|
|
46
60
|
}
|
|
47
|
-
return decodeTextureData(tex);
|
|
61
|
+
return decodeTextureData(tex, { compressed });
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
function configure<T extends THREE.Texture>(tex: T, srgb = true): T {
|
|
@@ -87,9 +101,11 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
87
101
|
// 关 GPU mip,层级由 StyleMaterial.s3mAtlasUnit 的内联金字塔自己算。
|
|
88
102
|
const isAtlas = tex.packed === true || tex.flipY === true || tex.composed === true;
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
// 压缩直传只在「宿主 GL 支持 + 非图集」时开:图集要 flipY / 无 mip,压缩上传做不了翻行
|
|
105
|
+
const wantCompressed = compressedTexturesSupported && !isAtlas;
|
|
106
|
+
const data = await decodeTexture(tex, wantCompressed);
|
|
91
107
|
|
|
92
|
-
if (!data.rgba && !data.bitmap) {
|
|
108
|
+
if (!data.rgba && !data.blocks && !data.bitmap) {
|
|
93
109
|
if (data.error) {
|
|
94
110
|
// 一张纹理坏了不该带崩整个瓦片,如实上报并降级成无贴图
|
|
95
111
|
console.warn('[s3m] CRN 转码失败:', data.error);
|
|
@@ -100,12 +116,30 @@ export async function createTexture(tex: S3MTexture | null | undefined): Promise
|
|
|
100
116
|
let texture: THREE.Texture;
|
|
101
117
|
if (data.bitmap) {
|
|
102
118
|
texture = new THREE.Texture(data.bitmap);
|
|
119
|
+
} else if (data.blocks && data.blockFormat) {
|
|
120
|
+
// DXT 块直传:GPU 硬件解压,省 4~8 倍上传带宽与显存。压缩纹理不能 generateMipmaps
|
|
121
|
+
// (WebGL 限制),远处靠 LinearFilter 兜底;s3mHasAlpha 走 alphaTest 的 DXT1 1bit
|
|
122
|
+
// alpha 历史上没开过,DXT5 的 hasAlpha 由 decodeTextureData 判定
|
|
123
|
+
const format =
|
|
124
|
+
data.blockFormat === 'dxt1' ? THREE.RGB_S3TC_DXT1_Format : THREE.RGBA_S3TC_DXT5_Format;
|
|
125
|
+
texture = new THREE.CompressedTexture(
|
|
126
|
+
[{ data: data.blocks, width: data.width, height: data.height }],
|
|
127
|
+
data.width,
|
|
128
|
+
data.height,
|
|
129
|
+
format,
|
|
130
|
+
THREE.UnsignedByteType
|
|
131
|
+
);
|
|
103
132
|
} else {
|
|
104
133
|
const dt = new THREE.DataTexture(data.rgba!, data.width, data.height, THREE.RGBAFormat, THREE.UnsignedByteType);
|
|
105
134
|
if (data.hasAlpha) dt.userData.s3mHasAlpha = true;
|
|
106
135
|
texture = dt;
|
|
107
136
|
}
|
|
108
137
|
texture.needsUpdate = true;
|
|
138
|
+
const configured = isAtlas ? configureAtlas(texture) : configure(texture);
|
|
139
|
+
if (data.blocks) {
|
|
140
|
+
configured.generateMipmaps = false;
|
|
141
|
+
configured.minFilter = THREE.LinearFilter;
|
|
142
|
+
}
|
|
109
143
|
// flipY 为真即 S3MBLOCK 图集(见 core/types.ts),必须无 mip
|
|
110
|
-
return { texture:
|
|
144
|
+
return { texture: configured, decoded: data.kind };
|
|
111
145
|
}
|
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
TexblockRequestPlan,
|
|
28
28
|
} from '../core/S3MBlockParser.js';
|
|
29
29
|
import type { Anchor, ParsedTile, Stats, StyleUniforms } from '../core/types.js';
|
|
30
|
+
import type { TileGeometryData } from '../core/TileGeometry.js';
|
|
30
31
|
import type { Group, Texture } from 'three';
|
|
31
32
|
|
|
32
33
|
function dirOf(url: string): string {
|
|
@@ -226,7 +227,12 @@ export interface TileNode {
|
|
|
226
227
|
/** 瓦片构建器可注入,用于测试 / 服务端渲染(此时不需要 three) */
|
|
227
228
|
export type BuildTileFn = (
|
|
228
229
|
tile: ParsedTile,
|
|
229
|
-
options: {
|
|
230
|
+
options: {
|
|
231
|
+
textureCache: Map<string, Texture>;
|
|
232
|
+
uniforms: StyleUniforms | null;
|
|
233
|
+
/** Worker 侧已完成的纯数据几何组装(经典 .s3mb 路径),构建器直接包装成 three 对象 */
|
|
234
|
+
assembled?: TileGeometryData;
|
|
235
|
+
}
|
|
230
236
|
) => Promise<{ group: Group; stats: Partial<Stats> }>;
|
|
231
237
|
|
|
232
238
|
export interface S3MTilesetOptions {
|
|
@@ -691,17 +697,18 @@ export class S3MTileset {
|
|
|
691
697
|
}
|
|
692
698
|
|
|
693
699
|
/**
|
|
694
|
-
* 经典 `.s3mb` 瓦片解析:优先走 Worker
|
|
695
|
-
*
|
|
700
|
+
* 经典 `.s3mb` 瓦片解析:优先走 Worker(inflate / Draco / MeshOpt / 几何组装
|
|
701
|
+
* 全在 Worker 做,一次往返带回 tile + 纯数据几何),Worker 不可用或已崩溃时
|
|
702
|
+
* 主线程兜底,两条路径输出完全一致。
|
|
696
703
|
*/
|
|
697
|
-
private async _parseClassicTile(buffer: ArrayBuffer): Promise<ParsedTile> {
|
|
704
|
+
private async _parseClassicTile(buffer: ArrayBuffer): Promise<{ tile: ParsedTile; geometry?: TileGeometryData }> {
|
|
698
705
|
if (this._useWorker && this._workerClient === undefined) {
|
|
699
706
|
// 与纹理解码共用进程级单例,避免每个数据集各起一个 Worker
|
|
700
707
|
this._workerClient = getSharedS3MWorker();
|
|
701
708
|
}
|
|
702
709
|
if (this._workerClient) {
|
|
703
710
|
try {
|
|
704
|
-
return await this._workerClient.parseTile(buffer);
|
|
711
|
+
return await this._workerClient.parseTile(buffer, true);
|
|
705
712
|
} catch (e) {
|
|
706
713
|
if (e instanceof WorkerDeadError) {
|
|
707
714
|
// 脚本加载失败 / 崩溃属于环境问题,永久退回主线程;瓦片本身的问题照常抛
|
|
@@ -711,7 +718,7 @@ export class S3MTileset {
|
|
|
711
718
|
}
|
|
712
719
|
}
|
|
713
720
|
}
|
|
714
|
-
return parseS3MB(buffer);
|
|
721
|
+
return { tile: await parseS3MB(buffer) };
|
|
715
722
|
}
|
|
716
723
|
|
|
717
724
|
/**
|
|
@@ -985,6 +992,7 @@ export class S3MTileset {
|
|
|
985
992
|
const isBlock = /\.s3mblock($|\?)/i.test(baseUrl);
|
|
986
993
|
|
|
987
994
|
let tile: ParsedTile;
|
|
995
|
+
let assembled: TileGeometryData | undefined;
|
|
988
996
|
let groupNames: string[] = [];
|
|
989
997
|
const cachedSet = this._groupCache.get(baseUrl);
|
|
990
998
|
if (cachedSet) {
|
|
@@ -1007,7 +1015,9 @@ export class S3MTileset {
|
|
|
1007
1015
|
groupNames = set.names;
|
|
1008
1016
|
tile = set.tiles[groupIndex] ?? set.tiles[0]!;
|
|
1009
1017
|
} else {
|
|
1010
|
-
|
|
1018
|
+
const parsed = await this._parseClassicTile(buffer);
|
|
1019
|
+
tile = parsed.tile;
|
|
1020
|
+
assembled = parsed.geometry;
|
|
1011
1021
|
}
|
|
1012
1022
|
}
|
|
1013
1023
|
if (isBlock) {
|
|
@@ -1017,7 +1027,11 @@ export class S3MTileset {
|
|
|
1017
1027
|
}
|
|
1018
1028
|
|
|
1019
1029
|
const build = await this._builder();
|
|
1020
|
-
const buildRes = await build(tile, {
|
|
1030
|
+
const buildRes = await build(tile, {
|
|
1031
|
+
textureCache: this.textureCache,
|
|
1032
|
+
uniforms: this._uniforms,
|
|
1033
|
+
assembled,
|
|
1034
|
+
});
|
|
1021
1035
|
const { group, stats } = buildRes;
|
|
1022
1036
|
const texKeys = (buildRes as any).textureKeys ?? (group.userData?.textureKeys as string[] | undefined) ?? Object.keys(tile.textures ?? {});
|
|
1023
1037
|
this.registerTileTextures(url, texKeys);
|
|
@@ -1164,7 +1178,9 @@ export class S3MTileset {
|
|
|
1164
1178
|
if (inRange) node.lastSeen = this._frame;
|
|
1165
1179
|
|
|
1166
1180
|
const childUrls = node.childUrls ?? (node.childUrl ? [node.childUrl] : []);
|
|
1167
|
-
|
|
1181
|
+
// 加入 8% 迟滞区间(Hysteresis Band),防止相机在 LOD 临界距离处发生父子瓦片逐帧来回横跳闪烁
|
|
1182
|
+
const hFactor = node.needRefine ? (byDistance ? 1.08 : 0.92) : 1.0;
|
|
1183
|
+
const needRefine = childUrls.length > 0 && (byDistance ? metric < threshold * hFactor : metric > threshold * hFactor);
|
|
1168
1184
|
node.needRefine = needRefine;
|
|
1169
1185
|
|
|
1170
1186
|
const dbgSeen = ((globalThis as { __S3M_LOD_SEEN?: Set<string> }).__S3M_LOD_SEEN ??= new Set<string>());
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { ParsedTile, S3MTexture } from '../core/types.js';
|
|
12
12
|
import type { DecodedTextureData } from '../core/TextureDecode.js';
|
|
13
|
+
import type { TileGeometryData } from '../core/TileGeometry.js';
|
|
13
14
|
|
|
14
15
|
/** Worker 崩溃 / 不可用 —— 与「瓦片数据本身有问题」区分开,调用方据此决定降级还是重试 */
|
|
15
16
|
export class WorkerDeadError extends Error {
|
|
@@ -23,6 +24,7 @@ interface ParseOkResponse {
|
|
|
23
24
|
id: number;
|
|
24
25
|
ok: true;
|
|
25
26
|
tile: ParsedTile;
|
|
27
|
+
geometry?: TileGeometryData;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
interface TexOkResponse {
|
|
@@ -73,7 +75,7 @@ export class S3MWorkerClient {
|
|
|
73
75
|
(err as { permanent?: boolean }).permanent = msg.permanent;
|
|
74
76
|
waiter.reject(err);
|
|
75
77
|
} else if ('tile' in msg) {
|
|
76
|
-
waiter.resolve(msg.tile);
|
|
78
|
+
waiter.resolve({ tile: msg.tile, geometry: msg.geometry });
|
|
77
79
|
} else {
|
|
78
80
|
waiter.resolve(msg.data);
|
|
79
81
|
}
|
|
@@ -82,23 +84,26 @@ export class S3MWorkerClient {
|
|
|
82
84
|
worker.onmessageerror = () => this._die('s3m 解析 Worker 消息无法反序列化');
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
+
/**
|
|
88
|
+
* 解析一块瓦片。buffer 会被 structured clone(不 transfer),主线程保留原 buffer 以便崩溃后降级重解。
|
|
89
|
+
* assemble = true 时顺带在 Worker 里做纯数据几何组装,返回 { tile, geometry } 一次带回。
|
|
90
|
+
*/
|
|
91
|
+
parseTile(buffer: ArrayBuffer, assemble = false, batching = true): Promise<{ tile: ParsedTile; geometry?: TileGeometryData }> {
|
|
87
92
|
if (this.dead) return Promise.reject(new WorkerDeadError('s3m 解析 Worker 已终止'));
|
|
88
93
|
const id = ++this._seq;
|
|
89
|
-
return new Promise<ParsedTile>((resolve, reject) => {
|
|
94
|
+
return new Promise<{ tile: ParsedTile; geometry?: TileGeometryData }>((resolve, reject) => {
|
|
90
95
|
this._pending.set(id, { resolve: resolve as (v: unknown) => void, reject });
|
|
91
|
-
this._worker.postMessage({ type: 'parse', id, buffer });
|
|
96
|
+
this._worker.postMessage({ type: 'parse', id, buffer, assemble, batching });
|
|
92
97
|
});
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
/** 解码一张纹理(DXT / CRN / WEBP / RGB 系)。tex.data 走 structured clone,主线程保留原数据 */
|
|
96
|
-
decodeTexture(tex: S3MTexture): Promise<DecodedTextureData> {
|
|
101
|
+
decodeTexture(tex: S3MTexture, compressed = false): Promise<DecodedTextureData> {
|
|
97
102
|
if (this.dead) return Promise.reject(new WorkerDeadError('s3m 解析 Worker 已终止'));
|
|
98
103
|
const id = ++this._seq;
|
|
99
104
|
return new Promise<DecodedTextureData>((resolve, reject) => {
|
|
100
105
|
this._pending.set(id, { resolve: resolve as (v: unknown) => void, reject });
|
|
101
|
-
this._worker.postMessage({ type: 'tex', id, tex });
|
|
106
|
+
this._worker.postMessage({ type: 'tex', id, tex, compressed });
|
|
102
107
|
});
|
|
103
108
|
}
|
|
104
109
|
|
|
@@ -138,12 +143,16 @@ export function getSharedS3MWorker(): S3MWorkerClient | null {
|
|
|
138
143
|
* 纹理解码的「Worker 优先」入口,供 TextureFactory.setTextureOffThreadDecode 装配。
|
|
139
144
|
* 永不 reject:Worker 不可用 / 崩溃 / 单张解码失败都返回 null,调用方主线程兜底
|
|
140
145
|
* (解码失败本身在 decodeTextureData 里也不抛错,走 crn(failed) 之类的标记)。
|
|
146
|
+
* compressed 由 TextureFactory 按宿主 GL 能力传入,Worker 端据此跳过 DXT 解码。
|
|
141
147
|
*/
|
|
142
|
-
export async function decodeTextureWithWorker(
|
|
148
|
+
export async function decodeTextureWithWorker(
|
|
149
|
+
tex: S3MTexture,
|
|
150
|
+
compressed: boolean
|
|
151
|
+
): Promise<DecodedTextureData | null> {
|
|
143
152
|
const client = getSharedS3MWorker();
|
|
144
153
|
if (!client) return null;
|
|
145
154
|
try {
|
|
146
|
-
return await client.decodeTexture(tex);
|
|
155
|
+
return await client.decodeTexture(tex, compressed);
|
|
147
156
|
} catch {
|
|
148
157
|
return null;
|
|
149
158
|
}
|
package/src/worker/s3mWorker.ts
CHANGED
|
@@ -16,15 +16,24 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { parseS3MB } from '../core/S3MBParser.js';
|
|
18
18
|
import { decodeTextureData } from '../core/TextureDecode.js';
|
|
19
|
+
import { assembleTileGeometry } from '../core/TileGeometry.js';
|
|
19
20
|
import type { ParsedTile, S3MTexture } from '../core/types.js';
|
|
21
|
+
import type { TileGeometryData } from '../core/TileGeometry.js';
|
|
20
22
|
import { collectTransferables } from './transferables.js';
|
|
21
23
|
|
|
22
|
-
type ParseRequest = {
|
|
23
|
-
type
|
|
24
|
+
type ParseRequest = {
|
|
25
|
+
type: 'parse';
|
|
26
|
+
id: number;
|
|
27
|
+
buffer: ArrayBuffer;
|
|
28
|
+
/** 解析后顺带做纯数据几何组装(解交错 / 法线 / 合批),一次往返带回 */
|
|
29
|
+
assemble?: boolean;
|
|
30
|
+
batching?: boolean;
|
|
31
|
+
};
|
|
32
|
+
type TexRequest = { type: 'tex'; id: number; tex: S3MTexture; compressed?: boolean };
|
|
24
33
|
type Request = ParseRequest | TexRequest;
|
|
25
34
|
|
|
26
35
|
type ParseResponse =
|
|
27
|
-
| { id: number; ok: true; tile: ParsedTile }
|
|
36
|
+
| { id: number; ok: true; tile: ParsedTile; geometry?: TileGeometryData }
|
|
28
37
|
| { id: number; ok: false; message: string; permanent: boolean };
|
|
29
38
|
|
|
30
39
|
type TexResponse = { id: number; ok: true; data: Awaited<ReturnType<typeof decodeTextureData>> };
|
|
@@ -44,10 +53,19 @@ ctx.onmessage = async (e: MessageEvent<Request>) => {
|
|
|
44
53
|
try {
|
|
45
54
|
if (msg.type === 'parse') {
|
|
46
55
|
const tile = await parseS3MB(msg.buffer);
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
if (msg.assemble) {
|
|
57
|
+
// 解析 + 组装一起做,避免 tile 回主线程后再整块克隆回来一次
|
|
58
|
+
const geometry = assembleTileGeometry(tile, { batching: msg.batching });
|
|
59
|
+
ctx.postMessage(
|
|
60
|
+
{ id, ok: true, tile, geometry } satisfies ParseResponse,
|
|
61
|
+
[...collectTransferables(tile), ...collectTransferables(geometry)]
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
// 响应整体过线(零拷贝);请求 buffer 是主线程 structured clone 过来的副本,无需回传
|
|
65
|
+
ctx.postMessage({ id, ok: true, tile } satisfies ParseResponse, collectTransferables(tile));
|
|
66
|
+
}
|
|
49
67
|
} else {
|
|
50
|
-
const data = await decodeTextureData(msg.tex);
|
|
68
|
+
const data = await decodeTextureData(msg.tex, { compressed: msg.compressed });
|
|
51
69
|
ctx.postMessage({ id, ok: true, data } satisfies TexResponse, texTransferList(data));
|
|
52
70
|
}
|
|
53
71
|
} catch (err) {
|