1use crate::basics::{iround, VertexSource};
7use crate::color::Rgba8;
8use crate::dda_line::DdaLineInterpolator;
9use crate::math::cross_product;
10use crate::renderer_scanline::SpanGenerator;
11use crate::span_gouraud::{CoordType, SpanGouraud};
12
13const SUBPIXEL_SHIFT: i32 = 4;
14const SUBPIXEL_SCALE: i32 = 1 << SUBPIXEL_SHIFT;
15
16struct RgbaCalc {
27 x1: f64,
28 y1: f64,
29 dx: f64,
30 inv_dy: f64,
31 r1: i32,
32 g1: i32,
33 b1: i32,
34 a1: i32,
35 dr: i32,
36 dg: i32,
37 db: i32,
38 da: i32,
39 r: i32,
40 g: i32,
41 b: i32,
42 a: i32,
43 x: i32,
44}
45
46impl RgbaCalc {
47 fn new() -> Self {
48 Self {
49 x1: 0.0,
50 y1: 0.0,
51 dx: 0.0,
52 inv_dy: 0.0,
53 r1: 0,
54 g1: 0,
55 b1: 0,
56 a1: 0,
57 dr: 0,
58 dg: 0,
59 db: 0,
60 da: 0,
61 r: 0,
62 g: 0,
63 b: 0,
64 a: 0,
65 x: 0,
66 }
67 }
68
69 fn init(&mut self, c1: &CoordType<Rgba8>, c2: &CoordType<Rgba8>) {
70 self.x1 = c1.x - 0.5;
71 self.y1 = c1.y - 0.5;
72 self.dx = c2.x - c1.x;
73 let dy = c2.y - c1.y;
74 self.inv_dy = if dy < 1e-5 { 1e5 } else { 1.0 / dy };
75 self.r1 = c1.color.r as i32;
76 self.g1 = c1.color.g as i32;
77 self.b1 = c1.color.b as i32;
78 self.a1 = c1.color.a as i32;
79 self.dr = c2.color.r as i32 - self.r1;
80 self.dg = c2.color.g as i32 - self.g1;
81 self.db = c2.color.b as i32 - self.b1;
82 self.da = c2.color.a as i32 - self.a1;
83 }
84
85 fn calc(&mut self, y: f64) {
86 let k = ((y - self.y1) * self.inv_dy).clamp(0.0, 1.0);
87 self.r = self.r1 + iround(self.dr as f64 * k);
88 self.g = self.g1 + iround(self.dg as f64 * k);
89 self.b = self.b1 + iround(self.db as f64 * k);
90 self.a = self.a1 + iround(self.da as f64 * k);
91 self.x = iround((self.x1 + self.dx * k) * SUBPIXEL_SCALE as f64);
92 }
93}
94
95fn dda_sub(dda: &mut DdaLineInterpolator<14, 0>, n: i32) {
97 if n >= 0 {
98 dda.dec_by(n as u32);
99 } else {
100 dda.inc_by((-n) as u32);
101 }
102}
103
104pub struct SpanGouraudRgba {
116 base: SpanGouraud<Rgba8>,
117 swap: bool,
118 y2: i32,
119 rgba1: RgbaCalc,
120 rgba2: RgbaCalc,
121 rgba3: RgbaCalc,
122}
123
124impl SpanGouraudRgba {
125 pub fn new() -> Self {
126 Self {
127 base: SpanGouraud::new(),
128 swap: false,
129 y2: 0,
130 rgba1: RgbaCalc::new(),
131 rgba2: RgbaCalc::new(),
132 rgba3: RgbaCalc::new(),
133 }
134 }
135
136 #[allow(clippy::too_many_arguments)]
137 pub fn new_with_triangle(
138 c1: Rgba8,
139 c2: Rgba8,
140 c3: Rgba8,
141 x1: f64,
142 y1: f64,
143 x2: f64,
144 y2: f64,
145 x3: f64,
146 y3: f64,
147 d: f64,
148 ) -> Self {
149 Self {
150 base: SpanGouraud::new_with_triangle(c1, c2, c3, x1, y1, x2, y2, x3, y3, d),
151 swap: false,
152 y2: 0,
153 rgba1: RgbaCalc::new(),
154 rgba2: RgbaCalc::new(),
155 rgba3: RgbaCalc::new(),
156 }
157 }
158
159 pub fn colors(&mut self, c1: Rgba8, c2: Rgba8, c3: Rgba8) {
161 self.base.colors(c1, c2, c3);
162 }
163
164 #[allow(clippy::too_many_arguments)]
166 pub fn triangle(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, d: f64) {
167 self.base.triangle(x1, y1, x2, y2, x3, y3, d);
168 }
169}
170
171impl Default for SpanGouraudRgba {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177impl VertexSource for SpanGouraudRgba {
178 fn rewind(&mut self, path_id: u32) {
179 self.base.rewind(path_id);
180 }
181
182 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
183 self.base.vertex(x, y)
184 }
185}
186
187impl SpanGenerator for SpanGouraudRgba {
188 type Color = Rgba8;
189
190 fn prepare(&mut self) {
191 let coord = self.base.arrange_vertices();
192
193 self.y2 = coord[1].y as i32;
194
195 self.swap = cross_product(
196 coord[0].x, coord[0].y, coord[2].x, coord[2].y, coord[1].x, coord[1].y,
197 ) < 0.0;
198
199 self.rgba1.init(&coord[0], &coord[2]);
200 self.rgba2.init(&coord[0], &coord[1]);
201 self.rgba3.init(&coord[1], &coord[2]);
202 }
203
204 fn generate(&mut self, span: &mut [Rgba8], x: i32, y: i32, len: u32) {
205 self.rgba1.calc(y as f64);
206
207 let (pc1_r, pc1_g, pc1_b, pc1_a, pc1_x, pc2_r, pc2_g, pc2_b, pc2_a, pc2_x);
208
209 if y <= self.y2 {
210 self.rgba2.calc(y as f64 + self.rgba2.inv_dy);
212 if self.swap {
213 pc1_r = self.rgba2.r;
214 pc1_g = self.rgba2.g;
215 pc1_b = self.rgba2.b;
216 pc1_a = self.rgba2.a;
217 pc1_x = self.rgba2.x;
218 pc2_r = self.rgba1.r;
219 pc2_g = self.rgba1.g;
220 pc2_b = self.rgba1.b;
221 pc2_a = self.rgba1.a;
222 pc2_x = self.rgba1.x;
223 } else {
224 pc1_r = self.rgba1.r;
225 pc1_g = self.rgba1.g;
226 pc1_b = self.rgba1.b;
227 pc1_a = self.rgba1.a;
228 pc1_x = self.rgba1.x;
229 pc2_r = self.rgba2.r;
230 pc2_g = self.rgba2.g;
231 pc2_b = self.rgba2.b;
232 pc2_a = self.rgba2.a;
233 pc2_x = self.rgba2.x;
234 }
235 } else {
236 self.rgba3.calc(y as f64 - self.rgba3.inv_dy);
238 if self.swap {
239 pc1_r = self.rgba3.r;
240 pc1_g = self.rgba3.g;
241 pc1_b = self.rgba3.b;
242 pc1_a = self.rgba3.a;
243 pc1_x = self.rgba3.x;
244 pc2_r = self.rgba1.r;
245 pc2_g = self.rgba1.g;
246 pc2_b = self.rgba1.b;
247 pc2_a = self.rgba1.a;
248 pc2_x = self.rgba1.x;
249 } else {
250 pc1_r = self.rgba1.r;
251 pc1_g = self.rgba1.g;
252 pc1_b = self.rgba1.b;
253 pc1_a = self.rgba1.a;
254 pc1_x = self.rgba1.x;
255 pc2_r = self.rgba3.r;
256 pc2_g = self.rgba3.g;
257 pc2_b = self.rgba3.b;
258 pc2_a = self.rgba3.a;
259 pc2_x = self.rgba3.x;
260 }
261 }
262
263 let mut nlen = (pc2_x - pc1_x).abs();
265 if nlen <= 0 {
266 nlen = 1;
267 }
268
269 let mut r = DdaLineInterpolator::<14, 0>::new(pc1_r, pc2_r, nlen as u32);
270 let mut g = DdaLineInterpolator::<14, 0>::new(pc1_g, pc2_g, nlen as u32);
271 let mut b = DdaLineInterpolator::<14, 0>::new(pc1_b, pc2_b, nlen as u32);
272 let mut a = DdaLineInterpolator::<14, 0>::new(pc1_a, pc2_a, nlen as u32);
273
274 let mut start = pc1_x - (x << SUBPIXEL_SHIFT);
276 dda_sub(&mut r, start);
277 dda_sub(&mut g, start);
278 dda_sub(&mut b, start);
279 dda_sub(&mut a, start);
280 nlen += start;
281
282 let lim = Rgba8::BASE_MASK as i32;
283 let mut idx = 0usize;
284 let mut remaining = len as i32;
285
286 while remaining > 0 && start > 0 {
288 let vr = r.y().clamp(0, lim);
289 let vg = g.y().clamp(0, lim);
290 let vb = b.y().clamp(0, lim);
291 let va = a.y().clamp(0, lim);
292 span[idx].r = vr as u8;
293 span[idx].g = vg as u8;
294 span[idx].b = vb as u8;
295 span[idx].a = va as u8;
296 r.inc_by(SUBPIXEL_SCALE as u32);
297 g.inc_by(SUBPIXEL_SCALE as u32);
298 b.inc_by(SUBPIXEL_SCALE as u32);
299 a.inc_by(SUBPIXEL_SCALE as u32);
300 nlen -= SUBPIXEL_SCALE;
301 start -= SUBPIXEL_SCALE;
302 idx += 1;
303 remaining -= 1;
304 }
305
306 while remaining > 0 && nlen > 0 {
308 span[idx].r = r.y() as u8;
309 span[idx].g = g.y() as u8;
310 span[idx].b = b.y() as u8;
311 span[idx].a = a.y() as u8;
312 r.inc_by(SUBPIXEL_SCALE as u32);
313 g.inc_by(SUBPIXEL_SCALE as u32);
314 b.inc_by(SUBPIXEL_SCALE as u32);
315 a.inc_by(SUBPIXEL_SCALE as u32);
316 nlen -= SUBPIXEL_SCALE;
317 idx += 1;
318 remaining -= 1;
319 }
320
321 while remaining > 0 {
323 let vr = r.y().clamp(0, lim);
324 let vg = g.y().clamp(0, lim);
325 let vb = b.y().clamp(0, lim);
326 let va = a.y().clamp(0, lim);
327 span[idx].r = vr as u8;
328 span[idx].g = vg as u8;
329 span[idx].b = vb as u8;
330 span[idx].a = va as u8;
331 r.inc_by(SUBPIXEL_SCALE as u32);
332 g.inc_by(SUBPIXEL_SCALE as u32);
333 b.inc_by(SUBPIXEL_SCALE as u32);
334 a.inc_by(SUBPIXEL_SCALE as u32);
335 idx += 1;
336 remaining -= 1;
337 }
338 }
339}
340
341#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn test_new_default() {
351 let sg = SpanGouraudRgba::new();
352 assert_eq!(sg.y2, 0);
353 assert!(!sg.swap);
354 }
355
356 #[test]
357 fn test_prepare_simple_triangle() {
358 let mut sg = SpanGouraudRgba::new();
359 let red = Rgba8::new(255, 0, 0, 255);
360 let green = Rgba8::new(0, 255, 0, 255);
361 let blue = Rgba8::new(0, 0, 255, 255);
362 sg.colors(red, green, blue);
363 sg.triangle(0.0, 0.0, 100.0, 50.0, 50.0, 100.0, 0.0);
364 sg.prepare();
365 assert!(sg.y2 >= 0);
367 }
368
369 #[test]
370 fn test_generate_horizontal_gradient() {
371 let mut sg = SpanGouraudRgba::new();
373 let red = Rgba8::new(255, 0, 0, 255);
374 let green = Rgba8::new(0, 255, 0, 255);
375 let blue_ish = Rgba8::new(128, 128, 0, 255);
376 sg.colors(red, green, blue_ish);
377 sg.triangle(0.0, 0.0, 100.0, 0.0, 50.0, 100.0, 0.0);
378 sg.prepare();
379
380 let mut span = vec![Rgba8::default(); 10];
381 sg.generate(&mut span, 0, 50, 10);
382
383 let has_nonzero = span.iter().any(|c| c.a > 0);
386 assert!(has_nonzero, "Expected some visible pixels");
387 }
388
389 #[test]
390 fn test_generate_single_color() {
391 let c = Rgba8::new(100, 150, 200, 255);
393 let mut sg = SpanGouraudRgba::new();
394 sg.colors(c, c, c);
395 sg.triangle(0.0, 0.0, 100.0, 0.0, 50.0, 100.0, 0.0);
396 sg.prepare();
397
398 let mut span = vec![Rgba8::default(); 5];
399 sg.generate(&mut span, 20, 25, 5);
400
401 for pixel in &span {
403 assert!(
404 (pixel.r as i32 - 100).abs() <= 2,
405 "r={} expected ~100",
406 pixel.r
407 );
408 assert!(
409 (pixel.g as i32 - 150).abs() <= 2,
410 "g={} expected ~150",
411 pixel.g
412 );
413 assert!(
414 (pixel.b as i32 - 200).abs() <= 2,
415 "b={} expected ~200",
416 pixel.b
417 );
418 }
419 }
420
421 #[test]
422 fn test_vertex_source_delegation() {
423 let mut sg = SpanGouraudRgba::new();
424 let c = Rgba8::new(128, 128, 128, 255);
425 sg.colors(c, c, c);
426 sg.triangle(10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 0.0);
427
428 sg.rewind(0);
429 let mut x = 0.0;
430 let mut y = 0.0;
431 let cmd = sg.vertex(&mut x, &mut y);
432 assert_eq!(cmd, 1); assert_eq!(x, 10.0);
434 assert_eq!(y, 20.0);
435 }
436
437 #[test]
438 fn test_new_with_triangle() {
439 let red = Rgba8::new(255, 0, 0, 255);
440 let green = Rgba8::new(0, 255, 0, 255);
441 let blue = Rgba8::new(0, 0, 255, 255);
442 let mut sg = SpanGouraudRgba::new_with_triangle(
443 red, green, blue, 0.0, 0.0, 100.0, 0.0, 50.0, 100.0, 0.0,
444 );
445 sg.prepare();
446 let mut span = vec![Rgba8::default(); 3];
448 sg.generate(&mut span, 40, 50, 3);
449 }
450
451 #[test]
452 fn test_rgba_calc_init_and_calc() {
453 let c1 = CoordType {
454 x: 0.0,
455 y: 0.0,
456 color: Rgba8::new(0, 0, 0, 255),
457 };
458 let c2 = CoordType {
459 x: 100.0,
460 y: 100.0,
461 color: Rgba8::new(255, 255, 255, 255),
462 };
463
464 let mut calc = RgbaCalc::new();
465 calc.init(&c1, &c2);
466
467 calc.calc(0.0);
470 assert!(calc.r <= 2, "r={}", calc.r);
471 assert!(calc.g <= 2, "g={}", calc.g);
472
473 calc.calc(100.0);
475 assert!(calc.r >= 253, "r={}", calc.r);
476 assert!(calc.g >= 253, "g={}", calc.g);
477
478 calc.calc(50.0);
480 assert!(calc.r > 100 && calc.r < 160, "r={}", calc.r);
481 }
482
483 #[test]
484 fn test_rgba_calc_zero_height() {
485 let c1 = CoordType {
487 x: 0.0,
488 y: 50.0,
489 color: Rgba8::new(100, 100, 100, 255),
490 };
491 let c2 = CoordType {
492 x: 100.0,
493 y: 50.0,
494 color: Rgba8::new(200, 200, 200, 255),
495 };
496
497 let mut calc = RgbaCalc::new();
498 calc.init(&c1, &c2);
499 calc.calc(50.0);
501 }
502
503 #[test]
504 fn test_dda_sub_positive() {
505 let mut dda = DdaLineInterpolator::<14, 0>::new(0, 255, 100);
506 let y_before = dda.y();
507 dda_sub(&mut dda, 10);
508 assert!(dda.y() <= y_before);
510 }
511
512 #[test]
513 fn test_dda_sub_negative() {
514 let mut dda = DdaLineInterpolator::<14, 0>::new(0, 255, 100);
515 let y_before = dda.y();
516 dda_sub(&mut dda, -10);
517 assert!(dda.y() >= y_before);
519 }
520}