Raymarch 初步
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -24,3 +24,6 @@ __pycache__
|
|||||||
/.venv
|
/.venv
|
||||||
/venv
|
/venv
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
|
||||||
|
# OS 相关
|
||||||
|
.DS_Store
|
||||||
160
konabot/plugins/sksl/marchtoy/command.py
Normal file
160
konabot/plugins/sksl/marchtoy/command.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
"""
|
||||||
|
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 obj import Object, Camera, OBJECT_ENTRIES
|
||||||
|
from op import OPERATION_ENTRIES
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
"""
|
||||||
|
# cmd = on_command("march", priority=10, block=True)
|
||||||
|
# @cmd.handle()
|
||||||
|
# async def _(args: Message = CommandArg()):
|
||||||
|
# if inst_text := args.extract_plain_text():
|
||||||
|
# instructions = inst_text.split(' ')
|
||||||
|
# for instruction in instructions:
|
||||||
|
# if instruction == '': continue
|
||||||
|
# if parsed_inst := parse_instruction(instruction):
|
||||||
|
# pass
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@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) -> str:
|
||||||
|
path = (
|
||||||
|
pathlib.Path("konabot")
|
||||||
|
/ "plugins"
|
||||||
|
/ "sksl"
|
||||||
|
/ "marchtoy"
|
||||||
|
/ "shaders"
|
||||||
|
/ "raymarch.sksl"
|
||||||
|
)
|
||||||
|
with path.open(encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
sdf_block: str = ""
|
||||||
|
|
||||||
|
index = 0
|
||||||
|
for canvas_item in self.canvas_objs:
|
||||||
|
sdf_block += f"float sd{index} = {canvas_item[1]};"
|
||||||
|
sdf_block += f"if(qry.value < sd{index})"
|
||||||
|
sdf_block += "{" + f"qry.value = sd{index}; qry.obj_id = {index}; " + "}\n"
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
content = content.replace("<SDF_BLOCK>", sdf_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
|
||||||
|
|
||||||
|
|
||||||
33
konabot/plugins/sksl/marchtoy/compiler.py
Normal file
33
konabot/plugins/sksl/marchtoy/compiler.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .obj import Scene
|
||||||
|
from .utilities import format_float, format_float3
|
||||||
|
|
||||||
|
|
||||||
|
TEMPLATE_PATH = Path(__file__).parent / "passes" / "pass1.sksl"
|
||||||
|
TEMPLATE = TEMPLATE_PATH.read_text("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def compile_scene_to_shader(scene: Scene) -> str:
|
||||||
|
object_blocks = "\n".join(
|
||||||
|
obj.compile_block(index)
|
||||||
|
for index, obj in enumerate(scene.objects, start=1)
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
TEMPLATE.replace("/*<SCENE_OBJECTS>*/", object_blocks)
|
||||||
|
.replace("/*<CAM_POS>*/", ", ".join(format_float(v) for v in scene.camera.position))
|
||||||
|
.replace(
|
||||||
|
"/*<CAM_TARGET>*/",
|
||||||
|
", ".join(format_float(v) for v in scene.camera.target),
|
||||||
|
)
|
||||||
|
.replace("/*<CAM_FOV>*/", format_float(scene.camera.fov))
|
||||||
|
.replace(
|
||||||
|
"/*<LIGHT_DIR>*/",
|
||||||
|
", ".join(format_float(v) for v in scene.light.direction),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"/*<GAMMA>*/",
|
||||||
|
format_float3((0.454545, 0.454545, 0.454545)),
|
||||||
|
)
|
||||||
|
)
|
||||||
123
konabot/plugins/sksl/marchtoy/obj.py
Normal file
123
konabot/plugins/sksl/marchtoy/obj.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import numpy as np
|
||||||
|
from texture import Texture
|
||||||
|
from utilities import ArgParser, SkslFormatter
|
||||||
|
|
||||||
|
OBJECT_ENTRIES = {}
|
||||||
|
|
||||||
|
|
||||||
|
def make_obj(name: str, aliases: list[str] = []):
|
||||||
|
def decorator(cls):
|
||||||
|
OBJECT_ENTRIES[name] = cls
|
||||||
|
for alias in aliases:
|
||||||
|
OBJECT_ENTRIES[id] = cls
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class Transform:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.t: np.ndarray = np.identity(4, dtype=np.float32)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def normalize(p: np.ndarray) -> np.ndarray:
|
||||||
|
return p / np.sqrt(np.dot(p, p))
|
||||||
|
|
||||||
|
def translate(self, x: float, y: float, z: float):
|
||||||
|
mat = np.identity(4, dtype=np.float32)
|
||||||
|
mat[0:3, 3] = [x, y, z]
|
||||||
|
self.t = mat @ self.t
|
||||||
|
return self
|
||||||
|
|
||||||
|
# scale 会破坏 sdf 的性质,不应该使用
|
||||||
|
def scale(self, x: float, y: float, z: float):
|
||||||
|
mat = np.identity(4, dtype=np.float32)
|
||||||
|
mat[0, 0], mat[1, 1], mat[2, 2] = x, y, z
|
||||||
|
self.t = mat @ self.t
|
||||||
|
return self
|
||||||
|
|
||||||
|
def rotate(self, x: float, y: float, z: float):
|
||||||
|
cx, sx = np.cos(x), np.sin(x)
|
||||||
|
cy, sy = np.cos(y), np.sin(y)
|
||||||
|
cz, sz = np.cos(z), np.sin(z)
|
||||||
|
|
||||||
|
mat = np.identity(4, dtype=np.float32)
|
||||||
|
mat[0, 0] = cy * cz
|
||||||
|
mat[0, 1] = sx * sy * cz - cx * sz
|
||||||
|
mat[0, 2] = cx * sy * cz + sx * sz
|
||||||
|
|
||||||
|
mat[1, 0] = cy * sz
|
||||||
|
mat[1, 1] = sx * sy * sz + cx * cz
|
||||||
|
mat[1, 2] = cx * sy * sz - sx * cz
|
||||||
|
|
||||||
|
mat[2, 0] = -sy
|
||||||
|
mat[2, 1] = sx * cy
|
||||||
|
mat[2, 2] = cx * cy
|
||||||
|
|
||||||
|
self.t = mat @ self.t
|
||||||
|
return self
|
||||||
|
|
||||||
|
def lookat(
|
||||||
|
self, x: float, y: float, z: float, up: np.ndarray = np.array([0.0, 1.0, 0.0])
|
||||||
|
):
|
||||||
|
p = self.t[0:3, 3]
|
||||||
|
q = np.array([x, y, z])
|
||||||
|
yaxis = up
|
||||||
|
zaxis = Transform.normalize(q - p)
|
||||||
|
xaxis = Transform.normalize(np.cross(zaxis, yaxis))
|
||||||
|
yaxis = Transform.normalize(np.cross(xaxis, zaxis))
|
||||||
|
V = np.array([xaxis, yaxis, zaxis])
|
||||||
|
self.t[:3, :3] = V
|
||||||
|
return self
|
||||||
|
|
||||||
|
def p_expr(self) -> str:
|
||||||
|
inv = np.linalg.inv(self.t) # + 1e-5 * np.identity(4, dtype=np.float32))
|
||||||
|
return f"({SkslFormatter.mat4(inv)} * float4(p, 1.0)).xyz"
|
||||||
|
|
||||||
|
|
||||||
|
class Object:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.transform: Transform = Transform()
|
||||||
|
self.texture: Texture = Texture()
|
||||||
|
|
||||||
|
def parse_args(self, args: list[str]):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def sdf_block(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@make_obj("cube")
|
||||||
|
class Cube(Object):
|
||||||
|
def __init__(self, _size: np.ndarray = np.array([1.0, 1.0, 1.0])) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.size = _size
|
||||||
|
|
||||||
|
def parse_args(self, args: list[str]):
|
||||||
|
self.size = ArgParser.as_vec3(args)
|
||||||
|
|
||||||
|
def sdf_block(self) -> str:
|
||||||
|
return f"sdCube({self.transform.p_expr()}, float3({self.size[0]}, {self.size[1]}, {self.size[2]}))"
|
||||||
|
|
||||||
|
|
||||||
|
@make_obj("sphere")
|
||||||
|
class Sphere(Object):
|
||||||
|
def __init__(self, _radius: float = 1.0) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.radius = _radius
|
||||||
|
|
||||||
|
def parse_args(self, args: list[str]):
|
||||||
|
self.radius = ArgParser.as_float(args)
|
||||||
|
|
||||||
|
def sdf_block(self) -> str:
|
||||||
|
return f"sdSphere({self.transform.p_expr()}, {self.radius})"
|
||||||
|
|
||||||
|
|
||||||
|
@make_obj("camera")
|
||||||
|
class Camera(Object):
|
||||||
|
def __init__(self, _focus: float = 1.0) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.focus = _focus
|
||||||
|
|
||||||
|
def parse_args(self, args: list[str]):
|
||||||
|
self.focus = ArgParser.as_float(args)
|
||||||
47
konabot/plugins/sksl/marchtoy/op.py
Normal file
47
konabot/plugins/sksl/marchtoy/op.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from obj import Object
|
||||||
|
import numpy as np
|
||||||
|
from utilities import ArgParser
|
||||||
|
|
||||||
|
OPERATION_ENTRIES = {}
|
||||||
|
|
||||||
|
|
||||||
|
def make_operation(name: str, aliases: list[str] = []):
|
||||||
|
def decorator(op):
|
||||||
|
OPERATION_ENTRIES[name] = op
|
||||||
|
for alias in aliases:
|
||||||
|
OPERATION_ENTRIES[alias] = op
|
||||||
|
return op
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
@make_operation("pos", ["translate", "position", "p"])
|
||||||
|
def translate(obj: Object, args: list[str]):
|
||||||
|
pos = ArgParser.as_vec3(args)
|
||||||
|
obj.transform.translate(pos[0], pos[1], pos[2])
|
||||||
|
|
||||||
|
|
||||||
|
@make_operation("rot", ["rotate", "r"])
|
||||||
|
def rotate(obj: Object, args: list[str]):
|
||||||
|
pos = ArgParser.as_vec3(args)
|
||||||
|
obj.transform.rotate(pos[0], pos[1], pos[2])
|
||||||
|
|
||||||
|
|
||||||
|
@make_operation("lookat", ["look", "l"])
|
||||||
|
def lookat(obj: Object, args: list[str]):
|
||||||
|
pos = ArgParser.as_vec3(args)
|
||||||
|
obj.transform.lookat(pos[0], pos[1], pos[2])
|
||||||
|
|
||||||
|
|
||||||
|
@make_operation("color", ["col", "texture"])
|
||||||
|
def color(obj: Object, args: list[str]):
|
||||||
|
try:
|
||||||
|
if len(args) == 3:
|
||||||
|
col = ArgParser.as_vec3(args)
|
||||||
|
obj.texture.color = (col[0], col[1], col[2], 1.0)
|
||||||
|
elif len(args) == 4:
|
||||||
|
col = ArgParser.as_vec4(args)
|
||||||
|
obj.texture.color = (col[0], col[1], col[2], col[4])
|
||||||
|
|
||||||
|
except:
|
||||||
|
raise Exception("unknown color")
|
||||||
64
konabot/plugins/sksl/marchtoy/shaders/raymarch.sksl
Normal file
64
konabot/plugins/sksl/marchtoy/shaders/raymarch.sksl
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
uniform float u_time;
|
||||||
|
uniform float2 u_resolution;
|
||||||
|
|
||||||
|
const float EPS = 0.001;
|
||||||
|
const float MAX_ITER = 64;
|
||||||
|
struct sdQuery {
|
||||||
|
float value;
|
||||||
|
int obj_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sdCube(float3 p, float3 b) {
|
||||||
|
p = abs(p) - b;
|
||||||
|
return length(max(p, 0.0)) + min(max(p.x, max(p.y, p.z)), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float sdSphere(float3 p, float r) {
|
||||||
|
return length(p) - r
|
||||||
|
}
|
||||||
|
|
||||||
|
sdQuery sd(float3 p) {
|
||||||
|
sdQuery qry;
|
||||||
|
qry.value = 100000000.0;
|
||||||
|
<SDF_BLOCK>
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 nrm(float3 p) {
|
||||||
|
float2 d = float2(EPS, 0.0);
|
||||||
|
return normalize(float4(
|
||||||
|
sd(p + d.xyy) - sd(p),
|
||||||
|
sd(p + d.yxy) - sd(p),
|
||||||
|
sd(p + d.yyx) - sd(p),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 color(float3 p) {
|
||||||
|
|
||||||
|
return float4(nrm(p), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 march(float3 p, float3 r) {
|
||||||
|
sdQuery qry;
|
||||||
|
float4 col;
|
||||||
|
for(int i = 0; i < MAX_ITER; ++i) {
|
||||||
|
qry = sd(p);
|
||||||
|
if(qry.value < EPS){
|
||||||
|
col = color(p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p += qry.value * r;
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
half4 main(float2 fragCoord) {
|
||||||
|
float2 uv = -2. * (fragCoord / u_resolution - .5) * float2(u_resolution.x / u_resolution.y, 1.);
|
||||||
|
float3 c_p = float3(<CAM_POS>);
|
||||||
|
float3 c_z = normalize(float3(<CAM_DIR>));
|
||||||
|
float3 c_y = float3(0., 1., 0.);
|
||||||
|
float3 c_x = normalize(cross(c_z, c_y));
|
||||||
|
float3x3 view = float3x3(c_x, c_y, c_z);
|
||||||
|
float3 r = normalize(float3(u, v, <CAM_FOCUS>));
|
||||||
|
return half4(march(c_p, view * r));
|
||||||
|
}
|
||||||
|
|
||||||
13
konabot/plugins/sksl/marchtoy/texture.py
Normal file
13
konabot/plugins/sksl/marchtoy/texture.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
COLORS = {
|
||||||
|
"red": (1.0, 0.0, 0.0, 1.0),
|
||||||
|
"green": (0.0, 1.0, 0.0, 1.0),
|
||||||
|
"blue": (0.0, 0.0, 1.0, 1.0),
|
||||||
|
"white": (1.0, 1.0, 1.0, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Texture:
|
||||||
|
color: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0)
|
||||||
67
konabot/plugins/sksl/marchtoy/utilities.py
Normal file
67
konabot/plugins/sksl/marchtoy/utilities.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import numpy as np
|
||||||
|
from texture import COLORS
|
||||||
|
|
||||||
|
|
||||||
|
class SkslFormatter:
|
||||||
|
@staticmethod
|
||||||
|
def mat4(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"float4x4(float4({v_0}), float4({v_1}), float4({v_2}), float4({v_3}))"
|
||||||
|
|
||||||
|
|
||||||
|
class ArgParser:
|
||||||
|
@staticmethod
|
||||||
|
def as_float(args: list[str], default: float = 0.0) -> float:
|
||||||
|
try:
|
||||||
|
if len(args) >= 1:
|
||||||
|
x = float(args[0])
|
||||||
|
return x
|
||||||
|
except:
|
||||||
|
raise Exception(f"cannot parse {args}")
|
||||||
|
return default
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def as_vec3(
|
||||||
|
args: list[str], default: np.ndarray = np.array((0.0, 0.0, 0.0))
|
||||||
|
) -> np.ndarray:
|
||||||
|
try:
|
||||||
|
if len(args) == 1:
|
||||||
|
x = float(args[0])
|
||||||
|
return np.array((x, x, x))
|
||||||
|
elif len(args) == 3:
|
||||||
|
x = float(args[0])
|
||||||
|
y = float(args[1])
|
||||||
|
z = float(args[2])
|
||||||
|
return np.array((x, y, z))
|
||||||
|
except:
|
||||||
|
raise Exception(f"cannot parse {args}")
|
||||||
|
return default
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def as_vec4(
|
||||||
|
args: list[str], default: np.ndarray = np.array((0.0, 0.0, 0.0, 0.0))
|
||||||
|
) -> np.ndarray:
|
||||||
|
try:
|
||||||
|
if len(args) == 1:
|
||||||
|
x = float(args[0])
|
||||||
|
return np.array((x, x, x))
|
||||||
|
elif len(args) == 3:
|
||||||
|
x = float(args[0])
|
||||||
|
y = float(args[1])
|
||||||
|
z = float(args[2])
|
||||||
|
w = float(args[3])
|
||||||
|
return np.array((x, y, z, w))
|
||||||
|
except:
|
||||||
|
raise Exception(f"cannot parse {args}")
|
||||||
|
return default
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def as_literal_color(args: list[str], default=np.array((1.0, 1.0, 1.0, 1.0))):
|
||||||
|
if len(args) == 1 and args[0] in COLORS:
|
||||||
|
return np.array(COLORS[args[0]])
|
||||||
|
return default
|
||||||
@ -24,7 +24,6 @@ def _pack_uniforms(uniforms_dict, width, height, time_val):
|
|||||||
# 移除填充字节,使用紧凑布局
|
# 移除填充字节,使用紧凑布局
|
||||||
return time_bytes + res_bytes
|
return time_bytes + res_bytes
|
||||||
|
|
||||||
|
|
||||||
def _render_sksl_shader_to_gif(
|
def _render_sksl_shader_to_gif(
|
||||||
sksl_code: str,
|
sksl_code: str,
|
||||||
width: int = 256,
|
width: int = 256,
|
||||||
@ -152,4 +151,4 @@ async def render_sksl_shader_to_gif(
|
|||||||
height,
|
height,
|
||||||
duration,
|
duration,
|
||||||
fps,
|
fps,
|
||||||
)
|
)
|
||||||
2148
poetry.lock
generated
2148
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,6 @@ dependencies = [
|
|||||||
"imagetext-py (>=2.2.0,<3.0.0)",
|
"imagetext-py (>=2.2.0,<3.0.0)",
|
||||||
"opencv-python-headless (>=4.12.0.88,<5.0.0.0)",
|
"opencv-python-headless (>=4.12.0.88,<5.0.0.0)",
|
||||||
"returns (>=0.26.0,<0.27.0)",
|
"returns (>=0.26.0,<0.27.0)",
|
||||||
"skia-python (>=138.0,<139.0)",
|
|
||||||
"nonebot-plugin-analysis-bilibili (>=2.8.1,<3.0.0)",
|
"nonebot-plugin-analysis-bilibili (>=2.8.1,<3.0.0)",
|
||||||
"qrcode (>=8.2,<9.0)",
|
"qrcode (>=8.2,<9.0)",
|
||||||
"nanoid (>=2.0.0,<3.0.0)",
|
"nanoid (>=2.0.0,<3.0.0)",
|
||||||
@ -39,6 +38,7 @@ dependencies = [
|
|||||||
"pytest-cov (>=7.0.0,<8.0.0)",
|
"pytest-cov (>=7.0.0,<8.0.0)",
|
||||||
"aiosignal (>=1.4.0,<2.0.0)",
|
"aiosignal (>=1.4.0,<2.0.0)",
|
||||||
"pytest-mock (>=3.15.1,<4.0.0)",
|
"pytest-mock (>=3.15.1,<4.0.0)",
|
||||||
|
"skia-python (>=144.0.post2,<145.0)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
|
|||||||
Reference in New Issue
Block a user