column major

This commit is contained in:
bk_office
2026-04-25 14:58:50 +08:00
parent facd2d0e84
commit 3f3a375dd6
9 changed files with 65 additions and 45 deletions

View File

@ -2,7 +2,7 @@ from nonebot import on_command
from nonebot.adapters import Message from nonebot.adapters import Message
from nonebot_plugin_alconna import UniMessage from nonebot_plugin_alconna import UniMessage
from nonebot.params import CommandArg from nonebot.params import CommandArg
import render import konabot.plugins.marchtoy.gl_render as render
import io import io
cmd_marchtoy = on_command("march", priority=10, block=True) cmd_marchtoy = on_command("march", priority=10, block=True)
@cmd_marchtoy.handle() @cmd_marchtoy.handle()

View File

@ -18,9 +18,9 @@ from dataclasses import dataclass
import re import re
import numpy as np import numpy as np
from dataclasses import dataclass from dataclasses import dataclass
from obj import Object, Camera, OBJECT_ENTRIES
from op import OPERATION_ENTRIES
from typing import Optional from typing import Optional
from konabot.plugins.marchtoy.obj import Object, Camera, OBJECT_ENTRIES
from konabot.plugins.marchtoy.op import OPERATION_ENTRIES
@dataclass @dataclass
class Command: class Command:

View File

