From 61e2fb2c284d81e906ca4c5bf0ea4704cdeb7df1 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Thu, 30 Jan 2025 03:07:20 -0600 Subject: [PATCH] initial --- .gitignore | 2 + generate.py | 42 ++++++++++ generate_cpp.py | 61 ++++++++++++++ generate_java.py | 57 +++++++++++++ java/model/FacePTN.java | 13 +++ java/model/ModelObject.java | 5 ++ java/model/Vec2.java | 11 +++ java/model/Vec3.java | 13 +++ obj.py | 161 ++++++++++++++++++++++++++++++++++++ 9 files changed, 365 insertions(+) create mode 100644 .gitignore create mode 100644 generate.py create mode 100644 generate_cpp.py create mode 100644 generate_java.py create mode 100644 java/model/FacePTN.java create mode 100644 java/model/ModelObject.java create mode 100644 java/model/Vec2.java create mode 100644 java/model/Vec3.java create mode 100644 obj.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/generate.py b/generate.py new file mode 100644 index 0000000..ad924f9 --- /dev/null +++ b/generate.py @@ -0,0 +1,42 @@ +import io + +def should_autonewline(line): + return ( + "static_assert" not in line + and "extern" not in line + and (len(line.split()) < 2 or line.split()[1] != '=') # hacky; meh + ) + +def _render(out, lines, indent_length): + indent = " " + level = 0 + namespace = 0 + for l in lines: + if l and (l[0] == "}" or l[0] == ")"): + level -= indent_length + if level < 0: + assert namespace >= 0 + namespace -= 1 + level = 0 + + if len(l) == 0: + out.write("\n") + else: + out.write(indent * level + l + "\n") + + if l and (l[-1] == "{" or l[-1] == "("): + if l.startswith("namespace"): + namespace += 1 + else: + level += indent_length + + if level == 0 and l and l[-1] == ";": + if should_autonewline(l): + out.write("\n") + return out + +def renderer(indent_length=2): + out = io.StringIO() + def render(lines): + return _render(out, lines, indent_length) + return render, out diff --git a/generate_cpp.py b/generate_cpp.py new file mode 100644 index 0000000..cd76c0b --- /dev/null +++ b/generate_cpp.py @@ -0,0 +1,61 @@ +from generate import renderer +from obj import parse_obj_file +import sys + +def generate_vec3(vs): + for v in vs: + yield f"{{{v.x}f, {v.y}f, {v.z}f}}," + +def generate_vec2(vs): + for v in vs: + yield f"{{{v.x}f, {v.y}f}}," + +def generate_vec(name, type, func, vs): + yield f"vertex_{type} {name}_{type}[] = {{" + yield from func(vs) + yield "};" + +def filter_n(l, n): + for a in l: + if len(a.ptn) == n: + yield a + +def generate_ptn(ptn): + for p in ptn: + yield f"{{{p.position}, {p.texture}, {p.normal}}}," + +def generate_faces(group_name, model_name, faces, type, n): + yield f"union {type} {group_name}_{model_name}_triangles = {{" + for face in filter_n(faces, n): + yield "{ .v = {" + yield from generate_ptn(face.ptn) + yield "}}," + yield "};" + +def generate_object(group_name, object): + yield f"struct object {group_name}_{object.name} = {{" + yield f".triangle = &{group_name}_{object.name}_triangle[0]," + yield f".quadrilateral = &{group_name}_{object.name}_quadrilateral[0]," + triangle_count = sum(1 for _ in filter_n(object.faces, 3)) + quadrilateral_count = sum(1 for _ in filter_n(object.faces, 4)) + yield f".triangle_count = {triangle_count}," + yield f".quadrilateral_count = {quadrilateral_count}," + yield ".material = 0," + yield "};" + +def generate_obj_file(group_name, obj_file): + yield from generate_vec(group_name, "position", generate_vec3, obj_file.position) + yield from generate_vec(group_name, "texture", generate_vec2, obj_file.texture) + yield from generate_vec(group_name, "normal", generate_vec3, obj_file.normal) + + for object in obj_file.objects: + assert all(len(face.ptn) in {3, 4} for face in object.faces), object.name + yield from generate_faces(group_name, object.name, object.faces, "triangle", 3) + yield from generate_faces(group_name, object.name, object.faces, "quadrilateral", 4) + yield from generate_object(group_name, object) + +if __name__ == "__main__": + obj_file = parse_obj_file(sys.argv[1]) + render, out = renderer(indent_length=4) + render(generate_obj_file(sys.argv[2], obj_file)) + sys.stdout.write(out.getvalue()) diff --git a/generate_java.py b/generate_java.py new file mode 100644 index 0000000..4e260ef --- /dev/null +++ b/generate_java.py @@ -0,0 +1,57 @@ +from generate import renderer +from obj import parse_obj_file +import sys + +def generate_vec3(field, vs): + for i, v in enumerate(vs): + vec3_args = f"{v.x}f, {v.y}f, {v.z}f" + yield f"{field}[{i}] = new Vec3({vec3_args});" + +def generate_vec2(field, vs): + for i, v in enumerate(vs): + vec2_args = f"{v.x}f, {v.y}f" + yield f"{field}[{i}] = new Vec2({vec2_args});" + +def generate_face(length, i, j, face): + assert len(face.ptn) == length, (face.ptn, length) + for k, ptn in enumerate(face.ptn): + ptn_args = f"{ptn.position}, {ptn.texture}, {ptn.normal}" + yield f"objects[{i}].faces[{j}][{k}] = new FacePTN({ptn_args});" + +def generate_model_objects(objects): + face_length = len(objects[0].faces[0].ptn) + for i, o in enumerate(objects): + yield f"objects[{i}] = new ModelObject();" + yield f"objects[{i}].faces = new FacePTN[{len(o.faces)}][{face_length}];" + for j, face in enumerate(objects[i].faces): + yield from generate_face(face_length, i, j, face) + +def generate_model(name, obj_file): + yield "package model;" + yield f"public class {name} {{" + yield "public static Vec3[] position;" + yield "public static Vec3[] normal;" + yield "public static Vec2[] texture;" + yield "public static ModelObject[] objects;" + yield f"private {name}() {{" + yield "}" + yield "static {" + yield f"position = new Vec3[{len(obj_file.position)}];" + yield f"normal = new Vec3[{len(obj_file.normal)}];" + yield f"texture = new Vec2[{len(obj_file.texture)}];" + yield f"objects = new ModelObject[{len(obj_file.objects)}];" + + yield from generate_vec3("position", obj_file.position) + yield from generate_vec3("normal", obj_file.normal) + yield from generate_vec2("texture", obj_file.texture) + + yield from generate_model_objects(obj_file.objects) + + yield "}" + yield "}" + +if __name__ == "__main__": + obj_file = parse_obj_file(sys.argv[1]) + render, out = renderer(indent_length=4) + render(generate_model(sys.argv[2], obj_file)) + sys.stdout.write(out.getvalue()) diff --git a/java/model/FacePTN.java b/java/model/FacePTN.java new file mode 100644 index 0000000..5b575e4 --- /dev/null +++ b/java/model/FacePTN.java @@ -0,0 +1,13 @@ +package model; + +public class FacePTN { + public int position; + public int texture; + public int normal; + + FacePTN(int position, int texture, int normal) { + this.position = position; + this.texture = texture; + this.normal = normal; + } +} diff --git a/java/model/ModelObject.java b/java/model/ModelObject.java new file mode 100644 index 0000000..a4a4fe1 --- /dev/null +++ b/java/model/ModelObject.java @@ -0,0 +1,5 @@ +package model; + +public class ModelObject { + public FacePTN[][] faces; +} diff --git a/java/model/Vec2.java b/java/model/Vec2.java new file mode 100644 index 0000000..ad7a55a --- /dev/null +++ b/java/model/Vec2.java @@ -0,0 +1,11 @@ +package model; + +public class Vec2 { + public float x; + public float y; + + public Vec2(float x, float y) { + this.x = x; + this.y = y; + } +} diff --git a/java/model/Vec3.java b/java/model/Vec3.java new file mode 100644 index 0000000..42969b4 --- /dev/null +++ b/java/model/Vec3.java @@ -0,0 +1,13 @@ +package model; + +public class Vec3 { + public float x; + public float y; + public float z; + + public Vec3(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } +} diff --git a/obj.py b/obj.py new file mode 100644 index 0000000..7c0db1a --- /dev/null +++ b/obj.py @@ -0,0 +1,161 @@ +from dataclasses import dataclass +import sys + +@dataclass +class VertexPosition: + x: float + y: float + z: float + +@dataclass +class VertexNormal: + x: float + y: float + z: float + +@dataclass +class VertexTexture: + x: float + y: float + z: float + +@dataclass +class ObjectEvent: + name: str + +@dataclass +class FacePTN: + position: int + texture: int + normal: int + +@dataclass +class Face: + ptn: list[FacePTN] + +@dataclass +class Object: + name: str + faces: list[Face] + + def __init__(self, name): + self.name = name + self.faces = [] + +@dataclass +class ObjFile: + position: list[VertexPosition] + normal: list[VertexNormal] + texture: list[VertexTexture] + objects: list[Object] + + def __init__(self): + self.position = [] + self.normal = [] + self.texture = [] + self.objects = [] + +def parse_float(s): + if '.' not in s: + return int(s, 10) + i, f = s.split('.') + f_digits = len(f) + i = int(i, 10) + f = int(f, 10) + return i + (f / 10 ** f_digits) + +def parse_vertex(line, n, type): + assert len(line) == n + vs = [parse_float(line[i]) for i in range(n)] + if len(vs) < 3: + vs.append(0) + return type(*vs) + +def parse_vertex_position(line): + return parse_vertex(line, 3, VertexPosition) + +def parse_vertex_normal(line): + return parse_vertex(line, 3, VertexNormal) + +def parse_vertex_texture(line): + return parse_vertex(line, 2, VertexTexture) + +def parse_face_indices(indices): + assert "/" in indices + indices = indices.split("/") + assert len(indices) == 3, indices + def face_ix(s): + i = int(s, 10) + assert i >= 1 + return i - 1 + return FacePTN(*(face_ix(i) for i in indices)) + +def parse_face(line): + return Face([parse_face_indices(indices) for indices in line]) + +def parse_object_event(line): + assert len(line) == 1 + name, = line + return ObjectEvent(name) + +def parse_line(line): + t, *line = line.split(' ') + if t == '#': + return None + if t == 'usemtl': + return None + if t == 'mtllib': + return None + if t == 'o': + return parse_object_event(line) + if t == 'v': + return parse_vertex_position(line) + if t == 'vn': + return parse_vertex_normal(line) + if t == 'vt': + return parse_vertex_texture(line) + if t == 'f': + return parse_face(line) + if t == 's': + # smooth shading + return None + assert False, (t, line) + +def parse_obj_lines(lines): + file = ObjFile() + object = Object(None) + for line in lines: + x = parse_line(line) + if x is None: + continue + elif type(x) is VertexPosition: + file.position.append(x) + elif type(x) is VertexNormal: + file.normal.append(x) + elif type(x) is VertexTexture: + file.texture.append(x) + elif type(x) is ObjectEvent: + if object.faces: + assert object.name != None + file.objects.append(object) + object = Object(x.name) + elif type(x) is Face: + object.faces.append(x) + else: + assert False, x + if object.faces: + assert object.name != None + file.objects.append(object) + return file + + +def parse_obj_file(filename): + with open(filename, "r") as f: + lines = f.read().strip().split("\n") + file = parse_obj_lines(lines) + return file + +if __name__ == "__main__": + file = parse_obj_file(sys.argv[1]) + from pprint import pprint + pprint(file)