s3m-three 0.2.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 +2 -2
- package/dist/browser/s3m-three.iife.ext-three.js +585 -0
- package/dist/browser/s3m-three.iife.js +4419 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +21 -0
- package/dist/browser.js.map +1 -0
- 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 +672 -54
- 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 +112 -11
- package/package.json +5 -3
- package/src/browser.ts +21 -0
- 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 +704 -59
- 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
|
@@ -218,8 +218,10 @@ function DecompressImage(rgba: Uint8Array, width: number, height: number, blocks
|
|
|
218
218
|
const sx = x + px;
|
|
219
219
|
const sy = y + py;
|
|
220
220
|
if (sx < width && sy < height) {
|
|
221
|
-
// flip Y
|
|
222
|
-
|
|
221
|
+
// flip Y:squish 的原式是 height - 1 - sy。写成 height - sy 会让 sy = 0
|
|
222
|
+
// 落到 row = height 越界(TypedArray 越界写是静默丢弃),于是缓冲区第 0 行
|
|
223
|
+
// 永远是全 0(透明黑),其余行整体错位一行。
|
|
224
|
+
let nBegin = 4 * (width * (height - 1 - sy) + sx);
|
|
223
225
|
|
|
224
226
|
for (let i = 0; i < 4; ++i) rgba[nBegin++] = targetRgba[nOffsetTarget++];
|
|
225
227
|
} else {
|
|
@@ -245,11 +247,31 @@ const DXTTextureDecode = {
|
|
|
245
247
|
decode(out: Uint8Array, width: number, height: number, block: Uint8Array, format: number): void {
|
|
246
248
|
if (out == null || block == null || height === 0 || width === 0) return;
|
|
247
249
|
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
|
|
250
|
+
// 块大小与 alpha 编码只由压缩格式决定:DXT1 = 8 B/块,DXT3/4/5 = 16 B/块。
|
|
251
|
+
//
|
|
252
|
+
// 不能再靠「format > 11」判有没有 alpha:S3MPixelFormat 里 DXT1 = 17 也 > 11,
|
|
253
|
+
// 那样会把 DXT1 当 16 字节块读——512×512 的 DXT1 实际只有 131072 B(0.5 B/px),
|
|
254
|
+
// 按 16 字节块要 262144 B,正好差一倍,越界部分解成全 0。
|
|
255
|
+
// 官方 JS 那个判断是在「无 alpha 时纹理标成 RGB(10)/BGR(11)」的前提下才成立。
|
|
256
|
+
let flags: number;
|
|
257
|
+
switch (format) {
|
|
258
|
+
case 17: // DXT1
|
|
259
|
+
flags = kDxt1;
|
|
260
|
+
break;
|
|
261
|
+
case 18:
|
|
262
|
+
case 19: // DXT3
|
|
263
|
+
flags = kDxt3;
|
|
264
|
+
break;
|
|
265
|
+
case 20: // DXT4
|
|
266
|
+
case 21: // DXT5
|
|
267
|
+
flags = kDxt5;
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
// 老数据只在 RGB / BGR / LUMINANCE_ALPHA 里出现:无 alpha 时走 DXT1 + rgb565 快路径
|
|
271
|
+
flags = format > 11 || format === 5 ? kDxt5 : kDxt1 | krgb565;
|
|
272
|
+
}
|
|
251
273
|
|
|
252
|
-
if (flags & kDxt1 && flags & krgb565) {
|
|
274
|
+
if ((flags & kDxt1) !== 0 && (flags & krgb565) !== 0) {
|
|
253
275
|
DecompressImage565(asU16(out), width, height, asU16(block));
|
|
254
276
|
} else {
|
|
255
277
|
DecompressImage(out, width, height, block, flags);
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3M v3 Draco 几何压缩(Skeleton 的 Draco 分支)
|
|
3
|
+
*
|
|
4
|
+
* 与 MeshOpt 一样,这里的布局不是照规范写的 —— 规范里的 Draco 分支描述和实际数据
|
|
5
|
+
* 对不上,字段顺序是从真实瓦片逐字节标定出来的(两个瓦片 5 个骨架完全一致,
|
|
6
|
+
* 且用 payloadSize / dracoSize / "DRACO" magic / skeletonStreamSize 四重自校验)。
|
|
7
|
+
*
|
|
8
|
+
* 二进制布局(v3.01 实测,偏移以 tag 字段之后为 0):
|
|
9
|
+
* ```
|
|
10
|
+
* u32 payloadSize // 覆盖 [本字段之后 .. 条目末尾 OBB 之前]
|
|
11
|
+
* u32 unknown = 32 // 实测恒 32,语义未知
|
|
12
|
+
* i32 posUniqueId // draco 属性 uid,POSITION;-1 = 无
|
|
13
|
+
* i32 normalUniqueId // -1 = 无
|
|
14
|
+
* i32 colorUniqueId // -1 = 无
|
|
15
|
+
* i32 secondColorUniqueId
|
|
16
|
+
* u32 texCoordCount // 自定义属性个数,实测 3
|
|
17
|
+
* i32 texCoordUniqueIds[texCoordCount] // 各自定义属性的 draco uid
|
|
18
|
+
* u32 vertexAttrCount // 实测恒 0
|
|
19
|
+
* u32 dracoSize
|
|
20
|
+
* byte draco[dracoSize] // 标准 draco 比特流,开头 5 字节是 "DRACO" magic
|
|
21
|
+
* pad4
|
|
22
|
+
* String descript // {"PointCloudClassCode":2,"PointCloudIntensity":1,"VertexWeight":0}
|
|
23
|
+
* pad4
|
|
24
|
+
* f64 obb[12] // ← 由调用方 parseSkeletonEntries 统一按条目末尾读
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* descript 的 value 是「自定义属性在 texCoordUniqueIds 里的下标」:
|
|
28
|
+
* VertexWeight→uid2(float 高度)、PointCloudIntensity→uid3、PointCloudClassCode→uid4。
|
|
29
|
+
* uid1 没被 descript 引用(实测是 0~29 的 uint32,用途不明)。
|
|
30
|
+
*
|
|
31
|
+
* 官方 SDK 的 `parseDracoSkeleton` 字段顺序不一样(多了 materialCount,且
|
|
32
|
+
* `version === 3` 的判定对 3.01 瓦片不成立),实际解析不了这个服务,所以以实测为准。
|
|
33
|
+
*/
|
|
34
|
+
import type { BinaryReader } from './BinaryReader.js';
|
|
35
|
+
import type { AttrDType, IndexPackage, VertexAttribute, VertexPackage } from './types.js';
|
|
36
|
+
import type { DracoModule, DracoPointAttribute, DracoPointCloud } from '../vendor/draco_decoder.module.js';
|
|
37
|
+
|
|
38
|
+
/* ------------------------------------------------------------ draco 枚举(本地副本) */
|
|
39
|
+
|
|
40
|
+
/** draco::GeometryAttribute::Type */
|
|
41
|
+
const ATTR_POSITION = 0;
|
|
42
|
+
const ATTR_NORMAL = 1;
|
|
43
|
+
const ATTR_COLOR = 2;
|
|
44
|
+
const ATTR_GENERIC = 4;
|
|
45
|
+
|
|
46
|
+
/** draco::DataType(DT_INVALID = 0 占位) */
|
|
47
|
+
const DT_UINT8 = 2;
|
|
48
|
+
const DT_UINT16 = 4;
|
|
49
|
+
const DT_UINT32 = 6;
|
|
50
|
+
const DT_FLOAT32 = 9;
|
|
51
|
+
|
|
52
|
+
/** draco::EncodedGeometryType */
|
|
53
|
+
const GEOM_POINT_CLOUD = 0;
|
|
54
|
+
const GEOM_TRIANGULAR_MESH = 1;
|
|
55
|
+
|
|
56
|
+
const DRACO_MAGIC = 'DRACO';
|
|
57
|
+
|
|
58
|
+
/** primitiveType = 1:点云包(渲染层据此走 THREE.Points) */
|
|
59
|
+
const POINT_PRIMITIVE = 1;
|
|
60
|
+
|
|
61
|
+
/** 单个 skeleton 的点数上限,防头读错时开出天文数字的缓冲 */
|
|
62
|
+
const MAX_POINTS = 64 * 1024 * 1024;
|
|
63
|
+
|
|
64
|
+
/* ------------------------------------------------------------ 解码器 */
|
|
65
|
+
|
|
66
|
+
let decoderPromise: Promise<DracoModule> | null = null;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 懒加载 Draco 解码器(wasm 内联,约 266KB)。
|
|
70
|
+
* 只有真正遇到 Draco 瓦片时才会编译,标准 / MeshOpt 瓦片不受影响。
|
|
71
|
+
*/
|
|
72
|
+
export function loadDracoDecoder(): Promise<DracoModule> {
|
|
73
|
+
if (!decoderPromise) {
|
|
74
|
+
decoderPromise = import('../vendor/draco_decoder.module.js').then((mod) => mod.default());
|
|
75
|
+
}
|
|
76
|
+
return decoderPromise;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* ------------------------------------------------------------ 属性取值 */
|
|
80
|
+
|
|
81
|
+
/** embind 数组包装:只暴露逐个取值 */
|
|
82
|
+
interface DracoScalarArray {
|
|
83
|
+
size(): number;
|
|
84
|
+
GetValue(index: number): number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 把 draco 的一个属性读成 JS 数组。
|
|
89
|
+
*
|
|
90
|
+
* 不能直接把 JS TypedArray 传给 `GetAttribute*ForAllPoints`:embind 会把期望
|
|
91
|
+
* 对象的参数原样透传,TypedArray 没有 `.ptr` 就变成 undefined,wasm 侧写不进
|
|
92
|
+
* 数据,函数却照样返回 true(实测取出来全是 0)。只能逐个 GetValue ——
|
|
93
|
+
* 6 千点 × 3 分量实测 3.5ms,可以接受。
|
|
94
|
+
*
|
|
95
|
+
* 量化 / 八面体变换由 draco 解码时自动还原,这里拿到的是最终值。
|
|
96
|
+
*/
|
|
97
|
+
function readAttribute(
|
|
98
|
+
draco: DracoModule,
|
|
99
|
+
decoder: InstanceType<DracoModule['Decoder']>,
|
|
100
|
+
geometry: DracoPointCloud,
|
|
101
|
+
attr: DracoPointAttribute
|
|
102
|
+
): Float32Array | Uint8Array | Uint16Array | Uint32Array | null {
|
|
103
|
+
const comps = Math.max(1, attr.num_components());
|
|
104
|
+
const n = geometry.num_points() * comps;
|
|
105
|
+
let src: DracoScalarArray;
|
|
106
|
+
|
|
107
|
+
switch (attr.data_type()) {
|
|
108
|
+
case DT_FLOAT32:
|
|
109
|
+
src = new draco.DracoFloat32Array();
|
|
110
|
+
if (!decoder.GetAttributeFloatForAllPoints(geometry, attr, src as never)) return null;
|
|
111
|
+
break;
|
|
112
|
+
case DT_UINT8:
|
|
113
|
+
src = new draco.DracoUInt8Array();
|
|
114
|
+
if (!decoder.GetAttributeUInt8ForAllPoints(geometry, attr, src as never)) return null;
|
|
115
|
+
break;
|
|
116
|
+
case DT_UINT16:
|
|
117
|
+
src = new draco.DracoUInt16Array();
|
|
118
|
+
if (!decoder.GetAttributeUInt16ForAllPoints(geometry, attr, src as never)) return null;
|
|
119
|
+
break;
|
|
120
|
+
case DT_UINT32:
|
|
121
|
+
src = new draco.DracoUInt32Array();
|
|
122
|
+
if (!decoder.GetAttributeUInt32ForAllPoints(geometry, attr, src as never)) return null;
|
|
123
|
+
break;
|
|
124
|
+
default:
|
|
125
|
+
// int8/int16/int32/float64:真实点云里没见过,对应取值接口也存在,
|
|
126
|
+
// 需要时按上面同款补一行即可
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const size = src.size();
|
|
131
|
+
const isFloat = attr.data_type() === DT_FLOAT32;
|
|
132
|
+
let out: Float32Array | Uint8Array | Uint16Array | Uint32Array;
|
|
133
|
+
if (isFloat) out = new Float32Array(n);
|
|
134
|
+
else if (attr.data_type() === DT_UINT16) out = new Uint16Array(n);
|
|
135
|
+
else if (attr.data_type() === DT_UINT32) out = new Uint32Array(n);
|
|
136
|
+
else out = new Uint8Array(n);
|
|
137
|
+
for (let i = 0; i < size && i < n; i++) out[i] = src.GetValue(i);
|
|
138
|
+
// Draco*Array 是 new 出来的,占 wasm 堆;不解引用会一直涨
|
|
139
|
+
draco.destroy(src);
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ------------------------------------------------------------ 分类着色 */
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* LAS / ASPRS 标准分类色带(1~18,其余走兜底灰)。
|
|
147
|
+
*
|
|
148
|
+
* 点云分类服务(3D-PointCloudClassific 这类)本身不带顶点色,颜色全靠分类码。
|
|
149
|
+
* 渲染层的点云路径只认 `color` 属性,所以这里直接把分类码烘成 RGBA8 顶点色,
|
|
150
|
+
* 下游零改动。实测该服务的分类分布是 {5: 高植被, 14: 导线, 15: 输电塔, 18: 高噪声}。
|
|
151
|
+
*/
|
|
152
|
+
const CLASS_COLORS: Array<[number, number, number]> = [
|
|
153
|
+
[190, 190, 190], // 0 未分类
|
|
154
|
+
[166, 166, 166], // 1 未分类(旧)
|
|
155
|
+
[139, 119, 101], // 2 地面
|
|
156
|
+
[35, 190, 35], // 3 低植被
|
|
157
|
+
[60, 179, 60], // 4 中植被
|
|
158
|
+
[30, 160, 30], // 5 高植被
|
|
159
|
+
[255, 170, 60], // 6 建筑
|
|
160
|
+
[255, 110, 190], // 7 低噪点
|
|
161
|
+
[190, 190, 190], // 8 关键点
|
|
162
|
+
[60, 120, 255], // 9 水面
|
|
163
|
+
[255, 235, 80], // 10 铁轨
|
|
164
|
+
[255, 200, 120], // 11 路面
|
|
165
|
+
[200, 120, 255], // 12 保留
|
|
166
|
+
[255, 120, 255], // 13 线缆
|
|
167
|
+
[255, 235, 60], // 14 导线
|
|
168
|
+
[255, 200, 40], // 15 输电塔
|
|
169
|
+
[120, 200, 255], // 16 桥面
|
|
170
|
+
[200, 160, 255], // 17 桥墩
|
|
171
|
+
[255, 60, 60], // 18 高噪声
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
/* ------------------------------------------------------------ 骨架 */
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 读取一个 Draco 压缩的 Skeleton。
|
|
178
|
+
*
|
|
179
|
+
* 点云没有索引包(顶点即点),但渲染层是「有 indexPackages 才建几何」,
|
|
180
|
+
* 所以这里合成一个 primitiveType = 1 的空点包,让下游走 THREE.Points 路径。
|
|
181
|
+
*
|
|
182
|
+
* 包围盒不在这里读:调用方 parseSkeletonEntries 统一按 skeletonStreamSize
|
|
183
|
+
* 定位到条目末尾的 OBB,各分支行为保持一致。
|
|
184
|
+
*/
|
|
185
|
+
export async function parseDracoGeometry(r: BinaryReader, vp: VertexPackage): Promise<IndexPackage[]> {
|
|
186
|
+
const draco = await loadDracoDecoder();
|
|
187
|
+
|
|
188
|
+
/* ---- 1. 容器头 ---- */
|
|
189
|
+
|
|
190
|
+
const payloadSize = r.u32();
|
|
191
|
+
const payloadStart = r.offset;
|
|
192
|
+
r.u32(); // 实测恒 32,语义未知
|
|
193
|
+
|
|
194
|
+
const posUniqueId = r.i32();
|
|
195
|
+
const normalUniqueId = r.i32();
|
|
196
|
+
const colorUniqueId = r.i32();
|
|
197
|
+
r.i32(); // secondColorUniqueId
|
|
198
|
+
|
|
199
|
+
const texCoordCount = r.u32();
|
|
200
|
+
if (texCoordCount > 16) {
|
|
201
|
+
throw new Error(`[s3m] Draco 自定义属性个数异常:texCoordCount=${texCoordCount}`);
|
|
202
|
+
}
|
|
203
|
+
const texCoordUniqueIds: number[] = [];
|
|
204
|
+
for (let i = 0; i < texCoordCount; i++) texCoordUniqueIds.push(r.i32());
|
|
205
|
+
|
|
206
|
+
r.u32(); // vertexAttrCount,实测恒 0
|
|
207
|
+
|
|
208
|
+
const dracoSize = r.u32();
|
|
209
|
+
if (dracoSize < 16 || dracoSize > r.remaining) {
|
|
210
|
+
throw new Error(`[s3m] Draco 流长度异常:dracoSize=${dracoSize},剩余 ${r.remaining} 字节`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const data = r.bytes(dracoSize);
|
|
214
|
+
// 头读错时这里必然对不上,比让 wasm 报一句看不懂的错有用
|
|
215
|
+
if (String.fromCharCode(...data.subarray(0, 5)) !== DRACO_MAGIC) {
|
|
216
|
+
throw new Error('[s3m] Draco 流缺少 "DRACO" magic,容器头解析错位');
|
|
217
|
+
}
|
|
218
|
+
r.align4();
|
|
219
|
+
|
|
220
|
+
const descript = r.string();
|
|
221
|
+
r.align4();
|
|
222
|
+
|
|
223
|
+
// 自定义属性 uid → 语义名:{"PointCloudClassCode":2,...} 的 value 是 texCoordUniqueIds 下标
|
|
224
|
+
const customNames = new Map<number, string>();
|
|
225
|
+
try {
|
|
226
|
+
const map = JSON.parse(descript) as Record<string, number>;
|
|
227
|
+
for (const [name, idx] of Object.entries(map)) {
|
|
228
|
+
const uid = texCoordUniqueIds[idx];
|
|
229
|
+
if (uid !== undefined) customNames.set(uid, name);
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
// descript 不是 JSON 也不影响渲染,只是自定义属性没名字
|
|
233
|
+
}
|
|
234
|
+
vp.customVertexAttribute = descript ? safeJson(descript) : undefined;
|
|
235
|
+
|
|
236
|
+
if (payloadSize > 0) {
|
|
237
|
+
const consumed = r.offset - payloadStart;
|
|
238
|
+
if (consumed !== payloadSize) {
|
|
239
|
+
// 条目末尾由 streamSize 兜着,错位不会波及后面的骨架,只提示
|
|
240
|
+
console.warn(`[s3m] Draco 载荷长度不符:声明 ${payloadSize},实读 ${consumed}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* ---- 2. 解码 ---- */
|
|
245
|
+
|
|
246
|
+
const decoder = new draco.Decoder();
|
|
247
|
+
const geometry = new draco.PointCloud();
|
|
248
|
+
try {
|
|
249
|
+
const geomType = decoder.GetEncodedGeometryType(data);
|
|
250
|
+
if (geomType !== GEOM_POINT_CLOUD) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`[s3m] Draco ${geomType === GEOM_TRIANGULAR_MESH ? '三角网格' : '未知'} 几何暂未实现(仅支持点云)`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const status = decoder.DecodeArrayToPointCloud(data, data.length, geometry);
|
|
257
|
+
if (!status.ok()) throw new Error(`[s3m] Draco 解码失败:${status.error_msg()}`);
|
|
258
|
+
|
|
259
|
+
const numPoints = geometry.num_points();
|
|
260
|
+
if (!Number.isInteger(numPoints) || numPoints < 0 || numPoints > MAX_POINTS) {
|
|
261
|
+
throw new Error(`[s3m] Draco 点数异常:${numPoints}`);
|
|
262
|
+
}
|
|
263
|
+
if (numPoints === 0) {
|
|
264
|
+
vp.verticesCount = 0;
|
|
265
|
+
return [pointPack()];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* ---- 3. 属性装配 ---- */
|
|
269
|
+
|
|
270
|
+
let classCode: Uint8Array | null = null;
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < geometry.num_attributes(); i++) {
|
|
273
|
+
const attr = decoder.GetAttribute(geometry, i);
|
|
274
|
+
const uid = attr.unique_id();
|
|
275
|
+
const comps = Math.max(1, attr.num_components());
|
|
276
|
+
|
|
277
|
+
if (uid === posUniqueId || attr.attribute_type() === ATTR_POSITION) {
|
|
278
|
+
if (attr.data_type() !== DT_FLOAT32) {
|
|
279
|
+
throw new Error(`[s3m] Draco 位置属性不是 float32(dt=${attr.data_type()})`);
|
|
280
|
+
}
|
|
281
|
+
const raw = readAttribute(draco, decoder, geometry, attr) as Float32Array | null;
|
|
282
|
+
if (!raw) throw new Error('[s3m] Draco 位置属性读取失败');
|
|
283
|
+
// draco 偶尔按 3 分量以上的格式存位置,取前 3 个
|
|
284
|
+
const out = new Float32Array(numPoints * 3);
|
|
285
|
+
for (let p = 0; p < numPoints; p++) {
|
|
286
|
+
out[p * 3] = raw[p * comps] ?? 0;
|
|
287
|
+
out[p * 3 + 1] = raw[p * comps + 1] ?? 0;
|
|
288
|
+
out[p * 3 + 2] = raw[p * comps + 2] ?? 0;
|
|
289
|
+
}
|
|
290
|
+
pushAttr(vp, 'position', new Uint8Array(out.buffer), 3, 12, 0, undefined, 'f32');
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (uid === colorUniqueId || attr.attribute_type() === ATTR_COLOR) {
|
|
295
|
+
const raw = readAttribute(draco, decoder, geometry, attr);
|
|
296
|
+
if (raw) {
|
|
297
|
+
const out = new Uint8Array(numPoints * 4);
|
|
298
|
+
const isFloat = raw instanceof Float32Array;
|
|
299
|
+
for (let p = 0; p < numPoints; p++) {
|
|
300
|
+
const o = p * comps;
|
|
301
|
+
if (isFloat) {
|
|
302
|
+
out[p * 4] = clamp255(raw[o] ?? 1);
|
|
303
|
+
out[p * 4 + 1] = clamp255(raw[o + 1] ?? 1);
|
|
304
|
+
out[p * 4 + 2] = clamp255(raw[o + 2] ?? 1);
|
|
305
|
+
} else {
|
|
306
|
+
out[p * 4] = raw[o] ?? 255;
|
|
307
|
+
out[p * 4 + 1] = raw[o + 1] ?? 255;
|
|
308
|
+
out[p * 4 + 2] = raw[o + 2] ?? 255;
|
|
309
|
+
}
|
|
310
|
+
out[p * 4 + 3] = comps >= 4 && !isFloat ? (raw[o + 3] ?? 255) : 255;
|
|
311
|
+
}
|
|
312
|
+
pushAttr(vp, 'color', out, 4, 4, 0, undefined, 'u8');
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (uid === normalUniqueId || attr.attribute_type() === ATTR_NORMAL) {
|
|
318
|
+
// 点云不需要法线,八面体编码由 draco 解码时自动还原,这里直接跳过
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 自定义属性:只挑分类码,其余(高度 / 强度)渲染层用不上
|
|
323
|
+
const name = customNames.get(uid);
|
|
324
|
+
if (name === 'PointCloudClassCode' && comps === 1) {
|
|
325
|
+
const raw = readAttribute(draco, decoder, geometry, attr);
|
|
326
|
+
if (raw && !(raw instanceof Float32Array)) {
|
|
327
|
+
classCode = new Uint8Array(numPoints);
|
|
328
|
+
for (let p = 0; p < numPoints; p++) classCode[p] = raw[p] ?? 0;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (vp.attrLocation.position === undefined) {
|
|
334
|
+
throw new Error('[s3m] Draco 点云缺少 position 属性');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 数据自带顶点色优先;没有色但有分类码时,用分类色带烘一份 RGBA8
|
|
338
|
+
if (vp.attrLocation.color === undefined && classCode) {
|
|
339
|
+
const out = new Uint8Array(numPoints * 4);
|
|
340
|
+
for (let p = 0; p < numPoints; p++) {
|
|
341
|
+
const c = CLASS_COLORS[classCode[p]] ?? CLASS_COLORS[0];
|
|
342
|
+
out[p * 4] = c[0];
|
|
343
|
+
out[p * 4 + 1] = c[1];
|
|
344
|
+
out[p * 4 + 2] = c[2];
|
|
345
|
+
out[p * 4 + 3] = 255;
|
|
346
|
+
}
|
|
347
|
+
pushAttr(vp, 'color', out, 4, 4, 0, undefined, 'u8');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
vp.verticesCount = numPoints;
|
|
351
|
+
return [pointPack()];
|
|
352
|
+
} finally {
|
|
353
|
+
// embind 对象占的是 wasm 堆,不显式释放会一直涨
|
|
354
|
+
try {
|
|
355
|
+
draco.destroy(decoder);
|
|
356
|
+
draco.destroy(geometry);
|
|
357
|
+
} catch {
|
|
358
|
+
// 某些构建没有 destroy,忽略
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* ------------------------------------------------------------ 工具 */
|
|
364
|
+
|
|
365
|
+
/** 点云包:索引只是点序号,交给渲染层走 drawRange */
|
|
366
|
+
function pointPack(): IndexPackage {
|
|
367
|
+
return {
|
|
368
|
+
indexCount: 0,
|
|
369
|
+
indexType: 0,
|
|
370
|
+
useIndex: 0,
|
|
371
|
+
primitiveType: POINT_PRIMITIVE,
|
|
372
|
+
indices: null,
|
|
373
|
+
passNames: [],
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function pushAttr(
|
|
378
|
+
vp: VertexPackage,
|
|
379
|
+
semantic: string,
|
|
380
|
+
data: Uint8Array,
|
|
381
|
+
components: number,
|
|
382
|
+
stride: number,
|
|
383
|
+
offset = 0,
|
|
384
|
+
divisor?: number,
|
|
385
|
+
dtype: AttrDType = 'f32'
|
|
386
|
+
): void {
|
|
387
|
+
vp.attrLocation[semantic] = vp.vertexAttributes.length;
|
|
388
|
+
vp.vertexAttributes.push({
|
|
389
|
+
semantic,
|
|
390
|
+
data,
|
|
391
|
+
components,
|
|
392
|
+
stride,
|
|
393
|
+
offsetInBytes: offset,
|
|
394
|
+
divisor,
|
|
395
|
+
dtype,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function clamp255(v: number): number {
|
|
400
|
+
return v <= 0 ? 0 : v >= 1 ? 255 : Math.round(v * 255);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function safeJson(s: string): unknown {
|
|
404
|
+
try {
|
|
405
|
+
return JSON.parse(s);
|
|
406
|
+
} catch {
|
|
407
|
+
return s;
|
|
408
|
+
}
|
|
409
|
+
}
|
package/src/core/MeshOpt.ts
CHANGED
|
@@ -169,13 +169,57 @@ const SEMANTIC_NAME: Record<number, string> = {
|
|
|
169
169
|
[MeshOptSemantic.GS_ROTATION]: 'gsRotation',
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
+
/** 单块解码出来的属性,最后按语义跨块拼成一条 */
|
|
173
|
+
interface AttrSink {
|
|
174
|
+
dtype: 'f32' | 'u8';
|
|
175
|
+
components: number;
|
|
176
|
+
chunks: Array<Float32Array | Uint8Array>;
|
|
177
|
+
/** 已累计的标量个数(不是顶点数) */
|
|
178
|
+
length: number;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function pushSink(
|
|
182
|
+
sinks: Map<string, AttrSink>,
|
|
183
|
+
semantic: string,
|
|
184
|
+
dtype: 'f32' | 'u8',
|
|
185
|
+
components: number,
|
|
186
|
+
chunk: Float32Array | Uint8Array,
|
|
187
|
+
): void {
|
|
188
|
+
let sink = sinks.get(semantic);
|
|
189
|
+
if (!sink) {
|
|
190
|
+
sink = { dtype, components, chunks: [], length: 0 };
|
|
191
|
+
sinks.set(semantic, sink);
|
|
192
|
+
}
|
|
193
|
+
sink.chunks.push(chunk);
|
|
194
|
+
sink.length += chunk.length;
|
|
195
|
+
}
|
|
196
|
+
|
|
172
197
|
/**
|
|
173
|
-
*
|
|
198
|
+
* 索引是块内局部编号(实测 max index 恰为所在块的 vertexCount - 1),
|
|
199
|
+
* 多块合并成一份顶点缓冲后要整体加上该块的顶点基数。
|
|
200
|
+
*
|
|
201
|
+
* 用 DataView 而不是 Uint32Array:未压缩索引那段是底层 buffer 的 subarray,
|
|
202
|
+
* byteOffset 未必 4 字节对齐,直接建 Uint32Array 会抛 RangeError。
|
|
203
|
+
*/
|
|
204
|
+
function offsetIndices(packs: IndexPackage[], base: number): void {
|
|
205
|
+
for (const pack of packs) {
|
|
206
|
+
const buf = pack.indices;
|
|
207
|
+
if (!buf || pack.indexCount <= 0 || buf.byteLength < pack.indexCount * 4) continue;
|
|
208
|
+
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
209
|
+
for (let i = 0; i < pack.indexCount; i++) {
|
|
210
|
+
dv.setUint32(i * 4, dv.getUint32(i * 4, true) + base, true);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 读取一个 MeshOpt 压缩的 Skeleton,以及**内嵌在块之间的索引包**。
|
|
174
217
|
*
|
|
175
218
|
* 二进制布局(v3.01 实测):
|
|
176
219
|
* ```
|
|
177
220
|
* uint32 compressOptions // 0x13 = Position | Normal | TexCoord
|
|
178
|
-
* uint32 blockCount //
|
|
221
|
+
* uint32 blockCount // 实测 1 或 2
|
|
222
|
+
* ↑ 下面整段重复 blockCount 次:
|
|
179
223
|
* uint32 vertexCount
|
|
180
224
|
* float32 vertCompressConstant
|
|
181
225
|
* float64 minVerticesValue.xyz
|
|
@@ -191,14 +235,27 @@ const SEMANTIC_NAME: Record<number, string> = {
|
|
|
191
235
|
* pad4
|
|
192
236
|
* String descript // {"TextureCoordMatrix":1,"VertexWeight":0}
|
|
193
237
|
* pad4
|
|
238
|
+
* IndexPackage[] // ← 每块各带一份,不是所有块之后再统一放
|
|
194
239
|
* ```
|
|
240
|
+
*
|
|
241
|
+
* 注意最后一行:blockCount > 1 时索引包夹在块与块之间。之前按「读完所有块再读索引包」
|
|
242
|
+
* 处理,会把第一块的索引包当成第二块的块头,读出天文数字的 compressedSize。
|
|
243
|
+
*
|
|
244
|
+
* 多块是同一份几何的拆分,这里把各块属性按顺序拼成一条流,并把各块索引加上
|
|
245
|
+
* 顶点基数偏移,对外仍是一份顶点缓冲 + 多组索引包,下游无需改动。
|
|
195
246
|
*/
|
|
196
|
-
export async function
|
|
247
|
+
export async function parseMeshOptGeometry(
|
|
248
|
+
r: BinaryReader,
|
|
249
|
+
vp: VertexPackage,
|
|
250
|
+
): Promise<{ info: MeshOptBlockInfo; indexPackages: IndexPackage[] }> {
|
|
197
251
|
const decoder = await loadMeshoptDecoder();
|
|
198
252
|
|
|
199
253
|
const compressOptions = r.u32();
|
|
200
254
|
vp.compressOptions = compressOptions;
|
|
201
255
|
const blockCount = r.u32();
|
|
256
|
+
if (blockCount > 64) {
|
|
257
|
+
throw new Error(`[s3m] MeshOpt 块数异常:blockCount=${blockCount}`);
|
|
258
|
+
}
|
|
202
259
|
|
|
203
260
|
const info: MeshOptBlockInfo = {
|
|
204
261
|
vertexCount: 0,
|
|
@@ -216,6 +273,11 @@ export async function parseMeshOptSkeleton(r: BinaryReader, vp: VertexPackage):
|
|
|
216
273
|
streams: [],
|
|
217
274
|
};
|
|
218
275
|
|
|
276
|
+
const sinks = new Map<string, AttrSink>();
|
|
277
|
+
const indexPackages: IndexPackage[] = [];
|
|
278
|
+
let baseVertex = 0;
|
|
279
|
+
let hasCustomAttribute = false;
|
|
280
|
+
|
|
219
281
|
for (let bi = 0; bi < blockCount; bi++) {
|
|
220
282
|
const vertexCount = r.u32();
|
|
221
283
|
const vertScale = r.f32();
|
|
@@ -234,15 +296,12 @@ export async function parseMeshOptSkeleton(r: BinaryReader, vp: VertexPackage):
|
|
|
234
296
|
}
|
|
235
297
|
|
|
236
298
|
const attrCount = r.i32();
|
|
237
|
-
const decodeThisBlock = bi === 0;
|
|
238
299
|
|
|
239
|
-
if (
|
|
240
|
-
info.vertexCount = vertexCount;
|
|
241
|
-
info.vertScale = vertScale;
|
|
300
|
+
if (bi === 0) {
|
|
242
301
|
info.minVertices = [minX, minY, minZ];
|
|
302
|
+
info.vertScale = vertScale;
|
|
243
303
|
info.texCoordScale = texCoordScale;
|
|
244
304
|
info.texCoordOffset = texCoordOffset;
|
|
245
|
-
vp.verticesCount = vertexCount;
|
|
246
305
|
}
|
|
247
306
|
|
|
248
307
|
for (let ai = 0; ai < attrCount; ai++) {
|
|
@@ -260,8 +319,6 @@ export async function parseMeshOptSkeleton(r: BinaryReader, vp: VertexPackage):
|
|
|
260
319
|
const data = r.bytes(compressedSize);
|
|
261
320
|
r.align4();
|
|
262
321
|
|
|
263
|
-
if (!decodeThisBlock) continue;
|
|
264
|
-
|
|
265
322
|
// 切线流我们不用(渲染里没有法线贴图通道),解不出来也不该拖垮整个瓦片:
|
|
266
323
|
// 真实服务里带 PBR 的数据(如 SubwayStationCombined)都带这条流,之前直接抛错导致整套数据不可见
|
|
267
324
|
if (semantic === MeshOptSemantic.TANGENT) {
|
|
@@ -297,18 +354,18 @@ export async function parseMeshOptSkeleton(r: BinaryReader, vp: VertexPackage):
|
|
|
297
354
|
pos[i * 3 + 1] = minY + u16[i * 4 + 1] * vertScale;
|
|
298
355
|
pos[i * 3 + 2] = minZ + u16[i * 4 + 2] * vertScale;
|
|
299
356
|
}
|
|
300
|
-
|
|
357
|
+
pushSink(sinks, 'position', 'f32', 3, pos);
|
|
301
358
|
} else if (semantic === MeshOptSemantic.NORMAL) {
|
|
302
359
|
const i16 = new Int16Array(out.buffer, out.byteOffset, vertexCount * 2);
|
|
303
360
|
const nrm = new Float32Array(vertexCount * 3);
|
|
304
361
|
for (let i = 0; i < vertexCount; i++) {
|
|
305
362
|
octDecode(i16[i * 2] / 32767, i16[i * 2 + 1] / 32767, nrm, i * 3);
|
|
306
363
|
}
|
|
307
|
-
|
|
364
|
+
pushSink(sinks, 'normal', 'f32', 3, nrm);
|
|
308
365
|
} else if (semantic === MeshOptSemantic.COLOR) {
|
|
309
|
-
|
|
366
|
+
pushSink(sinks, 'color', 'u8', 4, out);
|
|
310
367
|
} else if (semantic === MeshOptSemantic.COLOR1) {
|
|
311
|
-
|
|
368
|
+
pushSink(sinks, 'color2', 'u8', 4, out);
|
|
312
369
|
} else if (semantic === MeshOptSemantic.TEXCOORD0 || semantic === MeshOptSemantic.TEXCOORD1) {
|
|
313
370
|
const slot = semantic === MeshOptSemantic.TEXCOORD0 ? 0 : 1;
|
|
314
371
|
const scale = texCoordScale[slot] ?? [0, 0];
|
|
@@ -325,20 +382,69 @@ export async function parseMeshOptSkeleton(r: BinaryReader, vp: VertexPackage):
|
|
|
325
382
|
uv[i * 2 + 1] = offset[1] + u16[i * 2 + 1] * scale[1];
|
|
326
383
|
}
|
|
327
384
|
}
|
|
328
|
-
|
|
385
|
+
pushSink(sinks, slot === 0 ? 'uv' : 'uv2', 'f32', 2, uv);
|
|
386
|
+
} else if (
|
|
387
|
+
semantic === MeshOptSemantic.CUSTOM0 ||
|
|
388
|
+
semantic === MeshOptSemantic.CUSTOM1 ||
|
|
389
|
+
semantic === MeshOptSemantic.GS_SCALE ||
|
|
390
|
+
semantic === MeshOptSemantic.GS_ROTATION
|
|
391
|
+
) {
|
|
392
|
+
// 自定义属性流 / 高斯泼溅的逐点参数:原样保留解码结果(每点 stride 字节),
|
|
393
|
+
// 语义由 vp.customVertexAttribute 的名字→槽位映射给出。
|
|
394
|
+
// 以前这里直接丢掉,导致 3DGaussianSplatting 数据只剩「点 + 颜色」,
|
|
395
|
+
// 上层永远拿不到泼溅尺寸与朝向。
|
|
396
|
+
pushSink(sinks, SEMANTIC_NAME[semantic]!, 'u8', stride, out);
|
|
329
397
|
}
|
|
330
|
-
// tangent / custom / GS 系列暂不使用
|
|
331
398
|
}
|
|
332
399
|
|
|
333
400
|
const descript = r.string();
|
|
334
401
|
r.align4();
|
|
335
|
-
if (
|
|
402
|
+
if (descript && !hasCustomAttribute) {
|
|
403
|
+
hasCustomAttribute = true;
|
|
336
404
|
try {
|
|
337
405
|
vp.customVertexAttribute = JSON.parse(descript);
|
|
338
406
|
} catch {
|
|
339
407
|
vp.customVertexAttribute = descript;
|
|
340
408
|
}
|
|
341
409
|
}
|
|
410
|
+
|
|
411
|
+
// 每块后面紧跟自己的索引包(blockCount = 1 时等价于「骨架之后读索引包」)
|
|
412
|
+
const packs = await parseMeshOptIndexPackages(r);
|
|
413
|
+
if (baseVertex > 0) offsetIndices(packs, baseVertex);
|
|
414
|
+
indexPackages.push(...packs);
|
|
415
|
+
baseVertex += vertexCount;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// blockCount = 0 的老数据:索引包仍然跟在骨架后面
|
|
419
|
+
if (blockCount === 0) {
|
|
420
|
+
indexPackages.push(...(await parseMeshOptIndexPackages(r)));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
info.vertexCount = baseVertex;
|
|
424
|
+
vp.verticesCount = baseVertex;
|
|
425
|
+
// 留下原始流清单:诊断用,也是接自定义属性 / 高斯泼溅属性的入口
|
|
426
|
+
vp.meshOptInfo = info;
|
|
427
|
+
|
|
428
|
+
// 跨块拼接:属性流长度按理各块一致,缺块时补 0,宁可少一块颜色也不整块不可见
|
|
429
|
+
for (const [semantic, sink] of sinks) {
|
|
430
|
+
const expected = baseVertex * sink.components;
|
|
431
|
+
if (sink.dtype === 'f32') {
|
|
432
|
+
const out = new Float32Array(expected);
|
|
433
|
+
let off = 0;
|
|
434
|
+
for (const chunk of sink.chunks) {
|
|
435
|
+
out.set(chunk as Float32Array, off);
|
|
436
|
+
off += chunk.length;
|
|
437
|
+
}
|
|
438
|
+
vp.vertexAttributes.push(f32Attribute(semantic, out, sink.components));
|
|
439
|
+
} else {
|
|
440
|
+
const out = new Uint8Array(expected);
|
|
441
|
+
let off = 0;
|
|
442
|
+
for (const chunk of sink.chunks) {
|
|
443
|
+
out.set(chunk as Uint8Array, off);
|
|
444
|
+
off += chunk.length;
|
|
445
|
+
}
|
|
446
|
+
vp.vertexAttributes.push(u8Attribute(semantic, out, sink.components));
|
|
447
|
+
}
|
|
342
448
|
}
|
|
343
449
|
|
|
344
450
|
// attrLocation 供老接口按名字查索引
|
|
@@ -346,7 +452,7 @@ export async function parseMeshOptSkeleton(r: BinaryReader, vp: VertexPackage):
|
|
|
346
452
|
vp.attrLocation[`a${vp.vertexAttributes[i].semantic}`] = i;
|
|
347
453
|
}
|
|
348
454
|
|
|
349
|
-
return info;
|
|
455
|
+
return { info, indexPackages };
|
|
350
456
|
}
|
|
351
457
|
|
|
352
458
|
/* ------------------------------------------------------------------ 索引包 */
|