diff --git a/Cargo.toml b/Cargo.toml
index d48495a..3e8bfa5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -79,7 +79,6 @@ cxx = { version = "1.0", optional = true }
delaunator = "1.0"
eyre = "0.6.12"
geo = "0.31"
-hex = "0.4"
itertools = "0.14"
kdtree = "0.7"
noise = "0.9"
@@ -87,11 +86,10 @@ petgraph = "0.8"
rand = "0.9"
rand_distr = "0.5"
rectangle-pack = "0.4"
-rhai = { version = "1.23", features = ["only_i64", "no_index", "no_object", "no_time", "no_function", "no_module", "no_custom_syntax", "sync"] }
+rune = "0.14"
svg = "0.18"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
-wkb = "0.7" # Hard to update. See #183
wkt = "0.14"
[build-dependencies]
@@ -117,3 +115,10 @@ cxx-tests = ["cxx"]
cxx-bindings = ["cxx", "dep:cxx", "dep:cxx-build"]
default = ["cxx-bindings"]
+
+[patch.crates-io]
+# Unreleased changes add support for Trig functions
+#
+# Unfortunately 0.14.1 was a hotfix branch, and master is still 0.14.0 with breaking API changes
+# since 0.14.0 was released.
+rune = { git = "https://github.com/rune-rs/rune.git", branch = "main" }
diff --git a/README.md b/README.md
index 4320978..36b242b 100644
--- a/README.md
+++ b/README.md
@@ -468,7 +468,9 @@ $ point-cloud \
--max-y=1 \
--delta-h=0.1 \
--time-steps=20 \
- --function "let temp = sqrt(x ** 2.0 + y ** 2.0 + 4.0); x = -sin(x) / temp; y = y / temp;" \
+ --function "let temp = f64::sqrt(x.powf(2.0) + y.powf(2.0) + 4.0);" \
+ --function "let x_new = -f64::sin(x) / temp;" \
+ --function "let y_new = y / temp;" \
--draw-vector-field \
--vector-field-style="STROKE(gray)" \
--vector-field-style="STROKEDASHARRAY(1)" \
diff --git a/examples/streamline/field2.svg b/examples/streamline/field2.svg
index 308a538..dcfa9d3 100644
--- a/examples/streamline/field2.svg
+++ b/examples/streamline/field2.svg
@@ -45,12 +45,12 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
-
-
+
+
-
+
@@ -62,7 +62,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -92,11 +92,11 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
-
+
@@ -106,7 +106,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -128,8 +128,8 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
-
+
+
@@ -157,7 +157,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -187,7 +187,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -250,7 +250,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -351,7 +351,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -363,7 +363,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -378,7 +378,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -389,7 +389,7 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
@@ -444,21 +444,21 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
-
+
-
+
-
-
-
+
+
+
@@ -467,28 +467,28 @@ svg { stroke:black; stroke-width:2; fill:none;}
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
diff --git a/examples/streamline/generate.sh b/examples/streamline/generate.sh
index 169aa02..bdf0edd 100755
--- a/examples/streamline/generate.sh
+++ b/examples/streamline/generate.sh
@@ -19,7 +19,9 @@ point-cloud \
--max-y=1 \
--delta-h=0.1 \
--time-steps=20 \
- --function "let temp = sqrt(x ** 2.0 + y ** 2.0 + 4.0); x = -sin(x) / temp; y = y / temp;" \
+ --function "let temp = f64::sqrt(x.powf(2.0) + y.powf(2.0) + 4.0);" \
+ --function "let x_new = -f64::sin(x) / temp;" \
+ --function "let y_new = y / temp;" \
--draw-vector-field \
--vector-field-style="STROKE(gray)" \
--vector-field-style="STROKEDASHARRAY(1)" \
diff --git a/generative/io/mod.rs b/generative/io/mod.rs
index 7276030..1e2b5eb 100644
--- a/generative/io/mod.rs
+++ b/generative/io/mod.rs
@@ -8,6 +8,6 @@ pub use stdio::{get_input_reader, get_output_writer};
pub use tgf::{GraphFormat, read_tgf_graph, write_graph, write_tgf_graph};
pub use self::wkt::{
- GeometryAndStyle, GeometryFormat, SvgStyle, read_geometries, read_wkt_geometries,
+ GeometryAndStyle, SvgStyle, read_geometries, read_wkt_geometries,
read_wkt_geometries_and_styles, write_geometries, write_wkt_geometries,
};
diff --git a/generative/io/wkt.rs b/generative/io/wkt.rs
index 81b8686..2971b75 100644
--- a/generative/io/wkt.rs
+++ b/generative/io/wkt.rs
@@ -1,38 +1,12 @@
use std::io::{BufRead, BufReader, Lines, Read, Write};
use std::str::FromStr;
-use clap::ValueEnum;
use geo::{
CoordNum, Geometry, GeometryCollection, Line, LineString, MultiLineString, MultiPoint,
MultiPolygon, Point, Polygon, Rect, Triangle,
};
-use hex::{decode, encode_upper};
-use wkb::{geom_to_wkb, wkb_to_geom, write_geom_to_wkb};
use wkt::{ToWkt, Wkt};
-#[derive(Debug, Clone, Copy, ValueEnum)]
-pub enum GeometryFormat {
- /// One WKT geometry per line. Ignores trailing garbage; does not skip over leading garbage.
- Wkt,
- /// Stringified hex encoded WKB, one geometry per line
- WkbHex,
- /// Raw WKB bytes with no separator between geometries
- WkbRaw,
- // TODO: Flat?
- // TODO: Splines?
-}
-
-impl std::fmt::Display for GeometryFormat {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- match self {
- // important: Should match clap::ValueEnum format
- GeometryFormat::Wkt => write!(f, "wkt"),
- GeometryFormat::WkbHex => write!(f, "wkb-hex"),
- GeometryFormat::WkbRaw => write!(f, "wkb-raw"),
- }
- }
-}
-
#[derive(PartialEq, Clone, Debug)]
pub enum SvgStyle {
PointRadius(f64),
@@ -152,30 +126,19 @@ where
}
}
-pub fn read_geometries(
- reader: R,
- format: &GeometryFormat,
-) -> Box>>
+pub fn read_geometries(reader: R) -> Box>>
where
R: Read + 'static,
{
- match format {
- GeometryFormat::Wkt => Box::new(read_wkt_geometries(reader)),
- GeometryFormat::WkbHex => Box::new(read_wkbhex_geometries(reader)),
- GeometryFormat::WkbRaw => Box::new(read_wkbraw_geometries(reader)),
- }
+ Box::new(read_wkt_geometries(reader))
}
-pub fn write_geometries(writer: W, geometries: G, format: GeometryFormat) -> eyre::Result<()>
+pub fn write_geometries(writer: W, geometries: G) -> eyre::Result<()>
where
W: Write,
G: IntoIterator- >,
{
- match format {
- GeometryFormat::Wkt => write_wkt_geometries(writer, geometries),
- GeometryFormat::WkbHex => write_wkbhex_geometries(writer, geometries),
- GeometryFormat::WkbRaw => write_wkbraw_geometries(writer, geometries),
- }
+ write_wkt_geometries(writer, geometries)
}
pub struct WktGeometries
@@ -192,20 +155,6 @@ where
lines: Lines>,
}
-pub struct WkbHexGeometries
-where
- R: Read,
-{
- lines: Lines>,
-}
-
-pub struct WkbRawGeometries
-where
- R: Read,
-{
- reader: BufReader,
-}
-
impl Iterator for WktGeometries
where
R: Read,
@@ -271,69 +220,6 @@ where
}
}
-impl Iterator for WkbRawGeometries
-where
- R: Read,
-{
- type Item = Geometry;
-
- fn next(&mut self) -> Option {
- // This is the only way to tell if a BufRead is exhausted without using the nightly only
- // unstable has_data_left() API.
- match self.reader.fill_buf() {
- Ok(buf) => {
- if buf.is_empty() {
- return None;
- }
- }
- Err(e) => {
- tracing::warn!("Failed to read WKB: {e:?}");
- return None;
- }
- }
-
- match wkb_to_geom(&mut self.reader) {
- Ok(geom) => Some(geom),
- Err(e) => {
- tracing::warn!("Failed to parse WKB: {e:?}");
- None
- }
- }
- }
-}
-
-impl Iterator for WkbHexGeometries
-where
- R: Read,
-{
- type Item = Geometry;
-
- fn next(&mut self) -> Option {
- match self.lines.next() {
- Some(line) => match line {
- Ok(line) => match decode(line) {
- Ok(buf) => match wkb_to_geom(&mut &buf[..]) {
- Ok(geom) => Some(geom),
- Err(e) => {
- tracing::warn!("Failed to parse WKB(hex): {e:?}");
- None
- }
- },
- Err(e) => {
- tracing::warn!("Failed to decode WKB(hex): {e:?}");
- None
- }
- },
- Err(e) => {
- tracing::warn!("Failed to read WKB(hex) from line: {e:?}");
- None
- }
- },
- None => None,
- }
- }
-}
-
/// Return an iterator to the WKT geometries passed in through the given BufReader
///
/// Expects one geometry per line (LF or CRLF). Parsing any given line ends after either the first
@@ -349,24 +235,6 @@ where
}
}
-fn read_wkbhex_geometries(reader: R) -> WkbHexGeometries
-where
- R: Read,
-{
- WkbHexGeometries {
- lines: BufReader::new(reader).lines(),
- }
-}
-
-fn read_wkbraw_geometries(reader: R) -> WkbRawGeometries
-where
- R: Read,
-{
- WkbRawGeometries {
- reader: BufReader::new(reader),
- }
-}
-
/// Write the given geometries with the given Writer in WKT format
///
/// Each geometry will be written on its own line.
@@ -382,37 +250,6 @@ where
Ok(())
}
-fn write_wkbhex_geometries(mut writer: W, geometries: G) -> eyre::Result<()>
-where
- W: Write,
- G: IntoIterator
- >,
-{
- for geom in geometries {
- match geom_to_wkb(&geom) {
- Ok(buffer) => {
- writeln!(writer, "{}", encode_upper(buffer))?;
- }
- Err(e) => {
- tracing::warn!("Failed to serialize geometry to WKB: {e:?}");
- }
- }
- }
- Ok(())
-}
-
-fn write_wkbraw_geometries(mut writer: W, geometries: G) -> eyre::Result<()>
-where
- W: Write,
- G: IntoIterator
- >,
-{
- for geom in geometries {
- // TODO: What's this about the endianity byte?
- write_geom_to_wkb(&geom, &mut writer)
- .map_err(|e| eyre::eyre!("Failed to write WKB geometry: {e:?}"))?;
- }
- Ok(())
-}
-
pub fn read_wkt_geometries_and_styles(reader: R) -> WktGeometriesAndStyles
where
R: Read,
@@ -474,75 +311,6 @@ mod tests {
assert_eq!(actual, expected);
}
- #[test]
- fn test_wkb_single_input() {
- let input_wkt = b"POINT(2 3.5)";
- let input_wkbhex = b"010100000000000000000000400000000000000C40";
- let input_wkbraw = decode(input_wkbhex).unwrap();
-
- let expected = Geometry::Point(Point::new(2.0, 3.5));
-
- let mut wkt_geometries = read_wkt_geometries(&input_wkt[..]);
- assert_eq!(wkt_geometries.next().unwrap(), expected);
-
- let mut wkbraw_geometries = read_wkbraw_geometries(input_wkbraw.as_slice());
- assert_eq!(wkbraw_geometries.next().unwrap(), expected);
-
- let mut wkbhex_geometries = read_wkbhex_geometries(&input_wkbhex[..]);
- assert_eq!(wkbhex_geometries.next().unwrap(), expected);
- }
-
- #[test]
- fn test_wkb_multi_input() {
- let input_wkt = b"POINT(1 1)\nPOINT(2 3.5)";
- let input_wkbhex = b"0101000000000000000000F03F000000000000F03F\n010100000000000000000000400000000000000C40";
- let buffer: Vec = input_wkbhex[..]
- .lines()
- .flat_map(|l| decode(l.unwrap()).unwrap())
- .collect();
-
- let expected1 = Geometry::Point(Point::new(1.0, 1.0));
- let expected2 = Geometry::Point(Point::new(2.0, 3.5));
-
- let mut wkt_geometries = read_wkt_geometries(&input_wkt[..]);
- assert_eq!(wkt_geometries.next().unwrap(), expected1);
- assert_eq!(wkt_geometries.next().unwrap(), expected2);
-
- let mut wkbhex_geometries = read_wkbhex_geometries(&input_wkbhex[..]);
- assert_eq!(wkbhex_geometries.next().unwrap(), expected1);
- assert_eq!(wkbhex_geometries.next().unwrap(), expected2);
-
- let mut wkbraw_geometries = read_wkbraw_geometries(buffer.as_slice());
- assert_eq!(wkbraw_geometries.next().unwrap(), expected1);
- assert_eq!(wkbraw_geometries.next().unwrap(), expected2);
- }
-
- #[test]
- fn test_wkbhex_output() {
- let input_wkbhex = b"0101000000000000000000F03F000000000000F03F\n010100000000000000000000400000000000000C40\n";
- let geometries = read_wkbhex_geometries(&input_wkbhex[..]);
-
- let mut output_buffer = Vec::::new();
- write_wkbhex_geometries(&mut output_buffer, geometries).unwrap();
-
- assert_eq!(output_buffer, input_wkbhex);
- }
-
- #[test]
- fn test_wkbraw_output() {
- let input_wkbhex = b"0101000000000000000000F03F000000000000F03F\n010100000000000000000000400000000000000C40\n";
- let buffer: Vec = input_wkbhex[..]
- .lines()
- .flat_map(|l| decode(l.unwrap()).unwrap())
- .collect();
- let geometries = read_wkbraw_geometries(buffer.as_slice());
-
- let mut output_buffer = Vec::::new();
- write_wkbraw_geometries(&mut output_buffer, geometries).unwrap();
-
- assert_eq!(output_buffer, buffer);
- }
-
#[test]
fn test_can_parse_3d() {
let wkt = b"POINT Z(1 2 3)";
diff --git a/tools/bitwise.rs b/tools/bitwise.rs
index 84b5d20..91d3f9d 100644
--- a/tools/bitwise.rs
+++ b/tools/bitwise.rs
@@ -2,10 +2,9 @@ use std::io::Write;
use std::path::PathBuf;
use clap::{Parser, ValueEnum};
-use generative::io::{GeometryFormat, get_output_writer, write_geometries};
+use generative::io::{get_output_writer, write_geometries};
use geo::{Geometry, Line, Point};
use itertools::Itertools;
-use rhai::{AST, Engine, EvalAltResult, Scope};
/// Perform bitwise operations on a grid
///
@@ -21,10 +20,6 @@ struct CmdlineOptions {
#[clap(short, long)]
output: Option,
- /// Output geometry format.
- #[clap(short = 'O', long, default_value_t = GeometryFormat::Wkt)]
- output_format: GeometryFormat,
-
/// Whether to output points or connected lines
#[clap(long, default_value_t = false)]
points: bool,
@@ -58,22 +53,42 @@ struct CmdlineOptions {
expression: String,
}
-fn expression(engine: &Engine, ast: &AST, x: i64, y: i64) -> Result> {
- let mut scope = Scope::new();
- scope.push("x", x);
- scope.push("y", y);
+type BitwiseExpression = Box i64>;
+
+fn build_expression(expr: &str) -> eyre::Result {
+ // Ok(Box::new(|x, y| (x & y) & ((x ^ y) % 13)))
+
+ let context = rune::Context::with_default_modules()?;
+ let runtime = rune::sync::Arc::try_new(context.runtime()?)?;
+ let mut script = rune::Sources::new();
+ script.insert(build_function(expr)?)?;
+ let mut diagnostics = rune::Diagnostics::new();
+ let maybe_unit = rune::prepare(&mut script)
+ .with_context(&context)
+ .with_diagnostics(&mut diagnostics)
+ .build();
+ if !diagnostics.is_empty() {
+ let mut writer =
+ rune::termcolor::StandardStream::stderr(rune::termcolor::ColorChoice::Always);
+ diagnostics.emit(&mut writer, &script)?;
+ }
+ let unit = rune::sync::Arc::try_new(maybe_unit?)?;
+ let vm = rune::Vm::new(runtime, unit);
+ let eval_expr = vm.lookup_function(["eval_expr"])?;
+ let eval_expr = move |x: i64, y: i64| -> i64 {
+ eval_expr.call((x, y)).expect("Failed to call eval_expr()")
+ };
+ Ok(Box::new(eval_expr))
+}
- engine.eval_ast_with_scope::(&mut scope, ast)
+fn build_function(expr: &str) -> eyre::Result {
+ let lines = ["pub fn eval_expr(x, y) {", expr, "}"];
+ let script = lines.join("\n");
+ let source = rune::Source::memory(script)?;
+ Ok(source)
}
-fn write_line(
- writer: W,
- format: GeometryFormat,
- x1: i64,
- y1: i64,
- x2: i64,
- y2: i64,
-) -> eyre::Result<()>
+fn write_line(writer: W, x1: i64, y1: i64, x2: i64, y2: i64) -> eyre::Result<()>
where
W: Write,
{
@@ -82,19 +97,19 @@ where
Point::new(x2 as f64, y2 as f64),
);
let geometries = std::iter::once(Geometry::Line(line));
- write_geometries(writer, geometries, format)
+ write_geometries(writer, geometries)
}
-fn write_point(writer: W, format: GeometryFormat, x1: i64, y1: i64) -> eyre::Result<()>
+fn write_point(writer: W, x1: i64, y1: i64) -> eyre::Result<()>
where
W: Write,
{
let point = Point::new(x1 as f64, y1 as f64);
let geometries = std::iter::once(Geometry::Point(point));
- write_geometries(writer, geometries, format)
+ write_geometries(writer, geometries)
}
-#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
enum Neighbor {
North,
NorthEast,
@@ -147,8 +162,7 @@ fn main() -> eyre::Result<()> {
.with_writer(std::io::stderr)
.init();
- let engine = Engine::new();
- let ast = engine.compile_expression(&args.expression)?;
+ let expr_fn = build_expression(&args.expression)?;
let xs = args.x_min..args.x_max;
let ys = args.y_min..args.y_max;
@@ -157,38 +171,32 @@ fn main() -> eyre::Result<()> {
let mut writer = get_output_writer(&args.output)?;
if args.points {
let geometries = cross.filter_map(|(x, y)| {
- if let Ok(value) = expression(&engine, &ast, x, y) {
- if value > 0 {
- return Some(Geometry::Point(Point::new(x as f64, y as f64)));
- }
- } else {
- tracing::error!(
- "Failed to evaluate expression '{}' given x={x}, y={y}",
- args.expression,
- );
+ let value = expr_fn(x, y);
+ if value > 0 {
+ return Some(Geometry::Point(Point::new(x as f64, y as f64)));
}
None
});
- write_geometries(writer, geometries, args.output_format)?;
+ write_geometries(writer, geometries)?;
} else {
tracing::info!(
"Searching neighbors in order: {:?}",
args.neighbor_search_order
);
for (x, y) in cross {
- if expression(&engine, &ast, x, y)? > 0 {
+ if expr_fn(x, y) > 0 {
let mut wrote_line = false;
for n in args.neighbor_search_order.iter() {
- let (x2, y2) = neighbor(x, y, n.clone());
- if expression(&engine, &ast, x2, y2)? > 0 {
- write_line(&mut writer, args.output_format, x, y, x2, y2)?;
+ let (x2, y2) = neighbor(x, y, *n);
+ if expr_fn(x2, y2) > 0 {
+ write_line(&mut writer, x, y, x2, y2)?;
wrote_line = true;
break;
}
}
if !wrote_line {
- write_point(&mut writer, args.output_format, x, y)?;
+ write_point(&mut writer, x, y)?;
}
}
}
diff --git a/tools/bundle.rs b/tools/bundle.rs
index 2f34552..fb4f8c2 100644
--- a/tools/bundle.rs
+++ b/tools/bundle.rs
@@ -1,9 +1,7 @@
use std::path::PathBuf;
use clap::Parser;
-use generative::io::{
- GeometryFormat, get_input_reader, get_output_writer, read_geometries, write_geometries,
-};
+use generative::io::{get_input_reader, get_output_writer, read_geometries, write_geometries};
/// Bundle the given geometries into a GEOMETRYCOLLECTION
#[derive(Debug, Parser)]
@@ -17,17 +15,9 @@ struct CmdlineOptions {
#[clap(short, long)]
input: Option,
- /// Input geometry format.
- #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)]
- input_format: GeometryFormat,
-
/// Output file to write result to. Defaults to stdout.
#[clap(short, long)]
output: Option,
-
- /// Output geometry format.
- #[clap(short = 'O', long, default_value_t = GeometryFormat::Wkt)]
- output_format: GeometryFormat,
}
fn main() -> eyre::Result<()> {
@@ -44,11 +34,11 @@ fn main() -> eyre::Result<()> {
.init();
let reader = get_input_reader(&args.input)?;
- let geometries = read_geometries(reader, &args.input_format);
+ let geometries = read_geometries(reader);
let bundle: geo::GeometryCollection = geometries.collect();
let geometries = std::iter::once(geo::Geometry::GeometryCollection(bundle));
let writer = get_output_writer(&args.output)?;
- write_geometries(writer, geometries, args.output_format)
+ write_geometries(writer, geometries)
}
diff --git a/tools/geom2graph.rs b/tools/geom2graph.rs
index 54cff84..ea3b2b7 100644
--- a/tools/geom2graph.rs
+++ b/tools/geom2graph.rs
@@ -3,8 +3,8 @@ use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use generative::graph::GeometryGraph;
use generative::io::{
- GeometryFormat, GraphFormat, get_input_reader, get_output_writer, read_geometries,
- read_tgf_graph, write_geometries, write_graph,
+ GraphFormat, get_input_reader, get_output_writer, read_geometries, read_tgf_graph,
+ write_geometries, write_graph,
};
use generative::noding::{node, polygonize};
use generative::snap::{SnappingStrategy, snap_geoms, snap_graph};
@@ -42,10 +42,6 @@ struct CmdlineOptions {
#[clap(short, long)]
output: Option,
- /// Format to output --graph2geom geometries as
- #[clap(long, default_value_t = GeometryFormat::Wkt)]
- geometry_format: GeometryFormat,
-
/// Format to output (the default) --graph2geom graphs as
#[clap(long, default_value_t = GraphFormat::Tgf)]
graph_format: GraphFormat,
@@ -93,7 +89,7 @@ fn main() -> eyre::Result<()> {
};
if args.geom2graph || !args.graph2geom {
- let geometries = read_geometries(reader, &args.geometry_format);
+ let geometries = read_geometries(reader);
let graph = node::<_, petgraph::Undirected>(geometries);
let graph = if args.tolerance.is_some() {
@@ -116,6 +112,6 @@ fn main() -> eyre::Result<()> {
Box::new(geometries)
};
- write_geometries(writer, geometries, args.geometry_format)
+ write_geometries(writer, geometries)
}
}
diff --git a/tools/grid.rs b/tools/grid.rs
index 0ab948f..6df1bf7 100644
--- a/tools/grid.rs
+++ b/tools/grid.rs
@@ -2,9 +2,7 @@ use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use generative::graph::GeometryGraph;
-use generative::io::{
- GeometryFormat, GraphFormat, get_output_writer, write_geometries, write_graph,
-};
+use generative::io::{GraphFormat, get_output_writer, write_geometries, write_graph};
#[cfg(feature = "cxx-bindings")]
use generative::noding::{node, polygonize};
use generative::snap::{SnappingStrategy, snap_geoms};
@@ -631,7 +629,7 @@ fn main() -> eyre::Result<()> {
let geoms = rings.chain(spokes);
match args.output_format {
- GridFormat::Lines => write_geometries(writer, geoms, GeometryFormat::Wkt),
+ GridFormat::Lines => write_geometries(writer, geoms),
GridFormat::Points => {
let mut points = Vec::new();
for geom in geoms {
@@ -642,7 +640,7 @@ fn main() -> eyre::Result<()> {
}
// Snap points as a way of deduplicating vertices
let points = snap_geoms(points.into_iter(), SnappingStrategy::ClosestPoint(0.0));
- write_geometries(writer, points, GeometryFormat::Wkt)
+ write_geometries(writer, points)
}
#[cfg(feature = "cxx-bindings")]
GridFormat::Graph | GridFormat::Cells => {
@@ -654,7 +652,7 @@ fn main() -> eyre::Result<()> {
let polygons = polygons.into_iter().map(Geometry::Polygon);
let dangles = dangles.into_iter().map(Geometry::LineString);
let geoms = polygons.chain(dangles);
- write_geometries(writer, geoms, GeometryFormat::Wkt)
+ write_geometries(writer, geoms)
}
}
#[cfg(not(feature = "cxx-bindings"))]
@@ -668,18 +666,16 @@ fn main() -> eyre::Result<()> {
match args.output_format {
GridFormat::Graph => write_graph(writer, &graph, &GraphFormat::Tgf),
GridFormat::Lines => write_graph(writer, &graph, &GraphFormat::Wkt),
- GridFormat::Points => write_geometries(
- writer,
- graph.node_weights().map(|p| Geometry::Point(*p)),
- GeometryFormat::Wkt,
- ),
+ GridFormat::Points => {
+ write_geometries(writer, graph.node_weights().map(|p| Geometry::Point(*p)))
+ }
#[cfg(feature = "cxx-bindings")]
GridFormat::Cells => {
let (polygons, dangles) = polygonize(&graph);
let polygons = polygons.into_iter().map(Geometry::Polygon);
let dangles = dangles.into_iter().map(Geometry::LineString);
let geoms = polygons.chain(dangles);
- write_geometries(writer, geoms, GeometryFormat::Wkt)
+ write_geometries(writer, geoms)
}
}
}
diff --git a/tools/pack.rs b/tools/pack.rs
index fa23e36..34ec5c1 100644
--- a/tools/pack.rs
+++ b/tools/pack.rs
@@ -2,9 +2,7 @@ use std::collections::BTreeMap;
use std::path::PathBuf;
use clap::Parser;
-use generative::io::{
- GeometryFormat, get_input_reader, get_output_writer, read_geometries, write_geometries,
-};
+use generative::io::{get_input_reader, get_output_writer, read_geometries, write_geometries};
use geo::{BoundingRect, Coord, Translate};
use rectangle_pack::{
GroupedRectsToPlace, RectToInsert, TargetBin, contains_smallest_box, pack_rects,
@@ -26,18 +24,10 @@ struct CmdlineOptions {
#[clap(short, long)]
input: Option,
- /// Input geometry format.
- #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)]
- input_format: GeometryFormat,
-
/// Output file to write result to. Defaults to stdout.
#[clap(short, long)]
output: Option,
- /// Output geometry format.
- #[clap(short = 'O', long, default_value_t = GeometryFormat::Wkt)]
- output_format: GeometryFormat,
-
/// The width of the target rectangle
#[clap(long, default_value_t = 100)]
width: u32,
@@ -65,7 +55,7 @@ fn main() -> eyre::Result<()> {
.init();
let reader = get_input_reader(&args.input)?;
- let mut geometries: Vec<_> = read_geometries(reader, &args.input_format).collect();
+ let mut geometries: Vec<_> = read_geometries(reader).collect();
let padding = Coord {
x: args.padding,
y: args.padding,
@@ -111,5 +101,5 @@ fn main() -> eyre::Result<()> {
}
let writer = get_output_writer(&args.output)?;
- write_geometries(writer, geometries, args.output_format)
+ write_geometries(writer, geometries)
}
diff --git a/tools/smooth.rs b/tools/smooth.rs
index 59addf2..6e53a1d 100644
--- a/tools/smooth.rs
+++ b/tools/smooth.rs
@@ -2,9 +2,7 @@ use std::path::PathBuf;
use clap::Parser;
use generative::flatten::flatten_nested_geometries;
-use generative::io::{
- GeometryFormat, get_input_reader, get_output_writer, read_geometries, write_geometries,
-};
+use generative::io::{get_input_reader, get_output_writer, read_geometries, write_geometries};
use geo::{ChaikinSmoothing, Geometry};
/// Smooth the given geometries
@@ -19,18 +17,10 @@ struct CmdlineOptions {
#[clap(short, long)]
input: Option,
- /// Input geometry format.
- #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)]
- input_format: GeometryFormat,
-
/// Output file to write result to. Defaults to stdout.
#[clap(short, long)]
output: Option,
- /// Output geometry format.
- #[clap(short = 'O', long, default_value_t = GeometryFormat::Wkt)]
- output_format: GeometryFormat,
-
/// Number of iterations to run Chaikins smoothing algorithm
#[clap(short = 'n', long, default_value_t = 10)]
iterations: usize,
@@ -50,7 +40,7 @@ fn main() -> eyre::Result<()> {
.init();
let reader = get_input_reader(&args.input)?;
- let geometries = read_geometries(reader, &args.input_format);
+ let geometries = read_geometries(reader);
let geometries = flatten_nested_geometries(geometries);
let geometries = geometries.map(|g| match g {
Geometry::Point(_)
@@ -68,5 +58,5 @@ fn main() -> eyre::Result<()> {
});
let writer = get_output_writer(&args.output)?;
- write_geometries(writer, geometries, args.output_format)
+ write_geometries(writer, geometries)
}
diff --git a/tools/snap.rs b/tools/snap.rs
index 347f485..8ec7070 100644
--- a/tools/snap.rs
+++ b/tools/snap.rs
@@ -3,8 +3,8 @@ use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use generative::graph::GeometryGraph;
use generative::io::{
- GeometryFormat, get_input_reader, get_output_writer, read_geometries, read_tgf_graph,
- write_geometries, write_tgf_graph,
+ get_input_reader, get_output_writer, read_geometries, read_tgf_graph, write_geometries,
+ write_tgf_graph,
};
use generative::snap::{SnappingStrategy, snap_geoms, snap_graph};
use petgraph::Undirected;
@@ -13,10 +13,6 @@ use petgraph::Undirected;
enum InputFormat {
/// One WKT geometry per line. Ignores trailing garbage; does not skip over leading garbage.
Wkt,
- /// Stringified hex encoded WKB, one geometry per line
- WkbHex,
- /// Raw WKB bytes with no separator between geometries
- WkbRaw,
/// A geometry graph in TGF format
Tgf,
}
@@ -26,24 +22,11 @@ impl std::fmt::Display for InputFormat {
match self {
// important: Should match clap::ValueEnum format
InputFormat::Wkt => write!(f, "wkt"),
- InputFormat::WkbHex => write!(f, "wkb-hex"),
- InputFormat::WkbRaw => write!(f, "wkb-raw"),
InputFormat::Tgf => write!(f, "tgf"),
}
}
}
-impl From for GeometryFormat {
- fn from(f: InputFormat) -> GeometryFormat {
- match f {
- InputFormat::Wkt => GeometryFormat::Wkt,
- InputFormat::WkbHex => GeometryFormat::WkbHex,
- InputFormat::WkbRaw => GeometryFormat::WkbRaw,
- InputFormat::Tgf => unreachable!(),
- }
- }
-}
-
#[derive(Debug, Clone, ValueEnum)]
enum CliSnappingStrategy {
ClosestPoint,
@@ -110,10 +93,10 @@ fn main() -> eyre::Result<()> {
};
match args.input_format {
- InputFormat::Wkt | InputFormat::WkbHex | InputFormat::WkbRaw => {
- let geometries = read_geometries(reader, &args.input_format.clone().into());
+ InputFormat::Wkt => {
+ let geometries = read_geometries(reader);
let geometries = snap_geoms(geometries, strategy);
- write_geometries(writer, geometries, args.input_format.into())
+ write_geometries(writer, geometries)
}
InputFormat::Tgf => {
let graph: GeometryGraph = read_tgf_graph(reader);
diff --git a/tools/streamline.rs b/tools/streamline.rs
index 33e5729..e4a1944 100644
--- a/tools/streamline.rs
+++ b/tools/streamline.rs
@@ -3,16 +3,13 @@ use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use generative::MapCoordsInPlaceMut;
-use generative::io::{
- GeometryFormat, get_input_reader, get_output_writer, read_geometries, write_geometries,
-};
+use generative::io::{get_input_reader, get_output_writer, read_geometries, write_geometries};
use geo::{AffineOps, AffineTransform, Centroid, Coord, Geometry, Line, LineString};
// use noise::Billow;
use noise::{NoiseFn, Perlin};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use rand_distr::{Binomial, Distribution};
-use rhai::{Engine, Scope};
#[derive(Debug, Clone, Copy, ValueEnum)]
enum StreamlineKind {
@@ -48,30 +45,24 @@ struct CmdlineOptions {
#[clap(short, long)]
input: Option,
- /// Input geometry format.
- #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)]
- input_format: GeometryFormat,
-
/// Output file to write result to. Defaults to stdout.
#[clap(short, long)]
output: Option,
- /// Output geometry format.
- #[clap(short = 'O', long, default_value_t = GeometryFormat::Wkt)]
- output_format: GeometryFormat,
-
- /// A Rhai script that defines the vector field. If not given, a Perlin noise field will be
+ /// A Rune script that defines the vector field. If not given, a Perlin noise field will be
/// used instead.
///
/// Example:
///
- /// let temp = sqrt(x ** 2.0 + y ** 2.0 + 4.0);
- /// x = -sin(x) / temp;
- /// y = y / temp;
+ /// let temp = f64::sqrt(x.powf(2.0) + y.powf(2.0) + 4.0);
+ /// let x_new = -f64::sin(x) / temp;
+ /// let y_new = y / temp;
+ ///
+ /// I.e., the f64 x, y variables are inputs, and the x_new, y_new variables are outputs.
///
- /// I.e., the f64 x and y variables are both input and output.
+ /// May be given multiple times for multi-line functions.
#[clap(short, long)]
- function: Option,
+ function: Vec,
/// The random seed to use. Use zero to let the tool pick its own random seed.
#[clap(long, default_value_t = 0)]
@@ -147,8 +138,47 @@ fn generate_random_seed_if_not_specified(seed: u64) -> u64 {
}
}
+type VectorFieldFunction = Box (f64, f64)>;
+
+fn build_function_from_lines(lines: &[String]) -> eyre::Result {
+ let context = rune::Context::with_default_modules()?;
+ let runtime = rune::sync::Arc::try_new(context.runtime()?)?;
+ let mut script = build_script_from_lines(lines)?;
+ let mut diagnostics = rune::Diagnostics::new();
+ let maybe_unit = rune::prepare(&mut script)
+ .with_context(&context)
+ .with_diagnostics(&mut diagnostics)
+ .build();
+ if !diagnostics.is_empty() {
+ let mut writer =
+ rune::termcolor::StandardStream::stderr(rune::termcolor::ColorChoice::Always);
+ diagnostics.emit(&mut writer, &script)?;
+ }
+ let unit = rune::sync::Arc::try_new(maybe_unit?)?;
+ let vm = rune::Vm::new(runtime, unit);
+ let eval_field = vm.lookup_function(["eval_field"])?;
+ let eval_expr = move |x: f64, y: f64| -> (f64, f64) {
+ eval_field
+ .call((x, y))
+ .expect("Failed to call eval_field()")
+ };
+ Ok(Box::new(eval_expr))
+}
+
+fn build_script_from_lines(lines: &[String]) -> eyre::Result {
+ let mut sources = rune::Sources::new();
+ let mut all_lines = vec!["pub fn eval_field(x, y) {".to_string()];
+ all_lines.extend_from_slice(lines);
+ all_lines.push("return (x_new, y_new); }".to_string());
+
+ let script = all_lines.join("\n");
+ let source = rune::Source::memory(script)?;
+ sources.insert(source)?;
+ Ok(sources)
+}
+
struct VectorField {
- function: Box [f64; 2]>,
+ function: VectorFieldFunction,
min_x: f64,
max_x: f64,
@@ -164,7 +194,7 @@ impl VectorField {
min_y: f64,
max_y: f64,
stride: f64,
- function: impl Fn(f64, f64) -> [f64; 2] + 'static,
+ function: VectorFieldFunction,
) -> Self {
Self {
function: Box::new(function),
@@ -192,7 +222,7 @@ impl VectorField {
((y - self.min_y) / self.stride) as usize
}
- fn write(&self, writer: &mut W, format: GeometryFormat) -> eyre::Result<()>
+ fn write(&self, writer: &mut W) -> eyre::Result<()>
where
W: std::io::Write,
{
@@ -212,8 +242,8 @@ impl VectorField {
// Vector field visualizations don't look good if the vectors use the same scale as the
// uniform grid they're drawn on. So we scale by the delta-h.
- let dx = vector[0] * self.stride;
- let dy = vector[1] * self.stride;
+ let dx = vector.0 * self.stride;
+ let dy = vector.1 * self.stride;
let x2 = x1 + dx;
let y2 = y1 + dy;
@@ -223,7 +253,7 @@ impl VectorField {
})
});
- write_geometries(writer, vectors, format)
+ write_geometries(writer, vectors)
}
}
@@ -310,8 +340,8 @@ fn simulate_coordinate(
let current_vector = (field.function)(current.x, current.y);
- current.x += timestep * current_vector[0];
- current.y += timestep * current_vector[1];
+ current.x += timestep * current_vector.0;
+ current.y += timestep * current_vector.1;
if record_streamlines {
streamline.push(current);
@@ -396,30 +426,13 @@ fn main() -> eyre::Result<()> {
// let perlin = Billow::::new(seed as u32);
let perlin = Perlin::new(seed as u32);
- let function: Box [f64; 2]> = match args.function {
- Some(string) => {
- let engine = Engine::new();
- let ast = engine.compile(string)?;
-
- let func = move |x: f64, y: f64| -> [f64; 2] {
- let mut scope = Scope::new();
- scope.push("x", x);
- scope.push("y", y);
-
- engine.eval_ast_with_scope::<()>(&mut scope, &ast).unwrap();
-
- let new_x = scope.get_value::("x").unwrap();
- let new_y = scope.get_value::("y").unwrap();
-
- [new_x, new_y]
- };
-
- Box::new(func)
- }
- None => Box::new(move |x, y| {
+ let function: VectorFieldFunction = if args.function.is_empty() {
+ Box::new(move |x, y| {
let angle = perlin.get([x, y]);
- [f64::cos(angle), f64::sin(angle)]
- }),
+ (f64::cos(angle), f64::sin(angle))
+ })
+ } else {
+ build_function_from_lines(&args.function)?
};
let field = VectorField::new(
@@ -438,10 +451,10 @@ fn main() -> eyre::Result<()> {
for style in args.vector_field_style {
writeln!(&mut writer, "{style}")?;
}
- field.write(&mut writer, args.output_format)?;
+ field.write(&mut writer)?;
}
- let geometries = read_geometries(reader, &args.input_format);
+ let geometries = read_geometries(reader);
let geoms_and_streamlines = simulate(
geometries,
@@ -460,13 +473,13 @@ fn main() -> eyre::Result<()> {
for style in args.streamline_style {
writeln!(&mut writer, "{style}")?;
}
- write_geometries(&mut writer, streamlines, args.output_format)?;
+ write_geometries(&mut writer, streamlines)?;
}
if args.draw_geometries {
for style in args.geometry_style {
writeln!(&mut writer, "{style}")?;
}
- write_geometries(&mut writer, geometries, args.output_format)?;
+ write_geometries(&mut writer, geometries)?;
}
Ok(())
}
diff --git a/tools/template.rs b/tools/template.rs
index e72aaeb..1d7a355 100644
--- a/tools/template.rs
+++ b/tools/template.rs
@@ -1,9 +1,7 @@
use std::path::PathBuf;
use clap::Parser;
-use generative::io::{
- GeometryFormat, get_input_reader, get_output_writer, read_geometries, write_geometries,
-};
+use generative::io::{get_input_reader, get_output_writer, read_geometries, write_geometries};
/// A template tool
///
@@ -19,17 +17,9 @@ struct CmdlineOptions {
#[clap(short, long)]
input: Option,
- /// Input geometry format.
- #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)]
- input_format: GeometryFormat,
-
/// Output file to write result to. Defaults to stdout.
#[clap(short, long)]
output: Option,
-
- /// Output geometry format.
- #[clap(short = 'O', long, default_value_t = GeometryFormat::Wkt)]
- output_format: GeometryFormat,
}
fn main() -> eyre::Result<()> {
@@ -46,10 +36,10 @@ fn main() -> eyre::Result<()> {
.init();
let reader = get_input_reader(&args.input)?;
- let geometries = read_geometries(reader, &args.input_format); // lazily loaded
+ let geometries = read_geometries(reader); // lazily loaded
// Do some kind of transformation to the geometries here.
let writer = get_output_writer(&args.output)?;
- write_geometries(writer, geometries, args.output_format)
+ write_geometries(writer, geometries)
}
diff --git a/tools/transform.rs b/tools/transform.rs
index 9468a90..3615055 100644
--- a/tools/transform.rs
+++ b/tools/transform.rs
@@ -1,9 +1,7 @@
use std::path::PathBuf;
use clap::{Parser, ValueEnum};
-use generative::io::{
- GeometryFormat, get_input_reader, get_output_writer, read_geometries, write_geometries,
-};
+use generative::io::{get_input_reader, get_output_writer, read_geometries, write_geometries};
use geo::{
AffineOps, AffineTransform, BoundingRect, Coord, Geometry, MapCoordsInPlace, Rect, coord,
};
@@ -44,18 +42,10 @@ struct CmdlineOptions {
#[clap(short, long)]
output: Option,
- /// Output geometry format.
- #[clap(short = 'O', long, default_value_t = GeometryFormat::Wkt)]
- output_format: GeometryFormat,
-
/// Input file to read input from. Defaults to stdin.
#[clap(short, long)]
input: Option,
- /// Input geometry format.
- #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)]
- input_format: GeometryFormat,
-
/// How to center the affine transformation
#[clap(long, default_value = "origin")]
center: TransformCenter,
@@ -299,7 +289,7 @@ fn main() -> eyre::Result<()> {
let reader = get_input_reader(&args.input)?;
let writer = get_output_writer(&args.output)?;
- let geometries = read_geometries(reader, &args.input_format);
+ let geometries = read_geometries(reader);
let mut transformed = affine_transform(geometries, &args);
if args.range1.len() == 2 || args.range2.len() == 2 {
@@ -337,5 +327,5 @@ fn main() -> eyre::Result<()> {
transformed = Box::new(geoms_coordwise(transformed, from_polar));
}
- write_geometries(writer, transformed, args.output_format)
+ write_geometries(writer, transformed)
}
diff --git a/tools/traverse.rs b/tools/traverse.rs
index f9740e1..0bcb8f9 100644
--- a/tools/traverse.rs
+++ b/tools/traverse.rs
@@ -3,9 +3,7 @@ use std::path::PathBuf;
use clap::Parser;
use generative::graph::GeometryGraph;
-use generative::io::{
- GeometryFormat, get_input_reader, get_output_writer, read_tgf_graph, write_geometries,
-};
+use generative::io::{get_input_reader, get_output_writer, read_tgf_graph, write_geometries};
use geo::{Geometry, LineString, Point};
use petgraph::{EdgeType, Undirected};
use rand::rngs::StdRng;
@@ -32,10 +30,6 @@ struct CmdlineOptions {
#[clap(short, long)]
output: Option,
- /// The output geometry format
- #[clap(short='O', long, default_value_t=GeometryFormat::Wkt)]
- output_format: GeometryFormat,
-
/// The random seed to use. Use zero to let the tool pick its own random seed.
#[clap(long, default_value_t = 0)]
seed: u64,
@@ -203,14 +197,13 @@ fn main() -> eyre::Result<()> {
.map(Geometry::LineString);
let mut writer = get_output_writer(&args.output)?;
- write_geometries(&mut writer, traversals, args.output_format)?;
+ write_geometries(&mut writer, traversals)?;
// dump the remaining nodes
if args.untraversed {
write_geometries(
&mut writer,
graph.node_weights().map(|p| Geometry::Point(*p)),
- args.output_format,
)?;
}
Ok(())
diff --git a/tools/triangulate.rs b/tools/triangulate.rs
index a8838c4..883f71d 100644
--- a/tools/triangulate.rs
+++ b/tools/triangulate.rs
@@ -3,7 +3,7 @@ use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use generative::flatten::flatten_geometries_into_points;
use generative::io::{
- GeometryFormat, GraphFormat, get_input_reader, get_output_writer, read_geometries, write_graph,
+ GraphFormat, get_input_reader, get_output_writer, read_geometries, write_graph,
};
use generative::triangulation::triangulate;
@@ -37,10 +37,6 @@ struct CmdlineOptions {
#[clap(short, long)]
input: Option,
- /// Input geometry format.
- #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)]
- input_format: GeometryFormat,
-
/// How to triangulate the input geometries
#[clap(short, long, default_value = "whole-collection")]
strategy: TriangulationStrategy,
@@ -61,7 +57,7 @@ fn main() -> eyre::Result<()> {
let reader = get_input_reader(&args.input)?;
let mut writer = get_output_writer(&args.output)?;
- let geometries = read_geometries(reader, &args.input_format); // lazily loaded
+ let geometries = read_geometries(reader); // lazily loaded
match args.strategy {
TriangulationStrategy::EachGeometry => {
diff --git a/tools/urquhart.rs b/tools/urquhart.rs
index 7e4f1bf..5271c45 100644
--- a/tools/urquhart.rs
+++ b/tools/urquhart.rs
@@ -3,7 +3,7 @@ use std::path::PathBuf;
use clap::Parser;
use generative::flatten::flatten_geometries_into_points;
use generative::io::{
- GeometryFormat, GraphFormat, get_input_reader, get_output_writer, read_geometries, write_graph,
+ GraphFormat, get_input_reader, get_output_writer, read_geometries, write_graph,
};
use generative::triangulation::triangulate;
@@ -28,10 +28,6 @@ struct CmdlineOptions {
/// Input file to read input from. Defaults to stdin.
#[clap(short, long)]
input: Option,
-
- /// Input geometry format.
- #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)]
- input_format: GeometryFormat,
}
fn main() -> eyre::Result<()> {
@@ -48,7 +44,7 @@ fn main() -> eyre::Result<()> {
.init();
let reader = get_input_reader(&args.input)?;
- let geometries = read_geometries(reader, &args.input_format); // lazily loaded
+ let geometries = read_geometries(reader); // lazily loaded
let points = flatten_geometries_into_points(geometries);
if let Some(triangulation) = triangulate(points) {