use glam::{Mat4, Quat, Vec3};
pub type BoneId = usize;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Transform {
pub translation: Vec3,
pub rotation: Quat,
pub scale: Vec3,
}
impl Transform {
pub const IDENTITY: Self = Self {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
pub fn to_mat4(&self) -> Mat4 {
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
}
}
impl Default for Transform {
fn default() -> Self {
Self::IDENTITY
}
}
#[derive(Debug, Clone)]
pub struct Bone {
pub name: String,
pub parent: Option<BoneId>,
pub rest: Transform,
pub inverse_bind: Option<Mat4>,
}
#[derive(Debug, Clone, Default)]
pub struct Skeleton {
pub bones: Vec<Bone>,
}
impl Skeleton {
pub fn bone_name(&self, id: BoneId) -> &str {
&self.bones[id].name
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Property {
Translation,
Rotation,
Scale,
}
impl Property {
pub fn as_str(self) -> &'static str {
match self {
Property::Translation => "translation",
Property::Rotation => "rotation",
Property::Scale => "scale",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Interpolation {
Linear,
Step,
CubicSpline,
}
#[derive(Debug, Clone)]
pub enum TrackValues {
Vec3s(Vec<Vec3>),
Quats(Vec<Quat>),
}
impl TrackValues {
pub fn len(&self) -> usize {
match self {
TrackValues::Vec3s(v) => v.len(),
TrackValues::Quats(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone)]
pub struct Track {
pub bone: BoneId,
pub property: Property,
pub interpolation: Interpolation,
pub times: Vec<f32>,
pub values: TrackValues,
}
impl Track {
pub fn key_count(&self) -> usize {
self.times.len()
}
pub fn value_index(&self, k: usize) -> usize {
match self.interpolation {
Interpolation::CubicSpline => 3 * k + 1,
_ => k,
}
}
pub fn key_vec3(&self, k: usize) -> Option<Vec3> {
match &self.values {
TrackValues::Vec3s(v) => v.get(self.value_index(k)).copied(),
TrackValues::Quats(_) => None,
}
}
pub fn key_quat(&self, k: usize) -> Option<Quat> {
match &self.values {
TrackValues::Quats(v) => v.get(self.value_index(k)).copied(),
TrackValues::Vec3s(_) => None,
}
}
pub fn start_time(&self) -> f32 {
self.times.first().copied().unwrap_or(0.0)
}
pub fn end_time(&self) -> f32 {
self.times.last().copied().unwrap_or(0.0)
}
}
#[derive(Debug, Clone)]
pub struct Clip {
pub name: String,
pub duration_s: f64,
pub tracks: Vec<Track>,
}
#[derive(Debug, Clone, Default)]
pub struct SourceInfo {
pub path: Option<String>,
pub format: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct Document {
pub skeleton: Skeleton,
pub clips: Vec<Clip>,
pub assets: SceneAssets,
pub source: SourceInfo,
}
#[derive(Debug, Clone, Default)]
pub struct Primitive {
pub material: Option<usize>,
pub indices: Vec<u32>,
pub positions: Vec<Vec3>,
pub normals: Vec<Vec3>,
pub uvs: Vec<[f32; 2]>,
pub joints: Vec<[u16; 4]>,
pub weights: Vec<[f32; 4]>,
}
#[derive(Debug, Clone, Default)]
pub struct MeshAsset {
pub name: String,
pub node: BoneId,
pub primitives: Vec<Primitive>,
pub skin_joints: Vec<BoneId>,
pub skin_ibms: Vec<Mat4>,
}
#[derive(Debug, Clone)]
pub struct TextureAsset {
pub bytes: Vec<u8>,
pub mime: String,
}
#[derive(Debug, Clone)]
pub struct MaterialAsset {
pub name: String,
pub base_color: [f32; 4],
pub metallic: f32,
pub roughness: f32,
pub base_color_texture: Option<TextureAsset>,
}
impl Primitive {
pub fn weld(&mut self) {
if !self.indices.is_empty() || self.positions.is_empty() {
return;
}
let corner_key = |i: usize| -> Vec<u8> {
let mut key = Vec::with_capacity(64);
let mut push_f32s = |vals: &[f32]| {
for v in vals {
key.extend_from_slice(&v.to_le_bytes());
}
};
push_f32s(&self.positions[i].to_array());
if let Some(n) = self.normals.get(i) {
push_f32s(&n.to_array());
}
if let Some(uv) = self.uvs.get(i) {
push_f32s(uv);
}
if let Some(w) = self.weights.get(i) {
push_f32s(w);
}
if let Some(j) = self.joints.get(i) {
for v in j {
key.extend_from_slice(&v.to_le_bytes());
}
}
key
};
let mut seen: std::collections::HashMap<Vec<u8>, u32> = std::collections::HashMap::new();
let mut indices = Vec::with_capacity(self.positions.len());
let mut positions = Vec::new();
let mut normals = Vec::new();
let mut uvs = Vec::new();
let mut joints = Vec::new();
let mut weights = Vec::new();
for i in 0..self.positions.len() {
let index = *seen.entry(corner_key(i)).or_insert_with(|| {
positions.push(self.positions[i]);
if let Some(n) = self.normals.get(i) {
normals.push(*n);
}
if let Some(uv) = self.uvs.get(i) {
uvs.push(*uv);
}
if let Some(j) = self.joints.get(i) {
joints.push(*j);
}
if let Some(w) = self.weights.get(i) {
weights.push(*w);
}
(positions.len() - 1) as u32
});
indices.push(index);
}
self.indices = indices;
self.positions = positions;
self.normals = normals;
self.uvs = uvs;
self.joints = joints;
self.weights = weights;
}
}
#[derive(Debug, Clone, Default)]
pub struct SceneAssets {
pub meshes: Vec<MeshAsset>,
pub materials: Vec<MaterialAsset>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn weld_preserves_uv_seams_at_shared_positions() {
let mut primitive = Primitive {
positions: vec![Vec3::ZERO, Vec3::ZERO, Vec3::ZERO],
uvs: vec![[0.0, 0.0], [1.0, 0.0], [0.0, 0.0]],
..Primitive::default()
};
primitive.weld();
assert_eq!(primitive.positions.len(), 2);
let reconstructed_corners = primitive
.indices
.iter()
.map(|&index| {
let index = index as usize;
(primitive.positions[index], primitive.uvs[index])
})
.collect::<Vec<_>>();
assert_eq!(
reconstructed_corners,
vec![
(Vec3::ZERO, [0.0, 0.0]),
(Vec3::ZERO, [1.0, 0.0]),
(Vec3::ZERO, [0.0, 0.0]),
]
);
}
}