149 lines
5.2 KiB
Python
149 lines
5.2 KiB
Python
"""
|
|
raymarch toy
|
|
|
|
usage: march <scene1> <scene2> ... <sceneN>
|
|
|
|
<scene> ::= <scene> "." <command>
|
|
| <command>
|
|
|
|
<command> ::= <id>
|
|
| <id> "(" <args> ")"
|
|
|
|
example:
|
|
march cube.pos(0, 0, 0).color(red) cam(1.0).pos(1, 1, 1).lookat(0, 0, 0)
|
|
"""
|
|
|
|
import pathlib
|
|
from dataclasses import dataclass
|
|
import re
|
|
import numpy as np
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
from konabot.plugins.marchtoy.obj import Object, Camera, OBJECT_ENTRIES
|
|
from konabot.plugins.marchtoy.op import OPERATION_ENTRIES
|
|
|
|
@dataclass
|
|
class Command:
|
|
id: str
|
|
args: list[str]
|
|
|
|
|
|
class CommandChainParser:
|
|
CHAIN_PATTERN = r"^[a-zA-Z]+(\([^(]*\))?(\.[a-zA-Z]+(\([^(]*\))?)*"
|
|
|
|
def __init__(self, _command_chain: str) -> None:
|
|
self.command_chain = _command_chain
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
if query := re.match(CommandChainParser.CHAIN_PATTERN, self.command_chain):
|
|
cmd_chain = query[0]
|
|
self.command_chain = self.command_chain[len(cmd_chain) + 1 :]
|
|
return cmd_chain
|
|
raise StopIteration
|
|
|
|
|
|
class CommandParser:
|
|
CMD_PATTERN = r"^[a-zA-Z]+(\([^(]*\))?"
|
|
ID_PATTERN = r"^[a-zA-Z]+(?=\(|\.|$)"
|
|
|
|
def __init__(self, _command: str) -> None:
|
|
self.command = _command
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
if query := re.match(CommandParser.CMD_PATTERN, self.command):
|
|
cmd = query[0]
|
|
if cmd_id_qry := re.match(CommandParser.ID_PATTERN, cmd):
|
|
cmd_id = cmd_id_qry[0]
|
|
self.command = self.command[len(cmd) + 1 :]
|
|
cmd_args = cmd[len(cmd_id) + 1 : -1].replace(" ", "").split(",")
|
|
while "" in cmd_args:
|
|
cmd_args.remove("")
|
|
return Command(cmd_id, cmd_args)
|
|
raise StopIteration
|
|
|
|
class Scene:
|
|
def __init__(self, _instruction: str) -> None:
|
|
self.canvas_objs: list[tuple[Object, str]] = []
|
|
self.camera: Camera = Camera()
|
|
for raw_cmd in CommandChainParser(_instruction):
|
|
cmd_queue = CommandParser(raw_cmd)
|
|
cmd_obj = next(cmd_queue)
|
|
obj_id, obj_args = cmd_obj.id, cmd_obj.args
|
|
obj_instance: Optional[Object] = None
|
|
if obj_id in OBJECT_ENTRIES:
|
|
obj_cls = OBJECT_ENTRIES[obj_id]
|
|
if not issubclass(obj_cls, Object):
|
|
raise Exception(f"{obj_id} is not a subclass of Object.")
|
|
obj_instance = obj_cls()
|
|
try:
|
|
if len(obj_args) != 0:
|
|
obj_instance.parse_args(obj_args)
|
|
except Exception as e:
|
|
raise Exception(
|
|
f"object {obj_id} failed to parse args passed in: {obj_args}"
|
|
) from e
|
|
else:
|
|
raise Exception(f"{obj_id} is not a valid object type.")
|
|
|
|
if obj_instance != None:
|
|
for cmd in cmd_queue:
|
|
op_id, op_args = cmd.id, cmd.args
|
|
if op_id in OPERATION_ENTRIES:
|
|
op_func = OPERATION_ENTRIES[op_id]
|
|
if not callable(op_func):
|
|
raise Exception(f"{op_id} is not a valid operation.")
|
|
op_func(obj_instance, op_args)
|
|
else:
|
|
raise Exception(f"{op_id} is not a valid operation.")
|
|
|
|
try:
|
|
sdf_block = obj_instance.sdf_block()
|
|
self.canvas_objs.append((obj_instance, sdf_block))
|
|
except:
|
|
if type(obj_instance) == Camera:
|
|
self.camera = obj_instance
|
|
|
|
def __str__(self) -> str:
|
|
return ", ".join([str(type(obj)) for obj in self.canvas_objs])
|
|
|
|
def compile(self, fs_src: str = "frag.glsl") -> str:
|
|
PATH = pathlib.Path(__file__).parent / "shaders" / fs_src
|
|
with PATH.open(encoding="utf-8") as f:
|
|
content = f.read()
|
|
sdf_block: str = ""
|
|
color_block: str = ""
|
|
|
|
index = 0
|
|
for canvas_item in self.canvas_objs:
|
|
obj, sdf_expr = canvas_item
|
|
sdf_block += f"float sd{index} = {sdf_expr};"
|
|
sdf_block += f"if(sd{index} < qry.value)"
|
|
sdf_block += "{" + f"qry.value = sd{index}; qry.obj_id = {index}; " + "}\n"
|
|
color = obj.texture.color
|
|
color_block += (
|
|
f"if(obj_id == {index}) return float4("
|
|
f"{color[0]}, {color[1]}, {color[2]}, {color[3]});\n"
|
|
)
|
|
index += 1
|
|
|
|
content = content.replace("<SDF_BLOCK>", sdf_block)
|
|
content = content.replace("<COLOR_BLOCK>", color_block)
|
|
|
|
cam_pos = self.camera.transform.t[0:3, 3]
|
|
cam_focus = self.camera.focus
|
|
cam_dir = self.camera.transform.t @ np.array((0.0, 0.0, -1.0, 0.0))
|
|
content = content.replace(
|
|
"<CAM_POS>", f"{cam_pos[0]}, {cam_pos[1]}, {cam_pos[2]}"
|
|
)
|
|
content = content.replace("<CAM_FOCUS>", str(cam_focus))
|
|
content = content.replace(
|
|
"<CAM_DIR>", f"{cam_dir[0]}, {cam_dir[1]}, {cam_dir[2]}"
|
|
)
|
|
return content
|