Files
Juliet/JulietApp/main.cpp

692 lines
21 KiB
C++

#include "main.h"
#ifdef global
#undef global
#endif
#include <algorithm>
#include <ios>
#include <iosfwd>
#define global static
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#endif
#include <Core/Application/ApplicationManager.h>
#include <Core/Common/EnumUtils.h>
#include <Core/Common/String.h>
#include <Core/HAL/Display/Display.h>
#include <Core/HAL/Event/SystemEvent.h>
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/JulietInit.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Main.h>
#include <Core/Math/Matrix.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Memory/Utils.h>
#include <cstdlib>
#include <Engine/Debug/MemoryDebugger.h>
#include <Graphics/Camera.h>
#include <Graphics/DebugDisplay.h>
#include <Graphics/Graphics.h>
#include <Graphics/GraphicsConfig.h>
#include <Graphics/GraphicsPipeline.h>
#include <Graphics/Mesh.h>
#include <Graphics/MeshRenderer.h>
#include <Graphics/RenderPass.h>
#include <Graphics/SkyboxRenderer.h>
#include <Graphics/VertexData.h>
#ifdef JULIET_ENABLE_IMGUI
#include <imgui.h>
#endif
static bool ShowMemoryDebugger = false;
static bool animateCubes = true;
static bool animateLights = true;
static bool animateCamera = true;
static bool freeCameraMode = false;
static float camYaw = 0.0f;
static float camPitch = 0.0f;
static Juliet::Vector3 camPos = { 25.0f, 0.0f, 12.5f };
static float animateCubesTime = 0.0f;
static float animateLightsTime = 0.0f;
static float animateCameraTime = 0.0f;
static float redLightRadius = 10.0f;
static float redLightIntensity = 5.0f;
static float redLightColor[3] = { 1.0f, 0.2f, 0.2f };
static bool redLightFollowsCamera = false;
static bool enableGlobalLight = true;
static float globalLightDir[3] = { 0.5f, -1.0f, -0.5f };
static float globalLightColor[3] = { 1.0f, 0.95f, 0.8f };
static float globalAmbientIntensity = 0.2f;
static float blueLightRadius = 15.0f;
static float blueLightIntensity = 8.0f;
static float blueLightColor[3] = { 0.2f, 0.2f, 1.0f };
// TODO : Replace with message box from framework + call main and not winmain + subsystem
// TODO : Think how to do the draw pipeline.
// Ex: Expose a Draw method ?
// Store a graphics context ?
// For now. Put everything in Update down below.
// Should split update from draw, update should have a != timestep than graphics (60fps or more)
// TODO : Remove main.h. Useless
// May be remove that Application class, useless too.
using namespace Juliet;
namespace
{
using GameInit_t = void (*)(GameInitParams*);
using GameShutdown_t = void (*)(void);
using GameUpdate_t = void (*)(float deltaTime);
struct GameFunctionTable
{
GameInit_t Init = nullptr;
GameShutdown_t Shutdown = nullptr;
GameUpdate_t Update = nullptr;
} Game;
const char* GameFunctionTable[] = { "GameInit", "GameShutdown", "GameUpdate" };
LightID RedLightID = 0;
LightID BlueLightID = 0;
} // namespace
void JulietApplication::Init(NonNullPtr<Arena>)
{
Log(LogLevel::Message, LogCategory::Tool, "Initializing Juliet Application...");
Log(LogLevel::Message, LogCategory::Tool, "%s", CStr(GetBasePath()));
GraphicsConfig config;
#if JULIET_DEBUG
config.EnableDebug = true;
#endif
GraphicsDevice = CreateGraphicsDevice(config);
MainWindow = CreatePlatformWindow("Juliet Editor", 1280, 720);
Running = MainWindow != nullptr && GraphicsDevice != nullptr;
if (Running)
{
AttachToWindow(GraphicsDevice, MainWindow);
{
// Create Depth Buffer
TextureCreateInfo depthCI = {};
depthCI.Type = TextureType::Texture_2D;
depthCI.Width = 1280;
depthCI.Height = 720;
depthCI.Format = TextureFormat::D32_FLOAT;
depthCI.Flags = TextureUsageFlag::DepthStencilTarget;
depthCI.LayerCount = 1;
depthCI.MipLevelCount = 1;
depthCI.SampleCount = TextureSampleCount::One;
DepthBuffer = CreateTexture(GraphicsDevice, depthCI);
if (DepthBuffer == nullptr)
{
LogError(LogCategory::Game, "Failed to create depth buffer!");
Running = false;
}
constexpr int kGridSize = 10;
constexpr float kSpacing = 2.5f;
constexpr float kOffset = (kGridSize - 1) * kSpacing * 0.5f;
for (int row = 0; row < kGridSize; ++row)
{
for (int col = 0; col < kGridSize; ++col)
{
MeshID cube = AddCube();
float x = static_cast<float>(col) * kSpacing - kOffset;
float y = static_cast<float>(row) * kSpacing - kOffset;
float seed = static_cast<float>(row * kGridSize + col);
Matrix rotation = MatrixRotation(seed * 0.73f, seed * 1.17f, seed * 0.53f);
SetMeshTransform(cube, MatrixTranslation(x, y, 0.0f) * rotation);
}
}
// Start with some default test lights
PointLight redLight = {};
redLight.Position = { 5.0f, 5.0f, 2.0f };
redLight.Radius = redLightRadius;
redLight.Color = { redLightColor[0], redLightColor[1], redLightColor[2] };
redLight.Intensity = redLightIntensity;
RedLightID = AddPointLight(redLight);
PointLight blueLight = {};
blueLight.Position = { -5.0f, 0.0f, 2.0f };
blueLight.Radius = blueLightRadius;
blueLight.Color = { blueLightColor[0], blueLightColor[1], blueLightColor[2] };
blueLight.Intensity = blueLightIntensity;
BlueLightID = AddPointLight(blueLight);
}
GameCode.Functions = reinterpret_cast<void**>(&Game);
GameCode.FunctionCount = ArraySize(GameFunctionTable);
GameCode.FunctionNames = GameFunctionTable;
InitHotReloadCode(GameCode, ConstString("Game.dll"), ConstString("Game_Temp.dll"), ConstString("lock.tmp"));
if ((Running = GameCode.IsValid))
{
GameInitParams params;
params.GameArena = GameArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Game Arena"));
params.ScratchArena = GameScratchArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Scratch Arena"));
Game.Init(&params);
}
}
}
void JulietApplication::Shutdown()
{
Log(LogLevel::Message, LogCategory::Tool, "Shutting down Juliet Application...");
if (GameCode.IsValid)
{
Game.Shutdown();
ShutdownHotReloadCode(GameCode);
}
if (GraphicsPipeline)
{
DestroyGraphicsPipeline(GraphicsDevice, GraphicsPipeline);
}
if (DepthBuffer)
{
DestroyTexture(GraphicsDevice, DepthBuffer);
}
if (MainWindow && GraphicsDevice)
{
DetachFromWindow(GraphicsDevice, MainWindow);
}
if (MainWindow)
{
DestroyPlatformWindow(MainWindow);
}
if (GraphicsDevice)
{
DestroyGraphicsDevice(GraphicsDevice);
}
Log(LogLevel::Message, LogCategory::Tool, "Juliet App shutdown Completed");
}
void JulietApplication::Update()
{
static LARGE_INTEGER frequency = {};
static LARGE_INTEGER lastTime = {};
if (frequency.QuadPart == 0)
{
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&lastTime);
}
LARGE_INTEGER currentTime;
QueryPerformanceCounter(&currentTime);
float deltaTime = static_cast<float>(currentTime.QuadPart - lastTime.QuadPart) / static_cast<float>(frequency.QuadPart);
lastTime = currentTime;
CameraTime += deltaTime;
static float fpsTimer = 0.0f;
static int fpsFrames = 0;
fpsTimer += deltaTime;
fpsFrames++;
if (fpsTimer >= 0.5f)
{
float fps = static_cast<float>(fpsFrames) / fpsTimer;
float ms = (fpsTimer / static_cast<float>(fpsFrames)) * 1000.0f;
char title[64];
snprintf(title, sizeof(title), "Juliet | %.1f FPS | %.2f ms", static_cast<double>(fps), static_cast<double>(ms));
SetWindowTitle(MainWindow, WrapString(title));
fpsTimer = 0.0f;
fpsFrames = 0;
}
bool reloadShaders = false;
static bool reloadShadersDebounce = false;
static bool f1Debounce = false;
SystemEvent evt;
while (GetEvent(evt))
{
if (evt.Type == EventType::Window_Close_Request)
{
if (evt.Data.Window.AssociatedWindowID == GetWindowID(MainWindow))
{
Running = false;
}
}
// Shader hot reload using keyboard.
if (!reloadShadersDebounce && ((GetKeyModState() & KeyMod::Alt) != KeyMod::None) && IsKeyDown(ScanCode::R))
{
reloadShaders = true;
reloadShadersDebounce = true;
}
if (reloadShadersDebounce && !IsKeyDown(ScanCode::R))
{
reloadShadersDebounce = false;
}
}
static bool firstFreeFrame = false;
if (IsKeyDown(ScanCode::F1))
{
if (!f1Debounce)
{
freeCameraMode = !freeCameraMode;
if (freeCameraMode)
{
firstFreeFrame = true;
}
f1Debounce = true;
}
}
else
{
f1Debounce = false;
}
// Confine and hide the mouse for Free Camera mode
if (freeCameraMode)
{
#ifdef JULIET_ENABLE_IMGUI
ImGui::SetMouseCursor(ImGuiMouseCursor_None);
#endif
#ifdef _WIN32
HWND hwnd = GetForegroundWindow();
if (hwnd)
{
RECT rect;
GetClientRect(hwnd, &rect);
POINT ptCenterClient = { (rect.right - rect.left) / 2, (rect.bottom - rect.top) / 2 };
if (!firstFreeFrame)
{
POINT currentPos;
GetCursorPos(&currentPos);
ScreenToClient(hwnd, &currentPos);
float deltaX = static_cast<float>(currentPos.x - ptCenterClient.x);
float deltaY = static_cast<float>(currentPos.y - ptCenterClient.y);
float sensitivity = 0.005f;
// Plus because the mouse is inverted inherently by windows to screen coordinate
camYaw += deltaX * sensitivity;
camPitch -= deltaY * sensitivity;
camPitch = std::min(camPitch, 1.5f);
camPitch = std::max(camPitch, -1.5f);
}
firstFreeFrame = false;
POINT ptCenterScreen = ptCenterClient;
ClientToScreen(hwnd, &ptCenterScreen);
SetCursorPos(ptCenterScreen.x, ptCenterScreen.y);
}
#endif
}
if (freeCameraMode)
{
float speed = 10.0f * deltaTime;
if ((GetKeyModState() & KeyMod::Shift) != KeyMod::None)
{
speed *= 3.0f;
}
Vector3 forward = { cosf(camYaw) * cosf(camPitch), sinf(camYaw) * cosf(camPitch), sinf(camPitch) };
Vector3 right = { cosf(camYaw + 1.5708f), sinf(camYaw + 1.5708f), 0.0f };
Vector3 up = { 0.0f, 0.0f, 1.0f };
if (IsKeyDown(ScanCode::W))
{
camPos = camPos + forward * speed;
}
if (IsKeyDown(ScanCode::S))
{
camPos = camPos - forward * speed;
}
if (IsKeyDown(ScanCode::D))
{
camPos = camPos + right * speed;
}
if (IsKeyDown(ScanCode::A))
{
camPos = camPos - right * speed;
}
if (IsKeyDown(ScanCode::E))
{
camPos = camPos + up * speed;
}
if (IsKeyDown(ScanCode::Q))
{
camPos = camPos - up * speed;
}
}
if (animateCubes)
{
animateCubesTime += deltaTime;
}
if (animateLights)
{
animateLightsTime += deltaTime;
}
if (animateCamera)
{
animateCameraTime += deltaTime;
}
#ifdef JULIET_ENABLE_IMGUI
ImGui::Begin("Debug Controls");
ImGui::Checkbox("Animate Cubes", &animateCubes);
ImGui::Checkbox("Animate Lights", &animateLights);
ImGui::Checkbox("Animate Camera", &animateCamera);
ImGui::Separator();
ImGui::Text("Global Light");
ImGui::Checkbox("Enable Global Light", &enableGlobalLight);
if (enableGlobalLight)
{
ImGui::SliderFloat3("Direction", globalLightDir, -1.0f, 1.0f);
ImGui::ColorEdit3("Color", globalLightColor);
ImGui::SliderFloat("Ambient Intensity", &globalAmbientIntensity, 0.0f, 1.0f);
}
ImGui::Separator();
ImGui::Text("Red Point Light");
ImGui::ColorEdit3("Red Color", redLightColor);
ImGui::SliderFloat("Red Radius", &redLightRadius, 1.0f, 50.0f);
ImGui::SliderFloat("Red Intensity", &redLightIntensity, 0.0f, 50.0f);
ImGui::Checkbox("Red Light Follows Camera", &redLightFollowsCamera);
ImGui::Separator();
ImGui::Text("Blue Point Light");
ImGui::ColorEdit3("Blue Color", blueLightColor);
ImGui::SliderFloat("Blue Radius", &blueLightRadius, 1.0f, 50.0f);
ImGui::SliderFloat("Blue Intensity", &blueLightIntensity, 0.0f, 50.0f);
ImGui::End();
#endif
ArenaClear(GameScratchArena);
Vector3 redLightPos = { 5.0f, 5.0f, 2.0f };
Vector3 blueLightPos = { -5.0f, 0.0f, 2.0f };
if (animateLights || animateLightsTime > 0.0f)
{
redLightPos = { cosf(animateLightsTime * 2.0f) * 5.0f, sinf(animateLightsTime * 2.0f) * 5.0f, 2.0f };
blueLightPos = { -5.0f, cosf(animateLightsTime) * 3.0f, 2.0f };
}
if (redLightFollowsCamera)
{
Camera cam = GetDebugCamera();
redLightPos = cam.Position;
}
SetPointLightPosition(RedLightID, redLightPos);
SetPointLightPosition(BlueLightID, blueLightPos);
SetPointLightColor(RedLightID, { redLightColor[0], redLightColor[1], redLightColor[2] });
SetPointLightRadius(RedLightID, redLightRadius);
SetPointLightIntensity(RedLightID, redLightIntensity);
SetPointLightColor(BlueLightID, { blueLightColor[0], blueLightColor[1], blueLightColor[2] });
SetPointLightRadius(BlueLightID, blueLightRadius);
SetPointLightIntensity(BlueLightID, blueLightIntensity);
// Animate the 100 cubes (10x10 grid)
constexpr int kGridSize = 10;
constexpr float kSpacing = 2.5f;
constexpr float kOffset = (kGridSize - 1) * kSpacing * 0.5f;
for (int row = 0; row < kGridSize; ++row)
{
for (int col = 0; col < kGridSize; ++col)
{
MeshID cube = static_cast<MeshID>(row * kGridSize + col); // Assuming they were added first
float x = static_cast<float>(col) * kSpacing - kOffset;
float y = static_cast<float>(row) * kSpacing - kOffset;
float seed = static_cast<float>(cube);
float timeZ = animateCubesTime * 2.0f + seed * 0.5f;
float z = 0.0f;
float scaleF = 1.0f;
Matrix rotation = MatrixIdentity();
if (animateCubes || animateCubesTime > 0.0f)
{
z = sinf(timeZ) * 1.5f; // Oscillate up and down
scaleF = 1.0f + sinf(animateCubesTime * 1.5f + seed) * 0.3f; // Pulse scale
rotation = MatrixRotation(animateCubesTime * 1.2f + seed * 0.73f, animateCubesTime * 0.8f + seed * 1.17f,
animateCubesTime * 0.5f + seed * 0.53f);
}
Matrix scale = MatrixScale(scaleF, scaleF, scaleF);
SetMeshTransform(cube, MatrixTranslation(x, y, z) * rotation * scale);
}
}
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, false);
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, true);
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, true);
DebugDisplay_DrawSphere({ 0.0f, 0.0f, 0.0f }, 0.5f, { 1.0f, 1.0f, 0.0f, 1.0f }, true);
DebugDisplay_DrawSphere(blueLightPos, 0.5f, { 0.0f, 0.0f, 1.0f, 1.0f }, true);
DebugDisplay_DrawSphere(redLightPos, 0.5f, { 1.0f, 0.0f, 0.0f, 1.0f }, true);
Game.Update(0.0f);
if (ShouldReloadCode(GameCode))
{
ReloadCode(GameCode);
}
if (reloadShaders)
{
WaitUntilGPUIsIdle(GraphicsDevice);
#if ALLOW_SHADER_HOT_RELOAD
ReloadMeshRendererShaders();
#endif
}
// Memory debugger toggle
static bool toggleDebounce = false;
if (IsKeyDown(Juliet::ScanCode::Home))
{
if (!toggleDebounce)
{
ShowMemoryDebugger = !ShowMemoryDebugger;
toggleDebounce = true;
}
}
else
{
toggleDebounce = false;
}
// Auto-close logic
if (AutoCloseFrameCount > 0)
{
AutoCloseFrameCount--;
if (AutoCloseFrameCount == 0)
{
Log(LogLevel::Message, LogCategory::Tool, "Auto-closing application as requested.");
Running = false;
}
}
if (ShowMemoryDebugger)
{
#if JULIET_DEBUG
Debug::DebugDrawMemoryArena();
#endif
}
ArenaClear(GameScratchArena);
}
void JulietApplication::OnPreRender(CommandList* /*cmd*/) {}
void JulietApplication::OnRender(RenderPass* pass, CommandList* cmd)
{
PushData pushData = {};
pushData.ViewProjection = Camera_GetViewProjectionMatrix(GetDebugCamera());
if (enableGlobalLight)
{
pushData.GlobalLightDirection = Normalize({ globalLightDir[0], globalLightDir[1], globalLightDir[2] });
pushData.GlobalLightColor = { globalLightColor[0], globalLightColor[1], globalLightColor[2] };
pushData.GlobalAmbientIntensity = globalAmbientIntensity;
}
else
{
pushData.GlobalLightDirection = { 0.0f, -1.0f, 0.0f };
pushData.GlobalLightColor = { 0.0f, 0.0f, 0.0f };
pushData.GlobalAmbientIntensity = 0.0f;
}
RenderSkybox(pass, cmd, pushData.ViewProjection);
RenderMeshes(pass, cmd, pushData);
}
ColorTargetInfo JulietApplication::GetColorTargetInfo(Texture* swapchainTexture)
{
ColorTargetInfo info = {};
info.TargetTexture = swapchainTexture;
info.ClearColor = { .R = 0.0f, .G = 0.0f, .B = 0.0f, .A = 1.0f };
info.LoadOperation = LoadOperation::Clear;
info.StoreOperation = StoreOperation::Store;
return info;
}
DepthStencilTargetInfo* JulietApplication::GetDepthTargetInfo()
{
static DepthStencilTargetInfo info = {};
info.TargetTexture = DepthBuffer;
info.ClearDepth = 1.0f;
info.LoadOperation = LoadOperation::Clear;
info.StoreOperation = StoreOperation::Store;
return &info;
}
Camera JulietApplication::GetDebugCamera()
{
if (freeCameraMode)
{
Camera cam = {};
cam.Position = camPos;
cam.Target = camPos + Vector3{ cosf(camYaw) * cosf(camPitch), sinf(camYaw) * cosf(camPitch), sinf(camPitch) };
cam.Up = { 0.0f, 0.0f, 1.0f };
cam.FOV = 1.047f;
cam.AspectRatio = 1200.0f / 800.0f;
cam.NearPlane = 0.1f;
cam.FarPlane = 1000.0f;
return cam;
}
// --- Adjusted for 1-Meter Scale ---
float baseRadius = 25.0f; // Increased to see 10x10 cube grid
float radius = baseRadius;
if (animateCamera || animateCameraTime > 0.0f)
{
float orbitSpeed = 0.5f;
float currentOrbitTime = animateCameraTime * orbitSpeed;
float zoomAmplitude = 15.0f;
float zoomSpeed = 0.5f;
radius = baseRadius + (sinf(animateCameraTime * zoomSpeed) * zoomAmplitude);
float zHeight = radius * 0.5f; // Keep a nice downward viewing angle
Camera cam = {};
cam.Position = { cosf(currentOrbitTime) * radius, sinf(currentOrbitTime) * radius, zHeight };
cam.Target = { 0.0f, 0.0f, 0.0f };
cam.Up = { 0.0f, 0.0f, 1.0f };
cam.FOV = 1.047f;
cam.AspectRatio = 1200.0f / 800.0f;
cam.NearPlane = 0.1f;
cam.FarPlane = 1000.0f;
return cam;
}
float zHeight = radius * 0.5f; // Keep a nice downward viewing angle
Camera cam = {};
cam.Position = { radius, 0.0f, zHeight };
cam.Target = { 0.0f, 0.0f, 0.0f };
cam.Up = { 0.0f, 0.0f, 1.0f };
cam.FOV = 1.047f;
cam.AspectRatio = 1200.0f / 800.0f;
cam.NearPlane = 0.1f;
cam.FarPlane = 1000.0f;
return cam;
}
bool JulietApplication::IsRunning()
{
return Running;
}
namespace
{
JulietApplication EditorApplication;
}
JulietApplication& GetEditorApplication()
{
return EditorApplication;
}
int JulietMain(int argc, wchar_t** argv)
{
if (argc > 1)
{
for (int i = 1; i < argc; ++i)
{
if (wcscmp(argv[i], L"-autoclose") == 0 && (i + 1 < argc))
{
int frames = _wtoi(argv[i + 1]);
EditorApplication.SetAutoCloseFrameCount(frames);
}
}
}
StartApplication(EditorApplication, JulietInit_Flags::Display);
// Pause here to not close the console window immediatly on stop
// Only pause if not in auto-close mode
if (EditorApplication.GetAutoCloseFrameCount() == -1 && !Debug::IsDebuggerPresent())
{
system("PAUSE");
}
return EXIT_SUCCESS;
}