526 lines
16 KiB
Rust
526 lines
16 KiB
Rust
// use crate::Arena;
|
|
use crate::utils::sampling::PiecewiseConstant2D;
|
|
use anyhow::{Context, Result as AnyResult, bail};
|
|
use ply_rs::parser::Parser;
|
|
use ply_rs::ply::{DefaultElement, Property};
|
|
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike};
|
|
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh};
|
|
use shared::utils::{Ptr, Transform};
|
|
use std::fs::File;
|
|
use std::ops::Deref;
|
|
use std::path::Path;
|
|
|
|
/// Intermediate mesh from PLY
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct TriQuadMesh {
|
|
pub p: Vec<Point3f>,
|
|
pub n: Vec<Normal3f>,
|
|
pub uv: Vec<Point2f>,
|
|
pub face_indices: Vec<i32>,
|
|
pub tri_indices: Vec<i32>,
|
|
pub quad_indices: Vec<i32>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TriangleMeshStorage {
|
|
pub p: Vec<Point3f>,
|
|
pub n: Vec<Normal3f>,
|
|
pub s: Vec<Vector3f>,
|
|
pub uv: Vec<Point2f>,
|
|
pub vertex_indices: Vec<i32>,
|
|
pub face_indices: Vec<i32>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct BilinearMeshStorage {
|
|
pub vertex_indices: Vec<i32>,
|
|
pub p: Vec<Point3f>,
|
|
pub n: Vec<Normal3f>,
|
|
pub uv: Vec<Point2f>,
|
|
pub image_distribution: Option<PiecewiseConstant2D>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct TriangleMesh {
|
|
pub storage: Box<TriangleMeshStorage>,
|
|
pub device: DeviceTriangleMesh,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BilinearPatchMesh {
|
|
pub storage: Box<BilinearMeshStorage>,
|
|
pub device: DeviceBilinearPatchMesh,
|
|
}
|
|
|
|
impl Deref for TriangleMesh {
|
|
type Target = DeviceTriangleMesh;
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.device
|
|
}
|
|
}
|
|
|
|
impl Deref for BilinearPatchMesh {
|
|
type Target = DeviceBilinearPatchMesh;
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.device
|
|
}
|
|
}
|
|
|
|
impl TriangleMesh {
|
|
pub fn new(
|
|
render_from_object: &Transform,
|
|
reverse_orientation: bool,
|
|
vertex_indices: Vec<i32>,
|
|
mut p: Vec<Point3f>,
|
|
mut n: Vec<Normal3f>,
|
|
mut s: Vec<Vector3f>,
|
|
uv: Vec<Point2f>,
|
|
face_indices: Vec<i32>,
|
|
) -> Self {
|
|
let n_triangles = vertex_indices.len() / 3;
|
|
let n_vertices = p.len();
|
|
|
|
// Transform positions to render space
|
|
for pt in p.iter_mut() {
|
|
*pt = render_from_object.apply_to_point(*pt);
|
|
}
|
|
|
|
// Transform and optionally flip normals
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Transform tangents
|
|
if !s.is_empty() {
|
|
assert_eq!(n_vertices, s.len(), "Tangent count mismatch");
|
|
for ss in s.iter_mut() {
|
|
*ss = render_from_object.apply_to_vector(*ss);
|
|
}
|
|
}
|
|
|
|
// Validate UVs
|
|
if !uv.is_empty() {
|
|
assert_eq!(n_vertices, uv.len(), "UV count mismatch");
|
|
}
|
|
|
|
// Validate face indices
|
|
if !face_indices.is_empty() {
|
|
assert_eq!(n_triangles, face_indices.len(), "Face index count mismatch");
|
|
}
|
|
|
|
let transform_swaps_handedness = render_from_object.swaps_handedness();
|
|
|
|
let storage = Box::new(TriangleMeshStorage {
|
|
vertex_indices,
|
|
p,
|
|
n,
|
|
s,
|
|
uv,
|
|
face_indices,
|
|
});
|
|
|
|
// Build device struct with pointers into storage
|
|
let device = DeviceTriangleMesh {
|
|
n_triangles: n_triangles as u32,
|
|
n_vertices: n_vertices as u32,
|
|
vertex_indices: storage.vertex_indices.as_ptr().into(),
|
|
p: storage.p.as_ptr().into(),
|
|
n: if storage.n.is_empty() {
|
|
Ptr::null()
|
|
} else {
|
|
storage.n.as_ptr().into()
|
|
},
|
|
s: if storage.s.is_empty() {
|
|
Ptr::null()
|
|
} else {
|
|
storage.s.as_ptr().into()
|
|
},
|
|
uv: if storage.uv.is_empty() {
|
|
Ptr::null()
|
|
} else {
|
|
storage.uv.as_ptr().into()
|
|
},
|
|
face_indices: if storage.face_indices.is_empty() {
|
|
Ptr::null()
|
|
} else {
|
|
storage.face_indices.as_ptr().into()
|
|
},
|
|
reverse_orientation,
|
|
transform_swaps_handedness,
|
|
};
|
|
|
|
Self { storage, device }
|
|
}
|
|
|
|
pub fn positions(&self) -> &[Point3f] {
|
|
&self.storage.p
|
|
}
|
|
|
|
pub fn indices(&self) -> &[i32] {
|
|
&self.storage.vertex_indices
|
|
}
|
|
|
|
pub fn normals(&self) -> &[Normal3f] {
|
|
&self.storage.n
|
|
}
|
|
|
|
pub fn uvs(&self) -> &[Point2f] {
|
|
&self.storage.uv
|
|
}
|
|
}
|
|
|
|
impl BilinearPatchMesh {
|
|
pub fn new(
|
|
render_from_object: &Transform,
|
|
reverse_orientation: bool,
|
|
vertex_indices: Vec<ui2>,
|
|
mut p: Vec<Point3f>,
|
|
mut n: Vec<Normal3f>,
|
|
uv: Vec<Point2f>,
|
|
image_distribution: Option<PiecewiseConstant2D>,
|
|
) -> Self {
|
|
let n_patches = vertex_indices.len() / 4;
|
|
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 transform_swaps_handedness = render_from_object.swaps_handedness();
|
|
|
|
let storage = Box::new(BilinearMeshStorage {
|
|
vertex_indices,
|
|
p,
|
|
n,
|
|
uv,
|
|
image_distribution,
|
|
});
|
|
|
|
let device = DeviceBilinearPatchMesh {
|
|
n_patches: n_patches as u32,
|
|
n_vertices: n_vertices as u32,
|
|
vertex_indices: storage.vertex_indices.as_ptr().into(),
|
|
p: storage.p.as_ptr().into(),
|
|
n: if storage.n.is_empty() {
|
|
Ptr::null()
|
|
} else {
|
|
storage.n.as_ptr().into()
|
|
},
|
|
uv: if storage.uv.is_empty() {
|
|
Ptr::null()
|
|
} else {
|
|
storage.uv.as_ptr().into()
|
|
},
|
|
reverse_orientation,
|
|
transform_swaps_handedness,
|
|
image_distribution: storage
|
|
.image_distribution
|
|
.as_ref()
|
|
.map(|d| Ptr::from(&d.device))
|
|
.unwrap_or(Ptr::null()),
|
|
};
|
|
|
|
Self { storage, device }
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PLY Helper Functions
|
|
// ============================================================================
|
|
|
|
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::ListInt(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
|
|
Some(Property::ListUInt(vec)) => Ok(vec.clone()),
|
|
Some(Property::ListUChar(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
|
|
Some(Property::ListChar(vec)) => Ok(vec.iter().map(|&x| x as u32).collect()),
|
|
_ => bail!("Property {} is not a list", key),
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// TriQuadMesh Implementation
|
|
// ============================================================================
|
|
|
|
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(path)
|
|
.with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?;
|
|
|
|
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();
|
|
|
|
// Parse vertices
|
|
if let Some(vertices) = ply.payload.get("vertex") {
|
|
if vertices.is_empty() {
|
|
bail!("{}: PLY file has no vertices", filename_display);
|
|
}
|
|
|
|
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");
|
|
|
|
mesh.p.reserve(vertices.len());
|
|
if has_normal {
|
|
mesh.n.reserve(vertices.len());
|
|
}
|
|
if has_uv {
|
|
mesh.uv.reserve(vertices.len());
|
|
}
|
|
|
|
for v_elem in vertices {
|
|
let x = get_float(v_elem, "x")?;
|
|
let y = get_float(v_elem, "y")?;
|
|
let z = get_float(v_elem, "z")?;
|
|
mesh.p.push(Point3f::new(x, y, z));
|
|
|
|
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(Normal3f::new(nx, ny, nz));
|
|
}
|
|
|
|
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(Point2f::new(u, v));
|
|
}
|
|
}
|
|
} else {
|
|
bail!("{}: PLY file has no vertex elements", filename_display);
|
|
}
|
|
|
|
// Parse faces
|
|
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.face_indices.push(fi);
|
|
}
|
|
|
|
if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") {
|
|
match indices.len() {
|
|
3 => mesh.tri_indices.extend(indices.iter().map(|&i| i as i32)),
|
|
4 => mesh.quad_indices.extend(indices.iter().map(|&i| i as i32)),
|
|
n => {
|
|
log::warn!(
|
|
"{}: Skipping face with {} vertices (only 3 or 4 supported)",
|
|
filename_display,
|
|
n
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
bail!("{}: vertex_indices not found in face", filename_display);
|
|
}
|
|
}
|
|
} else {
|
|
bail!("{}: PLY file has no face elements", filename_display);
|
|
}
|
|
|
|
// Validate indices
|
|
let vertex_count = mesh.p.len() as i32;
|
|
for &idx in &mesh.tri_indices {
|
|
if idx >= vertex_count {
|
|
bail!(
|
|
"{}: Vertex index {} out of bounds [0..{})",
|
|
filename_display,
|
|
idx,
|
|
vertex_count
|
|
);
|
|
}
|
|
}
|
|
for &idx in &mesh.quad_indices {
|
|
if idx >= vertex_count {
|
|
bail!(
|
|
"{}: Vertex index {} out of bounds [0..{})",
|
|
filename_display,
|
|
idx,
|
|
vertex_count
|
|
);
|
|
}
|
|
}
|
|
|
|
Ok(mesh)
|
|
}
|
|
|
|
pub fn convert_to_only_triangles(&mut self) {
|
|
if self.quad_indices.is_empty() {
|
|
return;
|
|
}
|
|
|
|
// Each quad becomes 2 triangles
|
|
self.tri_indices.reserve(self.quad_indices.len() / 4 * 6);
|
|
|
|
for quad in self.quad_indices.chunks_exact(4) {
|
|
let (v0, v1, v2, v3) = (quad[0], quad[1], quad[2], quad[3]);
|
|
|
|
// Triangle 1: v0, v1, v2
|
|
self.tri_indices.push(v0);
|
|
self.tri_indices.push(v1);
|
|
self.tri_indices.push(v2);
|
|
|
|
// Triangle 2: v0, v2, v3
|
|
self.tri_indices.push(v0);
|
|
self.tri_indices.push(v2);
|
|
self.tri_indices.push(v3);
|
|
}
|
|
|
|
self.quad_indices.clear();
|
|
}
|
|
|
|
pub fn compute_normals(&mut self) {
|
|
// Initialize normals to zero
|
|
self.n = vec![Normal3f::new(0.0, 0.0, 0.0); self.p.len()];
|
|
|
|
// Accumulate face normals for triangles
|
|
for tri in self.tri_indices.chunks_exact(3) {
|
|
let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
|
|
|
|
let p0 = self.p[i0];
|
|
let p1 = self.p[i1];
|
|
let p2 = self.p[i2];
|
|
|
|
let v10 = p1 - p0;
|
|
let v20 = p2 - p0;
|
|
let face_normal = v10.cross(v20);
|
|
|
|
// Accumulate (will normalize later)
|
|
self.n[i0] = self.n[i0] + Normal3f::from(face_normal);
|
|
self.n[i1] = self.n[i1] + Normal3f::from(face_normal);
|
|
self.n[i2] = self.n[i2] + Normal3f::from(face_normal);
|
|
}
|
|
|
|
// Accumulate face normals for quads
|
|
for quad in self.quad_indices.chunks_exact(4) {
|
|
let indices: Vec<usize> = quad.iter().map(|&i| i as usize).collect();
|
|
|
|
let p0 = self.p[indices[0]];
|
|
let p1 = self.p[indices[1]];
|
|
let p2 = self.p[indices[2]];
|
|
|
|
let v10 = p1 - p0;
|
|
let v20 = p2 - p0;
|
|
let face_normal = v10.cross(v20);
|
|
|
|
for &idx in &indices {
|
|
self.n[idx] = self.n[idx] + Normal3f::from(face_normal);
|
|
}
|
|
}
|
|
|
|
// Normalize all normals
|
|
for normal in &mut self.n {
|
|
let len_sq = normal.norm_squared();
|
|
if len_sq > 0.0 {
|
|
*normal = *normal / len_sq.sqrt();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert to a TriangleMesh, consuming self
|
|
pub fn into_triangle_mesh(
|
|
mut self,
|
|
render_from_object: &Transform,
|
|
reverse_orientation: bool,
|
|
) -> TriangleMesh {
|
|
self.convert_to_only_triangles();
|
|
|
|
if self.n.is_empty() {
|
|
self.compute_normals();
|
|
}
|
|
|
|
TriangleMesh::new(
|
|
render_from_object,
|
|
reverse_orientation,
|
|
self.tri_indices,
|
|
self.p,
|
|
self.n,
|
|
Vec::new(), // s (tangents)
|
|
self.uv,
|
|
self.face_indices,
|
|
)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Convenience: Create from PLY directly
|
|
// ============================================================================
|
|
|
|
impl TriangleMesh {
|
|
pub fn from_ply<P: AsRef<Path>>(
|
|
filename: P,
|
|
render_from_object: &Transform,
|
|
reverse_orientation: bool,
|
|
) -> AnyResult<Self> {
|
|
let mesh = TriQuadMesh::read_ply(filename)?;
|
|
Ok(mesh.into_triangle_mesh(render_from_object, reverse_orientation))
|
|
}
|
|
}
|