Skip to content

Commit

Permalink
Merge pull request #11 from alanpq/dev
Browse files Browse the repository at this point in the history
patch bump
  • Loading branch information
alanpq authored Aug 17, 2024
2 parents cda99f3 + cc01a42 commit 0273e6a
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 275 deletions.
6 changes: 5 additions & 1 deletion bindings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ crate-type = ["cdylib"]
[dependencies]
glam = { version = "0.27.0", features = ["glam-assert"] }
lib = { version = "0.1.0", path = "../league-toolkit/crates/league-toolkit", package = "league-toolkit" }
pyo3 = { version = "0.21.2", features = ["extension-module"] }
pyo3 = { version = "0.21.2", features = [
"extension-module",
"abi3",
"abi3-py310",
] }
287 changes: 13 additions & 274 deletions bindings/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,294 +1,33 @@
use lib::core::animation::{joint, RigResource};
use lib::core::mem::{
IndexBuffer, IndexFormat, VertexBuffer, VertexBufferDescription, VertexBufferUsage,
VertexElement,
};
use lib::core::mesh::{SkinnedMesh, SkinnedMeshVertexType};
use pyo3::prelude::*;
use std::collections::{HashMap, VecDeque};
use std::io::BufWriter;
use std::path::PathBuf;

pub mod skinned;

#[pyfunction]
fn version() -> PyResult<&'static str> {
Ok("1.0.2")
}

type Vec3 = [f32; 3];
type Vec4 = [f32; 4];
type Mat4 = [[f32; 4]; 4];

#[derive(Debug)]
#[pyclass]
struct Vertex {
#[pyo3(get, set)]
pos: Vec3,
#[pyo3(get, set)]
normal: Vec3,

#[pyo3(get, set)]
blend_indices: [u8; 4],
#[pyo3(get, set)]
blend_weights: [f32; 4],
}

#[pymethods]
impl Vertex {
#[new]
fn py_new(
pos: Vec3,
normal: Vec3,
blend_indices: Option<[u8; 4]>,
blend_weights: Option<[f32; 4]>,
) -> Self {
Self {
pos,
normal,
blend_indices: blend_indices.unwrap_or_default(),
blend_weights: blend_weights.unwrap_or_default(),
}
}
}

impl Vertex {
fn push_bytes(&self, v: &mut Vec<u8>) {
// 52 total
for i in self.pos {
// POSITION (12)
v.extend_from_slice(&i.to_le_bytes())
}
v.extend(self.blend_indices); // BLEND_INDEX (4)
v.extend(self.blend_weights.iter().flat_map(|w| w.to_le_bytes())); // BLEND_WEIGHT (16)

for i in self.normal {
// NORMAL (12)
v.extend_from_slice(&i.to_le_bytes())
}
v.extend([0_u8; 8]); // TEXCOORD_0 (8)
}
}

#[pyfunction]
fn export_skn(
path: PathBuf,
vertices: Vec<PyRef<'_, Vertex>>,
triangles: Vec<[i64; 3]>,
influences: Option<Vec<[(i16, f32); 4]>>,
) -> PyResult<()> {
// println!("ve!rts: {vertices:?}");

println!("influences: {influences:?}");
pub type Vec3 = [f32; 3];
pub type Vec4 = [f32; 4];
pub type Mat4 = [[f32; 4]; 4];

let mut buf = Vec::new();
for v in vertices {
v.push_bytes(&mut buf);
}
// println!("{buf:?}");
let vert_buf =
VertexBufferDescription::from(SkinnedMeshVertexType::Basic).into_vertex_buffer(buf);
// println!("{vert_buf:?}");
let mut buf = Vec::new();

for tri in triangles {
for i in tri {
buf.extend((i as u16).to_le_bytes());
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
#[cfg(debug_assertions)] {
println!($($arg)*);
}
}

let idx_buf = IndexBuffer::new(IndexFormat::U16, buf);

let skn = SkinnedMesh::new(vec![], vert_buf, idx_buf);
let mut file = std::fs::File::create(path).map(BufWriter::new).unwrap();
skn.to_writer(&mut file).unwrap();

Ok(())
}

#[derive(Debug)]
#[pyclass]
struct Bone {
#[pyo3(get, set)]
parent: Option<String>,
ibm: Mat4,
local: Mat4,
is_influence: bool,
}

#[pymethods]
impl Bone {
#[new]
fn py_new(parent: String, local: Mat4, ibm: Mat4, is_influence: bool) -> Self {
Self {
parent: match parent.is_empty() {
true => None,
false => Some(parent),
},
ibm,
local,
is_influence,
}
}
}

fn topological_sort(graph: &HashMap<String, Vec<String>>) -> Option<Vec<String>> {
let mut in_degree: HashMap<String, usize> = HashMap::new();
let mut zero_in_degree_queue: VecDeque<String> = VecDeque::new();
let mut sorted_list: Vec<String> = Vec::new();

// Initialize in-degree of all nodes to 0
for node in graph.keys() {
in_degree.insert(node.clone(), 0);
}

// Calculate in-degree of each node
for children in graph.values() {
for child in children {
if let Some(degree) = in_degree.get_mut(child) {
*degree += 1;
}
}
}

// Collect nodes with zero in-degree
for (node, &degree) in in_degree.iter() {
if degree == 0 {
zero_in_degree_queue.push_back(node.clone());
}
}

// Process nodes with zero in-degree
while let Some(node) = zero_in_degree_queue.pop_front() {
sorted_list.push(node.clone());

if let Some(children) = graph.get(&node) {
for child in children {
if let Some(degree) = in_degree.get_mut(child) {
*degree -= 1;
if *degree == 0 {
zero_in_degree_queue.push_back(child.clone());
}
}
}
}
}

// If sorted list contains all nodes, return it, otherwise there's a cycle
if sorted_list.len() == graph.len() {
Some(sorted_list)
} else {
None
}
}

#[pyfunction]
fn export_skl(
bones: HashMap<String, PyRef<'_, Bone>>,
path: Option<PathBuf>,
) -> PyResult<HashMap<String, i16>> {
let mut skl = RigResource::builder("skeleton_name", "skeleton_asset_name");

let orig_bone_count = bones.len();
let mut joints = bones
.into_iter()
.map(|(name, bone)| {
let local_transform = glam::Mat4::from_cols_array_2d(&bone.local).transpose();
let ibm = glam::Mat4::from_cols_array_2d(&bone.ibm).transpose();

println!("mat: {:?}", local_transform.to_scale_rotation_translation());
(
name.clone(),
(
joint::Builder::new(name)
.with_local_transform(local_transform)
.with_inverse_bind_transform(ibm)
.with_influence(bone.is_influence),
bone.parent.clone(),
),
)
})
.collect::<HashMap<_, _>>();

let mut child_map: HashMap<String, Vec<String>> = joints
.keys()
.map(|name| (name.to_owned(), Vec::new()))
.collect();

for (name, (joint, parent)) in &joints {
println!("bone {name:?} -> {parent:?}");
let Some(parent_map) = parent.clone().and_then(|p| child_map.get_mut(&p)) else {
continue;
};
parent_map.push(name.clone());
// skl.add_root_joint(joint.clone().with_children([joint::Builder::new("assas")]));
}

let mut processed = 0;
let nodes = topological_sort(&child_map).unwrap();
for n in nodes.iter().rev() {
println!("- {n}");
let Some(children) = child_map.remove(n) else {
continue;
};
let children = children
.into_iter()
.filter_map(|c| joints.remove(&c).map(|j| j.0))
.collect::<Vec<_>>();
println!(" - {children:?}");
let Some(joint) = joints.get_mut(n) else {
continue;
};
processed += children.len();
joint.0.add_children(children);
}

println!("{processed} child bones.");
println!("{} root bones.", joints.len());
if orig_bone_count != processed + joints.len() {
println!("[!!] we got {orig_bone_count} bones!");
}

for (_, joint) in joints {
skl.add_root_joint(joint.0);
}

// let root_joints = bones
// .iter()
// .filter_map(|(name, bone)| match bone.parent.is_some() {
// true => None,
// false => {
// joints.insert(name, joint::Builder::new(name));
// Some(name)
// }
// })
// .collect::<Vec<_>>();

// println!("parsed {} bones", joints.len());
// if orig_bone_count != joints.len() {
// println!("[!!] we got {orig_bone_count} bones!");
// }

let rig = skl.build();
if let Some(path) = path {
let mut file = std::fs::File::create(path).map(BufWriter::new).unwrap();
rig.to_writer(&mut file).unwrap();
}

let joint_map = rig
.influences()
.iter()
.copied()
.map(|i| (rig.joints()[i as usize].name().to_string(), i))
.collect();
println!("joint_map: {joint_map:?}");
Ok(joint_map)
};
}

#[pymodule]
fn league_toolkit(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Vertex>()?;
m.add_class::<Bone>()?;
m.add_function(wrap_pyfunction!(version, m)?)?;
m.add_function(wrap_pyfunction!(export_skn, m)?)?;
m.add_function(wrap_pyfunction!(export_skl, m)?)?;

skinned::export::register(m)?;
Ok(())
}
28 changes: 28 additions & 0 deletions bindings/src/skinned/export/bone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::Mat4;
use pyo3::prelude::*;

#[derive(Debug)]
#[pyclass]
pub struct Bone {
#[pyo3(get, set)]
pub parent: Option<String>,
pub ibm: Mat4,
pub local: Mat4,
pub is_influence: bool,
}

#[pymethods]
impl Bone {
#[new]
fn py_new(parent: String, local: Mat4, ibm: Mat4, is_influence: bool) -> Self {
Self {
parent: match parent.is_empty() {
true => None,
false => Some(parent),
},
ibm,
local,
is_influence,
}
}
}
18 changes: 18 additions & 0 deletions bindings/src/skinned/export/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
mod bone;
mod skl;
mod skn;
mod vertex;
pub use bone::*;
pub use skl::*;
pub use skn::*;
pub use vertex::*;

use pyo3::prelude::*;
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Vertex>()?;
m.add_function(wrap_pyfunction!(export_skn, m)?)?;

m.add_class::<Bone>()?;
m.add_function(wrap_pyfunction!(export_skl, m)?)?;
Ok(())
}
Loading

0 comments on commit 0273e6a

Please sign in to comment.