From bbfa160da9e15e80e62440a920503537ea8569ad Mon Sep 17 00:00:00 2001 From: Alex Kocharin Date: Wed, 30 Aug 2023 18:37:47 +0400 Subject: [PATCH] high-level API for ConvexMesh, TriangleMesh, HeightField (#195) * high-level API for ConvexMesh, TriangleMesh, HeightField --------- Co-authored-by: Tom Solberg --- physx/src/convex_mesh.rs | 139 +++++++++++++++++++- physx/src/height_field.rs | 255 ++++++++++++++++++++++++++++++++++++- physx/src/triangle_mesh.rs | 155 +++++++++++++++++++++- 3 files changed, 543 insertions(+), 6 deletions(-) diff --git a/physx/src/convex_mesh.rs b/physx/src/convex_mesh.rs index a0fcd5ec..12e12513 100644 --- a/physx/src/convex_mesh.rs +++ b/physx/src/convex_mesh.rs @@ -1,6 +1,27 @@ -use crate::{owner::Owner, traits::Class}; +use std::mem::MaybeUninit; -use physx_sys::PxConvexMesh_release_mut; +use crate::{ + math::{PxBounds3, PxVec3}, + owner::Owner, + traits::Class, +}; + +use physx_sys::{ + PxConvexMesh_getIndexBuffer, + PxConvexMesh_getLocalBounds, + PxConvexMesh_getMassInformation, + PxConvexMesh_getNbPolygons, + PxConvexMesh_getNbVertices, + PxConvexMesh_getPolygonData, + PxConvexMesh_getVertices, + // TODO: SDF getters + //PxConvexMesh_getSDF, + //PxConvexMesh_getConcreteTypeName, + PxConvexMesh_isGpuCompatible, + PxConvexMesh_release_mut, + // TODO: high level wrapper for PxMassProperties + PxMassProperties, +}; #[repr(transparent)] pub struct ConvexMesh { @@ -18,6 +39,90 @@ impl ConvexMesh { pub unsafe fn from_raw(ptr: *mut physx_sys::PxConvexMesh) -> Option> { unsafe { Owner::from_raw(ptr as *mut Self) } } + + /// Returns the number of vertices. + pub fn get_nb_vertices(&self) -> u32 { + unsafe { PxConvexMesh_getNbVertices(self.as_ptr()) } + } + + /// Returns the vertices. + pub fn get_vertices(&self) -> &[PxVec3] { + unsafe { + std::slice::from_raw_parts( + PxConvexMesh_getVertices(self.as_ptr()) as *const PxVec3, + self.get_nb_vertices() as usize, + ) + } + } + + /// Returns the index buffer. + pub fn get_index_buffer(&self) -> &[u8] { + let polygon_count = self.get_nb_polygons(); + + // for each polygon index buffer contains its points, + // so we take last polygon's index offset plus its length to calculate total size + let index_buffer_length = if polygon_count > 0 { + let last_polygon = self.get_polygon_data(polygon_count - 1).unwrap(); + last_polygon.index_base as usize + last_polygon.nb_verts as usize + } else { + 0 + }; + + unsafe { + std::slice::from_raw_parts( + PxConvexMesh_getIndexBuffer(self.as_ptr()), + index_buffer_length, + ) + } + } + + /// Returns the number of polygons. + pub fn get_nb_polygons(&self) -> u32 { + unsafe { PxConvexMesh_getNbPolygons(self.as_ptr()) } + } + + /// Returns the polygon data. + pub fn get_polygon_data(&self, index: u32) -> Option { + let mut polygon = MaybeUninit::uninit(); + + if unsafe { PxConvexMesh_getPolygonData(self.as_ptr(), index, polygon.as_mut_ptr()) } { + Some(unsafe { polygon.assume_init() }.into()) + } else { + None + } + } + + /// Returns the mass properties of the mesh assuming unit density. + pub fn get_mass_information(&self) -> PxMassProperties { + let mut mass = MaybeUninit::uninit(); + let mut local_inertia = MaybeUninit::uninit(); + let mut local_center_of_mass = MaybeUninit::uninit(); + + unsafe { + PxConvexMesh_getMassInformation( + self.as_ptr(), + mass.as_mut_ptr(), + local_inertia.as_mut_ptr(), + local_center_of_mass.as_mut_ptr(), + ); + + PxMassProperties { + inertiaTensor: local_inertia.assume_init(), + centerOfMass: local_center_of_mass.assume_init(), + mass: mass.assume_init(), + } + } + } + + /// Returns the local-space (vertex space) AABB from the convex mesh. + pub fn get_local_bounds(&self) -> PxBounds3 { + unsafe { PxConvexMesh_getLocalBounds(self.as_ptr()) }.into() + } + + /// This method decides whether a convex mesh is gpu compatible. + pub fn is_gpu_compatible(&self) -> bool { + unsafe { PxConvexMesh_isGpuCompatible(self.as_ptr()) } + } } unsafe impl Send for ConvexMesh {} @@ -28,3 +133,33 @@ impl Drop for ConvexMesh { unsafe { PxConvexMesh_release_mut(self.as_mut_ptr()) } } } + +#[derive(Debug, Clone)] +pub struct HullPolygon { + /// Plane equation for this polygon. + pub plane: [f32; 4], + /// Number of vertices/edges in the polygon. + pub nb_verts: u16, + /// Offset in index buffer. + pub index_base: u16, +} + +impl From for HullPolygon { + fn from(value: physx_sys::PxHullPolygon) -> Self { + Self { + plane: value.mPlane, + nb_verts: value.mNbVerts, + index_base: value.mIndexBase, + } + } +} + +impl From for physx_sys::PxHullPolygon { + fn from(value: HullPolygon) -> Self { + Self { + mPlane: value.plane, + mNbVerts: value.nb_verts, + mIndexBase: value.index_base, + } + } +} diff --git a/physx/src/height_field.rs b/physx/src/height_field.rs index 3084753f..c87f2805 100644 --- a/physx/src/height_field.rs +++ b/physx/src/height_field.rs @@ -2,8 +2,36 @@ // Copyright © 2019, Embark Studios, all rights reserved. // Created: 11 April 2019 -use crate::{owner::Owner, traits::Class}; -use physx_sys::PxHeightField_release_mut; +use std::ffi::c_void; + +use crate::{cooking::PxHeightFieldDesc, math::PxVec3, owner::Owner, traits::Class}; + +use physx_sys::{ + PxBitAndByte, + PxHeightField_getConvexEdgeThreshold, + PxHeightField_getFlags, + PxHeightField_getFormat, + PxHeightField_getHeight, + PxHeightField_getNbColumns, + PxHeightField_getNbRows, + PxHeightField_getSample, + PxHeightField_getSampleStride, + PxHeightField_getTimestamp, + //PxHeightField_getConcreteTypeName, + PxHeightField_getTriangleMaterialIndex, + PxHeightField_getTriangleNormal, + PxHeightField_modifySamples_mut, + PxHeightField_release_mut, + PxHeightField_saveCells, +}; + +pub use physx_sys::{ + //PxHeightFieldTessFlag as HeightFieldTessFlag, + PxHeightFieldFlag as HeightFieldFlag, + PxHeightFieldFlags as HeightFieldFlags, + //PxHeightFieldMaterial as HeightFieldMaterial, + PxHeightFieldFormat as HeightFieldFormat, +}; pub const HEIGHT_SCALE: f32 = 1.0; pub const XZ_SCALE: f32 = 100.0; @@ -24,10 +52,233 @@ impl HeightField { pub(crate) unsafe fn from_raw(ptr: *mut physx_sys::PxHeightField) -> Option> { unsafe { Owner::from_raw(ptr as *mut Self) } } + + /// Writes out the sample data array. + pub fn save_cells(&self) -> Vec { + let buffer_size = self.get_nb_columns() * self.get_nb_rows(); + let byte_size = buffer_size * std::mem::size_of::() as u32; + let mut samples = Vec::with_capacity(buffer_size as usize); + + unsafe { + // SAFETY: HeightFieldSample is repr(transparent) of PxHeightFieldSample + assert_eq!( + PxHeightField_saveCells( + self.as_ptr(), + samples.as_mut_ptr() as *mut c_void, + byte_size + ), + byte_size + ); + + // SAFETY: call above should populate all the values, as verified by the assertion + samples.set_len(buffer_size as usize) + } + + samples + } + + /// Replaces a rectangular subfield in the sample data array. + pub fn modify_samples( + &mut self, + start_col: i32, + start_row: i32, + subfield_desc: &PxHeightFieldDesc, + shrink_bounds: bool, + ) -> bool { + unsafe { + PxHeightField_modifySamples_mut( + self.as_mut_ptr(), + start_col, + start_row, + subfield_desc.as_ptr(), + shrink_bounds, + ) + } + } + + /// Retrieves the number of sample rows in the samples array. + pub fn get_nb_rows(&self) -> u32 { + unsafe { PxHeightField_getNbRows(self.as_ptr()) } + } + + /// Retrieves the number of sample columns in the samples array. + pub fn get_nb_columns(&self) -> u32 { + unsafe { PxHeightField_getNbColumns(self.as_ptr()) } + } + + /// Retrieves the format of the sample data. + pub fn get_format(&self) -> HeightFieldFormat { + unsafe { PxHeightField_getFormat(self.as_ptr()) } + } + + /// Retrieves the offset in bytes between consecutive samples in the array. + pub fn get_sample_stride(&self) -> u32 { + unsafe { PxHeightField_getSampleStride(self.as_ptr()) } + } + + /// Retrieves the convex edge threshold. + pub fn get_convex_edge_threshold(&self) -> f32 { + unsafe { PxHeightField_getConvexEdgeThreshold(self.as_ptr()) } + } + + /// Retrieves the flags bits, combined from values of the enum HeightFieldFlag. + pub fn get_flags(&self) -> HeightFieldFlags { + unsafe { PxHeightField_getFlags(self.as_ptr()) } + } + + /// Retrieves the height at the given coordinates in grid space. + pub fn get_height(&self, x: f32, z: f32) -> f32 { + unsafe { PxHeightField_getHeight(self.as_ptr(), x, z) } + } + + /// Returns material table index of given triangle. + pub fn get_triangle_material_index(&self, triangle_index: u32) -> u16 { + unsafe { PxHeightField_getTriangleMaterialIndex(self.as_ptr(), triangle_index) } + } + + /// Returns a triangle face normal for a given triangle index. + pub fn get_triangle_normal(&self, triangle_index: u32) -> PxVec3 { + unsafe { PxHeightField_getTriangleNormal(self.as_ptr(), triangle_index) }.into() + } + + /// Returns heightfield sample of given row and column. + pub fn get_sample(&self, row: u32, column: u32) -> Option<&HeightFieldSample> { + // need to do bound checks, otherwise C++ code will crash with assertion error + if row < self.get_nb_rows() || column < self.get_nb_columns() { + Some(unsafe { + &*(PxHeightField_getSample(self.as_ptr(), row, column) as *const HeightFieldSample) + }) + } else { + None + } + } + + /// Returns the number of times the heightfield data has been modified. + pub fn get_timestamp(&self) -> u32 { + unsafe { PxHeightField_getTimestamp(self.as_ptr()) } + } } +unsafe impl Send for HeightField {} +unsafe impl Sync for HeightField {} + impl Drop for HeightField { fn drop(&mut self) { unsafe { PxHeightField_release_mut(self.as_mut_ptr()) } } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct HeightFieldMaterial(i8); + +impl HeightFieldMaterial { + pub const HOLE: Self = Self(physx_sys::PxHeightFieldMaterial::Hole as _); + + pub const fn new(index: i8) -> Self { + assert!( + index >= 0, + "HeightFieldMaterial index must be in 0x00..0x7F range" + ); + Self(index) + } + + pub const fn get(&self) -> i8 { + self.0 + } +} + +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct HeightFieldSample { + obj: physx_sys::PxHeightFieldSample, +} + +crate::DeriveClassForNewType!(HeightFieldSample: PxHeightFieldSample); + +impl From for HeightFieldSample { + fn from(value: physx_sys::PxHeightFieldSample) -> Self { + Self { obj: value } + } +} + +impl From for physx_sys::PxHeightFieldSample { + fn from(value: HeightFieldSample) -> Self { + value.obj + } +} + +impl HeightFieldSample { + // In PhysX, material can only be in 0x00..0x7F range, high bit has special meaning. + // In material0 high bit is tesselation flag, in material1 it is reserved. + // We can use PxHeightFieldSample_tessFlag methods, but it's easier to just do bit arithmetic in Rust. + const BIT_MASK: u8 = 0x80; + + pub fn new( + height: i16, + material0: HeightFieldMaterial, + material1: HeightFieldMaterial, + tess_flag: bool, + ) -> Self { + let mut result: Self = physx_sys::PxHeightFieldSample { + height, + materialIndex0: PxBitAndByte { + structgen_pad0: [material0.0 as u8], + }, + materialIndex1: PxBitAndByte { + structgen_pad0: [material1.0 as u8], + }, + } + .into(); + + if tess_flag { + result.set_tess_flag(); + } + + result + } + + pub fn height(&self) -> i16 { + self.obj.height + } + + pub fn set_height(&mut self, height: i16) { + self.obj.height = height; + } + + pub fn tess_flag(&self) -> bool { + // same as PxHeightFieldSample_tessFlag + self.obj.materialIndex0.structgen_pad0[0] & Self::BIT_MASK != 0 + } + + pub fn set_tess_flag(&mut self) { + // same as PxHeightFieldSample_setTessFlag_mut + self.obj.materialIndex0.structgen_pad0[0] |= Self::BIT_MASK; + } + + pub fn clear_tess_flag(&mut self) { + // same as PxHeightFieldSample_clearTessFlag_mut + self.obj.materialIndex0.structgen_pad0[0] &= !Self::BIT_MASK; + } + + pub fn material0(&self) -> HeightFieldMaterial { + HeightFieldMaterial::new( + (self.obj.materialIndex0.structgen_pad0[0] & !Self::BIT_MASK) as i8, + ) + } + + pub fn material1(&self) -> HeightFieldMaterial { + HeightFieldMaterial::new( + (self.obj.materialIndex1.structgen_pad0[0] & !Self::BIT_MASK) as i8, + ) + } + + pub fn set_material0(&mut self, material: HeightFieldMaterial) { + self.obj.materialIndex0.structgen_pad0[0] = + material.get() as u8 | self.obj.materialIndex0.structgen_pad0[0] & Self::BIT_MASK; + } + + pub fn set_material1(&mut self, material: HeightFieldMaterial) { + self.obj.materialIndex1.structgen_pad0[0] = + material.get() as u8 | self.obj.materialIndex1.structgen_pad0[0] & Self::BIT_MASK; + } +} diff --git a/physx/src/triangle_mesh.rs b/physx/src/triangle_mesh.rs index 822586b4..bae816d1 100644 --- a/physx/src/triangle_mesh.rs +++ b/physx/src/triangle_mesh.rs @@ -1,6 +1,36 @@ -use crate::{owner::Owner, traits::Class}; +use std::mem::MaybeUninit; -use physx_sys::PxTriangleMesh_release_mut; +use crate::{ + math::{PxBounds3, PxVec3}, + owner::Owner, + traits::Class, +}; + +use physx_sys::{ + // TODO: high level wrapper for PxMassProperties + PxMassProperties, + PxTriangleMesh_getLocalBounds, + // TODO: SDF getters + //PxTriangleMesh_getSDF, + //PxTriangleMesh_getSDFDimensions, + //PxTriangleMesh_setPreferSDFProjection_mut, + //PxTriangleMesh_getPreferSDFProjection, + PxTriangleMesh_getMassInformation, + PxTriangleMesh_getNbTriangles, + PxTriangleMesh_getNbVertices, + PxTriangleMesh_getTriangleMaterialIndex, + PxTriangleMesh_getTriangleMeshFlags, + PxTriangleMesh_getTriangles, + PxTriangleMesh_getTrianglesRemap, + PxTriangleMesh_getVertices, + PxTriangleMesh_getVerticesForModification_mut, + PxTriangleMesh_refitBVH_mut, + PxTriangleMesh_release_mut, +}; + +pub use physx_sys::{ + PxTriangleMeshFlag as TriangleMeshFlag, PxTriangleMeshFlags as TriangleMeshFlags, +}; #[repr(transparent)] pub struct TriangleMesh { @@ -11,12 +41,114 @@ crate::DeriveClassForNewType!(TriangleMesh: PxTriangleMesh, PxBase); impl TriangleMesh { /// # Safety + /// /// Owner's own the pointer they wrap, using the pointer after dropping the Owner, /// or creating multiple Owners from the same pointer will cause UB. Use `into_ptr` to /// retrieve the pointer and consume the Owner without dropping the pointee. pub unsafe fn from_raw(ptr: *mut physx_sys::PxTriangleMesh) -> Option> { unsafe { Owner::from_raw(ptr as *mut Self) } } + + /// Returns the number of vertices. + pub fn get_nb_vertices(&self) -> u32 { + unsafe { PxTriangleMesh_getNbVertices(self.as_ptr()) } + } + + /// Returns the vertices. + pub fn get_vertices(&self) -> &[PxVec3] { + unsafe { + std::slice::from_raw_parts( + PxTriangleMesh_getVertices(self.as_ptr()) as *const PxVec3, + self.get_nb_vertices() as usize, + ) + } + } + + /// Returns all mesh vertices for modification. + pub fn get_vertices_mut(&mut self) -> &mut [PxVec3] { + unsafe { + std::slice::from_raw_parts_mut( + PxTriangleMesh_getVerticesForModification_mut(self.as_mut_ptr()) as *mut PxVec3, + self.get_nb_vertices() as usize, + ) + } + } + + /// Refits BVH for mesh vertices. + pub fn refit_bvh(&mut self) -> PxBounds3 { + unsafe { PxTriangleMesh_refitBVH_mut(self.as_mut_ptr()) }.into() + } + + /// Returns the number of triangles. + pub fn get_nb_triangles(&self) -> u32 { + unsafe { PxTriangleMesh_getNbTriangles(self.as_ptr()) } + } + + /// Returns the triangle indices. + pub fn get_triangles(&self) -> TriangleMeshIndices<'_> { + let buffer = unsafe { PxTriangleMesh_getTriangles(self.as_ptr()) }; + let length = self.get_nb_triangles() as usize * 3; + + if self + .get_triangle_mesh_flags() + .contains(TriangleMeshFlags::E16BitIndices) + { + TriangleMeshIndices::U16(unsafe { + std::slice::from_raw_parts(buffer as *const u16, length) + }) + } else { + TriangleMeshIndices::U32(unsafe { + std::slice::from_raw_parts(buffer as *const u32, length) + }) + } + } + + /// Reads the TriangleMesh flags. + pub fn get_triangle_mesh_flags(&self) -> TriangleMeshFlags { + unsafe { PxTriangleMesh_getTriangleMeshFlags(self.as_ptr()) } + } + + /// Returns the triangle remapping table. + pub fn get_triangles_remap(&self) -> &[u32] { + unsafe { + std::slice::from_raw_parts( + PxTriangleMesh_getTrianglesRemap(self.as_ptr()), + self.get_nb_triangles() as usize, + ) + } + } + + /// Returns material table index of given triangle. + pub fn get_triangle_material_index(&self, triangle_index: u32) -> u16 { + unsafe { PxTriangleMesh_getTriangleMaterialIndex(self.as_ptr(), triangle_index) } + } + + /// Returns the mass properties of the mesh assuming unit density. + pub fn get_mass_information(&self) -> PxMassProperties { + let mut mass = MaybeUninit::uninit(); + let mut local_inertia = MaybeUninit::uninit(); + let mut local_center_of_mass = MaybeUninit::uninit(); + + unsafe { + PxTriangleMesh_getMassInformation( + self.as_ptr(), + mass.as_mut_ptr(), + local_inertia.as_mut_ptr(), + local_center_of_mass.as_mut_ptr(), + ); + + PxMassProperties { + inertiaTensor: local_inertia.assume_init(), + centerOfMass: local_center_of_mass.assume_init(), + mass: mass.assume_init(), + } + } + } + + /// Returns the local-space (vertex space) AABB from the triangle mesh. + pub fn get_local_bounds(&self) -> PxBounds3 { + unsafe { PxTriangleMesh_getLocalBounds(self.as_ptr()) }.into() + } } unsafe impl Send for TriangleMesh {} @@ -27,3 +159,22 @@ impl Drop for TriangleMesh { unsafe { PxTriangleMesh_release_mut(self.as_mut_ptr()) } } } + +#[derive(Debug)] +pub enum TriangleMeshIndices<'a> { + U16(&'a [u16]), + U32(&'a [u32]), +} + +impl<'a> TriangleMeshIndices<'a> { + pub fn len(&self) -> usize { + match self { + Self::U16(vec) => vec.len(), + Self::U32(vec) => vec.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +}