From b2c4124d790342578fcafe0ce9a7c1cfc8245743 Mon Sep 17 00:00:00 2001
From: Evan Pratten <ewpratten@gmail.com>
Date: Thu, 30 Sep 2021 12:18:58 -0400
Subject: [PATCH] fix interp fns and make the loading screen fade

---
 .vscode/tasks.json                   | 12 ++++++
 game/Cargo.toml                      |  1 +
 game/src/lib.rs                      |  7 ++--
 game/src/scenes/loading_screen.rs    | 57 +++++++++++++++++++++++-----
 game/src/utilities/math.rs           | 51 ++++++++++++++++++++++++-
 game/src/utilities/non_ref_raylib.rs | 17 +++++++--
 6 files changed, 127 insertions(+), 18 deletions(-)

diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 737fdd9..8e6e932 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -33,6 +33,18 @@
 			"env": {
 				"RUST_LOG": "debug"
 			}
+		},
+		{
+			"type": "cargo",
+			"command": "run",
+			"problemMatcher": [
+				"$rustc"
+			],
+			"group": "build",
+			"label": "Rust: Run Game - TRACE",
+			"env": {
+				"RUST_LOG": "trace"
+			}
 		}
 	]
 }
diff --git a/game/Cargo.toml b/game/Cargo.toml
index dfb38f6..a5d48a2 100644
--- a/game/Cargo.toml
+++ b/game/Cargo.toml
@@ -23,6 +23,7 @@ num-traits = "0.2"
 sentry = "0.23"
 image = "0.23"
 tempfile = "3.2"
+approx = "0.5"
 
 [dev-dependencies]
 puffin_viewer = "0.6"
diff --git a/game/src/lib.rs b/game/src/lib.rs
index 5576cd1..00fca29 100644
--- a/game/src/lib.rs
+++ b/game/src/lib.rs
@@ -89,6 +89,8 @@ use crate::{
 extern crate thiserror;
 #[macro_use]
 extern crate serde;
+#[macro_use]
+extern crate approx;
 
 mod context;
 mod discord_rpc;
@@ -191,10 +193,7 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
         }
 
         // Fetch the screen size once to work with in render code
-        let screen_size = Vector2::new(
-            context.renderer.borrow().get_screen_width() as f32,
-            context.renderer.borrow().get_screen_height() as f32,
-        );
+        let screen_size = context.renderer.borrow().get_screen_size();
 
         // Update the pixel shader to correctly handle the screen size
         pixel_shader.set_variable("viewport", screen_size)?;
diff --git a/game/src/scenes/loading_screen.rs b/game/src/scenes/loading_screen.rs
index e585f1f..7860a0e 100644
--- a/game/src/scenes/loading_screen.rs
+++ b/game/src/scenes/loading_screen.rs
@@ -1,8 +1,18 @@
+use std::ops::{Div, Sub};
+
 use chrono::{DateTime, Utc};
 use dirty_fsm::{Action, ActionFlag};
-use raylib::{RaylibThread, texture::Texture2D};
+use raylib::prelude::*;
 
-use crate::{context::GameContext, utilities::{datastore::{ResourceLoadError, load_texture_from_internal_data}, non_ref_raylib::HackedRaylibHandle, render_layer::ScreenSpaceRender}};
+use crate::{
+    context::GameContext,
+    utilities::{
+        datastore::{load_texture_from_internal_data, ResourceLoadError},
+        math::interpolate_exp,
+        non_ref_raylib::HackedRaylibHandle,
+        render_layer::ScreenSpaceRender,
+    },
+};
 
 use super::{Scenes, ScreenError};
 use tracing::{debug, info, trace};
