From a696fc60b9821990acd0856fd7ecfc5276ed7e3d Mon Sep 17 00:00:00 2001 From: Walther Date: Mon, 29 Jul 2024 00:09:28 +0300 Subject: [PATCH] feat: add PLY object format support --- clovers-cli/Cargo.toml | 1 + clovers-cli/src/scenefile.rs | 1 + clovers/Cargo.toml | 2 + clovers/src/objects.rs | 12 +++ clovers/src/objects/ply.rs | 166 +++++++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+) create mode 100644 clovers/src/objects/ply.rs diff --git a/clovers-cli/Cargo.toml b/clovers-cli/Cargo.toml index 3eb35d12..2eadaea9 100644 --- a/clovers-cli/Cargo.toml +++ b/clovers-cli/Cargo.toml @@ -19,6 +19,7 @@ clovers = { path = "../clovers", features = [ "stl", "traces", "gl_tf", + "ply", ], default-features = false } # External diff --git a/clovers-cli/src/scenefile.rs b/clovers-cli/src/scenefile.rs index aaaf3d38..46a6dd9f 100644 --- a/clovers-cli/src/scenefile.rs +++ b/clovers-cli/src/scenefile.rs @@ -70,6 +70,7 @@ impl SceneFile { Object::RotateY(i) => i.priority, Object::Sphere(i) => i.priority, Object::STL(i) => i.priority, + Object::PLY(i) => i.priority, Object::GLTF(i) => i.priority, Object::Translate(i) => i.priority, Object::Triangle(i) => i.priority, diff --git a/clovers/Cargo.toml b/clovers/Cargo.toml index 5e88c681..b5230aa3 100644 --- a/clovers/Cargo.toml +++ b/clovers/Cargo.toml @@ -11,6 +11,7 @@ path = "src/lib.rs" crate-type = ["lib"] [features] +ply = ["ply-rs"] serde-derive = ["serde/derive", "nalgebra/serde-serialize"] stl = ["stl_io", "std"] gl_tf = ["gltf"] @@ -22,6 +23,7 @@ enum_dispatch = "0.3.13" gltf = { version = "1.4.1", optional = true } nalgebra = { version = "0.33.0" } palette = { version = "0.7.6", features = ["serializing"] } +ply-rs = { version = "0.1.3", optional = true } rand = { version = "0.8.5", features = ["small_rng"], default-features = false } rand_distr = { version = "0.4.3", features = ["std_math"] } serde = { version = "1.0.204", features = [ diff --git a/clovers/src/objects.rs b/clovers/src/objects.rs index c051c856..64ee218b 100644 --- a/clovers/src/objects.rs +++ b/clovers/src/objects.rs @@ -11,6 +11,8 @@ pub mod constant_medium; #[cfg(feature = "gl_tf")] pub mod gltf; pub mod moving_sphere; +#[cfg(feature = "ply")] +pub mod ply; pub mod quad; pub mod rotate; pub mod sphere; @@ -25,6 +27,8 @@ use alloc::vec::Vec; pub use boxy::*; // avoid keyword pub use constant_medium::*; pub use moving_sphere::*; +#[cfg(feature = "ply")] +pub use ply::*; pub use quad::*; pub use rotate::*; pub use sphere::*; @@ -70,6 +74,9 @@ pub enum Object { #[cfg(feature = "stl")] /// STL object initializer STL(STLInit), + #[cfg(feature = "ply")] + /// PLY object initializer + PLY(PLYInit), #[cfg(feature = "gl_tf")] /// GLTF object initializer GLTF(GLTFInit), @@ -127,6 +134,11 @@ pub fn object_to_hitable(obj: Object, materials: &[SharedMaterial]) -> Hitable<' let stl = initialize_stl(stl_init, materials); Hitable::HitableList(HitableList::new(stl.hitables)) } + #[cfg(feature = "ply")] + Object::PLY(ply_init) => { + let ply = initialize_ply(ply_init, materials); + Hitable::HitableList(HitableList::new(ply.hitables)) + } #[cfg(feature = "gl_tf")] Object::GLTF(x) => { let gltf = GLTF::new(x); diff --git a/clovers/src/objects/ply.rs b/clovers/src/objects/ply.rs new file mode 100644 index 00000000..124a7c14 --- /dev/null +++ b/clovers/src/objects/ply.rs @@ -0,0 +1,166 @@ +//! PLY utilities + +use alloc::string::String; +use nalgebra::Rotation3; +use ply_rs::{ + parser::Parser, + ply::{self, Property}, +}; + +use crate::{ + aabb::AABB, + bvh::build::utils::vec_bounding_box, + hitable::Hitable, + materials::{Material, MaterialInit, SharedMaterial}, + objects::Triangle, + Float, Position, Vec3, +}; + +/// Internal PLY object representation after initialization. Contains the material for all triangles in it to avoid having n copies. +#[derive(Debug, Clone)] +pub struct PLY<'scene> { + /// Primitives of the `PLY` object, a list of `Triangle`s. + pub hitables: Vec>, + /// Material for the object + pub material: &'scene Material, + /// Axis-aligned bounding box of the object + pub aabb: AABB, +} + +/// PLY structure. This gets converted into an internal representation using [Triangles](crate::objects::Triangle) +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))] +pub struct PLYInit { + /// Used for multiple importance sampling + #[cfg_attr(feature = "serde-derive", serde(default))] + pub priority: bool, + /// Path of the .ply file + pub path: String, + /// Material to use for the .ply object + #[cfg_attr(feature = "serde-derive", serde(default))] + pub material: MaterialInit, + /// Scaling factor for the object + pub scale: Float, + /// Location of the object in the rendered scene + pub center: Position, + /// Rotation of the object. Described as three angles, `roll`, `pitch`, `yaw`, applied in that order. + pub rotation: Vec3, +} + +#[must_use] +/// Initializes a PLY +pub fn initialize_ply<'scene>( + ply_init: PLYInit, + materials: &'scene [SharedMaterial], +) -> PLY<'scene> { + let material: &Material = match ply_init.material { + MaterialInit::Shared(name) => &materials.iter().find(|m| m.name == name).unwrap().material, + MaterialInit::Owned(m) => { + // TODO: do not leak memory + let material: &'scene Material = Box::leak(Box::new(m)); + material + } + }; + let mut hitables = Vec::new(); + + // TODO: error handling! + let mut f = std::fs::File::open(ply_init.path).unwrap(); + let parser = Parser::::new(); + let ply = parser.read_ply(&mut f); + let ply = ply.unwrap(); + + let mut vertices = Vec::new(); + for vertex in &ply.payload["vertex"] { + let x = property_to_float(&vertex["x"]); + let y = property_to_float(&vertex["y"]); + let z = property_to_float(&vertex["z"]); + vertices.push(Vec3::new(x, y, z)); + } + + for face in &ply.payload["face"] { + let indices = property_to_vec_u32(&face["vertex_indices"]); + + let a = vertices[indices[0]]; + let b = vertices[indices[1]]; + let c = vertices[indices[2]]; + + let a: Vec3 = Vec3::new(a[0], a[1], a[2]); + let b: Vec3 = Vec3::new(b[0], b[1], b[2]); + let c: Vec3 = Vec3::new(c[0], c[1], c[2]); + // Handle rotation + let rotation = Rotation3::from_euler_angles( + ply_init.rotation[0].to_radians(), + ply_init.rotation[1].to_radians(), + ply_init.rotation[2].to_radians(), + ); + let a: Vec3 = rotation * a; + let b: Vec3 = rotation * b; + let c: Vec3 = rotation * c; + // Handle scaling and offset + let a: Vec3 = a * ply_init.scale + ply_init.center; + let b: Vec3 = b * ply_init.scale + ply_init.center; + let c: Vec3 = c * ply_init.scale + ply_init.center; + + let triangle = Triangle::from_coordinates(a, b, c, material); + hitables.push(Hitable::Triangle(triangle)); + } + // TODO: remove unwrap + let aabb = vec_bounding_box(&hitables).unwrap(); + + PLY { + hitables, + material, + aabb, + } +} + +// TODO: better ergonomics? +#[allow(trivial_numeric_casts)] +#[allow(clippy::cast_precision_loss)] +#[allow(clippy::cast_possible_truncation)] +fn property_to_float(p: &Property) -> Float { + match *p { + Property::Int(i) => i as Float, + Property::UInt(u) => u as Float, + Property::Float(f) => f as Float, + Property::Double(f) => f as Float, + // Unsupported + Property::Char(_) + | Property::UChar(_) + | Property::Short(_) + | Property::UShort(_) + | Property::ListChar(_) + | Property::ListUChar(_) + | Property::ListShort(_) + | Property::ListUShort(_) + | Property::ListInt(_) + | Property::ListUInt(_) + | Property::ListFloat(_) + | Property::ListDouble(_) => unimplemented!("PLY: unsupported property format {p:?}"), + } +} + +// TODO: better ergonomics? +#[allow(trivial_numeric_casts)] +fn property_to_vec_u32(p: &Property) -> Vec { + match p { + Property::Char(_) + | Property::UChar(_) + | Property::Short(_) + | Property::UShort(_) + | Property::Int(_) + | Property::UInt(_) + | Property::Float(_) + | Property::Double(_) + | Property::ListChar(_) + | Property::ListFloat(_) + | Property::ListDouble(_) => unimplemented!("PLY: unsupported property format {p:?}"), + // + Property::ListUChar(vec_u) => vec_u.iter().map(|&u| u.into()).collect(), + Property::ListUShort(vec_u) => vec_u.iter().map(|&u| u.into()).collect(), + Property::ListUInt(vec_u) => vec_u.iter().map(|&u| u as usize).collect(), + // + Property::ListShort(vec_i) => vec_i.iter().map(|&i| i.unsigned_abs().into()).collect(), + Property::ListInt(vec_i) => vec_i.iter().map(|&i| i.unsigned_abs() as usize).collect(), + } +}