1mod math;
2
3use math::{mat, vec};
4use subsphere::prelude::*;
5
6pub fn render<Sphere: subsphere::Sphere>(
8 scene: &Scene<Sphere>,
9 camera: &Camera,
10 target: &mut image::RgbImage,
11) {
12 let size_x = target.width();
14 let size_y = target.height();
15 for (x, y, pixel) in target.enumerate_pixels_mut() {
16 let mut total = [0.0, 0.0, 0.0];
17 for [offset_x, offset_y] in SAMPLES {
18 let view_x = ((x as f64 + offset_x) / size_x as f64) * 2.0 - 1.0;
19 let view_y = 1.0 - ((y as f64 + offset_y) / size_y as f64) * 2.0;
20 let (start, dir) = camera.unproject([view_x, view_y]);
21 if let Some(t) = trace_sphere(start, dir) {
22 let pos = vec::add(start, vec::mul(dir, t));
23 let face = scene.sphere.face_at(pos);
24 let light =
25 scene.ambient + vec::dot(pos, scene.light_dir).max(0.0) * (1.0 - scene.ambient);
26 let color = scene.faces[face.index()];
27 total = vec::add(total, vec::mul(color, light));
28 } else {
29 total = vec::add(total, scene.background);
30 }
31 }
32 *pixel = to_srgb(vec::div(total, SAMPLES.len() as f64));
33 }
34}
35
36const SAMPLES: &[[f64; 2]] = &[
38 [0.375, 0.125],
39 [0.875, 0.375],
40 [0.125, 0.625],
41 [0.625, 0.875],
42];
43
44pub struct Scene<'a, Sphere> {
46 pub background: Color,
48
49 pub light_dir: [f64; 3],
51
52 pub ambient: f64,
54
55 pub sphere: &'a Sphere,
59
60 pub faces: Box<[Color]>,
62}
63
64pub struct Camera {
66 pos: [f64; 3],
68
69 forward: [f64; 3],
71
72 extent: [[f64; 3]; 2],
74
75 divergence: f64,
79}
80
81impl Camera {
82 pub fn ortho(pos: [f64; 3], extent: [f64; 2]) -> Self {
90 Self::perspective(pos, extent, 0.0)
91 }
92
93 pub fn perspective(pos: [f64; 3], extent: [f64; 2], divergence: f64) -> Self {
98 let up = [0.0, 0.0, 1.0];
99 let forward = vec::normalize(vec::neg(pos));
100 let extent_x = vec::mul(vec::normalize(vec::cross(forward, up)), extent[0]);
101 let extent_y = vec::mul(vec::normalize(vec::cross(extent_x, forward)), extent[1]);
102 Self {
103 pos,
104 forward,
105 extent: [extent_x, extent_y],
106 divergence
107 }
108 }
109
110 fn unproject(&self, coords: [f64; 2]) -> ([f64; 3], [f64; 3]) {
112 let offset = mat::apply(self.extent, coords);
113 let start = vec::add(self.pos, offset);
114 let dir = vec::normalize(vec::add(self.forward, vec::mul(offset, self.divergence)));
115 (start, dir)
116 }
117}
118
119pub type Color = [f64; 3];
123
124pub fn colorize(sphere: &impl subsphere::Sphere) -> Box<[Color]> {
126 use rand::seq::SliceRandom;
127 use rand::{Rng, SeedableRng};
128 let mut face_colors: Box<[Option<u32>]> = vec![None; sphere.num_faces()].into_boxed_slice();
129
130 let mut rng = rand::rngs::SmallRng::seed_from_u64(1);
132 let mut faces = sphere.faces().collect::<Vec<_>>();
133 faces.shuffle(&mut rng);
134 let mut num_colors = 6;
135 'retry: loop {
136 for face in faces.iter() {
137 let index = face.index();
138 let mut available_colors = (1u64 << num_colors) - 1;
139 for side in face.sides() {
140 let neighbor = side.complement().inside();
141 if let Some(color) = face_colors[neighbor.index()] {
142 available_colors &= !(1 << color);
143 }
144 }
145 if available_colors == 0 {
146 num_colors += 1;
148 continue 'retry;
149 }
150 let mut select = rng.random_range(0..available_colors.count_ones());
151 let mut color = available_colors.trailing_zeros();
152 while select > 0 {
153 available_colors &= !(1 << color);
154 select -= 1;
155 color = available_colors.trailing_zeros();
156 }
157 face_colors[index] = Some(color);
158 }
159 break;
160 }
161
162 face_colors
164 .into_iter()
165 .map(|i| PALETTE[i.unwrap() as usize])
166 .collect::<Vec<_>>()
167 .into_boxed_slice()
168}
169
170const PALETTE: &[Color] = &[
172 [1.0, 0.2, 0.2], [0.2, 0.8, 0.2], [0.1, 0.4, 1.0], [1.0, 1.0, 0.2], [1.0, 0.2, 1.0], [0.2, 1.0, 1.0], [0.7, 0.7, 0.7], ];
180
181fn to_srgb(linear: Color) -> image::Rgb<u8> {
183 image::Rgb(palette::Srgb::<u8>::from(palette::LinSrgb::from(linear)).into())
184}
185
186fn trace_sphere(start: [f64; 3], dir: [f64; 3]) -> Option<f64> {
191 let a = 1.0;
192 let b = 2.0 * vec::dot(start, dir);
193 let c = vec::dot(start, start) - 1.0;
194 let disc = b * b - 4.0 * a * c;
195 if disc >= 0.0 {
196 let u = -b - b.signum() * disc.sqrt();
197 let t_0 = u / (2.0 * a);
198 let t_1 = 2.0 * c / u;
199 [t_0, t_1]
200 .into_iter()
201 .filter(|&t| t >= 0.0)
202 .min_by(|a, b| a.partial_cmp(b).unwrap())
203 } else {
204 None
205 }
206}