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
|
||||
- Trade
|
||||
- Missions
|
||||
- Procedural suns
|
||||
|
||||
## Camera
|
||||
- Shake/wobble on heavy hits?
|
||||
|
@ -134,6 +135,8 @@
|
|||
- Handles
|
||||
- Content specification and pipeline
|
||||
- How packer and optimizations work, and why
|
||||
- How big should sprites be? (resize existing)
|
||||
- Naming: atlas, sprite, image, frame, texture
|
||||
|
||||
|
||||
## 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)
|
||||
rate_rng = 0.1
|
||||
|
||||
# TODO: apply force on fire
|
||||
projectile.sprite_texture = "projectile::blaster"
|
||||
# TODO: apply force to ship on fire
|
||||
projectile.sprite = "projectile::blaster"
|
||||
# Height of projectile in game units
|
||||
projectile.size = 6
|
||||
projectile.size_rng = 0.0
|
||||
|
@ -29,13 +29,13 @@ projectile.force = 0.0
|
|||
|
||||
projectile.collider.ball.radius = 2.0
|
||||
|
||||
projectile.impact.texture = "particle::blaster"
|
||||
projectile.impact.sprite = "particle::explosion"
|
||||
projectile.impact.lifetime = "inherit"
|
||||
projectile.impact.inherit_velocity = "target"
|
||||
projectile.impact.size = 3.0
|
||||
|
||||
|
||||
projectile.expire.texture = "particle::blaster"
|
||||
projectile.expire.sprite = "particle::blaster"
|
||||
projectile.expire.lifetime = "inherit"
|
||||
projectile.expire.inherit_velocity = "projectile"
|
||||
projectile.expire.size = 3.0
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
space.engine = 20
|
||||
|
||||
engine.thrust = 100
|
||||
engine.flare_texture = "flare::ion"
|
||||
engine.flare_sprite = "flare::ion"
|
||||
steering.power = 20
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[ship."Gypsum"]
|
||||
sprite_texture = "ship::gypsum"
|
||||
sprite = "ship::gypsum"
|
||||
size = 100
|
||||
mass = 1
|
||||
hull = 200
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
[texture."starfield"]
|
||||
[sprite."starfield"]
|
||||
file = "starfield.png"
|
||||
|
||||
[texture."star::star"]
|
||||
[sprite."star::star"]
|
||||
file = "star/B-09.png"
|
||||
|
||||
[texture."flare::ion"]
|
||||
[sprite."flare::ion"]
|
||||
file = "flare/1.png"
|
||||
|
||||
[texture."planet::earth"]
|
||||
[sprite."planet::earth"]
|
||||
file = "planet/earth.png"
|
||||
|
||||
[texture."planet::luna"]
|
||||
[sprite."planet::luna"]
|
||||
file = "planet/luna.png"
|
||||
|
||||
[texture."projectile::blaster"]
|
||||
[sprite."projectile::blaster"]
|
||||
file = "projectile/blaster.png"
|
||||
|
||||
[texture."ship::gypsum"]
|
||||
[sprite."ship::gypsum"]
|
||||
file = "ship/gypsum.png"
|
||||
|
||||
[texture."ui::radar"]
|
||||
[sprite."ui::radar"]
|
||||
file = "ui/radar.png"
|
||||
|
||||
[texture."ui::shipblip"]
|
||||
[sprite."ui::shipblip"]
|
||||
file = "ui/ship-blip.png"
|
||||
|
||||
[texture."ui::planetblip"]
|
||||
[sprite."ui::planetblip"]
|
||||
file = "ui/planet-blip.png"
|
||||
|
||||
[texture."ui::radarframe"]
|
||||
[sprite."ui::radarframe"]
|
||||
file = "ui/radarframe.png"
|
||||
|
||||
[texture."ui::centerarrow"]
|
||||
[sprite."ui::centerarrow"]
|
||||
file = "ui/center-arrow.png"
|
||||
|
||||
[texture."particle::blaster"]
|
||||
[sprite."particle::blaster"]
|
||||
duration = 0.15
|
||||
repeat = "once"
|
||||
frames = [
|
||||
|
@ -45,7 +45,7 @@ frames = [
|
|||
]
|
||||
|
||||
|
||||
[texture."particle::explosion"]
|
||||
[sprite."particle::explosion"]
|
||||
duration = 0.4
|
||||
repeat = "once"
|
||||
frames = [
|
|
@ -1,17 +1,17 @@
|
|||
[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.size = 2000
|
||||
|
||||
object.earth.sprite_texture = "planet::earth"
|
||||
object.earth.sprite = "planet::earth"
|
||||
object.earth.position.center = "star"
|
||||
object.earth.position.radius = 4000
|
||||
object.earth.position.angle = 0
|
||||
object.earth.position.z = 10.0
|
||||
object.earth.size = 1000
|
||||
|
||||
object.luna.sprite_texture = "planet::luna"
|
||||
object.luna.sprite = "planet::luna"
|
||||
object.luna.position.center = "earth"
|
||||
object.luna.position.radius = 1600
|
||||
object.luna.position.angle = 135
|
||||
|
|
|
@ -35,14 +35,14 @@ pub const STARFIELD_DENSITY: f64 = 0.01;
|
|||
/// Must fit inside an i32
|
||||
pub const STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
|
||||
|
||||
/// Name of starfield texture
|
||||
pub const STARFIELD_TEXTURE_NAME: &'static str = "starfield";
|
||||
/// Name of starfield sprite
|
||||
pub const STARFIELD_SPRITE_NAME: &'static str = "starfield";
|
||||
|
||||
/// Root directory of game content
|
||||
pub const CONTENT_ROOT: &'static str = "./content";
|
||||
|
||||
/// Root directory of game textures
|
||||
pub const TEXTURE_ROOT: &'static str = "./assets/render";
|
||||
/// Root directory of game images
|
||||
pub const IMAGE_ROOT: &'static str = "./assets/render";
|
||||
|
||||
/// We can draw at most this many object sprites on the screen.
|
||||
pub const OBJECT_SPRITE_INSTANCE_LIMIT: u64 = 500;
|
||||
|
|
|
@ -17,6 +17,8 @@ readme = { workspace = true }
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
galactica-packer = { workspace = true }
|
||||
|
||||
serde = { workspace = true }
|
||||
toml = { 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.
|
||||
use std::{cmp::Eq, hash::Hash};
|
||||
|
||||
/// A lightweight representation of a
|
||||
/// A lightweight representation of a sprite
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TextureHandle {
|
||||
/// The index of this texture in content.textures
|
||||
pub(crate) index: usize,
|
||||
pub struct SpriteHandle {
|
||||
/// The index of this sprite in content.sprites
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl Hash for TextureHandle {
|
||||
impl Hash for SpriteHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.index.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TextureHandle {}
|
||||
impl PartialEq for TextureHandle {
|
||||
impl Eq for SpriteHandle {}
|
||||
impl PartialEq for SpriteHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index.eq(&other.index)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ mod part;
|
|||
mod util;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
|
@ -17,10 +18,10 @@ use std::{
|
|||
use toml;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SystemHandle, TextureHandle};
|
||||
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SpriteHandle, SystemHandle};
|
||||
pub use part::{
|
||||
EnginePoint, Faction, Gun, GunPoint, ImpactInheritVelocity, Outfit, OutfitSpace, Projectile,
|
||||
ProjectileCollider, ProjectileParticle, Relationship, RepeatMode, Ship, System, Texture,
|
||||
ProjectileCollider, ProjectileParticle, Relationship, RepeatMode, Ship, Sprite, System,
|
||||
};
|
||||
|
||||
mod syntax {
|
||||
|
@ -28,7 +29,7 @@ mod syntax {
|
|||
use serde::Deserialize;
|
||||
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)]
|
||||
pub struct Root {
|
||||
|
@ -36,7 +37,7 @@ mod syntax {
|
|||
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
|
||||
pub system: Option<HashMap<String, system::syntax::System>>,
|
||||
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>>,
|
||||
}
|
||||
|
||||
|
@ -72,7 +73,7 @@ mod syntax {
|
|||
ship: None,
|
||||
system: None,
|
||||
outfit: None,
|
||||
texture: None,
|
||||
sprite: None,
|
||||
faction: None,
|
||||
}
|
||||
}
|
||||
|
@ -84,8 +85,8 @@ mod syntax {
|
|||
.with_context(|| "while merging systems")?;
|
||||
merge_hashmap(&mut self.outfit, other.outfit)
|
||||
.with_context(|| "while merging outfits")?;
|
||||
merge_hashmap(&mut self.texture, other.texture)
|
||||
.with_context(|| "while merging textures")?;
|
||||
merge_hashmap(&mut self.sprite, other.sprite)
|
||||
.with_context(|| "while merging sprites")?;
|
||||
merge_hashmap(&mut self.faction, other.faction)
|
||||
.with_context(|| "while merging factions")?;
|
||||
return Ok(());
|
||||
|
@ -94,10 +95,10 @@ mod syntax {
|
|||
}
|
||||
|
||||
trait Build {
|
||||
type InputSyntax;
|
||||
type InputSyntaxType;
|
||||
|
||||
/// 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
|
||||
Self: Sized;
|
||||
}
|
||||
|
@ -106,18 +107,21 @@ trait Build {
|
|||
#[derive(Debug)]
|
||||
pub struct Content {
|
||||
/* Configuration values */
|
||||
/// Root directory for textures
|
||||
texture_root: PathBuf,
|
||||
/// Name of starfield texture
|
||||
starfield_texture_name: String,
|
||||
/// Root directory for image
|
||||
image_root: PathBuf,
|
||||
/// Name of starfield sprite
|
||||
starfield_sprite_name: String,
|
||||
|
||||
/// Textures
|
||||
pub textures: Vec<part::texture::Texture>,
|
||||
/// Sprites
|
||||
pub sprites: Vec<part::sprite::Sprite>,
|
||||
/// Map strings to texture names.
|
||||
/// 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
|
||||
starfield_handle: Option<handle::TextureHandle>,
|
||||
starfield_handle: Option<handle::SpriteHandle>,
|
||||
|
||||
/// Keeps track of which images are in which texture
|
||||
sprite_atlas: SpriteAtlas,
|
||||
|
||||
/// Outfits
|
||||
outfits: Vec<part::outfit::Outfit>,
|
||||
|
@ -148,6 +152,7 @@ impl Content {
|
|||
pub fn load_dir(
|
||||
path: PathBuf,
|
||||
texture_root: PathBuf,
|
||||
atlas_index: PathBuf,
|
||||
starfield_texture_name: String,
|
||||
) -> Result<Self> {
|
||||
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 {
|
||||
sprite_atlas: atlas,
|
||||
systems: Vec::new(),
|
||||
ships: Vec::new(),
|
||||
guns: Vec::new(),
|
||||
outfits: Vec::new(),
|
||||
textures: Vec::new(),
|
||||
sprites: Vec::new(),
|
||||
factions: Vec::new(),
|
||||
texture_index: HashMap::new(),
|
||||
sprite_index: HashMap::new(),
|
||||
starfield_handle: None,
|
||||
texture_root,
|
||||
starfield_texture_name,
|
||||
image_root: texture_root,
|
||||
starfield_sprite_name: starfield_texture_name,
|
||||
};
|
||||
|
||||
// Order here matters, usually
|
||||
if root.texture.is_some() {
|
||||
part::texture::Texture::build(root.texture.take().unwrap(), &mut content)?;
|
||||
if root.sprite.is_some() {
|
||||
part::sprite::Sprite::build(root.sprite.take().unwrap(), &mut content)?;
|
||||
}
|
||||
|
||||
if root.ship.is_some() {
|
||||
part::ship::Ship::build(root.ship.take().unwrap(), &mut content)?;
|
||||
}
|
||||
|
@ -216,27 +230,32 @@ impl Content {
|
|||
|
||||
// Access methods
|
||||
impl Content {
|
||||
/// Get the texture handle for the starfield texture
|
||||
pub fn get_starfield_handle(&self) -> TextureHandle {
|
||||
/// Get the handle for the starfield sprite
|
||||
pub fn get_starfield_handle(&self) -> SpriteHandle {
|
||||
match self.starfield_handle {
|
||||
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
|
||||
pub fn get_texture_handle(&self, name: &str) -> TextureHandle {
|
||||
return match self.texture_index.get(name) {
|
||||
/// Get a handle from a sprite name
|
||||
pub fn get_sprite_handle(&self, name: &str) -> SpriteHandle {
|
||||
return match self.sprite_index.get(name) {
|
||||
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
|
||||
pub fn get_texture(&self, h: TextureHandle) -> &Texture {
|
||||
/// Get a sprite from a handle
|
||||
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.
|
||||
// The only TextureHandles that exist should be created by this crate.
|
||||
return &self.textures[h.index];
|
||||
// The only handles that exist should be created by this crate.
|
||||
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
|
||||
|
@ -249,7 +268,7 @@ impl Content {
|
|||
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 {
|
||||
return &self.ships[h.index];
|
||||
}
|
||||
|
|
|
@ -59,9 +59,9 @@ pub struct 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.
|
||||
// This lets us build FactionHandles before finishing all factions.
|
||||
let faction_names: Vec<String> = factions.keys().map(|x| x.to_owned()).collect();
|
||||
|
|
|
@ -3,7 +3,7 @@ use cgmath::Deg;
|
|||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{handle::TextureHandle, Content};
|
||||
use crate::{handle::SpriteHandle, Content};
|
||||
|
||||
use crate::OutfitSpace;
|
||||
|
||||
|
@ -23,7 +23,7 @@ pub(crate) mod syntax {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Projectile {
|
||||
pub sprite_texture: String,
|
||||
pub sprite: String,
|
||||
pub size: f32,
|
||||
pub size_rng: f32,
|
||||
pub speed: f32,
|
||||
|
@ -40,7 +40,7 @@ pub(crate) mod syntax {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ProjectileParticle {
|
||||
pub texture: String,
|
||||
pub sprite: String,
|
||||
pub lifetime: ParticleLifetime,
|
||||
pub inherit_velocity: super::ImpactInheritVelocity,
|
||||
pub size: f32,
|
||||
|
@ -110,7 +110,7 @@ pub struct Gun {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Projectile {
|
||||
/// The projectile sprite
|
||||
pub sprite_texture: TextureHandle,
|
||||
pub sprite: SpriteHandle,
|
||||
|
||||
/// The average size of this projectile
|
||||
/// (height in game units)
|
||||
|
@ -155,9 +155,9 @@ pub struct Projectile {
|
|||
/// The particle a projectile will spawn when it hits something
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProjectileParticle {
|
||||
/// The texture to use for this particle.
|
||||
/// The sprite to use for this particle.
|
||||
/// This is most likely animated.
|
||||
pub texture: TextureHandle,
|
||||
pub sprite: SpriteHandle,
|
||||
|
||||
/// How many seconds this particle should live
|
||||
pub lifetime: f32,
|
||||
|
@ -174,8 +174,8 @@ fn parse_projectile_particle(
|
|||
p: Option<syntax::ProjectileParticle>,
|
||||
) -> Result<Option<ProjectileParticle>> {
|
||||
if let Some(impact) = p {
|
||||
let impact_texture = match ct.texture_index.get(&impact.texture) {
|
||||
None => bail!("impact texture `{}` doesn't exist", impact.texture),
|
||||
let impact_sprite_handle = match ct.sprite_index.get(&impact.sprite) {
|
||||
None => bail!("impact sprite `{}` doesn't exist", impact.sprite),
|
||||
Some(t) => *t,
|
||||
};
|
||||
|
||||
|
@ -183,8 +183,8 @@ fn parse_projectile_particle(
|
|||
syntax::ParticleLifetime::Seconds(s) => s,
|
||||
syntax::ParticleLifetime::Inherit(s) => {
|
||||
if s == "inherit" {
|
||||
let t = ct.get_texture(impact_texture);
|
||||
t.fps * t.frames.len() as f32
|
||||
let sprite = ct.get_sprite(impact_sprite_handle);
|
||||
sprite.fps * sprite.frames.len() as f32
|
||||
} else {
|
||||
bail!("bad impact lifetime, must be float or \"inherit\"",)
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ fn parse_projectile_particle(
|
|||
};
|
||||
|
||||
Ok(Some(ProjectileParticle {
|
||||
texture: impact_texture,
|
||||
sprite: impact_sprite_handle,
|
||||
lifetime: impact_lifetime,
|
||||
inherit_velocity: impact.inherit_velocity,
|
||||
size: impact.size,
|
||||
|
@ -203,15 +203,15 @@ fn parse_projectile_particle(
|
|||
}
|
||||
|
||||
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 {
|
||||
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!(
|
||||
"In gun `{}`: projectile texture `{}` doesn't exist",
|
||||
"In gun `{}`: projectile sprite `{}` doesn't exist",
|
||||
gun_name,
|
||||
gun.projectile.sprite_texture
|
||||
gun.projectile.sprite
|
||||
),
|
||||
Some(t) => *t,
|
||||
};
|
||||
|
@ -229,7 +229,7 @@ impl crate::Build for Gun {
|
|||
rate_rng: gun.rate_rng,
|
||||
projectile: Projectile {
|
||||
force: gun.projectile.force,
|
||||
sprite_texture: projectile_texture,
|
||||
sprite: projectile_sprite_handle,
|
||||
size: gun.projectile.size,
|
||||
size_rng: gun.projectile.size_rng,
|
||||
speed: gun.projectile.speed,
|
||||
|
|
|
@ -5,13 +5,13 @@ pub mod gun;
|
|||
pub mod outfit;
|
||||
mod shared;
|
||||
pub mod ship;
|
||||
pub mod sprite;
|
||||
pub mod system;
|
||||
pub mod texture;
|
||||
|
||||
pub use faction::{Faction, Relationship};
|
||||
pub use gun::{Gun, ImpactInheritVelocity, Projectile, ProjectileCollider, ProjectileParticle};
|
||||
pub use outfit::Outfit;
|
||||
pub use shared::OutfitSpace;
|
||||
pub use ship::{EnginePoint, GunPoint, Ship};
|
||||
pub use sprite::{RepeatMode, Sprite};
|
||||
pub use system::{Object, System};
|
||||
pub use texture::{RepeatMode, Texture};
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::{handle::TextureHandle, Content, OutfitSpace};
|
||||
use crate::{handle::SpriteHandle, Content, OutfitSpace};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use crate::part::shared;
|
||||
|
@ -20,7 +20,7 @@ pub(crate) mod syntax {
|
|||
#[derive(Debug, Deserialize)]
|
||||
pub struct Engine {
|
||||
pub thrust: f32,
|
||||
pub flare_texture: String,
|
||||
pub flare_sprite: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -44,37 +44,37 @@ pub struct Outfit {
|
|||
/// The engine flare sprite this outfit creates.
|
||||
/// Its location and size is determined by a ship's
|
||||
/// engine points.
|
||||
pub engine_flare_texture: Option<TextureHandle>,
|
||||
pub engine_flare_sprite: Option<SpriteHandle>,
|
||||
|
||||
/// How much space this outfit requires
|
||||
pub space: OutfitSpace,
|
||||
}
|
||||
|
||||
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 {
|
||||
let mut o = Self {
|
||||
name: outfit_name.clone(),
|
||||
engine_thrust: 0.0,
|
||||
steer_power: 0.0,
|
||||
engine_flare_texture: None,
|
||||
engine_flare_sprite: None,
|
||||
space: OutfitSpace::from(outfit.space),
|
||||
};
|
||||
|
||||
// Engine stats
|
||||
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!(
|
||||
"In outfit `{}`: texture `{}` doesn't exist",
|
||||
"In outfit `{}`: flare sprite `{}` doesn't exist",
|
||||
outfit_name,
|
||||
engine.flare_texture
|
||||
engine.flare_sprite
|
||||
),
|
||||
Some(t) => *t,
|
||||
};
|
||||
o.engine_thrust = engine.thrust;
|
||||
o.engine_flare_texture = Some(th);
|
||||
o.engine_flare_sprite = Some(th);
|
||||
}
|
||||
|
||||
// Steering stats
|
||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::{bail, Result};
|
|||
use cgmath::Point2;
|
||||
use nalgebra::{point, Point};
|
||||
|
||||
use crate::{handle::TextureHandle, Content, OutfitSpace};
|
||||
use crate::{handle::SpriteHandle, Content, OutfitSpace};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use crate::part::shared;
|
||||
|
@ -15,7 +15,7 @@ pub(crate) mod syntax {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Ship {
|
||||
pub sprite_texture: String,
|
||||
pub sprite: String,
|
||||
pub size: f32,
|
||||
pub engines: Vec<Engine>,
|
||||
pub guns: Vec<Gun>,
|
||||
|
@ -57,7 +57,7 @@ pub struct Ship {
|
|||
pub name: String,
|
||||
|
||||
/// This ship's sprite
|
||||
pub sprite_texture: TextureHandle,
|
||||
pub sprite: SpriteHandle,
|
||||
|
||||
/// The size of this ship.
|
||||
/// Measured as unrotated height,
|
||||
|
@ -123,26 +123,26 @@ pub struct GunPoint {
|
|||
}
|
||||
|
||||
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 {
|
||||
let th = match ct.texture_index.get(&ship.sprite_texture) {
|
||||
let handle = match ct.sprite_index.get(&ship.sprite) {
|
||||
None => bail!(
|
||||
"In ship `{}`: texture `{}` doesn't exist",
|
||||
"In ship `{}`: sprite `{}` doesn't exist",
|
||||
ship_name,
|
||||
ship.sprite_texture
|
||||
ship.sprite
|
||||
),
|
||||
Some(t) => *t,
|
||||
};
|
||||
|
||||
let size = ship.size;
|
||||
let aspect = th.aspect;
|
||||
let aspect = ct.get_sprite(handle).aspect;
|
||||
|
||||
ct.ships.push(Self {
|
||||
aspect,
|
||||
name: ship_name,
|
||||
sprite_texture: th,
|
||||
sprite: handle,
|
||||
mass: ship.mass,
|
||||
space: OutfitSpace::from(ship.space),
|
||||
angular_drag: ship.angular_drag,
|
||||
|
|
|
@ -3,7 +3,7 @@ use image::io::Reader;
|
|||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use crate::{handle::TextureHandle, Content};
|
||||
use crate::{handle::SpriteHandle, Content};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use serde::Deserialize;
|
||||
|
@ -16,18 +16,18 @@ pub(crate) mod syntax {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Texture {
|
||||
Static(StaticTexture),
|
||||
Frames(FramesTexture),
|
||||
pub enum Sprite {
|
||||
Static(StaticSprite),
|
||||
Frames(FrameSprite),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct StaticTexture {
|
||||
pub struct StaticSprite {
|
||||
pub file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct FramesTexture {
|
||||
pub struct FrameSprite {
|
||||
pub frames: Vec<PathBuf>,
|
||||
pub duration: f32,
|
||||
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)]
|
||||
pub struct Texture {
|
||||
/// The name of this texture
|
||||
pub struct Sprite {
|
||||
/// The name of this sprite
|
||||
pub name: String,
|
||||
|
||||
/// The handle for this texture
|
||||
pub handle: TextureHandle,
|
||||
/// This sprite's handle
|
||||
pub handle: SpriteHandle,
|
||||
|
||||
/// The frames of this texture
|
||||
/// (static textures have one frame)
|
||||
/// The file names of frames of this sprite.
|
||||
/// unanimated sprites have one frame.
|
||||
pub frames: Vec<PathBuf>,
|
||||
|
||||
/// The speed of this texture's animation
|
||||
/// (static textures have zero fps)
|
||||
/// The speed of this sprite's animation.
|
||||
/// unanimated sprites have zero fps.
|
||||
pub fps: f32,
|
||||
|
||||
/// How to replay this texture's animation
|
||||
/// How to replay this sprite's animation
|
||||
pub repeat: RepeatMode,
|
||||
|
||||
/// Aspect ratio of this sprite (width / height)
|
||||
pub aspect: f32,
|
||||
}
|
||||
|
||||
impl crate::Build for Texture {
|
||||
type InputSyntax = HashMap<String, syntax::Texture>;
|
||||
impl crate::Build for Sprite {
|
||||
type InputSyntaxType = HashMap<String, syntax::Sprite>;
|
||||
|
||||
fn build(texture: Self::InputSyntax, ct: &mut Content) -> Result<()> {
|
||||
for (texture_name, t) in texture {
|
||||
fn build(sprites: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||
for (sprite_name, t) in sprites {
|
||||
match t {
|
||||
syntax::Texture::Static(t) => {
|
||||
let file = ct.texture_root.join(t.file);
|
||||
syntax::Sprite::Static(t) => {
|
||||
let file = ct.image_root.join(&t.file);
|
||||
let reader = Reader::open(&file).with_context(|| {
|
||||
format!(
|
||||
"Failed to read texture `{}` from file `{}`",
|
||||
texture_name,
|
||||
file.display()
|
||||
"Failed to read file `{}` in sprite `{}`",
|
||||
file.display(),
|
||||
sprite_name,
|
||||
)
|
||||
})?;
|
||||
let dim = reader.into_dimensions().with_context(|| {
|
||||
format!(
|
||||
"Failed to get dimensions of texture `{}` from file `{}`",
|
||||
texture_name,
|
||||
file.display()
|
||||
"Failed to get dimensions of file `{}` in sprite `{}`",
|
||||
file.display(),
|
||||
sprite_name,
|
||||
)
|
||||
})?;
|
||||
|
||||
let h = TextureHandle {
|
||||
index: ct.textures.len(),
|
||||
let h = SpriteHandle {
|
||||
index: ct.sprites.len() as u32,
|
||||
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() {
|
||||
ct.starfield_handle = Some(h)
|
||||
} else {
|
||||
// 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 {
|
||||
name: texture_name,
|
||||
frames: vec![file],
|
||||
ct.sprites.push(Self {
|
||||
name: sprite_name,
|
||||
frames: vec![t.file],
|
||||
fps: 0.0,
|
||||
handle: h,
|
||||
repeat: RepeatMode::Once,
|
||||
aspect: dim.0 as f32 / dim.1 as f32,
|
||||
});
|
||||
}
|
||||
syntax::Texture::Frames(t) => {
|
||||
syntax::Sprite::Frames(t) => {
|
||||
let mut dim = None;
|
||||
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(|| {
|
||||
format!(
|
||||
"Failed to read texture `{}` from file `{}`",
|
||||
texture_name,
|
||||
file.display()
|
||||
"Failed to read file `{}` in sprite `{}`",
|
||||
file.display(),
|
||||
sprite_name,
|
||||
)
|
||||
})?;
|
||||
let d = reader.into_dimensions().with_context(|| {
|
||||
format!(
|
||||
"Failed to get dimensions of texture `{}` from file `{}`",
|
||||
texture_name,
|
||||
file.display()
|
||||
"Failed to get dimensions of file `{}` in sprite `{}`",
|
||||
file.display(),
|
||||
sprite_name,
|
||||
)
|
||||
})?;
|
||||
match dim {
|
||||
|
@ -148,37 +152,34 @@ impl crate::Build for Texture {
|
|||
Some(e) => {
|
||||
if d != e {
|
||||
bail!(
|
||||
"Failed to load frames of texture `{}`. Frames have different sizes `{}`",
|
||||
texture_name,
|
||||
file.display()
|
||||
"Failed to load frames of sprite `{}` because frames have different sizes.",
|
||||
sprite_name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let dim = dim.unwrap();
|
||||
|
||||
let h = TextureHandle {
|
||||
index: ct.textures.len(),
|
||||
aspect: dim.unwrap().0 as f32 / dim.unwrap().1 as f32,
|
||||
let h = SpriteHandle {
|
||||
index: ct.sprites.len() as u32,
|
||||
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")
|
||||
}
|
||||
|
||||
let fps = t.duration / t.frames.len() as f32;
|
||||
|
||||
ct.texture_index.insert(texture_name.clone(), h);
|
||||
ct.textures.push(Self {
|
||||
name: texture_name,
|
||||
frames: t
|
||||
.frames
|
||||
.into_iter()
|
||||
.map(|f| ct.texture_root.join(f))
|
||||
.collect(),
|
||||
ct.sprite_index.insert(sprite_name.clone(), h);
|
||||
ct.sprites.push(Self {
|
||||
name: sprite_name,
|
||||
frames: t.frames,
|
||||
fps,
|
||||
handle: h,
|
||||
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() {
|
||||
bail!(
|
||||
"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 std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::{handle::TextureHandle, util::Polar, Content};
|
||||
use crate::{handle::SpriteHandle, util::Polar, Content};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use serde::Deserialize;
|
||||
|
@ -17,7 +17,7 @@ pub(crate) mod syntax {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Object {
|
||||
pub sprite_texture: String,
|
||||
pub sprite: String,
|
||||
pub position: Position,
|
||||
|
||||
pub size: f32,
|
||||
|
@ -94,7 +94,7 @@ pub struct System {
|
|||
#[derive(Debug)]
|
||||
pub struct Object {
|
||||
/// This object's sprite
|
||||
pub sprite_texture: TextureHandle,
|
||||
pub sprite: SpriteHandle,
|
||||
|
||||
/// This object's size.
|
||||
/// Measured as height in game units.
|
||||
|
@ -175,9 +175,9 @@ fn resolve_position(
|
|||
}
|
||||
|
||||
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 {
|
||||
let mut objects = Vec::new();
|
||||
|
||||
|
@ -185,17 +185,17 @@ impl crate::Build for System {
|
|||
let mut cycle_detector = HashSet::new();
|
||||
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!(
|
||||
"In system `{}`: texture `{}` doesn't exist",
|
||||
"In system `{}`: sprite `{}` doesn't exist",
|
||||
system_name,
|
||||
obj.sprite_texture
|
||||
obj.sprite
|
||||
),
|
||||
Some(t) => *t,
|
||||
};
|
||||
|
||||
objects.push(Object {
|
||||
sprite_texture: th,
|
||||
sprite: handle,
|
||||
position: resolve_position(&system.object, &obj, cycle_detector)
|
||||
.with_context(|| format!("In object {:#?}", label))?,
|
||||
size: obj.size,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[[bin]]
|
||||
name = "galactic"
|
||||
name = "galactica"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
|
|
|
@ -10,7 +10,7 @@ use galactica_behavior::{behavior, ShipBehavior};
|
|||
use galactica_constants;
|
||||
use galactica_content as content;
|
||||
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_world::{util, ShipPhysicsHandle, World};
|
||||
|
||||
|
@ -174,14 +174,15 @@ impl Game {
|
|||
self.last_update = Instant::now();
|
||||
}
|
||||
|
||||
pub fn get_frame_state(&mut self) -> FrameState {
|
||||
FrameState {
|
||||
pub fn get_frame_state(&mut self) -> RenderState {
|
||||
RenderState {
|
||||
camera_pos: self.camera.pos,
|
||||
camera_zoom: self.camera.zoom,
|
||||
object_sprites: self.get_object_sprites(),
|
||||
ui_sprites: self.get_ui_sprites(),
|
||||
new_particles: &mut self.new_particles,
|
||||
current_time: self.start_instant.elapsed().as_secs_f32(),
|
||||
content: &self.content,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,14 +17,15 @@ fn main() -> Result<()> {
|
|||
// TODO: error if missing
|
||||
let content = content::Content::load_dir(
|
||||
PathBuf::from(galactica_constants::CONTENT_ROOT),
|
||||
PathBuf::from(galactica_constants::TEXTURE_ROOT),
|
||||
PathBuf::from(galactica_constants::IMAGE_ROOT),
|
||||
PathBuf::from("spriteatlas.toml"),
|
||||
galactica_constants::STARFIELD_TEXTURE_NAME.to_owned(),
|
||||
galactica_constants::STARFIELD_SPRITE_NAME.to_owned(),
|
||||
)?;
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?;
|
||||
gpu.init();
|
||||
|
||||
let mut game = game::Game::new(content);
|
||||
gpu.update_starfield_buffer();
|
||||
|
|
|
@ -33,7 +33,7 @@ impl ShipGun {
|
|||
pub struct OutfitStatSum {
|
||||
pub engine_thrust: f32,
|
||||
pub steer_power: f32,
|
||||
pub engine_flare_textures: Vec<content::TextureHandle>,
|
||||
pub engine_flare_sprites: Vec<content::SpriteHandle>,
|
||||
}
|
||||
|
||||
impl OutfitStatSum {
|
||||
|
@ -41,23 +41,23 @@ impl OutfitStatSum {
|
|||
Self {
|
||||
engine_thrust: 0.0,
|
||||
steer_power: 0.0,
|
||||
engine_flare_textures: Vec::new(),
|
||||
engine_flare_sprites: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, o: &content::Outfit) {
|
||||
self.engine_thrust += o.engine_thrust;
|
||||
if let Some(t) = o.engine_flare_texture {
|
||||
self.engine_flare_textures.push(t);
|
||||
if let Some(t) = o.engine_flare_sprite {
|
||||
self.engine_flare_sprites.push(t);
|
||||
};
|
||||
self.steer_power += o.steer_power;
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, o: &content::Outfit) {
|
||||
self.engine_thrust -= o.engine_thrust;
|
||||
if let Some(t) = o.engine_flare_texture {
|
||||
self.engine_flare_textures.remove(
|
||||
self.engine_flare_textures
|
||||
if let Some(t) = o.engine_flare_sprite {
|
||||
self.engine_flare_sprites.remove(
|
||||
self.engine_flare_sprites
|
||||
.iter()
|
||||
.position(|x| *x == t)
|
||||
.unwrap(),
|
||||
|
@ -185,7 +185,7 @@ impl<'a> OutfitSet {
|
|||
pub fn update_engine_flares(&mut self) {
|
||||
// TODO: better way to pick flare texture
|
||||
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
|
||||
} else {
|
||||
return;
|
||||
|
@ -200,7 +200,7 @@ impl<'a> OutfitSet {
|
|||
y: p.pos.y,
|
||||
z: 1.0,
|
||||
},
|
||||
texture: *t,
|
||||
sprite: *s,
|
||||
angle: Deg(0.0),
|
||||
size: p.size,
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ impl System {
|
|||
for o in &sys.objects {
|
||||
s.bodies.push(SystemObject {
|
||||
pos: o.position,
|
||||
sprite_texture: o.sprite_texture,
|
||||
sprite: o.sprite,
|
||||
size: o.size,
|
||||
angle: o.angle,
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ use galactica_content as content;
|
|||
use galactica_render::ObjectSprite;
|
||||
|
||||
pub struct SystemObject {
|
||||
pub sprite_texture: content::TextureHandle,
|
||||
pub sprite: content::SpriteHandle,
|
||||
pub pos: Point3<f32>,
|
||||
pub size: f32,
|
||||
pub angle: Deg<f32>,
|
||||
|
@ -13,7 +13,7 @@ pub struct SystemObject {
|
|||
impl SystemObject {
|
||||
pub(crate) fn get_sprite(&self) -> ObjectSprite {
|
||||
return ObjectSprite {
|
||||
texture: self.sprite_texture,
|
||||
sprite: self.sprite,
|
||||
pos: self.pos,
|
||||
angle: self.angle,
|
||||
size: self.size,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{bail, Result};
|
||||
use galactica_packer::{SpriteAtlasImage, SpriteAtlasIndex};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
||||
use image::{imageops, ImageBuffer, Rgba, RgbaImage};
|
||||
use std::{
|
||||
fs::File,
|
||||
|
@ -11,6 +11,7 @@ use std::{
|
|||
// TODO: rework texturearray
|
||||
// TODO: reasonable sprite sizes
|
||||
// TODO: consistent naming
|
||||
// TODO: parallelize
|
||||
// spriteatlas: the big images
|
||||
// texture: the same, what we load to wgpu
|
||||
// image: a single file
|
||||
|
@ -28,13 +29,16 @@ pub struct AtlasSet {
|
|||
texture_limit: usize,
|
||||
|
||||
/// Keeps track of image files
|
||||
index: SpriteAtlasIndex,
|
||||
index: SpriteAtlas,
|
||||
|
||||
/// Array of textures, grows as needed
|
||||
texture_list: Vec<ImageBuffer<Rgba<u8>, Vec<u8>>>,
|
||||
|
||||
/// 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
|
||||
/// Format: ([xpos, ypos], [width, height])
|
||||
|
@ -43,105 +47,137 @@ pub struct AtlasSet {
|
|||
|
||||
/// Used to calculate packing efficiency
|
||||
used_area: f64,
|
||||
|
||||
/// The root directory that contains all image files.
|
||||
/// Files outside this directory will not be packed.
|
||||
asset_root: PathBuf,
|
||||
}
|
||||
|
||||
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 {
|
||||
asset_root: asset_root.to_path_buf(),
|
||||
texture_width,
|
||||
texture_height,
|
||||
texture_limit,
|
||||
texture_list: Vec::new(),
|
||||
image_max_sizes: Vec::new(),
|
||||
used_regions: Vec::new(),
|
||||
index: SpriteAtlasIndex::new(),
|
||||
index: SpriteAtlas::new(),
|
||||
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
|
||||
pub fn write_image(&mut self, path: &Path, dim: [u32; 2]) -> Result<usize> {
|
||||
let mut f = File::open(&path)?;
|
||||
let mut bytes = Vec::new();
|
||||
f.read_to_end(&mut 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.
|
||||
// Includes a few speed optimizations
|
||||
loop {
|
||||
let mut x = 0;
|
||||
let mut y = 0;
|
||||
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() {
|
||||
// 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
|
||||
self.texture_list
|
||||
.push(RgbaImage::new(self.texture_width, self.texture_height));
|
||||
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;
|
||||
let y = pixel_idx / self.texture_height;
|
||||
let new = ([x, y], dim);
|
||||
let mut used = false;
|
||||
// Optimization: save the smallest sprite that didn't fit in each atlas,
|
||||
// and don't try to add similarly-sized sprites.
|
||||
if dim[0] >= self.image_max_sizes[atlas_idx][0]
|
||||
&& dim[1] >= self.image_max_sizes[atlas_idx][1]
|
||||
{
|
||||
continue 'outer;
|
||||
}
|
||||
|
||||
x = 0;
|
||||
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 self.boxes_overlap(*r, new) {
|
||||
// Speed boost: skip the whole box
|
||||
pixel_idx += new.1[0] - 1;
|
||||
used = true;
|
||||
break;
|
||||
// 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 {
|
||||
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;
|
||||
}
|
||||
|
||||
if y + dim[1] >= self.texture_height {
|
||||
// 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;
|
||||
self.image_max_sizes[atlas_idx] = [dim[0] / 2, dim[1] / 2];
|
||||
} else if free {
|
||||
final_atlas_idx = Some(atlas_idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
let x = pixel_idx % self.texture_width;
|
||||
let y = pixel_idx / self.texture_height;
|
||||
//let img = RgbaImage::from_pixel(dim[0], dim[1], Rgba([0, 0, 0, 255]));
|
||||
imageops::overlay(&mut self.texture_list[atlas_idx], &img, x.into(), y.into());
|
||||
self.used_regions[atlas_idx].push(([x, y], dim));
|
||||
self.used_area += dim[0] as f64 * dim[1] as f64;
|
||||
|
||||
self.index.insert(
|
||||
path.to_path_buf(),
|
||||
let (sy, sd) = self.image_y_start[atlas_idx];
|
||||
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 {
|
||||
atlas: atlas_idx,
|
||||
x: x as f32 / self.texture_width as f32,
|
||||
|
|
|
@ -3,10 +3,7 @@
|
|||
//! This crate creates texture atlases from an asset tree.
|
||||
//! The main interface for this crate is ... TODO
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -36,26 +33,16 @@ pub struct SpriteAtlasImage {
|
|||
/// A map between file paths (relative to the root asset dir)
|
||||
/// and [`AtlasTexture`]s.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SpriteAtlasIndex {
|
||||
pub(crate) index: HashMap<PathBuf, SpriteAtlasImage>,
|
||||
pub struct SpriteAtlas {
|
||||
/// The images in this atlas
|
||||
pub index: HashMap<PathBuf, SpriteAtlasImage>,
|
||||
}
|
||||
|
||||
impl SpriteAtlasIndex {
|
||||
impl SpriteAtlas {
|
||||
/// Make an empty [`SpriteAtlasIndex`]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
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 anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use image::io::Reader;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
// TODO: procedural sun coloring
|
||||
// TODO: transparency buffer
|
||||
// TODO: warning when images have extra transparency
|
||||
// TODO: don't re-encode. Direct to gpu?
|
||||
// (maybe not, tiling is slow. Make it work with files first.)
|
||||
// TODO: path for atlas files
|
||||
// TODO: rework texturearray
|
||||
// TODO: reasonable sprite sizes (especially ui, document rules)
|
||||
// TODO: consistent naming
|
||||
// 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<()> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
for e in WalkDir::new("./assets/render")
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
{
|
||||
let asset_root = Path::new("./assets/render");
|
||||
|
||||
// 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() {
|
||||
// TODO: better warnings
|
||||
match e.path().extension() {
|
||||
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.");
|
||||
continue;
|
||||
}
|
||||
|
@ -46,7 +40,8 @@ fn main() -> Result<()> {
|
|||
let path = e.path().to_path_buf();
|
||||
let reader = Reader::open(&path)?;
|
||||
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)
|
||||
});
|
||||
|
||||
// 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
|
||||
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 mut i = 0;
|
||||
let mut peak_efficiency = 0f64;
|
||||
for (path, dim) in files {
|
||||
i += 1;
|
||||
let atlas_idx = atlas_set.write_image(&path, dim)?;
|
||||
|
@ -69,6 +73,7 @@ fn main() -> Result<()> {
|
|||
100.0 * atlas_set.get_efficiency(),
|
||||
path.display()
|
||||
);
|
||||
peak_efficiency = peak_efficiency.max(atlas_set.get_efficiency());
|
||||
}
|
||||
|
||||
println!(
|
||||
|
@ -76,9 +81,11 @@ fn main() -> Result<()> {
|
|||
100.0 * atlas_set.get_efficiency()
|
||||
);
|
||||
|
||||
println!("Peak efficiency: {:.02}%", 100.0 * peak_efficiency);
|
||||
|
||||
println!("Saving 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"),
|
||||
)?;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// INCLUDE: global uniform header
|
||||
|
||||
struct InstanceInput {
|
||||
@location(2) transform_matrix_0: vec4<f32>,
|
||||
@location(3) transform_matrix_1: vec4<f32>,
|
||||
|
@ -9,27 +11,12 @@ struct InstanceInput {
|
|||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) texture_coords: vec2<f32>,
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) texture_coords: vec2<f32>,
|
||||
@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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// INCLUDE: global uniform header
|
||||
|
||||
struct InstanceInput {
|
||||
@location(2) position: vec2<f32>,
|
||||
@location(3) velocity: vec2<f32>,
|
||||
|
@ -6,8 +8,7 @@ struct InstanceInput {
|
|||
@location(6) size: f32,
|
||||
@location(7) created: f32,
|
||||
@location(8) expires: f32,
|
||||
@location(9) texture_index_len_rep: vec3<u32>,
|
||||
@location(10) texture_aspect_fps: vec2<f32>,
|
||||
@location(9) texture_index: u32,
|
||||
};
|
||||
|
||||
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)
|
||||
var texture_array: binding_array<texture_2d<f32>>;
|
||||
@group(0) @binding(1)
|
||||
|
@ -57,37 +43,51 @@ fn vertex_main(
|
|||
out.texture_coords = vertex.texture_coords;
|
||||
|
||||
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);
|
||||
return out;
|
||||
}
|
||||
|
||||
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);
|
||||
if instance.texture_index_len_rep.z == u32(1) {
|
||||
if rep == u32(1) {
|
||||
// Repeat
|
||||
frame = u32(fmod(
|
||||
(age / instance.texture_aspect_fps.y),
|
||||
f32(instance.texture_index_len_rep.y)
|
||||
(age / fps),
|
||||
f32(len)
|
||||
));
|
||||
} else {
|
||||
// Once
|
||||
frame = u32(min(
|
||||
(age / instance.texture_aspect_fps.y),
|
||||
f32(instance.texture_index_len_rep.y) - 1.0
|
||||
(age / fps),
|
||||
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);
|
||||
|
||||
var scale: f32 = instance.size / global.camera_zoom.x;
|
||||
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
|
||||
|
||||
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
|
||||
);
|
||||
pos = rotation * pos;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// INCLUDE: global uniform header
|
||||
|
||||
struct InstanceInput {
|
||||
@location(2) position: vec3<f32>,
|
||||
@location(3) size: f32,
|
||||
|
@ -12,24 +14,10 @@ struct VertexInput {
|
|||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<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)
|
||||
var texture_array: binding_array<texture_2d<f32>>;
|
||||
@group(0) @binding(1)
|
||||
|
@ -49,7 +37,6 @@ fn vertex_main(
|
|||
) -> VertexOutput {
|
||||
|
||||
var out: VertexOutput;
|
||||
out.texture_coords = vertex.texture_coords;
|
||||
out.tint = instance.tint;
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -136,7 +135,7 @@ fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||
let c_del = c_bot - c_top;
|
||||
|
||||
return textureSampleLevel(
|
||||
texture_array[global.starfield_texture.x],
|
||||
texture_array[in.texture_index],
|
||||
sampler_array[0],
|
||||
in.texture_coords,
|
||||
0.0
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// INCLUDE: global uniform header
|
||||
|
||||
struct InstanceInput {
|
||||
@location(2) transform_matrix_0: vec4<f32>,
|
||||
@location(3) transform_matrix_1: vec4<f32>,
|
||||
|
@ -19,22 +21,6 @@ struct VertexOutput {
|
|||
@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)
|
||||
var texture_array: binding_array<texture_2d<f32>>;
|
||||
@group(0) @binding(1)
|
||||
|
@ -58,9 +44,19 @@ fn vertex_main(
|
|||
|
||||
var out: VertexOutput;
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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::{
|
||||
content,
|
||||
globaldata::{GlobalData, GlobalDataContent},
|
||||
globaluniform::{AtlasContent, DataContent, GlobalUniform, SpriteContent},
|
||||
pipeline::PipelineBuilder,
|
||||
sprite::ObjectSubSprite,
|
||||
starfield::Starfield,
|
||||
|
@ -18,7 +18,7 @@ use crate::{
|
|||
types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
||||
BufferObject, VertexBuffer,
|
||||
},
|
||||
FrameState, ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
||||
ObjectSprite, RenderState, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
||||
};
|
||||
|
||||
/// A high-level GPU wrapper. Consumes game state,
|
||||
|
@ -44,7 +44,7 @@ pub struct GPUState {
|
|||
|
||||
starfield: Starfield,
|
||||
texture_array: TextureArray,
|
||||
global_data: GlobalData,
|
||||
global_uniform: GlobalUniform,
|
||||
vertex_buffers: VertexBuffers,
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,19 @@ struct VertexBuffers {
|
|||
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 {
|
||||
/// Make a new GPUState that draws on `window`
|
||||
pub async fn new(window: Window, ct: &content::Content) -> Result<Self> {
|
||||
|
@ -161,22 +174,26 @@ impl GPUState {
|
|||
};
|
||||
|
||||
// Load uniforms
|
||||
let global_data = GlobalData::new(&device);
|
||||
let global_uniform = GlobalUniform::new(&device);
|
||||
let texture_array = TextureArray::new(&device, &queue, ct)?;
|
||||
|
||||
// Make sure these match the indices in each shader
|
||||
let bind_group_layouts = &[
|
||||
&texture_array.bind_group_layout,
|
||||
&global_data.bind_group_layout,
|
||||
&global_uniform.bind_group_layout,
|
||||
];
|
||||
|
||||
// Create render pipelines
|
||||
let object_pipeline = PipelineBuilder::new("object", &device)
|
||||
.set_shader(include_str!(concat!(
|
||||
.set_shader(&preprocess_shader(
|
||||
&include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/shaders/",
|
||||
"object.wgsl"
|
||||
)))
|
||||
)),
|
||||
&global_uniform,
|
||||
1,
|
||||
))
|
||||
.set_format(config.format)
|
||||
.set_triangle(true)
|
||||
.set_vertex_buffer(&vertex_buffers.object)
|
||||
|
@ -184,11 +201,15 @@ impl GPUState {
|
|||
.build();
|
||||
|
||||
let starfield_pipeline = PipelineBuilder::new("starfield", &device)
|
||||
.set_shader(include_str!(concat!(
|
||||
.set_shader(&preprocess_shader(
|
||||
&include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/shaders/",
|
||||
"starfield.wgsl"
|
||||
)))
|
||||
)),
|
||||
&global_uniform,
|
||||
1,
|
||||
))
|
||||
.set_format(config.format)
|
||||
.set_triangle(true)
|
||||
.set_vertex_buffer(&vertex_buffers.starfield)
|
||||
|
@ -196,11 +217,11 @@ impl GPUState {
|
|||
.build();
|
||||
|
||||
let ui_pipeline = PipelineBuilder::new("ui", &device)
|
||||
.set_shader(include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/shaders/",
|
||||
"ui.wgsl"
|
||||
)))
|
||||
.set_shader(&preprocess_shader(
|
||||
&include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", "ui.wgsl")),
|
||||
&global_uniform,
|
||||
1,
|
||||
))
|
||||
.set_format(config.format)
|
||||
.set_triangle(true)
|
||||
.set_vertex_buffer(&vertex_buffers.ui)
|
||||
|
@ -208,11 +229,15 @@ impl GPUState {
|
|||
.build();
|
||||
|
||||
let particle_pipeline = PipelineBuilder::new("particle", &device)
|
||||
.set_shader(include_str!(concat!(
|
||||
.set_shader(&preprocess_shader(
|
||||
&include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/shaders/",
|
||||
"particle.wgsl"
|
||||
)))
|
||||
)),
|
||||
&global_uniform,
|
||||
1,
|
||||
))
|
||||
.set_format(config.format)
|
||||
.set_triangle(true)
|
||||
.set_vertex_buffer(&vertex_buffers.particle)
|
||||
|
@ -239,7 +264,7 @@ impl GPUState {
|
|||
|
||||
starfield,
|
||||
texture_array,
|
||||
global_data,
|
||||
global_uniform,
|
||||
vertex_buffers,
|
||||
});
|
||||
}
|
||||
|
@ -267,8 +292,7 @@ impl GPUState {
|
|||
/// Also handles child sprites.
|
||||
fn push_object_sprite(
|
||||
&self,
|
||||
camera_zoom: f32,
|
||||
camera_pos: Point2<f32>,
|
||||
state: &RenderState,
|
||||
instances: &mut Vec<ObjectInstance>,
|
||||
clip_ne: Point2<f32>,
|
||||
clip_sw: Point2<f32>,
|
||||
|
@ -280,10 +304,9 @@ impl GPUState {
|
|||
(Point2 {
|
||||
x: s.pos.x,
|
||||
y: s.pos.y,
|
||||
} - camera_pos.to_vec())
|
||||
} - state.camera_pos.to_vec())
|
||||
/ s.pos.z
|
||||
};
|
||||
let texture = self.texture_array.get_texture(s.texture);
|
||||
|
||||
// Game dimensions of this sprite post-scale.
|
||||
// Don't divide by 2, we use this later.
|
||||
|
@ -291,7 +314,7 @@ impl GPUState {
|
|||
|
||||
// Width or height, whichever is larger.
|
||||
// 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)
|
||||
// sprites that are off the screen
|
||||
|
@ -304,7 +327,7 @@ impl GPUState {
|
|||
}
|
||||
|
||||
// TODO: clean up
|
||||
let scale = height / camera_zoom;
|
||||
let scale = height / state.camera_zoom;
|
||||
|
||||
// Note that our mesh starts centered at (0, 0).
|
||||
// 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
|
||||
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
|
||||
let rotate = Matrix4::from_angle_z(s.angle);
|
||||
|
@ -333,8 +356,8 @@ impl GPUState {
|
|||
// The height of the viewport is `zoom` in game units,
|
||||
// but it's 2 in screen units! (since coordinates range from -1 to 1)
|
||||
let translate = Matrix4::from_translation(Vector3 {
|
||||
x: pos.x / (camera_zoom / 2.0) / self.window_aspect,
|
||||
y: pos.y / (camera_zoom / 2.0),
|
||||
x: pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
|
||||
y: pos.y / (state.camera_zoom / 2.0),
|
||||
z: 0.0,
|
||||
});
|
||||
|
||||
|
@ -345,13 +368,13 @@ impl GPUState {
|
|||
|
||||
instances.push(ObjectInstance {
|
||||
transform: t.into(),
|
||||
texture_index: texture.index,
|
||||
sprite_index: s.sprite.index,
|
||||
});
|
||||
|
||||
// Add children
|
||||
if let Some(children) = &s.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`.
|
||||
fn push_object_subsprite(
|
||||
&self,
|
||||
camera_zoom: f32,
|
||||
state: &RenderState,
|
||||
instances: &mut Vec<ObjectInstance>,
|
||||
s: &ObjectSubSprite,
|
||||
parent_pos: Point2<f32>,
|
||||
parent_angle: Deg<f32>,
|
||||
) {
|
||||
let texture = self.texture_array.get_texture(s.texture);
|
||||
let scale = s.size / (s.pos.z * camera_zoom);
|
||||
let scale = s.size / (s.pos.z * state.camera_zoom);
|
||||
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 screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
|
||||
|
||||
let ptranslate = Matrix4::from_translation(Vector3 {
|
||||
x: parent_pos.x / (camera_zoom / 2.0) / self.window_aspect,
|
||||
y: parent_pos.y / (camera_zoom / 2.0),
|
||||
x: parent_pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
|
||||
y: parent_pos.y / (state.camera_zoom / 2.0),
|
||||
z: 0.0,
|
||||
});
|
||||
let protate = Matrix4::from_angle_z(parent_angle);
|
||||
|
||||
let translate = Matrix4::from_translation(Vector3 {
|
||||
x: s.pos.x / (camera_zoom / 2.0) / self.window_aspect,
|
||||
y: s.pos.y / (camera_zoom / 2.0),
|
||||
x: s.pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
|
||||
y: s.pos.y / (state.camera_zoom / 2.0),
|
||||
z: 0.0,
|
||||
});
|
||||
|
||||
|
@ -395,7 +417,7 @@ impl GPUState {
|
|||
|
||||
instances.push(ObjectInstance {
|
||||
transform: t.into(),
|
||||
texture_index: texture.index,
|
||||
sprite_index: s.sprite.index,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -404,7 +426,6 @@ impl GPUState {
|
|||
let logical_size: LogicalSize<f32> =
|
||||
self.window_size.to_logical(self.window.scale_factor());
|
||||
|
||||
let texture = self.texture_array.get_texture(s.texture);
|
||||
let width = s.dimensions.x;
|
||||
let height = s.dimensions.y;
|
||||
|
||||
|
@ -448,7 +469,7 @@ impl GPUState {
|
|||
|
||||
instances.push(UiInstance {
|
||||
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]),
|
||||
});
|
||||
}
|
||||
|
@ -456,23 +477,16 @@ impl GPUState {
|
|||
/// Make an instance for all the game's sprites
|
||||
/// (Objects and UI)
|
||||
/// 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();
|
||||
|
||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||
// Used to skip off-screen sprites.
|
||||
let clip_ne = Point2::from((-self.window_aspect, 1.0)) * framestate.camera_zoom;
|
||||
let clip_sw = 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)) * state.camera_zoom;
|
||||
|
||||
for s in framestate.object_sprites {
|
||||
self.push_object_sprite(
|
||||
framestate.camera_zoom,
|
||||
framestate.camera_pos,
|
||||
&mut object_instances,
|
||||
clip_ne,
|
||||
clip_sw,
|
||||
&s,
|
||||
);
|
||||
for s in &state.object_sprites {
|
||||
self.push_object_sprite(state, &mut object_instances, clip_ne, clip_sw, &s);
|
||||
}
|
||||
|
||||
// Enforce sprite limit
|
||||
|
@ -489,7 +503,7 @@ impl GPUState {
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
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 view = output
|
||||
.texture
|
||||
|
@ -552,32 +587,32 @@ impl GPUState {
|
|||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
let s = state.content.get_starfield_handle();
|
||||
// Update global values
|
||||
self.queue.write_buffer(
|
||||
&self.global_data.buffer,
|
||||
&self.global_uniform.data_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[GlobalDataContent {
|
||||
camera_position: framestate.camera_pos.into(),
|
||||
camera_zoom: [framestate.camera_zoom, 0.0],
|
||||
bytemuck::cast_slice(&[DataContent {
|
||||
camera_position: state.camera_pos.into(),
|
||||
camera_zoom: [state.camera_zoom, 0.0],
|
||||
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX],
|
||||
window_size: [
|
||||
self.window_size.width as f32,
|
||||
self.window_size.height as f32,
|
||||
],
|
||||
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_size_limits: [
|
||||
galactica_constants::STARFIELD_SIZE_MIN,
|
||||
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
|
||||
for i in framestate.new_particles.iter() {
|
||||
let texture = self.texture_array.get_texture(i.texture);
|
||||
for i in state.new_particles.iter() {
|
||||
self.queue.write_buffer(
|
||||
&self.vertex_buffers.particle.instances,
|
||||
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
|
||||
|
@ -586,10 +621,9 @@ impl GPUState {
|
|||
velocity: i.velocity.into(),
|
||||
rotation: Matrix2::from_angle(i.angle).into(),
|
||||
size: i.size,
|
||||
texture_index_len_rep: [texture.index, texture.len, texture.repeat],
|
||||
texture_aspect_fps: [texture.aspect, texture.fps],
|
||||
created: framestate.current_time,
|
||||
expires: framestate.current_time + i.lifetime,
|
||||
sprite_index: i.sprite.index,
|
||||
created: state.current_time,
|
||||
expires: state.current_time + i.lifetime,
|
||||
}]),
|
||||
);
|
||||
self.vertex_buffers.particle_array_head += 1;
|
||||
|
@ -599,15 +633,15 @@ impl GPUState {
|
|||
self.vertex_buffers.particle_array_head = 0;
|
||||
}
|
||||
}
|
||||
framestate.new_particles.clear();
|
||||
state.new_particles.clear();
|
||||
|
||||
// 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,
|
||||
// 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(1, &self.global_data.bind_group, &[]);
|
||||
render_pass.set_bind_group(1, &self.global_uniform.bind_group, &[]);
|
||||
|
||||
// Starfield pipeline
|
||||
self.vertex_buffers.starfield.set_in_pass(&mut render_pass);
|
||||
|
|
|
@ -7,18 +7,18 @@
|
|||
//! and the only one external code should interact with.
|
||||
//! (Excluding data structs, like [`ObjectSprite`])
|
||||
|
||||
mod framestate;
|
||||
mod globaldata;
|
||||
mod globaluniform;
|
||||
mod gpustate;
|
||||
mod pipeline;
|
||||
mod renderstate;
|
||||
mod sprite;
|
||||
mod starfield;
|
||||
mod texturearray;
|
||||
mod vertexbuffer;
|
||||
|
||||
pub use framestate::FrameState;
|
||||
use galactica_content as content;
|
||||
pub use gpustate::GPUState;
|
||||
pub use renderstate::RenderState;
|
||||
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
|
||||
|
||||
use cgmath::Matrix4;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use cgmath::Point2;
|
||||
use galactica_content::Content;
|
||||
|
||||
use crate::{ObjectSprite, ParticleBuilder, UiSprite};
|
||||
|
||||
/// Bundles parameters passed to a single call to GPUState::render
|
||||
pub struct FrameState<'a> {
|
||||
pub struct RenderState<'a> {
|
||||
/// Camera position, in world units
|
||||
pub camera_pos: Point2<f32>,
|
||||
|
||||
|
@ -23,4 +24,7 @@ pub struct FrameState<'a> {
|
|||
// TODO: handle overflow
|
||||
/// The current time, in seconds
|
||||
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
|
||||
pub struct ParticleBuilder {
|
||||
/// The texture to use for this particle
|
||||
pub texture: content::TextureHandle,
|
||||
/// The sprite to use for this particle
|
||||
pub sprite: content::SpriteHandle,
|
||||
|
||||
/// This object's center, in world coordinates.
|
||||
pub pos: Point2<f32>,
|
||||
|
@ -18,7 +18,7 @@ pub struct ParticleBuilder {
|
|||
/// This particle's lifetime, in seconds
|
||||
pub lifetime: f32,
|
||||
|
||||
/// The size of this sprite,
|
||||
/// The size of this particle,
|
||||
/// given as height in world units.
|
||||
pub size: f32,
|
||||
}
|
||||
|
@ -54,8 +54,8 @@ pub enum AnchoredUiPosition {
|
|||
/// A sprite that represents a ui element
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UiSprite {
|
||||
/// The texture to use for this sprite
|
||||
pub texture: content::TextureHandle,
|
||||
/// The sprite to draw
|
||||
pub sprite: content::SpriteHandle,
|
||||
|
||||
/// This object's position, in logical (dpi-adjusted) pixels
|
||||
pub pos: AnchoredUiPosition,
|
||||
|
@ -75,8 +75,8 @@ pub struct UiSprite {
|
|||
/// Ships, planets, debris, etc
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjectSprite {
|
||||
/// The texture to use for this sprite
|
||||
pub texture: content::TextureHandle,
|
||||
/// The sprite to draw
|
||||
pub sprite: content::SpriteHandle,
|
||||
|
||||
/// This object's center, in world coordinates.
|
||||
pub pos: Point3<f32>,
|
||||
|
@ -97,8 +97,8 @@ pub struct ObjectSprite {
|
|||
/// A sprite that is drawn relative to an ObjectSprite.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjectSubSprite {
|
||||
/// The sprite texture to draw
|
||||
pub texture: content::TextureHandle,
|
||||
/// The sprite to draw
|
||||
pub sprite: content::SpriteHandle,
|
||||
|
||||
/// This object's position, in world coordinates.
|
||||
/// 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 bytemuck::Zeroable;
|
||||
use galactica_packer::SpriteAtlasImage;
|
||||
use image::GenericImageView;
|
||||
use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32};
|
||||
use std::{fs::File, io::Read, num::NonZeroU32};
|
||||
use wgpu::BindGroupLayout;
|
||||
|
||||
pub(crate) struct RawTexture {
|
||||
|
@ -67,57 +72,60 @@ impl RawTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Texture {
|
||||
pub index: u32, // Index in texture array
|
||||
pub len: u32, // Number of frames
|
||||
pub fps: f32, // Frames per second
|
||||
pub aspect: f32, // width / height
|
||||
pub repeat: u32, // How to re-play this texture
|
||||
pub location: Vec<SpriteAtlasImage>,
|
||||
}
|
||||
|
||||
pub struct TextureArray {
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
pub bind_group_layout: BindGroupLayout,
|
||||
starfield_handle: content::TextureHandle,
|
||||
textures: HashMap<content::TextureHandle, Texture>,
|
||||
pub image_locations: ImageLocationArray,
|
||||
pub sprite_data: SpriteDataArray,
|
||||
}
|
||||
|
||||
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> {
|
||||
// Load all textures
|
||||
let mut texture_data = Vec::new();
|
||||
let mut textures = HashMap::new();
|
||||
|
||||
for t in &ct.textures {
|
||||
let index = texture_data.len() as u32;
|
||||
for f in &t.frames {
|
||||
let mut f = File::open(&f)?;
|
||||
println!("opening image");
|
||||
let mut f = File::open("atlas-0.bmp")?;
|
||||
let mut bytes = Vec::new();
|
||||
f.read_to_end(&mut bytes)?;
|
||||
texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, &t.name)?);
|
||||
}
|
||||
textures.insert(
|
||||
t.handle,
|
||||
Texture {
|
||||
index,
|
||||
aspect: t.handle.aspect,
|
||||
texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, "Atlas")?);
|
||||
|
||||
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,
|
||||
len: t.frames.len() as u32,
|
||||
repeat: t.repeat.as_int(),
|
||||
},
|
||||
);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
|
@ -136,7 +144,7 @@ impl TextureArray {
|
|||
// Texture data
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
|
@ -147,7 +155,7 @@ impl TextureArray {
|
|||
// Texture sampler
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: NonZeroU32::new(1),
|
||||
},
|
||||
|
@ -161,7 +169,6 @@ impl TextureArray {
|
|||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
// Array of all views
|
||||
resource: wgpu::BindingResource::TextureViewArray(&views),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
|
@ -174,8 +181,8 @@ impl TextureArray {
|
|||
return Ok(Self {
|
||||
bind_group,
|
||||
bind_group_layout,
|
||||
textures: textures,
|
||||
starfield_handle: ct.get_starfield_handle(),
|
||||
image_locations,
|
||||
sprite_data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ pub struct ObjectInstance {
|
|||
pub transform: [[f32; 4]; 4],
|
||||
|
||||
/// What texture to use for this sprite
|
||||
pub texture_index: u32,
|
||||
pub sprite_index: u32,
|
||||
}
|
||||
|
||||
impl BufferObject for ObjectInstance {
|
||||
|
@ -122,7 +122,7 @@ impl BufferObject for ObjectInstance {
|
|||
shader_location: 5,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
// Texture
|
||||
// Sprite
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
|
||||
shader_location: 6,
|
||||
|
@ -146,7 +146,7 @@ pub struct UiInstance {
|
|||
pub color: [f32; 4],
|
||||
|
||||
/// What texture to use for this sprite
|
||||
pub texture_index: u32,
|
||||
pub sprite_index: u32,
|
||||
}
|
||||
|
||||
impl BufferObject for UiInstance {
|
||||
|
@ -185,7 +185,7 @@ impl BufferObject for UiInstance {
|
|||
shader_location: 6,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
// Texture
|
||||
// Sprite
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 20]>() as wgpu::BufferAddress,
|
||||
shader_location: 7,
|
||||
|
@ -219,9 +219,8 @@ pub struct ParticleInstance {
|
|||
/// Time is kept by a variable in the global uniform.
|
||||
pub expires: f32,
|
||||
|
||||
/// What texture to use for this particle
|
||||
pub texture_index_len_rep: [u32; 3],
|
||||
pub texture_aspect_fps: [f32; 2],
|
||||
/// What sprite to use for this particle
|
||||
pub sprite_index: u32,
|
||||
}
|
||||
|
||||
impl BufferObject for ParticleInstance {
|
||||
|
@ -271,17 +270,11 @@ impl BufferObject for ParticleInstance {
|
|||
shader_location: 8,
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
},
|
||||
// Texture index / len / repeat
|
||||
// Sprite
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
||||
shader_location: 9,
|
||||
format: wgpu::VertexFormat::Uint32x3,
|
||||
},
|
||||
// Texture aspect / fps
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 14]>() as wgpu::BufferAddress,
|
||||
shader_location: 10,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
format: wgpu::VertexFormat::Uint32,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ pub fn build_radar(
|
|||
|
||||
let (_, player_body) = physics.get_ship_body(player).unwrap();
|
||||
let player_position = util::rigidbody_position(player_body);
|
||||
let planet_texture = ct.get_texture_handle("ui::planetblip");
|
||||
let ship_texture = ct.get_texture_handle("ui::shipblip");
|
||||
let arrow_texture = ct.get_texture_handle("ui::centerarrow");
|
||||
let planet_sprite = ct.get_sprite_handle("ui::planetblip");
|
||||
let ship_sprite = ct.get_sprite_handle("ui::shipblip");
|
||||
let arrow_sprite = ct.get_sprite_handle("ui::centerarrow");
|
||||
|
||||
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 }),
|
||||
dimensions: Point2 {
|
||||
x: radar_size,
|
||||
|
@ -57,7 +57,7 @@ pub fn build_radar(
|
|||
continue;
|
||||
}
|
||||
out.push(UiSprite {
|
||||
texture: planet_texture,
|
||||
sprite: planet_sprite,
|
||||
pos: AnchoredUiPosition::NwC(
|
||||
Point2 {
|
||||
x: radar_size / 2.0 + 10.0,
|
||||
|
@ -65,7 +65,7 @@ pub fn build_radar(
|
|||
} + (d * (radar_size / 2.0)),
|
||||
),
|
||||
dimensions: Point2 {
|
||||
x: planet_texture.aspect,
|
||||
x: planet_sprite.aspect,
|
||||
y: 1.0,
|
||||
} * size,
|
||||
angle: o.angle,
|
||||
|
@ -77,7 +77,7 @@ pub fn build_radar(
|
|||
// Draw ships
|
||||
for (s, r) in physics.iter_ship_body() {
|
||||
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 d = (p - player_position) / radar_range;
|
||||
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 = [f[0], f[1], f[2], 1.0];
|
||||
out.push(UiSprite {
|
||||
texture: ship_texture,
|
||||
sprite: ship_sprite,
|
||||
pos: AnchoredUiPosition::NwC(
|
||||
Point2 {
|
||||
x: radar_size / 2.0 + 10.0,
|
||||
|
@ -100,7 +100,7 @@ pub fn build_radar(
|
|||
} + (d * (radar_size / 2.0)),
|
||||
),
|
||||
dimensions: Point2 {
|
||||
x: ship_texture.aspect,
|
||||
x: ship_sprite.aspect,
|
||||
y: 1.0,
|
||||
} * size,
|
||||
angle: -angle,
|
||||
|
@ -118,13 +118,13 @@ pub fn build_radar(
|
|||
let d = d * (radar_size / 2.0);
|
||||
let color = Some([0.3, 0.3, 0.3, 1.0]);
|
||||
if m < 0.8 {
|
||||
let texture = ct.get_texture_handle("ui::radarframe");
|
||||
let sprite = ct.get_sprite_handle("ui::radarframe");
|
||||
let dimensions = Point2 {
|
||||
x: texture.aspect,
|
||||
x: sprite.aspect,
|
||||
y: 1.0,
|
||||
} * 7.0f32.min((0.8 - m) * 70.0);
|
||||
out.push(UiSprite {
|
||||
texture,
|
||||
sprite,
|
||||
pos: AnchoredUiPosition::NwNw(Point2 {
|
||||
x: (radar_size / 2.0 + 10.0) - d.x,
|
||||
y: (radar_size / -2.0 - 10.0) + d.y,
|
||||
|
@ -135,7 +135,7 @@ pub fn build_radar(
|
|||
});
|
||||
|
||||
out.push(UiSprite {
|
||||
texture,
|
||||
sprite,
|
||||
pos: AnchoredUiPosition::NwSw(Point2 {
|
||||
x: (radar_size / 2.0 + 10.0) - d.x,
|
||||
y: (radar_size / -2.0 - 10.0) - d.y,
|
||||
|
@ -146,7 +146,7 @@ pub fn build_radar(
|
|||
});
|
||||
|
||||
out.push(UiSprite {
|
||||
texture,
|
||||
sprite,
|
||||
pos: AnchoredUiPosition::NwSe(Point2 {
|
||||
x: (radar_size / 2.0 + 10.0) + d.x,
|
||||
y: (radar_size / -2.0 - 10.0) - d.y,
|
||||
|
@ -157,7 +157,7 @@ pub fn build_radar(
|
|||
});
|
||||
|
||||
out.push(UiSprite {
|
||||
texture,
|
||||
sprite,
|
||||
pos: AnchoredUiPosition::NwNe(Point2 {
|
||||
x: (radar_size / 2.0 + 10.0) + d.x,
|
||||
y: (radar_size / -2.0 - 10.0) + d.y,
|
||||
|
@ -174,7 +174,7 @@ pub fn build_radar(
|
|||
if m > 200.0 {
|
||||
let player_angle: Deg<f32> = q.angle(Vector2 { x: 0.0, y: 1.0 }).into();
|
||||
out.push(UiSprite {
|
||||
texture: arrow_texture,
|
||||
sprite: arrow_sprite,
|
||||
pos: AnchoredUiPosition::NwC(
|
||||
Point2 {
|
||||
x: radar_size / 2.0 + 10.0,
|
||||
|
@ -182,7 +182,7 @@ pub fn build_radar(
|
|||
} + ((q.normalize() * 0.865) * (radar_size / 2.0)),
|
||||
),
|
||||
dimensions: Point2 {
|
||||
x: arrow_texture.aspect,
|
||||
x: arrow_sprite.aspect,
|
||||
y: 1.0,
|
||||
} * 10.0,
|
||||
angle: -player_angle,
|
||||
|
|
|
@ -44,7 +44,7 @@ impl ProjectileWorldObject {
|
|||
let ang: Deg<f32> = rot.angle(Vector2 { x: 1.0, y: 0.0 }).into();
|
||||
|
||||
ObjectSprite {
|
||||
texture: self.projectile.content.sprite_texture,
|
||||
sprite: self.projectile.content.sprite,
|
||||
pos: Point3 {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
|
|
|
@ -86,7 +86,7 @@ impl ShipWorldObject {
|
|||
|
||||
ObjectSprite {
|
||||
pos: (ship_pos.x, ship_pos.y, 1.0).into(),
|
||||
texture: s.sprite_texture,
|
||||
sprite: s.sprite,
|
||||
angle: -ship_ang,
|
||||
size: s.size,
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ impl<'a> World {
|
|||
}
|
||||
};
|
||||
particles.push(ParticleBuilder {
|
||||
texture: x.texture,
|
||||
sprite: x.sprite,
|
||||
pos: Point2 { x: pos.x, y: pos.y },
|
||||
velocity,
|
||||
angle: -angle,
|
||||
|
@ -332,7 +332,7 @@ impl<'a> World {
|
|||
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(&pr),
|
||||
};
|
||||
particles.push(ParticleBuilder {
|
||||
texture: x.texture,
|
||||
sprite: x.sprite,
|
||||
pos: Point2 { x: pos.x, y: pos.y },
|
||||
velocity,
|
||||
angle: -angle,
|
||||
|
|
Loading…
Reference in New Issue