use super::Buffers;
use crate::{error, GltfSceneOptions};
use amethyst_core::math::{zero, Vector3};
use amethyst_error::Error;
use amethyst_rendy::{
rendy::mesh::{Color, MeshBuilder, Normal, Position, Tangent, TexCoord},
skinning::JointCombined,
};
use log::trace;
use std::{iter::repeat, ops::Range};
fn compute_if<T, F: Fn() -> T>(predicate: bool, func: F) -> Option<T> {
if predicate {
Some(func())
} else {
None
}
}
fn try_compute_if<T, F: Fn() -> Option<T>>(predicate: bool, func: F) -> Option<T> {
if predicate {
func()
} else {
None
}
}
enum Indices {
None,
U16(Vec<u16>),
U32(Vec<u32>),
}
impl Indices {
fn len(&self) -> Option<usize> {
match self {
Indices::None => None,
Indices::U16(vec) => Some(vec.len()),
Indices::U32(vec) => Some(vec.len()),
}
}
fn map(&self, face: usize, vert: usize) -> usize {
match self {
Indices::None => face * 3 + vert,
Indices::U16(vec) => vec[face * 3 + vert] as usize,
Indices::U32(vec) => vec[face * 3 + vert] as usize,
}
}
}
pub fn load_mesh(
mesh: &gltf::Mesh<'_>,
buffers: &Buffers,
options: &GltfSceneOptions,
) -> Result<Vec<(MeshBuilder<'static>, Option<usize>, Range<[f32; 3]>)>, Error> {
trace!("Loading mesh");
let mut primitives = vec![];
for primitive in mesh.primitives() {
trace!("Loading mesh primitive");
let reader = primitive.reader(|buffer| buffers.buffer(&buffer));
let mut builder = MeshBuilder::new();
trace!("Loading indices");
use gltf::mesh::util::ReadIndices;
let indices = match reader.read_indices() {
Some(ReadIndices::U8(iter)) => Indices::U16(iter.map(u16::from).collect()),
Some(ReadIndices::U16(iter)) => Indices::U16(iter.collect()),
Some(ReadIndices::U32(iter)) => Indices::U32(iter.collect()),
None => Indices::None,
};
trace!("Loading positions");
let positions = reader
.read_positions()
.ok_or(error::Error::MissingPositions)?
.map(Position)
.collect::<Vec<_>>();
let normals = compute_if(options.load_normals || options.load_tangents, || {
trace!("Loading normals");
if let Some(normals) = reader.read_normals() {
normals.map(Normal).collect::<Vec<_>>()
} else {
trace!("Calculating normals");
calculate_normals(&positions, &indices)
}
});
let tex_coords = compute_if(options.load_texcoords || options.load_tangents, || {
trace!("Loading texture coordinates");
if let Some(tex_coords) = reader.read_tex_coords(0).map(|t| t.into_f32()) {
if options.flip_v_coord {
tex_coords
.map(|[u, v]| TexCoord([u, 1. - v]))
.collect::<Vec<_>>()
} else {
tex_coords.map(TexCoord).collect::<Vec<_>>()
}
} else {
let (u, v) = options.generate_tex_coords;
let v = if options.flip_v_coord { v } else { 1.0 - v };
repeat(TexCoord([u, v]))
.take(positions.len())
.collect::<Vec<_>>()
}
});
let tangents = compute_if(options.load_tangents, || {
trace!("Loading tangents");
let tangents = reader.read_tangents();
match tangents {
Some(tangents) => tangents.map(Tangent).collect::<Vec<_>>(),
None => {
trace!("Calculating tangents");
calculate_tangents(
&positions,
normals.as_ref().unwrap(),
tex_coords.as_ref().unwrap(),
&indices,
)
}
}
});
let colors = try_compute_if(options.load_colors, || {
trace!("Loading colors");
if let Some(colors) = reader.read_colors(0) {
Some(colors.into_rgba_f32().map(Color).collect::<Vec<_>>())
} else {
None
}
});
let joints = try_compute_if(options.load_animations, || {
trace!("Loading animations");
if let (Some(ids), Some(weights)) = (reader.read_joints(0), reader.read_weights(0)) {
let zip = ids.into_u16().zip(weights.into_f32());
let joints = zip
.map(|(ids, weights)| JointCombined::new(ids, weights))
.collect::<Vec<_>>();
Some(joints)
} else {
None
}
});
match indices {
Indices::U16(vec) => {
builder.set_indices(vec);
}
Indices::U32(vec) => {
builder.set_indices(vec);
}
Indices::None => {}
};
builder.add_vertices(positions);
normals.map(|v| builder.add_vertices(v));
tangents.map(|v| builder.add_vertices(v));
tex_coords.map(|v| builder.add_vertices(v));
colors.map(|v| builder.add_vertices(v));
joints.map(|v| builder.add_vertices(v));
trace!("Loading bounding box");
let bounds = primitive.bounding_box();
let bounds = bounds.min..bounds.max;
let material = primitive.material().index();
primitives.push((builder, material, bounds));
}
trace!("Loaded mesh");
Ok(primitives)
}
fn calculate_normals(positions: &[Position], indices: &Indices) -> Vec<Normal> {
let mut normals = vec![zero::<Vector3<f32>>(); positions.len()];
let num_faces = indices.len().unwrap_or_else(|| positions.len()) / 3;
for face in 0..num_faces {
let i0 = indices.map(face, 0);
let i1 = indices.map(face, 1);
let i2 = indices.map(face, 2);
let a = Vector3::from(positions[i0].0);
let b = Vector3::from(positions[i1].0);
let c = Vector3::from(positions[i2].0);
let n = (b - a).cross(&(c - a));
normals[i0] += n;
normals[i1] += n;
normals[i2] += n;
}
normals
.into_iter()
.map(|n| Normal(n.normalize().into()))
.collect::<Vec<_>>()
}
fn calculate_tangents(
positions: &[Position],
normals: &[Normal],
tex_coords: &[TexCoord],
indices: &Indices,
) -> Vec<Tangent> {
let mut tangents = vec![Tangent([0.0, 0.0, 0.0, 0.0]); positions.len()];
let num_faces = indices.len().unwrap_or_else(|| positions.len()) / 3;
mikktspace::generate_tangents(
&|| 3,
&|| num_faces,
&|face, vert| &positions[indices.map(face, vert)].0,
&|face, vert| &normals[indices.map(face, vert)].0,
&|face, vert| &tex_coords[indices.map(face, vert)].0,
&mut |face, vert, tangent| {
let [x, y, z, w] = tangent;
tangents[indices.map(face, vert)] = Tangent([x, y, z, -w]);
},
);
tangents
}
#[cfg(test)]
mod tests {
use super::{calculate_tangents, Indices};
use amethyst_rendy::rendy::mesh::{Normal, Position, Tangent, TexCoord};
const POSITIONS: &[Position] = &[
Position([0.0, 0.0, 0.0]),
Position([0.0, 1.0, 0.0]),
Position([1.0, 1.0, 0.0]),
Position([0.0, 1.0, 0.0]),
Position([1.0, 1.0, 0.0]),
Position([1.0, 0.0, 0.0]),
];
const NORMALS: &[Normal] = &[
Normal([0.0, 0.0, 1.0]),
Normal([0.0, 0.0, 1.0]),
Normal([0.0, 0.0, 1.0]),
Normal([0.0, 0.0, 1.0]),
Normal([0.0, 0.0, 1.0]),
Normal([1.0, 0.0, 0.0]),
];
const TEX_COORDS: &[TexCoord] = &[
TexCoord([0.0, 0.0]),
TexCoord([0.0, 1.0]),
TexCoord([1.0, 1.0]),
TexCoord([0.0, 1.0]),
TexCoord([1.0, 1.0]),
TexCoord([1.0, 0.0]),
];
#[test]
fn test_tangent_calc() {
let tangents = calculate_tangents(POSITIONS, NORMALS, TEX_COORDS, &Indices::None);
assert_eq!(
tangents,
vec![
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([0.0, 0.0, 0.0, 1.0]),
]
);
}
#[test]
fn test_indexed_tangent_calc() {
let tangents = calculate_tangents(
POSITIONS,
NORMALS,
TEX_COORDS,
&Indices::U32(vec![3, 4, 5, 0, 1, 2]),
);
assert_eq!(
tangents,
vec![
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([1.0, 0.0, 0.0, 1.0]),
Tangent([0.0, 0.0, 0.0, 1.0]),
]
);
}
}