diff --git a/README.md b/README.md
index e843ff0..2d5d125 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,6 @@ A ray marching renderer in rust
## What is planned
- Testing of more shapes
-- Support for a lua-based scene representation DSL
- Support for controlling the whole application from lua
- Support for linking against a `scene.so` exporting a scene
- Support for using as a library
@@ -27,6 +26,7 @@ A ray marching renderer in rust
## Examples
![1st test scene](prod/1.png)
![2nd test scene](prod/2.png)
+![3rd test scene](prod/3.png)
![randomly generated spheres](prod/randomspheres.png)
![randomly generated objects](prod/smolgalaxy.png)
diff --git a/prod/3.png b/prod/3.png
new file mode 100644
index 0000000..526ec82
Binary files /dev/null and b/prod/3.png differ
diff --git a/scenes/randomspheres.lua b/scenes/randomspheres.lua
index 273eb4a..87d6788 100644
--- a/scenes/randomspheres.lua
+++ b/scenes/randomspheres.lua
@@ -1,28 +1,20 @@
local unpack = table.unpack or _G.unpack
-math.randomseed(os.time())
-
-local function randab(a, b)
- return math.random() * (b-a) + a
-end
+util.seed()
local function randtable(n, a, b)
local t = {}
for i=1, n do
- t[i] = randab(a, b)
+ t[i] = util.randab(a, b)
end
return t
end
-local function choice(list)
- return list[math.random(1, #list)]
-end
-
local function randomsphere()
local pos = vec3.new(unpack(randtable(3, -2, 2)))
- local radius = randab(0, 0.5)
+ local radius = util.randab(0, 0.5)
local emission = colorvec.new(unpack(randtable(4, 0, 1)))
local reflection = colormat.new(unpack(randtable(16, 0, 1)))
- local surfacetype = choice({surfacetype.DIFFUSE, surfacetype.REFLECTIVE, surfacetype.STOP})
+ local surfacetype = util.choice({surfacetype.DIFFUSE, surfacetype.REFLECTIVE, surfacetype.STOP})
return obj.withmaterial(
obj.sphere(pos, radius),
@@ -30,23 +22,8 @@ local function randomsphere()
)
end
-local function union(objs)
- local n = #objs
- if n == 1 then return objs[1] end
-
- local mid = n//2
- local left, right = {}, {}
- for i=1, mid do
- left[i] = objs[i]
- end
- for i=mid+1, n do
- right[i-mid] = objs[i]
- end
- return obj.union(union(left), union(right))
-end
-
local spheres = {}
for i=1, 100 do
spheres[i] = randomsphere()
end
-return union(spheres)
\ No newline at end of file
+return util.union(spheres)
\ No newline at end of file
diff --git a/scenes/smolgalaxy.lua b/scenes/smolgalaxy.lua
index 23dfec3..845a84d 100644
--- a/scenes/smolgalaxy.lua
+++ b/scenes/smolgalaxy.lua
@@ -1,30 +1,22 @@
local unpack = table.unpack or _G.unpack
-math.randomseed(os.time())
+util.seed()
local SCALE = 0.3
local N_EACH = 25
-local function randab(a, b)
- return math.random() * (b-a) + a
-end
-
local function randtable(n, a, b)
local t = {}
for i=1, n do
- t[i] = randab(a, b)
+ t[i] = util.randab(a, b)
end
return t
end
-local function choice(list)
- return list[math.random(1, #list)]
-end
-
local MATERIALS = {surfacetype.DIFFUSE, surfacetype.REFLECTIVE, surfacetype.STOP}
local function randommaterial()
local emission = colorvec.new(unpack(randtable(4, 0, 1)))
local reflection = colormat.new(unpack(randtable(16, 0, 1)))
- local surfacetype = choice(MATERIALS)
+ local surfacetype = util.choice(MATERIALS)
return material.new(emission, reflection, surfacetype)
end
@@ -62,20 +54,20 @@ local ORIENTATIONS = {
)
}
local function randomorientation()
- return choice(ORIENTATIONS)
+ return util.choice(ORIENTATIONS)
end
local function randomsphere()
local pos = vec3.new(unpack(randtable(3, -2, 2)))
- local radius = randab(0, SCALE)
+ local radius = util.randab(0, SCALE)
return obj.sphere(pos, radius)
end
local function randomtorus()
local pos = vec3.new(unpack(randtable(3, -2, 2)))
- local radius = randab(0, SCALE)
- local thickness = randab(radius/4, radius)
+ local radius = util.randab(0, SCALE)
+ local thickness = util.randab(radius/4, radius)
local orientation = randomorientation()
return obj.affinetransform(obj.torus(pos, radius, thickness), orientation, vec3.O)
@@ -83,15 +75,15 @@ end
local function randomcuboid()
local pos = vec3.new(unpack(randtable(3, -2, 2)))
- local radius = randab(0, SCALE)
+ local radius = util.randab(0, SCALE)
return obj.cuboid(pos, vec3.new(radius, radius, radius))
end
local function randomcylinder()
local pos = vec3.new(unpack(randtable(3, -2, 2)))
- local radius = randab(0, SCALE)
- local height = randab(0, SCALE)
+ local radius = util.randab(0, SCALE)
+ local height = util.randab(0, SCALE)
local orientation = randomorientation()
return obj.affinetransform(
@@ -104,25 +96,10 @@ local function randomcylinder()
)
end
-local function union(objs)
- local n = #objs
- if n == 1 then return objs[1] end
-
- local mid = n//2
- local left, right = {}, {}
- for i=1, mid do
- left[i] = objs[i]
- end
- for i=mid+1, n do
- right[i-mid] = objs[i]
- end
- return obj.union(union(left), union(right))
-end
-
local objects = {}
for i=1, N_EACH do table.insert(objects, randomsphere()) end
for i=1, N_EACH do table.insert(objects, randomtorus()) end
for i=1, N_EACH do table.insert(objects, randomcuboid()) end
for i=1, N_EACH do table.insert(objects, randomcylinder()) end
for i, object in ipairs(objects) do objects[i] = obj.withmaterial(object, randommaterial()) end
-return union(objects)
\ No newline at end of file
+return util.union(objects)
\ No newline at end of file
diff --git a/src/lua/color.rs b/src/lua/color.rs
index 5e337ab..388c224 100644
--- a/src/lua/color.rs
+++ b/src/lua/color.rs
@@ -4,7 +4,7 @@ use rlua::{UserData, Context, Table};
impl UserData for ColorVec {}
impl UserData for ColorMat {}
-pub fn color_vec(ctx: Context, _: ()) -> rlua::Result
{
+pub fn color_vec<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
let module = ctx.create_table()?;
module.set("new", ctx.create_function(
@@ -16,7 +16,7 @@ pub fn color_vec(ctx: Context, _: ()) -> rlua::Result {
Ok(module)
}
-pub fn color_mat(ctx: Context, _: ()) -> rlua::Result {
+pub fn color_mat<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
let module = ctx.create_table()?;
module.set("new", ctx.create_function(
diff --git a/src/lua/light.rs b/src/lua/light.rs
index 9d7b872..3a99198 100644
--- a/src/lua/light.rs
+++ b/src/lua/light.rs
@@ -3,7 +3,7 @@ use rlua::{UserData, Context, Table};
impl UserData for Light {}
-pub fn light(ctx: Context, _: ()) -> rlua::Result {
+pub fn light<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
let module = ctx.create_table()?;
module.set("new", ctx.create_function(
diff --git a/src/lua/mat3.rs b/src/lua/mat3.rs
index ccd78f0..cb763ba 100644
--- a/src/lua/mat3.rs
+++ b/src/lua/mat3.rs
@@ -29,7 +29,7 @@ impl UserData for Mat3 {
}
}
-pub fn mat3(ctx: Context, _: ()) -> rlua::Result {
+pub fn mat3<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
let module = ctx.create_table()?;
module.set("new", ctx.create_function(
diff --git a/src/lua/material.rs b/src/lua/material.rs
index d5e9ec2..fe80759 100644
--- a/src/lua/material.rs
+++ b/src/lua/material.rs
@@ -4,7 +4,7 @@ use rlua::{UserData, Context, Table};
impl UserData for SurfaceType {}
impl UserData for Material {}
-pub fn surface_type(ctx: Context, _: ()) -> rlua::Result {
+pub fn surface_type<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
let module = ctx.create_table()?;
module.set("DIFFUSE", SurfaceType::Diffuse)?;
@@ -14,7 +14,7 @@ pub fn surface_type(ctx: Context, _: ()) -> rlua::Result {
Ok(module)
}
-pub fn material(ctx: Context, _: ()) -> rlua::Result {
+pub fn material<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
let module = ctx.create_table()?;
module.set("new", ctx.create_function(
diff --git a/src/lua/mod.rs b/src/lua/mod.rs
index f1f98e0..c25074d 100644
--- a/src/lua/mod.rs
+++ b/src/lua/mod.rs
@@ -6,6 +6,8 @@ mod mat3;
mod color;
mod material;
mod light;
+mod transform;
+mod util;
pub use obj::{LuaObject, obj};
pub use vec3::vec3;
@@ -13,25 +15,28 @@ pub use mat3::mat3;
pub use color::{color_vec, color_mat};
pub use material::{surface_type, material};
pub use light::light;
+pub use transform::transform;
+pub use util::util;
-pub fn env(ctx: Context, _: ()) -> rlua::Result {
- let module = ctx.create_table()?;
+pub fn add_scene_env<'lua>(ctx: Context<'lua>, env: Table<'lua>) -> rlua::Result> {
+ env.set("obj", obj(ctx, env.clone())?)?;
+ env.set("vec3", vec3(ctx, env.clone())?)?;
+ env.set("mat3", mat3(ctx, env.clone())?)?;
+ env.set("colorvec", color_vec(ctx, env.clone())?)?;
+ env.set("colormat", color_mat(ctx, env.clone())?)?;
+ env.set("surfacetype", surface_type(ctx, env.clone())?)?;
+ env.set("material", material(ctx, env.clone())?)?;
+ env.set("light", light(ctx, env.clone())?)?;
+ env.set("transform", transform(ctx, env.clone())?)?;
+ env.set("util", util(ctx, env.clone())?)?;
- module.set("obj", obj(ctx, ())?)?;
- module.set("vec3", vec3(ctx, ())?)?;
- module.set("mat3", mat3(ctx, ())?)?;
- module.set("colorvec", color_vec(ctx, ())?)?;
- module.set("colormat", color_mat(ctx, ())?)?;
- module.set("surfacetype", surface_type(ctx, ())?)?;
- module.set("material", material(ctx, ())?)?;
- module.set("light", light(ctx, ())?)?;
-
- Ok(module)
+ Ok(env)
}
pub fn scene_from_file(file: String) -> rlua::Result {
Lua::new().context(|ctx| {
- let env = env(ctx, ())?;
+ let env = ctx.create_table()?;
+ add_scene_env(ctx, env.clone())?;
let meta = ctx.create_table()?;
meta.set("__index", ctx.globals())?;
env.set_metatable(Some(meta));
diff --git a/src/lua/obj.rs b/src/lua/obj.rs
index 8ba7088..5b51bcf 100644
--- a/src/lua/obj.rs
+++ b/src/lua/obj.rs
@@ -27,7 +27,7 @@ impl Obj for LuaObject {
impl UserData for LuaObject {}
-pub fn obj(ctx: Context, _: ()) -> rlua::Result {
+pub fn obj<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
let module = ctx.create_table()?;
module.set("cuboid", ctx.create_function(
@@ -70,10 +70,14 @@ pub fn obj(ctx: Context, _: ()) -> rlua::Result {
|ctx, ()| LuaObject::new(Waves::new())
)?)?;
- module.set("withlights", ctx.create_function(
+ module.set("withlight", ctx.create_function(
|ctx, (obj, light): (LuaObject, Light)| LuaObject::new(WithLights::new_one(obj.get(), light))
)?)?;
+ module.set("withlights", ctx.create_function(
+ |ctx, (obj, lights): (LuaObject, Vec)| LuaObject::new(WithAnyLights::new(obj.get(), lights))
+ )?)?;
+
module.set("withmaterial", ctx.create_function(
|ctx, (obj, material): (LuaObject, Material)| LuaObject::new(WithMaterial::new(obj.get(), material))
)?)?;
diff --git a/src/lua/transform.rs b/src/lua/transform.rs
new file mode 100644
index 0000000..3b9ad5c
--- /dev/null
+++ b/src/lua/transform.rs
@@ -0,0 +1,40 @@
+use rlua::{Context, Table, Error};
+use crate::object::{SWAP_XY, SWAP_YZ, SWAP_XZ, scale_xyz, scale, scale_x, scale_y, scale_z};
+use crate::structs::{Mat3, I3};
+
+pub fn transform<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
+ let module = ctx.create_table()?;
+
+ module.set("SWAPXY", SWAP_XY)?;
+ module.set("SWAPXZ", SWAP_XZ)?;
+ module.set("SWAPYZ", SWAP_YZ)?;
+
+ module.set("scalexyz", ctx.create_function(
+ |ctx, (x, y, z)| Ok(scale_xyz(x, y, z))
+ )?)?;
+ module.set("scale", ctx.create_function(
+ |ctx, k| Ok(scale(k))
+ )?)?;
+
+ module.set("scalex", ctx.create_function(
+ |ctx, k| Ok(scale_x(k))
+ )?)?;
+ module.set("scaley", ctx.create_function(
+ |ctx, k| Ok(scale_y(k))
+ )?)?;
+ module.set("scalez", ctx.create_function(
+ |ctx, k| Ok(scale_z(k))
+ )?)?;
+
+ module.set("stack", ctx.create_function(
+ |ctx, transforms: Vec| {
+ let mut acc = I3;
+ for trans in transforms.iter().rev().cloned() {
+ acc = trans * I3;
+ }
+ Ok(acc)
+ }
+ )?)?;
+
+ Ok(module)
+}
\ No newline at end of file
diff --git a/src/lua/util.lua b/src/lua/util.lua
new file mode 100644
index 0000000..e1b4730
--- /dev/null
+++ b/src/lua/util.lua
@@ -0,0 +1,71 @@
+local env = ...
+local obj = env.obj
+local vec3 = env.vec3
+local mat3 = env.mat3
+local transform = env.transform
+
+local M = {}
+
+-- technically a binary fold
+local function binstack(objs, fn)
+ local n = #objs
+ if n == 1 then return objs[1] end
+
+ local mid = n//2
+ local left, right = {}, {}
+ for i=1, mid do
+ left[i] = objs[i]
+ end
+ for i=mid+1, n do
+ right[i-mid] = objs[i]
+ end
+ return fn(binstack(left, fn), binstack(right, fn))
+end
+
+-- technically a fold
+local function linstack(objs, fn)
+ local head = objs[1]
+ for i=2, #objs do
+ head = fn(head, objs[i])
+ end
+ return head
+end
+
+-- random functions
+local SEED = os.getenv "SEED" or os.time()
+function M.seed()
+ math.randomseed(SEED)
+end
+function M.randab(a, b)
+ return math.random() * (b-a) + a
+end
+function M.choice(list)
+ return list[math.random(1, #list)]
+end
+
+-- multiple object composition
+function M.union(objs)
+ return binstack(objs, obj.union)
+end
+function M.intersection(objs)
+ return binstack(objs, obj.intersection)
+end
+function M.smoothunion(objs, k)
+ return linstack(objs, function(a, b) return obj.smoothunion(a, b, k) end)
+end
+function M.smoothintersection(objs, k)
+ return linstack(objs, function(a, b) return obj.smoothintersection(a, b, k) end)
+end
+
+-- simpler transformations
+function M.scale(object, k)
+ return obj.affinetransform(object, transform.scale(k), vec3.O)
+end
+function M.transform(object, mat)
+ return obj.affinetransform(object, mat, vec3.O)
+end
+function M.translate(object, vec)
+ return obj.affinetransform(object, mat3.O, vec)
+end
+
+return M
\ No newline at end of file
diff --git a/src/lua/util.rs b/src/lua/util.rs
new file mode 100644
index 0000000..e0f90ae
--- /dev/null
+++ b/src/lua/util.rs
@@ -0,0 +1,5 @@
+use rlua::{Context, Table};
+
+pub fn util<'lua>(ctx: Context<'lua>, env: Table<'lua>) -> rlua::Result> {
+ ctx.load(include_str!("util.lua")).call(env)
+}
\ No newline at end of file
diff --git a/src/lua/vec3.rs b/src/lua/vec3.rs
index 7e8f315..1bbbbcd 100644
--- a/src/lua/vec3.rs
+++ b/src/lua/vec3.rs
@@ -24,7 +24,7 @@ impl UserData for Vec3 {
}
}
-pub fn vec3(ctx: Context, _: ()) -> rlua::Result {
+pub fn vec3<'lua>(ctx: Context<'lua>, _env: Table<'lua>) -> rlua::Result> {
let module = ctx.create_table()?;
module.set("new", ctx.create_function(
diff --git a/src/main.rs b/src/main.rs
index 9ff326a..693a294 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -68,8 +68,7 @@ fn default_scene2() -> Scene {
}
fn default_scene3() -> Scene {
- //TODO fix this scene
- let s1 = WithMaterial::new(Sphere::new_xyz(4., 0., 0., 1.), WHITE);
+ let s1 = WithMaterial::new(Sphere::new_xyz(4., 0., 0., 1.), MIRROR);
let s2 = WithMaterial::new(Sphere::new_xyz(3., 1., 1., 0.5), GREEN);
let navion = WithMaterial::new(Plane::new_xyz(0., 1., -1., 3.), BLUE);
let backwall = WithMaterial::new(Plane::new_xyz(-1., -1., -0.5, 8.), RED);
@@ -86,7 +85,8 @@ fn default_scene3() -> Scene {
fn main() {
// get scene and camera
- let scene = scene_from_file("scenes/smolgalaxy.lua".to_owned()).unwrap();
+ //let scene = scene_from_file("scenes/randomspheres.lua".to_owned()).unwrap();
+ let scene = default_scene3();
let cam = default_cam();
// get stats on the scene we're about to render
diff --git a/src/object/mod.rs b/src/object/mod.rs
index 8adedbe..1c5aed1 100644
--- a/src/object/mod.rs
+++ b/src/object/mod.rs
@@ -70,6 +70,6 @@ pub use cylinder::Cylinder;
pub use torus::Torus;
pub use waves::Waves;
pub use with_material::{WithMaterial, WithDynamicMaterial};
-pub use with_lights::{WithLights, WithLight};
+pub use with_lights::{WithLights, WithLight, WithAnyLights};
pub use transform::*;
pub use scene::Scene;
\ No newline at end of file
diff --git a/src/object/transform.rs b/src/object/transform.rs
index 264a8a6..499f962 100644
--- a/src/object/transform.rs
+++ b/src/object/transform.rs
@@ -8,18 +8,19 @@ pub struct AffineTransform {
obj: T,
transform: Mat3,
transform_inv: Mat3,
- translate: Vec3
+ translate: Vec3,
+ scale: f64
}
impl AffineTransform {
pub fn new(obj: T, transform: Mat3, translate: Vec3) -> AffineTransform {
- AffineTransform { obj, transform, transform_inv: transform.invert(), translate }
+ AffineTransform { obj, transform, transform_inv: transform.invert(), translate, scale: transform.det().cbrt() }
}
pub fn new_linear(obj: T, transform: Mat3) -> AffineTransform {
- AffineTransform { obj, transform, transform_inv: transform.invert(), translate: O }
+ AffineTransform { obj, transform, transform_inv: transform.invert(), translate: O, scale: transform.det().cbrt() }
}
pub fn new_translate(obj: T, translate: Vec3) -> AffineTransform {
- AffineTransform { obj, transform: I3, transform_inv: I3, translate }
+ AffineTransform { obj, transform: I3, transform_inv: I3, translate, scale: 1. }
}
fn apply_rev(&self, point: Vec3) -> Vec3 {
@@ -60,7 +61,7 @@ pub const fn scale_z(k: f64) -> Mat3 { scale_xyz(1., 1., k) }
impl Obj for AffineTransform {
fn distance_to(&self, point: Vec3) -> f64 {
- self.obj.distance_to(self.apply_rev(point))
+ self.obj.distance_to(self.apply_rev(point)) * self.scale
}
fn normal_at(&self, point: Vec3) -> Vec3 {
self.apply_fwd(self.obj.normal_at(self.apply_rev(point))).unit()
diff --git a/src/object/with_lights.rs b/src/object/with_lights.rs
index f22ac71..5d013f9 100644
--- a/src/object/with_lights.rs
+++ b/src/object/with_lights.rs
@@ -41,4 +41,40 @@ impl WithLight {
pub fn new_one(obj: T, light: Light) -> WithLight {
WithLight { obj, lights: [light; 1] }
}
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct WithAnyLights {
+ obj: T,
+ lights: Vec
+}
+
+impl WithAnyLights {
+ pub fn new(obj: T, lights: Vec) -> WithAnyLights {
+ WithAnyLights { obj, lights }
+ }
+
+ pub fn add_light(&mut self, light: Light) {
+ self.lights.push(light)
+ }
+}
+
+impl Obj for WithAnyLights {
+ fn distance_to(&self, point: Vec3) -> f64 {
+ self.obj.distance_to(point)
+ }
+ fn normal_at(&self, point: Vec3) -> Vec3 {
+ self.obj.normal_at(point)
+ }
+ fn material_at(&self, point: Vec3) -> Material {
+ self.obj.material_at(point)
+ }
+ fn get_lights(&self) -> Vec {
+ let mut l = self.obj.get_lights();
+ l.extend(&self.lights);
+ l
+ }
+ fn node_count(&self) -> u32 {
+ self.obj.node_count() + 1
+ }
}
\ No newline at end of file
diff --git a/test b/test
index b6daf5b..05bc9e0 100755
--- a/test
+++ b/test
@@ -2,6 +2,6 @@
clear
cargo build --release && \
clear && \
- time target/release/rmarcher && \
+ time SEED=0 target/release/rmarcher && \
printf '\n' && \
kitty +kitten icat --align=left a.png