From 10c2e5f2b098cc541bb05e264f9ce129cdb9d2c8 Mon Sep 17 00:00:00 2001 From: Patrick Marsee Date: Sun, 7 Jul 2024 22:13:09 -0400 Subject: [PATCH] initial commit --- Cargo.toml | 12 ++ src/lib.rs | 12 ++ src/path_mesh_3d.rs | 410 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 434 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/path_mesh_3d.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..465b04e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "path_mesh" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] # Compile this crate to a dynamic C library. + +[dependencies] +godot = { git = "https://github.com/godot-rust/gdext", branch = "master" } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..87c35c3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +mod path_mesh_3d; + +mod prelude { + pub use godot::prelude::*; +} + +use prelude::*; + +struct PathMeshExtension; + +#[gdextension] +unsafe impl ExtensionLibrary for PathMeshExtension {} diff --git a/src/path_mesh_3d.rs b/src/path_mesh_3d.rs new file mode 100644 index 0000000..e0d70ac --- /dev/null +++ b/src/path_mesh_3d.rs @@ -0,0 +1,410 @@ +use crate::prelude::*; +use std::cmp::Eq; +use std::cmp::PartialEq; +use std::collections::HashMap; +//use std::collections::BTreeMap; +use std::hash::Hash; +use std::hash::Hasher; +use std::iter::zip; +use std::ops::Mul; +use std::vec::Vec; +use godot::classes::mesh::ArrayType; +use godot::classes::mesh::PrimitiveType; +use godot::classes::Mesh; +use godot::classes::ArrayMesh; +use godot::classes::Curve3D; +use godot::classes::Material; +use godot::classes::MeshInstance3D; +use godot::classes::IMeshInstance3D; + +const VERBOSE: bool = false; + +const ARRAY_VERTEX: usize = 0; +const ARRAY_NORMAL: usize = 1; +const ARRAY_TANGENT: usize = 2; +const ARRAY_COLOR: usize = 3; +const ARRAY_TEX_UV: usize = 4; +const ARRAY_TEX_UV2: usize = 5; +const ARRAY_CUSTOM0: usize = 6; +const ARRAY_CUSTOM1: usize = 7; +const ARRAY_CUSTOM2: usize = 8; +const ARRAY_CUSTOM3: usize = 9; +const ARRAY_BONES: usize = 10; +const ARRAY_WEIGHTS: usize = 11; +const ARRAY_INDEX: usize = 12; +const ARRAY_MAX: usize = 13; + +const DEFAULT_PRIMITIVE: PrimitiveType = PrimitiveType::TRIANGLES; + +enum MeshArrayError { + NoCurve, + NotEnoughPoints, +} + +/*struct TriReverseIter { + step: usize + temp: i32 +} + +impl TriReverseIter { + fn new() -> Self { + Self { + step: 0, + temp: 0, + } + } +} + +impl Iterator for TriReverseIter { + type Item = i32; + + fn next(&mut self) -> Option { + match step { + 0 => { + +}*/ + +#[derive(PartialEq)] +struct HashableF32 { + pub val: f32, +} + +impl HashableF32 { + fn new(val: f32) -> Self { + Self { + val: val, + } + } +} + +impl Hash for HashableF32 { + fn hash(&self, state: &mut H) { + state.write_u32(self.val.to_bits()); + } +} + +impl Eq for HashableF32{} + +struct PathMeshData<'a> { + pub curve: &'a Gd, + pub mesh_length: f32, + pub whole_reps: i32, + pub stretch: f32, + vertical_offset: f32, + horizontal_offset: f32, + sample_cache: HashMap, +} + +impl<'a> PathMeshData<'a> { + pub fn new(curve: &'a Gd, + repeating_mesh: &Gd, + vertical_offset: f32, + horizontal_offset: f32, + lower_padding: f32, + upper_padding: f32) -> Self { + let a_mesh_length = repeating_mesh.get_aabb().size.z; + let a_reps = (curve.get_baked_length() - lower_padding - upper_padding) / a_mesh_length; + let a_whole_reps = a_reps.round().max(1.0) as i32; + let a_stretch = a_reps / (a_whole_reps as f32); + Self { + curve: curve, + mesh_length: a_mesh_length, + whole_reps: a_whole_reps, + stretch: a_stretch, + vertical_offset: vertical_offset, + horizontal_offset: horizontal_offset, + sample_cache: HashMap::new(), + } + } + + pub fn sample(&mut self, offset: f32) -> &Transform3D { + self.sample_cache.entry(HashableF32::new(offset)).or_insert_with( + || -> Transform3D { + let mut tf = self.curve.sample_baked_with_rotation_ex().offset(offset).done(); + let x_axis = tf.basis.col_c().cross(Vector3::UP).normalized(); + let y_axis = x_axis.cross(tf.basis.col_c()).normalized(); + tf.basis.set_col_a(x_axis); + tf.basis.set_col_b(y_axis); + tf.origin += self.horizontal_offset * x_axis + self.vertical_offset * y_axis; + tf + } + ) + } +} + +#[derive(GodotClass)] +#[class(init, base=MeshInstance3D)] +pub struct PathMesh3D { + #[export] + curve: Option>, + + #[export] + repeating_mesh: Option>, + + #[export] + vertical_offset: f32, + + #[export] + horizontal_offset: f32, + + #[export] + lower_padding: f32, + + #[export] + upper_padding: f32, + + #[export] + invert_faces: bool, + + base: Base, +} + +#[godot_api] +impl IMeshInstance3D for PathMesh3D { + fn ready(&mut self) { + if self.curve.is_some() && self.repeating_mesh.is_some() { + self.generate_mesh(); + } + } +} + +#[godot_api] +impl PathMesh3D { + #[func] + fn get_mesh_curve(&self) -> Option> { + self.curve.clone() + } + + #[func] + fn set_mesh_curve(&mut self, curve: Gd) { + self.curve = Some(curve); + } + + #[func] + fn generate_mesh(&mut self) { + if let Some(mesh) = &self.repeating_mesh { + if let Some(curve) = &self.curve { + let mut data = PathMeshData::new(curve, + mesh, + self.vertical_offset, + self.horizontal_offset, + self.lower_padding, + self.upper_padding); + let surface_count = mesh.get_surface_count(); + if VERBOSE { + godot_print!("surface_count = {}", surface_count); + } + + let mut new_mesh = ArrayMesh::new_gd(); + for i in 0..surface_count { + + // try to get surace details + let mut primitive = DEFAULT_PRIMITIVE; + let mut name = GString::new(); + if let Ok(mesh) = mesh.clone().try_cast::() { + primitive = mesh.surface_get_primitive_type(i); + name = mesh.surface_get_name(i); + } + + let arrays = mesh.surface_get_arrays(i); + if let Ok(new_arrays) = self.process_array(&arrays, &mut data) { + new_mesh.add_surface_from_arrays(primitive, new_arrays); + new_mesh.surface_set_name(i, name); + if let Some(material) = mesh.surface_get_material(i) { + new_mesh.surface_set_material(i, material); + } + } + } + if new_mesh.get_surface_count() > 0 { + if VERBOSE { + godot_print!("new_mesh.surface_count = {}", new_mesh.get_surface_count()); + } + self.base_mut().set_mesh(new_mesh.upcast::()); + } + } + } + } + + fn process_array(&self, original: &Array, data: &mut PathMeshData) -> Result, MeshArrayError>{ + // Optimization ideas: + // 1. Often, multiple verticies have the same z coordinate. + // Caching curve samples could speed up their processing. + // 2. Current method results in duplicate vertices where model ends meet. + // Reusing previous vertices could speed up processing and be lighter on memory. + let mut processed = VariantArray::new(); + processed.resize(ArrayType::MAX.ord().try_into().unwrap(), &Variant::nil()); + if data.curve.get_point_count() > 1 { + let vertices = original.at(ARRAY_VERTEX).to::(); //must have + let new_vertices = self.generate_vertices(&vertices, data); + + if let Ok(normals) = original.at(ARRAY_NORMAL).try_to::() { + processed.set(ARRAY_NORMAL, self.generate_normals(&normals, &vertices, data).to_variant()); + } + + if let Ok(tangents) = original.at(ARRAY_TANGENT).try_to::() { + processed.set(ARRAY_TANGENT, self.generate_tangents(&tangents, &vertices, data).to_variant()); + } + + if let Ok(colors) = original.at(ARRAY_COLOR).try_to::() { + processed.set(ARRAY_COLOR, self.generate_colors(&colors, data).to_variant()); + } + + if let Ok(uvs) = original.at(ARRAY_TEX_UV).try_to::() { + processed.set(ARRAY_TEX_UV, self.generate_uvs(&uvs, data).to_variant()); + } + else if let Ok(_uvs) = original.at(ARRAY_TEX_UV).try_to::() { + processed.set(ARRAY_TEX_UV, self.generate_triplanar_uvs(&vertices).to_variant()); + } + + if let Ok(uvs) = original.at(ARRAY_TEX_UV2).try_to::() { + processed.set(ARRAY_TEX_UV2, self.generate_uvs(&uvs, data).to_variant()); + } + else if let Ok(_uvs) = original.at(ARRAY_TEX_UV2).try_to::() { + processed.set(ARRAY_TEX_UV2, self.generate_triplanar_uvs(&vertices).to_variant()); + } + + if let Ok(indecies) = original.at(ARRAY_INDEX).try_to::() { + processed.set(ARRAY_INDEX, self.generate_indecies(&indecies, vertices.len() as i32, data).to_variant()); + } + + processed.set(ARRAY_VERTEX, new_vertices.to_variant()); + return Ok(processed); + } + return Err(MeshArrayError::NotEnoughPoints); + } + + fn generate_vertices(&self, original: &PackedVector3Array, data: &mut PathMeshData) -> PackedVector3Array { + if VERBOSE { + godot_print!("old_vertices.len() = {}", original.len()); + } + let mut new_vertices = PackedVector3Array::new(); + for i in 0..data.whole_reps { + let offset = (i as f32 + 0.5) * data.mesh_length + self.lower_padding; + for vertex in original.as_slice().iter() { + // Sample the curve + let curve_point = data.sample((vertex.z + offset) * data.stretch); + + // Vertex + let new_vertex = curve_point.basis.col_a() * vertex.x + + curve_point.basis.col_b() * vertex.y + + curve_point.origin; + new_vertices.push(new_vertex); + } + } + if VERBOSE { + godot_print!("new_vertices.len() = {}", new_vertices.len()); + } + new_vertices + } + + fn generate_normals(&self, original: &PackedVector3Array, vertices: &PackedVector3Array, data: &mut PathMeshData) -> PackedVector3Array { + if VERBOSE { + godot_print!("old_normals.len() = {}", original.len()); + } + let mut new_normals = PackedVector3Array::new(); + for i in 0..data.whole_reps { + let offset = (i as f32 + 0.5) * data.mesh_length + self.lower_padding; + for (normal, vertex) in zip(original.as_slice().iter(), vertices.as_slice().iter()) { + // Sample the curve + let curve_point = data.sample((vertex.z + offset) * data.stretch); + + // Normal + let new_normal = curve_point.basis.mul(normal.clone()); + new_normals.push(new_normal); + } + } + if VERBOSE { + godot_print!("new_normals.len() = {}", new_normals.len()); + } + new_normals + } + + fn generate_tangents(&self, original: &PackedFloat32Array, vertices: &PackedVector3Array, data: &mut PathMeshData) -> PackedFloat32Array { + if VERBOSE { + godot_print!("old_tangents.len() = {}", original.len()); + } + let mut new_tangents = PackedFloat32Array::new(); + for i in 0..data.whole_reps { + let offset = (i as f32 + 0.5) * data.mesh_length + self.lower_padding; + let mut tangent_vectors = Vec::new(); + let mut tangent_binormals = Vec::new(); + for j in (0..original.len()).step_by(4) { + tangent_vectors.push(Vector3::new(original[j + 0], original[j + 1], original[j + 2])); + tangent_binormals.push(original[j + 3]); + } + for ((tangent, binormal), vertex) in zip(zip(tangent_vectors.as_slice().iter(), tangent_binormals.as_slice().iter()), vertices.as_slice().iter()) { + // Sample the curve + let curve_point = data.sample((vertex.z + offset) * data.stretch); + + // Normal + let new_tangent = curve_point.basis.mul(*tangent); + new_tangents.push(new_tangent.x); + new_tangents.push(new_tangent.y); + new_tangents.push(new_tangent.z); + new_tangents.push(*binormal); + } + } + if VERBOSE { + godot_print!("new_tangents.len() = {}", new_tangents.len()); + } + new_tangents + } + + fn generate_colors(&self, original: &PackedColorArray, data: &mut PathMeshData) -> PackedColorArray { + if VERBOSE { + let color_count = original.len() as i32; + godot_print!("old_colors.len() = {}", color_count); + } + let mut new_colors = original.clone(); + for _i in 1..data.whole_reps { + new_colors.extend_array(original); + } + if VERBOSE { + godot_print!("new_colors.len() = {}", new_colors.len()); + } + new_colors + } + + fn generate_uvs(&self, original: &PackedVector2Array, data: &mut PathMeshData) -> PackedVector2Array { + if VERBOSE { + let uv_count = original.len() as i32; + godot_print!("old_uvs.len() = {}", uv_count); + } + let mut new_uvs = original.clone(); + for _i in 1..data.whole_reps { + //let repeated_section = original.as_slice().into_iter().map(|x| x + i * vertex_count); + new_uvs.extend_array(original); + } + if VERBOSE { + godot_print!("new_uvs.len() = {}", new_uvs.len()); + } + new_uvs + } + + fn generate_triplanar_uvs(&self, vertices: &PackedVector3Array) -> PackedVector3Array { + return vertices.clone(); + } + + fn generate_indecies(&self, original: &PackedInt32Array, vertex_count: i32, data: &mut PathMeshData) -> PackedInt32Array { + let index_count = original.len() as i32; + if VERBOSE { + godot_print!("old_indecies.len() = {}", index_count); + } + let mut new_indecies = original.clone(); + for i in 1..data.whole_reps { + let repeated_section = original.as_slice().into_iter().map(|x| x + i * vertex_count); + new_indecies.extend(repeated_section); + } + if self.invert_faces { + for j in (0..new_indecies.len()).step_by(3) { + let temp = new_indecies[j + 1]; + new_indecies[j + 1] = new_indecies[j + 2]; + new_indecies[j + 2] = temp; + } + } + if VERBOSE { + godot_print!("new_indecies.len() = {}", new_indecies.len()); + } + new_indecies + } +}