Add conversion of models from .RSD to .MDL

This commit is contained in:
Lucas S. Vieira 2024-09-21 01:29:54 -03:00
parent b0fd36db4f
commit f13ddc3c3d
15 changed files with 2797 additions and 1774 deletions

BIN
assets/objs/common/ring.mdl Normal file

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -98,6 +98,14 @@
</dir> <!-- LEVELS -->
<dir name="OBJS">
<dir name="COMMON">
<file name="RING.MDL"
type="data"
source="${PROJECT_SOURCE_DIR}/assets/objs/common/ring.mdl" />
</dir> <!-- COMMON -->
</dir> <!-- OBJS -->
<dir name="MISC">
<file name="DISK.TIM"
type="data"

2
tools/convrsd/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
__pycache__/

44
tools/convrsd/common.py Normal file
View file

@ -0,0 +1,44 @@
from ctypes import c_ubyte, c_short, c_ushort
from enum import Enum
c_short = c_short.__ctype_be__
c_ushort = c_ushort.__ctype_be__
# c_int = c_int.__ctype_be__
# c_uint = c_uint.__ctype_be__
class SVECTOR:
vx: c_short
vy: c_short
vz: c_short
def __repr__(self):
return f"{{ {self.vx.value:05X}, {self.vy.value:05X}, {self.vz.value:05X} }}"
# ------------------------------------------
class FaceType(Enum):
TRIANGLE = 0
QUAD = 1
LINE = 2
SPRITE = 3
class MaterialType(Enum):
Flat = "C"
Gouraud = "G"
Texture = "T"
TextureFlat = "D"
TextureGouraud = "H"
# Don't care about these others:
MATERIAL_W = "W" # Repeating textures, no-color
MATERIAL_S = "S" # Repeating textures, flat colored
MATERIAL_N = "N" # Repeating textures, gouraud-shaded
class ShadingType(Enum):
FLAT = "F"
GOURAUD = "G"

320
tools/convrsd/convrsd.py Executable file
View file

