pbrt/src/shapes/mesh.rs
2026-01-18 16:29:27 +00:00

431 lines
13 KiB
Rust

use anyhow::{Context, Result as AnyResult, bail};
use ply_rs::parser::Parser;
use ply_rs::ply::{DefaultElement, Property};
use shared::utils::Transform;
use shared::utils::mesh::{BilinearPatchMesh, TriangleMesh};
use shared::utils::sampling::DevicePiecewiseConstant2D;
use std::fs::File;
use std::path::Path;
#[derive(Debug, Clone, Copy, Default)]
pub struct TriQuadMesh {
pub p: Vec<Point3f>,
pub n: Vec<Normal3f>,
pub uv: Vec<Point2f>,
pub face_indices: Vec<u32>,
pub tri_indices: Vec<u32>,
pub quad_indices: Vec<u32>,
}
fn get_float(elem: &DefaultElement, key: &str) -> AnyResult<f32> {
match elem.get(key) {
Some(Property::Float(v)) => Ok(*v),
Some(Property::Double(v)) => Ok(*v as f32),
Some(_) => bail!("Property {} is not a float", key),
None => bail!("Property {} not found", key),
}
}
fn get_int(elem: &DefaultElement, key: &str) -> AnyResult<i32> {
match elem.get(key) {
Some(Property::Int(v)) => Ok(*v),
Some(Property::UInt(v)) => Ok(*v as i32),
Some(Property::Short(v)) => Ok(*v as i32),
Some(Property::UShort(v)) => Ok(*v as i32),
Some(Property::Char(v)) => Ok(*v as i32),
Some(Property::UChar(v)) => Ok(*v as i32),
_ => bail!("Property {} not found or not integer", key),
}
}
fn get_float_any(elem: &DefaultElement, keys: &[&str]) -> Option<f32> {
for k in keys {
if let Ok(val) = get_float(elem, k) {
return Some(val);
}
}
None
}
fn get_list_uint(elem: &DefaultElement, key: &str) -> AnyResult<Vec<u32>> {
match elem.get(key) {
Some(Property::List_Int(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
Some(Property::List_UInt(vec)) => Ok(vec.clone()),
Some(Property::List_UChar(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
Some(Property::List_Char(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
_ => bail!("Property {} is not a list", key),
}
}
impl TriQuadMesh {
pub fn read_ply<P: AsRef<Path>>(filename: P) -> AnyResult<Self> {
let path = filename.as_ref();
let filename_display = path.display().to_string();
let mut f = File::open(&filename)
.with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?;
// Going to ply-rs
let p = Parser::<DefaultElement>::new();
let ply = p
.read_ply(&mut f)
.with_context(|| format!("Unable to read/parse PLY file \"{}\"", filename_display))?;
let mut mesh = TriQuadMesh::default();
if let Some(vertices) = ply.payload.get("vertex") {
let first = &vertices[0];
let has_uv = (first.contains_key("u") && first.contains_key("v"))
|| (first.contains_key("s") && first.contains_key("t"))
|| (first.contains_key("texture_u") && first.contains_key("texture_v"))
|| (first.contains_key("texture_s") && first.contains_key("texture_t"));
let has_normal =
first.contains_key("nx") && first.contains_key("ny") && first.contains_key("nz");
for v_elem in vertices {
// Read Position
let x = get_float(v_elem, "x")?;
let y = get_float(v_elem, "y")?;
let z = get_float(v_elem, "z")?;
mesh.p.push([x, y, z]);
// Read Normal
if has_normal {
let nx = get_float(v_elem, "nx").unwrap_or(0.0);
let ny = get_float(v_elem, "ny").unwrap_or(0.0);
let nz = get_float(v_elem, "nz").unwrap_or(0.0);
mesh.n.push([nx, ny, nz]);
}
// Read UVs (Optional, handling variable naming convention)
if has_uv {
let u =
get_float_any(v_elem, &["u", "s", "texture_u", "texture_s"]).unwrap_or(0.0);
let v =
get_float_any(v_elem, &["v", "t", "texture_v", "texture_t"]).unwrap_or(0.0);
mesh.uv.push([u, v]);
}
}
} else {
bail!(
"{}: PLY file is invalid! No vertex elements found!",
filename_display
);
}
if let Some(faces) = ply.payload.get("face") {
mesh.tri_indices.reserve(faces.len() * 3);
mesh.quad_indices.reserve(faces.len() * 4);
for f_elem in faces {
if let Ok(fi) = get_int(f_elem, "face_indices") {
mesh.faceIndices.push(fi);
}
if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") {
match indices.len() {
3 => {
mesh.tri_indices.extend_from_slice(&indices);
}
4 => {
mesh.quad_indices.extend_from_slice(&indices);
}
_ => {}
}
} else {
bail!("{}: vertex indices not found in PLY file", filename_display);
}
}
} else {
bail!(
"{}: PLY file is invalid! No face elements found!",
filename_display
);
}
let vertex_count = mesh.p.len() as u32;
for &idx in &mesh.tri_indices {
if idx >= vertex_count {
bail!(
"plymesh: Vertex index {} is out of bounds! Valid range is [0..{})",
idx,
vertex_count
);
}
}
for &idx in &mesh.quad_indices {
if idx >= vertex_count {
bail!(
"plymesh: Vertex index {} is out of bounds! Valid range is [0..{})",
idx,
vertex_count
);
}
}
Ok(mesh)
}
pub fn convert_to_only_triangles(&mut self) {
if self.quad_indices.is_empty() {
return;
}
for i in (0..self.quad_indices.len()).step_by(4) {
// First triangle: 0, 1, 3
self.tri_indices.push(self.quad_indices[i]);
self.tri_indices.push(self.quad_indices[i + 1]);
self.tri_indices.push(self.quad_indices[i + 3]);
// Second triangle: 0, 3, 2
self.tri_indices.push(self.quad_indices[i]);
self.tri_indices.push(self.quad_indices[i + 3]);
self.tri_indices.push(self.quad_indices[i + 2]);
}
self.quad_indices.clear();
}
pub fn compute_normals(&mut self) {
self.n.resize(self.p.len(), Normal3f::zero());
for i in (0..self.tri_indices.len()).step_by(3) {
let v = vec![
self.tri_indices[i],
self.tri_indices[i + 1],
self.tri_indices[i + 2],
];
let v10 = self.p[v[1]] - self.p[v[0]];
let v21 = self.p[v[2]] - self.p[v[1]];
let mut vn = v10.cross(v21);
if vn.norm_squared() > 0. {
vn = vn.normalize();
self.n[v[0]] += vn;
self.n[v[1]] += vn;
self.n[v[2]] += vn;
}
}
assert!(self.quad_indices == 0.);
for i in 0..self.n.len() {
if n[i].normalize() > 0. {
self.n[i] = self.n[i].normalize()
}
}
}
}
pub trait TriangleMeshFactory {
fn create(
arena: &mut Arena,
render_from_object: &Transform,
reverse_orientation: bool,
vertex_indices: Vec<u32>,
p: Vec<Point3f>,
n: Vec<Normal3f>,
s: Vec<Vector3f>,
uv: Vec<Point2f>,
face_indices: Vec<u32>,
) -> ArenaPtr<TriangleMesh>;
}
impl TriangleMeshFactory for TriangleMesh {
fn create(
arena: &mut Arena,
render_from_object: &Transform,
reverse_orientation: bool,
vertex_indices: Vec<u32>,
p: Vec<Point3f>,
n: Vec<Normal3f>,
s: Vec<Vector3f>,
uv: Vec<Point2f>,
face_indices: Vec<u32>,
) -> ArenaPtr<TriangleMesh> {
let n_triangles = indices.len() / 3;
let n_vertices = p.len();
for pt in p.iter_mut() {
*pt = render_from_object.apply_to_point(*pt);
}
let transform_swaps_handedness = render_from_object.swaps_handedness();
let uv = if !uv.is_empty() {
assert_eq!(n_vertices, uv.len());
Some(uv)
} else {
None
};
let n = if !n.is_empty() {
assert_eq!(n_vertices, n.len());
for nn in n.iter_mut() {
*nn = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
*nn = -*nn;
}
}
Some(n)
} else {
None
};
let s = if !s.is_empty() {
assert_eq!(n_vertices, s.len());
for ss in s.iter_mut() {
*ss = render_from_object.apply_to_vector(*ss);
}
Some(s)
} else {
None
};
let face_indices = if !face_indices.is_empty() {
assert_eq!(n_triangles, face_indices.len());
Some(face_indices)
} else {
None
};
let storage = Box::new(TriangleMeshStorage {
p,
vertex_indices,
n,
s,
uv,
face_indices,
});
assert!(p.len() <= i32::MAX as usize);
assert!(indices.len() <= i32::MAX as usize);
let p_ptr = storage.p.as_ptr();
let idx_ptr = storage.vertex_indices.as_ptr();
let n_ptr = if storage.n.is_empty() {
ptr::null()
} else {
storage.n.as_ptr()
};
let uv_ptr = if storage.uv.is_empty() {
ptr::null()
} else {
storage.uv.as_ptr()
};
let s_ptr = if storage.s.is_empty() {
ptr::null()
} else {
storage.s.as_ptr()
};
let mesh = TriangleMeshHost::new(
render_from_object,
reverse_orientation,
vertex_indices,
p,
s,
n,
uv,
face_indices,
);
}
}
#[derive(Debug, Clone, Copy)]
pub struct BilinearMeshStorage {
vertex_indices: Vec<u32>,
p: Vec<Point3f>,
n: Vec<Normal3f>,
uv: Vec<Point2f>,
image_distribution: Option<DevicePiecewiseConstant2D>,
}
#[derive(Debug, Clone, Copy)]
pub struct BilinearPatchMeshHost {
pub view: BilinearPatchMesh,
_storage: Box<BilinearMeshStorage>,
}
#[derive(Debug, Clone, Copy)]
pub struct TriangleMeshHost {
pub view: BilinearPatchMesh,
_storage: Box<BilinearMeshStorage>,
}
impl Deref for TriangleMeshHost {
type Target = TriangleMesh;
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl BilinearPatchMeshHost {
pub fn new(
render_from_object: Transform,
reverse_orientation: bool,
vertex_indices: Vec<usize>,
mut p: Vec<Point3f>,
mut n: Vec<Normal3f>,
uv: Vec<Point2f>,
image_distribution: Option<DevicePiecewiseConstant2D>,
) -> Self {
let n_patches = indices.len() / 3;
let n_vertices = p.len();
for pt in p.iter_mut() {
*pt = render_from_object.apply_to_point(*pt);
}
if !n.is_empty() {
assert_eq!(n_vertices, n.len(), "Normal count mismatch");
for nn in n.iter_mut() {
*nn = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
*nn = -*nn;
}
}
}
if !uv.is_empty() {
assert_eq!(n_vertices, uv.len(), "UV count mismatch");
}
let storage = Box::new(BilinearMeshStorage {
vertex_indices,
p,
n,
uv,
image_distribution,
});
let transform_swaps_handedness = render_from_object.swaps_handedness();
let p_ptr = storage.p.as_ptr();
let idx_ptr = storage.vertex_indices.as_ptr();
let n_ptr = if storage.n.is_empty() {
ptr::null()
} else {
storage.n.as_ptr()
};
let uv_ptr = if storage.uv.is_empty() {
ptr::null()
} else {
storage.uv.as_ptr()
};
let view = BilinearPatchMesh {
n_patches: n_patches as u32,
n_vertices: n_vertices as u32,
vertex_indices: idx_ptr,
p: p_ptr,
n: n_ptr,
uv: uv_ptr,
reverse_orientation,
transform_swaps_handedness,
image_distribution: dist_ptr,
};
Self { view, _storage }
}
}