use animation;
use color;
use geometry;
use gltf;
use material;
use mint;
use std::collections::HashMap;
use camera::{Orthographic, Perspective, Projection};
use std::path::Path;
use {Material, Texture};
use geometry::{Geometry, Shape};
use image::{DynamicImage, ImageBuffer};
use node::Transform;
use super::Factory;
use template::{
AnimationTemplate,
BoneTemplate,
CameraTemplate,
InstancedGeometry,
MeshTemplate,
ObjectTemplate,
Template,
};
fn load_textures(
factory: &mut Factory,
document: &gltf::Document,
images: Vec<gltf::image::Data>,
) -> Vec<Texture<[f32; 4]>> {
let mut textures = Vec::new();
for (texture, data) in document.textures().zip(images.into_iter()) {
let (width, height) = (data.width, data.height);
let image = match data.format {
gltf::image::Format::R8 => DynamicImage::ImageLuma8(
ImageBuffer::from_raw(
width,
height,
data.pixels,
).expect("incorrect image dimensions")
),
gltf::image::Format::R8G8 => DynamicImage::ImageLumaA8(
ImageBuffer::from_raw(
width,
height,
data.pixels,
).expect("incorrect image dimensions")
),
gltf::image::Format::R8G8B8 => DynamicImage::ImageRgb8(
ImageBuffer::from_raw(
width,
height,
data.pixels,
).expect("incorrect image dimensions")
),
gltf::image::Format::R8G8B8A8 => DynamicImage::ImageRgba8(
ImageBuffer::from_raw(
width,
height,
data.pixels,
).unwrap()
),
}.to_rgba();
use {FilterMethod, WrapMode};
use gltf::texture::{MagFilter, WrappingMode};
let params = texture.sampler();
let mag_filter = match params.mag_filter() {
None | Some(MagFilter::Nearest) => FilterMethod::Scale,
Some(MagFilter::Linear) => FilterMethod::Bilinear,
};
let wrap_s = match params.wrap_s() {
WrappingMode::ClampToEdge => WrapMode::Clamp,
WrappingMode::MirroredRepeat => WrapMode::Mirror,
WrappingMode::Repeat => WrapMode::Tile,
};
let wrap_t = match params.wrap_t() {
WrappingMode::ClampToEdge => WrapMode::Clamp,
WrappingMode::MirroredRepeat => WrapMode::Mirror,
WrappingMode::Repeat => WrapMode::Tile,
};
let sampler = factory.sampler(mag_filter, wrap_s, wrap_t);
let texture = factory.load_texture_from_memory(width as u16, height as u16, &image, sampler);
textures.push(texture);
}
textures
}
fn load_material<'a>(
mat: gltf::Material<'a>,
textures: &[Texture<[f32; 4]>],
) -> Material {
let pbr = mat.pbr_metallic_roughness();
let mut is_basic_material = true;
let base_color_map = pbr.base_color_texture()
.map(|t| textures[t.as_ref().index()].clone());
let normal_map = mat.normal_texture().map(|t| {
is_basic_material = false;
textures[t.as_ref().index()].clone()
});
let emissive_map = mat.emissive_texture().map(|t| {
is_basic_material = false;
textures[t.as_ref().index()].clone()
});
let metallic_roughness_map = pbr.metallic_roughness_texture().map(|t| {
is_basic_material = false;
textures[t.as_ref().index()].clone()
});
let occlusion_map = mat.occlusion_texture().map(|t| {
is_basic_material = false;
textures[t.as_ref().index()].clone()
});
let (base_color_factor, base_color_alpha) = {
let x = pbr.base_color_factor();
(color::from_linear_rgb([x[0], x[1], x[2]]), x[3])
};
if false { material::Basic {
color: base_color_factor,
map: base_color_map,
}.into()
} else {
material::Pbr {
base_color_factor,
base_color_alpha,
metallic_factor: pbr.metallic_factor(),
roughness_factor: pbr.roughness_factor(),
occlusion_strength: mat.occlusion_texture().map_or(1.0, |t| {
t.strength()
}),
emissive_factor: color::from_linear_rgb(mat.emissive_factor()),
normal_scale: mat.normal_texture().map_or(1.0, |t| {
t.scale()
}),
base_color_map,
normal_map,
emissive_map,
metallic_roughness_map,
occlusion_map,
}.into()
}
}
fn load_primitive<'a>(
factory: &mut Factory,
primitive: gltf::Primitive<'a>,
buffers: &[gltf::buffer::Data],
textures: &[Texture<[f32; 4]>],
) -> (InstancedGeometry, Material) {
use itertools::Itertools;
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()].0));
let mut faces = vec![];
if let Some(iter) = reader.read_indices() {
faces.extend(iter.into_u32().tuples().map(|(a, b, c)| [a, b, c]));
}
let vertices: Vec<mint::Point3<f32>> = reader
.read_positions()
.unwrap()
.map(|x| x.into())
.collect();
let normals = if let Some(iter) = reader.read_normals() {
iter.map(|x| x.into()).collect()
} else {
Vec::new()
};
let tangents = if let Some(iter) = reader.read_tangents() {
iter.map(|x| x.into()).collect()
} else {
Vec::new()
};
let tex_coords = if let Some(iter) = reader.read_tex_coords(0) {
iter.into_f32().map(|x| x.into()).collect()
} else {
Vec::new()
};
let joint_indices = if let Some(iter) = reader.read_joints(0) {
iter.into_u16()
.map(|x| [x[0] as i32, x[1] as i32, x[2] as i32, x[3] as i32])
.collect()
} else {
Vec::new()
};
let joint_weights = if let Some(iter) = reader.read_weights(0) {
iter.into_f32().collect()
} else {
Vec::new()
};
let shapes = {
reader
.read_morph_targets()
.map(|(positions, normals, tangents)| {
let mut shape = Shape::default();
if let Some(iter) = positions {
shape.vertices.extend(iter.map(mint::Point3::<f32>::from));
}
if let Some(iter) = normals {
shape.normals.extend(iter.map(mint::Vector3::<f32>::from));
}
if let Some(iter) = tangents {
shape.tangents.extend(iter.map(|v| mint::Vector4{ x: v[0], y: v[1], z: v[2], w: 1.0 }));
}
shape
})
.collect()
};
let geometry = Geometry {
base: Shape {
vertices,
normals,
tangents,
},
tex_coords,
faces,
shapes,
joints: geometry::Joints {
indices: joint_indices,
weights: joint_weights,
},
};
let geometry = factory.upload_geometry(geometry);
let material = load_material(primitive.material(), textures);
(geometry, material)
}
fn load_skin<'a>(
skin: gltf::Skin<'a>,
objects: &mut Vec<ObjectTemplate>,
bones: &mut Vec<BoneTemplate>,
buffers: &[gltf::buffer::Data],
) -> usize {
use std::iter::repeat;
let reader = skin.reader(|buffer| Some(&buffers[buffer.index()].0));
let mut ibms = Vec::new();
if let Some(iter) = reader.read_inverse_bind_matrices() {
for ibm in iter {
ibms.push(ibm.into());
}
}
let mx_id = mint::ColumnMatrix4::from([
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.],
]);
let ibm_iter = ibms.
into_iter()
.chain(repeat(mx_id));
let joint_iter = skin
.joints()
.map(|joint| joint.index());
for (index, (joint_index, inverse_bind_matrix)) in joint_iter.zip(ibm_iter).enumerate() {
let object = objects.len();
objects.push(ObjectTemplate {
parent: Some(joint_index),
.. Default::default()
});
bones.push(BoneTemplate {
object,
index,
inverse_bind_matrix,
skeleton: skin.index(),
});
}
let object = objects.len();
objects.push(ObjectTemplate {
parent: skin.skeleton().map(|node| node.index()),
.. Default::default()
});
object
}
fn load_animation<'a>(
animation: gltf::Animation<'a>,
buffers: &[gltf::buffer::Data],
groups: &[usize],
) -> AnimationTemplate {
use gltf::animation::Interpolation::*;
let mut tracks = Vec::new();
let name = animation.name().map(str::to_string);
for channel in animation.channels() {
let sampler = channel.sampler();
let target = channel.target();
let node = target.node();
let interpolation = match sampler.interpolation() {
Linear => animation::Interpolation::Linear,
Step => animation::Interpolation::Discrete,
CubicSpline => animation::Interpolation::Cubic,
CatmullRomSpline => animation::Interpolation::Cubic,
};
use animation::{Binding, Track, Values};
let reader = channel.reader(|buffer| Some(&buffers[buffer.index()].0));
let times: Vec<f32> = reader.read_inputs().unwrap().collect();
let (binding, values) = match reader.read_outputs().unwrap() {
gltf::animation::util::ReadOutputs::Translations(iter) => {
let values = iter
.map(|v| mint::Vector3::from(v))
.collect::<Vec<_>>();
assert_eq!(values.len(), times.len());
(Binding::Position, Values::Vector3(values))
}
gltf::animation::util::ReadOutputs::Rotations(rotations) => {
let values = rotations
.into_f32()
.map(|r| mint::Quaternion::from(r))
.collect::<Vec<_>>();
assert_eq!(values.len(), times.len());
(Binding::Orientation, Values::Quaternion(values))
}
gltf::animation::util::ReadOutputs::Scales(iter) => {
let values = iter.map(|s| s[1]).collect::<Vec<_>>();
assert_eq!(values.len(), times.len());
(Binding::Scale, Values::Scalar(values))
}
gltf::animation::util::ReadOutputs::MorphTargetWeights(weights) => {
let num_targets = node
.mesh()
.unwrap()
.primitives()
.next()
.unwrap()
.morph_targets()
.len();
let mut values = vec![0.0; times.len() * num_targets];
let raw = weights.into_f32().collect::<Vec<_>>();
for (i, chunk) in raw.chunks(num_targets).enumerate() {
for (j, value) in chunk.iter().enumerate() {
values[j * times.len() + i] = *value;
}
}
(Binding::Weights, Values::Scalar(values))
}
};
tracks.push((
Track {
binding,
interpolation,
times,
values,
},
groups[node.index()],
));
}
AnimationTemplate {
name,
tracks,
}
}
fn load_node<'a>(
node: gltf::Node<'a>,
objects: &mut Vec<ObjectTemplate>,
meshes: &mut Vec<MeshTemplate>,
cameras: &mut Vec<CameraTemplate>,
mesh_map: &HashMap<usize, Vec<usize>>,
primitives: &[(InstancedGeometry, Material)],
) -> usize {
let name = node.name().map(Into::into);
let (translation, rotation, scale) = node.transform().decomposed();
let scale = scale[1];
let object_index = objects.len();
objects.push(ObjectTemplate {
name,
transform: Transform {
position: translation.into(),
orientation: rotation.into(),
scale,
},
parent: None,
});
let skeleton = node.skin().map(|skin| skin.index());
if let Some(gltf_mesh) = node.mesh() {
for &geometry_index in &mesh_map[&gltf_mesh.index()] {
let (geometry, material) = primitives[geometry_index].clone();
let object = objects.len();
objects.push(ObjectTemplate {
parent: Some(node.index()),
.. Default::default()
});
meshes.push(MeshTemplate {
object,
geometry,
material,
skeleton,
});
}
}
if let Some(camera) = node.camera() {
let object = objects.len();
objects.push(ObjectTemplate {
parent: Some(node.index()),
.. Default::default()
});
cameras.push(CameraTemplate {
object,
projection: load_camera(camera),
});
}
object_index
}
fn load_camera<'a>(
entry: gltf::Camera<'a>,
) -> Projection {
match entry.projection() {
gltf::camera::Projection::Orthographic(values) => {
let center = mint::Point2::<f32>::from([0.0, 0.0]);
let extent_y = values.ymag();
let range = values.znear() .. values.zfar();
Projection::Orthographic(Orthographic { center, extent_y, range })
}
gltf::camera::Projection::Perspective(values) => {
let fov_y = values.yfov().to_degrees();
let near = values.znear();
let zrange = match values.zfar() {
Some(far) => (near .. far).into(),
None => (near ..).into(),
};
Projection::Perspective(Perspective { fov_y, zrange })
}
}
}
fn load_scene<'a>(scene: gltf::Scene<'a>, raw: &Template) -> Template {
Template {
name: scene.name().map(Into::into),
.. raw.clone()
}
}
impl super::Factory {
pub fn load_gltf(
&mut self,
path_str: &str,
) -> Vec<Template> {
info!("Loading glTF file {}", path_str);
let path = Path::new(path_str);
let (gltf, buffers, images) = gltf::import(path)
.expect("invalid glTF 2.0");
let textures = load_textures(self, &gltf, images);
let mut mesh_map = HashMap::new();
let mut primitives = Vec::new();
for gltf_mesh in gltf.meshes() {
let gltf_index = gltf_mesh.index();
let mut indices = Vec::new();
let prim_iter = gltf_mesh
.primitives()
.map(|prim| load_primitive(self, prim, &buffers, &textures));
for primitive in prim_iter {
indices.push(primitives.len());
primitives.push(primitive);
}
mesh_map.insert(gltf_index, indices);
}
let mut objects = Vec::with_capacity(gltf.nodes().len());
let mut meshes = Vec::new();
let mut cameras = Vec::new();
let groups: Vec<_> = gltf
.nodes()
.map(|node| {
load_node(node, &mut objects, &mut meshes, &mut cameras, &mesh_map, &primitives)
})
.collect();
for gltf_node in gltf.nodes() {
for child_index in gltf_node.children().map(|child| child.index()) {
let object = &mut objects[groups[child_index]];
assert!(object.parent.is_none(), "Object template already had a parent specified");
object.parent = Some(gltf_node.index());
}
}
let mut bones = Vec::new();
let skeletons = gltf
.skins()
.map(|skin| load_skin(skin, &mut objects, &mut bones, &buffers))
.collect();
let animations = gltf
.animations()
.map(|anim| load_animation(anim, &buffers, &groups))
.collect();
let raw_template = Template {
name: None,
objects,
groups,
cameras,
meshes,
lights: Vec::new(),
bones,
skeletons,
animations,
};
if gltf.scenes().len() > 1 {
warn!("Mutliple scenes found in {}, glTF loading does not currently work correctly for glTF files with multiple scenes", path.display());
}
gltf
.scenes()
.map(|scene| load_scene(scene, &raw_template))
.collect()
}
}