Galactica/crates/render/src/starfield.rs
2024-01-10 22:44:22 -08:00

130 lines
3.6 KiB
Rust

use cgmath::{Point2, Point3, Vector2, Vector3};
use galactica_content::Content;
use rand::{self, Rng};
use crate::{
datastructs::RenderState,
vertexbuffer::{types::StarfieldInstance, BufferObject},
};
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: 0,
}
}
pub fn regenerate(&mut self, ct: &Content) {
// TODO: save seed in system, regenerate on jump
let mut rng = rand::thread_rng();
let sz = ct.get_config().starfield_size as f32 / 2.0;
self.stars = (0..ct.get_config().starfield_count)
.map(|_| StarfieldStar {
pos: Point3 {
x: rng.gen_range(-sz..=sz),
y: rng.gen_range(-sz..=sz),
z: rng.gen_range(
ct.get_config().starfield_min_dist..=ct.get_config().starfield_max_dist,
),
},
size: rng.gen_range(
ct.get_config().starfield_min_size..ct.get_config().starfield_max_size,
),
tint: Vector2 {
x: rng.gen_range(0.0..=1.0),
y: rng.gen_range(0.0..=1.0),
},
})
.collect();
}
pub fn update_buffer(&mut self, ct: &Content, state: &mut RenderState) {
let sz = ct.get_config().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((state.window_aspect, 1.0)) * ct.get_config().zoom_max;
// Parallax correction.
// Also, adjust v for mod to work properly
// (v is centered at 0)
let v: Point2<f32> = clip_nw * ct.get_config().starfield_min_dist;
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) * ct.get_config().starfield_count as i32)
> ct.get_config().starfield_instance_limit as i32
{
nw_tile -= Vector2::from((1, 1));
}
// Add all tiles to buffer
self.instance_count = 0; // Keep track of buffer index
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 {
state.queue.write_buffer(
&state.vertex_buffers.starfield.instances,
StarfieldInstance::SIZE * self.instance_count as u64,
bytemuck::cast_slice(&[StarfieldInstance {
position: (s.pos + offset).into(),
size: s.size,
tint: s.tint.into(),
}]),
);
self.instance_count += 1;
// instance_count is guaranteed to stay within buffer limits,
// this is guaranteed by previous checks.
}
}
}
}
}