2020-10-30 20:59:54 +00:00
|
|
|
""" Contains functions for extracting a scene from a .msh file"""
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
from itertools import islice
|
|
|
|
from typing import Dict
|
|
|
|
from .msh_scene import Scene
|
|
|
|
from .msh_model import *
|
|
|
|
from .msh_material import *
|
|
|
|
from .msh_reader import Reader
|
|
|
|
from .msh_utilities import *
|
|
|
|
|
|
|
|
from .crc import *
|
|
|
|
|
2020-11-29 02:42:27 +00:00
|
|
|
model_counter = 0
|
|
|
|
|
2020-12-04 08:33:46 +00:00
|
|
|
mndx_remap = {}
|
|
|
|
|
2020-11-22 05:24:20 +00:00
|
|
|
|
2020-10-20 18:28:53 +00:00
|
|
|
def read_scene(input_file) -> Scene:
|
|
|
|
|
|
|
|
scene = Scene()
|
|
|
|
scene.models = []
|
2020-10-30 20:59:54 +00:00
|
|
|
scene.materials = {}
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-12-04 08:33:46 +00:00
|
|
|
global mndx_remap
|
|
|
|
mndx_remap = {}
|
|
|
|
|
2020-11-22 05:24:20 +00:00
|
|
|
global model_counter
|
|
|
|
model_counter = 0
|
|
|
|
|
2020-12-04 08:33:46 +00:00
|
|
|
with Reader(file=input_file, debug=True) as hedr:
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
while hedr.could_have_child():
|
|
|
|
|
|
|
|
next_header = hedr.peak_next_header()
|
|
|
|
|
|
|
|
if "MSH2" in next_header:
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-11-01 16:48:07 +00:00
|
|
|
with hedr.read_child() as msh2:
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
materials_list = []
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
while (msh2.could_have_child()):
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
next_header = msh2.peak_next_header()
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
if "SINF" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with msh2.read_child() as sinf:
|
2020-10-30 20:59:54 +00:00
|
|
|
pass
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
elif "MATL" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with msh2.read_child() as matl:
|
2020-10-30 20:59:54 +00:00
|
|
|
materials_list += _read_matl_and_get_materials_list(matl)
|
|
|
|
for i,mat in enumerate(materials_list):
|
|
|
|
scene.materials[mat.name] = mat
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
elif "MODL" in next_header:
|
|
|
|
while ("MODL" in msh2.peak_next_header()):
|
2020-11-01 16:48:07 +00:00
|
|
|
with msh2.read_child() as modl:
|
2020-10-30 20:59:54 +00:00
|
|
|
scene.models.append(_read_modl(modl, materials_list))
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
else:
|
2020-12-04 08:33:46 +00:00
|
|
|
msh2.skip_bytes(1)
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-11-01 16:48:07 +00:00
|
|
|
elif "SKL2" in next_header:
|
2020-11-01 23:11:26 +00:00
|
|
|
with hedr.read_child() as skl2:
|
|
|
|
num_bones = skl2.read_u32()
|
|
|
|
scene.skeleton = [skl2.read_u32(5)[0] for i in range(num_bones)]
|
2020-11-22 05:24:20 +00:00
|
|
|
|
2020-11-01 23:11:26 +00:00
|
|
|
elif "ANM2" in next_header:
|
|
|
|
with hedr.read_child() as anm2:
|
|
|
|
_read_anm2(anm2, scene.models)
|
2020-11-01 16:48:07 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
else:
|
2020-12-04 08:33:46 +00:00
|
|
|
hedr.skip_bytes(1)
|
|
|
|
|
2020-11-29 02:42:27 +00:00
|
|
|
|
2020-11-22 05:24:20 +00:00
|
|
|
if scene.skeleton:
|
|
|
|
print("Skeleton models: ")
|
|
|
|
for model in scene.models:
|
2020-12-04 08:33:46 +00:00
|
|
|
for i in range(len(scene.skeleton)):
|
|
|
|
if crc(model.name) == scene.skeleton[i]:
|
|
|
|
print("\t" + model.name)
|
|
|
|
if model.model_type == ModelType.SKIN:
|
|
|
|
scene.skeleton.pop(i)
|
|
|
|
break
|
2020-11-22 05:24:20 +00:00
|
|
|
|
2020-12-04 08:33:46 +00:00
|
|
|
|
|
|
|
for model in scene.models:
|
|
|
|
if model.geometry:
|
|
|
|
for seg in model.geometry:
|
|
|
|
if seg.weights:
|
|
|
|
for weight_set in seg.weights:
|
|
|
|
for vweight in weight_set:
|
|
|
|
vweight.bone = mndx_remap[vweight.bone]
|
2020-11-26 04:10:14 +00:00
|
|
|
|
2020-10-20 18:28:53 +00:00
|
|
|
return scene
|
|
|
|
|
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
def _read_matl_and_get_materials_list(matl: Reader) -> List[Material]:
|
|
|
|
materials_list: List[Material] = []
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
num_mats = matl.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_mats):
|
2020-11-01 16:48:07 +00:00
|
|
|
with matl.read_child() as matd:
|
2020-10-20 18:28:53 +00:00
|
|
|
materials_list.append(_read_matd(matd))
|
|
|
|
|
|
|
|
return materials_list
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _read_matd(matd: Reader) -> Material:
|
|
|
|
|
|
|
|
mat = Material()
|
|
|
|
|
|
|
|
while matd.could_have_child():
|
|
|
|
|
|
|
|
next_header = matd.peak_next_header()
|
|
|
|
|
|
|
|
if "NAME" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with matd.read_child() as name:
|
2020-10-20 18:28:53 +00:00
|
|
|
mat.name = name.read_string()
|
|
|
|
|
|
|
|
elif "DATA" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with matd.read_child() as data:
|
2020-10-20 18:28:53 +00:00
|
|
|
data.read_f32(4) # Diffuse Color (Seams to get ignored by modelmunge)
|
|
|
|
mat.specular_color = data.read_f32(4)
|
|
|
|
data.read_f32(4) # Ambient Color (Seams to get ignored by modelmunge and Zero(?))
|
|
|
|
data.read_f32() # Specular Exponent/Decay (Gets ignored by RedEngine in SWBFII for all known materials)
|
|
|
|
|
|
|
|
elif "ATRB" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with matd.read_child() as atrb:
|
2020-10-20 18:28:53 +00:00
|
|
|
mat.flags = atrb.read_u8()
|
|
|
|
mat.rendertype = atrb.read_u8()
|
|
|
|
mat.data = atrb.read_u8(2)
|
|
|
|
|
|
|
|
elif "TX0D" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with matd.read_child() as tx0d:
|
2020-10-20 18:28:53 +00:00
|
|
|
mat.texture0 = tx0d.read_string()
|
|
|
|
|
|
|
|
elif "TX1D" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with matd.read_child() as tx1d:
|
2020-10-20 18:28:53 +00:00
|
|
|
mat.texture1 = tx1d.read_string()
|
|
|
|
|
|
|
|
elif "TX2D" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with matd.read_child() as tx2d:
|
2020-10-20 18:28:53 +00:00
|
|
|
mat.texture2 = tx2d.read_string()
|
|
|
|
|
|
|
|
elif "TX3D" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with matd.read_child() as tx3d:
|
2020-10-20 18:28:53 +00:00
|
|
|
mat.texture3 = tx3d.read_string()
|
|
|
|
|
|
|
|
else:
|
2020-12-04 08:33:46 +00:00
|
|
|
matd.skip_bytes(1)
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
return mat
|
|
|
|
|
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
def _read_modl(modl: Reader, materials_list: List[Material]) -> Model:
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
model = Model()
|
|
|
|
|
|
|
|
while modl.could_have_child():
|
|
|
|
|
|
|
|
next_header = modl.peak_next_header()
|
|
|
|
|
|
|
|
if "MTYP" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with modl.read_child() as mtyp:
|
2020-10-30 20:59:54 +00:00
|
|
|
model.model_type = ModelType(mtyp.read_u32())
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
elif "MNDX" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with modl.read_child() as mndx:
|
2020-12-04 08:33:46 +00:00
|
|
|
index = mndx.read_u32()
|
|
|
|
|
2020-11-29 02:42:27 +00:00
|
|
|
global model_counter
|
2020-12-04 08:33:46 +00:00
|
|
|
print(mndx.indent + "MNDX doesn't match counter, expected: {} found: {}".format(model_counter, index))
|
|
|
|
|
|
|
|
global mndx_remap
|
|
|
|
mndx_remap[index] = model_counter
|
|
|
|
|
2020-11-29 02:42:27 +00:00
|
|
|
model_counter += 1
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
elif "NAME" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with modl.read_child() as name:
|
2020-10-20 18:28:53 +00:00
|
|
|
model.name = name.read_string()
|
|
|
|
|
|
|
|
elif "PRNT" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with modl.read_child() as prnt:
|
2020-10-20 18:28:53 +00:00
|
|
|
model.parent = prnt.read_string()
|
|
|
|
|
|
|
|
elif "FLGS" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with modl.read_child() as flgs:
|
2020-10-20 18:28:53 +00:00
|
|
|
model.hidden = flgs.read_u32()
|
|
|
|
|
|
|
|
elif "TRAN" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with modl.read_child() as tran:
|
2020-10-20 18:28:53 +00:00
|
|
|
model.transform = _read_tran(tran)
|
|
|
|
|
|
|
|
elif "GEOM" in next_header:
|
|
|
|
model.geometry = []
|
2020-11-29 02:42:27 +00:00
|
|
|
envelope = []
|
|
|
|
|
2020-11-01 16:48:07 +00:00
|
|
|
with modl.read_child() as geom:
|
2020-11-29 02:42:27 +00:00
|
|
|
|
2020-11-01 23:11:26 +00:00
|
|
|
while geom.could_have_child():
|
|
|
|
next_header_geom = geom.peak_next_header()
|
|
|
|
|
|
|
|
if "SEGM" in next_header_geom:
|
|
|
|
with geom.read_child() as segm:
|
|
|
|
model.geometry.append(_read_segm(segm, materials_list))
|
|
|
|
|
|
|
|
elif "ENVL" in next_header_geom:
|
|
|
|
with geom.read_child() as envl:
|
|
|
|
num_indicies = envl.read_u32()
|
2020-12-04 08:33:46 +00:00
|
|
|
envelope += [envl.read_u32() for _ in range(num_indicies)]
|
2020-11-01 23:11:26 +00:00
|
|
|
|
|
|
|
else:
|
2020-12-04 08:33:46 +00:00
|
|
|
geom.skip_bytes(1)
|
|
|
|
#with geom.read_child() as null:
|
|
|
|
#pass
|
2020-11-29 02:42:27 +00:00
|
|
|
|
|
|
|
for seg in model.geometry:
|
2020-12-04 08:33:46 +00:00
|
|
|
if seg.weights and envelope:
|
2020-11-29 02:42:27 +00:00
|
|
|
for weight_set in seg.weights:
|
|
|
|
for i in range(len(weight_set)):
|
2020-12-04 08:33:46 +00:00
|
|
|
vertex_weight = weight_set[i]
|
|
|
|
index = vertex_weight.bone
|
|
|
|
weight_set[i] = VertexWeight(vertex_weight.weight, envelope[vertex_weight.bone])
|
2020-10-30 20:59:54 +00:00
|
|
|
|
2020-10-20 18:28:53 +00:00
|
|
|
elif "SWCI" in next_header:
|
|
|
|
prim = CollisionPrimitive()
|
2020-11-01 16:48:07 +00:00
|
|
|
with modl.read_child() as swci:
|
|
|
|
prim.shape = CollisionPrimitiveShape(swci.read_u32())
|
2020-10-20 18:28:53 +00:00
|
|
|
prim.radius = swci.read_f32()
|
|
|
|
prim.height = swci.read_f32()
|
|
|
|
prim.length = swci.read_f32()
|
|
|
|
model.collisionprimitive = prim
|
|
|
|
|
|
|
|
else:
|
2020-12-04 08:33:46 +00:00
|
|
|
modl.skip_bytes(1)
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-12-04 08:33:46 +00:00
|
|
|
print(modl.indent + "Read model " + model.name + " of type: " + str(model.model_type)[10:])
|
2020-11-22 05:24:20 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
return model
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _read_tran(tran: Reader) -> ModelTransform:
|
|
|
|
|
|
|
|
xform = ModelTransform()
|
|
|
|
|
2020-11-01 23:11:26 +00:00
|
|
|
tran.skip_bytes(12) #ignore scale
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
rot = tran.read_f32(4)
|
|
|
|
xform.rotation = Quaternion((rot[3], rot[0], rot[1], rot[2]))
|
|
|
|
xform.translation = Vector(tran.read_f32(3))
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-12-04 06:22:58 +00:00
|
|
|
print(tran.indent + "Rot: {} Loc: {}".format(str(xform.rotation), str(xform.translation)))
|
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
return xform
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
|
2020-10-30 20:59:54 +00:00
|
|
|
def _read_segm(segm: Reader, materials_list: List[Material]) -> GeometrySegment:
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
geometry_seg = GeometrySegment()
|
|
|
|
|
|
|
|
while segm.could_have_child():
|
|
|
|
|
|
|
|
next_header = segm.peak_next_header()
|
|
|
|
|
|
|
|
if "MATI" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with segm.read_child() as mati:
|
2020-10-30 20:59:54 +00:00
|
|
|
geometry_seg.material_name = materials_list[mati.read_u32()].name
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
elif "POSL" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with segm.read_child() as posl:
|
2020-10-20 18:28:53 +00:00
|
|
|
num_positions = posl.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_positions):
|
|
|
|
geometry_seg.positions.append(Vector(posl.read_f32(3)))
|
|
|
|
|
|
|
|
elif "NRML" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with segm.read_child() as nrml:
|
2020-10-20 18:28:53 +00:00
|
|
|
num_normals = nrml.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_positions):
|
|
|
|
geometry_seg.normals.append(Vector(nrml.read_f32(3)))
|
|
|
|
|
|
|
|
elif "CLRL" in next_header:
|
|
|
|
geometry_seg.colors = []
|
|
|
|
|
2020-11-01 16:48:07 +00:00
|
|
|
with segm.read_child() as clrl:
|
2020-10-20 18:28:53 +00:00
|
|
|
num_colors = clrl.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_colors):
|
|
|
|
geometry_seg.colors += unpack_color(clrl.read_u32())
|
|
|
|
|
|
|
|
elif "UV0L" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with segm.read_child() as uv0l:
|
2020-10-20 18:28:53 +00:00
|
|
|
num_texcoords = uv0l.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_texcoords):
|
|
|
|
geometry_seg.texcoords.append(Vector(uv0l.read_f32(2)))
|
|
|
|
|
|
|
|
elif "NDXL" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with segm.read_child() as ndxl:
|
2020-10-20 18:28:53 +00:00
|
|
|
num_polygons = ndxl.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_polygons):
|
|
|
|
polygon = ndxl.read_u16(ndxl.read_u16())
|
|
|
|
geometry_seg.polygons.append(polygon)
|
|
|
|
|
|
|
|
elif "NDXT" in next_header:
|
2020-11-01 16:48:07 +00:00
|
|
|
with segm.read_child() as ndxt:
|
2020-10-20 18:28:53 +00:00
|
|
|
num_tris = ndxt.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_tris):
|
|
|
|
geometry_seg.triangles.append(ndxt.read_u16(3))
|
|
|
|
|
|
|
|
elif "STRP" in next_header:
|
2020-12-04 08:33:46 +00:00
|
|
|
strips : List[List[int]] = []
|
|
|
|
|
2020-11-01 16:48:07 +00:00
|
|
|
with segm.read_child() as strp:
|
2020-12-04 08:33:46 +00:00
|
|
|
num_indicies = strp.read_u32()
|
|
|
|
|
|
|
|
num_indicies_read = 0
|
|
|
|
|
|
|
|
curr_strip = []
|
|
|
|
previous_flag = False
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-12-04 08:33:46 +00:00
|
|
|
if num_indicies > 0:
|
|
|
|
index, index1 = strp.read_u16(2)
|
|
|
|
curr_strip = [index & 0x7fff, index1 & 0x7fff]
|
|
|
|
num_indicies_read += 2
|
|
|
|
|
|
|
|
for i in range(num_indicies - 2):
|
|
|
|
index = strp.read_u16(1)
|
|
|
|
|
|
|
|
if index & 0x8000 > 0:
|
|
|
|
index = index & 0x7fff
|
|
|
|
|
|
|
|
if previous_flag:
|
|
|
|
previous_flag = False
|
|
|
|
curr_strip.append(index)
|
|
|
|
strips.append(curr_strip[:-2])
|
|
|
|
curr_strip = curr_strip[-2:]
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
previous_flag = True
|
|
|
|
|
|
|
|
else:
|
|
|
|
previous_flag = False
|
|
|
|
|
|
|
|
curr_strip.append(index)
|
|
|
|
|
|
|
|
geometry_seg.triangle_strips = strips
|
|
|
|
|
|
|
|
#if segm.read_u16 != 0: #trailing 0 bug https://schlechtwetterfront.github.io/ze_filetypes/msh.html#STRP
|
|
|
|
# segm.skip_bytes(-2)
|
2020-10-20 18:28:53 +00:00
|
|
|
|
2020-11-01 23:11:26 +00:00
|
|
|
elif "WGHT" in next_header:
|
2020-11-26 04:10:14 +00:00
|
|
|
with segm.read_child() as wght:
|
2020-11-29 02:42:27 +00:00
|
|
|
|
2020-11-26 04:10:14 +00:00
|
|
|
geometry_seg.weights = []
|
|
|
|
num_weights = wght.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_weights):
|
|
|
|
weight_set = []
|
|
|
|
for _ in range(4):
|
|
|
|
index = wght.read_u32()
|
|
|
|
value = wght.read_f32()
|
|
|
|
|
|
|
|
if value > 0.000001:
|
2020-12-04 08:33:46 +00:00
|
|
|
weight_set.append(VertexWeight(value,index))
|
2020-11-01 23:11:26 +00:00
|
|
|
|
2020-11-26 04:10:14 +00:00
|
|
|
geometry_seg.weights.append(weight_set)
|
2020-11-29 02:42:27 +00:00
|
|
|
|
2020-10-20 18:28:53 +00:00
|
|
|
else:
|
2020-12-04 08:33:46 +00:00
|
|
|
segm.skip_bytes(1)
|
2020-10-20 18:28:53 +00:00
|
|
|
|
|
|
|
return geometry_seg
|
|
|
|
|
|
|
|
|
2020-11-01 16:48:07 +00:00
|
|
|
|
2020-11-01 23:11:26 +00:00
|
|
|
def _read_anm2(anm2: Reader, models):
|
|
|
|
|
|
|
|
hash_dict = {}
|
|
|
|
for model in models:
|
|
|
|
hash_dict[crc(model.name)] = model.name
|
|
|
|
|
|
|
|
while anm2.could_have_child():
|
|
|
|
|
|
|
|
next_header = anm2.peak_next_header()
|
|
|
|
|
|
|
|
if "CYCL" in next_header:
|
|
|
|
with anm2.read_child() as cycl:
|
|
|
|
pass
|
|
|
|
|
|
|
|
elif "KFR3" in next_header:
|
|
|
|
with anm2.read_child() as kfr3:
|
|
|
|
|
|
|
|
num_bones = kfr3.read_u32()
|
|
|
|
|
|
|
|
for _ in range(num_bones):
|
|
|
|
|
|
|
|
kfr3.read_u32()
|
|
|
|
|
|
|
|
frametype = kfr3.read_u32()
|
|
|
|
|
|
|
|
num_loc_frames = kfr3.read_u32()
|
|
|
|
num_rot_frames = kfr3.read_u32()
|
|
|
|
|
|
|
|
kfr3.skip_bytes(16 * num_loc_frames + 20 * num_rot_frames)
|
|
|
|
|
|
|
|
|
|
|
|
|