use crate::mesh::{InstanceMeta, Mesh};
use nalgebra::Matrix4;
use rustc_hash::FxHashMap;
const IDENTITY16: [f64; 16] = [
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ];
fn compose_world(meta: &InstanceMeta) -> Matrix4<f64> {
let t = Matrix4::from_row_slice(&meta.transform);
let l = Matrix4::from_row_slice(meta.local_transform.as_ref().unwrap_or(&IDENTITY16));
let c = Matrix4::from_row_slice(meta.canonical_transform.as_ref().unwrap_or(&IDENTITY16));
t * l * c
}
pub(super) fn mat4_to_row_major_f32(m: &Matrix4<f64>) -> [f32; 16] {
let mut out = [0.0f32; 16];
for r in 0..4 {
for c in 0..4 {
out[r * 4 + c] = m[(r, c)] as f32;
}
}
out
}
#[derive(Debug, Clone)]
pub struct InstanceOccurrence {
pub mesh_index: usize,
pub transform: [f32; 16],
}
#[derive(Debug, Clone)]
pub struct InstanceTemplate {
pub rep_identity: u128,
pub template_index: usize,
pub occurrences: Vec<InstanceOccurrence>,
}
#[derive(Debug, Clone, Default)]
pub struct Collated {
pub templates: Vec<InstanceTemplate>,
pub flat_indices: Vec<usize>,
}
impl Collated {
pub fn unique_geometry_count(&self) -> usize {
self.templates.len() + self.flat_indices.len()
}
pub fn instanced_occurrence_count(&self) -> usize {
self.templates.iter().map(|t| t.occurrences.len()).sum()
}
}
pub struct InstanceMeshRef<'a> {
pub positions: &'a [f32],
pub normals: &'a [f32],
pub indices: &'a [u32],
pub origin: [f64; 3],
pub instance_meta: Option<&'a InstanceMeta>,
pub entity_id: u32,
pub color: [f32; 4],
}
impl<'a> InstanceMeshRef<'a> {
pub fn from_mesh(m: &'a Mesh) -> Self {
InstanceMeshRef {
positions: &m.positions,
normals: &m.normals,
indices: &m.indices,
origin: m.origin,
instance_meta: m.instance_meta.as_ref(),
entity_id: 0,
color: [0.0; 4],
}
}
}
fn to_post_rtc(mut m: Matrix4<f64>, rtc: [f64; 3]) -> Matrix4<f64> {
m[(0, 3)] -= rtc[0];
m[(1, 3)] -= rtc[1];
m[(2, 3)] -= rtc[2];
m
}
pub fn collate_refs(meshes: &[InstanceMeshRef], min_group: usize, rtc: [f64; 3]) -> Collated {
let mut order: Vec<u128> = Vec::new();
let mut groups: FxHashMap<u128, Vec<usize>> = FxHashMap::default();
let mut flat: Vec<usize> = Vec::new();
for (i, m) in meshes.iter().enumerate() {
match m.instance_meta {
Some(im) if im.instanceable => {
groups
.entry(im.rep_identity)
.or_insert_with(|| {
order.push(im.rep_identity);
Vec::new()
})
.push(i);
}
_ if !m.positions.is_empty() => flat.push(i),
_ => {}
}
}
let drawable = |members: &[usize]| -> Vec<usize> {
members
.iter()
.copied()
.filter(|&i| !meshes[i].positions.is_empty())
.collect::<Vec<_>>()
};
let mut out = Collated {
flat_indices: flat,
..Collated::default()
};
for rep in order {
let members = &groups[&rep];
if members.len() < min_group.max(1) {
out.flat_indices.extend(drawable(members));
continue;
}
let Some(t_idx) = members
.iter()
.copied()
.find(|&i| !meshes[i].positions.is_empty())
else {
continue;
};
let template = &meshes[t_idx];
let m_ref = to_post_rtc(compose_world(template.instance_meta.unwrap()), rtc);
let Some(m_ref_inv) = m_ref.try_inverse() else {
out.flat_indices.extend(drawable(members));
continue;
};
let is_rigid = members
.iter()
.any(|&i| meshes[i].instance_meta.and_then(|m| m.canonical_transform).is_some());
let (vlen, ilen) = (template.positions.len(), template.indices.len());
let mut occurrences = Vec::with_capacity(members.len());
let mut shapes_match = true;
for &i in members {
let mesh = &meshes[i];
let pose_only = mesh.positions.is_empty();
if !pose_only
&& !is_rigid
&& (mesh.positions.len() != vlen || mesh.indices.len() != ilen)
{
shapes_match = false;
break;
}
let m_k = to_post_rtc(compose_world(mesh.instance_meta.unwrap()), rtc);
let rel = m_k * m_ref_inv;
occurrences.push(InstanceOccurrence {
mesh_index: i,
transform: mat4_to_row_major_f32(&rel),
});
}
if shapes_match {
out.templates.push(InstanceTemplate {
rep_identity: rep,
template_index: t_idx,
occurrences,
});
} else {
out.flat_indices.extend(drawable(members));
}
}
out
}
pub fn compose_instance_world_row_major(meta: &InstanceMeta) -> [f64; 16] {
let m = compose_world(meta);
let mut out = [0.0f64; 16];
for r in 0..4 {
for c in 0..4 {
out[r * 4 + c] = m[(r, c)];
}
}
out
}
pub fn instance_rel_row_major_f32(
m_k: &[f64; 16],
m_ref: &[f64; 16],
rtc: [f64; 3],
) -> Option<[f32; 16]> {
let mk = to_post_rtc(Matrix4::from_row_slice(m_k), rtc);
let mref = to_post_rtc(Matrix4::from_row_slice(m_ref), rtc);
let mref_inv = mref.try_inverse()?;
Some(mat4_to_row_major_f32(&(mk * mref_inv)))
}
pub fn bake_source_at_world(
source: &Mesh,
world_row_major: &[f64; 16],
rtc: [f64; 3],
) -> (Vec<f32>, Vec<f32>, Vec<u32>) {
let m = to_post_rtc(Matrix4::from_row_slice(world_row_major), rtc);
let vcount = source.positions.len() / 3;
let mut positions = Vec::with_capacity(source.positions.len());
for v in 0..vcount {
let p = m * nalgebra::Vector4::new(
source.positions[v * 3] as f64,
source.positions[v * 3 + 1] as f64,
source.positions[v * 3 + 2] as f64,
1.0,
);
positions.push((p.x / p.w) as f32);
positions.push((p.y / p.w) as f32);
positions.push((p.z / p.w) as f32);
}
let linear = m.fixed_view::<3, 3>(0, 0).into_owned();
let nmat = linear
.try_inverse()
.map(|inv| inv.transpose())
.unwrap_or(linear);
let ncount = source.normals.len() / 3;
let mut normals = Vec::with_capacity(source.normals.len());
for v in 0..ncount {
let nv = nmat
* nalgebra::Vector3::new(
source.normals[v * 3] as f64,
source.normals[v * 3 + 1] as f64,
source.normals[v * 3 + 2] as f64,
);
let nv = nv.try_normalize(0.0).unwrap_or(nv);
normals.push(nv.x as f32);
normals.push(nv.y as f32);
normals.push(nv.z as f32);
}
(positions, normals, source.indices.clone())
}
pub fn collate_instances(meshes: &[Mesh], min_group: usize, rtc: [f64; 3]) -> Collated {
let refs: Vec<InstanceMeshRef> = meshes.iter().map(InstanceMeshRef::from_mesh).collect();
collate_refs(&refs, min_group, rtc)
}
pub fn verify_recomposition(meshes: &[Mesh], collated: &Collated) -> f64 {
let mut max_err = 0.0f64;
for tmpl in &collated.templates {
let template = &meshes[tmpl.template_index];
for occ in &tmpl.occurrences {
let target = &meshes[occ.mesh_index];
let rel = Matrix4::from_row_slice(&occ.transform.map(|v| v as f64));
let n = template.positions.len() / 3;
if target.positions.len() / 3 != n {
max_err = f64::INFINITY;
continue;
}
for v in 0..n {
let tx = template.origin[0] + template.positions[v * 3] as f64;
let ty = template.origin[1] + template.positions[v * 3 + 1] as f64;
let tz = template.origin[2] + template.positions[v * 3 + 2] as f64;
let w = rel * nalgebra::Vector4::new(tx, ty, tz, 1.0);
let (rx, ry, rz) = (w.x / w.w, w.y / w.w, w.z / w.w);
let gx = target.origin[0] + target.positions[v * 3] as f64;
let gy = target.origin[1] + target.positions[v * 3 + 1] as f64;
let gz = target.origin[2] + target.positions[v * 3 + 2] as f64;
let err = ((rx - gx).powi(2) + (ry - gy).powi(2) + (rz - gz).powi(2)).sqrt();
if err > max_err {
max_err = err;
}
}
}
}
max_err
}