Skip to content

Commit

Permalink
feat: add PLY object format support
Browse files Browse the repository at this point in the history
  • Loading branch information
Walther committed Jul 28, 2024
1 parent 53a7084 commit a696fc6
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 0 deletions.
1 change: 1 addition & 0 deletions clovers-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ clovers = { path = "../clovers", features = [
"stl",
"traces",
"gl_tf",
"ply",
], default-features = false }

# External
Expand Down
1 change: 1 addition & 0 deletions clovers-cli/src/scenefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions clovers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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 = [
Expand Down
12 changes: 12 additions & 0 deletions clovers/src/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::*;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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);
Expand Down
166 changes: 166 additions & 0 deletions clovers/src/objects/ply.rs
Original file line number Diff line number Diff line change
@@ -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<Hitable<'scene>>,
/// 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::<ply::DefaultElement>::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<usize> {
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(),
}
}

0 comments on commit a696fc6

Please sign in to comment.