1use std::{
7 hash::{Hash, Hasher},
8 rc::Rc,
9};
10
11use microcad_core::{Geometry2D, Geometry3D, Mat3, Mat4, RenderResolution};
12
13use crate::{model::*, render::*};
14
15pub type Geometry2DOutput = Rc<WithBounds2D<Geometry2D>>;
17
18pub type Geometry3DOutput = Rc<WithBounds3D<Geometry3D>>;
20
21#[derive(Debug, Clone)]
23pub enum GeometryOutput {
24 Geometry2D(Geometry2DOutput),
26 Geometry3D(Geometry3DOutput),
28}
29
30impl From<Geometry2D> for GeometryOutput {
31 fn from(geo: Geometry2D) -> Self {
32 Self::Geometry2D(Rc::new(geo.into()))
33 }
34}
35
36impl From<Geometry3D> for GeometryOutput {
37 fn from(geo: Geometry3D) -> Self {
38 Self::Geometry3D(Rc::new(geo.into()))
39 }
40}
41
42impl From<Geometry2DOutput> for GeometryOutput {
43 fn from(geo: Geometry2DOutput) -> Self {
44 Self::Geometry2D(geo)
45 }
46}
47
48impl From<Geometry3DOutput> for GeometryOutput {
49 fn from(geo: Geometry3DOutput) -> Self {
50 Self::Geometry3D(geo)
51 }
52}
53
54#[derive(Debug, Clone)]
56pub enum RenderOutput {
57 Geometry2D {
59 local_matrix: Option<Mat3>,
61 world_matrix: Option<Mat3>,
63 resolution: Option<RenderResolution>,
65 geometry: Option<Geometry2DOutput>,
67 hash: HashId,
69 },
70
71 Geometry3D {
73 local_matrix: Option<Mat4>,
75 world_matrix: Option<Mat4>,
77 resolution: Option<RenderResolution>,
79 geometry: Option<Geometry3DOutput>,
81 hash: HashId,
83 },
84}
85
86impl RenderOutput {
87 pub fn new(model: &Model) -> RenderResult<Self> {
89 let output_type = model.deduce_output_type();
90 let mut hasher = rustc_hash::FxHasher::default();
91 model.hash(&mut hasher);
92 let hash = hasher.finish();
93
94 match output_type {
95 OutputType::Geometry2D => {
96 let local_matrix = model
97 .borrow()
98 .element
99 .get_affine_transform()?
100 .map(|affine_transform| affine_transform.mat2d());
101
102 Ok(RenderOutput::Geometry2D {
103 local_matrix,
104 world_matrix: None,
105 resolution: None,
106 geometry: None,
107 hash,
108 })
109 }
110
111 OutputType::Geometry3D => {
112 let local_matrix = model
113 .borrow()
114 .element
115 .get_affine_transform()?
116 .map(|affine_transform| affine_transform.mat3d());
117
118 Ok(RenderOutput::Geometry3D {
119 local_matrix,
120 world_matrix: None,
121 resolution: None,
122 geometry: None,
123 hash,
124 })
125 }
126 output_type => Err(RenderError::InvalidOutputType(output_type)),
127 }
128 }
129
130 pub fn set_world_matrix(&mut self, m: Mat4) {
132 match self {
133 RenderOutput::Geometry2D { world_matrix, .. } => *world_matrix = Some(mat4_to_mat3(&m)),
134 RenderOutput::Geometry3D { world_matrix, .. } => {
135 *world_matrix = Some(m);
136 }
137 }
138 }
139
140 pub fn set_geometry_2d(&mut self, geo: Geometry2DOutput) {
142 match self {
143 RenderOutput::Geometry2D { geometry, .. } => *geometry = Some(geo),
144 RenderOutput::Geometry3D { .. } => unreachable!(),
145 }
146 }
147
148 pub fn set_geometry_3d(&mut self, geo: Geometry3DOutput) {
150 match self {
151 RenderOutput::Geometry2D { .. } => unreachable!(),
152 RenderOutput::Geometry3D { geometry, .. } => *geometry = Some(geo),
153 }
154 }
155
156 pub fn resolution(&self) -> &Option<RenderResolution> {
158 match self {
159 RenderOutput::Geometry2D { resolution, .. }
160 | RenderOutput::Geometry3D { resolution, .. } => resolution,
161 }
162 }
163
164 pub fn set_resolution(&mut self, render_resolution: RenderResolution) {
166 match self {
167 RenderOutput::Geometry2D { resolution, .. }
168 | RenderOutput::Geometry3D { resolution, .. } => *resolution = Some(render_resolution),
169 }
170 }
171
172 pub fn local_matrix(&self) -> Option<Mat4> {
174 match self {
175 RenderOutput::Geometry2D { local_matrix, .. } => {
176 local_matrix.as_ref().map(mat3_to_mat4)
177 }
178 RenderOutput::Geometry3D { local_matrix, .. } => *local_matrix,
179 }
180 }
181
182 pub fn world_matrix(&self) -> Mat4 {
184 match self {
185 RenderOutput::Geometry2D { world_matrix, .. } => {
186 mat3_to_mat4(&world_matrix.expect("World matrix"))
187 }
188 RenderOutput::Geometry3D { world_matrix, .. } => world_matrix.expect("World matrix"),
189 }
190 }
191}
192
193fn mat4_to_mat3(m: &Mat4) -> Mat3 {
194 Mat3::from_cols(m.x.truncate_n(2), m.y.truncate_n(2), m.w.truncate_n(2))
195}
196
197fn mat3_to_mat4(m: &Mat3) -> Mat4 {
198 Mat4::new(
199 m.x.x, m.x.y, 0.0, m.x.z, m.y.x, m.y.y, 0.0, m.y.z, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, )
204}
205
206impl std::fmt::Display for RenderOutput {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 match &self {
209 RenderOutput::Geometry2D {
210 local_matrix,
211 geometry,
212 hash,
213 ..
214 } => {
215 write!(f, "2D ({hash:X}): ")?;
216 if local_matrix.is_none() && geometry.is_none() {
217 write!(f, "(nothing to render)")?;
218 }
219 if local_matrix.is_some() {
220 write!(f, "transform ")?;
221 }
222 if let Some(geometry) = geometry {
223 write!(
224 f,
225 "{} {}",
226 match &geometry.inner {
227 Geometry2D::Collection(geometries) =>
228 format!("Collection({} items)", geometries.len()),
229 geometry => geometry.name().to_string(),
230 },
231 geometry.bounds
232 )?;
233 }
234 }
235 RenderOutput::Geometry3D {
236 local_matrix,
237 geometry,
238 hash,
239 ..
240 } => {
241 write!(f, "3D ({hash:X}): ")?;
242 match (geometry, local_matrix) {
243 (None, None) => write!(f, "(nothing to render)"),
244 (None, Some(_)) => {
245 write!(f, "transform")
246 }
247 (Some(geometry), None) => write!(f, "{}", geometry.inner.name()),
248 (Some(geometry), Some(_)) => write!(f, "transformed {}", geometry.inner.name()),
249 }?;
250 }
251 }
252
253 if let Some(resolution) = self.resolution() {
254 write!(f, " {resolution}")?
255 }
256 Ok(())
257 }
258}
259
260impl ComputedHash for RenderOutput {
261 fn computed_hash(&self) -> HashId {
262 match self {
263 RenderOutput::Geometry2D { hash, .. } | RenderOutput::Geometry3D { hash, .. } => *hash,
264 }
265 }
266}