veloren_voxygen/ui/graphic/
renderer.rs

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// TODO: use norm or remove it
43#[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        //a * x + b * y
67        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        //a * x + b * y + c * z
73        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        // Assumes
150        //  - rotations are multiples of 90 degrees
151        //  - the projection is orthographic
152        //  - no translation or zooming is performed
153        //  - stretch is enabled
154        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,            // fov
177            dims[0] as f32, // width
178            dims[1] as f32, // height
179            0.0,
180            1.0,
181        )
182    } * Mat4::scaling_3d(
183        // TODO replace with camera-like parameters?
184        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    // Rendering buffers
199    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}
265// TODO: Generalize meshing code.
266fn 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    // Flip to fix anisotropy.
285    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, // Tri 1
293        c, d, a, // Tri 2
294    ]
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                // Would be nice to generate unit_x and unit_y from a given direction.
308                [
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            // -x
321            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            // +x
332            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            // -y
343            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            // +y
354            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            // -z
365            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            // +z
376            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}