Files
Asset_Inspectors/loaders/imesh_loader.py
2025-12-30 00:25:33 -06:00

157 lines
5.0 KiB
Python

import struct
IMSH_MAGIC = b"IMSH"
IMSH_VERSION = 2
IMSH_HEADER_STRUCT = struct.Struct("<4sIIII" + "IHH" + "I" + "Q")
IMSH_SUBMESH_STRUCT = struct.Struct("<IIQ" + "IHH" + "ffffff" + "IIQ")
IMSH_LOD_STRUCT = struct.Struct("<IIQQ")
IMSH_HEADER_SIZE = IMSH_HEADER_STRUCT.size
IMSH_SUBMESH_SIZE = IMSH_SUBMESH_STRUCT.size
IMSH_LOD_SIZE = IMSH_LOD_STRUCT.size
MODEL_VERTEX_STRIDE = 48
def _h_from(value, typ, meta):
return (int(value) & 0xFFFFFFFF, int(typ) & 0xFFFF, int(meta) & 0xFFFF)
def _h_is_zero(h):
return (h[0] | h[1] | h[2]) == 0
def load_imesh_from_bytes(data, base_off=0):
n = len(data)
if base_off < 0 or base_off > n:
raise ValueError("IMSH: bad base_off")
if base_off + IMSH_HEADER_SIZE > n:
raise ValueError("IMSH: too small")
(magic, version, flags, submesh_count, reserved0,
model_value, model_type, model_meta,
_pad0,
submesh_table_offset) = IMSH_HEADER_STRUCT.unpack_from(data, base_off)
if magic != IMSH_MAGIC:
raise ValueError("IMSH: bad magic")
if version != IMSH_VERSION:
raise ValueError("IMSH: bad version")
submesh_table_offset = int(submesh_table_offset)
if submesh_table_offset < 0 or submesh_table_offset >= (n - base_off):
raise ValueError("IMSH: submesh table offset out of range")
smt_off = base_off + submesh_table_offset
need = smt_off + int(submesh_count) * IMSH_SUBMESH_SIZE
if need > n:
raise ValueError("IMSH: submesh table out of range")
handle = _h_from(model_value, model_type, model_meta)
refs = []
submeshes = []
for i in range(int(submesh_count)):
o = smt_off + i * IMSH_SUBMESH_SIZE
(sm_flags,
material_name_len,
material_name_offset,
mat_value, mat_type, mat_meta,
aabb_min_x, aabb_min_y, aabb_min_z,
aabb_max_x, aabb_max_y, aabb_max_z,
lod_count,
sm_reserved0,
lods_offset) = IMSH_SUBMESH_STRUCT.unpack_from(data, o)
material_name_len = int(material_name_len)
material_name_offset = int(material_name_offset)
lod_count = int(lod_count)
lods_offset = int(lods_offset)
mat_handle = _h_from(mat_value, mat_type, mat_meta)
if not _h_is_zero(mat_handle):
refs.append(mat_handle)
else:
mat_handle = None
mat_name = None
if material_name_len > 0:
mo = base_off + material_name_offset
me = mo + material_name_len
if mo < base_off or me > n:
raise ValueError("IMSH: material name out of range")
mat_name = data[mo:me].decode("utf-8", errors="ignore")
if lod_count <= 0:
raise ValueError("IMSH: lod_count=0")
lods_abs = base_off + lods_offset
lods_need = lods_abs + lod_count * IMSH_LOD_SIZE
if lods_abs < base_off or lods_need > n:
raise ValueError("IMSH: lod table out of range")
lods = []
for li in range(lod_count):
lo = lods_abs + li * IMSH_LOD_SIZE
vcount, icount, voff, ioff = IMSH_LOD_STRUCT.unpack_from(data, lo)
vcount = int(vcount)
icount = int(icount)
voff = int(voff)
ioff = int(ioff)
if vcount <= 0 or icount <= 0:
raise ValueError("IMSH: empty lod")
vb = base_off + voff
ib = base_off + ioff
vbytes = vcount * MODEL_VERTEX_STRIDE
ibytes = icount * 4
if vb < base_off or vb + vbytes > n:
raise ValueError("IMSH: vertices out of range")
if ib < base_off or ib + ibytes > n:
raise ValueError("IMSH: indices out of range")
lods.append({
"vertex_count": vcount,
"index_count": icount,
"vertices_offset": voff,
"indices_offset": ioff,
"vertices_size": vbytes,
"indices_size": ibytes,
"vertex_stride": MODEL_VERTEX_STRIDE,
})
submeshes.append({
"flags": int(sm_flags),
"material_handle": mat_handle,
"material_name": mat_name,
"aabb_min": (float(aabb_min_x), float(aabb_min_y), float(aabb_min_z)),
"aabb_max": (float(aabb_max_x), float(aabb_max_y), float(aabb_max_z)),
"lod_count": lod_count,
"lods_offset": lods_offset,
"lods": lods,
})
info = {
"version": int(version),
"flags": int(flags),
"submesh_count": int(submesh_count),
"submesh_table_offset": int(submesh_table_offset),
"decode_offset": int(base_off),
"header_size": int(IMSH_HEADER_SIZE),
"submesh_record_size": int(IMSH_SUBMESH_SIZE),
"lod_record_size": int(IMSH_LOD_SIZE),
"vertex_stride": int(MODEL_VERTEX_STRIDE),
}
return {
"handle": handle,
"refs": refs,
"submeshes": submeshes,
"info": info,
"blob": data,
"base_off": int(base_off),
}