gardesk/garcalc / 5b07250

Browse files

extend 3D engine with render cycling, camera presets, transparency, and new surface types

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
5b07250edbe9755c0457bfa1a2ab755c207d1481
Parents
3cd3071
Tree
060be6f

1 changed file

StatusFile+-
M garcalc-graph/src/plot3d.rs 464 79
garcalc-graph/src/plot3d.rsmodified
@@ -36,7 +36,38 @@ impl Default for Camera3D {
3636
     }
3737
 }
3838
 
39
+/// Camera preset positions
40
+#[derive(Debug, Clone, Copy)]
41
+pub enum CameraPreset {
42
+    Front,
43
+    Side,
44
+    Top,
45
+    Isometric,
46
+}
47
+
3948
 impl Camera3D {
49
+    /// Apply a camera preset
50
+    pub fn apply_preset(&mut self, preset: CameraPreset) {
51
+        match preset {
52
+            CameraPreset::Front => {
53
+                self.azimuth = 0.0;
54
+                self.elevation = 0.0;
55
+            }
56
+            CameraPreset::Side => {
57
+                self.azimuth = std::f64::consts::FRAC_PI_2;
58
+                self.elevation = 0.0;
59
+            }
60
+            CameraPreset::Top => {
61
+                self.azimuth = 0.0;
62
+                self.elevation = 89.0_f64.to_radians();
63
+            }
64
+            CameraPreset::Isometric => {
65
+                self.azimuth = 45.0_f64.to_radians();
66
+                self.elevation = 30.0_f64.to_radians();
67
+            }
68
+        }
69
+    }
70
+
4071
     /// Rotate the camera by delta angles
4172
     pub fn rotate(&mut self, d_azimuth: f64, d_elevation: f64) {
4273
         self.azimuth += d_azimuth;
@@ -116,6 +147,26 @@ impl Colormap {
116147
             }
117148
         }
118149
     }
150
+
151
+    /// Cycle to the next colormap
152
+    pub fn next(self) -> Self {
153
+        match self {
154
+            Colormap::Viridis => Colormap::Plasma,
155
+            Colormap::Plasma => Colormap::Coolwarm,
156
+            Colormap::Coolwarm => Colormap::Grayscale,
157
+            Colormap::Grayscale => Colormap::Viridis,
158
+        }
159
+    }
160
+
161
+    /// Display name
162
+    pub fn name(&self) -> &'static str {
163
+        match self {
164
+            Colormap::Viridis => "Viridis",
165
+            Colormap::Plasma => "Plasma",
166
+            Colormap::Coolwarm => "Coolwarm",
167
+            Colormap::Grayscale => "Grayscale",
168
+        }
169
+    }
119170
 }
120171
 
121172
 /// A 3D plottable surface
@@ -137,6 +188,30 @@ pub enum Surface3D {
137188
         u_range: (f64, f64),
138189
         v_range: (f64, f64),
139190
     },
191
+    /// Spherical: r = f(theta, phi)
192
+    Spherical {
193
+        expr: Expr,
194
+        theta_var: String,
195
+        phi_var: String,
196
+        theta_range: (f64, f64),
197
+        phi_range: (f64, f64),
198
+    },
199
+    /// Cylindrical: r = f(theta, z)
200
+    Cylindrical {
201
+        expr: Expr,
202
+        theta_var: String,
203
+        z_var: String,
204
+        theta_range: (f64, f64),
205
+        z_range: (f64, f64),
206
+    },
207
+    /// Level surface: f(x,y,z) = c, rendered as z-plane contour slices
208
+    LevelSurface {
209
+        expr: Expr,
210
+        x_var: String,
211
+        y_var: String,
212
+        z_var: String,
213
+        level: f64,
214
+    },
140215
 }
141216
 
142217
 /// Render mode for surfaces
@@ -147,6 +222,26 @@ pub enum RenderMode {
147222
     FilledWithWireframe,
148223
 }
149224
 
225
+impl RenderMode {
226
+    /// Cycle to the next render mode
227
+    pub fn next(self) -> Self {
228
+        match self {
229
+            RenderMode::FilledWithWireframe => RenderMode::Filled,
230
+            RenderMode::Filled => RenderMode::Wireframe,
231
+            RenderMode::Wireframe => RenderMode::FilledWithWireframe,
232
+        }
233
+    }
234
+
235
+    /// Display name
236
+    pub fn name(&self) -> &'static str {
237
+        match self {
238
+            RenderMode::Wireframe => "Wireframe",
239
+            RenderMode::Filled => "Filled",
240
+            RenderMode::FilledWithWireframe => "FilledWire",
241
+        }
242
+    }
243
+}
244
+
150245
 /// Configuration for 3D plot appearance
