#![allow(dead_code)]
use std::f32::consts::TAU;
#[derive(Debug, Clone)]
pub struct ImpostorConfig {
pub angle_steps: u32,
pub resolution: u32,
pub use_alpha: bool,
}
impl Default for ImpostorConfig {
fn default() -> Self {
Self {
angle_steps: 8,
resolution: 256,
use_alpha: true,
}
}
}
#[derive(Debug, Clone)]
pub struct ImpostorQuad {
pub center: [f32; 3],
pub half_width: f32,
pub half_height: f32,
pub angle_index: u32,
}
#[derive(Debug, Default, Clone)]
pub struct ImpostorAtlas {
pub quads: Vec<ImpostorQuad>,
pub config: ImpostorConfig,
}
impl ImpostorAtlas {
pub fn generate(config: ImpostorConfig, center: [f32; 3], half_extents: [f32; 2]) -> Self {
let mut quads = Vec::with_capacity(config.angle_steps as usize);
for i in 0..config.angle_steps {
quads.push(ImpostorQuad {
center,
half_width: half_extents[0],
half_height: half_extents[1],
angle_index: i,
});
}
Self { quads, config }
}
pub fn angle_for_index(&self, idx: u32) -> f32 {
(idx as f32 / self.config.angle_steps as f32) * TAU
}
pub fn quad_count(&self) -> usize {
self.quads.len()
}
}
pub fn nearest_angle_index(azimuth_radians: f32, angle_steps: u32) -> u32 {
let normalized = azimuth_radians.rem_euclid(TAU) / TAU;
((normalized * angle_steps as f32).round() as u32) % angle_steps
}
pub fn billboard_quad_vertices(center: [f32; 3], hw: f32, hh: f32) -> [[f32; 3]; 4] {
[
[center[0] - hw, center[1] - hh, center[2]],
[center[0] + hw, center[1] - hh, center[2]],
[center[0] + hw, center[1] + hh, center[2]],
[center[0] - hw, center[1] + hh, center[2]],
]
}
pub fn billboard_quad_uvs() -> [[f32; 2]; 4] {
[[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]]
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_default_config() {
let cfg = ImpostorConfig::default();
assert_eq!(cfg.angle_steps, 8);
}
#[test]
fn test_generate_quad_count() {
let cfg = ImpostorConfig {
angle_steps: 8,
resolution: 128,
use_alpha: true,
};
let atlas = ImpostorAtlas::generate(cfg, [0.0; 3], [1.0, 2.0]);
assert_eq!(atlas.quad_count(), 8);
}
#[test]
fn test_angle_for_index_zero() {
let cfg = ImpostorConfig::default();
let atlas = ImpostorAtlas::generate(cfg, [0.0; 3], [1.0, 1.0]);
assert_eq!(atlas.angle_for_index(0), 0.0);
}
#[test]
fn test_angle_for_index_half() {
let cfg = ImpostorConfig {
angle_steps: 8,
resolution: 128,
use_alpha: false,
};
let atlas = ImpostorAtlas::generate(cfg, [0.0; 3], [1.0, 1.0]);
assert!((atlas.angle_for_index(4) - PI).abs() < 0.001);
}
#[test]
fn test_nearest_angle_index_zero() {
assert_eq!(nearest_angle_index(0.0, 8), 0);
}
#[test]
fn test_nearest_angle_wraps() {
assert_eq!(nearest_angle_index(TAU, 8), 0);
}
#[test]
fn test_billboard_quad_vertices_count() {
let verts = billboard_quad_vertices([0.0; 3], 1.0, 1.0);
assert_eq!(verts.len(), 4);
}
#[test]
fn test_billboard_quad_uvs_count() {
assert_eq!(billboard_quad_uvs().len(), 4);
}
#[test]
fn test_billboard_center() {
let c = [1.0f32, 2.0, 3.0];
let verts = billboard_quad_vertices(c, 1.0, 1.0);
let avg_x = verts.iter().map(|v| v[0]).sum::<f32>() / 4.0;
assert!((avg_x - c[0]).abs() < 0.001);
}
#[test]
fn test_empty_atlas_is_empty() {
let atlas = ImpostorAtlas::default();
assert_eq!(atlas.quad_count(), 0);
}
}