Added fonts & config
parent
188dc9eb89
commit
7e12a0e26d
|
@ -66,6 +66,5 @@ cgmath = "0.18.0"
|
|||
rand = "0.8.5"
|
||||
walkdir = "2.4.0"
|
||||
toml = "0.8.8"
|
||||
|
||||
# Glyphon's crates.io release doesn't support wgpu 0.18 yet
|
||||
glyphon = { git = "https://github.com/grovesNL/glyphon.git", branch = "main" }
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 1674e86c1edcbd119d94516950d2d274b46a19d4
|
||||
Subproject commit 9746acb16c6e2c5f6f232e8d1b53e27fb9ef486e
|
|
@ -0,0 +1,38 @@
|
|||
[config]
|
||||
# Per-game config values.
|
||||
# These should rarely be changed.
|
||||
|
||||
sprite_root = "render"
|
||||
|
||||
fonts.files = [
|
||||
"fonts/PTAstraSans-Bold.ttf",
|
||||
"fonts/PTAstraSans-BoldItalic.ttf",
|
||||
"fonts/PTAstraSans-Italic.ttf",
|
||||
"fonts/PTAstraSans-Regular.ttf",
|
||||
"fonts/PTAstraSerif-Bold.ttf",
|
||||
"fonts/PTAstraSerif-BoldItalic.ttf",
|
||||
"fonts/PTAstraSerif-Italic.ttf",
|
||||
"fonts/PTAstraSerif-Regular.ttf",
|
||||
"fonts/PTMono-Regular.ttf",
|
||||
]
|
||||
|
||||
fonts.serif = "PT Astra Serif"
|
||||
fonts.sans = "PT Astra Sans"
|
||||
fonts.mono = "PT Mono"
|
||||
|
||||
# Size range for starfield stars, in game units.
|
||||
# This is scaled for zoom, but NOT for distance.
|
||||
starfield.min_size = 0.2
|
||||
starfield.max_size = 1.8
|
||||
|
||||
# Z-axis (parallax) range for starfield stars
|
||||
starfield.min_dist = 75.0
|
||||
starfield.max_dist = 200.0
|
||||
# Name of starfield sprite
|
||||
starfield.sprite = "starfield"
|
||||
|
||||
|
||||
# Zoom level bounds.
|
||||
# Zoom is measured as "window height in game units."
|
||||
zoom_min = 200.0
|
||||
zoom_max = 2000.0
|
|
@ -7,7 +7,7 @@ mod handle;
|
|||
mod part;
|
||||
mod util;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
|
@ -28,7 +28,10 @@ mod syntax {
|
|||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, fmt::Display, hash::Hash};
|
||||
|
||||
use crate::part::{effect, faction, outfit, ship, sprite, system};
|
||||
use crate::{
|
||||
config,
|
||||
part::{effect, faction, outfit, ship, sprite, system},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Root {
|
||||
|
@ -38,6 +41,7 @@ mod syntax {
|
|||
pub sprite: Option<HashMap<String, sprite::syntax::Sprite>>,
|
||||
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
|
||||
pub effect: Option<HashMap<String, effect::syntax::Effect>>,
|
||||
pub config: Option<config::syntax::Config>,
|
||||
}
|
||||
|
||||
fn merge_hashmap<K, V>(
|
||||
|
@ -74,6 +78,7 @@ mod syntax {
|
|||
sprite: None,
|
||||
faction: None,
|
||||
effect: None,
|
||||
config: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +94,13 @@ mod syntax {
|
|||
.with_context(|| "while merging factions")?;
|
||||
merge_hashmap(&mut self.effect, other.effect)
|
||||
.with_context(|| "while merging effects")?;
|
||||
if self.config.is_some() {
|
||||
if other.config.is_some() {
|
||||
bail!("invalid content dir, multiple config tables")
|
||||
}
|
||||
} else {
|
||||
self.config = other.config;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
@ -124,12 +136,6 @@ impl ContentBuildContext {
|
|||
/// Represents static game content
|
||||
#[derive(Debug)]
|
||||
pub struct Content {
|
||||
/* Configuration values */
|
||||
/// Root directory for image
|
||||
image_root: PathBuf,
|
||||
/// Name of starfield sprite
|
||||
starfield_sprite_name: String,
|
||||
|
||||
/// Sprites
|
||||
pub sprites: Vec<Sprite>,
|
||||
/// Map strings to texture names.
|
||||
|
@ -147,6 +153,7 @@ pub struct Content {
|
|||
systems: Vec<System>,
|
||||
factions: Vec<Faction>,
|
||||
effects: Vec<Effect>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
// Loading methods
|
||||
|
@ -159,12 +166,7 @@ impl Content {
|
|||
}
|
||||
|
||||
/// Load content from a directory.
|
||||
pub fn load_dir(
|
||||
path: PathBuf,
|
||||
texture_root: PathBuf,
|
||||
atlas_index: PathBuf,
|
||||
starfield_texture_name: String,
|
||||
) -> Result<Self> {
|
||||
pub fn load_dir(path: PathBuf, asset_root: PathBuf, atlas_index: PathBuf) -> Result<Self> {
|
||||
let mut root = syntax::Root::new();
|
||||
|
||||
for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
|
||||
|
@ -202,6 +204,15 @@ impl Content {
|
|||
let mut build_context = ContentBuildContext::new();
|
||||
|
||||
let mut content = Self {
|
||||
config: {
|
||||
if let Some(c) = root.config {
|
||||
c.build(&asset_root)
|
||||
.with_context(|| "while parsing config table")?
|
||||
} else {
|
||||
bail!("failed loading content: no config table specified")
|
||||
}
|
||||
},
|
||||
|
||||
sprite_atlas: atlas,
|
||||
systems: Vec::new(),
|
||||
ships: Vec::new(),
|
||||
|
@ -212,8 +223,6 @@ impl Content {
|
|||
effects: Vec::new(),
|
||||
sprite_index: HashMap::new(),
|
||||
starfield_handle: None,
|
||||
image_root: texture_root,
|
||||
starfield_sprite_name: starfield_texture_name,
|
||||
};
|
||||
|
||||
// TODO: enforce sprite and image limits
|
||||
|
@ -333,4 +342,9 @@ impl Content {
|
|||
pub fn get_effect(&self, h: EffectHandle) -> &Effect {
|
||||
return &self.effects[h.index];
|
||||
}
|
||||
|
||||
/// Get content configuration
|
||||
pub fn get_config(&self) -> &Config {
|
||||
return &self.config;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use anyhow::{bail, Result};
|
||||
use serde::Deserialize;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// Raw serde syntax structs.
|
||||
// These are never seen by code outside this crate.
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub fonts: Fonts,
|
||||
pub sprite_root: PathBuf,
|
||||
pub starfield: Starfield,
|
||||
pub zoom_min: f32,
|
||||
pub zoom_max: f32,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// TODO: clean up build trait
|
||||
pub fn build(self, asset_root: &Path) -> Result<super::Config> {
|
||||
for i in &self.fonts.files {
|
||||
if !asset_root.join(i).exists() {
|
||||
bail!("font file `{}` doesn't exist", i.display());
|
||||
}
|
||||
}
|
||||
|
||||
let starfield_density = 0.01;
|
||||
let starfield_size = self.starfield.min_dist * self.zoom_max;
|
||||
let starfield_count = (starfield_size * starfield_density) as i32;
|
||||
|
||||
// 12, because that should be enough to tile any screen.
|
||||
// Starfield squares are tiled to cover the viewport, adapting to any screen ratio.
|
||||
// An insufficient limit will result in some tiles not being drawn
|
||||
let starfield_instance_limit = 12 * starfield_count as u64;
|
||||
|
||||
return Ok(super::Config {
|
||||
sprite_root: asset_root.join(self.sprite_root),
|
||||
font_files: self
|
||||
.fonts
|
||||
.files
|
||||
.iter()
|
||||
.map(|i| asset_root.join(i))
|
||||
.collect(),
|
||||
|
||||
font_sans: self.fonts.sans,
|
||||
font_serif: self.fonts.serif,
|
||||
font_mono: self.fonts.mono,
|
||||
//font_cursive: self.fonts.cursive,
|
||||
//font_fantasy: self.fonts.fantasy,
|
||||
starfield_max_dist: self.starfield.max_dist,
|
||||
starfield_min_dist: self.starfield.min_dist,
|
||||
starfield_max_size: self.starfield.max_size,
|
||||
starfield_min_size: self.starfield.min_size,
|
||||
starfield_sprite: self.starfield.sprite,
|
||||
starfield_count,
|
||||
starfield_density,
|
||||
starfield_size,
|
||||
starfield_instance_limit,
|
||||
zoom_max: self.zoom_max,
|
||||
zoom_min: self.zoom_min,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Fonts {
|
||||
pub files: Vec<PathBuf>,
|
||||
pub sans: String,
|
||||
pub serif: String,
|
||||
pub mono: String,
|
||||
//pub cursive: String,
|
||||
//pub fantasy: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Starfield {
|
||||
pub min_size: f32,
|
||||
pub max_size: f32,
|
||||
pub min_dist: f32,
|
||||
pub max_dist: f32,
|
||||
pub sprite: String,
|
||||
}
|
||||
}
|
||||
|
||||
/// Content configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
/// The directory where all images are stored.
|
||||
/// Image paths are always interpreted relative to this path.
|
||||
/// This is a subdirectory of the asset root.
|
||||
pub sprite_root: PathBuf,
|
||||
|
||||
/// List of font files to load
|
||||
pub font_files: Vec<PathBuf>,
|
||||
|
||||
/// Sans Serif font family name
|
||||
pub font_sans: String,
|
||||
|
||||
/// Serif font family name
|
||||
pub font_serif: String,
|
||||
|
||||
/// Monospace font family name
|
||||
pub font_mono: String,
|
||||
//pub font_cursive: String,
|
||||
//pub font_fantasy: String,
|
||||
/// Min size of starfield sprite, in game units
|
||||
pub starfield_min_size: f32,
|
||||
|
||||
/// Max size of starfield sprite, in game units
|
||||
pub starfield_max_size: f32,
|
||||
|
||||
/// Minimum z-distance of starfield star, in game units
|
||||
pub starfield_min_dist: f32,
|
||||
|
||||
/// Maximum z-distance of starfield star, in game units
|
||||
pub starfield_max_dist: f32,
|
||||
|
||||
/// Name of starfield sprite
|
||||
pub starfield_sprite: String,
|
||||
|
||||
/// Size of a square starfield tile, in game units.
|
||||
/// A tile of size STARFIELD_Z_MAX * screen-size-in-game-units
|
||||
/// will completely cover a (square) screen.
|
||||
/// This should be big enough to cover the height of the screen at max zoom.
|
||||
pub starfield_size: f32,
|
||||
|
||||
/// Average number of stars per game unit
|
||||
pub starfield_density: f32,
|
||||
|
||||
/// Number of stars in one starfield tile
|
||||
/// Must be positive
|
||||
pub starfield_count: i32,
|
||||
|
||||
// TODO: this shouldn't be here, it depends on graphics implementation
|
||||
/// The maximum number of starfield sprites we can create
|
||||
pub starfield_instance_limit: u64,
|
||||
|
||||
/// Minimum zoom, in game units
|
||||
pub zoom_min: f32,
|
||||
|
||||
/// Maximum zoom,in game units
|
||||
pub zoom_max: f32,
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
//! Content parts
|
||||
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod effect;
|
||||
pub(crate) mod faction;
|
||||
pub(crate) mod outfit;
|
||||
|
@ -8,6 +9,7 @@ pub(crate) mod ship;
|
|||
pub(crate) mod sprite;
|
||||
pub(crate) mod system;
|
||||
|
||||
pub use config::Config;
|
||||
pub use effect::Effect;
|
||||
pub use faction::{Faction, Relationship};
|
||||
pub use outfit::{Gun, Outfit, Projectile, ProjectileCollider};
|
||||
|
|
|
@ -120,7 +120,7 @@ impl crate::Build for Sprite {
|
|||
for (sprite_name, t) in sprites {
|
||||
match t {
|
||||
syntax::Sprite::Static(t) => {
|
||||
let file = content.image_root.join(&t.file);
|
||||
let file = content.config.sprite_root.join(&t.file);
|
||||
let reader = Reader::open(&file).with_context(|| {
|
||||
format!(
|
||||
"Failed to read file `{}` in sprite `{}`",
|
||||
|
@ -141,7 +141,7 @@ impl crate::Build for Sprite {
|
|||
aspect: dim.0 as f32 / dim.1 as f32,
|
||||
};
|
||||
|
||||
if sprite_name == content.starfield_sprite_name {
|
||||
if sprite_name == content.config.starfield_sprite {
|
||||
if content.starfield_handle.is_none() {
|
||||
content.starfield_handle = Some(h)
|
||||
} else {
|
||||
|
@ -166,7 +166,7 @@ impl crate::Build for Sprite {
|
|||
syntax::Sprite::Frames(t) => {
|
||||
let mut dim = None;
|
||||
for f in &t.frames {
|
||||
let file = content.image_root.join(f);
|
||||
let file = content.config.sprite_root.join(f);
|
||||
let reader = Reader::open(&file).with_context(|| {
|
||||
format!(
|
||||
"Failed to read file `{}` in sprite `{}`",
|
||||
|
@ -200,7 +200,7 @@ impl crate::Build for Sprite {
|
|||
aspect: dim.0 as f32 / dim.1 as f32,
|
||||
};
|
||||
|
||||
if sprite_name == content.starfield_sprite_name {
|
||||
if sprite_name == content.config.starfield_sprite {
|
||||
unreachable!("Starfield texture may not be animated")
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ impl crate::Build for Sprite {
|
|||
if content.starfield_handle.is_none() {
|
||||
bail!(
|
||||
"Could not find a starfield texture (name: `{}`)",
|
||||
content.starfield_sprite_name
|
||||
content.config.starfield_sprite
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle};
|
||||
use galactica_galaxy::{ship::ShipPersonality, Galaxy, GxShipHandle};
|
||||
use galactica_util::{
|
||||
constants::{ZOOM_MAX, ZOOM_MIN},
|
||||
timing::Timing,
|
||||
};
|
||||
use galactica_util::timing::Timing;
|
||||
use std::time::Instant;
|
||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
||||
|
||||
|
@ -21,8 +18,9 @@ pub struct Game {
|
|||
start_instant: Instant,
|
||||
camera: Camera,
|
||||
|
||||
galaxy: Galaxy,
|
||||
content: Content,
|
||||
ct: Content,
|
||||
gx: Galaxy,
|
||||
|
||||
systemsim: SystemSim,
|
||||
|
||||
new_particles: Vec<ParticleBuilder>,
|
||||
|
@ -86,8 +84,8 @@ impl Game {
|
|||
paused: false,
|
||||
time_scale: 1.0,
|
||||
systemsim: physics,
|
||||
galaxy,
|
||||
content: ct,
|
||||
gx: galaxy,
|
||||
ct,
|
||||
new_particles: Vec::new(),
|
||||
timing: Timing::new(),
|
||||
}
|
||||
|
@ -123,7 +121,7 @@ impl Game {
|
|||
|
||||
self.timing.start_frame();
|
||||
self.timing.start_galaxy();
|
||||
self.galaxy.step(t);
|
||||
self.gx.step(t);
|
||||
self.timing.mark_galaxy();
|
||||
|
||||
self.systemsim.step(StepResources {
|
||||
|
@ -134,15 +132,16 @@ impl Game {
|
|||
thrust: self.input.key_thrust,
|
||||
guns: self.input.key_guns,
|
||||
},
|
||||
ct: &self.content,
|
||||
gx: &mut self.galaxy,
|
||||
ct: &self.ct,
|
||||
gx: &mut self.gx,
|
||||
particles: &mut self.new_particles,
|
||||
timing: &mut self.timing,
|
||||
t,
|
||||
});
|
||||
|
||||
if self.input.v_scroll != 0.0 {
|
||||
self.camera.zoom = (self.camera.zoom + self.input.v_scroll).clamp(ZOOM_MIN, ZOOM_MAX);
|
||||
self.camera.zoom = (self.camera.zoom + self.input.v_scroll)
|
||||
.clamp(self.ct.get_config().zoom_min, self.ct.get_config().zoom_max);
|
||||
self.input.v_scroll = 0.0;
|
||||
}
|
||||
|
||||
|
@ -161,13 +160,17 @@ impl Game {
|
|||
camera_pos: self.camera.pos,
|
||||
camera_zoom: self.camera.zoom,
|
||||
current_time: self.start_instant.elapsed().as_secs_f32(),
|
||||
content: &self.content,
|
||||
ct: &self.ct,
|
||||
systemsim: &self.systemsim, // TODO: maybe system should be stored here?
|
||||
particles: &mut self.new_particles,
|
||||
player_data: self.player,
|
||||
data: &self.galaxy,
|
||||
gx: &self.gx,
|
||||
current_system: SystemHandle { index: 0 },
|
||||
timing: &mut self.timing,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_content(&self) -> &Content {
|
||||
&self.ct
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ mod inputstatus;
|
|||
|
||||
use anyhow::{bail, Result};
|
||||
use galactica_content::Content;
|
||||
use galactica_util::constants::{ASSET_CACHE, CONTENT_ROOT, IMAGE_ROOT, STARFIELD_SPRITE_NAME};
|
||||
use galactica_util::constants::ASSET_CACHE;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
|
@ -27,19 +27,17 @@ fn main() -> Result<()> {
|
|||
|
||||
// TODO: pretty error if missing
|
||||
let content = Content::load_dir(
|
||||
PathBuf::from(CONTENT_ROOT),
|
||||
PathBuf::from(IMAGE_ROOT),
|
||||
PathBuf::from("./content"),
|
||||
PathBuf::from("./assets"),
|
||||
atlas_index,
|
||||
STARFIELD_SPRITE_NAME.to_owned(),
|
||||
)?;
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?;
|
||||
gpu.init();
|
||||
gpu.init(&content);
|
||||
|
||||
let mut game = game::Game::new(content);
|
||||
gpu.update_starfield_buffer();
|
||||
game.set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
|
||||
);
|
||||
|
@ -49,7 +47,7 @@ fn main() -> Result<()> {
|
|||
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
|
||||
match gpu.render(game.get_frame_state()) {
|
||||
Ok(_) => {}
|
||||
Err(wgpu::SurfaceError::Lost) => gpu.resize(),
|
||||
Err(wgpu::SurfaceError::Lost) => gpu.resize(game.get_content()),
|
||||
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||
// All other errors (Outdated, Timeout) should be resolved by the next frame
|
||||
Err(e) => eprintln!("{:?}", e),
|
||||
|
@ -85,14 +83,14 @@ fn main() -> Result<()> {
|
|||
game.process_scroll(delta, phase);
|
||||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
gpu.resize();
|
||||
gpu.resize(game.get_content());
|
||||
game.set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32
|
||||
/ gpu.window().inner_size().height as f32,
|
||||
);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { .. } => {
|
||||
gpu.resize();
|
||||
gpu.resize(game.get_content());
|
||||
game.set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32
|
||||
/ gpu.window().inner_size().height as f32,
|
||||
|
|
|
@ -32,10 +32,10 @@ pub struct RenderInput<'a> {
|
|||
pub current_time: f32,
|
||||
|
||||
/// Game content
|
||||
pub content: &'a Content,
|
||||
pub ct: &'a Content,
|
||||
|
||||
/// Game data
|
||||
pub data: &'a Galaxy,
|
||||
pub gx: &'a Galaxy,
|
||||
|
||||
/// Particles to spawn during this frame
|
||||
pub particles: &'a mut Vec<ParticleBuilder>,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use bytemuck;
|
||||
use galactica_content::Content;
|
||||
use wgpu;
|
||||
use winit;
|
||||
|
||||
|
@ -40,7 +41,7 @@ impl GPUState {
|
|||
|
||||
/// Update window size.
|
||||
/// This should be called whenever our window is resized.
|
||||
pub fn resize(&mut self) {
|
||||
pub fn resize(&mut self, ct: &Content) {
|
||||
let new_size = self.state.window.inner_size();
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.state.window_size = new_size;
|
||||
|
@ -49,23 +50,11 @@ impl GPUState {
|
|||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
}
|
||||
self.update_starfield_buffer()
|
||||
}
|
||||
|
||||
/// Make a StarfieldInstance for each star that needs to be drawn.
|
||||
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
|
||||
///
|
||||
/// Starfield data rarely changes, so this is called only when it's needed.
|
||||
pub fn update_starfield_buffer(&mut self) {
|
||||
self.state.queue.write_buffer(
|
||||
&self.state.vertex_buffers.starfield.instances,
|
||||
0,
|
||||
bytemuck::cast_slice(&self.starfield.make_instances(self.state.window_aspect)),
|
||||
);
|
||||
self.starfield.update_buffer(ct, &mut self.state);
|
||||
}
|
||||
|
||||
/// Initialize the rendering engine
|
||||
pub fn init(&mut self) {
|
||||
pub fn init(&mut self, ct: &Content) {
|
||||
// Update global values
|
||||
self.state.queue.write_buffer(
|
||||
&self.state.global_uniform.atlas_buffer,
|
||||
|
@ -78,6 +67,6 @@ impl GPUState {
|
|||
bytemuck::cast_slice(&[self.texture_array.sprite_data]),
|
||||
);
|
||||
|
||||
self.update_starfield_buffer();
|
||||
self.starfield.update_buffer(ct, &mut self.state);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use galactica_content::Content;
|
||||
use galactica_util::constants::{
|
||||
OBJECT_SPRITE_INSTANCE_LIMIT, PARTICLE_SPRITE_INSTANCE_LIMIT, STARFIELD_SPRITE_INSTANCE_LIMIT,
|
||||
UI_SPRITE_INSTANCE_LIMIT,
|
||||
OBJECT_SPRITE_INSTANCE_LIMIT, PARTICLE_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT,
|
||||
};
|
||||
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
||||
use std::rc::Rc;
|
||||
|
@ -110,7 +109,7 @@ impl GPUState {
|
|||
&device,
|
||||
Some(SPRITE_VERTICES),
|
||||
Some(SPRITE_INDICES),
|
||||
STARFIELD_SPRITE_INSTANCE_LIMIT,
|
||||
ct.get_config().starfield_instance_limit,
|
||||
)),
|
||||
|
||||
ui: Rc::new(VertexBuffer::new::<TexturedVertex, UiInstance>(
|
||||
|
@ -150,7 +149,32 @@ impl GPUState {
|
|||
|
||||
// Text renderer
|
||||
let mut text_atlas = TextAtlas::new(&device, &queue, wgpu::TextureFormat::Bgra8UnormSrgb);
|
||||
let text_font_system = FontSystem::new();
|
||||
let mut text_font_system = FontSystem::new_with_locale_and_db(
|
||||
"en-US".to_string(),
|
||||
glyphon::fontdb::Database::new(),
|
||||
);
|
||||
|
||||
let conf = ct.get_config();
|
||||
for font in &conf.font_files {
|
||||
text_font_system.db_mut().load_font_file(font)?;
|
||||
}
|
||||
|
||||
// TODO: nice error if no family with this name is found
|
||||
text_font_system
|
||||
.db_mut()
|
||||
.set_sans_serif_family(conf.font_sans.clone());
|
||||
text_font_system
|
||||
.db_mut()
|
||||
.set_serif_family(conf.font_serif.clone());
|
||||
text_font_system
|
||||
.db_mut()
|
||||
.set_monospace_family(conf.font_mono.clone());
|
||||
//text_font_system
|
||||
// .db_mut()
|
||||
// .set_cursive_family(conf.font_cursive.clone());
|
||||
//text_font_system
|
||||
// .db_mut()
|
||||
// .set_fantasy_family(conf.font_fantasy.clone());
|
||||
|
||||
let text_cache = SwashCache::new();
|
||||
let text_renderer = TextRenderer::new(
|
||||
|
@ -238,7 +262,7 @@ impl GPUState {
|
|||
.build();
|
||||
|
||||
let mut starfield = Starfield::new();
|
||||
starfield.regenerate();
|
||||
starfield.regenerate(ct);
|
||||
|
||||
let mut state = RenderState {
|
||||
queue,
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use bytemuck;
|
||||
use cgmath::Point2;
|
||||
use galactica_util::constants::{
|
||||
PARTICLE_SPRITE_INSTANCE_LIMIT, STARFIELD_SIZE, STARFIELD_SIZE_MAX, STARFIELD_SIZE_MIN,
|
||||
ZOOM_MAX, ZOOM_MIN,
|
||||
};
|
||||
use galactica_util::constants::PARTICLE_SPRITE_INSTANCE_LIMIT;
|
||||
use glyphon::Resolution;
|
||||
use rand::seq::SliceRandom;
|
||||
use std::iter;
|
||||
|
@ -56,7 +53,7 @@ impl super::GPUState {
|
|||
self.state.vertex_buffers.radialbar_counter = 0;
|
||||
// Don't reset particle counter, it's special
|
||||
|
||||
let s = input.content.get_starfield_handle();
|
||||
let s = input.ct.get_starfield_handle();
|
||||
|
||||
// Update global values
|
||||
self.state.queue.write_buffer(
|
||||
|
@ -65,7 +62,10 @@ impl super::GPUState {
|
|||
bytemuck::cast_slice(&[GlobalDataContent {
|
||||
camera_position: input.camera_pos.into(),
|
||||
camera_zoom: [input.camera_zoom, 0.0],
|
||||
camera_zoom_limits: [ZOOM_MIN, ZOOM_MAX],
|
||||
camera_zoom_limits: [
|
||||
input.ct.get_config().zoom_min,
|
||||
input.ct.get_config().zoom_max,
|
||||
],
|
||||
window_size: [
|
||||
self.state.window_size.width as f32,
|
||||
self.state.window_size.height as f32,
|
||||
|
@ -73,8 +73,11 @@ impl super::GPUState {
|
|||
window_scale: [self.state.window.scale_factor() as f32, 0.0],
|
||||
window_aspect: [self.state.window_aspect, 0.0],
|
||||
starfield_sprite: [s.get_index(), 0],
|
||||
starfield_tile_size: [STARFIELD_SIZE as f32, 0.0],
|
||||
starfield_size_limits: [STARFIELD_SIZE_MIN, STARFIELD_SIZE_MAX],
|
||||
starfield_tile_size: [input.ct.get_config().starfield_size, 0.0],
|
||||
starfield_size_limits: [
|
||||
input.ct.get_config().starfield_min_size,
|
||||
input.ct.get_config().starfield_max_size,
|
||||
],
|
||||
current_time: [input.current_time, 0.0],
|
||||
}]),
|
||||
);
|
||||
|
|
|
@ -23,7 +23,7 @@ impl GPUState {
|
|||
let ship_pos = util::rigidbody_position(&r);
|
||||
let ship_rot = util::rigidbody_rotation(r);
|
||||
let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix!
|
||||
let ship_cnt = state.content.get_ship(s.data_handle.content_handle());
|
||||
let ship_cnt = state.ct.get_ship(s.data_handle.content_handle());
|
||||
|
||||
// Position adjusted for parallax
|
||||
// TODO: adjust parallax for zoom?
|
||||
|
@ -83,12 +83,12 @@ impl GPUState {
|
|||
// This will be None if this ship is dead.
|
||||
// (physics object stays around to complete the death animation)
|
||||
// If that is the case, we're done, no flares to draw anyway!
|
||||
let ship = match state.data.get_ship(s.data_handle) {
|
||||
let ship = match state.gx.get_ship(s.data_handle) {
|
||||
None => continue,
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
let flare = ship.get_outfits().get_flare_sprite(state.content);
|
||||
let flare = ship.get_outfits().get_flare_sprite(state.ct);
|
||||
if s.get_controls().thrust && flare.is_some() {
|
||||
for engine_point in &ship_cnt.engines {
|
||||
self.state.queue.write_buffer(
|
||||
|
@ -204,7 +204,7 @@ impl GPUState {
|
|||
// NE and SW corners of screen
|
||||
screen_clip: (Point2<f32>, Point2<f32>),
|
||||
) {
|
||||
let system = state.content.get_system(state.current_system);
|
||||
let system = state.ct.get_system(state.current_system);
|
||||
|
||||
for o in &system.objects {
|
||||
// Position adjusted for parallax
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
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 galactica_content::Content;
|
||||
use rand::{self, Rng};
|
||||
|
||||
use crate::vertexbuffer::types::StarfieldInstance;
|
||||
use crate::{
|
||||
datastructs::RenderState,
|
||||
vertexbuffer::{types::StarfieldInstance, BufferObject},
|
||||
};
|
||||
|
||||
pub(crate) struct StarfieldStar {
|
||||
/// Star coordinates, in world space.
|
||||
|
@ -30,22 +30,26 @@ impl Starfield {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
stars: Vec::new(),
|
||||
instance_count: 0u32,
|
||||
instance_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn regenerate(&mut self) {
|
||||
pub fn regenerate(&mut self, ct: &Content) {
|
||||
// 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)
|
||||
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(STARFIELD_Z_MIN..STARFIELD_Z_MAX),
|
||||
z: rng.gen_range(
|
||||
ct.get_config().starfield_min_dist..=ct.get_config().starfield_max_dist,
|
||||
),
|
||||
},
|
||||
size: rng.gen_range(STARFIELD_SIZE_MIN..STARFIELD_SIZE_MAX),
|
||||
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),
|
||||
|
@ -54,18 +58,18 @@ impl Starfield {
|
|||
.collect();
|
||||
}
|
||||
|
||||
pub fn make_instances(&mut self, aspect: f32) -> Vec<StarfieldInstance> {
|
||||
let sz = STARFIELD_SIZE as f32;
|
||||
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((aspect, 1.0)) * ZOOM_MAX;
|
||||
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 * STARFIELD_Z_MIN;
|
||||
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]
|
||||
|
@ -89,14 +93,14 @@ impl Starfield {
|
|||
|
||||
// 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
|
||||
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
|
||||
let mut instances = Vec::new();
|
||||
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 {
|
||||
|
@ -105,21 +109,21 @@ impl Starfield {
|
|||
z: 0.0,
|
||||
};
|
||||
for s in &self.stars {
|
||||
instances.push(StarfieldInstance {
|
||||
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;
|
||||
|
||||
// Enforce starfield limit
|
||||
if instances.len() as u64 > STARFIELD_SPRITE_INSTANCE_LIMIT {
|
||||
unreachable!("Starfield limit exceeded!")
|
||||
// instance_count is guaranteed to stay within buffer limits,
|
||||
// this is guaranteed by previous checks.
|
||||
}
|
||||
}
|
||||
}
|
||||
self.instance_count = instances.len() as u32;
|
||||
|
||||
return instances;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ pub(super) struct FpsIndicator {
|
|||
|
||||
impl FpsIndicator {
|
||||
pub fn new(state: &mut RenderState) -> Self {
|
||||
let mut buffer = Buffer::new(&mut state.text_font_system, Metrics::new(12.0, 20.0));
|
||||
let mut buffer = Buffer::new(&mut state.text_font_system, Metrics::new(15.0, 20.0));
|
||||
buffer.set_size(
|
||||
&mut state.text_font_system,
|
||||
state.window_size.width as f32,
|
||||
|
@ -36,7 +36,7 @@ impl FpsIndicator {
|
|||
self.buffer.set_text(
|
||||
&mut state.text_font_system,
|
||||
&format!(
|
||||
"Frame: {:04.02?}%\nGame: {:04.02?}%\nShips: {:04.02?}%\nPhys: {:04.02?}%\nRender: {:.02?}",
|
||||
"Frame: {:05.02?}%\nGame: {:05.02?}%\nShips: {:05.02?}%\nPhys: {:05.02?}%\nRender: {:.02?}",
|
||||
100.0 * (input.timing.frame / input.timing.render),
|
||||
100.0 * (input.timing.galaxy / input.timing.frame),
|
||||
100.0 * (input.timing.physics_sim / input.timing.frame),
|
||||
|
|
|
@ -32,9 +32,9 @@ impl Radar {
|
|||
.unwrap();
|
||||
|
||||
let player_position = util::rigidbody_position(player_body);
|
||||
let planet_sprite = input.content.get_sprite_handle("ui::planetblip");
|
||||
let ship_sprite = input.content.get_sprite_handle("ui::shipblip");
|
||||
let arrow_sprite = input.content.get_sprite_handle("ui::centerarrow");
|
||||
let planet_sprite = input.ct.get_sprite_handle("ui::planetblip");
|
||||
let ship_sprite = input.ct.get_sprite_handle("ui::shipblip");
|
||||
let arrow_sprite = input.ct.get_sprite_handle("ui::centerarrow");
|
||||
|
||||
// Enforce buffer limit
|
||||
if state.vertex_buffers.ui_counter as u64 > UI_SPRITE_INSTANCE_LIMIT {
|
||||
|
@ -52,13 +52,13 @@ impl Radar {
|
|||
angle: 0.0,
|
||||
size: radar_size,
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
sprite_index: input.content.get_sprite_handle("ui::radar").get_index(),
|
||||
sprite_index: input.ct.get_sprite_handle("ui::radar").get_index(),
|
||||
}]),
|
||||
);
|
||||
state.vertex_buffers.ui_counter += 1;
|
||||
|
||||
// Draw system objects
|
||||
let system = input.content.get_system(input.current_system);
|
||||
let system = input.ct.get_system(input.current_system);
|
||||
for o in &system.objects {
|
||||
let size = (o.size / o.pos.z) / (radar_range * system_object_scale);
|
||||
let p = Point2 {
|
||||
|
@ -107,17 +107,17 @@ impl Radar {
|
|||
for (s, r) in input.systemsim.iter_ship_body() {
|
||||
// This will be None if this ship is dead.
|
||||
// Stays around while the physics system runs a collapse sequence
|
||||
let color = match input.data.get_ship(s.data_handle) {
|
||||
let color = match input.gx.get_ship(s.data_handle) {
|
||||
None => {
|
||||
// TODO: configurable
|
||||
[0.2, 0.2, 0.2, 1.0]
|
||||
}
|
||||
Some(data) => {
|
||||
let c = input.content.get_faction(data.get_faction()).color;
|
||||
let c = input.ct.get_faction(data.get_faction()).color;
|
||||
[c[0], c[1], c[2], 1.0]
|
||||
}
|
||||
};
|
||||
let ship = input.content.get_ship(s.data_handle.content_handle());
|
||||
let ship = input.ct.get_ship(s.data_handle.content_handle());
|
||||
let size = (ship.size * ship.sprite.aspect) * ship_scale;
|
||||
let p = util::rigidbody_position(r);
|
||||
let d = (p - player_position) / radar_range;
|
||||
|
@ -167,7 +167,7 @@ impl Radar {
|
|||
let d = d * (radar_size / 2.0);
|
||||
let color = [0.3, 0.3, 0.3, 1.0];
|
||||
if m < 0.8 {
|
||||
let sprite = input.content.get_sprite_handle("ui::radarframe");
|
||||
let sprite = input.ct.get_sprite_handle("ui::radarframe");
|
||||
let size = 7.0f32.min((0.8 - m) * 70.0);
|
||||
|
||||
// Enforce buffer limit (this section adds four items)
|
||||
|
|
|
@ -27,14 +27,11 @@ impl Status {
|
|||
|
||||
let player_world_object = input.systemsim.get_ship(input.player_data).unwrap();
|
||||
|
||||
let data = input
|
||||
.data
|
||||
.get_ship(player_world_object.data_handle)
|
||||
.unwrap();
|
||||
let data = input.gx.get_ship(player_world_object.data_handle).unwrap();
|
||||
let max_shields = data.get_outfits().get_shield_strength();
|
||||
let current_shields = data.get_shields();
|
||||
let current_hull = data.get_hull();
|
||||
let max_hull = input.content.get_ship(data.get_content()).hull;
|
||||
let max_hull = input.ct.get_ship(data.get_content()).hull;
|
||||
|
||||
state.queue.write_buffer(
|
||||
&state.vertex_buffers.ui.instances,
|
||||
|
@ -45,7 +42,7 @@ impl Status {
|
|||
angle: 0.0,
|
||||
size: 200.0,
|
||||
color: [1.0, 1.0, 1.0, 1.0],
|
||||
sprite_index: input.content.get_sprite_handle("ui::status").get_index(),
|
||||
sprite_index: input.ct.get_sprite_handle("ui::status").get_index(),
|
||||
}]),
|
||||
);
|
||||
state.vertex_buffers.ui_counter += 1;
|
||||
|
|
|
@ -2,48 +2,6 @@
|
|||
|
||||
// TODO: many of these should be moved to a config file or cli option
|
||||
|
||||
/// Minimum zoom level
|
||||
pub const ZOOM_MIN: f32 = 200.0;
|
||||
/// Maximum zoom level
|
||||
pub const ZOOM_MAX: f32 = 2000.0;
|
||||
|
||||
/// Z-axis range for starfield stars
|
||||
/// This does not affect scale.
|
||||
pub const STARFIELD_Z_MIN: f32 = 75.0;
|
||||
/// Z-axis range for starfield stars
|
||||
/// This does not affect scale.
|
||||
pub const STARFIELD_Z_MAX: f32 = 200.0;
|
||||
|
||||
/// Size range for starfield stars, in game units.
|
||||
/// This is scaled for zoom, but NOT for distance.
|
||||
pub const STARFIELD_SIZE_MIN: f32 = 0.2;
|
||||
/// Size range for starfield stars, in game units.
|
||||
/// This is scaled for zoom, but NOT for distance.
|
||||
pub const STARFIELD_SIZE_MAX: f32 = 1.8;
|
||||
|
||||
/// Size of a square starfield tile, in game units.
|
||||
/// A tile of size STARFIELD_Z_MAX * screen-size-in-game-units
|
||||
/// will completely cover a (square) screen. This depends on zoom!
|
||||
///
|
||||
/// Use a value smaller than zoom_max for debug.
|
||||
pub const STARFIELD_SIZE: u64 = STARFIELD_Z_MAX as u64 * ZOOM_MAX as u64;
|
||||
|
||||
/// Average number of stars per game unit
|
||||
pub const STARFIELD_DENSITY: f64 = 0.01;
|
||||
|
||||
/// Number of stars in one starfield tile
|
||||
/// Must fit inside an i32
|
||||
pub const STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
|
||||
|
||||
/// Name of starfield sprite
|
||||
pub const STARFIELD_SPRITE_NAME: &'static str = "starfield";
|
||||
|
||||
/// Root directory of game content
|
||||
pub const CONTENT_ROOT: &'static str = "./content";
|
||||
|
||||
/// Root directory of game images
|
||||
pub const IMAGE_ROOT: &'static str = "./assets/render";
|
||||
|
||||
/// We can draw at most this many object sprites on the screen.
|
||||
pub const OBJECT_SPRITE_INSTANCE_LIMIT: u64 = 500;
|
||||
|
||||
|
@ -57,9 +15,6 @@ pub const RADIALBAR_SPRITE_INSTANCE_LIMIT: u64 = 10;
|
|||
/// The size of our circular particle buffer. When we create particles, the oldest ones are replaced.
|
||||
pub const PARTICLE_SPRITE_INSTANCE_LIMIT: u64 = 1000;
|
||||
|
||||
/// Must be small enough to fit in an i32
|
||||
pub const STARFIELD_SPRITE_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 24;
|
||||
|
||||
/// The maximum number of sprites we can define
|
||||
pub const SPRITE_LIMIT: u32 = 1024;
|
||||
|
||||
|
|
Loading…
Reference in New Issue