#include "main.h" #ifdef global #undef global #endif #include #include #include #define global static #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef JULIET_ENABLE_IMGUI #include #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) { 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(col) * kSpacing - kOffset; float y = static_cast(row) * kSpacing - kOffset; float seed = static_cast(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(&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(currentTime.QuadPart - lastTime.QuadPart) / static_cast(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(fpsFrames) / fpsTimer; float ms = (fpsTimer / static_cast(fpsFrames)) * 1000.0f; char title[64]; snprintf(title, sizeof(title), "Juliet | %.1f FPS | %.2f ms", static_cast(fps), static_cast(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(¤tPos); ScreenToClient(hwnd, ¤tPos); float deltaX = static_cast(currentPos.x - ptCenterClient.x); float deltaY = static_cast(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(row * kGridSize + col); // Assuming they were added first float x = static_cast(col) * kSpacing - kOffset; float y = static_cast(row) * kSpacing - kOffset; float seed = static_cast(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; }