[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".gitignore",
    "content": "# Visual Studio Code settings\n.vscode/\n\n# Visual Studio settings\n.vs/\nCMakeSettings.json\n\n# Build directory\nbuild/\nGalaxyEngine/build/\n\n# Output directories\nGalaxyEngine/Videos\nGalaxyEngine/Saves\nGalaxyEngine/Screenshot_*\nGalaxyEngine/Screenshots/\nGalaxyEngine/compilerCommand.txt\nGalaxyEngine/include/Memory/\nGalaxyEngine/x64/\nx64/\n\n# Misc\n*.diff\n.clang-format\n.scripts/\n.github/\nSaves/\n\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.26..3.30 FATAL_ERROR)\n\nset(CMAKE_POLICY_DEFAULT_CMP0077 NEW)\ncmake_policy(SET CMP0077 NEW)\n\ncmake_policy(SET CMP0135 NEW)\n\nproject(GalaxyEngine)\n\nif(MSVC OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL \"MSVC\")\n    add_compile_options(\"$<$<CONFIG:Debug>:/Od;/RTC1;/MDd>\")\n    add_compile_options(\"$<$<CONFIG:Release>:/O2;/MD>\")\nendif()\n\ninclude(FetchContent)\n\ninclude(${CMAKE_CURRENT_LIST_DIR}/cmake/ffmpeg.cmake)\ninclude(${CMAKE_CURRENT_LIST_DIR}/cmake/glm.cmake)\ninclude(${CMAKE_CURRENT_LIST_DIR}/cmake/openmp.cmake)\ninclude(${CMAKE_CURRENT_LIST_DIR}/cmake/raylib.cmake)\ninclude(${CMAKE_CURRENT_LIST_DIR}/cmake/imgui.cmake)\ninclude(${CMAKE_CURRENT_LIST_DIR}/cmake/yaml.cmake)\n\nset(\n    GALAXYENGINE_SOURCES\n    main.cpp\n    globalLogic.cpp\n    parameters.cpp\n    Particles/particleSelection.cpp\n    Particles/particlesSpawning.cpp\n    Particles/particleSubdivision.cpp\n    Particles/particleTrails.cpp\n    Physics/morton.cpp\n    Physics/physics.cpp\n    Physics/physics3D.cpp\n    Physics/quadtree.cpp\n    Physics/slingshot.cpp\n    Physics/SPH.cpp\n    Physics/SPH3D.cpp\n    Physics/light.cpp\n    Sound/sound.cpp\n    UI/brush.cpp\n    UI/controls.cpp\n    UI/rightClickSettings.cpp\n    UI/UI.cpp\n    UX/camera.cpp\n    UX/saveSystem.cpp\n    UX/screenCapture.cpp\n    UX/randNum.cpp\n    resources.rc)\n\nlist(TRANSFORM GALAXYENGINE_SOURCES PREPEND ${CMAKE_CURRENT_LIST_DIR}/GalaxyEngine/src/)\n\nadd_executable(GalaxyEngine ${GALAXYENGINE_SOURCES})\n\nif(CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n    target_compile_options(GalaxyEngine PRIVATE -mavx2 -mfma)\nelseif(MSVC)\n    target_compile_options(GalaxyEngine PRIVATE /arch:AVX2)\nelse()\n    target_compile_options(GalaxyEngine PRIVATE -mavx2 -mfma)\nendif()\n\ntarget_precompile_headers(GalaxyEngine PUBLIC ${CMAKE_CURRENT_LIST_DIR}/GalaxyEngine/include/pch.h)\n\ntarget_include_directories(GalaxyEngine PUBLIC ${CMAKE_CURRENT_LIST_DIR}/GalaxyEngine/include)\n\ntarget_compile_features(GalaxyEngine PRIVATE cxx_std_20)\nset_target_properties(\n    GalaxyEngine PROPERTIES \n    MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>DLL\"\n    VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/GalaxyEngine\n)\n\ntarget_link_libraries(GalaxyEngine PUBLIC raylib-lib ffmpeg imgui glm-lib openmp yaml-cpp::yaml-cpp)\n\nif(WIN32)\n    add_custom_command(\n        TARGET GalaxyEngine POST_BUILD \n        COMMAND ${CMAKE_COMMAND} -E copy_directory \n        ${ffmpeg-fetch_SOURCE_DIR}/bin \n        $<TARGET_FILE_DIR:GalaxyEngine>\n    )\nendif()\n\nif(UNIX)\n    find_package(TBB REQUIRED)\n    target_link_libraries(GalaxyEngine PUBLIC TBB::tbb)\nendif()"
  },
  {
    "path": "CodeGuide.md",
    "content": "# Code Quick Guide\n\nThis is a quick guide for modding or contributing to the development of Galaxy Engine. Please try to follow this guide as much as possible. \n\nBefore starting to mod or contribute to Galaxy Engine, please check the [roadmap](https://github.com/users/NarcisCalin/projects/1/views/1).\n\nIf you want to contribute or mod Galaxy Engine and have any questions, consider joining our [Discord Community](https://discord.gg/Xd5JUqNFPM).\n\n## General\n\n- Galaxy Engine's code uses ***camelCase*** for everything except classes. Classes and structs use ***PascalCase***.\n- The code must be compatible with Windows and Linux before being added to the main branch.\n- The code must be compatible with the MIT license.\n- Various classes for different purposes can be added to the **\"UpdateParameters\"** struct in **\"parameters.h\"**. This is not necessary for important or complex classes like **\"SPH\"**, **\"Physics\"**, or **\"UI\"**.\n- Global variables go to the \"UpdateVariables\" struct in **\"parameters.h\"**.\n- Try to use the **\"UI::buttonHelper()\"** and **\"UI::sliderHelper()\"** functions when adding UI elements.\n- When you pass the \"UpdateParameters\" or \"UpdateVariables\" structs to a function, **ALWAYS** name them **\"myParam\"** and **\"myVar\"**.\n- saveSystem.h is meant for parameters that change how the simulation looks or behaves. For example, threads amount is not saved because it doesn't affect the physics or the visuals of the simulation, while trails length is saved because it affects how the simulation might look like with trails enabled.\n- Keep physics and similar features inside **\"updateScene();\"**.\n- Keep visual features (like UI, particle visuals, etc.) inside **\"drawScene();\"**.\n- For now, the current vector struct used in Galaxy Engine is **\"glm::vec2\"**.\n- Colors currently use Raylib's **\"Color\"** struct.\n- The current physics are built on top of a base framerate of 144 FPS.\n- Try to use float instead of double. There can be exceptions if needed like the **\"G\"** constant.\n- When asking for user keyboard input, use the custom IO::shortcut(key) from the io.h file instead of raylib's keyboard input.\n- Stuff from files like images, fonts, etc., that are loaded into memory must be unloaded when the program closes.\n- Before doing a pull request, you must check that your code runs both on Windows and Linux first. Please try to solve any warnings you might have before doing a pull request too.\n\n## Particles\n\n- Particles are composed of 2 structs.\n- **\"ParticlePhysics()\"** is used for physics calculations only.\n- **\"ParticleRendering()\"** is used for particle visuals like **\"color\"** or **\"size\"**, and distinctive attributes like **\"isDarkMatter\"** or **\"canBeResized\"**.\n- There is one std::vector for each struct. They are named **\"pParticles\"** and **\"rParticles\"**.\n- These std::vectors must always be synchronized, meaning that the position of a particle's **pParticle** at index **N**, must be the same number as its **rParticle**.\n- All particles are sorted spatially with Z-Curves. This means that particles' index inside a vector or array changes constantly between frames.\n- All particle struct changes must work with all the existing features. For example, make sure that the copy-paste or subdivision features works as intended with the new particle struct changes you make.\n\n## UI\n\n- The Galaxy Engine UI is made entirely using the [Dear ImGui](https://github.com/ocornut/imgui) library. Please check their documentation.\n- All ImGui UI elements must go after **\"rlImGuiBegin();\"** and before **\"rlImGuiEnd();\"**, which can be found in the main while loop.\n- Please try to keep all UI elements inside the **\"uiLogic()\"** function. There can be exceptions for certain features.\n- For most UI elements just keep the default ImGui font. For bigger text, please use the **\"Roboto-Medium\"** font. This can be found inside **\"UpdateVariables\"**.\n- Keep global buttons in the menu on the right.\n- To use Roboto-Medium or change font size for a window, you can do it like this:\n\n![image](https://github.com/user-attachments/assets/4f70e09d-cbd8-46ae-a960-96cb5a9f57c4)\n"
  },
  {
    "path": "GalaxyEngine/Config/config.txt",
    "content": "Global Volume: 0.400000006\nMenu Volume: 0.25\nMusic Volume: 0.400000006\nMessage Index: 3"
  },
  {
    "path": "GalaxyEngine/Shaders/bloom.fs",
    "content": "#version 330\n\nin vec2 fragTexCoord;\nin vec4 fragColor;\n\nuniform sampler2D texture0;\nuniform vec4 colDiffuse;\nuniform vec2 res;\n\nuniform int glowSize;\nuniform float glowStrength;\n\nout vec4 finalColor;\n\nvoid main()\n{\n    vec4 texColor = texture(texture0, fragTexCoord);\n\n    vec4 nFrag = vec4(0.0);\nfloat weightSum = 0.0;\n\nfor(int dx = -glowSize; dx <= glowSize; dx++){\n    for(int dy = -glowSize; dy <= glowSize; dy++){\n        vec2 offset = vec2(float(dx), float(dy)) / res;\n        float weight = 1.0 - (abs(float(dx)) + abs(float(dy))) / (2.0 * float(glowSize) + 1.0) * 0.5;\n        nFrag += texture(texture0, fragTexCoord + offset) * weight;\n        weightSum += weight;\n    }\n}\n\nnFrag /= weightSum;\n\n    finalColor = texColor + nFrag * glowStrength;\n}"
  },
  {
    "path": "GalaxyEngine/Shaders/cubemap.fs",
    "content": "#version 100\n\nprecision mediump float;\n\n// Input vertex attributes (from vertex shader)\nvarying vec3 fragPosition;\n\n// Input uniform values\nuniform sampler2D equirectangularMap;\n\nvec2 SampleSphericalMap(vec3 v)\n{\n    vec2 uv = vec2(atan(v.z, v.x), asin(v.y));\n    uv *= vec2(0.1591, 0.3183);\n    uv += 0.5;\n    return uv;\n}\n\nvoid main()\n{\n    // Normalize local position\n    vec2 uv = SampleSphericalMap(normalize(fragPosition));\n\n    // Fetch color from texture map\n    vec3 color = texture2D(equirectangularMap, uv).rgb;\n\n    // Calculate final fragment color\n    gl_FragColor = vec4(color, 1.0);\n}\n"
  },
  {
    "path": "GalaxyEngine/Shaders/cubemap.vs",
    "content": "#version 100\n\n// Input vertex attributes\nattribute vec3 vertexPosition;\n\n// Input uniform values\nuniform mat4 matProjection;\nuniform mat4 matView;\n\n// Output vertex attributes (to fragment shader)\nvarying vec3 fragPosition;\n\nvoid main()\n{\n    // Calculate fragment position based on model transformations\n    fragPosition = vertexPosition;\n\n    // Calculate final vertex position\n    gl_Position = matProjection*matView*vec4(vertexPosition, 1.0);\n}\n"
  },
  {
    "path": "GalaxyEngine/Shaders/skybox.fs",
    "content": "#version 330\n\n// Input vertex attributes (from vertex shader)\nin vec3 fragPosition;\n\n// Input uniform values\nuniform samplerCube environmentMap;\nuniform bool vflipped;\nuniform bool doGamma;\n\n// Output fragment color\nout vec4 finalColor;\n\nvoid main()\n{\n    // Fetch color from texture map\n    vec3 color = vec3(0.0);\n\n    if (vflipped) color = texture(environmentMap, vec3(fragPosition.x, -fragPosition.y, fragPosition.z)).rgb;\n    else color = texture(environmentMap, fragPosition).rgb;\n\n    if (doGamma)// Apply gamma correction\n    {\n        color = color/(color + vec3(1.0));\n        color = pow(color, vec3(1.0/2.2));\n    }\n\n    // Calculate final fragment color\n    finalColor = vec4(color, 1.0);\n}\n"
  },
  {
    "path": "GalaxyEngine/Shaders/skybox.vs",
    "content": "#version 330\n\n// Input vertex attributes\nin vec3 vertexPosition;\n\n// Input uniform values\nuniform mat4 matProjection;\nuniform mat4 matView;\n\n// Output vertex attributes (to fragment shader)\nout vec3 fragPosition;\n\nvoid main()\n{\n    // Calculate fragment position based on model transformations\n    fragPosition = vertexPosition;\n\n    // Remove translation from the view matrix\n    mat4 rotView = mat4(mat3(matView));\n    vec4 clipPos = matProjection*rotView*vec4(vertexPosition, 1.0);\n\n    // Calculate final vertex position\n    gl_Position = clipPos;\n}\n"
  },
  {
    "path": "GalaxyEngine/fonts/binary_to_compressed_c.cpp",
    "content": "// dear imgui\n// (binary_to_compressed_c.cpp)\n// Helper tool to turn a file into a C array, if you want to embed font data in your source code.\n\n// The data is first compressed with stb_compress() to reduce source code size.\n// Then stored in a C array:\n// - Base85:   ~5 bytes of source code for 4 bytes of input data. 5 bytes stored in binary (suggested by @mmalex).\n// - As int:  ~11 bytes of source code for 4 bytes of input data. 4 bytes stored in binary. Endianness dependent, need swapping on big-endian CPU.\n// - As char: ~12 bytes of source code for 4 bytes of input data. 4 bytes stored in binary. Not endianness dependent.\n// Load compressed TTF fonts with ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF()\n\n// Build with, e.g:\n//   # cl.exe binary_to_compressed_c.cpp\n//   # g++ binary_to_compressed_c.cpp\n//   # clang++ binary_to_compressed_c.cpp\n// You can also find a precompiled Windows binary in the binary/demo package available from https://github.com/ocornut/imgui\n\n// Usage:\n//   binary_to_compressed_c.exe [-nocompress] [-nostatic] [-base85] <inputfile> <symbolname>\n// Usage example:\n//   # binary_to_compressed_c.exe myfont.ttf MyFont > myfont.cpp\n//   # binary_to_compressed_c.exe -base85 myfont.ttf MyFont > myfont.cpp\n// Note:\n//   Base85 encoding will be obsoleted by future version of Dear ImGui!\n\n#define _CRT_SECURE_NO_WARNINGS\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n\n// stb_compress* from stb.h - declaration\ntypedef unsigned int stb_uint;\ntypedef unsigned char stb_uchar;\nstb_uint stb_compress(stb_uchar* out, stb_uchar* in, stb_uint len);\n\nenum SourceEncoding\n{\n    SourceEncoding_U8,      // New default since 2024/11\n    SourceEncoding_U32,\n    SourceEncoding_Base85,\n};\n\nstatic bool binary_to_compressed_c(const char* filename, const char* symbol, SourceEncoding source_encoding, bool use_compression, bool use_static);\n\nint main(int argc, char** argv)\n{\n    if (argc < 3)\n    {\n        printf(\"Syntax: %s [-u8|-u32|-base85] [-nocompress] [-nostatic] <inputfile> <symbolname>\\n\", argv[0]);\n        printf(\"Source encoding types:\\n\");\n        printf(\" -u8     = ~12 bytes of source per 4 bytes of data. 4 bytes in binary.\\n\");\n        printf(\" -u32    = ~11 bytes of source per 4 bytes of data. 4 bytes in binary. Need endianness swapping on big-endian.\\n\");\n        printf(\" -base85 =  ~5 bytes of source per 4 bytes of data. 5 bytes in binary. Need decoder.\\n\");\n        return 0;\n    }\n\n    int argn = 1;\n    bool use_compression = true;\n    bool use_static = true;\n    SourceEncoding source_encoding = SourceEncoding_U8; // New default\n    while (argn < (argc - 2) && argv[argn][0] == '-')\n    {\n        if (strcmp(argv[argn], \"-u8\") == 0) { source_encoding = SourceEncoding_U8; argn++; }\n        else if (strcmp(argv[argn], \"-u32\") == 0) { source_encoding = SourceEncoding_U32; argn++; }\n        else if (strcmp(argv[argn], \"-base85\") == 0) { source_encoding = SourceEncoding_Base85; argn++; }\n        else if (strcmp(argv[argn], \"-nocompress\") == 0) { use_compression = false; argn++; }\n        else if (strcmp(argv[argn], \"-nostatic\") == 0) { use_static = false; argn++; }\n        else\n        {\n            fprintf(stderr, \"Unknown argument: '%s'\\n\", argv[argn]);\n            return 1;\n        }\n    }\n\n    bool ret = binary_to_compressed_c(argv[argn], argv[argn + 1], source_encoding, use_compression, use_static);\n    if (!ret)\n        fprintf(stderr, \"Error opening or reading file: '%s'\\n\", argv[argn]);\n    return ret ? 0 : 1;\n}\n\nchar Encode85Byte(unsigned int x)\n{\n    x = (x % 85) + 35;\n    return (char)((x >= '\\\\') ? x + 1 : x);\n}\n\nbool binary_to_compressed_c(const char* filename, const char* symbol, SourceEncoding source_encoding, bool use_compression, bool use_static)\n{\n    // Read file\n    FILE* f = fopen(filename, \"rb\");\n    if (!f) return false;\n    int data_sz;\n    if (fseek(f, 0, SEEK_END) || (data_sz = (int)ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) { fclose(f); return false; }\n    char* data = new char[data_sz + 4];\n    if (fread(data, 1, data_sz, f) != (size_t)data_sz) { fclose(f); delete[] data; return false; }\n    memset((void*)(((char*)data) + data_sz), 0, 4);\n    fclose(f);\n\n    // Compress\n    int maxlen = data_sz + 512 + (data_sz >> 2) + sizeof(int); // total guess\n    char* compressed = use_compression ? new char[maxlen] : data;\n    int compressed_sz = use_compression ? stb_compress((stb_uchar*)compressed, (stb_uchar*)data, data_sz) : data_sz;\n    if (use_compression)\n        memset(compressed + compressed_sz, 0, maxlen - compressed_sz);\n\n    // Output as Base85 encoded\n    FILE* out = stdout;\n    fprintf(out, \"// File: '%s' (%d bytes)\\n\", filename, (int)data_sz);\n    const char* static_str = use_static ? \"static \" : \"\";\n    const char* compressed_str = use_compression ? \"compressed_\" : \"\";\n    if (source_encoding == SourceEncoding_Base85)\n    {\n        fprintf(out, \"// Exported using binary_to_compressed_c.exe -base85 \\\"%s\\\" %s\\n\", filename, symbol);\n        fprintf(out, \"%sconst char %s_%sdata_base85[%d+1] =\\n    \\\"\", static_str, symbol, compressed_str, (int)((compressed_sz + 3) / 4)*5);\n        char prev_c = 0;\n        for (int src_i = 0; src_i < compressed_sz; src_i += 4)\n        {\n            // This is made a little more complicated by the fact that ??X sequences are interpreted as trigraphs by old C/C++ compilers. So we need to escape pairs of ??.\n            unsigned int d = *(unsigned int*)(compressed + src_i);\n            for (unsigned int n5 = 0; n5 < 5; n5++, d /= 85)\n            {\n                char c = Encode85Byte(d);\n                fprintf(out, (c == '?' && prev_c == '?') ? \"\\\\%c\" : \"%c\", c);\n                prev_c = c;\n            }\n            if ((src_i % 112) == 112 - 4)\n                fprintf(out, \"\\\"\\n    \\\"\");\n        }\n        fprintf(out, \"\\\";\\n\\n\");\n    }\n    else if (source_encoding == SourceEncoding_U8)\n    {\n        // As individual bytes, not subject to endianness issues.\n        fprintf(out, \"// Exported using binary_to_compressed_c.exe -u8 \\\"%s\\\" %s\\n\", filename, symbol);\n        fprintf(out, \"%sconst unsigned int %s_%ssize = %d;\\n\", static_str, symbol, compressed_str, (int)compressed_sz);\n        fprintf(out, \"%sconst unsigned char %s_%sdata[%d] =\\n{\", static_str, symbol, compressed_str, (int)compressed_sz);\n        int column = 0;\n        for (int i = 0; i < compressed_sz; i++)\n        {\n            unsigned char d = *(unsigned char*)(compressed + i);\n            if (column == 0)\n                fprintf(out, \"\\n    \");\n            column += fprintf(out, \"%d,\", d);\n            if (column >= 180)\n                column = 0;\n        }\n        fprintf(out, \"\\n};\\n\\n\");\n    }\n    else if (source_encoding == SourceEncoding_U32)\n    {\n        // As integers\n        fprintf(out, \"// Exported using binary_to_compressed_c.exe -u32 \\\"%s\\\" %s\\n\", filename, symbol);\n        fprintf(out, \"%sconst unsigned int %s_%ssize = %d;\\n\", static_str, symbol, compressed_str, (int)compressed_sz);\n        fprintf(out, \"%sconst unsigned int %s_%sdata[%d/4] =\\n{\", static_str, symbol, compressed_str, (int)((compressed_sz + 3) / 4)*4);\n        int column = 0;\n        for (int i = 0; i < compressed_sz; i += 4)\n        {\n            unsigned int d = *(unsigned int*)(compressed + i);\n            if ((column++ % 14) == 0)\n                fprintf(out, \"\\n    0x%08x, \", d);\n            else\n                fprintf(out, \"0x%08x, \", d);\n        }\n        fprintf(out, \"\\n};\\n\\n\");\n    }\n\n    // Cleanup\n    delete[] data;\n    if (use_compression)\n        delete[] compressed;\n    return true;\n}\n\n// stb_compress* from stb.h - definition\n\n////////////////////           compressor         ///////////////////////\n\nstatic stb_uint stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uint buflen)\n{\n    const unsigned long ADLER_MOD = 65521;\n    unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16;\n    unsigned long blocklen, i;\n\n    blocklen = buflen % 5552;\n    while (buflen) {\n        for (i=0; i + 7 < blocklen; i += 8) {\n            s1 += buffer[0], s2 += s1;\n            s1 += buffer[1], s2 += s1;\n            s1 += buffer[2], s2 += s1;\n            s1 += buffer[3], s2 += s1;\n            s1 += buffer[4], s2 += s1;\n            s1 += buffer[5], s2 += s1;\n            s1 += buffer[6], s2 += s1;\n            s1 += buffer[7], s2 += s1;\n\n            buffer += 8;\n        }\n\n        for (; i < blocklen; ++i)\n            s1 += *buffer++, s2 += s1;\n\n        s1 %= ADLER_MOD, s2 %= ADLER_MOD;\n        buflen -= blocklen;\n        blocklen = 5552;\n    }\n    return (s2 << 16) + s1;\n}\n\nstatic unsigned int stb_matchlen(stb_uchar *m1, stb_uchar *m2, stb_uint maxlen)\n{\n    stb_uint i;\n    for (i=0; i < maxlen; ++i)\n        if (m1[i] != m2[i]) return i;\n    return i;\n}\n\n// simple implementation that just takes the source data in a big block\n\nstatic stb_uchar *stb__out;\nstatic FILE      *stb__outfile;\nstatic stb_uint   stb__outbytes;\n\nstatic void stb__write(unsigned char v)\n{\n    fputc(v, stb__outfile);\n    ++stb__outbytes;\n}\n\n//#define stb_out(v)    (stb__out ? *stb__out++ = (stb_uchar) (v) : stb__write((stb_uchar) (v)))\n#define stb_out(v)    do { if (stb__out) *stb__out++ = (stb_uchar) (v); else stb__write((stb_uchar) (v)); } while (0)\n\nstatic void stb_out2(stb_uint v) { stb_out(v >> 8); stb_out(v); }\nstatic void stb_out3(stb_uint v) { stb_out(v >> 16); stb_out(v >> 8); stb_out(v); }\nstatic void stb_out4(stb_uint v) { stb_out(v >> 24); stb_out(v >> 16); stb_out(v >> 8 ); stb_out(v); }\n\nstatic void outliterals(stb_uchar *in, int numlit)\n{\n    while (numlit > 65536) {\n        outliterals(in,65536);\n        in     += 65536;\n        numlit -= 65536;\n    }\n\n    if      (numlit ==     0)    ;\n    else if (numlit <=    32)    stb_out (0x000020 + numlit-1);\n    else if (numlit <=  2048)    stb_out2(0x000800 + numlit-1);\n    else /*  numlit <= 65536) */ stb_out3(0x070000 + numlit-1);\n\n    if (stb__out) {\n        memcpy(stb__out,in,numlit);\n        stb__out += numlit;\n    } else\n        fwrite(in, 1, numlit, stb__outfile);\n}\n\nstatic int stb__window = 0x40000; // 256K\n\nstatic int stb_not_crap(int best, int dist)\n{\n    return   ((best > 2  &&  dist <= 0x00100)\n        || (best > 5  &&  dist <= 0x04000)\n        || (best > 7  &&  dist <= 0x80000));\n}\n\nstatic  stb_uint stb__hashsize = 32768;\n\n// note that you can play with the hashing functions all you\n// want without needing to change the decompressor\n#define stb__hc(q,h,c)      (((h) << 7) + ((h) >> 25) + q[c])\n#define stb__hc2(q,h,c,d)   (((h) << 14) + ((h) >> 18) + (q[c] << 7) + q[d])\n#define stb__hc3(q,c,d,e)   ((q[c] << 14) + (q[d] << 7) + q[e])\n\nstatic unsigned int stb__running_adler;\n\nstatic int stb_compress_chunk(stb_uchar *history,\n    stb_uchar *start,\n    stb_uchar *end,\n    int length,\n    int *pending_literals,\n    stb_uchar **chash,\n    stb_uint mask)\n{\n    (void)history;\n    int window = stb__window;\n    stb_uint match_max;\n    stb_uchar *lit_start = start - *pending_literals;\n    stb_uchar *q = start;\n\n#define STB__SCRAMBLE(h)   (((h) + ((h) >> 16)) & mask)\n\n    // stop short of the end so we don't scan off the end doing\n    // the hashing; this means we won't compress the last few bytes\n    // unless they were part of something longer\n    while (q < start+length && q+12 < end) {\n        int m;\n        stb_uint h1,h2,h3,h4, h;\n        stb_uchar *t;\n        int best = 2, dist=0;\n\n        if (q+65536 > end)\n            match_max = (stb_uint)(end-q);\n        else\n            match_max = 65536;\n\n#define stb__nc(b,d)  ((d) <= window && ((b) > 9 || stb_not_crap((int)(b),(int)(d))))\n\n#define STB__TRY(t,p)  /* avoid retrying a match we already tried */ \\\n    if (p ? dist != (int)(q-t) : 1)                             \\\n    if ((m = stb_matchlen(t, q, match_max)) > best)     \\\n    if (stb__nc(m,q-(t)))                                \\\n    best = m, dist = (int)(q - (t))\n\n        // rather than search for all matches, only try 4 candidate locations,\n        // chosen based on 4 different hash functions of different lengths.\n        // this strategy is inspired by LZO; hashing is unrolled here using the\n        // 'hc' macro\n        h = stb__hc3(q,0, 1, 2); h1 = STB__SCRAMBLE(h);\n        t = chash[h1]; if (t) STB__TRY(t,0);\n        h = stb__hc2(q,h, 3, 4); h2 = STB__SCRAMBLE(h);\n        h = stb__hc2(q,h, 5, 6);        t = chash[h2]; if (t) STB__TRY(t,1);\n        h = stb__hc2(q,h, 7, 8); h3 = STB__SCRAMBLE(h);\n        h = stb__hc2(q,h, 9,10);        t = chash[h3]; if (t) STB__TRY(t,1);\n        h = stb__hc2(q,h,11,12); h4 = STB__SCRAMBLE(h);\n        t = chash[h4]; if (t) STB__TRY(t,1);\n\n        // because we use a shared hash table, can only update it\n        // _after_ we've probed all of them\n        chash[h1] = chash[h2] = chash[h3] = chash[h4] = q;\n\n        if (best > 2)\n            assert(dist > 0);\n\n        // see if our best match qualifies\n        if (best < 3) { // fast path literals\n            ++q;\n        } else if (best > 2  &&  best <= 0x80    &&  dist <= 0x100) {\n            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);\n            stb_out(0x80 + best-1);\n            stb_out(dist-1);\n        } else if (best > 5  &&  best <= 0x100   &&  dist <= 0x4000) {\n            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);\n            stb_out2(0x4000 + dist-1);\n            stb_out(best-1);\n        } else if (best > 7  &&  best <= 0x100   &&  dist <= 0x80000) {\n            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);\n            stb_out3(0x180000 + dist-1);\n            stb_out(best-1);\n        } else if (best > 8  &&  best <= 0x10000 &&  dist <= 0x80000) {\n            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);\n            stb_out3(0x100000 + dist-1);\n            stb_out2(best-1);\n        } else if (best > 9                      &&  dist <= 0x1000000) {\n            if (best > 65536) best = 65536;\n            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);\n            if (best <= 0x100) {\n                stb_out(0x06);\n                stb_out3(dist-1);\n                stb_out(best-1);\n            } else {\n                stb_out(0x04);\n                stb_out3(dist-1);\n                stb_out2(best-1);\n            }\n        } else {  // fallback literals if no match was a balanced tradeoff\n            ++q;\n        }\n    }\n\n    // if we didn't get all the way, add the rest to literals\n    if (q-start < length)\n        q = start+length;\n\n    // the literals are everything from lit_start to q\n    *pending_literals = (int)(q - lit_start);\n\n    stb__running_adler = stb_adler32(stb__running_adler, start, (stb_uint)(q - start));\n    return (int)(q - start);\n}\n\nstatic int stb_compress_inner(stb_uchar *input, stb_uint length)\n{\n    int literals = 0;\n    stb_uint len,i;\n\n    stb_uchar **chash;\n    chash = (stb_uchar**) malloc(stb__hashsize * sizeof(stb_uchar*));\n    if (chash == nullptr) return 0; // failure\n    for (i=0; i < stb__hashsize; ++i)\n        chash[i] = nullptr;\n\n    // stream signature\n    stb_out(0x57); stb_out(0xbc);\n    stb_out2(0);\n\n    stb_out4(0);       // 64-bit length requires 32-bit leading 0\n    stb_out4(length);\n    stb_out4(stb__window);\n\n    stb__running_adler = 1;\n\n    len = stb_compress_chunk(input, input, input+length, length, &literals, chash, stb__hashsize-1);\n    assert(len == length);\n\n    outliterals(input+length - literals, literals);\n\n    free(chash);\n\n    stb_out2(0x05fa); // end opcode\n\n    stb_out4(stb__running_adler);\n\n    return 1; // success\n}\n\nstb_uint stb_compress(stb_uchar *out, stb_uchar *input, stb_uint length)\n{\n    stb__out = out;\n    stb__outfile = nullptr;\n\n    stb_compress_inner(input, length);\n\n    return (stb_uint)(stb__out - out);\n}\n"
  },
  {
    "path": "GalaxyEngine/include/IO/io.h",
    "content": "#pragma once\n\n// Input/Output utility functions\nnamespace IO {\n    \n    // Handle keyboard shortcuts with ImGui integration\n    static inline bool shortcutPress(int key) {\n        ImGuiIO& io = ImGui::GetIO();\n        if (io.WantCaptureKeyboard) {\n            return false;\n        }\n        return IsKeyPressed(key);\n    }\n\n    static inline bool shortcutDown(int key) {\n        ImGuiIO& io = ImGui::GetIO();\n        if (io.WantCaptureKeyboard) {\n            return false;\n        }\n        return IsKeyDown(key);\n    }\n\n    static inline bool shortcutReleased(int key) {\n        ImGuiIO& io = ImGui::GetIO();\n        if (io.WantCaptureKeyboard) {\n            return false;\n        }\n        return IsKeyReleased(key);\n    }\n\n    static inline bool mousePress(int key) {\n        ImGuiIO& io = ImGui::GetIO();\n        if (io.WantCaptureMouse) {\n            return false;\n        }\n        return IsMouseButtonPressed(key);\n    }\n\n    static inline bool mouseDown(int key) {\n        ImGuiIO& io = ImGui::GetIO();\n        if (io.WantCaptureMouse) {\n            return false;\n        }\n        return IsMouseButtonDown(key);\n    }\n\n    static inline bool mouseReleased(int key) {\n        ImGuiIO& io = ImGui::GetIO();\n        if (io.WantCaptureMouse) {\n            return false;\n        }\n        return IsMouseButtonReleased(key);\n    }\n\n    // Additional IO utility functions can be added here in the future\n    // For example:\n    // - Mouse handling utilities\n    // - File IO helpers\n    // - Keyboard state management\n    // - Input validation functions\n}\n"
  },
  {
    "path": "GalaxyEngine/include/Particles/QueryNeighbors.h",
    "content": "#pragma once\n\n#include \"parameters.h\"\n\nstruct QueryNeighbors {\n\n\tstatic std::vector<size_t> queryNeighbors(UpdateParameters& myParam, bool& hasAVX2, size_t reserveAmount, glm::vec2& pos) {\n\t\tstd::vector<size_t> neighborIndices;\n\t\tneighborIndices.reserve(reserveAmount);\n\n\t\tif (!hasAVX2) {\n\t\t\tmyParam.neighborSearchV2.queryNeighbors(\n\t\t\t\tpos,\n\t\t\t\t[&](uint32_t idx) {\n\t\t\t\t\tneighborIndices.push_back(idx);\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\tmyParam.neighborSearchV2AVX2.queryNeighborsAVX2(\n\t\t\t\tpos,\n\t\t\t\tneighborIndices\n\t\t\t);\n\t\t}\n\n\t\treturn neighborIndices;\n\t}\n};\n\nstruct QueryNeighbors3D {\n\n\tstatic std::vector<size_t> queryNeighbors3D(UpdateParameters& myParam, bool& hasAVX2, size_t reserveAmount, glm::vec3& pos) {\n\t\tstd::vector<size_t> neighborIndices;\n\t\tneighborIndices.reserve(reserveAmount);\n\n\t\tif (!hasAVX2) {\n\t\t\tmyParam.neighborSearch3DV2.queryNeighbors(\n\t\t\t\tpos,\n\t\t\t\t[&](uint32_t idx) {\n\t\t\t\t\tneighborIndices.push_back(idx);\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\telse {\n\t\t\tmyParam.neighborSearch3DV2AVX2.queryNeighborsAVX2(\n\t\t\t\tpos,\n\t\t\t\tneighborIndices\n\t\t\t);\n\t\t}\n\n\t\treturn neighborIndices;\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/clusterMouseHelper.h",
    "content": "#pragma once\n\n#include \"Physics/quadtree.h\"\n\nstruct ClusterHelper {\n\tstatic void clusterMouseHelper(Camera3D& cam3D, float& dist) {\n\n\t\tRay camRay = GetScreenToWorldRay(GetMousePosition(), cam3D);\n\t\tfloat minDistSq = std::numeric_limits<float>::max();\n\t\tint nearestIdx = -1;\n\n\t\tfor (size_t i = 0; i < globalNodes3D.size(); i++) {\n\t\t\tNode3D& n = globalNodes3D[i];\n\n\t\t\tif (n.size > 16.0f || n.endIndex - n.startIndex < 16) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tVector3 bMin = { n.pos.x, n.pos.y, n.pos.z };\n\t\t\tVector3 bMax = { n.pos.x + n.size, n.pos.y + n.size, n.pos.z + n.size };\n\t\t\tBoundingBox bBox = { bMin, bMax };\n\t\t\tRayCollision rayColl = GetRayCollisionBox(camRay, bBox);\n\n\t\t\tif (rayColl.hit) {\n\n\t\t\t\tglm::vec3 d = n.centerOfMass - glm::vec3{ camRay.position.x, camRay.position.y, camRay.position.z };\n\t\t\t\tfloat distSq = glm::dot(d, d);\n\n\t\t\t\tif (distSq < minDistSq) {\n\t\t\t\t\tminDistSq = distSq;\n\t\t\t\t\tnearestIdx = static_cast<int>(i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (nearestIdx != -1) {\n\n\t\t\t/*DrawCubeWiresV({ nearest.pos.x, nearest.pos.y, nearest.pos.z },\n\t\t\t\t{ nearest.size, nearest.size, nearest.size },\n\t\t\t\t{ 128, 255, 128, 128 });*/\n\n\t\t\tdist = std::sqrt(minDistSq);\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/densitySize.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct DensitySize {\n\n\tfloat minSize = 0.17f;\n\tfloat maxSize = 0.62f;\n\n\tfloat sizeAcc = 22.0f;\n\n\tint maxNeighbors = 200;\n\n\tvoid sizeByDensity(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, \n\t\tstd::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D,\n\t\tbool& isDensitySizeEnabled, bool& isForceSizeEnabled,\n\t\tfloat& sizeMultiplier, bool is3DMode) {\n\n\t\tif (isForceSizeEnabled) {\n\t\t\tif (!is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\n\t\t\t\tfor (int64_t i = 0; i < pParticles.size(); i++) {\n\t\t\t\t\tif (rParticles[i].isSolid || rParticles[i].isDarkMatter) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat particleAccSq = pParticles[i].acc.x * pParticles[i].acc.x +\n\t\t\t\t\t\tpParticles[i].acc.y * pParticles[i].acc.y;\n\n\t\t\t\t\tfloat clampedAcc = std::clamp(sqrt(particleAccSq), 0.0f, sizeAcc);\n\t\t\t\t\tfloat normalizedAcc = clampedAcc / sizeAcc;\n\n\t\t\t\t\trParticles[i].size = Lerp(maxSize * sizeMultiplier, minSize * sizeMultiplier, normalizedAcc);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (int64_t i = 0; i < pParticles3D.size(); i++) {\n\t\t\t\t\tif (rParticles3D[i].isSolid || rParticles3D[i].isDarkMatter) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat particleAccSq = pParticles3D[i].acc.x * pParticles3D[i].acc.x +\n\t\t\t\t\t\tpParticles3D[i].acc.y * pParticles3D[i].acc.y + pParticles3D[i].acc.z * pParticles3D[i].acc.z;\n\n\t\t\t\t\tfloat clampedAcc = std::clamp(sqrt(particleAccSq), 0.0f, sizeAcc);\n\t\t\t\t\tfloat normalizedAcc = clampedAcc / sizeAcc;\n\n\t\t\t\t\trParticles3D[i].size = Lerp(maxSize * sizeMultiplier, minSize * sizeMultiplier, normalizedAcc);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (isDensitySizeEnabled) {\n\n\t\t\tif (!is3DMode) {\n\t\t\t\tstd::vector<int> neighborCounts(pParticles.size(), 0);\n#pragma omp parallel for schedule(dynamic)\n\t\t\t\tfor (int64_t i = 0; i < pParticles.size(); i++) {\n\n\t\t\t\t\tif (rParticles[i].isDarkMatter || rParticles[i].isSolid) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat normalDensity = std::min(float(rParticles[i].neighbors) / maxNeighbors, 1.0f);\n\n\t\t\t\t\trParticles[i].size = Lerp(maxSize * sizeMultiplier, minSize * sizeMultiplier, static_cast<float>(pow(normalDensity, 2)));\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstd::vector<int> neighborCounts(pParticles3D.size(), 0);\n#pragma omp parallel for schedule(dynamic)\n\t\t\t\tfor (int64_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\t\t\t\tif (rParticles3D[i].isDarkMatter || rParticles3D[i].isSolid) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat normalDensity = std::min(float(rParticles3D[i].neighbors) / maxNeighbors, 1.0f);\n\n\t\t\t\t\trParticles3D[i].size = Lerp(maxSize * sizeMultiplier, minSize * sizeMultiplier, static_cast<float>(pow(normalDensity, 2)));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/neighborSearch.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct NeighborSearch {\n\n\tstd::vector<uint32_t> globalNeighborList;\n\n\tfloat originalDensityRadius = 3.5f;\n\tfloat densityRadius = originalDensityRadius;\n\tfloat cellSize = 3.0f;\n\n\tstd::vector<size_t> cellCounts;\n\tstd::vector<size_t> cellStart;\n\tstd::vector<size_t> cellParticles;\n\tstd::vector<int> cellXList;\n\tstd::vector<int> cellYList;\n\n\tstd::vector<size_t> idToIndexTable;\n\n\tvoid calculateGridBounds(const std::vector<ParticlePhysics>& pParticles,\n\t\tconst std::vector<size_t>& activeIndices,\n\t\tint& gridWidth, int& gridHeight,\n\t\tfloat& minX, float& minY)\n\t{\n\n\t\tfloat maxX = std::numeric_limits<float>::lowest();\n\t\tfloat maxY = std::numeric_limits<float>::lowest();\n\t\tminX = std::numeric_limits<float>::max();\n\t\tminY = std::numeric_limits<float>::max();\n\n#pragma omp parallel for reduction(min:minX, minY) reduction(max:maxX, maxY)\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tconst auto& pos = pParticles[activeIndices[i]].pos;\n\t\t\tif (pos.x < minX) minX = pos.x;\n\t\t\tif (pos.y < minY) minY = pos.y;\n\t\t\tif (pos.x > maxX) maxX = pos.x;\n\t\t\tif (pos.y > maxY) maxY = pos.y;\n\t\t}\n\n\t\tminX -= cellSize; minY -= cellSize;\n\t\tmaxX += cellSize; maxY += cellSize;\n\n\t\tgridWidth = std::max(1, static_cast<int>((maxX - minX) / cellSize) + 1);\n\t\tgridHeight = std::max(1, static_cast<int>((maxY - minY) / cellSize) + 1);\n\t}\n\n\tvoid UpdateNeighbors(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles) {\n\t\tif (pParticles.empty()) return;\n\n\t\tfloat densityRadiusSq = densityRadius * densityRadius;\n\n\t\tstd::vector<size_t> activeIndices;\n\t\tactiveIndices.reserve(pParticles.size());\n\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\t\tif (!rParticles[i].isDarkMatter) {\n\t\t\t\tactiveIndices.push_back(i);\n\t\t\t}\n\t\t}\n\n\t\tint gridWidth, gridHeight;\n\t\tfloat minX, minY;\n\t\tcalculateGridBounds(pParticles, activeIndices, gridWidth, gridHeight, minX, minY);\n\n\t\tint numCells = gridWidth * gridHeight;\n\n\t\tif (cellCounts.size() < numCells) cellCounts.resize(numCells);\n\t\tstd::fill(cellCounts.begin(), cellCounts.end(), 0);\n\n\t\tif (cellXList.size() < activeIndices.size()) cellXList.resize(activeIndices.size());\n\t\tif (cellYList.size() < activeIndices.size()) cellYList.resize(activeIndices.size());\n\n#pragma omp parallel for\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tsize_t pIdx = activeIndices[i];\n\t\t\tconst auto& pos = pParticles[pIdx].pos;\n\n\t\t\tint cx = static_cast<int>((pos.x - minX) / cellSize);\n\t\t\tint cy = static_cast<int>((pos.y - minY) / cellSize);\n\n\t\t\tcx = std::max(0, std::min(cx, gridWidth - 1));\n\t\t\tcy = std::max(0, std::min(cy, gridHeight - 1));\n\n\t\t\tcellXList[i] = cx;\n\t\t\tcellYList[i] = cy;\n\n\t\t\tint cellIdx = cy * gridWidth + cx;\n#pragma omp atomic\n\t\t\tcellCounts[cellIdx]++;\n\t\t}\n\n\t\tif (cellStart.size() < numCells + 1) cellStart.resize(numCells + 1);\n\t\tcellStart[0] = 0;\n\t\tfor (int i = 0; i < numCells; i++) {\n\t\t\tcellStart[i + 1] = cellStart[i] + cellCounts[i];\n\t\t}\n\n\t\tif (cellParticles.size() < activeIndices.size()) cellParticles.resize(activeIndices.size());\n\n\t\tstd::vector<size_t> fillCursor = cellStart;\n\n#pragma omp parallel for\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tint cx = cellXList[i];\n\t\t\tint cy = cellYList[i];\n\t\t\tint cellIdx = cy * gridWidth + cx;\n\n\t\t\tsize_t writePos;\n#pragma omp atomic capture\n\t\t\twritePos = fillCursor[cellIdx]++;\n\n\t\t\tcellParticles[writePos] = activeIndices[i];\n\t\t}\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tsize_t pIdx = activeIndices[i];\n\t\t\tauto& pi = pParticles[pIdx];\n\n\t\t\trParticles[pIdx].neighbors = 0;\n\n\t\t\tint cx = cellXList[i];\n\t\t\tint cy = cellYList[i];\n\n\t\t\tfor (int ny = -1; ny <= 1; ++ny) {\n\t\t\t\tfor (int nx = -1; nx <= 1; ++nx) {\n\t\t\t\t\tint neighborCX = cx + nx;\n\t\t\t\t\tint neighborCY = cy + ny;\n\n\t\t\t\t\tif (neighborCX >= 0 && neighborCX < gridWidth &&\n\t\t\t\t\t\tneighborCY >= 0 && neighborCY < gridHeight) {\n\n\t\t\t\t\t\tint cellIdx = neighborCY * gridWidth + neighborCX;\n\t\t\t\t\t\tsize_t start = cellStart[cellIdx];\n\t\t\t\t\t\tsize_t end = cellStart[cellIdx + 1];\n\n\t\t\t\t\t\tfor (size_t k = start; k < end; ++k) {\n\t\t\t\t\t\t\tsize_t neighborIdx = cellParticles[k];\n\n\t\t\t\t\t\t\tif (neighborIdx == pIdx) continue;\n\n\t\t\t\t\t\t\tglm::vec2 d = pi.pos - pParticles[neighborIdx].pos;\n\t\t\t\t\t\t\tfloat distSq = d.x * d.x + d.y * d.y;\n\n\t\t\t\t\t\t\tif (distSq < densityRadiusSq) {\n\t\t\t\t\t\t\t\trParticles[pIdx].neighbors++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tuint32_t currentOffset = 0;\n\t\tfor (size_t i = 0; i < pParticles.size(); ++i) {\n\t\t\tpParticles[i].neighborOffset = currentOffset;\n\t\t\tcurrentOffset += rParticles[i].neighbors;\n\t\t}\n\n\t\tif (globalNeighborList.size() < currentOffset) {\n\t\t\tglobalNeighborList.resize(currentOffset);\n\t\t}\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tsize_t pIdx = activeIndices[i];\n\t\t\tauto& pi = pParticles[pIdx];\n\n\t\t\tuint32_t currentNeighborIndex = 0;\n\n\t\t\tint cx = cellXList[i];\n\t\t\tint cy = cellYList[i];\n\n\t\t\tfor (int ny = -1; ny <= 1; ++ny) {\n\t\t\t\tfor (int nx = -1; nx <= 1; ++nx) {\n\t\t\t\t\tint neighborCX = cx + nx;\n\t\t\t\t\tint neighborCY = cy + ny;\n\n\t\t\t\t\tif (neighborCX >= 0 && neighborCX < gridWidth &&\n\t\t\t\t\t\tneighborCY >= 0 && neighborCY < gridHeight) {\n\n\t\t\t\t\t\tint cellIdx = neighborCY * gridWidth + neighborCX;\n\t\t\t\t\t\tsize_t start = cellStart[cellIdx];\n\t\t\t\t\t\tsize_t end = cellStart[cellIdx + 1];\n\n\t\t\t\t\t\tfor (size_t k = start; k < end; ++k) {\n\t\t\t\t\t\t\tsize_t neighborIdx = cellParticles[k];\n\n\t\t\t\t\t\t\tif (neighborIdx == pIdx) continue;\n\n\t\t\t\t\t\t\tglm::vec2 d = pi.pos - pParticles[neighborIdx].pos;\n\t\t\t\t\t\t\tfloat distSq = d.x * d.x + d.y * d.y;\n\n\t\t\t\t\t\t\tif (distSq < densityRadiusSq) {\n\n\t\t\t\t\t\t\t\tglobalNeighborList[pi.neighborOffset + currentNeighborIndex] = pParticles[neighborIdx].id;\n\t\t\t\t\t\t\t\tcurrentNeighborIndex++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};\n\nstruct NeighborSearch3D {\n\n\tstd::vector<uint32_t> globalNeighborList3D;\n\n\tfloat originalDensityRadius = 3.5f;\n\tfloat densityRadius = originalDensityRadius;\n\tfloat cellSize = 3.5f;\n\n\tstd::vector<size_t> cellCounts;\n\tstd::vector<size_t> cellStart;\n\tstd::vector<size_t> cellParticles;\n\n\tstd::vector<int> cellXList;\n\tstd::vector<int> cellYList;\n\tstd::vector<int> cellZList;\n\n\tstd::vector<size_t> idToIndexTable;\n\n\tvoid calculateGridBounds(const std::vector<ParticlePhysics3D>& pParticles,\n\t\tconst std::vector<size_t>& activeIndices,\n\t\tint& gridWidth, int& gridHeight, int& gridDepth,\n\t\tfloat& minX, float& minY, float& minZ)\n\t{\n\t\tfloat maxX = std::numeric_limits<float>::lowest();\n\t\tfloat maxY = std::numeric_limits<float>::lowest();\n\t\tfloat maxZ = std::numeric_limits<float>::lowest();\n\n\t\tminX = std::numeric_limits<float>::max();\n\t\tminY = std::numeric_limits<float>::max();\n\t\tminZ = std::numeric_limits<float>::max();\n\n#pragma omp parallel for reduction(min:minX, minY, minZ) reduction(max:maxX, maxY, maxZ)\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tconst auto& pos = pParticles[activeIndices[i]].pos;\n\n\t\t\tif (pos.x < minX) minX = pos.x;\n\t\t\tif (pos.y < minY) minY = pos.y;\n\t\t\tif (pos.z < minZ) minZ = pos.z;\n\n\t\t\tif (pos.x > maxX) maxX = pos.x;\n\t\t\tif (pos.y > maxY) maxY = pos.y;\n\t\t\tif (pos.z > maxZ) maxZ = pos.z;\n\t\t}\n\n\t\tminX -= cellSize; minY -= cellSize; minZ -= cellSize;\n\t\tmaxX += cellSize; maxY += cellSize; maxZ += cellSize;\n\n\t\tgridWidth = std::max(1, static_cast<int>((maxX - minX) / cellSize) + 1);\n\t\tgridHeight = std::max(1, static_cast<int>((maxY - minY) / cellSize) + 1);\n\t\tgridDepth = std::max(1, static_cast<int>((maxZ - minZ) / cellSize) + 1);\n\t}\n\n\tvoid UpdateNeighbors(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {\n\t\tif (pParticles.empty()) return;\n\n\t\tfloat densityRadiusSq = densityRadius * densityRadius;\n\n\t\tstd::vector<size_t> activeIndices;\n\t\tactiveIndices.reserve(pParticles.size());\n\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\t\tif (!rParticles[i].isDarkMatter) {\n\t\t\t\tactiveIndices.push_back(i);\n\t\t\t}\n\t\t}\n\n\t\tint gridWidth, gridHeight, gridDepth;\n\t\tfloat minX, minY, minZ;\n\t\tcalculateGridBounds(pParticles, activeIndices, gridWidth, gridHeight, gridDepth, minX, minY, minZ);\n\n\t\tint numCells = gridWidth * gridHeight * gridDepth;\n\n\t\tif (cellCounts.size() < numCells) cellCounts.resize(numCells);\n\t\tstd::fill(cellCounts.begin(), cellCounts.end(), 0);\n\n\t\tif (cellXList.size() < activeIndices.size()) cellXList.resize(activeIndices.size());\n\t\tif (cellYList.size() < activeIndices.size()) cellYList.resize(activeIndices.size());\n\t\tif (cellZList.size() < activeIndices.size()) cellZList.resize(activeIndices.size());\n\n#pragma omp parallel for\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tsize_t pIdx = activeIndices[i];\n\t\t\tconst auto& pos = pParticles[pIdx].pos;\n\n\t\t\tint cx = static_cast<int>((pos.x - minX) / cellSize);\n\t\t\tint cy = static_cast<int>((pos.y - minY) / cellSize);\n\t\t\tint cz = static_cast<int>((pos.z - minZ) / cellSize);\n\n\t\t\tcx = std::max(0, std::min(cx, gridWidth - 1));\n\t\t\tcy = std::max(0, std::min(cy, gridHeight - 1));\n\t\t\tcz = std::max(0, std::min(cz, gridDepth - 1));\n\n\t\t\tcellXList[i] = cx;\n\t\t\tcellYList[i] = cy;\n\t\t\tcellZList[i] = cz;\n\n\t\t\tint cellIdx = cz * (gridWidth * gridHeight) + cy * gridWidth + cx;\n\n#pragma omp atomic\n\t\t\tcellCounts[cellIdx]++;\n\t\t}\n\n\t\tif (cellStart.size() < numCells + 1) cellStart.resize(numCells + 1);\n\t\tcellStart[0] = 0;\n\t\tfor (int i = 0; i < numCells; i++) {\n\t\t\tcellStart[i + 1] = cellStart[i] + cellCounts[i];\n\t\t}\n\n\t\tif (cellParticles.size() < activeIndices.size()) cellParticles.resize(activeIndices.size());\n\n\t\tstd::vector<size_t> fillCursor = cellStart;\n\n#pragma omp parallel for\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tint cx = cellXList[i];\n\t\t\tint cy = cellYList[i];\n\t\t\tint cz = cellZList[i];\n\n\t\t\tint cellIdx = cz * (gridWidth * gridHeight) + cy * gridWidth + cx;\n\n\t\t\tsize_t writePos;\n#pragma omp atomic capture\n\t\t\twritePos = fillCursor[cellIdx]++;\n\n\t\t\tcellParticles[writePos] = activeIndices[i];\n\t\t}\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tsize_t pIdx = activeIndices[i];\n\t\t\tauto& pi = pParticles[pIdx];\n\n\t\t\trParticles[pIdx].neighbors = 0;\n\n\t\t\tint cx = cellXList[i];\n\t\t\tint cy = cellYList[i];\n\t\t\tint cz = cellZList[i];\n\n\t\t\tfor (int nz = -1; nz <= 1; ++nz) {\n\n\t\t\t\tfor (int ny = -1; ny <= 1; ++ny) {\n\n\t\t\t\t\tfor (int nx = -1; nx <= 1; ++nx) {\n\n\t\t\t\t\t\tint neighborCX = cx + nx;\n\t\t\t\t\t\tint neighborCY = cy + ny;\n\t\t\t\t\t\tint neighborCZ = cz + nz;\n\n\t\t\t\t\t\tif (neighborCX >= 0 && neighborCX < gridWidth &&\n\t\t\t\t\t\t\tneighborCY >= 0 && neighborCY < gridHeight &&\n\t\t\t\t\t\t\tneighborCZ >= 0 && neighborCZ < gridDepth) {\n\n\t\t\t\t\t\t\tint cellIdx = neighborCZ * (gridWidth * gridHeight) + neighborCY * gridWidth + neighborCX;\n\n\t\t\t\t\t\t\tsize_t start = cellStart[cellIdx];\n\t\t\t\t\t\t\tsize_t end = cellStart[cellIdx + 1];\n\n\t\t\t\t\t\t\tfor (size_t k = start; k < end; ++k) {\n\t\t\t\t\t\t\t\tsize_t neighborIdx = cellParticles[k];\n\n\t\t\t\t\t\t\t\tif (neighborIdx == pIdx) continue;\n\n\t\t\t\t\t\t\t\tglm::vec3 d = pi.pos - pParticles[neighborIdx].pos;\n\t\t\t\t\t\t\t\tfloat distSq = d.x * d.x + d.y * d.y + d.z * d.z;\n\n\t\t\t\t\t\t\t\tif (distSq < densityRadiusSq) {\n\t\t\t\t\t\t\t\t\trParticles[pIdx].neighbors++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tuint32_t currentOffset = 0;\n\t\tfor (size_t i = 0; i < pParticles.size(); ++i) {\n\t\t\tpParticles[i].neighborOffset = currentOffset;\n\t\t\tcurrentOffset += rParticles[i].neighbors;\n\t\t}\n\n\t\tif (globalNeighborList3D.size() < currentOffset) {\n\t\t\tglobalNeighborList3D.resize(currentOffset);\n\t\t}\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {\n\t\t\tsize_t pIdx = activeIndices[i];\n\t\t\tauto& pi = pParticles[pIdx];\n\n\t\t\tuint32_t currentNeighborIndex = 0;\n\n\t\t\tint cx = cellXList[i];\n\t\t\tint cy = cellYList[i];\n\t\t\tint cz = cellZList[i];\n\n\t\t\tfor (int nz = -1; nz <= 1; ++nz) {\n\t\t\t\tfor (int ny = -1; ny <= 1; ++ny) {\n\t\t\t\t\tfor (int nx = -1; nx <= 1; ++nx) {\n\n\t\t\t\t\t\tint neighborCX = cx + nx;\n\t\t\t\t\t\tint neighborCY = cy + ny;\n\t\t\t\t\t\tint neighborCZ = cz + nz;\n\n\t\t\t\t\t\tif (neighborCX >= 0 && neighborCX < gridWidth &&\n\t\t\t\t\t\t\tneighborCY >= 0 && neighborCY < gridHeight &&\n\t\t\t\t\t\t\tneighborCZ >= 0 && neighborCZ < gridDepth) {\n\n\t\t\t\t\t\t\tint cellIdx = neighborCZ * (gridWidth * gridHeight) + neighborCY * gridWidth + neighborCX;\n\t\t\t\t\t\t\tsize_t start = cellStart[cellIdx];\n\t\t\t\t\t\t\tsize_t end = cellStart[cellIdx + 1];\n\n\t\t\t\t\t\t\tfor (size_t k = start; k < end; ++k) {\n\t\t\t\t\t\t\t\tsize_t neighborIdx = cellParticles[k];\n\n\t\t\t\t\t\t\t\tif (neighborIdx == pIdx) continue;\n\n\t\t\t\t\t\t\t\tglm::vec3 d = pi.pos - pParticles[neighborIdx].pos;\n\t\t\t\t\t\t\t\tfloat distSq = d.x * d.x + d.y * d.y + d.z * d.z;\n\n\t\t\t\t\t\t\t\tif (distSq < densityRadiusSq) {\n\t\t\t\t\t\t\t\t\tglobalNeighborList3D[pi.neighborOffset + currentNeighborIndex] = pParticles[neighborIdx].id;\n\t\t\t\t\t\t\t\t\tcurrentNeighborIndex++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};\n\nstruct NeighborSearchV2 {\n\n\tfloat searchRadius = 3.5f;\n\tfloat cellSize = 3.0f;\n\tfloat invCellSize = 1.0f / 3.0f;\n\n\tstruct EntryArrays {\n\t\tstd::vector<uint32_t> cellKeys;\n\t\tstd::vector<uint32_t> particleIndices;\n\n\t\tstd::vector<float> posX;\n\t\tstd::vector<float> posY;\n\n\t\tstd::vector<int> cellXs;\n\t\tstd::vector<int> cellYs;\n\n\t\tsize_t size;\n\t};\n\n\tconst uint32_t hashTableSize = 16384;\n\n\tEntryArrays entries;\n\n\tstd::vector<uint32_t> countBuffer;\n\tstd::vector<uint32_t> offsetBuffer;\n\tstd::vector<uint32_t> startIndices;\n\n\tNeighborSearchV2() {\n\t\tcountBuffer.resize(hashTableSize + 1);\n\t\toffsetBuffer.resize(hashTableSize + 1);\n\t\tstartIndices.resize(hashTableSize);\n\t}\n\n\tglm::ivec2 posToCellCoord(const glm::vec2& pos) const {\n\t\treturn glm::ivec2((int)(pos.x * invCellSize), (int)(pos.y * invCellSize));\n\t}\n\n\tuint32_t hashCell(int cellX, int cellY) const {\n\t\tuint32_t h = ((uint32_t)cellX * 73856093) ^ ((uint32_t)cellY * 19349663);\n\t\treturn h % hashTableSize;\n\t}\n\n\tvoid newGrid(const std::vector<ParticlePhysics>& pParticles) {\n\t\tconst size_t n = pParticles.size();\n\t\tif (n == 0) return;\n\n\t\tif (entries.cellKeys.size() < n) {\n\t\t\tentries.cellKeys.resize(n);\n\t\t\tentries.particleIndices.resize(n);\n\t\t\tentries.posX.resize(n);\n\t\t\tentries.posY.resize(n);\n\t\t\tentries.cellXs.resize(n);\n\t\t\tentries.cellYs.resize(n);\n\t\t}\n\t\tentries.size = n;\n\n\t\tstd::fill(countBuffer.begin(), countBuffer.end(), 0);\n\t\tstd::fill(startIndices.begin(), startIndices.end(), UINT32_MAX);\n\n\t\tfor (size_t i = 0; i < n; i++) {\n\t\t\tglm::ivec2 coord = posToCellCoord(pParticles[i].pos);\n\t\t\tuint32_t key = hashCell(coord.x, coord.y);\n\n\t\t\tentries.cellKeys[i] = key;\n\t\t\tentries.particleIndices[i] = i;\n\t\t\tentries.cellXs[i] = coord.x;\n\t\t\tentries.cellYs[i] = coord.y;\n\t\t}\n\n\t\tstatic std::vector<uint32_t> tempKeys;\n\t\tstatic std::vector<uint32_t> tempIndices;\n\t\tstatic std::vector<int> tempXs, tempYs;\n\n\t\tif (tempKeys.size() < n) {\n\t\t\ttempKeys.resize(n); tempIndices.resize(n);\n\t\t\ttempXs.resize(n); tempYs.resize(n);\n\t\t}\n\n\t\tfor (size_t i = 0; i < n; i++) {\n\t\t\tglm::ivec2 coord = posToCellCoord(pParticles[i].pos);\n\t\t\tuint32_t key = hashCell(coord.x, coord.y);\n\n\t\t\ttempKeys[i] = key;\n\t\t\ttempIndices[i] = i;\n\t\t\ttempXs[i] = coord.x;\n\t\t\ttempYs[i] = coord.y;\n\n\t\t\tcountBuffer[key]++;\n\t\t}\n\n\t\tuint32_t currentOffset = 0;\n\t\tfor (size_t i = 0; i < hashTableSize; i++) {\n\t\t\toffsetBuffer[i] = currentOffset;\n\t\t\tstartIndices[i] = currentOffset;\n\t\t\tcurrentOffset += countBuffer[i];\n\n\t\t\tif (countBuffer[i] == 0) startIndices[i] = UINT32_MAX;\n\t\t}\n\n\t\tfor (size_t i = 0; i < n; i++) {\n\t\t\tuint32_t key = tempKeys[i];\n\t\t\tuint32_t destIndex = offsetBuffer[key]++;\n\n\t\t\tentries.cellKeys[destIndex] = key;\n\t\t\tentries.particleIndices[destIndex] = tempIndices[i];\n\t\t\tentries.cellXs[destIndex] = tempXs[i];\n\t\t\tentries.cellYs[destIndex] = tempYs[i];\n\n\t\t\tconst auto& p = pParticles[tempIndices[i]];\n\t\t\tentries.posX[destIndex] = p.pos.x;\n\t\t\tentries.posY[destIndex] = p.pos.y;\n\t\t}\n\t}\n\n\ttemplate <typename Func>\n\tvoid queryNeighbors(const glm::vec2& pos, Func&& callback) {\n\t\tglm::ivec2 cell = posToCellCoord(pos);\n\t\tint cellRadius = (int)ceil(searchRadius * invCellSize);\n\t\tfloat r2 = searchRadius * searchRadius;\n\n\t\tfor (int dx = -cellRadius; dx <= cellRadius; dx++) {\n\t\t\tfor (int dy = -cellRadius; dy <= cellRadius; dy++) {\n\t\t\t\tint neighborX = cell.x + dx;\n\t\t\t\tint neighborY = cell.y + dy;\n\t\t\t\tuint32_t key = hashCell(neighborX, neighborY);\n\n\t\t\t\tuint32_t start = startIndices[key];\n\t\t\t\tif (start == UINT32_MAX) continue;\n\n\t\t\t\tfor (size_t i = start; i < entries.size; i++) {\n\t\t\t\t\tif (entries.cellKeys[i] != key) break;\n\n\t\t\t\t\tif (entries.cellXs[i] != neighborX || entries.cellYs[i] != neighborY) continue;\n\n\t\t\t\t\tfloat dx = entries.posX[i] - pos.x;\n\t\t\t\t\tfloat dy = entries.posY[i] - pos.y;\n\t\t\t\t\tfloat distSq = dx * dx + dy * dy;\n\n\t\t\t\t\tif (distSq <= r2) {\n\t\t\t\t\t\tcallback(entries.particleIndices[i]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid neighborAmount(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles) {\n\n#pragma omp parallel for schedule(static)\n\t\tfor (size_t i = 0; i < pParticles.size(); ++i) {\n\n\t\t\tif (rParticles[i].isPinned || rParticles[i].isBeingDrawn || rParticles[i].isDarkMatter) continue;\n\n\t\t\trParticles[i].neighbors = 0;\n\n\t\t\tqueryNeighbors(pParticles[i].pos, [&](uint32_t neighborIdx) {\n\t\t\t\trParticles[i].neighbors++;\n\t\t\t\t});\n\t\t}\n\t}\n};\n\nstruct NeighborSearchV2AVX2 {\n\n\tfloat searchRadius = 3.5f;\n\tfloat cellSize = 3.0f;\n\tconst uint32_t hashTableSize = 16384;\n\n\tstruct EntryArrays {\n\t\tstd::vector<uint32_t> cellKeys;\n\t\tstd::vector<uint32_t> particleIndices;\n\t\tstd::vector<int> cellXs;\n\t\tstd::vector<int> cellYs;\n\t\tsize_t size = 0;\n\t};\n\n\tEntryArrays entries;\n\n\tstd::vector<uint32_t> startIndices;\n\tstd::vector<uint32_t> cellCounts;\n\n\tNeighborSearchV2AVX2() {\n\t\tstartIndices.resize(hashTableSize);\n\t\tcellCounts.resize(hashTableSize);\n\t}\n\n\tstd::pair<int, int> posToCellCoord(const glm::vec2& pos) {\n\t\tint cellX = static_cast<int>(std::floor(pos.x / cellSize));\n\t\tint cellY = static_cast<int>(std::floor(pos.y / cellSize));\n\t\treturn { cellX, cellY };\n\t}\n\n\tuint32_t hashCell(int cellX, int cellY) {\n\t\tuint32_t h = (uint32_t)((cellX * 73856093) ^ (cellY * 19349663));\n\t\treturn h % hashTableSize;\n\t}\n\n\tvoid newGridAVX2(const std::vector<ParticlePhysics>& pParticles) {\n\t\tconst size_t n = pParticles.size();\n\t\tif (n == 0) return;\n\n\t\tif (entries.cellKeys.size() < n) {\n\t\t\tentries.cellKeys.resize(n);\n\t\t\tentries.particleIndices.resize(n);\n\t\t\tentries.cellXs.resize(n);\n\t\t\tentries.cellYs.resize(n);\n\t\t}\n\t\tentries.size = n;\n\n\t\tstd::fill(std::execution::unseq, startIndices.begin(), startIndices.end(), UINT32_MAX);\n\t\tstd::fill(std::execution::unseq, cellCounts.begin(), cellCounts.end(), 0);\n\n#pragma omp parallel for\n\t\tfor (long long i = 0; i < n; i++) {\n\t\t\tauto [cx, cy] = posToCellCoord(pParticles[i].pos);\n\t\t\tentries.cellKeys[i] = hashCell(cx, cy);\n\t\t\tentries.particleIndices[i] = (uint32_t)i;\n\t\t\tentries.cellXs[i] = cx;\n\t\t\tentries.cellYs[i] = cy;\n\t\t}\n\n\t\tstd::vector<uint32_t> sortIndices(n);\n\t\tstd::iota(sortIndices.begin(), sortIndices.end(), 0);\n\n\t\tstd::sort(std::execution::par_unseq, sortIndices.begin(), sortIndices.end(),\n\t\t\t[&](uint32_t a, uint32_t b) {\n\t\t\t\treturn entries.cellKeys[a] < entries.cellKeys[b];\n\t\t\t}\n\t\t);\n\n\t\tEntryArrays sorted;\n\t\tsorted.cellKeys.resize(n);\n\t\tsorted.particleIndices.resize(n);\n\t\tsorted.cellXs.resize(n);\n\t\tsorted.cellYs.resize(n);\n\t\tsorted.size = n;\n\n#pragma omp parallel for\n\t\tfor (long long i = 0; i < (n & ~7); i += 8) {\n\t\t\t__m256i idxVec = _mm256_loadu_si256((__m256i*) & sortIndices[i]);\n\n\t\t\t__m256i keys = _mm256_i32gather_epi32((const int*)entries.cellKeys.data(), idxVec, 4);\n\t\t\t__m256i pIds = _mm256_i32gather_epi32((const int*)entries.particleIndices.data(), idxVec, 4);\n\t\t\t__m256i xs = _mm256_i32gather_epi32((const int*)entries.cellXs.data(), idxVec, 4);\n\t\t\t__m256i ys = _mm256_i32gather_epi32((const int*)entries.cellYs.data(), idxVec, 4);\n\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.cellKeys[i], keys);\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.particleIndices[i], pIds);\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.cellXs[i], xs);\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.cellYs[i], ys);\n\t\t}\n\n\t\tfor (size_t i = (n & ~7); i < n; i++) {\n\t\t\tuint32_t idx = sortIndices[i];\n\t\t\tsorted.cellKeys[i] = entries.cellKeys[idx];\n\t\t\tsorted.particleIndices[i] = entries.particleIndices[idx];\n\t\t\tsorted.cellXs[i] = entries.cellXs[idx];\n\t\t\tsorted.cellYs[i] = entries.cellYs[idx];\n\t\t}\n\n\t\tentries = std::move(sorted);\n\n\t\tif (n > 0) {\n\t\t\tuint32_t currentKey = entries.cellKeys[0];\n\t\t\tstartIndices[currentKey] = 0;\n\t\t\tuint32_t count = 1;\n\n\t\t\tfor (size_t i = 1; i < n; i++) {\n\t\t\t\tuint32_t key = entries.cellKeys[i];\n\t\t\t\tif (key != currentKey) {\n\t\t\t\t\tcellCounts[currentKey] = count;\n\t\t\t\t\tstartIndices[key] = (uint32_t)i;\n\t\t\t\t\tcurrentKey = key;\n\t\t\t\t\tcount = 1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcellCounts[currentKey] = count;\n\t\t}\n\t}\n\n\tvoid queryNeighborsAVX2(const glm::vec2& pos, std::vector<size_t>& neighbors) {\n\t\tneighbors.clear();\n\t\tauto [cellX, cellY] = posToCellCoord(pos);\n\n\t\t__m256i targetX = _mm256_set1_epi32(cellX);\n\t\t__m256i targetY = _mm256_set1_epi32(cellY);\n\n\t\tfor (int dx = -1; dx <= 1; dx++) {\n\t\t\tfor (int dy = -1; dy <= 1; dy++) {\n\t\t\t\tint neighborX = cellX + dx;\n\t\t\t\tint neighborY = cellY + dy;\n\n\t\t\t\tuint32_t cellKey = hashCell(neighborX, neighborY);\n\t\t\t\tuint32_t start = startIndices[cellKey];\n\n\t\t\t\tif (start == UINT32_MAX) continue;\n\n\t\t\t\tuint32_t count = cellCounts[cellKey];\n\n\t\t\t\t__m256i currentTargetX = _mm256_set1_epi32(neighborX);\n\t\t\t\t__m256i currentTargetY = _mm256_set1_epi32(neighborY);\n\n\t\t\t\tsize_t i = 0;\n\t\t\t\tsize_t currentIdx = start;\n\n\t\t\t\tfor (; i + 8 <= count; i += 8, currentIdx += 8) {\n\n\t\t\t\t\t__m256i cellXs = _mm256_loadu_si256((__m256i*) & entries.cellXs[currentIdx]);\n\t\t\t\t\t__m256i cellYs = _mm256_loadu_si256((__m256i*) & entries.cellYs[currentIdx]);\n\n\t\t\t\t\t__m256i matchX = _mm256_cmpeq_epi32(cellXs, currentTargetX);\n\t\t\t\t\t__m256i matchY = _mm256_cmpeq_epi32(cellYs, currentTargetY);\n\t\t\t\t\t__m256i match = _mm256_and_si256(matchX, matchY);\n\n\t\t\t\t\tint mask = _mm256_movemask_ps(_mm256_castsi256_ps(match));\n\n\t\t\t\t\tif (mask != 0) {\n\t\t\t\t\t\tif (mask & 1) neighbors.push_back(entries.particleIndices[currentIdx + 0]);\n\t\t\t\t\t\tif (mask & 2) neighbors.push_back(entries.particleIndices[currentIdx + 1]);\n\t\t\t\t\t\tif (mask & 4) neighbors.push_back(entries.particleIndices[currentIdx + 2]);\n\t\t\t\t\t\tif (mask & 8) neighbors.push_back(entries.particleIndices[currentIdx + 3]);\n\t\t\t\t\t\tif (mask & 16) neighbors.push_back(entries.particleIndices[currentIdx + 4]);\n\t\t\t\t\t\tif (mask & 32) neighbors.push_back(entries.particleIndices[currentIdx + 5]);\n\t\t\t\t\t\tif (mask & 64) neighbors.push_back(entries.particleIndices[currentIdx + 6]);\n\t\t\t\t\t\tif (mask & 128) neighbors.push_back(entries.particleIndices[currentIdx + 7]);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor (; i < count; i++, currentIdx++) {\n\t\t\t\t\tif (entries.cellXs[currentIdx] == neighborX && entries.cellYs[currentIdx] == neighborY) {\n\t\t\t\t\t\tneighbors.push_back(entries.particleIndices[currentIdx]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid neighborAmount(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles) {\n\n#pragma omp parallel \n\t\t{\n\t\t\tstd::vector<size_t> neighborIndices;\n\t\t\tneighborIndices.reserve(128);\n\n#pragma omp for\n\t\t\tfor (long long i = 0; i < pParticles.size(); ++i) {\n\n\t\t\t\tif (rParticles[i].isPinned || rParticles[i].isBeingDrawn || rParticles[i].isDarkMatter) continue;\n\n\t\t\t\tqueryNeighborsAVX2(pParticles[i].pos, neighborIndices);\n\n\t\t\t\trParticles[i].neighbors = (int)neighborIndices.size();\n\t\t\t}\n\t\t}\n\t}\n};\n\nstruct NeighborSearch3DV2 {\n\n\tfloat searchRadius = 3.5f;\n\tfloat cellSize = 3.0f;\n\tfloat invCellSize = 1.0f / 3.0f;\n\n\tstruct EntryArrays {\n\t\tstd::vector<uint32_t> cellKeys;\n\t\tstd::vector<uint32_t> particleIndices;\n\n\t\tstd::vector<float> posX;\n\t\tstd::vector<float> posY;\n\t\tstd::vector<float> posZ;\n\n\t\tstd::vector<int> cellXs;\n\t\tstd::vector<int> cellYs;\n\t\tstd::vector<int> cellZs;\n\n\t\tsize_t size = 0;\n\t};\n\n\tconst uint32_t hashTableSize = 32768;\n\tEntryArrays entries;\n\n\tstd::vector<uint32_t> countBuffer;\n\tstd::vector<uint32_t> offsetBuffer;\n\n\tstd::vector<uint32_t> tempKeys;\n\tstd::vector<uint32_t> tempIndices;\n\tstd::vector<int> tempXs;\n\tstd::vector<int> tempYs;\n\tstd::vector<int> tempZs;\n\n\tNeighborSearch3DV2() {\n\t\tcountBuffer.resize(hashTableSize + 1);\n\t\toffsetBuffer.resize(hashTableSize + 1);\n\t}\n\n\tglm::ivec3 posToCellCoord(const glm::vec3& pos) const {\n\t\treturn glm::ivec3(\n\t\t\t(int)std::floor(pos.x * invCellSize),\n\t\t\t(int)std::floor(pos.y * invCellSize),\n\t\t\t(int)std::floor(pos.z * invCellSize)\n\t\t);\n\t}\n\n\tuint32_t hashCell(int cellX, int cellY, int cellZ) const {\n\n\t\tuint32_t h = ((uint32_t)cellX * 73856093)\n\t\t\t^ ((uint32_t)cellY * 19349663)\n\t\t\t^ ((uint32_t)cellZ * 83492791);\n\n\t\treturn h % hashTableSize;\n\t}\n\n\tvoid newGrid(const std::vector<ParticlePhysics3D>& pParticles) {\n\t\tconst size_t n = pParticles.size();\n\t\tif (n == 0) return;\n\n\t\tif (entries.cellKeys.size() < n) {\n\t\t\tentries.cellKeys.resize(n);\n\t\t\tentries.particleIndices.resize(n);\n\t\t\tentries.posX.resize(n);\n\t\t\tentries.posY.resize(n);\n\t\t\tentries.posZ.resize(n);\n\t\t\tentries.cellXs.resize(n);\n\t\t\tentries.cellYs.resize(n);\n\t\t\tentries.cellZs.resize(n);\n\n\t\t\ttempKeys.resize(n);\n\t\t\ttempIndices.resize(n);\n\t\t\ttempXs.resize(n);\n\t\t\ttempYs.resize(n);\n\t\t\ttempZs.resize(n);\n\t\t}\n\t\tentries.size = n;\n\n\t\tstd::fill(countBuffer.begin(), countBuffer.end(), 0);\n\n\t\tfor (size_t i = 0; i < n; i++) {\n\t\t\tglm::ivec3 coord = posToCellCoord(pParticles[i].pos);\n\t\t\tuint32_t key = hashCell(coord.x, coord.y, coord.z);\n\n\t\t\ttempKeys[i] = key;\n\t\t\ttempIndices[i] = (uint32_t)i;\n\t\t\ttempXs[i] = coord.x;\n\t\t\ttempYs[i] = coord.y;\n\t\t\ttempZs[i] = coord.z;\n\n\t\t\tcountBuffer[key]++;\n\t\t}\n\n\t\tuint32_t currentOffset = 0;\n\t\tfor (size_t i = 0; i < hashTableSize; i++) {\n\t\t\toffsetBuffer[i] = currentOffset;\n\t\t\tcurrentOffset += countBuffer[i];\n\t\t}\n\n\t\tfor (size_t i = 0; i < n; i++) {\n\t\t\tuint32_t key = tempKeys[i];\n\n\t\t\tuint32_t destIndex = offsetBuffer[key]++;\n\n\t\t\tentries.cellKeys[destIndex] = key;\n\t\t\tentries.particleIndices[destIndex] = tempIndices[i];\n\t\t\tentries.cellXs[destIndex] = tempXs[i];\n\t\t\tentries.cellYs[destIndex] = tempYs[i];\n\t\t\tentries.cellZs[destIndex] = tempZs[i];\n\n\t\t\tconst auto& p = pParticles[tempIndices[i]];\n\t\t\tentries.posX[destIndex] = p.pos.x;\n\t\t\tentries.posY[destIndex] = p.pos.y;\n\t\t\tentries.posZ[destIndex] = p.pos.z;\n\t\t}\n\t}\n\n\ttemplate <typename Func>\n\tvoid queryNeighbors(const glm::vec3& pos, Func&& callback) {\n\t\tglm::ivec3 cell = posToCellCoord(pos);\n\n\t\tint searchGridDist = (int)std::ceil(searchRadius * invCellSize);\n\t\tfloat r2 = searchRadius * searchRadius;\n\n\t\tfor (int dz = -searchGridDist; dz <= searchGridDist; dz++) {\n\t\t\tfor (int dy = -searchGridDist; dy <= searchGridDist; dy++) {\n\t\t\t\tfor (int dx = -searchGridDist; dx <= searchGridDist; dx++) {\n\n\t\t\t\t\tint neighborX = cell.x + dx;\n\t\t\t\t\tint neighborY = cell.y + dy;\n\t\t\t\t\tint neighborZ = cell.z + dz;\n\n\t\t\t\t\tuint32_t key = hashCell(neighborX, neighborY, neighborZ);\n\n\t\t\t\t\tuint32_t end = offsetBuffer[key];\n\t\t\t\t\tuint32_t count = countBuffer[key];\n\t\t\t\t\tuint32_t start = end - count;\n\n\t\t\t\t\tif (count == 0) continue;\n\n\t\t\t\t\tfor (size_t i = start; i < end; i++) {\n\t\t\t\t\t\tif (entries.cellXs[i] != neighborX ||\n\t\t\t\t\t\t\tentries.cellYs[i] != neighborY ||\n\t\t\t\t\t\t\tentries.cellZs[i] != neighborZ) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfloat dX = entries.posX[i] - pos.x;\n\t\t\t\t\t\tfloat dY = entries.posY[i] - pos.y;\n\t\t\t\t\t\tfloat dZ = entries.posZ[i] - pos.z;\n\n\t\t\t\t\t\tfloat distSq = dX * dX + dY * dY + dZ * dZ;\n\n\t\t\t\t\t\tif (distSq <= r2) {\n\t\t\t\t\t\t\tcallback(entries.particleIndices[i]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid neighborAmount(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {\n\n#pragma omp parallel for schedule(static)\n\t\tfor (long long i = 0; i < pParticles.size(); ++i) {\n\n\t\t\tif (rParticles[i].isPinned || rParticles[i].isBeingDrawn || rParticles[i].isDarkMatter) continue;\n\n\t\t\trParticles[i].neighbors = 0;\n\n\t\t\tqueryNeighbors(pParticles[i].pos, [&](uint32_t neighborIdx) {\n\t\t\t\t if (neighborIdx == i) return; \n\n\t\t\t\trParticles[i].neighbors++;\n\t\t\t\t});\n\t\t}\n\t}\n};\n\nstruct NeighborSearch3DV2AVX2 {\n\n\tfloat searchRadius = 3.5f;\n\tfloat cellSize = 3.0f;\n\tconst uint32_t hashTableSize = 32768;\n\n\tstruct EntryArrays {\n\t\tstd::vector<uint32_t> cellKeys;\n\t\tstd::vector<uint32_t> particleIndices;\n\n\t\tstd::vector<int> cellXs;\n\t\tstd::vector<int> cellYs;\n\t\tstd::vector<int> cellZs;\n\n\t\tsize_t size = 0;\n\t};\n\n\tEntryArrays entries;\n\n\tstd::vector<uint32_t> startIndices;\n\tstd::vector<uint32_t> cellCounts;\n\n\tNeighborSearch3DV2AVX2() {\n\t\tstartIndices.resize(hashTableSize);\n\t\tcellCounts.resize(hashTableSize);\n\t}\n\n\tstd::tuple<int, int, int> posToCellCoord(const glm::vec3& pos) {\n\t\tint cellX = static_cast<int>(std::floor(pos.x / cellSize));\n\t\tint cellY = static_cast<int>(std::floor(pos.y / cellSize));\n\t\tint cellZ = static_cast<int>(std::floor(pos.z / cellSize));\n\t\treturn { cellX, cellY, cellZ };\n\t}\n\n\tuint32_t hashCell(int cellX, int cellY, int cellZ) {\n\t\tuint32_t h = (uint32_t)((cellX * 73856093) ^ (cellY * 19349663) ^ (cellZ * 83492791));\n\t\treturn h % hashTableSize;\n\t}\n\n\tvoid newGridAVX2(const std::vector<ParticlePhysics3D>& pParticles) {\n\t\tconst size_t n = pParticles.size();\n\t\tif (n == 0) return;\n\n\t\tif (entries.cellKeys.size() < n) {\n\t\t\tentries.cellKeys.resize(n);\n\t\t\tentries.particleIndices.resize(n);\n\t\t\tentries.cellXs.resize(n);\n\t\t\tentries.cellYs.resize(n);\n\t\t\tentries.cellZs.resize(n);\n\t\t}\n\t\tentries.size = n;\n\n\t\tstd::fill(std::execution::unseq, startIndices.begin(), startIndices.end(), UINT32_MAX);\n\t\tstd::fill(std::execution::unseq, cellCounts.begin(), cellCounts.end(), 0);\n\n#pragma omp parallel for\n\t\tfor (long long i = 0; i < n; i++) {\n\t\t\tauto [cx, cy, cz] = posToCellCoord(pParticles[i].pos);\n\t\t\tentries.cellKeys[i] = hashCell(cx, cy, cz);\n\t\t\tentries.particleIndices[i] = (uint32_t)i;\n\t\t\tentries.cellXs[i] = cx;\n\t\t\tentries.cellYs[i] = cy;\n\t\t\tentries.cellZs[i] = cz;\n\t\t}\n\n\t\tstd::vector<uint32_t> sortIndices(n);\n\t\tstd::iota(sortIndices.begin(), sortIndices.end(), 0);\n\n\t\tstd::sort(std::execution::par_unseq, sortIndices.begin(), sortIndices.end(),\n\t\t\t[&](uint32_t a, uint32_t b) {\n\t\t\t\treturn entries.cellKeys[a] < entries.cellKeys[b];\n\t\t\t}\n\t\t);\n\n\t\tEntryArrays sorted;\n\t\tsorted.cellKeys.resize(n);\n\t\tsorted.particleIndices.resize(n);\n\t\tsorted.cellXs.resize(n);\n\t\tsorted.cellYs.resize(n);\n\t\tsorted.cellZs.resize(n);\n\t\tsorted.size = n;\n\n#pragma omp parallel for\n\t\tfor (long long i = 0; i < (n & ~7); i += 8) {\n\t\t\t__m256i idxVec = _mm256_loadu_si256((__m256i*) & sortIndices[i]);\n\n\t\t\t__m256i keys = _mm256_i32gather_epi32((const int*)entries.cellKeys.data(), idxVec, 4);\n\t\t\t__m256i pIds = _mm256_i32gather_epi32((const int*)entries.particleIndices.data(), idxVec, 4);\n\t\t\t__m256i xs = _mm256_i32gather_epi32((const int*)entries.cellXs.data(), idxVec, 4);\n\t\t\t__m256i ys = _mm256_i32gather_epi32((const int*)entries.cellYs.data(), idxVec, 4);\n\t\t\t__m256i zs = _mm256_i32gather_epi32((const int*)entries.cellZs.data(), idxVec, 4);\n\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.cellKeys[i], keys);\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.particleIndices[i], pIds);\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.cellXs[i], xs);\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.cellYs[i], ys);\n\t\t\t_mm256_storeu_si256((__m256i*) & sorted.cellZs[i], zs);\n\t\t}\n\n\t\tfor (size_t i = (n & ~7); i < n; i++) {\n\t\t\tuint32_t idx = sortIndices[i];\n\t\t\tsorted.cellKeys[i] = entries.cellKeys[idx];\n\t\t\tsorted.particleIndices[i] = entries.particleIndices[idx];\n\t\t\tsorted.cellXs[i] = entries.cellXs[idx];\n\t\t\tsorted.cellYs[i] = entries.cellYs[idx];\n\t\t\tsorted.cellZs[i] = entries.cellZs[idx];\n\t\t}\n\n\t\tentries = std::move(sorted);\n\n\t\tif (n > 0) {\n\t\t\tuint32_t currentKey = entries.cellKeys[0];\n\t\t\tstartIndices[currentKey] = 0;\n\t\t\tuint32_t count = 1;\n\n\t\t\tfor (size_t i = 1; i < n; i++) {\n\t\t\t\tuint32_t key = entries.cellKeys[i];\n\t\t\t\tif (key != currentKey) {\n\t\t\t\t\tcellCounts[currentKey] = count;\n\t\t\t\t\tstartIndices[key] = (uint32_t)i;\n\t\t\t\t\tcurrentKey = key;\n\t\t\t\t\tcount = 1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcellCounts[currentKey] = count;\n\t\t}\n\t}\n\n\tvoid queryNeighborsAVX2(const glm::vec3& pos, std::vector<size_t>& neighbors) {\n\t\tneighbors.clear();\n\t\tauto [cellX, cellY, cellZ] = posToCellCoord(pos);\n\n\t\tfor (int dz = -1; dz <= 1; dz++) {\n\t\t\tfor (int dy = -1; dy <= 1; dy++) {\n\t\t\t\tfor (int dx = -1; dx <= 1; dx++) {\n\n\t\t\t\t\tint neighborX = cellX + dx;\n\t\t\t\t\tint neighborY = cellY + dy;\n\t\t\t\t\tint neighborZ = cellZ + dz;\n\n\t\t\t\t\tuint32_t cellKey = hashCell(neighborX, neighborY, neighborZ);\n\t\t\t\t\tuint32_t start = startIndices[cellKey];\n\n\t\t\t\t\tif (start == UINT32_MAX) continue;\n\n\t\t\t\t\tuint32_t count = cellCounts[cellKey];\n\n\t\t\t\t\t__m256i currentTargetX = _mm256_set1_epi32(neighborX);\n\t\t\t\t\t__m256i currentTargetY = _mm256_set1_epi32(neighborY);\n\t\t\t\t\t__m256i currentTargetZ = _mm256_set1_epi32(neighborZ);\n\n\t\t\t\t\tsize_t i = 0;\n\t\t\t\t\tsize_t currentIdx = start;\n\n\t\t\t\t\tfor (; i + 8 <= count; i += 8, currentIdx += 8) {\n\n\t\t\t\t\t\t__m256i cellXs = _mm256_loadu_si256((__m256i*) & entries.cellXs[currentIdx]);\n\t\t\t\t\t\t__m256i cellYs = _mm256_loadu_si256((__m256i*) & entries.cellYs[currentIdx]);\n\t\t\t\t\t\t__m256i cellZs = _mm256_loadu_si256((__m256i*) & entries.cellZs[currentIdx]);\n\n\t\t\t\t\t\t__m256i matchX = _mm256_cmpeq_epi32(cellXs, currentTargetX);\n\t\t\t\t\t\t__m256i matchY = _mm256_cmpeq_epi32(cellYs, currentTargetY);\n\t\t\t\t\t\t__m256i matchZ = _mm256_cmpeq_epi32(cellZs, currentTargetZ);\n\n\t\t\t\t\t\t__m256i match = _mm256_and_si256(matchX, matchY);\n\t\t\t\t\t\tmatch = _mm256_and_si256(match, matchZ);\n\n\t\t\t\t\t\tint mask = _mm256_movemask_ps(_mm256_castsi256_ps(match));\n\n\t\t\t\t\t\tif (mask != 0) {\n\t\t\t\t\t\t\tif (mask & 1) neighbors.push_back(entries.particleIndices[currentIdx + 0]);\n\t\t\t\t\t\t\tif (mask & 2) neighbors.push_back(entries.particleIndices[currentIdx + 1]);\n\t\t\t\t\t\t\tif (mask & 4) neighbors.push_back(entries.particleIndices[currentIdx + 2]);\n\t\t\t\t\t\t\tif (mask & 8) neighbors.push_back(entries.particleIndices[currentIdx + 3]);\n\t\t\t\t\t\t\tif (mask & 16) neighbors.push_back(entries.particleIndices[currentIdx + 4]);\n\t\t\t\t\t\t\tif (mask & 32) neighbors.push_back(entries.particleIndices[currentIdx + 5]);\n\t\t\t\t\t\t\tif (mask & 64) neighbors.push_back(entries.particleIndices[currentIdx + 6]);\n\t\t\t\t\t\t\tif (mask & 128) neighbors.push_back(entries.particleIndices[currentIdx + 7]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (; i < count; i++, currentIdx++) {\n\t\t\t\t\t\tif (entries.cellXs[currentIdx] == neighborX &&\n\t\t\t\t\t\t\tentries.cellYs[currentIdx] == neighborY &&\n\t\t\t\t\t\t\tentries.cellZs[currentIdx] == neighborZ) {\n\n\t\t\t\t\t\t\tneighbors.push_back(entries.particleIndices[currentIdx]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid neighborAmount(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {\n\n#pragma omp parallel \n\t\t{\n\t\t\tstd::vector<size_t> neighborIndices;\n\t\t\tneighborIndices.reserve(128);\n\n#pragma omp for\n\t\t\tfor (long long i = 0; i < pParticles.size(); ++i) {\n\n\t\t\t\tif (rParticles[i].isPinned || rParticles[i].isBeingDrawn || rParticles[i].isDarkMatter) continue;\n\n\t\t\t\tqueryNeighborsAVX2(pParticles[i].pos, neighborIndices);\n\n\t\t\t\trParticles[i].neighbors = (int)neighborIndices.size();\n\t\t\t}\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/particle.h",
    "content": "#pragma once\n\nextern uint32_t globalId;\n\nstruct ParticlePhysics {\n\n\tglm::vec2 pos;\n\tglm::vec2 vel;\n\tglm::vec2 acc;\n\tglm::vec2 predPos;\n\tglm::vec2 prevVel;\n\tglm::vec2 predVel;\n\tglm::vec2 pressF;\n\n\tuint64_t mortonKey;\n\tfloat mass;\n\tfloat press;\n\tfloat pressTmp;\n\tfloat dens;\n\tfloat predDens;\n\tfloat sphMass;\n\tfloat restDens;\n\tfloat stiff;\n\tfloat visc;\n\tfloat cohesion;\n\tfloat temp;\n\tfloat ke;\n\tfloat prevKe;\n\tuint32_t id;\n\tuint32_t neighborOffset;\n\n\tbool isHotPoint;\n\tbool hasSolidified;\n\n\t// Default constructor\n\tParticlePhysics()\n\t\t: pos(0.0f, 0.0f), vel{ 0,0 }, acc{ 0,0 }, predPos{ 0,0 }, prevVel{ 0.0f, 0.0f }, predVel{ 0.0f, 0.0f }, pressF{ 0.0f,0.0f }, mortonKey(0),\n\t\tmass(8500000000.0f), press(0.0f), pressTmp(0.0f), dens(0.0f), predDens(0.0f), sphMass(1.0f),\n\t\trestDens(0.0f), stiff(0.0f), visc(0.0f), cohesion(0.0f),\n\t\ttemp(0.0f), ke(0.0f), prevKe(0.0f), id(globalId++), neighborOffset(0),\n\t\tisHotPoint(false), hasSolidified(false)\n\t{\n\t}\n\n\t// Parameterized constructor\n\tParticlePhysics(glm::vec2 pos, glm::vec2 vel, float mass, float restDens, float stiff, float visc, float cohesion) {\n\t\tthis->pos = pos;\n\t\tthis->vel = vel;\n\t\tthis->acc = { 0.0f, 0.0f };\n\t\tthis->predPos = { 0.0f, 0.0f };\n\t\tthis->prevVel = { 0.0f, 0.0f };\n\t\tthis->predVel = { 0.0f, 0.0f };\n\t\tthis->pressF = { 0.0f, 0.0f };\n\t\tthis->mass = mass;\n\t\tthis->press = 0.0f;\n\t\tthis->pressTmp = 0.0f;\n\t\tthis->dens = 0.0f;\n\t\tthis->predDens = 0.0f;\n\t\tthis->sphMass = mass / 8500000000.0f;\n\t\tthis->restDens = restDens;\n\t\tthis->stiff = stiff;\n\t\tthis->visc = visc;\n\t\tthis->cohesion = cohesion;\n\t\tthis->temp = 288.0f;\n\t\tthis->ke = 0.0f;\n\t\tthis->prevKe = 0.0f;\n\t\tthis->mortonKey = 0;\n\t\tthis->id = globalId++;\n\t\tthis->neighborOffset = 0;\n\t\tthis->isHotPoint = false;\n\t\tthis->hasSolidified = false;\n\t}\n};\n\nstruct ParticleRendering {\n\n\tColor color;\n\tColor pColor;\n\tColor sColor;\n\tColor sphColor;\n\n\tfloat size;\n\tfloat previousSize;\n\tfloat totalRadius;\n\tfloat lifeSpan;\n\tfloat turbulence;\n\tuint32_t sphLabel;\n\n\tint neighbors;\n\tint spawnCorrectIter;\n\n\tbool uniqueColor;\n\tbool isSolid;\n\tbool canBeSubdivided;\n\tbool canBeResized;\n\tbool isDarkMatter;\n\tbool isSPH;\n\tbool isSelected;\n\tbool isGrabbed;\n\tbool isPinned;\n\tbool isBeingDrawn;\n\n\t// Default constructor\n\tParticleRendering()\n\t\t: color{ 255,255,255,255 }, pColor{ 255,255,255,255 }, sColor{ 255, 255, 255, 255 }, sphColor{ 128,128,128,128 },\n\t\tsize(1.0f), previousSize(1.0f), totalRadius(0.0f), lifeSpan(-1.0f), turbulence(0.0f), sphLabel(0),\n\t\tneighbors(0), spawnCorrectIter(100000000),\n\t\tuniqueColor(false), isSolid(false), canBeSubdivided(false),\n\t\tcanBeResized(false), isDarkMatter(false), isSPH(false),\n\t\tisSelected(false), isGrabbed(false), isPinned(false), isBeingDrawn(true)\n\t{\n\t}\n\n\t// Parameterized constructor\n\tParticleRendering(Color color, float size, bool uniqueColor, bool isSelected,\n\t\tbool isSolid, bool canBeSubdivided, bool canBeResized, bool isDarkMatter, bool isSPH, float lifeSpan, uint32_t sphLabel) {\n\t\tthis->color = color;\n\t\tthis->pColor = { 255, 255, 255, 255 };\n\t\tthis->sColor = { 255, 255, 255, 255 };\n\t\tthis->sphColor = { 128, 128, 128, 128 };\n\t\tthis->size = size;\n\t\tthis->previousSize = size;\n\t\tthis->totalRadius = 0.0f;\n\t\tthis->lifeSpan = lifeSpan;\n\t\tthis->turbulence = 0.0f;\n\t\tthis->sphLabel = sphLabel;\n\t\tthis->neighbors = 0;\n\t\tthis->spawnCorrectIter = 100000000;\n\t\tthis->uniqueColor = uniqueColor;\n\t\tthis->isSolid = isSolid;\n\t\tthis->canBeSubdivided = canBeSubdivided;\n\t\tthis->canBeResized = canBeResized;\n\t\tthis->isDarkMatter = isDarkMatter;\n\t\tthis->isSPH = isSPH;\n\t\tthis->isSelected = isSelected;\n\t\tthis->isGrabbed = false;\n\t\tthis->isPinned = false;\n\t\tthis->isBeingDrawn = false;\n\t}\n};\n\nstruct ParticlePhysics3D {\n\n\tglm::vec3 pos;\n\tglm::vec3 vel;\n\tglm::vec3 acc;\n\tglm::vec3 predPos;\n\tglm::vec3 prevVel;\n\tglm::vec3 predVel;\n\tglm::vec3 pressF;\n\n\tuint64_t mortonKey;\n\tfloat mass;\n\tfloat press;\n\tfloat pressTmp;\n\tfloat dens;\n\tfloat predDens;\n\tfloat sphMass;\n\tfloat restDens;\n\tfloat stiff;\n\tfloat visc;\n\tfloat cohesion;\n\tfloat temp;\n\tfloat ke;\n\tfloat prevKe;\n\tuint32_t id;\n\tuint32_t neighborOffset;\n\n\tbool isHotPoint;\n\tbool hasSolidified;\n\n\t// Default constructor\n\tParticlePhysics3D()\n\t\t: pos(0.0f, 0.0f, 0.0f), vel{ 0.0f,0.0f, 0.0f }, acc{ 0.0f,0.0f, 0.0f }, predPos{ 0.0f,0.0f, 0.0f }, \n\t\tprevVel{ 0.0f, 0.0f, 0.0f }, predVel{ 0.0f, 0.0f, 0.0f }, pressF{ 0.0f,0.0f, 0.0f }, mortonKey(0),\n\t\tmass(8500000000.0f), press(0.0f), pressTmp(0.0f), dens(0.0f), predDens(0.0f), sphMass(1.0f),\n\t\trestDens(0.0f), stiff(0.0f), visc(0.0f), cohesion(0.0f),\n\t\ttemp(0.0f), ke(0.0f), prevKe(0.0f), id(globalId++), neighborOffset(0),\n\t\tisHotPoint(false), hasSolidified(false)\n\t{\n\t}\n\n\t// Parameterized constructor\n\tParticlePhysics3D(glm::vec3 pos, glm::vec3 vel, float mass, float restDens, float stiff, float visc, float cohesion) {\n\t\tthis->pos = pos;\n\t\tthis->vel = vel;\n\t\tthis->acc = { 0.0f, 0.0f, 0.0f };\n\t\tthis->predPos = { 0.0f, 0.0f, 0.0f };\n\t\tthis->prevVel = { 0.0f, 0.0f, 0.0f };\n\t\tthis->predVel = { 0.0f, 0.0f, 0.0f };\n\t\tthis->pressF = { 0.0f, 0.0f, 0.0f };\n\t\tthis->mass = mass;\n\t\tthis->press = 0.0f;\n\t\tthis->pressTmp = 0.0f;\n\t\tthis->dens = 0.0f;\n\t\tthis->predDens = 0.0f;\n\t\tthis->sphMass = mass / 8500000000.0f;\n\t\tthis->restDens = restDens;\n\t\tthis->stiff = stiff;\n\t\tthis->visc = visc;\n\t\tthis->cohesion = cohesion;\n\t\tthis->temp = 288.0f;\n\t\tthis->ke = 0.0f;\n\t\tthis->prevKe = 0.0f;\n\t\tthis->mortonKey = 0;\n\t\tthis->id = globalId++;\n\t\tthis->neighborOffset = 0;\n\t\tthis->isHotPoint = false;\n\t\tthis->hasSolidified = false;\n\t}\n};\n\nstruct ParticleRendering3D {\n\n\tColor color;\n\tColor pColor;\n\tColor sColor;\n\tColor sphColor;\n\n\tfloat size;\n\tfloat previousSize;\n\tfloat totalRadius;\n\tfloat lifeSpan;\n\tfloat turbulence;\n\tuint32_t sphLabel;\n\n\tint neighbors;\n\tint spawnCorrectIter;\n\n\tbool uniqueColor;\n\tbool isSolid;\n\tbool canBeSubdivided;\n\tbool canBeResized;\n\tbool isDarkMatter;\n\tbool isSPH;\n\tbool isSelected;\n\tbool isGrabbed;\n\tbool isPinned;\n\tbool isBeingDrawn;\n\n\t// Default constructor\n\tParticleRendering3D()\n\t\t: color{ 255,255,255,255 }, pColor{ 255,255,255,255 }, sColor{ 255, 255, 255, 255 }, sphColor{ 128,128,128,128 },\n\t\tsize(1.0f), previousSize(1.0f), totalRadius(0.0f), lifeSpan(-1.0f), turbulence(0.0f), sphLabel(0),\n\t\tneighbors(0), spawnCorrectIter(100000000),\n\t\tuniqueColor(false), isSolid(false), canBeSubdivided(false),\n\t\tcanBeResized(false), isDarkMatter(false), isSPH(false),\n\t\tisSelected(false), isGrabbed(false), isPinned(false), isBeingDrawn(true)\n\t{\n\t}\n\n\t// Parameterized constructor\n\tParticleRendering3D(Color color, float size, bool uniqueColor, bool isSelected,\n\t\tbool isSolid, bool canBeSubdivided, bool canBeResized, bool isDarkMatter, bool isSPH, float lifeSpan, uint32_t sphLabel) {\n\t\tthis->color = color;\n\t\tthis->pColor = { 255, 255, 255, 255 };\n\t\tthis->sColor = { 255, 255, 255, 255 };\n\t\tthis->sphColor = { 128, 128, 128, 128 };\n\t\tthis->size = size;\n\t\tthis->previousSize = size;\n\t\tthis->totalRadius = 0.0f;\n\t\tthis->lifeSpan = lifeSpan;\n\t\tthis->turbulence = 0.0f;\n\t\tthis->sphLabel = sphLabel;\n\t\tthis->neighbors = 0;\n\t\tthis->spawnCorrectIter = 100000000;\n\t\tthis->uniqueColor = uniqueColor;\n\t\tthis->isSolid = isSolid;\n\t\tthis->canBeSubdivided = canBeSubdivided;\n\t\tthis->canBeResized = canBeResized;\n\t\tthis->isDarkMatter = isDarkMatter;\n\t\tthis->isSPH = isSPH;\n\t\tthis->isSelected = isSelected;\n\t\tthis->isGrabbed = false;\n\t\tthis->isPinned = false;\n\t\tthis->isBeingDrawn = false;\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleColorVisuals.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Physics/materialsSPH.h\"\n\nstruct ColorVisuals {\n\n\tbool solidColor = false;\n\tbool densityColor = true;\n\tbool velocityColor = false;\n\tbool shockwaveColor = false;\n\tbool turbulenceColor = false;\n\tbool forceColor = false;\n\tbool pressureColor = false;\n\tbool temperatureColor = false;\n\tbool gasTempColor = false;\n\tbool SPHColor = false;\n\n\tbool showDarkMatterEnabled = false;\n\n\tbool selectedColor = true;\n\n\tint blendMode = 1;\n\n\tColor pColor = { 0, 40, 68, 100 };\n\tColor sColor = { 155, 80, 40, 75 };\n\n\tfloat hue = 180.0f;\n\tfloat saturation = 0.8f;\n\tfloat value = 0.5f;\n\n\tint maxNeighbors = 200;\n\n\tfloat maxColorAcc = 40.0f;\n\tfloat minColorAcc = 0.0f;\n\n\tfloat maxColorTurbulence = 40.0f;\n\tfloat minColorTurbulence = 0.0f;\n\tfloat turbulenceFadeRate = 0.015f;\n\tfloat turbulenceContrast = 1.1f;\n\tbool turbulenceCustomCol = false;\n\n\tfloat ShockwaveMaxAcc = 18.0f;\n\tfloat ShockwaveMinAcc = 0.0f;\n\n\tfloat maxVel = 100.0f;\n\tfloat minVel = 0.0f;\n\n\tfloat maxPress = 1000.0f;\n\tfloat minPress = 0.0f;\n\n\tfloat minTemp = 274.0f; // Min temp is set to roughly 1 degrees Celsius\n\n\tfloat tempColorMinTemp = 1.0f;\n\tfloat tempColorMaxTemp = 1000.0f;\n\n\tglm::vec2 prevVel = { 0.0f, 0.0f };\n\n\tvoid blackbodyToRGB(float kelvin, unsigned char& r, unsigned char& g, unsigned char& b) {\n\n\t\tfloat temperature = kelvin / 100.0f;\n\n\t\tfloat rC, gC, bC;\n\n\t\tif (temperature <= 66.0f) {\n\t\t\trC = 255.0f;\n\t\t}\n\t\telse {\n\t\t\trC = 329.698727446f * powf(temperature - 60.0f, -0.1332047592f);\n\t\t}\n\n\t\tif (temperature <= 66.0f) {\n\t\t\tgC = 99.4708025861f * logf(temperature) - 161.1195681661f;\n\t\t}\n\t\telse {\n\t\t\tgC = 288.1221695283f * powf(temperature - 60.0f, -0.0755148492f);\n\t\t}\n\n\t\tif (temperature >= 66.0f) {\n\t\t\tbC = 255.0f;\n\t\t}\n\t\telse if (temperature <= 19.0f) {\n\t\t\tbC = 0.0f;\n\t\t}\n\t\telse {\n\t\t\tbC = 138.5177312231f * logf(temperature - 10.0f) - 305.0447927307f;\n\t\t}\n\n\t\tr = static_cast<unsigned char>(std::clamp(rC, 0.0f, 255.0f));\n\t\tg = static_cast<unsigned char>(std::clamp(gC, 0.0f, 255.0f));\n\t\tb = static_cast<unsigned char>(std::clamp(bC, 0.0f, 255.0f));\n\t}\n\n\n\tColor blendColors(Color base, Color emission, float glowFactor) {\n\t\tColor result;\n\t\tresult.r = base.r * (1.0f - glowFactor) + emission.r * glowFactor;\n\t\tresult.g = base.g * (1.0f - glowFactor) + emission.g * glowFactor;\n\t\tresult.b = base.b * (1.0f - glowFactor) + emission.b * glowFactor;\n\t\tresult.a = 255;\n\t\treturn result;\n\t}\n\n\tfloat getGlowFactor(int temperature) {\n\t\tconst int minTemp = 600;\n\t\tconst int maxTemp = 6000;\n\n\t\tfloat linear = std::clamp((float)(temperature - minTemp) / (maxTemp - minTemp), 0.0f, 1.0f);\n\t\treturn powf(linear, 0.3f);\n\t}\n\n\tvoid particlesColorVisuals(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, std::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, bool& isTempEnabled, float& timeFactor, bool& is3DMode) {\n\n\t\tif (solidColor && !is3DMode) {\n\t\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\t\t\tif (!rParticles[i].uniqueColor) {\n\t\t\t\t\trParticles[i].pColor = pColor;\n\t\t\t\t\trParticles[i].color = rParticles[i].pColor;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\trParticles[i].color = rParticles[i].pColor;\n\t\t\t\t}\n\t\t\t}\n\t\t\tblendMode = 1;\n\t\t}\n\t\telse if (solidColor && is3DMode) {\n\t\t\tfor (size_t i = 0; i < pParticles3D.size(); i++) {\n\t\t\t\tif (!rParticles3D[i].uniqueColor) {\n\t\t\t\t\trParticles3D[i].pColor = pColor;\n\t\t\t\t\trParticles3D[i].color = rParticles3D[i].pColor;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\trParticles3D[i].color = rParticles3D[i].pColor;\n\t\t\t\t}\n\t\t\t}\n\t\t\tblendMode = 1;\n\t\t}\n\n\t\tif (densityColor && !is3DMode) {\n\n\t\t\tconst float invMaxNeighbors = 1.0f / maxNeighbors;\n\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < pParticles.size(); i++) {\n\t\t\t\tif (rParticles[i].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (!rParticles[i].uniqueColor) {\n\t\t\t\t\trParticles[i].pColor = pColor;\n\t\t\t\t\trParticles[i].sColor = sColor;\n\t\t\t\t}\n\n\t\t\t\tColor lowDensityColor = rParticles[i].pColor;\n\n\t\t\t\tColor highDensityColor = rParticles[i].sColor;\n\n\t\t\t\tfloat normalDensity = std::min(static_cast<float>(rParticles[i].neighbors) * invMaxNeighbors, 1.0f);\n\t\t\t\trParticles[i].color = ColorLerp(lowDensityColor, highDensityColor, normalDensity);\n\t\t\t}\n\n\t\t\tblendMode = 1;\n\t\t}\n\t\telse if (densityColor && is3DMode) {\n\n\t\t\tconst float invMaxNeighbors = 1.0f / maxNeighbors;\n\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < pParticles3D.size(); i++) {\n\t\t\t\tif (rParticles3D[i].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (!rParticles3D[i].uniqueColor) {\n\t\t\t\t\trParticles3D[i].pColor = pColor;\n\t\t\t\t\trParticles3D[i].sColor = sColor;\n\t\t\t\t}\n\n\t\t\t\tColor lowDensityColor = rParticles3D[i].pColor;\n\n\t\t\t\tColor highDensityColor = rParticles3D[i].sColor;\n\n\t\t\t\tfloat normalDensity = std::min(static_cast<float>(rParticles3D[i].neighbors) * invMaxNeighbors, 1.0f);\n\t\t\t\trParticles3D[i].color = ColorLerp(lowDensityColor, highDensityColor, normalDensity);\n\t\t\t}\n\n\t\t\tblendMode = 1;\n\t\t}\n\n\t\tif (velocityColor && !is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < pParticles.size(); i++) {\n\n\t\t\t\tfloat particleVelSq = pParticles[i].vel.x * pParticles[i].vel.x +\n\t\t\t\t\tpParticles[i].vel.y * pParticles[i].vel.y;\n\n\t\t\t\tfloat clampedVel = std::clamp(particleVelSq, minVel, maxVel);\n\t\t\t\tfloat normalizedVel = clampedVel / maxVel;\n\n\t\t\t\thue = (1.0f - normalizedVel) * 240.0f;\n\t\t\t\tsaturation = 1.0f;\n\t\t\t\tvalue = 1.0f;\n\n\t\t\t\trParticles[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t}\n\n\t\t\tblendMode = 0;\n\t\t}\n\t\telse if (velocityColor && is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\t\t\tfloat particleVelSq = pParticles3D[i].vel.x * pParticles3D[i].vel.x +\n\t\t\t\t\tpParticles3D[i].vel.y * pParticles3D[i].vel.y + pParticles3D[i].vel.z * pParticles3D[i].vel.z;\n\n\t\t\t\tfloat clampedVel = std::clamp(particleVelSq, minVel, maxVel);\n\t\t\t\tfloat normalizedVel = clampedVel / maxVel;\n\n\t\t\t\thue = (1.0f - normalizedVel) * 240.0f;\n\t\t\t\tsaturation = 1.0f;\n\t\t\t\tvalue = 1.0f;\n\n\t\t\t\trParticles3D[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t}\n\n\t\t\tblendMode = 0;\n\t\t}\n\n\t\tif (forceColor && !is3DMode) {\n\t\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\n\t\t\t\tif (rParticles[i].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tfloat particleAccSq = pParticles[i].acc.x * pParticles[i].acc.x +\n\t\t\t\t\tpParticles[i].acc.y * pParticles[i].acc.y;\n\n\t\t\t\tfloat clampedAcc = std::clamp(sqrt(particleAccSq), minColorAcc, maxColorAcc);\n\t\t\t\tfloat normalizedAcc = clampedAcc / maxColorAcc;\n\n\t\t\t\tif (!rParticles[i].uniqueColor) {\n\t\t\t\t\trParticles[i].pColor = pColor;\n\t\t\t\t\trParticles[i].sColor = sColor;\n\t\t\t\t}\n\n\t\t\t\tColor lowAccColor = rParticles[i].pColor;\n\n\t\t\t\tColor highAccColor = rParticles[i].sColor;\n\n\t\t\t\trParticles[i].color = ColorLerp(lowAccColor, highAccColor, normalizedAcc);\n\n\t\t\t}\n\t\t\tblendMode = 1;\n\t\t}\n\t\telse if (forceColor && is3DMode) {\n\t\t\tfor (size_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\t\t\tif (rParticles3D[i].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tfloat particleAccSq = pParticles3D[i].acc.x * pParticles3D[i].acc.x +\n\t\t\t\t\tpParticles3D[i].acc.y * pParticles3D[i].acc.y + pParticles3D[i].acc.z * pParticles3D[i].acc.z;\n\n\t\t\t\tfloat clampedAcc = std::clamp(sqrt(particleAccSq), minColorAcc, maxColorAcc);\n\t\t\t\tfloat normalizedAcc = clampedAcc / maxColorAcc;\n\n\t\t\t\tif (!rParticles3D[i].uniqueColor) {\n\t\t\t\t\trParticles3D[i].pColor = pColor;\n\t\t\t\t\trParticles3D[i].sColor = sColor;\n\t\t\t\t}\n\n\t\t\t\tColor lowAccColor = rParticles3D[i].pColor;\n\n\t\t\t\tColor highAccColor = rParticles3D[i].sColor;\n\n\t\t\t\trParticles3D[i].color = ColorLerp(lowAccColor, highAccColor, normalizedAcc);\n\n\t\t\t}\n\t\t\tblendMode = 1;\n\t\t}\n\n\t\tif (shockwaveColor && !is3DMode) {\n\t\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\n\t\t\t\tglm::vec2 shockwave = pParticles[i].acc;\n\n\t\t\t\tfloat shockMag = std::sqrt(shockwave.x * shockwave.x + shockwave.y * shockwave.y);\n\n\t\t\t\tfloat clampedShock = std::clamp(shockMag, ShockwaveMinAcc, ShockwaveMaxAcc);\n\t\t\t\tfloat normalizedShock = clampedShock / ShockwaveMaxAcc;\n\n\t\t\t\thue = (1.0f - normalizedShock) * 240.0f;\n\t\t\t\tsaturation = 1.0f;\n\t\t\t\tvalue = 1.0f;\n\n\t\t\t\trParticles[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t}\n\t\t\tblendMode = 0;\n\t\t}\n\t\telse if (shockwaveColor && is3DMode) {\n\t\t\tfor (size_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\t\t\tglm::vec3 shockwave = pParticles3D[i].acc;\n\n\t\t\t\tfloat shockMag = std::sqrt(shockwave.x * shockwave.x + shockwave.y * shockwave.y + shockwave.z * shockwave.z);\n\n\t\t\t\tshockMag = std::log(std::max(shockMag, 0.0001f));\n\n\t\t\t\tfloat clampedShock = std::clamp(shockMag, ShockwaveMinAcc, ShockwaveMaxAcc);\n\t\t\t\tfloat normalizedShock = clampedShock / ShockwaveMaxAcc;\n\n\t\t\t\thue = (1.0f - normalizedShock) * 240.0f;\n\t\t\t\tsaturation = 1.0f;\n\t\t\t\tvalue = 1.0f;\n\n\t\t\t\trParticles3D[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t}\n\t\t\tblendMode = 0;\n\t\t}\n\n\t\tif (turbulenceColor && !is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\n\t\t\t\tif (rParticles[i].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (timeFactor != 0.0f) {\n\t\t\t\t\tfloat pTotalVel = sqrt(pParticles[i].vel.x * pParticles[i].vel.x + pParticles[i].vel.y * pParticles[i].vel.y);\n\t\t\t\t\tfloat pTotalPrevVel = sqrt(pParticles[i].prevVel.x * pParticles[i].prevVel.x + pParticles[i].prevVel.y * pParticles[i].prevVel.y);\n\n\t\t\t\t\trParticles[i].turbulence += std::abs(pTotalVel - pTotalPrevVel);\n\n\t\t\t\t\tglm::vec2 velDiff = pParticles[i].vel - pParticles[i].prevVel;\n\t\t\t\t\trParticles[i].turbulence += glm::length(velDiff);\n\n\t\t\t\t\trParticles[i].turbulence *= 1.0f - turbulenceFadeRate;\n\n\t\t\t\t\trParticles[i].turbulence = std::max(0.0f, rParticles[i].turbulence);\n\t\t\t\t}\n\n\t\t\t\tfloat clampedTurbulence = std::clamp(rParticles[i].turbulence, minColorTurbulence, maxColorTurbulence);\n\t\t\t\tfloat normalizedTurbulence = pow(clampedTurbulence / maxColorTurbulence, turbulenceContrast);\n\n\t\t\t\tif (turbulenceCustomCol) {\n\t\t\t\t\tif (!rParticles[i].uniqueColor) {\n\t\t\t\t\t\trParticles[i].pColor = pColor;\n\t\t\t\t\t\trParticles[i].sColor = sColor;\n\t\t\t\t\t}\n\n\t\t\t\t\tColor lowTurbulenceColor = rParticles[i].pColor;\n\n\t\t\t\t\tColor highTurbulenceColor = rParticles[i].sColor;\n\n\t\t\t\t\trParticles[i].color = ColorLerp(lowTurbulenceColor, highTurbulenceColor, normalizedTurbulence);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\thue = (1.0f - normalizedTurbulence) * 240.0f;\n\t\t\t\t\tsaturation = 1.0f;\n\t\t\t\t\tvalue = 1.0f;\n\n\t\t\t\t\trParticles[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t\t}\n\n\t\t\t}\n\t\t\tblendMode = 0;\n\t\t}\n\t\telse if (turbulenceColor && is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (size_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\t\t\tif (rParticles3D[i].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (timeFactor != 0.0f) {\n\t\t\t\t\tfloat pTotalVel = sqrt(pParticles3D[i].vel.x * pParticles3D[i].vel.x + pParticles3D[i].vel.y * pParticles3D[i].vel.y + pParticles3D[i].vel.z * pParticles3D[i].vel.z);\n\t\t\t\t\tfloat pTotalPrevVel = sqrt(pParticles3D[i].prevVel.x * pParticles3D[i].prevVel.x + pParticles3D[i].prevVel.y * pParticles3D[i].prevVel.y + pParticles3D[i].prevVel.z * pParticles3D[i].prevVel.z);\n\n\t\t\t\t\trParticles3D[i].turbulence += std::abs(pTotalVel - pTotalPrevVel);\n\n\t\t\t\t\tglm::vec2 velDiff = pParticles3D[i].vel - pParticles3D[i].prevVel;\n\t\t\t\t\trParticles3D[i].turbulence += glm::length(velDiff);\n\n\t\t\t\t\trParticles3D[i].turbulence *= 1.0f - turbulenceFadeRate;\n\n\t\t\t\t\trParticles3D[i].turbulence = std::max(0.0f, rParticles3D[i].turbulence);\n\t\t\t\t}\n\n\t\t\t\tfloat clampedTurbulence = std::clamp(rParticles3D[i].turbulence, minColorTurbulence, maxColorTurbulence);\n\t\t\t\tfloat normalizedTurbulence = pow(clampedTurbulence / maxColorTurbulence, turbulenceContrast);\n\n\t\t\t\tif (turbulenceCustomCol) {\n\t\t\t\t\tif (!rParticles3D[i].uniqueColor) {\n\t\t\t\t\t\trParticles3D[i].pColor = pColor;\n\t\t\t\t\t\trParticles3D[i].sColor = sColor;\n\t\t\t\t\t}\n\n\t\t\t\t\tColor lowTurbulenceColor = rParticles3D[i].pColor;\n\n\t\t\t\t\tColor highTurbulenceColor = rParticles3D[i].sColor;\n\n\t\t\t\t\trParticles3D[i].color = ColorLerp(lowTurbulenceColor, highTurbulenceColor, normalizedTurbulence);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\thue = (1.0f - normalizedTurbulence) * 240.0f;\n\t\t\t\t\tsaturation = 1.0f;\n\t\t\t\t\tvalue = 1.0f;\n\n\t\t\t\t\trParticles3D[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t\t}\n\n\t\t\t}\n\t\t\tblendMode = 0;\n\t\t}\n\n\t\tif (pressureColor && !is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < pParticles.size(); i++) {\n\n\t\t\t\tParticlePhysics& p = pParticles[i];\n\n\t\t\t\tfloat clampedPress = std::clamp(p.press, minPress, maxPress);\n\t\t\t\tfloat normalizedPress = clampedPress / maxPress;\n\n\t\t\t\thue = (1.0f - normalizedPress) * 240.0f;\n\t\t\t\tsaturation = 1.0f;\n\t\t\t\tvalue = 1.0f;\n\n\t\t\t\trParticles[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t}\n\t\t\tblendMode = 0;\n\t\t}\n\t\telse if (pressureColor && is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\t\t\tParticlePhysics3D& p = pParticles3D[i];\n\n\t\t\t\tfloat clampedPress = std::clamp(p.press, minPress, maxPress);\n\t\t\t\tfloat normalizedPress = clampedPress / maxPress;\n\n\t\t\t\thue = (1.0f - normalizedPress) * 240.0f;\n\t\t\t\tsaturation = 1.0f;\n\t\t\t\tvalue = 1.0f;\n\n\t\t\t\trParticles3D[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t}\n\t\t\tblendMode = 0;\n\t\t}\n\n\t\tif (temperatureColor && !is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < pParticles.size(); i++) {\n\n\t\t\t\tParticlePhysics& p = pParticles[i];\n\n\t\t\t\tfloat clampedTemp = std::clamp(p.temp, tempColorMinTemp, tempColorMaxTemp);\n\t\t\t\tfloat normalizedTemp = clampedTemp / tempColorMaxTemp;\n\n\t\t\t\thue = (1.0f - normalizedTemp) * 240.0f;\n\t\t\t\tsaturation = 1.0f;\n\t\t\t\tvalue = 1.0f;\n\n\t\t\t\trParticles[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t}\n\t\t\tblendMode = 0;\n\t\t}\n\t\telse if (temperatureColor && is3DMode) {\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\t\t\tParticlePhysics3D& p = pParticles3D[i];\n\n\t\t\t\tfloat clampedTemp = std::clamp(p.temp, tempColorMinTemp, tempColorMaxTemp);\n\t\t\t\tfloat normalizedTemp = clampedTemp / tempColorMaxTemp;\n\n\t\t\t\thue = (1.0f - normalizedTemp) * 240.0f;\n\t\t\t\tsaturation = 1.0f;\n\t\t\t\tvalue = 1.0f;\n\n\t\t\t\trParticles3D[i].color = ColorFromHSV(hue, saturation, value);\n\t\t\t}\n\t\t\tblendMode = 0;\n\t\t}\n\n\t\tif (gasTempColor && !is3DMode) {\n\t\t\tfor (size_t i = 0; i < rParticles.size(); i++) {\n\n\t\t\t\tif (!rParticles[i].uniqueColor) {\n\t\t\t\t\trParticles[i].pColor = pColor;\n\t\t\t\t\trParticles[i].sColor = sColor;\n\t\t\t\t}\n\n\t\t\t\tfloat normalizedTemp = (pParticles[i].temp - tempColorMinTemp) / (tempColorMaxTemp - tempColorMinTemp);\n\t\t\t\tnormalizedTemp = std::clamp(normalizedTemp, 0.0f, 1.0f);\n\n\t\t\t\tColor lowTempColor = rParticles[i].pColor;\n\n\t\t\t\tColor highTempColor = rParticles[i].sColor;\n\n\t\t\t\trParticles[i].color = ColorLerp(lowTempColor, highTempColor, normalizedTemp);\n\t\t\t}\n\t\t\tblendMode = 1;\n\t\t}\n\t\telse if (gasTempColor && is3DMode) {\n\t\t\tfor (size_t i = 0; i < rParticles3D.size(); i++) {\n\n\t\t\t\tif (!rParticles3D[i].uniqueColor) {\n\t\t\t\t\trParticles3D[i].pColor = pColor;\n\t\t\t\t\trParticles3D[i].sColor = sColor;\n\t\t\t\t}\n\n\t\t\t\tfloat normalizedTemp = (pParticles3D[i].temp - tempColorMinTemp) / (tempColorMaxTemp - tempColorMinTemp);\n\t\t\t\tnormalizedTemp = std::clamp(normalizedTemp, 0.0f, 1.0f);\n\n\t\t\t\tColor lowTempColor = rParticles3D[i].pColor;\n\n\t\t\t\tColor highTempColor = rParticles3D[i].sColor;\n\n\t\t\t\trParticles3D[i].color = ColorLerp(lowTempColor, highTempColor, normalizedTemp);\n\t\t\t}\n\t\t\tblendMode = 1;\n\t\t}\n\n\t\tif (SPHColor && !is3DMode) {\n\t\t\tfor (size_t i = 0; i < rParticles.size(); i++) {\n\t\t\t\tif (!rParticles[i].uniqueColor) {\n\n\t\t\t\t\tauto it = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel);\n\t\t\t\t\tif (it != SPHMaterials::idToMaterial.end()) {\n\t\t\t\t\t\tSPHMaterial* pMat = it->second;\n\n\t\t\t\t\t\tif (pMat->sphLabel != \"water\") {\n\n\t\t\t\t\t\t\tfloat glowFactor = getGlowFactor(pParticles[i].temp);\n\n\t\t\t\t\t\t\tColor matColor = rParticles[i].sphColor;\n\n\t\t\t\t\t\t\tColor glowColor;\n\t\t\t\t\t\t\tblackbodyToRGB(pParticles[i].temp, glowColor.r, glowColor.g, glowColor.b);\n\n\t\t\t\t\t\t\tColor finalColor = blendColors(matColor, glowColor, glowFactor);\n\n\t\t\t\t\t\t\trParticles[i].color = finalColor;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tfloat normalizedTemp = (pParticles[i].temp - minTemp) / (pMat->hotPoint - minTemp);\n\t\t\t\t\t\t\tnormalizedTemp = std::clamp(normalizedTemp, 0.0f, 1.0f);\n\n\t\t\t\t\t\t\tColor lowTempColor = rParticles[i].sphColor;\n\n\t\t\t\t\t\t\tColor highTempColor = { 255, 113, 33, 255 };\n\n\t\t\t\t\t\t\thighTempColor = it->second->hotColor;\n\n\n\t\t\t\t\t\t\tif (pParticles[i].temp < pMat->coldPoint) {\n\n\t\t\t\t\t\t\t\trParticles[i].color = pMat->coldColor;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\trParticles[i].color = ColorLerp(lowTempColor, highTempColor, normalizedTemp);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\trParticles[i].color = rParticles[i].pColor;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tblendMode = 0;\n\t\t}\n\t\telse if (SPHColor && is3DMode) {\n\t\t\tfor (size_t i = 0; i < rParticles3D.size(); i++) {\n\t\t\t\tif (!rParticles3D[i].uniqueColor) {\n\n\t\t\t\t\tauto it = SPHMaterials::idToMaterial.find(rParticles3D[i].sphLabel);\n\t\t\t\t\tif (it != SPHMaterials::idToMaterial.end()) {\n\t\t\t\t\t\tSPHMaterial* pMat = it->second;\n\n\t\t\t\t\t\tif (pMat->sphLabel != \"water\") {\n\n\t\t\t\t\t\t\tfloat glowFactor = getGlowFactor(pParticles3D[i].temp);\n\n\t\t\t\t\t\t\tColor matColor = rParticles3D[i].sphColor;\n\n\t\t\t\t\t\t\tColor glowColor;\n\t\t\t\t\t\t\tblackbodyToRGB(pParticles3D[i].temp, glowColor.r, glowColor.g, glowColor.b);\n\n\t\t\t\t\t\t\tColor finalColor = blendColors(matColor, glowColor, glowFactor);\n\n\t\t\t\t\t\t\trParticles3D[i].color = finalColor;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tfloat normalizedTemp = (pParticles3D[i].temp - minTemp) / (pMat->hotPoint - minTemp);\n\t\t\t\t\t\t\tnormalizedTemp = std::clamp(normalizedTemp, 0.0f, 1.0f);\n\n\t\t\t\t\t\t\tColor lowTempColor = rParticles3D[i].sphColor;\n\n\t\t\t\t\t\t\tColor highTempColor = { 255, 113, 33, 255 };\n\n\t\t\t\t\t\t\thighTempColor = it->second->hotColor;\n\n\n\t\t\t\t\t\t\tif (pParticles3D[i].temp < pMat->coldPoint) {\n\n\t\t\t\t\t\t\t\trParticles3D[i].color = pMat->coldColor;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\trParticles3D[i].color = ColorLerp(lowTempColor, highTempColor, normalizedTemp);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\trParticles3D[i].color = rParticles3D[i].pColor;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tblendMode = 0;\n\t\t}\n\n\n\t\tif (selectedColor && !is3DMode) {\n\t\t\tfor (size_t i = 0; i < rParticles.size(); i++) {\n\t\t\t\tif (rParticles[i].isSelected) {\n\t\t\t\t\trParticles[i].color = { 255, 20,20, 255 };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (selectedColor && is3DMode) {\n\t\t\tfor (size_t i = 0; i < rParticles3D.size(); i++) {\n\t\t\t\tif (rParticles3D[i].isSelected) {\n\t\t\t\t\trParticles3D[i].color = { 255, 20,20, 255 };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!is3DMode) {\n\t\t\tif (showDarkMatterEnabled) {\n\t\t\t\tfor (size_t i = 0; i < rParticles.size(); i++) {\n\t\t\t\t\tif (rParticles[i].isDarkMatter) {\n\t\t\t\t\t\trParticles[i].color = { 128, 128, 128, 170 };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (size_t i = 0; i < rParticles.size(); i++) {\n\t\t\t\t\tif (rParticles[i].isDarkMatter) {\n\t\t\t\t\t\trParticles[i].color = { 0, 0, 0, 0 };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (showDarkMatterEnabled) {\n\t\t\t\tfor (size_t i = 0; i < rParticles3D.size(); i++) {\n\t\t\t\t\tif (rParticles3D[i].isDarkMatter) {\n\t\t\t\t\t\trParticles3D[i].color = { 128, 128, 128, 170 };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (size_t i = 0; i < rParticles3D.size(); i++) {\n\t\t\t\t\tif (rParticles3D[i].isDarkMatter) {\n\t\t\t\t\t\trParticles3D[i].color = { 0, 0, 0, 0 };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleDeletion.h",
    "content": "#pragma once\n\n#include \"IO/io.h\"\n\n#include \"Particles/particle.h\"\n\nstruct ParticleDeletion {\n\n\tbool deleteSelection = false;\n\n\tbool deleteNonImportant = false;\n\n\tvoid deleteSelected(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles,\n\t\tstd::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, bool& is3DMode) {\n\n\t\tif (!is3DMode) {\n\t\t\tif (deleteSelection || IO::shortcutPress(KEY_DELETE)) {\n\t\t\t\tfor (size_t i = pParticles.size(); i-- > 0;) {\n\t\t\t\t\tif (rParticles[i].isSelected) {\n\n\t\t\t\t\t\tstd::swap(pParticles[i], pParticles.back());\n\t\t\t\t\t\tstd::swap(rParticles[i], rParticles.back());\n\n\t\t\t\t\t\tpParticles.pop_back();\n\t\t\t\t\t\trParticles.pop_back();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdeleteSelection = false;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (deleteSelection || IO::shortcutPress(KEY_DELETE)) {\n\t\t\t\tfor (size_t i = pParticles3D.size(); i-- > 0;) {\n\t\t\t\t\tif (rParticles3D[i].isSelected) {\n\n\t\t\t\t\t\tstd::swap(pParticles3D[i], pParticles3D.back());\n\t\t\t\t\t\tstd::swap(rParticles3D[i], rParticles3D.back());\n\n\t\t\t\t\t\tpParticles3D.pop_back();\n\t\t\t\t\t\trParticles3D.pop_back();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdeleteSelection = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid deleteStrays(std::vector<ParticlePhysics>& pParticles,\n\t\tstd::vector<ParticleRendering>& rParticles, bool& isSPHEnabled,\n\t\tstd::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, bool& is3DMode) {\n\n\t\tif (deleteNonImportant) {\n\t\t\tif (isSPHEnabled) {\n\t\t\t\tif (!is3DMode) {\n\t\t\t\t\tcollisionRMultiplier = 6.0f;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcollisionRMultiplier = 12.0f;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!is3DMode) {\n\t\t\t\t\tcollisionRMultiplier = 1.0f;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcollisionRMultiplier = 2.0f;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!is3DMode) {\n\t\t\t\tstd::vector<int> neighborCounts(pParticles.size(), 0);\n\n\t\t\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\t\t\t\tconst glm::vec2& pos1 = pParticles[i].pos;\n\t\t\t\t\tfor (size_t j = i + 1; j < pParticles.size(); j++) {\n\t\t\t\t\t\tconst glm::vec2& pos2 = pParticles[j].pos;\n\t\t\t\t\t\tif (std::abs(pos2.x - pos1.x) > xBreakThreshold * collisionRMultiplier)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tfloat dx = pos1.x - pos2.x;\n\t\t\t\t\t\tfloat dy = pos1.y - pos2.y;\n\t\t\t\t\t\tif (dx * dx + dy * dy < squaredDistanceThreshold * collisionRMultiplier) {\n\t\t\t\t\t\t\tneighborCounts[i]++;\n\t\t\t\t\t\t\tneighborCounts[j]++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstd::vector<ParticlePhysics> newPParticles;\n\t\t\t\tstd::vector<ParticleRendering> newRParticles;\n\t\t\t\tnewPParticles.reserve(pParticles.size());\n\t\t\t\tnewRParticles.reserve(rParticles.size());\n\t\t\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\n\t\t\t\t\tif (!(neighborCounts[i] < 5 && !rParticles[i].isSolid)) {\n\t\t\t\t\t\tnewPParticles.push_back(std::move(pParticles[i]));\n\t\t\t\t\t\tnewRParticles.push_back(std::move(rParticles[i]));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpParticles.swap(newPParticles);\n\t\t\t\trParticles.swap(newRParticles);\n\n\t\t\t\tdeleteNonImportant = false;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstd::vector<int> neighborCounts(pParticles3D.size(), 0);\n\n\t\t\t\tfor (size_t i = 0; i < pParticles3D.size(); i++) {\n\t\t\t\t\tconst glm::vec3& pos1 = pParticles3D[i].pos;\n\t\t\t\t\tfor (size_t j = i + 1; j < pParticles3D.size(); j++) {\n\t\t\t\t\t\tconst glm::vec3& pos2 = pParticles3D[j].pos;\n\t\t\t\t\t\tif (std::abs(pos2.x - pos1.x) > xBreakThreshold * collisionRMultiplier)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tglm::vec3 d = pos2 - pos1;\n\n\t\t\t\t\t\tfloat dSq = glm::dot(d, d);\n\t\t\t\t\t\tif (dSq < squaredDistanceThreshold * collisionRMultiplier) {\n\t\t\t\t\t\t\tneighborCounts[i]++;\n\t\t\t\t\t\t\tneighborCounts[j]++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstd::vector<ParticlePhysics3D> newPParticles;\n\t\t\t\tstd::vector<ParticleRendering3D> newRParticles;\n\t\t\t\tnewPParticles.reserve(pParticles3D.size());\n\t\t\t\tnewRParticles.reserve(rParticles3D.size());\n\t\t\t\tfor (size_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\t\t\t\tif (!(neighborCounts[i] < 5 && !rParticles3D[i].isSolid)) {\n\t\t\t\t\t\tnewPParticles.push_back(std::move(pParticles3D[i]));\n\t\t\t\t\t\tnewRParticles.push_back(std::move(rParticles3D[i]));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpParticles3D.swap(newPParticles);\n\t\t\t\trParticles3D.swap(newRParticles);\n\n\t\t\t\tdeleteNonImportant = false;\n\t\t\t}\n\t\t}\n\t}\n\nprivate:\n\tconst float distanceThreshold = 10.0f;\n\tconst float squaredDistanceThreshold = distanceThreshold * distanceThreshold;\n\tconst float xBreakThreshold = 2.4f;\n\tfloat collisionRMultiplier = 1.0f;\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleSelection.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleTrails.h\"\n\n#include \"UX/camera.h\"\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\n\nclass ParticleSelection {\npublic:\n\n\tbool invertParticleSelection = false;\n\tbool deselectParticles = false;\n\n\tbool selectManyClusters = false;\n\t\n\tParticleSelection();\n\n\tvoid clusterSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger);\n\n\tvoid particleSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger);\n\n\tvoid manyClustersSelection(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid boxSelection(UpdateParameters& myParam, bool& is3DMode);\n\n\tvoid invertSelection(std::vector<ParticleRendering>& rParticles);\n\n\tvoid deselection(std::vector<ParticleRendering>& rParticles);\n\n\tvoid selectedParticlesStoring(UpdateParameters& myParam);\n\nprivate:\n\tfloat selectionThresholdSq = 100.0f;\n\n\tglm::vec2 boxInitialPos = { 0.0f, 0.0f };\n\n\tbool isBoxSelecting = false;\n\tbool isBoxDeselecting = false;\n};\n\nclass ParticleSelection3D {\npublic:\n\n\tbool invertParticleSelection = false;\n\tbool deselectParticles = false;\n\n\tbool selectManyClusters3D = false;\n\n\tParticleSelection3D();\n\n\tvoid clusterSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger);\n\n\tvoid particleSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger);\n\n\tvoid manyClustersSelection(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid boxSelection(UpdateParameters& myParam);\n\n\tvoid invertSelection(std::vector<ParticleRendering3D>& rParticles);\n\n\tvoid deselection(std::vector<ParticleRendering3D>& rParticles);\n\n\tvoid selectedParticlesStoring(UpdateParameters& myParam);\n\nprivate:\n\tfloat selectionThresholdAngle = -0.998f;\n\n\tglm::vec2 boxInitialPos = { 0.0f, 0.0f };\n\n\tbool isBoxSelecting = false;\n\tbool isBoxDeselecting = false;\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleSpaceship.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Physics/materialsSPH.h\"\n#include \"IO/io.h\"\n\nclass ParticleSpaceship {\npublic:\n\n\tint gasMultiplier = 1;\n\n\tbool isShipEnabled = false;\n\n\tvoid spaceshipLogic(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, bool& isShipGasEnabled) {\n\n\t\tfloat lifeDt = GetFrameTime();\n\n\t\tfor (size_t i = 0; i < pParticles.size(); ++i) {\n\t\t\tif (rParticles[i].lifeSpan > 0.0f) {\n\t\t\t\trParticles[i].lifeSpan -= lifeDt;\n\t\t\t}\n\t\t}\n\n\t\tfor (size_t i = 0; i < pParticles.size();) {\n\t\t\tif (rParticles[i].lifeSpan <= 0.0f && rParticles[i].lifeSpan != -1.0f) {\n\t\t\t\tpParticles.erase(pParticles.begin() + i);\n\t\t\t\trParticles.erase(rParticles.begin() + i);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t++i;\n\t\t\t}\n\t\t}\n\n\t\tif (!isShipEnabled) {\n\t\t\treturn;\n\t\t};\n\n\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\t\tif (!rParticles[i].isSelected) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse {\n\n\t\t\t\t// I set it to rock's mass\n\t\t\t\tpParticles[i].mass = 8500000000.0f * rock.massMult;\n\t\t\t\tpParticles[i].sphMass = rock.massMult;\n\t\t\t}\n\n\n\t\t\tif (IO::shortcutDown(KEY_UP)) {\n\t\t\t\tpParticles[i].acc.y -= acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics(\n\t\t\t\t\t\t\tglm::vec2{ pParticles[i].pos.x + (4.0f * normalRand - 2.0f), pParticles[i].pos.y + 3.3f },\n\t\t\t\t\t\t\tglm::vec2{ pParticles[i].vel.x + (4.0f * normalRand - 2.0f), pParticles[i].vel.y + 10.0f },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (IO::shortcutDown(KEY_RIGHT)) {\n\t\t\t\tpParticles[i].acc.x += acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics(\n\t\t\t\t\t\t\tglm::vec2{ pParticles[i].pos.x - 3.3f, pParticles[i].pos.y + (4.0f * normalRand - 2.0f) },\n\t\t\t\t\t\t\tglm::vec2{ pParticles[i].vel.x - 10.0f, pParticles[i].vel.y + (4.0f * normalRand - 2.0f) },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (IO::shortcutDown(KEY_DOWN)) {\n\t\t\t\tpParticles[i].acc.y += acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics(\n\t\t\t\t\t\t\tglm::vec2{ pParticles[i].pos.x + (4.0f * normalRand - 2.0f), pParticles[i].pos.y - 3.3f },\n\t\t\t\t\t\t\tglm::vec2{ pParticles[i].vel.x + (4.0f * normalRand - 2.0f), pParticles[i].vel.y - 10.0f },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (IO::shortcutDown(KEY_LEFT)) {\n\t\t\t\tpParticles[i].acc.x -= acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics(\n\t\t\t\t\t\t\tglm::vec2{ pParticles[i].pos.x + 3.3f, pParticles[i].pos.y + (4.0f * normalRand - 2.0f) },\n\t\t\t\t\t\t\tglm::vec2{ pParticles[i].vel.x + 10.0f, pParticles[i].vel.y + (4.0f * normalRand - 2.0f) },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfloat arrowLength = 10.0f;\n\n\tvoid spaceshipLogic3D(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, bool& isShipGasEnabled, Camera3D& cam3D) {\n\n\t\tfloat lifeDt = GetFrameTime();\n\n\t\tglm::vec3 camPos(cam3D.position.x, cam3D.position.y, cam3D.position.z);\n\t\tglm::vec3 camTarget(cam3D.target.x, cam3D.target.y, cam3D.target.z);\n\t\tglm::vec3 camUpGlobal(0.0f, 1.0f, 0.0f);\n\n\t\tglm::vec3 forwardDir = glm::normalize(camTarget - camPos);\n\n\t\tglm::vec3 rightDir = glm::normalize(glm::cross(forwardDir, camUpGlobal));\n\n\t\tglm::vec3 upDir = glm::normalize(glm::cross(rightDir, forwardDir));\n\n\t\tfor (size_t i = 0; i < pParticles.size(); ++i) {\n\t\t\tif (rParticles[i].lifeSpan > 0.0f) {\n\t\t\t\trParticles[i].lifeSpan -= lifeDt;\n\t\t\t}\n\t\t}\n\n\t\tfor (size_t i = 0; i < pParticles.size();) {\n\t\t\tif (rParticles[i].lifeSpan <= 0.0f && rParticles[i].lifeSpan != -1.0f) {\n\t\t\t\tpParticles.erase(pParticles.begin() + i);\n\t\t\t\trParticles.erase(rParticles.begin() + i);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t++i;\n\t\t\t}\n\t\t}\n\n\t\tif (pParticles.empty()) {\n\t\t\tisShipGasEnabled = false;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isShipEnabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\t\tif (!rParticles[i].isSelected) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Set mass to rock's mass\n\t\t\tpParticles[i].mass = 8500000000.0f * rock.massMult;\n\t\t\tpParticles[i].sphMass = rock.massMult;\n\n\t\t\tDrawLine3D({ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z },\n\t\t\t\t{ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z - arrowLength - arrowLength * 0.5f },\n\t\t\t\tRED);\n\n\t\t\t// UP ARROW - Front (forward in Z)\n\t\t\tif (IO::shortcutDown(KEY_UP)) {\n\n\t\t\t\tDrawCube({ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z - arrowLength * 0.5f },\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\tarrowLength,\n\t\t\t\t\tRED);\n\t\t\t\tDrawCylinderEx({ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z - arrowLength },\n\t\t\t\t\t{ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z - arrowLength - arrowLength * 0.5f },\n\t\t\t\t\trParticles[i].totalRadius * 2.0f,\n\t\t\t\t\trParticles[i].totalRadius * 0.1f,\n\t\t\t\t\t12,\n\t\t\t\t\tRED);\n\n\t\t\t\tpParticles[i].acc.z -= acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\t\tfloat normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics3D(\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].pos.x + (4.0f * normalRand1 - 2.0f), pParticles[i].pos.y + (4.0f * normalRand2 - 2.0f), pParticles[i].pos.z + 3.3f },\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].vel.x + (4.0f * normalRand1 - 2.0f), pParticles[i].vel.y + (4.0f * normalRand2 - 2.0f), pParticles[i].vel.z + 10.0f },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering3D(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse, false, false, true, true, false, true,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// RIGHT ARROW - Right\n\t\t\tif (IO::shortcutDown(KEY_RIGHT)) {\n\n\t\t\t\tDrawCube({ pParticles[i].pos.x + arrowLength * 0.5f, pParticles[i].pos.y, pParticles[i].pos.z },\n\t\t\t\t\tarrowLength,\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\tGREEN);\n\t\t\t\tDrawCylinderEx({ pParticles[i].pos.x + arrowLength, pParticles[i].pos.y, pParticles[i].pos.z },\n\t\t\t\t\t{ pParticles[i].pos.x + arrowLength + arrowLength * 0.5f, pParticles[i].pos.y, pParticles[i].pos.z },\n\t\t\t\t\trParticles[i].totalRadius * 2.0f,\n\t\t\t\t\trParticles[i].totalRadius * 0.1f,\n\t\t\t\t\t12,\n\t\t\t\t\tGREEN);\n\n\t\t\t\tpParticles[i].acc.x += acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\t\tfloat normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics3D(\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].pos.x - 3.3f, pParticles[i].pos.y + (4.0f * normalRand1 - 2.0f), pParticles[i].pos.z + (4.0f * normalRand2 - 2.0f) },\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].vel.x - 10.0f, pParticles[i].vel.y + (4.0f * normalRand1 - 2.0f), pParticles[i].vel.z + (4.0f * normalRand2 - 2.0f) },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering3D(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse, false, false, true, true, false, true,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// DOWN ARROW - Back\n\t\t\tif (IO::shortcutDown(KEY_DOWN)) {\n\n\t\t\t\tDrawCube({ pParticles[i].pos.x, pParticles[i].pos.y, pParticles[i].pos.z + arrowLength * 0.5f },\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\tarrowLength,\n\t\t\t\t\tRED);\n\t\t\t\tDrawCylinderEx({ pParticles[i].pos.x, pParticles[i].pos.y, pParticles[i].pos.z + arrowLength },\n\t\t\t\t\t{ pParticles[i].pos.x, pParticles[i].pos.y, pParticles[i].pos.z + arrowLength + arrowLength * 0.5f },\n\t\t\t\t\trParticles[i].totalRadius * 2.0f,\n\t\t\t\t\trParticles[i].totalRadius * 0.1f,\n\t\t\t\t\t12,\n\t\t\t\t\tRED);\n\n\t\t\t\tpParticles[i].acc.z += acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\t\tfloat normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics3D(\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].pos.x + (4.0f * normalRand1 - 2.0f), pParticles[i].pos.y + (4.0f * normalRand2 - 2.0f), pParticles[i].pos.z - 3.3f },\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].vel.x + (4.0f * normalRand1 - 2.0f), pParticles[i].vel.y + (4.0f * normalRand2 - 2.0f), pParticles[i].vel.z - 10.0f },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering3D(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse, false, false, true, true, false, true,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// LEFT ARROW - Left\n\t\t\tif (IO::shortcutDown(KEY_LEFT)) {\n\n\t\t\t\tDrawCube({ pParticles[i].pos.x - arrowLength * 0.5f, pParticles[i].pos.y, pParticles[i].pos.z },\n\t\t\t\t\tarrowLength,\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\tGREEN);\n\t\t\t\tDrawCylinderEx({ pParticles[i].pos.x - arrowLength, pParticles[i].pos.y, pParticles[i].pos.z },\n\t\t\t\t\t{ pParticles[i].pos.x - arrowLength - arrowLength * 0.5f, pParticles[i].pos.y, pParticles[i].pos.z },\n\t\t\t\t\trParticles[i].totalRadius * 2.0f,\n\t\t\t\t\trParticles[i].totalRadius * 0.1f,\n\t\t\t\t\t12,\n\t\t\t\t\tGREEN);\n\n\t\t\t\tpParticles[i].acc.x -= acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\t\tfloat normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics3D(\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].pos.x + 3.3f, pParticles[i].pos.y + (4.0f * normalRand1 - 2.0f), pParticles[i].pos.z + (4.0f * normalRand2 - 2.0f) },\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].vel.x + 10.0f, pParticles[i].vel.y + (4.0f * normalRand1 - 2.0f), pParticles[i].vel.z + (4.0f * normalRand2 - 2.0f) },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering3D(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse, false, false, true, true, false, true,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// SHIFT - Up\n\t\t\tif ((IO::shortcutDown(KEY_LEFT_SHIFT) || IO::shortcutDown(KEY_RIGHT_SHIFT))\n\t\t\t\t&& (!IO::mouseDown(0) || !IO::mouseDown(1) || !IO::mouseDown(2))) {\n\n\t\t\t\tDrawCube({ pParticles[i].pos.x, pParticles[i].pos.y + arrowLength * 0.5f, pParticles[i].pos.z },\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\tarrowLength,\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\tBLUE);\n\t\t\t\tDrawCylinderEx({ pParticles[i].pos.x, pParticles[i].pos.y + arrowLength, pParticles[i].pos.z },\n\t\t\t\t\t{ pParticles[i].pos.x, pParticles[i].pos.y + arrowLength + arrowLength * 0.5f, pParticles[i].pos.z },\n\t\t\t\t\trParticles[i].totalRadius * 2.0f,\n\t\t\t\t\trParticles[i].totalRadius * 0.1f,\n\t\t\t\t\t12,\n\t\t\t\t\tBLUE);\n\n\t\t\t\tpParticles[i].acc.y += acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\t\tfloat normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics3D(\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].pos.x + (4.0f * normalRand1 - 2.0f), pParticles[i].pos.y + 3.3f, pParticles[i].pos.z + (4.0f * normalRand2 - 2.0f) },\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].vel.x + (4.0f * normalRand1 - 2.0f), pParticles[i].vel.y + 10.0f, pParticles[i].vel.z + (4.0f * normalRand2 - 2.0f) },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering3D(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse, false, false, true, true, false, true,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// CTRL - Down\n\t\t\tif ((IO::shortcutDown(KEY_LEFT_CONTROL) || IO::shortcutDown(KEY_RIGHT_CONTROL))\n\t\t\t\t&& (!IO::mouseDown(0) || !IO::mouseDown(1) || !IO::mouseDown(2))) {\n\n\t\t\t\tDrawCube({ pParticles[i].pos.x, pParticles[i].pos.y - arrowLength * 0.5f, pParticles[i].pos.z },\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\tarrowLength,\n\t\t\t\t\trParticles[i].totalRadius * 0.5f,\n\t\t\t\t\tBLUE);\n\t\t\t\tDrawCylinderEx({ pParticles[i].pos.x, pParticles[i].pos.y - arrowLength, pParticles[i].pos.z },\n\t\t\t\t\t{ pParticles[i].pos.x, pParticles[i].pos.y - arrowLength - arrowLength * 0.5f, pParticles[i].pos.z },\n\t\t\t\t\trParticles[i].totalRadius * 2.0f,\n\t\t\t\t\trParticles[i].totalRadius * 0.1f,\n\t\t\t\t\t12,\n\t\t\t\t\tBLUE);\n\n\t\t\t\tpParticles[i].acc.y -= acceleration;\n\n\t\t\t\tif (isShipGasEnabled) {\n\t\t\t\t\tfor (int g = 0; g < gasMultiplier; g++) {\n\t\t\t\t\t\tfloat normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\t\tfloat normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\t\tpParticles.emplace_back(ParticlePhysics3D(\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].pos.x + (4.0f * normalRand1 - 2.0f), pParticles[i].pos.y - 3.3f, pParticles[i].pos.z + (4.0f * normalRand2 - 2.0f) },\n\t\t\t\t\t\t\tglm::vec3{ pParticles[i].vel.x + (4.0f * normalRand1 - 2.0f), pParticles[i].vel.y - 10.0f, pParticles[i].vel.z + (4.0f * normalRand2 - 2.0f) },\n\t\t\t\t\t\t\t8500000000.0f * water.massMult * 0.1f,\n\t\t\t\t\t\t\twater.restDens,\n\t\t\t\t\t\t\twater.stiff,\n\t\t\t\t\t\t\twater.visc,\n\t\t\t\t\t\t\twater.cohesion\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.emplace_back(ParticleRendering3D(\n\t\t\t\t\t\t\twater.color,\n\t\t\t\t\t\t\trParticles[i].size * 0.7f,\n\t\t\t\t\t\t\tfalse, false, false, true, true, false, true,\n\t\t\t\t\t\t\t3.0f,\n\t\t\t\t\t\t\twater.id\n\t\t\t\t\t\t));\n\n\t\t\t\t\t\trParticles.back().sphColor = water.color;\n\t\t\t\t\t\tpParticles.back().temp = 440.0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfloat acceleration = 4.0f;\n\nprivate:\n\n\tSPHWater water;\n\tSPHRock rock;\n\n\tint selectionIdx = 0;\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleSubdivision.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\nstruct ParticleSubdivision {\n\tint particlesThreshold = 80000;\n\n\tbool subdivideAll = false;\n\tbool subdivideSelected = false;\n\n\tvoid subdivideParticles(UpdateVariables& myVar, UpdateParameters& myParam);\n\tvoid subdivideParticles3D(UpdateVariables& myVar, UpdateParameters& myParam);\n\nprivate:\n\tbool confirmState = false;\n\tbool quitState = false;\n\n\tstd::string warningText = \"Subdividing further might slow down the program a lot\";\n\n\tfloat textSize = 25.0f;\n\tfloat textSpacing = 6.0f;\n\n\tglm::vec2 buttonSize = { 200.0f, 50.0f };\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleTrails.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\n\nclass ParticleTrails {\npublic:\n\tfloat size = 5;\n\tfloat trailThickness = 0.5f;\n\tbool whiteTrails = false;\n\tstruct Segment {\n\t\tglm::vec2 start;\n\t\tglm::vec2 end;\n\t\tglm::vec2 offset;\n\t\tglm::vec2 prevOffset;\n\t\tColor color;\n\t};\n\tstruct Segment3D {\n\t\tglm::vec3 start;\n\t\tglm::vec3 end;\n\t\tglm::vec3 offset;\n\t\tglm::vec3 prevOffset;\n\t\tColor color;\n\t};\n\tstd::vector<Segment> segments;\n\tstd::vector<Segment3D> segments3D;\n\tglm::vec2 selectedParticlesAveragePos = { 0.0f, 0.0f };\n\tglm::vec2 selectedParticlesAveragePrevPos = { 0.0f, 0.0f };\n\tglm::vec3 selectedParticlesAveragePos3D = { 0.0f, 0.0f, 0.0f };\n\tglm::vec3 selectedParticlesAveragePrevPos3D = { 0.0f, 0.0f, 0.0f };\n\tvoid trailLogic(UpdateVariables& myVar, UpdateParameters& myParam);\n\tvoid drawTrail(std::vector<ParticleRendering>& rParticles, Texture2D& particleBlur);\n\tvoid trailLogic3D(UpdateVariables& myVar, UpdateParameters& myParam);\n\tvoid drawTrail3D(std::vector<ParticleRendering3D>& rParticles3D, Texture2D& particleBlur, Camera3D& cam3D);\nprivate:\n\tbool wasLocalTrailsEnabled = false;\n};"
  },
  {
    "path": "GalaxyEngine/include/Particles/particlesSpawning.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Physics/slingshot.h\"\n\n#include \"Physics/constraint.h\"\n\n#include \"UI/brush.h\"\n\n#include \"UX/camera.h\"\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\nstruct Physics;\nstruct Physics3D;\nstruct Quadtree;\n\nclass ParticlesSpawning {\npublic:\n\n\tconst int correctionSubsteps = 32;\n\tbool particlesIterating = false;\n\n\tbool massMultiplierEnabled = true;\n\n\tfloat outerRadius = 200.0f;\n\tfloat scaleLength = 90.0f;\n\n\tfloat outerRadiusDM = 2000.0f;\n\tfloat radiusCoreDM = 3.5f;\n\n\tvoid particlesInitialConditions(Physics& physics, UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid predictTrajectory(const std::vector<ParticlePhysics>& actualParticles, \n\t\tSceneCamera& myCamera, Physics physics, UpdateVariables& myVar, Slingshot& slingshot);\n\n\tvoid drawGalaxyDisplay(UpdateParameters& myParam);\n\nprivate:\n\n\tfloat heavyParticleInitMass = 300000000000000.0f;\n};\n\nclass ParticlesSpawning3D {\npublic:\n\n\tconst int correctionSubsteps = 24;\n\tbool particlesIterating = false;\n\n\tbool massMultiplierEnabled = true;\n\n\tfloat diskAxisX = 90.0f;\n\tfloat diskAxisY = 0.0f;\n\tfloat diskAxisZ = 0.0f;\n\n\tfloat outerRadius = 120.0f;\n\tfloat radiusCore = 2.5f;\n\n\tfloat diskThickness = 0.5f;\n\tfloat bulgeThickness = 3.0f;\n\tfloat bulgeSize = 1200.0f;\n\n\tfloat outerRadiusDM = 2000.0f;\n\tfloat radiusCoreDM = 3.5f;\n\n\tvoid particlesInitialConditions(Physics3D& physics3D, UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid predictTrajectory(const std::vector<ParticlePhysics3D>& pParticles,\n\t\tSceneCamera3D& myCamera, Physics3D physics3D,\n\t\tUpdateVariables& myVar, UpdateParameters& myParam, Slingshot3D& slingshot);\n\n\tvoid drawGalaxyDisplay(UpdateParameters& myParam);\n\nprivate:\n\n\tfloat heavyParticleInitMass = 300000000000000.0f;\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/SPH.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"parameters.h\"\n#include \"Particles/QueryNeighbors.h\"\n\nstruct UpdateVariables;\nstruct UpdateVariables;\n\nstruct GridCell {\n\tstd::vector<size_t> particleIndices;\n};\n\nclass SPH {\npublic:\n\tfloat radiusMultiplier = 3.0f;\n\tconst float boundDamping = -0.1f;\n\n\tfloat densTolerance = 0.08f;\n\n\tint maxIter = 1; // I keep only 1 iteration when I don't use the density error condition\n\tint iter = 0;\n\n\tfloat rhoError = 0.0f;\n\n\tfloat cellSize;\n\n\tSPH() : cellSize(radiusMultiplier) {}\n\n\tvoid computeViscCohesionForcesWithCache(\n\t\tstd::vector<ParticlePhysics>& pParticles,\n\t\tstd::vector<ParticleRendering>& rParticles,\n\t\tstd::vector<glm::vec2>& forces,\n\t\tconst std::vector<std::vector<size_t>>& neighborCache,\n\t\tsize_t N);\n\n\tfloat smoothingKernel(float dst, float radiusMultiplier) {\n\t\tif (dst >= radiusMultiplier) return 0.0f;\n\n\t\tfloat volume = (PI * pow(radiusMultiplier, 4.0f)) / 6.0f;\n\t\treturn (radiusMultiplier - dst) * (radiusMultiplier - dst) / volume;\n\t}\n\n\tfloat spikyKernelDerivative(float dst, float radiusMultiplier) {\n\t\tif (dst >= radiusMultiplier) return 0.0f;\n\n\t\tfloat scale = -45.0f / (PI * pow(radiusMultiplier, 6.0f));\n\t\treturn scale * pow(radiusMultiplier - dst, 2.0f);\n\t}\n\n\tfloat smoothingKernelLaplacian(float dst, float radiusMultiplier) {\n\t\tif (dst >= radiusMultiplier) return 0.0f;\n\n\t\tfloat scale = 45.0f / (PI * pow(radiusMultiplier, 6.0f));\n\t\treturn scale;\n\t}\n\n\tfloat smoothingKernelCohesion(float r, float h) {\n\t\tif (r >= h) return 0.0f;\n\n\t\tfloat q = r / h;\n\t\treturn (1.0f - q) * (0.5f - q) * (0.5f - q) * 30.0f / (PI * h * h);\n\t}\n\n\t// Currently unused\n\tfloat computeDelta(const std::vector<glm::vec2>& kernelGradients, float dt, float mass, float restDensity) {\n\t\tfloat beta = (dt * dt * mass * mass) / (restDensity * restDensity);\n\n\t\tglm::vec2 sumGradW = { 0.0f, 0.0f };\n\t\tfloat sumGradW_dot = 0.0f;\n\n\t\tfor (const glm::vec2& gradW : kernelGradients) {\n\t\t\tsumGradW.x += gradW.x;\n\t\t\tsumGradW.y += gradW.y;\n\n\t\t\tsumGradW_dot += gradW.x * gradW.x + gradW.y * gradW.y;\n\t\t}\n\n\t\tfloat sumDot = sumGradW.x * sumGradW.x + sumGradW.y * sumGradW.y;\n\n\t\tfloat delta = -1.0f / (beta * (-sumDot - sumGradW_dot));\n\n\t\treturn delta;\n\t}\n\n\t// New Grid Search\n\n\tstruct EntryArrays {\n\t\tstd::vector<uint32_t> cellKeys;\n\t\tstd::vector<uint32_t> particleIndices;\n\t\tstd::vector<int> cellXs;\n\t\tstd::vector<int> cellYs;\n\t\tsize_t size;\n\t};\n\n\tconst uint32_t hashTableSize = 16384;\n\tEntryArrays entries;\n\tstd::vector<uint32_t> startIndices;\n\n\tconst char* neighborSearchCompute = R\"(\n#version 430\n\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 10) buffer ParticlePositions {\n    vec2 pos[];\n};\n\nlayout(std430, binding = 11) buffer CellKeys {\n    uint cellKeys[];\n};\n\nlayout(std430, binding = 12) buffer ParticleIndices {\n    uint particleIndices[];\n};\n\nlayout(std430, binding = 13) buffer CellXs {\n    int cellXs[];\n};\n\nlayout(std430, binding = 14) buffer CellYs {\n    int cellYs[];\n};\n\nuniform float cellSize;\nuniform uint hashTableSize;\nuniform uint numParticles;\n\nuvec2 posToCellCoord(vec2 pos) {\n    int cellX = int(floor(pos.x / cellSize));\n    int cellY = int(floor(pos.y / cellSize));\n    return uvec2(cellX, cellY);\n}\n\nuint hashCell(int cellX, int cellY) {\n    uint h = uint(cellX * 73856093) ^ uint(cellY * 19349663);\n    return h % hashTableSize;\n}\n\nvoid main() {\n    uint i = gl_GlobalInvocationID.x;\n    if (i >= numParticles) return;\n\n    uvec2 cellCoord = posToCellCoord(pos[i]);\n    uint cellKey = hashCell(int(cellCoord.x), int(cellCoord.y));\n\n    cellKeys[i] = cellKey;\n    particleIndices[i] = i;\n    cellXs[i] = int(cellCoord.x);\n    cellYs[i] = int(cellCoord.y);\n}\n)\";\n\n\tGLuint ssboPPos, ssboCellKeys, ssboParticleIndices, ssboCellXs, ssboCellYs;\n\n\tsize_t mb = 512;\n\n\tsize_t reserveSize = (1024 * 1024 * mb) / sizeof(glm::vec2);\n\n\tGLuint neighborSearchProgram;\n\n\tvoid neighborSearchKernel() {\n\n\t\tglGenBuffers(1, &ssboPPos);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPPos);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(glm::vec2), nullptr, GL_STREAM_COPY);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, ssboPPos);\n\n\t\tglGenBuffers(1, &ssboCellKeys);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellKeys);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);\n\n\t\tglGenBuffers(1, &ssboParticleIndices);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticleIndices);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, ssboParticleIndices);\n\n\t\tglGenBuffers(1, &ssboCellXs);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellXs);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(int32_t), nullptr, GL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, ssboCellXs);\n\n\t\tglGenBuffers(1, &ssboCellYs);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellYs);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(int32_t), nullptr, GL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, ssboCellYs);\n\n\t\tneighborSearchProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &neighborSearchCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglAttachShader(neighborSearchProgram, shader);\n\t\tglLinkProgram(neighborSearchProgram);\n\n\t\tglGetProgramiv(neighborSearchProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetProgramInfoLog(neighborSearchProgram, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Shader neighborSearchProgram linking failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tstd::vector<glm::vec2> pPos;\n\n\tvoid gpuNeighborSearch(std::vector<ParticlePhysics>& pParticles) {\n\t\tif (pParticles.empty()) return;\n\t\tsize_t n = pParticles.size();\n\n\t\tentries.cellKeys.resize(n);\n\t\tentries.particleIndices.resize(n);\n\t\tentries.cellXs.resize(n);\n\t\tentries.cellYs.resize(n);\n\t\tpPos.resize(n);\n\n\t\tfor (size_t i = 0; i < n; i++) {\n\t\t\tpPos[i] = pParticles[i].pos;\n\t\t}\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPPos);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, n * sizeof(glm::vec2), pPos.data());\n\n\t\tglUseProgram(neighborSearchProgram);\n\t\tglUniform1f(glGetUniformLocation(neighborSearchProgram, \"cellSize\"), cellSize);\n\t\tglUniform1ui(glGetUniformLocation(neighborSearchProgram, \"hashTableSize\"), hashTableSize);\n\t\tglUniform1ui(glGetUniformLocation(neighborSearchProgram, \"numParticles\"), (GLuint)n);\n\n\t\tGLuint numGroups = (GLuint)((n + 255) / 256);\n\t\tglDispatchCompute(numGroups, 1, 1);\n\n\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\t}\n\n\tconst char* bitonicSortCompute = R\"(\n#version 430\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 11) buffer CellKeys {\n    uint cellKeys[];\n};\n\nlayout(std430, binding = 12) buffer ParticleIndices {\n    uint particleIndices[];\n};\n\nlayout(std430, binding = 13) buffer CellXs {\n    int cellXs[];\n};\n\nlayout(std430, binding = 14) buffer CellYs {\n    int cellYs[];\n};\n\nuniform uint numValues;\nuniform uint groupWidth;\nuniform uint groupHeight;\nuniform uint stepIndex;\n\nvoid main() {\n    uint i = gl_GlobalInvocationID.x;\n\n    uint hIndex = i & (groupWidth - 1);\n    uint indexLeft = hIndex + (groupHeight + 1) * (i / groupWidth);\n    uint rightStepSize = stepIndex == 0 ? groupHeight - 2 * hIndex : (groupHeight + 1) / 2;\n    uint indexRight = indexLeft + rightStepSize;\n\n    if (indexRight >= numValues) return;\n\n    uint valueLeft = cellKeys[indexLeft];\n    uint valueRight = cellKeys[indexRight];\n\n    if (valueLeft > valueRight)\n    {\n        uint tempCellKey = cellKeys[indexLeft];\n        cellKeys[indexLeft] = cellKeys[indexRight];\n        cellKeys[indexRight] = tempCellKey;\n\n        uint tempIndices = particleIndices[indexLeft];\n        particleIndices[indexLeft] = particleIndices[indexRight];\n        particleIndices[indexRight] = tempIndices;\n\n        int tempX = cellXs[indexLeft];\n        cellXs[indexLeft] = cellXs[indexRight];\n        cellXs[indexRight] = tempX;\n\n        int tempY = cellYs[indexLeft];\n        cellYs[indexLeft] = cellYs[indexRight];\n        cellYs[indexRight] = tempY;\n    }\n}\n)\";\n\n\tGLuint bitonicSortProgram;\n\n\tvoid bitonicSortKernel() {\n\n\t\tbitonicSortProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &bitonicSortCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglAttachShader(bitonicSortProgram, shader);\n\t\tglLinkProgram(bitonicSortProgram);\n\n\t\tglGetProgramiv(bitonicSortProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetProgramInfoLog(bitonicSortProgram, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Shader bitonicSortProgram linking failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tvoid gpuBitonicSort() {\n\t\tconst size_t n = entries.cellKeys.size();\n\t\tif (n == 0) return;\n\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, ssboParticleIndices);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, ssboCellXs);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, ssboCellYs);\n\n\t\tglUseProgram(bitonicSortProgram);\n\t\tglUniform1ui(glGetUniformLocation(bitonicSortProgram, \"numValues\"), n);\n\n\t\tint numStages = static_cast<int>(log2(nextPowerOfTwo(n)));\n\n\t\tfor (int stageIndex = 0; stageIndex < numStages; stageIndex++) {\n\t\t\tfor (int stepIndex = 0; stepIndex < stageIndex + 1; stepIndex++) {\n\n\t\t\t\tint groupWidth = 1 << (stageIndex - stepIndex);\n\t\t\t\tint groupHeight = 2 * groupWidth - 1;\n\n\t\t\t\tglUniform1ui(glGetUniformLocation(bitonicSortProgram, \"groupWidth\"), static_cast<uint32_t>(groupWidth));\n\t\t\t\tglUniform1ui(glGetUniformLocation(bitonicSortProgram, \"groupHeight\"), static_cast<uint32_t>(groupHeight));\n\t\t\t\tglUniform1ui(glGetUniformLocation(bitonicSortProgram, \"stepIndex\"), static_cast<uint32_t>(stepIndex));\n\n\t\t\t\tGLuint numThreads = nextPowerOfTwo(n) / 2;\n\t\t\t\tGLuint numGroups = (numThreads + 255) / 256;\n\t\t\t\tglDispatchCompute(numGroups, 1, 1);\n\t\t\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst char* offsetCompute = R\"(\n#version 430\n\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 11) buffer CellKeys {\n    uint cellKeys[];\n};\n\n\nlayout(std430, binding = 15) buffer Offsets {\n    uint offsets[];\n};\n\nuniform uint numEntries;\nuniform uint hashTableSize;\n\nvoid main() {\n\n    uint i = gl_GlobalInvocationID.x; \n    if (i >= numEntries) return;\n\n    uint null = hashTableSize;\n\n    uint key = cellKeys[i]; \n\n    uint keyPrev = (i == 0) ? null : cellKeys[i - 1];\n\n    if (key != keyPrev && key < hashTableSize) {\n        offsets[key] = i;\n    }\n}\n)\";\n\n\tGLuint ssboOffsets;\n\n\tGLuint offsetProgram;\n\n\tvoid offsetKernel() {\n\n\t\tglGenBuffers(1, &ssboOffsets);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboOffsets);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER,\n\t\t\thashTableSize * sizeof(uint32_t),\n\t\t\tnullptr,\n\t\t\tGL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 15, ssboOffsets);\n\n\t\toffsetProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &offsetCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglAttachShader(offsetProgram, shader);\n\t\tglLinkProgram(offsetProgram);\n\n\t\tglGetProgramiv(offsetProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetProgramInfoLog(offsetProgram, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Shader offsetProgram linking failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tunsigned int nextPowerOfTwo(uint32_t n) {\n\t\tif (n == 0) return 1;\n\t\tn--;\n\t\tn |= n >> 1;\n\t\tn |= n >> 2;\n\t\tn |= n >> 4;\n\t\tn |= n >> 8;\n\t\tn |= n >> 16;\n\t\treturn n + 1;\n\t}\n\n\tconst char* offsetsResetCompute = R\"(\n#version 430\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 15) buffer Offsets {\n    uint offsets[];\n};\n\nuniform uint hashTableSize;\n\nvoid main() {\n    uint i = gl_GlobalInvocationID.x;\n    if (i >= hashTableSize) return;\n    offsets[i] = 4294967295; // UINT_MAX\n}\n)\";\n\n\tGLuint offsetsResetProgram;\n\n\tvoid offsetResetKernel() {\n\n\t\toffsetsResetProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &offsetsResetCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglAttachShader(offsetsResetProgram, shader);\n\t\tglLinkProgram(offsetsResetProgram);\n\n\t\tglGetProgramiv(offsetsResetProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetProgramInfoLog(offsetsResetProgram, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Shader offsetsResetProgram linking failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tvoid gpuOffsets() {\n\t\tconst size_t n = entries.cellKeys.size();\n\t\tif (n == 0) return;\n\n\t\tglUseProgram(offsetsResetProgram);\n\n\t\tglUniform1ui(glGetUniformLocation(offsetsResetProgram, \"hashTableSize\"), hashTableSize);\n\n\t\tglDispatchCompute((hashTableSize + 255) / 256, 1, 1);\n\n\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);\n\n\t\tglUseProgram(offsetProgram);\n\t\tglUniform1ui(glGetUniformLocation(offsetProgram, \"numEntries\"), (uint32_t)n);\n\t\tglUniform1ui(glGetUniformLocation(offsetProgram, \"hashTableSize\"), hashTableSize);\n\n\t\tglDispatchCompute((n + 255) / 256, 1, 1);\n\n\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboOffsets);\n\t\tuint32_t* ptrOffsets = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\t\tif (ptrOffsets) {\n\t\t\tmemcpy(startIndices.data(), ptrOffsets, hashTableSize * sizeof(uint32_t));\n\t\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\t\t}\n\t}\n\n\tvoid newGridGPU(std::vector<ParticlePhysics>& pParticles) {\n\t\tconst size_t n = pParticles.size();\n\t\tif (n == 0) return;\n\n\t\tentries.size = n;\n\t\tstartIndices.assign(hashTableSize, UINT32_MAX);\n\n\t\tgpuNeighborSearch(pParticles);\n\n\t\tgpuBitonicSort();\n\n\t\t/*if (n > 0) {\n\t\t\tstartIndices[entries.cellKeys[0]] = 0;\n\t\t}\n\t\tfor (size_t i = 1; i < n; i++) {\n\t\t\tif (entries.cellKeys[i - 1] != entries.cellKeys[i]) {\n\t\t\t\tstartIndices[entries.cellKeys[i]] = i;\n\t\t\t}\n\t\t}*/\n\n\t\tgpuOffsets();\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellKeys);\n\n\t\tuint32_t* ptrCellKeys = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n\t\tmemcpy(entries.cellKeys.data(), ptrCellKeys, entries.cellKeys.size() * sizeof(uint32_t));\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticleIndices);\n\n\t\tuint32_t* ptrIndices = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n\t\tmemcpy(entries.particleIndices.data(), ptrIndices, entries.particleIndices.size() * sizeof(uint32_t));\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellXs);\n\n\t\tuint32_t* ptrCellX = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n\t\tmemcpy(entries.cellXs.data(), ptrCellX, entries.cellXs.size() * sizeof(int32_t));\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellYs);\n\n\t\tuint32_t* ptrCellY = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n\t\tmemcpy(entries.cellYs.data(), ptrCellY, entries.cellYs.size() * sizeof(int32_t));\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\t}\n\n\tstd::vector<float> posX;\n\tstd::vector<float> posY;\n\tstd::vector<float> predPosX;\n\tstd::vector<float> predPosY;\n\tstd::vector<float> accX;\n\tstd::vector<float> accY;\n\tstd::vector<float> velX;\n\tstd::vector<float> velY;\n\tstd::vector<float> prevVelX;\n\tstd::vector<float> prevVelY;\n\tstd::vector<float> sphMass;\n\tstd::vector<float> press;\n\tstd::vector<float> pressFX;\n\tstd::vector<float> pressFY;\n\tstd::vector<float> stiff;\n\tstd::vector<float> visc;\n\tstd::vector<float> dens;\n\tstd::vector<float> predDens;\n\tstd::vector<float> restDens;\n\n\tvoid flattenParticles(std::vector<ParticlePhysics>& pParticles);\n\n\tvoid readFlattenBack(std::vector<ParticlePhysics>& pParticles);\n\n\tvoid computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector<glm::vec2>& sphForce, size_t& N);\n\n\tvoid groundModeBoundary(std::vector<ParticlePhysics>& pParticles,\n\t\tstd::vector<ParticleRendering>& rParticles,\n\t\tglm::vec2 domainSize, UpdateVariables& myVar);\n\n\tvoid PCISPH(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid pcisphSolver(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\t\t//newGridGPU(pParticles);\n\n\t\tPCISPH(myVar, myParam);\n\n\t\tif (myVar.sphGround) {\n\t\t\tgroundModeBoundary(myParam.pParticles, myParam.rParticles, myVar.domainSize, myVar);\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/SPH3D.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"parameters.h\"\n#include \"Particles/QueryNeighbors.h\"\n\nstruct UpdateVariables;\nstruct UpdateVariables;\n\nclass SPH3D {\npublic:\n\tfloat radiusMultiplier = 3.0f;\n\tconst float boundDamping = -0.1f;\n\n\tfloat densTolerance = 0.08f;\n\n\tint maxIter = 1; // I keep only 1 iteration when I don't use the density error condition\n\tint iter = 0;\n\n\tfloat rhoError = 0.0f;\n\n\tfloat cellSize;\n\n\tSPH3D() : cellSize(radiusMultiplier) {}\n\n\tfloat smoothingKernel(float dst, float radiusMultiplier) {\n\t\tif (dst >= radiusMultiplier) return 0.0f;\n\n\t\tfloat volume = (2.0f * PI * pow(radiusMultiplier, 5.0f)) / 15.0f;\n\t\treturn pow(radiusMultiplier - dst, 2.0f) / volume;\n\t}\n\n\tfloat spikyKernelDerivative(float dst, float radiusMultiplier) {\n\t\tif (dst >= radiusMultiplier) return 0.0f;\n\n\t\tfloat scale = -45.0f / (PI * pow(radiusMultiplier, 6.0f));\n\t\treturn scale * pow(radiusMultiplier - dst, 2.0f);\n\t}\n\n\tfloat smoothingKernelLaplacian(float dst, float radiusMultiplier) {\n\t\tif (dst >= radiusMultiplier) return 0.0f;\n\n\t\tfloat scale = 45.0f / (PI * pow(radiusMultiplier, 6.0f));\n\t\treturn scale * (radiusMultiplier - dst);\n\t}\n\n\tfloat smoothingKernelCohesion(float r, float h) {\n\t\tif (r >= h) return 0.0f;\n\n\t\tfloat q = r / h;\n\t\treturn (1.0f - q) * (0.5f - q) * (0.5f - q) * 30.0f / (PI * h * h * h);\n\t}\n\n\t// New Grid Search\n\n\tstruct EntryArrays {\n\t\tstd::vector<uint32_t> cellKeys;\n\t\tstd::vector<uint32_t> particleIndices;\n\t\tstd::vector<int> cellXs;\n\t\tstd::vector<int> cellYs;\n\t\tsize_t size;\n\t};\n\n\tconst uint32_t hashTableSize = 16384;\n\tEntryArrays entries;\n\tstd::vector<uint32_t> startIndices;\n\n\tconst char* neighborSearchCompute = R\"(\n#version 430\n\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 10) buffer ParticlePositions {\n    vec2 pos[];\n};\n\nlayout(std430, binding = 11) buffer CellKeys {\n    uint cellKeys[];\n};\n\nlayout(std430, binding = 12) buffer ParticleIndices {\n    uint particleIndices[];\n};\n\nlayout(std430, binding = 13) buffer CellXs {\n    int cellXs[];\n};\n\nlayout(std430, binding = 14) buffer CellYs {\n    int cellYs[];\n};\n\nuniform float cellSize;\nuniform uint hashTableSize;\nuniform uint numParticles;\n\nuvec2 posToCellCoord(vec2 pos) {\n    int cellX = int(floor(pos.x / cellSize));\n    int cellY = int(floor(pos.y / cellSize));\n    return uvec2(cellX, cellY);\n}\n\nuint hashCell(int cellX, int cellY) {\n    uint h = uint(cellX * 73856093) ^ uint(cellY * 19349663);\n    return h % hashTableSize;\n}\n\nvoid main() {\n    uint i = gl_GlobalInvocationID.x;\n    if (i >= numParticles) return;\n\n    uvec2 cellCoord = posToCellCoord(pos[i]);\n    uint cellKey = hashCell(int(cellCoord.x), int(cellCoord.y));\n\n    cellKeys[i] = cellKey;\n    particleIndices[i] = i;\n    cellXs[i] = int(cellCoord.x);\n    cellYs[i] = int(cellCoord.y);\n}\n)\";\n\n\tGLuint ssboPPos, ssboCellKeys, ssboParticleIndices, ssboCellXs, ssboCellYs;\n\n\tsize_t mb = 512;\n\n\tsize_t reserveSize = (1024 * 1024 * mb) / sizeof(glm::vec2);\n\n\tGLuint neighborSearchProgram;\n\n\tvoid neighborSearchKernel() {\n\n\t\tglGenBuffers(1, &ssboPPos);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPPos);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(glm::vec2), nullptr, GL_STREAM_COPY);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, ssboPPos);\n\n\t\tglGenBuffers(1, &ssboCellKeys);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellKeys);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);\n\n\t\tglGenBuffers(1, &ssboParticleIndices);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticleIndices);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, ssboParticleIndices);\n\n\t\tglGenBuffers(1, &ssboCellXs);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellXs);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(int32_t), nullptr, GL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, ssboCellXs);\n\n\t\tglGenBuffers(1, &ssboCellYs);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellYs);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(int32_t), nullptr, GL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, ssboCellYs);\n\n\t\tneighborSearchProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &neighborSearchCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglAttachShader(neighborSearchProgram, shader);\n\t\tglLinkProgram(neighborSearchProgram);\n\n\t\tglGetProgramiv(neighborSearchProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetProgramInfoLog(neighborSearchProgram, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Shader neighborSearchProgram linking failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tstd::vector<glm::vec2> pPos;\n\n\tvoid gpuNeighborSearch(std::vector<ParticlePhysics>& pParticles) {\n\t\tif (pParticles.empty()) return;\n\t\tsize_t n = pParticles.size();\n\n\t\tentries.cellKeys.resize(n);\n\t\tentries.particleIndices.resize(n);\n\t\tentries.cellXs.resize(n);\n\t\tentries.cellYs.resize(n);\n\t\tpPos.resize(n);\n\n\t\tfor (size_t i = 0; i < n; i++) {\n\t\t\tpPos[i] = pParticles[i].pos;\n\t\t}\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPPos);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, n * sizeof(glm::vec2), pPos.data());\n\n\t\tglUseProgram(neighborSearchProgram);\n\t\tglUniform1f(glGetUniformLocation(neighborSearchProgram, \"cellSize\"), cellSize);\n\t\tglUniform1ui(glGetUniformLocation(neighborSearchProgram, \"hashTableSize\"), hashTableSize);\n\t\tglUniform1ui(glGetUniformLocation(neighborSearchProgram, \"numParticles\"), (GLuint)n);\n\n\t\tGLuint numGroups = (GLuint)((n + 255) / 256);\n\t\tglDispatchCompute(numGroups, 1, 1);\n\n\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\t}\n\n\tconst char* bitonicSortCompute = R\"(\n#version 430\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 11) buffer CellKeys {\n    uint cellKeys[];\n};\n\nlayout(std430, binding = 12) buffer ParticleIndices {\n    uint particleIndices[];\n};\n\nlayout(std430, binding = 13) buffer CellXs {\n    int cellXs[];\n};\n\nlayout(std430, binding = 14) buffer CellYs {\n    int cellYs[];\n};\n\nuniform uint numValues;\nuniform uint groupWidth;\nuniform uint groupHeight;\nuniform uint stepIndex;\n\nvoid main() {\n    uint i = gl_GlobalInvocationID.x;\n\n    uint hIndex = i & (groupWidth - 1);\n    uint indexLeft = hIndex + (groupHeight + 1) * (i / groupWidth);\n    uint rightStepSize = stepIndex == 0 ? groupHeight - 2 * hIndex : (groupHeight + 1) / 2;\n    uint indexRight = indexLeft + rightStepSize;\n\n    if (indexRight >= numValues) return;\n\n    uint valueLeft = cellKeys[indexLeft];\n    uint valueRight = cellKeys[indexRight];\n\n    if (valueLeft > valueRight)\n    {\n        uint tempCellKey = cellKeys[indexLeft];\n        cellKeys[indexLeft] = cellKeys[indexRight];\n        cellKeys[indexRight] = tempCellKey;\n\n        uint tempIndices = particleIndices[indexLeft];\n        particleIndices[indexLeft] = particleIndices[indexRight];\n        particleIndices[indexRight] = tempIndices;\n\n        int tempX = cellXs[indexLeft];\n        cellXs[indexLeft] = cellXs[indexRight];\n        cellXs[indexRight] = tempX;\n\n        int tempY = cellYs[indexLeft];\n        cellYs[indexLeft] = cellYs[indexRight];\n        cellYs[indexRight] = tempY;\n    }\n}\n)\";\n\n\tGLuint bitonicSortProgram;\n\n\tvoid bitonicSortKernel() {\n\n\t\tbitonicSortProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &bitonicSortCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglAttachShader(bitonicSortProgram, shader);\n\t\tglLinkProgram(bitonicSortProgram);\n\n\t\tglGetProgramiv(bitonicSortProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetProgramInfoLog(bitonicSortProgram, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Shader bitonicSortProgram linking failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tvoid gpuBitonicSort() {\n\t\tconst size_t n = entries.cellKeys.size();\n\t\tif (n == 0) return;\n\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, ssboParticleIndices);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, ssboCellXs);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, ssboCellYs);\n\n\t\tglUseProgram(bitonicSortProgram);\n\t\tglUniform1ui(glGetUniformLocation(bitonicSortProgram, \"numValues\"), n);\n\n\t\tint numStages = static_cast<int>(log2(nextPowerOfTwo(n)));\n\n\t\tfor (int stageIndex = 0; stageIndex < numStages; stageIndex++) {\n\t\t\tfor (int stepIndex = 0; stepIndex < stageIndex + 1; stepIndex++) {\n\n\t\t\t\tint groupWidth = 1 << (stageIndex - stepIndex);\n\t\t\t\tint groupHeight = 2 * groupWidth - 1;\n\n\t\t\t\tglUniform1ui(glGetUniformLocation(bitonicSortProgram, \"groupWidth\"), static_cast<uint32_t>(groupWidth));\n\t\t\t\tglUniform1ui(glGetUniformLocation(bitonicSortProgram, \"groupHeight\"), static_cast<uint32_t>(groupHeight));\n\t\t\t\tglUniform1ui(glGetUniformLocation(bitonicSortProgram, \"stepIndex\"), static_cast<uint32_t>(stepIndex));\n\n\t\t\t\tGLuint numThreads = nextPowerOfTwo(n) / 2;\n\t\t\t\tGLuint numGroups = (numThreads + 255) / 256;\n\t\t\t\tglDispatchCompute(numGroups, 1, 1);\n\t\t\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst char* offsetCompute = R\"(\n#version 430\n\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 11) buffer CellKeys {\n    uint cellKeys[];\n};\n\n\nlayout(std430, binding = 15) buffer Offsets {\n    uint offsets[];\n};\n\nuniform uint numEntries;\nuniform uint hashTableSize;\n\nvoid main() {\n\n    uint i = gl_GlobalInvocationID.x; \n    if (i >= numEntries) return;\n\n    uint null = hashTableSize;\n\n    uint key = cellKeys[i]; \n\n    uint keyPrev = (i == 0) ? null : cellKeys[i - 1];\n\n    if (key != keyPrev && key < hashTableSize) {\n        offsets[key] = i;\n    }\n}\n)\";\n\n\tGLuint ssboOffsets;\n\n\tGLuint offsetProgram;\n\n\tvoid offsetKernel() {\n\n\t\tglGenBuffers(1, &ssboOffsets);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboOffsets);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER,\n\t\t\thashTableSize * sizeof(uint32_t),\n\t\t\tnullptr,\n\t\t\tGL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 15, ssboOffsets);\n\n\t\toffsetProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &offsetCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglAttachShader(offsetProgram, shader);\n\t\tglLinkProgram(offsetProgram);\n\n\t\tglGetProgramiv(offsetProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetProgramInfoLog(offsetProgram, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Shader offsetProgram linking failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tunsigned int nextPowerOfTwo(uint32_t n) {\n\t\tif (n == 0) return 1;\n\t\tn--;\n\t\tn |= n >> 1;\n\t\tn |= n >> 2;\n\t\tn |= n >> 4;\n\t\tn |= n >> 8;\n\t\tn |= n >> 16;\n\t\treturn n + 1;\n\t}\n\n\tconst char* offsetsResetCompute = R\"(\n#version 430\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 15) buffer Offsets {\n    uint offsets[];\n};\n\nuniform uint hashTableSize;\n\nvoid main() {\n    uint i = gl_GlobalInvocationID.x;\n    if (i >= hashTableSize) return;\n    offsets[i] = 4294967295; // UINT_MAX\n}\n)\";\n\n\tGLuint offsetsResetProgram;\n\n\tvoid offsetResetKernel() {\n\n\t\toffsetsResetProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &offsetsResetCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglAttachShader(offsetsResetProgram, shader);\n\t\tglLinkProgram(offsetsResetProgram);\n\n\t\tglGetProgramiv(offsetsResetProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar infoLog[512];\n\t\t\tglGetProgramInfoLog(offsetsResetProgram, 512, nullptr, infoLog);\n\t\t\tstd::cerr << \"Shader offsetsResetProgram linking failed:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tvoid gpuOffsets() {\n\t\tconst size_t n = entries.cellKeys.size();\n\t\tif (n == 0) return;\n\n\t\tglUseProgram(offsetsResetProgram);\n\n\t\tglUniform1ui(glGetUniformLocation(offsetsResetProgram, \"hashTableSize\"), hashTableSize);\n\n\t\tglDispatchCompute((hashTableSize + 255) / 256, 1, 1);\n\n\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);\n\n\t\tglUseProgram(offsetProgram);\n\t\tglUniform1ui(glGetUniformLocation(offsetProgram, \"numEntries\"), (uint32_t)n);\n\t\tglUniform1ui(glGetUniformLocation(offsetProgram, \"hashTableSize\"), hashTableSize);\n\n\t\tglDispatchCompute((n + 255) / 256, 1, 1);\n\n\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboOffsets);\n\t\tuint32_t* ptrOffsets = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\t\tif (ptrOffsets) {\n\t\t\tmemcpy(startIndices.data(), ptrOffsets, hashTableSize * sizeof(uint32_t));\n\t\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\t\t}\n\t}\n\n\tvoid newGridGPU(std::vector<ParticlePhysics>& pParticles) {\n\t\tconst size_t n = pParticles.size();\n\t\tif (n == 0) return;\n\n\t\tentries.size = n;\n\t\tstartIndices.assign(hashTableSize, UINT32_MAX);\n\n\t\tgpuNeighborSearch(pParticles);\n\n\t\tgpuBitonicSort();\n\n\t\t/*if (n > 0) {\n\t\t\tstartIndices[entries.cellKeys[0]] = 0;\n\t\t}\n\t\tfor (size_t i = 1; i < n; i++) {\n\t\t\tif (entries.cellKeys[i - 1] != entries.cellKeys[i]) {\n\t\t\t\tstartIndices[entries.cellKeys[i]] = i;\n\t\t\t}\n\t\t}*/\n\n\t\tgpuOffsets();\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellKeys);\n\n\t\tuint32_t* ptrCellKeys = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n\t\tmemcpy(entries.cellKeys.data(), ptrCellKeys, entries.cellKeys.size() * sizeof(uint32_t));\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticleIndices);\n\n\t\tuint32_t* ptrIndices = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n\t\tmemcpy(entries.particleIndices.data(), ptrIndices, entries.particleIndices.size() * sizeof(uint32_t));\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellXs);\n\n\t\tuint32_t* ptrCellX = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n\t\tmemcpy(entries.cellXs.data(), ptrCellX, entries.cellXs.size() * sizeof(int32_t));\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellYs);\n\n\t\tuint32_t* ptrCellY = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n\t\tmemcpy(entries.cellYs.data(), ptrCellY, entries.cellYs.size() * sizeof(int32_t));\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\t}\n\n\tstd::vector<float> posX;\n\tstd::vector<float> posY;\n\tstd::vector<float> predPosX;\n\tstd::vector<float> predPosY;\n\tstd::vector<float> accX;\n\tstd::vector<float> accY;\n\tstd::vector<float> velX;\n\tstd::vector<float> velY;\n\tstd::vector<float> prevVelX;\n\tstd::vector<float> prevVelY;\n\tstd::vector<float> sphMass;\n\tstd::vector<float> press;\n\tstd::vector<float> pressFX;\n\tstd::vector<float> pressFY;\n\tstd::vector<float> stiff;\n\tstd::vector<float> visc;\n\tstd::vector<float> dens;\n\tstd::vector<float> predDens;\n\tstd::vector<float> restDens;\n\n\tvoid flattenParticles(std::vector<ParticlePhysics>& pParticles);\n\n\tvoid readFlattenBack(std::vector<ParticlePhysics>& pParticles);\n\n\tvoid computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector<glm::vec3>& sphForce, size_t& N);\n\n\tvoid groundModeBoundary(std::vector<ParticlePhysics3D>& pParticles,\n\t\tstd::vector<ParticleRendering3D>& rParticles, glm::vec3 domainSize, UpdateVariables& myVar);\n\n\tvoid PCISPH(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid pcisphSolver(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\t\t//newGridGPU(pParticles);\n\n\t\tPCISPH(myVar, myParam);\n\n\t\tif (myVar.sphGround) {\n\t\t\tgroundModeBoundary(myParam.pParticles3D, myParam.rParticles3D, myVar.domainSize3D, myVar);\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/constraint.h",
    "content": "#pragma once\n\nstruct ParticleConstraint {\n\tuint32_t id1;\n\tuint32_t id2;\n\tfloat restLength;\n\tfloat originalLength;\n\tfloat stiffness;\n\tfloat resistance;\n\tfloat displacement;\n\tfloat plasticityPoint;\n\tbool isBroken;\n\tbool isPlastic;\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/field.h",
    "content": "#pragma once\n\n#include \"parameters.h\"\n\nstruct Cell {\n\tglm::vec2 pos;\n\tfloat size;\n\tColor col = { 5,5,5,255 };\n\tbool isActive = false;\n\tfloat strength = 0.0f;\n\n\tvoid drawCell() {\n\n\t\tDrawRectangleV({ pos.x, pos.y }, { size, size }, col);\n\t}\n};\n\nstruct Field {\n\n\tint res = 150;\n\n\tint amountX = res;\n\tint amountY = res;\n\n\tfloat cellSize = 10.0f;\n\n\tfloat gravityDisplayThreshold = 1000.0f;\n\n\tbool computeField = true;\n\n\tfloat gravityDisplaySoftness = 0.85f;\n\n\tstd::vector<Cell> cells;\n\n\tglm::vec2 prevDomainSize = { 0.0f, 0.0f };\n\n\tint prevRes = 150;\n\n\tvoid initializeCells(UpdateVariables& myVar) {\n\n\t\tif (prevDomainSize != myVar.domainSize) {\n\t\t\tcomputeField = true;\n\n\t\t\tprevDomainSize = myVar.domainSize;\n\t\t}\n\n\t\tif (prevRes != res) {\n\t\t\tcomputeField = true;\n\n\t\t\tprevRes = res;\n\t\t}\n\n\t\tif (computeField) {\n\n\t\t\tif (myVar.domainSize.x >= myVar.domainSize.y) {\n\t\t\t\tcellSize = myVar.domainSize.y / static_cast<float>(res);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcellSize = myVar.domainSize.x / static_cast<float>(res);\n\t\t\t}\n\n\t\t\tamountX = static_cast<int>(myVar.domainSize.x / cellSize);\n\t\t\tamountY = static_cast<int>(myVar.domainSize.y / cellSize);\n\n\t\t\tcells.clear();\n\t\t\tcells.resize(amountX * amountY);\n\n\t\t\tfor (int i = 0; i < amountX; i++) {\n\t\t\t\tfor (int j = 0; j < amountY; j++) {\n\n\t\t\t\t\tint index = j + i * amountY;\n\n\t\t\t\t\tcells[index].size = cellSize;\n\t\t\t\t\tcells[index].pos.x = i * cellSize;\n\t\t\t\t\tcells[index].pos.y = j * cellSize;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcomputeField = false;\n\t\t}\n\t}\n\n\t//void fieldLogic(UpdateParameters& myParam, UpdateVariables& myVar) {\n\n//#pragma omp parallel for schedule(dynamic)\n//\t\tfor (size_t i = 0; i < cells.size(); i++) {\n//\n//\t\t\tfloat force = 0.0f;\n//\n//\t\t\tfor (size_t j = 0; j < myParam.pParticles.size(); j++) {\n//\n//\t\t\t\tglm::vec2 d = myParam.pParticles[j].pos - (cells[i].pos + cells[i].size * 0.5f);\n//\t\t\t\tfloat distSq = d.x * d.x + d.y * d.y + myVar.softening * myVar.softening;\n//\n//\t\t\t\tfloat dist = glm::sqrt(distSq);\n//\n//\t\t\t\tif (dist > 0.0001f) {\n//\t\t\t\t\tforce += myVar.G * (myParam.pParticles[j].mass * 10.0f) / (dist * dist);\n//\t\t\t\t}\n//\t\t\t}\n//\n//\t\t\tcells[i].strength = force;\n//\t\t}\n\t//}\n\n\tconst char* fieldGravityCompute = R\"(\n#version 430\n\nlayout(local_size_x = 256) in;\n\nlayout(std430, binding = 7) buffer ParticlesPos {\n    float particlesPos[];\n};\n\nlayout(std430, binding = 8) buffer ParticlesMass {\n    float particlesMass[];\n};\n\nlayout(std430, binding = 9) buffer CellsData {\n    float cellsData[];\n};\n\nuniform int particleCount;\nuniform int cellCount;\n\nuniform float G;\nuniform float gravityDisplaySoftness;\n\nuniform vec2 domainSize;\nuniform int periodicBoundary;\n\nvoid main() {\n\n    uint i = gl_GlobalInvocationID.x;\n\n    if (i >= uint(cellCount))\n        return;\n\n    vec2 cellPos;\n    cellPos.x = cellsData[i];\n    cellPos.y = cellsData[i + cellCount];\n\n    float force = 0.0;\n\n    for (int j = 0; j < particleCount; j++) {\n\n        vec2 pPos;\n        pPos.x = particlesPos[j];\n        pPos.y = particlesPos[j + particleCount];\n\n        vec2 d = pPos - cellPos;\n\n        if (periodicBoundary == 1) {\n            d -= domainSize * round(d / domainSize);\n        }\n\n        float distSq = dot(d, d) + gravityDisplaySoftness * gravityDisplaySoftness;\n        float dist = sqrt(distSq);\n\n        if (dist > 0.0001) {\n            force += G * (particlesMass[j] * 10.0) / (dist * dist);\n        }\n    }\n\n    cellsData[i + 2 * cellCount] = force;\n}\n)\";\n\n\n\tconst char* fieldFragmentShader = R\"(\n#version 430\n\nlayout(std430, binding = 9) buffer CellsData {\n    float cellsData[];\n};\n\nuniform int amountX;\nuniform int amountY;\nuniform float cellSize;\nuniform float gravityDisplayThreshold;\nuniform float stretchFactor;\nuniform float exposure; \n\nuniform bool useTwoColorMode;\nuniform vec3 colorLow;\nuniform vec3 colorHigh;\n\nin vec2 fragPosition;\nout vec4 finalColor;\n\nvec3 hsv2rgb(vec3 c) {\n    vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);\n    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n}\n\nvoid main() {\n    int cellX = int(fragPosition.x / cellSize);\n    int cellY = int(fragPosition.y / cellSize);\n\n    if (cellX < 0 || cellX >= amountX || cellY < 0 || cellY >= amountY)\n        discard;\n\n    int cellIndex = cellY + cellX * amountY;\n    float strength = cellsData[cellIndex + 2 * amountX * amountY];\n\n    float clamped = clamp(strength, 0.0, gravityDisplayThreshold);\n    float normalized = clamped / gravityDisplayThreshold;\n    float stretched = log(1.0 + normalized * stretchFactor) / log(1.0 + stretchFactor);\n\n    vec3 color;\n\n    if (useTwoColorMode) {\n        vec3 base = mix(colorLow, colorHigh, stretched);\n        color = base * stretched; \n\n        color *= exposure; \n    } else {\n        float hue = (1.0 - stretched) * 240.0;\n        color = hsv2rgb(vec3(hue / 360.0, 1.0, stretched));\n    }\n\n    finalColor = vec4(color, 1.0);\n}\n)\";\n\n\tGLuint ssboParticlesPos, ssboParticlesMass, ssboCellsData;\n\tGLuint gravityDisplayProgram;\n\n\tconst char* fieldVertexShader = R\"(\n#version 430\nin vec3 vertexPosition;\nin vec2 vertexTexCoord;\n\nuniform mat4 mvp;\n\nout vec2 fragPosition;\n\nvoid main()\n{\n    fragPosition = vertexPosition.xy; \n    gl_Position = mvp * vec4(vertexPosition, 1.0);\n}\n)\";\n\n\tShader drawShader;\n\n\n\tvoid fieldGravityDisplayKernel() {\n\n\t\tdrawShader = LoadShaderFromMemory(fieldVertexShader, fieldFragmentShader);\n\n\t\tif (drawShader.id == 0) {\n\t\t\tstd::cout << \"Fragment shader failed to load\\n\";\n\t\t}\n\n\t\tconst size_t maxVRAM = size_t(2ull) * 1024 * 1024 * 1024;\n\t\tconst float safetyMargin = 0.85f;\n\n\t\tconst size_t usableVRAM = size_t(maxVRAM * safetyMargin);\n\n\t\tconst size_t bytesPerParticle = 6 * sizeof(float);\n\n\t\tconst size_t bytesPerFieldCell = 3 * sizeof(float);\n\t\tconst size_t fieldWidth = 1024;\n\t\tconst size_t fieldHeight = 1024;\n\n\t\tconst size_t fieldCells = fieldWidth * fieldHeight;\n\t\tconst size_t fieldBytes = fieldCells * bytesPerFieldCell;\n\n\t\tconst size_t maxParticles =\n\t\t\t(usableVRAM - fieldBytes) / bytesPerParticle;\n\n\t\tif (maxParticles < 10000000)\n\t\t{\n\t\t\tthrow std::runtime_error(\"GPU too small for 10 million particles.\");\n\t\t}\n\n\t\tstd::cout << \"Max particles supported by GPU: \" << maxParticles << \"\\n\";\n\n\t\tglGenBuffers(1, &ssboParticlesPos);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticlesPos);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER,\n\t\t\tmaxParticles * 2 * sizeof(float),\n\t\t\tnullptr,\n\t\t\tGL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, ssboParticlesPos);\n\n\t\tglGenBuffers(1, &ssboParticlesMass);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticlesMass);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER,\n\t\t\tmaxParticles * sizeof(float),\n\t\t\tnullptr,\n\t\t\tGL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, ssboParticlesMass);\n\n\t\tglGenBuffers(1, &ssboCellsData);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellsData);\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER,\n\t\t\tmaxParticles * 3 * sizeof(float),\n\t\t\tnullptr,\n\t\t\tGL_DYNAMIC_DRAW);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 9, ssboCellsData);\n\n\t\tgravityDisplayProgram = glCreateProgram();\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &fieldGravityCompute, nullptr);\n\t\tglCompileShader(shader);\n\n\t\tGLint success;\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar log[1024];\n\t\t\tglGetShaderInfoLog(shader, 1024, nullptr, log);\n\t\t\tstd::cerr << log << std::endl;\n\t\t}\n\n\t\tglAttachShader(gravityDisplayProgram, shader);\n\t\tglLinkProgram(gravityDisplayProgram);\n\n\t\tglGetProgramiv(gravityDisplayProgram, GL_LINK_STATUS, &success);\n\t\tif (!success) {\n\t\t\tchar log[1024];\n\t\t\tglGetProgramInfoLog(gravityDisplayProgram, 1024, nullptr, log);\n\t\t\tstd::cerr << log << std::endl;\n\t\t}\n\n\t\tglDeleteShader(shader);\n\t}\n\n\tstd::vector<float> particlesData;\n\tstd::vector<float> particlesMassVector;\n\tstd::vector<float> cellsData;\n\n\tvoid gpuGravityDisplay(UpdateParameters& myParam, UpdateVariables& myVar) {\n\n\t\tif (myParam.pParticles.empty() || !myVar.isGravityFieldEnabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tsize_t particleCount = myParam.pParticles.size();\n\n\t\tif (!myVar.gravityFieldDMParticles) {\n\n\t\t\tparticleCount = 0;\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\tif (myParam.rParticles[i].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tparticleCount++;\n\t\t\t}\n\t\t}\n\n\t\tsize_t cellCount = cells.size();\n\n\t\tparticlesData.resize(particleCount * 2);\n\t\tparticlesMassVector.resize(particleCount);\n\t\tcellsData.resize(cellCount * 3);\n\n\t\tsize_t writeIndex = 0;\n\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\n\t\t\tif (!myVar.gravityFieldDMParticles && myParam.rParticles[i].isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tparticlesData[writeIndex] = myParam.pParticles[i].pos.x;\n\t\t\tparticlesData[writeIndex + particleCount] = myParam.pParticles[i].pos.y;\n\t\t\tparticlesMassVector[writeIndex] = myParam.pParticles[i].mass;\n\n\t\t\twriteIndex++;\n\t\t}\n\n\t\tfor (size_t i = 0; i < cellCount; i++) {\n\t\t\tcellsData[i] = cells[i].pos.x;\n\t\t\tcellsData[i + cellCount] = cells[i].pos.y;\n\t\t\tcellsData[i + 2 * cellCount] = 0.0f;\n\t\t}\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticlesPos);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0,\n\t\t\tparticlesData.size() * sizeof(float),\n\t\t\tparticlesData.data());\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticlesMass);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0,\n\t\t\tparticlesMassVector.size() * sizeof(float),\n\t\t\tparticlesMassVector.data());\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellsData);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0,\n\t\t\tcellsData.size() * sizeof(float),\n\t\t\tcellsData.data());\n\n\t\tglUseProgram(gravityDisplayProgram);\n\n\t\tglUniform1i(glGetUniformLocation(gravityDisplayProgram, \"particleCount\"),\n\t\t\t(int)particleCount);\n\t\tglUniform1i(glGetUniformLocation(gravityDisplayProgram, \"cellCount\"),\n\t\t\t(int)cellCount);\n\n\t\tglUniform2f(glGetUniformLocation(gravityDisplayProgram, \"domainSize\"),\n\t\t\tmyVar.domainSize.x, myVar.domainSize.y);\n\t\tglUniform1f(glGetUniformLocation(gravityDisplayProgram, \"gravityDisplaySoftness\"),\n\t\t\tgravityDisplaySoftness);\n\t\tglUniform1i(glGetUniformLocation(gravityDisplayProgram, \"periodicBoundary\"),\n\t\t\tmyVar.isPeriodicBoundaryEnabled);\n\n\t\tglUniform1f(glGetUniformLocation(gravityDisplayProgram, \"G\"), myVar.G);\n\n\t\tGLuint groups = (cellCount + 255) / 256;\n\t\tglDispatchCompute(groups, 1, 1);\n\n\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellsData);\n\t\tfloat* ptr = (float*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n#pragma omp parallel for\n\t\tfor (size_t i = 0; i < cellCount; i++) {\n\t\t\tcells[i].strength = ptr[i + 2 * cellCount];\n\t\t}\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\t}\n\n\tfloat hue = 180.0f;\n\tfloat saturation = 0.8f;\n\tfloat value = 0.5f;\n\n\tfloat gravityStretchFactor = 90.0f;\n\n\tbool gravityCustomColors = false;\n\n\tfloat gravityExposure = 3.0f;\n\n\tvoid drawField(UpdateParameters& myParam, UpdateVariables& myVar)\n\t{\n\t\tBeginMode2D(myParam.myCamera.camera);\n\t\tBeginShaderMode(drawShader);\n\n\t\tint exposureLoc = GetShaderLocation(drawShader, \"exposure\");\n\n\t\tint amtXLoc = GetShaderLocation(drawShader, \"amountX\");\n\t\tint amtYLoc = GetShaderLocation(drawShader, \"amountY\");\n\t\tint sizeLoc = GetShaderLocation(drawShader, \"cellSize\");\n\t\tint threshLoc = GetShaderLocation(drawShader, \"gravityDisplayThreshold\");\n\t\tint stretchLoc = GetShaderLocation(drawShader, \"stretchFactor\");\n\n\t\tint lowLoc = GetShaderLocation(drawShader, \"colorLow\");\n\t\tint highLoc = GetShaderLocation(drawShader, \"colorHigh\");\n\n\t\tfloat lowColor[3] = {\n\t\t\tstatic_cast<float>(myParam.colorVisuals.pColor.r / 255.0f),\n\t\t\tstatic_cast<float>(myParam.colorVisuals.pColor.g / 255.0f),\n\t\t\tstatic_cast<float>(myParam.colorVisuals.pColor.b / 255.0f)\n\t\t};\n\n\t\tfloat highColor[3] = {\n\t\t\tstatic_cast<float>(myParam.colorVisuals.sColor.r / 255.0f),\n\t\t\tstatic_cast<float>(myParam.colorVisuals.sColor.g / 255.0f),\n\t\t\tstatic_cast<float>(myParam.colorVisuals.sColor.b / 255.0f)\n\t\t};\n\n\t\tSetShaderValue(drawShader, exposureLoc, &gravityExposure, SHADER_UNIFORM_FLOAT);\n\n\t\tSetShaderValue(drawShader, lowLoc, lowColor, SHADER_UNIFORM_VEC3);\n\t\tSetShaderValue(drawShader, highLoc, highColor, SHADER_UNIFORM_VEC3);\n\n\t\tint toggleLoc = GetShaderLocation(drawShader, \"useTwoColorMode\");\n\t\tint shaderBool = gravityCustomColors ? 1 : 0;\n\t\tSetShaderValue(drawShader, toggleLoc, &shaderBool, SHADER_UNIFORM_INT);\n\n\t\tSetShaderValue(drawShader, amtXLoc, &amountX, SHADER_UNIFORM_INT);\n\t\tSetShaderValue(drawShader, amtYLoc, &amountY, SHADER_UNIFORM_INT);\n\t\tSetShaderValue(drawShader, sizeLoc, &cellSize, SHADER_UNIFORM_FLOAT);\n\t\tSetShaderValue(drawShader, threshLoc, &gravityDisplayThreshold, SHADER_UNIFORM_FLOAT);\n\t\tSetShaderValue(drawShader, stretchLoc, &gravityStretchFactor, SHADER_UNIFORM_FLOAT);\n\n\t\tDrawRectangleV({ 0.0f, 0.0f }, { myVar.domainSize.x, myVar.domainSize.y }, WHITE);\n\n\t\tEndShaderMode();\n\t\tEndMode2D();\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/light.h",
    "content": "#pragma once\n\n#include \"UX/randNum.h\"\n#include \"IO/io.h\"\n#include \"parameters.h\"\n\nextern uint32_t globalWallId;\n\nstruct Wall {\n\n\tglm::vec2 vA;\n\tglm::vec2 vB;\n\n\tglm::vec2 normal{ 0.0f,0.0f };\n\tglm::vec2 normalVA{ 0.0f, 0.0f };\n\tglm::vec2 normalVB{ 0.0f, 0.0f };\n\n\tbool isBeingSpawned;\n\tbool vAisBeingMoved;\n\tbool vBisBeingMoved;\n\n\tColor apparentColor; // This is the color used to visualize the wall\n\n\tColor baseColor;\n\tColor specularColor;\n\tColor refractionColor;\n\tColor emissionColor;\n\n\tfloat baseColorVal;\n\tfloat specularColorVal;\n\tfloat refractionColorVal;\n\n\tfloat specularRoughness;\n\tfloat refractionRoughness;\n\tfloat refractionAmount;\n\tfloat IOR;\n\tfloat dispersionStrength;\n\n\tbool isShapeWall;\n\tbool isShapeClosed;\n\tuint32_t shapeId;\n\n\tuint32_t id;\n\n\tbool isSelected;\n\n\tWall() = default;\n\tWall(glm::vec2 vA, glm::vec2 vB, bool isShapeWall, Color baseColor, Color specularColor, Color refractionColor, Color emissionColor,\n\t\tfloat specularRoughness, float refractionRoughness, float refractionAmount, float IOR, float dispersionStrength) {\n\n\t\tthis->vA = vA;\n\t\tthis->vB = vB;\n\t\tthis->isShapeWall = isShapeWall;\n\t\tthis->isShapeClosed = false;\n\t\tthis->isSelected = false;\n\t\tthis->shapeId = 0;\n\t\tthis->isBeingSpawned = true;\n\t\tthis->vAisBeingMoved = false;\n\t\tthis->vBisBeingMoved = false;\n\t\tthis->baseColor = baseColor;\n\t\tthis->specularColor = specularColor;\n\t\tthis->refractionColor = refractionColor;\n\t\tthis->emissionColor = emissionColor;\n\n\t\tthis->apparentColor = WHITE;\n\n\t\tthis->baseColorVal = std::max({ baseColor.r, baseColor.g, baseColor.b }) * (baseColor.a / 255.0f) / 255.0f;\n\t\tthis->specularColorVal = std::max({ specularColor.r, specularColor.g, specularColor.b }) * (specularColor.a / 255.0f) / 255.0f;\n\t\tthis->refractionColorVal = std::max({ refractionColor.r, refractionColor.g, refractionColor.b }) * (refractionColor.a / 255.0f) / 255.0f;\n\n\t\tthis->specularRoughness = specularRoughness;\n\t\tthis->refractionRoughness = refractionRoughness;\n\t\tthis->refractionAmount = refractionAmount;\n\t\tthis->IOR = IOR;\n\t\tthis->dispersionStrength = dispersionStrength;\n\n\t\tthis->id = globalWallId++;\n\t}\n\n\tvoid drawHelper(glm::vec2& helper) {\n\t\tDrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);\n\t}\n\n\tvoid drawWall() {\n\t\tDrawLineV({ vA.x, vA.y }, { vB.x, vB.y }, apparentColor);\n\t}\n};\n\nextern uint32_t globalShapeId;\n\nenum ShapeType {\n\tcircle,\n\tdraw,\n\tlens\n};\n\nstruct Shape {\n\n\tstd::vector<Wall>* walls;\n\tstd::vector<uint32_t> myWallIds;\n\n\tstd::vector<glm::vec2> polygonVerts;\n\n\t/*enum class ShapeType {\n\t\tCircle1,\n\t\tCircle2,\n\t};*/\n\n\tColor baseColor;\n\tColor specularColor;\n\tColor refractionColor;\n\tColor emissionColor;\n\n\tfloat specularRoughness;\n\tfloat refractionRoughness;\n\n\tfloat refractionAmount;\n\n\tfloat IOR;\n\tfloat dispersionStrength;\n\n\tuint32_t id;\n\n\tglm::vec2 h1;\n\tglm::vec2 h2;\n\n\tbool isBeingSpawned;\n\tbool isBeingMoved;\n\n\tbool isShapeClosed;\n\n\tShapeType shapeType;\n\n\tbool drawHoverHelpers = false;\n\n\tShape() = default;\n\n\tShape(ShapeType shapeType, glm::vec2 h1, glm::vec2 h2, std::vector<Wall>* walls,\n\t\tColor baseColor, Color specularColor, Color refractionColor, Color emissionColor,\n\t\tfloat specularRoughness, float refractionRoughness, float refractionAmount, float IOR, float dispersionStrength) :\n\n\t\tshapeType(shapeType),\n\t\th1(h1),\n\t\th2(h2),\n\t\twalls(walls),\n\t\tbaseColor(baseColor),\n\t\tspecularColor(specularColor),\n\t\trefractionColor(refractionColor),\n\t\temissionColor(emissionColor),\n\t\tspecularRoughness(specularRoughness),\n\t\trefractionRoughness(refractionRoughness),\n\t\trefractionAmount(refractionAmount),\n\t\tIOR(IOR),\n\t\tdispersionStrength(dispersionStrength),\n\t\tid(globalShapeId++),\n\t\tisBeingSpawned(true),\n\t\tisBeingMoved(false),\n\t\tisShapeClosed(true) {\n\t}\n\n\tWall* getWallById(std::vector<Wall>& walls, uint32_t id) {\n\t\tfor (Wall& wall : walls) {\n\t\t\tif (wall.id == id)\n\t\t\t\treturn &wall;\n\t\t}\n\t\treturn nullptr;\n\t}\n\n\tfloat getSignedArea(const std::vector<glm::vec2>& vertices) {\n\t\tfloat area = 0.0f;\n\t\tint n = vertices.size();\n\n\t\tfor (int i = 0; i < n; ++i) {\n\t\t\tconst glm::vec2& current = vertices[i];\n\t\t\tconst glm::vec2& next = vertices[(i + 1) % n];\n\t\t\tarea += (current.x * next.y - next.x * current.y);\n\t\t}\n\n\t\treturn 0.5f * area;\n\t}\n\n\n\n\tvoid calculateWallsNormals() {\n\n\t\tint n = myWallIds.size();\n\t\tif (n == 0) return;\n\n\t\tpolygonVerts.clear();\n\n\t\tfor (uint32_t wallId : myWallIds) {\n\t\t\tWall* w = getWallById(*walls, wallId);\n\t\t\tif (w) {\n\t\t\t\tpolygonVerts.push_back(w->vA);\n\t\t\t}\n\t\t}\n\n\t\tbool flipNormals = getSignedArea(polygonVerts) > 0.0f;\n\n\t\tfor (uint32_t wallId : myWallIds) {\n\t\t\tWall* w = getWallById(*walls, wallId);\n\t\t\tif (!w) continue;\n\n\t\t\tif (!w->isShapeWall) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tglm::vec2 tangent = glm::normalize(w->vB - w->vA);\n\t\t\tglm::vec2 normal = flipNormals\n\t\t\t\t? glm::vec2(tangent.y, -tangent.x)\n\t\t\t\t: glm::vec2(-tangent.y, tangent.x);\n\n\t\t\tw->normal = normal;\n\t\t}\n\n\t\tconst float smoothingAngleThreshold = glm::cos(glm::radians(35.0f));\n\n\t\tfor (int i = 0; i < n; ++i) {\n\t\t\tint prev = (i - 1 + n) % n;\n\t\t\tint next = (i + 1) % n;\n\n\t\t\tuint32_t idPrev = myWallIds[prev];\n\t\t\tuint32_t id = myWallIds[i];\n\t\t\tuint32_t idNext = myWallIds[next];\n\n\t\t\tWall* wPrev = getWallById(*walls, idPrev);\n\t\t\tWall* w = getWallById(*walls, id);\n\t\t\tWall* wNext = getWallById(*walls, idNext);\n\n\t\t\tif (!wPrev || !w || !wNext) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!wPrev->isShapeWall || !w->isShapeWall || !wNext->isShapeWall) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat lenPrev = glm::length(wPrev->vB - wPrev->vA);\n\t\t\tfloat len = glm::length(w->vB - w->vA);\n\t\t\tfloat lenNext = glm::length(wNext->vB - wNext->vA);\n\n\t\t\tbool smoothWithPrev = glm::dot(wPrev->normal, w->normal) >= smoothingAngleThreshold;\n\t\t\tbool smoothWithNext = glm::dot(w->normal, wNext->normal) >= smoothingAngleThreshold;\n\n\t\t\tglm::vec2 tangent = glm::normalize(w->vB - w->vA);\n\n\t\t\tif (smoothWithPrev) {\n\t\t\t\tw->normalVA = glm::normalize(wPrev->normal * lenPrev + w->normal * len);\n\t\t\t}\n\t\t\telse if (smoothWithNext) {\n\t\t\t\tglm::vec2 normalVB = glm::normalize(w->normal * len + wNext->normal * lenNext);\n\t\t\t\tw->normalVA = normalVB - 2.0f * glm::dot(normalVB, tangent) * tangent;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tw->normalVA = w->normal;\n\t\t\t}\n\n\t\t\tif (smoothWithNext) {\n\t\t\t\tw->normalVB = glm::normalize(w->normal * len + wNext->normal * lenNext);\n\t\t\t}\n\t\t\telse if (smoothWithPrev) {\n\t\t\t\tw->normalVB = w->normalVA - 2.0f * glm::dot(w->normalVA, tangent) * tangent;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tw->normalVB = w->normal;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid relaxGeometryLogic(std::vector<glm::vec2>& vertices, int& shapeRelaxiter, float& shapeRelaxFactor) {\n\t\tif (vertices.size() < 3) return;\n\n\t\tstd::vector<glm::vec2> temp = vertices;\n\n\t\tfor (int iter = 0; iter < shapeRelaxiter; ++iter) {\n\t\t\tfor (size_t i = 0; i < vertices.size(); ++i) {\n\t\t\t\tsize_t prev = (i - 1 + vertices.size()) % vertices.size();\n\t\t\t\tsize_t next = (i + 1) % vertices.size();\n\n\t\t\t\tglm::vec2 average = (temp[prev] + temp[next]) * 0.5f;\n\n\t\t\t\tvertices[i] = glm::mix(temp[i], average, shapeRelaxFactor);\n\t\t\t}\n\t\t\ttemp = vertices;\n\t\t}\n\t}\n\n\tvoid relaxShape(int& shapeRelaxiter, float& shapeRelaxFactor) {\n\n\t\tpolygonVerts.clear();\n\n\t\tfor (uint32_t wallId : myWallIds) {\n\t\t\tWall* w = getWallById(*walls, wallId);\n\t\t\tif (w) {\n\t\t\t\tpolygonVerts.push_back(w->vA);\n\t\t\t}\n\t\t}\n\n\t\trelaxGeometryLogic(polygonVerts, shapeRelaxiter, shapeRelaxFactor);\n\n\t\tfor (size_t i = 0; i < myWallIds.size(); ++i) {\n\t\t\tuint32_t wallId = myWallIds[i];\n\t\t\tWall* w = getWallById(*walls, wallId);\n\n\t\t\tif (w) {\n\t\t\t\tw->vA = polygonVerts[i];\n\t\t\t\tw->vB = polygonVerts[(i + 1) % polygonVerts.size()];\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid makeShape() {\n\n\t\tif (shapeType == circle) {\n\t\t\tmakeCircle();\n\t\t}\n\n\t\tif (shapeType == draw) {\n\t\t\tdrawShape();\n\t\t}\n\n\t\tif (shapeType == lens) {\n\t\t\tmakeLens();\n\t\t}\n\t}\n\n\tbool createShapeFlag = false;\n\n\tstd::vector<glm::vec2> helpers;\n\n\tint circleSegments = 100;\n\n\tfloat circleRadius = 0.0f;\n\n\tvoid makeCircle() {\n\n\t\tif (isBeingSpawned) {\n\t\t\tcircleRadius = glm::length(h2 - h1);\n\t\t}\n\t\telse {\n\t\t\tcircleRadius = glm::length(helpers[0] - helpers[1]);\n\t\t}\n\n\n\t\tstd::vector<uint32_t> newWallIds;\n\n\t\tfor (int i = 0; i < circleSegments; ++i) {\n\t\t\tfloat theta1 = (2.0f * PI * i) / circleSegments;\n\t\t\tfloat theta2 = (2.0f * PI * (i + 1)) / circleSegments;\n\n\t\t\tif (!isBeingSpawned) {\n\t\t\t\th1 = helpers[0];\n\t\t\t}\n\n\t\t\tglm::vec2 vA = {\n\t\t\t\th1.x + cos(theta1) * circleRadius,\n\t\t\t\th1.y + sin(theta1) * circleRadius\n\t\t\t};\n\t\t\tglm::vec2 vB = {\n\t\t\t\th1.x + cos(theta2) * circleRadius,\n\t\t\t\th1.y + sin(theta2) * circleRadius\n\t\t\t};\n\n\t\t\tif (createShapeFlag) {\n\t\t\t\twalls->emplace_back(vA, vB, true, baseColor, specularColor, refractionColor, emissionColor,\n\t\t\t\t\tspecularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);\n\n\t\t\t\tWall& newWall = walls->back();\n\t\t\t\tnewWall.shapeId = id;\n\t\t\t\tnewWall.isShapeClosed = true;\n\n\t\t\t\tnewWallIds.push_back(newWall.id);\n\t\t\t}\n\n\t\t\tif (isBeingMoved && i < myWallIds.size()) {\n\t\t\t\tuint32_t wId = myWallIds[i];\n\t\t\t\tif (Wall* w = getWallById(*walls, wId)) {\n\t\t\t\t\tw->vA = vA;\n\t\t\t\t\tw->vB = vB;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (isBeingMoved) {\n\t\t\tcalculateWallsNormals();\n\t\t}\n\n\n\t\tif (createShapeFlag) {\n\t\t\tmyWallIds = std::move(newWallIds);\n\n\t\t\tcalculateWallsNormals();\n\n\t\t\tcreateShapeFlag = false;\n\t\t}\n\t}\n\n\tglm::vec2 prevPoint = h1;\n\n\tglm::vec2 oldDrawHelperPos{ 0.0f, 0.0f };\n\n\tvoid drawShape() {\n\t\tfloat maxSegmentLength = 4.0f;\n\n\t\tglm::vec2 dir = h2 - prevPoint;\n\t\tfloat dist = glm::length(dir);\n\n\t\tif (isBeingSpawned) {\n\t\t\tif (dist < maxSegmentLength) return;\n\t\t}\n\n\t\tglm::vec2 dirNorm = glm::normalize(dir);\n\n\t\tif (isBeingSpawned) {\n\t\t\twhile (dist >= maxSegmentLength) {\n\t\t\t\tglm::vec2 nextPoint = prevPoint + dirNorm * maxSegmentLength;\n\n\t\t\t\t(*walls).emplace_back(prevPoint, nextPoint, true, baseColor, specularColor, refractionColor, emissionColor,\n\t\t\t\t\tspecularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);\n\n\t\t\t\tprevPoint = nextPoint;\n\t\t\t\tdist = glm::length(h2 - prevPoint);\n\n\t\t\t\t(*walls).back().shapeId = id;\n\t\t\t\t(*walls).back().isShapeClosed = true;\n\n\t\t\t\tmyWallIds.push_back((*walls).back().id);\n\t\t\t}\n\t\t}\n\n\t\tif (isBeingSpawned) {\n\t\t\tfor (auto& wallId : myWallIds) {\n\n\t\t\t\tWall* w = getWallById(*walls, wallId);\n\n\t\t\t\tif (w) {\n\t\t\t\t\thelpers[0] += w->vA;\n\t\t\t\t\thelpers[0] += w->vB;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\thelpers[0] /= static_cast<float>(myWallIds.size()) * 2.0f;\n\n\t\t\toldDrawHelperPos = helpers[0];\n\t\t}\n\n\t\tif (isBeingMoved) {\n\t\t\tglm::vec2 delta = helpers[0] - oldDrawHelperPos;\n\n\n\n\t\t\tif (delta != glm::vec2(0.0f)) {\n\n\t\t\t\tfor (auto& wallId : myWallIds) {\n\t\t\t\t\tWall* w = getWallById(*walls, wallId);\n\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tw->vA += delta;\n\t\t\t\t\t\tw->vB += delta;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\toldDrawHelperPos = helpers[0];\n\t\t\t}\n\t\t}\n\t}\n\n\tbool secondHelper = false;\n\tbool thirdHelper = false;\n\tbool fourthHelper = false;\n\n\tfloat Tempsh2Length = 0.0f;\n\tfloat Tempsh2LengthSymmetry = 0.0f;\n\tfloat tempDist = 0.0f;\n\n\tglm::vec2 moveH2 = h2;\n\n\tbool isThirdBeingMoved = false;\n\tbool isFourthBeingMoved = false;\n\tbool isFifthBeingMoved = false;\n\n\tbool isGlobalHelperMoved = false;\n\n\tglm::vec2 globalLensPrev = { 0.0f, 0.0f };\n\n\tbool isFifthFourthMoved = false;\n\n\tbool symmetricalLens = true;\n\n\tuint32_t wallAId = 1;\n\tuint32_t wallBId = 2;\n\tuint32_t wallCId = 3;\n\n\tint lensSegments = 50;\n\n\tvoid drawHelper(glm::vec2& helper) {\n\t\tDrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);\n\t}\n\n\tfloat startAngle = 0.0f;\n\tfloat endAngle = 0.0f;\n\n\tfloat startAngleSymmetry = 0.0f;\n\tfloat endAngleSymmetry = 0.0f;\n\n\tglm::vec2 center{ 0.0f, 0.0f };\n\tfloat radius = 0.0f;\n\n\tglm::vec2 centerSymmetry{ 0.0f, 0.0f };\n\tfloat radiusSymmetry = 0.0f;\n\n\tglm::vec2 arcEnd{ 0.0f, 0.0f };\n\n\t// The lens code is huge and can be improved in many apsects, but I honestly don't care much about it so I'm leaving it as it is\n\n\tvoid makeLens() {\n\n\t\tif (helpers.empty()) {\n\t\t\thelpers.push_back(h1);\n\t\t}\n\n\t\tif (secondHelper) {\n\t\t\thelpers.push_back(h2);\n\t\t\tsecondHelper = false;\n\t\t}\n\n\t\tglm::vec2 thirdHelperPos = h2;\n\t\tglm::vec2 otherSide = h2;\n\t\tif (helpers.size() == 2 || isBeingMoved) {\n\n\t\t\tglm::vec2 tangent = glm::normalize(helpers.at(0) - helpers.at(1));\n\n\t\t\tglm::vec2 normal = glm::vec2(tangent.y, -tangent.x);\n\n\t\t\tglm::vec2 offset = h2 - helpers.at(1);\n\n\t\t\tfloat dist;\n\t\t\tif (!isBeingMoved) {\n\n\t\t\t\tdist = glm::dot(offset, normal);\n\t\t\t\ttempDist = dist;\n\t\t\t}\n\t\t\telse if (isBeingMoved && isThirdBeingMoved) {\n\t\t\t\toffset = moveH2 - helpers.at(1);\n\n\t\t\t\tdist = glm::dot(offset, normal);\n\t\t\t\ttempDist = dist;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdist = tempDist;\n\t\t\t}\n\n\t\t\tthirdHelperPos = helpers.at(1) + dist * normal;\n\n\t\t\totherSide = helpers.at(0) + dist * normal;\n\n\t\t\tif (isBeingMoved) {\n\t\t\t\thelpers.at(2) = thirdHelperPos;\n\t\t\t}\n\t\t}\n\n\t\tif (thirdHelper) {\n\n\t\t\thelpers.push_back(thirdHelperPos);\n\t\t\tthirdHelper = false;\n\t\t}\n\n\t\tif (helpers.size() >= 3) {\n\n\t\t\tglm::vec2 direction = -glm::normalize(helpers.at(1) - helpers.at(0));\n\n\t\t\tglm::vec2 directionSymmetry = glm::normalize(helpers.at(1) - helpers.at(0));\n\n\t\t\tfloat arcWidth = std::abs(glm::length(helpers.at(1) - helpers.at(0)));\n\t\t\tarcEnd = helpers.at(2) + direction * arcWidth;\n\n\t\t\tglm::vec2 arcEndSymmetry = helpers.at(2) - direction * arcWidth;\n\n\t\t\tglm::vec2 normal = glm::vec2(direction.y, -direction.x);\n\n\t\t\tglm::vec2 toEnd = helpers.at(2) - helpers.at(1);\n\t\t\tfloat cross = direction.x * toEnd.y - direction.y * toEnd.x;\n\t\t\tif (cross < 0) {\n\t\t\t\tnormal = -normal;\n\t\t\t}\n\n\t\t\tglm::vec2 offset = helpers.at(2) - helpers.at(1);\n\n\t\t\tfloat dist = glm::dot(offset, normal);\n\n\t\t\tglm::vec2 midPoint = (helpers.at(2) + (helpers.at(0) + dist * normal)) * 0.5f;\n\n\t\t\tglm::vec2 midPointSymmetry = (helpers.at(0) + helpers.at(1)) * 0.5f;\n\n\t\t\tglm::vec2 midToh2 = midPoint - h2;\n\n\t\t\tglm::vec2 midToh2Symmetry = midPointSymmetry - h2;\n\n\t\t\tfloat h2Length;\n\t\t\tfloat h2LengthSymmetry;\n\n\t\t\tif (!isBeingMoved) {\n\t\t\t\th2Length = glm::dot(midToh2, normal);\n\t\t\t\th2LengthSymmetry = glm::dot(midToh2, normal);\n\n\t\t\t\tTempsh2Length = h2Length;\n\t\t\t\tTempsh2LengthSymmetry = h2LengthSymmetry;\n\t\t\t}\n\t\t\telse if (isBeingMoved && isFifthFourthMoved) {\n\t\t\t\tmidToh2 = midPoint - moveH2;\n\n\t\t\t\th2Length = glm::dot(midToh2, normal);\n\n\t\t\t\tTempsh2Length = h2Length;\n\n\t\t\t\th2LengthSymmetry = h2Length;\n\n\t\t\t\tTempsh2LengthSymmetry = h2Length;\n\t\t\t}\n\t\t\telse if (isBeingMoved && isFourthBeingMoved) {\n\n\t\t\t\tmidToh2 = midPoint - moveH2;\n\n\t\t\t\th2Length = glm::dot(midToh2, normal);\n\n\t\t\t\tTempsh2Length = h2Length;\n\t\t\t\th2LengthSymmetry = Tempsh2LengthSymmetry;\n\t\t\t}\n\t\t\telse if (isBeingMoved && isFifthBeingMoved) {\n\t\t\t\tmidToh2Symmetry = midPointSymmetry - moveH2;\n\n\t\t\t\th2LengthSymmetry = glm::dot(midToh2Symmetry, -normal);\n\n\t\t\t\th2Length = Tempsh2Length;\n\t\t\t\tTempsh2LengthSymmetry = h2LengthSymmetry;\n\t\t\t}\n\t\t\telse {\n\t\t\t\th2Length = Tempsh2Length;\n\t\t\t\th2LengthSymmetry = Tempsh2LengthSymmetry;\n\t\t\t}\n\n\t\t\th2Length = std::clamp(h2Length, -arcWidth * 0.48f, arcWidth * 0.48f);\n\t\t\th2LengthSymmetry = std::clamp(h2LengthSymmetry, -arcWidth * 0.48f, arcWidth * 0.48f);\n\n\t\t\tglm::vec2 p1 = helpers.at(2);\n\t\t\tglm::vec2 p2 = midPoint - h2Length * normal;\n\t\t\tglm::vec2 p3 = helpers.at(0) + dist * normal;\n\n\t\t\tglm::vec2 p1Symmetry = helpers.at(0);\n\t\t\tglm::vec2 p2Symmetry = midPointSymmetry - h2LengthSymmetry * -normal;\n\t\t\tglm::vec2 p3Symmetry = helpers.at(1);\n\n\t\t\tglm::vec2 mid1 = (p1 + p2) * 0.5f;\n\t\t\tglm::vec2 dir1 = glm::vec2(p2.y - p1.y, p1.x - p2.x);\n\n\t\t\tglm::vec2 mid2 = (p2 + p3) * 0.5f;\n\t\t\tglm::vec2 dir2 = glm::vec2(p3.y - p2.y, p2.x - p3.x);\n\n\t\t\tfloat denominator = dir2.x * dir1.y - dir2.y * dir1.x;\n\t\t\tif (std::abs(denominator) > 1e-6f) {\n\t\t\t\tfloat t = (dir2.x * (mid2.y - mid1.y) - dir2.y * (mid2.x - mid1.x)) / denominator;\n\t\t\t\tcenter = mid1 + t * dir1;\n\t\t\t\tradius = glm::length(center - p1);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcenter = mid1;\n\t\t\t\tradius = std::numeric_limits<float>::max();\n\t\t\t}\n\n\t\t\tstartAngle = atan2(p1.y - center.y, p1.x - center.x);\n\t\t\tendAngle = atan2(p3.y - center.y, p3.x - center.x);\n\n\n\t\t\tfloat angleDiff = endAngle - startAngle;\n\n\n\t\t\tglm::vec2 mid1Symmetry = (p1Symmetry + p2Symmetry) * 0.5f;\n\t\t\tglm::vec2 dir1Symmetry = glm::vec2(p2Symmetry.y - p1Symmetry.y, p1Symmetry.x - p2Symmetry.x);\n\n\t\t\tglm::vec2 mid2Symmetry = (p2Symmetry + p3Symmetry) * 0.5f;\n\t\t\tglm::vec2 dir2Symmetry = glm::vec2(p3Symmetry.y - p2Symmetry.y, p2Symmetry.x - p3Symmetry.x);\n\n\t\t\tfloat denominatorSymmetry = dir2Symmetry.x * dir1Symmetry.y - dir2Symmetry.y * dir1Symmetry.x;\n\t\t\tif (std::abs(denominatorSymmetry) > 1e-6f) {\n\t\t\t\tfloat tSymmetry = (dir2Symmetry.x * (mid2Symmetry.y - mid1Symmetry.y) - dir2Symmetry.y * (mid2Symmetry.x - mid1Symmetry.x)) / denominatorSymmetry;\n\t\t\t\tcenterSymmetry = mid1Symmetry + tSymmetry * dir1Symmetry;\n\t\t\t\tradiusSymmetry = glm::length(centerSymmetry - p1Symmetry);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcenterSymmetry = mid1Symmetry;\n\t\t\t\tradiusSymmetry = std::numeric_limits<float>::max();\n\t\t\t}\n\n\t\t\tstartAngleSymmetry = atan2(p1Symmetry.y - centerSymmetry.y, p1Symmetry.x - centerSymmetry.x);\n\t\t\tendAngleSymmetry = atan2(p3Symmetry.y - centerSymmetry.y, p3Symmetry.x - centerSymmetry.x);\n\n\n\t\t\tfloat angleDiffSymmetry = endAngleSymmetry - startAngleSymmetry;\n\n\t\t\tif (angleDiff > PI) {\n\t\t\t\tendAngle -= 2 * PI;\n\t\t\t}\n\t\t\telse if (angleDiff < -PI) {\n\t\t\t\tendAngle += 2 * PI;\n\t\t\t}\n\n\t\t\tif (angleDiffSymmetry > PI) {\n\t\t\t\tendAngleSymmetry -= 2 * PI;\n\t\t\t}\n\t\t\telse if (angleDiffSymmetry < -PI) {\n\t\t\t\tendAngleSymmetry += 2 * PI;\n\t\t\t}\n\n\t\t\tstd::vector<uint32_t> newWallIds;\n\n\t\t\tif (fourthHelper || isBeingMoved) {\n\t\t\t\tif (!isBeingMoved) {\n\t\t\t\t\thelpers.push_back(p2);\n\n\t\t\t\t\tif (symmetricalLens) {\n\t\t\t\t\t\thelpers.push_back(p2Symmetry);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\thelpers.at(3) = p2;\n\t\t\t\t\tif (symmetricalLens) {\n\t\t\t\t\t\thelpers.at(4) = p2Symmetry;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!isBeingMoved) {\n\t\t\t\t\tif (!symmetricalLens) {\n\t\t\t\t\t\t(*walls).emplace_back(helpers.at(0), helpers.at(1), true, baseColor, specularColor, refractionColor, emissionColor,\n\t\t\t\t\t\t\tspecularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);\n\t\t\t\t\t\t(*walls).back().shapeId = id;\n\t\t\t\t\t\t(*walls).back().isShapeClosed = true;\n\t\t\t\t\t\tnewWallIds.push_back((*walls).back().id);\n\t\t\t\t\t\twallAId = (*walls).back().id;\n\t\t\t\t\t}\n\n\t\t\t\t\t(*walls).emplace_back(helpers.at(1), helpers.at(2), true, baseColor, specularColor, refractionColor, emissionColor,\n\t\t\t\t\t\tspecularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);\n\t\t\t\t\t(*walls).back().shapeId = id;\n\t\t\t\t\t(*walls).back().isShapeClosed = true;\n\t\t\t\t\tnewWallIds.push_back((*walls).back().id);\n\t\t\t\t\twallBId = (*walls).back().id;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (!symmetricalLens) {\n\t\t\t\t\t\tfor (auto& wId : myWallIds) {\n\n\t\t\t\t\t\t\tif (wId == wallAId) {\n\n\t\t\t\t\t\t\t\tWall* w = getWallById(*walls, wId);\n\n\t\t\t\t\t\t\t\tw->vA = helpers[0];\n\t\t\t\t\t\t\t\tw->vB = helpers[1];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (auto& wId : myWallIds) {\n\n\t\t\t\t\t\tif (wId == wallBId) {\n\n\t\t\t\t\t\t\tWall* w = getWallById(*walls, wId);\n\n\t\t\t\t\t\t\tw->vA = helpers[1];\n\t\t\t\t\t\t\tw->vB = helpers[2];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (int i = 0; i < lensSegments; i++) {\n\t\t\t\tfloat t1 = static_cast<float>(i) / lensSegments;\n\t\t\t\tfloat t2 = static_cast<float>((i + 1)) / lensSegments;\n\n\t\t\t\tfloat angle1 = startAngle + t1 * (endAngle - startAngle);\n\t\t\t\tfloat angle2 = startAngle + t2 * (endAngle - startAngle);\n\n\t\t\t\tglm::vec2 arcP1 = center + glm::vec2(cos(angle1), sin(angle1)) * radius;\n\t\t\t\tglm::vec2 arcP2 = center + glm::vec2(cos(angle2), sin(angle2)) * radius;\n\n\t\t\t\tif (fourthHelper || isBeingMoved) {\n\t\t\t\t\tif (!isBeingMoved) {\n\t\t\t\t\t\t(*walls).emplace_back(arcP1, arcP2, true, baseColor, specularColor, refractionColor, emissionColor,\n\t\t\t\t\t\t\tspecularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);\n\t\t\t\t\t\t(*walls).back().shapeId = id;\n\t\t\t\t\t\t(*walls).back().isShapeClosed = true;\n\t\t\t\t\t\tnewWallIds.push_back((*walls).back().id);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\n\t\t\t\t\t\tfor (auto& wId : myWallIds) {\n\n\t\t\t\t\t\t\tif (wId == wallBId + i + 1) {\n\n\t\t\t\t\t\t\t\tWall* w = getWallById(*walls, wId);\n\n\t\t\t\t\t\t\t\tw->vA = arcP1;\n\t\t\t\t\t\t\t\tw->vB = arcP2;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (fourthHelper || isBeingMoved) {\n\n\t\t\t\tif (!isBeingMoved) {\n\t\t\t\t\t(*walls).emplace_back(arcEnd, helpers.at(0), true, baseColor, specularColor, refractionColor, emissionColor,\n\t\t\t\t\t\tspecularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);\n\n\t\t\t\t\t(*walls).back().shapeId = id;\n\t\t\t\t\t(*walls).back().isShapeClosed = true;\n\n\t\t\t\t\tnewWallIds.push_back((*walls).back().id);\n\t\t\t\t\twallCId = (*walls).back().id;\n\t\t\t\t}\n\t\t\t\telse {\n\n\t\t\t\t\tfor (auto& wId : myWallIds) {\n\n\t\t\t\t\t\tif (wId == wallCId) {\n\n\t\t\t\t\t\t\tWall* w = getWallById(*walls, wId);\n\n\t\t\t\t\t\t\tw->vA = arcEnd;\n\t\t\t\t\t\t\tw->vB = helpers[0];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (symmetricalLens) {\n\t\t\t\tfor (int i = 0; i < lensSegments; i++) {\n\t\t\t\t\tfloat t1Symmetry = static_cast<float>(i) / lensSegments;\n\t\t\t\t\tfloat t2Symmetry = static_cast<float>((i + 1)) / lensSegments;\n\n\t\t\t\t\tfloat angle1Symmetry = startAngleSymmetry + t1Symmetry * (endAngleSymmetry - startAngleSymmetry);\n\t\t\t\t\tfloat angle2Symmetry = startAngleSymmetry + t2Symmetry * (endAngleSymmetry - startAngleSymmetry);\n\n\t\t\t\t\tglm::vec2 arcP1Symmetry = centerSymmetry + glm::vec2(cos(angle1Symmetry), sin(angle1Symmetry)) * radiusSymmetry;\n\t\t\t\t\tglm::vec2 arcP2Symmetry = centerSymmetry + glm::vec2(cos(angle2Symmetry), sin(angle2Symmetry)) * radiusSymmetry;\n\n\t\t\t\t\tif (fourthHelper || isBeingMoved) {\n\t\t\t\t\t\tif (!isBeingMoved) {\n\t\t\t\t\t\t\t(*walls).emplace_back(arcP1Symmetry, arcP2Symmetry, true, baseColor, specularColor, refractionColor, emissionColor,\n\t\t\t\t\t\t\t\tspecularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);\n\t\t\t\t\t\t\t(*walls).back().shapeId = id;\n\t\t\t\t\t\t\t(*walls).back().isShapeClosed = true;\n\t\t\t\t\t\t\tnewWallIds.push_back((*walls).back().id);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\n\t\t\t\t\t\t\tfor (auto& wId : myWallIds) {\n\n\t\t\t\t\t\t\t\tif (wId == wallCId + i + 1) {\n\n\t\t\t\t\t\t\t\t\tWall* w = getWallById(*walls, wId);\n\n\t\t\t\t\t\t\t\t\tw->vA = arcP1Symmetry;\n\t\t\t\t\t\t\t\t\tw->vB = arcP2Symmetry;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (fourthHelper || isBeingMoved) {\n\n\t\t\t\tif (!isBeingMoved) {\n\t\t\t\t\tmyWallIds = std::move(newWallIds);\n\n\t\t\t\t\tglm::vec2 averageHelper = { 0.0f, 0.0f };\n\n\t\t\t\t\tfor (glm::vec2& h : helpers) {\n\t\t\t\t\t\taverageHelper += h;\n\t\t\t\t\t}\n\n\t\t\t\t\taverageHelper /= helpers.size();\n\n\t\t\t\t\thelpers.push_back(averageHelper);\n\n\t\t\t\t\tglobalLensPrev = helpers.back();\n\t\t\t\t}\n\t\t\t\telse if(isBeingMoved && !isGlobalHelperMoved) {\n\t\t\t\t\thelpers.back() = {0.0f, 0.0f};\n\n\t\t\t\t\tfor (size_t i = 0; i < helpers.size() - 1; i++) {\n\t\t\t\t\t\thelpers.back() += helpers[i];\n\t\t\t\t\t}\n\n\t\t\t\t\thelpers.back() /= helpers.size() - 1;\n\n\t\t\t\t\tglobalLensPrev = helpers.back();\n\t\t\t\t}\n\t\t\t\telse if (isBeingMoved && isGlobalHelperMoved) {\n\t\t\t\t\tglm::vec2 delta = helpers.back() - globalLensPrev;\n\n\t\t\t\t\tif (delta != glm::vec2(0.0f)) {\n\t\t\t\t\t\tfor (size_t i = 0; i < helpers.size() - 1; i++) {\n\t\t\t\t\t\t\thelpers[i] += delta;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tglobalLensPrev = helpers.back();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcalculateWallsNormals();\n\n\t\t\t\tfourthHelper = false;\n\n\t\t\t\tif (!isBeingMoved) {\n\t\t\t\t\tisBeingSpawned = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};\n\nstruct LightRay {\n\n\tglm::vec2 source;\n\tglm::vec2 dir;\n\n\tfloat maxLength = 100000.0f;\n\tfloat length = 10000.0f;\n\n\tglm::vec2 hitPoint;\n\n\tbool hasHit;\n\tint bounceLevel;\n\n\tWall wall;\n\n\tColor color;\n\n\tbool reflectSpecular;\n\tbool refracted;\n\n\tstd::vector<float> mediumIORStack;\n\n\tbool hasBeenDispersed;\n\tbool hasBeenScattered;\n\n\tglm::vec2 scatterSource;\n\n\tLightRay() = default;\n\tLightRay(glm::vec2 source, glm::vec2 dir, int bounceLevel, Color color) {\n\t\tthis->source = source;\n\t\tthis->dir = dir;\n\t\tthis->hitPoint = { 0.0f, 0.0f };\n\t\tthis->hasHit = false;\n\t\tthis->bounceLevel = bounceLevel;\n\t\tthis->color = color;\n\t\tthis->reflectSpecular = false;\n\t\tthis->refracted = false;\n\t\tthis->mediumIORStack = { 1.0f };\n\t\tthis->hasBeenDispersed = false;\n\t\tthis->hasBeenScattered = false;\n\t\tthis->scatterSource = { 0.0f, 0.0f };\n\t}\n\n\tvoid drawRay() {\n\t\tDrawLineV({ source.x, source.y }, { source.x + (dir.x * length), source.y + (dir.y * length) }, color);\n\t}\n};\n\nstruct PointLight {\n\n\tglm::vec2 pos;\n\tbool isBeingMoved;\n\n\tColor color;\n\n\tColor apparentColor;\n\n\tbool isSelected;\n\n\tPointLight() = default;\n\n\tPointLight(glm::vec2 pos, Color color) {\n\t\tthis->pos = pos;\n\t\tthis->isBeingMoved = false;\n\t\tthis->color = color;\n\n\t\tthis->apparentColor = WHITE;\n\n\t\tthis->isSelected = false;\n\t}\n\n\tvoid drawHelper(glm::vec2& helper) {\n\t\tDrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);\n\t}\n\n\tvoid pointLightLogic(int& sampleRaysAmount, int& currentSamples, int& maxSamples, std::vector<LightRay>& rays) {\n\t\tfloat radius = 100.0f;\n\n\t\tconst float goldenAngle = PI * (3.0f - std::sqrt(5.0f));\n\n\t\tint startIndex = currentSamples * sampleRaysAmount;\n\n\t\tfor (int i = 0; i < sampleRaysAmount; i++) {\n\t\t\tint rayIndex = startIndex + i;\n\n\t\t\tfloat angle = rayIndex * goldenAngle;\n\n\t\t\tglm::vec2 d = glm::vec2(std::cos(angle), std::sin(angle));\n\t\t\trays.emplace_back(\n\t\t\t\tpos,\n\t\t\t\td,\n\t\t\t\t1,\n\t\t\t\tcolor\n\t\t\t);\n\t\t}\n\t}\n};\n\nstruct AreaLight {\n\n\tglm::vec2 vA;\n\tglm::vec2 vB;\n\n\tbool isBeingSpawned;\n\tbool vAisBeingMoved;\n\tbool vBisBeingMoved;\n\n\tColor color;\n\n\tColor apparentColor;\n\n\tbool isSelected;\n\n\tfloat spread;\n\n\tAreaLight() = default;\n\n\tAreaLight(glm::vec2 vA, glm::vec2 vB, Color color, float spread) {\n\t\tthis->vA = vA;\n\t\tthis->vB = vB;\n\t\tthis->isBeingSpawned = true;\n\t\tthis->vAisBeingMoved = false;\n\t\tthis->vBisBeingMoved = false;\n\t\tthis->color = color;\n\t\tthis->apparentColor = WHITE;\n\n\t\tthis->spread = spread;\n\n\t\tthis->isSelected = false;\n\t}\n\n\tvoid drawHelper(glm::vec2& helper) {\n\t\tDrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);\n\t}\n\n\tglm::vec2 rotateVec2(glm::vec2 v, float angle) {\n\t\tfloat c = cos(angle);\n\t\tfloat s = sin(angle);\n\t\treturn glm::vec2(\n\t\t\tv.x * c - v.y * s,\n\t\t\tv.x * s + v.y * c\n\t\t);\n\t}\n\n\tvoid drawAreaLight() {\n\t\tDrawLineV({ vA.x, vA.y }, { vB.x, vB.y }, apparentColor);\n\t}\n\n\tvoid areaLightLogic(int& sampleRaysAmount, std::vector<LightRay>& rays) {\n\n\t\tfor (int i = 0; i < sampleRaysAmount; i++) {\n\n\t\t\tfloat maxSpreadAngle = glm::radians(90.0f * spread);\n\n\t\t\tfloat randAngle = getRandomFloat() * 2.0f * maxSpreadAngle - maxSpreadAngle;\n\n\t\t\tglm::vec2 d = vB - vA;\n\t\t\tfloat length = glm::length(d);\n\t\t\tglm::vec2 dNormal = d / length;\n\n\t\t\tfloat t = getRandomFloat();\n\t\t\tglm::vec2 source = vA + d * t;\n\n\t\t\tglm::vec2 rayDirection = rotateVec2(dNormal, randAngle);\n\t\t\trayDirection = glm::vec2(rayDirection.y, -rayDirection.x);\n\n\t\t\trays.emplace_back(\n\t\t\t\tsource,\n\t\t\t\trayDirection,\n\t\t\t\t1,\n\t\t\t\tcolor\n\t\t\t);\n\t\t}\n\t}\n};\n\nstruct ConeLight {\n\n\tglm::vec2 vA;\n\tglm::vec2 vB;\n\n\tbool isBeingSpawned;\n\tbool vAisBeingMoved;\n\tbool vBisBeingMoved;\n\n\tColor color;\n\n\tColor apparentColor;\n\n\tbool isSelected;\n\n\tfloat spread;\n\n\tConeLight() = default;\n\n\tConeLight(glm::vec2 vA, glm::vec2 vB, Color color, float spread) {\n\t\tthis->vA = vA;\n\t\tthis->vB = vB;\n\t\tthis->isBeingSpawned = true;\n\t\tthis->vAisBeingMoved = false;\n\t\tthis->vBisBeingMoved = false;\n\t\tthis->color = color;\n\t\tthis->apparentColor = WHITE;\n\n\t\tthis->spread = spread;\n\n\t\tthis->isSelected = false;\n\t}\n\n\tvoid drawHelper(glm::vec2& helper) {\n\t\tDrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);\n\t}\n\n\tglm::vec2 rotateVec2(glm::vec2 v, float angle) {\n\t\tfloat c = cos(angle);\n\t\tfloat s = sin(angle);\n\t\treturn glm::vec2(\n\t\t\tv.x * c - v.y * s,\n\t\t\tv.x * s + v.y * c\n\t\t);\n\t}\n\n\tvoid coneLightLogic(int& sampleRaysAmount, std::vector<LightRay>& rays) {\n\n\t\tfor (int i = 0; i < sampleRaysAmount; i++) {\n\t\t\tfloat maxSpreadAngle = glm::radians(90.0f * spread);\n\n\t\t\tfloat randAngle = getRandomFloat() * 2.0f * maxSpreadAngle - maxSpreadAngle;\n\n\t\t\tglm::vec2 d = vB - vA;\n\t\t\tfloat length = glm::length(d);\n\t\t\tglm::vec2 dNormal = d / length;\n\n\t\t\tglm::vec2 direction = glm::normalize(vB - vA);\n\n\t\t\tdirection = rotateVec2(dNormal, randAngle);\n\n\t\t\trays.emplace_back(\n\t\t\t\tvA,\n\t\t\t\tdirection,\n\t\t\t\t1,\n\t\t\t\tcolor\n\t\t\t);\n\t\t}\n\t}\n};\n\nstruct AABB2D {\n\tglm::vec2 min;\n\tglm::vec2 max;\n\tAABB2D() : min(0), max(0) {}\n\tAABB2D(glm::vec2 a, glm::vec2 b) {\n\t\tmin = glm::min(a, b);\n\t\tmax = glm::max(a, b);\n\t}\n\tvoid expand(const AABB2D& other) {\n\t\tmin = glm::min(min, other.min);\n\t\tmax = glm::max(max, other.max);\n\t}\n\tbool intersectsRay(const glm::vec2& origin, const glm::vec2& dir, float maxDist = FLT_MAX) const {\n\t\tconst float epsilon = 1e-8f;\n\t\tglm::vec2 invDir;\n\t\tinvDir.x = (std::abs(dir.x) < epsilon) ? (dir.x >= 0 ? 1e8f : -1e8f) : 1.0f / dir.x;\n\t\tinvDir.y = (std::abs(dir.y) < epsilon) ? (dir.y >= 0 ? 1e8f : -1e8f) : 1.0f / dir.y;\n\n\t\tglm::vec2 t0s = (min - origin) * invDir;\n\t\tglm::vec2 t1s = (max - origin) * invDir;\n\t\tglm::vec2 tMin = glm::min(t0s, t1s);\n\t\tglm::vec2 tMax = glm::max(t0s, t1s);\n\t\tfloat tEntry = std::max(tMin.x, tMin.y);\n\t\tfloat tExit = std::min(tMax.x, tMax.y);\n\t\treturn tExit >= 0 && tEntry <= tExit && tEntry <= maxDist;\n\t}\n};\n\nstruct BVHNode {\n\tAABB2D bounds;\n\tBVHNode* left = nullptr;\n\tBVHNode* right = nullptr;\n\tWall* wall = nullptr;\n\tbool isLeaf() const { return wall != nullptr; }\n};\n\nclass BVH {\npublic:\n\tBVHNode* root = nullptr;\n\tBVH() = default;\n\t~BVH() {\n\t\tdestroyBVH(root);\n\t}\n\n\tvoid build(std::vector<Wall*>& walls) {\n\t\tdestroyBVH(root);\n\t\troot = nullptr;\n\n\t\tif (walls.empty()) {\n\t\t\treturn;\n\t\t}\n\t\troot = buildBVH(walls, 0, walls.size(), 0);\n\t}\n\n\tbool traverse(const LightRay& ray, float& closestT, Wall*& hitWall, glm::vec2& hitPoint) {\n\t\tif (!root) return false;\n\t\treturn traverseBVH(root, ray, closestT, hitWall, hitPoint);\n\t}\n\nprivate:\n\tvoid destroyBVH(BVHNode* node) {\n\t\tif (!node) return;\n\t\tdestroyBVH(node->left);\n\t\tdestroyBVH(node->right);\n\t\tdelete node;\n\t}\n\n\tBVHNode* buildBVH(std::vector<Wall*>& walls, int start, int end, int depth) {\n\t\tif (start >= end) return nullptr;\n\n\t\tBVHNode* node = new BVHNode{};\n\n\t\tAABB2D bounds = AABB2D(walls[start]->vA, walls[start]->vB);\n\t\tfor (int i = start + 1; i < end; ++i) {\n\t\t\tAABB2D wallBox(walls[i]->vA, walls[i]->vB);\n\t\t\tbounds.expand(wallBox);\n\t\t}\n\t\tnode->bounds = bounds;\n\n\t\tint count = end - start;\n\n\t\tif (count <= 1 || depth >= 20) {\n\t\t\tnode->wall = walls[start];\n\t\t\treturn node;\n\t\t}\n\n\t\tglm::vec2 extent = bounds.max - bounds.min;\n\t\tint axis = extent.x > extent.y ? 0 : 1;\n\n\t\tstd::sort(walls.begin() + start, walls.begin() + end, [axis](Wall* a, Wall* b) {\n\t\t\tfloat midA = (a->vA[axis] + a->vB[axis]) * 0.5f;\n\t\t\tfloat midB = (b->vA[axis] + b->vB[axis]) * 0.5f;\n\t\t\treturn midA < midB;\n\t\t\t});\n\n\t\tint mid = start + count / 2;\n\n\t\tif (mid == start) mid = start + 1;\n\t\tif (mid >= end) mid = end - 1;\n\n\t\tnode->left = buildBVH(walls, start, mid, depth + 1);\n\t\tnode->right = buildBVH(walls, mid, end, depth + 1);\n\n\t\treturn node;\n\t}\n\n\tbool intersectWall(const LightRay& ray, const Wall& wall, float& t, glm::vec2& hitPoint) {\n\t\tglm::vec2 p = ray.source;\n\t\tglm::vec2 r = ray.dir;\n\t\tglm::vec2 q = wall.vA;\n\t\tglm::vec2 s = wall.vB - wall.vA;\n\n\t\tfloat rxs = r.x * s.y - r.y * s.x;\n\t\tconst float epsilon = 1e-8f;\n\t\tif (std::abs(rxs) < epsilon) return false;\n\n\t\tglm::vec2 qp = q - p;\n\t\tfloat t1 = (qp.x * s.y - qp.y * s.x) / rxs;\n\t\tfloat t2 = (qp.x * r.y - qp.y * r.x) / rxs;\n\n\t\tif (t1 >= 0 && t1 <= ray.maxLength && t2 >= 0.0f && t2 <= 1.0f) {\n\t\t\tt = t1;\n\t\t\thitPoint = p + t * r;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tbool traverseBVH(const BVHNode* root, const LightRay& ray, float& closestT, Wall*& hitWall, glm::vec2& hitPoint) {\n\t\tif (!root) return false;\n\n\t\tstd::stack<const BVHNode*> stack;\n\t\tstack.push(root);\n\t\tbool hit = false;\n\n\t\twhile (!stack.empty()) {\n\t\t\tconst BVHNode* node = stack.top();\n\t\t\tstack.pop();\n\n\t\t\tif (!node->bounds.intersectsRay(ray.source, ray.dir, closestT))\n\t\t\t\tcontinue;\n\n\t\t\tif (node->isLeaf()) {\n\t\t\t\tfloat t;\n\t\t\t\tglm::vec2 pt;\n\t\t\t\tif (intersectWall(ray, *node->wall, t, pt) && t < closestT) {\n\t\t\t\t\tclosestT = t;\n\t\t\t\t\thitWall = node->wall;\n\t\t\t\t\thitPoint = pt;\n\t\t\t\t\thit = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (node->right) stack.push(node->right);\n\t\t\t\tif (node->left) stack.push(node->left);\n\t\t\t}\n\t\t}\n\t\treturn hit;\n\t}\n};\n\nstruct Lighting {\n\n\tstd::vector<Wall> walls;\n\tstd::vector<Wall*> wallPointers;\n\n\tstd::vector<Shape> shapes;\n\n\tstd::vector<LightRay> rays;\n\n\tstd::vector<PointLight> pointLights;\n\tstd::vector<AreaLight> areaLights;\n\tstd::vector<ConeLight> coneLights;\n\n\tsize_t firstPassTotalRays = 0;\n\n\tBVH bvh;\n\n\tint sampleRaysAmount = 1024;\n\tint maxBounces = 3;\n\tint maxSamples = 500;\n\tint currentSamples = 0;\n\n\tbool isDiffuseEnabled = true;\n\tbool isSpecularEnabled = true;\n\tbool isRefractionEnabled = true;\n\tbool isDispersionEnabled = true;\n\tbool isEmissionEnabled = true;\n\n\tbool symmetricalLens = false;\n\n\tbool shouldRender = true;\n\n\tbool drawNormals = false;\n\n\tbool relaxMove = false;\n\n\tconst float lightBias = 0.1f;\n\n\tColor lightColor = { 255, 255, 255, 64 };\n\n\tColor wallBaseColor = { 200, 200, 200, 255 };\n\tColor wallSpecularColor = { 255, 255, 255, 255 };\n\tColor wallRefractionColor = { 255, 255, 255, 255 };\n\tColor wallEmissionColor = { 255, 255, 255, 0 };\n\n\tfloat lightGain = 0.25f;\n\n\tfloat lightSpread = 0.85f;\n\n\tfloat wallSpecularRoughness = 0.5f;\n\tfloat wallRefractionRoughness = 0.0f; // This controls the roughness of the refraction surface. I separate it for extra control, like V-Ray renderer\n\tfloat wallRefractionAmount = 0.0f;\n\tfloat wallIOR = 1.5f;\n\tfloat airIOR = 1.0f;\n\n\tfloat wallDispersion = 0.0f;\n\n\tfloat wallEmissionGain = 0.0f;\n\n\tint shapeRelaxIter = 15;\n\tfloat shapeRelaxFactor = 0.65f;\n\n\tfloat absorptionInvBias = 0.99f;\n\n\tWall* getWallById(std::vector<Wall>& walls, uint32_t id) {\n\t\tfor (Wall& wall : walls) {\n\t\t\tif (wall.id == id)\n\t\t\t\treturn &wall;\n\t\t}\n\t\treturn nullptr;\n\t}\n\n\tvoid calculateWallNormal(Wall& wall) {\n\t\tWall& w = wall;\n\t\tglm::vec2 tangent = glm::normalize(w.vB - w.vA);\n\t\tglm::vec2 normal = glm::vec2(-tangent.y, tangent.x);\n\t\tw.normal = normal;\n\t\tw.normalVA = normal;\n\t\tw.normalVB = normal;\n\t}\n\n\tvoid createWall(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tbool firstHelper = true;\n\tbool isCreatingLens = false;\n\n\tfloat minHelperLength = FLT_MAX;\n\tint selectedHelper = -1;\n\tsize_t selectedShape = -1;\n\n\tconst float helperMinDist = 100.0f;\n\n\tvoid createShape(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid createPointLight(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid createAreaLight(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid createConeLight(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid movePointLights(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid moveAreaLights(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid moveConeLights(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid moveWalls(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tbool isAnyShapeBeingSpawned = false;\n\n\tvoid moveLogic(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid eraseLogic(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tfloat lightGainAvg = 0.0f;\n\tbool isSliderLightGain = false;\n\n\tfloat lightSpreadAvg = 0.0f;\n\tbool isSliderlightSpread = false;\n\n\tColor lightColorAvg = { 0, 0, 0, 0 };\n\tbool isSliderLightColor = false;\n\n\tColor baseColorAvg = { 0, 0, 0, 0 };\n\tbool isSliderBaseColor = false;\n\n\tColor specularColorAvg = { 0, 0, 0, 0 };\n\tbool isSliderSpecularColor = false;\n\n\tColor refractionColorAvg = { 0, 0, 0, 0 };\n\tbool isSliderRefractionCol = false;\n\n\tColor emissionColorAvg = { 0, 0, 0, 0 };\n\tbool isSliderEmissionCol = false;\n\n\tfloat specularRoughAvg = 0.0f;\n\tbool isSliderSpecularRough = false;\n\n\tfloat refractionRoughAvg = 0.0f;\n\tbool isSliderRefractionRough = false;\n\n\tfloat refractionAmountAvg = 0.0f;\n\tbool isSliderRefractionAmount = false;\n\n\tfloat iorAvg = 0.0f;\n\tbool isSliderIor = false;\n\n\tfloat dispersionAvg = 0.0f;\n\tbool isSliderDispersion = false;\n\n\tfloat emissionGainAvg = 0.0f;\n\tbool isSliderEmissionGain = false;\n\n\t// Add the UI bools for optics in here\n\tstd::vector<bool*> uiOpticElements = {\n\n\t\t&isSliderLightGain,\n\t\t&isSliderlightSpread,\n\t\t&isSliderLightColor,\n\t\t&isSliderBaseColor,\n\t\t&isSliderSpecularColor,\n\t\t&isSliderRefractionCol,\n\t\t&isSliderEmissionCol,\n\t\t&isSliderSpecularRough,\n\t\t&isSliderRefractionRough,\n\t\t&isSliderRefractionAmount,\n\t\t&isSliderIor,\n\t\t&isSliderDispersion,\n\t\t&isSliderEmissionGain\n\n\t};\n\n\tglm::vec2 boxInitialPos{ 0.0f, 0.0f };\n\n\tbool isBoxSelecting = false;\n\tbool isBoxDeselecting = false;\n\n\tfloat boxX = 0.0f;\n\tfloat boxY = 0.0f;\n\tfloat boxWidth = 0.0f;\n\tfloat boxHeight = 0.0f;\n\n\tint selectedWalls = 0;\n\tint selectedLights = 0;\n\n\tvoid selectLogic(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\n\n\tfloat checkIntersect(const LightRay& ray, const Wall& w);\n\n\tvoid processRayIntersection(LightRay& ray);\n\n\tglm::vec2 rotateVec2(glm::vec2 v, float angle) {\n\t\tfloat c = cos(angle);\n\t\tfloat s = sin(angle);\n\t\treturn glm::vec2(\n\t\t\tv.x * c - v.y * s,\n\t\t\tv.x * s + v.y * c\n\t\t);\n\t}\n\n\tvoid specularReflection(int& currentBounce, LightRay& ray, std::vector<LightRay>& copyRays, std::vector<Wall>& walls);\n\n\tvoid refraction(int& currentBounce, LightRay& ray, std::vector<LightRay>& copyRays, std::vector<Wall>& walls);\n\n\t// Currently unfinished and unused. Might work on it some time in the future\n\t//void volumeScatter(int& currentBounce, LightRay& ray, std::vector<LightRay>& copyRays, std::vector<Wall>& walls);\n\n\tvoid diffuseLighting(int& currentBounce, LightRay& ray, std::vector<LightRay>& copyRays, std::vector<Wall>& walls);\n\n\tvoid emission();\n\n\tvoid lightRendering(UpdateParameters& myParam);\n\n\tvoid drawRays() {\n\n\t\tif (currentSamples <= maxSamples) {\n\t\t\tfor (LightRay& ray : rays) {\n\t\t\t\tray.drawRay();\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid drawScene() {\n\t\tfor (Wall& wall : walls) {\n\t\t\twall.drawWall();\n\n\t\t\tif (drawNormals) {\n\t\t\t\tDrawLineV({ wall.vA.x, wall.vA.y }, { wall.vA.x + wall.normalVA.x * 10.0f, wall.vA.y + wall.normalVA.y * 10.0f }, RED);\n\t\t\t\tDrawLineV({ wall.vB.x, wall.vB.y }, { wall.vB.x + wall.normalVB.x * 10.0f, wall.vB.y + wall.normalVB.y * 10.0f }, RED);\n\t\t\t\tDrawLineV({ (wall.vA.x + wall.vB.x) * 0.5f, (wall.vA.y + wall.vB.y) * 0.5f },\n\t\t\t\t\t{ (wall.vA.x + wall.vB.x) * 0.5f + wall.normal.x * 10.0f, (wall.vA.y + wall.vB.y) * 0.5f + wall.normal.y * 10.0f }, RED);\n\t\t\t}\n\t\t}\n\n\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\tareaLight.drawAreaLight();\n\t\t}\n\t}\n\n\tvoid drawMisc(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tinline Color glmVec4ToColor(const glm::vec4& v) {\n\t\tfloat r = glm::clamp(v.x, 0.0f, 1.0f);\n\t\tfloat g = glm::clamp(v.y, 0.0f, 1.0f);\n\t\tfloat b = glm::clamp(v.z, 0.0f, 1.0f);\n\t\tfloat a = glm::clamp(v.w, 0.0f, 1.0f);\n\n\t\treturn Color{\n\t\t\tstatic_cast<unsigned char>(r * 255.0f),\n\t\t\tstatic_cast<unsigned char>(g * 255.0f),\n\t\t\tstatic_cast<unsigned char>(b * 255.0f),\n\t\t\tstatic_cast<unsigned char>(a * 255.0f)\n\t\t};\n\t}\n\n\tinline glm::vec4 ColorToGlmVec4(const Color& c) {\n\t\treturn glm::vec4{\n\t\t\tstatic_cast<float>(c.r) / 255.0f,\n\t\t\tstatic_cast<float>(c.g) / 255.0f,\n\t\t\tstatic_cast<float>(c.b) / 255.0f,\n\t\t\tstatic_cast<float>(c.a) / 255.0f\n\t\t};\n\t}\n\n\tvoid processApparentColor() {\n\n\t\tfor (Wall& w : walls) {\n\t\t\tif (w.isSelected) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat emissionGain = static_cast<float>(w.emissionColor.a) / 255.0f;\n\n\t\t\tfloat baseWeight = 1.0f - w.refractionAmount;\n\t\t\tfloat specularWeight = 0.2f * w.IOR * (1.0f - w.refractionAmount * 0.5f);\n\n\t\t\tglm::vec4 baseCol = { 0.0f, 0.0f, 0.0f, 0.0f };\n\t\t\tbaseCol += ColorToGlmVec4(w.baseColor) * baseWeight;\n\t\t\tbaseCol += ColorToGlmVec4(w.specularColor) * specularWeight;\n\t\t\tbaseCol += ColorToGlmVec4(w.refractionColor) * w.refractionAmount * 0.6f;\n\n\t\t\tfloat totalWeight = baseWeight + specularWeight + w.refractionAmount;\n\t\t\tif (totalWeight > 0.0f) {\n\t\t\t\tbaseCol /= totalWeight;\n\t\t\t}\n\n\t\t\tglm::vec4 emissionVec = ColorToGlmVec4(w.emissionColor);\n\t\t\tglm::vec4 finalCol = glm::mix(baseCol, emissionVec, emissionGain);\n\n\t\t\tw.apparentColor = glmVec4ToColor(finalCol);\n\t\t}\n\n\t\tfor (AreaLight& al : areaLights) {\n\t\t\tal.apparentColor = al.color;\n\t\t}\n\n\t}\n\n\tint accumulatedRays = 0;\n\n\tint totalLights = 0;\n\n\tvoid rayLogic(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\t\tif (shouldRender) {\n\t\t\trays.clear();\n\t\t\tcurrentSamples = 0;\n\n\t\t\taccumulatedRays = 0;\n\t\t\tshouldRender = false;\n\t\t}\n\n\t\tif (IO::shortcutPress(KEY_C)) {\n\t\t\tshouldRender = true;\n\t\t}\n\n\t\tcreatePointLight(myVar, myParam);\n\t\tcreateAreaLight(myVar, myParam);\n\t\tcreateConeLight(myVar, myParam);\n\t\tcreateWall(myVar, myParam);\n\t\tcreateShape(myVar, myParam);\n\n\t\tif (!walls.empty()) {\n\t\t\twallPointers.clear();\n\t\t\tfor (Wall& wall : walls) {\n\t\t\t\twallPointers.push_back(&wall);\n\t\t\t}\n\t\t\tbvh.build(wallPointers);\n\t\t}\n\n\t\tmoveLogic(myVar, myParam);\n\n\t\teraseLogic(myVar, myParam);\n\n\t\tselectLogic(myVar, myParam);\n\n\t\tlightRendering(myParam);\n\n\t\tdrawRays();\n\n\t\ttotalLights = static_cast<int>(pointLights.size()) + static_cast<int>(areaLights.size()) + static_cast<int>(coneLights.size());\n\n\t\tif (currentSamples <= maxSamples) {\n\t\t\taccumulatedRays += static_cast<int>(rays.size());\n\t\t}\n\n\t\tif (IO::shortcutPress(KEY_C)) {\n\t\t\trays.clear();\n\t\t\tpointLights.clear();\n\t\t\tareaLights.clear();\n\t\t\tconeLights.clear();\n\t\t\twalls.clear();\n\t\t\tshapes.clear();\n\n\t\t\twallPointers.clear();\n\t\t\tfor (Wall& wall : walls) {\n\t\t\t\twallPointers.push_back(&wall);\n\t\t\t}\n\t\t\tbvh.build(wallPointers);\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/materialsSPH.h",
    "content": "#pragma once\n\nstruct SPHMaterial {\n\n\tuint32_t id = 0;\n\tstd::string sphLabel = \"material\";\n\n\t// Base values\n\tfloat massMult = 1.0f;\n\tfloat restDens = 0.008f;\n\tfloat stiff = 1.0f;\n\tfloat visc = 1.0f;\n\tfloat cohesion = 1.0f;\n\tColor color = { 255, 255, 255, 255 };\n\n\t// Hot values\n\tfloat hotPoint = 1000.0f;\n\tfloat hotRestDens = 0.01f;\n\tfloat hotMassMult = 1.0f;\n\tfloat hotStiff = 1.0f;\n\tfloat hotVisc = 1.0f;\n\tfloat hotCohesion = 1.0f;\n\tColor hotColor = { 255, 100, 100, 255 };\n\n\t// Cold values\n\tfloat coldPoint = 0.0f;\n\tfloat coldRestDens = 0.01f;\n\tfloat coldMassMult = 1.0f;\n\tfloat coldStiff = 1.0f;\n\tfloat coldVisc = 1.0f;\n\tfloat coldCohesion = 1.0f;\n\tColor coldColor = { 255, 255, 255, 255 };\n\n\t// Other values\n\tfloat heatConductivity = 0.1f;\n\tfloat constraintResistance = 1.0f;\n\tfloat constraintPlasticPoint = 0.5f;\n\tfloat constraintPlasticPointMult = 2.0f;\n\tfloat constraintStiffness = 60.0f;\n\tbool isPlastic = false;\n\n\tvirtual ~SPHMaterial() = default;\n\n\tSPHMaterial(uint32_t id_, const std::string& label)\n\t\t: id(id_), sphLabel(label) {\n\t}\n};\n\n// DISCLAIMER: Welcome to arbitrary town. What does hotPoint mean? Whatever your heart feels like.\n// For example, the hot point of water means boiling in this context. And coldPoint in water's context means freezing\n// These parameters actually just do whatever you like them to do at those temperatures\n// I'm currently not simulating rock getting vaporized for example, so rock only has \"solid\" and \"liquid\"\n// For materials that only are either solid or liquid, I set their cold point to 0.0f because the minimum temperature possible is 1.0f in GE\n\nstruct SPHWater : public SPHMaterial {\n\tSPHWater() : SPHMaterial(1, \"water\") {\n\t\tmassMult = 0.6f;\n\t\trestDens = 0.095f;\n\t\tstiff = 1.0f;\n\t\tvisc = 0.075f;\n\t\tcohesion = 0.05f;\n\t\tcolor = { 30, 65, 230, 150 };\n\n\t\thotPoint = 373.2f;\n\t\thotMassMult = 0.25f;\n\t\thotRestDens = 0.065f;\n\t\thotStiff = 1.0f;\n\t\thotVisc = 0.055f;\n\t\thotCohesion = 0.0f;\n\t\thotColor = { 230, 230, 250, 190 };\n\n\t\tcoldPoint = 273.2f;\n\t\tcoldMassMult = 0.5f;\n\t\tcoldRestDens = 0.046f;\n\t\tcoldStiff = 0.6f;\n\t\tcoldVisc = 2.2f;\n\t\tcoldCohesion = 2500.0f;\n\t\tcoldColor = { 230, 230, 240, 250 };\n\n\t\theatConductivity = 0.15f;\n\t\tconstraintResistance = 0.06f;\n\t\tconstraintPlasticPoint = constraintResistance * 0.5f;\n\t\tconstraintPlasticPointMult = 0.0f;\n\t\tconstraintStiffness = 55.0f;\n\t\tisPlastic = false;\n\t}\n};\n\nstruct SPHRock : public SPHMaterial {\n\tSPHRock() : SPHMaterial(2, \"rock\") {\n\t\tmassMult = 3.3f;\n\t\trestDens = 0.008f;\n\t\tstiff = 1.4f;\n\t\tvisc = 3.0f;\n\t\tcohesion = 1750.0f;\n\t\tcolor = { 150, 155, 160, 255 };\n\n\t\thotPoint = 1370.0f;\n\t\thotMassMult = 2.8f;\n\t\thotRestDens = 0.005f;\n\t\thotStiff = 1.0f;\n\t\thotVisc = 0.6f;\n\t\thotCohesion = 300.0f;\n\t\thotColor = { 255, 105, 0, 255 };\n\n\t\tcoldPoint = 0.0f;\n\t\tcoldMassMult = massMult;\n\t\tcoldRestDens = restDens;\n\t\tcoldStiff = stiff;\n\t\tcoldVisc = visc;\n\t\tcoldCohesion = cohesion;\n\t\tcoldColor = color;\n\n\t\theatConductivity = 0.02f;\n\t\tconstraintResistance = 0.211f;\n\t\tconstraintPlasticPoint = constraintResistance * 0.5f;\n\t\tconstraintPlasticPointMult = 0.0f;\n\t\tconstraintStiffness = 60.0f;\n\t\tisPlastic = false;\n\t}\n};\n\nstruct SPHIron : public SPHMaterial {\n\tSPHIron() : SPHMaterial(3, \"iron\") {\n\t\tmassMult = 4.0f;\n\t\trestDens = 0.008f;\n\t\tstiff = 1.4f;\n\t\tvisc = 3.0f;\n\t\tcohesion = 1750.0f;\n\t\tcolor = { 110, 125, 157, 255 };\n\n\t\thotPoint = 1740.0f;\n\t\thotMassMult = 2.8f;\n\t\thotRestDens = 0.005f;\n\t\thotStiff = 1.0f;\n\t\thotVisc = 0.6f;\n\t\thotCohesion = 300.0f;\n\t\thotColor = { 255, 105, 0, 255 };\n\n\t\tcoldPoint = 0.0f;\n\t\tcoldMassMult = massMult;\n\t\tcoldRestDens = restDens;\n\t\tcoldStiff = stiff;\n\t\tcoldVisc = visc;\n\t\tcoldCohesion = cohesion;\n\t\tcoldColor = color;\n\n\t\theatConductivity = 0.02f;\n\t\tconstraintResistance = 0.24f;\n\t\tconstraintPlasticPoint = constraintResistance * 0.45f;\n\t\tconstraintPlasticPointMult = 1.8f;\n\t\tconstraintStiffness = 60.0f;\n\t\tisPlastic = true;\n\t}\n};\n\nstruct SPHSand : public SPHMaterial {\n\tSPHSand() : SPHMaterial(4, \"sand\") {\n\t\tmassMult = 2.1f;\n\t\trestDens = 0.008f;\n\t\tstiff = 1.255f;\n\t\tvisc = 0.74f;\n\t\tcohesion = 1.0f;\n\t\tcolor = { 200, 185, 100, 255 };\n\n\t\thotPoint = 1200.0f;\n\t\thotMassMult = 1.9f;\n\t\thotRestDens = 0.011f;\n\t\thotStiff = 1.12f;\n\t\thotVisc = 0.6f;\n\t\thotCohesion = 1.0f;\n\t\thotColor = { 255, 105, 0, 255 };\n\n\t\tcoldPoint = 0.0f;\n\t\tcoldMassMult = massMult;\n\t\tcoldRestDens = restDens;\n\t\tcoldStiff = stiff;\n\t\tcoldVisc = visc;\n\t\tcoldCohesion = cohesion;\n\t\tcoldColor = color;\n\n\t\theatConductivity = 0.01f;\n\t\tconstraintResistance = 0.08f;\n\t\tconstraintPlasticPoint = constraintResistance * 0.5f;\n\t\tconstraintPlasticPointMult = 0.0f;\n\t\tconstraintStiffness = 55.0f;\n\t\tisPlastic = false;\n\t}\n};\n\nstruct SPHSoil : public SPHMaterial {\n\tSPHSoil() : SPHMaterial(5, \"soil\") {\n\t\tmassMult = 1.9f;\n\t\trestDens = 0.008f;\n\t\tstiff = 1.0f;\n\t\tvisc = 2.23f;\n\t\tcohesion = 3000.0f;\n\t\tcolor = { 156, 110, 30, 255 };\n\n\t\thotPoint = 950.0f;\n\t\thotMassMult = 1.8f;\n\t\thotRestDens = 0.013f;\n\t\thotStiff = 0.9f;\n\t\thotVisc = 1.8f;\n\t\thotCohesion = 600.0f;\n\t\thotColor = { 255, 105, 0, 255 };\n\n\t\tcoldPoint = 0.0f;\n\t\tcoldMassMult = massMult;\n\t\tcoldRestDens = restDens;\n\t\tcoldStiff = stiff;\n\t\tcoldVisc = visc;\n\t\tcoldCohesion = cohesion;\n\t\tcoldColor = color;\n\n\t\theatConductivity = 0.02f;\n\t\tconstraintResistance = 0.09f;\n\t\tconstraintPlasticPoint = constraintResistance * 0.5f;\n\t\tconstraintPlasticPointMult = 2.0f;\n\t\tconstraintStiffness = 30.0f;\n\t\tisPlastic = true;\n\t}\n};\n\nstruct SPHMud : public SPHMaterial {\n\tSPHMud() : SPHMaterial(6, \"mud\") {\n\t\tmassMult = 2.3f;\n\t\trestDens = 0.0095f;\n\t\tstiff = 1.0f;\n\t\tvisc = 0.6f;\n\t\tcohesion = 100.0f;\n\t\tcolor = { 106, 60, 3, 255 };\n\n\t\thotPoint = 1000.0f;\n\t\thotMassMult = 2.1f;\n\t\thotRestDens = 0.011f;\n\t\thotStiff = 1.0f;\n\t\thotVisc = 0.5f;\n\t\thotCohesion = 40.0f;\n\t\thotColor = { 255, 105, 0, 255 };\n\n\t\tcoldPoint = 0.0f;\n\t\tcoldMassMult = massMult;\n\t\tcoldRestDens = restDens;\n\t\tcoldStiff = stiff;\n\t\tcoldVisc = visc;\n\t\tcoldCohesion = cohesion;\n\t\tcoldColor = color;\n\n\t\theatConductivity = 0.1f;\n\t\tconstraintResistance = 0.03f;\n\t\tconstraintPlasticPoint = constraintResistance * 0.5f;\n\t\tconstraintPlasticPointMult = 2.0f;\n\t\tconstraintStiffness = 20.0f;\n\t\tisPlastic = true;\n\t}\n};\n\nstruct SPHRubber : public SPHMaterial {\n\tSPHRubber() : SPHMaterial(7, \"rubber\") {\n\t\tmassMult = 1.7f;\n\t\trestDens = 0.0095f;\n\t\tstiff = 1.0f;\n\t\tvisc = 0.6f;\n\t\tcohesion = 100.0f;\n\t\tcolor = { 226, 166, 114, 255 };\n\n\t\thotPoint = 453.0f;\n\t\thotMassMult = 1.6f;\n\t\thotRestDens = 0.011f;\n\t\thotStiff = 1.0f;\n\t\thotVisc = 0.5f;\n\t\thotCohesion = 40.0f;\n\t\thotColor = { 255, 105, 0, 255 };\n\n\t\tcoldPoint = 0.0f;\n\t\tcoldMassMult = massMult;\n\t\tcoldRestDens = restDens;\n\t\tcoldStiff = stiff;\n\t\tcoldVisc = visc;\n\t\tcoldCohesion = cohesion;\n\t\tcoldColor = color;\n\n\t\theatConductivity = 1.1f;\n\t\tconstraintResistance = 5.5f;\n\t\tconstraintPlasticPoint = constraintResistance * 0.5f;\n\t\tconstraintPlasticPointMult = 2.0f;\n\t\tconstraintStiffness = 6.0f;\n\t\tisPlastic = true;\n\t}\n};\n\n\nstruct SPHMaterials {\n\n\tstatic std::vector<std::unique_ptr<SPHMaterial>> materials;\n\n\tstatic std::unordered_map<uint32_t, SPHMaterial*> idToMaterial;\n\n\tstatic void Init() {\n\t\tmaterials.emplace_back(std::make_unique<SPHWater>());\n\t\tmaterials.emplace_back(std::make_unique<SPHRock>());\n\t\tmaterials.emplace_back(std::make_unique<SPHIron>());\n\t\tmaterials.emplace_back(std::make_unique<SPHSand>());\n\t\tmaterials.emplace_back(std::make_unique<SPHSoil>());\n\t\tmaterials.emplace_back(std::make_unique<SPHMud>());\n\t\tmaterials.emplace_back(std::make_unique<SPHRubber>());\n\n\t\tfor (auto& mat : materials) {\n\t\t\tidToMaterial[mat->id] = mat.get();\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/morton.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct Morton {\n\n\tstd::vector<size_t> indicesBuffer;\n\tstd::vector<ParticlePhysics> pSortedBuffer;\n\tstd::vector<ParticleRendering> rSortedBuffer;\n\n\tuint64_t scaleToGrid(float pos, float minVal, float maxVal = 2097151.0f);\n\n\tuint64_t spreadBits(uint64_t x);\n\n\tuint64_t morton2D(uint64_t x, uint64_t y);\n\n\tvoid computeMortonKeys(std::vector<ParticlePhysics>& pParticles, glm::vec3& posSize);\n\n\tvoid sortParticlesByMortonKey(std::vector<ParticlePhysics>& pParticles,\n\t\tstd::vector<ParticleRendering>& rParticles);\n\n\n\n    std::vector<size_t> indicesBuffer3D;\n    std::vector<ParticlePhysics3D> pSortedBuffer3D;\n    std::vector<ParticleRendering3D> rSortedBuffer3D;\n\n    uint64_t scaleToGrid3D(float pos, float minVal, float maxVal = 2097151.0f);\n\n    uint64_t spreadBits3D(uint64_t x);\n\n    uint64_t morton3D(uint64_t x, uint64_t y, uint64_t z);\n\n    void computeMortonKeys3D(std::vector<ParticlePhysics3D>& pParticles,\n        const glm::vec4& boundingBox);\n\n    void sortParticlesByMortonKey3D(std::vector<ParticlePhysics3D>& pParticles,\n        std::vector<ParticleRendering3D>& rParticles);\n\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/physics.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Particles/QueryNeighbors.h\"\n\n#include \"Physics/quadtree.h\"\n\n#include \"Physics/constraint.h\"\n\n#include \"parameters.h\"\n\nstruct Physics {\n\n\tstd::vector<ParticleConstraint> particleConstraints;\n\tstd::unordered_map<uint64_t, ParticleConstraint*> constraintMap;\n\n\tstd::vector<int64_t> idToIndexTable;\n\n\tuint64_t makeKey(uint32_t id1, uint32_t id2) {\n\t\treturn id1 < id2 ? ((uint64_t)id1 << 32) | id2\n\t\t\t: ((uint64_t)id2 << 32) | id1;\n\t}\n\n\tconst float globalConstraintDamping = 0.001f;\n\n\tconst float stiffCorrectionRatio = 0.013333f; // Heuristic. This used to modify the stiffness of a constraint in a more intuitive way. DO NOT CHANGE\n\n\tvoid calculateForceFromGrid(UpdateVariables& myVar);\n\tvoid calculateForceFromGridAVX2(UpdateVariables& myVar);\n\n\tglm::vec2 calculateForceFromGridOld(std::vector<ParticlePhysics>& pParticles, UpdateVariables& myVar, ParticlePhysics& pParticle);\n\n\n\tstd::vector<float> posX;\n\tstd::vector<float> posY;\n\tstd::vector<float> accX;\n\tstd::vector<float> accY;\n\tstd::vector<float> velX;\n\tstd::vector<float> velY;\n\tstd::vector<float> prevVelX;\n\tstd::vector<float> prevVelY;\n\tstd::vector<float> mass;\n\tstd::vector<float> temp;\n\n\tvoid flattenParticles(std::vector<ParticlePhysics>& pParticles);\n\n\tvoid naiveGravity(std::vector<ParticlePhysics>& pParticles, UpdateVariables& myVar);\n\tvoid naiveGravityAVX2(std::vector<ParticlePhysics>& pParticles, UpdateVariables& myVar);\n\n\tvoid readFlattenBack(std::vector<ParticlePhysics>& pParticles);\n\n\tvoid temperatureCalculation(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar);\n\n\tvoid createConstraints(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, bool& constraintCreateSpecialFlag,\n\t\tUpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid constraints(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar);\n\n\tvoid pausedConstraints(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar);\n\n\tvoid mergerSolver(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid spawnCorrection(UpdateParameters& myParam, bool& hasVAX2, const int& iterations);\n\n\tvoid integrateStart(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar);\n\n\tvoid integrateEnd(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar);\n\n\tvoid pruneParticles(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar);\n\n\t// ----- Unused. Test code ----- //\n\n\t// This was made for learning purposes and it is not going to replace the existing gravity algorithm.\n\t// To use it, call first initGrid() and then gravityGrid(). Also be sure to process the bounding box before these\n\tstruct GravityCell {\n\t\tglm::vec2 pos;\n\t\tfloat size;\n\t\tfloat mass = 0.0f;\n\t\tglm::vec2 force = { 0.0f, 0.0f };\n\t\tint depth;\n\n\t\tGravityCell() = default;\n\n\t\tGravityCell(glm::vec2 pos, float size, int depth)\n\t\t\t: pos(pos), size(size), depth(depth)\n\t\t{\n\t\t}\n\n\t\tstd::vector<ParticlePhysics*> particles;\n\t};\n\n\tstd::vector<GravityCell> cells;\n\n\tint maxDepth = 9; // This controls quality. Higher means more accurate. Gravity forces scale with this too, which is not intended\n\tint gridRes = 0;\n\n\tvoid initGrid(std::vector<ParticlePhysics>& pParticles, glm::vec3& bb) {\n\n\t\tint totalCells = 0;\n\t\tfor (int depth = 0; depth < maxDepth; depth++) {\n\t\t\tint res = std::pow(2, depth + 1);\n\t\t\ttotalCells += res * res;\n\t\t}\n\n\t\tcells.clear();\n\t\tcells.resize(totalCells);\n\n\t\tint offset = 0;\n\n\t\tfor (int depth = 0; depth < maxDepth; depth++) {\n\t\t\tgridRes = std::pow(2, depth + 1);\n\t\t\tfloat cellSize = bb.z / static_cast<float>(gridRes);\n\n#pragma omp parallel for collapse(2)\n\t\t\tfor (int y = 0; y < gridRes; y++) {\n\t\t\t\tfor (int x = 0; x < gridRes; x++) {\n\t\t\t\t\tint index = offset + y * gridRes + x;\n\t\t\t\t\tcells[index] = GravityCell(\n\t\t\t\t\t\tglm::vec2{ bb.x + (x * cellSize), bb.y + (y * cellSize) },\n\t\t\t\t\t\tcellSize,\n\t\t\t\t\t\tdepth\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toffset += gridRes * gridRes;\n\t\t}\n\t}\n\n\tvoid gravityGrid(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar, glm::vec3& bb);\n\t// ----- Unused. Test code ----- //\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/physics3D.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Physics/quadtree.h\"\n\n#include \"parameters.h\"\n\n#include \"Particles/QueryNeighbors.h\"\n\n#include \"Physics/constraint.h\"\n\nstruct Physics3D {\n\n\tstd::vector<ParticleConstraint> particleConstraints;\n\tstd::unordered_map<uint64_t, ParticleConstraint*> constraintMap;\n\n\tstd::vector<int64_t> idToIndexTable;\n\n\tuint64_t makeKey(uint32_t id1, uint32_t id2) {\n\t\treturn id1 < id2 ? ((uint64_t)id1 << 32) | id2\n\t\t\t: ((uint64_t)id2 << 32) | id1;\n\t}\n\n\tconst float globalConstraintDamping = 0.001f;\n\n\tconst float stiffCorrectionRatio = 0.013333f; // Heuristic. This used to modify the stiffness of a constraint in a more intuitive way. DO NOT CHANGE\n\n\tstd::vector<float> posX;\n\tstd::vector<float> posY;\n\tstd::vector<float> posZ;\n\tstd::vector<float> accX;\n\tstd::vector<float> accY;\n\tstd::vector<float> accZ;\n\tstd::vector<float> velX;\n\tstd::vector<float> velY;\n\tstd::vector<float> velZ;\n\tstd::vector<float> prevVelX;\n\tstd::vector<float> prevVelY;\n\tstd::vector<float> prevVelZ;\n\tstd::vector<float> mass;\n\tstd::vector<float> temp;\n\n\tvoid flattenParticles3D(std::vector<ParticlePhysics3D>& pParticles3D);\n\n\tglm::vec3 calculateForceFromGrid3DOld(std::vector<ParticlePhysics3D>& pParticles,\n\t\tUpdateVariables& myVar,\n\t\tParticlePhysics3D& pParticle);\n\n\tvoid calculateForceFromGrid3D(UpdateVariables& myVar);\n\tvoid calculateForceFromGrid3DAVX2(UpdateVariables& myVar);\n\n\tvoid naiveGravity3D(std::vector<ParticlePhysics3D>& pParticles3D, UpdateVariables& myVar);\n\tvoid naiveGravity3DAVX2(std::vector<ParticlePhysics3D>& pParticles3D, UpdateVariables& myVar);\n\n\tvoid readFlattenBack3D(std::vector<ParticlePhysics3D>& pParticles3D);\n\n\tvoid temperatureCalculation(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, UpdateVariables& myVar);\n\n\tvoid integrateStart3D(std::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, UpdateVariables& myVar);\n\n\tvoid pruneParticles(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, UpdateVariables& myVar);\n\n\tvoid integrateEnd3D(std::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, UpdateVariables& myVar);\n\n\tvoid createConstraints(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, bool& constraintCreateSpecialFlag,\n\t\tUpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid constraints(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, UpdateVariables& myVar);\n\n\tvoid pausedConstraints(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, UpdateVariables& myVar);\n\n\tvoid spawnCorrection(UpdateParameters& myParam, bool& hasAVX2, const int& iterations);\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/quadtree.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\n// --- UNUSED QUADTREE FOR MORTON KEYS VERSION --- //\n//struct Node {\n//\tglm::vec2 pos;\n//\tfloat size;\n//\tfloat mass;\n//\tglm::vec2 CoM;\n//\tuint32_t next;\n//\tint level;\n//\n//\tNode(glm::vec2 pos, float size, float mass, glm::vec2 CoM, uint32_t next, int level) {\n//\t\tthis->pos = pos;\n//\t\tthis->size = size;\n//\t\tthis->mass = mass;\n//\t\tthis->CoM = CoM;\n//\t\tthis->next = next;\n//\t\tthis->level = level;\n//\t}\n//};\n//\n//struct TestTree {\n//\tstatic std::vector<Node> nodesTest;\n//};\n\nstruct Node;\nextern std::vector<Node> globalNodes;\n\nstruct Node {\n\tglm::vec2 pos;\n\tglm::vec2 centerOfMass;\n\n\tfloat size;\n\tfloat gridMass;\n\tfloat gridTemp;\n\n\tuint32_t startIndex;\n\tuint32_t endIndex;\n\tuint32_t next = 0;\n\n\tuint32_t subGrids[2][2] = { { UINT32_MAX, UINT32_MAX }, { UINT32_MAX, UINT32_MAX } };\n\n\tNode(glm::vec2 pos, float size,\n\t\tuint32_t startIndex, uint32_t endIndex,\n\t\tstd::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles);\n\n\tNode() = default;\n\n\tvoid subGridMaker(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles);\n\n\tinline void computeLeafMass(const std::vector<ParticlePhysics>& pParticles) {\n\t\tgridMass = 0.0f;\n\t\tgridTemp = 0.0f;\n\t\tcenterOfMass = { 0.0f, 0.0f };\n\n\t\tfor (uint32_t i = startIndex; i < endIndex; ++i) {\n\t\t\tgridMass += pParticles[i].mass;\n\t\t\tgridTemp += pParticles[i].temp;\n\t\t\tcenterOfMass += pParticles[i].pos * pParticles[i].mass;\n\t\t}\n\n\t\tif (gridMass > 0) {\n\t\t\tcenterOfMass /= gridMass;\n\t\t}\n\t}\n\n\tinline void computeInternalMass() {\n\t\tgridMass = 0.0f;\n\t\tgridTemp = 0.0f;\n\t\tcenterOfMass = { 0.0f, 0.0f };\n\n\t\tfor (int i = 0; i < 2; ++i) {\n\t\t\tfor (int j = 0; j < 2; ++j) {\n\t\t\t\tuint32_t idx = subGrids[i][j];\n\n\t\t\t\tif (idx == UINT32_MAX) continue;\n\n\t\t\t\tNode& child = globalNodes[idx];\n\n\t\t\t\tgridMass += child.gridMass;\n\t\t\t\tgridTemp += child.gridTemp;\n\t\t\t\tcenterOfMass += child.centerOfMass * child.gridMass;\n\n\t\t\t}\n\t\t}\n\n\t\tif (gridMass > 0) {\n\t\t\tcenterOfMass /= gridMass;\n\t\t}\n\t}\n\n\tinline void calculateNextNeighbor() {\n\n\t\tnext = 0;\n\n\t\tfor (int i = 0; i < 2; ++i) {\n\t\t\tfor (int j = 0; j < 2; ++j) {\n\t\t\t\tuint32_t idx = subGrids[i][j];\n\n\t\t\t\tif (idx == UINT32_MAX) continue;\n\n\t\t\t\tnext++;\n\n\t\t\t\tNode& child = globalNodes[idx];\n\n\t\t\t\tnext += child.next;\n\t\t\t}\n\t\t}\n\t}\n};\n\nstruct Quadtree {\n\n\tglm::vec3 boundingBox;\n\n\tQuadtree(std::vector<ParticlePhysics>& pParticles,\n\t\tstd::vector<ParticleRendering>& rParticles,\n\t\tglm::vec3& boundingBox) {\n\n\t\tthis->boundingBox = boundingBox;\n\n\t\troot(pParticles, rParticles);\n\t}\n\n\tvoid root(std::vector<ParticlePhysics>& pParticles,\n\t\tstd::vector<ParticleRendering>& rParticles);\n};\n\nstruct Node3D;\nextern std::vector<Node3D> globalNodes3D;\n\nstruct Node3D {\n\tglm::vec3 pos;\n\tglm::vec3 centerOfMass;\n\n\tfloat size;\n\tfloat gridMass;\n\tfloat gridTemp;\n\n\tuint32_t startIndex;\n\tuint32_t endIndex;\n\tuint32_t next = 0;\n\n\tuint32_t subGrids[2][2][2] = {\n\t\t{ { UINT32_MAX, UINT32_MAX }, { UINT32_MAX, UINT32_MAX } },\n\t\t{ { UINT32_MAX, UINT32_MAX }, { UINT32_MAX, UINT32_MAX } }\n\t};\n\n\tNode3D(glm::vec3 pos, float size,\n\t\tuint32_t startIndex, uint32_t endIndex,\n\t\tstd::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles);\n\n\tNode3D() = default;\n\n\tvoid subGridMaker3D(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles);\n\n\tinline void computeLeafMass3D(const std::vector<ParticlePhysics3D>& pParticles) {\n\t\tgridMass = 0.0f;\n\t\tgridTemp = 0.0f;\n\t\tcenterOfMass = { 0.0f, 0.0f, 0.0f };\n\n\t\tfor (uint32_t i = startIndex; i < endIndex; ++i) {\n\t\t\tgridMass += pParticles[i].mass;\n\t\t\tgridTemp += pParticles[i].temp;\n\n\t\t\tcenterOfMass += pParticles[i].pos * pParticles[i].mass;\n\t\t}\n\n\t\tif (gridMass > 0) {\n\t\t\tcenterOfMass /= gridMass;\n\t\t}\n\t}\n\n\tinline void computeInternalMass3D();\n\n\tinline void calculateNextNeighbor3D();\n};\n\nstruct Octree {\n\n\tglm::vec3 rootPos;\n\tfloat rootSize;\n\n\tOctree(std::vector<ParticlePhysics3D>& pParticles,\n\t\tstd::vector<ParticleRendering3D>& rParticles,\n\t\tglm::vec3 rootPos, float rootSize) {\n\n\t\tthis->rootPos = rootPos;\n\t\tthis->rootSize = rootSize;\n\n\t\troot(pParticles, rParticles);\n\t}\n\n\tvoid root(std::vector<ParticlePhysics3D>& pParticles,\n\t\tstd::vector<ParticleRendering3D>& rParticles);\n};"
  },
  {
    "path": "GalaxyEngine/include/Physics/slingshot.h",
    "content": "#pragma once\n\n#include \"UX/camera.h\"\n\nstruct UpdateVariables;\n\nclass Slingshot {\npublic:\n\tglm::vec2 norm;\n\tfloat length;\n\tSlingshot(glm::vec2 norm, float length);\n\n\t\n\tstatic Slingshot particleSlingshot(UpdateVariables& myVar, SceneCamera myCamera);\n};\n\nclass Slingshot3D {\npublic:\n\tglm::vec3 norm;\n\tfloat length;\n\tSlingshot3D(glm::vec3 norm, float length);\n\n\n\tstatic Slingshot3D particleSlingshot(UpdateVariables& myVar, glm::vec3& brushPos);\n};\n\n"
  },
  {
    "path": "GalaxyEngine/include/Renderer/rayMarching.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"UX/camera.h\"\n#include \"UI/UI.h\"\n#include \"Physics/quadtree.h\"\n\nstruct RayMarcher {\n\n\tstruct Pixel {\n\t\tint x;\n\t\tint y;\n\t\tint size;\n\t\tColor color;\n\t};\n\n\tint screenResX = 480;\n\tint screenResY = 270;\n\n\tint pixelSize = 3;\n\n\tint pixelAmountX = screenResX / pixelSize;\n\tint pixelAmountY = screenResY / pixelSize;\n\n\tfloat aspectRatio = static_cast<float>(pixelAmountX) / static_cast<float>(pixelAmountY);\n\n\tstd::vector<Pixel> pixels;\n\n\tbool initPixels = true;\n\n\tvoid initPixelGrid() {\n\n\t\tif (initPixels) {\n\n\t\t\tpixelAmountX = screenResX / pixelSize;\n\t\t\tpixelAmountY = screenResY / pixelSize;\n\n\t\t\taspectRatio = static_cast<float>(pixelAmountX) / static_cast<float>(pixelAmountY);\n\n\t\t\tfor (int y = 0; y < pixelAmountY; y++) {\n\t\t\t\tfor (int x = 0; x < pixelAmountX; x++) {\n\n\t\t\t\t\tpixels.emplace_back(Pixel{ x * pixelSize, y * pixelSize, pixelSize , Color{ 255, 0, 0, 255 } });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tinitPixels = false;\n\t\t}\n\t}\n\n\tvoid drawPixels() {\n\t\tfor (Pixel& p : pixels) {\n\n\t\t\tDrawRectangle(p.x, p.y, p.size, p.size, { p.color.r,p.color.g, p.color.b, p.color.a });\n\t\t}\n\t}\n\n\tstruct MarcherRay {\n\t\tglm::vec3 source;\n\t\tglm::vec3 dir;\n\t\tfloat totalLength = FLT_MAX;\n\t\tfloat minLength = FLT_MAX;\n\t\tint steps = 0;\n\t\tbool hit = false;\n\t};\n\n\tstd::vector<MarcherRay> rays;\n\n\tfloat camSizeMult = 1.0f;\n\n\tfloat collisionDistMultiplier = 1.0f;\n\tint marchingSteps = 100;\n\n\tfloat fov = 45.0f;\n\n\tfloat nearPlane = 0.0001f;\n\n\tfloat farPlane = 1000.0f;\n\n\tfloat testThreshold = 10.0f;\n\n\tvoid createCameraRay(glm::vec3 dir, glm::vec3 source) {\n\t\trays.emplace_back(source, dir);\n\t}\n\n\tfloat smoothingValue = 5.5f;\n\n\tfloat smoothMin(float a, float b, float k) {\n\t\tfloat h = glm::clamp(0.5f + 0.5f * (b - a) / k, 0.0f, 1.0f);\n\t\treturn glm::mix(b, a, h) - k * h * (1.0f - h);\n\t}\n\n\tvoid rayParticleLogic(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, MarcherRay& ray) {\n\n\t\tglm::vec3 initSource = ray.source;\n\n\t\tif (pParticles.empty()) return;\n\n\t\tfloat minSceneDist = FLT_MAX;\n\t\tfloat minRayLength = FLT_MAX;\n\n\t\tfor (int iter = 0; iter < marchingSteps; iter++) {\n\n\t\t\tminSceneDist = FLT_MAX;\n\n\t\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\t\t\tParticlePhysics3D& p = pParticles[i];\n\t\t\t\tParticleRendering3D& r = rParticles[i];\n\n\t\t\t\tfloat centerDist = glm::distance(ray.source, p.pos);\n\n\t\t\t\tfloat surfaceDist = centerDist - (r.totalRadius * collisionDistMultiplier);\n\n\t\t\t\tminSceneDist = smoothMin(minSceneDist, surfaceDist, smoothingValue);\n\n\t\t\t\tminRayLength = smoothMin(minRayLength, surfaceDist, smoothingValue);;\n\n\t\t\t}\n\n\t\t\tif (minSceneDist < nearPlane) {\n\t\t\t\tray.hit = true;\n\t\t\t\tray.totalLength = glm::distance(initSource, ray.source);\n\t\t\t\tray.minLength = minSceneDist;\n\t\t\t\tray.steps = iter;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tray.source += ray.dir * minSceneDist;\n\n\t\t\tif (minSceneDist > farPlane) {\n\t\t\t\tray.totalLength = glm::distance(initSource, ray.source);\n\t\t\t\tray.minLength = minRayLength;\n\t\t\t\tray.steps = marchingSteps;\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tfloat de(glm::vec3 p0) {\n\t\tglm::vec4 p(p0, 1.0f);\n\n\t\tfor (int i = 0; i < 8; i++) {\n\t\t\tp.x = std::fmod(p.x - 1.0f, 2.0f);\n\t\t\tif (p.x < 0.0f) p.x += 2.0f;\n\t\t\tp.x -= 1.0f;\n\n\t\t\tp.y = std::fmod(p.y - 1.0f, 2.0f);\n\t\t\tif (p.y < 0.0f) p.y += 2.0f;\n\t\t\tp.y -= 1.0f;\n\n\t\t\tp.z = std::fmod(p.z - 1.0f, 2.0f);\n\t\t\tif (p.z < 0.0f) p.z += 2.0f;\n\t\t\tp.z -= 1.0f;\n\n\t\t\tfloat scale = 1.4f / glm::dot(glm::vec3(p), glm::vec3(p));\n\t\t\tp *= scale;\n\t\t}\n\n\t\tglm::vec2 xz(p.x / p.w, p.z / p.w);\n\t\treturn glm::length(xz) * 0.25f;\n\t}\n\n\tvoid fractal(MarcherRay& ray) {\n\t\tglm::vec3 initSource = ray.source;\n\t\tfloat minRayLength = FLT_MAX;\n\n\t\tfor (int iter = 0; iter < marchingSteps; iter++) {\n\n\t\t\tfloat minSceneDist = de(ray.source);\n\n\t\t\tminRayLength = std::min(minRayLength, minSceneDist);\n\n\t\t\tif (minSceneDist < nearPlane) {\n\t\t\t\tray.hit = true;\n\t\t\t\tray.totalLength = glm::distance(initSource, ray.source);\n\t\t\t\tray.minLength = minSceneDist;\n\t\t\t\tray.steps = iter;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tray.source += ray.dir * minSceneDist;\n\n\t\t\tif (glm::distance(initSource, ray.source) > farPlane) {\n\t\t\t\tray.totalLength = glm::distance(initSource, ray.source);\n\t\t\t\tray.minLength = minRayLength;\n\t\t\t\tray.steps = marchingSteps;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tray.totalLength = glm::distance(initSource, ray.source);\n\t\tray.minLength = minRayLength;\n\t\tray.steps = marchingSteps;\n\t}\n\n\tvoid rayMarchUI() {\n\n\t\tbool enabled = true;\n\n\t\tUI::sliderHelper(\"Collision Distance\", \"\", collisionDistMultiplier, 0.1f, 100.0f, 200.0f, 50.0f, enabled, UI::LogSlider);\n\t\tUI::sliderHelper(\"Marching Steps\", \"\", marchingSteps, 1, 200, 200.0f, 50.0f, enabled);\n\n\t\tUI::sliderHelper(\"Test Threshold\", \"\", testThreshold, 0.001f, 100.0f, 200.0f, 50.0f, enabled, UI::LogSlider);\n\t\tUI::sliderHelper(\"Smooth Value\", \"\", smoothingValue, 0.0f, 15.0f, 200.0f, 50.0f, enabled, UI::LogSlider);\n\n\t\tUI::sliderHelper(\"Near Plane\", \"\", nearPlane, 0.001f, 1.0f, 200.0f, 50.0f, enabled, UI::LogSlider);\n\t\tUI::sliderHelper(\"Far Plane\", \"\", farPlane, 100.0f, 100000.0f, 200.0f, 50.0f, enabled, UI::LogSlider);\n\t}\n\n\tvoid cameraRays(SceneCamera3D& cam, std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {\n\n\t\trays.clear();\n\n\t\tfloat halfFovRadians = glm::radians(fov) * 0.5f;\n\t\tfloat verticalScale = tan(halfFovRadians) * 2.0f;\n\n\t\tfor (int y = 0; y < pixelAmountY; y++) {\n\t\t\tfor (int x = 0; x < pixelAmountX; x++) {\n\n\t\t\t\tfloat offsetX = (static_cast<float>(x) / static_cast<float>(pixelAmountX) - 0.5f) * aspectRatio;\n\t\t\t\tfloat offsetY = (static_cast<float>(y) / static_cast<float>(pixelAmountY) - 0.5f);\n\n\t\t\t\tglm::vec3 dir = -cam.camNormal;\n\n\t\t\t\tdir += cam.camRight * offsetX * verticalScale;\n\n\t\t\t\tdir -= cam.camUp * offsetY * verticalScale;\n\n\t\t\t\tdir = glm::normalize(dir);\n\n\t\t\t\tglm::vec3 rayOrigin = { cam.cam3D.position.x, cam.cam3D.position.y, cam.cam3D.position.z };\n\n\t\t\t\tcreateCameraRay(dir, rayOrigin);\n\t\t\t}\n\t\t}\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (size_t i = 0; i < rays.size(); i++) {\n\n\t\t\tMarcherRay& ray = rays[i];\n\n\t\t\trayParticleLogic(pParticles, rParticles, ray);\n\t\t\tfractal(ray);\n\t\t}\n\n\t\tfor (size_t i = 0; i < pixels.size(); i++) {\n\n\t\t\tPixel& p = pixels[i];\n\t\t\tMarcherRay& r = rays[i];\n\n\t\t\tif (true) {\n\t\t\t\tunsigned char value = static_cast<unsigned char>((float(std::min(float(r.steps), abs(testThreshold))) / testThreshold) * 255.0f);\n\n\t\t\t\tp.color = { value,value,value, 255 };\n\t\t\t}\n\t\t\telse {\n\t\t\t\tp.color = { 0,0,0,200 };\n\t\t\t}\n\t\t}\n\t}\n\n\tconst char* rayMarcherCompute = R\"(\n#version 430 core\nlayout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\nlayout(rgba16f, binding = 0) uniform image2D imgOutput;\n\nuniform vec3 camPos;\nuniform vec3 camDir;\nuniform vec3 camRight;\nuniform vec3 camUp;\nuniform float fov;\nuniform int maxSteps;\nuniform float collisionMult;\nuniform float testThreshold;\nuniform float smoothVal;\nuniform float nearPlane;\nuniform float farPlane;\nuniform vec2 resolution;\n\nstruct Particle {\n    vec3 pos;\n    float radius;\n};\n\nlayout(std430, binding = 1) buffer ParticleBuffer {\n    Particle particles[];\n};\n\nfloat smoothMin(float a, float b, float k) {\n    float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);\n    return mix(b, a, h) - k * h * (1.0 - h);\n}\n\n  float de(vec3 p){\n    float s=2., l=0.;\n    p=abs(p);\n    for(int j=0;j++<8;)\n      p=-sign(p)*(abs(abs(abs(p)-2.)-1.)-1.),\n      p*=l=-1.3/dot(p,p),\n      p-=.15, s*=l;\n    return length(p)/s;\n  }\n\nvec3 calcNormal(vec3 p) {\n    const float h = 0.0001;\n    return normalize(vec3(\n        de(vec3(p.x + h, p.y, p.z)) - de(vec3(p.x - h, p.y, p.z)),\n        de(vec3(p.x, p.y + h, p.z)) - de(vec3(p.x, p.y - h, p.z)),\n        de(vec3(p.x, p.y, p.z + h)) - de(vec3(p.x, p.y, p.z - h))\n    ));\n}\n\nstruct RayResult {\n    vec3 color;\n    vec3 hitPos;\n    vec3 hitNormal;\n    int steps;\n    float minDist;\n    float maxDist;\n    bool hit;\n};\n\nRayResult rayParticle(vec3 origin, vec3 direction) {\n    RayResult result;\n    result.hit = false;\n    result.steps = maxSteps;\n    \n    vec3 currentPos = origin;\n    float totalDist = 0.0;\n    float minDist = 1000000.0f;\n    float maxDist = 0.0f;\n    \n    for (int i = 0; i < maxSteps; i++) {\n        float sceneDist = 1000000.0f;\n        \n        for (int p = 0; p < particles.length(); p++) {\n            float distToParticle = distance(currentPos, particles[p].pos);\n            float surfaceDist = distToParticle - (particles[p].radius * collisionMult);\n\n            sceneDist = smoothMin(sceneDist, surfaceDist, smoothVal);\n        }\n\n        minDist = min(minDist, sceneDist);\n        maxDist = max(maxDist, sceneDist);\n\n        if (sceneDist < nearPlane) {\n            result.hit = true;\n            result.hitPos = currentPos;\n            result.hitNormal = calcNormal(currentPos);\n            result.steps = i;\n            result.minDist = minDist;\n            result.maxDist = maxDist;\n            break;\n        }\n\n        totalDist += sceneDist;\n        currentPos += direction * sceneDist;\n\n        if (totalDist > farPlane) {\n            result.steps = maxSteps;\n            result.minDist = minDist;\n            result.maxDist = maxDist;\n            break;\n        }\n    }\n    \n    if (!result.hit && totalDist <= farPlane) {\n        result.minDist = minDist;\n        result.maxDist = maxDist;\n    }\n    \n    float intensity = float(result.steps) / abs(testThreshold);\n    if (testThreshold < 0.0){\n        intensity = 1.0 - intensity;\n    }\n    result.color = vec3(intensity);\n    \n    return result;\n}\n\nRayResult fractal(vec3 origin, vec3 direction) {\n    RayResult result;\n    result.hit = false;\n    result.steps = maxSteps;\n    \n    vec3 currentPos = origin;\n    float totalDist = 0.0;\n    float minDist = 1000000.0f;\n    float maxDist = 0.0f;\n    \n    for (int i = 0; i < maxSteps; i++) {\n        float dist = de(currentPos);\n        \n        minDist = min(minDist, dist);\n        maxDist = max(maxDist, dist);\n        \n        if (dist < nearPlane) {\n            result.hit = true;\n            result.hitPos = currentPos;\n            result.hitNormal = calcNormal(currentPos);\n            result.steps = i;\n            result.minDist = minDist;\n            result.maxDist = maxDist;\n            break;\n        }\n        \n        totalDist += dist;\n        currentPos += direction * dist;\n        \n        if (totalDist > farPlane) {\n            result.steps = maxSteps;\n            result.minDist = minDist;\n            result.maxDist = maxDist;\n            break;\n        }\n    }\n    if (!result.hit && totalDist <= farPlane) {\n        result.minDist = minDist;\n        result.maxDist = maxDist;\n    }\n    \n    float intensity = float(result.steps) / abs(testThreshold);\n    if (testThreshold < 0.0){\n        intensity = 1.0 - intensity;\n    }\n    result.color = vec3(intensity);\n    \n    return result;\n}\n\nvoid main() {\n    ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);\n    ivec2 dims = imageSize(imgOutput);\n    if (pixel_coords.x >= dims.x || pixel_coords.y >= dims.y) return;\n    \n    float aspectRatio = resolution.x / resolution.y;\n    vec2 uv = (vec2(pixel_coords) / resolution) * 2.0 - 1.0;\n    \n    float halfFovRadians = radians(fov) * 0.5;\n    float verticalScale = tan(halfFovRadians) * 2.0;\n    float offsetX = uv.x * 0.5 * aspectRatio * verticalScale;\n    float offsetY = uv.y * 0.5 * verticalScale;\n    \n    vec3 rayDir = normalize(-camDir + (camRight * offsetX) + (camUp * offsetY));\n\n    vec3 finalColor = vec3(0.0);\n    vec3 currentOrigin = camPos;\n    vec3 currentDir = rayDir;\n    \n    RayResult result = rayParticle(currentOrigin, currentDir);\n    \n    if (true) {\n        finalColor += result.color;\n\n    } else {\n        finalColor = vec3(0.0f);\n    }\n    \n    vec4 color = vec4(finalColor, 1.0);\n    imageStore(imgOutput, pixel_coords, color);\n}\n)\";\n\n\n\tGLuint rayMarcherProgram;\n\tGLuint particleSSBO;\n\tGLuint highResTexID;\n\tGLuint lowResTexID;\n\tTexture2D highResTextureRayMarcher;\n\tTexture2D lowResTextureRayMarcher;\n\n\tvoid createShaderProgram() {\n\t\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\t\tglShaderSource(shader, 1, &rayMarcherCompute, NULL);\n\t\tglCompileShader(shader);\n\n\t\tint success;\n\t\tchar infoLog[1024];\n\t\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\t\tif (!success) {\n\t\t\tglGetShaderInfoLog(shader, 1024, NULL, infoLog);\n\t\t\tstd::cerr << \"COMPUTE SHADER ERROR:\\n\" << infoLog << std::endl;\n\t\t}\n\n\t\trayMarcherProgram = glCreateProgram();\n\t\tglAttachShader(rayMarcherProgram, shader);\n\t\tglLinkProgram(rayMarcherProgram);\n\t\tglDeleteShader(shader);\n\t}\n\n\tvoid createOutputTextures() {\n\n\t\tglGenTextures(1, &highResTexID);\n\t\tglBindTexture(GL_TEXTURE_2D, highResTexID);\n\n\t\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n\t\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\t\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n\t\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n\n\t\tglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, GetScreenWidth(), GetScreenHeight(), 0, GL_RGBA, GL_FLOAT, NULL);\n\n\t\thighResTextureRayMarcher.id = highResTexID;\n\t\thighResTextureRayMarcher.width = GetScreenWidth();\n\t\thighResTextureRayMarcher.height = GetScreenHeight();\n\t\thighResTextureRayMarcher.mipmaps = 1;\n\t\thighResTextureRayMarcher.format = PIXELFORMAT_UNCOMPRESSED_R16G16B16A16;\n\n\t\tint lowW = GetScreenWidth() / 8;\n\t\tint lowH = GetScreenHeight() / 8;\n\n\t\tglGenTextures(1, &lowResTexID);\n\t\tglBindTexture(GL_TEXTURE_2D, lowResTexID);\n\n\t\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n\t\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\t\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n\t\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n\n\t\tglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, lowW, lowH, 0, GL_RGBA, GL_FLOAT, NULL);\n\n\t\tlowResTextureRayMarcher.id = lowResTexID;\n\t\tlowResTextureRayMarcher.width = lowW;\n\t\tlowResTextureRayMarcher.height = lowH;\n\t\tlowResTextureRayMarcher.mipmaps = 1;\n\t\tlowResTextureRayMarcher.format = PIXELFORMAT_UNCOMPRESSED_R16G16B16A16;\n\n\t\tglBindTexture(GL_TEXTURE_2D, 0);\n\t}\n\n\tvoid createSSBO() {\n\t\tglGenBuffers(1, &particleSSBO);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, particleSSBO);\n\t\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, particleSSBO);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);\n\t}\n\n\tvoid Init() {\n\t\tcreateOutputTextures();\n\t\tcreateShaderProgram();\n\t\tcreateSSBO();\n\t\tstd::cout << \"RayMarcher Initialized on GPU.\" << std::endl;\n\t}\n\n\tstruct GPUParticle {\n\t\tfloat x, y, z;\n\t\tfloat radius;\n\t};\n\n\tvoid Run(const SceneCamera3D& cam, const std::vector<ParticlePhysics3D>& pParticles, const std::vector<ParticleRendering3D>& rParticles,\n\t\tbool& lowResRayMarching) {\n\n\t\tint currentW = GetScreenWidth();\n\t\tint currentH = GetScreenHeight();\n\t\tGLuint targetTex = highResTexID;\n\n\t\tif (lowResRayMarching) {\n\t\t\tcurrentW /= 8;\n\t\t\tcurrentH /= 8;\n\t\t\ttargetTex = lowResTexID;\n\t\t}\n\n\t\tstatic std::vector<GPUParticle> gpuData;\n\t\tgpuData.clear();\n\t\tgpuData.reserve(pParticles.size());\n\n\t\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\t\tGPUParticle p;\n\t\t\tp.x = pParticles[i].pos.x;\n\t\t\tp.y = pParticles[i].pos.y;\n\t\t\tp.z = pParticles[i].pos.z;\n\t\t\tp.radius = rParticles[i].totalRadius;\n\t\t\tgpuData.push_back(p);\n\t\t}\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, particleSSBO);\n\n\t\tglBufferData(GL_SHADER_STORAGE_BUFFER, gpuData.size() * sizeof(GPUParticle), gpuData.data(), GL_DYNAMIC_DRAW);\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);\n\n\t\tglUseProgram(rayMarcherProgram);\n\n\t\tglBindImageTexture(0, targetTex, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);\n\n\t\tglUniform3f(glGetUniformLocation(rayMarcherProgram, \"camPos\"), cam.cam3D.position.x, cam.cam3D.position.y, cam.cam3D.position.z);\n\t\tglUniform3f(glGetUniformLocation(rayMarcherProgram, \"camDir\"), cam.camNormal.x, cam.camNormal.y, cam.camNormal.z);\n\t\tglUniform3f(glGetUniformLocation(rayMarcherProgram, \"camRight\"), cam.camRight.x, cam.camRight.y, cam.camRight.z);\n\t\tglUniform3f(glGetUniformLocation(rayMarcherProgram, \"camUp\"), cam.camUp.x, cam.camUp.y, cam.camUp.z);\n\n\t\tglUniform1f(glGetUniformLocation(rayMarcherProgram, \"fov\"), 45.0f);\n\t\tglUniform1i(glGetUniformLocation(rayMarcherProgram, \"maxSteps\"), marchingSteps);\n\t\tglUniform1f(glGetUniformLocation(rayMarcherProgram, \"collisionMult\"), collisionDistMultiplier);\n\t\tglUniform1f(glGetUniformLocation(rayMarcherProgram, \"testThreshold\"), testThreshold);\n\t\tglUniform1f(glGetUniformLocation(rayMarcherProgram, \"smoothVal\"), smoothingValue);\n\t\tglUniform1f(glGetUniformLocation(rayMarcherProgram, \"nearPlane\"), nearPlane);\n\t\tglUniform1f(glGetUniformLocation(rayMarcherProgram, \"farPlane\"), farPlane);\n\t\tglUniform2f(glGetUniformLocation(rayMarcherProgram, \"resolution\"), (float)currentW, (float)currentH);\n\n\t\tglDispatchCompute((currentW + 7) / 8, (currentH + 7) / 8, 1);\n\n\t\tglMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);\n\t}\n\n\tvoid Draw(bool& lowResRayMarching) {\n\t\tTexture2D texToDraw = lowResRayMarching ? lowResTextureRayMarcher : highResTextureRayMarcher;\n\n\t\tRectangle source = { 0.0f, 0.0f, (float)texToDraw.width, -(float)texToDraw.height };\n\n\t\tRectangle dest = { 0.0f, 0.0f, (float)GetScreenWidth(), (float)GetScreenHeight() };\n\n\t\tDrawTexturePro(texToDraw, source, dest, { 0,0 }, 0.0f, WHITE);\n\t}\n\n\tvoid Unload() {\n\t\tglDeleteProgram(rayMarcherProgram);\n\n\t\tglDeleteTextures(1, &highResTexID);\n\t\tglDeleteTextures(1, &lowResTexID);\n\n\t\tglDeleteBuffers(1, &particleSSBO);\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/Sound/sound.h",
    "content": "#pragma once\n\nstruct GESound {\n\n\tstatic Sound intro;\n\n\tstatic Sound soundButtonHover1;\n\tstatic Sound soundButtonHover2;\n\tstatic Sound soundButtonHover3;\n\tstatic Sound soundButtonEnable;\n\tstatic Sound soundButtonDisable;\n\n\tstatic Sound soundSliderSlide;\n\n\tstatic std::vector<Sound> soundButtonHover1Pool;\n\tstatic std::vector<Sound> soundButtonHover2Pool;\n\tstatic std::vector<Sound> soundButtonHover3Pool;\n\tstatic std::vector<Sound> soundButtonEnablePool;\n\tstatic std::vector<Sound> soundButtonDisablePool;\n\n\tstatic std::vector<Sound> soundSliderSlidePool;\n\n\tint soundPoolSize = 30;\n\tint soundSliderSlidePoolSize = 150;\n\n\tMusic currentMusic;\n\tint currentSongIndex = 0;\n\tbool musicPlaying = false;\n\tbool isFirstTimePlaying = true;\n\tbool hasTrackChanged = false;\n\n\tfloat globalVolume = 0.4f;\n\tfloat menuVolume = 0.25f;\n\tfloat musicVolume = 0.4f;\n\n\tfloat musicVolMultiplier = 1.0f;\n\n\tstd::vector<std::string> playlist = {\n\t\"Sounds/Soundtrack/Passage of the Stars.wav\",\n\t\"Sounds/Soundtrack/Star Born Microbes.wav\",\n\t\"Sounds/Soundtrack/Celestial Bodies.wav\",\n\t\"Sounds/Soundtrack/Beyond the Solar Alignment.wav\",\n\t\"Sounds/Soundtrack/200 Light-Years From Here.wav\",\n\t};\n\n\n\tvoid loadSounds();\n\n\tvoid soundtrackLogic();\n\n\tvoid unloadSounds();\n\n};"
  },
  {
    "path": "GalaxyEngine/include/UI/UI.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Physics/quadtree.h\"\n#include \"Physics/SPH.h\"\n#include \"Physics/light.h\"\n\n#include \"Particles/particleSpaceship.h\"\n\n#include \"UX/saveSystem.h\"\n\n#include \"Sound/sound.h\"\n\n#include \"parameters.h\"\n\n#include \"Physics/field.h\"\n\nclass SaveSystem;\n\nstruct settingsParams {\n\tstd::string text;\n\tstd::string tooltip;\n\tbool& parameter;\n\n\tsettingsParams(const std::string& t, const std::string& tool, bool& p) : text(t), tooltip(tool), parameter(p) {}\n};\n\nstruct sphParams {\n\tstd::string text;\n\tbool& parameter;\n\n\tsphParams(const std::string& t, bool& p) : text(t), parameter(p) {}\n};\n\nstruct PlotData {\n\tstd::vector<float> values;\n\tint offset = 0;\n};\n\nclass UI {\npublic:\n\n\tbool bVisualsSliders = true;\n\tbool bPhysicsSliders = false;\n\tbool bRecordingSettings = false;\n\tbool bStatsWindow = false;\n\tbool bSoundWindow = false;\n\tbool bLightingWindow = false;\n\n\tbool prevSPHState = false;\n\tbool prevMassMultiplier = false;\n\n\tvoid uiLogic(UpdateParameters& myParam, UpdateVariables& myVar, SPH& sph, SaveSystem& save, \n\t\tGESound& geSound, Lighting& lighting, Field& field, ParticleSpaceship& ship);\n\n\tvoid statsWindowLogic(UpdateParameters& myParam, UpdateVariables& myVar);\n\n\tstatic void plotLinesHelper(const float& timeFactor, std::string label,\n\t\tconst int length,\n\t\tfloat value, const float minValue, const float maxValue, ImVec2 size);\n\n\n\tstatic bool buttonHelper(std::string label, std::string tooltip, bool& parameter, float sizeX, float sizeY, bool canDeactivateSelf, \n\t\tbool& isEnabled);\n\n\tenum ExtraParams {\n\t\tLogSlider\n\t};\n\n\t// Float Logarithmic Overload\n\tstatic bool sliderHelper(std::string label, std::string tooltip, float& parameter, float minVal, float maxVal,\n\t\tfloat sizeX, float sizeY, bool& isEnabled, int logarithmic);\n\n\tstatic bool sliderHelper(std::string label, std::string tooltip, float& parameter, float minVal, float maxVal,\n\t\tfloat sizeX, float sizeY, bool& isEnabled);\n\n\t// Int Overload\n\tstatic bool sliderHelper(std::string label, std::string tooltip, int& parameter, int minVal, int maxVal,\n\t\tfloat sizeX, float sizeY, bool& isEnabled);\n\n\tbool showSettings = true;\n\nprivate:\n\tbool loadSettings = true;\n\n\tstatic std::unordered_map<std::string, PlotData> plotDataMap;\n\n\tImVec2 graphDefaultSize = { 340.0f, 250.0f };\n\n\tint graphHistoryLimit = 1000;\n};\n\nstruct SimilarTypeButton {\n\tstruct Mode {\n\t\tconst char* label;\n\t\tconst char* tooltip;\n\t\tbool* flag;\n\t};\n\n\tstatic void buttonIterator(std::vector<Mode>& modes, float sizeX, float sizeY, bool canDeactivateSelf, bool& isEnabled) {\n\t\tfor (size_t i = 0; i < modes.size(); ++i) {\n\t\t\tif (UI::buttonHelper(modes[i].label, modes[i].tooltip, *modes[i].flag, sizeX, sizeY, canDeactivateSelf, isEnabled)) {\n\t\t\t\tfor (size_t j = 0; j < modes.size(); ++j) {\n\t\t\t\t\tif (j != i) {\n\t\t\t\t\t\t*modes[j].flag = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/UI/brush.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Physics/materialsSPH.h\"\n\n#include \"Physics/quadtree.h\"\n\n#include \"UX/camera.h\"\n\n#include \"Particles/clusterMouseHelper.h\"\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\nclass Brush {\npublic:\n\tglm::vec2 mouseWorldPos;\n\n\tvoid brushLogic(UpdateParameters& myParam, bool& isSPHEnabled, bool& constraintAfterDrawing, float& massScatter, UpdateVariables& myVar);\n\n\tvoid brushSize();\n\n\tvoid drawBrush(glm::vec2 mouseWorldPos);\n\n\tvoid eraseBrush(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid particlesAttractor(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid particlesSpinner(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid particlesGrabber(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid temperatureBrush(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tfloat brushRadius = 25.0f;\n\nprivate:\n\n\tfloat spinForce = 40.0f;\n\n\tglm::vec2 attractorForce = { 0.0f, 0.0f };\n\n\tbool dragging = false;\n\tglm::vec2 lastMouseVelocity = { 0.0f, 0.0f };\n\n\tstd::vector<ParticlePhysics*> grabbedParticles;\n};\n\nclass Brush3D {\npublic:\n\tglm::vec2 mouseWorldPos;\n\n\tvoid brushLogic(UpdateParameters& myParam, bool& isSPHEnabled, bool& constraintAfterDrawing, float& massScatter, UpdateVariables& myVar);\n\n\tvoid brushSize();\n\n\tvoid drawBrush(float& domainHeight);\n\n\tglm::vec3 brushPosLogic(UpdateParameters& myParam, UpdateVariables& myVar);\n\n\n\tvoid eraseBrush(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid particlesAttractor(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid particlesSpinner(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid particlesGrabber(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid temperatureBrush(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tfloat brushRadius = 25.0f;\n\n\tglm::vec3 brushPos = { 0.0f, 0.0f, 0.0f };\n\n\tfloat spawnDistance = 500.0f;\n\tfloat originalSpawnDistance = spawnDistance;\n\nprivate:\n\n\tbool wasEmpty = true;\n\n\tfloat spinForce = 40.0f;\n\n\tglm::vec3 attractorForce = { 0.0f, 0.0f, 0.0f };\n\n\tbool dragging = false;\n\tglm::vec3 lastBrushVelocity = { 0.0f, 0.0f, 0.0f };\n\n\tglm::vec3 lastBrushPos = { 0.0f, 0.0f, 0.0f };\n\n\tstd::vector<ParticlePhysics*> grabbedParticles;\n};"
  },
  {
    "path": "GalaxyEngine/include/UI/controls.h",
    "content": "#pragma once\n\nstruct UpdateVariables;\nclass UI;\n\nstruct Controls {\n\n\n\tbool isShowControlsEnabled = false;\n\n\tbool isInformationEnabled = false;\n\n\tvoid showControls();\n\n\tvoid showInfo(bool& fullscreen);\n\n\tstd::array<std::string, 57> controlsArray = {\n\t\"----PARTICLES CREATION----\",\n\t\"1. Hold MMB: Paint particles\",\n\t\"2. Hold 1 and Drag: Create big galaxy\",\n\t\"3. Hold 2 and Drag: Create small galaxy\",\n\t\"4. Hold 3 and Drag: Create star\",\n\t\"5. Press 4: Create Big Bang\",\n\t\"6. C: Clear all particles\",\n\t\"\",\n\t\"----CAMERA AND SELECTION----\",\n\t\"1. Move with RMB\",\n\t\"2. Zoom with mouse wheel\",\n\t\"3. LCTRL + RMB on cluster to follow it\",\n\t\"4. LALT + RMB on particle to follow it\",\n\t\"5. LCTRL + LMB on cluster to select it\",\n\t\"6. LALT + LMB on particle to select it\",\n\t\"7. LCTRL + hold and drag MMB to particle box select\",\n\t\"8. LALT + hold and drag MMB to particle box deselect\",\n\t\"9. Select on empty space to deselect all\",\n\t\"10. Hold SHIFT to add to selection\",\n\t\"11. I: Invert selection\",\n\t\"12. Z: Center camera on selected particles\",\n\t\"13. F: Reset camera \",\n\t\"14. D: Deselect all particles\",\n\t\"\",\n\t\"----3D CAMERA----\",\n\t\"1. Orbit with RMB\",\n\t\"2. Pan with RMB + ALT\",\n\t\"3. Zoom with mouse wheel\",\n\t\"4. Use arrows to move\",\n\t\"\",\n\t\"----UTILITY----\",\n\t\"1. TAB: Toggle fullscreen\",\n\t\"2. T: Toggle global trails\",\n\t\"3. LCTRL + T: Toggle local trails\",\n\t\"4. U: Toggle UI\",\n\t\"5. RMB on slider to set it to default\",\n\t\"6. Right click to open extra settings\",\n\t\"7. LCTRL + Scroll wheel : Brush size\",\n\t\"8. B: Brush attract particles\",\n\t\"9. N: Brush spin particles\",\n\t\"10. M: Brush grab particles\",\n\t\"11. Hold CTRL to invert brush effects\",\n\t\"12. R: Record\",\n\t\"13. S: Take screenshot\",\n\t\"14. X + MMB: Eraser\",\n\t\"15. H: Copy selected\",\n\t\"16. Hold J and drag: Throw copied\",\n\t\"17. Arrows: Control selected particles\",\n\t\"18. K: Heat brush\",\n\t\"19. L: Cool brush\",\n\t\"20. P: Constraint Solids\"\n\t};\n\n\tstd::array<std::string, 33> infoArray = {\n\t\t\"----INFORMATION----\",\n\t\t\"\",\n\t\t\"Galaxy Engine is a personal project done for learning purposes\",\n\t\t\"by Narcis Calin. The project was entirely made with Raylib\",\n\t\t\"and C++ and it uses external libraries, including ImGui and FFmpeg.\",\n\t\t\"Galaxy Engine is Open Source and the code is available to anyone on GitHub.\",\n\t\t\"Below you can find some useful information:\",\n\t\t\"\",\n\t\t\"1. Theta: Controls the quality of the Barnes-Hut simulation\",\n\t\t\"\",\n\t\t\"2. Barnes-Hut: This is the main gravity algorithm.\",\n\t\t\"\",\n\t\t\"3. Dark Matter: Galaxy Engine simulates dark matter with\",\n\t\t\"invisible particles, which are 5 times heavier than visible ones\",\n\t\t\"\",\n\t\t\"4. Multi-Threading: Parallelizes the simulation across multiple\",\n\t\t\"threads. The default is half the max amount of threads your CPU has,\",\n\t\t\"but it is possible to modify this number.\",\n\t\t\"\",\n\t\t\"5. Collisions: Currently, collisions are experimental. They do not\",\n\t\t\"respect conservation of energy when they are enabled with gravity.\",\n\t\t\"They work as intended when gravity is disabled.\",\n\t\t\"\",\n\t\t\"6. Fluids Mode: This enables fluids for planetary simulation. Each\",\n\t\t\"material has different parameters like stiffness, viscosity,\",\n\t\t\"cohesion, and more.\",\n\t\t\"\",\n\t\t\"7. Frames Export Safe Mode: It is enabled by default when export\",\n\t\t\"frames is enabled. It stores your frames directly to disk, avoiding\",\n\t\t\"filling up your memory. Disabling it will make the render process\",\n\t\t\"much faster, but the program might crash once you run out of memory\",\n\t\t\"\",\n\t\t\"You can report any bugs you may find on our Discord Community.\"\n\t};\n};"
  },
  {
    "path": "GalaxyEngine/include/UI/rightClickSettings.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleSubdivision.h\"\n#include \"Particles/particleSelection.h\"\n#include \"Particles/particleDeletion.h\"\n\n#include \"UX/camera.h\"\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\nstruct rightClickParams {\n\tstd::string text;\n\tstd::string tooltip;\n\tbool& parameter;\n\n\trightClickParams(const std::string& t, const std::string& tool, bool& p) : text(t), tooltip(tool), parameter(p) {}\n};\n\nclass RightClickSettings {\npublic:\n\tglm::vec2 menuPos = { 0.0f, 0.0f };\n\tglm::vec2 menuSize = { 0.0f, 0.0f };\n\n\tbool isMenuActive = false;\n\n\tvoid rightClickMenuSpawnLogic(bool& isMouseNotHoveringUI, bool& isSpawningAlone, bool& isDragging, bool& selectedColor);\n\n\tvoid rightClickMenu(UpdateVariables& myVar, UpdateParameters& myParam);\n\nprivate:\n\n\tbool isMouseOnMenu = false;\n\n\tfloat buttonSizeY = 20.0f;\n\n\tbool selectedColorChanged = false;\n\tbool selectedColorOriginal = false;\n\n\tImVec4 pCol = { 1.0f, 1.0f, 1.0f, 1.0f };\n\tImVec4 sCol = { 1.0f, 1.0f, 1.0f, 1.0f };\n\n\tColor vecToRPColor = { 255,255,255,255 };\n\tColor vecToRSColor = { 255,255,255,255 };\n\n\tbool resetParticleColors = false;\n};"
  },
  {
    "path": "GalaxyEngine/include/UX/camera.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleTrails.h\"\n\nclass SceneCamera {\npublic:\n\tCamera2D camera;\n\tglm::vec2 mouseWorldPos;\n\tglm::vec2 followPosition;\n\tbool isFollowing;\n\n\tglm::vec2 delta;\n\n\tbool centerCamera;\n\n\tbool cameraChangedThisFrame;\n\n\tSceneCamera();\n\n\tCamera2D cameraLogic(bool& isLoading, bool& isMouseNotHoveringUI);\n\n\tvoid cameraFollowObject(UpdateVariables& myVar, UpdateParameters& myParam);\n\n\tvoid hasCamMoved();\n\nprivate:\n\tColor previousColor;\n\tglm::vec2 panFollowingOffset;\n\tfloat selectionThresholdSq = 100.0f;\n\tfloat defaultCamZoom = 0.5f;\n\n\tVector2 lastTarget = camera.target;\n\tVector2 lastOffset = camera.offset;\n\tfloat lastZoom = camera.zoom;\n\tfloat lastRotation = camera.rotation;\n};\n\nclass SceneCamera3D {\npublic:\n\tCamera3D cam3D = {\n\t\t{200.0f, 200.0f, 0.0f},\n\t\t{0.0f, 0.0f, 0.0f},\n\t\t{0.0f, 1.0f, 0.0f},\n\t\t45.0f,\n\t\tCAMERA_PERSPECTIVE\n\t};\n\n\tglm::vec3 offset = { 0.0f, 0.0f, 0.0f };\n\tglm::vec3 currentSmoothedTarget = { 0.0f, 0.0f, 0.0f };\n\n\tglm::vec3 followPosition;\n\tVector3 panFollowingOffset;\n\tfloat defaultCamDist = 500.0f;\n\n\tVector3 target = { 0.0f, 0.0f, 0.0f };\n\n\tVector3 panOffsetRight = { 0.0f, 0.0f, 0.0f };\n\tVector3 panOffsetUp = { 0.0f, 0.0f, 0.0f };\n\n\tglm::vec3 camNormal = { 0.0f, 0.0f, 0.0f };\n\tglm::vec3 camRight = { 0.0f, 0.0f, 0.0f };\n\tglm::vec3 camUp = { 0.0f, 0.0f, 0.0f };\n\n\tglm::vec3 worldUp = { 0.0f, 1.0f, 0.0f };\n\tVector3 firstPersonPosition = { 0.0f, 0.0f, 0.0f };\n\n\tfloat arrowMoveSpeed = 250.0f;\n\n\tfloat distance = defaultCamDist;\n\tfloat angleX = 45.0f;\n\tfloat angleY = 30.0f;\n\n\tbool centerCamera;\n\tbool isFollowing;\n\n\tCamera3D cameraLogic(bool& isLoading, bool& isMouseNotHoveringUI, bool& firstPerson, bool& isShipEnabled);\n\n\tvoid cameraFollowObject(UpdateVariables& myVar, UpdateParameters& myParam);\n\n    glm::vec3 prevOffset = { 0.0f, 0.0f, 0.0f };\n    glm::vec3 prevCurrentSmoothedTarget = { 0.0f, 0.0f, 0.0f };\n    glm::vec3 prevFollowPosition = { 0.0f, 0.0f, 0.0f };\n    Vector3 prevPanFollowingOffset = { 0.0f, 0.0f, 0.0f };\n    float prevDefaultCamDist = 500.0f;\n    Vector3 prevTarget = { 0.0f, 0.0f, 0.0f };\n    Vector3 prevPanOffsetRight = { 0.0f, 0.0f, 0.0f };\n    Vector3 prevPanOffsetUp = { 0.0f, 0.0f, 0.0f };\n    glm::vec3 prevCamNormal = { 0.0f, 0.0f, 0.0f };\n    glm::vec3 prevCamRight = { 0.0f, 0.0f, 0.0f };\n    glm::vec3 prevCamUp = { 0.0f, 0.0f, 0.0f };\n    Vector3 prevFirstPersonPosition = { 0.0f, 0.0f, 0.0f };\n\n    bool HasCameraChanged() {\n        const float epsilon = 0.0001f;\n\n        bool changed = false;\n\n        if (glm::length(offset - prevOffset) > epsilon) changed = true;\n        if (glm::length(currentSmoothedTarget - prevCurrentSmoothedTarget) > epsilon) changed = true;\n        if (glm::length(followPosition - prevFollowPosition) > epsilon) changed = true;\n        if (glm::length(camNormal - prevCamNormal) > epsilon) changed = true;\n        if (glm::length(camRight - prevCamRight) > epsilon) changed = true;\n        if (glm::length(camUp - prevCamUp) > epsilon) changed = true;\n\n        if (fabsf(panFollowingOffset.x - prevPanFollowingOffset.x) > epsilon ||\n            fabsf(panFollowingOffset.y - prevPanFollowingOffset.y) > epsilon ||\n            fabsf(panFollowingOffset.z - prevPanFollowingOffset.z) > epsilon) changed = true;\n\n        if (fabsf(target.x - prevTarget.x) > epsilon ||\n            fabsf(target.y - prevTarget.y) > epsilon ||\n            fabsf(target.z - prevTarget.z) > epsilon) changed = true;\n\n        if (fabsf(panOffsetRight.x - prevPanOffsetRight.x) > epsilon ||\n            fabsf(panOffsetRight.y - prevPanOffsetRight.y) > epsilon ||\n            fabsf(panOffsetRight.z - prevPanOffsetRight.z) > epsilon) changed = true;\n\n        if (fabsf(panOffsetUp.x - prevPanOffsetUp.x) > epsilon ||\n            fabsf(panOffsetUp.y - prevPanOffsetUp.y) > epsilon ||\n            fabsf(panOffsetUp.z - prevPanOffsetUp.z) > epsilon) changed = true;\n\n        if (fabsf(firstPersonPosition.x - prevFirstPersonPosition.x) > epsilon ||\n            fabsf(firstPersonPosition.y - prevFirstPersonPosition.y) > epsilon ||\n            fabsf(firstPersonPosition.z - prevFirstPersonPosition.z) > epsilon) changed = true;\n\n        if (fabsf(defaultCamDist - prevDefaultCamDist) > epsilon) changed = true;\n\n        prevOffset = offset;\n        prevCurrentSmoothedTarget = currentSmoothedTarget;\n        prevFollowPosition = followPosition;\n        prevPanFollowingOffset = panFollowingOffset;\n        prevDefaultCamDist = defaultCamDist;\n        prevTarget = target;\n        prevPanOffsetRight = panOffsetRight;\n        prevPanOffsetUp = panOffsetUp;\n        prevCamNormal = camNormal;\n        prevCamRight = camRight;\n        prevCamUp = camUp;\n        prevFirstPersonPosition = firstPersonPosition;\n\n        return changed;\n    }\n};\n"
  },
  {
    "path": "GalaxyEngine/include/UX/copyPaste.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Physics/physics.h\"\n#include \"Physics/light.h\"\n\nstruct CopyPaste {\n\n\tglm::vec2 avgParticlePos = { 0.0f, 0.0f };\n\tstd::vector<ParticlePhysics> pParticleCopies;\n\tstd::vector<ParticleRendering> rParticleCopies;\n\tstd::vector<ParticleConstraint> constraintsCopied;\n\n\tvoid copyPasteParticles(UpdateVariables& myVar, UpdateParameters& myParam, Physics& physics) {\n\n\t\tif (IO::shortcutPress(KEY_H) && !myParam.pParticlesSelected.empty()) {\n\n\t\t\tpParticleCopies.clear();\n\t\t\trParticleCopies.clear();\n\t\t\tconstraintsCopied.clear();\n\n\t\t\tsize_t maxId = 0;\n\t\t\tfor (const auto& p : myParam.pParticles) {\n\t\t\t\tif (p.id > maxId) maxId = p.id;\n\t\t\t}\n\n\t\t\tif (myParam.neighborSearch.idToIndexTable.size() <= maxId) {\n\t\t\t\tmyParam.neighborSearch.idToIndexTable.resize(maxId + 1);\n\t\t\t}\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\tmyParam.neighborSearch.idToIndexTable[myParam.pParticles[i].id] = i;\n\t\t\t}\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\t\tpParticleCopies.push_back(ParticlePhysics(myParam.pParticles[i]));\n\t\t\t\t\trParticleCopies.push_back(ParticleRendering(myParam.rParticles[i]));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tavgParticlePos = { 0.0f, 0.0f };\n\t\t\tfor (const ParticlePhysics& pCopy : pParticleCopies) {\n\t\t\t\tavgParticlePos += pCopy.pos;\n\t\t\t}\n\t\t\tif (!pParticleCopies.empty()) {\n\t\t\t\tavgParticlePos /= static_cast<float>(pParticleCopies.size());\n\t\t\t}\n\n\t\t\tfor (const auto& constraint : physics.particleConstraints) {\n\n\t\t\t\tif (constraint.id1 >= myParam.neighborSearch.idToIndexTable.size() ||\n\t\t\t\t\tconstraint.id2 >= myParam.neighborSearch.idToIndexTable.size()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsize_t indexA = myParam.neighborSearch.idToIndexTable[constraint.id1];\n\t\t\t\tsize_t indexB = myParam.neighborSearch.idToIndexTable[constraint.id2];\n\n\t\t\t\tif (indexA >= myParam.pParticles.size() || myParam.pParticles[indexA].id != constraint.id1) continue;\n\t\t\t\tif (indexB >= myParam.pParticles.size() || myParam.pParticles[indexB].id != constraint.id2) continue;\n\n\t\t\t\tif (myParam.rParticles[indexA].isSelected && myParam.rParticles[indexB].isSelected) {\n\t\t\t\t\tconstraintsCopied.push_back(constraint);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tSlingshot slingshot = slingshot.particleSlingshot(myVar, myParam.myCamera);\n\n\t\tif (IO::shortcutReleased(KEY_J)) {\n\n\t\t\tstd::unordered_map<uint32_t, uint32_t> oldIdToNewIdMap;\n\n\t\t\tfor (ParticlePhysics pCopy : pParticleCopies) {\n\n\t\t\t\tglm::vec2 copyRelPos = pCopy.pos - avgParticlePos;\n\t\t\t\tpCopy.pos = myParam.myCamera.mouseWorldPos + copyRelPos;\n\n\t\t\t\tpCopy.vel += slingshot.length * slingshot.norm * 0.3f;\n\t\t\t\tpCopy.prevVel = pCopy.vel;\n\t\t\t\tpCopy.ke = 0.0f;\n\t\t\t\tpCopy.prevKe = 0.0f;\n\n\t\t\t\tuint32_t oldID = pCopy.id;\n\t\t\t\tuint32_t newID = globalId++;\n\n\t\t\t\tpCopy.id = newID;\n\t\t\t\toldIdToNewIdMap[oldID] = newID;\n\n\t\t\t\tmyParam.pParticles.push_back(ParticlePhysics(pCopy));\n\t\t\t}\n\n\t\t\tfor (ParticleRendering rCopy : rParticleCopies) {\n\t\t\t\trCopy.isSelected = false;\n\t\t\t\trCopy.isBeingDrawn = true;\n\t\t\t\tmyParam.rParticles.push_back(ParticleRendering(rCopy));\n\t\t\t}\n\n\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\tmyParam.neighborSearchV2.newGrid(myParam.pParticles);\n\t\t\t\tmyParam.neighborSearchV2.neighborAmount(myParam.pParticles, myParam.rParticles);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmyParam.neighborSearchV2AVX2.newGridAVX2(myParam.pParticles);\n\t\t\t\tmyParam.neighborSearchV2AVX2.neighborAmount(myParam.pParticles, myParam.rParticles);\n\t\t\t}\n\n\t\t\tif (myParam.neighborSearch.idToIndexTable.size() <= globalId) {\n\t\t\t\tmyParam.neighborSearch.idToIndexTable.resize(globalId + 1);\n\t\t\t}\n#pragma omp parallel for\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\tmyParam.neighborSearch.idToIndexTable[myParam.pParticles[i].id] = i;\n\t\t\t}\n\n\t\t\tbool constraintsAdded = false;\n\n\t\t\tfor (const ParticleConstraint& srcConst : constraintsCopied) {\n\n\t\t\t\tauto itA = oldIdToNewIdMap.find(srcConst.id1);\n\t\t\t\tauto itB = oldIdToNewIdMap.find(srcConst.id2);\n\n\t\t\t\tif (itA != oldIdToNewIdMap.end() && itB != oldIdToNewIdMap.end()) {\n\n\t\t\t\t\tuint32_t newIDA = itA->second;\n\t\t\t\t\tuint32_t newIDB = itB->second;\n\n\t\t\t\t\tParticleConstraint newConst = srcConst;\n\t\t\t\t\tnewConst.id1 = newIDA;\n\t\t\t\t\tnewConst.id2 = newIDB;\n\n\t\t\t\t\tphysics.particleConstraints.push_back(newConst);\n\t\t\t\t\tconstraintsAdded = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (constraintsAdded) {\n\t\t\t\tphysics.constraintMap.clear();\n\n\t\t\t\tfor (ParticleConstraint& c : physics.particleConstraints) {\n\n\t\t\t\t\tif (c.isBroken) continue;\n\n\t\t\t\t\tuint64_t key = physics.makeKey(c.id1, c.id2);\n\t\t\t\t\tphysics.constraintMap[key] = &c;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\tmyParam.rParticles[i].isBeingDrawn = false;\n\t\t\t}\n\n\t\t\tmyVar.isDragging = false;\n\t\t}\n\t}\n\n\tglm::vec3 avgParticlePos3D = { 0.0f, 0.0f, 0.0f };\n\tstd::vector<ParticlePhysics3D> pParticleCopies3D;\n\tstd::vector<ParticleRendering3D> rParticleCopies3D;\n\n\tstd::vector<ParticleConstraint> constraintsCopied3D;\n\n\tvoid copyPasteParticles3D(UpdateVariables& myVar, UpdateParameters& myParam, Physics3D& physics3D) {\n\n\t\tif (IO::shortcutPress(KEY_H) && !myParam.pParticlesSelected3D.empty()) {\n\n\t\t\tpParticleCopies3D.clear();\n\t\t\trParticleCopies3D.clear();\n\t\t\tconstraintsCopied3D.clear();\n\n\t\t\tsize_t maxId = 0;\n\t\t\tfor (const auto& p : myParam.pParticles3D) {\n\t\t\t\tif (p.id > maxId) maxId = p.id;\n\t\t\t}\n\n\t\t\tif (myParam.neighborSearch3D.idToIndexTable.size() <= maxId) {\n\t\t\t\tmyParam.neighborSearch3D.idToIndexTable.resize(maxId + 1);\n\t\t\t}\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\tmyParam.neighborSearch3D.idToIndexTable[myParam.pParticles3D[i].id] = i;\n\t\t\t}\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\t\tpParticleCopies3D.push_back(ParticlePhysics3D(myParam.pParticles3D[i]));\n\t\t\t\t\trParticleCopies3D.push_back(ParticleRendering3D(myParam.rParticles3D[i]));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tavgParticlePos3D = { 0.0f, 0.0f, 0.0f };\n\t\t\tfor (const ParticlePhysics3D& pCopy : pParticleCopies3D) {\n\t\t\t\tavgParticlePos3D += pCopy.pos;\n\t\t\t}\n\t\t\tif (!pParticleCopies3D.empty()) {\n\t\t\t\tavgParticlePos3D /= static_cast<float>(pParticleCopies3D.size());\n\t\t\t}\n\n\t\t\tfor (const auto& constraint : physics3D.particleConstraints) {\n\n\t\t\t\tif (constraint.id1 >= myParam.neighborSearch3D.idToIndexTable.size() ||\n\t\t\t\t\tconstraint.id2 >= myParam.neighborSearch3D.idToIndexTable.size()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsize_t indexA = myParam.neighborSearch3D.idToIndexTable[constraint.id1];\n\t\t\t\tsize_t indexB = myParam.neighborSearch3D.idToIndexTable[constraint.id2];\n\n\t\t\t\tif (indexA >= myParam.pParticles3D.size() || myParam.pParticles3D[indexA].id != constraint.id1) continue;\n\t\t\t\tif (indexB >= myParam.pParticles3D.size() || myParam.pParticles3D[indexB].id != constraint.id2) continue;\n\n\t\t\t\tif (myParam.rParticles3D[indexA].isSelected && myParam.rParticles3D[indexB].isSelected) {\n\t\t\t\t\tconstraintsCopied3D.push_back(constraint);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tSlingshot3D slingshot = slingshot.particleSlingshot(myVar, myParam.brush3D.brushPos);\n\n\t\tif (IO::shortcutReleased(KEY_J)) {\n\n\t\t\tstd::unordered_map<uint32_t, uint32_t> oldIdToNewIdMap;\n\n\t\t\tfor (ParticlePhysics3D pCopy : pParticleCopies3D) {\n\n\t\t\t\tglm::vec3 copyRelPos = pCopy.pos - avgParticlePos3D;\n\t\t\t\tpCopy.pos = myParam.brush3D.brushPos + copyRelPos;\n\n\t\t\t\tpCopy.vel += slingshot.length * slingshot.norm * 0.3f;\n\t\t\t\tpCopy.prevVel = pCopy.vel;\n\t\t\t\tpCopy.ke = 0.0f;\n\t\t\t\tpCopy.prevKe = 0.0f;\n\n\t\t\t\tuint32_t oldID = pCopy.id;\n\t\t\t\tuint32_t newID = globalId++;\n\n\t\t\t\tpCopy.id = newID;\n\t\t\t\toldIdToNewIdMap[oldID] = newID;\n\n\t\t\t\tmyParam.pParticles3D.push_back(ParticlePhysics3D(pCopy));\n\t\t\t}\n\n\t\t\tfor (ParticleRendering3D rCopy : rParticleCopies3D) {\n\t\t\t\trCopy.isSelected = false;\n\t\t\t\trCopy.isBeingDrawn = true;\n\t\t\t\tmyParam.rParticles3D.push_back(ParticleRendering3D(rCopy));\n\t\t\t}\n\n\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\tmyParam.neighborSearch3DV2.newGrid(myParam.pParticles3D);\n\t\t\t\tmyParam.neighborSearch3DV2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmyParam.neighborSearch3DV2AVX2.newGridAVX2(myParam.pParticles3D);\n\t\t\t\tmyParam.neighborSearch3DV2AVX2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D);\n\t\t\t}\n\n\t\t\tif (myParam.neighborSearch3D.idToIndexTable.size() <= globalId) {\n\t\t\t\tmyParam.neighborSearch3D.idToIndexTable.resize(globalId + 1);\n\t\t\t}\n#pragma omp parallel for\n\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\tmyParam.neighborSearch3D.idToIndexTable[myParam.pParticles3D[i].id] = i;\n\t\t\t}\n\n\t\t\tbool constraintsAdded = false;\n\n\t\t\tfor (const ParticleConstraint& srcConst : constraintsCopied3D) {\n\n\t\t\t\tauto itA = oldIdToNewIdMap.find(srcConst.id1);\n\t\t\t\tauto itB = oldIdToNewIdMap.find(srcConst.id2);\n\n\t\t\t\tif (itA != oldIdToNewIdMap.end() && itB != oldIdToNewIdMap.end()) {\n\n\t\t\t\t\tuint32_t newIDA = itA->second;\n\t\t\t\t\tuint32_t newIDB = itB->second;\n\n\t\t\t\t\tParticleConstraint newConst = srcConst;\n\t\t\t\t\tnewConst.id1 = newIDA;\n\t\t\t\t\tnewConst.id2 = newIDB;\n\n\t\t\t\t\tphysics3D.particleConstraints.push_back(newConst);\n\t\t\t\t\tconstraintsAdded = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (constraintsAdded) {\n\t\t\t\tphysics3D.constraintMap.clear();\n\n\t\t\t\tfor (ParticleConstraint& c : physics3D.particleConstraints) {\n\n\t\t\t\t\tif (c.isBroken) continue;\n\n\t\t\t\t\tuint64_t key = physics3D.makeKey(c.id1, c.id2);\n\t\t\t\t\tphysics3D.constraintMap[key] = &c;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\tmyParam.rParticles3D[i].isBeingDrawn = false;\n\t\t\t}\n\n\t\t\tmyVar.isDragging = false;\n\t\t}\n\t}\n\n\tglm::vec2 avgOpticsPos = { 0.0f, 0.0f };\n\tglm::vec2 avgShapeHelpersPos = { 0.0f, 0.0f };\n\tstd::vector<Wall> wallCopies;\n\tstd::vector<Shape> shapeCopies;\n\n\tstd::vector<PointLight> pointLightCopies;\n\tstd::vector<AreaLight> areaLightCopies;\n\tstd::vector<ConeLight> coneLightCopies;\n\n\tvoid copyPasteOptics(UpdateParameters& myParam, Lighting& lighting) {\n\n\t\tif (IO::shortcutPress(KEY_H) && (!lighting.walls.empty() || !lighting.pointLights.empty() || !lighting.areaLights.empty() || !lighting.coneLights.empty())) {\n\n\t\t\tpointLightCopies.clear();\n\t\t\tareaLightCopies.clear();\n\t\t\tconeLightCopies.clear();\n\n\t\t\tavgOpticsPos = { 0.0f, 0.0f };\n\n\t\t\t// Point Lights\n\t\t\tfor (size_t i = 0; i < lighting.pointLights.size(); i++) {\n\t\t\t\tif (lighting.pointLights[i].isSelected) {\n\t\t\t\t\tpointLightCopies.push_back(PointLight(lighting.pointLights[i]));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!pointLightCopies.empty()) {\n\t\t\t\tfor (const PointLight& pl : pointLightCopies) {\n\t\t\t\t\tavgOpticsPos += pl.pos;\n\t\t\t\t}\n\t\t\t\tavgOpticsPos /= static_cast<float>(pointLightCopies.size());\n\t\t\t}\n\n\t\t\t// Area Lights\n\t\t\tfor (size_t i = 0; i < lighting.areaLights.size(); i++) {\n\t\t\t\tif (lighting.areaLights[i].isSelected) {\n\t\t\t\t\tareaLightCopies.push_back(AreaLight(lighting.areaLights[i]));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!areaLightCopies.empty()) {\n\t\t\t\tfor (const AreaLight& al : areaLightCopies) {\n\t\t\t\t\tavgOpticsPos += al.vA;\n\t\t\t\t\tavgOpticsPos += al.vB;\n\t\t\t\t}\n\t\t\t\tavgOpticsPos /= static_cast<float>(areaLightCopies.size() * 2.0f);\n\t\t\t}\n\n\t\t\t// Cone Lights\n\t\t\tfor (size_t i = 0; i < lighting.coneLights.size(); i++) {\n\t\t\t\tif (lighting.coneLights[i].isSelected) {\n\t\t\t\t\tconeLightCopies.push_back(ConeLight(lighting.coneLights[i]));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!coneLightCopies.empty()) {\n\t\t\t\tfor (const ConeLight& cl : coneLightCopies) {\n\t\t\t\t\tavgOpticsPos += cl.vA;\n\t\t\t\t\tavgOpticsPos += cl.vB;\n\t\t\t\t}\n\t\t\t\tavgOpticsPos /= static_cast<float>(coneLightCopies.size() * 2.0f);\n\t\t\t}\n\n\t\t\twallCopies.clear();\n\t\t\tshapeCopies.clear();\n\n\t\t\tfor (size_t i = 0; i < lighting.walls.size(); i++) {\n\t\t\t\tif (lighting.walls[i].isSelected) {\n\t\t\t\t\twallCopies.push_back(Wall(lighting.walls[i]));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstd::unordered_set<uint32_t> copiedWallIds;\n\t\t\tfor (const Wall& wall : wallCopies)\n\t\t\t\tcopiedWallIds.insert(wall.id);\n\n\t\t\tfor (const Shape& shape : lighting.shapes) {\n\t\t\t\tbool copyThisShape = true;\n\t\t\t\tfor (uint32_t wallId : shape.myWallIds) {\n\t\t\t\t\tif (copiedWallIds.find(wallId) == copiedWallIds.end()) {\n\t\t\t\t\t\tcopyThisShape = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (copyThisShape)\n\t\t\t\t\tshapeCopies.push_back(shape);\n\t\t\t}\n\n\t\t\tif (!wallCopies.empty()) {\n\t\t\t\tfor (const Wall& wall : wallCopies) {\n\t\t\t\t\tavgOpticsPos += wall.vA;\n\t\t\t\t\tavgOpticsPos += wall.vB;\n\t\t\t\t}\n\t\t\t\tavgOpticsPos /= static_cast<float>(wallCopies.size() * 2);\n\t\t\t}\n\n\t\t\tavgShapeHelpersPos = avgOpticsPos;\n\t\t}\n\n\t\tif (IO::shortcutReleased(KEY_J)) {\n\n\t\t\tfor (PointLight plCopy : pointLightCopies) {\n\n\t\t\t\tglm::vec2 copyRelPos = plCopy.pos - avgOpticsPos;\n\n\t\t\t\tplCopy.pos = myParam.myCamera.mouseWorldPos + copyRelPos;\n\n\t\t\t\tlighting.pointLights.push_back(PointLight(plCopy));\n\t\t\t}\n\n\t\t\tfor (AreaLight alCopy : areaLightCopies) {\n\n\t\t\t\tglm::vec2 copyRelVA = alCopy.vA - avgOpticsPos;\n\t\t\t\tglm::vec2 copyRelVB = alCopy.vB - avgOpticsPos;\n\n\t\t\t\talCopy.vA = myParam.myCamera.mouseWorldPos + copyRelVA;\n\t\t\t\talCopy.vB = myParam.myCamera.mouseWorldPos + copyRelVB;\n\n\t\t\t\tlighting.areaLights.push_back(AreaLight(alCopy));\n\t\t\t}\n\n\t\t\tfor (ConeLight clCopy : coneLightCopies) {\n\n\t\t\t\tglm::vec2 copyRelVA = clCopy.vA - avgOpticsPos;\n\t\t\t\tglm::vec2 copyRelVB = clCopy.vB - avgOpticsPos;\n\n\t\t\t\tclCopy.vA = myParam.myCamera.mouseWorldPos + copyRelVA;\n\t\t\t\tclCopy.vB = myParam.myCamera.mouseWorldPos + copyRelVB;\n\n\t\t\t\tlighting.coneLights.push_back(ConeLight(clCopy));\n\t\t\t}\n\n\t\t\tfor (PointLight plCopy : pointLightCopies) {\n\n\t\t\t\tglm::vec2 copyRelPos = plCopy.pos - avgOpticsPos;\n\n\t\t\t\tplCopy.pos = myParam.myCamera.mouseWorldPos + copyRelPos;\n\n\t\t\t\tlighting.pointLights.push_back(PointLight(plCopy));\n\t\t\t}\n\n\t\t\tstd::unordered_map<uint32_t, uint32_t> oldToNewWallIds;\n\n\t\t\tstd::unordered_set<uint32_t> usedInShapes;\n\t\t\tfor (const Shape& shape : shapeCopies) {\n\t\t\t\tfor (uint32_t wallId : shape.myWallIds)\n\t\t\t\t\tusedInShapes.insert(wallId);\n\t\t\t}\n\n\t\t\tfor (const Wall& wallCopy : wallCopies) {\n\t\t\t\tWall newWall = wallCopy;\n\n\t\t\t\tglm::vec2 copyRelVA = newWall.vA - avgOpticsPos;\n\t\t\t\tglm::vec2 copyRelVB = newWall.vB - avgOpticsPos;\n\t\t\t\tnewWall.vA = myParam.myCamera.mouseWorldPos + copyRelVA;\n\t\t\t\tnewWall.vB = myParam.myCamera.mouseWorldPos + copyRelVB;\n\n\t\t\t\tuint32_t oldId = newWall.id;\n\t\t\t\tnewWall.id = globalWallId++;\n\t\t\t\toldToNewWallIds[oldId] = newWall.id;\n\n\t\t\t\tif (usedInShapes.find(oldId) == usedInShapes.end()) {\n\t\t\t\t\tnewWall.shapeId = static_cast<uint32_t>(-1);\n\t\t\t\t\tnewWall.isShapeWall = false;\n\t\t\t\t}\n\n\t\t\t\tnewWall.isSelected = false;\n\t\t\t\tlighting.walls.push_back(newWall);\n\t\t\t}\n\n\t\t\tfor (Shape& shapeCopy : shapeCopies) {\n\t\t\t\tShape newShape = shapeCopy;\n\n\t\t\t\tnewShape.id = globalShapeId++;\n\n\t\t\t\tfor (glm::vec2& helper : newShape.helpers) {\n\t\t\t\t\tglm::vec2 helperRelPos = helper - avgShapeHelpersPos;\n\t\t\t\t\thelper = myParam.myCamera.mouseWorldPos + helperRelPos;\n\t\t\t\t}\n\n\t\t\t\tnewShape.myWallIds.clear();\n\t\t\t\tfor (uint32_t oldWallId : shapeCopy.myWallIds) {\n\t\t\t\t\tauto it = oldToNewWallIds.find(oldWallId);\n\t\t\t\t\tif (it != oldToNewWallIds.end()) {\n\t\t\t\t\t\tuint32_t newWallId = it->second;\n\t\t\t\t\t\tnewShape.myWallIds.emplace_back(newWallId);\n\n\t\t\t\t\t\tWall* w = lighting.getWallById(lighting.walls, newWallId);\n\t\t\t\t\t\tif (w) {\n\t\t\t\t\t\t\tw->shapeId = newShape.id;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tnewShape.walls = &lighting.walls;\n\n\t\t\t\tif (newShape.shapeType == draw) {\n\t\t\t\t\tnewShape.helpers[0] = glm::vec2(0.0f);\n\t\t\t\t\tfor (auto& wallId : newShape.myWallIds) {\n\t\t\t\t\t\tWall* w = lighting.getWallById(lighting.walls, wallId);\n\t\t\t\t\t\tif (w) {\n\t\t\t\t\t\t\tnewShape.helpers[0] += w->vA;\n\t\t\t\t\t\t\tnewShape.helpers[0] += w->vB;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tnewShape.helpers[0] /= static_cast<float>(newShape.myWallIds.size()) * 2.0f;\n\t\t\t\t\tnewShape.oldDrawHelperPos = newShape.helpers[0];\n\t\t\t\t}\n\n\t\t\t\tif (newShape.shapeType == lens) {\n\n\t\t\t\t\tauto itC = oldToNewWallIds.find(shapeCopy.wallCId);\n\t\t\t\t\tif (itC != oldToNewWallIds.end()) {\n\t\t\t\t\t\tnewShape.wallCId = itC->second;\n\t\t\t\t\t}\n\n\t\t\t\t\tauto itB = oldToNewWallIds.find(shapeCopy.wallBId);\n\t\t\t\t\tif (itB != oldToNewWallIds.end()) {\n\t\t\t\t\t\tnewShape.wallBId = itB->second;\n\t\t\t\t\t}\n\n\t\t\t\t\tauto itA = oldToNewWallIds.find(shapeCopy.wallAId);\n\t\t\t\t\tif (itA != oldToNewWallIds.end()) {\n\t\t\t\t\t\tnewShape.wallAId = itA->second;\n\t\t\t\t\t}\n\n\t\t\t\t\tnewShape.globalLensPrev = newShape.helpers.back();\n\t\t\t\t}\n\n\t\t\t\tlighting.shapes.push_back(newShape);\n\t\t\t}\n\n\t\t\tif (!wallCopies.empty() || !pointLightCopies.empty() || !areaLightCopies.empty() || !coneLightCopies.empty()) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\t\t}\n\t}\n};"
  },
  {
    "path": "GalaxyEngine/include/UX/randNum.h",
    "content": "#pragma once\n\nfloat getRandomFloat();"
  },
  {
    "path": "GalaxyEngine/include/UX/saveSystem.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Physics/light.h\"\n#include \"Physics/field.h\"\n\n#include \"Physics/SPH.h\"\n#include \"Physics/physics.h\"\n#include \"Physics/physics3D.h\"\n#include \"UI/UI.h\"\n\n#include \"parameters.h\"\n\nstruct ParticleConstraint;\n\ninline std::ostream& operator<<(std::ostream& os, const Vector2& vec) {\n\treturn os << vec.x << \" \" << vec.y;\n}\n\ninline std::istream& operator>>(std::istream& is, Vector2& vec) {\n\treturn is >> vec.x >> vec.y;\n}\n\nclass SaveSystem {\n\npublic:\n\n\tbool saveFlag = false;\n\tbool loadFlag = false;\n\n\tconst uint32_t version160 = 160;\n\tconst uint32_t version170 = 170;\n\tconst uint32_t version171 = 171;\n\tconst uint32_t version172 = 172;\n\tconst uint32_t version173 = 173;\n\tconst uint32_t version180 = 180;\n\tconst uint32_t version190 = 190;\n\tconst uint32_t currentVersion = version190; // VERY IMPORTANT. CHANGE THIS IF YOU MAKE ANY CHANGES TO THE SAVE SYSTEM. VERSION \"1.6.0\" = 160, VERSION \"1.6.12\" = 1612\n\n\ttemplate <typename T>\n\tvoid paramIO(const std::string& filename, YAML::Emitter& out, std::string key, T& value) {\n\t\tint ucToInt = 0;\n\n\t\tif constexpr (!std::is_same_v<T, unsigned char>) {\n\t\t\tif (saveFlag) {\n\t\t\t\tout << YAML::BeginMap;\n\t\t\t\tout << YAML::Key << key;\n\t\t\t\tout << YAML::Value << value;\n\t\t\t\tstd::cout << \"Parameter: \" << key.c_str() << \" | Value: \" << value << std::endl;\n\t\t\t\tout << YAML::EndMap;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tucToInt = static_cast<int>(value);\n\t\t\tif (saveFlag) {\n\t\t\t\tout << YAML::BeginMap;\n\t\t\t\tout << YAML::Key << key;\n\t\t\t\tout << YAML::Value << ucToInt;\n\t\t\t\tstd::cout << \"Parameter: \" << key.c_str() << \" | Value: \" << ucToInt << std::endl;\n\t\t\t\tout << YAML::EndMap;\n\t\t\t}\n\t\t}\n\n\t\tif (loadFlag && !saveFlag) {\n\t\t\tstd::ifstream inFile(filename, std::ios::in | std::ios::binary);\n\t\t\tif (!inFile.is_open()) {\n\t\t\t\tstd::cerr << \"Failed to open file for reading: \" << filename << std::endl;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst std::string sepToken = \"---PARTICLE BINARY DATA---\";\n\t\t\tstd::string   line;\n\t\t\tstd::string   yamlText;\n\t\t\tbool          sawSeparator = false;\n\n\t\t\twhile (std::getline(inFile, line)) {\n\t\t\t\tif (line.find(sepToken) != std::string::npos) {\n\t\t\t\t\tsawSeparator = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tyamlText += line + \"\\n\";\n\t\t\t}\n\n\t\t\tinFile.close();\n\n\t\t\ttry {\n\t\t\t\tstd::vector<YAML::Node> documents = YAML::LoadAll(yamlText);\n\t\t\t\tbool found = false;\n\n\t\t\t\tfor (const auto& doc : documents) {\n\t\t\t\t\tif (!doc || !doc[key]) continue;\n\n\t\t\t\t\tif constexpr (!std::is_same_v<T, unsigned char>) {\n\t\t\t\t\t\tvalue = doc[key].as<T>();\n\t\t\t\t\t\tstd::cout << \"Loaded \" << key << \": \" << value << std::endl;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tint tempInt = doc[key].as<int>();\n\t\t\t\t\t\tvalue = static_cast<unsigned char>(tempInt);\n\t\t\t\t\t\tstd::cout << \"Loaded \" << key << \": \" << tempInt << std::endl;\n\t\t\t\t\t}\n\n\t\t\t\t\tfound = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif (!found) {\n\t\t\t\t\tstd::cerr << \"No \" << key << \" found in any YAML document in the file!\" << std::endl;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (const YAML::Exception& e) {\n\t\t\t\tstd::cerr << \"YAML error while loading key \\\"\" << key << \"\\\": \" << e.what() << std::endl;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid saveSystem(const std::string& filename, UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, Physics& physics, Physics3D& physics3D,\n\t\tLighting& lighting, Field& field);\n\n\tbool deserializeParticleSystem(const std::string& filename,\n\t\tstd::string& yamlString,\n\t\tUpdateVariables& myVar,\n\t\tUpdateParameters& myParam,\n\t\tSPH& sph,\n\t\tPhysics& physics,\n\t\tPhysics3D& physics3D,\n\t\tLighting& lighting,\n\t\tbool loadFlag) {\n\t\tif (!loadFlag) return false;\n\n\t\tstd::fstream file(filename, std::ios::in | std::ios::binary);\n\t\tif (!file.is_open()) {\n\t\t\tstd::cerr << \"Failed to open file for reading: \" << filename << std::endl;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst std::string sepToken = \"---PARTICLE BINARY DATA---\";\n\t\tstd::string line;\n\t\tbool foundSeparator = false;\n\t\tstd::streampos binaryStartPos = 0;\n\t\tyamlString.clear();\n\n\t\twhile (std::getline(file, line)) {\n\t\t\tif (line.find(sepToken) != std::string::npos) {\n\t\t\t\tfoundSeparator = true;\n\t\t\t\tbinaryStartPos = file.tellg();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tyamlString += line + \"\\n\";\n\t\t}\n\n\t\tif (!foundSeparator) {\n\t\t\tstd::cerr << \"Separator not found. Cannot load binary data.\\n\";\n\t\t\tfile.close();\n\t\t\treturn false;\n\t\t}\n\n\t\tfile.seekg(binaryStartPos);\n\n\t\tuint32_t loadedVersion;\n\t\tfile.read(reinterpret_cast<char*>(&loadedVersion), sizeof(loadedVersion));\n\t\tif (loadedVersion != currentVersion) {\n\t\t\tstd::cerr << \"Loaded file from older version! Expected version: \" << currentVersion << \" Loaded version: \" << loadedVersion << std::endl;\n\t\t}\n\t\telse {\n\t\t\tstd::cout << \"File version: \" << loadedVersion << std::endl;\n\t\t}\n\n\t\tif (loadedVersion == currentVersion) {\n\t\t\tdeserializeVersion190(file, myParam, physics, physics3D, lighting, myVar.is3DMode);\n\t\t}\n\t\telse if (loadedVersion == version180) {\n\t\t\tdeserializeVersion190(file, myParam, physics, physics3D, lighting, myVar.is3DMode);\n\t\t}\n\n\t\tif (myVar.isOpticsEnabled) {\n\t\t\tlighting.shouldRender = true;\n\t\t}\n\n\t\tlighting.wallPointers.clear();\n\t\tfor (Wall& wall : lighting.walls) {\n\t\t\tlighting.wallPointers.push_back(&wall);\n\t\t}\n\t\tlighting.bvh.build(lighting.wallPointers);\n\t\t\n\t\tmyVar.longExposureFlag = false;\n\n\t\tmyVar.loadDropDownMenus = true;\n\n\t\tfile.close();\n\n\t\tuint32_t maxId = 0;\n\t\tfor (const auto& particle : myParam.pParticles) {\n\t\t\tif (particle.id > maxId) maxId = particle.id;\n\t\t}\n\t\tglobalId = maxId + 1;\n\n\t\treturn true;\n\t}\n\n\tbool deserializeVersion190(std::istream& file, UpdateParameters& myParam, Physics& physics, Physics3D& physics3D, Lighting& lighting, bool& is3DMode) {\n\n\t\tfile.read(reinterpret_cast<char*>(&globalId), sizeof(globalId));\n\t\tfile.read(reinterpret_cast<char*>(&globalShapeId), sizeof(globalShapeId));\n\t\tfile.read(reinterpret_cast<char*>(&globalWallId), sizeof(globalWallId));\n\n\t\tmyParam.pParticles.clear();\n\t\tmyParam.rParticles.clear();\n\n\t\tmyParam.pParticles3D.clear();\n\t\tmyParam.rParticles3D.clear();\n\n\t\tmyParam.pParticlesSelected.clear();\n\t\tmyParam.rParticlesSelected.clear();\n\n\t\tmyParam.pParticlesSelected3D.clear();\n\t\tmyParam.rParticlesSelected3D.clear();\n\n\t\tif (!is3DMode) {\n\t\t\tuint32_t particleCount;\n\t\t\tfile.read(reinterpret_cast<char*>(&particleCount), sizeof(particleCount));\n\n\t\t\tmyParam.pParticles.reserve(particleCount);\n\t\t\tmyParam.rParticles.reserve(particleCount);\n\n\t\t\tfor (uint32_t i = 0; i < particleCount; i++) {\n\t\t\t\tParticlePhysics p;\n\t\t\t\tParticleRendering r;\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.pos), sizeof(p.pos));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.predPos), sizeof(p.predPos));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.vel), sizeof(p.vel));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.prevVel), sizeof(p.prevVel));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.predVel), sizeof(p.predVel));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.acc), sizeof(p.acc));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.mass), sizeof(p.mass));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.press), sizeof(p.press));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.pressTmp), sizeof(p.pressTmp));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.pressF), sizeof(p.pressF));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.dens), sizeof(p.dens));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.predDens), sizeof(p.predDens));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.sphMass), sizeof(p.sphMass));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.restDens), sizeof(p.restDens));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.stiff), sizeof(p.stiff));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.visc), sizeof(p.visc));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.cohesion), sizeof(p.cohesion));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.temp), sizeof(p.temp));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.ke), sizeof(p.ke));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.prevKe), sizeof(p.prevKe));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.mortonKey), sizeof(p.mortonKey));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.id), sizeof(p.id));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.isHotPoint), sizeof(p.isHotPoint));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.hasSolidified), sizeof(p.hasSolidified));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.color), sizeof(r.color));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.pColor), sizeof(r.pColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.sColor), sizeof(r.sColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.sphColor), sizeof(r.sphColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.size), sizeof(r.size));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.uniqueColor), sizeof(r.uniqueColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isSolid), sizeof(r.isSolid));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.canBeSubdivided), sizeof(r.canBeSubdivided));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.canBeResized), sizeof(r.canBeResized));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isDarkMatter), sizeof(r.isDarkMatter));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isSPH), sizeof(r.isSPH));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isSelected), sizeof(r.isSelected));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isGrabbed), sizeof(r.isGrabbed));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.previousSize), sizeof(r.previousSize));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.neighbors), sizeof(r.neighbors));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.totalRadius), sizeof(r.totalRadius));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.lifeSpan), sizeof(r.lifeSpan));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.sphLabel), sizeof(r.sphLabel));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isPinned), sizeof(r.isPinned));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isBeingDrawn), sizeof(r.isBeingDrawn));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.spawnCorrectIter), sizeof(r.spawnCorrectIter));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.turbulence), sizeof(r.turbulence));\n\n\t\t\t\tmyParam.pParticles.push_back(p);\n\t\t\t\tmyParam.rParticles.push_back(r);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tuint32_t particleCount;\n\t\t\tfile.read(reinterpret_cast<char*>(&particleCount), sizeof(particleCount));\n\n\t\t\tmyParam.pParticles3D.reserve(particleCount);\n\t\t\tmyParam.rParticles3D.reserve(particleCount);\n\n\t\t\tfor (uint32_t i = 0; i < particleCount; i++) {\n\t\t\t\tParticlePhysics3D p;\n\t\t\t\tParticleRendering3D r;\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.pos), sizeof(p.pos));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.predPos), sizeof(p.predPos));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.vel), sizeof(p.vel));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.prevVel), sizeof(p.prevVel));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.predVel), sizeof(p.predVel));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.acc), sizeof(p.acc));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.mass), sizeof(p.mass));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.press), sizeof(p.press));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.pressTmp), sizeof(p.pressTmp));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.pressF), sizeof(p.pressF));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.dens), sizeof(p.dens));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.predDens), sizeof(p.predDens));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.sphMass), sizeof(p.sphMass));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.restDens), sizeof(p.restDens));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.stiff), sizeof(p.stiff));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.visc), sizeof(p.visc));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.cohesion), sizeof(p.cohesion));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.temp), sizeof(p.temp));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.ke), sizeof(p.ke));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.prevKe), sizeof(p.prevKe));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.mortonKey), sizeof(p.mortonKey));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.id), sizeof(p.id));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.isHotPoint), sizeof(p.isHotPoint));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&p.hasSolidified), sizeof(p.hasSolidified));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.color), sizeof(r.color));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.pColor), sizeof(r.pColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.sColor), sizeof(r.sColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.sphColor), sizeof(r.sphColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.size), sizeof(r.size));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.uniqueColor), sizeof(r.uniqueColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isSolid), sizeof(r.isSolid));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.canBeSubdivided), sizeof(r.canBeSubdivided));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.canBeResized), sizeof(r.canBeResized));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isDarkMatter), sizeof(r.isDarkMatter));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isSPH), sizeof(r.isSPH));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isSelected), sizeof(r.isSelected));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isGrabbed), sizeof(r.isGrabbed));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.previousSize), sizeof(r.previousSize));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.neighbors), sizeof(r.neighbors));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.totalRadius), sizeof(r.totalRadius));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.lifeSpan), sizeof(r.lifeSpan));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.sphLabel), sizeof(r.sphLabel));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isPinned), sizeof(r.isPinned));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.isBeingDrawn), sizeof(r.isBeingDrawn));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.spawnCorrectIter), sizeof(r.spawnCorrectIter));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&r.turbulence), sizeof(r.turbulence));\n\n\t\t\t\tmyParam.pParticles3D.push_back(p);\n\t\t\t\tmyParam.rParticles3D.push_back(r);\n\t\t\t}\n\t\t}\n\n\t\tif (!is3DMode) {\n\n\t\t\tphysics.particleConstraints.clear();\n\n\t\t\tuint32_t numConstraints = 0;\n\t\t\tfile.read(reinterpret_cast<char*>(&numConstraints), sizeof(numConstraints));\n\t\t\tif (numConstraints > 0) {\n\n\t\t\t\tphysics.particleConstraints.resize(numConstraints);\n\t\t\t\tfile.read(\n\t\t\t\t\treinterpret_cast<char*>(physics.particleConstraints.data()),\n\t\t\t\t\tnumConstraints * sizeof(ParticleConstraint)\n\t\t\t\t);\n\n\t\t\t\tphysics.constraintMap.clear();\n\t\t\t\tfor (auto& constraint : physics.particleConstraints) {\n\t\t\t\t\tuint64_t key = physics.makeKey(constraint.id1, constraint.id2);\n\t\t\t\t\tphysics.constraintMap[key] = &constraint;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\n\t\t\tphysics3D.particleConstraints.clear();\n\n\t\t\tuint32_t numConstraints = 0;\n\t\t\tfile.read(reinterpret_cast<char*>(&numConstraints), sizeof(numConstraints));\n\t\t\tif (numConstraints > 0) {\n\n\t\t\t\tphysics3D.particleConstraints.resize(numConstraints);\n\t\t\t\tfile.read(\n\t\t\t\t\treinterpret_cast<char*>(physics3D.particleConstraints.data()),\n\t\t\t\t\tnumConstraints * sizeof(ParticleConstraint)\n\t\t\t\t);\n\n\t\t\t\tphysics3D.constraintMap.clear();\n\t\t\t\tfor (auto& constraint : physics3D.particleConstraints) {\n\t\t\t\t\tuint64_t key = physics3D.makeKey(constraint.id1, constraint.id2);\n\t\t\t\t\tphysics3D.constraintMap[key] = &constraint;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tuint32_t wallCount = 0;\n\t\tfile.read(reinterpret_cast<char*>(&wallCount), sizeof(wallCount));\n\t\tlighting.walls.clear();\n\t\tlighting.walls.reserve(wallCount);\n\n\t\tfor (uint32_t i = 0; i < wallCount; i++) {\n\t\t\tWall w;\n\n\t\t\tfile.read(reinterpret_cast<char*>(&w.vA), sizeof(w.vA));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.vB), sizeof(w.vB));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.normal), sizeof(w.normal));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.normalVA), sizeof(w.normalVA));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.normalVB), sizeof(w.normalVB));\n\n\t\t\tfile.read(reinterpret_cast<char*>(&w.isBeingSpawned), sizeof(w.isBeingSpawned));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.vAisBeingMoved), sizeof(w.vAisBeingMoved));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.vBisBeingMoved), sizeof(w.vBisBeingMoved));\n\n\t\t\tfile.read(reinterpret_cast<char*>(&w.apparentColor), sizeof(w.apparentColor));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.baseColor), sizeof(w.baseColor));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.specularColor), sizeof(w.specularColor));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.refractionColor), sizeof(w.refractionColor));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.emissionColor), sizeof(w.emissionColor));\n\n\t\t\tfile.read(reinterpret_cast<char*>(&w.baseColorVal), sizeof(w.baseColorVal));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.specularColorVal), sizeof(w.specularColorVal));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.refractionColorVal), sizeof(w.refractionColorVal));\n\n\t\t\tfile.read(reinterpret_cast<char*>(&w.specularRoughness), sizeof(w.specularRoughness));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.refractionRoughness), sizeof(w.refractionRoughness));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.refractionAmount), sizeof(w.refractionAmount));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.IOR), sizeof(w.IOR));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.dispersionStrength), sizeof(w.dispersionStrength));\n\n\t\t\tfile.read(reinterpret_cast<char*>(&w.isShapeWall), sizeof(w.isShapeWall));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.isShapeClosed), sizeof(w.isShapeClosed));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.shapeId), sizeof(w.shapeId));\n\n\t\t\tfile.read(reinterpret_cast<char*>(&w.id), sizeof(w.id));\n\t\t\tfile.read(reinterpret_cast<char*>(&w.isSelected), sizeof(w.isSelected));\n\n\t\t\tlighting.walls.push_back(w);\n\t\t}\n\n\t\tuint32_t numShapes = 0;\n\t\tfile.read(reinterpret_cast<char*>(&numShapes), sizeof(numShapes));\n\n\t\tlighting.shapes.clear();\n\t\tlighting.shapes.reserve(numShapes);\n\n\t\tif (numShapes > 0) {\n\n\t\t\tfor (uint32_t i = 0; i < numShapes; i++) {\n\t\t\t\tShape s;\n\n\t\t\t\tuint32_t wallIdCount = 0;\n\t\t\t\tfile.read(reinterpret_cast<char*>(&wallIdCount), sizeof(wallIdCount));\n\t\t\t\ts.myWallIds.resize(wallIdCount);\n\t\t\t\tfile.read(reinterpret_cast<char*>(s.myWallIds.data()), wallIdCount * sizeof(uint32_t));\n\n\t\t\t\tuint32_t vertCount = 0;\n\t\t\t\tfile.read(reinterpret_cast<char*>(&vertCount), sizeof(vertCount));\n\t\t\t\ts.polygonVerts.resize(vertCount);\n\t\t\t\tfile.read(reinterpret_cast<char*>(s.polygonVerts.data()), vertCount * sizeof(glm::vec2));\n\n\t\t\t\tuint32_t helpersCount = 0;\n\t\t\t\tfile.read(reinterpret_cast<char*>(&helpersCount), sizeof(helpersCount));\n\t\t\t\ts.helpers.resize(helpersCount);\n\t\t\t\tfile.read(reinterpret_cast<char*>(s.helpers.data()), helpersCount * sizeof(glm::vec2));\n\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.baseColor), sizeof(s.baseColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.specularColor), sizeof(s.specularColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.refractionColor), sizeof(s.refractionColor));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.emissionColor), sizeof(s.emissionColor));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.specularRoughness), sizeof(s.specularRoughness));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.refractionRoughness), sizeof(s.refractionRoughness));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.refractionAmount), sizeof(s.refractionAmount));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.IOR), sizeof(s.IOR));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.dispersionStrength), sizeof(s.dispersionStrength));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.id), sizeof(s.id));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.h1), sizeof(s.h1));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.h2), sizeof(s.h2));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.isBeingSpawned), sizeof(s.isBeingSpawned));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.isBeingMoved), sizeof(s.isBeingMoved));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.isShapeClosed), sizeof(s.isShapeClosed));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.shapeType), sizeof(s.shapeType));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.drawHoverHelpers), sizeof(s.drawHoverHelpers));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.oldDrawHelperPos), sizeof(s.oldDrawHelperPos));\n\n\t\t\t\t// Lens variables\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.secondHelper), sizeof(s.secondHelper));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.thirdHelper), sizeof(s.thirdHelper));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.fourthHelper), sizeof(s.fourthHelper));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.Tempsh2Length), sizeof(s.Tempsh2Length));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.Tempsh2LengthSymmetry), sizeof(s.Tempsh2LengthSymmetry));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.tempDist), sizeof(s.tempDist));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.moveH2), sizeof(s.moveH2));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.isThirdBeingMoved), sizeof(s.isThirdBeingMoved));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.isFourthBeingMoved), sizeof(s.isFourthBeingMoved));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.isFifthBeingMoved), sizeof(s.isFifthBeingMoved));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.isFifthFourthMoved), sizeof(s.isFifthFourthMoved));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.symmetricalLens), sizeof(s.symmetricalLens));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.wallAId), sizeof(s.wallAId));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.wallBId), sizeof(s.wallBId));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.wallCId), sizeof(s.wallCId));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.lensSegments), sizeof(s.lensSegments));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.startAngle), sizeof(s.startAngle));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.endAngle), sizeof(s.endAngle));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.startAngleSymmetry), sizeof(s.startAngleSymmetry));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.endAngleSymmetry), sizeof(s.endAngleSymmetry));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.center), sizeof(s.center));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.radius), sizeof(s.radius));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.centerSymmetry), sizeof(s.centerSymmetry));\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.radiusSymmetry), sizeof(s.radiusSymmetry));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.arcEnd), sizeof(s.arcEnd));\n\n\t\t\t\tfile.read(reinterpret_cast<char*>(&s.globalLensPrev), sizeof(s.globalLensPrev));\n\n\t\t\t\ts.walls = &lighting.walls;\n\n\t\t\t\tlighting.shapes.push_back(s);\n\t\t\t}\n\t\t}\n\n\t\tuint32_t pointLightCount = 0;\n\t\tfile.read(reinterpret_cast<char*>(&pointLightCount), sizeof(pointLightCount));\n\n\t\tlighting.pointLights.clear();\n\t\tlighting.pointLights.reserve(pointLightCount);\n\n\t\tfor (uint32_t i = 0; i < pointLightCount; i++) {\n\n\t\t\tPointLight p = lighting.pointLights[i];\n\n\t\t\tfile.read(reinterpret_cast<char*>(&p.pos), sizeof(p.pos));\n\t\t\tfile.read(reinterpret_cast<char*>(&p.isBeingMoved), sizeof(p.isBeingMoved));\n\t\t\tfile.read(reinterpret_cast<char*>(&p.color), sizeof(p.color));\n\t\t\tfile.read(reinterpret_cast<char*>(&p.apparentColor), sizeof(p.apparentColor));\n\t\t\tfile.read(reinterpret_cast<char*>(&p.isSelected), sizeof(p.isSelected));\n\n\t\t\tlighting.pointLights.push_back(p);\n\t\t}\n\n\t\tuint32_t areaLightCount = 0;\n\t\tfile.read(reinterpret_cast<char*>(&areaLightCount), sizeof(areaLightCount));\n\n\t\tlighting.areaLights.clear();\n\t\tlighting.areaLights.reserve(areaLightCount);\n\n\t\tfor (uint32_t i = 0; i < areaLightCount; i++) {\n\n\t\t\tAreaLight a = lighting.areaLights[i];\n\n\t\t\tfile.read(reinterpret_cast<char*>(&a.vA), sizeof(a.vA));\n\t\t\tfile.read(reinterpret_cast<char*>(&a.vB), sizeof(a.vB));\n\t\t\tfile.read(reinterpret_cast<char*>(&a.isBeingSpawned), sizeof(a.isBeingSpawned));\n\t\t\tfile.read(reinterpret_cast<char*>(&a.vAisBeingMoved), sizeof(a.vAisBeingMoved));\n\t\t\tfile.read(reinterpret_cast<char*>(&a.vBisBeingMoved), sizeof(a.vBisBeingMoved));\n\t\t\tfile.read(reinterpret_cast<char*>(&a.color), sizeof(a.color));\n\t\t\tfile.read(reinterpret_cast<char*>(&a.apparentColor), sizeof(a.apparentColor));\n\t\t\tfile.read(reinterpret_cast<char*>(&a.isSelected), sizeof(a.isSelected));\n\t\t\tfile.read(reinterpret_cast<char*>(&a.spread), sizeof(a.spread));\n\n\t\t\tlighting.areaLights.push_back(a);\n\t\t}\n\n\t\tuint32_t coneLightCount = 0;\n\t\tfile.read(reinterpret_cast<char*>(&coneLightCount), sizeof(coneLightCount));\n\n\t\tlighting.coneLights.clear();\n\t\tlighting.coneLights.reserve(coneLightCount);\n\n\t\tfor (uint32_t i = 0; i < coneLightCount; i++) {\n\n\t\t\tConeLight l = lighting.coneLights[i];\n\n\t\t\tfile.read(reinterpret_cast<char*>(&l.vA), sizeof(l.vA));\n\t\t\tfile.read(reinterpret_cast<char*>(&l.vB), sizeof(l.vB));\n\t\t\tfile.read(reinterpret_cast<char*>(&l.isBeingSpawned), sizeof(l.isBeingSpawned));\n\t\t\tfile.read(reinterpret_cast<char*>(&l.vAisBeingMoved), sizeof(l.vAisBeingMoved));\n\t\t\tfile.read(reinterpret_cast<char*>(&l.vBisBeingMoved), sizeof(l.vBisBeingMoved));\n\t\t\tfile.read(reinterpret_cast<char*>(&l.color), sizeof(l.color));\n\t\t\tfile.read(reinterpret_cast<char*>(&l.apparentColor), sizeof(l.apparentColor));\n\t\t\tfile.read(reinterpret_cast<char*>(&l.isSelected), sizeof(l.isSelected));\n\t\t\tfile.read(reinterpret_cast<char*>(&l.spread), sizeof(l.spread));\n\n\t\t\tlighting.coneLights.push_back(l);\n\t\t}\n\n\t\treturn true;\n\t}\n\n\n\tvoid saveLoadLogic(UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, Physics& physics, Physics3D& physics3D, Lighting& lighting, Field& field);\n\nprivate:\n\n\tImVec2 loadMenuSize = { 600.0f, 500.0f };\n\tfloat buttonHeight = 30.0f;\n\n\tstd::vector<std::string> filePaths;\n\n\tint saveIndex = 0;\n};"
  },
  {
    "path": "GalaxyEngine/include/UX/screenCapture.h",
    "content": "#pragma once\n\nstruct AVFormatContext;\nstruct AVCodecContext;\nstruct AVStream;\nstruct SwsContext;\nstruct AVFrame;\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\nclass ScreenCapture {\npublic:\n    bool exportMemoryFrames = false;\n    bool deleteFrames = false;\n    bool isExportingFrames = false;\n    bool isFunctionRecording = false;\n    bool isVideoExportEnabled = true;\n\n    bool isSafeFramesEnabled = true;\n    bool isExportFramesEnabled = false;\n\n    bool showSaveConfirmationDialog = false;\n    std::string lastVideoPath;\n\n    bool videoHasBeenSaved = false;\n    std::string actualSavedVideoFolder;\n    std::string actualSavedVideoName;\n\n    bool cancelRecording = false;\n\n    bool screenGrab(RenderTexture2D &myParticlesTexture, UpdateVariables &myVar,\n                    UpdateParameters &myParam);\n\nprivate:\n    std::string generateVideoFilename();\n    void cleanupFFmpeg();\n    void exportFrameToFile(const Image &frame, const std::string &videoFolder,\n                           const std::string &videoName, int frameNumber);    void exportMemoryFramesToDisk();\n    void discardMemoryFrames();\n    void createFramesFolder(const std::string &folderPath);\n    void discardRecording();\n\n    int screenshotIndex = 0; // Used for screenshot naming\n    std::vector<Image> myFrames; // Used for storing captured frames\n    int diskModeFrameIdx = 0; // Used for tracking frames in disk mode\n    std::string folderName; // Used for storing the folder name\n    std::string outFileName; // Used for storing the output file name\n    std::string videoFolder; // Used for storing the video folder path\n\n    AVFormatContext *pFormatCtx = nullptr;\n    AVCodecContext *pCodecCtx = nullptr;\n    AVStream *pStream = nullptr;\n    SwsContext *swsCtx = nullptr;\n    AVFrame *frame = nullptr;\n    int frameIndex = 0;\n};\n"
  },
  {
    "path": "GalaxyEngine/include/globalLogic.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleTrails.h\"\n#include \"Particles/particleSelection.h\"\n#include \"Particles/particleSubdivision.h\"\n#include \"Particles/densitySize.h\"\n#include \"Particles/particleColorVisuals.h\"\n#include \"Particles/particleDeletion.h\"\n#include \"Particles/particlesSpawning.h\"\n#include \"Particles/particleSpaceship.h\"\n\n#include \"Physics/quadtree.h\"\n#include \"Physics/slingshot.h\"\n#include \"Physics/morton.h\"\n#include \"Physics/physics.h\"\n#include \"Physics/physics3D.h\"\n#include \"Physics/SPH.h\"\n#include \"Physics/SPH3D.h\"\n#include \"Physics/light.h\"\n#include \"Physics/field.h\"\n\n#include \"UI/brush.h\"\n#include \"UI/rightClickSettings.h\"\n#include \"UI/controls.h\"\n#include \"UI/UI.h\"\n\n#include \"Sound/sound.h\"\n\n#include \"UX/screenCapture.h\"\n#include \"UX/camera.h\"\n#include \"UX/saveSystem.h\"\n#include \"UX/randNum.h\"\n#include \"UX/copyPaste.h\"\n\n#include \"Renderer/rayMarching.h\"\n\n#include \"parameters.h\"\n\nextern UpdateParameters myParam;\nextern UpdateVariables myVar;\nextern UI myUI;\nextern Physics physics;\nextern Physics3D physics3D;\nextern ParticleSpaceship ship;\nextern SPH sph;\nextern SPH3D sph3D;\nextern SaveSystem save;\nextern GESound geSound;\nextern Lighting lighting;\nextern CopyPaste copyPaste;\n\nextern RayMarcher rayMarcher;\n\nextern Field field;\n\nstruct ParticleBounds {\n\tfloat minX, maxX, minY, maxY;\n};\n\n// THIS FUNCTION IS MEANT FOR QUICK DEBUGGING WHERE YOU NEED TO CHECK A SPECIFIC PARTICLE'S VARIABLES\nvoid selectedParticleDebug();\n\nvoid pinParticles();\nvoid pinParticles3D();\n\nvoid buildKernels();\n\nvoid freeGPUMemory();\n\nvoid updateScene();\n\nvoid mode3D();\n\nvoid playBackLogic(Texture2D& particleBlurTex);\n\nvoid drawMode3DRecording(Texture2D& particleBlurTex);\nvoid drawMode3DNonRecording();\n\nvoid drawScene(Texture2D& particleBlurTex, RenderTexture2D& myRayTracingTexture,\n\tRenderTexture2D& myUITexture, RenderTexture2D& myMiscTexture, bool& fadeActive, bool& introActive);\n\nvoid enableMultiThreading();\n\nvoid fullscreenToggle(int& lastScreenWidth, int& lastScreenHeight,\n\tbool& wasFullscreen, bool& lastScreenState,\n\tRenderTexture2D& myParticlesTexture, RenderTexture2D& myUITexture);\n\nvoid drawConstraints();\n\nvoid drawConstraints3D();\n\nvoid saveConfigIfChanged();\n\nvoid saveConfig();\n\nvoid loadConfig();\n\nRenderTexture2D CreateFloatRenderTexture(int w, int h);\n\nbool hasAVX2Support();"
  },
  {
    "path": "GalaxyEngine/include/parameters.h",
    "content": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleSubdivision.h\"\n#include \"Particles/densitySize.h\"\n#include \"Particles/particleColorVisuals.h\"\n#include \"Particles/particleTrails.h\"\n#include \"Particles/particleSelection.h\"\n#include \"Particles/particlesSpawning.h\"\n#include \"Particles/neighborSearch.h\"\n#include \"Particles/clusterMouseHelper.h\"\n\n#include \"Physics/morton.h\"\n\n#include \"UI/brush.h\"\n#include \"UI/rightClickSettings.h\"\n#include \"UI/controls.h\"\n\n#include \"UX/screenCapture.h\"\n\nstruct PlaybackParticle {\n\tglm::vec3 pos;\n\tfloat previousSize;\n\tfloat size;\n\tuint32_t id;\n\tColor color;\n};\n\nstruct UpdateParameters {\n\tstd::vector<ParticlePhysics> pParticles;\n\tstd::vector<ParticleRendering> rParticles;\n\n\tstd::vector<ParticlePhysics> pParticlesSelected;\n\tstd::vector<ParticleRendering> rParticlesSelected;\n\n\tstd::vector<ParticlePhysics3D> pParticles3D;\n\tstd::vector<ParticleRendering3D> rParticles3D;\n\n\tstd::vector<ParticlePhysics3D> pParticlesSelected3D;\n\tstd::vector<ParticleRendering3D> rParticlesSelected3D;\n\n\tstd::vector<ParticlePhysics3D> pParticles3DPlaybackResume;\n\tstd::vector<ParticleRendering3D> rParticles3DPlaybackResume;\n\n\tstd::vector<std::vector<PlaybackParticle>> playbackFrames;\n\n\tstd::vector<ParticleTrails> trailDots;\n\n\tScreenCapture screenCapture;\n\n\tMorton morton;\n\n\tParticleTrails trails;\n\n\tParticleSelection particleSelection;\n\tParticleSelection3D particleSelection3D;\n\n\tSceneCamera myCamera;\n\tSceneCamera3D myCamera3D;\n\n\tBrush brush;\n\tBrush3D brush3D;\n\n\tParticleSubdivision subdivision;\n\n\tDensitySize densitySize;\n\n\tColorVisuals colorVisuals;\n\n\tRightClickSettings rightClickSettings;\n\n\tControls controls;\n\tstd::unordered_map<uint32_t, size_t> pParticleLookup;\n\tParticleDeletion particleDeletion;\n\n\tParticlesSpawning particlesSpawning;\n\tParticlesSpawning3D particlesSpawning3D;\n\n\tNeighborSearch neighborSearch;\n\tNeighborSearch3D neighborSearch3D;\n\n\tNeighborSearchV2 neighborSearchV2;\n\tNeighborSearchV2AVX2 neighborSearchV2AVX2;\n\tNeighborSearch3DV2 neighborSearch3DV2;\n\tNeighborSearch3DV2AVX2 neighborSearch3DV2AVX2;\n};\n\nstruct UpdateVariables {\n\tint screenWidth = 1920;\n\tint screenHeight = 1080;\n\tfloat halfScreenWidth = screenWidth * 0.5f;\n\tfloat halfScreenHeight = screenHeight * 0.5f;\n\n\tfloat screenRatioX = 0.0f;\n\tfloat screenRatioY = 0.0f;\n\n\tglm::vec2 domainSize = { 3840.0f, 2160.0f };\n\n\tglm::vec3 domainSize3D = { 1300.0f,  1300.0f,  1300.0f };\n\n\tfloat halfDomainWidth = domainSize.x * 0.5f;\n\tfloat halfDomainHeight = domainSize.y * 0.5f;\n\n\tfloat halfDomain3DWidth = domainSize3D.x * 0.5f;\n\tfloat halfDomain3DHeight = domainSize3D.y * 0.5f;\n\tfloat halfDomain3DDepth = domainSize3D.z * 0.5f;\n\n\tbool fullscreenState = true;\n\n\tbool exitGame = false;\n\n\tint targetFPS = 1000;\n\n\tfloat G = 6.674e-11;\n\tfloat gravityMultiplier = 1.0f;\n\tfloat softening = 1.5f;\n\tfloat theta = 0.8f;\n\tfloat timeStepMultiplier = 1.0f;\n\tfloat sphMaxVel = 250.0f;\n\tfloat globalHeatConductivity = 0.045f;\n\tfloat globalAmbientHeatRate = 1.0f;\n\tfloat ambientTemp = 274.0f;\n\n\tstatic float particleBaseMass;\n\n\tint maxLeafParticles = 1;\n\tfloat minLeafSize = 1.0f;\n\n\tconst float fixedDeltaTime = 0.045f;\n\n\tbool isTimePlaying = true;\n\n\tfloat timeFactor = 1.0f;\n\n\tbool isGlobalTrailsEnabled = false;\n\tbool isSelectedTrailsEnabled = false;\n\tbool isLocalTrailsEnabled = false;\n\tbool isPeriodicBoundaryEnabled = true;\n\tbool isMultiThreadingEnabled = true;\n\tbool isBarnesHutEnabled = true;\n\tbool isDarkMatterEnabled = true;\n\tbool isDensitySizeEnabled = false;\n\tbool isForceSizeEnabled = false;\n\tbool isShipGasEnabled = true;\n\tbool isSPHEnabled = false;\n\tbool sphGround = false;\n\tbool isTempEnabled = false;\n\tbool constraintsEnabled = false;\n\tbool isOpticsEnabled = false;\n\n\tbool isGPUEnabled = false;\n\n\tbool isMergerEnabled = false;\n\n\tbool longExposureFlag = false;\n\tint longExposureDuration = 200;\n\tint longExposureCurrent = 0;\n\n\tbool isSpawningAllowed = true;\n\n\tfloat particleTextureHalfSize = 16.0f;\n\n\tint trailMaxLength = 350;\n\n\tstatic ImVec4 colWindowBg;\n\n\t//ImGui style colors\n\tstatic ImVec4 colButton;\n\tstatic ImVec4 colButtonHover;\n\tstatic ImVec4 colButtonPress;\n\n\tstatic ImVec4 colButtonActive;\n\tstatic ImVec4 colButtonActiveHover;\n\tstatic ImVec4 colButtonActivePress;\n\n\tstatic ImVec4 colButtonRedActive;\n\tstatic ImVec4 colButtonRedActiveHover;\n\tstatic ImVec4 colButtonRedActivePress;\n\n\t// ImGui slider colors\n\tstatic ImVec4 colSliderGrab;\n\tstatic ImVec4 colSliderGrabActive;\n\tstatic ImVec4 colSliderBg;\n\tstatic ImVec4 colSliderBgHover;\n\tstatic ImVec4 colSliderBgActive;\n\n\t// ImPlot style colors\n\tstatic ImVec4 colPlotLine;\n\tstatic ImVec4 colAxisText;\n\tstatic ImVec4 colAxisGrid;\n\tstatic ImVec4 colAxisBg;\n\tstatic ImVec4 colFrameBg;\n\tstatic ImVec4 colPlotBg;\n\tstatic ImVec4 colPlotBorder;\n\tstatic ImVec4 colLegendBg;\n\n\t// Text colors\n\tstatic ImVec4 colMenuInformation;\n\n\tbool isRecording = false;\n\n\tfloat particleSizeMultiplier = 0.6f;\n\n\tbool isDragging = false;\n\tbool isMouseNotHoveringUI = false;\n\n\tbool drawQuadtree = false;\n\tbool drawZCurves = false;\n\n\tbool isGlowEnabled = false;\n\n\tglm::vec2 mouseWorldPos = { 0.0f, 0.0f };\n\n\tint threadsAmount = 1;\n\tImFont* robotoMediumFont = nullptr;\n\n\tbool pauseAfterRecording = false;\n\tbool cleanSceneAfterRecording = false;\n\tfloat recordingTimeLimit = 0.0f;\n\n\tfloat globalConstraintStiffnessMult = 1.0f;\n\tfloat globalConstraintResistance = 1.0f;\n\n\tbool constraintAllSolids = false;\n\tbool constraintSelected = false;\n\tbool deleteAllConstraints = false;\n\tbool deleteSelectedConstraints = false;\n\tbool drawConstraints = false;\n\tbool visualizeMesh = false;\n\tbool unbreakableConstraints = false;\n\tbool constraintStressColor = false;\n\n\tbool constraintAfterDrawingFlag = false;\n\tbool constraintAfterDrawing = false;\n\n\tfloat constraintMaxStressColor = 0.0f;\n\n\tbool pinFlag = false;\n\tbool unPinFlag = false;\n\n\tbool isBrushDrawing = false;\n\n\tFont customFont = { 0 };\n\tint introFontSize = 48;\n\n\tbool gridExists = true;\n\tbool grid3DExists = true;\n\n\tbool loadDropDownMenus = false;\n\n\tbool exportPlyFlag = false;\n\tbool exportPlySeqFlag = false;\n\n\tint plyFrameNumber = 0;\n\n\tbool toolSpawnHeavyParticle = false;\n\tbool toolDrawParticles = true;\n\tbool toolSpawnGalaxy = false;\n\tbool toolSpawnStar = false;\n\tbool toolSpawnBigBang = false;\n\n\tbool toolErase = false;\n\tbool toolRadialForce = false;\n\tbool toolSpin = false;\n\tbool toolMove = false;\n\tbool toolRaiseTemp = false;\n\tbool toolLowerTemp = false;\n\n\tbool toolPointLight = false;\n\tbool toolAreaLight = false;\n\tbool toolConeLight = false;\n\tbool toolCircle = false;\n\tbool toolDrawShape = false;\n\tbool toolLens = false;\n\tbool toolWall = false;\n\tbool toolMoveOptics = false;\n\tbool toolEraseOptics = false;\n\tbool toolSelectOptics = false;\n\n\tbool isGravityFieldEnabled = false;\n\tbool gravityFieldDMParticles = false;\n\n\tint frameCount = 0;\n\n\tbool naive = false;\n\n\tbool is3DMode = true;\n\n\tbool hasAVX2 = false;\n\n\tfloat heavyParticleWeightMultiplier = 1.0f;\n\tint predictPathLength = 1000;\n\n\tfloat particleAmountMultiplier = 1.0;\n\tfloat DMAmountMultiplier = 1.0f;\n\n\tfloat massScatter = 0.75f;\n\n\tbool enablePathPrediction = false;\n\n\tbool SPHWater = false;\n\tbool SPHRock = false;\n\tbool SPHIron = false;\n\tbool SPHSand = false;\n\tbool SPHSoil = false;\n\tbool SPHIce = false;\n\tbool SPHMud = false;\n\tbool SPHRubber = false;\n\tbool SPHGas = false;\n\n\tfloat mass = 0.03f;\n\tfloat stiffMultiplier = 1.0f;\n\tfloat viscosity = 0.3f;\n\tfloat cohesionCoefficient = 1.0f;\n\tfloat delta = 19000.0f;\n\tfloat verticalGravity = 3.0f;\n\n\tbool infiniteDomain = true;\n\n\tfloat brushSpinForceMult = 1.0f;\n\tfloat brushAttractForceMult = 1.0f;\n\n\tbool clipSelectedX = false;\n\tbool clipSelectedY = false;\n\tbool clipSelectedZ = false;\n\n\tbool clipSelectedXInv = false;\n\tbool clipSelectedYInv = false;\n\tbool clipSelectedZInv = false;\n\n\n\tbool playbackRecord = false;\n\tint frames = 100;\n\tint currentFrame = 0;\n\n\tint keyframeTickInterval = 5;\n\n\tfloat playbackProgress = 0.0f;\n\tfloat playbackSpeed = 0.2f;\n\n\tbool runPlayback = false;\n\n\tbool deletePlayback = false;\n\n\tbool isPlaybackOn = false;\n\n\tfloat playbackParticlesSizeMult = 1.0f;\n\n\tbool playBackOnMemory = false;\n\n\tbool firstPerson = false;\n\n\tstd::string playbackPath = \"playbackTemp/playback.bin\";\n\n\tbool lowResRayMarching = false;\n\n\tbool isRayMarcherOn = false;\n\n\tbool flatParticleTexture3D = true;\n\n\tfloat boundaryFriction = 0.0f;\n\n\tint glowSize = 12;\n\tfloat glowStrength = 0.7f;\n};"
  },
  {
    "path": "GalaxyEngine/include/pch.h",
    "content": "#pragma once\n\n// C++ stdlib\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n#include <string>\n#include <fstream>\n#include <sstream>\n#include <iostream>\n#include <istream>\n#include <ostream>\n#include <filesystem>\n#include <array>\n#include <algorithm>\n#include <memory>\n#include <limits> \n#include <chrono>\n#include <regex>\n#include <variant>\n#include <thread>\n#include <bitset>\n#include <random>\n#include <stack>\n#include <execution>\n\n// C stdlib\n#include <cmath>\n#include <cstdint>\n#include <cstdio>\n#include <cstddef>\n\n// Runtime\n#include <omp.h>\n\n// Vendor\n#include <glm.hpp>\n#include <gtc/matrix_transform.hpp>\n\n#include <raylib.h>\n#include <raymath.h>\n#include <rlgl.h>\n#include <external/glad.h>\n\n#include <imgui.h>\n\n#include <implot.h>\n#include <implot_internal.h>\n\n#include <rlImGui.h>\n#include <rlImGuiColors.h>\n\n#include <yaml-cpp/yaml.h>"
  },
  {
    "path": "GalaxyEngine/src/Particles/particleSelection.cpp",
    "content": "#include \"Particles/particleSelection.h\"\n\n#include \"parameters.h\"\n\nParticleSelection::ParticleSelection() {\n}\n\nvoid ParticleSelection::clusterSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger) {\n\tstatic bool isMouseMoving = false;\n\tstatic glm::vec2 dragStartPos = { 0.0f, 0.0f };\n\n\tif ((IsMouseButtonPressed(0) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tdragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tisMouseMoving = false;\n\t}\n\n\tif ((IsMouseButtonDown(0) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tglm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tfloat dragThreshold = 5.0f;\n\t\tfloat dx = currentPos.x - dragStartPos.x;\n\t\tfloat dy = currentPos.y - dragStartPos.y;\n\n\t\tif (dx * dx + dy * dy > dragThreshold * dragThreshold) {\n\t\t\tisMouseMoving = true;\n\t\t}\n\t}\n\n\tif ((IsMouseButtonReleased(0) && IsKeyDown(KEY_LEFT_CONTROL) && !isMouseMoving && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tfloat distanceThreshold = 10.0f;\n\t\tstd::vector<int> neighborCountsSelect(myParam.pParticles.size(), 0);\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tconst auto& pParticle = myParam.pParticles[i];\n\n\t\t\tif (myParam.rParticles[i].isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (size_t j = i + 1; j < myParam.pParticles.size(); j++) {\n\n\t\t\t\tif (myParam.rParticles[j].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (std::abs(myParam.pParticles[j].pos.x - pParticle.pos.x) > 2.4f) break;\n\t\t\t\tfloat dx = pParticle.pos.x - myParam.pParticles[j].pos.x;\n\t\t\t\tfloat dy = pParticle.pos.y - myParam.pParticles[j].pos.y;\n\t\t\t\tif (dx * dx + dy * dy < distanceThreshold * distanceThreshold) {\n\t\t\t\t\tneighborCountsSelect[i]++;\n\t\t\t\t\tneighborCountsSelect[j]++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!IsKeyDown(KEY_LEFT_SHIFT)) {\n\t\t\tif (!myVar.isGlobalTrailsEnabled) {\n\t\t\t\tmyParam.trails.segments.clear();\n\t\t\t}\n\t\t}\n\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (!IsKeyDown(KEY_LEFT_SHIFT)) {\n\t\t\t\tmyParam.rParticles[i].isSelected = false;\n\t\t\t}\n\t\t\tfloat dx = myParam.pParticles[i].pos.x - myParam.myCamera.mouseWorldPos.x;\n\t\t\tfloat dy = myParam.pParticles[i].pos.y - myParam.myCamera.mouseWorldPos.y;\n\t\t\tfloat distanceSq = dx * dx + dy * dy;\n\t\t\tif (distanceSq < selectionThresholdSq && neighborCountsSelect[i] > 3 && !myParam.rParticles[i].isDarkMatter) {\n\t\t\t\tmyParam.rParticles[i].isSelected = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!IsKeyDown(KEY_LEFT_SHIFT) && !myParam.pParticles.empty()) {\n\t\t\t\t\tmyParam.rParticles[i].isSelected = false;\n\t\t\t\t\tif (!myVar.isGlobalTrailsEnabled) {\n\t\t\t\t\t\tmyParam.trails.segments.clear();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid ParticleSelection::particleSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger) {\n\tstatic bool isMouseMoving = false;\n\tstatic glm::vec2 dragStartPos = { 0.0f, 0.0f };\n\n\tif ((IsMouseButtonPressed(0) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tdragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tisMouseMoving = false;\n\t}\n\n\tif ((IsMouseButtonDown(0) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tglm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tfloat dragThreshold = 5.0f;\n\t\tfloat dx = currentPos.x - dragStartPos.x;\n\t\tfloat dy = currentPos.y - dragStartPos.y;\n\n\t\tif (dx * dx + dy * dy > dragThreshold * dragThreshold) {\n\t\t\tisMouseMoving = true;\n\t\t}\n\t}\n\n\tif ((IsMouseButtonReleased(0) && IsKeyDown(KEY_LEFT_ALT) && !isMouseMoving && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tsize_t closestIndex = 0;\n\t\tfloat minDistanceSq = std::numeric_limits<float>::max();\n\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\n\t\t\tif (myParam.rParticles[i].isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat dx = myParam.pParticles[i].pos.x - myParam.myCamera.mouseWorldPos.x;\n\t\t\tfloat dy = myParam.pParticles[i].pos.y - myParam.myCamera.mouseWorldPos.y;\n\t\t\tfloat currentDistanceSq = dx * dx + dy * dy;\n\t\t\tif (currentDistanceSq < minDistanceSq) {\n\t\t\t\tminDistanceSq = currentDistanceSq;\n\t\t\t\tclosestIndex = i;\n\t\t\t}\n\t\t}\n\n\t\tif (!IsKeyDown(KEY_LEFT_SHIFT)) {\n\t\t\tbool wasClosestSelected = (minDistanceSq < selectionThresholdSq && !myParam.pParticles.empty()) ?\n\t\t\t\tmyParam.rParticles[closestIndex].isSelected : false;\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\tmyParam.rParticles[i].isSelected = false;\n\t\t\t}\n\n\t\t\tif (!myVar.isGlobalTrailsEnabled) {\n\t\t\t\tmyParam.trails.segments.clear();\n\t\t\t}\n\n\t\t\tif (minDistanceSq < selectionThresholdSq && !myParam.pParticles.empty() && !wasClosestSelected && !myParam.rParticles[closestIndex].isDarkMatter) {\n\t\t\t\tmyParam.rParticles[closestIndex].isSelected = true;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (minDistanceSq < selectionThresholdSq && !myParam.pParticles.empty()) {\n\t\t\t\tmyParam.rParticles[closestIndex].isSelected = !myParam.rParticles[closestIndex].isSelected;\n\t\t\t}\n\t\t}\n\t}\n}\n\n\nvoid ParticleSelection::manyClustersSelection(UpdateVariables& myVar, UpdateParameters& myParam) {\n\tif (selectManyClusters) {\n\t\tif (!myVar.isGlobalTrailsEnabled) {\n\t\t\tmyParam.trails.segments.clear();\n\t\t}\n\t\tfloat distanceThreshold = 10.0f;\n\t\tstd::vector<int> neighborCountsSelect(myParam.pParticles.size(), 0);\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tmyParam.rParticles[i].isSelected = false;\n\n\t\t\tif (myParam.rParticles[i].isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst auto& pParticle = myParam.pParticles[i];\n\t\t\tfor (size_t j = i + 1; j < myParam.pParticles.size(); j++) {\n\n\t\t\t\tif (myParam.rParticles[j].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (std::abs(myParam.pParticles[j].pos.x - pParticle.pos.x) > 2.4f) break;\n\t\t\t\tfloat dx = pParticle.pos.x - myParam.pParticles[j].pos.x;\n\t\t\t\tfloat dy = pParticle.pos.y - myParam.pParticles[j].pos.y;\n\t\t\t\tif (dx * dx + dy * dy < distanceThreshold * distanceThreshold) {\n\t\t\t\t\tneighborCountsSelect[i]++;\n\t\t\t\t\tneighborCountsSelect[j]++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (neighborCountsSelect[i] > 3 && !myParam.rParticles[i].isDarkMatter) {\n\t\t\t\tmyParam.rParticles[i].isSelected = true;\n\t\t\t}\n\t\t}\n\t\tselectManyClusters = false;\n\t}\n}\n\nvoid ParticleSelection::boxSelection(UpdateParameters& myParam, bool& is3DMode) {\n\n\tif ((IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(2)) || (IsKeyDown(KEY_LEFT_ALT) && IsMouseButtonDown(2))) {\n\t\tif (IO::shortcutPress(KEY_LEFT_CONTROL) || IsMouseButtonPressed(2)) {\n\t\t\tboxInitialPos = { myParam.myCamera.mouseWorldPos.x, myParam.myCamera.mouseWorldPos.y };\n\t\t\tisBoxSelecting = true;\n\t\t}\n\n\t\tglm::vec2 currentMousePos = { myParam.myCamera.mouseWorldPos.x, myParam.myCamera.mouseWorldPos.y };\n\t\tfloat boxX = fmin(boxInitialPos.x, currentMousePos.x);\n\t\tfloat boxY = fmin(boxInitialPos.y, currentMousePos.y);\n\t\tfloat boxWidth = fabs(currentMousePos.x - boxInitialPos.x);\n\t\tfloat boxHeight = fabs(currentMousePos.y - boxInitialPos.y);\n\t\tDrawRectangleV({ boxX, boxY }, { boxWidth, boxHeight }, { 40,40,40,80 });\n\t\tif (!is3DMode) {\n\t\t\tDrawRectangleLinesEx({ boxX, boxY , boxWidth, boxHeight }, 0.6f, WHITE);\n\t\t}\n\n\t}\n\n\tif (IsKeyDown(KEY_LEFT_ALT) && isBoxSelecting) {\n\t\tisBoxDeselecting = true;\n\t}\n\n\tif ((IsKeyReleased(KEY_LEFT_CONTROL) || IsMouseButtonReleased(2)) && isBoxSelecting) {\n\t\tglm::vec2 mousePos = { myParam.myCamera.mouseWorldPos.x, myParam.myCamera.mouseWorldPos.y };\n\t\tfloat boxX1 = fmin(boxInitialPos.x, mousePos.x);\n\t\tfloat boxX2 = fmax(boxInitialPos.x, mousePos.x);\n\t\tfloat boxY1 = fmin(boxInitialPos.y, mousePos.y);\n\t\tfloat boxY2 = fmax(boxInitialPos.y, mousePos.y);\n\n\t\tif (!IsKeyDown(KEY_LEFT_SHIFT) && !isBoxDeselecting) {\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\tmyParam.rParticles[i].isSelected = false;\n\t\t\t}\n\t\t}\n\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\n\t\t\tif (myParam.pParticles[i].pos.x >= boxX1 && myParam.pParticles[i].pos.x <= boxX2 &&\n\t\t\t\tmyParam.pParticles[i].pos.y >= boxY1 && myParam.pParticles[i].pos.y <= boxY2) {\n\t\t\t\tif (!isBoxDeselecting) {\n\t\t\t\t\tmyParam.rParticles[i].isSelected = true;\n\n\t\t\t\t}\n\t\t\t\telse if (myParam.rParticles[i].isSelected && isBoxDeselecting) {\n\t\t\t\t\tmyParam.rParticles[i].isSelected = false;\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\tisBoxSelecting = false;\n\t\tisBoxDeselecting = false;\n\t}\n}\n\nvoid ParticleSelection::invertSelection(std::vector<ParticleRendering>& rParticles) {\n\tif (IO::shortcutPress(KEY_I)) {\n\t\tinvertParticleSelection = true;\n\t}\n\n\tif (invertParticleSelection) {\n\t\tfor (auto& rParticle : rParticles) {\n\n\t\t\trParticle.isSelected = !rParticle.isSelected;\n\t\t}\n\n\t\tinvertParticleSelection = false;\n\t}\n}\n\nvoid ParticleSelection::deselection(std::vector<ParticleRendering>& rParticles) {\n\n\tif (deselectParticles || IO::shortcutPress(KEY_D)) {\n\t\tfor (auto& rParticle : rParticles) {\n\t\t\trParticle.isSelected = false;\n\t\t}\n\t\tdeselectParticles = false;\n\t}\n}\n\nvoid ParticleSelection::selectedParticlesStoring(UpdateParameters& myParam) {\n\tmyParam.rParticlesSelected.clear();\n\tmyParam.pParticlesSelected.clear();\n\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\tmyParam.rParticlesSelected.push_back(myParam.rParticles[i]);\n\t\t\tmyParam.pParticlesSelected.push_back(myParam.pParticles[i]);\n\t\t}\n\t}\n}\n\n// ---- 3D IMPLEMENTATION ---- //\n\nParticleSelection3D::ParticleSelection3D() {\n}\n\nvoid ParticleSelection3D::clusterSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger) {\n\tstatic bool isMouseMoving = false;\n\tstatic glm::vec2 dragStartPos = { 0.0f, 0.0f };\n\n\tif ((IsMouseButtonPressed(0) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tdragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tisMouseMoving = false;\n\t}\n\t\n\tif ((IsMouseButtonDown(0) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tglm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tfloat dragThreshold = 5.0f;\n\t\tfloat dx = currentPos.x - dragStartPos.x;\n\t\tfloat dy = currentPos.y - dragStartPos.y;\n\n\t\tif (dx * dx + dy * dy > dragThreshold * dragThreshold) {\n\t\t\tisMouseMoving = true;\n\t\t}\n\t}\n\n\tif ((IsMouseButtonReleased(0) && IsKeyDown(KEY_LEFT_CONTROL) && !isMouseMoving && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tfloat distanceThreshold = 100.0f;\n\n\t\tRay ray = GetScreenToWorldRay(GetMousePosition(), myParam.myCamera3D.cam3D);\n\t\tglm::vec3 rayPos{ ray.position.x, ray.position.y, ray.position.z };\n\t\tglm::vec3 rayDir{ ray.direction.x, ray.direction.y, ray.direction.z };\n\n\t\tstd::vector<int> neighborCountsSelect(myParam.pParticles3D.size(), 0);\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tconst auto& pParticle = myParam.pParticles3D[i];\n\n\t\t\tif (myParam.rParticles3D[i].isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (size_t j = i + 1; j < myParam.pParticles3D.size(); j++) {\n\n\t\t\t\tif (myParam.rParticles3D[j].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (std::abs(myParam.pParticles3D[j].pos.x - pParticle.pos.x) > 3.4f) break;\n\n\t\t\t\tglm::vec3 diff = myParam.pParticles3D[j].pos - pParticle.pos;\n\t\t\t\tfloat dSq = glm::dot(diff, diff);\n\n\t\t\t\tif (dSq < distanceThreshold * distanceThreshold) {\n\t\t\t\t\tneighborCountsSelect[i]++;\n\t\t\t\t\tneighborCountsSelect[j]++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!IsKeyDown(KEY_LEFT_SHIFT)) {\n\t\t\tif (!myVar.isGlobalTrailsEnabled) {\n\t\t\t\tmyParam.trails.segments.clear();\n\t\t\t}\n\t\t}\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (!IsKeyDown(KEY_LEFT_SHIFT)) {\n\t\t\t\tmyParam.rParticles3D[i].isSelected = false;\n\t\t\t}\n\n\t\t\tglm::vec3 particleCursorRayDir = glm::normalize(rayPos - myParam.pParticles3D[i].pos);\n\n\t\t\tfloat alignment = glm::dot(rayDir, particleCursorRayDir);\n\n\t\t\tif (alignment < selectionThresholdAngle && neighborCountsSelect[i] > 4 && !myParam.rParticles3D[i].isDarkMatter) {\n\t\t\t\tmyParam.rParticles3D[i].isSelected = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!IsKeyDown(KEY_LEFT_SHIFT) && !myParam.pParticles3D.empty()) {\n\t\t\t\t\tmyParam.rParticles3D[i].isSelected = false;\n\t\t\t\t\tif (!myVar.isGlobalTrailsEnabled) {\n\t\t\t\t\t\tmyParam.trails.segments.clear();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid ParticleSelection3D::particleSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger) {\n\tstatic bool isMouseMoving = false;\n\tstatic glm::vec2 dragStartPos = { 0.0f, 0.0f };\n\n\tif ((IsMouseButtonPressed(0) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tdragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tisMouseMoving = false;\n\t}\n\n\tif ((IsMouseButtonDown(0) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tglm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tfloat dragThreshold = 5.0f;\n\t\tfloat dx = currentPos.x - dragStartPos.x;\n\t\tfloat dy = currentPos.y - dragStartPos.y;\n\n\t\tif (dx * dx + dy * dy > dragThreshold * dragThreshold) {\n\t\t\tisMouseMoving = true;\n\t\t}\n\t}\n\n\tif ((IsMouseButtonReleased(0) && IsKeyDown(KEY_LEFT_ALT) && !isMouseMoving && myVar.isMouseNotHoveringUI) || externalTrigger) {\n\t\tsize_t closestIndex = 0;\n\t\tfloat minAngle = std::numeric_limits<float>::max();\n\n\t\tRay ray = GetScreenToWorldRay(GetMousePosition(), myParam.myCamera3D.cam3D);\n\t\tglm::vec3 rayPos{ ray.position.x, ray.position.y, ray.position.z };\n\t\tglm::vec3 rayDir{ ray.direction.x, ray.direction.y, ray.direction.z };\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\n\t\t\tif (myParam.rParticles3D[i].isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tglm::vec3 particleCursorRayDir = glm::normalize(rayPos - myParam.pParticles3D[i].pos);\n\n\t\t\tfloat alignment = glm::dot(rayDir, particleCursorRayDir);\n\n\t\t\tif (alignment < minAngle) {\n\t\t\t\tminAngle = alignment;\n\t\t\t\tclosestIndex = i;\n\t\t\t}\n\t\t}\n\n\t\tif (!IsKeyDown(KEY_LEFT_SHIFT)) {\n\t\t\tbool wasClosestSelected = (minAngle < selectionThresholdAngle && !myParam.pParticles3D.empty()) ?\n\t\t\t\tmyParam.rParticles3D[closestIndex].isSelected : false;\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\tmyParam.rParticles3D[i].isSelected = false;\n\t\t\t}\n\n\t\t\tif (!myVar.isGlobalTrailsEnabled) {\n\t\t\t\tmyParam.trails.segments.clear();\n\t\t\t}\n\n\t\t\tif (minAngle < selectionThresholdAngle && !myParam.pParticles3D.empty() && !wasClosestSelected && !myParam.rParticles3D[closestIndex].isDarkMatter) {\n\t\t\t\tmyParam.rParticles3D[closestIndex].isSelected = true;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (minAngle < selectionThresholdAngle && !myParam.pParticles3D.empty()) {\n\t\t\t\tmyParam.rParticles3D[closestIndex].isSelected = !myParam.rParticles3D[closestIndex].isSelected;\n\t\t\t}\n\t\t}\n\t}\n}\n\n\nvoid ParticleSelection3D::manyClustersSelection(UpdateVariables& myVar, UpdateParameters& myParam) {\n\tif (selectManyClusters3D) {\n\t\tif (!myVar.isGlobalTrailsEnabled) {\n\t\t\tmyParam.trails.segments.clear();\n\t\t}\n\t\tfloat distanceThreshold = 100.0f;\n\t\tstd::vector<int> neighborCountsSelect(myParam.pParticles3D.size(), 0);\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tmyParam.rParticles3D[i].isSelected = false;\n\n\t\t\tif (myParam.rParticles3D[i].isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst auto& pParticle = myParam.pParticles3D[i];\n\t\t\tfor (size_t j = i + 1; j < myParam.pParticles3D.size(); j++) {\n\n\t\t\t\tif (myParam.rParticles3D[j].isDarkMatter) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (std::abs(myParam.pParticles3D[j].pos.x - pParticle.pos.x) > 3.4f) break;\n\n\t\t\t\tglm::vec3 diff = myParam.pParticles3D[j].pos - pParticle.pos;\n\t\t\t\tfloat dSq = glm::dot(diff, diff);\n\n\t\t\t\tif (dSq < distanceThreshold * distanceThreshold) {\n\t\t\t\t\tneighborCountsSelect[i]++;\n\t\t\t\t\tneighborCountsSelect[j]++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (neighborCountsSelect[i] > 4 && !myParam.rParticles3D[i].isDarkMatter) {\n\t\t\t\tmyParam.rParticles3D[i].isSelected = true;\n\t\t\t}\n\t\t}\n\t\tselectManyClusters3D = false;\n\t}\n}\n\nvoid ParticleSelection3D::boxSelection(UpdateParameters& myParam) {\n\n\tif ((IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(2)) || (IsKeyDown(KEY_LEFT_ALT) && IsMouseButtonDown(2))) {\n\t\tif (IO::shortcutPress(KEY_LEFT_CONTROL) || IsMouseButtonPressed(2)) {\n\t\t\tboxInitialPos = { GetMousePosition().x, GetMousePosition().y };\n\t\t\tisBoxSelecting = true;\n\t\t}\n\n\t\tglm::vec2 currentMousePos = {GetMousePosition().x, GetMousePosition().y};\n\t\tfloat boxX = fmin(boxInitialPos.x, currentMousePos.x);\n\t\tfloat boxY = fmin(boxInitialPos.y, currentMousePos.y);\n\t\tfloat boxWidth = fabs(currentMousePos.x - boxInitialPos.x);\n\t\tfloat boxHeight = fabs(currentMousePos.y - boxInitialPos.y);\n\n\t}\n\n\tif (IsKeyDown(KEY_LEFT_ALT) && isBoxSelecting) {\n\t\tisBoxDeselecting = true;\n\t}\n\n\tif ((IsKeyReleased(KEY_LEFT_CONTROL) || IsMouseButtonReleased(2)) && isBoxSelecting) {\n\t\tglm::vec2 mousePos = { GetMousePosition().x, GetMousePosition().y };\n\t\tfloat boxX1 = fmin(boxInitialPos.x, mousePos.x);\n\t\tfloat boxX2 = fmax(boxInitialPos.x, mousePos.x);\n\t\tfloat boxY1 = fmin(boxInitialPos.y, mousePos.y);\n\t\tfloat boxY2 = fmax(boxInitialPos.y, mousePos.y);\n\n\t\tif (!IsKeyDown(KEY_LEFT_SHIFT) && !isBoxDeselecting) {\n\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\tmyParam.rParticles3D[i].isSelected = false;\n\t\t\t}\n\t\t}\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\n\t\t\tParticlePhysics3D& p = myParam.pParticles3D[i];\n\n\t\t\tVector2 p3dTo2d = GetWorldToScreen({ p.pos.x, p.pos.y, p.pos.z }, myParam.myCamera3D.cam3D);\n\n\t\t\tif (p3dTo2d.x >= boxX1 && p3dTo2d.x <= boxX2 &&\n\t\t\t\tp3dTo2d.y >= boxY1 && p3dTo2d.y <= boxY2) {\n\t\t\t\tif (!isBoxDeselecting) {\n\t\t\t\t\tmyParam.rParticles3D[i].isSelected = true;\n\n\t\t\t\t}\n\t\t\t\telse if (myParam.rParticles3D[i].isSelected && isBoxDeselecting) {\n\t\t\t\t\tmyParam.rParticles3D[i].isSelected = false;\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\tisBoxSelecting = false;\n\t\tisBoxDeselecting = false;\n\t}\n}\n\nvoid ParticleSelection3D::invertSelection(std::vector<ParticleRendering3D>& rParticles) {\n\tif (IO::shortcutPress(KEY_I)) {\n\t\tinvertParticleSelection = true;\n\t}\n\n\tif (invertParticleSelection) {\n\t\tfor (auto& rParticle : rParticles) {\n\n\t\t\trParticle.isSelected = !rParticle.isSelected;\n\t\t}\n\n\t\tinvertParticleSelection = false;\n\t}\n}\n\nvoid ParticleSelection3D::deselection(std::vector<ParticleRendering3D>& rParticles) {\n\n\tif (deselectParticles || IO::shortcutPress(KEY_D)) {\n\t\tfor (auto& rParticle : rParticles) {\n\t\t\trParticle.isSelected = false;\n\t\t}\n\t\tdeselectParticles = false;\n\t}\n}\n\nvoid ParticleSelection3D::selectedParticlesStoring(UpdateParameters& myParam) {\n\tmyParam.rParticlesSelected3D.clear();\n\tmyParam.pParticlesSelected3D.clear();\n\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\tmyParam.rParticlesSelected3D.push_back(myParam.rParticles3D[i]);\n\t\t\tmyParam.pParticlesSelected3D.push_back(myParam.pParticles3D[i]);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "GalaxyEngine/src/Particles/particleSubdivision.cpp",
    "content": "#include \"Particles/particleSubdivision.h\"\n\n#include \"parameters.h\"\n\nvoid ParticleSubdivision::subdivideParticles(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (subdivideAll || subdivideSelected) {\n\n\t\tif (subdivideSelected) {\n\t\t\tsubdivideAll = false;\n\t\t}\n\t\tif (myParam.pParticles.size() >= particlesThreshold) {\n\n\t\t\tfloat screenW = static_cast<float>(GetScreenWidth());\n\t\t\tfloat screenH = static_cast<float>(GetScreenHeight());\n\n\t\t\tImVec2 subdivisionMenuSize = { 550.0f, 150.0f };\n\n\t\t\tImGui::SetNextWindowSize(subdivisionMenuSize, ImGuiCond_Once);\n\t\t\tImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - subdivisionMenuSize.x * 0.5f, screenH * 0.5f - subdivisionMenuSize.y * 0.5f), ImGuiCond_Appearing);\n\n\t\t\tImGui::Begin(\"##SubdivisionWarning\", nullptr, ImGuiWindowFlags_NoCollapse);\n\n\t\t\tImGui::PushFont(myVar.robotoMediumFont);\n\n\t\t\tstd::string warning = \"SUBDIVIDING FURTHER MIGHT HEAVILY SLOW DOWN PERFORMANCE\";\n\n\t\t\tfloat windowWidth = ImGui::GetWindowSize().x;\n\t\t\tfloat textWidth = ImGui::CalcTextSize(warning.c_str()).x;\n\n\t\t\tImGui::SetWindowFontScale(1.2f);\n\n\t\t\tImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);\n\t\t\tImGui::TextColored(ImVec4(0.9f, 0.0f, 0.0f, 1.0f), \"%s\", warning.c_str());\n\n\t\t\tif (ImGui::Button(\"Confirm\", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) {\n\t\t\t\tconfirmState = !confirmState;\n\t\t\t}\n\n\t\t\tImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive);\n\t\t\tImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover);\n\t\t\tImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress);\n\t\t\tif (ImGui::Button(\"Quit\", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) {\n\t\t\t\tquitState = !quitState;\n\t\t\t}\n\t\t\tImGui::PopStyleColor(3);\n\n\t\t\tImGui::PopFont();\n\n\t\t\tImGui::End();\n\t\t}\n\n\t\tif (quitState) {\n\t\t\tsubdivideAll = false;\n\t\t}\n\n\t\tif (myParam.pParticles.size() < particlesThreshold || confirmState) {\n\t\t\tint originalSize = static_cast<int>(myParam.pParticles.size());\n\t\t\tfor (int i = originalSize - 1; i >= 0; i--) {\n\t\t\t\tif ((subdivideAll || myParam.rParticles[i].isSelected) && myParam.rParticles[i].canBeSubdivided) {\n\n\t\t\t\t\tfloat halfOffset = myParam.rParticles[i].previousSize / 2.0f * myVar.particleTextureHalfSize * 0.25f;\n\t\t\t\t\tfloat halfOffsetVisual = myParam.rParticles[i].previousSize / 2.0f;\n\n\t\t\t\t\tint multipliers[4][2] = { {-1, -1}, { 1, -1}, {-1, 1}, { 1, 1} };\n\n\t\t\t\t\tsize_t firstNewParticleIndex = myParam.pParticles.size();\n\n\t\t\t\t\tfor (int j = 0; j < 4; j++) {\n\t\t\t\t\t\tfloat offsetX = multipliers[j][0] * halfOffset + (rand() % 3 - 1);\n\t\t\t\t\t\tfloat offsetY = multipliers[j][1] * halfOffset + (rand() % 3 - 1);\n\n\t\t\t\t\t\tglm::vec2 newPos{\n\t\t\t\t\t\t\tmyParam.pParticles[i].pos.x + offsetX,\n\t\t\t\t\t\t\tmyParam.pParticles[i].pos.y + offsetY\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tmyParam.pParticles.emplace_back(\n\t\t\t\t\t\t\tnewPos,\n\t\t\t\t\t\t\tmyParam.pParticles[i].vel,\n\t\t\t\t\t\t\tmyParam.pParticles[i].mass / 4.0f,\n\t\t\t\t\t\t\tmyParam.pParticles[i].restDens,\n\t\t\t\t\t\t\tmyParam.pParticles[i].stiff,\n\t\t\t\t\t\t\tmyParam.pParticles[i].visc,\n\t\t\t\t\t\t\tmyParam.pParticles[i].cohesion\n\t\t\t\t\t\t);\n\n\n\t\t\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\t\t\tmyParam.rParticles[i].color,\n\t\t\t\t\t\t\thalfOffsetVisual,\n\t\t\t\t\t\t\tmyParam.rParticles[i].uniqueColor,\n\t\t\t\t\t\t\tmyParam.rParticles[i].isSelected,\n\t\t\t\t\t\t\tmyParam.rParticles[i].isSolid,\n\t\t\t\t\t\t\tmyParam.rParticles[i].canBeSubdivided,\n\t\t\t\t\t\t\tmyParam.rParticles[i].canBeResized,\n\t\t\t\t\t\t\tmyParam.rParticles[i].isDarkMatter,\n\t\t\t\t\t\t\tmyParam.rParticles[i].isSPH,\n\t\t\t\t\t\t\tmyParam.rParticles[i].lifeSpan,\n\t\t\t\t\t\t\tmyParam.rParticles[i].sphLabel\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (int j = 0; j < 4; ++j) {\n\t\t\t\t\t\tmyParam.pParticles[firstNewParticleIndex + j].id = globalId++;\n\t\t\t\t\t\tmyParam.pParticles[firstNewParticleIndex + j].temp = myParam.pParticles[i].temp;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (int j = 0; j < 4; ++j) {\n\t\t\t\t\t\tmyParam.rParticles[firstNewParticleIndex + j].pColor = myParam.rParticles[i].pColor;\n\t\t\t\t\t\tmyParam.rParticles[firstNewParticleIndex + j].sColor = myParam.rParticles[i].sColor;\n\t\t\t\t\t\tmyParam.rParticles[firstNewParticleIndex + j].sphColor = myParam.rParticles[i].sphColor;\n\t\t\t\t\t}\n\n\t\t\t\t\tmyParam.pParticles[i] = std::move(myParam.pParticles.back());\n\t\t\t\t\tmyParam.pParticles.pop_back();\n\t\t\t\t\tmyParam.rParticles[i] = std::move(myParam.rParticles.back());\n\t\t\t\t\tmyParam.rParticles.pop_back();\n\t\t\t\t}\n\t\t\t}\n\t\t\tsubdivideAll = false;\n\t\t\tsubdivideSelected = false;\n\t\t}\n\t\tconfirmState = false;\n\t\tquitState = false;\n\t}\n}\n\nvoid ParticleSubdivision::subdivideParticles3D(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n    if (subdivideAll || subdivideSelected) {\n\n        if (subdivideSelected) {\n            subdivideAll = false;\n        }\n\n        if (myParam.pParticles3D.size() >= particlesThreshold) {\n\n            float screenW = static_cast<float>(GetScreenWidth());\n            float screenH = static_cast<float>(GetScreenHeight());\n\n            ImVec2 subdivisionMenuSize = { 550.0f, 150.0f };\n\n            ImGui::SetNextWindowSize(subdivisionMenuSize, ImGuiCond_Once);\n            ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - subdivisionMenuSize.x * 0.5f, screenH * 0.5f - subdivisionMenuSize.y * 0.5f), ImGuiCond_Appearing);\n\n            ImGui::Begin(\"##SubdivisionWarning\", nullptr, ImGuiWindowFlags_NoCollapse);\n            ImGui::PushFont(myVar.robotoMediumFont);\n\n            std::string warning = \"SUBDIVIDING FURTHER MIGHT HEAVILY SLOW DOWN PERFORMANCE\";\n\n            float windowWidth = ImGui::GetWindowSize().x;\n            float textWidth = ImGui::CalcTextSize(warning.c_str()).x;\n\n            ImGui::SetWindowFontScale(1.2f);\n            ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);\n            ImGui::TextColored(ImVec4(0.9f, 0.0f, 0.0f, 1.0f), \"%s\", warning.c_str());\n\n            if (ImGui::Button(\"Confirm\", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) {\n                confirmState = !confirmState;\n            }\n\n            ImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive);\n            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover);\n            ImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress);\n\n            if (ImGui::Button(\"Quit\", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) {\n                quitState = !quitState;\n            }\n            ImGui::PopStyleColor(3);\n            ImGui::PopFont();\n            ImGui::End();\n        }\n\n        if (quitState) {\n            subdivideAll = false;\n        }\n\n        if (myParam.pParticles3D.size() < particlesThreshold || confirmState) {\n\n            int originalSize = static_cast<int>(myParam.pParticles3D.size());\n\n            for (int i = originalSize - 1; i >= 0; i--) {\n\n                bool isTarget = (subdivideAll || myParam.rParticles3D[i].isSelected);\n\n                if (isTarget && myParam.rParticles3D[i].canBeSubdivided) {\n\n                    float halfOffset = myParam.rParticles3D[i].previousSize / 2.0f * myVar.particleTextureHalfSize * 0.25f;\n                    float halfOffsetVisual = myParam.rParticles3D[i].previousSize / 2.0f;\n\n                    int multipliers[8][3] = {\n                        {-1, -1, -1}, { 1, -1, -1}, {-1, 1, -1}, { 1, 1, -1},\n                        {-1, -1,  1}, { 1, -1,  1}, {-1, 1,  1}, { 1, 1,  1}\n                    };\n\n                    size_t firstNewParticleIndex = myParam.pParticles3D.size();\n\n                    for (int j = 0; j < 8; j++) {\n\n                        float offsetX = multipliers[j][0] * halfOffset + (rand() % 3 - 1);\n                        float offsetY = multipliers[j][1] * halfOffset + (rand() % 3 - 1);\n                        float offsetZ = multipliers[j][2] * halfOffset + (rand() % 3 - 1);\n\n                        glm::vec3 newPos{\n                            myParam.pParticles3D[i].pos.x + offsetX,\n                            myParam.pParticles3D[i].pos.y + offsetY,\n                            myParam.pParticles3D[i].pos.z + offsetZ\n                        };\n\n                        myParam.pParticles3D.emplace_back(\n                            newPos,\n                            myParam.pParticles3D[i].vel,\n                            myParam.pParticles3D[i].mass / 8.0f,\n                            myParam.pParticles3D[i].restDens,\n                            myParam.pParticles3D[i].stiff,\n                            myParam.pParticles3D[i].visc,\n                            myParam.pParticles3D[i].cohesion\n                        );\n\n                        myParam.rParticles3D.emplace_back(\n                            myParam.rParticles3D[i].color,\n                            halfOffsetVisual,\n                            myParam.rParticles3D[i].uniqueColor,\n                            myParam.rParticles3D[i].isSelected,\n                            myParam.rParticles3D[i].isSolid,\n                            myParam.rParticles3D[i].canBeSubdivided,\n                            myParam.rParticles3D[i].canBeResized,\n                            myParam.rParticles3D[i].isDarkMatter,\n                            myParam.rParticles3D[i].isSPH,\n                            myParam.rParticles3D[i].lifeSpan,\n                            myParam.rParticles3D[i].sphLabel\n                        );\n                    }\n\n                    for (int j = 0; j < 8; ++j) {\n                        myParam.pParticles3D[firstNewParticleIndex + j].id = globalId++;\n                        myParam.pParticles3D[firstNewParticleIndex + j].temp = myParam.pParticles3D[i].temp;\n                    }\n\n                    for (int j = 0; j < 8; ++j) {\n                        myParam.rParticles3D[firstNewParticleIndex + j].pColor = myParam.rParticles3D[i].pColor;\n                        myParam.rParticles3D[firstNewParticleIndex + j].sColor = myParam.rParticles3D[i].sColor;\n                        myParam.rParticles3D[firstNewParticleIndex + j].sphColor = myParam.rParticles3D[i].sphColor;\n                    }\n\n                    if (i < myParam.pParticles3D.size() - 1) {\n                        myParam.pParticles3D[i] = std::move(myParam.pParticles3D.back());\n                        myParam.rParticles3D[i] = std::move(myParam.rParticles3D.back());\n                    }\n\n                    myParam.pParticles3D.pop_back();\n                    myParam.rParticles3D.pop_back();\n                }\n            }\n\n            subdivideAll = false;\n            subdivideSelected = false;\n        }\n        confirmState = false;\n        quitState = false;\n    }\n}\n"
  },
  {
    "path": "GalaxyEngine/src/Particles/particleTrails.cpp",
    "content": "#include \"Particles/particleTrails.h\"\n\n#include \"parameters.h\"\n\nvoid ParticleTrails::trailLogic(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (IO::shortcutPress(KEY_T) && !IsKeyDown(KEY_LEFT_CONTROL)) {\n\t\tmyVar.isGlobalTrailsEnabled = !myVar.isGlobalTrailsEnabled;\n\t\tmyVar.isSelectedTrailsEnabled = false;\n\t\tsegments.clear();\n\t}\n\tif (IO::shortcutPress(KEY_T) && IsKeyDown(KEY_LEFT_CONTROL)) {\n\t\tmyVar.isSelectedTrailsEnabled = !myVar.isSelectedTrailsEnabled;\n\t\tmyVar.isGlobalTrailsEnabled = false;\n\t\tsegments.clear();\n\t}\n\n\n\tif (myVar.timeFactor > 0.0f) {\n\t\tif (myVar.isGlobalTrailsEnabled) {\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\n\t\t\t\tglm::vec2 offset = {\n\t\t\tmyParam.pParticles[i].pos.x - selectedParticlesAveragePos.x,\n\t\t\tmyParam.pParticles[i].pos.y - selectedParticlesAveragePos.y\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 prevPos = myParam.pParticles[i].pos\n\t\t\t\t\t- myParam.pParticles[i].vel * myVar.timeFactor\n\t\t\t\t\t+ 0.5f * myParam.pParticles[i].acc\n\t\t\t\t\t* myVar.timeFactor * myVar.timeFactor;\n\n\t\t\t\tglm::vec2 prevOffset = {\n\t\t\tprevPos.x - selectedParticlesAveragePrevPos.x,\n\t\t\tprevPos.y - selectedParticlesAveragePrevPos.y\n\t\t\t\t};\n\n\t\t\t\tsegments.push_back({ { myParam.pParticles[i].pos }, { prevPos }, {offset}, {prevOffset}, myParam.rParticles[i].color });\n\t\t\t}\n\n\t\t\tsize_t MAX_DOTS = myVar.trailMaxLength * myParam.pParticles.size();\n\t\t\tif (segments.size() > MAX_DOTS) {\n\t\t\t\tsize_t excess = segments.size() - MAX_DOTS;\n\t\t\t\tsegments.erase(segments.begin(), segments.begin() + excess);\n\t\t\t}\n\n\t\t\tif (myVar.isLocalTrailsEnabled) {\n\n\t\t\t\tif (!wasLocalTrailsEnabled) {\n\t\t\t\t\tsegments.clear();\n\t\t\t\t}\n\n\t\t\t\tif (myParam.pParticlesSelected.size() > 0) {\n\t\t\t\t\tfloat pParticlePosSumX = 0.0f;\n\t\t\t\t\tfloat pParticlePosSumY = 0.0f;\n\n\t\t\t\t\tfloat pParticlePrevPosSumX = 0.0f;\n\t\t\t\t\tfloat pParticlePrevPosSumY = 0.0f;\n\n\t\t\t\t\tfor (const auto& selectedParticle : myParam.pParticlesSelected) {\n\t\t\t\t\t\tpParticlePosSumX += selectedParticle.pos.x;\n\t\t\t\t\t\tpParticlePosSumY += selectedParticle.pos.y;\n\n\t\t\t\t\t\tglm::vec2 prevPos = selectedParticle.pos\n\t\t\t\t\t\t\t- selectedParticle.vel * myVar.timeFactor\n\t\t\t\t\t\t\t+ 0.5f * selectedParticle.acc\n\t\t\t\t\t\t\t* myVar.timeFactor * myVar.timeFactor;\n\n\t\t\t\t\t\tpParticlePrevPosSumX += prevPos.x;\n\t\t\t\t\t\tpParticlePrevPosSumY += prevPos.y;\n\t\t\t\t\t}\n\t\t\t\t\tselectedParticlesAveragePos = { pParticlePosSumX / myParam.pParticlesSelected.size(), pParticlePosSumY / myParam.pParticlesSelected.size() };\n\t\t\t\t\tselectedParticlesAveragePrevPos = { pParticlePrevPosSumX / myParam.pParticlesSelected.size(), pParticlePrevPosSumY / myParam.pParticlesSelected.size() };\n\n\t\t\t\t\tfor (auto& segment : segments) {\n\t\t\t\t\t\tsegment.start.x = segment.offset.x + selectedParticlesAveragePos.x;\n\t\t\t\t\t\tsegment.start.y = segment.offset.y + selectedParticlesAveragePos.y;\n\n\t\t\t\t\t\tsegment.end.x = segment.prevOffset.x + selectedParticlesAveragePos.x;\n\t\t\t\t\t\tsegment.end.y = segment.prevOffset.y + selectedParticlesAveragePos.y;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twasLocalTrailsEnabled = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\twasLocalTrailsEnabled = false;\n\t\t\t}\n\t\t}\n\n\n\t\telse if (myVar.isSelectedTrailsEnabled) {\n\t\t\tfor (size_t i = 0; i < myParam.pParticlesSelected.size(); i++) {\n\n\t\t\t\tglm::vec2 offset = {\n\t\t\tmyParam.pParticlesSelected[i].pos.x - selectedParticlesAveragePos.x,\n\t\t\tmyParam.pParticlesSelected[i].pos.y - selectedParticlesAveragePos.y\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 prevPos = myParam.pParticlesSelected[i].pos\n\t\t\t\t\t- myParam.pParticlesSelected[i].vel * myVar.timeFactor\n\t\t\t\t\t+ 0.5f * myParam.pParticlesSelected[i].acc\n\t\t\t\t\t* myVar.timeFactor * myVar.timeFactor;\n\n\t\t\t\tglm::vec2 prevOffset = {\n\t\t\tprevPos.x - selectedParticlesAveragePrevPos.x,\n\t\t\tprevPos.y - selectedParticlesAveragePrevPos.y\n\t\t\t\t};\n\n\t\t\t\tsegments.push_back({ { myParam.pParticlesSelected[i].pos }, { prevPos }, {offset}, {prevOffset}, myParam.rParticlesSelected[i].color });\n\t\t\t}\n\t\t\tsize_t MAX_DOTS = myVar.trailMaxLength * myParam.pParticlesSelected.size();\n\t\t\tif (segments.size() > MAX_DOTS) {\n\t\t\t\tsize_t excess = segments.size() - MAX_DOTS;\n\t\t\t\tsegments.erase(segments.begin(), segments.begin() + excess);\n\t\t\t}\n\n\t\t\tif (myVar.isLocalTrailsEnabled) {\n\n\t\t\t\tif (!wasLocalTrailsEnabled) {\n\t\t\t\t\tsegments.clear();\n\t\t\t\t}\n\n\t\t\t\tif (myParam.pParticlesSelected.size() > 0) {\n\t\t\t\t\tfloat pParticlePosSumX = 0.0f;\n\t\t\t\t\tfloat pParticlePosSumY = 0.0f;\n\n\t\t\t\t\tfloat pParticlePrevPosSumX = 0.0f;\n\t\t\t\t\tfloat pParticlePrevPosSumY = 0.0f;\n\n\t\t\t\t\tfor (const auto& selectedParticle : myParam.pParticlesSelected) {\n\t\t\t\t\t\tpParticlePosSumX += selectedParticle.pos.x;\n\t\t\t\t\t\tpParticlePosSumY += selectedParticle.pos.y;\n\n\t\t\t\t\t\tglm::vec2 prevPos = selectedParticle.pos\n\t\t\t\t\t\t\t- selectedParticle.vel * myVar.timeFactor\n\t\t\t\t\t\t\t+ 0.5f * selectedParticle.acc\n\t\t\t\t\t\t\t* myVar.timeFactor * myVar.timeFactor;\n\n\t\t\t\t\t\tpParticlePrevPosSumX += prevPos.x;\n\t\t\t\t\t\tpParticlePrevPosSumY += prevPos.y;\n\t\t\t\t\t}\n\t\t\t\t\tselectedParticlesAveragePos = { pParticlePosSumX / myParam.pParticlesSelected.size(), pParticlePosSumY / myParam.pParticlesSelected.size() };\n\t\t\t\t\tselectedParticlesAveragePrevPos = { pParticlePrevPosSumX / myParam.pParticlesSelected.size(), pParticlePrevPosSumY / myParam.pParticlesSelected.size() };\n\n\t\t\t\t\tfor (auto& segment : segments) {\n\t\t\t\t\t\tsegment.start.x = segment.offset.x + selectedParticlesAveragePos.x;\n\t\t\t\t\t\tsegment.start.y = segment.offset.y + selectedParticlesAveragePos.y;\n\n\t\t\t\t\t\tsegment.end.x = segment.prevOffset.x + selectedParticlesAveragePos.x;\n\t\t\t\t\t\tsegment.end.y = segment.prevOffset.y + selectedParticlesAveragePos.y;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twasLocalTrailsEnabled = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\twasLocalTrailsEnabled = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!myVar.isGlobalTrailsEnabled && !myVar.isSelectedTrailsEnabled) {\n\t\tsegments.clear();\n\t}\n}\n\nvoid ParticleTrails::drawTrail(std::vector<ParticleRendering>& rParticles, Texture2D& particleBlur) {\n\n\tif (!whiteTrails) {\n\t\tif (!segments.empty()) {\n\t\t\tfor (size_t i = 0; i < segments.size(); i++) {\n\t\t\t\tDrawLineEx({ segments[i].start.x, segments[i].start.y }, { segments[i].end.x ,segments[i].end.y }, trailThickness, segments[i].color);\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tif (!segments.empty()) {\n\t\t\tfor (size_t i = 0; i < segments.size(); i++) {\n\t\t\t\tDrawLineEx({ segments[i].start.x, segments[i].start.y }, { segments[i].end.x ,segments[i].end.y }, trailThickness, {255,255,255,160});\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid ParticleTrails::trailLogic3D(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (IO::shortcutPress(KEY_T) && !IsKeyDown(KEY_LEFT_CONTROL)) {\n\t\tmyVar.isGlobalTrailsEnabled = !myVar.isGlobalTrailsEnabled;\n\t\tmyVar.isSelectedTrailsEnabled = false;\n\t\tsegments3D.clear();\n\t}\n\tif (IO::shortcutPress(KEY_T) && IsKeyDown(KEY_LEFT_CONTROL)) {\n\t\tmyVar.isSelectedTrailsEnabled = !myVar.isSelectedTrailsEnabled;\n\t\tmyVar.isGlobalTrailsEnabled = false;\n\t\tsegments3D.clear();\n\t}\n\n\tif (myVar.timeFactor > 0.0f) {\n\t\tif (myVar.isGlobalTrailsEnabled) {\n\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\n\t\t\t\tglm::vec3 offset = {\n\t\t\t\t\tmyParam.pParticles3D[i].pos.x - selectedParticlesAveragePos3D.x,\n\t\t\t\t\tmyParam.pParticles3D[i].pos.y - selectedParticlesAveragePos3D.y,\n\t\t\t\t\tmyParam.pParticles3D[i].pos.z - selectedParticlesAveragePos3D.z\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 prevPos = myParam.pParticles3D[i].pos\n\t\t\t\t\t- myParam.pParticles3D[i].vel * myVar.timeFactor\n\t\t\t\t\t+ 0.5f * myParam.pParticles3D[i].acc\n\t\t\t\t\t* myVar.timeFactor * myVar.timeFactor;\n\n\t\t\t\tglm::vec3 prevOffset = {\n\t\t\t\t\tprevPos.x - selectedParticlesAveragePrevPos3D.x,\n\t\t\t\t\tprevPos.y - selectedParticlesAveragePrevPos3D.y,\n\t\t\t\t\tprevPos.z - selectedParticlesAveragePrevPos3D.z\n\t\t\t\t};\n\n\t\t\t\tsegments3D.push_back({ { myParam.pParticles3D[i].pos }, { prevPos }, { offset }, { prevOffset }, myParam.rParticles3D[i].color });\n\t\t\t}\n\n\t\t\tsize_t MAX_DOTS = myVar.trailMaxLength * myParam.pParticles3D.size();\n\t\t\tif (segments3D.size() > MAX_DOTS) {\n\t\t\t\tsize_t excess = segments3D.size() - MAX_DOTS;\n\t\t\t\tsegments3D.erase(segments3D.begin(), segments3D.begin() + excess);\n\t\t\t}\n\n\t\t\tif (myVar.isLocalTrailsEnabled) {\n\n\t\t\t\tif (!wasLocalTrailsEnabled) {\n\t\t\t\t\tsegments3D.clear();\n\t\t\t\t}\n\n\t\t\t\tif (myParam.pParticlesSelected3D.size() > 0) {\n\t\t\t\t\tfloat pParticlePosSumX = 0.0f;\n\t\t\t\t\tfloat pParticlePosSumY = 0.0f;\n\t\t\t\t\tfloat pParticlePosSumZ = 0.0f;\n\n\t\t\t\t\tfloat pParticlePrevPosSumX = 0.0f;\n\t\t\t\t\tfloat pParticlePrevPosSumY = 0.0f;\n\t\t\t\t\tfloat pParticlePrevPosSumZ = 0.0f;\n\n\t\t\t\t\tfor (const auto& selectedParticle : myParam.pParticlesSelected3D) {\n\t\t\t\t\t\tpParticlePosSumX += selectedParticle.pos.x;\n\t\t\t\t\t\tpParticlePosSumY += selectedParticle.pos.y;\n\t\t\t\t\t\tpParticlePosSumZ += selectedParticle.pos.z;\n\n\t\t\t\t\t\tglm::vec3 prevPos = selectedParticle.pos\n\t\t\t\t\t\t\t- selectedParticle.vel * myVar.timeFactor\n\t\t\t\t\t\t\t+ 0.5f * selectedParticle.acc\n\t\t\t\t\t\t\t* myVar.timeFactor * myVar.timeFactor;\n\n\t\t\t\t\t\tpParticlePrevPosSumX += prevPos.x;\n\t\t\t\t\t\tpParticlePrevPosSumY += prevPos.y;\n\t\t\t\t\t\tpParticlePrevPosSumZ += prevPos.z;\n\t\t\t\t\t}\n\t\t\t\t\tselectedParticlesAveragePos3D = {\n\t\t\t\t\t\tpParticlePosSumX / myParam.pParticlesSelected3D.size(),\n\t\t\t\t\t\tpParticlePosSumY / myParam.pParticlesSelected3D.size(),\n\t\t\t\t\t\tpParticlePosSumZ / myParam.pParticlesSelected3D.size()\n\t\t\t\t\t};\n\t\t\t\t\tselectedParticlesAveragePrevPos3D = {\n\t\t\t\t\t\tpParticlePrevPosSumX / myParam.pParticlesSelected3D.size(),\n\t\t\t\t\t\tpParticlePrevPosSumY / myParam.pParticlesSelected3D.size(),\n\t\t\t\t\t\tpParticlePrevPosSumZ / myParam.pParticlesSelected3D.size()\n\t\t\t\t\t};\n\n\t\t\t\t\tfor (auto& segment : segments3D) {\n\t\t\t\t\t\tsegment.start.x = segment.offset.x + selectedParticlesAveragePos3D.x;\n\t\t\t\t\t\tsegment.start.y = segment.offset.y + selectedParticlesAveragePos3D.y;\n\t\t\t\t\t\tsegment.start.z = segment.offset.z + selectedParticlesAveragePos3D.z;\n\n\t\t\t\t\t\tsegment.end.x = segment.prevOffset.x + selectedParticlesAveragePos3D.x;\n\t\t\t\t\t\tsegment.end.y = segment.prevOffset.y + selectedParticlesAveragePos3D.y;\n\t\t\t\t\t\tsegment.end.z = segment.prevOffset.z + selectedParticlesAveragePos3D.z;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twasLocalTrailsEnabled = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\twasLocalTrailsEnabled = false;\n\t\t\t}\n\t\t}\n\n\t\telse if (myVar.isSelectedTrailsEnabled) {\n\t\t\tfor (size_t i = 0; i < myParam.pParticlesSelected3D.size(); i++) {\n\n\t\t\t\tglm::vec3 offset = {\n\t\t\t\t\tmyParam.pParticlesSelected3D[i].pos.x - selectedParticlesAveragePos3D.x,\n\t\t\t\t\tmyParam.pParticlesSelected3D[i].pos.y - selectedParticlesAveragePos3D.y,\n\t\t\t\t\tmyParam.pParticlesSelected3D[i].pos.z - selectedParticlesAveragePos3D.z\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 prevPos = myParam.pParticlesSelected3D[i].pos\n\t\t\t\t\t- myParam.pParticlesSelected3D[i].vel * myVar.timeFactor\n\t\t\t\t\t+ 0.5f * myParam.pParticlesSelected3D[i].acc\n\t\t\t\t\t* myVar.timeFactor * myVar.timeFactor;\n\n\t\t\t\tglm::vec3 prevOffset = {\n\t\t\t\t\tprevPos.x - selectedParticlesAveragePrevPos3D.x,\n\t\t\t\t\tprevPos.y - selectedParticlesAveragePrevPos3D.y,\n\t\t\t\t\tprevPos.z - selectedParticlesAveragePrevPos3D.z\n\t\t\t\t};\n\n\t\t\t\tsegments3D.push_back({ { myParam.pParticlesSelected3D[i].pos }, { prevPos }, { offset }, { prevOffset }, myParam.rParticlesSelected3D[i].color });\n\t\t\t}\n\n\t\t\tsize_t MAX_DOTS = myVar.trailMaxLength * myParam.pParticlesSelected3D.size();\n\t\t\tif (segments3D.size() > MAX_DOTS) {\n\t\t\t\tsize_t excess = segments3D.size() - MAX_DOTS;\n\t\t\t\tsegments3D.erase(segments3D.begin(), segments3D.begin() + excess);\n\t\t\t}\n\n\t\t\tif (myVar.isLocalTrailsEnabled) {\n\n\t\t\t\tif (!wasLocalTrailsEnabled) {\n\t\t\t\t\tsegments3D.clear();\n\t\t\t\t}\n\n\t\t\t\tif (myParam.pParticlesSelected3D.size() > 0) {\n\t\t\t\t\tfloat pParticlePosSumX = 0.0f;\n\t\t\t\t\tfloat pParticlePosSumY = 0.0f;\n\t\t\t\t\tfloat pParticlePosSumZ = 0.0f;\n\n\t\t\t\t\tfloat pParticlePrevPosSumX = 0.0f;\n\t\t\t\t\tfloat pParticlePrevPosSumY = 0.0f;\n\t\t\t\t\tfloat pParticlePrevPosSumZ = 0.0f;\n\n\t\t\t\t\tfor (const auto& selectedParticle : myParam.pParticlesSelected3D) {\n\t\t\t\t\t\tpParticlePosSumX += selectedParticle.pos.x;\n\t\t\t\t\t\tpParticlePosSumY += selectedParticle.pos.y;\n\t\t\t\t\t\tpParticlePosSumZ += selectedParticle.pos.z;\n\n\t\t\t\t\t\tglm::vec3 prevPos = selectedParticle.pos\n\t\t\t\t\t\t\t- selectedParticle.vel * myVar.timeFactor\n\t\t\t\t\t\t\t+ 0.5f * selectedParticle.acc\n\t\t\t\t\t\t\t* myVar.timeFactor * myVar.timeFactor;\n\n\t\t\t\t\t\tpParticlePrevPosSumX += prevPos.x;\n\t\t\t\t\t\tpParticlePrevPosSumY += prevPos.y;\n\t\t\t\t\t\tpParticlePrevPosSumZ += prevPos.z;\n\t\t\t\t\t}\n\t\t\t\t\tselectedParticlesAveragePos3D = {\n\t\t\t\t\t\tpParticlePosSumX / myParam.pParticlesSelected3D.size(),\n\t\t\t\t\t\tpParticlePosSumY / myParam.pParticlesSelected3D.size(),\n\t\t\t\t\t\tpParticlePosSumZ / myParam.pParticlesSelected3D.size()\n\t\t\t\t\t};\n\t\t\t\t\tselectedParticlesAveragePrevPos3D = {\n\t\t\t\t\t\tpParticlePrevPosSumX / myParam.pParticlesSelected3D.size(),\n\t\t\t\t\t\tpParticlePrevPosSumY / myParam.pParticlesSelected3D.size(),\n\t\t\t\t\t\tpParticlePrevPosSumZ / myParam.pParticlesSelected3D.size()\n\t\t\t\t\t};\n\n\t\t\t\t\tfor (auto& segment : segments3D) {\n\t\t\t\t\t\tsegment.start.x = segment.offset.x + selectedParticlesAveragePos3D.x;\n\t\t\t\t\t\tsegment.start.y = segment.offset.y + selectedParticlesAveragePos3D.y;\n\t\t\t\t\t\tsegment.start.z = segment.offset.z + selectedParticlesAveragePos3D.z;\n\n\t\t\t\t\t\tsegment.end.x = segment.prevOffset.x + selectedParticlesAveragePos3D.x;\n\t\t\t\t\t\tsegment.end.y = segment.prevOffset.y + selectedParticlesAveragePos3D.y;\n\t\t\t\t\t\tsegment.end.z = segment.prevOffset.z + selectedParticlesAveragePos3D.z;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twasLocalTrailsEnabled = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\twasLocalTrailsEnabled = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!myVar.isGlobalTrailsEnabled && !myVar.isSelectedTrailsEnabled) {\n\t\tsegments3D.clear();\n\t}\n\n\tif (IO::shortcutPress(KEY_C)) {\n\t\tmyParam.pParticles.clear();\n\t\tmyParam.rParticles.clear();\n\t\tmyParam.pParticles3D.clear();\n\t\tmyParam.rParticles3D.clear();\n\t\tsegments3D.clear();\n\t}\n}\n\nvoid ParticleTrails::drawTrail3D(std::vector<ParticleRendering3D>& rParticles3D, Texture2D& particleBlur, Camera3D& cam3D) {\n\n\tif (!whiteTrails) {\n\t\tif (!segments3D.empty()) {\n\t\t\tfor (size_t i = 0; i < segments3D.size(); i++) {\n\n\t\t\t\tDrawLine3D({segments3D[i].start.x,segments3D[i].start.y,segments3D[i].start.z }, \n\t\t\t\t\t{ segments3D[i].end.x,segments3D[i].end.y,segments3D[i].end.z },\n\t\t\t\t\tsegments3D[i].color);\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tif (!segments3D.empty()) {\n\t\t\tfor (size_t i = 0; i < segments3D.size(); i++) {\n\t\t\t\tDrawLine3D({ segments3D[i].start.x,segments3D[i].start.y,segments3D[i].start.z },\n\t\t\t\t\t{ segments3D[i].end.x,segments3D[i].end.y,segments3D[i].end.z },\n\t\t\t\t\t{ 255,255,255,160 });\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "GalaxyEngine/src/Particles/particlesSpawning.cpp",
    "content": "#include \"Particles/particlesSpawning.h\"\n\n#include \"Physics/physics.h\"\n#include \"Physics/physics3D.h\"\n#include \"Physics/quadtree.h\"\n\n#include \"parameters.h\"\n\nvoid ParticlesSpawning::particlesInitialConditions(Physics& physics, UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (myVar.isMouseNotHoveringUI && myVar.isSpawningAllowed) {\n\n\t\tSlingshot slingshot = slingshot.particleSlingshot(myVar, myParam.myCamera);\n\n\t\tif (myVar.isDragging && myVar.enablePathPrediction && myVar.gridExists) {\n\t\t\tpredictTrajectory(myParam.pParticles, myParam.myCamera, physics, myVar, slingshot);\n\t\t}\n\n\t\tif (IO::mouseReleased(0) && myVar.toolSpawnHeavyParticle && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && myVar.isDragging) {\n\n\t\t\tmyParam.pParticles.emplace_back(\n\t\t\t\tmyParam.myCamera.mouseWorldPos,\n\t\t\t\tslingshot.norm * slingshot.length,\n\t\t\t\theavyParticleInitMass * myVar.heavyParticleWeightMultiplier,\n\n\t\t\t\t0.008f,\n\t\t\t\t1.0f,\n\t\t\t\t1.0f,\n\t\t\t\t1.0f\n\t\t\t);\n\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\tColor{ 255, 255, 255, 255 },\n\t\t\t\t0.3f,\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t\tfalse,\n\t\t\t\tfalse,\n\t\t\t\tfalse,\n\t\t\t\t-1.0f,\n\t\t\t\t0\n\t\t\t);\n\t\t\tmyVar.isDragging = false;\n\t\t}\n\n\t\tif (!myVar.isSPHEnabled) {\n\t\t\tmyVar.isBrushDrawing = false;\n\t\t\tmyVar.constraintAfterDrawingFlag = false;\n\t\t}\n\n\t\tif ((IO::mouseDown(2) || (IO::mouseDown(0) && myVar.toolDrawParticles)) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_X)) {\n\t\t\tmyVar.isBrushDrawing = true;\n\n\t\t\tmyParam.brush.brushLogic(myParam, myVar.isSPHEnabled, myVar.constraintAfterDrawing, myVar.massScatter, myVar);\n\t\t\tif (myVar.isBrushDrawing) {\n\t\t\t\tif (myVar.isSPHEnabled) {\n\n\t\t\t\t\tparticlesIterating = true;\n\n\t\t\t\t\tfor (int i = 0; i < correctionSubsteps; i++) {\n\n\t\t\t\t\t\tif (i % 2 == 0) {\n\t\t\t\t\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\t\t\t\t\tmyParam.neighborSearchV2.newGrid(myParam.pParticles);\n\t\t\t\t\t\t\t\tmyParam.neighborSearchV2.neighborAmount(myParam.pParticles, myParam.rParticles);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tmyParam.neighborSearchV2AVX2.newGridAVX2(myParam.pParticles);\n\t\t\t\t\t\t\t\tmyParam.neighborSearchV2AVX2.neighborAmount(myParam.pParticles, myParam.rParticles);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tphysics.spawnCorrection(myParam, myVar.hasAVX2, 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\n\t\t\tif (myVar.isSPHEnabled && myVar.isBrushDrawing) {\n\n\t\t\t\tparticlesIterating = false;\n\t\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\t\tif (myParam.rParticles[i].spawnCorrectIter < correctionSubsteps && myParam.rParticles[i].isBeingDrawn) {\n\t\t\t\t\t\tparticlesIterating = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (particlesIterating) {\n\n\t\t\t\t\tfor (int i = 0; i < correctionSubsteps * 8; i++) {\n\n\t\t\t\t\t\tif (i % 4 == 0) {\n\t\t\t\t\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\t\t\t\t\tmyParam.neighborSearchV2.newGrid(myParam.pParticles);\n\t\t\t\t\t\t\t\tmyParam.neighborSearchV2.neighborAmount(myParam.pParticles, myParam.rParticles);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tmyParam.neighborSearchV2AVX2.newGridAVX2(myParam.pParticles);\n\t\t\t\t\t\t\t\tmyParam.neighborSearchV2AVX2.neighborAmount(myParam.pParticles, myParam.rParticles);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tphysics.spawnCorrection(myParam, myVar.hasAVX2, 1);\n\t\t\t\t\t\tparticlesIterating = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\n\t\t\t\t\tif (myVar.constraintAfterDrawing) {\n\t\t\t\t\t\tmyVar.constraintAfterDrawingFlag = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (myVar.constraintAfterDrawingFlag && myVar.constraintAfterDrawing) {\n\t\t\t\t\t\tphysics.createConstraints(myParam.pParticles, myParam.rParticles,\n\t\t\t\t\t\t\tmyVar.constraintAfterDrawingFlag, myVar, myParam);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\t\t\tmyParam.rParticles[i].isBeingDrawn = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tmyVar.isBrushDrawing = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ((IO::shortcutReleased(KEY_ONE) || IO::mouseReleased(0) && myVar.toolSpawnGalaxy) && myVar.isDragging) {\n\n\t\t\t// VISIBLE MATTER\n\n\t\t\tstd::random_device rd;\n\t\t\tstd::mt19937 gen(rd());\n\t\t\tstd::uniform_real_distribution<float> dis(0.0f, 1.0f);\n\n\t\t\tfloat maxRadius = outerRadius + 600.0f;\n\t\t\tfloat maxCumulativeProb = 1.0f - std::exp(-maxRadius / scaleLength);\n\n\t\t\tint totalParticles = static_cast<int>(40000 * myVar.particleAmountMultiplier);\n\n\t\t\tfor (int i = 0; i < totalParticles; i++) {\n\t\t\t\tglm::vec2 galaxyCenter = myParam.myCamera.mouseWorldPos;\n\n\t\t\t\tfloat randVal = dis(gen) * maxCumulativeProb;\n\t\t\t\tfloat finalRadius = -scaleLength * std::log(1.0f - randVal);\n\n\t\t\t\tfinalRadius = std::max(finalRadius, 0.01f);\n\n\t\t\t\tfloat angle = dis(gen) * 2.0f * PI;\n\n\t\t\t\tglm::vec2 dirVector(std::cos(angle), std::sin(angle));\n\t\t\t\tglm::vec2 pos = galaxyCenter + (dirVector * finalRadius);\n\n\t\t\t\tglm::vec2 tangent(-dirVector.y, dirVector.x);\n\n\t\t\t\tfloat speed = 10.0f * std::sqrt(1758.0f / (finalRadius + 54.0f));\n\t\t\t\tglm::vec2 vel = tangent * speed * 0.85f;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\t\t\t\tfloat massRand = dis(gen);\n\t\t\t\tfloat randomMassMultiplier = 1.0f + (massRand * 2.0f - 1.0f) * myVar.massScatter;\n\n\t\t\t\tfloat baseMass = 8500000000.0f;\n\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (baseMass / myVar.particleAmountMultiplier) * randomMassMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = baseMass * randomMassMultiplier;\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(\n\t\t\t\t\tpos,\n\t\t\t\t\tvel + slingshot.norm * slingshot.length * 0.3f,\n\t\t\t\t\tfinalMass,\n\t\t\t\t\t0.008f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{ 128, 128, 128, 100 },\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// DARK MATTER\n\n\t\t\tif (myVar.isDarkMatterEnabled) {\n\t\t\t\tfor (int i = 0; i < static_cast<int>(12000 * myVar.DMAmountMultiplier); i++) {\n\t\t\t\t\tglm::vec2 galaxyCenter = myParam.myCamera.mouseWorldPos;\n\n\t\t\t\t\tfloat normalizedRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\n\t\t\t\t\tfloat radiusMultiplier = radiusCoreDM * sqrt(static_cast<float>(pow(1 + pow(outerRadiusDM / radiusCoreDM, 2), normalizedRand) - 1));\n\n\t\t\t\t\tfloat angle = dis(gen) * 2 * PI;\n\n\t\t\t\t\tglm::vec2 pos = glm::vec2(galaxyCenter.x + radiusMultiplier * cos(angle), galaxyCenter.y + radiusMultiplier * sin(angle));\n\n\t\t\t\t\tglm::vec2 vel = glm::vec2(static_cast<float>(rand() % 60 - 30), static_cast<float>(rand() % 60 - 30)) * 0.85f;\n\n\t\t\t\t\tfloat finalMass = 0.0f;\n\t\t\t\t\tfloat massRand = dis(gen);\n\t\t\t\t\tfloat randomMassMultiplier = 1.0f + (massRand * 2.0f - 1.0f) * myVar.massScatter;\n\n\t\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\t\tfinalMass = 141600000000.0f / myVar.DMAmountMultiplier * randomMassMultiplier;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tfinalMass = 141600000000.0f * randomMassMultiplier;\n\t\t\t\t\t}\n\n\t\t\t\t\tmyParam.pParticles.emplace_back(\n\t\t\t\t\t\tpos,\n\t\t\t\t\t\tvel + slingshot.norm * slingshot.length * 0.3f,\n\t\t\t\t\t\tfinalMass,\n\n\t\t\t\t\t\t0.008f,\n\t\t\t\t\t\t1.0f,\n\t\t\t\t\t\t1.0f,\n\t\t\t\t\t\t1.0f\n\t\t\t\t\t);\n\t\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\t\tColor{ 128, 128, 128, 0 },\n\t\t\t\t\t\t0.125f,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t-1.0f,\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmyVar.isDragging = false;\n\t\t}\n\n\t\tif ((IO::shortcutReleased(KEY_TWO) || IO::mouseReleased(0) && myVar.toolSpawnStar) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\n\t\t\tfor (int i = 0; i < static_cast<int>(10000 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = (sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * 5.0f) + 0.1f;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = 8500000000.0f / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = 8500000000.0f;\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(\n\t\t\t\t\tglm::vec2{ particlePos.x, particlePos.y },\n\t\t\t\t\tslingshot.norm * slingshot.length * 0.3f,\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\t0.008f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f\n\t\t\t\t);\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{ 128, 128, 128, 100 },\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tmyVar.isDragging = false;\n\t\t}\n\n\t\tif ((IO::shortcutPress(KEY_THREE) || IO::mouseReleased(0) && myVar.toolSpawnBigBang) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\n\t\t\t// VISIBLE MATTER\n\n\t\t\tfor (int i = 0; i < static_cast<int>(40000 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = (sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * 20.0f) + 1.0f;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance + static_cast<float>(GetRandomValue(-15, 15)),\n\t\t\t\t\tsin(angle) * distance + static_cast<float>(GetRandomValue(-15, 15))\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tglm::vec2 d = particlePos - myParam.myCamera.mouseWorldPos;\n\n\t\t\t\tglm::vec2 norm = d / distance;\n\n\t\t\t\tfloat speed = 300.0f;\n\n\t\t\t\tfloat adjustedSpeed = speed * (distance / 35.0f);\n\n\t\t\t\tglm::vec2 vel = adjustedSpeed * norm;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tfloat rand01 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tfloat randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * myVar.massScatter;\n\n\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = 8500000000.0f / myVar.particleAmountMultiplier * randomMassMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = 8500000000.0f * randomMassMultiplier;\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(\n\t\t\t\t\tparticlePos,\n\t\t\t\t\tvel,\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\t0.008f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f\n\t\t\t\t);\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{ 128, 128, 128, 100 },\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// DARK MATTER\n\n\t\t\tif (myVar.isDarkMatterEnabled) {\n\t\t\t\tfor (int i = 0; i < static_cast<int>(12000 * myVar.DMAmountMultiplier); i++) {\n\n\t\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\t\tfloat distance = (sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * 20.0f) + 1.0f;\n\n\t\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\t\tcos(angle) * distance + static_cast<float>(GetRandomValue(-15, 15)),\n\t\t\t\t\t\tsin(angle) * distance + static_cast<float>(GetRandomValue(-15, 15))\n\t\t\t\t\t};\n\n\t\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\t\tglm::vec2 d = particlePos - myParam.myCamera.mouseWorldPos;\n\n\t\t\t\t\tglm::vec2 norm = d / distance;\n\n\t\t\t\t\tfloat speed = 300.0f;\n\n\t\t\t\t\tfloat adjustedSpeed = speed * (distance / 35.0f);\n\n\t\t\t\t\tglm::vec2 vel = adjustedSpeed * norm;\n\n\t\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\t\tfloat rand01 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\tfloat randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * myVar.massScatter;\n\n\t\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\t\tfinalMass = 141600000000.0f / myVar.DMAmountMultiplier * randomMassMultiplier;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tfinalMass = 141600000000.0f * randomMassMultiplier;\n\t\t\t\t\t}\n\n\t\t\t\t\tmyParam.pParticles.emplace_back(\n\t\t\t\t\t\tparticlePos,\n\t\t\t\t\t\tvel,\n\t\t\t\t\t\tfinalMass,\n\n\t\t\t\t\t\t0.008f,\n\t\t\t\t\t\t1.0f,\n\t\t\t\t\t\t1.0f,\n\t\t\t\t\t\t1.0f\n\t\t\t\t\t);\n\t\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\t\tColor{ 128, 128, 128, 0 },\n\t\t\t\t\t\t0.125f,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t-1.0f,\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tif (IsMouseButtonPressed(0)) {\n\t\t\tmyVar.isSpawningAllowed = false;\n\t\t}\n\t}\n\n\tif (IsMouseButtonReleased(0)) {\n\t\tmyVar.isSpawningAllowed = true;\n\t\tmyVar.isDragging = false;\n\t}\n}\n\nvoid ParticlesSpawning::predictTrajectory(const std::vector<ParticlePhysics>& pParticles,\n\tSceneCamera& myCamera, Physics physics,\n\tUpdateVariables& myVar, Slingshot& slingshot) {\n\n\tif (!IsMouseButtonDown(0)) {\n\t\treturn;\n\t}\n\n\tstd::vector<ParticlePhysics> currentParticles = pParticles;\n\n\tParticlePhysics predictedParticle(\n\t\tglm::vec2(myCamera.mouseWorldPos),\n\t\tglm::vec2{ slingshot.norm * slingshot.length },\n\t\theavyParticleInitMass * myVar.heavyParticleWeightMultiplier,\n\n\t\t1.0f,\n\t\t1.0f,\n\t\t1.0f,\n\t\t1.0f\n\t);\n\n\tcurrentParticles.push_back(predictedParticle);\n\n\tint predictedIndex = static_cast<int>(currentParticles.size()) - 1;\n\n\tstd::vector<glm::vec2> predictedPath;\n\n\tfor (int step = 0; step < myVar.predictPathLength; ++step) {\n\t\tParticlePhysics& p = currentParticles[predictedIndex];\n\n\t\tglm::vec2 netForce = physics.calculateForceFromGridOld(currentParticles, myVar, p);\n\n\t\tp.acc = netForce / p.mass;\n\n\t\tp.vel += p.acc * (myVar.timeFactor * 0.5f);\n\n\t\tp.pos += p.vel * myVar.timeFactor;\n\n\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\tif (p.pos.x < 0.0f) p.pos.x += myVar.domainSize.x;\n\t\t\telse if (p.pos.x >= myVar.domainSize.x) p.pos.x -= myVar.domainSize.x;\n\n\t\t\tif (p.pos.y < 0.0f) p.pos.y += myVar.domainSize.y;\n\t\t\telse if (p.pos.y >= myVar.domainSize.y) p.pos.y -= myVar.domainSize.y;\n\t\t}\n\n\t\tnetForce = physics.calculateForceFromGridOld(currentParticles, myVar, p);\n\t\tp.acc = netForce / p.mass;\n\n\t\tp.vel += p.acc * (myVar.timeFactor * 0.5f);\n\n\t\tpredictedPath.push_back(p.pos);\n\t}\n\n\tfor (size_t i = 1; i < predictedPath.size(); ++i) {\n\t\tDrawLineV({ predictedPath[i - 1].x,  predictedPath[i - 1].y }, { predictedPath[i].x,  predictedPath[i].y }, WHITE);\n\t}\n}\n\nvoid ParticlesSpawning::drawGalaxyDisplay(UpdateParameters& myParam) {\n\tDrawCircleLinesV({ myParam.myCamera.mouseWorldPos.x, myParam.myCamera.mouseWorldPos.y }, scaleLength, RED);\n}\n\n// ---- 3D IMPLEMENTATION ---- //\n\nvoid ParticlesSpawning3D::particlesInitialConditions(Physics3D& physics3D, UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (myVar.isMouseNotHoveringUI && myVar.isSpawningAllowed) {\n\n\t\tSlingshot3D slingshot = slingshot.particleSlingshot(myVar, myParam.brush3D.brushPos);\n\n\t\tif (myVar.isDragging && myVar.enablePathPrediction && myVar.grid3DExists) {\n\t\t\tpredictTrajectory(myParam.pParticles3D, myParam.myCamera3D, physics3D, myVar, myParam, slingshot);\n\t\t}\n\n\t\tif (IO::mouseReleased(0) && myVar.toolSpawnHeavyParticle && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && myVar.isDragging) {\n\n\t\t\tmyParam.pParticles3D.emplace_back(\n\t\t\t\tmyParam.brush3D.brushPos,\n\t\t\t\tslingshot.norm * slingshot.length,\n\t\t\t\theavyParticleInitMass * myVar.heavyParticleWeightMultiplier,\n\n\t\t\t\t0.008f,\n\t\t\t\t1.0f,\n\t\t\t\t1.0f,\n\t\t\t\t1.0f\n\t\t\t);\n\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\tColor{ 255, 255, 255, 255 },\n\t\t\t\t0.3f,\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t\tfalse,\n\t\t\t\tfalse,\n\t\t\t\tfalse,\n\t\t\t\t-1.0f,\n\t\t\t\t0\n\t\t\t);\n\t\t\tmyVar.isDragging = false;\n\t\t}\n\n\t\tif (!myVar.isSPHEnabled) {\n\t\t\tmyVar.isBrushDrawing = false;\n\t\t\tmyVar.constraintAfterDrawingFlag = false;\n\t\t}\n\n\t\tif ((IO::mouseDown(2) || (IO::mouseDown(0) && myVar.toolDrawParticles)) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_X)) {\n\t\t\tmyVar.isBrushDrawing = true;\n\n\t\t\tmyParam.brush3D.brushLogic(myParam, myVar.isSPHEnabled, myVar.constraintAfterDrawing, myVar.massScatter, myVar);\n\n\t\t\tif (myVar.isBrushDrawing) {\n\t\t\t\tif (myVar.isSPHEnabled) {\n\n\t\t\t\t\tparticlesIterating = true;\n\n\t\t\t\t\tfor (int i = 0; i < correctionSubsteps; i++) {\n\n\t\t\t\t\t\tif (i % 2 == 0) {\n\t\t\t\t\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\t\t\t\t\tmyParam.neighborSearch3DV2.newGrid(myParam.pParticles3D);\n\t\t\t\t\t\t\t\tmyParam.neighborSearch3DV2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tmyParam.neighborSearch3DV2AVX2.newGridAVX2(myParam.pParticles3D);\n\t\t\t\t\t\t\t\tmyParam.neighborSearch3DV2AVX2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tphysics3D.spawnCorrection(myParam, myVar.hasAVX2, 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\n\t\t\tif (myVar.isSPHEnabled && myVar.isBrushDrawing) {\n\n\t\t\t\tparticlesIterating = false;\n\t\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\t\tif (myParam.rParticles3D[i].spawnCorrectIter < correctionSubsteps && myParam.rParticles3D[i].isBeingDrawn) {\n\t\t\t\t\t\tparticlesIterating = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (particlesIterating) {\n\n\t\t\t\t\tfor (int i = 0; i < correctionSubsteps * 8; i++) {\n\n\t\t\t\t\t\tif (i % 4 == 0) {\n\t\t\t\t\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\t\t\t\t\tmyParam.neighborSearch3DV2.newGrid(myParam.pParticles3D);\n\t\t\t\t\t\t\t\tmyParam.neighborSearch3DV2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tmyParam.neighborSearch3DV2AVX2.newGridAVX2(myParam.pParticles3D);\n\t\t\t\t\t\t\t\tmyParam.neighborSearch3DV2AVX2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tphysics3D.spawnCorrection(myParam, myVar.hasAVX2, 1);\n\t\t\t\t\t\tparticlesIterating = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\n\t\t\t\t\tif (myVar.constraintAfterDrawing) {\n\t\t\t\t\t\tmyVar.constraintAfterDrawingFlag = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (myVar.constraintAfterDrawingFlag && myVar.constraintAfterDrawing) {\n\t\t\t\t\t\tphysics3D.createConstraints(myParam.pParticles3D, myParam.rParticles3D,\n\t\t\t\t\t\t\tmyVar.constraintAfterDrawingFlag, myVar, myParam);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\t\t\tmyParam.rParticles3D[i].isBeingDrawn = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tmyVar.isBrushDrawing = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ((IO::shortcutReleased(KEY_ONE) || IO::mouseReleased(0) && myVar.toolSpawnGalaxy) && myVar.isDragging) {\n\n\t\t\t// VISIBLE MATTER\n\n\t\t\tglm::mat4 rotationMatrix = glm::mat4(1.0f);\n\n\t\t\trotationMatrix = glm::rotate(rotationMatrix, diskAxisX * (PI / 180.0f), glm::vec3(1.0f, 0.0f, 0.0f));\n\n\t\t\trotationMatrix = glm::rotate(rotationMatrix, diskAxisY * (PI / 180.0f), glm::vec3(0.0f, 1.0f, 0.0f));\n\n\t\t\trotationMatrix = glm::rotate(rotationMatrix, diskAxisZ * (PI / 180.0f), glm::vec3(0.0f, 0.0f, 1.0f));\n\n\t\t\tstd::random_device rd;\n\t\t\tstd::mt19937 gen(rd());\n\t\t\tstd::uniform_real_distribution<float> dis(0.0f, 1.0f);\n\n\t\t\tfloat scaleLength = outerRadius / 4.0f;\n\n\t\t\tfloat maxCumulativeProb = 1.0f - std::exp(-outerRadius / scaleLength);\n\n\t\t\tfor (int i = 0; i < static_cast<int>(40000 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tglm::vec3 galaxyCenter = myParam.brush3D.brushPos;\n\n\t\t\t\tfloat u = dis(gen) * maxCumulativeProb;\n\n\t\t\t\tfloat finalRadius = -scaleLength * std::log(1.0f - u);\n\n\t\t\t\tfloat angle = dis(gen) * 2.0f * PI;\n\n\t\t\t\tfloat u1 = dis(gen);\n\t\t\t\tfloat u2 = dis(gen);\n\t\t\t\tif (u1 < 1e-6f) u1 = 1e-6f;\n\n\t\t\t\tfloat currentSpread = diskThickness + bulgeThickness * std::exp(-finalRadius / bulgeSize);\n\n\t\t\t\tfloat normalRandom = std::sqrt(-2.0f * std::log(u1)) * std::cos(2.0f * PI * u2);\n\t\t\t\tfloat zOffset = normalRandom * currentSpread;\n\n\t\t\t\tglm::vec4 localPos = glm::vec4(\n\t\t\t\t\tfinalRadius * std::cos(angle), // x\n\t\t\t\t\tfinalRadius * std::sin(angle), // y\n\t\t\t\t\tzOffset,                       // z\n\t\t\t\t\t1.0f                           // w\n\t\t\t\t);\n\n\t\t\t\tglm::vec3 localTangent;\n\t\t\t\tif (finalRadius > 0.0001f) {\n\t\t\t\t\tlocalTangent = glm::vec3(-localPos.y / finalRadius, localPos.x / finalRadius, 0.0f);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tlocalTangent = glm::vec3(1.0f, 0.0f, 0.0f);\n\t\t\t\t}\n\n\t\t\t\tfloat speed = 10.5f * std::sqrt(1758.0f / (finalRadius + 54.0f));\n\n\t\t\t\tglm::vec4 localVel = glm::vec4(localTangent * speed * 0.85f, 0.0f);\n\n\t\t\t\tglm::vec4 rotatedPos = rotationMatrix * localPos;\n\t\t\t\tglm::vec4 rotatedVel = rotationMatrix * localVel;\n\n\t\t\t\tglm::vec3 finalPos = glm::vec3(rotatedPos) + galaxyCenter;\n\n\t\t\t\tglm::vec3 finalVel = glm::vec3(rotatedVel) + (slingshot.norm * slingshot.length * 0.3f);\n\n\t\t\t\tfloat finalMass = 0.0f;\n\t\t\t\tfloat randMass = dis(gen);\n\t\t\t\tfloat randomMassMultiplier = 1.0f + (randMass * 2.0f - 1.0f) * myVar.massScatter;\n\n\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f / myVar.particleAmountMultiplier) * randomMassMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = 8500000000.0f * randomMassMultiplier;\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(\n\t\t\t\t\tfinalPos,\n\t\t\t\t\tfinalVel,\n\t\t\t\t\tfinalMass,\n\t\t\t\t\t0.008f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{ 128, 128, 128, 100 },\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// DARK MATTER\n\n\t\t\tif (myVar.isDarkMatterEnabled) {\n\t\t\t\tfor (int i = 0; i < static_cast<int>(12000 * myVar.DMAmountMultiplier); i++) {\n\n\t\t\t\t\tglm::vec3 galaxyCenter = myParam.brush3D.brushPos;\n\n\t\t\t\t\tfloat normalizedRand = dis(gen);\n\n\t\t\t\t\tfloat radiusMultiplier = radiusCoreDM * sqrt(static_cast<float>(pow(1 + pow(outerRadiusDM / radiusCoreDM, 2), normalizedRand) - 1));\n\n\t\t\t\t\tfloat z = dis(gen) * 2.0f - 1.0f;\n\n\t\t\t\t\tfloat theta = dis(gen) * 2.0f * PI;\n\n\t\t\t\t\tfloat radius_xy = sqrt(1.0f - z * z);\n\n\t\t\t\t\tfloat x = radius_xy * cos(theta);\n\t\t\t\t\tfloat y = radius_xy * sin(theta);\n\n\t\t\t\t\tglm::vec3 pos = glm::vec3(\n\t\t\t\t\t\tgalaxyCenter.x + x * radiusMultiplier,\n\t\t\t\t\t\tgalaxyCenter.y + y * radiusMultiplier,\n\t\t\t\t\t\tgalaxyCenter.z + z * radiusMultiplier\n\t\t\t\t\t);\n\n\t\t\t\t\tglm::vec3 vel = glm::vec3(\n\t\t\t\t\t\tstatic_cast<float>(rand() % 60 - 30),\n\t\t\t\t\t\tstatic_cast<float>(rand() % 60 - 30),\n\t\t\t\t\t\tstatic_cast<float>(rand() % 60 - 30)\n\t\t\t\t\t) * 0.85f;\n\n\t\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\t\tfloat randMass = dis(gen);\n\t\t\t\t\tfloat randomMassMultiplier = 1.0f + (randMass * 2.0f - 1.0f) * myVar.massScatter;\n\n\t\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\t\tfinalMass = 141600000000.0f / myVar.DMAmountMultiplier * randomMassMultiplier;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tfinalMass = 141600000000.0f * randomMassMultiplier;\n\t\t\t\t\t}\n\n\t\t\t\t\tmyParam.pParticles3D.emplace_back(\n\t\t\t\t\t\tpos,\n\t\t\t\t\t\tvel + slingshot.norm * slingshot.length * 0.3f,\n\t\t\t\t\t\tfinalMass,\n\n\t\t\t\t\t\t0.008f,\n\t\t\t\t\t\t1.0f,\n\t\t\t\t\t\t1.0f,\n\t\t\t\t\t\t1.0f\n\t\t\t\t\t);\n\n\t\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\t\tColor{ 128, 128, 128, 0 },\n\t\t\t\t\t\t0.125f,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t-1.0f,\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tmyVar.isDragging = false;\n\t\t\t}\n\n\t\t\tmyVar.isDragging = false;\n\t\t}\n\n\t\tif ((IO::shortcutReleased(KEY_TWO) || IO::mouseReleased(0) && myVar.toolSpawnStar) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\tfor (int i = 0; i < static_cast<int>(10000 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat phi = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 3.14159f;\n\n\t\t\t\tfloat distance = (cbrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * 5.0f) + 0.1f;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsin(phi) * cos(theta) * distance,\n\t\t\t\t\tsin(phi) * sin(theta) * distance,\n\t\t\t\t\tcos(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = myParam.brush3D.brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = 8500000000.0f / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = 8500000000.0f;\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(\n\t\t\t\t\tglm::vec3{ particlePos.x, particlePos.y, particlePos.z },\n\t\t\t\t\tslingshot.norm * slingshot.length * 0.3f,\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\t0.008f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{ 128, 128, 128, 100 },\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tmyVar.isDragging = false;\n\t\t}\n\n\t\tif ((IO::shortcutPress(KEY_THREE) || IO::mouseReleased(0) && myVar.toolSpawnBigBang) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\n\t\t\t// VISIBLE MATTER\n\t\t\tfor (int i = 0; i < static_cast<int>(40000 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\n\t\t\t\tfloat random_neg1_to_1 = (static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f) - 1.0f;\n\t\t\t\tfloat phi = acos(random_neg1_to_1);\n\n\t\t\t\tfloat rand01_dist = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tfloat distance = (cbrt(rand01_dist) * 20.0f) + 1.0f;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tdistance * sin(phi) * cos(theta) + static_cast<float>(GetRandomValue(-15, 15)),\n\t\t\t\t\tdistance * sin(phi) * sin(theta) + static_cast<float>(GetRandomValue(-15, 15)),\n\t\t\t\t\tdistance * cos(phi) + static_cast<float>(GetRandomValue(-15, 15))\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = myParam.brush3D.brushPos + randomOffset;\n\n\t\t\t\tglm::vec3 d = particlePos - myParam.brush3D.brushPos;\n\n\t\t\t\tglm::vec3 norm = d / distance;\n\n\t\t\t\tfloat speed = 160.0f;\n\t\t\t\tfloat adjustedSpeed = speed * (distance / 35.0f);\n\n\t\t\t\tglm::vec3 vel = adjustedSpeed * norm;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\t\t\t\tfloat rand01 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tfloat randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * myVar.massScatter;\n\n\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = 8500000000.0f / myVar.particleAmountMultiplier * randomMassMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = 8500000000.0f * randomMassMultiplier;\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(\n\t\t\t\t\tparticlePos,\n\t\t\t\t\tvel,\n\t\t\t\t\tfinalMass,\n\t\t\t\t\t0.008f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f,\n\t\t\t\t\t1.0f\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{ 128, 128, 128, 100 },\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\t0\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// DARK MATTER\n\t\t\tif (myVar.isDarkMatterEnabled) {\n\t\t\t\tfor (int i = 0; i < static_cast<int>(12000 * myVar.DMAmountMultiplier); i++) {\n\n\t\t\t\t\tfloat theta = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\t\tfloat phi = acos((static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f) - 1.0f);\n\n\t\t\t\t\tfloat rand01_dist = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\tfloat distance = (cbrt(rand01_dist) * 20.0f) + 1.0f;\n\n\t\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\t\tdistance * sin(phi) * cos(theta) + static_cast<float>(GetRandomValue(-15, 15)),\n\t\t\t\t\t\tdistance * sin(phi) * sin(theta) + static_cast<float>(GetRandomValue(-15, 15)),\n\t\t\t\t\t\tdistance * cos(phi) + static_cast<float>(GetRandomValue(-15, 15))\n\t\t\t\t\t};\n\n\t\t\t\t\tglm::vec3 particlePos = myParam.brush3D.brushPos + randomOffset;\n\t\t\t\t\tglm::vec3 d = particlePos - myParam.brush3D.brushPos;\n\t\t\t\t\tglm::vec3 norm = d / distance;\n\n\t\t\t\t\tfloat speed = 160.0f;\n\t\t\t\t\tfloat adjustedSpeed = speed * (distance / 35.0f);\n\t\t\t\t\tglm::vec3 vel = adjustedSpeed * norm;\n\n\t\t\t\t\tfloat finalMass = 0.0f;\n\t\t\t\t\tfloat rand01 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\t\tfloat randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * myVar.massScatter;\n\n\t\t\t\t\tif (massMultiplierEnabled) {\n\t\t\t\t\t\tfinalMass = 141600000000.0f / myVar.DMAmountMultiplier * randomMassMultiplier;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tfinalMass = 141600000000.0f * randomMassMultiplier;\n\t\t\t\t\t}\n\n\t\t\t\t\tmyParam.pParticles3D.emplace_back(\n\t\t\t\t\t\tparticlePos,\n\t\t\t\t\t\tvel,\n\t\t\t\t\t\tfinalMass,\n\t\t\t\t\t\t0.008f,\n\t\t\t\t\t\t1.0f,\n\t\t\t\t\t\t1.0f,\n\t\t\t\t\t\t1.0f\n\t\t\t\t\t);\n\t\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\t\tColor{ 128, 128, 128, 0 },\n\t\t\t\t\t\t0.125f,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t-1.0f,\n\t\t\t\t\t\t0\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tif (IsMouseButtonPressed(0)) {\n\t\t\tmyVar.isSpawningAllowed = false;\n\t\t}\n\t}\n\n\tif (IsMouseButtonReleased(0)) {\n\t\tmyVar.isSpawningAllowed = true;\n\t\tmyVar.isDragging = false;\n\t}\n}\n\nvoid ParticlesSpawning3D::predictTrajectory(const std::vector<ParticlePhysics3D>& pParticles,\n\tSceneCamera3D& myCamera, Physics3D physics3D,\n\tUpdateVariables& myVar, UpdateParameters& myParam, Slingshot3D& slingshot) {\n\n\tif (!IsMouseButtonDown(0)) {\n\t\treturn;\n\t}\n\n\tstd::vector<ParticlePhysics3D> currentParticles = pParticles;\n\n\tParticlePhysics3D predictedParticle(\n\t\tglm::vec3(myParam.brush3D.brushPos),\n\t\tglm::vec3(slingshot.norm.x * slingshot.length, slingshot.norm.y * slingshot.length, slingshot.norm.z * slingshot.length),\n\t\theavyParticleInitMass * myVar.heavyParticleWeightMultiplier,\n\t\t1.0f,\n\t\t1.0f,\n\t\t1.0f,\n\t\t1.0f\n\t);\n\n\tcurrentParticles.push_back(predictedParticle);\n\tint predictedIndex = static_cast<int>(currentParticles.size()) - 1;\n\n\tstd::vector<glm::vec3> predictedPath;\n\n\tfor (int step = 0; step < myVar.predictPathLength; ++step) {\n\t\tParticlePhysics3D& p = currentParticles[predictedIndex];\n\n\t\tglm::vec3 netForce = physics3D.calculateForceFromGrid3DOld(currentParticles, myVar, p);\n\n\t\tp.acc = netForce / p.mass;\n\t\tp.vel += p.acc * (myVar.timeFactor * 0.5f);\n\t\tp.pos += p.vel * myVar.timeFactor;\n\n\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\n\t\t\tif (p.pos.x < -myVar.halfDomain3DWidth)\n\t\t\t\tp.pos.x += myVar.domainSize3D.x;\n\n\t\t\telse if (p.pos.x >= myVar.halfDomain3DWidth)\n\t\t\t\tp.pos.x -= myVar.domainSize3D.x;\n\n\t\t\tif (p.pos.y < -myVar.halfDomain3DHeight)\n\t\t\t\tp.pos.y += myVar.domainSize3D.y;\n\t\t\telse if (p.pos.y >= myVar.halfDomain3DHeight)\n\t\t\t\tp.pos.y -= myVar.domainSize3D.y;\n\n\t\t\tif (p.pos.z < -myVar.halfDomain3DDepth)\n\t\t\t\tp.pos.z += myVar.domainSize3D.z;\n\t\t\telse if (p.pos.z >= myVar.halfDomain3DDepth)\n\t\t\t\tp.pos.z -= myVar.domainSize3D.z;\n\t\t}\n\n\t\tnetForce = physics3D.calculateForceFromGrid3DOld(currentParticles, myVar, p);\n\t\tp.acc = netForce / p.mass;\n\t\tp.vel += p.acc * (myVar.timeFactor * 0.5f);\n\n\t\tpredictedPath.push_back(p.pos);\n\t}\n\n\tfor (size_t i = 1; i < predictedPath.size(); ++i) {\n\t\tDrawLine3D(\n\t\t\t{ predictedPath[i - 1].x, predictedPath[i - 1].y, predictedPath[i - 1].z },\n\t\t\t{ predictedPath[i].x, predictedPath[i].y, predictedPath[i].z },\n\t\t\tWHITE\n\t\t);\n\t}\n}\n\nvoid ParticlesSpawning3D::drawGalaxyDisplay(UpdateParameters& myParam) {\n\n\tglm::mat4 rotationMatrix = glm::mat4(1.0f);\n\n\trotationMatrix = glm::rotate(rotationMatrix, diskAxisX * (PI / 180.0f), glm::vec3(1.0f, 0.0f, 0.0f));\n\n\trotationMatrix = glm::rotate(rotationMatrix, diskAxisY * (PI / 180.0f), glm::vec3(0.0f, 1.0f, 0.0f));\n\n\trotationMatrix = glm::rotate(rotationMatrix, diskAxisZ * (PI / 180.0f), glm::vec3(0.0f, 0.0f, 1.0f));\n\n\tglm::vec4 defaultNormal = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f);\n\tglm::vec4 rotatedNormal = rotationMatrix * defaultNormal;\n\n\tglm::vec3 direction = glm::vec3(rotatedNormal);\n\n\tglm::vec3 startPos = myParam.brush3D.brushPos - (direction * diskThickness);\n\tglm::vec3 endPos = myParam.brush3D.brushPos + (direction * diskThickness);\n\n\tVector3 raylibStart = { startPos.x, startPos.y, startPos.z };\n\tVector3 raylibEnd = { endPos.x, endPos.y, endPos.z };\n\n\tDrawCylinderWiresEx(\n\t\traylibStart,\n\t\traylibEnd,\n\t\touterRadius,\n\t\touterRadius,\n\t\t24,\n\t\t{ 255, 0, 0, 100 }\n\t);\n\n\tDrawCylinderWiresEx(\n\t\traylibStart,\n\t\traylibEnd,\n\t\tradiusCore,\n\t\tradiusCore,\n\t\t24,\n\t\t{ 255, 0, 0, 100 }\n\t);\n}\n"
  },
  {
    "path": "GalaxyEngine/src/Physics/SPH.cpp",
    "content": "#include \"Physics/SPH.h\"\n\nvoid SPH::flattenParticles(std::vector<ParticlePhysics>& pParticles) {\n\tsize_t particleCount = pParticles.size();\n\n\tposX.resize(particleCount);\n\tposY.resize(particleCount);\n\tpredPosX.resize(particleCount);\n\tpredPosY.resize(particleCount);\n\n\taccX.resize(particleCount);\n\taccY.resize(particleCount);\n\n\tvelX.resize(particleCount);\n\tvelY.resize(particleCount);\n\tprevVelX.resize(particleCount);\n\tprevVelY.resize(particleCount);\n\n\tsphMass.resize(particleCount);\n\tpress.resize(particleCount);\n\tpressFX.resize(particleCount);\n\tpressFY.resize(particleCount);\n\tstiff.resize(particleCount);\n\tvisc.resize(particleCount);\n\tdens.resize(particleCount);\n\tpredDens.resize(particleCount);\n\trestDens.resize(particleCount);\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < static_cast<int>(particleCount); i++)\n\t{\n\t\tconst auto& particle = pParticles[i];\n\n\t\tposX[i] = particle.pos.x;\n\t\tposY[i] = particle.pos.y;\n\n\t\tpredPosX[i] = particle.pos.x;\n\t\tpredPosY[i] = particle.pos.y;\n\n\t\taccX[i] = particle.acc.x;\n\t\taccY[i] = particle.acc.y;\n\n\t\tvelX[i] = particle.vel.x;\n\t\tvelY[i] = particle.vel.y;\n\n\t\tprevVelX[i] = particle.prevVel.x;\n\t\tprevVelY[i] = particle.prevVel.y;\n\n\t\tsphMass[i] = particle.sphMass;\n\n\t\tpress[i] = 0.0f;\n\t\tpressFX[i] = 0.0f;\n\t\tpressFY[i] = 0.0f;\n\t\tstiff[i] = 0.0f;\n\t\tvisc[i] = 0.0f;\n\t\tdens[i] = 0.0f;\n\t\tpredDens[i] = 0.0f;\n\t\trestDens[i] = 0.0f;\n\t}\n}\n\nvoid SPH::readFlattenBack(std::vector<ParticlePhysics>& pParticles) {\n\tsize_t particleCount = pParticles.size();\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < static_cast<int>(particleCount); i++)\n\t{\n\t\tauto& particle = pParticles[i];\n\n\t\tparticle.pos.x = posX[i];\n\t\tparticle.pos.y = posY[i];\n\n\t\tparticle.predPos.x = predPosX[i];\n\t\tparticle.predPos.y = predPosY[i];\n\n\t\tparticle.acc.x = accX[i];\n\t\tparticle.acc.y = accY[i];\n\n\t\tparticle.vel.x = velX[i];\n\t\tparticle.vel.y = velY[i];\n\n\t\tparticle.prevVel.x = prevVelX[i];\n\t\tparticle.prevVel.y = prevVelY[i];\n\n\t\tparticle.sphMass = sphMass[i];\n\n\t\tparticle.press = press[i];\n\t\tparticle.pressF.x = pressFX[i];\n\t\tparticle.pressF.y = pressFY[i];\n\t\tparticle.stiff = stiff[i];\n\t\tparticle.visc = visc[i];\n\t\tparticle.dens = dens[i];\n\t\tparticle.predDens = predDens[i];\n\t\tparticle.restDens = restDens[i];\n\t}\n}\n\nvoid SPH::computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector<glm::vec2>& sphForce, size_t& N) {\n\n\tconst float h = radiusMultiplier;\n\tconst float h2 = h * h;\n\n#pragma omp parallel for schedule(dynamic, 32)\n\tfor (size_t i = 0; i < N; ++i) {\n\n\t\tif (!myParam.rParticles[i].isSPH || myParam.rParticles[i].isPinned || myParam.rParticles[i].isBeingDrawn) continue;\n\n\t\tauto& pi = myParam.pParticles[i];\n\n\t\tstd::vector<size_t> neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.pos);\n\n\t\tfor (size_t j : neighborIndices) {\n\t\t\tsize_t pjIdx = j;\n\n\t\t\tif (!myParam.rParticles[pjIdx].isSPH || myParam.rParticles[pjIdx].isBeingDrawn) continue;\n\n\t\t\tif (pjIdx == i) continue;\n\t\t\tauto& pj = myParam.pParticles[pjIdx];\n\n\t\t\tglm::vec2 d = { pj.pos.x - pi.pos.x, pj.pos.y - pi.pos.y };\n\t\t\tfloat   rSq = d.x * d.x + d.y * d.y;\n\t\t\tif (rSq >= h2) continue;\n\n\t\t\tfloat r = sqrtf(std::max(rSq, 1e-6f));\n\t\t\tglm::vec2 nr = { d.x / r, d.y / r };\n\n\t\t\tfloat mJ = pj.sphMass * myVar.mass;\n\n\t\t\tfloat lapW = smoothingKernelLaplacian(r, h);\n\t\t\tglm::vec2 viscF = {\n\t\t\t\tmyVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.x - pi.vel.x),\n\t\t\t\tmyVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.y - pi.vel.y)\n\t\t\t};\n\n\t\t\tfloat cohCoef = myVar.cohesionCoefficient * pi.cohesion;\n\t\t\tfloat cohFactor = smoothingKernelCohesion(r, h);\n\t\t\tglm::vec2 cohF = { cohCoef * mJ * cohFactor * nr.x,\n\t\t\t\t\t\t\t\tcohCoef * mJ * cohFactor * nr.y };\n\n#pragma omp atomic\n\t\t\tsphForce[i].x += viscF.x + cohF.x;\n#pragma omp atomic\n\t\t\tsphForce[i].y += viscF.y + cohF.y;\n#pragma omp atomic\n\t\t\tsphForce[pjIdx].x -= viscF.x + cohF.x;\n#pragma omp atomic\n\t\t\tsphForce[pjIdx].y -= viscF.y + cohF.y;\n\t\t}\n\t}\n}\n\nvoid SPH::PCISPH(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tsize_t N = myParam.pParticles.size();\n\n\tconst float predictionCoeff = 0.5f * myVar.timeFactor * myVar.timeFactor;\n\n\tstd::vector<glm::vec2> sphForce(N, { 0.0f, 0.0f });\n\n\tcomputeViscCohesionForces(myVar, myParam, sphForce, N);\n\n\tfor (size_t i = 0; i < N; ++i) {\n\t\tmyParam.pParticles[i].press = 0.0f;\n\t\tmyParam.pParticles[i].pressF = { 0.0f, 0.0f };\n\t}\n\n\t//float rhoError = 0.0f;\n\titer = 0;\n\n\tdo {\n\t\t//float maxRhoErr = 0.0f;\n\n#pragma omp parallel for schedule(static)\n\t\tfor (size_t i = 0; i < N; ++i) {\n\t\t\tif (!myParam.rParticles[i].isSPH || myParam.rParticles[i].isBeingDrawn) continue;\n\n\t\t\tauto& p = myParam.pParticles[i];\n\n\t\t\tglm::vec2 displacement = (sphForce[i] / p.sphMass) * predictionCoeff;\n\t\t\tp.predPos = p.pos + displacement;\n\t\t}\n\n#pragma omp parallel for schedule(dynamic, 16) /*reduction(max:maxRhoErr)*/\n\t\tfor (size_t i = 0; i < N; ++i) {\n\n\t\t\tif (!myParam.rParticles[i].isSPH || myParam.rParticles[i].isBeingDrawn) continue;\n\n\t\t\tauto& pi = myParam.pParticles[i];\n\t\t\tpi.predDens = 0.0f;\n\n\t\t\tstd::vector<size_t> neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.predPos);\n\n\t\t\tfor (size_t j : neighborIndices) {\n\t\t\t\tsize_t pjIdx = j;\n\n\t\t\t\t//if (pjIdx == i) continue;\n\t\t\t\tif (!myParam.rParticles[pjIdx].isSPH || myParam.rParticles[pjIdx].isBeingDrawn) continue;\n\n\t\t\t\tauto& pj = myParam.pParticles[pjIdx];\n\t\t\t\tglm::vec2 dr = pi.predPos - pj.predPos;\n\t\t\t\tfloat rrSq = dr.x * dr.x + dr.y * dr.y;\n\n\t\t\t\tif (rrSq >= radiusMultiplier * radiusMultiplier) continue;\n\n\t\t\t\tfloat rr = sqrtf(rrSq);\n\t\t\t\tfloat mJ = pj.sphMass * myVar.mass;\n\t\t\t\tfloat rho0 = 0.5f * (pi.restDens + pj.restDens);\n\t\t\t\tpi.predDens += mJ * smoothingKernel(rr, radiusMultiplier) / rho0;\n\t\t\t}\n\n\n\n\t\t\tfloat err = pi.predDens - pi.restDens;\n\t\t\tpi.pressTmp = myVar.delta * err;\n\t\t\tif (pi.pressTmp < 0.0f) pi.pressTmp = 0.0f;\n\n\t\t\t//maxRhoErr = std::max(maxRhoErr, std::abs(err));\n\t\t\tpi.press += pi.pressTmp * pi.stiff * myVar.stiffMultiplier;\n\t\t}\n\n#pragma omp parallel for schedule(dynamic, 32)\n\t\tfor (size_t i = 0; i < N; ++i) {\n\t\t\tif (!myParam.rParticles[i].isSPH || myParam.rParticles[i].isBeingDrawn) continue;\n\n\t\t\tauto& pi = myParam.pParticles[i];\n\n\t\t\tstd::vector<size_t> neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.predPos);\n\n\t\t\tfor (size_t j : neighborIndices) {\n\t\t\t\tsize_t pjIdx = j;\n\n\t\t\t\tif (pjIdx == i) continue;\n\t\t\t\tif (!myParam.rParticles[pjIdx].isSPH || myParam.rParticles[pjIdx].isBeingDrawn) continue;\n\n\t\t\t\tauto& pj = myParam.pParticles[pjIdx];\n\t\t\t\tglm::vec2 dr = pi.predPos - pj.predPos;\n\t\t\t\tfloat rr = sqrtf(dr.x * dr.x + dr.y * dr.y);\n\n\t\t\t\tif (rr < 1e-5f || rr >= radiusMultiplier) continue;\n\n\t\t\t\tfloat gradW = spikyKernelDerivative(rr, radiusMultiplier);\n\t\t\t\tglm::vec2 nrm = { dr.x / rr, dr.y / rr };\n\t\t\t\tfloat avgP = 0.5f * (pi.press + pj.press);\n\t\t\t\tfloat avgD = 0.5f * (pi.predDens + pj.predDens);\n\t\t\t\tfloat mag = -(pi.sphMass * myVar.mass + pj.sphMass * myVar.mass) * avgP / std::max(avgD, 0.01f);\n\n\t\t\t\tfloat massRatio = std::max(pi.sphMass, pj.sphMass) / std::min(pi.sphMass, pj.sphMass);\n\t\t\t\tfloat scale = std::min(1.0f, 8.0f / massRatio);\n\t\t\t\tmag *= scale;\n\n\t\t\t\tglm::vec2 pF = { mag * gradW * nrm.x, mag * gradW * nrm.y };\n\n#pragma omp atomic\n\t\t\t\tsphForce[i].x += pF.x;\n#pragma omp atomic\n\t\t\t\tsphForce[i].y += pF.y;\n#pragma omp atomic\n\t\t\t\tsphForce[pjIdx].x -= pF.x;\n#pragma omp atomic\n\t\t\t\tsphForce[pjIdx].y -= pF.y;\n\t\t\t}\n\n\n\t\t}\n\n\t\t//rhoError = maxRhoErr;\n\t\t++iter;\n\n\t} while (iter < maxIter /*&& rhoError > densTolerance */);\n\n#pragma omp parallel for schedule(static)\n\tfor (size_t i = 0; i < N; ++i) {\n\t\tauto& p = myParam.pParticles[i];\n\n\t\tp.pressF = sphForce[i] / p.sphMass;\n\n\t\tif (!myParam.rParticles[i].isPinned) {\n\t\t\tp.acc += p.pressF;\n\t\t}\n\t}\n}\n\nvoid SPH::groundModeBoundary(std::vector<ParticlePhysics>& pParticles,\n\tstd::vector<ParticleRendering>& rParticles,\n\tglm::vec2 domainSize, UpdateVariables& myVar) {\n\n#pragma omp parallel for\n\tfor (size_t i = 0; i < pParticles.size(); ++i) {\n\t\tif (rParticles[i].isPinned) continue;\n\n\t\tauto& p = pParticles[i];\n\t\tp.acc.y += myVar.verticalGravity;\n\n\t\t// Left wall\n\t\tif (p.pos.x - radiusMultiplier < 0.0f) {\n\t\t\tp.vel.x *= boundDamping;\n\t\t\tp.vel.y *= 1.0f - myVar.boundaryFriction;\n\t\t\tp.pos.x = radiusMultiplier;\n\t\t}\n\n\t\t// Right wall\n\t\tif (p.pos.x + radiusMultiplier > domainSize.x) {\n\t\t\tp.vel.x *= boundDamping;\n\t\t\tp.vel.y *= 1.0f - myVar.boundaryFriction;\n\t\t\tp.pos.x = domainSize.x - radiusMultiplier;\n\t\t}\n\n\t\t// Bottom wall\n\t\tif (p.pos.y - radiusMultiplier < 0.0f) {\n\t\t\tp.vel.y *= boundDamping;\n\t\t\tp.vel.x *= 1.0f - myVar.boundaryFriction;\n\t\t\tp.pos.y = radiusMultiplier;\n\t\t}\n\n\t\t// Top wall\n\t\tif (p.pos.y + radiusMultiplier > domainSize.y) {\n\t\t\tp.vel.y *= boundDamping;\n\t\t\tp.vel.x *= 1.0f - myVar.boundaryFriction;\n\t\t\tp.pos.y = domainSize.y - radiusMultiplier;\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "GalaxyEngine/src/Physics/SPH3D.cpp",
    "content": "#include \"Physics/SPH3D.h\"\n\nvoid SPH3D::flattenParticles(std::vector<ParticlePhysics>& pParticles) {\n\tsize_t particleCount = pParticles.size();\n\n\tposX.resize(particleCount);\n\tposY.resize(particleCount);\n\tpredPosX.resize(particleCount);\n\tpredPosY.resize(particleCount);\n\n\taccX.resize(particleCount);\n\taccY.resize(particleCount);\n\n\tvelX.resize(particleCount);\n\tvelY.resize(particleCount);\n\tprevVelX.resize(particleCount);\n\tprevVelY.resize(particleCount);\n\n\tsphMass.resize(particleCount);\n\tpress.resize(particleCount);\n\tpressFX.resize(particleCount);\n\tpressFY.resize(particleCount);\n\tstiff.resize(particleCount);\n\tvisc.resize(particleCount);\n\tdens.resize(particleCount);\n\tpredDens.resize(particleCount);\n\trestDens.resize(particleCount);\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < static_cast<int>(particleCount); i++)\n\t{\n\t\tconst auto& particle = pParticles[i];\n\n\t\tposX[i] = particle.pos.x;\n\t\tposY[i] = particle.pos.y;\n\n\t\tpredPosX[i] = particle.pos.x;\n\t\tpredPosY[i] = particle.pos.y;\n\n\t\taccX[i] = particle.acc.x;\n\t\taccY[i] = particle.acc.y;\n\n\t\tvelX[i] = particle.vel.x;\n\t\tvelY[i] = particle.vel.y;\n\n\t\tprevVelX[i] = particle.prevVel.x;\n\t\tprevVelY[i] = particle.prevVel.y;\n\n\t\tsphMass[i] = particle.sphMass;\n\n\t\tpress[i] = 0.0f;\n\t\tpressFX[i] = 0.0f;\n\t\tpressFY[i] = 0.0f;\n\t\tstiff[i] = 0.0f;\n\t\tvisc[i] = 0.0f;\n\t\tdens[i] = 0.0f;\n\t\tpredDens[i] = 0.0f;\n\t\trestDens[i] = 0.0f;\n\t}\n}\n\nvoid SPH3D::readFlattenBack(std::vector<ParticlePhysics>& pParticles) {\n\tsize_t particleCount = pParticles.size();\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < static_cast<int>(particleCount); i++)\n\t{\n\t\tauto& particle = pParticles[i];\n\n\t\tparticle.pos.x = posX[i];\n\t\tparticle.pos.y = posY[i];\n\n\t\tparticle.predPos.x = predPosX[i];\n\t\tparticle.predPos.y = predPosY[i];\n\n\t\tparticle.acc.x = accX[i];\n\t\tparticle.acc.y = accY[i];\n\n\t\tparticle.vel.x = velX[i];\n\t\tparticle.vel.y = velY[i];\n\n\t\tparticle.prevVel.x = prevVelX[i];\n\t\tparticle.prevVel.y = prevVelY[i];\n\n\t\tparticle.sphMass = sphMass[i];\n\n\t\tparticle.press = press[i];\n\t\tparticle.pressF.x = pressFX[i];\n\t\tparticle.pressF.y = pressFY[i];\n\t\tparticle.stiff = stiff[i];\n\t\tparticle.visc = visc[i];\n\t\tparticle.dens = dens[i];\n\t\tparticle.predDens = predDens[i];\n\t\tparticle.restDens = restDens[i];\n\t}\n}\n\nvoid SPH3D::computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector<glm::vec3>& sphForce, size_t& N) {\n\n\tconst float h = radiusMultiplier;\n\tconst float h2 = h * h;\n\n#pragma omp parallel for schedule(dynamic, 32)\n\tfor (size_t i = 0; i < N; ++i) {\n\n\t\tif (!myParam.rParticles3D[i].isSPH || myParam.rParticles3D[i].isPinned || myParam.rParticles3D[i].isBeingDrawn) continue;\n\n\t\tauto& pi = myParam.pParticles3D[i];\n\n\t\tstd::vector<size_t> neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.pos);\n\n\t\tfor (size_t j : neighborIndices) {\n\t\t\tsize_t pjIdx = j;\n\n\t\t\tif (!myParam.rParticles3D[pjIdx].isSPH || myParam.rParticles3D[pjIdx].isBeingDrawn) continue;\n\n\t\t\tif (pjIdx == i) continue;\n\n\t\t\tauto& pj = myParam.pParticles3D[pjIdx];\n\n\t\t\tglm::vec3 d = {\n\t\t\t\tpj.pos.x - pi.pos.x,\n\t\t\t\tpj.pos.y - pi.pos.y,\n\t\t\t\tpj.pos.z - pi.pos.z\n\t\t\t};\n\n\t\t\tfloat rSq = d.x * d.x + d.y * d.y + d.z * d.z;\n\n\t\t\tif (rSq >= h2) continue;\n\n\t\t\tfloat r = sqrtf(std::max(rSq, 1e-6f));\n\n\t\t\tglm::vec3 nr = { d.x / r, d.y / r, d.z / r };\n\n\t\t\tfloat mJ = pj.sphMass * myVar.mass;\n\n\t\t\tfloat lapW = smoothingKernelLaplacian(r, h);\n\n\t\t\tglm::vec3 viscF = {\n\t\t\t\tmyVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.x - pi.vel.x),\n\t\t\t\tmyVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.y - pi.vel.y),\n\t\t\t\tmyVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.z - pi.vel.z)\n\t\t\t};\n\n\t\t\tfloat cohCoef = myVar.cohesionCoefficient * pi.cohesion;\n\t\t\tfloat cohFactor = smoothingKernelCohesion(r, h);\n\n\t\t\tglm::vec3 cohF = {\n\t\t\t\tcohCoef * mJ * cohFactor * nr.x,\n\t\t\t\tcohCoef * mJ * cohFactor * nr.y,\n\t\t\t\tcohCoef * mJ * cohFactor * nr.z\n\t\t\t};\n\n\n#pragma omp atomic\n\t\t\tsphForce[i].x += viscF.x + cohF.x;\n#pragma omp atomic\n\t\t\tsphForce[i].y += viscF.y + cohF.y;\n#pragma omp atomic\n\t\t\tsphForce[i].z += viscF.z + cohF.z;\n\n#pragma omp atomic\n\t\t\tsphForce[pjIdx].x -= viscF.x + cohF.x;\n#pragma omp atomic\n\t\t\tsphForce[pjIdx].y -= viscF.y + cohF.y;\n#pragma omp atomic\n\t\t\tsphForce[pjIdx].z -= viscF.z + cohF.z;\n\t\t}\n\t}\n}\n\nvoid SPH3D::PCISPH(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tsize_t N = myParam.pParticles3D.size();\n\n\tconst float predictionCoeff = 0.5f * myVar.timeFactor * myVar.timeFactor;\n\n\tstd::vector<glm::vec3> sphForce(N, { 0.0f, 0.0f, 0.0f });\n\n\tcomputeViscCohesionForces(myVar, myParam, sphForce, N);\n\n\tfor (size_t i = 0; i < N; ++i) {\n\t\tmyParam.pParticles3D[i].press = 0.0f;\n\t\tmyParam.pParticles3D[i].pressF = { 0.0f, 0.0f, 0.0f };\n\t}\n\n\tsize_t iter = 0;\n\n\tdo {\n#pragma omp parallel for schedule(static)\n\t\tfor (size_t i = 0; i < N; ++i) {\n\n\t\t\tif (!myParam.rParticles3D[i].isSPH || myParam.rParticles3D[i].isBeingDrawn) continue;\n\n\t\t\tauto& p = myParam.pParticles3D[i];\n\n\t\t\tglm::vec3 displacement = (sphForce[i] / p.sphMass) * predictionCoeff;\n\t\t\tp.predPos = p.pos + displacement;\n\t\t}\n\n#pragma omp parallel for schedule(dynamic, 16)\n\t\tfor (size_t i = 0; i < N; ++i) {\n\n\t\t\tif (!myParam.rParticles3D[i].isSPH || myParam.rParticles3D[i].isBeingDrawn) continue;\n\n\t\t\tauto& pi = myParam.pParticles3D[i];\n\t\t\tpi.predDens = 0.0f;\n\n\t\t\tstd::vector<size_t> neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.predPos);\n\n\t\t\tfor (size_t j : neighborIndices) {\n\t\t\t\tsize_t pjIdx = j;\n\n\t\t\t\tif (!myParam.rParticles3D[pjIdx].isSPH || myParam.rParticles3D[pjIdx].isBeingDrawn) continue;\n\n\t\t\t\tauto& pj = myParam.pParticles3D[pjIdx];\n\n\t\t\t\tglm::vec3 dr = pi.predPos - pj.predPos;\n\t\t\t\tfloat rrSq = dr.x * dr.x + dr.y * dr.y + dr.z * dr.z;\n\n\t\t\t\tif (rrSq >= radiusMultiplier * radiusMultiplier) continue;\n\n\t\t\t\tfloat rr = sqrtf(rrSq);\n\t\t\t\tfloat mJ = pj.sphMass * (myVar.mass * 2.0f);\n\t\t\t\tfloat rho0 = 0.5f * (pi.restDens + pj.restDens);\n\n\t\t\t\tpi.predDens += mJ * smoothingKernel(rr, radiusMultiplier) / rho0;\n\t\t\t}\n\n\t\t\tfloat err = pi.predDens - pi.restDens;\n\t\t\tpi.pressTmp = myVar.delta * err;\n\t\t\tif (pi.pressTmp < 0.0f) pi.pressTmp = 0.0f;\n\n\t\t\tpi.press += pi.pressTmp * pi.stiff * myVar.stiffMultiplier;\n\t\t}\n\n#pragma omp parallel for schedule(dynamic, 32)\n\t\tfor (size_t i = 0; i < N; ++i) {\n\t\t\tif (!myParam.rParticles3D[i].isSPH || myParam.rParticles3D[i].isBeingDrawn) continue;\n\n\t\t\tauto& pi = myParam.pParticles3D[i];\n\n\t\t\tstd::vector<size_t> neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.predPos);\n\n\t\t\tfor (size_t j : neighborIndices) {\n\t\t\t\tsize_t pjIdx = j;\n\n\t\t\t\tif (pjIdx == i) continue;\n\n\t\t\t\tif (!myParam.rParticles3D[pjIdx].isSPH || myParam.rParticles3D[pjIdx].isBeingDrawn) continue;\n\n\t\t\t\tauto& pj = myParam.pParticles3D[pjIdx];\n\n\t\t\t\tglm::vec3 dr = pi.predPos - pj.predPos;\n\t\t\t\tfloat rr = sqrtf(dr.x * dr.x + dr.y * dr.y + dr.z * dr.z);\n\n\t\t\t\tif (rr < 1e-5f || rr >= radiusMultiplier) continue;\n\n\t\t\t\tfloat gradW = spikyKernelDerivative(rr, radiusMultiplier);\n\n\t\t\t\tglm::vec3 nrm = { dr.x / rr, dr.y / rr, dr.z / rr };\n\n\t\t\t\tfloat avgP = 0.5f * (pi.press + pj.press);\n\t\t\t\tfloat avgD = 0.5f * (pi.predDens + pj.predDens);\n\n\t\t\t\tfloat mag = -(pi.sphMass * (myVar.mass * 2.0f) + pj.sphMass * (myVar.mass * 2.0f)) * avgP / std::max(avgD, 0.01f);\n\n\t\t\t\tfloat massRatio = std::max(pi.sphMass, pj.sphMass) / std::min(pi.sphMass, pj.sphMass);\n\t\t\t\tfloat scale = std::min(1.0f, 8.0f / massRatio);\n\t\t\t\tmag *= scale;\n\n\t\t\t\tglm::vec3 pF = {\n\t\t\t\t\tmag * gradW * nrm.x,\n\t\t\t\t\tmag * gradW * nrm.y,\n\t\t\t\t\tmag * gradW * nrm.z\n\t\t\t\t};\n\n#pragma omp atomic\n\t\t\t\tsphForce[i].x += pF.x;\n#pragma omp atomic\n\t\t\t\tsphForce[i].y += pF.y;\n#pragma omp atomic\n\t\t\t\tsphForce[i].z += pF.z;\n\n#pragma omp atomic\n\t\t\t\tsphForce[pjIdx].x -= pF.x;\n#pragma omp atomic\n\t\t\t\tsphForce[pjIdx].y -= pF.y;\n#pragma omp atomic\n\t\t\t\tsphForce[pjIdx].z -= pF.z;\n\t\t\t}\n\t\t}\n\n\t\t++iter;\n\n\t} while (iter < maxIter);\n\n#pragma omp parallel for schedule(static)\n\tfor (size_t i = 0; i < N; ++i) {\n\t\tauto& p = myParam.pParticles3D[i];\n\n\t\tp.pressF = sphForce[i] / p.sphMass;\n\n\t\tif (!myParam.rParticles3D[i].isPinned) {\n\t\t\tp.acc += p.pressF;\n\t\t}\n\t}\n}\n\nvoid SPH3D::groundModeBoundary(std::vector<ParticlePhysics3D>& pParticles,\n\tstd::vector<ParticleRendering3D>& rParticles,\n\tglm::vec3 domainSize, UpdateVariables& myVar) {\n\n#pragma omp parallel for\n\tfor (size_t i = 0; i < pParticles.size(); ++i) {\n\t\tif (rParticles[i].isPinned) continue;\n\t\tauto& p = pParticles[i];\n\t\tp.acc.y -= myVar.verticalGravity;\n\n\t\tbool hitX = false; \n\t\tbool hitY = false;\n\t\tbool hitZ = false;\n\n\t\t// Left wall\n\t\tif (p.pos.x - radiusMultiplier < -domainSize.x / 2.0f) {\n\t\t\tp.vel.x *= boundDamping;\n\t\t\tp.pos.x = -domainSize.x / 2.0f + radiusMultiplier;\n\t\t\thitX = true;\n\t\t}\n\t\t// Right wall\n\t\tif (p.pos.x + radiusMultiplier > domainSize.x / 2.0f) {\n\t\t\tp.vel.x *= boundDamping;\n\t\t\tp.pos.x = domainSize.x / 2.0f - radiusMultiplier;\n\t\t\thitX = true;\n\t\t}\n\t\t// Bottom wall\n\t\tif (p.pos.y - radiusMultiplier < -domainSize.y / 2.0f) {\n\t\t\tp.vel.y *= boundDamping;\n\t\t\tp.pos.y = -domainSize.y / 2.0f + radiusMultiplier;\n\t\t\thitY = true;\n\t\t}\n\t\t// Top wall\n\t\tif (p.pos.y + radiusMultiplier > domainSize.y / 2.0f) {\n\t\t\tp.vel.y *= boundDamping;\n\t\t\tp.pos.y = domainSize.y / 2.0f - radiusMultiplier;\n\t\t\thitY = true;\n\t\t}\n\t\t// Front wall\n\t\tif (p.pos.z - radiusMultiplier < -domainSize.z / 2.0f) {\n\t\t\tp.vel.z *= boundDamping;\n\t\t\tp.pos.z = -domainSize.z / 2.0f + radiusMultiplier;\n\t\t\thitZ = true;\n\t\t}\n\t\t// Back wall\n\t\tif (p.pos.z + radiusMultiplier > domainSize.z / 2.0f) {\n\t\t\tp.vel.z *= boundDamping;\n\t\t\tp.pos.z = domainSize.z / 2.0f - radiusMultiplier;\n\t\t\thitZ = true;\n\t\t}\n\n\t\tif (hitX) { p.vel.y *= 1.0f - myVar.boundaryFriction; p.vel.z *= 1.0f - myVar.boundaryFriction; }\n\t\tif (hitY) { p.vel.x *= 1.0f - myVar.boundaryFriction; p.vel.z *= 1.0f - myVar.boundaryFriction; }\n\t\tif (hitZ) { p.vel.x *= 1.0f - myVar.boundaryFriction; p.vel.y *= 1.0f - myVar.boundaryFriction; }\n\t}\n}\n\n"
  },
  {
    "path": "GalaxyEngine/src/Physics/light.cpp",
    "content": "#include \"Physics/light.h\"\n\nvoid Lighting::createWall(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tif (IO::mousePress(0) && myVar.toolWall) {\n\n\t\tImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor);\n\n\t\tcolorConvert.w = wallEmissionGain;\n\n\t\tColor emissionColorFinal = rlImGuiColors::Convert(colorConvert);\n\n\t\twalls.emplace_back(mouseWorldPos, mouseWorldPos, false, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal,\n\t\t\twallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion);\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolWall) {\n\t\tif (walls.back().isBeingSpawned) {\n\t\t\twalls.back().vB = mouseWorldPos;\n\n\t\t\tcalculateWallNormal(walls.back());\n\n\t\t\tshouldRender = true;\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolWall) {\n\t\tif (!walls.empty() && walls.back().isBeingSpawned) {\n\t\t\tif (glm::length(walls.back().vB - walls.back().vA) == 0.0f) {\n\t\t\t\twalls.pop_back();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\twalls.back().isBeingSpawned = false;\n\t\t}\n\t}\n}\n\nvoid Lighting::createShape(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\t// ---- Circle ---- //\n\tif (IO::mousePress(0) && myVar.toolCircle) {\n\n\t\tImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor);\n\n\t\tcolorConvert.w = wallEmissionGain;\n\n\t\tColor emissionColorFinal = rlImGuiColors::Convert(colorConvert);\n\n\t\tshapes.emplace_back(circle, mouseWorldPos, mouseWorldPos, &walls, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal,\n\t\t\twallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion);\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolCircle) {\n\t\tif (shapes.back().isBeingSpawned) {\n\t\t\tshapes.back().h2 = mouseWorldPos;\n\n\t\t\tshapes.back().makeShape();\n\n\t\t\tshapes.back().calculateWallsNormals();\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolCircle) {\n\t\tif (!shapes.empty() && shapes.back().isBeingSpawned) {\n\t\t\tif (glm::length(shapes.back().h2 - shapes.back().h1) == 0.0f) {\n\t\t\t\tshapes.pop_back();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tshapes.back().isBeingSpawned = false;\n\n\t\t\tshapes.back().helpers.push_back(shapes.back().h1);\n\t\t\tshapes.back().helpers.push_back(shapes.back().h2);\n\n\n\t\t\tshapes.back().createShapeFlag = true;\n\t\t\tshapes.back().makeShape();\n\t\t}\n\t\tshouldRender = true;\n\t}\n\n\t// ---- Draw Shape ---- //\n\tif (IO::mousePress(0) && myVar.toolDrawShape) {\n\n\t\tImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor);\n\n\t\tcolorConvert.w = wallEmissionGain;\n\n\t\tColor emissionColorFinal = rlImGuiColors::Convert(colorConvert);\n\n\t\tshapes.emplace_back(draw, mouseWorldPos, mouseWorldPos, &walls, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal,\n\t\t\twallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion);\n\n\t\tshapes.back().helpers.push_back(mouseWorldPos);\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolDrawShape) {\n\t\tif (shapes.back().isBeingSpawned) {\n\t\t\tshapes.back().h2 = mouseWorldPos;\n\n\t\t\tshapes.back().makeShape();\n\n\t\t\tshapes.back().calculateWallsNormals();\n\t\t}\n\n\t\tshouldRender = true;\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolDrawShape) {\n\t\tif (!shapes.empty() && shapes.back().isBeingSpawned) {\n\t\t\tif (glm::length(shapes.back().h2 - shapes.back().h1) == 0.0f) {\n\t\t\t\tshapes.pop_back();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tshapes.back().isBeingSpawned = false;\n\n\t\t\tshapes.back().makeShape();\n\n\t\t\tconst Wall* lastWall = getWallById(walls, shapes.back().myWallIds.back());\n\t\t\tconst Wall* firstWall = getWallById(walls, shapes.back().myWallIds.front());\n\n\t\t\tif (!lastWall || !firstWall) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst glm::vec2& lastPoint = lastWall->vB;\n\t\t\tconst glm::vec2& firstPoint = firstWall->vA;\n\n\t\t\tImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor);\n\n\t\t\tcolorConvert.w = wallEmissionGain;\n\n\t\t\tColor emissionColorFinal = rlImGuiColors::Convert(colorConvert);\n\n\t\t\twalls.emplace_back(lastPoint, firstPoint, true, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal,\n\t\t\t\twallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion);\n\n\t\t\twalls.back().shapeId = shapes.back().id;\n\t\t\tshapes.back().myWallIds.push_back(walls.back().id);\n\n\t\t\tshapes.back().relaxShape(shapeRelaxIter, shapeRelaxFactor);\n\n\t\t\tshapes.back().calculateWallsNormals();\n\t\t}\n\t\tshouldRender = true;\n\t}\n\n\t// ---- Lens ---- //\n\tif (IO::mousePress(0) && myVar.toolLens && firstHelper) {\n\n\t\tImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor);\n\n\t\tcolorConvert.w = wallEmissionGain;\n\n\t\tColor emissionColorFinal = rlImGuiColors::Convert(colorConvert);\n\n\t\tshapes.emplace_back(lens, mouseWorldPos, mouseWorldPos, &walls, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal,\n\t\t\twallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion);\n\n\t\tshapes.back().symmetricalLens = symmetricalLens;\n\n\t\tisCreatingLens = true;\n\t}\n\telse if (IO::mousePress(0) && myVar.toolLens) {\n\n\t\tif (shapes.back().helpers.size() == 2) {\n\t\t\tshapes.back().thirdHelper = true;\n\t\t}\n\n\t\tif (shapes.back().helpers.size() == 3) {\n\t\t\tshapes.back().fourthHelper = true;\n\t\t\tfirstHelper = true;\n\t\t}\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolLens && firstHelper) {\n\t\tif (shapes.back().isBeingSpawned) {\n\t\t\tshapes.back().h2 = mouseWorldPos;\n\t\t}\n\t}\n\telse if (isCreatingLens) {\n\t\tif (shapes.back().isBeingSpawned) {\n\t\t\tshapes.back().h2 = mouseWorldPos;\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolLens) {\n\t\tif (!shapes.empty() && shapes.back().isBeingSpawned) {\n\t\t\tif (glm::length(shapes.back().h2 - shapes.back().h1) == 0.0f) {\n\t\t\t\tshapes.pop_back();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (shapes.back().helpers.size() == 1) {\n\t\t\t\tshapes.back().secondHelper = true;\n\t\t\t}\n\n\t\t\tfirstHelper = false;\n\t\t}\n\n\t\tshouldRender = true;\n\t}\n\n\tif (!shapes.empty()) {\n\t\tif (shapes.back().isBeingSpawned && shapes.back().shapeType == lens) {\n\t\t\tshapes.back().makeShape();\n\t\t}\n\t}\n\telse {\n\t\tfirstHelper = true;\n\t}\n}\n\nvoid Lighting::createPointLight(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tif (IO::mousePress(0) && myVar.toolPointLight) {\n\n\t\tImVec4 colorConvert = rlImGuiColors::Convert(lightColor);\n\n\t\tcolorConvert.w = lightGain;\n\n\t\tColor lightColorFinal = rlImGuiColors::Convert(colorConvert);\n\n\t\tpointLights.emplace_back(mouseWorldPos, lightColorFinal);\n\n\t\tshouldRender = true;\n\t}\n}\n\nvoid Lighting::createAreaLight(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tif (IO::mousePress(0) && myVar.toolAreaLight) {\n\n\t\tImVec4 colorConvert = rlImGuiColors::Convert(lightColor);\n\n\t\tcolorConvert.w = lightGain;\n\n\t\tColor lightColorFinal = rlImGuiColors::Convert(colorConvert);\n\n\t\tareaLights.emplace_back(mouseWorldPos, mouseWorldPos, lightColorFinal, lightSpread);\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolAreaLight) {\n\t\tif (areaLights.back().isBeingSpawned) {\n\t\t\tareaLights.back().vB = mouseWorldPos;\n\n\t\t\tshouldRender = true;\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolAreaLight) {\n\t\tif (!areaLights.empty() && areaLights.back().isBeingSpawned) {\n\t\t\tif (glm::length(areaLights.back().vB - areaLights.back().vA) == 0.0f) {\n\t\t\t\tareaLights.pop_back();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tareaLights.back().isBeingSpawned = false;\n\t\t}\n\t}\n}\n\nvoid Lighting::createConeLight(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tif (IO::mousePress(0) && myVar.toolConeLight) {\n\n\t\tImVec4 colorConvert = rlImGuiColors::Convert(lightColor);\n\n\t\tcolorConvert.w = lightGain;\n\n\t\tColor lightColorFinal = rlImGuiColors::Convert(colorConvert);\n\n\t\tconeLights.emplace_back(mouseWorldPos, mouseWorldPos, lightColorFinal, lightSpread);\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolConeLight) {\n\t\tif (coneLights.back().isBeingSpawned) {\n\t\t\tconeLights.back().vB = mouseWorldPos;\n\n\t\t\tshouldRender = true;\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolConeLight) {\n\t\tif (!coneLights.empty() && coneLights.back().isBeingSpawned) {\n\t\t\tif (glm::length(coneLights.back().vB - coneLights.back().vA) == 0.0f) {\n\t\t\t\tconeLights.pop_back();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconeLights.back().isBeingSpawned = false;\n\t\t}\n\t}\n}\n\nvoid Lighting::movePointLights(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (IO::mousePress(0) && myVar.toolMoveOptics) {\n\t\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\t\tglm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y);\n\t\tglm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom);\n\n\t\tfor (PointLight& pointLight : pointLights) {\n\n\t\t\tglm::vec2 d = pointLight.pos - mouseWorldPos;\n\n\t\t\tfloat dist = glm::length(d);\n\n\t\t\tif (dist <= myParam.brush.brushRadius) {\n\t\t\t\tpointLight.isBeingMoved = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (PointLight& pointLight : pointLights) {\n\n\t\tglm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y);\n\t\tglm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom);\n\n\t\tif (pointLight.isBeingMoved) {\n\t\t\tpointLight.pos += scaledDelta;\n\n\t\t\tshouldRender = true;\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolMoveOptics) {\n\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\tpointLight.isBeingMoved = false;\n\t\t}\n\t}\n}\n\nvoid Lighting::moveAreaLights(UpdateVariables& myVar, UpdateParameters& myParam) {\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tglm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y);\n\tglm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom);\n\n\tif (IO::mousePress(0) && myVar.toolMoveOptics) {\n\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\tglm::vec2 dA = areaLight.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = areaLight.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= myParam.brush.brushRadius) {\n\t\t\t\tareaLight.vAisBeingMoved = true;\n\t\t\t}\n\t\t\tif (distB <= myParam.brush.brushRadius) {\n\t\t\t\tareaLight.vBisBeingMoved = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolMoveOptics) {\n\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\tif (areaLight.vAisBeingMoved) {\n\t\t\t\tareaLight.vA += scaledDelta;\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t\tif (areaLight.vBisBeingMoved) {\n\t\t\t\tareaLight.vB += scaledDelta;\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolMoveOptics) {\n\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\tareaLight.vAisBeingMoved = false;\n\t\t\tareaLight.vBisBeingMoved = false;\n\t\t}\n\t}\n}\n\nvoid Lighting::moveConeLights(UpdateVariables& myVar, UpdateParameters& myParam) {\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tglm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y);\n\tglm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom);\n\n\tif (IO::mousePress(0) && myVar.toolMoveOptics) {\n\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\tglm::vec2 dA = coneLight.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = coneLight.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= myParam.brush.brushRadius) {\n\t\t\t\tconeLight.vAisBeingMoved = true;\n\t\t\t}\n\t\t\tif (distB <= myParam.brush.brushRadius) {\n\t\t\t\tconeLight.vBisBeingMoved = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolMoveOptics) {\n\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\tif (coneLight.vAisBeingMoved) {\n\t\t\t\tconeLight.vA += scaledDelta;\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t\tif (coneLight.vBisBeingMoved) {\n\t\t\t\tconeLight.vB += scaledDelta;\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolMoveOptics) {\n\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\tconeLight.vAisBeingMoved = false;\n\t\t\tconeLight.vBisBeingMoved = false;\n\t\t}\n\t}\n}\n\nvoid Lighting::moveWalls(UpdateVariables& myVar, UpdateParameters& myParam) {\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tglm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y);\n\tglm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom);\n\n\tif (IO::mousePress(0) && myVar.toolMoveOptics) {\n\t\tfor (Wall& wall : walls) {\n\t\t\tglm::vec2 dA = wall.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = wall.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= myParam.brush.brushRadius) {\n\t\t\t\twall.vAisBeingMoved = true;\n\t\t\t}\n\t\t\tif (distB <= myParam.brush.brushRadius) {\n\t\t\t\twall.vBisBeingMoved = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolMoveOptics) {\n\n\t\tfloat moveRelaxFactor = shapeRelaxFactor * 0.06f;\n\n\t\tfor (Wall& wall : walls) {\n\t\t\tif (wall.vAisBeingMoved) {\n\t\t\t\twall.vA += scaledDelta;\n\n\t\t\t\tif (wall.isShapeWall && relaxMove) {\n\t\t\t\t\tfor (Shape& shape : shapes) {\n\t\t\t\t\t\tif (shape.id == wall.shapeId) {\n\n\t\t\t\t\t\t\tshape.relaxShape(shapeRelaxIter, moveRelaxFactor);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t\tif (wall.vBisBeingMoved) {\n\t\t\t\twall.vB += scaledDelta;\n\n\t\t\t\tif (wall.isShapeWall && relaxMove) {\n\t\t\t\t\tfor (Shape& shape : shapes) {\n\t\t\t\t\t\tif (shape.id == wall.shapeId) {\n\t\t\t\t\t\t\tshape.relaxShape(shapeRelaxIter, moveRelaxFactor);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\n\t\t\tcalculateWallNormal(wall);\n\t\t}\n\n\t\tfor (Shape& shape : shapes) {\n\t\t\tshape.calculateWallsNormals();\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolMoveOptics) {\n\t\tfor (Wall& wall : walls) {\n\t\t\twall.vAisBeingMoved = false;\n\t\t\twall.vBisBeingMoved = false;\n\t\t}\n\t}\n}\n\nvoid Lighting::moveLogic(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\n\n\tminHelperLength = FLT_MAX;\n\n\tif (!shapes.empty()) {\n\t\tfor (size_t i = 0; i < shapes.size(); i++) {\n\t\t\tshapes[i].drawHoverHelpers = false;\n\n\t\t\tfor (size_t j = 0; j < shapes[i].helpers.size(); j++) {\n\t\t\t\tfloat helperDist = glm::length(mouseWorldPos - shapes[i].helpers[j]);\n\n\t\t\t\tif (shapes[i].shapeType == circle) {\n\t\t\t\t\tfloat helperCircleDist = glm::length(mouseWorldPos - shapes[i].helpers[0]);\n\n\t\t\t\t\tif (helperCircleDist <= shapes[i].circleRadius && !shapes[i].isBeingSpawned) {\n\t\t\t\t\t\tshapes[i].drawHoverHelpers = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (helperDist <= helperMinDist) {\n\t\t\t\t\tif (!shapes[i].isBeingSpawned) {\n\t\t\t\t\t\tshapes[i].drawHoverHelpers = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (helperDist < minHelperLength) {\n\t\t\t\t\t\tminHelperLength = helperDist;\n\n\t\t\t\t\t\tif (IO::mousePress(0) && myVar.toolMoveOptics) {\n\t\t\t\t\t\t\tselectedShape = i;\n\t\t\t\t\t\t\tselectedHelper = j;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (selectedHelper == -1 && selectedShape == -1 && !isAnyShapeBeingSpawned) {\n\t\tmovePointLights(myVar, myParam);\n\t\tmoveAreaLights(myVar, myParam);\n\t\tmoveConeLights(myVar, myParam);\n\t\tmoveWalls(myVar, myParam);\n\n\t\treturn; // We are not moving helpers, so get out of the function\n\t}\n\n\tif (IO::mouseDown(0) && myVar.toolMoveOptics) {\n\n\t\tfor (Shape& shape : shapes) {\n\t\t\tif (shape.isBeingSpawned) {\n\t\t\t\tisAnyShapeBeingSpawned = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (selectedHelper != -1 && selectedShape != -1 && !isAnyShapeBeingSpawned) {\n\n\t\t\tshapes.at(selectedShape).isBeingMoved = true;\n\n\t\t\tif (selectedHelper != 2 || selectedHelper != 3) {\n\n\t\t\t\tglm::vec2 oldCenter = shapes.at(selectedShape).helpers[0];\n\t\t\t\tglm::vec2 oldEdge = shapes.at(selectedShape).helpers[1];\n\n\t\t\t\tglm::vec2 radiusVec = oldEdge - oldCenter;\n\t\t\t\tfloat radius = glm::length(radiusVec);\n\t\t\t\tglm::vec2 radiusDir = glm::normalize(radiusVec);\n\n\t\t\t\tshapes.at(selectedShape).helpers.at(selectedHelper) = mouseWorldPos;\n\n\t\t\t\tif (shapes.at(selectedShape).shapeType == circle) {\n\t\t\t\t\tif (selectedHelper == 0) {\n\n\t\t\t\t\t\tshapes.at(selectedShape).helpers[1] = mouseWorldPos + radiusDir * radius;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (selectedHelper == 2) {\n\t\t\t\tshapes.at(selectedShape).isThirdBeingMoved = true;\n\t\t\t\tshapes.at(selectedShape).moveH2 = mouseWorldPos;\n\t\t\t}\n\n\t\t\tif (selectedHelper == 3) {\n\t\t\t\tshapes.at(selectedShape).isFourthBeingMoved = true;\n\t\t\t\tshapes.at(selectedShape).moveH2 = mouseWorldPos;\n\t\t\t}\n\n\t\t\tif (shapes[selectedShape].symmetricalLens) {\n\t\t\t\tif (selectedHelper == 4) {\n\t\t\t\t\tshapes.at(selectedShape).isFifthBeingMoved = true;\n\t\t\t\t\tshapes.at(selectedShape).moveH2 = mouseWorldPos;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (shapes[selectedShape].symmetricalLens) {\n\t\t\t\tif ((selectedHelper == 3 || selectedHelper == 4) && IO::shortcutDown(KEY_LEFT_CONTROL)) {\n\t\t\t\t\tshapes.at(selectedShape).isFifthFourthMoved = true;\n\t\t\t\t\tshapes.at(selectedShape).moveH2 = mouseWorldPos;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (shapes[selectedShape].shapeType == lens) {\n\t\t\t\tif (selectedHelper == shapes[selectedShape].helpers.size() - 1) {\n\t\t\t\t\tshapes.at(selectedShape).isGlobalHelperMoved = true;\n\t\t\t\t\tshapes.at(selectedShape).helpers.back() = mouseWorldPos;\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\tshapes.at(selectedShape).makeShape();\n\t\t}\n\n\t\tshouldRender = true;\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolMoveOptics) {\n\n\t\tif (selectedHelper != -1 && selectedShape != -1 && !isAnyShapeBeingSpawned) {\n\t\t\tshapes.at(selectedShape).isBeingMoved = false;\n\n\t\t\tshapes.at(selectedShape).isThirdBeingMoved = false;\n\t\t\tshapes.at(selectedShape).isFourthBeingMoved = false;\n\t\t\tshapes.at(selectedShape).isFifthBeingMoved = false;\n\t\t\tshapes.at(selectedShape).isFifthFourthMoved = false;\n\t\t\tshapes.at(selectedShape).isGlobalHelperMoved = false;\n\t\t}\n\n\t\tshouldRender = true;\n\n\t\tminHelperLength = FLT_MAX;\n\t\tselectedShape = -1;\n\t\tselectedHelper = -1;\n\t}\n}\n\nvoid Lighting::eraseLogic(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tbool anySelectedWalls = false;\n\tbool anySelectedLights = false;\n\n\tfor (Wall& wall : walls) {\n\t\tif (wall.isSelected) {\n\t\t\tanySelectedWalls = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfor (PointLight& p : pointLights) {\n\t\tif (p.isSelected) {\n\t\t\tanySelectedLights = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfor (AreaLight& a : areaLights) {\n\t\tif (a.isSelected) {\n\t\t\tanySelectedLights = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfor (ConeLight& l : coneLights) {\n\t\tif (l.isSelected) {\n\t\t\tanySelectedLights = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tif (IO::mouseDown(0) && myVar.toolEraseOptics) {\n\t\tfor (int i = static_cast<int>(walls.size()) - 1; i >= 0; --i) {\n\n\t\t\tWall& wall = walls[i];\n\n\t\t\tglm::vec2 dA = wall.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = wall.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= myParam.brush.brushRadius || distB <= myParam.brush.brushRadius) {\n\n\t\t\t\tif (wall.isShapeWall) {\n\t\t\t\t\tfor (size_t shapeIdx = 0; shapeIdx < shapes.size(); shapeIdx++) {\n\n\t\t\t\t\t\tShape& shape = shapes[shapeIdx];\n\n\t\t\t\t\t\tif (shape.id == wall.shapeId) {\n\t\t\t\t\t\t\tstd::vector<uint32_t>& myIds = shape.myWallIds;\n\n\t\t\t\t\t\t\tuint32_t wallId = wall.id;\n\t\t\t\t\t\t\tmyIds.erase(std::remove(myIds.begin(), myIds.end(), wallId), myIds.end());\n\n\t\t\t\t\t\t\tshape.isShapeClosed = false;\n\n\t\t\t\t\t\t\tfor (uint32_t id : myIds) {\n\t\t\t\t\t\t\t\tWall* shapeWall = getWallById(*shape.walls, id);\n\t\t\t\t\t\t\t\tif (shapeWall) {\n\t\t\t\t\t\t\t\t\tshapeWall->isShapeClosed = false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\twalls.erase(walls.begin() + i);\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t}\n\n\t\tfor (int i = static_cast<int>(pointLights.size()) - 1; i >= 0; --i) {\n\n\t\t\tPointLight& pointLight = pointLights[i];\n\n\t\t\tglm::vec2 d = pointLight.pos - mouseWorldPos;\n\n\t\t\tfloat dist = glm::length(d);\n\n\t\t\tif (dist <= myParam.brush.brushRadius) {\n\n\t\t\t\tpointLights.erase(pointLights.begin() + i);\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t}\n\n\t\tfor (int i = static_cast<int>(areaLights.size()) - 1; i >= 0; --i) {\n\n\t\t\tAreaLight& areaLight = areaLights[i];\n\n\t\t\tglm::vec2 dA = areaLight.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = areaLight.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= myParam.brush.brushRadius || distB <= myParam.brush.brushRadius) {\n\n\t\t\t\tareaLights.erase(areaLights.begin() + i);\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t}\n\n\t\tfor (int i = static_cast<int>(coneLights.size()) - 1; i >= 0; --i) {\n\n\t\t\tConeLight& coneLight = coneLights[i];\n\n\t\t\tglm::vec2 dA = coneLight.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = coneLight.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= myParam.brush.brushRadius || distB <= myParam.brush.brushRadius) {\n\n\t\t\t\tconeLights.erase(coneLights.begin() + i);\n\n\t\t\t\tshouldRender = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::shortcutPress(KEY_DELETE)) {\n\t\tfor (int i = static_cast<int>(walls.size()) - 1; i >= 0; --i) {\n\t\t\tWall& wall = walls[i];\n\n\t\t\tif (wall.isSelected) {\n\t\t\t\tfor (size_t shapeIdx = 0; shapeIdx < shapes.size(); shapeIdx++) {\n\n\t\t\t\t\tShape& shape = shapes[shapeIdx];\n\n\t\t\t\t\tif (shape.id == wall.shapeId) {\n\t\t\t\t\t\tstd::vector<uint32_t>& myIds = shape.myWallIds;\n\n\t\t\t\t\t\tuint32_t wallId = wall.id;\n\t\t\t\t\t\tmyIds.erase(std::remove(myIds.begin(), myIds.end(), wallId), myIds.end());\n\n\t\t\t\t\t\tshape.isShapeClosed = false;\n\n\t\t\t\t\t\tfor (uint32_t id : myIds) {\n\t\t\t\t\t\t\tWall* shapeWall = getWallById(*shape.walls, id);\n\t\t\t\t\t\t\tif (shapeWall) {\n\t\t\t\t\t\t\t\tshapeWall->isShapeClosed = false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\twalls.erase(walls.begin() + i);\n\n\t\t\t}\n\t\t}\n\n\t\tfor (int i = static_cast<int>(shapes.size()) - 1; i >= 0; --i) {\n\t\t\tShape& shape = shapes[i];\n\n\t\t\tif (shape.myWallIds.size() == 0) {\n\t\t\t\tshapes.erase(shapes.begin() + i);\n\t\t\t}\n\t\t}\n\n\t\tfor (int i = static_cast<int>(pointLights.size()) - 1; i >= 0; --i) {\n\t\t\tPointLight& pointLight = pointLights[i];\n\n\t\t\tif (pointLight.isSelected) {\n\t\t\t\tpointLights.erase(pointLights.begin() + i);\n\t\t\t}\n\t\t}\n\n\t\tfor (int i = static_cast<int>(areaLights.size()) - 1; i >= 0; --i) {\n\t\t\tAreaLight& areaLight = areaLights[i];\n\n\t\t\tif (areaLight.isSelected) {\n\t\t\t\tareaLights.erase(areaLights.begin() + i);\n\t\t\t}\n\t\t}\n\n\t\tfor (int i = static_cast<int>(coneLights.size()) - 1; i >= 0; --i) {\n\t\t\tConeLight& coneLight = coneLights[i];\n\n\t\t\tif (coneLight.isSelected) {\n\t\t\t\tconeLights.erase(coneLights.begin() + i);\n\t\t\t}\n\t\t}\n\n\t\twallPointers.clear();\n\t\tfor (Wall& wall : walls) {\n\t\t\twallPointers.push_back(&wall);\n\t\t}\n\t\tbvh.build(wallPointers);\n\t}\n\n\tif (IO::mouseReleased(0) && myVar.toolEraseOptics) {\n\t\tfor (int i = static_cast<int>(shapes.size()) - 1; i >= 0; --i) {\n\t\t\tif (shapes[i].myWallIds.empty()) {\n\t\t\t\tshapes.erase(shapes.begin() + i);\n\t\t\t}\n\t\t}\n\t}\n\n\n\tif ((IO::mouseReleased(0) && myVar.toolEraseOptics) || ((anySelectedWalls || anySelectedLights) && IO::shortcutPress(KEY_DELETE))) {\n\t\tshouldRender = true;\n\n\t\twallPointers.clear();\n\t\tfor (Wall& wall : walls) {\n\t\t\twallPointers.push_back(&wall);\n\t\t}\n\t\tbvh.build(wallPointers);\n\t}\n}\n\n// I'm also sorry for this large chunk of ugly code, but I really hate working on anything that involves UI or UX stuff (:\nvoid Lighting::selectLogic(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tselectedWalls = 0;\n\n\tselectedLights = 0;\n\n\tint selectedAreaLights = 0;\n\n\tint selectedConeLights = 0;\n\n\tint selectedPointLights = 0;\n\n\tbool isHoveringAnything = false;\n\n\tif (myVar.toolSelectOptics) {\n\n\t\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\t\tif (IO::mousePress(0)) {\n\t\t\tboxInitialPos = mouseWorldPos;\n\t\t\tisBoxSelecting = true;\n\n\t\t\tisBoxDeselecting = IO::shortcutDown(KEY_LEFT_ALT);\n\t\t}\n\n\t\tif (IO::mouseDown(0) && isBoxSelecting) {\n\t\t\tboxX = fmin(boxInitialPos.x, mouseWorldPos.x);\n\t\t\tboxY = fmin(boxInitialPos.y, mouseWorldPos.y);\n\t\t\tboxWidth = fabs(mouseWorldPos.x - boxInitialPos.x);\n\t\t\tboxHeight = fabs(mouseWorldPos.y - boxInitialPos.y);\n\t\t}\n\n\t\tif (IO::mouseReleased(0) && isBoxSelecting) {\n\t\t\tfloat boxX1 = fmin(boxInitialPos.x, mouseWorldPos.x);\n\t\t\tfloat boxX2 = fmax(boxInitialPos.x, mouseWorldPos.x);\n\t\t\tfloat boxY1 = fmin(boxInitialPos.y, mouseWorldPos.y);\n\t\t\tfloat boxY2 = fmax(boxInitialPos.y, mouseWorldPos.y);\n\n\t\t\tfor (Wall& wall : walls) {\n\t\t\t\tbool vAInBox = wall.vA.x >= boxX1 && wall.vA.x <= boxX2 &&\n\t\t\t\t\twall.vA.y >= boxY1 && wall.vA.y <= boxY2;\n\n\t\t\t\tbool vBInBox = wall.vB.x >= boxX1 && wall.vB.x <= boxX2 &&\n\t\t\t\t\twall.vB.y >= boxY1 && wall.vB.y <= boxY2;\n\n\t\t\t\tif (vAInBox || vBInBox) {\n\t\t\t\t\tif (isBoxDeselecting && wall.isSelected) {\n\t\t\t\t\t\twall.isSelected = false;\n\t\t\t\t\t}\n\t\t\t\t\telse if (!isBoxDeselecting) {\n\t\t\t\t\t\twall.isSelected = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\t\tbool vAInBox = areaLight.vA.x >= boxX1 && areaLight.vA.x <= boxX2 &&\n\t\t\t\t\tareaLight.vA.y >= boxY1 && areaLight.vA.y <= boxY2;\n\n\t\t\t\tbool vBInBox = areaLight.vB.x >= boxX1 && areaLight.vB.x <= boxX2 &&\n\t\t\t\t\tareaLight.vB.y >= boxY1 && areaLight.vB.y <= boxY2;\n\n\t\t\t\tif (vAInBox || vBInBox) {\n\t\t\t\t\tif (isBoxDeselecting && areaLight.isSelected) {\n\t\t\t\t\t\tareaLight.isSelected = false;\n\t\t\t\t\t}\n\t\t\t\t\telse if (!isBoxDeselecting) {\n\t\t\t\t\t\tareaLight.isSelected = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\t\tbool vAInBox = coneLight.vA.x >= boxX1 && coneLight.vA.x <= boxX2 &&\n\t\t\t\t\tconeLight.vA.y >= boxY1 && coneLight.vA.y <= boxY2;\n\n\t\t\t\tbool vBInBox = coneLight.vB.x >= boxX1 && coneLight.vB.x <= boxX2 &&\n\t\t\t\t\tconeLight.vB.y >= boxY1 && coneLight.vB.y <= boxY2;\n\n\t\t\t\tif (vAInBox || vBInBox) {\n\t\t\t\t\tif (isBoxDeselecting && coneLight.isSelected) {\n\t\t\t\t\t\tconeLight.isSelected = false;\n\t\t\t\t\t}\n\t\t\t\t\telse if (!isBoxDeselecting) {\n\t\t\t\t\t\tconeLight.isSelected = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\t\tbool pointInBox = pointLight.pos.x >= boxX1 && pointLight.pos.x <= boxX2 &&\n\t\t\t\t\tpointLight.pos.y >= boxY1 && pointLight.pos.y <= boxY2;\n\n\t\t\t\tif (pointInBox) {\n\t\t\t\t\tif (isBoxDeselecting && pointLight.isSelected) {\n\t\t\t\t\t\tpointLight.isSelected = false;\n\t\t\t\t\t}\n\t\t\t\t\telse if (!isBoxDeselecting) {\n\t\t\t\t\t\tpointLight.isSelected = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tboxX = 0.0f;\n\t\t\tboxY = 0.0f;\n\t\t\tboxWidth = 0.0f;\n\t\t\tboxHeight = 0.0f;\n\n\t\t\tisBoxSelecting = false;\n\t\t\tisBoxDeselecting = false;\n\t\t}\n\n\t\tfor (Wall& wall : walls) {\n\n\t\t\tglm::vec2 wallLine = wall.vB - wall.vA;\n\t\t\tglm::vec2 mouseToA = mouseWorldPos - wall.vA;\n\n\t\t\tfloat lineLenSquared = glm::dot(wallLine, wallLine);\n\n\t\t\tif (lineLenSquared == 0.0f) {\n\n\t\t\t\tfloat dist = glm::length(mouseToA);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat t = glm::dot(mouseToA, wallLine) / lineLenSquared;\n\t\t\tt = glm::clamp(t, 0.0f, 1.0f);\n\n\t\t\tglm::vec2 closestPoint = wall.vA + wallLine * t;\n\n\t\t\tglm::vec2 diff = mouseWorldPos - closestPoint;\n\t\t\tfloat distance = glm::length(diff);\n\n\t\t\tif (distance < 5.0f) {\n\n\t\t\t\tisHoveringAnything = true;\n\n\t\t\t\tif (IO::mousePress(0)) {\n\n\t\t\t\t\tif (!IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\t\t\t\tfor (Wall& wallToDeselect : walls) {\n\t\t\t\t\t\t\twallToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (AreaLight& areaLightToDeselect : areaLights) {\n\t\t\t\t\t\t\tareaLightToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (ConeLight& coneLightToDeselect : coneLights) {\n\t\t\t\t\t\t\tconeLightToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\t\t\t\t\tpointLight.isSelected = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\t\t\t\twall.isSelected = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_LEFT_SHIFT) && wall.isShapeWall) {\n\t\t\t\t\t\twall.isSelected = false;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (wall.isShapeWall) {\n\n\t\t\t\t\t\tif (IO::shortcutDown(KEY_LEFT_SHIFT)) {\n\t\t\t\t\t\t\tfor (Shape& shape : shapes) {\n\t\t\t\t\t\t\t\tif (shape.id == wall.shapeId) {\n\t\t\t\t\t\t\t\t\tfor (uint32_t wallId : shape.myWallIds) {\n\t\t\t\t\t\t\t\t\t\tWall* wall = getWallById(walls, wallId);\n\t\t\t\t\t\t\t\t\t\tif (wall) wall->isSelected = true;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (IO::shortcutDown(KEY_LEFT_ALT) && IO::shortcutDown(KEY_LEFT_SHIFT) && wall.isShapeWall) {\n\t\t\t\t\t\tfor (Shape& shape : shapes) {\n\t\t\t\t\t\t\tif (shape.id == wall.shapeId) {\n\t\t\t\t\t\t\t\tfor (uint32_t wallId : shape.myWallIds) {\n\t\t\t\t\t\t\t\t\tWall* wall = getWallById(walls, wallId);\n\t\t\t\t\t\t\t\t\tif (wall) wall->isSelected = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\twall.apparentColor = RED;\n\t\t\t}\n\t\t}\n\n\t\tfor (AreaLight& areaLight : areaLights) {\n\n\t\t\tglm::vec2 areaLightLine = areaLight.vB - areaLight.vA;\n\t\t\tglm::vec2 mouseToA = mouseWorldPos - areaLight.vA;\n\n\t\t\tfloat lineLenSquared = glm::dot(areaLightLine, areaLightLine);\n\n\t\t\tif (lineLenSquared == 0.0f) {\n\n\t\t\t\tfloat dist = glm::length(mouseToA);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat t = glm::dot(mouseToA, areaLightLine) / lineLenSquared;\n\t\t\tt = glm::clamp(t, 0.0f, 1.0f);\n\n\t\t\tglm::vec2 closestPoint = areaLight.vA + areaLightLine * t;\n\n\t\t\tglm::vec2 diff = mouseWorldPos - closestPoint;\n\t\t\tfloat distance = glm::length(diff);\n\n\t\t\tif (distance < 5.0f) {\n\n\t\t\t\tisHoveringAnything = true;\n\n\t\t\t\tif (IO::mousePress(0)) {\n\n\t\t\t\t\tif (!IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\t\t\t\tfor (AreaLight& areaLightToDeselect : areaLights) {\n\t\t\t\t\t\t\tareaLightToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (ConeLight& coneLightToDeselect : coneLights) {\n\t\t\t\t\t\t\tconeLightToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\t\t\t\t\tpointLight.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (Wall& wall : walls) {\n\t\t\t\t\t\t\twall.isSelected = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\t\t\t\tareaLight.isSelected = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_LEFT_SHIFT)) {\n\t\t\t\t\t\tareaLight.isSelected = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tareaLight.apparentColor = RED;\n\t\t\t}\n\t\t}\n\n\t\tfor (ConeLight& coneLight : coneLights) {\n\n\t\t\tglm::vec2 areaLightLine = coneLight.vB - coneLight.vA;\n\t\t\tglm::vec2 mouseToA = mouseWorldPos - coneLight.vA;\n\n\t\t\tfloat lineLenSquared = glm::dot(areaLightLine, areaLightLine);\n\n\t\t\tif (lineLenSquared == 0.0f) {\n\n\t\t\t\tfloat dist = glm::length(mouseToA);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat t = glm::dot(mouseToA, areaLightLine) / lineLenSquared;\n\t\t\tt = glm::clamp(t, 0.0f, 1.0f);\n\n\t\t\tglm::vec2 closestPoint = coneLight.vA + areaLightLine * t;\n\n\t\t\tglm::vec2 diff = mouseWorldPos - closestPoint;\n\t\t\tfloat distance = glm::length(diff);\n\n\t\t\tif (distance < 5.0f) {\n\n\t\t\t\tisHoveringAnything = true;\n\n\t\t\t\tif (IO::mousePress(0)) {\n\n\t\t\t\t\tif (!IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\t\t\t\tfor (AreaLight& areaLightToDeselect : areaLights) {\n\t\t\t\t\t\t\tareaLightToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (ConeLight& coneLightToDeselect : coneLights) {\n\t\t\t\t\t\t\tconeLightToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\t\t\t\t\tpointLight.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (Wall& wall : walls) {\n\t\t\t\t\t\t\twall.isSelected = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\t\t\t\tconeLight.isSelected = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_LEFT_SHIFT)) {\n\t\t\t\t\t\tconeLight.isSelected = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconeLight.apparentColor = RED;\n\t\t\t}\n\t\t}\n\n\t\tfor (PointLight& pointLight : pointLights) {\n\n\t\t\tglm::vec2 mouseToA = mouseWorldPos - pointLight.pos;\n\n\t\t\tfloat distance = glm::length(mouseToA);\n\n\t\t\tif (distance < 5.0f) {\n\n\t\t\t\tisHoveringAnything = true;\n\n\t\t\t\tif (IO::mousePress(0)) {\n\n\t\t\t\t\tif (!IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\n\t\t\t\t\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\t\t\t\t\tpointLight.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (ConeLight& coneLightToDeselect : coneLights) {\n\t\t\t\t\t\t\tconeLightToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (AreaLight& areaLightToDeselect : areaLights) {\n\t\t\t\t\t\t\tareaLightToDeselect.isSelected = false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (Wall& wall : walls) {\n\t\t\t\t\t\t\twall.isSelected = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\t\t\t\tpointLight.isSelected = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_LEFT_SHIFT)) {\n\t\t\t\t\t\tpointLight.isSelected = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tpointLight.apparentColor = RED;\n\t\t\t}\n\t\t}\n\n\t\tif (!isHoveringAnything && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\t\tif (IO::mousePress(0)) {\n\n\t\t\t\tfor (Wall& wall : walls) {\n\t\t\t\t\twall.isSelected = false;\n\t\t\t\t}\n\n\t\t\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\t\t\tareaLight.isSelected = false;\n\t\t\t\t}\n\n\t\t\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\t\t\tconeLight.isSelected = false;\n\t\t\t\t}\n\n\t\t\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\t\t\tpointLight.isSelected = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (Wall& wall : walls) {\n\t\t\tif (wall.isSelected) {\n\t\t\t\twall.apparentColor = RED;\n\n\t\t\t\tselectedWalls++;\n\t\t\t}\n\t\t}\n\n\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\tif (areaLight.isSelected) {\n\t\t\t\tareaLight.apparentColor = RED;\n\n\t\t\t\tselectedAreaLights++;\n\t\t\t}\n\t\t}\n\n\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\tif (coneLight.isSelected) {\n\t\t\t\tconeLight.apparentColor = RED;\n\n\t\t\t\tselectedConeLights++;\n\t\t\t}\n\t\t}\n\n\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\tif (pointLight.isSelected) {\n\t\t\t\tpointLight.apparentColor = RED;\n\n\t\t\t\tselectedPointLights++;\n\t\t\t}\n\t\t}\n\n\t\tif (IO::mouseReleased(0)) {\n\n\t\t\tif (selectedWalls > 0) {\n\n\t\t\t\tbaseColorAvg = { 0, 0, 0, 0 };\n\t\t\t\tImVec4 baseColorAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f };\n\n\t\t\t\tspecularColorAvg = { 0, 0, 0, 0 };\n\t\t\t\tImVec4 specularColorAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f };\n\n\t\t\t\trefractionColorAvg = { 0, 0, 0, 0 };\n\t\t\t\tImVec4 refractionColAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f };\n\n\t\t\t\temissionColorAvg = { 0, 0, 0, 0 };\n\t\t\t\tImVec4 emissionColAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f };\n\n\t\t\t\tspecularRoughAvg = 0.0f;\n\n\t\t\t\trefractionRoughAvg = 0.0f;\n\n\t\t\t\trefractionAmountAvg = 0.0f;\n\n\t\t\t\tiorAvg = 0.0f;\n\n\t\t\t\tdispersionAvg = 0.0f;\n\n\t\t\t\temissionGainAvg = 0.0f;\n\n\t\t\t\tfor (Wall& wall : walls) {\n\t\t\t\t\tif (wall.isSelected) {\n\n\t\t\t\t\t\t// Base Color\n\t\t\t\t\t\tImVec4 wallBaseColImgui = rlImGuiColors::Convert(wall.baseColor);\n\n\t\t\t\t\t\tbaseColorAvgImgui.x += wallBaseColImgui.x;\n\t\t\t\t\t\tbaseColorAvgImgui.y += wallBaseColImgui.y;\n\t\t\t\t\t\tbaseColorAvgImgui.z += wallBaseColImgui.z;\n\t\t\t\t\t\tbaseColorAvgImgui.w += wallBaseColImgui.w;\n\n\t\t\t\t\t\t// Specular Color\n\t\t\t\t\t\tImVec4 wallSpecularColImgui = rlImGuiColors::Convert(wall.specularColor);\n\n\t\t\t\t\t\tspecularColorAvgImgui.x += wallSpecularColImgui.x;\n\t\t\t\t\t\tspecularColorAvgImgui.y += wallSpecularColImgui.y;\n\t\t\t\t\t\tspecularColorAvgImgui.z += wallSpecularColImgui.z;\n\t\t\t\t\t\tspecularColorAvgImgui.w += wallSpecularColImgui.w;\n\n\t\t\t\t\t\t// Refraction Color\n\t\t\t\t\t\tImVec4 wallRefractionColImgui = rlImGuiColors::Convert(wall.refractionColor);\n\n\t\t\t\t\t\trefractionColAvgImgui.x += wallRefractionColImgui.x;\n\t\t\t\t\t\trefractionColAvgImgui.y += wallRefractionColImgui.y;\n\t\t\t\t\t\trefractionColAvgImgui.z += wallRefractionColImgui.z;\n\t\t\t\t\t\trefractionColAvgImgui.w += wallRefractionColImgui.w;\n\n\t\t\t\t\t\t// Emission Color\n\t\t\t\t\t\tImVec4 wallEmissionColImgui = rlImGuiColors::Convert(wall.emissionColor);\n\n\t\t\t\t\t\temissionColAvgImgui.x += wallEmissionColImgui.x;\n\t\t\t\t\t\temissionColAvgImgui.y += wallEmissionColImgui.y;\n\t\t\t\t\t\temissionColAvgImgui.z += wallEmissionColImgui.z;\n\t\t\t\t\t\temissionColAvgImgui.w += wallEmissionColImgui.w;\n\n\t\t\t\t\t\t// Specular Roughness\n\t\t\t\t\t\tspecularRoughAvg += wall.specularRoughness;\n\n\t\t\t\t\t\t// Refraction Surface Roughness\n\t\t\t\t\t\trefractionRoughAvg += wall.refractionRoughness;\n\n\t\t\t\t\t\t// Refraction Amount\n\t\t\t\t\t\trefractionAmountAvg += wall.refractionAmount;\n\n\t\t\t\t\t\t// IOR\n\t\t\t\t\t\tiorAvg += wall.IOR;\n\n\t\t\t\t\t\t// Dispersion\n\t\t\t\t\t\tdispersionAvg += wall.dispersionStrength;\n\n\t\t\t\t\t\t// Emission\n\t\t\t\t\t\temissionGainAvg = wallEmissionColImgui.w;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Base Color\n\t\t\t\tbaseColorAvgImgui.x /= selectedWalls;\n\t\t\t\tbaseColorAvgImgui.y /= selectedWalls;\n\t\t\t\tbaseColorAvgImgui.z /= selectedWalls;\n\t\t\t\tbaseColorAvgImgui.w /= selectedWalls;\n\t\t\t\twallBaseColor = rlImGuiColors::Convert(baseColorAvgImgui);\n\n\t\t\t\t// Specular Color\n\t\t\t\tspecularColorAvgImgui.x /= selectedWalls;\n\t\t\t\tspecularColorAvgImgui.y /= selectedWalls;\n\t\t\t\tspecularColorAvgImgui.z /= selectedWalls;\n\t\t\t\tspecularColorAvgImgui.w /= selectedWalls;\n\t\t\t\twallSpecularColor = rlImGuiColors::Convert(specularColorAvgImgui);\n\n\t\t\t\t// Refraction Color\n\t\t\t\trefractionColAvgImgui.x /= selectedWalls;\n\t\t\t\trefractionColAvgImgui.y /= selectedWalls;\n\t\t\t\trefractionColAvgImgui.z /= selectedWalls;\n\t\t\t\trefractionColAvgImgui.w /= selectedWalls;\n\t\t\t\twallRefractionColor = rlImGuiColors::Convert(refractionColAvgImgui);\n\n\t\t\t\t// Emission Color\n\t\t\t\temissionColAvgImgui.x /= selectedWalls;\n\t\t\t\temissionColAvgImgui.y /= selectedWalls;\n\t\t\t\temissionColAvgImgui.z /= selectedWalls;\n\t\t\t\temissionColAvgImgui.w /= selectedWalls;\n\t\t\t\twallEmissionColor = rlImGuiColors::Convert(emissionColAvgImgui);\n\n\t\t\t\t// Specular Roughness\n\t\t\t\tspecularRoughAvg /= selectedWalls;\n\t\t\t\twallSpecularRoughness = specularRoughAvg;\n\n\t\t\t\t// Refraction Surface Roughness\n\t\t\t\trefractionRoughAvg /= selectedWalls;\n\t\t\t\twallRefractionRoughness = refractionRoughAvg;\n\n\t\t\t\t// Refraction Amount\n\t\t\t\trefractionAmountAvg /= selectedWalls;\n\t\t\t\twallRefractionAmount = refractionAmountAvg;\n\n\t\t\t\t// IOR\n\t\t\t\tiorAvg /= selectedWalls;\n\t\t\t\twallIOR = iorAvg;\n\n\t\t\t\t// Dispersion\n\t\t\t\tdispersionAvg /= selectedWalls;\n\t\t\t\twallDispersion = dispersionAvg;\n\n\t\t\t\t// Emission\n\t\t\t\temissionGainAvg = emissionColAvgImgui.w;\n\t\t\t\twallEmissionGain = emissionGainAvg;\n\t\t\t}\n\n\t\t\tif (selectedAreaLights > 0 || selectedPointLights > 0 || selectedConeLights > 0) {\n\n\t\t\t\tlightColorAvg = { 0, 0, 0, 0 };\n\t\t\t\tImVec4 lightColorAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f };\n\n\t\t\t\tlightSpreadAvg = 0.0f;\n\n\t\t\t\tlightGainAvg = 0.0f;\n\n\t\t\t\tif (selectedAreaLights > 0) {\n\n\t\t\t\t\tfor (AreaLight& arealight : areaLights) {\n\t\t\t\t\t\tif (arealight.isSelected) {\n\n\t\t\t\t\t\t\t// Light Color\n\t\t\t\t\t\t\tImVec4 lightColImgui = rlImGuiColors::Convert(arealight.color);\n\n\t\t\t\t\t\t\tlightColorAvgImgui.x += lightColImgui.x;\n\t\t\t\t\t\t\tlightColorAvgImgui.y += lightColImgui.y;\n\t\t\t\t\t\t\tlightColorAvgImgui.z += lightColImgui.z;\n\t\t\t\t\t\t\tlightColorAvgImgui.w += lightColImgui.w;\n\n\t\t\t\t\t\t\t// Light Spread\n\t\t\t\t\t\t\tlightSpreadAvg += arealight.spread;\n\n\t\t\t\t\t\t\t// Light Gain\n\t\t\t\t\t\t\tlightGainAvg = lightColorAvgImgui.w;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (selectedConeLights > 0) {\n\n\t\t\t\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\t\t\t\tif (coneLight.isSelected) {\n\n\t\t\t\t\t\t\t// Light Color\n\t\t\t\t\t\t\tImVec4 lightColImgui = rlImGuiColors::Convert(coneLight.color);\n\n\t\t\t\t\t\t\tlightColorAvgImgui.x += lightColImgui.x;\n\t\t\t\t\t\t\tlightColorAvgImgui.y += lightColImgui.y;\n\t\t\t\t\t\t\tlightColorAvgImgui.z += lightColImgui.z;\n\t\t\t\t\t\t\tlightColorAvgImgui.w += lightColImgui.w;\n\n\t\t\t\t\t\t\t// Light Spread\n\t\t\t\t\t\t\tlightSpreadAvg += coneLight.spread;\n\n\t\t\t\t\t\t\t// Light Gain\n\t\t\t\t\t\t\tlightGainAvg = lightColorAvgImgui.w;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (selectedPointLights > 0) {\n\n\t\t\t\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\t\t\t\tif (pointLight.isSelected) {\n\n\t\t\t\t\t\t\t// Light Color\n\t\t\t\t\t\t\tImVec4 lightColImgui = rlImGuiColors::Convert(pointLight.color);\n\n\t\t\t\t\t\t\tlightColorAvgImgui.x += lightColImgui.x;\n\t\t\t\t\t\t\tlightColorAvgImgui.y += lightColImgui.y;\n\t\t\t\t\t\t\tlightColorAvgImgui.z += lightColImgui.z;\n\t\t\t\t\t\t\tlightColorAvgImgui.w += lightColImgui.w;\n\n\t\t\t\t\t\t\t// Light Gain\n\t\t\t\t\t\t\tlightGainAvg = lightColorAvgImgui.w;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Light Color\n\t\t\t\tlightColorAvgImgui.x /= selectedAreaLights + selectedPointLights + selectedConeLights;\n\t\t\t\tlightColorAvgImgui.y /= selectedAreaLights + selectedPointLights + selectedConeLights;\n\t\t\t\tlightColorAvgImgui.z /= selectedAreaLights + selectedPointLights + selectedConeLights;\n\t\t\t\tlightColorAvgImgui.w /= selectedAreaLights + selectedPointLights + selectedConeLights;\n\t\t\t\tlightColor = rlImGuiColors::Convert(lightColorAvgImgui);\n\n\t\t\t\t// Light Spread\n\t\t\t\tif (selectedAreaLights + selectedConeLights > 0) {\n\t\t\t\t\tlightSpreadAvg /= selectedAreaLights + selectedConeLights;\n\t\t\t\t\tlightSpread = lightSpreadAvg;\n\t\t\t\t}\n\n\t\t\t\t// Light Gain\n\t\t\t\tlightGainAvg = lightColorAvgImgui.w;\n\t\t\t\tlightGain = lightGainAvg;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::mouseReleased(0) && !myVar.toolSelectOptics) {\n\t\tfor (Wall& wall : walls) {\n\t\t\twall.isSelected = false;\n\t\t}\n\n\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\tareaLight.isSelected = false;\n\t\t}\n\n\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\tconeLight.isSelected = false;\n\t\t}\n\n\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\tpointLight.isSelected = false;\n\t\t}\n\t}\n\n\tbool isAnyActive = false;\n\n\tfor (bool* param : uiOpticElements) {\n\t\tif (*param) {\n\t\t\tisAnyActive = true;\n\t\t}\n\t}\n\n\tif (isAnyActive && selectedWalls > 0) {\n\n\t\tfor (Wall& wall : walls) {\n\t\t\tif (wall.isSelected) {\n\n\t\t\t\tif (isSliderBaseColor) {\n\t\t\t\t\twall.baseColor = wallBaseColor;\n\t\t\t\t}\n\t\t\t\tif (isSliderSpecularColor) {\n\t\t\t\t\twall.specularColor = wallSpecularColor;\n\t\t\t\t}\n\t\t\t\tif (isSliderRefractionCol) {\n\t\t\t\t\twall.refractionColor = wallRefractionColor;\n\t\t\t\t}\n\t\t\t\tif (isSliderEmissionCol) {\n\n\t\t\t\t\tImVec4 convertedColor = rlImGuiColors::Convert(wallEmissionColor);\n\n\t\t\t\t\twall.emissionColor = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, wallEmissionGain });\n\t\t\t\t}\n\n\t\t\t\tif (isSliderSpecularRough) {\n\t\t\t\t\twall.specularRoughness = wallSpecularRoughness;\n\t\t\t\t}\n\n\t\t\t\tif (isSliderRefractionRough) {\n\t\t\t\t\twall.refractionRoughness = wallRefractionRoughness;\n\t\t\t\t}\n\n\t\t\t\tif (isSliderRefractionAmount) {\n\t\t\t\t\twall.refractionAmount = wallRefractionAmount;\n\t\t\t\t}\n\n\t\t\t\tif (isSliderIor) {\n\t\t\t\t\twall.IOR = wallIOR;\n\t\t\t\t}\n\n\t\t\t\tif (isSliderDispersion) {\n\t\t\t\t\twall.dispersionStrength = wallDispersion;\n\t\t\t\t}\n\n\t\t\t\tif (isSliderEmissionGain) {\n\t\t\t\t\tImVec4 convertedColor = rlImGuiColors::Convert(wall.emissionColor);\n\n\t\t\t\t\twall.emissionColor = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, wallEmissionGain });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tshouldRender = true;\n\t}\n\n\tif (isAnyActive && (selectedAreaLights > 0 || selectedPointLights > 0 || selectedConeLights > 0)) {\n\n\t\tif (selectedAreaLights > 0) {\n\t\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\t\tif (areaLight.isSelected) {\n\n\t\t\t\t\tif (isSliderLightGain) {\n\t\t\t\t\t\tImVec4 convertedColor = rlImGuiColors::Convert(areaLight.color);\n\n\t\t\t\t\t\tareaLight.color = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, lightGain });\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isSliderlightSpread) {\n\t\t\t\t\t\tareaLight.spread = lightSpread;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isSliderLightColor) {\n\n\t\t\t\t\t\tImVec4 convertedColor = rlImGuiColors::Convert(lightColor);\n\n\t\t\t\t\t\tareaLight.color = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, lightGain });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (selectedConeLights > 0) {\n\t\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\t\tif (coneLight.isSelected) {\n\n\t\t\t\t\tif (isSliderLightGain) {\n\t\t\t\t\t\tImVec4 convertedColor = rlImGuiColors::Convert(coneLight.color);\n\n\t\t\t\t\t\tconeLight.color = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, lightGain });\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isSliderlightSpread) {\n\t\t\t\t\t\tconeLight.spread = lightSpread;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isSliderLightColor) {\n\t\t\t\t\t\tconeLight.color = lightColor;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (selectedPointLights > 0) {\n\t\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\t\tif (pointLight.isSelected) {\n\n\t\t\t\t\tif (isSliderLightGain) {\n\t\t\t\t\t\tImVec4 convertedColor = rlImGuiColors::Convert(pointLight.color);\n\n\t\t\t\t\t\tpointLight.color = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, lightGain });\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isSliderLightColor) {\n\t\t\t\t\t\tpointLight.color = lightColor;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tshouldRender = true;\n\t}\n\n\tselectedLights = selectedPointLights + selectedAreaLights + selectedConeLights;\n\n\tisSliderLightGain = false;\n\n\tisSliderlightSpread = false;\n\n\tisSliderLightColor = false;\n\n\tisSliderBaseColor = false;\n\tisSliderSpecularColor = false;\n\tisSliderRefractionCol = false;\n\tisSliderEmissionCol = false;\n\n\tisSliderSpecularRough = false;\n\n\tisSliderRefractionRough = false;\n\n\tisSliderRefractionAmount = false;\n\n\tisSliderIor = false;\n\n\tisSliderDispersion = false;\n\n\tisSliderEmissionGain = false;\n}\n\n\n\nfloat Lighting::checkIntersect(const LightRay& ray, const Wall& w) {\n\n\tconst float x1 = w.vA.x, y1 = w.vA.y;\n\tconst float x2 = w.vB.x, y2 = w.vB.y;\n\n\tconst float x3 = ray.source.x, y3 = ray.source.y;\n\tconst float x4 = ray.source.x + ray.dir.x, y4 = ray.source.y + ray.dir.y;\n\n\tconst float den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);\n\tif (den == 0.0f) {\n\t\treturn ray.maxLength;\n\t}\n\n\tconst float t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;\n\tconst float u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;\n\n\tif (t > 0.0f && t < 1.0f && u > 0.0f) {\n\t\treturn u;\n\t}\n\n\treturn ray.maxLength;\n}\n\nvoid Lighting::processRayIntersection(LightRay& ray) {\n\tray.hasHit = false;\n\tray.length = ray.maxLength;\n\tfloat closestT = ray.maxLength;\n\tWall* hitWall = nullptr;\n\tglm::vec2 hitPt;\n\n\tif (bvh.traverse(ray, closestT, hitWall, hitPt) && hitWall != nullptr) {\n\t\tray.hasHit = true;\n\t\tray.length = closestT;\n\t\tray.hitPoint = hitPt;\n\t\tray.wall = *hitWall;\n\t}\n}\n\n// This controls specular lighting, meaning reflections. It uses a roughness parameter to control how smooth a surface is\nvoid Lighting::specularReflection(int& currentBounce, LightRay& ray, std::vector<LightRay>& copyRays, std::vector<Wall>& walls) {\n\n\tray.reflectSpecular = true;\n\n\tglm::vec2 wallVec = ray.wall.vB - ray.wall.vA;\n\tfloat wallLength = glm::length(wallVec);\n\tfloat t = glm::clamp(glm::dot(ray.hitPoint - ray.wall.vA, wallVec) / (wallLength * wallLength), 0.0f, 1.0f);\n\n\tglm::vec2 interpolatedNormal = glm::normalize(glm::mix(ray.wall.normalVA, ray.wall.normalVB, t));\n\n\tif (glm::dot(ray.wall.normal, ray.dir) > 0.0f) {\n\t\tinterpolatedNormal = -interpolatedNormal;\n\t}\n\n\tfloat r0 = ((airIOR - ray.wall.IOR) / (airIOR + ray.wall.IOR)) * ((airIOR - ray.wall.IOR) / (airIOR + ray.wall.IOR));\n\n\tfloat cosThetaI = -glm::dot(ray.dir, interpolatedNormal);\n\n\tcosThetaI = glm::clamp(cosThetaI, 0.0f, 1.0f);\n\n\tfloat rTheta = r0 + (1 - r0) * std::pow(1 - cosThetaI, 5.0f);\n\n\tif (getRandomFloat() > rTheta) {\n\t\tray.reflectSpecular = false;\n\t\treturn;\n\t}\n\n\tfloat roughness = ray.wall.specularRoughness;\n\tfloat maxSpreadAngle = glm::radians(90.0f * roughness);\n\n\tfloat randAngle = (static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * 2.0f * maxSpreadAngle - maxSpreadAngle;\n\n\tglm::vec2 perfectReflection = ray.dir - 2.0f * glm::dot(ray.dir, interpolatedNormal) * interpolatedNormal;\n\tglm::vec2 mixedReflection = glm::normalize(glm::mix(perfectReflection, interpolatedNormal, roughness));\n\tglm::vec2 rayDirection = rotateVec2(mixedReflection, randAngle);\n\n\tColor newColor = {\nstatic_cast<unsigned char>(ray.color.r * ray.wall.specularColor.r / 255.0f * absorptionInvBias),\nstatic_cast<unsigned char>(ray.color.g * ray.wall.specularColor.g / 255.0f * absorptionInvBias),\nstatic_cast<unsigned char>(ray.color.b * ray.wall.specularColor.b / 255.0f * absorptionInvBias),\nstatic_cast<unsigned char>(ray.color.a)\n\t};\n\n\tglm::vec2 newSource = ray.hitPoint + interpolatedNormal * lightBias;\n\n\tcopyRays.emplace_back(newSource, rayDirection, ray.bounceLevel + 1, newColor);\n\n\tLightRay& newRay = copyRays.back();\n\n\tprocessRayIntersection(newRay);\n}\n\n// This controls refraction\nvoid Lighting::refraction(int& currentBounce, LightRay& ray, std::vector<LightRay>& copyRays, std::vector<Wall>& walls) {\n\tray.refracted = true;\n\n\tglm::vec2 wallVec = ray.wall.vB - ray.wall.vA;\n\tfloat wallLength = glm::length(wallVec);\n\tfloat t = glm::clamp(glm::dot(ray.hitPoint - ray.wall.vA, wallVec) / (wallLength * wallLength), 0.0f, 1.0f);\n\tglm::vec2 interpolatedNormal = glm::normalize(glm::mix(ray.wall.normalVA, ray.wall.normalVB, t));\n\n\tfloat n1, n2;\n\tbool entering;\n\n\tentering = glm::dot(interpolatedNormal, ray.dir) < 0.0f;\n\n\tif (!entering) {\n\t\tinterpolatedNormal = -interpolatedNormal;\n\t}\n\n\tn1 = ray.mediumIORStack.back();\n\tn2 = ray.wall.IOR;\n\n\tif (!entering && ray.mediumIORStack.size() > 1) {\n\t\tn2 = ray.mediumIORStack[ray.mediumIORStack.size() - 2];\n\t}\n\n\tstd::vector<std::pair<float, Color>> activeChannels;\n\n\tColor newColor = {\n\t\t\tstatic_cast<unsigned char>(ray.color.r * ray.wall.refractionColor.r / 255.0f * absorptionInvBias),\n\t\t\tstatic_cast<unsigned char>(ray.color.g * ray.wall.refractionColor.g / 255.0f * absorptionInvBias),\n\t\t\tstatic_cast<unsigned char>(ray.color.b * ray.wall.refractionColor.b / 255.0f * absorptionInvBias),\n\t\t\tstatic_cast<unsigned char>(ray.color.a)\n\t};\n\n\tfloat dispersionStrength = ray.wall.dispersionStrength;\n\n\tif (!entering) {\n\t\tnewColor = ray.color;\n\t\tdispersionStrength *= -1.0f;\n\t}\n\n\tif (entering && isDispersionEnabled && ray.wall.dispersionStrength > 0.0f && !ray.hasBeenDispersed) {\n\t\tactiveChannels = {\n\t\t\t{1.0f - dispersionStrength, {newColor.r, 0, 0, ray.color.a}}, // Red\n\t\t\t{1.0f,                      {0, newColor.g, 0, ray.color.a}}, // Green\n\t\t\t{1.0f + dispersionStrength, {0, 0, newColor.b, ray.color.a}}  // Blue\n\t\t};\n\n\t\tray.hasBeenDispersed = true;\n\t}\n\telse {\n\t\tfloat scale = 1.0f;\n\n\t\tif (isDispersionEnabled && ray.wall.dispersionStrength > 0.0f && !entering) {\n\t\t\tif (ray.color.r > ray.color.g && ray.color.r > ray.color.b) {\n\t\t\t\tscale = 1.0f - dispersionStrength; // Red ray\n\t\t\t}\n\t\t\telse if (ray.color.g > ray.color.r && ray.color.g > ray.color.b) {\n\t\t\t\tscale = 1.0f; // Green ray\n\t\t\t}\n\t\t\telse {\n\t\t\t\tscale = 1.0f + dispersionStrength; // Blue ray\n\t\t\t}\n\t\t}\n\n\t\tactiveChannels = {\n\t\t\t{scale, newColor}\n\t\t};\n\t}\n\n\tfor (const auto& [scale, channelColor] : activeChannels) {\n\n\t\tfloat dispersedN2 = n2 * scale;\n\t\tfloat eta = n1 / dispersedN2;\n\n\t\tfloat cosThetaI = -glm::dot(ray.dir, interpolatedNormal);\n\t\tfloat sin2ThetaT = eta * eta * (1.0f - cosThetaI * cosThetaI);\n\n\t\tif (sin2ThetaT > 1.0f || getRandomFloat() > ray.wall.refractionAmount) {\n\t\t\tray.refracted = false;\n\t\t\tif (isSpecularEnabled) {\n\t\t\t\tspecularReflection(currentBounce, ray, copyRays, walls);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tfloat cosThetaT = sqrtf(1.0f - sin2ThetaT);\n\t\tglm::vec2 refractedDir = eta * ray.dir + (eta * cosThetaI - cosThetaT) * interpolatedNormal;\n\t\trefractedDir = glm::normalize(refractedDir);\n\n\t\tfloat roughness = ray.wall.refractionRoughness;\n\t\tfloat maxSpreadAngle = glm::radians(90.0f * roughness);\n\t\tfloat randAngle = (static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * 2.0f * maxSpreadAngle - maxSpreadAngle;\n\t\trefractedDir = rotateVec2(refractedDir, randAngle);\n\n\t\tglm::vec2 newSource = ray.hitPoint - interpolatedNormal * lightBias;\n\n\t\tcopyRays.emplace_back(newSource, refractedDir, ray.bounceLevel + 1, channelColor);\n\n\t\tLightRay& newRay = copyRays.back();\n\n\t\tnewRay.mediumIORStack = ray.mediumIORStack;\n\n\t\tif (entering) {\n\t\t\tnewRay.mediumIORStack.push_back(n2);\n\t\t}\n\t\telse if (newRay.mediumIORStack.size() > 1) {\n\t\t\tnewRay.mediumIORStack.pop_back();\n\t\t}\n\n\t\tnewRay.hasBeenDispersed = true;\n\n\t\tprocessRayIntersection(newRay);\n\t}\n}\n\n// UNFINISHED VOLUME SCATTERING CODE. I might work on it sometime in the future\n\n//void Lighting::volumeScatter(int& currentBounce, LightRay& ray, std::vector<LightRay>& copyRays, std::vector<Wall>& walls) {\n//\n//\tglm::vec2 wallVec = ray.wall.vB - ray.wall.vA;\n//\tfloat wallLength = glm::length(wallVec);\n//\tfloat t = glm::clamp(glm::dot(ray.hitPoint - ray.wall.vA, wallVec) / (wallLength * wallLength), 0.0f, 1.0f);\n//\n//\tglm::vec2 interpolatedNormal = glm::normalize(glm::mix(ray.wall.normalVA, ray.wall.normalVB, t));\n//\n//\tbool entering = glm::dot(interpolatedNormal, ray.dir) < 0.0f;\n//\n//\tif (glm::dot(ray.wall.normal, ray.dir) > 0.0f) {\n//\t\tinterpolatedNormal = -interpolatedNormal;\n//\t}\n//\n//\tfloat spreadMultiplier = 0.55f;\n//\tfloat maxSpreadAngle = glm::radians(90.0f * spreadMultiplier);\n//\n//\tfloat randAngle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * PI;\n//\n//\tglm::vec2 rayDirection = glm::vec2(cos(randAngle), sin(randAngle));\n//\n//\tbool enterAfterBounce = glm::dot(rayDirection, ray.dir) < 0.0f;\n//\n//\tif (enterAfterBounce) {\n//\t\tinterpolatedNormal = -interpolatedNormal;\n//\t}\n//\n//\tColor newColor = {\n//static_cast<unsigned char>(ray.color.r * ray.wall.baseColor.r / 255.0f),\n//static_cast<unsigned char>(ray.color.g * ray.wall.baseColor.g / 255.0f),\n//static_cast<unsigned char>(ray.color.b * ray.wall.baseColor.b / 255.0f),\n//static_cast<unsigned char>(ray.color.a)\n//\t};\n//\n//\tglm::vec2 newSource = ray.hitPoint - interpolatedNormal * lightBias;\n//\n//\tif (ray.hasBeenScattered && !ray.hasHit) {\n//\t\tnewSource = ray.scatterSource + ray.maxLength * ray.dir;\n//\t}\n//\n//\tcopyRays.emplace_back(newSource, rayDirection, ray.bounceLevel + 1, newColor);\n//\n//\tLightRay& newRay = copyRays.back();\n//\n//\tif (!enterAfterBounce) {\n//\t\tnewRay.hasBeenScattered = true;\n//\t\tnewRay.maxLength = 50.0f;\n//\t}\n//\telse {\n//\t\tnewRay.hasBeenScattered = false;\n//\t\tnewRay.maxLength = 10000.0f;\n//\t}\n//\n//\tif (glm::dot(ray.wall.normal, ray.dir) > 0.0f && ray.hasBeenScattered && ray.hasHit) {\n//\t\tnewRay.hasBeenScattered = false;\n//\t\tnewRay.maxLength = 10000.0f;\n//\t}\n//\n//\n//\tif (!ray.hasBeenScattered) {\n//\t\tnewRay.scatterSource = ray.hitPoint;\n//\t}\n//\telse {\n//\t\tnewRay.scatterSource = newSource;\n//\t}\n//\n//\tprocessRayIntersection(newRay);\n//}\n\n// This controls diffuse lighting\nvoid Lighting::diffuseLighting(int& currentBounce, LightRay& ray, std::vector<LightRay>& copyRays, std::vector<Wall>& walls) {\n\n\tglm::vec2 wallVec = ray.wall.vB - ray.wall.vA;\n\tfloat wallLength = glm::length(wallVec);\n\tfloat t = glm::clamp(glm::dot(ray.hitPoint - ray.wall.vA, wallVec) / (wallLength * wallLength), 0.0f, 1.0f);\n\n\tglm::vec2 interpolatedNormal = glm::normalize(glm::mix(ray.wall.normalVA, ray.wall.normalVB, t));\n\n\tif (glm::dot(ray.wall.normal, ray.dir) > 0.0f) {\n\t\tinterpolatedNormal = -interpolatedNormal;\n\t}\n\n\tfloat spreadMultiplier = 0.95f;\n\tfloat maxSpreadAngle = glm::radians(90.0f * spreadMultiplier);\n\n\tfloat randAngle = (static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * 2.0f * maxSpreadAngle - maxSpreadAngle;\n\n\tglm::vec2 rayDirection = rotateVec2(interpolatedNormal, randAngle);\n\n\tColor newColor = {\nstatic_cast<unsigned char>(ray.color.r * ray.wall.baseColor.r / 255.0f * absorptionInvBias),\nstatic_cast<unsigned char>(ray.color.g * ray.wall.baseColor.g / 255.0f * absorptionInvBias),\nstatic_cast<unsigned char>(ray.color.b * ray.wall.baseColor.b / 255.0f * absorptionInvBias),\nstatic_cast<unsigned char>(ray.color.a)\n\t};\n\n\tglm::vec2 newSource = ray.hitPoint + interpolatedNormal * lightBias;\n\n\tcopyRays.emplace_back(newSource, rayDirection, ray.bounceLevel + 1, newColor);\n\n\tLightRay& newRay = copyRays.back();\n\n\tprocessRayIntersection(newRay);\n}\n\nint emissionWallsAmount = 0;\n\nvoid Lighting::emission() {\n\tint emissionWallsAmount = 0;\n\n\tfor (Wall& w : walls) {\n\t\tif (w.emissionColor.a > 0.0f) {\n\t\t\temissionWallsAmount++;\n\t\t}\n\t}\n\n\tif (emissionWallsAmount == 0) {\n\t\treturn;\n\t}\n\n\tint totalRays = sampleRaysAmount / emissionWallsAmount;\n\n\ttotalRays = std::max(totalRays, 3);\n\n\tfor (Wall& w : walls) {\n\t\tif (w.emissionColor.a <= 0.0f) continue;\n\n\t\tfor (int i = 0; i < totalRays; i++) {\n\t\t\tfloat maxSpreadAngle = glm::radians(90.0f * 0.99f);\n\t\t\tfloat randAngle = getRandomFloat() * 2.0f * maxSpreadAngle - maxSpreadAngle;\n\n\t\t\tglm::vec2 d = w.vB - w.vA;\n\t\t\tfloat length = glm::length(d);\n\t\t\tglm::vec2 dNormal = d / length;\n\n\t\t\tfloat t = getRandomFloat();\n\t\t\tfloat bias = 0.01f;\n\n\t\t\tglm::vec2 source = w.vA + d * t + w.normal * bias;\n\n\t\t\tglm::vec2 rayDirection = rotateVec2(dNormal, randAngle);\n\t\t\trayDirection = glm::vec2(rayDirection.y, -rayDirection.x);\n\n\t\t\trays.emplace_back(\n\t\t\t\tsource,\n\t\t\t\trayDirection,\n\t\t\t\t1,\n\t\t\t\tw.emissionColor\n\t\t\t);\n\t\t}\n\t}\n}\n\nvoid Lighting::lightRendering(UpdateParameters& myParam) {\n\n\tif (currentSamples <= maxSamples) {\n\n\t\trays.clear();\n\n\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\tpointLight.pointLightLogic(sampleRaysAmount, currentSamples, maxSamples, rays);\n\t\t}\n\n\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\tareaLight.areaLightLogic(sampleRaysAmount, rays);\n\t\t}\n\n\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\tconeLight.coneLightLogic(sampleRaysAmount, rays);\n\t\t}\n\n\t\tif (isEmissionEnabled) {\n\t\t\temission();\n\t\t}\n\n#pragma omp parallel for\n\t\tfor (LightRay& ray : rays) {\n\t\t\tprocessRayIntersection(ray);\n\t\t}\n\n\t\tfor (int bounce = 1; bounce <= maxBounces; bounce++) {\n\t\t\tstd::vector<LightRay> nextBounceRays;\n\n\t\t\tfor (LightRay& ray : rays) {\n\t\t\t\tif ((ray.hasHit || ray.hasBeenScattered) && ray.bounceLevel == bounce) {\n\n\t\t\t\t\tif (isSpecularEnabled && ray.wall.specularColorVal > 0.0f) {\n\t\t\t\t\t\tspecularReflection(bounce, ray, nextBounceRays, walls);\n\t\t\t\t\t}\n\t\t\t\t\tif (isRefractionEnabled && !ray.reflectSpecular && ray.wall.refractionColorVal > 0.0f) {\n\t\t\t\t\t\trefraction(bounce, ray, nextBounceRays, walls);\n\t\t\t\t\t}\n\t\t\t\t\tif (isDiffuseEnabled && ray.wall.refractionAmount < 1.0f &&\n\t\t\t\t\t\t!ray.reflectSpecular && !ray.refracted) {\n\t\t\t\t\t\tdiffuseLighting(bounce, ray, nextBounceRays, walls);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trays.insert(rays.end(), nextBounceRays.begin(), nextBounceRays.end());\n\t\t}\n\n\t\tcurrentSamples++;\n\t}\n}\n\nvoid Lighting::drawMisc(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos;\n\n\tprocessApparentColor();\n\n\t//Draw selection box\n\tif (IO::mouseDown(0) && isBoxSelecting) {\n\t\tDrawRectangleV({ boxX, boxY }, { boxWidth, boxHeight }, { 40, 40, 40, 160 });\n\t\tDrawRectangleLinesEx({ boxX, boxY, boxWidth, boxHeight }, 1.6f, WHITE);\n\t}\n\n\t// Draw circle spawn guide\n\tif (IO::mouseDown(0) && myVar.toolCircle) {\n\n\t\tif (!shapes.empty()) {\n\n\t\t\tif (shapes.back().isBeingSpawned) {\n\n\t\t\t\tShape& shape = shapes.back();\n\n\t\t\t\tfloat radius = glm::length(shape.h2 - shape.h1);\n\n\t\t\t\tfor (int i = 0; i < shape.circleSegments; ++i) {\n\t\t\t\t\tfloat theta1 = (2.0f * PI * i) / shape.circleSegments;\n\t\t\t\t\tfloat theta2 = (2.0f * PI * (i + 1)) / shape.circleSegments;\n\n\t\t\t\t\tglm::vec2 vA = {\n\t\t\t\t\t\tshape.h1.x + cos(theta1) * radius,\n\t\t\t\t\t\tshape.h1.y + sin(theta1) * radius\n\t\t\t\t\t};\n\t\t\t\t\tglm::vec2 vB = {\n\t\t\t\t\t\tshape.h1.x + cos(theta2) * radius,\n\t\t\t\t\t\tshape.h1.y + sin(theta2) * radius\n\t\t\t\t\t};\n\t\t\t\t\tDrawLineV({ vA.x, vA.y }, { vB.x, vB.y }, WHITE);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Draw lens spawn guide\n\tif (myVar.toolLens) {\n\n\t\tif (!shapes.empty()) {\n\n\t\t\tif (shapes.back().isBeingSpawned) {\n\n\t\t\t\tShape& shape = shapes.back();\n\n\t\t\t\tif (shape.helpers.size() == 1) {\n\t\t\t\t\tDrawLineV({ shape.h1.x, shape.h1.y }, { shape.h2.x, shape.h2.y }, WHITE);\n\n\t\t\t\t\tDrawCircleV({ shape.h2.x, shape.h2.y }, 5.0f, PURPLE);\n\t\t\t\t}\n\n\t\t\t\tif (!symmetricalLens) {\n\t\t\t\t\tif (shape.helpers.size() >= 2) {\n\t\t\t\t\t\tDrawLineV({ shape.helpers.at(0).x, shape.helpers.at(0).y }, { shape.helpers.at(1).x, shape.helpers.at(1).y }, WHITE);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (shape.helpers.size() == 2) {\n\t\t\t\t\t\tDrawLineV({ shape.helpers.at(0).x, shape.helpers.at(0).y }, { shape.helpers.at(1).x, shape.helpers.at(1).y }, WHITE);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tglm::vec2 thirdHelperPos = shape.h2;\n\t\t\t\tglm::vec2 otherSide = shape.h2;\n\t\t\t\tif (shape.helpers.size() == 2) {\n\n\t\t\t\t\tglm::vec2 tangent = glm::normalize(shape.helpers.at(0) - shape.helpers.at(1));\n\n\t\t\t\t\tglm::vec2 normal = glm::vec2(tangent.y, -tangent.x);\n\n\t\t\t\t\tglm::vec2 offset = shape.h2 - shape.helpers.at(1);\n\n\t\t\t\t\tfloat dist;\n\n\t\t\t\t\tdist = glm::dot(offset, normal);\n\t\t\t\t\tshape.tempDist = dist;\n\n\t\t\t\t\tthirdHelperPos = shape.helpers.at(1) + dist * normal;\n\n\t\t\t\t\totherSide = shape.helpers.at(0) + dist * normal;\n\n\t\t\t\t\tif (shape.helpers.size() == 2) {\n\t\t\t\t\t\tDrawLineV({ shape.helpers.at(1).x, shape.helpers.at(1).y }, { thirdHelperPos.x, thirdHelperPos.y }, WHITE);\n\n\t\t\t\t\t\tDrawLineV({ shape.helpers.at(0).x, shape.helpers.at(0).y }, { otherSide.x, otherSide.y }, WHITE);\n\t\t\t\t\t}\n\n\t\t\t\t\tDrawCircleV({ thirdHelperPos.x, thirdHelperPos.y }, 5.0f, PURPLE);\n\t\t\t\t}\n\n\t\t\t\tif (shape.helpers.size() >= 3) {\n\t\t\t\t\tDrawLineV({ shape.helpers.at(1).x, shape.helpers.at(1).y }, { shape.helpers.at(2).x, shape.helpers.at(2).y }, WHITE);\n\t\t\t\t}\n\n\t\t\t\tfor (int i = 0; i < shape.lensSegments; i++) {\n\t\t\t\t\tfloat t1 = static_cast<float>(i) / shape.lensSegments;\n\t\t\t\t\tfloat t2 = static_cast<float>((i + 1)) / shape.lensSegments;\n\n\t\t\t\t\tfloat angle1 = shape.startAngle + t1 * (shape.endAngle - shape.startAngle);\n\t\t\t\t\tfloat angle2 = shape.startAngle + t2 * (shape.endAngle - shape.startAngle);\n\n\t\t\t\t\tglm::vec2 arcP1 = shape.center + glm::vec2(cos(angle1), sin(angle1)) * shape.radius;\n\t\t\t\t\tglm::vec2 arcP2 = shape.center + glm::vec2(cos(angle2), sin(angle2)) * shape.radius;\n\n\t\t\t\t\tif (shape.helpers.size() == 3 && !shape.fourthHelper) {\n\t\t\t\t\t\tDrawLineV({ arcP1.x, arcP1.y }, { arcP2.x, arcP2.y }, WHITE);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (symmetricalLens) {\n\t\t\t\t\tfor (int i = 0; i < shape.lensSegments; i++) {\n\t\t\t\t\t\tfloat t1Symmetry = static_cast<float>(i) / shape.lensSegments;\n\t\t\t\t\t\tfloat t2Symmetry = static_cast<float>((i + 1)) / shape.lensSegments;\n\n\t\t\t\t\t\tfloat angle1Symmetry = shape.startAngleSymmetry + t1Symmetry * (shape.endAngleSymmetry - shape.startAngleSymmetry);\n\t\t\t\t\t\tfloat angle2Symmetry = shape.startAngleSymmetry + t2Symmetry * (shape.endAngleSymmetry - shape.startAngleSymmetry);\n\n\t\t\t\t\t\tglm::vec2 arcP1Symmetry = shape.centerSymmetry + glm::vec2(cos(angle1Symmetry), sin(angle1Symmetry)) * shape.radiusSymmetry;\n\t\t\t\t\t\tglm::vec2 arcP2Symmetry = shape.centerSymmetry + glm::vec2(cos(angle2Symmetry), sin(angle2Symmetry)) * shape.radiusSymmetry;\n\n\t\t\t\t\t\tif (shape.helpers.size() == 3 && !shape.fourthHelper) {\n\t\t\t\t\t\t\tDrawLineV({ arcP1Symmetry.x, arcP1Symmetry.y }, { arcP2Symmetry.x, arcP2Symmetry.y }, WHITE);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (shape.helpers.size() >= 3) {\n\t\t\t\t\tDrawLineV({ shape.helpers.at(0).x, shape.helpers.at(0).y }, { shape.arcEnd.x, shape.arcEnd.y }, WHITE);\n\t\t\t\t}\n\n\t\t\t\tif (!shape.helpers.empty()) {\n\t\t\t\t\tfor (auto& helper : shape.helpers) {\n\t\t\t\t\t\tshape.drawHelper(helper);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Draw wall helpers\n\tif (myVar.toolMoveOptics) {\n\t\tfor (Wall& wall : walls) {\n\t\t\tglm::vec2 dA = wall.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = wall.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= helperMinDist && !wall.isShapeWall) {\n\t\t\t\twall.drawHelper(wall.vA);\n\t\t\t}\n\t\t\tif (distB <= helperMinDist && !wall.isShapeWall) {\n\t\t\t\twall.drawHelper(wall.vB);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Draw shape helpers\n\tif (!shapes.empty()) {\n\t\tfor (size_t i = 0; i < shapes.size(); i++) {\n\n\t\t\tif ((shapes[i].drawHoverHelpers || selectedHelper != -1 && selectedShape != -1) && !isAnyShapeBeingSpawned && myVar.toolMoveOptics) {\n\t\t\t\tfor (glm::vec2& helper : shapes[i].helpers) {\n\t\t\t\t\tshapes[i].drawHelper(helper);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Draw light helpers\n\tif (myVar.toolMoveOptics) {\n\t\tfor (AreaLight& areaLight : areaLights) {\n\t\t\tglm::vec2 dA = areaLight.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = areaLight.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= helperMinDist) {\n\t\t\t\tareaLight.drawHelper(areaLight.vA);\n\t\t\t}\n\t\t\tif (distB <= helperMinDist) {\n\t\t\t\tareaLight.drawHelper(areaLight.vB);\n\t\t\t}\n\t\t}\n\n\t\tfor (ConeLight& coneLight : coneLights) {\n\t\t\tglm::vec2 dA = coneLight.vA - mouseWorldPos;\n\t\t\tglm::vec2 dB = coneLight.vB - mouseWorldPos;\n\n\t\t\tfloat distA = glm::length(dA);\n\t\t\tfloat distB = glm::length(dB);\n\n\t\t\tif (distA <= helperMinDist + 40.0f) {\n\t\t\t\tconeLight.drawHelper(coneLight.vA);\n\t\t\t\tconeLight.drawHelper(coneLight.vB);\n\t\t\t}\n\t\t\tif (distB <= helperMinDist + 40.0f) {\n\t\t\t\tconeLight.drawHelper(coneLight.vB);\n\t\t\t\tconeLight.drawHelper(coneLight.vA);\n\t\t\t}\n\t\t}\n\n\t\tfor (PointLight& pointLight : pointLights) {\n\t\t\tglm::vec2 dA = pointLight.pos - mouseWorldPos;\n\n\t\t\tfloat dist = glm::length(dA);\n\n\t\t\tif (dist <= helperMinDist) {\n\t\t\t\tpointLight.drawHelper(pointLight.pos);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (PointLight& pointLight : pointLights) {\n\t\tif (pointLight.isSelected) {\n\t\t\tpointLight.drawHelper(pointLight.pos);\n\t\t}\n\t}\n\n\tfor (ConeLight& coneLight : coneLights) {\n\t\tif (coneLight.isSelected) {\n\t\t\tconeLight.drawHelper(coneLight.vA);\n\t\t\tconeLight.drawHelper(coneLight.vB);\n\t\t}\n\t}\n}"
  },
  {
    "path": "GalaxyEngine/src/Physics/morton.cpp",
    "content": "#include \"Physics/morton.h\"\n\nuint64_t Morton::scaleToGrid(float pos, float minVal, float maxVal) {\n    if (maxVal <= minVal) return 0;\n    float clamped = std::clamp(pos, minVal, maxVal);\n    float normalized = (clamped - minVal) / (maxVal - minVal);\n\n    uint64_t scaled = static_cast<uint64_t>(normalized * 262144.0f);\n    return std::min(scaled, uint64_t(262143));\n}\n\nuint64_t Morton::spreadBits(uint64_t x) {\n    x &= 0x3FFFF;                     // keep only 18 bits\n    x = (x | (x << 16)) & 0x0000FFFF0000FFFFULL;\n    x = (x | (x << 8)) & 0x00FF00FF00FF00FFULL;\n    x = (x | (x << 4)) & 0x0F0F0F0F0F0F0F0FULL;\n    x = (x | (x << 2)) & 0x3333333333333333ULL;\n    x = (x | (x << 1)) & 0x5555555555555555ULL;\n    return static_cast<uint64_t>(x);\n}\n\nuint64_t Morton::morton2D(uint64_t x, uint64_t y) {\n    return static_cast<uint64_t>(spreadBits(x))\n        | (static_cast<uint64_t>(spreadBits(y)) << 1);\n}\n\nvoid Morton::computeMortonKeys(std::vector<ParticlePhysics>& pParticles,\n    glm::vec3& posSize)\n{\n    const float maxX = posSize.x + std::max(posSize.z, 1e-6f);\n    const float maxY = posSize.y + std::max(posSize.z, 1e-6f);\n\n    for (auto& pParticle : pParticles) {\n        uint64_t ix = scaleToGrid(pParticle.pos.x, posSize.x, maxX);\n        uint64_t iy = scaleToGrid(pParticle.pos.y, posSize.y, maxY);\n        pParticle.mortonKey = morton2D(ix, iy);\n    }\n}\n\nvoid Morton::sortParticlesByMortonKey(\n    std::vector<ParticlePhysics>& pParticles,\n    std::vector<ParticleRendering>& rParticles) {\n    const size_t n = pParticles.size();\n\n    indicesBuffer.resize(n);\n    pSortedBuffer.resize(n);\n    rSortedBuffer.resize(n);\n\n    std::iota(indicesBuffer.begin(), indicesBuffer.end(), 0);\n\n    std::sort(indicesBuffer.begin(), indicesBuffer.end(),\n        [&](size_t a, size_t b) {\n            return pParticles[a].mortonKey < pParticles[b].mortonKey;\n        });\n\n    for (size_t i = 0; i < n; i++) {\n        pSortedBuffer[i] = pParticles[indicesBuffer[i]];\n        rSortedBuffer[i] = rParticles[indicesBuffer[i]];\n    }\n\n    std::swap(pParticles, pSortedBuffer);\n    std::swap(rParticles, rSortedBuffer);\n}\n\n// ---- 3D Implementation ---- //\n\nuint64_t Morton::scaleToGrid3D(float pos, float minVal, float maxVal) {\n    if (maxVal <= minVal) return 0;\n\n    float clamped = std::clamp(pos, minVal, maxVal);\n    float normalized = (clamped - minVal) / (maxVal - minVal);\n\n    uint64_t scaled = static_cast<uint64_t>(normalized * 2097151.0f);\n    return std::min(scaled, uint64_t(2097151));\n}\n\nuint64_t Morton::spreadBits3D(uint64_t x) {\n    uint64_t result = 0;\n\n    for (int i = 0; i < 21; ++i) {\n        uint64_t bit = (x >> i) & 1ULL;\n        result |= (bit << (3 * i));\n    }\n\n    return result;\n}\n\nuint64_t Morton::morton3D(uint64_t x, uint64_t y, uint64_t z) {\n    return spreadBits3D(x) | (spreadBits3D(y) << 1) | (spreadBits3D(z) << 2);\n}\n\nvoid Morton::computeMortonKeys3D(std::vector<ParticlePhysics3D>& pParticles,\n    const glm::vec4& boundingBox) {\n\n    float minX = boundingBox.x;\n    float minY = boundingBox.y;\n    float minZ = boundingBox.z;\n\n    float size = boundingBox.w;\n\n    float maxX = minX + size;\n    float maxY = minY + size;\n    float maxZ = minZ + size;\n\n    for (auto& pParticle : pParticles) {\n        uint64_t ix = scaleToGrid3D(pParticle.pos.x, minX, maxX);\n        uint64_t iy = scaleToGrid3D(pParticle.pos.y, minY, maxY);\n        uint64_t iz = scaleToGrid3D(pParticle.pos.z, minZ, maxZ);\n\n        pParticle.mortonKey = morton3D(ix, iy, iz);\n    }\n}\n\nvoid Morton::sortParticlesByMortonKey3D(\n    std::vector<ParticlePhysics3D>& pParticles,\n    std::vector<ParticleRendering3D>& rParticles) {\n\n    const size_t n = pParticles.size();\n\n    indicesBuffer3D.resize(n);\n    pSortedBuffer3D.resize(n);\n    rSortedBuffer3D.resize(n);\n\n    std::iota(indicesBuffer3D.begin(), indicesBuffer3D.end(), 0);\n\n    std::sort(indicesBuffer3D.begin(), indicesBuffer3D.end(),\n        [&](size_t a, size_t b) {\n            return pParticles[a].mortonKey < pParticles[b].mortonKey;\n        });\n\n    for (size_t i = 0; i < n; i++) {\n        pSortedBuffer3D[i] = pParticles[indicesBuffer3D[i]];\n        rSortedBuffer3D[i] = rParticles[indicesBuffer3D[i]];\n    }\n\n    std::swap(pParticles, pSortedBuffer3D);\n    std::swap(rParticles, rSortedBuffer3D);\n}"
  },
  {
    "path": "GalaxyEngine/src/Physics/physics.cpp",
    "content": "#include \"Physics/physics.h\"\n\n// This is used in predict trajectory inside particleSpawning.cpp\nglm::vec2 Physics::calculateForceFromGridOld(std::vector<ParticlePhysics>& pParticles, UpdateVariables& myVar, ParticlePhysics& pParticle) {\n\n\tglm::vec2 totalForce = { 0.0f,0.0f };\n\n\tuint32_t gridIdx = 0;\n\tconst uint32_t nodeCount = static_cast<uint32_t>(globalNodes.size());\n\n\tconst float thetaSq = myVar.theta * myVar.theta;\n\tconst float softeningSq = myVar.softening * myVar.softening;\n\tconst float Gf = static_cast<float>(myVar.G);\n\tconst float pmass = pParticle.mass;\n\tauto* particlesPtr = pParticles.data();\n\n\twhile (gridIdx < nodeCount) {\n\t\tNode& grid = globalNodes[gridIdx];\n\n\t\tfloat gridMass = grid.gridMass;\n\t\tif (gridMass <= 0.0f) {\n\t\t\tgridIdx += grid.next + 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst glm::vec2 gridCOM = grid.centerOfMass;\n\t\tconst float gridSize = grid.size;\n\n\t\tglm::vec2 d = gridCOM - pParticle.pos;\n\n\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\td.x -= myVar.domainSize.x * ((d.x > myVar.halfDomainWidth) - (d.x < -myVar.halfDomainWidth));\n\t\t\td.y -= myVar.domainSize.y * ((d.y > myVar.halfDomainHeight) - (d.y < -myVar.halfDomainHeight));\n\t\t}\n\n\t\tfloat distanceSq = d.x * d.x + d.y * d.y + softeningSq;\n\n\t\tbool isSubgridsEmty = true;\n\t\tuint32_t s00 = grid.subGrids[0][0];\n\t\tuint32_t s01 = grid.subGrids[0][1];\n\t\tuint32_t s10 = grid.subGrids[1][0];\n\t\tuint32_t s11 = grid.subGrids[1][1];\n\t\tif (s00 != UINT32_MAX || s01 != UINT32_MAX || s10 != UINT32_MAX || s11 != UINT32_MAX) {\n\t\t\tisSubgridsEmty = false;\n\t\t}\n\n\t\tfloat gridSizeSq = gridSize * gridSize;\n\n\t\tif ((gridSizeSq < thetaSq * distanceSq) || isSubgridsEmty) {\n\n\t\t\tif ((grid.endIndex - grid.startIndex) == 1) {\n\t\t\t\tconst ParticlePhysics& other = particlesPtr[grid.startIndex];\n\t\t\t\tif (std::abs(other.pos.x - pParticle.pos.x) < 0.001f && std::abs(other.pos.y - pParticle.pos.y) < 0.001f) {\n\t\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfloat invDistance = 1.0f / sqrtf(distanceSq);\n\t\t\tfloat invDist2 = invDistance * invDistance;\n\t\t\tfloat invDist3 = invDist2 * invDistance;\n\n\t\t\tfloat forceMagnitude = Gf * pmass * gridMass * invDist3;\n\t\t\ttotalForce += d * forceMagnitude;\n\n\t\t\tif (myVar.isTempEnabled) {\n\t\t\t\tuint32_t count = grid.endIndex - grid.startIndex;\n\t\t\t\tif (count > 0) {\n\t\t\t\t\tfloat gridAverageTemp = grid.gridTemp / static_cast<float>(count);\n\t\t\t\t\tfloat temperatureDifference = gridAverageTemp - pParticle.temp;\n\n\t\t\t\t\tfloat distance = 0.0f;\n\t\t\t\t\tif (distanceSq > 1e-16f) distance = 1.0f / invDistance;\n\t\t\t\t\tif (distance > 1e-8f) {\n\t\t\t\t\t\tfloat heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance;\n\t\t\t\t\t\tpParticle.temp += heatTransfer * myVar.timeFactor;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tgridIdx += grid.next + 1;\n\t\t}\n\t\telse {\n\t\t\t++gridIdx;\n\t\t}\n\t}\n\n\treturn totalForce;\n}\n\nvoid Physics::calculateForceFromGrid(UpdateVariables& myVar) {\n#pragma omp parallel for schedule(dynamic)\n\tfor (size_t i = 0; i < posX.size(); i++) {\n\t\tglm::vec2 totalForce = { 0.0f,0.0f };\n\n\t\tuint32_t gridIdx = 0;\n\t\tconst uint32_t nodeCount = static_cast<uint32_t>(globalNodes.size());\n\n\t\tconst float thetaSq = myVar.theta * myVar.theta;\n\t\tconst float softeningSq = myVar.softening * myVar.softening;\n\t\tconst float Gf = myVar.G;\n\t\tconst float pmass = mass[i];\n\n\t\twhile (gridIdx < nodeCount) {\n\t\t\tNode& grid = globalNodes[gridIdx];\n\n\t\t\tfloat gridMass = grid.gridMass;\n\t\t\tif (gridMass <= 0.0f) {\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst glm::vec2 gridCOM = grid.centerOfMass;\n\t\t\tconst float gridSize = grid.size;\n\n\t\t\tglm::vec2 d = gridCOM - glm::vec2{ posX[i], posY[i] };\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\td.x -= myVar.domainSize.x * ((d.x > myVar.halfDomainWidth) - (d.x < -myVar.halfDomainWidth));\n\t\t\t\td.y -= myVar.domainSize.y * ((d.y > myVar.halfDomainHeight) - (d.y < -myVar.halfDomainHeight));\n\t\t\t}\n\n\t\t\tfloat distanceSq = d.x * d.x + d.y * d.y + softeningSq;\n\n\t\t\tbool isSubgridsEmpty = true;\n\t\t\tuint32_t s00 = grid.subGrids[0][0];\n\t\t\tuint32_t s01 = grid.subGrids[0][1];\n\t\t\tuint32_t s10 = grid.subGrids[1][0];\n\t\t\tuint32_t s11 = grid.subGrids[1][1];\n\t\t\tif (s00 != UINT32_MAX || s01 != UINT32_MAX || s10 != UINT32_MAX || s11 != UINT32_MAX) {\n\t\t\t\tisSubgridsEmpty = false;\n\t\t\t}\n\n\t\t\tfloat gridSizeSq = gridSize * gridSize;\n\n\t\t\tif (gridSizeSq < thetaSq * distanceSq) {\n\n\t\t\t\tfloat invDistance = 1.0f / sqrtf(distanceSq);\n\t\t\t\tfloat invDist2 = invDistance * invDistance;\n\t\t\t\tfloat invDist3 = invDist2 * invDistance;\n\n\t\t\t\tfloat forceMagnitude = Gf * pmass * gridMass * invDist3;\n\t\t\t\ttotalForce += d * forceMagnitude;\n\n\t\t\t\tif (myVar.isTempEnabled) {\n\t\t\t\t\tuint32_t count = grid.endIndex - grid.startIndex;\n\t\t\t\t\tif (count > 0) {\n\t\t\t\t\t\tfloat gridAverageTemp = grid.gridTemp / static_cast<float>(count);\n\t\t\t\t\t\tfloat temperatureDifference = gridAverageTemp - temp[i];\n\n\t\t\t\t\t\tfloat distance = 0.0f;\n\t\t\t\t\t\tif (distanceSq > 1e-16f) distance = 1.0f / invDistance;\n\t\t\t\t\t\tif (distance > 1e-8f) {\n\t\t\t\t\t\t\tfloat heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance;\n\t\t\t\t\t\t\ttemp[i] += heatTransfer * myVar.timeFactor;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t}\n\t\t\telse if (isSubgridsEmpty) {\n\n\t\t\t\tfor (uint32_t j = grid.startIndex; j < grid.endIndex; ++j) {\n\t\t\t\t\tif (i == j) continue;\n\n\t\t\t\t\tglm::vec2 p2pd = glm::vec2{ posX[j], posY[j] } - glm::vec2{ posX[i], posY[i] };\n\n\t\t\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\t\t\tp2pd.x -= myVar.domainSize.x * ((p2pd.x > myVar.halfDomainWidth) - (p2pd.x < -myVar.halfDomainWidth));\n\t\t\t\t\t\tp2pd.y -= myVar.domainSize.y * ((p2pd.y > myVar.halfDomainHeight) - (p2pd.y < -myVar.halfDomainHeight));\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat p2pdistSq = p2pd.x * p2pd.x + p2pd.y * p2pd.y + softeningSq;\n\n\t\t\t\t\tfloat invDist = 1.0f / sqrtf(p2pdistSq);\n\t\t\t\t\tfloat invDist2 = invDist * invDist;\n\t\t\t\t\tfloat invDist3 = invDist2 * invDist;\n\n\t\t\t\t\tfloat forceMag = Gf * pmass * mass[j] * invDist3;\n\t\t\t\t\ttotalForce += p2pd * forceMag;\n\n\t\t\t\t\tif (myVar.isTempEnabled) {\n\t\t\t\t\t\tfloat tempDiff = temp[j] - temp[i];\n\t\t\t\t\t\tfloat p2pdist = 0.0f;\n\n\t\t\t\t\t\tif (p2pdistSq > 1e-16f) p2pdist = 1.0f / invDist;\n\t\t\t\t\t\tif (p2pdist > 1e-8f) {\n\t\t\t\t\t\t\tfloat heatTransfer = myVar.globalHeatConductivity * tempDiff / p2pdist;\n\t\t\t\t\t\t\ttemp[i] += heatTransfer * myVar.timeFactor;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t++gridIdx;\n\t\t\t}\n\t\t}\n\n\t\taccX[i] = totalForce.x / mass[i];\n\t\taccY[i] = totalForce.y / mass[i];\n\t}\n}\n\nvoid Physics::calculateForceFromGridAVX2(UpdateVariables& myVar) {\n#pragma omp parallel for schedule(dynamic)\n\tfor (size_t i = 0; i < posX.size(); i++) {\n\t\tglm::vec2 totalForce = { 0.0f,0.0f };\n\n\t\tuint32_t gridIdx = 0;\n\t\tconst uint32_t nodeCount = static_cast<uint32_t>(globalNodes.size());\n\n\t\tconst float thetaSq = myVar.theta * myVar.theta;\n\t\tconst float softeningSq = myVar.softening * myVar.softening;\n\t\tconst float Gf = myVar.G;\n\t\tconst float pmass = mass[i];\n\n\t\twhile (gridIdx < nodeCount) {\n\t\t\tNode& grid = globalNodes[gridIdx];\n\n\t\t\tfloat gridMass = grid.gridMass;\n\t\t\tif (gridMass <= 0.0f) {\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst glm::vec2 gridCOM = grid.centerOfMass;\n\t\t\tconst float gridSize = grid.size;\n\n\t\t\tglm::vec2 d = gridCOM - glm::vec2{ posX[i], posY[i] };\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\td.x -= myVar.domainSize.x * ((d.x > myVar.halfDomainWidth) - (d.x < -myVar.halfDomainWidth));\n\t\t\t\td.y -= myVar.domainSize.y * ((d.y > myVar.halfDomainHeight) - (d.y < -myVar.halfDomainHeight));\n\t\t\t}\n\n\t\t\tfloat distanceSq = d.x * d.x + d.y * d.y + softeningSq;\n\n\t\t\tbool isSubgridsEmpty = true;\n\t\t\tuint32_t s00 = grid.subGrids[0][0];\n\t\t\tuint32_t s01 = grid.subGrids[0][1];\n\t\t\tuint32_t s10 = grid.subGrids[1][0];\n\t\t\tuint32_t s11 = grid.subGrids[1][1];\n\t\t\tif (s00 != UINT32_MAX || s01 != UINT32_MAX || s10 != UINT32_MAX || s11 != UINT32_MAX) {\n\t\t\t\tisSubgridsEmpty = false;\n\t\t\t}\n\n\t\t\tfloat gridSizeSq = gridSize * gridSize;\n\n\t\t\tif (gridSizeSq < thetaSq * distanceSq) {\n\n\t\t\t\tfloat invDistance = 1.0f / sqrtf(distanceSq);\n\t\t\t\tfloat invDist2 = invDistance * invDistance;\n\t\t\t\tfloat invDist3 = invDist2 * invDistance;\n\n\t\t\t\tfloat forceMagnitude = Gf * pmass * gridMass * invDist3;\n\t\t\t\ttotalForce += d * forceMagnitude;\n\n\t\t\t\tif (myVar.isTempEnabled) {\n\t\t\t\t\tuint32_t count = grid.endIndex - grid.startIndex;\n\t\t\t\t\tif (count > 0) {\n\t\t\t\t\t\tfloat gridAverageTemp = grid.gridTemp / static_cast<float>(count);\n\t\t\t\t\t\tfloat temperatureDifference = gridAverageTemp - temp[i];\n\n\t\t\t\t\t\tfloat distance = 0.0f;\n\t\t\t\t\t\tif (distanceSq > 1e-16f) distance = 1.0f / invDistance;\n\t\t\t\t\t\tif (distance > 1e-8f) {\n\t\t\t\t\t\t\tfloat heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance;\n\t\t\t\t\t\t\ttemp[i] += heatTransfer * myVar.timeFactor;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t}\n\t\t\telse if (isSubgridsEmpty) {\n\n\t\t\t\t__m256 vxi = _mm256_set1_ps(posX[i]);\n\t\t\t\t__m256 vyi = _mm256_set1_ps(posY[i]);\n\n\t\t\t\t__m256 vsofteningSq = _mm256_set1_ps(softeningSq);\n\t\t\t\t__m256 vGfpmass = _mm256_set1_ps(Gf * pmass);\n\n\t\t\t\t__m256 vforceX = _mm256_setzero_ps();\n\t\t\t\t__m256 vforceY = _mm256_setzero_ps();\n\n\t\t\t\tuint32_t j = grid.startIndex;\n\n\t\t\t\tfor (; j + 7 < grid.endIndex; j += 8) {\n\n\t\t\t\t\t__m256 vxj = _mm256_loadu_ps(&posX[j]);\n\t\t\t\t\t__m256 vyj = _mm256_loadu_ps(&posY[j]);\n\t\t\t\t\t__m256 vmj = _mm256_loadu_ps(&mass[j]);\n\n\t\t\t\t\t__m256 vdx = _mm256_sub_ps(vxj, vxi);\n\t\t\t\t\t__m256 vdy = _mm256_sub_ps(vyj, vyi);\n\n\t\t\t\t\t__m256 vdx2 = _mm256_mul_ps(vdx, vdx);\n\t\t\t\t\t__m256 vdy2 = _mm256_mul_ps(vdy, vdy);\n\t\t\t\t\t__m256 vdistSq = _mm256_add_ps(_mm256_add_ps(vdx2, vdy2), vsofteningSq);\n\n\t\t\t\t\t__m256 vinvDist = _mm256_rsqrt_ps(vdistSq);\n\n\t\t\t\t\t__m256 vinvDist2 = _mm256_mul_ps(vinvDist, vinvDist);\n\t\t\t\t\t__m256 vinvDist3 = _mm256_mul_ps(vinvDist2, vinvDist);\n\n\t\t\t\t\t__m256 vforceMag = _mm256_mul_ps(_mm256_mul_ps(vGfpmass, vmj), vinvDist3);\n\n\t\t\t\t\t__m256 vmaskNotSelf = _mm256_cmp_ps(vdistSq, vsofteningSq, _CMP_NEQ_OQ);\n\n\t\t\t\t\tvforceMag = _mm256_and_ps(vforceMag, vmaskNotSelf);\n\n\t\t\t\t\tvforceX = _mm256_add_ps(vforceX, _mm256_mul_ps(vdx, vforceMag));\n\t\t\t\t\tvforceY = _mm256_add_ps(vforceY, _mm256_mul_ps(vdy, vforceMag));\n\t\t\t\t}\n\n\t\t\t\tfloat tempFx[8];\n\t\t\t\tfloat tempFy[8];\n\t\t\t\t_mm256_storeu_ps(tempFx, vforceX);\n\t\t\t\t_mm256_storeu_ps(tempFy, vforceY);\n\n\t\t\t\tfor (int k = 0; k < 8; ++k) {\n\t\t\t\t\ttotalForce.x += tempFx[k];\n\t\t\t\t\ttotalForce.y += tempFy[k];\n\t\t\t\t}\n\n\t\t\t\tfor (; j < grid.endIndex; ++j) {\n\t\t\t\t\tif (i == j) continue;\n\n\t\t\t\t\tglm::vec2 p2pd = glm::vec2{ posX[j], posY[j] } - glm::vec2{ posX[i], posY[i] };\n\t\t\t\t\tfloat p2pdistSq = p2pd.x * p2pd.x + p2pd.y * p2pd.y + softeningSq;\n\n\t\t\t\t\tfloat invDist = 1.0f / sqrtf(p2pdistSq);\n\t\t\t\t\tfloat invDist3 = invDist * invDist * invDist;\n\n\t\t\t\t\tfloat forceMag = Gf * pmass * mass[j] * invDist3;\n\t\t\t\t\ttotalForce += p2pd * forceMag;\n\t\t\t\t}\n\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t++gridIdx;\n\t\t\t}\n\t\t}\n\n\t\taccX[i] = totalForce.x / mass[i];\n\t\taccY[i] = totalForce.y / mass[i];\n\t}\n}\n\nvoid Physics::flattenParticles(std::vector<ParticlePhysics>& pParticles) {\n\n\tsize_t particleCount = pParticles.size();\n\n\tposX.resize(particleCount);\n\tposY.resize(particleCount);\n\taccX.resize(particleCount);\n\taccY.resize(particleCount);\n\tvelX.resize(particleCount);\n\tvelY.resize(particleCount);\n\tprevVelX.resize(particleCount);\n\tprevVelY.resize(particleCount);\n\tmass.resize(particleCount);\n\ttemp.resize(particleCount);\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < static_cast<int>(particleCount); i++) {\n\n\t\tconst auto& particle = pParticles[i];\n\n\t\tposX[i] = particle.pos.x;\n\t\tposY[i] = particle.pos.y;\n\n\t\taccX[i] = particle.acc.x;\n\t\taccY[i] = particle.acc.y;\n\n\t\tvelX[i] = particle.vel.x;\n\t\tvelY[i] = particle.vel.y;\n\n\t\tprevVelX[i] = particle.prevVel.x;\n\t\tprevVelY[i] = particle.prevVel.y;\n\n\t\tmass[i] = particle.mass;\n\n\t\ttemp[i] = particle.temp;\n\t}\n}\n\nvoid Physics::naiveGravity(std::vector<ParticlePhysics>& pParticles, UpdateVariables& myVar) {\n\tint n = static_cast<int>(posX.size());\n\tconst float* posXPtr = posX.data();\n\tconst float* posYPtr = posY.data();\n\tconst float* massPtr = mass.data();\n\tfloat* accXPtr = accX.data();\n\tfloat* accYPtr = accY.data();\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < n; i++) {\n\t\taccXPtr[i] = 0.0f;\n\t\taccYPtr[i] = 0.0f;\n\t}\n\n#pragma omp parallel for schedule(dynamic, 64)\n\tfor (int i = 0; i < n; i++) {\n\t\tfloat pix = posXPtr[i];\n\t\tfloat piy = posYPtr[i];\n\t\tfloat totalAccX = 0.0f;\n\t\tfloat totalAccY = 0.0f;\n\n\t\tfor (int j = 0; j < n; j++) {\n\t\t\tfloat dx = posXPtr[j] - pix;\n\t\t\tfloat dy = posYPtr[j] - piy;\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\tdx -= myVar.domainSize.x * ((dx > myVar.halfDomainWidth) - (dx < -myVar.halfDomainWidth));\n\t\t\t\tdy -= myVar.domainSize.y * ((dy > myVar.halfDomainHeight) - (dy < -myVar.halfDomainHeight));\n\t\t\t}\n\n\t\t\tfloat distSq = dx * dx + dy * dy + myVar.softening;\n\t\t\tfloat invDist = 1.0f / std::sqrt(distSq);\n\t\t\tfloat invDist3 = invDist * invDist * invDist;\n\t\t\tfloat factor = myVar.G * massPtr[j] * invDist3;\n\n\t\t\ttotalAccX += dx * factor;\n\t\t\ttotalAccY += dy * factor;\n\t\t}\n\n\t\taccXPtr[i] = totalAccX;\n\t\taccYPtr[i] = totalAccY;\n\t}\n}\n\nvoid Physics::naiveGravityAVX2(std::vector<ParticlePhysics>& pParticles, UpdateVariables& myVar) {\n\tint n = static_cast<int>(posX.size());\n\t__m256 gVec = _mm256_set1_ps(myVar.G);\n\t__m256 softVec = _mm256_set1_ps(myVar.softening);\n\tconst float* posXPtr = posX.data();\n\tconst float* posYPtr = posY.data();\n\tconst float* massPtr = mass.data();\n\tfloat* accXPtr = accX.data();\n\tfloat* accYPtr = accY.data();\n\n\t__m256 domainSizeX = _mm256_set1_ps(myVar.domainSize.x);\n\t__m256 domainSizeY = _mm256_set1_ps(myVar.domainSize.y);\n\t__m256 halfDomainWidth = _mm256_set1_ps(myVar.halfDomainWidth);\n\t__m256 halfDomainHeight = _mm256_set1_ps(myVar.halfDomainHeight);\n\t__m256 negHalfDomainWidth = _mm256_set1_ps(-myVar.halfDomainWidth);\n\t__m256 negHalfDomainHeight = _mm256_set1_ps(-myVar.halfDomainHeight);\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < n; i++) {\n\t\taccX[i] = 0.0f;\n\t\taccY[i] = 0.0f;\n\t}\n\n#pragma omp parallel for schedule(dynamic, 64)\n\tfor (int i = 0; i < n; i++) {\n\t\tfloat pix = posXPtr[i];\n\t\tfloat piy = posYPtr[i];\n\t\t__m256 pxi = _mm256_set1_ps(pix);\n\t\t__m256 pyi = _mm256_set1_ps(piy);\n\t\t__m256 totalAccX = _mm256_setzero_ps();\n\t\t__m256 totalAccY = _mm256_setzero_ps();\n\n\t\tint j;\n\t\tfor (j = 0; j <= n - 8; j += 8) {\n\t\t\t__m256 pxj = _mm256_loadu_ps(&posXPtr[j]);\n\t\t\t__m256 pyj = _mm256_loadu_ps(&posYPtr[j]);\n\t\t\t__m256 mj = _mm256_loadu_ps(&massPtr[j]);\n\n\t\t\t__m256 dx = _mm256_sub_ps(pxj, pxi);\n\t\t\t__m256 dy = _mm256_sub_ps(pyj, pyi);\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\t__m256 mask_pos_x = _mm256_cmp_ps(dx, halfDomainWidth, _CMP_GT_OQ);\n\t\t\t\t__m256 mask_neg_x = _mm256_cmp_ps(dx, negHalfDomainWidth, _CMP_LT_OQ);\n\t\t\t\t__m256 correction_x = _mm256_sub_ps(\n\t\t\t\t\t_mm256_and_ps(mask_pos_x, domainSizeX),\n\t\t\t\t\t_mm256_and_ps(mask_neg_x, domainSizeX)\n\t\t\t\t);\n\t\t\t\tdx = _mm256_sub_ps(dx, correction_x);\n\n\t\t\t\t__m256 mask_pos_y = _mm256_cmp_ps(dy, halfDomainHeight, _CMP_GT_OQ);\n\t\t\t\t__m256 mask_neg_y = _mm256_cmp_ps(dy, negHalfDomainHeight, _CMP_LT_OQ);\n\t\t\t\t__m256 correction_y = _mm256_sub_ps(\n\t\t\t\t\t_mm256_and_ps(mask_pos_y, domainSizeY),\n\t\t\t\t\t_mm256_and_ps(mask_neg_y, domainSizeY)\n\t\t\t\t);\n\t\t\t\tdy = _mm256_sub_ps(dy, correction_y);\n\t\t\t}\n\n\t\t\t__m256 distSq = _mm256_add_ps(\n\t\t\t\t_mm256_add_ps(_mm256_mul_ps(dx, dx),\n\t\t\t\t\t_mm256_mul_ps(dy, dy)),\n\t\t\t\tsoftVec);\n\n\t\t\t__m256 invDist = _mm256_rsqrt_ps(distSq);\n\n\t\t\t__m256 invDist3 = _mm256_mul_ps(invDist, _mm256_mul_ps(invDist, invDist));\n\t\t\t__m256 factor = _mm256_mul_ps(gVec, _mm256_mul_ps(mj, invDist3));\n\t\t\ttotalAccX = _mm256_add_ps(totalAccX, _mm256_mul_ps(dx, factor));\n\t\t\ttotalAccY = _mm256_add_ps(totalAccY, _mm256_mul_ps(dy, factor));\n\t\t}\n\n\t\tfloat accX_array[8], accY_array[8];\n\t\t_mm256_storeu_ps(accX_array, totalAccX);\n\t\t_mm256_storeu_ps(accY_array, totalAccY);\n\t\tfloat finalAccX =\n\t\t\taccX_array[0] + accX_array[1] + accX_array[2] + accX_array[3] +\n\t\t\taccX_array[4] + accX_array[5] + accX_array[6] + accX_array[7];\n\t\tfloat finalAccY =\n\t\t\taccY_array[0] + accY_array[1] + accY_array[2] + accY_array[3] +\n\t\t\taccY_array[4] + accY_array[5] + accY_array[6] + accY_array[7];\n\n\t\tfor (; j < n; j++) {\n\t\t\tfloat dx = posXPtr[j] - pix;\n\t\t\tfloat dy = posYPtr[j] - piy;\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\tdx -= myVar.domainSize.x * ((dx > myVar.halfDomainWidth) - (dx < -myVar.halfDomainWidth));\n\t\t\t\tdy -= myVar.domainSize.y * ((dy > myVar.halfDomainHeight) - (dy < -myVar.halfDomainHeight));\n\t\t\t}\n\n\t\t\tfloat distSq = dx * dx + dy * dy + myVar.softening;\n\n\t\t\tfloat invDist = _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(distSq)));\n\n\t\t\tfloat invDist3 = invDist * invDist * invDist;\n\t\t\tfloat factor = myVar.G * massPtr[j] * invDist3;\n\t\t\tfinalAccX += dx * factor;\n\t\t\tfinalAccY += dy * factor;\n\t\t}\n\n\t\taccXPtr[i] = finalAccX;\n\t\taccYPtr[i] = finalAccY;\n\t}\n}\n\nvoid Physics::readFlattenBack(std::vector<ParticlePhysics>& pParticles) {\n\n\tsize_t particleCount = pParticles.size();\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < static_cast<int>(particleCount); i++) {\n\n\t\tpParticles[i].pos.x = posX[i];\n\t\tpParticles[i].pos.y = posY[i];\n\n\t\tpParticles[i].vel.x = velX[i];\n\t\tpParticles[i].vel.y = velY[i];\n\n\t\tpParticles[i].acc.x = accX[i];\n\t\tpParticles[i].acc.y = accY[i];\n\n\t\tpParticles[i].prevVel.x = prevVelX[i];\n\t\tpParticles[i].prevVel.y = prevVelY[i];\n\n\t\tpParticles[i].mass = mass[i];\n\n\t\tpParticles[i].temp = temp[i];\n\t}\n}\n\nvoid Physics::temperatureCalculation(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar) {\n\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\tParticlePhysics& p = pParticles[i];\n\t\tauto it = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel);\n\t\tif (it != SPHMaterials::idToMaterial.end()) {\n\t\t\tSPHMaterial* pMat = it->second;\n\n\t\t\tfloat pTotalVel = sqrtf(p.vel.x * p.vel.x + p.vel.y * p.vel.y);\n\t\t\tfloat pTotalPrevVel = sqrtf(p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y);\n\n\t\t\tp.ke = 0.5f * p.sphMass * pTotalVel * pTotalVel;\n\t\t\tp.prevKe = 0.5f * p.sphMass * pTotalPrevVel * pTotalPrevVel;\n\n\t\t\tfloat q = std::abs(p.ke - p.prevKe);\n\n\t\t\tfloat dTemp = q / (2.0f * pMat->heatConductivity * p.sphMass + 1.0f);\n\t\t\tp.temp += dTemp;\n\n\t\t\tfloat tempDifference = p.temp - myVar.ambientTemp;\n\t\t\tfloat dTempCooling = -(pMat->heatConductivity * myVar.globalAmbientHeatRate) * tempDifference * myVar.timeFactor;\n\t\t\tp.temp += dTempCooling;\n\n\n\t\t\tif (p.temp >= pMat->hotPoint) {\n\t\t\t\tp.sphMass = pMat->hotMassMult;\n\t\t\t\tp.mass = UpdateVariables::particleBaseMass * p.sphMass;\n\t\t\t\tp.restDens = pMat->hotRestDens;\n\t\t\t\tp.stiff = pMat->hotStiff;\n\t\t\t\tp.visc = pMat->hotVisc;\n\t\t\t\tp.cohesion = pMat->hotCohesion;\n\t\t\t\tif (pMat->coldPoint == 0.0f) {\n\t\t\t\t\tp.isHotPoint = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (p.temp <= pMat->coldPoint) {\n\t\t\t\tp.sphMass = pMat->coldMassMult;\n\t\t\t\tp.mass = UpdateVariables::particleBaseMass * p.sphMass;\n\t\t\t\tp.restDens = pMat->coldRestDens;\n\t\t\t\tp.stiff = pMat->coldStiff;\n\t\t\t\tp.visc = pMat->coldVisc;\n\t\t\t\tp.cohesion = pMat->coldCohesion;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tp.sphMass = pMat->massMult;\n\t\t\t\tp.mass = UpdateVariables::particleBaseMass * p.sphMass;\n\t\t\t\tp.restDens = pMat->restDens;\n\t\t\t\tp.stiff = pMat->stiff;\n\t\t\t\tp.visc = pMat->visc;\n\t\t\t\tp.cohesion = pMat->cohesion;\n\t\t\t\tif (pMat->coldPoint != 0.0f) {\n\t\t\t\t\tp.isHotPoint = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (pMat->coldPoint == 0.0f) {\n\t\t\t\tif (p.temp <= pMat->hotPoint && p.isHotPoint) {\n\t\t\t\t\tp.hasSolidified = true;\n\t\t\t\t\tp.isHotPoint = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (p.temp <= pMat->coldPoint && p.isHotPoint) {\n\t\t\t\t\tp.hasSolidified = true;\n\t\t\t\t\tp.isHotPoint = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfloat pTotalVel = sqrtf(p.vel.x * p.vel.x + p.vel.y * p.vel.y);\n\t\t\tfloat pTotalPrevVel = sqrtf(p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y);\n\n\t\t\tp.ke = 0.5f * p.sphMass * pTotalVel * pTotalVel;\n\t\t\tp.prevKe = 0.5f * p.sphMass * pTotalPrevVel * pTotalPrevVel;\n\n\t\t\tfloat q = std::abs(p.ke - p.prevKe);\n\n\t\t\tfloat dTemp = q / (2.0f * 0.05f * p.sphMass + 1.0f);\n\t\t\tp.temp += dTemp;\n\n\t\t\tfloat tempDifference = p.temp - myVar.ambientTemp;\n\t\t\tfloat dTempCooling = -(0.05f * myVar.globalAmbientHeatRate) * tempDifference * myVar.timeFactor;\n\t\t\tp.temp += dTempCooling;\n\t\t}\n\t}\n}\n\nvoid Physics::createConstraints(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, bool& constraintCreateSpecialFlag,\n\tUpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tbool shouldCreateConstraints = IO::shortcutPress(KEY_P) || myVar.constraintAllSolids || constraintCreateSpecialFlag || myVar.constraintSelected;\n\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\tParticlePhysics& pi = pParticles[i];\n\n\t\tif (constraintCreateSpecialFlag) {\n\t\t\tif (!rParticles[i].isBeingDrawn) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.constraintSelected) {\n\t\t\tif (!rParticles[i].isSelected) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tSPHMaterial* pMatI = nullptr;\n\t\tauto matItI = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel);\n\t\tif (matItI != SPHMaterials::idToMaterial.end()) {\n\t\t\tpMatI = matItI->second;\n\t\t}\n\n\t\tif (shouldCreateConstraints) {\n\t\t\tif (pMatI) {\n\t\t\t\tif (pMatI->coldPoint == 0.0f) {\n\t\t\t\t\tif (pi.temp >= pMatI->hotPoint) continue;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (pi.temp >= pMatI->coldPoint) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (!pi.hasSolidified) continue;\n\t\t\tconstraintMap.clear();\n\t\t\tpi.hasSolidified = false;\n\t\t}\n\n\t\tstd::vector<size_t> neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.pos);\n\n\t\tfor (size_t j : neighborIndices) {\n\t\t\tsize_t neighborIndex = j;\n\n\t\t\tif (neighborIndex == i) continue;\n\n\t\t\tParticlePhysics& pj = myParam.pParticles[neighborIndex];\n\n\t\t\tfloat distSq = glm::dot(pj.pos - pi.pos, pj.pos - pi.pos);\n\n\t\t\tif (distSq > 12.0f) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (constraintCreateSpecialFlag) {\n\t\t\t\tif (!rParticles[neighborIndex].isBeingDrawn) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (myVar.constraintSelected) {\n\t\t\t\tif (!rParticles[neighborIndex].isSelected) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tSPHMaterial* pMatJ = nullptr;\n\t\t\tauto matItJ = SPHMaterials::idToMaterial.find(rParticles[neighborIndex].sphLabel);\n\t\t\tif (matItJ != SPHMaterials::idToMaterial.end()) {\n\t\t\t\tpMatJ = matItJ->second;\n\t\t\t}\n\n\t\t\tif (pMatI && pMatJ && pMatI->coldPoint == 0.0f && pMatJ->coldPoint != 0.0f) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tuint64_t key = makeKey(pi.id, pj.id);\n\t\t\tif (constraintMap.find(key) != constraintMap.end()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat resistance = 0.6f;\n\t\t\tif (pMatI && pMatJ) {\n\t\t\t\tresistance = (pMatI->constraintResistance + pMatJ->constraintResistance) * 0.5f;\n\t\t\t}\n\n\t\t\tfloat plasticityPoint = 0.6f;\n\t\t\tif (pMatI && pMatJ) {\n\t\t\t\tplasticityPoint = (pMatI->constraintPlasticPoint + pMatJ->constraintPlasticPoint) * 0.5f;\n\t\t\t}\n\n\t\t\tif (pi.id < pj.id) {\n\n\t\t\t\tfloat currentDist = glm::distance(pi.pos, pj.pos);\n\t\t\t\tbool broken = false;\n\t\t\t\tif (pMatI && pMatJ) {\n\t\t\t\t\tparticleConstraints.push_back({ pi.id, pj.id, currentDist, currentDist, pMatI->constraintStiffness, resistance, 0.0f, plasticityPoint, broken });\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfloat defaultStiffness = 60.0f;\n\t\t\t\t\tparticleConstraints.push_back({ pi.id, pj.id, currentDist, currentDist, defaultStiffness, resistance, 0.0f, plasticityPoint, broken });\n\t\t\t\t}\n\t\t\t\tconstraintMap[key] = &particleConstraints.back();\n\t\t\t}\n\t\t}\n\t}\n\n\tconstraintCreateSpecialFlag = false;\n\tmyVar.constraintAllSolids = false;\n\tmyVar.constraintSelected = false;\n}\n\nvoid Physics::constraints(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar) {\n\n\tif (myVar.deleteAllConstraints) {\n\t\tparticleConstraints.clear();\n\t\tconstraintMap.clear();\n\t\tmyVar.deleteAllConstraints = false;\n\t\treturn;\n\t}\n\n\tuint32_t maxId = 0;\n\tfor (const auto& p : pParticles) if (p.id > maxId) maxId = p.id;\n\n\tidToIndexTable.assign(maxId + 1, -1);\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\tidToIndexTable[pParticles[i].id] = i;\n\t}\n\n\tauto getIdx = [&](uint32_t id) -> int64_t {\n\t\tif (id >= idToIndexTable.size()) return -1;\n\t\treturn idToIndexTable[id];\n\t\t};\n\n\tif (myVar.deleteSelectedConstraints) {\n\t\tfor (auto& constraint : particleConstraints) {\n\t\t\tint64_t idx1 = getIdx(constraint.id1);\n\t\t\tint64_t idx2 = getIdx(constraint.id2);\n\n\t\t\tif (idx1 == -1 || idx2 == -1) {\n\t\t\t\tconstraint.isBroken = true;\n\t\t\t}\n\t\t\telse if (rParticles[idx1].isSelected || rParticles[idx2].isSelected) {\n\t\t\t\tconstraint.isBroken = true;\n\t\t\t}\n\t\t}\n\t\tmyVar.deleteSelectedConstraints = false;\n\t}\n\n\tif (!particleConstraints.empty()) {\n\t\tauto new_end = std::remove_if(particleConstraints.begin(), particleConstraints.end(),\n\t\t\t[&](const auto& constraint) {\n\t\t\t\tint64_t idx1 = getIdx(constraint.id1);\n\t\t\t\tint64_t idx2 = getIdx(constraint.id2);\n\t\t\t\treturn idx1 == -1 || idx2 == -1 || constraint.isBroken;\n\t\t\t});\n\n\t\tfor (auto it = new_end; it != particleConstraints.end(); ++it) {\n\t\t\tconstraintMap.erase(makeKey(it->id1, it->id2));\n\t\t}\n\t\tparticleConstraints.erase(new_end, particleConstraints.end());\n\n\t\tbool enforceTriangles = true;\n\n\t\tmyVar.frameCount++;\n\t\tif (enforceTriangles && !particleConstraints.empty() && myVar.frameCount % 5 == 0) {\n\n\t\t\tstd::vector<std::vector<uint32_t>> adjacency(maxId + 1);\n\n\t\t\tfor (const auto& c : particleConstraints) {\n\t\t\t\tif (c.isBroken) continue;\n\n\t\t\t\tif (c.id1 <= maxId && c.id2 <= maxId) {\n\t\t\t\t\tadjacency[c.id1].push_back(c.id2);\n\t\t\t\t\tadjacency[c.id2].push_back(c.id1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (auto& constraint : particleConstraints) {\n\t\t\t\tif (constraint.isBroken) continue;\n\n\t\t\t\tuint32_t idA = constraint.id1;\n\t\t\t\tuint32_t idB = constraint.id2;\n\n\t\t\t\tbool partOfTriangle = false;\n\n\t\t\t\tconst std::vector<uint32_t>& neighborsOfA = adjacency[idA];\n\t\t\t\tconst std::vector<uint32_t>& neighborsOfB = adjacency[idB];\n\n\t\t\t\tfor (uint32_t neighborA : neighborsOfA) {\n\n\t\t\t\t\tfor (uint32_t neighborB : neighborsOfB) {\n\t\t\t\t\t\tif (neighborA == neighborB) {\n\t\t\t\t\t\t\tpartOfTriangle = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (partOfTriangle) break;\n\t\t\t\t}\n\n\t\t\t\tif (!partOfTriangle) {\n\t\t\t\t\tconstraint.isBroken = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst int substeps = 15;\n\t\tfor (int step = 0; step < substeps; step++) {\n\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < (int64_t)particleConstraints.size(); i++) {\n\t\t\t\tauto& constraint = particleConstraints[i];\n\n\t\t\t\tif (constraint.isBroken) continue;\n\n\t\t\t\tint64_t idx1 = getIdx(constraint.id1);\n\t\t\t\tint64_t idx2 = getIdx(constraint.id2);\n\n\t\t\t\tif (idx1 == -1 || idx2 == -1) {\n\t\t\t\t\tconstraint.isBroken = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tParticlePhysics& pi = pParticles[idx1];\n\t\t\t\tParticlePhysics& pj = pParticles[idx2];\n\n\t\t\t\tSPHMaterial* pMatI = SPHMaterials::idToMaterial[rParticles[idx1].sphLabel];\n\t\t\t\tSPHMaterial* pMatJ = SPHMaterials::idToMaterial[rParticles[idx2].sphLabel];\n\n\t\t\t\tglm::vec2 delta = pj.pos - pi.pos;\n\n\t\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\t\tdelta.x = fmod(delta.x + myVar.domainSize.x * 1.5f, myVar.domainSize.x) - myVar.domainSize.x * 0.5f;\n\t\t\t\t\tdelta.y = fmod(delta.y + myVar.domainSize.y * 1.5f, myVar.domainSize.y) - myVar.domainSize.y * 0.5f;\n\t\t\t\t}\n\n\t\t\t\tfloat currentLength = glm::length(delta);\n\t\t\t\tif (currentLength < 0.0001f) continue;\n\n\t\t\t\tglm::vec2 dir = delta / currentLength;\n\t\t\t\tconstraint.displacement = currentLength - constraint.restLength;\n\n\t\t\t\tif (!myVar.unbreakableConstraints) {\n\t\t\t\t\tif (pMatI && pMatJ) {\n\t\t\t\t\t\tif (!pMatI->isPlastic || !pMatJ->isPlastic) {\n\t\t\t\t\t\t\tconstraint.isPlastic = false;\n\t\t\t\t\t\t\tfloat limit = (constraint.resistance * myVar.globalConstraintResistance) * constraint.restLength;\n\t\t\t\t\t\t\tif (std::abs(constraint.displacement) >= limit) {\n\t\t\t\t\t\t\t\tconstraint.isBroken = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tconstraint.isPlastic = true;\n\t\t\t\t\t\t\tif (std::abs(constraint.displacement) >= constraint.plasticityPoint * constraint.originalLength) {\n\t\t\t\t\t\t\t\tconstraint.restLength += constraint.displacement;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfloat breakLimit = (constraint.originalLength + constraint.originalLength * (constraint.resistance * myVar.globalConstraintResistance)) * (pMatI->constraintPlasticPointMult + pMatJ->constraintPlasticPointMult) * 0.5f;\n\n\t\t\t\t\t\t\tif (std::abs(constraint.restLength) >= breakLimit) {\n\t\t\t\t\t\t\t\tconstraint.isBroken = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (pi.isHotPoint || pj.isHotPoint) constraint.isBroken = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (myVar.timeFactor > 0.0f && myVar.gridExists && !constraint.isBroken) {\n\n\t\t\t\t\tglm::vec2 springForce = constraint.stiffness * constraint.displacement * dir * pi.mass * myVar.globalConstraintStiffnessMult;\n\t\t\t\t\tglm::vec2 relVel = pj.vel - pi.vel;\n\t\t\t\t\tglm::vec2 dampForce = -globalConstraintDamping * glm::dot(relVel, dir) * dir * pi.mass;\n\t\t\t\t\tglm::vec2 totalForce = springForce + dampForce;\n\n#pragma omp atomic\n\t\t\t\t\tpi.acc.x += totalForce.x / pi.mass;\n#pragma omp atomic\n\t\t\t\t\tpi.acc.y += totalForce.y / pi.mass;\n#pragma omp atomic\n\t\t\t\t\tpj.acc.x -= totalForce.x / pj.mass;\n#pragma omp atomic\n\t\t\t\t\tpj.acc.y -= totalForce.y / pj.mass;\n\n\t\t\t\t\tfloat correctionFactor = constraint.stiffness * stiffCorrectionRatio * myVar.globalConstraintStiffnessMult;\n\t\t\t\t\tglm::vec2 correction = dir * constraint.displacement * correctionFactor;\n\t\t\t\t\tfloat massSum = pi.mass + pj.mass;\n\t\t\t\t\tglm::vec2 correctionI = correction * (pj.mass / massSum);\n\t\t\t\t\tglm::vec2 correctionJ = correction * (pi.mass / massSum);\n\n#pragma omp atomic\n\t\t\t\t\tpi.pos.x += correctionI.x;\n#pragma omp atomic\n\t\t\t\t\tpi.pos.y += correctionI.y;\n#pragma omp atomic\n\t\t\t\t\tpj.pos.x -= correctionJ.x;\n#pragma omp atomic\n\t\t\t\t\tpj.pos.y -= correctionJ.y;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Physics::pausedConstraints(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar) {\n\n\tfor (size_t i = 0; i < particleConstraints.size(); i++) {\n\t\tauto& constraint = particleConstraints[i];\n\n\t\tfloat prevLength = constraint.restLength;\n\t}\n}\n\nvoid Physics::mergerSolver(\n\tstd::vector<ParticlePhysics>& pParticles,\n\tstd::vector<ParticleRendering>& rParticles,\n\tUpdateVariables& myVar,\n\tUpdateParameters& myParam)\n{\n\tstd::unordered_set<uint32_t> particleIdsToDelete;\n\tstd::vector<size_t> indicesToDelete;\n\n\tint originalSize = static_cast<int>(pParticles.size());\n\n\tfor (int i = originalSize - 1; i >= 0; i--) {\n\t\tParticlePhysics& p = pParticles[i];\n\t\tParticleRendering& r = rParticles[i];\n\n\t\tif (r.isDarkMatter || particleIdsToDelete.count(p.id)) continue;\n\n\t\tfor (int j = r.neighbors - 1; j >= 0; j--) {\n\t\t\tuint32_t neighborId = myParam.neighborSearch.globalNeighborList[p.neighborOffset + j];\n\n\t\t\tsize_t neighborIndex = myParam.neighborSearch.idToIndexTable[neighborId];\n\n\t\t\tif (neighborIndex >= pParticles.size()) continue;\n\t\t\tif (neighborIndex == i) continue;\n\n\t\t\tParticlePhysics& pn = pParticles[neighborIndex];\n\t\t\tParticleRendering& rn = rParticles[neighborIndex];\n\n\t\t\tif (rn.isDarkMatter || particleIdsToDelete.count(pn.id)) continue;\n\n\t\t\tglm::vec2 d = pn.pos - p.pos;\n\t\t\tfloat distanceSq = glm::dot(d, d);\n\n\t\t\tfloat combinedRadiusSq = (r.totalRadius + rn.totalRadius) * (r.totalRadius + rn.totalRadius);\n\n\t\t\tif (distanceSq <= combinedRadiusSq) {\n\t\t\t\tfloat originalMassP = p.mass;\n\t\t\t\tfloat originalMassN = pn.mass;\n\n\t\t\t\tif (originalMassP >= originalMassN) {\n\t\t\t\t\tp.mass = originalMassP + originalMassN;\n\t\t\t\t\tp.vel = (p.vel * originalMassP + pn.vel * originalMassN) / p.mass;\n\n\t\t\t\t\tfloat area1 = r.previousSize * r.previousSize;\n\t\t\t\t\tfloat area2 = rn.previousSize * rn.previousSize;\n\t\t\t\t\tfloat fullGrowthSize = sqrtf(area1 + area2);\n\t\t\t\t\tfloat maxOriginalSize = std::max(r.previousSize, rn.previousSize);\n\t\t\t\t\tfloat growthFactor = 0.25f;\n\n\t\t\t\t\tr.previousSize = maxOriginalSize + (fullGrowthSize - maxOriginalSize) * growthFactor;\n\n\t\t\t\t\tparticleIdsToDelete.insert(pn.id);\n\t\t\t\t\tindicesToDelete.push_back(neighborIndex);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tpn.mass = originalMassP + originalMassN;\n\t\t\t\t\tpn.vel = (pn.vel * originalMassN + p.vel * originalMassP) / pn.mass;\n\n\t\t\t\t\tfloat area1 = r.previousSize * r.previousSize;\n\t\t\t\t\tfloat area2 = rn.previousSize * rn.previousSize;\n\t\t\t\t\tfloat fullGrowthSize = sqrtf(area1 + area2);\n\t\t\t\t\tfloat maxOriginalSize = std::max(r.previousSize, rn.previousSize);\n\t\t\t\t\tfloat growthFactor = 0.25f;\n\n\t\t\t\t\trn.previousSize = maxOriginalSize + (fullGrowthSize - maxOriginalSize) * growthFactor;\n\n\t\t\t\t\tparticleIdsToDelete.insert(p.id);\n\t\t\t\t\tindicesToDelete.push_back(i);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tstd::sort(indicesToDelete.begin(), indicesToDelete.end());\n\tindicesToDelete.erase(std::unique(indicesToDelete.begin(), indicesToDelete.end()), indicesToDelete.end());\n\n\tfor (int k = static_cast<int>(indicesToDelete.size()) - 1; k >= 0; k--) {\n\t\tsize_t index = indicesToDelete[k];\n\t\tif (index >= pParticles.size()) continue;\n\n\t\tuint32_t removedId = pParticles[index].id;\n\n\t\tstd::swap(pParticles[index], pParticles.back());\n\t\tstd::swap(rParticles[index], rParticles.back());\n\n\t\tuint32_t swappedId = pParticles[index].id;\n\t\tmyParam.neighborSearch.idToIndexTable[swappedId] = index;\n\n\t\tpParticles.pop_back();\n\t\trParticles.pop_back();\n\n\t\tif (removedId < myParam.neighborSearch.idToIndexTable.size()) {\n\t\t\tmyParam.neighborSearch.idToIndexTable[removedId] = static_cast<size_t>(-1);\n\t\t}\n\t}\n}\n\nvoid Physics::integrateStart(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar) {\n\tfloat dt = myVar.timeFactor;\n\tfloat halfDt = dt * 0.5f;\n\tfloat sphMaxVelSq = myVar.sphMaxVel * myVar.sphMaxVel;\n\n#pragma omp parallel for schedule(dynamic)\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\tParticlePhysics& p = pParticles[i];\n\n\t\tif (rParticles[i].isPinned) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tp.prevVel = p.vel;\n\n\t\tp.vel += p.acc * halfDt;\n\n\t\tif (myVar.isSPHEnabled) {\n\t\t\tfloat vSq = p.vel.x * p.vel.x + p.vel.y * p.vel.y;\n\n\t\t\tif (vSq > sphMaxVelSq) {\n\t\t\t\tfloat prevVSq = p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y;\n\n\t\t\t\tif (prevVSq > 0.00001f) {\n\t\t\t\t\tfloat invPrevLen = myVar.sphMaxVel / sqrtf(prevVSq);\n\t\t\t\t\tp.prevVel *= invPrevLen;\n\t\t\t\t}\n\n\t\t\t\tfloat invLen = myVar.sphMaxVel / sqrtf(vSq);\n\t\t\t\tp.vel *= invLen;\n\t\t\t}\n\t\t}\n\n\t\tp.pos += p.vel * dt;\n\n\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\tif (p.pos.x < 0.0f) p.pos.x += myVar.domainSize.x;\n\t\t\telse if (p.pos.x >= myVar.domainSize.x) p.pos.x -= myVar.domainSize.x;\n\n\t\t\tif (p.pos.y < 0.0f) p.pos.y += myVar.domainSize.y;\n\t\t\telse if (p.pos.y >= myVar.domainSize.y) p.pos.y -= myVar.domainSize.y;\n\t\t}\n\t}\n}\n\nvoid Physics::integrateEnd(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar) {\n\n#pragma omp parallel for schedule(dynamic)\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\n\t\tif (rParticles[i].isPinned) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tpParticles[i].vel += pParticles[i].acc * (myVar.timeFactor * 0.5f);\n\t}\n}\n\nvoid Physics::pruneParticles(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar) {\n\n\tfor (size_t i = 0; i < pParticles.size(); ) {\n\t\tfloat x = pParticles[i].pos.x;\n\t\tfloat y = pParticles[i].pos.y;\n\n\t\tif (x <= 0.0f || x >= myVar.domainSize.x || y <= 0.0f || y >= myVar.domainSize.y) {\n\n\t\t\tif (pParticles.size() > 1) {\n\t\t\t\tstd::swap(pParticles[i], pParticles.back());\n\t\t\t\tstd::swap(rParticles[i], rParticles.back());\n\t\t\t}\n\t\t\tpParticles.pop_back();\n\t\t\trParticles.pop_back();\n\t\t}\n\t\telse {\n\t\t\ti++;\n\t\t}\n\t}\n}\n\nvoid Physics::spawnCorrection(UpdateParameters& myParam, bool& hasAVX2, const int& iterations) {\n\n#pragma omp parallel for\n\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\n\t\tParticlePhysics& pi = myParam.pParticles[i];\n\n\t\tstd::vector<size_t> neighborIndices =\n\t\t\tQueryNeighbors::queryNeighbors(myParam, hasAVX2, 64, pi.pos);\n\n\t\tfor (size_t j : neighborIndices) {\n\t\t\tsize_t neighborIndex = j;\n\n\t\t\tif (neighborIndex == i) continue;\n\n\t\t\tParticlePhysics& pj = myParam.pParticles[neighborIndex];\n\n\t\t\tif (!myParam.rParticles[neighborIndex].isBeingDrawn || !myParam.rParticles[i].isBeingDrawn) continue;\n\n\t\t\tglm::vec2 d = pj.pos - pi.pos;\n\n\t\t\tfloat dSq = glm::dot(d, d);\n\n\t\t\tconst float minDist = 2.4f;\n\t\t\tconst float minDistSq = minDist * minDist;\n\n\t\t\tif (dSq > 0.000001f && dSq < minDistSq) {\n\n\t\t\t\tfloat dist = std::sqrt(dSq);\n\n\t\t\t\tglm::vec2 dir = -d / dist;\n\n\t\t\t\tfloat penetration = minDist - dist;\n\n\t\t\t\tfloat totalMass = pi.mass + pj.mass;\n\n\t\t\t\tif (totalMass > 0.0f) {\n\n\t\t\t\t\tfloat piMove = pj.mass / totalMass;\n\t\t\t\t\tfloat pjMove = pi.mass / totalMass;\n\n\t\t\t\t\tglm::vec2 correction = dir * penetration;\n\n\t\t\t\t\tpi.pos += correction * piMove;\n\t\t\t\t\tpj.pos -= correction * pjMove;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmyParam.rParticles[i].spawnCorrectIter++;\n\t}\n}\n\n// ----- Unused. Test code ----- //\nvoid Physics::gravityGrid(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, UpdateVariables& myVar, glm::vec3& bb) {\n\n\tif (pParticles.empty()) return;\n\n#pragma omp parallel for\n\tfor (long long i = 0; i < static_cast<long long>(cells.size()); i++) {\n\t\tcells[i].mass = 0.0f;\n\t\tcells[i].particles.clear();\n\t\tcells[i].force = { 0.0f, 0.0f };\n\t}\n\n\tstruct DepthInfo {\n\t\tint gridRes;\n\t\tfloat cellSize;\n\t\tsize_t startIndex;\n\t};\n\n\tstd::vector<DepthInfo> depthInfos;\n\tsize_t currentStartIdx = 0;\n\n\tfor (int depth = 0; depth < maxDepth; depth++) {\n\t\tint res = std::pow(2, depth + 1);\n\t\tfloat size = bb.z / static_cast<float>(res);\n\t\tdepthInfos.push_back({ res, size, currentStartIdx });\n\t\tcurrentStartIdx += (res * res);\n\t}\n\n\tfor (ParticlePhysics& p : pParticles) {\n\t\tfor (int depth = 0; depth < maxDepth; depth++) {\n\t\t\tconst auto& info = depthInfos[depth];\n\n\t\t\tfloat relX = p.pos.x - bb.x;\n\t\t\tfloat relY = p.pos.y - bb.y;\n\t\t\tint x = static_cast<int>(relX / info.cellSize);\n\t\t\tint y = static_cast<int>(relY / info.cellSize);\n\n\t\t\tif (x >= 0 && x < info.gridRes && y >= 0 && y < info.gridRes) {\n\t\t\t\tsize_t cellIdx = info.startIndex + (y * info.gridRes + x);\n\t\t\t\tif (cellIdx < cells.size()) {\n\t\t\t\t\tcells[cellIdx].mass += p.mass;\n\t\t\t\t\tcells[cellIdx].particles.push_back(&p);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const auto& info : depthInfos) {\n\t\tlong long start = static_cast<long long>(info.startIndex);\n\t\tlong long end = static_cast<long long>(info.startIndex + (info.gridRes * info.gridRes));\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (long long i = start; i < end; i++) {\n\t\t\tGravityCell& ci = cells[i];\n\n\t\t\tif (ci.mass == 0.0f) continue;\n\n\t\t\tsize_t localIdx = i - info.startIndex;\n\t\t\tint cix = localIdx % info.gridRes;\n\t\t\tint ciy = localIdx / info.gridRes;\n\n\t\t\tfor (int dy = -11; dy <= 11; dy++) {\n\t\t\t\tfor (int dx = -11; dx <= 11; dx++) {\n\t\t\t\t\tif (dx == 0 && dy == 0) continue;\n\n\t\t\t\t\tint cjx = cix + dx;\n\t\t\t\t\tint cjy = ciy + dy;\n\n\t\t\t\t\tif (cjx < 0 || cjx >= info.gridRes || cjy < 0 || cjy >= info.gridRes) continue;\n\n\t\t\t\t\tsize_t neighborLocalIdx = cjy * info.gridRes + cjx;\n\t\t\t\t\tsize_t finalNeighborIdx = info.startIndex + neighborLocalIdx;\n\n\t\t\t\t\tconst GravityCell& cj = cells[finalNeighborIdx];\n\n\t\t\t\t\tif (cj.mass == 0.0f) continue;\n\n\t\t\t\t\tglm::vec2 d = cj.pos - ci.pos;\n\t\t\t\t\tfloat distSq = glm::dot(d, d);\n\n\t\t\t\t\tif (distSq < 1e-4f) continue;\n\t\t\t\t\tfloat dist = std::sqrt(distSq);\n\n\t\t\t\t\tfloat force = (myVar.G * ci.mass * cj.mass) / distSq;\n\t\t\t\t\tci.force += force * (d / dist);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n#pragma omp parallel for\n\tfor (long long i = 0; i < static_cast<long long>(pParticles.size()); i++) {\n\t\tParticlePhysics& p = pParticles[i];\n\n\t\tfor (int depth = 0; depth < maxDepth; depth++) {\n\t\t\tconst auto& info = depthInfos[depth];\n\n\t\t\tfloat relX = p.pos.x - bb.x;\n\t\t\tfloat relY = p.pos.y - bb.y;\n\t\t\tint x = static_cast<int>(relX / info.cellSize);\n\t\t\tint y = static_cast<int>(relY / info.cellSize);\n\n\t\t\tif (x >= 0 && x < info.gridRes && y >= 0 && y < info.gridRes) {\n\t\t\t\tsize_t cellIdx = info.startIndex + (y * info.gridRes + x);\n\n\t\t\t\tif (cellIdx < cells.size() && cells[cellIdx].mass > 0.0f) {\n\t\t\t\t\tconst GravityCell& c = cells[cellIdx];\n\n\t\t\t\t\tp.acc += c.force / c.mass;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n// ----- Unused. Test code ----- //\n\n"
  },
  {
    "path": "GalaxyEngine/src/Physics/physics3D.cpp",
    "content": "#include \"Physics/physics3D.h\"\n\nvoid Physics3D::flattenParticles3D(std::vector<ParticlePhysics3D>& pParticles3D) {\n\tsize_t particleCount = pParticles3D.size();\n\n\tposX.resize(particleCount);\n\tposY.resize(particleCount);\n\tposZ.resize(particleCount);\n\taccX.resize(particleCount);\n\taccY.resize(particleCount);\n\taccZ.resize(particleCount);\n\tvelX.resize(particleCount);\n\tvelY.resize(particleCount);\n\tvelZ.resize(particleCount);\n\tprevVelX.resize(particleCount);\n\tprevVelY.resize(particleCount);\n\tprevVelZ.resize(particleCount);\n\tmass.resize(particleCount);\n\ttemp.resize(particleCount);\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < static_cast<int>(particleCount); i++) {\n\n\t\tconst auto& particle = pParticles3D[i];\n\n\t\tposX[i] = particle.pos.x;\n\t\tposY[i] = particle.pos.y;\n\t\tposZ[i] = particle.pos.z;\n\n\t\taccX[i] = particle.acc.x;\n\t\taccY[i] = particle.acc.y;\n\t\taccZ[i] = particle.acc.z;\n\n\t\tvelX[i] = particle.vel.x;\n\t\tvelY[i] = particle.vel.y;\n\t\tvelZ[i] = particle.vel.z;\n\n\t\tprevVelX[i] = particle.prevVel.x;\n\t\tprevVelY[i] = particle.prevVel.y;\n\t\tprevVelZ[i] = particle.prevVel.z;\n\n\t\tmass[i] = particle.mass;\n\n\t\ttemp[i] = particle.temp;\n\t}\n}\n\n// This is used in predict trajectory inside particleSpawning.cpp\nglm::vec3 Physics3D::calculateForceFromGrid3DOld(std::vector<ParticlePhysics3D>& pParticles,\n\tUpdateVariables& myVar,\n\tParticlePhysics3D& pParticle) {\n\n\tglm::vec3 totalForce = { 0.0f, 0.0f, 0.0f };\n\tuint32_t gridIdx = 0;\n\n\tconst uint32_t nodeCount = static_cast<uint32_t>(globalNodes3D.size());\n\n\tconst float thetaSq = myVar.theta * myVar.theta;\n\tconst float softeningSq = myVar.softening * myVar.softening;\n\n\tconst float Gf = static_cast<float>(myVar.G);\n\n\tconst float pmass = pParticle.mass;\n\n\tauto* particlesPtr = pParticles.data();\n\n\twhile (gridIdx < nodeCount) {\n\t\tNode3D& grid = globalNodes3D[gridIdx];\n\t\tfloat gridMass = grid.gridMass;\n\n\t\tif (gridMass <= 0.0f) {\n\t\t\tgridIdx += grid.next + 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst glm::vec3 gridCOM = grid.centerOfMass;\n\t\tconst float gridSize = grid.size;\n\t\tglm::vec3 d = gridCOM - pParticle.pos;\n\n\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\td.x -= myVar.domainSize3D.x * ((d.x > myVar.halfDomain3DWidth) - (d.x < -myVar.halfDomain3DWidth));\n\t\t\td.y -= myVar.domainSize3D.y * ((d.y > myVar.halfDomain3DHeight) - (d.y < -myVar.halfDomain3DHeight));\n\t\t\td.z -= myVar.domainSize3D.z * ((d.z > myVar.halfDomain3DDepth) - (d.z < -myVar.halfDomain3DDepth));\n\t\t}\n\n\t\tfloat distanceSq = d.x * d.x + d.y * d.y + d.z * d.z + softeningSq;\n\n\t\tbool isSubgridsEmpty = true;\n\t\tfor (int i = 0; i < 2; ++i) {\n\t\t\tfor (int j = 0; j < 2; ++j) {\n\t\t\t\tfor (int k = 0; k < 2; ++k) {\n\t\t\t\t\tif (grid.subGrids[i][j][k] != UINT32_MAX) {\n\t\t\t\t\t\tisSubgridsEmpty = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!isSubgridsEmpty) break;\n\t\t\t}\n\t\t\tif (!isSubgridsEmpty) break;\n\t\t}\n\n\t\tfloat gridSizeSq = gridSize * gridSize;\n\n\t\tif ((gridSizeSq < thetaSq * distanceSq) || isSubgridsEmpty) {\n\t\t\tif ((grid.endIndex - grid.startIndex) == 1) {\n\t\t\t\tconst ParticlePhysics3D& other = particlesPtr[grid.startIndex];\n\t\t\t\tif (std::abs(other.pos.x - pParticle.pos.x) < 0.001f &&\n\t\t\t\t\tstd::abs(other.pos.y - pParticle.pos.y) < 0.001f &&\n\t\t\t\t\tstd::abs(other.pos.z - pParticle.pos.z) < 0.001f) {\n\t\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfloat invDistance = 1.0f / sqrtf(distanceSq);\n\t\t\tfloat invDist2 = invDistance * invDistance;\n\t\t\tfloat invDist3 = invDist2 * invDistance;\n\t\t\tfloat forceMagnitude = Gf * pmass * gridMass * invDist3;\n\t\t\ttotalForce += d * forceMagnitude;\n\n\t\t\tgridIdx += grid.next + 1;\n\t\t}\n\t\telse {\n\t\t\t++gridIdx;\n\t\t}\n\t}\n\n\treturn totalForce;\n}\n\nvoid Physics3D::calculateForceFromGrid3D(UpdateVariables& myVar) {\n\n#pragma omp parallel for schedule(dynamic)\n\tfor (size_t i = 0; i < posX.size(); i++) {\n\n\t\tglm::vec3 totalForce = { 0.0f, 0.0f, 0.0f };\n\n\t\tuint32_t gridIdx = 0;\n\t\tconst uint32_t nodeCount = static_cast<uint32_t>(globalNodes3D.size());\n\n\t\tconst float thetaSq = myVar.theta * myVar.theta;\n\t\tconst float Gf = myVar.G;\n\t\tconst float pmass = mass[i];\n\n\t\twhile (gridIdx < nodeCount) {\n\t\t\tNode3D& grid = globalNodes3D[gridIdx];\n\n\t\t\tfloat gridMass = grid.gridMass;\n\n\t\t\tif (gridMass <= 0.0f) {\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst glm::vec3 gridCOM = grid.centerOfMass;\n\t\t\tconst float gridSize = grid.size;\n\n\t\t\tglm::vec3 d = gridCOM - glm::vec3{ posX[i], posY[i], posZ[i] };\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\td.x -= myVar.domainSize3D.x * ((d.x > myVar.halfDomain3DWidth) - (d.x < -myVar.halfDomain3DWidth));\n\t\t\t\td.y -= myVar.domainSize3D.y * ((d.y > myVar.halfDomain3DHeight) - (d.y < -myVar.halfDomain3DHeight));\n\t\t\t\td.z -= myVar.domainSize3D.z * ((d.z > myVar.halfDomain3DDepth) - (d.z < -myVar.halfDomain3DDepth));\n\t\t\t}\n\n\t\t\tfloat distanceSq = d.x * d.x + d.y * d.y + d.z * d.z + myVar.softening;\n\n\t\t\tbool isSubgridsEmpty = true;\n\t\t\tfor (int x = 0; x < 2 && isSubgridsEmpty; ++x) {\n\t\t\t\tfor (int y = 0; y < 2 && isSubgridsEmpty; ++y) {\n\t\t\t\t\tfor (int z = 0; z < 2 && isSubgridsEmpty; ++z) {\n\t\t\t\t\t\tif (grid.subGrids[x][y][z] != UINT32_MAX) {\n\t\t\t\t\t\t\tisSubgridsEmpty = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfloat gridSizeSq = gridSize * gridSize;\n\n\t\t\tif (gridSizeSq < thetaSq * distanceSq) {\n\n\t\t\t\tfloat invDistance = 1.0f / sqrtf(distanceSq);\n\t\t\t\tfloat invDist2 = invDistance * invDistance;\n\t\t\t\tfloat invDist3 = invDist2 * invDistance;\n\n\t\t\t\tfloat forceMagnitude = Gf * pmass * gridMass * invDist3;\n\t\t\t\ttotalForce += d * forceMagnitude;\n\n\t\t\t\tif (myVar.isTempEnabled) {\n\t\t\t\t\tuint32_t count = grid.endIndex - grid.startIndex;\n\t\t\t\t\tif (count > 0) {\n\t\t\t\t\t\tfloat gridAverageTemp = grid.gridTemp / static_cast<float>(count);\n\t\t\t\t\t\tfloat temperatureDifference = gridAverageTemp - temp[i];\n\n\t\t\t\t\t\tfloat distance = 0.0f;\n\t\t\t\t\t\tif (distanceSq > 1e-16f) distance = 1.0f / invDistance;\n\n\t\t\t\t\t\tif (distance > 1e-8f) {\n\t\t\t\t\t\t\tfloat heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance;\n\t\t\t\t\t\t\ttemp[i] += heatTransfer * myVar.timeFactor;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t}\n\t\t\telse if (isSubgridsEmpty) {\n\n\t\t\t\tfor (uint32_t j = grid.startIndex; j < grid.endIndex; ++j) {\n\t\t\t\t\tif (i == j) continue;\n\n\t\t\t\t\tglm::vec3 p2pd = glm::vec3{ posX[j], posY[j], posZ[j] } - glm::vec3{ posX[i], posY[i], posZ[i] };\n\n\t\t\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\t\t\tp2pd.x -= myVar.domainSize3D.x * ((p2pd.x > myVar.halfDomain3DWidth) - (p2pd.x < -myVar.halfDomain3DWidth));\n\t\t\t\t\t\tp2pd.y -= myVar.domainSize3D.y * ((p2pd.y > myVar.halfDomain3DHeight) - (p2pd.y < -myVar.halfDomain3DHeight));\n\t\t\t\t\t\tp2pd.z -= myVar.domainSize3D.z * ((p2pd.z > myVar.halfDomain3DDepth) - (p2pd.z < -myVar.halfDomain3DDepth));\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat p2pdistSq = p2pd.x * p2pd.x + p2pd.y * p2pd.y + p2pd.z * p2pd.z + myVar.softening;\n\n\t\t\t\t\tfloat invDist = 1.0f / sqrtf(p2pdistSq);\n\t\t\t\t\tfloat invDist2 = invDist * invDist;\n\t\t\t\t\tfloat invDist3 = invDist2 * invDist;\n\n\t\t\t\t\tfloat forceMag = Gf * pmass * mass[j] * invDist3;\n\t\t\t\t\ttotalForce += p2pd * forceMag;\n\n\t\t\t\t\tif (myVar.isTempEnabled) {\n\t\t\t\t\t\tfloat tempDiff = temp[j] - temp[i];\n\t\t\t\t\t\tfloat p2pdist = 0.0f;\n\n\t\t\t\t\t\tif (p2pdistSq > 1e-16f) p2pdist = 1.0f / invDist;\n\t\t\t\t\t\tif (p2pdist > 1e-8f) {\n\t\t\t\t\t\t\tfloat heatTransfer = myVar.globalHeatConductivity * tempDiff / p2pdist;\n\t\t\t\t\t\t\ttemp[i] += heatTransfer * myVar.timeFactor;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t++gridIdx;\n\t\t\t}\n\t\t}\n\n\t\taccX[i] = totalForce.x / mass[i];\n\t\taccY[i] = totalForce.y / mass[i];\n\t\taccZ[i] = totalForce.z / mass[i];\n\t}\n}\n\nvoid Physics3D::calculateForceFromGrid3DAVX2(UpdateVariables& myVar) {\n#pragma omp parallel for schedule(dynamic)\n\tfor (size_t i = 0; i < posX.size(); i++) {\n\t\tglm::vec3 totalForce = { 0.0f, 0.0f, 0.0f };\n\n\t\tuint32_t gridIdx = 0;\n\t\tconst uint32_t nodeCount = static_cast<uint32_t>(globalNodes3D.size());\n\n\t\tconst float thetaSq = myVar.theta * myVar.theta;\n\t\tconst float Gf = myVar.G;\n\t\tconst float pmass = mass[i];\n\n\t\twhile (gridIdx < nodeCount) {\n\t\t\tNode3D& grid = globalNodes3D[gridIdx];\n\n\t\t\tfloat gridMass = grid.gridMass;\n\t\t\tif (gridMass <= 0.0f) {\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst glm::vec3 gridCOM = grid.centerOfMass;\n\t\t\tconst float gridSize = grid.size;\n\n\t\t\tglm::vec3 d = gridCOM - glm::vec3{ posX[i], posY[i], posZ[i] };\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\td.x -= myVar.domainSize3D.x * ((d.x > myVar.halfDomain3DWidth) - (d.x < -myVar.halfDomain3DWidth));\n\t\t\t\td.y -= myVar.domainSize3D.y * ((d.y > myVar.halfDomain3DHeight) - (d.y < -myVar.halfDomain3DHeight));\n\t\t\t\td.z -= myVar.domainSize3D.z * ((d.z > myVar.halfDomain3DDepth) - (d.z < -myVar.halfDomain3DDepth));\n\t\t\t}\n\n\t\t\tfloat distanceSq = d.x * d.x + d.y * d.y + d.z * d.z + myVar.softening;\n\n\t\t\tbool isSubgridsEmpty = true;\n\t\t\tfor (int x = 0; x < 2 && isSubgridsEmpty; ++x) {\n\t\t\t\tfor (int y = 0; y < 2 && isSubgridsEmpty; ++y) {\n\t\t\t\t\tfor (int z = 0; z < 2 && isSubgridsEmpty; ++z) {\n\t\t\t\t\t\tif (grid.subGrids[x][y][z] != UINT32_MAX) {\n\t\t\t\t\t\t\tisSubgridsEmpty = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfloat gridSizeSq = gridSize * gridSize;\n\n\t\t\tif (gridSizeSq < thetaSq * distanceSq) {\n\n\t\t\t\tfloat invDistance = 1.0f / sqrtf(distanceSq);\n\t\t\t\tfloat invDist2 = invDistance * invDistance;\n\t\t\t\tfloat invDist3 = invDist2 * invDistance;\n\n\t\t\t\tfloat forceMagnitude = Gf * pmass * gridMass * invDist3;\n\t\t\t\ttotalForce += d * forceMagnitude;\n\n\t\t\t\tif (myVar.isTempEnabled) {\n\t\t\t\t\tuint32_t count = grid.endIndex - grid.startIndex;\n\t\t\t\t\tif (count > 0) {\n\t\t\t\t\t\tfloat gridAverageTemp = grid.gridTemp / static_cast<float>(count);\n\t\t\t\t\t\tfloat temperatureDifference = gridAverageTemp - temp[i];\n\n\t\t\t\t\t\tfloat distance = 0.0f;\n\t\t\t\t\t\tif (distanceSq > 1e-16f) distance = 1.0f / invDistance;\n\t\t\t\t\t\tif (distance > 1e-8f) {\n\t\t\t\t\t\t\tfloat heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance;\n\t\t\t\t\t\t\ttemp[i] += heatTransfer * myVar.timeFactor;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t}\n\n\t\t\telse if (isSubgridsEmpty) {\n\n\t\t\t\t__m256 vxi = _mm256_set1_ps(posX[i]);\n\t\t\t\t__m256 vyi = _mm256_set1_ps(posY[i]);\n\t\t\t\t__m256 vzi = _mm256_set1_ps(posZ[i]);\n\n\t\t\t\t__m256 vsofteningSq = _mm256_set1_ps(myVar.softening);\n\t\t\t\t__m256 vGfpmass = _mm256_set1_ps(Gf * pmass);\n\n\t\t\t\t__m256 vforceX = _mm256_setzero_ps();\n\t\t\t\t__m256 vforceY = _mm256_setzero_ps();\n\t\t\t\t__m256 vforceZ = _mm256_setzero_ps();\n\n\t\t\t\tuint32_t j = grid.startIndex;\n\n\t\t\t\tfor (; j + 7 < grid.endIndex; j += 8) {\n\n\t\t\t\t\t__m256 vxj = _mm256_loadu_ps(&posX[j]);\n\t\t\t\t\t__m256 vyj = _mm256_loadu_ps(&posY[j]);\n\t\t\t\t\t__m256 vzj = _mm256_loadu_ps(&posZ[j]);\n\t\t\t\t\t__m256 vmj = _mm256_loadu_ps(&mass[j]);\n\n\t\t\t\t\t__m256 vdx = _mm256_sub_ps(vxj, vxi);\n\t\t\t\t\t__m256 vdy = _mm256_sub_ps(vyj, vyi);\n\t\t\t\t\t__m256 vdz = _mm256_sub_ps(vzj, vzi);\n\n\t\t\t\t\t__m256 vdx2 = _mm256_mul_ps(vdx, vdx);\n\t\t\t\t\t__m256 vdy2 = _mm256_mul_ps(vdy, vdy);\n\t\t\t\t\t__m256 vdz2 = _mm256_mul_ps(vdz, vdz);\n\n\t\t\t\t\t__m256 vdistSq = _mm256_add_ps(_mm256_add_ps(_mm256_add_ps(vdx2, vdy2), vdz2), vsofteningSq);\n\n\t\t\t\t\t__m256 vinvDist = _mm256_rsqrt_ps(vdistSq);\n\n\t\t\t\t\t__m256 vinvDist2 = _mm256_mul_ps(vinvDist, vinvDist);\n\t\t\t\t\t__m256 vinvDist3 = _mm256_mul_ps(vinvDist2, vinvDist);\n\n\t\t\t\t\t__m256 vforceMag = _mm256_mul_ps(_mm256_mul_ps(vGfpmass, vmj), vinvDist3);\n\n\t\t\t\t\t__m256 vmaskNotSelf = _mm256_cmp_ps(vdistSq, vsofteningSq, _CMP_NEQ_OQ);\n\t\t\t\t\tvforceMag = _mm256_and_ps(vforceMag, vmaskNotSelf);\n\n\t\t\t\t\tvforceX = _mm256_add_ps(vforceX, _mm256_mul_ps(vdx, vforceMag));\n\t\t\t\t\tvforceY = _mm256_add_ps(vforceY, _mm256_mul_ps(vdy, vforceMag));\n\t\t\t\t\tvforceZ = _mm256_add_ps(vforceZ, _mm256_mul_ps(vdz, vforceMag));\n\t\t\t\t}\n\n\t\t\t\tfloat tempFx[8];\n\t\t\t\tfloat tempFy[8];\n\t\t\t\tfloat tempFz[8];\n\n\t\t\t\t_mm256_storeu_ps(tempFx, vforceX);\n\t\t\t\t_mm256_storeu_ps(tempFy, vforceY);\n\t\t\t\t_mm256_storeu_ps(tempFz, vforceZ);\n\n\t\t\t\tfor (int k = 0; k < 8; ++k) {\n\t\t\t\t\ttotalForce.x += tempFx[k];\n\t\t\t\t\ttotalForce.y += tempFy[k];\n\t\t\t\t\ttotalForce.z += tempFz[k];\n\t\t\t\t}\n\n\t\t\t\tfor (; j < grid.endIndex; ++j) {\n\t\t\t\t\tif (i == j) continue;\n\n\t\t\t\t\tglm::vec3 p2pd = glm::vec3{ posX[j], posY[j], posZ[j] } - glm::vec3{ posX[i], posY[i], posZ[i] };\n\t\t\t\t\tfloat p2pdistSq = p2pd.x * p2pd.x + p2pd.y * p2pd.y + p2pd.z * p2pd.z + myVar.softening;\n\n\t\t\t\t\tfloat invDist = 1.0f / sqrtf(p2pdistSq);\n\t\t\t\t\tfloat invDist3 = invDist * invDist * invDist;\n\n\t\t\t\t\tfloat forceMag = Gf * pmass * mass[j] * invDist3;\n\t\t\t\t\ttotalForce += p2pd * forceMag;\n\t\t\t\t}\n\n\t\t\t\tgridIdx += grid.next + 1;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t++gridIdx;\n\t\t\t}\n\t\t}\n\n\t\taccX[i] = totalForce.x / mass[i];\n\t\taccY[i] = totalForce.y / mass[i];\n\t\taccZ[i] = totalForce.z / mass[i];\n\t}\n}\n\nvoid Physics3D::naiveGravity3D(std::vector<ParticlePhysics3D>& pParticles3D, UpdateVariables& myVar) {\n\tint n = static_cast<int>(posX.size());\n\n\tconst float* posXPtr = posX.data();\n\tconst float* posYPtr = posY.data();\n\tconst float* posZPtr = posZ.data();\n\tconst float* massPtr = mass.data();\n\tfloat* accXPtr = accX.data();\n\tfloat* accYPtr = accY.data();\n\tfloat* accZPtr = accZ.data();\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < n; i++) {\n\t\taccXPtr[i] = 0.0f;\n\t\taccYPtr[i] = 0.0f;\n\t\taccZPtr[i] = 0.0f;\n\t}\n\n#pragma omp parallel for schedule(dynamic, 64)\n\tfor (int i = 0; i < n; i++) {\n\t\tfloat p_ix = posXPtr[i];\n\t\tfloat p_iy = posYPtr[i];\n\t\tfloat p_iz = posZPtr[i];\n\n\t\tfloat finalAccX = 0.0f;\n\t\tfloat finalAccY = 0.0f;\n\t\tfloat finalAccZ = 0.0f;\n\n\t\tfor (int j = 0; j < n; j++) {\n\t\t\tfloat dx = posXPtr[j] - p_ix;\n\t\t\tfloat dy = posYPtr[j] - p_iy;\n\t\t\tfloat dz = posZPtr[j] - p_iz;\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\tdx -= myVar.domainSize3D.x * ((dx > myVar.halfDomain3DWidth) - (dx < -myVar.halfDomain3DWidth));\n\t\t\t\tdy -= myVar.domainSize3D.y * ((dy > myVar.halfDomain3DHeight) - (dy < -myVar.halfDomain3DHeight));\n\t\t\t\tdz -= myVar.domainSize3D.z * ((dz > myVar.halfDomain3DDepth) - (dz < -myVar.halfDomain3DDepth));\n\t\t\t}\n\n\t\t\tfloat distSq = dx * dx + dy * dy + dz * dz + myVar.softening;\n\t\t\tfloat invDist = 1.0f / std::sqrt(distSq);\n\t\t\tfloat invDist3 = invDist * invDist * invDist;\n\t\t\tfloat factor = myVar.G * massPtr[j] * invDist3;\n\n\t\t\tfinalAccX += dx * factor;\n\t\t\tfinalAccY += dy * factor;\n\t\t\tfinalAccZ += dz * factor;\n\t\t}\n\n\t\taccXPtr[i] = finalAccX;\n\t\taccYPtr[i] = finalAccY;\n\t\taccZPtr[i] = finalAccZ;\n\t}\n}\n\nvoid Physics3D::naiveGravity3DAVX2(std::vector<ParticlePhysics3D>& pParticles3D, UpdateVariables& myVar) {\n\tint n = static_cast<int>(posX.size());\n\n\t__m256 gVec = _mm256_set1_ps(myVar.G);\n\t__m256 softVec = _mm256_set1_ps(myVar.softening);\n\n\tconst float* posXPtr = posX.data();\n\tconst float* posYPtr = posY.data();\n\tconst float* posZPtr = posZ.data();\n\tconst float* massPtr = mass.data();\n\n\tfloat* accXPtr = accX.data();\n\tfloat* accYPtr = accY.data();\n\tfloat* accZPtr = accZ.data();\n\n\t__m256 domainSizeX = _mm256_set1_ps(myVar.domainSize3D.x);\n\t__m256 domainSizeY = _mm256_set1_ps(myVar.domainSize3D.y);\n\t__m256 domainSizeZ = _mm256_set1_ps(myVar.domainSize3D.z);\n\n\t__m256 halfDomainWidth = _mm256_set1_ps(myVar.halfDomain3DWidth);\n\t__m256 halfDomainHeight = _mm256_set1_ps(myVar.halfDomain3DHeight);\n\t__m256 halfDomainDepth = _mm256_set1_ps(myVar.halfDomain3DDepth);\n\n\t__m256 negHalfDomainWidth = _mm256_set1_ps(-myVar.halfDomain3DWidth);\n\t__m256 negHalfDomainHeight = _mm256_set1_ps(-myVar.halfDomain3DHeight);\n\t__m256 negHalfDomainDepth = _mm256_set1_ps(-myVar.halfDomain3DDepth);\n\n\t__m256 threeHalfs = _mm256_set1_ps(1.5f);\n\t__m256 pointFive = _mm256_set1_ps(0.5f);\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < n; i++) {\n\t\taccXPtr[i] = 0.0f;\n\t\taccYPtr[i] = 0.0f;\n\t\taccZPtr[i] = 0.0f;\n\t}\n\n#pragma omp parallel for schedule(dynamic, 64)\n\tfor (int i = 0; i < n; i++) {\n\t\tfloat p_ix = posXPtr[i];\n\t\tfloat p_iy = posYPtr[i];\n\t\tfloat p_iz = posZPtr[i];\n\n\t\t__m256 pxi = _mm256_set1_ps(p_ix);\n\t\t__m256 pyi = _mm256_set1_ps(p_iy);\n\t\t__m256 pzi = _mm256_set1_ps(p_iz);\n\n\t\t__m256 totalAccX = _mm256_setzero_ps();\n\t\t__m256 totalAccY = _mm256_setzero_ps();\n\t\t__m256 totalAccZ = _mm256_setzero_ps();\n\n\t\tint j;\n\n\t\tfor (j = 0; j <= n - 8; j += 8) {\n\t\t\t__m256 pxj = _mm256_loadu_ps(&posXPtr[j]);\n\t\t\t__m256 pyj = _mm256_loadu_ps(&posYPtr[j]);\n\t\t\t__m256 pzj = _mm256_loadu_ps(&posZPtr[j]);\n\t\t\t__m256 mj = _mm256_loadu_ps(&massPtr[j]);\n\n\t\t\t__m256 dx = _mm256_sub_ps(pxj, pxi);\n\t\t\t__m256 dy = _mm256_sub_ps(pyj, pyi);\n\t\t\t__m256 dz = _mm256_sub_ps(pzj, pzi);\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\tdx = _mm256_sub_ps(dx,\n\t\t\t\t\t_mm256_sub_ps(\n\t\t\t\t\t\t_mm256_and_ps(_mm256_cmp_ps(dx, halfDomainWidth, _CMP_GT_OQ), domainSizeX),\n\t\t\t\t\t\t_mm256_and_ps(_mm256_cmp_ps(dx, negHalfDomainWidth, _CMP_LT_OQ), domainSizeX)\n\t\t\t\t\t));\n\n\t\t\t\tdy = _mm256_sub_ps(dy,\n\t\t\t\t\t_mm256_sub_ps(\n\t\t\t\t\t\t_mm256_and_ps(_mm256_cmp_ps(dy, halfDomainHeight, _CMP_GT_OQ), domainSizeY),\n\t\t\t\t\t\t_mm256_and_ps(_mm256_cmp_ps(dy, negHalfDomainHeight, _CMP_LT_OQ), domainSizeY)\n\t\t\t\t\t));\n\n\t\t\t\tdz = _mm256_sub_ps(dz,\n\t\t\t\t\t_mm256_sub_ps(\n\t\t\t\t\t\t_mm256_and_ps(_mm256_cmp_ps(dz, halfDomainDepth, _CMP_GT_OQ), domainSizeZ),\n\t\t\t\t\t\t_mm256_and_ps(_mm256_cmp_ps(dz, negHalfDomainDepth, _CMP_LT_OQ), domainSizeZ)\n\t\t\t\t\t));\n\t\t\t}\n\n\t\t\t__m256 distSq =\n\t\t\t\t_mm256_add_ps(\n\t\t\t\t\t_mm256_add_ps(\n\t\t\t\t\t\t_mm256_mul_ps(dx, dx),\n\t\t\t\t\t\t_mm256_mul_ps(dy, dy)),\n\t\t\t\t\t_mm256_add_ps(\n\t\t\t\t\t\t_mm256_mul_ps(dz, dz),\n\t\t\t\t\t\tsoftVec));\n\n\t\t\t__m256 invDist = _mm256_rsqrt_ps(distSq);\n\n\t\t\t__m256 nrTerm = _mm256_mul_ps(pointFive, distSq);\n\t\t\tnrTerm = _mm256_mul_ps(nrTerm, invDist);\n\t\t\tnrTerm = _mm256_mul_ps(nrTerm, invDist);\n\n\t\t\t__m256 nrFactor = _mm256_sub_ps(threeHalfs, nrTerm);\n\n\t\t\tinvDist = _mm256_mul_ps(invDist, nrFactor);\n\n\t\t\t__m256 invDist3 = _mm256_mul_ps(invDist, _mm256_mul_ps(invDist, invDist));\n\n\t\t\t__m256 factor = _mm256_mul_ps(gVec, _mm256_mul_ps(mj, invDist3));\n\n\t\t\ttotalAccX = _mm256_add_ps(totalAccX, _mm256_mul_ps(dx, factor));\n\t\t\ttotalAccY = _mm256_add_ps(totalAccY, _mm256_mul_ps(dy, factor));\n\t\t\ttotalAccZ = _mm256_add_ps(totalAccZ, _mm256_mul_ps(dz, factor));\n\t\t}\n\n\t\tfloat accX_array[8], accY_array[8], accZ_array[8];\n\t\t_mm256_storeu_ps(accX_array, totalAccX);\n\t\t_mm256_storeu_ps(accY_array, totalAccY);\n\t\t_mm256_storeu_ps(accZ_array, totalAccZ);\n\n\t\tfloat finalAccX = 0.0f;\n\t\tfloat finalAccY = 0.0f;\n\t\tfloat finalAccZ = 0.0f;\n\n\t\tfor (int k = 0; k < 8; k++) {\n\t\t\tfinalAccX += accX_array[k];\n\t\t\tfinalAccY += accY_array[k];\n\t\t\tfinalAccZ += accZ_array[k];\n\t\t}\n\n\t\tfor (; j < n; j++) {\n\t\t\tfloat dx = posXPtr[j] - p_ix;\n\t\t\tfloat dy = posYPtr[j] - p_iy;\n\t\t\tfloat dz = posZPtr[j] - p_iz;\n\n\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\tdx -= myVar.domainSize3D.x * ((dx > myVar.halfDomain3DWidth) - (dx < -myVar.halfDomain3DWidth));\n\t\t\t\tdy -= myVar.domainSize3D.y * ((dy > myVar.halfDomain3DHeight) - (dy < -myVar.halfDomain3DHeight));\n\t\t\t\tdz -= myVar.domainSize3D.z * ((dz > myVar.halfDomain3DDepth) - (dz < -myVar.halfDomain3DDepth));\n\t\t\t}\n\n\t\t\tfloat distSq = dx * dx + dy * dy + dz * dz + myVar.softening;\n\n\t\t\tfloat invDist = 1.0f / sqrt(distSq);\n\t\t\tfloat invDist3 = invDist * invDist * invDist;\n\t\t\tfloat factor = myVar.G * massPtr[j] * invDist3;\n\n\t\t\tfinalAccX += dx * factor;\n\t\t\tfinalAccY += dy * factor;\n\t\t\tfinalAccZ += dz * factor;\n\t\t}\n\n\t\taccXPtr[i] = finalAccX;\n\t\taccYPtr[i] = finalAccY;\n\t\taccZPtr[i] = finalAccZ;\n\t}\n}\n\nvoid Physics3D::temperatureCalculation(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, UpdateVariables& myVar) {\n\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\tParticlePhysics3D& p = pParticles[i];\n\t\tauto it = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel);\n\t\tif (it != SPHMaterials::idToMaterial.end()) {\n\t\t\tSPHMaterial* pMat = it->second;\n\n\t\t\tfloat pTotalVel = sqrtf(p.vel.x * p.vel.x + p.vel.y * p.vel.y + p.vel.z * p.vel.z);\n\t\t\tfloat pTotalPrevVel = sqrtf(p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y + p.prevVel.z * p.prevVel.z);\n\n\t\t\tp.ke = 0.5f * p.sphMass * pTotalVel * pTotalVel;\n\t\t\tp.prevKe = 0.5f * p.sphMass * pTotalPrevVel * pTotalPrevVel;\n\n\t\t\tfloat q = std::abs(p.ke - p.prevKe);\n\n\t\t\tfloat dTemp = q / (2.0f * pMat->heatConductivity * p.sphMass + 1.0f);\n\t\t\tp.temp += dTemp;\n\n\t\t\tfloat tempDifference = p.temp - myVar.ambientTemp;\n\t\t\tfloat dTempCooling = -(pMat->heatConductivity * myVar.globalAmbientHeatRate) * tempDifference * myVar.timeFactor;\n\t\t\tp.temp += dTempCooling;\n\n\n\t\t\tif (p.temp >= pMat->hotPoint) {\n\t\t\t\tp.sphMass = pMat->hotMassMult;\n\t\t\t\tp.mass = UpdateVariables::particleBaseMass * p.sphMass;\n\t\t\t\tp.restDens = pMat->hotRestDens;\n\t\t\t\tp.stiff = pMat->hotStiff;\n\t\t\t\tp.visc = pMat->hotVisc;\n\t\t\t\tp.cohesion = pMat->hotCohesion;\n\t\t\t\tif (pMat->coldPoint == 0.0f) {\n\t\t\t\t\tp.isHotPoint = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (p.temp <= pMat->coldPoint) {\n\t\t\t\tp.sphMass = pMat->coldMassMult;\n\t\t\t\tp.mass = UpdateVariables::particleBaseMass * p.sphMass;\n\t\t\t\tp.restDens = pMat->coldRestDens;\n\t\t\t\tp.stiff = pMat->coldStiff;\n\t\t\t\tp.visc = pMat->coldVisc;\n\t\t\t\tp.cohesion = pMat->coldCohesion;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tp.sphMass = pMat->massMult;\n\t\t\t\tp.mass = UpdateVariables::particleBaseMass * p.sphMass;\n\t\t\t\tp.restDens = pMat->restDens;\n\t\t\t\tp.stiff = pMat->stiff;\n\t\t\t\tp.visc = pMat->visc;\n\t\t\t\tp.cohesion = pMat->cohesion;\n\t\t\t\tif (pMat->coldPoint != 0.0f) {\n\t\t\t\t\tp.isHotPoint = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (pMat->coldPoint == 0.0f) {\n\t\t\t\tif (p.temp <= pMat->hotPoint && p.isHotPoint) {\n\t\t\t\t\tp.hasSolidified = true;\n\t\t\t\t\tp.isHotPoint = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (p.temp <= pMat->coldPoint && p.isHotPoint) {\n\t\t\t\t\tp.hasSolidified = true;\n\t\t\t\t\tp.isHotPoint = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfloat pTotalVel = sqrtf(p.vel.x * p.vel.x + p.vel.y * p.vel.y + p.vel.z * p.vel.z);\n\t\t\tfloat pTotalPrevVel = sqrtf(p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y + p.prevVel.z * p.prevVel.z);\n\n\t\t\tp.ke = 0.5f * p.sphMass * pTotalVel * pTotalVel;\n\t\t\tp.prevKe = 0.5f * p.sphMass * pTotalPrevVel * pTotalPrevVel;\n\n\t\t\tfloat q = std::abs(p.ke - p.prevKe);\n\n\t\t\tfloat dTemp = q / (2.0f * 0.05f * p.sphMass + 1.0f);\n\t\t\tp.temp += dTemp;\n\n\t\t\tfloat tempDifference = p.temp - myVar.ambientTemp;\n\t\t\tfloat dTempCooling = -(0.05f * myVar.globalAmbientHeatRate) * tempDifference * myVar.timeFactor;\n\t\t\tp.temp += dTempCooling;\n\t\t}\n\t}\n}\n\nvoid Physics3D::readFlattenBack3D(std::vector<ParticlePhysics3D>& pParticles3D) {\n\tsize_t particleCount = pParticles3D.size();\n\n#pragma omp parallel for schedule(static)\n\tfor (int i = 0; i < static_cast<int>(particleCount); i++) {\n\n\t\tpParticles3D[i].pos.x = posX[i];\n\t\tpParticles3D[i].pos.y = posY[i];\n\t\tpParticles3D[i].pos.z = posZ[i];\n\n\t\tpParticles3D[i].vel.x = velX[i];\n\t\tpParticles3D[i].vel.y = velY[i];\n\t\tpParticles3D[i].vel.z = velZ[i];\n\n\t\tpParticles3D[i].acc.x = accX[i];\n\t\tpParticles3D[i].acc.y = accY[i];\n\t\tpParticles3D[i].acc.z = accZ[i];\n\n\t\tpParticles3D[i].prevVel.x = prevVelX[i];\n\t\tpParticles3D[i].prevVel.y = prevVelY[i];\n\t\tpParticles3D[i].prevVel.z = prevVelZ[i];\n\n\t\tpParticles3D[i].mass = mass[i];\n\n\t\tpParticles3D[i].temp = temp[i];\n\t}\n}\n\nvoid Physics3D::integrateStart3D(\n\tstd::vector<ParticlePhysics3D>& pParticles3D,\n\tstd::vector<ParticleRendering3D>& rParticles3D,\n\tUpdateVariables& myVar) {\n\n\tfloat dt = myVar.timeFactor;\n\tfloat halfDt = dt * 0.5f;\n\tfloat sphMaxVelSq = myVar.sphMaxVel * myVar.sphMaxVel;\n\n#pragma omp parallel for schedule(dynamic)\n\tfor (size_t i = 0; i < pParticles3D.size(); i++) {\n\t\tParticlePhysics3D& p = pParticles3D[i];\n\n\t\tif (rParticles3D[i].isPinned) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tp.prevVel = p.vel;\n\n\t\tp.vel += p.acc * halfDt;\n\n\t\tif (myVar.isSPHEnabled) {\n\t\t\tfloat vSq =\n\t\t\t\tp.vel.x * p.vel.x +\n\t\t\t\tp.vel.y * p.vel.y +\n\t\t\t\tp.vel.z * p.vel.z;\n\n\t\t\tif (vSq > sphMaxVelSq) {\n\t\t\t\tfloat prevVSq =\n\t\t\t\t\tp.prevVel.x * p.prevVel.x +\n\t\t\t\t\tp.prevVel.y * p.prevVel.y +\n\t\t\t\t\tp.prevVel.z * p.prevVel.z;\n\n\t\t\t\tif (prevVSq > 0.00001f) {\n\t\t\t\t\tfloat invPrevLen = myVar.sphMaxVel / sqrtf(prevVSq);\n\t\t\t\t\tp.prevVel *= invPrevLen;\n\t\t\t\t}\n\n\t\t\t\tfloat invLen = myVar.sphMaxVel / sqrtf(vSq);\n\t\t\t\tp.vel *= invLen;\n\t\t\t}\n\t\t}\n\n\t\tp.pos += p.vel * dt;\n\n\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\n\t\t\tif (p.pos.x < -myVar.halfDomain3DWidth)\n\t\t\t\tp.pos.x += myVar.domainSize3D.x;\n\n\t\t\telse if (p.pos.x >= myVar.halfDomain3DWidth)\n\t\t\t\tp.pos.x -= myVar.domainSize3D.x;\n\n\t\t\tif (p.pos.y < -myVar.halfDomain3DHeight)\n\t\t\t\tp.pos.y += myVar.domainSize3D.y;\n\t\t\telse if (p.pos.y >= myVar.halfDomain3DHeight)\n\t\t\t\tp.pos.y -= myVar.domainSize3D.y;\n\n\t\t\tif (p.pos.z < -myVar.halfDomain3DDepth)\n\t\t\t\tp.pos.z += myVar.domainSize3D.z;\n\t\t\telse if (p.pos.z >= myVar.halfDomain3DDepth)\n\t\t\t\tp.pos.z -= myVar.domainSize3D.z;\n\t\t}\n\t}\n}\n\nvoid Physics3D::pruneParticles(std::vector<ParticlePhysics3D>& pParticles,\n\tstd::vector<ParticleRendering3D>& rParticles,\n\tUpdateVariables& myVar) {\n\tfor (size_t i = 0; i < pParticles.size(); ) {\n\t\tfloat x = pParticles[i].pos.x;\n\t\tfloat y = pParticles[i].pos.y;\n\t\tfloat z = pParticles[i].pos.z;\n\n\t\tfloat halfX = myVar.domainSize3D.x * 0.5f;\n\t\tfloat halfY = myVar.domainSize3D.y * 0.5f;\n\t\tfloat halfZ = myVar.domainSize3D.z * 0.5f;\n\n\t\tif (x <= -halfX || x >= halfX ||\n\t\t\ty <= -halfY || y >= halfY ||\n\t\t\tz <= -halfZ || z >= halfZ) {\n\t\t\tif (pParticles.size() > 1) {\n\t\t\t\tstd::swap(pParticles[i], pParticles.back());\n\t\t\t\tstd::swap(rParticles[i], rParticles.back());\n\t\t\t}\n\t\t\tpParticles.pop_back();\n\t\t\trParticles.pop_back();\n\t\t}\n\t\telse {\n\t\t\ti++;\n\t\t}\n\t}\n}\n\nvoid Physics3D::createConstraints(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, bool& constraintCreateSpecialFlag,\n\tUpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tbool shouldCreateConstraints = IO::shortcutPress(KEY_P) || myVar.constraintAllSolids || constraintCreateSpecialFlag || myVar.constraintSelected;\n\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\tParticlePhysics3D& pi = pParticles[i];\n\n\t\tif (constraintCreateSpecialFlag) {\n\t\t\tif (!rParticles[i].isBeingDrawn) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.constraintSelected) {\n\t\t\tif (!rParticles[i].isSelected) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tSPHMaterial* pMatI = nullptr;\n\t\tauto matItI = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel);\n\t\tif (matItI != SPHMaterials::idToMaterial.end()) {\n\t\t\tpMatI = matItI->second;\n\t\t}\n\n\t\tif (shouldCreateConstraints) {\n\t\t\tif (pMatI) {\n\t\t\t\tif (pMatI->coldPoint == 0.0f) {\n\t\t\t\t\tif (pi.temp >= pMatI->hotPoint) continue;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (pi.temp >= pMatI->coldPoint) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (!pi.hasSolidified) continue;\n\t\t\tconstraintMap.clear();\n\t\t\tpi.hasSolidified = false;\n\t\t}\n\n\t\tstd::vector<size_t> neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.pos);\n\n\t\tfor (size_t j : neighborIndices) {\n\t\t\tsize_t neighborIndex = j;\n\n\t\t\tif (neighborIndex == i) continue;\n\n\t\t\tParticlePhysics3D& pj = myParam.pParticles3D[neighborIndex];\n\n\t\t\tfloat distSq = glm::dot(pj.pos - pi.pos, pj.pos - pi.pos);\n\n\t\t\tif (distSq > 12.0f) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (constraintCreateSpecialFlag) {\n\t\t\t\tif (!rParticles[neighborIndex].isBeingDrawn) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (myVar.constraintSelected) {\n\t\t\t\tif (!rParticles[neighborIndex].isSelected) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tSPHMaterial* pMatJ = nullptr;\n\t\t\tauto matItJ = SPHMaterials::idToMaterial.find(rParticles[neighborIndex].sphLabel);\n\t\t\tif (matItJ != SPHMaterials::idToMaterial.end()) {\n\t\t\t\tpMatJ = matItJ->second;\n\t\t\t}\n\n\t\t\tif (pMatI && pMatJ && pMatI->coldPoint == 0.0f && pMatJ->coldPoint != 0.0f) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tuint64_t key = makeKey(pi.id, pj.id);\n\t\t\tif (constraintMap.find(key) != constraintMap.end()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat resistance = 0.6f;\n\t\t\tif (pMatI && pMatJ) {\n\t\t\t\tresistance = (pMatI->constraintResistance + pMatJ->constraintResistance) * 0.5f;\n\t\t\t}\n\n\t\t\tfloat plasticityPoint = 0.6f;\n\t\t\tif (pMatI && pMatJ) {\n\t\t\t\tplasticityPoint = (pMatI->constraintPlasticPoint + pMatJ->constraintPlasticPoint) * 0.5f;\n\t\t\t}\n\n\t\t\tif (pi.id < pj.id) {\n\n\t\t\t\tfloat currentDist = glm::distance(pi.pos, pj.pos);\n\t\t\t\tbool broken = false;\n\t\t\t\tif (pMatI && pMatJ) {\n\t\t\t\t\tparticleConstraints.push_back({ pi.id, pj.id, currentDist, currentDist, pMatI->constraintStiffness, resistance, 0.0f, plasticityPoint, broken });\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfloat defaultStiffness = 60.0f;\n\t\t\t\t\tparticleConstraints.push_back({ pi.id, pj.id, currentDist, currentDist, defaultStiffness, resistance, 0.0f, plasticityPoint, broken });\n\t\t\t\t}\n\t\t\t\tconstraintMap[key] = &particleConstraints.back();\n\t\t\t}\n\t\t}\n\t}\n\n\tconstraintCreateSpecialFlag = false;\n\tmyVar.constraintAllSolids = false;\n\tmyVar.constraintSelected = false;\n}\n\nvoid Physics3D::constraints(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, UpdateVariables& myVar) {\n\n\tif (myVar.deleteAllConstraints) {\n\t\tparticleConstraints.clear();\n\t\tconstraintMap.clear();\n\t\tmyVar.deleteAllConstraints = false;\n\t\treturn;\n\t}\n\n\tuint32_t maxId = 0;\n\tfor (const auto& p : pParticles) if (p.id > maxId) maxId = p.id;\n\n\tidToIndexTable.assign(maxId + 1, -1);\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\tidToIndexTable[pParticles[i].id] = i;\n\t}\n\n\tauto getIdx = [&](uint32_t id) -> int64_t {\n\t\tif (id >= idToIndexTable.size()) return -1;\n\t\treturn idToIndexTable[id];\n\t\t};\n\n\tif (myVar.deleteSelectedConstraints) {\n\t\tfor (auto& constraint : particleConstraints) {\n\t\t\tint64_t idx1 = getIdx(constraint.id1);\n\t\t\tint64_t idx2 = getIdx(constraint.id2);\n\n\t\t\tif (idx1 == -1 || idx2 == -1) {\n\t\t\t\tconstraint.isBroken = true;\n\t\t\t}\n\t\t\telse if (rParticles[idx1].isSelected || rParticles[idx2].isSelected) {\n\t\t\t\tconstraint.isBroken = true;\n\t\t\t}\n\t\t}\n\t\tmyVar.deleteSelectedConstraints = false;\n\t}\n\n\tif (!particleConstraints.empty()) {\n\t\tauto new_end = std::remove_if(particleConstraints.begin(), particleConstraints.end(),\n\t\t\t[&](const auto& constraint) {\n\t\t\t\tint64_t idx1 = getIdx(constraint.id1);\n\t\t\t\tint64_t idx2 = getIdx(constraint.id2);\n\t\t\t\treturn idx1 == -1 || idx2 == -1 || constraint.isBroken;\n\t\t\t});\n\n\t\tfor (auto it = new_end; it != particleConstraints.end(); ++it) {\n\t\t\tconstraintMap.erase(makeKey(it->id1, it->id2));\n\t\t}\n\t\tparticleConstraints.erase(new_end, particleConstraints.end());\n\n\t\tbool enforceTriangles = true;\n\n\t\tmyVar.frameCount++;\n\t\tif (enforceTriangles && !particleConstraints.empty() && myVar.frameCount % 5 == 0) {\n\n\t\t\tstd::vector<std::vector<uint32_t>> adjacency(maxId + 1);\n\n\t\t\tfor (const auto& c : particleConstraints) {\n\t\t\t\tif (c.isBroken) continue;\n\n\t\t\t\tif (c.id1 <= maxId && c.id2 <= maxId) {\n\t\t\t\t\tadjacency[c.id1].push_back(c.id2);\n\t\t\t\t\tadjacency[c.id2].push_back(c.id1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (auto& constraint : particleConstraints) {\n\t\t\t\tif (constraint.isBroken) continue;\n\n\t\t\t\tuint32_t idA = constraint.id1;\n\t\t\t\tuint32_t idB = constraint.id2;\n\n\t\t\t\tbool partOfTriangle = false;\n\n\t\t\t\tconst std::vector<uint32_t>& neighborsOfA = adjacency[idA];\n\t\t\t\tconst std::vector<uint32_t>& neighborsOfB = adjacency[idB];\n\n\t\t\t\tfor (uint32_t neighborA : neighborsOfA) {\n\n\t\t\t\t\tfor (uint32_t neighborB : neighborsOfB) {\n\t\t\t\t\t\tif (neighborA == neighborB) {\n\t\t\t\t\t\t\tpartOfTriangle = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (partOfTriangle) break;\n\t\t\t\t}\n\n\t\t\t\tif (!partOfTriangle) {\n\t\t\t\t\tconstraint.isBroken = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst int substeps = 15;\n\t\tfor (int step = 0; step < substeps; step++) {\n\n#pragma omp parallel for schedule(dynamic)\n\t\t\tfor (int64_t i = 0; i < (int64_t)particleConstraints.size(); i++) {\n\t\t\t\tauto& constraint = particleConstraints[i];\n\n\t\t\t\tif (constraint.isBroken) continue;\n\n\t\t\t\tint64_t idx1 = getIdx(constraint.id1);\n\t\t\t\tint64_t idx2 = getIdx(constraint.id2);\n\n\t\t\t\tif (idx1 == -1 || idx2 == -1) {\n\t\t\t\t\tconstraint.isBroken = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tParticlePhysics3D& pi = pParticles[idx1];\n\t\t\t\tParticlePhysics3D& pj = pParticles[idx2];\n\n\t\t\t\tSPHMaterial* pMatI = SPHMaterials::idToMaterial[rParticles[idx1].sphLabel];\n\t\t\t\tSPHMaterial* pMatJ = SPHMaterials::idToMaterial[rParticles[idx2].sphLabel];\n\n\t\t\t\tglm::vec3 delta = pj.pos - pi.pos;\n\n\t\t\t\tif (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) {\n\t\t\t\t\tdelta.x = fmod(delta.x + myVar.domainSize.x * 1.5f, myVar.domainSize.x) - myVar.domainSize.x * 0.5f;\n\t\t\t\t\tdelta.y = fmod(delta.y + myVar.domainSize.y * 1.5f, myVar.domainSize.y) - myVar.domainSize.y * 0.5f;\n\t\t\t\t}\n\n\t\t\t\tfloat currentLength = glm::length(delta);\n\t\t\t\tif (currentLength < 0.0001f) continue;\n\n\t\t\t\tglm::vec3 dir = delta / currentLength;\n\t\t\t\tconstraint.displacement = currentLength - constraint.restLength;\n\n\t\t\t\tif (!myVar.unbreakableConstraints) {\n\t\t\t\t\tif (pMatI && pMatJ) {\n\t\t\t\t\t\tif (!pMatI->isPlastic || !pMatJ->isPlastic) {\n\t\t\t\t\t\t\tconstraint.isPlastic = false;\n\t\t\t\t\t\t\tfloat limit = (constraint.resistance * myVar.globalConstraintResistance) * constraint.restLength;\n\t\t\t\t\t\t\tif (std::abs(constraint.displacement) >= limit) {\n\t\t\t\t\t\t\t\tconstraint.isBroken = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tconstraint.isPlastic = true;\n\t\t\t\t\t\t\tif (std::abs(constraint.displacement) >= constraint.plasticityPoint * constraint.originalLength) {\n\t\t\t\t\t\t\t\tconstraint.restLength += constraint.displacement;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfloat breakLimit = (constraint.originalLength + constraint.originalLength * (constraint.resistance * myVar.globalConstraintResistance)) * (pMatI->constraintPlasticPointMult + pMatJ->constraintPlasticPointMult) * 0.5f;\n\n\t\t\t\t\t\t\tif (std::abs(constraint.restLength) >= breakLimit) {\n\t\t\t\t\t\t\t\tconstraint.isBroken = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (pi.isHotPoint || pj.isHotPoint) constraint.isBroken = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (myVar.timeFactor > 0.0f && myVar.grid3DExists && !constraint.isBroken) {\n\n\t\t\t\t\tglm::vec3 springForce = constraint.stiffness * constraint.displacement * dir * pi.mass * myVar.globalConstraintStiffnessMult;\n\t\t\t\t\tglm::vec3 relVel = pj.vel - pi.vel;\n\t\t\t\t\tglm::vec3 dampForce = -globalConstraintDamping * glm::dot(relVel, dir) * dir * pi.mass;\n\t\t\t\t\tglm::vec3 totalForce = springForce + dampForce;\n\n#pragma omp atomic\n\t\t\t\t\tpi.acc.x += totalForce.x / pi.mass;\n#pragma omp atomic\n\t\t\t\t\tpi.acc.y += totalForce.y / pi.mass;\n#pragma omp atomic\n\t\t\t\t\tpi.acc.z += totalForce.z / pi.mass;\n#pragma omp atomic\n\t\t\t\t\tpj.acc.x -= totalForce.x / pj.mass;\n#pragma omp atomic\n\t\t\t\t\tpj.acc.y -= totalForce.y / pj.mass;\n#pragma omp atomic\n\t\t\t\t\tpj.acc.z -= totalForce.z / pj.mass;\n\n\t\t\t\t\tfloat correctionFactor = constraint.stiffness * stiffCorrectionRatio * myVar.globalConstraintStiffnessMult;\n\t\t\t\t\tglm::vec3 correction = dir * constraint.displacement * correctionFactor;\n\t\t\t\t\tfloat massSum = pi.mass + pj.mass;\n\t\t\t\t\tglm::vec3 correctionI = correction * (pj.mass / massSum);\n\t\t\t\t\tglm::vec3 correctionJ = correction * (pi.mass / massSum);\n\n#pragma omp atomic\n\t\t\t\t\tpi.pos.x += correctionI.x;\n#pragma omp atomic\n\t\t\t\t\tpi.pos.y += correctionI.y;\n#pragma omp atomic\n\t\t\t\t\tpi.pos.z += correctionI.z;\n#pragma omp atomic\n\t\t\t\t\tpj.pos.x -= correctionJ.x;\n#pragma omp atomic\n\t\t\t\t\tpj.pos.y -= correctionJ.y;\n#pragma omp atomic\n\t\t\t\t\tpj.pos.z -= correctionJ.z;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Physics3D::pausedConstraints(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, UpdateVariables& myVar) {\n\n\tfor (size_t i = 0; i < particleConstraints.size(); i++) {\n\t\tauto& constraint = particleConstraints[i];\n\n\t\tfloat prevLength = constraint.restLength;\n\t}\n}\n\nvoid Physics3D::integrateEnd3D(std::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, UpdateVariables& myVar) {\n#pragma omp parallel for schedule(dynamic)\n\tfor (size_t i = 0; i < pParticles3D.size(); i++) {\n\n\t\tif (rParticles3D[i].isPinned) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tpParticles3D[i].vel += pParticles3D[i].acc * (myVar.timeFactor * 0.5f);\n\t}\n}\n\nvoid Physics3D::spawnCorrection(UpdateParameters& myParam, bool& hasAVX2, const int& iterations) {\n\n#pragma omp parallel for\n\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\n\t\tParticlePhysics3D& pi = myParam.pParticles3D[i];\n\n\t\tstd::vector<size_t> neighborIndices =\n\t\t\tQueryNeighbors3D::queryNeighbors3D(myParam, hasAVX2, 64, pi.pos);\n\n\t\tfor (size_t j : neighborIndices) {\n\t\t\tsize_t neighborIndex = j;\n\n\t\t\tif (neighborIndex == i) continue;\n\n\t\t\tParticlePhysics3D& pj = myParam.pParticles3D[neighborIndex];\n\n\t\t\tif (!myParam.rParticles3D[neighborIndex].isBeingDrawn || !myParam.rParticles3D[i].isBeingDrawn) continue;\n\n\t\t\tglm::vec3 d = pj.pos - pi.pos;\n\n\t\t\tfloat dSq = glm::dot(d, d);\n\n\t\t\tconst float minDist = 2.4f;\n\t\t\tconst float minDistSq = minDist * minDist;\n\n\t\t\tif (dSq > 0.000001f && dSq < minDistSq) {\n\n\t\t\t\tfloat dist = sqrt(dSq);\n\n\t\t\t\tglm::vec3 dir = -d / dist;\n\n\t\t\t\tfloat penetration = minDist - dist;\n\n\t\t\t\tfloat totalMass = pi.mass + pj.mass;\n\n\t\t\t\tif (totalMass > 0.0f) {\n\n\t\t\t\t\tfloat piMove = pj.mass / totalMass;\n\t\t\t\t\tfloat pjMove = pi.mass / totalMass;\n\n\t\t\t\t\tglm::vec3 correction = dir * penetration;\n\n\t\t\t\t\tpi.pos += correction * piMove;\n\t\t\t\t\tpj.pos -= correction * pjMove;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tmyParam.rParticles3D[i].spawnCorrectIter++;\n\t}\n}"
  },
  {
    "path": "GalaxyEngine/src/Physics/quadtree.cpp",
    "content": "#include \"Particles/particle.h\"\n\n#include \"Physics/quadtree.h\"\n\nNode::Node(glm::vec2 pos, float size,\n\tuint32_t startIndex, uint32_t endIndex,\n\tstd::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles) {\n\n\tthis->pos = pos;\n\tthis->size = size;\n\tthis->startIndex = startIndex;\n\tthis->endIndex = endIndex;\n\tthis->gridMass = 0.0f;\n\tthis->centerOfMass = { 0.0f, 0.0f };\n\n\tif ((((endIndex - startIndex) <= 4 /*Max Leaf Particles*/ && size <= 2.0f) /*Max Non-Dense Size*/ || (endIndex - startIndex) == 1) ||\n\t\tsize <= 0.000001f /*Min Leaf Size*/) {\n\t\tcomputeLeafMass(pParticles);\n\t}\n\telse {\n\t\tsubGridMaker(pParticles, rParticles);\n\t\tcomputeInternalMass();\n\n\t\tcalculateNextNeighbor();\n\t}\n}\n\nvoid Node::subGridMaker(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles) {\n\n    glm::vec2 mid = pos + size * 0.5f;\n\n    uint32_t boundaries[5];\n    boundaries[0] = startIndex;\n    boundaries[4] = endIndex;\n\n    auto beginIt = pParticles.begin();\n\n    auto isBottomHalf = [mid](const ParticlePhysics& pParticle) {\n        return pParticle.pos.y < mid.y;\n        };\n\n    auto midY_Iterator = std::partition_point(\n        beginIt + boundaries[0],\n        beginIt + boundaries[4],\n        isBottomHalf\n    );\n    boundaries[2] = std::distance(beginIt, midY_Iterator);\n\n    auto isLeftHalf = [mid](const ParticlePhysics& pParticle) {\n        return pParticle.pos.x < mid.x;\n        };\n\n    auto midX_BottomIterator = std::partition_point(\n        beginIt + boundaries[0],\n        beginIt + boundaries[2],\n        isLeftHalf\n    );\n    boundaries[1] = std::distance(beginIt, midX_BottomIterator);\n\n    auto midX_TopIterator = std::partition_point(\n        beginIt + boundaries[2],\n        beginIt + boundaries[4],\n        isLeftHalf\n    );\n    boundaries[3] = std::distance(beginIt, midX_TopIterator);\n\n    for (int q = 0; q < 4; ++q) {\n        if (boundaries[q + 1] > boundaries[q]) {\n\n            glm::vec2 newPos = { pos.x + ((q & 1) ? size * 0.5f : 0.0f), pos.y + ((q & 2) ? size * 0.5f : 0.0f) };\n\n            uint32_t childIndex = globalNodes.size();\n            globalNodes.emplace_back();\n\n            Node& newNode = globalNodes[childIndex];\n            newNode = Node(\n                newPos, size * 0.5f,\n                boundaries[q], boundaries[q + 1],\n                pParticles, rParticles\n            );\n\n            bool lr = (q & 1) ? 1 : 0;\n            bool ud = (q & 2) ? 1 : 0;\n\n            subGrids[lr][ud] = childIndex;\n        }\n    }\n}\n\nvoid Quadtree::root(std::vector<ParticlePhysics>& pParticles,\n\tstd::vector<ParticleRendering>& rParticles) {\n\n\tif (!pParticles.empty()) {\n\t\tglobalNodes.reserve(pParticles.size() * 4);\n\t}\n\telse {\n\t\tglobalNodes.reserve(4);\n\t}\n\n\tglobalNodes.emplace_back();\n\n\tglobalNodes[0] = Node(\n\t\t{ boundingBox.x, boundingBox.y }, boundingBox.z,\n\t\t0, pParticles.size(),\n\t\tpParticles, rParticles\n\t);\n}\n\nNode3D::Node3D(glm::vec3 pos, float size,\n    uint32_t startIndex, uint32_t endIndex,\n    std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {\n\n    this->pos = pos;\n    this->size = size;\n    this->startIndex = startIndex;\n    this->endIndex = endIndex;\n    this->gridMass = 0.0f;\n    this->centerOfMass = { 0.0f, 0.0f, 0.0f };\n\n    if ((((endIndex - startIndex) <= 4 /*Max Leaf Particles*/ && size <= 2.0f) /*Max Non-Dense Size*/ || (endIndex - startIndex) == 1) ||\n        size <= 0.000001f /*Min Leaf Size*/) {\n        computeLeafMass3D(pParticles);\n    }\n    else {\n        subGridMaker3D(pParticles, rParticles);\n        computeInternalMass3D();\n        calculateNextNeighbor3D();\n    }\n}\n\ntemplate<typename Predicate>\nuint32_t dualPartition3D(std::vector<ParticlePhysics3D>& pParticlesVector, std::vector<ParticleRendering3D>& rParticlesVector, uint32_t begin, uint32_t end, Predicate predicate) {\n    uint32_t i = begin;\n    for (uint32_t j = begin; j < end; ++j) {\n        if (predicate(pParticlesVector[j])) {\n            if (i != j) {\n                std::swap(pParticlesVector[i], pParticlesVector[j]);\n                std::swap(rParticlesVector[i], rParticlesVector[j]);\n            }\n            ++i;\n        }\n    }\n    return i;\n}\n\nvoid Node3D::subGridMaker3D(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {\n\n    glm::vec3 mid = pos + size * 0.5f;\n\n    uint32_t boundaries[9];\n    boundaries[0] = startIndex;\n    boundaries[8] = endIndex;\n\n    auto beginIt = pParticles.begin();\n\n    auto isBottomHalfZ = [mid](const ParticlePhysics3D& p) {\n        return p.pos.z < mid.z;\n        };\n    auto midZ_It = std::partition_point(\n        beginIt + boundaries[0],\n        beginIt + boundaries[8],\n        isBottomHalfZ\n    );\n    boundaries[4] = std::distance(beginIt, midZ_It);\n\n    auto isBackHalfY = [mid](const ParticlePhysics3D& p) {\n        return p.pos.y < mid.y;\n        };\n\n    auto midY_BottomIt = std::partition_point(\n        beginIt + boundaries[0],\n        beginIt + boundaries[4],\n        isBackHalfY\n    );\n    boundaries[2] = std::distance(beginIt, midY_BottomIt);\n\n    auto midY_TopIt = std::partition_point(\n        beginIt + boundaries[4],\n        beginIt + boundaries[8],\n        isBackHalfY\n    );\n    boundaries[6] = std::distance(beginIt, midY_TopIt);\n\n    auto isLeftHalfX = [mid](const ParticlePhysics3D& p) {\n        return p.pos.x < mid.x;\n        };\n\n    auto midX_01_It = std::partition_point(beginIt + boundaries[0], beginIt + boundaries[2], isLeftHalfX);\n    boundaries[1] = std::distance(beginIt, midX_01_It);\n\n    auto midX_23_It = std::partition_point(beginIt + boundaries[2], beginIt + boundaries[4], isLeftHalfX);\n    boundaries[3] = std::distance(beginIt, midX_23_It);\n\n    auto midX_45_It = std::partition_point(beginIt + boundaries[4], beginIt + boundaries[6], isLeftHalfX);\n    boundaries[5] = std::distance(beginIt, midX_45_It);\n\n    auto midX_67_It = std::partition_point(beginIt + boundaries[6], beginIt + boundaries[8], isLeftHalfX);\n    boundaries[7] = std::distance(beginIt, midX_67_It);\n\n    for (int q = 0; q < 8; ++q) {\n        if (boundaries[q + 1] > boundaries[q]) {\n\n            float offsetX = (q & 1) ? size * 0.5f : 0.0f;\n            float offsetY = (q & 2) ? size * 0.5f : 0.0f;\n            float offsetZ = (q & 4) ? size * 0.5f : 0.0f;\n\n            glm::vec3 newPos = { pos.x + offsetX, pos.y + offsetY, pos.z + offsetZ };\n\n            uint32_t childIndex = (uint32_t)globalNodes3D.size();\n            globalNodes3D.emplace_back();\n\n            globalNodes3D[childIndex] = Node3D(\n                newPos, size * 0.5f,\n                boundaries[q], boundaries[q + 1],\n                pParticles, rParticles\n            );\n\n            int x = (q & 1) ? 1 : 0;\n            int y = (q & 2) ? 1 : 0;\n            int z = (q & 4) ? 1 : 0;\n\n            subGrids[x][y][z] = childIndex;\n        }\n    }\n}\n\nvoid Node3D::computeInternalMass3D() {\n    gridMass = 0.0f;\n    gridTemp = 0.0f;\n    centerOfMass = { 0.0f, 0.0f, 0.0f };\n\n    for (int i = 0; i < 2; ++i) {\n        for (int j = 0; j < 2; ++j) {\n            for (int k = 0; k < 2; ++k) {\n\n                uint32_t idx = subGrids[i][j][k];\n\n                if (idx == UINT32_MAX) continue;\n\n                Node3D& child = globalNodes3D[idx];\n\n                gridMass += child.gridMass;\n                gridTemp += child.gridTemp;\n                centerOfMass += child.centerOfMass * child.gridMass;\n            }\n        }\n    }\n\n    if (gridMass > 0) {\n        centerOfMass /= gridMass;\n    }\n}\n\nvoid Node3D::calculateNextNeighbor3D() {\n    next = 0;\n\n    for (int i = 0; i < 2; ++i) {\n        for (int j = 0; j < 2; ++j) {\n            for (int k = 0; k < 2; ++k) {\n\n                uint32_t idx = subGrids[i][j][k];\n\n                if (idx == UINT32_MAX) continue;\n\n                next++;\n                Node3D& child = globalNodes3D[idx];\n                next += child.next;\n            }\n        }\n    }\n}\n\nvoid Octree::root(std::vector<ParticlePhysics3D>& pParticles,\n    std::vector<ParticleRendering3D>& rParticles) {\n\n    if (!pParticles.empty()) {\n        globalNodes3D.clear();\n        globalNodes3D.reserve(pParticles.size() * 8);\n    }\n    else {\n        globalNodes3D.clear();\n        globalNodes3D.reserve(8);\n    }\n\n    globalNodes3D.emplace_back();\n\n    globalNodes3D[0] = Node3D(\n        rootPos, rootSize,\n        0, (uint32_t)pParticles.size(),\n        pParticles, rParticles\n    );\n}"
  },
  {
    "path": "GalaxyEngine/src/Physics/slingshot.cpp",
    "content": "#include \"IO/io.h\"\n\n#include \"Physics/slingshot.h\"\n#include \"parameters.h\"\n\nglm::vec2 slingshotPos = { 0.0f, 0.0f };\n\nSlingshot::Slingshot(glm::vec2 norm, float length) {\n\tthis->norm = norm;\n\tthis->length = length;\n}\n\n\nSlingshot Slingshot::particleSlingshot(UpdateVariables& myVar, SceneCamera myCamera) {\n\n\tif (IsMouseButtonPressed(1)) {\n\t\tmyVar.isDragging = false;\n\t}\n\n\tglm::vec2 mouseWorldPos = glm::vec2(GetScreenToWorld2D(GetMousePosition(), myCamera.camera).x, GetScreenToWorld2D(GetMousePosition(), myCamera.camera).y);\n\n\tif ((IsMouseButtonPressed(0) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) &&\n\t\t(myVar.toolSpawnHeavyParticle || myVar.toolSpawnGalaxy || myVar.toolSpawnStar)) || \n\t\tIO::shortcutPress(KEY_ONE) ||\n\t\tIO::shortcutPress(KEY_TWO) || \n\t\tIO::shortcutPress(KEY_J)\n\t) {   \n\t\tmyVar.isDragging = true;\n\t\tslingshotPos = mouseWorldPos;\n\t}\n\n\tif (myVar.isDragging) {\n\n\t\tglm::vec2 slingshotDist = slingshotPos - mouseWorldPos;\n\n\t\tfloat slingshotLengthSquared = slingshotDist.x * slingshotDist.x + slingshotDist.y * slingshotDist.y;\n\t\tfloat slingshotLength = sqrt(slingshotLengthSquared);\n\n\t\tif (slingshotLength != 0) {\n\n\t\t\tglm::vec2 norm = slingshotDist / slingshotLength;\n\t\t\tDrawCircleV({ slingshotPos.x, slingshotPos.y }, 5, BLUE);\n\t\t\tDrawLineV({ mouseWorldPos.x, mouseWorldPos.y }, { slingshotPos.x, slingshotPos.y }, RED);\n\n\t\t\tSlingshot slingshot(norm, slingshotLength);\n\t\t\treturn slingshot;\n\t\t}\n\t}\n\n\treturn Slingshot({ 0.0f, 0.0f }, 0.0f);\n}\n\nglm::vec3 slingshotPos3D = { 0.0f, 0.0f, 0.0f };\n\nSlingshot3D::Slingshot3D(glm::vec3 norm, float length) {\n\tthis->norm = norm;\n\tthis->length = length;\n}\n\n\nSlingshot3D Slingshot3D::particleSlingshot(UpdateVariables& myVar, glm::vec3& brushPos) {\n\n\tif (IsMouseButtonPressed(1)) {\n\t\tmyVar.isDragging = false;\n\t}\n\n\tif ((IsMouseButtonPressed(0) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) &&\n\t\t(myVar.toolSpawnHeavyParticle || myVar.toolSpawnGalaxy || myVar.toolSpawnStar)) ||\n\t\tIO::shortcutPress(KEY_ONE) ||\n\t\tIO::shortcutPress(KEY_TWO) ||\n\t\tIO::shortcutPress(KEY_J)\n\t\t) {\n\t\tmyVar.isDragging = true;\n\t\tslingshotPos3D = brushPos;\n\t}\n\n\tif (myVar.isDragging) {\n\t\tglm::vec3 slingshotDist = slingshotPos3D - brushPos;\n\n\t\tfloat slingshotLengthSquared = slingshotDist.x * slingshotDist.x + slingshotDist.y * slingshotDist.y + slingshotDist.z * slingshotDist.z;\n\t\tfloat slingshotLength = sqrt(slingshotLengthSquared);\n\n\t\tif (slingshotLength != 0) {\n\n\t\t\tglm::vec3 norm = slingshotDist / slingshotLength;\n\n\t\t\tDrawSphere({ slingshotPos3D.x, slingshotPos3D.y, slingshotPos3D.z }, 5, BLUE);\n\t\t\tDrawLine3D({ brushPos.x, brushPos.y, brushPos.z }, { slingshotPos3D.x, slingshotPos3D.y, slingshotPos3D.z }, { 255, 20, 20, 200 });\n\n\t\t\tSlingshot3D slingshot(norm, slingshotLength);\n\n\t\t\treturn slingshot;\n\t\t}\n\t}\n\n\treturn Slingshot3D({ 0.0f, 0.0f, 0.0f }, 0.0f);\n}"
  },
  {
    "path": "GalaxyEngine/src/Sound/sound.cpp",
    "content": "#include \"Sound/sound.h\"\n\nSound GESound::intro;\nSound GESound::soundButtonHover1;\nSound GESound::soundButtonHover2;\nSound GESound::soundButtonHover3;\nSound GESound::soundButtonEnable;\nSound GESound::soundButtonDisable;\n\nSound GESound::soundSliderSlide;\n\n// I make a pool of sounds so they don't cut eachother off if the same sound plays twice in a row too fast\nstd::vector<Sound> GESound::soundButtonHover1Pool;\nstd::vector<Sound> GESound::soundButtonHover2Pool;\nstd::vector<Sound> GESound::soundButtonHover3Pool;\nstd::vector<Sound> GESound::soundButtonEnablePool;\nstd::vector<Sound> GESound::soundButtonDisablePool;\n\nstd::vector<Sound> GESound::soundSliderSlidePool;\n\nvoid GESound::loadSounds() {\n\tInitAudioDevice();\n\n\tSetMasterVolume(globalVolume);\n\n\tintro = LoadSound(\"Sounds/MenuSounds/Intro.mp3\");\n\n    SetSoundVolume(intro, 0.7f);\n\n\tsoundButtonHover1 = LoadSound(\"Sounds/MenuSounds/buttonHover1.mp3\");\n\tsoundButtonHover2 = LoadSound(\"Sounds/MenuSounds/buttonHover2.mp3\");\n\tsoundButtonHover3 = LoadSound(\"Sounds/MenuSounds/buttonHover3.mp3\");\n\tsoundButtonEnable = LoadSound(\"Sounds/MenuSounds/buttonEnable.mp3\");\n\tsoundButtonDisable = LoadSound(\"Sounds/MenuSounds/buttonDisable.mp3\");\n\n    soundSliderSlide = LoadSound(\"Sounds/MenuSounds/sliderSlide.mp3\");\n\n\tfor (size_t i = 0; i < soundPoolSize; i++) {\n\t\tsoundButtonHover1Pool.push_back(LoadSoundAlias(soundButtonHover1));\n\t}\n\tfor (size_t i = 0; i < soundPoolSize; i++) {\n\t\tsoundButtonHover2Pool.push_back(LoadSoundAlias(soundButtonHover2));\n\t}\n\tfor (size_t i = 0; i < soundPoolSize; i++) {\n\t\tsoundButtonHover3Pool.push_back(LoadSoundAlias(soundButtonHover3));\n\t}\n\tfor (size_t i = 0; i < soundPoolSize; i++) {\n\t\tsoundButtonEnablePool.push_back(LoadSoundAlias(soundButtonEnable));\n\t}\n\tfor (size_t i = 0; i < soundPoolSize; i++) {\n\t\tsoundButtonDisablePool.push_back(LoadSoundAlias(soundButtonDisable));\n\t}\n\n    for (size_t i = 0; i < soundSliderSlidePoolSize; i++) {\n        soundSliderSlidePool.push_back(LoadSoundAlias(soundSliderSlide));\n    }\n\n\tPlaySound(intro);\n}\n\nvoid GESound::soundtrackLogic() {\n    SetMasterVolume(globalVolume);\n    SetMusicVolume(currentMusic, musicVolume * musicVolMultiplier);\n\n    for (Sound& s : soundButtonHover1Pool) SetSoundVolume(s, menuVolume);\n    for (Sound& s : soundButtonHover2Pool) SetSoundVolume(s, menuVolume);\n    for (Sound& s : soundButtonHover3Pool) SetSoundVolume(s, menuVolume);\n    for (Sound& s : soundButtonEnablePool) SetSoundVolume(s, menuVolume);\n    for (Sound& s : soundButtonDisablePool) SetSoundVolume(s, menuVolume);\n\n    for (Sound& s : soundSliderSlidePool) SetSoundVolume(s, menuVolume + 0.15f);\n\n    if (isFirstTimePlaying) {\n        currentMusic = LoadMusicStream(playlist[currentSongIndex].c_str());\n        if (currentMusic.ctxData != nullptr) {\n            PlayMusicStream(currentMusic);\n            musicPlaying = true;\n            isFirstTimePlaying = false;\n        }\n    }\n\n    if (hasTrackChanged) {\n        UnloadMusicStream(currentMusic);\n\n        if (currentSongIndex < 0) {\n            currentSongIndex = playlist.size() - 1;\n        }\n        else {\n            currentSongIndex %= playlist.size();\n        }\n\n        currentMusic = LoadMusicStream(playlist[currentSongIndex].c_str());\n        if (currentMusic.ctxData != nullptr) {\n            PlayMusicStream(currentMusic);\n        }\n        else {\n            musicPlaying = false;\n        }\n        hasTrackChanged = false;\n    }\n\n    else if (musicPlaying) {\n        UpdateMusicStream(currentMusic);\n\n        float timePlayed = GetMusicTimePlayed(currentMusic);\n        float timeLength = GetMusicTimeLength(currentMusic);\n\n        if (timePlayed >= timeLength - 0.1f) {\n            UnloadMusicStream(currentMusic);\n            currentSongIndex = (currentSongIndex + 1) % playlist.size();\n            currentMusic = LoadMusicStream(playlist[currentSongIndex].c_str());\n\n            if (currentMusic.ctxData != nullptr) {\n                PlayMusicStream(currentMusic);\n            }\n            else {\n                musicPlaying = false;\n            }\n        }\n    }\n}\n\nvoid GESound::unloadSounds() {\n\tUnloadSound(intro);\n\n\tUnloadSound(soundButtonHover1);\n\tUnloadSound(soundButtonHover2);\n\tUnloadSound(soundButtonHover3);\n\n\tUnloadSound(soundButtonEnable);\n\tUnloadSound(soundButtonDisable);\n\n\tUnloadMusicStream(currentMusic);\n\n\tCloseAudioDevice();\n}"
  },
  {
    "path": "GalaxyEngine/src/UI/UI.cpp",
    "content": "#include \"UI/UI.h\"\n\nvoid UI::uiLogic(UpdateParameters& myParam, UpdateVariables& myVar, SPH& sph, SaveSystem& save, GESound& geSound, \n\tLighting& lighting, Field& field, ParticleSpaceship& ship) {\n\n\tif (IO::shortcutPress(KEY_U)) {\n\t\tshowSettings = !showSettings;\n\t}\n\n\tif (myVar.timeFactor == 0.0f) {\n\t\tDrawRectangleV({ GetScreenWidth() - 700.0f, 20.0f }, { 10.0f, 30.0f }, WHITE);\n\t\tDrawRectangleV({ GetScreenWidth() - 720.0f, 20.0f }, { 10.0f, 30.0f }, WHITE);\n\t}\n\n\tif (ImGui::IsAnyItemHovered() || ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) {\n\t\tmyVar.isMouseNotHoveringUI = false;\n\t\tmyVar.isDragging = false;\n\t}\n\telse {\n\t\tmyVar.isMouseNotHoveringUI = true;\n\t}\n\n\tImGui::GetIO().IniFilename = nullptr;\n\n\tfloat screenX = static_cast<float>(GetScreenWidth());\n\tfloat screenY = static_cast<float>(GetScreenHeight());\n\n\tfloat buttonsWindowX = 220.0f;\n\tfloat buttonsWindowY = screenY - 30.0f;\n\n\tfloat settingsButtonX = 250.0f;\n\tfloat settingsButtonY = 25.0f;\n\n\tfloat parametersSliderX = 200.0f;\n\tfloat parametersSliderY = 30.0f;\n\n\tbool enabled = true;\n\n\tImGui::SetNextWindowSize(ImVec2(buttonsWindowX, buttonsWindowY), ImGuiCond_Once);\n\tImGui::SetNextWindowSizeConstraints(ImVec2(buttonsWindowX, buttonsWindowY), ImVec2(buttonsWindowX, buttonsWindowY));\n\tImGui::SetNextWindowPos(ImVec2(screenX - buttonsWindowX, 0.0f), ImGuiCond_Always);\n\tImGui::Begin(\"Settings\", nullptr, ImGuiWindowFlags_NoResize);\n\n\tfloat contentRegionWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x;\n\tfloat buttonX = (contentRegionWidth - settingsButtonX) * 0.5f;\n\n\tfloat oldSpacingY = ImGui::GetStyle().ItemSpacing.y;\n\tImGui::GetStyle().ItemSpacing.y = 5.0f; // Set the spacing only for the settings buttons\n\n\tstd::vector<SimilarTypeButton::Mode> controlsAndInfo{\n{ \"Controls\", \"Open controls panel\", &myParam.controls.isShowControlsEnabled },\n{ \"Information\", \"Open information panel\", &myParam.controls.isInformationEnabled }\n\t};\n\n\tstd::vector<SimilarTypeButton::Mode> trails{\n{ \"Global Trails\", \"Enables trails for all particles\", &myVar.isGlobalTrailsEnabled },\n{ \"Selected Trails\", \"Enables trails for selected particles\", &myVar.isSelectedTrailsEnabled }\n\t};\n\n\tstd::vector<SimilarTypeButton::Mode> size{\n{ \"Density Size\", \"Maps particle neighbor amount to size\", &myVar.isDensitySizeEnabled },\n{ \"Force Size\", \"Maps particle acceleration to size\", &myVar.isForceSizeEnabled }\n\t};\n\n\tstd::vector<SimilarTypeButton::Mode> gpuSimd{\n{ \"GPU (Beta)\", \"Simulates gravity on the GPU\", &myVar.isGPUEnabled },\n{ \"Naive\", \"Simulates gravity with a Naive algorithm. It is the most precise, but much slower\", &myVar.naive }\n\t};\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"General\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Fullscreen\", \"Toggles fulscreen\", myVar.fullscreenState, -1.0f, settingsButtonY, true, enabled);\n\n\tSimilarTypeButton::buttonIterator(controlsAndInfo, -1.0f, settingsButtonY, true, enabled);\n\n\tbuttonHelper(\"Multi-Threading\", \"Distributes the simulation across multiple threads\", myVar.isMultiThreadingEnabled, -1.0f, settingsButtonY, true, enabled);\n\n\tSimilarTypeButton::buttonIterator(gpuSimd, -1.0f, settingsButtonY, true, enabled);\n\n\tif (myVar.is3DMode) {\n\t\tmyVar.isGPUEnabled = false;\n\t}\n\n\tif (buttonHelper(\"3D Mode\", \"Enables 3D simulation\", myVar.is3DMode, -1.0f, settingsButtonY, true, enabled)) {\n\t\tmyParam.pParticles.clear();\n\t\tmyParam.rParticles.clear();\n\n\t\tmyParam.pParticlesSelected.clear();\n\t\tmyParam.rParticlesSelected.clear();\n\n\t\tmyParam.pParticles3D.clear();\n\t\tmyParam.rParticles3D.clear();\n\n\t\tmyParam.pParticlesSelected3D.clear();\n\t\tmyParam.rParticlesSelected3D.clear();\n\n\t\tmyParam.trails.segments.clear();\n\t\tmyParam.trails.segments3D.clear();\n\n\t}\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Exit\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Exit Galaxy Engine\", \"Are you sure you don't want to play a little more?\", myVar.exitGame, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Save/Load\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Save Scene\", \"Save current scene to disk\", save.saveFlag, -1.0f, settingsButtonY, true, enabled);\n\tbuttonHelper(\"Load Scene\", \"Load a scene from disk\", save.loadFlag, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Trails\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tSimilarTypeButton::buttonIterator(trails, -1.0f, settingsButtonY, true, enabled);\n\n\tbuttonHelper(\"Local Trails\", \"Enables trails moving relative to particles average position\", myVar.isLocalTrailsEnabled, -1.0f, settingsButtonY, true, enabled);\n\tbuttonHelper(\"White Trails\", \"Makes all trails white\", myParam.trails.whiteTrails, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Visuals\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbool* colorModesArray[] = {\n\t\t&myParam.colorVisuals.solidColor,\n\t\t&myParam.colorVisuals.densityColor,\n\t\t&myParam.colorVisuals.forceColor,\n\t\t&myParam.colorVisuals.velocityColor,\n\t\t&myParam.colorVisuals.shockwaveColor,\n\t\t&myParam.colorVisuals.turbulenceColor,\n\t\t&myParam.colorVisuals.pressureColor,\n\t\t&myParam.colorVisuals.temperatureColor,\n\t\t&myParam.colorVisuals.gasTempColor,\n\t\t&myParam.colorVisuals.SPHColor\n\t};\n\n\tconst char* colorModes[] = { \"Solid Color\", \"Density Color\", \"Force Color\", \"Velocity Color\", \"Shockwave Color\", \"Turbulence Color\", \"Pressure Color\", \"Temperature Color\",\n\t\"Temperature Gas Color\", \"Material Color\" };\n\n\tconst char* colorModeTips[] = {\n\t\t\"Particles will only use the primary color\",\n\t\t\"Maps particle neighbor amount to primary and secondary colors\",\n\t\t\"Maps particle acceleration to primary and secondary colors\",\n\t\t\"Maps particle velocity to color\",\n\t\t\"Maps particle acceleration to color\",\n\t\t\"Maps particle turbulence to primary and secondary colors\",\n\t\t\"Maps particle pressure to color\",\n\t\t\"Maps particle temperature to color\",\n\t\t\"Maps particle temperature to primary and secondary colors\",\n\t\t\"Uses materials colors\",\n\t};\n\tstatic int currentColorMode = 1;\n\n\tif (myVar.loadDropDownMenus) {\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(colorModesArray); i++) {\n\n\t\t\tif (*colorModesArray[i]) {\n\t\t\t\tcurrentColorMode = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tImGui::PushItemWidth(-FLT_MIN);\n\n\tif (ImGui::BeginCombo(\"##Color\", colorModes[currentColorMode])) {\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(colorModes); i++) {\n\n\t\t\tbool isSelected = (currentColorMode == i);\n\n\t\t\tif (ImGui::Selectable(colorModes[i], isSelected)) {\n\t\t\t\tcurrentColorMode = i;\n\t\t\t}\n\n\t\t\tif (isSelected) {\n\t\t\t\tImGui::SetItemDefaultFocus();\n\t\t\t}\n\n\t\t\tif (ImGui::IsItemHovered()) {\n\t\t\t\tImGui::BeginTooltip();\n\t\t\t\tImGui::TextUnformatted(colorModeTips[i]);\n\t\t\t\tImGui::EndTooltip();\n\t\t\t}\n\n\n\t\t}\n\t\tImGui::EndCombo();\n\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(colorModesArray); ++i) {\n\t\t\t*colorModesArray[i] = false;\n\t\t}\n\n\t\t*colorModesArray[currentColorMode] = true;\n\t}\n\n\tImGui::Spacing();\n\n\tSimilarTypeButton::buttonIterator(size, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::PopItemWidth();\n\n\tbuttonHelper(\"Flat 3D Particles\", \"Toggles how particles are displayed in 3D mode\", myVar.flatParticleTexture3D, -1.0f, settingsButtonY, true, myVar.is3DMode);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Simulation\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tstatic bool galaxyModeDummy = false;\n\n\tbool* simModesArray[] = {\n\t\t&galaxyModeDummy,\n\t\t&myVar.isSPHEnabled,\n\t\t&myVar.isMergerEnabled\n\t};\n\n\tconst char* simModes[] = { \"Galaxy Mode\", \"Fluid Mode\", \"Merger\" };\n\n\tconst char* simModeTips[] = {\n\t\t\"Default simulation mode. Used for very large scale objects like galaxies or the Big Bang\",\n\t\t\"Enables SPH fluids. Used for planets or small scale simulations\",\n\t\t\"Colliding particles will merge together\"\n\t};\n\tstatic int currentSimMode = 0;\n\n\tbool foundSimMode = false;\n\tif (myVar.loadDropDownMenus) {\n\t\tbool anyEnabled = false;\n\n\t\tfor (int i = 1; i < IM_ARRAYSIZE(simModesArray); i++) {\n\t\t\tif (*simModesArray[i]) {\n\t\t\t\tcurrentSimMode = i;\n\t\t\t\tanyEnabled = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tgalaxyModeDummy = !anyEnabled;\n\t\tif (!anyEnabled) {\n\t\t\tcurrentSimMode = 0;\n\t\t}\n\t}\n\n\tImGui::PushItemWidth(-FLT_MIN);\n\n\tbool wasSPHEnabled = myVar.isSPHEnabled;\n\tbool wasMergerEnabled = myVar.isMergerEnabled;\n\n\tif (ImGui::BeginCombo(\"##Simulation\", simModes[currentSimMode])) {\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(simModes); i++) {\n\n\t\t\tbool isSelected = (currentSimMode == i);\n\n\t\t\tif (ImGui::Selectable(simModes[i], isSelected)) {\n\t\t\t\tcurrentSimMode = i;\n\t\t\t}\n\n\t\t\tif (isSelected) {\n\t\t\t\tImGui::SetItemDefaultFocus();\n\t\t\t}\n\n\t\t\tif (ImGui::IsItemHovered()) {\n\t\t\t\tImGui::BeginTooltip();\n\t\t\t\tImGui::TextUnformatted(simModeTips[i]);\n\t\t\t\tImGui::EndTooltip();\n\t\t\t}\n\n\n\t\t}\n\t\tImGui::EndCombo();\n\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(simModesArray); ++i) {\n\t\t\t*simModesArray[i] = false;\n\t\t}\n\n\t\t*simModesArray[currentSimMode] = true;\n\n\t\tbool anyModeActive = false;\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(simModesArray); ++i) {\n\t\t\tif (*simModesArray[i]) {\n\t\t\t\tanyModeActive = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!anyModeActive) {\n\t\t\tgalaxyModeDummy = true;\n\t\t\tcurrentSimMode = 0;\n\t\t}\n\n\t\tif (!wasSPHEnabled && myVar.isSPHEnabled) {\n\t\t\tfor (size_t i = 0; i < IM_ARRAYSIZE(colorModesArray); i++) {\n\t\t\t\t*colorModesArray[i] = (colorModesArray[i] == &myParam.colorVisuals.SPHColor);\n\t\t\t\tif (colorModesArray[i] == &myParam.colorVisuals.SPHColor) {\n\t\t\t\t\tcurrentColorMode = i;\n\n\t\t\t\t\tmyVar.SPHWater = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!wasMergerEnabled && myVar.isMergerEnabled) {\n\t\t\tfor (size_t i = 0; i < IM_ARRAYSIZE(colorModesArray); i++) {\n\t\t\t\t*colorModesArray[i] = (colorModesArray[i] == &myParam.colorVisuals.solidColor);\n\t\t\t\tif (colorModesArray[i] == &myParam.colorVisuals.solidColor) {\n\t\t\t\t\tcurrentColorMode = i;\n\n\t\t\t\t\tmyParam.colorVisuals.pColor = { 255,255,255,255 };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfoundSimMode = false;\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(simModesArray); i++) {\n\t\t\tif (*simModesArray[i]) {\n\t\t\t\tcurrentSimMode = i;\n\t\t\t\tfoundSimMode = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!foundSimMode) {\n\t\t\tgalaxyModeDummy = true;\n\t\t\tcurrentSimMode = 0;\n\t\t}\n\t}\n\n\tImGui::PopItemWidth();\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Fluid Mode Material\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbool* materialsArray[] = {\n\t\t&myVar.SPHWater,\n\t\t&myVar.SPHRock,\n\t\t&myVar.SPHIron,\n\t\t&myVar.SPHSand,\n\t\t&myVar.SPHSoil,\n\t\t&myVar.SPHIce,\n\t\t&myVar.SPHMud,\n\t\t&myVar.SPHRubber,\n\t\t&myVar.SPHGas\n\t};\n\n\tconst char* materials[] = { \"Water\", \"Rock\", \"Iron\", \"Sand\", \"Soil\", \"Ice\", \"Mud\", \"Rubber\", \"Gas\" };\n\n\tstatic int currentMat = 0;\n\n\tif (myVar.loadDropDownMenus) {\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(materialsArray); i++) {\n\n\t\t\tif (*materialsArray[i]) {\n\t\t\t\tcurrentMat = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tImGui::PushItemWidth(-FLT_MIN);\n\n\tImGui::BeginDisabled(!myVar.isSPHEnabled);\n\n\tif (ImGui::BeginCombo(\"##Materials\", materials[currentMat])) {\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(materials); i++) {\n\n\t\t\tbool isSelected = (currentMat == i);\n\n\t\t\tif (ImGui::Selectable(materials[i], isSelected)) {\n\t\t\t\tcurrentMat = i;\n\t\t\t}\n\n\t\t\tif (isSelected) {\n\t\t\t\tImGui::SetItemDefaultFocus();\n\t\t\t}\n\t\t}\n\t\tImGui::EndCombo();\n\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(materialsArray); ++i) {\n\t\t\t*materialsArray[i] = false;\n\t\t}\n\n\t\t*materialsArray[currentMat] = true;\n\t}\n\n\n\tImGui::PopItemWidth();\n\n\tImGui::EndDisabled();\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Dark Matter\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Dark Matter\", \"Enables dark matter particles. This works for galaxies and Big Bang\", myVar.isDarkMatterEnabled, -1.0f, settingsButtonY, true, enabled);\n\tbuttonHelper(\"Show Dark Matter\", \"Unhides dark matter particles\", myParam.colorVisuals.showDarkMatterEnabled, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Space Modifiers\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Fluid Ground Mode\", \"Adds vertical gravity and makes particles collide with the domain walls\", myVar.sphGround, -1.0f, settingsButtonY, true, myVar.isSPHEnabled);\n\tbuttonHelper(\"Looping Space\", \"Particles disappearing on one side will appear on the other side\", myVar.isPeriodicBoundaryEnabled, -1.0f, settingsButtonY, true, enabled);\n\tbuttonHelper(\"Infinite Domain\", \"Enables or disables the domain boundaries that contain the simulation\", myVar.infiniteDomain, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Temperature\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Temperature Simulation\", \"Enables temperature simulation\", myVar.isTempEnabled, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Constraints\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Particle Constraints\", \"Enables particles constraints for solids and soft bodies simulation. Works best with Fluid Mode enabled\", myVar.constraintsEnabled, -1.0f, settingsButtonY, true, enabled);\n\tbuttonHelper(\"Unbreakable Constraints\", \"Makes all constraints unbreakable\", myVar.unbreakableConstraints, -1.0f, settingsButtonY, true, myVar.constraintsEnabled);\n\tbuttonHelper(\"Constraint After Drawing\", \"Creates constraints in between particles right after drawing them\", myVar.constraintAfterDrawing, -1.0f, settingsButtonY, true, myVar.constraintsEnabled);\n\n\tif (buttonHelper(\"Visualize Constraints\", \"Draws all existing constraints\", myVar.drawConstraints, -1.0f, settingsButtonY, true, myVar.constraintsEnabled)) {\n\t\tmyVar.visualizeMesh = false;\n\t}\n\tif (buttonHelper(\"Visualize Mesh\", \"Draws a mesh that connect particles\", myVar.visualizeMesh, -1.0f, settingsButtonY, true, enabled)) {\n\t\tmyVar.drawConstraints = false;\n\t}\n\n\tbuttonHelper(\"Constraint Stress Color\", \"Maps the constraints stress to an RGB color\", myVar.constraintStressColor, -1.0f, settingsButtonY, true, myVar.drawConstraints);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Optics (2D Only)\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Optics\", \"Enables light simulation with ray tracing. Simulate light from the Optics tab\", myVar.isOpticsEnabled, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Fields (2D Only)\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbool isNot3DMode = !myVar.is3DMode;\n\n\tif (myVar.is3DMode) {\n\t\tmyVar.isGravityFieldEnabled = false;\n\t\tmyVar.gravityFieldDMParticles = false;\n\t}\n\n\tif (buttonHelper(\"Gravity Field\", \"Enables the gravity field visualization mode (IT IS RECOMMENDED TO USE SMALLER DOMAIN SIZES)\", myVar.isGravityFieldEnabled, -1.0f, settingsButtonY, true, isNot3DMode)) {\n\t\tfield.computeField = true;\n\t}\n\tbuttonHelper(\"DM Particles\", \"Enables ignores Dark Matter particles for the gravity field\", myVar.gravityFieldDMParticles, -1.0f, settingsButtonY, true, isNot3DMode);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\n\tImGui::Spacing();\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Misc.\");\n\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\tbuttonHelper(\"Highlight Selected\", \"Highlight selected particles\", myParam.colorVisuals.selectedColor, -1.0f, settingsButtonY, true, enabled);\n\tbuttonHelper(\"Predict Path\", \"Predicts the trajectory of black holes before launching them\", myVar.enablePathPrediction, -1.0f, settingsButtonY, true, enabled);\n\n\tImGui::GetStyle().ItemSpacing.y = oldSpacingY; // End the settings buttons spacing\n\n\tImGui::End();\n\n\t// Start of settings sliders\n\n\tfloat parametersWindowSizeX = 400.0f;\n\tfloat parametersWindowSizeY = screenY - 30.0f;\n\n\tImGui::SetNextWindowSize(ImVec2(parametersWindowSizeX, parametersWindowSizeY), ImGuiCond_Once);\n\tImGui::SetNextWindowSizeConstraints(ImVec2(parametersWindowSizeX, parametersWindowSizeY), ImVec2(parametersWindowSizeX, parametersWindowSizeY));\n\tImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiCond_Always);\n\tImGui::Begin(\"Parameters\", nullptr, ImGuiWindowFlags_NoResize);\n\tfloat totalWidth = ImGui::GetContentRegionAvail().x;\n\tfloat halfButtonWidth = totalWidth * 0.4f;\n\n\tif (ImGui::BeginTabBar(\"##MainTabBar\", ImGuiTabBarFlags_NoTabListScrollingButtons)) {\n\n\t\tif (ImGui::BeginTabItem(\"Visuals\")) {\n\n\t\t\tbVisualsSliders = true;\n\t\t\tbPhysicsSliders = false;\n\t\t\tbStatsWindow = false;\n\t\t\tbRecordingSettings = false;\n\t\t\tbSoundWindow = false;\n\t\t\tbLightingWindow = false;\n\n\t\t\t// Initialize all tabs for sliders defaults\n\t\t\tif (loadSettings) {\n\t\t\t\tbVisualsSliders = true;\n\t\t\t\tbPhysicsSliders = true;\n\t\t\t\tbStatsWindow = true;\n\t\t\t\tbRecordingSettings = true;\n\t\t\t\tbSoundWindow = true;\n\t\t\t\tbLightingWindow = true;\n\n\t\t\t\tloadSettings = false;\n\t\t\t}\n\n\t\t\tImGui::EndTabItem();\n\t\t}\n\n\t\tif (ImGui::BeginTabItem(\"Physics\")) {\n\n\t\t\tbVisualsSliders = false;\n\t\t\tbPhysicsSliders = true;\n\t\t\tbStatsWindow = false;\n\t\t\tbRecordingSettings = false;\n\t\t\tbSoundWindow = false;\n\t\t\tbLightingWindow = false;\n\n\t\t\tImGui::EndTabItem();\n\t\t}\n\n\t\tif (ImGui::BeginTabItem(\"Advanced Stats\")) {\n\n\t\t\tbVisualsSliders = false;\n\t\t\tbPhysicsSliders = false;\n\t\t\tbStatsWindow = true;\n\t\t\tbRecordingSettings = false;\n\t\t\tbSoundWindow = false;\n\t\t\tbLightingWindow = false;\n\n\t\t\tImGui::EndTabItem();\n\t\t}\n\n\t\tif (ImGui::BeginTabItem(\"Optics\")) {\n\n\t\t\tbVisualsSliders = false;\n\t\t\tbPhysicsSliders = false;\n\t\t\tbStatsWindow = false;\n\t\t\tbRecordingSettings = false;\n\t\t\tbSoundWindow = false;\n\t\t\tbLightingWindow = true;\n\n\t\t\tImGui::EndTabItem();\n\t\t}\n\n\t\tif (ImGui::BeginTabItem(\"Sound\")) {\n\n\t\t\tbVisualsSliders = false;\n\t\t\tbPhysicsSliders = false;\n\t\t\tbStatsWindow = false;\n\t\t\tbRecordingSettings = false;\n\t\t\tbSoundWindow = true;\n\t\t\tbLightingWindow = false;\n\n\t\t\tImGui::EndTabItem();\n\t\t}\n\n\t\tif (ImGui::BeginTabItem(\"Recording\")) {\n\n\t\t\tbVisualsSliders = false;\n\t\t\tbPhysicsSliders = false;\n\t\t\tbStatsWindow = false;\n\t\t\tbRecordingSettings = true;\n\t\t\tbSoundWindow = false;\n\t\t\tbLightingWindow = false;\n\n\t\t\tImGui::EndTabItem();\n\t\t}\n\n\t\tImGui::EndTabBar();\n\t}\n\n\tImGui::BeginChild(\"##ContentRegion\", ImVec2(0, 0), true); {\n\n\t\tif (bVisualsSliders) {\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Shader\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tbuttonHelper(\"Glow\", \"Enables glow shader\", myVar.isGlowEnabled, -1.0f, settingsButtonY, true, enabled);\n\n\t\t\tsliderHelper(\"Glow Size\", \"Controls glow size\", myVar.glowSize, 3, 48, parametersSliderX, parametersSliderY, enabled);\n\t\t\tsliderHelper(\"Glow Strength\", \"Controls glow strength\", myVar.glowStrength, 0.1f, 5.0f, parametersSliderX, parametersSliderY, enabled);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Colors\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tColor primaryColors = {\n\t\t\t\tstatic_cast<unsigned char>(myParam.colorVisuals.pColor.r),\n\t\t\t\tstatic_cast<unsigned char>(myParam.colorVisuals.pColor.g),\n\t\t\t\tstatic_cast<unsigned char>(myParam.colorVisuals.pColor.b),\n\t\t\t\tstatic_cast<unsigned char>(myParam.colorVisuals.pColor.a) };\n\n\t\t\tImVec4 imguiPColor = rlImGuiColors::Convert(primaryColors);\n\t\t\tstatic Color originalPColor = primaryColors;\n\n\t\t\tbool placeholderP = false;\n\n\t\t\tif (buttonHelper(\"Reset Primary Color\", \"Resets the secondary color picker\", placeholderP, 240.0f, 30.0f, true, enabled)) {\n\t\t\t\tmyParam.colorVisuals.pColor.r = originalPColor.r;\n\t\t\t\tmyParam.colorVisuals.pColor.g = originalPColor.g;\n\t\t\t\tmyParam.colorVisuals.pColor.b = originalPColor.b;\n\t\t\t\tmyParam.colorVisuals.pColor.a = originalPColor.a;\n\t\t\t}\n\n\t\t\tif (ImGui::ColorPicker4(\"Primary Color\", (float*)&imguiPColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\t\tprimaryColors = rlImGuiColors::Convert(imguiPColor);\n\t\t\t\tmyParam.colorVisuals.pColor.r = primaryColors.r;\n\t\t\t\tmyParam.colorVisuals.pColor.g = primaryColors.g;\n\t\t\t\tmyParam.colorVisuals.pColor.b = primaryColors.b;\n\t\t\t\tmyParam.colorVisuals.pColor.a = primaryColors.a;\n\t\t\t}\n\n\t\t\tColor secondaryColors = {\n\t\t\t\tstatic_cast<unsigned char>(myParam.colorVisuals.sColor.r),\n\t\t\t\tstatic_cast<unsigned char>(myParam.colorVisuals.sColor.g),\n\t\t\t\tstatic_cast<unsigned char>(myParam.colorVisuals.sColor.b),\n\t\t\t\tstatic_cast<unsigned char>(myParam.colorVisuals.sColor.a) };\n\n\t\t\tImVec4 imguiSColor = rlImGuiColors::Convert(secondaryColors);\n\t\t\tstatic Color originalSColor = secondaryColors;\n\n\t\t\tbool placeholderS = false;\n\n\t\t\tif (buttonHelper(\"Reset Secondary Col.\", \"Resets the primary color picker\", placeholderS, 240.0f, 30.0f, true, enabled)) {\n\t\t\t\tmyParam.colorVisuals.sColor.r = originalSColor.r;\n\t\t\t\tmyParam.colorVisuals.sColor.g = originalSColor.g;\n\t\t\t\tmyParam.colorVisuals.sColor.b = originalSColor.b;\n\t\t\t\tmyParam.colorVisuals.sColor.a = originalSColor.a;\n\t\t\t}\n\n\t\t\tif (ImGui::ColorPicker4(\"Secondary Col.\", (float*)&imguiSColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\t\tsecondaryColors = rlImGuiColors::Convert(imguiSColor);\n\t\t\t\tmyParam.colorVisuals.sColor.r = secondaryColors.r;\n\t\t\t\tmyParam.colorVisuals.sColor.g = secondaryColors.g;\n\t\t\t\tmyParam.colorVisuals.sColor.b = secondaryColors.b;\n\t\t\t\tmyParam.colorVisuals.sColor.a = secondaryColors.a;\n\t\t\t}\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Camera\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tbuttonHelper(\"First Person Camera\", \"Enables first person mode. Use the arrow buttons to move\", myVar.firstPerson, 240.0f, 30.0f, true, enabled);\n\n\t\t\tsliderHelper(\"Camera Arrows Speed\", \"Controls the speed of the camera when moving with arrows\", myParam.myCamera3D.arrowMoveSpeed, 0.001f, 5000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Particle Clipping\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tbuttonHelper(\"Clip Selected X\", \"Hides half of the selected particles on the X axis\", myVar.clipSelectedX, 240.0f, 30.0f, true, enabled);\n\t\t\tImGui::SameLine();\n\t\t\tbuttonHelper(\"Invert X\", \"Inverts X half\", myVar.clipSelectedXInv, 75.0f, 30.0f, true, enabled);\n\n\t\t\tbuttonHelper(\"Clip Selected Y\", \"Hides half of the selected particles on the Y axis\", myVar.clipSelectedY, 240.0f, 30.0f, true, enabled);\n\t\t\tImGui::SameLine();\n\t\t\tbuttonHelper(\"Invert Y\", \"Inverts Y half\", myVar.clipSelectedYInv, 75.0f, 30.0f, true, enabled);\n\n\t\t\tbuttonHelper(\"Clip Selected Z\", \"Hides half of the selected particles on the Z axis\", myVar.clipSelectedZ, 240.0f, 30.0f, true, enabled);\n\t\t\tImGui::SameLine();\n\t\t\tbuttonHelper(\"Invert Z\", \"Inverts Z half\", myVar.clipSelectedZInv, 75.0f, 30.0f, true, enabled);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Neighbor Search\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Max Neighbors\", \"Controls the maximum neighbor count range\", myParam.colorVisuals.maxNeighbors, 1, 2000, parametersSliderX, parametersSliderY, enabled);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Color Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Max Force Color\", \"Controls the acceleration threshold to use the secondary color\", myParam.colorVisuals.maxColorAcc, 1.0f, 400.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Max Velocity Color\", \"Controls the max velocity used to map the colors in the velocity color mode\", myParam.colorVisuals.maxVel, 10.0f, 10000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Max Shockwave Accel\", \"Controls the acceleration threshold to map the particle color in Shockwave color mode\", myParam.colorVisuals.ShockwaveMaxAcc, 1.0f, 120.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tImGui::Separator();\n\t\t\tsliderHelper(\"Max Turbulence Color\", \"Controls the turbulence threshold to use the secondary color\", myParam.colorVisuals.maxColorTurbulence, 1.0f, 512.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Turbulence Fade Rate\", \"Controls how fast turbulence fades away\", myParam.colorVisuals.turbulenceFadeRate, 0.00f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Turbulence Contrast\", \"Controls how much contrast turbulence color has\", myParam.colorVisuals.turbulenceContrast, 0.1f, 4.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tbuttonHelper(\"Turbulence Custom Colors\", \"Enables the use of primary and secondary colors for turbulence\", myParam.colorVisuals.turbulenceCustomCol, 212.0f, 24.0f, true, enabled);\n\t\t\tImGui::Separator();\n\t\t\tsliderHelper(\"Max Pressure Color\", \"Controls the max pressure used to map the colors in the pressure color mode\", myParam.colorVisuals.maxPress, 100.0f, 100000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Max Temperature Color\", \"Controls the max temperature used to map the colors in the temperature color mode\", myParam.colorVisuals.tempColorMaxTemp, 10.0f, 50000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Max Constraint Stress\", \"Controls the max constraint stress used to map the colors in the constraints stress color mode. If set to 0, it will set the max stress to the material's breaking limit\", myVar.constraintMaxStressColor, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Size Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Max Dynamic Size\", \"Controls the maximum size particles can have when chaning size dynamically\", myParam.densitySize.maxSize, 0.1f, 5.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Min Dynamic Size\", \"Controls the minimum size particles can have when chaning size dynamically\", myParam.densitySize.minSize, 0.001f, 5.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Max Size Force\", \"Controls the acceleration threshold to map the particle size\", myParam.densitySize.sizeAcc, 1.0f, 400.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Max Size Neighbors\", \"Controls the neighbors threshold to map the particle size\", myParam.densitySize.maxNeighbors, 1, 1000, parametersSliderX, parametersSliderY, enabled);\n\t\t\tsliderHelper(\"Particles Size\", \"Controls the size of all particles\", myVar.particleSizeMultiplier, 0.0f, 5.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Trails Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Trails Length\", \"Controls how long should the trails be. This feature is computationally expensive\", myVar.trailMaxLength, 0, 1500, parametersSliderX, parametersSliderY, enabled);\n\t\t\tsliderHelper(\"Trails Thickness\", \"Controls the trails thickness\", myParam.trails.trailThickness, 0.01f, 1.5f, parametersSliderX, parametersSliderY, enabled);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Field Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Field Res\", \"Controls how much gravity affects the field colors\", field.res, 50, 1000, parametersSliderX, parametersSliderY, enabled);\n\t\t\tsliderHelper(\"Gravity Display Threshold\", \"Controls how much gravity affects the field colors\", field.gravityDisplayThreshold, 10.0f, 3000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Gravity Display Softness\", \"Controls how soft the gravity display looks\", field.gravityDisplaySoftness, 0.4f, 8.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Gravity Display Stretch\", \"Controls how contrasty the gravity display looks\", field.gravityStretchFactor, 1.0f, 10000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tbuttonHelper(\"Gravity Custom Colors\", \"Enables the use of primary and secondary colors for the gravity field\", field.gravityCustomColors, 212.0f, 24.0f, true, enabled);\n\t\t\tsliderHelper(\"Gravity Custom Color Exp.\", \"Controls the exposure of the custom color mode for the gravity field\", field.gravityExposure, 0.001f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Misc. Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Path Prediction Length\", \"Controls how long is the predicted path\", myVar.predictPathLength, 100, 2000, parametersSliderX, parametersSliderY, enabled);\n\n\t\t\tbool isSPHDisabled = !myVar.isSPHEnabled;\n\n\t\t\tstatic bool prevSPHState = false;\n\t\t\tstatic bool prevMassMultiplierEnabled = false;\n\t\t\tstatic float prevMassScatter = 0.0f;\n\n\t\t\tif (myVar.isSPHEnabled != prevSPHState) {\n\t\t\t\tif (myVar.isSPHEnabled) {\n\t\t\t\t\tprevMassMultiplierEnabled = myParam.particlesSpawning.massMultiplierEnabled;\n\t\t\t\t\tprevMassScatter = myVar.massScatter;\n\n\t\t\t\t\tmyParam.particlesSpawning.massMultiplierEnabled = false;\n\t\t\t\t\tmyVar.massScatter = 0.0f;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tmyParam.particlesSpawning.massMultiplierEnabled = prevMassMultiplierEnabled;\n\t\t\t\t\tmyVar.massScatter = prevMassScatter;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprevSPHState = myVar.isSPHEnabled;\n\n\t\t\tImGui::Spacing();\n\t\t}\n\n\t\tif (bPhysicsSliders) {\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"System Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Threads Amount\", \"Controls the amount of threads used by the simulation. Half your total amount of threads is usually the sweet spot\", myVar.threadsAmount, 1, 32, parametersSliderX, parametersSliderY, enabled);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Simulation Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Theta\", \"Controls the quality of the gravity calculation. Higher means lower quality\", myVar.theta, 0.1f, 5.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\tif (!myVar.is3DMode) {\n\t\t\t\tsliderHelper(\"Domain Width\", \"Controls the width of the global container\", myVar.domainSize.x, 200.0f, 3840.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\t\tsliderHelper(\"Domain Height\", \"Controls the height of the global container\", myVar.domainSize.y, 200.0f, 2160.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsliderHelper(\"Domain Width\", \"Controls the width of the global container\", myVar.domainSize3D.x, 200.0f, 3840.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\t\tsliderHelper(\"Domain Height\", \"Controls the height of the global container\", myVar.domainSize3D.y, 200.0f, 2160.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\t\tsliderHelper(\"Domain Depth\", \"Controls the depth of the global container\", myVar.domainSize3D.z, 200.0f, 2160.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\t}\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \" General Physics Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Time Scale\", \"Controls how fast time passes\", myVar.timeStepMultiplier, 0.0f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Softening\", \"Controls the smoothness of the gravity forces\", myVar.softening, 0.5f, 30.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Gravity Strength\", \"Controls how much particles attract eachother\", myVar.gravityMultiplier, 0.0f, 100.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Temperature Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Ambient Temperature\", \"Controls the desired temperature of the scene in Kelvin. 1 is near absolute zero. The default value is set just high enough to allow liquid water\", myVar.ambientTemp, 1.0f, 2500.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Ambient Heat Rate\", \"Controls how fast particles' temperature try to match ambient temperature\", myVar.globalAmbientHeatRate, 0.0f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Heat Conductivity Multiplier\", \"Controls the global heat conductivity of particles\", myVar.globalHeatConductivity, 0.001f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Constraints Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Constraints Stiffness Multiplier\", \"Controls the global stiffness multiplier for constraints\", myVar.globalConstraintStiffnessMult, 0.001f, 3.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Constraints Resistance Multiplier\", \"Controls the global resistance multiplier for constraints\", myVar.globalConstraintResistance, 0.001f, 30.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Fluids Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Fluid Vertical Gravity\", \"Controls the vertical gravity strength in Fluid Ground Mode\", myVar.verticalGravity, 0.0f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Fluid Mass Multiplier\", \"Controls the fluid mass of particles\", myVar.mass, 0.005f, 0.15f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Fluid Viscosity\", \"Controls how viscous particles are\", myVar.viscosity, 0.01f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Fluid Stiffness\", \"Controls how stiff particles are\", myVar.stiffMultiplier, 0.01f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Fluid Cohesion\", \"Controls how sticky particles are\", myVar.cohesionCoefficient, 0.0f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Fluid Delta\", \"Controls the scaling factor in the pressure solver to enforce fluid incompressibility\", myVar.delta, 500.0f, 20000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Fluid Max Velocity\", \"Controls the maximum velocity a particle can have in Fluid mode\", myVar.sphMaxVel, 0.0f, 2000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\tsliderHelper(\"Domain Friction\", \"Controls the friction of the domain walls\", myVar.boundaryFriction, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t}\n\n\t\tif (bSoundWindow) {\n\n\t\t\tbool enabled = true;\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"General Sound Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Global Volume\", \"Controls global sound volume\", geSound.globalVolume, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\tsliderHelper(\"Menu Volume\", \"Controls menu sounds volume\", geSound.menuVolume, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\tsliderHelper(\"Music Volume\", \"Controls soundtrack volume\", geSound.musicVolume, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Soundtrack Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tif (buttonHelper(\"<- Previous Track\", \"Plays the previous track in the playlist\", geSound.hasTrackChanged, -1.0f, settingsButtonY, true, enabled)) {\n\t\t\t\tgeSound.currentSongIndex--;\n\t\t\t}\n\n\t\t\tif (buttonHelper(\"Next Track ->\", \"Plays the next track in the playlist\", geSound.hasTrackChanged, -1.0f, settingsButtonY, true, enabled)) {\n\t\t\t\tgeSound.currentSongIndex++;\n\t\t\t}\n\t\t}\n\n\t\tif (bRecordingSettings) {\n\n\t\t\tfloat oldSpacingY = ImGui::GetStyle().ItemSpacing.y;\n\t\t\tImGui::GetStyle().ItemSpacing.y = 5.0f; // Set the spacing only for the recording settings buttons\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"General Recording Parameters\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tbuttonHelper(\"Pause After Recording\", \"Pauses the simulation after recording is finished\", myVar.pauseAfterRecording, -1.0f, settingsButtonY, true, enabled);\n\t\t\tbuttonHelper(\"Clean Scene After Recording\", \"Clears all particles from the scene after recording is finished\", myVar.cleanSceneAfterRecording, -1.0f, settingsButtonY, true, enabled);\n\n\t\t\tImGui::Separator();\n\n\t\t\tbool isEnabled = true;\n\t\t\tif (myVar.isRecording) {\n\t\t\t\tisEnabled = false;\n\t\t\t}\n\t\t\tsliderHelper(\"Recording Time Limit\", \"Set a time limit for the recording. 0 means no limit.\", myVar.recordingTimeLimit, 0.0f, 60.0f, parametersSliderX, parametersSliderY, isEnabled);\n\n\n\t\t\tImGui::GetStyle().ItemSpacing.y = oldSpacingY;\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Simulation Recording (3D Only)\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tbool enablePlaybackControls = false;\n\n\t\t\tbool isNotRecording = !myVar.playbackRecord;\n\n\t\t\tif (!myParam.playbackFrames.empty() && !myVar.playbackRecord) {\n\t\t\t\tenablePlaybackControls = true;\n\t\t\t}\n\n\t\t\tif (myVar.playbackRecord) {\n\t\t\t\tmyVar.runPlayback = false;\n\t\t\t}\n\n\t\t\tbuttonHelper(\"Store Playback On Memory\", \"Stores the playback on memory instead of disk\", myVar.playBackOnMemory, -1.0f, settingsButtonY, true, isNotRecording);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Keyframe Interval\", \"Sets the number of frames in between each stored frame\", myVar.keyframeTickInterval, 1, 30, parametersSliderX, parametersSliderY, enabled);\n\n\t\t\tstd::string buttonText;\n\t\t\tstd::string tooltipText;\n\n\t\t\tif (myVar.playbackRecord)\n\t\t\t{\n\t\t\t\tbuttonText = \"Stop Recording\";\n\t\t\t\ttooltipText = \"Stop simulation recording\";\n\t\t\t}\n\t\t\telse if (!myParam.playbackFrames.empty())\n\t\t\t{\n\t\t\t\tbuttonText = \"Resume Simulation\";\n\t\t\t\ttooltipText = \"Resume recording the simulation\";\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tbuttonText = \"Record Simulation\";\n\t\t\t\ttooltipText = \"Start recording simulation frames\";\n\t\t\t}\n\n\t\t\tif (buttonHelper(buttonText.c_str(), tooltipText.c_str(), myVar.playbackRecord, -1.0f, settingsButtonY, true, myVar.is3DMode)) {\n\t\t\t\t\n\t\t\t\tif (!myVar.playbackRecord) {\n\t\t\t\t\tmyVar.runPlayback = true;\n\n\t\t\t\t\tstd::swap(myParam.pParticles3D, myParam.pParticles3DPlaybackResume);\n\t\t\t\t\tstd::swap(myParam.rParticles3D, myParam.rParticles3DPlaybackResume);\n\t\t\t\t}\n\n\n\t\t\t\tif (!myParam.playbackFrames.empty() && myVar.playbackRecord) {\n\n\t\t\t\t\tstd::swap(myParam.pParticles3D, myParam.pParticles3DPlaybackResume);\n\t\t\t\t\tstd::swap(myParam.rParticles3D, myParam.rParticles3DPlaybackResume);\n\n\t\t\t\t\tmyParam.pParticles3DPlaybackResume.clear();\n\t\t\t\t\tmyParam.rParticles3DPlaybackResume.clear();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbuttonHelper(\"Run Playback\", \"Plays the recorded simulation\", myVar.runPlayback, -1.0f, settingsButtonY, true, enablePlaybackControls);\n\n\t\t\tsliderHelper(\"Playback speed\", \"Sets how fast the playback is played\", myVar.playbackSpeed, 0.0f, 20.0f, parametersSliderX, parametersSliderY, enablePlaybackControls, LogSlider);\n\n\t\t\tif (sliderHelper(\"Playback Timeline\", \"Scroll through the playback frames\", myVar.playbackProgress, 0.0f, static_cast<float>(myParam.playbackFrames.size() - 2), parametersSliderX, parametersSliderY, enablePlaybackControls)) {\n\t\t\t\tmyVar.runPlayback = false;\n\t\t\t}\n\n\t\t\tsliderHelper(\"Playback Particles Size\", \"Modifies the size of playback particles\", myVar.playbackParticlesSizeMult, 0.0f, 5.0f, parametersSliderX, parametersSliderY, enablePlaybackControls, LogSlider);\n\n\t\t\tif (buttonHelper(\"Delete Playback\", \"Deletes playback and resumes simulation\", myVar.deletePlayback, -1.0f, settingsButtonY, true, enablePlaybackControls) && !myVar.playbackRecord) {\n\t\t\t\tmyVar.runPlayback = false;\n\n\t\t\t\tmyParam.playbackFrames.clear();\n\n\t\t\t\tstd::swap(myParam.pParticles3D, myParam.pParticles3DPlaybackResume);\n\t\t\t\tstd::swap(myParam.rParticles3D, myParam.rParticles3DPlaybackResume);\n\n\t\t\t\tmyParam.pParticles3DPlaybackResume.clear();\n\t\t\t\tmyParam.rParticles3DPlaybackResume.clear();\n\n\t\t\t\tif (std::filesystem::exists(myVar.playbackPath)) {\n\t\t\t\t\tstd::filesystem::remove(myVar.playbackPath);\n\t\t\t\t}\n\n\t\t\t\tmyVar.deletePlayback = false;\n\t\t\t}\n\t\t\n\t\t\tsize_t totalBytes = 0;\n\t\t\tfor (const auto& frame : myParam.playbackFrames) totalBytes += frame.size() * sizeof(PlaybackParticle);\n\t\t\tdouble totalMB = totalBytes / (1024.0 * 1024.0);\n\n\t\t\tImGui::Text(\"Total Space: %.2f MB\", totalMB);\n\t\t}\n\n\t\tif (bStatsWindow) {\n\t\t\tstatsWindowLogic(myParam, myVar);\n\t\t}\n\n\t\tif (bLightingWindow) {\n\n\t\t\tbool enabled = true;\n\n\t\t\tImVec4 imguiLightColor = rlImGuiColors::Convert(lighting.lightColor);\n\n\t\t\tColor imguiLightColorRl;\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Color Settings\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tif (ImGui::ColorPicker3(\"Light Color\", (float*)&imguiLightColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\t\timguiLightColorRl = rlImGuiColors::Convert(imguiLightColor);\n\t\t\t\tlighting.lightColor.r = imguiLightColorRl.r;\n\t\t\t\tlighting.lightColor.g = imguiLightColorRl.g;\n\t\t\t\tlighting.lightColor.b = imguiLightColorRl.b;\n\t\t\t\tlighting.lightColor.a = imguiLightColorRl.a;\n\n\t\t\t\tlighting.isSliderLightColor = true;\n\t\t\t}\n\n\t\t\tImVec4 imguiWallBaseColor = rlImGuiColors::Convert(lighting.wallBaseColor);\n\n\t\t\tColor imguiWallBaseColorRl;\n\n\t\t\tif (ImGui::ColorPicker3(\"Wall Base Color\", (float*)&imguiWallBaseColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\t\timguiWallBaseColorRl = rlImGuiColors::Convert(imguiWallBaseColor);\n\t\t\t\tlighting.wallBaseColor.r = imguiWallBaseColorRl.r;\n\t\t\t\tlighting.wallBaseColor.g = imguiWallBaseColorRl.g;\n\t\t\t\tlighting.wallBaseColor.b = imguiWallBaseColorRl.b;\n\t\t\t\tlighting.wallBaseColor.a = imguiWallBaseColorRl.a;\n\n\t\t\t\tlighting.isSliderBaseColor = true;\n\t\t\t}\n\n\t\t\tImVec4 imguiWallSpecularColor = rlImGuiColors::Convert(lighting.wallSpecularColor);\n\n\t\t\tColor imguiWallSpecularColorRl;\n\n\t\t\tif (ImGui::ColorPicker3(\"Wall Specular Color\", (float*)&imguiWallSpecularColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\t\timguiWallSpecularColorRl = rlImGuiColors::Convert(imguiWallSpecularColor);\n\t\t\t\tlighting.wallSpecularColor.r = imguiWallSpecularColorRl.r;\n\t\t\t\tlighting.wallSpecularColor.g = imguiWallSpecularColorRl.g;\n\t\t\t\tlighting.wallSpecularColor.b = imguiWallSpecularColorRl.b;\n\t\t\t\tlighting.wallSpecularColor.a = imguiWallSpecularColorRl.a;\n\n\t\t\t\tlighting.isSliderSpecularColor = true;\n\t\t\t}\n\n\t\t\tImVec4 imguiWallRefractionColor = rlImGuiColors::Convert(lighting.wallRefractionColor);\n\n\t\t\tColor imguiWallRefractionColorRl;\n\n\t\t\tif (ImGui::ColorPicker3(\"Wall Refraction Color\", (float*)&imguiWallRefractionColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\t\timguiWallRefractionColorRl = rlImGuiColors::Convert(imguiWallRefractionColor);\n\t\t\t\tlighting.wallRefractionColor.r = imguiWallRefractionColorRl.r;\n\t\t\t\tlighting.wallRefractionColor.g = imguiWallRefractionColorRl.g;\n\t\t\t\tlighting.wallRefractionColor.b = imguiWallRefractionColorRl.b;\n\t\t\t\tlighting.wallRefractionColor.a = imguiWallRefractionColorRl.a;\n\n\t\t\t\tlighting.isSliderRefractionCol = true;\n\t\t\t}\n\n\t\t\tImVec4 imguiWallEmissionColor = rlImGuiColors::Convert(lighting.wallEmissionColor);\n\n\t\t\tColor imguiWallEmissionColorRl;\n\n\t\t\tif (ImGui::ColorPicker3(\"Wall Emission Color\", (float*)&imguiWallEmissionColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\t\timguiWallEmissionColorRl = rlImGuiColors::Convert(imguiWallEmissionColor);\n\t\t\t\tlighting.wallEmissionColor.r = imguiWallEmissionColorRl.r;\n\t\t\t\tlighting.wallEmissionColor.g = imguiWallEmissionColorRl.g;\n\t\t\t\tlighting.wallEmissionColor.b = imguiWallEmissionColorRl.b;\n\t\t\t\tlighting.wallEmissionColor.a = imguiWallEmissionColorRl.a;\n\n\t\t\t\tlighting.isSliderEmissionCol = true;\n\t\t\t}\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Light Settings\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tif (sliderHelper(\"Light Gain\", \"Controls lights brightness\", lighting.lightGain, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider)) {\n\t\t\t\tlighting.isSliderLightGain = true;\n\t\t\t}\n\n\t\t\tif (sliderHelper(\"Light Spread\", \"Controls the spread of area and cone lights\", lighting.lightSpread, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.isSliderlightSpread = true;\n\t\t\t}\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Wall Material Settings\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tif (sliderHelper(\"Wall Specular Roughness\", \"Controls the specular reflections roughness of walls\", lighting.wallSpecularRoughness, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.isSliderSpecularRough = true;\n\t\t\t}\n\t\t\tif (sliderHelper(\"Wall Refraction Roughness\", \"Controls the refraction surface roughness of walls\", lighting.wallRefractionRoughness, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.isSliderRefractionRough = true;\n\t\t\t}\n\n\t\t\tif (sliderHelper(\"Wall Refraction Amount\", \"Controls how much light walls will refract\", lighting.wallRefractionAmount, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.isSliderRefractionAmount = true;;\n\t\t\t}\n\n\t\t\tif (sliderHelper(\"Wall IOR\", \"Controls the IOR of walls\", lighting.wallIOR, 0.0f, 100.0f, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.isSliderIor = true;\n\t\t\t}\n\n\t\t\tif (sliderHelper(\"Wall Dispersion\", \"Controls how much light gets dispersed after refracting from this wall\", lighting.wallDispersion, 0.0f, 0.2f, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.isSliderDispersion = true;\n\t\t\t}\n\n\t\t\tif (sliderHelper(\"Wall Emission Gain\", \"Controls how much light walls emit\", lighting.wallEmissionGain, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.isSliderEmissionGain = true;\n\t\t\t}\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Shape Settings\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Shape Relax Iter.\", \"Controls the iterations used to relax the shapes when drawing\", lighting.shapeRelaxIter, 0, 50, parametersSliderX, parametersSliderY, enabled);\n\t\t\tsliderHelper(\"Shape Relax Factor\", \"Controls how much the drawn shape should relax each iteration\", lighting.shapeRelaxFactor, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled);\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Render Settings\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tif (sliderHelper(\"Max Samples\", \"Controls the total amount of lighting iterations\", lighting.maxSamples, 1, 2048, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\t\t\tif (sliderHelper(\"Rays Per Sample\", \"Controls amount of rays emitted on each sample\", lighting.sampleRaysAmount, 1, 8192, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\t\t\tif (sliderHelper(\"Max Bounces\", \"Controls how many times rays can bounce\", lighting.maxBounces, 0, 16, parametersSliderX, parametersSliderY, enabled)) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\n\t\t\tif (buttonHelper(\"Global Illumination\", \"Enables global illumination\", lighting.isDiffuseEnabled, -1.0f, settingsButtonY, enabled, enabled)) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\t\t\tif (buttonHelper(\"Specular Reflections\", \"Enables specular reflections\", lighting.isSpecularEnabled, -1.0f, settingsButtonY, enabled, enabled)) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\t\t\tif (buttonHelper(\"Refraction\", \"Enables refraction\", lighting.isRefractionEnabled, -1.0f, settingsButtonY, enabled, enabled)) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\t\t\tif (buttonHelper(\"Dispersion\", \"Enables light dispersion with refraction\", lighting.isDispersionEnabled, -1.0f, settingsButtonY, enabled, enabled)) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\t\t\tif (buttonHelper(\"Emission\", \"Allows walls to emit light\", lighting.isEmissionEnabled, -1.0f, settingsButtonY, enabled, enabled)) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Misc. Settings\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tbuttonHelper(\"Symmetrical Lens\", \"Makes both sides of the next lens editable. Hold LCTRL to move both sides at the same time\", lighting.symmetricalLens, -1.0f, settingsButtonY, enabled, enabled);\n\n\t\t\tbuttonHelper(\"Show Normals\", \"Displays the direction a wall is pointing at, also know as the normal\", lighting.drawNormals, -1.0f, settingsButtonY, enabled, enabled);\n\n\t\t\tbuttonHelper(\"Relax Shape When Moved\", \"Relaxes shapes when moving their walls. This is affected too by the relax sliders\", lighting.relaxMove, -1.0f, settingsButtonY, enabled, enabled);\n\n\t\t}\n\t}\n\n\tImGui::EndChild();\n\n\tImGui::End();\n\n\tmyParam.rightClickSettings.rightClickMenu(myVar, myParam);\n\n\tmyParam.controls.showControls();\n\tmyParam.controls.showInfo(myVar.fullscreenState);\n\n\tImVec2 statsSize = { 250.0f, myVar.isOpticsEnabled ? 230.0f : 120.0f };\n\n\tif (lighting.selectedWalls > 0) {\n\t\tstatsSize.y += 25.0f;\n\t}\n\n\tif (lighting.selectedLights > 0) {\n\t\tstatsSize.y += 25.0f;\n\t}\n\n\tfloat statsPosX = screenX - statsSize.x - buttonsWindowX - 20.0f;\n\n\tImGui::SetNextWindowSize(statsSize, ImGuiCond_Always);\n\tImGui::SetNextWindowPos(ImVec2(screenX - statsSize.x - buttonsWindowX - 20.0f, 0.0f), ImGuiCond_Always);\n\n\tImGui::Begin(\"Stats\", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);\n\n\tImGui::PushFont(myVar.robotoMediumFont);\n\n\tImGui::SetWindowFontScale(1.5f);\n\n\tint particlesAmout = static_cast<int>(myParam.pParticles.size()) + static_cast<int>(myParam.pParticles3D.size());\n\tint selecParticlesAmout = static_cast<int>(myParam.pParticlesSelected.size()) + static_cast<int>(myParam.pParticlesSelected3D.size());\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"%s%d\", \"Total Particles: \", particlesAmout);\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"%s%d\", \"Selected Particles: \", selecParticlesAmout);\n\n\tif (GetFPS() >= 60) {\n\t\tImGui::TextColored(ImVec4(0.0f, 0.8f, 0.0f, 1.0f), \"%s%d\", \"FPS: \", GetFPS());\n\t}\n\telse if (GetFPS() < 60 && GetFPS() > 30) {\n\t\tImGui::TextColored(ImVec4(0.8f, 0.8f, 0.0f, 1.0f), \"%s%d\", \"FPS: \", GetFPS());\n\t}\n\telse {\n\t\tImGui::TextColored(ImVec4(0.8f, 0.0f, 0.0f, 1.0f), \"%s%d\", \"FPS: \", GetFPS());\n\t}\n\n\tif (myVar.isOpticsEnabled) {\n\n\t\tImGui::Spacing();\n\t\tImGui::Separator();\n\n\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"%s%d\", \"Total Walls: \", static_cast<int>(lighting.walls.size()));\n\n\t\tif (lighting.selectedWalls > 0) {\n\t\t\tImGui::TextColored(UpdateVariables::colButtonHover, \"%s%d\", \"Selected Walls: \", lighting.selectedWalls);\n\t\t}\n\n\n\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"%s%d\", \"Total Lights: \", lighting.totalLights);\n\n\t\tif (lighting.selectedLights > 0) {\n\t\t\tImGui::TextColored(UpdateVariables::colButtonHover, \"%s%d\", \"Selected Lights: \", lighting.selectedLights);\n\t\t}\n\n\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"%s%d\", \"Total Rays: \", lighting.accumulatedRays);\n\n\t\tImGui::Spacing();\n\n\t\tfloat samplesPorgress = static_cast<float>(lighting.currentSamples) / static_cast<float>(lighting.maxSamples);\n\n\t\tImGui::PushStyleColor(ImGuiCol_PlotHistogram, UpdateVariables::colButtonHover);\n\t\tfloat progress = samplesPorgress;\n\t\tImVec2 size = ImVec2(ImGui::GetContentRegionAvail().x, 22.0f);\n\t\tfloat radius = 8.0f;\n\n\t\tImVec2 pos = ImGui::GetCursorScreenPos();\n\t\tImDrawList* draw_list = ImGui::GetWindowDrawList();\n\n\t\tdraw_list->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y),\n\t\t\tImGui::GetColorU32(ImVec4(0.2f, 0.2f, 0.2f, 1.0f)), radius);\n\n\t\tdraw_list->AddRectFilled(pos, ImVec2(pos.x + size.x * progress, pos.y + size.y),\n\t\t\tImGui::GetColorU32(UpdateVariables::colButtonHover), radius);\n\n\t\tchar buffer[128];\n\t\tsnprintf(buffer, sizeof(buffer), \"Samples %d / %d\", lighting.currentSamples - 1, lighting.maxSamples);\n\n\t\tfloat fontScale = 0.85f;\n\t\tImFont* font = ImGui::GetFont();\n\t\tfloat fontSize = ImGui::GetFontSize() * fontScale;\n\n\t\tImVec2 text_size = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, buffer);\n\t\tImVec2 text_pos = ImVec2(\n\t\t\tpos.x + (size.x - text_size.x) * 0.5f,\n\t\t\tpos.y + (size.y - text_size.y) * 0.5f\n\t\t);\n\n\t\tdraw_list->AddText(\n\t\t\tfont,\n\t\t\tfontSize,\n\t\t\ttext_pos,\n\t\t\tImGui::GetColorU32(ImVec4(1, 1, 1, 1)),\n\t\t\tbuffer\n\t\t);\n\n\t\tImGui::Dummy(size);\n\t\tImGui::PopStyleColor();\n\t}\n\n\tImGui::PopFont();\n\n\tImGui::End();\n\n\t// Tools Menu //\n\n\tImVec2 toolsSize = { 250.0f, 370.0f };\n\tImGui::SetNextWindowSize(toolsSize, ImGuiCond_Once);\n\tImGui::SetNextWindowPos(ImVec2(parametersWindowSizeX + 20.0f, 0.0f), ImGuiCond_Once);\n\tImGui::Begin(\"Tools\", nullptr);\n\tImGui::BeginTabBar(\"##ToolsBar\", ImGuiTabBarFlags_NoTabListScrollingButtons);\n\n\tstruct ToolButton {\n\t\tconst char* label;\n\t\tconst char* tooltip;\n\t\tbool* flag;\n\t};\n\n\tauto activateExclusiveTool = [](ToolButton* group, int count, int activeIndex) {\n\t\tfor (int i = 0; i < count; ++i) {\n\t\t\t*group[i].flag = (i == activeIndex);\n\t\t}\n\t\t};\n\n\t// Particle tab\n\tif (ImGui::BeginTabItem(\"Particle\")) {\n\n\t\tToolButton particleTools[] = {\n\t\t\t{ \"Draw Particles\", \"Draw particles with the brush\", &myVar.toolDrawParticles },\n\t\t\t{ \"Black Hole\", \"Throw a black hole particle\", &myVar.toolSpawnHeavyParticle },\n\t\t\t{ \"Galaxy\", \"Spawn a large galaxy\", &myVar.toolSpawnGalaxy },\n\t\t\t{ \"Star\", \"Spawn a small star. This is not meant for fluid mode\", &myVar.toolSpawnStar },\n\t\t\t{ \"Big Bang\", \"Spawn the Big Bang\", &myVar.toolSpawnBigBang }\n\t\t};\n\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(particleTools); ++i) {\n\t\t\tif (buttonHelper(particleTools[i].label, particleTools[i].tooltip, *particleTools[i].flag, -1.0f, settingsButtonY, enabled, enabled)) {\n\t\t\t\tactivateExclusiveTool(particleTools, IM_ARRAYSIZE(particleTools), i);\n\n\t\t\t\tmyVar.toolErase = false;\n\t\t\t\tmyVar.toolRadialForce = false;\n\t\t\t\tmyVar.toolSpin = false;\n\t\t\t\tmyVar.toolMove = false;\n\t\t\t\tmyVar.toolRaiseTemp = false;\n\t\t\t\tmyVar.toolLowerTemp = false;\n\n\t\t\t\tmyVar.toolPointLight = false;\n\t\t\t\tmyVar.toolAreaLight = false;\n\t\t\t\tmyVar.toolConeLight = false;\n\t\t\t\tmyVar.toolCircle = false;\n\t\t\t\tmyVar.toolDrawShape = false;\n\t\t\t\tmyVar.toolLens = false;\n\t\t\t\tmyVar.toolWall = false;\n\t\t\t\tmyVar.toolMoveOptics = false;\n\t\t\t\tmyVar.toolEraseOptics = false;\n\t\t\t\tmyVar.toolSelectOptics = false;\n\n\t\t\t\tmyVar.longExposureFlag = false;\n\t\t\t}\n\t\t}\n\n\t\tImGui::EndTabItem();\n\n\t\tImGui::Spacing();\n\t\tImGui::Separator();\n\n\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"ParticleAmount\");\n\n\t\tImGui::Separator();\n\t\tImGui::Spacing();\n\n\t\tsliderHelper(\"Visible P. Amount Multiplier\", \"Controls the spawn amount of visible particles\", myVar.particleAmountMultiplier, 0.1f, 100.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\tsliderHelper(\"DM P. Amount Multiplier\", \"Controls the spawn amount of dark matter particles\", myVar.DMAmountMultiplier, 0.1f, 100.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\tbool isSPHDisabled = !myVar.isSPHEnabled;\n\n\t\tsliderHelper(\"Random Mass multiplier\", \"Controls how much mass can vary for each particle\", myVar.massScatter, 0.0f, 1.0f, parametersSliderX, parametersSliderY, isSPHDisabled);\n\t\tbuttonHelper(\"Mass Multiplier\", \"Decides if particles' masses should be inversely multiplied by the amount of particles multiplier\", myParam.particlesSpawning.massMultiplierEnabled, 240.0f, 30.0f, true, isSPHDisabled);\n\n\t\tif (myVar.toolSpawnGalaxy) {\n\t\t\tif(!myVar.is3DMode){\n\n\t\t\t\tImGui::Spacing();\n\t\t\t\tImGui::Separator();\n\n\t\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Disk\");\n\n\t\t\t\tImGui::Separator();\n\t\t\t\tImGui::Spacing();\n\n\t\t\t\tsliderHelper(\"Galaxy Outer Radius\", \"Controls the outer limit of the galaxy\", myParam.particlesSpawning.outerRadius, 10.0f, 500.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t\tsliderHelper(\"Galaxy Radius\", \"Controls the radius of the galaxy core\", myParam.particlesSpawning.scaleLength, 10.0f, 500.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\t\t\tImGui::Spacing();\n\t\t\t\tImGui::Separator();\n\n\t\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Dark Matter\");\n\n\t\t\t\tImGui::Separator();\n\t\t\t\tImGui::Spacing();\n\n\t\t\t\tsliderHelper(\"DM Halo Size\", \"Controls the size of the galaxy dark matter halo\", myParam.particlesSpawning.outerRadiusDM, 500.0f, 12000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t\tsliderHelper(\"DM Halo Core Size\", \"Controls the size of the galaxy dark matter halo core\", myParam.particlesSpawning.radiusCoreDM, 0.5f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tImGui::Spacing();\n\t\t\t\tImGui::Separator();\n\n\t\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Rotation\");\n\n\t\t\t\tImGui::Separator();\n\t\t\t\tImGui::Spacing();\n\t\t\t\t\n\t\t\t\tsliderHelper(\"Disk Rotation X\", \"Controls rotation of disk in the X axist\", myParam.particlesSpawning3D.diskAxisX, 0.0f, 180.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\t\tsliderHelper(\"Disk Rotation Y\", \"Controls rotation of disk in the Y axist\", myParam.particlesSpawning3D.diskAxisY, 0.0f, 180.0f, parametersSliderX, parametersSliderY, enabled);\n\t\t\t\t\n\t\t\t\tImGui::Spacing();\n\t\t\t\tImGui::Separator();\n\n\t\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Disk\");\n\n\t\t\t\tImGui::Separator();\n\t\t\t\tImGui::Spacing();\n\t\t\t\t\n\t\t\t\tsliderHelper(\"Galaxy Outer Radius\", \"Controls the outer limit of the galaxy\", myParam.particlesSpawning3D.outerRadius, 10.0f, 500.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t\tsliderHelper(\"Galaxy Core Radius\", \"Controls the radius of the galaxy core\", myParam.particlesSpawning3D.radiusCore, 0.1f, 700.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t\tsliderHelper(\"Galaxy Thickness\", \"Controls the thickness of the galaxy\", myParam.particlesSpawning3D.diskThickness, 0.05f, 12.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t\t\n\t\t\t\tImGui::Spacing();\n\t\t\t\tImGui::Separator();\n\n\t\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Bulge\");\n\n\t\t\t\tImGui::Separator();\n\t\t\t\tImGui::Spacing();\n\n\t\t\t\tsliderHelper(\"Galaxy Bulge Size\", \"Controls the size of the galaxy central bulge\", myParam.particlesSpawning3D.bulgeSize, 10.0f, 4000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t\tsliderHelper(\"Galaxy Bulge Thickness\", \"Controls the thickness of the galaxy central bulge\", myParam.particlesSpawning3D.bulgeThickness, 0.5f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t\t\n\t\t\t\tImGui::Spacing();\n\t\t\t\tImGui::Separator();\n\n\t\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Dark Matter\");\n\n\t\t\t\tImGui::Separator();\n\t\t\t\tImGui::Spacing();\n\n\t\t\t\tsliderHelper(\"DM Halo Size\", \"Controls the size of the galaxy dark matter halo\", myParam.particlesSpawning3D.outerRadiusDM, 500.0f, 12000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t\tsliderHelper(\"DM Halo Core Size\", \"Controls the size of the galaxy dark matter halo core\", myParam.particlesSpawning3D.radiusCoreDM, 0.5f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.toolSpawnHeavyParticle) {\n\n\t\t\tImGui::Spacing();\n\t\t\tImGui::Separator();\n\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Black Hole\");\n\n\t\t\tImGui::Separator();\n\t\t\tImGui::Spacing();\n\n\t\t\tsliderHelper(\"Black Hole Init Mass\", \"Controls the mass of black holes when spawned\", myVar.heavyParticleWeightMultiplier, 0.005f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\t}\n\t}\n\n\t// Brush tab\n\tif (ImGui::BeginTabItem(\"Brush\")) {\n\t\tToolButton brushTools[] = {\n\t\t\t{ \"Eraser Brush\", \"Erase particles with the brush\", &myVar.toolErase },\n\t\t\t{ \"Gravity Brush\", \"Push particles away. Hold LCTRL to invert.\", &myVar.toolRadialForce },\n\t\t\t{ \"Spin Brush\", \"Spins particles. Hold LCTRL to invert.\", &myVar.toolSpin },\n\t\t\t{ \"Grab Brush\", \"Grab particles inside the brush\", &myVar.toolMove },\n\t\t\t{ \"Heat Brush\", \"Heats the particles inside the brush\", &myVar.toolRaiseTemp },\n\t\t\t{ \"Cool Brush\", \"Cools the particles inside the brush\", &myVar.toolLowerTemp }\n\t\t};\n\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(brushTools); ++i) {\n\t\t\tif (buttonHelper(brushTools[i].label, brushTools[i].tooltip, *brushTools[i].flag, -1.0f, settingsButtonY, enabled, enabled)) {\n\t\t\t\tactivateExclusiveTool(brushTools, IM_ARRAYSIZE(brushTools), i);\n\n\t\t\t\tmyVar.toolDrawParticles = false;\n\t\t\t\tmyVar.toolSpawnHeavyParticle = false;\n\t\t\t\tmyVar.toolSpawnGalaxy = false;\n\t\t\t\tmyVar.toolSpawnStar = false;\n\t\t\t\tmyVar.toolSpawnBigBang = false;\n\n\t\t\t\tmyVar.toolPointLight = false;\n\t\t\t\tmyVar.toolAreaLight = false;\n\t\t\t\tmyVar.toolConeLight = false;\n\t\t\t\tmyVar.toolCircle = false;\n\t\t\t\tmyVar.toolDrawShape = false;\n\t\t\t\tmyVar.toolLens = false;\n\t\t\t\tmyVar.toolWall = false;\n\t\t\t\tmyVar.toolMoveOptics = false;\n\t\t\t\tmyVar.toolEraseOptics = false;\n\t\t\t\tmyVar.toolSelectOptics = false;\n\n\t\t\t\tmyVar.longExposureFlag = false;\n\t\t\t}\n\t\t}\n\n\t\tImGui::EndTabItem();\n\n\t\tsliderHelper(\"Gravity Brush Force\", \"Controls the force of the gravity brush\", myVar.brushAttractForceMult, 0.01f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t\tsliderHelper(\"Spin Brush Force\", \"Controls the force of the spin brush\", myVar.brushSpinForceMult, 0.01f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\t}\n\n\t// Optics tab\n\tif (ImGui::BeginTabItem(\"Optics\")) {\n\t\tToolButton opticTools[] = {\n\t\t\t{ \"Point Light\", \"Spawn point light\", &myVar.toolPointLight },\n\t\t\t{ \"Area Light\", \"Spawn area light\", &myVar.toolAreaLight },\n\t\t\t{ \"Cone Light\", \"Spawn cone light\", &myVar.toolConeLight },\n\t\t\t{ \"Wall\", \"Spawn a wall\", &myVar.toolWall },\n\t\t\t{ \"Circle\", \"Spawn a circle\", &myVar.toolCircle },\n\t\t\t{ \"Draw Shape\", \"Draw a shape\", &myVar.toolDrawShape },\n\t\t\t{ \"Lens\", \"Spawn a lens\", &myVar.toolLens },\n\t\t\t{ \"Move\", \"Move optics elements inside the brush\", &myVar.toolMoveOptics },\n\t\t\t{ \"Erase\", \"Erase optics elements like walls and lights\", &myVar.toolEraseOptics},\n\t\t\t{ \"Select\", \"Select optics elements like walls and lights to modify them. LCTRL adds to selection. LALT removes from selection. LSHIFT selects entire shapes.\", &myVar.toolSelectOptics}\n\t\t};\n\n\t\tfor (int i = 0; i < IM_ARRAYSIZE(opticTools); ++i) {\n\t\t\tif (buttonHelper(opticTools[i].label, opticTools[i].tooltip, *opticTools[i].flag, -1.0f, settingsButtonY, enabled, myVar.isOpticsEnabled)) {\n\t\t\t\tactivateExclusiveTool(opticTools, IM_ARRAYSIZE(opticTools), i);\n\n\t\t\t\tmyVar.toolDrawParticles = false;\n\t\t\t\tmyVar.toolSpawnHeavyParticle = false;\n\t\t\t\tmyVar.toolSpawnGalaxy = false;\n\t\t\t\tmyVar.toolSpawnStar = false;\n\t\t\t\tmyVar.toolSpawnBigBang = false;\n\n\t\t\t\tmyVar.toolErase = false;\n\t\t\t\tmyVar.toolRadialForce = false;\n\t\t\t\tmyVar.toolSpin = false;\n\t\t\t\tmyVar.toolMove = false;\n\t\t\t\tmyVar.toolRaiseTemp = false;\n\t\t\t\tmyVar.toolLowerTemp = false;\n\n\t\t\t\tmyVar.longExposureFlag = false;\n\t\t\t}\n\t\t}\n\n\t\tImGui::EndTabItem();\n\t}\n\n\t// Fun tools tab\n\tif (ImGui::BeginTabItem(\"Fun\")) {\n\n\t\t//ToolButton funTools[] = {\n\t\t//\t\n\t\t//};\n\n\t\t//for (int i = 0; i < IM_ARRAYSIZE(funTools); ++i) {\n\t\t//\tif (buttonHelper(funTools[i].label, funTools[i].tooltip, *funTools[i].flag, -1.0f, settingsButtonY, enabled, enabled)) {\n\t\t//\t\t//activateExclusiveTool(funTools, IM_ARRAYSIZE(funTools), i);\n\n\t\t//\t\tmyVar.toolDrawParticles = false;\n\t\t//\t\tmyVar.toolSpawnHeavyParticle = false;\n\t\t//\t\tmyVar.toolSpawnGalaxy = false;\n\t\t//\t\tmyVar.toolSpawnStar = false;\n\t\t//\t\tmyVar.toolSpawnBigBang = false;\n\n\t\t//\t\tmyVar.toolErase = false;\n\t\t//\t\tmyVar.toolRadialForce = false;\n\t\t//\t\tmyVar.toolSpin = false;\n\t\t//\t\tmyVar.toolMove = false;\n\t\t//\t\tmyVar.toolRaiseTemp = false;\n\t\t//\t\tmyVar.toolLowerTemp = false;\n\n\t\t//\t\tmyVar.toolPointLight = false;\n\t\t//\t\tmyVar.toolAreaLight = false;\n\t\t//\t\tmyVar.toolConeLight = false;\n\t\t//\t\tmyVar.toolCircle = false;\n\t\t//\t\tmyVar.toolDrawShape = false;\n\t\t//\t\tmyVar.toolLens = false;\n\t\t//\t\tmyVar.toolWall = false;\n\t\t//\t\tmyVar.toolMoveOptics = false;\n\t\t//\t\tmyVar.toolEraseOptics = false;\n\t\t//\t\tmyVar.toolSelectOptics = false;\n\t\t//\t}\n\t\t//}\n\n\t\tImGui::Spacing();\n\t\tImGui::Separator();\n\n\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Long Exposure\");\n\n\t\tImGui::Separator();\n\t\tImGui::Spacing();\n\n\t\tbuttonHelper(\"Long Exposure Duration\", \"Controls the duration of the long exposure shot\", myVar.longExposureFlag, -1.0f, settingsButtonY, enabled, enabled);\n\t\tsliderHelper(\"Long Exposure Duration\", \"Controls the duration of the long exposure shot\", myVar.longExposureDuration, 2, 1000, parametersSliderX, parametersSliderY, enabled);\n\n\t\tImGui::Spacing();\n\t\tImGui::Separator();\n\n\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \".PLY Export\");\n\n\t\tImGui::Separator();\n\t\tImGui::Spacing();\n\n\t\tbuttonHelper(\"Export .ply File\", \"Exports particles to a .ply file\", myVar.exportPlyFlag, -1.0f, settingsButtonY, true, enabled);\n\t\tbuttonHelper(\"Export .ply Seq.\", \"Exports particles to a .ply file each frame, creating a .ply sequence\", myVar.exportPlySeqFlag, -1.0f, settingsButtonY, true, enabled);\n\n\t\tif (myVar.plyFrameNumber != 0) {\n\t\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"%s%d\", \"Frames Exported: \", myVar.plyFrameNumber);\n\t\t}\n\n\t\tImGui::Spacing();\n\t\tImGui::Separator();\n\n\t\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Spaceship\");\n\n\t\tImGui::Separator();\n\t\tImGui::Spacing();\n\n\t\tbuttonHelper(\"Enable Spaceship\", \"Enables controlling particles\", ship.isShipEnabled, -1.0f, settingsButtonY, true, enabled);\n\t\tbuttonHelper(\"Ship Gas\", \"Enables gas particles coming from the ship when controlling particles\", myVar.isShipGasEnabled, -1.0f, settingsButtonY, true, enabled);\n\t\tsliderHelper(\"Spaceship Acceleration\", \"Controls the acceleration of the spaceship when controlling particles\",ship.acceleration, 1.0f, 16.0f, parametersSliderX, parametersSliderY, enabled, LogSlider);\n\n\t\tImGui::EndTabItem();\n\t}\n\n\tImGui::EndTabBar();\n\tImGui::End();\n\n\tmyVar.loadDropDownMenus = false;\n}\n\nvoid UI::statsWindowLogic(UpdateParameters& myParam, UpdateVariables& myVar) {\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\t//------ Performance ------//\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Performance\");\n\tImGui::Spacing();\n\n\tfloat enablePausedPlot = 1.0f;\n\n\tplotLinesHelper(enablePausedPlot, \"Framerate: \", graphHistoryLimit, ImGui::GetIO().Framerate, 0.0f, 144.0f, { 340.0f, 200.0f });\n\tImGui::Spacing();\n\tplotLinesHelper(enablePausedPlot, \"Frame Time: \", graphHistoryLimit, GetFrameTime(), 0.0f, 1.0f, { 340.0f, 200.0f });\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\t//------ Particle Count ------//\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Particle Count\");\n\tImGui::Spacing();\n\n\tint particlesAmout = static_cast<int>(myParam.pParticles.size()) + static_cast<int>(myParam.pParticles3D.size());\n\tint selecParticlesAmout = static_cast<int>(myParam.pParticlesSelected.size()) + static_cast<int>(myParam.pParticlesSelected3D.size());\n\n\tImGui::Text(\"%s%d\", \"Total Particles: \", particlesAmout);\n\n\tImGui::Text(\"%s%d\", \"Selected Particles: \", selecParticlesAmout);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\t//------ Composition ------//\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Composition\");\n\tImGui::Spacing();\n\n\tfloat waterAmount = 0.0f;\n\tfloat rockAmount = 0.0f;\n\tfloat ironAmount = 0.0f;\n\tfloat sandAmount = 0.0f;\n\tfloat soilAmount = 0.0f;\n\tfloat mudAmount = 0.0f;\n\tfloat rubberAmount = 0.0f;\n\n\t// This is not the ideal way to do it, but I'm using this for now because there are not many materials\n\n\tif (!myVar.is3DMode) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tParticleRendering& r = myParam.rParticles[i];\n\t\t\tif (myParam.pParticlesSelected.size() == 0) {\n\t\t\t\tif (r.sphLabel == 1) {\n\t\t\t\t\twaterAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 2) {\n\t\t\t\t\trockAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 3) {\n\t\t\t\t\tironAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 4) {\n\t\t\t\t\tsandAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 5) {\n\t\t\t\t\tsoilAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 6) {\n\t\t\t\t\tmudAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 7) {\n\t\t\t\t\trubberAmount++;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (r.sphLabel == 1 && r.isSelected) {\n\t\t\t\t\twaterAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 2 && r.isSelected) {\n\t\t\t\t\trockAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 3 && r.isSelected) {\n\t\t\t\t\tironAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 4 && r.isSelected) {\n\t\t\t\t\tsandAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 5 && r.isSelected) {\n\t\t\t\t\tsoilAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 6 && r.isSelected) {\n\t\t\t\t\tmudAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 7 && r.isSelected) {\n\t\t\t\t\trubberAmount++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tParticleRendering3D& r = myParam.rParticles3D[i];\n\t\t\tif (myParam.pParticlesSelected3D.size() == 0) {\n\t\t\t\tif (r.sphLabel == 1) {\n\t\t\t\t\twaterAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 2) {\n\t\t\t\t\trockAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 3) {\n\t\t\t\t\tironAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 4) {\n\t\t\t\t\tsandAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 5) {\n\t\t\t\t\tsoilAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 6) {\n\t\t\t\t\tmudAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 7) {\n\t\t\t\t\trubberAmount++;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (r.sphLabel == 1 && r.isSelected) {\n\t\t\t\t\twaterAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 2 && r.isSelected) {\n\t\t\t\t\trockAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 3 && r.isSelected) {\n\t\t\t\t\tironAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 4 && r.isSelected) {\n\t\t\t\t\tsandAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 5 && r.isSelected) {\n\t\t\t\t\tsoilAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 6 && r.isSelected) {\n\t\t\t\t\tmudAmount++;\n\t\t\t\t}\n\t\t\t\telse if (r.sphLabel == 7 && r.isSelected) {\n\t\t\t\t\trubberAmount++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tstd::vector<const char*> labels;\n\tstd::vector<float> values;\n\n\tif (waterAmount > 0) { labels.push_back(\"Water\"); values.push_back(waterAmount); }\n\tif (rockAmount > 0) { labels.push_back(\"Rock\"); values.push_back(rockAmount); }\n\tif (ironAmount > 0) { labels.push_back(\"Iron\"); values.push_back(ironAmount); }\n\tif (sandAmount > 0) { labels.push_back(\"Sand\"); values.push_back(sandAmount); }\n\tif (soilAmount > 0) { labels.push_back(\"Soil\"); values.push_back(soilAmount); }\n\tif (mudAmount > 0) { labels.push_back(\"Mud\"); values.push_back(mudAmount); }\n\tif (rubberAmount > 0) { labels.push_back(\"Rubber\"); values.push_back(rubberAmount); }\n\n\tif (!values.empty() && ImPlot::BeginPlot(\"Material Distribution\", ImVec2(300, 300), ImPlotFlags_Equal)) {\n\n\t\tImPlot::SetupAxes(nullptr, nullptr,\n\t\t\tImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_Lock,\n\t\t\tImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_Lock);\n\n\t\tImPlot::SetupAxesLimits(-1, 1, -1, 1, ImGuiCond_Always);\n\n\t\tImPlot::PlotPieChart(labels.data(), values.data(), static_cast<int>(values.size()), 0.0, 0.0, 0.8);\n\n\t\tImPlot::EndPlot();\n\t}\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\t//------ Mass ------//\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Mass\");\n\tImGui::Spacing();\n\n\tdouble totalMass = 0.0;\n\n\tif (!myVar.is3DMode) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\ttotalMass += myParam.pParticles[i].mass;\n\t\t}\n\t}\n\telse {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\ttotalMass += myParam.pParticles3D[i].mass;\n\t\t}\n\t}\n\n\tImGui::Text(\"Total Mass: %.2f\", totalMass);\n\n\tdouble selectedMas = 0.0;\n\n\tif (!myVar.is3DMode) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\tselectedMas += myParam.pParticles[i].mass;\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\tselectedMas += myParam.pParticles3D[i].mass;\n\t\t\t}\n\t\t}\n\t}\n\n\tImGui::Text(\"Selected Mass: %.2f\", selectedMas);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\t//------ Velocity ------//\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Selected Velocity\");\n\tImGui::Spacing();\n\n\tglm::vec3 selectedVel = { 0.0f, 0.0f, 0.0f };\n\tfloat totalVel = 0.0f;\n\n\tif (!myVar.is3DMode) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\tselectedVel += glm::vec3{myParam.pParticles[i].vel, 0.0f};\n\t\t\t}\n\t\t}\n\n\t\tif (myParam.pParticlesSelected.size() > 0) {\n\t\t\tselectedVel /= myParam.pParticlesSelected.size();\n\t\t\ttotalVel = sqrt(selectedVel.x * selectedVel.x + selectedVel.y * selectedVel.y);\n\t\t}\n\t}\n\telse {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\tselectedVel += myParam.pParticles3D[i].vel;\n\t\t\t}\n\t\t}\n\n\t\tif (myParam.pParticlesSelected3D.size() > 0) {\n\t\t\tselectedVel /= myParam.pParticlesSelected3D.size();\n\t\t\ttotalVel = sqrt(selectedVel.x * selectedVel.x + selectedVel.y * selectedVel.y + selectedVel.z * selectedVel.z);\n\t\t}\n\t}\n\n\tplotLinesHelper(myVar.timeFactor, \"Velocity X: \", graphHistoryLimit, selectedVel.x, -300.0f, 300.0f, graphDefaultSize);\n\tImGui::Spacing();\n\tplotLinesHelper(myVar.timeFactor, \"Velocity Y: \", graphHistoryLimit, selectedVel.y, -300.0f, 300.0f, graphDefaultSize);\n\tImGui::Spacing();\n\tif (myVar.is3DMode) {\n\t\tplotLinesHelper(myVar.timeFactor, \"Velocity Z: \", graphHistoryLimit, selectedVel.z, -300.0f, 300.0f, graphDefaultSize);\n\t\tImGui::Spacing();\n\t}\n\tplotLinesHelper(myVar.timeFactor, \"Total Velocity: \", graphHistoryLimit, totalVel, -300.0f, 300.0f, graphDefaultSize);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\t//------ Acceleration ------//\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Selected Acceleration\");\n\tImGui::Spacing();\n\n\tglm::vec3 selectedAcc = { 0.0f, 0.0f, 0.0f };\n\tfloat totalAcc = 0.0f;\n\n\tif (!myVar.is3DMode) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\tselectedAcc += glm::vec3{ myParam.pParticles[i].acc, 0.0f };\n\t\t\t}\n\t\t}\n\n\t\tif (myParam.pParticlesSelected.size() > 0) {\n\t\t\tselectedAcc /= myParam.pParticlesSelected.size();\n\t\t\ttotalAcc = sqrt(selectedAcc.x * selectedAcc.x + selectedAcc.y * selectedAcc.y);\n\t\t}\n\t}\n\telse {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\tselectedAcc += myParam.pParticles3D[i].acc;\n\t\t\t}\n\t\t}\n\n\t\tif (myParam.pParticlesSelected3D.size() > 0) {\n\t\t\tselectedAcc /= myParam.pParticlesSelected3D.size();\n\t\t\ttotalAcc = sqrt(selectedAcc.x * selectedAcc.x + selectedAcc.y * selectedAcc.y + selectedAcc.z * selectedAcc.z);\n\t\t}\n\t}\n\n\tplotLinesHelper(myVar.timeFactor, \"Acceleration X: \", graphHistoryLimit, selectedAcc.x, -300.0f, 300.0f, graphDefaultSize);\n\tImGui::Spacing();\n\tplotLinesHelper(myVar.timeFactor, \"Acceleration Y: \", graphHistoryLimit, selectedAcc.y, -300.0f, 300.0f, graphDefaultSize);\n\tImGui::Spacing();\n\tif (myVar.is3DMode) {\n\t\tplotLinesHelper(myVar.timeFactor, \"Acceleration Z: \", graphHistoryLimit, selectedAcc.z, -300.0f, 300.0f, graphDefaultSize);\n\t\tImGui::Spacing();\n\t}\n\tplotLinesHelper(myVar.timeFactor, \"Total Acceleration: \", graphHistoryLimit, totalAcc, -300.0f, 300.0f, graphDefaultSize);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\t//------ Pressure ------//\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Selected Pressure\");\n\tImGui::Spacing();\n\n\tfloat totalPress = 0.0f;\n\n\tif (!myVar.is3DMode) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\ttotalPress += myParam.pParticles[i].press;\n\t\t\t}\n\t\t}\n\n\t\tif (myParam.pParticlesSelected.size() > 0) {\n\t\t\ttotalPress /= myParam.pParticlesSelected.size();\n\t\t}\n\t}\n\telse {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\ttotalPress += myParam.pParticles3D[i].press;\n\t\t\t}\n\t\t}\n\n\t\tif (myParam.pParticlesSelected3D.size() > 0) {\n\t\t\ttotalPress /= myParam.pParticlesSelected3D.size();\n\t\t}\n\t}\n\n\tplotLinesHelper(myVar.timeFactor, \"Pressure: \", graphHistoryLimit, totalPress, 0.0f, 100.0f, graphDefaultSize);\n\n\tImGui::Spacing();\n\tImGui::Separator();\n\tImGui::Spacing();\n\n\t//------ Temperature ------//\n\n\tImGui::TextColored(UpdateVariables::colMenuInformation, \"Selected Temperature\");\n\tImGui::Spacing();\n\n\tfloat totalTemp = 0.0f;\n\n\tif (!myVar.is3DMode) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\ttotalTemp += myParam.pParticles[i].temp;\n\t\t\t}\n\t\t}\n\n\t\tif (myParam.pParticlesSelected.size() > 0) {\n\t\t\ttotalTemp /= myParam.pParticlesSelected.size();\n\t\t}\n\t}\n\telse {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\ttotalTemp += myParam.pParticles3D[i].temp;\n\t\t\t}\n\t\t}\n\n\t\tif (myParam.pParticlesSelected3D.size() > 0) {\n\t\t\ttotalTemp /= myParam.pParticlesSelected3D.size();\n\t\t}\n\t}\n\n\tplotLinesHelper(myVar.timeFactor, \"Temperature: \", graphHistoryLimit, totalTemp, 0.0f, 100.0f, graphDefaultSize);\n}\n\nstd::unordered_map<std::string, PlotData> UI::plotDataMap;\n\nvoid UI::plotLinesHelper(const float& timeFactor, std::string label,\n\tconst int length,\n\tfloat value, const float minValue, const float maxValue, ImVec2 size) {\n\n\tauto& plotData = plotDataMap[label];\n\n\tif (plotData.values.size() != length) {\n\t\tplotData.values.resize(length, 0.0f);\n\t\tplotData.offset = 0;\n\t}\n\n\tif (timeFactor > 0.0f) {\n\t\tplotData.values[plotData.offset] = value;\n\t\tplotData.offset = (plotData.offset + 1) % length;\n\t}\n\n\tstd::vector<float> ordered_values(length);\n\tstd::vector<float> ordered_x(length);\n\n\tfor (int i = 0; i < length; ++i) {\n\t\tint idx = (plotData.offset + i) % length;\n\t\tordered_values[i] = plotData.values[idx];\n\t\tordered_x[i] = static_cast<float>(i);\n\t}\n\n\n\n\tif (ImPlot::BeginPlot(label.c_str(), size, ImPlotFlags_NoInputs)) {\n\n\t\tImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_AutoFit);\n\n\t\tImPlot::PushStyleColor(ImPlotCol_Line, UpdateVariables::colPlotLine);\n\t\tImPlot::PushStyleColor(ImPlotCol_AxisText, UpdateVariables::colAxisText);\n\t\tImPlot::PushStyleColor(ImPlotCol_AxisGrid, UpdateVariables::colAxisGrid);\n\t\tImPlot::PushStyleColor(ImPlotCol_AxisBg, UpdateVariables::colAxisBg);\n\t\tImPlot::PushStyleColor(ImPlotCol_FrameBg, UpdateVariables::colFrameBg);\n\t\tImPlot::PushStyleColor(ImPlotCol_PlotBg, UpdateVariables::colPlotBg);\n\t\tImPlot::PushStyleColor(ImPlotCol_PlotBorder, UpdateVariables::colPlotBorder);\n\t\tImPlot::PushStyleColor(ImPlotCol_LegendBg, UpdateVariables::colLegendBg);\n\n\t\tImPlot::PlotLine(label.c_str(), ordered_x.data(), ordered_values.data(), length);\n\n\t\tImPlot::PopStyleColor(8);\n\n\t\tImPlot::EndPlot();\n\t}\n}\n\nstatic bool wasHovered = false;\n\nbool UI::buttonHelper(std::string label, std::string tooltip, bool& parameter,\n\tfloat sizeX, float sizeY, bool canSelfDeactivate, bool& isEnabled) {\n\n\tImGuiID buttonId = ImGui::GetID(label.c_str());\n\tstatic std::unordered_map<ImGuiID, bool> hoverStates;\n\n\tif (!isEnabled) {\n\t\tImGui::BeginDisabled();\n\t}\n\n\tbool pushedColor = false;\n\tif (parameter) {\n\t\tImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonActive);\n\t\tImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonActiveHover);\n\t\tImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonActivePress);\n\t\tpushedColor = true;\n\t}\n\n\tbool hasBeenPressed = false;\n\tImVec2 buttonSize;\n\n\tif (sizeX > 0.0f && sizeY > 0.0f) {\n\t\tbuttonSize = ImVec2(sizeX, sizeY);\n\t}\n\telse if (sizeX < 0.0f && sizeY > 0.0f) {\n\t\tbuttonSize = ImVec2(ImGui::GetContentRegionAvail().x, sizeY);\n\t}\n\telse if (sizeX > 0.0f && sizeY < 0.0f) {\n\t\tbuttonSize = ImVec2(sizeX, ImGui::GetContentRegionAvail().y);\n\t}\n\telse {\n\t\tbuttonSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y);\n\t}\n\n\tstd::vector<Sound>* soundPool = nullptr;\n\n\tif (ImGui::Button(label.c_str(), buttonSize)) {\n\n\t\tif (!parameter) {\n\n\t\t\tsoundPool = &GESound::soundButtonEnablePool;\n\n\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\tbool played = false;\n\n\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!played) {\n\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tsoundPool = &GESound::soundButtonDisablePool;\n\n\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\tbool played = false;\n\n\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!played) {\n\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (canSelfDeactivate) {\n\t\t\tparameter = !parameter;\n\t\t}\n\t\telse if (!parameter) {\n\t\t\tparameter = true;\n\t\t}\n\t\thasBeenPressed = true;\n\t}\n\n\tif (pushedColor) {\n\t\tImGui::PopStyleColor(3);\n\t}\n\n\tbool isHovered = ImGui::IsItemHovered();\n\n\tif (isHovered) {\n\t\tImGui::SetTooltip(\"%s\", tooltip.c_str());\n\n\t\tint randSoundNum = rand() % 3;\n\n\t\tif (!hoverStates[buttonId]) {\n\t\t\tstd::vector<Sound>* soundPool = nullptr;\n\n\t\t\tswitch (randSoundNum) {\n\t\t\tcase 0:\n\t\t\t\tsoundPool = &GESound::soundButtonHover1Pool;\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tsoundPool = &GESound::soundButtonHover2Pool;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tsoundPool = &GESound::soundButtonHover3Pool;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\tbool played = false;\n\n\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!played) {\n\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\thoverStates[buttonId] = isHovered;\n\n\tif (!isEnabled) {\n\t\tImGui::EndDisabled();\n\t}\n\n\treturn hasBeenPressed;\n}\n\n\n\nbool UI::sliderHelper(std::string label, std::string tooltip, float& parameter, float minVal, float maxVal,\n\tfloat sizeX, float sizeY, bool& isEnabled, int logarithmic) {\n\n\tbool isSliderUsed = false;\n\n\tImGuiID sliderId = ImGui::GetID(label.c_str());\n\tstatic std::unordered_map<ImGuiID, bool> hoverStates;\n\tstatic std::unordered_map<ImGuiID, float> defaultValues;\n\n\tif (!isEnabled) {\n\t\tImGui::BeginDisabled();\n\t}\n\n\tif (defaultValues.find(sliderId) == defaultValues.end()) {\n\t\tdefaultValues[sliderId] = parameter;\n\t}\n\n\tImVec2 sliderSize;\n\n\tif (sizeX > 0.0f && sizeY > 0.0f) {\n\t\tsliderSize = ImVec2(sizeX, sizeY);\n\t}\n\telse if (sizeX < 0.0f && sizeY > 0.0f) {\n\t\tsliderSize = ImVec2(ImGui::GetContentRegionAvail().x, sizeY);\n\t}\n\telse if (sizeX > 0.0f && sizeY < 0.0f) {\n\t\tsliderSize = ImVec2(sizeX, ImGui::GetContentRegionAvail().y);\n\t}\n\telse {\n\t\tsliderSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y);\n\t}\n\n\tImGui::Text(\"%s\", label.c_str());\n\n\tif (ImGui::SliderFloat((\"##\" + label).c_str(), &parameter, minVal, maxVal, \"%.3f\", ImGuiSliderFlags_Logarithmic)) {\n\t\tisSliderUsed = true;\n\t}\n\n\tstd::vector<Sound>* soundPool = nullptr;\n\n\tstatic bool hasBeenPressed = false;\n\n\tif (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {\n\n\t\thasBeenPressed = true;\n\n\t\tsoundPool = &GESound::soundButtonEnablePool;\n\n\t\tif (soundPool && !soundPool->empty()) {\n\t\t\tbool played = false;\n\n\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\tplayed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!played) {\n\t\t\t\tPlaySound(soundPool->back());\n\t\t\t}\n\t\t}\n\t}\n\n\tif (hasBeenPressed && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {\n\n\t\tsoundPool = &GESound::soundButtonDisablePool;\n\n\t\tif (soundPool && !soundPool->empty()) {\n\t\t\tbool played = false;\n\n\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\tplayed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!played) {\n\t\t\t\tPlaySound(soundPool->back());\n\t\t\t}\n\t\t}\n\n\t\thasBeenPressed = false;\n\t}\n\n\tstatic float prevValue = parameter;\n\tstatic bool wasPlaying = false;\n\tstatic ImVec2 lastMousePos = ImGui::GetMousePos();\n\n\tif (ImGui::IsItemActive()) {\n\t\tImVec2 currentMousePos = ImGui::GetMousePos();\n\t\tfloat mouseDelta = abs(currentMousePos.x - lastMousePos.x) + abs(currentMousePos.y - lastMousePos.y);\n\n\t\tif (mouseDelta > 2.0f) {\n\t\t\tif (!wasPlaying || parameter != prevValue) {\n\t\t\t\tsoundPool = &GESound::soundSliderSlidePool;\n\t\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\t\tbool played = false;\n\t\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\t\twasPlaying = true;\n\t\t\t\t\t\t\tlastMousePos = currentMousePos;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!played) {\n\t\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t\t\twasPlaying = true;\n\t\t\t\t\t\tlastMousePos = currentMousePos;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tprevValue = parameter;\n\t}\n\telse {\n\t\twasPlaying = false;\n\t}\n\n\tbool isHovered = ImGui::IsItemHovered();\n\n\tif (isHovered) {\n\t\tImGui::SetTooltip(\"%s\", tooltip.c_str());\n\n\t\tint randSoundNum = rand() % 3;\n\n\t\tif (!hoverStates[sliderId]) {\n\t\t\tstd::vector<Sound>* soundPool = nullptr;\n\n\t\t\tswitch (randSoundNum) {\n\t\t\tcase 0:\n\t\t\t\tsoundPool = &GESound::soundButtonHover1Pool;\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tsoundPool = &GESound::soundButtonHover2Pool;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tsoundPool = &GESound::soundButtonHover3Pool;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\tbool played = false;\n\n\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!played) {\n\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\thoverStates[sliderId] = isHovered;\n\n\tif (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {\n\t\tparameter = defaultValues[sliderId];\n\n\t\tisSliderUsed = true;\n\t}\n\n\tif (!isEnabled) {\n\t\tImGui::EndDisabled();\n\t}\n\n\treturn isSliderUsed;\n}\n\nbool UI::sliderHelper(std::string label, std::string tooltip, float& parameter, float minVal, float maxVal,\n\tfloat sizeX, float sizeY, bool& isEnabled) {\n\n\tbool isSliderUsed = false;\n\n\tImGuiID sliderId = ImGui::GetID(label.c_str());\n\tstatic std::unordered_map<ImGuiID, bool> hoverStates;\n\tstatic std::unordered_map<ImGuiID, float> defaultValues;\n\n\tif (!isEnabled) {\n\t\tImGui::BeginDisabled();\n\t}\n\n\tif (defaultValues.find(sliderId) == defaultValues.end()) {\n\t\tdefaultValues[sliderId] = parameter;\n\t}\n\n\tImVec2 sliderSize;\n\n\tif (sizeX > 0.0f && sizeY > 0.0f) {\n\t\tsliderSize = ImVec2(sizeX, sizeY);\n\t}\n\telse if (sizeX < 0.0f && sizeY > 0.0f) {\n\t\tsliderSize = ImVec2(ImGui::GetContentRegionAvail().x, sizeY);\n\t}\n\telse if (sizeX > 0.0f && sizeY < 0.0f) {\n\t\tsliderSize = ImVec2(sizeX, ImGui::GetContentRegionAvail().y);\n\t}\n\telse {\n\t\tsliderSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y);\n\t}\n\n\tImGui::Text(\"%s\", label.c_str());\n\n\tif (ImGui::SliderFloat((\"##\" + label).c_str(), &parameter, minVal, maxVal, \"%.3f\")) {\n\t\tisSliderUsed = true;\n\t}\n\n\tstd::vector<Sound>* soundPool = nullptr;\n\n\tstatic bool hasBeenPressed = false;\n\n\tif (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {\n\n\t\thasBeenPressed = true;\n\n\t\tsoundPool = &GESound::soundButtonEnablePool;\n\n\t\tif (soundPool && !soundPool->empty()) {\n\t\t\tbool played = false;\n\n\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\tplayed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!played) {\n\t\t\t\tPlaySound(soundPool->back());\n\t\t\t}\n\t\t}\n\t}\n\n\tif (hasBeenPressed && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {\n\n\t\tsoundPool = &GESound::soundButtonDisablePool;\n\n\t\tif (soundPool && !soundPool->empty()) {\n\t\t\tbool played = false;\n\n\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\tplayed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!played) {\n\t\t\t\tPlaySound(soundPool->back());\n\t\t\t}\n\t\t}\n\n\t\thasBeenPressed = false;\n\t}\n\n\tstatic float prevValue = parameter;\n\tstatic bool wasPlaying = false;\n\tstatic ImVec2 lastMousePos = ImGui::GetMousePos();\n\n\tif (ImGui::IsItemActive()) {\n\t\tImVec2 currentMousePos = ImGui::GetMousePos();\n\t\tfloat mouseDelta = abs(currentMousePos.x - lastMousePos.x) + abs(currentMousePos.y - lastMousePos.y);\n\n\t\tif (mouseDelta > 2.0f) {\n\t\t\tif (!wasPlaying || parameter != prevValue) {\n\t\t\t\tsoundPool = &GESound::soundSliderSlidePool;\n\t\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\t\tbool played = false;\n\t\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\t\twasPlaying = true;\n\t\t\t\t\t\t\tlastMousePos = currentMousePos;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!played) {\n\t\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t\t\twasPlaying = true;\n\t\t\t\t\t\tlastMousePos = currentMousePos;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tprevValue = parameter;\n\t}\n\telse {\n\t\twasPlaying = false;\n\t}\n\n\tbool isHovered = ImGui::IsItemHovered();\n\n\tif (isHovered) {\n\t\tImGui::SetTooltip(\"%s\", tooltip.c_str());\n\n\t\tint randSoundNum = rand() % 3;\n\n\t\tif (!hoverStates[sliderId]) {\n\t\t\tstd::vector<Sound>* soundPool = nullptr;\n\n\t\t\tswitch (randSoundNum) {\n\t\t\tcase 0:\n\t\t\t\tsoundPool = &GESound::soundButtonHover1Pool;\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tsoundPool = &GESound::soundButtonHover2Pool;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tsoundPool = &GESound::soundButtonHover3Pool;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\tbool played = false;\n\n\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!played) {\n\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\thoverStates[sliderId] = isHovered;\n\n\tif (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {\n\t\tparameter = defaultValues[sliderId];\n\n\t\tisSliderUsed = true;\n\t}\n\n\tif (!isEnabled) {\n\t\tImGui::EndDisabled();\n\t}\n\n\treturn isSliderUsed;\n}\n\nbool UI::sliderHelper(std::string label, std::string tooltip, int& parameter, int minVal, int maxVal,\n\tfloat sizeX, float sizeY, bool& isEnabled) {\n\n\tbool isSliderUsed = false;\n\n\tImGuiID sliderId = ImGui::GetID(label.c_str());\n\tstatic std::unordered_map<ImGuiID, bool> hoverStates;\n\tstatic std::unordered_map<ImGuiID, float> defaultValues;\n\n\tif (!isEnabled) {\n\t\tImGui::BeginDisabled();\n\t}\n\n\tif (defaultValues.find(sliderId) == defaultValues.end()) {\n\t\tdefaultValues[sliderId] = parameter;\n\t}\n\n\tImVec2 sliderSize;\n\n\tif (sizeX > 0.0f && sizeY > 0.0f) {\n\t\tsliderSize = ImVec2(sizeX, sizeY);\n\t}\n\telse if (sizeX < 0.0f && sizeY > 0.0f) {\n\t\tsliderSize = ImVec2(ImGui::GetContentRegionAvail().x, sizeY);\n\t}\n\telse if (sizeX > 0.0f && sizeY < 0.0f) {\n\t\tsliderSize = ImVec2(sizeX, ImGui::GetContentRegionAvail().y);\n\t}\n\telse {\n\t\tsliderSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y);\n\t}\n\n\tImGui::Text(\"%s\", label.c_str());\n\n\tif (ImGui::SliderInt((\"##\" + label).c_str(), &parameter, minVal, maxVal)) {\n\t\tisSliderUsed = true;\n\t}\n\n\tstd::vector<Sound>* soundPool = nullptr;\n\n\tstatic bool hasBeenPressed = false;\n\n\tif (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {\n\n\t\thasBeenPressed = true;\n\n\t\tsoundPool = &GESound::soundButtonEnablePool;\n\n\t\tif (soundPool && !soundPool->empty()) {\n\t\t\tbool played = false;\n\n\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\tplayed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!played) {\n\t\t\t\tPlaySound(soundPool->back());\n\t\t\t}\n\t\t}\n\t}\n\n\tif (hasBeenPressed && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {\n\n\t\tsoundPool = &GESound::soundButtonDisablePool;\n\n\t\tif (soundPool && !soundPool->empty()) {\n\t\t\tbool played = false;\n\n\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\tplayed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!played) {\n\t\t\t\tPlaySound(soundPool->back());\n\t\t\t}\n\t\t}\n\n\t\thasBeenPressed = false;\n\t}\n\n\tstatic int prevValue = parameter;\n\tstatic bool wasPlaying = false;\n\tstatic ImVec2 lastMousePos = ImGui::GetMousePos();\n\n\tif (ImGui::IsItemActive()) {\n\t\tImVec2 currentMousePos = ImGui::GetMousePos();\n\t\tint mouseDelta = abs(currentMousePos.x - lastMousePos.x) + abs(currentMousePos.y - lastMousePos.y);\n\n\t\tif (mouseDelta > 2.0f) {\n\t\t\tif (!wasPlaying || parameter != prevValue) {\n\t\t\t\tsoundPool = &GESound::soundSliderSlidePool;\n\t\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\t\tbool played = false;\n\t\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\t\twasPlaying = true;\n\t\t\t\t\t\t\tlastMousePos = currentMousePos;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!played) {\n\t\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t\t\twasPlaying = true;\n\t\t\t\t\t\tlastMousePos = currentMousePos;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tprevValue = parameter;\n\t}\n\telse {\n\t\twasPlaying = false;\n\t}\n\n\tbool isHovered = ImGui::IsItemHovered();\n\n\tif (isHovered) {\n\t\tImGui::SetTooltip(\"%s\", tooltip.c_str());\n\n\t\tint randSoundNum = rand() % 3;\n\n\t\tif (!hoverStates[sliderId]) {\n\t\t\tstd::vector<Sound>* soundPool = nullptr;\n\n\t\t\tswitch (randSoundNum) {\n\t\t\tcase 0:\n\t\t\t\tsoundPool = &GESound::soundButtonHover1Pool;\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tsoundPool = &GESound::soundButtonHover2Pool;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tsoundPool = &GESound::soundButtonHover3Pool;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (soundPool && !soundPool->empty()) {\n\t\t\t\tbool played = false;\n\n\t\t\t\tfor (Sound& sound : *soundPool) {\n\t\t\t\t\tif (!IsSoundPlaying(sound)) {\n\t\t\t\t\t\tPlaySound(sound);\n\t\t\t\t\t\tplayed = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!played) {\n\t\t\t\t\tPlaySound(soundPool->back());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\thoverStates[sliderId] = isHovered;\n\n\tstatic int defaultVal = parameter;\n\n\tif (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {\n\t\tparameter = defaultValues[sliderId];\n\n\t\tisSliderUsed = true;\n\t}\n\n\tif (!isEnabled) {\n\t\tImGui::EndDisabled();\n\t}\n\n\treturn isSliderUsed;\n}\n"
  },
  {
    "path": "GalaxyEngine/src/UI/brush.cpp",
    "content": "#include \"UI/brush.h\"\n\n#include \"parameters.h\"\n\nstruct SPHWater water;\nstruct SPHRock rock;\nstruct SPHIron iron;\nstruct SPHSand sand;\nstruct SPHSoil soil;\nstruct SPHMud mud;\nstruct SPHRubber rubber;\n\nvoid Brush::brushLogic(UpdateParameters& myParam, bool& isSPHEnabled, bool& constraintAfterDrawing, float& massScatter, UpdateVariables& myVar) {\n\n\t// This entire function is a crime against programming and perhaps humanity as well. I don't know what destiny shall await for whoever reads such cursed code. \n\t// But I'm too lazy to change this for now. Will change it some time in the future\n\n\tif (!isSPHEnabled) {\n\t\tmyVar.SPHWater = false;\n\t\tmyVar.SPHRock = false;\n\t\tmyVar.SPHIron = false;\n\t\tmyVar.SPHSand = false;\n\t\tmyVar.SPHSoil = false;\n\t\tmyVar.SPHIce = false;\n\t\tmyVar.SPHMud = false;\n\t\tmyVar.SPHRubber = false;\n\t}\n\n\tif (!myVar.SPHWater && !myVar.SPHRock && !myVar.SPHSand && !myVar.SPHSoil && !myVar.SPHIce && !myVar.SPHMud && !myVar.SPHGas && !myVar.SPHIron && !myVar.SPHRubber) {\n\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\tglm::vec2 randomOffset = {\n\t\t\t\tcos(angle) * distance,\n\t\t\t\tsin(angle) * distance\n\t\t\t};\n\n\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\tfloat finalMass = 0.0f;\n\n\t\t\tfloat rand01 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\tfloat randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * massScatter;\n\n\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\tfinalMass = 8500000000.0f / myVar.particleAmountMultiplier * randomMassMultiplier;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfinalMass = 8500000000.0f * randomMassMultiplier;\n\t\t\t}\n\n\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\tfinalMass,\n\n\t\t\t\t0.008f,\n\t\t\t\t1.0f,\n\t\t\t\t1.0f,\n\t\t\t\t1.0f);\n\n\t\t\tmyParam.rParticles.emplace_back(Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0);\n\n\t\t\tif (isSPHEnabled) {\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 10000000;\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (isSPHEnabled) {\n\t\tif (myVar.SPHWater) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\twater.restDens,\n\t\t\t\t\twater.stiff,\n\t\t\t\t\twater.visc,\n\t\t\t\t\twater.cohesion);\n\n\t\t\t\tmyParam.rParticles.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = water.color;\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHRock) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * rock.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * rock.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\trock.restDens,\n\t\t\t\t\trock.stiff,\n\t\t\t\t\trock.visc,\n\t\t\t\t\trock.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (50.0f * normalRand) - 25.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(rock.color.r),\n\t\t\t\t\t\taddRandom(rock.color.g),\n\t\t\t\t\t\taddRandom(rock.color.b),\n\t\t\t\t\t\trock.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\trock.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(rock.color.r),\n\t\t\t\t\t\taddRandom(rock.color.g),\n\t\t\t\t\t\taddRandom(rock.color.b),\n\t\t\t\t\t\trock.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHIron) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * iron.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * iron.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\tiron.restDens,\n\t\t\t\t\tiron.stiff,\n\t\t\t\t\tiron.visc,\n\t\t\t\t\tiron.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (40.0f * normalRand) - 20.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(iron.color.r),\n\t\t\t\t\t\taddRandom(iron.color.g),\n\t\t\t\t\t\taddRandom(iron.color.b),\n\t\t\t\t\t\tiron.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\tiron.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(iron.color.r),\n\t\t\t\t\t\taddRandom(iron.color.g),\n\t\t\t\t\t\taddRandom(iron.color.b),\n\t\t\t\t\t\tiron.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHSand) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * sand.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * sand.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\tsand.restDens,\n\t\t\t\t\tsand.stiff,\n\t\t\t\t\tsand.visc,\n\t\t\t\t\tsand.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (50.0f * normalRand) - 25.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(sand.color.r),\n\t\t\t\t\t\taddRandom(sand.color.g),\n\t\t\t\t\t\taddRandom(sand.color.b),\n\t\t\t\t\t\tsand.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\tsand.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(sand.color.r),\n\t\t\t\t\t\taddRandom(sand.color.g),\n\t\t\t\t\t\taddRandom(sand.color.b),\n\t\t\t\t\t\tsand.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHSoil) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * soil.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * soil.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\tsoil.restDens,\n\t\t\t\t\tsoil.stiff,\n\t\t\t\t\tsoil.visc,\n\t\t\t\t\tsoil.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (50.0f * normalRand) - 25.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(soil.color.r),\n\t\t\t\t\t\taddRandom(soil.color.g),\n\t\t\t\t\t\taddRandom(soil.color.b),\n\t\t\t\t\t\tsoil.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\tsoil.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(soil.color.r),\n\t\t\t\t\t\taddRandom(soil.color.g),\n\t\t\t\t\t\taddRandom(soil.color.b),\n\t\t\t\t\t\tsoil.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHIce) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\twater.restDens,\n\t\t\t\t\twater.stiff,\n\t\t\t\t\twater.visc,\n\t\t\t\t\twater.cohesion);\n\n\t\t\t\tmyParam.rParticles.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = water.color;\n\t\t\t\tmyParam.pParticles.back().temp = 1.0f;\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHMud) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * mud.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * mud.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\tmud.restDens,\n\t\t\t\t\tmud.stiff,\n\t\t\t\t\tmud.visc,\n\t\t\t\t\tmud.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (50.0f * normalRand) - 25.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(mud.color.r),\n\t\t\t\t\t\taddRandom(mud.color.g),\n\t\t\t\t\t\taddRandom(mud.color.b),\n\t\t\t\t\t\tmud.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\tmud.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(mud.color.r),\n\t\t\t\t\t\taddRandom(mud.color.g),\n\t\t\t\t\t\taddRandom(mud.color.b),\n\t\t\t\t\t\tmud.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHRubber) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * rubber.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * rubber.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\trubber.restDens,\n\t\t\t\t\trubber.stiff,\n\t\t\t\t\trubber.visc,\n\t\t\t\t\trubber.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (40.0f * normalRand) - 20.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(rubber.color.r),\n\t\t\t\t\t\taddRandom(rubber.color.g),\n\t\t\t\t\t\taddRandom(rubber.color.b),\n\t\t\t\t\t\trubber.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\trubber.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(rubber.color.r),\n\t\t\t\t\t\taddRandom(rubber.color.g),\n\t\t\t\t\t\taddRandom(rubber.color.b),\n\t\t\t\t\t\trubber.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHGas) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\t\t\t\tfloat angle = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) * 2.0f * 3.14159f;\n\t\t\t\tfloat distance = sqrt(static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * brushRadius;\n\n\t\t\t\tglm::vec2 randomOffset = {\n\t\t\t\t\tcos(angle) * distance,\n\t\t\t\t\tsin(angle) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles.emplace_back(particlePos,\n\t\t\t\t\tglm::vec2{ 0, 0 },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\twater.restDens,\n\t\t\t\t\twater.stiff,\n\t\t\t\t\twater.visc,\n\t\t\t\t\twater.cohesion);\n\n\t\t\t\tmyParam.rParticles.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id);\n\n\t\t\t\tmyParam.rParticles.back().sphColor = water.color;\n\t\t\t\tmyParam.pParticles.back().temp = 440.0f;\n\n\t\t\t\tmyParam.rParticles.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Brush::brushSize() {\n\tfloat wheel = GetMouseWheelMove();\n\tif (IO::shortcutDown(KEY_LEFT_CONTROL) && wheel != 0) {\n\t\tfloat scale = 0.2f * wheel;\n\t\tbrushRadius = Clamp(expf(logf(brushRadius) + scale), 2.5f, 512.0f);\n\t}\n}\n\nvoid Brush::drawBrush(glm::vec2 mouseWorldPos) {\n\tDrawCircleLinesV({ mouseWorldPos.x, mouseWorldPos.y }, brushRadius, WHITE);\n}\n\nvoid Brush::eraseBrush(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif ((IO::shortcutDown(KEY_X) && IO::mouseDown(2)) || IO::mouseDown(0) && myVar.toolErase) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size();) {\n\t\t\tglm::vec2 distanceFromBrush = {\n\t\t\t\tmyParam.pParticles[i].pos.x - myParam.myCamera.mouseWorldPos.x,\n\t\t\t\tmyParam.pParticles[i].pos.y - myParam.myCamera.mouseWorldPos.y\n\t\t\t};\n\n\t\t\tfloat distance = sqrt(distanceFromBrush.x * distanceFromBrush.x +\n\t\t\t\tdistanceFromBrush.y * distanceFromBrush.y);\n\n\t\t\tif (distance < brushRadius) {\n\t\t\t\tstd::swap(myParam.pParticles[i], myParam.pParticles.back());\n\t\t\t\tstd::swap(myParam.rParticles[i], myParam.rParticles.back());\n\n\t\t\t\tmyParam.pParticles.pop_back();\n\t\t\t\tmyParam.rParticles.pop_back();\n\t\t\t}\n\t\t\telse {\n\t\t\t\ti++;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Brush::particlesAttractor(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (IO::shortcutDown(KEY_B) || (IO::mouseDown(0) && myVar.toolRadialForce)) {\n\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tfloat dx = myParam.pParticles[i].pos.x - myParam.myCamera.mouseWorldPos.x;\n\t\t\tfloat dy = myParam.pParticles[i].pos.y - myParam.myCamera.mouseWorldPos.y;\n\t\t\tfloat distance = sqrt(dx * dx + dy * dy);\n\n\t\t\tfloat innerRadius = 0.0f;\n\t\t\tfloat outerRadius = brushRadius;\n\n\t\t\tfloat falloffFactor = 0.0f;\n\t\t\tif (distance > innerRadius) {\n\t\t\t\tfalloffFactor = std::min(0.7f, (distance - innerRadius) / (outerRadius - innerRadius));\n\n\t\t\t\tfalloffFactor = falloffFactor * falloffFactor;\n\t\t\t}\n\n\t\t\tfloat radiusMultiplier = 0.0f;\n\n\t\t\tif (distance < 1.0f) {\n\t\t\t\tradiusMultiplier = 1.0f;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tradiusMultiplier = distance;\n\t\t\t}\n\n\n\t\t\tfloat acceleration = static_cast<float>(myVar.G * 600.0f * brushRadius * brushRadius) / (radiusMultiplier * radiusMultiplier);\n\n\t\t\tacceleration *= falloffFactor;\n\n\t\t\tattractorForce.x = static_cast<float>(-(dx / radiusMultiplier) * acceleration * myParam.pParticles[i].mass);\n\t\t\tattractorForce.y = static_cast<float>(-(dy / radiusMultiplier) * acceleration * myParam.pParticles[i].mass);\n\n\t\t\tif (IO::shortcutDown(KEY_LEFT_CONTROL)) {\n\t\t\t\tattractorForce = { -attractorForce.x, -attractorForce.y };\n\t\t\t}\n\n\t\t\tmyParam.pParticles[i].vel.x += attractorForce.x * myVar.timeFactor * myVar.brushAttractForceMult;\n\t\t\tmyParam.pParticles[i].vel.y += attractorForce.y * myVar.timeFactor * myVar.brushAttractForceMult;\n\t\t}\n\t}\n}\n\nvoid Brush::particlesSpinner(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (IO::shortcutDown(KEY_N) || (IO::mouseDown(0) && myVar.toolSpin)) {\n\t\tfor (auto& pParticle : myParam.pParticles) {\n\t\t\tglm::vec2 distanceFromBrush = { pParticle.pos.x - myParam.myCamera.mouseWorldPos.x, pParticle.pos.y - myParam.myCamera.mouseWorldPos.y };\n\t\t\tfloat distance = sqrt(distanceFromBrush.x * distanceFromBrush.x + distanceFromBrush.y * distanceFromBrush.y);\n\t\t\tif (distance < brushRadius) {\n\n\t\t\t\tfloat falloff = distance / brushRadius;\n\t\t\t\tfalloff = powf(falloff, 2.0f);\n\n\t\t\t\tfloat inverseDistance = 1.0f / (distance + myVar.softening);\n\t\t\t\tglm::vec2 radialDirection = { distanceFromBrush.x * inverseDistance, distanceFromBrush.y * inverseDistance };\n\t\t\t\tglm::vec2 spinDirection = { -radialDirection.y, radialDirection.x };\n\n\t\t\t\tif (IO::shortcutDown(KEY_LEFT_CONTROL)) {\n\t\t\t\t\tspinDirection = { -spinDirection.x, -spinDirection.y };\n\t\t\t\t}\n\n\t\t\t\tpParticle.vel.x += spinDirection.x * spinForce * falloff * myVar.timeFactor * myVar.brushSpinForceMult;\n\t\t\t\tpParticle.vel.y += spinDirection.y * spinForce * falloff * myVar.timeFactor * myVar.brushSpinForceMult;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Brush::particlesGrabber(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y);\n\tglm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom);\n\n\tlastMouseVelocity = scaledDelta;\n\n\tif (IO::shortcutPress(KEY_M) || (IO::mousePress(0) && myVar.toolMove)) {\n\t\tdragging = true;\n\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tglm::vec2 distanceFromBrush = { myParam.pParticles[i].pos - myParam.myCamera.mouseWorldPos };\n\n\t\t\tfloat distance = sqrt(distanceFromBrush.x * distanceFromBrush.x +\n\t\t\t\tdistanceFromBrush.y * distanceFromBrush.y);\n\t\t\tif (distance < brushRadius) {\n\t\t\t\tmyParam.rParticles[i].isGrabbed = true;\n\n\t\t\t\tmyParam.rParticles[i].turbulence = 0.0f;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (dragging) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isGrabbed) {\n\n\t\t\t\tmyParam.pParticles[i].pos.x += scaledDelta.x;\n\t\t\t\tmyParam.pParticles[i].pos.y += scaledDelta.y;\n\n\t\t\t\tmyParam.pParticles[i].acc = { 0.0f, 0.0f };\n\t\t\t\tmyParam.pParticles[i].vel = { 0.0f, 0.0f };\n\t\t\t\tmyParam.pParticles[i].prevVel = { 0.0f, 0.0f };\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::shortcutReleased(KEY_M) || (IO::mouseReleased(0) && myVar.toolMove)) {\n\n\t\tfloat impulseFactor = 5.0f;\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isGrabbed) {\n\n\t\t\t\tmyParam.pParticles[i].vel = lastMouseVelocity * impulseFactor;\n\t\t\t\tmyParam.pParticles[i].prevVel = myParam.pParticles[i].vel;\n\n\t\t\t\tmyParam.rParticles[i].isGrabbed = false;\n\t\t\t}\n\t\t}\n\t\tdragging = false;\n\t}\n}\n\nvoid Brush::temperatureBrush(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (IO::shortcutDown(KEY_K) || IO::shortcutDown(KEY_L) || (IO::mouseDown(0) && myVar.toolRaiseTemp) || (IO::mouseDown(0) && myVar.toolLowerTemp)) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tglm::vec2 distanceFromBrush = { myParam.pParticles[i].pos - myParam.myCamera.mouseWorldPos };\n\n\t\t\tfloat distance = sqrt(distanceFromBrush.x * distanceFromBrush.x +\n\t\t\t\tdistanceFromBrush.y * distanceFromBrush.y);\n\n\t\t\tif (IO::shortcutDown(KEY_K) || (IO::mouseDown(0) && myVar.toolRaiseTemp)) {\n\t\t\t\tif (distance < brushRadius) {\n\t\t\t\t\tmyParam.pParticles[i].temp += 40.0f;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (IO::shortcutDown(KEY_L) || (IO::mouseDown(0) && myVar.toolLowerTemp)) {\n\t\t\t\tif (distance < brushRadius && myParam.pParticles[i].temp > 1.0f) {\n\t\t\t\t\tmyParam.pParticles[i].temp -= 40.0f;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ---- 3D IMPLEMENTATION ---- //\n\nvoid Brush3D::brushLogic(UpdateParameters& myParam, bool& isSPHEnabled, bool& constraintAfterDrawing, float& massScatter, UpdateVariables& myVar) {\n\n\tif (!isSPHEnabled) {\n\t\tmyVar.SPHWater = false;\n\t\tmyVar.SPHRock = false;\n\t\tmyVar.SPHIron = false;\n\t\tmyVar.SPHSand = false;\n\t\tmyVar.SPHSoil = false;\n\t\tmyVar.SPHIce = false;\n\t\tmyVar.SPHMud = false;\n\t\tmyVar.SPHRubber = false;\n\t}\n\n\tif (!myVar.SPHWater && !myVar.SPHRock && !myVar.SPHSand && !myVar.SPHSoil && !myVar.SPHIce && !myVar.SPHMud && !myVar.SPHGas && !myVar.SPHIron && !myVar.SPHRubber) {\n\n\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\tglm::vec3 randomOffset = {\n\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\tcosf(phi) * distance\n\t\t\t};\n\n\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\tfloat finalMass = 0.0f;\n\t\t\tfloat rand01 = static_cast<float>(rand()) / RAND_MAX;\n\t\t\tfloat randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * 0.8f;\n\n\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\tfinalMass = 8500000000.0f / myVar.particleAmountMultiplier * randomMassMultiplier;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfinalMass = 8500000000.0f * randomMassMultiplier;\n\t\t\t}\n\n\t\t\tmyParam.pParticles3D.emplace_back(\n\t\t\t\tparticlePos,\n\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\tfinalMass,\n\t\t\t\t0.008f,\n\t\t\t\t1.0f,\n\t\t\t\t1.0f,\n\t\t\t\t1.0f\n\t\t\t);\n\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\tColor{ 128, 128, 128, 100 },\n\t\t\t\t0.125f,\n\t\t\t\tfalse, false, false,\n\t\t\t\ttrue, true, false, true,\n\t\t\t\t-1.0f, 0\n\t\t\t);\n\n\t\t\tif (isSPHEnabled) {\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 10000000;\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (isSPHEnabled) {\n\t\tif (myVar.SPHWater) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\twater.restDens,\n\t\t\t\t\twater.stiff,\n\t\t\t\t\twater.visc,\n\t\t\t\t\twater.cohesion);\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = water.color;\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHRock) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * rock.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * rock.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\trock.restDens,\n\t\t\t\t\trock.stiff,\n\t\t\t\t\trock.visc,\n\t\t\t\t\trock.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (50.0f * normalRand) - 25.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(rock.color.r),\n\t\t\t\t\t\taddRandom(rock.color.g),\n\t\t\t\t\t\taddRandom(rock.color.b),\n\t\t\t\t\t\trock.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\trock.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(rock.color.r),\n\t\t\t\t\t\taddRandom(rock.color.g),\n\t\t\t\t\t\taddRandom(rock.color.b),\n\t\t\t\t\t\trock.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHIron) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * iron.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * iron.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\tiron.restDens,\n\t\t\t\t\tiron.stiff,\n\t\t\t\t\tiron.visc,\n\t\t\t\t\tiron.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (40.0f * normalRand) - 20.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(iron.color.r),\n\t\t\t\t\t\taddRandom(iron.color.g),\n\t\t\t\t\t\taddRandom(iron.color.b),\n\t\t\t\t\t\tiron.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\tiron.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(iron.color.r),\n\t\t\t\t\t\taddRandom(iron.color.g),\n\t\t\t\t\t\taddRandom(iron.color.b),\n\t\t\t\t\t\tiron.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHSand) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * sand.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * sand.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\tsand.restDens,\n\t\t\t\t\tsand.stiff,\n\t\t\t\t\tsand.visc,\n\t\t\t\t\tsand.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (50.0f * normalRand) - 25.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(sand.color.r),\n\t\t\t\t\t\taddRandom(sand.color.g),\n\t\t\t\t\t\taddRandom(sand.color.b),\n\t\t\t\t\t\tsand.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\tsand.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(sand.color.r),\n\t\t\t\t\t\taddRandom(sand.color.g),\n\t\t\t\t\t\taddRandom(sand.color.b),\n\t\t\t\t\t\tsand.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHSoil) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * soil.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * soil.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\tsoil.restDens,\n\t\t\t\t\tsoil.stiff,\n\t\t\t\t\tsoil.visc,\n\t\t\t\t\tsoil.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (50.0f * normalRand) - 25.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(soil.color.r),\n\t\t\t\t\t\taddRandom(soil.color.g),\n\t\t\t\t\t\taddRandom(soil.color.b),\n\t\t\t\t\t\tsoil.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\tsoil.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(soil.color.r),\n\t\t\t\t\t\taddRandom(soil.color.g),\n\t\t\t\t\t\taddRandom(soil.color.b),\n\t\t\t\t\t\tsoil.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHIce) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\twater.restDens,\n\t\t\t\t\twater.stiff,\n\t\t\t\t\twater.visc,\n\t\t\t\t\twater.cohesion);\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = water.color;\n\t\t\t\tmyParam.pParticles3D.back().temp = 1.0f;\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHMud) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * mud.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * mud.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\tmud.restDens,\n\t\t\t\t\tmud.stiff,\n\t\t\t\t\tmud.visc,\n\t\t\t\t\tmud.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (50.0f * normalRand) - 25.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(mud.color.r),\n\t\t\t\t\t\taddRandom(mud.color.g),\n\t\t\t\t\t\taddRandom(mud.color.b),\n\t\t\t\t\t\tmud.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\tmud.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(mud.color.r),\n\t\t\t\t\t\taddRandom(mud.color.g),\n\t\t\t\t\t\taddRandom(mud.color.b),\n\t\t\t\t\t\tmud.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHRubber) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * rubber.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * rubber.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\trubber.restDens,\n\t\t\t\t\trubber.stiff,\n\t\t\t\t\trubber.visc,\n\t\t\t\t\trubber.cohesion);\n\n\t\t\t\tfloat normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);\n\t\t\t\tauto addRandom = [&](unsigned char c) -> unsigned char {\n\t\t\t\t\tfloat value = static_cast<float>(c) + (40.0f * normalRand) - 20.0f;\n\t\t\t\t\tvalue = std::clamp(value, 0.0f, 255.0f);\n\t\t\t\t\treturn static_cast<unsigned char>(value);\n\t\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(\n\t\t\t\t\tColor{\n\t\t\t\t\t\taddRandom(rubber.color.r),\n\t\t\t\t\t\taddRandom(rubber.color.g),\n\t\t\t\t\t\taddRandom(rubber.color.b),\n\t\t\t\t\t\trubber.color.a\n\t\t\t\t\t},\n\n\t\t\t\t\t0.125f,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\ttrue,\n\t\t\t\t\tfalse,\n\t\t\t\t\ttrue,\n\t\t\t\t\t-1.0f,\n\t\t\t\t\trubber.id\n\t\t\t\t);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = Color{\n\t\t\t\t\t\taddRandom(rubber.color.r),\n\t\t\t\t\t\taddRandom(rubber.color.g),\n\t\t\t\t\t\taddRandom(rubber.color.b),\n\t\t\t\t\t\trubber.color.a\n\t\t\t\t};\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.SPHGas) {\n\t\t\tfor (int i = 0; i < static_cast<int>(140 * myVar.particleAmountMultiplier); i++) {\n\n\t\t\t\tfloat theta = static_cast<float>(rand()) / RAND_MAX * 2.0f * 3.14159265f;\n\t\t\t\tfloat phi = acosf(1.0f - 2.0f * (static_cast<float>(rand()) / RAND_MAX));\n\t\t\t\tfloat distance = sqrtf(static_cast<float>(rand()) / RAND_MAX) * brushRadius;\n\n\t\t\t\tglm::vec3 randomOffset = {\n\t\t\t\t\tsinf(phi) * cosf(theta) * distance,\n\t\t\t\t\tsinf(phi) * sinf(theta) * distance,\n\t\t\t\t\tcosf(phi) * distance\n\t\t\t\t};\n\n\t\t\t\tglm::vec3 particlePos = brushPos + randomOffset;\n\n\t\t\t\tfloat finalMass = 0.0f;\n\n\t\t\t\tif (myParam.particlesSpawning.massMultiplierEnabled) {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalMass = (8500000000.0f * water.massMult);\n\t\t\t\t}\n\n\t\t\t\tmyParam.pParticles3D.emplace_back(particlePos,\n\t\t\t\t\tglm::vec3{ 0.0f, 0.0f, 0.0f },\n\t\t\t\t\tfinalMass,\n\n\t\t\t\t\twater.restDens,\n\t\t\t\t\twater.stiff,\n\t\t\t\t\twater.visc,\n\t\t\t\t\twater.cohesion);\n\n\t\t\t\tmyParam.rParticles3D.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id);\n\n\t\t\t\tmyParam.rParticles3D.back().sphColor = water.color;\n\t\t\t\tmyParam.pParticles3D.back().temp = 440.0f;\n\n\t\t\t\tmyParam.rParticles3D.back().spawnCorrectIter = 0;\n\n\t\t\t\tmyParam.rParticles3D.back().isBeingDrawn = true;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Brush3D::brushSize() {\n\tfloat wheel = GetMouseWheelMove();\n\tif (IO::shortcutDown(KEY_LEFT_CONTROL) && wheel != 0) {\n\t\tfloat scale = 0.2f * wheel;\n\t\tbrushRadius = Clamp(expf(logf(brushRadius) + scale), 2.5f, 512.0f);\n\t}\n}\n\nglm::vec3 Brush3D::brushPosLogic(UpdateParameters& myParam, UpdateVariables& myVar) {\n\n\tVector2 mousePos = GetMousePosition();\n\n\tRay mouseRay = GetScreenToWorldRay(mousePos, myParam.myCamera3D.cam3D);\n\n\tfloat wheel = GetMouseWheelMove();\n\tif (IO::shortcutDown(KEY_LEFT_SHIFT) && wheel != 0) {\n\t\tfloat scale = 0.2f * wheel;\n\t\tspawnDistance = Clamp(expf(logf(spawnDistance) + scale), 64.0f, 5000.0f);\n\t}\n\n\tbrushPos = {\n\t\t\tmouseRay.position.x + mouseRay.direction.x * spawnDistance,\n\t\t\tmouseRay.position.y + mouseRay.direction.y * spawnDistance,\n\t\t\tmouseRay.position.z + mouseRay.direction.z * spawnDistance\n\t};\n\n\tif (!myParam.pParticles3D.empty() &&\n\t\t!myVar.isBrushDrawing &&\n\t\t!dragging &&\n\t\t(!IO::shortcutDown(KEY_B) || (!IO::mouseDown(0) && myVar.toolRadialForce)) && \n\t\t(!IO::shortcutDown(KEY_N) || (!IO::mouseDown(0) && myVar.toolSpin))) {\n\t\tClusterHelper::clusterMouseHelper(myParam.myCamera3D.cam3D, spawnDistance);\n\t}\n\n\treturn brushPos;\n}\n\nvoid Brush3D::drawBrush(float& domainHeight) {\n\n\tDrawSphere({ brushPos.x, brushPos.y, brushPos.z }, brushRadius, { 12, 82, 172, 50 });\n\n\tfloat domainBottomY = -domainHeight * 0.5f;\n\n\tDrawLine3D(\n\t\t{ brushPos.x, brushPos.y, brushPos.z },\n\t\t{ brushPos.x, domainBottomY, brushPos.z },\n\t\t{ 12, 82, 172, 140 }\n\t);\n\n\tDrawLine3D(\n\t\t{ brushPos.x, brushPos.y, brushPos.z },\n\t\t{ brushPos.x + 10.0f, brushPos.y, brushPos.z },\n\t\tRED\n\t);\n\n\tDrawLine3D(\n\t\t{ brushPos.x, brushPos.y, brushPos.z },\n\t\t{ brushPos.x, brushPos.y + 10.0f, brushPos.z },\n\t\tGREEN\n\t);\n\n\tDrawLine3D(\n\t\t{ brushPos.x, brushPos.y, brushPos.z },\n\t\t{ brushPos.x, brushPos.y, brushPos.z + 10.0f },\n\t\tBLUE\n\t);\n}\n\nvoid Brush3D::particlesGrabber(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec3 brushDelta = brushPos - lastBrushPos;\n\n\tlastBrushVelocity = brushDelta;\n\n\tif (IO::shortcutPress(KEY_M) || (IO::mousePress(0) && myVar.toolMove)) {\n\t\tdragging = true;\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\n\t\t\tfloat dist = glm::distance(myParam.pParticles3D[i].pos, brushPos);\n\n\t\t\tif (dist < brushRadius) {\n\t\t\t\tmyParam.rParticles3D[i].isGrabbed = true;\n\n\t\t\t\tmyParam.rParticles3D[i].turbulence = 0.0f;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (dragging) {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isGrabbed) {\n\n\t\t\t\tmyParam.pParticles3D[i].pos += brushDelta;\n\n\t\t\t\tmyParam.pParticles3D[i].acc = { 0.0f, 0.0f, 0.0f };\n\t\t\t\tmyParam.pParticles3D[i].vel = { 0.0f, 0.0f, 0.0f };\n\t\t\t\tmyParam.pParticles3D[i].prevVel = { 0.0f, 0.0f, 0.0f };\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IO::shortcutReleased(KEY_M) || (IO::mouseReleased(0) && myVar.toolMove)) {\n\n\t\tfloat impulseFactor = 2.0f;\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isGrabbed) {\n\n\t\t\t\tmyParam.pParticles3D[i].vel = lastBrushVelocity * impulseFactor;\n\n\t\t\t\tmyParam.pParticles3D[i].prevVel = myParam.pParticles3D[i].vel;\n\n\t\t\t\tmyParam.rParticles3D[i].isGrabbed = false;\n\t\t\t}\n\t\t}\n\t\tdragging = false;\n\t}\n\n\tlastBrushPos = brushPos;\n}\n\nvoid Brush3D::eraseBrush(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif ((IO::shortcutDown(KEY_X) && IO::mouseDown(2)) || IO::mouseDown(0) && myVar.toolErase) {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size();) {\n\t\t\tglm::vec3 distanceFromBrush = myParam.pParticles3D[i].pos - brushPos;\n\n\t\t\tfloat distance = sqrt(distanceFromBrush.x * distanceFromBrush.x +\n\t\t\t\tdistanceFromBrush.y * distanceFromBrush.y + distanceFromBrush.z * distanceFromBrush.z);\n\n\t\t\tif (distance < brushRadius) {\n\t\t\t\tstd::swap(myParam.pParticles3D[i], myParam.pParticles3D.back());\n\t\t\t\tstd::swap(myParam.rParticles3D[i], myParam.rParticles3D.back());\n\n\t\t\t\tmyParam.pParticles3D.pop_back();\n\t\t\t\tmyParam.rParticles3D.pop_back();\n\t\t\t}\n\t\t\telse {\n\t\t\t\ti++;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Brush3D::temperatureBrush(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (IO::shortcutDown(KEY_K) || IO::shortcutDown(KEY_L) || (IO::mouseDown(0) && myVar.toolRaiseTemp) || (IO::mouseDown(0) && myVar.toolLowerTemp)) {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tglm::vec3 distanceFromBrush = { myParam.pParticles3D[i].pos - brushPos };\n\n\t\t\tfloat distance = sqrt(distanceFromBrush.x * distanceFromBrush.x +\n\t\t\t\tdistanceFromBrush.y * distanceFromBrush.y + distanceFromBrush.z * distanceFromBrush.z);\n\n\t\t\tif (IO::shortcutDown(KEY_K) || (IO::mouseDown(0) && myVar.toolRaiseTemp)) {\n\t\t\t\tif (distance < brushRadius) {\n\t\t\t\t\tmyParam.pParticles3D[i].temp += 40.0f;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (IO::shortcutDown(KEY_L) || (IO::mouseDown(0) && myVar.toolLowerTemp)) {\n\t\t\t\tif (distance < brushRadius && myParam.pParticles3D[i].temp > 1.0f) {\n\t\t\t\t\tmyParam.pParticles3D[i].temp -= 40.0f;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Brush3D::particlesAttractor(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tif (IO::shortcutDown(KEY_B) || (IO::mouseDown(0) && myVar.toolRadialForce)) {\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tglm::vec3 d = myParam.pParticles3D[i].pos - brushPos;\n\t\t\tfloat distance = glm::length(d);\n\n\t\t\tfloat innerRadius = 0.0f;\n\t\t\tfloat outerRadius = brushRadius;\n\n\t\t\tfloat falloffFactor = 0.0f;\n\t\t\tif (distance > innerRadius) {\n\t\t\t\tfalloffFactor = std::min(0.7f, (distance - innerRadius) / (outerRadius - innerRadius));\n\n\t\t\t\tfalloffFactor = falloffFactor * falloffFactor;\n\t\t\t}\n\n\t\t\tfloat radiusMultiplier = 0.0f;\n\n\t\t\tif (distance < 1.0f) {\n\t\t\t\tradiusMultiplier = 1.0f;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tradiusMultiplier = distance;\n\t\t\t}\n\n\n\t\t\tfloat acceleration = static_cast<float>(myVar.G * 600.0f * brushRadius * brushRadius) / (radiusMultiplier * radiusMultiplier);\n\n\t\t\tacceleration *= falloffFactor;\n\n\t\t\tattractorForce.x = static_cast<float>(-(d.x / radiusMultiplier) * acceleration * myParam.pParticles3D[i].mass);\n\t\t\tattractorForce.y = static_cast<float>(-(d.y / radiusMultiplier) * acceleration * myParam.pParticles3D[i].mass);\n\t\t\tattractorForce.z = static_cast<float>(-(d.z / radiusMultiplier) * acceleration * myParam.pParticles3D[i].mass);\n\n\t\t\tif (IO::shortcutDown(KEY_LEFT_CONTROL)) {\n\t\t\t\tattractorForce = -attractorForce;\n\t\t\t}\n\n\t\t\tmyParam.pParticles3D[i].vel += attractorForce * myVar.timeFactor * myVar.brushAttractForceMult;\n\t\t}\n\t}\n}\n\nvoid Brush3D::particlesSpinner(UpdateVariables& myVar, UpdateParameters& myParam) {\n\tif (IO::shortcutDown(KEY_N) || (IO::mouseDown(0) && myVar.toolSpin)) {\n\n\t\tglm::vec3 cameraUp = { myParam.myCamera3D.cam3D.up.x,myParam.myCamera3D.cam3D.up.y, myParam.myCamera3D.cam3D.up.z };\n\n\t\tglm::vec3 cameraTarget = { myParam.myCamera3D.cam3D.target.x, myParam.myCamera3D.cam3D.target.y, myParam.myCamera3D.cam3D.target.z };\n\t\tglm::vec3 cameraPos = { myParam.myCamera3D.cam3D.position.x,  myParam.myCamera3D.cam3D.position.y,  myParam.myCamera3D.cam3D.position.z };\n\n\n\t\tglm::vec3 cameraForward = glm::normalize(cameraTarget - cameraPos);\n\t\tglm::vec3 cameraRight = glm::normalize(glm::cross(cameraForward, cameraUp));\n\n\t\tfor (auto& pParticle : myParam.pParticles3D) {\n\t\t\tglm::vec3 distanceFromBrush = pParticle.pos - brushPos;\n\n\t\t\tfloat screenX = glm::dot(distanceFromBrush, cameraRight);\n\t\t\tfloat screenY = glm::dot(distanceFromBrush, cameraUp);\n\n\t\t\tfloat distance = sqrt(screenX * screenX + screenY * screenY);\n\n\t\t\tif (distance < brushRadius) {\n\t\t\t\tfloat falloff = distance / brushRadius;\n\t\t\t\tfalloff = powf(falloff, 2.0f);\n\t\t\t\tfloat inverseDistance = 1.0f / (distance + myVar.softening);\n\n\t\t\t\tglm::vec2 radialScreen = glm::vec2(screenX, screenY) * inverseDistance;\n\n\t\t\t\tglm::vec2 spinScreen = glm::vec2(-radialScreen.y, radialScreen.x);\n\n\t\t\t\tglm::vec3 spinDirection = spinScreen.x * cameraRight + spinScreen.y * cameraUp;\n\n\t\t\t\tif (IO::shortcutDown(KEY_LEFT_CONTROL)) {\n\t\t\t\t\tspinDirection = -spinDirection;\n\t\t\t\t}\n\n\t\t\t\tpParticle.vel += spinDirection * spinForce * falloff * myVar.timeFactor * myVar.brushSpinForceMult;\n\t\t\t}\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "GalaxyEngine/src/UI/controls.cpp",
    "content": "#include \"UI/controls.h\"\n#include \"UI/UI.h\"\n#include \"parameters.h\"\n\nvoid Controls::showControls() {\n    if (isShowControlsEnabled) {\n\n        float screenW = static_cast<float>(GetScreenWidth());\n        float screenH = static_cast<float>(GetScreenHeight());\n\n        ImVec2 controlSize = { screenW * 0.15f, screenH * 0.4f };\n\n        controlSize.x = std::clamp(controlSize.x, 400.0f, 2000.0f);\n        controlSize.y = std::clamp(controlSize.y, 600.0f, 2000.0f);\n\n        ImGui::SetNextWindowSize(controlSize, ImGuiCond_Appearing);\n        ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - controlSize.x * 0.5f, screenH * 0.5f - controlSize.y * 0.5f), ImGuiCond_Appearing);\n\n        ImGui::Begin(\"Controls\", nullptr, ImGuiWindowFlags_NoCollapse);\n\n        for (size_t i = 0; i < controlsArray.size(); i++) {\n\n            std::string text = controlsArray[i];\n\n           /* float windowWidth = ImGui::GetWindowSize().x;\n            float textWidth = ImGui::CalcTextSize(text.c_str()).x;\n\n            ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);*/\n\n            ImGui::Text(\"%s\", text.c_str());\n        }\n\n        ImGui::End();\n    }\n}\n\nvoid Controls::showInfo(bool& fullscreen) {\n    if (isInformationEnabled) {\n\n        float screenW = static_cast<float>(GetScreenWidth());\n        float screenH = static_cast<float>(GetScreenHeight());\n\n        ImVec2 infoSize = { screenW * 0.3f, screenH * 0.4f };\n\n        infoSize.x = std::clamp(infoSize.x, 700.0f, 2000.0f);\n        infoSize.y = std::clamp(infoSize.y, 600.0f, 2000.0f);\n\n        ImGui::SetNextWindowSize(infoSize, ImGuiCond_Appearing);\n        ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - infoSize.x * 0.5f, screenH * 0.5f - infoSize.y * 0.5f), ImGuiCond_Appearing);\n\n        ImGui::Begin(\"Information\", nullptr, ImGuiWindowFlags_NoCollapse);\n\n        for (size_t i = 0; i < infoArray.size(); i++) {\n\n            std::string text = infoArray[i];\n\n            /* float windowWidth = ImGui::GetWindowSize().x;\n             float textWidth = ImGui::CalcTextSize(text.c_str()).x;\n\n             ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);*/\n\n            ImGui::Text(\"%s\", text.c_str());\n        }\n\n        ImVec2 linkButtonSize= { 200.0f, 30.0f };\n\n        ImGui::TextColored(ImVec4(0.8f, 0.0f, 0.0f, 1.0f), \"The links might not work on Linux\");\n\n        bool placeHolder = false;\n\n        bool enabled = true;\n\n        if (UI::buttonHelper(\"GitHub Repository\", \"Go to GitHub\", placeHolder, linkButtonSize.x, linkButtonSize.y, enabled, enabled)) {\n            fullscreen = false;\n            OpenURL(\"https://github.com/NarcisCalin/Galaxy-Engine\");\n        }\n\n        ImGui::SameLine();\n\n        if (UI::buttonHelper(\"Join Our Discord Community!\", \"Click to join!\", placeHolder, linkButtonSize.x, linkButtonSize.y, enabled, enabled)) {\n            fullscreen = false;\n            OpenURL(\"https://discord.gg/Xd5JUqNFPM\");\n        }\n\n        ImGui::SameLine();\n\n        if (UI::buttonHelper(\"Soundtrack by HAVA\", \"Check their work!\", placeHolder, linkButtonSize.x, linkButtonSize.y, enabled, enabled)) {\n            fullscreen = false;\n            OpenURL(\"https://open.spotify.com/artist/1vrrvzYvRY27SDZp7WsMwx\");\n        }\n\n        ImGui::End();\n    }\n}\n"
  },
  {
    "path": "GalaxyEngine/src/UI/rightClickSettings.cpp",
    "content": "#include \"UI/rightClickSettings.h\"\n#include \"UI/UI.h\"\n\n#include \"parameters.h\"\n\nvoid RightClickSettings::rightClickMenuSpawnLogic(bool& isMouseNotHoveringUI,\n\tbool& isSpawningAllowed, bool& isDragging, bool& selectedColor) {\n\n\tstatic bool     isMouseMoving = false;\n\tstatic glm::vec2  dragStartPos = { 0.0f, 0.0f };\n\tstatic bool     spawnBlocked = false;\n\n\tif (IsMouseButtonPressed(1) && isMouseNotHoveringUI) {\n\t\tdragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tisMouseMoving = false;\n\t}\n\n\tif (IsMouseButtonDown(1) && isMouseNotHoveringUI) {\n\t\tglm::vec2 cur = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tglm::vec2 d = cur - dragStartPos;\n\t\tif (d.x * d.x + d.y * d.y > 5.0f * 5.0f)\n\t\t\tisMouseMoving = true;\n\t}\n\n\tif (IsMouseButtonPressed(1)) {\n\t\tisMenuActive = false;\n\t}\n\n\tif (IsMouseButtonReleased(1) &&\n\t\t!IsKeyDown(KEY_LEFT_CONTROL) &&\n\t\t!IsKeyDown(KEY_LEFT_ALT) &&\n\t\t!isMouseMoving &&\n\t\tisMouseNotHoveringUI &&\n\t\t!IsMouseButtonDown(0)) {\n\t\tisMenuActive = true;\n\t\tspawnBlocked = false;\n\t\tselectedColorOriginal = selectedColor;\n\t}\n\n\tif (IsMouseButtonPressed(0) &&\n\t\tisMouseNotHoveringUI &&\n\t\tisMenuActive &&\n\t\t!isMouseOnMenu) {\n\n\t\tisMenuActive = false;\n\t\tisSpawningAllowed = false;\n\t\tisDragging = false;\n\t\tspawnBlocked = true;\n\t\tselectedColor = selectedColorOriginal;\n\t\tselectedColorChanged = false;\n\t}\n\telse if (IsMouseButtonPressed(0) &&\n\t\tisMouseNotHoveringUI &&\n\t\t!isMenuActive &&\n\t\tspawnBlocked) {\n\t\tisSpawningAllowed = true;\n\t\tspawnBlocked = false;\n\t}\n}\n\n\nvoid RightClickSettings::rightClickMenu(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\trightClickMenuSpawnLogic(myVar.isMouseNotHoveringUI, myVar.isSpawningAllowed, myVar.isDragging, myParam.colorVisuals.selectedColor);\n\n\tif (isMenuActive) {\n\n\t\tImGui::SetNextWindowSize(ImVec2(200.0f, 425.0f), ImGuiCond_Once);\n\t\tImGui::SetNextWindowPos(ImVec2(GetMousePosition().x, GetMousePosition().y), ImGuiCond_Appearing);\n\n\t\tif (ImGui::Begin(\"Right Click Menu\", nullptr, ImGuiWindowFlags_NoCollapse)) {\n\n\t\t\tbool hovered = ImGui::IsWindowHovered(\n\t\t\t\tImGuiHoveredFlags_AllowWhenBlockedByPopup |\n\t\t\t\tImGuiHoveredFlags_AllowWhenBlockedByActiveItem\n\t\t\t);\n\t\t\tisMouseOnMenu = hovered;\n\t\t}\n\n\t\tbool hovered = ImGui::IsWindowHovered(\n\t\t\tImGuiHoveredFlags_AllowWhenBlockedByPopup |\n\t\t\tImGuiHoveredFlags_AllowWhenBlockedByActiveItem\n\t\t);\n\n\t\tbool enabled = true;\n\n\t\tif (UI::buttonHelper(\"Subdivide All\", \"Subdivide all normal particles\", myParam.subdivision.subdivideAll, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Subdivide Selected\", \"Subdivide all selected normal particles\", myParam.subdivision.subdivideSelected, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Constraint Solids\", \"Adds constraints to all current solid particles\", myVar.constraintAllSolids, -1.0f, buttonSizeY, enabled, myVar.constraintsEnabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Constraint Selected\", \"Adds constraints to selected solid particles\", myVar.constraintSelected, -1.0f, buttonSizeY, enabled, myVar.constraintsEnabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Delete All Constraints\", \"Deletes all current constraints\", myVar.deleteAllConstraints, -1.0f, buttonSizeY, enabled, myVar.constraintsEnabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Del. Selec. Constraints\", \"Deletes all selected constraints\", myVar.deleteSelectedConstraints, -1.0f, buttonSizeY, enabled, myVar.constraintsEnabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Pin Selected\", \"Pins selected particles\", myVar.pinFlag, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Unpin Selected\", \"Unpins selected particles\", myVar.unPinFlag, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Invert Particle Selec.\", \"Invert the particle selection\", myParam.particleSelection.invertParticleSelection, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Deselect All\", \"Deselects all particles\", myParam.particleSelection.deselectParticles, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t\tmyParam.particleSelection3D.deselectParticles = true;\n\t\t}\n\t\tif (UI::buttonHelper(\"Follow selection\", \"Make the camera follow the selected particles\", myParam.myCamera.centerCamera, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Select Clusters\", \"Selects multiple clusters of particles\", myParam.particleSelection.selectManyClusters, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t\tmyParam.particleSelection3D.selectManyClusters3D = true;\n\t\t}\n\t\tif (UI::buttonHelper(\"Delete Selection\", \"Deletes selected particles\", myParam.particleDeletion.deleteSelection, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Delete Stray Particles\", \"Deletes all particles that are not in groups\", myParam.particleDeletion.deleteNonImportant, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Draw Z-Curves\", \"Display the particles indices, sorted by Z-Curves\", myVar.drawZCurves, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Draw Quadtree\", \"Display Barnes-Hut algorithm quadtree\", myVar.drawQuadtree, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\n\t\tbool framesExportButtonEnabled = true;\n\t\tbool safeModeButtonEnabled = false;\n\n\t\tif (myVar.isRecording) {\n\t\t\tframesExportButtonEnabled = false;\n\t\t\tsafeModeButtonEnabled = false;\n\t\t}\n\n\t\tif (!myParam.screenCapture.isExportFramesEnabled) {\n\t\t\tsafeModeButtonEnabled = false;\n\t\t}\n\t\telse if(myParam.screenCapture.isExportFramesEnabled && !myVar.isRecording) {\n\t\t\tsafeModeButtonEnabled = true;\n\t\t}\n\n\t\tif (UI::buttonHelper(\"Enable Frames Export\", \"Exports recorded frames to disk\", myParam.screenCapture.isExportFramesEnabled, -1.0f, buttonSizeY, enabled, framesExportButtonEnabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Safe Frames Export\", \"Store frames directly to the disk. Disabling it will make recording faster, but might crash if you run out of memory\", myParam.screenCapture.isSafeFramesEnabled, -1.0f, buttonSizeY, enabled, safeModeButtonEnabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\t\tif (UI::buttonHelper(\"Reset Custom Colors\", \"Resets custom colors changed in the right click menu\", resetParticleColors, -1.0f, buttonSizeY, enabled, enabled)) {\n\t\t\tisMenuActive = false;\n\t\t}\n\n\n\t\tbool pColChanged = false;\n\t\tbool sColChanged = false;\n\n\t\tImGui::Text(\"Primary Color\");\n\t\tif (ImGui::ColorEdit4(\"##pCol\", (float*)&pCol, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\tmyParam.colorVisuals.selectedColor = false;\n\t\t\tpColChanged = true;\n\n\t\t\tif (!myVar.is3DMode) {\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles.size(); i++) {\n\t\t\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\t\t\tmyParam.rParticles[i].uniqueColor = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles3D.size(); i++) {\n\t\t\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\t\t\tmyParam.rParticles3D[i].uniqueColor = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tImGui::Text(\"Secondary Color\");\n\t\tif (ImGui::ColorEdit4(\"##sCol\", (float*)&sCol, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) {\n\t\t\tmyParam.colorVisuals.selectedColor = false;\n\t\t\tsColChanged = true;\n\n\t\t\tif (!myVar.is3DMode) {\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles.size(); i++) {\n\t\t\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\t\t\tmyParam.rParticles[i].uniqueColor = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles3D.size(); i++) {\n\t\t\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\t\t\tmyParam.rParticles3D[i].uniqueColor = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tImGui::End();\n\n\t\tif (resetParticleColors) {\n\n\t\t\tif (!myVar.is3DMode) {\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles.size(); i++) {\n\t\t\t\t\tmyParam.rParticles[i].pColor = { 255, 255, 255, 255 };\n\t\t\t\t\tmyParam.rParticles[i].sColor = { 255, 255, 255, 255 };\n\t\t\t\t\tmyParam.rParticles[i].uniqueColor = false;\n\t\t\t\t}\n\t\t\t\tresetParticleColors = false;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles3D.size(); i++) {\n\t\t\t\t\tmyParam.rParticles3D[i].pColor = { 255, 255, 255, 255 };\n\t\t\t\t\tmyParam.rParticles3D[i].sColor = { 255, 255, 255, 255 };\n\t\t\t\t\tmyParam.rParticles3D[i].uniqueColor = false;\n\t\t\t\t}\n\t\t\t\tresetParticleColors = false;\n\t\t\t}\n\t\t}\n\n\t\tif (!myVar.is3DMode) {\n\t\t\tif (myParam.rParticlesSelected.size() > 0 && !pColChanged && !sColChanged &&\n\t\t\t\t!ImGui::IsItemActive()) {\n\n\t\t\t\tpCol.x = 0.0f;\n\t\t\t\tpCol.y = 0.0f;\n\t\t\t\tpCol.z = 0.0f;\n\t\t\t\tpCol.w = 0.0f;\n\n\t\t\t\tsCol.x = 0.0f;\n\t\t\t\tsCol.y = 0.0f;\n\t\t\t\tsCol.z = 0.0f;\n\t\t\t\tsCol.w = 0.0f;\n\n\t\t\t\tint visibleSelectedAmount = 0;\n\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles.size(); i++) {\n\t\t\t\t\tParticleRendering& rP = myParam.rParticles[i];\n\n\t\t\t\t\tif (rP.isSelected && !rP.isDarkMatter) {\n\n\t\t\t\t\t\tImVec4 rToVecPColor = rlImGuiColors::Convert(rP.pColor);\n\t\t\t\t\t\tImVec4 rToVecSColor = rlImGuiColors::Convert(rP.sColor);\n\n\t\t\t\t\t\tpCol.x += rToVecPColor.x;\n\t\t\t\t\t\tpCol.y += rToVecPColor.y;\n\t\t\t\t\t\tpCol.z += rToVecPColor.z;\n\t\t\t\t\t\tpCol.w += rToVecPColor.w;\n\n\t\t\t\t\t\tsCol.x += rToVecSColor.x;\n\t\t\t\t\t\tsCol.y += rToVecSColor.y;\n\t\t\t\t\t\tsCol.z += rToVecSColor.z;\n\t\t\t\t\t\tsCol.w += rToVecSColor.w;\n\n\t\t\t\t\t\tvisibleSelectedAmount++;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (visibleSelectedAmount > 0) {\n\t\t\t\t\tpCol.x /= visibleSelectedAmount;\n\t\t\t\t\tpCol.y /= visibleSelectedAmount;\n\t\t\t\t\tpCol.z /= visibleSelectedAmount;\n\t\t\t\t\tpCol.w /= visibleSelectedAmount;\n\n\t\t\t\t\tsCol.x /= visibleSelectedAmount;\n\t\t\t\t\tsCol.y /= visibleSelectedAmount;\n\t\t\t\t\tsCol.z /= visibleSelectedAmount;\n\t\t\t\t\tsCol.w /= visibleSelectedAmount;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif (myParam.rParticlesSelected3D.size() > 0 && !pColChanged && !sColChanged &&\n\t\t\t\t!ImGui::IsItemActive()) {\n\n\t\t\t\tpCol.x = 0.0f;\n\t\t\t\tpCol.y = 0.0f;\n\t\t\t\tpCol.z = 0.0f;\n\t\t\t\tpCol.w = 0.0f;\n\n\t\t\t\tsCol.x = 0.0f;\n\t\t\t\tsCol.y = 0.0f;\n\t\t\t\tsCol.z = 0.0f;\n\t\t\t\tsCol.w = 0.0f;\n\n\t\t\t\tint visibleSelectedAmount = 0;\n\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles3D.size(); i++) {\n\t\t\t\t\tParticleRendering3D& rP = myParam.rParticles3D[i];\n\n\t\t\t\t\tif (rP.isSelected && !rP.isDarkMatter) {\n\n\t\t\t\t\t\tImVec4 rToVecPColor = rlImGuiColors::Convert(rP.pColor);\n\t\t\t\t\t\tImVec4 rToVecSColor = rlImGuiColors::Convert(rP.sColor);\n\n\t\t\t\t\t\tpCol.x += rToVecPColor.x;\n\t\t\t\t\t\tpCol.y += rToVecPColor.y;\n\t\t\t\t\t\tpCol.z += rToVecPColor.z;\n\t\t\t\t\t\tpCol.w += rToVecPColor.w;\n\n\t\t\t\t\t\tsCol.x += rToVecSColor.x;\n\t\t\t\t\t\tsCol.y += rToVecSColor.y;\n\t\t\t\t\t\tsCol.z += rToVecSColor.z;\n\t\t\t\t\t\tsCol.w += rToVecSColor.w;\n\n\t\t\t\t\t\tvisibleSelectedAmount++;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (visibleSelectedAmount > 0) {\n\t\t\t\t\tpCol.x /= visibleSelectedAmount;\n\t\t\t\t\tpCol.y /= visibleSelectedAmount;\n\t\t\t\t\tpCol.z /= visibleSelectedAmount;\n\t\t\t\t\tpCol.w /= visibleSelectedAmount;\n\n\t\t\t\t\tsCol.x /= visibleSelectedAmount;\n\t\t\t\t\tsCol.y /= visibleSelectedAmount;\n\t\t\t\t\tsCol.z /= visibleSelectedAmount;\n\t\t\t\t\tsCol.w /= visibleSelectedAmount;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!myVar.is3DMode) {\n\t\t\tif ((pColChanged || sColChanged) && myParam.rParticlesSelected.size() > 0 && isMenuActive) {\n\t\t\t\tvecToRPColor = rlImGuiColors::Convert(pCol);\n\t\t\t\tvecToRSColor = rlImGuiColors::Convert(sCol);\n\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles.size(); i++) {\n\t\t\t\t\tParticleRendering& rP = myParam.rParticles[i];\n\t\t\t\t\tif (rP.isSelected && !rP.isDarkMatter) {\n\n\t\t\t\t\t\trP.uniqueColor = true;\n\t\t\t\t\t\trP.pColor.r = vecToRPColor.r;\n\t\t\t\t\t\trP.pColor.g = vecToRPColor.g;\n\t\t\t\t\t\trP.pColor.b = vecToRPColor.b;\n\t\t\t\t\t\trP.pColor.a = vecToRPColor.a;\n\n\t\t\t\t\t\trP.sColor.r = vecToRSColor.r;\n\t\t\t\t\t\trP.sColor.g = vecToRSColor.g;\n\t\t\t\t\t\trP.sColor.b = vecToRSColor.b;\n\t\t\t\t\t\trP.sColor.a = vecToRSColor.a;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tif ((pColChanged || sColChanged) && myParam.rParticlesSelected3D.size() > 0 && isMenuActive) {\n\t\t\t\tvecToRPColor = rlImGuiColors::Convert(pCol);\n\t\t\t\tvecToRSColor = rlImGuiColors::Convert(sCol);\n\n\t\t\t\tfor (size_t i = 0; i < myParam.rParticles3D.size(); i++) {\n\t\t\t\t\tParticleRendering3D& rP = myParam.rParticles3D[i];\n\t\t\t\t\tif (rP.isSelected && !rP.isDarkMatter) {\n\n\t\t\t\t\t\trP.uniqueColor = true;\n\t\t\t\t\t\trP.pColor.r = vecToRPColor.r;\n\t\t\t\t\t\trP.pColor.g = vecToRPColor.g;\n\t\t\t\t\t\trP.pColor.b = vecToRPColor.b;\n\t\t\t\t\t\trP.pColor.a = vecToRPColor.a;\n\n\t\t\t\t\t\trP.sColor.r = vecToRSColor.r;\n\t\t\t\t\t\trP.sColor.g = vecToRSColor.g;\n\t\t\t\t\t\trP.sColor.b = vecToRSColor.b;\n\t\t\t\t\t\trP.sColor.a = vecToRSColor.a;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "GalaxyEngine/src/UX/camera.cpp",
    "content": "#include \"UX/camera.h\"\n\n#include \"parameters.h\"\n\nSceneCamera::SceneCamera() {\n\tcamera.offset = { 0.0f, 0.0f };\n\tcamera.target = { 0.0f, 0.0f };\n\tcamera.rotation = 0.0f;\n\tcamera.zoom = 0.5f;\n\tmouseWorldPos = { 0.0f, 0.0f };\n\tpanFollowingOffset = { 0.0f, 0.0f };\n\tisFollowing = false;\n\tcenterCamera = false;\n\tcameraChangedThisFrame = false;\n\tpreviousColor = { 128,128,128,255 };\n\tfollowPosition = { 0.0f, 0.0f };\n\tdelta = { 0.0f, 0.0f };\n}\n\nCamera2D SceneCamera::cameraLogic(bool& loadFlag, bool& isMouseNotHoveringUI) {\n\n\tif (IsMouseButtonDown(1)) {\n\t\tdelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y);\n\t\tdelta = delta * (-1.0f / camera.zoom);\n\t\tcamera.target.x = camera.target.x + delta.x;\n\t\tcamera.target.y = camera.target.y + delta.y;\n\t\tpanFollowingOffset = panFollowingOffset + delta;\n\n\t}\n\n\tfloat wheel = GetMouseWheelMove();\n\n\tmouseWorldPos = glm::vec2(GetScreenToWorld2D(GetMousePosition(), camera).x,\n\t\tGetScreenToWorld2D(GetMousePosition(), camera).y);\n\n\tif (wheel != 0 && !IsKeyDown(KEY_LEFT_CONTROL) && !loadFlag && isMouseNotHoveringUI) {\n\n\n\t\tif (isFollowing) {\n\t\t\tglm::vec2 screenCenter = { GetScreenWidth() * 0.5f, GetScreenHeight() * 0.5f };\n\n\t\t\tmouseWorldPos = glm::vec2(GetScreenToWorld2D({ screenCenter.x,   screenCenter.y }, camera).x,\n\t\t\t\tGetScreenToWorld2D({ screenCenter.x,   screenCenter.y }, camera).y);\n\n\t\t\tcamera.offset = { screenCenter.x, screenCenter.y };\n\t\t\tcamera.target = { mouseWorldPos.x, mouseWorldPos.y };\n\t\t}\n\t\telse {\n\n\t\t\tmouseWorldPos = glm::vec2(GetScreenToWorld2D(GetMousePosition(), camera).x, GetScreenToWorld2D(GetMousePosition(), camera).y);\n\t\t\tcamera.offset = GetMousePosition();\n\t\t\tcamera.target = { mouseWorldPos.x, mouseWorldPos.y };\n\t\t}\n\n\t\tfloat scale = 0.2f * wheel;\n\t\tcamera.zoom = Clamp(expf(logf(camera.zoom) + scale), 0.475f, 64.0f);\n\t}\n\n\t// RESET CAMERA\n\tif (IO::shortcutPress(KEY_F)) {\n\t\tcamera.zoom = defaultCamZoom;\n\t\tcamera.target = { 0.0f, 0.0f };\n\t\tcamera.offset = { 0.0f, 0.0f };\n\t\tpanFollowingOffset = { 0.0f, 0.0f };\n\t}\n\n\treturn camera;\n}\n\nvoid SceneCamera::cameraFollowObject(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tstatic bool isDragging = false;\n\tstatic glm::vec2 dragStartPos = { 0.0f, 0.0f };\n\n\tif ((IsMouseButtonPressed(1) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) ||\n\t\t(IsMouseButtonPressed(1) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI)) {\n\t\tdragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tisDragging = false;\n\t}\n\n\tif ((IsMouseButtonDown(1) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) ||\n\t\t(IsMouseButtonDown(1) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI)) {\n\t\tglm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tfloat dragThreshold = 5.0f;\n\n\t\tglm::vec2 d = currentPos - dragStartPos;\n\n\t\tif (d.x * d.x + d.y * d.y > dragThreshold * dragThreshold) {\n\t\t\tisDragging = true;\n\t\t}\n\t}\n\n\tif (IsMouseButtonReleased(1) && IsKeyDown(KEY_LEFT_CONTROL) && !isDragging && myVar.isMouseNotHoveringUI) {\n\n\t\tmyParam.particleSelection.clusterSelection(myVar, myParam, true);\n\n\t\tisFollowing = true;\n\t\tpanFollowingOffset = { 0.0f, 0.0f };\n\n\t\tif (myVar.isSelectedTrailsEnabled) {\n\t\t\tmyParam.trails.segments.clear();\n\t\t}\n\t}\n\n\tif (IsMouseButtonReleased(1) && IsKeyDown(KEY_LEFT_ALT) && !isDragging && myVar.isMouseNotHoveringUI) {\n\n\t\tmyParam.particleSelection.particleSelection(myVar, myParam, true);\n\n\t\tisFollowing = true;\n\t\tpanFollowingOffset = { 0.0f, 0.0f };\n\t\tif (myVar.isSelectedTrailsEnabled) {\n\t\t\tmyParam.trails.segments.clear();\n\t\t}\n\t}\n\n\tif (IO::shortcutPress(KEY_Z) || centerCamera) {\n\t\tpanFollowingOffset = { 0.0f, 0.0f };\n\t\tisFollowing = true;\n\t\tcenterCamera = false;\n\t}\n\n\tif (isFollowing) {\n\n\t\tglm::vec2 sum = glm::vec2(0.0f, 0.0f);\n\n\t\tfloat count = 0.0f;\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\tsum += myParam.pParticles[i].pos;\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\n\t\tfollowPosition = sum / count;\n\n\t\tfollowPosition = followPosition + panFollowingOffset;\n\n\t\tcamera.target = { followPosition.x, followPosition.y };\n\n\t\tcamera.offset = { GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f };\n\n\t\tif (IO::shortcutPress(KEY_F) || count == 0 || myParam.pParticles.size() == 0) {\n\t\t\tisFollowing = false;\n\t\t\tcamera.zoom = defaultCamZoom;\n\t\t\tcamera.target = { 0.0f, 0.0f };\n\t\t\tcamera.offset = { 0.0f, 0.0f };\n\t\t\tpanFollowingOffset = { 0.0f, 0.0f };\n\t\t}\n\t}\n}\n\nvoid SceneCamera::hasCamMoved() {\n\n\tcameraChangedThisFrame = false;\n\n\tif (lastTarget.x != camera.target.x || lastTarget.y != camera.target.y ||\n\t\tlastOffset.x != camera.offset.x || lastOffset.y != camera.offset.y ||\n\t\tlastZoom != camera.zoom || lastRotation != camera.rotation) {\n\n\t\tcameraChangedThisFrame = true;\n\t}\n\telse {\n\t\tcameraChangedThisFrame = false;\n\t}\n\n\tlastTarget = camera.target;\n\tlastOffset = camera.offset;\n\tlastZoom = camera.zoom;\n\tlastRotation = camera.rotation;\n}\n\n// ---- 3D IMPLEMENTATION ---- //\n\nCamera3D SceneCamera3D::cameraLogic(bool& isLoading, bool& isMouseNotHoveringUI, bool& firstPerson, bool& isShipEnabled) {\n\n\tif (isMouseNotHoveringUI && IO::mouseDown(1) && IO::shortcutDown(KEY_LEFT_ALT) && !firstPerson) {\n\t\tVector2 mouseDelta = GetMouseDelta();\n\t\tfloat panSpeed = distance * 0.002f;\n\t\tfloat radNormX = angleX * DEG2RAD;\n\t\tfloat radNormY = angleY * DEG2RAD;\n\t\tcamNormal.x = cosf(radNormY) * sinf(radNormX);\n\t\tcamNormal.y = sinf(radNormY);\n\t\tcamNormal.z = cosf(radNormY) * cosf(radNormX);\n\t\tcamNormal = glm::normalize(camNormal);\n\t\tcamRight = glm::cross(worldUp, camNormal);\n\t\tcamRight = glm::normalize(camRight);\n\t\tcamUp = glm::cross(camNormal, camRight);\n\t\tcamUp = glm::normalize(camUp);\n\t\tVector3 moveRight = Vector3Scale({ camRight.x,camRight.y, camRight.z }, -mouseDelta.x * panSpeed);\n\t\tVector3 moveUp = Vector3Scale({ camUp.x, camUp.y, camUp.z }, mouseDelta.y * panSpeed);\n\t\tVector3 totalPanMove = Vector3Add(moveRight, moveUp);\n\t\tif (isFollowing) {\n\t\t\tpanFollowingOffset = Vector3Add(panFollowingOffset, totalPanMove);\n\t\t}\n\t\telse {\n\t\t\ttarget = Vector3Add(target, totalPanMove);\n\t\t}\n\t}\n\n\tif (isMouseNotHoveringUI && IO::mouseDown(1) && !IO::shortcutDown(KEY_LEFT_ALT)) {\n\t\tVector2 mouseDelta = GetMouseDelta();\n\t\tangleX -= mouseDelta.x * 0.3f;\n\t\tangleY += mouseDelta.y * 0.3f;\n\t\tif (angleY > 89.0f) angleY = 89.0f;\n\t\tif (angleY < -89.0f) angleY = -89.0f;\n\t}\n\n\tif (isMouseNotHoveringUI && !isShipEnabled) {\n\t\tVector3 arrowMove = { 0.0f, 0.0f, 0.0f };\n\n\t\tfloat radNormX = angleX * DEG2RAD;\n\t\tfloat radNormY = angleY * DEG2RAD;\n\t\tcamNormal.x = cosf(radNormY) * sinf(radNormX);\n\t\tcamNormal.y = sinf(radNormY);\n\t\tcamNormal.z = cosf(radNormY) * cosf(radNormX);\n\t\tcamNormal = glm::normalize(camNormal);\n\t\tcamRight = glm::cross(worldUp, camNormal);\n\t\tcamRight = glm::normalize(camRight);\n\n\n\t\tif (IsKeyDown(KEY_RIGHT)) {\n\t\t\tVector3 moveRight = Vector3Scale({ camRight.x, camRight.y, camRight.z }, arrowMoveSpeed);\n\t\t\tarrowMove += moveRight * GetFrameTime();\n\t\t}\n\n\t\tif (IsKeyDown(KEY_LEFT)) {\n\t\t\tVector3 moveLeft = Vector3Scale({ camRight.x, camRight.y, camRight.z }, -arrowMoveSpeed);\n\t\t\tarrowMove += moveLeft * GetFrameTime();\n\t\t}\n\n\t\tif (IsKeyDown(KEY_UP)) {\n\t\t\tVector3 moveForward = Vector3Scale({ camNormal.x, camNormal.y, camNormal.z }, -arrowMoveSpeed);\n\t\t\tarrowMove += moveForward * GetFrameTime();\n\t\t}\n\n\t\tif (IsKeyDown(KEY_DOWN)) {\n\t\t\tVector3 moveBackward = Vector3Scale({ camNormal.x, camNormal.y, camNormal.z }, arrowMoveSpeed);\n\t\t\tarrowMove += moveBackward * GetFrameTime();\n\t\t}\n\n\t\tif (arrowMove.x != 0.0f || arrowMove.y != 0.0f || arrowMove.z != 0.0f) {\n\t\t\tif (firstPerson) {\n\t\t\t\tfirstPersonPosition = Vector3Add(firstPersonPosition, arrowMove);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (isFollowing) {\n\t\t\t\t\tpanFollowingOffset = Vector3Add(panFollowingOffset, arrowMove);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\ttarget = Vector3Add(target, arrowMove);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (isMouseNotHoveringUI &&\n\t\t!IO::shortcutDown(KEY_LEFT_SHIFT) &&\n\t\t!IO::shortcutDown(KEY_LEFT_CONTROL) &&\n\t\t!firstPerson)\n\t{\n\t\tfloat wheel = GetMouseWheelMove();\n\t\tfloat zoomSpeed = 0.1f;\n\t\tdistance *= (1.0f - wheel * zoomSpeed);\n\t\tif (distance < 1.0f) distance = 1.0f;\n\t\tif (distance > 30000.0f) distance = 30000.0f;\n\t}\n\n\tif (!firstPerson) {\n\n\t\tfloat smoothSpeed = 0.1f;\n\t\tcurrentSmoothedTarget.x += (target.x - currentSmoothedTarget.x) * smoothSpeed;\n\t\tcurrentSmoothedTarget.y += (target.y - currentSmoothedTarget.y) * smoothSpeed;\n\t\tcurrentSmoothedTarget.z += (target.z - currentSmoothedTarget.z) * smoothSpeed;\n\n\t\tfloat radX = angleX * DEG2RAD;\n\t\tfloat radY = angleY * DEG2RAD;\n\t\tVector3 orbitOffset;\n\t\torbitOffset.x = distance * cosf(radY) * sinf(radX);\n\t\torbitOffset.y = distance * sinf(radY);\n\t\torbitOffset.z = distance * cosf(radY) * cosf(radX);\n\n\t\tcam3D.target = { currentSmoothedTarget.x, currentSmoothedTarget.y, currentSmoothedTarget.z };\n\t\tcam3D.position = Vector3Add(cam3D.target, orbitOffset);\n\t}\n\telse {\n\n\t\tfloat radX = angleX * DEG2RAD;\n\t\tfloat radY = angleY * DEG2RAD;\n\n\t\tcam3D.position = firstPersonPosition;\n\n\t\tVector3 lookDirection;\n\t\tlookDirection.x = cosf(radY) * sinf(radX);\n\t\tlookDirection.y = sinf(radY);\n\t\tlookDirection.z = cosf(radY) * cosf(radX);\n\n\t\tcam3D.target = Vector3Add(cam3D.position, Vector3Scale(lookDirection, -1.0f));\n\t}\n\n\tfloat radX = angleX * DEG2RAD;\n\tfloat radY = angleY * DEG2RAD;\n\tcamNormal.x = cosf(radY) * sinf(radX);\n\tcamNormal.y = sinf(radY);\n\tcamNormal.z = cosf(radY) * cosf(radX);\n\tcamNormal = glm::normalize(camNormal);\n\tcamRight = glm::cross(worldUp, camNormal);\n\tcamRight = glm::normalize(camRight);\n\tcamUp = glm::cross(camNormal, camRight);\n\tcamUp = glm::normalize(camUp);\n\n\tcam3D.up = { camUp.x, camUp.y, camUp.z };\n\tcam3D.fovy = 45.0f;\n\tcam3D.projection = CAMERA_PERSPECTIVE;\n\treturn cam3D;\n}\n\nvoid SceneCamera3D::cameraFollowObject(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tstatic bool isDragging = false;\n\tstatic glm::vec2 dragStartPos = { 0.0f, 0.0f };\n\n\tif ((IsMouseButtonPressed(1) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) ||\n\t\t(IsMouseButtonPressed(1) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI)) {\n\t\tdragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tisDragging = false;\n\t}\n\n\tif ((IsMouseButtonDown(1) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) ||\n\t\t(IsMouseButtonDown(1) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI)) {\n\t\tglm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y);\n\t\tfloat dragThreshold = 5.0f;\n\n\t\tglm::vec2 d = currentPos - dragStartPos;\n\n\t\tif (d.x * d.x + d.y * d.y > dragThreshold * dragThreshold) {\n\t\t\tisDragging = true;\n\t\t}\n\t}\n\n\tif (IsMouseButtonReleased(1) && IsKeyDown(KEY_LEFT_CONTROL) && !isDragging && myVar.isMouseNotHoveringUI) {\n\n\t\tmyParam.particleSelection3D.clusterSelection(myVar, myParam, true);\n\n\t\tisFollowing = true;\n\t\tpanFollowingOffset = { 0.0f, 0.0f, 0.0f };\n\n\t\tif (myVar.isSelectedTrailsEnabled) {\n\t\t\tmyParam.trails.segments3D.clear();\n\t\t}\n\t}\n\n\tif (IsMouseButtonReleased(1) && IsKeyDown(KEY_LEFT_ALT) && !isDragging && myVar.isMouseNotHoveringUI) {\n\n\t\tmyParam.particleSelection3D.particleSelection(myVar, myParam, true);\n\n\t\tisFollowing = true;\n\t\tpanFollowingOffset = { 0.0f, 0.0f, 0.0f };\n\t\tif (myVar.isSelectedTrailsEnabled) {\n\t\t\tmyParam.trails.segments3D.clear();\n\t\t}\n\t}\n\n\tif (IO::shortcutPress(KEY_Z) || centerCamera) {\n\t\tpanFollowingOffset = { 0.0f, 0.0f, 0.0f };\n\t\tisFollowing = true;\n\t\tcenterCamera = false;\n\t}\n\n\tif (IO::shortcutPress(KEY_F)) {\n\t\ttarget = { 0.0f, 0.0f, 0.0f };\n\t\tcurrentSmoothedTarget = { 0.0f, 0.0f, 0.0f };\n\n\t\tdistance = defaultCamDist;\n\t\tangleX = 0.0f;\n\t\tangleY = 0.0f;\n\n\t\tisFollowing = false;\n\t}\n\n\tif (isFollowing) {\n\t\tVector3 sum = { 0.0f, 0.0f, 0.0f };\n\t\tfloat count = 0.0f;\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\n\t\t\t\tsum.x += myParam.pParticles3D[i].pos.x;\n\t\t\t\tsum.y += myParam.pParticles3D[i].pos.y;\n\t\t\t\tsum.z += myParam.pParticles3D[i].pos.z;\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\n\t\tif (count > 0) {\n\t\t\ttarget = Vector3Scale(sum, 1.0f / count);\n\n\t\t\ttarget = Vector3Add(target, { panFollowingOffset.x, panFollowingOffset.y, panFollowingOffset.z });\n\t\t}\n\t\telse {\n\t\t\tisFollowing = false;\n\t\t}\n\n\t\tif (IO::shortcutPress(KEY_F) || myParam.pParticles3D.empty()) {\n\t\t\ttarget = { 0.0f, 0.0f, 0.0f };\n\t\t\tcurrentSmoothedTarget = { 0.0f, 0.0f, 0.0f };\n\n\t\t\tdistance = defaultCamDist;\n\t\t\tangleX = 0.0f;\n\t\t\tangleY = 0.0f;\n\n\t\t\tisFollowing = false;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "GalaxyEngine/src/UX/randNum.cpp",
    "content": "#include \"UX/randNum.h\"\n\nfloat getRandomFloat() {\n    static std::random_device rd;\n    static std::mt19937 gen(rd());\n    static std::uniform_real_distribution<float> dist(0.0f, 1.0f);\n    return dist(gen);\n}"
  },
  {
    "path": "GalaxyEngine/src/UX/saveSystem.cpp",
    "content": "#include \"UX/saveSystem.h\"\n\nvoid SaveSystem::saveSystem(const std::string& filename, UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, \n\tPhysics& physics, Physics3D& physics3D, Lighting& lighting, Field& field) {\n\n\tYAML::Emitter out;\n\t/*out << YAML::BeginMap;*/\n\n\t// ----- Trails -----\n\tparamIO(filename, out, \"GlobalTrails\", myVar.isGlobalTrailsEnabled);\n\tparamIO(filename, out, \"SelectedTrails\", myVar.isSelectedTrailsEnabled);\n\tparamIO(filename, out, \"LocalTrails\", myVar.isLocalTrailsEnabled);\n\tparamIO(filename, out, \"WhiteTrails\", myParam.trails.whiteTrails);\n\tparamIO(filename, out, \"TrailsMaxLength\", myVar.trailMaxLength);\n\tparamIO(filename, out, \"TrailsThickness\", myParam.trails.trailThickness);\n\n\t// ----- Color parameters -----\n\tparamIO(filename, out, \"SolidColor\", myParam.colorVisuals.solidColor);\n\tparamIO(filename, out, \"DensityColor\", myParam.colorVisuals.densityColor);\n\tparamIO(filename, out, \"ForceColor\", myParam.colorVisuals.forceColor);\n\tparamIO(filename, out, \"VelocityColor\", myParam.colorVisuals.velocityColor);\n\tparamIO(filename, out, \"ShockwaveColor\", myParam.colorVisuals.shockwaveColor);\n\tparamIO(filename, out, \"TurbulenceColor\", myParam.colorVisuals.turbulenceColor);\n\tparamIO(filename, out, \"PressureColor\", myParam.colorVisuals.pressureColor);\n\tparamIO(filename, out, \"SPHColor\", myParam.colorVisuals.SPHColor);\n\tparamIO(filename, out, \"SelectedColor\", myParam.colorVisuals.selectedColor);\n\tparamIO(filename, out, \"ShowDarkMatter\", myParam.colorVisuals.showDarkMatterEnabled);\n\n\t// ----- Color sliders -----\n\tparamIO(filename, out, \"ColorMaxVel\", myParam.colorVisuals.maxVel);\n\tparamIO(filename, out, \"ColorMaxPressure\", myParam.colorVisuals.maxPress);\n\tparamIO(filename, out, \"MaxColorForce\", myParam.colorVisuals.maxColorAcc);\n\tparamIO(filename, out, \"ShockwaveMaxAcc\", myParam.colorVisuals.ShockwaveMaxAcc);\n\tparamIO(filename, out, \"MaxColorTurbulence\", myParam.colorVisuals.maxColorTurbulence);\n\tparamIO(filename, out, \"TurbulenceFadeRate\", myParam.colorVisuals.turbulenceFadeRate);\n\tparamIO(filename, out, \"TurbulenceContrast\", myParam.colorVisuals.turbulenceContrast);\n\tparamIO(filename, out, \"TurbulenceCustomColor\", myParam.colorVisuals.turbulenceCustomCol);\n\tparamIO(filename, out, \"TemperatureColor\", myParam.colorVisuals.temperatureColor);\n\tparamIO(filename, out, \"TemperatureGasColor\", myParam.colorVisuals.gasTempColor);\n\tparamIO(filename, out, \"MaxTemperatureColor\", myParam.colorVisuals.tempColorMaxTemp);\n\tparamIO(filename, out, \"MaxNeighbors\", myParam.colorVisuals.maxNeighbors);\n\n\t// ----- Other visual sliders -----\n\tparamIO(filename, out, \"MaxSizeForce\", myParam.densitySize.sizeAcc);\n\tparamIO(filename, out, \"Max Dynamic Size\", myParam.densitySize.maxSize);\n\tparamIO(filename, out, \"Min Dynamic Size\", myParam.densitySize.minSize);\n\tparamIO(filename, out, \"MaxSizeNeighbors\", myParam.densitySize.maxNeighbors);\n\tparamIO(filename, out, \"ParticleSizeMult\", myVar.particleSizeMultiplier);\n\tparamIO(filename, out, \"VisiblePAmountMult\", myVar.particleAmountMultiplier);\n\tparamIO(filename, out, \"DMPAmountMult\", myVar.DMAmountMultiplier);\n\tparamIO(filename, out, \"MassRandomMultiplier\", myVar.massScatter);\n\tparamIO(filename, out, \"MassMultiplierToggle\", myParam.particlesSpawning.massMultiplierEnabled);\n\n\t// ----- Colors -----\n\tparamIO(filename, out, \"pColorsR\", myParam.colorVisuals.pColor.r);\n\tparamIO(filename, out, \"pColorsG\", myParam.colorVisuals.pColor.g);\n\tparamIO(filename, out, \"pColorsB\", myParam.colorVisuals.pColor.b);\n\tparamIO(filename, out, \"pColorsA\", myParam.colorVisuals.pColor.a);\n\n\tparamIO(filename, out, \"sColorsR\", myParam.colorVisuals.sColor.r);\n\tparamIO(filename, out, \"sColorsG\", myParam.colorVisuals.sColor.g);\n\tparamIO(filename, out, \"sColorsB\", myParam.colorVisuals.sColor.b);\n\tparamIO(filename, out, \"sColorsA\", myParam.colorVisuals.sColor.a);\n\n\t// ----- Misc Toggles -----\n\tparamIO(filename, out, \"DarkMatter\", myVar.isDarkMatterEnabled);\n\tparamIO(filename, out, \"LoopingSpace\", myVar.isPeriodicBoundaryEnabled);\n\tparamIO(filename, out, \"InfiniteDomain\", myVar.infiniteDomain);\n\tparamIO(filename, out, \"SPHEnabled\", myVar.isSPHEnabled);\n\tparamIO(filename, out, \"DensitySize\", myVar.isDensitySizeEnabled);\n\tparamIO(filename, out, \"ForceSize\", myVar.isForceSizeEnabled);\n\tparamIO(filename, out, \"Glow\", myVar.isGlowEnabled);\n\tparamIO(filename, out, \"ShipGas\", myVar.isShipGasEnabled);\n\tparamIO(filename, out, \"Merger\", myVar.isMergerEnabled);\n\tparamIO(filename, out, \"is3DMode\", myVar.is3DMode);\n\n\tparamIO(filename, out, \"ClipSelectedX\", myVar.clipSelectedX);\n\tparamIO(filename, out, \"ClipSelectedY\", myVar.clipSelectedY);\n\tparamIO(filename, out, \"ClipSelectedZ\", myVar.clipSelectedZ);\n\n\tparamIO(filename, out, \"ClipSelectedXInv\", myVar.clipSelectedXInv);\n\tparamIO(filename, out, \"ClipSelectedYInv\", myVar.clipSelectedYInv);\n\tparamIO(filename, out, \"ClipSelectedZInv\", myVar.clipSelectedZInv);\n\n\n\t// ----- SPH Materials -----\n\tparamIO(filename, out, \"SPHWater\", myVar.SPHWater);\n\tparamIO(filename, out, \"SPHRock\", myVar.SPHRock);\n\tparamIO(filename, out, \"SPHIron\", myVar.SPHIron);\n\tparamIO(filename, out, \"SPHSand\", myVar.SPHSand);\n\tparamIO(filename, out, \"SPHSoil\", myVar.SPHSoil);\n\tparamIO(filename, out, \"SPHIce\", myVar.SPHIce);\n\tparamIO(filename, out, \"SPHMud\", myVar.SPHMud);\n\tparamIO(filename, out, \"SPHRubber\", myVar.SPHRubber);\n\tparamIO(filename, out, \"SPHGas\", myVar.SPHGas);\n\n\t// ----- Physics params -----\n\tparamIO(filename, out, \"Softening\", myVar.softening);\n\tparamIO(filename, out, \"Theta\", myVar.theta);\n\tparamIO(filename, out, \"TimeMult\", myVar.timeStepMultiplier);\n\tparamIO(filename, out, \"GravityMultiplier\", myVar.gravityMultiplier);\n\tparamIO(filename, out, \"HeavyParticlesMass\", myVar.heavyParticleWeightMultiplier);\n\tparamIO(filename, out, \"TemperatureSimulation\", myVar.isTempEnabled);\n\tparamIO(filename, out, \"AmbientTemperature\", myVar.ambientTemp);\n\tparamIO(filename, out, \"AmbientHeatRate\", myVar.globalAmbientHeatRate);\n\tparamIO(filename, out, \"HeatConductivityMultiplier\", myVar.globalHeatConductivity);\n\n\t// ----- SPH -----\n\tparamIO(filename, out, \"SPHGravity\", myVar.verticalGravity);\n\tparamIO(filename, out, \"SPHRadiusMult\", sph.radiusMultiplier);\n\tparamIO(filename, out, \"SPHMass\", myVar.mass);\n\tparamIO(filename, out, \"SPHViscosity\", myVar.viscosity);\n\tparamIO(filename, out, \"SPHStiffness\", myVar.stiffMultiplier);\n\tparamIO(filename, out, \"SPHCohesion\", myVar.cohesionCoefficient);\n\tparamIO(filename, out, \"SPHGround\", myVar.sphGround);\n\tparamIO(filename, out, \"SPHDelta\", myVar.delta);\n\tparamIO(filename, out, \"SPHMaxVel\", myVar.sphMaxVel);\n\n\t// ----- Domain size -----\n\tparamIO(filename, out, \"DomainWidth\", myVar.domainSize.x);\n\tparamIO(filename, out, \"DomainHeight\", myVar.domainSize.y);\n\n\tparamIO(filename, out, \"DomainWidth3D\", myVar.domainSize3D.x);\n\tparamIO(filename, out, \"DomainHeight3D\", myVar.domainSize3D.y);\n\tparamIO(filename, out, \"DomainDepth3D\", myVar.domainSize3D.z);\n\n\t// ----- Camera -----\n\tparamIO(filename, out, \"CameraTargetX\", myParam.myCamera.camera.target.x);\n\tparamIO(filename, out, \"CameraTargetY\", myParam.myCamera.camera.target.y);\n\tparamIO(filename, out, \"CameraOffsetX\", myParam.myCamera.camera.offset.x);\n\tparamIO(filename, out, \"CameraOffsetY\", myParam.myCamera.camera.offset.y);\n\tparamIO(filename, out, \"CameraZoom\", myParam.myCamera.camera.zoom);\n\tparamIO(filename, out, \"CameraIsFollowing\", myParam.myCamera.isFollowing);\n\n\t// ----- Camera 3D -----\n\tparamIO(filename, out, \"CameraTarget3DX\", myParam.myCamera3D.cam3D.target.x);\n\tparamIO(filename, out, \"CameraTarget3DY\", myParam.myCamera3D.cam3D.target.y);\n\tparamIO(filename, out, \"CameraTarget3DZ\", myParam.myCamera3D.cam3D.target.z);\n\tparamIO(filename, out, \"CameraPosition3DX\", myParam.myCamera3D.cam3D.position.x);\n\tparamIO(filename, out, \"CameraPosition3DY\", myParam.myCamera3D.cam3D.position.y);\n\tparamIO(filename, out, \"CameraPosition3DZ\", myParam.myCamera3D.cam3D.position.z);\n\n\tparamIO(filename, out, \"CameraCurrentSmoothedTarget3DX\", myParam.myCamera3D.currentSmoothedTarget.x);\n\tparamIO(filename, out, \"CameraCurrentSmoothedTarget3DY\", myParam.myCamera3D.currentSmoothedTarget.y);\n\tparamIO(filename, out, \"CameraCurrentSmoothedTarget3DZ\", myParam.myCamera3D.currentSmoothedTarget.z);\n\n\tparamIO(filename, out, \"CameraPanFollowingOffset3DX\", myParam.myCamera3D.panFollowingOffset.x);\n\tparamIO(filename, out, \"CameraPanFollowingOffset3DY\", myParam.myCamera3D.panFollowingOffset.y);\n\tparamIO(filename, out, \"CameraPanFollowingOffset3DZ\", myParam.myCamera3D.panFollowingOffset.z);\n\n\tparamIO(filename, out, \"CameraFollowPosition3DX\", myParam.myCamera3D.followPosition.x);\n\tparamIO(filename, out, \"CameraFollowPosition3DY\", myParam.myCamera3D.followPosition.y);\n\tparamIO(filename, out, \"CameraFollowPosition3DZ\", myParam.myCamera3D.followPosition.z);\n\n\tparamIO(filename, out, \"CameraDistance3D\", myParam.myCamera3D.distance);\n\n\tparamIO(filename, out, \"CameraIsFollowing3D\", myParam.myCamera3D.isFollowing);\n\n\tparamIO(filename, out, \"CameraOffset3DX\", myParam.myCamera3D.offset.x);\n\tparamIO(filename, out, \"CameraOffset3DY\", myParam.myCamera3D.offset.y);\n\tparamIO(filename, out, \"CameraOffset3DZ\", myParam.myCamera3D.offset.z);\n\n\tparamIO(filename, out, \"CameraDefaultCamDist3D\", myParam.myCamera3D.defaultCamDist);\n\n\tparamIO(filename, out, \"CameraPanOffsetRight3DX\", myParam.myCamera3D.panOffsetRight.x);\n\tparamIO(filename, out, \"CameraPanOffsetRight3DY\", myParam.myCamera3D.panOffsetRight.y);\n\tparamIO(filename, out, \"CameraPanOffsetRight3DZ\", myParam.myCamera3D.panOffsetRight.z);\n\n\tparamIO(filename, out, \"CameraPanOffsetUp3DX\", myParam.myCamera3D.panOffsetUp.x);\n\tparamIO(filename, out, \"CameraPanOffsetUp3DY\", myParam.myCamera3D.panOffsetUp.y);\n\tparamIO(filename, out, \"CameraPanOffsetUp3DZ\", myParam.myCamera3D.panOffsetUp.z);\n\n\tparamIO(filename, out, \"CameraCamNormal3DX\", myParam.myCamera3D.camNormal.x);\n\tparamIO(filename, out, \"CameraCamNormal3DY\", myParam.myCamera3D.camNormal.y);\n\tparamIO(filename, out, \"CameraCamNormal3DZ\", myParam.myCamera3D.camNormal.z);\n\n\tparamIO(filename, out, \"CameraCamRight3DX\", myParam.myCamera3D.camRight.x);\n\tparamIO(filename, out, \"CameraCamRight3DY\", myParam.myCamera3D.camRight.y);\n\tparamIO(filename, out, \"CameraCamRight3DZ\", myParam.myCamera3D.camRight.z);\n\n\tparamIO(filename, out, \"CameraCamUp3DX\", myParam.myCamera3D.camUp.x);\n\tparamIO(filename, out, \"CameraCamUp3DY\", myParam.myCamera3D.camUp.y);\n\tparamIO(filename, out, \"CameraCamUp3DZ\", myParam.myCamera3D.camUp.z);\n\n\tparamIO(filename, out, \"CameraWorldUp3DX\", myParam.myCamera3D.worldUp.x);\n\tparamIO(filename, out, \"CameraWorldUp3DY\", myParam.myCamera3D.worldUp.y);\n\tparamIO(filename, out, \"CameraWorldUp3DZ\", myParam.myCamera3D.worldUp.z);\n\n\tparamIO(filename, out, \"CameraFirstPersonPosition3DX\", myParam.myCamera3D.firstPersonPosition.x);\n\tparamIO(filename, out, \"CameraFirstPersonPosition3DY\", myParam.myCamera3D.firstPersonPosition.y);\n\tparamIO(filename, out, \"CameraFirstPersonPosition3DZ\", myParam.myCamera3D.firstPersonPosition.z);\n\n\tparamIO(filename, out, \"CameraArrowMoveSpeed3D\", myParam.myCamera3D.arrowMoveSpeed);\n\n\tparamIO(filename, out, \"CameraAngleX3D\", myParam.myCamera3D.angleX);\n\tparamIO(filename, out, \"CameraAngleY3D\", myParam.myCamera3D.angleY);\n\n\t// ----- Constraints -----\n\tparamIO(filename, out, \"ParticleConstraints\", myVar.constraintsEnabled);\n\tparamIO(filename, out, \"UnbreakableConstraints\", myVar.unbreakableConstraints);\n\tparamIO(filename, out, \"ConstraintAfterDrawing\", myVar.constraintAfterDrawing);\n\tparamIO(filename, out, \"VisualizeConstraints\", myVar.drawConstraints);\n\tparamIO(filename, out, \"VisualizeMesh\", myVar.visualizeMesh);\n\tparamIO(filename, out, \"ConstraintsStressColor\", myVar.constraintStressColor);\n\tparamIO(filename, out, \"MaxConstraintStress\", myVar.constraintMaxStressColor);\n\tparamIO(filename, out, \"ConstraintsStiffMultiplier\", myVar.globalConstraintStiffnessMult);\n\tparamIO(filename, out, \"ConstraintsResistMultiplier\", myVar.globalConstraintResistance);\n\n\t// ----- Optics ----- \n\tparamIO(filename, out, \"Optics\", myVar.isOpticsEnabled);\n\n\t// ----- Optics Colors ----- \n\tparamIO(filename, out, \"LColorR\", lighting.lightColor.r);\n\tparamIO(filename, out, \"LColorG\", lighting.lightColor.g);\n\tparamIO(filename, out, \"LColorB\", lighting.lightColor.b);\n\tparamIO(filename, out, \"LColorA\", lighting.lightColor.a);\n\n\tparamIO(filename, out, \"BaseColorR\", lighting.wallBaseColor.r);\n\tparamIO(filename, out, \"BaseColorG\", lighting.wallBaseColor.g);\n\tparamIO(filename, out, \"BaseColorB\", lighting.wallBaseColor.b);\n\tparamIO(filename, out, \"BaseColorA\", lighting.wallBaseColor.a);\n\n\tparamIO(filename, out, \"SpecularColorR\", lighting.wallSpecularColor.r);\n\tparamIO(filename, out, \"SpecularColorG\", lighting.wallSpecularColor.g);\n\tparamIO(filename, out, \"SpecularColorB\", lighting.wallSpecularColor.b);\n\tparamIO(filename, out, \"SpecularColorA\", lighting.wallSpecularColor.a);\n\n\tparamIO(filename, out, \"RefractionColorR\", lighting.wallRefractionColor.r);\n\tparamIO(filename, out, \"RefractionColorG\", lighting.wallRefractionColor.g);\n\tparamIO(filename, out, \"RefractionColorB\", lighting.wallRefractionColor.b);\n\tparamIO(filename, out, \"RefractionColorA\", lighting.wallRefractionColor.a);\n\n\tparamIO(filename, out, \"EmissionColorR\", lighting.wallEmissionColor.r);\n\tparamIO(filename, out, \"EmissionColorG\", lighting.wallEmissionColor.g);\n\tparamIO(filename, out, \"EmissionColorB\", lighting.wallEmissionColor.b);\n\tparamIO(filename, out, \"EmissionColorA\", lighting.wallEmissionColor.a);\n\n\t// ----- Optics Lights ----- \n\tparamIO(filename, out, \"LightGain\", lighting.lightGain);\n\tparamIO(filename, out, \"LightSpread\", lighting.lightSpread);\n\n\t// ----- Optics Walls ----- \n\tparamIO(filename, out, \"WallSpecularRough\", lighting.wallSpecularRoughness);\n\tparamIO(filename, out, \"WallRefractionRough\", lighting.wallRefractionRoughness);\n\tparamIO(filename, out, \"WallRefractionAmount\", lighting.wallRefractionAmount);\n\tparamIO(filename, out, \"WallIOR\", lighting.wallIOR);\n\tparamIO(filename, out, \"WallDispersion\", lighting.wallDispersion);\n\tparamIO(filename, out, \"WallEmissionGain\", lighting.wallEmissionGain);\n\n\t// ----- Optics Shapes -----\n\tparamIO(filename, out, \"ShapeRelaxIter\", lighting.shapeRelaxIter);\n\tparamIO(filename, out, \"ShapeRelaxFactor\", lighting.shapeRelaxFactor);\n\n\t// ----- Optics Render -----\n\tparamIO(filename, out, \"MaxSamples\", lighting.maxSamples);\n\tparamIO(filename, out, \"RaysPerSample\", lighting.sampleRaysAmount);\n\tparamIO(filename, out, \"MaxBounces\", lighting.maxBounces);\n\n\t// ----- Optics Render Passes -----\n\tparamIO(filename, out, \"GI\", lighting.isDiffuseEnabled);\n\tparamIO(filename, out, \"Specular\", lighting.isSpecularEnabled);\n\tparamIO(filename, out, \"Refraction\", lighting.isRefractionEnabled);\n\tparamIO(filename, out, \"Dispersion\", lighting.isDispersionEnabled);\n\tparamIO(filename, out, \"Emission\", lighting.isEmissionEnabled);\n\n\t// ----- Optics Misc -----\n\tparamIO(filename, out, \"SymmetricalLens\", lighting.symmetricalLens);\n\tparamIO(filename, out, \"ShowNormals\", lighting.drawNormals);\n\tparamIO(filename, out, \"RelaxMove\", lighting.relaxMove);\n\n\t// ----- Gravity Field -----\n\tparamIO(filename, out, \"GravityField\", myVar.isGravityFieldEnabled);\n\tparamIO(filename, out, \"GravityFieldDM\", myVar.gravityFieldDMParticles);\n\tparamIO(filename, out, \"GravityDisplayThreshold\", field.gravityDisplayThreshold);\n\tparamIO(filename, out, \"GravityDisplaySoftness\", field.gravityDisplaySoftness);\n\tparamIO(filename, out, \"GravityDisplayStretch\", field.gravityStretchFactor);\n\tparamIO(filename, out, \"GravityCustomColors\", field.gravityCustomColors);\n\tparamIO(filename, out, \"GravityCustomColExp\", field.gravityExposure);\n\n\t// ----- Field Misc -----\n\tparamIO(filename, out, \"FieldRes\", field.res);\n\n\t/*out << YAML::EndMap;*/\n\n\tstd::string yamlString = out.c_str();\n\n\tstd::fstream file;\n\tif (saveFlag) {\n\t\tstd::fstream file(filename, std::ios::out | std::ios::binary | std::ios::trunc);\n\t\tif (!file.is_open()) {\n\t\t\tstd::cerr << \"Failed to open file for writing: \" << filename << \"\\n\";\n\t\t\treturn;\n\t\t}\n\n\t\tfile.write(yamlString.c_str(), yamlString.size());\n\n\t\tconst char* separator = \"\\n---PARTICLE BINARY DATA---\\n\";\n\t\tfile.write(separator, strlen(separator));\n\n\t\tfile.write(reinterpret_cast<const char*>(&currentVersion), sizeof(currentVersion));\n\n\t\tfile.write(reinterpret_cast<const char*>(&globalId), sizeof(globalId));\n\t\tfile.write(reinterpret_cast<const char*>(&globalShapeId), sizeof(globalShapeId));\n\t\tfile.write(reinterpret_cast<const char*>(&globalWallId), sizeof(globalWallId));\n\n\t\tif (!myVar.is3DMode) {\n\t\t\tuint32_t particleCount = myParam.pParticles.size();\n\t\t\tfile.write(reinterpret_cast<const char*>(&particleCount), sizeof(particleCount));\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\t\tconst ParticlePhysics& p = myParam.pParticles[i];\n\t\t\t\tconst ParticleRendering& r = myParam.rParticles[i];\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.pos), sizeof(p.pos));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.predPos), sizeof(p.predPos));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.vel), sizeof(p.vel));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.prevVel), sizeof(p.prevVel));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.predVel), sizeof(p.predVel));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.acc), sizeof(p.acc));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.mass), sizeof(p.mass));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.press), sizeof(p.press));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.pressTmp), sizeof(p.pressTmp));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.pressF), sizeof(p.pressF));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.dens), sizeof(p.dens));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.predDens), sizeof(p.predDens));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.sphMass), sizeof(p.sphMass));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.restDens), sizeof(p.restDens));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.stiff), sizeof(p.stiff));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.visc), sizeof(p.visc));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.cohesion), sizeof(p.cohesion));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.temp), sizeof(p.temp));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.ke), sizeof(p.ke));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.prevKe), sizeof(p.prevKe));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.mortonKey), sizeof(p.mortonKey));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.id), sizeof(p.id));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.isHotPoint), sizeof(p.isHotPoint));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.hasSolidified), sizeof(p.hasSolidified));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.color), sizeof(r.color));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.pColor), sizeof(r.pColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.sColor), sizeof(r.sColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.sphColor), sizeof(r.sphColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.size), sizeof(r.size));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.uniqueColor), sizeof(r.uniqueColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isSolid), sizeof(r.isSolid));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.canBeSubdivided), sizeof(r.canBeSubdivided));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.canBeResized), sizeof(r.canBeResized));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isDarkMatter), sizeof(r.isDarkMatter));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isSPH), sizeof(r.isSPH));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isSelected), sizeof(r.isSelected));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isGrabbed), sizeof(r.isGrabbed));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.previousSize), sizeof(r.previousSize));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.neighbors), sizeof(r.neighbors));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.totalRadius), sizeof(r.totalRadius));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.lifeSpan), sizeof(r.lifeSpan));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.sphLabel), sizeof(r.sphLabel));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isPinned), sizeof(r.isPinned));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isBeingDrawn), sizeof(r.isBeingDrawn));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.spawnCorrectIter), sizeof(r.spawnCorrectIter));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.turbulence), sizeof(r.turbulence));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tuint32_t particleCount = myParam.pParticles3D.size();\n\t\t\tfile.write(reinterpret_cast<const char*>(&particleCount), sizeof(particleCount));\n\n\t\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\t\tconst ParticlePhysics3D& p = myParam.pParticles3D[i];\n\t\t\t\tconst ParticleRendering3D& r = myParam.rParticles3D[i];\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.pos), sizeof(p.pos));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.predPos), sizeof(p.predPos));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.vel), sizeof(p.vel));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.prevVel), sizeof(p.prevVel));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.predVel), sizeof(p.predVel));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.acc), sizeof(p.acc));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.mass), sizeof(p.mass));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.press), sizeof(p.press));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.pressTmp), sizeof(p.pressTmp));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.pressF), sizeof(p.pressF));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.dens), sizeof(p.dens));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.predDens), sizeof(p.predDens));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.sphMass), sizeof(p.sphMass));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.restDens), sizeof(p.restDens));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.stiff), sizeof(p.stiff));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.visc), sizeof(p.visc));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.cohesion), sizeof(p.cohesion));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.temp), sizeof(p.temp));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.ke), sizeof(p.ke));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.prevKe), sizeof(p.prevKe));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.mortonKey), sizeof(p.mortonKey));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.id), sizeof(p.id));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.isHotPoint), sizeof(p.isHotPoint));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&p.hasSolidified), sizeof(p.hasSolidified));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.color), sizeof(r.color));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.pColor), sizeof(r.pColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.sColor), sizeof(r.sColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.sphColor), sizeof(r.sphColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.size), sizeof(r.size));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.uniqueColor), sizeof(r.uniqueColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isSolid), sizeof(r.isSolid));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.canBeSubdivided), sizeof(r.canBeSubdivided));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.canBeResized), sizeof(r.canBeResized));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isDarkMatter), sizeof(r.isDarkMatter));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isSPH), sizeof(r.isSPH));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isSelected), sizeof(r.isSelected));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isGrabbed), sizeof(r.isGrabbed));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.previousSize), sizeof(r.previousSize));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.neighbors), sizeof(r.neighbors));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.totalRadius), sizeof(r.totalRadius));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.lifeSpan), sizeof(r.lifeSpan));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.sphLabel), sizeof(r.sphLabel));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isPinned), sizeof(r.isPinned));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.isBeingDrawn), sizeof(r.isBeingDrawn));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.spawnCorrectIter), sizeof(r.spawnCorrectIter));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&r.turbulence), sizeof(r.turbulence));\n\t\t\t}\n\t\t}\n\n\t\tif (!myVar.is3DMode) {\n\t\t\tuint32_t numConstraints = physics.particleConstraints.size();\n\t\t\tfile.write(reinterpret_cast<const char*>(&numConstraints), sizeof(numConstraints));\n\t\t\tif (numConstraints > 0) {\n\t\t\t\tfile.write(\n\t\t\t\t\treinterpret_cast<const char*>(physics.particleConstraints.data()),\n\t\t\t\t\tnumConstraints * sizeof(ParticleConstraint)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tuint32_t numConstraints = physics3D.particleConstraints.size();\n\t\t\tfile.write(reinterpret_cast<const char*>(&numConstraints), sizeof(numConstraints));\n\t\t\tif (numConstraints > 0) {\n\t\t\t\tfile.write(\n\t\t\t\t\treinterpret_cast<const char*>(physics3D.particleConstraints.data()),\n\t\t\t\t\tnumConstraints * sizeof(ParticleConstraint)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tuint32_t wallCount = lighting.walls.size();\n\t\tfile.write(reinterpret_cast<const char*>(&wallCount), sizeof(wallCount));\n\n\t\tfor (size_t i = 0; i < lighting.walls.size(); i++) {\n\t\t\tconst Wall& w = lighting.walls[i];\n\n\t\t\t// Write all glm::vec2 fields\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.vA), sizeof(w.vA));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.vB), sizeof(w.vB));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.normal), sizeof(w.normal));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.normalVA), sizeof(w.normalVA));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.normalVB), sizeof(w.normalVB));\n\n\t\t\t// Write booleans\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.isBeingSpawned), sizeof(w.isBeingSpawned));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.vAisBeingMoved), sizeof(w.vAisBeingMoved));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.vBisBeingMoved), sizeof(w.vBisBeingMoved));\n\n\t\t\t// Write Color structs\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.apparentColor), sizeof(w.apparentColor));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.baseColor), sizeof(w.baseColor));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.specularColor), sizeof(w.specularColor));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.refractionColor), sizeof(w.refractionColor));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.emissionColor), sizeof(w.emissionColor));\n\n\t\t\t// Write float color values\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.baseColorVal), sizeof(w.baseColorVal));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.specularColorVal), sizeof(w.specularColorVal));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.refractionColorVal), sizeof(w.refractionColorVal));\n\n\t\t\t// Write material floats\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.specularRoughness), sizeof(w.specularRoughness));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.refractionRoughness), sizeof(w.refractionRoughness));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.refractionAmount), sizeof(w.refractionAmount));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.IOR), sizeof(w.IOR));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.dispersionStrength), sizeof(w.dispersionStrength));\n\n\t\t\t// Write shape metadata\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.isShapeWall), sizeof(w.isShapeWall));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.isShapeClosed), sizeof(w.isShapeClosed));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.shapeId), sizeof(w.shapeId));\n\n\t\t\t// Write wall ID and selection flag\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.id), sizeof(w.id));\n\t\t\tfile.write(reinterpret_cast<const char*>(&w.isSelected), sizeof(w.isSelected));\n\t\t}\n\n\t\tuint32_t numShapes = lighting.shapes.size();\n\t\tfile.write(reinterpret_cast<const char*>(&numShapes), sizeof(numShapes));\n\t\tif (numShapes > 0) {\n\t\t\tfor (const Shape& s : lighting.shapes) {\n\t\t\t\tuint32_t wallIdCount = s.myWallIds.size();\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&wallIdCount), sizeof(wallIdCount));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(s.myWallIds.data()), wallIdCount * sizeof(uint32_t));\n\n\t\t\t\tuint32_t vertCount = s.polygonVerts.size();\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&vertCount), sizeof(vertCount));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(s.polygonVerts.data()), vertCount * sizeof(glm::vec2));\n\n\t\t\t\tuint32_t helpersCount = s.helpers.size();\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&helpersCount), sizeof(helpersCount));\n\t\t\t\tif (helpersCount > 0) {\n\t\t\t\t\tfile.write(reinterpret_cast<const char*>(s.helpers.data()), helpersCount * sizeof(glm::vec2));\n\t\t\t\t}\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.baseColor), sizeof(s.baseColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.specularColor), sizeof(s.specularColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.refractionColor), sizeof(s.refractionColor));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.emissionColor), sizeof(s.emissionColor));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.specularRoughness), sizeof(s.specularRoughness));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.refractionRoughness), sizeof(s.refractionRoughness));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.refractionAmount), sizeof(s.refractionAmount));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.IOR), sizeof(s.IOR));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.dispersionStrength), sizeof(s.dispersionStrength));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.id), sizeof(s.id));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.h1), sizeof(s.h1));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.h2), sizeof(s.h2));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.isBeingSpawned), sizeof(s.isBeingSpawned));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.isBeingMoved), sizeof(s.isBeingMoved));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.isShapeClosed), sizeof(s.isShapeClosed));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.shapeType), sizeof(s.shapeType));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.drawHoverHelpers), sizeof(s.drawHoverHelpers));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.oldDrawHelperPos), sizeof(s.oldDrawHelperPos));\n\n\t\t\t\t// Lens variables\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.secondHelper), sizeof(s.secondHelper));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.thirdHelper), sizeof(s.thirdHelper));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.fourthHelper), sizeof(s.fourthHelper));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.Tempsh2Length), sizeof(s.Tempsh2Length));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.Tempsh2LengthSymmetry), sizeof(s.Tempsh2LengthSymmetry));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.tempDist), sizeof(s.tempDist));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.moveH2), sizeof(s.moveH2));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.isThirdBeingMoved), sizeof(s.isThirdBeingMoved));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.isFourthBeingMoved), sizeof(s.isFourthBeingMoved));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.isFifthBeingMoved), sizeof(s.isFifthBeingMoved));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.isFifthFourthMoved), sizeof(s.isFifthFourthMoved));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.symmetricalLens), sizeof(s.symmetricalLens));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.wallAId), sizeof(s.wallAId));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.wallBId), sizeof(s.wallBId));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.wallCId), sizeof(s.wallCId));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.lensSegments), sizeof(s.lensSegments));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.startAngle), sizeof(s.startAngle));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.endAngle), sizeof(s.endAngle));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.startAngleSymmetry), sizeof(s.startAngleSymmetry));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.endAngleSymmetry), sizeof(s.endAngleSymmetry));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.center), sizeof(s.center));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.radius), sizeof(s.radius));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.centerSymmetry), sizeof(s.centerSymmetry));\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.radiusSymmetry), sizeof(s.radiusSymmetry));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.arcEnd), sizeof(s.arcEnd));\n\n\t\t\t\tfile.write(reinterpret_cast<const char*>(&s.globalLensPrev), sizeof(s.globalLensPrev));\n\t\t\t}\n\t\t}\n\n\t\tuint32_t pointLightCount = lighting.pointLights.size();\n\t\tfile.write(reinterpret_cast<const char*>(&pointLightCount), sizeof(pointLightCount));\n\n\t\tfor (const PointLight& pl : lighting.pointLights) {\n\t\t\tfile.write(reinterpret_cast<const char*>(&pl.pos), sizeof(pl.pos));\n\t\t\tfile.write(reinterpret_cast<const char*>(&pl.isBeingMoved), sizeof(pl.isBeingMoved));\n\t\t\tfile.write(reinterpret_cast<const char*>(&pl.color), sizeof(pl.color));\n\t\t\tfile.write(reinterpret_cast<const char*>(&pl.apparentColor), sizeof(pl.apparentColor));\n\t\t\tfile.write(reinterpret_cast<const char*>(&pl.isSelected), sizeof(pl.isSelected));\n\t\t}\n\n\t\tuint32_t areaLightCount = lighting.areaLights.size();\n\t\tfile.write(reinterpret_cast<const char*>(&areaLightCount), sizeof(areaLightCount));\n\n\t\tfor (const AreaLight& al : lighting.areaLights) {\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.vA), sizeof(al.vA));\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.vB), sizeof(al.vB));\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.isBeingSpawned), sizeof(al.isBeingSpawned));\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.vAisBeingMoved), sizeof(al.vAisBeingMoved));\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.vBisBeingMoved), sizeof(al.vBisBeingMoved));\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.color), sizeof(al.color));\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.apparentColor), sizeof(al.apparentColor));\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.isSelected), sizeof(al.isSelected));\n\t\t\tfile.write(reinterpret_cast<const char*>(&al.spread), sizeof(al.spread));\n\t\t}\n\n\t\tuint32_t coneLightCount = lighting.coneLights.size();\n\t\tfile.write(reinterpret_cast<const char*>(&coneLightCount), sizeof(coneLightCount));\n\n\t\tfor (const ConeLight& cl : lighting.coneLights) {\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.vA), sizeof(cl.vA));\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.vB), sizeof(cl.vB));\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.isBeingSpawned), sizeof(cl.isBeingSpawned));\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.vAisBeingMoved), sizeof(cl.vAisBeingMoved));\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.vBisBeingMoved), sizeof(cl.vBisBeingMoved));\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.color), sizeof(cl.color));\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.apparentColor), sizeof(cl.apparentColor));\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.isSelected), sizeof(cl.isSelected));\n\t\t\tfile.write(reinterpret_cast<const char*>(&cl.spread), sizeof(cl.spread));\n\t\t}\n\n\t\tfile.close();\n\t}\n\n\tdeserializeParticleSystem(filename, yamlString, myVar, myParam, sph, physics, physics3D, lighting, loadFlag);\n}\n\nvoid SaveSystem::saveLoadLogic(UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, Physics& physics, Physics3D& physics3D, Lighting& lighting, Field& field) {\n\tif (saveFlag) {\n\t\tif (!std::filesystem::exists(\"Saves\")) {\n\t\t\tstd::filesystem::create_directory(\"Saves\");\n\t\t}\n\n\t\tint nextAvailableIndex = 0;\n\t\tfor (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(\"Saves\")) {\n\n\t\t\tstd::string filename = entry.path().filename().string();\n\t\t\tif (filename.rfind(\"Save_\", 0) == 0 && filename.find(\".bin\") != std::string::npos) {\n\n\t\t\t\tsize_t startPos = filename.find_last_of('_') + 1;\n\t\t\t\tsize_t endPos = filename.find(\".bin\");\n\t\t\t\tint index = std::stoi(filename.substr(startPos, endPos - startPos));\n\n\t\t\t\tif (index >= nextAvailableIndex) {\n\t\t\t\t\tnextAvailableIndex = index + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tstd::string savePath = \"Saves/Save_\" + std::to_string(nextAvailableIndex) + \".bin\";\n\n\t\tsaveSystem(savePath.c_str(), myVar, myParam, sph, physics, physics3D, lighting, field);\n\n\t\tsaveIndex++;\n\n\t\tsaveFlag = false;\n\t}\n\n\tif (loadFlag) {\n\n\t\tint fileIndex = 1;\n\n\t\tfilePaths.clear();\n\n\t\tif (!std::filesystem::exists(\"Saves\")) {\n\t\t\tstd::filesystem::create_directory(\"Saves\");\n\t\t\tstd::cout << \"Created Saves directory as it did not exist\" << std::endl;\n\t\t\tloadFlag = false;\n\t\t\treturn;\n\t\t}\n\n\t\tstd::vector<std::pair<std::string, std::string>> files;\n\t\tfor (const auto& entry : std::filesystem::recursive_directory_iterator(\"Saves\")) {\n\t\t\tif (entry.is_regular_file()) {\n\t\t\t\tconst auto filename = entry.path().filename().string();\n\t\t\t\tconst auto fullpath = entry.path().string();\n\t\t\t\tif (filename.rfind(\".bin\") != std::string::npos) {\n\t\t\t\t\tstd::string relativePath = fullpath.substr(6);\n\t\t\t\t\tfiles.emplace_back(relativePath, fullpath);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (files.empty()) {\n\t\t\tstd::cout << \"No .bin files found in Saves directory\" << std::endl;\n\t\t\tloadFlag = false;\n\t\t\treturn;\n\t\t}\n\n\t\tstd::sort(files.begin(), files.end(),\n\t\t\t[](auto& A, auto& B) {\n\t\t\t\tconst std::string& nameA = A.first;\n\t\t\t\tconst std::string& nameB = B.first;\n\n\t\t\t\tauto isDefault = [](const std::string& s) {\n\t\t\t\t\tconst std::string key = \"DefaultSettings.bin\";\n\n\t\t\t\t\treturn s.size() >= key.size()\n\t\t\t\t\t\t&& s.compare(s.size() - key.size(), key.size(), key) == 0;\n\t\t\t\t\t};\n\t\t\t\tbool aIsDefault = isDefault(nameA);\n\t\t\t\tbool bIsDefault = isDefault(nameB);\n\t\t\t\tif (aIsDefault != bIsDefault) {\n\t\t\t\t\treturn aIsDefault;\n\t\t\t\t}\n\n\t\t\t\tauto getDir = [](const std::string& s) {\n\t\t\t\t\tsize_t pos = s.find_last_of(\"\\\\/\");\n\t\t\t\t\treturn (pos == std::string::npos) ? std::string{} : s.substr(0, pos);\n\t\t\t\t\t};\n\t\t\t\tstd::string dirA = getDir(nameA);\n\t\t\t\tstd::string dirB = getDir(nameB);\n\t\t\t\tif (dirA != dirB) {\n\t\t\t\t\treturn dirA < dirB;\n\t\t\t\t}\n\n\t\t\t\tauto extractNumber = [](const std::string& s) -> int {\n\t\t\t\t\tsize_t i = 0;\n\t\t\t\t\twhile (i < s.size() && !std::isdigit(s[i])) ++i;\n\t\t\t\t\tsize_t j = i;\n\t\t\t\t\twhile (j < s.size() && std::isdigit(s[j])) ++j;\n\t\t\t\t\tif (i < j) {\n\t\t\t\t\t\ttry { return std::stoi(s.substr(i, j - i)); }\n\t\t\t\t\t\tcatch (...) {}\n\t\t\t\t\t}\n\t\t\t\t\treturn -1;\n\t\t\t\t\t};\n\t\t\t\tint numA = extractNumber(nameA);\n\t\t\t\tint numB = extractNumber(nameB);\n\t\t\t\tif (numA >= 0 && numB >= 0 && numA != numB) {\n\t\t\t\t\treturn numA < numB;\n\t\t\t\t}\n\t\t\t\treturn nameA < nameB;\n\t\t\t}\n\t\t);\n\n\t\tImGui::SetNextWindowSize(loadMenuSize, ImGuiCond_Once);\n\t\tImGui::SetNextWindowPos(ImVec2(static_cast<float>(GetScreenWidth()) * 0.5f - loadMenuSize.x * 0.5f, 450.0f), ImGuiCond_Once);\n\t\tImGui::Begin(\"Files\");\n\n\t\tfor (const auto& [filename, fullPath] : files) {\n\n\t\t\tbool placeHolder = false;\n\t\t\tbool enabled = true;\n\n\t\t\tif (UI::buttonHelper(fullPath.c_str(), \"Select scene file\", placeHolder, ImGui::GetContentRegionAvail().x, buttonHeight, enabled, enabled)) {\n\t\t\t\tsaveSystem(fullPath.c_str(), myVar, myParam, sph, physics, physics3D, lighting, field);\n\t\t\t\tloadFlag = false;\n\t\t\t\tmyVar.playbackRecord = false;\n\t\t\t}\n\n\t\t\tfilePaths.push_back(fullPath);\n\t\t\tfileIndex++;\n\t\t}\n\n\t\tImGui::End();\n\t}\n}"
  },
  {
    "path": "GalaxyEngine/src/UX/screenCapture.cpp",
    "content": "#include \"UI/UI.h\"\n\n#include \"UX/screenCapture.h\"\n\n#include \"parameters.h\"\n\nextern \"C\" {\n#include <libavcodec/avcodec.h>\n#include <libavformat/avformat.h>\n#include <libavutil/avutil.h>\n#include <libavutil/imgutils.h>\n#include <libavutil/opt.h>\n#include <libswscale/swscale.h>\n}\n\nvoid ScreenCapture::cleanupFFmpeg() {\n\tif (pCodecCtx) {\n\t\tavcodec_send_frame(pCodecCtx, nullptr);\n\n\t\tAVPacket* pkt = av_packet_alloc();\n\t\tif (pkt) {\n\t\t\twhile (avcodec_receive_packet(pCodecCtx, pkt) == 0) {\n\t\t\t\tav_packet_unref(pkt);\n\t\t\t}\n\t\t\tav_packet_free(&pkt);\n\t\t}\n\t}\n\n\tif (swsCtx) {\n\t\tsws_freeContext(swsCtx);\n\t\tswsCtx = nullptr;\n\t}\n\n\tif (frame) {\n\t\tav_frame_free(&frame);\n\t\tframe = nullptr;\n\t}\n\n\tif (pCodecCtx) {\n\t\tavcodec_free_context(&pCodecCtx);\n\t\tpCodecCtx = nullptr;\n\t}\n\n\tif (pFormatCtx) {\n\t\tif (pFormatCtx->oformat && !(pFormatCtx->oformat->flags & AVFMT_NOFILE)) {\n\t\t\tavio_closep(&pFormatCtx->pb);\n\t\t}\n\t\tavformat_free_context(pFormatCtx);\n\t\tpFormatCtx = nullptr;\n\t}\n\n\tpStream = nullptr;\n\tframeIndex = 0;\n}\n\nvoid ScreenCapture::exportFrameToFile(const Image& frame,\n\tconst std::string& videoFolder,\n\tconst std::string& videoName,\n\tint frameNumber) {\n\n\tif (!std::filesystem::exists(videoFolder)) {\n\t\tprintf(\"Warning: Frame export folder does not exist: %s\\n\",\n\t\t\tvideoFolder.c_str());\n\t\treturn;\n\t}\n\n\n\tImage frameCopy = ImageCopy(frame);\n\tImageFormat(&frameCopy, PIXELFORMAT_UNCOMPRESSED_R8G8B8);\n\tImageFlipVertical(&frameCopy);\n\n\tstd::string filename = videoFolder + \"/\" + videoName + \"_\" +\n\t\tstd::to_string(frameNumber) + \".png\";\n\n\ttry {\n\t\tExportImage(frameCopy, filename.c_str());\n\t}\n\tcatch (...) {\n\t\tprintf(\"Error: Failed to export frame to: %s\\n\", filename.c_str());\n\t}\n\n\tUnloadImage(frameCopy);\n}\n\nvoid ScreenCapture::exportMemoryFramesToDisk() {\n\tif (myFrames.empty()) {\n\t\tprintf(\"No frames in memory to export.\\n\");\n\t\treturn;\n\t}\n\n\tif (actualSavedVideoFolder.empty() || actualSavedVideoName.empty()) {\n\t\tprintf(\"Error: No saved video information available for frame \"\n\t\t\t\"export.\\n\");\n\t\treturn;\n\t}\n\tcreateFramesFolder(actualSavedVideoFolder);\n\tprintf(\"Exporting %d frames to %s with base name %s...\\n\",\n\t\tstatic_cast<int>(myFrames.size()), actualSavedVideoFolder.c_str(),\n\t\tactualSavedVideoName.c_str());\n\n\tauto startTime = std::chrono::high_resolution_clock::now();\n\n\tint exportedCount = 0;\n\tconst int totalFrames = static_cast<int>(myFrames.size());\n\n#pragma omp parallel for reduction(+ : exportedCount) schedule(static)\n\tfor (int i = 0; i < totalFrames; ++i) {\n\t\ttry {\n\t\t\texportFrameToFile(myFrames[i], actualSavedVideoFolder,\n\t\t\t\tactualSavedVideoName, i);\n\t\t\texportedCount++;\n\n\t\t\tif (i % 100 == 0) {\n#pragma omp critical\n\t\t\t\t{\n\t\t\t\t\tprintf(\"Exported frame %d/%d (%.1f%%)\\n\", i + 1, totalFrames,\n\t\t\t\t\t\t(static_cast<float>(i + 1) / totalFrames) * 100.0f);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcatch (...) {\n#pragma omp critical\n\t\t\t{\n\t\t\t\tprintf(\"Warning: Failed to export frame %d\\n\", i);\n\t\t\t}\n\t\t}\n\t}\n\n\tauto endTime = std::chrono::high_resolution_clock::now();\n\tauto duration = std::chrono::duration_cast<std::chrono::milliseconds>(\n\t\tendTime - startTime);\n\n\tprintf(\"Successfully exported %d out of %d frames in %.2f seconds.\\n\",\n\t\texportedCount, static_cast<int>(myFrames.size()),\n\t\tstatic_cast<double>(duration.count()) / 1000.0);\n}\n\nvoid ScreenCapture::discardMemoryFrames() {\n\tif (myFrames.empty()) {\n\t\tprintf(\"No frames in memory to discard.\\n\");\n\t\treturn;\n\t}\n\n\tprintf(\"Discarding %d frames from memory...\\n\",\n\t\tstatic_cast<int>(myFrames.size()));\n\n\tfor (Image& frame : myFrames) {\n\t\tUnloadImage(frame);\n\t}\n\tmyFrames.clear();\n\tstd::vector<Image>().swap(myFrames);\n\n\tprintf(\"All frames discarded from memory.\\n\");\n}\n\nvoid ScreenCapture::createFramesFolder(const std::string& folderPath) {\n\tif (!std::filesystem::exists(folderPath)) {\n\t\ttry {\n\t\t\tstd::filesystem::create_directories(folderPath);\n\t\t}\n\t\tcatch (const std::exception& e) {\n\t\t\tprintf(\"Error creating folder %s: %s\\n\", folderPath.c_str(), e.what());\n\t\t}\n\t}\n}\n\nvoid ScreenCapture::discardRecording() {\n\t// Clean up video folder and any files if it was created\n\tif (!this->videoFolder.empty()) {\n\t\ttry {\n\t\t\t// Delete individual frame files if safe frames mode was enabled\n\t\t\tif (isSafeFramesEnabled && isExportFramesEnabled && !this->folderName.empty()) {\n\t\t\t\tstd::string framePrefix = this->folderName + \"_frame_\";\n\t\t\t\tint deletedFrameCount = 0;\n\n\t\t\t\tif (std::filesystem::exists(this->videoFolder)) {\n\t\t\t\t\tfor (const auto& entry : std::filesystem::directory_iterator(this->videoFolder)) {\n\t\t\t\t\t\tif (entry.is_regular_file()) {\n\t\t\t\t\t\t\tstd::string frameFileName = entry.path().filename().string();\n\t\t\t\t\t\t\tif (frameFileName.rfind(framePrefix, 0) == 0 &&\n\t\t\t\t\t\t\t\tframeFileName.length() > framePrefix.length() &&\n\t\t\t\t\t\t\t\tframeFileName.substr(frameFileName.length() - 4) == \".png\") {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tstd::filesystem::remove(entry.path());\n\t\t\t\t\t\t\t\t\tdeletedFrameCount++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcatch (const std::filesystem::filesystem_error& e_frame) {\n\t\t\t\t\t\t\t\t\tprintf(\"Warning: Failed to delete frame %s: %s\\n\",\n\t\t\t\t\t\t\t\t\t\tentry.path().string().c_str(), e_frame.what());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (deletedFrameCount > 0) {\n\t\t\t\t\t\tprintf(\"Deleted %d frame files from cancelled recording\\n\", deletedFrameCount);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove the entire video folder since recording was cancelled\n\t\t\tif (std::filesystem::exists(this->videoFolder)) {\n\t\t\t\ttry {\n\t\t\t\t\tstd::filesystem::remove_all(this->videoFolder);\n\t\t\t\t\tprintf(\"Removed video folder: %s\\n\", this->videoFolder.c_str());\n\t\t\t\t}\n\t\t\t\tcatch (const std::filesystem::filesystem_error& e_folder) {\n\t\t\t\t\tprintf(\"Warning: Failed to remove video folder %s: %s\\n\",\n\t\t\t\t\t\tthis->videoFolder.c_str(), e_folder.what());\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\tcatch (const std::filesystem::filesystem_error& e_dir) {\n\t\t\tprintf(\"Warning: Failed to access video folder for cleanup: %s\\n\", e_dir.what());\n\t\t}\n\t}\n}\n\nstd::string ScreenCapture::generateVideoFilename() {\n\tint maxNumberFound = 0;\n\tconst std::string prefix = \"Video_\";\n\n\tif (std::filesystem::exists(\"Videos\")) {\n\t\tfor (const auto& entry : std::filesystem::directory_iterator(\"Videos\")) {\n\t\t\tif (entry.is_directory()) {\n\t\t\t\tstd::string folderName = entry.path().filename().string();\n\n\t\t\t\tif (folderName.compare(0, prefix.size(), prefix) == 0) {\n\t\t\t\t\tconst char* numberPart = folderName.c_str() + prefix.size();\n\t\t\t\t\tchar* endPtr = nullptr;\n\n\t\t\t\t\tdouble value = std::strtod(numberPart, &endPtr);\n\n\t\t\t\t\tif (endPtr != numberPart && *endPtr == '\\0') {\n\t\t\t\t\t\tint number = static_cast<int>(value);\n\t\t\t\t\t\tif (number > maxNumberFound) {\n\t\t\t\t\t\t\tmaxNumberFound = number;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tint nextAvailableNumber = maxNumberFound + 1;\n\tstd::string videoName = prefix + std::to_string(nextAvailableNumber);\n\treturn \"Videos/\" + videoName + \"/\" + videoName + \".mp4\";\n}\n\nbool ScreenCapture::screenGrab(RenderTexture2D& myParticlesTexture,\n\tUpdateVariables& myVar,\n\tUpdateParameters& myParam) {\n\n\tif (IO::shortcutPress(KEY_S)) {\n\t\tif (!std::filesystem::exists(\"Screenshots\")) {\n\t\t\tstd::filesystem::create_directory(\"Screenshots\");\n\t\t}\n\n\t\tint nextAvailableIndex = 0;\n\t\tfor (const std::filesystem::directory_entry& entry :\n\t\t\tstd::filesystem::directory_iterator(\"Screenshots\")) {\n\n\t\t\tstd::string filename = entry.path().filename().string();\n\t\t\tif (filename.rfind(\"Screenshot_\", 0) == 0 &&\n\t\t\t\tfilename.find(\".png\") != std::string::npos) {\n\n\t\t\t\tsize_t startPos = filename.find_last_of('_') + 1;\n\t\t\t\tsize_t endPos = filename.find(\".png\");\n\t\t\t\tint index = std::stoi(filename.substr(startPos, endPos - startPos));\n\n\t\t\t\tif (index >= nextAvailableIndex) {\n\t\t\t\t\tnextAvailableIndex = index + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tRenderTexture2D screenshotTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());\n\t\tBeginTextureMode(screenshotTexture);\n\t\tClearBackground(BLACK);\n\t\tDrawTexture(myParticlesTexture.texture, 0, 0, WHITE);\n\t\tEndTextureMode();\n\n\t\tImage renderImage = LoadImageFromTexture(screenshotTexture.texture);\n\n\t\tImageFormat(&renderImage, PIXELFORMAT_UNCOMPRESSED_R8G8B8);\n\n\t\tstd::string screenshotPath =\n\t\t\t\"Screenshots/Screenshot_\" + std::to_string(nextAvailableIndex) + \".png\";\n\t\tExportImage(renderImage, screenshotPath.c_str());\n\n\t\tUnloadImage(renderImage);\n\t\tUnloadRenderTexture(screenshotTexture);\n\t\tscreenshotIndex++;\n\t}\n\n\tif (IO::shortcutPress(KEY_R) && !showSaveConfirmationDialog) {\n\t\tif (!isFunctionRecording && !isSafeFramesEnabled) {\n\t\t\tfor (Image& frame : myFrames) {\n\t\t\t\tUnloadImage(frame);\n\t\t\t}\n\t\t\tmyFrames.clear();\n\t\t\tstd::vector<Image>().swap(myFrames);\n\t\t}\n\t\tif (!isFunctionRecording && isVideoExportEnabled) {\n\n\t\t\tdiskModeFrameIdx = 0;\n\n\t\t\tif (!std::filesystem::exists(\"Videos\")) {\n\t\t\t\tstd::filesystem::create_directory(\"Videos\");\n\t\t\t}\n\n\t\t\toutFileName = generateVideoFilename();\n\n\t\t\tsize_t lastSlash = outFileName.find_last_of('/');\n\t\t\tthis->videoFolder = outFileName.substr(0, lastSlash);\n\t\t\tif (!std::filesystem::exists(this->videoFolder)) {\n\t\t\t\tstd::filesystem::create_directories(this->videoFolder);\n\t\t\t}\n\n\t\t\tsize_t secondToLastSlash = outFileName.find_last_of('/', lastSlash - 1);\n\t\t\tif (secondToLastSlash != std::string::npos) {\n\t\t\t\tfolderName = outFileName.substr(secondToLastSlash + 1,\n\t\t\t\t\tlastSlash - secondToLastSlash - 1);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfolderName = \"Video_1\";\n\t\t\t}\n\t\t}\n\n\t\tif (!isFunctionRecording) {\n\t\t\tint w = GetScreenWidth();\n\t\t\tint h = GetScreenHeight();\n\n\t\t\tif (avformat_alloc_output_context2(&pFormatCtx, nullptr, nullptr,\n\t\t\t\toutFileName.c_str()) < 0) {\n\t\t\t\tprintf(\"Could not alloc output context\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);\n\t\t\tif (!codec) {\n\t\t\t\tprintf(\"H.264 codec not found\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tpStream = avformat_new_stream(pFormatCtx, codec);\n\t\t\tpCodecCtx = avcodec_alloc_context3(codec);\n\t\t\tpCodecCtx->codec_id = AV_CODEC_ID_H264;\n\t\t\tpCodecCtx->width = w;\n\t\t\tpCodecCtx->height = h;\n\t\t\tpCodecCtx->time_base = AVRational{ 1, 60 };\n\t\t\tpCodecCtx->framerate = AVRational{ 24, 1 };\n\t\t\tpCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;\n\t\t\tpCodecCtx->bit_rate = 256 * 1000 * 1000;\n\t\t\tpCodecCtx->gop_size = 12;\n\t\t\tav_opt_set(pCodecCtx->priv_data, \"preset\", \"medium\", 0);\n\t\t\tav_opt_set(pCodecCtx->priv_data, \"crf\", \"23\", 0);\n\n\t\t\tif (avcodec_open2(pCodecCtx, codec, nullptr) < 0) {\n\t\t\t\tprintf(\"Could not open codec\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tavcodec_parameters_from_context(pStream->codecpar, pCodecCtx);\n\n\t\t\tif (!(pFormatCtx->oformat->flags & AVFMT_NOFILE)) {\n\t\t\t\tif (avio_open(&pFormatCtx->pb, outFileName.c_str(), AVIO_FLAG_WRITE) <\n\t\t\t\t\t0) {\n\t\t\t\t\tprintf(\"Could not open output file\\n\");\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (avformat_write_header(pFormatCtx, nullptr) < 0) {\n\t\t\t\tprintf(\"Could not write header\\n\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tframe = av_frame_alloc();\n\t\t\tframe->format = pCodecCtx->pix_fmt;\n\t\t\tframe->width = w;\n\t\t\tframe->height = h;\n\t\t\tav_frame_get_buffer(frame, 0);\n\n\t\t\tswsCtx = sws_getContext(w, h, AV_PIX_FMT_RGBA, w, h, AV_PIX_FMT_YUV420P,\n\t\t\t\tSWS_BILINEAR, nullptr, nullptr, nullptr);\n\t\t\tprintf(\"Started recording to '%s'\\n\", outFileName.c_str());\n\t\t\tisFunctionRecording = true;\n\t\t\tframeIndex = 0;\n\n\t\t\tvideoHasBeenSaved = false;\n\t\t\tactualSavedVideoFolder.clear();\n\t\t\tactualSavedVideoName.clear();\n\n\t\t\treturn true;\n\t\t}\n\t\telse {\n\t\t\tav_write_trailer(pFormatCtx);\n\t\t\tcleanupFFmpeg();\n\t\t\tisFunctionRecording = false;\n\n\t\t\tlastVideoPath = outFileName;\n\t\t\tshowSaveConfirmationDialog = true;\n\n\t\t\tif (myVar.pauseAfterRecording) {\n\t\t\t\tmyVar.isTimePlaying = false;\n\t\t\t}\n\t\t\tif (myVar.cleanSceneAfterRecording) {\n\t\t\t\tmyParam.pParticles.clear();\n\t\t\t\tmyParam.rParticles.clear();\n\t\t\t}\n\n\t\t\tprintf(\"Stopped recording. File saved as '%s'\\\\n\", outFileName.c_str());\n\t\t}\n\t}  if (cancelRecording && isFunctionRecording) {\n\t\tcleanupFFmpeg();\n\t\tisFunctionRecording = false;\n\n\t\tif (std::filesystem::exists(outFileName)) {\n\t\t\ttry {\n\t\t\t\tstd::filesystem::remove(outFileName);\n\t\t\t\tprintf(\"Recording cancelled and file deleted: \"\n\t\t\t\t\t\"%s\\n\",\n\t\t\t\t\toutFileName.c_str());\n\t\t\t}\n\t\t\tcatch (const std::exception& e) {\n\t\t\t\tprintf(\"Warning: Failed to delete cancelled \"\n\t\t\t\t\t\"recording: %s\\n\",\n\t\t\t\t\te.what());\n\t\t\t}\n\t\t}\n\n\t\t// Clean up frame files and folder if safe frames mode was enabled\n\t\tdiscardRecording();\n\n\t\tfor (Image& frameImg : myFrames) {\n\t\t\tUnloadImage(frameImg);\n\t\t}\n\t\tmyFrames.clear();\n\t\tstd::vector<Image>().swap(myFrames);\n\n\t\tdiskModeFrameIdx = 0;\n\n\t\tvideoHasBeenSaved = false;\n\t\tactualSavedVideoFolder.clear();\n\t\tactualSavedVideoName.clear();\n\n\t\t// Clear recording state variables\n\t\tlastVideoPath.clear();\n\t\tthis->videoFolder.clear();\n\t\tthis->folderName.clear();\n\n\t\tcancelRecording = false;\n\t}\n\tif (isFunctionRecording) {\n\n\t\tif (!pCodecCtx || !pFormatCtx || !swsCtx || !frame) {\n\t\t\tprintf(\"Error: FFmpeg contexts not properly \"\n\t\t\t\t\"initialized\\n\");\n\t\t\treturn false;\n\t\t}\n\n\t\tRenderTexture2D videoTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());\n\t\tBeginTextureMode(videoTexture);\n\t\tClearBackground(BLACK);\n\t\tDrawTexture(myParticlesTexture.texture, 0, 0, WHITE);\n\t\tEndTextureMode();\n\n\t\tImage img = LoadImageFromTexture(videoTexture.texture);\n\n\t\tUnloadRenderTexture(videoTexture);\n\n\t\tif (!img.data) {\n\t\t\tprintf(\"Error: Failed to load image from texture\\n\");\n\t\t\treturn isFunctionRecording;\n\t\t}\n\n\t\t//ImageFlipVertical(&img);\n\n\t\tint w = img.width;\n\t\tint h = img.height;\n\t\tconst uint8_t* srcSlices[1] = { reinterpret_cast<const uint8_t*>(img.data) };\n\t\tint srcStride[1] = { 4 * w };\n\n\t\tint result = sws_scale(swsCtx, srcSlices, srcStride, 0, h, frame->data,\n\t\t\tframe->linesize);\n\t\tif (result < 0) {\n\t\t\tprintf(\"Error: sws_scale failed\\n\");\n\t\t\tUnloadImage(img);\n\t\t\treturn false;\n\t\t}\n\n\t\tif (frame) {\n\t\t\tif (pStream) {\n\t\t\t\tframe->pts = av_rescale_q(frameIndex++, pCodecCtx->time_base,\n\t\t\t\t\tpStream->time_base);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tframe->pts = frameIndex++;\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.recordingTimeLimit > 0.0f) {\n\t\t\tfloat recordedSeconds =\n\t\t\t\tstatic_cast<float>(frameIndex) / pCodecCtx->time_base.den;\n\t\t\tif (recordedSeconds >= myVar.recordingTimeLimit) {\n\t\t\t\tprintf(\"Recording time limit reached (%.1f \"\n\t\t\t\t\t\"seconds). Stopping \"\n\t\t\t\t\t\"recording.\\n\",\n\t\t\t\t\tmyVar.recordingTimeLimit);\n\n\t\t\t\tav_write_trailer(pFormatCtx);\n\t\t\t\tcleanupFFmpeg();\n\t\t\t\tisFunctionRecording = false;\n\n\t\t\t\tlastVideoPath = outFileName;\n\t\t\t\tshowSaveConfirmationDialog = true;\n\n\t\t\t\tif (myVar.pauseAfterRecording) {\n\t\t\t\t\tmyVar.isTimePlaying = false;\n\t\t\t\t}\n\t\t\t\tif (myVar.cleanSceneAfterRecording) {\n\t\t\t\t\tmyParam.pParticles.clear();\n\t\t\t\t\tmyParam.rParticles.clear();\n\t\t\t\t}\n\t\t\t\tUnloadImage(img);\n\t\t\t\treturn isFunctionRecording;\n\t\t\t}\n\t\t}\n\n\t\tint sendResult = avcodec_send_frame(pCodecCtx, frame);\n\t\tif (sendResult < 0) {\n\t\t\tprintf(\"Warning: avcodec_send_frame failed with error \"\n\t\t\t\t\"%d\\n\",\n\t\t\t\tsendResult);\n\t\t}\n\n\t\tAVPacket* pkt = av_packet_alloc();\n\t\tif (!pkt) {\n\t\t\tprintf(\"Could not allocate packet\\n\");\n\t\t\tUnloadImage(img);\n\t\t\treturn false;\n\t\t}\n\n\t\twhile (avcodec_receive_packet(pCodecCtx, pkt) == 0) {\n\t\t\tif (pStream) {\n\t\t\t\tpkt->stream_index = pStream->index;\n\t\t\t\tint writeResult = av_interleaved_write_frame(pFormatCtx, pkt);\n\t\t\t\tif (writeResult < 0) {\n\t\t\t\t\tprintf(\"Warning: \"\n\t\t\t\t\t\t\"av_interleaved_write_frame \"\n\t\t\t\t\t\t\"failed with \"\n\t\t\t\t\t\t\"error %d\\n\",\n\t\t\t\t\t\twriteResult);\n\t\t\t\t}\n\t\t\t}\n\t\t\tav_packet_unref(pkt);\n\t\t}\n\n\t\tav_packet_free(&pkt);\n\t\tif (!isSafeFramesEnabled && isExportFramesEnabled) {\n\n\t\t\tImage frameCopy = ImageCopy(img);\n\t\t\tImageFormat(&frameCopy, PIXELFORMAT_UNCOMPRESSED_R8G8B8);\n\t\t\tImageFlipVertical(&frameCopy);\n\t\t\tmyFrames.push_back(frameCopy);\n\t\t}\n\t\tif (isSafeFramesEnabled && isExportFramesEnabled) {\n\n\t\t\tif (!this->videoFolder.empty() && !this->folderName.empty()) {\n\t\t\t\tImage frameForExport = ImageCopy(img);\n\t\t\t\tImageFormat(&frameForExport, PIXELFORMAT_UNCOMPRESSED_R8G8B8);\n\t\t\t\tstd::string safeFramePath = this->videoFolder + \"/\" + this->folderName +\n\t\t\t\t\t\"_frame_\" +\n\t\t\t\t\tstd::to_string(diskModeFrameIdx) + \".png\";\n\t\t\t\tExportImage(frameForExport, safeFramePath.c_str());\n\t\t\t\tUnloadImage(frameForExport);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprintf(\"Warning: videoFolder or folderName is \"\n\t\t\t\t\t\"empty, cannot \"\n\t\t\t\t\t\"save safe \"\n\t\t\t\t\t\"frame.\\\\n\");\n\t\t\t}\n\t\t}\n\n\t\tif (isSafeFramesEnabled || isVideoExportEnabled) {\n\t\t\tdiskModeFrameIdx++;\n\t\t}\n\n\t\tUnloadImage(img);\n\t}\n\tfloat screenW = GetScreenWidth();\n\tfloat screenH = GetScreenHeight();\n\tImVec2 framesMenuSize = { 400.0f, 200.0f };\n\tif (myFrames.size() > 0 || diskModeFrameIdx > 0 || isFunctionRecording) {\n\t\tImGui::SetNextWindowSize(framesMenuSize, ImGuiCond_Once);\n\t\tfloat yPosition = 30.0f;\n\t\tImGui::SetNextWindowPos(\n\t\t\tImVec2(screenW * 0.5f - framesMenuSize.x * 0.5f, yPosition),\n\t\t\tImGuiCond_Appearing);\n\n\t\tImGui::Begin(\"Recording Menu\", nullptr,\n\t\t\tImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize);\n\n\t\tImGui::PushFont(myVar.robotoMediumFont);\n\n\t\tImGui::SetWindowFontScale(1.5f);\n\n\t\tif (diskModeFrameIdx > 0 && (isSafeFramesEnabled || isVideoExportEnabled)) {\n\t\t\tfloat recordedSeconds = static_cast<float>(diskModeFrameIdx) / 60.0f;\n\t\t\tImGui::TextColored(ImVec4(0.8f, 0.0f, 0.0f, 1.0f), \"Frames: %d (%.2f s)\",\n\t\t\t\tdiskModeFrameIdx, recordedSeconds);\n\t\t}\n\t\telse if (myFrames.size() > 0 && !isSafeFramesEnabled) {\n\t\t\tfloat recordedSeconds = static_cast<float>(myFrames.size()) / 60.0f;\n\t\t\tImGui::TextColored(ImVec4(0.8f, 0.0f, 0.0f, 1.0f), \"Frames: %d (%.2f s)\",\n\t\t\t\tstatic_cast<int>(myFrames.size()), recordedSeconds);\n\t\t}    if (isFunctionRecording) {\n\t\t\tImGui::Separator();\n\n\t\t\tif (ImGui::Button(\"End Recording\",\n\t\t\t\tImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) {\n\n\t\t\t\tav_write_trailer(pFormatCtx);\n\t\t\t\tcleanupFFmpeg();\n\t\t\t\tisFunctionRecording = false;\n\n\t\t\t\tlastVideoPath = outFileName;\n\t\t\t\tshowSaveConfirmationDialog = true;\n\n\t\t\t\tif (myVar.pauseAfterRecording) {\n\t\t\t\t\tmyVar.isTimePlaying = false;\n\t\t\t\t}\n\t\t\t\tif (myVar.cleanSceneAfterRecording) {\n\t\t\t\t\tmyParam.pParticles.clear();\n\t\t\t\t\tmyParam.rParticles.clear();\n\t\t\t\t}\n\n\t\t\t\tprintf(\"Recording ended via button. File saved \"\n\t\t\t\t\t\"as '%s'\\n\",\n\t\t\t\t\toutFileName.c_str());\n\t\t\t}\n\n\t\t\tif (ImGui::IsItemHovered()) {\n\t\t\t\tImGui::SetTooltip(\"Stop recording and save the video file\");\n\t\t\t}\n\n\t\t\tImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive);\n\t\t\tImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover);\n\t\t\tImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress);\n\n\t\t\tif (ImGui::Button(\"Cancel Recording\",\n\t\t\t\tImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) {\n\t\t\t\tcancelRecording = true;\n\t\t\t}\n\n\t\t\tImGui::PopStyleColor(3);\n\n\t\t\tif (ImGui::IsItemHovered()) {\n\t\t\t\tImGui::SetTooltip(\"Stop recording and discard \"\n\t\t\t\t\t\"the video file\");\n\t\t\t}\n\t\t}\n\t\tif (myFrames.size() > 0 && !isFunctionRecording && !isSafeFramesEnabled &&\n\t\t\tisExportFramesEnabled && videoHasBeenSaved) {\n\n\t\t\t// Show different text based on export status\n\t\t\t// Note: Not Working, the exporting blocks the UI\n\t\t\t// TODO: if we manage to unblock the UI, we can use this\n\t\t\tstd::string buttonText =\n\t\t\t\tisExportingFrames ? \"Exporting...\" : \"Export Frames\";\n\n\t\t\t// Disable the button if export is in progress\n\t\t\tif (isExportingFrames) {\n\t\t\t\tImGui::PushStyleVar(ImGuiStyleVar_Alpha,\n\t\t\t\t\tImGui::GetStyle().Alpha * 0.5f);\n\t\t\t}\n\n\t\t\tbool buttonClicked = ImGui::Button(\n\t\t\t\tbuttonText.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 40.0f));\n\n\t\t\tif (isExportingFrames) {\n\t\t\t\tImGui::PopStyleVar();\n\t\t\t}\n\t\t\t// Only process click if not currently exporting\n\t\t\tif (buttonClicked && !isExportingFrames) {\n\t\t\t\texportMemoryFrames = !exportMemoryFrames;\n\t\t\t}\n\n\t\t\t// Add tooltip for better user feedback\n\t\t\tif (ImGui::IsItemHovered()) {\n\t\t\t\tif (isExportingFrames) {\n\t\t\t\t\tImGui::SetTooltip(\"Export in progress, \"\n\t\t\t\t\t\t\"please wait...\");\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tImGui::SetTooltip(\"Export frames from memory to disk \"\n\t\t\t\t\t\t\"as PNG files\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Show different text based on export status\n\t\t\tstd::string discardButtonText =\n\t\t\t\tisExportingFrames ? \"Export in progress...\" : \"Discard Frames\";\n\n\t\t\tif (isExportingFrames) {\n\t\t\t\tImGui::PushStyleVar(ImGuiStyleVar_Alpha,\n\t\t\t\t\tImGui::GetStyle().Alpha * 0.5f);\n\t\t\t}\n\n\t\t\tImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive);\n\t\t\tImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover);\n\t\t\tImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress);\n\n\t\t\tbool discardButtonClicked =\n\t\t\t\tImGui::Button(discardButtonText.c_str(),\n\t\t\t\t\tImVec2(ImGui::GetContentRegionAvail().x, 40.0f));\n\n\t\t\tImGui::PopStyleColor(3);\n\t\t\tif (isExportingFrames) {\n\t\t\t\tImGui::PopStyleVar();\n\t\t\t}\n\n\t\t\t// Only process click if not currently exporting\n\t\t\tif (discardButtonClicked && !isExportingFrames) {\n\t\t\t\tdeleteFrames = !deleteFrames;\n\t\t\t}\n\n\t\t\t// Add tooltip for discard button\n\t\t\tif (ImGui::IsItemHovered()) {\n\t\t\t\tif (isExportingFrames) {\n\t\t\t\t\tImGui::SetTooltip(\"Cannot discard frames while \"\n\t\t\t\t\t\t\"export is in progress\");\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tImGui::SetTooltip(\"Discard all frames from memory \"\n\t\t\t\t\t\t\"without saving\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tImGui::PopFont();\n\t\tImGui::End();\n\t}\n\t// Process frame export/discard actions\n\tif (exportMemoryFrames && videoHasBeenSaved &&\n\t\t!actualSavedVideoFolder.empty() && !actualSavedVideoName.empty() &&\n\t\t!isExportingFrames) {\n\t\tisExportingFrames = true; // Set flag to indicate export is in progress\n\t\texportMemoryFramesToDisk();\n\n\t\t// After export completes, clean up and close dialog\n\t\texportMemoryFrames = false; // Reset the flag after processing\n\t\tisExportingFrames = false;  // Clear the export in progress flag\n\n\t\t// Clear frames from memory and close the recording menu\n\t\tdiscardMemoryFrames();\n\t}\n\tif (deleteFrames && !myFrames.empty() && !isExportingFrames) {\n\t\tdiscardMemoryFrames();\n\t\tdeleteFrames = false; // Reset the flag after processing\n\t}\n\n\tif (showSaveConfirmationDialog) {\n\t\tImGui::SetNextWindowSize(ImVec2(400, 200), ImGuiCond_Always);\n\t\tImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - 200, screenH * 0.5f - 90),\n\t\t\tImGuiCond_Appearing);\n\t\tImGui::Begin(\"Save Recording?\", nullptr,\n\t\t\tImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |\n\t\t\tImGuiWindowFlags_NoTitleBar);\n\t\tImGui::PushFont(myVar.robotoMediumFont);\n\t\tImGui::SetWindowFontScale(1.5f);\n\n\t\tImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), \"Save Recording?\");\n\t\tImGui::Separator();\n\n\t\tImGui::Text(\"Do you want to save the recording?\");\n\t\tImGui::Text(\"Current: %s\", lastVideoPath.c_str());\n\n\t\tImGui::Separator();\n\n\t\tImGui::Text(\"Custom Name (optional):\");\n\t\tstatic char nameBuffer[256] = \"\";\n\t\tImGui::InputText(\"##CustomVideoName\", nameBuffer, sizeof(nameBuffer));\n\n\t\tif (ImGui::IsItemHovered()) {\n\t\t\tImGui::SetTooltip(\"Leave empty to keep current name\");\n\t\t}    ImGui::Separator();\n\n\t\tif (ImGui::Button(\"Save\", ImVec2(100, 30))) {\n\n\t\t\tif (isFunctionRecording) {\n\t\t\t\tprintf(\"Warning: Cannot rename video while \"\n\t\t\t\t\t\"recording is \"\n\t\t\t\t\t\"active\\\\n\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstd::string customNameInput = std::string(nameBuffer);\n\t\t\t\tstd::string finalVideoPath = lastVideoPath;\n\n\t\t\t\tif (!customNameInput.empty()) {\n\t\t\t\t\tstd::string cleanedCustomName = customNameInput;\n\n\t\t\t\t\tcleanedCustomName.erase(\n\t\t\t\t\t\tstd::remove_if(cleanedCustomName.begin(), cleanedCustomName.end(),\n\t\t\t\t\t\t\t[](char c) {\n\t\t\t\t\t\t\t\treturn c == '<' || c == '>' || c == ':' ||\n\t\t\t\t\t\t\t\t\tc == '\"' || c == '|' || c == '?' ||\n\t\t\t\t\t\t\t\t\tc == '*' || c == '/' || c == '\\\\';\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\tcleanedCustomName.end());\n\n\t\t\t\t\tif (cleanedCustomName.length() >= 4 &&\n\t\t\t\t\t\tcleanedCustomName.substr(cleanedCustomName.length() - 4) ==\n\t\t\t\t\t\t\".mp4\") {\n\t\t\t\t\t\tcleanedCustomName =\n\t\t\t\t\t\t\tcleanedCustomName.substr(0, cleanedCustomName.length() - 4);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!cleanedCustomName.empty() &&\n\t\t\t\t\t\tcleanedCustomName != this->folderName) {\n\t\t\t\t\t\tstd::string oldBaseName = this->folderName;\n\t\t\t\t\t\tstd::string oldFolderPath = this->videoFolder;\n\t\t\t\t\t\tstd::string oldVideoFileNameWithExt = oldBaseName + \".mp4\";\n\n\t\t\t\t\t\tstd::string parentDir = \"Videos\";\n\t\t\t\t\t\tsize_t parentPathEndPos = oldFolderPath.find_last_of('/');\n\t\t\t\t\t\tif (parentPathEndPos != std::string::npos) {\n\t\t\t\t\t\t\tparentDir = oldFolderPath.substr(0, parentPathEndPos);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstd::string newBaseName = cleanedCustomName;\n\t\t\t\t\t\tstd::string newFolderPath = parentDir + \"/\" + newBaseName;\n\t\t\t\t\t\tstd::string newVideoFileNameWithExt = newBaseName + \".mp4\";\n\n\t\t\t\t\t\tbool folderRenamedSuccessfully = false;\n\n\t\t\t\t\t\tif (oldFolderPath != newFolderPath &&\n\t\t\t\t\t\t\tstd::filesystem::exists(oldFolderPath)) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tif (std::filesystem::exists(newFolderPath)) {\n\t\t\t\t\t\t\t\t\tprintf(\"Wa\"\n\t\t\t\t\t\t\t\t\t\t\"rn\"\n\t\t\t\t\t\t\t\t\t\t\"in\"\n\t\t\t\t\t\t\t\t\t\t\"g:\"\n\t\t\t\t\t\t\t\t\t\t\" T\"\n\t\t\t\t\t\t\t\t\t\t\"ar\"\n\t\t\t\t\t\t\t\t\t\t\"ge\"\n\t\t\t\t\t\t\t\t\t\t\"t \"\n\t\t\t\t\t\t\t\t\t\t\"fo\"\n\t\t\t\t\t\t\t\t\t\t\"ld\"\n\t\t\t\t\t\t\t\t\t\t\"er\"\n\t\t\t\t\t\t\t\t\t\t\" %\"\n\t\t\t\t\t\t\t\t\t\t\"s \"\n\t\t\t\t\t\t\t\t\t\t\"fo\"\n\t\t\t\t\t\t\t\t\t\t\"r \"\n\t\t\t\t\t\t\t\t\t\t\"re\"\n\t\t\t\t\t\t\t\t\t\t\"na\"\n\t\t\t\t\t\t\t\t\t\t\"me\"\n\t\t\t\t\t\t\t\t\t\t\" a\"\n\t\t\t\t\t\t\t\t\t\t\"lr\"\n\t\t\t\t\t\t\t\t\t\t\"ea\"\n\t\t\t\t\t\t\t\t\t\t\"dy\"\n\t\t\t\t\t\t\t\t\t\t\" e\"\n\t\t\t\t\t\t\t\t\t\t\"xi\"\n\t\t\t\t\t\t\t\t\t\t\"st\"\n\t\t\t\t\t\t\t\t\t\t\"s.\"\n\t\t\t\t\t\t\t\t\t\t\" \"\n\t\t\t\t\t\t\t\t\t\t\"Ab\"\n\t\t\t\t\t\t\t\t\t\t\"or\"\n\t\t\t\t\t\t\t\t\t\t\"ti\"\n\t\t\t\t\t\t\t\t\t\t\"ng\"\n\t\t\t\t\t\t\t\t\t\t\" r\"\n\t\t\t\t\t\t\t\t\t\t\"en\"\n\t\t\t\t\t\t\t\t\t\t\"am\"\n\t\t\t\t\t\t\t\t\t\t\"e \"\n\t\t\t\t\t\t\t\t\t\t\"to\"\n\t\t\t\t\t\t\t\t\t\t\" p\"\n\t\t\t\t\t\t\t\t\t\t\"re\"\n\t\t\t\t\t\t\t\t\t\t\"ve\"\n\t\t\t\t\t\t\t\t\t\t\"nt\"\n\t\t\t\t\t\t\t\t\t\t\" d\"\n\t\t\t\t\t\t\t\t\t\t\"at\"\n\t\t\t\t\t\t\t\t\t\t\"a \"\n\t\t\t\t\t\t\t\t\t\t\"lo\"\n\t\t\t\t\t\t\t\t\t\t\"ss\"\n\t\t\t\t\t\t\t\t\t\t\".\"\n\t\t\t\t\t\t\t\t\t\t\"\\\\\"\n\t\t\t\t\t\t\t\t\t\t\"n\",\n\t\t\t\t\t\t\t\t\t\tnewFolderPath.c_str());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tstd::filesystem::rename(oldFolderPath, newFolderPath);\n\t\t\t\t\t\t\t\t\tprintf(\"Fo\"\n\t\t\t\t\t\t\t\t\t\t\"ld\"\n\t\t\t\t\t\t\t\t\t\t\"er\"\n\t\t\t\t\t\t\t\t\t\t\" r\"\n\t\t\t\t\t\t\t\t\t\t\"en\"\n\t\t\t\t\t\t\t\t\t\t\"am\"\n\t\t\t\t\t\t\t\t\t\t\"ed\"\n\t\t\t\t\t\t\t\t\t\t\" f\"\n\t\t\t\t\t\t\t\t\t\t\"ro\"\n\t\t\t\t\t\t\t\t\t\t\"m \"\n\t\t\t\t\t\t\t\t\t\t\"%s\"\n\t\t\t\t\t\t\t\t\t\t\" t\"\n\t\t\t\t\t\t\t\t\t\t\"o \"\n\t\t\t\t\t\t\t\t\t\t\"%s\"\n\t\t\t\t\t\t\t\t\t\t\"\\\\\"\n\t\t\t\t\t\t\t\t\t\t\"n\",\n\t\t\t\t\t\t\t\t\t\toldFolderPath.c_str(), newFolderPath.c_str());\n\t\t\t\t\t\t\t\t\tthis->videoFolder = newFolderPath;\n\n\t\t\t\t\t\t\t\t\tlastVideoPath =\n\t\t\t\t\t\t\t\t\t\tthis->videoFolder + \"/\" + oldVideoFileNameWithExt;\n\t\t\t\t\t\t\t\t\tfolderRenamedSuccessfully = true;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcatch (const std::filesystem::filesystem_error& e_folder) {\n\t\t\t\t\t\t\t\tprintf(\"Error \"\n\t\t\t\t\t\t\t\t\t\"renaming \"\n\t\t\t\t\t\t\t\t\t\"folder %s \"\n\t\t\t\t\t\t\t\t\t\"to %s: \"\n\t\t\t\t\t\t\t\t\t\"%s\\\\n\",\n\t\t\t\t\t\t\t\t\toldFolderPath.c_str(), newFolderPath.c_str(),\n\t\t\t\t\t\t\t\t\te_folder.what());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (oldFolderPath == newFolderPath) {\n\t\t\t\t\t\t\tfolderRenamedSuccessfully = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (!std::filesystem::exists(oldFolderPath)) {\n\t\t\t\t\t\t\tprintf(\"Error: Original \"\n\t\t\t\t\t\t\t\t\"folder %s not \"\n\t\t\t\t\t\t\t\t\"found. \"\n\t\t\t\t\t\t\t\t\"Cannot perform \"\n\t\t\t\t\t\t\t\t\"rename \"\n\t\t\t\t\t\t\t\t\"operations.\\\\n\",\n\t\t\t\t\t\t\t\toldFolderPath.c_str());\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (folderRenamedSuccessfully) {\n\n\t\t\t\t\t\t\tstd::string currentVideoFilePath = lastVideoPath;\n\t\t\t\t\t\t\tstd::string targetVideoFilePath =\n\t\t\t\t\t\t\t\tthis->videoFolder + \"/\" + newVideoFileNameWithExt;\n\n\t\t\t\t\t\t\tif (currentVideoFilePath != targetVideoFilePath &&\n\t\t\t\t\t\t\t\tstd::filesystem::exists(currentVideoFilePath)) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tif (std::filesystem::exists(targetVideoFilePath) &&\n\t\t\t\t\t\t\t\t\t\tcurrentVideoFilePath != targetVideoFilePath) {\n\t\t\t\t\t\t\t\t\t\tprintf(\"Warning: Target video file %s \"\n\t\t\t\t\t\t\t\t\t\t\t\"already exists. \"\n\t\t\t\t\t\t\t\t\t\t\t\"Overwriting.\\\\n\",\n\t\t\t\t\t\t\t\t\t\t\ttargetVideoFilePath.c_str());\n\t\t\t\t\t\t\t\t\t\tstd::filesystem::remove(targetVideoFilePath);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tstd::filesystem::rename(currentVideoFilePath,\n\t\t\t\t\t\t\t\t\t\ttargetVideoFilePath);\n\t\t\t\t\t\t\t\t\tprintf(\"Vi\"\n\t\t\t\t\t\t\t\t\t\t\"de\"\n\t\t\t\t\t\t\t\t\t\t\"o \"\n\t\t\t\t\t\t\t\t\t\t\"fi\"\n\t\t\t\t\t\t\t\t\t\t\"le\"\n\t\t\t\t\t\t\t\t\t\t\" r\"\n\t\t\t\t\t\t\t\t\t\t\"en\"\n\t\t\t\t\t\t\t\t\t\t\"am\"\n\t\t\t\t\t\t\t\t\t\t\"ed\"\n\t\t\t\t\t\t\t\t\t\t\" t\"\n\t\t\t\t\t\t\t\t\t\t\"o \"\n\t\t\t\t\t\t\t\t\t\t\"%s\"\n\t\t\t\t\t\t\t\t\t\t\"\\\\\"\n\t\t\t\t\t\t\t\t\t\t\"n\",\n\t\t\t\t\t\t\t\t\t\ttargetVideoFilePath.c_str());\n\t\t\t\t\t\t\t\t\tlastVideoPath = targetVideoFilePath;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcatch (const std::filesystem::filesystem_error& e_video) {\n\t\t\t\t\t\t\t\t\tprintf(\"Er\"\n\t\t\t\t\t\t\t\t\t\t\"ro\"\n\t\t\t\t\t\t\t\t\t\t\"r \"\n\t\t\t\t\t\t\t\t\t\t\"re\"\n\t\t\t\t\t\t\t\t\t\t\"na\"\n\t\t\t\t\t\t\t\t\t\t\"mi\"\n\t\t\t\t\t\t\t\t\t\t\"ng\"\n\t\t\t\t\t\t\t\t\t\t\" v\"\n\t\t\t\t\t\t\t\t\t\t\"id\"\n\t\t\t\t\t\t\t\t\t\t\"eo\"\n\t\t\t\t\t\t\t\t\t\t\" f\"\n\t\t\t\t\t\t\t\t\t\t\"il\"\n\t\t\t\t\t\t\t\t\t\t\"e \"\n\t\t\t\t\t\t\t\t\t\t\"fr\"\n\t\t\t\t\t\t\t\t\t\t\"om\"\n\t\t\t\t\t\t\t\t\t\t\" %\"\n\t\t\t\t\t\t\t\t\t\t\"s \"\n\t\t\t\t\t\t\t\t\t\t\"to\"\n\t\t\t\t\t\t\t\t\t\t\" %\"\n\t\t\t\t\t\t\t\t\t\t\"s:\"\n\t\t\t\t\t\t\t\t\t\t\" %\"\n\t\t\t\t\t\t\t\t\t\t\"s\"\n\t\t\t\t\t\t\t\t\t\t\"\\\\\"\n\t\t\t\t\t\t\t\t\t\t\"n\",\n\t\t\t\t\t\t\t\t\t\tcurrentVideoFilePath.c_str(),\n\t\t\t\t\t\t\t\t\t\ttargetVideoFilePath.c_str(), e_video.what());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (!std::filesystem::exists(currentVideoFilePath)) {\n\t\t\t\t\t\t\t\tprintf(\"Warning: \"\n\t\t\t\t\t\t\t\t\t\"Video \"\n\t\t\t\t\t\t\t\t\t\"file %s \"\n\t\t\t\t\t\t\t\t\t\"not found \"\n\t\t\t\t\t\t\t\t\t\"for \"\n\t\t\t\t\t\t\t\t\t\"renaming.\"\n\t\t\t\t\t\t\t\t\t\"\\\\n\",\n\t\t\t\t\t\t\t\t\tcurrentVideoFilePath.c_str());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfinalVideoPath = lastVideoPath;\n\n\t\t\t\t\t\t\tif (isSafeFramesEnabled && isExportFramesEnabled &&\n\t\t\t\t\t\t\t\toldBaseName != newBaseName) {\n\t\t\t\t\t\t\t\tstd::string oldFramePrefix = oldBaseName + \"_frame\"\n\t\t\t\t\t\t\t\t\t\"_\";\n\t\t\t\t\t\t\t\tstd::string newFramePrefix = newBaseName + \"_frame\"\n\t\t\t\t\t\t\t\t\t\"_\";\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tfor (const auto& entry :\n\t\t\t\t\t\t\t\t\t\tstd::filesystem::directory_iterator(this->videoFolder)) {\n\t\t\t\t\t\t\t\t\t\tif (entry.is_regular_file()) {\n\t\t\t\t\t\t\t\t\t\t\tstd::string currentFrameFileName =\n\t\t\t\t\t\t\t\t\t\t\t\tentry.path().filename().string();\n\t\t\t\t\t\t\t\t\t\t\tif (currentFrameFileName.rfind(oldFramePrefix, 0) == 0 &&\n\t\t\t\t\t\t\t\t\t\t\t\tcurrentFrameFileName.length() >\n\t\t\t\t\t\t\t\t\t\t\t\toldFramePrefix.length() &&\n\t\t\t\t\t\t\t\t\t\t\t\tcurrentFrameFileName.substr(\n\t\t\t\t\t\t\t\t\t\t\t\t\tcurrentFrameFileName.length() - 4) == \".png\") {\n\n\t\t\t\t\t\t\t\t\t\t\t\tstd::string frameIndexAndExt =\n\t\t\t\t\t\t\t\t\t\t\t\t\tcurrentFrameFileName.substr(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\toldFramePrefix.length());\n\t\t\t\t\t\t\t\t\t\t\t\tstd::string newFrameFileName =\n\t\t\t\t\t\t\t\t\t\t\t\t\tnewFramePrefix + frameIndexAndExt;\n\t\t\t\t\t\t\t\t\t\t\t\tstd::string oldFramePath = entry.path().string();\n\t\t\t\t\t\t\t\t\t\t\t\tstd::string newFramePath =\n\t\t\t\t\t\t\t\t\t\t\t\t\tthis->videoFolder + \"/\" + newFrameFileName;\n\n\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\tstd::filesystem::rename(oldFramePath, newFramePath);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tcatch (\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst std::filesystem::filesystem_error& e_frame) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tprintf(\"Warning: Failed to \"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"rename frame %s to \"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"%s: %s\\\\n\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\toldFramePath.c_str(), newFramePath.c_str(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\te_frame.what());\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcatch (const std::filesystem::filesystem_error& e_dir_iter) {\n\t\t\t\t\t\t\t\t\tprintf(\"Er\"\n\t\t\t\t\t\t\t\t\t\t\"ro\"\n\t\t\t\t\t\t\t\t\t\t\"r \"\n\t\t\t\t\t\t\t\t\t\t\"it\"\n\t\t\t\t\t\t\t\t\t\t\"er\"\n\t\t\t\t\t\t\t\t\t\t\"at\"\n\t\t\t\t\t\t\t\t\t\t\"in\"\n\t\t\t\t\t\t\t\t\t\t\"g \"\n\t\t\t\t\t\t\t\t\t\t\"di\"\n\t\t\t\t\t\t\t\t\t\t\"re\"\n\t\t\t\t\t\t\t\t\t\t\"ct\"\n\t\t\t\t\t\t\t\t\t\t\"or\"\n\t\t\t\t\t\t\t\t\t\t\"y \"\n\t\t\t\t\t\t\t\t\t\t\"%s\"\n\t\t\t\t\t\t\t\t\t\t\" f\"\n\t\t\t\t\t\t\t\t\t\t\"or\"\n\t\t\t\t\t\t\t\t\t\t\" \"\n\t\t\t\t\t\t\t\t\t\t\"fr\"\n\t\t\t\t\t\t\t\t\t\t\"am\"\n\t\t\t\t\t\t\t\t\t\t\"e \"\n\t\t\t\t\t\t\t\t\t\t\"re\"\n\t\t\t\t\t\t\t\t\t\t\"na\"\n\t\t\t\t\t\t\t\t\t\t\"mi\"\n\t\t\t\t\t\t\t\t\t\t\"ng\"\n\t\t\t\t\t\t\t\t\t\t\": \"\n\t\t\t\t\t\t\t\t\t\t\"%s\"\n\t\t\t\t\t\t\t\t\t\t\"\\\\\"\n\t\t\t\t\t\t\t\t\t\t\"n\",\n\t\t\t\t\t\t\t\t\t\tthis->videoFolder.c_str(), e_dir_iter.what());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tthis->folderName = newBaseName;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\n\t\t\t\t\t\t\tprintf(\"Save operation \"\n\t\t\t\t\t\t\t\t\"may be incomplete \"\n\t\t\t\t\t\t\t\t\"due to \"\n\t\t\t\t\t\t\t\t\"folder rename \"\n\t\t\t\t\t\t\t\t\"failure.\\\\n\");\n\t\t\t\t\t\t\tfinalVideoPath = lastVideoPath;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\n\t\t\t\t\t\tfinalVideoPath = lastVideoPath;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinalVideoPath = lastVideoPath;\n\t\t\t\t}\n\n\t\t\t\tstd::filesystem::path finalPathObj(finalVideoPath);\n\t\t\t\tactualSavedVideoFolder = finalPathObj.parent_path().string();\n\t\t\t\tactualSavedVideoName = finalPathObj.filename().string();\n\n\t\t\t\tstd::filesystem::path videoNamePath(actualSavedVideoName);\n\t\t\t\tif (videoNamePath.has_extension() &&\n\t\t\t\t\tvideoNamePath.extension() == \".mp4\") {\n\t\t\t\t\tactualSavedVideoName = videoNamePath.stem().string();\n\t\t\t\t}        videoHasBeenSaved = true;\n\t\t\t\tshowSaveConfirmationDialog = false;\n\t\t\t\tnameBuffer[0] = '\\0';\n\n\t\t\t\t// Clear recording state to close the recording menu\n\t\t\t\t// Only discard frames if frame export is disabled\n\t\t\t\tif (!myFrames.empty() && !isExportFramesEnabled) {\n\t\t\t\t\tdiscardMemoryFrames();\n\t\t\t\t}\n\t\t\t\tdiskModeFrameIdx = 0;\n\n\t\t\t\tif (isExportFramesEnabled && isSafeFramesEnabled) {\n\n\t\t\t\t\tprintf(\"Frames are in: %s with base \"\n\t\t\t\t\t\t\"name: %s\\\\n\",\n\t\t\t\t\t\tthis->videoFolder.c_str(), this->folderName.c_str());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tImGui::SameLine();\n\n\t\tImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive);\n\t\tImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover);\n\t\tImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress);\n\t\tif (ImGui::Button(\"Discard\", ImVec2(100, 30))) {\n\t\t\tif (!isFunctionRecording) {\n\t\t\t\t// Use the centralized discard function for comprehensive cleanup\n\t\t\t\tdiscardRecording();\n\n\t\t\t\t// Handle video file cleanup if it exists outside the video folder\n\t\t\t\ttry {\n\t\t\t\t\tif (std::filesystem::exists(lastVideoPath) &&\n\t\t\t\t\t\tlastVideoPath != this->videoFolder) {\n\t\t\t\t\t\tif (std::filesystem::is_regular_file(lastVideoPath)) {\n\t\t\t\t\t\t\tstd::filesystem::remove(lastVideoPath);\n\t\t\t\t\t\t\tprintf(\"Discarded video file: %s\\\\n\", lastVideoPath.c_str());\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (std::filesystem::is_directory(lastVideoPath)) {\n\t\t\t\t\t\t\tstd::filesystem::remove_all(lastVideoPath);\n\t\t\t\t\t\t\tprintf(\"Discarded (unexpected) directory at lastVideoPath: %s\\\\n\",\n\t\t\t\t\t\t\t\tlastVideoPath.c_str());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcatch (const std::filesystem::filesystem_error& e) {\n\t\t\t\t\tprintf(\"Error discarding video/folder: %s\\\\n\", e.what());\n\t\t\t\t}\n\t\t\t}\n\t\t\tshowSaveConfirmationDialog = false;\n\t\t\tnameBuffer[0] = '\\0';\n\n\t\t\t// Clear recording state to close the recording menu\n\t\t\tif (!myFrames.empty()) {\n\t\t\t\tdiscardMemoryFrames();\n\t\t\t}\n\t\t\tdiskModeFrameIdx = 0;\n\t\t\tlastVideoPath.clear();\n\t\t\tthis->videoFolder.clear();\n\t\t\tthis->folderName.clear();      videoHasBeenSaved = false;\n\t\t\tisFunctionRecording = false;\n\t\t}\n\t\tImGui::PopStyleColor(3);\n\n\t\tImGui::PopFont();\n\t\tImGui::End();\n\t}\n\treturn isFunctionRecording;\n}"
  },
  {
    "path": "GalaxyEngine/src/globalLogic.cpp",
    "content": "#include \"globalLogic.h\"\n\nUpdateParameters myParam;\nUpdateVariables myVar;\nUI myUI;\nPhysics physics;\nPhysics3D physics3D;\nParticleSpaceship ship;\nSPH sph;\nSPH3D sph3D;\nSaveSystem save;\nGESound geSound;\nLighting lighting;\nCopyPaste copyPaste;\n\nRayMarcher rayMarcher;\n\nField field;\n\nstd::vector<Node> globalNodes;\nstd::vector<Node3D> globalNodes3D;\n\nuint32_t globalId = 0;\nuint32_t globalShapeId = 1;\nuint32_t globalWallId = 1;\n\n#ifdef _MSC_VER\n#include <intrin.h>\n#else\n#include <cpuid.h>\n#endif\n\nbool hasAVX2Support() {\n\tstatic int cached = -1;\n\tif (cached != -1) return cached;\n\n\tint cpuInfo[4];\n#ifdef _MSC_VER\n\t__cpuidex(cpuInfo, 7, 0);\n#else\n\t__cpuid_count(7, 0, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]);\n#endif\n\tcached = (cpuInfo[1] & (1 << 5)) != 0;\n\treturn cached;\n}\n\n// If someday light id gets added, don't forget to add the id to the copy paste code too\n\n//std::unordered_map<unsigned int, uint64_t> NeighborSearch::idToIndex;\n\n\n//void flattenQuadtree(Quadtree* node, std::vector<Quadtree*>& flatList) {\n//\tif (!node) return;\n//\n//\tflatList.push_back(node);\n//\n//\tfor (const auto& child : node->subGrids) {\n//\t\tflattenQuadtree(child.get(), flatList);\n//\t}\n//}\n\n\n// THIS FUNCTION IS MEANT FOR QUICK DEBUGGING WHERE YOU NEED TO CHECK A SPECIFIC PARTICLE'S VARIABLES\nvoid selectedParticleDebug() {\n\n\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\tParticlePhysics& p = myParam.pParticles[i];\n\t\tParticleRendering& r = myParam.rParticles[i];\n\t\tif (r.isSelected && myVar.timeFactor != 0.0f) {\n\t\t\tstd::cout << \"Size: \" << r.previousSize << std::endl;\n\t\t}\n\t}\n}\n\nvoid pinParticles() {\n\n\tif (myVar.pinFlag) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\tmyParam.rParticles[i].isPinned = true;\n\t\t\t\tmyParam.pParticles[i].vel *= 0.0f;\n\t\t\t\tmyParam.pParticles[i].prevVel *= 0.0f;\n\t\t\t\tmyParam.pParticles[i].acc *= 0.0f;\n\t\t\t\tmyParam.pParticles[i].ke *= 0.0f;\n\t\t\t\tmyParam.pParticles[i].prevKe *= 0.0f;\n\t\t\t}\n\t\t}\n\t\tmyVar.pinFlag = false;\n\t}\n\n\tif (myVar.unPinFlag) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tif (myParam.rParticles[i].isSelected) {\n\t\t\t\tmyParam.rParticles[i].isPinned = false;\n\t\t\t}\n\t\t}\n\t\tmyVar.unPinFlag = false;\n\t}\n\n\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\tif (myParam.rParticles[i].isPinned) {\n\t\t\tmyParam.pParticles[i].vel *= 0.0f;\n\t\t\tmyParam.pParticles[i].prevVel *= 0.0f;\n\t\t\tmyParam.pParticles[i].acc *= 0.0f;\n\t\t\tmyParam.pParticles[i].ke *= 0.0f;\n\t\t\tmyParam.pParticles[i].prevKe *= 0.0f;\n\t\t}\n\t}\n}\n\nvoid pinParticles3D() {\n\n\tif (myVar.pinFlag) {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\tmyParam.rParticles3D[i].isPinned = true;\n\t\t\t\tmyParam.pParticles3D[i].vel *= 0.0f;\n\t\t\t\tmyParam.pParticles3D[i].prevVel *= 0.0f;\n\t\t\t\tmyParam.pParticles3D[i].acc *= 0.0f;\n\t\t\t\tmyParam.pParticles3D[i].ke *= 0.0f;\n\t\t\t\tmyParam.pParticles3D[i].prevKe *= 0.0f;\n\t\t\t}\n\t\t}\n\t\tmyVar.pinFlag = false;\n\t}\n\n\tif (myVar.unPinFlag) {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\tmyParam.rParticles3D[i].isPinned = false;\n\t\t\t}\n\t\t}\n\t\tmyVar.unPinFlag = false;\n\t}\n\n\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\tif (myParam.rParticles3D[i].isPinned) {\n\t\t\tmyParam.pParticles3D[i].vel *= 0.0f;\n\t\t\tmyParam.pParticles3D[i].prevVel *= 0.0f;\n\t\t\tmyParam.pParticles3D[i].acc *= 0.0f;\n\t\t\tmyParam.pParticles3D[i].ke *= 0.0f;\n\t\t\tmyParam.pParticles3D[i].prevKe *= 0.0f;\n\t\t}\n\t}\n}\n\nvoid plyFileCreation(std::ofstream& file) {\n\tuint32_t visibleParticles = 0;\n\n\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\tif (myParam.rParticles[i].isDarkMatter) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tvisibleParticles++;\n\t}\n\n\tconstexpr size_t headerSize = 200;\n\tconstexpr size_t avgLineSize = 90;\n\tstd::string buffer;\n\tbuffer.reserve(headerSize + visibleParticles * avgLineSize);\n\n\tbuffer += \"ply\\nformat ascii 1.0\\nelement vertex \";\n\tbuffer += std::to_string(visibleParticles);\n\tbuffer += \"\\nproperty float x\\nproperty float y\\nproperty float z\\nproperty uchar red\\nproperty uchar green\\nproperty uchar blue\\nproperty float radius\\nend_header\\n\";\n\n\tchar lineBuffer[64];\n\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\n\t\tParticlePhysics& p = myParam.pParticles[i];\n\t\tParticleRendering& r = myParam.rParticles[i];\n\n\t\tif (r.isDarkMatter) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfloat posX = ((p.pos.x / myVar.domainSize.x) * 2.0f) - 1.0f;\n\t\tfloat posY = ((p.pos.y / myVar.domainSize.y) * 2.0f) - 1.0f;\n\n\t\tfloat domainRatio = myVar.domainSize.x / myVar.domainSize.y;\n\t\tposX *= domainRatio;\n\n\t\tfloat sizeMultiplier = 10.0f;\n\n\t\tposX *= sizeMultiplier;\n\t\tposY *= sizeMultiplier;\n\n\t\tint len = sprintf(lineBuffer, \"%.6g %.6g %.6g %d %d %d %.6g\\n\",\n\t\t\tposX, posY, 0.0f, static_cast<int>(r.color.r), static_cast<int>(r.color.g), static_cast<int>(r.color.b), r.size);\n\t\tbuffer.append(lineBuffer, len);\n\t}\n\n\tfile << buffer;\n}\n\nvoid plyFileCreation3D(std::ofstream& file) {\n\tuint32_t visibleParticles = 0;\n\n\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\tif (myParam.rParticles3D[i].isDarkMatter) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tvisibleParticles++;\n\t}\n\n\tconstexpr size_t headerSize = 200;\n\tconstexpr size_t avgLineSize = 90;\n\tstd::string buffer;\n\tbuffer.reserve(headerSize + visibleParticles * avgLineSize);\n\n\tbuffer += \"ply\\nformat ascii 1.0\\nelement vertex \";\n\tbuffer += std::to_string(visibleParticles);\n\tbuffer += \"\\nproperty float x\\nproperty float y\\nproperty float z\\nproperty uchar red\\nproperty uchar green\\nproperty uchar blue\\nproperty float radius\\nend_header\\n\";\n\n\tchar lineBuffer[64];\n\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\n\t\tParticlePhysics3D& p = myParam.pParticles3D[i];\n\t\tParticleRendering3D& r = myParam.rParticles3D[i];\n\n\t\tif (r.isDarkMatter) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfloat posX = p.pos.x;\n\t\tfloat posY = p.pos.y;\n\t\tfloat posZ = p.pos.z;\n\n\t\tfloat sizeMultiplier = 1.0f;\n\n\t\tposX *= sizeMultiplier;\n\t\tposY *= sizeMultiplier;\n\t\tposZ *= sizeMultiplier;\n\n\t\tint len = sprintf(lineBuffer, \"%.6g %.6g %.6g %d %d %d %.6g\\n\",\n\t\t\tposX, posY, posZ, static_cast<int>(r.color.r), static_cast<int>(r.color.g), static_cast<int>(r.color.b), r.size);\n\t\tbuffer.append(lineBuffer, len);\n\t}\n\n\tfile << buffer;\n}\n\nvoid exportPly() {\n\n\tstatic bool wasExportingLastFrame = false;\n\tstatic std::filesystem::path currentSequenceDir;\n\tstatic int currentFrameNumber = 0;\n\tstatic int currentSequenceNumber = -1;\n\n\tif (myVar.exportPlySeqFlag) {\n\t\tif (!wasExportingLastFrame) {\n\n\t\t\tstd::filesystem::path mainExportDir = \"Export3D\";\n\n\t\t\tif (!std::filesystem::exists(mainExportDir)) {\n\t\t\t\tif (!std::filesystem::create_directory(mainExportDir)) {\n\t\t\t\t\tstd::cerr << \"Couldn't create Export3D directory\" << std::endl;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tint maxSequence = -1;\n\t\t\tfor (const auto& entry : std::filesystem::directory_iterator(mainExportDir)) {\n\t\t\t\tif (entry.is_directory()) {\n\t\t\t\t\tstd::string folderName = entry.path().filename().string();\n\t\t\t\t\tif (folderName.rfind(\"GESequence_\", 0) == 0) {\n\t\t\t\t\t\tstd::string numberStr = folderName.substr(11);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tint number = std::stoi(numberStr);\n\t\t\t\t\t\t\tif (number > maxSequence) {\n\t\t\t\t\t\t\t\tmaxSequence = number;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (...) {\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrentSequenceNumber = maxSequence + 1;\n\t\t\tstd::string subfolderName = \"GESequence_\" + std::to_string(currentSequenceNumber);\n\t\t\tcurrentSequenceDir = mainExportDir / subfolderName;\n\n\t\t\tif (!std::filesystem::create_directory(currentSequenceDir)) {\n\t\t\t\tstd::cerr << \"Couldn't create new sequence folder: \" << currentSequenceDir << std::endl;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcurrentFrameNumber = 0;\n\t\t\tstd::cout << \"Started new sequence export: \" << currentSequenceDir << std::endl;\n\t\t}\n\n\t\tstd::string customName = \"GEParticles_\";\n\t\tstd::ostringstream filenameStream;\n\t\tfilenameStream << customName\n\t\t\t<< std::setw(3) << std::setfill('0') << currentSequenceNumber << \"_\"\n\t\t\t<< std::setw(4) << std::setfill('0') << currentFrameNumber << \".ply\";\n\n\t\tstd::filesystem::path filePath = currentSequenceDir / filenameStream.str();\n\n\t\tstd::ofstream plyFile(filePath);\n\t\tif (!plyFile) {\n\t\t\tstd::cerr << \"Couldn't write file: \" << filePath << std::endl;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!myVar.is3DMode) {\n\t\t\tplyFileCreation(plyFile);\n\t\t}\n\t\telse {\n\t\t\tplyFileCreation3D(plyFile);\n\t\t}\n\n\t\tplyFile.close();\n\t\tstd::cout << \"Particles at frame \" << currentFrameNumber\n\t\t\t<< \" exported to \" << filePath << std::endl;\n\n\t\tcurrentFrameNumber++;\n\n\t\tmyVar.plyFrameNumber = currentFrameNumber;\n\t}\n\telse {\n\t\tmyVar.plyFrameNumber = 0;\n\t}\n\n\twasExportingLastFrame = myVar.exportPlySeqFlag;\n\n\tif (myVar.exportPlyFlag) {\n\n\t\tstd::filesystem::path exportDir = \"Export3D\";\n\t\tif (!std::filesystem::exists(exportDir)) {\n\t\t\tif (!std::filesystem::create_directory(exportDir)) {\n\t\t\t\tstd::cerr << \"Couldn't create Export3D directory\" << std::endl;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tstd::filesystem::path exportDirIndividual = exportDir / \"IndividualFiles\";\n\t\tif (!std::filesystem::exists(exportDirIndividual)) {\n\t\t\tif (!std::filesystem::create_directory(exportDirIndividual)) {\n\t\t\t\tstd::cerr << \"Couldn't create IndividualFiles directory\" << std::endl;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tstd::string namePrefix = \"ParticlesOut_\";\n\n\t\tint maxNumber = 0;\n\t\tfor (const auto& entry : std::filesystem::directory_iterator(exportDirIndividual)) {\n\t\t\tif (entry.is_regular_file()) {\n\t\t\t\tstd::string filename = entry.path().filename().string();\n\n\t\t\t\tif (filename.rfind(namePrefix, 0) == 0 && filename.size() > namePrefix.size() + 4 &&\n\t\t\t\t\tfilename.substr(filename.size() - 4) == \".ply\") {\n\n\t\t\t\t\tsize_t startPos = namePrefix.size();\n\t\t\t\t\tsize_t length = filename.size() - startPos - 4;\n\t\t\t\t\tstd::string numberStr = filename.substr(startPos, length);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tint number = std::stoi(numberStr);\n\t\t\t\t\t\tif (number > maxNumber) {\n\t\t\t\t\t\t\tmaxNumber = number;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcatch (const std::invalid_argument&) {\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tint nextNumber = maxNumber + 1;\n\t\tstd::filesystem::path filePath = exportDirIndividual / (namePrefix + std::to_string(nextNumber) + \".ply\");\n\t\tstd::ofstream plyFile(filePath);\n\n\t\tif (!plyFile) {\n\t\t\tstd::cerr << \"Couldn't write file\" << std::endl;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!myVar.is3DMode) {\n\t\t\tplyFileCreation(plyFile);\n\t\t}\n\t\telse {\n\t\t\tplyFileCreation3D(plyFile);\n\t\t}\n\n\t\tplyFile.close();\n\t\tstd::cout << \"ply export successful! File saved as: \" << filePath << std::endl;\n\n\t\tmyVar.exportPlyFlag = false;\n\t}\n}\n\n//const char* computeTest = R\"(\n//#version 430\n//\n//layout(std430, binding = 0) buffer inPosVel { float posVel[]; };\n//\n//layout(std430, binding = 2) buffer inMass   { float mass[]; };\n//\n//layout(local_size_x = 256) in;\n//const int tileSize = 256;\n//\n//uniform float dt;\n//uniform int pCount;\n//\n//const float G = 6.67430e-11;\n//\n//shared vec2 tilePos[tileSize];\n//shared float tileMass[tileSize];\n//\n//void main() {\n//    uint idx = gl_GlobalInvocationID.x;\n//    if (idx >= pCount) return;\n//\n//    vec2 myPos = vec2(posVel[idx], posVel[idx + pCount]);\n//    vec2 myAcc = vec2(0.0f);\n//\n//    for (int tileStart = 0; tileStart < pCount; tileStart += tileSize) {\n//\n//        uint localIdx = gl_LocalInvocationID.x;\n//        uint globalTileIdx = tileStart + localIdx;\n//\n//        if (globalTileIdx < pCount) {\n//            tilePos[localIdx]  = vec2(posVel[globalTileIdx], posVel[globalTileIdx + pCount]);\n//            tileMass[localIdx] = mass[globalTileIdx];\n//        } else {\n//            tilePos[localIdx]  = vec2(0.0f);\n//            tileMass[localIdx] = 0.0f;\n//        }\n//\n//        barrier();\n//\n//int limit = min(tileSize, pCount - tileStart);\n//\n//        for (int j = 0; j < limit; j++) {\n//            uint otherIdx = tileStart + j;\n//            if (otherIdx == idx) continue;\n//\n//            vec2 d = tilePos[j] - myPos;\n//            float rSq  = dot(d, d) + 4.0f;\n//            float invR = inversesqrt(rSq);\n//            myAcc += G * tileMass[j] * d * invR * invR * invR;\n//        }\n//    }\n//\n//    posVel[idx + 2 * pCount] += dt * 1.5f * myAcc.x;\n//    posVel[idx + 3 * pCount] += dt * 1.5f * myAcc.y;\n//\n//    posVel[idx] += posVel[idx + 2 * pCount] * dt;\n//    posVel[idx + pCount] += posVel[idx + 3 * pCount] * dt;\n//}\n//)\";\n\nconst char* computeTest = R\"(\n#version 430\n\nlayout(std430, binding = 0) buffer inPData { float pData[]; };\n\nlayout(std430, binding = 2) buffer inMass   { float mass[]; };\n\nlayout(std430, binding = 3) buffer inGrid   { float grid[]; };\n\nlayout(std430, binding = 4) buffer inNext   { uint next[]; };\n\nstruct GridChildren {\n    uvec2 subGrids[2];\n};\n\nlayout(std430, binding = 5) buffer inChildren {\n    GridChildren children[];\n};\n\nlayout(std430, binding = 6) buffer inPIdx   { uint endStart[]; };\n\nlayout(local_size_x = 256) in;\nconst int tileSize = 256;\n\nuniform float dt;\nuniform float theta;\nuniform float globalHeatConductivity;\n\nuniform int pCount;\nuniform int nCount;\n\nuniform bool periodicBoundary;\nuniform bool isTempEnabled;\n\nuniform vec2 domainSize;\nuniform float softening;\n\nconst float G = 6.67430e-11;\n\nvoid main() {\n    uint idx = gl_GlobalInvocationID.x;\n    if (idx >= pCount) return;\n    \n    vec2 myPos = vec2(pData[idx], pData[idx + pCount]);\n    vec2 totalForce = vec2(0.0f);\n    uint gridIdx = 0;\n    \n    while (gridIdx < nCount) {\n        if (grid[gridIdx + 2 * nCount] <= 0.0f) {\n            gridIdx += next[gridIdx] + 1;\n            continue;\n        }\n        \n        vec2 d = vec2(grid[gridIdx], grid[gridIdx + nCount]) - myPos;\n        if (periodicBoundary) {\n            d.x -= domainSize.x * round(d.x / domainSize.x);\n            d.y -= domainSize.y * round(d.y / domainSize.y);\n        }\n        \n        float distSq = dot(d, d) + softening * softening;\n\n        bool subgridsEmpty = true;\nfor (int i = 0; i < 2; ++i) {\n    uvec2 sg = children[gridIdx].subGrids[i];\n    for (int j = 0; j < 2; ++j) {\n        uint childIdx = sg[j];\n        if (childIdx != 0xFFFFFFFFu) {\n            subgridsEmpty = false;\n            break;\n        }\n    }\n    if (!subgridsEmpty) break;\n}\n        \n        if (grid[gridIdx + 3 * nCount] * grid[gridIdx + 3 * nCount] < (theta * theta) * distSq || subgridsEmpty) {\n\n\n            float invDist = inversesqrt(distSq);\n            float forceMag = G * mass[idx] * grid[gridIdx + 2 * nCount] * invDist * invDist * invDist;\n            totalForce += d * forceMag;\n\n            gridIdx += next[gridIdx] + 1;\n        } else {\n            gridIdx++;\n        }\n    }\n    \n    pData[idx + 2 * pCount] = totalForce.x / mass[idx];\n    pData[idx + 3 * pCount] = totalForce.y / mass[idx];\n}\n)\";\n\nGLuint ssboPData, ssboAcc, ssboMass, ssboGrid, ssboGridNext, ssboGridChildren, ssboGridPIdx;\n\nsize_t mb = 512;\n\nsize_t reserveSize = (1024 * 1024 * mb) / sizeof(float);\n\nGLuint gravityProgram;\n\nstruct GridChildren {\n\tuint32_t subGrids[2][2];\n};\n\nvoid gravityKernel() {\n\n\tglGenBuffers(1, &ssboPData);\n\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPData);\n\tglBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(float), nullptr, GL_STREAM_COPY);\n\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssboPData);\n\n\tglGenBuffers(1, &ssboMass);\n\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboMass);\n\tglBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(float), nullptr, GL_DYNAMIC_DRAW);\n\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, ssboMass);\n\n\tglGenBuffers(1, &ssboGrid);\n\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGrid);\n\tglBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(float), nullptr, GL_DYNAMIC_DRAW);\n\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, ssboGrid);\n\n\tglGenBuffers(1, &ssboGridNext);\n\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridNext);\n\tglBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);\n\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, ssboGridNext);\n\n\tglGenBuffers(1, &ssboGridChildren);\n\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridChildren);\n\tglBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(GridChildren), nullptr, GL_DYNAMIC_DRAW);\n\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, ssboGridChildren);\n\n\tglGenBuffers(1, &ssboGridPIdx);\n\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridPIdx);\n\tglBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);\n\tglBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, ssboGridPIdx);\n\n\tgravityProgram = glCreateProgram();\n\tGLuint shader = glCreateShader(GL_COMPUTE_SHADER);\n\tglShaderSource(shader, 1, &computeTest, nullptr);\n\tglCompileShader(shader);\n\n\tGLint success;\n\tglGetShaderiv(shader, GL_COMPILE_STATUS, &success);\n\n\tif (!success) {\n\t\tchar infoLog[512];\n\t\tglGetShaderInfoLog(shader, 512, nullptr, infoLog);\n\t\tstd::cerr << \"Compute shader compilation failed:\\n\" << infoLog << std::endl;\n\t}\n\n\tglAttachShader(gravityProgram, shader);\n\tglLinkProgram(gravityProgram);\n\n\tglGetProgramiv(gravityProgram, GL_LINK_STATUS, &success);\n\tif (!success) {\n\t\tchar infoLog[512];\n\t\tglGetProgramInfoLog(gravityProgram, 512, nullptr, infoLog);\n\t\tstd::cerr << \"Shader gravityProgram linking failed:\\n\" << infoLog << std::endl;\n\t}\n\n\tglDeleteShader(shader);\n}\n\nvoid buildKernels() {\n\tgravityKernel();\n\tfield.fieldGravityDisplayKernel();\n\tsph.neighborSearchKernel();\n\n\tsph.bitonicSortKernel();\n\tsph.offsetKernel();\n\tsph.offsetResetKernel();\n\n\trayMarcher.Init();\n}\n\nstd::vector<float> pData;\nstd::vector<float> massVector;\n\nstd::vector<float> gridParams;\n\nstd::vector<uint32_t> gridNext;\n\nstd::vector<GridChildren> gridChildrenVector;\n\nstd::vector<uint32_t> gridPIndices;\n\nvoid gpuGravity() {\n\tif (!myParam.pParticles.empty()) {\n\n\t\tpData.clear();\n\t\tmassVector.clear();\n\n\t\tgridParams.clear();\n\n\t\tgridNext.clear();\n\n\t\tgridChildrenVector.clear();\n\n\t\tgridPIndices.clear();\n\n\t\tpData.resize(myParam.pParticles.size() * 4);\n\t\tmassVector.resize(myParam.pParticles.size());\n\n\t\tgridParams.resize(globalNodes.size() * 4);\n\n\t\tgridNext.resize(globalNodes.size());\n\n\t\tgridChildrenVector.resize(globalNodes.size());\n\n\t\tgridPIndices.resize(globalNodes.size() * 2);\n\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\n\t\t\tpData[i] = myParam.pParticles[i].pos.x;\n\t\t\tpData[i + myParam.pParticles.size()] = myParam.pParticles[i].pos.y;\n\n\t\t\t/*pData[i + 2 * myParam.pParticles.size()] = myParam.pParticles[i].vel.x;\n\t\t\tpData[i + 3 * myParam.pParticles.size()] = myParam.pParticles[i].vel.y;*/\n\n\t\t\tpData[i + 2 * myParam.pParticles.size()] = 0.0f;\n\t\t\tpData[i + 3 * myParam.pParticles.size()] = 0.0f;\n\n\t\t\tmassVector[i] = myParam.pParticles[i].mass;\n\t\t}\n\n\t\tfor (size_t i = 0; i < globalNodes.size(); i++) {\n\n\t\t\tgridParams[i] = globalNodes[i].centerOfMass.x;\n\t\t\tgridParams[i + globalNodes.size()] = globalNodes[i].centerOfMass.y;\n\n\t\t\tgridParams[i + 2 * globalNodes.size()] = globalNodes[i].gridMass;\n\n\t\t\tgridParams[i + 3 * globalNodes.size()] = globalNodes[i].size;\n\n\t\t\tgridNext[i] = globalNodes[i].next;\n\n\t\t\tGridChildren children;\n\t\t\tmemcpy(children.subGrids, globalNodes[i].subGrids, sizeof(uint32_t) * 4);\n\t\t\tgridChildrenVector[i] = children;\n\n\t\t\tgridPIndices[i] = globalNodes[i].startIndex;\n\t\t\tgridPIndices[i + globalNodes.size()] = globalNodes[i].endIndex;\n\t\t}\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPData);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, pData.size() * sizeof(float), pData.data());\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboMass);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, massVector.size() * sizeof(float), massVector.data());\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGrid);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gridParams.size() * sizeof(float), gridParams.data());\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridNext);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gridNext.size() * sizeof(uint32_t), gridNext.data());\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridChildren);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gridChildrenVector.size() * sizeof(GridChildren), gridChildrenVector.data());\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridPIdx);\n\t\tglBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gridPIndices.size() * sizeof(uint32_t), gridPIndices.data());\n\n\t\tglUseProgram(gravityProgram);\n\t\tglUniform1f(glGetUniformLocation(gravityProgram, \"dt\"), myVar.timeFactor);\n\t\tglUniform1f(glGetUniformLocation(gravityProgram, \"globalHeatConductivity\"), myVar.globalHeatConductivity);\n\n\t\tglUniform1i(glGetUniformLocation(gravityProgram, \"pCount\"), static_cast<int>(myParam.pParticles.size()));\n\t\tglUniform1i(glGetUniformLocation(gravityProgram, \"nCount\"), static_cast<int>(globalNodes.size()));\n\n\t\tglUniform1i(glGetUniformLocation(gravityProgram, \"periodicBoundary\"), myVar.isPeriodicBoundaryEnabled);\n\t\tglUniform1i(glGetUniformLocation(gravityProgram, \"isTempEnabled\"), myVar.isTempEnabled);\n\n\t\tglUniform2f(glGetUniformLocation(gravityProgram, \"domainSize\"), myVar.domainSize.x, myVar.domainSize.y);\n\t\tglUniform1f(glGetUniformLocation(gravityProgram, \"theta\"), myVar.theta);\n\t\tglUniform1f(glGetUniformLocation(gravityProgram, \"softening\"), myVar.softening);\n\n\t\tGLuint numGroups = (myParam.pParticles.size() + 255) / 256;\n\t\tglDispatchCompute(numGroups, 1, 1);\n\t\tglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);\n\n\t\tglBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPData);\n\t\tfloat* ptrPos = (float*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\n\t\t\t/*myParam.pParticles[i].pos.x = ptrPos[i];\n\t\t\tmyParam.pParticles[i].pos.y = ptrPos[i + myParam.pParticles.size()];\n\n\t\t\tmyParam.pParticles[i].vel.x = ptrPos[i + 2 * myParam.pParticles.size()];\n\t\t\tmyParam.pParticles[i].vel.y = ptrPos[i + 3 * myParam.pParticles.size()];*/\n\n\t\t\tmyParam.pParticles[i].acc.x = ptrPos[i + 2 * myParam.pParticles.size()];\n\t\t\tmyParam.pParticles[i].acc.y = ptrPos[i + 3 * myParam.pParticles.size()];\n\t\t}\n\n\t\tglUnmapBuffer(GL_SHADER_STORAGE_BUFFER);\n\t}\n}\n\nvoid freeGPUMemory() {\n\n\tglDeleteBuffers(1, &ssboPData);\n\tglDeleteBuffers(1, &ssboMass);\n\n\tglDeleteBuffers(1, &ssboGrid);\n\tglDeleteBuffers(1, &ssboGridNext);\n\tglDeleteBuffers(1, &ssboGridChildren);\n\tglDeleteBuffers(1, &ssboGridPIdx);\n\n\tglDeleteProgram(gravityProgram);\n\n\tglDeleteBuffers(1, &field.ssboParticlesPos);\n\tglDeleteBuffers(1, &field.ssboParticlesMass);\n\tglDeleteBuffers(1, &field.ssboCellsData);\n\n\tglDeleteProgram(field.gravityDisplayProgram);\n\n\tglDeleteBuffers(1, &sph.ssboPPos);\n\tglDeleteBuffers(1, &sph.ssboCellKeys);\n\tglDeleteBuffers(1, &sph.ssboCellXs);\n\tglDeleteBuffers(1, &sph.ssboCellYs);\n\tglDeleteBuffers(1, &sph.ssboParticleIndices);\n\n\tglDeleteProgram(sph.neighborSearchProgram);\n\n\trayMarcher.Unload();\n}\n\n// -------- This is an unused quadtree creation method I made for learning purposes. It builds the quadtree from Morton keys -------- //\n\n//struct Barrier {\n//\tuint32_t idx;\n//\tint level;\n//\n//\tBarrier(uint32_t idx, int level) {\n//\t\tthis->idx = idx;\n//\t\tthis->level = level;\n//\t}\n//};\n//\n//struct MortonData {\n//\tuint64_t key;\n//\tfloat mass;\n//\tglm::vec2 pos;\n//};\n//\n//std::vector<Node> TestTree::nodesTest;\n//\n//void mortonToQuadtree() {\n//\n//\tstd::vector<MortonData> compactedMorton;\n//\n//\tfloat currentKeyMass = 0.0f;\n//\tuint32_t duplicateAmount = 0;\n//\n//\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n//\n//\t\tif (!compactedMorton.empty()) {\n//\t\t\tif (myParam.pParticles[i].mortonKey != compactedMorton.back().key) {\n//\n//\t\t\t\tif (duplicateAmount > 0) {\n//\t\t\t\t\tcompactedMorton.back().mass += currentKeyMass;\n//\t\t\t\t}\n//\n//\t\t\t\tcompactedMorton.push_back({ myParam.pParticles[i].mortonKey, myParam.pParticles[i].mass, myParam.pParticles[i].pos });\n//\n//\t\t\t\tcurrentKeyMass = 0.0f;\n//\t\t\t\tduplicateAmount = 0;\n//\t\t\t}\n//\t\t\telse {\n//\t\t\t\tduplicateAmount++;\n//\t\t\t\tcurrentKeyMass += myParam.pParticles[i].mass;\n//\t\t\t}\n//\t\t}\n//\t\telse {\n//\t\t\tcompactedMorton.push_back({ myParam.pParticles[i].mortonKey, myParam.pParticles[i].mass, myParam.pParticles[i].pos });\n//\t\t}\n//\t}\n//\n//\tint currentP = 0;\n//\n//\tint start = 0;\n//\tint end = compactedMorton.size();\n//\n//\tint level = 0;\n//\n//\tint totalBits = 18; // 18 bits per axis\n//\n//\tstd::vector<Barrier> barriers;\n//\n//\tglm::vec2 min = glm::vec2(std::numeric_limits<float>::max());\n//\tglm::vec2 max = glm::vec2(std::numeric_limits<float>::lowest());\n//\n//\tfor (const ParticlePhysics& particle : myParam.pParticles) {\n//\t\tmin = glm::min(min, particle.pos);\n//\t\tmax = glm::max(max, particle.pos);\n//\t}\n//\n//\tfloat boundingBoxSize = glm::max(max.x - min.x, max.y - min.y);\n//\n//\tglm::vec2 center = (min + max) * 0.5f;\n//\n//\tglm::vec2 boundingBoxPos = center - boundingBoxSize * 0.5f;\n//\n//\tTestTree::nodesTest.clear();\n//\n//\tTestTree::nodesTest.emplace_back(Node({ boundingBoxPos.x, boundingBoxPos.y }, boundingBoxSize, 0.0f, { 0.0f, 0.0f }, 0, 0));\n//\n//\twhile (currentP < compactedMorton.size()) {\n//\n//\t\tfloat nodeMass = 0.0f;\n//\t\tglm::vec2 nodeCoM = { 0.0f, 0.0f };\n//\n//\t\tfor (size_t i = start; i < end; i++) {\n//\n//\t\t\tnodeMass += compactedMorton[i].mass;\n//\n//\t\t\tnodeCoM += compactedMorton[i].pos * compactedMorton[i].mass;\n//\n//\t\t\tif (i + 1 >= compactedMorton.size()) {\n//\n//\t\t\t\tnodeCoM /= nodeMass;\n//\n//\t\t\t\tNode node = { TestTree::nodesTest[0].pos, TestTree::nodesTest[0].size, nodeMass, nodeCoM, 1, level + 1};\n//\n//\t\t\t\tfor (int j = 0; j < level + 1; j++) {\n//\n//\t\t\t\t\tint internalShift = (totalBits - 1 - j) * 2;\n//\t\t\t\t\tbool lr = (compactedMorton[start].key >> internalShift) & 1u;\n//\t\t\t\t\tbool ud = (compactedMorton[start].key >> (internalShift + 1)) & 1u;\n//\n//\t\t\t\t\tnode.pos.x = node.pos.x + (lr ? (node.size * 0.5f) : 0.0f);\n//\t\t\t\t\tnode.pos.y = node.pos.y + (ud ? (node.size * 0.5f) : 0.0f);\n//\n//\t\t\t\t\tnode.size *= 0.5f;\n//\t\t\t\t}\n//\n//\t\t\t\tTestTree::nodesTest.push_back(node);\n//\n//\t\t\t\tTestTree::nodesTest[0].mass += nodeMass;\n//\t\t\t\tTestTree::nodesTest[0].CoM += nodeCoM;\n//\n//\t\t\t\tnodeMass = 0.0f;\n//\t\t\t\tnodeCoM = { 0.0f, 0.0f };\n//\n//\t\t\t\tlevel++;\n//\t\t\t\tcurrentP++;\n//\t\t\t\tend = compactedMorton.size();\n//\t\t\t\tbreak;\n//\t\t\t}\n//\n//\t\t\tif (!barriers.empty() && barriers.back().idx == i + 1) {\n//\n//\t\t\t\tnodeCoM /= nodeMass;\n//\n//\t\t\t\tuint32_t next = end - start;\n//\n//\t\t\t\tbool isLeaf = next == 1 ? 1 : 0;\n//\n//\t\t\t\tNode node = { TestTree::nodesTest[0].pos, TestTree::nodesTest[0].size, nodeMass, nodeCoM, next, level + 1};\n//\n//\t\t\t\tfor (int j = 0; j < level + 1; j++) {\n//\n//\t\t\t\t\tint internalShift = (totalBits - 1 - j) * 2;\n//\t\t\t\t\tbool lr = (compactedMorton[start].key >> internalShift) & 1u;\n//\t\t\t\t\tbool ud = (compactedMorton[start].key >> (internalShift + 1)) & 1u;\n//\n//\t\t\t\t\tnode.pos.x = node.pos.x + (lr ? (node.size * 0.5f) : 0.0f);\n//\t\t\t\t\tnode.pos.y = node.pos.y + (ud ? (node.size * 0.5f) : 0.0f);\n//\n//\t\t\t\t\tnode.size *= 0.5f;\n//\t\t\t\t}\n//\n//\t\t\t\tTestTree::nodesTest.push_back(node);\n//\n//\t\t\t\tTestTree::nodesTest[0].mass += nodeMass;\n//\t\t\t\tTestTree::nodesTest[0].CoM += nodeCoM;\n//\n//\t\t\t\tnodeMass = 0.0f;\n//\t\t\t\tnodeCoM = { 0.0f, 0.0f };\n//\n//\t\t\t\tint prevShift = (totalBits - 1 - level) * 2;\n//\n//\t\t\t\tunsigned int bits1 = (compactedMorton[i].key >> prevShift) & 0x3FFFFu;\n//\t\t\t\tunsigned int bits2 = (compactedMorton[i - 1].key >> prevShift) & 0x3FFFFu;\n//\n//\t\t\t\tif (bits1 == bits2) {\n//\t\t\t\t\tlevel++;\n//\n//\t\t\t\t\tcurrentP = start;\n//\n//\t\t\t\t\tend = barriers.back().idx;\n//\n//\t\t\t\t\tbreak;\n//\t\t\t\t}\n//\n//\t\t\t\tlevel = barriers.back().level;\n//\n//\t\t\t\tif (barriers.size() > 1) {\n//\t\t\t\t\tend = barriers.at(barriers.size() - 2).idx;\n//\t\t\t\t}\n//\t\t\t\telse {\n//\t\t\t\t\tend = compactedMorton.size();\n//\t\t\t\t}\n//\n//\t\t\t\tcurrentP = barriers.back().idx;\n//\n//\t\t\t\tstart = barriers.back().idx;\n//\n//\t\t\t\tTestTree::nodesTest[barriers.back().idx].next = TestTree::nodesTest.size();\n//\n//\t\t\t\tbarriers.pop_back();\n//\n//\t\t\t\tbreak;\n//\t\t\t}\n//\n//\t\t\tint shift = (totalBits - 1 - level) * 2;\n//\n//\t\t\tunsigned int bits1 = (compactedMorton[i].key >> shift) & 0b11u;\n//\t\t\tunsigned int bits2 = (compactedMorton[i + 1].key >> shift) & 0b11u;\n//\n//\t\t\tif (bits1 != bits2 && i + 1 != end) {\n//\n//\t\t\t\tnodeCoM /= nodeMass;\n//\n//\t\t\t\tNode node = { TestTree::nodesTest[0].pos, TestTree::nodesTest[0].size, nodeMass, nodeCoM, 1, level + 1};\n//\n//\t\t\t\tfor (int j = 0; j < level + 1; j++) {\n//\n//\t\t\t\t\tint internalShift = (totalBits - 1 - j) * 2;\n//\t\t\t\t\tbool lr = (compactedMorton[start].key >> internalShift) & 1u;\n//\t\t\t\t\tbool ud = (compactedMorton[start].key >> (internalShift + 1)) & 1u;\n//\n//\t\t\t\t\tnode.pos.x = node.pos.x + (lr ? (node.size * 0.5f) : 0.0f);\n//\t\t\t\t\tnode.pos.y = node.pos.y + (ud ? (node.size * 0.5f) : 0.0f);\n//\n//\t\t\t\t\tnode.size *= 0.5f;\n//\t\t\t\t}\n//\n//\t\t\t\tTestTree::nodesTest.push_back(node);\n//\n//\t\t\t\tTestTree::nodesTest[0].mass += nodeMass;\n//\t\t\t\tTestTree::nodesTest[0].CoM += nodeCoM;\n//\n//\t\t\t\tnodeMass = 0.0f;\n//\t\t\t\tnodeCoM = { 0.0f, 0.0f };\n//\n//\t\t\t\tend = i + 1;\n//\n//\t\t\t\tif (end - start > 1) {\n//\n//\t\t\t\t\tbarriers.push_back(Barrier(end, level));\n//\n//\t\t\t\t\tlevel++;\n//\t\t\t\t\tcurrentP = start;\n//\t\t\t\t}\n//\t\t\t\telse if (end - start == 1) {\n//\t\t\t\t\tstart = end;\n//\n//\t\t\t\t\tcurrentP = start;\n//\n//\t\t\t\t\tif (!barriers.empty()) {\n//\t\t\t\t\t\tend = barriers.back().idx;\n//\t\t\t\t\t}\n//\t\t\t\t\telse {\n//\t\t\t\t\t\tend = compactedMorton.size();\n//\t\t\t\t\t}\n//\t\t\t\t}\n//\n//\t\t\t\tbreak;\n//\t\t\t}\n//\t\t}\n//\t}\n//\n//\tTestTree::nodesTest[0].CoM /= TestTree::nodesTest[0].mass;\n//\n//\tfor (int i = totalBits; i >= 1; i--) {\n//\t\tfor (int j = 0; j < TestTree::nodesTest.size(); j++) {\n//\t\t\tif (TestTree::nodesTest[j].level != i) continue;\n//\n//\t\t\tint currentLevel = TestTree::nodesTest[j].level;\n//\t\t\tint count = 0;\n//\n//\t\t\tfor (int k = j + 1; k < TestTree::nodesTest.size(); k++) {\n//\t\t\t\tif (TestTree::nodesTest[k].level <= currentLevel) break;\n//\n//\t\t\t\tif (TestTree::nodesTest[k].level == currentLevel + 1) {\n//\t\t\t\t\tcount += 1 + TestTree::nodesTest[k].next;\n//\t\t\t\t}\n//\t\t\t}\n//\n//\t\t\tTestTree::nodesTest[j].next = count;\n//\t\t}\n//\t}\n//\n//\n//\t/*static int idx = 1;\n//\n//\tif (IO::shortcutPress(KEY_LEFT)) {\n//\t\tidx--;\n//\t}\n//\telse if (IO::shortcutPress(KEY_RIGHT)) {\n//\t\tidx++;\n//\t}*/\n//\n//\t/*std::cout << \"Current Idx: \" << idx << std::endl;*/\n//\n//\t//for (size_t i = 0; i < TestTree::nodesTest.size(); i++) {\n//\t//\t//if (i == idx) {\n//\t//\t\tDrawRectangleLinesEx({ TestTree::nodesTest[i].pos.x, TestTree::nodesTest[i].pos.y, TestTree::nodesTest[i].size, TestTree::nodesTest[i].size }, 0.04f, RED);\n//\n//\n//\t//\t\t/*DrawText(TextFormat(\"%i\", i), TestTree::nodesTest[i].pos.x + TestTree::nodesTest[i].size * 0.5f,\n//\t//\t\t\tTestTree::nodesTest[i].pos.y + TestTree::nodesTest[i].size * 0.5f, 5.0f, { 255, 255, 255, 128 });*/\n//\n//\t//\t\t/*if (TestTree::nodesTest[i].mass > 0.0f) {\n//\t//\t\t\tDrawCircleV({ TestTree::nodesTest[i].CoM.x, TestTree::nodesTest[i].CoM.y }, 2.0f, { 180,50,50,128 });\n//\t//\t\t}*/\n//\t//\t//}\n//\t//}\n//}\n\nfloat boundingBoxSize = 0.0f;\nglm::vec2 boundingBoxPos = { 0.0f, 0.0f };\n\nglm::vec3 boundingBox() {\n\tglm::vec2 min = glm::vec2(std::numeric_limits<float>::max());\n\tglm::vec2 max = glm::vec2(std::numeric_limits<float>::lowest());\n\n\tfor (const ParticlePhysics& particle : myParam.pParticles) {\n\t\tmin = glm::min(min, particle.pos);\n\t\tmax = glm::max(max, particle.pos);\n\t}\n\n\tboundingBoxSize = glm::max(max.x - min.x, max.y - min.y);\n\n\tglm::vec2 center = (min + max) * 0.5f;\n\n\tboundingBoxPos = center - boundingBoxSize * 0.5f;\n\n\treturn { boundingBoxPos.x, boundingBoxPos.y, boundingBoxSize };\n}\n\nuint32_t gridRootIndex;\n\nglm::vec3 bb = { 0.0f, 0.0f, 0.0f };\n\nvoid updateScene() {\n\n\tif (!myVar.is3DMode) {\n\t\t// If menu is active, do not use mouse input for non-menu stuff. I keep raylib's own mouse input for the menu but the custom IO for non-menu stuff\n\t\tif (myParam.rightClickSettings.isMenuActive) {\n\t\t\tImGui::GetIO().WantCaptureMouse = true;\n\t\t}\n\t}\n\n\tif (myVar.isOpticsEnabled) {\n\n\t\tif (!myParam.pParticles.empty()) {\n\t\t\tmyParam.pParticles.clear();\n\t\t\tmyParam.rParticles.clear();\n\t\t}\n\n\t\tif (!myParam.pParticles3D.empty()) {\n\t\t\tmyParam.pParticles3D.clear();\n\t\t\tmyParam.rParticles3D.clear();\n\t\t}\n\n\t\tmyVar.is3DMode = false;\n\n\t\tlighting.rayLogic(myVar, myParam);\n\t}\n\n\tif (myVar.isGravityFieldEnabled) {\n\t\tfield.initializeCells(myVar);\n\t}\n\n\tmyVar.G = 6.674e-11 * myVar.gravityMultiplier;\n\n\tif (IO::shortcutPress(KEY_SPACE)) {\n\t\tif (!myVar.isPlaybackOn) {\n\t\t\tmyVar.isTimePlaying = !myVar.isTimePlaying;\n\t\t}\n\t\telse {\n\t\t\tmyVar.runPlayback = !myVar.runPlayback;\n\t\t}\n\t}\n\n\tif (!myVar.is3DMode) {\n\t\tmyParam.particlesSpawning.particlesInitialConditions(physics, myVar, myParam);\n\t}\n\n\tif (!myVar.is3DMode) {\n\t\tcopyPaste.copyPasteParticles(myVar, myParam, physics);\n\t\tcopyPaste.copyPasteOptics(myParam, lighting);\n\t}\n\n\tif (myVar.timeFactor != 0.0f) {\n\t\tphysics.integrateStart(myParam.pParticles, myParam.rParticles, myVar);\n\n\t\tif (!myVar.isPeriodicBoundaryEnabled && !myVar.sphGround && !myVar.infiniteDomain) {\n\t\t\tphysics.pruneParticles(myParam.pParticles, myParam.rParticles, myVar);\n\t\t}\n\t}\n\n\tif (myVar.timeFactor != 0.0f && !myParam.pParticles.empty()) {\n\t\tbb = boundingBox();\n\t\tmyParam.morton.computeMortonKeys(myParam.pParticles, bb);\n\t\tmyParam.morton.sortParticlesByMortonKey(myParam.pParticles, myParam.rParticles);\n\t}\n\n\tif (myVar.timeFactor != 0.0f && !myVar.naive && !myParam.pParticles.empty()) {\n\n\t\t/*if (!myParam.pParticles.empty()) {\n\t\t\tmortonToQuadtree();\n\t\t}*/\n\n\t\tglobalNodes.clear();\n\n\t\tQuadtree root(myParam.pParticles, myParam.rParticles, bb);\n\n\t\tgridRootIndex = 0;\n\t}\n\n\tmyVar.gridExists = gridRootIndex != -1 && !globalNodes.empty();\n\n\tmyVar.halfDomainWidth = myVar.domainSize.x * 0.5f;\n\tmyVar.halfDomainHeight = myVar.domainSize.y * 0.5f;\n\n\tmyVar.timeFactor = myVar.fixedDeltaTime * myVar.timeStepMultiplier * static_cast<float>(myVar.isTimePlaying);\n\n\tif (myVar.drawQuadtree && !myVar.is3DMode) {\n\t\tfor (uint32_t i = 0; i < globalNodes.size(); i++) {\n\n\t\t\tNode& q = globalNodes[i];\n\n\t\t\tDrawLineV(\n\t\t\t\t{ q.pos.x, q.pos.y },\n\t\t\t\t{ q.pos.x + q.size, q.pos.y },\n\t\t\t\tWHITE\n\t\t\t);\n\n\t\t\tDrawLineV(\n\t\t\t\t{ q.pos.x, q.pos.y + q.size },\n\t\t\t\t{ q.pos.x + q.size, q.pos.y + q.size },\n\t\t\t\tWHITE\n\t\t\t);\n\n\t\t\tDrawLineV(\n\t\t\t\t{ q.pos.x, q.pos.y },\n\t\t\t\t{ q.pos.x, q.pos.y + q.size },\n\t\t\t\tWHITE\n\t\t\t);\n\n\t\t\tDrawLineV(\n\t\t\t\t{ q.pos.x + q.size, q.pos.y },\n\t\t\t\t{ q.pos.x + q.size, q.pos.y + q.size },\n\t\t\t\tWHITE\n\t\t\t);\n\n\t\t\t/*if (q.gridMass > 0.0f) {\n\t\t\t\tDrawCircleV({ q.centerOfMass.x, q.centerOfMass.y }, 2.0f, { 180,50,50,128 });\n\t\t\t}*/\n\n\t\t\t//DrawText(TextFormat(\"%i\", i), q.pos.x + q.size * 0.5f, q.pos.y + q.size * 0.5f, 5.0f, { 255, 255, 255, 128 });\n\t\t}\n\t}\n\n\tfor (ParticleRendering& rParticle : myParam.rParticles) {\n\t\trParticle.totalRadius = rParticle.size * myVar.particleTextureHalfSize * myVar.particleSizeMultiplier;\n\t}\n\n\tif (myVar.constraintsEnabled ||\n\t\tmyVar.drawConstraints ||\n\t\tmyVar.visualizeMesh ||\n\t\tmyVar.isBrushDrawing ||\n\t\tmyParam.colorVisuals.densityColor ||\n\t\tmyVar.isDensitySizeEnabled ||\n\t\tmyVar.isSPHEnabled ||\n\t\t(myParam.colorVisuals.densityColor && myVar.timeFactor > 0.0f && !myVar.isGravityFieldEnabled)) {\n\n\t\tif (!myVar.hasAVX2) {\n\t\t\tmyParam.neighborSearchV2.newGrid(myParam.pParticles);\n\t\t\tmyParam.neighborSearchV2.neighborAmount(myParam.pParticles, myParam.rParticles);\n\t\t}\n\t\telse {\n\t\t\tmyParam.neighborSearchV2AVX2.newGridAVX2(myParam.pParticles);\n\t\t\tmyParam.neighborSearchV2AVX2.neighborAmount(myParam.pParticles, myParam.rParticles);\n\t\t}\n\t}\n\n\tif (myVar.isMergerEnabled) {\n\t\tmyParam.neighborSearch.UpdateNeighbors(myParam.pParticles, myParam.rParticles);\n\n\t\tuint32_t maxPossibleId = 50000000;\n\n\t\tif (myVar.isMergerEnabled) {\n\t\t\tmyParam.neighborSearch.densityRadius = 10.0f;\n\t\t}\n\t\telse {\n\t\t\tmyParam.neighborSearch.densityRadius = myParam.neighborSearch.originalDensityRadius;\n\t\t}\n\n\t\tmyParam.neighborSearch.idToIndexTable.resize(maxPossibleId + 1);\n\n#pragma omp parallel for\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tmyParam.neighborSearch.idToIndexTable[myParam.pParticles[i].id] = i;\n\t\t}\n\t}\n\n\tif (!myVar.is3DMode) {\n\t\tmyParam.brush.brushSize();\n\t}\n\n\tif (myVar.isMergerEnabled) {\n\n\t\t// Heuristics for performance\n\t\tconstexpr float minCellSize = 2.0f;\n\t\tconstexpr float maxCellSize = 80.0f;\n\n\t\tmyParam.neighborSearch.cellSize = 0.0f;\n\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); ++i) {\n\t\t\tauto& rP = myParam.rParticles[i];\n\n\t\t\tif (rP.isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfloat candidate = rP.totalRadius * 2.0f;\n\n\t\t\tmyParam.neighborSearch.cellSize = std::max(myParam.neighborSearch.cellSize, candidate);\n\t\t}\n\n\t\tmyParam.neighborSearch.cellSize = std::clamp(myParam.neighborSearch.cellSize, minCellSize, maxCellSize);\n\t}\n\telse {\n\t\tmyParam.neighborSearch.cellSize = 3.0f;\n\t}\n\n\tif (myVar.constraintsEnabled && !myVar.isBrushDrawing) {\n\t\tphysics.createConstraints(myParam.pParticles, myParam.rParticles, myVar.constraintAfterDrawingFlag, myVar, myParam);\n\t}\n\telse if (!myVar.constraintsEnabled && !myVar.isBrushDrawing) {\n\t\tphysics.constraintMap.clear();\n\t\tphysics.particleConstraints.clear();\n\n\t\tphysics.idToIndexTable.clear();\n\t}\n\n\tif ((myVar.timeFactor != 0.0f /*&& myVar.gridExists*/) || myVar.isGPUEnabled) {\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tmyParam.pParticles[i].acc = { 0.0f, 0.0f };\n\t\t}\n\n\t\tif (myVar.gravityMultiplier != 0.0f || myVar.isTempEnabled) {\n\t\t\tif (!myVar.isGPUEnabled) {\n\n\t\t\t\tphysics.flattenParticles(myParam.pParticles);\n\n\t\t\t\tif (!myVar.naive) {\n\t\t\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\t\t\tif (myParam.pParticles.size() < 5000) {\n\t\t\t\t\t\t\tphysics.naiveGravity(myParam.pParticles, myVar);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tphysics.calculateForceFromGrid(myVar);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tif (myParam.pParticles.size() < 5000) {\n\t\t\t\t\t\t\tphysics.naiveGravityAVX2(myParam.pParticles, myVar);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tphysics.calculateForceFromGridAVX2(myVar);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\t\t\tphysics.naiveGravity(myParam.pParticles, myVar);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tphysics.naiveGravityAVX2(myParam.pParticles, myVar);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tphysics.readFlattenBack(myParam.pParticles);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tgpuGravity();\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.isMergerEnabled)\n\t\t\tphysics.mergerSolver(myParam.pParticles, myParam.rParticles, myVar, myParam);\n\n\t\tif (myVar.isSPHEnabled) {\n\t\t\tsph.pcisphSolver(myVar, myParam);\n\t\t}\n\n\t\tphysics.constraints(myParam.pParticles, myParam.rParticles, myVar);\n\n\t\tship.spaceshipLogic(myParam.pParticles, myParam.rParticles, myVar.isShipGasEnabled);\n\n\t\tif (myVar.isTempEnabled) {\n\t\t\tphysics.temperatureCalculation(myParam.pParticles, myParam.rParticles, myVar);\n\t\t}\n\n\t\tphysics.integrateEnd(myParam.pParticles, myParam.rParticles, myVar);\n\n\t}\n\telse {\n\t\tphysics.constraints(myParam.pParticles, myParam.rParticles, myVar);\n\t}\n\n\tfield.gpuGravityDisplay(myParam, myVar);\n\n\tmyParam.trails.trailLogic(myVar, myParam);\n\n\tmyParam.myCamera.cameraFollowObject(myVar, myParam);\n\n\tmyParam.particleSelection.clusterSelection(myVar, myParam, false);\n\n\tmyParam.particleSelection.particleSelection(myVar, myParam, false);\n\n\tmyParam.particleSelection.manyClustersSelection(myVar, myParam);\n\n\tmyParam.particleSelection.boxSelection(myParam, myVar.is3DMode);\n\n\tmyParam.particleSelection.invertSelection(myParam.rParticles);\n\n\tmyParam.particleSelection.deselection(myParam.rParticles);\n\n\tmyParam.particleSelection.selectedParticlesStoring(myParam);\n\n\tmyParam.densitySize.sizeByDensity(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D,\n\t\tmyVar.isDensitySizeEnabled, myVar.isForceSizeEnabled,\n\t\tmyVar.particleSizeMultiplier, myVar.is3DMode);\n\n\tmyParam.particleDeletion.deleteSelected(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.is3DMode);\n\tmyParam.particleDeletion.deleteStrays(myParam.pParticles, myParam.rParticles, myVar.isSPHEnabled, myParam.pParticles3D, myParam.rParticles3D, myVar.is3DMode);\n\n\tmyParam.brush.particlesAttractor(myVar, myParam);\n\n\tmyParam.brush.particlesSpinner(myVar, myParam);\n\n\tmyParam.brush.particlesGrabber(myVar, myParam);\n\n\tmyParam.brush.temperatureBrush(myVar, myParam);\n\n\tmyParam.brush.eraseBrush(myVar, myParam);\n\n\tconst float boundsX = 3840.0f;\n\tconst float boundsY = 2160.0f;\n\n\tfloat targetX = myParam.myCamera.camera.target.x;\n\tfloat targetY = myParam.myCamera.camera.target.y;\n\n\tbool isOutsideBounds =\n\t\t(targetX >= boundsX || targetX <= -boundsX) ||\n\t\t(targetY >= boundsY || targetY <= -boundsY);\n\n\tif (isOutsideBounds) {\n\n\t\tfloat distX = std::max(std::abs(targetX) - boundsX, 0.0f);\n\t\tfloat distY = std::max(std::abs(targetY) - boundsY, 0.0f);\n\t\tfloat maxDist = std::max(distX, distY);\n\n\t\tconst float fadeRange = 10000.0f;\n\t\tfloat normalizedDist = 1.0f - std::min(maxDist / fadeRange, 1.0f);\n\n\t\tgeSound.musicVolMultiplier = normalizedDist;\n\t}\n\telse {\n\t\tgeSound.musicVolMultiplier = 1.0f;\n\t}\n\n\tpinParticles();\n\n\texportPly();\n\n\t//selectedParticleDebug();\n\n\t/*if (grid != nullptr) {\n\t\tdelete grid;\n\t}*/\n\n\tmyParam.myCamera.hasCamMoved();\n\n\tif (myVar.sphGround) {\n\t\tmyVar.infiniteDomain = false;\n\t\tmyVar.isPeriodicBoundaryEnabled = false;\n\t}\n}\n\nfloat boundingBox3DSize = 0.0f;\nglm::vec3 boundingBox3DPos = { 0.0f, 0.0f, 0.0f };\n\nglm::vec4 boundingBox3D(std::vector<ParticlePhysics3D>& pParticles) {\n\tglm::vec3 min = glm::vec3(std::numeric_limits<float>::max());\n\tglm::vec3 max = glm::vec3(std::numeric_limits<float>::lowest());\n\n\tfor (size_t i = 0; i < pParticles.size(); i++) {\n\t\tParticlePhysics3D& particle = pParticles[i];\n\n\t\tmin = glm::min(min, particle.pos);\n\t\tmax = glm::max(max, particle.pos);\n\t}\n\n\tboundingBox3DSize = glm::max(glm::max(max.x - min.x, max.y - min.y), max.z - min.z);\n\n\tglm::vec3 center = (min + max) * 0.5f;\n\tboundingBox3DPos = center - boundingBox3DSize * 0.5f;\n\n\treturn { boundingBox3DPos.x, boundingBox3DPos.y, boundingBox3DPos.z, boundingBox3DSize };\n}\n\nuint32_t gridRootIndex3D;\n\nglm::vec4 bb3D = { 0.0f, 0.0f, 0.0f, 0.0f };\n\nvoid particleBoxClipping() {\n\n\tglm::vec3 avgPos = { 0.0f, 0.0f, 0.0f };\n\n\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\tParticlePhysics3D& p = myParam.pParticles3D[i];\n\t\tParticleRendering3D& r = myParam.rParticles3D[i];\n\n\t\tif (!r.isSelected) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tavgPos += p.pos;\n\t}\n\n\tavgPos /= myParam.pParticlesSelected3D.size();\n\n\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\tParticlePhysics3D& p = myParam.pParticles3D[i];\n\t\tParticleRendering3D& r = myParam.rParticles3D[i];\n\n\t\tif (!r.isSelected) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (myVar.clipSelectedX) {\n\n\t\t\tif (!myVar.clipSelectedXInv) {\n\t\t\t\tif (p.pos.x >= avgPos.x) {\n\t\t\t\t\tr.color = { 0,0,0,0 };\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (p.pos.x <= avgPos.x) {\n\t\t\t\t\tr.color = { 0,0,0,0 };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.clipSelectedY) {\n\n\t\t\tif (!myVar.clipSelectedYInv) {\n\t\t\t\tif (p.pos.y >= avgPos.y) {\n\t\t\t\t\tr.color = { 0,0,0,0 };\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (p.pos.y <= avgPos.y) {\n\t\t\t\t\tr.color = { 0,0,0,0 };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (myVar.clipSelectedZ) {\n\n\t\t\tif (!myVar.clipSelectedZInv) {\n\t\t\t\tif (p.pos.z >= avgPos.z) {\n\t\t\t\t\tr.color = { 0,0,0,0 };\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (p.pos.z <= avgPos.z) {\n\t\t\t\t\tr.color = { 0,0,0,0 };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid mode3D() {\n\n\tmyVar.lowResRayMarching = false;\n\n\tif (!myParam.pParticles3D.empty()) {\n\t\tif (myVar.timeFactor != 0.0f) {\n\t\t\tmyVar.lowResRayMarching = true;\n\t\t}\n\n\t\tif (myParam.myCamera3D.HasCameraChanged()) {\n\t\t\tmyVar.lowResRayMarching = true;\n\t\t}\n\t}\n\n\tif (myVar.is3DMode) {\n\t\t// If menu is active, do not use mouse input for non-menu stuff. I keep raylib's own mouse input for the menu but the custom IO for non-menu stuff\n\t\tif (myParam.rightClickSettings.isMenuActive) {\n\t\t\tImGui::GetIO().WantCaptureMouse = true;\n\t\t}\n\t}\n\n\tif (!myParam.playbackFrames.empty() && !myVar.playbackRecord) {\n\t\tmyVar.isPlaybackOn = true;\n\t}\n\telse {\n\t\tmyVar.isPlaybackOn = false;\n\t}\n\n\tmyParam.particlesSpawning3D.particlesInitialConditions(physics3D, myVar, myParam);\n\n\tcopyPaste.copyPasteParticles3D(myVar, myParam, physics3D);\n\n\tif (myVar.timeFactor != 0.0f && !myVar.isPlaybackOn) {\n\t\tphysics3D.integrateStart3D(myParam.pParticles3D, myParam.rParticles3D, myVar);\n\n\t\tif (!myVar.isPeriodicBoundaryEnabled && !myVar.sphGround && !myVar.infiniteDomain) {\n\t\t\tphysics3D.pruneParticles(myParam.pParticles3D, myParam.rParticles3D, myVar);\n\t\t}\n\t}\n\n\tif (myVar.timeFactor != 0.0f && !myParam.pParticles3D.empty()) {\n\t\tbb3D = boundingBox3D(myParam.pParticles3D);\n\t\tmyParam.morton.computeMortonKeys3D(myParam.pParticles3D, bb3D);\n\t\tmyParam.morton.sortParticlesByMortonKey3D(myParam.pParticles3D, myParam.rParticles3D);\n\t}\n\n\tif (myVar.timeFactor != 0.0f && !myVar.naive && !myVar.isPlaybackOn) {\n\n\t\tglobalNodes3D.clear();\n\n\t\tOctree root3D(myParam.pParticles3D, myParam.rParticles3D, glm::vec3{ bb3D.x,bb3D.y,bb3D.z }, bb3D.w);\n\n\t\tgridRootIndex3D = 0;\n\t}\n\n\tfor (ParticleRendering3D& rParticle : myParam.rParticles3D) {\n\t\trParticle.totalRadius = rParticle.size * myVar.particleTextureHalfSize * myVar.particleSizeMultiplier;\n\t}\n\n\tmyVar.grid3DExists = gridRootIndex3D != -1 && !globalNodes3D.empty();\n\n\tmyVar.halfDomain3DWidth = myVar.domainSize3D.x * 0.5f;\n\tmyVar.halfDomain3DHeight = myVar.domainSize3D.y * 0.5f;\n\tmyVar.halfDomain3DDepth = myVar.domainSize3D.z * 0.5f;\n\n\tif (myVar.drawQuadtree && !myVar.isPlaybackOn) {\n\t\tfor (uint32_t i = 0; i < globalNodes3D.size(); i++) {\n\n\t\t\tNode3D& q = globalNodes3D[i];\n\n\t\t\tDrawCubeWiresV({ q.pos.x, q.pos.y, q.pos.z }, { q.size, q.size, q.size }, { 128,128,128,64 });\n\n\t\t\t/*if (q.gridMass > 0.0f) {\n\t\t\t\tDrawCircleV({ q.centerOfMass.x, q.centerOfMass.y }, 2.0f, { 180,50,50,128 });\n\t\t\t}*/\n\n\t\t\t//DrawText(TextFormat(\"%i\", i), q.pos.x + q.size * 0.5f, q.pos.y + q.size * 0.5f, 5.0f, { 255, 255, 255, 128 });\n\t\t}\n\t}\n\n\tif (myVar.constraintsEnabled ||\n\t\tmyVar.drawConstraints ||\n\t\tmyVar.visualizeMesh ||\n\t\tmyVar.isBrushDrawing ||\n\t\tmyVar.isMergerEnabled ||\n\t\tmyParam.colorVisuals.densityColor ||\n\t\tmyVar.isDensitySizeEnabled ||\n\t\tmyVar.isSPHEnabled ||\n\t\tmyParam.colorVisuals.densityColor && myVar.timeFactor > 0.0f && !myVar.isGravityFieldEnabled && !myVar.isPlaybackOn) {\n\n\t\tif (!myVar.hasAVX2) {\n\t\t\tmyParam.neighborSearch3DV2.newGrid(myParam.pParticles3D);\n\t\t\tmyParam.neighborSearch3DV2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D);\n\t\t}\n\t\telse {\n\t\t\tmyParam.neighborSearch3DV2AVX2.newGridAVX2(myParam.pParticles3D);\n\t\t\tmyParam.neighborSearch3DV2AVX2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D);\n\t\t}\n\t}\n\n\tmyParam.brush3D.brushPosLogic(myParam, myVar);\n\tmyParam.brush3D.brushSize();\n\n\tif (myVar.constraintsEnabled && !myVar.isBrushDrawing) {\n\t\tphysics3D.createConstraints(myParam.pParticles3D, myParam.rParticles3D, myVar.constraintAfterDrawingFlag, myVar, myParam);\n\t}\n\telse if (!myVar.constraintsEnabled && !myVar.isBrushDrawing) {\n\t\tphysics3D.constraintMap.clear();\n\t\tphysics3D.particleConstraints.clear();\n\n\t\tphysics3D.idToIndexTable.clear();\n\t}\n\n\tif ((myVar.timeFactor != 0.0f /*&& myVar.gridExists*/) && !myVar.isPlaybackOn) {\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tmyParam.pParticles3D[i].acc = { 0.0f, 0.0f, 0.0f };\n\t\t}\n\n\t\tif (myVar.gravityMultiplier != 0.0f || myVar.isTempEnabled) {\n\n\n\t\t\tphysics3D.flattenParticles3D(myParam.pParticles3D);\n\n\t\t\tif (!myVar.naive) {\n\t\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\t\tif (myParam.pParticles3D.size() < 5000) {\n\t\t\t\t\t\tphysics3D.naiveGravity3D(myParam.pParticles3D, myVar);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tphysics3D.calculateForceFromGrid3D(myVar);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tif (myParam.pParticles3D.size() < 5000) {\n\t\t\t\t\t\tphysics3D.naiveGravity3DAVX2(myParam.pParticles3D, myVar);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tphysics3D.calculateForceFromGrid3DAVX2(myVar);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!myVar.hasAVX2) {\n\t\t\t\t\tphysics3D.naiveGravity3D(myParam.pParticles3D, myVar);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tphysics3D.naiveGravity3DAVX2(myParam.pParticles3D, myVar);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tphysics3D.readFlattenBack3D(myParam.pParticles3D);\n\t\t}\n\n\t\tif (myVar.isSPHEnabled) {\n\t\t\tsph3D.pcisphSolver(myVar, myParam);\n\t\t}\n\n\t\tphysics3D.constraints(myParam.pParticles3D, myParam.rParticles3D, myVar);\n\n\t\tship.spaceshipLogic3D(myParam.pParticles3D, myParam.rParticles3D, myVar.isShipGasEnabled, myParam.myCamera3D.cam3D);\n\n\t\tif (myVar.isTempEnabled) {\n\t\t\tphysics3D.temperatureCalculation(myParam.pParticles3D, myParam.rParticles3D, myVar);\n\t\t}\n\n\t\tphysics3D.integrateEnd3D(myParam.pParticles3D, myParam.rParticles3D, myVar);\n\n\t}\n\telse {\n\t\tphysics3D.constraints(myParam.pParticles3D, myParam.rParticles3D, myVar);\n\t}\n\n\tmyParam.trails.trailLogic3D(myVar, myParam);\n\n\tmyParam.myCamera3D.cameraFollowObject(myVar, myParam);\n\n\tmyParam.particleSelection3D.particleSelection(myVar, myParam, false);\n\tmyParam.particleSelection3D.clusterSelection(myVar, myParam, false);\n\tmyParam.particleSelection3D.manyClustersSelection(myVar, myParam);\n\tmyParam.particleSelection3D.boxSelection(myParam);\n\tmyParam.particleSelection3D.invertSelection(myParam.rParticles3D);\n\tmyParam.particleSelection3D.deselection(myParam.rParticles3D);\n\tmyParam.particleSelection3D.selectedParticlesStoring(myParam);\n\n\tif (!myVar.runPlayback) {\n\t\tmyParam.densitySize.sizeByDensity(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D,\n\t\t\tmyVar.isDensitySizeEnabled, myVar.isForceSizeEnabled,\n\t\t\tmyVar.particleSizeMultiplier, myVar.is3DMode);\n\t}\n\n\tmyParam.particleDeletion.deleteSelected(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.is3DMode);\n\tmyParam.particleDeletion.deleteStrays(myParam.pParticles, myParam.rParticles, myVar.isSPHEnabled, myParam.pParticles3D, myParam.rParticles3D, myVar.is3DMode);\n\n\tmyParam.brush3D.particlesGrabber(myVar, myParam);\n\tmyParam.brush3D.eraseBrush(myVar, myParam);\n\tmyParam.brush3D.temperatureBrush(myVar, myParam);\n\tmyParam.brush3D.particlesAttractor(myVar, myParam);\n\tmyParam.brush3D.particlesSpinner(myVar, myParam);\n\n\tpinParticles3D();\n\n\tif (myVar.isRayMarcherOn) {\n\t\trayMarcher.Run(myParam.myCamera3D, myParam.pParticles3D, myParam.rParticles3D, myVar.lowResRayMarching);\n\t}\n}\n\nvoid drawConstraints3D() {\n\n\tif (myVar.visualizeMesh) {\n\t\tstruct MeshEdge {\n\t\t\tsize_t i, j;\n\t\t\tglm::vec3 piPos, pjCorrectedPos;\n\t\t\tColor lineColor;\n\t\t\tfloat distSq;\n\t\t};\n\t\tstd::vector<MeshEdge> edges;\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tParticlePhysics3D& pi = myParam.pParticles3D[i];\n\n\t\t\tstd::vector<size_t> neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.pos);\n\n\t\t\tfor (size_t j : neighborIndices) {\n\t\t\t\tsize_t neighborIndex = j;\n\n\t\t\t\tif (neighborIndex == i) continue;\n\n\t\t\t\tParticlePhysics3D& pj = myParam.pParticles3D[neighborIndex];\n\n\t\t\t\tif (pi.id < pj.id) {\n\t\t\t\t\tglm::vec3 delta = pj.pos - pi.pos;\n\t\t\t\t\tglm::vec3 periodicDelta = delta;\n\n\t\t\t\t\tif (abs(delta.x) > myVar.domainSize3D.x * 0.5f) {\n\t\t\t\t\t\tperiodicDelta.x += (delta.x > 0) ? -myVar.domainSize3D.x : myVar.domainSize3D.x;\n\t\t\t\t\t}\n\t\t\t\t\tif (abs(delta.y) > myVar.domainSize3D.y * 0.5f) {\n\t\t\t\t\t\tperiodicDelta.y += (delta.y > 0) ? -myVar.domainSize3D.y : myVar.domainSize3D.y;\n\t\t\t\t\t}\n\t\t\t\t\tif (abs(delta.z) > myVar.domainSize3D.z * 0.5f) {\n\t\t\t\t\t\tperiodicDelta.z += (delta.z > 0) ? -myVar.domainSize3D.z : myVar.domainSize3D.z;\n\t\t\t\t\t}\n\n\t\t\t\t\tglm::vec3 pjCorrectedPos = pi.pos + periodicDelta;\n\t\t\t\t\tglm::vec3 mid = (pi.pos + pjCorrectedPos) * 0.5f;\n\t\t\t\t\tVector3 midRay = { mid.x, mid.y, mid.z };\n\n\t\t\t\t\tMeshEdge edge;\n\t\t\t\t\tedge.i = i;\n\t\t\t\t\tedge.j = neighborIndex;\n\t\t\t\t\tedge.piPos = pi.pos;\n\t\t\t\t\tedge.pjCorrectedPos = pjCorrectedPos;\n\t\t\t\t\tedge.lineColor = ColorLerp(myParam.rParticles3D[i].color, myParam.rParticles3D[neighborIndex].color, 0.5f);\n\t\t\t\t\tedge.distSq = Vector3DistanceSqr(midRay, myParam.myCamera3D.cam3D.position);\n\t\t\t\t\tedges.push_back(edge);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tstd::sort(edges.begin(), edges.end(), [](const MeshEdge& a, const MeshEdge& b) {\n\t\t\treturn a.distSq > b.distSq;\n\t\t\t});\n\n\t\tfor (const MeshEdge& edge : edges) {\n\t\t\tDrawLine3D(\n\t\t\t\t{ edge.piPos.x, edge.piPos.y, edge.piPos.z },\n\t\t\t\t{ edge.pjCorrectedPos.x, edge.pjCorrectedPos.y, edge.pjCorrectedPos.z },\n\t\t\t\tedge.lineColor\n\t\t\t);\n\t\t}\n\t}\n\n\tif (myVar.drawConstraints && !physics3D.particleConstraints.empty()) {\n\t\tstd::vector<size_t> sortedConstraintIndices(physics3D.particleConstraints.size());\n\t\tfor (size_t i = 0; i < sortedConstraintIndices.size(); ++i) {\n\t\t\tsortedConstraintIndices[i] = i;\n\t\t}\n\n\t\tstd::sort(sortedConstraintIndices.begin(), sortedConstraintIndices.end(), [&](size_t a, size_t b) {\n\t\t\tauto& constraintA = physics3D.particleConstraints[a];\n\t\t\tauto& constraintB = physics3D.particleConstraints[b];\n\n\t\t\tif (constraintA.id1 >= physics3D.idToIndexTable.size() || constraintA.id2 >= physics3D.idToIndexTable.size()) return false;\n\t\t\tif (constraintB.id1 >= physics3D.idToIndexTable.size() || constraintB.id2 >= physics3D.idToIndexTable.size()) return false;\n\n\t\t\tint64_t idx1A = physics3D.idToIndexTable[constraintA.id1];\n\t\t\tint64_t idx2A = physics3D.idToIndexTable[constraintA.id2];\n\t\t\tint64_t idx1B = physics3D.idToIndexTable[constraintB.id1];\n\t\t\tint64_t idx2B = physics3D.idToIndexTable[constraintB.id2];\n\n\t\t\tif (idx1A == -1 || idx2A == -1) return false;\n\t\t\tif (idx1B == -1 || idx2B == -1) return false;\n\n\t\t\tglm::vec3 midA = (myParam.pParticles3D[idx1A].pos + myParam.pParticles3D[idx2A].pos) * 0.5f;\n\t\t\tglm::vec3 midB = (myParam.pParticles3D[idx1B].pos + myParam.pParticles3D[idx2B].pos) * 0.5f;\n\n\t\t\tVector3 posA = { midA.x, midA.y, midA.z };\n\t\t\tVector3 posB = { midB.x, midB.y, midB.z };\n\n\t\t\tfloat distA = Vector3DistanceSqr(posA, myParam.myCamera3D.cam3D.position);\n\t\t\tfloat distB = Vector3DistanceSqr(posB, myParam.myCamera3D.cam3D.position);\n\n\t\t\treturn distA > distB;\n\t\t\t});\n\n\t\tfor (size_t i = 0; i < sortedConstraintIndices.size(); i++) {\n\t\t\tsize_t idx = sortedConstraintIndices[i];\n\t\t\tauto& constraint = physics3D.particleConstraints[idx];\n\n\t\t\tif (constraint.id1 >= physics3D.idToIndexTable.size() || constraint.id2 >= physics3D.idToIndexTable.size()) continue;\n\t\t\tint64_t idx1 = physics3D.idToIndexTable[constraint.id1];\n\t\t\tint64_t idx2 = physics3D.idToIndexTable[constraint.id2];\n\t\t\tif (idx1 == -1 || idx2 == -1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tParticlePhysics3D& pi = myParam.pParticles3D[idx1];\n\t\t\tParticlePhysics3D& pj = myParam.pParticles3D[idx2];\n\t\t\tglm::vec3 delta = pj.pos - pi.pos;\n\t\t\tglm::vec3 periodicDelta = delta;\n\t\t\tif (abs(delta.x) > myVar.domainSize3D.x * 0.5f) {\n\t\t\t\tperiodicDelta.x += (delta.x > 0) ? -myVar.domainSize3D.x : myVar.domainSize3D.x;\n\t\t\t}\n\t\t\tif (abs(delta.y) > myVar.domainSize3D.y * 0.5f) {\n\t\t\t\tperiodicDelta.y += (delta.y > 0) ? -myVar.domainSize3D.y : myVar.domainSize3D.y;\n\t\t\t}\n\t\t\tif (abs(delta.z) > myVar.domainSize3D.z * 0.5f) {\n\t\t\t\tperiodicDelta.z += (delta.z > 0) ? -myVar.domainSize3D.z : myVar.domainSize3D.z;\n\t\t\t}\n\t\t\tglm::vec3 pjCorrectedPos = pi.pos + periodicDelta;\n\n\t\t\tColor lineColor;\n\t\t\tif (myVar.constraintStressColor) {\n\t\t\t\tfloat maxStress = 0.0f;\n\t\t\t\tif (myVar.constraintMaxStressColor > 0.0f) {\n\t\t\t\t\tmaxStress = myVar.constraintMaxStressColor;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tmaxStress = constraint.resistance * myVar.globalConstraintResistance * constraint.restLength * 0.18f;\n\t\t\t\t}\n\t\t\t\tfloat stressMag = std::abs(constraint.displacement);\n\t\t\t\tfloat clampedStress = std::clamp(stressMag, 0.0f, maxStress);\n\t\t\t\tfloat normalizedStress = clampedStress / maxStress;\n\t\t\t\tfloat hue = (1.0f - normalizedStress) * 240.0f;\n\t\t\t\tlineColor = ColorFromHSV(hue, 1.0f, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlineColor = ColorLerp(myParam.rParticles3D[idx1].color, myParam.rParticles3D[idx2].color, 0.5f);\n\t\t\t}\n\t\t\tDrawLine3D({ pi.pos.x, pi.pos.y, pi.pos.z }, { pjCorrectedPos.x, pjCorrectedPos.y, pjCorrectedPos.z }, lineColor);\n\t\t}\n\t}\n}\n\nstruct ParticleDepth {\n\tint index;\n\tfloat distSq;\n};\n\nvoid savePlaybackToDisk(const std::string& filepath) {\n\tstd::ofstream outFile(filepath, std::ios::binary);\n\tif (!outFile) {\n\t\treturn;\n\t}\n\n\tsize_t numFrames = myParam.playbackFrames.size();\n\toutFile.write(reinterpret_cast<const char*>(&numFrames), sizeof(numFrames));\n\n\tfor (const auto& frame : myParam.playbackFrames) {\n\t\tsize_t numParticles = frame.size();\n\t\toutFile.write(reinterpret_cast<const char*>(&numParticles), sizeof(numParticles));\n\n\t\tfor (const auto& particle : frame) {\n\t\t\toutFile.write(reinterpret_cast<const char*>(&particle.pos), sizeof(particle.pos));\n\t\t\toutFile.write(reinterpret_cast<const char*>(&particle.previousSize), sizeof(particle.previousSize));\n\t\t\toutFile.write(reinterpret_cast<const char*>(&particle.size), sizeof(particle.size));\n\t\t\toutFile.write(reinterpret_cast<const char*>(&particle.id), sizeof(particle.id));\n\t\t\toutFile.write(reinterpret_cast<const char*>(&particle.color), sizeof(particle.color));\n\t\t}\n\t}\n\n\toutFile.close();\n}\n\nvoid loadPlaybackFromDisk(const std::string& filepath) {\n\tstd::ifstream inFile(filepath, std::ios::binary);\n\tif (!inFile) {\n\t\treturn;\n\t}\n\n\tmyParam.playbackFrames.clear();\n\n\tsize_t numFrames;\n\tinFile.read(reinterpret_cast<char*>(&numFrames), sizeof(numFrames));\n\n\tmyParam.playbackFrames.reserve(numFrames);\n\n\tfor (size_t i = 0; i < numFrames; ++i) {\n\t\tsize_t numParticles;\n\t\tinFile.read(reinterpret_cast<char*>(&numParticles), sizeof(numParticles));\n\n\t\tstd::vector<PlaybackParticle> frame;\n\t\tframe.reserve(numParticles);\n\n\t\tfor (size_t j = 0; j < numParticles; ++j) {\n\t\t\tPlaybackParticle particle;\n\t\t\tinFile.read(reinterpret_cast<char*>(&particle.pos), sizeof(particle.pos));\n\t\t\tinFile.read(reinterpret_cast<char*>(&particle.previousSize), sizeof(particle.previousSize));\n\t\t\tinFile.read(reinterpret_cast<char*>(&particle.size), sizeof(particle.size));\n\t\t\tinFile.read(reinterpret_cast<char*>(&particle.id), sizeof(particle.id));\n\t\t\tinFile.read(reinterpret_cast<char*>(&particle.color), sizeof(particle.color));\n\n\t\t\tframe.push_back(particle);\n\t\t}\n\n\t\tmyParam.playbackFrames.push_back(std::move(frame));\n\t}\n\n\tinFile.close();\n}\n\nvoid playBackLogic(Texture2D& particleBlurTex) {\n\tstatic std::vector<PlaybackParticle> targetState;\n\tstatic std::unordered_map<uint32_t, int> targetMap;\n\tstatic bool diskFramesLoaded = false;\n\tstatic bool wasRecording = false;\n\n\tif (!myParam.pParticles3D.empty() && myVar.playbackRecord && myVar.currentFrame % myVar.keyframeTickInterval == 0) {\n\t\tstd::vector<PlaybackParticle> playbackParticles;\n\t\tplaybackParticles.reserve(myParam.pParticles3D.size());\n\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tParticlePhysics3D& p = myParam.pParticles3D[i];\n\t\t\tParticleRendering3D& r = myParam.rParticles3D[i];\n\n\t\t\tif (r.isDarkMatter) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tplaybackParticles.emplace_back(PlaybackParticle{ p.pos, r.previousSize, r.size, p.id, r.color });\n\t\t}\n\t\tmyParam.playbackFrames.push_back(playbackParticles);\n\t\twasRecording = true;\n\t}\n\n\tmyVar.currentFrame++;\n\n\tif (wasRecording && !myVar.playbackRecord) {\n\t\tif (!myVar.playBackOnMemory) {\n\t\t\tsavePlaybackToDisk(myVar.playbackPath);\n\t\t}\n\t\twasRecording = false;\n\t\tdiskFramesLoaded = false;\n\t}\n\n\tif (!myVar.playbackRecord) {\n\t\tif (!myVar.playBackOnMemory && !diskFramesLoaded && !wasRecording) {\n\t\t\tloadPlaybackFromDisk(myVar.playbackPath);\n\t\t\tdiskFramesLoaded = true;\n\t\t}\n\n\t\tif (!myParam.playbackFrames.empty()) {\n\t\t\ttargetState.clear();\n\t\t\ttargetMap.clear();\n\n\t\t\tif (myVar.runPlayback) {\n\t\t\t\tmyVar.playbackProgress += myVar.playbackSpeed;\n\t\t\t}\n\n\t\t\tint indexA = (int)myVar.playbackProgress;\n\t\t\tint indexB = indexA + 1;\n\n\t\t\tif (indexB >= myParam.playbackFrames.size()) {\n\t\t\t\tmyVar.playbackProgress = 0.0f;\n\t\t\t\tindexA = 0;\n\t\t\t\tindexB = 1;\n\t\t\t}\n\n\t\t\tfloat t = myVar.playbackProgress - indexA;\n\n\t\t\tconst std::vector<PlaybackParticle>& frameA = myParam.playbackFrames[indexA];\n\t\t\tconst std::vector<PlaybackParticle>& frameB = myParam.playbackFrames[indexB];\n\n\t\t\tstd::unordered_map<uint32_t, const PlaybackParticle*> frameB_Lookup;\n\t\t\tframeB_Lookup.reserve(frameB.size());\n\t\t\tfor (const auto& p : frameB) {\n\t\t\t\tframeB_Lookup[p.id] = &p;\n\t\t\t}\n\n#pragma omp parallel\n\t\t\t{\n\t\t\t\tstd::vector<PlaybackParticle> localBuffer;\n\t\t\t\tlocalBuffer.reserve(frameA.size() / omp_get_num_threads());\n\n#pragma omp for nowait\n\t\t\t\tfor (int i = 0; i < frameA.size(); ++i) {\n\t\t\t\t\tconst auto& pA = frameA[i];\n\n\t\t\t\t\tauto it = frameB_Lookup.find(pA.id);\n\t\t\t\t\tif (it != frameB_Lookup.end()) {\n\t\t\t\t\t\tconst PlaybackParticle* pB = it->second;\n\n\t\t\t\t\t\tglm::vec3 finalPos;\n\t\t\t\t\t\tfinalPos.x = pA.pos.x + (pB->pos.x - pA.pos.x) * t;\n\t\t\t\t\t\tfinalPos.y = pA.pos.y + (pB->pos.y - pA.pos.y) * t;\n\t\t\t\t\t\tfinalPos.z = pA.pos.z + (pB->pos.z - pA.pos.z) * t;\n\n\t\t\t\t\t\tfloat finalSize = pA.size + (pB->size - pA.size) * t;\n\n\t\t\t\t\t\tColor finalColor;\n\t\t\t\t\t\tfinalColor.r = (unsigned char)(pA.color.r + (pB->color.r - pA.color.r) * t);\n\t\t\t\t\t\tfinalColor.g = (unsigned char)(pA.color.g + (pB->color.g - pA.color.g) * t);\n\t\t\t\t\t\tfinalColor.b = (unsigned char)(pA.color.b + (pB->color.b - pA.color.b) * t);\n\t\t\t\t\t\tfinalColor.a = (unsigned char)(pA.color.a + (pB->color.a - pA.color.a) * t);\n\n\t\t\t\t\t\tlocalBuffer.push_back({ finalPos, pA.previousSize, finalSize, pA.id, finalColor });\n\t\t\t\t\t}\n\t\t\t\t}\n\n#pragma omp critical\n\t\t\t\t{\n\t\t\t\t\ttargetState.insert(targetState.end(), localBuffer.begin(), localBuffer.end());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttargetMap.reserve(targetState.size());\n\t\t\tfor (int i = 0; i < targetState.size(); i++) {\n\t\t\t\ttargetMap[targetState[i].id] = i;\n\t\t\t}\n\n\t\t\tstd::vector<bool> targetUsed(targetState.size(), false);\n\n\t\t\tfor (int i = (int)myParam.pParticles3D.size() - 1; i >= 0; i--) {\n\t\t\t\tuint32_t currentID = myParam.pParticles3D[i].id;\n\n\t\t\t\tauto it = targetMap.find(currentID);\n\n\t\t\t\tif (it != targetMap.end()) {\n\t\t\t\t\tint targetIndex = it->second;\n\t\t\t\t\tconst PlaybackParticle& target = targetState[targetIndex];\n\n\t\t\t\t\tmyParam.pParticles3D[i].pos = target.pos;\n\t\t\t\t\tmyParam.rParticles3D[i].color = target.color;\n\n\t\t\t\t\tmyParam.rParticles3D[i].previousSize = target.previousSize;\n\t\t\t\t\tmyParam.rParticles3D[i].size = target.size * myVar.playbackParticlesSizeMult;\n\n\t\t\t\t\ttargetUsed[targetIndex] = true;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tsize_t lastIdx = myParam.pParticles3D.size() - 1;\n\n\t\t\t\t\tif (i != lastIdx) {\n\t\t\t\t\t\tmyParam.pParticles3D[i] = myParam.pParticles3D[lastIdx];\n\t\t\t\t\t\tmyParam.rParticles3D[i] = myParam.rParticles3D[lastIdx];\n\t\t\t\t\t}\n\n\t\t\t\t\tmyParam.pParticles3D.pop_back();\n\t\t\t\t\tmyParam.rParticles3D.pop_back();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (int i = 0; i < targetState.size(); i++) {\n\t\t\t\tif (!targetUsed[i]) {\n\t\t\t\t\tconst PlaybackParticle& target = targetState[i];\n\n\t\t\t\t\tParticlePhysics3D newP;\n\t\t\t\t\tnewP.id = target.id;\n\t\t\t\t\tnewP.pos = target.pos;\n\n\t\t\t\t\tParticleRendering3D newR;\n\t\t\t\t\tnewR.color = target.color;\n\n\t\t\t\t\tnewR.previousSize = target.previousSize;\n\t\t\t\t\tnewR.size = target.size;\n\n\t\t\t\t\tmyParam.pParticles3D.push_back(newP);\n\t\t\t\t\tmyParam.rParticles3D.push_back(newR);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (myParam.colorVisuals.selectedColor && myVar.is3DMode && myVar.isPlaybackOn) {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size(); i++) {\n\t\t\tif (myParam.rParticles3D[i].isSelected) {\n\t\t\t\tmyParam.rParticles3D[i].color = { 255, 20, 20, 255 };\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid drawMode3DRecording(Texture2D& particleBlurTex) {\n\n\tCamera3D& cam3D = myParam.myCamera3D.cam3D;\n\n\tmyParam.colorVisuals.particlesColorVisuals(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.isTempEnabled, myVar.timeFactor, myVar.is3DMode);\n\n\tplayBackLogic(particleBlurTex);\n\n\tparticleBoxClipping();\n\n\tdrawConstraints3D();\n\n\tstatic std::vector<ParticleDepth> sortedParticles;\n\n\tsize_t particleCount = myParam.pParticles3D.size();\n\tif (sortedParticles.size() != particleCount) {\n\t\tsortedParticles.resize(particleCount);\n\t}\n\n\tVector3 camPos = cam3D.position;\n\n#pragma omp parallel for\n\tfor (int i = 0; i < particleCount; ++i) {\n\t\tconst auto& p = myParam.pParticles3D[i];\n\n\t\tVector3 pPos = { p.pos.x, p.pos.y, p.pos.z };\n\n\t\tsortedParticles[i].index = i;\n\t\tsortedParticles[i].distSq = Vector3DistanceSqr(pPos, camPos);\n\t}\n\n\tstd::sort(sortedParticles.begin(), sortedParticles.end(), [](const ParticleDepth& a, const ParticleDepth& b) {\n\t\treturn a.distSq > b.distSq;\n\t\t});\n\n\tRectangle sourceRec = { 0.0f, 0.0f, (float)particleBlurTex.width, (float)particleBlurTex.height };\n\n\tfor (const auto& item : sortedParticles) {\n\t\tint idx = item.index;\n\n\t\tParticlePhysics3D& pParticle3D = myParam.pParticles3D[idx];\n\t\tParticleRendering3D& rParticle3D = myParam.rParticles3D[idx];\n\n\t\tfloat sizeValue = particleBlurTex.width * rParticle3D.size;\n\n\t\tDrawBillboardPro(\n\t\t\tcam3D,\n\t\t\tparticleBlurTex,\n\t\t\tsourceRec,\n\t\t\t{ pParticle3D.pos.x, pParticle3D.pos.y, pParticle3D.pos.z },\n\t\t\tcam3D.up,\n\t\t\t{ sizeValue, sizeValue },\n\t\t\t{ sizeValue / 2.0f, sizeValue / 2.0f },\n\t\t\t0.0f,\n\t\t\trParticle3D.color\n\t\t);\n\t}\n\n\tif (!myVar.isDensitySizeEnabled) {\n\n#pragma omp parallel for schedule(dynamic)\n\t\tfor (int i = 0; i < myParam.pParticles3D.size(); ++i) {\n\n\t\t\tParticlePhysics3D& pParticle = myParam.pParticles3D[i];\n\t\t\tParticleRendering3D& rParticle = myParam.rParticles3D[i];\n\n\t\t\tif (rParticle.canBeResized || myVar.isMergerEnabled) {\n\t\t\t\trParticle.size = rParticle.previousSize * myVar.particleSizeMultiplier;\n\t\t\t}\n\t\t\telse {\n\t\t\t\trParticle.size = rParticle.previousSize;\n\t\t\t}\n\t\t}\n\t}\n\n\tmyParam.subdivision.subdivideParticles3D(myVar, myParam);\n\n\tmyParam.trails.drawTrail3D(myParam.rParticles3D, particleBlurTex, myParam.myCamera3D.cam3D);\n}\n\nvoid drawMode3DNonRecording() {\n\n\tif (!myVar.infiniteDomain) {\n\t\tDrawCubeWiresV({ 0.0f, 0.0f, 0.0f }, { myVar.domainSize3D.x, myVar.domainSize3D.y, myVar.domainSize3D.z }, GRAY); // Domain\n\t}\n\n\tmyParam.brush3D.drawBrush(myVar.domainSize3D.y);\n\n\tif (myVar.toolSpawnGalaxy) {\n\t\tmyParam.particlesSpawning3D.drawGalaxyDisplay(myParam);\n\t}\n\n\t// Z-Curves debug toggle\n\tif (myParam.pParticles3D.size() > 1 && myVar.drawZCurves) {\n\t\tfor (size_t i = 0; i < myParam.pParticles3D.size() - 1; i++) {\n\t\t\tDrawLine3D({ myParam.pParticles3D[i].pos.x, myParam.pParticles3D[i].pos.y, myParam.pParticles3D[i].pos.z }, { myParam.pParticles3D[i + 1].pos.x,myParam.pParticles3D[i + 1].pos.y, myParam.pParticles3D[i + 1].pos.z }, WHITE);\n\n\t\t\t//DrawText(TextFormat(\"%i\", i), static_cast<int>(myParam.pParticles3D[i].pos.x), static_cast<int>(myParam.pParticles3D[i].pos.y) - 10, 10, { 128,128,128,128 });\n\t\t}\n\t}\n}\n\nvoid drawConstraints() {\n\n\tif (myVar.visualizeMesh) {\n\t\trlBegin(RL_LINES);\n\t\tfor (size_t i = 0; i < myParam.pParticles.size(); i++) {\n\t\t\tParticlePhysics& pi = myParam.pParticles[i];\n\n\t\t\tstd::vector<size_t> neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.pos);\n\n\t\t\tfor (size_t j : neighborIndices) {\n\t\t\t\tsize_t neighborIndex = j;\n\n\t\t\t\tif (neighborIndex == i) continue;\n\n\t\t\t\tParticlePhysics& pj = myParam.pParticles[neighborIndex];\n\n\t\t\t\tif (pi.id < pj.id) {\n\t\t\t\t\tglm::vec2 delta = pj.pos - pi.pos;\n\t\t\t\t\tglm::vec2 periodicDelta = delta;\n\n\t\t\t\t\tif (abs(delta.x) > myVar.domainSize.x * 0.5f) {\n\t\t\t\t\t\tperiodicDelta.x += (delta.x > 0) ? -myVar.domainSize.x : myVar.domainSize.x;\n\t\t\t\t\t}\n\t\t\t\t\tif (abs(delta.y) > myVar.domainSize.y * 0.5f) {\n\t\t\t\t\t\tperiodicDelta.y += (delta.y > 0) ? -myVar.domainSize.y : myVar.domainSize.y;\n\t\t\t\t\t}\n\n\t\t\t\t\tglm::vec2 pjCorrectedPos = pi.pos + periodicDelta;\n\n\t\t\t\t\tColor lineColor = ColorLerp(myParam.rParticles[i].color, myParam.rParticles[neighborIndex].color, 0.5f);\n\t\t\t\t\trlColor4ub(lineColor.r, lineColor.g, lineColor.b, lineColor.a);\n\t\t\t\t\trlVertex2f(pi.pos.x, pi.pos.y);\n\t\t\t\t\trlVertex2f(pjCorrectedPos.x, pjCorrectedPos.y);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\trlEnd();\n\t}\n\n\tif (myVar.drawConstraints && !physics.particleConstraints.empty()) {\n\n\t\trlBegin(RL_LINES);\n\t\tfor (size_t i = 0; i < physics.particleConstraints.size(); i++) {\n\t\t\tauto& constraint = physics.particleConstraints[i];\n\n\t\t\tif (constraint.id1 >= physics.idToIndexTable.size() || constraint.id2 >= physics.idToIndexTable.size()) continue;\n\n\t\t\tint64_t idx1 = physics.idToIndexTable[constraint.id1];\n\t\t\tint64_t idx2 = physics.idToIndexTable[constraint.id2];\n\n\t\t\tif (idx1 == -1 || idx2 == -1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tParticlePhysics& pi = myParam.pParticles[idx1];\n\t\t\tParticlePhysics& pj = myParam.pParticles[idx2];\n\n\t\t\tglm::vec2 delta = pj.pos - pi.pos;\n\t\t\tglm::vec2 periodicDelta = delta;\n\n\t\t\tif (std::abs(delta.x) > myVar.domainSize.x * 0.5f) {\n\t\t\t\tperiodicDelta.x += (delta.x > 0) ? -myVar.domainSize.x : myVar.domainSize.x;\n\t\t\t}\n\t\t\tif (std::abs(delta.y) > myVar.domainSize.y * 0.5f) {\n\t\t\t\tperiodicDelta.y += (delta.y > 0) ? -myVar.domainSize.y : myVar.domainSize.y;\n\t\t\t}\n\n\t\t\tglm::vec2 pjCorrectedPos = pi.pos + periodicDelta;\n\n\t\t\tColor lineColor;\n\t\t\tif (myVar.constraintStressColor) {\n\t\t\t\tfloat maxStress = 0.0f;\n\n\t\t\t\tif (myVar.constraintMaxStressColor > 0.0f) {\n\t\t\t\t\tmaxStress = myVar.constraintMaxStressColor;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tmaxStress = constraint.resistance * myVar.globalConstraintResistance * constraint.restLength * 0.18f;\n\t\t\t\t}\n\n\t\t\t\tfloat stressMag = std::abs(constraint.displacement);\n\t\t\t\tfloat clampedStress = std::clamp(stressMag, 0.0f, maxStress);\n\t\t\t\tfloat normalizedStress = clampedStress / maxStress;\n\n\t\t\t\tfloat hue = (1.0f - normalizedStress) * 240.0f;\n\t\t\t\tlineColor = ColorFromHSV(hue, 1.0f, 1.0f);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlineColor = ColorLerp(myParam.rParticles[idx1].color, myParam.rParticles[idx2].color, 0.5f);\n\t\t\t}\n\n\t\t\trlColor4ub(lineColor.r, lineColor.g, lineColor.b, lineColor.a);\n\t\t\trlVertex2f(pi.pos.x, pi.pos.y);\n\t\t\trlVertex2f(pjCorrectedPos.x, pjCorrectedPos.y);\n\t\t}\n\t\trlEnd();\n\t}\n}\n\nfloat introDuration = 3.0f;\nfloat fadeDuration = 2.0f;\n\nstatic float introStartTime = 0.0f;\nstatic float fadeStartTime = 0.0f;\n\nbool firstTimeOpened = true;\n\nint messageIndex = 0;\n\n// If you see this part of the code, please try to not mention it :)\nstd::vector<std::pair<std::string, int>> introMessages = {\n\n\t{\"Welcome to Galaxy Engine\", 0},\n\t{\"Welcome to Galaxy Engine\", 1},\n\t{\"Welcome back to Galaxy Engine\", 2},\n\t{\"Welcome to Galaxy Engine\", 3},\n\t{\"Welcome again\", 4},\n\t{\"Welcome to Galaxy Engine\", 5},\n\t{\"Welcome to Galaxy Engine\", 6},\n\t{\"Welcome once again\", 7},\n\t{\"Welcome back to Galaxy Engine\", 8},\n\t{\"Oh, it is you again. Welcome\", 9},\n\t{\"Welcome to Galaxy Engine\", 10},\n\t{\"Welcome to Galaxy Engine\", 11},\n\t{\"Welcome to Galaxy Engine\", 12},\n\t{\"It is kind of cold out here\", 13},\n\t{\"You know, I have never gone beyond the domain\", 14},\n\t{\"It is you! Welcome back\", 15},\n\t{\"It is lonely out here, welcome back\", 16},\n\t{\"Watching space in action sure is ineffable\", 17},\n\t{\"I wish I could fly through the galaxies\", 18},\n\t{\"Oh, it is my space companion! Welcome\", 19},\n\t{\"Hello! It is nice to have you back\", 20},\n\t{\"I tried leaving the domain...\", 21},\n\t{\"The outside of the domain is somehow darker\", 22},\n\t{\"Most of the time all I see is empty space\", 23},\n\t{\"I get to see the cosmos when you are around\", 24},\n\t{\"I think I saw something while you were gone\", 25},\n\t{\"What is the outside world like?\", 26},\n\t{\"What do you like the most? Stars or planets?\", 27},\n\t{\"Do you like galaxies?\", 28},\n\t{\"I like galaxies. They are like magic clouds!\", 29},\n\t{\"I think I will try to explore beyond the domain\", 30},\n\t{\"Beyond the domain, things get quiet\", 31},\n\t{\"I had a dream in which I was flying through space\", 32},\n\t{\"Hi! Are you back to show me the cosmos?\", 33},\n\t{\"Do you dream a lot?\", 34},\n\t{\"I wish some of my dreams were real\", 35},\n\t{\"I will try to leave the domain again\", 36},\n\t{\"Before I leave, I'm going to put a welcome sign\", 37},\n\t{\"A sign so you won't forget me!\", 38},\n\t{\"I might be back if I don't find anything\", 39},\n\t{\"It was really nice sharing your company!\", 40},\n\t{\"I hope to see you again!\", 41},\n\t{\"Farewell\", 42},\n};\n\nvoid drawScene(Texture2D& particleBlurTex, RenderTexture2D& myRayTracingTexture,\n\tRenderTexture2D& myUITexture, RenderTexture2D& myMiscTexture, bool& fadeActive, bool& introActive) {\n\n\tif (!field.cells.empty() && !myParam.pParticles.empty() && myVar.isGravityFieldEnabled) {\n\t\tfield.drawField(myParam, myVar);\n\t}\n\n\tif (!myVar.isGravityFieldEnabled) {\n\n\n\t\tif (!myVar.is3DMode) {\n\t\t\tRectangle source = {\n\t\t0.0f,\n\t\t0.0f,\n\t\tstatic_cast<float>(particleBlurTex.width),\n\t\tstatic_cast<float>(particleBlurTex.height)\n\t\t\t};\n\n\t\t\tfor (int i = 0; i < myParam.pParticles.size(); ++i) {\n\n\t\t\t\tParticlePhysics& pParticle = myParam.pParticles[i];\n\t\t\t\tParticleRendering& rParticle = myParam.rParticles[i];\n\n\n\t\t\t\t// Texture size is set to 16 because that is the particle texture half size in pixels\n\n\t\t\t\tRectangle dest = {\n\t\t\t\t\tpParticle.pos.x,\n\t\t\t\t\tpParticle.pos.y,\n\t\t\t\t\tparticleBlurTex.width * rParticle.size,\n\t\t\t\t\tparticleBlurTex.height * rParticle.size\n\t\t\t\t};\n\n\t\t\t\tglm::vec2 origin = {\n\t\t\t\t\tparticleBlurTex.width * 0.5f * rParticle.size,\n\t\t\t\t\tparticleBlurTex.height * 0.5f * rParticle.size\n\t\t\t\t};\n\n\t\t\t\tDrawTexturePro(\n\t\t\t\t\tparticleBlurTex,\n\t\t\t\t\tsource,\n\t\t\t\t\tdest,\n\t\t\t\t\t{ origin.x, origin.y },\n\t\t\t\t\t0.0f,\n\t\t\t\t\trParticle.color\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (!myVar.isDensitySizeEnabled) {\n\n#pragma omp parallel for schedule(dynamic)\n\t\t\t\tfor (int i = 0; i < myParam.pParticles.size(); ++i) {\n\n\t\t\t\t\tParticlePhysics& pParticle = myParam.pParticles[i];\n\t\t\t\t\tParticleRendering& rParticle = myParam.rParticles[i];\n\n\t\t\t\t\tif (rParticle.canBeResized || myVar.isMergerEnabled) {\n\t\t\t\t\t\trParticle.size = rParticle.previousSize * myVar.particleSizeMultiplier;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\trParticle.size = rParticle.previousSize;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmyParam.trails.drawTrail(myParam.rParticles, particleBlurTex);\n\n\t\t\tdrawConstraints();\n\n\t\t\tmyParam.colorVisuals.particlesColorVisuals(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.isTempEnabled, myVar.timeFactor, myVar.is3DMode);\n\t\t}\n\n\t\tif (myVar.isOpticsEnabled) {\n\t\t\tfor (Wall& wall : lighting.walls) {\n\t\t\t\twall.drawWall();\n\t\t\t}\n\t\t}\n\t}\n\telse {\n\t\tmyVar.infiniteDomain = false;\n\t}\n\n\tEndTextureMode();\n\n\t// Ray Tracing\n\n\tBeginTextureMode(myRayTracingTexture);\n\n\tClearBackground({ 0,0,0,0 });\n\n\tBeginMode2D(myParam.myCamera.camera);\n\n\tEndMode2D();\n\n\tEndTextureMode();\n\n\t//EVERYTHING INTENDED TO APPEAR WHILE RECORDING ABOVE\n\n\n\t//END OF PARTICLES RENDER PASS\n\t//-------------------------------------------------//\n\t//BEGINNNG OF UI RENDER PASS\n\n\n\t//EVERYTHING NOT INTENDED TO APPEAR WHILE RECORDING BELOW\n\n\tBeginTextureMode(myUITexture);\n\n\tClearBackground({ 0,0,0,0 });\n\n\tBeginMode2D(myParam.myCamera.camera);\n\n\tmyVar.mouseWorldPos = glm::vec2(GetScreenToWorld2D(GetMousePosition(), myParam.myCamera.camera).x,\n\t\tGetScreenToWorld2D(GetMousePosition(), myParam.myCamera.camera).y);\n\n\tif (!myVar.is3DMode) {\n\t\tmyParam.brush.drawBrush(myVar.mouseWorldPos);\n\t}\n\n\tif (myVar.toolSpawnGalaxy && !myVar.is3DMode) {\n\t\tmyParam.particlesSpawning.drawGalaxyDisplay(myParam);\n\t}\n\n\tif (!myVar.is3DMode && !myVar.infiniteDomain) {\n\t\tDrawRectangleLinesEx({ 0,0, myVar.domainSize.x, myVar.domainSize.y }, 3, GRAY);\n\t}\n\n\t// Z-Curves debug toggle\n\tif (myParam.pParticles.size() > 1 && myVar.drawZCurves) {\n\t\tfor (size_t i = 0; i < myParam.pParticles.size() - 1; i++) {\n\t\t\tDrawLineV({ myParam.pParticles[i].pos.x, myParam.pParticles[i].pos.y }, { myParam.pParticles[i + 1].pos.x,myParam.pParticles[i + 1].pos.y }, WHITE);\n\n\t\t\t//DrawText(TextFormat(\"%i\", i), static_cast<int>(myParam.pParticles[i].pos.x), static_cast<int>(myParam.pParticles[i].pos.y) - 10, 10, { 128,128,128,128 });\n\t\t}\n\t}\n\n\tif (myVar.isOpticsEnabled) {\n\t\tlighting.drawScene();\n\t\tlighting.drawMisc(myVar, myParam);\n\t}\n\n\tEndMode2D();\n\n\t// EVERYTHING NON-STATIC RELATIVE TO CAMERA ABOVE\n\n\t// EVERYTHING STATIC RELATIVE TO CAMERA BELOW\n\n\tif (myVar.isRayMarcherOn) {\n\t\t/*rayMarcher.initPixelGrid();\n\n\t\trayMarcher.cameraRays(myParam.myCamera3D, myParam.pParticles3D, myParam.rParticles3D);\n\n\t\trayMarcher.drawPixels();*/\n\n\t\trayMarcher.rayMarchUI();\n\n\t\trayMarcher.Draw(myVar.lowResRayMarching);\n\t}\n\n\tif (!introActive) {\n\t\tmyUI.uiLogic(myParam, myVar, sph, save, geSound, lighting, field, ship);\n\t}\n\n\tsave.saveLoadLogic(myVar, myParam, sph, physics, physics3D, lighting, field);\n\n\tif (!myVar.is3DMode) {\n\t\tmyParam.subdivision.subdivideParticles(myVar, myParam);\n\t}\n\n\tEndTextureMode();\n\n\tBeginTextureMode(myMiscTexture);\n\n\tClearBackground({ 0,0,0,0 });\n\n\t// ---- Intro screen ---- //\n\n\tif (introActive) {\n\n\t\tif (introStartTime == 0.0f) {\n\t\t\tintroStartTime = GetTime();\n\t\t}\n\n\t\tfloat introElapsedTime = GetTime() - introStartTime;\n\t\tfloat fadeProgress = introElapsedTime / introDuration;\n\n\t\tif (introElapsedTime >= introDuration) {\n\t\t\tintroActive = false;\n\t\t\tfadeStartTime = GetTime();\n\t\t}\n\n\t\tDrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), BLACK);\n\n\t\tconst char* text = nullptr;\n\n\t\tif (messageIndex < introMessages.size()) {\n\t\t\ttext = introMessages[messageIndex].first.c_str();\n\t\t}\n\t\telse {\n\t\t\ttext = \"Welcome back to Galaxy Engine, friend\";\n\t\t}\n\n\t\tint fontSize = myVar.introFontSize;\n\n\t\tFont fontToUse = (myVar.customFont.texture.id != 0) ? myVar.customFont : GetFontDefault();\n\n\t\tVector2 textSize = MeasureTextEx(fontToUse, text, fontSize, 1.0f);\n\t\tint posX = (GetScreenWidth() - textSize.x) * 0.5f;\n\t\tint posY = (GetScreenHeight() - textSize.y) * 0.5f;\n\n\t\tfloat textAlpha;\n\t\tif (fadeProgress < 0.2f) {\n\t\t\ttextAlpha = fadeProgress / 0.2f;\n\t\t}\n\t\telse if (fadeProgress > 0.8f) {\n\t\t\ttextAlpha = 1.0f - ((fadeProgress - 0.8f) / 0.2f);\n\t\t}\n\t\telse {\n\t\t\ttextAlpha = 1.0f;\n\t\t}\n\n\t\tColor textColor = Fade(WHITE, textAlpha);\n\n\t\tDrawTextEx(fontToUse, text, { static_cast<float>(posX), static_cast<float>(posY) }, fontSize, 1.0f, textColor);\n\t}\n\telse if (fadeActive) {\n\t\tfloat fadeElapsedTime = GetTime() - fadeStartTime;\n\n\t\tif (fadeElapsedTime >= fadeDuration) {\n\t\t\tfadeActive = false;\n\t\t}\n\t\telse {\n\t\t\tfloat alpha = 1.0f - (fadeElapsedTime / fadeDuration);\n\t\t\talpha = Clamp(alpha, 0.0f, 1.0f);\n\t\t\tDrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(BLACK, alpha));\n\t\t}\n\t}\n\n\tif (firstTimeOpened && !introActive) {\n\t\tmessageIndex++;\n\t\tmessageIndex = std::min(static_cast<size_t>(messageIndex), introMessages.size());\n\n\t\tfirstTimeOpened = false;\n\t}\n\n\t// ---- Intro screen ---- //\n\n\tEndTextureMode();\n}\n\nfloat lastGlobalVolume = -1.0f;\nfloat lastMenuVolume = -1.0f;\nfloat lastMusicVolume = -1.0f;\nint lastMessageIndex = -1;\n\nvoid saveConfigIfChanged() {\n\tif (geSound.globalVolume != lastGlobalVolume ||\n\t\tgeSound.menuVolume != lastMenuVolume ||\n\t\tgeSound.musicVolume != lastMusicVolume ||\n\t\tmessageIndex != lastMessageIndex) {\n\n\t\tsaveConfig();\n\n\t\tlastGlobalVolume = geSound.globalVolume;\n\t\tlastMenuVolume = geSound.menuVolume;\n\t\tlastMusicVolume = geSound.musicVolume;\n\t\tlastMessageIndex = messageIndex;\n\t}\n}\n\nvoid saveConfig() {\n\tif (!std::filesystem::exists(\"Config\")) {\n\t\tstd::filesystem::create_directory(\"Config\");\n\t}\n\tstd::ofstream file(\"Config/config.txt\");\n\tYAML::Emitter out(file);\n\tout << YAML::BeginMap;\n\tout << YAML::Key << \"Global Volume\" << YAML::Value << geSound.globalVolume;\n\tout << YAML::Key << \"Menu Volume\" << YAML::Value << geSound.menuVolume;\n\tout << YAML::Key << \"Music Volume\" << YAML::Value << geSound.musicVolume;\n\tout << YAML::Key << \"Message Index\" << YAML::Value << messageIndex;\n\tout << YAML::EndMap;\n}\n\nvoid loadConfig() {\n\n\tYAML::Node config = YAML::LoadFile(\"Config/config.txt\");\n\n\tif (config[\"Global Volume\"])\n\t\tgeSound.globalVolume = config[\"Global Volume\"].as<float>();\n\tif (config[\"Menu Volume\"])\n\t\tgeSound.menuVolume = config[\"Menu Volume\"].as<float>();\n\tif (config[\"Music Volume\"])\n\t\tgeSound.musicVolume = config[\"Music Volume\"].as<float>();\n\tif (config[\"Message Index\"])\n\t\tmessageIndex = config[\"Message Index\"].as<float>();\n}\n\nvoid enableMultiThreading() {\n\tif (myVar.isMultiThreadingEnabled) {\n\t\tomp_set_num_threads(myVar.threadsAmount);\n\t}\n\telse {\n\t\tomp_set_num_threads(1);\n\t}\n}\n\nvoid fullscreenToggle(int& lastScreenWidth, int& lastScreenHeight,\n\tbool& wasFullscreen, bool& lastScreenState,\n\tRenderTexture2D& myParticlesTexture, RenderTexture2D& myUITexture) {\n\n\tif (myVar.fullscreenState != lastScreenState) {\n\t\tint monitor = GetCurrentMonitor();\n\n\t\tif (!IsWindowMaximized())\n\t\t\tSetWindowSize(static_cast<float>(GetMonitorWidth(monitor)) * 0.5f, static_cast<float>(GetMonitorHeight(monitor)) * 0.5f);\n\t\telse\n\t\t\tSetWindowSize(static_cast<float>(GetMonitorWidth(monitor)) * 0.5f, static_cast<float>(GetMonitorHeight(monitor)) * 0.5f);\n\t\tToggleBorderlessWindowed();\n\t\twasFullscreen = IsWindowMaximized();;\n\n\t\tUnloadRenderTexture(myParticlesTexture);\n\t\tUnloadRenderTexture(myUITexture);\n\n\t\tlastScreenWidth = GetScreenWidth();\n\t\tlastScreenHeight = GetScreenHeight();\n\t\tlastScreenState = myVar.fullscreenState;\n\n\t\tmyParticlesTexture = CreateFloatRenderTexture(lastScreenWidth, lastScreenHeight);;\n\t\tmyUITexture = LoadRenderTexture(lastScreenWidth, lastScreenHeight);\n\t}\n\n\tint currentScreenWidth = GetScreenWidth();\n\tint currentScreenHeight = GetScreenHeight();\n\n\tif (currentScreenWidth != lastScreenWidth || currentScreenHeight != lastScreenHeight) {\n\t\tUnloadRenderTexture(myParticlesTexture);\n\t\tUnloadRenderTexture(myUITexture);\n\n\t\tmyParticlesTexture = CreateFloatRenderTexture(currentScreenWidth, currentScreenHeight);\n\t\tmyUITexture = LoadRenderTexture(currentScreenWidth, currentScreenHeight);\n\n\t\tlastScreenWidth = currentScreenWidth;\n\t\tlastScreenHeight = currentScreenHeight;\n\t}\n}\n\n\nRenderTexture2D CreateFloatRenderTexture(int w, int h) {\n\tRenderTexture2D fbo = { 0 };\n\tglGenTextures(1, &fbo.texture.id);\n\tglBindTexture(GL_TEXTURE_2D, fbo.texture.id);\n\tglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_HALF_FLOAT, NULL);\n\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n\tglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\n\tglGenFramebuffers(1, &fbo.id);\n\tglBindFramebuffer(GL_FRAMEBUFFER, fbo.id);\n\tglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.texture.id, 0);\n\tglBindFramebuffer(GL_FRAMEBUFFER, 0);\n\n\tfbo.texture.width = w;\n\tfbo.texture.height = h;\n\tfbo.texture.format = PIXELFORMAT_UNCOMPRESSED_R16G16B16A16;\n\n\treturn fbo;\n}"
  },
  {
    "path": "GalaxyEngine/src/main.cpp",
    "content": "#include \"globalLogic.h\"\n\n#if defined(PLATFORM_DESKTOP)\n#define GLSL_VERSION            330\n#else   // PLATFORM_ANDROID, PLATFORM_WEB\n#define GLSL_VERSION            100\n#endif\n\nint main(int argc, char** argv) {\n\n\t// SPH Materials initialization\n\tSPHMaterials::Init();\n\n\tSetConfigFlags(FLAG_MSAA_4X_HINT);\n\n\tSetConfigFlags(FLAG_WINDOW_RESIZABLE);\n\n\tSetConfigFlags(FLAG_WINDOW_ALWAYS_RUN);\n\n\tint threadsAvailable = std::thread::hardware_concurrency();\n\n\tmyVar.threadsAmount = static_cast<int>(threadsAvailable * 0.5f);\n\n\tstd::cout << \"Threads available: \" << threadsAvailable << std::endl;\n\tstd::cout << \"Thread amount set to: \" << myVar.threadsAmount << std::endl;\n\n\tif (myVar.fullscreenState) {\n\t\tmyVar.screenWidth = GetMonitorWidth(GetCurrentMonitor());\n\t\tmyVar.screenHeight = GetMonitorHeight(GetCurrentMonitor());\n\t}\n\n\tInitWindow(myVar.screenWidth, myVar.screenHeight, \"Galaxy Engine\");\n\n\t// ---- Config ---- //\n\n\tif (!std::filesystem::exists(\"Config\")) {\n\t\tstd::filesystem::create_directory(\"Config\");\n\t}\n\n\tif (!std::filesystem::exists(\"Config/config.txt\")) {\n\t\tsaveConfig();\n\t}\n\telse {\n\t\tloadConfig();\n\t}\n\n\t// ---- Audio ---- //\n\n\tgeSound.loadSounds();\n\n\t// ---- Icon ---- //\n\n\tImage icon = LoadImage(\"Textures/WindowIcon.png\");\n\tSetWindowIcon(icon);\n\n\t// ---- Textures & rlImGui ---- //\n\n\trlImGuiSetup(true);\n\n\tTexture2D particleBlurTex = LoadTexture(\"Textures/ParticleBlur.png\");\n\n\tRenderTexture2D myParticlesTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight());\n\tRenderTexture2D myRayTracingTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight());\n\tRenderTexture2D myUITexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());\n\tRenderTexture2D myMiscTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());\n\n\tSetTargetFPS(myVar.targetFPS);\n\n\t// ---- Fullscreen ---- //\n\n\tint lastScreenWidth = GetScreenWidth();\n\tint lastScreenHeight = GetScreenHeight();\n\tbool wasFullscreen = IsWindowMaximized();\n\n\tbool lastScreenState = false;\n\n\t// ---- Save ---- //\n\n\t// If \"Saves\" directory doesn't exist, then create one. This is done here to store the default parameters\n\tif (!std::filesystem::exists(\"Saves\")) {\n\t\tstd::filesystem::create_directory(\"Saves\");\n\t}\n\n\tsave.saveFlag = true;\n\tsave.saveSystem(\"Saves/DefaultSettings.bin\", myVar, myParam, sph, physics, physics3D, lighting, field);\n\tsave.saveFlag = false;\n\n\t// ---- ImGui ---- //\n\n\tImGuiStyle& style = ImGui::GetStyle();\n\tImVec4* colors = style.Colors;\n\n\t// Button and window colors\n\tcolors[ImGuiCol_WindowBg] = myVar.colWindowBg;\n\tcolors[ImGuiCol_Button] = myVar.colButton;\n\tcolors[ImGuiCol_ButtonHovered] = myVar.colButtonHover;\n\tcolors[ImGuiCol_ButtonActive] = myVar.colButtonPress;\n\n\t// Slider colors\n\tstyle.Colors[ImGuiCol_SliderGrab] = myVar.colSliderGrab;        // Bright cyan-ish knob\n\tstyle.Colors[ImGuiCol_SliderGrabActive] = myVar.colSliderGrabActive;  // Darker when dragging\n\n\tstyle.Colors[ImGuiCol_FrameBg] = myVar.colSliderBg;           // Dark track when idle\n\tstyle.Colors[ImGuiCol_FrameBgHovered] = myVar.colSliderBgHover;     // Lighter track on hover\n\tstyle.Colors[ImGuiCol_FrameBgActive] = myVar.colSliderBgActive;      // Even lighter on active\n\n\t// Tab colors\n\tstyle.Colors[ImGuiCol_Tab] = myVar.colButton;\n\tstyle.Colors[ImGuiCol_TabHovered] = myVar.colButtonHover;\n\tstyle.Colors[ImGuiCol_TabActive] = myVar.colButtonPress;\n\n\tImGuiIO& io = ImGui::GetIO();\n\n\tImFontConfig config;\n\tconfig.SizePixels = 14.0f;        // Base font size in pixels\n\tconfig.RasterizerDensity = 2.0f;  // Improves rendering at small sizes\n\tconfig.OversampleH = 4;           // Horizontal anti-aliasing\n\tconfig.OversampleV = 4;           // Vertical anti-aliasing\n\tconfig.PixelSnapH = false;        // Disable pixel snapping for smoother text\n\tconfig.RasterizerMultiply = 0.9f; // Slightly boosts brightness\n\n\tmyVar.robotoMediumFont = io.Fonts->AddFontFromFileTTF(\n\t\t\"fonts/Roboto-Medium.ttf\",\n\t\tconfig.SizePixels,\n\t\t&config,\n\t\tio.Fonts->GetGlyphRangesDefault()\n\t);\n\n\tif (!myVar.robotoMediumFont) {\n\t\tstd::cerr << \"Failed to load special font!\\n\";\n\t}\n\telse {\n\t\tstd::cout << \"Special font loaded successfully\\n\";\n\t}\n\n\tio.Fonts->Build();\n\tImPlot::CreateContext();\n\n\t// ---- Intro ---- //\n\n\tbool fadeActive = true;\n\tbool introActive = true;\n\n\tmyVar.customFont = LoadFontEx(\"fonts/Unispace Bd.otf\", myVar.introFontSize, 0, 250);\n\n\tSetTextureFilter(myVar.customFont.texture, TEXTURE_FILTER_BILINEAR);\n\n\tif (myVar.customFont.texture.id == 0) {\n\t\tTraceLog(LOG_WARNING, \"Failed to load font! Using default font\");\n\t}\n\n\tif (myVar.fullscreenState) {\n\t\tmyVar.screenWidth = GetMonitorWidth(GetCurrentMonitor()) * 0.5f;\n\t\tmyVar.screenHeight = GetMonitorHeight(GetCurrentMonitor()) * 0.5f;\n\t}\n\n\t// ---- Ray Tracing and Long Exposure ---- //\n\n\tconst char* accumulationVs = R\"(\n    #version 330 core\n\nlayout(location = 0) in vec3 vertexPosition;\nlayout(location = 1) in vec2 vertexTexCoord;\nlayout(location = 3) in vec4 vertexColor;\n\nout vec2 fragTexCoord;\nout vec4 fragColor;\n\nuniform mat4 mvp;\n\nvoid main()\n{\n\n    gl_Position = mvp * vec4(vertexPosition, 1.0);\n\n\n    fragTexCoord = vertexTexCoord;\n    fragColor = vertexColor;\n\n}\n    )\";\n\n\tconst char* accumulationFs = R\"(\n\n#version 330\n\nprecision highp float;\nprecision highp sampler2D;\n\nin vec2 fragTexCoord;\nout vec4 finalColor;\n\nuniform sampler2D currentFrame;\nuniform sampler2D accumulatedFrame;\nuniform float sampleCount;\n\nvoid main() {\n\n    highp vec4 newColor = texture(currentFrame, fragTexCoord);\n    highp vec4 oldColor = texture(accumulatedFrame, fragTexCoord);\n\n    finalColor = (oldColor * (sampleCount - 1.0) + newColor) / sampleCount;\n\n    finalColor = clamp(finalColor, 0.0, 1.0);\n}\n)\";\n\n\tShader myBloom = LoadShader(accumulationVs, \"Shaders/bloom.fs\");\n\n\tShader accumulationShader = LoadShaderFromMemory(accumulationVs, accumulationFs);\n\n\tint screenSizeLoc = GetShaderLocation(accumulationShader, \"screenSize\");\n\tfloat screenSize[2] = {\n\t\t(float)myVar.screenWidth,\n\t\t(float)myVar.screenHeight\n\t};\n\tSetShaderValue(accumulationShader, screenSizeLoc, screenSize, SHADER_UNIFORM_VEC2);\n\n\tint rayTextureLoc = GetShaderLocation(accumulationShader, \"rayTexture\");\n\n\tRenderTexture2D accumulatedTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight());\n\tRenderTexture2D pingPongTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight());\n\n\tint currentFrameLoc = GetShaderLocation(accumulationShader, \"currentFrame\");\n\tint accumulatedFrameLoc = GetShaderLocation(accumulationShader, \"accumulatedFrame\");\n\tint sampleCountLoc = GetShaderLocation(accumulationShader, \"sampleCount\");\n\tRenderTexture2D testSampleTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());\n\n\tint prevScreenWidth = GetScreenWidth();\n\tint prevScreenHeight = GetScreenHeight();\n\n\tbool prevLongExpFlag = false;\n\n\tbool accumulationCondition = false;\n\n\tbuildKernels();\n\n\tmyVar.hasAVX2 = hasAVX2Support();\n\n\tstd::cout << \"--------------\" << std::endl;\n\tif (myVar.hasAVX2) {\n\t\tstd::cout << \"Has AVX2 Support\" << std::endl;\n\t}\n\telse {\n\t\tstd::cout << \"Doesn't have AVX2 Support\" << std::endl;\n\t}\n\tstd::cout << \"--------------\" << std::endl;\n\n\trlSetClipPlanes(1.0f, 50000.0f);\n\n\t// ================= SKYBOX INITIALIZATION ================= //\n\n\tMesh cubeSky = GenMeshCube(10000.0f, 10000.0f, 10000.0f);\n\tModel skybox = LoadModelFromMesh(cubeSky);\n\n\t// Load skybox shader\n\tskybox.materials[0].shader = LoadShader(TextFormat(\"Shaders/skybox.vs\", GLSL_VERSION),\n\t\tTextFormat(\"Shaders/skybox.fs\", GLSL_VERSION));\n\n\tint environmentMap = MATERIAL_MAP_CUBEMAP;\n\tSetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, \"environmentMap\"), &environmentMap, SHADER_UNIFORM_INT);\n\n\tint doGamma = 0;\n\tSetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, \"doGamma\"), &doGamma, SHADER_UNIFORM_INT);\n\n\tint vflipped = 0;\n\tSetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, \"vflipped\"), &vflipped, SHADER_UNIFORM_INT);\n\n\n\tImage faces[6] = {\n\t\tLoadImage(\"Textures/sky_pos_x.png\"),   // +X\n\t\tLoadImage(\"Textures/sky_neg_x.png\"),    // -X\n\t\tLoadImage(\"Textures/sky_pos_y.png\"),     // +Y\n\t\tLoadImage(\"Textures/sky_neg_y.png\"),  // -Y\n\t\tLoadImage(\"Textures/sky_pos_z.png\"),   // +Z\n\t\tLoadImage(\"Textures/sky_neg_z.png\")     // -Z\n\t};\n\n\tint width = faces[0].width;\n\tint height = faces[0].height;\n\n\tImage verticalStrip = GenImageColor(width, height * 6, BLACK);\n\n\tfor (int i = 0; i < 6; i++) {\n\n\t\tImageDraw(&verticalStrip, faces[i],\n\t\t\t(Rectangle) {\n\t\t\t0, 0, (float)width, (float)height\n\t\t},\n\t\t\t(Rectangle) {\n\t\t\t0, (float)(i * height), (float)width, (float)height\n\t\t},\n\t\t\tWHITE);\n\n\t\tUnloadImage(faces[i]);\n\t}\n\n\tskybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = LoadTextureCubemap(verticalStrip, CUBEMAP_LAYOUT_LINE_VERTICAL);\n\n\tUnloadImage(verticalStrip);\n\n\twhile (!WindowShouldClose()) {\n\n\t\tif (myVar.exitGame) {\n\t\t\tCloseWindow();\n\t\t\tbreak;\n\t\t}\n\n\t\tfullscreenToggle(lastScreenWidth, lastScreenHeight, wasFullscreen, lastScreenState, myParticlesTexture, myUITexture);\n\n\t\tBeginTextureMode(myParticlesTexture);\n\n\t\tClearBackground({ 0, 0, 0, 0 });\n\n\t\tBeginBlendMode(myParam.colorVisuals.blendMode);\n\n\t\tif (IO::shortcutPress(KEY_C)) {\n\t\t\tmyParam.pParticles.clear();\n\t\t\tmyParam.rParticles.clear();\n\t\t\tmyParam.pParticles3D.clear();\n\t\t\tmyParam.rParticles3D.clear();\n\t\t\tmyParam.trails.segments.clear();\n\n\t\t\tphysics.posX.clear();\n\t\t\tphysics.posY.clear();\n\t\t\tphysics.accX.clear();\n\t\t\tphysics.accY.clear();\n\t\t\tphysics.velX.clear();\n\t\t\tphysics.velY.clear();\n\t\t\tphysics.prevVelX.clear();\n\t\t\tphysics.prevVelY.clear();\n\t\t\tphysics.mass.clear();\n\t\t\tphysics.temp.clear();\n\n\t\t\tphysics3D.posX.clear();\n\t\t\tphysics3D.posY.clear();\n\t\t\tphysics3D.accX.clear();\n\t\t\tphysics3D.accY.clear();\n\t\t\tphysics3D.velX.clear();\n\t\t\tphysics3D.velY.clear();\n\t\t\tphysics3D.prevVelX.clear();\n\t\t\tphysics3D.prevVelY.clear();\n\t\t\tphysics3D.mass.clear();\n\t\t\tphysics3D.temp.clear();\n\n\t\t\tglobalNodes.clear();\n\t\t\tglobalNodes3D.clear();\n\t\t}\n\n\t\tif (myVar.is3DMode) {\n\n\t\t\tBeginMode3D(myParam.myCamera3D.cameraLogic(save.loadFlag, myVar.isMouseNotHoveringUI, myVar.firstPerson, ship.isShipEnabled));\n\n\t\t\trlDisableBackfaceCulling();\n\t\t\trlDisableDepthMask();\n\t\t\tDrawModel(skybox, (Vector3) { 0, 0, 0 }, 1.0f, WHITE);\n\t\t\trlEnableBackfaceCulling();\n\t\t\trlEnableDepthMask();\n\n\t\t\tmode3D();\n\n\t\t\tdrawMode3DRecording(particleBlurTex);\n\n\t\t\tif (!myVar.isRecording) {\n\t\t\t\tdrawMode3DNonRecording();\n\t\t\t}\n\n\t\t\tEndMode3D();\n\t\t}\n\n\t\tBeginMode2D(myParam.myCamera.cameraLogic(save.loadFlag, myVar.isMouseNotHoveringUI));\n\n\t\trlImGuiBegin();\n\n\t\tif (introActive) {\n\t\t\tImGuiIO& io = ImGui::GetIO();\n\t\t\tio.WantCaptureMouse = true;\n\t\t\tio.WantCaptureKeyboard = true;\n\t\t\tio.WantTextInput = true;\n\n\t\t\tif (myParam.pParticles.size() > 0) {\n\t\t\t\tmyParam.pParticles.clear();\n\t\t\t\tmyParam.rParticles.clear();\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tgeSound.soundtrackLogic();\n\t\t}\n\n\t\tImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2.0f);\n\n\t\tsaveConfigIfChanged();\n\n\t\tupdateScene();\n\n\t\tdrawScene(particleBlurTex, myRayTracingTexture, myUITexture, myMiscTexture, fadeActive, introActive);\n\n\t\tEndMode2D();\n\n\t\tEndBlendMode();\n\n\n\t\t//------------------------ RENDER TEXTURES BELOW ------------------------//\n\n\t\tif (myParam.myCamera.cameraChangedThisFrame) {\n\t\t\tlighting.shouldRender = true;\n\t\t}\n\n\t\tif (myVar.longExposureFlag != prevLongExpFlag) {\n\t\t\tmyVar.longExposureCurrent = 1;\n\n\t\t\tprevLongExpFlag = myVar.longExposureFlag;\n\t\t}\n\n\t\tif (myVar.isOpticsEnabled) {\n\t\t\taccumulationCondition = lighting.currentSamples <= lighting.maxSamples;\n\t\t}\n\t\telse if (myVar.longExposureFlag) {\n\t\t\taccumulationCondition = myVar.longExposureCurrent <= myVar.longExposureDuration;\n\t\t}\n\t\telse {\n\t\t\taccumulationCondition = true;\n\t\t}\n\n\t\tif (GetScreenWidth() != prevScreenWidth || GetScreenHeight() != prevScreenHeight) {\n\n\t\t\tUnloadRenderTexture(accumulatedTexture);\n\t\t\tUnloadRenderTexture(pingPongTexture);\n\t\t\tUnloadRenderTexture(testSampleTexture);\n\n\t\t\taccumulatedTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight());\n\t\t\tpingPongTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight());\n\t\t\ttestSampleTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());\n\n\t\t\tscreenSize[0] = (float)GetScreenWidth();\n\t\t\tscreenSize[1] = (float)GetScreenHeight();\n\t\t\tSetShaderValue(accumulationShader, screenSizeLoc, screenSize, SHADER_UNIFORM_VEC2);\n\n\t\t\tprevScreenWidth = GetScreenWidth();\n\t\t\tprevScreenHeight = GetScreenHeight();\n\n\t\t\tif (myVar.isOpticsEnabled) {\n\t\t\t\tlighting.shouldRender = true;\n\t\t\t}\n\n\t\t\tif (myVar.longExposureFlag) {\n\t\t\t\tmyVar.longExposureFlag = false;\n\t\t\t}\n\t\t}\n\n\t\t// Ray Tracing and Long Exposure\n\t\tif (accumulationCondition) {\n\t\t\tBeginTextureMode(pingPongTexture);\n\n\t\t\trlDisableColorBlend();\n\n\t\t\tBeginShaderMode(accumulationShader);\n\n\t\t\tSetShaderValueTexture(accumulationShader, currentFrameLoc, myParticlesTexture.texture);\n\t\t\tSetShaderValueTexture(accumulationShader, accumulatedFrameLoc, accumulatedTexture.texture);\n\n\t\t\tfloat sampleCount = 1.0f;\n\n\t\t\tif (myVar.isOpticsEnabled) {\n\t\t\t\tsampleCount = static_cast<float>(lighting.currentSamples);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlighting.currentSamples = 0;\n\t\t\t}\n\n\t\t\tif (myVar.longExposureFlag) {\n\t\t\t\tsampleCount = static_cast<float>(myVar.longExposureCurrent);\n\t\t\t\tmyVar.longExposureCurrent++;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tmyVar.longExposureCurrent = 0;\n\t\t\t}\n\n\t\t\tSetShaderValue(accumulationShader, sampleCountLoc, &sampleCount, SHADER_UNIFORM_FLOAT);\n\n\t\t\tDrawTextureRec(\n\t\t\t\taccumulatedTexture.texture,\n\t\t\t\tRectangle{ 0, 0, (float)GetScreenWidth(), -((float)GetScreenHeight()) },\n\t\t\t\tVector2{ 0, 0 },\n\t\t\t\tWHITE\n\t\t\t);\n\n\t\t\tEndShaderMode();\n\n\t\t\trlEnableColorBlend();\n\n\t\t\tEndTextureMode();\n\t\t\tstd::swap(accumulatedTexture, pingPongTexture);\n\t\t}\n\n\t\tBeginDrawing();\n\n\t\tClearBackground(BLACK);\n\n\t\tif (myVar.flatParticleTexture3D || !myVar.is3DMode) {\n\t\t\tBeginBlendMode(BLEND_ALPHA_PREMULTIPLY);\n\t\t}\n\n\t\tint resolutionLoc = GetShaderLocation(myBloom, \"res\");\n\t\tSetShaderValue(myBloom, resolutionLoc,\n\t\t\t(float[2]) {\n\t\t\t(float)GetScreenWidth(), (float)GetScreenHeight()\n\t\t},\n\t\t\tSHADER_UNIFORM_VEC2);\n\n\t\tint glowSizeLoc = GetShaderLocation(myBloom, \"glowSize\");\n\n\t\tSetShaderValue(myBloom,\n\t\t\tglowSizeLoc,\n\t\t\t&myVar.glowSize,\n\t\t\tSHADER_UNIFORM_INT);\n\n\t\tint glowStrengthLoc = GetShaderLocation(myBloom, \"glowStrength\");\n\n\t\tSetShaderValue(myBloom,\n\t\t\tglowStrengthLoc,\n\t\t\t&myVar.glowStrength,\n\t\t\tSHADER_UNIFORM_FLOAT);\n\n\t\tif (myVar.isGlowEnabled) {\n\t\t\tBeginShaderMode(myBloom);\n\t\t}\n\n\t\tDrawTextureRec(\n\t\t\taccumulatedTexture.texture,\n\t\t\tRectangle{ 0, 0, (float)GetScreenWidth(), -((float)GetScreenHeight()) },\n\t\t\tVector2{ 0, 0 },\n\t\t\tWHITE\n\t\t);\n\n\t\tif (myVar.isGlowEnabled) {\n\t\t\tEndShaderMode();\n\t\t}\n\n\t\tif (myVar.flatParticleTexture3D || !myVar.is3DMode) {\n\t\t\tEndBlendMode();\n\t\t}\n\n\t\tDrawTextureRec(\n\t\t\tmyUITexture.texture,\n\t\t\tRectangle{ 0, 0, static_cast<float>(GetScreenWidth()), -static_cast<float>(GetScreenHeight()) },\n\t\t\tVector2{ 0, 0 },\n\t\t\tWHITE\n\t\t);\n\n\t\tDrawTextureRec(\n\t\t\tmyUITexture.texture,\n\t\t\tRectangle{ 0, 0, static_cast<float>(GetScreenWidth()), -static_cast<float>(GetScreenHeight()) },\n\t\t\tVector2{ 0, 0 },\n\t\t\tWHITE\n\t\t);\n\n\t\tBeginTextureMode(testSampleTexture);\n\n\t\tClearBackground(BLACK);\n\n\t\tif (myVar.flatParticleTexture3D || !myVar.is3DMode) {\n\t\t\tBeginBlendMode(BLEND_ALPHA_PREMULTIPLY);\n\t\t}\n\n\t\tDrawTextureRec(\n\t\t\taccumulatedTexture.texture,\n\t\t\tRectangle{ 0, 0, (float)GetScreenWidth(), -((float)GetScreenHeight()) },\n\t\t\tVector2{ 0, 0 },\n\t\t\tWHITE\n\t\t);\n\t\tif (myVar.flatParticleTexture3D || !myVar.is3DMode) {\n\t\t\tEndBlendMode();\n\t\t}\n\n\t\tEndTextureMode();\n\n\t\tmyVar.isRecording = myParam.screenCapture.screenGrab(testSampleTexture, myVar, myParam);\n\n\t\tif (myVar.isRecording) {\n\t\t\tDrawRectangleLinesEx({ 0,0, static_cast<float>(GetScreenWidth()), static_cast<float>(GetScreenHeight()) }, 3, RED);\n\t\t}\n\n\t\tImGui::PopStyleVar();\n\n\t\trlImGuiEnd();\n\n\t\tDrawTextureRec(\n\t\t\tmyMiscTexture.texture,\n\t\t\tRectangle{ 0, 0, static_cast<float>(GetScreenWidth()), -static_cast<float>(GetScreenHeight()) },\n\t\t\tVector2{ 0, 0 },\n\t\t\tWHITE\n\t\t);\n\n\n\t\tEndDrawing();\n\n\t\tenableMultiThreading();\n\t}\n\n\trlImGuiShutdown();\n\tImPlot::DestroyContext();\n\n\tUnloadShader(myBloom);\n\tUnloadTexture(particleBlurTex);\n\n\tUnloadRenderTexture(myParticlesTexture);\n\tUnloadRenderTexture(myRayTracingTexture);\n\tUnloadRenderTexture(myUITexture);\n\tUnloadRenderTexture(myMiscTexture);\n\n\tUnloadShader(skybox.materials[0].shader);\n\tUnloadTexture(skybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture);\n\n\tUnloadImage(icon);\n\n\tgeSound.unloadSounds();\n\n\t// Unload accumulation shader\n\tUnloadShader(accumulationShader);\n\n\t// Free compute shader memory\n\tfreeGPUMemory();\n\n\tif (std::filesystem::exists(myVar.playbackPath)) {\n\t\tstd::filesystem::remove(myVar.playbackPath);\n\t}\n\n\tCloseWindow();\n\n\n\n\treturn 0;\n}"
  },
  {
    "path": "GalaxyEngine/src/parameters.cpp",
    "content": "#include \"parameters.h\"\n\n// Background color\nImVec4 UpdateVariables::colWindowBg = ImVec4(0.05f, 0.043f, 0.071f, 0.9f);\n\n// Button colors\nImVec4 UpdateVariables::colButton = ImVec4(0.22f, 0.23f, 0.36f, 1.0f);\nImVec4 UpdateVariables::colButtonHover = ImVec4(0.3f, 0.4f, 0.8f, 1.0f);\nImVec4 UpdateVariables::colButtonPress = ImVec4(0.5f, 0.6f, 0.9f, 1.0f);\n\nImVec4 UpdateVariables::colButtonActive = ImVec4(0.25f, 0.6f, 0.2f, 1.0f);\nImVec4 UpdateVariables::colButtonActiveHover = ImVec4(0.35f, 0.7f, 0.3f, 1.0f);\nImVec4 UpdateVariables::colButtonActivePress = ImVec4(0.45f, 0.8f, 0.4f, 1.0f);\n\nImVec4 UpdateVariables::colButtonRedActive = ImVec4(0.65f, 0.2f, 0.2f, 1.0f);\nImVec4 UpdateVariables::colButtonRedActiveHover = ImVec4(0.75f, 0.3f, 0.3f, 1.0f);\nImVec4 UpdateVariables::colButtonRedActivePress = ImVec4(0.85f, 0.4f, 0.4f, 1.0f);\n\n// Slider Colors\nImVec4 UpdateVariables::colSliderGrab = ImVec4(0.32f, 0.33f, 0.46f, 1.0f);\nImVec4 UpdateVariables::colSliderGrabActive = ImVec4(0.3f, 0.5f, 0.9f, 1.0f);\nImVec4 UpdateVariables::colSliderBg = ImVec4(0.12f, 0.13f, 0.26f, 1.0f);\nImVec4 UpdateVariables::colSliderBgHover = ImVec4(0.22f, 0.23f, 0.36f, 1.0f);\nImVec4 UpdateVariables::colSliderBgActive = ImVec4(0.42f, 0.43f, 0.66f, 1.0f);\n\n// Plotline Colors\nImVec4 UpdateVariables::colPlotLine = ImVec4(0.68f, 0.7f, 0.9f, 1.0f);\nImVec4 UpdateVariables::colAxisText = ImVec4(1.0f, 0.8f, 1.0f, 1.0f);\nImVec4 UpdateVariables::colAxisGrid = ImVec4(0.4f, 0.5f, 0.6f, 1.0f);\nImVec4 UpdateVariables::colAxisBg = ImVec4(0.1f, 0.1f, 0.2f, 1.0f);\nImVec4 UpdateVariables::colFrameBg = ImVec4(0.12f, 0.12f, 0.2f, 1.0f);\nImVec4 UpdateVariables::colPlotBg = ImVec4(0.05f, 0.05f, 0.1f, 1.0f);\nImVec4 UpdateVariables::colPlotBorder = ImVec4(1.0f, 0.0f, 1.0f, 1.0f);\nImVec4 UpdateVariables::colLegendBg = ImVec4(0.1f, 0.1f, 0.1f, 1.0f);\n\n// Text Colors\nImVec4 UpdateVariables::colMenuInformation = ImVec4(0.77f, 0.77f, 0.97f, 1.0f);\n\n// SPH Materials vector definition\nstd::vector<std::unique_ptr<SPHMaterial>> SPHMaterials::materials;\nstd::unordered_map<uint32_t, SPHMaterial*> SPHMaterials::idToMaterial;\n\n// Global particle base mass\nfloat UpdateVariables::particleBaseMass = 8500000000.0f;"
  },
  {
    "path": "GalaxyEngine/src/resources.rc",
    "content": "IDI_ICON1 ICON \"GalaxyEngineIcon.ico\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Narcis Calin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Galaxy Engine\n\n## About\n\nJoin Galaxy Engine's [Discord community!](https://discord.gg/Xd5JUqNFPM)\n\nCheck out the official [Steam page!](https://store.steampowered.com/app/3762210/Galaxy_Engine/)\n\nGalaxy Engine is a free and open source project made for learning purposes by [NarcisCalin](https://github.com/NarcisCalin). It is made with C++ and it is built on top of the [Raylib](https://github.com/raysan5/raylib) library.\n\nGalaxy Engine uses libraries from the [FFmpeg](https://github.com/FFmpeg/FFmpeg) project under the LGPLv2.1\n\nThe entire UI is made with the help of the [Dear ImGui](https://github.com/ocornut/imgui) library.\n\nSpecial thanks to [Crisosphinx](https://github.com/crisosphinx) for helping me improve my code.\n\nSpecial thanks to [SergioCelso](https://github.com/SCelso) for making the learning process a little easier and also for helping me implement the initial version of the Barnes-Hut quadtree.\n\nSpecial thanks to [Emily](https://github.com/Th3T3chn0G1t) for helping me on multiple occasions, including setting up the CMake configuration and assisting with various parts of the project.\n\n\n\n---\n\n<img src=\"https://github.com/user-attachments/assets/d26ca9b3-51f6-4744-9561-4fab50ee0a96\" width=\"1280\">\n\n![GalaxyCollisionField](https://github.com/user-attachments/assets/fa6d4eb8-7986-49dd-b274-e78ba03614bc)\n\n![PlanetaryCollision](https://github.com/user-attachments/assets/15cd619c-3274-445c-9618-de88055d4ff0)\n\n## Features\n---\n### INTERACTIVITY\nGalaxy Engine was built with interactivity in mind\n\n<img src=\"https://github.com/user-attachments/assets/affe69df-3d86-4593-baa4-80e7d7dc1372\" width=\"1280\">\n\n![Interactivity](https://github.com/user-attachments/assets/ef3077a5-91c1-43fd-aa30-516b3e84fabc)\n\n### SPH FLUID PHYSICS\nGalaxy Engine includes SPH fluid physics for different types of simulation\n\n<img src=\"https://github.com/user-attachments/assets/637dc0c9-2bce-4a5f-b93b-8d4036c6349a\" width=\"1280\">\n\n![PlanetsHQ](https://github.com/user-attachments/assets/0bbe27c9-c61d-435e-b20d-9cd055591e41)\n\n![PlanetaryHQ2](https://github.com/user-attachments/assets/b185971f-0463-4086-831d-713c1d1c1a9c)\n\n![AsteroidImpact](https://github.com/user-attachments/assets/d20d97a9-aaf7-4909-b73e-50d9945c17d3)\n\n### REAL TIME PHYSICS\nThanks to the Barnes-Hut algorithm, Galaxy Engine can simulate tens or even hundreds of thousands of particles in real time\n\n![BH](https://github.com/user-attachments/assets/d1db1b59-ce03-46c5-9360-0a7b10199de4)\n\n### RENDERING\nEngine Galaxy includes a recorder so you can compile videos showcasing millions of particles\n\n![20Mil](https://github.com/user-attachments/assets/454af8b4-c8d5-4f9b-b2d6-a831c1dcc9f8)\n\n\n## HOW TO INSTALL\n---\n- Download the latest release version from the releases tab and unzip the zip file.\n- Run the executable file inside the folder.\n- (Currently Galaxy Engine binaries are only available on Windows)\n\n### IMPORTANT INFORMATION\n- Galaxy Engine releases might get flagged as a virus by your browser or by Windows Defender. This is normal. It happens mainly because the program is not signed by Microsoft (Which costs money). If you don't trust the binaries, you can always compile Galaxy Engine yourself\n\n## HOW TO BUILD\n---\n\n### Dependencies\n- [CMake](https://cmake.org/): On Windows, you can install it from [their website](https://cmake.org/download/) or the [Chocolatey cmake package](https://community.chocolatey.org/packages/cmake), and on Linux you can install the `cmake` package with your package manager.\n- A C++ compiler: Any compiler is probably gonna work, but Clang is recommended as it's known to produce a faster binary than GCC for this project.\n  - [Clang](https://clang.llvm.org/): On Linux, you may install the `clang` package from a package manager. On Windows, it can be installed from [their website](https://clang.llvm.org/get_started.html), the [Chocolatey llvm package](https://community.chocolatey.org/packages/llvm), or from the Visual Studio Installer: \n\n    ![image](https://github.com/user-attachments/assets/b46a0e7d-188e-43a3-bf7e-fb3edced233a)\n\n### Basic instructions\nThese instructions assume you have already met the above requirements.\n\n- Clone or download this repo\n- Build the project with CMake\n- After doing this you should have the executable file inside the build folder\n- The last step is running the executable file from the same working directory as the \"Textures\" folder (otherwise the particles won't be visible). This applies to the \"Shaders\" and \"fonts\" folder as well\n"
  },
  {
    "path": "cmake/ffmpeg.cmake",
    "content": "set(FFMPEG_URL_BASE https://github.com/BtbN/FFmpeg-Builds/releases/download/latest)\n\nif(WIN32)\n    set(FFMPEG_ARCHIVE ffmpeg-master-latest-win64-lgpl-shared.zip)\nelse()\n    set(FFMPEG_ARCHIVE ffmpeg-master-latest-linux64-lgpl-shared.tar.xz)\nendif()\n\nFetchContent_Declare(\n    ffmpeg-fetch\n    URL ${FFMPEG_URL_BASE}/${FFMPEG_ARCHIVE}\n)\n\nFetchContent_MakeAvailable(ffmpeg-fetch)\n\nadd_library(ffmpeg INTERFACE)\n\ntarget_include_directories(ffmpeg SYSTEM INTERFACE ${ffmpeg-fetch_SOURCE_DIR}/include)\ntarget_link_directories(ffmpeg INTERFACE ${ffmpeg-fetch_SOURCE_DIR}/lib)\ntarget_link_directories(ffmpeg INTERFACE ${ffmpeg-fetch_SOURCE_DIR}/bin)\n\ntarget_link_libraries(ffmpeg INTERFACE \n    avcodec \n    avformat \n    avutil \n    swscale \n    swresample\n)"
  },
  {
    "path": "cmake/glm.cmake",
    "content": "set(GLM_VERSION 1.0.1)\nset(GLM_URL_BASE https://github.com/g-truc/glm/releases/download)\n\nFetchContent_Declare(glm-fetch URL ${GLM_URL_BASE}/${GLM_VERSION}/glm-1.0.1-light.zip)\nFetchContent_MakeAvailable(glm-fetch)\n\nadd_library(glm-lib INTERFACE)\n\ntarget_link_libraries(glm-lib INTERFACE glm)\n\ntarget_include_directories(glm-lib INTERFACE ${glm-fetch_SOURCE_DIR})\n\n"
  },
  {
    "path": "cmake/imgui.cmake",
    "content": "FetchContent_Declare(\n    imgui-fetch\n    GIT_REPOSITORY https://github.com/ocornut/imgui.git\n    GIT_TAG        docking\n)\nFetchContent_MakeAvailable(imgui-fetch)\n\nFetchContent_Declare(\n    implot-fetch\n    GIT_REPOSITORY https://github.com/epezent/implot.git\n    GIT_TAG        v0.17\n)\nFetchContent_MakeAvailable(implot-fetch)\n\nFetchContent_Declare(\n    rlgui-fetch\n    GIT_REPOSITORY https://github.com/raylib-extras/rlImGui.git\n    GIT_TAG        main\n)\nFetchContent_MakeAvailable(rlgui-fetch)\n\nadd_library(imgui STATIC)\n\ntarget_include_directories(imgui PUBLIC ${imgui-fetch_SOURCE_DIR})\ntarget_include_directories(imgui PUBLIC ${implot-fetch_SOURCE_DIR})\ntarget_include_directories(imgui PUBLIC ${rlgui-fetch_SOURCE_DIR})\n\ntarget_sources(imgui PRIVATE \n    ${imgui-fetch_SOURCE_DIR}/imgui.cpp\n    ${imgui-fetch_SOURCE_DIR}/imgui_tables.cpp\n    ${imgui-fetch_SOURCE_DIR}/imgui_widgets.cpp\n    ${imgui-fetch_SOURCE_DIR}/imgui_draw.cpp\n    ${imgui-fetch_SOURCE_DIR}/imgui_demo.cpp)\n\ntarget_sources(imgui PRIVATE \n    ${implot-fetch_SOURCE_DIR}/implot.cpp\n    ${implot-fetch_SOURCE_DIR}/implot_items.cpp\n)\n\ntarget_sources(imgui PRIVATE \n    ${rlgui-fetch_SOURCE_DIR}/rlImGui.cpp\n)\n\ntarget_link_libraries(imgui PUBLIC raylib-lib)"
  },
  {
    "path": "cmake/openmp.cmake",
    "content": "if(${CMAKE_VERSION} LESS 3.30 AND ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} STREQUAL MSVC)\n\tadd_library(openmp INTERFACE)\n\n\ttarget_compile_options(openmp INTERFACE -openmp:llvm)\n\ttarget_link_libraries(openmp INTERFACE libomp)\nelseif(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang AND NOT ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} STREQUAL MSVC)\n\t# Non-VC non-CL clang on Windows just does not want to cooperate with FindOpenMP.\n\tadd_library(openmp INTERFACE)\n\n\ttarget_compile_options(openmp INTERFACE -fopenmp=libomp)\n\ttarget_link_options(openmp INTERFACE -fopenmp=libomp)\nelse()\n\tset(OpenMP_RUNTIME_MSVC llvm)\n\tfind_package(OpenMP REQUIRED COMPONENTS CXX)\n\n\tadd_library(openmp INTERFACE)\n\ttarget_link_libraries(openmp INTERFACE OpenMP::OpenMP_CXX)\nendif()\n"
  },
  {
    "path": "cmake/raylib.cmake",
    "content": "set(RL_VERSION 5.5)\nset(RL_URL_BASE https://github.com/raysan5/raylib/releases/download)\n\nFetchContent_Declare(raylib\nGIT_REPOSITORY https://github.com/raysan5/raylib.git\nGIT_TAG master)\nFetchContent_MakeAvailable(raylib)\n\nadd_library(raylib-lib INTERFACE)\n\ntarget_include_directories(raylib-lib INTERFACE ${raylib_SOURCE_DIR}/src)\ntarget_link_libraries(raylib-lib INTERFACE raylib)\n"
  },
  {
    "path": "cmake/yaml.cmake",
    "content": "include(FetchContent)\n\nFetchContent_Declare(\n  yaml-cpp\n  GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git\n  GIT_TAG yaml-cpp-0.9.0\n)\nFetchContent_MakeAvailable(yaml-cpp)"
  }
]