Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: PLY model file support #214

Merged
merged 2 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ply/**/*.ply binary linguist-generated

10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ If you make a PR to this repository, please acknowledge that you are giving all

### Model files

This repository has some example model files for demonstrating triangle-based object imports in addition to the declarative object primitives.
This repository has some example model files for demonstrating triangle-based object imports in addition to the declarative object primitives. Check the following directories:

- Utah Teapot model `teapot.stl` CC0 1.0 Universal Public Domain [Wikipedia](<https://en.wikipedia.org/wiki/File:Utah_teapot_(solid).stl>)
- Stanford Bunny model `bunny.stl` CC Attribution 3.0 Unported [Wikipedia](https://commons.wikimedia.org/wiki/File:Stanford_Bunny.stl)
- Stanford Dragon model `dragon.stl` (stl converted version) CC Attribution [Thingiverse](https://www.thingiverse.com/thing:27666)
- Rubber Duck model `duck.stl` CC0 1.0 Universal Public Domain [Thingiverse](https://www.thingiverse.com/thing:139894)
- Triangular Prism model `prism.stl` Public Domain [Wikipedia](https://commons.wikimedia.org/wiki/File:Triangular_prism.stl)
- `stl/`
- `ply/`
- `gltf/`

## Useful references

Expand Down
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(),
}
}
2 changes: 1 addition & 1 deletion gltf/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# gltf
# glTF models

Models borrowed from [glTF Sample Models](https://github.com/KhronosGroup/glTF-Sample-Models)
24 changes: 24 additions & 0 deletions ply/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# PLY models

These famous objects are from the [Large Geometric Models Archive at Georgia Tech](https://sites.cc.gatech.edu/projects/large_models/index.html).

| Model | Faces | Vertices |
| --------------- | --------- | -------- |
| Stanford Bunny | 69,451 | 35,947 |
| Stanford Dragon | 871,414 | 437,645 |
| Happy Buddha | 1,087,716 | 543,652 |

The files were converted from the ASCII ply format to the binary ply format using the `ply2binary` program, built from the sources available [here](https://sites.cc.gatech.edu/projects/large_models/ply.html). To quote from the README of that repository:

> These geometry filters have been developed on a Silicon Graphics
> workstation using the native C compiler. The code may very well run
> unmodified on other platforms but this has not yet been verified.

Indeed, the code assumes a different endianness from the running operating system. As a workaround, after running `./ply2binary < ~/example.ascii.ply > ~/example.binary.ply`, you need to edit the header in the binary file:

```diff
- format binary_big_endian 1.0
+ format binary_little_endian 1.0
```

After this change, the binary file can be read correctly by compliant parsers.
Binary file added ply/bunny.binary.ply
Binary file not shown.
Binary file added ply/dragon.binary.ply
Binary file not shown.
Binary file added ply/happy.binary.ply
Binary file not shown.
84 changes: 84 additions & 0 deletions scenes/bunny.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"time_0": 0,
"time_1": 1,
"background_color": [0.025, 0.025, 0.025],
"camera": {
"look_from": [0, 200, -800],
"look_at": [0, 200, 0],
"up": [0, 1, 0],
"vertical_fov": 40,
"aperture": 30,
"focus_distance": 650
},
"objects": [
{
"kind": "PLY",
"comment": "bunny",
"path": "ply/bunny.binary.ply",
"scale": 2500,
"center": [-45, -85, 0],
"rotation": [0, 200, 0],
"material": "white lambertian"
},
{
"kind": "Quad",
"comment": "floor",
"q": [-2000, 0.01, -500],
"u": [4000, 0, 0],
"v": [0, 0, 1000],
"material": "checkerboard"
},
{
"kind": "Quad",
"comment": "back wall",
"q": [-2000, 0, 500],
"u": [4000, 0, 0],
"v": [0, 1000, 0],
"material": "checkerboard"
},
{
"kind": "Sphere",
"center": [0, 800, -300],
"radius": 300,
"material": "lamp",
"comment": "big ceiling light",
"priority": true
}
],
"materials": [
{
"name": "dark lambertian",
"kind": "Lambertian",
"albedo": {
"kind": "SolidColor",
"color": [0.3, 0.3, 0.3]
}
},
{
"name": "white lambertian",
"kind": "Lambertian",
"albedo": {
"kind": "SolidColor",
"color": [0.8, 0.8, 0.8]
}
},
{
"name": "checkerboard",
"kind": "Lambertian",
"albedo": {
"kind": "SpatialChecker",
"even": [0.8, 0.8, 0.8],
"odd": [0.3, 0.3, 0.3],
"density": 0.01
}
},
{
"name": "lamp",
"kind": "DiffuseLight",
"emit": {
"kind": "SolidColor",
"color": [5, 5, 5]
}
}
]
}
Loading
Loading