#![allow(clippy::print_literal)]
mod old;
use super::*;
use crate::generation2::disp::Displacement;
use crate::prelude::{Material, Side, Solid, Vector2};
use crate::utils::Vec2d;
use crate::utils::{IterWithNext, OneOrVec};
#[inline]
fn clamp_promote(radius: f32, sides: &mut u32, options: &SolidOptions) -> bool {
let clamped_sides = radius as u32 / 2;
let mut changed = !options.allow_frac && options.frac_promote && (clamped_sides > *sides);
if changed {
*sides = clamped_sides;
}
if *sides < 3 {
*sides = 3;
changed = true;
}
changed
}
fn ellipse_verts_2d(
center: Vector2<f32>, x_radius: f32, y_radius: f32, mut num_sides: u32, options: &SolidOptions,
) -> impl ExactSizeIterator<Item = Vector2<f32>> + Clone {
assert!(x_radius > 0.0 && y_radius > 0.0, "cannot make ellipse of zero radius");
let changed = clamp_promote((x_radius).min(y_radius), &mut num_sides, options);
if changed {
eprintln!("[{}:{}] Warning Ellipse: Sides clamped to {}. Colinear ellipse points, too small/too many sides. Ellipse(x:{:.1},y:{:.1})",
file!(), line!(), num_sides, x_radius, y_radius);
}
let allow_frac = options.allow_frac;
let delta_angle = std::f64::consts::TAU / num_sides as f64;
(0..num_sides).map(move |n| {
let angle = delta_angle * (n + 1) as f64;
let x = x_radius as f64 * -angle.cos() + center.x as f64;
let y = y_radius as f64 * angle.sin() + center.y as f64;
Vector2::new_with_round(x as f32, y as f32, allow_frac)
})
}
pub fn ellipse_verts(
center: Vector3<f32>, x_radius: f32, y_radius: f32, mut num_sides: u32, options: &SolidOptions,
) -> impl ExactSizeIterator<Item = Vector3<f32>> + Clone {
let changed = clamp_promote((x_radius).min(y_radius), &mut num_sides, options);
if changed {
eprintln!("[{}:{}] Warning Ellipse: Sides clamped to {}. Colinear ellipse points, too small/too many sides. Ellipse(x:{:.1},y:{:.1})",
file!(), line!(), num_sides, x_radius, y_radius);
}
let allow_frac = options.allow_frac;
let delta_angle = std::f64::consts::TAU / num_sides as f64;
(0..num_sides).map(move |n| {
let angle = delta_angle * (n + 1) as f64;
let x = x_radius as f64 * -angle.cos() + center.x as f64;
let y = y_radius as f64 * angle.sin() + center.y as f64;
let z = center.z;
Vector3::new_with_round(x as f32, y as f32, z, allow_frac)
})
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Grouping {
#[default]
Auto,
Single,
Group,
}
pub fn prism<'a, I1, I2>(
top: I1, bottom: I2, prefer_top: bool, mats: [&'a Material<'a>; 3], options: &'a SolidOptions,
) -> impl Iterator<Item = Side<'a>> + Clone + 'a
where
I1: Iterator<Item = Vector3<f32>> + Clone + 'a,
I2: Iterator<Item = Vector3<f32>> + Clone + 'a,
{
#[inline(always)] fn iter_to_three<I, T>(mut iter: I) -> Option<[T; 3]>
where
I: Iterator<Item = T>,
{
Some([iter.next()?, iter.next()?, iter.next()?])
}
let [pt1, pt2, pt3] = iter_to_three(top.clone()).expect("Must be at least 3 len");
let is_top_one_point = pt1 == pt2 && pt2 == pt3;
let top_side = (!is_top_one_point).then_some(Side::new_verts(pt1, pt2, pt3, mats[0], options));
let [pt1, pt2, pt3] = iter_to_three(bottom.clone()).expect("Must be at least 3 len");
let is_bottom_one_point = pt1 == pt2 && pt2 == pt3; let bottom_side =
(!is_bottom_one_point).then_some(Side::new_verts(pt3, pt2, pt1, mats[1], options));
assert!(!(is_top_one_point && is_bottom_one_point), "Degenerate. Prism is a line");
let top_bottom = top_side.into_iter().chain(bottom_side.into_iter());
let points4 = IterWithNext::new(top).zip(IterWithNext::new(bottom));
let sides = points4.map(move |((top1, top2), (bottom1, bottom2))| {
if (is_bottom_one_point || prefer_top) && !(is_top_one_point && prefer_top) {
Side::new_verts(top2, top1, bottom1, mats[2], options)
} else {
Side::new_verts(bottom1, bottom2, top1, mats[2], options)
}
});
top_bottom.chain(sides)
}
#[rustfmt::skip]
pub fn cube<'a>(bounds: &Bounds, materials: &[&Material<'a>; 6], options: &SolidOptions) -> Solid<'a> {
let verts = bounds.verts();
let top = Side::new_verts(verts[SWT].clone(), verts[NWT].clone(), verts[NET].clone(), materials[0], options);
let bottom = Side::new_verts(verts[NEB].clone(), verts[NWB].clone(), verts[SWB].clone(), materials[1], options);
let north = Side::new_verts(verts[NEB].clone(), verts[NET].clone(), verts[NWT].clone(), materials[2], options);
let south = Side::new_verts(verts[SWB].clone(), verts[SWT].clone(), verts[SET].clone(), materials[3], options);
let east = Side::new_verts(verts[SEB].clone(), verts[SET].clone(), verts[NET].clone(), materials[4], options);
let west = Side::new_verts(verts[NWB].clone(), verts[NWT].clone(), verts[SWT].clone(), materials[5], options);
Solid::new(vec![top, bottom, north, south, east, west])
}
#[rustfmt::skip]
pub fn wedge<'a>(bounds: &Bounds, materials: &[&Material<'a>; 5], options: &SolidOptions) -> Solid<'a> {
let verts = bounds.verts();
let slope = Side::new_verts(verts[SWB].clone(), verts[NWB].clone(), verts[NET].clone(), materials[0], options);
let bottom = Side::new_verts(verts[NEB].clone(), verts[NWB].clone(), verts[SWB].clone(), materials[1], options);
let north = Side::new_verts(verts[NEB].clone(), verts[NET].clone(), verts[NWT].clone(), materials[2], options);
let south = Side::new_verts(verts[SWB].clone(), verts[SWT].clone(), verts[SET].clone(), materials[3], options);
let east = Side::new_verts(verts[SEB].clone(), verts[SET].clone(), verts[NET].clone(), materials[4], options);
Solid::new(vec![slope, bottom, north, south, east])
}
#[doc(alias = "cone")]
pub fn spike<'a>(
bounds: &Bounds, sides: u32, mats: [&'a Material<'a>; 2], options: &'a SolidOptions,
) -> OneOrVec<Solid<'a>> {
let x_radius = bounds.x_len() / 2.0;
let y_radius = bounds.y_len() / 2.0;
let top_points = std::iter::repeat(bounds.top_center());
let bottom_points = ellipse_verts(bounds.bottom_center(), x_radius, y_radius, sides, options);
let mats = [mats[0], mats[0], mats[1]];
OneOrVec::One(Solid::new(prism(top_points, bottom_points, false, mats, options).collect()))
}
pub fn cylinder<'a>(
bounds: &Bounds, sides: u32, mats: [&'a Material<'a>; 3], options: &'a SolidOptions,
) -> OneOrVec<Solid<'a>> {
let x_radius = bounds.x_len() / 2.0;
let y_radius = bounds.y_len() / 2.0;
let top_points = ellipse_verts(bounds.top_center(), x_radius, y_radius, sides, options);
let bottom_points = ellipse_verts(bounds.bottom_center(), x_radius, y_radius, sides, options);
OneOrVec::One(Solid::new(prism(top_points, bottom_points, false, mats, options).collect()))
}
pub fn sphere<'a>(
bounds: &Bounds, sides: u32, mats: [&'a Material<'a>; 3], options: &'a SolidOptions,
) -> OneOrVec<Solid<'a>> {
let x_radius = bounds.x_len() / 2.0;
let y_radius = bounds.y_len() / 2.0;
let z_radius = bounds.z_len() / 2.0;
let center = bounds.center();
let delta_angle = std::f64::consts::TAU / sides as f64;
let height_at_angles = (0..=sides).map(move |n| {
let angle = delta_angle * n as f64 / 2.0;
let z = z_radius as f64 * angle.cos();
if options.allow_frac {
z as f32
} else {
(z as f32).round()
}
});
let heights = height_at_angles.collect::<Vec<_>>();
let heights = heights.windows(2);
let layers = heights.map(|height| {
let height_top_from_center = height[0];
let height_bottom_from_center = height[1];
let top_radius_x =
radius_at_sphere_height(x_radius, height_top_from_center, options.allow_frac);
let top_radius_y =
radius_at_sphere_height(y_radius, height_top_from_center, options.allow_frac);
let bottom_radius_x =
radius_at_sphere_height(x_radius, height_bottom_from_center, options.allow_frac);
let bottom_radius_y =
radius_at_sphere_height(y_radius, height_bottom_from_center, options.allow_frac);
let top_center = Vector3 { z: height_top_from_center, ..center };
let bottom_center = Vector3 { z: height_bottom_from_center, ..center };
let top_circle = ellipse_verts(top_center, top_radius_x, top_radius_y, sides, options);
let bottom_circle =
ellipse_verts(bottom_center, bottom_radius_x, bottom_radius_y, sides, options);
Solid::new(prism(top_circle, bottom_circle, false, mats, options).collect::<Vec<_>>())
});
OneOrVec::Vec(layers.collect::<Vec<_>>())
}
fn radius_at_sphere_height(radius: f32, height_from_center: f32, allow_frac: bool) -> f32 {
const EPSILON: f32 = 0.25; if (radius - height_from_center).abs() < EPSILON {
return 0.0;
}
let height = height_from_center;
let radius = f32::sqrt(radius * radius - height * height);
if allow_frac {
radius
} else {
radius.round()
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub(crate) struct SphereOptions {
size: usize,
}
pub fn sphere_disp<'a>(
bounds: &Bounds, mats: [&'a Material<'a>; 1], options: &'a SolidOptions,
) -> OneOrVec<Solid<'a>> {
let mut power = options.power;
if power < 2 {
eprintln!("[sphere_disp()] power clamped to 2");
power = 1;
} else if power > 4 {
eprintln!("[sphere_disp()] power clamped to 4");
power = 4;
};
let size = Displacement::power_to_len(power);
let mut cube = cube(bounds, &[mats[0]; 6], options);
for side in cube.sides.iter_mut() {
let mut disp = Displacement::new_plane(side.plane.clone(), size);
let ideal = disp.ideal_points();
let projected = ideal.inner.iter().map(|p| {
let unit = bounds_to_unit(bounds, p);
let projected = disp::project_unit_cube_to_sphere(&unit);
unit_to_bounds(bounds, &projected)
});
let mut dirs = Vec2d::new(Vec2d::strides(size));
let mut dists = Vec2d::new(Vec2d::strides(size));
let alphas = Vec2d::from_parts(vec![0.0; disp.width * disp.width], Vec2d::strides(size));
for (ideal, projected) in ideal.inner.iter().zip(projected) {
let (mut dir, dist) = ideal.dir_and_dist(&projected);
if dir.x.is_nan() {
dir = Vector3::origin();
}
dirs.inner.push(dir);
dists.inner.push(dist);
}
disp.normals = dirs;
disp.distances = dists;
disp.alphas = alphas;
side.disp = Some(disp);
}
OneOrVec::One(cube)
}
fn bounds_to_unit(bounds: &Bounds, point: &Vector3<f32>) -> Vector3<f32> {
let x = unit_range(bounds.min.x, bounds.max.x, point.x);
let y = unit_range(bounds.min.y, bounds.max.y, point.y);
let z = unit_range(bounds.min.z, bounds.max.z, point.z);
Vector3::new(x, y, z)
}
fn unit_to_bounds(bounds: &Bounds, unit: &Vector3<f32>) -> Vector3<f32> {
let t = vec3_unit_to_multi(unit);
let x = disp::lerp(bounds.min.x, bounds.max.x, t.x);
let y = disp::lerp(bounds.min.y, bounds.max.y, t.y);
let z = disp::lerp(bounds.min.z, bounds.max.z, t.z);
Vector3::new(x, y, z)
}
fn unit_range(bottom: f32, top: f32, value: f32) -> f32 {
assert!(top > bottom);
let v = (value - bottom) / (top - bottom); v * 2.0 - 1.0 }
fn unit_to_multiplier(value: f32) -> f32 {
(value + 1.0) / 2.0
}
fn vec3_unit_to_multi(value: &Vector3<f32>) -> Vector3<f32> {
let x = unit_to_multiplier(value.x);
let y = unit_to_multiplier(value.y);
let z = unit_to_multiplier(value.z);
Vector3::new(x, y, z)
}
#[test]
#[cfg(test)]
fn unit() {
fn test(truth: f32, bottom: f32, top: f32, value: f32) {
assert_eq!(truth, unit_range(bottom, top, value));
assert_eq!(value, disp::lerp(bottom, top, unit_to_multiplier(truth)));
}
test(-1.0, 0.0, 1.0, 0.0);
test(-0.5, 0.0, 1.0, 0.25);
test(0.0, 0.0, 1.0, 0.5);
test(0.5, 0.0, 1.0, 0.75);
test(1.0, 0.0, 1.0, 1.0);
test(-1.0, -200.0, 200.0, -200.0);
test(0.0, -200.0, 200.0, 0.0);
test(1.0, -200.0, 200.0, 200.0);
test(-1.0, -100.0, 200.0, -100.0);
test(0.0, -100.0, 200.0, 50.0);
test(1.0, -100.0, 200.0, 200.0);
}
#[cfg(test)]
mod tests;