Files
CosmicEngine/lib/All/JoltPhysics/TestFramework/Application/Application.cpp

434 lines
11 KiB
C++

// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Application/Application.h>
#include <UI/UIManager.h>
#include <Application/DebugUI.h>
#include <Utils/Log.h>
#include <Utils/CustomMemoryHook.h>
#include <Jolt/Core/Factory.h>
#include <Jolt/RegisterTypes.h>
#include <Renderer/DebugRendererImp.h>
#ifdef JPH_PLATFORM_WINDOWS
#include <crtdbg.h>
#include <Input/Win/KeyboardWin.h>
#include <Input/Win/MouseWin.h>
#include <Window/ApplicationWindowWin.h>
#elif defined(JPH_PLATFORM_LINUX)
#include <Input/Linux/KeyboardLinux.h>
#include <Input/Linux/MouseLinux.h>
#include <Window/ApplicationWindowLinux.h>
#elif defined(JPH_PLATFORM_MACOS)
#include <Input/MacOS/KeyboardMacOS.h>
#include <Input/MacOS/MouseMacOS.h>
#include <Window/ApplicationWindowMacOS.h>
#endif
JPH_GCC_SUPPRESS_WARNING("-Wswitch")
// Constructor
Application::Application(const char *inApplicationName, [[maybe_unused]] const String &inCommandLine) :
mDebugRenderer(nullptr),
mRenderer(nullptr),
mKeyboard(nullptr),
mMouse(nullptr),
mUI(nullptr),
mDebugUI(nullptr)
{
#if defined(JPH_PLATFORM_WINDOWS) && defined(_DEBUG)
// Enable leak detection
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
// Register trace implementation
Trace = TraceImpl;
#ifdef JPH_ENABLE_ASSERTS
// Register assert failed handler
AssertFailed = [](const char *inExpression, const char *inMessage, const char *inFile, uint inLine)
{
Trace("%s (%d): Assert Failed: %s", inFile, inLine, inMessage != nullptr? inMessage : inExpression);
return true;
};
#endif // JPH_ENABLE_ASSERTS
// Create factory
Factory::sInstance = new Factory;
// Register physics types with the factory
RegisterTypes();
{
// Disable allocation checking
DisableCustomMemoryHook dcmh;
// Create window
#ifdef JPH_PLATFORM_WINDOWS
mWindow = new ApplicationWindowWin;
#elif defined(JPH_PLATFORM_LINUX)
mWindow = new ApplicationWindowLinux;
#elif defined(JPH_PLATFORM_MACOS)
mWindow = new ApplicationWindowMacOS;
#else
#error No window defined
#endif
mWindow->Initialize(inApplicationName);
// Create renderer
mRenderer = Renderer::sCreate();
mRenderer->Initialize(mWindow);
// Create font
Font *font = new Font(mRenderer);
font->Create("Roboto-Regular", 24);
mFont = font;
// Init debug renderer
mDebugRenderer = new DebugRendererImp(mRenderer, mFont);
// Init keyboard
#ifdef JPH_PLATFORM_WINDOWS
mKeyboard = new KeyboardWin;
#elif defined(JPH_PLATFORM_LINUX)
mKeyboard = new KeyboardLinux;
#elif defined(JPH_PLATFORM_MACOS)
mKeyboard = new KeyboardMacOS;
#else
#error No keyboard defined
#endif
mKeyboard->Initialize(mWindow);
// Init mouse
#ifdef JPH_PLATFORM_WINDOWS
mMouse = new MouseWin;
#elif defined(JPH_PLATFORM_LINUX)
mMouse = new MouseLinux;
#elif defined(JPH_PLATFORM_MACOS)
mMouse = new MouseMacOS;
#else
#error No mouse defined
#endif
mMouse->Initialize(mWindow);
// Init UI
mUI = new UIManager(mRenderer);
mUI->SetVisible(false);
// Init debug UI
mDebugUI = new DebugUI(mUI, mFont);
}
// Get initial time
mLastUpdateTime = chrono::high_resolution_clock::now();
}
// Destructor
Application::~Application()
{
{
// Disable allocation checking
DisableCustomMemoryHook dcmh;
delete mDebugUI;
delete mUI;
delete mMouse;
delete mKeyboard;
delete mDebugRenderer;
mFont = nullptr;
delete mRenderer;
delete mWindow;
}
// Unregisters all types with the factory and cleans up the default material
UnregisterTypes();
delete Factory::sInstance;
Factory::sInstance = nullptr;
}
String Application::sCreateCommandLine(int inArgC, char **inArgV)
{
String command_line;
for (int i = 0; i < inArgC; ++i)
{
if (i > 0)
command_line += " ";
command_line += inArgV[i];
}
return command_line;
}
// Clear debug lines / triangles / texts that have been accumulated
void Application::ClearDebugRenderer()
{
JPH_PROFILE_FUNCTION();
static_cast<DebugRendererImp *>(mDebugRenderer)->Clear();
mDebugRendererCleared = true;
}
// Main loop
void Application::Run()
{
// Set initial camera position
ResetCamera();
// Enter the main loop
mWindow->MainLoop([this]() { return RenderFrame(); });
}
bool Application::RenderFrame()
{
// Get new input
mKeyboard->Poll();
mMouse->Poll();
// Handle keyboard input
for (EKey key = mKeyboard->GetFirstKey(); key != EKey::Invalid; key = mKeyboard->GetNextKey())
switch (key)
{
case EKey::P:
mIsPaused = !mIsPaused;
break;
case EKey::O:
mSingleStep = true;
break;
case EKey::T:
// Dump timing info to file
JPH_PROFILE_DUMP();
break;
case EKey::Escape:
mDebugUI->ToggleVisibility();
break;
}
// Calculate delta time
chrono::high_resolution_clock::time_point time = chrono::high_resolution_clock::now();
chrono::microseconds delta = chrono::duration_cast<chrono::microseconds>(time - mLastUpdateTime);
mLastUpdateTime = time;
float clock_delta_time = 1.0e-6f * delta.count();
float world_delta_time = 0.0f;
if (mRequestedDeltaTime <= 0.0f)
{
// If no fixed frequency update is requested, update with variable time step
world_delta_time = !mIsPaused || mSingleStep? clock_delta_time : 0.0f;
mResidualDeltaTime = 0.0f;
}
else
{
// Else use fixed time steps
if (mSingleStep)
{
// Single step
world_delta_time = mRequestedDeltaTime;
}
else if (!mIsPaused)
{
// Calculate how much time has passed since the last render
world_delta_time = clock_delta_time + mResidualDeltaTime;
if (world_delta_time < mRequestedDeltaTime)
{
// Too soon, set the residual time and don't update
mResidualDeltaTime = world_delta_time;
world_delta_time = 0.0f;
}
else
{
// Update and clamp the residual time to a full update to avoid spiral of death
mResidualDeltaTime = min(mRequestedDeltaTime, world_delta_time - mRequestedDeltaTime);
world_delta_time = mRequestedDeltaTime;
}
}
}
mSingleStep = false;
// Clear debug lines if we're going to step
if (world_delta_time > 0.0f)
ClearDebugRenderer();
{
JPH_PROFILE("UpdateFrame");
if (!UpdateFrame(world_delta_time))
return false;
}
// Draw coordinate axis
if (mDebugRendererCleared)
mDebugRenderer->DrawCoordinateSystem(RMat44::sIdentity());
// For next frame: mark that we haven't cleared debug stuff
mDebugRendererCleared = false;
// Update the camera position
if (!mUI->IsVisible())
UpdateCamera(clock_delta_time);
// Start rendering
if (!mRenderer->BeginFrame(mWorldCamera, GetWorldScale()))
return true;
// Draw from light
static_cast<DebugRendererImp *>(mDebugRenderer)->DrawShadowPass();
// Start drawing normally
mRenderer->EndShadowPass();
// Draw debug information
static_cast<DebugRendererImp *>(mDebugRenderer)->Draw();
// Draw the frame rate counter
DrawFPS(clock_delta_time);
if (mUI->IsVisible())
{
// Send mouse input to UI
bool left_pressed = mMouse->IsLeftPressed();
if (left_pressed && !mLeftMousePressed)
mUI->MouseDown(mMouse->GetX(), mMouse->GetY());
else if (!left_pressed && mLeftMousePressed)
mUI->MouseUp(mMouse->GetX(), mMouse->GetY());
mLeftMousePressed = left_pressed;
mUI->MouseMove(mMouse->GetX(), mMouse->GetY());
{
// Disable allocation checking
DisableCustomMemoryHook dcmh;
// Update and draw the menu
mUI->Update(clock_delta_time);
mUI->Draw();
}
}
else
{
// Menu not visible, cancel any mouse operations
mUI->MouseCancel();
}
// Show the frame
mRenderer->EndFrame();
// Notify of next frame
JPH_PROFILE_NEXTFRAME();
return true;
}
void Application::GetCameraLocalHeadingAndPitch(float &outHeading, float &outPitch)
{
outHeading = ATan2(mLocalCamera.mForward.GetZ(), mLocalCamera.mForward.GetX());
outPitch = ATan2(mLocalCamera.mForward.GetY(), Vec3(mLocalCamera.mForward.GetX(), 0, mLocalCamera.mForward.GetZ()).Length());
}
void Application::ConvertCameraLocalToWorld(float inCameraHeading, float inCameraPitch)
{
// Convert local to world space using the camera pivot
RMat44 pivot = GetCameraPivot(inCameraHeading, inCameraPitch);
mWorldCamera = mLocalCamera;
mWorldCamera.mPos = pivot * mLocalCamera.mPos;
mWorldCamera.mForward = pivot.Multiply3x3(mLocalCamera.mForward);
mWorldCamera.mUp = pivot.Multiply3x3(mLocalCamera.mUp);
}
void Application::ResetCamera()
{
// Get local space camera state
mLocalCamera = CameraState();
GetInitialCamera(mLocalCamera);
// Convert to world space
float heading, pitch;
GetCameraLocalHeadingAndPitch(heading, pitch);
ConvertCameraLocalToWorld(heading, pitch);
}
// Update camera position
void Application::UpdateCamera(float inDeltaTime)
{
JPH_PROFILE_FUNCTION();
// Determine speed
float speed = 20.0f * GetWorldScale() * inDeltaTime;
bool shift = mKeyboard->IsKeyPressed(EKey::LShift) || mKeyboard->IsKeyPressed(EKey::RShift);
bool control = mKeyboard->IsKeyPressed(EKey::LControl) || mKeyboard->IsKeyPressed(EKey::RControl);
bool alt = mKeyboard->IsKeyPressed(EKey::LAlt) || mKeyboard->IsKeyPressed(EKey::RAlt);
if (shift) speed *= 10.0f;
else if (control) speed /= 25.0f;
else if (alt) speed = 0.0f;
// Position
Vec3 right = mLocalCamera.mForward.Cross(mLocalCamera.mUp);
if (mKeyboard->IsKeyPressed(EKey::A)) mLocalCamera.mPos -= speed * right;
if (mKeyboard->IsKeyPressed(EKey::D)) mLocalCamera.mPos += speed * right;
if (mKeyboard->IsKeyPressed(EKey::W)) mLocalCamera.mPos += speed * mLocalCamera.mForward;
if (mKeyboard->IsKeyPressed(EKey::S)) mLocalCamera.mPos -= speed * mLocalCamera.mForward;
// Forward
float heading, pitch;
GetCameraLocalHeadingAndPitch(heading, pitch);
heading += DegreesToRadians(mMouse->GetDX() * 0.5f);
pitch = Clamp(pitch - DegreesToRadians(mMouse->GetDY() * 0.5f), -0.49f * JPH_PI, 0.49f * JPH_PI);
mLocalCamera.mForward = Vec3(Cos(pitch) * Cos(heading), Sin(pitch), Cos(pitch) * Sin(heading));
// Convert to world space
ConvertCameraLocalToWorld(heading, pitch);
}
void Application::DrawFPS(float inDeltaTime)
{
JPH_PROFILE_FUNCTION();
// Don't divide by zero
if (inDeltaTime <= 0.0f)
return;
// Switch tho ortho mode
mRenderer->SetOrthoMode();
// Update stats
mTotalDeltaTime += inDeltaTime;
mNumFrames++;
if (mNumFrames > 10)
{
mFPS = mNumFrames / mTotalDeltaTime;
mNumFrames = 0;
mTotalDeltaTime = 0.0f;
}
// Create string
String fps = StringFormat("%.1f", (double)mFPS);
// Get size of text on screen
Float2 text_size = mFont->MeasureText(fps);
int text_w = int(text_size.x * mFont->GetCharHeight());
int text_h = int(text_size.y * mFont->GetCharHeight());
// Draw FPS counter
int x = (mWindow->GetWindowWidth() - text_w) / 2 - 20;
int y = 10;
mUI->DrawQuad(x - 5, y - 3, text_w + 10, text_h + 6, UITexturedQuad(), Color(0, 0, 0, 128));
mUI->DrawText(x, y, fps, mFont);
// Draw status string
if (!mStatusString.empty())
mUI->DrawText(5, 5, mStatusString, mFont);
// Draw paused string if the app is paused
if (mIsPaused)
{
string_view paused_str = "P: Unpause, ESC: Menu";
Float2 pause_size = mFont->MeasureText(paused_str);
mUI->DrawText(mWindow->GetWindowWidth() - 5 - int(pause_size.x * mFont->GetCharHeight()), 5, paused_str, mFont);
}
// Restore state
mRenderer->SetProjectionMode();
}