use crate::mesh::Mesh;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
#[derive(Clone)]
pub enum CapturedCsgJob {
Single { host: Mesh, cutter: Mesh },
Many { host: Mesh, cutters: Vec<Mesh> },
}
static CAPTURED: Mutex<Vec<CapturedCsgJob>> = Mutex::new(Vec::new());
static CAPTURE_ENABLED: AtomicBool = AtomicBool::new(false);
pub fn set_enabled(on: bool) {
CAPTURE_ENABLED.store(on, Ordering::Relaxed);
}
#[inline]
fn enabled() -> bool {
CAPTURE_ENABLED.load(Ordering::Relaxed)
}
pub fn record_single(host: &Mesh, cutter: &Mesh) {
if !enabled() {
return;
}
if let Ok(mut v) = CAPTURED.lock() {
v.push(CapturedCsgJob::Single { host: host.clone(), cutter: cutter.clone() });
}
}
pub fn record_many(host: &Mesh, cutters: &[&Mesh]) {
if !enabled() {
return;
}
if let Ok(mut v) = CAPTURED.lock() {
v.push(CapturedCsgJob::Many {
host: host.clone(),
cutters: cutters.iter().map(|m| (*m).clone()).collect(),
});
}
}
pub fn drain() -> Vec<CapturedCsgJob> {
CAPTURED.lock().map(|mut v| std::mem::take(&mut *v)).unwrap_or_default()
}
fn push_mesh(out: &mut Vec<u8>, m: &Mesh) {
out.extend_from_slice(&(m.positions.len() as u32).to_le_bytes());
for v in &m.positions {
out.extend_from_slice(&v.to_le_bytes());
}
out.extend_from_slice(&(m.normals.len() as u32).to_le_bytes());
for v in &m.normals {
out.extend_from_slice(&v.to_le_bytes());
}
out.extend_from_slice(&(m.indices.len() as u32).to_le_bytes());
for v in &m.indices {
out.extend_from_slice(&v.to_le_bytes());
}
out.push(m.rtc_applied as u8);
for v in &m.origin {
out.extend_from_slice(&v.to_le_bytes());
}
}
pub fn serialize(jobs: &[CapturedCsgJob]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&(jobs.len() as u32).to_le_bytes());
for j in jobs {
match j {
CapturedCsgJob::Single { host, cutter } => {
out.push(0);
push_mesh(&mut out, host);
push_mesh(&mut out, cutter);
}
CapturedCsgJob::Many { host, cutters } => {
out.push(1);
push_mesh(&mut out, host);
out.extend_from_slice(&(cutters.len() as u32).to_le_bytes());
for c in cutters {
push_mesh(&mut out, c);
}
}
}
}
out
}
struct Reader<'a> {
b: &'a [u8],
p: usize,
}
impl<'a> Reader<'a> {
fn take(&mut self, n: usize) -> Option<&'a [u8]> {
let s = self.b.get(self.p..self.p.checked_add(n)?)?;
self.p += n;
Some(s)
}
fn u32(&mut self) -> Option<u32> {
Some(u32::from_le_bytes(self.take(4)?.try_into().ok()?))
}
fn f32(&mut self) -> Option<f32> {
Some(f32::from_le_bytes(self.take(4)?.try_into().ok()?))
}
fn f64(&mut self) -> Option<f64> {
Some(f64::from_le_bytes(self.take(8)?.try_into().ok()?))
}
fn u8(&mut self) -> Option<u8> {
Some(self.take(1)?[0])
}
fn mesh(&mut self) -> Option<Mesh> {
let n = self.u32()? as usize;
let positions: Vec<f32> = (0..n).map(|_| self.f32()).collect::<Option<_>>()?;
let n = self.u32()? as usize;
let normals: Vec<f32> = (0..n).map(|_| self.f32()).collect::<Option<_>>()?;
let n = self.u32()? as usize;
let indices: Vec<u32> = (0..n).map(|_| self.u32()).collect::<Option<_>>()?;
let rtc_applied = self.u8()? != 0;
let origin = [self.f64()?, self.f64()?, self.f64()?];
Some(Mesh { positions, normals, indices, rtc_applied, origin, instance_meta: None, local_bounds: None, local_to_world: None })
}
}
pub fn deserialize(blob: &[u8]) -> Result<Vec<CapturedCsgJob>, &'static str> {
let mut r = Reader { b: blob, p: 0 };
let n = r.u32().ok_or("truncated blob: missing job count")? as usize;
let mut jobs = Vec::new();
for _ in 0..n {
let tag = r.u8().ok_or("truncated blob: missing tag")?;
let host = r.mesh().ok_or("truncated blob: bad host mesh")?;
match tag {
0 => {
let cutter = r.mesh().ok_or("truncated blob: bad cutter mesh")?;
jobs.push(CapturedCsgJob::Single { host, cutter });
}
1 => {
let nc = r.u32().ok_or("truncated blob: missing cutter count")? as usize;
let cutters: Vec<Mesh> =
(0..nc).map(|_| r.mesh()).collect::<Option<_>>().ok_or("truncated blob: bad cutter mesh")?;
jobs.push(CapturedCsgJob::Many { host, cutters });
}
_ => return Err("invalid job tag"),
}
}
if r.p != blob.len() {
return Err("trailing bytes after last job");
}
Ok(jobs)
}