diff --git a/Justfile b/Justfile index eb85f60..6f81dc4 100644 --- a/Justfile +++ b/Justfile @@ -16,4 +16,4 @@ example-video out="out.mp4" args='': ./shapemaker video --colors colorschemes/palenight.css {{out}} --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 1920 {{args}} example-image out="out.png" args='': - ./shapemaker image --colors colorschemes/palenight.css {{out}} {{args}} + ./shapemaker image --colors colorschemes/palenight.css --resolution 3000 {{out}} {{args}} diff --git a/src/canvas.rs b/src/canvas.rs index e35b377..fcdb0f5 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -102,9 +102,9 @@ impl Canvas { self.ensure_layer_exists(name); self.layers.sort_by(|a, _| { if a.name == name { - cmp::Ordering::Greater - } else { cmp::Ordering::Less + } else { + cmp::Ordering::Greater } }) } @@ -114,13 +114,41 @@ impl Canvas { self.ensure_layer_exists(name); self.layers.sort_by(|a, _| { if a.name == name { - cmp::Ordering::Less - } else { cmp::Ordering::Greater + } else { + cmp::Ordering::Less } }) } + /// re-order layers. The first layer in the list will be on top, the last at the bottom + pub fn reorder_layers(&mut self, new_order: Vec<&str>) { + println!( + "re-ordering {:?} to {:?}", + self.layers + .iter() + .map(|l| l.name.clone()) + .collect::>(), + new_order + ); + let current_order = self + .layers + .iter() + .map(|l| l.name.clone()) + .collect::>(); + + // make sure the new order is well-formed + // assert_eq!(self.layers.len(), new_order.len()); + assert!(new_order.iter().all(|name| self.layer_exists(name))); + + self.layers.sort_by_key(|o| { + new_order + .iter() + .position(|&n| n == o.name) + .unwrap_or(current_order.iter().position(|n| *n == o.name).unwrap()) + }); + } + pub fn root(&mut self) -> &mut Layer { self.layer_safe("root") .expect("Layer 'root' should always exist in a canvas") diff --git a/src/examples.rs b/src/examples.rs index 833fdd5..0b3b3b5 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -3,7 +3,7 @@ use rand::Rng; use crate::*; pub fn dna_analysis_machine() -> Canvas { - let mut canvas = Canvas::new(vec!["root"]); + let mut canvas = Canvas::new(vec![]); canvas.colormap = ColorMapping { black: "#000000".into(), @@ -20,42 +20,44 @@ pub fn dna_analysis_machine() -> Canvas { cyan: "#4fecec".into(), }; + canvas.canvas_outter_padding = 900; canvas.set_grid_size(16, 9); canvas.set_background(Color::Black); - let mut hatches_layer = Layer::new("root"); + let mut hatches_layer = Layer::new("hatches"); let draw_in = canvas.world_region.resized(-1, -1); let splines_area = Region::from_bottomleft(draw_in.bottomleft().translated(2, -1), (3, 3)); - let red_circle_in = Region::from(( - Point(splines_area.topright().0 + 3, draw_in.topright().1), - draw_in.bottomright(), - )); + let red_circle_in = Region::from_topright(draw_in.topright().translated(-3, 0), (4, 3)); let red_circle_at = red_circle_in.random_point_within(); + let red_dot_layer = canvas.new_layer("red dot"); + let mut red_dot_friends = Layer::new("red dot friends"); + for (i, point) in draw_in.iter().enumerate() { - println!("{}", point); if splines_area.contains(&point) { - println!("skipping {} has its contained in {}", point, splines_area); continue; } if point == red_circle_at { - println!("adding red circle at {} instead of sqr", point); - hatches_layer.add_object( - "redpoint", - Object::BigCircle(point.into()) + red_dot_layer.add_object( + format!("red circle @ {}", point), + Object::BigCircle(point) .color(Fill::Solid(Color::Red)) .filter(Filter::glow(5.0)), ); - continue; - } - let Point(x, y) = point; + for point in red_circle_at.region().enlarged(1, 1).iter() { + red_dot_friends.add_object( + format!("reddot @ {}", point), + Object::SmallCircle(point).color(Fill::Solid(Color::Red)), + ) + } + } hatches_layer.add_object( - &format!("{}-{}", x, y), + point, if rand::thread_rng().gen_bool(0.5) { Object::BigCircle(point) } else { @@ -69,8 +71,20 @@ pub fn dna_analysis_machine() -> Canvas { )), ); } - println!("{:?}", hatches_layer.objects.keys()); + + red_dot_friends.add_object( + "line", + Object::Line( + draw_in.bottomright().translated(1, -3), + draw_in.bottomright().translated(-3, 1), + 4.0, + ) + .color(Fill::Solid(Color::Cyan)) + .filter(Filter::glow(4.0)), + ); + canvas.layers.push(hatches_layer); + canvas.layers.push(red_dot_friends); let mut splines = canvas.n_random_linelikes_within("splines", &splines_area, 30); for (i, ColoredObject(_, ref mut fill, _)) in splines.objects.values_mut().enumerate() { *fill = Some(Fill::Solid(if i % 2 == 0 { @@ -80,7 +94,19 @@ pub fn dna_analysis_machine() -> Canvas { })) } splines.filter_all_objects(Filter::glow(4.0)); + canvas.layers.push(splines); + // let blackout = canvas.new_layer("black out splines"); + // splines_area.iter_upper_strict_triangle().for_each(|point| { + // println!("blacking out {}", point); + // blackout.add_object( + // point, + // Object::Rectangle(point, point).color(Fill::Solid(Color::Black)), + // ) + // }); + + // canvas.put_layer_on_top("black out splines"); + canvas.reorder_layers(vec!["red dot friends", "hatches", "red dot"]); canvas } diff --git a/src/layer.rs b/src/layer.rs index ef1f9e1..e901f39 100644 --- a/src/layer.rs +++ b/src/layer.rs @@ -1,5 +1,5 @@ use crate::{ColorMapping, ColoredObject, Fill, Filter, ObjectSizes, Region}; -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Display}; #[derive(Debug, Clone, Default)] // #[wasm_bindgen(getter_with_clone)] @@ -60,12 +60,14 @@ impl Layer { self.flush(); } - pub fn add_object(&mut self, name: &str, object: ColoredObject) { - if self.objects.contains_key(name) { - panic!("object {} already exists in layer {}", name, self.name); + pub fn add_object<'a, N: Display>(&mut self, name: N, object: ColoredObject) { + let name_str = format!("{}", name); + + if self.objects.contains_key(&name_str) { + panic!("object {} already exists in layer {}", name_str, self.name); } - self.objects.insert(name.to_string(), object); + self.objects.insert(name_str, object); self.flush(); } diff --git a/src/main.rs b/src/main.rs index 1e964f1..7b7c632 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ pub fn run(args: cli::Args) { let mut canvas = canvas_from_cli(&args); if args.cmd_image && !args.cmd_video { - canvas = examples::title(); + canvas = examples::dna_analysis_machine(); let rendered = canvas.render(&vec!["*"], true); if args.arg_file.ends_with(".svg") { std::fs::write(args.arg_file, rendered).unwrap(); diff --git a/src/objects.rs b/src/objects.rs index a435455..f90a723 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -331,13 +331,14 @@ impl Object { } fn render_line(&self, cell_size: usize) -> Box { - if let Object::Line(start, end, _) = self { + if let Object::Line(start, end, width) = self { return Box::new( svg::node::element::Line::new() .set("x1", start.coords(cell_size).0) .set("y1", start.coords(cell_size).1) .set("x2", end.coords(cell_size).0) - .set("y2", end.coords(cell_size).1), + .set("y2", end.coords(cell_size).1) + .set("stroke-width", *width), ); } diff --git a/src/region.rs b/src/region.rs index 375785f..b6d4c97 100644 --- a/src/region.rs +++ b/src/region.rs @@ -15,6 +15,14 @@ impl Region { self.into() } + pub fn iter_lower_triangle(&self) -> impl Iterator { + self.iter().filter(|Point(x, y)| x < y) + } + + pub fn iter_upper_strict_triangle(&self) -> impl Iterator { + self.iter().filter(|Point(x, y)| x >= y) + } + pub fn random_point_within(&self) -> Point { Point::from(self.random_coordinates_within()) } diff --git a/src/web.rs b/src/web.rs index b77cf85..755e460 100644 --- a/src/web.rs +++ b/src/web.rs @@ -40,7 +40,7 @@ macro_rules! console_log { #[wasm_bindgen] pub fn render_image(opacity: f32, color: Color) -> Result<(), JsValue> { - let mut canvas = examples::title(); + let mut canvas = examples::dna_analysis_machine(); canvas.colormap = ColorMapping { black: "#ffffff".into(), white: "#ffffff".into(),