151246
 #[derive(Debug, Clone)]
152247
 pub struct Plot3DConfig {
@@ -158,6 +253,10 @@ pub struct Plot3DConfig {
158253
     pub grid_lines: usize,
159254
     pub show_axes: bool,
160255
     pub show_labels: bool,
256
+    /// Surface transparency (0.0 = fully transparent, 1.0 = fully opaque)
257
+    pub surface_alpha: f64,
258
+    /// Whether to show coordinate plane grids
259
+    pub show_coord_planes: bool,
161260
 }
162261
 
163262
 impl Default for Plot3DConfig {
@@ -186,6 +285,8 @@ impl Default for Plot3DConfig {
186285
             grid_lines: 40,
187286
             show_axes: true,
188287
             show_labels: true,
288
+            surface_alpha: 0.85,
289
+            show_coord_planes: false,
189290
         }
190291
     }
191292
 }
@@ -243,6 +344,44 @@ impl Graph3D {
243344
         });
244345
     }
245346
 
347
+    /// Cycle the render mode
348
+    pub fn cycle_render_mode(&mut self) {
349
+        self.config.render_mode = self.config.render_mode.next();
350
+    }
351
+
352
+    /// Add a spherical surface r = f(theta, phi)
353
+    pub fn add_spherical(&mut self, expr: Expr) {
354
+        self.surfaces.push(Surface3D::Spherical {
355
+            expr,
356
+            theta_var: "theta".to_string(),
357
+            phi_var: "phi".to_string(),
358
+            theta_range: (0.0, std::f64::consts::TAU),
359
+            phi_range: (0.0, std::f64::consts::PI),
360
+        });
361
+    }
362
+
363
+    /// Add a cylindrical surface r = f(theta, z)
364
+    pub fn add_cylindrical(&mut self, expr: Expr) {
365
+        self.surfaces.push(Surface3D::Cylindrical {
366
+            expr,
367
+            theta_var: "theta".to_string(),
368
+            z_var: "z".to_string(),
369
+            theta_range: (0.0, std::f64::consts::TAU),
370
+            z_range: (-5.0, 5.0),
371
+        });
372
+    }
373
+
374
+    /// Add a level surface f(x,y,z) = c
375
+    pub fn add_level_surface(&mut self, expr: Expr, level: f64) {
376
+        self.surfaces.push(Surface3D::LevelSurface {
377
+            expr,
378
+            x_var: "x".to_string(),
379
+            y_var: "y".to_string(),
380
+            z_var: "z".to_string(),
381
+            level,
382
+        });
383
+    }
384
+
246385
     /// Clear all surfaces
247386
     pub fn clear_surfaces(&mut self) {
248387
         self.surfaces.clear();
@@ -327,6 +466,11 @@ impl Graph3D {
327466
             self.draw_axes(ctx, width, height);
328467
         }
329468
 
469
+        // Draw coordinate plane grids
470
+        if self.config.show_coord_planes {
471
+            self.draw_coord_planes(ctx, width, height);
472
+        }
473
+
330474
         // Draw surfaces
331475
         for surface in &self.surfaces {
332476
             self.draw_surface(ctx, surface, width, height);
@@ -398,6 +542,37 @@ impl Graph3D {
398542
                     ctx, x_expr, y_expr, z_expr, u_var, v_var, *u_range, *v_range, width, height,
399543
                 );
400544
             }
545
+            Surface3D::Spherical {
546
+                expr,
547
+                theta_var,
548
+                phi_var,
549
+                theta_range,
550
+                phi_range,
551
+            } => {
552
+                self.draw_spherical_surface(
553
+                    ctx, expr, theta_var, phi_var, *theta_range, *phi_range, width, height,
554
+                );
555
+            }
556
+            Surface3D::Cylindrical {
557
+                expr,
558
+                theta_var,
559
+                z_var,
560
+                theta_range,
561
+                z_range,
562
+            } => {
563
+                self.draw_cylindrical_surface(
564
+                    ctx, expr, theta_var, z_var, *theta_range, *z_range, width, height,
565
+                );
566
+            }
567
+            Surface3D::LevelSurface {
568
+                expr,
569
+                x_var,
570
+                y_var,
571
+                z_var,
572
+                level,
573
+            } => {
574
+                self.draw_level_surface(ctx, expr, x_var, y_var, z_var, *level, width, height);
575
+            }
401576
         }