@ -0,0 +1,320 @@
#!/bin/env python
import os
import sys
from pprint import pp as pprint
from dataclasses import dataclass, field
from fixedpoint import tofixed12
from common import *
from face import *
from material import *
from import_types import *
from export_types import *
# ------------------------------------------
def parse_rsd(filename: str) -> RSDModel:
if not os.path.isfile(filename):
print(f"File {filename} does not exist")
exit(1)
filename = os.path.abspath(filename)
rsd = RSDModel()
with open(filename, "r") as f:
# Read magic
while True:
rsd.magic = f.readline().strip()
if not rsd.magic:
print(f"Unexpected EOF at {filename}")
exit(1)
if rsd.magic[0] == "#":
continue
if rsd.magic[:4] != "@RSD":
print(f"Could not parse RSD file {filename}")
exit(1)
break
ply_filename = None
mat_filename = None
# Read other lines
while True:
buffer = f.readline().strip()
if not buffer:
break
elif buffer[:3] == "PLY":
ply_filename = buffer[4:]
elif buffer[:3] == "MAT":
mat_filename = buffer[4:]
elif buffer[:4] == "MTEX":
# TODO
pass
if ply_filename:
ply_filename = os.path.dirname(filename) + "/" + ply_filename
rsd.ply = parse_ply(ply_filename.strip())
if mat_filename:
mat_filename = os.path.dirname(filename) + "/" + mat_filename
rsd.mat = parse_mat(mat_filename.strip())
return rsd
# ------------------------------------------
def _parse_face(buffer: str) -> Face:
buffer = buffer.split()
ftype = FaceType(int(buffer[0]))
if ftype == FaceType.TRIANGLE:
v = TriangleFace()
v.iv0 = c_ushort(int(buffer[1]))
v.iv1 = c_ushort(int(buffer[2]))
v.iv2 = c_ushort(int(buffer[3]))
v.in0 = c_ushort(int(buffer[5]))
v.in1 = c_ushort(int(buffer[6]))
v.in2 = c_ushort(int(buffer[7]))
return v
elif ftype == FaceType.QUAD:
v = QuadFace()
v.iv0 = c_ushort(int(buffer[1]))
v.iv1 = c_ushort(int(buffer[2]))
v.iv2 = c_ushort(int(buffer[3]))
v.iv3 = c_ushort(int(buffer[4]))
v.in0 = c_ushort(int(buffer[5]))
v.in1 = c_ushort(int(buffer[6]))
v.in2 = c_ushort(int(buffer[7]))
v.iv3 = c_ushort(int(buffer[8]))
return v
elif ftype == FaceType.LINE:
v = LineFace()
v.iv0 = c_ushort(int(buffer[1]))
v.iv1 = c_ushort(int(buffer[2]))
return v
elif ftype == FaceType.SPRITE:
v = SpriteFace()
v.iv0 = c_ushort(int(buffer[1]))
v.iv1 = c_ushort(int(buffer[2]))
v.iw = c_ushort(int(buffer[3]))
v.ih = c_ushort(int(buffer[4]))
return v
else:
print(f"Unknown face type {ftype}")
exit(1)
def parse_ply(filename: str) -> PlyData:
if not os.path.isfile(filename):
print(f"File {filename} does not exist")
exit(1)
ply = PlyData()
with open(filename, "r") as f:
# Read magic
while True:
ply.magic = f.readline().strip()
if not ply.magic:
print(f"Unexpected EOF at {filename}")
exit(1)
if ply.magic[0] == "#":
continue
if ply.magic[:4] != "@PLY":
print(f"Could not parse PLY file {filename}")
exit(1)
break
step = 0
vertices = 0
normals = 0
faces = 0
while True:
buffer = f.readline().strip()
if not buffer:
break
elif buffer[0] == "#":
continue
if step == 0:
numbers = buffer.split()
ply.num_vertices = c_ushort(int(numbers[0]))
ply.num_normals = c_ushort(int(numbers[1]))
ply.num_faces = c_ushort(int(numbers[2]))
vertices = ply.num_vertices.value
normals = ply.num_normals.value
faces = ply.num_faces.value
step += 1
elif step == 1:
if vertices > 0:
values = buffer.split()
values = [tofixed12(float(x)) for x in values]
vertex = SVECTOR()
vertex.vx = c_short(values[0])
vertex.vy = c_short(values[1])
vertex.vz = c_short(values[2])
ply.vertices.append(vertex)
vertices -= 1
elif normals > 0:
values = buffer.split()
values = [tofixed12(float(x)) for x in values]
normal = SVECTOR()
normal.vx = c_short(values[0])
normal.vy = c_short(values[1])
normal.vz = c_short(values[2])
ply.normals.append(normal)
normals -= 1
elif faces > 0:
ply.faces.append(_parse_face(buffer))
faces -= 1
else:
step += 1
else:
break
assert ply.num_vertices.value == len(ply.vertices)
assert ply.num_normals.value == len(ply.normals)
assert ply.num_faces.value == len(ply.faces)
return ply
# ------------------------------------------
def _parse_material(buffer: str) -> Material:
buffer = buffer.split()
material = Material()
material.ipolygon = c_ushort(int(buffer[0]))
material.flag = c_ubyte(int(buffer[1]))
material.shading = ShadingType(buffer[2])
mtype = MaterialType(buffer[3])
if mtype == MaterialType.Flat:
material.info = FlatMaterial()
material.info.r0 = c_ubyte(int(buffer[4]))
material.info.g0 = c_ubyte(int(buffer[5]))
material.info.b0 = c_ubyte(int(buffer[6]))
elif mtype == MaterialType.Gouraud:
material.info = GouraudMaterial()
material.info.r0 = c_ubyte(int(buffer[4]))
material.info.g0 = c_ubyte(int(buffer[5]))
material.info.b0 = c_ubyte(int(buffer[6]))
material.info.r1 = c_ubyte(int(buffer[5]))
material.info.g1 = c_ubyte(int(buffer[6]))
material.info.b1 = c_ubyte(int(buffer[7]))
material.info.r2 = c_ubyte(int(buffer[8]))
material.info.g2 = c_ubyte(int(buffer[9]))
material.info.b2 = c_ubyte(int(buffer[10]))
material.info.r3 = c_ubyte(int(buffer[11]))
material.info.g3 = c_ubyte(int(buffer[12]))
material.info.b3 = c_ubyte(int(buffer[13]))
elif mtype == MaterialType.Texture:
m = TextureMaterial()
material.info.u0 = c_ubyte(int(buffer[4]))
material.info.v0 = c_ubyte(int(buffer[5]))
material.info.u1 = c_ubyte(int(buffer[6]))
material.info.v1 = c_ubyte(int(buffer[7]))
material.info.u2 = c_ubyte(int(buffer[8]))
material.info.v2 = c_ubyte(int(buffer[9]))
material.info.u3 = c_ubyte(int(buffer[10]))
material.info.v3 = c_ubyte(int(buffer[11]))
elif mtype == MaterialType.TextureFlat:
material.info = TextureFlatMaterial()
material.info.u0 = c_ubyte(int(buffer[4]))
material.info.v0 = c_ubyte(int(buffer[5]))
material.info.u1 = c_ubyte(int(buffer[6]))
material.info.v1 = c_ubyte(int(buffer[7]))
material.info.u2 = c_ubyte(int(buffer[8]))
material.info.v2 = c_ubyte(int(buffer[9]))
material.info.u3 = c_ubyte(int(buffer[10]))
material.info.v3 = c_ubyte(int(buffer[11]))
material.info.r0 = c_ubyte(int(buffer[12]))
material.info.g0 = c_ubyte(int(buffer[13]))
material.info.b0 = c_ubyte(int(buffer[14]))
elif mtype == MaterialType.TextureGouraud:
material.info = TextureGouraudMaterial()
material.info.u0 = c_ubyte(int(buffer[4]))
material.info.v0 = c_ubyte(int(buffer[5]))
material.info.u1 = c_ubyte(int(buffer[6]))
material.info.v1 = c_ubyte(int(buffer[7]))
material.info.u2 = c_ubyte(int(buffer[8]))
material.info.v2 = c_ubyte(int(buffer[9]))
material.info.u3 = c_ubyte(int(buffer[10]))
material.info.v3 = c_ubyte(int(buffer[11]))
material.info.r0 = c_ubyte(int(buffer[12]))
material.info.g0 = c_ubyte(int(buffer[13]))
material.info.b0 = c_ubyte(int(buffer[14]))
material.info.r1 = c_ubyte(int(buffer[15]))
material.info.g1 = c_ubyte(int(buffer[16]))
material.info.b1 = c_ubyte(int(buffer[17]))
material.info.r2 = c_ubyte(int(buffer[18]))
material.info.g2 = c_ubyte(int(buffer[19]))
material.info.b2 = c_ubyte(int(buffer[20]))
material.info.r3 = c_ubyte(int(buffer[21]))
material.info.g3 = c_ubyte(int(buffer[22]))
material.info.b3 = c_ubyte(int(buffer[23]))
else:
print(f"Unhandled material type {mtype}")
exit(1)
return material
def parse_mat(filename: str) -> MatData:
if not os.path.isfile(filename):
print(f"File {filename} does not exist")
exit(1)
mat = MatData()
with open(filename, "r") as f:
# Read magic
while True:
mat.magic = f.readline().strip()
if not mat.magic:
print(f"Unexpected EOF at {filename}")
exit(1)
if mat.magic[0] == "#":
continue
if mat.magic[:4] != "@MAT":
print(f"Could not parse MAT file {filename}")
exit(1)
break
step = 0
items = 0
while True:
buffer = f.readline().strip()
if not buffer:
break
elif buffer[0] == "#":
continue
if step == 0:
mat.num_items = c_ushort(int(buffer))
items = mat.num_items.value
step += 1
elif step == 1:
if items > 0:
mat.materials.append(_parse_material(buffer))
items -= 1
else:
step += 1
else:
break
assert mat.num_items.value == len(mat.materials)
return mat
def main():
filename = sys.argv[1]
rsd = parse_rsd(filename.strip())
mdl = convert_model(rsd)
out_filename = os.path.splitext(filename)[0] + ".mdl"
with open(out_filename, "wb") as f:
mdl.write(f)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,581 @@
from dataclasses import dataclass, field
from common import SVECTOR
from enum import Enum
from face import *
from material import *
from import_types import RSDModel
class PolyType(Enum):
F3 = c_ubyte(0)
G3 = c_ubyte(1)
F4 = c_ubyte(2)
G4 = c_ubyte(3)
FT3 = c_ubyte(4)
GT3 = c_ubyte(5)
FT4 = c_ubyte(6)
GT4 = c_ubyte(7)
@dataclass
class PolyF3:
ftype: c_ubyte = PolyType.F3
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
@staticmethod
def from_pair(face: TriangleFace, material: FlatMaterial) -> "PolyF3":
p = PolyF3()
p.r0 = material.r0
p.g0 = material.g0
p.b0 = material.b0
p.iv0 = face.iv0
p.iv1 = face.iv1
p.iv2 = face.iv2
p.in0 = face.in0
return p
def write(self, f):
f.write(self.ftype.value)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.in0)
@dataclass
class PolyG3:
ftype: c_ubyte = PolyType.G3
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
r1: c_ubyte = c_ubyte(0)
g1: c_ubyte = c_ubyte(0)
b1: c_ubyte = c_ubyte(0)
r2: c_ubyte = c_ubyte(0)
g2: c_ubyte = c_ubyte(0)
b2: c_ubyte = c_ubyte(0)
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
in1: c_ushort = c_ushort(0)
in2: c_ushort = c_ushort(0)
@staticmethod
def from_pair(face: TriangleFace, material: GouraudMaterial) -> "PolyG3":
p = PolyG3()
p.r0 = material.r0
p.g0 = material.g0
p.b0 = material.b0
p.r1 = material.r1
p.g1 = material.g1
p.b1 = material.b1
p.r2 = material.r2
p.g2 = material.g2
p.b2 = material.b2
p.iv0 = face.iv0
p.iv1 = face.iv1
p.iv2 = face.iv2
p.in0 = face.in0
p.in1 = face.in1
p.in2 = face.in2
return p
def write(self, f):
f.write(self.ftype.value)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.r1)
f.write(self.g1)
f.write(self.b1)
f.write(self.r2)
f.write(self.g2)
f.write(self.b2)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.in0)
f.write(self.in1)
f.write(self.in2)
@dataclass
class PolyF4:
ftype: c_ubyte = PolyType.F4
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
iv3: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
@staticmethod
def from_pair(face: QuadFace, material: FlatMaterial) -> "PolyF4":
p = PolyF4()
p.r0 = material.r0
p.g0 = material.g0
p.b0 = material.b0
p.iv0 = face.iv0
p.iv1 = face.iv1
p.iv2 = face.iv2
p.iv3 = face.iv3
p.in0 = face.in0
return p
def write(self, f):
f.write(self.ftype.value)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.iv3)
f.write(self.in0)
@dataclass
class PolyG4:
ftype: c_ubyte = PolyType.G4
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
r1: c_ubyte = c_ubyte(0)
g1: c_ubyte = c_ubyte(0)
b1: c_ubyte = c_ubyte(0)
r2: c_ubyte = c_ubyte(0)
g2: c_ubyte = c_ubyte(0)
b2: c_ubyte = c_ubyte(0)
r3: c_ubyte = c_ubyte(0)
g3: c_ubyte = c_ubyte(0)
b3: c_ubyte = c_ubyte(0)
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
iv3: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
in1: c_ushort = c_ushort(0)
in2: c_ushort = c_ushort(0)
in3: c_ushort = c_ushort(0)
@staticmethod
def from_pair(face: QuadFace, material: GouraudMaterial) -> "PolyG4":
p = PolyG4()
p.r0 = material.r0
p.g0 = material.g0
p.b0 = material.b0
p.r1 = material.r1
p.g1 = material.g1
p.b1 = material.b1
p.r2 = material.r2
p.g2 = material.g2
p.b2 = material.b2
p.r3 = material.r3
p.g3 = material.g3
p.b3 = material.b3
p.iv0 = face.iv0
p.iv1 = face.iv1
p.iv2 = face.iv2
p.iv3 = face.iv3
p.in0 = face.in0
p.in1 = face.in1
p.in2 = face.in2
p.in3 = face.in3
return p
def write(self, f):
f.write(self.ftype.value)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.r1)
f.write(self.g1)
f.write(self.b1)
f.write(self.r2)
f.write(self.g2)
f.write(self.b2)
f.write(self.r3)
f.write(self.g3)
f.write(self.b3)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.iv3)
f.write(self.in0)
f.write(self.in1)
f.write(self.in2)
f.write(self.in3)
@dataclass
class PolyFT3:
ftype: c_ubyte = PolyType.FT3
u0: c_ubyte = c_ubyte(0)
v0: c_ubyte = c_ubyte(0)
u1: c_ubyte = c_ubyte(0)
v1: c_ubyte = c_ubyte(0)
u2: c_ubyte = c_ubyte(0)
v2: c_ubyte = c_ubyte(0)
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
@staticmethod
def from_pair(face: TriangleFace, material: TextureFlatMaterial) -> "PolyFT3":
p = PolyFT3()
p.u0 = material.u0
p.v0 = material.v0
p.u1 = material.u1
p.v1 = material.v1
p.u2 = material.u2
p.v2 = material.v2
p.r0 = material.r0
p.g0 = material.g0
p.b0 = material.b0
p.iv0 = face.iv0
p.iv1 = face.iv1
p.iv2 = face.iv2
p.in0 = face.in0
return p
def write(self, f):
f.write(self.ftype.value)
f.write(self.u0)
f.write(self.v0)
f.write(self.u1)
f.write(self.v1)
f.write(self.u2)
f.write(self.v2)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.in0)
@dataclass
class PolyGT3:
ftype: c_ubyte = PolyType.GT3
u0: c_ubyte = c_ubyte(0)
v0: c_ubyte = c_ubyte(0)
u1: c_ubyte = c_ubyte(0)
v1: c_ubyte = c_ubyte(0)
u2: c_ubyte = c_ubyte(0)
v2: c_ubyte = c_ubyte(0)
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
r1: c_ubyte = c_ubyte(0)
g1: c_ubyte = c_ubyte(0)
b1: c_ubyte = c_ubyte(0)
r2: c_ubyte = c_ubyte(0)
g2: c_ubyte = c_ubyte(0)
b2: c_ubyte = c_ubyte(0)
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
in1: c_ushort = c_ushort(0)
in2: c_ushort = c_ushort(0)
@staticmethod
def from_pair(face: TriangleFace, material: TextureGouraudMaterial) -> "PolyGT3":
p = PolyGT3()
p.u0 = material.u0
p.v0 = material.v0
p.u1 = material.u1
p.v1 = material.v1
p.u2 = material.u2
p.v2 = material.v2
p.r0 = material.r0
p.g0 = material.g0
p.b0 = material.b0
p.r1 = material.r1
p.g1 = material.g1
p.b1 = material.b1
p.r2 = material.r2
p.g2 = material.g2
p.b2 = material.b2
p.iv0 = face.iv0
p.iv1 = face.iv1
p.iv2 = face.iv2
p.in0 = face.in0
p.in1 = face.in1
p.in2 = face.in2
return p
def write(self, f):
f.write(self.ftype.value)
f.write(self.u0)
f.write(self.v0)
f.write(self.u1)
f.write(self.v1)
f.write(self.u2)
f.write(self.v2)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.r1)
f.write(self.g1)
f.write(self.b1)
f.write(self.r2)
f.write(self.g2)
f.write(self.b2)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.in0)
f.write(self.in1)
f.write(self.in2)
@dataclass
class PolyFT4:
ftype: c_ubyte = PolyType.FT4
u0: c_ubyte = c_ubyte(0)
v0: c_ubyte = c_ubyte(0)
u1: c_ubyte = c_ubyte(0)
v1: c_ubyte = c_ubyte(0)
u2: c_ubyte = c_ubyte(0)
v2: c_ubyte = c_ubyte(0)
u3: c_ubyte = c_ubyte(0)
v3: c_ubyte = c_ubyte(0)
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
iv3: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
@staticmethod
def from_pair(face: QuadFace, material: TextureFlatMaterial) -> "PolyFT4":
p = PolyFT4()
f.write(self.u0)
f.write(self.v0)
f.write(self.u1)
f.write(self.v1)
f.write(self.u2)
f.write(self.v2)
f.write(self.u3)
f.write(self.v3)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.iv3)
f.write(self.in0)
return p
def write(self, f):
f.write(self.ftype.value)
f.write(self.u0)
f.write(self.v0)
f.write(self.u1)
f.write(self.v1)
f.write(self.u2)
f.write(self.v2)
f.write(self.u3)
f.write(self.v3)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.iv3)
f.write(self.in0)
@dataclass
class PolyGT4:
ftype: c_ubyte = PolyType.GT4
u0: c_ubyte = c_ubyte(0)
v0: c_ubyte = c_ubyte(0)
u1: c_ubyte = c_ubyte(0)
v1: c_ubyte = c_ubyte(0)
u2: c_ubyte = c_ubyte(0)
v2: c_ubyte = c_ubyte(0)
u3: c_ubyte = c_ubyte(0)
v3: c_ubyte = c_ubyte(0)
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
r1: c_ubyte = c_ubyte(0)
g1: c_ubyte = c_ubyte(0)
b1: c_ubyte = c_ubyte(0)
r2: c_ubyte = c_ubyte(0)
g2: c_ubyte = c_ubyte(0)
b2: c_ubyte = c_ubyte(0)
r3: c_ubyte = c_ubyte(0)
g3: c_ubyte = c_ubyte(0)
b3: c_ubyte = c_ubyte(0)
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
iv3: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
in1: c_ushort = c_ushort(0)
in2: c_ushort = c_ushort(0)
in3: c_ushort = c_ushort(0)
@staticmethod
def from_pair(face: QuadFace, material: TextureGouraudMaterial) -> "PolyGT4":
p = PolyGT4()
f.write(self.u0)
f.write(self.v0)
f.write(self.u1)
f.write(self.v1)
f.write(self.u2)
f.write(self.v2)
f.write(self.u3)
f.write(self.v3)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.r1)
f.write(self.g1)
f.write(self.b1)
f.write(self.r2)
f.write(self.g2)
f.write(self.b2)
f.write(self.r3)
f.write(self.g3)
f.write(self.b3)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.iv3)
f.write(self.in0)
f.write(self.in1)
f.write(self.in2)
f.write(self.in3)
return p
def write(self, f):
f.write(self.ftype.value)
f.write(self.u0)
f.write(self.v0)
f.write(self.u1)
f.write(self.v1)
f.write(self.u2)
f.write(self.v2)
f.write(self.u3)
f.write(self.v3)
f.write(self.r0)
f.write(self.g0)
f.write(self.b0)
f.write(self.r1)
f.write(self.g1)
f.write(self.b1)
f.write(self.r2)
f.write(self.g2)
f.write(self.b2)
f.write(self.r3)
f.write(self.g3)
f.write(self.b3)
f.write(self.iv0)
f.write(self.iv1)
f.write(self.iv2)
f.write(self.iv3)
f.write(self.in0)
f.write(self.in1)
f.write(self.in2)
f.write(self.in3)
Polygon = PolyF3 | PolyG3 | PolyF4 | PolyG4 | PolyFT3 | PolyGT3 | PolyFT4 | PolyGT4
def gen_polygon(face, material) -> Polygon:
ftype = type(face)
mtype = type(material.info)
if ftype == TriangleFace:
if mtype == FlatMaterial:
return PolyF3.from_pair(face, material.info)
elif mtype == GouraudMaterial:
return PolyG3.from_pair(face, material.info)
elif mtype == TextureFlatMaterial:
return PolyFT3.from_pair(face, material.info)
elif mtype == TextureGouraudMaterial:
return PolyGT3.from_pair(face, material.info)
else:
print(f"Unknown material type {mtype}")
exit(1)
elif ftype == QuadFace:
if mtype == FlatMaterial:
return PolyF4.from_pair(face, material.info)
elif mtype == GouraudMaterial:
return PolyG4.from_pair(face, material.info)
elif mtype == TextureFlatMaterial:
return PolyFT4.from_pair(face, material.info)
elif mtype == TextureGouraudMaterial:
return PolyGT4.from_pair(face, material.info)
else:
print(f"Unknown material type {mtype}")
exit(1)
else:
print(f"Unknown face type {type(face)}")
exit(1)
@dataclass
class MDLModel:
num_vertices: c_ushort = c_ushort(0)
num_normals: c_ushort = c_ushort(0)
num_polys: c_ushort = c_ushort(0)
vertices: [SVECTOR] = field(default_factory=list)
normals: [SVECTOR] = field(default_factory=list)
polygons: [Polygon] = field(default_factory=list)
def write(self, f):
f.write(self.num_vertices)
f.write(self.num_normals)
f.write(self.num_polys)
for v in self.vertices:
f.write(v.vx)
f.write(v.vy)
f.write(v.vz)
for n in self.normals:
f.write(n.vx)
f.write(n.vy)
f.write(n.vz)
for p in self.polygons:
p.write(f)
def convert_model(rsd: RSDModel) -> MDLModel:
model = MDLModel()
model.num_vertices = rsd.ply.num_vertices
model.num_normals = rsd.ply.num_normals
model.vertices = rsd.ply.vertices
model.normals = rsd.ply.normals
pairs = rsd.ply.pair_materials_and_faces(rsd.mat.materials)
for pair in pairs:
model.polygons.append(gen_polygon(pair[0], pair[1]))
model.num_polys = c_ushort(len(model.polygons))
return model

