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, /// 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, } pub(crate) struct Starfield { stars: Vec, 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 { let sz = STARFIELD_SIZE as f32; // Compute window size in starfield tiles let mut nw_tile: Point2 = { // 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 = clip_nw * STARFIELD_Z_MIN; let v_adj: Point2 = (v.x + (sz / 2.0), v.y + (sz / 2.0)).into(); #[rustfmt::skip] // Compute m = fmod(x, sz) let m: Vector2 = ( (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; } }