@@ -284,7 +284,8 @@ where | |||
} | |||
#[inline] | |||
pub fn translate(v: Vector2<T>) -> Self { | |||
pub fn translate<V: Into<Vector2<T>>>(v: V) -> Self { | |||
let v = V::into(v); | |||
let one = T::one(); | |||
let zro = T::zero(); | |||
@@ -378,7 +379,8 @@ where | |||
T: Float, | |||
{ | |||
#[inline] | |||
pub fn translate(v: Vector3<T>) -> Self { | |||
pub fn translate<V: Into<Vector3<T>>>(v: V) -> Self { | |||
let v = V::into(v); | |||
let one = T::one(); | |||
let zero = T::zero(); | |||
@@ -391,7 +393,8 @@ where | |||
} | |||
#[inline] | |||
pub fn scale(v: Vector3<T>) -> Self { | |||
pub fn scale<V: Into<Vector3<T>>>(v: V) -> Self { | |||
let v = V::into(v); | |||
let one = T::one(); | |||
let zero = T::zero(); | |||
@@ -405,8 +408,8 @@ where | |||
#[inline] | |||
#[allow(clippy::many_single_char_names)] | |||
pub fn rotate(axis: Vector3<T>, angle: Angle<T>) -> Self { | |||
let axis = axis.normalize(); | |||
pub fn rotate<V: Into<Vector3<T>>>(axis: V, angle: Angle<T>) -> Self { | |||
let axis = V::into(axis).normalize(); | |||
let x = axis.x; | |||
let y = axis.y; | |||
let z = axis.z; | |||
@@ -2,7 +2,7 @@ | |||
use std::convert::{AsMut, AsRef}; | |||
use std::fmt::{Debug, Formatter, Result as FmtResult}; | |||
use std::ops::{Add, Deref, DerefMut, Mul, Sub}; | |||
use std::ops::{Add, Deref, DerefMut, Mul, Neg, Sub}; | |||
#[cfg(feature = "serde")] | |||
use serde::{ | |||
@@ -296,6 +296,21 @@ where | |||
} | |||
} | |||
impl<T> Neg for Vector2<T> | |||
where | |||
T: Numeric, | |||
{ | |||
type Output = Self; | |||
#[inline] | |||
fn neg(self) -> Self::Output { | |||
Self { | |||
x: -self.x, | |||
y: -self.y, | |||
} | |||
} | |||
} | |||
impl<T> Add for Vector2<T> | |||
where | |||
T: Numeric, | |||
@@ -350,6 +365,15 @@ where | |||
} | |||
} | |||
impl<T> From<Vector3<T>> for Vector2<T> | |||
where | |||
T: Numeric, | |||
{ | |||
fn from(vec3: Vector3<T>) -> Self { | |||
vec3.into_vec2() | |||
} | |||
} | |||
impl<T> From<Vector4<T>> for Vector2<T> | |||
where | |||
T: Numeric, | |||
@@ -408,6 +432,19 @@ where | |||
z: self.x * other.y - self.y * other.x, | |||
} | |||
} | |||
#[inline] | |||
pub fn as_vec2(&self) -> &Vector2<T> { | |||
unsafe { &*(self as *const Vector3<T> as *const Vector2<T>) } | |||
} | |||
#[inline] | |||
pub fn into_vec2(self) -> Vector2<T> { | |||
Vector2 { | |||
x: self.x, | |||
y: self.y, | |||
} | |||
} | |||
} | |||
impl<T> Vector3<T> | |||
@@ -424,6 +461,22 @@ where | |||
} | |||
} | |||
impl<T> Neg for Vector3<T> | |||
where | |||
T: Numeric, | |||
{ | |||
type Output = Self; | |||
#[inline] | |||
fn neg(self) -> Self::Output { | |||
Self { | |||
x: -self.x, | |||
y: -self.y, | |||
z: -self.z, | |||
} | |||
} | |||
} | |||
impl<T> Add for Vector3<T> | |||
where | |||
T: Numeric, | |||
@@ -480,6 +533,28 @@ where | |||
} | |||
} | |||
impl<T> From<Vector2<T>> for Vector3<T> | |||
where | |||
T: Numeric, | |||
{ | |||
fn from(vec2: Vector2<T>) -> Self { | |||
Self { | |||
x: vec2.x, | |||
y: vec2.y, | |||
z: T::zero(), | |||
} | |||
} | |||
} | |||
impl<T> From<Vector4<T>> for Vector3<T> | |||
where | |||
T: Numeric, | |||
{ | |||
fn from(vec4: Vector4<T>) -> Self { | |||
vec4.into_vec3() | |||
} | |||
} | |||
/* Vector4 */ | |||
impl<T> Vector4<T> | |||
@@ -1,5 +1,6 @@ | |||
use std::string::ToString; | |||
use glc::vector::Vector2f; | |||
use space_crush_common::{misc::LogResult, resources::Global}; | |||
use specs::{prelude::*, Entities, ReadExpect, System}; | |||
@@ -15,8 +16,8 @@ pub struct Summary { | |||
cache: TextCache, | |||
text: Text, | |||
fps: usize, | |||
resolution: (u32, u32), | |||
mouse_pos: (f32, f32), | |||
resolution: Vector2f, | |||
mouse_pos: Vector2f, | |||
entity_count: usize, | |||
} | |||
@@ -44,8 +45,8 @@ impl Summary { | |||
cache, | |||
text, | |||
fps: 0, | |||
resolution: (0, 0), | |||
mouse_pos: (0.0, 0.0), | |||
resolution: Default::default(), | |||
mouse_pos: Default::default(), | |||
entity_count: 0, | |||
}) | |||
} | |||
@@ -83,14 +84,14 @@ impl<'a> System<'a> for Summary { | |||
4, | |||
&mut self.resolution, | |||
&input_state.resolution, | |||
|(w, h)| format!("{} | {}", w, h), | |||
|v| unsafe { format!("{:.0} | {:.0}", v.x, v.y) }, | |||
); | |||
update_text( | |||
&mut self.text, | |||
6, | |||
&mut self.mouse_pos, | |||
&input_state.mouse_pos, | |||
|(x, y)| format!("{:.2} | {:.2}", x, y), | |||
|v| unsafe { format!("{:.2} | {:.2}", v.x, v.y) }, | |||
); | |||
update_text( | |||
&mut self.text, | |||
@@ -2,7 +2,7 @@ use glc::{ | |||
matrix::Matrix4f, | |||
misc::Bindable, | |||
shader::{Program, Type}, | |||
vector::Vector3f, | |||
vector::{Vector2f, Vector3f}, | |||
}; | |||
use shrev::{EventChannel, ReaderId}; | |||
use space_crush_common::{ | |||
@@ -20,7 +20,7 @@ use crate::{ | |||
pub struct Init { | |||
program: Program, | |||
resolution: (u32, u32), | |||
resolution: Vector2f, | |||
mouse_event_id: ReaderId<MouseEvent>, | |||
} | |||
@@ -30,7 +30,7 @@ impl Init { | |||
(Type::Vertex, "resources/shader/noise/vert.glsl"), | |||
(Type::Fragment, "resources/shader/noise/frag.glsl"), | |||
])?; | |||
let resolution = (0, 0); | |||
let resolution = Vector2f::default(); | |||
let mouse_event_id = world.register_event_reader::<MouseEvent>()?; | |||
world | |||
@@ -80,31 +80,30 @@ impl<'a> System<'a> for Init { | |||
if self.resolution != input_state.resolution { | |||
self.resolution = input_state.resolution; | |||
gl::viewport(0, 0, self.resolution.0 as _, self.resolution.1 as _); | |||
gl::viewport(0, 0, self.resolution.x as _, self.resolution.y as _); | |||
camera | |||
.resize(self.resolution.0 as _, self.resolution.1 as _) | |||
.resize(self.resolution.x, self.resolution.y) | |||
.error("Error while updating camera"); | |||
} | |||
/* zoom */ | |||
let mouse_pos = &input_state.mouse_pos; | |||
let events = mouse_events.read(&mut self.mouse_event_id); | |||
for event in events { | |||
match event { | |||
MouseEvent::ScrollY(delta) => { | |||
let s = config.input.camera_zoom_speed; | |||
let z = s / (s - delta); | |||
let m = Matrix4f::translate((mouse_pos.0, mouse_pos.1, 0.0).into()) | |||
* Matrix4f::scale(z.into()) | |||
* Matrix4f::translate((-mouse_pos.0, -mouse_pos.1, 0.0).into()); | |||
let m = Matrix4f::translate(input_state.mouse_pos) | |||
* Matrix4f::scale(z) | |||
* Matrix4f::translate(-input_state.mouse_pos); | |||
camera | |||
.update_with(move |v| m * v) | |||
.error("Error while zooming camera"); | |||
} | |||
MouseEvent::Delta(x, y) if input_state.button_state(&[MouseButton::Right]) => { | |||
let m = Matrix4f::translate((*x, -*y, 0.0).into()); | |||
let m = Matrix4f::translate((*x, -*y, 0.0)); | |||
camera | |||
.update_with(move |v| m * v) | |||
@@ -14,6 +14,7 @@ use space_crush_common::{ | |||
constants::VECTOR_2F_POS_X, | |||
misc::{LogResult, WorldHelper as _}, | |||
resources::Global, | |||
return_if_none, | |||
}; | |||
use specs::{prelude::*, Entities, ReadExpect, ReadStorage, System, World}; | |||
@@ -44,9 +45,9 @@ pub struct SelectFleet { | |||
mouse_event_id: ReaderId<MouseEvent>, | |||
select_mode: SelectMode, | |||
camera_counter: usize, | |||
camera_view_invert: Matrix4f, | |||
need_value_update: bool, | |||
values_changed_once: bool, | |||
is_new_selection: bool, | |||
mouse_pos: Vector2f, | |||
count: ShipCount, | |||
@@ -69,7 +70,7 @@ enum SelectMode { | |||
} | |||
#[derive(SystemData)] | |||
pub struct SelectFleetData<'a> { | |||
pub struct FleetSelectData<'a> { | |||
player_state: WriteExpect<'a, PlayerState>, | |||
camera: ReadExpect<'a, Camera>, | |||
@@ -85,33 +86,24 @@ pub struct SelectFleetData<'a> { | |||
fleets: ReadStorage<'a, Fleet>, | |||
} | |||
macro_rules! unwrap { | |||
($value:expr) => { | |||
match $value { | |||
Some(value) => value, | |||
None => return, | |||
} | |||
}; | |||
} | |||
macro_rules! selection { | |||
(&$data:expr) => { | |||
unwrap!(&$data.player_state.selection) | |||
return_if_none!(&$data.player_state.selection) | |||
}; | |||
(&mut $data:expr) => { | |||
unwrap!(&mut $data.player_state.selection) | |||
return_if_none!(&mut $data.player_state.selection) | |||
}; | |||
} | |||
macro_rules! fleet_info { | |||
(&$data:expr, $id:expr) => { | |||
unwrap!($data.fleet_infos.get($id)) | |||
return_if_none!($data.fleet_infos.get($id)) | |||
}; | |||
} | |||
macro_rules! position { | |||
(&$data:expr, $id:expr) => { | |||
unwrap!($data.positions.get($id)) | |||
return_if_none!($data.positions.get($id)) | |||
}; | |||
} | |||
@@ -157,9 +149,9 @@ impl SelectFleet { | |||
mouse_event_id, | |||
select_mode, | |||
camera_counter: 0, | |||
camera_view_invert: Default::default(), | |||
need_value_update: true, | |||
values_changed_once: false, | |||
is_new_selection: true, | |||
mouse_pos: Default::default(), | |||
count: Default::default(), | |||
@@ -172,39 +164,46 @@ impl SelectFleet { | |||
}) | |||
} | |||
fn update_camera(&mut self, d: &SelectFleetData<'_>) { | |||
fn update_camera(&mut self, d: &FleetSelectData<'_>) { | |||
let camera_counter = d.camera.update_counter(); | |||
if self.camera_counter != camera_counter { | |||
self.camera_counter = camera_counter; | |||
self.need_value_update = true; | |||
self.camera_view_invert = d.camera.view().invert(); | |||
} | |||
} | |||
fn handle_events(&mut self, d: &mut SelectFleetData<'_>) { | |||
fn handle_events(&mut self, d: &mut FleetSelectData<'_>) { | |||
let events = d.mouse_events.read(&mut self.mouse_event_id); | |||
for event in events { | |||
match event { | |||
MouseEvent::ButtonDown(button) if button == &d.config.input.fleet_select_button => { | |||
let pos = self.window_to_world(d.input_state.mouse_pos); | |||
let pos = d.camera.view_to_world(d.input_state.mouse_pos); | |||
let selection = d.player_state.selection.take(); | |||
for (id, position, fleet) in (&d.entities, &d.positions, &d.fleets).join() { | |||
let r = fleet.orbit_max * fleet.orbit_max; | |||
if (position.pos - pos).length_sqr() <= r { | |||
d.player_state.selection = match selection { | |||
Some(s) if s.fleet == id => Some(s), | |||
_ => Some(Selection { | |||
fleet: id, | |||
count: ShipCount::all(), | |||
}), | |||
Some(s) if s.fleet == id => { | |||
self.is_new_selection = false; | |||
Some(s) | |||
} | |||
_ => { | |||
self.is_new_selection = true; | |||
Some(Selection { | |||
fleet: id, | |||
count: ShipCount::none(), | |||
}) | |||
} | |||
}; | |||
let selection = selection!(&d); | |||
let fleet_info = fleet_info!(&d, selection.fleet); | |||
let timeout = Instant::now() + FLEET_SELECT_DETAIL_TIMEOUT; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
self.mouse_pos = d.input_state.mouse_pos; | |||
self.select_mode = SelectMode::Init(timeout); | |||
self.values_changed_once = false; | |||
self.set_count(selection.count, &fleet_info.count); | |||
@@ -218,36 +217,41 @@ impl SelectFleet { | |||
SelectMode::Simple(progress) => { | |||
selection!(&mut d).count = self.count; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
self.mouse_pos = d.input_state.mouse_pos; | |||
SelectMode::SimpleClose(progress) | |||
} | |||
SelectMode::Detail(progress) => { | |||
selection!(&mut d).count = self.count; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
self.mouse_pos = d.input_state.mouse_pos; | |||
SelectMode::DetailClose(progress) | |||
} | |||
SelectMode::Init(_) if self.is_new_selection => { | |||
selection!(&mut d).count = ShipCount::all(); | |||
SelectMode::None | |||
} | |||
_ => SelectMode::None, | |||
} | |||
} | |||
MouseEvent::Move(_, _) if self.select_mode.is_init() => { | |||
self.need_value_update = true; | |||
self.values_changed_once = false; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
self.mouse_pos = d.input_state.mouse_pos; | |||
self.select_mode = SelectMode::Simple(0.0); | |||
} | |||
MouseEvent::Move(_, _) if self.select_mode.is_active() => { | |||
self.need_value_update = true; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
self.mouse_pos = d.input_state.mouse_pos; | |||
} | |||
_ => (), | |||
} | |||
} | |||
} | |||
fn update(&mut self, d: &SelectFleetData<'_>) { | |||
fn update(&mut self, d: &FleetSelectData<'_>) { | |||
if !self.select_mode.is_active() { | |||
return; | |||
} | |||
@@ -261,7 +265,7 @@ impl SelectFleet { | |||
let position = position!(&d, selection.fleet); | |||
let fleet_info = fleet_info!(&d, selection.fleet); | |||
self.marker = self.window_to_world(self.mouse_pos) - position.pos; | |||
self.marker = d.camera.view_to_world(self.mouse_pos) - position.pos; | |||
self.zoom = d.camera.view().axis_x.as_vec3().length(); | |||
self.shape_size = position.shape.radius().unwrap_or(0.0); | |||
self.ring0 = self.shape_size + FLEET_SELECT_OFFSET / self.zoom; | |||
@@ -400,7 +404,7 @@ impl SelectFleet { | |||
} | |||
} | |||
fn render(&mut self, progress: f32, d: &SelectFleetData<'_>) { | |||
fn render(&mut self, progress: f32, d: &FleetSelectData<'_>) { | |||
/* select program */ | |||
let is_simple = self.select_mode.is_simple(); | |||
let program = if is_simple { | |||
@@ -500,15 +504,11 @@ impl SelectFleet { | |||
self.values[3] = sum / total_max; | |||
} | |||
fn window_to_world<T: Into<Vector2f>>(&self, pos: T) -> Vector2f { | |||
(self.camera_view_invert * T::into(pos).into_vec4()).into_vec2() | |||
} | |||
fn text_pos<T: Into<Vector2f>>( | |||
&self, | |||
dir: T, | |||
fleet_pos: Vector2f, | |||
d: &SelectFleetData<'_>, | |||
d: &FleetSelectData<'_>, | |||
) -> Vector2f { | |||
let text_offset = self.ring1 + FLEET_SELECT_TEXT_OFFSET / self.zoom.sqrt(); | |||
let pos = fleet_pos + T::into(dir).normalize() * text_offset; | |||
@@ -518,7 +518,7 @@ impl SelectFleet { | |||
} | |||
impl<'a> System<'a> for SelectFleet { | |||
type SystemData = SelectFleetData<'a>; | |||
type SystemData = FleetSelectData<'a>; | |||
fn run(&mut self, mut data: Self::SystemData) { | |||
self.update_camera(&data); | |||
@@ -10,6 +10,8 @@ use glc::{ | |||
pub struct Camera { | |||
buffer: ArrayBuffer, | |||
data: Data, | |||
view_invert: Matrix4f, | |||
projection_invert: Matrix4f, | |||
update_counter: usize, | |||
} | |||
@@ -34,6 +36,8 @@ impl Camera { | |||
Ok(Self { | |||
buffer, | |||
data, | |||
view_invert: Default::default(), | |||
projection_invert: Default::default(), | |||
update_counter: 0, | |||
}) | |||
} | |||
@@ -42,10 +46,18 @@ impl Camera { | |||
&self.data.projection | |||
} | |||
pub fn projection_invert(&self) -> &Matrix4f { | |||
&self.projection_invert | |||
} | |||
pub fn view(&self) -> &Matrix4f { | |||
&self.data.view | |||
} | |||
pub fn view_invert(&self) -> &Matrix4f { | |||
&self.view_invert | |||
} | |||
pub fn update_counter(&self) -> usize { | |||
self.update_counter | |||
} | |||
@@ -63,6 +75,7 @@ impl Camera { | |||
data[0].size = self.data.size; | |||
self.update_counter = self.update_counter.wrapping_add(1); | |||
self.projection_invert = self.data.projection.invert(); | |||
Ok(()) | |||
} | |||
@@ -81,6 +94,7 @@ impl Camera { | |||
data[0].view = self.data.view; | |||
self.update_counter = self.update_counter.wrapping_add(1); | |||
self.view_invert = self.data.view.invert(); | |||
Ok(()) | |||
} | |||
@@ -89,11 +103,43 @@ impl Camera { | |||
Error::checked(|| self.buffer.bind_buffer_base(index)) | |||
} | |||
pub fn world_to_view<T: Into<Vector2f>>(&self, pos: T) -> Vector2f { | |||
self.data.view.transform(T::into(pos)).into() | |||
} | |||
pub fn view_to_world<T: Into<Vector2f>>(&self, pos: T) -> Vector2f { | |||
self.view_invert.transform(T::into(pos)).into() | |||
} | |||
pub fn view_to_window<T: Into<Vector2f>>(&self, pos: T) -> Vector2f { | |||
view_to_window(&self.data.size, pos) | |||
} | |||
pub fn window_to_view<T: Into<Vector2f>>(&self, pos: T) -> Vector2f { | |||
window_to_view(&self.data.size, pos) | |||
} | |||
pub fn world_to_window<T: Into<Vector2f>>(&self, pos: T) -> Vector2f { | |||
let mut pos = self.data.view.transform(T::into(pos)).into_vec2(); | |||
pos.x += self.data.size.x / 2.0; | |||
pos.y = self.data.size.y / 2.0 - pos.y; | |||
self.view_to_window(self.world_to_view(pos)) | |||
} | |||
pos | |||
pub fn window_to_world<T: Into<Vector2f>>(&self, pos: T) -> Vector2f { | |||
self.view_to_world(self.window_to_view(pos)) | |||
} | |||
} | |||
pub fn view_to_window<T: Into<Vector2f>>(res: &Vector2f, pos: T) -> Vector2f { | |||
let mut pos = T::into(pos); | |||
pos.x += res.x / 2.0; | |||
pos.y = res.y / 2.0 - pos.y; | |||
pos | |||
} | |||
pub fn window_to_view<T: Into<Vector2f>>(res: &Vector2f, pos: T) -> Vector2f { | |||
let mut pos = T::into(pos); | |||
pos.x -= res.x / 2.0; | |||
pos.y = res.y / 2.0 - pos.y; | |||
pos | |||
} |
@@ -3,12 +3,14 @@ | |||
use std::collections::HashSet; | |||
use std::iter::IntoIterator; | |||
use glc::vector::Vector2f; | |||
use crate::misc::{MouseButton, VirtualKeyCode}; | |||
#[derive(Default)] | |||
pub struct InputState { | |||
pub mouse_pos: (f32, f32), | |||
pub resolution: (u32, u32), | |||
pub mouse_pos: Vector2f, | |||
pub resolution: Vector2f, | |||
pub close_requested: bool, | |||
pub key_states: HashSet<VirtualKeyCode>, | |||
pub button_states: HashSet<MouseButton>, | |||
@@ -1,9 +1,9 @@ | |||
mod camera; | |||
mod config; | |||
mod geometry; | |||
mod input_state; | |||
mod player_state; | |||
mod uniform; | |||
pub mod camera; | |||
pub mod config; | |||
pub mod geometry; | |||
pub mod input_state; | |||
pub mod player_state; | |||
pub mod uniform; | |||
pub use camera::Camera; | |||
pub use config::Config; | |||
@@ -4,7 +4,7 @@ use specs::{prelude::*, ReadExpect, System, World, Write}; | |||
use crate::{ | |||
misc::{KeyboardEvent, MouseEvent, WindowEvent}, | |||
resources::InputState, | |||
resources::{camera::window_to_view, InputState}, | |||
Error, | |||
}; | |||
@@ -51,10 +51,7 @@ impl<'a> System<'a> for StateUpdate { | |||
for event in events { | |||
match event { | |||
MouseEvent::Move(x, y) => { | |||
let x = *x - input_state.resolution.0 as f32 / 2.0; | |||
let y = input_state.resolution.1 as f32 / 2.0 - *y; | |||
input_state.mouse_pos = (x, y); | |||
input_state.mouse_pos = window_to_view(&input_state.resolution, (*x, *y)) | |||
} | |||
MouseEvent::ButtonUp(button) => input_state.set_button_state(*button, false), | |||
MouseEvent::ButtonDown(button) => input_state.set_button_state(*button, true), | |||
@@ -66,7 +63,7 @@ impl<'a> System<'a> for StateUpdate { | |||
for event in events { | |||
match event { | |||
WindowEvent::Resize(w, h) => { | |||
input_state.resolution = (*w, *h); | |||
input_state.resolution = (*w as f32, *h as f32).into(); | |||
} | |||
WindowEvent::Close => { | |||
input_state.close_requested = true; | |||
@@ -40,6 +40,14 @@ impl Count { | |||
} | |||
} | |||
pub fn none() -> Self { | |||
Self { | |||
fighter: 0, | |||
bomber: 0, | |||
transporter: 0, | |||
} | |||
} | |||
pub fn total(&self) -> usize { | |||
self.fighter | |||
.saturating_add(self.bomber) | |||
@@ -2,6 +2,7 @@ pub mod components; | |||
pub mod constants; | |||
pub mod dispatcher; | |||
pub mod error; | |||
pub mod macros; | |||
pub mod misc; | |||
pub mod resources; | |||
pub mod systems; | |||
@@ -0,0 +1,19 @@ | |||
#[macro_export] | |||
macro_rules! return_if_none { | |||
($value:expr) => { | |||
match $value { | |||
Some(value) => value, | |||
None => return, | |||
} | |||
}; | |||
} | |||
#[macro_export] | |||
macro_rules! break_if_none { | |||
($value:expr) => { | |||
match $value { | |||
Some(value) => value, | |||
None => break, | |||
} | |||
}; | |||
} |