1use wassily_core::{canvas::Canvas, points::*};
67use wassily_color::rgb8;
68use std::f32::consts::PI;
69use tiny_skia::Color;
70
71pub struct SphereScene<'a> {
87 pub camera: Point3,
88 pub focal_len: f32,
89 pub center: Point3,
90 pub radius: f32,
91 pub texture: &'a Canvas,
92 pub rotation_x: f32,
93 pub rotation_y: f32,
94 pub rotation_z: f32,
95 pub lights: Vec<Light>,
96 pub specular: Option<f32>,
97}
98
99impl<'a> SphereScene<'a> {
100 pub fn new(
101 camera: Point3,
102 focal_len: f32,
103 center: Point3,
104 radius: f32,
105 texture: &'a Canvas,
106 rotation_x: f32,
107 rotation_y: f32,
108 rotation_z: f32,
109 lights: Vec<Light>,
110 specular: Option<f32>,
111 ) -> Self {
112 Self {
113 camera,
114 focal_len,
115 center,
116 radius,
117 texture,
118 rotation_x,
119 rotation_y,
120 rotation_z,
121 lights,
122 specular,
123 }
124 }
125
126 pub fn basic(center: Point3, texture: &'a Canvas) -> Self {
127 Self {
128 camera: Point3::new(0.0, 0.0, 0.0),
129 focal_len: texture.width() as f32,
130 center,
131 radius: texture.width() as f32 / 2.0,
132 texture,
133 rotation_x: PI / 2.0,
134 rotation_y: 0.0,
135 rotation_z: 0.0,
136 lights: vec![],
137 specular: None,
138 }
139 }
140
141 pub fn color(&self, point: Point3) -> Color {
142 let w = 2.0 * self.texture.width() as f32;
143 let h = self.texture.height() as f32;
144 let rot_point = (point - self.center)
145 .rotate_x(self.rotation_x)
146 .rotate_y(self.rotation_y)
147 .rotate_z(self.rotation_z)
148 + self.center;
149 let s = rot_point.to_spherical(self.center);
150 let u = w / 2.0 * (s.phi / PI + 1.0);
151 let v = h * (1.0 - s.theta / PI);
152 let c = self.texture.pixmap.pixel(u as u32, v as u32).unwrap();
153 let mut illumination = if self.lights.is_empty() {
154 1.0
155 } else {
156 let normal = (point - self.center).normalize();
157 lighting(&self.lights, point, normal, self.center, self.specular)
158 };
159 illumination = illumination.clamp(0.0, 1.0);
160 let red = c.red() as f32 * illumination;
161 let green = c.green() as f32 * illumination;
162 let blue = c.blue() as f32 * illumination;
163 rgb8(red as u8, green as u8, blue as u8)
164 }
165
166 pub fn intersect(&self, direction: Point3) -> Option<(f32, f32)> {
167 let w = self.camera - self.center;
168 let a = direction.mag2();
169 let b = 2.0 * w.dot(direction);
170 let c = w.mag2() - self.radius * self.radius;
171 let discr = b * b - 4.0 * a * c;
172 if discr < 0.0 {
173 return None;
174 }
175 let t1 = (-b + discr.sqrt()) / (2.0 * a);
176 let t2 = (-b - discr.sqrt()) / (2.0 * a);
177 Some((t1, t2))
178 }
179
180 pub fn trace_ray(&self, direction: Point3) -> Option<Color> {
181 if let Some((t1, t2)) = self.intersect(direction) {
182 let t = t1.min(t2);
183 let p = self.camera + direction * t;
184 return Some(self.color(p));
185 }
186 None
187 }
188
189 pub fn on_sphere(&self, canvas: &mut Canvas) {
190 let cw2 = canvas.width() as i32 / 2;
191 let ch2 = canvas.height() as i32 / 2;
192 for x in 1 - cw2..cw2 {
193 let x32 = x as f32;
194 for y in 1 - ch2..ch2 {
195 let y32 = y as f32;
196 let d = Point3::new(x32, y32, self.focal_len) - self.camera;
197 if let Some(c) = self.trace_ray(d) {
198 let p = pt(
199 canvas.width() as f32 / 2.0 + x32,
200 canvas.height() as f32 / 2.0 - y32,
201 );
202 canvas.dot(p.x, p.y, c);
203 }
204 }
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy)]
210pub enum LightSource {
211 Ambient,
212 Point,
213 Directional,
214}
215
216#[derive(Debug, Clone, Copy)]
217pub struct Light {
218 source: LightSource,
219 intensity: f32,
220 vector: Point3,
221}
222
223impl Light {
224 pub fn new(source: LightSource, intensity: f32, vector: Point3) -> Self {
225 Self {
226 source,
227 intensity,
228 vector,
229 }
230 }
231
232 pub fn ambient(intensity: f32) -> Self {
233 Self {
234 source: LightSource::Ambient,
235 intensity,
236 vector: Point3::new(0.0, 0.0, 0.0),
237 }
238 }
239
240 pub fn point(intensity: f32, x: f32, y: f32, z: f32) -> Self {
241 let vector = Point3::new(x, y, z);
242 Self {
243 source: LightSource::Point,
244 intensity,
245 vector,
246 }
247 }
248
249 pub fn directional(intensity: f32, x_dir: f32, y_dir: f32, z_dir: f32) -> Self {
250 let vector = Point3::new(x_dir, y_dir, z_dir);
251 Self {
252 source: LightSource::Directional,
253 intensity,
254 vector,
255 }
256 }
257}
258
259pub fn lighting(
260 lights: &[Light],
261 point: Point3,
262 normal: Point3,
263 camera: Point3,
264 specular: Option<f32>,
265) -> f32 {
266 let mut intensity = 0.0;
267 for light in lights {
268 let light_vec = match light.source {
269 LightSource::Ambient => Point3::new(0.0, 0.0, 0.0),
270 LightSource::Point => light.vector - point,
271 LightSource::Directional => light.vector,
272 };
273 match light.source {
274 LightSource::Ambient => intensity += light.intensity,
275 LightSource::Point | LightSource::Directional => {
276 let nl = normal.dot(light_vec);
277 let lv = light_vec.mag();
278 intensity += light.intensity * nl.max(0.0) / lv;
279 if let Some(s) = specular {
280 let r = (normal * 2.0 * normal.dot(light_vec)) - light_vec;
281 let rv = r.dot(point - camera);
282 if rv > 0.0 {
283 intensity +=
284 light.intensity * (rv / (r.mag() * (point - camera).mag())).powf(s)
285 }
286 };
287 }
288 }
289 }
290 intensity
291}