use {
super::{Canonicalize, Euler, Rotation, Writer, blob::BlobAsset, file_key, re_run_if_changed},
crate::{
MeshId,
index::IndexBuffer,
mesh::{Joint, Mesh, Primitive, Skin, VertexType},
},
anyhow::{Context, bail},
glam::{EulerRot, Mat4, Quat, Vec3, Vec4, vec3},
gltf::{
Buffer, Node,
buffer::Data,
import,
mesh::{Mode, Reader, util::ReadIndices},
},
log::{info, trace, warn},
meshopt::{
SimplifyOptions, VertexDataAdapter, optimize_overdraw_in_place,
optimize_vertex_cache_in_place, quantize_unorm, remap_index_buffer, simplify,
simplify_sloppy, unstripify,
},
ordered_float::OrderedFloat,
parking_lot::Mutex,
serde::{
Deserialize, Deserializer,
de::{SeqAccess, Visitor, value::SeqAccessDeserializer},
},
std::{
collections::{BTreeSet, HashMap, HashSet},
fmt::Formatter,
io::{Error, ErrorKind},
iter::repeat_n,
num::FpCategory,
path::{Path, PathBuf},
sync::Arc,
},
};
fn extract_transform(node: &Node) -> Mat4 {
let (translation, rotation, scale) = node.transform().decomposed();
let translation = Vec3::from_array(translation);
let rotation = Quat::from_array(rotation);
let scale = Vec3::from_array(scale);
Mat4::from_scale_rotation_translation(scale, rotation, translation)
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct MeshAsset {
data: Option<PathBuf>,
euler: Option<Euler>,
flip_x: Option<bool>,
flip_y: Option<bool>,
flip_z: Option<bool>,
ignore_skin: Option<bool>,
lod: Option<bool>,
lod_lock_border: Option<bool>,
lod_target_error: Option<OrderedFloat<f32>>,
max_index: Option<MaxIndex>,
min_lod_triangles: Option<usize>,
name: Option<String>,
normals: Option<bool>,
offset: Option<[OrderedFloat<f32>; 3]>,
optimize: Option<bool>,
overdraw_threshold: Option<OrderedFloat<f32>>,
rotation: Option<Rotation>,
#[serde(default, deserialize_with = "Scale::de")]
scale: Option<Scale>,
scene_name: Option<String>,
shadow: Option<bool>,
src: Option<PathBuf>,
tangents: Option<bool>,
}
impl MeshAsset {
pub const DEFAULT_LOD_MIN: usize = 64;
pub const DEFAULT_LOD_TARGET_ERROR: f32 = 0.05;
pub fn new(src: impl AsRef<Path>) -> Self {
Self {
data: None,
euler: None,
flip_x: None,
flip_y: None,
flip_z: None,
ignore_skin: None,
lod: None,
lod_lock_border: None,
lod_target_error: None,
max_index: None,
min_lod_triangles: None,
name: None,
normals: None,
offset: None,
optimize: None,
overdraw_threshold: None,
rotation: None,
scale: None,
scene_name: None,
shadow: None,
src: Some(src.as_ref().to_path_buf()),
tangents: None,
}
}
pub fn bake(
&self,
writer: &Arc<Mutex<Writer>>,
project_dir: impl AsRef<Path>,
path: Option<impl AsRef<Path>>,
) -> anyhow::Result<MeshId> {
let src = self
.src()
.ok_or(anyhow::Error::msg("unspecified mesh source"))?;
let asset = self.clone().into();
if let Some(id) = writer.lock().ctx.get(&asset) {
return id.as_mesh().context("asset context returned non-mesh id");
}
self.re_run_if_changed();
let key = path.as_ref().map(|path| file_key(&project_dir, path));
if let Some(key) = &key {
info!("Baking mesh: {}", key);
} else {
info!("Baking mesh: {} (inline)", file_key(&project_dir, src));
}
let mut mesh = self
.to_mesh(src)
.map_err(|err| Error::new(ErrorKind::InvalidData, err))
.context("Baking mesh data")?;
if let Some(data) = &self.data {
let data_id = BlobAsset::new(data)
.bake(writer, project_dir)
.context("Baking unstructured mesh data")?;
mesh.set_data(data_id);
}
let mut writer = writer.lock();
if let Some(id) = writer.ctx.get(&asset) {
return id.as_mesh().context("asset context returned non-mesh id");
}
let id = writer.push_mesh(mesh, key);
writer.ctx.insert(asset, id.into());
Ok(id)
}
fn calculate_lods(
&self,
indices: &[u32],
vertex_buf: &[u8],
vertex_stride: usize,
) -> anyhow::Result<Vec<Vec<u32>>> {
let mut res = vec![Vec::from(indices)];
if !self.lod() {
return Ok(res);
}
let target_error = self.lod_target_error();
let target_ratio = 1.0 + target_error;
let min_triangles = self.min_lod_triangles();
let vertices = VertexDataAdapter::new(vertex_buf, vertex_stride, 0)
.context("creating vertex data adapter for LOD calculation")?;
let opts = if self.lod_lock_border() {
SimplifyOptions::LockBorder
} else {
SimplifyOptions::None
};
while let Some(last) = res.last() {
let target_count = (last.len() / 3) >> 1;
if target_count < min_triangles {
break;
}
let lod = simplify(indices, &vertices, target_count, target_error, opts, None);
let lod_count = lod.len() / 3;
let lod_ratio = lod_count as f32 / target_count as f32;
if lod_ratio > target_ratio {
break;
}
res.push(lod);
}
Ok(res)
}
fn convert_triangle_fan_to_list(indices: &mut Vec<u32>) {
if indices.len() < 4 {
return;
}
let anchor = indices[0];
let mut result = Vec::with_capacity((indices.len() - 2) * 3);
result.extend_from_slice(&indices[0..3]);
for i in 3..indices.len() {
result.push(anchor);
result.push(indices[i - 1]);
result.push(indices[i]);
}
*indices = result;
}
fn convert_triangle_strip_to_list(
indices: &mut Vec<u32>,
restart_index: u32,
) -> anyhow::Result<()> {
*indices =
unstripify(indices, restart_index).context("unable to unstripify index buffer")?;
Ok(())
}
pub fn data(&self) -> Option<&Path> {
self.data.as_deref()
}
pub fn lod(&self) -> bool {
self.lod.unwrap_or_default()
}
pub fn lod_lock_border(&self) -> bool {
self.lod_lock_border.unwrap_or_default()
}
pub fn lod_target_error(&self) -> f32 {
self.lod_target_error
.unwrap_or(OrderedFloat(Self::DEFAULT_LOD_TARGET_ERROR))
.0
}
pub fn max_index(&self) -> Option<u32> {
self.max_index.map(MaxIndex::value)
}
pub fn min_lod_triangles(&self) -> usize {
self.min_lod_triangles
.unwrap_or(Self::DEFAULT_LOD_MIN)
.clamp(1, usize::MAX)
}
pub fn normals(&self) -> bool {
self.normals.unwrap_or(true)
}
pub fn offset(&self) -> Vec3 {
self.offset
.map(|offset| vec3(offset[0].0, offset[1].0, offset[2].0))
.unwrap_or(Vec3::ZERO)
}
pub fn optimize(&self) -> bool {
self.optimize.unwrap_or(true)
}
fn optimize_mesh(
&self,
indices: &mut Vec<u32>,
vertex_buf: &mut Vec<u8>,
vertex_stride: usize,
) -> anyhow::Result<()> {
mod hack {
pub fn generate_vertex_remap(
indices: &mut [u32],
vertex_buf: &mut [u8],
vertex_stride: usize,
) -> (usize, Vec<u32>) {
let vertex_count = vertex_buf.len() / vertex_stride;
let mut remap: Vec<u32> = vec![0; vertex_count];
let remap_count = unsafe {
meshopt::ffi::meshopt_generateVertexRemap(
remap.as_mut_ptr().cast(),
indices.as_ptr().cast(),
indices.len(),
vertex_buf.as_ptr().cast(),
vertex_count,
vertex_stride,
)
};
(remap_count, remap)
}
pub fn optimize_vertex_fetch_in_place(
indices: &mut [u32],
vertex_buf: &mut [u8],
vertex_stride: usize,
) {
let vertex_count = vertex_buf.len() / vertex_stride;
let res = unsafe {
meshopt::ffi::meshopt_optimizeVertexFetch(
vertex_buf.as_mut_ptr().cast(),
indices.as_mut_ptr().cast(),
indices.len(),
vertex_buf.as_ptr().cast(),
vertex_count,
vertex_stride,
)
};
assert_eq!(res, vertex_count);
}
pub fn remap_vertex_buffer(
vertex_buf: &[u8],
vertex_count: usize,
vertex_stride: usize,
remap: &[u32],
) -> Vec<u8> {
let mut res = vec![0u8; vertex_count * vertex_stride];
unsafe {
meshopt::ffi::meshopt_remapVertexBuffer(
res.as_mut_ptr().cast(),
vertex_buf.as_ptr().cast(),
vertex_buf.len() / vertex_stride,
vertex_stride,
remap.as_ptr().cast(),
);
}
res
}
}
let (vertex_count, remap) = hack::generate_vertex_remap(indices, vertex_buf, vertex_stride);
*indices = remap_index_buffer(Some(indices), vertex_buf.len() / vertex_stride, &remap);
*vertex_buf = hack::remap_vertex_buffer(vertex_buf, vertex_count, vertex_stride, &remap);
assert_eq!(indices.len() % 3, 0);
assert_eq!(vertex_buf.len() % vertex_stride, 0);
assert_eq!(vertex_buf.len() / vertex_stride, vertex_count);
if self.optimize() {
let vertices = VertexDataAdapter::new(vertex_buf, vertex_stride, 0)
.context("creating vertex data adapter for mesh optimization")?;
optimize_vertex_cache_in_place(indices, vertex_count);
optimize_overdraw_in_place(indices, &vertices, self.overdraw_threshold());
hack::optimize_vertex_fetch_in_place(indices, vertex_buf, vertex_stride);
}
Ok(())
}
fn compact_mesh_indices(
indices: &mut [u32],
vertex_buf: &[u8],
vertex_stride: usize,
) -> anyhow::Result<(Vec<u8>, usize)> {
let vertex_count = vertex_buf.len() / vertex_stride;
let mut remap = vec![None; vertex_count];
let mut compact = Vec::new();
for idx in indices {
let src = *idx as usize;
if src >= vertex_count {
bail!("mesh index {} exceeds vertex count {}", src, vertex_count);
}
let dst = match remap[src] {
Some(dst) => dst,
None => {
let dst = compact.len() / vertex_stride;
let offset = src * vertex_stride;
compact.extend_from_slice(&vertex_buf[offset..offset + vertex_stride]);
remap[src] = Some(dst as u32);
dst as u32
}
};
*idx = dst;
}
let compact_count = compact.len() / vertex_stride;
Ok((compact, compact_count))
}
fn reduce_mesh_to_max_index(
&self,
indices: &mut Vec<u32>,
vertex_buf: &mut Vec<u8>,
vertex_stride: usize,
) -> anyhow::Result<()> {
let Some(max_index) = self.max_index() else {
return Ok(());
};
if indices.iter().copied().max().unwrap_or_default() <= max_index {
return Ok(());
}
if max_index < 2 {
bail!("max-index must allow at least 3 vertices");
}
let max_vertices = max_index as usize + 1;
let mut compacted_indices = indices.clone();
let (compacted_vertices, compacted_vertex_count) =
Self::compact_mesh_indices(&mut compacted_indices, vertex_buf, vertex_stride)?;
if compacted_vertex_count <= max_vertices {
*indices = compacted_indices;
*vertex_buf = compacted_vertices;
return Ok(());
}
let target_error = self.lod_target_error();
let opts = if self.lod_lock_border() {
SimplifyOptions::LockBorder
} else {
SimplifyOptions::None
};
let fits = |candidate: Vec<u32>| -> anyhow::Result<Option<(Vec<u32>, Vec<u8>)>> {
if candidate.len() < 3 {
return Ok(None);
}
let mut candidate = candidate;
let (candidate_vertices, candidate_vertex_count) =
Self::compact_mesh_indices(&mut candidate, vertex_buf, vertex_stride)?;
if candidate_vertex_count <= max_vertices {
Ok(Some((candidate, candidate_vertices)))
} else {
Ok(None)
}
};
let initial_target = (indices.len() / 2).min(max_vertices * 3).max(3);
let mut target_count = initial_target - (initial_target % 3);
if target_count < 3 {
target_count = 3;
}
while target_count >= 3 {
let vertices = VertexDataAdapter::new(vertex_buf, vertex_stride, 0)
.context("creating vertex data adapter for index-limited simplification")?;
let candidate = simplify(indices, &vertices, target_count, target_error, opts, None);
if let Some((candidate, candidate_vertices)) = fits(candidate)? {
*indices = candidate;
*vertex_buf = candidate_vertices;
return Ok(());
}
if target_count == 3 {
break;
}
target_count = ((target_count / 2) / 3).max(1) * 3;
}
let mut target_count = initial_target;
while target_count >= 3 {
let vertices = VertexDataAdapter::new(vertex_buf, vertex_stride, 0)
.context("creating vertex data adapter for index-limited sloppy simplification")?;
let candidate = simplify_sloppy(indices, &vertices, target_count, target_error, None);
if let Some((candidate, candidate_vertices)) = fits(candidate)? {
*indices = candidate;
*vertex_buf = candidate_vertices;
return Ok(());
}
if target_count == 3 {
break;
}
target_count = ((target_count / 2) / 3).max(1) * 3;
}
bail!("unable to reduce mesh to max-index {max_index}")
}
pub fn overdraw_threshold(&self) -> f32 {
self.overdraw_threshold.unwrap_or(OrderedFloat(1.05)).0
}
fn read_skin(node: &Node, bufs: &[Data], transform: Mat4) -> Option<Skin> {
node.skin().and_then(|skin| {
let inverse_binds = skin
.reader(|buf| bufs.get(buf.index()).map(|data| data.0.as_slice()))
.read_inverse_bind_matrices()
.map(|data| {
data.map(|matrix| {
let inverse_bind = Mat4::from_cols_array_2d(&matrix);
let bind = inverse_bind.inverse();
(transform * bind).inverse()
})
.collect::<Box<_>>()
})
.unwrap_or_default();
if inverse_binds.is_empty() {
warn!("Unable to read inverse bind matrices");
return None;
}
if inverse_binds.len() != skin.joints().len() {
warn!("Incompatible joints found");
return None;
}
if skin.joints().any(|joint| joint.name().is_none()) {
warn!("Unnamed joints found");
return None;
}
{
let mut joint_names = HashSet::new();
for joint_name in skin.joints().filter_map(|joint| joint.name()) {
if !joint_names.insert(joint_name) {
warn!("Duplicate joint names found");
return None;
}
}
}
let mut parents = HashMap::with_capacity(skin.joints().len());
for (index, joint) in skin.joints().enumerate() {
for child in joint.children() {
if parents.insert(child.index(), index).is_some() {
warn!("Invalid skeleton hierarchy found");
return None;
}
}
}
let mut joints = Vec::with_capacity(skin.joints().len());
for (idx, joint) in skin.joints().enumerate() {
joints.push(Joint {
parent_index: parents.get(&joint.index()).copied().unwrap_or(idx),
inverse_bind: inverse_binds[idx].to_cols_array(),
name: joint.name().unwrap_or_default().to_string(),
});
}
Some(Skin::new(joints))
})
}
fn read_vertices<'a, 's, F>(data: Reader<'a, 's, F>) -> (u32, VertexData)
where
F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>,
{
let positions = data
.read_positions()
.map(|positions| positions.collect::<Vec<_>>())
.unwrap_or_default();
let (restart_index, indices) = {
let indices = data.read_indices().map(|indices| {
(
match indices {
ReadIndices::U8(_) => u8::MAX as u32,
ReadIndices::U16(_) => u16::MAX as u32,
ReadIndices::U32(_) => u32::MAX,
},
indices.into_u32().collect::<Vec<_>>(),
)
});
if indices.is_none() {
warn!("Missing indices!");
}
indices.unwrap_or_else(|| (u32::MAX, (0..positions.len() as u32).collect()))
};
let textures = {
let mut texture0 = data
.read_tex_coords(0)
.map(|data| data.into_f32())
.map(|tex_coords| tex_coords.collect::<Vec<_>>())
.unwrap_or_default();
if !texture0.is_empty() {
texture0.resize(positions.len(), Default::default());
}
let mut texture1 = data
.read_tex_coords(1)
.map(|data| data.into_f32())
.map(|tex_coords| tex_coords.collect::<Vec<_>>())
.unwrap_or_default();
if !texture1.is_empty() {
texture1.resize(positions.len(), Default::default());
}
(texture0, texture1)
};
let normals = {
let mut normals = data
.read_normals()
.map(|normals| normals.collect::<Vec<_>>())
.unwrap_or_default();
if !normals.is_empty() {
normals.resize(positions.len(), Default::default());
}
normals
};
let tangents = {
let mut tangents = data
.read_tangents()
.map(|tangents| tangents.collect::<Vec<_>>())
.unwrap_or_default();
if !tangents.is_empty() {
tangents.resize(positions.len(), Default::default());
}
tangents
};
let joints = data
.read_joints(0)
.map(|joints| {
let mut res = joints
.into_u16()
.map(|joints| {
#[cfg(debug_assertions)]
for joint in joints {
assert!(joint <= u8::MAX as u16);
}
joints[0] as u32
| (joints[1] as u32) << 8
| (joints[2] as u32) << 16
| (joints[3] as u32) << 24
})
.collect::<Vec<_>>();
res.resize(positions.len(), 0);
res
})
.unwrap_or_default();
let weights = data
.read_weights(0)
.map(|weights| {
let mut res = weights
.into_f32()
.map(|weights| {
#[cfg(debug_assertions)]
for weight in weights {
assert!(weight >= 0.0);
assert!(weight <= 1.0);
let weight = quantize_unorm(weight, 8);
assert!(weight <= u8::MAX as i32);
assert!(weight >= u8::MIN as i32);
}
(quantize_unorm(weights[0], 8)
| (quantize_unorm(weights[1], 8) << 8)
| (quantize_unorm(weights[2], 8) << 16)
| (quantize_unorm(weights[3], 8) << 24)) as u32
})
.collect::<Vec<_>>();
res.resize(positions.len(), 0);
res
})
.unwrap_or_default();
let has_skin = joints.len() == positions.len() && weights.len() == positions.len();
let skin = if has_skin {
Some((joints, weights))
} else {
None
};
(
restart_index,
VertexData {
indices,
normals,
positions,
skin,
tangents,
textures,
},
)
}
pub fn rotation(&self) -> Quat {
match self.rotation {
Some(Rotation::Euler(rotation)) => Quat::from_euler(
self.euler(),
rotation[0].0.to_radians(),
rotation[1].0.to_radians(),
rotation[2].0.to_radians(),
),
Some(Rotation::Quaternion(rotation)) => {
Quat::from_array([rotation[0].0, rotation[1].0, rotation[2].0, rotation[3].0])
}
None => Quat::IDENTITY,
}
}
pub fn euler(&self) -> EulerRot {
match self.euler.unwrap_or(Euler::XYZ) {
Euler::XYZ => EulerRot::XYZ,
Euler::XZY => EulerRot::XZY,
Euler::YXZ => EulerRot::YXZ,
Euler::YZX => EulerRot::YZX,
Euler::ZXY => EulerRot::ZXY,
Euler::ZYX => EulerRot::ZYX,
}
}
pub fn set_src(&mut self, src: impl AsRef<Path>) {
self.src = Some(src.as_ref().to_path_buf());
}
pub fn scale(&self) -> Vec3 {
self.scale
.map(|scale| match scale {
Scale::Array([OrderedFloat(x), OrderedFloat(y), OrderedFloat(z)]) => vec3(x, y, z),
Scale::Value(OrderedFloat(scale)) => Vec3::splat(scale),
})
.inspect(|scale| {
assert!(scale.is_finite(), "scale must be finite");
assert!(scale.min_element() > 0.0, "scale must be greater than zero");
})
.unwrap_or(Vec3::ONE)
}
pub fn shadow(&self) -> bool {
self.shadow.unwrap_or_default()
}
pub fn src(&self) -> Option<&Path> {
self.src.as_deref()
}
pub fn tangents(&self) -> bool {
self.tangents.unwrap_or(true)
}
fn to_mesh(&self, src: impl AsRef<Path>) -> anyhow::Result<Mesh> {
let src = src.as_ref();
let (doc, bufs, _) =
import(src).with_context(|| format!("Importing mesh source: {}", src.display()))?;
let scene = self
.scene_name
.as_deref()
.and_then(|name| doc.scenes().find(|scene| scene.name() == Some(name)))
.or_else(|| doc.default_scene())
.or_else(|| doc.scenes().next())
.ok_or(anyhow::Error::msg("No scene found"))?;
let all_nodes = {
let mut nodes = vec![];
let mut todo = scene.nodes().collect::<Vec<_>>();
while let Some(node) = todo.pop() {
todo.extend(node.children());
nodes.push(node);
}
nodes
};
let mut mesh_nodes = all_nodes.iter().filter(|node| node.mesh().is_some());
let node = self
.name
.as_deref()
.and_then(|name| mesh_nodes.find(|node| node.name() == Some(name)))
.or_else(|| mesh_nodes.next())
.ok_or(anyhow::Error::msg("No mesh found"))?;
let allow_skin = !self.ignore_skin.unwrap_or_default();
let mesh_transform =
Mat4::from_scale_rotation_translation(self.scale(), self.rotation(), self.offset());
info!("Loading mesh {}", node.name().unwrap_or_default());
let skin = allow_skin
.then(|| Self::read_skin(node, &bufs, mesh_transform))
.flatten();
let transform = mesh_transform * extract_transform(node);
let parts = node
.mesh()
.context("node has no mesh")?
.primitives()
.filter_map(|primitive| match primitive.mode() {
Mode::TriangleFan | Mode::TriangleStrip | Mode::Triangles => {
trace!(
"Reading mesh \"{}\" (material index {})",
node.name().unwrap_or_default(),
if primitive.material().index().is_some() {
format!("{}", primitive.material().index().unwrap_or_default())
} else {
"unset".to_string()
}
);
let material = primitive.material().index().unwrap_or_default();
let (restart_index, mut vertices) = Self::read_vertices(
primitive.reader(|buf| bufs.get(buf.index()).map(|data| data.0.as_slice())),
);
match primitive.mode() {
Mode::TriangleFan => {
Self::convert_triangle_fan_to_list(&mut vertices.indices)
}
Mode::TriangleStrip => Self::convert_triangle_strip_to_list(
&mut vertices.indices,
restart_index,
)
.ok()?,
_ => (),
}
if self.flip_x.unwrap_or_default() {
for [x, _y, _z] in &mut vertices.positions {
*x *= -1.0;
}
}
if self.flip_y.unwrap_or_default() {
for [_x, y, _z] in &mut vertices.positions {
*y *= -1.0;
}
}
if self.flip_z.unwrap_or_default() {
for [_x, _y, z] in &mut vertices.positions {
*z *= -1.0;
}
}
vertices.transform(transform);
Some((material, vertices))
}
_ => None,
})
.collect::<Vec<_>>();
let materials = parts
.iter()
.map(|(material, ..)| *material)
.collect::<BTreeSet<_>>()
.into_iter()
.enumerate()
.map(|(idx, material)| (material, idx as _))
.collect::<HashMap<_, _>>();
trace!(
"Document contains {} material{}",
materials.len(),
if materials.len() == 1 { "" } else { "s" },
);
let shadow = self.shadow();
let mut primitives = Vec::with_capacity(parts.len() + (parts.len() * shadow as usize));
for (material, mut data) in parts {
let material = materials.get(&material).copied().unwrap_or_default();
if skin.is_none() {
data.skin = None;
}
if !self.normals() {
data.normals.clear();
} else if data.normals.is_empty() {
data.generate_normals();
}
if !self.tangents() {
data.tangents.clear();
} else if data.tangents.is_empty() {
warn!(
"Tangent data requested but not found: {} (will generate)",
src.display()
);
data.generate_tangents();
} else if !data.tangents_are_valid() {
warn!(
"Invalid tangent data found: {} (will regenerate)",
src.display()
);
data.generate_tangents();
}
{
let (vertex, mut vertex_buf) = data.to_vertex_buf();
let vertex_stride = vertex.stride();
self.optimize_mesh(&mut data.indices, &mut vertex_buf, vertex_stride)?;
self.reduce_mesh_to_max_index(&mut data.indices, &mut vertex_buf, vertex_stride)?;
let mut primitive = Primitive::new(material, &vertex_buf, vertex);
for lod_indices in self.calculate_lods(&data.indices, &vertex_buf, vertex_stride)? {
primitive.push_lod(IndexBuffer::new(&lod_indices)?)
}
primitives.push(primitive);
}
if shadow {
let (vertex, mut vertex_buf) = data.to_shadow_buf();
let vertex_stride = vertex.stride();
self.optimize_mesh(&mut data.indices, &mut vertex_buf, vertex_stride)?;
self.reduce_mesh_to_max_index(&mut data.indices, &mut vertex_buf, vertex_stride)?;
let mut primitive = Primitive::new(material, &vertex_buf, vertex);
for lod_indices in self.calculate_lods(&data.indices, &vertex_buf, vertex_stride)? {
primitive.push_lod(IndexBuffer::new(&lod_indices)?);
}
primitives.push(primitive);
}
}
Ok(Mesh::new(primitives, skin))
}
fn re_run_if_changed(&self) {
if let Some(data) = self.data() {
re_run_if_changed(data);
}
if let Some(src) = &self.src {
re_run_if_changed(src);
let mut src_bin = src.to_path_buf();
src_bin.set_extension("bin");
re_run_if_changed(src_bin);
}
}
}
impl Canonicalize for MeshAsset {
fn canonicalize(&mut self, project_dir: impl AsRef<Path>, src_dir: impl AsRef<Path>) {
if let Some(data) = &self.data {
self.data = Some(Self::canonicalize_project_path(
&project_dir,
&src_dir,
data,
));
}
if let Some(src) = &mut self.src {
self.src = Some(Self::canonicalize_project_path(project_dir, src_dir, src))
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum MaxIndex {
U8,
U16,
Exact(u32),
}
impl MaxIndex {
fn value(self) -> u32 {
match self {
Self::U8 => u8::MAX as u32,
Self::U16 => u16::MAX as u32,
Self::Exact(value) => value,
}
}
}
impl<'de> Deserialize<'de> for MaxIndex {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MaxIndexVisitor;
impl Visitor<'_> for MaxIndexVisitor {
type Value = MaxIndex;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("u8, u16, or an exact maximum index value")
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let value = u32::try_from(value)
.map_err(|_| E::custom("max-index must be between 0 and u32::MAX"))?;
Ok(MaxIndex::Exact(value))
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let value = u32::try_from(value)
.map_err(|_| E::custom("max-index must be between 0 and u32::MAX"))?;
Ok(MaxIndex::Exact(value))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match value.to_ascii_lowercase().as_str() {
"u8" => Ok(MaxIndex::U8),
"u16" => Ok(MaxIndex::U16),
_ => Err(E::unknown_variant(value, &["u8", "u16"])),
}
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&value)
}
}
deserializer.deserialize_any(MaxIndexVisitor)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Scale {
Array([OrderedFloat<f32>; 3]),
Value(OrderedFloat<f32>),
}
impl Scale {
fn de<'de, D>(deserializer: D) -> Result<Option<Self>, D::Error>
where
D: Deserializer<'de>,
{
struct ScaleVisitor;
impl<'de> Visitor<'de> for ScaleVisitor {
type Value = Option<Scale>;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("floating point sequence or value")
}
fn visit_f64<E>(self, val: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let val = val as f32;
match val.classify() {
FpCategory::Zero | FpCategory::Normal => (),
_ => return Err(E::custom("expected a normal floating point value")),
}
Ok(Some(Scale::Value(OrderedFloat(val))))
}
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let val: Vec<f32> = Deserialize::deserialize(SeqAccessDeserializer::new(seq))?;
if val.len() != 3 {
return Err(serde::de::Error::custom("expected 3 values"));
}
for val in &val {
match val.classify() {
FpCategory::Zero | FpCategory::Normal => (),
_ => {
return Err(serde::de::Error::custom(
"expected a normal floating point value",
));
}
}
}
Ok(Some(Scale::Array([
OrderedFloat(val[0]),
OrderedFloat(val[1]),
OrderedFloat(val[2]),
])))
}
}
deserializer.deserialize_any(ScaleVisitor)
}
}
struct VertexData {
indices: Vec<u32>,
normals: Vec<[f32; 3]>,
positions: Vec<[f32; 3]>,
skin: Option<(Vec<u32>, Vec<u32>)>,
tangents: Vec<[f32; 4]>,
textures: (Vec<[f32; 2]>, Vec<[f32; 2]>),
}
impl VertexData {
fn generate_normals(&mut self) {
self.normals.clear();
self.normals
.resize(self.positions.len(), Default::default());
for idx in 0..self.indices.len() / 3 {
let offset = idx * 3;
let indices = [
self.indices[offset] as usize,
self.indices[offset + 1] as usize,
self.indices[offset + 2] as usize,
];
let vertices = [
Vec3::from_array(self.positions[indices[0]]),
Vec3::from_array(self.positions[indices[1]]),
Vec3::from_array(self.positions[indices[2]]),
];
let normal = (vertices[1] - vertices[0])
.cross(vertices[2] - vertices[0])
.normalize();
self.normals[indices[0]] =
(Vec3::from_array(self.normals[indices[0]]) + normal).to_array();
self.normals[indices[1]] =
(Vec3::from_array(self.normals[indices[1]]) + normal).to_array();
self.normals[indices[2]] =
(Vec3::from_array(self.normals[indices[2]]) + normal).to_array();
}
for idx in 0..self.normals.len() {
self.normals[idx] = Vec3::from_array(self.normals[idx]).normalize().to_array();
}
}
fn index(&self, face: usize, vert: usize) -> usize {
self.indices[face * 3 + vert] as _
}
fn generate_tangents(&mut self) {
if self.normals.is_empty() {
self.generate_normals();
}
if self.textures.0.is_empty() {
self.textures
.0
.resize(self.positions.len(), Default::default());
}
self.tangents.clear();
self.tangents
.extend(repeat_n([0.0; 4], self.positions.len()));
assert!(mikktspace::generate_tangents(self));
self.repair_invalid_tangents();
debug_assert!(self.tangents_are_valid());
}
fn repair_invalid_tangents(&mut self) {
for (tangent, normal) in self.tangents.iter_mut().zip(&self.normals) {
if !Self::tangent_is_valid(*tangent, *normal) {
let normal = Vec3::from_array(*normal).normalize_or(Vec3::Y);
let axis = if normal.x.abs() < 0.9 {
Vec3::X
} else {
Vec3::Y
};
let fallback = normal.cross(axis).normalize_or(Vec3::Z);
*tangent = [fallback.x, fallback.y, fallback.z, 1.0];
}
}
}
fn tangent_is_valid(tangent: [f32; 4], normal: [f32; 3]) -> bool {
let normal = Vec3::from_array(normal);
let tangent_vec = Vec4::from_array(tangent).truncate();
let projected = tangent_vec - normal * tangent_vec.dot(normal);
tangent[3] != 0.0 && projected.length_squared() > 0.000001
}
fn tangents_are_valid(&self) -> bool {
if self.tangents.len() != self.positions.len() || self.normals.len() != self.positions.len()
{
return false;
}
self.tangents
.iter()
.zip(&self.normals)
.all(|(tangent, normal)| Self::tangent_is_valid(*tangent, *normal))
}
fn to_vertex_buf(&self) -> (VertexType, Vec<u8>) {
let mut vertex_type = VertexType::POSITION;
if !self.normals.is_empty() {
vertex_type |= VertexType::NORMAL;
}
if self.skin.is_some() {
vertex_type |= VertexType::JOINTS_WEIGHTS;
}
if !self.tangents.is_empty() {
vertex_type |= VertexType::TANGENT;
}
if !self.textures.0.is_empty() {
vertex_type |= VertexType::TEXTURE0;
}
if !self.textures.1.is_empty() {
vertex_type |= VertexType::TEXTURE1;
}
let vertex_stride = vertex_type.stride();
let buf_len = self.positions.len() * vertex_stride;
let mut buf = Vec::with_capacity(buf_len);
for idx in 0..self.positions.len() {
let position = self.positions[idx];
buf.extend_from_slice(&position[0].to_ne_bytes());
buf.extend_from_slice(&position[1].to_ne_bytes());
buf.extend_from_slice(&position[2].to_ne_bytes());
if vertex_type.contains(VertexType::NORMAL) {
let normal = self.normals[idx];
buf.extend_from_slice(&normal[0].to_ne_bytes());
buf.extend_from_slice(&normal[1].to_ne_bytes());
buf.extend_from_slice(&normal[2].to_ne_bytes());
}
if vertex_type.contains(VertexType::TEXTURE0) {
let textures = self.textures.0[idx];
buf.extend_from_slice(&textures[0].to_ne_bytes());
buf.extend_from_slice(&textures[1].to_ne_bytes());
}
if vertex_type.contains(VertexType::TEXTURE1) {
let textures = self.textures.1[idx];
buf.extend_from_slice(&textures[0].to_ne_bytes());
buf.extend_from_slice(&textures[1].to_ne_bytes());
}
if vertex_type.contains(VertexType::TANGENT) {
let tangent = self.tangents[idx];
buf.extend_from_slice(&tangent[0].to_ne_bytes());
buf.extend_from_slice(&tangent[1].to_ne_bytes());
buf.extend_from_slice(&tangent[2].to_ne_bytes());
buf.extend_from_slice(&tangent[3].to_ne_bytes());
}
if let Some(skin) = self.skin.as_ref() {
let joints = skin.0[idx];
buf.extend_from_slice(&joints.to_ne_bytes());
let weights = skin.1[idx];
buf.extend_from_slice(&weights.to_ne_bytes());
}
assert_eq!(buf.len() % vertex_stride, 0);
}
assert_eq!(buf.len(), buf_len);
(vertex_type, buf)
}
fn to_shadow_buf(&self) -> (VertexType, Vec<u8>) {
let mut vertex_type = VertexType::POSITION;
if self.skin.is_some() {
vertex_type |= VertexType::JOINTS_WEIGHTS;
}
let vertex_stride = vertex_type.stride();
let buf_len = self.positions.len() * vertex_stride;
let mut buf = Vec::with_capacity(buf_len);
for idx in 0..self.positions.len() {
let position = self.positions[idx];
buf.extend_from_slice(&position[0].to_ne_bytes());
buf.extend_from_slice(&position[1].to_ne_bytes());
buf.extend_from_slice(&position[2].to_ne_bytes());
if let Some(skin) = self.skin.as_ref() {
let joints = skin.0[idx];
buf.extend_from_slice(&joints.to_ne_bytes());
let weights = skin.1[idx];
buf.extend_from_slice(&weights.to_ne_bytes());
}
assert_eq!(buf.len() % vertex_stride, 0);
}
assert_eq!(buf.len(), buf_len);
(vertex_type, buf)
}
fn transform(&mut self, transform: Mat4) {
let (_scale, rotation, _translation) = transform.to_scale_rotation_translation();
for position in &mut self.positions {
let position4 = Vec3::from_slice(position).extend(1.0);
position.copy_from_slice(&transform.mul_vec4(position4).to_array()[0..3]);
}
for normal in &mut self.normals {
*normal = rotation.mul_vec3(Vec3::from_array(*normal)).to_array();
}
}
}
impl mikktspace::Geometry for VertexData {
fn num_faces(&self) -> usize {
self.indices.len() / 3
}
fn num_vertices_of_face(&self, _face: usize) -> usize {
3
}
fn position(&self, face: usize, vert: usize) -> [f32; 3] {
self.positions[self.index(face, vert)]
}
fn normal(&self, face: usize, vert: usize) -> [f32; 3] {
self.normals[self.index(face, vert)]
}
fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] {
self.textures.0[self.index(face, vert)]
}
fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) {
let idx = self.index(face, vert);
self.tangents[idx] = tangent;
}
}
#[cfg(test)]
mod tests {
use {
super::{MaxIndex, MeshAsset},
crate::mesh::Mesh,
std::path::Path,
};
fn max_abs_position(mesh: &Mesh) -> f32 {
let mut max = 0.0f32;
for primitive in mesh.primitives() {
let stride = primitive.vertex_type().stride();
for vertex in primitive.vertex_data().chunks_exact(stride) {
let x = f32::from_ne_bytes(vertex[0..4].try_into().unwrap());
let y = f32::from_ne_bytes(vertex[4..8].try_into().unwrap());
let z = f32::from_ne_bytes(vertex[8..12].try_into().unwrap());
max = max.max(x.abs()).max(y.abs()).max(z.abs());
}
}
max
}
#[test]
fn max_index_deserializes_enum_variants() {
let mesh: MeshAsset =
toml::from_str("max-index = 'u8'").expect("max-index should accept u8 variant");
assert_eq!(mesh.max_index, Some(MaxIndex::U8));
assert_eq!(mesh.max_index(), Some(u8::MAX as u32));
let mesh: MeshAsset = toml::from_str("max-index = 'U16'")
.expect("max-index should accept u16 variant case-insensitively");
assert_eq!(mesh.max_index, Some(MaxIndex::U16));
assert_eq!(mesh.max_index(), Some(u16::MAX as u32));
}
#[test]
fn max_index_deserializes_exact_value() {
let mesh: MeshAsset =
toml::from_str("max-index = 4095").expect("max-index should accept exact values");
assert_eq!(mesh.max_index, Some(MaxIndex::Exact(4095)));
assert_eq!(mesh.max_index(), Some(4095));
}
#[test]
fn compact_mesh_indices_rewrites_sparse_indices() {
let vertex_stride = 4;
let vertex_buf = [10, 0, 0, 0, 20, 0, 0, 0, 30, 0, 0, 0, 40, 0, 0, 0];
let mut indices = vec![3, 1, 3, 2, 1, 3];
let (compact, vertex_count) =
MeshAsset::compact_mesh_indices(&mut indices, &vertex_buf, vertex_stride)
.expect("mesh should compact");
assert_eq!(indices, [0, 1, 0, 2, 1, 0]);
assert_eq!(vertex_count, 3);
assert_eq!(compact, [40, 0, 0, 0, 20, 0, 0, 0, 30, 0, 0, 0]);
}
#[test]
fn mesh_scale_applies_to_imported_positions() {
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/scene/cube.glb");
let unscaled: MeshAsset = toml::from_str(
"
optimize = false
normals = false
tangents = false
",
)
.expect("unscaled mesh config should deserialize");
let scaled: MeshAsset = toml::from_str(
"
scale = 2.0
optimize = false
normals = false
tangents = false
",
)
.expect("scaled mesh config should deserialize");
let unscaled = unscaled.to_mesh(&src).expect("unscaled mesh should import");
let scaled = scaled.to_mesh(&src).expect("scaled mesh should import");
let unscaled_max = max_abs_position(&unscaled);
let scaled_max = max_abs_position(&scaled);
assert!(unscaled_max > 0.0);
assert!((scaled_max - unscaled_max * 2.0).abs() < 0.0001);
}
}