Rework to split layer generation and triangle rendering.

This commit is contained in:
dongar committed 2024-06-12 22:25:44 -05:00
1 parent fa7b9345fe
commit 860ac88b61
6 files changed
+304 -86

No files matched your search

Generated
+22
View File
@@ -145,6 +145,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"approx", "approx",
"clap", "clap",
"serde",
"serde_derive",
"stl_io", "stl_io",
] ]
@@ -175,6 +177,26 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "serde"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "stl_io" name = "stl_io"
version = "0.7.0" version = "0.7.0"
+2
View File
@@ -6,4 +6,6 @@ edition = "2021"
[dependencies] [dependencies]
approx = "0.5.1" approx = "0.5.1"
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
serde = "1.0.203"
serde_derive = "1.0.203"
stl_io = "0.7.0" stl_io = "0.7.0"
+94
View File
@@ -0,0 +1,94 @@
use stl_io::{self, Vertex};
use crate::mushroom::Mushroom;
#[derive(Debug, PartialEq, Clone)]
pub struct Layer {
/// Stalk height in millimeters. Zero for default.
pub center: Vertex,
/// Stalk diameter in millimeters. Zero for default.
pub radius: f32,
}
impl Layer {
pub fn new() -> Self {
return Self {
center: Vertex::new([1.0, 0.0, 0.0]),
radius: 0.0,
};
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Layers {
pub base: Vertex,
pub layers: Vec<Layer>,
pub top: Vertex,
}
impl Layers {
pub fn max_radius(&self) -> f32 {
let mut max_radius: f32 = 0.0;
for layer in &self.layers {
max_radius = max_radius.max(layer.radius);
}
return max_radius;
}
}
fn stalk_layers(mushroom: &Mushroom, base: &Vertex, _resolution: f32) -> Vec<Layer> {
let stalk_top = Vertex::new([base[0], base[1], base[2] + mushroom.stalk_h]);
return [
Layer {
center: base.clone(),
radius: mushroom.stalk_r,
},
Layer {
center: stalk_top,
radius: mushroom.stalk_r,
},
]
.to_vec();
}
fn cap_layers(mushroom: &Mushroom, base: &Vertex, _resolution: f32) -> Vec<Layer> {
let top = Vertex::new([base[0], base[1], base[2] + mushroom.cap_h]);
return [
Layer {
center: base.clone(),
radius: mushroom.cap_r,
},
Layer {
center: top,
radius: mushroom.cap_r,
},
]
.to_vec();
}
pub fn mushroom(mushroom: &Mushroom, resolution: f32) -> Layers {
let _vertical_steps = (mushroom.height() / resolution) as u32;
let base = Vertex::new([0.0, 0.0, 0.0]);
// Get layers for the stalk.
let mut stalk_layers = stalk_layers(mushroom, &base, resolution);
// Get layers for the cap.
let cap_base = stalk_layers.last().unwrap().center;
let mut cap_layers = cap_layers(mushroom, &cap_base, resolution);
// The very top of the mushroom is directly above the top layer of the cap (or even with it)
// at the exact height of the total mushroom.
let cap_top = &cap_layers.last().unwrap().center;
let top = Vertex::new([cap_top[0], cap_top[1], base[2] + mushroom.height()]);
// Assemble the final Layers object.
let mut layers: Vec<Layer> = Vec::new();
layers.append(&mut stalk_layers);
layers.append(&mut cap_layers);
return Layers { base, top, layers };
}
+76 -47
View File
@@ -1,3 +1,4 @@
pub mod layers;
pub mod mushroom; pub mod mushroom;
pub mod render; pub mod render;
@@ -6,8 +7,30 @@ use clap::Parser;
use std::fs::OpenOptions; use std::fs::OpenOptions;
extern crate approx; extern crate approx;
pub use serde_derive::{Deserialize, Serialize};
// TODO: Investigate https://github.com/virtualritz/polyhedron-ops. fn main() {
let args = Args::parse();
// Define the mushroom.
let mushroom = mushroom_from_args(&args);
// Render the mushroom.
// let mesh = render::cylinder(mushroom.stalk_h, mushroom.stalk_d, 128);
let layers = layers::mushroom(&mushroom, 0.1);
let mesh = render::render(&layers, 0.1);
// Save output to file.
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&args.output)
.unwrap();
stl_io::write_stl(&mut file, mesh.iter()).unwrap();
println!("Generated from: {:?}", mushroom);
}
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@@ -35,6 +58,10 @@ struct Args {
/// Stalk diameter in millimeters. Zero for default. /// Stalk diameter in millimeters. Zero for default.
#[arg(long, default_value_t = 0)] #[arg(long, default_value_t = 0)]
cap_d: u32, cap_d: u32,
/// Shape of the cap.
#[arg(long, default_value_t, value_enum)]
cap_style: mushroom::CapStyle,
} }
fn mushroom_from_args(args: &Args) -> Mushroom { fn mushroom_from_args(args: &Args) -> Mushroom {
@@ -48,52 +75,33 @@ fn mushroom_from_args(args: &Args) -> Mushroom {
args.height as f32 * 0.66 args.height as f32 * 0.66
} }
}; };
let stalk_d: f32 = if args.stalk_d != 0 { let stalk_r: f32 = if args.stalk_d != 0 {
args.stalk_d as f32 args.stalk_d as f32
} else { } else {
args.height as f32 / 10. args.height as f32 / 10.
}; } / 2.0; // computer the diamter, and convert to radius.
let cap_h: f32 = if args.cap_h != 0 { let cap_h: f32 = if args.cap_h != 0 {
args.cap_h as f32 args.cap_h as f32
} else { } else {
args.height as f32 - stalk_h args.height as f32 - stalk_h
}; };
let cap_d: f32 = if args.cap_d != 0 { let cap_r: f32 = if args.cap_d != 0 {
args.cap_d as f32 args.cap_d as f32
} else { } else {
args.height as f32 args.height as f32
}; } / 2.0; // computer the diamter, and convert to radius.
let cap_style = args.cap_style.clone();
Mushroom { Mushroom {
stalk_h, stalk_h,
stalk_d, stalk_r,
cap_h, cap_h,
cap_d, cap_r,
cap_style,
} }
} }
fn main() {
let args = Args::parse();
// Define the mushroom.
let mushroom = mushroom_from_args(&args);
// Render the mushroom.
// let mesh = render::cylinder(mushroom.stalk_h, mushroom.stalk_d, 128);
let mesh = render::mushroom(&mushroom);
// Save output to file.
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&args.output)
.unwrap();
stl_io::write_stl(&mut file, mesh.iter()).unwrap();
println!("Generated from: {:?}", mushroom);
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -102,9 +110,10 @@ mod tests {
fn validate_mushroom_from_args<I, T>( fn validate_mushroom_from_args<I, T>(
string_args: I, string_args: I,
stalk_h: f32, stalk_h: f32,
stalk_d: f32, stalk_r: f32,
cap_h: f32, cap_h: f32,
cap_d: f32, cap_r: f32,
cap_style: mushroom::CapStyle,
) where ) where
I: IntoIterator<Item = T>, I: IntoIterator<Item = T>,
T: Into<OsString> + Clone, T: Into<OsString> + Clone,
@@ -115,31 +124,47 @@ mod tests {
result, result,
Mushroom { Mushroom {
stalk_h, stalk_h,
stalk_d, stalk_r,
cap_h, cap_h,
cap_d cap_r,
cap_style,
} }
); );
} }
#[test] #[test]
fn defaults() { fn defaults() {
validate_mushroom_from_args(["prog"], 66., 10., 34., 100.); validate_mushroom_from_args(
["prog"],
66.0,
5.0,
34.0,
50.0,
mushroom::CapStyle::Cylinder,
);
} }
#[test] #[test]
fn height_only() { fn height_only() {
validate_mushroom_from_args(["prog", "--height", "200"], 132., 20., 68., 200.); validate_mushroom_from_args(
["prog", "--height", "200"],
132.0,
10.0,
68.0,
100.0,
mushroom::CapStyle::Cylinder,
);
} }
#[test] #[test]
fn height_stalk_h() { fn height_stalk_h() {
validate_mushroom_from_args( validate_mushroom_from_args(
["prog", "--height", "200", "--stalk-h", "20"], ["prog", "--height", "200", "--stalk-h", "20"],
20., 20.0,
20., 10.0,
180., 180.0,
200., 100.0,
mushroom::CapStyle::Cylinder,
); );
} }
@@ -147,10 +172,11 @@ mod tests {
fn height_cap_h() { fn height_cap_h() {
validate_mushroom_from_args( validate_mushroom_from_args(
["prog", "--height", "200", "--cap-h", "20"], ["prog", "--height", "200", "--cap-h", "20"],
180., 180.0,
20., 10.0,
20., 20.0,
200., 100.0,
mushroom::CapStyle::Cylinder,
); );
} }
@@ -169,11 +195,14 @@ mod tests {
"40", "40",
"--cap-d", "--cap-d",
"50", "50",
"--cap-style",
"dome",
], ],
20., 20.0,
30., 15.0,
40., 40.0,
50., 25.0,
mushroom::CapStyle::Dome,
); );
} }
} }
+26 -2
View File
@@ -1,14 +1,38 @@
use serde_derive::Serialize;
#[derive(clap::ValueEnum, Clone, Default, Debug, Serialize, PartialEq)]
pub enum CapStyle {
#[default]
Cylinder,
Dome,
Pointed,
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Mushroom { pub struct Mushroom {
/// Stalk height in millimeters. Zero for default. /// Stalk height in millimeters. Zero for default.
pub stalk_h: f32, pub stalk_h: f32,
/// Stalk diameter in millimeters. Zero for default. /// Stalk diameter in millimeters. Zero for default.
pub stalk_d: f32, pub stalk_r: f32,
/// Cap height in millimeters. Zero for default. /// Cap height in millimeters. Zero for default.
pub cap_h: f32, pub cap_h: f32,
/// Stalk diameter in millimeters. Zero for default. /// Stalk diameter in millimeters. Zero for default.
pub cap_d: f32, pub cap_r: f32,
pub cap_style: CapStyle,
}
impl Mushroom {
/// Total height of the mushroom.
pub fn height(&self) -> f32 {
return self.stalk_h + self.cap_h;
}
/// Maximum radius of the mushroom.
pub fn radius(&self) -> f32 {
return self.stalk_r.max(self.cap_r) / 2.0;
}
} }
+84 -37
View File
@@ -1,6 +1,6 @@
use stl_io::{self, Normal, Triangle, Vertex}; use stl_io::{self, Normal, Triangle, Vertex};
use crate::mushroom::Mushroom; use crate::layers::{Layer, Layers};
fn new_triangle(p1: &Vertex, p2: &Vertex, p3: &Vertex) -> stl_io::Triangle { fn new_triangle(p1: &Vertex, p2: &Vertex, p3: &Vertex) -> stl_io::Triangle {
// Set Vector U to (Triangle.p2 minus Triangle.p1) // Set Vector U to (Triangle.p2 minus Triangle.p1)
@@ -40,10 +40,10 @@ fn vertex_on_circle(center: &Vertex, radius: f32, angle: f32) -> Vertex {
return Vertex::new([x, y, z]); return Vertex::new([x, y, z]);
} }
fn vertexes_on_circle(center: &Vertex, radius: f32, steps: u32) -> Vec<Vertex> { fn vertexes_on_circle(layer: &Layer, steps: u32) -> Vec<Vertex> {
return radian_steps(steps) return radian_steps(steps)
.iter() .iter()
.map(|angle| vertex_on_circle(center, radius, *angle)) .map(|angle| vertex_on_circle(&layer.center, layer.radius, *angle))
.collect(); .collect();
} }
@@ -80,46 +80,35 @@ fn tube_top(center: &Vertex, edge: &Vec<Vertex>) -> Vec<Triangle> {
return tube_base(&center, &edge); return tube_base(&center, &edge);
} }
pub fn mushroom(mushroom: &Mushroom) -> Vec<Triangle> { pub fn render(layers: &Layers, resolution: f32) -> Vec<Triangle> {
// Max size of any flat surface in mm. // Top and Bottom must be a single point, and must be at least one middle layer.
let resolution: f32 = 1.0; assert!(!layers.layers.is_empty());
// We always use the same number of diameter steps to ensure the number of circular vertices doesn't change, // How many vertexes per circle. IE: the resolution of the circle.
// which might create small breaks in our model. let circle_steps =
let diameter_steps = std::cmp::max( ((layers.max_radius() * std::f32::consts::TAU) / resolution).max(64.0) as u32;
(mushroom.cap_d * std::f32::consts::TAU * resolution) as u32,
64,
);
let stalk_base_center = Vertex::new([0.0, 0.0, 0.0]); let mut layer_vertexes: Vec<Vec<Vertex>> = Vec::new();
let stalk_base_edge = vertexes_on_circle(&stalk_base_center, mushroom.stalk_d, diameter_steps);
let stalk_top_center = Vertex::new([
stalk_base_center[0],
stalk_base_center[1],
stalk_base_center[2] + mushroom.stalk_h,
]);
let stalk_top_edge = vertexes_on_circle(&stalk_top_center, mushroom.stalk_d, diameter_steps);
let cap_base_edge = vertexes_on_circle(&stalk_top_center, mushroom.cap_d, diameter_steps);
let cap_top_center = Vertex::new([
stalk_top_center[0],
stalk_top_center[1],
stalk_top_center[2] + mushroom.cap_h,
]);
let cap_top_edge = vertexes_on_circle(&cap_top_center, mushroom.cap_d, diameter_steps);
let mut result = Vec::new(); for layer in &layers.layers {
layer_vertexes.push(vertexes_on_circle(&layer, circle_steps));
}
result.append(&mut tube_base(&stalk_base_center, &stalk_base_edge)); let mut result: Vec<Triangle> = Vec::new();
result.append(&mut tube_sides(&stalk_base_edge, &stalk_top_edge));
result.append(&mut tube_sides(&stalk_top_edge, &cap_base_edge)); result.append(&mut tube_base(&layers.base, &layer_vertexes[0]));
result.append(&mut tube_sides(&cap_base_edge, &cap_top_edge)); for i in 0..layer_vertexes.len() - 1 {
result.append(&mut tube_top(&cap_top_center, &cap_top_edge)); result.append(&mut tube_sides(&layer_vertexes[i], &layer_vertexes[i + 1]));
}
result.append(&mut tube_top(&layers.top, &layer_vertexes.last().unwrap()));
return result; return result;
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use approx::relative_eq;
use super::*; use super::*;
#[test] #[test]
@@ -196,7 +185,13 @@ mod tests {
#[test] #[test]
fn vertexes_on_circle_basic() { fn vertexes_on_circle_basic() {
let val = vertexes_on_circle(&Vertex::new([0.0, 0.0, 0.0]), 1.0, 4); let val = vertexes_on_circle(
&Layer {
center: Vertex::new([0.0, 0.0, 0.0]),
radius: 1.0,
},
4,
);
let expected = [ let expected = [
Vertex::new([1.0, 0.0, 0.0]), Vertex::new([1.0, 0.0, 0.0]),
@@ -211,7 +206,13 @@ mod tests {
#[test] #[test]
fn vertexes_on_circle_complex() { fn vertexes_on_circle_complex() {
let val = vertexes_on_circle(&Vertex::new([4.0, 5.0, 6.0]), 3.0, 4); let val = vertexes_on_circle(
&Layer {
center: Vertex::new([4.0, 5.0, 6.0]),
radius: 3.0,
},
4,
);
let expected = [ let expected = [
Vertex::new([7.0, 5.0, 6.0]), Vertex::new([7.0, 5.0, 6.0]),
@@ -229,7 +230,13 @@ mod tests {
// fn tube_base(center: &Vertex, edge: &Vec<Vertex>) -> Vec<Triangle> { // fn tube_base(center: &Vertex, edge: &Vec<Vertex>) -> Vec<Triangle> {
let center = Vertex::new([0.0, 0.0, 0.0]); let center = Vertex::new([0.0, 0.0, 0.0]);
let edge = vertexes_on_circle(&center, 1.0, 4); let edge = vertexes_on_circle(
&Layer {
center: center.clone(),
radius: 1.0,
},
4,
);
let triangles = tube_base(&center, &edge); let triangles = tube_base(&center, &edge);
@@ -244,7 +251,13 @@ mod tests {
#[test] #[test]
fn tube_top_test() { fn tube_top_test() {
let center = Vertex::new([0.0, 0.0, 0.0]); let center = Vertex::new([0.0, 0.0, 0.0]);
let edge = vertexes_on_circle(&center, 1.0, 4); let edge = vertexes_on_circle(
&Layer {
center: center.clone(),
radius: 1.0,
},
4,
);
let triangles = tube_top(&center, &edge); let triangles = tube_top(&center, &edge);
@@ -255,4 +268,38 @@ mod tests {
assert_eq!(t.normal, Vertex::new([0.0, 0.0, 1.0])); assert_eq!(t.normal, Vertex::new([0.0, 0.0, 1.0]));
} }
} }
#[test]
fn render_test() {
let layers = Layers {
base: Vertex::new([0.0, 0.0, 0.0]),
layers: [
Layer {
center: Vertex::new([0.0, 0.0, 1.0]),
radius: 1.0,
},
Layer {
center: Vertex::new([0.0, 0.0, 2.0]),
radius: 1.0,
},
]
.to_vec(),
top: Vertex::new([0.0, 0.0, 3.0]),
};
let mesh = render(&layers, 10.0); // Large resolution ensures minimum steps of 64.
// 64 triangles for top, 128 for sides, 64 for bottom.
assert_eq!(mesh.len(), 64 + 128 + 64);
for i in 0..mesh.len() {
if i < 64 {
assert!(mesh[i].normal[2] < 0.0) // Bottom triangles point down.
} else if i < (64 + 128) {
assert!(mesh[i].normal[2] == 0.0) // side triangles point out.
} else {
assert!(mesh[i].normal[2] > 0.0) // top triangles point up.
}
}
}
} }