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;