130 lines
3.6 KiB
Rust
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.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|