Compare commits
12 Commits
7334ebd00e
...
cb65b67530
Author | SHA1 | Date |
---|---|---|
Mark | cb65b67530 | |
Mark | 56160a8abe | |
Mark | 7d5b244492 | |
Mark | f7525901f5 | |
Mark | fdd481e8f0 | |
Mark | 73f540d30a | |
Mark | 132148fee3 | |
Mark | f4c0e91851 | |
Mark | 70c9ec3b92 | |
Mark | ade2a89a51 | |
Mark | 66dafa16bc | |
Mark | ad34dc4f70 |
2
TODO.md
2
TODO.md
|
@ -26,6 +26,8 @@
|
||||||
- Reverse engines + flares
|
- Reverse engines + flares
|
||||||
- Turn flares (physics by location?)
|
- Turn flares (physics by location?)
|
||||||
- Angled engines & guns
|
- Angled engines & guns
|
||||||
|
- Fix effect interaction with sprite sections
|
||||||
|
- Clean up starfieldsprite
|
||||||
|
|
||||||
## Misc fixes & Optimizations
|
## Misc fixes & Optimizations
|
||||||
- 🌟 Better errors when content/asset dirs don't exist
|
- 🌟 Better errors when content/asset dirs don't exist
|
||||||
|
|
|
@ -28,8 +28,8 @@ starfield.max_size = 1.8
|
||||||
# Z-axis (parallax) range for starfield stars
|
# Z-axis (parallax) range for starfield stars
|
||||||
starfield.min_dist = 75.0
|
starfield.min_dist = 75.0
|
||||||
starfield.max_dist = 200.0
|
starfield.max_dist = 200.0
|
||||||
# Name of starfield sprite
|
# Path to starfield sprite texture
|
||||||
starfield.sprite = "starfield"
|
starfield.texture = "starfield.png"
|
||||||
|
|
||||||
|
|
||||||
# Zoom level bounds.
|
# Zoom level bounds.
|
||||||
|
|
|
@ -1,17 +1,41 @@
|
||||||
# TODO:
|
# TODO:
|
||||||
# random start frame
|
# random start frame
|
||||||
# repeat once: stay on last frame
|
|
||||||
# blending mode: alpha / half-alpha / additive
|
# blending mode: alpha / half-alpha / additive
|
||||||
|
|
||||||
|
|
||||||
[sprite."starfield"]
|
|
||||||
file = "starfield.png"
|
|
||||||
|
|
||||||
[sprite."star::star"]
|
[sprite."star::star"]
|
||||||
file = "star/B-09.png"
|
file = "star/B-09.png"
|
||||||
|
|
||||||
[sprite."flare::ion"]
|
[sprite."flare::ion"]
|
||||||
file = "flare/1.png"
|
start_at = "rise:top"
|
||||||
|
random_start_frame = false
|
||||||
|
|
||||||
|
section.idle.timing.duration = 5
|
||||||
|
#section.idle.repeat = "reverse"
|
||||||
|
section.idle.frames = ["flare/1.png", "flare/4.png", "flare/5.png"]
|
||||||
|
section.idle.top = "reverse"
|
||||||
|
section.idle.bot = "reverse"
|
||||||
|
# stop: stop on last frame (special)
|
||||||
|
# restart: go to opposite end (same as self:tail)
|
||||||
|
# repeat: reverse and play again
|
||||||
|
# TODO: implement random
|
||||||
|
# spec: "idle:bot", "idle:top", or "idle:random"
|
||||||
|
|
||||||
|
section.rise.timing.duration = 0.15
|
||||||
|
section.rise.top = "stop"
|
||||||
|
section.rise.bot = "run:top"
|
||||||
|
section.rise.frames = [
|
||||||
|
"flare/6.png",
|
||||||
|
"flare/5.png",
|
||||||
|
"flare/4.png",
|
||||||
|
"flare/3.png",
|
||||||
|
"flare/2.png",
|
||||||
|
]
|
||||||
|
|
||||||
|
section.run.timing.duration = 0.01
|
||||||
|
section.run.top = "stop"
|
||||||
|
section.run.bot = "stop"
|
||||||
|
section.run.frames = ["flare/1.png"]
|
||||||
|
|
||||||
|
|
||||||
[sprite."planet::earth"]
|
[sprite."planet::earth"]
|
||||||
file = "planet/earth.png"
|
file = "planet/earth.png"
|
||||||
|
@ -27,8 +51,8 @@ file = "ship/gypsum.png"
|
||||||
|
|
||||||
[sprite."ship::peregrine"]
|
[sprite."ship::peregrine"]
|
||||||
timing.duration = 2
|
timing.duration = 2
|
||||||
repeat = "reverse"
|
top = "reverse"
|
||||||
random_start_frame = true
|
bot = "reverse"
|
||||||
frames = [
|
frames = [
|
||||||
"ship/peregrine/01.png",
|
"ship/peregrine/01.png",
|
||||||
"ship/peregrine/02.png",
|
"ship/peregrine/02.png",
|
||||||
|
@ -70,10 +94,22 @@ file = "ui/landscape/test.png"
|
||||||
[sprite."ui::landscapemask"]
|
[sprite."ui::landscapemask"]
|
||||||
file = "ui/landscape-mask.png"
|
file = "ui/landscape-mask.png"
|
||||||
|
|
||||||
|
[sprite."ui::planet::button"]
|
||||||
|
start_at = "off:top"
|
||||||
|
random_start_frame = false
|
||||||
|
|
||||||
|
section.off.top = "stop"
|
||||||
|
section.off.bot = "stop"
|
||||||
|
section.off.timing.fps = 60
|
||||||
|
section.off.frames = ["ui/planet-button-off.png"]
|
||||||
|
|
||||||
|
section.on.top = "stop"
|
||||||
|
section.on.bot = "stop"
|
||||||
|
section.on.timing.fps = 60
|
||||||
|
section.on.frames = ["ui/planet-button-on.png"]
|
||||||
|
|
||||||
[sprite."particle::blaster"]
|
[sprite."particle::blaster"]
|
||||||
timing.duration = 0.15
|
timing.duration = 0.15
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/blaster/01.png",
|
"particle/blaster/01.png",
|
||||||
"particle/blaster/02.png",
|
"particle/blaster/02.png",
|
||||||
|
@ -84,7 +120,6 @@ frames = [
|
||||||
|
|
||||||
[sprite."particle::explosion::tiny"]
|
[sprite."particle::explosion::tiny"]
|
||||||
timing.fps = 15
|
timing.fps = 15
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/explosion-tiny/01.png",
|
"particle/explosion-tiny/01.png",
|
||||||
"particle/explosion-tiny/02.png",
|
"particle/explosion-tiny/02.png",
|
||||||
|
@ -96,7 +131,6 @@ frames = [
|
||||||
|
|
||||||
[sprite."particle::explosion::small"]
|
[sprite."particle::explosion::small"]
|
||||||
timing.fps = 15
|
timing.fps = 15
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/explosion-small/01.png",
|
"particle/explosion-small/01.png",
|
||||||
"particle/explosion-small/02.png",
|
"particle/explosion-small/02.png",
|
||||||
|
@ -109,7 +143,6 @@ frames = [
|
||||||
|
|
||||||
[sprite."particle::explosion::medium"]
|
[sprite."particle::explosion::medium"]
|
||||||
timing.fps = 15
|
timing.fps = 15
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/explosion-medium/01.png",
|
"particle/explosion-medium/01.png",
|
||||||
"particle/explosion-medium/02.png",
|
"particle/explosion-medium/02.png",
|
||||||
|
@ -124,7 +157,6 @@ frames = [
|
||||||
|
|
||||||
[sprite."particle::explosion::large"]
|
[sprite."particle::explosion::large"]
|
||||||
timing.fps = 15
|
timing.fps = 15
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/explosion-large/01.png",
|
"particle/explosion-large/01.png",
|
||||||
"particle/explosion-large/02.png",
|
"particle/explosion-large/02.png",
|
||||||
|
@ -139,7 +171,6 @@ frames = [
|
||||||
|
|
||||||
[sprite."particle::explosion::huge"]
|
[sprite."particle::explosion::huge"]
|
||||||
timing.fps = 15
|
timing.fps = 15
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/explosion-huge/01.png",
|
"particle/explosion-huge/01.png",
|
||||||
"particle/explosion-huge/02.png",
|
"particle/explosion-huge/02.png",
|
||||||
|
@ -156,9 +187,8 @@ frames = [
|
||||||
|
|
||||||
[sprite."particle::spark::blue"]
|
[sprite."particle::spark::blue"]
|
||||||
timing.duration = 0.3
|
timing.duration = 0.3
|
||||||
#timing.rng = 0.2 # each frame will be independently sped up/slowed by this factor
|
top = "reverse"
|
||||||
#timing.uniform_rng = 0.2 # one factor for all frames
|
bot = "reverse"
|
||||||
repeat = "reverse"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/spark-blue/01.png",
|
"particle/spark-blue/01.png",
|
||||||
"particle/spark-blue/02.png",
|
"particle/spark-blue/02.png",
|
||||||
|
@ -170,7 +200,6 @@ frames = [
|
||||||
[sprite."particle::spark::yellow"]
|
[sprite."particle::spark::yellow"]
|
||||||
timing.duration = 0.3
|
timing.duration = 0.3
|
||||||
timing.rng = 0.2
|
timing.rng = 0.2
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/spark-yellow/01.png",
|
"particle/spark-yellow/01.png",
|
||||||
"particle/spark-yellow/02.png",
|
"particle/spark-yellow/02.png",
|
||||||
|
@ -182,7 +211,6 @@ frames = [
|
||||||
[sprite."particle::spark::red"]
|
[sprite."particle::spark::red"]
|
||||||
timing.duration = 0.3
|
timing.duration = 0.3
|
||||||
timing.rng = 0.2
|
timing.rng = 0.2
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
frames = [
|
||||||
"particle/spark-red/01.png",
|
"particle/spark-red/01.png",
|
||||||
"particle/spark-red/02.png",
|
"particle/spark-red/02.png",
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
use crate::{AnimSectionHandle, Content, SectionEdge, SpriteHandle, StartEdge};
|
||||||
|
|
||||||
|
/// A single frame's state
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AnimationState {
|
||||||
|
/// The index of the texture we're fading from
|
||||||
|
pub texture_a: u32,
|
||||||
|
|
||||||
|
/// The index of the texture we're fading to
|
||||||
|
pub texture_b: u32,
|
||||||
|
|
||||||
|
/// Between 0.0 and 1.0, denoting how far we are between
|
||||||
|
/// texture_a and texture_b
|
||||||
|
/// 0.0 means fully show texture_a;
|
||||||
|
/// 1.0 means fully show texture_b.
|
||||||
|
pub fade: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimationState {
|
||||||
|
/// Convenience method.
|
||||||
|
/// Get texture index as an array
|
||||||
|
pub fn texture_index(&self) -> [u32; 2] {
|
||||||
|
[self.texture_a, self.texture_b]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What direction are we playing our animation in?
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum AnimDirection {
|
||||||
|
/// Top to bottom, with increasing frame indices
|
||||||
|
/// (normal)
|
||||||
|
Up,
|
||||||
|
|
||||||
|
/// Bottom to top, with decreasing frame indices
|
||||||
|
/// (reverse)
|
||||||
|
Down,
|
||||||
|
|
||||||
|
/// Stopped, no animation
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manages a single sprite's animation state.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AnimAutomaton {
|
||||||
|
/// The sprite we're animating
|
||||||
|
sprite: SpriteHandle,
|
||||||
|
|
||||||
|
/// Which animation section we're on
|
||||||
|
/// This MUST be a section from this Automaton's sprite
|
||||||
|
current_section: AnimSectionHandle,
|
||||||
|
|
||||||
|
/// Which frame we're on
|
||||||
|
current_frame: usize,
|
||||||
|
|
||||||
|
/// Where we are between frames.
|
||||||
|
/// Always between zero and one.
|
||||||
|
current_fade: f32,
|
||||||
|
|
||||||
|
/// In what direction are we playing the current section?
|
||||||
|
current_direction: AnimDirection,
|
||||||
|
|
||||||
|
/// The texture we're fading from
|
||||||
|
/// (if we're moving downwards)
|
||||||
|
last_texture: u32,
|
||||||
|
|
||||||
|
/// The texture we're fading to
|
||||||
|
/// (if we're moving downwards)
|
||||||
|
next_texture: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimAutomaton {
|
||||||
|
/// Create a new AnimAutomaton
|
||||||
|
pub fn new(ct: &Content, sprite_handle: SpriteHandle) -> Self {
|
||||||
|
let sprite = ct.get_sprite(sprite_handle);
|
||||||
|
|
||||||
|
let (current_section, texture, current_direction) = match sprite.start_at {
|
||||||
|
StartEdge::Top { section } => (
|
||||||
|
section,
|
||||||
|
*sprite.get_section(section).frames.first().unwrap(),
|
||||||
|
AnimDirection::Down,
|
||||||
|
),
|
||||||
|
StartEdge::Bot { section } => (
|
||||||
|
section,
|
||||||
|
*sprite.get_section(section).frames.last().unwrap(),
|
||||||
|
AnimDirection::Up,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sprite: sprite.handle,
|
||||||
|
current_frame: 0,
|
||||||
|
current_fade: 0.0,
|
||||||
|
|
||||||
|
current_direction,
|
||||||
|
current_section,
|
||||||
|
last_texture: texture,
|
||||||
|
next_texture: texture,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset this animation
|
||||||
|
pub fn reset(&mut self, ct: &Content) {
|
||||||
|
*self = Self::new(ct, self.sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reverse this animation's direction
|
||||||
|
pub fn reverse(&mut self) {
|
||||||
|
match self.current_direction {
|
||||||
|
AnimDirection::Stop => {}
|
||||||
|
AnimDirection::Up => {
|
||||||
|
self.current_direction = AnimDirection::Down;
|
||||||
|
}
|
||||||
|
AnimDirection::Down => {
|
||||||
|
self.current_direction = AnimDirection::Up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Step this animation by `t` seconds
|
||||||
|
pub fn step(&mut self, ct: &Content, t: f32) {
|
||||||
|
let sprite = ct.get_sprite(self.sprite);
|
||||||
|
let current_section = sprite.get_section(self.current_section);
|
||||||
|
|
||||||
|
// Current_fade and current_frame keep track of where we are in the current section.
|
||||||
|
// current_frame indexes this section frames. When it exceeds the number of frames
|
||||||
|
// or falls below zero (when moving in reverse), we switch to the next section.
|
||||||
|
//
|
||||||
|
// current_fade keeps track of our state between frames. It is zero once a frame starts,
|
||||||
|
// and we switch to the next frame when it hits 1.0. If we are stepping foward, it increases,
|
||||||
|
// and if we are stepping backwards, it decreases.
|
||||||
|
|
||||||
|
// If this is zero, this section isn't animated.
|
||||||
|
if current_section.frame_duration == 0.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.current_direction {
|
||||||
|
AnimDirection::Down => self.current_fade += t / current_section.frame_duration,
|
||||||
|
AnimDirection::Up => self.current_fade -= t / current_section.frame_duration,
|
||||||
|
AnimDirection::Stop => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're stepping foward and finished this frame
|
||||||
|
// (implies we're travelling downwards)
|
||||||
|
if self.current_fade > 1.0 {
|
||||||
|
while self.current_fade > 1.0 {
|
||||||
|
self.current_fade -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.current_frame < current_section.frames.len() - 1 {
|
||||||
|
self.current_frame += 1;
|
||||||
|
} else {
|
||||||
|
match current_section.edge_bot {
|
||||||
|
SectionEdge::Stop => {
|
||||||
|
self.current_fade = 0.0;
|
||||||
|
self.current_frame = current_section.frames.len() - 1;
|
||||||
|
self.current_direction = AnimDirection::Stop;
|
||||||
|
}
|
||||||
|
SectionEdge::Top { section } => {
|
||||||
|
self.current_section = section;
|
||||||
|
self.current_frame = 0;
|
||||||
|
}
|
||||||
|
SectionEdge::Bot { section } => {
|
||||||
|
let s = sprite.get_section(section);
|
||||||
|
self.current_section = section;
|
||||||
|
self.current_frame = s.frames.len() - 1;
|
||||||
|
self.reverse();
|
||||||
|
}
|
||||||
|
SectionEdge::Restart => {
|
||||||
|
self.current_frame = 0;
|
||||||
|
}
|
||||||
|
SectionEdge::Reverse => {
|
||||||
|
// Jump to SECOND frame, since we've already shown the
|
||||||
|
// first during the fade transition
|
||||||
|
self.current_frame = current_section.frames.len() - 1;
|
||||||
|
self.reverse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_section = sprite.get_section(self.current_section);
|
||||||
|
self.last_texture = self.next_texture;
|
||||||
|
self.next_texture = current_section.frames[self.current_frame];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're stepping backward and finished this frame
|
||||||
|
// (implies we're travelling upwards)
|
||||||
|
if self.current_fade < 0.0 {
|
||||||
|
while self.current_fade < 0.0 {
|
||||||
|
self.current_fade += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.current_frame > 0 {
|
||||||
|
self.current_frame -= 1;
|
||||||
|
} else {
|
||||||
|
match current_section.edge_top {
|
||||||
|
SectionEdge::Stop => {
|
||||||
|
self.current_fade = 0.0;
|
||||||
|
self.current_frame = 0;
|
||||||
|
self.current_direction = AnimDirection::Stop;
|
||||||
|
}
|
||||||
|
SectionEdge::Top { section } => {
|
||||||
|
self.current_section = section;
|
||||||
|
self.current_frame = 0;
|
||||||
|
self.reverse();
|
||||||
|
}
|
||||||
|
SectionEdge::Bot { section } => {
|
||||||
|
let s = sprite.get_section(section);
|
||||||
|
self.current_section = section;
|
||||||
|
self.current_frame = s.frames.len() - 1;
|
||||||
|
}
|
||||||
|
SectionEdge::Reverse => {
|
||||||
|
self.current_frame = 0;
|
||||||
|
self.reverse();
|
||||||
|
}
|
||||||
|
SectionEdge::Restart => {
|
||||||
|
self.current_frame = current_section.frames.len() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_section = sprite.get_section(self.current_section);
|
||||||
|
self.next_texture = self.last_texture;
|
||||||
|
self.last_texture = current_section.frames[self.current_frame];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current frame of this animation
|
||||||
|
pub fn get_texture_idx(&self) -> AnimationState {
|
||||||
|
return AnimationState {
|
||||||
|
texture_a: self.last_texture,
|
||||||
|
texture_b: self.next_texture,
|
||||||
|
fade: self.current_fade,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,14 +16,6 @@ pub struct SpriteHandle {
|
||||||
pub aspect: f32,
|
pub aspect: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteHandle {
|
|
||||||
/// The index of this sprite in content's sprite array.
|
|
||||||
/// Render uses this to build its buffers.
|
|
||||||
pub fn get_index(&self) -> u32 {
|
|
||||||
self.index as u32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for SpriteHandle {
|
impl Hash for SpriteHandle {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.index.hash(state)
|
self.index.hash(state)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! This subcrate is responsible for loading, parsing, validating game content,
|
//! This subcrate is responsible for loading, parsing, validating game content,
|
||||||
//! which is usually stored in `./content`.
|
//! which is usually stored in `./content`.
|
||||||
|
|
||||||
|
mod animautomaton;
|
||||||
mod handle;
|
mod handle;
|
||||||
mod part;
|
mod part;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -18,6 +19,7 @@ use std::{
|
||||||
use toml;
|
use toml;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
pub use animautomaton::*;
|
||||||
pub use handle::*;
|
pub use handle::*;
|
||||||
pub use part::*;
|
pub use part::*;
|
||||||
|
|
||||||
|
@ -139,8 +141,6 @@ pub struct Content {
|
||||||
/// Map strings to texture names.
|
/// Map strings to texture names.
|
||||||
/// This is only necessary because we need to hard-code a few texture names for UI elements.
|
/// This is only necessary because we need to hard-code a few texture names for UI elements.
|
||||||
sprite_index: HashMap<String, SpriteHandle>,
|
sprite_index: HashMap<String, SpriteHandle>,
|
||||||
/// The texture to use for starfield stars
|
|
||||||
starfield_handle: Option<SpriteHandle>,
|
|
||||||
|
|
||||||
/// Keeps track of which images are in which texture
|
/// Keeps track of which images are in which texture
|
||||||
sprite_atlas: SpriteAtlas,
|
sprite_atlas: SpriteAtlas,
|
||||||
|
@ -204,7 +204,7 @@ impl Content {
|
||||||
let mut content = Self {
|
let mut content = Self {
|
||||||
config: {
|
config: {
|
||||||
if let Some(c) = root.config {
|
if let Some(c) = root.config {
|
||||||
c.build(&asset_root)
|
c.build(&asset_root, &atlas)
|
||||||
.with_context(|| "while parsing config table")?
|
.with_context(|| "while parsing config table")?
|
||||||
} else {
|
} else {
|
||||||
bail!("failed loading content: no config table specified")
|
bail!("failed loading content: no config table specified")
|
||||||
|
@ -220,7 +220,6 @@ impl Content {
|
||||||
factions: Vec::new(),
|
factions: Vec::new(),
|
||||||
effects: Vec::new(),
|
effects: Vec::new(),
|
||||||
sprite_index: HashMap::new(),
|
sprite_index: HashMap::new(),
|
||||||
starfield_handle: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: enforce sprite and image limits
|
// TODO: enforce sprite and image limits
|
||||||
|
@ -278,14 +277,6 @@ impl Content {
|
||||||
(0..self.systems.len()).map(|x| SystemHandle { index: x })
|
(0..self.systems.len()).map(|x| SystemHandle { index: x })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the handle for the starfield sprite
|
|
||||||
pub fn get_starfield_handle(&self) -> SpriteHandle {
|
|
||||||
match self.starfield_handle {
|
|
||||||
Some(h) => h,
|
|
||||||
None => unreachable!("Starfield sprite hasn't been loaded yet!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle from a sprite name
|
/// Get a handle from a sprite name
|
||||||
pub fn get_sprite_handle(&self, name: &str) -> SpriteHandle {
|
pub fn get_sprite_handle(&self, name: &str) -> SpriteHandle {
|
||||||
return match self.sprite_index.get(name) {
|
return match self.sprite_index.get(name) {
|
||||||
|
@ -306,9 +297,9 @@ impl Content {
|
||||||
return &self.sprite_atlas.atlas_list;
|
return &self.sprite_atlas.atlas_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a sprite from a path
|
/// Get a texture by its index
|
||||||
pub fn get_image(&self, p: &Path) -> &SpriteAtlasImage {
|
pub fn get_image(&self, idx: u32) -> &SpriteAtlasImage {
|
||||||
self.sprite_atlas.index.get(p).unwrap()
|
&self.sprite_atlas.index[idx as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an outfit from a handle
|
/// Get an outfit from a handle
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
use galactica_packer::SpriteAtlas;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
@ -19,7 +20,7 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
// TODO: clean up build trait
|
// TODO: clean up build trait
|
||||||
pub fn build(self, asset_root: &Path) -> Result<super::Config> {
|
pub fn build(self, asset_root: &Path, atlas: &SpriteAtlas) -> Result<super::Config> {
|
||||||
for i in &self.fonts.files {
|
for i in &self.fonts.files {
|
||||||
if !asset_root.join(i).exists() {
|
if !asset_root.join(i).exists() {
|
||||||
bail!("font file `{}` doesn't exist", i.display());
|
bail!("font file `{}` doesn't exist", i.display());
|
||||||
|
@ -35,6 +36,16 @@ pub(crate) mod syntax {
|
||||||
// An insufficient limit will result in some tiles not being drawn
|
// An insufficient limit will result in some tiles not being drawn
|
||||||
let starfield_instance_limit = 12 * starfield_count as u64;
|
let starfield_instance_limit = 12 * starfield_count as u64;
|
||||||
|
|
||||||
|
let starfield_texture = match atlas.path_map.get(&self.starfield.texture) {
|
||||||
|
Some(s) => *s,
|
||||||
|
None => {
|
||||||
|
bail!(
|
||||||
|
"starfield texture `{}` doesn't exist",
|
||||||
|
self.starfield.texture.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return Ok(super::Config {
|
return Ok(super::Config {
|
||||||
sprite_root: asset_root.join(self.sprite_root),
|
sprite_root: asset_root.join(self.sprite_root),
|
||||||
font_files: self
|
font_files: self
|
||||||
|
@ -53,7 +64,7 @@ pub(crate) mod syntax {
|
||||||
starfield_min_dist: self.starfield.min_dist,
|
starfield_min_dist: self.starfield.min_dist,
|
||||||
starfield_max_size: self.starfield.max_size,
|
starfield_max_size: self.starfield.max_size,
|
||||||
starfield_min_size: self.starfield.min_size,
|
starfield_min_size: self.starfield.min_size,
|
||||||
starfield_sprite: self.starfield.sprite,
|
starfield_texture,
|
||||||
starfield_count,
|
starfield_count,
|
||||||
starfield_density,
|
starfield_density,
|
||||||
starfield_size,
|
starfield_size,
|
||||||
|
@ -80,7 +91,7 @@ pub(crate) mod syntax {
|
||||||
pub max_size: f32,
|
pub max_size: f32,
|
||||||
pub min_dist: f32,
|
pub min_dist: f32,
|
||||||
pub max_dist: f32,
|
pub max_dist: f32,
|
||||||
pub sprite: String,
|
pub texture: PathBuf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,8 +128,8 @@ pub struct Config {
|
||||||
/// Maximum z-distance of starfield star, in game units
|
/// Maximum z-distance of starfield star, in game units
|
||||||
pub starfield_max_dist: f32,
|
pub starfield_max_dist: f32,
|
||||||
|
|
||||||
/// Name of starfield sprite
|
/// Index of starfield texture
|
||||||
pub starfield_sprite: String,
|
pub starfield_texture: u32,
|
||||||
|
|
||||||
/// Size of a square starfield tile, in game units.
|
/// Size of a square starfield tile, in game units.
|
||||||
/// A tile of size STARFIELD_Z_MAX * screen-size-in-game-units
|
/// A tile of size STARFIELD_Z_MAX * screen-size-in-game-units
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub(crate) mod syntax {
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{Content, ContentBuildContext, EffectHandle};
|
use crate::{Content, ContentBuildContext, EffectHandle, StartEdge};
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
// These are never seen by code outside this crate.
|
// These are never seen by code outside this crate.
|
||||||
|
|
||||||
|
@ -56,8 +56,13 @@ pub(crate) mod syntax {
|
||||||
TextOrFloat::Float(f) => f,
|
TextOrFloat::Float(f) => f,
|
||||||
TextOrFloat::Text(s) => {
|
TextOrFloat::Text(s) => {
|
||||||
if s == "inherit" {
|
if s == "inherit" {
|
||||||
|
// Match lifetime of first section of sprite
|
||||||
let sprite = content.get_sprite(sprite);
|
let sprite = content.get_sprite(sprite);
|
||||||
sprite.frame_duration * sprite.frames.len() as f32
|
let sec = match sprite.start_at {
|
||||||
|
StartEdge::Top { section } => sprite.get_section(section),
|
||||||
|
StartEdge::Bot { section } => sprite.get_section(section),
|
||||||
|
};
|
||||||
|
sec.frame_duration * sec.frames.len() as f32
|
||||||
} else {
|
} else {
|
||||||
bail!("bad effect lifetime, must be float or \"inherit\"",)
|
bail!("bad effect lifetime, must be float or \"inherit\"",)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,5 +18,5 @@ pub use ship::{
|
||||||
CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship,
|
CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship,
|
||||||
ShipCollapse,
|
ShipCollapse,
|
||||||
};
|
};
|
||||||
pub use sprite::{RepeatMode, Sprite};
|
pub use sprite::*;
|
||||||
pub use system::{System, SystemObject};
|
pub use system::{System, SystemObject};
|
||||||
|
|
|
@ -1,44 +1,36 @@
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use image::io::Reader;
|
use std::collections::HashMap;
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
|
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
|
use crate::{Content, ContentBuildContext};
|
||||||
|
use anyhow::{anyhow, bail, Context, Ok, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::PathBuf;
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use super::RepeatMode;
|
use super::AnimSectionHandle;
|
||||||
|
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
// These are never seen by code outside this crate.
|
// These are never seen by code outside this crate.
|
||||||
|
|
||||||
|
/// Convenience variants of sprite definitions
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Sprite {
|
pub enum Sprite {
|
||||||
Static(StaticSprite),
|
Static(StaticSprite),
|
||||||
Frames(FrameSprite),
|
OneSection(SpriteSection),
|
||||||
}
|
Complete(CompleteSprite),
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct StaticSprite {
|
|
||||||
pub file: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct FrameSprite {
|
|
||||||
pub frames: Vec<PathBuf>,
|
|
||||||
pub timing: Timing,
|
|
||||||
pub repeat: RepeatMode,
|
|
||||||
pub random_start_frame: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Two ways to specify animation length
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub enum TimingVariant {
|
pub enum TimingVariant {
|
||||||
|
/// The duration of this whole section
|
||||||
#[serde(rename = "duration")]
|
#[serde(rename = "duration")]
|
||||||
Duration(f32),
|
Duration(f32),
|
||||||
|
|
||||||
|
/// The fps of this section
|
||||||
#[serde(rename = "fps")]
|
#[serde(rename = "fps")]
|
||||||
Fps(f32),
|
Fps(f32),
|
||||||
}
|
}
|
||||||
|
@ -47,36 +39,208 @@ pub(crate) mod syntax {
|
||||||
pub struct Timing {
|
pub struct Timing {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub variant: TimingVariant,
|
pub variant: TimingVariant,
|
||||||
//pub uniform_rng: Option<f32>,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// How to replay a texture's animation
|
/// An unanimated sprite
|
||||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub enum RepeatMode {
|
pub struct StaticSprite {
|
||||||
/// Play this animation once, and stop at the last frame
|
pub file: PathBuf,
|
||||||
#[serde(rename = "once")]
|
}
|
||||||
Once,
|
|
||||||
|
|
||||||
/// After the first frame, jump to the last frame
|
/// The proper, full sprite definition
|
||||||
#[serde(rename = "repeat")]
|
#[derive(Debug, Deserialize)]
|
||||||
Repeat,
|
pub struct CompleteSprite {
|
||||||
|
pub section: HashMap<String, SpriteSection>,
|
||||||
|
pub start_at: SectionEdge,
|
||||||
|
}
|
||||||
|
|
||||||
/// Play this animation in reverse after the last frame
|
/// A single animation section
|
||||||
#[serde(rename = "reverse")]
|
#[derive(Debug, Deserialize)]
|
||||||
Reverse,
|
pub struct SpriteSection {
|
||||||
}
|
pub frames: Vec<PathBuf>,
|
||||||
|
pub timing: Timing,
|
||||||
|
pub top: Option<SectionEdge>,
|
||||||
|
pub bot: Option<SectionEdge>,
|
||||||
|
}
|
||||||
|
|
||||||
impl RepeatMode {
|
impl SpriteSection {
|
||||||
/// Represent this repeatmode as an integer
|
pub fn add_to(
|
||||||
/// Used to pass this enum into shaders
|
&self,
|
||||||
pub fn as_int(&self) -> u32 {
|
_build_context: &mut ContentBuildContext,
|
||||||
match self {
|
content: &mut Content,
|
||||||
Self::Repeat => 0,
|
|
||||||
Self::Once => 1,
|
// An index of all sections in this sprite, used to resolve
|
||||||
Self::Reverse => 2,
|
// top and bot edges.
|
||||||
|
all_sections: &HashMap<String, AnimSectionHandle>,
|
||||||
|
) -> Result<((u32, u32), super::SpriteSection)> {
|
||||||
|
// Make sure all frames have the same size and add them
|
||||||
|
// to the frame vector
|
||||||
|
let mut dim = None;
|
||||||
|
let mut frames = Vec::new();
|
||||||
|
for f in &self.frames {
|
||||||
|
let idx = match content.sprite_atlas.path_map.get(f) {
|
||||||
|
Some(s) => *s,
|
||||||
|
None => {
|
||||||
|
bail!("error: file `{}` isn't in the sprite atlas", f.display());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let img = &content.sprite_atlas.index[idx as usize];
|
||||||
|
|
||||||
|
match dim {
|
||||||
|
None => dim = Some(img.true_size),
|
||||||
|
Some(e) => {
|
||||||
|
if img.true_size != e {
|
||||||
|
bail!("failed to load section frames because frames have different sizes.",)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.push(img.idx);
|
||||||
|
}
|
||||||
|
let dim = dim.unwrap();
|
||||||
|
|
||||||
|
let frame_duration = match self.timing.variant {
|
||||||
|
TimingVariant::Duration(d) => d / self.frames.len() as f32,
|
||||||
|
TimingVariant::Fps(f) => 1.0 / f,
|
||||||
|
};
|
||||||
|
|
||||||
|
if frame_duration <= 0.0 {
|
||||||
|
bail!("frame duration must be positive (and therefore nonzero).")
|
||||||
|
}
|
||||||
|
|
||||||
|
let edge_top = match &self.top {
|
||||||
|
Some(x) => x.resolve_as_edge(all_sections)?,
|
||||||
|
None => super::SectionEdge::Stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
let edge_bot = match &self.bot {
|
||||||
|
Some(x) => x.resolve_as_edge(all_sections)?,
|
||||||
|
None => super::SectionEdge::Stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok((
|
||||||
|
dim,
|
||||||
|
super::SpriteSection {
|
||||||
|
frames,
|
||||||
|
frame_duration,
|
||||||
|
edge_top,
|
||||||
|
edge_bot,
|
||||||
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A link between two animation sections
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct SectionEdge {
|
||||||
|
pub val: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SectionEdge {
|
||||||
|
pub fn resolve_as_start(
|
||||||
|
&self,
|
||||||
|
all_sections: &HashMap<String, AnimSectionHandle>,
|
||||||
|
) -> Result<super::StartEdge> {
|
||||||
|
let e = self
|
||||||
|
.resolve_as_edge(all_sections)
|
||||||
|
.with_context(|| format!("while resolving start edge"))?;
|
||||||
|
match e {
|
||||||
|
super::SectionEdge::Bot { section } => Ok(super::StartEdge::Bot { section }),
|
||||||
|
super::SectionEdge::Top { section } => Ok(super::StartEdge::Top { section }),
|
||||||
|
_ => {
|
||||||
|
bail!("bad section start specification `{}`", self.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_as_edge(
|
||||||
|
&self,
|
||||||
|
all_sections: &HashMap<String, AnimSectionHandle>,
|
||||||
|
) -> Result<super::SectionEdge> {
|
||||||
|
if self.val == "stop" {
|
||||||
|
return Ok(super::SectionEdge::Stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.val == "reverse" {
|
||||||
|
return Ok(super::SectionEdge::Reverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.val == "restart" {
|
||||||
|
return Ok(super::SectionEdge::Restart);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (s, p) = match self.val.split_once(":") {
|
||||||
|
Some(x) => x,
|
||||||
|
None => {
|
||||||
|
bail!("bad section edge specification `{}`", self.val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let section = match all_sections.get(s) {
|
||||||
|
Some(s) => *s,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow!("bad section edge specification `{}`", self.val))
|
||||||
|
.with_context(|| format!("section `{}` doesn't exist", s));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match p {
|
||||||
|
"top" => Ok(super::SectionEdge::Top { section }),
|
||||||
|
"bot" => Ok(super::SectionEdge::Bot { section }),
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow!("bad section edge specification `{}`", self.val))
|
||||||
|
.with_context(|| format!("invalid target `{}`", p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should be pub crate
|
||||||
|
/// A handle for an animation section inside a sprite
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct AnimSectionHandle(pub(crate) usize);
|
||||||
|
|
||||||
|
/// An edge between two animation sections
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SectionEdge {
|
||||||
|
/// Stop at the last frame of this section
|
||||||
|
Stop,
|
||||||
|
|
||||||
|
/// Play the given section from the bottm
|
||||||
|
Bot {
|
||||||
|
/// The section to play
|
||||||
|
section: AnimSectionHandle,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Play the given section from the top
|
||||||
|
Top {
|
||||||
|
/// The section to play
|
||||||
|
section: AnimSectionHandle,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Replay this section in the opposite direction
|
||||||
|
Reverse,
|
||||||
|
|
||||||
|
/// Restart this section from the opposite end
|
||||||
|
Restart,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where to start an animation
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum StartEdge {
|
||||||
|
/// Play the given section from the bottm
|
||||||
|
Bot {
|
||||||
|
/// The section to play
|
||||||
|
section: AnimSectionHandle,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Play the given section from the top
|
||||||
|
Top {
|
||||||
|
/// The section to play
|
||||||
|
section: AnimSectionHandle,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a sprite that may be used in the game.
|
/// Represents a sprite that may be used in the game.
|
||||||
|
@ -88,25 +252,52 @@ pub struct Sprite {
|
||||||
/// This sprite's handle
|
/// This sprite's handle
|
||||||
pub handle: SpriteHandle,
|
pub handle: SpriteHandle,
|
||||||
|
|
||||||
/// The file names of frames of this sprite.
|
/// Where this sprite starts playing
|
||||||
/// unanimated sprites have one frame.
|
pub start_at: StartEdge,
|
||||||
pub frames: Vec<PathBuf>,
|
|
||||||
|
|
||||||
/// The speed of this sprite's animation.
|
/// This sprite's animation sections
|
||||||
/// This is zero for unanimate sprites.
|
sections: Vec<SpriteSection>,
|
||||||
pub frame_duration: f32,
|
|
||||||
|
|
||||||
/// All frames will be sped up/slowed by this factor.
|
|
||||||
//pub frame_uniform_rng: f32,
|
|
||||||
|
|
||||||
/// How to replay this sprite's animation
|
|
||||||
pub repeat: RepeatMode,
|
|
||||||
|
|
||||||
/// Aspect ratio of this sprite (width / height)
|
/// Aspect ratio of this sprite (width / height)
|
||||||
pub aspect: f32,
|
pub aspect: f32,
|
||||||
|
}
|
||||||
|
|
||||||
/// If true, start on a random frame of this sprite.
|
impl Sprite {
|
||||||
pub random_start_frame: bool,
|
/// Get an animation section from a handle
|
||||||
|
pub fn get_section(&self, section: AnimSectionHandle) -> &SpriteSection {
|
||||||
|
&self.sections[section.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this sprite's first frame
|
||||||
|
pub fn get_first_frame(&self) -> u32 {
|
||||||
|
match self.start_at {
|
||||||
|
StartEdge::Bot { section } => *self.get_section(section).frames.last().unwrap(),
|
||||||
|
StartEdge::Top { section } => *self.get_section(section).frames.first().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate this sprite's sections
|
||||||
|
pub fn iter_sections(&self) -> impl Iterator<Item = &SpriteSection> {
|
||||||
|
self.sections.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A part of a sprite's animation
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SpriteSection {
|
||||||
|
/// The texture index of each frame in this animation section.
|
||||||
|
/// unanimated sections have one frame.
|
||||||
|
pub frames: Vec<u32>,
|
||||||
|
|
||||||
|
/// The speed of this sprite's animation.
|
||||||
|
/// This must always be positive (and therefore, nonzero)
|
||||||
|
pub frame_duration: f32,
|
||||||
|
|
||||||
|
/// What to do when we reach the top of this section
|
||||||
|
pub edge_top: SectionEdge,
|
||||||
|
|
||||||
|
/// What to do when we reach the bottom of this section
|
||||||
|
pub edge_bot: SectionEdge,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Sprite {
|
impl crate::Build for Sprite {
|
||||||
|
@ -114,123 +305,141 @@ impl crate::Build for Sprite {
|
||||||
|
|
||||||
fn build(
|
fn build(
|
||||||
sprites: Self::InputSyntaxType,
|
sprites: Self::InputSyntaxType,
|
||||||
_build_context: &mut ContentBuildContext,
|
build_context: &mut ContentBuildContext,
|
||||||
content: &mut Content,
|
content: &mut Content,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (sprite_name, t) in sprites {
|
for (sprite_name, t) in sprites {
|
||||||
match t {
|
match t {
|
||||||
syntax::Sprite::Static(t) => {
|
syntax::Sprite::Static(t) => {
|
||||||
let file = content.config.sprite_root.join(&t.file);
|
let idx = match content.sprite_atlas.path_map.get(&t.file) {
|
||||||
let reader = Reader::open(&file).with_context(|| {
|
Some(s) => *s,
|
||||||
format!(
|
None => {
|
||||||
"Failed to read file `{}` in sprite `{}`",
|
return Err(
|
||||||
file.display(),
|
anyhow!("error while processing sprite `{}`", sprite_name,),
|
||||||
sprite_name,
|
)
|
||||||
)
|
.with_context(|| {
|
||||||
})?;
|
format!(
|
||||||
let dim = reader.into_dimensions().with_context(|| {
|
"file `{}` isn't in the sprite atlas, cannot proceed",
|
||||||
format!(
|
t.file.display()
|
||||||
"Failed to get dimensions of file `{}` in sprite `{}`",
|
)
|
||||||
file.display(),
|
});
|
||||||
sprite_name,
|
}
|
||||||
)
|
};
|
||||||
})?;
|
let img = &content.sprite_atlas.index[idx as usize];
|
||||||
|
let aspect = img.w / img.h;
|
||||||
|
|
||||||
let h = SpriteHandle {
|
let h = SpriteHandle {
|
||||||
index: content.sprites.len(),
|
index: content.sprites.len(),
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect,
|
||||||
};
|
};
|
||||||
|
|
||||||
if sprite_name == content.config.starfield_sprite {
|
|
||||||
if content.starfield_handle.is_none() {
|
|
||||||
content.starfield_handle = Some(h)
|
|
||||||
} else {
|
|
||||||
// This can't happen, since this is a hashmap.
|
|
||||||
unreachable!("Found two starfield sprites! Something is very wrong.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content.sprite_index.insert(sprite_name.clone(), h);
|
content.sprite_index.insert(sprite_name.clone(), h);
|
||||||
|
|
||||||
content.sprites.push(Self {
|
content.sprites.push(Self {
|
||||||
name: sprite_name,
|
name: sprite_name,
|
||||||
frames: vec![t.file],
|
start_at: StartEdge::Top {
|
||||||
frame_duration: 0.0,
|
section: AnimSectionHandle(0),
|
||||||
//frame_uniform_rng: 0.0,
|
},
|
||||||
|
sections: vec![SpriteSection {
|
||||||
|
frames: vec![img.idx],
|
||||||
|
// We implement unanimated sprites with a very fast framerate
|
||||||
|
// and STOP endpoints.
|
||||||
|
frame_duration: 0.01,
|
||||||
|
edge_top: SectionEdge::Stop,
|
||||||
|
edge_bot: SectionEdge::Stop,
|
||||||
|
}],
|
||||||
handle: h,
|
handle: h,
|
||||||
repeat: RepeatMode::Once,
|
aspect,
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
|
||||||
random_start_frame: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
syntax::Sprite::Frames(t) => {
|
syntax::Sprite::OneSection(s) => {
|
||||||
|
let mut section_names: HashMap<String, _> = HashMap::new();
|
||||||
|
// Name the one section in this sprite "anim"
|
||||||
|
section_names.insert("anim".to_owned(), AnimSectionHandle(0));
|
||||||
|
|
||||||
|
let (dim, section) = s
|
||||||
|
.add_to(build_context, content, §ion_names)
|
||||||
|
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
||||||
|
let aspect = dim.0 as f32 / dim.1 as f32;
|
||||||
|
let h = SpriteHandle {
|
||||||
|
index: content.sprites.len(),
|
||||||
|
aspect,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sections = Vec::new();
|
||||||
|
sections.push(section);
|
||||||
|
|
||||||
|
content.sprite_index.insert(sprite_name.clone(), h);
|
||||||
|
content.sprites.push(Self {
|
||||||
|
name: sprite_name,
|
||||||
|
sections,
|
||||||
|
start_at: StartEdge::Bot {
|
||||||
|
section: AnimSectionHandle(0),
|
||||||
|
},
|
||||||
|
handle: h,
|
||||||
|
aspect,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
syntax::Sprite::Complete(s) => {
|
||||||
|
let mut idx = 0;
|
||||||
|
let mut section_names = HashMap::new();
|
||||||
|
for (name, _) in &s.section {
|
||||||
|
section_names.insert(name.to_owned(), AnimSectionHandle(idx));
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_at = s
|
||||||
|
.start_at
|
||||||
|
.resolve_as_start(§ion_names)
|
||||||
|
.with_context(|| format!("while loading sprite `{}`", sprite_name))?;
|
||||||
|
|
||||||
|
let mut sections = Vec::with_capacity(idx);
|
||||||
let mut dim = None;
|
let mut dim = None;
|
||||||
for f in &t.frames {
|
|
||||||
let file = content.config.sprite_root.join(f);
|
// Make sure we add sections in order
|
||||||
let reader = Reader::open(&file).with_context(|| {
|
let mut names = section_names.iter().collect::<Vec<_>>();
|
||||||
format!(
|
names.sort_by(|a, b| (a.1).0.cmp(&(b.1).0));
|
||||||
"Failed to read file `{}` in sprite `{}`",
|
|
||||||
file.display(),
|
for (k, _) in names {
|
||||||
|
let v = s.section.get(k).unwrap();
|
||||||
|
let (d, s) = v
|
||||||
|
.add_to(build_context, content, §ion_names)
|
||||||
|
.with_context(|| format!("while parsing sprite `{}`", sprite_name))
|
||||||
|
.with_context(|| format!("while parsing section `{}`", k))?;
|
||||||
|
|
||||||
|
// Make sure all dimensions are the same
|
||||||
|
if dim.is_none() {
|
||||||
|
dim = Some(d);
|
||||||
|
} else if dim.unwrap() != d {
|
||||||
|
bail!(
|
||||||
|
"could not load sprite `{}`, image sizes in section `{}` are different",
|
||||||
sprite_name,
|
sprite_name,
|
||||||
)
|
k
|
||||||
})?;
|
);
|
||||||
let d = reader.into_dimensions().with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Failed to get dimensions of file `{}` in sprite `{}`",
|
|
||||||
file.display(),
|
|
||||||
sprite_name,
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
match dim {
|
|
||||||
None => dim = Some(d),
|
|
||||||
Some(e) => {
|
|
||||||
if d != e {
|
|
||||||
bail!(
|
|
||||||
"Failed to load frames of sprite `{}` because frames have different sizes.",
|
|
||||||
sprite_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sections.push(s);
|
||||||
}
|
}
|
||||||
let dim = dim.unwrap();
|
let dim = dim.unwrap();
|
||||||
|
let aspect = dim.0 as f32 / dim.1 as f32;
|
||||||
|
|
||||||
let h = SpriteHandle {
|
let h = SpriteHandle {
|
||||||
index: content.sprites.len(),
|
index: content.sprites.len(),
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect,
|
||||||
};
|
|
||||||
|
|
||||||
if sprite_name == content.config.starfield_sprite {
|
|
||||||
unreachable!("Starfield texture may not be animated")
|
|
||||||
}
|
|
||||||
|
|
||||||
let frame_duration = match t.timing.variant {
|
|
||||||
syntax::TimingVariant::Duration(d) => d / t.frames.len() as f32,
|
|
||||||
syntax::TimingVariant::Fps(f) => 1.0 / f,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
content.sprite_index.insert(sprite_name.clone(), h);
|
content.sprite_index.insert(sprite_name.clone(), h);
|
||||||
content.sprites.push(Self {
|
content.sprites.push(Self {
|
||||||
name: sprite_name,
|
name: sprite_name,
|
||||||
frames: t.frames,
|
sections,
|
||||||
frame_duration,
|
start_at,
|
||||||
//frame_uniform_rng: t.timing.uniform_rng.unwrap_or(0.0),
|
|
||||||
handle: h,
|
handle: h,
|
||||||
repeat: t.repeat,
|
aspect,
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
|
||||||
random_start_frame: t.random_start_frame.unwrap_or(false),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.starfield_handle.is_none() {
|
|
||||||
bail!(
|
|
||||||
"Could not find a starfield texture (name: `{}`)",
|
|
||||||
content.config.starfield_sprite
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,12 +48,14 @@ impl<'a> Game {
|
||||||
);
|
);
|
||||||
|
|
||||||
let s = self.state.systemsim.get_ship_mut(&player).unwrap();
|
let s = self.state.systemsim.get_ship_mut(&player).unwrap();
|
||||||
s.data
|
s.add_outfits(
|
||||||
.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 0 }));
|
&self.ct,
|
||||||
s.data
|
[
|
||||||
.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 1 }));
|
OutfitHandle { index: 0 },
|
||||||
s.data
|
OutfitHandle { index: 1 },
|
||||||
.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 2 }));
|
OutfitHandle { index: 2 },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
@ -70,9 +72,14 @@ impl<'a> Game {
|
||||||
);
|
);
|
||||||
|
|
||||||
let s = systemsim.get_ship_mut(&a).unwrap();
|
let s = systemsim.get_ship_mut(&a).unwrap();
|
||||||
s.data.add_outfit(ct.get_outfit(OutfitHandle { index: 0 }));
|
s.add_outfits(
|
||||||
s.data.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
|
&ct,
|
||||||
s.data.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
|
[
|
||||||
|
OutfitHandle { index: 0 },
|
||||||
|
OutfitHandle { index: 1 },
|
||||||
|
OutfitHandle { index: 2 },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
let a = systemsim.add_ship(
|
let a = systemsim.add_ship(
|
||||||
&ct,
|
&ct,
|
||||||
|
@ -83,9 +90,14 @@ impl<'a> Game {
|
||||||
);
|
);
|
||||||
|
|
||||||
let s = systemsim.get_ship_mut(&a).unwrap();
|
let s = systemsim.get_ship_mut(&a).unwrap();
|
||||||
s.data.add_outfit(ct.get_outfit(OutfitHandle { index: 0 }));
|
s.add_outfits(
|
||||||
s.data.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
|
&ct,
|
||||||
s.data.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
|
[
|
||||||
|
OutfitHandle { index: 0 },
|
||||||
|
OutfitHandle { index: 1 },
|
||||||
|
OutfitHandle { index: 2 },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
let state = GameState {
|
let state = GameState {
|
||||||
systemsim,
|
systemsim,
|
||||||
|
|
|
@ -84,7 +84,7 @@ fn main() -> Result<()> {
|
||||||
.systemsim
|
.systemsim
|
||||||
.get_ship(&PhysSimShipHandle(player.ship.unwrap()));
|
.get_ship(&PhysSimShipHandle(player.ship.unwrap()));
|
||||||
if let Some(o) = o {
|
if let Some(o) = o {
|
||||||
match o.data.get_state() {
|
match o.get_data().get_state() {
|
||||||
ShipState::Landing { .. }
|
ShipState::Landing { .. }
|
||||||
| ShipState::UnLanding { .. }
|
| ShipState::UnLanding { .. }
|
||||||
| ShipState::Collapsing { .. }
|
| ShipState::Collapsing { .. }
|
||||||
|
@ -137,13 +137,16 @@ fn main() -> Result<()> {
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
player.process_key(state, key);
|
player.input.process_key(state, key);
|
||||||
|
}
|
||||||
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
|
player.input.process_mouse(position);
|
||||||
}
|
}
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
player.process_click(state, button);
|
player.input.process_click(state, button);
|
||||||
}
|
}
|
||||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||||
player.process_scroll(delta, phase);
|
player.input.process_scroll(delta, phase);
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(_) => {
|
WindowEvent::Resized(_) => {
|
||||||
gpu.resize(&content);
|
gpu.resize(&content);
|
||||||
|
|
|
@ -205,16 +205,19 @@ impl AtlasSet {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.index.index.insert(
|
self.index
|
||||||
p.to_path_buf(),
|
.path_map
|
||||||
SpriteAtlasImage {
|
.insert(p.to_path_buf(), self.index.index.len() as u32);
|
||||||
atlas: atlas_idx as u32,
|
|
||||||
x: (x + self.image_margin) as f32 / self.texture_width as f32,
|
self.index.index.push(SpriteAtlasImage {
|
||||||
y: (y + self.image_margin) as f32 / self.texture_height as f32,
|
true_size: image_dim,
|
||||||
w: image_dim.0 as f32 / self.texture_width as f32,
|
idx: self.index.index.len() as u32,
|
||||||
h: image_dim.1 as f32 / self.texture_height as f32,
|
atlas: atlas_idx as u32,
|
||||||
},
|
x: (x + self.image_margin) as f32 / self.texture_width as f32,
|
||||||
);
|
y: (y + self.image_margin) as f32 / self.texture_height as f32,
|
||||||
|
w: image_dim.0 as f32 / self.texture_width as f32,
|
||||||
|
h: image_dim.1 as f32 / self.texture_height as f32,
|
||||||
|
});
|
||||||
|
|
||||||
return Ok(atlas_idx);
|
return Ok(atlas_idx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,12 @@ pub struct SpriteAtlasImage {
|
||||||
/// This is an index in SpriteAtlas.atlas_list
|
/// This is an index in SpriteAtlas.atlas_list
|
||||||
pub atlas: u32,
|
pub atlas: u32,
|
||||||
|
|
||||||
|
/// A globally unique, consecutively numbered index for this sprite
|
||||||
|
pub idx: u32,
|
||||||
|
|
||||||
|
/// The size of this image, in pixels
|
||||||
|
pub true_size: (u32, u32),
|
||||||
|
|
||||||
/// x-position of this image
|
/// x-position of this image
|
||||||
/// (between 0 and 1, using wgpu texture coordinates)
|
/// (between 0 and 1, using wgpu texture coordinates)
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
|
@ -35,7 +41,10 @@ pub struct SpriteAtlasImage {
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct SpriteAtlas {
|
pub struct SpriteAtlas {
|
||||||
/// The images in this atlas
|
/// The images in this atlas
|
||||||
pub index: HashMap<PathBuf, SpriteAtlasImage>,
|
pub index: Vec<SpriteAtlasImage>,
|
||||||
|
|
||||||
|
/// Map paths to image indices
|
||||||
|
pub path_map: HashMap<PathBuf, u32>,
|
||||||
|
|
||||||
/// The file names of the atlas textures we've generated
|
/// The file names of the atlas textures we've generated
|
||||||
pub atlas_list: Vec<String>,
|
pub atlas_list: Vec<String>,
|
||||||
|
@ -45,7 +54,8 @@ impl SpriteAtlas {
|
||||||
/// Make an empty [`SpriteAtlasIndex`]
|
/// Make an empty [`SpriteAtlasIndex`]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
index: HashMap::new(),
|
path_map: HashMap::new(),
|
||||||
|
index: Vec::new(),
|
||||||
atlas_list: Vec::new(),
|
atlas_list: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
use winit::{
|
||||||
|
dpi::PhysicalPosition,
|
||||||
|
event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct InputStatus {
|
pub struct InputStatus {
|
||||||
// Parameters
|
// Parameters
|
||||||
scroll_speed: f32,
|
scroll_speed: f32,
|
||||||
|
|
||||||
|
mouse_position: PhysicalPosition<f32>,
|
||||||
|
|
||||||
// Continuous keys
|
// Continuous keys
|
||||||
key_left: bool,
|
key_left: bool,
|
||||||
key_right: bool,
|
key_right: bool,
|
||||||
key_thrust: bool,
|
key_thrust: bool,
|
||||||
key_guns: bool,
|
key_guns: bool,
|
||||||
|
key_leftclick: bool,
|
||||||
|
|
||||||
// One-shot keys (audomatically released at the end of each frame)
|
// One-shot keys (audomatically released at the end of each frame)
|
||||||
key_land: bool,
|
key_land: bool,
|
||||||
|
@ -23,6 +29,8 @@ impl InputStatus {
|
||||||
key_thrust: false,
|
key_thrust: false,
|
||||||
key_guns: false,
|
key_guns: false,
|
||||||
key_land: false,
|
key_land: false,
|
||||||
|
key_leftclick: false,
|
||||||
|
mouse_position: PhysicalPosition { x: 0.0, y: 0.0 },
|
||||||
v_scroll: 0.0,
|
v_scroll: 0.0,
|
||||||
scroll_speed: 10.0,
|
scroll_speed: 10.0,
|
||||||
}
|
}
|
||||||
|
@ -65,9 +73,17 @@ impl InputStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_mouse(&mut self, position: &PhysicalPosition<f64>) {
|
||||||
|
self.mouse_position = PhysicalPosition {
|
||||||
|
x: position.x as f32,
|
||||||
|
y: position.y as f32,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_click(&mut self, state: &ElementState, key: &MouseButton) {
|
pub fn process_click(&mut self, state: &ElementState, key: &MouseButton) {
|
||||||
let _down = state == &ElementState::Pressed;
|
let down = state == &ElementState::Pressed;
|
||||||
match key {
|
match key {
|
||||||
|
MouseButton::Left => self.key_leftclick = down,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use galactica_content::{Content, SystemHandle, SystemObjectHandle};
|
use galactica_content::{Content, SystemHandle, SystemObjectHandle};
|
||||||
use rapier2d::geometry::ColliderHandle;
|
use rapier2d::geometry::ColliderHandle;
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
|
||||||
|
|
||||||
use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus};
|
use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus};
|
||||||
|
|
||||||
|
@ -57,18 +56,6 @@ impl PlayerAgent {
|
||||||
self.camera.aspect = v
|
self.camera.aspect = v
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
|
|
||||||
self.input.process_key(state, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_click(&mut self, state: &ElementState, key: &MouseButton) {
|
|
||||||
self.input.process_click(state, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_scroll(&mut self, delta: &MouseScrollDelta, phase: &TouchPhase) {
|
|
||||||
self.input.process_scroll(delta, phase)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn step(&mut self, ct: &Content, status: PlayerStatus) {
|
pub fn step(&mut self, ct: &Content, status: PlayerStatus) {
|
||||||
if self.input.get_v_scroll() != 0.0 {
|
if self.input.get_v_scroll() != 0.0 {
|
||||||
self.camera.zoom = (self.camera.zoom + self.input.get_v_scroll())
|
self.camera.zoom = (self.camera.zoom + self.input.get_v_scroll())
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
// Pick a frame of a sprite animation from an instance.
|
|
||||||
fn animate(sprite_index: u32, age: f32, offset: f32) -> f32 {
|
|
||||||
|
|
||||||
let len = global_sprites[sprite_index].frame_count;
|
|
||||||
let rep = global_sprites[sprite_index].repeatmode;
|
|
||||||
let frame_duration = global_sprites[sprite_index].frame_duration;
|
|
||||||
var frame: f32 = 0.0;
|
|
||||||
|
|
||||||
// Once
|
|
||||||
if rep == u32(1) {
|
|
||||||
|
|
||||||
frame = min(
|
|
||||||
age / frame_duration + offset,
|
|
||||||
f32(len) - 1.0
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reverse
|
|
||||||
} else if rep == u32(2) {
|
|
||||||
let x = age / frame_duration + offset;
|
|
||||||
let m = f32(len) * 2.0 - 1.0;
|
|
||||||
// x fmod m
|
|
||||||
frame = x - floor(x / m) * m;
|
|
||||||
|
|
||||||
if frame >= f32(len) {
|
|
||||||
frame = (
|
|
||||||
f32(len) + f32(len) - 1.0
|
|
||||||
// Split integer and fractional part so tweening works properly
|
|
||||||
- floor(frame)
|
|
||||||
+ fract(frame)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repeat (default)
|
|
||||||
} else {
|
|
||||||
let x = age / frame_duration + offset;
|
|
||||||
let m = f32(len);
|
|
||||||
frame = x - floor(x / m) * m;
|
|
||||||
}
|
|
||||||
|
|
||||||
return frame + f32(global_sprites[sprite_index].first_frame);
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
// INCLUDE: global uniform header
|
// INCLUDE: global uniform header
|
||||||
|
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) sprite_index: u32,
|
@location(2) texture_index: vec2<u32>,
|
||||||
@location(3) object_index: u32,
|
@location(3) texture_fade: f32,
|
||||||
|
@location(4) object_index: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -25,20 +26,19 @@ var texture_array: binding_array<texture_2d<f32>>;
|
||||||
@group(0) @binding(1)
|
@group(0) @binding(1)
|
||||||
var sampler_array: binding_array<sampler>;
|
var sampler_array: binding_array<sampler>;
|
||||||
|
|
||||||
|
fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, texture_index: u32) -> vec4<f32> {
|
||||||
// INCLUDE: animate.wgsl
|
|
||||||
|
|
||||||
fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, sprite_index: u32) -> vec4<f32> {
|
|
||||||
// Object scale
|
// Object scale
|
||||||
var scale: f32 = obj.size / (global_data.camera_zoom.x * obj.zpos);
|
var scale: f32 = obj.size / (global_data.camera_zoom.x * obj.zpos);
|
||||||
if obj.is_child == 1u {
|
if obj.is_child == 1u {
|
||||||
scale /= objects[obj.parent].zpos;
|
scale /= objects[obj.parent].zpos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let texture = global_atlas[texture_index];
|
||||||
|
|
||||||
// Apply scale and sprite aspect
|
// Apply scale and sprite aspect
|
||||||
// Note that our mesh starts centered at (0, 0). This is important!
|
// Note that our mesh starts centered at (0, 0). This is important!
|
||||||
var pos: vec2<f32> = vec2(
|
var pos: vec2<f32> = vec2(
|
||||||
vertex_position.x * scale * global_sprites[sprite_index].aspect,
|
vertex_position.x * scale * (texture.width / texture.height),
|
||||||
vertex_position.y * scale
|
vertex_position.y * scale
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -113,18 +113,18 @@ fn vertex_main(
|
||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
|
|
||||||
|
out.position = transform_vertex(
|
||||||
|
objects[instance.object_index],
|
||||||
out.position = transform_vertex(objects[instance.object_index], vertex.position.xy, instance.sprite_index);
|
vertex.position.xy,
|
||||||
|
instance.texture_index.x
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
// Compute texture coordinates
|
// Compute texture coordinates
|
||||||
|
|
||||||
let age = global_data.current_time.x;
|
let age = global_data.current_time.x;
|
||||||
let frame = animate(instance.sprite_index, age, 0.0);
|
out.tween = instance.texture_fade;
|
||||||
out.tween = fract(frame);
|
|
||||||
|
|
||||||
let t = global_atlas[u32(floor(frame))];
|
let t = global_atlas[instance.texture_index.x];
|
||||||
out.texture_index_a = u32(t.atlas_texture);
|
out.texture_index_a = u32(t.atlas_texture);
|
||||||
out.texture_coords_a = vec2(t.xpos, t.ypos);
|
out.texture_coords_a = vec2(t.xpos, t.ypos);
|
||||||
if vertex.texture_coords.x == 1.0 {
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
@ -134,7 +134,7 @@ fn vertex_main(
|
||||||
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height);
|
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = global_atlas[u32(floor(animate(instance.sprite_index, age, 1.0)))];
|
let b = global_atlas[instance.texture_index.y];
|
||||||
out.texture_index_b = u32(b.atlas_texture);
|
out.texture_index_b = u32(b.atlas_texture);
|
||||||
out.texture_coords_b = vec2(b.xpos, b.ypos);
|
out.texture_coords_b = vec2(b.xpos, b.ypos);
|
||||||
if vertex.texture_coords.x == 1.0 {
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
|
|
@ -9,7 +9,8 @@ struct InstanceInput {
|
||||||
@location(7) created: f32,
|
@location(7) created: f32,
|
||||||
@location(8) expires: f32,
|
@location(8) expires: f32,
|
||||||
@location(9) fade: f32,
|
@location(9) fade: f32,
|
||||||
@location(10) sprite_index: u32,
|
@location(10) texture_index: vec2<u32>,
|
||||||
|
@location(11) texture_fade: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -33,9 +34,6 @@ var texture_array: binding_array<texture_2d<f32>>;
|
||||||
@group(0) @binding(1)
|
@group(0) @binding(1)
|
||||||
var sampler_array: binding_array<sampler>;
|
var sampler_array: binding_array<sampler>;
|
||||||
|
|
||||||
|
|
||||||
// INCLUDE: animate.wgsl
|
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex_main(
|
fn vertex_main(
|
||||||
vertex: VertexInput,
|
vertex: VertexInput,
|
||||||
|
@ -96,9 +94,9 @@ fn vertex_main(
|
||||||
|
|
||||||
// Compute texture coordinates
|
// Compute texture coordinates
|
||||||
let frame = animate(instance.sprite_index, age, 0.0);
|
let frame = animate(instance.sprite_index, age, 0.0);
|
||||||
out.tween = fract(frame);
|
out.tween = instance.texture_fade;
|
||||||
|
|
||||||
let t = global_atlas[u32(floor(frame))];
|
let t = global_atlas[instance.texture_index.x];
|
||||||
out.texture_index_a = u32(t.atlas_texture);
|
out.texture_index_a = u32(t.atlas_texture);
|
||||||
out.texture_coords_a = vec2(t.xpos, t.ypos);
|
out.texture_coords_a = vec2(t.xpos, t.ypos);
|
||||||
if vertex.texture_coords.x == 1.0 {
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
@ -108,7 +106,7 @@ fn vertex_main(
|
||||||
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height);
|
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = global_atlas[u32(floor(animate(instance.sprite_index, age, 1.0)))];
|
let b = global_atlas[instance.texture_index.y];
|
||||||
out.texture_index_b = u32(b.atlas_texture);
|
out.texture_index_b = u32(b.atlas_texture);
|
||||||
out.texture_coords_b = vec2(b.xpos, b.ypos);
|
out.texture_coords_b = vec2(b.xpos, b.ypos);
|
||||||
if vertex.texture_coords.x == 1.0 {
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
@ -118,7 +116,6 @@ fn vertex_main(
|
||||||
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height);
|
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,8 +109,7 @@ fn vertex_main(
|
||||||
|
|
||||||
|
|
||||||
// Starfield sprites may not be animated
|
// Starfield sprites may not be animated
|
||||||
let i = global_sprites[global_data.starfield_sprite.x].first_frame;
|
let t = global_atlas[global_data.starfield_sprite.x];
|
||||||
let t = global_atlas[i];
|
|
||||||
out.texture_index = u32(t.atlas_texture);
|
out.texture_index = u32(t.atlas_texture);
|
||||||
out.texture_coords = vec2(t.xpos, t.ypos);
|
out.texture_coords = vec2(t.xpos, t.ypos);
|
||||||
if vertex.texture_coords.x == 1.0 {
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
|
|
@ -6,8 +6,9 @@ struct InstanceInput {
|
||||||
@location(4) angle: f32,
|
@location(4) angle: f32,
|
||||||
@location(5) size: f32,
|
@location(5) size: f32,
|
||||||
@location(6) color_transform: vec4<f32>,
|
@location(6) color_transform: vec4<f32>,
|
||||||
@location(7) sprite_index: u32,
|
@location(7) texture_index: vec2<u32>,
|
||||||
@location(8) mask_index: vec2<u32>,
|
@location(8) texture_fade: f32,
|
||||||
|
@location(9) mask_index: vec2<u32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -30,7 +31,6 @@ var texture_array: binding_array<texture_2d<f32>>;
|
||||||
var sampler_array: binding_array<sampler>;
|
var sampler_array: binding_array<sampler>;
|
||||||
|
|
||||||
|
|
||||||
// INCLUDE: animate.wgsl
|
|
||||||
// INCLUDE: anchor.wgsl
|
// INCLUDE: anchor.wgsl
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
|
@ -42,12 +42,15 @@ fn vertex_main(
|
||||||
|
|
||||||
let window_dim = global_data.window_size / global_data.window_scale.x;
|
let window_dim = global_data.window_size / global_data.window_scale.x;
|
||||||
let scale = instance.size / window_dim.y;
|
let scale = instance.size / window_dim.y;
|
||||||
let sprite_aspect = global_sprites[instance.sprite_index].aspect;
|
let aspect = (
|
||||||
|
global_atlas[instance.texture_index.x].width /
|
||||||
|
global_atlas[instance.texture_index.x].height
|
||||||
|
);
|
||||||
|
|
||||||
// Apply scale and sprite aspect
|
// Apply scale and sprite aspect
|
||||||
// Note that our mesh starts centered at (0, 0). This is important!
|
// Note that our mesh starts centered at (0, 0). This is important!
|
||||||
var pos: vec2<f32> = vec2(
|
var pos: vec2<f32> = vec2(
|
||||||
vertex.position.x * scale * sprite_aspect,
|
vertex.position.x * scale * aspect,
|
||||||
vertex.position.y * scale
|
vertex.position.y * scale
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -66,7 +69,7 @@ fn vertex_main(
|
||||||
pos = pos + anchor(
|
pos = pos + anchor(
|
||||||
instance.anchor,
|
instance.anchor,
|
||||||
instance.position,
|
instance.position,
|
||||||
vec2(instance.size * sprite_aspect, instance.size)
|
vec2(instance.size * aspect, instance.size)
|
||||||
);
|
);
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
|
@ -77,7 +80,7 @@ fn vertex_main(
|
||||||
|
|
||||||
// TODO: function to get texture from sprite
|
// TODO: function to get texture from sprite
|
||||||
// Pick texture frame
|
// Pick texture frame
|
||||||
let t = global_atlas[u32(animate(instance.sprite_index, global_data.current_time.x, 0.0))];
|
let t = global_atlas[instance.texture_index.x];
|
||||||
out.texture_index = u32(t.atlas_texture);
|
out.texture_index = u32(t.atlas_texture);
|
||||||
out.texture_coords = vec2(t.xpos, t.ypos);
|
out.texture_coords = vec2(t.xpos, t.ypos);
|
||||||
if vertex.texture_coords.x == 1.0 {
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
@ -91,8 +94,7 @@ fn vertex_main(
|
||||||
// x coordinate of mask index is either 0 or 1, telling us whether or not to use a mask.
|
// x coordinate of mask index is either 0 or 1, telling us whether or not to use a mask.
|
||||||
// y coordinate is mask sprite index
|
// y coordinate is mask sprite index
|
||||||
if instance.mask_index.x == 1u {
|
if instance.mask_index.x == 1u {
|
||||||
let ms = global_sprites[instance.mask_index.y].first_frame;
|
let m = global_atlas[instance.mask_index.y];
|
||||||
let m = global_atlas[ms];
|
|
||||||
out.mask_index = vec2(1u, u32(m.atlas_texture));
|
out.mask_index = vec2(1u, u32(m.atlas_texture));
|
||||||
out.mask_coords = vec2(m.xpos, m.ypos);
|
out.mask_coords = vec2(m.xpos, m.ypos);
|
||||||
if vertex.texture_coords.x == 1.0 {
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use galactica_util::constants::{IMAGE_LIMIT, OBJECT_SPRITE_INSTANCE_LIMIT, SPRITE_LIMIT};
|
use galactica_util::constants::{IMAGE_LIMIT, OBJECT_SPRITE_INSTANCE_LIMIT};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
|
|
||||||
use super::{object::ObjectLocationArray, AtlasArray, GlobalDataContent, SpriteDataArray};
|
use super::{object::ObjectLocationArray, AtlasArray, GlobalDataContent};
|
||||||
|
|
||||||
pub struct GlobalUniform {
|
pub struct GlobalUniform {
|
||||||
pub data_buffer: wgpu::Buffer,
|
pub data_buffer: wgpu::Buffer,
|
||||||
pub atlas_buffer: wgpu::Buffer,
|
pub atlas_buffer: wgpu::Buffer,
|
||||||
pub sprite_buffer: wgpu::Buffer,
|
|
||||||
pub object_buffer: wgpu::Buffer,
|
pub object_buffer: wgpu::Buffer,
|
||||||
pub bind_group: wgpu::BindGroup,
|
pub bind_group: wgpu::BindGroup,
|
||||||
pub bind_group_layout: wgpu::BindGroupLayout,
|
pub bind_group_layout: wgpu::BindGroupLayout,
|
||||||
|
@ -70,35 +69,10 @@ impl GlobalUniform {
|
||||||
// `Buffer is bound with size 3456 where the shader expects 5184 in group[1] compact index 2`
|
// `Buffer is bound with size 3456 where the shader expects 5184 in group[1] compact index 2`
|
||||||
// More notes are in datacontent
|
// More notes are in datacontent
|
||||||
|
|
||||||
// Sprite data
|
|
||||||
out.push_str(&format!(
|
|
||||||
r#"
|
|
||||||
@group({group}) @binding(2)
|
|
||||||
var<uniform> global_sprites: array<SpriteData, {SPRITE_LIMIT}>;
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
out.push_str("\n");
|
|
||||||
out.push_str(
|
|
||||||
r#"
|
|
||||||
struct SpriteData {
|
|
||||||
frame_count: u32,
|
|
||||||
repeatmode: u32,
|
|
||||||
aspect: f32,
|
|
||||||
frame_duration: f32,
|
|
||||||
first_frame: u32,
|
|
||||||
|
|
||||||
padding_a: f32,
|
|
||||||
padding_b: f32,
|
|
||||||
padding_c: f32,
|
|
||||||
};
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
out.push_str("\n");
|
|
||||||
|
|
||||||
// Object location data
|
// Object location data
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
r#"
|
r#"
|
||||||
@group({group}) @binding(3)
|
@group({group}) @binding(2)
|
||||||
var<uniform> objects: array<ObjectData, {OBJECT_SPRITE_INSTANCE_LIMIT}>;
|
var<uniform> objects: array<ObjectData, {OBJECT_SPRITE_INSTANCE_LIMIT}>;
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
@ -138,13 +112,6 @@ impl GlobalUniform {
|
||||||
mapped_at_creation: false,
|
mapped_at_creation: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let sprite_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
||||||
label: Some("global uniform sprite buffer"),
|
|
||||||
size: SpriteDataArray::SIZE,
|
|
||||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
||||||
mapped_at_creation: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
let object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some("global uniform object buffer"),
|
label: Some("global uniform object buffer"),
|
||||||
size: ObjectLocationArray::SIZE,
|
size: ObjectLocationArray::SIZE,
|
||||||
|
@ -184,16 +151,6 @@ impl GlobalUniform {
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
wgpu::BindGroupLayoutEntry {
|
|
||||||
binding: 3,
|
|
||||||
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"),
|
label: Some("global uniform bind group layout"),
|
||||||
});
|
});
|
||||||
|
@ -211,10 +168,6 @@ impl GlobalUniform {
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 2,
|
binding: 2,
|
||||||
resource: sprite_buffer.as_entire_binding(),
|
|
||||||
},
|
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 3,
|
|
||||||
resource: object_buffer.as_entire_binding(),
|
resource: object_buffer.as_entire_binding(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -224,7 +177,6 @@ impl GlobalUniform {
|
||||||
return Self {
|
return Self {
|
||||||
data_buffer,
|
data_buffer,
|
||||||
atlas_buffer,
|
atlas_buffer,
|
||||||
sprite_buffer,
|
|
||||||
object_buffer,
|
object_buffer,
|
||||||
bind_group,
|
bind_group,
|
||||||
bind_group_layout,
|
bind_group_layout,
|
||||||
|
|
|
@ -2,10 +2,8 @@ mod atlas;
|
||||||
mod data;
|
mod data;
|
||||||
mod globaluniform;
|
mod globaluniform;
|
||||||
mod object;
|
mod object;
|
||||||
mod sprite;
|
|
||||||
|
|
||||||
pub use atlas::{AtlasArray, AtlasImageLocation};
|
pub use atlas::{AtlasArray, AtlasImageLocation};
|
||||||
pub use data::GlobalDataContent;
|
pub use data::GlobalDataContent;
|
||||||
pub use globaluniform::GlobalUniform;
|
pub use globaluniform::GlobalUniform;
|
||||||
pub use object::ObjectData;
|
pub use object::ObjectData;
|
||||||
pub use sprite::{SpriteData, SpriteDataArray};
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
use bytemuck::{Pod, Zeroable};
|
|
||||||
use galactica_util::constants::SPRITE_LIMIT;
|
|
||||||
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 frame_duration: 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; SPRITE_LIMIT as usize],
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Pod for SpriteDataArray {}
|
|
||||||
unsafe impl Zeroable for SpriteDataArray {
|
|
||||||
fn zeroed() -> Self {
|
|
||||||
Self {
|
|
||||||
data: [SpriteData::zeroed(); SPRITE_LIMIT as usize],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SpriteDataArray {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::zeroed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpriteDataArray {
|
|
||||||
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
|
|
||||||
}
|
|
|
@ -20,7 +20,7 @@ pub struct GPUState {
|
||||||
|
|
||||||
object_pipeline: wgpu::RenderPipeline,
|
object_pipeline: wgpu::RenderPipeline,
|
||||||
starfield_pipeline: wgpu::RenderPipeline,
|
starfield_pipeline: wgpu::RenderPipeline,
|
||||||
particle_pipeline: wgpu::RenderPipeline,
|
//particle_pipeline: wgpu::RenderPipeline,
|
||||||
ui_pipeline: wgpu::RenderPipeline,
|
ui_pipeline: wgpu::RenderPipeline,
|
||||||
radialbar_pipeline: wgpu::RenderPipeline,
|
radialbar_pipeline: wgpu::RenderPipeline,
|
||||||
|
|
||||||
|
@ -57,12 +57,7 @@ impl GPUState {
|
||||||
self.state.queue.write_buffer(
|
self.state.queue.write_buffer(
|
||||||
&self.state.global_uniform.atlas_buffer,
|
&self.state.global_uniform.atlas_buffer,
|
||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&[self.texture_array.image_locations]),
|
bytemuck::cast_slice(&[self.texture_array.texture_atlas]),
|
||||||
);
|
|
||||||
self.state.queue.write_buffer(
|
|
||||||
&self.state.global_uniform.sprite_buffer,
|
|
||||||
0,
|
|
||||||
bytemuck::cast_slice(&[self.texture_array.sprite_data]),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
self.starfield.update_buffer(ct, &mut self.state);
|
self.starfield.update_buffer(ct, &mut self.state);
|
||||||
|
|
|
@ -167,6 +167,7 @@ impl GPUState {
|
||||||
.set_bind_group_layouts(bind_group_layouts)
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/*
|
||||||
let particle_pipeline = PipelineBuilder::new("particle", &device)
|
let particle_pipeline = PipelineBuilder::new("particle", &device)
|
||||||
.set_shader(&preprocess_shader(
|
.set_shader(&preprocess_shader(
|
||||||
&include_str!(concat!(
|
&include_str!(concat!(
|
||||||
|
@ -181,7 +182,7 @@ impl GPUState {
|
||||||
.set_triangle(true)
|
.set_triangle(true)
|
||||||
.set_vertex_buffer(vertex_buffers.get_particle())
|
.set_vertex_buffer(vertex_buffers.get_particle())
|
||||||
.set_bind_group_layouts(bind_group_layouts)
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
.build();
|
.build();*/
|
||||||
|
|
||||||
let radialbar_pipeline = PipelineBuilder::new("radialbar", &device)
|
let radialbar_pipeline = PipelineBuilder::new("radialbar", &device)
|
||||||
.set_shader(&preprocess_shader(
|
.set_shader(&preprocess_shader(
|
||||||
|
@ -228,7 +229,7 @@ impl GPUState {
|
||||||
object_pipeline,
|
object_pipeline,
|
||||||
starfield_pipeline,
|
starfield_pipeline,
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
particle_pipeline,
|
//particle_pipeline,
|
||||||
radialbar_pipeline,
|
radialbar_pipeline,
|
||||||
|
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -22,7 +22,7 @@ impl GPUState {
|
||||||
let ship_pos;
|
let ship_pos;
|
||||||
let ship_ang;
|
let ship_ang;
|
||||||
let ship_cnt;
|
let ship_cnt;
|
||||||
match ship.data.get_state() {
|
match ship.get_data().get_state() {
|
||||||
ShipState::Dead | ShipState::Landed { .. } => continue,
|
ShipState::Dead | ShipState::Landed { .. } => continue,
|
||||||
|
|
||||||
ShipState::Collapsing { .. } | ShipState::Flying { .. } => {
|
ShipState::Collapsing { .. } | ShipState::Flying { .. } => {
|
||||||
|
@ -31,7 +31,7 @@ impl GPUState {
|
||||||
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
||||||
let ship_rot = r.rotation();
|
let ship_rot = r.rotation();
|
||||||
ship_ang = ship_rot.angle();
|
ship_ang = ship_rot.angle();
|
||||||
ship_cnt = state.ct.get_ship(ship.data.get_content());
|
ship_cnt = state.ct.get_ship(ship.get_data().get_content());
|
||||||
}
|
}
|
||||||
|
|
||||||
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
||||||
|
@ -40,7 +40,7 @@ impl GPUState {
|
||||||
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
||||||
let ship_rot = r.rotation();
|
let ship_rot = r.rotation();
|
||||||
ship_ang = ship_rot.angle();
|
ship_ang = ship_rot.angle();
|
||||||
ship_cnt = state.ct.get_ship(ship.data.get_content());
|
ship_cnt = state.ct.get_ship(ship.get_data().get_content());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,22 +84,23 @@ impl GPUState {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Push this object's instance
|
// Push this object's instance
|
||||||
|
let anim_state = ship.get_anim_state();
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
self.state.push_object_buffer(ObjectInstance {
|
||||||
sprite_index: ship_cnt.sprite.get_index(),
|
texture_index: anim_state.texture_index(),
|
||||||
|
texture_fade: anim_state.fade,
|
||||||
object_index: idx as u32,
|
object_index: idx as u32,
|
||||||
});
|
});
|
||||||
|
|
||||||
let flare = ship.data.get_outfits().get_flare_sprite(state.ct);
|
|
||||||
if {
|
if {
|
||||||
let is_flying = match ship.data.get_state() {
|
let is_flying = match ship.get_data().get_state() {
|
||||||
ShipState::Flying { .. }
|
ShipState::Flying { .. }
|
||||||
| ShipState::UnLanding { .. }
|
| ShipState::UnLanding { .. }
|
||||||
| ShipState::Landing { .. } => true,
|
| ShipState::Landing { .. } => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
ship.get_controls().thrust && flare.is_some() && is_flying
|
ship.get_controls().thrust && is_flying
|
||||||
} {
|
} {
|
||||||
for engine_point in &ship_cnt.engines {
|
for (engine_point, anim) in ship.iter_engine_anim() {
|
||||||
self.state.queue.write_buffer(
|
self.state.queue.write_buffer(
|
||||||
&self.state.global_uniform.object_buffer,
|
&self.state.global_uniform.object_buffer,
|
||||||
ObjectData::SIZE * self.state.get_object_counter() as u64,
|
ObjectData::SIZE * self.state.get_object_counter() as u64,
|
||||||
|
@ -122,8 +123,10 @@ impl GPUState {
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let anim_state = anim.get_texture_idx();
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
self.state.push_object_buffer(ObjectInstance {
|
||||||
sprite_index: flare.unwrap().get_index(),
|
texture_index: anim_state.texture_index(),
|
||||||
|
texture_fade: anim_state.fade,
|
||||||
object_index: self.state.get_object_counter() as u32,
|
object_index: self.state.get_object_counter() as u32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -182,9 +185,10 @@ impl GPUState {
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Push this object's instance
|
let anim_state = p.get_anim_state();
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
self.state.push_object_buffer(ObjectInstance {
|
||||||
sprite_index: proj_cnt.sprite.get_index(),
|
texture_index: anim_state.texture_index(),
|
||||||
|
texture_fade: anim_state.fade,
|
||||||
object_index: idx as u32,
|
object_index: idx as u32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -235,9 +239,13 @@ impl GPUState {
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let sprite = state.ct.get_sprite(o.sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
// Push this object's instance
|
// Push this object's instance
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
self.state.push_object_buffer(ObjectInstance {
|
||||||
sprite_index: o.sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
object_index: idx as u32,
|
object_index: idx as u32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
|
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
|
||||||
use galactica_util::constants::PARTICLE_SPRITE_INSTANCE_LIMIT;
|
use galactica_util::constants::PARTICLE_SPRITE_INSTANCE_LIMIT;
|
||||||
use glyphon::Resolution;
|
use glyphon::Resolution;
|
||||||
use nalgebra::Point2;
|
use nalgebra::Point2;
|
||||||
|
@ -12,9 +13,9 @@ use crate::{
|
||||||
RenderInput,
|
RenderInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl super::GPUState {
|
impl<'a> super::GPUState {
|
||||||
/// Main render function. Draws sprites on a window.
|
/// Render routines while player is flying
|
||||||
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
fn render_flying(&'a mut self, input: &RenderInput) -> Result<()> {
|
||||||
let output = self.surface.get_current_texture()?;
|
let output = self.surface.get_current_texture()?;
|
||||||
let view = output.texture.create_view(&Default::default());
|
let view = output.texture.create_view(&Default::default());
|
||||||
|
|
||||||
|
@ -45,45 +46,19 @@ impl super::GPUState {
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.state.frame_reset();
|
|
||||||
let s = input.ct.get_starfield_handle();
|
|
||||||
|
|
||||||
// Update global values
|
|
||||||
self.state.queue.write_buffer(
|
|
||||||
&self.state.global_uniform.data_buffer,
|
|
||||||
0,
|
|
||||||
bytemuck::cast_slice(&[GlobalDataContent {
|
|
||||||
camera_position: input.camera_pos.into(),
|
|
||||||
camera_zoom: [input.camera_zoom, 0.0],
|
|
||||||
camera_zoom_limits: [
|
|
||||||
input.ct.get_config().zoom_min,
|
|
||||||
input.ct.get_config().zoom_max,
|
|
||||||
],
|
|
||||||
window_size: [
|
|
||||||
self.state.window_size.width as f32,
|
|
||||||
self.state.window_size.height as f32,
|
|
||||||
],
|
|
||||||
window_scale: [self.state.window.scale_factor() as f32, 0.0],
|
|
||||||
window_aspect: [self.state.window_aspect, 0.0],
|
|
||||||
starfield_sprite: [s.get_index(), 0],
|
|
||||||
starfield_tile_size: [input.ct.get_config().starfield_size, 0.0],
|
|
||||||
starfield_size_limits: [
|
|
||||||
input.ct.get_config().starfield_min_size,
|
|
||||||
input.ct.get_config().starfield_max_size,
|
|
||||||
],
|
|
||||||
current_time: [input.current_time, 0.0],
|
|
||||||
}]),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Write all new particles to GPU buffer
|
// Write all new particles to GPU buffer
|
||||||
for i in input.particles.iter() {
|
for i in input.particles.iter() {
|
||||||
|
let sprite = input.ct.get_sprite(i.sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
self.state.push_particle_buffer(ParticleInstance {
|
self.state.push_particle_buffer(ParticleInstance {
|
||||||
position: [i.pos.x, i.pos.y],
|
position: [i.pos.x, i.pos.y],
|
||||||
velocity: i.velocity.into(),
|
velocity: i.velocity.into(),
|
||||||
angle: i.angle,
|
angle: i.angle,
|
||||||
angvel: i.angvel,
|
angvel: i.angvel,
|
||||||
size: i.size,
|
size: i.size,
|
||||||
sprite_index: i.sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
created: input.current_time,
|
created: input.current_time,
|
||||||
expires: input.current_time + i.lifetime,
|
expires: input.current_time + i.lifetime,
|
||||||
fade: i.fade,
|
fade: i.fade,
|
||||||
|
@ -134,6 +109,7 @@ impl super::GPUState {
|
||||||
0..self.state.get_object_counter(),
|
0..self.state.get_object_counter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
// Particle pipeline
|
// Particle pipeline
|
||||||
self.state
|
self.state
|
||||||
.vertex_buffers
|
.vertex_buffers
|
||||||
|
@ -144,7 +120,7 @@ impl super::GPUState {
|
||||||
0..SPRITE_INDICES.len() as u32,
|
0..SPRITE_INDICES.len() as u32,
|
||||||
0,
|
0,
|
||||||
0..PARTICLE_SPRITE_INSTANCE_LIMIT as _,
|
0..PARTICLE_SPRITE_INSTANCE_LIMIT as _,
|
||||||
);
|
); */
|
||||||
|
|
||||||
// Ui pipeline
|
// Ui pipeline
|
||||||
self.state
|
self.state
|
||||||
|
@ -171,7 +147,7 @@ impl super::GPUState {
|
||||||
0..self.state.get_radialbar_counter(),
|
0..self.state.get_radialbar_counter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let textareas = self.ui.get_textareas(&input, &self.state);
|
let textareas = self.ui.get_textareas_flying(input, &self.state);
|
||||||
self.state
|
self.state
|
||||||
.text_renderer
|
.text_renderer
|
||||||
.prepare(
|
.prepare(
|
||||||
|
@ -193,13 +169,175 @@ impl super::GPUState {
|
||||||
.render(&self.state.text_atlas, &mut render_pass)
|
.render(&self.state.text_atlas, &mut render_pass)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// begin_render_pass borrows encoder mutably, so we can't call finish()
|
// begin_render_pass borrows encoder mutably,
|
||||||
// without dropping this variable.
|
// so we need to drop it before calling finish.
|
||||||
drop(render_pass);
|
drop(render_pass);
|
||||||
|
|
||||||
self.state.queue.submit(iter::once(encoder.finish()));
|
self.state.queue.submit(iter::once(encoder.finish()));
|
||||||
output.present();
|
output.present();
|
||||||
|
|
||||||
Ok(())
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render routines while player is landed
|
||||||
|
fn render_landed(&'a mut self, input: &RenderInput) -> Result<()> {
|
||||||
|
let output = self.surface.get_current_texture()?;
|
||||||
|
let view = output.texture.create_view(&Default::default());
|
||||||
|
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("render encoder"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("render pass"),
|
||||||
|
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
|
r: 0.0,
|
||||||
|
g: 0.0,
|
||||||
|
b: 0.0,
|
||||||
|
a: 1.0,
|
||||||
|
}),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create sprite instances
|
||||||
|
self.ui.draw(&input, &mut self.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.state.global_uniform.bind_group, &[]);
|
||||||
|
|
||||||
|
// Starfield pipeline
|
||||||
|
self.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_starfield()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&self.starfield_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..self.state.get_starfield_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ui pipeline
|
||||||
|
self.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_ui()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&self.ui_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..self.state.get_ui_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Radial progress bars
|
||||||
|
self.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_radialbar()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&self.radialbar_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..self.state.get_radialbar_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let textareas = self.ui.get_textareas_landed(input, &self.state);
|
||||||
|
self.state
|
||||||
|
.text_renderer
|
||||||
|
.prepare(
|
||||||
|
&self.device,
|
||||||
|
&self.state.queue,
|
||||||
|
&mut self.state.text_font_system,
|
||||||
|
&mut self.state.text_atlas,
|
||||||
|
Resolution {
|
||||||
|
width: self.state.window_size.width,
|
||||||
|
height: self.state.window_size.height,
|
||||||
|
},
|
||||||
|
textareas,
|
||||||
|
&mut self.state.text_cache,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.state
|
||||||
|
.text_renderer
|
||||||
|
.render(&self.state.text_atlas, &mut render_pass)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// begin_render_pass borrows encoder mutably,
|
||||||
|
// so we need to drop it before calling finish.
|
||||||
|
drop(render_pass);
|
||||||
|
|
||||||
|
self.state.queue.submit(iter::once(encoder.finish()));
|
||||||
|
output.present();
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main render function. Draws sprites on a window.
|
||||||
|
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
||||||
|
// Update global values
|
||||||
|
self.state.queue.write_buffer(
|
||||||
|
&self.state.global_uniform.data_buffer,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[GlobalDataContent {
|
||||||
|
camera_position: input.camera_pos.into(),
|
||||||
|
camera_zoom: [input.camera_zoom, 0.0],
|
||||||
|
camera_zoom_limits: [
|
||||||
|
input.ct.get_config().zoom_min,
|
||||||
|
input.ct.get_config().zoom_max,
|
||||||
|
],
|
||||||
|
window_size: [
|
||||||
|
self.state.window_size.width as f32,
|
||||||
|
self.state.window_size.height as f32,
|
||||||
|
],
|
||||||
|
window_scale: [self.state.window.scale_factor() as f32, 0.0],
|
||||||
|
window_aspect: [self.state.window_aspect, 0.0],
|
||||||
|
starfield_sprite: [input.ct.get_config().starfield_texture, 0],
|
||||||
|
starfield_tile_size: [input.ct.get_config().starfield_size, 0.0],
|
||||||
|
starfield_size_limits: [
|
||||||
|
input.ct.get_config().starfield_min_size,
|
||||||
|
input.ct.get_config().starfield_max_size,
|
||||||
|
],
|
||||||
|
current_time: [input.current_time, 0.0],
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.state.frame_reset();
|
||||||
|
|
||||||
|
match input
|
||||||
|
.systemsim
|
||||||
|
.get_ship(&PhysSimShipHandle(input.player.ship.unwrap()))
|
||||||
|
.unwrap()
|
||||||
|
.get_data()
|
||||||
|
.get_state()
|
||||||
|
{
|
||||||
|
ShipState::Collapsing
|
||||||
|
| ShipState::Dead
|
||||||
|
| ShipState::Landing { .. }
|
||||||
|
| ShipState::UnLanding { .. }
|
||||||
|
| ShipState::Flying { .. } => {
|
||||||
|
self.render_flying(&input).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::Landed { .. } => {
|
||||||
|
self.render_landed(&input).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,6 @@ pub(crate) fn preprocess_shader(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Insert common functions
|
// Insert common functions
|
||||||
let shader = shader.replace(
|
|
||||||
"// INCLUDE: animate.wgsl",
|
|
||||||
&include_str!(concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/shaders/include/",
|
|
||||||
"animate.wgsl"
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let shader = shader.replace(
|
let shader = shader.replace(
|
||||||
"// INCLUDE: anchor.wgsl",
|
"// INCLUDE: anchor.wgsl",
|
||||||
&include_str!(concat!(
|
&include_str!(concat!(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::globaluniform::{AtlasArray, AtlasImageLocation, SpriteData, SpriteDataArray};
|
use crate::globaluniform::{AtlasArray, AtlasImageLocation};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck::Zeroable;
|
use bytemuck::Zeroable;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
|
@ -84,8 +84,7 @@ pub struct Texture {
|
||||||
pub struct TextureArray {
|
pub struct TextureArray {
|
||||||
pub bind_group: wgpu::BindGroup,
|
pub bind_group: wgpu::BindGroup,
|
||||||
pub bind_group_layout: BindGroupLayout,
|
pub bind_group_layout: BindGroupLayout,
|
||||||
pub image_locations: AtlasArray,
|
pub texture_atlas: AtlasArray,
|
||||||
pub sprite_data: SpriteDataArray,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureArray {
|
impl TextureArray {
|
||||||
|
@ -93,49 +92,36 @@ impl TextureArray {
|
||||||
// Load all textures
|
// Load all textures
|
||||||
let mut texture_data = Vec::new();
|
let mut texture_data = Vec::new();
|
||||||
|
|
||||||
for a in ct.atlas_files() {
|
for file in ct.atlas_files() {
|
||||||
println!("opening {a}");
|
println!("opening {file}");
|
||||||
let p = Path::new(ASSET_CACHE);
|
let p = Path::new(ASSET_CACHE);
|
||||||
let mut f = File::open(p.join(a))?;
|
let mut f = File::open(p.join(file))?;
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
f.read_to_end(&mut bytes)?;
|
f.read_to_end(&mut bytes)?;
|
||||||
texture_data.push(RawTexture::from_bytes(
|
texture_data.push(RawTexture::from_bytes(
|
||||||
&device,
|
&device,
|
||||||
&queue,
|
&queue,
|
||||||
&bytes,
|
&bytes,
|
||||||
&format!("Atlas `{a}`"),
|
&format!("Atlas `{file}`"),
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut image_locations = AtlasArray::zeroed();
|
let mut image_locations = AtlasArray::zeroed();
|
||||||
let mut sprite_data = SpriteDataArray::zeroed();
|
|
||||||
|
|
||||||
println!("sending to gpu");
|
println!("sending to gpu");
|
||||||
let mut image_counter = 0;
|
for sprite in &ct.sprites {
|
||||||
let mut sprite_counter = 0;
|
for section in sprite.iter_sections() {
|
||||||
for t in &ct.sprites {
|
for idx in §ion.frames {
|
||||||
sprite_data.data[sprite_counter] = SpriteData {
|
let image = ct.get_image(*idx);
|
||||||
frame_count: t.frames.len() as u32,
|
image_locations.data[*idx as usize] = AtlasImageLocation {
|
||||||
repeatmode: t.repeat.as_int(),
|
xpos: image.x,
|
||||||
aspect: t.aspect,
|
ypos: image.y,
|
||||||
frame_duration: t.frame_duration,
|
width: image.w,
|
||||||
first_frame: image_counter,
|
height: image.h,
|
||||||
_padding: Default::default(),
|
atlas_texture: image.atlas,
|
||||||
};
|
_padding: Default::default(),
|
||||||
sprite_counter += 1;
|
};
|
||||||
|
}
|
||||||
// Insert texture location data
|
|
||||||
for path in &t.frames {
|
|
||||||
let image = ct.get_image(&path);
|
|
||||||
image_locations.data[image_counter as usize] = AtlasImageLocation {
|
|
||||||
xpos: image.x,
|
|
||||||
ypos: image.y,
|
|
||||||
width: image.w,
|
|
||||||
height: image.h,
|
|
||||||
atlas_texture: image.atlas,
|
|
||||||
_padding: Default::default(),
|
|
||||||
};
|
|
||||||
image_counter += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,8 +178,7 @@ impl TextureArray {
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
bind_group,
|
bind_group,
|
||||||
bind_group_layout,
|
bind_group_layout,
|
||||||
image_locations,
|
texture_atlas: image_locations,
|
||||||
sprite_data,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl UiManager {
|
||||||
|
|
||||||
self.fps.update(input, state);
|
self.fps.update(input, state);
|
||||||
|
|
||||||
match ship.data.get_state() {
|
match ship.get_data().get_state() {
|
||||||
ShipState::Collapsing
|
ShipState::Collapsing
|
||||||
| ShipState::Dead
|
| ShipState::Dead
|
||||||
| ShipState::Flying { .. }
|
| ShipState::Flying { .. }
|
||||||
|
@ -48,23 +48,22 @@ impl UiManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_textareas(&self, input: &RenderInput, state: &RenderState) -> Vec<TextArea> {
|
/// Textareas to show while player is flying
|
||||||
|
pub fn get_textareas_flying(
|
||||||
|
&self,
|
||||||
|
_input: &RenderInput,
|
||||||
|
_state: &RenderState,
|
||||||
|
) -> Vec<TextArea> {
|
||||||
let mut v = Vec::with_capacity(5);
|
let mut v = Vec::with_capacity(5);
|
||||||
|
v.push(self.fps.get_textarea());
|
||||||
|
|
||||||
let ship_handle = input.player.ship.unwrap();
|
return v;
|
||||||
let ship = input
|
}
|
||||||
.systemsim
|
|
||||||
.get_ship(&PhysSimShipHandle(ship_handle))
|
|
||||||
.unwrap();
|
|
||||||
match ship.data.get_state() {
|
|
||||||
ShipState::Collapsing
|
|
||||||
| ShipState::Dead
|
|
||||||
| ShipState::Flying { .. }
|
|
||||||
| ShipState::Landing { .. }
|
|
||||||
| ShipState::UnLanding { .. } => v.push(self.fps.get_textarea()),
|
|
||||||
|
|
||||||
ShipState::Landed { .. } => v.extend(self.planet.get_textarea(input, state)),
|
/// Textareas to show when player is landed
|
||||||
};
|
pub fn get_textareas_landed(&self, input: &RenderInput, state: &RenderState) -> Vec<TextArea> {
|
||||||
|
let mut v = Vec::with_capacity(5);
|
||||||
|
v.extend(self.planet.get_textarea(input, state));
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,62 @@
|
||||||
use galactica_content::{Content, SystemObject, SystemObjectHandle};
|
use galactica_content::{Content, SystemObject, SystemObjectHandle};
|
||||||
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
|
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
|
||||||
use galactica_util::to_radians;
|
|
||||||
use glyphon::{cosmic_text::Align, Attrs, Color, Metrics, TextArea, Weight};
|
use glyphon::{cosmic_text::Align, Attrs, Color, Metrics, TextArea, Weight};
|
||||||
use nalgebra::{Point2, Vector2};
|
use nalgebra::{Point2, Vector2};
|
||||||
|
|
||||||
use super::util::{SpriteRect, UiImage, UiTextArea};
|
use super::util::{SpriteRect, UiSprite, UiTextArea};
|
||||||
use crate::{vertexbuffer::types::UiInstance, PositionAnchor, RenderInput, RenderState};
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
pub(super) struct Planet {
|
pub(super) struct Planet {
|
||||||
// UI elements
|
// UI elements
|
||||||
planet_desc: UiTextArea,
|
planet_desc: UiTextArea,
|
||||||
planet_name: UiTextArea,
|
planet_name: UiTextArea,
|
||||||
landscape: UiImage,
|
sprite: UiSprite,
|
||||||
|
|
||||||
// Height of whole element,in logical pixels
|
|
||||||
size: f32,
|
|
||||||
|
|
||||||
/// What object we're displaying currently.
|
/// What object we're displaying currently.
|
||||||
/// Whenever this changes, we need to reflow text.
|
/// Whenever this changes, we need to reflow text.
|
||||||
current_object: Option<SystemObjectHandle>,
|
current_object: Option<SystemObjectHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: no scroll
|
|
||||||
// TODO: animate in/out
|
// TODO: animate in/out
|
||||||
|
|
||||||
impl Planet {
|
impl Planet {
|
||||||
pub fn new(ct: &Content, state: &mut RenderState) -> Self {
|
pub fn new(ct: &Content, state: &mut RenderState) -> Self {
|
||||||
let size = 800.0;
|
let mut sprite = UiSprite::new(
|
||||||
|
ct.get_sprite_handle("ui::planet"),
|
||||||
|
None,
|
||||||
|
SpriteRect {
|
||||||
|
pos: Point2::new(0.0, 0.0),
|
||||||
|
dim: Vector2::new(800.0, 800.0),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
sprite.add_child_under(Box::new(UiSprite::new(
|
||||||
|
ct.get_sprite_handle("ui::landscape::test"),
|
||||||
|
Some(ct.get_sprite_handle("ui::landscapemask")),
|
||||||
|
SpriteRect {
|
||||||
|
pos: Point2::new(32.0, 75.0) / 512.0,
|
||||||
|
dim: Vector2::new(344.0, 173.0) / 512.0,
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
|
||||||
|
sprite.add_child_under(Box::new(UiSprite::new(
|
||||||
|
ct.get_sprite_handle("ui::planet::button"),
|
||||||
|
None,
|
||||||
|
SpriteRect {
|
||||||
|
pos: Point2::new(375.0, 90.0) / 512.0,
|
||||||
|
dim: Vector2::new(113.569, 20.0) / 512.0,
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
|
||||||
let s = Self {
|
let s = Self {
|
||||||
// height of element in logical pixels
|
// height of element in logical pixels
|
||||||
size,
|
|
||||||
current_object: None,
|
current_object: None,
|
||||||
|
|
||||||
planet_desc: UiTextArea::new(
|
planet_desc: UiTextArea::new(
|
||||||
state,
|
state,
|
||||||
ct.get_sprite_handle("ui::planet"),
|
ct.get_sprite_handle("ui::planet"),
|
||||||
Point2::new(0.0, 0.0),
|
Point2::new(0.0, 0.0),
|
||||||
size,
|
800.0,
|
||||||
SpriteRect {
|
SpriteRect {
|
||||||
pos: Point2::new(25.831, 284.883) / 512.0,
|
pos: Point2::new(25.831, 284.883) / 512.0,
|
||||||
dim: Vector2::new(433.140, 97.220) / 512.0,
|
dim: Vector2::new(433.140, 97.220) / 512.0,
|
||||||
|
@ -50,7 +70,7 @@ impl Planet {
|
||||||
state,
|
state,
|
||||||
ct.get_sprite_handle("ui::planet"),
|
ct.get_sprite_handle("ui::planet"),
|
||||||
Point2::new(0.0, 0.0),
|
Point2::new(0.0, 0.0),
|
||||||
size,
|
800.0,
|
||||||
SpriteRect {
|
SpriteRect {
|
||||||
pos: Point2::new(165.506, 82.0) / 512.0,
|
pos: Point2::new(165.506, 82.0) / 512.0,
|
||||||
dim: Vector2::new(74.883, 17.0) / 512.0,
|
dim: Vector2::new(74.883, 17.0) / 512.0,
|
||||||
|
@ -60,17 +80,9 @@ impl Planet {
|
||||||
Align::Center,
|
Align::Center,
|
||||||
),
|
),
|
||||||
|
|
||||||
landscape: UiImage::new(
|
// TODO: use both dimensions,
|
||||||
ct.get_sprite_handle("ui::planet"),
|
// not just height
|
||||||
Point2::new(0.0, 0.0),
|
sprite,
|
||||||
size,
|
|
||||||
ct.get_sprite_handle("ui::landscape::test"),
|
|
||||||
ct.get_sprite_handle("ui::landscapemask"),
|
|
||||||
SpriteRect {
|
|
||||||
pos: Point2::new(32.031, 75.587) / 512.0,
|
|
||||||
dim: Vector2::new(342.811, 171.402) / 512.0,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
|
@ -104,7 +116,7 @@ impl Planet {
|
||||||
.systemsim
|
.systemsim
|
||||||
.get_ship(&PhysSimShipHandle(ship_handle))
|
.get_ship(&PhysSimShipHandle(ship_handle))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let planet_handle = match ship_data.data.get_state() {
|
let planet_handle = match ship_data.get_data().get_state() {
|
||||||
ShipState::Landed { target } => *target,
|
ShipState::Landed { target } => *target,
|
||||||
_ => unreachable!("tried to draw planet interface while not landed!"),
|
_ => unreachable!("tried to draw planet interface while not landed!"),
|
||||||
};
|
};
|
||||||
|
@ -120,16 +132,7 @@ impl Planet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw elements
|
// Draw elements
|
||||||
self.landscape.push_to_buffer(state);
|
self.sprite.push_to_buffer(input, state);
|
||||||
state.push_ui_buffer(UiInstance {
|
|
||||||
anchor: PositionAnchor::CC.to_int(),
|
|
||||||
position: [0.0, 0.0],
|
|
||||||
angle: to_radians(90.0),
|
|
||||||
size: self.size,
|
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
|
||||||
sprite_index: input.ct.get_sprite_handle("ui::planet").get_index(),
|
|
||||||
mask_index: [0, 0],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_textarea(&self, _input: &RenderInput, state: &RenderState) -> [TextArea; 2] {
|
pub fn get_textarea(&self, _input: &RenderInput, state: &RenderState) -> [TextArea; 2] {
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl Radar {
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match player_ship.data.get_state() {
|
match player_ship.get_data().get_state() {
|
||||||
ShipState::Dead => {}
|
ShipState::Dead => {}
|
||||||
|
|
||||||
ShipState::Landed { target } => {
|
ShipState::Landed { target } => {
|
||||||
|
@ -60,6 +60,9 @@ impl Radar {
|
||||||
let ship_sprite = input.ct.get_sprite_handle("ui::shipblip");
|
let ship_sprite = input.ct.get_sprite_handle("ui::shipblip");
|
||||||
let arrow_sprite = input.ct.get_sprite_handle("ui::centerarrow");
|
let arrow_sprite = input.ct.get_sprite_handle("ui::centerarrow");
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(input.ct.get_sprite_handle("ui::radar"));
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
// Push this object's instance
|
// Push this object's instance
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
anchor: PositionAnchor::NwNw.to_int(),
|
anchor: PositionAnchor::NwNw.to_int(),
|
||||||
|
@ -67,7 +70,8 @@ impl Radar {
|
||||||
angle: 0.0,
|
angle: 0.0,
|
||||||
size: radar_size,
|
size: radar_size,
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
sprite_index: input.ct.get_sprite_handle("ui::radar").get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -87,6 +91,9 @@ impl Radar {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(planet_sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
// Push this object's instance
|
// Push this object's instance
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
anchor: PositionAnchor::NwC.to_int(),
|
anchor: PositionAnchor::NwC.to_int(),
|
||||||
|
@ -96,7 +103,8 @@ impl Radar {
|
||||||
angle: o.angle,
|
angle: o.angle,
|
||||||
size,
|
size,
|
||||||
color: [0.5, 0.5, 0.5, 1.0],
|
color: [0.5, 0.5, 0.5, 1.0],
|
||||||
sprite_index: planet_sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -104,8 +112,8 @@ impl Radar {
|
||||||
|
|
||||||
// Draw ships
|
// Draw ships
|
||||||
for (s, r) in input.systemsim.iter_ship_body() {
|
for (s, r) in input.systemsim.iter_ship_body() {
|
||||||
let ship = input.ct.get_ship(s.data.get_content());
|
let ship = input.ct.get_ship(s.get_data().get_content());
|
||||||
let (color, z_scale) = match s.data.get_state() {
|
let (color, z_scale) = match s.get_data().get_state() {
|
||||||
ShipState::Dead | ShipState::Landed { .. } => {
|
ShipState::Dead | ShipState::Landed { .. } => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +129,7 @@ impl Radar {
|
||||||
([0.2, 0.2, 0.2, 1.0], 1.0)
|
([0.2, 0.2, 0.2, 1.0], 1.0)
|
||||||
}
|
}
|
||||||
ShipState::Flying { .. } => {
|
ShipState::Flying { .. } => {
|
||||||
let c = input.ct.get_faction(s.data.get_faction()).color;
|
let c = input.ct.get_faction(s.get_data().get_faction()).color;
|
||||||
([c[0], c[1], c[2], 1.0], 1.0)
|
([c[0], c[1], c[2], 1.0], 1.0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -144,13 +152,17 @@ impl Radar {
|
||||||
let position = Point2::new(radar_size / 2.0 + 10.0, radar_size / -2.0 - 10.0)
|
let position = Point2::new(radar_size / 2.0 + 10.0, radar_size / -2.0 - 10.0)
|
||||||
+ (d * (radar_size / 2.0));
|
+ (d * (radar_size / 2.0));
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(ship_sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
anchor: PositionAnchor::NwC.to_int(),
|
anchor: PositionAnchor::NwC.to_int(),
|
||||||
position: position.into(),
|
position: position.into(),
|
||||||
angle: r.rotation().angle(),
|
angle: r.rotation().angle(),
|
||||||
size,
|
size,
|
||||||
color,
|
color,
|
||||||
sprite_index: ship_sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -168,6 +180,9 @@ impl Radar {
|
||||||
let sprite = input.ct.get_sprite_handle("ui::radarframe");
|
let sprite = input.ct.get_sprite_handle("ui::radarframe");
|
||||||
let size = 7.0f32.min((0.8 - m) * 70.0);
|
let size = 7.0f32.min((0.8 - m) * 70.0);
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
anchor: PositionAnchor::NwNw.to_int(),
|
anchor: PositionAnchor::NwNw.to_int(),
|
||||||
position: Point2::new(
|
position: Point2::new(
|
||||||
|
@ -178,7 +193,8 @@ impl Radar {
|
||||||
angle: to_radians(90.0),
|
angle: to_radians(90.0),
|
||||||
size,
|
size,
|
||||||
color,
|
color,
|
||||||
sprite_index: sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -192,7 +208,8 @@ impl Radar {
|
||||||
angle: to_radians(180.0),
|
angle: to_radians(180.0),
|
||||||
size,
|
size,
|
||||||
color,
|
color,
|
||||||
sprite_index: sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -206,7 +223,8 @@ impl Radar {
|
||||||
angle: to_radians(270.0),
|
angle: to_radians(270.0),
|
||||||
size,
|
size,
|
||||||
color,
|
color,
|
||||||
sprite_index: sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -220,7 +238,8 @@ impl Radar {
|
||||||
angle: to_radians(0.0),
|
angle: to_radians(0.0),
|
||||||
size,
|
size,
|
||||||
color,
|
color,
|
||||||
sprite_index: sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -233,13 +252,17 @@ impl Radar {
|
||||||
let position = Point2::new(10.0 + (radar_size / 2.0), -10.0 - (radar_size / 2.0))
|
let position = Point2::new(10.0 + (radar_size / 2.0), -10.0 - (radar_size / 2.0))
|
||||||
+ Rotation2::new(angle) * Vector2::new(0.915 * (radar_size / 2.0), 0.0);
|
+ Rotation2::new(angle) * Vector2::new(0.915 * (radar_size / 2.0), 0.0);
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(arrow_sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
anchor: PositionAnchor::NwC.to_int(),
|
anchor: PositionAnchor::NwC.to_int(),
|
||||||
position: position.into(),
|
position: position.into(),
|
||||||
angle,
|
angle,
|
||||||
size: 10.0,
|
size: 10.0,
|
||||||
color: [1.0, 1.0, 1.0, 1f32.min((m - 200.0) / 400.0)],
|
color: [1.0, 1.0, 1.0, 1f32.min((m - 200.0) / 400.0)],
|
||||||
sprite_index: arrow_sprite.get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,32 +26,38 @@ impl Status {
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match player_ship.data.get_state() {
|
match player_ship.get_data().get_state() {
|
||||||
ShipState::Dead => {
|
ShipState::Dead => {
|
||||||
current_shields = 0.0;
|
current_shields = 0.0;
|
||||||
current_hull = 0.0;
|
current_hull = 0.0;
|
||||||
max_shields = player_ship.data.get_outfits().get_shield_strength();
|
max_shields = player_ship.get_data().get_outfits().get_shield_strength();
|
||||||
max_hull = input.ct.get_ship(player_ship.data.get_content()).hull;
|
max_hull = input.ct.get_ship(player_ship.get_data().get_content()).hull;
|
||||||
}
|
}
|
||||||
ShipState::UnLanding { .. }
|
ShipState::UnLanding { .. }
|
||||||
| ShipState::Landing { .. }
|
| ShipState::Landing { .. }
|
||||||
| ShipState::Landed { .. }
|
| ShipState::Landed { .. }
|
||||||
| ShipState::Collapsing { .. }
|
| ShipState::Collapsing { .. }
|
||||||
| ShipState::Flying { .. } => {
|
| ShipState::Flying { .. } => {
|
||||||
current_shields = player_ship.data.get_shields();
|
current_shields = player_ship.get_data().get_shields();
|
||||||
current_hull = player_ship.data.get_hull();
|
current_hull = player_ship.get_data().get_hull();
|
||||||
max_shields = player_ship.data.get_outfits().get_shield_strength();
|
max_shields = player_ship.get_data().get_outfits().get_shield_strength();
|
||||||
max_hull = input.ct.get_ship(player_ship.data.get_content()).hull;
|
max_hull = input.ct.get_ship(player_ship.get_data().get_content()).hull;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sprite = input
|
||||||
|
.ct
|
||||||
|
.get_sprite(input.ct.get_sprite_handle("ui::status"));
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
anchor: PositionAnchor::NeNe.to_int(),
|
anchor: PositionAnchor::NeNe.to_int(),
|
||||||
position: [-10.0, -10.0],
|
position: [-10.0, -10.0],
|
||||||
angle: 0.0,
|
angle: 0.0,
|
||||||
size: 200.0,
|
size: 200.0,
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
sprite_index: input.ct.get_sprite_handle("ui::status").get_index(),
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
mask_index: [0, 0],
|
mask_index: [0, 0],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
use galactica_content::SpriteHandle;
|
|
||||||
use galactica_util::to_radians;
|
|
||||||
use nalgebra::{Point2, Vector2};
|
|
||||||
|
|
||||||
use super::SpriteRect;
|
|
||||||
use crate::{vertexbuffer::types::UiInstance, PositionAnchor, RenderState};
|
|
||||||
|
|
||||||
pub struct UiImage {
|
|
||||||
parent: SpriteHandle,
|
|
||||||
parent_position: Point2<f32>,
|
|
||||||
parent_size: f32,
|
|
||||||
|
|
||||||
inner: SpriteHandle,
|
|
||||||
mask: SpriteHandle,
|
|
||||||
rect: SpriteRect,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiImage {
|
|
||||||
pub fn new(
|
|
||||||
parent: SpriteHandle,
|
|
||||||
parent_position: Point2<f32>,
|
|
||||||
parent_size: f32,
|
|
||||||
inner: SpriteHandle,
|
|
||||||
mask: SpriteHandle,
|
|
||||||
rect: SpriteRect,
|
|
||||||
) -> Self {
|
|
||||||
return Self {
|
|
||||||
parent,
|
|
||||||
parent_position,
|
|
||||||
parent_size,
|
|
||||||
inner,
|
|
||||||
mask,
|
|
||||||
rect,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add this image to the gpu sprite buffer
|
|
||||||
pub fn push_to_buffer(&self, state: &mut RenderState) {
|
|
||||||
let h = self.parent_size;
|
|
||||||
let w = self.parent.aspect * h;
|
|
||||||
|
|
||||||
let zero = Point2::new(
|
|
||||||
self.parent_position.x - (w / 2.0),
|
|
||||||
self.parent_position.y + (h / 2.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
let pos = zero + Vector2::new(self.rect.pos.x * w, -self.rect.pos.y * h);
|
|
||||||
let dim = Vector2::new(self.rect.dim.x * w, self.rect.dim.y * h);
|
|
||||||
|
|
||||||
state.push_ui_buffer(UiInstance {
|
|
||||||
anchor: PositionAnchor::CNw.to_int(),
|
|
||||||
position: pos.into(),
|
|
||||||
angle: to_radians(90.0),
|
|
||||||
size: dim.y,
|
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
|
||||||
sprite_index: self.inner.get_index(),
|
|
||||||
mask_index: [1, self.mask.get_index()],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,13 @@
|
||||||
mod image;
|
mod sprite;
|
||||||
mod textarea;
|
mod textarea;
|
||||||
|
|
||||||
pub(super) use image::UiImage;
|
pub(super) use sprite::UiSprite;
|
||||||
pub(super) use textarea::UiTextArea;
|
pub(super) use textarea::UiTextArea;
|
||||||
|
|
||||||
use nalgebra::{Point2, Vector2};
|
use nalgebra::{Point2, Vector2};
|
||||||
|
|
||||||
|
use crate::{RenderInput, RenderState};
|
||||||
|
|
||||||
/// Represents a rectangular region inside a sprite.
|
/// Represents a rectangular region inside a sprite.
|
||||||
pub(crate) struct SpriteRect {
|
pub(crate) struct SpriteRect {
|
||||||
/// The position of the top-left corner of this rectangle, in fractional units.
|
/// The position of the top-left corner of this rectangle, in fractional units.
|
||||||
|
@ -16,3 +18,13 @@ pub(crate) struct SpriteRect {
|
||||||
/// 1.0 will be as tall as the sprite, 0.5 will be half as tall
|
/// 1.0 will be as tall as the sprite, 0.5 will be half as tall
|
||||||
pub dim: Vector2<f32>,
|
pub dim: Vector2<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) trait UiElement {
|
||||||
|
fn push_to_buffer_child(
|
||||||
|
&self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
parent_pos: Point2<f32>,
|
||||||
|
parent_size: Vector2<f32>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
use galactica_content::SpriteHandle;
|
||||||
|
use galactica_util::to_radians;
|
||||||
|
use nalgebra::{Point2, Vector2};
|
||||||
|
|
||||||
|
use super::{SpriteRect, UiElement};
|
||||||
|
use crate::{vertexbuffer::types::UiInstance, PositionAnchor, RenderInput, RenderState};
|
||||||
|
|
||||||
|
pub struct UiSprite {
|
||||||
|
sprite: SpriteHandle,
|
||||||
|
mask: Option<SpriteHandle>,
|
||||||
|
|
||||||
|
rect: SpriteRect,
|
||||||
|
children_under: Vec<Box<dyn UiElement>>,
|
||||||
|
children_above: Vec<Box<dyn UiElement>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiSprite {
|
||||||
|
pub fn new(sprite: SpriteHandle, mask: Option<SpriteHandle>, rect: SpriteRect) -> Self {
|
||||||
|
return Self {
|
||||||
|
sprite,
|
||||||
|
mask,
|
||||||
|
rect,
|
||||||
|
children_under: Vec::new(),
|
||||||
|
children_above: Vec::new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a child under this sprite
|
||||||
|
pub fn add_child_under(&mut self, child: Box<dyn UiElement>) {
|
||||||
|
self.children_under.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a child above this sprite
|
||||||
|
//pub fn add_child_above(&mut self, child: Box<dyn UiElement>) {
|
||||||
|
// self.children_above.push(child);
|
||||||
|
//}
|
||||||
|
|
||||||
|
/// Add this image to the gpu sprite buffer
|
||||||
|
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||||
|
let pos = Point2::new(self.rect.pos.x, self.rect.pos.y);
|
||||||
|
let dim = Vector2::new(self.rect.dim.y * self.sprite.aspect, self.rect.dim.y);
|
||||||
|
|
||||||
|
for c in &self.children_under {
|
||||||
|
c.push_to_buffer_child(input, state, pos, dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(self.sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::CC.to_int(),
|
||||||
|
position: pos.into(),
|
||||||
|
angle: to_radians(90.0),
|
||||||
|
size: dim.y,
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: self
|
||||||
|
.mask
|
||||||
|
.map(|x| {
|
||||||
|
let sprite = input.ct.get_sprite(x);
|
||||||
|
let texture_b = sprite.get_first_frame(); // ANIMATE
|
||||||
|
[1, texture_b]
|
||||||
|
})
|
||||||
|
.unwrap_or([0, 0]),
|
||||||
|
});
|
||||||
|
|
||||||
|
for c in &self.children_above {
|
||||||
|
c.push_to_buffer_child(input, state, pos, dim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiElement for UiSprite {
|
||||||
|
/// Add this image to the gpu sprite buffer,
|
||||||
|
/// as a child of another sprite
|
||||||
|
fn push_to_buffer_child(
|
||||||
|
&self,
|
||||||
|
input: &RenderInput,
|
||||||
|
state: &mut RenderState,
|
||||||
|
parent_pos: Point2<f32>,
|
||||||
|
parent_size: Vector2<f32>,
|
||||||
|
) {
|
||||||
|
let zero = Point2::new(
|
||||||
|
parent_pos.x - (parent_size.x / 2.0),
|
||||||
|
parent_pos.y + (parent_size.y / 2.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
let pos = zero
|
||||||
|
+ Vector2::new(
|
||||||
|
self.rect.pos.x * parent_size.x,
|
||||||
|
-self.rect.pos.y * parent_size.y,
|
||||||
|
);
|
||||||
|
let dim = Vector2::new(
|
||||||
|
self.rect.dim.x * parent_size.x,
|
||||||
|
self.rect.dim.y * parent_size.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
for c in &self.children_under {
|
||||||
|
c.push_to_buffer_child(input, state, pos, dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(self.sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: PositionAnchor::CNw.to_int(),
|
||||||
|
position: pos.into(),
|
||||||
|
angle: to_radians(90.0),
|
||||||
|
size: dim.y,
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
mask_index: self
|
||||||
|
.mask
|
||||||
|
.map(|x| {
|
||||||
|
let sprite = input.ct.get_sprite(x);
|
||||||
|
let texture_b = sprite.get_first_frame(); // ANIMATE
|
||||||
|
[1, texture_b]
|
||||||
|
})
|
||||||
|
.unwrap_or([0, 0]),
|
||||||
|
});
|
||||||
|
|
||||||
|
for c in &self.children_above {
|
||||||
|
c.push_to_buffer_child(input, state, pos, dim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,7 +85,13 @@ impl BufferObject for StarfieldInstance {
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct ObjectInstance {
|
pub struct ObjectInstance {
|
||||||
/// What texture to use for this instance
|
/// What texture to use for this instance
|
||||||
pub sprite_index: u32,
|
/// Result will be a faded mix of the two textures
|
||||||
|
/// given here. Only the first will be shown if fade = 0.0,
|
||||||
|
/// and only the second if fade = 1.0
|
||||||
|
pub texture_index: [u32; 2],
|
||||||
|
/// Fade parameter for texture index
|
||||||
|
/// Must be between 0 and 1.
|
||||||
|
pub texture_fade: f32,
|
||||||
|
|
||||||
/// Which object this instance is for
|
/// Which object this instance is for
|
||||||
pub object_index: u32,
|
pub object_index: u32,
|
||||||
|
@ -100,16 +106,22 @@ impl BufferObject for ObjectInstance {
|
||||||
// instance when the shader starts processing a new instance
|
// instance when the shader starts processing a new instance
|
||||||
step_mode: wgpu::VertexStepMode::Instance,
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
attributes: &[
|
attributes: &[
|
||||||
// Sprite
|
// Texture
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
shader_location: 2,
|
shader_location: 2,
|
||||||
format: wgpu::VertexFormat::Uint32,
|
format: wgpu::VertexFormat::Uint32x2,
|
||||||
|
},
|
||||||
|
// Texture fade
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 3,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Object
|
// Object
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 1]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||||
shader_location: 3,
|
shader_location: 4,
|
||||||
format: wgpu::VertexFormat::Uint32,
|
format: wgpu::VertexFormat::Uint32,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -138,12 +150,14 @@ pub struct UiInstance {
|
||||||
/// Fill this array with ones if no recoloring should be done.
|
/// Fill this array with ones if no recoloring should be done.
|
||||||
pub color: [f32; 4],
|
pub color: [f32; 4],
|
||||||
|
|
||||||
/// What texture to use for this sprite
|
/// What texture to use for this instance
|
||||||
pub sprite_index: u32,
|
pub texture_index: [u32; 2],
|
||||||
|
/// Fade parameter between textures
|
||||||
|
pub texture_fade: f32,
|
||||||
|
|
||||||
/// What mask to use for this sprite
|
/// What texture to use to mask this instance
|
||||||
/// If the first element is not 1, no mask is used.
|
/// If the first element is not 1, no mask is used.
|
||||||
/// Second element is sprite index of mask.
|
/// Second element is a texture index
|
||||||
pub mask_index: [u32; 2],
|
pub mask_index: [u32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,16 +200,22 @@ impl BufferObject for UiInstance {
|
||||||
shader_location: 6,
|
shader_location: 6,
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
},
|
},
|
||||||
// Sprite
|
// Texture
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
|
||||||
shader_location: 7,
|
shader_location: 7,
|
||||||
format: wgpu::VertexFormat::Uint32,
|
format: wgpu::VertexFormat::Uint32x2,
|
||||||
|
},
|
||||||
|
// Texture fade
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 8,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Mask
|
// Mask
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||||
shader_location: 8,
|
shader_location: 9,
|
||||||
format: wgpu::VertexFormat::Uint32x2,
|
format: wgpu::VertexFormat::Uint32x2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -232,8 +252,10 @@ pub struct ParticleInstance {
|
||||||
/// Fade this particle out over this many seconds as it expires
|
/// Fade this particle out over this many seconds as it expires
|
||||||
pub fade: f32,
|
pub fade: f32,
|
||||||
|
|
||||||
/// What sprite to use for this particle
|
/// What texture to use for this particle
|
||||||
pub sprite_index: u32,
|
pub texture_index: [u32; 2],
|
||||||
|
/// Fade parameter for texture index
|
||||||
|
pub texture_fade: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferObject for ParticleInstance {
|
impl BufferObject for ParticleInstance {
|
||||||
|
@ -290,11 +312,17 @@ impl BufferObject for ParticleInstance {
|
||||||
shader_location: 9,
|
shader_location: 9,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Sprite
|
// Texture
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
|
||||||
shader_location: 10,
|
shader_location: 10,
|
||||||
format: wgpu::VertexFormat::Uint32,
|
format: wgpu::VertexFormat::Uint32x2,
|
||||||
|
},
|
||||||
|
// Texture fade
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 11,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
mod outfitset;
|
mod outfitset;
|
||||||
mod personality;
|
mod personality;
|
||||||
mod ship;
|
mod ship;
|
||||||
|
mod shipstate;
|
||||||
|
|
||||||
pub use outfitset::*;
|
pub use outfitset::*;
|
||||||
pub use personality::*;
|
pub use personality::*;
|
||||||
pub use ship::*;
|
pub use ship::*;
|
||||||
|
pub use shipstate::*;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use galactica_content::{Content, GunPoint, Outfit, OutfitHandle, OutfitSpace, SpriteHandle};
|
use galactica_content::{GunPoint, Outfit, OutfitHandle, OutfitSpace};
|
||||||
|
|
||||||
/// Possible outcomes when adding an outfit
|
/// Possible outcomes when adding an outfit
|
||||||
pub enum OutfitAddResult {
|
pub enum OutfitAddResult {
|
||||||
|
@ -61,15 +61,16 @@ pub struct OutfitSet {
|
||||||
/// if value is Some, this point is taken.
|
/// if value is Some, this point is taken.
|
||||||
gun_points: HashMap<GunPoint, Option<OutfitHandle>>,
|
gun_points: HashMap<GunPoint, Option<OutfitHandle>>,
|
||||||
|
|
||||||
// Outfit values
|
/// Outfit values
|
||||||
// This isn't strictly necessary, but we don't want to
|
/// This isn't strictly necessary, but we don't want to
|
||||||
// re-compute this on each frame.
|
/// re-compute this on each frame.
|
||||||
engine_thrust: f32,
|
engine_thrust: f32,
|
||||||
steer_power: f32,
|
steer_power: f32,
|
||||||
shield_strength: f32,
|
shield_strength: f32,
|
||||||
|
|
||||||
// Delay, generation
|
/// All shield generators in this outfit set
|
||||||
// TODO: struct
|
// These can't be summed into one value, since each has a
|
||||||
|
// distinct delay.
|
||||||
shield_generators: Vec<ShieldGenerator>,
|
shield_generators: Vec<ShieldGenerator>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,19 +159,6 @@ impl OutfitSet {
|
||||||
|
|
||||||
return OutfitRemoveResult::Ok;
|
return OutfitRemoveResult::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: pick these better
|
|
||||||
/// Returns the flare sprite that should be shown when this
|
|
||||||
/// ship is using its thrusters
|
|
||||||
pub fn get_flare_sprite(&self, ct: &Content) -> Option<SpriteHandle> {
|
|
||||||
for i in self.outfits.keys() {
|
|
||||||
let c = ct.get_outfit(*i);
|
|
||||||
if c.engine_flare_sprite.is_some() {
|
|
||||||
return c.engine_flare_sprite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple getters to make sure nobody meddles with our internal state
|
// Simple getters to make sure nobody meddles with our internal state
|
||||||
|
|
|
@ -1,84 +1,9 @@
|
||||||
use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle};
|
use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle};
|
||||||
use nalgebra::Isometry2;
|
use nalgebra::Isometry2;
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
use rapier2d::math::Isometry;
|
|
||||||
use std::{collections::HashMap, time::Instant};
|
use std::{collections::HashMap, time::Instant};
|
||||||
|
|
||||||
use super::{OutfitSet, ShipPersonality};
|
use super::{OutfitSet, ShipAutoPilot, ShipPersonality, ShipState};
|
||||||
|
|
||||||
/// A ship autopilot.
|
|
||||||
/// An autopilot is a lightweight ShipController that
|
|
||||||
/// temporarily has control over a ship.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ShipAutoPilot {
|
|
||||||
/// No autopilot, use usual behavior.
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// Automatically arrange for landing on the given object
|
|
||||||
Landing {
|
|
||||||
/// The body we want to land on
|
|
||||||
target: SystemObjectHandle,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ship state machine.
|
|
||||||
/// Any ship we keep track of is in one of these states.
|
|
||||||
/// Dead ships don't exist---they removed once their collapse
|
|
||||||
/// sequence fully plays out.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ShipState {
|
|
||||||
/// This ship is dead, and should be removed from the game.
|
|
||||||
Dead,
|
|
||||||
|
|
||||||
/// This ship is alive and well in open space
|
|
||||||
Flying {
|
|
||||||
/// The autopilot we're using.
|
|
||||||
/// Overrides ship controller.
|
|
||||||
autopilot: ShipAutoPilot,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// This ship has been destroyed, and is playing its collapse sequence.
|
|
||||||
Collapsing,
|
|
||||||
|
|
||||||
/// This ship is landed on a planet
|
|
||||||
Landed {
|
|
||||||
/// The planet this ship is landed on
|
|
||||||
target: SystemObjectHandle,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// This ship is landing on a planet
|
|
||||||
/// (playing the animation)
|
|
||||||
Landing {
|
|
||||||
/// The planet we're landing on
|
|
||||||
target: SystemObjectHandle,
|
|
||||||
|
|
||||||
/// Our current z-coordinate
|
|
||||||
current_z: f32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// This ship is taking off from a planet
|
|
||||||
/// (playing the animation)
|
|
||||||
UnLanding {
|
|
||||||
/// The point to which we're going, in world coordinates
|
|
||||||
to_position: Isometry<f32>,
|
|
||||||
|
|
||||||
/// The planet we're taking off from
|
|
||||||
from: SystemObjectHandle,
|
|
||||||
|
|
||||||
/// Our current z-coordinate
|
|
||||||
current_z: f32,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipState {
|
|
||||||
/// What planet is this ship landed on?
|
|
||||||
pub fn landed_on(&self) -> Option<SystemObjectHandle> {
|
|
||||||
match self {
|
|
||||||
Self::Landed { target } => Some(*target),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents all attributes of a single ship
|
/// Represents all attributes of a single ship
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
use galactica_content::SystemObjectHandle;
|
||||||
|
use rapier2d::math::Isometry;
|
||||||
|
|
||||||
|
/// A ship autopilot.
|
||||||
|
/// An autopilot is a lightweight ShipController that
|
||||||
|
/// temporarily has control over a ship.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ShipAutoPilot {
|
||||||
|
/// No autopilot, use usual behavior.
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// Automatically arrange for landing on the given object
|
||||||
|
Landing {
|
||||||
|
/// The body we want to land on
|
||||||
|
target: SystemObjectHandle,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ship state machine.
|
||||||
|
/// Any ship we keep track of is in one of these states.
|
||||||
|
/// Dead ships don't exist---they removed once their collapse
|
||||||
|
/// sequence fully plays out.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ShipState {
|
||||||
|
/// This ship is dead, and should be removed from the game.
|
||||||
|
Dead,
|
||||||
|
|
||||||
|
/// This ship is alive and well in open space
|
||||||
|
Flying {
|
||||||
|
/// The autopilot we're using.
|
||||||
|
/// Overrides ship controller.
|
||||||
|
autopilot: ShipAutoPilot,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// This ship has been destroyed, and is playing its collapse sequence.
|
||||||
|
Collapsing,
|
||||||
|
|
||||||
|
/// This ship is landed on a planet
|
||||||
|
Landed {
|
||||||
|
/// The planet this ship is landed on
|
||||||
|
target: SystemObjectHandle,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// This ship is landing on a planet
|
||||||
|
/// (playing the animation)
|
||||||
|
Landing {
|
||||||
|
/// The planet we're landing on
|
||||||
|
target: SystemObjectHandle,
|
||||||
|
|
||||||
|
/// Our current z-coordinate
|
||||||
|
current_z: f32,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// This ship is taking off from a planet
|
||||||
|
/// (playing the animation)
|
||||||
|
UnLanding {
|
||||||
|
/// The point to which we're going, in world coordinates
|
||||||
|
to_position: Isometry<f32>,
|
||||||
|
|
||||||
|
/// The planet we're taking off from
|
||||||
|
from: SystemObjectHandle,
|
||||||
|
|
||||||
|
/// Our current z-coordinate
|
||||||
|
current_z: f32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShipState {
|
||||||
|
/// What planet is this ship landed on?
|
||||||
|
pub fn landed_on(&self) -> Option<SystemObjectHandle> {
|
||||||
|
match self {
|
||||||
|
Self::Landed { target } => Some(*target),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use galactica_content::{FactionHandle, Projectile};
|
use galactica_content::{AnimAutomaton, AnimationState, Content, FactionHandle, Projectile};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
||||||
|
|
||||||
|
@ -8,6 +8,9 @@ pub struct PhysProjectile {
|
||||||
/// This projectile's game data
|
/// This projectile's game data
|
||||||
pub content: Projectile,
|
pub content: Projectile,
|
||||||
|
|
||||||
|
/// This projectile's sprite animation state
|
||||||
|
anim: AnimAutomaton,
|
||||||
|
|
||||||
/// The remaining lifetime of this projectile, in seconds
|
/// The remaining lifetime of this projectile, in seconds
|
||||||
pub lifetime: f32,
|
pub lifetime: f32,
|
||||||
|
|
||||||
|
@ -27,7 +30,8 @@ pub struct PhysProjectile {
|
||||||
impl PhysProjectile {
|
impl PhysProjectile {
|
||||||
/// Create a new projectile
|
/// Create a new projectile
|
||||||
pub fn new(
|
pub fn new(
|
||||||
content: Projectile, // TODO: use a handle
|
ct: &Content,
|
||||||
|
content: Projectile, // TODO: use a handle?
|
||||||
rigid_body: RigidBodyHandle,
|
rigid_body: RigidBodyHandle,
|
||||||
faction: FactionHandle,
|
faction: FactionHandle,
|
||||||
collider: ColliderHandle,
|
collider: ColliderHandle,
|
||||||
|
@ -36,6 +40,7 @@ impl PhysProjectile {
|
||||||
let size_rng = content.size_rng;
|
let size_rng = content.size_rng;
|
||||||
let lifetime = content.lifetime;
|
let lifetime = content.lifetime;
|
||||||
PhysProjectile {
|
PhysProjectile {
|
||||||
|
anim: AnimAutomaton::new(ct, content.sprite),
|
||||||
rigid_body,
|
rigid_body,
|
||||||
collider,
|
collider,
|
||||||
content,
|
content,
|
||||||
|
@ -46,8 +51,9 @@ impl PhysProjectile {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process this projectile's state after `t` seconds
|
/// Process this projectile's state after `t` seconds
|
||||||
pub fn tick(&mut self, t: f32) {
|
pub fn tick(&mut self, ct: &Content, t: f32) {
|
||||||
self.lifetime -= t;
|
self.lifetime -= t;
|
||||||
|
self.anim.step(ct, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Has this projectile expired?
|
/// Has this projectile expired?
|
||||||
|
@ -55,3 +61,10 @@ impl PhysProjectile {
|
||||||
return self.lifetime < 0.0;
|
return self.lifetime < 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PhysProjectile {
|
||||||
|
/// Get this projectile's animation state
|
||||||
|
pub fn get_anim_state(&self) -> AnimationState {
|
||||||
|
self.anim.get_texture_idx()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use galactica_content::{Content, FactionHandle, ShipHandle};
|
use galactica_content::{
|
||||||
|
AnimAutomaton, AnimationState, Content, EnginePoint, FactionHandle, OutfitHandle, ShipHandle,
|
||||||
|
};
|
||||||
use nalgebra::{point, vector, Rotation2, Vector2};
|
use nalgebra::{point, vector, Rotation2, Vector2};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::{
|
use rapier2d::{
|
||||||
|
@ -51,7 +53,13 @@ pub struct PhysSimShip {
|
||||||
pub collider: ColliderHandle,
|
pub collider: ColliderHandle,
|
||||||
|
|
||||||
/// This ship's game data
|
/// This ship's game data
|
||||||
pub data: ShipData,
|
pub(crate) data: ShipData,
|
||||||
|
|
||||||
|
/// This ship's sprite animation state
|
||||||
|
anim: AnimAutomaton,
|
||||||
|
|
||||||
|
/// Animation state for each of this ship's engines
|
||||||
|
engine_anim: Vec<(EnginePoint, AnimAutomaton)>,
|
||||||
|
|
||||||
/// This ship's controls
|
/// This ship's controls
|
||||||
pub(crate) controls: ShipControls,
|
pub(crate) controls: ShipControls,
|
||||||
|
@ -72,9 +80,11 @@ impl PhysSimShip {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ship_ct = ct.get_ship(handle);
|
let ship_ct = ct.get_ship(handle);
|
||||||
PhysSimShip {
|
PhysSimShip {
|
||||||
|
anim: AnimAutomaton::new(ct, ship_ct.sprite),
|
||||||
rigid_body,
|
rigid_body,
|
||||||
collider,
|
collider,
|
||||||
data: ShipData::new(ct, handle, faction, personality),
|
data: ShipData::new(ct, handle, faction, personality),
|
||||||
|
engine_anim: Vec::new(),
|
||||||
controls: ShipControls::new(),
|
controls: ShipControls::new(),
|
||||||
collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)),
|
collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)),
|
||||||
}
|
}
|
||||||
|
@ -88,6 +98,18 @@ impl PhysSimShip {
|
||||||
collider: &mut Collider,
|
collider: &mut Collider,
|
||||||
) {
|
) {
|
||||||
self.data.step(res.t);
|
self.data.step(res.t);
|
||||||
|
self.anim.step(res.ct, res.t);
|
||||||
|
|
||||||
|
if self.controls.thrust {
|
||||||
|
for (_, e) in &mut self.engine_anim {
|
||||||
|
e.step(res.ct, res.t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (_, e) in &mut self.engine_anim {
|
||||||
|
e.reset(res.ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self.data.get_state() {
|
match self.data.get_state() {
|
||||||
ShipState::Collapsing { .. } => {
|
ShipState::Collapsing { .. } => {
|
||||||
// Borrow checker hack, so we may pass self.data
|
// Borrow checker hack, so we may pass self.data
|
||||||
|
@ -197,9 +219,69 @@ impl PhysSimShip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Public mutable
|
||||||
|
impl PhysSimShip {
|
||||||
|
/// Re-create this ship's engine flare animations
|
||||||
|
/// Should be called whenever we change outfits
|
||||||
|
fn update_flares(&mut self, ct: &Content) {
|
||||||
|
// TODO: better way to pick flare sprite
|
||||||
|
let mut flare = None;
|
||||||
|
for (h, _) in self.data.get_outfits().iter_outfits() {
|
||||||
|
let c = ct.get_outfit(*h);
|
||||||
|
if c.engine_flare_sprite.is_some() {
|
||||||
|
flare = c.engine_flare_sprite;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flare.is_none() {
|
||||||
|
self.engine_anim = Vec::new();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let flare = flare.unwrap();
|
||||||
|
self.engine_anim = ct
|
||||||
|
.get_ship(self.data.get_content())
|
||||||
|
.engines
|
||||||
|
.iter()
|
||||||
|
.map(|e| (e.clone(), AnimAutomaton::new(ct, flare)))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add one outfit to this ship
|
||||||
|
pub fn add_outfit(&mut self, ct: &Content, o: OutfitHandle) {
|
||||||
|
self.data.add_outfit(ct.get_outfit(o));
|
||||||
|
self.update_flares(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add many outfits to this ship
|
||||||
|
pub fn add_outfits(&mut self, ct: &Content, outfits: impl IntoIterator<Item = OutfitHandle>) {
|
||||||
|
for o in outfits {
|
||||||
|
self.data.add_outfit(ct.get_outfit(o));
|
||||||
|
}
|
||||||
|
self.update_flares(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public immutable
|
||||||
impl PhysSimShip {
|
impl PhysSimShip {
|
||||||
/// Get this ship's control state
|
/// Get this ship's control state
|
||||||
pub fn get_controls(&self) -> &ShipControls {
|
pub fn get_controls(&self) -> &ShipControls {
|
||||||
&self.controls
|
&self.controls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get this ship's engine animations
|
||||||
|
pub fn iter_engine_anim(&self) -> impl Iterator<Item = &(EnginePoint, AnimAutomaton)> {
|
||||||
|
self.engine_anim.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's animation state
|
||||||
|
pub fn get_anim_state(&self) -> AnimationState {
|
||||||
|
self.anim.get_texture_idx()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's game data struct
|
||||||
|
pub fn get_data(&self) -> &ShipData {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,6 +243,7 @@ impl PhysSim {
|
||||||
self.projectiles.insert(
|
self.projectiles.insert(
|
||||||
collider.clone(),
|
collider.clone(),
|
||||||
PhysProjectile::new(
|
PhysProjectile::new(
|
||||||
|
res.ct,
|
||||||
outfit.projectile.clone(),
|
outfit.projectile.clone(),
|
||||||
rigid_body,
|
rigid_body,
|
||||||
ship.data.get_faction(),
|
ship.data.get_faction(),
|
||||||
|
@ -290,7 +291,7 @@ impl PhysSim {
|
||||||
// Delete projectiles
|
// Delete projectiles
|
||||||
let mut to_remove = Vec::new();
|
let mut to_remove = Vec::new();
|
||||||
for (c, p) in &mut self.projectiles {
|
for (c, p) in &mut self.projectiles {
|
||||||
p.tick(res.t);
|
p.tick(res.ct, res.t);
|
||||||
if p.is_expired() {
|
if p.is_expired() {
|
||||||
to_remove.push(*c);
|
to_remove.push(*c);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue