///|
/// GLB 二进制解析结果
pub(all) struct GlbData {
/// 解析出的 JSON 字符串
json_str : String
/// 二进制数据块
bin_buffer : Bytes?
} derive(Debug, ToJson)
///|
/// 解析 GLB 二进制流数据
///
/// # Example
/// ```mbt check
/// test "parse glb header mock" {
/// // 一个极简 of GLB 模拟字节流(12字节头部 + JSON chunk 头部 + 数据)
/// // 头部: magic="glTF"(0x46546C67), version=2, length=32
/// // JSON chunk: length=8, type="JSON"(0x4E4F534A), data=`{}` (padded with spaces to 8 bytes)
/// let glb_bytes = Bytes::from_array([
/// 0x67, 0x6c, 0x54, 0x46, // magic
/// 0x02, 0x00, 0x00, 0x00, // version
/// 0x20, 0x00, 0x00, 0x00, // total length (32)
/// 0x08, 0x00, 0x00, 0x00, // chunk 0 length (8)
/// 0x4a, 0x53, 0x4f, 0x4e, // chunk 0 type (JSON)
/// 0x7b, 0x7d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // chunk 0 data: "{}" with spaces
/// ])
/// let glb = @gltf.unpack_glb(glb_bytes)
/// inspect(glb.json_str.trim(), content="{}")
/// }
/// ```
pub fn unpack_glb(data : Bytes) -> GlbData raise ParseError {
let len = data.length()
if len < 12 {
raise ParseError::OutOfRange(msg="GLB header too short")
}
// 1. 读取魔数 "glTF"
let magic = read_uint32_le(data, 0)
if magic != 0x46546C67 {
raise ParseError::InvalidGlbMagic
}
// 2. 读取版本号
let version = read_uint32_le(data, 4).reinterpret_as_int()
if version != 2 {
raise ParseError::UnsupportedGlbVersion(version~)
}
// 3. 读取总长度
let _total_length = read_uint32_le(data, 8).reinterpret_as_int()
// 4. 解析 Chunks
let mut offset = 12
let mut json_str : String? = None
let mut bin_buffer : Bytes? = None
while offset < len {
if offset + 8 > len {
raise ParseError::OutOfRange(msg="GLB chunk header too short")
}
let chunk_len = read_uint32_le(data, offset).reinterpret_as_int()
let chunk_type = read_uint32_le(data, offset + 4)
offset += 8
if offset + chunk_len > len {
raise ParseError::OutOfRange(msg="GLB chunk data exceeds file boundaries")
}
if chunk_type == 0x4E4F534A {
// JSON chunk
let json_bytes = Bytes::makei(chunk_len, i => data[offset + i])
json_str = Some(bytes_to_utf8_string(json_bytes))
} else if chunk_type == 0x004E4942 {
// BIN chunk
let bin_bytes = Bytes::makei(chunk_len, i => data[offset + i])
bin_buffer = Some(bin_bytes)
}
offset += chunk_len
}
match json_str {
Some(js) => { json_str: js, bin_buffer }
None => raise ParseError::MissingField(field="JSON chunk")
}
}
///|
fn read_uint32_le(bytes : Bytes, offset : Int) -> UInt {
let b0 = bytes[offset].to_uint()
let b1 = bytes[offset + 1].to_uint()
let b2 = bytes[offset + 2].to_uint()
let b3 = bytes[offset + 3].to_uint()
b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
}
///|
pub fn bytes_to_utf8_string(bytes : Bytes) -> String {
let sb = StringBuilder()
let mut i = 0
let len = bytes.length()
while i < len {
let b = bytes[i].to_int()
if b < 0x80 {
let char = b.unsafe_to_char()
sb.write_char(char)
i += 1
} else if (b & 0xE0) == 0xC0 {
if i + 1 < len {
let b2 = bytes[i + 1].to_int()
let val = ((b & 0x1F) << 6) | (b2 & 0x3F)
let char = val.unsafe_to_char()
sb.write_char(char)
}
i += 2
} else if (b & 0xF0) == 0xE0 {
if i + 2 < len {
let b2 = bytes[i + 1].to_int()
let b3 = bytes[i + 2].to_int()
let val = ((b & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)
let char = val.unsafe_to_char()
sb.write_char(char)
}
i += 3
} else {
i += 1
}
}
sb.to_string()
}