| @@ -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}; | use std::ops::{Add, Div, Mul, Neg, Sub}; | ||||
| #[cfg(feature = "serde")] | |||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||
| pub use super::numeric::{Float, Numeric}; | pub use super::numeric::{Float, Numeric}; | ||||
| @@ -57,6 +60,14 @@ where | |||||
| pub fn cos(self) -> T { | pub fn cos(self) -> T { | ||||
| T::cos(self.into_rad().into_inner()) | 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> | 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 | 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 { | pub fn invert<T: Float>(value: T) -> T { | ||||
| T::one() - value | T::one() - value | ||||
| } | } | ||||
| @@ -29,6 +29,9 @@ pub trait Float: Numeric { | |||||
| fn acos(self) -> Self; | fn acos(self) -> Self; | ||||
| fn atan(self) -> Self; | fn atan(self) -> Self; | ||||
| fn atan2(a: Self, b: 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 { | impl Numeric for gl::GLfloat { | ||||
| @@ -108,6 +111,21 @@ impl Float for gl::GLfloat { | |||||
| fn atan2(a: Self, b: Self) -> Self { | fn atan2(a: Self, b: Self) -> Self { | ||||
| f32::atan2(a, b) | 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 { | impl Numeric for gl::GLdouble { | ||||
| @@ -187,6 +205,21 @@ impl Float for gl::GLdouble { | |||||
| fn atan2(a: Self, b: Self) -> Self { | fn atan2(a: Self, b: Self) -> Self { | ||||
| f64::atan2(a, b) | 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 { | impl Numeric for gl::GLint { | ||||
| @@ -253,6 +253,25 @@ where | |||||
| pub fn scalar(&self, other: &Self) -> T { | pub fn scalar(&self, other: &Self) -> T { | ||||
| self.x * other.x + self.y * other.y | 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> | 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 */ | /* Vector3 */ | ||||
| impl<T> Vector3<T> | impl<T> Vector3<T> | ||||
| @@ -469,12 +497,12 @@ where | |||||
| #[inline] | #[inline] | ||||
| pub fn length_sqr(self) -> T { | pub fn length_sqr(self) -> T { | ||||
| self.xyz().length_sqr() | |||||
| self.into_vec3().length_sqr() | |||||
| } | } | ||||
| #[inline] | #[inline] | ||||
| pub fn length(self) -> T { | pub fn length(self) -> T { | ||||
| self.xyz().length() | |||||
| self.into_vec3().length() | |||||
| } | } | ||||
| #[inline] | #[inline] | ||||
| @@ -501,16 +529,41 @@ where | |||||
| #[inline] | #[inline] | ||||
| pub fn scalar(&self, other: &Self) -> T { | pub fn scalar(&self, other: &Self) -> T { | ||||
| self.xyz().scalar(&other.xyz()) | |||||
| self.into_vec3().scalar(&other.into_vec3()) | |||||
| } | } | ||||
| #[inline] | #[inline] | ||||
| pub fn cross(&self, other: &Self) -> Self { | 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] | #[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) } { | if unsafe { Numeric::is_zero(&self.w) } { | ||||
| Vector3 { | Vector3 { | ||||
| x: Numeric::zero(), | x: Numeric::zero(), | ||||
| @@ -533,7 +586,7 @@ where | |||||
| { | { | ||||
| #[inline] | #[inline] | ||||
| pub fn angle(&self, other: &Self) -> Angle<T> { | 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] | #[inline] | ||||
| fn add(self, other: Self) -> Self::Output { | 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] | #[inline] | ||||
| fn sub(self, other: Self) -> Self::Output { | 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> { | impl<T> From<(Vector3<T>, T)> for Vector4<T> { | ||||
| fn from((vec3, w): (Vector3<T>, T)) -> Self { | fn from((vec3, w): (Vector3<T>, T)) -> Self { | ||||
| 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 | #version 450 core | ||||
| #pragma include ./shared.glsl | #pragma include ./shared.glsl | ||||
| #pragma include ../../misc/camera.glsl | |||||
| #pragma include ../misc/camera.glsl | |||||
| in vec3 inPosition; | in vec3 inPosition; | ||||
| uniform mat4 uModel; | |||||
| layout (location = 0) uniform mat4 uModel; | |||||
| out FragmentData fragmentData; | out FragmentData fragmentData; | ||||
| @@ -9,10 +9,11 @@ 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; | ||||
| 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); | 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(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(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)?) | .with_thread_local(DebugSummary::new(&text_manager)?) | ||||
| .build(); | .build(); | ||||
| dispatcher.setup(world); | dispatcher.setup(world); | ||||
| @@ -650,7 +650,7 @@ impl Text { | |||||
| gl::draw_arrays_instanced(gl::TRIANGLE_STRIP, 0, 4, inner.vertex_count as _); | 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 | where | ||||
| S: Display, | S: Display, | ||||
| { | { | ||||
| @@ -668,13 +668,17 @@ impl Text { | |||||
| drop(inner); | 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(); | let mut inner = self.inner.borrow_mut(); | ||||
| inner.color = color; | inner.color = color; | ||||
| self | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,17 +1,17 @@ | |||||
| use std::mem::take; | |||||
| use std::time::Instant; | use std::time::Instant; | ||||
| use glc::{ | use glc::{ | ||||
| animation::{animate, linear}, | |||||
| math::{clamp, linear_step}, | math::{clamp, linear_step}, | ||||
| matrix::Matrix4f, | matrix::Matrix4f, | ||||
| misc::BindGuard, | misc::BindGuard, | ||||
| shader::{Program, Type, Uniform}, | shader::{Program, Type, Uniform}, | ||||
| vector::{Vector2f, Vector4f}, | vector::{Vector2f, Vector4f}, | ||||
| }; | }; | ||||
| use log::debug; | |||||
| use shrev::{EventChannel, ReaderId}; | use shrev::{EventChannel, ReaderId}; | ||||
| use space_crush_common::{ | use space_crush_common::{ | ||||
| components::{Fleet, Position, ShipCount}, | components::{Fleet, Position, ShipCount}, | ||||
| constants::VECTOR_2F_POS_X, | |||||
| misc::{LogResult, WorldHelper as _}, | misc::{LogResult, WorldHelper as _}, | ||||
| resources::Global, | resources::Global, | ||||
| }; | }; | ||||
| @@ -21,7 +21,8 @@ use crate::{ | |||||
| components::FleetInfo, | components::FleetInfo, | ||||
| constants::{ | constants::{ | ||||
| FLEET_SELECT_ANIMATION_TIME, FLEET_SELECT_DETAIL_TIMEOUT, FLEET_SELECT_OFFSET, | 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::{ | misc::{ | ||||
| HorizontalAlign, MouseEvent, Text, TextCache, TextManager, VerticalAlign, WorldHelper as _, | HorizontalAlign, MouseEvent, Text, TextCache, TextManager, VerticalAlign, WorldHelper as _, | ||||
| @@ -31,32 +32,46 @@ use crate::{ | |||||
| }; | }; | ||||
| pub struct SelectFleet { | 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, | cache: TextCache, | ||||
| text_total: Text, | text_total: Text, | ||||
| select_mode: SelectMode, | |||||
| text_fighter: Text, | |||||
| text_bomber: Text, | |||||
| text_transporter: Text, | |||||
| mouse_event_id: ReaderId<MouseEvent>, | 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)] | #[derive(Copy, Clone, Debug)] | ||||
| enum SelectMode { | enum SelectMode { | ||||
| None, | 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)] | #[derive(SystemData)] | ||||
| pub struct SelectFleetData<'a> { | pub struct SelectFleetData<'a> { | ||||
| player_state: WriteExpect<'a, PlayerState>, | player_state: WriteExpect<'a, PlayerState>, | ||||
| camera: WriteExpect<'a, Camera>, | |||||
| camera: ReadExpect<'a, Camera>, | |||||
| entities: Entities<'a>, | entities: Entities<'a>, | ||||
| mouse_events: ReadExpect<'a, EventChannel<MouseEvent>>, | mouse_events: ReadExpect<'a, EventChannel<MouseEvent>>, | ||||
| @@ -70,222 +85,439 @@ pub struct SelectFleetData<'a> { | |||||
| fleets: ReadStorage<'a, Fleet>, | 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 { | impl SelectFleet { | ||||
| pub fn new(world: &World, text_manager: &TextManager) -> Result<Self, Error> { | 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, | 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 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 select_mode = SelectMode::None; | ||||
| let mouse_event_id = world.register_event_reader::<MouseEvent>()?; | let mouse_event_id = world.register_event_reader::<MouseEvent>()?; | ||||
| Ok(Self { | Ok(Self { | ||||
| program, | |||||
| location_model, | |||||
| location_rings, | |||||
| location_progress, | |||||
| location_marker, | |||||
| location_size, | |||||
| location_value, | |||||
| program_simple, | |||||
| program_detail, | |||||
| cache, | cache, | ||||
| text_total, | text_total, | ||||
| select_mode, | |||||
| text_fighter, | |||||
| text_bomber, | |||||
| text_transporter, | |||||
| mouse_event_id, | 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<'_>) { | fn handle_events(&mut self, d: &mut SelectFleetData<'_>) { | ||||
| let events = d.mouse_events.read(&mut self.mouse_event_id); | let events = d.mouse_events.read(&mut self.mouse_event_id); | ||||
| for event in events { | for event in events { | ||||
| match event { | match event { | ||||
| MouseEvent::ButtonDown(button) if button == &d.config.input.fleet_select_button => { | 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(); | let selection = d.player_state.selection.take(); | ||||
| for (id, position, fleet) in (&d.entities, &d.positions, &d.fleets).join() { | for (id, position, fleet) in (&d.entities, &d.positions, &d.fleets).join() { | ||||
| let r = fleet.orbit_max * fleet.orbit_max; | let r = fleet.orbit_max * fleet.orbit_max; | ||||
| if (position.pos - pos).length_sqr() <= r { | if (position.pos - pos).length_sqr() <= r { | ||||
| d.player_state.selection = match selection { | 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; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| MouseEvent::ButtonUp(button) if button == &d.config.input.fleet_select_button => { | MouseEvent::ButtonUp(button) if button == &d.config.input.fleet_select_button => { | ||||
| self.select_mode = match self.select_mode { | 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, | _ => SelectMode::None, | ||||
| } | } | ||||
| } | } | ||||
| MouseEvent::Move(_, _) if self.select_mode.is_init() => { | 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"); | .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"); | .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"); | .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"); | .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"); | .error("Error while updating size"); | ||||
| /* render selection menu */ | |||||
| d.geometry.render_quad(); | d.geometry.render_quad(); | ||||
| drop(guard); | 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 | 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 { | } else { | ||||
| self.text_total | 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>; | type SystemData = SelectFleetData<'a>; | ||||
| fn run(&mut self, mut data: Self::SystemData) { | fn run(&mut self, mut data: Self::SystemData) { | ||||
| self.update_camera(&data); | |||||
| self.handle_events(&mut 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 { | pub struct Camera { | ||||
| buffer: ArrayBuffer, | buffer: ArrayBuffer, | ||||
| data: Data, | data: Data, | ||||
| view_inverted: Option<Matrix4f>, | |||||
| update_counter: usize, | |||||
| } | } | ||||
| #[repr(C, packed)] | #[repr(C, packed)] | ||||
| @@ -34,7 +34,7 @@ impl Camera { | |||||
| Ok(Self { | Ok(Self { | ||||
| buffer, | buffer, | ||||
| data, | data, | ||||
| view_inverted: None, | |||||
| update_counter: 0, | |||||
| }) | }) | ||||
| } | } | ||||
| @@ -46,12 +46,8 @@ impl Camera { | |||||
| &self.data.view | &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 { | pub fn size(&self) -> &Vector2f { | ||||
| @@ -66,29 +62,26 @@ impl Camera { | |||||
| data[0].projection = self.data.projection; | data[0].projection = self.data.projection; | ||||
| data[0].size = self.data.size; | data[0].size = self.data.size; | ||||
| self.update_counter = self.update_counter.wrapping_add(1); | |||||
| Ok(()) | 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> | pub fn update_with<F>(&mut self, f: F) -> Result<(), Error> | ||||
| where | where | ||||
| F: FnOnce(&Matrix4f) -> Matrix4f, | F: FnOnce(&Matrix4f) -> Matrix4f, | ||||
| { | { | ||||
| self.view_inverted = None; | |||||
| self.data.view = f(&self.data.view); | self.data.view = f(&self.data.view); | ||||
| let mut data = self.buffer.map_mut::<Data>(true)?; | let mut data = self.buffer.map_mut::<Data>(true)?; | ||||
| data[0].view = self.data.view; | data[0].view = self.data.view; | ||||
| self.update_counter = self.update_counter.wrapping_add(1); | |||||
| Ok(()) | Ok(()) | ||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| use std::cmp::min; | |||||
| use std::ops::{Index, IndexMut, Mul}; | use std::ops::{Index, IndexMut, Mul}; | ||||
| use glc::{matrix::Angle, vector::Vector2f}; | use glc::{matrix::Angle, vector::Vector2f}; | ||||
| @@ -44,6 +45,38 @@ impl Count { | |||||
| .saturating_add(self.bomber) | .saturating_add(self.bomber) | ||||
| .saturating_add(self.transporter) | .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 { | impl Index<Type> for Count { | ||||