402577
     }
403578
 
@@ -457,77 +632,8 @@ impl Graph3D {
457632
             z_max = z_min + 1.0;
458633
         }
459634
 
460
-        // Collect quads for depth sorting
461
-        let mut quads: Vec<Quad> = Vec::new();
462
-
463
-        for i in 0..n {
464
-            for j in 0..n {
465
-                if let (Some(p00), Some(p10), Some(p11), Some(p01)) = (
466
-                    points[i][j],
467
-                    points[i + 1][j],
468
-                    points[i + 1][j + 1],
469
-                    points[i][j + 1],
470
-                ) {
471
-                    let avg_z = (p00.2 + p10.2 + p11.2 + p01.2) / 4.0;
472
-                    let t = (avg_z - z_min) / (z_max - z_min);
473
-                    let color = self.config.colormap.color(t);
474
-
475
-                    // Calculate depth for sorting (distance from camera)
476
-                    let cam_pos = self.camera.position();
477
-                    let cx = (p00.0 + p10.0 + p11.0 + p01.0) / 4.0;
478
-                    let cy = (p00.1 + p10.1 + p11.1 + p01.1) / 4.0;
479
-                    let cz = (p00.2 + p10.2 + p11.2 + p01.2) / 4.0;
480
-                    let depth = (cx - cam_pos.0).powi(2)
481
-                        + (cy - cam_pos.1).powi(2)
482
-                        + (cz - cam_pos.2).powi(2);
483
-
484
-                    quads.push(Quad {
485
-                        corners: [p00, p10, p11, p01],
486
-                        color,
487
-                        depth,
488
-                    });
489
-                }
490
-            }
491
-        }
492
-
493
-        // Sort by depth (painter's algorithm - far to near)
494
-        quads.sort_by(|a, b| {
495
-            b.depth
496
-                .partial_cmp(&a.depth)
497
-                .unwrap_or(std::cmp::Ordering::Equal)
498
-        });
499
-
500
-        // Draw quads
501
-        for quad in &quads {
502
-            let corners: Vec<(f64, f64)> = quad
503
-                .corners
504
-                .iter()
505
-                .map(|p| self.project(p.0, p.1, p.2, width, height))
506
-                .collect();
507
-
508
-            if self.config.render_mode != RenderMode::Wireframe {
509
-                // Fill
510
-                set_color(ctx, quad.color);
511
-                ctx.move_to(corners[0].0, corners[0].1);
512
-                for c in &corners[1..] {
513
-                    ctx.line_to(c.0, c.1);
514
-                }
515
-                ctx.close_path();
516
-                let _ = ctx.fill();
517
-            }
518
-
519
-            if self.config.render_mode != RenderMode::Filled {
520
-                // Wireframe
521
-                set_color(ctx, self.config.wireframe_color);
522
-                ctx.set_line_width(0.5);
523
-                ctx.move_to(corners[0].0, corners[0].1);
524
-                for c in &corners[1..] {
525
-                    ctx.line_to(c.0, c.1);
526
-                }
527
-                ctx.close_path();
528
-                let _ = ctx.stroke();
529
-            }
530
-        }
635
+        let quads = self.build_quads(&points, n, z_min, z_max);
636
+        self.draw_quads(ctx, &quads, width, height);
531637
     }
532638
 
