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