space_filling/
lib.rs

1//! This is a library for generalized space filling in ℝ² (do not mix with _packing_).
2//!
3//! It is split into two main modules: [`solver`] for generating a distribution of shapes,
4//! and [`drawing`] for displaying it (requires `draw` feature).
5//! Here, "shape" denotes one or multiple regions of ℝ² space, that can be represented by a
6//! signed distance function.
7//!
8//! # Basic usage, Argmax2D solver
9//! ```no_run
10//! # use {
11//! #   space_filling::{
12//! #     geometry::{Shape, Circle, Scale, Translation},
13//! #     sdf::{self, SDF},
14//! #     solver::Argmax2D,
15//! #     drawing::Draw,
16//! #     util
17//! #   },
18//! #   anyhow::Result,
19//! #   image::{Luma, Pixel}
20//! # };
21//! #
22//! # fn main() -> Result<()> {
23    //! let path = "out.png";
24    //!
25    //! /**
26    //!   * Initialize instance of `Argmax2D`, which will store the discrete distance field in
27    //!   * a 1024x1024 bitmap, and 16x16 chunk size.
28    //!   * Resolution must be divisible by chunk size.
29    //!   * Resolution affects the precision of solver, and is not related to final picture size.
30    //!   * Chunk size is only important for optimization. Best values depend on the actual
31    //!   * system configuration, but typically `chunk = resolution.sqrt() / 2`
32    //!   **/
33    //! let mut representation = Argmax2D::new(1024, 16)?;
34    //! // prevent shapes from escaping image
35    //! representation.insert_sdf(sdf::boundary_rect);
36    //!
37    //! // Initialize image buffer, which will hold final image.
38    //! let mut image = image::RgbaImage::new(2048, 2048);
39    //!
40    //! // Generate the distribution of shapes:
41    //! for _ in 0..1000 {
42    //!   // find global maxima of the field
43    //!   let global_max = representation.find_max();
44    //!   // Make a new circle at the location with highest distance to all other circles.
45    //!   let circle = Circle
46    //!     .translate(global_max.point.to_vector())
47    //!     .scale(global_max.distance / 4.0);
48    //!   /** Update the field.
49    //!    * `Circle` impletemens the `SDF` trait. Additionally, it has been concluded that
50    //!    * only a certain part of the field is being changed every time, depending on the
51    //!    * current maximum distance. To be exact - a square region with a side of
52    //!    * max_dist * 4.0 * sqrt(2.0), which made possible achieving
53    //!    * greater speed of computation. **/
54    //!   representation.insert_sdf_domain(
55    //!     util::domain_empirical(global_max),
56    //!     |v| circle.sdf(v)
57    //!   );
58    //!   circle
59    //!     .texture(Luma([255u8]).to_rgba()) // fill with white color
60    //!     .draw(&mut image); // draw the shape on image
61    //! }
62    //! image.save(path)?; // save image
63//! #   Ok(())
64//! # }
65//! ```
66//! # GD-ADF solver
67//! ```no_run
68//! # use {
69//! #   space_filling::{
70//! #     geometry::{Shape, Circle, Translation, Scale, P2},
71//! #     sdf::{self, SDF},
72//! #     solver::{line_search::LineSearch, adf::ADF},
73//! #     drawing::Draw,
74//! #     util
75//! #   },
76//! #   image::{Luma, Pixel},
77//! #   anyhow::Result,
78//! #   rand::prelude::*,
79//! #   std::sync::{Arc, RwLock}
80//! # };
81//! #
82//! # fn main() -> Result<()> {
83    //! let path = "out.png";
84    //! let mut representation = RwLock::new(ADF::<f64>::new(5, vec![Arc::new(sdf::boundary_rect)]));
85    //! let mut image = image::RgbaImage::new(2048, 2048);
86    //! // In case of GD-ADF, it is adviced to use `util::local_maxima_iter`,
87    //! // as it is capable of finding multiple local maxima in parallel.
88    //! // By default, this is an infinite iterator.
89    //! util::local_maxima_iter(
90    //!   // provide a closure for sampling distance field
91    //!   Box::new(|p| representation.read().unwrap().sdf(p)),
92    //!   32, 0, LineSearch::default()
93    //! ).filter_map(|local_max| {
94    //!   let circle = Circle
95    //!     .translate(local_max.point.to_vector())
96    //!     .scale(local_max.distance / 4.0);
97    //!   // Update distance field. Since the precision is not perfect, sometimes update may fail -
98    //!   // thus Option is returned
99    //!   representation.write().unwrap().insert_sdf_domain(
100    //!     util::domain_empirical(local_max),
101    //!     Arc::new(move |p| circle.sdf(p))
102    //!   ).then(|| circle)
103    //! }).take(1000) // stop, once 1000 circles were successfully added
104    //!   .for_each(|shape| shape
105    //!     .texture(Luma([255u8]).to_rgba())
106    //!     .draw(&mut image)
107    //!   );
108    //!
109    //! image.save(path)?;
110  //! # Ok(())
111//! # }
112//!
113//! ```
114//! <img src="https://raw.githubusercontent.com/FredericaBernkastel/space-filling/master/doc/fractal_distribution.png">
115//!
116//! # Drawing
117//! Drawing was intended to be a rudimentary module for displaying sdf shapes. It is not highly
118//! optimized, and you are free to use third-party libraries for this purpose.
119//!
120//! There are two traits related to drawing:
121//! - `trait `[`Shape`](`geometry::Shape`)`: `[`SDF`](`sdf::SDF`)` + `[`BoundingBox`](`geometry::BoundingBox`)
122//! - `trait `[`Draw`](`drawing::Draw`)`:`[`Shape`](`geometry::Shape`)`
123//!
124//! Draw is primarily implemented on [`Texture`](`drawing::Texture`):
125//! ```text
126//! .texture(Rgba(...)) -> Texture<T, Rgba<u8>>
127//! .texture(image) -> Texture<T, image::DynamicImage>
128//! .texture(|pixel| { ... }) -> Texture<T, Fn(Point2D) -> Rgba<u8>>
129//! ```
130//!
131//! At first, you could think writing:
132//! ```ignore
133//! let shapes: Vec<Box<dyn Shape>> = vec![
134//!   Box::new(Circle.translate(...).scale(...)),
135//!   Box::new(Square.translate(...).scale(...))
136//! ];
137//! for shape in shapes {
138//!   shape.texture(...)
139//!     .draw(...);
140//! }
141//! ```
142//! But this won't work, because all of `Shape` methods require `Sized`, hence no object safe.
143//! Correct way is:
144//! ```ignore
145//! let shapes: Vec<Box<dyn Draw<RgbaImage>>> = ...
146//! ```
147//! However, since Rust never got a support of trait upcasting, we cannot obtain `dyn Shape` from
148//! `dyn Draw`, hence somewhat limited in capabilities.
149//!
150//! Lastly, there is: [`draw_parallel`](drawing::draw_parallel), which is convenient when shapes
151//! require heavy computations to draw, such as texture loading. It accepts an iterator on
152//! `dyn Draw<RgbaImage> + Send + Sync`, constructed via trait object casting, exactly as above.
153//! See `examples/argmax2d/03_embedded.rs`, `examples/gd_adf/04_polymorphic.rs` and
154//! `drawing/tests::polymorphic_*` for more details.
155//!
156//! This way, both distribution generation and drawing are guaranteed to evenly load all available
157//! cores.
158//!
159//! Have a good day, `nyaa~ =^_^=`
160//!
161//! <img src="https://raw.githubusercontent.com/FredericaBernkastel/space-filling/master/doc/neko.gif">
162
163#![allow(clippy::type_complexity)]
164
165#![cfg_attr(doc, feature(doc_cfg))]
166#![allow(rustdoc::private_intra_doc_links)]
167
168pub mod util;
169pub mod sdf;
170pub mod solver;
171pub mod geometry;
172#[cfg(feature = "drawing")]
173#[cfg_attr(doc, doc(cfg(feature = "drawing")))]
174pub mod drawing;