533639
     fn draw_parametric_surface(
@@ -593,9 +699,281 @@ impl Graph3D {
593699
             z_max = z_min + 1.0;
594700
         }
595701
 
596
-        // Collect and sort quads
597
-        let mut quads: Vec<Quad> = Vec::new();
702
+        let quads = self.build_quads(&points, n, z_min, z_max);
703
+        self.draw_quads(ctx, &quads, width, height);
704
+    }
705
+
706
+    fn draw_spherical_surface(
707
+        &self,
708
+        ctx: &Context,
709
+        expr: &Expr,
710
+        theta_var: &str,
711
+        phi_var: &str,
712
+        theta_range: (f64, f64),
713
+        phi_range: (f64, f64),
714
+        width: u32,
715
+        height: u32,
716
+    ) {
717
+        let mut evaluator = Evaluator::new();
718
+        let n = self.config.grid_lines;
719
+
720
+        let dtheta = (theta_range.1 - theta_range.0) / n as f64;
721
+        let dphi = (phi_range.1 - phi_range.0) / n as f64;
722
+
723
+        let mut points: Vec<Vec<Option<(f64, f64, f64)>>> = Vec::with_capacity(n + 1);
724
+        let mut z_min = f64::INFINITY;
725
+        let mut z_max = f64::NEG_INFINITY;
726
+
727
+        for i in 0..=n {
728
+            let mut row = Vec::with_capacity(n + 1);
729
+            let theta = theta_range.0 + i as f64 * dtheta;
730
+
731
+            for j in 0..=n {
732
+                let phi = phi_range.0 + j as f64 * dphi;
733
+
734
+                evaluator.set_var(theta_var, Expr::Float(theta));
735
+                evaluator.set_var(phi_var, Expr::Float(phi));
736
+
737
+                if let Ok(result) = evaluator.eval(expr) {
738
+                    if let Ok(r) = expr_to_f64(&result) {
739
+                        if r.is_finite() {
740
+                            let x = r * phi.sin() * theta.cos();
741
+                            let y = r * phi.sin() * theta.sin();
742
+                            let z = r * phi.cos();
743
+                            z_min = z_min.min(z);
744
+                            z_max = z_max.max(z);
745
+                            row.push(Some((x, y, z)));
746
+                        } else {
747
+                            row.push(None);
748
+                        }
749
+                    } else {
750
+                        row.push(None);
751
+                    }
752
+                } else {
753
+                    row.push(None);
754
+                }
755
+            }
756
+            points.push(row);
757
+        }
758
+
759
+        if (z_max - z_min).abs() < 1e-10 {
760
+            z_max = z_min + 1.0;
761
+        }
762
+
763
+        let quads = self.build_quads(&points, n, z_min, z_max);
764
+        self.draw_quads(ctx, &quads, width, height);
765
+    }
766
+
767
+    fn draw_cylindrical_surface(
768
+        &self,
769
+        ctx: &Context,
770
+        expr: &Expr,
771
+        theta_var: &str,
772
+        z_var: &str,
773
+        theta_range: (f64, f64),
774
+        z_range: (f64, f64),
775
+        width: u32,
776
+        height: u32,
777
+    ) {
778
+        let mut evaluator = Evaluator::new();
779
+        let n = self.config.grid_lines;
780
+
781
+        let dtheta = (theta_range.1 - theta_range.0) / n as f64;
782
+        let dz = (z_range.1 - z_range.0) / n as f64;
783
+
784
+        let mut points: Vec<Vec<Option<(f64, f64, f64)>>> = Vec::with_capacity(n + 1);
785
+        let mut z_min_val = f64::INFINITY;
786
+        let mut z_max_val = f64::NEG_INFINITY;
787
+
788
+        for i in 0..=n {
789
+            let mut row = Vec::with_capacity(n + 1);
790
+            let theta = theta_range.0 + i as f64 * dtheta;
791
+
792
+            for j in 0..=n {
793
+                let z = z_range.0 + j as f64 * dz;
794
+
795
+                evaluator.set_var(theta_var, Expr::Float(theta));
796
+                evaluator.set_var(z_var, Expr::Float(z));
797
+
798
+                if let Ok(result) = evaluator.eval(expr) {
799
+                    if let Ok(r) = expr_to_f64(&result) {
800
+                        if r.is_finite() {
801
+                            let x = r * theta.cos();
802
+                            let y = r * theta.sin();
803
+                            z_min_val = z_min_val.min(z);
804
+                            z_max_val = z_max_val.max(z);
805
+                            row.push(Some((x, y, z)));
806
+                        } else {
807
+                            row.push(None);
808
+                        }
809
+                    } else {
810
+                        row.push(None);
811
+                    }
812
+                } else {
813
+                    row.push(None);
814
+                }
815
+            }
816
+            points.push(row);
817
+        }
818
+
819
+        if (z_max_val - z_min_val).abs() < 1e-10 {
820
+            z_max_val = z_min_val + 1.0;
821
+        }
822
+
823
+        let quads = self.build_quads(&points, n, z_min_val, z_max_val);
824
+        self.draw_quads(ctx, &quads, width, height);
825
+    }
826
+
827
+    fn draw_level_surface(
828
+        &self,
829
+        ctx: &Context,
830
+        expr: &Expr,
831
+        x_var: &str,
832
+        y_var: &str,
833
+        z_var: &str,
834
+        level: f64,
835
+        width: u32,
836
+        height: u32,
837
+    ) {
838
+        let mut evaluator = Evaluator::new();
839
+        let num_slices: usize = 30;
840
+        let grid_size: usize = 60;
841
+
842
+        let z_step = (self.viewport.z_max - self.viewport.z_min) / num_slices as f64;
843
+        let dx = (self.viewport.x_max - self.viewport.x_min) / grid_size as f64;
844
+        let dy = (self.viewport.y_max - self.viewport.y_min) / grid_size as f64;
845
+
846
+        ctx.set_line_width(1.5);
847
+
848
+        for slice in 0..num_slices {
849
+            let z = self.viewport.z_min + (slice as f64 + 0.5) * z_step;
850
+            let t = (z - self.viewport.z_min) / (self.viewport.z_max - self.viewport.z_min);
851
+            let color = self.config.colormap.color(t.clamp(0.0, 1.0));
852
+            ctx.set_source_rgba(
853
+                color.r as f64 / 255.0,
854
+                color.g as f64 / 255.0,
855
+                color.b as f64 / 255.0,
856
+                self.config.surface_alpha,
857
+            );
858
+
859
+            evaluator.set_var(z_var, Expr::Float(z));
860
+
861
+            // Evaluate f(x,y,z) - c on a 2D grid at this z
862
+            let mut values = vec![vec![0.0f64; grid_size + 1]; grid_size + 1];
863
+            for i in 0..=grid_size {
864
+                let x = self.viewport.x_min + i as f64 * dx;
865
+                evaluator.set_var(x_var, Expr::Float(x));
866
+                for j in 0..=grid_size {
867
+                    let y = self.viewport.y_min + j as f64 * dy;
868
+                    evaluator.set_var(y_var, Expr::Float(y));
869
+                    if let Ok(result) = evaluator.eval(expr) {
870
+                        if let Ok(v) = expr_to_f64(&result) {
871
+                            values[i][j] = if v.is_finite() { v - level } else { f64::NAN };
872
+                        } else {
873
+                            values[i][j] = f64::NAN;
874
+                        }
875
+                    } else {
876
+                        values[i][j] = f64::NAN;
877
+                    }
878
+                }
879
+            }
880
+
881
+            // Marching squares on this z-plane
882
+            for i in 0..grid_size {
883
+                for j in 0..grid_size {
884
+                    let x0 = self.viewport.x_min + i as f64 * dx;
885
+                    let y0 = self.viewport.y_min + j as f64 * dy;
886
+                    let x1 = x0 + dx;
887
+                    let y1 = y0 + dy;
888
+
889
+                    let v00 = values[i][j];
890
+                    let v10 = values[i + 1][j];
891
+                    let v01 = values[i][j + 1];
892
+                    let v11 = values[i + 1][j + 1];
893
+
894
+                    if v00.is_nan() || v10.is_nan() || v01.is_nan() || v11.is_nan() {
895
+                        continue;
896
+                    }
897
+
898
+                    let s00 = v00 >= 0.0;
899
+                    let s10 = v10 >= 0.0;
900
+                    let s01 = v01 >= 0.0;
901
+                    let s11 = v11 >= 0.0;
902
+
903
+                    let case = (s00 as u8) | ((s10 as u8) << 1) | ((s01 as u8) << 2) | ((s11 as u8) << 3);
904
+
905
+                    let interp = |va: f64, vb: f64| -> f64 {
906
+                        if (va - vb).abs() < 1e-15 { 0.5 } else { va / (va - vb) }
907
+                    };
908
+
909
+                    let e_bottom = || { let t = interp(v00, v10); (x0 + t * dx, y0) };
910
+                    let e_top = || { let t = interp(v01, v11); (x0 + t * dx, y1) };
911
+                    let e_left = || { let t = interp(v00, v01); (x0, y0 + t * dy) };
912
+                    let e_right = || { let t = interp(v10, v11); (x1, y0 + t * dy) };
913
+
914
+                    let draw_line = |p1: (f64, f64), p2: (f64, f64)| {
915
+                        let (sx1, sy1) = self.project(p1.0, p1.1, z, width, height);
916
+                        let (sx2, sy2) = self.project(p2.0, p2.1, z, width, height);
917
+                        ctx.move_to(sx1, sy1);
918
+                        ctx.line_to(sx2, sy2);
919
+                    };
920
+
921
+                    match case {
922
+                        0 | 15 => {}
923
+                        1 | 14 => draw_line(e_bottom(), e_left()),
924
+                        2 | 13 => draw_line(e_bottom(), e_right()),
925
+                        3 | 12 => draw_line(e_left(), e_right()),
926
+                        4 | 11 => draw_line(e_left(), e_top()),
927
+                        5 | 10 => {
928
+                            draw_line(e_bottom(), e_left());
929
+                            draw_line(e_top(), e_right());
930
+                        }
931
+                        6 | 9 => draw_line(e_bottom(), e_top()),
932
+                        7 | 8 => draw_line(e_top(), e_right()),
933
+                        _ => {}
934
+                    }
935
+                }
936
+            }
937
+            let _ = ctx.stroke();
938
+        }
939
+    }
598940
 
