Browse Source

Implemented detailed fleet selection menu

master
Bergmann89 4 years ago
parent
commit
57d44a5030
16 changed files with 1008 additions and 320 deletions
  1. +92
    -0
      glc/src/angle.rs
  2. +4
    -0
      glc/src/animation.rs
  3. +33
    -0
      glc/src/numeric.rs
  4. +89
    -8
      glc/src/vector.rs
  5. +113
    -0
      space-crush-app/resources/shader/fleet_select/detail_frag.glsl
  6. +0
    -0
      space-crush-app/resources/shader/fleet_select/shared.glsl
  7. +145
    -0
      space-crush-app/resources/shader/fleet_select/shared_frag.glsl
  8. +0
    -96
      space-crush-app/resources/shader/fleet_select/simple/frag.glsl
  9. +44
    -0
      space-crush-app/resources/shader/fleet_select/simple_frag.glsl
  10. +2
    -2
      space-crush-app/resources/shader/fleet_select/vert.glsl
  11. +6
    -5
      space-crush-app/src/constants.rs
  12. +2
    -2
      space-crush-app/src/lib.rs
  13. +7
    -3
      space-crush-app/src/misc/text.rs
  14. +428
    -187
      space-crush-app/src/render/select_fleet.rs
  15. +10
    -17
      space-crush-app/src/resources/camera.rs
  16. +33
    -0
      space-crush-common/src/components/ship.rs

+ 92
- 0
glc/src/angle.rs View File

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

+ 4
- 0
glc/src/animation.rs View File

@@ -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
}


+ 33
- 0
glc/src/numeric.rs View File

@@ -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 {


+ 89
- 8
glc/src/vector.rs View File

@@ -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 {


+ 113
- 0
space-crush-app/resources/shader/fleet_select/detail_frag.glsl View File

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

space-crush-app/resources/shader/fleet_select/simple/shared.glsl → space-crush-app/resources/shader/fleet_select/shared.glsl View File


+ 145
- 0
space-crush-app/resources/shader/fleet_select/shared_frag.glsl View File

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

+ 0
- 96
space-crush-app/resources/shader/fleet_select/simple/frag.glsl View File

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

+ 44
- 0
space-crush-app/resources/shader/fleet_select/simple_frag.glsl View File

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

space-crush-app/resources/shader/fleet_select/simple/vert.glsl → space-crush-app/resources/shader/fleet_select/vert.glsl View File

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


+ 6
- 5
space-crush-app/src/constants.rs View File

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

+ 2
- 2
space-crush-app/src/lib.rs View File

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


+ 7
- 3
space-crush-app/src/misc/text.rs View File

@@ -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
}
}



+ 428
- 187
space-crush-app/src/render/select_fleet.rs View File

@@ -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
- 17
space-crush-app/src/resources/camera.rs View File

@@ -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(())
}



+ 33
- 0
space-crush-common/src/components/ship.rs View File

@@ -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 {


Loading…
Cancel
Save