| @@ -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); | |||||