use super::collate::{Collated, InstanceMeshRef};
use super::group::collate_refs;
use crate::mesh::Mesh;
pub const INSTANCED_MAGIC: u32 = 0x4946_4E53;
pub const INSTANCED_VERSION: u32 = 2;
const INSTANCED_VERSION_BASE_RECORD: u32 = 1;
const INSTANCE_RECORD_BASE_BYTES: usize = 88;
const INSTANCE_ITEM_ID_OFFSET: usize = INSTANCE_RECORD_BASE_BYTES;
const INSTANCE_RECORD_ITEM_ID_BYTES: usize = INSTANCE_ITEM_ID_OFFSET + 4;
const TEMPLATE_RECORD_BYTES: usize = 48;
const HEADER_BYTES: usize = 32;
const INST_IDENTITY_F32: [f32; 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,
];
#[derive(Debug, Clone)]
pub struct DecodedTemplate {
pub positions: Vec<f32>,
pub normals: Vec<f32>,
pub indices: Vec<u32>,
pub origin: [f64; 3],
}
#[derive(Debug, Clone)]
pub struct DecodedInstance {
pub template_index: u32,
pub entity_id: u32,
pub color: [f32; 4],
pub transform: [f32; 16],
pub item_id: Option<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct DecodedInstanced {
pub templates: Vec<DecodedTemplate>,
pub instances: Vec<DecodedInstance>,
}
pub fn encode_refs(meshes: &[InstanceMeshRef], collated: &Collated) -> Vec<u8> {
struct TSpec {
mesh_idx: usize,
instances: Vec<(usize, [f32; 16])>,
}
let mut tspecs: Vec<TSpec> = Vec::with_capacity(collated.templates.len() + collated.flat_indices.len());
for t in &collated.templates {
tspecs.push(TSpec {
mesh_idx: t.template_index,
instances: t.occurrences.iter().map(|o| (o.mesh_index, o.transform)).collect(),
});
}
for &f in &collated.flat_indices {
tspecs.push(TSpec {
mesh_idx: f,
instances: vec![(f, INST_IDENTITY_F32)],
});
}
let template_count = tspecs.len();
let instance_count: usize = tspecs.iter().map(|t| t.instances.len()).sum();
let positions_len: usize = tspecs.iter().map(|t| meshes[t.mesh_idx].positions.len()).sum();
let normals_len: usize = tspecs.iter().map(|t| meshes[t.mesh_idx].normals.len()).sum();
let indices_len: usize = tspecs.iter().map(|t| meshes[t.mesh_idx].indices.len()).sum();
assert!(
positions_len <= u32::MAX as usize
&& normals_len <= u32::MAX as usize
&& indices_len <= u32::MAX as usize
&& template_count <= u32::MAX as usize
&& instance_count <= u32::MAX as usize,
"instanced shard exceeds u32 wire limits (pos={positions_len} idx={indices_len}); chunk it"
);
let carries_item_id = tspecs
.iter()
.flat_map(|t| t.instances.iter())
.any(|(occ_idx, _)| meshes[*occ_idx].item_id.is_some());
let (version, instance_stride, stride_word) = if carries_item_id {
(INSTANCED_VERSION, INSTANCE_RECORD_ITEM_ID_BYTES, INSTANCE_RECORD_ITEM_ID_BYTES as u32)
} else {
(INSTANCED_VERSION_BASE_RECORD, INSTANCE_RECORD_BASE_BYTES, 0u32)
};
let mut buf: Vec<u8> = Vec::with_capacity(
HEADER_BYTES
+ template_count * TEMPLATE_RECORD_BYTES
+ instance_count * instance_stride
+ (positions_len + normals_len + indices_len) * 4,
);
let pu32 = |b: &mut Vec<u8>, v: u32| b.extend_from_slice(&v.to_le_bytes());
let pf32 = |b: &mut Vec<u8>, v: f32| b.extend_from_slice(&v.to_le_bytes());
let pf64 = |b: &mut Vec<u8>, v: f64| b.extend_from_slice(&v.to_le_bytes());
pu32(&mut buf, INSTANCED_MAGIC);
pu32(&mut buf, version);
pu32(&mut buf, template_count as u32);
pu32(&mut buf, instance_count as u32);
pu32(&mut buf, positions_len as u32);
pu32(&mut buf, normals_len as u32);
pu32(&mut buf, indices_len as u32);
pu32(&mut buf, stride_word);
let (mut pos_off, mut nrm_off, mut idx_off) = (0u32, 0u32, 0u32);
for t in &tspecs {
let m = &meshes[t.mesh_idx];
pu32(&mut buf, pos_off);
pu32(&mut buf, m.positions.len() as u32);
pu32(&mut buf, nrm_off);
pu32(&mut buf, m.normals.len() as u32);
pu32(&mut buf, idx_off);
pu32(&mut buf, m.indices.len() as u32);
pf64(&mut buf, m.origin[0]);
pf64(&mut buf, m.origin[1]);
pf64(&mut buf, m.origin[2]);
pos_off += m.positions.len() as u32;
nrm_off += m.normals.len() as u32;
idx_off += m.indices.len() as u32;
}
for (ti, t) in tspecs.iter().enumerate() {
for (occ_idx, transform) in &t.instances {
pu32(&mut buf, ti as u32);
pu32(&mut buf, meshes[*occ_idx].entity_id);
for c in meshes[*occ_idx].color {
pf32(&mut buf, c);
}
for v in transform {
pf32(&mut buf, *v);
}
debug_assert!(
carries_item_id || meshes[*occ_idx].item_id.is_none(),
"instance record carries an item id the declared stride has no room for"
);
if carries_item_id {
pu32(&mut buf, meshes[*occ_idx].item_id.unwrap_or(0));
}
}
}
for t in &tspecs {
for &p in meshes[t.mesh_idx].positions {
pf32(&mut buf, p);
}
}
for t in &tspecs {
for &n in meshes[t.mesh_idx].normals {
pf32(&mut buf, n);
}
}
for t in &tspecs {
for &i in meshes[t.mesh_idx].indices {
pu32(&mut buf, i);
}
}
buf
}
pub fn encode_instanced(
meshes: &[Mesh],
collated: &Collated,
entity_id: impl Fn(usize) -> u32,
color: impl Fn(usize) -> [f32; 4],
) -> Vec<u8> {
let refs: Vec<InstanceMeshRef> = meshes
.iter()
.enumerate()
.map(|(i, m)| {
let mut r = InstanceMeshRef::from_mesh(m);
r.entity_id = entity_id(i);
r.color = color(i);
r
})
.collect();
encode_refs(&refs, collated)
}
pub fn collate_and_encode(meshes: &[InstanceMeshRef], min_group: usize, rtc: [f64; 3]) -> Vec<u8> {
let collated = collate_refs(meshes, min_group, rtc);
encode_refs(meshes, &collated)
}
pub fn decode_instanced(bytes: &[u8]) -> Option<DecodedInstanced> {
let ru32 = |o: usize| -> Option<u32> {
bytes.get(o..o + 4).map(|s| u32::from_le_bytes(s.try_into().unwrap()))
};
let rf32 = |o: usize| -> Option<f32> {
bytes.get(o..o + 4).map(|s| f32::from_le_bytes(s.try_into().unwrap()))
};
let rf64 = |o: usize| -> Option<f64> {
bytes.get(o..o + 8).map(|s| f64::from_le_bytes(s.try_into().unwrap()))
};
let version = ru32(4)?;
if ru32(0)? != INSTANCED_MAGIC || version == 0 {
return None;
}
let template_count = ru32(8)? as usize;
let instance_count = ru32(12)? as usize;
let positions_len = ru32(16)? as usize;
let normals_len = ru32(20)? as usize;
let _indices_len = ru32(24)? as usize;
let declared_stride = if version >= 2 { ru32(28)? as usize } else { 0 };
let inst_bytes = if declared_stride == 0 {
INSTANCE_RECORD_BASE_BYTES
} else {
declared_stride
};
if inst_bytes < INSTANCE_RECORD_BASE_BYTES || inst_bytes % 4 != 0 {
return None;
}
let tt_off = HEADER_BYTES;
let it_off = tt_off.checked_add(template_count.checked_mul(TEMPLATE_RECORD_BYTES)?)?;
let data_off = it_off.checked_add(instance_count.checked_mul(inst_bytes)?)?;
let nrm_data = data_off.checked_add(positions_len.checked_mul(4)?)?;
let idx_data = nrm_data.checked_add(normals_len.checked_mul(4)?)?;
if bytes.len() < data_off {
return None;
}
let elem = |base: usize, off: usize, k: usize| -> Option<usize> {
base.checked_add(off.checked_add(k)?.checked_mul(4)?)
};
let mut templates = Vec::with_capacity(template_count);
for t in 0..template_count {
let r = tt_off + t * TEMPLATE_RECORD_BYTES;
let pos_off = ru32(r)? as usize;
let pos_len = ru32(r + 4)? as usize;
let nrm_off = ru32(r + 8)? as usize;
let nrm_len = ru32(r + 12)? as usize;
let i_off = ru32(r + 16)? as usize;
let i_len = ru32(r + 20)? as usize;
let origin = [rf64(r + 24)?, rf64(r + 32)?, rf64(r + 40)?];
let positions = (0..pos_len)
.map(|k| rf32(elem(data_off, pos_off, k)?))
.collect::<Option<Vec<f32>>>()?;
let normals = (0..nrm_len)
.map(|k| rf32(elem(nrm_data, nrm_off, k)?))
.collect::<Option<Vec<f32>>>()?;
let indices = (0..i_len)
.map(|k| ru32(elem(idx_data, i_off, k)?))
.collect::<Option<Vec<u32>>>()?;
templates.push(DecodedTemplate { positions, normals, indices, origin });
}
let mut instances = Vec::with_capacity(instance_count);
for i in 0..instance_count {
let r = it_off + i * inst_bytes;
let template_index = ru32(r)?;
let entity_id = ru32(r + 4)?;
let mut color = [0.0f32; 4];
for (k, c) in color.iter_mut().enumerate() {
*c = rf32(r + 8 + k * 4)?;
}
let mut transform = [0.0f32; 16];
for (k, v) in transform.iter_mut().enumerate() {
*v = rf32(r + 24 + k * 4)?;
}
let item_id = if inst_bytes >= INSTANCE_RECORD_ITEM_ID_BYTES {
Some(ru32(r + INSTANCE_ITEM_ID_OFFSET)?).filter(|&id| id != 0)
} else {
None
};
instances.push(DecodedInstance { template_index, entity_id, color, transform, item_id });
}
Some(DecodedInstanced { templates, instances })
}