diff --git a/README.md b/README.md index 2909dca..5a6900a 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,19 @@ A ray marching renderer in rust - Spectral simulation (currently using 4 color components) - Global illumination (with diffuse and reflective surfaces) - Punctual illumination +- Defining scenes in lua ## 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 ## Examples ![1st test scene](prod/1.png) ![2nd test scene](prod/2.png) +![randomly generated spheres](prod/randomspheres.png) ## License MIT \ No newline at end of file diff --git a/prod/randomspheres.png b/prod/randomspheres.png new file mode 100644 index 0000000..095d142 Binary files /dev/null and b/prod/randomspheres.png differ diff --git a/scenes/randomspheres.lua b/scenes/randomspheres.lua new file mode 100644 index 0000000..273eb4a --- /dev/null +++ b/scenes/randomspheres.lua @@ -0,0 +1,52 @@ +local unpack = table.unpack or _G.unpack +math.randomseed(os.time()) + +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) + 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 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}) + + return obj.withmaterial( + obj.sphere(pos, radius), + material.new(emission, reflection, surfacetype) + ) +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 diff --git a/src/lua/color.rs b/src/lua/color.rs new file mode 100644 index 0000000..5e337ab --- /dev/null +++ b/src/lua/color.rs @@ -0,0 +1,47 @@ +use crate::structs::{ColorVec, ColorMat}; +use rlua::{UserData, Context, Table}; + +impl UserData for ColorVec {} +impl UserData for ColorMat {} + +pub fn color_vec(ctx: Context, _: ()) -> rlua::Result { + let module = ctx.create_table()?; + + module.set("new", ctx.create_function( + |ctx, (r, g, b, u)| Ok(ColorVec::new([r, g, b, u])) + )?)?; + + module.set("ZERO", ColorVec::new_zero())?; + + Ok(module) +} + +pub fn color_mat(ctx: Context, _: ()) -> rlua::Result
{ + let module = ctx.create_table()?; + + module.set("new", ctx.create_function( + |ctx, ( + a, b, c, d, + e, f, g, h, + i, j, k, l, + m, n, o, p + )| Ok(ColorMat::new([ + [a, b, c, d], + [e, f, g, h], + [i, j, k, l], + [m, n, o, p] + ])) + )?)?; + + module.set("newfromvec", ctx.create_function( + |ctx, vec: ColorVec| Ok(ColorMat::new_from_diagonal(vec)) + )?)?; + + module.set("newfromdiagonal", ctx.create_function( + |ctx, (r, g, b, u)| Ok(ColorMat::new_from_diagonal(ColorVec::new([r, g, b, u]))) + )?)?; + + module.set("ZERO", ColorMat::new_zero())?; + + Ok(module) +} diff --git a/src/lua/light.rs b/src/lua/light.rs new file mode 100644 index 0000000..9d7b872 --- /dev/null +++ b/src/lua/light.rs @@ -0,0 +1,14 @@ +use crate::light::Light; +use rlua::{UserData, Context, Table}; + +impl UserData for Light {} + +pub fn light(ctx: Context, _: ()) -> rlua::Result
{ + let module = ctx.create_table()?; + + module.set("new", ctx.create_function( + |ctx, (pos, color)| Ok(Light::new(pos, color)) + )?)?; + + Ok(module) +} \ No newline at end of file diff --git a/src/lua/mat3.rs b/src/lua/mat3.rs new file mode 100644 index 0000000..ccd78f0 --- /dev/null +++ b/src/lua/mat3.rs @@ -0,0 +1,43 @@ +use crate::structs::{Mat3, I3, O3}; +use rlua::{UserData, UserDataMethods, Context, MetaMethod, Table}; + +impl UserData for Mat3 { + fn add_methods<'lua, T: UserDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_meta_method(MetaMethod::Add, |ctx, a: &Self, b: Self| Ok(*a+b)); + methods.add_meta_method(MetaMethod::Sub, |ctx, a: &Self, b: Self| Ok(*a-b)); + methods.add_meta_method(MetaMethod::Mul, |ctx, a: &Self, b: Self| Ok(*a*b)); + methods.add_meta_method(MetaMethod::Unm, |ctx, a: &Self, b: ()| Ok(-*a)); + methods.add_meta_method(MetaMethod::Mul, |ctx, a: &Self, b: f64| Ok(*a*b)); + methods.add_meta_method(MetaMethod::Div, |ctx, a: &Self, b: f64| Ok(*a/b)); + + methods.add_method("a", |ctx, x, ()| Ok(x.a())); + methods.add_method("b", |ctx, x, ()| Ok(x.b())); + methods.add_method("c", |ctx, x, ()| Ok(x.c())); + methods.add_method("d", |ctx, x, ()| Ok(x.d())); + methods.add_method("e", |ctx, x, ()| Ok(x.e())); + methods.add_method("f", |ctx, x, ()| Ok(x.f())); + methods.add_method("g", |ctx, x, ()| Ok(x.g())); + methods.add_method("h", |ctx, x, ()| Ok(x.h())); + methods.add_method("i", |ctx, x, ()| Ok(x.i())); + + methods.add_method("det", |ctx, x, ()| Ok(x.det())); + methods.add_method("trans",|ctx, x, ()| Ok(x.trans())); + methods.add_method("minor", |ctx, x, ()| Ok(x.minor())); + methods.add_method("invert", |ctx, x, ()| Ok(x.invert())); + + methods.add_method("membermul", |ctx, x, y| Ok(x.member_mul(y))); + } +} + +pub fn mat3(ctx: Context, _: ()) -> rlua::Result
{ + let module = ctx.create_table()?; + + module.set("new", ctx.create_function( + |ctx, (a, b, c, d, e, f, g, h, i)| Ok(Mat3::new(a, b, c, d, e, f, g, h, i)) + )?)?; + + module.set("I", I3)?; + module.set("O", O3)?; + + Ok(module) +} diff --git a/src/lua/material.rs b/src/lua/material.rs new file mode 100644 index 0000000..d5e9ec2 --- /dev/null +++ b/src/lua/material.rs @@ -0,0 +1,30 @@ +use crate::material::{SurfaceType, Material}; +use rlua::{UserData, Context, Table}; + +impl UserData for SurfaceType {} +impl UserData for Material {} + +pub fn surface_type(ctx: Context, _: ()) -> rlua::Result
{ + let module = ctx.create_table()?; + + module.set("DIFFUSE", SurfaceType::Diffuse)?; + module.set("REFLECTIVE", SurfaceType::Reflective)?; + module.set("STOP", SurfaceType::Stop)?; + + Ok(module) +} + +pub fn material(ctx: Context, _: ()) -> rlua::Result
{ + let module = ctx.create_table()?; + + module.set("new", ctx.create_function( + |ctx, (emission, reflection, surface)| Ok(Material::new(emission, reflection, surface)) + )?)?; + + module.set("newfromdiagonal", ctx.create_function( + |ctx, (emission, reflection, surface)| Ok(Material::new_from_diagonal(emission, reflection, surface)) + )?)?; + + Ok(module) +} + diff --git a/src/lua/mod.rs b/src/lua/mod.rs new file mode 100644 index 0000000..f1f98e0 --- /dev/null +++ b/src/lua/mod.rs @@ -0,0 +1,43 @@ +use rlua::{Context, Table, Lua, Function}; + +mod obj; +mod vec3; +mod mat3; +mod color; +mod material; +mod light; + +pub use obj::{LuaObject, obj}; +pub use vec3::vec3; +pub use mat3::mat3; +pub use color::{color_vec, color_mat}; +pub use material::{surface_type, material}; +pub use light::light; + +pub fn env(ctx: Context, _: ()) -> rlua::Result
{ + let module = ctx.create_table()?; + + 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) +} + +pub fn scene_from_file(file: String) -> rlua::Result { + Lua::new().context(|ctx| { + let env = env(ctx, ())?; + let meta = ctx.create_table()?; + meta.set("__index", ctx.globals())?; + env.set_metatable(Some(meta)); + + let loadfile: Function = ctx.globals().get("loadfile")?; + let chunk: Function = loadfile.call((file, "t", env))?; + ctx.unpack(chunk.call(())?) + }) +} \ No newline at end of file diff --git a/src/lua/obj.rs b/src/lua/obj.rs new file mode 100644 index 0000000..8ba7088 --- /dev/null +++ b/src/lua/obj.rs @@ -0,0 +1,84 @@ +use crate::object::*; +use crate::structs::{Vec3, Mat3}; +use std::sync::Arc; +use crate::material::Material; +use crate::light::Light; +use rlua::{UserData, Context, Table}; + +#[derive(Clone)] +pub struct LuaObject(Arc); + +impl LuaObject { + fn new(obj: T) -> rlua::Result { + Ok(LuaObject(Arc::new(Scene::new(obj)))) + } + fn get(self) -> Scene { + (*self.0).clone() + } +} + +impl Obj for LuaObject { + fn distance_to(&self, point: Vec3) -> f64 { self.0.distance_to(point) } + fn normal_at(&self, point: Vec3) -> Vec3 { self.0.normal_at(point) } + fn material_at(&self, point: Vec3) -> Material { self.0.material_at(point) } + fn get_lights(&self) -> Vec { self.0.get_lights() } + fn node_count(&self) -> u32 { 1 + self.0.node_count() } +} + +impl UserData for LuaObject {} + +pub fn obj(ctx: Context, _: ()) -> rlua::Result
{ + let module = ctx.create_table()?; + + module.set("cuboid", ctx.create_function( + |ctx, (pos, radius): (Vec3, Vec3)| LuaObject::new(Cuboid::new(pos, radius)) + )?)?; + + module.set("cylinder", ctx.create_function( + |ctx, (pos, radius): (Vec3, f64)| LuaObject::new(Cylinder::new(pos, radius)) + )?)?; + + module.set("exclusion", ctx.create_function( + |ctx, (a, b): (LuaObject, LuaObject)| LuaObject::new(Exclusion::new(a.get(), b.get())) + )?)?; + + module.set("intersection", ctx.create_function( + |ctx, (a, b): (LuaObject, LuaObject)| LuaObject::new(Intersection::new(a.get(), b.get())) + )?)?; + + module.set("plane", ctx.create_function( + |ctx, (normal, offset): (Vec3, f64)| LuaObject::new(Plane::new(normal, offset)) + )?)?; + + module.set("sphere", ctx.create_function( + |ctx, (pos, radius): (Vec3, f64)| LuaObject::new(Sphere::new(pos, radius)) + )?)?; + + module.set("torus", ctx.create_function( + |ctx, (center, radius, thickness): (Vec3, f64, f64)| LuaObject::new(Torus::new(center, radius, thickness)) + )?)?; + + module.set("affinetransform", ctx.create_function( + |ctx, (obj, trans, disp): (LuaObject, Mat3, Vec3)| LuaObject::new(AffineTransform::new(obj.get(), trans, disp)) + )?)?; + + module.set("union", ctx.create_function( + |ctx, (a, b): (LuaObject, LuaObject)| LuaObject::new(Union::new(a.get(), b.get())) + )?)?; + + module.set("waves", ctx.create_function( + |ctx, ()| LuaObject::new(Waves::new()) + )?)?; + + module.set("withlights", ctx.create_function( + |ctx, (obj, light): (LuaObject, Light)| LuaObject::new(WithLights::new_one(obj.get(), light)) + )?)?; + + module.set("withmaterial", ctx.create_function( + |ctx, (obj, material): (LuaObject, Material)| LuaObject::new(WithMaterial::new(obj.get(), material)) + )?)?; + + //TODO add the rest of the objects + + Ok(module) +} diff --git a/src/lua/vec3.rs b/src/lua/vec3.rs new file mode 100644 index 0000000..7e8f315 --- /dev/null +++ b/src/lua/vec3.rs @@ -0,0 +1,40 @@ +use crate::structs::{Vec3, X, Y, Z, O}; +use rlua::{UserData, UserDataMethods, MetaMethod, Context, Table}; + +impl UserData for Vec3 { + fn add_methods<'lua, T: UserDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_meta_method(MetaMethod::Add, |ctx, a: &Self, b: Self| Ok(*a+b)); + methods.add_meta_method(MetaMethod::Sub, |ctx, a: &Self, b: Self| Ok(*a-b)); + methods.add_meta_method(MetaMethod::Mul, |ctx, a: &Self, b: Self| Ok(*a*b)); + methods.add_meta_method(MetaMethod::Pow, |ctx, a: &Self, b: Self| Ok(*a^b)); + methods.add_meta_method(MetaMethod::Unm, |ctx, a: &Self, b: ()| Ok(-*a)); + methods.add_meta_method(MetaMethod::Mul, |ctx, a: &Self, b: f64| Ok(*a*b)); + methods.add_meta_method(MetaMethod::Div, |ctx, a: &Self, b: f64| Ok(*a/b)); + + methods.add_method("x", |ctx, x, ()| Ok(x.x())); + methods.add_method("y", |ctx, x, ()| Ok(x.y())); + methods.add_method("z", |ctx, x, ()| Ok(x.z())); + + methods.add_method("magnitude", |ctx, x, ()| Ok(x.magnitude())); + methods.add_method("unit", |ctx, x, ()| Ok(x.unit())); + + methods.add_method("distanceto", |ctx, x, y| Ok(x.distance_to(y))); + methods.add_method("angleto", |ctx, x, y| Ok(x.angle_to(y))); + methods.add_method("membermul", |ctx, x, y| Ok(x.member_mul(y))); + } +} + +pub fn vec3(ctx: Context, _: ()) -> rlua::Result
{ + let module = ctx.create_table()?; + + module.set("new", ctx.create_function( + |ctx, (x, y, z)| Ok(Vec3::new(x, y, z)) + )?)?; + + module.set("X", X)?; + module.set("Y", Y)?; + module.set("Z", Z)?; + module.set("O", O)?; + + Ok(module) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index bf4dba0..1d63a76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod light; mod object; mod structs; mod consts; +mod lua; use object::*; use crate::consts::*; @@ -10,6 +11,7 @@ use crate::structs::*; use crate::material::*; use image::{ColorType, ImageFormat}; use crate::light::Light; +use crate::lua::scene_from_file; fn default_cam() -> Cam { Cam::new_pointing(Y*3. - X*5., O, 0.5) @@ -84,7 +86,7 @@ fn default_scene3() -> Scene { fn main() { // get scene and camera - let scene = default_scene2(); + let scene = scene_from_file("scenes/randomspheres.lua".to_owned()).unwrap(); 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 d12c0df..8adedbe 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -1,5 +1,5 @@ use crate::consts::{EPSILON, MAX_DIST}; -use crate::structs::{Vec3, O, X, Y, Z}; +use crate::structs::{Vec3, X, Y, Z}; use crate::material::Material; use crate::material; use crate::light::Light; diff --git a/src/object/with_material.rs b/src/object/with_material.rs index 80af1b8..9323304 100644 --- a/src/object/with_material.rs +++ b/src/object/with_material.rs @@ -22,7 +22,7 @@ impl Obj for WithMaterial { fn normal_at(&self, point: Vec3) -> Vec3 { self.obj.normal_at(point) } - fn material_at(&self, point: Vec3) -> Material { + fn material_at(&self, _point: Vec3) -> Material { self.material } fn get_lights(&self) -> Vec { diff --git a/src/structs/cam.rs b/src/structs/cam.rs index 2f7ceaa..ff68df2 100644 --- a/src/structs/cam.rs +++ b/src/structs/cam.rs @@ -6,7 +6,6 @@ use crate::light::Light; use std::f64::consts::PI; use rand::prelude::*; use crossbeam_channel::unbounded; -use std::ops::{Mul, Add, Sub}; use std::io::Write; #[derive(Debug, Copy, Clone, PartialEq)] @@ -125,7 +124,7 @@ impl Cam { let mut stderr = std::io::stderr(); if REPORT_STATUS { stderr.write_all(format!("Rendering... 0/{} rows (0.00%)", IMG_HEIGHT).as_bytes()).unwrap(); - stderr.flush(); + stderr.flush().unwrap(); } let lights = scene.get_lights(); @@ -137,14 +136,14 @@ impl Cam { } if REPORT_STATUS { - stderr.write_all(format!("\x1b[1K\x1b[GRendering... {}/{} rows ({:.2}%)", y+1, IMG_HEIGHT, (y+1) as f64/IMG_HEIGHT as f64).as_bytes()); - stderr.flush(); + stderr.write_all(format!("\x1b[1K\x1b[GRendering... {}/{} rows ({:.2}%)", y+1, IMG_HEIGHT, (y+1) as f64/IMG_HEIGHT as f64).as_bytes()).unwrap(); + stderr.flush().unwrap(); } } if REPORT_STATUS { - stderr.write_all(format!("\x1b[1K\x1b[GRendering... Done\n").as_bytes()); - stderr.flush(); + stderr.write_all(format!("\x1b[1K\x1b[GRendering... Done\n").as_bytes()).unwrap(); + stderr.flush().unwrap(); } pixels @@ -216,7 +215,7 @@ impl Cam { let mut stderr = std::io::stderr(); if REPORT_STATUS { stderr.write_all(format!("Rendering... 0/{} slices (0.00%), 0.00% pixels", total_slices).as_bytes()).unwrap(); - stderr.flush(); + stderr.flush().unwrap(); } let mut rendered_slices = 0u32; let mut rendered_pixels = 0u64; @@ -241,12 +240,12 @@ impl Cam { let pct_slices = rendered_slices as f64 / total_slices as f64 * 100.; let pct_pixels = rendered_pixels as f64 / (IMG_WIDTH * IMG_HEIGHT) as f64 * 100.; stderr.write_all(format!("\x1b[1K\x1b[GRendering... {}/{} slices ({:.02}%), {:.02}% pixels", rendered_slices, total_slices, pct_slices, pct_pixels).as_bytes()).unwrap(); - stderr.flush(); + stderr.flush().unwrap(); } } if REPORT_STATUS { - stderr.write_all(format!("\x1b[1K\x1b[GRendering... Done\n").as_bytes()); - stderr.flush(); + stderr.write_all(format!("\x1b[1K\x1b[GRendering... Done\n").as_bytes()).unwrap(); + stderr.flush().unwrap(); } }).unwrap();