@ -3,11 +3,14 @@ headless moderngl
""" """
import pathlib import pathlib
import moderngl import moderngl
from command import Scene
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from konabot.plugins.marchtoy.command import Scene
def render(vs: str, fs: str, res: tuple[int, int]): async def render(instruction: str, res: tuple[int, int]):
fs = Scene(instruction).compile()
PATH = pathlib.Path(__file__).parent / "shaders"
with (PATH / "vert.glsl").open(encoding='utf-8') as f:
vs = f.read()
ctx = moderngl.create_context(standalone=True) ctx = moderngl.create_context(standalone=True)
try: try:
program = ctx.program( program = ctx.program(
@ -27,11 +30,4 @@ def render(vs: str, fs: str, res: tuple[int, int]):
fbo.use() fbo.use()
fbo.clear(0.0, 0.0, 0.0, 0.0) fbo.clear(0.0, 0.0, 0.0, 0.0)
vao.render(moderngl.TRIANGLES) vao.render(moderngl.TRIANGLES)
Image.frombytes('RGB', fbo.size, fbo.read(), 'raw', 'RGB', 0, -1).show() return Image.frombytes('RGBA', fbo.size, fbo.read(components=4), 'raw', 'RGBA', 0, -1)
fs = Scene("cube(0.5).pos(1) camera.pos(1).lookat(0)").compile()
PATH = pathlib.Path(__file__).parent / "shaders"
with (PATH / "vert.glsl").open(encoding='utf-8') as f:
vs = f.read()
render(vs, fs, (320, 320))

View File

@ -1,14 +1,14 @@
import numpy as np import numpy as np
from texture import Texture from konabot.plugins.marchtoy.texture import Texture
from utilities import ArgParser, Formatter from konabot.plugins.marchtoy.utilities import ArgParser, Formatter
OBJECT_ENTRIES = {} OBJECT_ENTRIES = {}
def make_obj(name: str, aliases: list[str] = []): def make_obj(*name: str):
def decorator(cls): def decorator(cls):
OBJECT_ENTRIES[name] = cls # OBJECT_ENTRIES[name] = cls
for alias in aliases: for alias in [*name]:
OBJECT_ENTRIES[alias] = cls OBJECT_ENTRIES[alias] = cls
return cls return cls
@ -29,7 +29,7 @@ class Transform:
self.t = mat @ self.t self.t = mat @ self.t
return self return self
# scale 会破坏 sdf 的性质,不应该使用 # scale 会破坏 sdf 的性质 梯度大小会变 导致 overshoot 等问题
def scale(self, x: float, y: float, z: float): def scale(self, x: float, y: float, z: float):
mat = np.identity(4, dtype=np.float32) mat = np.identity(4, dtype=np.float32)
mat[0, 0], mat[1, 1], mat[2, 2] = x, y, z mat[0, 0], mat[1, 1], mat[2, 2] = x, y, z
@ -72,7 +72,7 @@ class Transform:
def p_expr(self) -> str: def p_expr(self) -> str:
inv = np.linalg.inv(self.t) # + 1e-5 * np.identity(4, dtype=np.float32)) inv = np.linalg.inv(self.t) # + 1e-5 * np.identity(4, dtype=np.float32))
return f"({Formatter.float4(inv)} * float4(p, 1.0)).xyz" return f"({Formatter.float4(inv)} * vec4(p, 1.0)).xyz"
class Object: class Object:
@ -87,7 +87,7 @@ class Object:
raise NotImplementedError raise NotImplementedError
@make_obj("cube") @make_obj("cube", "box")
class Cube(Object): class Cube(Object):
def __init__(self, _size: np.ndarray = np.array([1.0, 1.0, 1.0])) -> None: def __init__(self, _size: np.ndarray = np.array([1.0, 1.0, 1.0])) -> None:
super().__init__() super().__init__()
@ -97,10 +97,10 @@ class Cube(Object):
self.size = ArgParser.as_vec3(args) self.size = ArgParser.as_vec3(args)
def sdf_block(self) -> str: def sdf_block(self) -> str:
return f"sdCube({self.transform.p_expr()}, float3({self.size[0]}, {self.size[1]}, {self.size[2]}))" return f"sdCube({self.transform.p_expr()}, vec3({self.size[0]}, {self.size[1]}, {self.size[2]}))"
@make_obj("sphere") @make_obj("sphere", "ball")
class Sphere(Object): class Sphere(Object):
def __init__(self, _radius: float = 1.0) -> None: def __init__(self, _radius: float = 1.0) -> None:
super().__init__() super().__init__()
@ -113,7 +113,7 @@ class Sphere(Object):
return f"sdSphere({self.transform.p_expr()}, {self.radius})" return f"sdSphere({self.transform.p_expr()}, {self.radius})"
@make_obj("camera", ["cam"]) @make_obj("camera", "cam")
class Camera(Object): class Camera(Object):
def __init__(self, _focus: float = 1.0) -> None: def __init__(self, _focus: float = 1.0) -> None:
super().__init__() super().__init__()

View File

@ -1,39 +1,37 @@
from obj import Object from konabot.plugins.marchtoy.obj import Object
import numpy as np from konabot.plugins.marchtoy.utilities import ArgParser
from utilities import ArgParser
OPERATION_ENTRIES = {} OPERATION_ENTRIES = {}
def make_operation(*name: str):
def make_operation(name: str, aliases: list[str] = []):
def decorator(op): def decorator(op):
OPERATION_ENTRIES[name] = op # OPERATION_ENTRIES[name] = op
for alias in aliases: for alias in [*name]:
OPERATION_ENTRIES[alias] = op OPERATION_ENTRIES[alias] = op
return op return op
return decorator return decorator
@make_operation("pos", ["translate", "position", "p"]) @make_operation("pos", "translate", "position", "p")
def translate(obj: Object, args: list[str]): def translate(obj: Object, args: list[str]):
pos = ArgParser.as_vec3(args) pos = ArgParser.as_vec3(args)
obj.transform.translate(pos[0], pos[1], pos[2]) obj.transform.translate(pos[0], pos[1], pos[2])
@make_operation("rot", ["rotate", "r"]) @make_operation("rot", "rotate", "r")
def rotate(obj: Object, args: list[str]): def rotate(obj: Object, args: list[str]):
pos = ArgParser.as_vec3(args) pos = ArgParser.as_vec3(args)
obj.transform.rotate(pos[0], pos[1], pos[2]) obj.transform.rotate(pos[0], pos[1], pos[2])
@make_operation("lookat", ["look", "l"]) @make_operation("lookat", "look", "l")
def lookat(obj: Object, args: list[str]): def lookat(obj: Object, args: list[str]):
pos = ArgParser.as_vec3(args) pos = ArgParser.as_vec3(args)
obj.transform.lookat(pos[0], pos[1], pos[2]) obj.transform.lookat(pos[0], pos[1], pos[2])
@make_operation("color", ["col", "texture"]) @make_operation("color", "col", "texture")
def color(obj: Object, args: list[str]): def color(obj: Object, args: list[str]):
try: try:
if len(args) == 1: if len(args) == 1:

View File

@ -1,3 +1,4 @@
raise DeprecationWarning
from command import Scene from command import Scene
import skia import skia
import struct import struct

View File

@ -1,3 +1,5 @@
from dataclasses import dataclass
COLORS = { COLORS = {
"red": (1.0, 0.0, 0.0, 1.0), "red": (1.0, 0.0, 0.0, 1.0),
"green": (0.0, 1.0, 0.0, 1.0), "green": (0.0, 1.0, 0.0, 1.0),
@ -5,9 +7,6 @@ COLORS = {
"white": (1.0, 1.0, 1.0, 1.0), "white": (1.0, 1.0, 1.0, 1.0),
} }
from dataclasses import dataclass
@dataclass @dataclass
class Texture: class Texture:
color: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0) color: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0)

View File

@ -1,17 +1,26 @@
import numpy as np import numpy as np
from texture import COLORS from konabot.plugins.marchtoy.texture import COLORS
class Formatter: class Formatter:
@staticmethod @staticmethod
def float4(m: np.ndarray) -> str: def float4(m: np.ndarray) -> str:
if m.shape != (4, 4): if m.shape != (4, 4):
m = np.identity(4) m = np.identity(4)
v_0 = ", ".join([str(x) for x in m[0]]) v_0 = ", ".join([str(x) for x in m[:, 0]])
v_1 = ", ".join([str(x) for x in m[1]]) v_1 = ", ".join([str(x) for x in m[:, 1]])
v_2 = ", ".join([str(x) for x in m[2]]) v_2 = ", ".join([str(x) for x in m[:, 2]])
v_3 = ", ".join([str(x) for x in m[3]]) v_3 = ", ".join([str(x) for x in m[:, 3]])
return f"float4x4(float4({v_0}), float4({v_1}), float4({v_2}), float4({v_3}))" return f"float4x4(float4({v_0}), float4({v_1}), float4({v_2}), float4({v_3}))"
@staticmethod
def vec4(m: np.ndarray) -> str:
if m.shape != (4, 4):
m = np.identity(4)
v_0 = ", ".join([str(x) for x in m[:, 0]])
v_1 = ", ".join([str(x) for x in m[:, 1]])
v_2 = ", ".join([str(x) for x in m[:, 2]])
v_3 = ", ".join([str(x) for x in m[:, 3]])
return f"mat4(vec4({v_0}), vec4({v_1}), vec4({v_2}), vec4({v_3}))"
""" """
TODO: 除零出现 nan 情况的单独处理 TODO: 除零出现 nan 情况的单独处理

View File

@ -0,0 +1,17 @@
import sys
from pathlib import Path
PLUGIN_DIR = Path(__file__).resolve().parents[1] / "konabot" / "plugins" / "marchtoy"
if str(PLUGIN_DIR) not in sys.path:
sys.path.insert(0, str(PLUGIN_DIR))
from obj import Transform
def test_translate_expression_puts_offset_in_matrix_column():
transform = Transform().translate(1.0, 2.0, 3.0)
expr = transform.p_expr()
assert "float4(-1.0, -2.0, -3.0, 1.0)" in expr