65
tools/convrsd/face.py Normal file
View file

@ -0,0 +1,65 @@
from dataclasses import dataclass, field
from common import *
@dataclass
class TriangleFace:
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
__unused0: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
in1: c_ushort = c_ushort(0)
in2: c_ushort = c_ushort(0)
__unused1: c_ushort = c_ushort(0)
def __repr__(self):
return f"<Triangle iv0:{self.iv0.value}, iv1:{self.iv1.value}, iv2:{self.iv2.value}, in0:{self.in0.value}, in1:{self.in1.value}, in2{self.in2.value}>"
@dataclass
class QuadFace:
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iv2: c_ushort = c_ushort(0)
iv3: c_ushort = c_ushort(0)
in0: c_ushort = c_ushort(0)
in1: c_ushort = c_ushort(0)
in2: c_ushort = c_ushort(0)
in3: c_ushort = c_ushort(0)
def __repr__(self):
return f"<Quad iv0:{self.iv0.value}, iv1:{self.iv1.value}, iv2:{self.iv2.value}, iv3:{self.iv3.value}, in0:{self.in0.value}, in1:{self.in1.value}, in2{self.in2.value}, in3:{self.in3.value}>"
@dataclass
class LineFace:
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
__unused0: c_ushort = c_ushort(0)
__unused1: c_ushort = c_ushort(0)
__unused2: c_ushort = c_ushort(0)
__unused3: c_ushort = c_ushort(0)
__unused4: c_ushort = c_ushort(0)
__unused5: c_ushort = c_ushort(0)
def __repr__(self):
return f"<Line iv0:{self.iv0.value}, iv1:{self.iv1.value}>"
@dataclass
class SpriteFace:
iv0: c_ushort = c_ushort(0)
iv1: c_ushort = c_ushort(0)
iw: c_ushort = c_ushort(0)
ih: c_ushort = c_ushort(0)
__unused0: c_ushort = c_ushort(0)
__unused1: c_ushort = c_ushort(0)
__unused2: c_ushort = c_ushort(0)
__unused3: c_ushort = c_ushort(0)
def __repr__(self):
return f"<Sprite iv0:{self.iv0.value}, iv1:{self.iv1.value}, iw:{self.iv2.value}, ih:{self.iv3.value}>"
Face = TriangleFace | QuadFace | LineFace | SpriteFace

