use bytemuck::{Pod, Zeroable};
use engawa::{Effect, Material, Node, ResourceId};
use super::{post_material, SCENE};
pub const EFFECT_NAME: &str = "aurora";
pub const PRIORITY: u16 = 450;
pub const PARAMS_RESOURCE: &str = "aurora:params";
pub const WGSL: &str = include_str!("wgsl/aurora.wgsl");
pub const DEFAULT_GREEN: [f32; 3] = [0.10, 0.95, 0.35];
pub const DEFAULT_CYAN: [f32; 3] = [0.05, 0.60, 0.70];
pub const DEFAULT_VIOLET: [f32; 3] = [0.45, 0.18, 0.85];
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AuroraQuality {
Off = 0,
Low = 1,
Medium = 2,
High = 3,
}
impl AuroraQuality {
#[must_use]
pub const fn as_u32(self) -> u32 {
self as u32
}
#[must_use]
pub const fn from_u32(word: u32) -> Option<Self> {
match word {
0 => Some(Self::Off),
1 => Some(Self::Low),
2 => Some(Self::Medium),
3 => Some(Self::High),
_ => None,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod, Zeroable, PartialEq)]
pub struct AuroraParams {
pub frame: [f32; 4],
pub geometry: [f32; 4],
pub color_green: [f32; 4],
pub color_cyan: [f32; 4],
pub color_violet: [f32; 4],
pub tier: [u32; 4],
}
impl Default for AuroraParams {
fn default() -> Self {
Self {
frame: [0.0, 0.35, 1.0, 0.5], geometry: [0.62, 2.4, 800.0, 600.0], color_green: [DEFAULT_GREEN[0], DEFAULT_GREEN[1], DEFAULT_GREEN[2], 0.0],
color_cyan: [DEFAULT_CYAN[0], DEFAULT_CYAN[1], DEFAULT_CYAN[2], 0.0],
color_violet: [DEFAULT_VIOLET[0], DEFAULT_VIOLET[1], DEFAULT_VIOLET[2], 0.0],
tier: [AuroraQuality::Medium as u32, 0, 0, 0],
}
}
}
impl AuroraParams {
#[must_use]
pub fn with_time(mut self, t: f32) -> Self {
self.frame[0] = t;
self
}
pub fn set_time(&mut self, t: f32) {
self.frame[0] = t;
}
#[must_use]
pub fn with_intensity(mut self, i: f32) -> Self {
self.frame[1] = i.clamp(0.0, 1.0);
self
}
pub fn set_intensity(&mut self, i: f32) {
self.frame[1] = i.clamp(0.0, 1.0);
}
#[must_use]
pub fn with_drift(mut self, d: f32) -> Self {
self.frame[2] = d.clamp(0.0, 4.0);
self
}
pub fn set_drift(&mut self, d: f32) {
self.frame[2] = d.clamp(0.0, 4.0);
}
#[must_use]
pub fn with_shimmer(mut self, s: f32) -> Self {
self.frame[3] = s.clamp(0.0, 1.0);
self
}
pub fn set_shimmer(&mut self, s: f32) {
self.frame[3] = s.clamp(0.0, 1.0);
}
#[must_use]
pub fn with_horizon(mut self, h: f32) -> Self {
self.geometry[0] = h.clamp(0.05, 1.0);
self
}
pub fn set_horizon(&mut self, h: f32) {
self.geometry[0] = h.clamp(0.05, 1.0);
}
#[must_use]
pub fn with_band_scale(mut self, b: f32) -> Self {
self.geometry[1] = b.clamp(0.5, 8.0);
self
}
pub fn set_band_scale(&mut self, b: f32) {
self.geometry[1] = b.clamp(0.5, 8.0);
}
#[must_use]
pub fn with_resolution(mut self, [w, h]: [f32; 2]) -> Self {
self.geometry[2] = w;
self.geometry[3] = h;
self
}
pub fn set_resolution(&mut self, [w, h]: [f32; 2]) {
self.geometry[2] = w;
self.geometry[3] = h;
}
#[must_use]
pub fn with_quality(mut self, q: AuroraQuality) -> Self {
self.tier[0] = q.as_u32();
self
}
pub fn set_quality(&mut self, q: AuroraQuality) {
self.tier[0] = q.as_u32();
}
#[must_use]
pub const fn quality(&self) -> Option<AuroraQuality> {
AuroraQuality::from_u32(self.tier[0])
}
#[must_use]
pub fn with_colors(
mut self,
green: [f32; 3],
cyan: [f32; 3],
violet: [f32; 3],
) -> Self {
self.color_green = [green[0], green[1], green[2], 0.0];
self.color_cyan = [cyan[0], cyan[1], cyan[2], 0.0];
self.color_violet = [violet[0], violet[1], violet[2], 0.0];
self
}
}
#[must_use]
pub fn material(input: &ResourceId) -> Material {
post_material(EFFECT_NAME, WGSL, input, PARAMS_RESOURCE)
}
#[must_use]
pub fn effect() -> Effect {
Effect {
name: EFFECT_NAME.to_string(),
enabled: true,
priority: PRIORITY,
material: material(&SCENE.into()),
}
}
#[must_use]
pub fn lower(input: &ResourceId, output: &ResourceId) -> Vec<Node> {
vec![Node::fullscreen_effect(
EFFECT_NAME,
material(input),
input.clone(),
output.clone(),
)]
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::float_cmp)]
#[test]
fn default_color_stops_are_pinned() {
let p = AuroraParams::default();
assert_eq!(&p.color_green[..3], &DEFAULT_GREEN);
assert_eq!(&p.color_cyan[..3], &DEFAULT_CYAN);
assert_eq!(&p.color_violet[..3], &DEFAULT_VIOLET);
assert_eq!(p.color_green[3], 0.0);
assert!(p.color_green[1] > p.color_cyan[1]);
assert!(p.color_violet[2] > p.color_green[2]);
}
#[test]
fn tier_constants_match_the_wgsl_contract() {
assert_eq!(AuroraQuality::Off.as_u32(), 0);
assert_eq!(AuroraQuality::Low.as_u32(), 1);
assert_eq!(AuroraQuality::Medium.as_u32(), 2);
assert_eq!(AuroraQuality::High.as_u32(), 3);
assert!(WGSL.contains("const QUALITY_OFF: u32 = 0u"));
assert!(WGSL.contains("const QUALITY_LOW: u32 = 1u"));
assert!(WGSL.contains("const QUALITY_MEDIUM: u32 = 2u"));
assert!(WGSL.contains("const QUALITY_HIGH: u32 = 3u"));
}
#[test]
fn quality_round_trips_and_rejects_out_of_contract_words() {
for q in [
AuroraQuality::Off,
AuroraQuality::Low,
AuroraQuality::Medium,
AuroraQuality::High,
] {
assert_eq!(AuroraQuality::from_u32(q.as_u32()), Some(q));
}
assert_eq!(AuroraQuality::from_u32(4), None);
assert_eq!(AuroraQuality::from_u32(u32::MAX), None);
let p = AuroraParams { tier: [7, 0, 0, 0], ..AuroraParams::default() };
assert_eq!(p.quality(), None);
}
#[allow(clippy::float_cmp)]
#[test]
fn defaults_pin_the_recommended_mado_shape() {
let p = AuroraParams::default();
assert_eq!(p.quality(), Some(AuroraQuality::Medium), "shipped default tier");
assert_eq!(p.frame[1], 0.35, "default opacity — sky dressing, scene reads through");
assert_eq!(p.frame[2], 1.0, "default drift multiplier");
assert_eq!(p.frame[3], 0.5, "default shimmer");
assert_eq!(p.geometry[0], 0.62, "default horizon — above the prompt area");
assert_eq!(p.geometry[1], 2.4, "default band scale");
}
#[allow(clippy::float_cmp)]
#[test]
fn builders_clamp_within_range() {
let p = AuroraParams::default()
.with_intensity(2.0)
.with_drift(-1.0)
.with_shimmer(9.0)
.with_horizon(0.0)
.with_band_scale(100.0);
assert_eq!(p.frame[1], 1.0);
assert_eq!(p.frame[2], 0.0);
assert_eq!(p.frame[3], 1.0);
assert_eq!(p.geometry[0], 0.05);
assert_eq!(p.geometry[1], 8.0);
}
#[test]
fn params_layout_is_96_bytes_of_six_vec4s() {
assert_eq!(size_of::<AuroraParams>(), 96);
assert_eq!(align_of::<AuroraParams>(), 4);
}
#[test]
fn wgsl_pins_the_craft_contract() {
assert!(WGSL.len() > 1000, "aurora.wgsl looks suspiciously small");
assert!(WGSL.contains("@fragment"));
assert!(WGSL.contains("fn vnoise"));
assert!(WGSL.contains("fn fbm"));
assert!(WGSL.contains("fn ray_factor"), "High tier's vertical ray march");
assert!(WGSL.contains("textureSample(input_tex"));
assert!(WGSL.contains("scene.rgb * (1.0 - alpha)"), "premultiplied over");
assert!(WGSL.contains("hash12(in.pos.xy)"), "spatial dither anchor");
assert!(WGSL.contains("quality == QUALITY_OFF || quality > QUALITY_HIGH"));
}
}