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 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 { let fields = v.as_class("Cubemap")?; Ok(Cubemap { texture: Texture2D::from_fields(fields)?, }) } } impl Cubemap { pub fn to_image(&self) -> Result { self.texture.to_image() } } /// Face ordering is Left, Right, Back, Front, Down, Up pub fn cubemap_to_equirectangular( faces: &[ImageBuffer, Vec>; 6], output_width: u32, output_height: u32, ) -> ImageBuffer, Vec> { info!( "reprojecting {}x{} cubemap faces to {output_width}x{output_height} equirectangular", faces[0].width(), faces[0].height() ); let mut output_image = ImageBuffer::, Vec>::new(output_width, output_height); for (er_x, er_y, out) in output_image.enumerate_pixels_mut() { let xy_angle = er_x as f32 / output_width as f32 * PI * 2.; let cyl_z = er_y as f32 / output_height as f32 * 2. - 1.; let sphere_azimuth = cyl_z.asin(); let r = sphere_azimuth.cos(); let normal = vec3(xy_angle.sin() * r, xy_angle.cos() * r, cyl_z); 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 }