562 lines
17 KiB
C++
562 lines
17 KiB
C++
#include "main.h"
|
|
|
|
#ifdef global
|
|
#undef global
|
|
#endif
|
|
#include <ios>
|
|
#include <iosfwd>
|
|
#define global static
|
|
|
|
#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;
|
|
|
|
// 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 = 10.0f;
|
|
redLight.Color = { 1.0f, 0.2f, 0.2f };
|
|
redLight.Intensity = 5.0f;
|
|
RedLightID = AddPointLight(redLight);
|
|
|
|
PointLight blueLight = {};
|
|
blueLight.Position = { -5.0f, 0.0f, 2.0f };
|
|
blueLight.Radius = 15.0f;
|
|
blueLight.Color = { 0.2f, 0.2f, 1.0f };
|
|
blueLight.Intensity = 8.0f;
|
|
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(¶ms);
|
|
}
|
|
}
|
|
}
|
|
|
|
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(¤tTime);
|
|
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;
|
|
}
|
|
|
|
if (evt.Type == EventType::Mouse_Move && freeCameraMode)
|
|
{
|
|
float sensitivity = 0.005f;
|
|
camYaw += evt.Data.MouseMovement.X_Displacement * sensitivity;
|
|
camPitch -= evt.Data.MouseMovement.Y_Displacement * sensitivity;
|
|
|
|
// Limit pitch to avoid flipping
|
|
if (camPitch > 1.5f) camPitch = 1.5f;
|
|
if (camPitch < -1.5f) camPitch = -1.5f;
|
|
}
|
|
}
|
|
|
|
if (IsKeyDown(ScanCode::F1))
|
|
{
|
|
if (!f1Debounce)
|
|
{
|
|
freeCameraMode = !freeCameraMode;
|
|
f1Debounce = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
f1Debounce = false;
|
|
}
|
|
|
|
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::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 };
|
|
}
|
|
|
|
SetPointLightPosition(RedLightID, redLightPos);
|
|
SetPointLightPosition(BlueLightID, blueLightPos);
|
|
|
|
// 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());
|
|
|
|
pushData.LightDirection = Normalize({ 0.5f, -1.0f, -0.5f });
|
|
pushData.LightColor = { 1.0f, 0.95f, 0.8f };
|
|
pushData.AmbientIntensity = 0.2f;
|
|
|
|
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;
|
|
}
|