@@ -13,19 +23,24 @@ const LOADING_SCREEN_DURATION_SECONDS: u8 = 3;
 #[derive(Debug)]
 pub struct LoadingScreen {
     start_timestamp: Option<DateTime<Utc>>,
-    game_logo_texture: Texture2D
+    game_logo_texture: Texture2D,
+    game_logo_size: Vector2,
 }
 
 impl LoadingScreen {
     /// Construct a new `LoadingScreen`
-    pub fn new(raylib_handle: &mut HackedRaylibHandle, thread: &RaylibThread) -> Result<Self, ResourceLoadError> {
-
+    pub fn new(
+        raylib_handle: &mut HackedRaylibHandle,
+        thread: &RaylibThread,
+    ) -> Result<Self, ResourceLoadError> {
         // Load the game logo asset
-        let game_logo = load_texture_from_internal_data(raylib_handle, thread, "logos/game-logo.png")?;
+        let game_logo =
+            load_texture_from_internal_data(raylib_handle, thread, "logos/game-logo.png")?;
 
         Ok(Self {
             start_timestamp: None,
-            game_logo_texture: game_logo
+            game_logo_size: Vector2::new(game_logo.width as f32, game_logo.height as f32),
+            game_logo_texture: game_logo,
         })
     }
 }
@@ -80,10 +95,34 @@ impl Action<Scenes, ScreenError, GameContext> for LoadingScreen {
 impl ScreenSpaceRender for LoadingScreen {
     fn render_screen_space(
         &self,
-        _raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle,
+        raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle,
     ) {
-
         // Calculate the loading screen fade in/out value
         // This makes the loading screen fade in/out over the duration of the loading screen
+        let cur_time = Utc::now();
+        let time_since_start =
+            cur_time.signed_duration_since(self.start_timestamp.unwrap_or(cur_time));
+        let fade_percentage = interpolate_exp(
+            time_since_start.num_milliseconds() as f32,
+            0.0..(LOADING_SCREEN_DURATION_SECONDS as f32 * 1000.0),
+            0.0..1.0,
+            8.0,
+        );
+        trace!("Loading screen fade at {:.2}%", fade_percentage);
+
+        // Render the background
+        raylib.clear_background(Color::WHITE);
+
+        // Calculate the logo position
+        let screen_size = raylib.get_screen_size();
+
+        // Render the game logo
+        raylib.draw_texture_ex(
+            &self.game_logo_texture,
+            screen_size.div(2.0).sub(self.game_logo_size.div(2.0)),
+            0.0,
+            1.0,
+            Color::WHITE.fade(fade_percentage),
+        );
     }
 }
diff --git a/game/src/utilities/math.rs b/game/src/utilities/math.rs
index 0eb61cf..af975be 100644
--- a/game/src/utilities/math.rs
+++ b/game/src/utilities/math.rs
@@ -1,6 +1,6 @@
 use std::ops::Range;
 
-use num_traits::{Float};
+use num_traits::Float;
 use raylib::math::Vector2;
 
 /// Rotate a vector by an angle
@@ -27,7 +27,9 @@ where
     let normalized_value = (value - input_range.start) / (input_range.end - input_range.start);
 
     // Map the value along an exponential curve as defined by the exponent
-    let mapped_value = ((normalized_value - T::one()).powf(exp) * -T::one()) + T::one();
+    let mapped_value = (-T::one())
+        .mul((normalized_value.mul(T::one().add(T::one())) - T::one()).powf(exp))
+        .add(T::one());
 
     // Return the value mapped to the output range
     (mapped_value * (output_range.end - output_range.start)) + output_range.start
@@ -45,3 +47,48 @@ where
     // Interpolate the value
     interpolate_exp_unchecked(clamped_value, input_range, output_range, exp)
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_rotate_vector() {
+        let vector = Vector2 { x: 1.0, y: 0.0 };
+        let angle_rad = 90.0.to_radians();
+        let expected_vector = Vector2 { x: 0.0, y: 1.0 };
+        let actual_vector = rotate_vector(vector, angle_rad);
+        assert!(relative_eq!(
+            actual_vector.x,
+            expected_vector.x,
+            epsilon = f32::EPSILON
+        ));
+        assert!(relative_eq!(
+            actual_vector.y,
+            expected_vector.y,
+            epsilon = f32::EPSILON
+        ));
+    }
+
+    #[test]
+    fn test_interpolate_exp_head() {
+        let input_range = 0.0..1.0;
+        let output_range = 0.0..1.0;
+        let exp = 8.0;
+        let value = 0.043;
+        let expected_value = 0.513;
+        let actual_value = interpolate_exp(value, input_range, output_range, exp);
+        assert!(relative_eq!(actual_value, expected_value, epsilon = 0.001));
+    }
+
+    #[test]
+    fn test_interpolate_exp_tail() {
+        let input_range = 0.0..1.0;
+        let output_range = 0.0..1.0;
+        let exp = 8.0;
+        let value = 0.957;
+        let expected_value = 0.513;
+        let actual_value = interpolate_exp(value, input_range, output_range, exp);
+        assert!(relative_eq!(actual_value, expected_value, epsilon = 0.001));
+    }
+}
diff --git a/game/src/utilities/non_ref_raylib.rs b/game/src/utilities/non_ref_raylib.rs
index 9aea9b4..7dbd3f9 100644
--- a/game/src/utilities/non_ref_raylib.rs
+++ b/game/src/utilities/non_ref_raylib.rs
@@ -1,11 +1,22 @@
-use std::{ops::{Deref, DerefMut}};
-
-use raylib::{prelude::RaylibDraw, RaylibHandle};
+use std::ops::{Deref, DerefMut};
 
+use raylib::{math::Vector2, prelude::RaylibDraw, RaylibHandle};
 
 #[derive(Debug)]
 pub struct HackedRaylibHandle(RaylibHandle);
 
+impl HackedRaylibHandle {
+
+    /// Get the screen size as a vector
+    #[inline]
+    pub fn get_screen_size(&self) -> Vector2 {
+        Vector2::new(
+            self.get_screen_width() as f32,
+            self.get_screen_height() as f32,
+        )
+    }
+}
+
 impl RaylibDraw for HackedRaylibHandle {}
 
 impl From<RaylibHandle> for HackedRaylibHandle {