use std::borrow::Cow;
use super::ToolPrimitive;
use crate::geometry::plane::GpuFacetPlane;
pub const TOOL_RECORD_VECS: usize = 5;
pub const GPU_MAX_TOOL_PRIMITIVES: usize = super::MAX_TOOL_PRIMITIVES;
const fn discriminants(tool: &ToolPrimitive) -> [f32; 4] {
let (kind, sweep) = (tool.kind as f32, tool.sweep_kind as f32);
[kind, sweep, 0.0, 0.0]
}
#[must_use]
pub const fn pack_tool_record(tool: &ToolPrimitive) -> [[f32; 4]; TOOL_RECORD_VECS] {
[
discriminants(tool),
tool.origin,
tool.axis,
tool.profile,
tool.sweep_dir,
]
}
const fn as_plane_entry(v: [f32; 4]) -> GpuFacetPlane {
GpuFacetPlane {
normal: [v[0], v[1], v[2]],
d: v[3],
}
}
#[must_use]
pub fn pack_planes_and_tools<'a>(
planes: &'a [GpuFacetPlane],
tools: &[ToolPrimitive],
) -> Cow<'a, [GpuFacetPlane]> {
if tools.is_empty() {
return Cow::Borrowed(planes);
}
let mut out = Vec::with_capacity(planes.len() + TOOL_RECORD_VECS * tools.len());
out.extend_from_slice(planes);
for tool in tools {
out.extend(pack_tool_record(tool).into_iter().map(as_plane_entry));
}
Cow::Owned(out)
}
#[must_use]
pub const fn packed_entry_count(plane_count: usize, tool_count: usize) -> usize {
plane_count + TOOL_RECORD_VECS * tool_count
}
#[cfg(test)]
mod tests {
use super::super::ToolSweep;
use super::*;
use glam::Vec3;
fn plane(i: u32) -> GpuFacetPlane {
let x = i as f32;
GpuFacetPlane {
normal: [x, 0.0, 1.0],
d: -x,
}
}
#[test]
fn no_tools_borrows_the_planes_unchanged() {
let planes = [plane(1), plane(2)];
let packed = pack_planes_and_tools(&planes, &[]);
assert!(matches!(packed, Cow::Borrowed(_)));
assert_eq!(&*packed, &planes);
}
#[test]
fn tools_follow_the_planes_five_entries_each() {
let planes = [plane(1), plane(2), plane(3)];
let ball = ToolPrimitive::ball(Vec3::new(0.1, 0.2, 0.3), 0.25);
let cyl = ToolPrimitive::cylinder(Vec3::new(1.0, 2.0, 3.0), Vec3::X, 0.5, 0.75).with_sweep(
ToolSweep::AcrossAxis,
0.125,
Vec3::Y,
);
let packed = pack_planes_and_tools(&planes, &[ball, cyl]);
assert_eq!(packed.len(), packed_entry_count(3, 2));
assert_eq!(&packed[..3], &planes);
let entry = |i: usize| {
let e = packed[i];
[e.normal[0], e.normal[1], e.normal[2], e.d]
};
assert_eq!(entry(3), [0.0, 0.0, 0.0, 0.0]);
assert_eq!(entry(4), ball.origin);
assert_eq!(entry(5), ball.axis);
assert_eq!(entry(6), ball.profile);
assert_eq!(entry(7), ball.sweep_dir);
assert_eq!(entry(8), [1.0, 2.0, 0.0, 0.0]);
assert_eq!(entry(9), cyl.origin);
assert_eq!(entry(10), cyl.axis);
assert_eq!(entry(11), cyl.profile);
assert_eq!(entry(12), cyl.sweep_dir);
}
#[test]
fn every_kind_and_sweep_discriminant_round_trips_through_f32() {
for kind in 0..=3_u32 {
for sweep in 0..=2_u32 {
let mut tool = ToolPrimitive::ball(Vec3::ZERO, 1.0);
tool.kind = kind;
tool.sweep_kind = sweep;
let rec = pack_tool_record(&tool);
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "exact small non-negative floats, as the shader's `u32(...)` conversion"
)]
let (k, s) = (rec[0][0] as u32, rec[0][1] as u32);
assert_eq!((k, s), (kind, sweep));
}
}
}
#[test]
fn a_record_is_five_sixteen_byte_entries() {
assert_eq!(size_of::<GpuFacetPlane>() * TOOL_RECORD_VECS, 80);
assert_eq!(size_of::<ToolPrimitive>(), 80);
}
}