use cadrum::{DVec3, Solid};
use std::f64::consts::TAU;
use std::io::{Read, Seek};
use crate::Result;
use crate::artifact::Artifact;
use crate::vmec::{NormalKind, VmecData};
type SurfaceFn<'a> = Box<dyn Fn(f64, f64) -> [f64; 3] + 'a>;
struct LayerSpec<'a> {
name: &'static str,
color: &'static str,
surface: SurfaceFn<'a>,
}
pub struct VesselBuilder<'a> {
grid_phi: usize,
grid_theta: usize,
scale: f64,
layers: Vec<LayerSpec<'a>>,
}
impl<'a> VesselBuilder<'a> {
pub fn builder(grid_phi: usize, grid_theta: usize, scale: f64) -> Self {
VesselBuilder {
grid_phi,
grid_theta,
scale,
layers: Vec::new(),
}
}
pub fn layer<F>(mut self, name: &'static str, color: &'static str, surface: F) -> Self
where
F: Fn(f64, f64) -> [f64; 3] + 'a,
{
self.layers.push(LayerSpec {
name,
color,
surface: Box::new(surface),
});
self
}
pub fn build(self) -> Result<Vec<Artifact>> {
let m = self.grid_phi;
let n = self.grid_theta;
println!(
"Building {} nested filled solids (scale = {}, grid = {}×{})...",
self.layers.len(),
self.scale,
m,
n
);
let mut full_solids: Vec<Solid> = Vec::with_capacity(self.layers.len());
let mut layer_points: Vec<Vec<DVec3>> = Vec::with_capacity(self.layers.len());
for (i, spec) in self.layers.iter().enumerate() {
println!(" [{}] sampling layer {}", i, spec.name);
let pts: Vec<DVec3> = (0..m)
.flat_map(|ii| {
let phi = TAU * (ii as f64) / (m as f64);
(0..n).map(move |jj| {
let theta = TAU * (jj as f64) / (n as f64);
DVec3::from((spec.surface)(phi, theta)) * self.scale
})
})
.collect();
let solid = Solid::bspline(m, n, true, |ii, jj| pts[ii * n + jj])
.map_err(|e| format!("bspline #{}: {:?}", i, e))?;
full_solids.push(solid);
layer_points.push(pts);
}
let mut artifacts: Vec<Artifact> = Vec::with_capacity(self.layers.len());
for (i, spec) in self.layers.iter().enumerate() {
println!("Building layer: {}", spec.name);
let solids: Vec<Solid> = if i == 0 {
vec![full_solids[0].clone()]
} else {
(&full_solids[i] - &full_solids[i - 1])
.build_vec()
.map_err(|e| format!("subtract {}: {:?}", spec.name, e))?
};
if solids.is_empty() {
return Err(format!("layer {} produced no solid", spec.name).into());
}
let colored: Vec<Solid> = solids.into_iter().map(|s| s.color(spec.color)).collect();
artifacts.push(Artifact {
name: spec.name.to_string(),
solids: colored,
points: layer_points[i].clone(),
});
}
Ok(artifacts)
}
}
pub fn run(input: impl Read + Seek + 'static, scale: f64) -> Result<Vec<Artifact>> {
let vmec = VmecData::load(input)?;
println!(
" ns = {}, mnmax = {}, s_max in grid = {}",
vmec.s_grid.len(),
vmec.mode_poloidal.len(),
vmec.s_grid.last().unwrap()
);
const M_TORO: usize = 128;
const N_POLO: usize = 48;
const THICK_FW_M: f64 = 0.05;
const THICK_BREEDER_M: f64 = 0.50;
const THICK_BACK_WALL_M: f64 = 0.05;
const THICK_SHIELD_M: f64 = 0.50;
const THICK_VV_M: f64 = 0.10;
let normal = NormalKind::Planar;
let wall_s = 1.08;
let o0 = 0.0;
let o1 = THICK_FW_M;
let o2 = o1 + THICK_BREEDER_M;
let o3 = o2 + THICK_BACK_WALL_M;
let o4 = o3 + THICK_SHIELD_M;
let o5 = o4 + THICK_VV_M;
VesselBuilder::builder(M_TORO, N_POLO, scale)
.layer("chamber", "#FF0000", |phi, theta| vmec.interpolate(phi, theta, wall_s, o0, normal))
.layer("first_wall", "#FF4D00", |phi, theta| vmec.interpolate(phi, theta, wall_s, o1, normal))
.layer("breeder", "#FF9900", |phi, theta| vmec.interpolate(phi, theta, wall_s, o2, normal))
.layer("back_wall", "#FFE500", |phi, theta| vmec.interpolate(phi, theta, wall_s, o3, normal))
.layer("shield", "#CCFF00", |phi, theta| vmec.interpolate(phi, theta, wall_s, o4, normal))
.layer("vacuum_vessel", "#80FF00", |phi, theta| vmec.interpolate(phi, theta, wall_s, o5, normal))
.build()
}