From 48bcdfed9d7b36c5b24aa43758fa09c0b01b08cb Mon Sep 17 00:00:00 2001 From: LeandroSQ Date: Fri, 2 Feb 2024 00:35:53 -0300 Subject: [PATCH] refactor: Better organization + GUI improvements --- .github/images/Z_Key_Dark.png | Bin 0 -> 1588 bytes .gitignore | 3 +- CMakeLists.txt | 5 +- README.md | 2 + lib/CMakeLists.txt | 11 +- src/core/app.cpp | 172 ----------------- src/core/gui_style.hpp | 67 ------- src/core/interface/gui.cpp | 185 +++++++++++++++++++ src/core/{ => interface}/palette.hpp | 3 +- src/core/main.cpp | 9 +- src/core/models/app.cpp | 138 ++++++++++++++ src/core/{ => models}/app.hpp | 16 +- src/core/{ => models}/interation_handler.cpp | 34 ++-- src/core/{ => models}/interation_handler.hpp | 12 +- src/core/{ => models}/object.cpp | 11 +- src/core/{ => models}/object.hpp | 2 +- src/core/{ => models}/quadtree.cpp | 2 +- src/core/{ => models}/quadtree.hpp | 2 +- src/core/{ => models}/quadtree_dummy.cpp | 3 +- src/core/{ => models}/quadtree_grid.cpp | 3 +- src/core/models/solver.cpp | 140 ++++++++++++++ src/core/{ => models}/solver.hpp | 2 +- src/core/precomp.hpp | 1 - src/core/settings.cpp | 11 ++ src/core/{constants.hpp => settings.hpp} | 30 ++- src/core/solver.cpp | 130 ------------- src/core/utils.hpp | 7 +- 27 files changed, 571 insertions(+), 430 deletions(-) create mode 100644 .github/images/Z_Key_Dark.png delete mode 100644 src/core/app.cpp delete mode 100644 src/core/gui_style.hpp create mode 100644 src/core/interface/gui.cpp rename src/core/{ => interface}/palette.hpp (87%) create mode 100644 src/core/models/app.cpp rename src/core/{ => models}/app.hpp (70%) rename src/core/{ => models}/interation_handler.cpp (74%) rename src/core/{ => models}/interation_handler.hpp (57%) rename src/core/{ => models}/object.cpp (84%) rename src/core/{ => models}/object.hpp (95%) rename src/core/{ => models}/quadtree.cpp (99%) rename src/core/{ => models}/quadtree.hpp (96%) rename src/core/{ => models}/quadtree_dummy.cpp (98%) rename src/core/{ => models}/quadtree_grid.cpp (99%) create mode 100644 src/core/models/solver.cpp rename src/core/{ => models}/solver.hpp (96%) create mode 100644 src/core/settings.cpp rename src/core/{constants.hpp => settings.hpp} (50%) delete mode 100644 src/core/solver.cpp diff --git a/.github/images/Z_Key_Dark.png b/.github/images/Z_Key_Dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e9a129993e428717cd6c97cb8d690e06b4c00931 GIT binary patch literal 1588 zcmV-42Fv-0P) z&raJ)6vjXQNf3gR1_CGq1X5|Gc5^SP5Gx$^EVyrQd6d2h+&AFvyQtzWijXL$-CyY)2JEQL3)%Rsq0u-Kyg_pH5Ft|LfUqFBj+Y z`B`1pKLA+o)scj-Y1{UPUWDNmq);gQp{nX9055rMB$P|XaaK-FPWE{&R(VTRRdov_ z28);XZNmkZAcaEVZvb<=g+2r3?%Swqbe@Y8MOkO14-O7O)3nD=l4-SCkDt4{J74~N z8~xL*U0y*}ylI(C217$bJmzALuIsqIzJ8L%YNH@<#}F^iRSe)~&mfAf1<7W!Bi=Mc z5pp{dTU1qLwgIcHerv1S3+nayl{d|G-EPe!4ptkm+Uj?3#gJ~vWHJ~V8^gfBz~gtB z*tU(kyStvtd58;*MgtcY7rrkT(XebWwk>?<6wj)$aBAW3<|Fxm)-=&ae8zmzI3R&uZ@Dh zE6AQV!5gT(K`*R6SO#9-y>;s_Nb$8%5JKy+h+vQ)5rYJY7_sQsMUK#-KKzb_AZu%D z7#$t$*^c8l*xcOg-64s@LJ$B#1gX_(0z8q6)EJV4q3`d8yrQ9?9x-C`mpwZ>3;bJ6 z(=a|hF3}r##6pm*t>6m*3xxt!S67*N2L}hDT#`njx~!3r5fqC>ym;}#m$$RCgWcWT zK6up~i4jE8G!%xFijJUMk6#|0CwGZcOi2L^0586ov;L1T3W)w z!h$co(P*GlD&hS6JQP3LVbz`0MnQ;Gm-X)5J1j3RGxJQ-M5R&**(349Mv&Rr*^a~8 z?d@&s?QxAVBS$O*866#kVHnJ9bBBkAv6&VLLgZ(A)9Ew}!@%U^q%ZII_!y>XVt9Bs zusz3daC>_z(nX;}UXWZahxz$=X5Pfa1lHHryR!H3<41h^_Dz6ia)`VjzapJZ_uB>G zNR1)hyXd&=3u(vev+Vtmu?XtViCA=5vZv3ZMC1jrEDIYO8^Y|-%0&UhG4}96F(hJ; zAQ3~_Y_Io(oDQcQ!~(DH-n#XDwl~Q05bv7Us0Y(u;q~2Hx4u6QA>={Zq5}}7@6rVE zK1S6v4XIR0R&mlur_;VJN#<;^wEK`|vx!Ed@pwiS5Z*C>uIr-JYDw9Ta(#U*b$}wZ z%euI@kZHanilRsn!?tbw`0+!y-FA2d@g1Pd>Sry>LcLz+EFxOiwvBSR-04VI)+Jnr zO!MA{{OL{W6d%5S{|?KtLSLFAl6t+~Q4D}xcUBt(A(Xi+%d%iuRv1Oa9=Z>S7{UaJ z^VCSMh^#yB)QN8qRHy#+Il!trtBr!dd-nStfG0ug=H>=KG8Z2Fie|I `Space bar` to flip the Gravity vector +> `Z` to toggle Gravity ON/OFF + > `Up arrow` to increase the Gravity force > `Down arrow` to decrease the Gravity force diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 5397697..aeda898 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.15.0) set(TEMP_PATH ${CMAKE_CURRENT_SOURCE_DIR}/temp) set(INSTALL_PATH ${CMAKE_CURRENT_SOURCE_DIR}) -# Function to download and include a library, given name, version and source +# Function to download and include a library, given name, version/revision and source function(include_library name version source) # Check if the provided version is valid 0.0.0 format, useful for branches or commits string(REGEX MATCH "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$" version_match ${version}) @@ -50,12 +50,19 @@ function(include_library name version source) message(STATUS "Moving library ${name} ${version} to ${INSTALL_PATH}/${name}-${version}") file(GLOB children RELATIVE ${TEMP_PATH}/${name}-${version} ${TEMP_PATH}/${name}-${version}/*) list(LENGTH children children_length) + # Remove if exists + if (EXISTS ${INSTALL_PATH}/${name}-${version}) + file(REMOVE_RECURSE ${INSTALL_PATH}/${name}-${version}) + endif() file(MAKE_DIRECTORY ${INSTALL_PATH}/${name}-${version}) + message(STATUS "Children: ${children}") if (children_length EQUAL 1) # Move everything inside the top level folder to the install path + message(STATUS "Moving everything inside the top level folder to the install path. From ${TEMP_PATH}/${name}-${version}/${children} to ${INSTALL_PATH}/${name}-${version}") file(RENAME ${TEMP_PATH}/${name}-${version}/${children} ${INSTALL_PATH}/${name}-${version}) else() # Move everything to the install path + message(STATUS "Moving everything to the install path. From ${TEMP_PATH}/${name}-${version} to ${INSTALL_PATH}/${name}-${version}") file(RENAME ${TEMP_PATH}/${name}-${version} ${INSTALL_PATH}/${name}-${version}) endif() file(REMOVE_RECURSE ${TEMP_PATH}/${name}-${version}) @@ -114,5 +121,5 @@ target_compile_definitions(rlimgui PRIVATE NO_FONT_AWESOME) target_link_libraries(rlimgui dear_imgui raylib) # Clean -message(STATUS "Removing temporary files") +# message(STATUS "Removing temporary files") # file(REMOVE_RECURSE ${TEMP_PATH}) \ No newline at end of file diff --git a/src/core/app.cpp b/src/core/app.cpp deleted file mode 100644 index 7b3e755..0000000 --- a/src/core/app.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "app.hpp" -#include "precomp.hpp" -#include "constants.hpp" -#include "palette.hpp" -#include "gui_style.hpp" -#include "utils.hpp" - -App::App() : quadtree(raylib::Rectangle{ 0.0f, 0.0f, (float)WIDTH, (float)HEIGHT }), solver(quadtree), spawner(quadtree) { - camera.offset = raylib::Vector2{ 0.0f, 0.0f }; - camera.target = raylib::Vector2{ 0.0f, 0.0f }; - camera.zoom = 1.0f; - camera.rotation = 0.0f; -} - -App::~App() { - rlImGuiShutdown(); -} - -void App::handleInput(float delta) { - if (ImGui::GetIO().WantCaptureKeyboard) return; - - // Rotate gravity vector - const float angleVelocity = 1.25f; - if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) { - solver.gravity = solver.gravity.Rotate(angleVelocity * delta); - } - if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) { - solver.gravity = solver.gravity.Rotate(-angleVelocity * delta); - } - - // Increment/decrement gravity force - const auto steps = raylib::Vector2::One() * 2.5f * delta; - if (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP)) { - solver.gravity += steps * solver.gravity.Normalize(); - } - if (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN)) { - solver.gravity -= steps * solver.gravity.Normalize(); - } - - // Reverse gravity direction - if (IsKeyPressed(KEY_SPACE)) { - solver.gravity *= -1.0f; - } - - // Toggle rendering debug quadtree - if (IsKeyPressed(KEY_APOSTROPHE) || IsKeyPressed(KEY_GRAVE)) { - TraceLog(LOG_INFO, "app: Toggling quadtree rendering"); - isRenderingQuadtree = !isRenderingQuadtree; - } -} - -void App::renderGUI() { - rlImGuiBegin(); - - ImGui::Begin("Stats"); - ImGui::Text("FPS: %i", GetFPS()); - ImGui::Text("Frame time: %.2f ms", GetFrameTime() * 1000.0f); - ImGui::Text("Objects: %i", (int)quadtree.getAll().size()); - ImGui::Text("Subdivisions: %i", quadtree.getSubdivisions()); - ImGui::Text("Collision checks: %i", solver.getAverageIterations()); - - ImGui::NewLine(); - ImGui::Separator(); - ImGui::NewLine(); - - ImGui::Checkbox("Render quadtree", &isRenderingQuadtree); - ImGui::SliderInt("Sub-steps", (int *)&solver.substeps, 1, 10); - - if (ImGui::Button("Kill objects")) { - quadtree.clear(); - } - - ImGui::NewLine(); - ImGui::Separator(); - ImGui::NewLine(); - - // Apply the size of the circle to the cursor - ImGui::Text("Gravity: %.2f", solver.gravity.Length()); - ImGui::SameLine(); - - // Draw custom circle with arrow - const float radius = ImGui::GetTextLineHeightWithSpacing(); - constexpr float arrowAngle = 30.0f * DEG2RAD; - const float arrowLength = radius / 2.5f; - const auto vec = solver.gravity.Normalize() * radius * 0.8f; - const auto center = raylib::Vector2( - ImGui::GetWindowPos().x + ImGui::GetContentRegionMax().x - radius, - ImGui::GetCursorScreenPos().y + radius - ); - const auto color = convert(ImGui::GetStyle().Colors[ImGuiCol_Text]); - const auto colorArrow = convert(ImGui::GetStyle().Colors[ImGuiCol_CheckMark]); - ImGui::GetWindowDrawList()->AddCircle(convert(center), radius, color); - ImGui::GetWindowDrawList()->AddLine(convert(center), convert(center + vec), colorArrow, 1.0f); - ImGui::GetWindowDrawList()->AddLine(convert(center + vec), convert(center + vec - solver.gravity.Normalize().Rotate(arrowAngle) * arrowLength), colorArrow, 1.0f); - ImGui::GetWindowDrawList()->AddLine(convert(center + vec), convert(center + vec - solver.gravity.Normalize().Rotate(-arrowAngle) * arrowLength), colorArrow, 1.0f); - - ImGui::NewLine(); - ImGui::Text("{ %.2f, %.2f }", solver.gravity.x, solver.gravity.y); - - ImGui::End(); - - if constexpr (ENABLE_TEMPERATURE) { - ImGui::Begin("Controls"); - ImGui::SliderFloat("Transfer contact", &solver.temperatureTransferContactMultiplier, 0.0f, 1.0f); - ImGui::SliderFloat("Transfer air", &solver.temperatureTransferAirMultiplier, 0.0f, 0.25f); - ImGui::SliderFloat("Transfer ground", &solver.temperatureTransferGroundMultiplier, 0.0f, 1.0f); - ImGui::SliderFloat("Floating force", &solver.temperatureFloatingForce, 0.0f, 100.0f); - ImGui::End(); - } - - if (ImGui::GetIO().WantCaptureMouse && (ImGui::IsAnyItemHovered() || ImGui::IsAnyItemActive())) { - SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); - } else { - SetMouseCursor(MOUSE_CURSOR_DEFAULT); - } - - rlImGuiEnd(); -} - -void App::setup() { -#ifdef DEBUG - SetTraceLogLevel(LOG_DEBUG); -#else - SetTraceLogLevel(LOG_ERROR); -#endif - - TraceLog(LOG_INFO, "app: Starting..."); - SetRandomSeed(GetTime()); - - rlImGuiSetup(true); - setupStyle(); -} - -void App::update() { - auto delta = GetFrameTime(); - - spawner.update(getRelativeMousePosition(), delta, !ImGui::GetIO().WantCaptureMouse); - solver.solve(delta); - - handleInput(delta); -} - -void App::render() { -#ifdef CENTER_CIRCLE - ClearBackground(PALETTE_GREY); - DrawCircle(GetRenderWidth() / 2, GetRenderHeight() / 2, 400.0f, PALETTE_BLACK); -#else - ClearBackground(PALETTE_BLACK); -#endif - - // Use the 2D camera to translate the simulation if it's smaller than the window - BeginMode2D(camera); - if (isRenderingQuadtree) { - quadtree.render(); - } else { - auto all = quadtree.getAll(); - for (auto object : all) { - object->render(); - } - } - EndMode2D(); - - renderGUI(); -} - -void App::resize() { - // Ensure the simulation is centered - camera.target = raylib::Vector2( - WIDTH / 2.0f - GetRenderWidth() / 2.0f, - HEIGHT / 2.0f - GetRenderHeight() / 2.0f - ); -} \ No newline at end of file diff --git a/src/core/gui_style.hpp b/src/core/gui_style.hpp deleted file mode 100644 index cd3c87e..0000000 --- a/src/core/gui_style.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include "precomp.hpp" -#include "palette.hpp" - -inline void setupStyle() { - ImGuiStyle &style = ImGui::GetStyle(); - - auto primary = ImVec4(0.13f, 0.61f, 0.93f, 1.00f); - auto background = ImVec4(0.13f, 0.14f, 0.17f, 1.00f); - auto text = ImVec4(0.86f, 0.93f, 0.89f, 0.78f); - - style.WindowMinSize = ImVec2(160, 20); - style.FramePadding = ImVec2(4, 2); - style.ItemSpacing = ImVec2(6, 2); - style.ItemInnerSpacing = ImVec2(6, 4); - style.Alpha = 0.95f; - style.WindowRounding = 4.0f; - style.FrameRounding = 2.0f; - style.IndentSpacing = 6.0f; - style.ItemInnerSpacing = ImVec2(6, 4); - style.ColumnsMinSpacing = 50.0f; - style.GrabMinSize = 14.0f; - style.GrabRounding = 16.0f; - style.ScrollbarSize = 12.0f; - style.ScrollbarRounding = 16.0f; - style.SeparatorTextPadding = ImVec2(10, 10); - - style.Colors[ImGuiCol_Text] = text; - style.Colors[ImGuiCol_TextDisabled] = ImVec4(text.x, text.y, text.z, 0.28f); - style.Colors[ImGuiCol_WindowBg] = background; - style.Colors[ImGuiCol_Border] = ImVec4(0.31f, 0.31f, 1.00f, 0.13f); - style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - style.Colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f); - style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(primary.x, primary.y, primary.z, 0.78f); - style.Colors[ImGuiCol_FrameBgActive] = primary; - style.Colors[ImGuiCol_TitleBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f); - style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.20f, 0.22f, 0.27f, 0.75f); - style.Colors[ImGuiCol_TitleBgActive] = primary; - style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.47f); - style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f); - style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.09f, 0.15f, 0.16f, 1.00f); - style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(primary.x, primary.y, primary.z, 0.78f); - style.Colors[ImGuiCol_ScrollbarGrabActive] = primary; - style.Colors[ImGuiCol_CheckMark] = ImVec4(0.71f, 0.22f, 0.27f, 1.00f); - style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.47f, 0.77f, 0.83f, 0.14f); - style.Colors[ImGuiCol_SliderGrabActive] = primary; - style.Colors[ImGuiCol_Button] = ImVec4(0.47f, 0.77f, 0.83f, 0.14f); - style.Colors[ImGuiCol_ButtonHovered] = ImVec4(primary.x, primary.y, primary.z, 0.86f); - style.Colors[ImGuiCol_ButtonActive] = primary; - style.Colors[ImGuiCol_Header] = ImVec4(primary.x, primary.y, primary.z, 0.76f); - style.Colors[ImGuiCol_HeaderHovered] = ImVec4(primary.x, primary.y, primary.z, 0.86f); - style.Colors[ImGuiCol_HeaderActive] = primary; - style.Colors[ImGuiCol_Separator] = ImVec4(0.14f, 0.16f, 0.19f, 1.00f); - style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(primary.x, primary.y, primary.z, 0.78f); - style.Colors[ImGuiCol_SeparatorActive] = primary; - style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.47f, 0.77f, 0.83f, 0.04f); - style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(primary.x, primary.y, primary.z, 0.78f); - style.Colors[ImGuiCol_ResizeGripActive] = primary; - style.Colors[ImGuiCol_PlotLines] = ImVec4(text.x, text.y, text.z, 0.63f); - style.Colors[ImGuiCol_PlotLinesHovered] = primary; - style.Colors[ImGuiCol_PlotHistogram] = ImVec4(primary.x, primary.y, primary.z, 0.63f); - style.Colors[ImGuiCol_PlotHistogramHovered] = primary; - style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(primary.x, primary.y, primary.z, 0.43f); - style.Colors[ImGuiCol_PopupBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.9f); - style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.73f); -} \ No newline at end of file diff --git a/src/core/interface/gui.cpp b/src/core/interface/gui.cpp new file mode 100644 index 0000000..c260ba9 --- /dev/null +++ b/src/core/interface/gui.cpp @@ -0,0 +1,185 @@ +#include "../precomp.hpp" +#include "../settings.hpp" +#include "../utils.hpp" +#include "../models/app.hpp" +#include "imgui.h" + +void App::setupGUI() { + rlImGuiSetup(true); + ImGuiStyle &style = ImGui::GetStyle(); + + auto primary = ImVec4(0.13f, 0.61f, 0.93f, 1.00f); + auto background = ImVec4(0.13f, 0.14f, 0.17f, 1.00f); + auto text = ImVec4(0.86f, 0.93f, 0.89f, 0.78f); + + style.WindowMinSize = ImVec2(160, 20); + style.FramePadding = ImVec2(4, 2); + style.ItemSpacing = ImVec2(6, 2); + style.ItemInnerSpacing = ImVec2(6, 4); + style.Alpha = 0.95f; + style.WindowRounding = 4.0f; + style.FrameRounding = 2.0f; + style.IndentSpacing = 6.0f; + style.ItemInnerSpacing = ImVec2(6, 4); + style.ColumnsMinSpacing = 50.0f; + style.GrabMinSize = 14.0f; + style.GrabRounding = 16.0f; + style.ScrollbarSize = 12.0f; + style.ScrollbarRounding = 16.0f; + style.SeparatorTextPadding = ImVec2(10, 10); + + style.Colors[ImGuiCol_Text] = text; + style.Colors[ImGuiCol_TextDisabled] = ImVec4(text.x, text.y, text.z, 0.28f); + style.Colors[ImGuiCol_WindowBg] = background; + style.Colors[ImGuiCol_Border] = ImVec4(0.31f, 0.31f, 1.00f, 0.13f); + style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + style.Colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f); + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(primary.x, primary.y, primary.z, 0.78f); + style.Colors[ImGuiCol_FrameBgActive] = primary; + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.20f, 0.22f, 0.27f, 0.75f); + style.Colors[ImGuiCol_TitleBgActive] = primary; + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.47f); + style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f); + style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.09f, 0.15f, 0.16f, 1.00f); + style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(primary.x, primary.y, primary.z, 0.78f); + style.Colors[ImGuiCol_ScrollbarGrabActive] = primary; + style.Colors[ImGuiCol_CheckMark] = ImVec4(0.71f, 0.22f, 0.27f, 1.00f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.47f, 0.77f, 0.83f, 0.14f); + style.Colors[ImGuiCol_SliderGrabActive] = primary; + style.Colors[ImGuiCol_Button] = ImVec4(0.47f, 0.77f, 0.83f, 0.14f); + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(primary.x, primary.y, primary.z, 0.86f); + style.Colors[ImGuiCol_ButtonActive] = primary; + style.Colors[ImGuiCol_Header] = ImVec4(primary.x, primary.y, primary.z, 0.76f); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(primary.x, primary.y, primary.z, 0.86f); + style.Colors[ImGuiCol_HeaderActive] = primary; + style.Colors[ImGuiCol_Separator] = ImVec4(0.14f, 0.16f, 0.19f, 1.00f); + style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(primary.x, primary.y, primary.z, 0.78f); + style.Colors[ImGuiCol_SeparatorActive] = primary; + style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.47f, 0.77f, 0.83f, 0.04f); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(primary.x, primary.y, primary.z, 0.78f); + style.Colors[ImGuiCol_ResizeGripActive] = primary; + style.Colors[ImGuiCol_PlotLines] = ImVec4(text.x, text.y, text.z, 0.63f); + style.Colors[ImGuiCol_PlotLinesHovered] = primary; + style.Colors[ImGuiCol_PlotHistogram] = ImVec4(primary.x, primary.y, primary.z, 0.63f); + style.Colors[ImGuiCol_PlotHistogramHovered] = primary; + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(primary.x, primary.y, primary.z, 0.43f); + style.Colors[ImGuiCol_PopupBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.9f); + style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.22f, 0.27f, 0.73f); +} + +void App::renderGUI() { + rlImGuiBegin(); + + ImGui::Begin("Stats"); + ImGui::Text("FPS: %i", GetFPS()); + ImGui::Text("Frame time: %.2f ms", GetFrameTime() * 1000.0f); + if (ENABLE_AUTO_ADJUST_SUBSTEPS) { + ImGui::Text("Perceived time: %.2f ms", frameTimeSum / frameCounter * 1000.0f); + ImGui::Text("Sub-steps: %i", solver.substeps); + } + ImGui::Text("Objects: %i", (int)quadtree.getAll().size()); + ImGui::Text("Subdivisions: %i", quadtree.getSubdivisions()); + ImGui::Text("Collision checks: %i", solver.getAverageIterations()); + + ImGui::NewLine(); + ImGui::Separator(); + ImGui::NewLine(); + + ImGui::Checkbox("Render quadtree", &isRenderingQuadtree); + ImGui::Checkbox("Constraint to circle", &ENABLE_CIRCLE_CONSTRAINT); + if (ENABLE_CIRCLE_CONSTRAINT && ENABLE_TEMPERATURE) { + ImGui::Checkbox("Heat from circle border", &ENABLE_HEAT_FROM_CIRCLE_BORDER); + } + + ImGui::Checkbox("Auto adjust simulation quality", &ENABLE_AUTO_ADJUST_SUBSTEPS); + if (!ENABLE_AUTO_ADJUST_SUBSTEPS) { + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f); + ImGui::SliderInt("Sub-steps", (int *)&solver.substeps, 1, 10); + } + + ImGui::Checkbox("Spread spawn", &ENABLE_SPAWN_SPREAD); + if (ImGui::Button("Kill objects")) { + quadtree.clear(); + } + if (quadtree.size() < SPAWN_COUNT) { + ImGui::SameLine(); + ImGui::Checkbox("Is spawning", &spawner.isSpawning); + } + + ImGui::NewLine(); + ImGui::Separator(); + ImGui::NewLine(); + ImGui::Text("Render mode"); + + if (ImGui::RadioButton("Solid color", ENABLE_SOLID_COLOR)) { + ENABLE_RAINBOW_COLORS = false; + ENABLE_FIXED_RAINBOW = false; + ENABLE_TEMPERATURE = false; + ENABLE_SOLID_COLOR = true; + } + if (ImGui::RadioButton("Rainbow on spawn", ENABLE_RAINBOW_COLORS)) { + ENABLE_FIXED_RAINBOW = false; + ENABLE_RAINBOW_COLORS = true; + ENABLE_SOLID_COLOR = false; + ENABLE_TEMPERATURE = false; + } + if (ImGui::RadioButton("Rainbow gradient", ENABLE_FIXED_RAINBOW)) { + ENABLE_RAINBOW_COLORS = false; + ENABLE_FIXED_RAINBOW = true; + ENABLE_SOLID_COLOR = false; + ENABLE_TEMPERATURE = false; + } + if (ImGui::RadioButton("Temperature", ENABLE_TEMPERATURE)) { + ENABLE_TEMPERATURE = true; + ENABLE_SOLID_COLOR = false; + ENABLE_FIXED_RAINBOW = false; + ENABLE_RAINBOW_COLORS = false; + } + + ImGui::NewLine(); + ImGui::Separator(); + ImGui::NewLine(); + + // Apply the size of the circle to the cursor + ImGui::Text("Gravity: %.2f", solver.gravity.Length()); + ImGui::SameLine(); + + // Draw custom circle with arrow + const float radius = ImGui::GetTextLineHeightWithSpacing(); + float arrowAngle = 30.0f * DEG2RAD; + const float arrowLength = radius / 2.5f; + const auto vec = solver.gravity.Normalize() * radius * 0.8f; + const auto center = raylib::Vector2( + ImGui::GetWindowPos().x + ImGui::GetContentRegionMax().x - radius, + ImGui::GetCursorScreenPos().y + radius + ); + const auto color = convert(ImGui::GetStyle().Colors[ImGuiCol_Text]); + const auto colorArrow = convert(ImGui::GetStyle().Colors[ImGuiCol_CheckMark]); + ImGui::GetWindowDrawList()->AddCircle(convert(center), radius, color); + ImGui::GetWindowDrawList()->AddLine(convert(center), convert(center + vec), colorArrow, 1.0f); + ImGui::GetWindowDrawList()->AddLine(convert(center + vec), convert(center + vec - solver.gravity.Normalize().Rotate(arrowAngle) * arrowLength), colorArrow, 1.0f); + ImGui::GetWindowDrawList()->AddLine(convert(center + vec), convert(center + vec - solver.gravity.Normalize().Rotate(-arrowAngle) * arrowLength), colorArrow, 1.0f); + + ImGui::NewLine(); + ImGui::Text("{ %.2f, %.2f }", solver.gravity.x, solver.gravity.y); + + ImGui::End(); + + if (ENABLE_TEMPERATURE) { + ImGui::Begin("Controls"); + ImGui::SliderFloat("Transfer contact", &solver.temperatureTransferContactMultiplier, 0.0f, 1.0f); + ImGui::SliderFloat("Transfer air", &solver.temperatureTransferAirMultiplier, 0.0f, 0.25f); + ImGui::SliderFloat("Transfer ground", &solver.temperatureTransferGroundMultiplier, 0.0f, 1.0f); + ImGui::SliderFloat("Floating force", &solver.temperatureFloatingForce, 0.0f, 100.0f); + ImGui::End(); + } + + if (ImGui::GetIO().WantCaptureMouse && (ImGui::IsAnyItemHovered() || ImGui::IsAnyItemActive())) { + SetMouseCursor(MOUSE_CURSOR_POINTING_HAND); + } else { + SetMouseCursor(MOUSE_CURSOR_DEFAULT); + } + + rlImGuiEnd(); +} \ No newline at end of file diff --git a/src/core/palette.hpp b/src/core/interface/palette.hpp similarity index 87% rename from src/core/palette.hpp rename to src/core/interface/palette.hpp index b3bded9..1a8decb 100644 --- a/src/core/palette.hpp +++ b/src/core/interface/palette.hpp @@ -1,7 +1,6 @@ #pragma once -#include "Color.hpp" -#include +#include "../precomp.hpp" const raylib::Color PALETTE_RED = raylib::Color(231, 76, 60); const raylib::Color PALETTE_BLUE = raylib::Color(15, 188, 249); diff --git a/src/core/main.cpp b/src/core/main.cpp index 074a107..1fddff9 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -1,16 +1,19 @@ #include "precomp.hpp" -#include "constants.hpp" -#include "app.hpp" +#include "settings.hpp" +#include "models/app.hpp" App app; - void render() { if (IsWindowResized()) app.resize(); + float start = GetTime(); + app.onFrameStart(); + app.update(); BeginDrawing(); app.render(); + app.onFrameEnd(); EndDrawing(); } diff --git a/src/core/models/app.cpp b/src/core/models/app.cpp new file mode 100644 index 0000000..7a658c5 --- /dev/null +++ b/src/core/models/app.cpp @@ -0,0 +1,138 @@ +#include "app.hpp" +#include "../precomp.hpp" +#include "../settings.hpp" +#include "../interface/palette.hpp" +#include "../utils.hpp" +#include "raylib.h" + +App::App() : quadtree(raylib::Rectangle{ 0.0f, 0.0f, (float)WIDTH, (float)HEIGHT }), solver(quadtree), spawner(quadtree) { + camera.offset = raylib::Vector2{ 0.0f, 0.0f }; + camera.target = raylib::Vector2{ 0.0f, 0.0f }; + camera.zoom = 1.0f; + camera.rotation = 0.0f; +} + +App::~App() { + rlImGuiShutdown(); +} + +void App::handleInput(float delta) { + if (ImGui::GetIO().WantCaptureKeyboard) return; + + // Rotate gravity vector + const float angleVelocity = 1.25f; + if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) { + solver.gravity = solver.gravity.Rotate(angleVelocity * delta); + } + if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) { + solver.gravity = solver.gravity.Rotate(-angleVelocity * delta); + } + + // Increment/decrement gravity force + const auto steps = raylib::Vector2::One() * 2.5f * delta; + if (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP)) { + solver.gravity += steps * solver.gravity.Normalize(); + } + if (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN)) { + solver.gravity -= steps * solver.gravity.Normalize(); + } + + // Reverse gravity direction + if (IsKeyPressed(KEY_SPACE)) { + solver.gravity *= -1.0f; + } + + // Zero gravity + if (IsKeyPressed(KEY_Z)) { + if (solver.gravity.Length() == 0.0f) + solver.gravity = raylib::Vector2(0.0f, 9.8f); + else + solver.gravity = raylib::Vector2::Zero(); + } + + // Toggle rendering debug quadtree + if (IsKeyPressed(KEY_APOSTROPHE) || IsKeyPressed(KEY_GRAVE)) { + TraceLog(LOG_INFO, "app: Toggling quadtree rendering"); + isRenderingQuadtree = !isRenderingQuadtree; + } +} + +void App::setup() { +#ifdef DEBUG + SetTraceLogLevel(LOG_DEBUG); +#else + SetTraceLogLevel(LOG_WARNING); +#endif + + TraceLog(LOG_INFO, "app: Starting..."); + SetRandomSeed(GetTime()); + + setupGUI(); +} + +void App::update() { + auto delta = GetFrameTime(); + + spawner.update(getRelativeMousePosition(), solver.substeps, delta, !ImGui::GetIO().WantCaptureMouse); + solver.solve(delta); + + handleInput(delta); +} + +void App::render() { + // Background + ClearBackground(PALETTE_GREY); + DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, CENTER_CIRCLE_CURRENT_RADIUS, PALETTE_BLACK); + + // Use the 2D camera to translate the simulation if it's smaller than the window + BeginMode2D(camera); + if (isRenderingQuadtree) { + quadtree.render(); + } else { + auto all = quadtree.getAll(); + for (auto object : all) { + object->render(); + } + } + EndMode2D(); + + renderGUI(); +} + +void App::resize() { + // Ensure the simulation is centered + camera.target = raylib::Vector2( + WIDTH / 2.0f - GetScreenWidth() / 2.0f, + HEIGHT / 2.0f - GetScreenHeight() / 2.0f + ); +} + +void App::onFrameStart() { + frameStartTime = GetTime(); +} + +void App::onFrameEnd() { + if (!ENABLE_AUTO_ADJUST_SUBSTEPS) return; + + const float frameTime = GetTime() - frameStartTime; + frameCounter++; + frameTimeSum += frameTime; + + if (frameCounter <= TARGET_FPS) return; + + // Detect slowness and adjust simulation substeps accordingly + float expectedFrameTime = (1.0f / TARGET_FPS); + float averageFrameTime = (frameTimeSum / frameCounter); + if (solver.substeps > 1 && averageFrameTime > expectedFrameTime) { + TraceLog(LOG_WARNING, "Detected slowness, decreasing simulation substeps. Simulation time: %.2fms (%.2fms expected)", averageFrameTime, expectedFrameTime); + solver.substeps --; + if (solver.substeps < 1) solver.substeps = 1; + } else if (solver.substeps < 10 && expectedFrameTime - averageFrameTime >= 0.5f * expectedFrameTime) { + TraceLog(LOG_DEBUG, "Detected under utilization, increasing simulation substeps. Simulation time: %.2fms (%.2fms expected)", averageFrameTime, expectedFrameTime); + solver.substeps ++; + if (solver.substeps > 10) solver.substeps = 10; + } + + frameCounter = 0; + frameTimeSum = 0; +} \ No newline at end of file diff --git a/src/core/app.hpp b/src/core/models/app.hpp similarity index 70% rename from src/core/app.hpp rename to src/core/models/app.hpp index c8e0afe..eec212b 100644 --- a/src/core/app.hpp +++ b/src/core/models/app.hpp @@ -1,25 +1,35 @@ #pragma once -#include "precomp.hpp" +#include "../precomp.hpp" #include "quadtree.hpp" #include "solver.hpp" #include "interation_handler.hpp" class App { private: - Quadtree quadtree; Solver solver; - InterationHandler spawner; Camera2D camera; + Quadtree quadtree; + InterationHandler spawner; + bool isRenderingQuadtree; + float frameStartTime; + uint8_t frameCounter; + float frameTimeSum; + void handleInput(float deltaTime); void renderGUI(); + void setupGUI(); public: + App(); ~App(); + void onFrameStart(); + void onFrameEnd(); + void setup(); void update(); void render(); diff --git a/src/core/interation_handler.cpp b/src/core/models/interation_handler.cpp similarity index 74% rename from src/core/interation_handler.cpp rename to src/core/models/interation_handler.cpp index 0070cb7..3bbdf03 100644 --- a/src/core/interation_handler.cpp +++ b/src/core/models/interation_handler.cpp @@ -1,12 +1,10 @@ -#include "Vector2.hpp" #include "interation_handler.hpp" -#include "constants.hpp" -#include "palette.hpp" -#include "raylib.h" +#include "../settings.hpp" +#include "../interface/palette.hpp" InterationHandler::InterationHandler(Quadtree &quadtree) : quadtree(quadtree) { } -void InterationHandler::spawn(raylib::Vector2 mouse) { +void InterationHandler::spawn(raylib::Vector2 mouse, uint8_t substeps) { const int count = 4; const float mass = 100.0f; const float temperature = 0.0f; @@ -16,16 +14,15 @@ void InterationHandler::spawn(raylib::Vector2 mouse) { for (int i = 0; i < count; i++) { if (isSpawning) { position = raylib::Vector2(WIDTH / 2.0f, CENTER_CIRCLE_RADIUS / 2.0f); - acceleration = raylib::Vector2(cosf(angle), sinf(angle) * 0.5f) * 2000.0f * mass; + acceleration = (raylib::Vector2(cosf(angle), sinf(angle) * 0.5f) * 500.0f * mass) * (float)substeps; } else { position = mouse; } - position.x += i * (OBJECT_RADIUS * 2.5f) - (count - 1) * (OBJECT_RADIUS * 1.5f); raylib::Color color; - if constexpr (ENABLE_RAINBOW) { + if (ENABLE_RAINBOW_COLORS) { color = raylib::Color(raylib::Vector3((quadtree.size() % 361 / 360.0f) * 360.0f, 1.0f, 1.0f)); } else { color = PALETTE_BLUE; @@ -36,17 +33,17 @@ void InterationHandler::spawn(raylib::Vector2 mouse) { } } -void InterationHandler::update(raylib::Vector2 mouse, float deltaTime, bool consumeInput) { +void InterationHandler::update(raylib::Vector2 mouse, uint8_t substeps, float deltaTime, bool consumeInput) { if (isSpawning) { timer += deltaTime; if (timer >= SPAWN_INTERVAL) { timer -= SPAWN_INTERVAL; - if constexpr (ENABLE_SPAWN_SPREAD) - angle = sinf(GetTime()) * 0.25f * PI + 0.5f * PI; + if (ENABLE_SPAWN_SPREAD) + angle = Lerp(angle, sinf(GetTime()) * 0.25f * PI + 0.5f * PI, deltaTime * 10); else - angle = 0.5f * PI; + angle = Lerp(angle, 0.5f * PI, deltaTime * 10); - spawn(raylib::Vector2::Zero()); + spawn(raylib::Vector2::Zero(), substeps); if (quadtree.size() >= SPAWN_COUNT) { isSpawning = false; @@ -59,20 +56,21 @@ void InterationHandler::update(raylib::Vector2 mouse, float deltaTime, bool cons if (!consumeInput) return; - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) { - spawn(mouse); + // Manual spawn + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {// Click + spawn(mouse, substeps); timer = 0; - } else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { + } else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {// Hold timer += deltaTime; if (timer >= MANUAL_SPAWN_INTERVAL) { timer -= MANUAL_SPAWN_INTERVAL; - spawn(mouse); + spawn(mouse, substeps); } } if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) {// Dragging if (draggingObjects.size() == 0) { - constexpr float OBJECT_RADIUS = 50.0f; + float OBJECT_RADIUS = 50.0f; auto objects = quadtree.query(raylib::Rectangle{ mouse.x - OBJECT_RADIUS, mouse.y - OBJECT_RADIUS, OBJECT_RADIUS * 2.0f, OBJECT_RADIUS * 2.0f }); for (auto object : objects) { diff --git a/src/core/interation_handler.hpp b/src/core/models/interation_handler.hpp similarity index 57% rename from src/core/interation_handler.hpp rename to src/core/models/interation_handler.hpp index 29933a9..d42ec0c 100644 --- a/src/core/interation_handler.hpp +++ b/src/core/models/interation_handler.hpp @@ -1,7 +1,6 @@ #pragma once -#include "Vector2.hpp" -#include "precomp.hpp" +#include "../precomp.hpp" #include "quadtree.hpp" #include "object.hpp" @@ -10,14 +9,15 @@ class InterationHandler { Quadtree &quadtree; std::vector> draggingObjects; - float angle = 0.0f; + float angle = 90.0f * DEG2RAD; float timer = 0.0f; - bool isSpawning = true; - void spawn(raylib::Vector2 mouse); + void spawn(raylib::Vector2 mouse, uint8_t substeps); public: + bool isSpawning = true; + InterationHandler(Quadtree &quadtree); - void update(raylib::Vector2 mouse, float deltaTime, bool consumeInput); + void update(raylib::Vector2 mouse, uint8_t substeps, float deltaTime, bool consumeInput); }; diff --git a/src/core/object.cpp b/src/core/models/object.cpp similarity index 84% rename from src/core/object.cpp rename to src/core/models/object.cpp index cd137c5..ead4f03 100644 --- a/src/core/object.cpp +++ b/src/core/models/object.cpp @@ -1,5 +1,6 @@ #include "object.hpp" -#include "constants.hpp" +#include "../settings.hpp" +#include "../utils.hpp" Object::Object(raylib::Vector2 position, raylib::Vector2 acceleration, float mass, float radius, float temperature, raylib::Color color) { this->position = position; @@ -50,10 +51,14 @@ raylib::Color getColorFromTemperature(float temperature) { } void Object::render() { - if constexpr (ENABLE_TEMPERATURE) + if (ENABLE_TEMPERATURE) { position.DrawCircle(radius, getColorFromTemperature(temperature)); - else + } else if (ENABLE_FIXED_RAINBOW) { + const int hue = (position.y / ((float)GetScreenHeight() / 2.0f)) * 240.0f; + DrawCircleV(position, radius, raylib::Color(raylib::Vector3(hue, 1.0f, 1.0f))); + } else { DrawCircleV(position, radius, color); + } } raylib::Rectangle Object::getBounds() { diff --git a/src/core/object.hpp b/src/core/models/object.hpp similarity index 95% rename from src/core/object.hpp rename to src/core/models/object.hpp index 8eb9dac..92585e6 100644 --- a/src/core/object.hpp +++ b/src/core/models/object.hpp @@ -1,6 +1,6 @@ #pragma once -#include "precomp.hpp" +#include "../precomp.hpp" #include "quadtree.hpp" class Quadtree; diff --git a/src/core/quadtree.cpp b/src/core/models/quadtree.cpp similarity index 99% rename from src/core/quadtree.cpp rename to src/core/models/quadtree.cpp index 7654ded..204bd9e 100644 --- a/src/core/quadtree.cpp +++ b/src/core/models/quadtree.cpp @@ -1,8 +1,8 @@ -#include "constants.hpp" #if !defined(QUADTREE_DUMMY) && !defined(QUADTREE_GRID) #include "quadtree.hpp" +#include "../settings.hpp" Quadtree::Quadtree(raylib::Rectangle bounds, uint8_t depth) : bounds(bounds), depth(depth) { color = raylib::Color { diff --git a/src/core/quadtree.hpp b/src/core/models/quadtree.hpp similarity index 96% rename from src/core/quadtree.hpp rename to src/core/models/quadtree.hpp index f574dfd..af33ff1 100644 --- a/src/core/quadtree.hpp +++ b/src/core/models/quadtree.hpp @@ -1,7 +1,7 @@ #pragma once +#include "../precomp.hpp" #include "object.hpp" -#include "precomp.hpp" class Object; class Quadtree; diff --git a/src/core/quadtree_dummy.cpp b/src/core/models/quadtree_dummy.cpp similarity index 98% rename from src/core/quadtree_dummy.cpp rename to src/core/models/quadtree_dummy.cpp index c54a2ad..6b46df6 100644 --- a/src/core/quadtree_dummy.cpp +++ b/src/core/models/quadtree_dummy.cpp @@ -8,11 +8,10 @@ * so it can be easily compared with the actual quadtree implementation */ -#include "constants.hpp" - #ifdef QUADTREE_DUMMY #include "quadtree.hpp" +#include "../settings.hpp" Quadtree::Quadtree(raylib::Rectangle bounds, uint8_t depth) : bounds(bounds), depth(depth) { } diff --git a/src/core/quadtree_grid.cpp b/src/core/models/quadtree_grid.cpp similarity index 99% rename from src/core/quadtree_grid.cpp rename to src/core/models/quadtree_grid.cpp index ce23126..0492bb2 100644 --- a/src/core/quadtree_grid.cpp +++ b/src/core/models/quadtree_grid.cpp @@ -10,10 +10,11 @@ * It exposes the same interface as the quadtree so it can be easily compared with it */ -#include "constants.hpp" #ifdef QUADTREE_GRID + #include "quadtree.hpp" +#include "../settings.hpp" // Although it says "Quadtree" this is but a spatial grid diff --git a/src/core/models/solver.cpp b/src/core/models/solver.cpp new file mode 100644 index 0000000..2c23a4e --- /dev/null +++ b/src/core/models/solver.cpp @@ -0,0 +1,140 @@ +#include "solver.hpp" +#include "../settings.hpp" +#include "raylib.h" +#include "raymath.h" + +#pragma region Misc +uint32_t Solver::getAverageIterations() { + return iterationSum / iterationCount; +} + +void interpolateCenterCircleRadius() { + const float target = ENABLE_CIRCLE_CONSTRAINT ? CENTER_CIRCLE_RADIUS : std::max(GetScreenWidth(), GetScreenHeight()); + CENTER_CIRCLE_CURRENT_RADIUS = Lerp(CENTER_CIRCLE_CURRENT_RADIUS, target, 0.05f); +} +#pragma endregion + +Solver::Solver(Quadtree &quadtree) : quadtree(quadtree) { + centerCirclePosition = raylib::Vector2{ WIDTH / 2.0f, HEIGHT / 2.0f }; +} + +void Solver::applyGravity(std::shared_ptr object, float deltaTime) { + object->acceleration += gravity * object->mass; + + + if (!ENABLE_TEMPERATURE) return; + + // The more heat, the more upwards acceleration + auto temp = (object->temperature - AIR_TEMPERATURE) / (GROUND_TEMPERATURE - AIR_TEMPERATURE); + object->acceleration -= gravity * object->mass * powf(temp, 2.0f) * temperatureFloatingForce; + +} + +void Solver::applyConstraints(std::shared_ptr object, float deltaTime) { + if (ENABLE_CIRCLE_CONSTRAINT) { + const raylib::Vector2 direction = object->position - centerCirclePosition; + float distance = direction.Length(); + + if (ENABLE_TEMPERATURE) { + if (ENABLE_HEAT_FROM_CIRCLE_BORDER) { + // Transfer heat from the border of the circle + if (distance >= CENTER_CIRCLE_RADIUS - object->radius * 4) { + auto deltaTemperature = (object->temperature - GROUND_TEMPERATURE) * temperatureTransferGroundMultiplier; + object->temperature -= deltaTemperature; + } + } else { + // Transfer heat from the bottom of the circle + auto distanceFromBottom = object->position - raylib::Vector2{ centerCirclePosition.x, centerCirclePosition.y + CENTER_CIRCLE_CURRENT_RADIUS }; + if (distanceFromBottom.Length() <= object->radius * 14) { + auto deltaTemperature = (object->temperature - GROUND_TEMPERATURE) * temperatureTransferGroundMultiplier; + object->temperature -= deltaTemperature; + } + } + } + + if (distance > CENTER_CIRCLE_CURRENT_RADIUS - object->radius) { + auto normal = direction / distance; + object->position = centerCirclePosition + normal * (CENTER_CIRCLE_CURRENT_RADIUS - object->radius); + } + } + + // Keep the object inside the screen + const raylib::Vector2 screen((float) WIDTH, (float) HEIGHT); + if (object->position.x - object->radius < 0.0f) { + object->position.x = object->radius; + } else if (object->position.x + object->radius > screen.x) { + object->position.x = screen.x - object->radius; + } + + if (object->position.y - object->radius < 0.0f) { + object->position.y = object->radius; + } else if (object->position.y + object->radius > screen.y) { + object->position.y = screen.y - object->radius; + } + + // Transfer heat from the ground + if (ENABLE_TEMPERATURE && !ENABLE_CIRCLE_CONSTRAINT && object->position.y + object->radius >= screen.y - object->radius * 4) { + // Transfer temperature from the ground + auto deltaTemperature = (object->temperature - GROUND_TEMPERATURE) * temperatureTransferGroundMultiplier; + object->temperature -= deltaTemperature; + } +} + +void Solver::solveCollisions(std::shared_ptr object) { + auto padding = 3.0f * object->radius; + auto others = quadtree.query(raylib::Rectangle{ object->position.x - padding, object->position.y - padding, padding * 2.0f, padding * 2.0f }); + bool collision = false; + + // Use private scope and parallelize for loop + for (auto other : others) { + iterationSum++; + if (other == object) continue; + + const raylib::Vector2 direction = object->position - other->position; + float distance = direction.LengthSqr(); + if (distance < powf(object->radius + other->radius, 2.0f)) { + distance = sqrtf(distance); + collision = true; + auto normal = direction / distance; + auto delta = (object->radius + other->radius) - distance; + object->position += normal * 0.5f * delta; + other->position -= normal * 0.5f * delta; + + // Transfer temperature between objects slightly 10% of the time + if (ENABLE_TEMPERATURE) { + auto deltaTemperature = (object->temperature - other->temperature) * temperatureTransferContactMultiplier; + object->temperature -= deltaTemperature; + other->temperature += deltaTemperature; + } + } + } + + if (!collision && ENABLE_TEMPERATURE) { + // Cool temperature transfering to air + auto deltaTemperature = (object->temperature - AIR_TEMPERATURE) * temperatureTransferAirMultiplier; + object->temperature -= deltaTemperature; + } +} + +void Solver::solve(float deltaTime) { + interpolateCenterCircleRadius(); + + if (iterationCount >= 100) { + iterationCount = 0; + iterationSum = 0; + } + + float dt = deltaTime / float(substeps); + for (uint8_t i = 0; i < substeps; i++) { + for (auto object : quadtree.getAll()) { + applyGravity(object, dt); + solveCollisions(object); + applyConstraints(object, dt); + + object->update(dt); + quadtree.update(object); + } + } + + iterationCount++; +} \ No newline at end of file diff --git a/src/core/solver.hpp b/src/core/models/solver.hpp similarity index 96% rename from src/core/solver.hpp rename to src/core/models/solver.hpp index 40c3ec0..c65c4bf 100644 --- a/src/core/solver.hpp +++ b/src/core/models/solver.hpp @@ -1,7 +1,7 @@ #pragma once +#include "../precomp.hpp" #include "object.hpp" -#include "precomp.hpp" #include "quadtree.hpp" class Solver { diff --git a/src/core/precomp.hpp b/src/core/precomp.hpp index 3df768d..90852e5 100644 --- a/src/core/precomp.hpp +++ b/src/core/precomp.hpp @@ -8,7 +8,6 @@ #ifdef PLATFORM_WEB #include #include -#else #endif // GUI diff --git a/src/core/settings.cpp b/src/core/settings.cpp new file mode 100644 index 0000000..2b0431f --- /dev/null +++ b/src/core/settings.cpp @@ -0,0 +1,11 @@ +#include "settings.hpp" + +bool ENABLE_CIRCLE_CONSTRAINT = true; +float CENTER_CIRCLE_CURRENT_RADIUS = CENTER_CIRCLE_RADIUS; +bool ENABLE_SPAWN_SPREAD = false; +bool ENABLE_HEAT_FROM_CIRCLE_BORDER = false; +bool ENABLE_SOLID_COLOR = false; +bool ENABLE_TEMPERATURE = false; +bool ENABLE_FIXED_RAINBOW = true; +bool ENABLE_RAINBOW_COLORS = false; +bool ENABLE_AUTO_ADJUST_SUBSTEPS = true; \ No newline at end of file diff --git a/src/core/constants.hpp b/src/core/settings.hpp similarity index 50% rename from src/core/constants.hpp rename to src/core/settings.hpp index 3ed3e5e..4d636bc 100644 --- a/src/core/constants.hpp +++ b/src/core/settings.hpp @@ -14,21 +14,35 @@ const unsigned int MAX_SUBDIVISIONS = 5; const float OBJECT_RADIUS = 10.0f; const unsigned int SPAWN_COUNT = 500; #else - const float OBJECT_RADIUS = 6.0f; - const unsigned int SPAWN_COUNT = 1000; + const float OBJECT_RADIUS = 10.0f; + const unsigned int SPAWN_COUNT = 800; #endif -const float SPAWN_INTERVAL = 0.05f; +const float SPAWN_INTERVAL = 0.07f; const float MANUAL_SPAWN_INTERVAL = 0.25f; +// Constants - Temperature +const float AIR_TEMPERATURE = 4000.0f; +const float GROUND_TEMPERATURE = 10000.0f; + // Constants - Interaction const float CENTER_CIRCLE_RADIUS = 400.0f; const float DRAGGING_ACCELERATION = 35000.0f; const float EXPLOSION_FORCE = 1500000.0f; const float EXPLOSION_RADIUS = 50.0f; -// Flags // #define QUADTREE_DUMMY -#define CENTER_CIRCLE -constexpr bool ENABLE_TEMPERATURE = false; -constexpr bool ENABLE_RAINBOW = true; -constexpr bool ENABLE_SPAWN_SPREAD = false; + +// Flags - Constraints +extern float CENTER_CIRCLE_CURRENT_RADIUS; +extern bool ENABLE_CIRCLE_CONSTRAINT; + +// Flags - Render mode +extern bool ENABLE_HEAT_FROM_CIRCLE_BORDER; +extern bool ENABLE_TEMPERATURE; +extern bool ENABLE_SOLID_COLOR; +extern bool ENABLE_FIXED_RAINBOW; +extern bool ENABLE_RAINBOW_COLORS; +extern bool ENABLE_AUTO_ADJUST_SUBSTEPS; + +// Flags - Spawning +extern bool ENABLE_SPAWN_SPREAD; diff --git a/src/core/solver.cpp b/src/core/solver.cpp deleted file mode 100644 index 69560b9..0000000 --- a/src/core/solver.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "solver.hpp" -#include "constants.hpp" - -const float AIR_TEMPERATURE = 4000.0f; -const float GROUND_TEMPERATURE = 10000.0f; - -Solver::Solver(Quadtree &quadtree) : quadtree(quadtree) { - centerCirclePosition = raylib::Vector2{ WIDTH / 2.0f, HEIGHT / 2.0f }; -} - -void Solver::applyGravity(std::shared_ptr object, float deltaTime) { - object->acceleration += gravity * object->mass; - - - if constexpr (!ENABLE_TEMPERATURE) return; - - // The more heat, the more upwards acceleration - auto temp = (object->temperature - AIR_TEMPERATURE) / (GROUND_TEMPERATURE - AIR_TEMPERATURE); - object->acceleration -= gravity * object->mass * powf(temp, 2.0f) * temperatureFloatingForce; - -} - -void Solver::applyConstraints(std::shared_ptr object, float deltaTime) { -#ifdef CENTER_CIRCLE - const raylib::Vector2 direction = object->position - centerCirclePosition; - float distance = direction.Length(); - - if constexpr (ENABLE_TEMPERATURE) { - // Transfer heat from the border of the circle - /* if (distance >= CENTER_CIRCLE_RADIUS - object->radius * 4) { - auto deltaTemperature = (object->temperature - GROUND_TEMPERATURE) * temperatureTransferGroundMultiplier; - object->temperature -= deltaTemperature; - } */ - - // Transfer heat from the bottom of the circle - auto distanceFromBottom = object->position - raylib::Vector2{ centerCirclePosition.x, centerCirclePosition.y + CENTER_CIRCLE_RADIUS }; - if (distanceFromBottom.Length() <= object->radius * 14) { - auto deltaTemperature = (object->temperature - GROUND_TEMPERATURE) * temperatureTransferGroundMultiplier; - object->temperature -= deltaTemperature; - } - } - - if (distance > CENTER_CIRCLE_RADIUS - object->radius) { - auto normal = direction / distance; - object->position = centerCirclePosition + normal * (CENTER_CIRCLE_RADIUS - object->radius); - } -#else - const raylib::Vector2 screen((float) WIDTH, (float) HEIGHT); - if (object->position.x - object->radius < 0.0f) { - object->position.x = object->radius; - } else if (object->position.x + object->radius > screen.x) { - object->position.x = screen.x - object->radius; - } - - if (object->position.y - object->radius < 0.0f) { - object->position.y = object->radius; - } else if (object->position.y + object->radius > screen.y) { - object->position.y = screen.y - object->radius; - } - - if constexpr (!ENABLE_TEMPERATURE) return; - - if (object->position.y + object->radius >= screen.y - object->radius * 4) { - // Transfer temperature from the ground - auto deltaTemperature = (object->temperature - GROUND_TEMPERATURE) * temperatureTransferGroundMultiplier; - object->temperature -= deltaTemperature; - } -#endif -} - -void Solver::solveCollisions(std::shared_ptr object) { - auto padding = 3.0f * object->radius; - auto others = quadtree.query(raylib::Rectangle{ object->position.x - padding, object->position.y - padding, padding * 2.0f, padding * 2.0f }); - bool collision = false; - - // Use private scope and parallelize for loop - for (auto other : others) { - iterationSum++; - if (other == object) continue; - - const raylib::Vector2 direction = object->position - other->position; - float distance = direction.LengthSqr(); - if (distance < powf(object->radius + other->radius, 2.0f)) { - distance = sqrtf(distance); - collision = true; - auto normal = direction / distance; - auto delta = (object->radius + other->radius) - distance; - object->position += normal * 0.5f * delta; - other->position -= normal * 0.5f * delta; - - // Transfer temperature between objects slightly 10% of the time - if constexpr (ENABLE_TEMPERATURE) { - auto deltaTemperature = (object->temperature - other->temperature) * temperatureTransferContactMultiplier; - object->temperature -= deltaTemperature; - other->temperature += deltaTemperature; - } - } - } - - if (!collision && ENABLE_TEMPERATURE) { - // Cool temperature transfering to air - auto deltaTemperature = (object->temperature - AIR_TEMPERATURE) * temperatureTransferAirMultiplier; - object->temperature -= deltaTemperature; - } -} - -uint32_t Solver::getAverageIterations() { - return iterationSum / iterationCount; -} - -void Solver::solve(float deltaTime) { - if (iterationCount >= 100) { - iterationCount = 0; - iterationSum = 0; - } - - float dt = deltaTime / float(substeps); - for (uint8_t i = 0; i < substeps; i++) { - for (auto object : quadtree.getAll()) { - applyGravity(object, dt); - solveCollisions(object); - applyConstraints(object, dt); - - object->update(dt); - quadtree.update(object); - } - } - - iterationCount++; -} \ No newline at end of file diff --git a/src/core/utils.hpp b/src/core/utils.hpp index 685718b..b3fbaaf 100644 --- a/src/core/utils.hpp +++ b/src/core/utils.hpp @@ -1,12 +1,13 @@ #pragma once #include "precomp.hpp" -#include "constants.hpp" +#include "settings.hpp" inline raylib::Vector2 getRelativeMousePosition() { + const auto dpi = GetWindowScaleDPI(); return raylib::Vector2( - std::clamp(GetMouseX() - GetRenderWidth() / 2.0f + WIDTH / 2.0f, 0.0f, (float)WIDTH), - std::clamp(GetMouseY() - GetRenderHeight() / 2.0f + HEIGHT / 2.0f, 0.0f, (float)HEIGHT) + std::clamp(GetMouseX() - GetScreenWidth() / 2.0f + WIDTH / 2.0f, 0.0f, (float)WIDTH), + std::clamp(GetMouseY() - GetScreenHeight() / 2.0f + HEIGHT / 2.0f, 0.0f, (float)HEIGHT) ); }