Compare commits

..

7 Commits

Author SHA1 Message Date
Mark b1de7f37ff
Documentation 2023-12-31 18:59:08 -08:00
Mark 975d2e6590
Minor cleanup 2023-12-31 18:48:35 -08:00
Mark fc728eaf0e
Moved `render` into `crates` 2023-12-31 18:39:37 -08:00
Mark fa8a0f5761
Added basic radar 2023-12-31 17:58:17 -08:00
Mark 8fbbf7f110
Minor edits 2023-12-31 17:57:32 -08:00
Mark 4f6a1b4bf7
Asset reorganization 2023-12-31 17:56:43 -08:00
Mark 48d1fc3093
Updated TODO 2023-12-31 16:48:21 -08:00
38 changed files with 463 additions and 210 deletions

15
Cargo.lock generated
View File

@ -578,10 +578,10 @@ name = "galactica"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytemuck",
"cgmath", "cgmath",
"crossbeam", "crossbeam",
"galactica-content", "galactica-content",
"galactica-render",
"image", "image",
"nalgebra", "nalgebra",
"pollster", "pollster",
@ -605,6 +605,19 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "galactica-render"
version = "0.0.0"
dependencies = [
"anyhow",
"bytemuck",
"cgmath",
"galactica-content",
"image",
"wgpu",
"winit",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.11" version = "0.2.11"

View File

@ -28,26 +28,27 @@ rpath = false
[workspace] [workspace]
members = ["crates/content"] members = ["crates/content", "crates/render"]
[dependencies] [dependencies]
# Internal crates # Internal crates
galactica-content = { path = "crates/content" } galactica-content = { path = "crates/content" }
galactica-render = { path = "crates/render" }
# Files # Files
image = { version = "0.24", features = ["png"] } image = { version = "0.24", features = ["png"] }
# Graphics # Graphics
winit = "0.28" winit = "0.28"
wgpu = "0.18" wgpu = "0.18"
bytemuck = { version = "1.12", features = ["derive"] }
# Physics # Physics
rapier2d = { version = "0.17.2" } #, features = ["parallel"] } rapier2d = { version = "0.17.2" }
nalgebra = "0.32.3" nalgebra = "0.32.3"
crossbeam = "0.8.3" crossbeam = "0.8.3"
# Misc helpers # Misc helpers
pollster = "0.3" pollster = "0.3"
anyhow = "1.0" anyhow = "1.0"
# TODO: migrate to nalgebra
cgmath = "0.18.0" cgmath = "0.18.0"
rand = "0.8.5" rand = "0.8.5"
walkdir = "2.4.0" walkdir = "2.4.0"

10
TODO.md
View File

@ -64,7 +64,10 @@
- Pause game - Pause game
- Better player controller? (only one shipbehavior needs inputs) - Better player controller? (only one shipbehavior needs inputs)
- Clear all `// TODO:` comments littered in the source - Clear all `// TODO:` comments littered in the source
- Config file - CLI options (debug, save location, content location, check content)
- Config file and compile options, remove all those consts.
- Engine flares shouldn't be centered
- Sprite optimization: do we need to allocate a new `Vec` every frame? Probably not.
## Content ## Content
- Angled engines - Angled engines
@ -92,7 +95,6 @@
- Lookahead -> position or direction? - Lookahead -> position or direction?
- Damping? - Damping?
- Important objects affect camera - Important objects affect camera
-
## Visuals ## Visuals
- Particles - Particles
@ -101,6 +103,7 @@
- Zoom parallax (?) - Zoom parallax (?)
- Background haze - Background haze
- Nova dust parallax - Nova dust parallax
- Ship outlines in radar
## Write and Document ## Write and Document
@ -130,4 +133,5 @@
- More interesting trading? - More interesting trading?
- Death penalty - Death penalty
- Find your wreckage when you die (dark souls/HK) - Find your wreckage when you die (dark souls/HK)
- Lose some outfits, lose ship? Real risk for going out! (HK does this well) - Lose some outfits, lose ship? Real risk for going out! (HK does this well)
- Damage to ship subsystems

2
assets

@ -1 +1 @@
Subproject commit 711e4fd58100ee31e41c3c27273f7caa706d8d91 Subproject commit 8205d79e8aa9d7e4976fe8a7794e83819ec19688

View File

@ -18,3 +18,9 @@ path = "projectile/blaster.png"
[texture."ship::gypsum"] [texture."ship::gypsum"]
path = "ship/gypsum.png" path = "ship/gypsum.png"
[texture."ui::radar"]
path = "ui/radar.png"
[texture."ui::blip"]
path = "ui/blip.png"

View File

@ -209,6 +209,12 @@ impl Content {
} }
} }
/// Get a texture from a handle
pub fn get_texture_handle(&self, name: &str) -> TextureHandle {
return *self.texture_index.get(name).unwrap();
// TODO: handle errors
}
/// Get a texture from a handle /// Get a texture from a handle
pub fn get_texture(&self, h: TextureHandle) -> &Texture { pub fn get_texture(&self, h: TextureHandle) -> &Texture {
// In theory, this could fail if h has a bad index, but that shouldn't ever happen. // In theory, this could fail if h has a bad index, but that shouldn't ever happen.

View File

@ -5,10 +5,10 @@ use cgmath::Deg;
use crate::{handle::TextureHandle, Content}; use crate::{handle::TextureHandle, Content};
use super::OutfitSpace; use crate::OutfitSpace;
pub(crate) mod syntax { pub(crate) mod syntax {
use super::super::shared; use crate::part::shared;
use serde::Deserialize; use serde::Deserialize;
// Raw serde syntax structs. // Raw serde syntax structs.
// These are never seen by code outside this crate. // These are never seen by code outside this crate.

View File

@ -2,12 +2,10 @@ use std::collections::HashMap;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use crate::{handle::TextureHandle, Content}; use crate::{handle::TextureHandle, Content, OutfitSpace};
use super::OutfitSpace;
pub(crate) mod syntax { pub(crate) mod syntax {
use super::super::shared; use crate::part::shared;
use serde::Deserialize; use serde::Deserialize;
// Raw serde syntax structs. // Raw serde syntax structs.
// These are never seen by code outside this crate. // These are never seen by code outside this crate.

View File

@ -4,12 +4,10 @@ use anyhow::{bail, Result};
use cgmath::Point2; use cgmath::Point2;
use nalgebra::{point, Point}; use nalgebra::{point, Point};
use crate::{handle::TextureHandle, Content}; use crate::{handle::TextureHandle, Content, OutfitSpace};
use super::OutfitSpace;
pub(crate) mod syntax { pub(crate) mod syntax {
use super::super::shared; use crate::part::shared;
use serde::Deserialize; use serde::Deserialize;
// Raw serde syntax structs. // Raw serde syntax structs.

View File

@ -5,8 +5,8 @@ use std::collections::{HashMap, HashSet};
use crate::{handle::TextureHandle, util::Polar, Content}; use crate::{handle::TextureHandle, util::Polar, Content};
pub(crate) mod syntax { pub(crate) mod syntax {
use super::HashMap;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap;
// Raw serde syntax structs. // Raw serde syntax structs.
// These are never seen by code outside this crate. // These are never seen by code outside this crate.

17
crates/render/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "galactica-render"
version = "0.0.0"
edition = "2021"
[dependencies]
galactica-content = { path = "../content" }
# Misc helpers
anyhow = "1.0"
cgmath = "0.18.0"
# Files
image = { version = "0.24", features = ["png"] }
# Graphics
winit = "0.28"
wgpu = "0.18"
bytemuck = { version = "1.12", features = ["derive"] }

View File

@ -1,4 +1,4 @@
use crate::consts; use crate::consts_main;
use cgmath::Matrix4; use cgmath::Matrix4;
// We can draw at most this many sprites on the screen. // We can draw at most this many sprites on the screen.
@ -6,7 +6,7 @@ use cgmath::Matrix4;
pub const SPRITE_INSTANCE_LIMIT: u64 = 500; pub const SPRITE_INSTANCE_LIMIT: u64 = 500;
// Must be small enough to fit in an i32 // Must be small enough to fit in an i32
pub const STARFIELD_INSTANCE_LIMIT: u64 = consts::STARFIELD_COUNT * 24; pub const STARFIELD_INSTANCE_LIMIT: u64 = consts_main::STARFIELD_COUNT * 24;
/// Shader entry points /// Shader entry points
pub const SHADER_MAIN_VERTEX: &'static str = "vertex_main"; pub const SHADER_MAIN_VERTEX: &'static str = "vertex_main";

View File

@ -0,0 +1,26 @@
pub const ZOOM_MIN: f32 = 200.0;
pub const ZOOM_MAX: f32 = 2000.0;
/// Z-axis range for starfield stars
/// This does not affect scale.
pub const STARFIELD_Z_MIN: f32 = 100.0;
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;
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;

View File

@ -1,34 +1,39 @@
use anyhow::Result; use anyhow::Result;
use bytemuck; use bytemuck;
use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector2, Vector3}; use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector2, Vector3};
use galactica_content::Content;
use std::{iter, rc::Rc}; use std::{iter, rc::Rc};
use wgpu; use wgpu;
use winit::{self, dpi::PhysicalSize, window::Window}; use winit::{self, dpi::LogicalSize, window::Window};
use super::{ use crate::{
consts::{OPENGL_TO_WGPU_MATRIX, SPRITE_INSTANCE_LIMIT, STARFIELD_INSTANCE_LIMIT}, consts::{OPENGL_TO_WGPU_MATRIX, SPRITE_INSTANCE_LIMIT, STARFIELD_INSTANCE_LIMIT},
consts_main, content,
globaldata::{GlobalData, GlobalDataContent}, globaldata::{GlobalData, GlobalDataContent},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
sprite::SubSprite, sprite::ObjectSubSprite,
texturearray::TextureArray, texturearray::TextureArray,
vertexbuffer::{ vertexbuffer::{
consts::{SPRITE_INDICES, SPRITE_VERTICES}, consts::{SPRITE_INDICES, SPRITE_VERTICES},
types::{SpriteInstance, StarfieldInstance, TexturedVertex}, types::{SpriteInstance, StarfieldInstance, TexturedVertex},
VertexBuffer, VertexBuffer,
}, },
Sprite, ObjectSprite, StarfieldStar, UiSprite,
}; };
use crate::{consts, game::Game};
/// A high-level GPU wrapper. Consumes game state,
/// produces pretty pictures.
pub struct GPUState { pub struct GPUState {
/// The window to we draw on
pub window: Window,
/// The size of the window we draw on
pub window_size: winit::dpi::PhysicalSize<u32>,
device: wgpu::Device, device: wgpu::Device,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
surface: wgpu::Surface, surface: wgpu::Surface,
queue: wgpu::Queue, queue: wgpu::Queue,
pub window: Window,
pub window_size: winit::dpi::PhysicalSize<u32>,
window_aspect: f32, window_aspect: f32,
sprite_pipeline: wgpu::RenderPipeline, sprite_pipeline: wgpu::RenderPipeline,
@ -46,7 +51,8 @@ struct VertexBuffers {
} }
impl GPUState { impl GPUState {
pub async fn new(window: Window, ct: &Content) -> Result<Self> { /// Make a new GPUState that draws on `window`
pub async fn new(window: Window, ct: &content::Content) -> Result<Self> {
let window_size = window.inner_size(); let window_size = window.inner_size();
let window_aspect = window_size.width as f32 / window_size.height as f32; let window_aspect = window_size.width as f32 / window_size.height as f32;
@ -141,7 +147,7 @@ impl GPUState {
let sprite_pipeline = PipelineBuilder::new("sprite", &device) let sprite_pipeline = PipelineBuilder::new("sprite", &device)
.set_shader(include_str!(concat!( .set_shader(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"), env!("CARGO_MANIFEST_DIR"),
"/src/render/shaders/", "/shaders/",
"sprite.wgsl" "sprite.wgsl"
))) )))
.set_format(config.format) .set_format(config.format)
@ -153,7 +159,7 @@ impl GPUState {
let starfield_pipeline = PipelineBuilder::new("starfield", &device) let starfield_pipeline = PipelineBuilder::new("starfield", &device)
.set_shader(include_str!(concat!( .set_shader(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"), env!("CARGO_MANIFEST_DIR"),
"/src/render/shaders/", "/shaders/",
"starfield.wgsl" "starfield.wgsl"
))) )))
.set_format(config.format) .set_format(config.format)
@ -182,11 +188,15 @@ impl GPUState {
}); });
} }
/// Get the window this GPUState is attached to
pub fn window(&self) -> &Window { pub fn window(&self) -> &Window {
&self.window &self.window
} }
pub fn resize(&mut self, game: &Game, new_size: PhysicalSize<u32>) { /// Update window size.
/// This should be called whenever our window is resized.
pub fn resize(&mut self, starfield: &Vec<StarfieldStar>) {
let new_size = self.window.inner_size();
if new_size.width > 0 && new_size.height > 0 { if new_size.width > 0 && new_size.height > 0 {
self.window_size = new_size; self.window_size = new_size;
self.window_aspect = new_size.width as f32 / new_size.height as f32; self.window_aspect = new_size.width as f32 / new_size.height as f32;
@ -194,18 +204,19 @@ impl GPUState {
self.config.height = new_size.height; self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
} }
self.update_starfield_buffer(game) self.update_starfield_buffer(starfield)
} }
/// Create a SpriteInstance for s and add it to instances. /// Create a SpriteInstance for an object and add it to `instances`.
/// Also handles child sprites. /// Also handles child sprites.
fn push_sprite( fn push_object_sprite(
&self, &self,
game: &Game, camera_zoom: f32,
camera_pos: Point2<f32>,
instances: &mut Vec<SpriteInstance>, instances: &mut Vec<SpriteInstance>,
clip_ne: Point2<f32>, clip_ne: Point2<f32>,
clip_sw: Point2<f32>, clip_sw: Point2<f32>,
s: Sprite, s: &ObjectSprite,
) { ) {
// Position adjusted for parallax // Position adjusted for parallax
// TODO: adjust parallax for zoom? // TODO: adjust parallax for zoom?
@ -213,7 +224,7 @@ impl GPUState {
(Point2 { (Point2 {
x: s.pos.x, x: s.pos.x,
y: s.pos.y, y: s.pos.y,
} - game.camera.pos.to_vec()) } - camera_pos.to_vec())
/ s.pos.z / s.pos.z
}; };
let texture = self.texture_array.get_texture(s.texture); let texture = self.texture_array.get_texture(s.texture);
@ -237,7 +248,7 @@ impl GPUState {
} }
// TODO: clean up // TODO: clean up
let scale = height / game.camera.zoom; let scale = height / camera_zoom;
// Note that our mesh starts centered at (0, 0). // Note that our mesh starts centered at (0, 0).
// This is essential---we do not want scale and rotation // This is essential---we do not want scale and rotation
@ -266,8 +277,8 @@ impl GPUState {
// The height of the viewport is `zoom` in game units, // The height of the viewport is `zoom` in game units,
// but it's 2 in screen units! (since coordinates range from -1 to 1) // but it's 2 in screen units! (since coordinates range from -1 to 1)
let translate = Matrix4::from_translation(Vector3 { let translate = Matrix4::from_translation(Vector3 {
x: pos.x / (game.camera.zoom / 2.0) / self.window_aspect, x: pos.x / (camera_zoom / 2.0) / self.window_aspect,
y: pos.y / (game.camera.zoom / 2.0), y: pos.y / (camera_zoom / 2.0),
z: 0.0, z: 0.0,
}); });
@ -282,40 +293,40 @@ impl GPUState {
}); });
// Add children // Add children
if let Some(children) = s.children { if let Some(children) = &s.children {
for c in children { for c in children {
self.push_subsprite(game, instances, c, pos, s.angle); self.push_object_subsprite(camera_zoom, instances, c, pos, s.angle);
} }
} }
} }
/// Add a sprite's subsprite to instance. /// Add an object sprite's subsprite to `instances`.
/// Only called by push_sprite. /// Only called by `self.push_object_sprite`.
fn push_subsprite( fn push_object_subsprite(
&self, &self,
game: &Game, camera_zoom: f32,
instances: &mut Vec<SpriteInstance>, instances: &mut Vec<SpriteInstance>,
s: SubSprite, s: &ObjectSubSprite,
parent_pos: Point2<f32>, parent_pos: Point2<f32>,
parent_angle: Deg<f32>, parent_angle: Deg<f32>,
) { ) {
let texture = self.texture_array.get_texture(s.texture); let texture = self.texture_array.get_texture(s.texture);
let scale = s.size / (s.pos.z * game.camera.zoom); let scale = s.size / (s.pos.z * camera_zoom);
let sprite_aspect_and_scale = let sprite_aspect_and_scale =
Matrix4::from_nonuniform_scale(texture.aspect * scale, scale, 1.0); Matrix4::from_nonuniform_scale(texture.aspect * scale, scale, 1.0);
let rotate = Matrix4::from_angle_z(s.angle); let rotate = Matrix4::from_angle_z(s.angle);
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0); let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
let ptranslate = Matrix4::from_translation(Vector3 { let ptranslate = Matrix4::from_translation(Vector3 {
x: parent_pos.x / (game.camera.zoom / 2.0) / self.window_aspect, x: parent_pos.x / (camera_zoom / 2.0) / self.window_aspect,
y: parent_pos.y / (game.camera.zoom / 2.0), y: parent_pos.y / (camera_zoom / 2.0),
z: 0.0, z: 0.0,
}); });
let protate = Matrix4::from_angle_z(parent_angle); let protate = Matrix4::from_angle_z(parent_angle);
let translate = Matrix4::from_translation(Vector3 { let translate = Matrix4::from_translation(Vector3 {
x: s.pos.x / (game.camera.zoom / 2.0) / self.window_aspect, x: s.pos.x / (camera_zoom / 2.0) / self.window_aspect,
y: s.pos.y / (game.camera.zoom / 2.0), y: s.pos.y / (camera_zoom / 2.0),
z: 0.0, z: 0.0,
}); });
@ -332,20 +343,65 @@ impl GPUState {
}); });
} }
/// Create a SpriteInstance for a ui sprite and add it to `instances`
fn push_ui_sprite(&self, instances: &mut Vec<SpriteInstance>, s: &UiSprite) {
let logical_size: LogicalSize<f32> =
self.window_size.to_logical(self.window.scale_factor());
let texture = self.texture_array.get_texture(s.texture);
let width = s.dimensions.x;
let height = s.dimensions.y;
let scale = Matrix4::from_nonuniform_scale(
width / logical_size.width,
height / logical_size.height,
1.0,
);
let rotate = Matrix4::from_angle_z(s.angle);
let translate = Matrix4::from_translation(match s.pos {
super::AnchoredUiPosition::NorthWest(p) => Vector3 {
// Note the signs. Positive y points north!
x: -1.0 + (width / 2.0 + p.x) / (logical_size.width / 2.0),
y: 1.0 - (height / 2.0 - p.y) / (logical_size.height / 2.0),
z: 0.0,
},
_ => Vector3 {
x: 0.0,
y: 0.0,
z: 0.0,
},
});
instances.push(SpriteInstance {
transform: (OPENGL_TO_WGPU_MATRIX * translate * rotate * scale).into(),
texture_index: texture.index,
});
}
/// Make a SpriteInstance for each of the game's visible sprites. /// Make a SpriteInstance for each of the game's visible sprites.
/// Will panic if SPRITE_INSTANCE_LIMIT is exceeded. /// Will panic if SPRITE_INSTANCE_LIMIT is exceeded.
/// ///
/// This is only called inside self.render() /// This is only called inside self.render()
fn make_sprite_instances(&self, game: &Game) -> Vec<SpriteInstance> { fn make_sprite_instances(
&self,
camera_zoom: f32,
camera_pos: Point2<f32>,
objects: &Vec<ObjectSprite>,
ui: &Vec<UiSprite>,
) -> Vec<SpriteInstance> {
let mut instances: Vec<SpriteInstance> = Vec::new(); let mut instances: Vec<SpriteInstance> = Vec::new();
// Game coordinates (relative to camera) of ne and sw corners of screen. // Game coordinates (relative to camera) of ne and sw corners of screen.
// Used to skip off-screen sprites. // Used to skip off-screen sprites.
let clip_ne = Point2::from((-self.window_aspect, 1.0)) * game.camera.zoom; let clip_ne = Point2::from((-self.window_aspect, 1.0)) * camera_zoom;
let clip_sw = Point2::from((self.window_aspect, -1.0)) * game.camera.zoom; let clip_sw = Point2::from((self.window_aspect, -1.0)) * camera_zoom;
for s in game.get_sprites() { for s in objects {
self.push_sprite(game, &mut instances, clip_ne, clip_sw, s); self.push_object_sprite(camera_zoom, camera_pos, &mut instances, clip_ne, clip_sw, s);
}
for s in ui {
self.push_ui_sprite(&mut instances, s);
} }
// Enforce sprite limit // Enforce sprite limit
@ -361,18 +417,19 @@ impl GPUState {
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded. /// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
/// ///
/// Starfield data rarely changes, so this is called only when it's needed. /// Starfield data rarely changes, so this is called only when it's needed.
pub fn update_starfield_buffer(&mut self, game: &Game) { pub fn update_starfield_buffer(&mut self, starfield: &Vec<StarfieldStar>) {
let sz = consts::STARFIELD_SIZE as f32; let sz = consts_main::STARFIELD_SIZE as f32;
// Compute window size in starfield tiles // Compute window size in starfield tiles
let mut nw_tile: Point2<i32> = { let mut nw_tile: Point2<i32> = {
// Game coordinates (relative to camera) of nw corner of screen. // Game coordinates (relative to camera) of nw corner of screen.
let clip_nw = Point2::from((self.window_aspect, 1.0)) * consts::ZOOM_MAX; // TODO: we probably don't need to re-compute this on resize
let clip_nw = Point2::from((self.window_aspect, 1.0)) * consts_main::ZOOM_MAX;
// Parallax correction. // Parallax correction.
// Also, adjust v for mod to work properly // Also, adjust v for mod to work properly
// (v is centered at 0) // (v is centered at 0)
let v: Point2<f32> = clip_nw * consts::STARFIELD_Z_MIN; let v: Point2<f32> = clip_nw * consts_main::STARFIELD_Z_MIN;
let v_adj: Point2<f32> = (v.x + (sz / 2.0), v.y + (sz / 2.0)).into(); let v_adj: Point2<f32> = (v.x + (sz / 2.0), v.y + (sz / 2.0)).into();
#[rustfmt::skip] #[rustfmt::skip]
@ -396,7 +453,7 @@ impl GPUState {
// Truncate tile grid to buffer size // Truncate tile grid to buffer size
// (The window won't be full of stars if our instance limit is too small) // (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) * consts::STARFIELD_COUNT as i32) while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * consts_main::STARFIELD_COUNT as i32)
> STARFIELD_INSTANCE_LIMIT as i32 > STARFIELD_INSTANCE_LIMIT as i32
{ {
nw_tile -= Vector2::from((1, 1)); nw_tile -= Vector2::from((1, 1));
@ -411,7 +468,7 @@ impl GPUState {
y: sz * y as f32, y: sz * y as f32,
z: 0.0, z: 0.0,
}; };
for s in &game.system.starfield { for s in starfield {
instances.push(StarfieldInstance { instances.push(StarfieldInstance {
position: (s.pos + offset).into(), position: (s.pos + offset).into(),
size: s.size, size: s.size,
@ -434,7 +491,14 @@ impl GPUState {
); );
} }
pub fn render(&mut self, game: &Game) -> Result<(), wgpu::SurfaceError> { /// Main render function. Draws sprites on a window.
pub fn render(
&mut self,
camera_pos: Point2<f32>,
camera_zoom: f32,
object_sprites: &Vec<ObjectSprite>,
ui_sprites: &Vec<UiSprite>,
) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?; let output = self.surface.get_current_texture()?;
let view = output let view = output
.texture .texture
@ -472,22 +536,26 @@ impl GPUState {
&self.global_data.buffer, &self.global_data.buffer,
0, 0,
bytemuck::cast_slice(&[GlobalDataContent { bytemuck::cast_slice(&[GlobalDataContent {
camera_position: game.camera.pos.into(), camera_position: camera_pos.into(),
camera_zoom: [game.camera.zoom, 0.0], camera_zoom: [camera_zoom, 0.0],
camera_zoom_limits: [consts::ZOOM_MIN, consts::ZOOM_MAX], camera_zoom_limits: [consts_main::ZOOM_MIN, consts_main::ZOOM_MAX],
window_size: [ window_size: [
self.window_size.width as f32, self.window_size.width as f32,
self.window_size.height as f32, self.window_size.height as f32,
], ],
window_aspect: [self.window_aspect, 0.0], window_aspect: [self.window_aspect, 0.0],
starfield_texture: [self.texture_array.get_starfield_texture().index, 0], starfield_texture: [self.texture_array.get_starfield_texture().index, 0],
starfield_tile_size: [consts::STARFIELD_SIZE as f32, 0.0], starfield_tile_size: [consts_main::STARFIELD_SIZE as f32, 0.0],
starfield_size_limits: [consts::STARFIELD_SIZE_MIN, consts::STARFIELD_SIZE_MAX], starfield_size_limits: [
consts_main::STARFIELD_SIZE_MIN,
consts_main::STARFIELD_SIZE_MAX,
],
}]), }]),
); );
// Create sprite instances // Create sprite instances
let sprite_instances = self.make_sprite_instances(game); let sprite_instances =
self.make_sprite_instances(camera_zoom, camera_pos, object_sprites, ui_sprites);
self.queue.write_buffer( self.queue.write_buffer(
&self.vertex_buffers.sprite.instances, &self.vertex_buffers.sprite.instances,
0, 0,

39
crates/render/src/lib.rs Normal file
View File

@ -0,0 +1,39 @@
#![warn(missing_docs)]
//! This crate contains all drawing logic and NO game logic.
//! It converts game state to a nice picture.
//!
//! [`GPUState`] is the main struct this crate provides,
//! and the only one external code should interact with.
//! (Excluding data structs, like [`ObjectSprite`])
mod consts;
mod globaldata;
mod gpustate;
mod pipeline;
mod sprite;
mod texturearray;
mod vertexbuffer;
// TODO: remove
mod consts_main;
use cgmath::{Point3, Vector2};
use galactica_content as content;
pub use gpustate::GPUState;
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, UiSprite};
/// TODO: this shouldn't be here
pub 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>,
}

View File

@ -1,8 +1,8 @@
use std::rc::Rc; use std::rc::Rc;
use wgpu; use wgpu;
use super::consts::{SHADER_MAIN_FRAGMENT, SHADER_MAIN_VERTEX}; use crate::consts::{SHADER_MAIN_FRAGMENT, SHADER_MAIN_VERTEX};
use super::vertexbuffer::VertexBuffer; use crate::vertexbuffer::VertexBuffer;
pub struct PipelineBuilder<'a> { pub struct PipelineBuilder<'a> {
// These are provided with new() // These are provided with new()

View File

@ -0,0 +1,78 @@
use crate::content;
use cgmath::{Deg, Point2, Point3};
/// The location of a UI element, in one of a few
/// possible coordinate systems.
///
/// Positive Y always points up,
/// positive X always points right.
#[derive(Debug, Clone)]
pub enum AnchoredUiPosition {
/// Position of this sprite's nw corner,
/// relative to the nw corner of the window.
NorthWest(Point2<f32>),
/// Position of this sprite's sw corner,
/// relative to the sw corner of the window.
SouthWest(Point2<f32>),
}
/// A sprite that represents a ui element
#[derive(Debug, Clone)]
pub struct UiSprite {
/// The texture to use for this sprite
pub texture: content::TextureHandle,
/// This object's position, in logical (dpi-adjusted) pixels
pub pos: AnchoredUiPosition,
/// The size of this sprite, in logical (dpi-adjusted) pixels
pub dimensions: Point2<f32>,
/// This sprite's rotation, measured ccw
pub angle: Deg<f32>,
}
/// A sprite that represents a world object:
/// Ships, planets, debris, etc
#[derive(Debug, Clone)]
pub struct ObjectSprite {
/// The texture to use for this sprite
pub texture: content::TextureHandle,
/// This object's center, in world coordinates.
pub pos: Point3<f32>,
/// The size of this sprite,
/// given as height in world units.
pub size: f32,
/// This sprite's rotation
/// (relative to north, measured ccw)
/// Note that this is different from the angle used by our physics system.
pub angle: Deg<f32>,
/// Sprites that should be drawn relative to this sprite.
pub children: Option<Vec<ObjectSubSprite>>,
}
/// A sprite that is drawn relative to an ObjectSprite.
#[derive(Debug, Clone)]
pub struct ObjectSubSprite {
/// The sprite texture to draw
pub texture: content::TextureHandle,
/// This object's position, in world coordinates.
/// This is relative to this sprite's parent.
pub pos: Point3<f32>,
/// The size of this sprite,
/// given as height in world units.
pub size: f32,
/// This sprite's rotation
/// (relative to north, measured ccw)
/// Just as position, this is relative to this
/// subsprite's parent sprite.
pub angle: Deg<f32>,
}

View File

@ -1,15 +1,15 @@
use crate::content;
use anyhow::Result; use anyhow::Result;
use galactica_content::{Content, TextureHandle};
use image::GenericImageView; use image::GenericImageView;
use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32}; use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32};
use wgpu::BindGroupLayout; use wgpu::BindGroupLayout;
pub(super) struct RawTexture { pub(crate) struct RawTexture {
pub(super) view: wgpu::TextureView, pub(crate) view: wgpu::TextureView,
} }
impl RawTexture { impl RawTexture {
pub(super) fn from_bytes( pub(crate) fn from_bytes(
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
bytes: &[u8], bytes: &[u8],
@ -19,7 +19,7 @@ impl RawTexture {
Self::from_image(device, queue, &img, Some(label)) Self::from_image(device, queue, &img, Some(label))
} }
pub(super) fn from_image( pub(crate) fn from_image(
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
img: &image::DynamicImage, img: &image::DynamicImage,
@ -76,8 +76,8 @@ pub struct Texture {
pub struct TextureArray { pub struct TextureArray {
pub bind_group: wgpu::BindGroup, pub bind_group: wgpu::BindGroup,
pub bind_group_layout: BindGroupLayout, pub bind_group_layout: BindGroupLayout,
starfield_handle: TextureHandle, starfield_handle: content::TextureHandle,
textures: HashMap<TextureHandle, Texture>, textures: HashMap<content::TextureHandle, Texture>,
} }
impl TextureArray { impl TextureArray {
@ -85,14 +85,14 @@ impl TextureArray {
*self.textures.get(&self.starfield_handle).unwrap() *self.textures.get(&self.starfield_handle).unwrap()
} }
pub fn get_texture(&self, handle: TextureHandle) -> Texture { pub fn get_texture(&self, handle: content::TextureHandle) -> Texture {
match self.textures.get(&handle) { match self.textures.get(&handle) {
Some(x) => *x, Some(x) => *x,
None => unreachable!("Tried to get a texture that doesn't exist"), None => unreachable!("Tried to get a texture that doesn't exist"),
} }
} }
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &Content) -> Result<Self> { pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &content::Content) -> Result<Self> {
// Load all textures // Load all textures
let mut texture_data = Vec::new(); let mut texture_data = Vec::new();
let mut textures = HashMap::new(); let mut textures = HashMap::new();

View File

@ -36,9 +36,8 @@ impl BufferObject for TexturedVertex {
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct StarfieldInstance { pub struct StarfieldInstance {
/// Position in the starfield. /// Position in the starfield. This is NOT world position!
/// ///
/// This is NOT world position, i.e, different from sprite positioning!
/// The x and y coordinates here represent position relative to the center /// The x and y coordinates here represent position relative to the center
/// of a starfield tile in world units, which is converted to world position /// of a starfield tile in world units, which is converted to world position
/// by the starfield vertex shader. /// by the starfield vertex shader.

View File

@ -32,4 +32,4 @@ pub const STARFIELD_TEXTURE_NAME: &'static str = "starfield";
pub const CONTENT_ROOT: &'static str = "./content"; pub const CONTENT_ROOT: &'static str = "./content";
/// Root directory of game textures /// Root directory of game textures
pub const TEXTURE_ROOT: &'static str = "./assets"; pub const TEXTURE_ROOT: &'static str = "./assets/render";

View File

@ -1,18 +1,72 @@
use cgmath::Point2; use cgmath::{Deg, InnerSpace, Point2};
use galactica_content::FactionHandle; use galactica_render::{AnchoredUiPosition, ObjectSprite, UiSprite};
use std::time::Instant; use std::time::Instant;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode}; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
use super::{camera::Camera, outfits, system::System}; use super::{camera::Camera, outfits, system::System};
use crate::{ use crate::{
consts, consts, content,
content::Content,
inputstatus::InputStatus, inputstatus::InputStatus,
physics::{util, Physics, ShipHandle}, physics::{util, Physics, ShipHandle},
render::Sprite,
shipbehavior::{self, ShipBehavior}, shipbehavior::{self, ShipBehavior},
}; };
struct Ui {}
impl Ui {
fn new() -> Self {
Self {}
}
fn build_radar(
&self,
player: &ShipHandle,
physics: &Physics,
ct: &content::Content,
) -> Vec<UiSprite> {
let mut out = Vec::new();
let radar_range = 2000.0;
let radar_size = 300.0;
out.push(UiSprite {
texture: ct.get_texture_handle("ui::radar"),
pos: AnchoredUiPosition::NorthWest(Point2 { x: 10.0, y: -10.0 }),
dimensions: Point2 {
x: radar_size,
y: radar_size,
},
angle: Deg(0.0),
});
let (_, pr) = physics.get_ship_body(player).unwrap();
let pr = util::rigidbody_position(pr);
for (s, r) in physics.iter_ship_body() {
if s.physics_handle == *player {
continue;
}
let r = util::rigidbody_position(r);
let d = r - pr;
let m = d.magnitude() / radar_range;
if m < 0.8 {
out.push(UiSprite {
texture: ct.get_texture_handle("ui::blip"),
pos: AnchoredUiPosition::NorthWest(
Point2 {
x: radar_size / 2.0 + 10.0,
y: radar_size / -2.0 - 10.0,
} + (d / radar_range * 150.0),
),
dimensions: Point2 { x: 1.0, y: 1.0 } * 5.0f32.min((0.8 - m) * 50.0),
angle: Deg(0.0),
});
}
}
return out;
}
}
pub struct Game { pub struct Game {
pub input: InputStatus, pub input: InputStatus,
pub last_update: Instant, pub last_update: Instant,
@ -22,13 +76,14 @@ pub struct Game {
paused: bool, paused: bool,
pub time_scale: f32, pub time_scale: f32,
ui: Ui,
physics: Physics, physics: Physics,
shipbehaviors: Vec<Box<dyn ShipBehavior>>, shipbehaviors: Vec<Box<dyn ShipBehavior>>,
content: Content, content: content::Content,
} }
impl Game { impl Game {
pub fn new(ct: Content) -> Self { pub fn new(ct: content::Content) -> Self {
let mut physics = Physics::new(); let mut physics = Physics::new();
let mut o1 = outfits::ShipOutfits::new(&ct.ships[0]); let mut o1 = outfits::ShipOutfits::new(&ct.ships[0]);
@ -40,14 +95,14 @@ impl Game {
&ct.ships[0], &ct.ships[0],
o1, o1,
Point2 { x: 0.0, y: 0.0 }, Point2 { x: 0.0, y: 0.0 },
FactionHandle { index: 0 }, content::FactionHandle { index: 0 },
); );
let h2 = physics.add_ship( let h2 = physics.add_ship(
&ct.ships[0], &ct.ships[0],
outfits::ShipOutfits::new(&ct.ships[0]), outfits::ShipOutfits::new(&ct.ships[0]),
Point2 { x: 300.0, y: 300.0 }, Point2 { x: 300.0, y: 300.0 },
FactionHandle { index: 0 }, content::FactionHandle { index: 0 },
); );
let mut o2 = outfits::ShipOutfits::new(&ct.ships[0]); let mut o2 = outfits::ShipOutfits::new(&ct.ships[0]);
@ -60,7 +115,7 @@ impl Game {
x: -300.0, x: -300.0,
y: 300.0, y: 300.0,
}, },
FactionHandle { index: 1 }, content::FactionHandle { index: 1 },
); );
let mut shipbehaviors: Vec<Box<dyn ShipBehavior>> = Vec::new(); let mut shipbehaviors: Vec<Box<dyn ShipBehavior>> = Vec::new();
@ -84,6 +139,7 @@ impl Game {
physics, physics,
shipbehaviors, shipbehaviors,
content: ct, content: ct,
ui: Ui::new(),
} }
} }
@ -140,8 +196,8 @@ impl Game {
self.last_update = Instant::now(); self.last_update = Instant::now();
} }
pub fn get_sprites(&self) -> Vec<Sprite> { pub fn get_object_sprites(&self) -> Vec<ObjectSprite> {
let mut sprites: Vec<Sprite> = Vec::new(); let mut sprites: Vec<ObjectSprite> = Vec::new();
sprites.append(&mut self.system.get_sprites()); sprites.append(&mut self.system.get_sprites());
sprites.extend(self.physics.get_ship_sprites()); sprites.extend(self.physics.get_ship_sprites());
@ -158,4 +214,10 @@ impl Game {
return sprites; return sprites;
} }
pub fn get_ui_sprites(&self) -> Vec<UiSprite> {
return self
.ui
.build_radar(&self.player, &self.physics, &self.content);
}
} }

View File

@ -1,7 +1,7 @@
use cgmath::{Deg, Point3}; use cgmath::{Deg, Point3};
use content::{OutfitSpace, TextureHandle}; use galactica_render::ObjectSubSprite;
use crate::{content, render::SubSprite}; use crate::content;
/// Represents a gun attached to a specific ship at a certain gunpoint. /// Represents a gun attached to a specific ship at a certain gunpoint.
#[derive(Debug)] #[derive(Debug)]
@ -28,7 +28,7 @@ impl ShipGun {
pub struct OutfitStatSum { pub struct OutfitStatSum {
pub engine_thrust: f32, pub engine_thrust: f32,
pub steer_power: f32, pub steer_power: f32,
pub engine_flare_textures: Vec<TextureHandle>, pub engine_flare_textures: Vec<content::TextureHandle>,
} }
impl OutfitStatSum { impl OutfitStatSum {
@ -67,9 +67,9 @@ impl OutfitStatSum {
#[derive(Debug)] #[derive(Debug)]
pub struct ShipOutfits { pub struct ShipOutfits {
pub stats: OutfitStatSum, pub stats: OutfitStatSum,
pub total_space: OutfitSpace, pub total_space: content::OutfitSpace,
available_space: OutfitSpace, available_space: content::OutfitSpace,
outfits: Vec<content::Outfit>, outfits: Vec<content::Outfit>,
guns: Vec<ShipGun>, guns: Vec<ShipGun>,
enginepoints: Vec<content::EnginePoint>, enginepoints: Vec<content::EnginePoint>,
@ -77,7 +77,7 @@ pub struct ShipOutfits {
// Minor performance optimization, since we // Minor performance optimization, since we
// rarely need to re-compute these. // rarely need to re-compute these.
engine_flare_sprites: Vec<SubSprite>, engine_flare_sprites: Vec<ObjectSubSprite>,
} }
impl<'a> ShipOutfits { impl<'a> ShipOutfits {
@ -177,7 +177,7 @@ impl<'a> ShipOutfits {
self.engine_flare_sprites = self self.engine_flare_sprites = self
.enginepoints .enginepoints
.iter() .iter()
.map(|p| SubSprite { .map(|p| ObjectSubSprite {
pos: Point3 { pos: Point3 {
x: p.pos.x, x: p.pos.x,
y: p.pos.y, y: p.pos.y,
@ -190,7 +190,7 @@ impl<'a> ShipOutfits {
.collect(); .collect();
} }
pub fn get_engine_flares(&self) -> Vec<SubSprite> { pub fn get_engine_flares(&self) -> Vec<ObjectSubSprite> {
return self.engine_flare_sprites.clone(); return self.engine_flare_sprites.clone();
} }
} }

View File

@ -1,22 +1,9 @@
use cgmath::{Point3, Vector2}; use cgmath::{Point3, Vector2};
use galactica_render::{ObjectSprite, StarfieldStar};
use rand::{self, Rng}; use rand::{self, Rng};
use super::SystemObject; use super::SystemObject;
use crate::{consts, content, render::Sprite}; use crate::{consts, content};
pub 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 struct System { pub struct System {
pub name: String, pub name: String,
@ -59,7 +46,7 @@ impl System {
return s; return s;
} }
pub fn get_sprites(&self) -> Vec<Sprite> { pub fn get_sprites(&self) -> Vec<ObjectSprite> {
return self.bodies.iter().map(|x| x.get_sprite()).collect(); return self.bodies.iter().map(|x| x.get_sprite()).collect();
} }
} }

View File

@ -1,18 +1,17 @@
use crate::content;
use cgmath::{Deg, Point3}; use cgmath::{Deg, Point3};
use galactica_content::TextureHandle; use galactica_render::ObjectSprite;
use crate::render::Sprite;
pub struct SystemObject { pub struct SystemObject {
pub sprite_texture: TextureHandle, pub sprite_texture: content::TextureHandle,
pub pos: Point3<f32>, pub pos: Point3<f32>,
pub size: f32, pub size: f32,
pub angle: Deg<f32>, pub angle: Deg<f32>,
} }
impl SystemObject { impl SystemObject {
pub(super) fn get_sprite(&self) -> Sprite { pub(crate) fn get_sprite(&self) -> ObjectSprite {
return Sprite { return ObjectSprite {
texture: self.sprite_texture, texture: self.sprite_texture,
pos: self.pos, pos: self.pos,
angle: self.angle, angle: self.angle,

View File

@ -3,13 +3,12 @@ mod game;
mod inputstatus; mod inputstatus;
mod objects; mod objects;
mod physics; mod physics;
mod render;
mod shipbehavior; mod shipbehavior;
use std::path::PathBuf; pub use galactica_content as content;
use anyhow::Result; use anyhow::Result;
use galactica_content as content; use std::path::PathBuf;
use winit::{ use winit::{
event::{Event, KeyboardInput, WindowEvent}, event::{Event, KeyboardInput, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
@ -26,17 +25,22 @@ fn main() -> Result<()> {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
let mut gpu = pollster::block_on(render::GPUState::new(window, &content))?; let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?;
let mut game = game::Game::new(content); let mut game = game::Game::new(content);
gpu.update_starfield_buffer(&game); gpu.update_starfield_buffer(&game.system.starfield);
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
match event { match event {
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => { Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
match gpu.render(&game) { match gpu.render(
game.camera.pos,
game.camera.zoom,
&game.get_object_sprites(),
&game.get_ui_sprites(),
) {
Ok(_) => {} Ok(_) => {}
Err(wgpu::SurfaceError::Lost) => gpu.resize(&game, gpu.window_size), Err(wgpu::SurfaceError::Lost) => gpu.resize(&game.system.starfield),
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// All other errors (Outdated, Timeout) should be resolved by the next frame // All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e), Err(e) => eprintln!("{:?}", e),
@ -71,11 +75,11 @@ fn main() -> Result<()> {
WindowEvent::MouseWheel { delta, phase, .. } => { WindowEvent::MouseWheel { delta, phase, .. } => {
game.process_scroll(delta, phase); game.process_scroll(delta, phase);
} }
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(_) => {
gpu.resize(&game, *physical_size); gpu.resize(&game.system.starfield);
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { WindowEvent::ScaleFactorChanged { .. } => {
gpu.resize(&game, **new_inner_size); gpu.resize(&game.system.starfield);
} }
_ => {} _ => {}
}, },

View File

@ -1,20 +1,20 @@
use cgmath::{Deg, InnerSpace, Point3, Vector2}; use cgmath::{Deg, InnerSpace, Point3, Vector2};
use galactica_content::{FactionHandle, TextureHandle}; use galactica_render::ObjectSprite;
use rapier2d::{ use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle}, dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
geometry::{ColliderBuilder, ColliderHandle}, geometry::{ColliderBuilder, ColliderHandle},
}; };
use crate::{physics::util, render::Sprite}; use crate::{content, physics::util};
pub struct ProjectileBuilder { pub struct ProjectileBuilder {
pub rigid_body: RigidBodyBuilder, pub rigid_body: RigidBodyBuilder,
pub collider: ColliderBuilder, pub collider: ColliderBuilder,
pub sprite_texture: TextureHandle, pub sprite_texture: content::TextureHandle,
pub lifetime: f32, pub lifetime: f32,
pub size: f32, pub size: f32,
pub damage: f32, pub damage: f32,
pub faction: FactionHandle, pub faction: content::FactionHandle,
} }
impl ProjectileBuilder { impl ProjectileBuilder {
@ -35,11 +35,11 @@ impl ProjectileBuilder {
pub struct Projectile { pub struct Projectile {
pub rigid_body: RigidBodyHandle, pub rigid_body: RigidBodyHandle,
pub collider: ColliderHandle, pub collider: ColliderHandle,
pub sprite_texture: TextureHandle, pub sprite_texture: content::TextureHandle,
pub lifetime: f32, pub lifetime: f32,
pub size: f32, pub size: f32,
pub damage: f32, pub damage: f32,
pub faction: FactionHandle, pub faction: content::FactionHandle,
} }
impl Projectile { impl Projectile {
@ -51,14 +51,14 @@ impl Projectile {
return self.lifetime < 0.0; return self.lifetime < 0.0;
} }
pub fn get_sprite(&self, r: &RigidBody) -> Sprite { pub fn get_sprite(&self, r: &RigidBody) -> ObjectSprite {
let pos = util::rigidbody_position(r); let pos = util::rigidbody_position(r);
let rot = util::rigidbody_rotation(r); let rot = util::rigidbody_rotation(r);
// Sprites point north at 0 degrees // Sprites point north at 0 degrees
let ang: Deg<f32> = rot.angle(Vector2 { x: 1.0, y: 0.0 }).into(); let ang: Deg<f32> = rot.angle(Vector2 { x: 1.0, y: 0.0 }).into();
Sprite { ObjectSprite {
texture: self.sprite_texture, texture: self.sprite_texture,
pos: Point3 { pos: Point3 {
x: pos.x, x: pos.x,

View File

@ -1,5 +1,6 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2};
use content::{FactionHandle, TextureHandle}; use content::{FactionHandle, TextureHandle};
use galactica_render::ObjectSprite;
use nalgebra::vector; use nalgebra::vector;
use rand::Rng; use rand::Rng;
use rapier2d::{ use rapier2d::{
@ -13,7 +14,6 @@ use crate::{
content, content,
game::outfits, game::outfits,
physics::{util, ShipHandle}, physics::{util, ShipHandle},
render::Sprite,
}; };
pub struct ShipTickResult { pub struct ShipTickResult {
@ -157,14 +157,14 @@ impl Ship {
return ShipTickResult { projectiles: p }; return ShipTickResult { projectiles: p };
} }
pub fn get_sprite(&self, r: &RigidBody) -> Sprite { pub fn get_sprite(&self, r: &RigidBody) -> ObjectSprite {
let ship_pos = util::rigidbody_position(r); let ship_pos = util::rigidbody_position(r);
let ship_rot = util::rigidbody_rotation(r); let ship_rot = util::rigidbody_rotation(r);
// Sprites point north at 0 degrees // Sprites point north at 0 degrees
let ship_ang: Deg<f32> = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }).into(); let ship_ang: Deg<f32> = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }).into();
Sprite { ObjectSprite {
pos: (ship_pos.x, ship_pos.y, 1.0).into(), pos: (ship_pos.x, ship_pos.y, 1.0).into(),
texture: self.sprite_texture, texture: self.sprite_texture,
angle: -ship_ang, angle: -ship_ang,

View File

@ -6,5 +6,5 @@ pub use physics::Physics;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ShipHandle(pub(super) RigidBodyHandle, ColliderHandle); pub struct ShipHandle(pub(super) RigidBodyHandle, ColliderHandle);

View File

@ -1,6 +1,6 @@
use cgmath::Point2; use cgmath::Point2;
use content::{Content, FactionHandle};
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use galactica_render::ObjectSprite;
use nalgebra::vector; use nalgebra::vector;
use rapier2d::{ use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle}, dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
@ -10,7 +10,7 @@ use rapier2d::{
use std::{collections::HashMap, f32::consts::PI}; use std::{collections::HashMap, f32::consts::PI};
use super::{wrapper::Wrapper, ShipHandle}; use super::{wrapper::Wrapper, ShipHandle};
use crate::{content, game::outfits, objects, render::Sprite}; use crate::{content, game::outfits, objects};
/// Keeps track of all objects in the world that we can interact with. /// Keeps track of all objects in the world that we can interact with.
/// Also wraps our physics engine /// Also wraps our physics engine
@ -84,7 +84,7 @@ impl Physics {
ct: &content::Ship, ct: &content::Ship,
outfits: outfits::ShipOutfits, outfits: outfits::ShipOutfits,
position: Point2<f32>, position: Point2<f32>,
faction: FactionHandle, faction: content::FactionHandle,
) -> ShipHandle { ) -> ShipHandle {
let cl = ColliderBuilder::convex_decomposition( let cl = ColliderBuilder::convex_decomposition(
&ct.collision.points[..], &ct.collision.points[..],
@ -114,7 +114,7 @@ impl Physics {
return h; return h;
} }
pub fn step(&mut self, t: f32, ct: &Content) { pub fn step(&mut self, t: f32, ct: &content::Content) {
// Run ship updates // Run ship updates
let mut res = Vec::new(); let mut res = Vec::new();
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
@ -205,13 +205,13 @@ impl Physics {
}) })
} }
pub fn get_ship_sprites(&self) -> impl Iterator<Item = Sprite> + '_ { pub fn get_ship_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
self.ships self.ships
.values() .values()
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.physics_handle.0])) .map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.physics_handle.0]))
} }
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = Sprite> + '_ { pub fn get_projectile_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
self.projectiles self.projectiles
.values() .values()
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body])) .map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))

View File

@ -1,10 +0,0 @@
mod consts;
mod globaldata;
mod gpustate;
mod pipeline;
mod sprite;
mod texturearray;
mod vertexbuffer;
pub use gpustate::GPUState;
pub use sprite::{Sprite, SubSprite};

View File

@ -1,42 +0,0 @@
use cgmath::{Deg, Point3};
use galactica_content::TextureHandle;
#[derive(Debug, Clone)]
pub struct Sprite {
/// The sprite texture to draw
pub texture: TextureHandle,
/// This object's position, in world coordinates.
pub pos: Point3<f32>,
/// The size of this sprite,
/// given as height in world units.
pub size: f32,
/// This sprite's rotation
/// (relative to north, measured ccw)
pub angle: Deg<f32>,
/// Sprites that should be drawn relative to this sprite.
pub children: Option<Vec<SubSprite>>,
}
#[derive(Debug, Clone)]
pub struct SubSprite {
/// The sprite texture to draw
pub texture: TextureHandle,
/// This object's position, in world coordinates.
/// This is relative to this sprite's parent.
pub pos: Point3<f32>,
/// The size of this sprite,
/// given as height in world units.
pub size: f32,
/// This sprite's rotation
/// (relative to north, measured ccw)
/// Just as position, this is relative to this
/// subsprite's parent sprite.
pub angle: Deg<f32>,
}

View File

@ -1,7 +1,7 @@
use cgmath::{Deg, InnerSpace}; use cgmath::{Deg, InnerSpace};
use galactica_content as content;
use crate::{ use crate::{
content,
inputstatus::InputStatus, inputstatus::InputStatus,
physics::{util, Physics, ShipHandle}, physics::{util, Physics, ShipHandle},
}; };