@@ -1510,7 +1510,6 @@ dependencies = [ | |||||
"glc", | "glc", | ||||
"glutin", | "glutin", | ||||
"glyph_brush", | "glyph_brush", | ||||
"lazy_static", | |||||
"log", | "log", | ||||
"log4rs", | "log4rs", | ||||
"ordered-float 2.0.1", | "ordered-float 2.0.1", | ||||
@@ -1531,7 +1530,6 @@ version = "0.1.0" | |||||
dependencies = [ | dependencies = [ | ||||
"glc", | "glc", | ||||
"hibitset", | "hibitset", | ||||
"lazy_static", | |||||
"log", | "log", | ||||
"log4rs", | "log4rs", | ||||
"rand", | "rand", | ||||
@@ -0,0 +1,225 @@ | |||||
use super::{angle::Angle, numeric::Float}; | |||||
pub fn animate<T, F>(low: T, high: T, value: T, f: F) -> T | |||||
where | |||||
T: Float, | |||||
F: FnOnce(T) -> T, | |||||
{ | |||||
low + f(value) * (high - low) | |||||
} | |||||
pub fn zero<T: Float>(_: T) -> T { | |||||
T::zero() | |||||
} | |||||
pub fn one<T: Float>(_: T) -> T { | |||||
T::one() | |||||
} | |||||
pub fn step<T: Float + Ord>(value: T) -> T { | |||||
if value < T::new(0.5) { | |||||
T::zero() | |||||
} else { | |||||
T::one() | |||||
} | |||||
} | |||||
pub fn linear<T: Float>(value: T) -> T { | |||||
value | |||||
} | |||||
pub fn invert<T: Float>(value: T) -> T { | |||||
T::one() - value | |||||
} | |||||
pub fn sinus<T: Float>(value: T) -> T { | |||||
Angle::Deg(T::new(360.0) * value) | |||||
.into_rad() | |||||
.into_inner() | |||||
.sin() | |||||
} | |||||
pub fn cosinus<T: Float>(value: T) -> T { | |||||
Angle::Deg(T::new(360.0) * value) | |||||
.into_rad() | |||||
.into_inner() | |||||
.cos() | |||||
} | |||||
pub fn ease_in_quadric<T: Float>(value: T) -> T { | |||||
value * value | |||||
} | |||||
pub fn ease_in_cubic<T: Float>(value: T) -> T { | |||||
value * value * value | |||||
} | |||||
pub fn ease_in_quartic<T: Float>(value: T) -> T { | |||||
value * value * value * value | |||||
} | |||||
pub fn ease_in_quintic<T: Float>(value: T) -> T { | |||||
value * value * value * value * value | |||||
} | |||||
pub fn ease_in_sinusoidal<T: Float>(value: T) -> T { | |||||
Angle::Deg(T::new(90.0) * value + T::one()) | |||||
.into_rad() | |||||
.into_inner() | |||||
.cos() | |||||
.neg() | |||||
} | |||||
pub fn ease_in_circular<T: Float + Ord>(value: T) -> T { | |||||
if value > T::one() { | |||||
T::one() | |||||
} else { | |||||
T::one() - (T::one() - value * value) | |||||
} | |||||
} | |||||
pub fn ease_in_back_cubic<T: Float>(value: T) -> T { | |||||
T::new(4.0) * value * value * value - T::new(3.0) * value * value | |||||
} | |||||
pub fn ease_in_back_quadric<T: Float>(value: T) -> T { | |||||
T::new(2.0) * value * value * value * value + T::new(2.0) * value * value * value | |||||
- T::new(3.0) * value * value | |||||
} | |||||
pub fn ease_in_elastic_small<T: Float>(value: T) -> T { | |||||
T::new(33.0) * value * value * value * value * value | |||||
- T::new(59.0) * value * value * value * value | |||||
+ T::new(32.0) * value * value * value | |||||
- T::new(5.0) * value * value | |||||
} | |||||
pub fn ease_in_elastic_big<T: Float>(value: T) -> T { | |||||
T::new(56.0) * value * value * value * value * value | |||||
- T::new(105.0) * value * value * value * value | |||||
+ T::new(60.0) * value * value * value | |||||
- T::new(10.0) * value * value | |||||
} | |||||
pub fn ease_out_quadric<T: Float>(value: T) -> T { | |||||
-value * (value - T::new(2.0)) | |||||
} | |||||
pub fn ease_out_cubic<T: Float>(value: T) -> T { | |||||
let value = value - T::one(); | |||||
value * value * value + T::one() | |||||
} | |||||
pub fn ease_out_quartic<T: Float>(value: T) -> T { | |||||
let value = value - T::one(); | |||||
-value * value * value * value + T::one() | |||||
} | |||||
pub fn ease_out_quintic<T: Float>(value: T) -> T { | |||||
let value = value - T::one(); | |||||
value * value * value * value * value + T::one() | |||||
} | |||||
pub fn ease_out_sinusoidal<T: Float>(value: T) -> T { | |||||
Angle::Deg(T::new(90.0) * value) | |||||
.into_rad() | |||||
.into_inner() | |||||
.sin() | |||||
} | |||||
pub fn ease_out_circular<T: Float + Ord>(value: T) -> T { | |||||
if value < T::zero() { | |||||
T::zero() | |||||
} else { | |||||
let value = value - T::one(); | |||||
(T::one() - value * value).sqrt() | |||||
} | |||||
} | |||||
pub fn ease_out_back_cubic<T: Float>(value: T) -> T { | |||||
T::new(4.0) * value * value * value - T::new(9.0) * value * value + T::new(6.0) * value | |||||
} | |||||
pub fn ease_out_back_quadric<T: Float>(value: T) -> T { | |||||
-T::new(2.0) * value * value * value * value + T::new(10.0) * value * value * value | |||||
- T::new(15.0) * value * value | |||||
+ T::new(8.0) * value | |||||
} | |||||
pub fn ease_out_elastic_small<T: Float>(value: T) -> T { | |||||
T::new(33.0) * value * value * value * value * value | |||||
- T::new(106.0) * value * value * value * value | |||||
+ T::new(126.0) * value * value * value | |||||
- T::new(67.0) * value * value | |||||
+ T::new(15.0) * value | |||||
} | |||||
pub fn ease_out_elastic_big<T: Float>(value: T) -> T { | |||||
T::new(56.0) * value * value * value * value * value | |||||
- T::new(175.0) * value * value * value * value | |||||
+ T::new(200.0) * value * value * value | |||||
- T::new(100.0) * value * value | |||||
+ T::new(20.0) * value | |||||
} | |||||
pub fn ease_in_out_quadric<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_quadric, ease_out_quadric) | |||||
} | |||||
pub fn ease_in_out_cubic<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_cubic, ease_out_cubic) | |||||
} | |||||
pub fn ease_in_out_quartic<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_quartic, ease_out_quartic) | |||||
} | |||||
pub fn ease_in_out_quintic<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_quintic, ease_out_quintic) | |||||
} | |||||
pub fn ease_in_out_sinusodial<T: Float>(value: T) -> T { | |||||
-T::new(0.5) | |||||
* (Angle::Deg(T::new(180.0) * value) | |||||
.into_rad() | |||||
.into_inner() | |||||
.cos() | |||||
- T::one()) | |||||
} | |||||
pub fn ease_in_out_circular<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_circular, ease_out_circular) | |||||
} | |||||
pub fn ease_in_out_back_cubic<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_back_cubic, ease_out_back_cubic) | |||||
} | |||||
pub fn ease_in_out_back_quadtric<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_back_quadric, ease_out_back_quadric) | |||||
} | |||||
pub fn ease_in_out_elasic_small<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_elastic_small, ease_out_elastic_small) | |||||
} | |||||
pub fn ease_in_out_elasic_big<T: Float + Ord>(value: T) -> T { | |||||
ease_in_out(value, ease_in_elastic_big, ease_out_elastic_big) | |||||
} | |||||
pub fn ease_in_out<T, F1, F2>(value: T, f1: F1, f2: F2) -> T | |||||
where | |||||
T: Float + Ord, | |||||
F1: FnOnce(T) -> T, | |||||
F2: FnOnce(T) -> T, | |||||
{ | |||||
if value < T::new(0.5) { | |||||
T::new(0.5) * f1(T::new(2.0) * value) | |||||
} else { | |||||
T::new(0.5) * f2(T::new(2.0) * (value - T::new(0.5))) + T::new(0.5) | |||||
} | |||||
} |
@@ -1,4 +1,5 @@ | |||||
pub mod angle; | pub mod angle; | ||||
pub mod animation; | |||||
pub mod array_buffer; | pub mod array_buffer; | ||||
pub mod error; | pub mod error; | ||||
pub mod math; | pub mod math; | ||||
@@ -1,5 +1,7 @@ | |||||
use std::cmp::PartialOrd; | use std::cmp::PartialOrd; | ||||
use std::ops::{Div, Mul, Sub}; | |||||
use std::ops::{Mul, Sub}; | |||||
use super::numeric::Float; | |||||
#[inline] | #[inline] | ||||
pub fn min<T>(a: T, b: T) -> T | pub fn min<T>(a: T, b: T) -> T | ||||
@@ -44,15 +46,15 @@ where | |||||
#[inline] | #[inline] | ||||
pub fn linear_step<T>(low: T, high: T, value: T) -> T | pub fn linear_step<T>(low: T, high: T, value: T) -> T | ||||
where | where | ||||
T: PartialOrd + Copy + Sub<T, Output = T> + Div<T, Output = T>, | |||||
T: Float + PartialOrd, | |||||
{ | { | ||||
clamp(low, high, (value - low) / (high - low)) | |||||
clamp(T::zero(), T::one(), (value - low) / (high - low)) | |||||
} | } | ||||
#[inline] | #[inline] | ||||
pub fn smooth_step<T>(low: T, high: T, value: T) -> T | pub fn smooth_step<T>(low: T, high: T, value: T) -> T | ||||
where | where | ||||
T: PartialOrd + Copy + Sub<T, Output = T> + Div<T, Output = T> + Mul<T, Output = T>, | |||||
T: Float + PartialOrd, | |||||
f32: Mul<T, Output = T> + Sub<T, Output = T>, | f32: Mul<T, Output = T> + Sub<T, Output = T>, | ||||
{ | { | ||||
let x = linear_step(low, high, value); | let x = linear_step(low, high, value); | ||||
@@ -620,18 +620,37 @@ where | |||||
} | } | ||||
} | } | ||||
impl<T, M> Mul<M> for Matrix4<T> | |||||
where | |||||
T: Float, | |||||
M: Borrow<Matrix4<T>>, | |||||
{ | |||||
type Output = Self; | |||||
macro_rules! impl_mul_mat4 { | |||||
($input:ty, $output:ty, $func:ident) => { | |||||
impl<T> Mul<$input> for Matrix4<T> | |||||
where | |||||
T: Float, | |||||
{ | |||||
type Output = $output; | |||||
fn mul(self, rhs: M) -> Self::Output { | |||||
self.multiply(rhs.borrow()) | |||||
} | |||||
fn mul(self, rhs: $input) -> Self::Output { | |||||
self.$func(&rhs) | |||||
} | |||||
} | |||||
impl<T> Mul<$input> for &Matrix4<T> | |||||
where | |||||
T: Float, | |||||
{ | |||||
type Output = $output; | |||||
fn mul(self, rhs: $input) -> Self::Output { | |||||
self.$func(&rhs) | |||||
} | |||||
} | |||||
}; | |||||
} | } | ||||
impl_mul_mat4!(Matrix4<T>, Matrix4<T>, multiply); | |||||
impl_mul_mat4!(&Matrix4<T>, Matrix4<T>, multiply); | |||||
impl_mul_mat4!(Vector4<T>, Vector4<T>, transform); | |||||
impl_mul_mat4!(&Vector4<T>, Vector4<T>, transform); | |||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use super::*; | use super::*; | ||||
@@ -21,6 +21,7 @@ pub trait Numeric: | |||||
} | } | ||||
pub trait Float: Numeric { | pub trait Float: Numeric { | ||||
fn new(value: f64) -> Self; | |||||
fn sin(self) -> Self; | fn sin(self) -> Self; | ||||
fn cos(self) -> Self; | fn cos(self) -> Self; | ||||
fn tan(self) -> Self; | fn tan(self) -> Self; | ||||
@@ -68,6 +69,11 @@ impl Numeric for gl::GLfloat { | |||||
} | } | ||||
impl Float for gl::GLfloat { | impl Float for gl::GLfloat { | ||||
#[inline] | |||||
fn new(value: f64) -> Self { | |||||
value as _ | |||||
} | |||||
#[inline] | #[inline] | ||||
fn sin(self) -> Self { | fn sin(self) -> Self { | ||||
f32::sin(self) | f32::sin(self) | ||||
@@ -142,6 +148,11 @@ impl Numeric for gl::GLdouble { | |||||
} | } | ||||
impl Float for gl::GLdouble { | impl Float for gl::GLdouble { | ||||
#[inline] | |||||
fn new(value: f64) -> Self { | |||||
value as _ | |||||
} | |||||
#[inline] | #[inline] | ||||
fn sin(self) -> Self { | fn sin(self) -> Self { | ||||
f64::sin(self) | f64::sin(self) | ||||
@@ -9,7 +9,7 @@ use crate::{ | |||||
error::Error, | error::Error, | ||||
matrix::Matrix4f, | matrix::Matrix4f, | ||||
misc::{AsEnum, Bindable}, | misc::{AsEnum, Bindable}, | ||||
vector::Vector4f, | |||||
vector::{Vector2f, Vector4f}, | |||||
}; | }; | ||||
/* Programm */ | /* Programm */ | ||||
@@ -84,6 +84,8 @@ impl Program { | |||||
match uniform { | match uniform { | ||||
Uniform::Matrix4f(v) => gl::uniform_matrix_4fv(location, 1, gl::FALSE, v.as_ptr()), | Uniform::Matrix4f(v) => gl::uniform_matrix_4fv(location, 1, gl::FALSE, v.as_ptr()), | ||||
Uniform::Vector4f(v) => gl::uniform_4fv(location, 1, v.as_ptr()), | Uniform::Vector4f(v) => gl::uniform_4fv(location, 1, v.as_ptr()), | ||||
Uniform::Vector2f(v) => gl::uniform_2fv(location, 1, v.as_ptr()), | |||||
Uniform::Float(v) => gl::uniform_1f(location, v), | |||||
Uniform::Texture(i) => gl::uniform_1i(location, i), | Uniform::Texture(i) => gl::uniform_1i(location, i), | ||||
} | } | ||||
@@ -280,6 +282,8 @@ impl AsEnum for Type { | |||||
pub enum Uniform<'a> { | pub enum Uniform<'a> { | ||||
Matrix4f(&'a Matrix4f), | Matrix4f(&'a Matrix4f), | ||||
Vector4f(&'a Vector4f), | Vector4f(&'a Vector4f), | ||||
Vector2f(&'a Vector2f), | |||||
Float(f32), | |||||
Texture(gl::GLint), | Texture(gl::GLint), | ||||
} | } | ||||
@@ -30,7 +30,7 @@ macro_rules! define_vec { | |||||
impl<T> $Name<T> { | impl<T> $Name<T> { | ||||
#[inline] | #[inline] | ||||
pub fn new($($f: T,)+) -> Self { | |||||
pub const fn new($($f: T,)+) -> Self { | |||||
Self { $($f,)+ } | Self { $($f,)+ } | ||||
} | } | ||||
@@ -9,7 +9,6 @@ gl = { version = "0.1", features = [ "use_log_crate" ] } | |||||
glc = "0.1" | glc = "0.1" | ||||
glutin = { version = "0.25", features = [ "serde" ] } | glutin = { version = "0.25", features = [ "serde" ] } | ||||
glyph_brush = "0.7" | glyph_brush = "0.7" | ||||
lazy_static = "1.4" | |||||
log = { version = "0.4", features = [ "max_level_trace", "release_max_level_warn" ] } | log = { version = "0.4", features = [ "max_level_trace", "release_max_level_warn" ] } | ||||
log4rs = "0.13" | log4rs = "0.13" | ||||
ordered-float = "2.0" | ordered-float = "2.0" | ||||
@@ -0,0 +1,96 @@ | |||||
#version 450 core | |||||
#pragma include ./shared.glsl | |||||
#pragma include ../../misc/camera.glsl | |||||
in FragmentData fragmentData; | |||||
uniform vec2 uRings; | |||||
uniform vec2 uMarker; | |||||
uniform float uProgress; | |||||
uniform float uSize; | |||||
uniform float uValue; | |||||
out vec4 outColor; | |||||
const float RING_STRENGTH = 0.0000; | |||||
const float RING_BLUR = 0.0020; | |||||
const float MARKER_STRENGTH = 0.0000; | |||||
const float MARKER_BLUR = 0.0020; | |||||
const vec2 VEC_HORZ = vec2(1.0, 0.0); | |||||
const float PI = 3.1415926535897932384626433832795; | |||||
float smoothstep2(float b0, float b1, float b2, float b3, float v) { | |||||
return smoothstep(b0, b1, v) * (1.0 - smoothstep(b2, b3, v)); | |||||
} | |||||
float calcLine(float actual, float expected, float strength, float blur) | |||||
{ | |||||
return smoothstep2( | |||||
expected - strength - blur, | |||||
expected - strength, | |||||
expected + strength, | |||||
expected + strength + blur, | |||||
actual); | |||||
} | |||||
float calcAngle(vec2 v1, vec2 v2) | |||||
{ | |||||
v1 = -v1; | |||||
return -atan( | |||||
v1.x * v2.y - v1.y * v2.x, | |||||
v1.x * v2.x + v1.y * v2.y); | |||||
} | |||||
float random(float seed) { | |||||
return fract(sin(dot(vec4(seed), vec4(12.9898, 78.233, 45.164, 53.1324))) * 43758.5453); | |||||
} | |||||
float animate(float v0, float v1, float t) { | |||||
float x = t * t * (3.0 - 2.0 * t); | |||||
return v0 + x * (v1 - v0); | |||||
} | |||||
void main() { | |||||
float ts = clamp(uProgress, 0.0, 1.0); | |||||
float te = clamp(uProgress - 1.0, 0.0, 1.0); | |||||
float r = length(fragmentData.texCoords); | |||||
float zoom = pow(length(uCamera.view[0].xyz), 0.5); | |||||
float angle = calcAngle(fragmentData.texCoords, VEC_HORZ); | |||||
/* marker */ | |||||
float m = calcAngle(uMarker, VEC_HORZ); | |||||
m = calcLine(angle, m, MARKER_STRENGTH / r / zoom, MARKER_BLUR / r / zoom); | |||||
m *= smoothstep2( | |||||
ts * (uRings[0] - 0.25), | |||||
ts * uRings[0], | |||||
ts * uRings[1], | |||||
ts * (uRings[1] + 0.10), | |||||
r); | |||||
m *= 0.75; | |||||
/* rings */ | |||||
float f = angle / PI + 1.0; | |||||
f = f * 2.0; | |||||
f = fract(f + 0.5); | |||||
f = smoothstep2( | |||||
0.0 - (ts - 0.5), | |||||
0.2 - (ts - 0.5), | |||||
0.8 + (ts - 0.5), | |||||
1.0 + (ts - 0.5), | |||||
f); | |||||
float r0 = 0.25 * f * calcLine(r, animate(uSize, uRings[0], ts), RING_STRENGTH / zoom, RING_BLUR / zoom); | |||||
float r1 = 0.25 * f * calcLine(r, animate(uSize, uRings[1], ts), RING_STRENGTH / zoom, RING_BLUR / zoom); | |||||
float v = 0.75 * f * calcLine(r, animate(uSize, uValue, ts), RING_STRENGTH / zoom, RING_BLUR / zoom); | |||||
/* alpha */ | |||||
float as = step(1.0 - ts, random(uProgress)); | |||||
float ae = (1.0 - te) * step(te, random(uProgress)); | |||||
float a = as * ae; | |||||
/* put together */ | |||||
vec3 rgb = vec3(1.0); | |||||
outColor = vec4(rgb, (v + r0 + r1 + m) * a + uSize * 0.0001); | |||||
} |
@@ -0,0 +1,3 @@ | |||||
struct FragmentData { | |||||
vec2 texCoords; | |||||
}; |
@@ -0,0 +1,15 @@ | |||||
#version 450 core | |||||
#pragma include ./shared.glsl | |||||
#pragma include ../../misc/camera.glsl | |||||
in vec3 inPosition; | |||||
uniform mat4 uModel; | |||||
out FragmentData fragmentData; | |||||
void main() { | |||||
fragmentData.texCoords = 2.0 * inPosition.xy; | |||||
gl_Position = uCamera.projection * uCamera.view * uModel * vec4(2.0 * inPosition, 1.0); | |||||
} |
@@ -4,7 +4,8 @@ | |||||
in FragmentData fragmentData; | in FragmentData fragmentData; | ||||
uniform sampler2D uTexture; | |||||
layout (location = 0) uniform sampler2D uTexture; | |||||
layout (location = 1) uniform vec4 uColor; | |||||
out vec4 outColor; | out vec4 outColor; | ||||
@@ -15,5 +16,5 @@ void main() { | |||||
discard; | discard; | ||||
} | } | ||||
outColor = fragmentData.color * vec4(1.0, 1.0, 1.0, alpha); | |||||
outColor = fragmentData.color * uColor * vec4(1.0, 1.0, 1.0, alpha); | |||||
} | } |
@@ -9,6 +9,8 @@ layout (location = 2) in vec2 inTexMin; | |||||
layout (location = 3) in vec2 inTexMax; | layout (location = 3) in vec2 inTexMax; | ||||
layout (location = 4) in vec4 inColor; | layout (location = 4) in vec4 inColor; | ||||
layout (location = 2) uniform vec2 uOffset; | |||||
out FragmentData fragmentData; | out FragmentData fragmentData; | ||||
void main() { | void main() { | ||||
@@ -44,7 +46,7 @@ void main() { | |||||
vec4(-1.0, 1.0, 1.0, 1.0) | vec4(-1.0, 1.0, 1.0, 1.0) | ||||
); | ); | ||||
gl_Position = ortho * vec4(position, 0.0, 1.0); | |||||
gl_Position = ortho * vec4(position + uOffset, 0.0, 1.0); | |||||
fragmentData.texCoords = texCoords; | fragmentData.texCoords = texCoords; | ||||
fragmentData.color = inColor; | fragmentData.color = inColor; | ||||
@@ -1,5 +1,6 @@ | |||||
use std::time::Duration; | |||||
use glc::vector::Vector4f; | use glc::vector::Vector4f; | ||||
use lazy_static::lazy_static; | |||||
pub const UNIFORM_BUFFER_INDEX_CAMERA: gl::GLuint = 0; | pub const UNIFORM_BUFFER_INDEX_CAMERA: gl::GLuint = 0; | ||||
pub const UNIFORM_BUFFER_INDEX_UNIFORM: gl::GLuint = 1; | pub const UNIFORM_BUFFER_INDEX_UNIFORM: gl::GLuint = 1; | ||||
@@ -8,6 +9,10 @@ pub const SHIP_SIZE: f32 = 15.0; | |||||
pub const PLANET_SIZE: f32 = 200.0; | pub const PLANET_SIZE: f32 = 200.0; | ||||
pub const ASTEROID_SIZE: f32 = 100.0; | pub const ASTEROID_SIZE: f32 = 100.0; | ||||
lazy_static! { | |||||
pub static ref PLAYER_COLOR_DEFAULT: Vector4f = Vector4f::new(1.0, 1.0, 1.0, 0.1); | |||||
} | |||||
pub const FLEET_SELECT_DETAIL_TIMEOUT: Duration = Duration::from_millis(500); | |||||
pub const FLEET_SELECT_OFFSET: f32 = 25.0; | |||||
pub const FLEET_SELECT_WIDTH: f32 = 125.0; | |||||
pub const FLEET_SELECT_TEXT_OFFSET: f32 = 50.0; | |||||
pub const FLEET_SELECT_ANIMATION_TIME: f32 = 0.500; | |||||
pub const PLAYER_COLOR_DEFAULT: Vector4f = Vector4f::new(1.0, 1.0, 1.0, 0.1); |
@@ -101,7 +101,12 @@ impl<'a> System<'a> for Summary { | |||||
); | ); | ||||
drop(guard); | drop(guard); | ||||
self.text.render(true); | |||||
gl::enable(gl::BLEND); | |||||
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); | |||||
self.text.render(); | |||||
gl::disable(gl::BLEND); | |||||
} | } | ||||
} | } | ||||
@@ -13,7 +13,7 @@ pub use error::Error; | |||||
use debug::{Fleets as DebugFleets, Ships as DebugShips, Summary as DebugSummary}; | use debug::{Fleets as DebugFleets, Ships as DebugShips, Summary as DebugSummary}; | ||||
use misc::{Events, TextManager, Window}; | use misc::{Events, TextManager, Window}; | ||||
use render::{Asteroids, Init, Planets, Ships}; | |||||
use render::{Asteroids, Init, Planets, SelectFleet, Ships}; | |||||
use resources::{Camera, Config, Geometry, InputState, PlayerState, Uniform}; | use resources::{Camera, Config, Geometry, InputState, PlayerState, Uniform}; | ||||
use systems::{FleetInfoUpdate, StateUpdate}; | use systems::{FleetInfoUpdate, StateUpdate}; | ||||
@@ -52,6 +52,7 @@ impl<'a, 'b> App<'a, 'b> { | |||||
.with_thread_local(Planets::new(world)?) | .with_thread_local(Planets::new(world)?) | ||||
.with_thread_local(Asteroids::new(world)?) | .with_thread_local(Asteroids::new(world)?) | ||||
.with_thread_local(Ships::new(world)?) | .with_thread_local(Ships::new(world)?) | ||||
.with_thread_local(SelectFleet::new(world, &text_manager)?) | |||||
.with_thread_local(DebugShips::default()) | .with_thread_local(DebugShips::default()) | ||||
.with_thread_local(DebugFleets::default()) | .with_thread_local(DebugFleets::default()) | ||||
.with_thread_local(DebugSummary::new(&text_manager)?) | .with_thread_local(DebugSummary::new(&text_manager)?) | ||||
@@ -6,6 +6,6 @@ mod world; | |||||
pub use events::{ | pub use events::{ | ||||
ControlEvent, Events, KeyboardEvent, MouseButton, MouseEvent, VirtualKeyCode, WindowEvent, | ControlEvent, Events, KeyboardEvent, MouseButton, MouseEvent, VirtualKeyCode, WindowEvent, | ||||
}; | }; | ||||
pub use text::{Text, TextCache, TextManager}; | |||||
pub use text::{HorizontalAlign, Text, TextCache, TextManager, VerticalAlign}; | |||||
pub use window::Window; | pub use window::Window; | ||||
pub use world::WorldHelper; | pub use world::WorldHelper; |
@@ -14,15 +14,15 @@ use glc::{ | |||||
array_buffer::{ArrayBuffer, Target, Usage}, | array_buffer::{ArrayBuffer, Target, Usage}, | ||||
error::Error as GlcError, | error::Error as GlcError, | ||||
misc::BindGuard, | misc::BindGuard, | ||||
shader::{Program, Type}, | |||||
shader::{Program, Type, Uniform}, | |||||
texture::{FilterMag, FilterMin, Target as TextureTarget, Texture, Wrap}, | texture::{FilterMag, FilterMin, Target as TextureTarget, Texture, Wrap}, | ||||
vector::{Vector2f, Vector4f}, | vector::{Vector2f, Vector4f}, | ||||
vertex_array::{DataType, VertexArray}, | vertex_array::{DataType, VertexArray}, | ||||
}; | }; | ||||
use glyph_brush::{ | use glyph_brush::{ | ||||
ab_glyph::{FontArc, FontVec}, | ab_glyph::{FontArc, FontVec}, | ||||
BrushAction, BrushError, FontId, GlyphBrush, GlyphBrushBuilder, GlyphVertex, OwnedSection, | |||||
OwnedText, Rectangle, | |||||
BrushAction, BrushError, FontId, GlyphBrush, GlyphBrushBuilder, GlyphVertex, Layout, | |||||
OwnedSection, OwnedText, Rectangle, | |||||
}; | }; | ||||
use ordered_float::OrderedFloat; | use ordered_float::OrderedFloat; | ||||
use space_crush_common::misc::{LogResult, Vfs, WorldHelper as _}; | use space_crush_common::misc::{LogResult, Vfs, WorldHelper as _}; | ||||
@@ -30,6 +30,8 @@ use specs::World; | |||||
use crate::{misc::WorldHelper, Error}; | use crate::{misc::WorldHelper, Error}; | ||||
pub use glyph_brush::{HorizontalAlign, VerticalAlign}; | |||||
/* TextManager */ | /* TextManager */ | ||||
#[derive(Clone)] | #[derive(Clone)] | ||||
@@ -391,6 +393,10 @@ enum BuilderItem { | |||||
Font(String), | Font(String), | ||||
Text(String), | Text(String), | ||||
Scale(f32), | Scale(f32), | ||||
VertAlign(VerticalAlign), | |||||
HorzAlign(HorizontalAlign), | |||||
Wrap, | |||||
NoWrap, | |||||
} | } | ||||
impl TextBuilder { | impl TextBuilder { | ||||
@@ -408,6 +414,9 @@ impl TextBuilder { | |||||
let mut font_id = None; | let mut font_id = None; | ||||
let mut color = Vector4f::new(0.0, 0.0, 0.0, 1.0); | let mut color = Vector4f::new(0.0, 0.0, 0.0, 1.0); | ||||
let mut position = None; | let mut position = None; | ||||
let mut wrap = true; | |||||
let mut vert_align = VerticalAlign::Top; | |||||
let mut horz_align = HorizontalAlign::Left; | |||||
for item in self.items { | for item in self.items { | ||||
match item { | match item { | ||||
@@ -418,12 +427,20 @@ impl TextBuilder { | |||||
.with_scale(scale) | .with_scale(scale) | ||||
.with_font_id(font_id); | .with_font_id(font_id); | ||||
let layout = if wrap { | |||||
Layout::default_wrap() | |||||
} else { | |||||
Layout::default_single_line() | |||||
}; | |||||
let layout = layout.v_align(vert_align).h_align(horz_align); | |||||
match (sections.pop(), position.take()) { | match (sections.pop(), position.take()) { | ||||
(Some(section), Some(pos)) => { | (Some(section), Some(pos)) => { | ||||
sections.push(section); | sections.push(section); | ||||
sections.push( | sections.push( | ||||
OwnedSection::default() | OwnedSection::default() | ||||
.with_screen_position(pos) | .with_screen_position(pos) | ||||
.with_layout(layout) | |||||
.add_text(text), | .add_text(text), | ||||
); | ); | ||||
} | } | ||||
@@ -431,19 +448,23 @@ impl TextBuilder { | |||||
sections.push(section.add_text(text)); | sections.push(section.add_text(text)); | ||||
} | } | ||||
(None, Some(pos)) => sections.push( | (None, Some(pos)) => sections.push( | ||||
OwnedSection::<Extra>::default() | |||||
OwnedSection::default() | |||||
.with_screen_position(pos) | .with_screen_position(pos) | ||||
.with_layout(layout) | |||||
.add_text(text), | .add_text(text), | ||||
), | ), | ||||
(None, None) => { | |||||
sections.push(OwnedSection::<Extra>::default().add_text(text)) | |||||
} | |||||
(None, None) => sections | |||||
.push(OwnedSection::default().with_layout(layout).add_text(text)), | |||||
}; | }; | ||||
} | } | ||||
BuilderItem::Position(x, y) => position = Some((x, y)), | BuilderItem::Position(x, y) => position = Some((x, y)), | ||||
BuilderItem::Color(c) => color = c, | BuilderItem::Color(c) => color = c, | ||||
BuilderItem::Scale(s) => scale = s, | BuilderItem::Scale(s) => scale = s, | ||||
BuilderItem::Font(path) => font_id = Some(self.cache.font(&path)?), | BuilderItem::Font(path) => font_id = Some(self.cache.font(&path)?), | ||||
BuilderItem::VertAlign(value) => vert_align = value, | |||||
BuilderItem::HorzAlign(value) => horz_align = value, | |||||
BuilderItem::Wrap => wrap = true, | |||||
BuilderItem::NoWrap => wrap = false, | |||||
} | } | ||||
} | } | ||||
@@ -494,6 +515,30 @@ impl TextBuilder { | |||||
self | self | ||||
} | } | ||||
pub fn wrap(mut self) -> Self { | |||||
self.items.push(BuilderItem::Wrap); | |||||
self | |||||
} | |||||
pub fn nowrap(mut self) -> Self { | |||||
self.items.push(BuilderItem::NoWrap); | |||||
self | |||||
} | |||||
pub fn vert_align(mut self, value: VerticalAlign) -> Self { | |||||
self.items.push(BuilderItem::VertAlign(value)); | |||||
self | |||||
} | |||||
pub fn horz_align(mut self, value: HorizontalAlign) -> Self { | |||||
self.items.push(BuilderItem::HorzAlign(value)); | |||||
self | |||||
} | |||||
} | } | ||||
/* Text */ | /* Text */ | ||||
@@ -509,6 +554,7 @@ struct TextInner { | |||||
texture: Rc<RefCell<Texture>>, | texture: Rc<RefCell<Texture>>, | ||||
sections: Vec<OwnedSection<Extra>>, | sections: Vec<OwnedSection<Extra>>, | ||||
vertex_count: usize, | vertex_count: usize, | ||||
color: Vector4f, | |||||
} | } | ||||
#[derive(Default, Debug, Clone)] | #[derive(Default, Debug, Clone)] | ||||
@@ -570,6 +616,7 @@ impl Text { | |||||
texture, | texture, | ||||
sections, | sections, | ||||
vertex_count: 0, | vertex_count: 0, | ||||
color: Vector4f::new(1.0, 1.0, 1.0, 1.0), | |||||
}; | }; | ||||
let text = Text { | let text = Text { | ||||
cache, | cache, | ||||
@@ -579,23 +626,28 @@ impl Text { | |||||
Ok(text) | Ok(text) | ||||
} | } | ||||
pub fn render(&self, enable_blending: bool) { | |||||
pub fn render(&self) { | |||||
self.render_offset(&Vector2f::new(0.0, 0.0)); | |||||
} | |||||
pub fn render_offset(&self, pos: &Vector2f) { | |||||
let inner = self.inner.borrow(); | let inner = self.inner.borrow(); | ||||
let texture = inner.texture.borrow(); | let texture = inner.texture.borrow(); | ||||
if enable_blending { | |||||
gl::enable(gl::BLEND); | |||||
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); | |||||
} | |||||
let _guard = BindGuard::new(&*texture); | let _guard = BindGuard::new(&*texture); | ||||
let _guard = BindGuard::new(&inner.array); | let _guard = BindGuard::new(&inner.array); | ||||
let _guard = BindGuard::new(&*inner.program); | let _guard = BindGuard::new(&*inner.program); | ||||
gl::draw_arrays_instanced(gl::TRIANGLE_STRIP, 0, 4, inner.vertex_count as _); | |||||
if enable_blending { | |||||
gl::disable(gl::BLEND); | |||||
} | |||||
inner | |||||
.program | |||||
.uniform(1, Uniform::Vector4f(&inner.color)) | |||||
.warn("Unable to update color"); | |||||
inner | |||||
.program | |||||
.uniform(2, Uniform::Vector2f(pos)) | |||||
.warn("Unable to update text offset"); | |||||
gl::draw_arrays_instanced(gl::TRIANGLE_STRIP, 0, 4, inner.vertex_count as _); | |||||
} | } | ||||
pub fn update<S>(&mut self, mut index: usize, text: S) -> Result<(), Error> | pub fn update<S>(&mut self, mut index: usize, text: S) -> Result<(), Error> | ||||
@@ -618,6 +670,12 @@ impl Text { | |||||
self.cache.update() | self.cache.update() | ||||
} | } | ||||
pub fn color(&self, color: Vector4f) { | |||||
let mut inner = self.inner.borrow_mut(); | |||||
inner.color = color; | |||||
} | |||||
} | } | ||||
impl TextInner { | impl TextInner { | ||||
@@ -97,7 +97,7 @@ impl<'a> System<'a> for Asteroids { | |||||
let c = match owned.and_then(|owned| player.get(owned.owner)) { | let c = match owned.and_then(|owned| player.get(owned.owner)) { | ||||
Some(pv) => &pv.color, | Some(pv) => &pv.color, | ||||
None => &*PLAYER_COLOR_DEFAULT, | |||||
None => &PLAYER_COLOR_DEFAULT, | |||||
}; | }; | ||||
let m = Matrix4f::new( | let m = Matrix4f::new( | ||||
@@ -1,9 +1,11 @@ | |||||
mod asteroids; | mod asteroids; | ||||
mod init; | mod init; | ||||
mod planets; | mod planets; | ||||
mod select_fleet; | |||||
mod ships; | mod ships; | ||||
pub use asteroids::Asteroids; | pub use asteroids::Asteroids; | ||||
pub use init::Init; | pub use init::Init; | ||||
pub use planets::Planets; | pub use planets::Planets; | ||||
pub use select_fleet::SelectFleet; | |||||
pub use ships::Ships; | pub use ships::Ships; |
@@ -90,7 +90,7 @@ impl<'a> System<'a> for Planets { | |||||
let c = match owned.and_then(|owned| player.get(owned.owner)) { | let c = match owned.and_then(|owned| player.get(owned.owner)) { | ||||
Some(pv) => &pv.color, | Some(pv) => &pv.color, | ||||
None => &*PLAYER_COLOR_DEFAULT, | |||||
None => &PLAYER_COLOR_DEFAULT, | |||||
}; | }; | ||||
let m = Matrix4f::new( | let m = Matrix4f::new( | ||||
@@ -0,0 +1,337 @@ | |||||
use std::time::Instant; | |||||
use glc::{ | |||||
animation::{animate, linear}, | |||||
math::{clamp, linear_step}, | |||||
matrix::Matrix4f, | |||||
misc::BindGuard, | |||||
shader::{Program, Type, Uniform}, | |||||
vector::{Vector2f, Vector4f}, | |||||
}; | |||||
use log::debug; | |||||
use shrev::{EventChannel, ReaderId}; | |||||
use space_crush_common::{ | |||||
components::{Fleet, Position, ShipCount}, | |||||
misc::{LogResult, WorldHelper as _}, | |||||
resources::Global, | |||||
}; | |||||
use specs::{prelude::*, Entities, ReadExpect, ReadStorage, System, World}; | |||||
use crate::{ | |||||
components::FleetInfo, | |||||
constants::{ | |||||
FLEET_SELECT_ANIMATION_TIME, FLEET_SELECT_DETAIL_TIMEOUT, FLEET_SELECT_OFFSET, | |||||
FLEET_SELECT_TEXT_OFFSET, FLEET_SELECT_WIDTH, UNIFORM_BUFFER_INDEX_CAMERA, | |||||
}, | |||||
misc::{ | |||||
HorizontalAlign, MouseEvent, Text, TextCache, TextManager, VerticalAlign, WorldHelper as _, | |||||
}, | |||||
resources::{Camera, Config, Geometry, InputState, PlayerState, Selection}, | |||||
Error, | |||||
}; | |||||
pub struct SelectFleet { | |||||
program: Program, | |||||
location_model: gl::GLint, | |||||
location_rings: gl::GLint, | |||||
location_progress: gl::GLint, | |||||
location_marker: gl::GLint, | |||||
location_size: gl::GLint, | |||||
location_value: gl::GLint, | |||||
cache: TextCache, | |||||
text_total: Text, | |||||
select_mode: SelectMode, | |||||
mouse_event_id: ReaderId<MouseEvent>, | |||||
} | |||||
#[derive(Copy, Clone, Debug)] | |||||
enum SelectMode { | |||||
None, | |||||
Init { detail_timeout: Instant }, | |||||
Simple { progress: f32 }, | |||||
SimpleClose { progress: f32, mouse_pos: Vector2f }, | |||||
Detail, | |||||
} | |||||
#[derive(SystemData)] | |||||
pub struct SelectFleetData<'a> { | |||||
player_state: WriteExpect<'a, PlayerState>, | |||||
camera: WriteExpect<'a, Camera>, | |||||
entities: Entities<'a>, | |||||
mouse_events: ReadExpect<'a, EventChannel<MouseEvent>>, | |||||
input_state: ReadExpect<'a, InputState>, | |||||
geometry: ReadExpect<'a, Geometry>, | |||||
global: ReadExpect<'a, Global>, | |||||
config: ReadExpect<'a, Config>, | |||||
fleet_infos: ReadStorage<'a, FleetInfo>, | |||||
positions: ReadStorage<'a, Position>, | |||||
fleets: ReadStorage<'a, Fleet>, | |||||
} | |||||
impl SelectFleet { | |||||
pub fn new(world: &World, text_manager: &TextManager) -> Result<Self, Error> { | |||||
let program = world.load_program(vec![ | |||||
( | |||||
Type::Vertex, | |||||
"resources/shader/fleet_select/simple/vert.glsl", | |||||
), | |||||
( | |||||
Type::Fragment, | |||||
"resources/shader/fleet_select/simple/frag.glsl", | |||||
), | |||||
])?; | |||||
program.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?; | |||||
let location_model = program.uniform_location("uModel")?; | |||||
let location_rings = program.uniform_location("uRings")?; | |||||
let location_progress = program.uniform_location("uProgress")?; | |||||
let location_marker = program.uniform_location("uMarker")?; | |||||
let location_size = program.uniform_location("uSize")?; | |||||
let location_value = program.uniform_location("uValue")?; | |||||
let cache = text_manager.create_cache()?; | |||||
let text_total = cache | |||||
.new_text() | |||||
.scale(32.0) | |||||
.font("resources/fonts/DroidSansMono.ttf") | |||||
.color(1.0, 1.0, 1.0, 0.75) | |||||
.nowrap() | |||||
.vert_align(VerticalAlign::Center) | |||||
.horz_align(HorizontalAlign::Center) | |||||
.text("-") | |||||
.build()?; | |||||
let select_mode = SelectMode::None; | |||||
let mouse_event_id = world.register_event_reader::<MouseEvent>()?; | |||||
Ok(Self { | |||||
program, | |||||
location_model, | |||||
location_rings, | |||||
location_progress, | |||||
location_marker, | |||||
location_size, | |||||
location_value, | |||||
cache, | |||||
text_total, | |||||
select_mode, | |||||
mouse_event_id, | |||||
}) | |||||
} | |||||
fn handle_events(&mut self, d: &mut SelectFleetData<'_>) { | |||||
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 = d.input_state.mouse_pos; | |||||
let pos = Vector4f::new(pos.0, pos.1, 0.0, 1.0); | |||||
let pos = d.camera.view_inverted() * pos; | |||||
let pos = Vector2f::new(pos.x, pos.y); | |||||
self.select_mode = SelectMode::Init { | |||||
detail_timeout: Instant::now() + FLEET_SELECT_DETAIL_TIMEOUT, | |||||
}; | |||||
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 => { | |||||
debug!("Re-selected fleet: {:?}", id); | |||||
Some(s) | |||||
} | |||||
_ => { | |||||
debug!("Selected fleet: {:?}", id); | |||||
Some(Selection { | |||||
fleet: id, | |||||
count: ShipCount::all(), | |||||
}) | |||||
} | |||||
}; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
MouseEvent::ButtonUp(button) if button == &d.config.input.fleet_select_button => { | |||||
self.select_mode = match self.select_mode { | |||||
SelectMode::Simple { progress, .. } => SelectMode::SimpleClose { | |||||
progress, | |||||
mouse_pos: d.input_state.mouse_pos.into(), | |||||
}, | |||||
_ => SelectMode::None, | |||||
} | |||||
} | |||||
MouseEvent::Move(_, _) if self.select_mode.is_init() => { | |||||
self.select_mode = SelectMode::Simple { progress: 0.0 } | |||||
} | |||||
_ => (), | |||||
} | |||||
} | |||||
} | |||||
fn render_simple( | |||||
&mut self, | |||||
progress: f32, | |||||
mouse_pos: Vector2f, | |||||
d: &mut SelectFleetData<'_>, | |||||
) -> Option<()> { | |||||
let selection = d.player_state.selection.as_mut()?; | |||||
let fleet_info = d.fleet_infos.get(selection.fleet)?; | |||||
let position = d.positions.get(selection.fleet)?; | |||||
let mut axis_x = d.camera.view().axis_x; | |||||
axis_x.w = 1.0; | |||||
let zoom = axis_x.length(); | |||||
let shape_size = position.shape.radius().unwrap_or(0.0); | |||||
let ring0 = shape_size + FLEET_SELECT_OFFSET / zoom; | |||||
let ring1 = ring0 + FLEET_SELECT_WIDTH / zoom; | |||||
let size = ring1 + 50.0; | |||||
let px = position.pos.x; | |||||
let py = position.pos.y; | |||||
let m = Matrix4f::new( | |||||
Vector4f::new(size, 0.0, 0.0, 0.0), | |||||
Vector4f::new(0.0, size, 0.0, 0.0), | |||||
Vector4f::new(0.0, 0.0, size, 0.0), | |||||
Vector4f::new(px, py, 0.0, 1.0), | |||||
); | |||||
let rings = Vector2f::new(ring0 / size, ring1 / size); | |||||
let marker = mouse_pos; | |||||
let marker = Vector4f::new(marker.x, marker.y, 0.0, 1.0); | |||||
let marker = d.camera.view_inverted() * marker; | |||||
let marker = Vector2f::new(marker.x, marker.y); | |||||
let marker = marker - position.pos; | |||||
let value = marker.length(); | |||||
let value = if value > ring1 { | |||||
selection.count = ShipCount::all(); | |||||
let _guard = self.cache.begin_update(); | |||||
self.text_total | |||||
.update(0, fleet_info.count.total().to_string()) | |||||
.error("Unable to update text"); | |||||
ring1 | |||||
} else if value > shape_size { | |||||
let value = linear_step(ring0, ring1, value); | |||||
selection.count = fleet_info.count * value; | |||||
let _guard = self.cache.begin_update(); | |||||
self.text_total | |||||
.update(0, selection.count.total().to_string()) | |||||
.error("Unable to update text"); | |||||
animate(ring0, ring1, value, linear) | |||||
} else { | |||||
let value = selection.count.total() as f32 / fleet_info.count.total() as f32; | |||||
let value = clamp(0.0, 1.0, value); | |||||
animate(ring0, ring1, value, linear) | |||||
}; | |||||
let guard = BindGuard::new(&self.program); | |||||
self.program | |||||
.uniform(self.location_model, Uniform::Matrix4f(&m)) | |||||
.error("Error while updating model matrix"); | |||||
self.program | |||||
.uniform(self.location_rings, Uniform::Vector2f(&rings)) | |||||
.error("Error while updating rings"); | |||||
self.program | |||||
.uniform(self.location_progress, Uniform::Float(progress)) | |||||
.error("Error while updating progress"); | |||||
self.program | |||||
.uniform(self.location_marker, Uniform::Vector2f(&marker)) | |||||
.error("Error while updating marker"); | |||||
self.program | |||||
.uniform(self.location_value, Uniform::Float(value / size)) | |||||
.error("Error while updating value"); | |||||
self.program | |||||
.uniform(self.location_size, Uniform::Float(shape_size / size)) | |||||
.error("Error while updating size"); | |||||
d.geometry.render_quad(); | |||||
drop(guard); | |||||
let pos = marker.normalize() * (ring1 + FLEET_SELECT_TEXT_OFFSET / zoom.sqrt()); | |||||
let pos = position.pos + pos; | |||||
let pos = Vector4f::new(pos.x, pos.y, 0.0, 1.0); | |||||
let pos = d.camera.view() * pos; | |||||
let mut pos = Vector2f::new(pos.x, pos.y); | |||||
pos.x += d.input_state.resolution.0 as f32 / 2.0; | |||||
pos.y = d.input_state.resolution.1 as f32 / 2.0 - pos.y; | |||||
if progress <= 1.0 { | |||||
self.text_total | |||||
.color(Vector4f::new(1.0, 1.0, 1.0, progress)); | |||||
} else { | |||||
self.text_total | |||||
.color(Vector4f::new(1.0, 1.0, 1.0, 1.0 - (progress - 1.0))); | |||||
} | |||||
self.text_total.render_offset(&pos); | |||||
Some(()) | |||||
} | |||||
} | |||||
impl SelectMode { | |||||
pub fn is_init(&self) -> bool { | |||||
matches!(self, Self::Init { .. }) | |||||
} | |||||
} | |||||
impl<'a> System<'a> for SelectFleet { | |||||
type SystemData = SelectFleetData<'a>; | |||||
fn run(&mut self, mut data: Self::SystemData) { | |||||
self.handle_events(&mut data); | |||||
gl::enable(gl::BLEND); | |||||
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); | |||||
match &mut self.select_mode { | |||||
SelectMode::None => (), | |||||
SelectMode::Init { detail_timeout } => { | |||||
if *detail_timeout < Instant::now() { | |||||
self.select_mode = SelectMode::Detail; | |||||
} | |||||
} | |||||
SelectMode::Simple { progress } => { | |||||
let delta = data.global.delta / FLEET_SELECT_ANIMATION_TIME; | |||||
*progress = clamp(0.0, 1.0, *progress + delta); | |||||
let progress = *progress; | |||||
self.render_simple(progress, data.input_state.mouse_pos.into(), &mut data); | |||||
} | |||||
SelectMode::SimpleClose { | |||||
progress, | |||||
mouse_pos, | |||||
} => { | |||||
let mouse_pos = *mouse_pos; | |||||
*progress += data.global.delta / FLEET_SELECT_ANIMATION_TIME; | |||||
let progress = if *progress > 2.0 { | |||||
self.select_mode = SelectMode::None; | |||||
2.0 | |||||
} else { | |||||
*progress | |||||
}; | |||||
self.render_simple(progress, mouse_pos, &mut data); | |||||
} | |||||
SelectMode::Detail => (), | |||||
} | |||||
gl::disable(gl::BLEND); | |||||
} | |||||
} |
@@ -98,7 +98,7 @@ impl<'a> System<'a> for Ships { | |||||
let c = match owned.and_then(|owned| player.get(owned.owner)) { | let c = match owned.and_then(|owned| player.get(owned.owner)) { | ||||
Some(pv) => &pv.color, | Some(pv) => &pv.color, | ||||
None => &*PLAYER_COLOR_DEFAULT, | |||||
None => &PLAYER_COLOR_DEFAULT, | |||||
}; | }; | ||||
let p_x = p.pos.x; | let p_x = p.pos.x; | ||||
@@ -9,36 +9,72 @@ use glc::{ | |||||
pub struct Camera { | pub struct Camera { | ||||
buffer: ArrayBuffer, | buffer: ArrayBuffer, | ||||
data: Data, | |||||
view_inverted: Option<Matrix4f>, | |||||
} | |||||
#[repr(C, packed)] | |||||
#[derive(Clone)] | |||||
struct Data { | |||||
projection: Matrix4f, | |||||
view: Matrix4f, | |||||
size: Vector2f, | |||||
} | } | ||||
impl Camera { | impl Camera { | ||||
pub fn new() -> Result<Self, Error> { | pub fn new() -> Result<Self, Error> { | ||||
let data = Data { | |||||
projection: Matrix4f::identity(), | |||||
view: Matrix4f::identity(), | |||||
size: Vector2f::default(), | |||||
}; | |||||
let mut buffer = ArrayBuffer::new(Target::UniformBuffer)?; | let mut buffer = ArrayBuffer::new(Target::UniformBuffer)?; | ||||
buffer.buffer_data( | |||||
Usage::StaticDraw, | |||||
&[Data { | |||||
projection: Matrix4f::identity(), | |||||
view: Matrix4f::identity(), | |||||
size: Vector2f::default(), | |||||
}], | |||||
)?; | |||||
Ok(Self { buffer }) | |||||
buffer.buffer_data(Usage::StaticDraw, &[data.clone()])?; | |||||
Ok(Self { | |||||
buffer, | |||||
data, | |||||
view_inverted: None, | |||||
}) | |||||
} | |||||
pub fn projection(&self) -> &Matrix4f { | |||||
&self.data.projection | |||||
} | |||||
pub fn view(&self) -> &Matrix4f { | |||||
&self.data.view | |||||
} | |||||
pub fn view_inverted(&mut self) -> &Matrix4f { | |||||
if self.view_inverted.is_none() { | |||||
self.view_inverted = Some(self.data.view.invert()); | |||||
} | |||||
self.view_inverted.as_ref().unwrap() | |||||
} | |||||
pub fn size(&self) -> &Vector2f { | |||||
&self.data.size | |||||
} | } | ||||
pub fn resize(&mut self, w: f32, h: f32) -> Result<(), Error> { | pub fn resize(&mut self, w: f32, h: f32) -> Result<(), Error> { | ||||
let mut data = self.buffer.map_mut::<Data>(true)?; | |||||
self.data.projection = Matrix4f::ortho(-w / 2.0, w / 2.0, -h / 2.0, h / 2.0, -100.0, 100.0); | |||||
self.data.size = (w, h).into(); | |||||
data[0].projection = Matrix4f::ortho(-w / 2.0, w / 2.0, -h / 2.0, h / 2.0, -100.0, 100.0); | |||||
data[0].size = (w, h).into(); | |||||
let mut data = self.buffer.map_mut::<Data>(true)?; | |||||
data[0].projection = self.data.projection; | |||||
data[0].size = self.data.size; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
pub fn update(&mut self, view: Matrix4f) -> Result<(), Error> { | pub fn update(&mut self, view: Matrix4f) -> Result<(), Error> { | ||||
let mut data = self.buffer.map_mut::<Data>(true)?; | |||||
self.view_inverted = None; | |||||
self.data.view = self.data.view * view; | |||||
data[0].view = data[0].view * view; | |||||
let mut data = self.buffer.map_mut::<Data>(true)?; | |||||
data[0].view = view; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
@@ -47,9 +83,11 @@ impl Camera { | |||||
where | where | ||||
F: FnOnce(&Matrix4f) -> Matrix4f, | F: FnOnce(&Matrix4f) -> Matrix4f, | ||||
{ | { | ||||
let mut data = self.buffer.map_mut::<Data>(true)?; | |||||
self.view_inverted = None; | |||||
self.data.view = f(&self.data.view); | |||||
data[0].view = f(&data[0].view); | |||||
let mut data = self.buffer.map_mut::<Data>(true)?; | |||||
data[0].view = self.data.view; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
@@ -58,10 +96,3 @@ impl Camera { | |||||
Error::checked(|| self.buffer.bind_buffer_base(index)) | Error::checked(|| self.buffer.bind_buffer_base(index)) | ||||
} | } | ||||
} | } | ||||
#[repr(C, packed)] | |||||
struct Data { | |||||
projection: Matrix4f, | |||||
view: Matrix4f, | |||||
size: Vector2f, | |||||
} |
@@ -44,6 +44,8 @@ pub struct Input { | |||||
#[serde(default = "defaults::camera_move_button")] | #[serde(default = "defaults::camera_move_button")] | ||||
pub camera_move_button: MouseButton, | pub camera_move_button: MouseButton, | ||||
#[serde(default = "defaults::fleet_select_button")] | |||||
pub fleet_select_button: MouseButton, | |||||
#[serde(default = "defaults::camera_move_speed")] | #[serde(default = "defaults::camera_move_speed")] | ||||
pub camera_move_speed: f32, | pub camera_move_speed: f32, | ||||
@@ -92,6 +94,7 @@ impl Default for Input { | |||||
camera_move_key_right: defaults::camera_move_key_right(), | camera_move_key_right: defaults::camera_move_key_right(), | ||||
camera_move_button: defaults::camera_move_button(), | camera_move_button: defaults::camera_move_button(), | ||||
fleet_select_button: defaults::fleet_select_button(), | |||||
camera_move_speed: defaults::camera_move_speed(), | camera_move_speed: defaults::camera_move_speed(), | ||||
camera_zoom_speed: defaults::camera_zoom_speed(), | camera_zoom_speed: defaults::camera_zoom_speed(), | ||||
@@ -138,6 +141,10 @@ mod defaults { | |||||
MouseButton::Right | MouseButton::Right | ||||
} | } | ||||
pub fn fleet_select_button() -> MouseButton { | |||||
MouseButton::Left | |||||
} | |||||
pub fn camera_move_speed() -> f32 { | pub fn camera_move_speed() -> f32 { | ||||
500.0 | 500.0 | ||||
} | } | ||||
@@ -9,5 +9,5 @@ pub use camera::Camera; | |||||
pub use config::Config; | pub use config::Config; | ||||
pub use geometry::Geometry; | pub use geometry::Geometry; | ||||
pub use input_state::InputState; | pub use input_state::InputState; | ||||
pub use player_state::PlayerState; | |||||
pub use player_state::{PlayerState, Selection}; | |||||
pub use uniform::Uniform; | pub use uniform::Uniform; |
@@ -1,13 +1,23 @@ | |||||
#![allow(dead_code)] | #![allow(dead_code)] | ||||
use space_crush_common::components::ShipCount; | |||||
use specs::Entity; | use specs::Entity; | ||||
pub struct PlayerState { | pub struct PlayerState { | ||||
pub player_id: Entity, | pub player_id: Entity, | ||||
pub selection: Option<Selection>, | |||||
} | |||||
pub struct Selection { | |||||
pub fleet: Entity, | |||||
pub count: ShipCount, | |||||
} | } | ||||
impl PlayerState { | impl PlayerState { | ||||
pub fn new(player_id: Entity) -> Self { | pub fn new(player_id: Entity) -> Self { | ||||
Self { player_id } | |||||
Self { | |||||
player_id, | |||||
selection: None, | |||||
} | |||||
} | } | ||||
} | } |
@@ -8,7 +8,6 @@ edition = "2018" | |||||
glc = { version = "0.1", features = [ "serde" ] } | glc = { version = "0.1", features = [ "serde" ] } | ||||
hibitset = "0.6" | hibitset = "0.6" | ||||
log = { version = "0.4", features = [ "max_level_trace", "release_max_level_warn" ] } | log = { version = "0.4", features = [ "max_level_trace", "release_max_level_warn" ] } | ||||
lazy_static = "1.4" | |||||
log4rs = "0.13" | log4rs = "0.13" | ||||
rand = "0.7" | rand = "0.7" | ||||
serde = "1.0" | serde = "1.0" | ||||
@@ -19,6 +19,13 @@ impl Component for Position { | |||||
} | } | ||||
impl Shape { | impl Shape { | ||||
pub fn radius(&self) -> Option<f32> { | |||||
match self { | |||||
Self::Dot => Some(0.0), | |||||
Self::Circle(r) => Some(*r), | |||||
} | |||||
} | |||||
pub fn circle(&self) -> Option<f32> { | pub fn circle(&self) -> Option<f32> { | ||||
if let Self::Circle(r) = &self { | if let Self::Circle(r) = &self { | ||||
Some(*r) | Some(*r) | ||||
@@ -1,4 +1,4 @@ | |||||
use std::ops::{Index, IndexMut}; | |||||
use std::ops::{Index, IndexMut, Mul}; | |||||
use glc::{matrix::Angle, vector::Vector2f}; | use glc::{matrix::Angle, vector::Vector2f}; | ||||
use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||
@@ -12,7 +12,7 @@ pub struct Ship { | |||||
pub target_dir: Vector2f, | pub target_dir: Vector2f, | ||||
} | } | ||||
#[derive(Clone, Debug, Default)] | |||||
#[derive(Copy, Clone, Debug, Default)] | |||||
pub struct Count { | pub struct Count { | ||||
pub fighter: usize, | pub fighter: usize, | ||||
pub bomber: usize, | pub bomber: usize, | ||||
@@ -30,6 +30,22 @@ impl Component for Ship { | |||||
type Storage = VecStorage<Self>; | type Storage = VecStorage<Self>; | ||||
} | } | ||||
impl Count { | |||||
pub fn all() -> Self { | |||||
Self { | |||||
fighter: usize::MAX, | |||||
bomber: usize::MAX, | |||||
transporter: usize::MAX, | |||||
} | |||||
} | |||||
pub fn total(&self) -> usize { | |||||
self.fighter | |||||
.saturating_add(self.bomber) | |||||
.saturating_add(self.transporter) | |||||
} | |||||
} | |||||
impl Index<Type> for Count { | impl Index<Type> for Count { | ||||
type Output = usize; | type Output = usize; | ||||
@@ -51,3 +67,40 @@ impl IndexMut<Type> for Count { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
impl Mul<f32> for Count { | |||||
type Output = Count; | |||||
#[allow(unused_assignments)] | |||||
fn mul(self, rhs: f32) -> Self::Output { | |||||
let expected = self.total() as f32; | |||||
let expected = (rhs * expected).ceil() as usize; | |||||
let mut fighter = (rhs * self.fighter as f32) as usize; | |||||
let mut bomber = (rhs * self.bomber as f32) as usize; | |||||
let mut transporter = (rhs * self.transporter as f32) as usize; | |||||
let mut actual = fighter.saturating_add(bomber).saturating_add(transporter); | |||||
if actual < expected && fighter < self.fighter { | |||||
fighter += 1; | |||||
actual += 1; | |||||
} | |||||
if actual < expected && bomber < self.bomber { | |||||
bomber += 1; | |||||
actual += 1; | |||||
} | |||||
if actual < expected && transporter < self.transporter { | |||||
transporter += 1; | |||||
actual += 1; | |||||
} | |||||
Count { | |||||
fighter, | |||||
bomber, | |||||
transporter, | |||||
} | |||||
} | |||||
} |
@@ -1,5 +1,4 @@ | |||||
use glc::{matrix::Angle, vector::Vector2f}; | use glc::{matrix::Angle, vector::Vector2f}; | ||||
use lazy_static::lazy_static; | |||||
/// Distance to orbit before ship is handled as "in orbit" in % | /// Distance to orbit before ship is handled as "in orbit" in % | ||||
pub const SHIP_ORBIT_DISTANCE_MAX: f32 = 1.10; | pub const SHIP_ORBIT_DISTANCE_MAX: f32 = 1.10; | ||||
@@ -13,6 +12,4 @@ pub const SHIP_ORBIT_ANGLE_DELTA_RND: Angle<f32> = Angle::Deg(4000.0); | |||||
/// Agility of ships inside orbit | /// Agility of ships inside orbit | ||||
pub const SHIP_ORBIT_AGILITY: Angle<f32> = Angle::Deg(90.0); | pub const SHIP_ORBIT_AGILITY: Angle<f32> = Angle::Deg(90.0); | ||||
lazy_static! { | |||||
pub static ref VECTOR_2F_POS_X: Vector2f = Vector2f::new(1.0, 0.0); | |||||
} | |||||
pub const VECTOR_2F_POS_X: Vector2f = Vector2f::new(1.0, 0.0); |