Skip to main content

fidget_raster/
config.rs

1use crate::{DistancePixel, GeometryBuffer, Image, RenderConfig, TileSizesRef};
2use fidget_core::{
3    eval::Function,
4    render::{CancelToken, ImageSize, ThreadPool, TileSizes, VoxelSize},
5    shape::{Shape, ShapeVars},
6};
7use nalgebra::{Const, Matrix3, Matrix4, OPoint, Point2, Vector2};
8
9/// Settings for 2D rendering
10pub struct ImageRenderConfig<'a> {
11    /// Render size
12    pub image_size: ImageSize,
13
14    /// World-to-model transform
15    pub world_to_model: Matrix3<f32>,
16
17    /// Render the distance values of individual pixels
18    pub pixel_perfect: bool,
19
20    /// Tile sizes to use during evaluation.
21    ///
22    /// You'll likely want to use
23    /// [`RenderHints::tile_sizes_2d`](fidget_core::render::RenderHints::tile_sizes_2d)
24    /// to select this based on evaluator type.
25    pub tile_sizes: TileSizes,
26
27    /// Thread pool to use for rendering
28    ///
29    /// If this is `None`, then rendering is done in a single thread; otherwise,
30    /// the provided pool is used.
31    pub threads: Option<&'a ThreadPool>,
32
33    /// Token to cancel rendering
34    pub cancel: CancelToken,
35}
36
37impl Default for ImageRenderConfig<'_> {
38    fn default() -> Self {
39        Self {
40            image_size: ImageSize::from(512),
41            tile_sizes: TileSizes::new(&[128, 32, 8]).unwrap(),
42            world_to_model: Matrix3::identity(),
43            pixel_perfect: false,
44            threads: Some(&ThreadPool::Global),
45            cancel: CancelToken::new(),
46        }
47    }
48}
49
50impl RenderConfig for ImageRenderConfig<'_> {
51    fn width(&self) -> u32 {
52        self.image_size.width()
53    }
54    fn height(&self) -> u32 {
55        self.image_size.height()
56    }
57    fn threads(&self) -> Option<&ThreadPool> {
58        self.threads
59    }
60    fn tile_sizes(&self) -> TileSizesRef<'_> {
61        let max_size = self.width().max(self.height()) as usize;
62        TileSizesRef::new(&self.tile_sizes, max_size)
63    }
64    fn is_cancelled(&self) -> bool {
65        self.cancel.is_cancelled()
66    }
67}
68
69impl ImageRenderConfig<'_> {
70    /// Render a shape in 2D using this configuration
71    pub fn run<F: Function>(
72        &self,
73        shape: Shape<F>,
74    ) -> Option<Image<DistancePixel>> {
75        self.run_with_vars::<F>(shape, &ShapeVars::new())
76    }
77
78    /// Render a shape in 2D using this configuration and variables
79    pub fn run_with_vars<F: Function>(
80        &self,
81        shape: Shape<F>,
82        vars: &ShapeVars<f32>,
83    ) -> Option<Image<DistancePixel>> {
84        crate::render2d::<F>(shape, vars, self)
85    }
86
87    /// Returns the combined screen-to-model transform matrix
88    pub fn mat(&self) -> Matrix3<f32> {
89        self.world_to_model * self.image_size.screen_to_world()
90    }
91}
92
93/// Settings for 3D rendering
94pub struct VoxelRenderConfig<'a> {
95    /// Render size
96    ///
97    /// The resulting image will have the given width and height; depth sets the
98    /// number of voxels to evaluate within each pixel of the image (stacked
99    /// into a column going into the screen).
100    pub image_size: VoxelSize,
101
102    /// World-to-model transform
103    pub world_to_model: Matrix4<f32>,
104
105    /// Tile sizes to use during evaluation.
106    ///
107    /// You'll likely want to use
108    /// [`RenderHints::tile_sizes_3d`](fidget_core::render::RenderHints::tile_sizes_3d)
109    /// to select this based on evaluator type.
110    pub tile_sizes: TileSizes,
111
112    /// Thread pool to use for rendering
113    ///
114    /// If this is `None`, then rendering is done in a single thread; otherwise,
115    /// the provided pool is used.
116    pub threads: Option<&'a ThreadPool>,
117
118    /// Token to cancel rendering
119    pub cancel: CancelToken,
120}
121
122impl Default for VoxelRenderConfig<'_> {
123    fn default() -> Self {
124        Self {
125            image_size: VoxelSize::from(512),
126            tile_sizes: TileSizes::new(&[128, 64, 32, 16, 8]).unwrap(),
127            world_to_model: Matrix4::identity(),
128            threads: Some(&ThreadPool::Global),
129            cancel: CancelToken::new(),
130        }
131    }
132}
133
134impl RenderConfig for VoxelRenderConfig<'_> {
135    fn width(&self) -> u32 {
136        self.image_size.width()
137    }
138    fn height(&self) -> u32 {
139        self.image_size.height()
140    }
141    fn threads(&self) -> Option<&ThreadPool> {
142        self.threads
143    }
144    fn tile_sizes(&self) -> TileSizesRef<'_> {
145        let max_size = self.width().max(self.height()) as usize;
146        TileSizesRef::new(&self.tile_sizes, max_size)
147    }
148    fn is_cancelled(&self) -> bool {
149        self.cancel.is_cancelled()
150    }
151}
152
153impl VoxelRenderConfig<'_> {
154    /// Render a shape in 3D using this configuration
155    ///
156    /// Returns a [`GeometryBuffer`] of pixel data, or `None` if rendering was
157    /// cancelled.
158    ///
159    /// In the resulting image, saturated pixels (i.e. pixels in the image which
160    /// are fully occupied up to the camera) are represented with `depth =
161    /// self.image_size.depth()` and a normal of `[0, 0, 1]`.
162    pub fn run<F: Function>(&self, shape: Shape<F>) -> Option<GeometryBuffer> {
163        self.run_with_vars::<F>(shape, &ShapeVars::new())
164    }
165
166    /// Render a shape in 3D using this configuration and variables
167    pub fn run_with_vars<F: Function>(
168        &self,
169        shape: Shape<F>,
170        vars: &ShapeVars<f32>,
171    ) -> Option<GeometryBuffer> {
172        crate::render3d::<F>(shape, vars, self)
173    }
174
175    /// Returns the combined screen-to-model transform matrix
176    pub fn mat(&self) -> Matrix4<f32> {
177        self.world_to_model * self.image_size.screen_to_world()
178    }
179}
180
181////////////////////////////////////////////////////////////////////////////////
182
183#[derive(Copy, Clone, Debug)]
184pub(crate) struct Tile<const N: usize> {
185    /// Corner of this tile, in global screen (pixel) coordinates
186    pub corner: OPoint<usize, Const<N>>,
187}
188
189impl<const N: usize> Tile<N> {
190    /// Build a new tile from its global coordinates
191    #[inline]
192    pub(crate) fn new(corner: OPoint<usize, Const<N>>) -> Tile<N> {
193        Tile { corner }
194    }
195
196    /// Converts a relative position within the tile into a global position
197    ///
198    /// This function operates in pixel space, using the `.xy` coordinates
199    pub(crate) fn add(&self, pos: Vector2<usize>) -> Point2<usize> {
200        let corner = Point2::new(self.corner[0], self.corner[1]);
201        corner + pos
202    }
203}
204
205////////////////////////////////////////////////////////////////////////////////
206
207#[cfg(test)]
208mod test {
209    use super::*;
210    use fidget_core::render::ImageSize;
211
212    #[test]
213    fn test_default_render_config() {
214        let config = ImageRenderConfig {
215            image_size: ImageSize::from(512),
216            ..Default::default()
217        };
218        let mat = config.mat();
219        assert_eq!(
220            mat.transform_point(&Point2::new(0.0, -1.0)),
221            Point2::new(-1.0, 1.0)
222        );
223        assert_eq!(
224            mat.transform_point(&Point2::new(512.0, -1.0)),
225            Point2::new(1.0, 1.0)
226        );
227        assert_eq!(
228            mat.transform_point(&Point2::new(512.0, 511.0)),
229            Point2::new(1.0, -1.0)
230        );
231
232        let config = ImageRenderConfig {
233            image_size: ImageSize::from(575),
234            ..Default::default()
235        };
236        let mat = config.mat();
237        assert_eq!(
238            mat.transform_point(&Point2::new(0.0, -1.0)),
239            Point2::new(-1.0, 1.0)
240        );
241        assert_eq!(
242            mat.transform_point(&Point2::new(
243                config.image_size.width() as f32,
244                -1.0
245            )),
246            Point2::new(1.0, 1.0)
247        );
248        assert_eq!(
249            mat.transform_point(&Point2::new(
250                config.image_size.width() as f32,
251                config.image_size.height() as f32 - 1.0,
252            )),
253            Point2::new(1.0, -1.0)
254        );
255    }
256}