View file

@ -0,0 +1,6 @@
def tofixed(value: float, scale: int) -> int:
return int(value * (1 << scale))
def tofixed12(value: float) -> int:
return tofixed(value, 12)

View file

@ -0,0 +1,37 @@
from dataclasses import dataclass, field
from common import *
from face import *
from material import *
@dataclass
class PlyData:
magic: str = ""
num_vertices: c_ushort = c_ushort(0)
num_normals: c_ushort = c_ushort(0)
numfaces: c_ushort = c_ushort(0)
vertices: [SVECTOR] = field(default_factory=list)
normals: [SVECTOR] = field(default_factory=list)
faces: [Face] = field(default_factory=list)
def pair_materials_and_faces(self, materials) -> (Face, Material):
pairs = []
for material in materials:
# Use type(face) to determine if triangle or quad
# Use type(material.info) to determine if F, G, FT, GT
pairs.append((self.faces[material.ipolygon.value], material))
return pairs
@dataclass
class MatData:
magic: str = ""
num_items: c_ushort = c_ushort(0)
materials: [Material] = field(default_factory=list)
class RSDModel:
magic: str = ""
ply: PlyData | None = None
mat: MatData | None = None

135
tools/convrsd/material.py Normal file
View file

@ -0,0 +1,135 @@
from dataclasses import dataclass, field
from common import *
@dataclass
class FlatMaterial:
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
def __repr__(self):
return f"<F #{self.r0.value:02X}{self.g0.value:02X}{self.b0.value:02X}>"
@dataclass
class GouraudMaterial:
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
r1: c_ubyte = c_ubyte(0)
g1: c_ubyte = c_ubyte(0)
b1: c_ubyte = c_ubyte(0)
r2: c_ubyte = c_ubyte(0)
g2: c_ubyte = c_ubyte(0)
b2: c_ubyte = c_ubyte(0)
r3: c_ubyte = c_ubyte(0)
g3: c_ubyte = c_ubyte(0)
b3: c_ubyte = c_ubyte(0)
def __repr__(self):
rgb0 = f"#{self.r0.value:02X}{self.g0.value:02X}{self.b0.value:02X}"
rgb1 = f"#{self.r1.value:02X}{self.g1.value:02X}{self.b1.value:02X}"
rgb2 = f"#{self.r2.value:02X}{self.g2.value:02X}{self.b2.value:02X}"
rgb3 = f"#{self.r3.value:02X}{self.g3.value:02X}{self.b3.value:02X}"
return f"<G {rgb0}, {rgb1}, {rgb2}, {rgb3}>"
@dataclass
class TextureMaterial:
u0: c_ubyte = c_ubyte(0)
v0: c_ubyte = c_ubyte(0)
u1: c_ubyte = c_ubyte(0)
v1: c_ubyte = c_ubyte(0)
u2: c_ubyte = c_ubyte(0)
v2: c_ubyte = c_ubyte(0)
u3: c_ubyte = c_ubyte(0)
v3: c_ubyte = c_ubyte(0)
def __repr__(self):
uv0 = f"({self.u0.value:02X}, {self.v0.value:02X})"
uv1 = f"({self.u1.value:02X}, {self.v1.value:02X})"
uv2 = f"({self.u2.value:02X}, {self.v2.value:02X})"
uv3 = f"({self.u3.value:02X}, {self.v3.value:02X})"
return f"<T {uv0} {uv1} {uv2} {uv3}>"
@dataclass
class TextureFlatMaterial:
u0: c_ubyte = c_ubyte(0)
v0: c_ubyte = c_ubyte(0)
u1: c_ubyte = c_ubyte(0)
v1: c_ubyte = c_ubyte(0)
u2: c_ubyte = c_ubyte(0)
v2: c_ubyte = c_ubyte(0)
u3: c_ubyte = c_ubyte(0)
v3: c_ubyte = c_ubyte(0)
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
def __repr__(self):
uv0 = f"({self.u0.value:02X}, {self.v0.value:02X})"
uv1 = f"({self.u1.value:02X}, {self.v1.value:02X})"
uv2 = f"({self.u2.value:02X}, {self.v2.value:02X})"
uv3 = f"({self.u3.value:02X}, {self.v3.value:02X})"
rgb0 = f"#{self.r0.value:02X}{self.g0.value:02X}{self.b0.value:02X}"
return f"<FT {rgb0} {{{uv0} {uv1} {uv2} {uv3}}}"
@dataclass
class TextureGouraudMaterial:
u0: c_ubyte = c_ubyte(0)
v0: c_ubyte = c_ubyte(0)
u1: c_ubyte = c_ubyte(0)
v1: c_ubyte = c_ubyte(0)
u2: c_ubyte = c_ubyte(0)
v2: c_ubyte = c_ubyte(0)
u3: c_ubyte = c_ubyte(0)
v3: c_ubyte = c_ubyte(0)
r0: c_ubyte = c_ubyte(0)
g0: c_ubyte = c_ubyte(0)
b0: c_ubyte = c_ubyte(0)
r1: c_ubyte = c_ubyte(0)
g1: c_ubyte = c_ubyte(0)
b1: c_ubyte = c_ubyte(0)
r2: c_ubyte = c_ubyte(0)
g2: c_ubyte = c_ubyte(0)
b2: c_ubyte = c_ubyte(0)
r3: c_ubyte = c_ubyte(0)
g3: c_ubyte = c_ubyte(0)
b3: c_ubyte = c_ubyte(0)
def __repr__(self):
uv0 = f"({self.u0.value:02X}, {self.v0.value:02X})"
uv1 = f"({self.u1.value:02X}, {self.v1.value:02X})"
uv2 = f"({self.u2.value:02X}, {self.v2.value:02X})"
uv3 = f"({self.u3.value:02X}, {self.v3.value:02X})"
rgb0 = f"#{self.r0.value:02X}{self.g0.value:02X}{self.b0.value:02X}"
rgb1 = f"#{self.r1.value:02X}{self.g1.value:02X}{self.b1.value:02X}"
rgb2 = f"#{self.r2.value:02X}{self.g2.value:02X}{self.b2.value:02X}"
rgb3 = f"#{self.r3.value:02X}{self.g3.value:02X}{self.b3.value:02X}"
return (
f"<GT {{{rgb0} {uv0}}} {{{rgb1} {uv1}}} {{{rgb2} {uv2}}} {{{rgb3} {uv3}}}>"
)
MaterialInfo = (
FlatMaterial
| GouraudMaterial
| TextureMaterial
| TextureFlatMaterial
| TextureGouraudMaterial
)
@dataclass
class Material:
ipolygon: c_ushort = c_ushort(0)
flag: c_ubyte = c_ubyte(0)
shading: ShadingType = ShadingType.FLAT
info: MaterialInfo | None = None
def __repr__(self):
return f"<Material (Face {self.ipolygon.value}): {self.shading} ST({self.flag.value:02X}); {self.info}>"

