#![allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_lossless,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::similar_names
)]
#[derive(Debug, Clone)]
pub struct Sky {
pub pixels: Vec<i32>,
pub xsiz: i32,
pub ysiz: i32,
pub bpl: i32,
pub lng: Vec<[f32; 2]>,
pub lat: Vec<i32>,
pub lng_mul: f32,
}
impl Sky {
#[must_use]
pub fn from_pixels(pixels: Vec<i32>, original_xsiz: u32, ysiz: u32) -> Self {
assert!(
original_xsiz >= 2 && ysiz >= 1,
"sky texture must be ≥ 2 wide and ≥ 1 tall (got {original_xsiz}×{ysiz})"
);
assert_eq!(
pixels.len(),
(original_xsiz as usize) * (ysiz as usize),
"sky pixel count {} != xsiz*ysiz = {}",
pixels.len(),
(original_xsiz as usize) * (ysiz as usize)
);
let bpl = (original_xsiz as i32) * 4;
let ysiz_i = ysiz as i32;
let original_xsiz_i = original_xsiz as i32;
let mut lng = vec![[0.0_f32; 2]; ysiz as usize];
let f = std::f32::consts::PI * 2.0 / (ysiz as f32);
for y in 0..ysiz {
let a = (y as f32) * f + std::f32::consts::PI;
lng[y as usize] = [a.cos(), a.sin()];
}
if ysiz == 1 {
lng[0] = [0.0, 0.0];
}
let lng_mul = (ysiz as f32) / (std::f32::consts::PI * 2.0);
let mut lat = vec![0i32; original_xsiz as usize];
let f = std::f32::consts::PI * 0.5 / (original_xsiz as f32);
for x in (1..original_xsiz_i).rev() {
let ang = ((x << 1) - original_xsiz_i) as f32 * f;
let xoff = crate::fixed::ftol(ang.cos() * 32767.0);
let yoff = crate::fixed::ftol(ang.sin() * 32767.0);
lat[x as usize] = (xoff << 16) | ((-yoff) & 0xffff);
}
Self {
pixels,
xsiz: original_xsiz_i - 1,
ysiz: ysiz_i,
bpl,
lng,
lat,
lng_mul,
}
}
#[must_use]
pub fn blue_gradient() -> Self {
const SKYXSIZ: i32 = 512;
const SKYYSIZ: i32 = 1;
let mut pixels = vec![0i32; (SKYXSIZ * SKYYSIZ) as usize];
let y = SKYXSIZ * SKYXSIZ;
for x in 0..=(SKYXSIZ >> 1) {
let r = ((x * 1081 - SKYXSIZ * 252) * x) / y + 35;
let g = ((x * 950 - SKYXSIZ * 198) * x) / y + 53;
let b = ((x * 439 - SKYXSIZ * 21) * x) / y + 98;
pixels[x as usize] = (r << 16) | (g << 8) | b;
}
pixels[(SKYXSIZ - 1) as usize] = 0x0050_903c;
let mid = SKYXSIZ >> 1;
let r_mid = (pixels[mid as usize] >> 16) & 0xff;
let g_mid = (pixels[mid as usize] >> 8) & 0xff;
let b_mid = pixels[mid as usize] & 0xff;
for x in (mid + 1)..SKYXSIZ {
let denom = SKYXSIZ - 1 - mid;
let r = ((0x50 - r_mid) * (x - mid)) / denom + r_mid;
let g = ((0x90 - g_mid) * (x - mid)) / denom + g_mid;
let b = ((0x3c - b_mid) * (x - mid)) / denom + b_mid;
pixels[x as usize] = (r << 16) | (g << 8) | b;
}
Self::from_pixels(pixels, SKYXSIZ as u32, SKYYSIZ as u32)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blue_gradient_builds() {
let s = Sky::blue_gradient();
assert_eq!(s.pixels.len(), 512);
assert_eq!(s.xsiz, 511); assert_eq!(s.ysiz, 1);
assert_eq!(s.bpl, 512 * 4);
assert_eq!(s.lng.len(), 1);
assert_eq!(s.lat.len(), 512);
assert_eq!(s.lat[0], 0);
assert_eq!(s.lng[0][0].to_bits(), 0u32);
assert_eq!(s.lng[0][1].to_bits(), 0u32);
}
#[test]
fn lat_entries_are_unit_vectors() {
let s = Sky::blue_gradient();
for x in [1, 50, 100, 256, 400, 510] {
let entry = s.lat[x];
let neg_yoff = (entry & 0xffff) as i16 as i32;
let xoff = ((entry >> 16) & 0xffff) as i16 as i32;
let yoff = -neg_yoff;
let len2 = xoff * xoff + yoff * yoff;
assert!(
(len2 - 32767 * 32767).abs() < 200_000,
"lat[{x}] = ({xoff}, {yoff}); len² = {len2}, expected ~{}",
32767 * 32767
);
}
}
#[test]
fn from_pixels_4x4() {
let pixels: Vec<i32> = (0..16).collect();
let s = Sky::from_pixels(pixels, 4, 4);
assert_eq!(s.xsiz, 3);
assert_eq!(s.ysiz, 4);
assert_eq!(s.bpl, 16);
assert_eq!(s.lng.len(), 4);
assert_eq!(s.lat.len(), 4);
assert_eq!(s.pixels[3], 3);
}
}