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