dreamcast/tools/obj_to_cpp.py
Zack Buhman 4bb04a0362 example: add maple_analog
Also adds the incomplete modifier_volume example.

This also adds vec2 for UV coordinates, and obj_to_cpp has been
modified to parse vertex texture coordinates from obj files.
2023-12-30 10:53:39 +08:00

164 lines
4.1 KiB
Python

from dataclasses import dataclass
import sys
from generate import renderer
with open(sys.argv[1], 'r') as f:
lines = f.read().split("\n")
@dataclass(frozen=True)
class Vertex:
x: float
y: float
z: float
@dataclass(frozen=True)
class TextureCoordinate:
u: float
v: float
@dataclass
class VertexTextureNormal:
vertex: int
texture: int
normal: int
Face = tuple[VertexTextureNormal, VertexTextureNormal, VertexTextureNormal]
name = None
def parse_object(line):
h, name = line.split()
assert h == 'o'
return name.lower()
def parse_vertex(line):
h, *xyz = line.split()
assert h == 'v' or h == 'vn'
assert len(xyz) == 3
return Vertex(*map(float, xyz))
def parse_texture_coordinate(line):
h, *uv = line.split()
assert h == 'vt'
assert len(uv) == 2
return TextureCoordinate(*map(float, uv))
def maybe_int(i, offset):
if i.strip() == "":
assert False
else:
return int(i) + offset
def parse_face(line):
h, *tri = line.split()
assert h == 'f'
assert len(tri) == 3
def parse_ixs(ixs):
ix = ixs.split('/')
assert len(ix) == 3
vertex_ix, uv_ix, normal_ix = [
maybe_int(iix, offset=-1)
for iix in ix
]
return VertexTextureNormal(vertex_ix, uv_ix, normal_ix)
return tuple(map(parse_ixs, tri))
def generate_vertices(vertices):
yield "constexpr vec3 vertices[] = {"
for v in vertices:
yield f"{{ {v.x:9f}f, {v.y:9f}f, {v.z:9f}f }},"
yield "};"
yield ""
def generate_normals(normals):
yield "constexpr vec3 normals[] = {"
for n in normals:
yield f"{{ {n.x:9f}f, {n.y:9f}f, {n.z:9f}f }},"
yield "};"
yield ""
def generate_texture_coordinates(texture_coordinates):
yield "constexpr vec2 texture[] = {"
for t in texture_coordinates:
yield f"{{ {t.u:9f}f, {t.v:9f}f }},"
yield "};"
yield ""
def generate_faces(faces):
yield "constexpr face faces[] = {"
for f in faces:
inner = ", ".join(f"{{{vtn.vertex:3}, {vtn.texture:3}, {vtn.normal:3}}}" for vtn in f)
yield f"{{{inner}}},"
yield "};"
yield ""
yield "constexpr uint32_t num_faces = (sizeof (faces)) / (sizeof (face));"
yield ""
def generate_namespace(vertices, texture_coordinates, normals, faces):
global name
assert name is not None
yield "#pragma once"
yield ""
yield '#include "geometry.hpp"'
yield ""
yield f"namespace {name} {{"
yield from generate_vertices(vertices)
yield from generate_texture_coordinates(texture_coordinates)
yield from generate_normals(normals)
yield from generate_faces(faces)
yield "}"
def merge_texture_coordinates(texture_coordinates, faces):
ix = 0
_texture = dict()
_faces = []
for face in faces:
for vtn in face:
key = texture_coordinates[vtn.texture]
if key not in _texture:
_texture[key] = ix
ix += 1
_faces.append(tuple(
VertexTextureNormal(vtn.vertex,
_texture[texture_coordinates[vtn.texture]],
vtn.normal)
for vtn in face
))
return _texture, _faces
def main():
global name
vertices = []
texture_coordinates = []
normals = []
faces = []
for line in lines:
if line.startswith('o '):
assert name is None
name = parse_object(line)
elif line.startswith('v '):
vertices.append(parse_vertex(line))
elif line.startswith('vn '):
normals.append(parse_vertex(line))
elif line.startswith('vt '):
texture_coordinates.append(parse_texture_coordinate(line))
elif line.startswith('f '):
faces.append(parse_face(line))
else:
pass
texture_coordinates, faces = merge_texture_coordinates(texture_coordinates, faces)
render, out = renderer()
render(generate_namespace(vertices, texture_coordinates, normals, faces))
sys.stdout.write(out.getvalue())
if __name__ == '__main__':
main()