View file

@ -1,34 +0,0 @@
#ifndef FIXEDPOINT_HPP
#define FIXEDPOINT_HPP
#include <cstdlib>
#include <cstdint>
template<class Type, size_t Scale>
class FixedPoint
{
private:
const static Type factor = 1 << Scale;
Type data;
public:
FixedPoint(double value) {
*this = value;
}
FixedPoint&
operator=(double value) {
this->data = static_cast<Type>(value * factor);
return *this;
}
Type
getRaw() const {
return this->data;
}
};
typedef FixedPoint<int32_t, 12> psxfixed32;
typedef FixedPoint<int16_t, 12> psxfixed16;
#endif

View file

@ -1,17 +0,0 @@
#include <iostream>
#include "fixedpoint.hpp"
#include <cstdint>
int
main(void)
{
double value1 = 3.1416;
double value2 = 1.0;
FixedPoint<int32_t, 12> fvalue1(value1);
FixedPoint<int32_t, 12> fvalue2(value2);
std::cout << "Value 1: " << value1 << " -> " << fvalue1.getRaw() << std::endl
<< "Value 2: " << value2 << " -> " << fvalue2.getRaw() << std::endl;
return 0;
}

View file

@ -1,246 +0,0 @@
#include <cstdio>
#include <cstdlib>
#include "fixedpoint.hpp"
struct SVECTOR
{
int16_t vx;
int16_t vy;
int16_t vz;
//int16_t _unused;
};
struct CVECTOR
{
uint8_t r;
uint8_t g;
uint8_t b;
};
/* Face attributes */
#define TYPE_TRIANGLE 0x0 // ARGS: FLAG V0 V1 V2 __ N0 N1 N2 __
#define TYPE_QUAD 0x1 // ARGS: FLAG V0 V1 V2 V3 N0 N1 N2 N3
#define TYPE_LINE 0x2 // ARGS: FLAG V0 V1 __ __ __ __ __ __
#define TYPE_SPRITE 0x3 // ARGS: FLAG V0 W H
union Face
{
struct {
uint8_t flag;
uint16_t param[8];
} gn; // Generic
struct {
uint8_t flag; // flag == 0
uint16_t iv0;
uint16_t iv1;
uint16_t iv2;
uint16_t _unused0;
uint16_t in0;
uint16_t in1;
uint16_t in2;
uint16_t _unused1;
} triangle;
struct {
uint8_t flag; // flag == 1
uint16_t iv0;
uint16_t iv1;
uint16_t iv2;
uint16_t iv3;
uint16_t in0;
uint16_t in1;
uint16_t in2;
uint16_t in3;
} quad;
struct {
uint8_t flag; // flag == 2
uint16_t iv0;
uint16_t iv1;
uint16_t __unused0;
uint16_t __unused1;
uint16_t __unused2;
uint16_t __unused3;
uint16_t __unused4;
uint16_t __unused5;
} line;
struct {
uint8_t flag; // flag == 3
uint16_t iv0;
uint16_t iv1;
uint16_t iw;
uint16_t ih;
uint16_t __unused2;
uint16_t __unused3;
uint16_t __unused4;
uint16_t __unused5;
} sprite;
};
// ========================
enum MaterialType
{
MATERIAL_TYPE_C, // "C": Flat colored
MATERIAL_TYPE_G, // "G": Gouraud shaded
MATERIAL_TYPE_T, // "T": Textured no-color
MATERIAL_TYPE_D, // "T": Textured, flat colored
MATERIAL_TYPE_H, // "H": Textured, gouraud shaded
// MATERIAL_TYPE_W, // "W": Repeating textures, no-color
// MATERIAL_TYPE_S, // "S": Repeating textures, flat colored
// MATERIAL_TYPE_N, // "N": Repeating textures, gouraud shaded
};
union MaterialInfo
{
MaterialType type;
struct {
MaterialType type;
uint8_t r0;
uint8_t g0;
uint8_t b0;
} f;
struct {
MaterialType type;
uint8_t r0;
uint8_t g0;
uint8_t b0;
uint8_t r1;
uint8_t g1;
uint8_t b1;
uint8_t r2;
uint8_t g2;
uint8_t b2;
uint8_t r3;
uint8_t g3;
uint8_t b3;
} g;
struct {
MaterialType type;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
} t;
struct {
MaterialType type;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
uint8_t r0;
uint8_t g0;
uint8_t b0;
} ft;
struct {
MaterialType type;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
uint8_t r0;
uint8_t g0;
uint8_t b0;
uint8_t r1;
uint8_t g1;
uint8_t b1;
uint8_t r2;
uint8_t g2;
uint8_t b2;
uint8_t r3;
uint8_t g3;
uint8_t b3;
} gt;
// Won't care about the rest
};
enum ShadingType
{
SHADING_TYPE_FLAT, // "F"
SHADING_TYPE_GOURAUD, // "G"
};
struct Material
{
// Can be in 0-5 format, specifies polygons 0 thru 5 use this material
uint16_t ipolygon;
uint8_t flag;
ShadingType shading;
MaterialInfo info;
};
// ========================
struct PlyData
{
char magic[11];
uint32_t num_vertices;
uint32_t num_normals;
uint32_t num_faces;
SVECTOR *vertices;
SVECTOR *normals;
Face *faces;
};
struct MatData
{
char magic[11];
uint16_t num_items;
Material *data;
};
// ========================
struct RSDModel
{
char magic[11];
PlyData ply;
MatData mat;
// TODO: NTEX and TEX[n] for textures
};
// ========================
void
parse_rsd(const char *filename, RSDModel &model)
{
}
void
parse_ply(const char *filename, PlyData &ply)
{
}
void
parse_mat(const char *filename, MatData &mat)
{
}
int
main(void)
{
// Read and parse RSD file and its acessories
// Convert RSD to actual engine model format (still don't know what to do here...)
return 0;
}

