Improved scenes & event handling
parent
a6a0884737
commit
d354a88543
|
@ -1,6 +1,11 @@
|
|||
fn init(state) {
|
||||
|
||||
fn config() {
|
||||
let config = SceneConfig();
|
||||
config.show_starfield(true);
|
||||
config.show_phys(true);
|
||||
return config
|
||||
}
|
||||
|
||||
fn init(state) {
|
||||
let ring = SpriteBuilder(
|
||||
"ring",
|
||||
"ui::status",
|
||||
|
@ -40,5 +45,11 @@ fn init(state) {
|
|||
];
|
||||
}
|
||||
|
||||
fn hover(element, hover_state) {}
|
||||
fn click(element, click_state) {}
|
||||
fn event(state, event) {
|
||||
if type_of(event) == "PlayerShipStateEvent" {
|
||||
if state.player_ship().is_landed() {
|
||||
return SceneAction::GoTo("landed");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,9 @@
|
|||
fn config() {
|
||||
let config = SceneConfig();
|
||||
config.show_starfield(true);
|
||||
config.show_phys(false);
|
||||
return config
|
||||
}
|
||||
|
||||
fn init(state) {
|
||||
let frame = SpriteBuilder(
|
||||
|
@ -12,7 +18,7 @@ fn init(state) {
|
|||
|
||||
let landscape = SpriteBuilder(
|
||||
"landscape",
|
||||
state.planet_landscape,
|
||||
"ui::landscape::test",
|
||||
Rect(
|
||||
-180.0, 142.0, 274.0, 135.0,
|
||||
SpriteAnchor::NorthWest,
|
||||
|
@ -40,7 +46,7 @@ fn init(state) {
|
|||
SpriteAnchor::Center
|
||||
)
|
||||
);
|
||||
title.set_text(state.planet_name);
|
||||
title.set_text("Title");
|
||||
|
||||
return [
|
||||
button,
|
||||
|
@ -50,22 +56,36 @@ fn init(state) {
|
|||
];
|
||||
}
|
||||
|
||||
fn hover(element, hover_state) {
|
||||
if element.has_name("button") {
|
||||
if hover_state {
|
||||
element.take_edge("on:top", 0.1);
|
||||
} else {
|
||||
element.take_edge("off:top", 0.1);
|
||||
fn event(state, event) {
|
||||
if type_of(event) == "MouseHoverEvent" {
|
||||
let element = event.element();
|
||||
if element.has_name("button") {
|
||||
if event.is_enter() {
|
||||
element.take_edge("on:top", 0.1);
|
||||
} else {
|
||||
element.take_edge("off:top", 0.1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn click(element, click_state) {
|
||||
if !click_state {
|
||||
return SceneAction::None;
|
||||
|
||||
|
||||
if type_of(event) == "MouseClickEvent" {
|
||||
if !event.is_down() {
|
||||
return SceneAction::None;
|
||||
}
|
||||
|
||||
let element = event.element();
|
||||
if element.has_name("button") {
|
||||
return SceneAction::GoTo("outfitter");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if element.has_name("button") {
|
||||
return SceneAction::SceneOutfitter;
|
||||
if type_of(event) == "PlayerShipStateEvent" {
|
||||
if !state.player_ship().is_landed() {
|
||||
return SceneAction::GoTo("flying");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,9 @@
|
|||
fn config() {
|
||||
let config = SceneConfig();
|
||||
config.show_starfield(true);
|
||||
config.show_phys(false);
|
||||
return config
|
||||
}
|
||||
|
||||
fn init(state) {
|
||||
let se_box = SpriteBuilder(
|
||||
|
@ -19,7 +25,7 @@ fn init(state) {
|
|||
SpriteAnchor::SouthWest
|
||||
)
|
||||
);
|
||||
exit_text.set_text(state.planet_name);
|
||||
exit_text.set_text("Earth");
|
||||
|
||||
let exit_button = SpriteBuilder(
|
||||
"exit_button",
|
||||
|
@ -64,7 +70,7 @@ fn init(state) {
|
|||
SpriteAnchor::NorthWest
|
||||
)
|
||||
);
|
||||
ship_name.set_text(state.planet_name);
|
||||
ship_name.set_text("Earth");
|
||||
|
||||
let ship_type = TextBoxBuilder(
|
||||
"ship_type",
|
||||
|
@ -75,7 +81,7 @@ fn init(state) {
|
|||
SpriteAnchor::NorthWest
|
||||
)
|
||||
);
|
||||
ship_type.set_text(state.planet_name);
|
||||
ship_type.set_text("Earth");
|
||||
|
||||
let ship_stats = TextBoxBuilder(
|
||||
"ship_stats",
|
||||
|
@ -86,7 +92,7 @@ fn init(state) {
|
|||
SpriteAnchor::NorthWest,
|
||||
)
|
||||
);
|
||||
ship_stats.set_text(state.planet_name);
|
||||
ship_stats.set_text("Earth");
|
||||
|
||||
|
||||
|
||||
|
@ -123,7 +129,7 @@ fn init(state) {
|
|||
SpriteAnchor::NorthEast,
|
||||
)
|
||||
);
|
||||
outfit_name.set_text(state.planet_name);
|
||||
outfit_name.set_text("Earth");
|
||||
|
||||
let outfit_desc = TextBoxBuilder(
|
||||
"outfit_desc",
|
||||
|
@ -134,7 +140,7 @@ fn init(state) {
|
|||
SpriteAnchor::NorthEast,
|
||||
)
|
||||
);
|
||||
outfit_desc.set_text(state.planet_name);
|
||||
outfit_desc.set_text("Earth");
|
||||
|
||||
let outfit_stats = TextBoxBuilder(
|
||||
"outfit_stats",
|
||||
|
@ -145,7 +151,7 @@ fn init(state) {
|
|||
SpriteAnchor::NorthEast,
|
||||
)
|
||||
);
|
||||
outfit_stats.set_text(state.planet_name);
|
||||
outfit_stats.set_text("Earth");
|
||||
|
||||
return [
|
||||
ship_bg,
|
||||
|
@ -166,22 +172,37 @@ fn init(state) {
|
|||
];
|
||||
}
|
||||
|
||||
fn hover(element, hover_state) {
|
||||
if element.has_name("exit_button") {
|
||||
if hover_state {
|
||||
element.take_edge("on:top", 0.1);
|
||||
} else {
|
||||
element.take_edge("off:top", 0.1);
|
||||
|
||||
fn event(state, event) {
|
||||
if type_of(event) == "MouseHoverEvent" {
|
||||
let element = event.element();
|
||||
if element.has_name("exit_button") {
|
||||
if event.is_enter() {
|
||||
element.take_edge("on:top", 0.1);
|
||||
} else {
|
||||
element.take_edge("off:top", 0.1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn click(element, click_state) {
|
||||
if !click_state {
|
||||
return SceneAction::None;
|
||||
|
||||
|
||||
if type_of(event) == "MouseClickEvent" {
|
||||
if !event.is_down() {
|
||||
return SceneAction::None;
|
||||
}
|
||||
|
||||
let element = event.element();
|
||||
if element.has_name("exit_button") {
|
||||
return SceneAction::GoTo("landed");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if element.has_name("exit_button") {
|
||||
return SceneAction::SceneLanded;
|
||||
if type_of(event) == "PlayerShipStateEvent" {
|
||||
if !state.player_ship().is_landed() {
|
||||
return SceneAction::GoTo("flying");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ use anyhow::{bail, Result};
|
|||
use clap::Parser;
|
||||
use galactica_content::{Content, SystemHandle};
|
||||
use galactica_playeragent::{PlayerAgent, PlayerStatus};
|
||||
use galactica_render::{RenderInput, RenderScenes};
|
||||
use galactica_render::RenderInput;
|
||||
use galactica_system::{
|
||||
data::ShipState,
|
||||
phys::{PhysImage, PhysSimShipHandle},
|
||||
|
@ -122,21 +122,19 @@ fn try_main() -> Result<()> {
|
|||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, content.clone()))?;
|
||||
gpu.set_scene(RenderScenes::System);
|
||||
gpu.init(&content);
|
||||
|
||||
// TODO: don't clone content
|
||||
let mut game = game::Game::new(content.clone());
|
||||
let p = game.make_player();
|
||||
|
||||
let mut player = PlayerAgent::new(p.0);
|
||||
player.set_camera_aspect(
|
||||
let mut player = Rc::new(PlayerAgent::new(p.0));
|
||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
|
||||
);
|
||||
|
||||
let mut phys_img = PhysImage::new();
|
||||
let mut phys_img = Rc::new(PhysImage::new());
|
||||
let mut last_run = Instant::now();
|
||||
let mut was_landed = false;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
|
@ -146,8 +144,8 @@ fn try_main() -> Result<()> {
|
|||
camera_zoom: player.camera.zoom,
|
||||
current_time: game.get_current_time(),
|
||||
ct: content.clone(),
|
||||
phys_img: &phys_img,
|
||||
player: &player,
|
||||
phys_img: phys_img.clone(),
|
||||
player: player.clone(),
|
||||
time_since_last_run: last_run.elapsed().as_secs_f32(),
|
||||
current_system: SystemHandle { index: 0 },
|
||||
timing: game.get_timing().clone(),
|
||||
|
@ -164,9 +162,9 @@ fn try_main() -> Result<()> {
|
|||
}
|
||||
|
||||
Event::MainEventsCleared => {
|
||||
game.update_player_controls(&mut player);
|
||||
game.update_player_controls(Rc::get_mut(&mut player).unwrap());
|
||||
game.step(&phys_img);
|
||||
game.update_image(&mut phys_img);
|
||||
game.update_image(Rc::get_mut(&mut phys_img).unwrap());
|
||||
|
||||
// TODO: clean up
|
||||
let player_status = {
|
||||
|
@ -177,21 +175,9 @@ fn try_main() -> Result<()> {
|
|||
ShipState::Landing { .. }
|
||||
| ShipState::UnLanding { .. }
|
||||
| ShipState::Collapsing { .. }
|
||||
| ShipState::Flying { .. } => {
|
||||
if was_landed {
|
||||
was_landed = false;
|
||||
gpu.set_scene(RenderScenes::System);
|
||||
}
|
||||
|
||||
Some(*o.rigidbody.translation())
|
||||
}
|
||||
| ShipState::Flying { .. } => Some(*o.rigidbody.translation()),
|
||||
|
||||
ShipState::Landed { target } => {
|
||||
if !was_landed {
|
||||
was_landed = true;
|
||||
gpu.set_scene(RenderScenes::Landed);
|
||||
}
|
||||
|
||||
let b = content.get_system_object(*target);
|
||||
Some(Vector2::new(b.pos.x, b.pos.y))
|
||||
}
|
||||
|
@ -207,9 +193,10 @@ fn try_main() -> Result<()> {
|
|||
};
|
||||
|
||||
// This must be updated BEFORE rendering!
|
||||
player.step(&content, player_status);
|
||||
|
||||
player.input.clear_inputs();
|
||||
Rc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.step(&content, player_status);
|
||||
Rc::get_mut(&mut player).unwrap().input.clear_inputs();
|
||||
gpu.window().request_redraw();
|
||||
}
|
||||
|
||||
|
@ -230,27 +217,39 @@ fn try_main() -> Result<()> {
|
|||
},
|
||||
..
|
||||
} => {
|
||||
player.input.process_key(state, key);
|
||||
Rc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.input
|
||||
.process_key(state, key);
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
player.input.process_mouse(position);
|
||||
Rc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.input
|
||||
.process_mouse(position);
|
||||
}
|
||||
WindowEvent::MouseInput { state, button, .. } => {
|
||||
player.input.process_click(state, button);
|
||||
Rc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.input
|
||||
.process_click(state, button);
|
||||
}
|
||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||
player.input.process_scroll(delta, phase);
|
||||
Rc::get_mut(&mut player)
|
||||
.unwrap()
|
||||
.input
|
||||
.process_scroll(delta, phase);
|
||||
}
|
||||
WindowEvent::Resized(_) => {
|
||||
gpu.resize(&content);
|
||||
player.set_camera_aspect(
|
||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32
|
||||
/ gpu.window().inner_size().height as f32,
|
||||
);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { .. } => {
|
||||
gpu.resize(&content);
|
||||
player.set_camera_aspect(
|
||||
Rc::get_mut(&mut player).unwrap().set_camera_aspect(
|
||||
gpu.window().inner_size().width as f32
|
||||
/ gpu.window().inner_size().height as f32,
|
||||
);
|
||||
|
|
|
@ -3,6 +3,7 @@ use winit::{
|
|||
event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputStatus {
|
||||
// Parameters
|
||||
scroll_speed: f32,
|
||||
|
|
|
@ -25,6 +25,7 @@ impl PlayerSelection {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlayerAgent {
|
||||
/// Which ship this player is controlling
|
||||
pub ship: Option<ColliderHandle>,
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
use std::rc::Rc;
|
||||
use std::{iter, rc::Rc};
|
||||
|
||||
use anyhow::Result;
|
||||
use bytemuck;
|
||||
use galactica_content::Content;
|
||||
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
||||
use log::debug;
|
||||
use galactica_system::data::ShipState;
|
||||
use galactica_util::to_radians;
|
||||
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
||||
use nalgebra::{Point2, Point3};
|
||||
use wgpu;
|
||||
use winit;
|
||||
|
||||
use crate::{
|
||||
globaluniform::{GlobalDataContent, GlobalUniform},
|
||||
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
||||
pipeline::PipelineBuilder,
|
||||
renderscene::{LandedScene, RenderScene, SystemScene},
|
||||
shaderprocessor::preprocess_shader,
|
||||
starfield::Starfield,
|
||||
texturearray::TextureArray,
|
||||
ui::{UiManager, UiScene},
|
||||
RenderInput, RenderScenes, RenderState, VertexBuffers,
|
||||
ui::UiManager,
|
||||
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
||||
RenderInput, RenderState, VertexBuffers,
|
||||
};
|
||||
|
||||
/// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures.
|
||||
|
@ -32,7 +34,6 @@ pub struct GPUState {
|
|||
pub(crate) texture_array: TextureArray,
|
||||
pub(crate) state: RenderState,
|
||||
pub(crate) ui: UiManager,
|
||||
pub(crate) scene: RenderScenes,
|
||||
}
|
||||
|
||||
impl GPUState {
|
||||
|
@ -238,7 +239,6 @@ impl GPUState {
|
|||
starfield_pipeline,
|
||||
ui_pipeline,
|
||||
radialbar_pipeline,
|
||||
scene: RenderScenes::Landed,
|
||||
state,
|
||||
});
|
||||
}
|
||||
|
@ -250,18 +250,6 @@ impl GPUState {
|
|||
&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.
|
||||
/// This should be called whenever our window is resized.
|
||||
pub fn resize(&mut self, ct: &Content) {
|
||||
|
@ -290,6 +278,8 @@ impl GPUState {
|
|||
|
||||
/// Main render function. Draws sprites on a window.
|
||||
pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> {
|
||||
let input = Rc::new(input);
|
||||
|
||||
// Update global values
|
||||
self.state.queue.write_buffer(
|
||||
&self.state.global_uniform.data_buffer,
|
||||
|
@ -313,11 +303,453 @@ impl GPUState {
|
|||
|
||||
self.state.frame_reset();
|
||||
|
||||
match self.scene {
|
||||
RenderScenes::System => SystemScene::render(self, &input).unwrap(),
|
||||
RenderScenes::Landed => LandedScene::render(self, &input).unwrap(),
|
||||
let output = self.surface.get_current_texture()?;
|
||||
let view = output.texture.create_view(&Default::default());
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("render encoder"),
|
||||
});
|
||||
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("render pass"),
|
||||
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
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(());
|
||||
}
|
||||
}
|
||||
|
||||
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()],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ mod gpustate;
|
|||
mod pipeline;
|
||||
mod positionanchor;
|
||||
mod renderinput;
|
||||
mod renderscene;
|
||||
mod renderstate;
|
||||
mod shaderprocessor;
|
||||
mod starfield;
|
||||
|
@ -23,7 +22,6 @@ mod vertexbuffer;
|
|||
pub use gpustate::GPUState;
|
||||
pub use positionanchor::PositionAnchor;
|
||||
pub use renderinput::RenderInput;
|
||||
pub use renderscene::RenderScenes;
|
||||
use renderstate::*;
|
||||
|
||||
use nalgebra::Matrix4;
|
||||
|
|
|
@ -7,12 +7,13 @@ use galactica_util::timing::Timing;
|
|||
use nalgebra::Vector2;
|
||||
|
||||
/// Bundles parameters passed to a single call to GPUState::render
|
||||
pub struct RenderInput<'a> {
|
||||
#[derive(Debug)]
|
||||
pub struct RenderInput {
|
||||
/// Camera position, in world units
|
||||
pub camera_pos: Vector2<f32>,
|
||||
|
||||
/// Player ship data
|
||||
pub player: &'a PlayerAgent,
|
||||
pub player: Rc<PlayerAgent>,
|
||||
|
||||
/// The system we're currently in
|
||||
pub current_system: SystemHandle,
|
||||
|
@ -21,7 +22,7 @@ pub struct RenderInput<'a> {
|
|||
pub camera_zoom: f32,
|
||||
|
||||
/// The world state to render
|
||||
pub phys_img: &'a PhysImage,
|
||||
pub phys_img: Rc<PhysImage>,
|
||||
|
||||
// TODO: handle overflow. is it a problem?
|
||||
/// The current time, in seconds
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
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(());
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
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"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
mod phys;
|
||||
mod system;
|
||||
|
||||
pub use system::SystemScene;
|
|
@ -1,321 +0,0 @@
|
|||
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()],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
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(());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
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
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
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,4 +1,6 @@
|
|||
mod color;
|
||||
mod config;
|
||||
mod event;
|
||||
mod radialbuilder;
|
||||
mod rect;
|
||||
mod sceneaction;
|
||||
|
@ -9,6 +11,8 @@ mod textboxbuilder;
|
|||
mod util;
|
||||
|
||||
pub use color::*;
|
||||
pub use config::*;
|
||||
pub use event::*;
|
||||
pub use radialbuilder::*;
|
||||
pub use rect::*;
|
||||
pub use sceneaction::*;
|
||||
|
@ -23,12 +27,17 @@ use rhai::{exported_module, Engine};
|
|||
pub fn register_into_engine(engine: &mut Engine) {
|
||||
engine
|
||||
.build_type::<State>()
|
||||
.build_type::<ShipState>()
|
||||
.build_type::<Rect>()
|
||||
.build_type::<Color>()
|
||||
.build_type::<RadialBuilder>()
|
||||
.build_type::<SpriteElement>()
|
||||
.build_type::<SceneConfig>()
|
||||
.build_type::<SpriteBuilder>()
|
||||
.build_type::<TextBoxBuilder>()
|
||||
.build_type::<MouseClickEvent>()
|
||||
.build_type::<MouseHoverEvent>()
|
||||
.build_type::<PlayerShipStateEvent>()
|
||||
.register_type_with_name::<SpriteAnchor>("SpriteAnchor")
|
||||
.register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into())
|
||||
.register_type_with_name::<TextBoxFont>("TextBoxFont")
|
||||
|
|
|
@ -3,8 +3,7 @@ use rhai::plugin::*;
|
|||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum SceneAction {
|
||||
None,
|
||||
SceneOutfitter,
|
||||
SceneLanded,
|
||||
GoTo(String),
|
||||
}
|
||||
|
||||
#[export_module]
|
||||
|
@ -12,9 +11,8 @@ 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;
|
||||
#[allow(non_snake_case)]
|
||||
pub fn GoTo(scene: String) -> SceneAction {
|
||||
SceneAction::GoTo(scene)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ pub struct SpriteElement {
|
|||
pub ct: Rc<Content>,
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
unsafe impl Send for SpriteElement {}
|
||||
unsafe impl Sync for SpriteElement {}
|
||||
|
||||
|
|
|
@ -1,7 +1,72 @@
|
|||
use galactica_system::{data, phys::PhysSimShipHandle};
|
||||
use rhai::{CustomType, TypeBuilder};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone, CustomType)]
|
||||
pub struct State {
|
||||
pub planet_landscape: String,
|
||||
pub planet_name: String,
|
||||
use crate::RenderInput;
|
||||
|
||||
#[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 {
|
||||
pub fn get_state(&mut self) -> &data::ShipState {
|
||||
let ship = self
|
||||
.input
|
||||
.phys_img
|
||||
.get_ship(self.ship.as_ref().unwrap())
|
||||
.unwrap();
|
||||
ship.ship.get_data().get_state()
|
||||
}
|
||||
}
|
||||
|
||||
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_state().is_dead())
|
||||
.with_fn("is_landed", |s: &mut Self| s.get_state().is_landed())
|
||||
.with_fn("is_landing", |s: &mut Self| s.get_state().is_landing())
|
||||
.with_fn("is_flying", |s: &mut Self| s.get_state().is_flying())
|
||||
.with_fn("is_unlanding", |s: &mut Self| s.get_state().is_unlanding())
|
||||
.with_fn("is_collapsing", |s: &mut Self| {
|
||||
s.get_state().is_collapsing()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State {
|
||||
input: Rc<RenderInput>,
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Event {
|
||||
None,
|
||||
MouseClick,
|
||||
MouseRelease,
|
||||
MouseHover,
|
||||
MouseUnhover,
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use galactica_content::Content;
|
||||
use galactica_system::phys::PhysSimShipHandle;
|
||||
use glyphon::TextArea;
|
||||
use log::{debug, error, trace};
|
||||
use rhai::{Array, Dynamic, Engine, Scope, AST};
|
||||
use std::{collections::HashSet, rc::Rc};
|
||||
use rhai::{Array, Dynamic, Engine, Scope};
|
||||
use std::{collections::HashSet, num::NonZeroU32, rc::Rc};
|
||||
|
||||
use super::{
|
||||
api::{self, SceneAction, SpriteElement, TextBoxBuilder},
|
||||
mouseevent::MouseEvent,
|
||||
api::{
|
||||
self, MouseClickEvent, MouseHoverEvent, PlayerShipStateEvent, SceneAction, SceneConfig,
|
||||
SpriteElement, TextBoxBuilder,
|
||||
},
|
||||
event::Event,
|
||||
util::{FpsIndicator, RadialBar, TextBox},
|
||||
UiElement, UiScene,
|
||||
UiElement,
|
||||
};
|
||||
use crate::{
|
||||
ui::{
|
||||
|
@ -20,12 +24,14 @@ use crate::{
|
|||
};
|
||||
|
||||
pub(crate) struct UiManager {
|
||||
current_scene: UiScene,
|
||||
current_scene: Option<String>,
|
||||
current_scene_config: SceneConfig,
|
||||
engine: Engine,
|
||||
scope: Scope<'static>,
|
||||
elements: Vec<UiElement>,
|
||||
ct: Rc<Content>,
|
||||
|
||||
last_player_state: u32,
|
||||
show_timings: bool,
|
||||
fps_indicator: FpsIndicator,
|
||||
}
|
||||
|
@ -39,43 +45,70 @@ impl UiManager {
|
|||
|
||||
Self {
|
||||
ct,
|
||||
current_scene: UiScene::Flying,
|
||||
current_scene: None,
|
||||
current_scene_config: SceneConfig::new(),
|
||||
engine,
|
||||
scope,
|
||||
elements: Vec::new(),
|
||||
show_timings: true,
|
||||
fps_indicator: FpsIndicator::new(state),
|
||||
last_player_state: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scene_ast(ct: &Content, scene: UiScene) -> &AST {
|
||||
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,
|
||||
}
|
||||
pub fn get_config(&self) -> &SceneConfig {
|
||||
&self.current_scene_config
|
||||
}
|
||||
|
||||
/// Change the current scene
|
||||
pub fn set_scene(&mut self, state: &mut RenderState, scene: UiScene) -> Result<()> {
|
||||
pub fn set_scene(
|
||||
&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);
|
||||
self.current_scene = scene;
|
||||
self.current_scene = Some(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.elements.clear();
|
||||
let mut used_names = HashSet::new();
|
||||
|
||||
trace!("running init for `{}`", self.current_scene.to_string());
|
||||
|
||||
let builders: Array = self.engine.call_fn(
|
||||
&mut self.scope,
|
||||
Self::get_scene_ast(&self.ct, self.current_scene),
|
||||
"init",
|
||||
(State {
|
||||
planet_landscape: "ui::landscape::test".to_string(),
|
||||
planet_name: "Earth".to_string(),
|
||||
},),
|
||||
)?;
|
||||
let builders: Array = self
|
||||
.engine
|
||||
.call_fn(
|
||||
&mut self.scope,
|
||||
&self
|
||||
.ct
|
||||
.get_config()
|
||||
.ui_scenes
|
||||
.get(self.current_scene.as_ref().unwrap())
|
||||
.unwrap(),
|
||||
"init",
|
||||
(State::new(input.clone()),),
|
||||
)
|
||||
.with_context(|| format!("while running `init()`"))
|
||||
.with_context(|| format!("in ui scene `{}`", self.current_scene.as_ref().unwrap()))?;
|
||||
|
||||
trace!("found {:?} builders", builders.len());
|
||||
|
||||
|
@ -85,7 +118,7 @@ impl UiManager {
|
|||
if used_names.contains(s.name.as_str()) {
|
||||
error!(
|
||||
"UI scene `{}` re-uses element name `{}`",
|
||||
self.current_scene.to_string(),
|
||||
self.current_scene.as_ref().unwrap(),
|
||||
s.name
|
||||
);
|
||||
} else {
|
||||
|
@ -103,7 +136,7 @@ impl UiManager {
|
|||
if used_names.contains(r.name.as_str()) {
|
||||
error!(
|
||||
"UI scene `{}` re-uses element name `{}`",
|
||||
self.current_scene.to_string(),
|
||||
self.current_scene.as_ref().unwrap(),
|
||||
r.name
|
||||
);
|
||||
} else {
|
||||
|
@ -122,7 +155,7 @@ impl UiManager {
|
|||
if used_names.contains(t.name.as_str()) {
|
||||
error!(
|
||||
"UI scene `{}` re-uses element name `{}`",
|
||||
self.current_scene.to_string(),
|
||||
self.current_scene.as_ref().unwrap(),
|
||||
t.name
|
||||
);
|
||||
} else {
|
||||
|
@ -148,49 +181,183 @@ impl UiManager {
|
|||
}
|
||||
|
||||
/// Draw all ui elements
|
||||
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) -> Result<()> {
|
||||
let mut iter = self.elements.iter();
|
||||
|
||||
if self.show_timings {
|
||||
self.fps_indicator.step(input, state);
|
||||
pub fn draw(&mut self, input: Rc<RenderInput>, state: &mut RenderState) -> Result<()> {
|
||||
if self.current_scene.is_none() {
|
||||
// Initialize start scene if we haven't yet
|
||||
self.set_scene(
|
||||
state,
|
||||
input.clone(),
|
||||
self.ct.get_config().start_ui_scene.clone(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let action: SceneAction = loop {
|
||||
let e = match iter.next() {
|
||||
Some(e) => e,
|
||||
None => break SceneAction::None,
|
||||
};
|
||||
if self.show_timings {
|
||||
self.fps_indicator.step(&input, state);
|
||||
}
|
||||
|
||||
// Check for player ship state changes
|
||||
let player = input.player.ship;
|
||||
let send_event = {
|
||||
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 mut actions = Vec::new();
|
||||
if send_event {
|
||||
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>() {
|
||||
match action {
|
||||
SceneAction::None => {}
|
||||
_ => {
|
||||
actions.push(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for e in &self.elements {
|
||||
let action: Dynamic = {
|
||||
if let Some(e) = e.sprite() {
|
||||
let mut x = (*e).borrow_mut();
|
||||
let m = x.check_mouse(input, state);
|
||||
x.step(input, state);
|
||||
x.push_to_buffer(input, state);
|
||||
let event = x.check_mouse(&input, state);
|
||||
x.step(&input, state);
|
||||
x.push_to_buffer(&input, state);
|
||||
drop(x);
|
||||
// we MUST drop here, since script calls mutate the sprite RefCell
|
||||
|
||||
match m {
|
||||
MouseEvent::None => Dynamic::from(SceneAction::None),
|
||||
match event {
|
||||
Event::None => Dynamic::from(SceneAction::None),
|
||||
|
||||
MouseEvent::Release | MouseEvent::Click => self.engine.call_fn(
|
||||
&mut self.scope,
|
||||
Self::get_scene_ast(&self.ct, self.current_scene),
|
||||
"click",
|
||||
(SpriteElement::new(self.ct.clone(), e.clone()), m.is_click()),
|
||||
)?,
|
||||
Event::MouseClick => 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()),
|
||||
MouseClickEvent {
|
||||
down: true,
|
||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
||||
},
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("while handling click event"))
|
||||
.with_context(|| {
|
||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
||||
})?,
|
||||
|
||||
MouseEvent::Leave | MouseEvent::Enter => self.engine.call_fn(
|
||||
&mut self.scope,
|
||||
Self::get_scene_ast(&self.ct, self.current_scene),
|
||||
"hover",
|
||||
(SpriteElement::new(self.ct.clone(), e.clone()), m.is_enter()),
|
||||
)?,
|
||||
Event::MouseRelease => 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()),
|
||||
MouseClickEvent {
|
||||
down: false,
|
||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
||||
},
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("while handling release event"))
|
||||
.with_context(|| {
|
||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
||||
})?,
|
||||
|
||||
Event::MouseHover => 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()),
|
||||
MouseHoverEvent {
|
||||
enter: true,
|
||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
||||
},
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("while handling hover event"))
|
||||
.with_context(|| {
|
||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
||||
})?,
|
||||
|
||||
Event::MouseUnhover => 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()),
|
||||
MouseHoverEvent {
|
||||
enter: false,
|
||||
element: SpriteElement::new(self.ct.clone(), e.clone()),
|
||||
},
|
||||
),
|
||||
)
|
||||
.with_context(|| format!("while handling unhover event"))
|
||||
.with_context(|| {
|
||||
format!("in ui scene `{}`", self.current_scene.as_ref().unwrap())
|
||||
})?,
|
||||
}
|
||||
} else if let Some(e) = e.radialbar() {
|
||||
let mut x = (*e).borrow_mut();
|
||||
x.step(input, state);
|
||||
x.push_to_buffer(input, state);
|
||||
x.step(&input, state);
|
||||
x.push_to_buffer(&input, state);
|
||||
Dynamic::from(SceneAction::None)
|
||||
} else {
|
||||
Dynamic::from(SceneAction::None)
|
||||
|
@ -201,18 +368,20 @@ impl UiManager {
|
|||
match action {
|
||||
SceneAction::None => {}
|
||||
_ => {
|
||||
break action;
|
||||
actions.push(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
drop(iter);
|
||||
|
||||
match action {
|
||||
SceneAction::None => {}
|
||||
SceneAction::SceneOutfitter => self.set_scene(state, UiScene::Outfitter)?,
|
||||
SceneAction::SceneLanded => self.set_scene(state, UiScene::Landed)?,
|
||||
for a in actions {
|
||||
match a {
|
||||
SceneAction::None => {}
|
||||
SceneAction::GoTo(s) => {
|
||||
self.set_scene(state, input.clone(), s.clone())?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
|
@ -227,6 +396,11 @@ impl<'a> UiManager {
|
|||
state: &RenderState,
|
||||
) -> Vec<TextArea<'a>> {
|
||||
let mut v = Vec::with_capacity(32);
|
||||
|
||||
if self.current_scene.is_none() {
|
||||
return v;
|
||||
}
|
||||
|
||||
if self.show_timings {
|
||||
v.push(self.fps_indicator.get_textarea(state, input))
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
mod api;
|
||||
mod event;
|
||||
mod manager;
|
||||
mod mouseevent;
|
||||
mod uielement;
|
||||
mod uiscene;
|
||||
mod util;
|
||||
|
||||
pub(crate) use manager::UiManager;
|
||||
pub(crate) use uielement::UiElement;
|
||||
pub(crate) use uiscene::UiScene;
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ 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>>),
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
#[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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,9 +2,7 @@ use galactica_content::{Content, SpriteAutomaton, SpriteHandle};
|
|||
use galactica_util::to_radians;
|
||||
|
||||
use super::super::api::Rect;
|
||||
use crate::{
|
||||
ui::mouseevent::MouseEvent, vertexbuffer::types::UiInstance, RenderInput, RenderState,
|
||||
};
|
||||
use crate::{ui::event::Event, vertexbuffer::types::UiInstance, RenderInput, RenderState};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Sprite {
|
||||
|
@ -73,7 +71,7 @@ impl Sprite {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn check_mouse(&mut self, input: &RenderInput, state: &mut RenderState) -> MouseEvent {
|
||||
pub fn check_mouse(&mut self, input: &RenderInput, state: &mut RenderState) -> Event {
|
||||
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() {
|
||||
|
@ -86,18 +84,18 @@ impl Sprite {
|
|||
&& input.player.input.pressed_leftclick()
|
||||
{
|
||||
self.has_click = true;
|
||||
return MouseEvent::Click;
|
||||
return Event::MouseClick;
|
||||
}
|
||||
|
||||
if self.has_mouse && self.has_click && !input.player.input.pressed_leftclick() {
|
||||
self.has_click = false;
|
||||
return MouseEvent::Release;
|
||||
return Event::MouseRelease;
|
||||
}
|
||||
|
||||
// Release mouse when cursor leaves box
|
||||
if self.has_click && !self.has_mouse {
|
||||
self.has_click = false;
|
||||
return MouseEvent::Release;
|
||||
return Event::MouseRelease;
|
||||
}
|
||||
|
||||
if r.contains_mouse(input, state) && !self.has_mouse {
|
||||
|
@ -107,16 +105,16 @@ impl Sprite {
|
|||
self.waiting_for_release = true;
|
||||
}
|
||||
self.has_mouse = true;
|
||||
return MouseEvent::Enter;
|
||||
return Event::MouseHover;
|
||||
}
|
||||
|
||||
if !r.contains_mouse(input, state) && self.has_mouse {
|
||||
self.waiting_for_release = false;
|
||||
self.has_mouse = false;
|
||||
return MouseEvent::Leave;
|
||||
return Event::MouseUnhover;
|
||||
}
|
||||
|
||||
return MouseEvent::None;
|
||||
return Event::None;
|
||||
}
|
||||
|
||||
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) {
|
||||
|
|
Loading…
Reference in New Issue