use crate::ecs::animation::components::{
AnimationChannel, AnimationClip, AnimationInterpolation, AnimationProperty, AnimationSampler,
AnimationSamplerOutput,
};
use crate::ecs::material::components::AlphaMode;
use crate::ecs::mesh::components::{
Mesh, MorphTarget, MorphTargetData, SkinData, SkinnedVertex, Vertex,
};
use crate::ecs::world::components::{
Camera, LocalTransform, Material, Name, OrthographicCamera, PerspectiveCamera, Projection,
RenderMesh, Visibility,
};
use gltf::{Document, Node, Primitive, buffer, import, import_slice};
use nalgebra_glm::{Mat4, Quat, Vec2, Vec3, vec2, vec3};
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use super::super::components::{Prefab, PrefabComponents, PrefabNode};
#[derive(Debug, Clone)]
pub struct GltfSkin {
pub name: Option<String>,
pub joints: Vec<usize>,
pub inverse_bind_matrices: Vec<Mat4>,
}
pub struct GltfLoadResult {
pub prefabs: Vec<Prefab>,
pub meshes: HashMap<String, Mesh>,
pub materials: Vec<Material>,
pub textures: HashMap<String, (Vec<u8>, u32, u32)>,
pub animations: Vec<AnimationClip>,
pub skins: Vec<GltfSkin>,
pub node_to_skin: HashMap<usize, usize>,
pub node_to_morph_target_count: HashMap<usize, usize>,
pub node_count: usize,
}
pub fn import_gltf_from_path(
path: &std::path::Path,
) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
let (doc, buffers, images) = import(path)?;
let canonical_path = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
let namespace = canonical_path
.to_str()
.unwrap_or("unnamed")
.replace('\\', "/")
.replace('/', "::");
process_gltf_data(doc, &buffers, &images, &namespace)
}
pub fn import_gltf_from_bytes(bytes: &[u8]) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
let (doc, buffers, images) = import_slice(bytes)?;
let mut hasher = DefaultHasher::new();
bytes.hash(&mut hasher);
let namespace = format!("bytes_{:016x}", hasher.finish());
process_gltf_data(doc, &buffers, &images, &namespace)
}
fn process_gltf_data(
doc: Document,
buffers: &[buffer::Data],
images: &[gltf::image::Data],
namespace: &str,
) -> Result<GltfLoadResult, Box<dyn std::error::Error>> {
let mut meshes = HashMap::new();
let mut materials = Vec::new();
let mut textures = HashMap::new();
let mut mesh_index_to_primitives: HashMap<usize, Vec<(String, usize)>> = HashMap::new();
let mut gltf_material_index_to_deduplicated_index: HashMap<usize, usize> = HashMap::new();
let mut mesh_to_skin: HashMap<usize, usize> = HashMap::new();
let mut node_to_morph_target_count: HashMap<usize, usize> = HashMap::new();
for node in doc.nodes() {
if let (Some(mesh_index), Some(skin_index)) = (
node.mesh().map(|m| m.index()),
node.skin().map(|s| s.index()),
) {
mesh_to_skin.insert(mesh_index, skin_index);
}
if let Some(mesh) = node.mesh() {
let morph_count = mesh
.primitives()
.next()
.map(|p| p.morph_targets().count())
.unwrap_or(0);
if morph_count > 0 {
node_to_morph_target_count.insert(node.index(), morph_count);
}
}
}
let mut image_index_to_name = HashMap::new();
for image in doc.images() {
let base_name = image
.name()
.unwrap_or(&format!("Image_{}", image.index()))
.to_string();
let name = format!("{}::{}", namespace, base_name);
image_index_to_name.insert(image.index(), name);
}
for material in doc.materials() {
let pbr = material.pbr_metallic_roughness();
let base_color = pbr.base_color_factor();
let emissive = material.emissive_factor();
let (base_texture, base_texture_uv_set) = pbr
.base_color_texture()
.map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
.unwrap_or((None, 0));
let (emissive_texture, emissive_texture_uv_set) = material
.emissive_texture()
.map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
.unwrap_or((None, 0));
let (normal_texture, normal_texture_uv_set) = material
.normal_texture()
.map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
.unwrap_or((None, 0));
let normal_scale = material
.normal_texture()
.map(|tex| tex.scale())
.unwrap_or(1.0);
let (metallic_roughness_texture, metallic_roughness_texture_uv_set) = pbr
.metallic_roughness_texture()
.map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
.unwrap_or((None, 0));
let (occlusion_texture, occlusion_texture_uv_set) = material
.occlusion_texture()
.map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
.unwrap_or((None, 0));
let occlusion_strength = material
.occlusion_texture()
.map(|tex| tex.strength())
.unwrap_or(1.0);
let transmission_factor = material
.transmission()
.map(|t| t.transmission_factor())
.unwrap_or(0.0);
let (transmission_texture, transmission_texture_uv_set) = material
.transmission()
.and_then(|t| {
t.transmission_texture().map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
})
.unwrap_or((None, 0));
let thickness = material
.volume()
.map(|v| v.thickness_factor())
.unwrap_or(0.0);
let (thickness_texture, thickness_texture_uv_set) = material
.volume()
.and_then(|v| {
v.thickness_texture().map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
})
.unwrap_or((None, 0));
let attenuation_color = material
.volume()
.map(|v| v.attenuation_color())
.unwrap_or([1.0, 1.0, 1.0]);
let attenuation_distance = material
.volume()
.map(|v| v.attenuation_distance())
.unwrap_or(f32::INFINITY);
let ior = material.ior().unwrap_or(1.5);
let specular_factor = material
.specular()
.map(|s| s.specular_factor())
.unwrap_or(1.0);
let specular_color_factor = material
.specular()
.map(|s| s.specular_color_factor())
.unwrap_or([1.0, 1.0, 1.0]);
let (specular_texture, specular_texture_uv_set) = material
.specular()
.and_then(|s| {
s.specular_texture().map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
})
.unwrap_or((None, 0));
let (specular_color_texture, specular_color_texture_uv_set) = material
.specular()
.and_then(|s| {
s.specular_color_texture().map(|tex| {
let image_index = tex.texture().source().index();
(
image_index_to_name.get(&image_index).cloned(),
tex.tex_coord(),
)
})
})
.unwrap_or((None, 0));
let emissive_strength = material.emissive_strength().unwrap_or(1.0);
let world_material = Material {
base_color: [base_color[0], base_color[1], base_color[2], base_color[3]],
emissive_factor: [emissive[0], emissive[1], emissive[2]],
alpha_mode: match material.alpha_mode() {
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => AlphaMode::Mask,
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
},
alpha_cutoff: material.alpha_cutoff().unwrap_or(0.5),
base_texture,
base_texture_uv_set,
emissive_texture,
emissive_texture_uv_set,
normal_texture,
normal_texture_uv_set,
normal_scale,
normal_map_flip_y: false,
normal_map_two_component: false,
metallic_roughness_texture,
metallic_roughness_texture_uv_set,
occlusion_texture,
occlusion_texture_uv_set,
occlusion_strength,
roughness: pbr.roughness_factor(),
metallic: pbr.metallic_factor(),
unlit: material.unlit(),
double_sided: material.double_sided(),
uv_scale: [1.0, 1.0],
transmission_factor,
transmission_texture,
transmission_texture_uv_set,
thickness,
thickness_texture,
thickness_texture_uv_set,
attenuation_color,
attenuation_distance,
ior,
specular_factor,
specular_color_factor,
specular_texture,
specular_texture_uv_set,
specular_color_texture,
specular_color_texture_uv_set,
emissive_strength,
};
let deduplicated_index =
if let Some(existing_index) = materials.iter().position(|m| m == &world_material) {
if let Some(mat_index) = material.index() {
tracing::debug!(
"Material {} is identical to material {}, reusing",
mat_index,
existing_index
);
}
existing_index
} else {
let new_index = materials.len();
materials.push(world_material);
new_index
};
if let Some(mat_index) = material.index() {
gltf_material_index_to_deduplicated_index.insert(mat_index, deduplicated_index);
}
}
for mesh in doc.meshes() {
let base_mesh_name = mesh
.name()
.unwrap_or(&format!("Mesh_{}", mesh.index()))
.to_string();
let mut primitive_names = Vec::new();
let default_morph_weights: Option<Vec<f32>> = mesh.weights().map(|w| w.to_vec());
for (primitive_index, primitive) in mesh.primitives().enumerate() {
if let Some(mut mesh_primitive) =
load_gltf_primitive(&primitive, buffers, default_morph_weights.as_deref())
{
if let Some(&skin_index) = mesh_to_skin.get(&mesh.index())
&& let Some(skin_data) = &mut mesh_primitive.skin_data
{
skin_data.skin_index = Some(skin_index);
}
let base_primitive_name = if mesh.primitives().len() > 1 {
format!("{}_{}", base_mesh_name, primitive_index)
} else {
base_mesh_name.clone()
};
let primitive_name = format!("{}::{}", namespace, base_primitive_name);
let gltf_material_index = primitive.material().index().unwrap_or(0);
let material_index = gltf_material_index_to_deduplicated_index
.get(&gltf_material_index)
.copied()
.unwrap_or(0);
primitive_names.push((primitive_name.clone(), material_index));
meshes.insert(primitive_name, mesh_primitive);
}
}
if !primitive_names.is_empty() {
mesh_index_to_primitives.insert(mesh.index(), primitive_names);
}
}
let mut prefabs = Vec::new();
for scene in doc.scenes() {
let scene_name = scene
.name()
.unwrap_or(&format!("Scene_{}", scene.index()))
.to_string();
let mut root_nodes = Vec::new();
for node in scene.nodes() {
convert_node_to_prefab_nodes(
&node,
&mesh_index_to_primitives,
&materials,
&mut root_nodes,
);
}
prefabs.push(Prefab {
name: scene_name,
root_nodes,
});
}
for image in doc.images() {
if let Some(image_data) = images.get(image.index()) {
let base_name = image
.name()
.unwrap_or(&format!("Image_{}", image.index()))
.to_string();
let name = format!("{}::{}", namespace, base_name);
let (width, height) = match image_data.format {
gltf::image::Format::R8G8B8 => {
let mut rgba_data = Vec::with_capacity(image_data.pixels.len() / 3 * 4);
for chunk in image_data.pixels.chunks(3) {
rgba_data.push(chunk[0]);
rgba_data.push(chunk[1]);
rgba_data.push(chunk[2]);
rgba_data.push(255);
}
textures.insert(name, (rgba_data, image_data.width, image_data.height));
continue;
}
gltf::image::Format::R8G8B8A8 => (image_data.width, image_data.height),
_ => {
tracing::warn!("Unsupported texture format for {}", name);
continue;
}
};
textures.insert(name, (image_data.pixels.clone(), width, height));
}
}
let animations: Vec<AnimationClip> = doc
.animations()
.map(|animation| load_gltf_animation(&animation, buffers, &node_to_morph_target_count))
.collect();
let skins: Vec<GltfSkin> = doc
.skins()
.map(|skin| load_gltf_skin(&skin, buffers))
.collect();
let mut node_to_skin: HashMap<usize, usize> = HashMap::new();
for node in doc.nodes() {
if let Some(skin) = node.skin() {
node_to_skin.insert(node.index(), skin.index());
}
}
let node_count = doc.nodes().count();
Ok(GltfLoadResult {
prefabs,
meshes,
materials,
textures,
animations,
skins,
node_to_skin,
node_to_morph_target_count,
node_count,
})
}
fn convert_node_to_prefab_nodes(
node: &Node,
mesh_index_to_primitives: &HashMap<usize, Vec<(String, usize)>>,
materials: &[Material],
output_nodes: &mut Vec<PrefabNode>,
) {
let (translation, rotation, scale) = node.transform().decomposed();
let quat = Quat::new(rotation[3], rotation[0], rotation[1], rotation[2]);
let local_transform = LocalTransform {
translation: Vec3::new(translation[0], translation[1], translation[2]),
rotation: quat,
scale: Vec3::new(scale[0], scale[1], scale[2]),
};
let node_name = node.name().map(|s| s.to_string());
let node_skin_index = node.skin().map(|s| s.index());
if let Some(mesh) = node.mesh() {
if let Some(primitives) = mesh_index_to_primitives.get(&mesh.index()) {
if primitives.len() == 1 {
let (mesh_name, material_index) = &primitives[0];
let mut components = PrefabComponents::default();
if let Some(name_str) = &node_name {
components.name = Some(Name(name_str.clone()));
}
components.render_mesh = Some(RenderMesh::new(mesh_name.clone()));
components.material = Some(
materials
.get(*material_index)
.cloned()
.unwrap_or_else(Material::default),
);
if let Some(camera) = node.camera() {
components.camera = Some(load_gltf_camera(&camera));
}
components.visibility = Some(Visibility { visible: true });
components.skin_index = node_skin_index;
let mut children = Vec::new();
for child in node.children() {
convert_node_to_prefab_nodes(
&child,
mesh_index_to_primitives,
materials,
&mut children,
);
}
output_nodes.push(PrefabNode {
local_transform,
components,
children,
node_index: Some(node.index()),
});
} else {
let mut parent_components = PrefabComponents::default();
if let Some(name_str) = &node_name {
parent_components.name = Some(Name(name_str.clone()));
}
if let Some(camera) = node.camera() {
parent_components.camera = Some(load_gltf_camera(&camera));
}
parent_components.skin_index = node_skin_index;
let mut children = Vec::new();
for (index, (mesh_name, material_index)) in primitives.iter().enumerate() {
let mut primitive_components = PrefabComponents {
name: Some(Name(format!("Primitive_{}", index))),
render_mesh: Some(RenderMesh::new(mesh_name.clone())),
visibility: Some(Visibility { visible: true }),
skin_index: node_skin_index,
..Default::default()
};
primitive_components.material = Some(
materials
.get(*material_index)
.cloned()
.unwrap_or_else(Material::default),
);
children.push(PrefabNode {
local_transform: LocalTransform::default(),
components: primitive_components,
children: Vec::new(),
node_index: None,
});
}
for child in node.children() {
convert_node_to_prefab_nodes(
&child,
mesh_index_to_primitives,
materials,
&mut children,
);
}
output_nodes.push(PrefabNode {
local_transform,
components: parent_components,
children,
node_index: Some(node.index()),
});
}
} else {
let mut components = PrefabComponents::default();
if let Some(name_str) = &node_name {
components.name = Some(Name(name_str.clone()));
}
if let Some(camera) = node.camera() {
components.camera = Some(load_gltf_camera(&camera));
}
components.skin_index = node_skin_index;
let mut children = Vec::new();
for child in node.children() {
convert_node_to_prefab_nodes(
&child,
mesh_index_to_primitives,
materials,
&mut children,
);
}
output_nodes.push(PrefabNode {
local_transform,
components,
children,
node_index: Some(node.index()),
});
}
} else {
let mut components = PrefabComponents::default();
if let Some(name_str) = &node_name {
components.name = Some(Name(name_str.clone()));
}
if let Some(camera) = node.camera() {
components.camera = Some(load_gltf_camera(&camera));
}
components.skin_index = node_skin_index;
let mut children = Vec::new();
for child in node.children() {
convert_node_to_prefab_nodes(
&child,
mesh_index_to_primitives,
materials,
&mut children,
);
}
output_nodes.push(PrefabNode {
local_transform,
components,
children,
node_index: Some(node.index()),
});
}
}
fn load_gltf_camera(camera: &gltf::Camera<'_>) -> Camera {
let projection = match camera.projection() {
gltf::camera::Projection::Perspective(persp) => {
Projection::Perspective(PerspectiveCamera {
aspect_ratio: persp.aspect_ratio(),
y_fov_rad: persp.yfov(),
z_far: persp.zfar(),
z_near: persp.znear(),
})
}
gltf::camera::Projection::Orthographic(ortho) => {
Projection::Orthographic(OrthographicCamera {
x_mag: ortho.xmag(),
y_mag: ortho.ymag(),
z_far: ortho.zfar(),
z_near: ortho.znear(),
})
}
};
Camera {
projection,
smoothing: None,
}
}
fn load_gltf_primitive(
primitive: &Primitive,
buffers: &[buffer::Data],
default_morph_weights: Option<&[f32]>,
) -> Option<Mesh> {
let reader = primitive.reader(|buffer| buffers.get(buffer.index()).map(|b| &b.0[..]));
let positions: Vec<Vec3> = reader
.read_positions()?
.map(|p| vec3(p[0], p[1], p[2]))
.collect();
if positions.is_empty() {
tracing::warn!("glTF primitive has no positions, skipping");
return None;
}
let normals: Vec<Vec3> = if let Some(normals_iter) = reader.read_normals() {
let normals: Vec<_> = normals_iter.map(|n| vec3(n[0], n[1], n[2])).collect();
if normals.len() != positions.len() {
tracing::warn!("glTF primitive has mismatched normal count, generating normals");
generate_normals(&positions, &reader.read_indices())
} else {
normals
}
} else {
generate_normals(&positions, &reader.read_indices())
};
let mut texcoords: Vec<Vec2> = vec![vec2(0.0, 0.0); positions.len()];
if let Some(tex_iter) = reader.read_tex_coords(0) {
let coords: Vec<_> = tex_iter.into_f32().map(|t| vec2(t[0], t[1])).collect();
if coords.len() == positions.len() {
texcoords = coords;
} else {
tracing::warn!("glTF primitive has mismatched texcoord count, using defaults");
}
}
let mut texcoords_1: Vec<Vec2> = vec![vec2(0.0, 0.0); positions.len()];
if let Some(tex_iter) = reader.read_tex_coords(1) {
let coords: Vec<_> = tex_iter.into_f32().map(|t| vec2(t[0], t[1])).collect();
if coords.len() == positions.len() {
texcoords_1 = coords;
}
}
let colors: Vec<[f32; 4]> = if let Some(color_iter) = reader.read_colors(0) {
let color_data: Vec<[f32; 4]> = color_iter.into_rgba_f32().collect();
if color_data.len() == positions.len() {
color_data
} else {
vec![[1.0, 1.0, 1.0, 1.0]; positions.len()]
}
} else {
vec![[1.0, 1.0, 1.0, 1.0]; positions.len()]
};
let tangents: Vec<[f32; 4]> = if let Some(tangent_iter) = reader.read_tangents() {
let tangent_data: Vec<[f32; 4]> = tangent_iter.collect();
if tangent_data.len() == positions.len() {
tangent_data
} else {
vec![[1.0, 0.0, 0.0, 1.0]; positions.len()]
}
} else {
vec![[1.0, 0.0, 0.0, 1.0]; positions.len()]
};
let has_joints = reader.read_joints(0).is_some();
let skin_data = if has_joints {
let mut joint_indices: Vec<[u32; 4]> = vec![[0, 0, 0, 0]; positions.len()];
if let Some(joints_iter) = reader.read_joints(0) {
let joints: Vec<[u16; 4]> = joints_iter.into_u16().collect();
if joints.len() == positions.len() {
for (vertex_index, joints) in joints.iter().enumerate() {
joint_indices[vertex_index] = [
joints[0] as u32,
joints[1] as u32,
joints[2] as u32,
joints[3] as u32,
];
}
}
}
let joint_weights: Vec<[f32; 4]> = if let Some(weights_iter) = reader.read_weights(0) {
let weights: Vec<[f32; 4]> = weights_iter.into_f32().collect();
if weights.len() == positions.len() {
weights
.into_iter()
.map(|w| {
let sum = w[0] + w[1] + w[2] + w[3];
if sum > 0.0 {
[w[0] / sum, w[1] / sum, w[2] / sum, w[3] / sum]
} else {
[1.0, 0.0, 0.0, 0.0]
}
})
.collect()
} else {
vec![[1.0, 0.0, 0.0, 0.0]; positions.len()]
}
} else {
vec![[1.0, 0.0, 0.0, 0.0]; positions.len()]
};
let skinned_vertices: Vec<SkinnedVertex> = positions
.iter()
.zip(normals.iter())
.zip(texcoords.iter())
.zip(texcoords_1.iter())
.zip(tangents.iter())
.zip(colors.iter())
.zip(joint_indices.iter())
.zip(joint_weights.iter())
.map(
|(
(
(((((position, normal), tex_coords), tex_coords_1), tangent), color),
joint_indices,
),
joint_weights,
)| {
SkinnedVertex {
position: [position.x, position.y, position.z],
normal: [normal.x, normal.y, normal.z],
tex_coords: [tex_coords.x, tex_coords.y],
tex_coords_1: [tex_coords_1.x, tex_coords_1.y],
tangent: *tangent,
color: *color,
joint_indices: *joint_indices,
joint_weights: *joint_weights,
}
},
)
.collect();
Some(SkinData::new(skinned_vertices))
} else {
None
};
let indices: Vec<u32> = if let Some(indices_iter) = reader.read_indices() {
let mut indices: Vec<u32> = indices_iter.into_u32().collect();
if !indices.len().is_multiple_of(3) {
tracing::warn!("glTF primitive has non-triangular indices, truncating");
indices.truncate((indices.len() / 3) * 3);
}
indices
} else if positions.len().is_multiple_of(3) {
(0..positions.len() as u32).collect()
} else {
tracing::warn!(
"glTF primitive without indices has non-triangular vertex count, truncating"
);
let vertex_count = (positions.len() / 3) * 3;
(0..vertex_count as u32).collect()
};
if indices.is_empty() {
tracing::warn!("glTF primitive has no valid indices, skipping");
return None;
}
if positions.len() != normals.len() || positions.len() != texcoords.len() {
tracing::error!("glTF primitive has inconsistent vertex attribute lengths");
return None;
}
let vertices: Vec<Vertex> = positions
.iter()
.zip(&normals)
.zip(&texcoords)
.zip(&texcoords_1)
.zip(&tangents)
.zip(&colors)
.map(
|(((((position, normal), tex_coords), tex_coords_1), tangent), color)| Vertex {
position: [position.x, position.y, position.z],
normal: [normal.x, normal.y, normal.z],
tex_coords: [tex_coords.x, tex_coords.y],
tex_coords_1: [tex_coords_1.x, tex_coords_1.y],
tangent: *tangent,
color: *color,
},
)
.collect();
let mut min = nalgebra_glm::vec3(f32::MAX, f32::MAX, f32::MAX);
let mut max = nalgebra_glm::vec3(f32::MIN, f32::MIN, f32::MIN);
for position in &positions {
min.x = min.x.min(position.x);
min.y = min.y.min(position.y);
min.z = min.z.min(position.z);
max.x = max.x.max(position.x);
max.y = max.y.max(position.y);
max.z = max.z.max(position.z);
}
let center = (min + max) * 0.5;
let half_extents = (max - min) * 0.5;
let sphere_radius = nalgebra_glm::length(&half_extents);
let bounding_volume = crate::ecs::bounding_volume::components::BoundingVolume {
obb: crate::ecs::bounding_volume::components::OrientedBoundingBox {
center,
half_extents,
orientation: nalgebra_glm::quat_identity(),
},
sphere_radius,
};
let morph_targets = load_morph_targets(
primitive,
buffers,
&positions,
&normals,
default_morph_weights,
);
let mut mesh = Mesh::with_bounding_volume(vertices, indices, bounding_volume);
mesh.skin_data = skin_data;
mesh.morph_targets = morph_targets;
Some(mesh)
}
fn load_morph_targets(
primitive: &Primitive,
buffers: &[buffer::Data],
positions: &[Vec3],
normals: &[Vec3],
default_weights: Option<&[f32]>,
) -> Option<MorphTargetData> {
let reader = primitive.reader(|buffer| buffers.get(buffer.index()).map(|b| &b.0[..]));
let vertex_count = positions.len();
let morph_target_iter = reader.read_morph_targets();
let targets: Vec<MorphTarget> = morph_target_iter
.filter_map(|morph_target| {
let position_displacements: Vec<[f32; 3]> =
morph_target.0?.map(|p| [p[0], p[1], p[2]]).collect();
if position_displacements.len() != vertex_count {
return None;
}
let mut target = MorphTarget::new(position_displacements);
if let Some(normals_iter) = morph_target.1 {
let normal_displacements: Vec<[f32; 3]> =
normals_iter.map(|n| [n[0], n[1], n[2]]).collect();
if normal_displacements.len() == vertex_count {
target = target.with_normals(normal_displacements);
}
}
if let Some(tangents_iter) = morph_target.2 {
let tangent_displacements: Vec<[f32; 3]> =
tangents_iter.map(|t| [t[0], t[1], t[2]]).collect();
if tangent_displacements.len() == vertex_count {
target = target.with_tangents(tangent_displacements);
}
}
Some(target)
})
.collect();
if targets.is_empty() {
None
} else {
let base_positions: Vec<[f32; 3]> = positions.iter().map(|p| [p.x, p.y, p.z]).collect();
let base_normals: Vec<[f32; 3]> = normals.iter().map(|n| [n.x, n.y, n.z]).collect();
let weights = match default_weights {
Some(w) => w.iter().take(targets.len()).copied().collect(),
None => vec![0.0; targets.len()],
};
Some(
MorphTargetData::new(targets)
.with_base_data(base_positions, base_normals)
.with_default_weights(weights),
)
}
}
fn generate_normals(
positions: &[Vec3],
indices_reader: &Option<gltf::mesh::util::ReadIndices>,
) -> Vec<Vec3> {
let mut normals = vec![Vec3::zeros(); positions.len()];
let mut face_counts = vec![0u32; positions.len()];
let indices: Vec<u32> = if let Some(indices_iter) = indices_reader {
indices_iter.clone().into_u32().collect()
} else {
(0..positions.len() as u32).collect()
};
for triangle in indices.chunks(3) {
if triangle.len() != 3 {
continue;
}
let i0 = triangle[0] as usize;
let i1 = triangle[1] as usize;
let i2 = triangle[2] as usize;
if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
continue;
}
let v0 = positions[i0];
let v1 = positions[i1];
let v2 = positions[i2];
let edge1 = v1 - v0;
let edge2 = v2 - v0;
let face_normal = nalgebra_glm::cross(&edge1, &edge2);
let face_normal = if nalgebra_glm::length(&face_normal) > 1e-6 {
nalgebra_glm::normalize(&face_normal)
} else {
vec3(0.0, 1.0, 0.0)
};
normals[i0] += face_normal;
normals[i1] += face_normal;
normals[i2] += face_normal;
face_counts[i0] += 1;
face_counts[i1] += 1;
face_counts[i2] += 1;
}
for (index, normal) in normals.iter_mut().enumerate() {
if face_counts[index] > 0 {
*normal /= face_counts[index] as f32;
let magnitude = nalgebra_glm::length(normal);
if magnitude > 1e-6 {
*normal = nalgebra_glm::normalize(normal);
} else {
*normal = vec3(0.0, 1.0, 0.0);
}
} else {
*normal = vec3(0.0, 1.0, 0.0);
}
}
normals
}
fn load_gltf_animation(
animation: &gltf::Animation,
buffers: &[buffer::Data],
node_to_morph_target_count: &HashMap<usize, usize>,
) -> AnimationClip {
let mut channels = Vec::new();
for (channel_index, channel) in animation.channels().enumerate() {
let reader = channel.reader(|buffer| buffers.get(buffer.index()).map(|b| &b.0[..]));
let target_gltf_node = channel.target().node();
let target_node = target_gltf_node.index();
let target_bone_name = target_gltf_node.name().map(|s| s.to_string());
let target_property = match channel.target().property() {
gltf::animation::Property::Translation => AnimationProperty::Translation,
gltf::animation::Property::Rotation => AnimationProperty::Rotation,
gltf::animation::Property::Scale => AnimationProperty::Scale,
gltf::animation::Property::MorphTargetWeights => AnimationProperty::MorphWeights,
};
let interpolation = match channel.sampler().interpolation() {
gltf::animation::Interpolation::Linear => AnimationInterpolation::Linear,
gltf::animation::Interpolation::Step => AnimationInterpolation::Step,
gltf::animation::Interpolation::CubicSpline => AnimationInterpolation::CubicSpline,
};
let Some(inputs) = reader.read_inputs() else {
tracing::warn!(" Channel {}: skipping - no input data", channel_index);
continue;
};
let input: Vec<f32> = inputs.collect();
let Some(outputs) = reader.read_outputs() else {
tracing::warn!(" Channel {}: skipping - no output data", channel_index);
continue;
};
let output = match outputs {
gltf::animation::util::ReadOutputs::Translations(translations) => {
if interpolation == AnimationInterpolation::CubicSpline {
let all_data: Vec<[f32; 3]> = translations.collect();
let num_keyframes = all_data.len() / 3;
let mut in_tangents = Vec::with_capacity(num_keyframes);
let mut values = Vec::with_capacity(num_keyframes);
let mut out_tangents = Vec::with_capacity(num_keyframes);
for keyframe_index in 0..num_keyframes {
let base_idx = keyframe_index * 3;
in_tangents.push(vec3(
all_data[base_idx][0],
all_data[base_idx][1],
all_data[base_idx][2],
));
values.push(vec3(
all_data[base_idx + 1][0],
all_data[base_idx + 1][1],
all_data[base_idx + 1][2],
));
out_tangents.push(vec3(
all_data[base_idx + 2][0],
all_data[base_idx + 2][1],
all_data[base_idx + 2][2],
));
}
AnimationSamplerOutput::CubicSplineVec3 {
values,
in_tangents,
out_tangents,
}
} else {
let values = translations.map(|t| vec3(t[0], t[1], t[2])).collect();
AnimationSamplerOutput::Vec3(values)
}
}
gltf::animation::util::ReadOutputs::Rotations(rotations) => {
if interpolation == AnimationInterpolation::CubicSpline {
let all_data: Vec<[f32; 4]> = rotations.into_f32().collect();
let num_keyframes = all_data.len() / 3;
let mut in_tangents = Vec::with_capacity(num_keyframes);
let mut values = Vec::with_capacity(num_keyframes);
let mut out_tangents = Vec::with_capacity(num_keyframes);
for keyframe_index in 0..num_keyframes {
let base_idx = keyframe_index * 3;
in_tangents.push(Quat::new(
all_data[base_idx][3],
all_data[base_idx][0],
all_data[base_idx][1],
all_data[base_idx][2],
));
values.push(
Quat::new(
all_data[base_idx + 1][3],
all_data[base_idx + 1][0],
all_data[base_idx + 1][1],
all_data[base_idx + 1][2],
)
.normalize(),
);
out_tangents.push(Quat::new(
all_data[base_idx + 2][3],
all_data[base_idx + 2][0],
all_data[base_idx + 2][1],
all_data[base_idx + 2][2],
));
}
AnimationSamplerOutput::CubicSplineQuat {
values,
in_tangents,
out_tangents,
}
} else {
let values = rotations
.into_f32()
.map(|r| Quat::new(r[3], r[0], r[1], r[2]).normalize())
.collect();
AnimationSamplerOutput::Quat(values)
}
}
gltf::animation::util::ReadOutputs::Scales(scales) => {
if interpolation == AnimationInterpolation::CubicSpline {
let all_data: Vec<[f32; 3]> = scales.collect();
let num_keyframes = all_data.len() / 3;
let mut in_tangents = Vec::with_capacity(num_keyframes);
let mut values = Vec::with_capacity(num_keyframes);
let mut out_tangents = Vec::with_capacity(num_keyframes);
for keyframe_index in 0..num_keyframes {
let base_idx = keyframe_index * 3;
in_tangents.push(vec3(
all_data[base_idx][0],
all_data[base_idx][1],
all_data[base_idx][2],
));
values.push(vec3(
all_data[base_idx + 1][0],
all_data[base_idx + 1][1],
all_data[base_idx + 1][2],
));
out_tangents.push(vec3(
all_data[base_idx + 2][0],
all_data[base_idx + 2][1],
all_data[base_idx + 2][2],
));
}
AnimationSamplerOutput::CubicSplineVec3 {
values,
in_tangents,
out_tangents,
}
} else {
let values = scales.map(|s| vec3(s[0], s[1], s[2])).collect();
AnimationSamplerOutput::Vec3(values)
}
}
gltf::animation::util::ReadOutputs::MorphTargetWeights(weights) => {
let weight_count = node_to_morph_target_count
.get(&target_node)
.copied()
.unwrap_or(1);
if weight_count == 0 {
continue;
}
if interpolation == AnimationInterpolation::CubicSpline {
let all_data: Vec<f32> = weights.into_f32().collect();
let elements_per_keyframe = weight_count * 3;
if !all_data.len().is_multiple_of(elements_per_keyframe) {
continue;
}
let num_keyframes = all_data.len() / elements_per_keyframe;
let mut in_tangents = Vec::with_capacity(num_keyframes);
let mut values = Vec::with_capacity(num_keyframes);
let mut out_tangents = Vec::with_capacity(num_keyframes);
for keyframe_index in 0..num_keyframes {
let base_idx = keyframe_index * elements_per_keyframe;
let in_tangent: Vec<f32> =
all_data[base_idx..base_idx + weight_count].to_vec();
let value: Vec<f32> =
all_data[base_idx + weight_count..base_idx + weight_count * 2].to_vec();
let out_tangent: Vec<f32> = all_data
[base_idx + weight_count * 2..base_idx + weight_count * 3]
.to_vec();
in_tangents.push(in_tangent);
values.push(value);
out_tangents.push(out_tangent);
}
AnimationSamplerOutput::CubicSplineWeights {
values,
in_tangents,
out_tangents,
}
} else {
let all_data: Vec<f32> = weights.into_f32().collect();
if !all_data.len().is_multiple_of(weight_count) {
continue;
}
let values: Vec<Vec<f32>> = all_data
.chunks(weight_count)
.map(|chunk| chunk.to_vec())
.collect();
AnimationSamplerOutput::Weights(values)
}
}
};
let sampler = AnimationSampler {
input,
output,
interpolation,
};
channels.push(AnimationChannel {
target_node,
target_bone_name,
target_property,
sampler,
});
}
let duration = channels
.iter()
.flat_map(|channel| channel.sampler.input.last())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.copied()
.unwrap_or(0.0);
AnimationClip {
name: animation.name().unwrap_or("Animation").to_string(),
duration,
channels,
}
}
fn load_gltf_skin(skin: &gltf::Skin, buffers: &[buffer::Data]) -> GltfSkin {
let joints: Vec<usize> = skin.joints().map(|j| j.index()).collect();
let mut inverse_bind_matrices = Vec::new();
if let Some(accessor) = skin.inverse_bind_matrices() {
let view = accessor
.view()
.expect("Inverse bind matrices accessor has no view");
let buffer_index = view.buffer().index();
if let Some(buffer_data) = buffers.get(buffer_index) {
let buffer_bytes = &buffer_data.0;
let view_offset = view.offset();
let accessor_offset = accessor.offset();
let start = view_offset + accessor_offset;
let stride = view.stride().unwrap_or(64);
for joint_index in 0..accessor.count() {
let matrix_start = start + joint_index * stride;
if matrix_start + 64 <= buffer_bytes.len() {
let mut floats = [0.0f32; 16];
for (float_index, float_value) in floats.iter_mut().enumerate() {
let byte_offset = matrix_start + float_index * 4;
let bytes = &buffer_bytes[byte_offset..byte_offset + 4];
*float_value = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
}
let matrix = Mat4::new(
floats[0], floats[4], floats[8], floats[12], floats[1], floats[5],
floats[9], floats[13], floats[2], floats[6], floats[10], floats[14],
floats[3], floats[7], floats[11], floats[15],
);
inverse_bind_matrices.push(matrix);
}
}
}
}
while inverse_bind_matrices.len() < joints.len() {
inverse_bind_matrices.push(Mat4::identity());
}
GltfSkin {
name: skin.name().map(|s| s.to_string()),
joints,
inverse_bind_matrices,
}
}