122
tools/layouts/mdl.hexpat Normal file
View file

@ -0,0 +1,122 @@
#include <type/color.pat>
enum PolygonType: u8 {
F3 = 0,
G3 = 1,
F4 = 2,
G4 = 3,
FT3 = 4,
GT3 = 5,
FT4 = 6,
GT4 = 7,
};
struct Polygon {
PolygonType ftype;
if(ftype == PolygonType::F3) {
le type::RGB8 rgb0;
be u16 iv0;
be u16 iv1;
be u16 iv2;
be u16 in0;
} else if(ftype == PolygonType::G3) {
le type::RGB8 rgb0;
le type::RGB8 rgb1;
le type::RGB8 rgb2;
be u16 iv0;
be u16 iv1;
be u16 iv2;
be u16 in0;
be u16 in1;
be u16 in2;
} else if(ftype == PolygonType::F4) {
le type::RGB8 rgb0;
be u16 iv0;
be u16 iv1;
be u16 iv2;
be u16 iv3;
be u16 in0;
} else if(ftype == PolygonType::G4) {
le type::RGB8 rgb0;
le type::RGB8 rgb1;
le type::RGB8 rgb2;
le type::RGB8 rgb3;
be u16 iv0;
be u16 iv1;
be u16 iv2;
be u16 iv3;
be u16 in0;
be u16 in1;
be u16 in2;
be u16 in3;
} else if(ftype == PolygonType::FT3) {
u8 u0; u8 v0;
u8 u1; u8 v1;
u8 u2; u8 v2;
le type::RGB8 rgb0;
be u16 iv0;
be u16 iv1;
be u16 iv2;
be u16 in0;
} else if(ftype == PolygonType::GT3) {
u8 u0; u8 v0;
u8 u1; u8 v1;
u8 u2; u8 v2;
le type::RGB8 rgb0;
le type::RGB8 rgb1;
le type::RGB8 rgb2;
be u16 iv0;
be u16 iv1;
be u16 iv2;
be u16 in0;
be u16 in1;
be u16 in2;
} else if(ftype == PolygonType::FT4) {
u8 u0; u8 v0;
u8 u1; u8 v1;
u8 u2; u8 v2;
u8 u3; u8 v3;
le type::RGB8 rgb0;
be u16 iv0;
be u16 iv1;
be u16 iv2;
be u16 iv3;
be u16 in0;
} else if(ftype == PolygonType::GT4) {
u8 u0; u8 v0;
u8 u1; u8 v1;
u8 u2; u8 v2;
u8 u3; u8 v3;
le type::RGB8 rgb0;
le type::RGB8 rgb1;
le type::RGB8 rgb2;
le type::RGB8 rgb3;
be u16 iv0;
be u16 iv1;
be u16 iv2;
be u16 iv3;
be u16 in0;
be u16 in1;
be u16 in2;
be u16 in3;
} else {
std::error(std::format("Undefined polygon type: {}\n", ftype));
}
};
struct SVECTOR {
be s16 vx;
be s16 vy;
be s16 vz;
};
struct Model {
be u16 num_vertices;
be u16 num_normals;
be u16 num_polys;
SVECTOR vertices[num_vertices];
SVECTOR normals[num_normals];
Polygon polygons[num_polys];
};
Model data @ 0x00;