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_vox(
114    segment: &Segment,
115    output_size: Vec2<u16>,
116    transform: Transform,
117    sample_strat: SampleStrat,
118) -> RgbaImage {
119    let output_size = output_size.map(|e| e as usize);
120    debug_assert!(output_size.map(|e| e != 0).reduce_and());
121
122    let ori_mat = Mat4::from(transform.ori);
123    let rotated_segment_dims = (ori_mat * Vec4::from_direction(segment.size().map(|e| e as f32)))
124        .xyz()
125        .map(|e| e.abs());
126
127    let dims = match sample_strat {
128        SampleStrat::None => output_size,
129        SampleStrat::SuperSampling(min_samples) => {
130            output_size * (min_samples as f32).sqrt().ceil() as usize
131        },
132        // Assumes
133        //  - rotations are multiples of 90 degrees
134        //  - the projection is orthographic
135        //  - no translation or zooming is performed
136        //  - stretch is enabled
137        SampleStrat::PixelCoverage => Vec2::new(
138            rotated_segment_dims.x.round() as usize,
139            rotated_segment_dims.y.round() as usize,
140        ),
141    }
142    .into_array();
143
144    debug_assert!(dims[0] != 0 && dims[1] != 0);
145
146    // Rendering buffers
147    let mut color = Buffer2d::new(dims, [0; 4]);
148    let mut depth = Buffer2d::new(dims, 1.0);
149
150    let (w, h, d) = segment.size().map(|e| e as f32).into_tuple();
151
152    let mvp = if transform.orth {
153        Mat4::<f32>::orthographic_rh_no(FrustumPlanes {
154            left: -1.0,
155            right: 1.0,
156            bottom: -1.0,
157            top: 1.0,
158            near: 0.0,
159            far: 1.0,
160        })
161    } else {
162        Mat4::<f32>::perspective_fov_rh_no(
163            1.1,            // fov
164            dims[0] as f32, // width
165            dims[1] as f32, // height
166            0.0,
167            1.0,
168        )
169    } * Mat4::scaling_3d(
170        // TODO replace with camera-like parameters?
171        if transform.stretch {
172            rotated_segment_dims.map(|e| 2.0 / e)
173        } else {
174            let s = w.max(h).max(d);
175            Vec3::from(2.0 / s)
176        } * transform.zoom,
177    ) * Mat4::translation_3d(transform.offset)
178        * ori_mat
179        * Mat4::translation_3d([-w / 2.0, -h / 2.0, -d / 2.0]);
180
181    Voxel {
182        mvp,
183        light_dir: Vec3::broadcast(-1.0).normalized(),
184    }
185    .draw::<rasterizer::Triangles<_>, _>(
186        &generate_mesh(segment, Vec3::from(0.0)),
187        &mut color,
188        Some(&mut depth),
189    );
190
191    let rgba_img = RgbaImage::from_vec(
192        dims[0] as u32,
193        dims[1] as u32,
194        color
195            .as_ref()
196            .iter()
197            .flatten()
198            .copied()
199            .collect::<Vec<u8>>(),
200    )
201    .unwrap();
202
203    match sample_strat {
204        SampleStrat::None => rgba_img,
205        SampleStrat::SuperSampling(_) => DynamicImage::ImageRgba8(rgba_img)
206            .resize_exact(
207                output_size.x as u32,
208                output_size.y as u32,
209                image::imageops::FilterType::Triangle,
210            )
211            .to_rgba8(),
212        SampleStrat::PixelCoverage => super::pixel_art::resize_pixel_art(
213            &rgba_img,
214            output_size.x as u32,
215            output_size.y as u32,
216        ),
217    }
218}
219
220fn ao_level(side1: bool, corner: bool, side2: bool) -> u8 {
221    if side1 && side2 {
222        0
223    } else {
224        3 - [side1, corner, side2].iter().filter(|e| **e).count() as u8
225    }
226}
227// TODO: Generalize meshing code.
228fn create_quad(
229    origin: Vec3<f32>,
230    unit_x: Vec3<f32>,
231    unit_y: Vec3<f32>,
232    norm: Vec3<f32>,
233    col: Rgb<f32>,
234    occluders: [bool; 8],
235) -> [Vert; 6] {
236    let a_ao = ao_level(occluders[0], occluders[1], occluders[2]);
237    let b_ao = ao_level(occluders[2], occluders[3], occluders[4]);
238    let c_ao = ao_level(occluders[4], occluders[5], occluders[6]);
239    let d_ao = ao_level(occluders[6], occluders[7], occluders[0]);
240
241    let a = Vert::new(origin, col, norm, a_ao);
242    let b = Vert::new(origin + unit_x, col, norm, b_ao);
243    let c = Vert::new(origin + unit_x + unit_y, col, norm, c_ao);
244    let d = Vert::new(origin + unit_y, col, norm, d_ao);
245
246    // Flip to fix anisotropy.
247    let (a, b, c, d) = if a_ao + c_ao > b_ao + d_ao {
248        (d, a, b, c)
249    } else {
250        (a, b, c, d)
251    };
252
253    [
254        a, b, c, // Tri 1
255        c, d, a, // Tri 2
256    ]
257}
258
259fn generate_mesh(segment: &Segment, offs: Vec3<f32>) -> Vec<Vert> {
260    let mut vertices = Vec::new();
261
262    for (pos, vox) in segment.full_vol_iter() {
263        if let Some(col) = vox.get_color() {
264            let col = col.map(|e| e as f32 / 255.0);
265
266            let is_filled = |pos| segment.get(pos).map(|v| v.is_filled()).unwrap_or(false);
267
268            let occluders = |unit_x, unit_y, dir| {
269                // Would be nice to generate unit_x and unit_y from a given direction.
270                [
271                    is_filled(pos + dir - unit_x),
272                    is_filled(pos + dir - unit_x - unit_y),
273                    is_filled(pos + dir - unit_y),
274                    is_filled(pos + dir + unit_x - unit_y),
275                    is_filled(pos + dir + unit_x),
276                    is_filled(pos + dir + unit_x + unit_y),
277                    is_filled(pos + dir + unit_y),
278                    is_filled(pos + dir - unit_x + unit_y),
279                ]
280            };
281
282            // -x
283            if !is_filled(pos - Vec3::unit_x()) {
284                vertices.extend_from_slice(&create_quad(
285                    offs + pos.map(|e| e as f32) + Vec3::unit_y(),
286                    -Vec3::unit_y(),
287                    Vec3::unit_z(),
288                    -Vec3::unit_x(),
289                    col,
290                    occluders(-Vec3::unit_y(), Vec3::unit_z(), -Vec3::unit_x()),
291                ));
292            }
293            // +x
294            if !is_filled(pos + Vec3::unit_x()) {
295                vertices.extend_from_slice(&create_quad(
296                    offs + pos.map(|e| e as f32) + Vec3::unit_x(),
297                    Vec3::unit_y(),
298                    Vec3::unit_z(),
299                    Vec3::unit_x(),
300                    col,
301                    occluders(Vec3::unit_y(), Vec3::unit_z(), Vec3::unit_x()),
302                ));
303            }
304            // -y
305            if !is_filled(pos - Vec3::unit_y()) {
306                vertices.extend_from_slice(&create_quad(
307                    offs + pos.map(|e| e as f32),
308                    Vec3::unit_x(),
309                    Vec3::unit_z(),
310                    -Vec3::unit_y(),
311                    col,
312                    occluders(Vec3::unit_x(), Vec3::unit_z(), -Vec3::unit_y()),
313                ));
314            }
315            // +y
316            if !is_filled(pos + Vec3::unit_y()) {
317                vertices.extend_from_slice(&create_quad(
318                    offs + pos.map(|e| e as f32) + Vec3::unit_y(),
319                    Vec3::unit_z(),
320                    Vec3::unit_x(),
321                    Vec3::unit_y(),
322                    col,
323                    occluders(Vec3::unit_z(), Vec3::unit_x(), Vec3::unit_y()),
324                ));
325            }
326            // -z
327            if !is_filled(pos - Vec3::unit_z()) {
328                vertices.extend_from_slice(&create_quad(
329                    offs + pos.map(|e| e as f32),
330                    Vec3::unit_y(),
331                    Vec3::unit_x(),
332                    -Vec3::unit_z(),
333                    col,
334                    occluders(Vec3::unit_y(), Vec3::unit_x(), -Vec3::unit_z()),
335                ));
336            }
337            // +z
338            if !is_filled(pos + Vec3::unit_z()) {
339                vertices.extend_from_slice(&create_quad(
340                    offs + pos.map(|e| e as f32) + Vec3::unit_z(),
341                    Vec3::unit_x(),
342                    Vec3::unit_y(),
343                    Vec3::unit_z(),
344                    col,
345                    occluders(Vec3::unit_x(), Vec3::unit_y(), Vec3::unit_z()),
346                ));
347            }
348        }
349    }
350
351    vertices
352}