hexpat: Add hexpat and MRP Python script for .msh files

Documentation is always nice.
This commit is contained in:
Lily Tsuru 2025-01-05 20:36:48 -05:00
parent 73e16a8b72
commit 788fcd9677
2 changed files with 177 additions and 0 deletions

View file

@ -0,0 +1,96 @@
#
# EuropaTools
#
# (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This is a Model Researcher Pro script which can be used
# to automatically display .msh meshes inside of it.
#
# While I haven't written a native exporter yet, this should *not*
# be considered a replacement for that, especially given the issues:
# - python (its slow as balls)
# - Model Researcher is Windows-only
# - Generally this is just a quick hack and it shows in code quality.
import mrp
bf = mrp.get_bfile()
# read '\r\n' terminated string
# why the fuck did they do this
def Storage_ReadLine():
ret = ""
rntest = b""
while True:
a = bf.reads("1s")[0]
if a == b'\r' or a == b'\n':
rntest += a
if rntest == b'\r\n':
return ret
else:
ret += a.decode('ascii')
def ReadMeshBlock(readCallback):
count = bf.readShort()
data = None
if count != 0:
data = readCallback(count)
return (count, data)
def ReadVec3(count):
return bf.read(count * (3 * 4))
def ReadVertexColors(count):
return bf.read(count * 4)
def ReadFaces(count):
return bf.read(count * (3 * 2))
# main
# Make sure this is a MESH and it's a 2.0 version
# 1.2 version is actually text-format and I can't be bothered
# to support it.
if Storage_ReadLine() != 'MESH':
raise ValueError('not a Europa msh')
if Storage_ReadLine() != '2.0':
raise ValueError('not a Europa msh 2.0 (this parses only binary version 2.0)')
mesh_count = bf.readShort()
for i in range(0, mesh_count):
mtlFilename = Storage_ReadLine()
# read some pieces
verts = ReadMeshBlock(ReadVec3)
vertcolors = ReadMeshBlock(ReadVertexColors)
normals = ReadMeshBlock(ReadVec3)
# Need to manually ReadMeshBlock() here
# since we need vert count to read the UV block
uvCount = bf.readShort()
uv = None
if uvCount != 0:
print(f' {uvCount} uvs')
uv = []
for i in range(0, uvCount):
uv_bytes = bf.read(verts[0] * (2 * 4))
uv.append(uv_bytes)
faces = ReadMeshBlock(ReadFaces)
# Add it and set everything up, add verts, faces,
# and normals/UV if provided
mesh = mrp.create_mesh(mtlFilename)
mesh.set_vertices(verts[1], tp="Float")
mesh.set_faces(faces[1], tp="Short")
if normals[0] != 0:
mesh.set_normals(normals[1], tp="Float")
if uvCount != 0:
mesh.set_uvs(uv[0], tp="Float")
print(f'Read mesh {mtlFilename}')
mrp.render("All")

81
hexpat/msh.hexpat Normal file
View file

@ -0,0 +1,81 @@
//
// EuropaTools
//
// (C) 2021-2025 modeco80 <lily.modeco80@protonmail.ch>
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
import std.io;
import std.mem;
// Fundamental Types
struct Vec3 {
float x;
float y;
float z;
// std::print("Vec3: {:4.4f}, {:4.4f}, {:4.4f}",
// x,
// y,
// z
//);
};
struct Uvf {
float u;
float v;
};
struct Face {
u16 faceX;
u16 faceY;
u16 faceZ;
};
// Europa .MSH types
// a generic block
struct tMeshBlock<Item> {
u16 count;
// count == 0 means no items in block
if(count != 0) {
std::print("Block with {:6d} items", count);
Item items[count];
}
};
// Used for vertex colors.
struct VertexColor {
u8 r;
u8 g;
u8 b;
u8 a;
};
struct UvBlockEntry {
Uvf uvs[parent.parent.vertexBlock.count];
};
struct MeshPiece {
char mtl_filename[while(std::mem::read_unsigned($-1, 1) != '\n')];
tMeshBlock<Vec3> vertexBlock;
tMeshBlock<VertexColor> vertexColorBlock;
tMeshBlock<Vec3> normalBlock;
tMeshBlock<UvBlockEntry> uvBlock;
tMeshBlock<Face> faceBlock;
};
struct MshFile {
// This should just be read as lines in any real code.
// I just do this to feel something
char magic[while(std::mem::read_unsigned($-1, 1) != '\n')];
char version[while(std::mem::read_unsigned($, 1) != '\n')];
char dummy; // fuckkk dude
// There is a meshpiece per material change I guess.
tMeshBlock<MeshPiece> meshPieceBlock;
};
MshFile msh @ 0;