1use common::{
2 figure::Segment,
3 util::{linear_to_srgba, srgb_to_linear_fast},
4 vol::{FilledVox, IntoFullVolIterator, ReadVol, SizedVol},
5};
6use euc::{Pipeline, buffer::Buffer2d, rasterizer};
7use image::{DynamicImage, RgbaImage};
8use vek::*;
9
10#[derive(Copy, Clone)]
11pub enum SampleStrat {
12 None,
13 SuperSampling(u8),
14 PixelCoverage,
15}
16
17#[derive(Clone, Copy)]
18pub struct Transform {
19 pub ori: Quaternion<f32>,
20 pub offset: Vec3<f32>,
21 pub zoom: f32,
22 pub orth: bool,
23 pub stretch: bool,
24}
25impl Default for Transform {
26 fn default() -> Self {
27 Self {
28 ori: Quaternion::identity(),
29 offset: Vec3::zero(),
30 zoom: 1.0,
31 orth: true,
32 stretch: true,
33 }
34 }
35}
36
37struct Voxel {
38 mvp: Mat4<f32>,
39 light_dir: Vec3<f32>,
40}
41
42#[derive(Copy, Clone)]
44struct Vert {
45 pos: Vec3<f32>,
46 col: Rgb<f32>,
47 norm: Vec3<f32>,
48 ao_level: u8,
49}
50impl Vert {
51 fn new(pos: Vec3<f32>, col: Rgb<f32>, norm: Vec3<f32>, ao_level: u8) -> Self {
52 Vert {
53 pos,
54 col,
55 norm,
56 ao_level,
57 }
58 }
59}
60
61#[derive(Clone, Copy)]
62struct VsOut(Rgba<f32>);
63impl euc::Interpolate for VsOut {
64 #[inline(always)]
65 fn lerp2(a: Self, b: Self, x: f32, y: f32) -> Self {
66 Self(a.0.map2(b.0, |a, b| a.mul_add(x, b * y)))
68 }
69
70 #[inline(always)]
71 fn lerp3(a: Self, b: Self, c: Self, x: f32, y: f32, z: f32) -> Self {
72 Self(
74 a.0.map2(b.0.map2(c.0, |b, c| b.mul_add(y, c * z)), |a, bc| {
75 a.mul_add(x, bc)
76 }),
77 )
78 }
79}
80
81impl Pipeline for Voxel {
82 type Pixel = [u8; 4];
83 type Vertex = Vert;
84 type VsOut = VsOut;
85
86 #[inline(always)]
87 fn vert(
88 &self,
89 Vert {
90 pos,
91 col,
92 norm,
93 ao_level,
94 }: &Self::Vertex,
95 ) -> ([f32; 4], Self::VsOut) {
96 let ambiance = 0.25;
97 let diffuse = norm.dot(-self.light_dir).max(0.0);
98 let brightness = 2.5;
99 let light = Rgb::from(*ao_level as f32 / 4.0) * (diffuse + ambiance) * brightness;
100 let color = light * srgb_to_linear_fast(*col);
101 let position = (self.mvp * Vec4::from_point(*pos)).into_array();
102 (position, VsOut(Rgba::from_opaque(color)))
103 }
104
105 #[inline(always)]
106 fn frag(&self, color: &Self::VsOut) -> Self::Pixel {
107 linear_to_srgba(color.0)
108 .map(|e| (e * 255.0) as u8)
109 .into_array()
110 }
111}
112
113pub fn draw_voxes(
114 segments: &[(Mat4<f32>, &Segment)],
115 output_size: Vec2<u16>,
116 transform: Transform,
117 sample_strat: SampleStrat,
118 offset_scaling: Vec3<f32>,
119) -> RgbaImage {
120 let output_size = output_size.map(|e| e as usize);
121 debug_assert!(output_size.map(|e| e != 0).reduce_and());
122 debug_assert!(!segments.is_empty());
123
124 let bounds = segments
125 .iter()
126 .map(|(t, s)| {
127 let size = s.size().as_::<f32>();
128
129 Aabb {
130 min: t.mul_point(Vec3::zero()),
131 max: t.mul_point(size),
132 }
133 .made_valid()
134 })
135 .reduce(|a, b| a.union(b))
136 .expect("`semgents` shouldn't be empty");
137
138 let ori_mat = Mat4::from(transform.ori);
139
140 let rotated_segment_dims = ori_mat
141 .mul_direction(Vec3::from(bounds.size()))
142 .map(|e| e.abs());
143
144 let dims = match sample_strat {
145 SampleStrat::None => output_size,
146 SampleStrat::SuperSampling(min_samples) => {
147 output_size * (min_samples as f32).sqrt().ceil() as usize
148 },
149 SampleStrat::PixelCoverage => Vec2::new(
155 rotated_segment_dims.x.round() as usize,
156 rotated_segment_dims.y.round() as usize,
157 ),
158 }
159 .into_array();
160
161 debug_assert!(dims[0] != 0 && dims[1] != 0);
162
163 let (w, h, d) = bounds.size().into_tuple();
164
165 let mvp = if transform.orth {
166 Mat4::<f32>::orthographic_rh_no(FrustumPlanes {
167 left: -1.0,
168 right: 1.0,
169 bottom: -1.0,
170 top: 1.0,
171 near: 0.0,
172 far: 1.0,
173 })
174 } else {
175 Mat4::<f32>::perspective_fov_rh_no(
176 1.1, dims[0] as f32, dims[1] as f32, 0.0,
180 1.0,
181 )
182 } * Mat4::scaling_3d(
183 if transform.stretch {
185 rotated_segment_dims.map(|e| 2.0 / e)
186 } else {
187 let s = w.max(h).max(d);
188 Vec3::from(2.0 / s)
189 } * transform.zoom,
190 ) * Mat4::translation_3d(transform.offset)
191 * ori_mat
192 * Mat4::translation_3d([
193 -w / 2.0 * offset_scaling.x,
194 -h / 2.0 * offset_scaling.y,
195 -d / 2.0 * offset_scaling.z,
196 ]);
197
198 let mut color = Buffer2d::new(dims, [0; 4]);
200 let mut depth = Buffer2d::new(dims, 1.0);
201
202 for (t, segment) in segments {
203 Voxel {
204 mvp: mvp * *t,
205 light_dir: Vec3::broadcast(-1.0).normalized(),
206 }
207 .draw::<rasterizer::Triangles<_>, _>(
208 &generate_mesh(segment, Vec3::from(0.0)),
209 &mut color,
210 Some(&mut depth),
211 );
212 }
213
214 let rgba_img = RgbaImage::from_vec(
215 dims[0] as u32,
216 dims[1] as u32,
217 color
218 .as_ref()
219 .iter()
220 .flatten()
221 .copied()
222 .collect::<Vec<u8>>(),
223 )
224 .unwrap();
225
226 match sample_strat {
227 SampleStrat::None => rgba_img,
228 SampleStrat::SuperSampling(_) => DynamicImage::ImageRgba8(rgba_img)
229 .resize_exact(
230 output_size.x as u32,
231 output_size.y as u32,
232 image::imageops::FilterType::Triangle,
233 )
234 .to_rgba8(),
235 SampleStrat::PixelCoverage => super::pixel_art::resize_pixel_art(
236 &rgba_img,
237 output_size.x as u32,
238 output_size.y as u32,
239 ),
240 }
241}
242
243pub fn draw_vox(
244 segment: &Segment,
245 output_size: Vec2<u16>,
246 transform: Transform,
247 sample_strat: SampleStrat,
248) -> RgbaImage {
249 draw_voxes(
250 &[(Mat4::identity(), segment)],
251 output_size,
252 transform,
253 sample_strat,
254 Vec3::one(),
255 )
256}
257
258fn ao_level(side1: bool, corner: bool, side2: bool) -> u8 {
259 if side1 && side2 {
260 0
261 } else {
262 3 - [side1, corner, side2].iter().filter(|e| **e).count() as u8
263 }
264}
265fn create_quad(
267 origin: Vec3<f32>,
268 unit_x: Vec3<f32>,
269 unit_y: Vec3<f32>,
270 norm: Vec3<f32>,
271 col: Rgb<f32>,
272 occluders: [bool; 8],
273) -> [Vert; 6] {
274 let a_ao = ao_level(occluders[0], occluders[1], occluders[2]);
275 let b_ao = ao_level(occluders[2], occluders[3], occluders[4]);
276 let c_ao = ao_level(occluders[4], occluders[5], occluders[6]);
277 let d_ao = ao_level(occluders[6], occluders[7], occluders[0]);
278
279 let a = Vert::new(origin, col, norm, a_ao);
280 let b = Vert::new(origin + unit_x, col, norm, b_ao);
281 let c = Vert::new(origin + unit_x + unit_y, col, norm, c_ao);
282 let d = Vert::new(origin + unit_y, col, norm, d_ao);
283
284 let (a, b, c, d) = if a_ao + c_ao > b_ao + d_ao {
286 (d, a, b, c)
287 } else {
288 (a, b, c, d)
289 };
290
291 [
292 a, b, c, c, d, a, ]
295}
296
297fn generate_mesh(segment: &Segment, offs: Vec3<f32>) -> Vec<Vert> {
298 let mut vertices = Vec::new();
299
300 for (pos, vox) in segment.full_vol_iter() {
301 if let Some(col) = vox.get_color() {
302 let col = col.map(|e| e as f32 / 255.0);
303
304 let is_filled = |pos| segment.get(pos).map(|v| v.is_filled()).unwrap_or(false);
305
306 let occluders = |unit_x, unit_y, dir| {
307 [
309 is_filled(pos + dir - unit_x),
310 is_filled(pos + dir - unit_x - unit_y),
311 is_filled(pos + dir - unit_y),
312 is_filled(pos + dir + unit_x - unit_y),
313 is_filled(pos + dir + unit_x),
314 is_filled(pos + dir + unit_x + unit_y),
315 is_filled(pos + dir + unit_y),
316 is_filled(pos + dir - unit_x + unit_y),
317 ]
318 };
319
320 if !is_filled(pos - Vec3::unit_x()) {
322 vertices.extend_from_slice(&create_quad(
323 offs + pos.map(|e| e as f32) + Vec3::unit_y(),
324 -Vec3::unit_y(),
325 Vec3::unit_z(),
326 -Vec3::unit_x(),
327 col,
328 occluders(-Vec3::unit_y(), Vec3::unit_z(), -Vec3::unit_x()),
329 ));
330 }
331 if !is_filled(pos + Vec3::unit_x()) {
333 vertices.extend_from_slice(&create_quad(
334 offs + pos.map(|e| e as f32) + Vec3::unit_x(),
335 Vec3::unit_y(),
336 Vec3::unit_z(),
337 Vec3::unit_x(),
338 col,
339 occluders(Vec3::unit_y(), Vec3::unit_z(), Vec3::unit_x()),
340 ));
341 }
342 if !is_filled(pos - Vec3::unit_y()) {
344 vertices.extend_from_slice(&create_quad(
345 offs + pos.map(|e| e as f32),
346 Vec3::unit_x(),
347 Vec3::unit_z(),
348 -Vec3::unit_y(),
349 col,
350 occluders(Vec3::unit_x(), Vec3::unit_z(), -Vec3::unit_y()),
351 ));
352 }
353 if !is_filled(pos + Vec3::unit_y()) {
355 vertices.extend_from_slice(&create_quad(
356 offs + pos.map(|e| e as f32) + Vec3::unit_y(),
357 Vec3::unit_z(),
358 Vec3::unit_x(),
359 Vec3::unit_y(),
360 col,
361 occluders(Vec3::unit_z(), Vec3::unit_x(), Vec3::unit_y()),
362 ));
363 }
364 if !is_filled(pos - Vec3::unit_z()) {
366 vertices.extend_from_slice(&create_quad(
367 offs + pos.map(|e| e as f32),
368 Vec3::unit_y(),
369 Vec3::unit_x(),
370 -Vec3::unit_z(),
371 col,
372 occluders(Vec3::unit_y(), Vec3::unit_x(), -Vec3::unit_z()),
373 ));
374 }
375 if !is_filled(pos + Vec3::unit_z()) {
377 vertices.extend_from_slice(&create_quad(
378 offs + pos.map(|e| e as f32) + Vec3::unit_z(),
379 Vec3::unit_x(),
380 Vec3::unit_y(),
381 Vec3::unit_z(),
382 col,
383 occluders(Vec3::unit_x(), Vec3::unit_y(), Vec3::unit_z()),
384 ));
385 }
386 }
387 }
388
389 vertices
390}