aboutsummaryrefslogtreecommitdiff
path: root/src/classes/cubemap.rs
blob: 58e77d65ebf7f2136a82a18263ad5d9ceb815f70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use super::texture2d::Texture2D;
use crate::object::{Value, parser::FromValue};
use anyhow::Result;
use glam::{UVec2, Vec3Swizzles, uvec2, vec2, vec3};
use image::{DynamicImage, ImageBuffer, Rgba};
use log::info;
use rayon::iter::ParallelIterator;
use serde::Serialize;
use std::f32::consts::PI;

#[derive(Debug, Serialize)]
pub struct Cubemap {
    pub texture: Texture2D,
}

impl FromValue for Cubemap {
    fn from_value(v: Value) -> Result<Self> {
        let fields = v.as_class("Cubemap")?;
        Ok(Cubemap {
            texture: Texture2D::from_fields(fields)?,
        })
    }
}
impl Cubemap {
    pub fn to_image(&self) -> Result<DynamicImage> {
        self.texture.to_image()
    }
}

/// Face ordering is Left, Right, Back, Front, Down, Up
pub fn cubemap_to_equirectangular(
    faces: &[ImageBuffer<Rgba<f32>, Vec<f32>>; 6],
    output_width: u32,
    output_height: u32,
) -> ImageBuffer<Rgba<f32>, Vec<f32>> {
    info!(
        "reprojecting {}x{} cubemap faces to {output_width}x{output_height} equirectangular",
        faces[0].width(),
        faces[0].height()
    );
    let mut output_image = ImageBuffer::<Rgba<f32>, Vec<f32>>::new(output_width, output_height);
    output_image
        .par_enumerate_pixels_mut()
        .for_each(|(er_x, er_y, out)| {
            let phi = er_x as f32 / output_width as f32 * PI * 2.;
            let theta = (1. - er_y as f32 / output_height as f32) * PI;
            let normal = vec3(
                phi.sin() * theta.sin(),
                phi.cos() * theta.sin(),
                theta.cos(),
            );
            assert!(normal.is_normalized());

            let normal_abs = normal.abs();
            let (axis, max_comp, other_comps) = if normal_abs.x > normal_abs.y.max(normal_abs.z) {
                (0, normal.x, normal.yz())
            } else if normal_abs.y > normal_abs.z {
                (1, normal.y, normal.xz())
            } else {
                (2, normal.z, normal.xy())
            };

            let face = axis as usize * 2 + if max_comp < 0. { 1 } else { 0 };
            let face_texture = &faces[face];
            let face_size = uvec2(face_texture.width(), face_texture.height());
            let face_uv = other_comps / max_comp * vec2(1., -max_comp.signum());
            let face_uv = face_uv / 2. + 0.5;

            let face_xy = (face_uv * face_size.as_vec2())
                .floor()
                .as_uvec2()
                .clamp(UVec2::MIN, face_size - UVec2::ONE);

            out.0 = face_texture.get_pixel(face_xy.x, face_xy.y).0;
        });
    output_image
}