microcad_lang/render/
mod.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Model methods and trait implementations for rendering.
5
6mod cache;
7mod context;
8mod output;
9
10use std::rc::Rc;
11
12pub use cache::*;
13pub use context::*;
14pub use output::*;
15
16use cgmath::SquareMatrix;
17use microcad_core::*;
18use thiserror::Error;
19
20use crate::{
21    builtin::{BuiltinWorkbenchKind, BuiltinWorkpiece, BuiltinWorkpieceOutput},
22    model::*,
23    tree_display::FormatTree,
24};
25
26/// An error that occurred during rendering.
27#[derive(Debug, Error)]
28pub enum RenderError {
29    /// Invalid output type.
30    #[error("Invalid output type: {0}")]
31    InvalidOutputType(OutputType),
32
33    /// Nothing to render.
34    #[error("Nothing to render")]
35    NothingToRender,
36}
37
38/// A result from rendering a model.
39pub type RenderResult<T> = Result<T, RenderError>;
40
41/// The render trait.
42pub trait Render<T> {
43    /// Render method.
44    fn render(&self, context: &mut RenderContext) -> RenderResult<T>;
45}
46
47impl Element {
48    /// Fetch the local matrix
49    pub fn get_affine_transform(&self) -> RenderResult<Option<AffineTransform>> {
50        match &self {
51            Element::BuiltinWorkpiece(builtin_workpiece) => match builtin_workpiece.kind {
52                BuiltinWorkbenchKind::Transform => match builtin_workpiece.call()? {
53                    BuiltinWorkpieceOutput::Transform(affine_transform) => {
54                        Ok(Some(affine_transform))
55                    }
56                    _ => unreachable!(),
57                },
58                _ => Ok(None),
59            },
60            _ => Ok(None),
61        }
62    }
63}
64
65impl ModelInner {
66    /// Get render resolution.
67    pub fn resolution(&self) -> RenderResolution {
68        let output = self.output.as_ref().expect("Some render output.");
69
70        match output {
71            RenderOutput::Geometry2D { resolution, .. }
72            | RenderOutput::Geometry3D { resolution, .. } => {
73                resolution.as_ref().expect("Some resolution.").clone()
74            }
75        }
76    }
77}
78
79impl Model {
80    /// Pre render the model.
81    ///
82    /// Rendering the model means that all geometry is calculated and stored
83    /// in the in the render cache.
84    pub fn prerender(&self, resolution: RenderResolution) -> RenderResult<()> {
85        pub fn create_render_output(model: &Model) -> RenderResult<()> {
86            let output = RenderOutput::new(model)?;
87            {
88                let mut model_ = model.borrow_mut();
89                model_.output = Some(output);
90            };
91
92            model
93                .borrow()
94                .children
95                .iter()
96                .try_for_each(create_render_output)
97        }
98
99        pub fn set_world_matrix(model: &Model, matrix: Mat4) -> RenderResult<()> {
100            let world_matrix = {
101                let mut model_ = model.borrow_mut();
102                let output = model_.output.as_mut().expect("Output");
103                let world_matrix = matrix * output.local_matrix().unwrap_or(Mat4::identity());
104                output.set_world_matrix(world_matrix);
105                world_matrix
106            };
107
108            model
109                .borrow()
110                .children
111                .iter()
112                .try_for_each(|model| set_world_matrix(model, world_matrix))
113        }
114
115        /// Set the resolution for this model.
116        pub fn set_resolution(model: &Model, resolution: RenderResolution) {
117            let new_resolution = {
118                let mut model_ = model.borrow_mut();
119                let output = model_.output.as_mut().expect("Output");
120
121                let resolution = resolution * output.local_matrix().unwrap_or(Mat4::identity());
122                output.set_resolution(resolution.clone());
123                resolution
124            };
125
126            model.borrow().children.iter().for_each(|model| {
127                set_resolution(model, new_resolution.clone());
128            });
129        }
130
131        // Create specific render output with local matrix.
132        create_render_output(self)?;
133
134        // Calculate the world matrix.
135        set_world_matrix(self, Mat4::identity())?;
136
137        // Calculate the resolution for the model.
138        set_resolution(self, resolution);
139
140        log::trace!("Finished prerender:\n{}", FormatTree(self));
141
142        Ok(())
143    }
144}
145
146impl FetchBounds2D for Model {
147    fn fetch_bounds_2d(&self) -> Bounds2D {
148        let self_ = self.borrow();
149        match self_.output() {
150            RenderOutput::Geometry2D { geometry, .. } => match geometry {
151                Some(geometry) => geometry.fetch_bounds_2d(),
152                None => Bounds2D::default(),
153            },
154            RenderOutput::Geometry3D { .. } => Bounds2D::default(),
155        }
156    }
157}
158
159/// This implementation renders a [`Geometry2D`] out of a [`Model`].
160///
161/// Notes:
162/// * The impl attaches the output geometry to the model's render output.
163/// * It is assumed the model has been pre-rendered.
164impl Render<Geometry2DOutput> for Model {
165    fn render(&self, context: &mut RenderContext) -> RenderResult<Geometry2DOutput> {
166        context.with_model(self.clone(), |context| {
167            let model = context.model();
168            let geometry = {
169                let model_ = model.borrow();
170                let output = model.deduce_output_type();
171                match output {
172                    OutputType::Geometry2D => {
173                        match model_.element() {
174                            // A group geometry will render the child geometry
175                            Element::BuiltinWorkpiece(builtin_workpiece) => {
176                                builtin_workpiece.render(context)
177                            }
178                            _ => model_.children.render(context),
179                        }
180                    }
181                    _ => Ok(None),
182                }
183            }?;
184
185            self.borrow_mut()
186                .output_mut()
187                .set_geometry_2d(geometry.clone());
188            Ok(geometry)
189        })
190    }
191}
192
193/// This implementation renders a [`Geometry3D`] out of a [`Model`].
194///
195/// Notes:
196/// * The impl attaches the output geometry to the model's render output.
197/// * It is assumed the model has been pre-rendered.
198impl Render<Geometry3DOutput> for Model {
199    fn render(&self, context: &mut RenderContext) -> RenderResult<Geometry3DOutput> {
200        context.with_model(self.clone(), |context| {
201            let model = context.model();
202            let geometry = {
203                let model_ = model.borrow();
204                let output = model.deduce_output_type();
205                match output {
206                    OutputType::Geometry3D => {
207                        match model_.element() {
208                            // A group geometry will render the child geometry
209                            Element::BuiltinWorkpiece(builtin_workpiece) => {
210                                builtin_workpiece.render(context)
211                            }
212                            _ => model_.children.render(context),
213                        }
214                    }
215                    _ => Ok(None),
216                }
217            }?;
218
219            self.borrow_mut()
220                .output_mut()
221                .set_geometry_3d(geometry.clone());
222            Ok(geometry)
223        })
224    }
225}
226
227impl Render<Model> for Model {
228    fn render(&self, context: &mut RenderContext) -> RenderResult<Model> {
229        match self.deduce_output_type() {
230            OutputType::Geometry2D => {
231                let _: Geometry2DOutput = self.render(context)?;
232            }
233            OutputType::Geometry3D => {
234                let _: Geometry3DOutput = self.render(context)?;
235            }
236            output_type => {
237                log::warn!("Nothing to render: {output_type}");
238            }
239        }
240        log::trace!("Finished render:\n{}", FormatTree(self));
241
242        Ok(self.clone())
243    }
244}
245
246impl Render<Geometries2D> for Models {
247    fn render(&self, context: &mut RenderContext) -> RenderResult<Geometries2D> {
248        let mut geometries = Vec::new();
249        for model in self.iter() {
250            if let Some(geo) = model.render(context)? {
251                geometries.push(geo);
252            }
253        }
254
255        Ok(geometries.into_iter().collect())
256    }
257}
258
259impl Render<Geometry2DOutput> for Models {
260    fn render(&self, context: &mut RenderContext) -> RenderResult<Geometry2DOutput> {
261        Ok(match self.len() {
262            0 => None,
263            1 => self.first().expect("One item").render(context)?,
264            _ => Some(Rc::new(Geometry2D::Collection(self.render(context)?))),
265        })
266    }
267}
268
269impl Render<Geometries3D> for Models {
270    fn render(&self, context: &mut RenderContext) -> RenderResult<Geometries3D> {
271        let mut geometries = Vec::new();
272        for model in self.iter() {
273            if let Some(geo) = model.render(context)? {
274                geometries.push(geo);
275            }
276        }
277
278        Ok(geometries.into_iter().collect())
279    }
280}
281
282impl Render<Geometry3DOutput> for Models {
283    fn render(&self, context: &mut RenderContext) -> RenderResult<Geometry3DOutput> {
284        Ok(match self.len() {
285            0 => None,
286            1 => self.first().expect("One item").render(context)?,
287            _ => Some(Rc::new(Geometry3D::Collection(self.render(context)?))),
288        })
289    }
290}
291
292impl Render<Geometry2DOutput> for BuiltinWorkpiece {
293    fn render(&self, context: &mut RenderContext) -> RenderResult<Geometry2DOutput> {
294        Ok(match self.call()? {
295            BuiltinWorkpieceOutput::Primitive2D(geo2d) => Some(Rc::new(geo2d)),
296            BuiltinWorkpieceOutput::Transform(transform) => {
297                let model = context.model();
298                let model_ = model.borrow();
299                let geometry: Geometry2DOutput = model_.children.render(context)?;
300
301                geometry.map(|geometry| {
302                    Rc::new(
303                        geometry.transformed_2d(&context.current_resolution(), &transform.mat2d()),
304                    )
305                })
306            }
307            BuiltinWorkpieceOutput::Operation(operation) => operation.process_2d(context)?,
308            _ => None,
309        })
310    }
311}
312
313impl Render<Geometry3DOutput> for BuiltinWorkpiece {
314    fn render(&self, context: &mut RenderContext) -> RenderResult<Geometry3DOutput> {
315        Ok(match self.call()? {
316            BuiltinWorkpieceOutput::Primitive3D(geo3d) => Some(Rc::new(geo3d)),
317            BuiltinWorkpieceOutput::Transform(transform) => {
318                let model = context.model();
319                let model_ = model.borrow();
320                let geometry: Geometry3DOutput = model_.children.render(context)?;
321
322                geometry.map(|geometry| {
323                    Rc::new(
324                        geometry.transformed_3d(&context.current_resolution(), &transform.mat3d()),
325                    )
326                })
327            }
328            BuiltinWorkpieceOutput::Operation(operation) => operation.process_3d(context)?,
329            _ => None,
330        })
331    }
332}