s3m-three 0.3.0 → 0.3.2
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 +3 -2
- 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 +11 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +68 -3
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/GeometryBuilder.d.ts +3 -0
- package/dist/render/GeometryBuilder.d.ts.map +1 -1
- package/dist/render/GeometryBuilder.js +189 -17
- 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 +75 -3
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +284 -54
- 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 +177 -1
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +754 -60
- 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 +150 -11
- 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 +3 -1
- package/src/render/DatasetManager.ts +70 -5
- package/src/render/GeometryBuilder.ts +212 -16
- 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 +322 -55
- package/src/render/StyleMaterial.ts +135 -2
- package/src/render/TextureFactory.ts +67 -11
- package/src/tileset/S3MTileset.ts +798 -65
- 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';
|
|
@@ -94,6 +95,7 @@ export type {
|
|
|
94
95
|
S3MFilter,
|
|
95
96
|
GroundMode,
|
|
96
97
|
GroundOptions,
|
|
98
|
+
LightOptions,
|
|
97
99
|
} from './render/S3MViewer.js';
|
|
98
100
|
|
|
99
101
|
export { buildTileObjects } from './render/GeometryBuilder.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
|
|
|
@@ -64,6 +64,68 @@ export interface DatasetManagerCallbacks {
|
|
|
64
64
|
export class DatasetManager {
|
|
65
65
|
readonly datasets = new Map<string, S3MDatasetEntry>();
|
|
66
66
|
sceneAnchor: { x: number; y: number; z: number; meterInMercator: number; altitude?: number } | null = null;
|
|
67
|
+
totalMaxTiles: number = 0;
|
|
68
|
+
|
|
69
|
+
get(id: string): S3MDatasetEntry | undefined {
|
|
70
|
+
return this.datasets.get(id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
values(): IterableIterator<S3MDatasetEntry> {
|
|
74
|
+
return this.datasets.values();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 同步预注册数据集(用于提前建立条目与预算平衡)
|
|
79
|
+
*/
|
|
80
|
+
registerDataset(options: S3MDatasetOptions): S3MDatasetEntry {
|
|
81
|
+
const existing = this.datasets.get(options.id);
|
|
82
|
+
if (existing) return existing;
|
|
83
|
+
const root = new THREE.Group();
|
|
84
|
+
root.name = `s3m-dataset:${options.id}`;
|
|
85
|
+
root.visible = options.visible !== false;
|
|
86
|
+
const entry: S3MDatasetEntry = {
|
|
87
|
+
id: options.id,
|
|
88
|
+
name: options.name ?? options.id,
|
|
89
|
+
options,
|
|
90
|
+
root,
|
|
91
|
+
tileset: null,
|
|
92
|
+
anchor: null,
|
|
93
|
+
baseAlignOffset: 0,
|
|
94
|
+
userOffset: options.heightOffset ?? 0,
|
|
95
|
+
heightOffset: options.heightOffset ?? 0,
|
|
96
|
+
offset: [0, 0, 0],
|
|
97
|
+
visible: options.visible !== false,
|
|
98
|
+
error: null,
|
|
99
|
+
};
|
|
100
|
+
this.datasets.set(options.id, entry);
|
|
101
|
+
this.rebalanceBudgets();
|
|
102
|
+
return entry;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 全局多数据集预算再平衡:在所有可见数据集之间均衡分配 maxTiles
|
|
107
|
+
*/
|
|
108
|
+
rebalanceBudgets(totalMaxTiles?: number): void {
|
|
109
|
+
const budget = totalMaxTiles ?? this.totalMaxTiles;
|
|
110
|
+
if (!budget || budget <= 0) return;
|
|
111
|
+
this.totalMaxTiles = budget;
|
|
112
|
+
|
|
113
|
+
const visibleEntries = Array.from(this.datasets.values()).filter((d) => d.visible && d.tileset);
|
|
114
|
+
const count = Math.max(1, visibleEntries.length);
|
|
115
|
+
const perDataset = Math.max(8, Math.floor(budget / count));
|
|
116
|
+
|
|
117
|
+
for (const entry of this.datasets.values()) {
|
|
118
|
+
if (!entry.tileset) continue;
|
|
119
|
+
if (entry.visible) {
|
|
120
|
+
entry.tileset.maxTiles = perDataset;
|
|
121
|
+
entry.tileset.hardCap = perDataset + Math.max(4, Math.ceil(perDataset * 0.5));
|
|
122
|
+
} else {
|
|
123
|
+
// 隐藏数据集仅保留极低配额(防止占着显存)
|
|
124
|
+
entry.tileset.maxTiles = Math.min(4, entry.tileset.maxTiles);
|
|
125
|
+
entry.tileset.hardCap = Math.min(6, entry.tileset.hardCap);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
67
129
|
|
|
68
130
|
/** 根据语义对齐方式('top' | 'bottom' | 'center')和数据源高度范围计算基准高度偏移 */
|
|
69
131
|
computeAlignOffset(tileset: S3MTileset | null, align?: DatasetAlignment): number {
|
|
@@ -144,6 +206,7 @@ export class DatasetManager {
|
|
|
144
206
|
},
|
|
145
207
|
});
|
|
146
208
|
entry.tileset = tileset;
|
|
209
|
+
this.rebalanceBudgets();
|
|
147
210
|
|
|
148
211
|
try {
|
|
149
212
|
await tileset.init();
|
|
@@ -179,6 +242,7 @@ export class DatasetManager {
|
|
|
179
242
|
entry.tileset?.dispose();
|
|
180
243
|
scene.remove(entry.root);
|
|
181
244
|
this.datasets.delete(id);
|
|
245
|
+
this.rebalanceBudgets();
|
|
182
246
|
|
|
183
247
|
if (this.datasets.size > 0) {
|
|
184
248
|
this.recomputeAnchors(geo);
|
|
@@ -277,6 +341,7 @@ export class DatasetManager {
|
|
|
277
341
|
if (!entry) return;
|
|
278
342
|
entry.visible = visible;
|
|
279
343
|
entry.root.visible = visible;
|
|
344
|
+
this.rebalanceBudgets();
|
|
280
345
|
}
|
|
281
346
|
|
|
282
347
|
/**
|
|
@@ -291,10 +356,10 @@ export class DatasetManager {
|
|
|
291
356
|
|
|
292
357
|
for (const entry of this.datasets.values()) {
|
|
293
358
|
if (!entry.visible || !entry.tileset?.config) continue;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (b
|
|
359
|
+
// 只认「确实是一张经纬度范围」的 geoBounds:OSGB/SuperMapCache 那类配置只给米制
|
|
360
|
+
// 局部包围盒,全 0 的退化框拿去 fitBounds 会把视角甩到几内亚湾
|
|
361
|
+
const b = usableGeoBounds(entry.tileset.config.geoBounds);
|
|
362
|
+
if (b) {
|
|
298
363
|
minLon = Math.min(minLon, b.left);
|
|
299
364
|
minLat = Math.min(minLat, b.bottom);
|
|
300
365
|
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[];
|
|
@@ -240,6 +288,7 @@ export interface BuildTileResult {
|
|
|
240
288
|
group: THREE.Group;
|
|
241
289
|
stats: TileBuildStats;
|
|
242
290
|
objects: THREE.Object3D[];
|
|
291
|
+
textureKeys?: string[];
|
|
243
292
|
}
|
|
244
293
|
|
|
245
294
|
interface CachedGeometry {
|
|
@@ -260,6 +309,7 @@ export async function buildTileObjects(
|
|
|
260
309
|
const textureByCode = options.textureCache ?? new Map<string, THREE.Texture>();
|
|
261
310
|
const group = new THREE.Group();
|
|
262
311
|
const objects: THREE.Object3D[] = [];
|
|
312
|
+
const usedTextureKeys: string[] = [];
|
|
263
313
|
const stats: TileBuildStats = {
|
|
264
314
|
meshes: 0,
|
|
265
315
|
vertices: 0,
|
|
@@ -280,11 +330,31 @@ export async function buildTileObjects(
|
|
|
280
330
|
group.userData.hasNativeEdges = false;
|
|
281
331
|
|
|
282
332
|
// 1) 纹理
|
|
333
|
+
//
|
|
334
|
+
// 缓存 key 不能只用 atlas id:拼图(AtlasPack)是**逐块**做的,同一个 id 在 LOD 兄弟块里
|
|
335
|
+
// 参与的拼图集合可能不同、落位也不同。只按 id 复用会把别块的排布拿过来,
|
|
336
|
+
// 而 aAtlasRect 是本块的 —— 整块贴错图。带 packKey 的按「id#集合签名」隔离。
|
|
337
|
+
const texKeyOf = (code: string): string => {
|
|
338
|
+
const t = (tile.textures as Record<string, { packKey?: string }> | undefined)?.[code];
|
|
339
|
+
return t?.packKey ? `${code}#${t.packKey}` : code;
|
|
340
|
+
};
|
|
341
|
+
|
|
283
342
|
for (const [code, tex] of Object.entries(tile.textures ?? {})) {
|
|
284
|
-
|
|
343
|
+
// 同一个图集 id 会被很多瓦片共用:先到的那块可能只拿到内嵌缩略图(120x184),
|
|
344
|
+
// 后面的块换成了高清版(240x304)。只按 id 判「缓存已有」会让高清永远进不来,
|
|
345
|
+
// 满屏都是马赛克。所以尺寸变大时必须重解码并覆盖。
|
|
346
|
+
const key = texKeyOf(code);
|
|
347
|
+
const prev = textureByCode.get(key);
|
|
348
|
+
if (prev) {
|
|
349
|
+
const img = prev.image as { width?: number; height?: number } | undefined;
|
|
350
|
+
const prevPixels = (img?.width ?? 0) * (img?.height ?? 0);
|
|
351
|
+
if (prevPixels >= (tex.width ?? 0) * (tex.height ?? 0)) continue;
|
|
352
|
+
textureByCode.delete(key);
|
|
353
|
+
}
|
|
285
354
|
const { texture, decoded } = await createTexture(tex);
|
|
286
355
|
if (texture) {
|
|
287
|
-
textureByCode.set(
|
|
356
|
+
textureByCode.set(key, texture);
|
|
357
|
+
usedTextureKeys.push(key);
|
|
288
358
|
stats.textures++;
|
|
289
359
|
} else {
|
|
290
360
|
stats.skipped.push(`texture ${code} → ${decoded}`);
|
|
@@ -299,12 +369,19 @@ export async function buildTileObjects(
|
|
|
299
369
|
// 材质对象本身很轻(program 由 customProgramCacheKey 复用),一栋一个不心疼。
|
|
300
370
|
const materialList = resolveMaterialList(tile.materials);
|
|
301
371
|
|
|
302
|
-
const makeMaterial = (code?: string): StyledMaterial => {
|
|
372
|
+
const makeMaterial = (code?: string, hasAtlasRect = false): StyledMaterial => {
|
|
303
373
|
const entry = materialList.find((e) => materialName(unwrapMaterial(e)) === code);
|
|
304
374
|
const m = unwrapMaterial(entry);
|
|
305
375
|
const units = materialUnits(m);
|
|
306
376
|
const unit0 = units[0];
|
|
307
|
-
let map = unit0 ? (textureByCode.get(materialName(unit0) ?? unit0.id ?? '') ?? null) : null;
|
|
377
|
+
let map = unit0 ? (textureByCode.get(texKeyOf(materialName(unit0) ?? unit0.id ?? '')) ?? null) : null;
|
|
378
|
+
// S3MBLOCK 的烘焙图(unit1 / BakeTex)是光照烘焙,官方与 base 相乘;缺它建筑就是一片平白
|
|
379
|
+
const unit1 = units[1];
|
|
380
|
+
const noBake = (globalThis as { __S3M_NO_BAKE?: boolean }).__S3M_NO_BAKE === true;
|
|
381
|
+
const secMap =
|
|
382
|
+
hasAtlasRect && unit1 && !noBake
|
|
383
|
+
? (textureByCode.get(texKeyOf(materialName(unit1) ?? unit1.id ?? '')) ?? null)
|
|
384
|
+
: null;
|
|
308
385
|
|
|
309
386
|
// 检查并应用 texmodmatrix 纹理变换矩阵
|
|
310
387
|
const texMod = unit0?.texmodmatrix ?? unit0?.matrix;
|
|
@@ -337,20 +414,36 @@ export async function buildTileObjects(
|
|
|
337
414
|
|
|
338
415
|
const diffuse = m?.diffuse ?? { r: 1, g: 1, b: 1, a: 1 };
|
|
339
416
|
const alphaMode = m?.alphaMode;
|
|
340
|
-
|
|
417
|
+
// SuperMap 的内置水面:纹理单元 id 是 `103.water`,块里**根本没有像素**
|
|
418
|
+
// (实测 Waters 的 s3mb 只有 1081 字节,textures = 0,材质 diffuse 还是纯白)。
|
|
419
|
+
// 以前当白色漫反射渲染,整片湖就是一块白板。这里按内置水面材质上色。
|
|
420
|
+
const isWater = [unit0, unit1].some((u) => /\.water$/i.test(String(materialName(u) ?? u?.id ?? '')));
|
|
421
|
+
const transparent =
|
|
422
|
+
alphaMode === 'AM_BLEND' || (diffuse.a ?? 1) < 0.999 || m?.transparentsorting === true || (isWater && !map);
|
|
341
423
|
|
|
342
424
|
// S3M 的 shininess 是旧式 0~128 高光指数,粗略映射到粗糙度
|
|
343
425
|
const roughness =
|
|
344
426
|
typeof m?.shininess === 'number' && m.shininess > 0 ? Math.max(0.15, 1 - m.shininess / 128) : 0.95;
|
|
345
427
|
|
|
428
|
+
// 镂空图集(S3MBLOCK):建筑照片是异形 alpha 抠图,轮廓顶点会采到 alpha=0 的像素,
|
|
429
|
+
// 不丢弃的话就是成片的黑面。cutoff 取材质自带的 alphaCutoff(实测 0.01)。
|
|
430
|
+
// rect 是图集里的原始子区域(含右 1/3 的内联 mip 列),不做任何裁切,
|
|
431
|
+
// 这里保留 alphaTest 只为挡掉轮廓外的零星像素,不会再出现整面墙被抠空。
|
|
432
|
+
const alphaTest = (map as THREE.Texture | null)?.userData?.s3mHasAlpha
|
|
433
|
+
? Math.max((m?.alphaCutoff as number) ?? 0, 0.01)
|
|
434
|
+
: 0;
|
|
435
|
+
|
|
346
436
|
const mat = createStyledMaterial({
|
|
347
437
|
uniforms,
|
|
348
438
|
map,
|
|
349
|
-
color: new THREE.Color(diffuse.r ?? 1, diffuse.g ?? 1, diffuse.b ?? 1),
|
|
439
|
+
color: isWater && !map ? WATER_COLOR.clone() : new THREE.Color(diffuse.r ?? 1, diffuse.g ?? 1, diffuse.b ?? 1),
|
|
350
440
|
side: resolveSide(m),
|
|
351
441
|
transparent,
|
|
352
|
-
opacity: diffuse.a ?? 1,
|
|
353
|
-
roughness,
|
|
442
|
+
opacity: isWater && !map ? 0.86 : (diffuse.a ?? 1),
|
|
443
|
+
roughness: isWater && !map ? 0.12 : roughness,
|
|
444
|
+
alphaTest,
|
|
445
|
+
atlas: hasAtlasRect && !!map,
|
|
446
|
+
secMap,
|
|
354
447
|
});
|
|
355
448
|
return mat;
|
|
356
449
|
};
|
|
@@ -388,21 +481,61 @@ export async function buildTileObjects(
|
|
|
388
481
|
? toBufferAttribute(batchAttr)
|
|
389
482
|
: new THREE.BufferAttribute(new Float32Array(vertexCount).fill(-1), 1);
|
|
390
483
|
|
|
484
|
+
// S3MBLOCK 图集:逐顶点带子区域矩形,平铺的 fract 交给片元做(顶点上做会画反)。
|
|
485
|
+
// 烘焙图(材质 unit1)用第二套原始 UV,这里另开一个 aSecUv 槽位,不跟 three 的 uv1 语义纠缠。
|
|
486
|
+
const rects = tile.atlasRects?.[name];
|
|
487
|
+
if (rects) {
|
|
488
|
+
attributes.aAtlasRect = new THREE.BufferAttribute(rects.uv ?? new Float32Array(vertexCount * 4), 4);
|
|
489
|
+
// 没有第二套 UV 的骨架不做烘焙:给一个退化矩形,片元里据此跳过
|
|
490
|
+
const uv1 = attributes.uv1 as THREE.BufferAttribute | undefined;
|
|
491
|
+
if (rects.uv2 && uv1) {
|
|
492
|
+
attributes.aAtlasRect1 = new THREE.BufferAttribute(rects.uv2, 4);
|
|
493
|
+
attributes.aSecUv = uv1;
|
|
494
|
+
} else {
|
|
495
|
+
attributes.aAtlasRect1 = new THREE.BufferAttribute(new Float32Array(vertexCount * 4), 4);
|
|
496
|
+
attributes.aSecUv = (attributes.uv as THREE.BufferAttribute) ?? new THREE.BufferAttribute(new Float32Array(vertexCount * 2), 2);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
391
500
|
const instanced = vp.instanceIndex !== -1 && (vp.instanceMode === 16 || vp.instanceMode === 17);
|
|
392
501
|
// 注意:indexPackages 属于 Skeleton(geoPackage[name]),不在 vertexPackage 里
|
|
393
|
-
|
|
502
|
+
//
|
|
503
|
+
// 一个索引包都没有的骨架 = 纯点集:高斯泼溅(3DGaussianSplatting)和无索引点云
|
|
504
|
+
// 都是这种——顶点就是点,没有任何三角形可画。不补一个「顶点即点」的伪包,
|
|
505
|
+
// 下面就不会产出任何对象,整块瓦片最后落到「tile 无可用几何」被丢掉。
|
|
506
|
+
let packs = geo.indexPackages ?? [];
|
|
507
|
+
if (packs.length === 0 && vertexCount > 0) {
|
|
508
|
+
packs = [
|
|
509
|
+
{
|
|
510
|
+
indexCount: 0,
|
|
511
|
+
indexType: 0,
|
|
512
|
+
useIndex: 0,
|
|
513
|
+
primitiveType: POINT_PRIMITIVE,
|
|
514
|
+
indices: null,
|
|
515
|
+
passNames: [],
|
|
516
|
+
},
|
|
517
|
+
];
|
|
518
|
+
}
|
|
519
|
+
skeletonInfo.set(name, { indexPackages: packs, instanced });
|
|
394
520
|
stats.vertices += vertexCount;
|
|
395
521
|
|
|
396
|
-
|
|
397
|
-
|
|
522
|
+
packs.forEach((pack, pi) => {
|
|
523
|
+
let geometry = new THREE.BufferGeometry();
|
|
398
524
|
for (const [k, v] of Object.entries(attributes)) geometry.setAttribute(k, v);
|
|
399
525
|
const idx = toIndex(pack);
|
|
400
526
|
if (idx) geometry.setIndex(idx);
|
|
401
527
|
else geometry.setDrawRange(0, vertexCount);
|
|
402
528
|
|
|
529
|
+
|
|
530
|
+
|
|
403
531
|
// 如果数据本身没有法线且是三角网格,必须在绑定了 index 之后计算法线
|
|
404
532
|
// 绝不能在未索引的 attributes.position 上算,否则非连续顶点会导致法线朝向错乱甚至越界 NaN 导致死黑面
|
|
405
|
-
|
|
533
|
+
// 线 / 点包不需要法线
|
|
534
|
+
if (
|
|
535
|
+
!isLinePrimitive(pack.primitiveType) &&
|
|
536
|
+
!isPointPrimitive(pack.primitiveType) &&
|
|
537
|
+
!geometry.getAttribute('normal')
|
|
538
|
+
) {
|
|
406
539
|
geometry.computeVertexNormals();
|
|
407
540
|
}
|
|
408
541
|
|
|
@@ -435,6 +568,7 @@ export async function buildTileObjects(
|
|
|
435
568
|
let hasUv1 = false;
|
|
436
569
|
let hasAColor = false;
|
|
437
570
|
let hasABatchId = false;
|
|
571
|
+
let hasAtlas = false;
|
|
438
572
|
|
|
439
573
|
for (const c of candidates) {
|
|
440
574
|
const g = c.cached.geometry;
|
|
@@ -446,6 +580,7 @@ export async function buildTileObjects(
|
|
|
446
580
|
if (g.attributes.uv1) hasUv1 = true;
|
|
447
581
|
if (g.attributes.aColor) hasAColor = true;
|
|
448
582
|
if (g.attributes.aBatchId) hasABatchId = true;
|
|
583
|
+
if (g.attributes.aAtlasRect) hasAtlas = true;
|
|
449
584
|
}
|
|
450
585
|
|
|
451
586
|
const mergedPos = new Float32Array(totalVertices * 3);
|
|
@@ -454,6 +589,31 @@ export async function buildTileObjects(
|
|
|
454
589
|
const mergedUv1 = hasUv1 ? new Float32Array(totalVertices * 2) : null;
|
|
455
590
|
const mergedColor = hasAColor ? new Float32Array(totalVertices * 3) : null;
|
|
456
591
|
const mergedBatchId = hasABatchId ? new Float32Array(totalVertices) : null;
|
|
592
|
+
const mergedRect0 = hasAtlas ? new Float32Array(totalVertices * 4) : null;
|
|
593
|
+
const mergedRect1 = hasAtlas ? new Float32Array(totalVertices * 4) : null;
|
|
594
|
+
const mergedSecUv = hasAtlas ? new Float32Array(totalVertices * 2) : null;
|
|
595
|
+
|
|
596
|
+
/** 逐顶点拷一个 n 分量的浮点属性;缺失时用 fallbackRect / 0 兜底 */
|
|
597
|
+
const copyAttr = (
|
|
598
|
+
dst: Float32Array,
|
|
599
|
+
attr: THREE.BufferAttribute | THREE.InterleavedBufferAttribute | undefined,
|
|
600
|
+
comp: number,
|
|
601
|
+
vCount: number,
|
|
602
|
+
vertexOffset: number,
|
|
603
|
+
fallback?: (i: number, out: number[]) => void
|
|
604
|
+
) => {
|
|
605
|
+
if (attr) {
|
|
606
|
+
const src = attr.array as ArrayLike<number>;
|
|
607
|
+
for (let i = 0; i < vCount; i++)
|
|
608
|
+
for (let k = 0; k < comp; k++) dst[(vertexOffset + i) * comp + k] = src[i * comp + k] ?? 0;
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
for (let i = 0; i < vCount; i++) {
|
|
612
|
+
const out: number[] = [];
|
|
613
|
+
fallback ? fallback(i, out) : out.push(...new Array(comp).fill(0));
|
|
614
|
+
for (let k = 0; k < comp; k++) dst[(vertexOffset + i) * comp + k] = out[k] ?? 0;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
457
617
|
|
|
458
618
|
const IndexArrayType = totalVertices > 65535 ? Uint32Array : Uint16Array;
|
|
459
619
|
const mergedIndices = new IndexArrayType(totalIndices);
|
|
@@ -582,7 +742,14 @@ export async function buildTileObjects(
|
|
|
582
742
|
}
|
|
583
743
|
}
|
|
584
744
|
|
|
585
|
-
// 7.
|
|
745
|
+
// 7. 图集矩形(S3MBLOCK)
|
|
746
|
+
if (mergedRect0) {
|
|
747
|
+
copyAttr(mergedRect0, g.attributes.aAtlasRect, 4, vCount, vertexOffset, (_i, out) => out.push(0, 0, 0, 0));
|
|
748
|
+
copyAttr(mergedRect1!, g.attributes.aAtlasRect1, 4, vCount, vertexOffset, (_i, out) => out.push(0, 0, 0, 0));
|
|
749
|
+
copyAttr(mergedSecUv!, g.attributes.aSecUv, 2, vCount, vertexOffset);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// 8. Indices
|
|
586
753
|
if (g.index) {
|
|
587
754
|
const idxArr = g.index.array as ArrayLike<number>;
|
|
588
755
|
const iCount = g.index.count;
|
|
@@ -607,6 +774,11 @@ export async function buildTileObjects(
|
|
|
607
774
|
if (mergedUv1) mergedGeo.setAttribute('uv1', new THREE.BufferAttribute(mergedUv1, 2));
|
|
608
775
|
if (mergedColor) mergedGeo.setAttribute('aColor', new THREE.BufferAttribute(mergedColor, 3));
|
|
609
776
|
if (mergedBatchId) mergedGeo.setAttribute('aBatchId', new THREE.BufferAttribute(mergedBatchId, 1));
|
|
777
|
+
if (mergedRect0) {
|
|
778
|
+
mergedGeo.setAttribute('aAtlasRect', new THREE.BufferAttribute(mergedRect0, 4));
|
|
779
|
+
mergedGeo.setAttribute('aAtlasRect1', new THREE.BufferAttribute(mergedRect1!, 4));
|
|
780
|
+
mergedGeo.setAttribute('aSecUv', new THREE.BufferAttribute(mergedSecUv!, 2));
|
|
781
|
+
}
|
|
610
782
|
mergedGeo.setIndex(new THREE.BufferAttribute(mergedIndices, 1));
|
|
611
783
|
mergedGeo.computeBoundingBox();
|
|
612
784
|
mergedGeo.computeBoundingSphere();
|
|
@@ -632,7 +804,7 @@ export async function buildTileObjects(
|
|
|
632
804
|
matrix: THREE.Matrix4 | null
|
|
633
805
|
): THREE.Object3D => {
|
|
634
806
|
const { geometry, pack } = cached;
|
|
635
|
-
const material = makeMaterial(pack.passNames?.[0]);
|
|
807
|
+
const material = makeMaterial(pack.passNames?.[0], !!geometry.getAttribute('aAtlasRect'));
|
|
636
808
|
let object: THREE.Mesh | THREE.InstancedMesh;
|
|
637
809
|
|
|
638
810
|
const info = skeletonInfo.get(name);
|
|
@@ -656,6 +828,28 @@ export async function buildTileObjects(
|
|
|
656
828
|
return lines;
|
|
657
829
|
}
|
|
658
830
|
|
|
831
|
+
// 点云包:顶点即点,按 THREE.Points 渲染。索引只是点序号,直接走 drawRange。
|
|
832
|
+
if (isPointPrimitive(pack.primitiveType)) {
|
|
833
|
+
// PointsMaterial 的顶点色读内置 'color' 槽位,与 aColor 共享同一份 buffer
|
|
834
|
+
if (!geometry.getAttribute('color')) {
|
|
835
|
+
geometry.setAttribute('color', geometry.getAttribute('aColor')!);
|
|
836
|
+
}
|
|
837
|
+
const points = new THREE.Points(geometry, getPointCloudMaterial());
|
|
838
|
+
// 点云不是 Mesh:viewer 的样式 / 拾取 / 卸载链路都靠这个标记放行
|
|
839
|
+
points.userData.isPointCloud = true;
|
|
840
|
+
points.userData.skeletonName = name;
|
|
841
|
+
points.userData.geoRef = geo;
|
|
842
|
+
points.userData.tileRef = tile;
|
|
843
|
+
points.userData.primitiveType = pack.primitiveType;
|
|
844
|
+
if (matrix && !matrix.equals(IDENTITY)) {
|
|
845
|
+
matrix.decompose(points.position, points.quaternion, points.scale);
|
|
846
|
+
}
|
|
847
|
+
group.add(points);
|
|
848
|
+
objects.push(points);
|
|
849
|
+
stats.meshes++;
|
|
850
|
+
return points;
|
|
851
|
+
}
|
|
852
|
+
|
|
659
853
|
if (info?.instanced && vp.instanceBuffer && vp.instanceCount > 0) {
|
|
660
854
|
// 逐实例的两条属性:
|
|
661
855
|
// aInstanceBatchId —— 单体控制位,着色器靠它查状态纹理(改色 / 隐藏 / 抬高)
|
|
@@ -822,7 +1016,8 @@ export async function buildTileObjects(
|
|
|
822
1016
|
if (list.length === 1) {
|
|
823
1017
|
placeObject(list[0].cached, list[0].name, list[0].geo, list[0].matrix);
|
|
824
1018
|
} else {
|
|
825
|
-
const
|
|
1019
|
+
const hasAtlasRect = !!list[0].cached.geometry.getAttribute('aAtlasRect');
|
|
1020
|
+
const material = makeMaterial(matKey === '__default__' ? undefined : matKey, hasAtlasRect);
|
|
826
1021
|
const batchedMesh = mergeBatchGeometries(list, material, tile);
|
|
827
1022
|
group.add(batchedMesh);
|
|
828
1023
|
objects.push(batchedMesh);
|
|
@@ -836,5 +1031,6 @@ export async function buildTileObjects(
|
|
|
836
1031
|
|
|
837
1032
|
if (!group.children.length) stats.skipped.push('tile 无可用几何');
|
|
838
1033
|
|
|
839
|
-
|
|
1034
|
+
group.userData.textureKeys = usedTextureKeys;
|
|
1035
|
+
return { group, stats, objects, textureKeys: usedTextureKeys };
|
|
840
1036
|
}
|