Skip to content

Commit

Permalink
Merge pull request #214 from Walther/ply
Browse files Browse the repository at this point in the history
Feature: PLY model file support
  • Loading branch information
Walther committed Aug 3, 2024
2 parents 7a44a3a + eb6dc76 commit b19fad1
Show file tree
Hide file tree
Showing 21 changed files with 535 additions and 223 deletions.
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

0 comments on commit b19fad1

Please sign in to comment.