volume.rs
9.5 kB · rust · 283 lines
1use super::sample::{membership, pack};2use super::stack::merge;3use super::{Combine, Spec};4use crate::core::error::{value_error, Result};5use crate::core::tensor::Tensor;6use serde::{Deserialize, Serialize};78/// A cubic grid of f32 samples, x-major.9#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]10pub struct Volume {11 /// The samples, x-major, then y, then z.12 pub data: Vec<f32>,13 /// The side in samples.14 pub size: usize,15}1617/// A plane through the unit box, framed for sampling: its centre, its two in-plane axes and the width of the square window that holds the whole section, all in the box `[-1, 1]^3`.18#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]19pub struct Frame {20 /// The point of the plane the window is centred on.21 pub centre: [f64; 3],22 /// The unit axis the window's columns run along.23 pub u: [f64; 3],24 /// The unit axis the window's rows run along.25 pub v: [f64; 3],26 /// The unit normal.27 pub normal: [f64; 3],28 /// The side of the square window.29 pub width: f64,30}3132fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {33 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]34}3536fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {37 [38 a[1] * b[2] - a[2] * b[1],39 a[2] * b[0] - a[0] * b[2],40 a[0] * b[1] - a[1] * b[0],41 ]42}4344fn unit(a: [f64; 3]) -> Option<[f64; 3]> {45 let n = dot(a, a).sqrt();46 (n > 1e-12).then(|| [a[0] / n, a[1] / n, a[2] / n])47}4849impl Volume {50 /// Builds a zeroed volume of the side.51 pub fn new(size: usize) -> Volume {52 Volume {53 data: vec![0.0; size * size * size],54 size,55 }56 }57 /// Wraps x-major samples of the side.58 ///59 /// # Errors60 ///61 /// Errors when the count is not the side cubed.62 pub fn from_data(data: Vec<f32>, size: usize) -> Result<Volume> {63 if data.len() != size * size * size {64 return value_error("data must be size*size*size.");65 }66 Ok(Volume { data, size })67 }68 /// Returns the smallest sample.69 pub fn min(&self) -> f32 {70 self.data.iter().cloned().fold(f32::INFINITY, f32::min)71 }72 /// Returns the largest sample.73 pub fn max(&self) -> f32 {74 self.data.iter().cloned().fold(f32::NEG_INFINITY, f32::max)75 }76 /// Reads the sample at a voxel.77 pub fn at(&self, x: usize, y: usize, z: usize) -> f32 {78 self.data[(x * self.size + y) * self.size + z]79 }80 /// Reads the voxel a point of the unit cube falls in, or zero outside it.81 pub fn sample(&self, p: [f64; 3]) -> Option<f32> {82 if p.iter().any(|&c| !(0.0..1.0).contains(&c)) {83 return None;84 }85 let s = self.size as f64;86 Some(self.at(87 (p[0] * s) as usize,88 (p[1] * s) as usize,89 (p[2] * s) as usize,90 ))91 }92 /// Thresholds into a byte tensor: one where a sample reaches the level, zero below.93 pub fn solid(&self, level: f32) -> Tensor {94 let mut grid = Tensor::new(vec![self.size; 3]);95 for (flat, &v) in self.data.iter().take(grid.size()).enumerate() {96 grid.put(flat, i64::from(v >= level));97 }98 grid99 }100 /// Counts the samples at or above the level.101 pub fn count(&self, level: f32) -> usize {102 self.data.iter().filter(|&&v| v >= level).count()103 }104 /// Samples the plane of the frame on an out by out window: the values row by row, and one byte per pixel saying whether it lies inside the cube.105 pub fn plane(&self, frame: &Frame, out: usize) -> (Vec<f32>, Vec<u8>) {106 let mut values = Vec::with_capacity(out * out);107 let mut inside = Vec::with_capacity(out * out);108 for i in 0..out {109 for j in 0..out {110 let a = ((j as f64 + 0.5) / out as f64 - 0.5) * frame.width;111 let b = ((i as f64 + 0.5) / out as f64 - 0.5) * frame.width;112 let p: Vec<f64> = (0..3)113 .map(|k| (frame.centre[k] + a * frame.u[k] + b * frame.v[k] + 1.0) / 2.0)114 .collect();115 match self.sample([p[0], p[1], p[2]]) {116 Some(v) => {117 values.push(v);118 inside.push(1);119 }120 None => {121 values.push(0.0);122 inside.push(0);123 }124 }125 }126 }127 (values, inside)128 }129}130131/// Frames the plane normal to the direction, at the offset from zero to one across the box along it; the window is the smallest square holding every section on that normal.132///133/// # Errors134///135/// Errors on a zero normal.136pub fn frame(normal: [f64; 3], offset: f64) -> Result<Frame> {137 let Some(n) = unit(normal) else {138 return value_error("the normal must not be zero.");139 };140 let seed = if n[0].abs() < 0.9 {141 [1.0, 0.0, 0.0]142 } else {143 [0.0, 1.0, 0.0]144 };145 let u = unit(cross(seed, n)).unwrap();146 let v = cross(n, u);147 let corners = (0..8).map(|c| {148 [149 if c & 1 == 0 { -1.0 } else { 1.0 },150 if c & 2 == 0 { -1.0 } else { 1.0 },151 if c & 4 == 0 { -1.0 } else { 1.0 },152 ]153 });154 let mut span = [[f64::INFINITY, f64::NEG_INFINITY]; 3];155 for c in corners {156 for (k, axis) in [u, v, n].iter().enumerate() {157 let t = dot(c, *axis);158 span[k][0] = span[k][0].min(t);159 span[k][1] = span[k][1].max(t);160 }161 }162 let mid = |k: usize| (span[k][0] + span[k][1]) / 2.0;163 let depth = span[2][0] + offset.clamp(0.0, 1.0) * (span[2][1] - span[2][0]);164 let centre = [165 mid(0) * u[0] + mid(1) * v[0] + depth * n[0],166 mid(0) * u[1] + mid(1) * v[1] + depth * n[1],167 mid(0) * u[2] + mid(1) * v[2] + depth * n[2],168 ];169 Ok(Frame {170 centre,171 u,172 v,173 normal: n,174 width: (span[0][1] - span[0][0]).max(span[1][1] - span[1][0]),175 })176}177178fn layer(spec: Spec, number: usize, level: usize, size: usize) -> Result<Vec<bool>> {179 let Spec {180 code,181 base: q,182 dimension: d,183 } = spec;184 if d != 3 {185 return value_error("a volume needs dimension 3.");186 }187 let table = membership(code, q, 3)?;188 let mut mask = vec![true; size * size * size];189 let inv = 1.0 / size as f64;190 for k in 0..level.max(1) {191 let s = (number * q.pow(k as u32)) as f64;192 let residue =193 |i: usize| ((s * (i as f64 + 0.5) * inv).floor() as i64).rem_euclid(q as i64) as usize;194 let digits: Vec<usize> = (0..size).map(residue).collect();195 for x in 0..size {196 for y in 0..size {197 for z in 0..size {198 let cell = (x * size + y) * size + z;199 if mask[cell] && !table[pack(&[digits[x], digits[y], digits[z]], q)] {200 mask[cell] = false;201 }202 }203 }204 }205 }206 Ok(mask)207}208209/// Layers one cube design at several side numbers into a volume under the chosen combine.210///211/// # Errors212///213/// Errors at size zero, or on a code out of range.214pub fn volume(215 spec: Spec,216 numbers: &[usize],217 combine: Combine,218 level: usize,219 size: usize,220) -> Result<Volume> {221 if size == 0 {222 return value_error("size must be at least 1.");223 }224 let mut acc = vec![0.0f32; size * size * size];225 let mut first = true;226 for &n in numbers {227 let mask = layer(spec, n, level, size)?;228 merge(&mut acc, &mask, combine, first);229 first = false;230 }231 Volume::from_data(acc, size)232}233234#[cfg(test)]235mod tests {236 use super::*;237 use crate::math::bang::corners_to_code;238239 fn low() -> Spec {240 Spec::new(corners_to_code(&[vec![0, 0, 0]], 3, 2).get(), 2, 3)241 }242243 #[test]244 fn the_low_corner_at_scale_one_fills_the_first_octant() {245 let v = volume(low(), &[1], Combine::Sum, 1, 4).unwrap();246 assert_eq!(v.count(1.0), 64);247 let v = volume(low(), &[2], Combine::Sum, 1, 4).unwrap();248 assert_eq!(v.count(1.0), 8);249 assert_eq!(v.at(0, 0, 0), 1.0);250 assert_eq!(v.at(3, 0, 0), 0.0);251 }252253 #[test]254 fn the_sponge_stack_counts_its_layers() {255 let sponge = Spec::new(23, 2, 3);256 let v = volume(sponge, &[1, 3], Combine::Sum, 1, 9).unwrap();257 assert_eq!((v.min(), v.max()), (1.0, 2.0));258 assert_eq!(v.count(2.0), 20 * 27);259 let x = volume(sponge, &[1, 3], Combine::Xor, 1, 9).unwrap();260 assert_eq!(x.count(1.0), 7 * 27);261 let a = volume(sponge, &[3, 9], Combine::And, 1, 9).unwrap();262 assert_eq!(a.count(1.0), 64 + 240 + 48);263 assert!(volume(Spec::new(1, 2, 2), &[1], Combine::Sum, 1, 4).is_err());264 assert!(volume(sponge, &[1], Combine::Sum, 1, 0).is_err());265 }266267 #[test]268 fn the_diagonal_frame_holds_the_hexagon() {269 let f = frame([1.0, 1.0, 1.0], 0.5).unwrap();270 assert!((dot(f.centre, f.normal)).abs() < 1e-12);271 assert!((dot(f.u, f.v)).abs() < 1e-12);272 assert!((f.width - 2.0 * (8.0f64 / 3.0).sqrt()).abs() < 1e-9);273 let solid = Volume::from_data(vec![1.0; 27], 3).unwrap();274 let (_, inside) = solid.plane(&f, 200);275 let share = inside.iter().map(|&b| b as f64).sum::<f64>() / 40000.0;276 let hexagon = 3.0 * 3f64.sqrt() / (f.width * f.width);277 assert!((share - hexagon).abs() < 0.01, "{share} {hexagon}");278 let x = frame([1.0, 0.0, 0.0], 0.25).unwrap();279 assert!((x.centre[0] + 0.5).abs() < 1e-12);280 assert_eq!(x.width, 2.0);281 assert!(frame([0.0, 0.0, 0.0], 0.5).is_err());282 }283}