From d397f61046fac6c0b7934ce041519c999092d0ff Mon Sep 17 00:00:00 2001 From: sneeker Date: Thu, 7 Jul 2022 12:27:02 +0000 Subject: [PATCH] feat(examples): generate scene translation units from .scene files scenegen.py compiles each scene into a C source and header, driven by a rule block with per-input stem expansion. --- examples/raytracer/tools/scenegen.py | 102 +++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100755 examples/raytracer/tools/scenegen.py diff --git a/examples/raytracer/tools/scenegen.py b/examples/raytracer/tools/scenegen.py new file mode 100755 index 0000000..befb446 --- /dev/null +++ b/examples/raytracer/tools/scenegen.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Turn a .scene description into a C translation unit and its header. + +usage: scenegen.py +""" +import os +import sys + +KINDS = {"lambert": "MAT_LAMBERT", "metal": "MAT_METAL", "light": "MAT_LIGHT"} + + +def die(path, lineno, msg): + sys.stderr.write("%s:%d: %s\n" % (path, lineno, msg)) + raise SystemExit(1) + + +def parse(path): + scene = { + "background": (0.0, 0.0, 0.0), + "look_from": (0.0, 0.0, 6.0), + "look_at": (0.0, 0.0, 0.0), + "fov": 40.0, + "spheres": [], + } + with open(path) as fh: + for lineno, raw in enumerate(fh, 1): + line = raw.split("#", 1)[0].strip() + if not line: + continue + word = line.split() + head, rest = word[0], word[1:] + try: + if head == "background": + scene["background"] = tuple(float(v) for v in rest[:3]) + elif head == "camera": + scene["look_from"] = tuple(float(v) for v in rest[0:3]) + scene["look_at"] = tuple(float(v) for v in rest[3:6]) + scene["fov"] = float(rest[6]) + elif head == "sphere": + centre = tuple(float(v) for v in rest[0:3]) + radius = float(rest[3]) + kind = rest[4] + if kind not in KINDS: + die(path, lineno, "unknown material %r" % kind) + albedo = tuple(float(v) for v in rest[5:8]) + fuzz = float(rest[8]) if len(rest) > 8 else 0.0 + scene["spheres"].append((centre, radius, kind, albedo, fuzz)) + else: + die(path, lineno, "unknown directive %r" % head) + except (IndexError, ValueError): + die(path, lineno, "malformed %s line" % head) + if not scene["spheres"]: + die(path, 0, "scene declares no spheres") + return scene + + +def v3(t): + return "{%.6f, %.6f, %.6f}" % t + + +def main(): + if len(sys.argv) != 4: + sys.stderr.write(__doc__) + raise SystemExit(2) + + src, out_c, out_h = sys.argv[1], sys.argv[2], sys.argv[3] + name = os.path.splitext(os.path.basename(src))[0] + scene = parse(src) + guard = ("%s_SCENE_H" % name).upper() + + for path in (out_c, out_h): + d = os.path.dirname(path) + if d: + os.makedirs(d, exist_ok=True) + + with open(out_h, "w") as fh: + fh.write("/* Generated from %s by scenegen.py. */\n" % src) + fh.write("#ifndef %s\n#define %s\n\n#include \"rt_scene.h\"\n\n" % (guard, guard)) + fh.write("extern const sphere %s_spheres[];\n" % name) + fh.write("extern const int %s_sphere_count;\n" % name) + fh.write("extern const vec3 %s_background;\n" % name) + fh.write("extern const vec3 %s_look_from;\n" % name) + fh.write("extern const vec3 %s_look_at;\n" % name) + fh.write("extern const double %s_fov;\n\n#endif\n" % name) + + with open(out_c, "w") as fh: + fh.write("/* Generated from %s by scenegen.py. */\n" % src) + fh.write("#include \"%s_scene.h\"\n\n" % name) + fh.write("const sphere %s_spheres[] = {\n" % name) + for centre, radius, kind, albedo, fuzz in scene["spheres"]: + fh.write(" {%s, %.6f, {%s, %s, %.6f}},\n" + % (v3(centre), radius, KINDS[kind], v3(albedo), fuzz)) + fh.write("};\n\n") + fh.write("const int %s_sphere_count = %d;\n" % (name, len(scene["spheres"]))) + fh.write("const vec3 %s_background = %s;\n" % (name, v3(scene["background"]))) + fh.write("const vec3 %s_look_from = %s;\n" % (name, v3(scene["look_from"]))) + fh.write("const vec3 %s_look_at = %s;\n" % (name, v3(scene["look_at"]))) + fh.write("const double %s_fov = %.6f;\n" % (name, scene["fov"])) + + +if __name__ == "__main__": + main()