wassily_algorithms/endo2d.rs
1//! # 2D Endomorphisms
2//!
3//! Mathematical transformations that map points from the unit square to itself.
4//! These functions are commonly used in fractal systems, particularly Iterated
5//! Function Systems (IFS), and provide various artistic and mathematical effects
6//! for generative art applications.
7//!
8//! ## What are Endomorphisms?
9//!
10//! An endomorphism is a mathematical function that maps a space to itself.
11//! In this context, all functions map points within or around the unit square
12//! `[-1, 1] × [-1, 1]` to new positions, creating various visual effects.
13//!
14//! ## Available Transformations
15//!
16//! ### Trigonometric Functions
17//! - [`sinusoid()`]: Applies sine function to both coordinates
18//! - [`hankerchief()`]: Creates handkerchief-like distortions
19//! - [`heart()`]: Creates heart-shaped transformations
20//!
21//! ### Geometric Transformations
22//! - [`spherical()`]: Spherical inversion transformation
23//! - [`swirl()`]: Creates swirling, spiral effects
24//! - [`horseshoe()`]: Horseshoe-shaped transformation
25//! - [`disc()`]: Disc-based coordinate transformation
26//!
27//! ### Coordinate System Changes
28//! - [`to_polar()`]: Converts to polar coordinate representation
29//!
30//! ## Usage in Fractal Systems
31//!
32//! ```no_run
33//! use wassily_algorithms::*;
34//! use wassily_core::*;
35//!
36//! // Create an IFS (Iterated Function System)
37//! let mut point = pt(0.5, 0.3);
38//! let mut canvas = Canvas::new(800, 600);
39//!
40//! // Apply transformations iteratively
41//! for _ in 0..10000 {
42//! // Randomly choose a transformation
43//! point = match rand::random::<u8>() % 3 {
44//! 0 => swirl(point),
45//! 1 => heart(point),
46//! _ => spherical(point),
47//! };
48//!
49//! // Plot the transformed point
50//! let screen_x = (point.x + 1.0) * 400.0;
51//! let screen_y = (point.y + 1.0) * 300.0;
52//! canvas.dot(screen_x, screen_y, *WHITE);
53//! }
54//! ```
55//!
56//! ## Mathematical Properties
57//!
58//! Each transformation has unique mathematical properties:
59//!
60//! - **Continuous**: All transformations are continuous functions
61//! - **Bounded**: Most transformations keep points within reasonable bounds
62//! - **Differentiable**: Most functions are smooth and differentiable
63//! - **Invertible**: Some transformations are invertible, others are not
64//!
65//! ## Applications
66//!
67//! - **Fractal Generation**: IFS fractals and strange attractors
68//! - **Artistic Effects**: Distortion effects for images and shapes
69//! - **Mathematical Visualization**: Studying function behavior
70//! - **Animation**: Creating organic, flowing motion patterns
71
72use wassily_core::points::{pt, Algebra};
73use tiny_skia::Point;
74use std::f32::consts::PI;
75
76/// Applies the sine function to both x and y coordinates.
77///
78/// This transformation creates a grid-like pattern with curved boundaries,
79/// mapping the input coordinates through sine functions.
80///
81/// # Example
82/// ```no_run
83/// use wassily_algorithms::sinusoid;
84/// use wassily_core::pt;
85///
86/// let input = pt(1.0, 0.5);
87/// let result = sinusoid(input);
88/// ```
89pub fn sinusoid(p: Point) -> Point {
90 pt(p.x.sin(), p.y.sin())
91}
92
93/// Spherical inversion transformation.
94///
95/// This transformation scales the point by its magnitude, creating a radial
96/// distortion effect that resembles looking at the plane through a spherical lens.
97///
98/// # Example
99/// ```no_run
100/// use wassily_algorithms::spherical;
101/// use wassily_core::pt;
102///
103/// let input = pt(0.5, 0.3);
104/// let result = spherical(input);
105/// ```
106pub fn spherical(p: Point) -> Point {
107 p.scale(p.mag())
108}
109
110/// Creates a swirling, spiral transformation.
111///
112/// This transformation rotates points around the origin with the rotation
113/// angle proportional to the square of the distance from the origin,
114/// creating a spiral or whirlpool effect.
115///
116/// # Example
117/// ```no_run
118/// use wassily_algorithms::swirl;
119/// use wassily_core::pt;
120///
121/// let input = pt(0.8, 0.2);
122/// let result = swirl(input);
123/// ```
124pub fn swirl(p: Point) -> Point {
125 let r = p.mag();
126 let r2 = r * r;
127 pt(
128 p.x * r2.sin() - p.y * r2.cos(),
129 p.x * r2.cos() + p.y * r2.sin(),
130 )
131}
132
133pub fn horseshoe(p: Point) -> Point {
134 pt((p.x + p.y) * (p.x - p.y), 2.0 * p.x * p.y).scale(1.0 / p.mag())
135}
136
137pub fn to_polar(p: Point) -> Point {
138 pt(p.x.atan2(p.y) / PI, p.mag() - 1.0)
139}
140
141pub fn hankerchief(p: Point) -> Point {
142 let theta = p.x.atan2(p.y);
143 let r = p.mag();
144 pt((theta + r).sin(), (theta - r).cos()).scale(r)
145}
146
147pub fn heart(p: Point) -> Point {
148 let theta = p.x.atan2(p.y);
149 let r = p.mag();
150 pt((theta * r).sin(), -(theta * r).cos()).scale(r)
151}
152
153pub fn disc(p: Point) -> Point {
154 let theta = p.x.atan2(p.y);
155 let r = p.mag();
156 pt((PI * r).sin(), (PI * r).cos()).scale(theta / PI)
157}
158
159pub fn spiral(p: Point) -> Point {
160 let theta = p.x.atan2(p.y);
161 let r = p.mag();
162 pt(theta.cos() + r.sin(), theta.sin() - r.cos()).scale(1.0 / p.mag())
163}
164
165pub fn hyperbolic(p: Point) -> Point {
166 let theta = p.x.atan2(p.y);
167 let r = p.mag();
168 pt(theta.sin() / r, r * theta.cos())
169}
170
171pub fn diamond(p: Point) -> Point {
172 let theta = p.x.atan2(p.y);
173 let r = p.mag();
174 pt(theta.sin() * r.cos(), theta.cos() * r.sin())
175}
176
177pub fn ex(p: Point) -> Point {
178 let theta = p.x.atan2(p.y);
179 let r = p.mag();
180 let p0 = (theta + r).sin();
181 let p1 = (theta - r).cos();
182 pt(p0 * p0 * p0 + p1 * p1 * p1, p0 * p0 * p0 - p1 * p1 * p1)
183}
184
185pub fn fisheye(p: Point) -> Point {
186 pt(p.x, p.y).scale(2.0 / (1.0 + p.mag()))
187}
188
189pub fn exponential(p: Point) -> Point {
190 pt((PI * p.y).cos(), (PI * p.y).sin()).scale((p.x - 1.0).exp())
191}
192
193pub fn tangent(p: Point) -> Point {
194 pt(p.x.sin() / p.y.cos(), p.y.tan())
195}
196
197pub fn cross(p: Point) -> Point {
198 let s = 1.0 / ((p.x * p.x - p.y * p.y) * (p.x * p.x - p.y * p.y));
199 p.scale(s)
200}