s3m-three 0.3.0 → 0.3.1
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/README.md +1 -1
- package/dist/browser/s3m-three.iife.ext-three.js +163 -15
- package/dist/browser/s3m-three.iife.js +190 -42
- package/dist/core/AtlasAlpha.d.ts +31 -0
- package/dist/core/AtlasAlpha.d.ts.map +1 -0
- package/dist/core/AtlasAlpha.js +82 -0
- package/dist/core/AtlasAlpha.js.map +1 -0
- package/dist/core/AtlasPack.d.ts +57 -0
- package/dist/core/AtlasPack.d.ts.map +1 -0
- package/dist/core/AtlasPack.js +81 -0
- package/dist/core/AtlasPack.js.map +1 -0
- package/dist/core/AtlasUpgrade.d.ts +68 -0
- package/dist/core/AtlasUpgrade.d.ts.map +1 -0
- package/dist/core/AtlasUpgrade.js +154 -0
- package/dist/core/AtlasUpgrade.js.map +1 -0
- package/dist/core/DXTTextureDecode.d.ts.map +1 -1
- package/dist/core/DXTTextureDecode.js +28 -6
- package/dist/core/DXTTextureDecode.js.map +1 -1
- package/dist/core/Draco.d.ts +52 -0
- package/dist/core/Draco.d.ts.map +1 -0
- package/dist/core/Draco.js +323 -0
- package/dist/core/Draco.js.map +1 -0
- package/dist/core/MeshOpt.d.ts +14 -3
- package/dist/core/MeshOpt.d.ts.map +1 -1
- package/dist/core/MeshOpt.js +101 -18
- package/dist/core/MeshOpt.js.map +1 -1
- package/dist/core/S3M1Parser.d.ts +16 -0
- package/dist/core/S3M1Parser.d.ts.map +1 -0
- package/dist/core/S3M1Parser.js +367 -0
- package/dist/core/S3M1Parser.js.map +1 -0
- package/dist/core/S3MBParser.d.ts +43 -1
- package/dist/core/S3MBParser.d.ts.map +1 -1
- package/dist/core/S3MBParser.js +101 -23
- package/dist/core/S3MBParser.js.map +1 -1
- package/dist/core/S3MBlockParser.d.ts +209 -0
- package/dist/core/S3MBlockParser.d.ts.map +1 -0
- package/dist/core/S3MBlockParser.js +789 -0
- package/dist/core/S3MBlockParser.js.map +1 -0
- package/dist/core/TextureFormat.d.ts +0 -1
- package/dist/core/TextureFormat.d.ts.map +1 -1
- package/dist/core/TextureFormat.js +19 -2
- package/dist/core/TextureFormat.js.map +1 -1
- package/dist/core/types.d.ts +65 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +5 -3
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +2 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +185 -16
- package/dist/render/GeometryBuilder.js.map +1 -1
- package/dist/render/GroundManager.d.ts +57 -1
- package/dist/render/GroundManager.d.ts.map +1 -1
- package/dist/render/GroundManager.js +149 -21
- package/dist/render/GroundManager.js.map +1 -1
- package/dist/render/Picker.d.ts.map +1 -1
- package/dist/render/Picker.js +12 -4
- package/dist/render/Picker.js.map +1 -1
- package/dist/render/PointCloudMaterial.d.ts +36 -0
- package/dist/render/PointCloudMaterial.d.ts.map +1 -0
- package/dist/render/PointCloudMaterial.js +159 -0
- package/dist/render/PointCloudMaterial.js.map +1 -0
- package/dist/render/S3MViewer.d.ts +19 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +143 -22
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts +6 -0
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +125 -2
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +64 -10
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +152 -1
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +669 -53
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/vendor/crunch_decoder.module.js +12 -18
- package/dist/vendor/draco_decoder.module.js +3178 -0
- package/docs/API.md +110 -9
- package/package.json +1 -1
- package/src/core/AtlasAlpha.ts +82 -0
- package/src/core/AtlasPack.ts +127 -0
- package/src/core/AtlasUpgrade.ts +182 -0
- package/src/core/DXTTextureDecode.ts +28 -6
- package/src/core/Draco.ts +409 -0
- package/src/core/MeshOpt.ts +124 -18
- package/src/core/S3M1Parser.ts +430 -0
- package/src/core/S3MBParser.ts +112 -21
- package/src/core/S3MBlockParser.ts +953 -0
- package/src/core/TextureFormat.ts +20 -2
- package/src/core/types.ts +63 -0
- package/src/index.ts +2 -1
- package/src/render/DatasetManager.ts +5 -5
- package/src/render/GeometryBuilder.ts +207 -15
- package/src/render/GroundManager.ts +174 -20
- package/src/render/Picker.ts +13 -4
- package/src/render/PointCloudMaterial.ts +174 -0
- package/src/render/S3MViewer.ts +140 -24
- package/src/render/StyleMaterial.ts +135 -2
- package/src/render/TextureFactory.ts +67 -11
- package/src/tileset/S3MTileset.ts +701 -58
- package/src/vendor/crunch_decoder.module.js +12 -18
- package/src/vendor/draco_decoder.module.d.ts +128 -0
- package/src/vendor/draco_decoder.module.js +3178 -0
|
@@ -65,6 +65,12 @@ function kindOfPixelFormat(pf: number): TextureKind | null {
|
|
|
65
65
|
|
|
66
66
|
function kindOfCompress(ct: number): TextureKind | null {
|
|
67
67
|
switch (ct) {
|
|
68
|
+
// 实测 iClient3D 数据里 compressType = 14 恒为 DXT5:
|
|
69
|
+
// · 块内嵌图集 ct=14 pf=21(DXT5),512×512 恰好 0.5 B/px 对应 DXT1、1 B/px 对应 DXT5
|
|
70
|
+
// · 独立 .texblock 里的图集 ct=14 pf=13,192×128 = 24576 B,正是 1 B/px 的 DXT5
|
|
71
|
+
// 后者 pf=13 会被 kindOfPixelFormat 判成 raw(ABGR 4 B/px)→ 按 1/4 长度读,整图碎掉。
|
|
72
|
+
case 14:
|
|
73
|
+
return 'dxt5';
|
|
68
74
|
case TextureCompress.DXT1_RGB:
|
|
69
75
|
case TextureCompress.DXT1_RGBA:
|
|
70
76
|
return 'dxt1';
|
|
@@ -97,7 +103,19 @@ export function resolveTextureKind(compressType: number, pixelFormat: number): T
|
|
|
97
103
|
return kindOfPixelFormat(pixelFormat) ?? 'unknown';
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* DXT 族对应的 pixelFormat,喂给 DXTTextureDecode 选块大小 / alpha 编码。
|
|
108
|
+
*
|
|
109
|
+
* 必须逐格式给回真实值:以前除 dxt1 之外一律给 21,DXT3 就被按 DXT5 的插值 alpha 解了
|
|
110
|
+
* (块大小同为 16 B,所以不会越界,但 alpha 是错的)。
|
|
111
|
+
*/
|
|
112
|
+
const DXT_PIXEL_FORMAT: Partial<Record<TextureKind, number>> = {
|
|
113
|
+
dxt1: 17,
|
|
114
|
+
dxt3: 19,
|
|
115
|
+
dxt4: 20,
|
|
116
|
+
dxt5: 21,
|
|
117
|
+
};
|
|
118
|
+
|
|
101
119
|
export function dxtPixelFormatOf(kind: TextureKind): number {
|
|
102
|
-
return kind
|
|
120
|
+
return DXT_PIXEL_FORMAT[kind] ?? 21;
|
|
103
121
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -47,11 +47,27 @@ export interface IndexPackage {
|
|
|
47
47
|
passNames: string[];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** MeshOpt 块解码后的原始流清单(语义名 + 每顶点字节数),来自 parseMeshOptGeometry */
|
|
51
|
+
export interface MeshOptInfo {
|
|
52
|
+
vertexCount: number;
|
|
53
|
+
compressOptions: number;
|
|
54
|
+
minVertices: [number, number, number];
|
|
55
|
+
vertScale: number;
|
|
56
|
+
texCoordScale: number[][];
|
|
57
|
+
texCoordOffset: number[][];
|
|
58
|
+
streams: Array<{ semantic: string; bytes: number; stride: number }>;
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
export interface VertexPackage {
|
|
51
62
|
verticesCount: number;
|
|
52
63
|
vertexAttributes: VertexAttribute[];
|
|
53
64
|
/** semantic → 在 vertexAttributes 中的下标 */
|
|
54
65
|
attrLocation: Record<string, number>;
|
|
66
|
+
/**
|
|
67
|
+
* MeshOpt 骨架的原始流清单(诊断用,也是接自定义属性 / 高斯泼溅属性的入口):
|
|
68
|
+
* 每条流解码后每个顶点占 stride 字节,压缩前的字节数在 bytes 里。
|
|
69
|
+
*/
|
|
70
|
+
meshOptInfo?: MeshOptInfo;
|
|
55
71
|
instanceCount: number;
|
|
56
72
|
/** 16 = 只有矩阵,17/29 = 矩阵 + 颜色 */
|
|
57
73
|
instanceMode: number;
|
|
@@ -89,6 +105,12 @@ export interface GeoPackageEntry {
|
|
|
89
105
|
indexPackages: IndexPackage[];
|
|
90
106
|
/** SMID → batchId + 顶点区间(单体化信息) */
|
|
91
107
|
pickInfo?: Record<number, PickEntry>;
|
|
108
|
+
/**
|
|
109
|
+
* v3 骨架条目末尾自带的 OBB(局部 ENU 系,中心/三个半轴都是 float64)。
|
|
110
|
+
* 多骨架瓦片里每块 geode 的范围各不相同,pageLOD 的包围球只取自其中一块,
|
|
111
|
+
* 要做按块的剔除/贴地就得用它。v2 没有这个字段。
|
|
112
|
+
*/
|
|
113
|
+
obb?: BoundingBox;
|
|
92
114
|
}
|
|
93
115
|
|
|
94
116
|
/* ------------------------------------------------------------------ LOD 树 */
|
|
@@ -123,10 +145,49 @@ export interface S3MTexture {
|
|
|
123
145
|
compressType: number;
|
|
124
146
|
pixelFormat: number;
|
|
125
147
|
data: Uint8Array;
|
|
148
|
+
/**
|
|
149
|
+
* S3MBLOCK 图集为真:数据按 GL 纹理方向(v=0 在最后一行)存储,
|
|
150
|
+
* 解码成 RGBA 后要翻一次行再上传,rect 坐标(offsetY 从 v=0 起)才对得上。
|
|
151
|
+
*/
|
|
152
|
+
flipY?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* 由 AtlasPack 拼合出来的 RGBA 大图(不是文件里的原始 DXT 块)。
|
|
155
|
+
* 这类纹理:① 已是自上而下,flipY 必须为 false;② 不能开 GPU mipmap,
|
|
156
|
+
* 层级由 StyleMaterial 的内联金字塔自己算;③ rect 是拼图坐标系。
|
|
157
|
+
*/
|
|
158
|
+
packed?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* leaf 模式按 `subTexInfos` 把多张精灵贴出来的自上而下 RGBA 画布(可能没进拼图,
|
|
161
|
+
* 只有一张图集时 packAtlases 返回 null)。与 packed 同等对待:flipY=false、
|
|
162
|
+
* 不开 GPU mipmap,层级由 StyleMaterial 的内联金字塔自己算。
|
|
163
|
+
*/
|
|
164
|
+
composed?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* 拼图集合签名(`408x192#id1,id2,...`)。拼图是逐块做的,同一个 atlas id 在不同块里
|
|
167
|
+
* 可能落在不同位置,所以跨瓦片共享纹理时 key 必须带上它,否则会复用别的块的排布。
|
|
168
|
+
*/
|
|
169
|
+
packKey?: string;
|
|
126
170
|
}
|
|
127
171
|
|
|
128
172
|
/* ------------------------------------------------------------------ 瓦片 */
|
|
129
173
|
|
|
174
|
+
/**
|
|
175
|
+
* S3MBLOCK 图集:每个顶点所属子区域的归一化矩形 (x, y, w, h)。
|
|
176
|
+
* 未被绑定覆盖的顶点是 (0, 0, 1, 1),即整张图集。
|
|
177
|
+
* uv 对应材质 textureunit 0,uv2 对应 unit 1(官方叫 BaseTex / BakeTex)。
|
|
178
|
+
*/
|
|
179
|
+
export interface S3MAtlasRects {
|
|
180
|
+
uv?: Float32Array;
|
|
181
|
+
uv2?: Float32Array;
|
|
182
|
+
/**
|
|
183
|
+
* 每个顶点所属的绑定组 id(= 块文件里「哪段顶点用哪张子纹理」)。
|
|
184
|
+
* 生成 UV 时按它分组,一组一面墙 → 一张精灵,跟官方 batch 表的语义一致。
|
|
185
|
+
*/
|
|
186
|
+
group?: Float32Array;
|
|
187
|
+
/** 每个顶点所属子区域的像素尺寸 (w, h):决定这段 uv 该怎么平铺 */
|
|
188
|
+
groupDim?: Float32Array;
|
|
189
|
+
}
|
|
190
|
+
|
|
130
191
|
export interface ParsedTile {
|
|
131
192
|
version: number;
|
|
132
193
|
compressedType: number;
|
|
@@ -134,6 +195,8 @@ export interface ParsedTile {
|
|
|
134
195
|
groupNode: GroupNode;
|
|
135
196
|
geoPackage: Record<string, GeoPackageEntry>;
|
|
136
197
|
textures: Record<string, S3MTexture>;
|
|
198
|
+
/** 仅 S3MBLOCK:图集子区域矩形(按骨架名) */
|
|
199
|
+
atlasRects?: Record<string, S3MAtlasRects>;
|
|
137
200
|
materials: unknown;
|
|
138
201
|
pickInfo: unknown;
|
|
139
202
|
extensions?: unknown;
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
// —— 解析层(Node 可用,不依赖 three / WebGL)
|
|
13
|
-
export { parseS3MB, S3M_VERSION, VertexTag, VertexTagV3 } from './core/S3MBParser.js';
|
|
13
|
+
export { parseS3MB, S3MFormatError, S3M_VERSION, VertexTag, VertexTagV3 } from './core/S3MBParser.js';
|
|
14
|
+
export { parseS3M1, isS3M1Buffer } from './core/S3M1Parser.js';
|
|
14
15
|
export type { ParseOptions, InflateFn } from './core/S3MBParser.js';
|
|
15
16
|
export { BinaryReader } from './core/BinaryReader.js';
|
|
16
17
|
export { resolveFeature, measureSegments, buildAttributes, DEFAULT_FLOOR_HEIGHT } from './core/FeatureInfo.js';
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 4. 多数据集全局经纬度包围盒计算(getBounds)
|
|
9
9
|
*/
|
|
10
10
|
import * as THREE from 'three';
|
|
11
|
-
import { S3MTileset } from '../tileset/S3MTileset.js';
|
|
11
|
+
import { S3MTileset, usableGeoBounds } from '../tileset/S3MTileset.js';
|
|
12
12
|
import type { Anchor } from '../core/types.js';
|
|
13
13
|
import { worldFromLngLat, type GeoAdapter } from './GeoAdapter.js';
|
|
14
14
|
|
|
@@ -291,10 +291,10 @@ export class DatasetManager {
|
|
|
291
291
|
|
|
292
292
|
for (const entry of this.datasets.values()) {
|
|
293
293
|
if (!entry.visible || !entry.tileset?.config) continue;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (b
|
|
294
|
+
// 只认「确实是一张经纬度范围」的 geoBounds:OSGB/SuperMapCache 那类配置只给米制
|
|
295
|
+
// 局部包围盒,全 0 的退化框拿去 fitBounds 会把视角甩到几内亚湾
|
|
296
|
+
const b = usableGeoBounds(entry.tileset.config.geoBounds);
|
|
297
|
+
if (b) {
|
|
298
298
|
minLon = Math.min(minLon, b.left);
|
|
299
299
|
minLat = Math.min(minLat, b.bottom);
|
|
300
300
|
maxLon = Math.max(maxLon, b.right);
|
|
@@ -35,11 +35,55 @@ import type {
|
|
|
35
35
|
*/
|
|
36
36
|
export const LINE_PRIMITIVE = 13;
|
|
37
37
|
|
|
38
|
+
/** primitiveType = 1:点云包。索引是点序号,画成三角面就是满屏彩色尖刺。 */
|
|
39
|
+
export const POINT_PRIMITIVE = 1;
|
|
40
|
+
|
|
38
41
|
function isLinePrimitive(primitiveType: number): boolean {
|
|
39
42
|
// 1 = 点,2 = 线段,3 = 折线,13 = 棱线包
|
|
40
43
|
return primitiveType === LINE_PRIMITIVE || primitiveType === 2 || primitiveType === 3;
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
function isPointPrimitive(primitiveType: number): boolean {
|
|
47
|
+
return primitiveType === POINT_PRIMITIVE;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ------------------------------------------------------------------ 图集 UV */
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* S3MBLOCK 图集的 UV 一律用几何自带的 uv0 原值,这里不再做任何重算。
|
|
54
|
+
*
|
|
55
|
+
* 走过三轮弯路才定下来(每一条都不要再试):
|
|
56
|
+
* 1. 「按世界尺度 fract」—— uv0 是建模工具留下的逐岛坐标,不是世界投影,
|
|
57
|
+
* 全局跨度 279 只是所有岛拼起来的包围盒,拿它除以瓦片尺寸算出的
|
|
58
|
+
* 「1 个 uv 周期 = 1.65 m」是错的;
|
|
59
|
+
* 2. 「按 binding 分组、逐面 0..1」—— 等于把作者排好的映射丢掉,
|
|
60
|
+
* 一面墙强行铺一整张精灵,长墙被横向拉糊;
|
|
61
|
+
* 3. 「按 uv 跨度 ÷ 精灵像素取整重复」—— 同上,而且依赖精灵像素尺寸这个
|
|
62
|
+
* 与几何无关的量。
|
|
63
|
+
*
|
|
64
|
+
* 官方引擎的做法(iClient3D 12.1.1,VS 里 getUVMatrixForBatch / FS 里
|
|
65
|
+
* czm_getTexColorForS3M)就是:fract(uv0) 直接乘子区域尺寸再平移到子区域,
|
|
66
|
+
* 一个 uv 周期 = 一张精灵,作者在 uv0 里已经把重复次数写死了。
|
|
67
|
+
* 唯一需要额外处理的是子区域右 1/3 那条内联 mip 链,见 StyleMaterial.s3mAtlasSample。
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 点云共享材质:顶点色直接上屏,不受光照影响。
|
|
73
|
+
* 固定像素尺寸(sizeAttenuation: false),远近点大小一致,高空浏览时不会缩成一团。
|
|
74
|
+
*/
|
|
75
|
+
let pointCloudMaterial: THREE.PointsMaterial | null = null;
|
|
76
|
+
function getPointCloudMaterial(): THREE.PointsMaterial {
|
|
77
|
+
if (!pointCloudMaterial) {
|
|
78
|
+
pointCloudMaterial = new THREE.PointsMaterial({
|
|
79
|
+
size: 2,
|
|
80
|
+
sizeAttenuation: false,
|
|
81
|
+
vertexColors: true,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return pointCloudMaterial;
|
|
85
|
+
}
|
|
86
|
+
|
|
43
87
|
/* ------------------------------------------------------------------ 顶点流 */
|
|
44
88
|
|
|
45
89
|
function bytesPerElement(dtype: AttrDType): number {
|
|
@@ -140,6 +184,9 @@ function toColorAttribute(attr: VertexAttribute | null, vertexCount: number): TH
|
|
|
140
184
|
return new THREE.BufferAttribute(out, 3);
|
|
141
185
|
}
|
|
142
186
|
|
|
187
|
+
/** SuperMap 内置水面(`103.water`)的基色:偏深的青蓝,配合低粗糙度出一点天光反射 */
|
|
188
|
+
const WATER_COLOR = new THREE.Color(0x1d4e63);
|
|
189
|
+
|
|
143
190
|
/* ------------------------------------------------------------------ 材质描述 */
|
|
144
191
|
|
|
145
192
|
interface RawMaterialUnit {
|
|
@@ -157,6 +204,7 @@ interface RawMaterialDef {
|
|
|
157
204
|
diffuse?: { r?: number; g?: number; b?: number; a?: number };
|
|
158
205
|
shininess?: number;
|
|
159
206
|
alphaMode?: string;
|
|
207
|
+
alphaCutoff?: number;
|
|
160
208
|
cullMode?: string;
|
|
161
209
|
transparentsorting?: boolean;
|
|
162
210
|
textureStates?: RawMaterialUnit[];
|
|
@@ -280,11 +328,30 @@ export async function buildTileObjects(
|
|
|
280
328
|
group.userData.hasNativeEdges = false;
|
|
281
329
|
|
|
282
330
|
// 1) 纹理
|
|
331
|
+
//
|
|
332
|
+
// 缓存 key 不能只用 atlas id:拼图(AtlasPack)是**逐块**做的,同一个 id 在 LOD 兄弟块里
|
|
333
|
+
// 参与的拼图集合可能不同、落位也不同。只按 id 复用会把别块的排布拿过来,
|
|
334
|
+
// 而 aAtlasRect 是本块的 —— 整块贴错图。带 packKey 的按「id#集合签名」隔离。
|
|
335
|
+
const texKeyOf = (code: string): string => {
|
|
336
|
+
const t = (tile.textures as Record<string, { packKey?: string }> | undefined)?.[code];
|
|
337
|
+
return t?.packKey ? `${code}#${t.packKey}` : code;
|
|
338
|
+
};
|
|
339
|
+
|
|
283
340
|
for (const [code, tex] of Object.entries(tile.textures ?? {})) {
|
|
284
|
-
|
|
341
|
+
// 同一个图集 id 会被很多瓦片共用:先到的那块可能只拿到内嵌缩略图(120x184),
|
|
342
|
+
// 后面的块换成了高清版(240x304)。只按 id 判「缓存已有」会让高清永远进不来,
|
|
343
|
+
// 满屏都是马赛克。所以尺寸变大时必须重解码并覆盖。
|
|
344
|
+
const key = texKeyOf(code);
|
|
345
|
+
const prev = textureByCode.get(key);
|
|
346
|
+
if (prev) {
|
|
347
|
+
const img = prev.image as { width?: number; height?: number } | undefined;
|
|
348
|
+
const prevPixels = (img?.width ?? 0) * (img?.height ?? 0);
|
|
349
|
+
if (prevPixels >= (tex.width ?? 0) * (tex.height ?? 0)) continue;
|
|
350
|
+
textureByCode.delete(key);
|
|
351
|
+
}
|
|
285
352
|
const { texture, decoded } = await createTexture(tex);
|
|
286
353
|
if (texture) {
|
|
287
|
-
textureByCode.set(
|
|
354
|
+
textureByCode.set(key, texture);
|
|
288
355
|
stats.textures++;
|
|
289
356
|
} else {
|
|
290
357
|
stats.skipped.push(`texture ${code} → ${decoded}`);
|
|
@@ -299,12 +366,19 @@ export async function buildTileObjects(
|
|
|
299
366
|
// 材质对象本身很轻(program 由 customProgramCacheKey 复用),一栋一个不心疼。
|
|
300
367
|
const materialList = resolveMaterialList(tile.materials);
|
|
301
368
|
|
|
302
|
-
const makeMaterial = (code?: string): StyledMaterial => {
|
|
369
|
+
const makeMaterial = (code?: string, hasAtlasRect = false): StyledMaterial => {
|
|
303
370
|
const entry = materialList.find((e) => materialName(unwrapMaterial(e)) === code);
|
|
304
371
|
const m = unwrapMaterial(entry);
|
|
305
372
|
const units = materialUnits(m);
|
|
306
373
|
const unit0 = units[0];
|
|
307
|
-
let map = unit0 ? (textureByCode.get(materialName(unit0) ?? unit0.id ?? '') ?? null) : null;
|
|
374
|
+
let map = unit0 ? (textureByCode.get(texKeyOf(materialName(unit0) ?? unit0.id ?? '')) ?? null) : null;
|
|
375
|
+
// S3MBLOCK 的烘焙图(unit1 / BakeTex)是光照烘焙,官方与 base 相乘;缺它建筑就是一片平白
|
|
376
|
+
const unit1 = units[1];
|
|
377
|
+
const noBake = (globalThis as { __S3M_NO_BAKE?: boolean }).__S3M_NO_BAKE === true;
|
|
378
|
+
const secMap =
|
|
379
|
+
hasAtlasRect && unit1 && !noBake
|
|
380
|
+
? (textureByCode.get(texKeyOf(materialName(unit1) ?? unit1.id ?? '')) ?? null)
|
|
381
|
+
: null;
|
|
308
382
|
|
|
309
383
|
// 检查并应用 texmodmatrix 纹理变换矩阵
|
|
310
384
|
const texMod = unit0?.texmodmatrix ?? unit0?.matrix;
|
|
@@ -337,20 +411,36 @@ export async function buildTileObjects(
|
|
|
337
411
|
|
|
338
412
|
const diffuse = m?.diffuse ?? { r: 1, g: 1, b: 1, a: 1 };
|
|
339
413
|
const alphaMode = m?.alphaMode;
|
|
340
|
-
|
|
414
|
+
// SuperMap 的内置水面:纹理单元 id 是 `103.water`,块里**根本没有像素**
|
|
415
|
+
// (实测 Waters 的 s3mb 只有 1081 字节,textures = 0,材质 diffuse 还是纯白)。
|
|
416
|
+
// 以前当白色漫反射渲染,整片湖就是一块白板。这里按内置水面材质上色。
|
|
417
|
+
const isWater = [unit0, unit1].some((u) => /\.water$/i.test(String(materialName(u) ?? u?.id ?? '')));
|
|
418
|
+
const transparent =
|
|
419
|
+
alphaMode === 'AM_BLEND' || (diffuse.a ?? 1) < 0.999 || m?.transparentsorting === true || (isWater && !map);
|
|
341
420
|
|
|
342
421
|
// S3M 的 shininess 是旧式 0~128 高光指数,粗略映射到粗糙度
|
|
343
422
|
const roughness =
|
|
344
423
|
typeof m?.shininess === 'number' && m.shininess > 0 ? Math.max(0.15, 1 - m.shininess / 128) : 0.95;
|
|
345
424
|
|
|
425
|
+
// 镂空图集(S3MBLOCK):建筑照片是异形 alpha 抠图,轮廓顶点会采到 alpha=0 的像素,
|
|
426
|
+
// 不丢弃的话就是成片的黑面。cutoff 取材质自带的 alphaCutoff(实测 0.01)。
|
|
427
|
+
// rect 是图集里的原始子区域(含右 1/3 的内联 mip 列),不做任何裁切,
|
|
428
|
+
// 这里保留 alphaTest 只为挡掉轮廓外的零星像素,不会再出现整面墙被抠空。
|
|
429
|
+
const alphaTest = (map as THREE.Texture | null)?.userData?.s3mHasAlpha
|
|
430
|
+
? Math.max((m?.alphaCutoff as number) ?? 0, 0.01)
|
|
431
|
+
: 0;
|
|
432
|
+
|
|
346
433
|
const mat = createStyledMaterial({
|
|
347
434
|
uniforms,
|
|
348
435
|
map,
|
|
349
|
-
color: new THREE.Color(diffuse.r ?? 1, diffuse.g ?? 1, diffuse.b ?? 1),
|
|
436
|
+
color: isWater && !map ? WATER_COLOR.clone() : new THREE.Color(diffuse.r ?? 1, diffuse.g ?? 1, diffuse.b ?? 1),
|
|
350
437
|
side: resolveSide(m),
|
|
351
438
|
transparent,
|
|
352
|
-
opacity: diffuse.a ?? 1,
|
|
353
|
-
roughness,
|
|
439
|
+
opacity: isWater && !map ? 0.86 : (diffuse.a ?? 1),
|
|
440
|
+
roughness: isWater && !map ? 0.12 : roughness,
|
|
441
|
+
alphaTest,
|
|
442
|
+
atlas: hasAtlasRect && !!map,
|
|
443
|
+
secMap,
|
|
354
444
|
});
|
|
355
445
|
return mat;
|
|
356
446
|
};
|
|
@@ -388,21 +478,61 @@ export async function buildTileObjects(
|
|
|
388
478
|
? toBufferAttribute(batchAttr)
|
|
389
479
|
: new THREE.BufferAttribute(new Float32Array(vertexCount).fill(-1), 1);
|
|
390
480
|
|
|
481
|
+
// S3MBLOCK 图集:逐顶点带子区域矩形,平铺的 fract 交给片元做(顶点上做会画反)。
|
|
482
|
+
// 烘焙图(材质 unit1)用第二套原始 UV,这里另开一个 aSecUv 槽位,不跟 three 的 uv1 语义纠缠。
|
|
483
|
+
const rects = tile.atlasRects?.[name];
|
|
484
|
+
if (rects) {
|
|
485
|
+
attributes.aAtlasRect = new THREE.BufferAttribute(rects.uv ?? new Float32Array(vertexCount * 4), 4);
|
|
486
|
+
// 没有第二套 UV 的骨架不做烘焙:给一个退化矩形,片元里据此跳过
|
|
487
|
+
const uv1 = attributes.uv1 as THREE.BufferAttribute | undefined;
|
|
488
|
+
if (rects.uv2 && uv1) {
|
|
489
|
+
attributes.aAtlasRect1 = new THREE.BufferAttribute(rects.uv2, 4);
|
|
490
|
+
attributes.aSecUv = uv1;
|
|
491
|
+
} else {
|
|
492
|
+
attributes.aAtlasRect1 = new THREE.BufferAttribute(new Float32Array(vertexCount * 4), 4);
|
|
493
|
+
attributes.aSecUv = (attributes.uv as THREE.BufferAttribute) ?? new THREE.BufferAttribute(new Float32Array(vertexCount * 2), 2);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
391
497
|
const instanced = vp.instanceIndex !== -1 && (vp.instanceMode === 16 || vp.instanceMode === 17);
|
|
392
498
|
// 注意:indexPackages 属于 Skeleton(geoPackage[name]),不在 vertexPackage 里
|
|
393
|
-
|
|
499
|
+
//
|
|
500
|
+
// 一个索引包都没有的骨架 = 纯点集:高斯泼溅(3DGaussianSplatting)和无索引点云
|
|
501
|
+
// 都是这种——顶点就是点,没有任何三角形可画。不补一个「顶点即点」的伪包,
|
|
502
|
+
// 下面就不会产出任何对象,整块瓦片最后落到「tile 无可用几何」被丢掉。
|
|
503
|
+
let packs = geo.indexPackages ?? [];
|
|
504
|
+
if (packs.length === 0 && vertexCount > 0) {
|
|
505
|
+
packs = [
|
|
506
|
+
{
|
|
507
|
+
indexCount: 0,
|
|
508
|
+
indexType: 0,
|
|
509
|
+
useIndex: 0,
|
|
510
|
+
primitiveType: POINT_PRIMITIVE,
|
|
511
|
+
indices: null,
|
|
512
|
+
passNames: [],
|
|
513
|
+
},
|
|
514
|
+
];
|
|
515
|
+
}
|
|
516
|
+
skeletonInfo.set(name, { indexPackages: packs, instanced });
|
|
394
517
|
stats.vertices += vertexCount;
|
|
395
518
|
|
|
396
|
-
|
|
397
|
-
|
|
519
|
+
packs.forEach((pack, pi) => {
|
|
520
|
+
let geometry = new THREE.BufferGeometry();
|
|
398
521
|
for (const [k, v] of Object.entries(attributes)) geometry.setAttribute(k, v);
|
|
399
522
|
const idx = toIndex(pack);
|
|
400
523
|
if (idx) geometry.setIndex(idx);
|
|
401
524
|
else geometry.setDrawRange(0, vertexCount);
|
|
402
525
|
|
|
526
|
+
|
|
527
|
+
|
|
403
528
|
// 如果数据本身没有法线且是三角网格,必须在绑定了 index 之后计算法线
|
|
404
529
|
// 绝不能在未索引的 attributes.position 上算,否则非连续顶点会导致法线朝向错乱甚至越界 NaN 导致死黑面
|
|
405
|
-
|
|
530
|
+
// 线 / 点包不需要法线
|
|
531
|
+
if (
|
|
532
|
+
!isLinePrimitive(pack.primitiveType) &&
|
|
533
|
+
!isPointPrimitive(pack.primitiveType) &&
|
|
534
|
+
!geometry.getAttribute('normal')
|
|
535
|
+
) {
|
|
406
536
|
geometry.computeVertexNormals();
|
|
407
537
|
}
|
|
408
538
|
|
|
@@ -435,6 +565,7 @@ export async function buildTileObjects(
|
|
|
435
565
|
let hasUv1 = false;
|
|
436
566
|
let hasAColor = false;
|
|
437
567
|
let hasABatchId = false;
|
|
568
|
+
let hasAtlas = false;
|
|
438
569
|
|
|
439
570
|
for (const c of candidates) {
|
|
440
571
|
const g = c.cached.geometry;
|
|
@@ -446,6 +577,7 @@ export async function buildTileObjects(
|
|
|
446
577
|
if (g.attributes.uv1) hasUv1 = true;
|
|
447
578
|
if (g.attributes.aColor) hasAColor = true;
|
|
448
579
|
if (g.attributes.aBatchId) hasABatchId = true;
|
|
580
|
+
if (g.attributes.aAtlasRect) hasAtlas = true;
|
|
449
581
|
}
|
|
450
582
|
|
|
451
583
|
const mergedPos = new Float32Array(totalVertices * 3);
|
|
@@ -454,6 +586,31 @@ export async function buildTileObjects(
|
|
|
454
586
|
const mergedUv1 = hasUv1 ? new Float32Array(totalVertices * 2) : null;
|
|
455
587
|
const mergedColor = hasAColor ? new Float32Array(totalVertices * 3) : null;
|
|
456
588
|
const mergedBatchId = hasABatchId ? new Float32Array(totalVertices) : null;
|
|
589
|
+
const mergedRect0 = hasAtlas ? new Float32Array(totalVertices * 4) : null;
|
|
590
|
+
const mergedRect1 = hasAtlas ? new Float32Array(totalVertices * 4) : null;
|
|
591
|
+
const mergedSecUv = hasAtlas ? new Float32Array(totalVertices * 2) : null;
|
|
592
|
+
|
|
593
|
+
/** 逐顶点拷一个 n 分量的浮点属性;缺失时用 fallbackRect / 0 兜底 */
|
|
594
|
+
const copyAttr = (
|
|
595
|
+
dst: Float32Array,
|
|
596
|
+
attr: THREE.BufferAttribute | THREE.InterleavedBufferAttribute | undefined,
|
|
597
|
+
comp: number,
|
|
598
|
+
vCount: number,
|
|
599
|
+
vertexOffset: number,
|
|
600
|
+
fallback?: (i: number, out: number[]) => void
|
|
601
|
+
) => {
|
|
602
|
+
if (attr) {
|
|
603
|
+
const src = attr.array as ArrayLike<number>;
|
|
604
|
+
for (let i = 0; i < vCount; i++)
|
|
605
|
+
for (let k = 0; k < comp; k++) dst[(vertexOffset + i) * comp + k] = src[i * comp + k] ?? 0;
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
for (let i = 0; i < vCount; i++) {
|
|
609
|
+
const out: number[] = [];
|
|
610
|
+
fallback ? fallback(i, out) : out.push(...new Array(comp).fill(0));
|
|
611
|
+
for (let k = 0; k < comp; k++) dst[(vertexOffset + i) * comp + k] = out[k] ?? 0;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
457
614
|
|
|
458
615
|
const IndexArrayType = totalVertices > 65535 ? Uint32Array : Uint16Array;
|
|
459
616
|
const mergedIndices = new IndexArrayType(totalIndices);
|
|
@@ -582,7 +739,14 @@ export async function buildTileObjects(
|
|
|
582
739
|
}
|
|
583
740
|
}
|
|
584
741
|
|
|
585
|
-
// 7.
|
|
742
|
+
// 7. 图集矩形(S3MBLOCK)
|
|
743
|
+
if (mergedRect0) {
|
|
744
|
+
copyAttr(mergedRect0, g.attributes.aAtlasRect, 4, vCount, vertexOffset, (_i, out) => out.push(0, 0, 0, 0));
|
|
745
|
+
copyAttr(mergedRect1!, g.attributes.aAtlasRect1, 4, vCount, vertexOffset, (_i, out) => out.push(0, 0, 0, 0));
|
|
746
|
+
copyAttr(mergedSecUv!, g.attributes.aSecUv, 2, vCount, vertexOffset);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// 8. Indices
|
|
586
750
|
if (g.index) {
|
|
587
751
|
const idxArr = g.index.array as ArrayLike<number>;
|
|
588
752
|
const iCount = g.index.count;
|
|
@@ -607,6 +771,11 @@ export async function buildTileObjects(
|
|
|
607
771
|
if (mergedUv1) mergedGeo.setAttribute('uv1', new THREE.BufferAttribute(mergedUv1, 2));
|
|
608
772
|
if (mergedColor) mergedGeo.setAttribute('aColor', new THREE.BufferAttribute(mergedColor, 3));
|
|
609
773
|
if (mergedBatchId) mergedGeo.setAttribute('aBatchId', new THREE.BufferAttribute(mergedBatchId, 1));
|
|
774
|
+
if (mergedRect0) {
|
|
775
|
+
mergedGeo.setAttribute('aAtlasRect', new THREE.BufferAttribute(mergedRect0, 4));
|
|
776
|
+
mergedGeo.setAttribute('aAtlasRect1', new THREE.BufferAttribute(mergedRect1!, 4));
|
|
777
|
+
mergedGeo.setAttribute('aSecUv', new THREE.BufferAttribute(mergedSecUv!, 2));
|
|
778
|
+
}
|
|
610
779
|
mergedGeo.setIndex(new THREE.BufferAttribute(mergedIndices, 1));
|
|
611
780
|
mergedGeo.computeBoundingBox();
|
|
612
781
|
mergedGeo.computeBoundingSphere();
|
|
@@ -632,7 +801,7 @@ export async function buildTileObjects(
|
|
|
632
801
|
matrix: THREE.Matrix4 | null
|
|
633
802
|
): THREE.Object3D => {
|
|
634
803
|
const { geometry, pack } = cached;
|
|
635
|
-
const material = makeMaterial(pack.passNames?.[0]);
|
|
804
|
+
const material = makeMaterial(pack.passNames?.[0], !!geometry.getAttribute('aAtlasRect'));
|
|
636
805
|
let object: THREE.Mesh | THREE.InstancedMesh;
|
|
637
806
|
|
|
638
807
|
const info = skeletonInfo.get(name);
|
|
@@ -656,6 +825,28 @@ export async function buildTileObjects(
|
|
|
656
825
|
return lines;
|
|
657
826
|
}
|
|
658
827
|
|
|
828
|
+
// 点云包:顶点即点,按 THREE.Points 渲染。索引只是点序号,直接走 drawRange。
|
|
829
|
+
if (isPointPrimitive(pack.primitiveType)) {
|
|
830
|
+
// PointsMaterial 的顶点色读内置 'color' 槽位,与 aColor 共享同一份 buffer
|
|
831
|
+
if (!geometry.getAttribute('color')) {
|
|
832
|
+
geometry.setAttribute('color', geometry.getAttribute('aColor')!);
|
|
833
|
+
}
|
|
834
|
+
const points = new THREE.Points(geometry, getPointCloudMaterial());
|
|
835
|
+
// 点云不是 Mesh:viewer 的样式 / 拾取 / 卸载链路都靠这个标记放行
|
|
836
|
+
points.userData.isPointCloud = true;
|
|
837
|
+
points.userData.skeletonName = name;
|
|
838
|
+
points.userData.geoRef = geo;
|
|
839
|
+
points.userData.tileRef = tile;
|
|
840
|
+
points.userData.primitiveType = pack.primitiveType;
|
|
841
|
+
if (matrix && !matrix.equals(IDENTITY)) {
|
|
842
|
+
matrix.decompose(points.position, points.quaternion, points.scale);
|
|
843
|
+
}
|
|
844
|
+
group.add(points);
|
|
845
|
+
objects.push(points);
|
|
846
|
+
stats.meshes++;
|
|
847
|
+
return points;
|
|
848
|
+
}
|
|
849
|
+
|
|
659
850
|
if (info?.instanced && vp.instanceBuffer && vp.instanceCount > 0) {
|
|
660
851
|
// 逐实例的两条属性:
|
|
661
852
|
// aInstanceBatchId —— 单体控制位,着色器靠它查状态纹理(改色 / 隐藏 / 抬高)
|
|
@@ -822,7 +1013,8 @@ export async function buildTileObjects(
|
|
|
822
1013
|
if (list.length === 1) {
|
|
823
1014
|
placeObject(list[0].cached, list[0].name, list[0].geo, list[0].matrix);
|
|
824
1015
|
} else {
|
|
825
|
-
const
|
|
1016
|
+
const hasAtlasRect = !!list[0].cached.geometry.getAttribute('aAtlasRect');
|
|
1017
|
+
const material = makeMaterial(matKey === '__default__' ? undefined : matKey, hasAtlasRect);
|
|
826
1018
|
const batchedMesh = mergeBatchGeometries(list, material, tile);
|
|
827
1019
|
group.add(batchedMesh);
|
|
828
1020
|
objects.push(batchedMesh);
|