use awsm_renderer_core::pipeline::primitive::FrontFace;
const BARYCENTRICS: [[f32; 2]; 3] = [[1.0, 0.0], [0.0, 1.0], [0.0, 0.0]];
const SYNTHETIC_TANGENT: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
pub fn pack_visibility_bytes(
positions: &[[f32; 3]],
normals: &[[f32; 3]],
tangents: Option<&[[f32; 4]]>,
indices: &[u32],
front_face: FrontFace,
) -> Vec<u8> {
let corner_order: [usize; 3] = match front_face {
FrontFace::Cw => [0, 2, 1],
_ => [0, 1, 2],
};
let triangle_count = indices.len() / 3;
let mut out = Vec::with_capacity(triangle_count * 3 * 56);
for (triangle_index, tri) in indices.chunks_exact(3).enumerate() {
for (slot, &corner) in corner_order.iter().enumerate() {
let vertex_index = tri[corner];
let v = vertex_index as usize;
let pos = positions[v];
let normal = normals[v];
let bary = BARYCENTRICS[corner_order[slot]];
let tan = tangents.map(|t| t[v]).unwrap_or(SYNTHETIC_TANGENT);
out.extend_from_slice(&pos[0].to_le_bytes());
out.extend_from_slice(&pos[1].to_le_bytes());
out.extend_from_slice(&pos[2].to_le_bytes());
out.extend_from_slice(&(triangle_index as u32).to_le_bytes());
out.extend_from_slice(&bary[0].to_le_bytes());
out.extend_from_slice(&bary[1].to_le_bytes());
out.extend_from_slice(&normal[0].to_le_bytes());
out.extend_from_slice(&normal[1].to_le_bytes());
out.extend_from_slice(&normal[2].to_le_bytes());
out.extend_from_slice(&tan[0].to_le_bytes());
out.extend_from_slice(&tan[1].to_le_bytes());
out.extend_from_slice(&tan[2].to_le_bytes());
out.extend_from_slice(&tan[3].to_le_bytes());
out.extend_from_slice(&vertex_index.to_le_bytes());
}
}
out
}
pub fn pack_visibility_slot_bytes(
positions: &[[f32; 3]],
normals: &[[f32; 3]],
corner_indices: &[u32],
pool_slot: usize,
page_verts: usize,
front_face: FrontFace,
out: &mut Vec<u8>,
) {
out.clear();
debug_assert_eq!(
corner_indices.len(),
page_verts,
"slot must be page_verts long"
);
let corner_order: [usize; 3] = match front_face {
FrontFace::Cw => [0, 2, 1],
_ => [0, 1, 2],
};
let tris_per_slot = page_verts / 3;
let base_triangle = pool_slot * tris_per_slot;
for (local_triangle, tri) in corner_indices.chunks_exact(3).enumerate() {
let triangle_index = (base_triangle + local_triangle) as u32;
for &corner in corner_order.iter() {
let vertex_index = tri[corner];
let v = vertex_index as usize;
let pos = positions[v];
let normal = normals[v];
let bary = BARYCENTRICS[corner];
let tan = SYNTHETIC_TANGENT;
out.extend_from_slice(&pos[0].to_le_bytes());
out.extend_from_slice(&pos[1].to_le_bytes());
out.extend_from_slice(&pos[2].to_le_bytes());
out.extend_from_slice(&triangle_index.to_le_bytes());
out.extend_from_slice(&bary[0].to_le_bytes());
out.extend_from_slice(&bary[1].to_le_bytes());
out.extend_from_slice(&normal[0].to_le_bytes());
out.extend_from_slice(&normal[1].to_le_bytes());
out.extend_from_slice(&normal[2].to_le_bytes());
out.extend_from_slice(&tan[0].to_le_bytes());
out.extend_from_slice(&tan[1].to_le_bytes());
out.extend_from_slice(&tan[2].to_le_bytes());
out.extend_from_slice(&tan[3].to_le_bytes());
out.extend_from_slice(&vertex_index.to_le_bytes());
}
}
}
pub fn pack_transparency_bytes(
positions: &[[f32; 3]],
normals: &[[f32; 3]],
tangents: Option<&[[f32; 4]]>,
vertex_count: usize,
) -> Vec<u8> {
let mut out = Vec::with_capacity(vertex_count * 40);
for (v, normal) in normals.iter().enumerate().take(vertex_count) {
let pos = positions[v];
let tan = tangents.map(|t| t[v]).unwrap_or(SYNTHETIC_TANGENT);
out.extend_from_slice(&pos[0].to_le_bytes());
out.extend_from_slice(&pos[1].to_le_bytes());
out.extend_from_slice(&pos[2].to_le_bytes());
out.extend_from_slice(&normal[0].to_le_bytes());
out.extend_from_slice(&normal[1].to_le_bytes());
out.extend_from_slice(&normal[2].to_le_bytes());
out.extend_from_slice(&tan[0].to_le_bytes());
out.extend_from_slice(&tan[1].to_le_bytes());
out.extend_from_slice(&tan[2].to_le_bytes());
out.extend_from_slice(&tan[3].to_le_bytes());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn tri() -> (Vec<[f32; 3]>, Vec<[f32; 3]>, Vec<[f32; 4]>, Vec<u32>) {
(
vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]],
vec![[0.0, 0.0, 1.0]; 3],
vec![[1.0, 0.0, 0.0, 1.0]; 3],
vec![0, 1, 2],
)
}
#[test]
fn visibility_layout_is_56_bytes_per_corner() {
let (p, n, t, i) = tri();
let bytes = pack_visibility_bytes(&p, &n, Some(&t), &i, FrontFace::Ccw);
assert_eq!(bytes.len(), 56 * 3, "56 bytes per exploded corner");
let read_f32 = |off: usize| f32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
assert_eq!([read_f32(0), read_f32(4), read_f32(8)], [1.0, 2.0, 3.0]);
assert_eq!(u32::from_le_bytes(bytes[12..16].try_into().unwrap()), 0);
assert_eq!(u32::from_le_bytes(bytes[52..56].try_into().unwrap()), 0);
assert_eq!(u32::from_le_bytes(bytes[108..112].try_into().unwrap()), 1);
}
#[test]
fn transparency_layout_is_40_bytes_per_vertex() {
let (p, n, t, _) = tri();
let bytes = pack_transparency_bytes(&p, &n, Some(&t), 3);
assert_eq!(bytes.len(), 40 * 3, "40 bytes per vertex");
let read_f32 = |off: usize| f32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
assert_eq!([read_f32(0), read_f32(4), read_f32(8)], [1.0, 2.0, 3.0]);
assert_eq!([read_f32(12), read_f32(16), read_f32(20)], [0.0, 0.0, 1.0]);
assert_eq!(read_f32(36), 1.0); }
#[test]
fn cw_front_face_swizzles_corners_and_barycentrics() {
let (p, n, t, i) = tri();
let ccw = pack_visibility_bytes(&p, &n, Some(&t), &i, FrontFace::Ccw);
let cw = pack_visibility_bytes(&p, &n, Some(&t), &i, FrontFace::Cw);
assert_eq!(&cw[0..56], &ccw[0..56], "slot 0 identical");
assert_eq!(&cw[56..112], &ccw[112..168], "slot 1 = ccw corner 2");
assert_eq!(&cw[112..168], &ccw[56..112], "slot 2 = ccw corner 1");
}
#[test]
fn none_tangents_pack_synthetic() {
let (p, n, _, i) = tri();
let bytes = pack_visibility_bytes(&p, &n, None, &i, FrontFace::Ccw);
let read_f32 = |off: usize| f32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
assert_eq!(
[read_f32(36), read_f32(40), read_f32(44), read_f32(48)],
[0.0, 0.0, 0.0, 1.0]
);
}
fn rf(b: &[u8], off: usize) -> f32 {
f32::from_le_bytes(b[off..off + 4].try_into().unwrap())
}
fn ru(b: &[u8], off: usize) -> u32 {
u32::from_le_bytes(b[off..off + 4].try_into().unwrap())
}
fn vis_corner(b: &[u8], rec: usize) -> ([f32; 3], u32, [f32; 2], [f32; 3], [f32; 4], u32) {
let o = rec * 56;
(
[rf(b, o), rf(b, o + 4), rf(b, o + 8)], ru(b, o + 12), [rf(b, o + 16), rf(b, o + 20)], [rf(b, o + 24), rf(b, o + 28), rf(b, o + 32)], [rf(b, o + 36), rf(b, o + 40), rf(b, o + 44), rf(b, o + 48)], ru(b, o + 52), )
}
#[test]
fn pack_field_decode_and_visibility_transparency_parity() {
let positions = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let normals = vec![
[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9],
[-0.1, -0.2, -0.3],
];
let tangents = vec![
[1.0, 0.0, 0.0, 1.0],
[0.0, 1.0, 0.0, -1.0],
[0.0, 0.0, 1.0, 1.0],
[0.5, 0.5, 0.0, -1.0],
];
let indices = vec![0u32, 1, 2, 0, 2, 3];
let vis = pack_visibility_bytes(
&positions,
&normals,
Some(&tangents),
&indices,
FrontFace::Ccw,
);
assert_eq!(vis.len(), 56 * 6, "2 tris × 3 corners");
for (rec, (tri, corner)) in [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
.into_iter()
.enumerate()
{
let orig = indices[tri * 3 + corner] as usize;
let (pos, tri_idx, bary, normal, tangent, ovi) = vis_corner(&vis, rec);
assert_eq!(pos, positions[orig], "corner {rec} position");
assert_eq!(
normal, normals[orig],
"corner {rec} normal (per-vertex index)"
);
assert_eq!(
tangent, tangents[orig],
"corner {rec} tangent (per-vertex index)"
);
assert_eq!(bary, BARYCENTRICS[corner], "corner {rec} barycentric");
assert_eq!(tri_idx, tri as u32, "corner {rec} triangle_index");
assert_eq!(ovi, orig as u32, "corner {rec} original_vertex_index");
}
let tr = pack_transparency_bytes(&positions, &normals, Some(&tangents), positions.len());
assert_eq!(tr.len(), 40 * 4);
for v in 0..positions.len() {
let o = v * 40;
let pos = [rf(&tr, o), rf(&tr, o + 4), rf(&tr, o + 8)];
let normal = [rf(&tr, o + 12), rf(&tr, o + 16), rf(&tr, o + 20)];
let tangent = [
rf(&tr, o + 24),
rf(&tr, o + 28),
rf(&tr, o + 32),
rf(&tr, o + 36),
];
assert_eq!(pos, positions[v], "transparency v{v} position");
assert_eq!(normal, normals[v], "transparency v{v} normal");
assert_eq!(tangent, tangents[v], "transparency v{v} tangent");
}
}
#[test]
fn slot_pack_matches_full_packer_except_triangle_index() {
let positions = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[2.0, 0.0, 0.0],
[2.0, 1.0, 0.0],
];
let normals = vec![
[0.1, 0.0, 0.9],
[0.2, 0.0, 0.8],
[0.3, 0.0, 0.7],
[0.4, 0.0, 0.6],
[0.5, 0.0, 0.5],
[0.6, 0.0, 0.4],
];
let page_verts = 6usize; let slot_indices = vec![0u32, 1, 2, 1, 4, 5];
let full = pack_visibility_bytes(&positions, &normals, None, &slot_indices, FrontFace::Ccw);
let mut slot0 = Vec::new();
pack_visibility_slot_bytes(
&positions,
&normals,
&slot_indices,
0,
page_verts,
FrontFace::Ccw,
&mut slot0,
);
assert_eq!(slot0, full, "slot 0 is byte-identical to the full packer");
assert_eq!(slot0.len(), 56 * page_verts);
let mut slot3 = Vec::new();
pack_visibility_slot_bytes(
&positions,
&normals,
&slot_indices,
3,
page_verts,
FrontFace::Ccw,
&mut slot3,
);
for rec in 0..page_verts {
let o = rec * 56;
assert_eq!(
u32::from_le_bytes(slot3[o + 12..o + 16].try_into().unwrap()),
6 + (rec / 3) as u32,
"slot 3 record {rec} triangle_index"
);
assert_eq!(slot3[o..o + 12], slot0[o..o + 12], "record {rec} pos");
assert_eq!(
slot3[o + 16..o + 56],
slot0[o + 16..o + 56],
"record {rec} rest"
);
}
pack_visibility_slot_bytes(
&positions,
&normals,
&slot_indices,
0,
page_verts,
FrontFace::Ccw,
&mut slot3,
);
assert_eq!(slot3, slot0, "reused buffer cleared + refilled correctly");
}
}