From 6ac656928475d0ce7afd4f8139ff36237a6d30ee Mon Sep 17 00:00:00 2001 From: raffitz Date: Wed, 21 Jul 2021 16:18:32 +0100 Subject: [PATCH] Add ngon subcommand --- Cargo.lock | 2 +- Cargo.toml | 4 +- src/astree.rs | 45 ++++++++++++- src/error.rs | 1 + src/main.rs | 116 +++++++++++++++++++++++++--------- src/ngon.rs | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/parser.rs | 50 ++------------- 7 files changed, 307 insertions(+), 81 deletions(-) create mode 100644 src/ngon.rs diff --git a/Cargo.lock b/Cargo.lock index 83621c5..d9ef259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,7 +311,7 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "voxelmap" -version = "0.3.3" +version = "0.4.0" dependencies = [ "clap", "lodepng", diff --git a/Cargo.toml b/Cargo.toml index 9838fd1..d5d1089 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "voxelmap" -version = "0.3.3" +version = "0.4.0" authors = ["raffitz "] edition = "2018" -description = "Converts mathematical descriptions of objects to voxel maps" +description = "Creates voxel maps to build voxelised objects" [dependencies] clap = "2.33" diff --git a/src/astree.rs b/src/astree.rs index b57aca5..f97145d 100644 --- a/src/astree.rs +++ b/src/astree.rs @@ -156,8 +156,8 @@ impl Expression { Expression::Var(c) } - pub fn float(f: f64) -> Self { - Expression::Float(f) + pub fn float>(f: T) -> Self { + Expression::Float(f.into()) } pub fn ident(s: String) -> Self { @@ -422,3 +422,44 @@ impl Junction { } } } + +#[derive(Debug)] +pub struct Boundary { + pub var: char, + pub min: Expression, + pub max: Expression, +} + +impl Boundary { + pub fn new( + l: Expression, + lcond: char, + var: char, + rcond: char, + r: Expression, + ) -> Result { + if var != 'x' && var != 'y' && var != 'z' { + return Err(()); + } + if lcond == '<' || lcond == '≤' { + if rcond == '<' || rcond == '≤' { + let min = l; + let max = r; + return Ok(Boundary { var, min, max }); + } + return Err(()); + } else if lcond == '>' || lcond == '≥' { + if rcond == '>' || rcond == '≥' { + let min = r; + let max = l; + return Ok(Boundary { var, min, max }); + } + return Err(()); + } + Err(()) + } +} + +pub type Boundaries = [Boundary; 3]; + +pub type Structure = (Option>, Boundaries, Junction); diff --git a/src/error.rs b/src/error.rs index 5b57752..e595336 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,6 +11,7 @@ pub enum Error { MissingIdentAssignment, MissingVarMap, MissingIdentMap, + MissingSubCommand, IllegalVarInBoundary, IllegarBoundedVar, UnboundedVar, diff --git a/src/main.rs b/src/main.rs index e89a6a6..4aa4b21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; +use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg, SubCommand}; use nbt::encode::write_gzip_compound_tag; use nbt::{CompoundTag, Tag}; use rgb::*; @@ -8,6 +8,7 @@ use std::io::{Error, ErrorKind, Read, Write}; mod astree; mod error; +mod ngon; mod parser; macro_rules! scale_message { @@ -16,6 +17,12 @@ macro_rules! scale_message { }; } +macro_rules! sides_message { + ($n:ident) => { + Err(format!("<{}> is not a valid number of sides", $n)) + }; +} + macro_rules! csv_line { ($f:ident, $n:expr, $t:expr, $c:expr, $s:expr, $b:expr) => { writeln!($f, "{},{},{},{},{}", $n, $t, $c, $s, $b)?; @@ -45,7 +52,7 @@ fn main() -> Result<(), error::Error> { .help("The scale parameter for the object") .takes_value(true) .multiple(false) - .value_name("N") + .value_name("S") .validator(|n: String| -> Result<(), String> { if let Ok(scale) = n.parse::() { if scale >= 0_f64 { @@ -99,7 +106,7 @@ fn main() -> Result<(), error::Error> { Arg::with_name("debug") .short("d") .long("debug") - .help("Show parsing steps") + .help("Show intermediate steps") .takes_value(false) .multiple(false), ) @@ -115,12 +122,44 @@ fn main() -> Result<(), error::Error> { Arg::with_name("test") .short("t") .long("test") - .help("Parses the input file, does not output") + .help("Parses the input, does not output") .takes_value(false) .multiple(false), ) - .arg( - Arg::with_name("FILE") + .subcommand(SubCommand::with_name("ngon") + .about("Make an ngon") + .arg( + Arg::with_name("N") + .help("The number of sides of the ngon") + .required(true) + .index(1) + .validator(|n: String| -> Result<(), String> { + if let Ok(sides) = n.parse::() { + if (3..=100).contains(&sides) { + return Ok(()); + } + } + sides_message!(n) + }), + ) + .arg( + Arg::with_name("OUTPUT_DIR") + .help("The folder where the output images will be stored") + .required(true) + .index(2) + .validator(move |path: String| -> Result<(), String> { + match fs::create_dir(&path) { + Ok(_) => Ok(()), + Err(error) => Err(io_error(error, &path)), + } + }) + .conflicts_with("test"), + ) + ) + .subcommand(SubCommand::with_name("solid") + .about("Read mathematical description of solid") + .arg( + Arg::with_name("FILE") .help("The file describing the shape to map") .required(true) .index(1) @@ -129,10 +168,10 @@ fn main() -> Result<(), error::Error> { Ok(_) => Ok(()), Err(error) => Err(io_error(error, &path)), } - }), - ) - .arg( - Arg::with_name("OUTPUT_DIR") + }) + ) + .arg( + Arg::with_name("OUTPUT_DIR") .help("The folder where the output images will be stored") .required(true) .index(2) @@ -143,13 +182,12 @@ fn main() -> Result<(), error::Error> { } }) .conflicts_with("test"), + ) ) .get_matches(); let scale = matches.value_of("scale").map(|s| s.parse::().unwrap()); - let mut object_description = fs::File::open(matches.value_of("FILE").unwrap()).unwrap(); - let debug = matches.is_present("debug"); let offset = matches.is_present("offset"); @@ -175,30 +213,46 @@ fn main() -> Result<(), error::Error> { let graph = matches.is_present("graph"); let test = matches.is_present("test"); - let output_folder = if test { - "." - } else { - matches.value_of("OUTPUT_DIR").unwrap() - }; + let mut output_folder = "."; - let mut data = String::new(); + let structure: astree::Structure; - let read_count = object_description.read_to_string(&mut data)?; + if let Some(submatches) = matches.subcommand_matches("solid") { + let mut data = String::new(); - if debug { - println!( - "\nRead {} bytes, scale is {}", - read_count, - scale.unwrap_or(1.0_f64) - ); + let mut object_description = fs::File::open(submatches.value_of("FILE").unwrap()).unwrap(); + output_folder = submatches.value_of("OUTPUT_DIR").unwrap_or(output_folder); + + let read_count = object_description.read_to_string(&mut data)?; + + if debug { + println!( + "\nRead {} bytes, scale is {}", + read_count, + scale.unwrap_or(1.0_f64) + ); + } + + structure = parser::parse( + &data, + submatches.value_of("FILE").unwrap(), + submatches.value_of("OUTPUT_DIR"), + debug, + )?; + } else if let Some(submatches) = matches.subcommand_matches("ngon") { + output_folder = submatches.value_of("OUTPUT_DIR").unwrap_or(output_folder); + structure = ngon::generate( + submatches + .value_of("N") + .map(|n| n.parse::().unwrap()) + .unwrap(), + )?; + } else { + println!("{}", matches.usage()); + return Err(Error::MissingSubCommand); } - let (assigns, limits, tree) = parser::parse( - &data, - matches.value_of("FILE").unwrap(), - matches.value_of("OUTPUT_DIR"), - debug, - )?; + let (assigns, limits, tree) = structure; let idents = assigns.unwrap_or_default(); diff --git a/src/ngon.rs b/src/ngon.rs new file mode 100644 index 0000000..57c5beb --- /dev/null +++ b/src/ngon.rs @@ -0,0 +1,170 @@ +use crate::astree; +use crate::astree::{Boundaries, Boundary, Condition, Expression, FunctionType, Junction}; +use crate::error; +use std::collections::HashMap; + +pub fn generate(n: u8) -> Result { + let mut junctions = Vec::::with_capacity(n as usize); + + let mut labels = HashMap::::new(); + let mut vars = HashMap::::new(); + vars.insert('s', 1_f64); + let vars_eval = Some(&vars); + + let double_n: u16 = (n as u16) * 2; + + let mut min_x = 1_f64; + let mut min_x_label = String::from("error"); + let mut max_x = -1_f64; + let mut max_x_label = String::from("error"); + + let mut min_y = 1_f64; + let mut min_y_label = String::from("error"); + let mut max_y = -1_f64; + let mut max_y_label = String::from("error"); + + for i in 0..n { + let angle_label = format!("@angle{}", i); + let angle_exp = Expression::operation( + '/', + Expression::operation( + '*', + Expression::float(i), + Expression::float(std::f64::consts::TAU), + ), + Expression::float(n), + ); + labels.insert(angle_label.to_string(), angle_exp); + + let idents = Some(&labels); + + let x_label = format!("@x{}", i); + let x_exp = Expression::operation( + '*', + Expression::var('s'), + Expression::function( + FunctionType::Cos, + Expression::ident(angle_label.to_string()), + ), + ); + let x_val = x_exp.eval(&idents, &vars_eval)?; + labels.insert(x_label.to_string(), x_exp); + + if x_val < min_x { + min_x = x_val; + min_x_label = x_label.to_string(); + } + + if x_val > max_x { + max_x = x_val; + max_x_label = x_label.to_string(); + } + + let idents = Some(&labels); + + let y_label = format!("@y{}", i); + let y_exp = Expression::operation( + '*', + Expression::var('s'), + Expression::function( + FunctionType::Sin, + Expression::ident(angle_label.to_string()), + ), + ); + let y_val = y_exp.eval(&idents, &vars_eval)?; + labels.insert(y_label.to_string(), y_exp); + + if y_val < min_y { + min_y = y_val; + min_y_label = y_label.to_string(); + } + + if y_val > max_y { + max_y = y_val; + max_y_label = y_label.to_string(); + } + + let normal_index: u16 = (i as u16) * 2 + 1; + + let normal_angle_label = format!("@nangle{}", i); + let normal_angle_exp = Expression::operation( + '/', + Expression::operation( + '*', + Expression::float(normal_index), + Expression::float(std::f64::consts::TAU), + ), + Expression::float(double_n), + ); + labels.insert(normal_angle_label.to_string(), normal_angle_exp); + + let normal_x_label = format!("@nx{}", i); + let normal_x_exp = Expression::function( + FunctionType::Cos, + Expression::ident(normal_angle_label.to_string()), + ); + labels.insert(normal_x_label.to_string(), normal_x_exp); + + let normal_y_label = format!("@ny{}", i); + let normal_y_exp = Expression::function( + FunctionType::Sin, + Expression::ident(normal_angle_label.to_string()), + ); + labels.insert(normal_y_label.to_string(), normal_y_exp); + + let j_exp = Expression::operation( + '+', + Expression::operation( + '*', + Expression::ident(normal_x_label.to_string()), + Expression::operation( + '-', + Expression::var('x'), + Expression::ident(x_label.to_string()), + ), + ), + Expression::operation( + '*', + Expression::ident(normal_y_label.to_string()), + Expression::operation( + '-', + Expression::var('y'), + Expression::ident(y_label.to_string()), + ), + ), + ); + + junctions.push(Junction::singleton(Condition::new( + '≤', + j_exp, + Expression::float(0), + ))); + } + + while junctions.len() > 1 { + let left = junctions.remove(0); + let right = junctions.remove(0); + junctions.push(Junction::meta('⋀', left, right)); + } + let the_junction = junctions.pop().unwrap(); + + let boundaries: Boundaries = [ + Boundary::new(Expression::float(0), '≤', 'z', '≤', Expression::float(0))?, + Boundary::new( + Expression::ident(min_x_label), + '≤', + 'x', + '≤', + Expression::ident(max_x_label), + )?, + Boundary::new( + Expression::ident(min_y_label), + '≤', + 'y', + '≤', + Expression::ident(max_y_label), + )?, + ]; + + Ok((Some(labels), boundaries, the_junction)) +} diff --git a/src/parser.rs b/src/parser.rs index a6d7211..c7c02b9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,3 +1,4 @@ +use crate::astree; use crate::error; use crate::error::Error; use logos::Logos; @@ -8,50 +9,9 @@ use std::path::Path; pomelo! { %include { use logos::{Lexer, Logos}; - use crate::astree::{Condition, Expression, FunctionType, Junction}; + use crate::astree::{Boundary, Boundaries, Condition, Expression, FunctionType, Junction, Structure}; use std::collections::HashMap; - #[derive(Debug)] - pub struct Boundary{ - pub var: char, - pub min: Expression, - pub max: Expression, - } - - impl Boundary{ - pub fn new( - l: Expression, - lcond: char, - var: char, - rcond: char, - r: Expression, - ) -> Result { - if var != 'x' && var != 'y' && var != 'z' { - return Err(()); - } - if lcond == '<' || lcond == '≤' { - if rcond == '<' || rcond == '≤'{ - let min = l; - let max = r; - return Ok(Boundary{var,min,max}); - } - return Err(()); - }else if lcond == '>' || lcond == '≥'{ - if rcond == '>' || rcond == '≥'{ - let min = r; - let max = l; - return Ok(Boundary{var,min,max}); - } - return Err(()); - } - Err(()) - } - } - - pub type Boundaries = [Boundary; 3]; - - pub type Return = (Option>, Boundaries, Junction); - fn read_var(lex: &mut Lexer) -> Option { lex.slice().chars().next() } @@ -162,7 +122,7 @@ pomelo! { %right Function; %left LineEnd; - %type input Return; + %type input Structure; input ::= boundaries(L) metajuncture(J) { (None,L,J) } input ::= LineEnd boundaries(L) metajuncture(J) { (None,L,J) } input ::= assignments(A) boundaries(L) metajuncture(J) { (Some(A),L,J) } @@ -231,7 +191,7 @@ pub fn parse + std::fmt::Display>( file: P, output_dir: Option

, debug: bool, -) -> Result { +) -> Result { let lex = parser::Token::lexer(source); let mut p = parser::Parser::new(); @@ -285,7 +245,7 @@ pub fn parse + std::fmt::Display>( Ok(result) => Ok(result), Err(_) => { eprintln!("{}: Unexpected end of file", file); - if let Some(the_dir) = output_dir{ + if let Some(the_dir) = output_dir { fs::remove_dir(the_dir)?; } Err(Error::ParserError)