Galactica/crates/render/src/starfield.rs
2024-01-10 18:53:19 -08:00

126 lines
3.4 KiB
Rust

use cgmath::{Point2, Point3, Vector2, Vector3};
use galactica_util::constants::{
STARFIELD_COUNT, STARFIELD_SIZE, STARFIELD_SIZE_MAX, STARFIELD_SIZE_MIN,
STARFIELD_SPRITE_INSTANCE_LIMIT, STARFIELD_Z_MAX, STARFIELD_Z_MIN, ZOOM_MAX,
};
use rand::{self, Rng};
use crate::vertexbuffer::types::StarfieldInstance;
pub(crate) struct StarfieldStar {
/// Star coordinates, in world space.
/// These are relative to the center of a starfield tile.
pub pos: Point3<f32>,
/// Height in game units.
/// Will be scaled for zoom, but not for distance.
pub size: f32,
/// Color/brightness variation. Random between 0 and 1.
/// Used in starfield shader.
pub tint: Vector2<f32>,
}
pub(crate) struct Starfield {
stars: Vec<StarfieldStar>,
pub instance_count: u32,
}
impl Starfield {
pub fn new() -> Self {
Self {
stars: Vec::new(),
instance_count: 0u32,
}
}
pub fn regenerate(&mut self) {
// TODO: save seed in system, regenerate on jump
let mut rng = rand::thread_rng();
let sz = STARFIELD_SIZE as f32 / 2.0;
self.stars = (0..STARFIELD_COUNT)
.map(|_| StarfieldStar {
pos: Point3 {
x: rng.gen_range(-sz..=sz),
y: rng.gen_range(-sz..=sz),
z: rng.gen_range(STARFIELD_Z_MIN..STARFIELD_Z_MAX),
},
size: rng.gen_range(STARFIELD_SIZE_MIN..STARFIELD_SIZE_MAX),
tint: Vector2 {
x: rng.gen_range(0.0..=1.0),
y: rng.gen_range(0.0..=1.0),
},
})
.collect();
}
pub fn make_instances(&mut self, aspect: f32) -> Vec<StarfieldInstance> {
let sz = STARFIELD_SIZE as f32;
// Compute window size in starfield tiles
let mut nw_tile: Point2<i32> = {
// Game coordinates (relative to camera) of nw corner of screen.
let clip_nw = Point2::from((aspect, 1.0)) * ZOOM_MAX;
// Parallax correction.
// Also, adjust v for mod to work properly
// (v is centered at 0)
let v: Point2<f32> = clip_nw * STARFIELD_Z_MIN;
let v_adj: Point2<f32> = (v.x + (sz / 2.0), v.y + (sz / 2.0)).into();
#[rustfmt::skip]
// Compute m = fmod(x, sz)
let m: Vector2<f32> = (
(v_adj.x - (v_adj.x / sz).floor() * sz) - (sz / 2.0),
(v_adj.y - (v_adj.y / sz).floor() * sz) - (sz / 2.0)
).into();
// Now, remainder and convert to "relative tile" coordinates
// ( where (0,0) is center tile, (0, 1) is north, etc)
let rel = (v - m) / sz;
// relative coordinates of north-east tile
(rel.x.round() as i32, rel.y.round() as i32).into()
};
// We need to cover the window with stars,
// but we also need a one-wide buffer to account for motion.
nw_tile += Vector2::from((1, 1));
// Truncate tile grid to buffer size
// (The window won't be full of stars if our instance limit is too small)
while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * STARFIELD_COUNT as i32)
> STARFIELD_SPRITE_INSTANCE_LIMIT as i32
{
nw_tile -= Vector2::from((1, 1));
}
// Add all tiles to buffer
let mut instances = Vec::new();
for x in (-nw_tile.x)..=nw_tile.x {
for y in (-nw_tile.y)..=nw_tile.y {
let offset = Vector3 {
x: sz * x as f32,
y: sz * y as f32,
z: 0.0,
};
for s in &self.stars {
instances.push(StarfieldInstance {
position: (s.pos + offset).into(),
size: s.size,
tint: s.tint.into(),
})
}
}
}
// Enforce starfield limit
if instances.len() as u64 > STARFIELD_SPRITE_INSTANCE_LIMIT {
unreachable!("Starfield limit exceeded!")
}
self.instance_count = instances.len() as u32;
return instances;
}
}