126 lines
3.4 KiB
Rust
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;
|
|
}
|
|
}
|