Compare commits
No commits in common. "eca7e7f5e6469a689fa8d1b17d7c3e3a70937617" and "24f46a2471d3bac9c782d66b0f221d3e34cf3d10" have entirely different histories.
eca7e7f5e6
...
24f46a2471
|
@ -76,9 +76,7 @@ log = "0.4.20"
|
||||||
log4rs = { version = "1.2.0", features = ["console_appender"] }
|
log4rs = { version = "1.2.0", features = ["console_appender"] }
|
||||||
rhai = { version = "1.17.0", features = [
|
rhai = { version = "1.17.0", features = [
|
||||||
"f32_float",
|
"f32_float",
|
||||||
"only_i32",
|
|
||||||
"metadata",
|
"metadata",
|
||||||
"sync",
|
"sync",
|
||||||
"no_custom_syntax",
|
"no_custom_syntax",
|
||||||
"no_closure",
|
|
||||||
] }
|
] }
|
||||||
|
|
1
TODO.md
1
TODO.md
|
@ -13,7 +13,6 @@
|
||||||
- Scriptable renderscene: script decides which parts to show
|
- Scriptable renderscene: script decides which parts to show
|
||||||
- No UI zoom scroll
|
- No UI zoom scroll
|
||||||
- Preserve aspect for icons
|
- Preserve aspect for icons
|
||||||
- Check game version in config
|
|
||||||
|
|
||||||
|
|
||||||
## Small jobs
|
## Small jobs
|
||||||
|
|
|
@ -39,8 +39,6 @@ zoom_max = 2000.0
|
||||||
|
|
||||||
# TODO: move to user config file
|
# TODO: move to user config file
|
||||||
ui_scale = 2
|
ui_scale = 2
|
||||||
|
ui_landed_scene = "ui/landed.rhai"
|
||||||
start_ui_scene = "flying"
|
ui_flying_scene = "ui/flying.rhai"
|
||||||
ui_scene.landed = "ui/landed.rhai"
|
ui_outfitter_scene = "ui/outfitter.rhai"
|
||||||
ui_scene.flying = "ui/flying.rhai"
|
|
||||||
ui_scene.outfitter = "ui/outfitter.rhai"
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ one planet has a greater population than a hundred planets elsewhere. As a resul
|
||||||
settlements of less than a million are grouped together into planetary districts that
|
settlements of less than a million are grouped together into planetary districts that
|
||||||
elect a single representative between them - a source of much frustration in the frontier worlds.
|
elect a single representative between them - a source of much frustration in the frontier worlds.
|
||||||
"""
|
"""
|
||||||
object.earth.image = "ui::landscape::test"
|
|
||||||
|
|
||||||
|
|
||||||
object.luna.sprite = "planet::luna"
|
object.luna.sprite = "planet::luna"
|
||||||
|
|
|
@ -1,55 +1,3 @@
|
||||||
fn config() {
|
fn init(state) { return []; }
|
||||||
let config = SceneConfig();
|
fn hover(element, hover_state) {}
|
||||||
config.show_starfield(true);
|
fn click(element, click_state) {}
|
||||||
config.show_phys(true);
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(state) {
|
|
||||||
let ring = SpriteBuilder(
|
|
||||||
"ring",
|
|
||||||
"ui::status",
|
|
||||||
Rect(
|
|
||||||
-5.0, -5.0, 100.0, 100.0,
|
|
||||||
SpriteAnchor::NorthEast,
|
|
||||||
SpriteAnchor::NorthEast
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let shield = RadialBuilder(
|
|
||||||
"shield", 2.5,
|
|
||||||
Color(0.3, 0.6, 0.8, 1.0),
|
|
||||||
Rect(
|
|
||||||
-9.5, -9.5, 91.0, 91.0,
|
|
||||||
SpriteAnchor::NorthEast,
|
|
||||||
SpriteAnchor::NorthEast
|
|
||||||
)
|
|
||||||
);
|
|
||||||
shield.set_progress(0.2);
|
|
||||||
|
|
||||||
let hull = RadialBuilder(
|
|
||||||
"hull", 2.5,
|
|
||||||
Color(0.8, 0.7, 0.5, 1.0),
|
|
||||||
Rect(
|
|
||||||
-13.5, -13.5, 83.0, 83.0,
|
|
||||||
SpriteAnchor::NorthEast,
|
|
||||||
SpriteAnchor::NorthEast
|
|
||||||
)
|
|
||||||
);
|
|
||||||
hull.set_progress(0.4);
|
|
||||||
|
|
||||||
return [
|
|
||||||
ring,
|
|
||||||
shield,
|
|
||||||
hull
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event(state, event) {
|
|
||||||
if type_of(event) == "PlayerShipStateEvent" {
|
|
||||||
if state.player_ship().is_landed() {
|
|
||||||
return SceneAction::GoTo("landed");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,5 @@
|
||||||
fn config() {
|
|
||||||
let config = SceneConfig();
|
|
||||||
config.show_starfield(true);
|
|
||||||
config.show_phys(false);
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(state) {
|
fn init(state) {
|
||||||
let player = state.player_ship();
|
|
||||||
|
|
||||||
let frame = SpriteBuilder(
|
let frame = SpriteBuilder(
|
||||||
"frame",
|
"frame",
|
||||||
"ui::planet",
|
"ui::planet",
|
||||||
|
@ -20,13 +12,7 @@ fn init(state) {
|
||||||
|
|
||||||
let landscape = SpriteBuilder(
|
let landscape = SpriteBuilder(
|
||||||
"landscape",
|
"landscape",
|
||||||
{
|
state.planet_landscape,
|
||||||
if player.is_landed() {
|
|
||||||
player.landed_on().image();
|
|
||||||
} else {
|
|
||||||
"";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Rect(
|
Rect(
|
||||||
-180.0, 142.0, 274.0, 135.0,
|
-180.0, 142.0, 274.0, 135.0,
|
||||||
SpriteAnchor::NorthWest,
|
SpriteAnchor::NorthWest,
|
||||||
|
@ -54,66 +40,32 @@ fn init(state) {
|
||||||
SpriteAnchor::Center
|
SpriteAnchor::Center
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if player.is_landed() {
|
title.set_text(state.planet_name);
|
||||||
title.set_text(player.landed_on().name());
|
|
||||||
} else {
|
|
||||||
title.set_text("");
|
|
||||||
}
|
|
||||||
|
|
||||||
let desc = TextBoxBuilder(
|
|
||||||
"desc",
|
|
||||||
7.5, 8.0, TextBoxFont::SansSerif, TextBoxJustify::Left,
|
|
||||||
Rect(
|
|
||||||
-178.92, -20.3, 343.0, 81.467,
|
|
||||||
SpriteAnchor::NorthWest,
|
|
||||||
SpriteAnchor::Center
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if player.is_landed() {
|
|
||||||
desc.set_text(player.landed_on().desc());
|
|
||||||
} else {
|
|
||||||
desc.set_text("");
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
button,
|
button,
|
||||||
landscape,
|
landscape,
|
||||||
frame,
|
frame,
|
||||||
title,
|
title,
|
||||||
desc,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(state, event) {
|
fn hover(element, hover_state) {
|
||||||
if type_of(event) == "MouseHoverEvent" {
|
|
||||||
let element = event.element();
|
|
||||||
if element.has_name("button") {
|
if element.has_name("button") {
|
||||||
if event.is_enter() {
|
if hover_state {
|
||||||
element.take_edge("on:top", 0.1);
|
element.take_edge("on:top", 0.1);
|
||||||
} else {
|
} else {
|
||||||
element.take_edge("off:top", 0.1);
|
element.take_edge("off:top", 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
fn click(element, click_state) {
|
||||||
if type_of(event) == "MouseClickEvent" {
|
if !click_state {
|
||||||
if !event.is_down() {
|
|
||||||
return SceneAction::None;
|
return SceneAction::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let element = event.element();
|
|
||||||
if element.has_name("button") {
|
if element.has_name("button") {
|
||||||
return SceneAction::GoTo("outfitter");
|
return SceneAction::SceneOutfitter;
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if type_of(event) == "PlayerShipStateEvent" {
|
|
||||||
if !state.player_ship().is_landed() {
|
|
||||||
return SceneAction::GoTo("flying");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,3 @@
|
||||||
fn config() {
|
|
||||||
let config = SceneConfig();
|
|
||||||
config.show_starfield(true);
|
|
||||||
config.show_phys(false);
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(state) {
|
fn init(state) {
|
||||||
let se_box = SpriteBuilder(
|
let se_box = SpriteBuilder(
|
||||||
|
@ -25,7 +19,7 @@ fn init(state) {
|
||||||
SpriteAnchor::SouthWest
|
SpriteAnchor::SouthWest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
exit_text.set_text("Exit");
|
exit_text.set_text(state.planet_name);
|
||||||
|
|
||||||
let exit_button = SpriteBuilder(
|
let exit_button = SpriteBuilder(
|
||||||
"exit_button",
|
"exit_button",
|
||||||
|
@ -70,7 +64,7 @@ fn init(state) {
|
||||||
SpriteAnchor::NorthWest
|
SpriteAnchor::NorthWest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
ship_name.set_text("Hyperion");
|
ship_name.set_text(state.planet_name);
|
||||||
|
|
||||||
let ship_type = TextBoxBuilder(
|
let ship_type = TextBoxBuilder(
|
||||||
"ship_type",
|
"ship_type",
|
||||||
|
@ -81,11 +75,7 @@ fn init(state) {
|
||||||
SpriteAnchor::NorthWest
|
SpriteAnchor::NorthWest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if state.player_ship().is_some() {
|
ship_type.set_text(state.planet_name);
|
||||||
ship_type.set_text(state.player_ship().name());
|
|
||||||
} else {
|
|
||||||
ship_type.set_text("ERR: SHIP IS NONE");
|
|
||||||
}
|
|
||||||
|
|
||||||
let ship_stats = TextBoxBuilder(
|
let ship_stats = TextBoxBuilder(
|
||||||
"ship_stats",
|
"ship_stats",
|
||||||
|
@ -96,7 +86,11 @@ fn init(state) {
|
||||||
SpriteAnchor::NorthWest,
|
SpriteAnchor::NorthWest,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
ship_stats.set_text("Earth");
|
ship_stats.set_text(state.planet_name);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,7 +123,7 @@ fn init(state) {
|
||||||
SpriteAnchor::NorthEast,
|
SpriteAnchor::NorthEast,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
outfit_name.set_text("Earth");
|
outfit_name.set_text(state.planet_name);
|
||||||
|
|
||||||
let outfit_desc = TextBoxBuilder(
|
let outfit_desc = TextBoxBuilder(
|
||||||
"outfit_desc",
|
"outfit_desc",
|
||||||
|
@ -140,7 +134,7 @@ fn init(state) {
|
||||||
SpriteAnchor::NorthEast,
|
SpriteAnchor::NorthEast,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
outfit_desc.set_text("Earth");
|
outfit_desc.set_text(state.planet_name);
|
||||||
|
|
||||||
let outfit_stats = TextBoxBuilder(
|
let outfit_stats = TextBoxBuilder(
|
||||||
"outfit_stats",
|
"outfit_stats",
|
||||||
|
@ -151,7 +145,7 @@ fn init(state) {
|
||||||
SpriteAnchor::NorthEast,
|
SpriteAnchor::NorthEast,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
outfit_stats.set_text("Earth");
|
outfit_stats.set_text(state.planet_name);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
ship_bg,
|
ship_bg,
|
||||||
|
@ -172,37 +166,22 @@ fn init(state) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hover(element, hover_state) {
|
||||||
fn event(state, event) {
|
|
||||||
if type_of(event) == "MouseHoverEvent" {
|
|
||||||
let element = event.element();
|
|
||||||
if element.has_name("exit_button") {
|
if element.has_name("exit_button") {
|
||||||
if event.is_enter() {
|
if hover_state {
|
||||||
element.take_edge("on:top", 0.1);
|
element.take_edge("on:top", 0.1);
|
||||||
} else {
|
} else {
|
||||||
element.take_edge("off:top", 0.1);
|
element.take_edge("off:top", 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
fn click(element, click_state) {
|
||||||
if type_of(event) == "MouseClickEvent" {
|
if !click_state {
|
||||||
if !event.is_down() {
|
|
||||||
return SceneAction::None;
|
return SceneAction::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let element = event.element();
|
|
||||||
if element.has_name("exit_button") {
|
if element.has_name("exit_button") {
|
||||||
return SceneAction::GoTo("landed");
|
return SceneAction::SceneLanded;
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if type_of(event) == "PlayerShipStateEvent" {
|
|
||||||
if !state.player_ship().is_landed() {
|
|
||||||
return SceneAction::GoTo("flying");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, num::NonZeroU32, path::PathBuf};
|
use std::{num::NonZeroU32, path::PathBuf};
|
||||||
|
|
||||||
use rhai::AST;
|
use rhai::AST;
|
||||||
|
|
||||||
|
@ -7,10 +7,7 @@ pub(crate) mod syntax {
|
||||||
use galactica_packer::SpriteAtlas;
|
use galactica_packer::SpriteAtlas;
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::path::{Path, PathBuf};
|
||||||
collections::HashMap,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -23,8 +20,9 @@ pub(crate) mod syntax {
|
||||||
pub zoom_min: f32,
|
pub zoom_min: f32,
|
||||||
pub zoom_max: f32,
|
pub zoom_max: f32,
|
||||||
pub ui_scale: f32,
|
pub ui_scale: f32,
|
||||||
pub ui_scene: HashMap<String, PathBuf>,
|
pub ui_flying_scene: PathBuf,
|
||||||
pub start_ui_scene: String,
|
pub ui_landed_scene: PathBuf,
|
||||||
|
pub ui_outfitter_scene: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -61,19 +59,18 @@ pub(crate) mod syntax {
|
||||||
};
|
};
|
||||||
|
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
let mut ui_scenes = HashMap::new();
|
let ui_landed_scene = engine
|
||||||
for (n, p) in self.ui_scene {
|
.compile_file(content_root.join(self.ui_landed_scene))
|
||||||
ui_scenes.insert(
|
.with_context(|| format!("while loading `landed` scene"))
|
||||||
n.clone(),
|
.with_context(|| format!("while loading config"))?;
|
||||||
engine
|
let ui_outfitter_scene = engine
|
||||||
.compile_file(content_root.join(p))
|
.compile_file(content_root.join(self.ui_outfitter_scene))
|
||||||
.with_context(|| format!("while loading scene script `{}`", n))?,
|
.with_context(|| format!("while loading `outfitter` scene"))
|
||||||
);
|
.with_context(|| format!("while loading config"))?;
|
||||||
}
|
let ui_flying_scene = engine
|
||||||
|
.compile_file(content_root.join(self.ui_flying_scene))
|
||||||
if !ui_scenes.contains_key(&self.start_ui_scene) {
|
.with_context(|| format!("while loading `flying` scene"))
|
||||||
bail!("starting ui scene `{}` doesn't exist", self.start_ui_scene)
|
.with_context(|| format!("while loading config"))?;
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(super::Config {
|
return Ok(super::Config {
|
||||||
sprite_root: asset_root.join(self.sprite_root),
|
sprite_root: asset_root.join(self.sprite_root),
|
||||||
|
@ -101,8 +98,9 @@ pub(crate) mod syntax {
|
||||||
zoom_max: self.zoom_max,
|
zoom_max: self.zoom_max,
|
||||||
zoom_min: self.zoom_min,
|
zoom_min: self.zoom_min,
|
||||||
ui_scale: self.ui_scale,
|
ui_scale: self.ui_scale,
|
||||||
ui_scenes,
|
ui_landed_scene,
|
||||||
start_ui_scene: self.start_ui_scene,
|
ui_flying_scene,
|
||||||
|
ui_outfitter_scene,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,10 +187,12 @@ pub struct Config {
|
||||||
/// Ui scale factor
|
/// Ui scale factor
|
||||||
pub ui_scale: f32,
|
pub ui_scale: f32,
|
||||||
|
|
||||||
/// Ui scene scripts
|
/// Ui landed scene script
|
||||||
pub ui_scenes: HashMap<String, AST>,
|
pub ui_landed_scene: AST,
|
||||||
|
|
||||||
/// The UI scene we start in.
|
/// Ui flying scene script
|
||||||
/// This is guaranteed to be a key in ui_scenes.
|
pub ui_flying_scene: AST,
|
||||||
pub start_ui_scene: String,
|
|
||||||
|
/// Ui outfitter scene script
|
||||||
|
pub ui_outfitter_scene: AST,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use nalgebra::{Point2, Point3};
|
use nalgebra::{Point2, Point3};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
@ -31,7 +31,6 @@ pub(crate) mod syntax {
|
||||||
pub landable: Option<bool>,
|
pub landable: Option<bool>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub desc: Option<String>,
|
pub desc: Option<String>,
|
||||||
pub image: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -127,13 +126,10 @@ pub struct SystemObject {
|
||||||
pub landable: bool,
|
pub landable: bool,
|
||||||
|
|
||||||
/// The display name of this object
|
/// The display name of this object
|
||||||
pub name: Option<String>,
|
pub name: String,
|
||||||
|
|
||||||
/// The description of this object (shown on landed ui)
|
/// The description of this object
|
||||||
pub desc: Option<String>,
|
pub desc: String,
|
||||||
|
|
||||||
/// This object's image (shown on landed ui)
|
|
||||||
pub image: Option<SpriteHandle>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for resolve_position, never called on its own.
|
/// Helper function for resolve_position, never called on its own.
|
||||||
|
@ -224,43 +220,8 @@ impl crate::Build for System {
|
||||||
Some(t) => *t,
|
Some(t) => *t,
|
||||||
};
|
};
|
||||||
|
|
||||||
let image_handle = match &obj.image {
|
|
||||||
Some(x) => match content.sprite_index.get(x) {
|
|
||||||
None => bail!(
|
|
||||||
"In system `{}`: sprite `{}` doesn't exist",
|
|
||||||
system_name,
|
|
||||||
obj.sprite
|
|
||||||
),
|
|
||||||
Some(t) => Some(*t),
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if obj.landable.unwrap_or(false) {
|
|
||||||
if obj.name.is_none() {
|
|
||||||
return Err(anyhow!("if an object is landable, it must have a name"))
|
|
||||||
.with_context(|| format!("in object labeled `{}`", label))
|
|
||||||
.with_context(|| format!("in system `{}`", system_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.desc.is_none() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"if an object is landable, it must have a description"
|
|
||||||
))
|
|
||||||
.with_context(|| format!("in object labeled `{}`", label))
|
|
||||||
.with_context(|| format!("in system `{}`", system_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.image.is_none() {
|
|
||||||
return Err(anyhow!("if an object is landable, it must have an image"))
|
|
||||||
.with_context(|| format!("in object labeled `{}`", label))
|
|
||||||
.with_context(|| format!("in system `{}`", system_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
objects.push(SystemObject {
|
objects.push(SystemObject {
|
||||||
sprite: sprite_handle,
|
sprite: sprite_handle,
|
||||||
image: image_handle,
|
|
||||||
pos: resolve_position(&system.object, &obj, cycle_detector)
|
pos: resolve_position(&system.object, &obj, cycle_detector)
|
||||||
.with_context(|| format!("in object {:#?}", label))?,
|
.with_context(|| format!("in object {:#?}", label))?,
|
||||||
size: obj.size,
|
size: obj.size,
|
||||||
|
@ -270,13 +231,19 @@ impl crate::Build for System {
|
||||||
body_index: 0,
|
body_index: 0,
|
||||||
},
|
},
|
||||||
landable: obj.landable.unwrap_or(false),
|
landable: obj.landable.unwrap_or(false),
|
||||||
name: obj.name.as_ref().map(|x| x.clone()),
|
name: obj
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.clone())
|
||||||
|
.unwrap_or("".to_string()),
|
||||||
|
|
||||||
// TODO: better linebreaks, handle double spaces
|
// TODO: better linebreaks, handle double spaces
|
||||||
// Tabs
|
// Tabs
|
||||||
desc: obj
|
desc: obj
|
||||||
.desc
|
.desc
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|x| x.replace("\n", " ").replace("<br>", "\n")),
|
.map(|x| x.replace("\n", " ").replace("<br>", "\n"))
|
||||||
|
.unwrap_or("".to_string()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::{bail, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use galactica_content::{Content, SystemHandle};
|
use galactica_content::{Content, SystemHandle};
|
||||||
use galactica_playeragent::{PlayerAgent, PlayerStatus};
|
use galactica_playeragent::{PlayerAgent, PlayerStatus};
|
||||||
use galactica_render::RenderInput;
|
use galactica_render::{RenderInput, RenderScenes};
|
||||||
use galactica_system::{
|
use galactica_system::{
|
||||||
data::ShipState,
|
data::ShipState,
|
||||||
phys::{PhysImage, PhysSimShipHandle},
|
phys::{PhysImage, PhysSimShipHandle},
|
||||||
|
@ -121,20 +121,25 @@ fn try_main() -> Result<()> {
|
||||||
|
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, content.clone()))?;
|
let mut gpu = pollster::block_on(galactica_render::GPUState::new(
|
||||||
|
window,
|
||||||
|
content.clone(),
|
||||||
|
RenderScenes::System,
|
||||||
|
))?;
|
||||||
gpu.init(&content);
|
gpu.init(&content);
|
||||||
|
|
||||||
// TODO: don't clone content
|
// TODO: don't clone content
|
||||||
let mut game = game::Game::new(content.clone());
|
let mut game = game::Game::new(content.clone());
|
||||||
let p = game.make_player();
|
let p = game.make_player();
|
||||||
|
|
||||||
let mut player = Rc::new(PlayerAgent::new(p.0));
|
let mut player = PlayerAgent::new(p.0);
|
||||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
player.set_camera_aspect(
|
||||||
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
|
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut phys_img = Rc::new(PhysImage::new());
|
let mut phys_img = PhysImage::new();
|
||||||
let mut last_run = Instant::now();
|
let mut last_run = Instant::now();
|
||||||
|
let mut was_landed = false;
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
match event {
|
match event {
|
||||||
|
@ -144,8 +149,8 @@ fn try_main() -> Result<()> {
|
||||||
camera_zoom: player.camera.zoom,
|
camera_zoom: player.camera.zoom,
|
||||||
current_time: game.get_current_time(),
|
current_time: game.get_current_time(),
|
||||||
ct: content.clone(),
|
ct: content.clone(),
|
||||||
phys_img: phys_img.clone(),
|
phys_img: &phys_img,
|
||||||
player: player.clone(),
|
player: &player,
|
||||||
time_since_last_run: last_run.elapsed().as_secs_f32(),
|
time_since_last_run: last_run.elapsed().as_secs_f32(),
|
||||||
current_system: SystemHandle { index: 0 },
|
current_system: SystemHandle { index: 0 },
|
||||||
timing: game.get_timing().clone(),
|
timing: game.get_timing().clone(),
|
||||||
|
@ -162,9 +167,9 @@ fn try_main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::MainEventsCleared => {
|
Event::MainEventsCleared => {
|
||||||
game.update_player_controls(Rc::get_mut(&mut player).unwrap());
|
game.update_player_controls(&mut player);
|
||||||
game.step(&phys_img);
|
game.step(&phys_img);
|
||||||
game.update_image(Rc::get_mut(&mut phys_img).unwrap());
|
game.update_image(&mut phys_img);
|
||||||
|
|
||||||
// TODO: clean up
|
// TODO: clean up
|
||||||
let player_status = {
|
let player_status = {
|
||||||
|
@ -175,9 +180,21 @@ fn try_main() -> Result<()> {
|
||||||
ShipState::Landing { .. }
|
ShipState::Landing { .. }
|
||||||
| ShipState::UnLanding { .. }
|
| ShipState::UnLanding { .. }
|
||||||
| ShipState::Collapsing { .. }
|
| ShipState::Collapsing { .. }
|
||||||
| ShipState::Flying { .. } => Some(*o.rigidbody.translation()),
|
| ShipState::Flying { .. } => {
|
||||||
|
if was_landed {
|
||||||
|
was_landed = false;
|
||||||
|
gpu.set_scene(RenderScenes::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(*o.rigidbody.translation())
|
||||||
|
}
|
||||||
|
|
||||||
ShipState::Landed { target } => {
|
ShipState::Landed { target } => {
|
||||||
|
if !was_landed {
|
||||||
|
was_landed = true;
|
||||||
|
gpu.set_scene(RenderScenes::Landed);
|
||||||
|
}
|
||||||
|
|
||||||
let b = content.get_system_object(*target);
|
let b = content.get_system_object(*target);
|
||||||
Some(Vector2::new(b.pos.x, b.pos.y))
|
Some(Vector2::new(b.pos.x, b.pos.y))
|
||||||
}
|
}
|
||||||
|
@ -193,10 +210,9 @@ fn try_main() -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// This must be updated BEFORE rendering!
|
// This must be updated BEFORE rendering!
|
||||||
Rc::get_mut(&mut player)
|
player.step(&content, player_status);
|
||||||
.unwrap()
|
|
||||||
.step(&content, player_status);
|
player.input.clear_inputs();
|
||||||
Rc::get_mut(&mut player).unwrap().input.clear_inputs();
|
|
||||||
gpu.window().request_redraw();
|
gpu.window().request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,39 +233,27 @@ fn try_main() -> Result<()> {
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
Rc::get_mut(&mut player)
|
player.input.process_key(state, key);
|
||||||
.unwrap()
|
|
||||||
.input
|
|
||||||
.process_key(state, key);
|
|
||||||
}
|
}
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
Rc::get_mut(&mut player)
|
player.input.process_mouse(position);
|
||||||
.unwrap()
|
|
||||||
.input
|
|
||||||
.process_mouse(position);
|
|
||||||
}
|
}
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
Rc::get_mut(&mut player)
|
player.input.process_click(state, button);
|
||||||
.unwrap()
|
|
||||||
.input
|
|
||||||
.process_click(state, button);
|
|
||||||
}
|
}
|
||||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||||
Rc::get_mut(&mut player)
|
player.input.process_scroll(delta, phase);
|
||||||
.unwrap()
|
|
||||||
.input
|
|
||||||
.process_scroll(delta, phase);
|
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(_) => {
|
WindowEvent::Resized(_) => {
|
||||||
gpu.resize(&content);
|
gpu.resize(&content);
|
||||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
player.set_camera_aspect(
|
||||||
gpu.window().inner_size().width as f32
|
gpu.window().inner_size().width as f32
|
||||||
/ gpu.window().inner_size().height as f32,
|
/ gpu.window().inner_size().height as f32,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
WindowEvent::ScaleFactorChanged { .. } => {
|
WindowEvent::ScaleFactorChanged { .. } => {
|
||||||
gpu.resize(&content);
|
gpu.resize(&content);
|
||||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
player.set_camera_aspect(
|
||||||
gpu.window().inner_size().width as f32
|
gpu.window().inner_size().width as f32
|
||||||
/ gpu.window().inner_size().height as f32,
|
/ gpu.window().inner_size().height as f32,
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,6 @@ use winit::{
|
||||||
event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode},
|
event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InputStatus {
|
pub struct InputStatus {
|
||||||
// Parameters
|
// Parameters
|
||||||
scroll_speed: f32,
|
scroll_speed: f32,
|
||||||
|
|
|
@ -25,7 +25,6 @@ impl PlayerSelection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PlayerAgent {
|
pub struct PlayerAgent {
|
||||||
/// Which ship this player is controlling
|
/// Which ship this player is controlling
|
||||||
pub ship: Option<ColliderHandle>,
|
pub ship: Option<ColliderHandle>,
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Given an anchored position and sprite dimensions,
|
||||||
|
// return the translation that should be applied on
|
||||||
|
// vertex coordinates
|
||||||
|
fn anchor(
|
||||||
|
anchor: u32, // Anchor index
|
||||||
|
position: vec2<f32>, // Anchored position
|
||||||
|
dim: vec2<f32>, // Sprite dimensions (width, height)
|
||||||
|
) -> vec2<f32> {
|
||||||
|
|
||||||
|
// TODO: remove, do in ui rust lib
|
||||||
|
var trans: vec2<f32> = vec2(0.0, 0.0);
|
||||||
|
let window_dim = (
|
||||||
|
vec2(global_data.window_size_w, global_data.window_size_h) /
|
||||||
|
global_data.window_scale
|
||||||
|
);
|
||||||
|
|
||||||
|
if anchor == 0u { // NW C (screen anchor, sprite anchor)
|
||||||
|
trans += vec2(-window_dim.x, window_dim.y) / 2.0; // origin
|
||||||
|
trans += vec2(0.0, 0.0) / 2.0; // offset
|
||||||
|
} else if anchor == 1u { // NW NW
|
||||||
|
trans += vec2(-window_dim.x, window_dim.y) / 2.0;
|
||||||
|
trans += vec2(dim.x, -dim.y) / 2.0;
|
||||||
|
} else if anchor == 2u { // NW NE
|
||||||
|
trans += vec2(-window_dim.x, window_dim.y) / 2.0;
|
||||||
|
trans += vec2(-dim.x, -dim.y) / 2.0;
|
||||||
|
} else if anchor == 3u { // NW SW
|
||||||
|
trans += vec2(-window_dim.x, window_dim.y) / 2.0;
|
||||||
|
trans += vec2(dim.x, dim.y) / 2.0;
|
||||||
|
} else if anchor == 4u { // NW SE
|
||||||
|
trans += vec2(-window_dim.x, window_dim.y) / 2.0;
|
||||||
|
trans += vec2(-dim.x, dim.y) / 2.0;
|
||||||
|
} else if anchor == 5u { // NE NE
|
||||||
|
trans += vec2(window_dim.x, window_dim.y) / 2.0;
|
||||||
|
trans += vec2(-dim.x, -dim.y) / 2.0;
|
||||||
|
} else if anchor == 6u { // C C
|
||||||
|
trans += vec2(0.0, 0.0) / 2.0;
|
||||||
|
trans += vec2(0.0, 0.0) / 2.0;
|
||||||
|
} else if anchor == 7u { // C NW
|
||||||
|
trans += vec2(0.0, 0.0) / 2.0;
|
||||||
|
trans += vec2(dim.x, -dim.y) / 2.0;
|
||||||
|
} else { // center / center as default, since it's the most visible variant.
|
||||||
|
trans += vec2(0.0, 0.0) / 2.0;
|
||||||
|
trans += vec2(0.0, 0.0) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
trans += position;
|
||||||
|
|
||||||
|
// This renders correctly, but the offsets here are off by a factor of two.
|
||||||
|
// I'm not sure why... might be because WGPU screen coordinates are -1 to 1.
|
||||||
|
return (trans / window_dim) * 2.0;
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
// INCLUDE: global uniform header
|
// INCLUDE: global uniform header
|
||||||
|
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) position: vec2<f32>,
|
@location(2) anchor: u32,
|
||||||
@location(3) diameter: f32,
|
@location(3) position: vec2<f32>,
|
||||||
@location(4) stroke: f32,
|
@location(4) diameter: f32,
|
||||||
@location(5) angle: f32,
|
@location(5) stroke: f32,
|
||||||
@location(6) color: vec4<f32>,
|
@location(6) angle: f32,
|
||||||
|
@location(7) color: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -28,6 +29,9 @@ 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: anchor.wgsl
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertex_main(
|
fn vertex_main(
|
||||||
vertex: VertexInput,
|
vertex: VertexInput,
|
||||||
|
@ -41,18 +45,17 @@ fn vertex_main(
|
||||||
out.color = instance.color;
|
out.color = instance.color;
|
||||||
out.angle = instance.angle;
|
out.angle = instance.angle;
|
||||||
|
|
||||||
|
|
||||||
let window_dim = (
|
|
||||||
vec2(global_data.window_size_w, global_data.window_size_h) /
|
|
||||||
global_data.window_scale
|
|
||||||
);
|
|
||||||
|
|
||||||
// Center of this radial bar, in logical pixels,
|
// Center of this radial bar, in logical pixels,
|
||||||
// with (0, 0) at the center of the screen.
|
// with (0, 0) at the center of the screen.
|
||||||
out.center = (instance.position / window_dim) * (
|
out.center = anchor(
|
||||||
|
instance.anchor,
|
||||||
|
instance.position,
|
||||||
|
vec2(instance.diameter, instance.diameter)
|
||||||
|
) / 2.0 * (
|
||||||
vec2(global_data.window_size_w, global_data.window_size_h) /
|
vec2(global_data.window_size_w, global_data.window_size_h) /
|
||||||
global_data.window_scale
|
global_data.window_scale
|
||||||
);
|
);
|
||||||
|
// ^ slight correction, since anchor gives us a different result than we need here
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
// INCLUDE: global uniform header
|
// INCLUDE: global uniform header
|
||||||
|
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) position: vec2<f32>,
|
@location(2) anchor: u32,
|
||||||
@location(3) angle: f32,
|
@location(3) position: vec2<f32>,
|
||||||
@location(4) dim: vec2<f32>,
|
@location(4) angle: f32,
|
||||||
@location(5) color_transform: vec4<f32>,
|
@location(5) dim: vec2<f32>,
|
||||||
@location(6) texture_index: vec2<u32>,
|
@location(6) color_transform: vec4<f32>,
|
||||||
@location(7) texture_fade: f32,
|
@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 {
|
||||||
|
@ -33,6 +34,10 @@ 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: anchor.wgsl
|
||||||
|
|
||||||
|
|
||||||
fn transform_vertex(
|
fn transform_vertex(
|
||||||
instance: InstanceInput,
|
instance: InstanceInput,
|
||||||
vertex_position: vec3<f32>,
|
vertex_position: vec3<f32>,
|
||||||
|
@ -52,19 +57,24 @@ fn transform_vertex(
|
||||||
vertex_position.y * scale
|
vertex_position.y * scale
|
||||||
);
|
);
|
||||||
|
|
||||||
// Apply rotation (and adjust sprite angle, since sprites point north)
|
|
||||||
pos = mat2x2(
|
pos = mat2x2(
|
||||||
vec2(cos(instance.angle - 1.5708), sin(instance.angle - 1.5708)),
|
vec2(cos(instance.angle - 1.5708), sin(instance.angle - 1.5708)),
|
||||||
vec2(-sin(instance.angle - 1.5708), cos(instance.angle - 1.5708))
|
vec2(-sin(instance.angle - 1.5708), cos(instance.angle - 1.5708))
|
||||||
) * pos;
|
) * pos;
|
||||||
|
|
||||||
|
// Apply rotation (and adjust sprite angle, since sprites point north)
|
||||||
|
|
||||||
// Correct for screen aspect, preserving height
|
// Correct for screen aspect, preserving height
|
||||||
pos = vec2(
|
pos = vec2(
|
||||||
pos.x / global_data.window_aspect,
|
pos.x / global_data.window_aspect,
|
||||||
pos.y
|
pos.y
|
||||||
);
|
);
|
||||||
|
|
||||||
pos = pos + (instance.position / window_dim) * 2.0;
|
pos = pos + anchor(
|
||||||
|
instance.anchor,
|
||||||
|
instance.position,
|
||||||
|
instance.dim
|
||||||
|
);
|
||||||
|
|
||||||
return vec4<f32>(pos, 0.0, 1.0);
|
return vec4<f32>(pos, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
use std::{iter, rc::Rc};
|
use std::rc::Rc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_system::data::ShipState;
|
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
||||||
use galactica_util::to_radians;
|
use log::debug;
|
||||||
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
|
||||||
use nalgebra::{Point2, Point3};
|
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit;
|
use winit;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
globaluniform::{GlobalDataContent, GlobalUniform},
|
||||||
pipeline::PipelineBuilder,
|
pipeline::PipelineBuilder,
|
||||||
|
renderscene::{LandedScene, RenderScene, SystemScene},
|
||||||
shaderprocessor::preprocess_shader,
|
shaderprocessor::preprocess_shader,
|
||||||
starfield::Starfield,
|
starfield::Starfield,
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
ui::UiManager,
|
ui::{UiManager, UiScene},
|
||||||
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
RenderInput, RenderScenes, RenderState, VertexBuffers,
|
||||||
RenderInput, RenderState, VertexBuffers,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures.
|
/// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures.
|
||||||
|
@ -34,11 +32,16 @@ pub struct GPUState {
|
||||||
pub(crate) texture_array: TextureArray,
|
pub(crate) texture_array: TextureArray,
|
||||||
pub(crate) state: RenderState,
|
pub(crate) state: RenderState,
|
||||||
pub(crate) ui: UiManager,
|
pub(crate) ui: UiManager,
|
||||||
|
pub(crate) scene: RenderScenes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GPUState {
|
impl GPUState {
|
||||||
/// Make a new GPUState that draws on `window`
|
/// Make a new GPUState that draws on `window`
|
||||||
pub async fn new(window: winit::window::Window, ct: Rc<Content>) -> Result<Self> {
|
pub async fn new(
|
||||||
|
window: winit::window::Window,
|
||||||
|
ct: Rc<Content>,
|
||||||
|
scene: RenderScenes,
|
||||||
|
) -> Result<Self> {
|
||||||
let window_size = window.inner_size();
|
let window_size = window.inner_size();
|
||||||
let window_aspect = window_size.width as f32 / window_size.height as f32;
|
let window_aspect = window_size.width as f32 / window_size.height as f32;
|
||||||
|
|
||||||
|
@ -215,7 +218,20 @@ impl GPUState {
|
||||||
let mut starfield = Starfield::new();
|
let mut starfield = Starfield::new();
|
||||||
starfield.regenerate(&ct);
|
starfield.regenerate(&ct);
|
||||||
|
|
||||||
let mut state = RenderState {
|
return Ok(Self {
|
||||||
|
ui: UiManager::new(ct),
|
||||||
|
device,
|
||||||
|
config,
|
||||||
|
surface,
|
||||||
|
starfield,
|
||||||
|
texture_array,
|
||||||
|
object_pipeline,
|
||||||
|
starfield_pipeline,
|
||||||
|
ui_pipeline,
|
||||||
|
radialbar_pipeline,
|
||||||
|
scene,
|
||||||
|
|
||||||
|
state: RenderState {
|
||||||
queue,
|
queue,
|
||||||
window,
|
window,
|
||||||
window_size,
|
window_size,
|
||||||
|
@ -226,20 +242,7 @@ impl GPUState {
|
||||||
text_cache,
|
text_cache,
|
||||||
text_font_system,
|
text_font_system,
|
||||||
text_renderer,
|
text_renderer,
|
||||||
};
|
},
|
||||||
|
|
||||||
return Ok(Self {
|
|
||||||
ui: UiManager::new(ct, &mut state),
|
|
||||||
device,
|
|
||||||
config,
|
|
||||||
surface,
|
|
||||||
starfield,
|
|
||||||
texture_array,
|
|
||||||
object_pipeline,
|
|
||||||
starfield_pipeline,
|
|
||||||
ui_pipeline,
|
|
||||||
radialbar_pipeline,
|
|
||||||
state,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,6 +253,18 @@ impl GPUState {
|
||||||
&self.state.window
|
&self.state.window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change the current scenection
|
||||||
|
pub fn set_scene(&mut self, scene: RenderScenes) {
|
||||||
|
debug!("switching to {:?}", scene);
|
||||||
|
|
||||||
|
match scene {
|
||||||
|
RenderScenes::Landed => self.ui.set_scene(&mut self.state, UiScene::Landed).unwrap(),
|
||||||
|
RenderScenes::System => self.ui.set_scene(&mut self.state, UiScene::Flying).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.scene = scene;
|
||||||
|
}
|
||||||
|
|
||||||
/// Update window size.
|
/// Update window size.
|
||||||
/// This should be called whenever our window is resized.
|
/// This should be called whenever our window is resized.
|
||||||
pub fn resize(&mut self, ct: &Content) {
|
pub fn resize(&mut self, ct: &Content) {
|
||||||
|
@ -278,8 +293,6 @@ impl GPUState {
|
||||||
|
|
||||||
/// Main render function. Draws sprites on a window.
|
/// Main render function. Draws sprites on a window.
|
||||||
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
||||||
let input = Rc::new(input);
|
|
||||||
|
|
||||||
// Update global values
|
// Update global values
|
||||||
self.state.queue.write_buffer(
|
self.state.queue.write_buffer(
|
||||||
&self.state.global_uniform.data_buffer,
|
&self.state.global_uniform.data_buffer,
|
||||||
|
@ -303,454 +316,11 @@ impl GPUState {
|
||||||
|
|
||||||
self.state.frame_reset();
|
self.state.frame_reset();
|
||||||
|
|
||||||
let output = self.surface.get_current_texture()?;
|
match self.scene {
|
||||||
let view = output.texture.create_view(&Default::default());
|
RenderScenes::System => SystemScene::render(self, &input).unwrap(),
|
||||||
|
RenderScenes::Landed => LandedScene::render(self, &input).unwrap(),
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
if self.ui.get_config().show_phys {
|
|
||||||
// Create sprite instances
|
|
||||||
|
|
||||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
|
||||||
// Used to skip off-screen sprites.
|
|
||||||
let clip_ne = Point2::new(-self.state.window_aspect, 1.0) * input.camera_zoom;
|
|
||||||
let clip_sw = Point2::new(self.state.window_aspect, -1.0) * input.camera_zoom;
|
|
||||||
|
|
||||||
// Order matters, it determines what is drawn on top.
|
|
||||||
// The order inside ships and projectiles doesn't matter,
|
|
||||||
// but ships should always be under projectiles.
|
|
||||||
self.push_system(&input, (clip_ne, clip_sw));
|
|
||||||
self.push_ships(&input, (clip_ne, clip_sw));
|
|
||||||
self.push_projectiles(&input, (clip_ne, clip_sw));
|
|
||||||
self.push_effects(&input, (clip_ne, clip_sw));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ui.draw(input.clone(), &mut self.state).unwrap();
|
|
||||||
|
|
||||||
// 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, &[]);
|
|
||||||
|
|
||||||
if self.ui.get_config().show_starfield {
|
|
||||||
// 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(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.ui.get_config().show_phys {
|
|
||||||
// Sprite pipeline
|
|
||||||
self.state
|
|
||||||
.vertex_buffers
|
|
||||||
.get_object()
|
|
||||||
.set_in_pass(&mut render_pass);
|
|
||||||
render_pass.set_pipeline(&self.object_pipeline);
|
|
||||||
render_pass.draw_indexed(
|
|
||||||
0..SPRITE_INDICES.len() as u32,
|
|
||||||
0,
|
|
||||||
0..self.state.get_object_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
|
|
||||||
// TODO: do we need to do this every time?
|
|
||||||
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(&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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render utilities
|
|
||||||
impl GPUState {
|
|
||||||
fn push_ships(
|
|
||||||
&mut self,
|
|
||||||
input: &RenderInput,
|
|
||||||
// NE and SW corners of screen
|
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
|
||||||
) {
|
|
||||||
for s in input.phys_img.iter_ships() {
|
|
||||||
let ship_pos;
|
|
||||||
let ship_ang;
|
|
||||||
let ship_cnt;
|
|
||||||
match s.ship.get_data().get_state() {
|
|
||||||
ShipState::Dead | ShipState::Landed { .. } => continue,
|
|
||||||
|
|
||||||
ShipState::Collapsing { .. } | ShipState::Flying { .. } => {
|
|
||||||
let r = &s.rigidbody;
|
|
||||||
let pos = *r.translation();
|
|
||||||
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
|
||||||
let ship_rot = r.rotation();
|
|
||||||
ship_ang = ship_rot.angle();
|
|
||||||
ship_cnt = input.ct.get_ship(s.ship.get_data().get_content());
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
|
||||||
let r = &s.rigidbody;
|
|
||||||
let pos = *r.translation();
|
|
||||||
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
|
||||||
let ship_rot = r.rotation();
|
|
||||||
ship_ang = ship_rot.angle();
|
|
||||||
ship_cnt = input.ct.get_ship(s.ship.get_data().get_content());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position adjusted for parallax
|
|
||||||
// TODO: adjust parallax for zoom?
|
|
||||||
// 1.0 is z-coordinate, which is constant for ships
|
|
||||||
let pos: Point2<f32> =
|
|
||||||
(Point2::new(ship_pos.x, ship_pos.y) - input.camera_pos) / ship_pos.z;
|
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
|
||||||
// Post-scale width or height, whichever is larger.
|
|
||||||
// This is in game units.
|
|
||||||
//
|
|
||||||
// We take the maximum to account for rotated sprites.
|
|
||||||
let m =
|
|
||||||
(ship_cnt.size / ship_pos.z) * input.ct.get_sprite(ship_cnt.sprite).aspect.max(1.0);
|
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
|
||||||
if pos.x < screen_clip.0.x - m
|
|
||||||
|| pos.y > screen_clip.0.y + m
|
|
||||||
|| pos.x > screen_clip.1.x + m
|
|
||||||
|| pos.y < screen_clip.1.y - m
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = self.state.get_object_counter();
|
|
||||||
// Write this object's location data
|
|
||||||
self.state.queue.write_buffer(
|
|
||||||
&self.state.global_uniform.object_buffer,
|
|
||||||
ObjectData::SIZE * idx as u64,
|
|
||||||
bytemuck::cast_slice(&[ObjectData {
|
|
||||||
xpos: ship_pos.x,
|
|
||||||
ypos: ship_pos.y,
|
|
||||||
zpos: ship_pos.z,
|
|
||||||
angle: ship_ang,
|
|
||||||
size: ship_cnt.size,
|
|
||||||
parent: 0,
|
|
||||||
is_child: 0,
|
|
||||||
_padding: Default::default(),
|
|
||||||
}]),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Push this object's instance
|
|
||||||
let anim_state = s.ship.get_anim_state();
|
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
|
||||||
texture_index: anim_state.texture_index(),
|
|
||||||
texture_fade: anim_state.fade,
|
|
||||||
object_index: idx as u32,
|
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
|
||||||
});
|
|
||||||
|
|
||||||
if {
|
|
||||||
let is_flying = match s.ship.get_data().get_state() {
|
|
||||||
ShipState::Flying { .. }
|
|
||||||
| ShipState::UnLanding { .. }
|
|
||||||
| ShipState::Landing { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
is_flying
|
|
||||||
} {
|
|
||||||
for (engine_point, anim) in s.ship.iter_engine_anim() {
|
|
||||||
self.state.queue.write_buffer(
|
|
||||||
&self.state.global_uniform.object_buffer,
|
|
||||||
ObjectData::SIZE * self.state.get_object_counter() as u64,
|
|
||||||
bytemuck::cast_slice(&[ObjectData {
|
|
||||||
// Note that we adjust the y-coordinate for half-height,
|
|
||||||
// not the x-coordinate, even though our ships point east
|
|
||||||
// at 0 degrees. This is because this is placed pre-rotation,
|
|
||||||
// and the parent rotation adjustment in our object shader
|
|
||||||
// automatically accounts for this.
|
|
||||||
xpos: engine_point.pos.x,
|
|
||||||
ypos: engine_point.pos.y - engine_point.size / 2.0,
|
|
||||||
zpos: 1.0,
|
|
||||||
// We still need an adjustment here, though,
|
|
||||||
// since engine sprites point north (with exhaust towards the south)
|
|
||||||
angle: to_radians(90.0),
|
|
||||||
size: engine_point.size,
|
|
||||||
parent: idx as u32,
|
|
||||||
is_child: 1,
|
|
||||||
_padding: Default::default(),
|
|
||||||
}]),
|
|
||||||
);
|
|
||||||
|
|
||||||
let anim_state = anim.get_texture_idx();
|
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
|
||||||
texture_index: anim_state.texture_index(),
|
|
||||||
texture_fade: anim_state.fade,
|
|
||||||
object_index: self.state.get_object_counter() as u32,
|
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_projectiles(
|
|
||||||
&mut self,
|
|
||||||
input: &RenderInput,
|
|
||||||
// NE and SW corners of screen
|
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
|
||||||
) {
|
|
||||||
for p in input.phys_img.iter_projectiles() {
|
|
||||||
let r = &p.rigidbody;
|
|
||||||
let proj_pos = *r.translation();
|
|
||||||
let proj_rot = r.rotation();
|
|
||||||
let proj_ang = proj_rot.angle();
|
|
||||||
let proj_cnt = &p.projectile.content; // TODO: don't clone this?
|
|
||||||
|
|
||||||
// Position adjusted for parallax
|
|
||||||
// TODO: adjust parallax for zoom?
|
|
||||||
// 1.0 is z-coordinate, which is constant for projectiles
|
|
||||||
let pos = (proj_pos - input.camera_pos) / 1.0;
|
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
|
||||||
// Post-scale width or height, whichever is larger.
|
|
||||||
// This is in game units.
|
|
||||||
//
|
|
||||||
// We take the maximum to account for rotated sprites.
|
|
||||||
let m = (proj_cnt.size / 1.0) * input.ct.get_sprite(proj_cnt.sprite).aspect.max(1.0);
|
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
|
||||||
if pos.x < screen_clip.0.x - m
|
|
||||||
|| pos.y > screen_clip.0.y + m
|
|
||||||
|| pos.x > screen_clip.1.x + m
|
|
||||||
|| pos.y < screen_clip.1.y - m
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = self.state.get_object_counter();
|
|
||||||
// Write this object's location data
|
|
||||||
self.state.queue.write_buffer(
|
|
||||||
&self.state.global_uniform.object_buffer,
|
|
||||||
ObjectData::SIZE * idx as u64,
|
|
||||||
bytemuck::cast_slice(&[ObjectData {
|
|
||||||
xpos: proj_pos.x,
|
|
||||||
ypos: proj_pos.y,
|
|
||||||
zpos: 1.0,
|
|
||||||
angle: proj_ang,
|
|
||||||
size: 0f32.max(proj_cnt.size + p.projectile.size_rng),
|
|
||||||
parent: 0,
|
|
||||||
is_child: 0,
|
|
||||||
_padding: Default::default(),
|
|
||||||
}]),
|
|
||||||
);
|
|
||||||
|
|
||||||
let anim_state = p.projectile.get_anim_state();
|
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
|
||||||
texture_index: anim_state.texture_index(),
|
|
||||||
texture_fade: anim_state.fade,
|
|
||||||
object_index: idx as u32,
|
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_system(
|
|
||||||
&mut self,
|
|
||||||
input: &RenderInput,
|
|
||||||
// NE and SW corners of screen
|
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
|
||||||
) {
|
|
||||||
let system = input.ct.get_system(input.current_system);
|
|
||||||
|
|
||||||
for o in &system.objects {
|
|
||||||
// Position adjusted for parallax
|
|
||||||
let pos: Point2<f32> = (Point2::new(o.pos.x, o.pos.y) - input.camera_pos) / o.pos.z;
|
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
|
||||||
// Post-scale width or height, whichever is larger.
|
|
||||||
// This is in game units.
|
|
||||||
//
|
|
||||||
// We take the maximum to account for rotated sprites.
|
|
||||||
let m = (o.size / o.pos.z) * input.ct.get_sprite(o.sprite).aspect.max(1.0);
|
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
|
||||||
if pos.x < screen_clip.0.x - m
|
|
||||||
|| pos.y > screen_clip.0.y + m
|
|
||||||
|| pos.x > screen_clip.1.x + m
|
|
||||||
|| pos.y < screen_clip.1.y - m
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = self.state.get_object_counter();
|
|
||||||
// Write this object's location data
|
|
||||||
self.state.queue.write_buffer(
|
|
||||||
&self.state.global_uniform.object_buffer,
|
|
||||||
ObjectData::SIZE * idx as u64,
|
|
||||||
bytemuck::cast_slice(&[ObjectData {
|
|
||||||
xpos: o.pos.x,
|
|
||||||
ypos: o.pos.y,
|
|
||||||
zpos: o.pos.z,
|
|
||||||
angle: o.angle,
|
|
||||||
size: o.size,
|
|
||||||
parent: 0,
|
|
||||||
is_child: 0,
|
|
||||||
_padding: Default::default(),
|
|
||||||
}]),
|
|
||||||
);
|
|
||||||
|
|
||||||
let sprite = input.ct.get_sprite(o.sprite);
|
|
||||||
let texture_a = sprite.get_first_frame(); // ANIMATE
|
|
||||||
|
|
||||||
// Push this object's instance
|
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
|
||||||
texture_index: [texture_a, texture_a],
|
|
||||||
texture_fade: 1.0,
|
|
||||||
object_index: idx as u32,
|
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_effects(
|
|
||||||
&mut self,
|
|
||||||
input: &RenderInput,
|
|
||||||
// NE and SW corners of screen
|
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
|
||||||
) {
|
|
||||||
for p in input.phys_img.iter_effects() {
|
|
||||||
let r = &p.rigidbody;
|
|
||||||
let pos = *r.translation();
|
|
||||||
let rot = r.rotation();
|
|
||||||
let ang = rot.angle();
|
|
||||||
|
|
||||||
// Position adjusted for parallax
|
|
||||||
// TODO: adjust parallax for zoom?
|
|
||||||
// 1.0 is z-coordinate, which is constant for projectiles
|
|
||||||
let adjusted_pos = (pos - input.camera_pos) / 1.0;
|
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
|
||||||
// Post-scale width or height, whichever is larger.
|
|
||||||
// This is in game units.
|
|
||||||
//
|
|
||||||
// We take the maximum to account for rotated sprites.
|
|
||||||
let m = (p.effect.size / 1.0)
|
|
||||||
* input
|
|
||||||
.ct
|
|
||||||
.get_sprite(p.effect.anim.get_sprite())
|
|
||||||
.aspect
|
|
||||||
.max(1.0);
|
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
|
||||||
if adjusted_pos.x < screen_clip.0.x - m
|
|
||||||
|| adjusted_pos.y > screen_clip.0.y + m
|
|
||||||
|| adjusted_pos.x > screen_clip.1.x + m
|
|
||||||
|| adjusted_pos.y < screen_clip.1.y - m
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = self.state.get_object_counter();
|
|
||||||
// Write this object's location data
|
|
||||||
self.state.queue.write_buffer(
|
|
||||||
&self.state.global_uniform.object_buffer,
|
|
||||||
ObjectData::SIZE * idx as u64,
|
|
||||||
bytemuck::cast_slice(&[ObjectData {
|
|
||||||
xpos: pos.x,
|
|
||||||
ypos: pos.y,
|
|
||||||
zpos: 1.0,
|
|
||||||
angle: ang,
|
|
||||||
size: p.effect.size,
|
|
||||||
parent: 0,
|
|
||||||
is_child: 0,
|
|
||||||
_padding: Default::default(),
|
|
||||||
}]),
|
|
||||||
);
|
|
||||||
|
|
||||||
let anim_state = p.effect.anim.get_texture_idx();
|
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
|
||||||
texture_index: anim_state.texture_index(),
|
|
||||||
texture_fade: anim_state.fade,
|
|
||||||
object_index: idx as u32,
|
|
||||||
color: [1.0, 1.0, 1.0, p.get_fade()],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
mod globaluniform;
|
mod globaluniform;
|
||||||
mod gpustate;
|
mod gpustate;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
mod positionanchor;
|
||||||
mod renderinput;
|
mod renderinput;
|
||||||
|
mod renderscene;
|
||||||
mod renderstate;
|
mod renderstate;
|
||||||
mod shaderprocessor;
|
mod shaderprocessor;
|
||||||
mod starfield;
|
mod starfield;
|
||||||
|
@ -19,7 +21,9 @@ mod ui;
|
||||||
mod vertexbuffer;
|
mod vertexbuffer;
|
||||||
|
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::GPUState;
|
||||||
|
pub use positionanchor::PositionAnchor;
|
||||||
pub use renderinput::RenderInput;
|
pub use renderinput::RenderInput;
|
||||||
|
pub use renderscene::RenderScenes;
|
||||||
use renderstate::*;
|
use renderstate::*;
|
||||||
|
|
||||||
use nalgebra::Matrix4;
|
use nalgebra::Matrix4;
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/// The location of a UI element, in one of a few
|
||||||
|
/// possible coordinate systems.
|
||||||
|
///
|
||||||
|
/// Positive Y always points up,
|
||||||
|
/// positive X always points right.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum PositionAnchor {
|
||||||
|
/// Position of this sprite's center,
|
||||||
|
/// relative to the nw corner of the window.
|
||||||
|
NwC,
|
||||||
|
|
||||||
|
/// Position of this sprite's nw corner,
|
||||||
|
/// relative to the nw corner of the window.
|
||||||
|
NwNw,
|
||||||
|
|
||||||
|
/// Position of this sprite's ne corner,
|
||||||
|
/// relative to the nw corner of the window.
|
||||||
|
NwNe,
|
||||||
|
|
||||||
|
/// Position of this sprite's sw corner,
|
||||||
|
/// relative to the nw corner of the window.
|
||||||
|
NwSw,
|
||||||
|
|
||||||
|
/// Position of this sprite's se corner,
|
||||||
|
/// relative to the nw corner of the window.
|
||||||
|
NwSe,
|
||||||
|
|
||||||
|
/// Position of this sprite's ne corner,
|
||||||
|
/// relative to the ne corner of the window.
|
||||||
|
NeNe,
|
||||||
|
|
||||||
|
/// Position of this sprite's center,
|
||||||
|
/// relative to the center of the window.
|
||||||
|
CC,
|
||||||
|
|
||||||
|
/// Position of this sprite's NW corner,
|
||||||
|
/// relative to the center of the window.
|
||||||
|
CNw,
|
||||||
|
}
|
||||||
|
|
||||||
|
// These offsets are implemented in wgsl shaders.
|
||||||
|
|
||||||
|
impl PositionAnchor {
|
||||||
|
/// Get the uint that represents this anchor mode in shaders
|
||||||
|
pub fn to_int(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::NwC => 0,
|
||||||
|
Self::NwNw => 1,
|
||||||
|
Self::NwNe => 2,
|
||||||
|
Self::NwSw => 3,
|
||||||
|
Self::NwSe => 4,
|
||||||
|
Self::NeNe => 5,
|
||||||
|
Self::CC => 6,
|
||||||
|
Self::CNw => 7,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,13 +7,12 @@ use galactica_util::timing::Timing;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
|
|
||||||
/// Bundles parameters passed to a single call to GPUState::render
|
/// Bundles parameters passed to a single call to GPUState::render
|
||||||
#[derive(Debug)]
|
pub struct RenderInput<'a> {
|
||||||
pub struct RenderInput {
|
|
||||||
/// Camera position, in world units
|
/// Camera position, in world units
|
||||||
pub camera_pos: Vector2<f32>,
|
pub camera_pos: Vector2<f32>,
|
||||||
|
|
||||||
/// Player ship data
|
/// Player ship data
|
||||||
pub player: Rc<PlayerAgent>,
|
pub player: &'a PlayerAgent,
|
||||||
|
|
||||||
/// The system we're currently in
|
/// The system we're currently in
|
||||||
pub current_system: SystemHandle,
|
pub current_system: SystemHandle,
|
||||||
|
@ -22,7 +21,7 @@ pub struct RenderInput {
|
||||||
pub camera_zoom: f32,
|
pub camera_zoom: f32,
|
||||||
|
|
||||||
/// The world state to render
|
/// The world state to render
|
||||||
pub phys_img: Rc<PhysImage>,
|
pub phys_img: &'a PhysImage,
|
||||||
|
|
||||||
// TODO: handle overflow. is it a problem?
|
// TODO: handle overflow. is it a problem?
|
||||||
/// The current time, in seconds
|
/// The current time, in seconds
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use glyphon::Resolution;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
use super::RenderScene;
|
||||||
|
use crate::vertexbuffer::consts::SPRITE_INDICES;
|
||||||
|
|
||||||
|
pub struct LandedScene {}
|
||||||
|
|
||||||
|
impl RenderScene for LandedScene {
|
||||||
|
fn render(g: &mut crate::GPUState, input: &crate::RenderInput) -> Result<()> {
|
||||||
|
let output = g.surface.get_current_texture()?;
|
||||||
|
let view = output.texture.create_view(&Default::default());
|
||||||
|
|
||||||
|
let mut encoder = g
|
||||||
|
.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
|
||||||
|
g.ui.draw(&input, &mut g.state)?;
|
||||||
|
|
||||||
|
// These should match the indices in each shader,
|
||||||
|
// and should each have a corresponding bind group layout.
|
||||||
|
render_pass.set_bind_group(0, &g.texture_array.bind_group, &[]);
|
||||||
|
render_pass.set_bind_group(1, &g.state.global_uniform.bind_group, &[]);
|
||||||
|
|
||||||
|
// Starfield pipeline
|
||||||
|
g.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_starfield()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&g.starfield_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..g.state.get_starfield_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ui pipeline
|
||||||
|
g.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_ui()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&g.ui_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..g.state.get_ui_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Radial progress bars
|
||||||
|
g.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_radialbar()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&g.radialbar_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..g.state.get_radialbar_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let textareas = g.ui.get_textareas(input, &g.state);
|
||||||
|
g.state
|
||||||
|
.text_renderer
|
||||||
|
.prepare(
|
||||||
|
&g.device,
|
||||||
|
&g.state.queue,
|
||||||
|
&mut g.state.text_font_system,
|
||||||
|
&mut g.state.text_atlas,
|
||||||
|
Resolution {
|
||||||
|
width: g.state.window_size.width,
|
||||||
|
height: g.state.window_size.height,
|
||||||
|
},
|
||||||
|
textareas,
|
||||||
|
&mut g.state.text_cache,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
g.state
|
||||||
|
.text_renderer
|
||||||
|
.render(&g.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);
|
||||||
|
|
||||||
|
g.state.queue.submit(iter::once(encoder.finish()));
|
||||||
|
output.present();
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
mod landed;
|
||||||
|
mod system;
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
pub use landed::LandedScene;
|
||||||
|
pub use system::SystemScene;
|
||||||
|
|
||||||
|
use crate::{GPUState, RenderInput};
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub trait RenderScene {
|
||||||
|
fn render(g: &mut GPUState, input: &RenderInput) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What render routine to run
|
||||||
|
pub enum RenderScenes {
|
||||||
|
/// Draw the system we're in
|
||||||
|
System,
|
||||||
|
|
||||||
|
/// Draw the landed UI
|
||||||
|
Landed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for RenderScenes {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Landed => write!(f, "RenderScenes::Landed"),
|
||||||
|
Self::System => write!(f, "RenderScenes::System"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
mod phys;
|
||||||
|
mod system;
|
||||||
|
|
||||||
|
pub use system::SystemScene;
|
|
@ -0,0 +1,321 @@
|
||||||
|
use bytemuck;
|
||||||
|
use galactica_system::data::ShipState;
|
||||||
|
use galactica_util::to_radians;
|
||||||
|
use nalgebra::{Point2, Point3};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
globaluniform::ObjectData, vertexbuffer::types::ObjectInstance, GPUState, RenderInput,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::SystemScene;
|
||||||
|
|
||||||
|
impl SystemScene {
|
||||||
|
pub(super) fn push_ships(
|
||||||
|
g: &mut GPUState,
|
||||||
|
input: &RenderInput,
|
||||||
|
// NE and SW corners of screen
|
||||||
|
screen_clip: (Point2<f32>, Point2<f32>),
|
||||||
|
) {
|
||||||
|
for s in input.phys_img.iter_ships() {
|
||||||
|
let ship_pos;
|
||||||
|
let ship_ang;
|
||||||
|
let ship_cnt;
|
||||||
|
match s.ship.get_data().get_state() {
|
||||||
|
ShipState::Dead | ShipState::Landed { .. } => continue,
|
||||||
|
|
||||||
|
ShipState::Collapsing { .. } | ShipState::Flying { .. } => {
|
||||||
|
let r = &s.rigidbody;
|
||||||
|
let pos = *r.translation();
|
||||||
|
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
||||||
|
let ship_rot = r.rotation();
|
||||||
|
ship_ang = ship_rot.angle();
|
||||||
|
ship_cnt = input.ct.get_ship(s.ship.get_data().get_content());
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
||||||
|
let r = &s.rigidbody;
|
||||||
|
let pos = *r.translation();
|
||||||
|
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
||||||
|
let ship_rot = r.rotation();
|
||||||
|
ship_ang = ship_rot.angle();
|
||||||
|
ship_cnt = input.ct.get_ship(s.ship.get_data().get_content());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position adjusted for parallax
|
||||||
|
// TODO: adjust parallax for zoom?
|
||||||
|
// 1.0 is z-coordinate, which is constant for ships
|
||||||
|
let pos: Point2<f32> =
|
||||||
|
(Point2::new(ship_pos.x, ship_pos.y) - input.camera_pos) / ship_pos.z;
|
||||||
|
|
||||||
|
// Game dimensions of this sprite post-scale.
|
||||||
|
// Post-scale width or height, whichever is larger.
|
||||||
|
// This is in game units.
|
||||||
|
//
|
||||||
|
// We take the maximum to account for rotated sprites.
|
||||||
|
let m =
|
||||||
|
(ship_cnt.size / ship_pos.z) * input.ct.get_sprite(ship_cnt.sprite).aspect.max(1.0);
|
||||||
|
|
||||||
|
// Don't draw sprites that are off the screen
|
||||||
|
if pos.x < screen_clip.0.x - m
|
||||||
|
|| pos.y > screen_clip.0.y + m
|
||||||
|
|| pos.x > screen_clip.1.x + m
|
||||||
|
|| pos.y < screen_clip.1.y - m
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = g.state.get_object_counter();
|
||||||
|
// Write this object's location data
|
||||||
|
g.state.queue.write_buffer(
|
||||||
|
&g.state.global_uniform.object_buffer,
|
||||||
|
ObjectData::SIZE * idx as u64,
|
||||||
|
bytemuck::cast_slice(&[ObjectData {
|
||||||
|
xpos: ship_pos.x,
|
||||||
|
ypos: ship_pos.y,
|
||||||
|
zpos: ship_pos.z,
|
||||||
|
angle: ship_ang,
|
||||||
|
size: ship_cnt.size,
|
||||||
|
parent: 0,
|
||||||
|
is_child: 0,
|
||||||
|
_padding: Default::default(),
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Push this object's instance
|
||||||
|
let anim_state = s.ship.get_anim_state();
|
||||||
|
g.state.push_object_buffer(ObjectInstance {
|
||||||
|
texture_index: anim_state.texture_index(),
|
||||||
|
texture_fade: anim_state.fade,
|
||||||
|
object_index: idx as u32,
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
});
|
||||||
|
|
||||||
|
if {
|
||||||
|
let is_flying = match s.ship.get_data().get_state() {
|
||||||
|
ShipState::Flying { .. }
|
||||||
|
| ShipState::UnLanding { .. }
|
||||||
|
| ShipState::Landing { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
is_flying
|
||||||
|
} {
|
||||||
|
for (engine_point, anim) in s.ship.iter_engine_anim() {
|
||||||
|
g.state.queue.write_buffer(
|
||||||
|
&g.state.global_uniform.object_buffer,
|
||||||
|
ObjectData::SIZE * g.state.get_object_counter() as u64,
|
||||||
|
bytemuck::cast_slice(&[ObjectData {
|
||||||
|
// Note that we adjust the y-coordinate for half-height,
|
||||||
|
// not the x-coordinate, even though our ships point east
|
||||||
|
// at 0 degrees. This is because this is placed pre-rotation,
|
||||||
|
// and the parent rotation adjustment in our object shader
|
||||||
|
// automatically accounts for this.
|
||||||
|
xpos: engine_point.pos.x,
|
||||||
|
ypos: engine_point.pos.y - engine_point.size / 2.0,
|
||||||
|
zpos: 1.0,
|
||||||
|
// We still need an adjustment here, though,
|
||||||
|
// since engine sprites point north (with exhaust towards the south)
|
||||||
|
angle: to_radians(90.0),
|
||||||
|
size: engine_point.size,
|
||||||
|
parent: idx as u32,
|
||||||
|
is_child: 1,
|
||||||
|
_padding: Default::default(),
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let anim_state = anim.get_texture_idx();
|
||||||
|
g.state.push_object_buffer(ObjectInstance {
|
||||||
|
texture_index: anim_state.texture_index(),
|
||||||
|
texture_fade: anim_state.fade,
|
||||||
|
object_index: g.state.get_object_counter() as u32,
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn push_projectiles(
|
||||||
|
g: &mut GPUState,
|
||||||
|
input: &RenderInput,
|
||||||
|
// NE and SW corners of screen
|
||||||
|
screen_clip: (Point2<f32>, Point2<f32>),
|
||||||
|
) {
|
||||||
|
for p in input.phys_img.iter_projectiles() {
|
||||||
|
let r = &p.rigidbody;
|
||||||
|
let proj_pos = *r.translation();
|
||||||
|
let proj_rot = r.rotation();
|
||||||
|
let proj_ang = proj_rot.angle();
|
||||||
|
let proj_cnt = &p.projectile.content; // TODO: don't clone this?
|
||||||
|
|
||||||
|
// Position adjusted for parallax
|
||||||
|
// TODO: adjust parallax for zoom?
|
||||||
|
// 1.0 is z-coordinate, which is constant for projectiles
|
||||||
|
let pos = (proj_pos - input.camera_pos) / 1.0;
|
||||||
|
|
||||||
|
// Game dimensions of this sprite post-scale.
|
||||||
|
// Post-scale width or height, whichever is larger.
|
||||||
|
// This is in game units.
|
||||||
|
//
|
||||||
|
// We take the maximum to account for rotated sprites.
|
||||||
|
let m = (proj_cnt.size / 1.0) * input.ct.get_sprite(proj_cnt.sprite).aspect.max(1.0);
|
||||||
|
|
||||||
|
// Don't draw sprites that are off the screen
|
||||||
|
if pos.x < screen_clip.0.x - m
|
||||||
|
|| pos.y > screen_clip.0.y + m
|
||||||
|
|| pos.x > screen_clip.1.x + m
|
||||||
|
|| pos.y < screen_clip.1.y - m
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = g.state.get_object_counter();
|
||||||
|
// Write this object's location data
|
||||||
|
g.state.queue.write_buffer(
|
||||||
|
&g.state.global_uniform.object_buffer,
|
||||||
|
ObjectData::SIZE * idx as u64,
|
||||||
|
bytemuck::cast_slice(&[ObjectData {
|
||||||
|
xpos: proj_pos.x,
|
||||||
|
ypos: proj_pos.y,
|
||||||
|
zpos: 1.0,
|
||||||
|
angle: proj_ang,
|
||||||
|
size: 0f32.max(proj_cnt.size + p.projectile.size_rng),
|
||||||
|
parent: 0,
|
||||||
|
is_child: 0,
|
||||||
|
_padding: Default::default(),
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let anim_state = p.projectile.get_anim_state();
|
||||||
|
g.state.push_object_buffer(ObjectInstance {
|
||||||
|
texture_index: anim_state.texture_index(),
|
||||||
|
texture_fade: anim_state.fade,
|
||||||
|
object_index: idx as u32,
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn push_system(
|
||||||
|
g: &mut GPUState,
|
||||||
|
input: &RenderInput,
|
||||||
|
// NE and SW corners of screen
|
||||||
|
screen_clip: (Point2<f32>, Point2<f32>),
|
||||||
|
) {
|
||||||
|
let system = input.ct.get_system(input.current_system);
|
||||||
|
|
||||||
|
for o in &system.objects {
|
||||||
|
// Position adjusted for parallax
|
||||||
|
let pos: Point2<f32> = (Point2::new(o.pos.x, o.pos.y) - input.camera_pos) / o.pos.z;
|
||||||
|
|
||||||
|
// Game dimensions of this sprite post-scale.
|
||||||
|
// Post-scale width or height, whichever is larger.
|
||||||
|
// This is in game units.
|
||||||
|
//
|
||||||
|
// We take the maximum to account for rotated sprites.
|
||||||
|
let m = (o.size / o.pos.z) * input.ct.get_sprite(o.sprite).aspect.max(1.0);
|
||||||
|
|
||||||
|
// Don't draw sprites that are off the screen
|
||||||
|
if pos.x < screen_clip.0.x - m
|
||||||
|
|| pos.y > screen_clip.0.y + m
|
||||||
|
|| pos.x > screen_clip.1.x + m
|
||||||
|
|| pos.y < screen_clip.1.y - m
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = g.state.get_object_counter();
|
||||||
|
// Write this object's location data
|
||||||
|
g.state.queue.write_buffer(
|
||||||
|
&g.state.global_uniform.object_buffer,
|
||||||
|
ObjectData::SIZE * idx as u64,
|
||||||
|
bytemuck::cast_slice(&[ObjectData {
|
||||||
|
xpos: o.pos.x,
|
||||||
|
ypos: o.pos.y,
|
||||||
|
zpos: o.pos.z,
|
||||||
|
angle: o.angle,
|
||||||
|
size: o.size,
|
||||||
|
parent: 0,
|
||||||
|
is_child: 0,
|
||||||
|
_padding: Default::default(),
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let sprite = input.ct.get_sprite(o.sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
// Push this object's instance
|
||||||
|
g.state.push_object_buffer(ObjectInstance {
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
object_index: idx as u32,
|
||||||
|
color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn push_effects(
|
||||||
|
g: &mut GPUState,
|
||||||
|
input: &RenderInput,
|
||||||
|
// NE and SW corners of screen
|
||||||
|
screen_clip: (Point2<f32>, Point2<f32>),
|
||||||
|
) {
|
||||||
|
for p in input.phys_img.iter_effects() {
|
||||||
|
let r = &p.rigidbody;
|
||||||
|
let pos = *r.translation();
|
||||||
|
let rot = r.rotation();
|
||||||
|
let ang = rot.angle();
|
||||||
|
|
||||||
|
// Position adjusted for parallax
|
||||||
|
// TODO: adjust parallax for zoom?
|
||||||
|
// 1.0 is z-coordinate, which is constant for projectiles
|
||||||
|
let adjusted_pos = (pos - input.camera_pos) / 1.0;
|
||||||
|
|
||||||
|
// Game dimensions of this sprite post-scale.
|
||||||
|
// Post-scale width or height, whichever is larger.
|
||||||
|
// This is in game units.
|
||||||
|
//
|
||||||
|
// We take the maximum to account for rotated sprites.
|
||||||
|
let m = (p.effect.size / 1.0)
|
||||||
|
* input
|
||||||
|
.ct
|
||||||
|
.get_sprite(p.effect.anim.get_sprite())
|
||||||
|
.aspect
|
||||||
|
.max(1.0);
|
||||||
|
|
||||||
|
// Don't draw sprites that are off the screen
|
||||||
|
if adjusted_pos.x < screen_clip.0.x - m
|
||||||
|
|| adjusted_pos.y > screen_clip.0.y + m
|
||||||
|
|| adjusted_pos.x > screen_clip.1.x + m
|
||||||
|
|| adjusted_pos.y < screen_clip.1.y - m
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = g.state.get_object_counter();
|
||||||
|
// Write this object's location data
|
||||||
|
g.state.queue.write_buffer(
|
||||||
|
&g.state.global_uniform.object_buffer,
|
||||||
|
ObjectData::SIZE * idx as u64,
|
||||||
|
bytemuck::cast_slice(&[ObjectData {
|
||||||
|
xpos: pos.x,
|
||||||
|
ypos: pos.y,
|
||||||
|
zpos: 1.0,
|
||||||
|
angle: ang,
|
||||||
|
size: p.effect.size,
|
||||||
|
parent: 0,
|
||||||
|
is_child: 0,
|
||||||
|
_padding: Default::default(),
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let anim_state = p.effect.anim.get_texture_idx();
|
||||||
|
g.state.push_object_buffer(ObjectInstance {
|
||||||
|
texture_index: anim_state.texture_index(),
|
||||||
|
texture_fade: anim_state.fade,
|
||||||
|
object_index: idx as u32,
|
||||||
|
color: [1.0, 1.0, 1.0, p.get_fade()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use glyphon::Resolution;
|
||||||
|
use nalgebra::Point2;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
use super::super::RenderScene;
|
||||||
|
use crate::vertexbuffer::consts::SPRITE_INDICES;
|
||||||
|
|
||||||
|
pub struct SystemScene {}
|
||||||
|
|
||||||
|
impl RenderScene for SystemScene {
|
||||||
|
fn render(g: &mut crate::GPUState, input: &crate::RenderInput) -> Result<()> {
|
||||||
|
let output = g.surface.get_current_texture()?;
|
||||||
|
let view = output.texture.create_view(&Default::default());
|
||||||
|
|
||||||
|
let mut encoder = g
|
||||||
|
.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
|
||||||
|
|
||||||
|
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||||
|
// Used to skip off-screen sprites.
|
||||||
|
let clip_ne = Point2::new(-g.state.window_aspect, 1.0) * input.camera_zoom;
|
||||||
|
let clip_sw = Point2::new(g.state.window_aspect, -1.0) * input.camera_zoom;
|
||||||
|
|
||||||
|
// Order matters, it determines what is drawn on top.
|
||||||
|
// The order inside ships and projectiles doesn't matter,
|
||||||
|
// but ships should always be under projectiles.
|
||||||
|
Self::push_system(g, &input, (clip_ne, clip_sw));
|
||||||
|
Self::push_ships(g, &input, (clip_ne, clip_sw));
|
||||||
|
Self::push_projectiles(g, &input, (clip_ne, clip_sw));
|
||||||
|
Self::push_effects(g, &input, (clip_ne, clip_sw));
|
||||||
|
g.ui.draw(&input, &mut g.state)?;
|
||||||
|
|
||||||
|
// These should match the indices in each shader,
|
||||||
|
// and should each have a corresponding bind group layout.
|
||||||
|
render_pass.set_bind_group(0, &g.texture_array.bind_group, &[]);
|
||||||
|
render_pass.set_bind_group(1, &g.state.global_uniform.bind_group, &[]);
|
||||||
|
|
||||||
|
// Starfield pipeline
|
||||||
|
g.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_starfield()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&g.starfield_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..g.state.get_starfield_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sprite pipeline
|
||||||
|
g.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_object()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&g.object_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..g.state.get_object_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ui pipeline
|
||||||
|
g.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_ui()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&g.ui_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..g.state.get_ui_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Radial progress bars
|
||||||
|
// TODO: do we need to do this every time?
|
||||||
|
g.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_radialbar()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&g.radialbar_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..g.state.get_radialbar_counter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let textareas = g.ui.get_textareas(input, &g.state);
|
||||||
|
g.state
|
||||||
|
.text_renderer
|
||||||
|
.prepare(
|
||||||
|
&g.device,
|
||||||
|
&g.state.queue,
|
||||||
|
&mut g.state.text_font_system,
|
||||||
|
&mut g.state.text_atlas,
|
||||||
|
Resolution {
|
||||||
|
width: g.state.window_size.width,
|
||||||
|
height: g.state.window_size.height,
|
||||||
|
},
|
||||||
|
textareas,
|
||||||
|
&mut g.state.text_cache,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
g.state
|
||||||
|
.text_renderer
|
||||||
|
.render(&g.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);
|
||||||
|
|
||||||
|
g.state.queue.submit(iter::once(encoder.finish()));
|
||||||
|
output.present();
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_util::constants::{
|
use galactica_util::constants::{OBJECT_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT};
|
||||||
OBJECT_SPRITE_INSTANCE_LIMIT, RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT,
|
|
||||||
};
|
|
||||||
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use wgpu::BufferAddress;
|
use wgpu::BufferAddress;
|
||||||
|
@ -154,6 +152,7 @@ impl RenderState {
|
||||||
self.vertex_buffers.object_counter as u32
|
self.vertex_buffers.object_counter as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
pub fn push_radialbar_buffer(&mut self, instance: RadialBarInstance) {
|
pub fn push_radialbar_buffer(&mut self, instance: RadialBarInstance) {
|
||||||
// Enforce buffer limit
|
// Enforce buffer limit
|
||||||
if self.vertex_buffers.radialbar_counter as u64 > RADIALBAR_SPRITE_INSTANCE_LIMIT {
|
if self.vertex_buffers.radialbar_counter as u64 > RADIALBAR_SPRITE_INSTANCE_LIMIT {
|
||||||
|
@ -168,6 +167,7 @@ impl RenderState {
|
||||||
);
|
);
|
||||||
self.vertex_buffers.radialbar_counter += 1;
|
self.vertex_buffers.radialbar_counter += 1;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
pub fn get_radialbar_counter(&self) -> u32 {
|
pub fn get_radialbar_counter(&self) -> u32 {
|
||||||
self.vertex_buffers.radialbar_counter as u32
|
self.vertex_buffers.radialbar_counter as u32
|
||||||
|
|
|
@ -14,5 +14,15 @@ pub(crate) fn preprocess_shader(
|
||||||
&global_uniform.shader_header(global_uniform_group),
|
&global_uniform.shader_header(global_uniform_group),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Insert common functions
|
||||||
|
let shader = shader.replace(
|
||||||
|
"// INCLUDE: anchor.wgsl",
|
||||||
|
&include_str!(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/shaders/include/",
|
||||||
|
"anchor.wgsl"
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
use nalgebra::Vector4;
|
|
||||||
use rhai::{CustomType, TypeBuilder};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Color {
|
|
||||||
pub val: Vector4<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Color {
|
|
||||||
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
val: Vector4::new(r, g, b, a),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_array(&self) -> [f32; 4] {
|
|
||||||
[self.val.x, self.val.y, self.val.z, self.val.w]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for Color {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder.with_name("Color").with_fn("Color", Self::new);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
use rhai::{CustomType, TypeBuilder};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct SceneConfig {
|
|
||||||
pub show_phys: bool,
|
|
||||||
pub show_starfield: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SceneConfig {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
show_phys: false,
|
|
||||||
show_starfield: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for SceneConfig {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("SceneConfig")
|
|
||||||
.with_fn("SceneConfig", Self::new)
|
|
||||||
.with_fn("show_phys", |s: &mut Self, x: bool| s.show_phys = x)
|
|
||||||
.with_fn("show_starfield", |s: &mut Self, x: bool| {
|
|
||||||
s.show_starfield = x
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
use rhai::{CustomType, TypeBuilder};
|
|
||||||
|
|
||||||
use super::SpriteElement;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct MouseClickEvent {
|
|
||||||
pub down: bool,
|
|
||||||
pub element: SpriteElement,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for MouseClickEvent {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("MouseClickEvent")
|
|
||||||
.with_fn("is_down", |s: &mut Self| s.down)
|
|
||||||
.with_fn("element", |s: &mut Self| s.element.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct MouseHoverEvent {
|
|
||||||
pub enter: bool,
|
|
||||||
pub element: SpriteElement,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for MouseHoverEvent {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("MouseHoverEvent")
|
|
||||||
.with_fn("is_enter", |s: &mut Self| s.enter)
|
|
||||||
.with_fn("element", |s: &mut Self| s.element.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PlayerShipStateEvent {}
|
|
||||||
|
|
||||||
impl CustomType for PlayerShipStateEvent {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder.with_name("PlayerShipStateEvent");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +1,19 @@
|
||||||
mod color;
|
|
||||||
mod config;
|
|
||||||
mod event;
|
|
||||||
mod radialbuilder;
|
|
||||||
mod rect;
|
mod rect;
|
||||||
mod sceneaction;
|
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod spritebuilder;
|
mod spritebuilder;
|
||||||
mod state;
|
mod state;
|
||||||
mod textboxbuilder;
|
mod textboxbuilder;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use color::*;
|
|
||||||
pub use config::*;
|
|
||||||
pub use event::*;
|
|
||||||
pub use radialbuilder::*;
|
|
||||||
pub use rect::*;
|
|
||||||
pub use sceneaction::*;
|
|
||||||
pub use sprite::*;
|
|
||||||
pub use spritebuilder::*;
|
|
||||||
pub use state::*;
|
|
||||||
pub use textboxbuilder::*;
|
|
||||||
pub use util::*;
|
|
||||||
|
|
||||||
use rhai::{exported_module, Engine};
|
use rhai::{exported_module, Engine};
|
||||||
|
|
||||||
pub fn register_into_engine(engine: &mut Engine) {
|
pub fn register_into_engine(engine: &mut Engine) {
|
||||||
engine
|
engine
|
||||||
// Helpers
|
|
||||||
.build_type::<Rect>()
|
|
||||||
.build_type::<Color>()
|
|
||||||
.build_type::<SceneConfig>()
|
|
||||||
// State
|
|
||||||
.build_type::<State>()
|
.build_type::<State>()
|
||||||
.build_type::<ShipState>()
|
.build_type::<Rect>()
|
||||||
.build_type::<SystemObjectState>()
|
|
||||||
// Builders
|
|
||||||
.build_type::<RadialBuilder>()
|
|
||||||
.build_type::<SpriteElement>()
|
.build_type::<SpriteElement>()
|
||||||
.build_type::<SpriteBuilder>()
|
.build_type::<SpriteBuilder>()
|
||||||
.build_type::<TextBoxBuilder>()
|
.build_type::<TextBoxBuilder>()
|
||||||
// Events
|
|
||||||
.build_type::<MouseClickEvent>()
|
|
||||||
.build_type::<MouseHoverEvent>()
|
|
||||||
.build_type::<PlayerShipStateEvent>()
|
|
||||||
// Larger modules
|
|
||||||
.register_type_with_name::<SpriteAnchor>("SpriteAnchor")
|
.register_type_with_name::<SpriteAnchor>("SpriteAnchor")
|
||||||
.register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into())
|
.register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into())
|
||||||
.register_type_with_name::<TextBoxFont>("TextBoxFont")
|
.register_type_with_name::<TextBoxFont>("TextBoxFont")
|
||||||
|
@ -59,3 +29,10 @@ pub fn register_into_engine(engine: &mut Engine) {
|
||||||
.register_type_with_name::<SceneAction>("SceneAction")
|
.register_type_with_name::<SceneAction>("SceneAction")
|
||||||
.register_static_module("SceneAction", exported_module!(sceneaction_mod).into());
|
.register_static_module("SceneAction", exported_module!(sceneaction_mod).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use rect::*;
|
||||||
|
pub use sprite::*;
|
||||||
|
pub use spritebuilder::*;
|
||||||
|
pub use state::*;
|
||||||
|
pub use textboxbuilder::*;
|
||||||
|
pub use util::*;
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
use nalgebra::clamp;
|
|
||||||
use rhai::{CustomType, ImmutableString, TypeBuilder};
|
|
||||||
|
|
||||||
use super::{color::Color, Rect};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RadialBuilder {
|
|
||||||
pub name: ImmutableString,
|
|
||||||
pub rect: Rect,
|
|
||||||
|
|
||||||
pub stroke: f32,
|
|
||||||
pub color: Color,
|
|
||||||
pub progress: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RadialBuilder {
|
|
||||||
pub fn new(name: ImmutableString, stroke: f32, color: Color, rect: Rect) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
rect,
|
|
||||||
stroke,
|
|
||||||
color,
|
|
||||||
progress: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_progress(&mut self, progress: f32) {
|
|
||||||
self.progress = clamp(progress, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for RadialBuilder {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("RadialBuilder")
|
|
||||||
.with_fn("RadialBuilder", Self::new)
|
|
||||||
.with_fn("set_progress", Self::set_progress);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
use rhai::plugin::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum SceneAction {
|
|
||||||
None,
|
|
||||||
GoTo(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export_module]
|
|
||||||
pub mod sceneaction_mod {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const None: SceneAction = SceneAction::None;
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn GoTo(scene: String) -> SceneAction {
|
|
||||||
SceneAction::GoTo(scene)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,7 +11,6 @@ pub struct SpriteElement {
|
||||||
pub ct: Rc<Content>,
|
pub ct: Rc<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove this
|
|
||||||
unsafe impl Send for SpriteElement {}
|
unsafe impl Send for SpriteElement {}
|
||||||
unsafe impl Sync for SpriteElement {}
|
unsafe impl Sync for SpriteElement {}
|
||||||
|
|
||||||
|
@ -26,18 +25,7 @@ impl SpriteElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_edge(&mut self, edge_name: &str, duration: f32) {
|
pub fn take_edge(&mut self, edge_name: &str, duration: f32) {
|
||||||
if self.sprite.as_ref().borrow().anim.is_none() {
|
let sprite_handle = self.sprite.as_ref().borrow().anim.get_sprite();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sprite_handle = self
|
|
||||||
.sprite
|
|
||||||
.as_ref()
|
|
||||||
.borrow()
|
|
||||||
.anim
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.get_sprite();
|
|
||||||
let sprite = self.ct.get_sprite(sprite_handle);
|
let sprite = self.ct.get_sprite(sprite_handle);
|
||||||
|
|
||||||
let edge = resolve_edge_as_edge(edge_name, duration, |x| {
|
let edge = resolve_edge_as_edge(edge_name, duration, |x| {
|
||||||
|
@ -59,8 +47,6 @@ impl SpriteElement {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.anim
|
.anim
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.jump_to(&self.ct, edge);
|
.jump_to(&self.ct, edge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use rhai::{CustomType, ImmutableString, TypeBuilder};
|
use rhai::{CustomType, TypeBuilder};
|
||||||
|
|
||||||
use super::Rect;
|
use super::Rect;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SpriteBuilder {
|
pub struct SpriteBuilder {
|
||||||
pub name: ImmutableString,
|
pub name: String,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub sprite: ImmutableString,
|
pub sprite: String,
|
||||||
pub mask: Option<ImmutableString>,
|
pub mask: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteBuilder {
|
impl SpriteBuilder {
|
||||||
pub fn new(name: ImmutableString, sprite: ImmutableString, rect: Rect) -> Self {
|
pub fn new(name: String, sprite: String, rect: Rect) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
rect,
|
rect,
|
||||||
|
@ -20,7 +20,7 @@ impl SpriteBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mask(&mut self, mask: ImmutableString) {
|
pub fn set_mask(&mut self, mask: String) {
|
||||||
self.mask = Some(mask);
|
self.mask = Some(mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,175 +1,7 @@
|
||||||
use galactica_content::{Ship, SystemObject, SystemObjectHandle};
|
|
||||||
use galactica_system::{
|
|
||||||
data::{self, ShipData},
|
|
||||||
phys::PhysSimShipHandle,
|
|
||||||
};
|
|
||||||
use log::error;
|
|
||||||
use rhai::{CustomType, TypeBuilder};
|
use rhai::{CustomType, TypeBuilder};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::RenderInput;
|
#[derive(Debug, Clone, CustomType)]
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ShipState {
|
|
||||||
ship: Option<PhysSimShipHandle>,
|
|
||||||
input: Rc<RenderInput>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove this
|
|
||||||
unsafe impl Send for ShipState {}
|
|
||||||
unsafe impl Sync for ShipState {}
|
|
||||||
|
|
||||||
impl ShipState {
|
|
||||||
fn get_content(&mut self) -> &Ship {
|
|
||||||
let ship = self
|
|
||||||
.input
|
|
||||||
.phys_img
|
|
||||||
.get_ship(self.ship.as_ref().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
let handle = ship.ship.get_data().get_content();
|
|
||||||
self.input.ct.get_ship(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_data(&mut self) -> &ShipData {
|
|
||||||
let ship = self
|
|
||||||
.input
|
|
||||||
.phys_img
|
|
||||||
.get_ship(self.ship.as_ref().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
ship.ship.get_data()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn landed_on(&mut self) -> SystemObjectState {
|
|
||||||
let input = self.input.clone();
|
|
||||||
match self.get_data().get_state() {
|
|
||||||
data::ShipState::Landed { target } => {
|
|
||||||
return SystemObjectState {
|
|
||||||
input,
|
|
||||||
object: Some(*target),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return SystemObjectState {
|
|
||||||
input,
|
|
||||||
object: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for ShipState {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("ShipState")
|
|
||||||
.with_fn("is_some", |s: &mut Self| s.ship.is_some())
|
|
||||||
.with_fn("is_dead", |s: &mut Self| s.get_data().get_state().is_dead())
|
|
||||||
.with_fn("is_landed", |s: &mut Self| {
|
|
||||||
s.get_data().get_state().is_landed()
|
|
||||||
})
|
|
||||||
.with_fn("is_landing", |s: &mut Self| {
|
|
||||||
s.get_data().get_state().is_landing()
|
|
||||||
})
|
|
||||||
.with_fn("is_flying", |s: &mut Self| {
|
|
||||||
s.get_data().get_state().is_flying()
|
|
||||||
})
|
|
||||||
.with_fn("is_unlanding", |s: &mut Self| {
|
|
||||||
s.get_data().get_state().is_unlanding()
|
|
||||||
})
|
|
||||||
.with_fn("is_collapsing", |s: &mut Self| {
|
|
||||||
s.get_data().get_state().is_collapsing()
|
|
||||||
})
|
|
||||||
.with_fn("name", |s: &mut Self| s.get_content().name.clone())
|
|
||||||
.with_fn("thumbnail", |s: &mut Self| s.get_content().thumb)
|
|
||||||
.with_fn("landed_on", |s: &mut Self| s.landed_on());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct SystemObjectState {
|
|
||||||
object: Option<SystemObjectHandle>,
|
|
||||||
input: Rc<RenderInput>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove this
|
|
||||||
unsafe impl Send for SystemObjectState {}
|
|
||||||
unsafe impl Sync for SystemObjectState {}
|
|
||||||
|
|
||||||
impl SystemObjectState {
|
|
||||||
fn get_content(&mut self) -> &SystemObject {
|
|
||||||
self.input.ct.get_system_object(self.object.unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for SystemObjectState {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("SystemObjectState")
|
|
||||||
//
|
|
||||||
// Get landable name
|
|
||||||
.with_fn("name", |s: &mut Self| {
|
|
||||||
s.get_content()
|
|
||||||
.name
|
|
||||||
.as_ref()
|
|
||||||
.map(|x| x.to_string())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
error!("UI called `name()` on a system object which doesn't provide one");
|
|
||||||
"".to_string()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
//
|
|
||||||
// Get landable description
|
|
||||||
.with_fn("desc", |s: &mut Self| {
|
|
||||||
s.get_content()
|
|
||||||
.desc
|
|
||||||
.as_ref()
|
|
||||||
.map(|x| x.to_string())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
error!("UI called `name()` on a system object which doesn't provide one");
|
|
||||||
"".to_string()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
//
|
|
||||||
// Get landable landscape image
|
|
||||||
.with_fn("image", |s: &mut Self| {
|
|
||||||
let handle = s.get_content().image;
|
|
||||||
if let Some(handle) = handle {
|
|
||||||
s.input.ct.get_sprite(handle).name.clone()
|
|
||||||
} else {
|
|
||||||
error!("UI called `image()` on a system object which doesn't provide one");
|
|
||||||
"".to_string()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.with_fn("is_some", |s: &mut Self| s.object.is_some());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
input: Rc<RenderInput>,
|
pub planet_landscape: String,
|
||||||
}
|
pub planet_name: String,
|
||||||
|
|
||||||
// TODO: remove this
|
|
||||||
unsafe impl Send for State {}
|
|
||||||
unsafe impl Sync for State {}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn new(input: Rc<RenderInput>) -> Self {
|
|
||||||
Self { input }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn player_ship(&mut self) -> ShipState {
|
|
||||||
ShipState {
|
|
||||||
input: self.input.clone(),
|
|
||||||
ship: self.input.player.ship.map(|x| PhysSimShipHandle(x)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CustomType for State {
|
|
||||||
fn build(mut builder: TypeBuilder<Self>) {
|
|
||||||
builder
|
|
||||||
.with_name("State")
|
|
||||||
.with_fn("player_ship", Self::player_ship);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
use rhai::{CustomType, ImmutableString, TypeBuilder};
|
use rhai::{CustomType, TypeBuilder};
|
||||||
|
|
||||||
use super::{Rect, TextBoxFont, TextBoxJustify};
|
use super::{Rect, TextBoxFont, TextBoxJustify};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TextBoxBuilder {
|
pub struct TextBoxBuilder {
|
||||||
pub name: ImmutableString,
|
pub name: String,
|
||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
pub line_height: f32,
|
pub line_height: f32,
|
||||||
pub font: TextBoxFont,
|
pub font: TextBoxFont,
|
||||||
pub justify: TextBoxJustify,
|
pub justify: TextBoxJustify,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub text: ImmutableString,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBoxBuilder {
|
impl TextBoxBuilder {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
name: ImmutableString,
|
name: String,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
font: TextBoxFont,
|
font: TextBoxFont,
|
||||||
|
@ -29,11 +29,11 @@ impl TextBoxBuilder {
|
||||||
font,
|
font,
|
||||||
justify,
|
justify,
|
||||||
rect,
|
rect,
|
||||||
text: ImmutableString::new(),
|
text: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: ImmutableString) {
|
pub fn set_text(&mut self, text: String) {
|
||||||
self.text = text
|
self.text = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,3 +60,22 @@ pub mod textboxjustify_mod {
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
pub const Left: TextBoxJustify = TextBoxJustify::Left;
|
pub const Left: TextBoxJustify = TextBoxJustify::Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum SceneAction {
|
||||||
|
None,
|
||||||
|
SceneOutfitter,
|
||||||
|
SceneLanded,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[export_module]
|
||||||
|
pub mod sceneaction_mod {
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const None: SceneAction = SceneAction::None;
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const SceneOutfitter: SceneAction = SceneAction::SceneOutfitter;
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const SceneLanded: SceneAction = SceneAction::SceneLanded;
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
None,
|
|
||||||
MouseClick,
|
|
||||||
MouseRelease,
|
|
||||||
MouseHover,
|
|
||||||
MouseUnhover,
|
|
||||||
}
|
|
|
@ -1,43 +1,100 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_system::phys::PhysSimShipHandle;
|
|
||||||
use glyphon::TextArea;
|
use glyphon::TextArea;
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error, trace};
|
||||||
use rhai::{Array, Dynamic, Engine, Scope};
|
use rhai::{Array, Dynamic, Engine, Scope, AST};
|
||||||
use std::{collections::HashSet, num::NonZeroU32, rc::Rc};
|
use std::{cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
api::{
|
api::{self, SceneAction, SpriteElement, TextBoxBuilder},
|
||||||
self, MouseClickEvent, MouseHoverEvent, PlayerShipStateEvent, SceneAction, SceneConfig,
|
util::{Sprite, TextBox},
|
||||||
SpriteElement, TextBoxBuilder,
|
|
||||||
},
|
|
||||||
event::Event,
|
|
||||||
util::{FpsIndicator, RadialBar, TextBox},
|
|
||||||
UiElement,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
ui::{
|
ui::api::{SpriteBuilder, State},
|
||||||
api::{RadialBuilder, SpriteBuilder, State},
|
|
||||||
util::Sprite,
|
|
||||||
},
|
|
||||||
RenderInput, RenderState,
|
RenderInput, RenderState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum MouseEvent {
|
||||||
|
Click,
|
||||||
|
Release,
|
||||||
|
Enter,
|
||||||
|
Leave,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MouseEvent {
|
||||||
|
pub fn is_enter(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Enter => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_click(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Click => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub(crate) enum UiScene {
|
||||||
|
Landed,
|
||||||
|
Flying,
|
||||||
|
Outfitter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for UiScene {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Flying => "flying".to_string(),
|
||||||
|
Self::Landed => "landed".to_string(),
|
||||||
|
Self::Outfitter => "outfitter".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UiElement {
|
||||||
|
Sprite(Rc<RefCell<Sprite>>),
|
||||||
|
Text(TextBox),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiElement {
|
||||||
|
pub fn new_sprite(sprite: Sprite) -> Self {
|
||||||
|
Self::Sprite(Rc::new(RefCell::new(sprite)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_text(text: TextBox) -> Self {
|
||||||
|
Self::Text(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sprite(&self) -> Option<Rc<RefCell<Sprite>>> {
|
||||||
|
match self {
|
||||||
|
Self::Sprite(s) => Some(s.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(&self) -> Option<&TextBox> {
|
||||||
|
match self {
|
||||||
|
Self::Text(t) => Some(t),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct UiManager {
|
pub(crate) struct UiManager {
|
||||||
current_scene: Option<String>,
|
current_scene: UiScene,
|
||||||
current_scene_config: SceneConfig,
|
|
||||||
engine: Engine,
|
engine: Engine,
|
||||||
scope: Scope<'static>,
|
scope: Scope<'static>,
|
||||||
elements: Vec<UiElement>,
|
elements: Vec<UiElement>,
|
||||||
ct: Rc<Content>,
|
ct: Rc<Content>,
|
||||||
|
|
||||||
last_player_state: u32,
|
|
||||||
show_timings: bool,
|
|
||||||
fps_indicator: FpsIndicator,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiManager {
|
impl UiManager {
|
||||||
pub fn new(ct: Rc<Content>, state: &mut RenderState) -> Self {
|
pub fn new(ct: Rc<Content>) -> Self {
|
||||||
let scope = Scope::new();
|
let scope = Scope::new();
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
@ -45,125 +102,73 @@ impl UiManager {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ct,
|
ct,
|
||||||
current_scene: None,
|
current_scene: UiScene::Flying,
|
||||||
current_scene_config: SceneConfig::new(),
|
|
||||||
engine,
|
engine,
|
||||||
scope,
|
scope,
|
||||||
elements: Vec::new(),
|
elements: Vec::new(),
|
||||||
show_timings: true,
|
|
||||||
fps_indicator: FpsIndicator::new(state),
|
|
||||||
last_player_state: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config(&self) -> &SceneConfig {
|
pub fn get_scene_ast(ct: &Content, scene: UiScene) -> &AST {
|
||||||
&self.current_scene_config
|
match scene {
|
||||||
|
UiScene::Landed => &ct.get_config().ui_landed_scene,
|
||||||
|
UiScene::Flying => &ct.get_config().ui_flying_scene,
|
||||||
|
UiScene::Outfitter => &ct.get_config().ui_outfitter_scene,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the current scene
|
/// Change the current scene
|
||||||
pub fn set_scene(
|
pub fn set_scene(&mut self, state: &mut RenderState, scene: UiScene) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
state: &mut RenderState,
|
|
||||||
input: Rc<RenderInput>,
|
|
||||||
scene: String,
|
|
||||||
) -> Result<()> {
|
|
||||||
if !self.ct.get_config().ui_scenes.contains_key(&scene) {
|
|
||||||
error!("tried to switch to ui scene `{scene}`, which doesn't exist");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("switching to {:?}", scene);
|
debug!("switching to {:?}", scene);
|
||||||
self.current_scene = Some(scene);
|
self.current_scene = scene;
|
||||||
self.current_scene_config = self
|
|
||||||
.engine
|
|
||||||
.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
&self
|
|
||||||
.ct
|
|
||||||
.get_config()
|
|
||||||
.ui_scenes
|
|
||||||
.get(self.current_scene.as_ref().unwrap())
|
|
||||||
.unwrap(),
|
|
||||||
"config",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
.with_context(|| format!("while handling `config()`"))
|
|
||||||
.with_context(|| format!("in ui scene `{}`", self.current_scene.as_ref().unwrap()))?;
|
|
||||||
|
|
||||||
self.scope.clear();
|
self.scope.clear();
|
||||||
self.elements.clear();
|
self.elements.clear();
|
||||||
let mut used_names = HashSet::new();
|
let mut used_names = HashSet::new();
|
||||||
|
|
||||||
let builders: Array = self
|
trace!("running init for `{}`", self.current_scene.to_string());
|
||||||
.engine
|
|
||||||
.call_fn(
|
let builders: Array = self.engine.call_fn(
|
||||||
&mut self.scope,
|
&mut self.scope,
|
||||||
&self
|
Self::get_scene_ast(&self.ct, self.current_scene),
|
||||||
.ct
|
|
||||||
.get_config()
|
|
||||||
.ui_scenes
|
|
||||||
.get(self.current_scene.as_ref().unwrap())
|
|
||||||
.unwrap(),
|
|
||||||
"init",
|
"init",
|
||||||
(State::new(input.clone()),),
|
(State {
|
||||||
)
|
planet_landscape: "ui::landscape::test".to_string(),
|
||||||
.with_context(|| format!("while running `init()`"))
|
planet_name: "Earth".to_string(),
|
||||||
.with_context(|| format!("in ui scene `{}`", self.current_scene.as_ref().unwrap()))?;
|
},),
|
||||||
|
)?;
|
||||||
|
|
||||||
trace!("found {:?} builders", builders.len());
|
trace!("found {:?} builders", builders.len());
|
||||||
|
|
||||||
for v in builders {
|
for v in builders {
|
||||||
if v.is::<SpriteBuilder>() {
|
if v.is::<SpriteBuilder>() {
|
||||||
let s = v.cast::<SpriteBuilder>();
|
let s = v.cast::<SpriteBuilder>();
|
||||||
if used_names.contains(s.name.as_str()) {
|
if used_names.contains(&s.name) {
|
||||||
error!(
|
error!(
|
||||||
"UI scene `{}` re-uses element name `{}`",
|
"UI scene `{}` re-uses element name `{}`",
|
||||||
self.current_scene.as_ref().unwrap(),
|
self.current_scene.to_string(),
|
||||||
s.name
|
s.name
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
used_names.insert(s.name.to_string());
|
used_names.insert(s.name.clone());
|
||||||
}
|
}
|
||||||
self.elements.push(UiElement::new_sprite(Sprite::new(
|
self.elements.push(UiElement::new_sprite(Sprite::new(
|
||||||
&self.ct,
|
&self.ct, s.name, s.sprite, s.mask, s.rect,
|
||||||
s.name.to_string(),
|
|
||||||
s.sprite.to_string(),
|
|
||||||
s.mask.map(|x| x.to_string()),
|
|
||||||
s.rect,
|
|
||||||
)));
|
|
||||||
} else if v.is::<RadialBuilder>() {
|
|
||||||
let r = v.cast::<RadialBuilder>();
|
|
||||||
if used_names.contains(r.name.as_str()) {
|
|
||||||
error!(
|
|
||||||
"UI scene `{}` re-uses element name `{}`",
|
|
||||||
self.current_scene.as_ref().unwrap(),
|
|
||||||
r.name
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
used_names.insert(r.name.to_string());
|
|
||||||
}
|
|
||||||
self.elements.push(UiElement::new_radialbar(RadialBar::new(
|
|
||||||
&self.ct,
|
|
||||||
r.name.to_string(),
|
|
||||||
r.stroke,
|
|
||||||
r.color,
|
|
||||||
r.rect,
|
|
||||||
r.progress,
|
|
||||||
)));
|
)));
|
||||||
} else if v.is::<TextBoxBuilder>() {
|
} else if v.is::<TextBoxBuilder>() {
|
||||||
let t = v.cast::<TextBoxBuilder>();
|
let t = v.cast::<TextBoxBuilder>();
|
||||||
if used_names.contains(t.name.as_str()) {
|
if used_names.contains(&t.name) {
|
||||||
error!(
|
error!(
|
||||||
"UI scene `{}` re-uses element name `{}`",
|
"UI scene `{}` re-uses element name `{}`",
|
||||||
self.current_scene.as_ref().unwrap(),
|
self.current_scene.to_string(),
|
||||||
t.name
|
t.name
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
used_names.insert(t.name.to_string());
|
used_names.insert(t.name.clone());
|
||||||
}
|
}
|
||||||
let mut b = TextBox::new(
|
let mut b = TextBox::new(
|
||||||
state,
|
state,
|
||||||
t.name.to_string(),
|
t.name,
|
||||||
t.font_size,
|
t.font_size,
|
||||||
t.line_height,
|
t.line_height,
|
||||||
t.font,
|
t.font,
|
||||||
|
@ -181,161 +186,65 @@ impl UiManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw all ui elements
|
/// Draw all ui elements
|
||||||
pub fn draw(&mut self, input: Rc<RenderInput>, state: &mut RenderState) -> Result<()> {
|
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) -> Result<()> {
|
||||||
// Initialize start scene if we haven't yet
|
let mut iter = self
|
||||||
if self.current_scene.is_none() {
|
.elements
|
||||||
self.set_scene(
|
.iter()
|
||||||
state,
|
.map(|x| x.sprite())
|
||||||
input.clone(),
|
.filter(|x| x.is_some())
|
||||||
self.ct.get_config().start_ui_scene.clone(),
|
.map(|x| x.unwrap());
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update timings if they're being displayed
|
let action: SceneAction = loop {
|
||||||
if self.show_timings {
|
let e = match iter.next() {
|
||||||
self.fps_indicator.step(&input, state);
|
Some(e) => e,
|
||||||
}
|
None => break SceneAction::None,
|
||||||
|
|
||||||
// Send player state change events
|
|
||||||
if {
|
|
||||||
let player = input.player.ship;
|
|
||||||
if let Some(player) = player {
|
|
||||||
let ship = input.phys_img.get_ship(&PhysSimShipHandle(player)).unwrap();
|
|
||||||
if self.last_player_state == 0
|
|
||||||
|| NonZeroU32::new(self.last_player_state).unwrap()
|
|
||||||
!= ship.ship.get_data().get_state().as_int()
|
|
||||||
{
|
|
||||||
self.last_player_state = ship.ship.get_data().get_state().as_int().into();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.last_player_state = 0;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} {
|
|
||||||
let action: Dynamic = self
|
|
||||||
.engine
|
|
||||||
.call_fn(
|
|
||||||
&mut self.scope,
|
|
||||||
&self
|
|
||||||
.ct
|
|
||||||
.get_config()
|
|
||||||
.ui_scenes
|
|
||||||
.get(self.current_scene.as_ref().unwrap())
|
|
||||||
.unwrap(),
|
|
||||||
"event",
|
|
||||||
(State::new(input.clone()), PlayerShipStateEvent {}),
|
|
||||||
)
|
|
||||||
.with_context(|| format!("while handling player state change event"))
|
|
||||||
.with_context(|| {
|
|
||||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(action) = action.try_cast::<SceneAction>() {
|
|
||||||
if self.handle_action(state, input.clone(), action)? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..self.elements.len() {
|
|
||||||
match &self.elements[i] {
|
|
||||||
UiElement::Sprite(e) => {
|
|
||||||
// Draw and update sprites
|
|
||||||
let mut sprite = (*e).borrow_mut();
|
|
||||||
sprite.step(&input, state);
|
|
||||||
sprite.push_to_buffer(&input, state);
|
|
||||||
|
|
||||||
let event = sprite.check_events(&input, state);
|
|
||||||
// we MUST drop here, since script calls mutate the sprite RefCell
|
|
||||||
drop(sprite);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::None => continue,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_arg = match event {
|
|
||||||
Event::None => unreachable!("this shouldn't happen"),
|
|
||||||
|
|
||||||
Event::MouseClick => Dynamic::from(MouseClickEvent {
|
|
||||||
down: true,
|
|
||||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
|
||||||
}),
|
|
||||||
|
|
||||||
Event::MouseRelease => Dynamic::from(MouseClickEvent {
|
|
||||||
down: false,
|
|
||||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
|
||||||
}),
|
|
||||||
|
|
||||||
Event::MouseHover => Dynamic::from(MouseHoverEvent {
|
|
||||||
enter: true,
|
|
||||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
|
||||||
}),
|
|
||||||
|
|
||||||
Event::MouseUnhover => Dynamic::from(MouseHoverEvent {
|
|
||||||
enter: false,
|
|
||||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let action: Dynamic = self
|
let mut x = (*e).borrow_mut();
|
||||||
.engine
|
let m = x.check_mouse(input, state);
|
||||||
.call_fn(
|
x.step(input, state);
|
||||||
|
x.push_to_buffer(input, state);
|
||||||
|
drop(x);
|
||||||
|
// we MUST drop here, since script calls mutate the sprite RefCell
|
||||||
|
|
||||||
|
let action: Dynamic = match m {
|
||||||
|
MouseEvent::None => Dynamic::from(SceneAction::None),
|
||||||
|
|
||||||
|
MouseEvent::Release | MouseEvent::Click => self.engine.call_fn(
|
||||||
&mut self.scope,
|
&mut self.scope,
|
||||||
&self
|
Self::get_scene_ast(&self.ct, self.current_scene),
|
||||||
.ct
|
"click",
|
||||||
.get_config()
|
(SpriteElement::new(self.ct.clone(), e.clone()), m.is_click()),
|
||||||
.ui_scenes
|
)?,
|
||||||
.get(self.current_scene.as_ref().unwrap())
|
|
||||||
.unwrap(),
|
MouseEvent::Leave | MouseEvent::Enter => self.engine.call_fn(
|
||||||
"event",
|
&mut self.scope,
|
||||||
(State::new(input.clone()), event_arg),
|
Self::get_scene_ast(&self.ct, self.current_scene),
|
||||||
)
|
"hover",
|
||||||
.with_context(|| format!("while handling event `{:?}`", event))
|
(SpriteElement::new(self.ct.clone(), e.clone()), m.is_enter()),
|
||||||
.with_context(|| {
|
)?,
|
||||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
};
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(action) = action.try_cast::<SceneAction>() {
|
if let Some(action) = action.try_cast::<SceneAction>() {
|
||||||
if self.handle_action(state, input.clone(), action)? {
|
match action {
|
||||||
return Ok(());
|
SceneAction::None => {}
|
||||||
|
_ => {
|
||||||
|
break action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
UiElement::RadialBar(e) => {
|
drop(iter);
|
||||||
// Draw and update radialbar
|
|
||||||
let mut x = (*e).borrow_mut();
|
|
||||||
x.step(&input, state);
|
|
||||||
x.push_to_buffer(&input, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
UiElement::Text(..) => {}
|
match action {
|
||||||
}
|
SceneAction::None => {}
|
||||||
|
SceneAction::SceneOutfitter => self.set_scene(state, UiScene::Outfitter)?,
|
||||||
|
SceneAction::SceneLanded => self.set_scene(state, UiScene::Landed)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do a SceneAction.
|
|
||||||
/// If this returns true, all evaluation stops immediately.
|
|
||||||
fn handle_action(
|
|
||||||
&mut self,
|
|
||||||
state: &mut RenderState,
|
|
||||||
input: Rc<RenderInput>,
|
|
||||||
action: SceneAction,
|
|
||||||
) -> Result<bool> {
|
|
||||||
Ok(match action {
|
|
||||||
SceneAction::None => false,
|
|
||||||
SceneAction::GoTo(s) => {
|
|
||||||
self.set_scene(state, input.clone(), s.clone())?;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> UiManager {
|
impl<'a> UiManager {
|
||||||
|
@ -345,27 +254,12 @@ impl<'a> UiManager {
|
||||||
input: &RenderInput,
|
input: &RenderInput,
|
||||||
state: &RenderState,
|
state: &RenderState,
|
||||||
) -> Vec<TextArea<'a>> {
|
) -> Vec<TextArea<'a>> {
|
||||||
let mut v = Vec::with_capacity(32);
|
self.elements
|
||||||
|
|
||||||
if self.current_scene.is_none() {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.show_timings {
|
|
||||||
v.push(self.fps_indicator.get_textarea(state, input))
|
|
||||||
}
|
|
||||||
|
|
||||||
for t in self
|
|
||||||
.elements
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.text())
|
.map(|x| x.text())
|
||||||
.filter(|x| x.is_some())
|
.filter(|x| x.is_some())
|
||||||
.map(|x| x.unwrap())
|
.map(|x| x.unwrap())
|
||||||
.map(|x| x.get_textarea(state, input))
|
.map(|x| x.get_textarea(state, input))
|
||||||
{
|
.collect()
|
||||||
v.push(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
mod api;
|
mod api;
|
||||||
mod event;
|
|
||||||
mod manager;
|
mod manager;
|
||||||
mod uielement;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub(crate) use manager::UiManager;
|
pub(crate) use manager::UiManager;
|
||||||
pub(crate) use uielement::UiElement;
|
pub(crate) use manager::UiScene;
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
use std::{cell::RefCell, rc::Rc};
|
|
||||||
|
|
||||||
use super::util::{RadialBar, Sprite, TextBox};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum UiElement {
|
|
||||||
Sprite(Rc<RefCell<Sprite>>),
|
|
||||||
RadialBar(Rc<RefCell<RadialBar>>),
|
|
||||||
Text(TextBox),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiElement {
|
|
||||||
pub fn new_sprite(sprite: Sprite) -> Self {
|
|
||||||
Self::Sprite(Rc::new(RefCell::new(sprite)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_radialbar(bar: RadialBar) -> Self {
|
|
||||||
Self::RadialBar(Rc::new(RefCell::new(bar)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_text(text: TextBox) -> Self {
|
|
||||||
Self::Text(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(&self) -> Option<&TextBox> {
|
|
||||||
match self {
|
|
||||||
Self::Text(t) => Some(t),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
use glyphon::{Attrs, Buffer, Color, Family, Metrics, Shaping, TextArea, TextBounds};
|
|
||||||
|
|
||||||
use crate::{RenderInput, RenderState};
|
|
||||||
|
|
||||||
pub(crate) struct FpsIndicator {
|
|
||||||
buffer: Buffer,
|
|
||||||
update_counter: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FpsIndicator {
|
|
||||||
pub fn new(state: &mut RenderState) -> Self {
|
|
||||||
let mut buffer = Buffer::new(&mut state.text_font_system, Metrics::new(7.0, 8.0));
|
|
||||||
buffer.set_size(
|
|
||||||
&mut state.text_font_system,
|
|
||||||
state.window_size.width as f32,
|
|
||||||
state.window_size.height as f32,
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
buffer,
|
|
||||||
update_counter: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FpsIndicator {
|
|
||||||
pub fn step(&mut self, input: &RenderInput, state: &mut RenderState) {
|
|
||||||
if self.update_counter > 0 {
|
|
||||||
self.update_counter -= 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.update_counter = 100;
|
|
||||||
|
|
||||||
self.buffer.set_text(
|
|
||||||
&mut state.text_font_system,
|
|
||||||
&input.timing.get_string(),
|
|
||||||
Attrs::new().family(Family::Monospace),
|
|
||||||
Shaping::Basic,
|
|
||||||
);
|
|
||||||
self.buffer.shape_until_scroll(&mut state.text_font_system);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b: 'a> FpsIndicator {
|
|
||||||
pub fn get_textarea(&'b self, _state: &RenderState, input: &RenderInput) -> TextArea<'a> {
|
|
||||||
TextArea {
|
|
||||||
buffer: &self.buffer,
|
|
||||||
left: 10.0,
|
|
||||||
top: 400.0,
|
|
||||||
scale: input.ct.get_config().ui_scale,
|
|
||||||
bounds: TextBounds {
|
|
||||||
left: 10,
|
|
||||||
top: 400,
|
|
||||||
right: 300,
|
|
||||||
bottom: 800,
|
|
||||||
},
|
|
||||||
default_color: Color::rgb(255, 255, 255),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,5 @@
|
||||||
mod fpsindicator;
|
|
||||||
mod radialbar;
|
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod textbox;
|
mod textbox;
|
||||||
|
|
||||||
pub(super) use fpsindicator::*;
|
pub use sprite::*;
|
||||||
pub(super) use radialbar::*;
|
pub use textbox::*;
|
||||||
pub(super) use sprite::*;
|
|
||||||
pub(super) use textbox::*;
|
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
use galactica_content::Content;
|
|
||||||
use std::f32::consts::TAU;
|
|
||||||
|
|
||||||
use super::super::api::Rect;
|
|
||||||
use crate::{ui::api::Color, vertexbuffer::types::RadialBarInstance, RenderInput, RenderState};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RadialBar {
|
|
||||||
pub name: String,
|
|
||||||
pub rect: Rect,
|
|
||||||
pub stroke: f32,
|
|
||||||
pub color: Color,
|
|
||||||
pub progress: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RadialBar {
|
|
||||||
pub fn new(
|
|
||||||
_ct: &Content,
|
|
||||||
name: String,
|
|
||||||
stroke: f32,
|
|
||||||
color: Color,
|
|
||||||
rect: Rect,
|
|
||||||
progress: f32,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
rect,
|
|
||||||
stroke,
|
|
||||||
color,
|
|
||||||
progress,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
|
||||||
let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
|
||||||
|
|
||||||
state.push_radialbar_buffer(RadialBarInstance {
|
|
||||||
position: [rect.pos.x, rect.pos.y],
|
|
||||||
diameter: rect.dim.x.min(rect.dim.y),
|
|
||||||
stroke: self.stroke * input.ct.get_config().ui_scale,
|
|
||||||
color: self.color.as_array(),
|
|
||||||
angle: self.progress * TAU,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn step(&mut self, _input: &RenderInput, _state: &mut RenderState) {}
|
|
||||||
}
|
|
|
@ -1,17 +1,14 @@
|
||||||
use galactica_content::{Content, SpriteAutomaton, SpriteHandle};
|
use galactica_content::{Content, SpriteAutomaton, SpriteHandle};
|
||||||
use galactica_util::to_radians;
|
use galactica_util::to_radians;
|
||||||
use log::error;
|
|
||||||
|
|
||||||
use super::super::api::Rect;
|
use super::super::api::Rect;
|
||||||
use crate::{ui::event::Event, vertexbuffer::types::UiInstance, RenderInput, RenderState};
|
use crate::{ui::manager::MouseEvent, vertexbuffer::types::UiInstance, RenderInput, RenderState};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sprite {
|
pub struct Sprite {
|
||||||
/// If this is none, this was constructed with an invalid sprite
|
pub anim: SpriteAutomaton,
|
||||||
pub anim: Option<SpriteAutomaton>,
|
pub rect: Rect,
|
||||||
|
pub mask: Option<SpriteHandle>,
|
||||||
rect: Rect,
|
|
||||||
mask: Option<SpriteHandle>,
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
has_mouse: bool,
|
has_mouse: bool,
|
||||||
|
@ -28,24 +25,10 @@ impl Sprite {
|
||||||
mask: Option<String>,
|
mask: Option<String>,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let sprite_handle = ct.get_sprite_handle(&sprite);
|
let sprite_handle = ct.get_sprite_handle(&sprite).unwrap(); // TODO: errors
|
||||||
|
|
||||||
if sprite_handle.is_none() {
|
|
||||||
error!("UI constructed a sprite named `{name}` using an invalid source `{sprite}`")
|
|
||||||
}
|
|
||||||
|
|
||||||
let mask = {
|
let mask = {
|
||||||
if mask.is_some() {
|
if mask.is_some() {
|
||||||
let m = ct.get_sprite_handle(mask.as_ref().unwrap());
|
Some(ct.get_sprite_handle(&mask.unwrap()).unwrap())
|
||||||
if m.is_none() {
|
|
||||||
error!(
|
|
||||||
"UI constructed a sprite named `{name}` using an invalid mask` {}`",
|
|
||||||
mask.unwrap()
|
|
||||||
);
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
m
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -53,7 +36,7 @@ impl Sprite {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
anim: sprite_handle.map(|x| SpriteAutomaton::new(&ct, x)),
|
anim: SpriteAutomaton::new(&ct, sprite_handle),
|
||||||
rect,
|
rect,
|
||||||
mask,
|
mask,
|
||||||
has_mouse: false,
|
has_mouse: false,
|
||||||
|
@ -63,17 +46,14 @@ impl Sprite {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
|
||||||
if self.anim.is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
||||||
|
|
||||||
// TODO: use both dimensions,
|
// TODO: use both dimensions,
|
||||||
// not just height
|
// not just height
|
||||||
let anim_state = self.anim.as_ref().unwrap().get_texture_idx();
|
let anim_state = self.anim.get_texture_idx();
|
||||||
|
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
|
anchor: crate::PositionAnchor::CC.to_int(),
|
||||||
position: rect.pos.into(),
|
position: rect.pos.into(),
|
||||||
angle: to_radians(90.0),
|
angle: to_radians(90.0),
|
||||||
dim: rect.dim.into(),
|
dim: rect.dim.into(),
|
||||||
|
@ -91,7 +71,7 @@ impl Sprite {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event {
|
pub fn check_mouse(&mut self, input: &RenderInput, state: &mut RenderState) -> MouseEvent {
|
||||||
let r = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
let r = self.rect.to_centered(state, input.ct.get_config().ui_scale);
|
||||||
|
|
||||||
if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
|
if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
|
||||||
|
@ -104,18 +84,18 @@ impl Sprite {
|
||||||
&& input.player.input.pressed_leftclick()
|
&& input.player.input.pressed_leftclick()
|
||||||
{
|
{
|
||||||
self.has_click = true;
|
self.has_click = true;
|
||||||
return Event::MouseClick;
|
return MouseEvent::Click;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.has_mouse && self.has_click && !input.player.input.pressed_leftclick() {
|
if self.has_mouse && self.has_click && !input.player.input.pressed_leftclick() {
|
||||||
self.has_click = false;
|
self.has_click = false;
|
||||||
return Event::MouseRelease;
|
return MouseEvent::Release;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release mouse when cursor leaves box
|
// Release mouse when cursor leaves box
|
||||||
if self.has_click && !self.has_mouse {
|
if self.has_click && !self.has_mouse {
|
||||||
self.has_click = false;
|
self.has_click = false;
|
||||||
return Event::MouseRelease;
|
return MouseEvent::Release;
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.contains_mouse(input, state) && !self.has_mouse {
|
if r.contains_mouse(input, state) && !self.has_mouse {
|
||||||
|
@ -125,26 +105,19 @@ impl Sprite {
|
||||||
self.waiting_for_release = true;
|
self.waiting_for_release = true;
|
||||||
}
|
}
|
||||||
self.has_mouse = true;
|
self.has_mouse = true;
|
||||||
return Event::MouseHover;
|
return MouseEvent::Enter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.contains_mouse(input, state) && self.has_mouse {
|
if !r.contains_mouse(input, state) && self.has_mouse {
|
||||||
self.waiting_for_release = false;
|
self.waiting_for_release = false;
|
||||||
self.has_mouse = false;
|
self.has_mouse = false;
|
||||||
return Event::MouseUnhover;
|
return MouseEvent::Leave;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event::None;
|
return MouseEvent::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
||||||
if self.anim.is_none() {
|
self.anim.step(&input.ct, input.time_since_last_run);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.anim
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.step(&input.ct, input.time_since_last_run);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,10 @@ impl BufferObject for ObjectInstance {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct UiInstance {
|
pub struct UiInstance {
|
||||||
|
/// How to interpret this object's coordinates.
|
||||||
|
/// this should be generated from an AnchoredUiPosition
|
||||||
|
pub anchor: u32,
|
||||||
|
|
||||||
/// Position of this object in logical pixels
|
/// Position of this object in logical pixels
|
||||||
pub position: [f32; 2],
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
@ -179,46 +183,52 @@ impl BufferObject for UiInstance {
|
||||||
// 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: &[
|
||||||
|
// Anchor
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: 0,
|
||||||
|
shader_location: 2,
|
||||||
|
format: wgpu::VertexFormat::Uint32,
|
||||||
|
},
|
||||||
// Position
|
// Position
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 0]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 1]>() as wgpu::BufferAddress,
|
||||||
shader_location: 2,
|
shader_location: 3,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
// Angle
|
// Angle
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||||
shader_location: 3,
|
shader_location: 4,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Dim
|
// Dim
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||||
shader_location: 4,
|
shader_location: 5,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
// Color
|
// Color
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
||||||
shader_location: 5,
|
shader_location: 6,
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
},
|
},
|
||||||
// Texture
|
// Texture
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
|
||||||
shader_location: 6,
|
shader_location: 7,
|
||||||
format: wgpu::VertexFormat::Uint32x2,
|
format: wgpu::VertexFormat::Uint32x2,
|
||||||
},
|
},
|
||||||
// Texture fade
|
// Texture fade
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||||
shader_location: 7,
|
shader_location: 8,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Mask
|
// Mask
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 13]>() as wgpu::BufferAddress,
|
||||||
shader_location: 8,
|
shader_location: 9,
|
||||||
format: wgpu::VertexFormat::Uint32x2,
|
format: wgpu::VertexFormat::Uint32x2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -229,6 +239,10 @@ impl BufferObject for UiInstance {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct RadialBarInstance {
|
pub struct RadialBarInstance {
|
||||||
|
/// How to interpret this object's coordinates.
|
||||||
|
/// this should be generated from an AnchoredUiPosition
|
||||||
|
pub anchor: u32,
|
||||||
|
|
||||||
/// Position of this object in logical pixels
|
/// Position of this object in logical pixels
|
||||||
pub position: [f32; 2],
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
@ -253,34 +267,40 @@ impl BufferObject for RadialBarInstance {
|
||||||
array_stride: Self::SIZE,
|
array_stride: Self::SIZE,
|
||||||
step_mode: wgpu::VertexStepMode::Instance,
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
attributes: &[
|
attributes: &[
|
||||||
|
// Anchor
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: 0,
|
||||||
|
shader_location: 2,
|
||||||
|
format: wgpu::VertexFormat::Uint32,
|
||||||
|
},
|
||||||
// Position
|
// Position
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 0]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 1]>() as wgpu::BufferAddress,
|
||||||
shader_location: 2,
|
shader_location: 3,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
// Diameter
|
// Diameter
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 3,
|
|
||||||
format: wgpu::VertexFormat::Float32,
|
|
||||||
},
|
|
||||||
// Width
|
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||||
shader_location: 4,
|
shader_location: 4,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Angle
|
// Width
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||||
shader_location: 5,
|
shader_location: 5,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Color
|
// Angle
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
||||||
shader_location: 6,
|
shader_location: 6,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Color
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 7,
|
||||||
format: wgpu::VertexFormat::Float32x4,
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::num::NonZeroU32;
|
|
||||||
|
|
||||||
use galactica_content::SystemObjectHandle;
|
use galactica_content::SystemObjectHandle;
|
||||||
use rapier2d::math::Isometry;
|
use rapier2d::math::Isometry;
|
||||||
|
|
||||||
|
@ -75,66 +73,4 @@ impl ShipState {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An integer representing each state.
|
|
||||||
/// Makes detecting state changes cheap and easy.
|
|
||||||
pub fn as_int(&self) -> NonZeroU32 {
|
|
||||||
NonZeroU32::new(match self {
|
|
||||||
Self::Dead => 1,
|
|
||||||
Self::Collapsing => 2,
|
|
||||||
Self::Flying { .. } => 3,
|
|
||||||
Self::Landed { .. } => 4,
|
|
||||||
Self::Landing { .. } => 5,
|
|
||||||
Self::UnLanding { .. } => 6,
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is this state Dead?
|
|
||||||
pub fn is_dead(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Dead => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is this state Collapsing?
|
|
||||||
pub fn is_collapsing(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Collapsing => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is this state Flying?
|
|
||||||
pub fn is_flying(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Flying { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is this state Landed?
|
|
||||||
pub fn is_landed(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Landed { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is this state Landing?
|
|
||||||
pub fn is_landing(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Landing { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Is this state UnLanding?
|
|
||||||
pub fn is_unlanding(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::UnLanding { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue