1use super::RenderHandle;
3use crate::{
4 Image, RenderConfig, RenderWorker, TileSizesRef,
5 config::{ImageRenderConfig, Tile},
6};
7use fidget_core::{
8 eval::Function,
9 shape::{Shape, ShapeBulkEval, ShapeTracingEval, ShapeVars},
10 types::Interval,
11};
12use nalgebra::{Point2, Vector2};
13
14struct Scratch {
17 x: Vec<f32>,
18 y: Vec<f32>,
19 z: Vec<f32>,
20}
21
22impl Scratch {
23 fn new(size: usize) -> Self {
24 Self {
25 x: vec![0.0; size],
26 y: vec![0.0; size],
27 z: vec![0.0; size],
28 }
29 }
30}
31
32#[derive(Copy, Clone, Debug, Default)]
37pub struct DistancePixel(f32);
38
39#[derive(Copy, Clone, Debug)]
40pub struct PixelFill {
41 pub depth: u8,
42 pub inside: bool,
43}
44
45impl DistancePixel {
46 const KEY: u32 = 0b1111_0110 << 9;
47 const KEY_MASK: u32 = 0b1111_1111 << 9;
48
49 #[inline]
53 pub fn inside(self) -> bool {
54 match self.fill() {
55 Ok(f) => f.inside,
56 Err(v) => v < 0.0,
57 }
58 }
59
60 #[inline]
62 pub fn is_distance(self) -> bool {
63 if !self.0.is_nan() {
64 return true;
65 }
66 let bits = self.0.to_bits();
67 (bits & Self::KEY_MASK) != Self::KEY
68 }
69
70 #[inline]
72 pub fn distance(self) -> Result<f32, PixelFill> {
73 match self.fill() {
74 Ok(v) => Err(v),
75 Err(v) => Ok(v),
76 }
77 }
78
79 #[inline]
81 pub fn fill(self) -> Result<PixelFill, f32> {
82 if self.is_distance() {
83 Err(self.0)
84 } else {
85 let bits = self.0.to_bits();
86 let inside = (bits & 1) == 1;
87 let depth = (bits >> 1) as u8;
88 Ok(PixelFill { inside, depth })
89 }
90 }
91}
92
93impl From<PixelFill> for DistancePixel {
94 #[inline]
95 fn from(p: PixelFill) -> Self {
96 let bits = 0x7FC00000
97 | (u32::from(p.depth) << 1)
98 | u32::from(p.inside)
99 | Self::KEY;
100 DistancePixel(f32::from_bits(bits))
101 }
102}
103
104impl From<f32> for DistancePixel {
105 #[inline]
106 fn from(p: f32) -> Self {
107 Self(if p.is_nan() { f32::NAN } else { p })
109 }
110}
111
112struct Worker<'a, F: Function> {
116 tile_sizes: TileSizesRef<'a>,
117 pixel_perfect: bool,
118 scratch: Scratch,
119
120 eval_float_slice: ShapeBulkEval<F::FloatSliceEval>,
121 eval_interval: ShapeTracingEval<F::IntervalEval>,
122
123 tape_storage: Vec<F::TapeStorage>,
125
126 shape_storage: Vec<F::Storage>,
128
129 workspace: F::Workspace,
131
132 image: Image<DistancePixel>,
136}
137
138impl<'a, F: Function, T> RenderWorker<'a, F, T> for Worker<'a, F> {
139 type Config = ImageRenderConfig<'a>;
140 type Output = Image<DistancePixel>;
141 fn new(cfg: &'a Self::Config) -> Self {
142 let tile_sizes = cfg.tile_sizes();
143 Worker::<F> {
144 scratch: Scratch::new(tile_sizes.last().pow(2)),
145 pixel_perfect: cfg.pixel_perfect,
146 image: Default::default(),
147 tile_sizes,
148 eval_float_slice: Default::default(),
149 eval_interval: Default::default(),
150 tape_storage: vec![],
151 shape_storage: vec![],
152 workspace: Default::default(),
153 }
154 }
155
156 fn render_tile(
157 &mut self,
158 shape: &mut RenderHandle<F, T>,
159 vars: &ShapeVars<f32>,
160 tile: super::config::Tile<2>,
161 ) -> Self::Output {
162 self.image = Image::new((self.tile_sizes[0] as u32).into());
163 self.render_tile_recurse(shape, vars, 0, tile);
164 std::mem::take(&mut self.image)
165 }
166}
167
168impl<F: Function> Worker<'_, F> {
169 fn render_tile_recurse<T>(
170 &mut self,
171 shape: &mut RenderHandle<F, T>,
172 vars: &ShapeVars<f32>,
173 depth: usize,
174 tile: Tile<2>,
175 ) {
176 let tile_size = self.tile_sizes[depth];
177
178 let base = Point2::from(tile.corner).cast::<f32>();
180 let x = Interval::new(base.x, base.x + tile_size as f32);
181 let y = Interval::new(base.y, base.y + tile_size as f32);
182 let z = Interval::new(0.0, 0.0);
183
184 let (i, simplify) = self
186 .eval_interval
187 .eval_v(shape.i_tape(&mut self.tape_storage), x, y, z, vars)
188 .unwrap();
189
190 if !self.pixel_perfect {
191 let pixel = if i.upper() < 0.0 {
192 Some(PixelFill {
193 inside: true,
194 depth: depth as u8,
195 })
196 } else if i.lower() > 0.0 {
197 Some(PixelFill {
198 inside: false,
199 depth: depth as u8,
200 })
201 } else {
202 None
203 };
204 if let Some(pixel) = pixel {
205 let fill = pixel.into();
206 for y in 0..tile_size {
207 let start = self
208 .tile_sizes
209 .pixel_offset(tile.add(Vector2::new(0, y)));
210 self.image[start..][..tile_size].fill(fill);
211 }
212 return;
213 }
214 }
215
216 let sub_tape = if let Some(trace) = simplify.as_ref() {
217 shape.simplify(
218 trace,
219 &mut self.workspace,
220 &mut self.shape_storage,
221 &mut self.tape_storage,
222 )
223 } else {
224 shape
225 };
226
227 if let Some(next_tile_size) = self.tile_sizes.get(depth + 1) {
228 let n = tile_size / next_tile_size;
229 for j in 0..n {
230 for i in 0..n {
231 self.render_tile_recurse(
232 sub_tape,
233 vars,
234 depth + 1,
235 Tile::new(
236 tile.corner + Vector2::new(i, j) * next_tile_size,
237 ),
238 );
239 }
240 }
241 } else {
242 self.render_tile_pixels(sub_tape, vars, tile_size, tile);
243 }
244 }
245
246 fn render_tile_pixels<T>(
247 &mut self,
248 shape: &mut RenderHandle<F, T>,
249 vars: &ShapeVars<f32>,
250 tile_size: usize,
251 tile: Tile<2>,
252 ) {
253 let mut index = 0;
254 for j in 0..tile_size {
255 for i in 0..tile_size {
256 self.scratch.x[index] = (tile.corner[0] + i) as f32;
257 self.scratch.y[index] = (tile.corner[1] + j) as f32;
258 index += 1;
259 }
260 }
261
262 let out = self
263 .eval_float_slice
264 .eval_v(
265 shape.f_tape(&mut self.tape_storage),
266 &self.scratch.x,
267 &self.scratch.y,
268 &self.scratch.z,
269 vars,
270 )
271 .unwrap();
272
273 let mut index = 0;
274 for j in 0..tile_size {
275 let o = self.tile_sizes.pixel_offset(tile.add(Vector2::new(0, j)));
276 for i in 0..tile_size {
277 self.image[o + i] = out[index].into();
278 index += 1;
279 }
280 }
281 }
282}
283
284pub fn render<F: Function>(
299 shape: Shape<F>,
300 vars: &ShapeVars<f32>,
301 config: &ImageRenderConfig,
302) -> Option<Image<DistancePixel>> {
303 let mat = config.mat();
305 let mat = mat.insert_row(2, 0.0);
306 let mat = mat.insert_column(2, 0.0);
307 let shape = shape.with_transform(mat);
308
309 let tiles =
310 super::render_tiles::<F, Worker<F>, _>(shape.clone(), vars, config)?;
311 let tile_sizes = config.tile_sizes();
312
313 let width = config.image_size.width() as usize;
314 let height = config.image_size.height() as usize;
315 let mut image = Image::new(config.image_size);
316 for (tile, data) in tiles.iter() {
317 let mut index = 0;
318 for j in 0..tile_sizes[0] {
319 let y = j + tile.corner.y;
320 for i in 0..tile_sizes[0] {
321 let x = i + tile.corner.x;
322 if y < height && x < width {
323 image[(y, x)] = data[index];
324 }
325 index += 1;
326 }
327 }
328 }
329 Some(image)
330}
331
332#[cfg(test)]
333mod test {
334 use super::*;
335 use fidget_core::{
336 Context, render::ImageSize, shape::Shape, vm::VmFunction,
337 };
338
339 const HI: &str =
340 include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../models/hi.vm"));
341
342 #[test]
343 fn render2d_cancel() {
344 let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
345 let shape = Shape::<VmFunction>::new(&ctx, root).unwrap();
346
347 let cfg = ImageRenderConfig {
348 image_size: ImageSize::new(64, 64),
349 ..Default::default()
350 };
351 let cancel = cfg.cancel.clone();
352 cancel.cancel();
353 let out = cfg.run(shape);
354 assert!(out.is_none());
355 }
356}