microcad_lang/render/
output.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Model output types.
5
6use std::rc::Rc;
7
8use microcad_core::{FetchBounds2D, Geometry2D, Geometry3D, Mat3, Mat4, RenderResolution};
9
10use crate::{model::*, render::*};
11
12/// Geometry 2D type alias.
13pub type Geometry2DOutput = Option<Rc<Geometry2D>>;
14
15/// Geometry 3D type alias.
16pub type Geometry3DOutput = Option<Rc<Geometry3D>>;
17
18/// The model output when a model has been processed.
19#[derive(Debug, Clone)]
20pub enum RenderOutput {
21    /// 2D render output.
22    Geometry2D {
23        /// Local transformation matrix.
24        local_matrix: Option<Mat3>,
25        /// World transformation matrix.
26        world_matrix: Option<Mat3>,
27        /// The render resolution, calculated from transformation matrix.
28        resolution: Option<RenderResolution>,
29        /// The output geometry.
30        geometry: Geometry2DOutput,
31    },
32
33    /// 3D render output.
34    Geometry3D {
35        /// Local transformation matrix.
36        local_matrix: Option<Mat4>,
37        /// World transformation matrix.
38        world_matrix: Option<Mat4>,
39        /// The render resolution, calculated from transformation matrix.
40        resolution: Option<RenderResolution>,
41        /// The output geometry.
42        geometry: Geometry3DOutput,
43    },
44}
45
46impl RenderOutput {
47    /// Create new render output for model.
48    pub fn new(model: &Model) -> RenderResult<Self> {
49        let output_type = model.deduce_output_type();
50
51        match output_type {
52            OutputType::Geometry2D => {
53                let local_matrix = model
54                    .borrow()
55                    .element
56                    .get_affine_transform()?
57                    .map(|affine_transform| affine_transform.mat2d());
58
59                Ok(RenderOutput::Geometry2D {
60                    local_matrix,
61                    world_matrix: None,
62                    resolution: None,
63                    geometry: None,
64                })
65            }
66
67            OutputType::Geometry3D => {
68                let local_matrix = model
69                    .borrow()
70                    .element
71                    .get_affine_transform()?
72                    .map(|affine_transform| affine_transform.mat3d());
73
74                Ok(RenderOutput::Geometry3D {
75                    local_matrix,
76                    world_matrix: None,
77                    resolution: None,
78                    geometry: None,
79                })
80            }
81            output_type => Err(RenderError::InvalidOutputType(output_type)),
82        }
83    }
84
85    /// Set the world matrix for render output.
86    pub fn set_world_matrix(&mut self, m: Mat4) {
87        match self {
88            RenderOutput::Geometry2D { world_matrix, .. } => *world_matrix = Some(mat4_to_mat3(&m)),
89            RenderOutput::Geometry3D { world_matrix, .. } => {
90                *world_matrix = Some(m);
91            }
92        }
93    }
94
95    /// Set the 2D geometry as render output.
96    pub fn set_geometry_2d(&mut self, geo: Geometry2DOutput) {
97        match self {
98            RenderOutput::Geometry2D { geometry, .. } => *geometry = geo,
99            RenderOutput::Geometry3D { .. } => unreachable!(),
100        }
101    }
102
103    /// Set the 2D geometry as render output.
104    pub fn set_geometry_3d(&mut self, geo: Geometry3DOutput) {
105        match self {
106            RenderOutput::Geometry2D { .. } => unreachable!(),
107            RenderOutput::Geometry3D { geometry, .. } => *geometry = geo,
108        }
109    }
110
111    /// Get render resolution.
112    pub fn resolution(&self) -> &Option<RenderResolution> {
113        match self {
114            RenderOutput::Geometry2D { resolution, .. }
115            | RenderOutput::Geometry3D { resolution, .. } => resolution,
116        }
117    }
118
119    /// Set render resolution.
120    pub fn set_resolution(&mut self, render_resolution: RenderResolution) {
121        match self {
122            RenderOutput::Geometry2D { resolution, .. }
123            | RenderOutput::Geometry3D { resolution, .. } => *resolution = Some(render_resolution),
124        }
125    }
126
127    /// Local matrix.
128    pub fn local_matrix(&self) -> Option<Mat4> {
129        match self {
130            RenderOutput::Geometry2D { local_matrix, .. } => {
131                local_matrix.as_ref().map(mat3_to_mat4)
132            }
133            RenderOutput::Geometry3D { local_matrix, .. } => *local_matrix,
134        }
135    }
136
137    /// Get world matrix.
138    pub fn world_matrix(&self) -> Mat4 {
139        match self {
140            RenderOutput::Geometry2D { world_matrix, .. } => {
141                mat3_to_mat4(&world_matrix.expect("World matrix"))
142            }
143            RenderOutput::Geometry3D { world_matrix, .. } => world_matrix.expect("World matrix"),
144        }
145    }
146}
147
148fn mat4_to_mat3(m: &Mat4) -> Mat3 {
149    Mat3::from_cols(m.x.truncate_n(2), m.y.truncate_n(2), m.w.truncate_n(2))
150}
151
152fn mat3_to_mat4(m: &Mat3) -> Mat4 {
153    Mat4::new(
154        m.x.x, m.x.y, 0.0, m.x.z, // First column: X basis + X translation
155        m.y.x, m.y.y, 0.0, m.y.z, // Second column: Y basis + Y translation
156        0.0, 0.0, 1.0, 0.0, // Z axis: identity (no change)
157        0.0, 0.0, 0.0, 1.0, // Homogeneous row
158    )
159}
160
161impl std::fmt::Display for RenderOutput {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        match &self {
164            RenderOutput::Geometry2D {
165                local_matrix,
166                geometry,
167                ..
168            } => {
169                write!(f, "2D: ")?;
170                if local_matrix.is_none() && geometry.is_none() {
171                    write!(f, "(nothing to render)")?;
172                }
173                if local_matrix.is_some() {
174                    write!(f, "transform ")?;
175                }
176                if let Some(geometry) = geometry {
177                    write!(
178                        f,
179                        "{} {}",
180                        match geometry.as_ref() {
181                            Geometry2D::Collection(geometries) =>
182                                format!("Collection({} items)", geometries.len()),
183                            geometry => geometry.name().to_string(),
184                        },
185                        geometry.fetch_bounds_2d()
186                    )?;
187                }
188            }
189            RenderOutput::Geometry3D {
190                local_matrix,
191                geometry,
192                ..
193            } => {
194                write!(f, "3D: ")?;
195                match (geometry, local_matrix) {
196                    (None, None) => write!(f, "(nothing to render)"),
197                    (None, Some(_)) => {
198                        write!(f, "transform")
199                    }
200                    (Some(geometry), None) => write!(f, "{}", geometry.name()),
201                    (Some(geometry), Some(_)) => write!(f, "transformed {}", geometry.name()),
202                }?;
203            }
204        }
205
206        if let Some(resolution) = self.resolution() {
207            write!(f, " {resolution}")?
208        }
209        Ok(())
210    }
211}