941
+    fn draw_coord_planes(&self, ctx: &Context, width: u32, height: u32) {
942
+        ctx.set_line_width(0.5);
943
+        ctx.set_source_rgba(0.5, 0.5, 0.6, 0.2);
944
+
945
+        let step = 1.0;
946
+        let range = 5.0;
947
+
948
+        // XY plane (z=0)
949
+        let mut x = -range;
950
+        while x <= range {
951
+            let (sx0, sy0) = self.project(x, -range, 0.0, width, height);
952
+            let (sx1, sy1) = self.project(x, range, 0.0, width, height);
953
+            ctx.move_to(sx0, sy0);
954
+            ctx.line_to(sx1, sy1);
955
+            x += step;
956
+        }
957
+        let mut y = -range;
958
+        while y <= range {
959
+            let (sx0, sy0) = self.project(-range, y, 0.0, width, height);
960
+            let (sx1, sy1) = self.project(range, y, 0.0, width, height);
961
+            ctx.move_to(sx0, sy0);
962
+            ctx.line_to(sx1, sy1);
963
+            y += step;
964
+        }
965
+        let _ = ctx.stroke();
966
+    }
967
+
968
+    /// Build quads from a grid of sampled points (shared by explicit, parametric, spherical, cylindrical)
969
+    fn build_quads(
970
+        &self,
971
+        points: &[Vec<Option<(f64, f64, f64)>>],
972
+        n: usize,
973
+        z_min: f64,
974
+        z_max: f64,
975
+    ) -> Vec<Quad> {
976
+        let mut quads = Vec::new();
599977
         for i in 0..n {
600978
             for j in 0..n {
601979
                 if let (Some(p00), Some(p10), Some(p11), Some(p01)) = (
@@ -624,14 +1002,16 @@ impl Graph3D {
6241002
                 }
6251003
             }
6261004
         }
627
-
6281005
         quads.sort_by(|a, b| {
629
-            b.depth
630
-                .partial_cmp(&a.depth)
631
-                .unwrap_or(std::cmp::Ordering::Equal)
1006
+            b.depth.partial_cmp(&a.depth).unwrap_or(std::cmp::Ordering::Equal)
6321007
         });
1008
+        quads
1009
+    }
6331010
 
634
-        for quad in &quads {
1011
+    /// Draw sorted quads with the current render mode and alpha
1012
+    fn draw_quads(&self, ctx: &Context, quads: &[Quad], width: u32, height: u32) {
1013
+        let alpha = self.config.surface_alpha;
1014
+        for quad in quads {
6351015
             let corners: Vec<(f64, f64)> = quad
6361016
                 .corners
6371017
                 .iter()
@@ -639,7 +1019,12 @@ impl Graph3D {
6391019
                 .collect();
6401020
 
6411021
             if self.config.render_mode != RenderMode::Wireframe {
642
-                set_color(ctx, quad.color);
1022
+                ctx.set_source_rgba(
1023
+                    quad.color.r as f64 / 255.0,
1024
+                    quad.color.g as f64 / 255.0,
1025
+                    quad.color.b as f64 / 255.0,
1026
+                    alpha,
1027
+                );
6431028
                 ctx.move_to(corners[0].0, corners[0].1);
6441029
                 for c in &corners[1..] {
6451030
                     ctx.line_to(c.0, c.1);