Skip to main content

pathtracer/tools/
mod.rs

1/*!
2Utility functions
3 */
4
5extern crate image;
6extern crate rand;
7
8use super::{Coordinate, Hash};
9use image::Rgba;
10use rand::{distributions::Uniform, Rng};
11
12use std::{
13    cmp::{max, min},
14    f64,
15    mem::swap,
16};
17
18/**
19Finds an element using their hashes.
20*/
21pub fn find<T: Hash>(element: u64, list: &[T]) -> Option<&T> {
22    list.iter().find(|&x| x.hash() == element)
23}
24
25/**
26Returns a Rgba with a modified value depending on how close it is to it's falloff point.
27
28
29## Examples
30
31First declare the colors and the range at which it becomes darker.
32
33```
34extern crate image;
35use pathtracer::{tools, Coordinate};
36let falloff = 100;
37let color = image::Rgba {
38     data: [100, 100, 100, 255],
39};
40```
41
42Evaluate that based on the modified distance, in the small sense it is modified from the defined color above.
43
44```
45# extern crate image;
46# use pathtracer::{tools, Coordinate};
47# let falloff = 100;
48# let color = image::Rgba([100, 100, 100, 255]);
49let base = Coordinate::new(0, 0);
50let to = Coordinate::new(10, 10);
51
52assert_eq!(
53    tools::range_color(falloff, color, base, to),
54    image::Rgba([77, 77, 77, 255])
55);
56```
57*/
58pub fn range_color(
59    falloff: i16,
60    base: image::Rgba<u8>,
61    base_geo: Coordinate,
62    to_geo: Coordinate,
63) -> image::Rgba<u8> {
64    let diff = (base_geo - to_geo).abs();
65    let x_scale: f64 = f64::from(diff.x) / f64::from(falloff);
66    let y_scale: f64 = f64::from(diff.y) / f64::from(falloff);
67    let max_multi: f64 =
68        f64::from(i32::from(base[0]) + i32::from(base[1]) + i32::from(base[2]) / 3);
69    let modify = (-max_multi * (x_scale + y_scale) / 2.0) as i32;
70
71    image::Rgba([
72        border(base[0], modify),
73        border(base[1], modify),
74        border(base[2], modify),
75        border(base[3], 0),
76    ])
77}
78
79/**
80 Returns a random number between the min and maximum.
81
82
83## Examples
84
85```
86# use pathtracer::tools;
87let nr = tools::roll(50u32, 60);
88assert!(nr >= 50 && nr <= 60);
89```
90 */
91pub fn roll<T: Into<u32>>(min: T, max: T) -> u32 {
92    let mut rng = rand::thread_rng();
93    rng.sample(Uniform::new(min.into(), max.into()))
94}
95
96/**
97Returns a random item from a given list.
98*/
99pub fn random_item(list: &[String]) -> &String {
100    let roll = roll(0, list.len() as u32);
101    &list[roll as usize]
102}
103
104/**
105 Checks so that the applied adjustments stay within a u8.
106
107
108## Examples
109
110 ```
111 use pathtracer::tools;
112 let a = 50;
113 let b = 250;
114 assert_eq!(tools::border(a, b), 255);
115 assert_eq!(tools::border(a, -b), 0);
116 ```
117 */
118pub fn border(a: u8, b: i32) -> u8 {
119    max(min(i32::from(a) + b, 255 as i32), 0) as u8
120}
121
122/**
123Returns a random Rgb color. the opacity is always 255.
124
125
126## Examples
127
128```
129# use pathtracer::tools;
130let rgba = tools::gen_rgba();
131println!("{:?}", rgba.data);
132```
133*/
134pub fn gen_rgba() -> Rgba<u8> {
135    (0..4).fold(super::consts::DEFAULT_RGBA, |mut acc, x| {
136        acc.data[x] = acc.data[x].saturating_add(roll(0u8, u8::max_value()) as u8);
137        acc
138    })
139}
140
141/**
142Returns a Rgb color based on a seed value. the opacity is always 255.
143*/
144pub fn seed_rgba(seed: u64) -> Rgba<u8> {
145    let max = 254;
146    let r = seed % max;
147    let g = (seed + 75) % max;
148    let b = (seed + 150) % max;
149
150    Rgba([r as u8, g as u8, b as u8, 255])
151}
152
153/**
154Generates a list of Coordinates between two points. Required for drawing direct edges.
155
156Implemented according to bresenham's 4 way line algorithm.
157
158More information can be found here.
159
160https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
161
162
163## Examples
164
165First we create a simple path between two coordinates.
166
167```
168use pathtracer::{tools, Coordinate};
169let a = Coordinate::new(0, 0);
170let b = Coordinate::new(1, 1);
171let path = tools::plot(a, b);
172```
173
174Paths can also be used from macro invocations.
175
176```
177# #[macro_use] use pathtracer::*;
178# fn main() {
179let path = tools::plot(coordinate!(), coordinate!(100, 100));
180# }
181```
182*/
183pub fn plot(a: Coordinate, b: Coordinate) -> Vec<Coordinate> {
184    plot_type(a, b, &plot_bresenham)
185}
186
187pub fn plot_type(
188    mut a: Coordinate,
189    mut b: Coordinate,
190    plot_kind: &Fn(Coordinate, Coordinate) -> Vec<Coordinate>,
191) -> Vec<Coordinate> {
192    // If any of the coordinates are negative, interally add to make them positive.
193    if a.lt(0) || b.lt(0) {
194        let add = Coordinate::new(max(-a.x, -b.x), max(-a.y, -b.y));
195        plot_type(a + add, b + add, &plot_bresenham)
196            .iter()
197            .fold(vec![], |mut acc, c| {
198                acc.push(*c - add);
199                acc
200            })
201    // If it's a vertical line
202    } else if a.x == b.x {
203        (min(a.y, b.y)..max(a.y, b.y))
204            .map(|y| Coordinate::new(a.x, y))
205            .collect()
206    } else {
207        if b.x < a.x {
208            swap(&mut a, &mut b);
209        }
210        plot_kind(a, b)
211    }
212}
213
214/**
215Draws a line between two coordinate points.
216Derived from: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
217
218
219## Panics
220
221from.x == to.x
222*/
223pub fn plot_bresenham(mut from: Coordinate, to: Coordinate) -> Vec<Coordinate> {
224    let delta = to - from;
225    let delta_x = f64::from(delta.x);
226    let delta_y = f64::from(delta.y);
227
228    if delta_x == 0.00 {
229        panic!("Bresenham does not support straight vertical lines!");
230    }
231
232    let delta_err: f64 = (delta_y / delta_x).abs();
233    let mut error: f64 = 0.00;
234
235    let mut plot: Vec<Coordinate> = Vec::new();
236    let mut last_y = from.y;
237
238    for x in min(from.x, to.x)..=max(from.x, to.x) {
239        for y in min(last_y, from.y)..=max(last_y, from.y) {
240            plot.push(Coordinate::new(x as i16, y));
241        }
242        last_y = from.y;
243        error += delta_err;
244        while error >= 0.50 {
245            from.y += f64::signum(delta_y) as i16;
246            error -= 1.00;
247        }
248    }
249    plot
250}
251
252/**
253Draws a line between two coordinate points in straight horizontal or vertical lines.
254*/
255pub fn plot_rectangle(mut from: Coordinate, to: Coordinate) -> Vec<Coordinate> {
256    let mut plot: Vec<Coordinate> = Vec::new();
257    let mut delta = Coordinate::new(1, 1);
258
259    if from.x > to.x {
260        delta.x = -1;
261    }
262
263    if from.y > to.y {
264        delta.y = -1;
265    }
266
267    while from.x != to.x {
268        from.x += delta.x;
269        plot.push(from);
270    }
271
272    while from.y != to.y {
273        from.y += delta.y;
274        plot.push(from);
275    }
276
277    plot
278}
279
280/**
281Gives the midpoint between two points.
282
283
284## Examples
285
286Declare the colors and the range at which it becomes darker.
287
288```
289# use pathtracer::{tools, Coordinate};
290let a = Coordinate::new(0, 0);
291let b = Coordinate::new(100, 100);
292let mid = tools::midpoint(a, b);
293assert_eq!(mid, Coordinate::new(50, 50));
294```
295*/
296pub fn midpoint(a: Coordinate, b: Coordinate) -> Coordinate {
297    Coordinate::new((a.x + b.x) / 2, (a.y + b.y) / 2)
298}
299
300/**
301Draws a line between two coordinate points in the form on a ellipse.
302
303Derived from: https://en.wikipedia.org/wiki/Ellipse
304
305
306## Experimental
307
308This functionality is heavily a work in progress and it's behaviour is unreliable.
309*/
310pub fn plot_ellipse(mut from: Coordinate, to: Coordinate) -> Vec<Coordinate> {
311    let min = Coordinate::new(min(from.x, to.x), min(from.y, to.y));
312    let _max = Coordinate::new(max(from.x, to.x), max(from.y, to.y));
313    let _t_max = midpoint(from, to);
314
315    let mut result = Vec::new();
316    let t = f64::consts::PI / 2.0; // How the ellipse is angled. and how steep it angles.
317    let mut theta: f64 = 0.5; // starting angle.
318    let diff = from - to;
319    let step: f64 = t / 4.5; // Number of ellipse steps from start to finish.
320    let c = min;
321    let r: f64 = f64::from(diff.x / 2).abs(); // distance from center to node aka radius.
322
323    debug!(
324        "r: {} |t: {} |s: {} |from: {} |to: {} |diff: {} |c: {}",
325        r, t, step, from, to, diff, c
326    );
327
328    // -r = J shape. r = n shape.
329    let mut s = Coordinate::new(1, 1);
330    if from.y > to.y {
331        s.y = -1;
332    }
333
334    while t > theta {
335        let point = from + coordinate!(r * theta.cos(), f64::from(s.y) * r * theta.sin() / 2.0);
336        debug!("Theta: {} | Coordinate: {} <- {}", theta, point, from);
337        let mut line = plot_type(from, point, &plot_bresenham);
338        result.append(&mut line);
339        from = point;
340        theta += step;
341    }
342    result.append(&mut plot_type(from, to, &plot_bresenham));
343    debug!("{:#?}", result);
344    result
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    #[test]
352    fn test_border() {
353        assert_eq!(border(0, 0), 0);
354        assert_eq!(border(0, -55), 0);
355        assert_eq!(border(100, 100), 200);
356        assert_eq!(border(255, 255), 255);
357        assert_eq!(border(0, 255), 255);
358        assert_eq!(border(255, 0), 255);
359        assert_eq!(border(255, -255), 0);
360    }
361
362    #[test]
363    fn test_roll() {
364        let res = roll(0u32, 5);
365        assert!(res <= 5);
366    }
367
368    #[test]
369    fn test_random_item() {
370        let strings = ["a".to_string(), "b".to_string()];
371        let res = random_item(&strings);
372        let res = res == &strings[0] || res == &strings[1];
373        assert!(res);
374    }
375
376    #[test]
377    fn test_gen_rgba() {
378        for _ in 0..5 {
379            let _ = gen_rgba();
380        }
381    }
382
383    #[test]
384    fn test_seed_rgba() {
385        let seed = 9611;
386        for i in 0..30 {
387            let _ = seed_rgba(seed * i);
388        }
389    }
390
391    #[test]
392    fn test_seed_rgba_consistancy() {
393        let seed = 9611;
394        let res = seed_rgba(seed);
395        for _ in 0..30 {
396            assert_eq!(seed_rgba(seed), res);
397        }
398    }
399
400    #[test]
401    fn test_plot_start_and_end() {
402        let c1 = Coordinate::new(0, 0);
403        let c2 = Coordinate::new(10, 10);
404        let plot = plot(c1, c2);
405        assert!(c1 == plot[0] && c2 == plot[plot.len() - 1]);
406    }
407
408    #[test]
409    fn test_plot_straight_line() {
410        let c1 = Coordinate::new(0, 0);
411        let c2 = Coordinate::new(0, 10);
412        let plot = plot(c1, c2);
413        for i in 0..10 {
414            assert_eq!(plot[i].y, i as i16);
415        }
416    }
417}