@@ -1,5 +1,8 @@ | |||
use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; | |||
use std::f64::consts::PI; | |||
use std::ops::{Add, Div, Mul, Neg, Sub}; | |||
#[cfg(feature = "serde")] | |||
use serde::{Deserialize, Serialize}; | |||
pub use super::numeric::{Float, Numeric}; | |||
@@ -57,6 +60,14 @@ where | |||
pub fn cos(self) -> T { | |||
T::cos(self.into_rad().into_inner()) | |||
} | |||
/// Normalizes the angle to (-pi, pi] / (-180.0, 180.0] | |||
pub fn normalize(self) -> Self { | |||
match self { | |||
Self::Deg(value) => Self::Deg(normalize(value, T::new(-180.0), T::new(180.0))), | |||
Self::Rad(value) => Self::Rad(normalize(value, T::new(-PI), T::new(PI))), | |||
} | |||
} | |||
} | |||
impl<T> Neg for Angle<T> | |||
@@ -128,3 +139,84 @@ where | |||
} | |||
} | |||
} | |||
impl<T, S> PartialEq<Angle<S>> for Angle<T> | |||
where | |||
T: Numeric + PartialEq<S>, | |||
S: Numeric, | |||
{ | |||
fn eq(&self, other: &Angle<S>) -> bool { | |||
match self { | |||
Self::Deg(v) => v.eq(&other.into_deg().into_inner()), | |||
Self::Rad(v) => v.eq(&other.into_rad().into_inner()), | |||
} | |||
} | |||
} | |||
impl<T> Eq for Angle<T> where T: Numeric + Eq {} | |||
impl<T, S> PartialOrd<Angle<S>> for Angle<T> | |||
where | |||
T: Numeric + PartialOrd<S>, | |||
S: Numeric, | |||
{ | |||
fn partial_cmp(&self, other: &Angle<S>) -> Option<Ordering> { | |||
match self { | |||
Self::Deg(v) => v.partial_cmp(&other.into_deg().into_inner()), | |||
Self::Rad(v) => v.partial_cmp(&other.into_rad().into_inner()), | |||
} | |||
} | |||
} | |||
impl<T> Ord for Angle<T> | |||
where | |||
T: Ord + Numeric, | |||
{ | |||
fn cmp(&self, other: &Self) -> Ordering { | |||
match self { | |||
Self::Deg(v) => v.cmp(&other.into_deg().into_inner()), | |||
Self::Rad(v) => v.cmp(&other.into_rad().into_inner()), | |||
} | |||
} | |||
} | |||
fn normalize<T>(value: T, min: T, max: T) -> T | |||
where | |||
T: Float, | |||
{ | |||
let range = max - min; | |||
let value = value - min; | |||
let f = (value / range).floor(); | |||
value + min - f * range | |||
} | |||
#[cfg(test)] | |||
mod tests { | |||
use super::*; | |||
#[test] | |||
fn normalize() { | |||
assert_eq!(Angle::Deg(-180.0), Angle::Deg(540.0).normalize()); | |||
assert_eq!(Angle::Deg(-180.0), Angle::Deg(180.0).normalize()); | |||
assert_eq!(Angle::Deg(-180.0), Angle::Deg(-180.0).normalize()); | |||
assert_eq!(Angle::Deg(-180.0), Angle::Deg(-540.0).normalize()); | |||
assert_eq!(Angle::Deg(0.0), Angle::Deg(720.0).normalize()); | |||
assert_eq!(Angle::Deg(0.0), Angle::Deg(360.0).normalize()); | |||
assert_eq!(Angle::Deg(0.0), Angle::Deg(-360.0).normalize()); | |||
assert_eq!(Angle::Deg(0.0), Angle::Deg(-720.0).normalize()); | |||
assert_eq!(Angle::Deg(90.0), Angle::Deg(-630.0).normalize()); | |||
assert_eq!(Angle::Deg(90.0), Angle::Deg(-270.0).normalize()); | |||
assert_eq!(Angle::Deg(90.0), Angle::Deg(90.0).normalize()); | |||
assert_eq!(Angle::Deg(90.0), Angle::Deg(450.0).normalize()); | |||
assert_eq!(Angle::Deg(90.0), Angle::Deg(810.0).normalize()); | |||
assert_eq!(Angle::Deg(-90.0), Angle::Deg(-810.0).normalize()); | |||
assert_eq!(Angle::Deg(-90.0), Angle::Deg(-450.0).normalize()); | |||
assert_eq!(Angle::Deg(-90.0), Angle::Deg(-90.0).normalize()); | |||
assert_eq!(Angle::Deg(-90.0), Angle::Deg(270.0).normalize()); | |||
assert_eq!(Angle::Deg(-90.0), Angle::Deg(630.0).normalize()); | |||
} | |||
} |
@@ -28,6 +28,10 @@ pub fn linear<T: Float>(value: T) -> T { | |||
value | |||
} | |||
pub fn smooth<T: Float>(value: T) -> T { | |||
value * value * (T::new(3.0) - T::new(2.0) * value) | |||
} | |||
pub fn invert<T: Float>(value: T) -> T { | |||
T::one() - value | |||
} | |||
@@ -29,6 +29,9 @@ pub trait Float: Numeric { | |||
fn acos(self) -> Self; | |||
fn atan(self) -> Self; | |||
fn atan2(a: Self, b: Self) -> Self; | |||
fn ceil(self) -> Self; | |||
fn round(self) -> Self; | |||
fn floor(self) -> Self; | |||
} | |||
impl Numeric for gl::GLfloat { | |||
@@ -108,6 +111,21 @@ impl Float for gl::GLfloat { | |||
fn atan2(a: Self, b: Self) -> Self { | |||
f32::atan2(a, b) | |||
} | |||
#[inline] | |||
fn ceil(self) -> Self { | |||
f32::ceil(self) | |||
} | |||
#[inline] | |||
fn round(self) -> Self { | |||
f32::round(self) | |||
} | |||
#[inline] | |||
fn floor(self) -> Self { | |||
f32::floor(self) | |||
} | |||
} | |||
impl Numeric for gl::GLdouble { | |||
@@ -187,6 +205,21 @@ impl Float for gl::GLdouble { | |||
fn atan2(a: Self, b: Self) -> Self { | |||
f64::atan2(a, b) | |||
} | |||
#[inline] | |||
fn ceil(self) -> Self { | |||
f64::ceil(self) | |||
} | |||
#[inline] | |||
fn round(self) -> Self { | |||
f64::round(self) | |||
} | |||
#[inline] | |||
fn floor(self) -> Self { | |||
f64::floor(self) | |||
} | |||
} | |||
impl Numeric for gl::GLint { | |||
@@ -253,6 +253,25 @@ where | |||
pub fn scalar(&self, other: &Self) -> T { | |||
self.x * other.x + self.y * other.y | |||
} | |||
#[inline] | |||
pub fn into_vec3(self) -> Vector3<T> { | |||
Vector3 { | |||
x: self.x, | |||
y: self.y, | |||
z: T::zero(), | |||
} | |||
} | |||
#[inline] | |||
pub fn into_vec4(self) -> Vector4<T> { | |||
Vector4 { | |||
x: self.x, | |||
y: self.y, | |||
z: T::zero(), | |||
w: T::one(), | |||
} | |||
} | |||
} | |||
impl<T> Vector2<T> | |||
@@ -331,6 +350,15 @@ where | |||
} | |||
} | |||
impl<T> From<Vector4<T>> for Vector2<T> | |||
where | |||
T: Numeric, | |||
{ | |||
fn from(vec4: Vector4<T>) -> Self { | |||
vec4.into_vec2() | |||
} | |||
} | |||
/* Vector3 */ | |||
impl<T> Vector3<T> | |||
@@ -469,12 +497,12 @@ where | |||
#[inline] | |||
pub fn length_sqr(self) -> T { | |||
self.xyz().length_sqr() | |||
self.into_vec3().length_sqr() | |||
} | |||
#[inline] | |||
pub fn length(self) -> T { | |||
self.xyz().length() | |||
self.into_vec3().length() | |||
} | |||
#[inline] | |||
@@ -501,16 +529,41 @@ where | |||
#[inline] | |||
pub fn scalar(&self, other: &Self) -> T { | |||
self.xyz().scalar(&other.xyz()) | |||
self.into_vec3().scalar(&other.into_vec3()) | |||
} | |||
#[inline] | |||
pub fn cross(&self, other: &Self) -> Self { | |||
(self.xyz().cross(&other.xyz()), Numeric::one()).into() | |||
(self.into_vec3().cross(&other.into_vec3()), Numeric::one()).into() | |||
} | |||
#[inline] | |||
pub fn as_vec2(&self) -> &Vector2<T> { | |||
unsafe { &*(self as *const Vector4<T> as *const Vector2<T>) } | |||
} | |||
#[inline] | |||
pub fn as_vec3(&self) -> &Vector3<T> { | |||
unsafe { &*(self as *const Vector4<T> as *const Vector3<T>) } | |||
} | |||
#[inline] | |||
pub fn xyz(self) -> Vector3<T> { | |||
pub fn into_vec2(self) -> Vector2<T> { | |||
if unsafe { Numeric::is_zero(&self.w) } { | |||
Vector2 { | |||
x: Numeric::zero(), | |||
y: Numeric::zero(), | |||
} | |||
} else { | |||
Vector2 { | |||
x: self.x / self.w, | |||
y: self.y / self.w, | |||
} | |||
} | |||
} | |||
#[inline] | |||
pub fn into_vec3(self) -> Vector3<T> { | |||
if unsafe { Numeric::is_zero(&self.w) } { | |||
Vector3 { | |||
x: Numeric::zero(), | |||
@@ -533,7 +586,7 @@ where | |||
{ | |||
#[inline] | |||
pub fn angle(&self, other: &Self) -> Angle<T> { | |||
self.xyz().angle(&other.xyz()) | |||
self.into_vec3().angle(&other.into_vec3()) | |||
} | |||
} | |||
@@ -545,7 +598,7 @@ where | |||
#[inline] | |||
fn add(self, other: Self) -> Self::Output { | |||
(self.xyz() + other.xyz(), T::one()).into() | |||
(self.into_vec3() + other.into_vec3(), T::one()).into() | |||
} | |||
} | |||
@@ -557,7 +610,7 @@ where | |||
#[inline] | |||
fn sub(self, other: Self) -> Self::Output { | |||
(self.xyz() - other.xyz(), T::one()).into() | |||
(self.into_vec3() - other.into_vec3(), T::one()).into() | |||
} | |||
} | |||
@@ -584,6 +637,34 @@ where | |||
} | |||
} | |||
impl<T> From<Vector2<T>> for Vector4<T> | |||
where | |||
T: Numeric, | |||
{ | |||
fn from(vec2: Vector2<T>) -> Self { | |||
Self { | |||
x: vec2.x, | |||
y: vec2.y, | |||
z: T::zero(), | |||
w: T::one(), | |||
} | |||
} | |||
} | |||
impl<T> From<Vector3<T>> for Vector4<T> | |||
where | |||
T: Numeric, | |||
{ | |||
fn from(vec3: Vector3<T>) -> Self { | |||
Self { | |||
x: vec3.x, | |||
y: vec3.y, | |||
z: vec3.z, | |||
w: T::one(), | |||
} | |||
} | |||
} | |||
impl<T> From<(Vector3<T>, T)> for Vector4<T> { | |||
fn from((vec3, w): (Vector3<T>, T)) -> Self { | |||
Self { | |||
@@ -0,0 +1,113 @@ | |||
#version 450 core | |||
#pragma include ./shared.glsl | |||
#pragma include ../misc/camera.glsl | |||
in FragmentData fragmentData; | |||
layout (location = 1) uniform vec2 uRings; | |||
layout (location = 2) uniform vec2 uMarker; | |||
layout (location = 3) uniform vec4 uValue; | |||
layout (location = 4) uniform float uProgress; | |||
layout (location = 5) uniform float uSize; | |||
out vec4 outColor; | |||
#pragma include ./shared_frag.glsl | |||
/** | |||
* Calculate the splitter animation | |||
* | |||
* @param t Animation process in range [0.0, 1.0] | |||
* @param r Radius of the current fragment | |||
* | |||
* @return The animation value of splitters | |||
*/ | |||
float calcSplitterAnimation(float t, float r) { | |||
return smoothstep2( | |||
animLinear(1.1 * uRings[0], (uRings[0] - 0.05), t), | |||
animLinear(1.1 * uRings[0], uRings[0], t), | |||
animLinear(1.1 * uRings[0], uRings[1], t), | |||
animLinear(1.1 * uRings[0], (uRings[1] + 0.05), t), | |||
r); | |||
} | |||
/** | |||
* Calculate the value of a splitter | |||
* | |||
* @param pi Position of the splitter as multiple of PI / 4 | |||
* @param anim Animation value calculated by 'calcSplitterAnimation' | |||
* @param a Angle of the current fragment | |||
* @param r Radius of the current fragment | |||
* @param zoom Current zoom factor | |||
* | |||
* @return Splitter value. | |||
*/ | |||
float calcSplitter(float pi, float anim, float a, float r, float zoom) { | |||
return anim * calcLine(a, pi * PI / 4.0, MARKER_STRENGTH / r / zoom, MARKER_BLUR / r / zoom); | |||
} | |||
/** | |||
* Calculate the value of a ring that represents a given ship count value | |||
* | |||
* @param pi0 Position to start the value ring at as multiple of PI / 4 | |||
* @param pi1 Position to end the value ring at as multiple of PI / 4 | |||
* @param angle Angle of the current fragment | |||
* @param t Animation process in range [0.0, 1.0] | |||
* @param anim Animation value calculated by 'calcSplitterAnimation' | |||
* @param value Actual value of the ring | |||
* @param r Radius of the current fragment | |||
* @param zoom Current zoom factor | |||
* | |||
* @return Value of the value ring | |||
*/ | |||
float calcValueRing(float pi0, float pi1, float angle, float t, float anim, float value, float r, float zoom) { | |||
float x = animLinear(uRings[0], uRings[1], value); | |||
x = anim * calcLine(r, animEaseInOutQuadric(uSize, x, t), RING_STRENGTH / zoom, RING_BLUR / zoom); | |||
if (pi0 < pi1) { | |||
x *= step( pi0 * PI / 4.0, angle) * step(angle, pi1 * PI / 4.0); | |||
} else { | |||
x *= step( pi0 * PI / 4.0, angle) + step(angle, pi1 * PI / 4.0); | |||
} | |||
return x; | |||
} | |||
void main() { | |||
/* setup */ | |||
float t_opn = clamp(uProgress, 0.0, 1.0); | |||
float t_cls = 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 = calcMarker(t_opn, angle, r, zoom); | |||
/* splitter */ | |||
float x = calcSplitterAnimation(t_opn, r); | |||
float s0 = 0.25 * calcSplitter( 3.0, x, angle, r, zoom); | |||
float s1 = 0.25 * calcSplitter( 1.0, x, angle, r, zoom); | |||
float s2 = 0.25 * calcSplitter(-1.0, x, angle, r, zoom); | |||
float s3 = 0.25 * calcSplitter(-3.0, x, angle, r, zoom); | |||
/* rings */ | |||
float f = calcRingsAnimation(t_opn, angle, 0.0); | |||
float t_opn_rad = clamp(2.0 * t_opn, 0.0, 1.0); | |||
float r0 = 0.25 * calcRing(t_opn_rad, f, uRings[0], r, zoom); | |||
float r1 = 0.25 * calcRing(t_opn_rad, f, uRings[1], r, zoom); | |||
float v0 = 0.75 * calcValueRing( 3.0, -3.0, angle, t_opn_rad, f, uValue[0], r, zoom); | |||
float v1 = 0.75 * calcValueRing( 1.0, 3.0, angle, t_opn_rad, f, uValue[1], r, zoom); | |||
float v2 = 0.75 * calcValueRing(-1.0, 1.0, angle, t_opn_rad, f, uValue[2], r, zoom); | |||
float v3 = 0.75 * calcValueRing(-3.0, -1.0, angle, t_opn_rad, f, uValue[3], r, zoom); | |||
/* alpha */ | |||
float a = calcAlpha(t_opn, t_cls); | |||
/* put together */ | |||
vec3 rgb = vec3(1.0); | |||
outColor = vec4(rgb, (v0 + v1 + v2 + v3 + r0 + r1 + m + s0 + s1 + s2 + s3) * a + uSize * 0.0001); | |||
} |
@@ -0,0 +1,145 @@ | |||
const float RING_STRENGTH = 0.0000; | |||
const float RING_BLUR = 0.0030; | |||
const float MARKER_STRENGTH = 0.0000; | |||
const float MARKER_BLUR = 0.0030; | |||
const vec2 VEC_HORZ = vec2(1.0, 0.0); | |||
const float PI = 3.1415926535897932384626433832795; | |||
/** | |||
* Calculate the a double ended smoothstep | |||
* | |||
* b0 --> b1 --> b2 --> b3 | |||
* 0.0 1.0 1.0 0.0 | |||
*/ | |||
float smoothstep2(float b0, float b1, float b2, float b3, float v) { | |||
return smoothstep(b0, b1, v) * (1.0 - smoothstep(b2, b3, v)); | |||
} | |||
/** | |||
* Calculate a line (vertical or horizonal) | |||
* | |||
* @param actual The current value of the fragment | |||
* @param expected The expected value of the fragment | |||
* @param strength The strength of the line | |||
* @param blur Blur value of the line at the borders | |||
*/ | |||
float calcLine(float actual, float expected, float strength, float blur) { | |||
return smoothstep2( | |||
expected - strength - blur, | |||
expected - strength, | |||
expected + strength, | |||
expected + strength + blur, | |||
actual); | |||
} | |||
/** | |||
* Calculate the angle between the two passed vectors | |||
*/ | |||
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); | |||
} | |||
/** | |||
* Calculate pseudo random value | |||
*/ | |||
float random(float seed) { | |||
return fract(sin(dot(vec4(seed), vec4(12.9898, 78.233, 45.164, 53.1324))) * 43758.5453); | |||
} | |||
/** | |||
* Linear animation | |||
* | |||
* @param v0 Start value | |||
* @param v1 End value | |||
* @param t Time in range of 0.0 to 1.0 | |||
*/ | |||
float animLinear(float v0, float v1, float t) { | |||
return v0 + t * (v1 - v0); | |||
} | |||
/** | |||
* Quadric ease in ease out animation | |||
* | |||
* @param v0 Start value | |||
* @param v1 End value | |||
* @param t Time in range of 0.0 to 1.0 | |||
*/ | |||
float animEaseInOutQuadric(float v0, float v1, float t) { | |||
float x = t * t * (3.0 - 2.0 * t); | |||
return v0 + x * (v1 - v0); | |||
} | |||
/** | |||
* Calculate the position of the marker | |||
* | |||
* @param t Animation progress in range 0.0 to 1.0 | |||
* @param a Angle of the current fragment | |||
* @param r Radius of the current fragment | |||
* @param zoom Current zoom value | |||
*/ | |||
float calcMarker(float t, float a, float r, float zoom) { | |||
float m = calcAngle(uMarker, VEC_HORZ); | |||
m = calcLine(a, m, MARKER_STRENGTH / r / zoom, MARKER_BLUR / r / zoom); | |||
m *= smoothstep2( | |||
t * (uRings[0] - 0.25), | |||
t * uRings[0], | |||
t * uRings[1], | |||
t * (uRings[1] + 0.10), | |||
r); | |||
m *= 0.75; | |||
return m; | |||
} | |||
/** | |||
* Calculate the animation value of a ring | |||
* | |||
* @param t Animation progress in range 0.0 to 1.0 | |||
* @param a Angle of the current fragment | |||
* @param overfill Overfilling the ring by the given value | |||
*/ | |||
float calcRingsAnimation(float t, float a, float overfill) { | |||
float t_opn_ring = clamp((t - 0.25) / 0.75, 0.0, 1.0); | |||
float f = a / PI + 1.0; | |||
f = f * 2.0; | |||
f = fract(f + 0.5); | |||
f = smoothstep2( | |||
animLinear(0.4, 0.0 - overfill, t_opn_ring), | |||
animLinear(0.5, 0.1 - overfill, t_opn_ring), | |||
animLinear(0.5, 0.9 + overfill, t_opn_ring), | |||
animLinear(0.6, 1.0 + overfill, t_opn_ring), | |||
f); | |||
return f; | |||
} | |||
/** | |||
* Calculate the value of an ring | |||
* | |||
* @param t Animation progress in range 0.0 to 1.0 | |||
* @param anim Animation value calculated by 'calcRingsAnimation' | |||
* @param ring Value / position of the ring | |||
* @param r Radius of the current fragment | |||
* @param zoom Current zoom value | |||
*/ | |||
float calcRing(float t, float anim, float ring, float r, float zoom) { | |||
return anim * calcLine(r, animEaseInOutQuadric(uSize, ring, t), RING_STRENGTH / zoom, RING_BLUR / zoom); | |||
} | |||
/** | |||
* Calculate the alpha value of the whole rendered primitive | |||
* | |||
* @param t_opn Progress of the start animation in the range of 0.0 to 1.0 | |||
* @param t_cls Progress of the close animation in the range of 0.0 to 1.0 | |||
*/ | |||
float calcAlpha(float t_opn, float t_cls) { | |||
float as = (0.5 + 0.5 * step(1.0 - t_opn, random(uProgress))); | |||
float ae = (0.5 + 0.5 * step( t_cls, random(uProgress))) * (1.0 - t_cls); | |||
return as * ae; | |||
} |
@@ -1,96 +0,0 @@ | |||
#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,44 @@ | |||
#version 450 core | |||
#pragma include ./shared.glsl | |||
#pragma include ../misc/camera.glsl | |||
in FragmentData fragmentData; | |||
layout (location = 1) uniform vec2 uRings; | |||
layout (location = 2) uniform vec2 uMarker; | |||
layout (location = 3) uniform vec4 uValue; | |||
layout (location = 4) uniform float uProgress; | |||
layout (location = 5) uniform float uSize; | |||
out vec4 outColor; | |||
#pragma include ./shared_frag.glsl | |||
void main() { | |||
/* setup */ | |||
float t_opn = clamp(uProgress, 0.0, 1.0); | |||
float t_cls = 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 = calcMarker(t_opn, angle, r, zoom); | |||
/* rings */ | |||
float f = calcRingsAnimation(t_opn, angle, 0.1); | |||
float t_opn_rad = clamp(2.0 * t_opn, 0.0, 1.0); | |||
float value = animLinear(uRings[0], uRings[1], uValue[3]); | |||
float r0 = 0.25 * calcRing(t_opn_rad, f, uRings[0], r, zoom); | |||
float r1 = 0.25 * calcRing(t_opn_rad, f, uRings[1], r, zoom); | |||
float v = 0.75 * calcRing(t_opn_rad, f, value, r, zoom); | |||
/* alpha */ | |||
float a = calcAlpha(t_opn, t_cls); | |||
/* put together */ | |||
vec3 rgb = vec3(1.0); | |||
outColor = vec4(rgb, (v + r0 + r1 + m) * a + uSize * 0.0001); | |||
} |
@@ -1,11 +1,11 @@ | |||
#version 450 core | |||
#pragma include ./shared.glsl | |||
#pragma include ../../misc/camera.glsl | |||
#pragma include ../misc/camera.glsl | |||
in vec3 inPosition; | |||
uniform mat4 uModel; | |||
layout (location = 0) uniform mat4 uModel; | |||
out FragmentData fragmentData; | |||
@@ -9,10 +9,11 @@ pub const SHIP_SIZE: f32 = 15.0; | |||
pub const PLANET_SIZE: f32 = 200.0; | |||
pub const ASTEROID_SIZE: f32 = 100.0; | |||
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 FLEET_SELECT_DETAIL_TIMEOUT: Duration = Duration::from_millis(250); | |||
pub const FLEET_SELECT_OFFSET: f32 = 10.0; | |||
pub const FLEET_SELECT_WIDTH: f32 = 75.0; | |||
pub const FLEET_SELECT_TEXT_OFFSET: f32 = 40.0; | |||
pub const FLEET_SELECT_ANIMATION_TIME: f32 = 0.400; | |||
pub const FLEET_SELECT_TEXT_SIZE: f32 = 24.0; | |||
pub const PLAYER_COLOR_DEFAULT: Vector4f = Vector4f::new(1.0, 1.0, 1.0, 0.1); |
@@ -53,8 +53,8 @@ impl<'a, 'b> App<'a, 'b> { | |||
.with_thread_local(Asteroids::new(world)?) | |||
.with_thread_local(Ships::new(world)?) | |||
.with_thread_local(SelectFleet::new(world, &text_manager)?) | |||
.with_thread_local(DebugShips::default()) | |||
.with_thread_local(DebugFleets::default()) | |||
// .with_thread_local(DebugShips::default()) | |||
// .with_thread_local(DebugFleets::default()) | |||
.with_thread_local(DebugSummary::new(&text_manager)?) | |||
.build(); | |||
dispatcher.setup(world); | |||
@@ -650,7 +650,7 @@ impl Text { | |||
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<&Self, Error> | |||
where | |||
S: Display, | |||
{ | |||
@@ -668,13 +668,17 @@ impl Text { | |||
drop(inner); | |||
self.cache.update() | |||
self.cache.update()?; | |||
Ok(self) | |||
} | |||
pub fn color(&self, color: Vector4f) { | |||
pub fn color(&self, color: Vector4f) -> &Self { | |||
let mut inner = self.inner.borrow_mut(); | |||
inner.color = color; | |||
self | |||
} | |||
} | |||
@@ -1,17 +1,17 @@ | |||
use std::mem::take; | |||
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}, | |||
constants::VECTOR_2F_POS_X, | |||
misc::{LogResult, WorldHelper as _}, | |||
resources::Global, | |||
}; | |||
@@ -21,7 +21,8 @@ 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, | |||
FLEET_SELECT_TEXT_OFFSET, FLEET_SELECT_TEXT_SIZE, FLEET_SELECT_WIDTH, | |||
UNIFORM_BUFFER_INDEX_CAMERA, | |||
}, | |||
misc::{ | |||
HorizontalAlign, MouseEvent, Text, TextCache, TextManager, VerticalAlign, WorldHelper as _, | |||
@@ -31,32 +32,46 @@ use crate::{ | |||
}; | |||
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, | |||
program_simple: Program, | |||
program_detail: Program, | |||
cache: TextCache, | |||
text_total: Text, | |||
select_mode: SelectMode, | |||
text_fighter: Text, | |||
text_bomber: Text, | |||
text_transporter: Text, | |||
mouse_event_id: ReaderId<MouseEvent>, | |||
select_mode: SelectMode, | |||
camera_counter: usize, | |||
camera_view_invert: Matrix4f, | |||
need_value_update: bool, | |||
values_changed_once: bool, | |||
mouse_pos: Vector2f, | |||
count: ShipCount, | |||
values: Vector4f, | |||
marker: Vector2f, | |||
shape_size: f32, | |||
ring0: f32, | |||
ring1: f32, | |||
zoom: f32, | |||
} | |||
#[derive(Copy, Clone, Debug)] | |||
enum SelectMode { | |||
None, | |||
Init { detail_timeout: Instant }, | |||
Simple { progress: f32 }, | |||
SimpleClose { progress: f32, mouse_pos: Vector2f }, | |||
Detail, | |||
Init(Instant), | |||
Simple(f32), | |||
Detail(f32), | |||
SimpleClose(f32), | |||
DetailClose(f32), | |||
} | |||
#[derive(SystemData)] | |||
pub struct SelectFleetData<'a> { | |||
player_state: WriteExpect<'a, PlayerState>, | |||
camera: WriteExpect<'a, Camera>, | |||
camera: ReadExpect<'a, Camera>, | |||
entities: Entities<'a>, | |||
mouse_events: ReadExpect<'a, EventChannel<MouseEvent>>, | |||
@@ -70,222 +85,439 @@ pub struct SelectFleetData<'a> { | |||
fleets: ReadStorage<'a, Fleet>, | |||
} | |||
macro_rules! unwrap { | |||
($value:expr) => { | |||
match $value { | |||
Some(value) => value, | |||
None => return, | |||
} | |||
}; | |||
} | |||
macro_rules! selection { | |||
(&$data:expr) => { | |||
unwrap!(&$data.player_state.selection) | |||
}; | |||
(&mut $data:expr) => { | |||
unwrap!(&mut $data.player_state.selection) | |||
}; | |||
} | |||
macro_rules! fleet_info { | |||
(&$data:expr, $id:expr) => { | |||
unwrap!($data.fleet_infos.get($id)) | |||
}; | |||
} | |||
macro_rules! position { | |||
(&$data:expr, $id:expr) => { | |||
unwrap!($data.positions.get($id)) | |||
}; | |||
} | |||
impl SelectFleet { | |||
pub fn new(world: &World, text_manager: &TextManager) -> Result<Self, Error> { | |||
let program = world.load_program(vec![ | |||
let program_simple = world.load_program(vec![ | |||
(Type::Vertex, "resources/shader/fleet_select/vert.glsl"), | |||
( | |||
Type::Vertex, | |||
"resources/shader/fleet_select/simple/vert.glsl", | |||
Type::Fragment, | |||
"resources/shader/fleet_select/simple_frag.glsl", | |||
), | |||
])?; | |||
program_simple.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?; | |||
let program_detail = world.load_program(vec![ | |||
(Type::Vertex, "resources/shader/fleet_select/vert.glsl"), | |||
( | |||
Type::Fragment, | |||
"resources/shader/fleet_select/simple/frag.glsl", | |||
"resources/shader/fleet_select/detail_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")?; | |||
program_detail.uniform_block_binding("Camera", UNIFORM_BUFFER_INDEX_CAMERA)?; | |||
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 text_total = new_text(&cache)?; | |||
let text_fighter = new_text(&cache)?; | |||
let text_bomber = new_text(&cache)?; | |||
let text_transporter = new_text(&cache)?; | |||
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, | |||
program_simple, | |||
program_detail, | |||
cache, | |||
text_total, | |||
select_mode, | |||
text_fighter, | |||
text_bomber, | |||
text_transporter, | |||
mouse_event_id, | |||
select_mode, | |||
camera_counter: 0, | |||
camera_view_invert: Default::default(), | |||
need_value_update: true, | |||
values_changed_once: false, | |||
mouse_pos: Default::default(), | |||
count: Default::default(), | |||
values: Default::default(), | |||
marker: Default::default(), | |||
shape_size: Default::default(), | |||
ring0: Default::default(), | |||
ring1: Default::default(), | |||
zoom: Default::default(), | |||
}) | |||
} | |||
fn update_camera(&mut self, d: &SelectFleetData<'_>) { | |||
let camera_counter = d.camera.update_counter(); | |||
if self.camera_counter != camera_counter { | |||
self.camera_counter = camera_counter; | |||
self.need_value_update = true; | |||
self.camera_view_invert = d.camera.view().invert(); | |||
} | |||
} | |||
fn handle_events(&mut self, d: &mut SelectFleetData<'_>) { | |||
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 pos = self.window_to_world(d.input_state.mouse_pos); | |||
let selection = d.player_state.selection.take(); | |||
for (id, position, fleet) in (&d.entities, &d.positions, &d.fleets).join() { | |||
let r = fleet.orbit_max * fleet.orbit_max; | |||
if (position.pos - pos).length_sqr() <= r { | |||
d.player_state.selection = match selection { | |||
Some(s) if s.fleet == id => { | |||
debug!("Re-selected fleet: {:?}", id); | |||
Some(s) | |||
} | |||
_ => { | |||
debug!("Selected fleet: {:?}", id); | |||
Some(Selection { | |||
fleet: id, | |||
count: ShipCount::all(), | |||
}) | |||
} | |||
Some(s) if s.fleet == id => Some(s), | |||
_ => Some(Selection { | |||
fleet: id, | |||
count: ShipCount::all(), | |||
}), | |||
}; | |||
let selection = selection!(&d); | |||
let fleet_info = fleet_info!(&d, selection.fleet); | |||
let timeout = Instant::now() + FLEET_SELECT_DETAIL_TIMEOUT; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
self.select_mode = SelectMode::Init(timeout); | |||
self.values_changed_once = false; | |||
self.set_count(selection.count, &fleet_info.count); | |||
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::Simple(progress) => { | |||
selection!(&mut d).count = self.count; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
SelectMode::SimpleClose(progress) | |||
} | |||
SelectMode::Detail(progress) => { | |||
selection!(&mut d).count = self.count; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
SelectMode::DetailClose(progress) | |||
} | |||
_ => SelectMode::None, | |||
} | |||
} | |||
MouseEvent::Move(_, _) if self.select_mode.is_init() => { | |||
self.select_mode = SelectMode::Simple { progress: 0.0 } | |||
self.need_value_update = true; | |||
self.values_changed_once = false; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
self.select_mode = SelectMode::Simple(0.0); | |||
} | |||
MouseEvent::Move(_, _) if self.select_mode.is_active() => { | |||
self.need_value_update = true; | |||
self.mouse_pos = d.input_state.mouse_pos.into(); | |||
} | |||
_ => (), | |||
} | |||
} | |||
} | |||
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), | |||
); | |||
fn update(&mut self, d: &SelectFleetData<'_>) { | |||
if !self.select_mode.is_active() { | |||
return; | |||
} | |||
if !take(&mut self.need_value_update) { | |||
return; | |||
} | |||
let rings = Vector2f::new(ring0 / size, ring1 / size); | |||
/* calculate values */ | |||
let selection = selection!(&d); | |||
let position = position!(&d, selection.fleet); | |||
let fleet_info = fleet_info!(&d, selection.fleet); | |||
self.marker = self.window_to_world(self.mouse_pos) - position.pos; | |||
self.zoom = d.camera.view().axis_x.as_vec3().length(); | |||
self.shape_size = position.shape.radius().unwrap_or(0.0); | |||
self.ring0 = self.shape_size + FLEET_SELECT_OFFSET / self.zoom; | |||
self.ring1 = self.ring0 + FLEET_SELECT_WIDTH / self.zoom; | |||
let is_simple = self.select_mode.is_simple(); | |||
let angle = self | |||
.marker | |||
.angle2(&VECTOR_2F_POS_X) | |||
.normalize() | |||
.into_deg() | |||
.into_inner(); | |||
let sector = match angle { | |||
x if (135.0 >= x && x > 45.0) || is_simple => 3, | |||
x if 45.0 >= x && x > -45.0 => 0, | |||
x if -45.0 >= x && x > -135.0 => 1, | |||
_ => 2, | |||
}; | |||
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; | |||
/* calulate ship value */ | |||
let value = self.marker.length(); | |||
if value > self.ring1 { | |||
self.values_changed_once = true; | |||
let value = marker.length(); | |||
let value = if value > ring1 { | |||
selection.count = ShipCount::all(); | |||
match sector { | |||
x @ 0..=2 => { | |||
let mut count = selection.count; | |||
count[x] = usize::MAX; | |||
self.set_count(count, &fleet_info.count); | |||
let _guard = self.cache.begin_update(); | |||
self.text_total | |||
.update(0, fleet_info.count.total().to_string()) | |||
.error("Unable to update text"); | |||
self.values[x] = 1.0; | |||
self.update_values(&fleet_info.count); | |||
} | |||
_ => { | |||
self.count = ShipCount::all(); | |||
self.values = Vector4f::new(1.0, 1.0, 1.0, 1.0); | |||
} | |||
} | |||
} else if value > self.shape_size { | |||
let value = linear_step(self.ring0, self.ring1, value); | |||
ring1 | |||
} else if value > shape_size { | |||
let value = linear_step(ring0, ring1, value); | |||
self.values_changed_once = true; | |||
selection.count = fleet_info.count * value; | |||
match sector { | |||
x @ 0..=2 => { | |||
let mut count = selection.count; | |||
count[x] = (fleet_info.count[x] as f32 * value).round() as usize; | |||
self.set_count(count, &fleet_info.count); | |||
let _guard = self.cache.begin_update(); | |||
self.text_total | |||
.update(0, selection.count.total().to_string()) | |||
.error("Unable to update text"); | |||
self.values[x] = value; | |||
self.update_values(&fleet_info.count); | |||
} | |||
_ => { | |||
self.count = fleet_info.count * value; | |||
self.values = value.into(); | |||
} | |||
} | |||
} else if self.values_changed_once { | |||
match sector { | |||
x @ 0..=2 => { | |||
let mut count = selection.count; | |||
count[x] = 0; | |||
self.set_count(count, &fleet_info.count); | |||
self.values[x] = 0.0; | |||
self.update_values(&fleet_info.count); | |||
} | |||
_ => { | |||
self.count = Default::default(); | |||
self.values = Default::default(); | |||
} | |||
} | |||
} | |||
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); | |||
/* update texts */ | |||
let c = self.count.merge(&fleet_info.count); | |||
let _guard = self.cache.begin_update(); | |||
self.text_total | |||
.update(0, c.total().to_string()) | |||
.error("Unable to update text for ship count (total)"); | |||
self.text_fighter | |||
.update(0, c.fighter.to_string()) | |||
.error("Unable to update text for ship count (fighter)"); | |||
self.text_bomber | |||
.update(0, c.bomber.to_string()) | |||
.error("Unable to update text for ship count (bomber)"); | |||
self.text_transporter | |||
.update(0, c.transporter.to_string()) | |||
.error("Unable to update text for ship count (transporter)"); | |||
} | |||
fn progress(&mut self, delta: f32) -> Option<f32> { | |||
match &mut self.select_mode { | |||
SelectMode::Init(detail_timeout) if *detail_timeout < Instant::now() => { | |||
self.need_value_update = true; | |||
self.values_changed_once = false; | |||
self.select_mode = SelectMode::Detail(0.0); | |||
Some(0.0) | |||
} | |||
SelectMode::Simple(progress) => { | |||
*progress += delta / FLEET_SELECT_ANIMATION_TIME; | |||
*progress = clamp(0.0, 1.0, *progress); | |||
Some(*progress) | |||
} | |||
SelectMode::SimpleClose(progress) => { | |||
*progress += delta / FLEET_SELECT_ANIMATION_TIME; | |||
animate(ring0, ring1, value, linear) | |||
if *progress > 2.0 { | |||
self.select_mode = SelectMode::None; | |||
Some(2.0) | |||
} else { | |||
Some(*progress) | |||
} | |||
} | |||
SelectMode::Detail(progress) => { | |||
*progress += delta / FLEET_SELECT_ANIMATION_TIME; | |||
*progress = clamp(0.0, 1.0, *progress); | |||
Some(*progress) | |||
} | |||
SelectMode::DetailClose(progress) => { | |||
*progress += delta / FLEET_SELECT_ANIMATION_TIME; | |||
if *progress > 2.0 { | |||
self.select_mode = SelectMode::None; | |||
Some(2.0) | |||
} else { | |||
Some(*progress) | |||
} | |||
} | |||
_ => None, | |||
} | |||
} | |||
fn render(&mut self, progress: f32, d: &SelectFleetData<'_>) { | |||
/* select program */ | |||
let is_simple = self.select_mode.is_simple(); | |||
let program = if is_simple { | |||
&self.program_simple | |||
} else { | |||
&self.program_detail | |||
}; | |||
let guard = BindGuard::new(&self.program); | |||
self.program | |||
.uniform(self.location_model, Uniform::Matrix4f(&m)) | |||
/* extract system data */ | |||
let selection = selection!(&d); | |||
let position = position!(&d, selection.fleet); | |||
/* calculate shared values */ | |||
let size = self.ring1 + 50.0; | |||
let rings = Vector2f::new(self.ring0 / size, self.ring1 / size); | |||
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), | |||
); | |||
/* update uniforms */ | |||
let guard = BindGuard::new(&program); | |||
program | |||
.uniform(0, Uniform::Matrix4f(&m)) | |||
.error("Error while updating model matrix"); | |||
self.program | |||
.uniform(self.location_rings, Uniform::Vector2f(&rings)) | |||
program | |||
.uniform(1, 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)) | |||
program | |||
.uniform(2, Uniform::Vector2f(&self.marker)) | |||
.error("Error while updating marker"); | |||
self.program | |||
.uniform(self.location_value, Uniform::Float(value / size)) | |||
program | |||
.uniform(3, Uniform::Vector4f(&self.values)) | |||
.error("Error while updating value"); | |||
self.program | |||
.uniform(self.location_size, Uniform::Float(shape_size / size)) | |||
program | |||
.uniform(4, Uniform::Float(progress)) | |||
.error("Error while updating progress"); | |||
program | |||
.uniform(5, Uniform::Float(self.shape_size / size)) | |||
.error("Error while updating size"); | |||
/* render selection menu */ | |||
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; | |||
/* render text */ | |||
let alpha = if progress <= 1.0 { | |||
progress | |||
} else { | |||
2.0 - progress | |||
}; | |||
if progress <= 1.0 { | |||
if is_simple { | |||
self.text_total | |||
.color(Vector4f::new(1.0, 1.0, 1.0, progress)); | |||
.color(Vector4f::new(1.0, 1.0, 1.0, alpha)) | |||
.render_offset(&self.text_pos(self.marker, position.pos, d)); | |||
} else { | |||
self.text_total | |||
.color(Vector4f::new(1.0, 1.0, 1.0, 1.0 - (progress - 1.0))); | |||
.color(Vector4f::new(1.0, 1.0, 1.0, alpha)) | |||
.render_offset(&self.text_pos((0.0, 1.0), position.pos, d)); | |||
self.text_fighter | |||
.color(Vector4f::new(1.0, 1.0, 1.0, alpha)) | |||
.render_offset(&self.text_pos((1.0, 0.0), position.pos, d)); | |||
self.text_bomber | |||
.color(Vector4f::new(1.0, 1.0, 1.0, alpha)) | |||
.render_offset(&self.text_pos((0.0, -1.0), position.pos, d)); | |||
self.text_transporter | |||
.color(Vector4f::new(1.0, 1.0, 1.0, alpha)) | |||
.render_offset(&self.text_pos((-1.0, 0.0), position.pos, d)); | |||
} | |||
} | |||
self.text_total.render_offset(&pos); | |||
fn set_count(&mut self, count: ShipCount, fleet_count: &ShipCount) { | |||
let c = count.merge(fleet_count); | |||
let f = &fleet_count; | |||
Some(()) | |||
self.count = count; | |||
self.values = Vector4f::new( | |||
clamp(0.0, 1.0, c.fighter as f32 / f.fighter as f32), | |||
clamp(0.0, 1.0, c.bomber as f32 / f.bomber as f32), | |||
clamp(0.0, 1.0, c.transporter as f32 / f.transporter as f32), | |||
clamp(0.0, 1.0, c.total() as f32 / f.total() as f32), | |||
); | |||
} | |||
} | |||
impl SelectMode { | |||
pub fn is_init(&self) -> bool { | |||
matches!(self, Self::Init { .. }) | |||
fn update_values(&mut self, fleet_count: &ShipCount) { | |||
let total_max = fleet_count.total() as f32; | |||
let sum = fleet_count[0] as f32 * self.values[0] | |||
+ fleet_count[1] as f32 * self.values[1] | |||
+ fleet_count[2] as f32 * self.values[2]; | |||
self.values[3] = sum / total_max; | |||
} | |||
fn window_to_world<T: Into<Vector2f>>(&self, pos: T) -> Vector2f { | |||
(self.camera_view_invert * T::into(pos).into_vec4()).into_vec2() | |||
} | |||
fn text_pos<T: Into<Vector2f>>( | |||
&self, | |||
dir: T, | |||
fleet_pos: Vector2f, | |||
d: &SelectFleetData<'_>, | |||
) -> Vector2f { | |||
let text_offset = self.ring1 + FLEET_SELECT_TEXT_OFFSET / self.zoom.sqrt(); | |||
let pos = fleet_pos + T::into(dir).normalize() * text_offset; | |||
let mut pos = (d.camera.view() * pos.into_vec4()).into_vec2(); | |||
pos.x += d.input_state.resolution.0 as f32 / 2.0; | |||
pos.y = d.input_state.resolution.1 as f32 / 2.0 - pos.y; | |||
pos | |||
} | |||
} | |||
@@ -293,45 +525,54 @@ impl<'a> System<'a> for SelectFleet { | |||
type SystemData = SelectFleetData<'a>; | |||
fn run(&mut self, mut data: Self::SystemData) { | |||
self.update_camera(&data); | |||
self.handle_events(&mut data); | |||
self.update(&data); | |||
gl::enable(gl::BLEND); | |||
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); | |||
let progress = self.progress(data.global.delta); | |||
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); | |||
if let Some(progress) = progress { | |||
gl::enable(gl::BLEND); | |||
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); | |||
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; | |||
self.render(progress, &data); | |||
2.0 | |||
} else { | |||
*progress | |||
}; | |||
gl::disable(gl::BLEND); | |||
} | |||
} | |||
} | |||
self.render_simple(progress, mouse_pos, &mut data); | |||
} | |||
SelectMode::Detail => (), | |||
impl SelectMode { | |||
pub fn is_init(&self) -> bool { | |||
matches!(self, Self::Init { .. }) | |||
} | |||
pub fn is_simple(&self) -> bool { | |||
match self { | |||
Self::Simple { .. } => true, | |||
Self::SimpleClose { .. } => true, | |||
_ => false, | |||
} | |||
} | |||
gl::disable(gl::BLEND); | |||
pub fn is_active(&self) -> bool { | |||
match self { | |||
Self::Simple { .. } => true, | |||
Self::Detail { .. } => true, | |||
_ => false, | |||
} | |||
} | |||
} | |||
fn new_text(cache: &TextCache) -> Result<Text, Error> { | |||
cache | |||
.new_text() | |||
.scale(FLEET_SELECT_TEXT_SIZE) | |||
.font("resources/fonts/DroidSansMono.ttf") | |||
.color(1.0, 1.0, 1.0, 0.75) | |||
.nowrap() | |||
.vert_align(VerticalAlign::Center) | |||
.horz_align(HorizontalAlign::Center) | |||
.text("0") | |||
.build() | |||
} |
@@ -10,7 +10,7 @@ use glc::{ | |||
pub struct Camera { | |||
buffer: ArrayBuffer, | |||
data: Data, | |||
view_inverted: Option<Matrix4f>, | |||
update_counter: usize, | |||
} | |||
#[repr(C, packed)] | |||
@@ -34,7 +34,7 @@ impl Camera { | |||
Ok(Self { | |||
buffer, | |||
data, | |||
view_inverted: None, | |||
update_counter: 0, | |||
}) | |||
} | |||
@@ -46,12 +46,8 @@ impl Camera { | |||
&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 update_counter(&self) -> usize { | |||
self.update_counter | |||
} | |||
pub fn size(&self) -> &Vector2f { | |||
@@ -66,29 +62,26 @@ impl Camera { | |||
data[0].projection = self.data.projection; | |||
data[0].size = self.data.size; | |||
self.update_counter = self.update_counter.wrapping_add(1); | |||
Ok(()) | |||
} | |||
pub fn update(&mut self, view: Matrix4f) -> Result<(), Error> { | |||
self.view_inverted = None; | |||
self.data.view = self.data.view * view; | |||
let mut data = self.buffer.map_mut::<Data>(true)?; | |||
data[0].view = view; | |||
Ok(()) | |||
pub fn update(&mut self, m: Matrix4f) -> Result<(), Error> { | |||
self.update_with(|view| view * m) | |||
} | |||
pub fn update_with<F>(&mut self, f: F) -> Result<(), Error> | |||
where | |||
F: FnOnce(&Matrix4f) -> Matrix4f, | |||
{ | |||
self.view_inverted = None; | |||
self.data.view = f(&self.data.view); | |||
let mut data = self.buffer.map_mut::<Data>(true)?; | |||
data[0].view = self.data.view; | |||
self.update_counter = self.update_counter.wrapping_add(1); | |||
Ok(()) | |||
} | |||
@@ -1,3 +1,4 @@ | |||
use std::cmp::min; | |||
use std::ops::{Index, IndexMut, Mul}; | |||
use glc::{matrix::Angle, vector::Vector2f}; | |||
@@ -44,6 +45,38 @@ impl Count { | |||
.saturating_add(self.bomber) | |||
.saturating_add(self.transporter) | |||
} | |||
pub fn merge(&self, other: &Self) -> Self { | |||
Self { | |||
fighter: min(self.fighter, other.fighter), | |||
bomber: min(self.bomber, other.bomber), | |||
transporter: min(self.transporter, other.transporter), | |||
} | |||
} | |||
} | |||
impl Index<usize> for Count { | |||
type Output = usize; | |||
fn index(&self, index: usize) -> &Self::Output { | |||
match index { | |||
0 => &self.fighter, | |||
1 => &self.bomber, | |||
2 => &self.transporter, | |||
x => panic!("Invalid ship count index: {}", x), | |||
} | |||
} | |||
} | |||
impl IndexMut<usize> for Count { | |||
fn index_mut(&mut self, index: usize) -> &mut Self::Output { | |||
match index { | |||
0 => &mut self.fighter, | |||
1 => &mut self.bomber, | |||
2 => &mut self.transporter, | |||
x => panic!("Invalid ship count index: {}", x), | |||
} | |||
} | |||
} | |||
impl Index<Type> for Count { | |||