initial commit

This commit is contained in:
Patrick Marsee 2024-07-07 22:13:09 -04:00
parent d413a3f6e1
commit 10c2e5f2b0
3 changed files with 434 additions and 0 deletions

12
Cargo.toml Normal file
View file

@ -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" }

12
src/lib.rs Normal file
View file

@ -0,0 +1,12 @@
mod path_mesh_3d;
mod prelude {
pub use godot::prelude::*;
}
use prelude::*;
struct PathMeshExtension;
#[gdextension]
unsafe impl ExtensionLibrary for PathMeshExtension {}

410
src/path_mesh_3d.rs Normal file
View file

@ -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<Self::Item> {
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<H: Hasher>(&self, state: &mut H) {
state.write_u32(self.val.to_bits());
}
}
impl Eq for HashableF32{}
struct PathMeshData<'a> {
pub curve: &'a Gd<Curve3D>,
pub mesh_length: f32,
pub whole_reps: i32,
pub stretch: f32,
vertical_offset: f32,
horizontal_offset: f32,
sample_cache: HashMap<HashableF32, Transform3D>,
}
impl<'a> PathMeshData<'a> {
pub fn new(curve: &'a Gd<Curve3D>,
repeating_mesh: &Gd<Mesh>,
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<Gd<Curve3D>>,
#[export]
repeating_mesh: Option<Gd<Mesh>>,
#[export]
vertical_offset: f32,
#[export]
horizontal_offset: f32,
#[export]
lower_padding: f32,
#[export]
upper_padding: f32,
#[export]
invert_faces: bool,
base: Base<MeshInstance3D>,
}
#[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<Gd<Curve3D>> {
self.curve.clone()
}
#[func]
fn set_mesh_curve(&mut self, curve: Gd<Curve3D>) {
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::<ArrayMesh>() {
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::<Mesh>());
}
}
}
}
fn process_array(&self, original: &Array<Variant>, data: &mut PathMeshData) -> Result<Array<Variant>, 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::<PackedVector3Array>(); //must have
let new_vertices = self.generate_vertices(&vertices, data);
if let Ok(normals) = original.at(ARRAY_NORMAL).try_to::<PackedVector3Array>() {
processed.set(ARRAY_NORMAL, self.generate_normals(&normals, &vertices, data).to_variant());
}
if let Ok(tangents) = original.at(ARRAY_TANGENT).try_to::<PackedFloat32Array>() {
processed.set(ARRAY_TANGENT, self.generate_tangents(&tangents, &vertices, data).to_variant());
}
if let Ok(colors) = original.at(ARRAY_COLOR).try_to::<PackedColorArray>() {
processed.set(ARRAY_COLOR, self.generate_colors(&colors, data).to_variant());
}
if let Ok(uvs) = original.at(ARRAY_TEX_UV).try_to::<PackedVector2Array>() {
processed.set(ARRAY_TEX_UV, self.generate_uvs(&uvs, data).to_variant());
}
else if let Ok(_uvs) = original.at(ARRAY_TEX_UV).try_to::<PackedVector3Array>() {
processed.set(ARRAY_TEX_UV, self.generate_triplanar_uvs(&vertices).to_variant());
}
if let Ok(uvs) = original.at(ARRAY_TEX_UV2).try_to::<PackedVector2Array>() {
processed.set(ARRAY_TEX_UV2, self.generate_uvs(&uvs, data).to_variant());
}
else if let Ok(_uvs) = original.at(ARRAY_TEX_UV2).try_to::<PackedVector3Array>() {
processed.set(ARRAY_TEX_UV2, self.generate_triplanar_uvs(&vertices).to_variant());
}
if let Ok(indecies) = original.at(ARRAY_INDEX).try_to::<PackedInt32Array>() {
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
}
}