Compare commits
7 Commits
1001b8ba4a
...
a4ca62e1dc
Author | SHA1 | Date |
---|---|---|
Mark | a4ca62e1dc | |
Mark | 613245b92e | |
Mark | 37a0aca5af | |
Mark | f926f5f172 | |
Mark | 7cde4f2346 | |
Mark | c382431747 | |
Mark | 10f9776108 |
3
TODO.md
3
TODO.md
|
@ -100,6 +100,7 @@
|
||||||
- Conversations
|
- Conversations
|
||||||
- Trade
|
- Trade
|
||||||
- Missions
|
- Missions
|
||||||
|
- Procedural suns
|
||||||
|
|
||||||
## Camera
|
## Camera
|
||||||
- Shake/wobble on heavy hits?
|
- Shake/wobble on heavy hits?
|
||||||
|
@ -134,6 +135,8 @@
|
||||||
- Handles
|
- Handles
|
||||||
- Content specification and pipeline
|
- Content specification and pipeline
|
||||||
- How packer and optimizations work, and why
|
- How packer and optimizations work, and why
|
||||||
|
- How big should sprites be? (resize existing)
|
||||||
|
- Naming: atlas, sprite, image, frame, texture
|
||||||
|
|
||||||
|
|
||||||
## Ideas
|
## Ideas
|
||||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 6e4b7022a963a3357ba5f578bdd91064ae9f6b3e
|
Subproject commit 05e5272f3789b44192d0bf2bd253641e76659a2a
|
|
@ -7,8 +7,8 @@ rate = 0.2
|
||||||
# Random rate variation (each cooldown is +- this)
|
# Random rate variation (each cooldown is +- this)
|
||||||
rate_rng = 0.1
|
rate_rng = 0.1
|
||||||
|
|
||||||
# TODO: apply force on fire
|
# TODO: apply force to ship on fire
|
||||||
projectile.sprite_texture = "projectile::blaster"
|
projectile.sprite = "projectile::blaster"
|
||||||
# Height of projectile in game units
|
# Height of projectile in game units
|
||||||
projectile.size = 6
|
projectile.size = 6
|
||||||
projectile.size_rng = 0.0
|
projectile.size_rng = 0.0
|
||||||
|
@ -29,13 +29,13 @@ projectile.force = 0.0
|
||||||
|
|
||||||
projectile.collider.ball.radius = 2.0
|
projectile.collider.ball.radius = 2.0
|
||||||
|
|
||||||
projectile.impact.texture = "particle::blaster"
|
projectile.impact.sprite = "particle::explosion"
|
||||||
projectile.impact.lifetime = "inherit"
|
projectile.impact.lifetime = "inherit"
|
||||||
projectile.impact.inherit_velocity = "target"
|
projectile.impact.inherit_velocity = "target"
|
||||||
projectile.impact.size = 3.0
|
projectile.impact.size = 3.0
|
||||||
|
|
||||||
|
|
||||||
projectile.expire.texture = "particle::blaster"
|
projectile.expire.sprite = "particle::blaster"
|
||||||
projectile.expire.lifetime = "inherit"
|
projectile.expire.lifetime = "inherit"
|
||||||
projectile.expire.inherit_velocity = "projectile"
|
projectile.expire.inherit_velocity = "projectile"
|
||||||
projectile.expire.size = 3.0
|
projectile.expire.size = 3.0
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
space.engine = 20
|
space.engine = 20
|
||||||
|
|
||||||
engine.thrust = 100
|
engine.thrust = 100
|
||||||
engine.flare_texture = "flare::ion"
|
engine.flare_sprite = "flare::ion"
|
||||||
steering.power = 20
|
steering.power = 20
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[ship."Gypsum"]
|
[ship."Gypsum"]
|
||||||
sprite_texture = "ship::gypsum"
|
sprite = "ship::gypsum"
|
||||||
size = 100
|
size = 100
|
||||||
mass = 1
|
mass = 1
|
||||||
hull = 200
|
hull = 200
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
[texture."starfield"]
|
[sprite."starfield"]
|
||||||
file = "starfield.png"
|
file = "starfield.png"
|
||||||
|
|
||||||
[texture."star::star"]
|
[sprite."star::star"]
|
||||||
file = "star/B-09.png"
|
file = "star/B-09.png"
|
||||||
|
|
||||||
[texture."flare::ion"]
|
[sprite."flare::ion"]
|
||||||
file = "flare/1.png"
|
file = "flare/1.png"
|
||||||
|
|
||||||
[texture."planet::earth"]
|
[sprite."planet::earth"]
|
||||||
file = "planet/earth.png"
|
file = "planet/earth.png"
|
||||||
|
|
||||||
[texture."planet::luna"]
|
[sprite."planet::luna"]
|
||||||
file = "planet/luna.png"
|
file = "planet/luna.png"
|
||||||
|
|
||||||
[texture."projectile::blaster"]
|
[sprite."projectile::blaster"]
|
||||||
file = "projectile/blaster.png"
|
file = "projectile/blaster.png"
|
||||||
|
|
||||||
[texture."ship::gypsum"]
|
[sprite."ship::gypsum"]
|
||||||
file = "ship/gypsum.png"
|
file = "ship/gypsum.png"
|
||||||
|
|
||||||
[texture."ui::radar"]
|
[sprite."ui::radar"]
|
||||||
file = "ui/radar.png"
|
file = "ui/radar.png"
|
||||||
|
|
||||||
[texture."ui::shipblip"]
|
[sprite."ui::shipblip"]
|
||||||
file = "ui/ship-blip.png"
|
file = "ui/ship-blip.png"
|
||||||
|
|
||||||
[texture."ui::planetblip"]
|
[sprite."ui::planetblip"]
|
||||||
file = "ui/planet-blip.png"
|
file = "ui/planet-blip.png"
|
||||||
|
|
||||||
[texture."ui::radarframe"]
|
[sprite."ui::radarframe"]
|
||||||
file = "ui/radarframe.png"
|
file = "ui/radarframe.png"
|
||||||
|
|
||||||
[texture."ui::centerarrow"]
|
[sprite."ui::centerarrow"]
|
||||||
file = "ui/center-arrow.png"
|
file = "ui/center-arrow.png"
|
||||||
|
|
||||||
[texture."particle::blaster"]
|
[sprite."particle::blaster"]
|
||||||
duration = 0.15
|
duration = 0.15
|
||||||
repeat = "once"
|
repeat = "once"
|
||||||
frames = [
|
frames = [
|
||||||
|
@ -45,7 +45,7 @@ frames = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[texture."particle::explosion"]
|
[sprite."particle::explosion"]
|
||||||
duration = 0.4
|
duration = 0.4
|
||||||
repeat = "once"
|
repeat = "once"
|
||||||
frames = [
|
frames = [
|
|
@ -1,17 +1,17 @@
|
||||||
[system."12 Autumn Above"]
|
[system."12 Autumn Above"]
|
||||||
|
|
||||||
object.star.sprite_texture = "star::star"
|
object.star.sprite = "star::star"
|
||||||
object.star.position = [0.0, 0.0, 30.0]
|
object.star.position = [0.0, 0.0, 30.0]
|
||||||
object.star.size = 2000
|
object.star.size = 2000
|
||||||
|
|
||||||
object.earth.sprite_texture = "planet::earth"
|
object.earth.sprite = "planet::earth"
|
||||||
object.earth.position.center = "star"
|
object.earth.position.center = "star"
|
||||||
object.earth.position.radius = 4000
|
object.earth.position.radius = 4000
|
||||||
object.earth.position.angle = 0
|
object.earth.position.angle = 0
|
||||||
object.earth.position.z = 10.0
|
object.earth.position.z = 10.0
|
||||||
object.earth.size = 1000
|
object.earth.size = 1000
|
||||||
|
|
||||||
object.luna.sprite_texture = "planet::luna"
|
object.luna.sprite = "planet::luna"
|
||||||
object.luna.position.center = "earth"
|
object.luna.position.center = "earth"
|
||||||
object.luna.position.radius = 1600
|
object.luna.position.radius = 1600
|
||||||
object.luna.position.angle = 135
|
object.luna.position.angle = 135
|
||||||
|
|
|
@ -35,14 +35,14 @@ pub const STARFIELD_DENSITY: f64 = 0.01;
|
||||||
/// Must fit inside an i32
|
/// Must fit inside an i32
|
||||||
pub const STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
|
pub const STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
|
||||||
|
|
||||||
/// Name of starfield texture
|
/// Name of starfield sprite
|
||||||
pub const STARFIELD_TEXTURE_NAME: &'static str = "starfield";
|
pub const STARFIELD_SPRITE_NAME: &'static str = "starfield";
|
||||||
|
|
||||||
/// Root directory of game content
|
/// Root directory of game content
|
||||||
pub const CONTENT_ROOT: &'static str = "./content";
|
pub const CONTENT_ROOT: &'static str = "./content";
|
||||||
|
|
||||||
/// Root directory of game textures
|
/// Root directory of game images
|
||||||
pub const TEXTURE_ROOT: &'static str = "./assets/render";
|
pub const IMAGE_ROOT: &'static str = "./assets/render";
|
||||||
|
|
||||||
/// We can draw at most this many object sprites on the screen.
|
/// We can draw at most this many object sprites on the screen.
|
||||||
pub const OBJECT_SPRITE_INSTANCE_LIMIT: u64 = 500;
|
pub const OBJECT_SPRITE_INSTANCE_LIMIT: u64 = 500;
|
||||||
|
|
|
@ -17,6 +17,8 @@ readme = { workspace = true }
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
galactica-packer = { workspace = true }
|
||||||
|
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
|
@ -7,24 +7,28 @@
|
||||||
//! in our code. It's managable, but the approach here is simpler and easier to understand.
|
//! in our code. It's managable, but the approach here is simpler and easier to understand.
|
||||||
use std::{cmp::Eq, hash::Hash};
|
use std::{cmp::Eq, hash::Hash};
|
||||||
|
|
||||||
/// A lightweight representation of a
|
/// A lightweight representation of a sprite
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct TextureHandle {
|
pub struct SpriteHandle {
|
||||||
/// The index of this texture in content.textures
|
/// The index of this sprite in content.sprites
|
||||||
pub(crate) index: usize,
|
/// This must be public, since render uses this to
|
||||||
|
/// select sprites.
|
||||||
|
///
|
||||||
|
/// This is a u32 for that same reason, too.
|
||||||
|
pub index: u32,
|
||||||
|
|
||||||
/// The aspect ratio of this texture (width / height)
|
/// The aspect ratio of this sprite (width / height)
|
||||||
pub aspect: f32,
|
pub aspect: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for TextureHandle {
|
impl Hash for SpriteHandle {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.index.hash(state)
|
self.index.hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for TextureHandle {}
|
impl Eq for SpriteHandle {}
|
||||||
impl PartialEq for TextureHandle {
|
impl PartialEq for SpriteHandle {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.index.eq(&other.index)
|
self.index.eq(&other.index)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ mod part;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::File,
|
fs::File,
|
||||||
|
@ -17,10 +18,10 @@ use std::{
|
||||||
use toml;
|
use toml;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SystemHandle, TextureHandle};
|
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SpriteHandle, SystemHandle};
|
||||||
pub use part::{
|
pub use part::{
|
||||||
EnginePoint, Faction, Gun, GunPoint, ImpactInheritVelocity, Outfit, OutfitSpace, Projectile,
|
EnginePoint, Faction, Gun, GunPoint, ImpactInheritVelocity, Outfit, OutfitSpace, Projectile,
|
||||||
ProjectileCollider, ProjectileParticle, Relationship, RepeatMode, Ship, System, Texture,
|
ProjectileCollider, ProjectileParticle, Relationship, RepeatMode, Ship, Sprite, System,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod syntax {
|
mod syntax {
|
||||||
|
@ -28,7 +29,7 @@ mod syntax {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, fmt::Display, hash::Hash};
|
use std::{collections::HashMap, fmt::Display, hash::Hash};
|
||||||
|
|
||||||
use crate::part::{faction, gun, outfit, ship, system, texture};
|
use crate::part::{faction, gun, outfit, ship, sprite, system};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
|
@ -36,7 +37,7 @@ mod syntax {
|
||||||
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
|
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
|
||||||
pub system: Option<HashMap<String, system::syntax::System>>,
|
pub system: Option<HashMap<String, system::syntax::System>>,
|
||||||
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
|
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
|
||||||
pub texture: Option<HashMap<String, texture::syntax::Texture>>,
|
pub sprite: Option<HashMap<String, sprite::syntax::Sprite>>,
|
||||||
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
|
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ mod syntax {
|
||||||
ship: None,
|
ship: None,
|
||||||
system: None,
|
system: None,
|
||||||
outfit: None,
|
outfit: None,
|
||||||
texture: None,
|
sprite: None,
|
||||||
faction: None,
|
faction: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,8 +85,8 @@ mod syntax {
|
||||||
.with_context(|| "while merging systems")?;
|
.with_context(|| "while merging systems")?;
|
||||||
merge_hashmap(&mut self.outfit, other.outfit)
|
merge_hashmap(&mut self.outfit, other.outfit)
|
||||||
.with_context(|| "while merging outfits")?;
|
.with_context(|| "while merging outfits")?;
|
||||||
merge_hashmap(&mut self.texture, other.texture)
|
merge_hashmap(&mut self.sprite, other.sprite)
|
||||||
.with_context(|| "while merging textures")?;
|
.with_context(|| "while merging sprites")?;
|
||||||
merge_hashmap(&mut self.faction, other.faction)
|
merge_hashmap(&mut self.faction, other.faction)
|
||||||
.with_context(|| "while merging factions")?;
|
.with_context(|| "while merging factions")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -94,10 +95,10 @@ mod syntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Build {
|
trait Build {
|
||||||
type InputSyntax;
|
type InputSyntaxType;
|
||||||
|
|
||||||
/// Build a processed System struct from raw serde data
|
/// Build a processed System struct from raw serde data
|
||||||
fn build(root: Self::InputSyntax, ct: &mut Content) -> Result<()>
|
fn build(root: Self::InputSyntaxType, ct: &mut Content) -> Result<()>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
@ -106,18 +107,21 @@ trait Build {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Content {
|
pub struct Content {
|
||||||
/* Configuration values */
|
/* Configuration values */
|
||||||
/// Root directory for textures
|
/// Root directory for image
|
||||||
texture_root: PathBuf,
|
image_root: PathBuf,
|
||||||
/// Name of starfield texture
|
/// Name of starfield sprite
|
||||||
starfield_texture_name: String,
|
starfield_sprite_name: String,
|
||||||
|
|
||||||
/// Textures
|
/// Sprites
|
||||||
pub textures: Vec<part::texture::Texture>,
|
pub sprites: Vec<part::sprite::Sprite>,
|
||||||
/// Map strings to texture names.
|
/// Map strings to texture names.
|
||||||
/// This is only necessary because we need to hard-code a few texture names for UI elements.
|
/// This is only necessary because we need to hard-code a few texture names for UI elements.
|
||||||
texture_index: HashMap<String, handle::TextureHandle>,
|
sprite_index: HashMap<String, handle::SpriteHandle>,
|
||||||
/// The texture to use for starfield stars
|
/// The texture to use for starfield stars
|
||||||
starfield_handle: Option<handle::TextureHandle>,
|
starfield_handle: Option<handle::SpriteHandle>,
|
||||||
|
|
||||||
|
/// Keeps track of which images are in which texture
|
||||||
|
sprite_atlas: SpriteAtlas,
|
||||||
|
|
||||||
/// Outfits
|
/// Outfits
|
||||||
outfits: Vec<part::outfit::Outfit>,
|
outfits: Vec<part::outfit::Outfit>,
|
||||||
|
@ -148,6 +152,7 @@ impl Content {
|
||||||
pub fn load_dir(
|
pub fn load_dir(
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
texture_root: PathBuf,
|
texture_root: PathBuf,
|
||||||
|
atlas_index: PathBuf,
|
||||||
starfield_texture_name: String,
|
starfield_texture_name: String,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut root = syntax::Root::new();
|
let mut root = syntax::Root::new();
|
||||||
|
@ -177,23 +182,32 @@ impl Content {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let atlas: SpriteAtlas = {
|
||||||
|
let mut file_string = String::new();
|
||||||
|
let _ = File::open(atlas_index)?.read_to_string(&mut file_string);
|
||||||
|
let file_string = file_string.trim();
|
||||||
|
toml::from_str(&file_string)?
|
||||||
|
};
|
||||||
|
|
||||||
let mut content = Self {
|
let mut content = Self {
|
||||||
|
sprite_atlas: atlas,
|
||||||
systems: Vec::new(),
|
systems: Vec::new(),
|
||||||
ships: Vec::new(),
|
ships: Vec::new(),
|
||||||
guns: Vec::new(),
|
guns: Vec::new(),
|
||||||
outfits: Vec::new(),
|
outfits: Vec::new(),
|
||||||
textures: Vec::new(),
|
sprites: Vec::new(),
|
||||||
factions: Vec::new(),
|
factions: Vec::new(),
|
||||||
texture_index: HashMap::new(),
|
sprite_index: HashMap::new(),
|
||||||
starfield_handle: None,
|
starfield_handle: None,
|
||||||
texture_root,
|
image_root: texture_root,
|
||||||
starfield_texture_name,
|
starfield_sprite_name: starfield_texture_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Order here matters, usually
|
// Order here matters, usually
|
||||||
if root.texture.is_some() {
|
if root.sprite.is_some() {
|
||||||
part::texture::Texture::build(root.texture.take().unwrap(), &mut content)?;
|
part::sprite::Sprite::build(root.sprite.take().unwrap(), &mut content)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.ship.is_some() {
|
if root.ship.is_some() {
|
||||||
part::ship::Ship::build(root.ship.take().unwrap(), &mut content)?;
|
part::ship::Ship::build(root.ship.take().unwrap(), &mut content)?;
|
||||||
}
|
}
|
||||||
|
@ -216,27 +230,32 @@ impl Content {
|
||||||
|
|
||||||
// Access methods
|
// Access methods
|
||||||
impl Content {
|
impl Content {
|
||||||
/// Get the texture handle for the starfield texture
|
/// Get the handle for the starfield sprite
|
||||||
pub fn get_starfield_handle(&self) -> TextureHandle {
|
pub fn get_starfield_handle(&self) -> SpriteHandle {
|
||||||
match self.starfield_handle {
|
match self.starfield_handle {
|
||||||
Some(h) => h,
|
Some(h) => h,
|
||||||
None => unreachable!("Starfield texture hasn't been loaded yet!"),
|
None => unreachable!("Starfield sprite hasn't been loaded yet!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a handle from a texture name
|
/// Get a handle from a sprite name
|
||||||
pub fn get_texture_handle(&self, name: &str) -> TextureHandle {
|
pub fn get_sprite_handle(&self, name: &str) -> SpriteHandle {
|
||||||
return match self.texture_index.get(name) {
|
return match self.sprite_index.get(name) {
|
||||||
Some(s) => *s,
|
Some(s) => *s,
|
||||||
None => unreachable!("get_texture_handle was called with a bad handle!"),
|
None => unreachable!("get_sprite_handle was called with a bad name!"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a texture from a handle
|
/// Get a sprite from a handle
|
||||||
pub fn get_texture(&self, h: TextureHandle) -> &Texture {
|
pub fn get_sprite(&self, h: SpriteHandle) -> &Sprite {
|
||||||
// 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.
|
||||||
// The only TextureHandles that exist should be created by this crate.
|
// The only handles that exist should be created by this crate.
|
||||||
return &self.textures[h.index];
|
return &self.sprites[h.index as usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a sprite from a path
|
||||||
|
pub fn get_image(&self, p: &Path) -> &SpriteAtlasImage {
|
||||||
|
self.sprite_atlas.index.get(p).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an outfit from a handle
|
/// Get an outfit from a handle
|
||||||
|
@ -249,7 +268,7 @@ impl Content {
|
||||||
return &self.guns[h.index];
|
return &self.guns[h.index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a texture from a handle
|
/// Get a ship from a handle
|
||||||
pub fn get_ship(&self, h: ShipHandle) -> &Ship {
|
pub fn get_ship(&self, h: ShipHandle) -> &Ship {
|
||||||
return &self.ships[h.index];
|
return &self.ships[h.index];
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,9 +59,9 @@ pub struct Faction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Faction {
|
impl crate::Build for Faction {
|
||||||
type InputSyntax = HashMap<String, syntax::Faction>;
|
type InputSyntaxType = HashMap<String, syntax::Faction>;
|
||||||
|
|
||||||
fn build(factions: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
fn build(factions: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
// Keeps track of position in faction array.
|
// Keeps track of position in faction array.
|
||||||
// This lets us build FactionHandles before finishing all factions.
|
// This lets us build FactionHandles before finishing all factions.
|
||||||
let faction_names: Vec<String> = factions.keys().map(|x| x.to_owned()).collect();
|
let faction_names: Vec<String> = factions.keys().map(|x| x.to_owned()).collect();
|
||||||
|
|
|
@ -3,7 +3,7 @@ use cgmath::Deg;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{handle::TextureHandle, Content};
|
use crate::{handle::SpriteHandle, Content};
|
||||||
|
|
||||||
use crate::OutfitSpace;
|
use crate::OutfitSpace;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Projectile {
|
pub struct Projectile {
|
||||||
pub sprite_texture: String,
|
pub sprite: String,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
pub size_rng: f32,
|
pub size_rng: f32,
|
||||||
pub speed: f32,
|
pub speed: f32,
|
||||||
|
@ -40,7 +40,7 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ProjectileParticle {
|
pub struct ProjectileParticle {
|
||||||
pub texture: String,
|
pub sprite: String,
|
||||||
pub lifetime: ParticleLifetime,
|
pub lifetime: ParticleLifetime,
|
||||||
pub inherit_velocity: super::ImpactInheritVelocity,
|
pub inherit_velocity: super::ImpactInheritVelocity,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
@ -110,7 +110,7 @@ pub struct Gun {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Projectile {
|
pub struct Projectile {
|
||||||
/// The projectile sprite
|
/// The projectile sprite
|
||||||
pub sprite_texture: TextureHandle,
|
pub sprite: SpriteHandle,
|
||||||
|
|
||||||
/// The average size of this projectile
|
/// The average size of this projectile
|
||||||
/// (height in game units)
|
/// (height in game units)
|
||||||
|
@ -155,9 +155,9 @@ pub struct Projectile {
|
||||||
/// The particle a projectile will spawn when it hits something
|
/// The particle a projectile will spawn when it hits something
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ProjectileParticle {
|
pub struct ProjectileParticle {
|
||||||
/// The texture to use for this particle.
|
/// The sprite to use for this particle.
|
||||||
/// This is most likely animated.
|
/// This is most likely animated.
|
||||||
pub texture: TextureHandle,
|
pub sprite: SpriteHandle,
|
||||||
|
|
||||||
/// How many seconds this particle should live
|
/// How many seconds this particle should live
|
||||||
pub lifetime: f32,
|
pub lifetime: f32,
|
||||||
|
@ -174,8 +174,8 @@ fn parse_projectile_particle(
|
||||||
p: Option<syntax::ProjectileParticle>,
|
p: Option<syntax::ProjectileParticle>,
|
||||||
) -> Result<Option<ProjectileParticle>> {
|
) -> Result<Option<ProjectileParticle>> {
|
||||||
if let Some(impact) = p {
|
if let Some(impact) = p {
|
||||||
let impact_texture = match ct.texture_index.get(&impact.texture) {
|
let impact_sprite_handle = match ct.sprite_index.get(&impact.sprite) {
|
||||||
None => bail!("impact texture `{}` doesn't exist", impact.texture),
|
None => bail!("impact sprite `{}` doesn't exist", impact.sprite),
|
||||||
Some(t) => *t,
|
Some(t) => *t,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -183,8 +183,8 @@ fn parse_projectile_particle(
|
||||||
syntax::ParticleLifetime::Seconds(s) => s,
|
syntax::ParticleLifetime::Seconds(s) => s,
|
||||||
syntax::ParticleLifetime::Inherit(s) => {
|
syntax::ParticleLifetime::Inherit(s) => {
|
||||||
if s == "inherit" {
|
if s == "inherit" {
|
||||||
let t = ct.get_texture(impact_texture);
|
let sprite = ct.get_sprite(impact_sprite_handle);
|
||||||
t.fps * t.frames.len() as f32
|
sprite.fps * sprite.frames.len() as f32
|
||||||
} else {
|
} else {
|
||||||
bail!("bad impact lifetime, must be float or \"inherit\"",)
|
bail!("bad impact lifetime, must be float or \"inherit\"",)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ fn parse_projectile_particle(
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(ProjectileParticle {
|
Ok(Some(ProjectileParticle {
|
||||||
texture: impact_texture,
|
sprite: impact_sprite_handle,
|
||||||
lifetime: impact_lifetime,
|
lifetime: impact_lifetime,
|
||||||
inherit_velocity: impact.inherit_velocity,
|
inherit_velocity: impact.inherit_velocity,
|
||||||
size: impact.size,
|
size: impact.size,
|
||||||
|
@ -203,15 +203,15 @@ fn parse_projectile_particle(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Gun {
|
impl crate::Build for Gun {
|
||||||
type InputSyntax = HashMap<String, syntax::Gun>;
|
type InputSyntaxType = HashMap<String, syntax::Gun>;
|
||||||
|
|
||||||
fn build(gun: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
fn build(gun: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
for (gun_name, gun) in gun {
|
for (gun_name, gun) in gun {
|
||||||
let projectile_texture = match ct.texture_index.get(&gun.projectile.sprite_texture) {
|
let projectile_sprite_handle = match ct.sprite_index.get(&gun.projectile.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In gun `{}`: projectile texture `{}` doesn't exist",
|
"In gun `{}`: projectile sprite `{}` doesn't exist",
|
||||||
gun_name,
|
gun_name,
|
||||||
gun.projectile.sprite_texture
|
gun.projectile.sprite
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => *t,
|
||||||
};
|
};
|
||||||
|
@ -229,7 +229,7 @@ impl crate::Build for Gun {
|
||||||
rate_rng: gun.rate_rng,
|
rate_rng: gun.rate_rng,
|
||||||
projectile: Projectile {
|
projectile: Projectile {
|
||||||
force: gun.projectile.force,
|
force: gun.projectile.force,
|
||||||
sprite_texture: projectile_texture,
|
sprite: projectile_sprite_handle,
|
||||||
size: gun.projectile.size,
|
size: gun.projectile.size,
|
||||||
size_rng: gun.projectile.size_rng,
|
size_rng: gun.projectile.size_rng,
|
||||||
speed: gun.projectile.speed,
|
speed: gun.projectile.speed,
|
||||||
|
|
|
@ -5,13 +5,13 @@ pub mod gun;
|
||||||
pub mod outfit;
|
pub mod outfit;
|
||||||
mod shared;
|
mod shared;
|
||||||
pub mod ship;
|
pub mod ship;
|
||||||
|
pub mod sprite;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
pub mod texture;
|
|
||||||
|
|
||||||
pub use faction::{Faction, Relationship};
|
pub use faction::{Faction, Relationship};
|
||||||
pub use gun::{Gun, ImpactInheritVelocity, Projectile, ProjectileCollider, ProjectileParticle};
|
pub use gun::{Gun, ImpactInheritVelocity, Projectile, ProjectileCollider, ProjectileParticle};
|
||||||
pub use outfit::Outfit;
|
pub use outfit::Outfit;
|
||||||
pub use shared::OutfitSpace;
|
pub use shared::OutfitSpace;
|
||||||
pub use ship::{EnginePoint, GunPoint, Ship};
|
pub use ship::{EnginePoint, GunPoint, Ship};
|
||||||
|
pub use sprite::{RepeatMode, Sprite};
|
||||||
pub use system::{Object, System};
|
pub use system::{Object, System};
|
||||||
pub use texture::{RepeatMode, Texture};
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
use crate::{handle::TextureHandle, Content, OutfitSpace};
|
use crate::{handle::SpriteHandle, Content, OutfitSpace};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::part::shared;
|
use crate::part::shared;
|
||||||
|
@ -20,7 +20,7 @@ pub(crate) mod syntax {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
pub thrust: f32,
|
pub thrust: f32,
|
||||||
pub flare_texture: String,
|
pub flare_sprite: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -44,37 +44,37 @@ pub struct Outfit {
|
||||||
/// The engine flare sprite this outfit creates.
|
/// The engine flare sprite this outfit creates.
|
||||||
/// Its location and size is determined by a ship's
|
/// Its location and size is determined by a ship's
|
||||||
/// engine points.
|
/// engine points.
|
||||||
pub engine_flare_texture: Option<TextureHandle>,
|
pub engine_flare_sprite: Option<SpriteHandle>,
|
||||||
|
|
||||||
/// How much space this outfit requires
|
/// How much space this outfit requires
|
||||||
pub space: OutfitSpace,
|
pub space: OutfitSpace,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Outfit {
|
impl crate::Build for Outfit {
|
||||||
type InputSyntax = HashMap<String, syntax::Outfit>;
|
type InputSyntaxType = HashMap<String, syntax::Outfit>;
|
||||||
|
|
||||||
fn build(outfits: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
fn build(outfits: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
for (outfit_name, outfit) in outfits {
|
for (outfit_name, outfit) in outfits {
|
||||||
let mut o = Self {
|
let mut o = Self {
|
||||||
name: outfit_name.clone(),
|
name: outfit_name.clone(),
|
||||||
engine_thrust: 0.0,
|
engine_thrust: 0.0,
|
||||||
steer_power: 0.0,
|
steer_power: 0.0,
|
||||||
engine_flare_texture: None,
|
engine_flare_sprite: None,
|
||||||
space: OutfitSpace::from(outfit.space),
|
space: OutfitSpace::from(outfit.space),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Engine stats
|
// Engine stats
|
||||||
if let Some(engine) = outfit.engine {
|
if let Some(engine) = outfit.engine {
|
||||||
let th = match ct.texture_index.get(&engine.flare_texture) {
|
let th = match ct.sprite_index.get(&engine.flare_sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In outfit `{}`: texture `{}` doesn't exist",
|
"In outfit `{}`: flare sprite `{}` doesn't exist",
|
||||||
outfit_name,
|
outfit_name,
|
||||||
engine.flare_texture
|
engine.flare_sprite
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => *t,
|
||||||
};
|
};
|
||||||
o.engine_thrust = engine.thrust;
|
o.engine_thrust = engine.thrust;
|
||||||
o.engine_flare_texture = Some(th);
|
o.engine_flare_sprite = Some(th);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Steering stats
|
// Steering stats
|
||||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::{bail, Result};
|
||||||
use cgmath::Point2;
|
use cgmath::Point2;
|
||||||
use nalgebra::{point, Point};
|
use nalgebra::{point, Point};
|
||||||
|
|
||||||
use crate::{handle::TextureHandle, Content, OutfitSpace};
|
use crate::{handle::SpriteHandle, Content, OutfitSpace};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::part::shared;
|
use crate::part::shared;
|
||||||
|
@ -15,7 +15,7 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
pub sprite_texture: String,
|
pub sprite: String,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
pub engines: Vec<Engine>,
|
pub engines: Vec<Engine>,
|
||||||
pub guns: Vec<Gun>,
|
pub guns: Vec<Gun>,
|
||||||
|
@ -57,7 +57,7 @@ pub struct Ship {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
/// This ship's sprite
|
/// This ship's sprite
|
||||||
pub sprite_texture: TextureHandle,
|
pub sprite: SpriteHandle,
|
||||||
|
|
||||||
/// The size of this ship.
|
/// The size of this ship.
|
||||||
/// Measured as unrotated height,
|
/// Measured as unrotated height,
|
||||||
|
@ -123,26 +123,26 @@ pub struct GunPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Ship {
|
impl crate::Build for Ship {
|
||||||
type InputSyntax = HashMap<String, syntax::Ship>;
|
type InputSyntaxType = HashMap<String, syntax::Ship>;
|
||||||
|
|
||||||
fn build(ship: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
fn build(ship: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
for (ship_name, ship) in ship {
|
for (ship_name, ship) in ship {
|
||||||
let th = match ct.texture_index.get(&ship.sprite_texture) {
|
let handle = match ct.sprite_index.get(&ship.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In ship `{}`: texture `{}` doesn't exist",
|
"In ship `{}`: sprite `{}` doesn't exist",
|
||||||
ship_name,
|
ship_name,
|
||||||
ship.sprite_texture
|
ship.sprite
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => *t,
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = ship.size;
|
let size = ship.size;
|
||||||
let aspect = th.aspect;
|
let aspect = ct.get_sprite(handle).aspect;
|
||||||
|
|
||||||
ct.ships.push(Self {
|
ct.ships.push(Self {
|
||||||
aspect,
|
aspect,
|
||||||
name: ship_name,
|
name: ship_name,
|
||||||
sprite_texture: th,
|
sprite: handle,
|
||||||
mass: ship.mass,
|
mass: ship.mass,
|
||||||
space: OutfitSpace::from(ship.space),
|
space: OutfitSpace::from(ship.space),
|
||||||
angular_drag: ship.angular_drag,
|
angular_drag: ship.angular_drag,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use image::io::Reader;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use crate::{handle::TextureHandle, Content};
|
use crate::{handle::SpriteHandle, Content};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -16,18 +16,18 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Texture {
|
pub enum Sprite {
|
||||||
Static(StaticTexture),
|
Static(StaticSprite),
|
||||||
Frames(FramesTexture),
|
Frames(FrameSprite),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct StaticTexture {
|
pub struct StaticSprite {
|
||||||
pub file: PathBuf,
|
pub file: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct FramesTexture {
|
pub struct FrameSprite {
|
||||||
pub frames: Vec<PathBuf>,
|
pub frames: Vec<PathBuf>,
|
||||||
pub duration: f32,
|
pub duration: f32,
|
||||||
pub repeat: RepeatMode,
|
pub repeat: RepeatMode,
|
||||||
|
@ -57,90 +57,94 @@ impl RepeatMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a texture that may be used in the game.
|
/// Represents a sprite that may be used in the game.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Texture {
|
pub struct Sprite {
|
||||||
/// The name of this texture
|
/// The name of this sprite
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
/// The handle for this texture
|
/// This sprite's handle
|
||||||
pub handle: TextureHandle,
|
pub handle: SpriteHandle,
|
||||||
|
|
||||||
/// The frames of this texture
|
/// The file names of frames of this sprite.
|
||||||
/// (static textures have one frame)
|
/// unanimated sprites have one frame.
|
||||||
pub frames: Vec<PathBuf>,
|
pub frames: Vec<PathBuf>,
|
||||||
|
|
||||||
/// The speed of this texture's animation
|
/// The speed of this sprite's animation.
|
||||||
/// (static textures have zero fps)
|
/// unanimated sprites have zero fps.
|
||||||
pub fps: f32,
|
pub fps: f32,
|
||||||
|
|
||||||
/// How to replay this texture's animation
|
/// How to replay this sprite's animation
|
||||||
pub repeat: RepeatMode,
|
pub repeat: RepeatMode,
|
||||||
|
|
||||||
|
/// Aspect ratio of this sprite (width / height)
|
||||||
|
pub aspect: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Texture {
|
impl crate::Build for Sprite {
|
||||||
type InputSyntax = HashMap<String, syntax::Texture>;
|
type InputSyntaxType = HashMap<String, syntax::Sprite>;
|
||||||
|
|
||||||
fn build(texture: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
fn build(sprites: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
for (texture_name, t) in texture {
|
for (sprite_name, t) in sprites {
|
||||||
match t {
|
match t {
|
||||||
syntax::Texture::Static(t) => {
|
syntax::Sprite::Static(t) => {
|
||||||
let file = ct.texture_root.join(t.file);
|
let file = ct.image_root.join(&t.file);
|
||||||
let reader = Reader::open(&file).with_context(|| {
|
let reader = Reader::open(&file).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to read texture `{}` from file `{}`",
|
"Failed to read file `{}` in sprite `{}`",
|
||||||
texture_name,
|
file.display(),
|
||||||
file.display()
|
sprite_name,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let dim = reader.into_dimensions().with_context(|| {
|
let dim = reader.into_dimensions().with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to get dimensions of texture `{}` from file `{}`",
|
"Failed to get dimensions of file `{}` in sprite `{}`",
|
||||||
texture_name,
|
file.display(),
|
||||||
file.display()
|
sprite_name,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let h = TextureHandle {
|
let h = SpriteHandle {
|
||||||
index: ct.textures.len(),
|
index: ct.sprites.len() as u32,
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
if texture_name == ct.starfield_texture_name {
|
if sprite_name == ct.starfield_sprite_name {
|
||||||
if ct.starfield_handle.is_none() {
|
if ct.starfield_handle.is_none() {
|
||||||
ct.starfield_handle = Some(h)
|
ct.starfield_handle = Some(h)
|
||||||
} else {
|
} else {
|
||||||
// This can't happen, since this is a hashmap.
|
// This can't happen, since this is a hashmap.
|
||||||
unreachable!("Found two starfield textures! Something is very wrong.")
|
unreachable!("Found two starfield sprites! Something is very wrong.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ct.texture_index.insert(texture_name.clone(), h);
|
ct.sprite_index.insert(sprite_name.clone(), h);
|
||||||
|
|
||||||
ct.textures.push(Self {
|
ct.sprites.push(Self {
|
||||||
name: texture_name,
|
name: sprite_name,
|
||||||
frames: vec![file],
|
frames: vec![t.file],
|
||||||
fps: 0.0,
|
fps: 0.0,
|
||||||
handle: h,
|
handle: h,
|
||||||
repeat: RepeatMode::Once,
|
repeat: RepeatMode::Once,
|
||||||
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
syntax::Texture::Frames(t) => {
|
syntax::Sprite::Frames(t) => {
|
||||||
let mut dim = None;
|
let mut dim = None;
|
||||||
for f in &t.frames {
|
for f in &t.frames {
|
||||||
let file = ct.texture_root.join(f);
|
let file = ct.image_root.join(f);
|
||||||
let reader = Reader::open(&file).with_context(|| {
|
let reader = Reader::open(&file).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to read texture `{}` from file `{}`",
|
"Failed to read file `{}` in sprite `{}`",
|
||||||
texture_name,
|
file.display(),
|
||||||
file.display()
|
sprite_name,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let d = reader.into_dimensions().with_context(|| {
|
let d = reader.into_dimensions().with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to get dimensions of texture `{}` from file `{}`",
|
"Failed to get dimensions of file `{}` in sprite `{}`",
|
||||||
texture_name,
|
file.display(),
|
||||||
file.display()
|
sprite_name,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
match dim {
|
match dim {
|
||||||
|
@ -148,37 +152,34 @@ impl crate::Build for Texture {
|
||||||
Some(e) => {
|
Some(e) => {
|
||||||
if d != e {
|
if d != e {
|
||||||
bail!(
|
bail!(
|
||||||
"Failed to load frames of texture `{}`. Frames have different sizes `{}`",
|
"Failed to load frames of sprite `{}` because frames have different sizes.",
|
||||||
texture_name,
|
sprite_name,
|
||||||
file.display()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let dim = dim.unwrap();
|
||||||
|
|
||||||
let h = TextureHandle {
|
let h = SpriteHandle {
|
||||||
index: ct.textures.len(),
|
index: ct.sprites.len() as u32,
|
||||||
aspect: dim.unwrap().0 as f32 / dim.unwrap().1 as f32,
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
if texture_name == ct.starfield_texture_name {
|
if sprite_name == ct.starfield_sprite_name {
|
||||||
unreachable!("Starfield texture may not be animated")
|
unreachable!("Starfield texture may not be animated")
|
||||||
}
|
}
|
||||||
|
|
||||||
let fps = t.duration / t.frames.len() as f32;
|
let fps = t.duration / t.frames.len() as f32;
|
||||||
|
|
||||||
ct.texture_index.insert(texture_name.clone(), h);
|
ct.sprite_index.insert(sprite_name.clone(), h);
|
||||||
ct.textures.push(Self {
|
ct.sprites.push(Self {
|
||||||
name: texture_name,
|
name: sprite_name,
|
||||||
frames: t
|
frames: t.frames,
|
||||||
.frames
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| ct.texture_root.join(f))
|
|
||||||
.collect(),
|
|
||||||
fps,
|
fps,
|
||||||
handle: h,
|
handle: h,
|
||||||
repeat: t.repeat,
|
repeat: t.repeat,
|
||||||
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,7 +188,7 @@ impl crate::Build for Texture {
|
||||||
if ct.starfield_handle.is_none() {
|
if ct.starfield_handle.is_none() {
|
||||||
bail!(
|
bail!(
|
||||||
"Could not find a starfield texture (name: `{}`)",
|
"Could not find a starfield texture (name: `{}`)",
|
||||||
ct.starfield_texture_name
|
ct.starfield_sprite_name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::{bail, Context, Result};
|
||||||
use cgmath::{Deg, Point3};
|
use cgmath::{Deg, Point3};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::{handle::TextureHandle, util::Polar, Content};
|
use crate::{handle::SpriteHandle, util::Polar, Content};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -17,7 +17,7 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
pub sprite_texture: String,
|
pub sprite: String,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
@ -94,7 +94,7 @@ pub struct System {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
/// This object's sprite
|
/// This object's sprite
|
||||||
pub sprite_texture: TextureHandle,
|
pub sprite: SpriteHandle,
|
||||||
|
|
||||||
/// This object's size.
|
/// This object's size.
|
||||||
/// Measured as height in game units.
|
/// Measured as height in game units.
|
||||||
|
@ -175,9 +175,9 @@ fn resolve_position(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for System {
|
impl crate::Build for System {
|
||||||
type InputSyntax = HashMap<String, syntax::System>;
|
type InputSyntaxType = HashMap<String, syntax::System>;
|
||||||
|
|
||||||
fn build(system: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
fn build(system: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
for (system_name, system) in system {
|
for (system_name, system) in system {
|
||||||
let mut objects = Vec::new();
|
let mut objects = Vec::new();
|
||||||
|
|
||||||
|
@ -185,17 +185,17 @@ impl crate::Build for System {
|
||||||
let mut cycle_detector = HashSet::new();
|
let mut cycle_detector = HashSet::new();
|
||||||
cycle_detector.insert(label.clone());
|
cycle_detector.insert(label.clone());
|
||||||
|
|
||||||
let th = match ct.texture_index.get(&obj.sprite_texture) {
|
let handle = match ct.sprite_index.get(&obj.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In system `{}`: texture `{}` doesn't exist",
|
"In system `{}`: sprite `{}` doesn't exist",
|
||||||
system_name,
|
system_name,
|
||||||
obj.sprite_texture
|
obj.sprite
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => *t,
|
||||||
};
|
};
|
||||||
|
|
||||||
objects.push(Object {
|
objects.push(Object {
|
||||||
sprite_texture: th,
|
sprite: handle,
|
||||||
position: resolve_position(&system.object, &obj, cycle_detector)
|
position: resolve_position(&system.object, &obj, cycle_detector)
|
||||||
.with_context(|| format!("In object {:#?}", label))?,
|
.with_context(|| format!("In object {:#?}", label))?,
|
||||||
size: obj.size,
|
size: obj.size,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "galactic"
|
name = "galactica"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
|
|
|
@ -10,7 +10,7 @@ use galactica_behavior::{behavior, ShipBehavior};
|
||||||
use galactica_constants;
|
use galactica_constants;
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
use galactica_render::{FrameState, ObjectSprite, ParticleBuilder, UiSprite};
|
use galactica_render::{ObjectSprite, ParticleBuilder, RenderState, UiSprite};
|
||||||
use galactica_ui as ui;
|
use galactica_ui as ui;
|
||||||
use galactica_world::{util, ShipPhysicsHandle, World};
|
use galactica_world::{util, ShipPhysicsHandle, World};
|
||||||
|
|
||||||
|
@ -174,14 +174,15 @@ impl Game {
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_frame_state(&mut self) -> FrameState {
|
pub fn get_frame_state(&mut self) -> RenderState {
|
||||||
FrameState {
|
RenderState {
|
||||||
camera_pos: self.camera.pos,
|
camera_pos: self.camera.pos,
|
||||||
camera_zoom: self.camera.zoom,
|
camera_zoom: self.camera.zoom,
|
||||||
object_sprites: self.get_object_sprites(),
|
object_sprites: self.get_object_sprites(),
|
||||||
ui_sprites: self.get_ui_sprites(),
|
ui_sprites: self.get_ui_sprites(),
|
||||||
new_particles: &mut self.new_particles,
|
new_particles: &mut self.new_particles,
|
||||||
current_time: self.start_instant.elapsed().as_secs_f32(),
|
current_time: self.start_instant.elapsed().as_secs_f32(),
|
||||||
|
content: &self.content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,15 @@ fn main() -> Result<()> {
|
||||||
// TODO: error if missing
|
// TODO: error if missing
|
||||||
let content = content::Content::load_dir(
|
let content = content::Content::load_dir(
|
||||||
PathBuf::from(galactica_constants::CONTENT_ROOT),
|
PathBuf::from(galactica_constants::CONTENT_ROOT),
|
||||||
PathBuf::from(galactica_constants::TEXTURE_ROOT),
|
PathBuf::from(galactica_constants::IMAGE_ROOT),
|
||||||
PathBuf::from("spriteatlas.toml"),
|
PathBuf::from("spriteatlas.toml"),
|
||||||
galactica_constants::STARFIELD_TEXTURE_NAME.to_owned(),
|
galactica_constants::STARFIELD_SPRITE_NAME.to_owned(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
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(galactica_render::GPUState::new(window, &content))?;
|
let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?;
|
||||||
|
gpu.init();
|
||||||
|
|
||||||
let mut game = game::Game::new(content);
|
let mut game = game::Game::new(content);
|
||||||
gpu.update_starfield_buffer();
|
gpu.update_starfield_buffer();
|
||||||
|
|
|
@ -33,7 +33,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<content::TextureHandle>,
|
pub engine_flare_sprites: Vec<content::SpriteHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutfitStatSum {
|
impl OutfitStatSum {
|
||||||
|
@ -41,23 +41,23 @@ impl OutfitStatSum {
|
||||||
Self {
|
Self {
|
||||||
engine_thrust: 0.0,
|
engine_thrust: 0.0,
|
||||||
steer_power: 0.0,
|
steer_power: 0.0,
|
||||||
engine_flare_textures: Vec::new(),
|
engine_flare_sprites: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, o: &content::Outfit) {
|
pub fn add(&mut self, o: &content::Outfit) {
|
||||||
self.engine_thrust += o.engine_thrust;
|
self.engine_thrust += o.engine_thrust;
|
||||||
if let Some(t) = o.engine_flare_texture {
|
if let Some(t) = o.engine_flare_sprite {
|
||||||
self.engine_flare_textures.push(t);
|
self.engine_flare_sprites.push(t);
|
||||||
};
|
};
|
||||||
self.steer_power += o.steer_power;
|
self.steer_power += o.steer_power;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, o: &content::Outfit) {
|
pub fn remove(&mut self, o: &content::Outfit) {
|
||||||
self.engine_thrust -= o.engine_thrust;
|
self.engine_thrust -= o.engine_thrust;
|
||||||
if let Some(t) = o.engine_flare_texture {
|
if let Some(t) = o.engine_flare_sprite {
|
||||||
self.engine_flare_textures.remove(
|
self.engine_flare_sprites.remove(
|
||||||
self.engine_flare_textures
|
self.engine_flare_sprites
|
||||||
.iter()
|
.iter()
|
||||||
.position(|x| *x == t)
|
.position(|x| *x == t)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -185,7 +185,7 @@ impl<'a> OutfitSet {
|
||||||
pub fn update_engine_flares(&mut self) {
|
pub fn update_engine_flares(&mut self) {
|
||||||
// TODO: better way to pick flare texture
|
// TODO: better way to pick flare texture
|
||||||
self.engine_flare_sprites.clear();
|
self.engine_flare_sprites.clear();
|
||||||
let t = if let Some(e) = self.stats.engine_flare_textures.iter().next() {
|
let s = if let Some(e) = self.stats.engine_flare_sprites.iter().next() {
|
||||||
e
|
e
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
|
@ -200,7 +200,7 @@ impl<'a> OutfitSet {
|
||||||
y: p.pos.y,
|
y: p.pos.y,
|
||||||
z: 1.0,
|
z: 1.0,
|
||||||
},
|
},
|
||||||
texture: *t,
|
sprite: *s,
|
||||||
angle: Deg(0.0),
|
angle: Deg(0.0),
|
||||||
size: p.size,
|
size: p.size,
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,7 +18,7 @@ impl System {
|
||||||
for o in &sys.objects {
|
for o in &sys.objects {
|
||||||
s.bodies.push(SystemObject {
|
s.bodies.push(SystemObject {
|
||||||
pos: o.position,
|
pos: o.position,
|
||||||
sprite_texture: o.sprite_texture,
|
sprite: o.sprite,
|
||||||
size: o.size,
|
size: o.size,
|
||||||
angle: o.angle,
|
angle: o.angle,
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ use galactica_content as content;
|
||||||
use galactica_render::ObjectSprite;
|
use galactica_render::ObjectSprite;
|
||||||
|
|
||||||
pub struct SystemObject {
|
pub struct SystemObject {
|
||||||
pub sprite_texture: content::TextureHandle,
|
pub sprite: content::SpriteHandle,
|
||||||
pub pos: Point3<f32>,
|
pub pos: Point3<f32>,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
pub angle: Deg<f32>,
|
pub angle: Deg<f32>,
|
||||||
|
@ -13,7 +13,7 @@ pub struct SystemObject {
|
||||||
impl SystemObject {
|
impl SystemObject {
|
||||||
pub(crate) fn get_sprite(&self) -> ObjectSprite {
|
pub(crate) fn get_sprite(&self) -> ObjectSprite {
|
||||||
return ObjectSprite {
|
return ObjectSprite {
|
||||||
texture: self.sprite_texture,
|
sprite: self.sprite,
|
||||||
pos: self.pos,
|
pos: self.pos,
|
||||||
angle: self.angle,
|
angle: self.angle,
|
||||||
size: self.size,
|
size: self.size,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use galactica_packer::{SpriteAtlasImage, SpriteAtlasIndex};
|
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
||||||
use image::{imageops, ImageBuffer, Rgba, RgbaImage};
|
use image::{imageops, ImageBuffer, Rgba, RgbaImage};
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
|
@ -11,6 +11,7 @@ use std::{
|
||||||
// TODO: rework texturearray
|
// TODO: rework texturearray
|
||||||
// TODO: reasonable sprite sizes
|
// TODO: reasonable sprite sizes
|
||||||
// TODO: consistent naming
|
// TODO: consistent naming
|
||||||
|
// TODO: parallelize
|
||||||
// spriteatlas: the big images
|
// spriteatlas: the big images
|
||||||
// texture: the same, what we load to wgpu
|
// texture: the same, what we load to wgpu
|
||||||
// image: a single file
|
// image: a single file
|
||||||
|
@ -28,13 +29,16 @@ pub struct AtlasSet {
|
||||||
texture_limit: usize,
|
texture_limit: usize,
|
||||||
|
|
||||||
/// Keeps track of image files
|
/// Keeps track of image files
|
||||||
index: SpriteAtlasIndex,
|
index: SpriteAtlas,
|
||||||
|
|
||||||
/// Array of textures, grows as needed
|
/// Array of textures, grows as needed
|
||||||
texture_list: Vec<ImageBuffer<Rgba<u8>, Vec<u8>>>,
|
texture_list: Vec<ImageBuffer<Rgba<u8>, Vec<u8>>>,
|
||||||
|
|
||||||
/// The size of the smallest image that didn't fit in each texture
|
/// The size of the smallest image that didn't fit in each texture
|
||||||
image_max_sizes: Vec<u32>,
|
image_max_sizes: Vec<[u32; 2]>,
|
||||||
|
|
||||||
|
/// (y-value, image size)
|
||||||
|
image_y_start: Vec<(u32, [u32; 2])>,
|
||||||
|
|
||||||
/// A list of used regions in each texture
|
/// A list of used regions in each texture
|
||||||
/// Format: ([xpos, ypos], [width, height])
|
/// Format: ([xpos, ypos], [width, height])
|
||||||
|
@ -43,105 +47,137 @@ pub struct AtlasSet {
|
||||||
|
|
||||||
/// Used to calculate packing efficiency
|
/// Used to calculate packing efficiency
|
||||||
used_area: f64,
|
used_area: f64,
|
||||||
|
|
||||||
|
/// The root directory that contains all image files.
|
||||||
|
/// Files outside this directory will not be packed.
|
||||||
|
asset_root: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AtlasSet {
|
impl AtlasSet {
|
||||||
pub fn new(texture_width: u32, texture_height: u32, texture_limit: usize) -> Self {
|
pub fn new(
|
||||||
|
texture_width: u32,
|
||||||
|
texture_height: u32,
|
||||||
|
texture_limit: usize,
|
||||||
|
asset_root: &Path,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
asset_root: asset_root.to_path_buf(),
|
||||||
texture_width,
|
texture_width,
|
||||||
texture_height,
|
texture_height,
|
||||||
texture_limit,
|
texture_limit,
|
||||||
texture_list: Vec::new(),
|
texture_list: Vec::new(),
|
||||||
image_max_sizes: Vec::new(),
|
image_max_sizes: Vec::new(),
|
||||||
used_regions: Vec::new(),
|
used_regions: Vec::new(),
|
||||||
index: SpriteAtlasIndex::new(),
|
index: SpriteAtlas::new(),
|
||||||
used_area: 0f64,
|
used_area: 0f64,
|
||||||
|
image_y_start: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if new and fixed overlap,
|
|
||||||
/// or if new exits the atlas.
|
|
||||||
/// Parameters: ([xpos, ypos], [width, height])
|
|
||||||
pub fn boxes_overlap(&self, fixed: ([u32; 2], [u32; 2]), new: ([u32; 2], [u32; 2])) -> bool {
|
|
||||||
if new.0[0] + new.1[0] >= self.texture_width || new.0[1] + new.1[1] >= self.texture_height {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fixed.0[0] <= new.0[0] + new.1[0]
|
|
||||||
&& fixed.0[0] + fixed.1[0] >= new.0[0]
|
|
||||||
&& fixed.0[1] <= new.0[1] + new.1[1]
|
|
||||||
&& fixed.0[1] + fixed.1[1] >= new.0[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a sprite to this atlas set
|
/// Add a sprite to this atlas set
|
||||||
pub fn write_image(&mut self, path: &Path, dim: [u32; 2]) -> Result<usize> {
|
pub fn write_image(&mut self, path: &Path, dim: [u32; 2]) -> Result<usize> {
|
||||||
let mut f = File::open(&path)?;
|
let mut f = File::open(&path)?;
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
f.read_to_end(&mut bytes)?;
|
f.read_to_end(&mut bytes)?;
|
||||||
let img = image::load_from_memory(&bytes)?;
|
let img = image::load_from_memory(&bytes)?;
|
||||||
let mut pixel_idx = 0;
|
|
||||||
let mut atlas_idx = 0;
|
|
||||||
|
|
||||||
// Find first available region, starting at top-left of atlas 0.
|
let mut x = 0;
|
||||||
// Includes a few speed optimizations
|
let mut y = 0;
|
||||||
loop {
|
let mut final_atlas_idx = None;
|
||||||
|
|
||||||
|
// Loop over atlas textures
|
||||||
|
'outer: for atlas_idx in 0..self.texture_limit {
|
||||||
if atlas_idx >= self.texture_list.len() {
|
if atlas_idx >= self.texture_list.len() {
|
||||||
// We can't start another atlas, we're at the limit
|
|
||||||
if atlas_idx >= self.texture_limit {
|
|
||||||
// TODO: how does a user resolve this?
|
|
||||||
bail!("Sprites didn't fit into atlas");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a new atlas
|
// Start a new atlas
|
||||||
self.texture_list
|
self.texture_list
|
||||||
.push(RgbaImage::new(self.texture_width, self.texture_height));
|
.push(RgbaImage::new(self.texture_width, self.texture_height));
|
||||||
self.used_regions.push(Vec::new());
|
self.used_regions.push(Vec::new());
|
||||||
self.image_max_sizes.push(u32::MAX)
|
self.image_max_sizes.push([u32::MAX, u32::MAX]);
|
||||||
|
self.image_y_start.push((0, [u32::MAX, u32::MAX]));
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = pixel_idx % self.texture_width;
|
// Optimization: save the smallest sprite that didn't fit in each atlas,
|
||||||
let y = pixel_idx / self.texture_height;
|
// and don't try to add similarly-sized sprites.
|
||||||
let new = ([x, y], dim);
|
if dim[0] >= self.image_max_sizes[atlas_idx][0]
|
||||||
let mut used = false;
|
&& dim[1] >= self.image_max_sizes[atlas_idx][1]
|
||||||
for r in &self.used_regions[atlas_idx] {
|
{
|
||||||
if self.boxes_overlap(*r, new) {
|
continue 'outer;
|
||||||
// Speed boost: skip the whole box
|
}
|
||||||
pixel_idx += new.1[0] - 1;
|
|
||||||
used = true;
|
x = 0;
|
||||||
break;
|
y = 0;
|
||||||
|
if self.image_y_start.len() != 0 {
|
||||||
|
let (sy, sd) = self.image_y_start[atlas_idx];
|
||||||
|
if dim[0] >= sd[0] || dim[1] >= sd[1] {
|
||||||
|
y = sy;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.image_y_start.push((0, [u32::MAX, u32::MAX]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut free = false;
|
||||||
|
let mut new;
|
||||||
|
'inner: while y < self.texture_height && !free {
|
||||||
|
new = ([x, y], dim);
|
||||||
|
free = true;
|
||||||
|
for r in &self.used_regions[atlas_idx] {
|
||||||
|
// If boxes overlap...
|
||||||
|
if r.0[0] < new.0[0] + new.1[0]
|
||||||
|
&& r.0[0] + r.1[0] > new.0[0]
|
||||||
|
&& r.0[1] < new.0[1] + new.1[1]
|
||||||
|
&& r.0[1] + r.1[1] > new.0[1]
|
||||||
|
{
|
||||||
|
// Skip the whole occupied area
|
||||||
|
x = r.1[0] + r.0[0];
|
||||||
|
if x + dim[0] >= self.texture_width {
|
||||||
|
y += 1;
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
free = false;
|
||||||
|
continue 'inner;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !used {
|
if y + dim[1] >= self.texture_height {
|
||||||
|
// This sprite didn't fit, move on to the next atlas
|
||||||
|
self.image_max_sizes[atlas_idx] = [dim[0] / 2, dim[1] / 2];
|
||||||
|
} else if free {
|
||||||
|
final_atlas_idx = Some(atlas_idx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pixel_idx += 1;
|
|
||||||
|
|
||||||
// Speed boost: save the smallest sprite that didn't fit in each atlas,
|
|
||||||
// and don't even try to add bigger sprite.
|
|
||||||
if dim[0] * dim[1] >= self.image_max_sizes[atlas_idx] {
|
|
||||||
atlas_idx += 1;
|
|
||||||
pixel_idx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This sprite didn't fit, move on to the next atlas
|
|
||||||
if pixel_idx >= self.texture_width * self.texture_height {
|
|
||||||
self.image_max_sizes[atlas_idx] = dim[0] * dim[1];
|
|
||||||
atlas_idx += 1;
|
|
||||||
pixel_idx = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let atlas_idx = match final_atlas_idx {
|
||||||
|
None => bail!("textures didn't fit!"),
|
||||||
|
Some(s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
// We found a spot for this image, write it.
|
// We found a spot for this image, write it.
|
||||||
let x = pixel_idx % self.texture_width;
|
//let img = RgbaImage::from_pixel(dim[0], dim[1], Rgba([0, 0, 0, 255]));
|
||||||
let y = pixel_idx / self.texture_height;
|
|
||||||
imageops::overlay(&mut self.texture_list[atlas_idx], &img, x.into(), y.into());
|
imageops::overlay(&mut self.texture_list[atlas_idx], &img, x.into(), y.into());
|
||||||
self.used_regions[atlas_idx].push(([x, y], dim));
|
self.used_regions[atlas_idx].push(([x, y], dim));
|
||||||
self.used_area += dim[0] as f64 * dim[1] as f64;
|
self.used_area += dim[0] as f64 * dim[1] as f64;
|
||||||
|
|
||||||
self.index.insert(
|
let (sy, sd) = self.image_y_start[atlas_idx];
|
||||||
path.to_path_buf(),
|
if dim[0] <= sd[0] && dim[1] <= sd[1] {
|
||||||
|
// Reset start y if both dimensions of this texture are smaller than the previous smallest texture
|
||||||
|
// We check for both, because that ensures that the smaller texture can tile the previous largest one.
|
||||||
|
self.image_y_start[atlas_idx] = (0, [dim[0] / 2, dim[1] / 2]);
|
||||||
|
} else {
|
||||||
|
self.image_y_start[atlas_idx] = (y.max(sy), sd);
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = path.strip_prefix(&self.asset_root).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"path `{}` is not relative to asset root `{}`",
|
||||||
|
path.display(),
|
||||||
|
self.asset_root.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.index.index.insert(
|
||||||
|
p.to_path_buf(),
|
||||||
SpriteAtlasImage {
|
SpriteAtlasImage {
|
||||||
atlas: atlas_idx,
|
atlas: atlas_idx,
|
||||||
x: x as f32 / self.texture_width as f32,
|
x: x as f32 / self.texture_width as f32,
|
||||||
|
|
|
@ -3,10 +3,7 @@
|
||||||
//! This crate creates texture atlases from an asset tree.
|
//! This crate creates texture atlases from an asset tree.
|
||||||
//! The main interface for this crate is ... TODO
|
//! The main interface for this crate is ... TODO
|
||||||
|
|
||||||
use std::{
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
collections::HashMap,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -36,26 +33,16 @@ pub struct SpriteAtlasImage {
|
||||||
/// A map between file paths (relative to the root asset dir)
|
/// A map between file paths (relative to the root asset dir)
|
||||||
/// and [`AtlasTexture`]s.
|
/// and [`AtlasTexture`]s.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct SpriteAtlasIndex {
|
pub struct SpriteAtlas {
|
||||||
pub(crate) index: HashMap<PathBuf, SpriteAtlasImage>,
|
/// The images in this atlas
|
||||||
|
pub index: HashMap<PathBuf, SpriteAtlasImage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteAtlasIndex {
|
impl SpriteAtlas {
|
||||||
/// Make an empty [`SpriteAtlasIndex`]
|
/// Make an empty [`SpriteAtlasIndex`]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
index: HashMap::new(),
|
index: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make an empty [`SpriteAtlasIndex`]
|
|
||||||
pub fn insert(&mut self, path: PathBuf, atlasimage: SpriteAtlasImage) {
|
|
||||||
self.index.insert(path, atlasimage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an [`AtlasImage`] for a file `p`.
|
|
||||||
/// Paths must be relative to the root of the asset directory.
|
|
||||||
pub fn get(&self, p: &Path) -> Option<&SpriteAtlasImage> {
|
|
||||||
self.index.get(p)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,37 +2,31 @@ mod atlasset;
|
||||||
|
|
||||||
use atlasset::AtlasSet;
|
use atlasset::AtlasSet;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use image::io::Reader;
|
use image::io::Reader;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
// TODO: procedural sun coloring
|
// TODO: warning when images have extra transparency
|
||||||
// TODO: transparency buffer
|
|
||||||
// TODO: don't re-encode. Direct to gpu?
|
// TODO: don't re-encode. Direct to gpu?
|
||||||
// (maybe not, tiling is slow. Make it work with files first.)
|
// (maybe not, tiling is slow. Make it work with files first.)
|
||||||
// TODO: path for atlas files
|
// TODO: path for atlas files
|
||||||
// TODO: rework texturearray
|
|
||||||
// TODO: reasonable sprite sizes (especially ui, document rules)
|
|
||||||
// TODO: consistent naming
|
|
||||||
// TODO: dynamic packing (for plugins)
|
// TODO: dynamic packing (for plugins)
|
||||||
// spriteatlas: the set of textures
|
|
||||||
// texture: a single plane of many images, what we load to wgpu
|
|
||||||
// image: a single file
|
|
||||||
// sprite: a possibly animated texture
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
|
|
||||||
for e in WalkDir::new("./assets/render")
|
let asset_root = Path::new("./assets/render");
|
||||||
.into_iter()
|
|
||||||
.filter_map(|e| e.ok())
|
// Total number of pixels we want to add
|
||||||
{
|
let mut total_dim = 0f64;
|
||||||
|
|
||||||
|
for e in WalkDir::new(&asset_root).into_iter().filter_map(|e| e.ok()) {
|
||||||
if e.metadata().unwrap().is_file() {
|
if e.metadata().unwrap().is_file() {
|
||||||
// TODO: better warnings
|
// TODO: better warnings
|
||||||
match e.path().extension() {
|
match e.path().extension() {
|
||||||
Some(t) => {
|
Some(t) => {
|
||||||
if t.to_str() != Some("png") {
|
if t.to_str() != Some("png") && t.to_str() != Some("jpg") {
|
||||||
println!("[WARNING] {e:#?} is not a png file, skipping.");
|
println!("[WARNING] {e:#?} is not a png file, skipping.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +40,8 @@ fn main() -> Result<()> {
|
||||||
let path = e.path().to_path_buf();
|
let path = e.path().to_path_buf();
|
||||||
let reader = Reader::open(&path)?;
|
let reader = Reader::open(&path)?;
|
||||||
let dim = reader.into_dimensions()?;
|
let dim = reader.into_dimensions()?;
|
||||||
files.push((path, [dim.0, dim.1]))
|
files.push((path, [dim.0, dim.1]));
|
||||||
|
total_dim += dim.0 as f64 * dim.1 as f64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,10 +52,19 @@ fn main() -> Result<()> {
|
||||||
b.cmp(&a)
|
b.cmp(&a)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Make sure we have enough pixels.
|
||||||
|
// This check is conservative and imperfect:
|
||||||
|
// Our tiling algorithm usually has efficiency better than 80% (~90%, as of writing)
|
||||||
|
// We need room for error, though, since this check doesn't guarante success.
|
||||||
|
if total_dim / 0.80 >= (8192.0 * 8192.0 * 16.0) {
|
||||||
|
bail!("Texture atlas is too small")
|
||||||
|
}
|
||||||
|
|
||||||
// Create atlas set
|
// Create atlas set
|
||||||
let mut atlas_set = AtlasSet::new(8192, 8192, 16);
|
let mut atlas_set = AtlasSet::new(8192, 8192, 16, &asset_root);
|
||||||
let total = files.len();
|
let total = files.len();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
let mut peak_efficiency = 0f64;
|
||||||
for (path, dim) in files {
|
for (path, dim) in files {
|
||||||
i += 1;
|
i += 1;
|
||||||
let atlas_idx = atlas_set.write_image(&path, dim)?;
|
let atlas_idx = atlas_set.write_image(&path, dim)?;
|
||||||
|
@ -69,6 +73,7 @@ fn main() -> Result<()> {
|
||||||
100.0 * atlas_set.get_efficiency(),
|
100.0 * atlas_set.get_efficiency(),
|
||||||
path.display()
|
path.display()
|
||||||
);
|
);
|
||||||
|
peak_efficiency = peak_efficiency.max(atlas_set.get_efficiency());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
|
@ -76,9 +81,11 @@ fn main() -> Result<()> {
|
||||||
100.0 * atlas_set.get_efficiency()
|
100.0 * atlas_set.get_efficiency()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
println!("Peak efficiency: {:.02}%", 100.0 * peak_efficiency);
|
||||||
|
|
||||||
println!("Saving files...");
|
println!("Saving files...");
|
||||||
atlas_set.save_files(
|
atlas_set.save_files(
|
||||||
|x| PathBuf::from(format!("atlas-{x:0.2}.png")),
|
|x| PathBuf::from(format!("atlas-{x:0.2}.bmp")),
|
||||||
&PathBuf::from("spriteatlas.toml"),
|
&PathBuf::from("spriteatlas.toml"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// INCLUDE: global uniform header
|
||||||
|
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) transform_matrix_0: vec4<f32>,
|
@location(2) transform_matrix_0: vec4<f32>,
|
||||||
@location(3) transform_matrix_1: vec4<f32>,
|
@location(3) transform_matrix_1: vec4<f32>,
|
||||||
|
@ -9,27 +11,12 @@ struct InstanceInput {
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(1) texture_coords: vec2<f32>,
|
@location(1) texture_coords: vec2<f32>,
|
||||||
}
|
};
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
@location(0) texture_coords: vec2<f32>,
|
@location(0) texture_coords: vec2<f32>,
|
||||||
@location(1) texture_index: u32,
|
@location(1) texture_index: u32,
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@group(1) @binding(0)
|
|
||||||
var<uniform> global: GlobalUniform;
|
|
||||||
struct GlobalUniform {
|
|
||||||
camera_position: vec2<f32>,
|
|
||||||
camera_zoom: vec2<f32>,
|
|
||||||
camera_zoom_limits: vec2<f32>,
|
|
||||||
window_size: vec2<f32>,
|
|
||||||
window_aspect: vec2<f32>,
|
|
||||||
starfield_texture: vec2<u32>,
|
|
||||||
starfield_tile_size: vec2<f32>,
|
|
||||||
starfield_size_limits: vec2<f32>,
|
|
||||||
current_time: vec2<f32>,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,8 +43,18 @@ fn vertex_main(
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
||||||
out.texture_coords = vertex.texture_coords;
|
|
||||||
out.texture_index = instance.texture_index;
|
let i = sprites.data[instance.texture_index].first_frame;
|
||||||
|
let t = atlas.data[i];
|
||||||
|
out.texture_index = u32(0);
|
||||||
|
out.texture_coords = vec2(t.xpos, t.ypos);
|
||||||
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
|
||||||
|
}
|
||||||
|
if vertex.texture_coords.y == 1.0 {
|
||||||
|
out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height);
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// INCLUDE: global uniform header
|
||||||
|
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) position: vec2<f32>,
|
@location(2) position: vec2<f32>,
|
||||||
@location(3) velocity: vec2<f32>,
|
@location(3) velocity: vec2<f32>,
|
||||||
|
@ -6,8 +8,7 @@ struct InstanceInput {
|
||||||
@location(6) size: f32,
|
@location(6) size: f32,
|
||||||
@location(7) created: f32,
|
@location(7) created: f32,
|
||||||
@location(8) expires: f32,
|
@location(8) expires: f32,
|
||||||
@location(9) texture_index_len_rep: vec3<u32>,
|
@location(9) texture_index: u32,
|
||||||
@location(10) texture_aspect_fps: vec2<f32>,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -22,21 +23,6 @@ struct VertexOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@group(1) @binding(0)
|
|
||||||
var<uniform> global: GlobalUniform;
|
|
||||||
struct GlobalUniform {
|
|
||||||
camera_position: vec2<f32>,
|
|
||||||
camera_zoom: vec2<f32>,
|
|
||||||
camera_zoom_limits: vec2<f32>,
|
|
||||||
window_size: vec2<f32>,
|
|
||||||
window_aspect: vec2<f32>,
|
|
||||||
starfield_texture: vec2<u32>,
|
|
||||||
starfield_tile_size: vec2<f32>,
|
|
||||||
starfield_size_limits: vec2<f32>,
|
|
||||||
current_time: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
var texture_array: binding_array<texture_2d<f32>>;
|
var texture_array: binding_array<texture_2d<f32>>;
|
||||||
@group(0) @binding(1)
|
@group(0) @binding(1)
|
||||||
|
@ -57,37 +43,51 @@ fn vertex_main(
|
||||||
out.texture_coords = vertex.texture_coords;
|
out.texture_coords = vertex.texture_coords;
|
||||||
|
|
||||||
if instance.expires < global.current_time.x {
|
if instance.expires < global.current_time.x {
|
||||||
out.texture_index = instance.texture_index_len_rep.x;
|
out.texture_index = u32(0);
|
||||||
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
|
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
let age = global.current_time.x - instance.created;
|
let age = global.current_time.x - instance.created;
|
||||||
|
|
||||||
|
let len = sprites.data[instance.texture_index].frame_count;
|
||||||
|
let rep = sprites.data[instance.texture_index].repeatmode;
|
||||||
|
let fps = sprites.data[instance.texture_index].fps;
|
||||||
var frame: u32 = u32(0);
|
var frame: u32 = u32(0);
|
||||||
if instance.texture_index_len_rep.z == u32(1) {
|
if rep == u32(1) {
|
||||||
// Repeat
|
// Repeat
|
||||||
frame = u32(fmod(
|
frame = u32(fmod(
|
||||||
(age / instance.texture_aspect_fps.y),
|
(age / fps),
|
||||||
f32(instance.texture_index_len_rep.y)
|
f32(len)
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// Once
|
// Once
|
||||||
frame = u32(min(
|
frame = u32(min(
|
||||||
(age / instance.texture_aspect_fps.y),
|
(age / fps),
|
||||||
f32(instance.texture_index_len_rep.y) - 1.0
|
f32(len) - 1.0
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
out.texture_index = instance.texture_index_len_rep.x + frame;
|
// Pick image
|
||||||
|
frame = frame + sprites.data[instance.texture_index].first_frame;
|
||||||
|
let t = atlas.data[frame];
|
||||||
|
out.texture_index = u32(0);
|
||||||
|
out.texture_coords = vec2(t.xpos, t.ypos);
|
||||||
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
|
||||||
|
}
|
||||||
|
if vertex.texture_coords.y == 1.0 {
|
||||||
|
out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height);
|
||||||
|
}
|
||||||
|
|
||||||
let rotation = mat2x2(instance.rotation_0, instance.rotation_1);
|
let rotation = mat2x2(instance.rotation_0, instance.rotation_1);
|
||||||
|
|
||||||
var scale: f32 = instance.size / global.camera_zoom.x;
|
var scale: f32 = instance.size / global.camera_zoom.x;
|
||||||
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
|
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
|
||||||
|
|
||||||
pos = pos * vec2<f32>(
|
pos = pos * vec2<f32>(
|
||||||
instance.texture_aspect_fps.x * scale / global.window_aspect.x,
|
sprites.data[instance.texture_index].aspect * scale / global.window_aspect.x,
|
||||||
scale
|
scale
|
||||||
);
|
);
|
||||||
pos = rotation * pos;
|
pos = rotation * pos;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// INCLUDE: global uniform header
|
||||||
|
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) position: vec3<f32>,
|
@location(2) position: vec3<f32>,
|
||||||
@location(3) size: f32,
|
@location(3) size: f32,
|
||||||
|
@ -12,24 +14,10 @@ struct VertexInput {
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
@location(0) texture_coords: vec2<f32>,
|
@location(0) texture_coords: vec2<f32>,
|
||||||
@location(1) tint: vec2<f32>,
|
@location(1) texture_index: u32,
|
||||||
|
@location(2) tint: vec2<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@group(1) @binding(0)
|
|
||||||
var<uniform> global: GlobalUniform;
|
|
||||||
struct GlobalUniform {
|
|
||||||
camera_position: vec2<f32>,
|
|
||||||
camera_zoom: vec2<f32>,
|
|
||||||
camera_zoom_limits: vec2<f32>,
|
|
||||||
window_size: vec2<f32>,
|
|
||||||
window_aspect: vec2<f32>,
|
|
||||||
starfield_texture: vec2<u32>,
|
|
||||||
starfield_tile_size: vec2<f32>,
|
|
||||||
starfield_size_limits: vec2<f32>,
|
|
||||||
current_time: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
var texture_array: binding_array<texture_2d<f32>>;
|
var texture_array: binding_array<texture_2d<f32>>;
|
||||||
@group(0) @binding(1)
|
@group(0) @binding(1)
|
||||||
|
@ -49,7 +37,6 @@ fn vertex_main(
|
||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.texture_coords = vertex.texture_coords;
|
|
||||||
out.tint = instance.tint;
|
out.tint = instance.tint;
|
||||||
|
|
||||||
// Center of the tile the camera is currently in, in game coordinates.
|
// Center of the tile the camera is currently in, in game coordinates.
|
||||||
|
@ -120,6 +107,18 @@ fn vertex_main(
|
||||||
);
|
);
|
||||||
|
|
||||||
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z;
|
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z;
|
||||||
|
|
||||||
|
let i = sprites.data[global.starfield_sprite.x].first_frame;
|
||||||
|
let t = atlas.data[i];
|
||||||
|
out.texture_index = u32(0);
|
||||||
|
out.texture_coords = vec2(t.xpos, t.ypos);
|
||||||
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
|
||||||
|
}
|
||||||
|
if vertex.texture_coords.y == 1.0 {
|
||||||
|
out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height);
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +135,7 @@ fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
let c_del = c_bot - c_top;
|
let c_del = c_bot - c_top;
|
||||||
|
|
||||||
return textureSampleLevel(
|
return textureSampleLevel(
|
||||||
texture_array[global.starfield_texture.x],
|
texture_array[in.texture_index],
|
||||||
sampler_array[0],
|
sampler_array[0],
|
||||||
in.texture_coords,
|
in.texture_coords,
|
||||||
0.0
|
0.0
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// INCLUDE: global uniform header
|
||||||
|
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) transform_matrix_0: vec4<f32>,
|
@location(2) transform_matrix_0: vec4<f32>,
|
||||||
@location(3) transform_matrix_1: vec4<f32>,
|
@location(3) transform_matrix_1: vec4<f32>,
|
||||||
|
@ -19,22 +21,6 @@ struct VertexOutput {
|
||||||
@location(2) color_transform: vec4<f32>,
|
@location(2) color_transform: vec4<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@group(1) @binding(0)
|
|
||||||
var<uniform> global: GlobalUniform;
|
|
||||||
struct GlobalUniform {
|
|
||||||
camera_position: vec2<f32>,
|
|
||||||
camera_zoom: vec2<f32>,
|
|
||||||
camera_zoom_limits: vec2<f32>,
|
|
||||||
window_size: vec2<f32>,
|
|
||||||
window_aspect: vec2<f32>,
|
|
||||||
starfield_texture: vec2<u32>,
|
|
||||||
starfield_tile_size: vec2<f32>,
|
|
||||||
starfield_size_limits: vec2<f32>,
|
|
||||||
current_time: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
var texture_array: binding_array<texture_2d<f32>>;
|
var texture_array: binding_array<texture_2d<f32>>;
|
||||||
@group(0) @binding(1)
|
@group(0) @binding(1)
|
||||||
|
@ -58,9 +44,19 @@ fn vertex_main(
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
||||||
out.texture_coords = vertex.texture_coords;
|
|
||||||
out.texture_index = instance.texture_index;
|
|
||||||
out.color_transform = instance.color_transform;
|
out.color_transform = instance.color_transform;
|
||||||
|
|
||||||
|
let i = sprites.data[instance.texture_index].first_frame;
|
||||||
|
let t = atlas.data[i];
|
||||||
|
out.texture_index = u32(0);
|
||||||
|
out.texture_coords = vec2(t.xpos, t.ypos);
|
||||||
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
|
||||||
|
}
|
||||||
|
if vertex.texture_coords.y == 1.0 {
|
||||||
|
out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height);
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
use bytemuck;
|
|
||||||
use std::mem;
|
|
||||||
use wgpu;
|
|
||||||
|
|
||||||
pub struct GlobalData {
|
|
||||||
pub buffer: wgpu::Buffer,
|
|
||||||
pub bind_group: wgpu::BindGroup,
|
|
||||||
pub bind_group_layout: wgpu::BindGroupLayout,
|
|
||||||
pub content: GlobalDataContent,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
// Uniforms require uniform alignment.
|
|
||||||
// Since the largest value in this array is a [f32; 2],
|
|
||||||
// all smaller values must be padded.
|
|
||||||
// also, [f32; 3] are aligned as [f32; 4]
|
|
||||||
// (since alignments must be powers of two)
|
|
||||||
pub struct GlobalDataContent {
|
|
||||||
/// Camera position, in game units
|
|
||||||
pub camera_position: [f32; 2],
|
|
||||||
|
|
||||||
/// Camera zoom value, in game units.
|
|
||||||
/// Second component is ignored.
|
|
||||||
pub camera_zoom: [f32; 2],
|
|
||||||
|
|
||||||
/// Camera zoom min and max.
|
|
||||||
pub camera_zoom_limits: [f32; 2],
|
|
||||||
|
|
||||||
/// Size ratio of window, in physical pixels
|
|
||||||
pub window_size: [f32; 2],
|
|
||||||
|
|
||||||
/// Aspect ratio of window
|
|
||||||
/// Second component is ignored.
|
|
||||||
pub window_aspect: [f32; 2],
|
|
||||||
|
|
||||||
/// Texture index of starfield sprites
|
|
||||||
/// Second component is ignored.
|
|
||||||
pub starfield_texture: [u32; 2],
|
|
||||||
|
|
||||||
// Size of (square) starfield tiles, in game units
|
|
||||||
/// Second component is ignored.
|
|
||||||
pub starfield_tile_size: [f32; 2],
|
|
||||||
|
|
||||||
/// Min and max starfield star size, in game units
|
|
||||||
pub starfield_size_limits: [f32; 2],
|
|
||||||
|
|
||||||
/// Current game time, in seconds.
|
|
||||||
/// Second component is ignored.
|
|
||||||
pub current_time: [f32; 2],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GlobalDataContent {
|
|
||||||
const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GlobalData {
|
|
||||||
pub fn new(device: &wgpu::Device) -> Self {
|
|
||||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
||||||
label: None,
|
|
||||||
size: GlobalDataContent::SIZE,
|
|
||||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
||||||
mapped_at_creation: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
||||||
entries: &[wgpu::BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
|
||||||
ty: wgpu::BindingType::Buffer {
|
|
||||||
ty: wgpu::BufferBindingType::Uniform,
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: None,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
label: Some("globaldata bind group layout"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
||||||
layout: &bind_group_layout,
|
|
||||||
entries: &[wgpu::BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: buffer.as_entire_binding(),
|
|
||||||
}],
|
|
||||||
label: Some("globaldata bind group"),
|
|
||||||
});
|
|
||||||
|
|
||||||
return Self {
|
|
||||||
buffer,
|
|
||||||
bind_group,
|
|
||||||
bind_group_layout,
|
|
||||||
content: GlobalDataContent::default(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use std::mem;
|
||||||
|
use wgpu;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)]
|
||||||
|
pub struct ImageLocation {
|
||||||
|
// Image box, in texture coordinates
|
||||||
|
pub xpos: f32,
|
||||||
|
pub ypos: f32,
|
||||||
|
pub width: f32,
|
||||||
|
pub height: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct ImageLocationArray {
|
||||||
|
pub data: [ImageLocation; 108],
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Pod for ImageLocationArray {}
|
||||||
|
unsafe impl Zeroable for ImageLocationArray {
|
||||||
|
fn zeroed() -> Self {
|
||||||
|
Self {
|
||||||
|
data: [ImageLocation::zeroed(); 108],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ImageLocationArray {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::zeroed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)]
|
||||||
|
pub struct AtlasContent {
|
||||||
|
pub data: ImageLocationArray,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtlasContent {
|
||||||
|
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
use bytemuck;
|
||||||
|
use std::mem;
|
||||||
|
use wgpu;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
// Uniforms require uniform alignment.
|
||||||
|
// Since the largest value in this array is a [f32; 2],
|
||||||
|
// all smaller values must be padded.
|
||||||
|
// also, [f32; 3] are aligned as [f32; 4]
|
||||||
|
// (since alignments must be powers of two)
|
||||||
|
pub struct DataContent {
|
||||||
|
/// Camera position, in game units
|
||||||
|
pub camera_position: [f32; 2],
|
||||||
|
|
||||||
|
/// Camera zoom value, in game units.
|
||||||
|
/// Second component is ignored.
|
||||||
|
pub camera_zoom: [f32; 2],
|
||||||
|
|
||||||
|
/// Camera zoom min and max.
|
||||||
|
pub camera_zoom_limits: [f32; 2],
|
||||||
|
|
||||||
|
/// Size ratio of window, in physical pixels
|
||||||
|
pub window_size: [f32; 2],
|
||||||
|
|
||||||
|
/// Aspect ratio of window
|
||||||
|
/// Second component is ignored.
|
||||||
|
pub window_aspect: [f32; 2],
|
||||||
|
|
||||||
|
/// Index of starfield sprite
|
||||||
|
/// Second component is ignored.
|
||||||
|
pub starfield_sprite: [u32; 2],
|
||||||
|
|
||||||
|
// Size of (square) starfield tiles, in game units
|
||||||
|
/// Second component is ignored.
|
||||||
|
pub starfield_tile_size: [f32; 2],
|
||||||
|
|
||||||
|
/// Min and max starfield star size, in game units
|
||||||
|
pub starfield_size_limits: [f32; 2],
|
||||||
|
|
||||||
|
/// Current game time, in seconds.
|
||||||
|
/// Second component is ignored.
|
||||||
|
pub current_time: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataContent {
|
||||||
|
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
use wgpu;
|
||||||
|
|
||||||
|
use super::{AtlasContent, DataContent, SpriteContent};
|
||||||
|
|
||||||
|
pub struct GlobalUniform {
|
||||||
|
pub data_buffer: wgpu::Buffer,
|
||||||
|
pub atlas_buffer: wgpu::Buffer,
|
||||||
|
pub sprite_buffer: wgpu::Buffer,
|
||||||
|
pub bind_group: wgpu::BindGroup,
|
||||||
|
pub bind_group_layout: wgpu::BindGroupLayout,
|
||||||
|
pub content: DataContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalUniform {
|
||||||
|
pub fn shader_header(&self, group: u32) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
out.push_str(&format!("@group({group}) @binding(0)\n"));
|
||||||
|
out.push_str(
|
||||||
|
r#"
|
||||||
|
var<uniform> global: GlobalUniform;
|
||||||
|
struct GlobalUniform {
|
||||||
|
camera_position: vec2<f32>,
|
||||||
|
camera_zoom: vec2<f32>,
|
||||||
|
camera_zoom_limits: vec2<f32>,
|
||||||
|
window_size: vec2<f32>,
|
||||||
|
window_aspect: vec2<f32>,
|
||||||
|
starfield_sprite: vec2<u32>,
|
||||||
|
starfield_tile_size: vec2<f32>,
|
||||||
|
starfield_size_limits: vec2<f32>,
|
||||||
|
current_time: vec2<f32>,
|
||||||
|
};
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
out.push_str("\n");
|
||||||
|
|
||||||
|
out.push_str(&format!("@group({group}) @binding(1)\n"));
|
||||||
|
out.push_str(
|
||||||
|
r#"
|
||||||
|
var<uniform> atlas: AtlasUniform;
|
||||||
|
struct ImageLocation {
|
||||||
|
xpos: f32,
|
||||||
|
ypos: f32,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
};
|
||||||
|
struct AtlasUniform {
|
||||||
|
data: array<ImageLocation, 108>,
|
||||||
|
};
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
out.push_str("\n");
|
||||||
|
|
||||||
|
// TODO: document
|
||||||
|
// wgpu uniforms require constant item sizes.
|
||||||
|
// if you get an error like the following,check!
|
||||||
|
// `Buffer is bound with size 3456 where the shader expects 5184 in group[1] compact index 2`
|
||||||
|
// More notes are in datacontent
|
||||||
|
|
||||||
|
out.push_str(&format!("@group({group}) @binding(2)\n"));
|
||||||
|
out.push_str(
|
||||||
|
r#"
|
||||||
|
var<uniform> sprites: SpriteUniform;
|
||||||
|
struct SpriteData {
|
||||||
|
frame_count: u32,
|
||||||
|
repeatmode: u32,
|
||||||
|
aspect: f32,
|
||||||
|
fps: f32,
|
||||||
|
first_frame: u32,
|
||||||
|
|
||||||
|
padding_a: f32,
|
||||||
|
padding_b: f32,
|
||||||
|
padding_c: f32,
|
||||||
|
};
|
||||||
|
struct SpriteUniform {
|
||||||
|
data: array<SpriteData, 108>,
|
||||||
|
};
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
out.push_str("\n");
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(device: &wgpu::Device) -> Self {
|
||||||
|
let data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("global uniform data buffer"),
|
||||||
|
size: DataContent::SIZE,
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let atlas_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("global uniform atlas buffer"),
|
||||||
|
size: AtlasContent::SIZE,
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let sprite_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("global uniform sprite buffer"),
|
||||||
|
size: SpriteContent::SIZE,
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("global uniform bind group layout"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: data_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: atlas_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: sprite_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("global uniform bind group"),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Self {
|
||||||
|
data_buffer,
|
||||||
|
atlas_buffer,
|
||||||
|
sprite_buffer,
|
||||||
|
bind_group,
|
||||||
|
bind_group_layout,
|
||||||
|
content: DataContent::default(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
mod atlascontent;
|
||||||
|
mod datacontent;
|
||||||
|
mod globaluniform;
|
||||||
|
mod spritecontent;
|
||||||
|
|
||||||
|
pub use atlascontent::{AtlasContent, ImageLocation, ImageLocationArray};
|
||||||
|
pub use datacontent::DataContent;
|
||||||
|
pub use globaluniform::GlobalUniform;
|
||||||
|
pub use spritecontent::{SpriteContent, SpriteData, SpriteDataArray};
|
|
@ -0,0 +1,48 @@
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use std::mem;
|
||||||
|
use wgpu;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)]
|
||||||
|
pub struct SpriteData {
|
||||||
|
// Animation parameters
|
||||||
|
pub frame_count: u32,
|
||||||
|
pub repeatmode: u32,
|
||||||
|
pub aspect: f32,
|
||||||
|
pub fps: f32,
|
||||||
|
|
||||||
|
// Index of first frame in ImageLocationArray
|
||||||
|
pub first_frame: u32,
|
||||||
|
// stride must be a multiple of 16
|
||||||
|
pub _padding: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct SpriteDataArray {
|
||||||
|
pub data: [SpriteData; 108],
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Pod for SpriteDataArray {}
|
||||||
|
unsafe impl Zeroable for SpriteDataArray {
|
||||||
|
fn zeroed() -> Self {
|
||||||
|
Self {
|
||||||
|
data: [SpriteData::zeroed(); 108],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SpriteDataArray {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::zeroed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, Default, Pod, Zeroable)]
|
||||||
|
pub struct SpriteContent {
|
||||||
|
pub data: SpriteDataArray,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpriteContent {
|
||||||
|
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ use winit::{self, dpi::LogicalSize, window::Window};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
content,
|
content,
|
||||||
globaldata::{GlobalData, GlobalDataContent},
|
globaluniform::{AtlasContent, DataContent, GlobalUniform, SpriteContent},
|
||||||
pipeline::PipelineBuilder,
|
pipeline::PipelineBuilder,
|
||||||
sprite::ObjectSubSprite,
|
sprite::ObjectSubSprite,
|
||||||
starfield::Starfield,
|
starfield::Starfield,
|
||||||
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
||||||
BufferObject, VertexBuffer,
|
BufferObject, VertexBuffer,
|
||||||
},
|
},
|
||||||
FrameState, ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
ObjectSprite, RenderState, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A high-level GPU wrapper. Consumes game state,
|
/// A high-level GPU wrapper. Consumes game state,
|
||||||
|
@ -44,7 +44,7 @@ pub struct GPUState {
|
||||||
|
|
||||||
starfield: Starfield,
|
starfield: Starfield,
|
||||||
texture_array: TextureArray,
|
texture_array: TextureArray,
|
||||||
global_data: GlobalData,
|
global_uniform: GlobalUniform,
|
||||||
vertex_buffers: VertexBuffers,
|
vertex_buffers: VertexBuffers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,19 @@ struct VertexBuffers {
|
||||||
particle: Rc<VertexBuffer>,
|
particle: Rc<VertexBuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Preprocess shader files
|
||||||
|
fn preprocess_shader(
|
||||||
|
shader: &str,
|
||||||
|
global_uniform: &GlobalUniform,
|
||||||
|
global_uniform_group: u32,
|
||||||
|
) -> String {
|
||||||
|
// Insert common headers
|
||||||
|
shader.replace(
|
||||||
|
"// INCLUDE: global uniform header",
|
||||||
|
&global_uniform.shader_header(global_uniform_group),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
impl GPUState {
|
impl GPUState {
|
||||||
/// Make a new GPUState that draws on `window`
|
/// Make a new GPUState that draws on `window`
|
||||||
pub async fn new(window: Window, ct: &content::Content) -> Result<Self> {
|
pub async fn new(window: Window, ct: &content::Content) -> Result<Self> {
|
||||||
|
@ -161,22 +174,26 @@ impl GPUState {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load uniforms
|
// Load uniforms
|
||||||
let global_data = GlobalData::new(&device);
|
let global_uniform = GlobalUniform::new(&device);
|
||||||
let texture_array = TextureArray::new(&device, &queue, ct)?;
|
let texture_array = TextureArray::new(&device, &queue, ct)?;
|
||||||
|
|
||||||
// Make sure these match the indices in each shader
|
// Make sure these match the indices in each shader
|
||||||
let bind_group_layouts = &[
|
let bind_group_layouts = &[
|
||||||
&texture_array.bind_group_layout,
|
&texture_array.bind_group_layout,
|
||||||
&global_data.bind_group_layout,
|
&global_uniform.bind_group_layout,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create render pipelines
|
// Create render pipelines
|
||||||
let object_pipeline = PipelineBuilder::new("object", &device)
|
let object_pipeline = PipelineBuilder::new("object", &device)
|
||||||
.set_shader(include_str!(concat!(
|
.set_shader(&preprocess_shader(
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
&include_str!(concat!(
|
||||||
"/shaders/",
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
"object.wgsl"
|
"/shaders/",
|
||||||
)))
|
"object.wgsl"
|
||||||
|
)),
|
||||||
|
&global_uniform,
|
||||||
|
1,
|
||||||
|
))
|
||||||
.set_format(config.format)
|
.set_format(config.format)
|
||||||
.set_triangle(true)
|
.set_triangle(true)
|
||||||
.set_vertex_buffer(&vertex_buffers.object)
|
.set_vertex_buffer(&vertex_buffers.object)
|
||||||
|
@ -184,11 +201,15 @@ impl GPUState {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let starfield_pipeline = PipelineBuilder::new("starfield", &device)
|
let starfield_pipeline = PipelineBuilder::new("starfield", &device)
|
||||||
.set_shader(include_str!(concat!(
|
.set_shader(&preprocess_shader(
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
&include_str!(concat!(
|
||||||
"/shaders/",
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
"starfield.wgsl"
|
"/shaders/",
|
||||||
)))
|
"starfield.wgsl"
|
||||||
|
)),
|
||||||
|
&global_uniform,
|
||||||
|
1,
|
||||||
|
))
|
||||||
.set_format(config.format)
|
.set_format(config.format)
|
||||||
.set_triangle(true)
|
.set_triangle(true)
|
||||||
.set_vertex_buffer(&vertex_buffers.starfield)
|
.set_vertex_buffer(&vertex_buffers.starfield)
|
||||||
|
@ -196,11 +217,11 @@ impl GPUState {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let ui_pipeline = PipelineBuilder::new("ui", &device)
|
let ui_pipeline = PipelineBuilder::new("ui", &device)
|
||||||
.set_shader(include_str!(concat!(
|
.set_shader(&preprocess_shader(
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
&include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", "ui.wgsl")),
|
||||||
"/shaders/",
|
&global_uniform,
|
||||||
"ui.wgsl"
|
1,
|
||||||
)))
|
))
|
||||||
.set_format(config.format)
|
.set_format(config.format)
|
||||||
.set_triangle(true)
|
.set_triangle(true)
|
||||||
.set_vertex_buffer(&vertex_buffers.ui)
|
.set_vertex_buffer(&vertex_buffers.ui)
|
||||||
|
@ -208,11 +229,15 @@ impl GPUState {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let particle_pipeline = PipelineBuilder::new("particle", &device)
|
let particle_pipeline = PipelineBuilder::new("particle", &device)
|
||||||
.set_shader(include_str!(concat!(
|
.set_shader(&preprocess_shader(
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
&include_str!(concat!(
|
||||||
"/shaders/",
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
"particle.wgsl"
|
"/shaders/",
|
||||||
)))
|
"particle.wgsl"
|
||||||
|
)),
|
||||||
|
&global_uniform,
|
||||||
|
1,
|
||||||
|
))
|
||||||
.set_format(config.format)
|
.set_format(config.format)
|
||||||
.set_triangle(true)
|
.set_triangle(true)
|
||||||
.set_vertex_buffer(&vertex_buffers.particle)
|
.set_vertex_buffer(&vertex_buffers.particle)
|
||||||
|
@ -239,7 +264,7 @@ impl GPUState {
|
||||||
|
|
||||||
starfield,
|
starfield,
|
||||||
texture_array,
|
texture_array,
|
||||||
global_data,
|
global_uniform,
|
||||||
vertex_buffers,
|
vertex_buffers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -267,8 +292,7 @@ impl GPUState {
|
||||||
/// Also handles child sprites.
|
/// Also handles child sprites.
|
||||||
fn push_object_sprite(
|
fn push_object_sprite(
|
||||||
&self,
|
&self,
|
||||||
camera_zoom: f32,
|
state: &RenderState,
|
||||||
camera_pos: Point2<f32>,
|
|
||||||
instances: &mut Vec<ObjectInstance>,
|
instances: &mut Vec<ObjectInstance>,
|
||||||
clip_ne: Point2<f32>,
|
clip_ne: Point2<f32>,
|
||||||
clip_sw: Point2<f32>,
|
clip_sw: Point2<f32>,
|
||||||
|
@ -280,10 +304,9 @@ impl GPUState {
|
||||||
(Point2 {
|
(Point2 {
|
||||||
x: s.pos.x,
|
x: s.pos.x,
|
||||||
y: s.pos.y,
|
y: s.pos.y,
|
||||||
} - camera_pos.to_vec())
|
} - state.camera_pos.to_vec())
|
||||||
/ s.pos.z
|
/ s.pos.z
|
||||||
};
|
};
|
||||||
let texture = self.texture_array.get_texture(s.texture);
|
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Don't divide by 2, we use this later.
|
// Don't divide by 2, we use this later.
|
||||||
|
@ -291,7 +314,7 @@ impl GPUState {
|
||||||
|
|
||||||
// Width or height, whichever is larger.
|
// Width or height, whichever is larger.
|
||||||
// Accounts for sprite rotation.
|
// Accounts for sprite rotation.
|
||||||
let m = height * texture.aspect.max(1.0);
|
let m = height * s.sprite.aspect.max(1.0);
|
||||||
|
|
||||||
// Don't draw (or compute matrices for)
|
// Don't draw (or compute matrices for)
|
||||||
// sprites that are off the screen
|
// sprites that are off the screen
|
||||||
|
@ -304,7 +327,7 @@ impl GPUState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: clean up
|
// TODO: clean up
|
||||||
let scale = height / camera_zoom;
|
let scale = height / state.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
|
||||||
|
@ -315,7 +338,7 @@ impl GPUState {
|
||||||
//
|
//
|
||||||
// We apply the provided scale here as well as a minor optimization
|
// We apply the provided scale here as well as a minor optimization
|
||||||
let sprite_aspect_and_scale =
|
let sprite_aspect_and_scale =
|
||||||
Matrix4::from_nonuniform_scale(texture.aspect * scale, scale, 1.0);
|
Matrix4::from_nonuniform_scale(s.sprite.aspect * scale, scale, 1.0);
|
||||||
|
|
||||||
// Apply rotation
|
// Apply rotation
|
||||||
let rotate = Matrix4::from_angle_z(s.angle);
|
let rotate = Matrix4::from_angle_z(s.angle);
|
||||||
|
@ -333,8 +356,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 / (camera_zoom / 2.0) / self.window_aspect,
|
x: pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
|
||||||
y: pos.y / (camera_zoom / 2.0),
|
y: pos.y / (state.camera_zoom / 2.0),
|
||||||
z: 0.0,
|
z: 0.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -345,13 +368,13 @@ impl GPUState {
|
||||||
|
|
||||||
instances.push(ObjectInstance {
|
instances.push(ObjectInstance {
|
||||||
transform: t.into(),
|
transform: t.into(),
|
||||||
texture_index: texture.index,
|
sprite_index: s.sprite.index,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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_object_subsprite(camera_zoom, instances, c, pos, s.angle);
|
self.push_object_subsprite(&state, instances, c, pos, s.angle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,29 +383,28 @@ impl GPUState {
|
||||||
/// Only called by `self.push_object_sprite`.
|
/// Only called by `self.push_object_sprite`.
|
||||||
fn push_object_subsprite(
|
fn push_object_subsprite(
|
||||||
&self,
|
&self,
|
||||||
camera_zoom: f32,
|
state: &RenderState,
|
||||||
instances: &mut Vec<ObjectInstance>,
|
instances: &mut Vec<ObjectInstance>,
|
||||||
s: &ObjectSubSprite,
|
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 scale = s.size / (s.pos.z * state.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(s.sprite.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 / (camera_zoom / 2.0) / self.window_aspect,
|
x: parent_pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
|
||||||
y: parent_pos.y / (camera_zoom / 2.0),
|
y: parent_pos.y / (state.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 / (camera_zoom / 2.0) / self.window_aspect,
|
x: s.pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
|
||||||
y: s.pos.y / (camera_zoom / 2.0),
|
y: s.pos.y / (state.camera_zoom / 2.0),
|
||||||
z: 0.0,
|
z: 0.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -395,7 +417,7 @@ impl GPUState {
|
||||||
|
|
||||||
instances.push(ObjectInstance {
|
instances.push(ObjectInstance {
|
||||||
transform: t.into(),
|
transform: t.into(),
|
||||||
texture_index: texture.index,
|
sprite_index: s.sprite.index,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,7 +426,6 @@ impl GPUState {
|
||||||
let logical_size: LogicalSize<f32> =
|
let logical_size: LogicalSize<f32> =
|
||||||
self.window_size.to_logical(self.window.scale_factor());
|
self.window_size.to_logical(self.window.scale_factor());
|
||||||
|
|
||||||
let texture = self.texture_array.get_texture(s.texture);
|
|
||||||
let width = s.dimensions.x;
|
let width = s.dimensions.x;
|
||||||
let height = s.dimensions.y;
|
let height = s.dimensions.y;
|
||||||
|
|
||||||
|
@ -448,7 +469,7 @@ impl GPUState {
|
||||||
|
|
||||||
instances.push(UiInstance {
|
instances.push(UiInstance {
|
||||||
transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(),
|
transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(),
|
||||||
texture_index: texture.index,
|
sprite_index: s.sprite.index,
|
||||||
color: s.color.unwrap_or([1.0, 1.0, 1.0, 1.0]),
|
color: s.color.unwrap_or([1.0, 1.0, 1.0, 1.0]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -456,23 +477,16 @@ impl GPUState {
|
||||||
/// Make an instance for all the game's sprites
|
/// Make an instance for all the game's sprites
|
||||||
/// (Objects and UI)
|
/// (Objects and UI)
|
||||||
/// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded.
|
/// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded.
|
||||||
fn update_sprite_instances(&self, framestate: FrameState) -> (usize, usize) {
|
fn update_sprite_instances(&self, state: &RenderState) -> (usize, usize) {
|
||||||
let mut object_instances: Vec<ObjectInstance> = Vec::new();
|
let mut object_instances: Vec<ObjectInstance> = 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)) * framestate.camera_zoom;
|
let clip_ne = Point2::from((-self.window_aspect, 1.0)) * state.camera_zoom;
|
||||||
let clip_sw = Point2::from((self.window_aspect, -1.0)) * framestate.camera_zoom;
|
let clip_sw = Point2::from((self.window_aspect, -1.0)) * state.camera_zoom;
|
||||||
|
|
||||||
for s in framestate.object_sprites {
|
for s in &state.object_sprites {
|
||||||
self.push_object_sprite(
|
self.push_object_sprite(state, &mut object_instances, clip_ne, clip_sw, &s);
|
||||||
framestate.camera_zoom,
|
|
||||||
framestate.camera_pos,
|
|
||||||
&mut object_instances,
|
|
||||||
clip_ne,
|
|
||||||
clip_sw,
|
|
||||||
&s,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce sprite limit
|
// Enforce sprite limit
|
||||||
|
@ -489,7 +503,7 @@ impl GPUState {
|
||||||
|
|
||||||
let mut ui_instances: Vec<UiInstance> = Vec::new();
|
let mut ui_instances: Vec<UiInstance> = Vec::new();
|
||||||
|
|
||||||
for s in framestate.ui_sprites {
|
for s in &state.ui_sprites {
|
||||||
self.push_ui_sprite(&mut ui_instances, &s);
|
self.push_ui_sprite(&mut ui_instances, &s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,8 +532,29 @@ impl GPUState {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize the rendering engine
|
||||||
|
pub fn init(&mut self) {
|
||||||
|
// Update global values
|
||||||
|
self.queue.write_buffer(
|
||||||
|
&self.global_uniform.atlas_buffer,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[AtlasContent {
|
||||||
|
data: self.texture_array.image_locations,
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
self.queue.write_buffer(
|
||||||
|
&self.global_uniform.sprite_buffer,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[SpriteContent {
|
||||||
|
data: self.texture_array.sprite_data,
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.update_starfield_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
/// Main render function. Draws sprites on a window.
|
/// Main render function. Draws sprites on a window.
|
||||||
pub fn render(&mut self, framestate: FrameState) -> Result<(), wgpu::SurfaceError> {
|
pub fn render(&mut self, state: RenderState) -> 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
|
||||||
|
@ -552,32 +587,32 @@ impl GPUState {
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let s = state.content.get_starfield_handle();
|
||||||
// Update global values
|
// Update global values
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.global_data.buffer,
|
&self.global_uniform.data_buffer,
|
||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&[GlobalDataContent {
|
bytemuck::cast_slice(&[DataContent {
|
||||||
camera_position: framestate.camera_pos.into(),
|
camera_position: state.camera_pos.into(),
|
||||||
camera_zoom: [framestate.camera_zoom, 0.0],
|
camera_zoom: [state.camera_zoom, 0.0],
|
||||||
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX],
|
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::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_sprite: [s.index, 0],
|
||||||
starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
|
starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
|
||||||
starfield_size_limits: [
|
starfield_size_limits: [
|
||||||
galactica_constants::STARFIELD_SIZE_MIN,
|
galactica_constants::STARFIELD_SIZE_MIN,
|
||||||
galactica_constants::STARFIELD_SIZE_MAX,
|
galactica_constants::STARFIELD_SIZE_MAX,
|
||||||
],
|
],
|
||||||
current_time: [framestate.current_time, 0.0],
|
current_time: [state.current_time, 0.0],
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write all new particles to GPU buffer
|
// Write all new particles to GPU buffer
|
||||||
for i in framestate.new_particles.iter() {
|
for i in state.new_particles.iter() {
|
||||||
let texture = self.texture_array.get_texture(i.texture);
|
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.vertex_buffers.particle.instances,
|
&self.vertex_buffers.particle.instances,
|
||||||
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
|
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
|
||||||
|
@ -586,10 +621,9 @@ impl GPUState {
|
||||||
velocity: i.velocity.into(),
|
velocity: i.velocity.into(),
|
||||||
rotation: Matrix2::from_angle(i.angle).into(),
|
rotation: Matrix2::from_angle(i.angle).into(),
|
||||||
size: i.size,
|
size: i.size,
|
||||||
texture_index_len_rep: [texture.index, texture.len, texture.repeat],
|
sprite_index: i.sprite.index,
|
||||||
texture_aspect_fps: [texture.aspect, texture.fps],
|
created: state.current_time,
|
||||||
created: framestate.current_time,
|
expires: state.current_time + i.lifetime,
|
||||||
expires: framestate.current_time + i.lifetime,
|
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
self.vertex_buffers.particle_array_head += 1;
|
self.vertex_buffers.particle_array_head += 1;
|
||||||
|
@ -599,15 +633,15 @@ impl GPUState {
|
||||||
self.vertex_buffers.particle_array_head = 0;
|
self.vertex_buffers.particle_array_head = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
framestate.new_particles.clear();
|
state.new_particles.clear();
|
||||||
|
|
||||||
// Create sprite instances
|
// Create sprite instances
|
||||||
let (n_object, n_ui) = self.update_sprite_instances(framestate);
|
let (n_object, n_ui) = self.update_sprite_instances(&state);
|
||||||
|
|
||||||
// These should match the indices in each shader,
|
// These should match the indices in each shader,
|
||||||
// and should each have a corresponding bind group layout.
|
// and should each have a corresponding bind group layout.
|
||||||
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
||||||
render_pass.set_bind_group(1, &self.global_data.bind_group, &[]);
|
render_pass.set_bind_group(1, &self.global_uniform.bind_group, &[]);
|
||||||
|
|
||||||
// Starfield pipeline
|
// Starfield pipeline
|
||||||
self.vertex_buffers.starfield.set_in_pass(&mut render_pass);
|
self.vertex_buffers.starfield.set_in_pass(&mut render_pass);
|
||||||
|
|
|
@ -7,18 +7,18 @@
|
||||||
//! and the only one external code should interact with.
|
//! and the only one external code should interact with.
|
||||||
//! (Excluding data structs, like [`ObjectSprite`])
|
//! (Excluding data structs, like [`ObjectSprite`])
|
||||||
|
|
||||||
mod framestate;
|
mod globaluniform;
|
||||||
mod globaldata;
|
|
||||||
mod gpustate;
|
mod gpustate;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
mod renderstate;
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod starfield;
|
mod starfield;
|
||||||
mod texturearray;
|
mod texturearray;
|
||||||
mod vertexbuffer;
|
mod vertexbuffer;
|
||||||
|
|
||||||
pub use framestate::FrameState;
|
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::GPUState;
|
||||||
|
pub use renderstate::RenderState;
|
||||||
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
|
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
|
||||||
|
|
||||||
use cgmath::Matrix4;
|
use cgmath::Matrix4;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use cgmath::Point2;
|
use cgmath::Point2;
|
||||||
|
use galactica_content::Content;
|
||||||
|
|
||||||
use crate::{ObjectSprite, ParticleBuilder, UiSprite};
|
use crate::{ObjectSprite, ParticleBuilder, UiSprite};
|
||||||
|
|
||||||
/// Bundles parameters passed to a single call to GPUState::render
|
/// Bundles parameters passed to a single call to GPUState::render
|
||||||
pub struct FrameState<'a> {
|
pub struct RenderState<'a> {
|
||||||
/// Camera position, in world units
|
/// Camera position, in world units
|
||||||
pub camera_pos: Point2<f32>,
|
pub camera_pos: Point2<f32>,
|
||||||
|
|
||||||
|
@ -23,4 +24,7 @@ pub struct FrameState<'a> {
|
||||||
// TODO: handle overflow
|
// TODO: handle overflow
|
||||||
/// The current time, in seconds
|
/// The current time, in seconds
|
||||||
pub current_time: f32,
|
pub current_time: f32,
|
||||||
|
|
||||||
|
/// Game content
|
||||||
|
pub content: &'a Content,
|
||||||
}
|
}
|
|
@ -3,8 +3,8 @@ use cgmath::{Deg, Point2, Point3, Vector2};
|
||||||
|
|
||||||
/// Instructions to create a new particle
|
/// Instructions to create a new particle
|
||||||
pub struct ParticleBuilder {
|
pub struct ParticleBuilder {
|
||||||
/// The texture to use for this particle
|
/// The sprite to use for this particle
|
||||||
pub texture: content::TextureHandle,
|
pub sprite: content::SpriteHandle,
|
||||||
|
|
||||||
/// This object's center, in world coordinates.
|
/// This object's center, in world coordinates.
|
||||||
pub pos: Point2<f32>,
|
pub pos: Point2<f32>,
|
||||||
|
@ -18,7 +18,7 @@ pub struct ParticleBuilder {
|
||||||
/// This particle's lifetime, in seconds
|
/// This particle's lifetime, in seconds
|
||||||
pub lifetime: f32,
|
pub lifetime: f32,
|
||||||
|
|
||||||
/// The size of this sprite,
|
/// The size of this particle,
|
||||||
/// given as height in world units.
|
/// given as height in world units.
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,8 @@ pub enum AnchoredUiPosition {
|
||||||
/// A sprite that represents a ui element
|
/// A sprite that represents a ui element
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UiSprite {
|
pub struct UiSprite {
|
||||||
/// The texture to use for this sprite
|
/// The sprite to draw
|
||||||
pub texture: content::TextureHandle,
|
pub sprite: content::SpriteHandle,
|
||||||
|
|
||||||
/// This object's position, in logical (dpi-adjusted) pixels
|
/// This object's position, in logical (dpi-adjusted) pixels
|
||||||
pub pos: AnchoredUiPosition,
|
pub pos: AnchoredUiPosition,
|
||||||
|
@ -75,8 +75,8 @@ pub struct UiSprite {
|
||||||
/// Ships, planets, debris, etc
|
/// Ships, planets, debris, etc
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjectSprite {
|
pub struct ObjectSprite {
|
||||||
/// The texture to use for this sprite
|
/// The sprite to draw
|
||||||
pub texture: content::TextureHandle,
|
pub sprite: content::SpriteHandle,
|
||||||
|
|
||||||
/// This object's center, in world coordinates.
|
/// This object's center, in world coordinates.
|
||||||
pub pos: Point3<f32>,
|
pub pos: Point3<f32>,
|
||||||
|
@ -97,8 +97,8 @@ pub struct ObjectSprite {
|
||||||
/// A sprite that is drawn relative to an ObjectSprite.
|
/// A sprite that is drawn relative to an ObjectSprite.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjectSubSprite {
|
pub struct ObjectSubSprite {
|
||||||
/// The sprite texture to draw
|
/// The sprite to draw
|
||||||
pub texture: content::TextureHandle,
|
pub sprite: content::SpriteHandle,
|
||||||
|
|
||||||
/// This object's position, in world coordinates.
|
/// This object's position, in world coordinates.
|
||||||
/// This is relative to this sprite's parent.
|
/// This is relative to this sprite's parent.
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
use crate::content;
|
use crate::{
|
||||||
|
content,
|
||||||
|
globaluniform::{ImageLocation, ImageLocationArray, SpriteData, SpriteDataArray},
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use bytemuck::Zeroable;
|
||||||
|
use galactica_packer::SpriteAtlasImage;
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32};
|
use std::{fs::File, io::Read, num::NonZeroU32};
|
||||||
use wgpu::BindGroupLayout;
|
use wgpu::BindGroupLayout;
|
||||||
|
|
||||||
pub(crate) struct RawTexture {
|
pub(crate) struct RawTexture {
|
||||||
|
@ -67,57 +72,60 @@ impl RawTexture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Texture {
|
pub struct Texture {
|
||||||
pub index: u32, // Index in texture array
|
pub index: u32, // Index in texture array
|
||||||
pub len: u32, // Number of frames
|
pub len: u32, // Number of frames
|
||||||
pub fps: f32, // Frames per second
|
pub fps: f32, // Frames per second
|
||||||
pub aspect: f32, // width / height
|
pub aspect: f32, // width / height
|
||||||
pub repeat: u32, // How to re-play this texture
|
pub repeat: u32, // How to re-play this texture
|
||||||
|
pub location: Vec<SpriteAtlasImage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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: content::TextureHandle,
|
pub image_locations: ImageLocationArray,
|
||||||
textures: HashMap<content::TextureHandle, Texture>,
|
pub sprite_data: SpriteDataArray,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureArray {
|
impl TextureArray {
|
||||||
pub fn get_starfield_texture(&self) -> Texture {
|
|
||||||
*self.textures.get(&self.starfield_handle).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_texture(&self, handle: content::TextureHandle) -> Texture {
|
|
||||||
match self.textures.get(&handle) {
|
|
||||||
Some(x) => *x,
|
|
||||||
None => unreachable!("Tried to get a texture that doesn't exist"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &content::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();
|
|
||||||
|
|
||||||
for t in &ct.textures {
|
println!("opening image");
|
||||||
let index = texture_data.len() as u32;
|
let mut f = File::open("atlas-0.bmp")?;
|
||||||
for f in &t.frames {
|
let mut bytes = Vec::new();
|
||||||
let mut f = File::open(&f)?;
|
f.read_to_end(&mut bytes)?;
|
||||||
let mut bytes = Vec::new();
|
texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, "Atlas")?);
|
||||||
f.read_to_end(&mut bytes)?;
|
|
||||||
texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, &t.name)?);
|
let mut image_locations = ImageLocationArray::zeroed();
|
||||||
|
let mut sprite_data = SpriteDataArray::zeroed();
|
||||||
|
|
||||||
|
println!("sending to gpu");
|
||||||
|
let mut image_counter = 0;
|
||||||
|
for t in &ct.sprites {
|
||||||
|
sprite_data.data[image_counter as usize] = SpriteData {
|
||||||
|
frame_count: t.frames.len() as u32,
|
||||||
|
repeatmode: t.repeat.as_int(),
|
||||||
|
aspect: t.aspect,
|
||||||
|
fps: t.fps,
|
||||||
|
first_frame: image_counter,
|
||||||
|
_padding: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert texture location data
|
||||||
|
for path in &t.frames {
|
||||||
|
let image = ct.get_image(&path);
|
||||||
|
image_locations.data[image_counter as usize] = ImageLocation {
|
||||||
|
xpos: image.x,
|
||||||
|
ypos: image.y,
|
||||||
|
width: image.w,
|
||||||
|
height: image.h,
|
||||||
|
};
|
||||||
|
image_counter += 1;
|
||||||
}
|
}
|
||||||
textures.insert(
|
|
||||||
t.handle,
|
|
||||||
Texture {
|
|
||||||
index,
|
|
||||||
aspect: t.handle.aspect,
|
|
||||||
fps: t.fps,
|
|
||||||
len: t.frames.len() as u32,
|
|
||||||
repeat: t.repeat.as_int(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
@ -136,7 +144,7 @@ impl TextureArray {
|
||||||
// Texture data
|
// Texture data
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
ty: wgpu::BindingType::Texture {
|
ty: wgpu::BindingType::Texture {
|
||||||
multisampled: false,
|
multisampled: false,
|
||||||
view_dimension: wgpu::TextureViewDimension::D2,
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
@ -147,7 +155,7 @@ impl TextureArray {
|
||||||
// Texture sampler
|
// Texture sampler
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||||
count: NonZeroU32::new(1),
|
count: NonZeroU32::new(1),
|
||||||
},
|
},
|
||||||
|
@ -161,7 +169,6 @@ impl TextureArray {
|
||||||
entries: &[
|
entries: &[
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
// Array of all views
|
|
||||||
resource: wgpu::BindingResource::TextureViewArray(&views),
|
resource: wgpu::BindingResource::TextureViewArray(&views),
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
|
@ -174,8 +181,8 @@ impl TextureArray {
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
bind_group,
|
bind_group,
|
||||||
bind_group_layout,
|
bind_group_layout,
|
||||||
textures: textures,
|
image_locations,
|
||||||
starfield_handle: ct.get_starfield_handle(),
|
sprite_data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ pub struct ObjectInstance {
|
||||||
pub transform: [[f32; 4]; 4],
|
pub transform: [[f32; 4]; 4],
|
||||||
|
|
||||||
/// What texture to use for this sprite
|
/// What texture to use for this sprite
|
||||||
pub texture_index: u32,
|
pub sprite_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferObject for ObjectInstance {
|
impl BufferObject for ObjectInstance {
|
||||||
|
@ -122,7 +122,7 @@ impl BufferObject for ObjectInstance {
|
||||||
shader_location: 5,
|
shader_location: 5,
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
},
|
},
|
||||||
// Texture
|
// Sprite
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
|
||||||
shader_location: 6,
|
shader_location: 6,
|
||||||
|
@ -146,7 +146,7 @@ pub struct UiInstance {
|
||||||
pub color: [f32; 4],
|
pub color: [f32; 4],
|
||||||
|
|
||||||
/// What texture to use for this sprite
|
/// What texture to use for this sprite
|
||||||
pub texture_index: u32,
|
pub sprite_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferObject for UiInstance {
|
impl BufferObject for UiInstance {
|
||||||
|
@ -185,7 +185,7 @@ impl BufferObject for UiInstance {
|
||||||
shader_location: 6,
|
shader_location: 6,
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
},
|
},
|
||||||
// Texture
|
// Sprite
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 20]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 20]>() as wgpu::BufferAddress,
|
||||||
shader_location: 7,
|
shader_location: 7,
|
||||||
|
@ -219,9 +219,8 @@ pub struct ParticleInstance {
|
||||||
/// Time is kept by a variable in the global uniform.
|
/// Time is kept by a variable in the global uniform.
|
||||||
pub expires: f32,
|
pub expires: f32,
|
||||||
|
|
||||||
/// What texture to use for this particle
|
/// What sprite to use for this particle
|
||||||
pub texture_index_len_rep: [u32; 3],
|
pub sprite_index: u32,
|
||||||
pub texture_aspect_fps: [f32; 2],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferObject for ParticleInstance {
|
impl BufferObject for ParticleInstance {
|
||||||
|
@ -271,17 +270,11 @@ impl BufferObject for ParticleInstance {
|
||||||
shader_location: 8,
|
shader_location: 8,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Texture index / len / repeat
|
// Sprite
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
||||||
shader_location: 9,
|
shader_location: 9,
|
||||||
format: wgpu::VertexFormat::Uint32x3,
|
format: wgpu::VertexFormat::Uint32,
|
||||||
},
|
|
||||||
// Texture aspect / fps
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: mem::size_of::<[f32; 14]>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 10,
|
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,12 @@ pub fn build_radar(
|
||||||
|
|
||||||
let (_, player_body) = physics.get_ship_body(player).unwrap();
|
let (_, player_body) = physics.get_ship_body(player).unwrap();
|
||||||
let player_position = util::rigidbody_position(player_body);
|
let player_position = util::rigidbody_position(player_body);
|
||||||
let planet_texture = ct.get_texture_handle("ui::planetblip");
|
let planet_sprite = ct.get_sprite_handle("ui::planetblip");
|
||||||
let ship_texture = ct.get_texture_handle("ui::shipblip");
|
let ship_sprite = ct.get_sprite_handle("ui::shipblip");
|
||||||
let arrow_texture = ct.get_texture_handle("ui::centerarrow");
|
let arrow_sprite = ct.get_sprite_handle("ui::centerarrow");
|
||||||
|
|
||||||
out.push(UiSprite {
|
out.push(UiSprite {
|
||||||
texture: ct.get_texture_handle("ui::radar"),
|
sprite: ct.get_sprite_handle("ui::radar"),
|
||||||
pos: AnchoredUiPosition::NwNw(Point2 { x: 10.0, y: -10.0 }),
|
pos: AnchoredUiPosition::NwNw(Point2 { x: 10.0, y: -10.0 }),
|
||||||
dimensions: Point2 {
|
dimensions: Point2 {
|
||||||
x: radar_size,
|
x: radar_size,
|
||||||
|
@ -57,7 +57,7 @@ pub fn build_radar(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
out.push(UiSprite {
|
out.push(UiSprite {
|
||||||
texture: planet_texture,
|
sprite: planet_sprite,
|
||||||
pos: AnchoredUiPosition::NwC(
|
pos: AnchoredUiPosition::NwC(
|
||||||
Point2 {
|
Point2 {
|
||||||
x: radar_size / 2.0 + 10.0,
|
x: radar_size / 2.0 + 10.0,
|
||||||
|
@ -65,7 +65,7 @@ pub fn build_radar(
|
||||||
} + (d * (radar_size / 2.0)),
|
} + (d * (radar_size / 2.0)),
|
||||||
),
|
),
|
||||||
dimensions: Point2 {
|
dimensions: Point2 {
|
||||||
x: planet_texture.aspect,
|
x: planet_sprite.aspect,
|
||||||
y: 1.0,
|
y: 1.0,
|
||||||
} * size,
|
} * size,
|
||||||
angle: o.angle,
|
angle: o.angle,
|
||||||
|
@ -77,7 +77,7 @@ pub fn build_radar(
|
||||||
// Draw ships
|
// Draw ships
|
||||||
for (s, r) in physics.iter_ship_body() {
|
for (s, r) in physics.iter_ship_body() {
|
||||||
let ship = ct.get_ship(s.ship.handle);
|
let ship = ct.get_ship(s.ship.handle);
|
||||||
let size = (ship.size * ship.sprite_texture.aspect) * ship_scale;
|
let size = (ship.size * ship.sprite.aspect) * ship_scale;
|
||||||
let p = util::rigidbody_position(r);
|
let p = util::rigidbody_position(r);
|
||||||
let d = (p - player_position) / radar_range;
|
let d = (p - player_position) / radar_range;
|
||||||
let m = d.magnitude() + (size / (2.0 * radar_size));
|
let m = d.magnitude() + (size / (2.0 * radar_size));
|
||||||
|
@ -92,7 +92,7 @@ pub fn build_radar(
|
||||||
let f = ct.get_faction(s.ship.faction).color;
|
let f = ct.get_faction(s.ship.faction).color;
|
||||||
let f = [f[0], f[1], f[2], 1.0];
|
let f = [f[0], f[1], f[2], 1.0];
|
||||||
out.push(UiSprite {
|
out.push(UiSprite {
|
||||||
texture: ship_texture,
|
sprite: ship_sprite,
|
||||||
pos: AnchoredUiPosition::NwC(
|
pos: AnchoredUiPosition::NwC(
|
||||||
Point2 {
|
Point2 {
|
||||||
x: radar_size / 2.0 + 10.0,
|
x: radar_size / 2.0 + 10.0,
|
||||||
|
@ -100,7 +100,7 @@ pub fn build_radar(
|
||||||
} + (d * (radar_size / 2.0)),
|
} + (d * (radar_size / 2.0)),
|
||||||
),
|
),
|
||||||
dimensions: Point2 {
|
dimensions: Point2 {
|
||||||
x: ship_texture.aspect,
|
x: ship_sprite.aspect,
|
||||||
y: 1.0,
|
y: 1.0,
|
||||||
} * size,
|
} * size,
|
||||||
angle: -angle,
|
angle: -angle,
|
||||||
|
@ -118,13 +118,13 @@ pub fn build_radar(
|
||||||
let d = d * (radar_size / 2.0);
|
let d = d * (radar_size / 2.0);
|
||||||
let color = Some([0.3, 0.3, 0.3, 1.0]);
|
let color = Some([0.3, 0.3, 0.3, 1.0]);
|
||||||
if m < 0.8 {
|
if m < 0.8 {
|
||||||
let texture = ct.get_texture_handle("ui::radarframe");
|
let sprite = ct.get_sprite_handle("ui::radarframe");
|
||||||
let dimensions = Point2 {
|
let dimensions = Point2 {
|
||||||
x: texture.aspect,
|
x: sprite.aspect,
|
||||||
y: 1.0,
|
y: 1.0,
|
||||||
} * 7.0f32.min((0.8 - m) * 70.0);
|
} * 7.0f32.min((0.8 - m) * 70.0);
|
||||||
out.push(UiSprite {
|
out.push(UiSprite {
|
||||||
texture,
|
sprite,
|
||||||
pos: AnchoredUiPosition::NwNw(Point2 {
|
pos: AnchoredUiPosition::NwNw(Point2 {
|
||||||
x: (radar_size / 2.0 + 10.0) - d.x,
|
x: (radar_size / 2.0 + 10.0) - d.x,
|
||||||
y: (radar_size / -2.0 - 10.0) + d.y,
|
y: (radar_size / -2.0 - 10.0) + d.y,
|
||||||
|
@ -135,7 +135,7 @@ pub fn build_radar(
|
||||||
});
|
});
|
||||||
|
|
||||||
out.push(UiSprite {
|
out.push(UiSprite {
|
||||||
texture,
|
sprite,
|
||||||
pos: AnchoredUiPosition::NwSw(Point2 {
|
pos: AnchoredUiPosition::NwSw(Point2 {
|
||||||
x: (radar_size / 2.0 + 10.0) - d.x,
|
x: (radar_size / 2.0 + 10.0) - d.x,
|
||||||
y: (radar_size / -2.0 - 10.0) - d.y,
|
y: (radar_size / -2.0 - 10.0) - d.y,
|
||||||
|
@ -146,7 +146,7 @@ pub fn build_radar(
|
||||||
});
|
});
|
||||||
|
|
||||||
out.push(UiSprite {
|
out.push(UiSprite {
|
||||||
texture,
|
sprite,
|
||||||
pos: AnchoredUiPosition::NwSe(Point2 {
|
pos: AnchoredUiPosition::NwSe(Point2 {
|
||||||
x: (radar_size / 2.0 + 10.0) + d.x,
|
x: (radar_size / 2.0 + 10.0) + d.x,
|
||||||
y: (radar_size / -2.0 - 10.0) - d.y,
|
y: (radar_size / -2.0 - 10.0) - d.y,
|
||||||
|
@ -157,7 +157,7 @@ pub fn build_radar(
|
||||||
});
|
});
|
||||||
|
|
||||||
out.push(UiSprite {
|
out.push(UiSprite {
|
||||||
texture,
|
sprite,
|
||||||
pos: AnchoredUiPosition::NwNe(Point2 {
|
pos: AnchoredUiPosition::NwNe(Point2 {
|
||||||
x: (radar_size / 2.0 + 10.0) + d.x,
|
x: (radar_size / 2.0 + 10.0) + d.x,
|
||||||
y: (radar_size / -2.0 - 10.0) + d.y,
|
y: (radar_size / -2.0 - 10.0) + d.y,
|
||||||
|
@ -174,7 +174,7 @@ pub fn build_radar(
|
||||||
if m > 200.0 {
|
if m > 200.0 {
|
||||||
let player_angle: Deg<f32> = q.angle(Vector2 { x: 0.0, y: 1.0 }).into();
|
let player_angle: Deg<f32> = q.angle(Vector2 { x: 0.0, y: 1.0 }).into();
|
||||||
out.push(UiSprite {
|
out.push(UiSprite {
|
||||||
texture: arrow_texture,
|
sprite: arrow_sprite,
|
||||||
pos: AnchoredUiPosition::NwC(
|
pos: AnchoredUiPosition::NwC(
|
||||||
Point2 {
|
Point2 {
|
||||||
x: radar_size / 2.0 + 10.0,
|
x: radar_size / 2.0 + 10.0,
|
||||||
|
@ -182,7 +182,7 @@ pub fn build_radar(
|
||||||
} + ((q.normalize() * 0.865) * (radar_size / 2.0)),
|
} + ((q.normalize() * 0.865) * (radar_size / 2.0)),
|
||||||
),
|
),
|
||||||
dimensions: Point2 {
|
dimensions: Point2 {
|
||||||
x: arrow_texture.aspect,
|
x: arrow_sprite.aspect,
|
||||||
y: 1.0,
|
y: 1.0,
|
||||||
} * 10.0,
|
} * 10.0,
|
||||||
angle: -player_angle,
|
angle: -player_angle,
|
||||||
|
|
|
@ -44,7 +44,7 @@ impl ProjectileWorldObject {
|
||||||
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();
|
||||||
|
|
||||||
ObjectSprite {
|
ObjectSprite {
|
||||||
texture: self.projectile.content.sprite_texture,
|
sprite: self.projectile.content.sprite,
|
||||||
pos: Point3 {
|
pos: Point3 {
|
||||||
x: pos.x,
|
x: pos.x,
|
||||||
y: pos.y,
|
y: pos.y,
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl ShipWorldObject {
|
||||||
|
|
||||||
ObjectSprite {
|
ObjectSprite {
|
||||||
pos: (ship_pos.x, ship_pos.y, 1.0).into(),
|
pos: (ship_pos.x, ship_pos.y, 1.0).into(),
|
||||||
texture: s.sprite_texture,
|
sprite: s.sprite,
|
||||||
angle: -ship_ang,
|
angle: -ship_ang,
|
||||||
size: s.size,
|
size: s.size,
|
||||||
|
|
||||||
|
|
|
@ -189,7 +189,7 @@ impl<'a> World {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
particles.push(ParticleBuilder {
|
particles.push(ParticleBuilder {
|
||||||
texture: x.texture,
|
sprite: x.sprite,
|
||||||
pos: Point2 { x: pos.x, y: pos.y },
|
pos: Point2 { x: pos.x, y: pos.y },
|
||||||
velocity,
|
velocity,
|
||||||
angle: -angle,
|
angle: -angle,
|
||||||
|
@ -332,7 +332,7 @@ impl<'a> World {
|
||||||
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(&pr),
|
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(&pr),
|
||||||
};
|
};
|
||||||
particles.push(ParticleBuilder {
|
particles.push(ParticleBuilder {
|
||||||
texture: x.texture,
|
sprite: x.sprite,
|
||||||
pos: Point2 { x: pos.x, y: pos.y },
|
pos: Point2 { x: pos.x, y: pos.y },
|
||||||
velocity,
|
velocity,
|
||||||
angle: -angle,
|
angle: -angle,
|
||||||
|
|
Loading…
Reference in New Issue