initial commit
This commit is contained in:
parent
d413a3f6e1
commit
10c2e5f2b0
3 changed files with 434 additions and 0 deletions
12
Cargo.toml
Normal file
12
Cargo.toml
Normal 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
12
src/lib.rs
Normal 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
410
src/path_mesh_3d.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue