Split into bin and lib, add clap, binary format
This commit is contained in:
169
src/bin.rs
Normal file
169
src/bin.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate byteorder;
|
||||
extern crate icosahedron;
|
||||
|
||||
use std::fs::{File, metadata};
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use byteorder::{WriteBytesExt, LittleEndian};
|
||||
use icosahedron::{Polyhedron};
|
||||
|
||||
fn write_to_binary_file(polyhedron: Polyhedron, path: &Path) {
|
||||
let bin_file = File::create(path).expect("Can't create file");
|
||||
let mut writer = BufWriter::new(bin_file);
|
||||
let write_error_message = "Error encountered while writing to binary file";
|
||||
writer.write_u32::<LittleEndian>(polyhedron.positions.len() as u32)
|
||||
.expect(write_error_message);
|
||||
writer.write_u32::<LittleEndian>(polyhedron.cells.len() as u32)
|
||||
.expect(write_error_message);
|
||||
for position in polyhedron.positions.iter() {
|
||||
writer.write_f32::<LittleEndian>(position.0.x).expect(write_error_message);
|
||||
writer.write_f32::<LittleEndian>(position.0.y).expect(write_error_message);
|
||||
writer.write_f32::<LittleEndian>(position.0.z).expect(write_error_message);
|
||||
}
|
||||
for normal in polyhedron.normals.iter() {
|
||||
writer.write_f32::<LittleEndian>(normal.0.x).expect(write_error_message);
|
||||
writer.write_f32::<LittleEndian>(normal.0.y).expect(write_error_message);
|
||||
writer.write_f32::<LittleEndian>(normal.0.z).expect(write_error_message);
|
||||
}
|
||||
for color in polyhedron.colors.iter() {
|
||||
writer.write_f32::<LittleEndian>(color.0.x).expect(write_error_message);
|
||||
writer.write_f32::<LittleEndian>(color.0.y).expect(write_error_message);
|
||||
writer.write_f32::<LittleEndian>(color.0.z).expect(write_error_message);
|
||||
}
|
||||
for cell in polyhedron.cells.iter() {
|
||||
writer.write_u32::<LittleEndian>(cell.a as u32).expect(write_error_message);
|
||||
writer.write_u32::<LittleEndian>(cell.b as u32).expect(write_error_message);
|
||||
writer.write_u32::<LittleEndian>(cell.c as u32).expect(write_error_message);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_json_file(polyhedron: Polyhedron, path: &Path) {
|
||||
let mut json_file = File::create(path).expect("Can't create file");
|
||||
let json = serde_json::to_string(&polyhedron).expect("Problem serializing");
|
||||
json_file.write_all(json.as_bytes()).expect("Can't write to file");
|
||||
}
|
||||
|
||||
fn generate_files(dir: &str, format: Format, truncated: bool, colored: bool,
|
||||
param_list: Vec<(f32, u32)>) {
|
||||
let mesh_type = if truncated {
|
||||
"hexsphere"
|
||||
} else {
|
||||
"icosahedron"
|
||||
};
|
||||
|
||||
for param in param_list {
|
||||
println!(
|
||||
"Generating {} with radius {} and detail {}...",
|
||||
mesh_type, param.0, param.1
|
||||
);
|
||||
|
||||
|
||||
let polyhedron = if truncated {
|
||||
let mut hexsphere = Polyhedron::new_truncated_isocahedron(param.0, param.1);
|
||||
hexsphere.compute_triangle_normals();
|
||||
hexsphere
|
||||
} else {
|
||||
let mut icosahedron = Polyhedron::new_isocahedron(param.0, param.1);
|
||||
icosahedron.compute_triangle_normals();
|
||||
icosahedron
|
||||
};
|
||||
|
||||
let colored_polyhedron = if colored {
|
||||
let mut colored = Polyhedron::new();
|
||||
colored.unique_vertices(polyhedron);
|
||||
colored.assign_random_face_colors();
|
||||
colored
|
||||
} else {
|
||||
polyhedron
|
||||
};
|
||||
|
||||
println!("triangles: {}", colored_polyhedron.cells.len());
|
||||
println!("vertices: {}", colored_polyhedron.positions.len());
|
||||
|
||||
let filename = Path::new(dir).join(
|
||||
format!("{}_r{}_d{}.{}", mesh_type, param.0, param.1, format.extension())
|
||||
);
|
||||
match format {
|
||||
Format::Bin => write_to_binary_file(colored_polyhedron, &filename),
|
||||
Format::Json => write_to_json_file(colored_polyhedron, &filename),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
arg_enum!{
|
||||
#[derive(Debug)]
|
||||
enum Format {
|
||||
Json,
|
||||
Bin,
|
||||
}
|
||||
}
|
||||
|
||||
impl Format {
|
||||
fn extension(&self) -> String {
|
||||
match self {
|
||||
Format::Bin => "bin".to_string(),
|
||||
Format::Json => "json".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let dir_exists = |path: String| {
|
||||
let path_clone = path.clone();
|
||||
match metadata(path) {
|
||||
Ok(metadata) => {
|
||||
if metadata.is_dir() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(String::from(format!("Output '{}' is not a directory", &path_clone)))
|
||||
}
|
||||
}
|
||||
Err(_) => Err(String::from(format!("Directory '{}' doesn't exist", &path_clone)))
|
||||
}
|
||||
};
|
||||
|
||||
let matches = clap_app!(icosahedron =>
|
||||
(version: "1.0")
|
||||
(author: "Tyler Hallada <tyler@hallada.net>")
|
||||
(about: "Generates 3D icosahedra meshes")
|
||||
(@arg truncated: -t --truncated "Generate truncated icosahedra (hexspheres).")
|
||||
(@arg colored: -c --colored "Assigns a random color to every face \
|
||||
(increases vertices count).")
|
||||
(@arg detail: -d --detail +takes_value default_value("7")
|
||||
"Maximum detail level to generate. \
|
||||
Each level multiplies the number of triangles by 4.")
|
||||
(@arg radius: -r --radius +takes_value default_value("1.0")
|
||||
"Radius of the polyhedron,")
|
||||
(@arg format: -f --format +takes_value possible_values(&Format::variants())
|
||||
default_value("Bin")
|
||||
"Format to write the files in.")
|
||||
(@arg output: [OUTPUT] {dir_exists} default_value("output/")
|
||||
"Directory to write the output files to.")
|
||||
).get_matches();
|
||||
|
||||
let truncated = matches.is_present("truncated");
|
||||
let colored = matches.is_present("colored");
|
||||
let detail = value_t!(matches.value_of("detail"), u32).unwrap_or(7);
|
||||
let radius = value_t!(matches.value_of("radius"), f32).unwrap_or(1.0);
|
||||
let format = value_t!(matches.value_of("format"), Format).unwrap_or(Format::Bin);
|
||||
let output = matches.value_of("output").unwrap_or("output/");
|
||||
|
||||
let param_list = |detail: u32, radius: f32| -> Vec<(f32, u32)> {
|
||||
let mut params = vec![];
|
||||
for detail in 0..(detail + 1) {
|
||||
params.push((radius, detail));
|
||||
}
|
||||
params
|
||||
};
|
||||
|
||||
generate_files(
|
||||
output,
|
||||
format,
|
||||
truncated,
|
||||
colored,
|
||||
param_list(detail, radius),
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,22 @@
|
||||
extern crate cgmath;
|
||||
extern crate rand;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::ops::AddAssign;
|
||||
use std::path::Path;
|
||||
|
||||
use cgmath::prelude::*;
|
||||
use cgmath::Vector3;
|
||||
use rand::prelude::*;
|
||||
use serde::ser::{SerializeSeq, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
const VERT_CACHE_PRECISION: f32 = 10000_f32;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Triangle {
|
||||
a: usize,
|
||||
b: usize,
|
||||
c: usize,
|
||||
pub struct Triangle {
|
||||
pub a: usize,
|
||||
pub b: usize,
|
||||
pub c: usize,
|
||||
}
|
||||
|
||||
impl Triangle {
|
||||
@@ -41,13 +40,14 @@ impl Serialize for Triangle {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ArraySerializedVector(Vector3<f32>);
|
||||
pub struct ArraySerializedVector(pub Vector3<f32>);
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct Polyhedron {
|
||||
positions: Vec<ArraySerializedVector>,
|
||||
cells: Vec<Triangle>,
|
||||
normals: Vec<ArraySerializedVector>,
|
||||
pub struct Polyhedron {
|
||||
pub positions: Vec<ArraySerializedVector>,
|
||||
pub cells: Vec<Triangle>,
|
||||
pub normals: Vec<ArraySerializedVector>,
|
||||
pub colors: Vec<ArraySerializedVector>,
|
||||
#[serde(skip)]
|
||||
added_vert_cache: HashMap<(i32, i32, i32), usize>,
|
||||
faces: Vec<Vec<usize>>,
|
||||
@@ -74,17 +74,18 @@ impl AddAssign for ArraySerializedVector {
|
||||
}
|
||||
|
||||
impl Polyhedron {
|
||||
fn new() -> Polyhedron {
|
||||
pub fn new() -> Polyhedron {
|
||||
Polyhedron {
|
||||
positions: vec![],
|
||||
cells: vec![],
|
||||
normals: vec![],
|
||||
colors: vec![],
|
||||
added_vert_cache: HashMap::new(),
|
||||
faces: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn new_isocahedron(radius: f32, detail: usize) -> Polyhedron {
|
||||
pub fn new_isocahedron(radius: f32, detail: u32) -> Polyhedron {
|
||||
let t = (1.0 + (5.0 as f32).sqrt()) / 2.0;
|
||||
let mut base_isocahedron = Polyhedron {
|
||||
positions: vec![],
|
||||
@@ -111,6 +112,7 @@ impl Polyhedron {
|
||||
Triangle::new(9, 8, 1),
|
||||
],
|
||||
normals: vec![],
|
||||
colors: vec![],
|
||||
added_vert_cache: HashMap::new(),
|
||||
faces: vec![],
|
||||
};
|
||||
@@ -129,17 +131,18 @@ impl Polyhedron {
|
||||
|
||||
let mut subdivided = Polyhedron::new();
|
||||
subdivided.subdivide(base_isocahedron, radius, detail);
|
||||
subdivided.triangles_to_faces();
|
||||
subdivided
|
||||
}
|
||||
|
||||
fn new_truncated_isocahedron(radius: f32, detail: usize) -> Polyhedron {
|
||||
pub fn new_truncated_isocahedron(radius: f32, detail: u32) -> Polyhedron {
|
||||
let isocahedron = Polyhedron::new_isocahedron(radius, detail);
|
||||
let mut truncated_isocahedron = Polyhedron::new();
|
||||
truncated_isocahedron.truncated(isocahedron);
|
||||
truncated_isocahedron
|
||||
}
|
||||
|
||||
fn subdivide(&mut self, other: Polyhedron, radius: f32, detail: usize) {
|
||||
fn subdivide(&mut self, other: Polyhedron, radius: f32, detail: u32) {
|
||||
for triangle in other.cells {
|
||||
let a = other.positions[triangle.a].0;
|
||||
let b = other.positions[triangle.b].0;
|
||||
@@ -148,15 +151,21 @@ impl Polyhedron {
|
||||
}
|
||||
}
|
||||
|
||||
fn triangles_to_faces(&mut self) {
|
||||
for (cell_index, _) in self.cells.iter().enumerate() {
|
||||
self.faces.push(vec![cell_index]);
|
||||
}
|
||||
}
|
||||
|
||||
fn subdivide_triangle(
|
||||
&mut self,
|
||||
a: Vector3<f32>,
|
||||
b: Vector3<f32>,
|
||||
c: Vector3<f32>,
|
||||
radius: f32,
|
||||
detail: usize,
|
||||
detail: u32,
|
||||
) {
|
||||
let cols = 2usize.pow(detail as u32);
|
||||
let cols = 2usize.pow(detail);
|
||||
let mut new_vertices: Vec<Vec<Vector3<f32>>> = vec![];
|
||||
|
||||
for i in 0..=cols {
|
||||
@@ -207,6 +216,8 @@ impl Polyhedron {
|
||||
self.positions.push(ArraySerializedVector(vertex));
|
||||
self.normals
|
||||
.push(ArraySerializedVector(Vector3::new(0.0, 0.0, 0.0)));
|
||||
self.colors
|
||||
.push(ArraySerializedVector(Vector3::new(1.0, 1.0, 1.0)));
|
||||
let added_index = self.positions.len() - 1;
|
||||
self.added_vert_cache.insert(vertex_key, added_index);
|
||||
return added_index;
|
||||
@@ -220,7 +231,6 @@ impl Polyhedron {
|
||||
let mut mid_centroid_cache: HashMap<(usize, usize, usize), Vector3<f32>> = HashMap::new();
|
||||
let mut hex_count = 0;
|
||||
let mut pent_count = 0;
|
||||
let mut count = 0;
|
||||
for i in 0..original_vert_count {
|
||||
let faces = &vert_to_faces[&i];
|
||||
if faces.len() == 6 {
|
||||
@@ -285,6 +295,34 @@ impl Polyhedron {
|
||||
println!("pentagons: {}", pent_count);
|
||||
}
|
||||
|
||||
pub fn unique_vertices(&mut self, other: Polyhedron) {
|
||||
for triangle in other.cells {
|
||||
let vertex_a = other.positions[triangle.a].0;
|
||||
let vertex_b = other.positions[triangle.b].0;
|
||||
let vertex_c = other.positions[triangle.c].0;
|
||||
let normal_a = other.normals[triangle.a].0;
|
||||
let normal_b = other.normals[triangle.b].0;
|
||||
let normal_c = other.normals[triangle.c].0;
|
||||
|
||||
self.positions.push(ArraySerializedVector(vertex_a));
|
||||
self.positions.push(ArraySerializedVector(vertex_b));
|
||||
self.positions.push(ArraySerializedVector(vertex_c));
|
||||
self.normals.push(ArraySerializedVector(normal_a));
|
||||
self.normals.push(ArraySerializedVector(normal_b));
|
||||
self.normals.push(ArraySerializedVector(normal_c));
|
||||
self.colors
|
||||
.push(ArraySerializedVector(Vector3::new(1.0, 1.0, 1.0)));
|
||||
self.colors
|
||||
.push(ArraySerializedVector(Vector3::new(1.0, 1.0, 1.0)));
|
||||
self.colors
|
||||
.push(ArraySerializedVector(Vector3::new(1.0, 1.0, 1.0)));
|
||||
let added_index = self.positions.len() - 1;
|
||||
self.cells
|
||||
.push(Triangle::new(added_index - 2, added_index - 1, added_index));
|
||||
}
|
||||
self.faces = other.faces;
|
||||
}
|
||||
|
||||
fn vert_to_faces(&self) -> HashMap<usize, Vec<usize>> {
|
||||
let mut vert_to_faces: HashMap<usize, Vec<usize>> = HashMap::new();
|
||||
for i in 0..self.cells.len() {
|
||||
@@ -375,7 +413,7 @@ impl Polyhedron {
|
||||
None
|
||||
}
|
||||
|
||||
fn compute_triangle_normals(&mut self) {
|
||||
pub fn compute_triangle_normals(&mut self) {
|
||||
let origin = Vector3::new(0.0, 0.0, 0.0);
|
||||
for i in 0..self.cells.len() {
|
||||
let vertex_a = &self.positions[self.cells[i].a].0;
|
||||
@@ -406,7 +444,7 @@ impl Polyhedron {
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_face_normals(&mut self) {
|
||||
pub fn compute_face_normals(&mut self) {
|
||||
let origin = Vector3::new(0.0, 0.0, 0.0);
|
||||
for i in 0..self.faces.len() {
|
||||
let first_cell = &self.cells[self.faces[i][0]];
|
||||
@@ -442,6 +480,31 @@ impl Polyhedron {
|
||||
*normal = ArraySerializedVector(normal.0.normalize());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_random_face_colors(&mut self) {
|
||||
let mut rng = rand::thread_rng();
|
||||
for i in 0..self.faces.len() {
|
||||
let face_color = Vector3::new(rng.gen(), rng.gen(), rng.gen());
|
||||
|
||||
for c in 0..self.faces[i].len() {
|
||||
let face_cell = &self.cells[self.faces[i][c]];
|
||||
|
||||
self.colors[face_cell.a] = ArraySerializedVector(face_color);
|
||||
self.colors[face_cell.b] = ArraySerializedVector(face_color);
|
||||
self.colors[face_cell.c] = ArraySerializedVector(face_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_cells(&self) -> Vec<u32> {
|
||||
let mut cell_vec: Vec<u32> = vec![];
|
||||
for cell in &self.cells {
|
||||
cell_vec.push(cell.a as u32);
|
||||
cell_vec.push(cell.b as u32);
|
||||
cell_vec.push(cell.c as u32);
|
||||
}
|
||||
cell_vec
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_centroid(pa: Vector3<f32>, pb: Vector3<f32>, pc: Vector3<f32>) -> Vector3<f32> {
|
||||
@@ -461,71 +524,3 @@ fn find_center_of_triangles(
|
||||
center_point /= triangle_indices.len() as f32;
|
||||
center_point
|
||||
}
|
||||
|
||||
fn generate_icosahedron_files(dir: &str, param_list: Vec<(f32, usize)>) {
|
||||
for param in param_list {
|
||||
println!(
|
||||
"Generating icosahedron with radius {} and detail {}...",
|
||||
param.0, param.1
|
||||
);
|
||||
let filename = Path::new(dir).join(format!("icosahedron_r{}_d{}.json", param.0, param.1));
|
||||
let mut file = File::create(filename).expect("Can't create file");
|
||||
let mut icosahedron = Polyhedron::new_isocahedron(param.0, param.1);
|
||||
icosahedron.compute_triangle_normals();
|
||||
println!("triangles: {}", icosahedron.cells.len());
|
||||
println!("vertices: {}", icosahedron.positions.len());
|
||||
let icosahedron_json = serde_json::to_string(&icosahedron).expect("Problem serializing");
|
||||
file.write_all(icosahedron_json.as_bytes())
|
||||
.expect("Can't write to file");
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_hexsphere_files(dir: &str, param_list: Vec<(f32, usize)>) {
|
||||
for param in param_list {
|
||||
println!(
|
||||
"Generating hexsphere with radius {} and detail {}...",
|
||||
param.0, param.1
|
||||
);
|
||||
let filename = Path::new(dir).join(format!("hexsphere_r{}_d{}.json", param.0, param.1));
|
||||
let mut file = File::create(filename).expect("Can't create file");
|
||||
let mut hexsphere = Polyhedron::new_truncated_isocahedron(param.0, param.1);
|
||||
hexsphere.compute_triangle_normals();
|
||||
// hexsphere.compute_face_normals();
|
||||
println!("triangles: {}", hexsphere.cells.len());
|
||||
println!("vertices: {}", hexsphere.positions.len());
|
||||
let hexsphere_json = serde_json::to_string(&hexsphere).expect("Problem serializing");
|
||||
file.write_all(hexsphere_json.as_bytes())
|
||||
.expect("Can't write to file");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
generate_hexsphere_files(
|
||||
"output/",
|
||||
vec![
|
||||
(1.0, 0),
|
||||
(1.0, 1),
|
||||
(1.0, 2),
|
||||
(1.0, 3),
|
||||
(1.0, 4),
|
||||
(1.0, 5),
|
||||
(1.0, 6),
|
||||
(1.0, 7),
|
||||
// (1.0, 8),
|
||||
// (1.0, 9),
|
||||
],
|
||||
);
|
||||
generate_icosahedron_files(
|
||||
"output/",
|
||||
vec![
|
||||
(1.0, 0),
|
||||
(1.0, 1),
|
||||
(1.0, 2),
|
||||
(1.0, 3),
|
||||
(1.0, 4),
|
||||
(1.0, 5),
|
||||
(1.0, 6),
|
||||
(1.0, 7),
|
||||
],
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user