#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LayerType {
Background,
Foreground,
Overlay,
Matte,
}
impl LayerType {
#[must_use]
pub fn is_transparent(&self) -> bool {
matches!(self, Self::Overlay | Self::Matte)
}
}
#[derive(Debug, Clone)]
pub struct LodConfig {
pub distance_thresholds: Vec<f32>,
pub scale_factors: Vec<f32>,
}
impl LodConfig {
#[must_use]
pub fn default_4band() -> Self {
Self {
distance_thresholds: vec![100.0, 500.0, 2000.0],
scale_factors: vec![1.0, 0.5, 0.25, 0.125],
}
}
#[must_use]
pub fn scale_for_distance(&self, distance: f32) -> f32 {
for (i, &threshold) in self.distance_thresholds.iter().enumerate() {
if distance < threshold {
return self.scale_factors.get(i).copied().unwrap_or(1.0);
}
}
self.scale_factors.last().copied().unwrap_or(0.125)
}
}
impl Default for LodConfig {
fn default() -> Self {
Self::default_4band()
}
}
fn bilinear_upsample(src: &[u8], src_w: u32, src_h: u32, dst_w: u32, dst_h: u32) -> Vec<u8> {
let mut out = vec![0u8; dst_w as usize * dst_h as usize * 3];
let scale_x = src_w as f32 / dst_w as f32;
let scale_y = src_h as f32 / dst_h as f32;
for dy in 0..dst_h {
for dx in 0..dst_w {
let sx = (dx as f32 + 0.5) * scale_x - 0.5;
let sy = (dy as f32 + 0.5) * scale_y - 0.5;
let x0 = (sx.floor() as i32).max(0) as u32;
let y0 = (sy.floor() as i32).max(0) as u32;
let x1 = (x0 + 1).min(src_w - 1);
let y1 = (y0 + 1).min(src_h - 1);
let fx = sx - sx.floor();
let fy = sy - sy.floor();
let sample = |px: u32, py: u32, c: usize| -> f32 {
let i = (py as usize * src_w as usize + px as usize) * 3 + c;
src.get(i).copied().unwrap_or(0) as f32
};
let dst_i = (dy as usize * dst_w as usize + dx as usize) * 3;
for c in 0..3 {
let tl = sample(x0, y0, c);
let tr = sample(x1, y0, c);
let bl = sample(x0, y1, c);
let br = sample(x1, y1, c);
let v = tl * (1.0 - fx) * (1.0 - fy)
+ tr * fx * (1.0 - fy)
+ bl * (1.0 - fx) * fy
+ br * fx * fy;
out[dst_i + c] = v.round().clamp(0.0, 255.0) as u8;
}
}
}
out
}
#[derive(Debug, Clone)]
pub struct RenderLayer {
pub name: String,
pub layer_type: LayerType,
depth: i32,
pub opacity: f32,
lod: Option<LodConfig>,
}
impl RenderLayer {
#[must_use]
pub fn new(name: impl Into<String>, layer_type: LayerType, depth: i32) -> Self {
Self {
name: name.into(),
layer_type,
depth,
opacity: 1.0,
lod: None,
}
}
#[must_use]
pub fn z_order(&self) -> i32 {
self.depth
}
#[must_use]
pub fn with_opacity(mut self, opacity: f32) -> Self {
self.opacity = opacity.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn with_lod(mut self, config: LodConfig) -> Self {
self.lod = Some(config);
self
}
#[must_use]
pub fn select_lod_scale(&self, camera_distance: f32) -> f32 {
self.lod
.as_ref()
.map(|c| c.scale_for_distance(camera_distance))
.unwrap_or(1.0)
}
#[must_use]
pub fn render_at_lod(&self, camera_distance: f32, w: u32, h: u32) -> Vec<u8> {
let scale = self.select_lod_scale(camera_distance).clamp(1e-3, 1.0);
let reduced_w = ((w as f32 * scale).round() as u32).max(1);
let reduced_h = ((h as f32 * scale).round() as u32).max(1);
let reduced_pixels = vec![128u8; reduced_w as usize * reduced_h as usize * 3];
if reduced_w == w && reduced_h == h {
return reduced_pixels;
}
bilinear_upsample(&reduced_pixels, reduced_w, reduced_h, w, h)
}
}
#[derive(Debug, Default)]
pub struct LayerStack {
layers: Vec<RenderLayer>,
}
impl LayerStack {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, layer: RenderLayer) {
self.layers.push(layer);
self.layers.sort_by_key(RenderLayer::z_order);
}
pub fn pop(&mut self) -> Option<RenderLayer> {
self.layers.pop()
}
#[must_use]
pub fn find_by_type(&self, layer_type: LayerType) -> Option<&RenderLayer> {
self.layers.iter().find(|l| l.layer_type == layer_type)
}
#[must_use]
pub fn len(&self) -> usize {
self.layers.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.layers.is_empty()
}
#[must_use]
pub fn layers(&self) -> &[RenderLayer] {
&self.layers
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_background_not_transparent() {
assert!(!LayerType::Background.is_transparent());
}
#[test]
fn test_foreground_not_transparent() {
assert!(!LayerType::Foreground.is_transparent());
}
#[test]
fn test_overlay_is_transparent() {
assert!(LayerType::Overlay.is_transparent());
}
#[test]
fn test_matte_is_transparent() {
assert!(LayerType::Matte.is_transparent());
}
#[test]
fn test_render_layer_z_order() {
let layer = RenderLayer::new("bg", LayerType::Background, 0);
assert_eq!(layer.z_order(), 0);
let layer2 = RenderLayer::new("fg", LayerType::Foreground, 10);
assert_eq!(layer2.z_order(), 10);
}
#[test]
fn test_render_layer_opacity_clamped() {
let layer = RenderLayer::new("hud", LayerType::Overlay, 100).with_opacity(1.5);
assert!((layer.opacity - 1.0).abs() < 1e-6);
let layer2 = RenderLayer::new("hud2", LayerType::Overlay, 100).with_opacity(-0.5);
assert!((layer2.opacity).abs() < 1e-6);
}
#[test]
fn test_stack_push_orders_by_z() {
let mut stack = LayerStack::new();
stack.push(RenderLayer::new("overlay", LayerType::Overlay, 50));
stack.push(RenderLayer::new("bg", LayerType::Background, 0));
stack.push(RenderLayer::new("fg", LayerType::Foreground, 20));
let orders: Vec<i32> = stack
.layers()
.iter()
.map(super::RenderLayer::z_order)
.collect();
assert_eq!(orders, vec![0, 20, 50]);
}
#[test]
fn test_stack_pop_returns_topmost() {
let mut stack = LayerStack::new();
stack.push(RenderLayer::new("bg", LayerType::Background, 0));
stack.push(RenderLayer::new("overlay", LayerType::Overlay, 99));
let popped = stack.pop().expect("should succeed in test");
assert_eq!(popped.z_order(), 99);
}
#[test]
fn test_stack_find_by_type() {
let mut stack = LayerStack::new();
stack.push(RenderLayer::new("bg", LayerType::Background, 0));
stack.push(RenderLayer::new("matte", LayerType::Matte, 5));
let found = stack.find_by_type(LayerType::Matte);
assert!(found.is_some());
assert_eq!(found.expect("should succeed in test").name, "matte");
}
#[test]
fn test_stack_find_missing_type() {
let mut stack = LayerStack::new();
stack.push(RenderLayer::new("bg", LayerType::Background, 0));
assert!(stack.find_by_type(LayerType::Overlay).is_none());
}
#[test]
fn test_stack_len_and_is_empty() {
let mut stack = LayerStack::new();
assert!(stack.is_empty());
stack.push(RenderLayer::new("bg", LayerType::Background, 0));
assert_eq!(stack.len(), 1);
assert!(!stack.is_empty());
}
#[test]
fn test_stack_pop_empty() {
let mut stack = LayerStack::new();
assert!(stack.pop().is_none());
}
}