Full Code of NarcisCalin/Galaxy-Engine for AI

master ce36f2926aad cached
84 files
898.3 KB
277.3k tokens
222 symbols
1 requests
Download .txt
Showing preview only (935K chars total). Download the full file or copy to clipboard to get everything.
Repository: NarcisCalin/Galaxy-Engine
Branch: master
Commit: ce36f2926aad
Files: 84
Total size: 898.3 KB

Directory structure:
gitextract_wwlh0ody/

├── .gitattributes
├── .gitignore
├── CMakeLists.txt
├── CodeGuide.md
├── GalaxyEngine/
│   ├── Config/
│   │   └── config.txt
│   ├── Shaders/
│   │   ├── bloom.fs
│   │   ├── cubemap.fs
│   │   ├── cubemap.vs
│   │   ├── skybox.fs
│   │   └── skybox.vs
│   ├── fonts/
│   │   ├── Unispace Bd.otf
│   │   ├── Unispace Rg.otf
│   │   └── binary_to_compressed_c.cpp
│   ├── include/
│   │   ├── IO/
│   │   │   └── io.h
│   │   ├── Particles/
│   │   │   ├── QueryNeighbors.h
│   │   │   ├── clusterMouseHelper.h
│   │   │   ├── densitySize.h
│   │   │   ├── neighborSearch.h
│   │   │   ├── particle.h
│   │   │   ├── particleColorVisuals.h
│   │   │   ├── particleDeletion.h
│   │   │   ├── particleSelection.h
│   │   │   ├── particleSpaceship.h
│   │   │   ├── particleSubdivision.h
│   │   │   ├── particleTrails.h
│   │   │   └── particlesSpawning.h
│   │   ├── Physics/
│   │   │   ├── SPH.h
│   │   │   ├── SPH3D.h
│   │   │   ├── constraint.h
│   │   │   ├── field.h
│   │   │   ├── light.h
│   │   │   ├── materialsSPH.h
│   │   │   ├── morton.h
│   │   │   ├── physics.h
│   │   │   ├── physics3D.h
│   │   │   ├── quadtree.h
│   │   │   └── slingshot.h
│   │   ├── Renderer/
│   │   │   └── rayMarching.h
│   │   ├── Sound/
│   │   │   └── sound.h
│   │   ├── UI/
│   │   │   ├── UI.h
│   │   │   ├── brush.h
│   │   │   ├── controls.h
│   │   │   └── rightClickSettings.h
│   │   ├── UX/
│   │   │   ├── camera.h
│   │   │   ├── copyPaste.h
│   │   │   ├── randNum.h
│   │   │   ├── saveSystem.h
│   │   │   └── screenCapture.h
│   │   ├── globalLogic.h
│   │   ├── parameters.h
│   │   └── pch.h
│   └── src/
│       ├── Particles/
│       │   ├── particleSelection.cpp
│       │   ├── particleSubdivision.cpp
│       │   ├── particleTrails.cpp
│       │   └── particlesSpawning.cpp
│       ├── Physics/
│       │   ├── SPH.cpp
│       │   ├── SPH3D.cpp
│       │   ├── light.cpp
│       │   ├── morton.cpp
│       │   ├── physics.cpp
│       │   ├── physics3D.cpp
│       │   ├── quadtree.cpp
│       │   └── slingshot.cpp
│       ├── Sound/
│       │   └── sound.cpp
│       ├── UI/
│       │   ├── UI.cpp
│       │   ├── brush.cpp
│       │   ├── controls.cpp
│       │   └── rightClickSettings.cpp
│       ├── UX/
│       │   ├── camera.cpp
│       │   ├── randNum.cpp
│       │   ├── saveSystem.cpp
│       │   └── screenCapture.cpp
│       ├── globalLogic.cpp
│       ├── main.cpp
│       ├── parameters.cpp
│       └── resources.rc
├── LICENSE
├── README.md
└── cmake/
    ├── ffmpeg.cmake
    ├── glm.cmake
    ├── imgui.cmake
    ├── openmp.cmake
    ├── raylib.cmake
    └── yaml.cmake

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs     diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following 
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln       merge=binary
#*.csproj    merge=binary
#*.vbproj    merge=binary
#*.vcxproj   merge=binary
#*.vcproj    merge=binary
#*.dbproj    merge=binary
#*.fsproj    merge=binary
#*.lsproj    merge=binary
#*.wixproj   merge=binary
#*.modelproj merge=binary
#*.sqlproj   merge=binary
#*.wwaproj   merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg   binary
#*.png   binary
#*.gif   binary

###############################################################################
# diff behavior for common document formats
# 
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the 
# entries below.
###############################################################################
#*.doc   diff=astextplain
#*.DOC   diff=astextplain
#*.docx  diff=astextplain
#*.DOCX  diff=astextplain
#*.dot   diff=astextplain
#*.DOT   diff=astextplain
#*.pdf   diff=astextplain
#*.PDF   diff=astextplain
#*.rtf   diff=astextplain
#*.RTF   diff=astextplain


================================================
FILE: .gitignore
================================================
# Visual Studio Code settings
.vscode/

# Visual Studio settings
.vs/
CMakeSettings.json

# Build directory
build/
GalaxyEngine/build/

# Output directories
GalaxyEngine/Videos
GalaxyEngine/Saves
GalaxyEngine/Screenshot_*
GalaxyEngine/Screenshots/
GalaxyEngine/compilerCommand.txt
GalaxyEngine/include/Memory/
GalaxyEngine/x64/
x64/

# Misc
*.diff
.clang-format
.scripts/
.github/
Saves/



================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.26..3.30 FATAL_ERROR)

set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
cmake_policy(SET CMP0077 NEW)

cmake_policy(SET CMP0135 NEW)

project(GalaxyEngine)

if(MSVC OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
    add_compile_options("$<$<CONFIG:Debug>:/Od;/RTC1;/MDd>")
    add_compile_options("$<$<CONFIG:Release>:/O2;/MD>")
endif()

include(FetchContent)

include(${CMAKE_CURRENT_LIST_DIR}/cmake/ffmpeg.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/glm.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/openmp.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/raylib.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/imgui.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/yaml.cmake)

set(
    GALAXYENGINE_SOURCES
    main.cpp
    globalLogic.cpp
    parameters.cpp
    Particles/particleSelection.cpp
    Particles/particlesSpawning.cpp
    Particles/particleSubdivision.cpp
    Particles/particleTrails.cpp
    Physics/morton.cpp
    Physics/physics.cpp
    Physics/physics3D.cpp
    Physics/quadtree.cpp
    Physics/slingshot.cpp
    Physics/SPH.cpp
    Physics/SPH3D.cpp
    Physics/light.cpp
    Sound/sound.cpp
    UI/brush.cpp
    UI/controls.cpp
    UI/rightClickSettings.cpp
    UI/UI.cpp
    UX/camera.cpp
    UX/saveSystem.cpp
    UX/screenCapture.cpp
    UX/randNum.cpp
    resources.rc)

list(TRANSFORM GALAXYENGINE_SOURCES PREPEND ${CMAKE_CURRENT_LIST_DIR}/GalaxyEngine/src/)

add_executable(GalaxyEngine ${GALAXYENGINE_SOURCES})

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    target_compile_options(GalaxyEngine PRIVATE -mavx2 -mfma)
elseif(MSVC)
    target_compile_options(GalaxyEngine PRIVATE /arch:AVX2)
else()
    target_compile_options(GalaxyEngine PRIVATE -mavx2 -mfma)
endif()

target_precompile_headers(GalaxyEngine PUBLIC ${CMAKE_CURRENT_LIST_DIR}/GalaxyEngine/include/pch.h)

target_include_directories(GalaxyEngine PUBLIC ${CMAKE_CURRENT_LIST_DIR}/GalaxyEngine/include)

target_compile_features(GalaxyEngine PRIVATE cxx_std_20)
set_target_properties(
    GalaxyEngine PROPERTIES 
    MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL"
    VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/GalaxyEngine
)

target_link_libraries(GalaxyEngine PUBLIC raylib-lib ffmpeg imgui glm-lib openmp yaml-cpp::yaml-cpp)

if(WIN32)
    add_custom_command(
        TARGET GalaxyEngine POST_BUILD 
        COMMAND ${CMAKE_COMMAND} -E copy_directory 
        ${ffmpeg-fetch_SOURCE_DIR}/bin 
        $<TARGET_FILE_DIR:GalaxyEngine>
    )
endif()

if(UNIX)
    find_package(TBB REQUIRED)
    target_link_libraries(GalaxyEngine PUBLIC TBB::tbb)
endif()

================================================
FILE: CodeGuide.md
================================================
# Code Quick Guide

This is a quick guide for modding or contributing to the development of Galaxy Engine. Please try to follow this guide as much as possible. 

Before starting to mod or contribute to Galaxy Engine, please check the [roadmap](https://github.com/users/NarcisCalin/projects/1/views/1).

If you want to contribute or mod Galaxy Engine and have any questions, consider joining our [Discord Community](https://discord.gg/Xd5JUqNFPM).

## General

- Galaxy Engine's code uses ***camelCase*** for everything except classes. Classes and structs use ***PascalCase***.
- The code must be compatible with Windows and Linux before being added to the main branch.
- The code must be compatible with the MIT license.
- 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"**.
- Global variables go to the "UpdateVariables" struct in **"parameters.h"**.
- Try to use the **"UI::buttonHelper()"** and **"UI::sliderHelper()"** functions when adding UI elements.
- When you pass the "UpdateParameters" or "UpdateVariables" structs to a function, **ALWAYS** name them **"myParam"** and **"myVar"**.
- 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.
- Keep physics and similar features inside **"updateScene();"**.
- Keep visual features (like UI, particle visuals, etc.) inside **"drawScene();"**.
- For now, the current vector struct used in Galaxy Engine is **"glm::vec2"**.
- Colors currently use Raylib's **"Color"** struct.
- The current physics are built on top of a base framerate of 144 FPS.
- Try to use float instead of double. There can be exceptions if needed like the **"G"** constant.
- When asking for user keyboard input, use the custom IO::shortcut(key) from the io.h file instead of raylib's keyboard input.
- Stuff from files like images, fonts, etc., that are loaded into memory must be unloaded when the program closes.
- 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.

## Particles

- Particles are composed of 2 structs.
- **"ParticlePhysics()"** is used for physics calculations only.
- **"ParticleRendering()"** is used for particle visuals like **"color"** or **"size"**, and distinctive attributes like **"isDarkMatter"** or **"canBeResized"**.
- There is one std::vector for each struct. They are named **"pParticles"** and **"rParticles"**.
- 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**.
- All particles are sorted spatially with Z-Curves. This means that particles' index inside a vector or array changes constantly between frames.
- 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.

## UI

- The Galaxy Engine UI is made entirely using the [Dear ImGui](https://github.com/ocornut/imgui) library. Please check their documentation.
- All ImGui UI elements must go after **"rlImGuiBegin();"** and before **"rlImGuiEnd();"**, which can be found in the main while loop.
- Please try to keep all UI elements inside the **"uiLogic()"** function. There can be exceptions for certain features.
- 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"**.
- Keep global buttons in the menu on the right.
- To use Roboto-Medium or change font size for a window, you can do it like this:

![image](https://github.com/user-attachments/assets/4f70e09d-cbd8-46ae-a960-96cb5a9f57c4)


================================================
FILE: GalaxyEngine/Config/config.txt
================================================
Global Volume: 0.400000006
Menu Volume: 0.25
Music Volume: 0.400000006
Message Index: 3

================================================
FILE: GalaxyEngine/Shaders/bloom.fs
================================================
#version 330

in vec2 fragTexCoord;
in vec4 fragColor;

uniform sampler2D texture0;
uniform vec4 colDiffuse;
uniform vec2 res;

uniform int glowSize;
uniform float glowStrength;

out vec4 finalColor;

void main()
{
    vec4 texColor = texture(texture0, fragTexCoord);

    vec4 nFrag = vec4(0.0);
float weightSum = 0.0;

for(int dx = -glowSize; dx <= glowSize; dx++){
    for(int dy = -glowSize; dy <= glowSize; dy++){
        vec2 offset = vec2(float(dx), float(dy)) / res;
        float weight = 1.0 - (abs(float(dx)) + abs(float(dy))) / (2.0 * float(glowSize) + 1.0) * 0.5;
        nFrag += texture(texture0, fragTexCoord + offset) * weight;
        weightSum += weight;
    }
}

nFrag /= weightSum;

    finalColor = texColor + nFrag * glowStrength;
}

================================================
FILE: GalaxyEngine/Shaders/cubemap.fs
================================================
#version 100

precision mediump float;

// Input vertex attributes (from vertex shader)
varying vec3 fragPosition;

// Input uniform values
uniform sampler2D equirectangularMap;

vec2 SampleSphericalMap(vec3 v)
{
    vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
    uv *= vec2(0.1591, 0.3183);
    uv += 0.5;
    return uv;
}

void main()
{
    // Normalize local position
    vec2 uv = SampleSphericalMap(normalize(fragPosition));

    // Fetch color from texture map
    vec3 color = texture2D(equirectangularMap, uv).rgb;

    // Calculate final fragment color
    gl_FragColor = vec4(color, 1.0);
}


================================================
FILE: GalaxyEngine/Shaders/cubemap.vs
================================================
#version 100

// Input vertex attributes
attribute vec3 vertexPosition;

// Input uniform values
uniform mat4 matProjection;
uniform mat4 matView;

// Output vertex attributes (to fragment shader)
varying vec3 fragPosition;

void main()
{
    // Calculate fragment position based on model transformations
    fragPosition = vertexPosition;

    // Calculate final vertex position
    gl_Position = matProjection*matView*vec4(vertexPosition, 1.0);
}


================================================
FILE: GalaxyEngine/Shaders/skybox.fs
================================================
#version 330

// Input vertex attributes (from vertex shader)
in vec3 fragPosition;

// Input uniform values
uniform samplerCube environmentMap;
uniform bool vflipped;
uniform bool doGamma;

// Output fragment color
out vec4 finalColor;

void main()
{
    // Fetch color from texture map
    vec3 color = vec3(0.0);

    if (vflipped) color = texture(environmentMap, vec3(fragPosition.x, -fragPosition.y, fragPosition.z)).rgb;
    else color = texture(environmentMap, fragPosition).rgb;

    if (doGamma)// Apply gamma correction
    {
        color = color/(color + vec3(1.0));
        color = pow(color, vec3(1.0/2.2));
    }

    // Calculate final fragment color
    finalColor = vec4(color, 1.0);
}


================================================
FILE: GalaxyEngine/Shaders/skybox.vs
================================================
#version 330

// Input vertex attributes
in vec3 vertexPosition;

// Input uniform values
uniform mat4 matProjection;
uniform mat4 matView;

// Output vertex attributes (to fragment shader)
out vec3 fragPosition;

void main()
{
    // Calculate fragment position based on model transformations
    fragPosition = vertexPosition;

    // Remove translation from the view matrix
    mat4 rotView = mat4(mat3(matView));
    vec4 clipPos = matProjection*rotView*vec4(vertexPosition, 1.0);

    // Calculate final vertex position
    gl_Position = clipPos;
}


================================================
FILE: GalaxyEngine/fonts/binary_to_compressed_c.cpp
================================================
// dear imgui
// (binary_to_compressed_c.cpp)
// Helper tool to turn a file into a C array, if you want to embed font data in your source code.

// The data is first compressed with stb_compress() to reduce source code size.
// Then stored in a C array:
// - Base85:   ~5 bytes of source code for 4 bytes of input data. 5 bytes stored in binary (suggested by @mmalex).
// - 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.
// - As char: ~12 bytes of source code for 4 bytes of input data. 4 bytes stored in binary. Not endianness dependent.
// Load compressed TTF fonts with ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF()

// Build with, e.g:
//   # cl.exe binary_to_compressed_c.cpp
//   # g++ binary_to_compressed_c.cpp
//   # clang++ binary_to_compressed_c.cpp
// You can also find a precompiled Windows binary in the binary/demo package available from https://github.com/ocornut/imgui

// Usage:
//   binary_to_compressed_c.exe [-nocompress] [-nostatic] [-base85] <inputfile> <symbolname>
// Usage example:
//   # binary_to_compressed_c.exe myfont.ttf MyFont > myfont.cpp
//   # binary_to_compressed_c.exe -base85 myfont.ttf MyFont > myfont.cpp
// Note:
//   Base85 encoding will be obsoleted by future version of Dear ImGui!

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

// stb_compress* from stb.h - declaration
typedef unsigned int stb_uint;
typedef unsigned char stb_uchar;
stb_uint stb_compress(stb_uchar* out, stb_uchar* in, stb_uint len);

enum SourceEncoding
{
    SourceEncoding_U8,      // New default since 2024/11
    SourceEncoding_U32,
    SourceEncoding_Base85,
};

static bool binary_to_compressed_c(const char* filename, const char* symbol, SourceEncoding source_encoding, bool use_compression, bool use_static);

int main(int argc, char** argv)
{
    if (argc < 3)
    {
        printf("Syntax: %s [-u8|-u32|-base85] [-nocompress] [-nostatic] <inputfile> <symbolname>\n", argv[0]);
        printf("Source encoding types:\n");
        printf(" -u8     = ~12 bytes of source per 4 bytes of data. 4 bytes in binary.\n");
        printf(" -u32    = ~11 bytes of source per 4 bytes of data. 4 bytes in binary. Need endianness swapping on big-endian.\n");
        printf(" -base85 =  ~5 bytes of source per 4 bytes of data. 5 bytes in binary. Need decoder.\n");
        return 0;
    }

    int argn = 1;
    bool use_compression = true;
    bool use_static = true;
    SourceEncoding source_encoding = SourceEncoding_U8; // New default
    while (argn < (argc - 2) && argv[argn][0] == '-')
    {
        if (strcmp(argv[argn], "-u8") == 0) { source_encoding = SourceEncoding_U8; argn++; }
        else if (strcmp(argv[argn], "-u32") == 0) { source_encoding = SourceEncoding_U32; argn++; }
        else if (strcmp(argv[argn], "-base85") == 0) { source_encoding = SourceEncoding_Base85; argn++; }
        else if (strcmp(argv[argn], "-nocompress") == 0) { use_compression = false; argn++; }
        else if (strcmp(argv[argn], "-nostatic") == 0) { use_static = false; argn++; }
        else
        {
            fprintf(stderr, "Unknown argument: '%s'\n", argv[argn]);
            return 1;
        }
    }

    bool ret = binary_to_compressed_c(argv[argn], argv[argn + 1], source_encoding, use_compression, use_static);
    if (!ret)
        fprintf(stderr, "Error opening or reading file: '%s'\n", argv[argn]);
    return ret ? 0 : 1;
}

char Encode85Byte(unsigned int x)
{
    x = (x % 85) + 35;
    return (char)((x >= '\\') ? x + 1 : x);
}

bool binary_to_compressed_c(const char* filename, const char* symbol, SourceEncoding source_encoding, bool use_compression, bool use_static)
{
    // Read file
    FILE* f = fopen(filename, "rb");
    if (!f) return false;
    int data_sz;
    if (fseek(f, 0, SEEK_END) || (data_sz = (int)ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) { fclose(f); return false; }
    char* data = new char[data_sz + 4];
    if (fread(data, 1, data_sz, f) != (size_t)data_sz) { fclose(f); delete[] data; return false; }
    memset((void*)(((char*)data) + data_sz), 0, 4);
    fclose(f);

    // Compress
    int maxlen = data_sz + 512 + (data_sz >> 2) + sizeof(int); // total guess
    char* compressed = use_compression ? new char[maxlen] : data;
    int compressed_sz = use_compression ? stb_compress((stb_uchar*)compressed, (stb_uchar*)data, data_sz) : data_sz;
    if (use_compression)
        memset(compressed + compressed_sz, 0, maxlen - compressed_sz);

    // Output as Base85 encoded
    FILE* out = stdout;
    fprintf(out, "// File: '%s' (%d bytes)\n", filename, (int)data_sz);
    const char* static_str = use_static ? "static " : "";
    const char* compressed_str = use_compression ? "compressed_" : "";
    if (source_encoding == SourceEncoding_Base85)
    {
        fprintf(out, "// Exported using binary_to_compressed_c.exe -base85 \"%s\" %s\n", filename, symbol);
        fprintf(out, "%sconst char %s_%sdata_base85[%d+1] =\n    \"", static_str, symbol, compressed_str, (int)((compressed_sz + 3) / 4)*5);
        char prev_c = 0;
        for (int src_i = 0; src_i < compressed_sz; src_i += 4)
        {
            // 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 ??.
            unsigned int d = *(unsigned int*)(compressed + src_i);
            for (unsigned int n5 = 0; n5 < 5; n5++, d /= 85)
            {
                char c = Encode85Byte(d);
                fprintf(out, (c == '?' && prev_c == '?') ? "\\%c" : "%c", c);
                prev_c = c;
            }
            if ((src_i % 112) == 112 - 4)
                fprintf(out, "\"\n    \"");
        }
        fprintf(out, "\";\n\n");
    }
    else if (source_encoding == SourceEncoding_U8)
    {
        // As individual bytes, not subject to endianness issues.
        fprintf(out, "// Exported using binary_to_compressed_c.exe -u8 \"%s\" %s\n", filename, symbol);
        fprintf(out, "%sconst unsigned int %s_%ssize = %d;\n", static_str, symbol, compressed_str, (int)compressed_sz);
        fprintf(out, "%sconst unsigned char %s_%sdata[%d] =\n{", static_str, symbol, compressed_str, (int)compressed_sz);
        int column = 0;
        for (int i = 0; i < compressed_sz; i++)
        {
            unsigned char d = *(unsigned char*)(compressed + i);
            if (column == 0)
                fprintf(out, "\n    ");
            column += fprintf(out, "%d,", d);
            if (column >= 180)
                column = 0;
        }
        fprintf(out, "\n};\n\n");
    }
    else if (source_encoding == SourceEncoding_U32)
    {
        // As integers
        fprintf(out, "// Exported using binary_to_compressed_c.exe -u32 \"%s\" %s\n", filename, symbol);
        fprintf(out, "%sconst unsigned int %s_%ssize = %d;\n", static_str, symbol, compressed_str, (int)compressed_sz);
        fprintf(out, "%sconst unsigned int %s_%sdata[%d/4] =\n{", static_str, symbol, compressed_str, (int)((compressed_sz + 3) / 4)*4);
        int column = 0;
        for (int i = 0; i < compressed_sz; i += 4)
        {
            unsigned int d = *(unsigned int*)(compressed + i);
            if ((column++ % 14) == 0)
                fprintf(out, "\n    0x%08x, ", d);
            else
                fprintf(out, "0x%08x, ", d);
        }
        fprintf(out, "\n};\n\n");
    }

    // Cleanup
    delete[] data;
    if (use_compression)
        delete[] compressed;
    return true;
}

// stb_compress* from stb.h - definition

////////////////////           compressor         ///////////////////////

static stb_uint stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uint buflen)
{
    const unsigned long ADLER_MOD = 65521;
    unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16;
    unsigned long blocklen, i;

    blocklen = buflen % 5552;
    while (buflen) {
        for (i=0; i + 7 < blocklen; i += 8) {
            s1 += buffer[0], s2 += s1;
            s1 += buffer[1], s2 += s1;
            s1 += buffer[2], s2 += s1;
            s1 += buffer[3], s2 += s1;
            s1 += buffer[4], s2 += s1;
            s1 += buffer[5], s2 += s1;
            s1 += buffer[6], s2 += s1;
            s1 += buffer[7], s2 += s1;

            buffer += 8;
        }

        for (; i < blocklen; ++i)
            s1 += *buffer++, s2 += s1;

        s1 %= ADLER_MOD, s2 %= ADLER_MOD;
        buflen -= blocklen;
        blocklen = 5552;
    }
    return (s2 << 16) + s1;
}

static unsigned int stb_matchlen(stb_uchar *m1, stb_uchar *m2, stb_uint maxlen)
{
    stb_uint i;
    for (i=0; i < maxlen; ++i)
        if (m1[i] != m2[i]) return i;
    return i;
}

// simple implementation that just takes the source data in a big block

static stb_uchar *stb__out;
static FILE      *stb__outfile;
static stb_uint   stb__outbytes;

static void stb__write(unsigned char v)
{
    fputc(v, stb__outfile);
    ++stb__outbytes;
}

//#define stb_out(v)    (stb__out ? *stb__out++ = (stb_uchar) (v) : stb__write((stb_uchar) (v)))
#define stb_out(v)    do { if (stb__out) *stb__out++ = (stb_uchar) (v); else stb__write((stb_uchar) (v)); } while (0)

static void stb_out2(stb_uint v) { stb_out(v >> 8); stb_out(v); }
static void stb_out3(stb_uint v) { stb_out(v >> 16); stb_out(v >> 8); stb_out(v); }
static void stb_out4(stb_uint v) { stb_out(v >> 24); stb_out(v >> 16); stb_out(v >> 8 ); stb_out(v); }

static void outliterals(stb_uchar *in, int numlit)
{
    while (numlit > 65536) {
        outliterals(in,65536);
        in     += 65536;
        numlit -= 65536;
    }

    if      (numlit ==     0)    ;
    else if (numlit <=    32)    stb_out (0x000020 + numlit-1);
    else if (numlit <=  2048)    stb_out2(0x000800 + numlit-1);
    else /*  numlit <= 65536) */ stb_out3(0x070000 + numlit-1);

    if (stb__out) {
        memcpy(stb__out,in,numlit);
        stb__out += numlit;
    } else
        fwrite(in, 1, numlit, stb__outfile);
}

static int stb__window = 0x40000; // 256K

static int stb_not_crap(int best, int dist)
{
    return   ((best > 2  &&  dist <= 0x00100)
        || (best > 5  &&  dist <= 0x04000)
        || (best > 7  &&  dist <= 0x80000));
}

static  stb_uint stb__hashsize = 32768;

// note that you can play with the hashing functions all you
// want without needing to change the decompressor
#define stb__hc(q,h,c)      (((h) << 7) + ((h) >> 25) + q[c])
#define stb__hc2(q,h,c,d)   (((h) << 14) + ((h) >> 18) + (q[c] << 7) + q[d])
#define stb__hc3(q,c,d,e)   ((q[c] << 14) + (q[d] << 7) + q[e])

static unsigned int stb__running_adler;

static int stb_compress_chunk(stb_uchar *history,
    stb_uchar *start,
    stb_uchar *end,
    int length,
    int *pending_literals,
    stb_uchar **chash,
    stb_uint mask)
{
    (void)history;
    int window = stb__window;
    stb_uint match_max;
    stb_uchar *lit_start = start - *pending_literals;
    stb_uchar *q = start;

#define STB__SCRAMBLE(h)   (((h) + ((h) >> 16)) & mask)

    // stop short of the end so we don't scan off the end doing
    // the hashing; this means we won't compress the last few bytes
    // unless they were part of something longer
    while (q < start+length && q+12 < end) {
        int m;
        stb_uint h1,h2,h3,h4, h;
        stb_uchar *t;
        int best = 2, dist=0;

        if (q+65536 > end)
            match_max = (stb_uint)(end-q);
        else
            match_max = 65536;

#define stb__nc(b,d)  ((d) <= window && ((b) > 9 || stb_not_crap((int)(b),(int)(d))))

#define STB__TRY(t,p)  /* avoid retrying a match we already tried */ \
    if (p ? dist != (int)(q-t) : 1)                             \
    if ((m = stb_matchlen(t, q, match_max)) > best)     \
    if (stb__nc(m,q-(t)))                                \
    best = m, dist = (int)(q - (t))

        // rather than search for all matches, only try 4 candidate locations,
        // chosen based on 4 different hash functions of different lengths.
        // this strategy is inspired by LZO; hashing is unrolled here using the
        // 'hc' macro
        h = stb__hc3(q,0, 1, 2); h1 = STB__SCRAMBLE(h);
        t = chash[h1]; if (t) STB__TRY(t,0);
        h = stb__hc2(q,h, 3, 4); h2 = STB__SCRAMBLE(h);
        h = stb__hc2(q,h, 5, 6);        t = chash[h2]; if (t) STB__TRY(t,1);
        h = stb__hc2(q,h, 7, 8); h3 = STB__SCRAMBLE(h);
        h = stb__hc2(q,h, 9,10);        t = chash[h3]; if (t) STB__TRY(t,1);
        h = stb__hc2(q,h,11,12); h4 = STB__SCRAMBLE(h);
        t = chash[h4]; if (t) STB__TRY(t,1);

        // because we use a shared hash table, can only update it
        // _after_ we've probed all of them
        chash[h1] = chash[h2] = chash[h3] = chash[h4] = q;

        if (best > 2)
            assert(dist > 0);

        // see if our best match qualifies
        if (best < 3) { // fast path literals
            ++q;
        } else if (best > 2  &&  best <= 0x80    &&  dist <= 0x100) {
            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
            stb_out(0x80 + best-1);
            stb_out(dist-1);
        } else if (best > 5  &&  best <= 0x100   &&  dist <= 0x4000) {
            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
            stb_out2(0x4000 + dist-1);
            stb_out(best-1);
        } else if (best > 7  &&  best <= 0x100   &&  dist <= 0x80000) {
            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
            stb_out3(0x180000 + dist-1);
            stb_out(best-1);
        } else if (best > 8  &&  best <= 0x10000 &&  dist <= 0x80000) {
            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
            stb_out3(0x100000 + dist-1);
            stb_out2(best-1);
        } else if (best > 9                      &&  dist <= 0x1000000) {
            if (best > 65536) best = 65536;
            outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
            if (best <= 0x100) {
                stb_out(0x06);
                stb_out3(dist-1);
                stb_out(best-1);
            } else {
                stb_out(0x04);
                stb_out3(dist-1);
                stb_out2(best-1);
            }
        } else {  // fallback literals if no match was a balanced tradeoff
            ++q;
        }
    }

    // if we didn't get all the way, add the rest to literals
    if (q-start < length)
        q = start+length;

    // the literals are everything from lit_start to q
    *pending_literals = (int)(q - lit_start);

    stb__running_adler = stb_adler32(stb__running_adler, start, (stb_uint)(q - start));
    return (int)(q - start);
}

static int stb_compress_inner(stb_uchar *input, stb_uint length)
{
    int literals = 0;
    stb_uint len,i;

    stb_uchar **chash;
    chash = (stb_uchar**) malloc(stb__hashsize * sizeof(stb_uchar*));
    if (chash == nullptr) return 0; // failure
    for (i=0; i < stb__hashsize; ++i)
        chash[i] = nullptr;

    // stream signature
    stb_out(0x57); stb_out(0xbc);
    stb_out2(0);

    stb_out4(0);       // 64-bit length requires 32-bit leading 0
    stb_out4(length);
    stb_out4(stb__window);

    stb__running_adler = 1;

    len = stb_compress_chunk(input, input, input+length, length, &literals, chash, stb__hashsize-1);
    assert(len == length);

    outliterals(input+length - literals, literals);

    free(chash);

    stb_out2(0x05fa); // end opcode

    stb_out4(stb__running_adler);

    return 1; // success
}

stb_uint stb_compress(stb_uchar *out, stb_uchar *input, stb_uint length)
{
    stb__out = out;
    stb__outfile = nullptr;

    stb_compress_inner(input, length);

    return (stb_uint)(stb__out - out);
}


================================================
FILE: GalaxyEngine/include/IO/io.h
================================================
#pragma once

// Input/Output utility functions
namespace IO {
    
    // Handle keyboard shortcuts with ImGui integration
    static inline bool shortcutPress(int key) {
        ImGuiIO& io = ImGui::GetIO();
        if (io.WantCaptureKeyboard) {
            return false;
        }
        return IsKeyPressed(key);
    }

    static inline bool shortcutDown(int key) {
        ImGuiIO& io = ImGui::GetIO();
        if (io.WantCaptureKeyboard) {
            return false;
        }
        return IsKeyDown(key);
    }

    static inline bool shortcutReleased(int key) {
        ImGuiIO& io = ImGui::GetIO();
        if (io.WantCaptureKeyboard) {
            return false;
        }
        return IsKeyReleased(key);
    }

    static inline bool mousePress(int key) {
        ImGuiIO& io = ImGui::GetIO();
        if (io.WantCaptureMouse) {
            return false;
        }
        return IsMouseButtonPressed(key);
    }

    static inline bool mouseDown(int key) {
        ImGuiIO& io = ImGui::GetIO();
        if (io.WantCaptureMouse) {
            return false;
        }
        return IsMouseButtonDown(key);
    }

    static inline bool mouseReleased(int key) {
        ImGuiIO& io = ImGui::GetIO();
        if (io.WantCaptureMouse) {
            return false;
        }
        return IsMouseButtonReleased(key);
    }

    // Additional IO utility functions can be added here in the future
    // For example:
    // - Mouse handling utilities
    // - File IO helpers
    // - Keyboard state management
    // - Input validation functions
}


================================================
FILE: GalaxyEngine/include/Particles/QueryNeighbors.h
================================================
#pragma once

#include "parameters.h"

struct QueryNeighbors {

	static std::vector<size_t> queryNeighbors(UpdateParameters& myParam, bool& hasAVX2, size_t reserveAmount, glm::vec2& pos) {
		std::vector<size_t> neighborIndices;
		neighborIndices.reserve(reserveAmount);

		if (!hasAVX2) {
			myParam.neighborSearchV2.queryNeighbors(
				pos,
				[&](uint32_t idx) {
					neighborIndices.push_back(idx);
				}
			);
		}
		else {
			myParam.neighborSearchV2AVX2.queryNeighborsAVX2(
				pos,
				neighborIndices
			);
		}

		return neighborIndices;
	}
};

struct QueryNeighbors3D {

	static std::vector<size_t> queryNeighbors3D(UpdateParameters& myParam, bool& hasAVX2, size_t reserveAmount, glm::vec3& pos) {
		std::vector<size_t> neighborIndices;
		neighborIndices.reserve(reserveAmount);

		if (!hasAVX2) {
			myParam.neighborSearch3DV2.queryNeighbors(
				pos,
				[&](uint32_t idx) {
					neighborIndices.push_back(idx);
				}
			);
		}
		else {
			myParam.neighborSearch3DV2AVX2.queryNeighborsAVX2(
				pos,
				neighborIndices
			);
		}

		return neighborIndices;
	}
};

================================================
FILE: GalaxyEngine/include/Particles/clusterMouseHelper.h
================================================
#pragma once

#include "Physics/quadtree.h"

struct ClusterHelper {
	static void clusterMouseHelper(Camera3D& cam3D, float& dist) {

		Ray camRay = GetScreenToWorldRay(GetMousePosition(), cam3D);
		float minDistSq = std::numeric_limits<float>::max();
		int nearestIdx = -1;

		for (size_t i = 0; i < globalNodes3D.size(); i++) {
			Node3D& n = globalNodes3D[i];

			if (n.size > 16.0f || n.endIndex - n.startIndex < 16) {
				continue;
			}

			Vector3 bMin = { n.pos.x, n.pos.y, n.pos.z };
			Vector3 bMax = { n.pos.x + n.size, n.pos.y + n.size, n.pos.z + n.size };
			BoundingBox bBox = { bMin, bMax };
			RayCollision rayColl = GetRayCollisionBox(camRay, bBox);

			if (rayColl.hit) {

				glm::vec3 d = n.centerOfMass - glm::vec3{ camRay.position.x, camRay.position.y, camRay.position.z };
				float distSq = glm::dot(d, d);

				if (distSq < minDistSq) {
					minDistSq = distSq;
					nearestIdx = static_cast<int>(i);
				}
			}
		}

		if (nearestIdx != -1) {

			/*DrawCubeWiresV({ nearest.pos.x, nearest.pos.y, nearest.pos.z },
				{ nearest.size, nearest.size, nearest.size },
				{ 128, 255, 128, 128 });*/

			dist = std::sqrt(minDistSq);
		}
	}
};

================================================
FILE: GalaxyEngine/include/Particles/densitySize.h
================================================
#pragma once

#include "Particles/particle.h"

struct DensitySize {

	float minSize = 0.17f;
	float maxSize = 0.62f;

	float sizeAcc = 22.0f;

	int maxNeighbors = 200;

	void sizeByDensity(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, 
		std::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D,
		bool& isDensitySizeEnabled, bool& isForceSizeEnabled,
		float& sizeMultiplier, bool is3DMode) {

		if (isForceSizeEnabled) {
			if (!is3DMode) {
#pragma omp parallel for schedule(dynamic)

				for (int64_t i = 0; i < pParticles.size(); i++) {
					if (rParticles[i].isSolid || rParticles[i].isDarkMatter) {
						continue;
					}

					float particleAccSq = pParticles[i].acc.x * pParticles[i].acc.x +
						pParticles[i].acc.y * pParticles[i].acc.y;

					float clampedAcc = std::clamp(sqrt(particleAccSq), 0.0f, sizeAcc);
					float normalizedAcc = clampedAcc / sizeAcc;

					rParticles[i].size = Lerp(maxSize * sizeMultiplier, minSize * sizeMultiplier, normalizedAcc);
				}
			}
			else {
				for (int64_t i = 0; i < pParticles3D.size(); i++) {
					if (rParticles3D[i].isSolid || rParticles3D[i].isDarkMatter) {
						continue;
					}

					float particleAccSq = pParticles3D[i].acc.x * pParticles3D[i].acc.x +
						pParticles3D[i].acc.y * pParticles3D[i].acc.y + pParticles3D[i].acc.z * pParticles3D[i].acc.z;

					float clampedAcc = std::clamp(sqrt(particleAccSq), 0.0f, sizeAcc);
					float normalizedAcc = clampedAcc / sizeAcc;

					rParticles3D[i].size = Lerp(maxSize * sizeMultiplier, minSize * sizeMultiplier, normalizedAcc);
				}
			}
		}

		if (isDensitySizeEnabled) {

			if (!is3DMode) {
				std::vector<int> neighborCounts(pParticles.size(), 0);
#pragma omp parallel for schedule(dynamic)
				for (int64_t i = 0; i < pParticles.size(); i++) {

					if (rParticles[i].isDarkMatter || rParticles[i].isSolid) {
						continue;
					}

					float normalDensity = std::min(float(rParticles[i].neighbors) / maxNeighbors, 1.0f);

					rParticles[i].size = Lerp(maxSize * sizeMultiplier, minSize * sizeMultiplier, static_cast<float>(pow(normalDensity, 2)));
				}
			}
			else {
				std::vector<int> neighborCounts(pParticles3D.size(), 0);
#pragma omp parallel for schedule(dynamic)
				for (int64_t i = 0; i < pParticles3D.size(); i++) {

					if (rParticles3D[i].isDarkMatter || rParticles3D[i].isSolid) {
						continue;
					}

					float normalDensity = std::min(float(rParticles3D[i].neighbors) / maxNeighbors, 1.0f);

					rParticles3D[i].size = Lerp(maxSize * sizeMultiplier, minSize * sizeMultiplier, static_cast<float>(pow(normalDensity, 2)));
				}
			}
		}
	}

};

================================================
FILE: GalaxyEngine/include/Particles/neighborSearch.h
================================================
#pragma once

#include "Particles/particle.h"

struct NeighborSearch {

	std::vector<uint32_t> globalNeighborList;

	float originalDensityRadius = 3.5f;
	float densityRadius = originalDensityRadius;
	float cellSize = 3.0f;

	std::vector<size_t> cellCounts;
	std::vector<size_t> cellStart;
	std::vector<size_t> cellParticles;
	std::vector<int> cellXList;
	std::vector<int> cellYList;

	std::vector<size_t> idToIndexTable;

	void calculateGridBounds(const std::vector<ParticlePhysics>& pParticles,
		const std::vector<size_t>& activeIndices,
		int& gridWidth, int& gridHeight,
		float& minX, float& minY)
	{

		float maxX = std::numeric_limits<float>::lowest();
		float maxY = std::numeric_limits<float>::lowest();
		minX = std::numeric_limits<float>::max();
		minY = std::numeric_limits<float>::max();

#pragma omp parallel for reduction(min:minX, minY) reduction(max:maxX, maxY)
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			const auto& pos = pParticles[activeIndices[i]].pos;
			if (pos.x < minX) minX = pos.x;
			if (pos.y < minY) minY = pos.y;
			if (pos.x > maxX) maxX = pos.x;
			if (pos.y > maxY) maxY = pos.y;
		}

		minX -= cellSize; minY -= cellSize;
		maxX += cellSize; maxY += cellSize;

		gridWidth = std::max(1, static_cast<int>((maxX - minX) / cellSize) + 1);
		gridHeight = std::max(1, static_cast<int>((maxY - minY) / cellSize) + 1);
	}

	void UpdateNeighbors(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles) {
		if (pParticles.empty()) return;

		float densityRadiusSq = densityRadius * densityRadius;

		std::vector<size_t> activeIndices;
		activeIndices.reserve(pParticles.size());
		for (size_t i = 0; i < pParticles.size(); i++) {
			if (!rParticles[i].isDarkMatter) {
				activeIndices.push_back(i);
			}
		}

		int gridWidth, gridHeight;
		float minX, minY;
		calculateGridBounds(pParticles, activeIndices, gridWidth, gridHeight, minX, minY);

		int numCells = gridWidth * gridHeight;

		if (cellCounts.size() < numCells) cellCounts.resize(numCells);
		std::fill(cellCounts.begin(), cellCounts.end(), 0);

		if (cellXList.size() < activeIndices.size()) cellXList.resize(activeIndices.size());
		if (cellYList.size() < activeIndices.size()) cellYList.resize(activeIndices.size());

#pragma omp parallel for
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			size_t pIdx = activeIndices[i];
			const auto& pos = pParticles[pIdx].pos;

			int cx = static_cast<int>((pos.x - minX) / cellSize);
			int cy = static_cast<int>((pos.y - minY) / cellSize);

			cx = std::max(0, std::min(cx, gridWidth - 1));
			cy = std::max(0, std::min(cy, gridHeight - 1));

			cellXList[i] = cx;
			cellYList[i] = cy;

			int cellIdx = cy * gridWidth + cx;
#pragma omp atomic
			cellCounts[cellIdx]++;
		}

		if (cellStart.size() < numCells + 1) cellStart.resize(numCells + 1);
		cellStart[0] = 0;
		for (int i = 0; i < numCells; i++) {
			cellStart[i + 1] = cellStart[i] + cellCounts[i];
		}

		if (cellParticles.size() < activeIndices.size()) cellParticles.resize(activeIndices.size());

		std::vector<size_t> fillCursor = cellStart;

#pragma omp parallel for
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			int cx = cellXList[i];
			int cy = cellYList[i];
			int cellIdx = cy * gridWidth + cx;

			size_t writePos;
#pragma omp atomic capture
			writePos = fillCursor[cellIdx]++;

			cellParticles[writePos] = activeIndices[i];
		}

#pragma omp parallel for schedule(dynamic)
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			size_t pIdx = activeIndices[i];
			auto& pi = pParticles[pIdx];

			rParticles[pIdx].neighbors = 0;

			int cx = cellXList[i];
			int cy = cellYList[i];

			for (int ny = -1; ny <= 1; ++ny) {
				for (int nx = -1; nx <= 1; ++nx) {
					int neighborCX = cx + nx;
					int neighborCY = cy + ny;

					if (neighborCX >= 0 && neighborCX < gridWidth &&
						neighborCY >= 0 && neighborCY < gridHeight) {

						int cellIdx = neighborCY * gridWidth + neighborCX;
						size_t start = cellStart[cellIdx];
						size_t end = cellStart[cellIdx + 1];

						for (size_t k = start; k < end; ++k) {
							size_t neighborIdx = cellParticles[k];

							if (neighborIdx == pIdx) continue;

							glm::vec2 d = pi.pos - pParticles[neighborIdx].pos;
							float distSq = d.x * d.x + d.y * d.y;

							if (distSq < densityRadiusSq) {
								rParticles[pIdx].neighbors++;
							}
						}
					}
				}
			}
		}

		uint32_t currentOffset = 0;
		for (size_t i = 0; i < pParticles.size(); ++i) {
			pParticles[i].neighborOffset = currentOffset;
			currentOffset += rParticles[i].neighbors;
		}

		if (globalNeighborList.size() < currentOffset) {
			globalNeighborList.resize(currentOffset);
		}

#pragma omp parallel for schedule(dynamic)
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			size_t pIdx = activeIndices[i];
			auto& pi = pParticles[pIdx];

			uint32_t currentNeighborIndex = 0;

			int cx = cellXList[i];
			int cy = cellYList[i];

			for (int ny = -1; ny <= 1; ++ny) {
				for (int nx = -1; nx <= 1; ++nx) {
					int neighborCX = cx + nx;
					int neighborCY = cy + ny;

					if (neighborCX >= 0 && neighborCX < gridWidth &&
						neighborCY >= 0 && neighborCY < gridHeight) {

						int cellIdx = neighborCY * gridWidth + neighborCX;
						size_t start = cellStart[cellIdx];
						size_t end = cellStart[cellIdx + 1];

						for (size_t k = start; k < end; ++k) {
							size_t neighborIdx = cellParticles[k];

							if (neighborIdx == pIdx) continue;

							glm::vec2 d = pi.pos - pParticles[neighborIdx].pos;
							float distSq = d.x * d.x + d.y * d.y;

							if (distSq < densityRadiusSq) {

								globalNeighborList[pi.neighborOffset + currentNeighborIndex] = pParticles[neighborIdx].id;
								currentNeighborIndex++;
							}
						}
					}
				}
			}
		}
	}
};

struct NeighborSearch3D {

	std::vector<uint32_t> globalNeighborList3D;

	float originalDensityRadius = 3.5f;
	float densityRadius = originalDensityRadius;
	float cellSize = 3.5f;

	std::vector<size_t> cellCounts;
	std::vector<size_t> cellStart;
	std::vector<size_t> cellParticles;

	std::vector<int> cellXList;
	std::vector<int> cellYList;
	std::vector<int> cellZList;

	std::vector<size_t> idToIndexTable;

	void calculateGridBounds(const std::vector<ParticlePhysics3D>& pParticles,
		const std::vector<size_t>& activeIndices,
		int& gridWidth, int& gridHeight, int& gridDepth,
		float& minX, float& minY, float& minZ)
	{
		float maxX = std::numeric_limits<float>::lowest();
		float maxY = std::numeric_limits<float>::lowest();
		float maxZ = std::numeric_limits<float>::lowest();

		minX = std::numeric_limits<float>::max();
		minY = std::numeric_limits<float>::max();
		minZ = std::numeric_limits<float>::max();

#pragma omp parallel for reduction(min:minX, minY, minZ) reduction(max:maxX, maxY, maxZ)
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			const auto& pos = pParticles[activeIndices[i]].pos;

			if (pos.x < minX) minX = pos.x;
			if (pos.y < minY) minY = pos.y;
			if (pos.z < minZ) minZ = pos.z;

			if (pos.x > maxX) maxX = pos.x;
			if (pos.y > maxY) maxY = pos.y;
			if (pos.z > maxZ) maxZ = pos.z;
		}

		minX -= cellSize; minY -= cellSize; minZ -= cellSize;
		maxX += cellSize; maxY += cellSize; maxZ += cellSize;

		gridWidth = std::max(1, static_cast<int>((maxX - minX) / cellSize) + 1);
		gridHeight = std::max(1, static_cast<int>((maxY - minY) / cellSize) + 1);
		gridDepth = std::max(1, static_cast<int>((maxZ - minZ) / cellSize) + 1);
	}

	void UpdateNeighbors(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {
		if (pParticles.empty()) return;

		float densityRadiusSq = densityRadius * densityRadius;

		std::vector<size_t> activeIndices;
		activeIndices.reserve(pParticles.size());
		for (size_t i = 0; i < pParticles.size(); i++) {
			if (!rParticles[i].isDarkMatter) {
				activeIndices.push_back(i);
			}
		}

		int gridWidth, gridHeight, gridDepth;
		float minX, minY, minZ;
		calculateGridBounds(pParticles, activeIndices, gridWidth, gridHeight, gridDepth, minX, minY, minZ);

		int numCells = gridWidth * gridHeight * gridDepth;

		if (cellCounts.size() < numCells) cellCounts.resize(numCells);
		std::fill(cellCounts.begin(), cellCounts.end(), 0);

		if (cellXList.size() < activeIndices.size()) cellXList.resize(activeIndices.size());
		if (cellYList.size() < activeIndices.size()) cellYList.resize(activeIndices.size());
		if (cellZList.size() < activeIndices.size()) cellZList.resize(activeIndices.size());

#pragma omp parallel for
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			size_t pIdx = activeIndices[i];
			const auto& pos = pParticles[pIdx].pos;

			int cx = static_cast<int>((pos.x - minX) / cellSize);
			int cy = static_cast<int>((pos.y - minY) / cellSize);
			int cz = static_cast<int>((pos.z - minZ) / cellSize);

			cx = std::max(0, std::min(cx, gridWidth - 1));
			cy = std::max(0, std::min(cy, gridHeight - 1));
			cz = std::max(0, std::min(cz, gridDepth - 1));

			cellXList[i] = cx;
			cellYList[i] = cy;
			cellZList[i] = cz;

			int cellIdx = cz * (gridWidth * gridHeight) + cy * gridWidth + cx;

#pragma omp atomic
			cellCounts[cellIdx]++;
		}

		if (cellStart.size() < numCells + 1) cellStart.resize(numCells + 1);
		cellStart[0] = 0;
		for (int i = 0; i < numCells; i++) {
			cellStart[i + 1] = cellStart[i] + cellCounts[i];
		}

		if (cellParticles.size() < activeIndices.size()) cellParticles.resize(activeIndices.size());

		std::vector<size_t> fillCursor = cellStart;

#pragma omp parallel for
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			int cx = cellXList[i];
			int cy = cellYList[i];
			int cz = cellZList[i];

			int cellIdx = cz * (gridWidth * gridHeight) + cy * gridWidth + cx;

			size_t writePos;
#pragma omp atomic capture
			writePos = fillCursor[cellIdx]++;

			cellParticles[writePos] = activeIndices[i];
		}

#pragma omp parallel for schedule(dynamic)
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			size_t pIdx = activeIndices[i];
			auto& pi = pParticles[pIdx];

			rParticles[pIdx].neighbors = 0;

			int cx = cellXList[i];
			int cy = cellYList[i];
			int cz = cellZList[i];

			for (int nz = -1; nz <= 1; ++nz) {

				for (int ny = -1; ny <= 1; ++ny) {

					for (int nx = -1; nx <= 1; ++nx) {

						int neighborCX = cx + nx;
						int neighborCY = cy + ny;
						int neighborCZ = cz + nz;

						if (neighborCX >= 0 && neighborCX < gridWidth &&
							neighborCY >= 0 && neighborCY < gridHeight &&
							neighborCZ >= 0 && neighborCZ < gridDepth) {

							int cellIdx = neighborCZ * (gridWidth * gridHeight) + neighborCY * gridWidth + neighborCX;

							size_t start = cellStart[cellIdx];
							size_t end = cellStart[cellIdx + 1];

							for (size_t k = start; k < end; ++k) {
								size_t neighborIdx = cellParticles[k];

								if (neighborIdx == pIdx) continue;

								glm::vec3 d = pi.pos - pParticles[neighborIdx].pos;
								float distSq = d.x * d.x + d.y * d.y + d.z * d.z;

								if (distSq < densityRadiusSq) {
									rParticles[pIdx].neighbors++;
								}
							}
						}
					}
				}
			}
		}

		uint32_t currentOffset = 0;
		for (size_t i = 0; i < pParticles.size(); ++i) {
			pParticles[i].neighborOffset = currentOffset;
			currentOffset += rParticles[i].neighbors;
		}

		if (globalNeighborList3D.size() < currentOffset) {
			globalNeighborList3D.resize(currentOffset);
		}

#pragma omp parallel for schedule(dynamic)
		for (int64_t i = 0; i < (int64_t)activeIndices.size(); ++i) {
			size_t pIdx = activeIndices[i];
			auto& pi = pParticles[pIdx];

			uint32_t currentNeighborIndex = 0;

			int cx = cellXList[i];
			int cy = cellYList[i];
			int cz = cellZList[i];

			for (int nz = -1; nz <= 1; ++nz) {
				for (int ny = -1; ny <= 1; ++ny) {
					for (int nx = -1; nx <= 1; ++nx) {

						int neighborCX = cx + nx;
						int neighborCY = cy + ny;
						int neighborCZ = cz + nz;

						if (neighborCX >= 0 && neighborCX < gridWidth &&
							neighborCY >= 0 && neighborCY < gridHeight &&
							neighborCZ >= 0 && neighborCZ < gridDepth) {

							int cellIdx = neighborCZ * (gridWidth * gridHeight) + neighborCY * gridWidth + neighborCX;
							size_t start = cellStart[cellIdx];
							size_t end = cellStart[cellIdx + 1];

							for (size_t k = start; k < end; ++k) {
								size_t neighborIdx = cellParticles[k];

								if (neighborIdx == pIdx) continue;

								glm::vec3 d = pi.pos - pParticles[neighborIdx].pos;
								float distSq = d.x * d.x + d.y * d.y + d.z * d.z;

								if (distSq < densityRadiusSq) {
									globalNeighborList3D[pi.neighborOffset + currentNeighborIndex] = pParticles[neighborIdx].id;
									currentNeighborIndex++;
								}
							}
						}
					}
				}
			}
		}
	}
};

struct NeighborSearchV2 {

	float searchRadius = 3.5f;
	float cellSize = 3.0f;
	float invCellSize = 1.0f / 3.0f;

	struct EntryArrays {
		std::vector<uint32_t> cellKeys;
		std::vector<uint32_t> particleIndices;

		std::vector<float> posX;
		std::vector<float> posY;

		std::vector<int> cellXs;
		std::vector<int> cellYs;

		size_t size;
	};

	const uint32_t hashTableSize = 16384;

	EntryArrays entries;

	std::vector<uint32_t> countBuffer;
	std::vector<uint32_t> offsetBuffer;
	std::vector<uint32_t> startIndices;

	NeighborSearchV2() {
		countBuffer.resize(hashTableSize + 1);
		offsetBuffer.resize(hashTableSize + 1);
		startIndices.resize(hashTableSize);
	}

	glm::ivec2 posToCellCoord(const glm::vec2& pos) const {
		return glm::ivec2((int)(pos.x * invCellSize), (int)(pos.y * invCellSize));
	}

	uint32_t hashCell(int cellX, int cellY) const {
		uint32_t h = ((uint32_t)cellX * 73856093) ^ ((uint32_t)cellY * 19349663);
		return h % hashTableSize;
	}

	void newGrid(const std::vector<ParticlePhysics>& pParticles) {
		const size_t n = pParticles.size();
		if (n == 0) return;

		if (entries.cellKeys.size() < n) {
			entries.cellKeys.resize(n);
			entries.particleIndices.resize(n);
			entries.posX.resize(n);
			entries.posY.resize(n);
			entries.cellXs.resize(n);
			entries.cellYs.resize(n);
		}
		entries.size = n;

		std::fill(countBuffer.begin(), countBuffer.end(), 0);
		std::fill(startIndices.begin(), startIndices.end(), UINT32_MAX);

		for (size_t i = 0; i < n; i++) {
			glm::ivec2 coord = posToCellCoord(pParticles[i].pos);
			uint32_t key = hashCell(coord.x, coord.y);

			entries.cellKeys[i] = key;
			entries.particleIndices[i] = i;
			entries.cellXs[i] = coord.x;
			entries.cellYs[i] = coord.y;
		}

		static std::vector<uint32_t> tempKeys;
		static std::vector<uint32_t> tempIndices;
		static std::vector<int> tempXs, tempYs;

		if (tempKeys.size() < n) {
			tempKeys.resize(n); tempIndices.resize(n);
			tempXs.resize(n); tempYs.resize(n);
		}

		for (size_t i = 0; i < n; i++) {
			glm::ivec2 coord = posToCellCoord(pParticles[i].pos);
			uint32_t key = hashCell(coord.x, coord.y);

			tempKeys[i] = key;
			tempIndices[i] = i;
			tempXs[i] = coord.x;
			tempYs[i] = coord.y;

			countBuffer[key]++;
		}

		uint32_t currentOffset = 0;
		for (size_t i = 0; i < hashTableSize; i++) {
			offsetBuffer[i] = currentOffset;
			startIndices[i] = currentOffset;
			currentOffset += countBuffer[i];

			if (countBuffer[i] == 0) startIndices[i] = UINT32_MAX;
		}

		for (size_t i = 0; i < n; i++) {
			uint32_t key = tempKeys[i];
			uint32_t destIndex = offsetBuffer[key]++;

			entries.cellKeys[destIndex] = key;
			entries.particleIndices[destIndex] = tempIndices[i];
			entries.cellXs[destIndex] = tempXs[i];
			entries.cellYs[destIndex] = tempYs[i];

			const auto& p = pParticles[tempIndices[i]];
			entries.posX[destIndex] = p.pos.x;
			entries.posY[destIndex] = p.pos.y;
		}
	}

	template <typename Func>
	void queryNeighbors(const glm::vec2& pos, Func&& callback) {
		glm::ivec2 cell = posToCellCoord(pos);
		int cellRadius = (int)ceil(searchRadius * invCellSize);
		float r2 = searchRadius * searchRadius;

		for (int dx = -cellRadius; dx <= cellRadius; dx++) {
			for (int dy = -cellRadius; dy <= cellRadius; dy++) {
				int neighborX = cell.x + dx;
				int neighborY = cell.y + dy;
				uint32_t key = hashCell(neighborX, neighborY);

				uint32_t start = startIndices[key];
				if (start == UINT32_MAX) continue;

				for (size_t i = start; i < entries.size; i++) {
					if (entries.cellKeys[i] != key) break;

					if (entries.cellXs[i] != neighborX || entries.cellYs[i] != neighborY) continue;

					float dx = entries.posX[i] - pos.x;
					float dy = entries.posY[i] - pos.y;
					float distSq = dx * dx + dy * dy;

					if (distSq <= r2) {
						callback(entries.particleIndices[i]);
					}
				}
			}
		}
	}

	void neighborAmount(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles) {

#pragma omp parallel for schedule(static)
		for (size_t i = 0; i < pParticles.size(); ++i) {

			if (rParticles[i].isPinned || rParticles[i].isBeingDrawn || rParticles[i].isDarkMatter) continue;

			rParticles[i].neighbors = 0;

			queryNeighbors(pParticles[i].pos, [&](uint32_t neighborIdx) {
				rParticles[i].neighbors++;
				});
		}
	}
};

struct NeighborSearchV2AVX2 {

	float searchRadius = 3.5f;
	float cellSize = 3.0f;
	const uint32_t hashTableSize = 16384;

	struct EntryArrays {
		std::vector<uint32_t> cellKeys;
		std::vector<uint32_t> particleIndices;
		std::vector<int> cellXs;
		std::vector<int> cellYs;
		size_t size = 0;
	};

	EntryArrays entries;

	std::vector<uint32_t> startIndices;
	std::vector<uint32_t> cellCounts;

	NeighborSearchV2AVX2() {
		startIndices.resize(hashTableSize);
		cellCounts.resize(hashTableSize);
	}

	std::pair<int, int> posToCellCoord(const glm::vec2& pos) {
		int cellX = static_cast<int>(std::floor(pos.x / cellSize));
		int cellY = static_cast<int>(std::floor(pos.y / cellSize));
		return { cellX, cellY };
	}

	uint32_t hashCell(int cellX, int cellY) {
		uint32_t h = (uint32_t)((cellX * 73856093) ^ (cellY * 19349663));
		return h % hashTableSize;
	}

	void newGridAVX2(const std::vector<ParticlePhysics>& pParticles) {
		const size_t n = pParticles.size();
		if (n == 0) return;

		if (entries.cellKeys.size() < n) {
			entries.cellKeys.resize(n);
			entries.particleIndices.resize(n);
			entries.cellXs.resize(n);
			entries.cellYs.resize(n);
		}
		entries.size = n;

		std::fill(std::execution::unseq, startIndices.begin(), startIndices.end(), UINT32_MAX);
		std::fill(std::execution::unseq, cellCounts.begin(), cellCounts.end(), 0);

#pragma omp parallel for
		for (long long i = 0; i < n; i++) {
			auto [cx, cy] = posToCellCoord(pParticles[i].pos);
			entries.cellKeys[i] = hashCell(cx, cy);
			entries.particleIndices[i] = (uint32_t)i;
			entries.cellXs[i] = cx;
			entries.cellYs[i] = cy;
		}

		std::vector<uint32_t> sortIndices(n);
		std::iota(sortIndices.begin(), sortIndices.end(), 0);

		std::sort(std::execution::par_unseq, sortIndices.begin(), sortIndices.end(),
			[&](uint32_t a, uint32_t b) {
				return entries.cellKeys[a] < entries.cellKeys[b];
			}
		);

		EntryArrays sorted;
		sorted.cellKeys.resize(n);
		sorted.particleIndices.resize(n);
		sorted.cellXs.resize(n);
		sorted.cellYs.resize(n);
		sorted.size = n;

#pragma omp parallel for
		for (long long i = 0; i < (n & ~7); i += 8) {
			__m256i idxVec = _mm256_loadu_si256((__m256i*) & sortIndices[i]);

			__m256i keys = _mm256_i32gather_epi32((const int*)entries.cellKeys.data(), idxVec, 4);
			__m256i pIds = _mm256_i32gather_epi32((const int*)entries.particleIndices.data(), idxVec, 4);
			__m256i xs = _mm256_i32gather_epi32((const int*)entries.cellXs.data(), idxVec, 4);
			__m256i ys = _mm256_i32gather_epi32((const int*)entries.cellYs.data(), idxVec, 4);

			_mm256_storeu_si256((__m256i*) & sorted.cellKeys[i], keys);
			_mm256_storeu_si256((__m256i*) & sorted.particleIndices[i], pIds);
			_mm256_storeu_si256((__m256i*) & sorted.cellXs[i], xs);
			_mm256_storeu_si256((__m256i*) & sorted.cellYs[i], ys);
		}

		for (size_t i = (n & ~7); i < n; i++) {
			uint32_t idx = sortIndices[i];
			sorted.cellKeys[i] = entries.cellKeys[idx];
			sorted.particleIndices[i] = entries.particleIndices[idx];
			sorted.cellXs[i] = entries.cellXs[idx];
			sorted.cellYs[i] = entries.cellYs[idx];
		}

		entries = std::move(sorted);

		if (n > 0) {
			uint32_t currentKey = entries.cellKeys[0];
			startIndices[currentKey] = 0;
			uint32_t count = 1;

			for (size_t i = 1; i < n; i++) {
				uint32_t key = entries.cellKeys[i];
				if (key != currentKey) {
					cellCounts[currentKey] = count;
					startIndices[key] = (uint32_t)i;
					currentKey = key;
					count = 1;
				}
				else {
					count++;
				}
			}
			cellCounts[currentKey] = count;
		}
	}

	void queryNeighborsAVX2(const glm::vec2& pos, std::vector<size_t>& neighbors) {
		neighbors.clear();
		auto [cellX, cellY] = posToCellCoord(pos);

		__m256i targetX = _mm256_set1_epi32(cellX);
		__m256i targetY = _mm256_set1_epi32(cellY);

		for (int dx = -1; dx <= 1; dx++) {
			for (int dy = -1; dy <= 1; dy++) {
				int neighborX = cellX + dx;
				int neighborY = cellY + dy;

				uint32_t cellKey = hashCell(neighborX, neighborY);
				uint32_t start = startIndices[cellKey];

				if (start == UINT32_MAX) continue;

				uint32_t count = cellCounts[cellKey];

				__m256i currentTargetX = _mm256_set1_epi32(neighborX);
				__m256i currentTargetY = _mm256_set1_epi32(neighborY);

				size_t i = 0;
				size_t currentIdx = start;

				for (; i + 8 <= count; i += 8, currentIdx += 8) {

					__m256i cellXs = _mm256_loadu_si256((__m256i*) & entries.cellXs[currentIdx]);
					__m256i cellYs = _mm256_loadu_si256((__m256i*) & entries.cellYs[currentIdx]);

					__m256i matchX = _mm256_cmpeq_epi32(cellXs, currentTargetX);
					__m256i matchY = _mm256_cmpeq_epi32(cellYs, currentTargetY);
					__m256i match = _mm256_and_si256(matchX, matchY);

					int mask = _mm256_movemask_ps(_mm256_castsi256_ps(match));

					if (mask != 0) {
						if (mask & 1) neighbors.push_back(entries.particleIndices[currentIdx + 0]);
						if (mask & 2) neighbors.push_back(entries.particleIndices[currentIdx + 1]);
						if (mask & 4) neighbors.push_back(entries.particleIndices[currentIdx + 2]);
						if (mask & 8) neighbors.push_back(entries.particleIndices[currentIdx + 3]);
						if (mask & 16) neighbors.push_back(entries.particleIndices[currentIdx + 4]);
						if (mask & 32) neighbors.push_back(entries.particleIndices[currentIdx + 5]);
						if (mask & 64) neighbors.push_back(entries.particleIndices[currentIdx + 6]);
						if (mask & 128) neighbors.push_back(entries.particleIndices[currentIdx + 7]);
					}
				}

				for (; i < count; i++, currentIdx++) {
					if (entries.cellXs[currentIdx] == neighborX && entries.cellYs[currentIdx] == neighborY) {
						neighbors.push_back(entries.particleIndices[currentIdx]);
					}
				}
			}
		}
	}

	void neighborAmount(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles) {

#pragma omp parallel 
		{
			std::vector<size_t> neighborIndices;
			neighborIndices.reserve(128);

#pragma omp for
			for (long long i = 0; i < pParticles.size(); ++i) {

				if (rParticles[i].isPinned || rParticles[i].isBeingDrawn || rParticles[i].isDarkMatter) continue;

				queryNeighborsAVX2(pParticles[i].pos, neighborIndices);

				rParticles[i].neighbors = (int)neighborIndices.size();
			}
		}
	}
};

struct NeighborSearch3DV2 {

	float searchRadius = 3.5f;
	float cellSize = 3.0f;
	float invCellSize = 1.0f / 3.0f;

	struct EntryArrays {
		std::vector<uint32_t> cellKeys;
		std::vector<uint32_t> particleIndices;

		std::vector<float> posX;
		std::vector<float> posY;
		std::vector<float> posZ;

		std::vector<int> cellXs;
		std::vector<int> cellYs;
		std::vector<int> cellZs;

		size_t size = 0;
	};

	const uint32_t hashTableSize = 32768;
	EntryArrays entries;

	std::vector<uint32_t> countBuffer;
	std::vector<uint32_t> offsetBuffer;

	std::vector<uint32_t> tempKeys;
	std::vector<uint32_t> tempIndices;
	std::vector<int> tempXs;
	std::vector<int> tempYs;
	std::vector<int> tempZs;

	NeighborSearch3DV2() {
		countBuffer.resize(hashTableSize + 1);
		offsetBuffer.resize(hashTableSize + 1);
	}

	glm::ivec3 posToCellCoord(const glm::vec3& pos) const {
		return glm::ivec3(
			(int)std::floor(pos.x * invCellSize),
			(int)std::floor(pos.y * invCellSize),
			(int)std::floor(pos.z * invCellSize)
		);
	}

	uint32_t hashCell(int cellX, int cellY, int cellZ) const {

		uint32_t h = ((uint32_t)cellX * 73856093)
			^ ((uint32_t)cellY * 19349663)
			^ ((uint32_t)cellZ * 83492791);

		return h % hashTableSize;
	}

	void newGrid(const std::vector<ParticlePhysics3D>& pParticles) {
		const size_t n = pParticles.size();
		if (n == 0) return;

		if (entries.cellKeys.size() < n) {
			entries.cellKeys.resize(n);
			entries.particleIndices.resize(n);
			entries.posX.resize(n);
			entries.posY.resize(n);
			entries.posZ.resize(n);
			entries.cellXs.resize(n);
			entries.cellYs.resize(n);
			entries.cellZs.resize(n);

			tempKeys.resize(n);
			tempIndices.resize(n);
			tempXs.resize(n);
			tempYs.resize(n);
			tempZs.resize(n);
		}
		entries.size = n;

		std::fill(countBuffer.begin(), countBuffer.end(), 0);

		for (size_t i = 0; i < n; i++) {
			glm::ivec3 coord = posToCellCoord(pParticles[i].pos);
			uint32_t key = hashCell(coord.x, coord.y, coord.z);

			tempKeys[i] = key;
			tempIndices[i] = (uint32_t)i;
			tempXs[i] = coord.x;
			tempYs[i] = coord.y;
			tempZs[i] = coord.z;

			countBuffer[key]++;
		}

		uint32_t currentOffset = 0;
		for (size_t i = 0; i < hashTableSize; i++) {
			offsetBuffer[i] = currentOffset;
			currentOffset += countBuffer[i];
		}

		for (size_t i = 0; i < n; i++) {
			uint32_t key = tempKeys[i];

			uint32_t destIndex = offsetBuffer[key]++;

			entries.cellKeys[destIndex] = key;
			entries.particleIndices[destIndex] = tempIndices[i];
			entries.cellXs[destIndex] = tempXs[i];
			entries.cellYs[destIndex] = tempYs[i];
			entries.cellZs[destIndex] = tempZs[i];

			const auto& p = pParticles[tempIndices[i]];
			entries.posX[destIndex] = p.pos.x;
			entries.posY[destIndex] = p.pos.y;
			entries.posZ[destIndex] = p.pos.z;
		}
	}

	template <typename Func>
	void queryNeighbors(const glm::vec3& pos, Func&& callback) {
		glm::ivec3 cell = posToCellCoord(pos);

		int searchGridDist = (int)std::ceil(searchRadius * invCellSize);
		float r2 = searchRadius * searchRadius;

		for (int dz = -searchGridDist; dz <= searchGridDist; dz++) {
			for (int dy = -searchGridDist; dy <= searchGridDist; dy++) {
				for (int dx = -searchGridDist; dx <= searchGridDist; dx++) {

					int neighborX = cell.x + dx;
					int neighborY = cell.y + dy;
					int neighborZ = cell.z + dz;

					uint32_t key = hashCell(neighborX, neighborY, neighborZ);

					uint32_t end = offsetBuffer[key];
					uint32_t count = countBuffer[key];
					uint32_t start = end - count;

					if (count == 0) continue;

					for (size_t i = start; i < end; i++) {
						if (entries.cellXs[i] != neighborX ||
							entries.cellYs[i] != neighborY ||
							entries.cellZs[i] != neighborZ) {
							continue;
						}

						float dX = entries.posX[i] - pos.x;
						float dY = entries.posY[i] - pos.y;
						float dZ = entries.posZ[i] - pos.z;

						float distSq = dX * dX + dY * dY + dZ * dZ;

						if (distSq <= r2) {
							callback(entries.particleIndices[i]);
						}
					}
				}
			}
		}
	}

	void neighborAmount(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {

#pragma omp parallel for schedule(static)
		for (long long i = 0; i < pParticles.size(); ++i) {

			if (rParticles[i].isPinned || rParticles[i].isBeingDrawn || rParticles[i].isDarkMatter) continue;

			rParticles[i].neighbors = 0;

			queryNeighbors(pParticles[i].pos, [&](uint32_t neighborIdx) {
				 if (neighborIdx == i) return; 

				rParticles[i].neighbors++;
				});
		}
	}
};

struct NeighborSearch3DV2AVX2 {

	float searchRadius = 3.5f;
	float cellSize = 3.0f;
	const uint32_t hashTableSize = 32768;

	struct EntryArrays {
		std::vector<uint32_t> cellKeys;
		std::vector<uint32_t> particleIndices;

		std::vector<int> cellXs;
		std::vector<int> cellYs;
		std::vector<int> cellZs;

		size_t size = 0;
	};

	EntryArrays entries;

	std::vector<uint32_t> startIndices;
	std::vector<uint32_t> cellCounts;

	NeighborSearch3DV2AVX2() {
		startIndices.resize(hashTableSize);
		cellCounts.resize(hashTableSize);
	}

	std::tuple<int, int, int> posToCellCoord(const glm::vec3& pos) {
		int cellX = static_cast<int>(std::floor(pos.x / cellSize));
		int cellY = static_cast<int>(std::floor(pos.y / cellSize));
		int cellZ = static_cast<int>(std::floor(pos.z / cellSize));
		return { cellX, cellY, cellZ };
	}

	uint32_t hashCell(int cellX, int cellY, int cellZ) {
		uint32_t h = (uint32_t)((cellX * 73856093) ^ (cellY * 19349663) ^ (cellZ * 83492791));
		return h % hashTableSize;
	}

	void newGridAVX2(const std::vector<ParticlePhysics3D>& pParticles) {
		const size_t n = pParticles.size();
		if (n == 0) return;

		if (entries.cellKeys.size() < n) {
			entries.cellKeys.resize(n);
			entries.particleIndices.resize(n);
			entries.cellXs.resize(n);
			entries.cellYs.resize(n);
			entries.cellZs.resize(n);
		}
		entries.size = n;

		std::fill(std::execution::unseq, startIndices.begin(), startIndices.end(), UINT32_MAX);
		std::fill(std::execution::unseq, cellCounts.begin(), cellCounts.end(), 0);

#pragma omp parallel for
		for (long long i = 0; i < n; i++) {
			auto [cx, cy, cz] = posToCellCoord(pParticles[i].pos);
			entries.cellKeys[i] = hashCell(cx, cy, cz);
			entries.particleIndices[i] = (uint32_t)i;
			entries.cellXs[i] = cx;
			entries.cellYs[i] = cy;
			entries.cellZs[i] = cz;
		}

		std::vector<uint32_t> sortIndices(n);
		std::iota(sortIndices.begin(), sortIndices.end(), 0);

		std::sort(std::execution::par_unseq, sortIndices.begin(), sortIndices.end(),
			[&](uint32_t a, uint32_t b) {
				return entries.cellKeys[a] < entries.cellKeys[b];
			}
		);

		EntryArrays sorted;
		sorted.cellKeys.resize(n);
		sorted.particleIndices.resize(n);
		sorted.cellXs.resize(n);
		sorted.cellYs.resize(n);
		sorted.cellZs.resize(n);
		sorted.size = n;

#pragma omp parallel for
		for (long long i = 0; i < (n & ~7); i += 8) {
			__m256i idxVec = _mm256_loadu_si256((__m256i*) & sortIndices[i]);

			__m256i keys = _mm256_i32gather_epi32((const int*)entries.cellKeys.data(), idxVec, 4);
			__m256i pIds = _mm256_i32gather_epi32((const int*)entries.particleIndices.data(), idxVec, 4);
			__m256i xs = _mm256_i32gather_epi32((const int*)entries.cellXs.data(), idxVec, 4);
			__m256i ys = _mm256_i32gather_epi32((const int*)entries.cellYs.data(), idxVec, 4);
			__m256i zs = _mm256_i32gather_epi32((const int*)entries.cellZs.data(), idxVec, 4);

			_mm256_storeu_si256((__m256i*) & sorted.cellKeys[i], keys);
			_mm256_storeu_si256((__m256i*) & sorted.particleIndices[i], pIds);
			_mm256_storeu_si256((__m256i*) & sorted.cellXs[i], xs);
			_mm256_storeu_si256((__m256i*) & sorted.cellYs[i], ys);
			_mm256_storeu_si256((__m256i*) & sorted.cellZs[i], zs);
		}

		for (size_t i = (n & ~7); i < n; i++) {
			uint32_t idx = sortIndices[i];
			sorted.cellKeys[i] = entries.cellKeys[idx];
			sorted.particleIndices[i] = entries.particleIndices[idx];
			sorted.cellXs[i] = entries.cellXs[idx];
			sorted.cellYs[i] = entries.cellYs[idx];
			sorted.cellZs[i] = entries.cellZs[idx];
		}

		entries = std::move(sorted);

		if (n > 0) {
			uint32_t currentKey = entries.cellKeys[0];
			startIndices[currentKey] = 0;
			uint32_t count = 1;

			for (size_t i = 1; i < n; i++) {
				uint32_t key = entries.cellKeys[i];
				if (key != currentKey) {
					cellCounts[currentKey] = count;
					startIndices[key] = (uint32_t)i;
					currentKey = key;
					count = 1;
				}
				else {
					count++;
				}
			}
			cellCounts[currentKey] = count;
		}
	}

	void queryNeighborsAVX2(const glm::vec3& pos, std::vector<size_t>& neighbors) {
		neighbors.clear();
		auto [cellX, cellY, cellZ] = posToCellCoord(pos);

		for (int dz = -1; dz <= 1; dz++) {
			for (int dy = -1; dy <= 1; dy++) {
				for (int dx = -1; dx <= 1; dx++) {

					int neighborX = cellX + dx;
					int neighborY = cellY + dy;
					int neighborZ = cellZ + dz;

					uint32_t cellKey = hashCell(neighborX, neighborY, neighborZ);
					uint32_t start = startIndices[cellKey];

					if (start == UINT32_MAX) continue;

					uint32_t count = cellCounts[cellKey];

					__m256i currentTargetX = _mm256_set1_epi32(neighborX);
					__m256i currentTargetY = _mm256_set1_epi32(neighborY);
					__m256i currentTargetZ = _mm256_set1_epi32(neighborZ);

					size_t i = 0;
					size_t currentIdx = start;

					for (; i + 8 <= count; i += 8, currentIdx += 8) {

						__m256i cellXs = _mm256_loadu_si256((__m256i*) & entries.cellXs[currentIdx]);
						__m256i cellYs = _mm256_loadu_si256((__m256i*) & entries.cellYs[currentIdx]);
						__m256i cellZs = _mm256_loadu_si256((__m256i*) & entries.cellZs[currentIdx]);

						__m256i matchX = _mm256_cmpeq_epi32(cellXs, currentTargetX);
						__m256i matchY = _mm256_cmpeq_epi32(cellYs, currentTargetY);
						__m256i matchZ = _mm256_cmpeq_epi32(cellZs, currentTargetZ);

						__m256i match = _mm256_and_si256(matchX, matchY);
						match = _mm256_and_si256(match, matchZ);

						int mask = _mm256_movemask_ps(_mm256_castsi256_ps(match));

						if (mask != 0) {
							if (mask & 1) neighbors.push_back(entries.particleIndices[currentIdx + 0]);
							if (mask & 2) neighbors.push_back(entries.particleIndices[currentIdx + 1]);
							if (mask & 4) neighbors.push_back(entries.particleIndices[currentIdx + 2]);
							if (mask & 8) neighbors.push_back(entries.particleIndices[currentIdx + 3]);
							if (mask & 16) neighbors.push_back(entries.particleIndices[currentIdx + 4]);
							if (mask & 32) neighbors.push_back(entries.particleIndices[currentIdx + 5]);
							if (mask & 64) neighbors.push_back(entries.particleIndices[currentIdx + 6]);
							if (mask & 128) neighbors.push_back(entries.particleIndices[currentIdx + 7]);
						}
					}

					for (; i < count; i++, currentIdx++) {
						if (entries.cellXs[currentIdx] == neighborX &&
							entries.cellYs[currentIdx] == neighborY &&
							entries.cellZs[currentIdx] == neighborZ) {

							neighbors.push_back(entries.particleIndices[currentIdx]);
						}
					}
				}
			}
		}
	}

	void neighborAmount(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles) {

#pragma omp parallel 
		{
			std::vector<size_t> neighborIndices;
			neighborIndices.reserve(128);

#pragma omp for
			for (long long i = 0; i < pParticles.size(); ++i) {

				if (rParticles[i].isPinned || rParticles[i].isBeingDrawn || rParticles[i].isDarkMatter) continue;

				queryNeighborsAVX2(pParticles[i].pos, neighborIndices);

				rParticles[i].neighbors = (int)neighborIndices.size();
			}
		}
	}
};

================================================
FILE: GalaxyEngine/include/Particles/particle.h
================================================
#pragma once

extern uint32_t globalId;

struct ParticlePhysics {

	glm::vec2 pos;
	glm::vec2 vel;
	glm::vec2 acc;
	glm::vec2 predPos;
	glm::vec2 prevVel;
	glm::vec2 predVel;
	glm::vec2 pressF;

	uint64_t mortonKey;
	float mass;
	float press;
	float pressTmp;
	float dens;
	float predDens;
	float sphMass;
	float restDens;
	float stiff;
	float visc;
	float cohesion;
	float temp;
	float ke;
	float prevKe;
	uint32_t id;
	uint32_t neighborOffset;

	bool isHotPoint;
	bool hasSolidified;

	// Default constructor
	ParticlePhysics()
		: 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),
		mass(8500000000.0f), press(0.0f), pressTmp(0.0f), dens(0.0f), predDens(0.0f), sphMass(1.0f),
		restDens(0.0f), stiff(0.0f), visc(0.0f), cohesion(0.0f),
		temp(0.0f), ke(0.0f), prevKe(0.0f), id(globalId++), neighborOffset(0),
		isHotPoint(false), hasSolidified(false)
	{
	}

	// Parameterized constructor
	ParticlePhysics(glm::vec2 pos, glm::vec2 vel, float mass, float restDens, float stiff, float visc, float cohesion) {
		this->pos = pos;
		this->vel = vel;
		this->acc = { 0.0f, 0.0f };
		this->predPos = { 0.0f, 0.0f };
		this->prevVel = { 0.0f, 0.0f };
		this->predVel = { 0.0f, 0.0f };
		this->pressF = { 0.0f, 0.0f };
		this->mass = mass;
		this->press = 0.0f;
		this->pressTmp = 0.0f;
		this->dens = 0.0f;
		this->predDens = 0.0f;
		this->sphMass = mass / 8500000000.0f;
		this->restDens = restDens;
		this->stiff = stiff;
		this->visc = visc;
		this->cohesion = cohesion;
		this->temp = 288.0f;
		this->ke = 0.0f;
		this->prevKe = 0.0f;
		this->mortonKey = 0;
		this->id = globalId++;
		this->neighborOffset = 0;
		this->isHotPoint = false;
		this->hasSolidified = false;
	}
};

struct ParticleRendering {

	Color color;
	Color pColor;
	Color sColor;
	Color sphColor;

	float size;
	float previousSize;
	float totalRadius;
	float lifeSpan;
	float turbulence;
	uint32_t sphLabel;

	int neighbors;
	int spawnCorrectIter;

	bool uniqueColor;
	bool isSolid;
	bool canBeSubdivided;
	bool canBeResized;
	bool isDarkMatter;
	bool isSPH;
	bool isSelected;
	bool isGrabbed;
	bool isPinned;
	bool isBeingDrawn;

	// Default constructor
	ParticleRendering()
		: color{ 255,255,255,255 }, pColor{ 255,255,255,255 }, sColor{ 255, 255, 255, 255 }, sphColor{ 128,128,128,128 },
		size(1.0f), previousSize(1.0f), totalRadius(0.0f), lifeSpan(-1.0f), turbulence(0.0f), sphLabel(0),
		neighbors(0), spawnCorrectIter(100000000),
		uniqueColor(false), isSolid(false), canBeSubdivided(false),
		canBeResized(false), isDarkMatter(false), isSPH(false),
		isSelected(false), isGrabbed(false), isPinned(false), isBeingDrawn(true)
	{
	}

	// Parameterized constructor
	ParticleRendering(Color color, float size, bool uniqueColor, bool isSelected,
		bool isSolid, bool canBeSubdivided, bool canBeResized, bool isDarkMatter, bool isSPH, float lifeSpan, uint32_t sphLabel) {
		this->color = color;
		this->pColor = { 255, 255, 255, 255 };
		this->sColor = { 255, 255, 255, 255 };
		this->sphColor = { 128, 128, 128, 128 };
		this->size = size;
		this->previousSize = size;
		this->totalRadius = 0.0f;
		this->lifeSpan = lifeSpan;
		this->turbulence = 0.0f;
		this->sphLabel = sphLabel;
		this->neighbors = 0;
		this->spawnCorrectIter = 100000000;
		this->uniqueColor = uniqueColor;
		this->isSolid = isSolid;
		this->canBeSubdivided = canBeSubdivided;
		this->canBeResized = canBeResized;
		this->isDarkMatter = isDarkMatter;
		this->isSPH = isSPH;
		this->isSelected = isSelected;
		this->isGrabbed = false;
		this->isPinned = false;
		this->isBeingDrawn = false;
	}
};

struct ParticlePhysics3D {

	glm::vec3 pos;
	glm::vec3 vel;
	glm::vec3 acc;
	glm::vec3 predPos;
	glm::vec3 prevVel;
	glm::vec3 predVel;
	glm::vec3 pressF;

	uint64_t mortonKey;
	float mass;
	float press;
	float pressTmp;
	float dens;
	float predDens;
	float sphMass;
	float restDens;
	float stiff;
	float visc;
	float cohesion;
	float temp;
	float ke;
	float prevKe;
	uint32_t id;
	uint32_t neighborOffset;

	bool isHotPoint;
	bool hasSolidified;

	// Default constructor
	ParticlePhysics3D()
		: 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 }, 
		prevVel{ 0.0f, 0.0f, 0.0f }, predVel{ 0.0f, 0.0f, 0.0f }, pressF{ 0.0f,0.0f, 0.0f }, mortonKey(0),
		mass(8500000000.0f), press(0.0f), pressTmp(0.0f), dens(0.0f), predDens(0.0f), sphMass(1.0f),
		restDens(0.0f), stiff(0.0f), visc(0.0f), cohesion(0.0f),
		temp(0.0f), ke(0.0f), prevKe(0.0f), id(globalId++), neighborOffset(0),
		isHotPoint(false), hasSolidified(false)
	{
	}

	// Parameterized constructor
	ParticlePhysics3D(glm::vec3 pos, glm::vec3 vel, float mass, float restDens, float stiff, float visc, float cohesion) {
		this->pos = pos;
		this->vel = vel;
		this->acc = { 0.0f, 0.0f, 0.0f };
		this->predPos = { 0.0f, 0.0f, 0.0f };
		this->prevVel = { 0.0f, 0.0f, 0.0f };
		this->predVel = { 0.0f, 0.0f, 0.0f };
		this->pressF = { 0.0f, 0.0f, 0.0f };
		this->mass = mass;
		this->press = 0.0f;
		this->pressTmp = 0.0f;
		this->dens = 0.0f;
		this->predDens = 0.0f;
		this->sphMass = mass / 8500000000.0f;
		this->restDens = restDens;
		this->stiff = stiff;
		this->visc = visc;
		this->cohesion = cohesion;
		this->temp = 288.0f;
		this->ke = 0.0f;
		this->prevKe = 0.0f;
		this->mortonKey = 0;
		this->id = globalId++;
		this->neighborOffset = 0;
		this->isHotPoint = false;
		this->hasSolidified = false;
	}
};

struct ParticleRendering3D {

	Color color;
	Color pColor;
	Color sColor;
	Color sphColor;

	float size;
	float previousSize;
	float totalRadius;
	float lifeSpan;
	float turbulence;
	uint32_t sphLabel;

	int neighbors;
	int spawnCorrectIter;

	bool uniqueColor;
	bool isSolid;
	bool canBeSubdivided;
	bool canBeResized;
	bool isDarkMatter;
	bool isSPH;
	bool isSelected;
	bool isGrabbed;
	bool isPinned;
	bool isBeingDrawn;

	// Default constructor
	ParticleRendering3D()
		: color{ 255,255,255,255 }, pColor{ 255,255,255,255 }, sColor{ 255, 255, 255, 255 }, sphColor{ 128,128,128,128 },
		size(1.0f), previousSize(1.0f), totalRadius(0.0f), lifeSpan(-1.0f), turbulence(0.0f), sphLabel(0),
		neighbors(0), spawnCorrectIter(100000000),
		uniqueColor(false), isSolid(false), canBeSubdivided(false),
		canBeResized(false), isDarkMatter(false), isSPH(false),
		isSelected(false), isGrabbed(false), isPinned(false), isBeingDrawn(true)
	{
	}

	// Parameterized constructor
	ParticleRendering3D(Color color, float size, bool uniqueColor, bool isSelected,
		bool isSolid, bool canBeSubdivided, bool canBeResized, bool isDarkMatter, bool isSPH, float lifeSpan, uint32_t sphLabel) {
		this->color = color;
		this->pColor = { 255, 255, 255, 255 };
		this->sColor = { 255, 255, 255, 255 };
		this->sphColor = { 128, 128, 128, 128 };
		this->size = size;
		this->previousSize = size;
		this->totalRadius = 0.0f;
		this->lifeSpan = lifeSpan;
		this->turbulence = 0.0f;
		this->sphLabel = sphLabel;
		this->neighbors = 0;
		this->spawnCorrectIter = 100000000;
		this->uniqueColor = uniqueColor;
		this->isSolid = isSolid;
		this->canBeSubdivided = canBeSubdivided;
		this->canBeResized = canBeResized;
		this->isDarkMatter = isDarkMatter;
		this->isSPH = isSPH;
		this->isSelected = isSelected;
		this->isGrabbed = false;
		this->isPinned = false;
		this->isBeingDrawn = false;
	}
};

================================================
FILE: GalaxyEngine/include/Particles/particleColorVisuals.h
================================================
#pragma once

#include "Particles/particle.h"
#include "Physics/materialsSPH.h"

struct ColorVisuals {

	bool solidColor = false;
	bool densityColor = true;
	bool velocityColor = false;
	bool shockwaveColor = false;
	bool turbulenceColor = false;
	bool forceColor = false;
	bool pressureColor = false;
	bool temperatureColor = false;
	bool gasTempColor = false;
	bool SPHColor = false;

	bool showDarkMatterEnabled = false;

	bool selectedColor = true;

	int blendMode = 1;

	Color pColor = { 0, 40, 68, 100 };
	Color sColor = { 155, 80, 40, 75 };

	float hue = 180.0f;
	float saturation = 0.8f;
	float value = 0.5f;

	int maxNeighbors = 200;

	float maxColorAcc = 40.0f;
	float minColorAcc = 0.0f;

	float maxColorTurbulence = 40.0f;
	float minColorTurbulence = 0.0f;
	float turbulenceFadeRate = 0.015f;
	float turbulenceContrast = 1.1f;
	bool turbulenceCustomCol = false;

	float ShockwaveMaxAcc = 18.0f;
	float ShockwaveMinAcc = 0.0f;

	float maxVel = 100.0f;
	float minVel = 0.0f;

	float maxPress = 1000.0f;
	float minPress = 0.0f;

	float minTemp = 274.0f; // Min temp is set to roughly 1 degrees Celsius

	float tempColorMinTemp = 1.0f;
	float tempColorMaxTemp = 1000.0f;

	glm::vec2 prevVel = { 0.0f, 0.0f };

	void blackbodyToRGB(float kelvin, unsigned char& r, unsigned char& g, unsigned char& b) {

		float temperature = kelvin / 100.0f;

		float rC, gC, bC;

		if (temperature <= 66.0f) {
			rC = 255.0f;
		}
		else {
			rC = 329.698727446f * powf(temperature - 60.0f, -0.1332047592f);
		}

		if (temperature <= 66.0f) {
			gC = 99.4708025861f * logf(temperature) - 161.1195681661f;
		}
		else {
			gC = 288.1221695283f * powf(temperature - 60.0f, -0.0755148492f);
		}

		if (temperature >= 66.0f) {
			bC = 255.0f;
		}
		else if (temperature <= 19.0f) {
			bC = 0.0f;
		}
		else {
			bC = 138.5177312231f * logf(temperature - 10.0f) - 305.0447927307f;
		}

		r = static_cast<unsigned char>(std::clamp(rC, 0.0f, 255.0f));
		g = static_cast<unsigned char>(std::clamp(gC, 0.0f, 255.0f));
		b = static_cast<unsigned char>(std::clamp(bC, 0.0f, 255.0f));
	}


	Color blendColors(Color base, Color emission, float glowFactor) {
		Color result;
		result.r = base.r * (1.0f - glowFactor) + emission.r * glowFactor;
		result.g = base.g * (1.0f - glowFactor) + emission.g * glowFactor;
		result.b = base.b * (1.0f - glowFactor) + emission.b * glowFactor;
		result.a = 255;
		return result;
	}

	float getGlowFactor(int temperature) {
		const int minTemp = 600;
		const int maxTemp = 6000;

		float linear = std::clamp((float)(temperature - minTemp) / (maxTemp - minTemp), 0.0f, 1.0f);
		return powf(linear, 0.3f);
	}

	void particlesColorVisuals(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, std::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, bool& isTempEnabled, float& timeFactor, bool& is3DMode) {

		if (solidColor && !is3DMode) {
			for (size_t i = 0; i < pParticles.size(); i++) {
				if (!rParticles[i].uniqueColor) {
					rParticles[i].pColor = pColor;
					rParticles[i].color = rParticles[i].pColor;
				}
				else {
					rParticles[i].color = rParticles[i].pColor;
				}
			}
			blendMode = 1;
		}
		else if (solidColor && is3DMode) {
			for (size_t i = 0; i < pParticles3D.size(); i++) {
				if (!rParticles3D[i].uniqueColor) {
					rParticles3D[i].pColor = pColor;
					rParticles3D[i].color = rParticles3D[i].pColor;
				}
				else {
					rParticles3D[i].color = rParticles3D[i].pColor;
				}
			}
			blendMode = 1;
		}

		if (densityColor && !is3DMode) {

			const float invMaxNeighbors = 1.0f / maxNeighbors;

#pragma omp parallel for schedule(dynamic)
			for (int64_t i = 0; i < pParticles.size(); i++) {
				if (rParticles[i].isDarkMatter) {
					continue;
				}

				if (!rParticles[i].uniqueColor) {
					rParticles[i].pColor = pColor;
					rParticles[i].sColor = sColor;
				}

				Color lowDensityColor = rParticles[i].pColor;

				Color highDensityColor = rParticles[i].sColor;

				float normalDensity = std::min(static_cast<float>(rParticles[i].neighbors) * invMaxNeighbors, 1.0f);
				rParticles[i].color = ColorLerp(lowDensityColor, highDensityColor, normalDensity);
			}

			blendMode = 1;
		}
		else if (densityColor && is3DMode) {

			const float invMaxNeighbors = 1.0f / maxNeighbors;

#pragma omp parallel for schedule(dynamic)
			for (int64_t i = 0; i < pParticles3D.size(); i++) {
				if (rParticles3D[i].isDarkMatter) {
					continue;
				}

				if (!rParticles3D[i].uniqueColor) {
					rParticles3D[i].pColor = pColor;
					rParticles3D[i].sColor = sColor;
				}

				Color lowDensityColor = rParticles3D[i].pColor;

				Color highDensityColor = rParticles3D[i].sColor;

				float normalDensity = std::min(static_cast<float>(rParticles3D[i].neighbors) * invMaxNeighbors, 1.0f);
				rParticles3D[i].color = ColorLerp(lowDensityColor, highDensityColor, normalDensity);
			}

			blendMode = 1;
		}

		if (velocityColor && !is3DMode) {
#pragma omp parallel for schedule(dynamic)
			for (int64_t i = 0; i < pParticles.size(); i++) {

				float particleVelSq = pParticles[i].vel.x * pParticles[i].vel.x +
					pParticles[i].vel.y * pParticles[i].vel.y;

				float clampedVel = std::clamp(particleVelSq, minVel, maxVel);
				float normalizedVel = clampedVel / maxVel;

				hue = (1.0f - normalizedVel) * 240.0f;
				saturation = 1.0f;
				value = 1.0f;

				rParticles[i].color = ColorFromHSV(hue, saturation, value);
			}

			blendMode = 0;
		}
		else if (velocityColor && is3DMode) {
#pragma omp parallel for schedule(dynamic)
			for (int64_t i = 0; i < pParticles3D.size(); i++) {

				float particleVelSq = pParticles3D[i].vel.x * pParticles3D[i].vel.x +
					pParticles3D[i].vel.y * pParticles3D[i].vel.y + pParticles3D[i].vel.z * pParticles3D[i].vel.z;

				float clampedVel = std::clamp(particleVelSq, minVel, maxVel);
				float normalizedVel = clampedVel / maxVel;

				hue = (1.0f - normalizedVel) * 240.0f;
				saturation = 1.0f;
				value = 1.0f;

				rParticles3D[i].color = ColorFromHSV(hue, saturation, value);
			}

			blendMode = 0;
		}

		if (forceColor && !is3DMode) {
			for (size_t i = 0; i < pParticles.size(); i++) {

				if (rParticles[i].isDarkMatter) {
					continue;
				}

				float particleAccSq = pParticles[i].acc.x * pParticles[i].acc.x +
					pParticles[i].acc.y * pParticles[i].acc.y;

				float clampedAcc = std::clamp(sqrt(particleAccSq), minColorAcc, maxColorAcc);
				float normalizedAcc = clampedAcc / maxColorAcc;

				if (!rParticles[i].uniqueColor) {
					rParticles[i].pColor = pColor;
					rParticles[i].sColor = sColor;
				}

				Color lowAccColor = rParticles[i].pColor;

				Color highAccColor = rParticles[i].sColor;

				rParticles[i].color = ColorLerp(lowAccColor, highAccColor, normalizedAcc);

			}
			blendMode = 1;
		}
		else if (forceColor && is3DMode) {
			for (size_t i = 0; i < pParticles3D.size(); i++) {

				if (rParticles3D[i].isDarkMatter) {
					continue;
				}

				float particleAccSq = pParticles3D[i].acc.x * pParticles3D[i].acc.x +
					pParticles3D[i].acc.y * pParticles3D[i].acc.y + pParticles3D[i].acc.z * pParticles3D[i].acc.z;

				float clampedAcc = std::clamp(sqrt(particleAccSq), minColorAcc, maxColorAcc);
				float normalizedAcc = clampedAcc / maxColorAcc;

				if (!rParticles3D[i].uniqueColor) {
					rParticles3D[i].pColor = pColor;
					rParticles3D[i].sColor = sColor;
				}

				Color lowAccColor = rParticles3D[i].pColor;

				Color highAccColor = rParticles3D[i].sColor;

				rParticles3D[i].color = ColorLerp(lowAccColor, highAccColor, normalizedAcc);

			}
			blendMode = 1;
		}

		if (shockwaveColor && !is3DMode) {
			for (size_t i = 0; i < pParticles.size(); i++) {

				glm::vec2 shockwave = pParticles[i].acc;

				float shockMag = std::sqrt(shockwave.x * shockwave.x + shockwave.y * shockwave.y);

				float clampedShock = std::clamp(shockMag, ShockwaveMinAcc, ShockwaveMaxAcc);
				float normalizedShock = clampedShock / ShockwaveMaxAcc;

				hue = (1.0f - normalizedShock) * 240.0f;
				saturation = 1.0f;
				value = 1.0f;

				rParticles[i].color = ColorFromHSV(hue, saturation, value);
			}
			blendMode = 0;
		}
		else if (shockwaveColor && is3DMode) {
			for (size_t i = 0; i < pParticles3D.size(); i++) {

				glm::vec3 shockwave = pParticles3D[i].acc;

				float shockMag = std::sqrt(shockwave.x * shockwave.x + shockwave.y * shockwave.y + shockwave.z * shockwave.z);

				shockMag = std::log(std::max(shockMag, 0.0001f));

				float clampedShock = std::clamp(shockMag, ShockwaveMinAcc, ShockwaveMaxAcc);
				float normalizedShock = clampedShock / ShockwaveMaxAcc;

				hue = (1.0f - normalizedShock) * 240.0f;
				saturation = 1.0f;
				value = 1.0f;

				rParticles3D[i].color = ColorFromHSV(hue, saturation, value);
			}
			blendMode = 0;
		}

		if (turbulenceColor && !is3DMode) {
#pragma omp parallel for schedule(dynamic)
			for (size_t i = 0; i < pParticles.size(); i++) {

				if (rParticles[i].isDarkMatter) {
					continue;
				}

				if (timeFactor != 0.0f) {
					float pTotalVel = sqrt(pParticles[i].vel.x * pParticles[i].vel.x + pParticles[i].vel.y * pParticles[i].vel.y);
					float pTotalPrevVel = sqrt(pParticles[i].prevVel.x * pParticles[i].prevVel.x + pParticles[i].prevVel.y * pParticles[i].prevVel.y);

					rParticles[i].turbulence += std::abs(pTotalVel - pTotalPrevVel);

					glm::vec2 velDiff = pParticles[i].vel - pParticles[i].prevVel;
					rParticles[i].turbulence += glm::length(velDiff);

					rParticles[i].turbulence *= 1.0f - turbulenceFadeRate;

					rParticles[i].turbulence = std::max(0.0f, rParticles[i].turbulence);
				}

				float clampedTurbulence = std::clamp(rParticles[i].turbulence, minColorTurbulence, maxColorTurbulence);
				float normalizedTurbulence = pow(clampedTurbulence / maxColorTurbulence, turbulenceContrast);

				if (turbulenceCustomCol) {
					if (!rParticles[i].uniqueColor) {
						rParticles[i].pColor = pColor;
						rParticles[i].sColor = sColor;
					}

					Color lowTurbulenceColor = rParticles[i].pColor;

					Color highTurbulenceColor = rParticles[i].sColor;

					rParticles[i].color = ColorLerp(lowTurbulenceColor, highTurbulenceColor, normalizedTurbulence);
				}
				else {
					hue = (1.0f - normalizedTurbulence) * 240.0f;
					saturation = 1.0f;
					value = 1.0f;

					rParticles[i].color = ColorFromHSV(hue, saturation, value);
				}

			}
			blendMode = 0;
		}
		else if (turbulenceColor && is3DMode) {
#pragma omp parallel for schedule(dynamic)
			for (size_t i = 0; i < pParticles3D.size(); i++) {

				if (rParticles3D[i].isDarkMatter) {
					continue;
				}

				if (timeFactor != 0.0f) {
					float 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);
					float 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);

					rParticles3D[i].turbulence += std::abs(pTotalVel - pTotalPrevVel);

					glm::vec2 velDiff = pParticles3D[i].vel - pParticles3D[i].prevVel;
					rParticles3D[i].turbulence += glm::length(velDiff);

					rParticles3D[i].turbulence *= 1.0f - turbulenceFadeRate;

					rParticles3D[i].turbulence = std::max(0.0f, rParticles3D[i].turbulence);
				}

				float clampedTurbulence = std::clamp(rParticles3D[i].turbulence, minColorTurbulence, maxColorTurbulence);
				float normalizedTurbulence = pow(clampedTurbulence / maxColorTurbulence, turbulenceContrast);

				if (turbulenceCustomCol) {
					if (!rParticles3D[i].uniqueColor) {
						rParticles3D[i].pColor = pColor;
						rParticles3D[i].sColor = sColor;
					}

					Color lowTurbulenceColor = rParticles3D[i].pColor;

					Color highTurbulenceColor = rParticles3D[i].sColor;

					rParticles3D[i].color = ColorLerp(lowTurbulenceColor, highTurbulenceColor, normalizedTurbulence);
				}
				else {
					hue = (1.0f - normalizedTurbulence) * 240.0f;
					saturation = 1.0f;
					value = 1.0f;

					rParticles3D[i].color = ColorFromHSV(hue, saturation, value);
				}

			}
			blendMode = 0;
		}

		if (pressureColor && !is3DMode) {
#pragma omp parallel for schedule(dynamic)
			for (int64_t i = 0; i < pParticles.size(); i++) {

				ParticlePhysics& p = pParticles[i];

				float clampedPress = std::clamp(p.press, minPress, maxPress);
				float normalizedPress = clampedPress / maxPress;

				hue = (1.0f - normalizedPress) * 240.0f;
				saturation = 1.0f;
				value = 1.0f;

				rParticles[i].color = ColorFromHSV(hue, saturation, value);
			}
			blendMode = 0;
		}
		else if (pressureColor && is3DMode) {
#pragma omp parallel for schedule(dynamic)
			for (int64_t i = 0; i < pParticles3D.size(); i++) {

				ParticlePhysics3D& p = pParticles3D[i];

				float clampedPress = std::clamp(p.press, minPress, maxPress);
				float normalizedPress = clampedPress / maxPress;

				hue = (1.0f - normalizedPress) * 240.0f;
				saturation = 1.0f;
				value = 1.0f;

				rParticles3D[i].color = ColorFromHSV(hue, saturation, value);
			}
			blendMode = 0;
		}

		if (temperatureColor && !is3DMode) {
#pragma omp parallel for schedule(dynamic)
			for (int64_t i = 0; i < pParticles.size(); i++) {

				ParticlePhysics& p = pParticles[i];

				float clampedTemp = std::clamp(p.temp, tempColorMinTemp, tempColorMaxTemp);
				float normalizedTemp = clampedTemp / tempColorMaxTemp;

				hue = (1.0f - normalizedTemp) * 240.0f;
				saturation = 1.0f;
				value = 1.0f;

				rParticles[i].color = ColorFromHSV(hue, saturation, value);
			}
			blendMode = 0;
		}
		else if (temperatureColor && is3DMode) {
#pragma omp parallel for schedule(dynamic)
			for (int64_t i = 0; i < pParticles3D.size(); i++) {

				ParticlePhysics3D& p = pParticles3D[i];

				float clampedTemp = std::clamp(p.temp, tempColorMinTemp, tempColorMaxTemp);
				float normalizedTemp = clampedTemp / tempColorMaxTemp;

				hue = (1.0f - normalizedTemp) * 240.0f;
				saturation = 1.0f;
				value = 1.0f;

				rParticles3D[i].color = ColorFromHSV(hue, saturation, value);
			}
			blendMode = 0;
		}

		if (gasTempColor && !is3DMode) {
			for (size_t i = 0; i < rParticles.size(); i++) {

				if (!rParticles[i].uniqueColor) {
					rParticles[i].pColor = pColor;
					rParticles[i].sColor = sColor;
				}

				float normalizedTemp = (pParticles[i].temp - tempColorMinTemp) / (tempColorMaxTemp - tempColorMinTemp);
				normalizedTemp = std::clamp(normalizedTemp, 0.0f, 1.0f);

				Color lowTempColor = rParticles[i].pColor;

				Color highTempColor = rParticles[i].sColor;

				rParticles[i].color = ColorLerp(lowTempColor, highTempColor, normalizedTemp);
			}
			blendMode = 1;
		}
		else if (gasTempColor && is3DMode) {
			for (size_t i = 0; i < rParticles3D.size(); i++) {

				if (!rParticles3D[i].uniqueColor) {
					rParticles3D[i].pColor = pColor;
					rParticles3D[i].sColor = sColor;
				}

				float normalizedTemp = (pParticles3D[i].temp - tempColorMinTemp) / (tempColorMaxTemp - tempColorMinTemp);
				normalizedTemp = std::clamp(normalizedTemp, 0.0f, 1.0f);

				Color lowTempColor = rParticles3D[i].pColor;

				Color highTempColor = rParticles3D[i].sColor;

				rParticles3D[i].color = ColorLerp(lowTempColor, highTempColor, normalizedTemp);
			}
			blendMode = 1;
		}

		if (SPHColor && !is3DMode) {
			for (size_t i = 0; i < rParticles.size(); i++) {
				if (!rParticles[i].uniqueColor) {

					auto it = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel);
					if (it != SPHMaterials::idToMaterial.end()) {
						SPHMaterial* pMat = it->second;

						if (pMat->sphLabel != "water") {

							float glowFactor = getGlowFactor(pParticles[i].temp);

							Color matColor = rParticles[i].sphColor;

							Color glowColor;
							blackbodyToRGB(pParticles[i].temp, glowColor.r, glowColor.g, glowColor.b);

							Color finalColor = blendColors(matColor, glowColor, glowFactor);

							rParticles[i].color = finalColor;
						}
						else {
							float normalizedTemp = (pParticles[i].temp - minTemp) / (pMat->hotPoint - minTemp);
							normalizedTemp = std::clamp(normalizedTemp, 0.0f, 1.0f);

							Color lowTempColor = rParticles[i].sphColor;

							Color highTempColor = { 255, 113, 33, 255 };

							highTempColor = it->second->hotColor;


							if (pParticles[i].temp < pMat->coldPoint) {

								rParticles[i].color = pMat->coldColor;
							}
							else {
								rParticles[i].color = ColorLerp(lowTempColor, highTempColor, normalizedTemp);
							}
						}
					}
				}
				else {
					rParticles[i].color = rParticles[i].pColor;
				}
			}

			blendMode = 0;
		}
		else if (SPHColor && is3DMode) {
			for (size_t i = 0; i < rParticles3D.size(); i++) {
				if (!rParticles3D[i].uniqueColor) {

					auto it = SPHMaterials::idToMaterial.find(rParticles3D[i].sphLabel);
					if (it != SPHMaterials::idToMaterial.end()) {
						SPHMaterial* pMat = it->second;

						if (pMat->sphLabel != "water") {

							float glowFactor = getGlowFactor(pParticles3D[i].temp);

							Color matColor = rParticles3D[i].sphColor;

							Color glowColor;
							blackbodyToRGB(pParticles3D[i].temp, glowColor.r, glowColor.g, glowColor.b);

							Color finalColor = blendColors(matColor, glowColor, glowFactor);

							rParticles3D[i].color = finalColor;
						}
						else {
							float normalizedTemp = (pParticles3D[i].temp - minTemp) / (pMat->hotPoint - minTemp);
							normalizedTemp = std::clamp(normalizedTemp, 0.0f, 1.0f);

							Color lowTempColor = rParticles3D[i].sphColor;

							Color highTempColor = { 255, 113, 33, 255 };

							highTempColor = it->second->hotColor;


							if (pParticles3D[i].temp < pMat->coldPoint) {

								rParticles3D[i].color = pMat->coldColor;
							}
							else {
								rParticles3D[i].color = ColorLerp(lowTempColor, highTempColor, normalizedTemp);
							}
						}
					}
				}
				else {
					rParticles3D[i].color = rParticles3D[i].pColor;
				}
			}

			blendMode = 0;
		}


		if (selectedColor && !is3DMode) {
			for (size_t i = 0; i < rParticles.size(); i++) {
				if (rParticles[i].isSelected) {
					rParticles[i].color = { 255, 20,20, 255 };
				}
			}
		}
		else if (selectedColor && is3DMode) {
			for (size_t i = 0; i < rParticles3D.size(); i++) {
				if (rParticles3D[i].isSelected) {
					rParticles3D[i].color = { 255, 20,20, 255 };
				}
			}
		}

		if (!is3DMode) {
			if (showDarkMatterEnabled) {
				for (size_t i = 0; i < rParticles.size(); i++) {
					if (rParticles[i].isDarkMatter) {
						rParticles[i].color = { 128, 128, 128, 170 };
					}
				}
			}
			else {
				for (size_t i = 0; i < rParticles.size(); i++) {
					if (rParticles[i].isDarkMatter) {
						rParticles[i].color = { 0, 0, 0, 0 };
					}
				}
			}
		}
		else {
			if (showDarkMatterEnabled) {
				for (size_t i = 0; i < rParticles3D.size(); i++) {
					if (rParticles3D[i].isDarkMatter) {
						rParticles3D[i].color = { 128, 128, 128, 170 };
					}
				}
			}
			else {
				for (size_t i = 0; i < rParticles3D.size(); i++) {
					if (rParticles3D[i].isDarkMatter) {
						rParticles3D[i].color = { 0, 0, 0, 0 };
					}
				}
			}
		}
	}
};

================================================
FILE: GalaxyEngine/include/Particles/particleDeletion.h
================================================
#pragma once

#include "IO/io.h"

#include "Particles/particle.h"

struct ParticleDeletion {

	bool deleteSelection = false;

	bool deleteNonImportant = false;

	void deleteSelected(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles,
		std::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, bool& is3DMode) {

		if (!is3DMode) {
			if (deleteSelection || IO::shortcutPress(KEY_DELETE)) {
				for (size_t i = pParticles.size(); i-- > 0;) {
					if (rParticles[i].isSelected) {

						std::swap(pParticles[i], pParticles.back());
						std::swap(rParticles[i], rParticles.back());

						pParticles.pop_back();
						rParticles.pop_back();
					}
				}
				deleteSelection = false;
			}
		}
		else {
			if (deleteSelection || IO::shortcutPress(KEY_DELETE)) {
				for (size_t i = pParticles3D.size(); i-- > 0;) {
					if (rParticles3D[i].isSelected) {

						std::swap(pParticles3D[i], pParticles3D.back());
						std::swap(rParticles3D[i], rParticles3D.back());

						pParticles3D.pop_back();
						rParticles3D.pop_back();
					}
				}
				deleteSelection = false;
			}
		}
	}

	void deleteStrays(std::vector<ParticlePhysics>& pParticles,
		std::vector<ParticleRendering>& rParticles, bool& isSPHEnabled,
		std::vector<ParticlePhysics3D>& pParticles3D, std::vector<ParticleRendering3D>& rParticles3D, bool& is3DMode) {

		if (deleteNonImportant) {
			if (isSPHEnabled) {
				if (!is3DMode) {
					collisionRMultiplier = 6.0f;
				}
				else {
					collisionRMultiplier = 12.0f;
				}
			}
			else {
				if (!is3DMode) {
					collisionRMultiplier = 1.0f;
				}
				else {
					collisionRMultiplier = 2.0f;
				}
			}

			if (!is3DMode) {
				std::vector<int> neighborCounts(pParticles.size(), 0);

				for (size_t i = 0; i < pParticles.size(); i++) {
					const glm::vec2& pos1 = pParticles[i].pos;
					for (size_t j = i + 1; j < pParticles.size(); j++) {
						const glm::vec2& pos2 = pParticles[j].pos;
						if (std::abs(pos2.x - pos1.x) > xBreakThreshold * collisionRMultiplier)
							break;
						float dx = pos1.x - pos2.x;
						float dy = pos1.y - pos2.y;
						if (dx * dx + dy * dy < squaredDistanceThreshold * collisionRMultiplier) {
							neighborCounts[i]++;
							neighborCounts[j]++;
						}
					}
				}

				std::vector<ParticlePhysics> newPParticles;
				std::vector<ParticleRendering> newRParticles;
				newPParticles.reserve(pParticles.size());
				newRParticles.reserve(rParticles.size());
				for (size_t i = 0; i < pParticles.size(); i++) {

					if (!(neighborCounts[i] < 5 && !rParticles[i].isSolid)) {
						newPParticles.push_back(std::move(pParticles[i]));
						newRParticles.push_back(std::move(rParticles[i]));
					}
				}
				pParticles.swap(newPParticles);
				rParticles.swap(newRParticles);

				deleteNonImportant = false;
			}
			else {
				std::vector<int> neighborCounts(pParticles3D.size(), 0);

				for (size_t i = 0; i < pParticles3D.size(); i++) {
					const glm::vec3& pos1 = pParticles3D[i].pos;
					for (size_t j = i + 1; j < pParticles3D.size(); j++) {
						const glm::vec3& pos2 = pParticles3D[j].pos;
						if (std::abs(pos2.x - pos1.x) > xBreakThreshold * collisionRMultiplier)
							break;
						glm::vec3 d = pos2 - pos1;

						float dSq = glm::dot(d, d);
						if (dSq < squaredDistanceThreshold * collisionRMultiplier) {
							neighborCounts[i]++;
							neighborCounts[j]++;
						}
					}
				}

				std::vector<ParticlePhysics3D> newPParticles;
				std::vector<ParticleRendering3D> newRParticles;
				newPParticles.reserve(pParticles3D.size());
				newRParticles.reserve(rParticles3D.size());
				for (size_t i = 0; i < pParticles3D.size(); i++) {

					if (!(neighborCounts[i] < 5 && !rParticles3D[i].isSolid)) {
						newPParticles.push_back(std::move(pParticles3D[i]));
						newRParticles.push_back(std::move(rParticles3D[i]));
					}
				}
				pParticles3D.swap(newPParticles);
				rParticles3D.swap(newRParticles);

				deleteNonImportant = false;
			}
		}
	}

private:
	const float distanceThreshold = 10.0f;
	const float squaredDistanceThreshold = distanceThreshold * distanceThreshold;
	const float xBreakThreshold = 2.4f;
	float collisionRMultiplier = 1.0f;
};

================================================
FILE: GalaxyEngine/include/Particles/particleSelection.h
================================================
#pragma once

#include "Particles/particle.h"
#include "Particles/particleTrails.h"

#include "UX/camera.h"

struct UpdateVariables;
struct UpdateParameters;


class ParticleSelection {
public:

	bool invertParticleSelection = false;
	bool deselectParticles = false;

	bool selectManyClusters = false;
	
	ParticleSelection();

	void clusterSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger);

	void particleSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger);

	void manyClustersSelection(UpdateVariables& myVar, UpdateParameters& myParam);

	void boxSelection(UpdateParameters& myParam, bool& is3DMode);

	void invertSelection(std::vector<ParticleRendering>& rParticles);

	void deselection(std::vector<ParticleRendering>& rParticles);

	void selectedParticlesStoring(UpdateParameters& myParam);

private:
	float selectionThresholdSq = 100.0f;

	glm::vec2 boxInitialPos = { 0.0f, 0.0f };

	bool isBoxSelecting = false;
	bool isBoxDeselecting = false;
};

class ParticleSelection3D {
public:

	bool invertParticleSelection = false;
	bool deselectParticles = false;

	bool selectManyClusters3D = false;

	ParticleSelection3D();

	void clusterSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger);

	void particleSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger);

	void manyClustersSelection(UpdateVariables& myVar, UpdateParameters& myParam);

	void boxSelection(UpdateParameters& myParam);

	void invertSelection(std::vector<ParticleRendering3D>& rParticles);

	void deselection(std::vector<ParticleRendering3D>& rParticles);

	void selectedParticlesStoring(UpdateParameters& myParam);

private:
	float selectionThresholdAngle = -0.998f;

	glm::vec2 boxInitialPos = { 0.0f, 0.0f };

	bool isBoxSelecting = false;
	bool isBoxDeselecting = false;
};

================================================
FILE: GalaxyEngine/include/Particles/particleSpaceship.h
================================================
#pragma once

#include "Particles/particle.h"
#include "Physics/materialsSPH.h"
#include "IO/io.h"

class ParticleSpaceship {
public:

	int gasMultiplier = 1;

	bool isShipEnabled = false;

	void spaceshipLogic(std::vector<ParticlePhysics>& pParticles, std::vector<ParticleRendering>& rParticles, bool& isShipGasEnabled) {

		float lifeDt = GetFrameTime();

		for (size_t i = 0; i < pParticles.size(); ++i) {
			if (rParticles[i].lifeSpan > 0.0f) {
				rParticles[i].lifeSpan -= lifeDt;
			}
		}

		for (size_t i = 0; i < pParticles.size();) {
			if (rParticles[i].lifeSpan <= 0.0f && rParticles[i].lifeSpan != -1.0f) {
				pParticles.erase(pParticles.begin() + i);
				rParticles.erase(rParticles.begin() + i);
			}
			else {
				++i;
			}
		}

		if (!isShipEnabled) {
			return;
		};

		for (size_t i = 0; i < pParticles.size(); i++) {
			if (!rParticles[i].isSelected) {
				continue;
			}
			else {

				// I set it to rock's mass
				pParticles[i].mass = 8500000000.0f * rock.massMult;
				pParticles[i].sphMass = rock.massMult;
			}


			if (IO::shortcutDown(KEY_UP)) {
				pParticles[i].acc.y -= acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics(
							glm::vec2{ pParticles[i].pos.x + (4.0f * normalRand - 2.0f), pParticles[i].pos.y + 3.3f },
							glm::vec2{ pParticles[i].vel.x + (4.0f * normalRand - 2.0f), pParticles[i].vel.y + 10.0f },
							8500000000.0f * water.massMult * 0.1f,

							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering(
							water.color,
							rParticles[i].size * 0.7f,
							false,
							false,
							false,
							true,
							true,
							false,
							true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}
			if (IO::shortcutDown(KEY_RIGHT)) {
				pParticles[i].acc.x += acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics(
							glm::vec2{ pParticles[i].pos.x - 3.3f, pParticles[i].pos.y + (4.0f * normalRand - 2.0f) },
							glm::vec2{ pParticles[i].vel.x - 10.0f, pParticles[i].vel.y + (4.0f * normalRand - 2.0f) },
							8500000000.0f * water.massMult * 0.1f,

							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering(
							water.color,
							rParticles[i].size * 0.7f,
							false,
							false,
							false,
							true,
							true,
							false,
							true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}
			if (IO::shortcutDown(KEY_DOWN)) {
				pParticles[i].acc.y += acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics(
							glm::vec2{ pParticles[i].pos.x + (4.0f * normalRand - 2.0f), pParticles[i].pos.y - 3.3f },
							glm::vec2{ pParticles[i].vel.x + (4.0f * normalRand - 2.0f), pParticles[i].vel.y - 10.0f },
							8500000000.0f * water.massMult * 0.1f,

							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering(
							water.color,
							rParticles[i].size * 0.7f,
							false,
							false,
							false,
							true,
							true,
							false,
							true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}
			if (IO::shortcutDown(KEY_LEFT)) {
				pParticles[i].acc.x -= acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics(
							glm::vec2{ pParticles[i].pos.x + 3.3f, pParticles[i].pos.y + (4.0f * normalRand - 2.0f) },
							glm::vec2{ pParticles[i].vel.x + 10.0f, pParticles[i].vel.y + (4.0f * normalRand - 2.0f) },
							8500000000.0f * water.massMult * 0.1f,

							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering(
							water.color,
							rParticles[i].size * 0.7f,
							false,
							false,
							false,
							true,
							true,
							false,
							true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}
		}
	}

	float arrowLength = 10.0f;

	void spaceshipLogic3D(std::vector<ParticlePhysics3D>& pParticles, std::vector<ParticleRendering3D>& rParticles, bool& isShipGasEnabled, Camera3D& cam3D) {

		float lifeDt = GetFrameTime();

		glm::vec3 camPos(cam3D.position.x, cam3D.position.y, cam3D.position.z);
		glm::vec3 camTarget(cam3D.target.x, cam3D.target.y, cam3D.target.z);
		glm::vec3 camUpGlobal(0.0f, 1.0f, 0.0f);

		glm::vec3 forwardDir = glm::normalize(camTarget - camPos);

		glm::vec3 rightDir = glm::normalize(glm::cross(forwardDir, camUpGlobal));

		glm::vec3 upDir = glm::normalize(glm::cross(rightDir, forwardDir));

		for (size_t i = 0; i < pParticles.size(); ++i) {
			if (rParticles[i].lifeSpan > 0.0f) {
				rParticles[i].lifeSpan -= lifeDt;
			}
		}

		for (size_t i = 0; i < pParticles.size();) {
			if (rParticles[i].lifeSpan <= 0.0f && rParticles[i].lifeSpan != -1.0f) {
				pParticles.erase(pParticles.begin() + i);
				rParticles.erase(rParticles.begin() + i);
			}
			else {
				++i;
			}
		}

		if (pParticles.empty()) {
			isShipGasEnabled = false;
			return;
		}

		if (!isShipEnabled) {
			return;
		}

		for (size_t i = 0; i < pParticles.size(); i++) {
			if (!rParticles[i].isSelected) {
				continue;
			}

			// Set mass to rock's mass
			pParticles[i].mass = 8500000000.0f * rock.massMult;
			pParticles[i].sphMass = rock.massMult;

			DrawLine3D({ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z },
				{ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z - arrowLength - arrowLength * 0.5f },
				RED);

			// UP ARROW - Front (forward in Z)
			if (IO::shortcutDown(KEY_UP)) {

				DrawCube({ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z - arrowLength * 0.5f },
					rParticles[i].totalRadius * 0.5f,
					rParticles[i].totalRadius * 0.5f,
					arrowLength,
					RED);
				DrawCylinderEx({ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z - arrowLength },
					{ pParticles[i].pos.x,pParticles[i].pos.y, pParticles[i].pos.z - arrowLength - arrowLength * 0.5f },
					rParticles[i].totalRadius * 2.0f,
					rParticles[i].totalRadius * 0.1f,
					12,
					RED);

				pParticles[i].acc.z -= acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
						float normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics3D(
							glm::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 },
							glm::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 },
							8500000000.0f * water.massMult * 0.1f,
							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering3D(
							water.color,
							rParticles[i].size * 0.7f,
							false, false, false, true, true, false, true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}

			// RIGHT ARROW - Right
			if (IO::shortcutDown(KEY_RIGHT)) {

				DrawCube({ pParticles[i].pos.x + arrowLength * 0.5f, pParticles[i].pos.y, pParticles[i].pos.z },
					arrowLength,
					rParticles[i].totalRadius * 0.5f,
					rParticles[i].totalRadius * 0.5f,
					GREEN);
				DrawCylinderEx({ pParticles[i].pos.x + arrowLength, pParticles[i].pos.y, pParticles[i].pos.z },
					{ pParticles[i].pos.x + arrowLength + arrowLength * 0.5f, pParticles[i].pos.y, pParticles[i].pos.z },
					rParticles[i].totalRadius * 2.0f,
					rParticles[i].totalRadius * 0.1f,
					12,
					GREEN);

				pParticles[i].acc.x += acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
						float normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics3D(
							glm::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) },
							glm::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) },
							8500000000.0f * water.massMult * 0.1f,
							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering3D(
							water.color,
							rParticles[i].size * 0.7f,
							false, false, false, true, true, false, true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}

			// DOWN ARROW - Back
			if (IO::shortcutDown(KEY_DOWN)) {

				DrawCube({ pParticles[i].pos.x, pParticles[i].pos.y, pParticles[i].pos.z + arrowLength * 0.5f },
					rParticles[i].totalRadius * 0.5f,
					rParticles[i].totalRadius * 0.5f,
					arrowLength,
					RED);
				DrawCylinderEx({ pParticles[i].pos.x, pParticles[i].pos.y, pParticles[i].pos.z + arrowLength },
					{ pParticles[i].pos.x, pParticles[i].pos.y, pParticles[i].pos.z + arrowLength + arrowLength * 0.5f },
					rParticles[i].totalRadius * 2.0f,
					rParticles[i].totalRadius * 0.1f,
					12,
					RED);

				pParticles[i].acc.z += acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
						float normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics3D(
							glm::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 },
							glm::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 },
							8500000000.0f * water.massMult * 0.1f,
							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering3D(
							water.color,
							rParticles[i].size * 0.7f,
							false, false, false, true, true, false, true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}

			// LEFT ARROW - Left
			if (IO::shortcutDown(KEY_LEFT)) {

				DrawCube({ pParticles[i].pos.x - arrowLength * 0.5f, pParticles[i].pos.y, pParticles[i].pos.z },
					arrowLength,
					rParticles[i].totalRadius * 0.5f,
					rParticles[i].totalRadius * 0.5f,
					GREEN);
				DrawCylinderEx({ pParticles[i].pos.x - arrowLength, pParticles[i].pos.y, pParticles[i].pos.z },
					{ pParticles[i].pos.x - arrowLength - arrowLength * 0.5f, pParticles[i].pos.y, pParticles[i].pos.z },
					rParticles[i].totalRadius * 2.0f,
					rParticles[i].totalRadius * 0.1f,
					12,
					GREEN);

				pParticles[i].acc.x -= acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
						float normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics3D(
							glm::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) },
							glm::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) },
							8500000000.0f * water.massMult * 0.1f,
							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering3D(
							water.color,
							rParticles[i].size * 0.7f,
							false, false, false, true, true, false, true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}

			// SHIFT - Up
			if ((IO::shortcutDown(KEY_LEFT_SHIFT) || IO::shortcutDown(KEY_RIGHT_SHIFT))
				&& (!IO::mouseDown(0) || !IO::mouseDown(1) || !IO::mouseDown(2))) {

				DrawCube({ pParticles[i].pos.x, pParticles[i].pos.y + arrowLength * 0.5f, pParticles[i].pos.z },
					rParticles[i].totalRadius * 0.5f,
					arrowLength,
					rParticles[i].totalRadius * 0.5f,
					BLUE);
				DrawCylinderEx({ pParticles[i].pos.x, pParticles[i].pos.y + arrowLength, pParticles[i].pos.z },
					{ pParticles[i].pos.x, pParticles[i].pos.y + arrowLength + arrowLength * 0.5f, pParticles[i].pos.z },
					rParticles[i].totalRadius * 2.0f,
					rParticles[i].totalRadius * 0.1f,
					12,
					BLUE);

				pParticles[i].acc.y += acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
						float normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics3D(
							glm::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) },
							glm::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) },
							8500000000.0f * water.massMult * 0.1f,
							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering3D(
							water.color,
							rParticles[i].size * 0.7f,
							false, false, false, true, true, false, true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}

			// CTRL - Down
			if ((IO::shortcutDown(KEY_LEFT_CONTROL) || IO::shortcutDown(KEY_RIGHT_CONTROL))
				&& (!IO::mouseDown(0) || !IO::mouseDown(1) || !IO::mouseDown(2))) {

				DrawCube({ pParticles[i].pos.x, pParticles[i].pos.y - arrowLength * 0.5f, pParticles[i].pos.z },
					rParticles[i].totalRadius * 0.5f,
					arrowLength,
					rParticles[i].totalRadius * 0.5f,
					BLUE);
				DrawCylinderEx({ pParticles[i].pos.x, pParticles[i].pos.y - arrowLength, pParticles[i].pos.z },
					{ pParticles[i].pos.x, pParticles[i].pos.y - arrowLength - arrowLength * 0.5f, pParticles[i].pos.z },
					rParticles[i].totalRadius * 2.0f,
					rParticles[i].totalRadius * 0.1f,
					12,
					BLUE);

				pParticles[i].acc.y -= acceleration;

				if (isShipGasEnabled) {
					for (int g = 0; g < gasMultiplier; g++) {
						float normalRand1 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
						float normalRand2 = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);

						pParticles.emplace_back(ParticlePhysics3D(
							glm::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) },
							glm::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) },
							8500000000.0f * water.massMult * 0.1f,
							water.restDens,
							water.stiff,
							water.visc,
							water.cohesion
						));

						rParticles.emplace_back(ParticleRendering3D(
							water.color,
							rParticles[i].size * 0.7f,
							false, false, false, true, true, false, true,
							3.0f,
							water.id
						));

						rParticles.back().sphColor = water.color;
						pParticles.back().temp = 440.0f;
					}
				}
			}
		}
	}

	float acceleration = 4.0f;

private:

	SPHWater water;
	SPHRock rock;

	int selectionIdx = 0;
};

================================================
FILE: GalaxyEngine/include/Particles/particleSubdivision.h
================================================
#pragma once

#include "Particles/particle.h"

struct UpdateVariables;
struct UpdateParameters;

struct ParticleSubdivision {
	int particlesThreshold = 80000;

	bool subdivideAll = false;
	bool subdivideSelected = false;

	void subdivideParticles(UpdateVariables& myVar, UpdateParameters& myParam);
	void subdivideParticles3D(UpdateVariables& myVar, UpdateParameters& myParam);

private:
	bool confirmState = false;
	bool quitState = false;

	std::string warningText = "Subdividing further might slow down the program a lot";

	float textSize = 25.0f;
	float textSpacing = 6.0f;

	glm::vec2 buttonSize = { 200.0f, 50.0f };
};

================================================
FILE: GalaxyEngine/include/Particles/particleTrails.h
================================================
#pragma once

#include "Particles/particle.h"

struct UpdateVariables;
struct UpdateParameters;


class ParticleTrails {
public:
	float size = 5;
	float trailThickness = 0.5f;
	bool whiteTrails = false;
	struct Segment {
		glm::vec2 start;
		glm::vec2 end;
		glm::vec2 offset;
		glm::vec2 prevOffset;
		Color color;
	};
	struct Segment3D {
		glm::vec3 start;
		glm::vec3 end;
		glm::vec3 offset;
		glm::vec3 prevOffset;
		Color color;
	};
	std::vector<Segment> segments;
	std::vector<Segment3D> segments3D;
	glm::vec2 selectedParticlesAveragePos = { 0.0f, 0.0f };
	glm::vec2 selectedParticlesAveragePrevPos = { 0.0f, 0.0f };
	glm::vec3 selectedParticlesAveragePos3D = { 0.0f, 0.0f, 0.0f };
	glm::vec3 selectedParticlesAveragePrevPos3D = { 0.0f, 0.0f, 0.0f };
	void trailLogic(UpdateVariables& myVar, UpdateParameters& myParam);
	void drawTrail(std::vector<ParticleRendering>& rParticles, Texture2D& particleBlur);
	void trailLogic3D(UpdateVariables& myVar, UpdateParameters& myParam);
	void drawTrail3D(std::vector<ParticleRendering3D>& rParticles3D, Texture2D& particleBlur, Camera3D& cam3D);
private:
	bool wasLocalTrailsEnabled = false;
};

================================================
FILE: GalaxyEngine/include/Particles/particlesSpawning.h
================================================
#pragma once

#include "Particles/particle.h"

#include "Physics/slingshot.h"

#include "Physics/constraint.h"

#include "UI/brush.h"

#include "UX/camera.h"

struct UpdateVariables;
struct UpdateParameters;

struct Physics;
struct Physics3D;
struct Quadtree;

class ParticlesSpawning {
public:

	const int correctionSubsteps = 32;
	bool particlesIterating = false;

	bool massMultiplierEnabled = true;

	float outerRadius = 200.0f;
	float scaleLength = 90.0f;

	float outerRadiusDM = 2000.0f;
	float radiusCoreDM = 3.5f;

	void particlesInitialConditions(Physics& physics, UpdateVariables& myVar, UpdateParameters& myParam);

	void predictTrajectory(const std::vector<ParticlePhysics>& actualParticles, 
		SceneCamera& myCamera, Physics physics, UpdateVariables& myVar, Slingshot& slingshot);

	void drawGalaxyDisplay(UpdateParameters& myParam);

private:

	float heavyParticleInitMass = 300000000000000.0f;
};

class ParticlesSpawning3D {
public:

	const int correctionSubsteps = 24;
	bool particlesIterating = false;

	bool massMultiplierEnabled = true;

	float diskAxisX = 90.0f;
	float diskAxisY = 0.0f;
	float diskAxisZ = 0.0f;

	float outerRadius = 120.0f;
	float radiusCore = 2.5f;

	float diskThickness = 0.5f;
	float bulgeThickness = 3.0f;
	float bulgeSize = 1200.0f;

	float outerRadiusDM = 2000.0f;
	float radiusCoreDM = 3.5f;

	void particlesInitialConditions(Physics3D& physics3D, UpdateVariables& myVar, UpdateParameters& myParam);

	void predictTrajectory(const std::vector<ParticlePhysics3D>& pParticles,
		SceneCamera3D& myCamera, Physics3D physics3D,
		UpdateVariables& myVar, UpdateParameters& myParam, Slingshot3D& slingshot);

	void drawGalaxyDisplay(UpdateParameters& myParam);

private:

	float heavyParticleInitMass = 300000000000000.0f;
};

================================================
FILE: GalaxyEngine/include/Physics/SPH.h
================================================
#pragma once

#include "Particles/particle.h"

#include "parameters.h"
#include "Particles/QueryNeighbors.h"

struct UpdateVariables;
struct UpdateVariables;

struct GridCell {
	std::vector<size_t> particleIndices;
};

class SPH {
public:
	float radiusMultiplier = 3.0f;
	const float boundDamping = -0.1f;

	float densTolerance = 0.08f;

	int maxIter = 1; // I keep only 1 iteration when I don't use the density error condition
	int iter = 0;

	float rhoError = 0.0f;

	float cellSize;

	SPH() : cellSize(radiusMultiplier) {}

	void computeViscCohesionForcesWithCache(
		std::vector<ParticlePhysics>& pParticles,
		std::vector<ParticleRendering>& rParticles,
		std::vector<glm::vec2>& forces,
		const std::vector<std::vector<size_t>>& neighborCache,
		size_t N);

	float smoothingKernel(float dst, float radiusMultiplier) {
		if (dst >= radiusMultiplier) return 0.0f;

		float volume = (PI * pow(radiusMultiplier, 4.0f)) / 6.0f;
		return (radiusMultiplier - dst) * (radiusMultiplier - dst) / volume;
	}

	float spikyKernelDerivative(float dst, float radiusMultiplier) {
		if (dst >= radiusMultiplier) return 0.0f;

		float scale = -45.0f / (PI * pow(radiusMultiplier, 6.0f));
		return scale * pow(radiusMultiplier - dst, 2.0f);
	}

	float smoothingKernelLaplacian(float dst, float radiusMultiplier) {
		if (dst >= radiusMultiplier) return 0.0f;

		float scale = 45.0f / (PI * pow(radiusMultiplier, 6.0f));
		return scale;
	}

	float smoothingKernelCohesion(float r, float h) {
		if (r >= h) return 0.0f;

		float q = r / h;
		return (1.0f - q) * (0.5f - q) * (0.5f - q) * 30.0f / (PI * h * h);
	}

	// Currently unused
	float computeDelta(const std::vector<glm::vec2>& kernelGradients, float dt, float mass, float restDensity) {
		float beta = (dt * dt * mass * mass) / (restDensity * restDensity);

		glm::vec2 sumGradW = { 0.0f, 0.0f };
		float sumGradW_dot = 0.0f;

		for (const glm::vec2& gradW : kernelGradients) {
			sumGradW.x += gradW.x;
			sumGradW.y += gradW.y;

			sumGradW_dot += gradW.x * gradW.x + gradW.y * gradW.y;
		}

		float sumDot = sumGradW.x * sumGradW.x + sumGradW.y * sumGradW.y;

		float delta = -1.0f / (beta * (-sumDot - sumGradW_dot));

		return delta;
	}

	// New Grid Search

	struct EntryArrays {
		std::vector<uint32_t> cellKeys;
		std::vector<uint32_t> particleIndices;
		std::vector<int> cellXs;
		std::vector<int> cellYs;
		size_t size;
	};

	const uint32_t hashTableSize = 16384;
	EntryArrays entries;
	std::vector<uint32_t> startIndices;

	const char* neighborSearchCompute = R"(
#version 430

layout(local_size_x = 256) in;

layout(std430, binding = 10) buffer ParticlePositions {
    vec2 pos[];
};

layout(std430, binding = 11) buffer CellKeys {
    uint cellKeys[];
};

layout(std430, binding = 12) buffer ParticleIndices {
    uint particleIndices[];
};

layout(std430, binding = 13) buffer CellXs {
    int cellXs[];
};

layout(std430, binding = 14) buffer CellYs {
    int cellYs[];
};

uniform float cellSize;
uniform uint hashTableSize;
uniform uint numParticles;

uvec2 posToCellCoord(vec2 pos) {
    int cellX = int(floor(pos.x / cellSize));
    int cellY = int(floor(pos.y / cellSize));
    return uvec2(cellX, cellY);
}

uint hashCell(int cellX, int cellY) {
    uint h = uint(cellX * 73856093) ^ uint(cellY * 19349663);
    return h % hashTableSize;
}

void main() {
    uint i = gl_GlobalInvocationID.x;
    if (i >= numParticles) return;

    uvec2 cellCoord = posToCellCoord(pos[i]);
    uint cellKey = hashCell(int(cellCoord.x), int(cellCoord.y));

    cellKeys[i] = cellKey;
    particleIndices[i] = i;
    cellXs[i] = int(cellCoord.x);
    cellYs[i] = int(cellCoord.y);
}
)";

	GLuint ssboPPos, ssboCellKeys, ssboParticleIndices, ssboCellXs, ssboCellYs;

	size_t mb = 512;

	size_t reserveSize = (1024 * 1024 * mb) / sizeof(glm::vec2);

	GLuint neighborSearchProgram;

	void neighborSearchKernel() {

		glGenBuffers(1, &ssboPPos);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPPos);
		glBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(glm::vec2), nullptr, GL_STREAM_COPY);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, ssboPPos);

		glGenBuffers(1, &ssboCellKeys);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellKeys);
		glBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);

		glGenBuffers(1, &ssboParticleIndices);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticleIndices);
		glBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, ssboParticleIndices);

		glGenBuffers(1, &ssboCellXs);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellXs);
		glBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(int32_t), nullptr, GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, ssboCellXs);

		glGenBuffers(1, &ssboCellYs);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellYs);
		glBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(int32_t), nullptr, GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, ssboCellYs);

		neighborSearchProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &neighborSearchCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

		if (!success) {
			char infoLog[512];
			glGetShaderInfoLog(shader, 512, nullptr, infoLog);
			std::cerr << "Compute shader compilation failed:\n" << infoLog << std::endl;
		}

		glAttachShader(neighborSearchProgram, shader);
		glLinkProgram(neighborSearchProgram);

		glGetProgramiv(neighborSearchProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char infoLog[512];
			glGetProgramInfoLog(neighborSearchProgram, 512, nullptr, infoLog);
			std::cerr << "Shader neighborSearchProgram linking failed:\n" << infoLog << std::endl;
		}

		glDeleteShader(shader);
	}

	std::vector<glm::vec2> pPos;

	void gpuNeighborSearch(std::vector<ParticlePhysics>& pParticles) {
		if (pParticles.empty()) return;
		size_t n = pParticles.size();

		entries.cellKeys.resize(n);
		entries.particleIndices.resize(n);
		entries.cellXs.resize(n);
		entries.cellYs.resize(n);
		pPos.resize(n);

		for (size_t i = 0; i < n; i++) {
			pPos[i] = pParticles[i].pos;
		}

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPPos);
		glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, n * sizeof(glm::vec2), pPos.data());

		glUseProgram(neighborSearchProgram);
		glUniform1f(glGetUniformLocation(neighborSearchProgram, "cellSize"), cellSize);
		glUniform1ui(glGetUniformLocation(neighborSearchProgram, "hashTableSize"), hashTableSize);
		glUniform1ui(glGetUniformLocation(neighborSearchProgram, "numParticles"), (GLuint)n);

		GLuint numGroups = (GLuint)((n + 255) / 256);
		glDispatchCompute(numGroups, 1, 1);

		glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
	}

	const char* bitonicSortCompute = R"(
#version 430
layout(local_size_x = 256) in;

layout(std430, binding = 11) buffer CellKeys {
    uint cellKeys[];
};

layout(std430, binding = 12) buffer ParticleIndices {
    uint particleIndices[];
};

layout(std430, binding = 13) buffer CellXs {
    int cellXs[];
};

layout(std430, binding = 14) buffer CellYs {
    int cellYs[];
};

uniform uint numValues;
uniform uint groupWidth;
uniform uint groupHeight;
uniform uint stepIndex;

void main() {
    uint i = gl_GlobalInvocationID.x;

    uint hIndex = i & (groupWidth - 1);
    uint indexLeft = hIndex + (groupHeight + 1) * (i / groupWidth);
    uint rightStepSize = stepIndex == 0 ? groupHeight - 2 * hIndex : (groupHeight + 1) / 2;
    uint indexRight = indexLeft + rightStepSize;

    if (indexRight >= numValues) return;

    uint valueLeft = cellKeys[indexLeft];
    uint valueRight = cellKeys[indexRight];

    if (valueLeft > valueRight)
    {
        uint tempCellKey = cellKeys[indexLeft];
        cellKeys[indexLeft] = cellKeys[indexRight];
        cellKeys[indexRight] = tempCellKey;

        uint tempIndices = particleIndices[indexLeft];
        particleIndices[indexLeft] = particleIndices[indexRight];
        particleIndices[indexRight] = tempIndices;

        int tempX = cellXs[indexLeft];
        cellXs[indexLeft] = cellXs[indexRight];
        cellXs[indexRight] = tempX;

        int tempY = cellYs[indexLeft];
        cellYs[indexLeft] = cellYs[indexRight];
        cellYs[indexRight] = tempY;
    }
}
)";

	GLuint bitonicSortProgram;

	void bitonicSortKernel() {

		bitonicSortProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &bitonicSortCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

		if (!success) {
			char infoLog[512];
			glGetShaderInfoLog(shader, 512, nullptr, infoLog);
			std::cerr << "Compute shader compilation failed:\n" << infoLog << std::endl;
		}

		glAttachShader(bitonicSortProgram, shader);
		glLinkProgram(bitonicSortProgram);

		glGetProgramiv(bitonicSortProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char infoLog[512];
			glGetProgramInfoLog(bitonicSortProgram, 512, nullptr, infoLog);
			std::cerr << "Shader bitonicSortProgram linking failed:\n" << infoLog << std::endl;
		}

		glDeleteShader(shader);
	}

	void gpuBitonicSort() {
		const size_t n = entries.cellKeys.size();
		if (n == 0) return;

		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, ssboParticleIndices);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, ssboCellXs);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, ssboCellYs);

		glUseProgram(bitonicSortProgram);
		glUniform1ui(glGetUniformLocation(bitonicSortProgram, "numValues"), n);

		int numStages = static_cast<int>(log2(nextPowerOfTwo(n)));

		for (int stageIndex = 0; stageIndex < numStages; stageIndex++) {
			for (int stepIndex = 0; stepIndex < stageIndex + 1; stepIndex++) {

				int groupWidth = 1 << (stageIndex - stepIndex);
				int groupHeight = 2 * groupWidth - 1;

				glUniform1ui(glGetUniformLocation(bitonicSortProgram, "groupWidth"), static_cast<uint32_t>(groupWidth));
				glUniform1ui(glGetUniformLocation(bitonicSortProgram, "groupHeight"), static_cast<uint32_t>(groupHeight));
				glUniform1ui(glGetUniformLocation(bitonicSortProgram, "stepIndex"), static_cast<uint32_t>(stepIndex));

				GLuint numThreads = nextPowerOfTwo(n) / 2;
				GLuint numGroups = (numThreads + 255) / 256;
				glDispatchCompute(numGroups, 1, 1);
				glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
			}
		}
	}

	const char* offsetCompute = R"(
#version 430

layout(local_size_x = 256) in;

layout(std430, binding = 11) buffer CellKeys {
    uint cellKeys[];
};


layout(std430, binding = 15) buffer Offsets {
    uint offsets[];
};

uniform uint numEntries;
uniform uint hashTableSize;

void main() {

    uint i = gl_GlobalInvocationID.x; 
    if (i >= numEntries) return;

    uint null = hashTableSize;

    uint key = cellKeys[i]; 

    uint keyPrev = (i == 0) ? null : cellKeys[i - 1];

    if (key != keyPrev && key < hashTableSize) {
        offsets[key] = i;
    }
}
)";

	GLuint ssboOffsets;

	GLuint offsetProgram;

	void offsetKernel() {

		glGenBuffers(1, &ssboOffsets);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboOffsets);
		glBufferData(GL_SHADER_STORAGE_BUFFER,
			hashTableSize * sizeof(uint32_t),
			nullptr,
			GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 15, ssboOffsets);

		offsetProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &offsetCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

		if (!success) {
			char infoLog[512];
			glGetShaderInfoLog(shader, 512, nullptr, infoLog);
			std::cerr << "Compute shader compilation failed:\n" << infoLog << std::endl;
		}

		glAttachShader(offsetProgram, shader);
		glLinkProgram(offsetProgram);

		glGetProgramiv(offsetProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char infoLog[512];
			glGetProgramInfoLog(offsetProgram, 512, nullptr, infoLog);
			std::cerr << "Shader offsetProgram linking failed:\n" << infoLog << std::endl;
		}

		glDeleteShader(shader);
	}

	unsigned int nextPowerOfTwo(uint32_t n) {
		if (n == 0) return 1;
		n--;
		n |= n >> 1;
		n |= n >> 2;
		n |= n >> 4;
		n |= n >> 8;
		n |= n >> 16;
		return n + 1;
	}

	const char* offsetsResetCompute = R"(
#version 430
layout(local_size_x = 256) in;

layout(std430, binding = 15) buffer Offsets {
    uint offsets[];
};

uniform uint hashTableSize;

void main() {
    uint i = gl_GlobalInvocationID.x;
    if (i >= hashTableSize) return;
    offsets[i] = 4294967295; // UINT_MAX
}
)";

	GLuint offsetsResetProgram;

	void offsetResetKernel() {

		offsetsResetProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &offsetsResetCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

		if (!success) {
			char infoLog[512];
			glGetShaderInfoLog(shader, 512, nullptr, infoLog);
			std::cerr << "Compute shader compilation failed:\n" << infoLog << std::endl;
		}

		glAttachShader(offsetsResetProgram, shader);
		glLinkProgram(offsetsResetProgram);

		glGetProgramiv(offsetsResetProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char infoLog[512];
			glGetProgramInfoLog(offsetsResetProgram, 512, nullptr, infoLog);
			std::cerr << "Shader offsetsResetProgram linking failed:\n" << infoLog << std::endl;
		}

		glDeleteShader(shader);
	}

	void gpuOffsets() {
		const size_t n = entries.cellKeys.size();
		if (n == 0) return;

		glUseProgram(offsetsResetProgram);

		glUniform1ui(glGetUniformLocation(offsetsResetProgram, "hashTableSize"), hashTableSize);

		glDispatchCompute((hashTableSize + 255) / 256, 1, 1);

		glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);

		glUseProgram(offsetProgram);
		glUniform1ui(glGetUniformLocation(offsetProgram, "numEntries"), (uint32_t)n);
		glUniform1ui(glGetUniformLocation(offsetProgram, "hashTableSize"), hashTableSize);

		glDispatchCompute((n + 255) / 256, 1, 1);

		glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboOffsets);
		uint32_t* ptrOffsets = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);
		if (ptrOffsets) {
			memcpy(startIndices.data(), ptrOffsets, hashTableSize * sizeof(uint32_t));
			glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
		}
	}

	void newGridGPU(std::vector<ParticlePhysics>& pParticles) {
		const size_t n = pParticles.size();
		if (n == 0) return;

		entries.size = n;
		startIndices.assign(hashTableSize, UINT32_MAX);

		gpuNeighborSearch(pParticles);

		gpuBitonicSort();

		/*if (n > 0) {
			startIndices[entries.cellKeys[0]] = 0;
		}
		for (size_t i = 1; i < n; i++) {
			if (entries.cellKeys[i - 1] != entries.cellKeys[i]) {
				startIndices[entries.cellKeys[i]] = i;
			}
		}*/

		gpuOffsets();

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellKeys);

		uint32_t* ptrCellKeys = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

		memcpy(entries.cellKeys.data(), ptrCellKeys, entries.cellKeys.size() * sizeof(uint32_t));

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);


		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticleIndices);

		uint32_t* ptrIndices = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

		memcpy(entries.particleIndices.data(), ptrIndices, entries.particleIndices.size() * sizeof(uint32_t));

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);


		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellXs);

		uint32_t* ptrCellX = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

		memcpy(entries.cellXs.data(), ptrCellX, entries.cellXs.size() * sizeof(int32_t));

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);


		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellYs);

		uint32_t* ptrCellY = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

		memcpy(entries.cellYs.data(), ptrCellY, entries.cellYs.size() * sizeof(int32_t));

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	}

	std::vector<float> posX;
	std::vector<float> posY;
	std::vector<float> predPosX;
	std::vector<float> predPosY;
	std::vector<float> accX;
	std::vector<float> accY;
	std::vector<float> velX;
	std::vector<float> velY;
	std::vector<float> prevVelX;
	std::vector<float> prevVelY;
	std::vector<float> sphMass;
	std::vector<float> press;
	std::vector<float> pressFX;
	std::vector<float> pressFY;
	std::vector<float> stiff;
	std::vector<float> visc;
	std::vector<float> dens;
	std::vector<float> predDens;
	std::vector<float> restDens;

	void flattenParticles(std::vector<ParticlePhysics>& pParticles);

	void readFlattenBack(std::vector<ParticlePhysics>& pParticles);

	void computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector<glm::vec2>& sphForce, size_t& N);

	void groundModeBoundary(std::vector<ParticlePhysics>& pParticles,
		std::vector<ParticleRendering>& rParticles,
		glm::vec2 domainSize, UpdateVariables& myVar);

	void PCISPH(UpdateVariables& myVar, UpdateParameters& myParam);

	void pcisphSolver(UpdateVariables& myVar, UpdateParameters& myParam) {

		//newGridGPU(pParticles);

		PCISPH(myVar, myParam);

		if (myVar.sphGround) {
			groundModeBoundary(myParam.pParticles, myParam.rParticles, myVar.domainSize, myVar);
		}
	}
};

================================================
FILE: GalaxyEngine/include/Physics/SPH3D.h
================================================
#pragma once

#include "Particles/particle.h"

#include "parameters.h"
#include "Particles/QueryNeighbors.h"

struct UpdateVariables;
struct UpdateVariables;

class SPH3D {
public:
	float radiusMultiplier = 3.0f;
	const float boundDamping = -0.1f;

	float densTolerance = 0.08f;

	int maxIter = 1; // I keep only 1 iteration when I don't use the density error condition
	int iter = 0;

	float rhoError = 0.0f;

	float cellSize;

	SPH3D() : cellSize(radiusMultiplier) {}

	float smoothingKernel(float dst, float radiusMultiplier) {
		if (dst >= radiusMultiplier) return 0.0f;

		float volume = (2.0f * PI * pow(radiusMultiplier, 5.0f)) / 15.0f;
		return pow(radiusMultiplier - dst, 2.0f) / volume;
	}

	float spikyKernelDerivative(float dst, float radiusMultiplier) {
		if (dst >= radiusMultiplier) return 0.0f;

		float scale = -45.0f / (PI * pow(radiusMultiplier, 6.0f));
		return scale * pow(radiusMultiplier - dst, 2.0f);
	}

	float smoothingKernelLaplacian(float dst, float radiusMultiplier) {
		if (dst >= radiusMultiplier) return 0.0f;

		float scale = 45.0f / (PI * pow(radiusMultiplier, 6.0f));
		return scale * (radiusMultiplier - dst);
	}

	float smoothingKernelCohesion(float r, float h) {
		if (r >= h) return 0.0f;

		float q = r / h;
		return (1.0f - q) * (0.5f - q) * (0.5f - q) * 30.0f / (PI * h * h * h);
	}

	// New Grid Search

	struct EntryArrays {
		std::vector<uint32_t> cellKeys;
		std::vector<uint32_t> particleIndices;
		std::vector<int> cellXs;
		std::vector<int> cellYs;
		size_t size;
	};

	const uint32_t hashTableSize = 16384;
	EntryArrays entries;
	std::vector<uint32_t> startIndices;

	const char* neighborSearchCompute = R"(
#version 430

layout(local_size_x = 256) in;

layout(std430, binding = 10) buffer ParticlePositions {
    vec2 pos[];
};

layout(std430, binding = 11) buffer CellKeys {
    uint cellKeys[];
};

layout(std430, binding = 12) buffer ParticleIndices {
    uint particleIndices[];
};

layout(std430, binding = 13) buffer CellXs {
    int cellXs[];
};

layout(std430, binding = 14) buffer CellYs {
    int cellYs[];
};

uniform float cellSize;
uniform uint hashTableSize;
uniform uint numParticles;

uvec2 posToCellCoord(vec2 pos) {
    int cellX = int(floor(pos.x / cellSize));
    int cellY = int(floor(pos.y / cellSize));
    return uvec2(cellX, cellY);
}

uint hashCell(int cellX, int cellY) {
    uint h = uint(cellX * 73856093) ^ uint(cellY * 19349663);
    return h % hashTableSize;
}

void main() {
    uint i = gl_GlobalInvocationID.x;
    if (i >= numParticles) return;

    uvec2 cellCoord = posToCellCoord(pos[i]);
    uint cellKey = hashCell(int(cellCoord.x), int(cellCoord.y));

    cellKeys[i] = cellKey;
    particleIndices[i] = i;
    cellXs[i] = int(cellCoord.x);
    cellYs[i] = int(cellCoord.y);
}
)";

	GLuint ssboPPos, ssboCellKeys, ssboParticleIndices, ssboCellXs, ssboCellYs;

	size_t mb = 512;

	size_t reserveSize = (1024 * 1024 * mb) / sizeof(glm::vec2);

	GLuint neighborSearchProgram;

	void neighborSearchKernel() {

		glGenBuffers(1, &ssboPPos);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPPos);
		glBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(glm::vec2), nullptr, GL_STREAM_COPY);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, ssboPPos);

		glGenBuffers(1, &ssboCellKeys);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellKeys);
		glBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);

		glGenBuffers(1, &ssboParticleIndices);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticleIndices);
		glBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, ssboParticleIndices);

		glGenBuffers(1, &ssboCellXs);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellXs);
		glBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(int32_t), nullptr, GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, ssboCellXs);

		glGenBuffers(1, &ssboCellYs);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellYs);
		glBufferData(GL_SHADER_STORAGE_BUFFER, 100000 * sizeof(int32_t), nullptr, GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, ssboCellYs);

		neighborSearchProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &neighborSearchCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

		if (!success) {
			char infoLog[512];
			glGetShaderInfoLog(shader, 512, nullptr, infoLog);
			std::cerr << "Compute shader compilation failed:\n" << infoLog << std::endl;
		}

		glAttachShader(neighborSearchProgram, shader);
		glLinkProgram(neighborSearchProgram);

		glGetProgramiv(neighborSearchProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char infoLog[512];
			glGetProgramInfoLog(neighborSearchProgram, 512, nullptr, infoLog);
			std::cerr << "Shader neighborSearchProgram linking failed:\n" << infoLog << std::endl;
		}

		glDeleteShader(shader);
	}

	std::vector<glm::vec2> pPos;

	void gpuNeighborSearch(std::vector<ParticlePhysics>& pParticles) {
		if (pParticles.empty()) return;
		size_t n = pParticles.size();

		entries.cellKeys.resize(n);
		entries.particleIndices.resize(n);
		entries.cellXs.resize(n);
		entries.cellYs.resize(n);
		pPos.resize(n);

		for (size_t i = 0; i < n; i++) {
			pPos[i] = pParticles[i].pos;
		}

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPPos);
		glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, n * sizeof(glm::vec2), pPos.data());

		glUseProgram(neighborSearchProgram);
		glUniform1f(glGetUniformLocation(neighborSearchProgram, "cellSize"), cellSize);
		glUniform1ui(glGetUniformLocation(neighborSearchProgram, "hashTableSize"), hashTableSize);
		glUniform1ui(glGetUniformLocation(neighborSearchProgram, "numParticles"), (GLuint)n);

		GLuint numGroups = (GLuint)((n + 255) / 256);
		glDispatchCompute(numGroups, 1, 1);

		glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
	}

	const char* bitonicSortCompute = R"(
#version 430
layout(local_size_x = 256) in;

layout(std430, binding = 11) buffer CellKeys {
    uint cellKeys[];
};

layout(std430, binding = 12) buffer ParticleIndices {
    uint particleIndices[];
};

layout(std430, binding = 13) buffer CellXs {
    int cellXs[];
};

layout(std430, binding = 14) buffer CellYs {
    int cellYs[];
};

uniform uint numValues;
uniform uint groupWidth;
uniform uint groupHeight;
uniform uint stepIndex;

void main() {
    uint i = gl_GlobalInvocationID.x;

    uint hIndex = i & (groupWidth - 1);
    uint indexLeft = hIndex + (groupHeight + 1) * (i / groupWidth);
    uint rightStepSize = stepIndex == 0 ? groupHeight - 2 * hIndex : (groupHeight + 1) / 2;
    uint indexRight = indexLeft + rightStepSize;

    if (indexRight >= numValues) return;

    uint valueLeft = cellKeys[indexLeft];
    uint valueRight = cellKeys[indexRight];

    if (valueLeft > valueRight)
    {
        uint tempCellKey = cellKeys[indexLeft];
        cellKeys[indexLeft] = cellKeys[indexRight];
        cellKeys[indexRight] = tempCellKey;

        uint tempIndices = particleIndices[indexLeft];
        particleIndices[indexLeft] = particleIndices[indexRight];
        particleIndices[indexRight] = tempIndices;

        int tempX = cellXs[indexLeft];
        cellXs[indexLeft] = cellXs[indexRight];
        cellXs[indexRight] = tempX;

        int tempY = cellYs[indexLeft];
        cellYs[indexLeft] = cellYs[indexRight];
        cellYs[indexRight] = tempY;
    }
}
)";

	GLuint bitonicSortProgram;

	void bitonicSortKernel() {

		bitonicSortProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &bitonicSortCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

		if (!success) {
			char infoLog[512];
			glGetShaderInfoLog(shader, 512, nullptr, infoLog);
			std::cerr << "Compute shader compilation failed:\n" << infoLog << std::endl;
		}

		glAttachShader(bitonicSortProgram, shader);
		glLinkProgram(bitonicSortProgram);

		glGetProgramiv(bitonicSortProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char infoLog[512];
			glGetProgramInfoLog(bitonicSortProgram, 512, nullptr, infoLog);
			std::cerr << "Shader bitonicSortProgram linking failed:\n" << infoLog << std::endl;
		}

		glDeleteShader(shader);
	}

	void gpuBitonicSort() {
		const size_t n = entries.cellKeys.size();
		if (n == 0) return;

		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 12, ssboParticleIndices);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 13, ssboCellXs);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 14, ssboCellYs);

		glUseProgram(bitonicSortProgram);
		glUniform1ui(glGetUniformLocation(bitonicSortProgram, "numValues"), n);

		int numStages = static_cast<int>(log2(nextPowerOfTwo(n)));

		for (int stageIndex = 0; stageIndex < numStages; stageIndex++) {
			for (int stepIndex = 0; stepIndex < stageIndex + 1; stepIndex++) {

				int groupWidth = 1 << (stageIndex - stepIndex);
				int groupHeight = 2 * groupWidth - 1;

				glUniform1ui(glGetUniformLocation(bitonicSortProgram, "groupWidth"), static_cast<uint32_t>(groupWidth));
				glUniform1ui(glGetUniformLocation(bitonicSortProgram, "groupHeight"), static_cast<uint32_t>(groupHeight));
				glUniform1ui(glGetUniformLocation(bitonicSortProgram, "stepIndex"), static_cast<uint32_t>(stepIndex));

				GLuint numThreads = nextPowerOfTwo(n) / 2;
				GLuint numGroups = (numThreads + 255) / 256;
				glDispatchCompute(numGroups, 1, 1);
				glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
			}
		}
	}

	const char* offsetCompute = R"(
#version 430

layout(local_size_x = 256) in;

layout(std430, binding = 11) buffer CellKeys {
    uint cellKeys[];
};


layout(std430, binding = 15) buffer Offsets {
    uint offsets[];
};

uniform uint numEntries;
uniform uint hashTableSize;

void main() {

    uint i = gl_GlobalInvocationID.x; 
    if (i >= numEntries) return;

    uint null = hashTableSize;

    uint key = cellKeys[i]; 

    uint keyPrev = (i == 0) ? null : cellKeys[i - 1];

    if (key != keyPrev && key < hashTableSize) {
        offsets[key] = i;
    }
}
)";

	GLuint ssboOffsets;

	GLuint offsetProgram;

	void offsetKernel() {

		glGenBuffers(1, &ssboOffsets);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboOffsets);
		glBufferData(GL_SHADER_STORAGE_BUFFER,
			hashTableSize * sizeof(uint32_t),
			nullptr,
			GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 15, ssboOffsets);

		offsetProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &offsetCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

		if (!success) {
			char infoLog[512];
			glGetShaderInfoLog(shader, 512, nullptr, infoLog);
			std::cerr << "Compute shader compilation failed:\n" << infoLog << std::endl;
		}

		glAttachShader(offsetProgram, shader);
		glLinkProgram(offsetProgram);

		glGetProgramiv(offsetProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char infoLog[512];
			glGetProgramInfoLog(offsetProgram, 512, nullptr, infoLog);
			std::cerr << "Shader offsetProgram linking failed:\n" << infoLog << std::endl;
		}

		glDeleteShader(shader);
	}

	unsigned int nextPowerOfTwo(uint32_t n) {
		if (n == 0) return 1;
		n--;
		n |= n >> 1;
		n |= n >> 2;
		n |= n >> 4;
		n |= n >> 8;
		n |= n >> 16;
		return n + 1;
	}

	const char* offsetsResetCompute = R"(
#version 430
layout(local_size_x = 256) in;

layout(std430, binding = 15) buffer Offsets {
    uint offsets[];
};

uniform uint hashTableSize;

void main() {
    uint i = gl_GlobalInvocationID.x;
    if (i >= hashTableSize) return;
    offsets[i] = 4294967295; // UINT_MAX
}
)";

	GLuint offsetsResetProgram;

	void offsetResetKernel() {

		offsetsResetProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &offsetsResetCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);

		if (!success) {
			char infoLog[512];
			glGetShaderInfoLog(shader, 512, nullptr, infoLog);
			std::cerr << "Compute shader compilation failed:\n" << infoLog << std::endl;
		}

		glAttachShader(offsetsResetProgram, shader);
		glLinkProgram(offsetsResetProgram);

		glGetProgramiv(offsetsResetProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char infoLog[512];
			glGetProgramInfoLog(offsetsResetProgram, 512, nullptr, infoLog);
			std::cerr << "Shader offsetsResetProgram linking failed:\n" << infoLog << std::endl;
		}

		glDeleteShader(shader);
	}

	void gpuOffsets() {
		const size_t n = entries.cellKeys.size();
		if (n == 0) return;

		glUseProgram(offsetsResetProgram);

		glUniform1ui(glGetUniformLocation(offsetsResetProgram, "hashTableSize"), hashTableSize);

		glDispatchCompute((hashTableSize + 255) / 256, 1, 1);

		glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssboCellKeys);

		glUseProgram(offsetProgram);
		glUniform1ui(glGetUniformLocation(offsetProgram, "numEntries"), (uint32_t)n);
		glUniform1ui(glGetUniformLocation(offsetProgram, "hashTableSize"), hashTableSize);

		glDispatchCompute((n + 255) / 256, 1, 1);

		glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboOffsets);
		uint32_t* ptrOffsets = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);
		if (ptrOffsets) {
			memcpy(startIndices.data(), ptrOffsets, hashTableSize * sizeof(uint32_t));
			glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
		}
	}

	void newGridGPU(std::vector<ParticlePhysics>& pParticles) {
		const size_t n = pParticles.size();
		if (n == 0) return;

		entries.size = n;
		startIndices.assign(hashTableSize, UINT32_MAX);

		gpuNeighborSearch(pParticles);

		gpuBitonicSort();

		/*if (n > 0) {
			startIndices[entries.cellKeys[0]] = 0;
		}
		for (size_t i = 1; i < n; i++) {
			if (entries.cellKeys[i - 1] != entries.cellKeys[i]) {
				startIndices[entries.cellKeys[i]] = i;
			}
		}*/

		gpuOffsets();

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellKeys);

		uint32_t* ptrCellKeys = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

		memcpy(entries.cellKeys.data(), ptrCellKeys, entries.cellKeys.size() * sizeof(uint32_t));

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);


		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticleIndices);

		uint32_t* ptrIndices = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

		memcpy(entries.particleIndices.data(), ptrIndices, entries.particleIndices.size() * sizeof(uint32_t));

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);


		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellXs);

		uint32_t* ptrCellX = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

		memcpy(entries.cellXs.data(), ptrCellX, entries.cellXs.size() * sizeof(int32_t));

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);


		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellYs);

		uint32_t* ptrCellY = (uint32_t*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

		memcpy(entries.cellYs.data(), ptrCellY, entries.cellYs.size() * sizeof(int32_t));

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	}

	std::vector<float> posX;
	std::vector<float> posY;
	std::vector<float> predPosX;
	std::vector<float> predPosY;
	std::vector<float> accX;
	std::vector<float> accY;
	std::vector<float> velX;
	std::vector<float> velY;
	std::vector<float> prevVelX;
	std::vector<float> prevVelY;
	std::vector<float> sphMass;
	std::vector<float> press;
	std::vector<float> pressFX;
	std::vector<float> pressFY;
	std::vector<float> stiff;
	std::vector<float> visc;
	std::vector<float> dens;
	std::vector<float> predDens;
	std::vector<float> restDens;

	void flattenParticles(std::vector<ParticlePhysics>& pParticles);

	void readFlattenBack(std::vector<ParticlePhysics>& pParticles);

	void computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector<glm::vec3>& sphForce, size_t& N);

	void groundModeBoundary(std::vector<ParticlePhysics3D>& pParticles,
		std::vector<ParticleRendering3D>& rParticles, glm::vec3 domainSize, UpdateVariables& myVar);

	void PCISPH(UpdateVariables& myVar, UpdateParameters& myParam);

	void pcisphSolver(UpdateVariables& myVar, UpdateParameters& myParam) {

		//newGridGPU(pParticles);

		PCISPH(myVar, myParam);

		if (myVar.sphGround) {
			groundModeBoundary(myParam.pParticles3D, myParam.rParticles3D, myVar.domainSize3D, myVar);
		}
	}
};

================================================
FILE: GalaxyEngine/include/Physics/constraint.h
================================================
#pragma once

struct ParticleConstraint {
	uint32_t id1;
	uint32_t id2;
	float restLength;
	float originalLength;
	float stiffness;
	float resistance;
	float displacement;
	float plasticityPoint;
	bool isBroken;
	bool isPlastic;
};

================================================
FILE: GalaxyEngine/include/Physics/field.h
================================================
#pragma once

#include "parameters.h"

struct Cell {
	glm::vec2 pos;
	float size;
	Color col = { 5,5,5,255 };
	bool isActive = false;
	float strength = 0.0f;

	void drawCell() {

		DrawRectangleV({ pos.x, pos.y }, { size, size }, col);
	}
};

struct Field {

	int res = 150;

	int amountX = res;
	int amountY = res;

	float cellSize = 10.0f;

	float gravityDisplayThreshold = 1000.0f;

	bool computeField = true;

	float gravityDisplaySoftness = 0.85f;

	std::vector<Cell> cells;

	glm::vec2 prevDomainSize = { 0.0f, 0.0f };

	int prevRes = 150;

	void initializeCells(UpdateVariables& myVar) {

		if (prevDomainSize != myVar.domainSize) {
			computeField = true;

			prevDomainSize = myVar.domainSize;
		}

		if (prevRes != res) {
			computeField = true;

			prevRes = res;
		}

		if (computeField) {

			if (myVar.domainSize.x >= myVar.domainSize.y) {
				cellSize = myVar.domainSize.y / static_cast<float>(res);
			}
			else {
				cellSize = myVar.domainSize.x / static_cast<float>(res);
			}

			amountX = static_cast<int>(myVar.domainSize.x / cellSize);
			amountY = static_cast<int>(myVar.domainSize.y / cellSize);

			cells.clear();
			cells.resize(amountX * amountY);

			for (int i = 0; i < amountX; i++) {
				for (int j = 0; j < amountY; j++) {

					int index = j + i * amountY;

					cells[index].size = cellSize;
					cells[index].pos.x = i * cellSize;
					cells[index].pos.y = j * cellSize;
				}
			}

			computeField = false;
		}
	}

	//void fieldLogic(UpdateParameters& myParam, UpdateVariables& myVar) {

//#pragma omp parallel for schedule(dynamic)
//		for (size_t i = 0; i < cells.size(); i++) {
//
//			float force = 0.0f;
//
//			for (size_t j = 0; j < myParam.pParticles.size(); j++) {
//
//				glm::vec2 d = myParam.pParticles[j].pos - (cells[i].pos + cells[i].size * 0.5f);
//				float distSq = d.x * d.x + d.y * d.y + myVar.softening * myVar.softening;
//
//				float dist = glm::sqrt(distSq);
//
//				if (dist > 0.0001f) {
//					force += myVar.G * (myParam.pParticles[j].mass * 10.0f) / (dist * dist);
//				}
//			}
//
//			cells[i].strength = force;
//		}
	//}

	const char* fieldGravityCompute = R"(
#version 430

layout(local_size_x = 256) in;

layout(std430, binding = 7) buffer ParticlesPos {
    float particlesPos[];
};

layout(std430, binding = 8) buffer ParticlesMass {
    float particlesMass[];
};

layout(std430, binding = 9) buffer CellsData {
    float cellsData[];
};

uniform int particleCount;
uniform int cellCount;

uniform float G;
uniform float gravityDisplaySoftness;

uniform vec2 domainSize;
uniform int periodicBoundary;

void main() {

    uint i = gl_GlobalInvocationID.x;

    if (i >= uint(cellCount))
        return;

    vec2 cellPos;
    cellPos.x = cellsData[i];
    cellPos.y = cellsData[i + cellCount];

    float force = 0.0;

    for (int j = 0; j < particleCount; j++) {

        vec2 pPos;
        pPos.x = particlesPos[j];
        pPos.y = particlesPos[j + particleCount];

        vec2 d = pPos - cellPos;

        if (periodicBoundary == 1) {
            d -= domainSize * round(d / domainSize);
        }

        float distSq = dot(d, d) + gravityDisplaySoftness * gravityDisplaySoftness;
        float dist = sqrt(distSq);

        if (dist > 0.0001) {
            force += G * (particlesMass[j] * 10.0) / (dist * dist);
        }
    }

    cellsData[i + 2 * cellCount] = force;
}
)";


	const char* fieldFragmentShader = R"(
#version 430

layout(std430, binding = 9) buffer CellsData {
    float cellsData[];
};

uniform int amountX;
uniform int amountY;
uniform float cellSize;
uniform float gravityDisplayThreshold;
uniform float stretchFactor;
uniform float exposure; 

uniform bool useTwoColorMode;
uniform vec3 colorLow;
uniform vec3 colorHigh;

in vec2 fragPosition;
out vec4 finalColor;

vec3 hsv2rgb(vec3 c) {
    vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

void main() {
    int cellX = int(fragPosition.x / cellSize);
    int cellY = int(fragPosition.y / cellSize);

    if (cellX < 0 || cellX >= amountX || cellY < 0 || cellY >= amountY)
        discard;

    int cellIndex = cellY + cellX * amountY;
    float strength = cellsData[cellIndex + 2 * amountX * amountY];

    float clamped = clamp(strength, 0.0, gravityDisplayThreshold);
    float normalized = clamped / gravityDisplayThreshold;
    float stretched = log(1.0 + normalized * stretchFactor) / log(1.0 + stretchFactor);

    vec3 color;

    if (useTwoColorMode) {
        vec3 base = mix(colorLow, colorHigh, stretched);
        color = base * stretched; 

        color *= exposure; 
    } else {
        float hue = (1.0 - stretched) * 240.0;
        color = hsv2rgb(vec3(hue / 360.0, 1.0, stretched));
    }

    finalColor = vec4(color, 1.0);
}
)";

	GLuint ssboParticlesPos, ssboParticlesMass, ssboCellsData;
	GLuint gravityDisplayProgram;

	const char* fieldVertexShader = R"(
#version 430
in vec3 vertexPosition;
in vec2 vertexTexCoord;

uniform mat4 mvp;

out vec2 fragPosition;

void main()
{
    fragPosition = vertexPosition.xy; 
    gl_Position = mvp * vec4(vertexPosition, 1.0);
}
)";

	Shader drawShader;


	void fieldGravityDisplayKernel() {

		drawShader = LoadShaderFromMemory(fieldVertexShader, fieldFragmentShader);

		if (drawShader.id == 0) {
			std::cout << "Fragment shader failed to load\n";
		}

		const size_t maxVRAM = size_t(2ull) * 1024 * 1024 * 1024;
		const float safetyMargin = 0.85f;

		const size_t usableVRAM = size_t(maxVRAM * safetyMargin);

		const size_t bytesPerParticle = 6 * sizeof(float);

		const size_t bytesPerFieldCell = 3 * sizeof(float);
		const size_t fieldWidth = 1024;
		const size_t fieldHeight = 1024;

		const size_t fieldCells = fieldWidth * fieldHeight;
		const size_t fieldBytes = fieldCells * bytesPerFieldCell;

		const size_t maxParticles =
			(usableVRAM - fieldBytes) / bytesPerParticle;

		if (maxParticles < 10000000)
		{
			throw std::runtime_error("GPU too small for 10 million particles.");
		}

		std::cout << "Max particles supported by GPU: " << maxParticles << "\n";

		glGenBuffers(1, &ssboParticlesPos);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticlesPos);
		glBufferData(GL_SHADER_STORAGE_BUFFER,
			maxParticles * 2 * sizeof(float),
			nullptr,
			GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, ssboParticlesPos);

		glGenBuffers(1, &ssboParticlesMass);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticlesMass);
		glBufferData(GL_SHADER_STORAGE_BUFFER,
			maxParticles * sizeof(float),
			nullptr,
			GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, ssboParticlesMass);

		glGenBuffers(1, &ssboCellsData);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellsData);
		glBufferData(GL_SHADER_STORAGE_BUFFER,
			maxParticles * 3 * sizeof(float),
			nullptr,
			GL_DYNAMIC_DRAW);
		glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 9, ssboCellsData);

		gravityDisplayProgram = glCreateProgram();
		GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
		glShaderSource(shader, 1, &fieldGravityCompute, nullptr);
		glCompileShader(shader);

		GLint success;
		glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
		if (!success) {
			char log[1024];
			glGetShaderInfoLog(shader, 1024, nullptr, log);
			std::cerr << log << std::endl;
		}

		glAttachShader(gravityDisplayProgram, shader);
		glLinkProgram(gravityDisplayProgram);

		glGetProgramiv(gravityDisplayProgram, GL_LINK_STATUS, &success);
		if (!success) {
			char log[1024];
			glGetProgramInfoLog(gravityDisplayProgram, 1024, nullptr, log);
			std::cerr << log << std::endl;
		}

		glDeleteShader(shader);
	}

	std::vector<float> particlesData;
	std::vector<float> particlesMassVector;
	std::vector<float> cellsData;

	void gpuGravityDisplay(UpdateParameters& myParam, UpdateVariables& myVar) {

		if (myParam.pParticles.empty() || !myVar.isGravityFieldEnabled) {
			return;
		}

		size_t particleCount = myParam.pParticles.size();

		if (!myVar.gravityFieldDMParticles) {

			particleCount = 0;

			for (size_t i = 0; i < myParam.pParticles.size(); i++) {
				if (myParam.rParticles[i].isDarkMatter) {
					continue;
				}
				particleCount++;
			}
		}

		size_t cellCount = cells.size();

		particlesData.resize(particleCount * 2);
		particlesMassVector.resize(particleCount);
		cellsData.resize(cellCount * 3);

		size_t writeIndex = 0;

		for (size_t i = 0; i < myParam.pParticles.size(); i++) {

			if (!myVar.gravityFieldDMParticles && myParam.rParticles[i].isDarkMatter) {
				continue;
			}

			particlesData[writeIndex] = myParam.pParticles[i].pos.x;
			particlesData[writeIndex + particleCount] = myParam.pParticles[i].pos.y;
			particlesMassVector[writeIndex] = myParam.pParticles[i].mass;

			writeIndex++;
		}

		for (size_t i = 0; i < cellCount; i++) {
			cellsData[i] = cells[i].pos.x;
			cellsData[i + cellCount] = cells[i].pos.y;
			cellsData[i + 2 * cellCount] = 0.0f;
		}

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticlesPos);
		glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0,
			particlesData.size() * sizeof(float),
			particlesData.data());

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboParticlesMass);
		glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0,
			particlesMassVector.size() * sizeof(float),
			particlesMassVector.data());

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellsData);
		glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0,
			cellsData.size() * sizeof(float),
			cellsData.data());

		glUseProgram(gravityDisplayProgram);

		glUniform1i(glGetUniformLocation(gravityDisplayProgram, "particleCount"),
			(int)particleCount);
		glUniform1i(glGetUniformLocation(gravityDisplayProgram, "cellCount"),
			(int)cellCount);

		glUniform2f(glGetUniformLocation(gravityDisplayProgram, "domainSize"),
			myVar.domainSize.x, myVar.domainSize.y);
		glUniform1f(glGetUniformLocation(gravityDisplayProgram, "gravityDisplaySoftness"),
			gravityDisplaySoftness);
		glUniform1i(glGetUniformLocation(gravityDisplayProgram, "periodicBoundary"),
			myVar.isPeriodicBoundaryEnabled);

		glUniform1f(glGetUniformLocation(gravityDisplayProgram, "G"), myVar.G);

		GLuint groups = (cellCount + 255) / 256;
		glDispatchCompute(groups, 1, 1);

		glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboCellsData);
		float* ptr = (float*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

#pragma omp parallel for
		for (size_t i = 0; i < cellCount; i++) {
			cells[i].strength = ptr[i + 2 * cellCount];
		}

		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
	}

	float hue = 180.0f;
	float saturation = 0.8f;
	float value = 0.5f;

	float gravityStretchFactor = 90.0f;

	bool gravityCustomColors = false;

	float gravityExposure = 3.0f;

	void drawField(UpdateParameters& myParam, UpdateVariables& myVar)
	{
		BeginMode2D(myParam.myCamera.camera);
		BeginShaderMode(drawShader);

		int exposureLoc = GetShaderLocation(drawShader, "exposure");

		int amtXLoc = GetShaderLocation(drawShader, "amountX");
		int amtYLoc = GetShaderLocation(drawShader, "amountY");
		int sizeLoc = GetShaderLocation(drawShader, "cellSize");
		int threshLoc = GetShaderLocation(drawShader, "gravityDisplayThreshold");
		int stretchLoc = GetShaderLocation(drawShader, "stretchFactor");

		int lowLoc = GetShaderLocation(drawShader, "colorLow");
		int highLoc = GetShaderLocation(drawShader, "colorHigh");

		float lowColor[3] = {
			static_cast<float>(myParam.colorVisuals.pColor.r / 255.0f),
			static_cast<float>(myParam.colorVisuals.pColor.g / 255.0f),
			static_cast<float>(myParam.colorVisuals.pColor.b / 255.0f)
		};

		float highColor[3] = {
			static_cast<float>(myParam.colorVisuals.sColor.r / 255.0f),
			static_cast<float>(myParam.colorVisuals.sColor.g / 255.0f),
			static_cast<float>(myParam.colorVisuals.sColor.b / 255.0f)
		};

		SetShaderValue(drawShader, exposureLoc, &gravityExposure, SHADER_UNIFORM_FLOAT);

		SetShaderValue(drawShader, lowLoc, lowColor, SHADER_UNIFORM_VEC3);
		SetShaderValue(drawShader, highLoc, highColor, SHADER_UNIFORM_VEC3);

		int toggleLoc = GetShaderLocation(drawShader, "useTwoColorMode");
		int shaderBool = gravityCustomColors ? 1 : 0;
		SetShaderValue(drawShader, toggleLoc, &shaderBool, SHADER_UNIFORM_INT);

		SetShaderValue(drawShader, amtXLoc, &amountX, SHADER_UNIFORM_INT);
		SetShaderValue(drawShader, amtYLoc, &amountY, SHADER_UNIFORM_INT);
		SetShaderValue(drawShader, sizeLoc, &cellSize, SHADER_UNIFORM_FLOAT);
		SetShaderValue(drawShader, threshLoc, &gravityDisplayThreshold, SHADER_UNIFORM_FLOAT);
		SetShaderValue(drawShader, stretchLoc, &gravityStretchFactor, SHADER_UNIFORM_FLOAT);

		DrawRectangleV({ 0.0f, 0.0f }, { myVar.domainSize.x, myVar.domainSize.y }, WHITE);

		EndShaderMode();
		EndMode2D();
	}
};

================================================
FILE: GalaxyEngine/include/Physics/light.h
================================================
#pragma once

#include "UX/randNum.h"
#include "IO/io.h"
#include "parameters.h"

extern uint32_t globalWallId;

struct Wall {

	glm::vec2 vA;
	glm::vec2 vB;

	glm::vec2 normal{ 0.0f,0.0f };
	glm::vec2 normalVA{ 0.0f, 0.0f };
	glm::vec2 normalVB{ 0.0f, 0.0f };

	bool isBeingSpawned;
	bool vAisBeingMoved;
	bool vBisBeingMoved;

	Color apparentColor; // This is the color used to visualize the wall

	Color baseColor;
	Color specularColor;
	Color refractionColor;
	Color emissionColor;

	float baseColorVal;
	float specularColorVal;
	float refractionColorVal;

	float specularRoughness;
	float refractionRoughness;
	float refractionAmount;
	float IOR;
	float dispersionStrength;

	bool isShapeWall;
	bool isShapeClosed;
	uint32_t shapeId;

	uint32_t id;

	bool isSelected;

	Wall() = default;
	Wall(glm::vec2 vA, glm::vec2 vB, bool isShapeWall, Color baseColor, Color specularColor, Color refractionColor, Color emissionColor,
		float specularRoughness, float refractionRoughness, float refractionAmount, float IOR, float dispersionStrength) {

		this->vA = vA;
		this->vB = vB;
		this->isShapeWall = isShapeWall;
		this->isShapeClosed = false;
		this->isSelected = false;
		this->shapeId = 0;
		this->isBeingSpawned = true;
		this->vAisBeingMoved = false;
		this->vBisBeingMoved = false;
		this->baseColor = baseColor;
		this->specularColor = specularColor;
		this->refractionColor = refractionColor;
		this->emissionColor = emissionColor;

		this->apparentColor = WHITE;

		this->baseColorVal = std::max({ baseColor.r, baseColor.g, baseColor.b }) * (baseColor.a / 255.0f) / 255.0f;
		this->specularColorVal = std::max({ specularColor.r, specularColor.g, specularColor.b }) * (specularColor.a / 255.0f) / 255.0f;
		this->refractionColorVal = std::max({ refractionColor.r, refractionColor.g, refractionColor.b }) * (refractionColor.a / 255.0f) / 255.0f;

		this->specularRoughness = specularRoughness;
		this->refractionRoughness = refractionRoughness;
		this->refractionAmount = refractionAmount;
		this->IOR = IOR;
		this->dispersionStrength = dispersionStrength;

		this->id = globalWallId++;
	}

	void drawHelper(glm::vec2& helper) {
		DrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);
	}

	void drawWall() {
		DrawLineV({ vA.x, vA.y }, { vB.x, vB.y }, apparentColor);
	}
};

extern uint32_t globalShapeId;

enum ShapeType {
	circle,
	draw,
	lens
};

struct Shape {

	std::vector<Wall>* walls;
	std::vector<uint32_t> myWallIds;

	std::vector<glm::vec2> polygonVerts;

	/*enum class ShapeType {
		Circle1,
		Circle2,
	};*/

	Color baseColor;
	Color specularColor;
	Color refractionColor;
	Color emissionColor;

	float specularRoughness;
	float refractionRoughness;

	float refractionAmount;

	float IOR;
	float dispersionStrength;

	uint32_t id;

	glm::vec2 h1;
	glm::vec2 h2;

	bool isBeingSpawned;
	bool isBeingMoved;

	bool isShapeClosed;

	ShapeType shapeType;

	bool drawHoverHelpers = false;

	Shape() = default;

	Shape(ShapeType shapeType, glm::vec2 h1, glm::vec2 h2, std::vector<Wall>* walls,
		Color baseColor, Color specularColor, Color refractionColor, Color emissionColor,
		float specularRoughness, float refractionRoughness, float refractionAmount, float IOR, float dispersionStrength) :

		shapeType(shapeType),
		h1(h1),
		h2(h2),
		walls(walls),
		baseColor(baseColor),
		specularColor(specularColor),
		refractionColor(refractionColor),
		emissionColor(emissionColor),
		specularRoughness(specularRoughness),
		refractionRoughness(refractionRoughness),
		refractionAmount(refractionAmount),
		IOR(IOR),
		dispersionStrength(dispersionStrength),
		id(globalShapeId++),
		isBeingSpawned(true),
		isBeingMoved(false),
		isShapeClosed(true) {
	}

	Wall* getWallById(std::vector<Wall>& walls, uint32_t id) {
		for (Wall& wall : walls) {
			if (wall.id == id)
				return &wall;
		}
		return nullptr;
	}

	float getSignedArea(const std::vector<glm::vec2>& vertices) {
		float area = 0.0f;
		int n = vertices.size();

		for (int i = 0; i < n; ++i) {
			const glm::vec2& current = vertices[i];
			const glm::vec2& next = vertices[(i + 1) % n];
			area += (current.x * next.y - next.x * current.y);
		}

		return 0.5f * area;
	}



	void calculateWallsNormals() {

		int n = myWallIds.size();
		if (n == 0) return;

		polygonVerts.clear();

		for (uint32_t wallId : myWallIds) {
			Wall* w = getWallById(*walls, wallId);
			if (w) {
				polygonVerts.push_back(w->vA);
			}
		}

		bool flipNormals = getSignedArea(polygonVerts) > 0.0f;

		for (uint32_t wallId : myWallIds) {
			Wall* w = getWallById(*walls, wallId);
			if (!w) continue;

			if (!w->isShapeWall) {
				continue;
			}

			glm::vec2 tangent = glm::normalize(w->vB - w->vA);
			glm::vec2 normal = flipNormals
				? glm::vec2(tangent.y, -tangent.x)
				: glm::vec2(-tangent.y, tangent.x);

			w->normal = normal;
		}

		const float smoothingAngleThreshold = glm::cos(glm::radians(35.0f));

		for (int i = 0; i < n; ++i) {
			int prev = (i - 1 + n) % n;
			int next = (i + 1) % n;

			uint32_t idPrev = myWallIds[prev];
			uint32_t id = myWallIds[i];
			uint32_t idNext = myWallIds[next];

			Wall* wPrev = getWallById(*walls, idPrev);
			Wall* w = getWallById(*walls, id);
			Wall* wNext = getWallById(*walls, idNext);

			if (!wPrev || !w || !wNext) {
				continue;
			}

			if (!wPrev->isShapeWall || !w->isShapeWall || !wNext->isShapeWall) {
				continue;
			}

			float lenPrev = glm::length(wPrev->vB - wPrev->vA);
			float len = glm::length(w->vB - w->vA);
			float lenNext = glm::length(wNext->vB - wNext->vA);

			bool smoothWithPrev = glm::dot(wPrev->normal, w->normal) >= smoothingAngleThreshold;
			bool smoothWithNext = glm::dot(w->normal, wNext->normal) >= smoothingAngleThreshold;

			glm::vec2 tangent = glm::normalize(w->vB - w->vA);

			if (smoothWithPrev) {
				w->normalVA = glm::normalize(wPrev->normal * lenPrev + w->normal * len);
			}
			else if (smoothWithNext) {
				glm::vec2 normalVB = glm::normalize(w->normal * len + wNext->normal * lenNext);
				w->normalVA = normalVB - 2.0f * glm::dot(normalVB, tangent) * tangent;
			}
			else {
				w->normalVA = w->normal;
			}

			if (smoothWithNext) {
				w->normalVB = glm::normalize(w->normal * len + wNext->normal * lenNext);
			}
			else if (smoothWithPrev) {
				w->normalVB = w->normalVA - 2.0f * glm::dot(w->normalVA, tangent) * tangent;
			}
			else {
				w->normalVB = w->normal;
			}
		}
	}

	void relaxGeometryLogic(std::vector<glm::vec2>& vertices, int& shapeRelaxiter, float& shapeRelaxFactor) {
		if (vertices.size() < 3) return;

		std::vector<glm::vec2> temp = vertices;

		for (int iter = 0; iter < shapeRelaxiter; ++iter) {
			for (size_t i = 0; i < vertices.size(); ++i) {
				size_t prev = (i - 1 + vertices.size()) % vertices.size();
				size_t next = (i + 1) % vertices.size();

				glm::vec2 average = (temp[prev] + temp[next]) * 0.5f;

				vertices[i] = glm::mix(temp[i], average, shapeRelaxFactor);
			}
			temp = vertices;
		}
	}

	void relaxShape(int& shapeRelaxiter, float& shapeRelaxFactor) {

		polygonVerts.clear();

		for (uint32_t wallId : myWallIds) {
			Wall* w = getWallById(*walls, wallId);
			if (w) {
				polygonVerts.push_back(w->vA);
			}
		}

		relaxGeometryLogic(polygonVerts, shapeRelaxiter, shapeRelaxFactor);

		for (size_t i = 0; i < myWallIds.size(); ++i) {
			uint32_t wallId = myWallIds[i];
			Wall* w = getWallById(*walls, wallId);

			if (w) {
				w->vA = polygonVerts[i];
				w->vB = polygonVerts[(i + 1) % polygonVerts.size()];
			}
		}
	}

	void makeShape() {

		if (shapeType == circle) {
			makeCircle();
		}

		if (shapeType == draw) {
			drawShape();
		}

		if (shapeType == lens) {
			makeLens();
		}
	}

	bool createShapeFlag = false;

	std::vector<glm::vec2> helpers;

	int circleSegments = 100;

	float circleRadius = 0.0f;

	void makeCircle() {

		if (isBeingSpawned) {
			circleRadius = glm::length(h2 - h1);
		}
		else {
			circleRadius = glm::length(helpers[0] - helpers[1]);
		}


		std::vector<uint32_t> newWallIds;

		for (int i = 0; i < circleSegments; ++i) {
			float theta1 = (2.0f * PI * i) / circleSegments;
			float theta2 = (2.0f * PI * (i + 1)) / circleSegments;

			if (!isBeingSpawned) {
				h1 = helpers[0];
			}

			glm::vec2 vA = {
				h1.x + cos(theta1) * circleRadius,
				h1.y + sin(theta1) * circleRadius
			};
			glm::vec2 vB = {
				h1.x + cos(theta2) * circleRadius,
				h1.y + sin(theta2) * circleRadius
			};

			if (createShapeFlag) {
				walls->emplace_back(vA, vB, true, baseColor, specularColor, refractionColor, emissionColor,
					specularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);

				Wall& newWall = walls->back();
				newWall.shapeId = id;
				newWall.isShapeClosed = true;

				newWallIds.push_back(newWall.id);
			}

			if (isBeingMoved && i < myWallIds.size()) {
				uint32_t wId = myWallIds[i];
				if (Wall* w = getWallById(*walls, wId)) {
					w->vA = vA;
					w->vB = vB;
				}
			}
		}

		if (isBeingMoved) {
			calculateWallsNormals();
		}


		if (createShapeFlag) {
			myWallIds = std::move(newWallIds);

			calculateWallsNormals();

			createShapeFlag = false;
		}
	}

	glm::vec2 prevPoint = h1;

	glm::vec2 oldDrawHelperPos{ 0.0f, 0.0f };

	void drawShape() {
		float maxSegmentLength = 4.0f;

		glm::vec2 dir = h2 - prevPoint;
		float dist = glm::length(dir);

		if (isBeingSpawned) {
			if (dist < maxSegmentLength) return;
		}

		glm::vec2 dirNorm = glm::normalize(dir);

		if (isBeingSpawned) {
			while (dist >= maxSegmentLength) {
				glm::vec2 nextPoint = prevPoint + dirNorm * maxSegmentLength;

				(*walls).emplace_back(prevPoint, nextPoint, true, baseColor, specularColor, refractionColor, emissionColor,
					specularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);

				prevPoint = nextPoint;
				dist = glm::length(h2 - prevPoint);

				(*walls).back().shapeId = id;
				(*walls).back().isShapeClosed = true;

				myWallIds.push_back((*walls).back().id);
			}
		}

		if (isBeingSpawned) {
			for (auto& wallId : myWallIds) {

				Wall* w = getWallById(*walls, wallId);

				if (w) {
					helpers[0] += w->vA;
					helpers[0] += w->vB;
				}
			}

			helpers[0] /= static_cast<float>(myWallIds.size()) * 2.0f;

			oldDrawHelperPos = helpers[0];
		}

		if (isBeingMoved) {
			glm::vec2 delta = helpers[0] - oldDrawHelperPos;



			if (delta != glm::vec2(0.0f)) {

				for (auto& wallId : myWallIds) {
					Wall* w = getWallById(*walls, wallId);

					if (w) {
						w->vA += delta;
						w->vB += delta;
					}
				}

				oldDrawHelperPos = helpers[0];
			}
		}
	}

	bool secondHelper = false;
	bool thirdHelper = false;
	bool fourthHelper = false;

	float Tempsh2Length = 0.0f;
	float Tempsh2LengthSymmetry = 0.0f;
	float tempDist = 0.0f;

	glm::vec2 moveH2 = h2;

	bool isThirdBeingMoved = false;
	bool isFourthBeingMoved = false;
	bool isFifthBeingMoved = false;

	bool isGlobalHelperMoved = false;

	glm::vec2 globalLensPrev = { 0.0f, 0.0f };

	bool isFifthFourthMoved = false;

	bool symmetricalLens = true;

	uint32_t wallAId = 1;
	uint32_t wallBId = 2;
	uint32_t wallCId = 3;

	int lensSegments = 50;

	void drawHelper(glm::vec2& helper) {
		DrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);
	}

	float startAngle = 0.0f;
	float endAngle = 0.0f;

	float startAngleSymmetry = 0.0f;
	float endAngleSymmetry = 0.0f;

	glm::vec2 center{ 0.0f, 0.0f };
	float radius = 0.0f;

	glm::vec2 centerSymmetry{ 0.0f, 0.0f };
	float radiusSymmetry = 0.0f;

	glm::vec2 arcEnd{ 0.0f, 0.0f };

	// 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

	void makeLens() {

		if (helpers.empty()) {
			helpers.push_back(h1);
		}

		if (secondHelper) {
			helpers.push_back(h2);
			secondHelper = false;
		}

		glm::vec2 thirdHelperPos = h2;
		glm::vec2 otherSide = h2;
		if (helpers.size() == 2 || isBeingMoved) {

			glm::vec2 tangent = glm::normalize(helpers.at(0) - helpers.at(1));

			glm::vec2 normal = glm::vec2(tangent.y, -tangent.x);

			glm::vec2 offset = h2 - helpers.at(1);

			float dist;
			if (!isBeingMoved) {

				dist = glm::dot(offset, normal);
				tempDist = dist;
			}
			else if (isBeingMoved && isThirdBeingMoved) {
				offset = moveH2 - helpers.at(1);

				dist = glm::dot(offset, normal);
				tempDist = dist;
			}
			else {
				dist = tempDist;
			}

			thirdHelperPos = helpers.at(1) + dist * normal;

			otherSide = helpers.at(0) + dist * normal;

			if (isBeingMoved) {
				helpers.at(2) = thirdHelperPos;
			}
		}

		if (thirdHelper) {

			helpers.push_back(thirdHelperPos);
			thirdHelper = false;
		}

		if (helpers.size() >= 3) {

			glm::vec2 direction = -glm::normalize(helpers.at(1) - helpers.at(0));

			glm::vec2 directionSymmetry = glm::normalize(helpers.at(1) - helpers.at(0));

			float arcWidth = std::abs(glm::length(helpers.at(1) - helpers.at(0)));
			arcEnd = helpers.at(2) + direction * arcWidth;

			glm::vec2 arcEndSymmetry = helpers.at(2) - direction * arcWidth;

			glm::vec2 normal = glm::vec2(direction.y, -direction.x);

			glm::vec2 toEnd = helpers.at(2) - helpers.at(1);
			float cross = direction.x * toEnd.y - direction.y * toEnd.x;
			if (cross < 0) {
				normal = -normal;
			}

			glm::vec2 offset = helpers.at(2) - helpers.at(1);

			float dist = glm::dot(offset, normal);

			glm::vec2 midPoint = (helpers.at(2) + (helpers.at(0) + dist * normal)) * 0.5f;

			glm::vec2 midPointSymmetry = (helpers.at(0) + helpers.at(1)) * 0.5f;

			glm::vec2 midToh2 = midPoint - h2;

			glm::vec2 midToh2Symmetry = midPointSymmetry - h2;

			float h2Length;
			float h2LengthSymmetry;

			if (!isBeingMoved) {
				h2Length = glm::dot(midToh2, normal);
				h2LengthSymmetry = glm::dot(midToh2, normal);

				Tempsh2Length = h2Length;
				Tempsh2LengthSymmetry = h2LengthSymmetry;
			}
			else if (isBeingMoved && isFifthFourthMoved) {
				midToh2 = midPoint - moveH2;

				h2Length = glm::dot(midToh2, normal);

				Tempsh2Length = h2Length;

				h2LengthSymmetry = h2Length;

				Tempsh2LengthSymmetry = h2Length;
			}
			else if (isBeingMoved && isFourthBeingMoved) {

				midToh2 = midPoint - moveH2;

				h2Length = glm::dot(midToh2, normal);

				Tempsh2Length = h2Length;
				h2LengthSymmetry = Tempsh2LengthSymmetry;
			}
			else if (isBeingMoved && isFifthBeingMoved) {
				midToh2Symmetry = midPointSymmetry - moveH2;

				h2LengthSymmetry = glm::dot(midToh2Symmetry, -normal);

				h2Length = Tempsh2Length;
				Tempsh2LengthSymmetry = h2LengthSymmetry;
			}
			else {
				h2Length = Tempsh2Length;
				h2LengthSymmetry = Tempsh2LengthSymmetry;
			}

			h2Length = std::clamp(h2Length, -arcWidth * 0.48f, arcWidth * 0.48f);
			h2LengthSymmetry = std::clamp(h2LengthSymmetry, -arcWidth * 0.48f, arcWidth * 0.48f);

			glm::vec2 p1 = helpers.at(2);
			glm::vec2 p2 = midPoint - h2Length * normal;
			glm::vec2 p3 = helpers.at(0) + dist * normal;

			glm::vec2 p1Symmetry = helpers.at(0);
			glm::vec2 p2Symmetry = midPointSymmetry - h2LengthSymmetry * -normal;
			glm::vec2 p3Symmetry = helpers.at(1);

			glm::vec2 mid1 = (p1 + p2) * 0.5f;
			glm::vec2 dir1 = glm::vec2(p2.y - p1.y, p1.x - p2.x);

			glm::vec2 mid2 = (p2 + p3) * 0.5f;
			glm::vec2 dir2 = glm::vec2(p3.y - p2.y, p2.x - p3.x);

			float denominator = dir2.x * dir1.y - dir2.y * dir1.x;
			if (std::abs(denominator) > 1e-6f) {
				float t = (dir2.x * (mid2.y - mid1.y) - dir2.y * (mid2.x - mid1.x)) / denominator;
				center = mid1 + t * dir1;
				radius = glm::length(center - p1);
			}
			else {
				center = mid1;
				radius = std::numeric_limits<float>::max();
			}

			startAngle = atan2(p1.y - center.y, p1.x - center.x);
			endAngle = atan2(p3.y - center.y, p3.x - center.x);


			float angleDiff = endAngle - startAngle;


			glm::vec2 mid1Symmetry = (p1Symmetry + p2Symmetry) * 0.5f;
			glm::vec2 dir1Symmetry = glm::vec2(p2Symmetry.y - p1Symmetry.y, p1Symmetry.x - p2Symmetry.x);

			glm::vec2 mid2Symmetry = (p2Symmetry + p3Symmetry) * 0.5f;
			glm::vec2 dir2Symmetry = glm::vec2(p3Symmetry.y - p2Symmetry.y, p2Symmetry.x - p3Symmetry.x);

			float denominatorSymmetry = dir2Symmetry.x * dir1Symmetry.y - dir2Symmetry.y * dir1Symmetry.x;
			if (std::abs(denominatorSymmetry) > 1e-6f) {
				float tSymmetry = (dir2Symmetry.x * (mid2Symmetry.y - mid1Symmetry.y) - dir2Symmetry.y * (mid2Symmetry.x - mid1Symmetry.x)) / denominatorSymmetry;
				centerSymmetry = mid1Symmetry + tSymmetry * dir1Symmetry;
				radiusSymmetry = glm::length(centerSymmetry - p1Symmetry);
			}
			else {
				centerSymmetry = mid1Symmetry;
				radiusSymmetry = std::numeric_limits<float>::max();
			}

			startAngleSymmetry = atan2(p1Symmetry.y - centerSymmetry.y, p1Symmetry.x - centerSymmetry.x);
			endAngleSymmetry = atan2(p3Symmetry.y - centerSymmetry.y, p3Symmetry.x - centerSymmetry.x);


			float angleDiffSymmetry = endAngleSymmetry - startAngleSymmetry;

			if (angleDiff > PI) {
				endAngle -= 2 * PI;
			}
			else if (angleDiff < -PI) {
				endAngle += 2 * PI;
			}

			if (angleDiffSymmetry > PI) {
				endAngleSymmetry -= 2 * PI;
			}
			else if (angleDiffSymmetry < -PI) {
				endAngleSymmetry += 2 * PI;
			}

			std::vector<uint32_t> newWallIds;

			if (fourthHelper || isBeingMoved) {
				if (!isBeingMoved) {
					helpers.push_back(p2);

					if (symmetricalLens) {
						helpers.push_back(p2Symmetry);
					}
				}
				else {
					helpers.at(3) = p2;
					if (symmetricalLens) {
						helpers.at(4) = p2Symmetry;
					}
				}

				if (!isBeingMoved) {
					if (!symmetricalLens) {
						(*walls).emplace_back(helpers.at(0), helpers.at(1), true, baseColor, specularColor, refractionColor, emissionColor,
							specularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);
						(*walls).back().shapeId = id;
						(*walls).back().isShapeClosed = true;
						newWallIds.push_back((*walls).back().id);
						wallAId = (*walls).back().id;
					}

					(*walls).emplace_back(helpers.at(1), helpers.at(2), true, baseColor, specularColor, refractionColor, emissionColor,
						specularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);
					(*walls).back().shapeId = id;
					(*walls).back().isShapeClosed = true;
					newWallIds.push_back((*walls).back().id);
					wallBId = (*walls).back().id;
				}
				else {
					if (!symmetricalLens) {
						for (auto& wId : myWallIds) {

							if (wId == wallAId) {

								Wall* w = getWallById(*walls, wId);

								w->vA = helpers[0];
								w->vB = helpers[1];
							}
						}
					}

					for (auto& wId : myWallIds) {

						if (wId == wallBId) {

							Wall* w = getWallById(*walls, wId);

							w->vA = helpers[1];
							w->vB = helpers[2];
						}
					}
				}
			}

			for (int i = 0; i < lensSegments; i++) {
				float t1 = static_cast<float>(i) / lensSegments;
				float t2 = static_cast<float>((i + 1)) / lensSegments;

				float angle1 = startAngle + t1 * (endAngle - startAngle);
				float angle2 = startAngle + t2 * (endAngle - startAngle);

				glm::vec2 arcP1 = center + glm::vec2(cos(angle1), sin(angle1)) * radius;
				glm::vec2 arcP2 = center + glm::vec2(cos(angle2), sin(angle2)) * radius;

				if (fourthHelper || isBeingMoved) {
					if (!isBeingMoved) {
						(*walls).emplace_back(arcP1, arcP2, true, baseColor, specularColor, refractionColor, emissionColor,
							specularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);
						(*walls).back().shapeId = id;
						(*walls).back().isShapeClosed = true;
						newWallIds.push_back((*walls).back().id);
					}
					else {

						for (auto& wId : myWallIds) {

							if (wId == wallBId + i + 1) {

								Wall* w = getWallById(*walls, wId);

								w->vA = arcP1;
								w->vB = arcP2;
							}
						}
					}
				}
			}

			if (fourthHelper || isBeingMoved) {

				if (!isBeingMoved) {
					(*walls).emplace_back(arcEnd, helpers.at(0), true, baseColor, specularColor, refractionColor, emissionColor,
						specularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);

					(*walls).back().shapeId = id;
					(*walls).back().isShapeClosed = true;

					newWallIds.push_back((*walls).back().id);
					wallCId = (*walls).back().id;
				}
				else {

					for (auto& wId : myWallIds) {

						if (wId == wallCId) {

							Wall* w = getWallById(*walls, wId);

							w->vA = arcEnd;
							w->vB = helpers[0];
						}
					}
				}
			}

			if (symmetricalLens) {
				for (int i = 0; i < lensSegments; i++) {
					float t1Symmetry = static_cast<float>(i) / lensSegments;
					float t2Symmetry = static_cast<float>((i + 1)) / lensSegments;

					float angle1Symmetry = startAngleSymmetry + t1Symmetry * (endAngleSymmetry - startAngleSymmetry);
					float angle2Symmetry = startAngleSymmetry + t2Symmetry * (endAngleSymmetry - startAngleSymmetry);

					glm::vec2 arcP1Symmetry = centerSymmetry + glm::vec2(cos(angle1Symmetry), sin(angle1Symmetry)) * radiusSymmetry;
					glm::vec2 arcP2Symmetry = centerSymmetry + glm::vec2(cos(angle2Symmetry), sin(angle2Symmetry)) * radiusSymmetry;

					if (fourthHelper || isBeingMoved) {
						if (!isBeingMoved) {
							(*walls).emplace_back(arcP1Symmetry, arcP2Symmetry, true, baseColor, specularColor, refractionColor, emissionColor,
								specularRoughness, refractionRoughness, refractionAmount, IOR, dispersionStrength);
							(*walls).back().shapeId = id;
							(*walls).back().isShapeClosed = true;
							newWallIds.push_back((*walls).back().id);
						}
						else {

							for (auto& wId : myWallIds) {

								if (wId == wallCId + i + 1) {

									Wall* w = getWallById(*walls, wId);

									w->vA = arcP1Symmetry;
									w->vB = arcP2Symmetry;
								}
							}
						}
					}
				}
			}

			if (fourthHelper || isBeingMoved) {

				if (!isBeingMoved) {
					myWallIds = std::move(newWallIds);

					glm::vec2 averageHelper = { 0.0f, 0.0f };

					for (glm::vec2& h : helpers) {
						averageHelper += h;
					}

					averageHelper /= helpers.size();

					helpers.push_back(averageHelper);

					globalLensPrev = helpers.back();
				}
				else if(isBeingMoved && !isGlobalHelperMoved) {
					helpers.back() = {0.0f, 0.0f};

					for (size_t i = 0; i < helpers.size() - 1; i++) {
						helpers.back() += helpers[i];
					}

					helpers.back() /= helpers.size() - 1;

					globalLensPrev = helpers.back();
				}
				else if (isBeingMoved && isGlobalHelperMoved) {
					glm::vec2 delta = helpers.back() - globalLensPrev;

					if (delta != glm::vec2(0.0f)) {
						for (size_t i = 0; i < helpers.size() - 1; i++) {
							helpers[i] += delta;
						}

						globalLensPrev = helpers.back();
					}
				}

				calculateWallsNormals();

				fourthHelper = false;

				if (!isBeingMoved) {
					isBeingSpawned = false;
				}
			}
		}
	}
};

struct LightRay {

	glm::vec2 source;
	glm::vec2 dir;

	float maxLength = 100000.0f;
	float length = 10000.0f;

	glm::vec2 hitPoint;

	bool hasHit;
	int bounceLevel;

	Wall wall;

	Color color;

	bool reflectSpecular;
	bool refracted;

	std::vector<float> mediumIORStack;

	bool hasBeenDispersed;
	bool hasBeenScattered;

	glm::vec2 scatterSource;

	LightRay() = default;
	LightRay(glm::vec2 source, glm::vec2 dir, int bounceLevel, Color color) {
		this->source = source;
		this->dir = dir;
		this->hitPoint = { 0.0f, 0.0f };
		this->hasHit = false;
		this->bounceLevel = bounceLevel;
		this->color = color;
		this->reflectSpecular = false;
		this->refracted = false;
		this->mediumIORStack = { 1.0f };
		this->hasBeenDispersed = false;
		this->hasBeenScattered = false;
		this->scatterSource = { 0.0f, 0.0f };
	}

	void drawRay() {
		DrawLineV({ source.x, source.y }, { source.x + (dir.x * length), source.y + (dir.y * length) }, color);
	}
};

struct PointLight {

	glm::vec2 pos;
	bool isBeingMoved;

	Color color;

	Color apparentColor;

	bool isSelected;

	PointLight() = default;

	PointLight(glm::vec2 pos, Color color) {
		this->pos = pos;
		this->isBeingMoved = false;
		this->color = color;

		this->apparentColor = WHITE;

		this->isSelected = false;
	}

	void drawHelper(glm::vec2& helper) {
		DrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);
	}

	void pointLightLogic(int& sampleRaysAmount, int& currentSamples, int& maxSamples, std::vector<LightRay>& rays) {
		float radius = 100.0f;

		const float goldenAngle = PI * (3.0f - std::sqrt(5.0f));

		int startIndex = currentSamples * sampleRaysAmount;

		for (int i = 0; i < sampleRaysAmount; i++) {
			int rayIndex = startIndex + i;

			float angle = rayIndex * goldenAngle;

			glm::vec2 d = glm::vec2(std::cos(angle), std::sin(angle));
			rays.emplace_back(
				pos,
				d,
				1,
				color
			);
		}
	}
};

struct AreaLight {

	glm::vec2 vA;
	glm::vec2 vB;

	bool isBeingSpawned;
	bool vAisBeingMoved;
	bool vBisBeingMoved;

	Color color;

	Color apparentColor;

	bool isSelected;

	float spread;

	AreaLight() = default;

	AreaLight(glm::vec2 vA, glm::vec2 vB, Color color, float spread) {
		this->vA = vA;
		this->vB = vB;
		this->isBeingSpawned = true;
		this->vAisBeingMoved = false;
		this->vBisBeingMoved = false;
		this->color = color;
		this->apparentColor = WHITE;

		this->spread = spread;

		this->isSelected = false;
	}

	void drawHelper(glm::vec2& helper) {
		DrawCircleV({ helper.x, helper.y }, 5.0f, PURPLE);
	}

	glm::vec2 rotateVec2(glm::vec2 v, float angle) {
		float c = cos(angle);
		float s = sin(angle);
		return glm::vec2(
			v.x * c - v.y * s,
			v.x * s + v.y * c
		);
	}

	void drawAreaLight() {
		DrawLineV({ vA.x, vA.y }, { vB.x, vB.y }, apparentColor);
	}

	void areaLightLogic(int& sampleRaysAmount, std::vector<LightRay>& rays) {

		for (int i = 0; i < sampleRaysAmount; i++) {

			float maxSpreadAngle = glm::radians(90.0f * spread);

			float randAngle = getRandomFloat() * 2.0f * maxSpreadAngle - maxSpreadAngle;

			glm::vec2 d = vB - vA;
			float length = glm::length(d);
			glm::vec2 dNormal = d / length;

			float t = getRandomFloat();
			glm::vec2 source = vA + d * t;

			glm::vec2 rayDirection = rotateVec2(dNormal, randAngle);
			rayDirection = glm::vec2(rayDirection.y, -rayDirection.x);

			rays.emplace_back(
				source,
				rayDirection,
				1,
				color
			);
		}
	}
};

struct ConeLight {

	glm::vec2 vA;
	glm::vec2 vB;

	bool isBeingSpawned;
	bool vAisBeingMoved;
	bool vBisBeingMoved;

	Color color;

	Color apparentColor;

	bool isSelected;

	float spread;

	ConeLight() = default;

	ConeLight(glm::vec2 vA, glm::vec2 vB, Col
Download .txt
gitextract_wwlh0ody/

├── .gitattributes
├── .gitignore
├── CMakeLists.txt
├── CodeGuide.md
├── GalaxyEngine/
│   ├── Config/
│   │   └── config.txt
│   ├── Shaders/
│   │   ├── bloom.fs
│   │   ├── cubemap.fs
│   │   ├── cubemap.vs
│   │   ├── skybox.fs
│   │   └── skybox.vs
│   ├── fonts/
│   │   ├── Unispace Bd.otf
│   │   ├── Unispace Rg.otf
│   │   └── binary_to_compressed_c.cpp
│   ├── include/
│   │   ├── IO/
│   │   │   └── io.h
│   │   ├── Particles/
│   │   │   ├── QueryNeighbors.h
│   │   │   ├── clusterMouseHelper.h
│   │   │   ├── densitySize.h
│   │   │   ├── neighborSearch.h
│   │   │   ├── particle.h
│   │   │   ├── particleColorVisuals.h
│   │   │   ├── particleDeletion.h
│   │   │   ├── particleSelection.h
│   │   │   ├── particleSpaceship.h
│   │   │   ├── particleSubdivision.h
│   │   │   ├── particleTrails.h
│   │   │   └── particlesSpawning.h
│   │   ├── Physics/
│   │   │   ├── SPH.h
│   │   │   ├── SPH3D.h
│   │   │   ├── constraint.h
│   │   │   ├── field.h
│   │   │   ├── light.h
│   │   │   ├── materialsSPH.h
│   │   │   ├── morton.h
│   │   │   ├── physics.h
│   │   │   ├── physics3D.h
│   │   │   ├── quadtree.h
│   │   │   └── slingshot.h
│   │   ├── Renderer/
│   │   │   └── rayMarching.h
│   │   ├── Sound/
│   │   │   └── sound.h
│   │   ├── UI/
│   │   │   ├── UI.h
│   │   │   ├── brush.h
│   │   │   ├── controls.h
│   │   │   └── rightClickSettings.h
│   │   ├── UX/
│   │   │   ├── camera.h
│   │   │   ├── copyPaste.h
│   │   │   ├── randNum.h
│   │   │   ├── saveSystem.h
│   │   │   └── screenCapture.h
│   │   ├── globalLogic.h
│   │   ├── parameters.h
│   │   └── pch.h
│   └── src/
│       ├── Particles/
│       │   ├── particleSelection.cpp
│       │   ├── particleSubdivision.cpp
│       │   ├── particleTrails.cpp
│       │   └── particlesSpawning.cpp
│       ├── Physics/
│       │   ├── SPH.cpp
│       │   ├── SPH3D.cpp
│       │   ├── light.cpp
│       │   ├── morton.cpp
│       │   ├── physics.cpp
│       │   ├── physics3D.cpp
│       │   ├── quadtree.cpp
│       │   └── slingshot.cpp
│       ├── Sound/
│       │   └── sound.cpp
│       ├── UI/
│       │   ├── UI.cpp
│       │   ├── brush.cpp
│       │   ├── controls.cpp
│       │   └── rightClickSettings.cpp
│       ├── UX/
│       │   ├── camera.cpp
│       │   ├── randNum.cpp
│       │   ├── saveSystem.cpp
│       │   └── screenCapture.cpp
│       ├── globalLogic.cpp
│       ├── main.cpp
│       ├── parameters.cpp
│       └── resources.rc
├── LICENSE
├── README.md
└── cmake/
    ├── ffmpeg.cmake
    ├── glm.cmake
    ├── imgui.cmake
    ├── openmp.cmake
    ├── raylib.cmake
    └── yaml.cmake
Download .txt
SYMBOL INDEX (222 symbols across 43 files)

FILE: GalaxyEngine/fonts/binary_to_compressed_c.cpp
  type SourceEncoding (line 37) | enum SourceEncoding
  function main (line 46) | int main(int argc, char** argv)
  function Encode85Byte (line 82) | char Encode85Byte(unsigned int x)
  function binary_to_compressed_c (line 88) | bool binary_to_compressed_c(const char* filename, const char* symbol, So...
  function stb_uint (line 179) | static stb_uint stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uin...
  function stb_matchlen (line 210) | static unsigned int stb_matchlen(stb_uchar *m1, stb_uchar *m2, stb_uint ...
  function stb__write (line 224) | static void stb__write(unsigned char v)
  function stb_out2 (line 233) | static void stb_out2(stb_uint v) { stb_out(v >> 8); stb_out(v); }
  function stb_out3 (line 234) | static void stb_out3(stb_uint v) { stb_out(v >> 16); stb_out(v >> 8); st...
  function stb_out4 (line 235) | static void stb_out4(stb_uint v) { stb_out(v >> 24); stb_out(v >> 16); s...
  function outliterals (line 237) | static void outliterals(stb_uchar *in, int numlit)
  function stb_not_crap (line 259) | static int stb_not_crap(int best, int dist)
  function stb_compress_chunk (line 276) | static int stb_compress_chunk(stb_uchar *history,
  function stb_compress_inner (line 381) | static int stb_compress_inner(stb_uchar *input, stb_uint length)
  function stb_uint (line 416) | stb_uint stb_compress(stb_uchar *out, stb_uchar *input, stb_uint length)

FILE: GalaxyEngine/include/IO/io.h
  function namespace (line 4) | namespace IO {

FILE: GalaxyEngine/include/Particles/QueryNeighbors.h
  function else (line 5) | struct QueryNeighbors {
  function else (line 30) | struct QueryNeighbors3D {

FILE: GalaxyEngine/include/Particles/clusterMouseHelper.h
  type ClusterHelper (line 5) | struct ClusterHelper {

FILE: GalaxyEngine/include/Particles/densitySize.h
  type DensitySize (line 5) | struct DensitySize {

FILE: GalaxyEngine/include/Particles/neighborSearch.h
  type NeighborSearch (line 5) | struct NeighborSearch {
  type NeighborSearch3D (line 207) | struct NeighborSearch3D {
  function posToCellCoord (line 439) | struct NeighborSearchV2 {
  function hashCell (line 476) | uint32_t hashCell(int cellX, int cellY) const {
  function newGrid (line 481) | void newGrid(const std::vector<ParticlePhysics>& pParticles) {
  function neighborAmount (line 585) | void neighborAmount(std::vector<ParticlePhysics>& pParticles, std::vecto...
  type NeighborSearchV2AVX2 (line 601) | struct NeighborSearchV2AVX2 {
  function hashCell (line 631) | uint32_t hashCell(int cellX, int cellY) {
  function newGridAVX2 (line 636) | void newGridAVX2(const std::vector<ParticlePhysics>& pParticles) {
  function queryNeighborsAVX2 (line 722) | void queryNeighborsAVX2(const glm::vec2& pos, std::vector<size_t>& neigh...
  function neighborAmount (line 779) | void neighborAmount(std::vector<ParticlePhysics>& pParticles, std::vecto...
  function posToCellCoord (line 799) | struct NeighborSearch3DV2 {
  function hashCell (line 845) | uint32_t hashCell(int cellX, int cellY, int cellZ) const {
  function newGrid (line 854) | void newGrid(const std::vector<ParticlePhysics3D>& pParticles) {
  function neighborAmount (line 960) | void neighborAmount(std::vector<ParticlePhysics3D>& pParticles, std::vec...
  type NeighborSearch3DV2AVX2 (line 978) | struct NeighborSearch3DV2AVX2 {
  function hashCell (line 1012) | uint32_t hashCell(int cellX, int cellY, int cellZ) {
  function newGridAVX2 (line 1017) | void newGridAVX2(const std::vector<ParticlePhysics3D>& pParticles) {
  function queryNeighborsAVX2 (line 1109) | void queryNeighborsAVX2(const glm::vec3& pos, std::vector<size_t>& neigh...
  function neighborAmount (line 1175) | void neighborAmount(std::vector<ParticlePhysics3D>& pParticles, std::vec...

FILE: GalaxyEngine/include/Particles/particleColorVisuals.h
  function else (line 6) | struct ColorVisuals {
  function Color (line 95) | Color blendColors(Color base, Color emission, float glowFactor) {
  function getGlowFactor (line 104) | float getGlowFactor(int temperature) {
  function particlesColorVisuals (line 112) | void particlesColorVisuals(std::vector<ParticlePhysics>& pParticles, std...

FILE: GalaxyEngine/include/Particles/particleDeletion.h
  type ParticleDeletion (line 7) | struct ParticleDeletion {

FILE: GalaxyEngine/include/Particles/particleSelection.h
  type UpdateVariables (line 8) | struct UpdateVariables
  type UpdateParameters (line 9) | struct UpdateParameters
  function class (line 12) | class ParticleSelection {
  function class (line 45) | class ParticleSelection3D {

FILE: GalaxyEngine/include/Particles/particleSubdivision.h
  type UpdateVariables (line 5) | struct UpdateVariables
  type UpdateParameters (line 6) | struct UpdateParameters

FILE: GalaxyEngine/include/Particles/particleTrails.h
  type UpdateVariables (line 5) | struct UpdateVariables
  type UpdateParameters (line 6) | struct UpdateParameters
  function class (line 9) | class ParticleTrails {

FILE: GalaxyEngine/include/Particles/particlesSpawning.h
  type UpdateVariables (line 13) | struct UpdateVariables
  type UpdateParameters (line 14) | struct UpdateParameters
  type Physics (line 16) | struct Physics
  type Physics3D (line 17) | struct Physics3D
  type Quadtree (line 18) | struct Quadtree
  function class (line 20) | class ParticlesSpawning {
  function class (line 46) | class ParticlesSpawning3D {

FILE: GalaxyEngine/include/Physics/SPH.h
  type UpdateVariables (line 8) | struct UpdateVariables
  type UpdateVariables (line 9) | struct UpdateVariables
  type GridCell (line 11) | struct GridCell {
  function class (line 15) | class SPH {

FILE: GalaxyEngine/include/Physics/SPH3D.h
  type UpdateVariables (line 8) | struct UpdateVariables
  type UpdateVariables (line 9) | struct UpdateVariables
  function class (line 11) | class SPH3D {

FILE: GalaxyEngine/include/Physics/constraint.h
  type ParticleConstraint (line 3) | struct ParticleConstraint {

FILE: GalaxyEngine/include/Physics/field.h
  function if (line 5) | struct Cell {
  function buffer (line 111) | buffer ParticlesPos {
  function buffer (line 115) | buffer ParticlesMass {
  function buffer (line 119) | buffer CellsData {
  function main (line 132) | void main() {
  function buffer (line 173) | buffer CellsData {
  function vec3 (line 191) | vec3 hsv2rgb(vec3 c) {
  function main (line 197) | void main() {
  function main (line 239) | void main()
  function fieldGravityDisplayKernel (line 249) | void fieldGravityDisplayKernel() {
  function gpuGravityDisplay (line 335) | void gpuGravityDisplay(UpdateParameters& myParam, UpdateVariables& myVar) {
  function drawField (line 439) | void drawField(UpdateParameters& myParam, UpdateVariables& myVar)

FILE: GalaxyEngine/include/Physics/light.h
  function drawHelper (line 9) | struct Wall {
  function drawWall (line 84) | void drawWall() {
  type ShapeType (line 91) | enum ShapeType {
  type Shape (line 97) | struct Shape {
  function calculateWallsNormals (line 184) | void calculateWallsNormals() {
  function drawShape (line 402) | void drawShape() {
  function drawHelper (line 972) | struct PointLight {
  function pointLightLogic (line 999) | void pointLightLogic(int& sampleRaysAmount, int& currentSamples, int& ma...
  function drawHelper (line 1022) | struct AreaLight {
  function drawAreaLight (line 1068) | void drawAreaLight() {
  function areaLightLogic (line 1072) | void areaLightLogic(int& sampleRaysAmount, std::vector<LightRay>& rays) {
  function drawHelper (line 1100) | struct ConeLight {
  function coneLightLogic (line 1146) | void coneLightLogic(int& sampleRaysAmount, std::vector<LightRay>& rays) {
  function expand (line 1171) | struct AABB2D {
  function intersectsRay (line 1183) | bool intersectsRay(const glm::vec2& origin, const glm::vec2& dir, float ...
  type BVHNode (line 1199) | struct BVHNode {
  function class (line 1207) | class BVH {
  type Lighting (line 1332) | struct Lighting {
  function calculateWallNormal (line 1404) | void calculateWallNormal(Wall& wall) {
  function drawRays (line 1547) | void drawRays() {
  function drawScene (line 1556) | void drawScene() {
  function processApparentColor (line 1598) | void processApparentColor() {

FILE: GalaxyEngine/include/Physics/materialsSPH.h
  type SPHMaterial (line 3) | struct SPHMaterial {
  function SPHMaterial (line 89) | struct SPHRock : public SPHMaterial {
  function SPHMaterial (line 123) | struct SPHIron : public SPHMaterial {
  function SPHMaterial (line 157) | struct SPHSand : public SPHMaterial {
  function SPHMaterial (line 191) | struct SPHSoil : public SPHMaterial {
  function SPHMaterial (line 225) | struct SPHMud : public SPHMaterial {
  function SPHMaterial (line 259) | struct SPHRubber : public SPHMaterial {
  type SPHMaterials (line 294) | struct SPHMaterials {

FILE: GalaxyEngine/include/Physics/morton.h
  type Morton (line 5) | struct Morton {

FILE: GalaxyEngine/include/Physics/physics.h
  type Physics (line 13) | struct Physics {
  type GravityCell (line 76) | struct GravityCell {
  function initGrid (line 98) | void initGrid(std::vector<ParticlePhysics>& pParticles, glm::vec3& bb) {

FILE: GalaxyEngine/include/Physics/physics3D.h
  type Physics3D (line 13) | struct Physics3D {

FILE: GalaxyEngine/include/Physics/quadtree.h
  type Node (line 28) | struct Node
  type Node (line 31) | struct Node {
  function computeInternalMass (line 69) | inline void computeInternalMass() {
  function calculateNextNeighbor (line 94) | inline void calculateNextNeighbor() {
  type Quadtree (line 114) | struct Quadtree {
  type Node3D (line 131) | struct Node3D
  type Node3D (line 134) | struct Node3D {
  type Octree (line 181) | struct Octree {

FILE: GalaxyEngine/include/Physics/slingshot.h
  type UpdateVariables (line 5) | struct UpdateVariables
  function class (line 7) | class Slingshot {
  function class (line 17) | class Slingshot3D {

FILE: GalaxyEngine/include/Renderer/rayMarching.h
  type RayMarcher (line 8) | struct RayMarcher {
  function drawPixels (line 51) | void drawPixels() {
  type Particle (line 281) | struct Particle {
  function buffer (line 286) | buffer ParticleBuffer {
  function smoothMin (line 290) | float smoothMin(float a, float b, float k) {
  function de (line 295) | float de(vec3 p){
  function vec3 (line 305) | vec3 calcNormal(vec3 p) {
  type RayResult (line 314) | struct RayResult {
  function RayResult (line 324) | RayResult rayParticle(vec3 origin, vec3 direction) {
  function RayResult (line 382) | RayResult fractal(vec3 origin, vec3 direction) {
  function main (line 432) | void main() {
  function createShaderProgram (line 473) | void createShaderProgram() {
  function createOutputTextures (line 492) | void createOutputTextures() {
  function createSSBO (line 532) | void createSSBO() {
  function Init (line 539) | void Init() {
  type GPUParticle (line 546) | struct GPUParticle {
  function Run (line 551) | void Run(const SceneCamera3D& cam, const std::vector<ParticlePhysics3D>&...
  function Draw (line 605) | void Draw(bool& lowResRayMarching) {
  function Unload (line 615) | void Unload() {

FILE: GalaxyEngine/include/UI/UI.h
  type sphParams (line 29) | struct sphParams {
  type SimilarTypeButton (line 94) | struct SimilarTypeButton {

FILE: GalaxyEngine/include/UI/brush.h
  type UpdateVariables (line 13) | struct UpdateVariables
  type UpdateParameters (line 14) | struct UpdateParameters
  function class (line 16) | class Brush {
  function class (line 50) | class Brush3D {

FILE: GalaxyEngine/include/UI/controls.h
  type UpdateVariables (line 3) | struct UpdateVariables

FILE: GalaxyEngine/include/UI/rightClickSettings.h
  type UpdateVariables (line 10) | struct UpdateVariables
  type UpdateParameters (line 11) | struct UpdateParameters
  type rightClickParams (line 13) | struct rightClickParams {

FILE: GalaxyEngine/include/UX/camera.h
  function class (line 6) | class SceneCamera {
  function class (line 39) | class SceneCamera3D {

FILE: GalaxyEngine/include/UX/copyPaste.h
  function if (line 7) | struct CopyPaste {

FILE: GalaxyEngine/include/UX/saveSystem.h
  type ParticleConstraint (line 14) | struct ParticleConstraint
  function class (line 24) | class SaveSystem {
  function deserializeParticleSystem (line 120) | bool deserializeParticleSystem(const std::string& filename,

FILE: GalaxyEngine/include/UX/screenCapture.h
  type AVFormatContext (line 3) | struct AVFormatContext
  type AVCodecContext (line 4) | struct AVCodecContext
  type AVStream (line 5) | struct AVStream
  type SwsContext (line 6) | struct SwsContext
  type AVFrame (line 7) | struct AVFrame
  type UpdateVariables (line 9) | struct UpdateVariables
  type UpdateParameters (line 10) | struct UpdateParameters
  function class (line 12) | class ScreenCapture {

FILE: GalaxyEngine/include/globalLogic.h
  type ParticleBounds (line 57) | struct ParticleBounds {

FILE: GalaxyEngine/include/parameters.h
  type PlaybackParticle (line 21) | struct PlaybackParticle {
  type UpdateParameters (line 29) | struct UpdateParameters {
  type UpdateVariables (line 88) | struct UpdateVariables {

FILE: GalaxyEngine/src/Physics/physics.cpp
  type DepthInfo (line 1213) | struct DepthInfo {

FILE: GalaxyEngine/src/Physics/quadtree.cpp
  function dualPartition3D (line 132) | uint32_t dualPartition3D(std::vector<ParticlePhysics3D>& pParticlesVecto...

FILE: GalaxyEngine/src/Physics/slingshot.cpp
  function Slingshot (line 14) | Slingshot Slingshot::particleSlingshot(UpdateVariables& myVar, SceneCame...
  function Slingshot3D (line 61) | Slingshot3D Slingshot3D::particleSlingshot(UpdateVariables& myVar, glm::...

FILE: GalaxyEngine/src/UI/UI.cpp
  type ToolButton (line 1402) | struct ToolButton {

FILE: GalaxyEngine/src/UI/brush.cpp
  type SPHWater (line 5) | struct SPHWater
  type SPHRock (line 6) | struct SPHRock
  type SPHIron (line 7) | struct SPHIron
  type SPHSand (line 8) | struct SPHSand
  type SPHSoil (line 9) | struct SPHSoil
  type SPHMud (line 10) | struct SPHMud
  type SPHRubber (line 11) | struct SPHRubber

FILE: GalaxyEngine/src/UX/camera.cpp
  function Camera2D (line 20) | Camera2D SceneCamera::cameraLogic(bool& loadFlag, bool& isMouseNotHoveri...
  function Camera3D (line 174) | Camera3D SceneCamera3D::cameraLogic(bool& isLoading, bool& isMouseNotHov...

FILE: GalaxyEngine/src/UX/randNum.cpp
  function getRandomFloat (line 3) | float getRandomFloat() {

FILE: GalaxyEngine/src/globalLogic.cpp
  function hasAVX2Support (line 33) | bool hasAVX2Support() {
  function selectedParticleDebug (line 64) | void selectedParticleDebug() {
  function pinParticles (line 75) | void pinParticles() {
  function pinParticles3D (line 111) | void pinParticles3D() {
  function plyFileCreation (line 147) | void plyFileCreation(std::ofstream& file) {
  function plyFileCreation3D (line 196) | void plyFileCreation3D(std::ofstream& file) {
  function exportPly (line 244) | void exportPly() {
  type GridChildren (line 557) | struct GridChildren {
  function gravityKernel (line 561) | void gravityKernel() {
  function buildKernels (line 620) | void buildKernels() {
  function gpuGravity (line 643) | void gpuGravity() {
  function freeGPUMemory (line 757) | void freeGPUMemory() {
  function boundingBox (line 1083) | glm::vec3 boundingBox() {
  function updateScene (line 1105) | void updateScene() {
  function boundingBox3D (line 1460) | glm::vec4 boundingBox3D(std::vector<ParticlePhysics3D>& pParticles) {
  function particleBoxClipping (line 1483) | void particleBoxClipping() {
  function mode3D (line 1552) | void mode3D() {
  function drawConstraints3D (line 1760) | void drawConstraints3D() {
  type ParticleDepth (line 1908) | struct ParticleDepth {
  function savePlaybackToDisk (line 1913) | void savePlaybackToDisk(const std::string& filepath) {
  function loadPlaybackFromDisk (line 1938) | void loadPlaybackFromDisk(const std::string& filepath) {
  function playBackLogic (line 1975) | void playBackLogic(Texture2D& particleBlurTex) {
  function drawMode3DRecording (line 2146) | void drawMode3DRecording(Texture2D& particleBlurTex) {
  function drawMode3DNonRecording (line 2226) | void drawMode3DNonRecording() {
  function drawConstraints (line 2248) | void drawConstraints() {
  function drawScene (line 2405) | void drawScene(Texture2D& particleBlurTex, RenderTexture2D& myRayTracing...
  function saveConfigIfChanged (line 2660) | void saveConfigIfChanged() {
  function saveConfig (line 2675) | void saveConfig() {
  function loadConfig (line 2689) | void loadConfig() {
  function enableMultiThreading (line 2703) | void enableMultiThreading() {
  function fullscreenToggle (line 2712) | void fullscreenToggle(int& lastScreenWidth, int& lastScreenHeight,
  function RenderTexture2D (line 2753) | RenderTexture2D CreateFloatRenderTexture(int w, int h) {

FILE: GalaxyEngine/src/main.cpp
  function main (line 9) | int main(int argc, char** argv) {
Condensed preview — 84 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,021K chars).
[
  {
    "path": ".gitattributes",
    "chars": 2518,
    "preview": "###############################################################################\n# Set default behavior to automatically "
  },
  {
    "path": ".gitignore",
    "chars": 389,
    "preview": "# Visual Studio Code settings\n.vscode/\n\n# Visual Studio settings\n.vs/\nCMakeSettings.json\n\n# Build directory\nbuild/\nGalax"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 2603,
    "preview": "cmake_minimum_required(VERSION 3.26..3.30 FATAL_ERROR)\n\nset(CMAKE_POLICY_DEFAULT_CMP0077 NEW)\ncmake_policy(SET CMP0077 N"
  },
  {
    "path": "CodeGuide.md",
    "chars": 4113,
    "preview": "# Code Quick Guide\n\nThis is a quick guide for modding or contributing to the development of Galaxy Engine. Please try to"
  },
  {
    "path": "GalaxyEngine/Config/config.txt",
    "chars": 87,
    "preview": "Global Volume: 0.400000006\nMenu Volume: 0.25\nMusic Volume: 0.400000006\nMessage Index: 3"
  },
  {
    "path": "GalaxyEngine/Shaders/bloom.fs",
    "chars": 755,
    "preview": "#version 330\n\nin vec2 fragTexCoord;\nin vec4 fragColor;\n\nuniform sampler2D texture0;\nuniform vec4 colDiffuse;\nuniform vec"
  },
  {
    "path": "GalaxyEngine/Shaders/cubemap.fs",
    "chars": 601,
    "preview": "#version 100\n\nprecision mediump float;\n\n// Input vertex attributes (from vertex shader)\nvarying vec3 fragPosition;\n\n// I"
  },
  {
    "path": "GalaxyEngine/Shaders/cubemap.vs",
    "chars": 449,
    "preview": "#version 100\n\n// Input vertex attributes\nattribute vec3 vertexPosition;\n\n// Input uniform values\nuniform mat4 matProject"
  },
  {
    "path": "GalaxyEngine/Shaders/skybox.fs",
    "chars": 704,
    "preview": "#version 330\n\n// Input vertex attributes (from vertex shader)\nin vec3 fragPosition;\n\n// Input uniform values\nuniform sam"
  },
  {
    "path": "GalaxyEngine/Shaders/skybox.vs",
    "chars": 554,
    "preview": "#version 330\n\n// Input vertex attributes\nin vec3 vertexPosition;\n\n// Input uniform values\nuniform mat4 matProjection;\nun"
  },
  {
    "path": "GalaxyEngine/fonts/binary_to_compressed_c.cpp",
    "chars": 15753,
    "preview": "// 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 da"
  },
  {
    "path": "GalaxyEngine/include/IO/io.h",
    "chars": 1559,
    "preview": "#pragma once\n\n// Input/Output utility functions\nnamespace IO {\n    \n    // Handle keyboard shortcuts with ImGui integrat"
  },
  {
    "path": "GalaxyEngine/include/Particles/QueryNeighbors.h",
    "chars": 1073,
    "preview": "#pragma once\n\n#include \"parameters.h\"\n\nstruct QueryNeighbors {\n\n\tstatic std::vector<size_t> queryNeighbors(UpdateParamet"
  },
  {
    "path": "GalaxyEngine/include/Particles/clusterMouseHelper.h",
    "chars": 1159,
    "preview": "#pragma once\n\n#include \"Physics/quadtree.h\"\n\nstruct ClusterHelper {\n\tstatic void clusterMouseHelper(Camera3D& cam3D, flo"
  },
  {
    "path": "GalaxyEngine/include/Particles/densitySize.h",
    "chars": 2663,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct DensitySize {\n\n\tfloat minSize = 0.17f;\n\tfloat maxSize = 0.62f;\n\n\tf"
  },
  {
    "path": "GalaxyEngine/include/Particles/neighborSearch.h",
    "chars": 34725,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct NeighborSearch {\n\n\tstd::vector<uint32_t> globalNeighborList;\n\n\tflo"
  },
  {
    "path": "GalaxyEngine/include/Particles/particle.h",
    "chars": 7312,
    "preview": "#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:"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleColorVisuals.h",
    "chars": 19081,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Physics/materialsSPH.h\"\n\nstruct ColorVisuals {\n\n\tbool solidColor"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleDeletion.h",
    "chars": 4209,
    "preview": "#pragma once\n\n#include \"IO/io.h\"\n\n#include \"Particles/particle.h\"\n\nstruct ParticleDeletion {\n\n\tbool deleteSelection = fa"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleSelection.h",
    "chars": 1873,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleTrails.h\"\n\n#include \"UX/camera.h\"\n\nstruct Upda"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleSpaceship.h",
    "chars": 16772,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Physics/materialsSPH.h\"\n#include \"IO/io.h\"\n\nclass ParticleSpaces"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleSubdivision.h",
    "chars": 625,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\nstruct ParticleSubdivis"
  },
  {
    "path": "GalaxyEngine/include/Particles/particleTrails.h",
    "chars": 1142,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct UpdateVariables;\nstruct UpdateParameters;\n\n\nclass ParticleTrails {"
  },
  {
    "path": "GalaxyEngine/include/Particles/particlesSpawning.h",
    "chars": 1765,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Physics/slingshot.h\"\n\n#include \"Physics/constraint.h\"\n\n#include"
  },
  {
    "path": "GalaxyEngine/include/Physics/SPH.h",
    "chars": 17485,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"parameters.h\"\n#include \"Particles/QueryNeighbors.h\"\n\nstruct Upd"
  },
  {
    "path": "GalaxyEngine/include/Physics/SPH3D.h",
    "chars": 16634,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"parameters.h\"\n#include \"Particles/QueryNeighbors.h\"\n\nstruct Upd"
  },
  {
    "path": "GalaxyEngine/include/Physics/constraint.h",
    "chars": 231,
    "preview": "#pragma once\n\nstruct ParticleConstraint {\n\tuint32_t id1;\n\tuint32_t id2;\n\tfloat restLength;\n\tfloat originalLength;\n\tfloat"
  },
  {
    "path": "GalaxyEngine/include/Physics/field.h",
    "chars": 12725,
    "preview": "#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 isAc"
  },
  {
    "path": "GalaxyEngine/include/Physics/light.h",
    "chars": 40387,
    "preview": "#pragma once\n\n#include \"UX/randNum.h\"\n#include \"IO/io.h\"\n#include \"parameters.h\"\n\nextern uint32_t globalWallId;\n\nstruct "
  },
  {
    "path": "GalaxyEngine/include/Physics/materialsSPH.h",
    "chars": 7454,
    "preview": "#pragma once\n\nstruct SPHMaterial {\n\n\tuint32_t id = 0;\n\tstd::string sphLabel = \"material\";\n\n\t// Base values\n\tfloat massMu"
  },
  {
    "path": "GalaxyEngine/include/Physics/morton.h",
    "chars": 1140,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\nstruct Morton {\n\n\tstd::vector<size_t> indicesBuffer;\n\tstd::vector<Particl"
  },
  {
    "path": "GalaxyEngine/include/Physics/physics.h",
    "chars": 4461,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Particles/QueryNeighbors.h\"\n\n#include \"Physics/quadtree.h\"\n\n#in"
  },
  {
    "path": "GalaxyEngine/include/Physics/physics3D.h",
    "chars": 2794,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Physics/quadtree.h\"\n\n#include \"parameters.h\"\n\n#include \"Particl"
  },
  {
    "path": "GalaxyEngine/include/Physics/quadtree.h",
    "chars": 4201,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\n// --- UNUSED QUADTREE FOR MORTON KEYS VERSION --- //\n//struct Node {\n//\t"
  },
  {
    "path": "GalaxyEngine/include/Physics/slingshot.h",
    "chars": 447,
    "preview": "#pragma once\n\n#include \"UX/camera.h\"\n\nstruct UpdateVariables;\n\nclass Slingshot {\npublic:\n\tglm::vec2 norm;\n\tfloat length;"
  },
  {
    "path": "GalaxyEngine/include/Renderer/rayMarching.h",
    "chars": 17276,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"UX/camera.h\"\n#include \"UI/UI.h\"\n#include \"Physics/quadtree.h\"\n\ns"
  },
  {
    "path": "GalaxyEngine/include/Sound/sound.h",
    "chars": 1235,
    "preview": "#pragma once\n\nstruct GESound {\n\n\tstatic Sound intro;\n\n\tstatic Sound soundButtonHover1;\n\tstatic Sound soundButtonHover2;\n"
  },
  {
    "path": "GalaxyEngine/include/UI/UI.h",
    "chars": 2841,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Physics/quadtree.h\"\n#include \"Physics/SPH.h\"\n#include \"Physics/"
  },
  {
    "path": "GalaxyEngine/include/UI/brush.h",
    "chars": 2241,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n\n#include \"Physics/materialsSPH.h\"\n\n#include \"Physics/quadtree.h\"\n\n#includ"
  },
  {
    "path": "GalaxyEngine/include/UI/controls.h",
    "chars": 3498,
    "preview": "#pragma once\n\nstruct UpdateVariables;\nclass UI;\n\nstruct Controls {\n\n\n\tbool isShowControlsEnabled = false;\n\n\tbool isInfor"
  },
  {
    "path": "GalaxyEngine/include/UI/rightClickSettings.h",
    "chars": 1142,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleSubdivision.h\"\n#include \"Particles/particleSel"
  },
  {
    "path": "GalaxyEngine/include/UX/camera.h",
    "chars": 4764,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleTrails.h\"\n\nclass SceneCamera {\npublic:\n\tCamera"
  },
  {
    "path": "GalaxyEngine/include/UX/copyPaste.h",
    "chars": 16084,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Physics/physics.h\"\n#include \"Physics/light.h\"\n\nstruct CopyPaste "
  },
  {
    "path": "GalaxyEngine/include/UX/randNum.h",
    "chars": 37,
    "preview": "#pragma once\n\nfloat getRandomFloat();"
  },
  {
    "path": "GalaxyEngine/include/UX/saveSystem.h",
    "chars": 24618,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Physics/light.h\"\n#include \"Physics/field.h\"\n\n#include \"Physics/S"
  },
  {
    "path": "GalaxyEngine/include/UX/screenCapture.h",
    "chars": 1811,
    "preview": "#pragma once\n\nstruct AVFormatContext;\nstruct AVCodecContext;\nstruct AVStream;\nstruct SwsContext;\nstruct AVFrame;\n\nstruct"
  },
  {
    "path": "GalaxyEngine/include/globalLogic.h",
    "chars": 2347,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleTrails.h\"\n#include \"Particles/particleSelectio"
  },
  {
    "path": "GalaxyEngine/include/parameters.h",
    "chars": 8288,
    "preview": "#pragma once\n\n#include \"Particles/particle.h\"\n#include \"Particles/particleSubdivision.h\"\n#include \"Particles/densitySize"
  },
  {
    "path": "GalaxyEngine/include/pch.h",
    "chars": 867,
    "preview": "#pragma once\n\n// C++ stdlib\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n#include <string>\n#inclu"
  },
  {
    "path": "GalaxyEngine/src/Particles/particleSelection.cpp",
    "chars": 18501,
    "preview": "#include \"Particles/particleSelection.h\"\n\n#include \"parameters.h\"\n\nParticleSelection::ParticleSelection() {\n}\n\nvoid Part"
  },
  {
    "path": "GalaxyEngine/src/Particles/particleSubdivision.cpp",
    "chars": 10433,
    "preview": "#include \"Particles/particleSubdivision.h\"\n\n#include \"parameters.h\"\n\nvoid ParticleSubdivision::subdivideParticles(Update"
  },
  {
    "path": "GalaxyEngine/src/Particles/particleTrails.cpp",
    "chars": 14048,
    "preview": "#include \"Particles/particleTrails.h\"\n\n#include \"parameters.h\"\n\nvoid ParticleTrails::trailLogic(UpdateVariables& myVar, "
  },
  {
    "path": "GalaxyEngine/src/Particles/particlesSpawning.cpp",
    "chars": 30348,
    "preview": "#include \"Particles/particlesSpawning.h\"\n\n#include \"Physics/physics.h\"\n#include \"Physics/physics3D.h\"\n#include \"Physics/"
  },
  {
    "path": "GalaxyEngine/src/Physics/SPH.cpp",
    "chars": 8836,
    "preview": "#include \"Physics/SPH.h\"\n\nvoid SPH::flattenParticles(std::vector<ParticlePhysics>& pParticles) {\n\tsize_t particleCount ="
  },
  {
    "path": "GalaxyEngine/src/Physics/SPH3D.cpp",
    "chars": 9935,
    "preview": "#include \"Physics/SPH3D.h\"\n\nvoid SPH3D::flattenParticles(std::vector<ParticlePhysics>& pParticles) {\n\tsize_t particleCou"
  },
  {
    "path": "GalaxyEngine/src/Physics/light.cpp",
    "chars": 62736,
    "preview": "#include \"Physics/light.h\"\n\nvoid Lighting::createWall(UpdateVariables& myVar, UpdateParameters& myParam) {\n\n\tglm::vec2 m"
  },
  {
    "path": "GalaxyEngine/src/Physics/morton.cpp",
    "chars": 4271,
    "preview": "#include \"Physics/morton.h\"\n\nuint64_t Morton::scaleToGrid(float pos, float minVal, float maxVal) {\n    if (maxVal <= min"
  },
  {
    "path": "GalaxyEngine/src/Physics/physics.cpp",
    "chars": 39605,
    "preview": "#include \"Physics/physics.h\"\n\n// This is used in predict trajectory inside particleSpawning.cpp\nglm::vec2 Physics::calcu"
  },
  {
    "path": "GalaxyEngine/src/Physics/physics3D.cpp",
    "chars": 36466,
    "preview": "#include \"Physics/physics3D.h\"\n\nvoid Physics3D::flattenParticles3D(std::vector<ParticlePhysics3D>& pParticles3D) {\n\tsize"
  },
  {
    "path": "GalaxyEngine/src/Physics/quadtree.cpp",
    "chars": 8395,
    "preview": "#include \"Particles/particle.h\"\n\n#include \"Physics/quadtree.h\"\n\nNode::Node(glm::vec2 pos, float size,\n\tuint32_t startInd"
  },
  {
    "path": "GalaxyEngine/src/Physics/slingshot.cpp",
    "chars": 2858,
    "preview": "#include \"IO/io.h\"\n\n#include \"Physics/slingshot.h\"\n#include \"parameters.h\"\n\nglm::vec2 slingshotPos = { 0.0f, 0.0f };\n\nSl"
  },
  {
    "path": "GalaxyEngine/src/Sound/sound.cpp",
    "chars": 4339,
    "preview": "#include \"Sound/sound.h\"\n\nSound GESound::intro;\nSound GESound::soundButtonHover1;\nSound GESound::soundButtonHover2;\nSoun"
  },
  {
    "path": "GalaxyEngine/src/UI/UI.cpp",
    "chars": 94035,
    "preview": "#include \"UI/UI.h\"\n\nvoid UI::uiLogic(UpdateParameters& myParam, UpdateVariables& myVar, SPH& sph, SaveSystem& save, GESo"
  },
  {
    "path": "GalaxyEngine/src/UI/brush.cpp",
    "chars": 48301,
    "preview": "#include \"UI/brush.h\"\n\n#include \"parameters.h\"\n\nstruct SPHWater water;\nstruct SPHRock rock;\nstruct SPHIron iron;\nstruct "
  },
  {
    "path": "GalaxyEngine/src/UI/controls.cpp",
    "chars": 3229,
    "preview": "#include \"UI/controls.h\"\n#include \"UI/UI.h\"\n#include \"parameters.h\"\n\nvoid Controls::showControls() {\n    if (isShowContr"
  },
  {
    "path": "GalaxyEngine/src/UI/rightClickSettings.cpp",
    "chars": 12487,
    "preview": "#include \"UI/rightClickSettings.h\"\n#include \"UI/UI.h\"\n\n#include \"parameters.h\"\n\nvoid RightClickSettings::rightClickMenuS"
  },
  {
    "path": "GalaxyEngine/src/UX/camera.cpp",
    "chars": 12328,
    "preview": "#include \"UX/camera.h\"\n\n#include \"parameters.h\"\n\nSceneCamera::SceneCamera() {\n\tcamera.offset = { 0.0f, 0.0f };\n\tcamera.t"
  },
  {
    "path": "GalaxyEngine/src/UX/randNum.cpp",
    "chars": 209,
    "preview": "#include \"UX/randNum.h\"\n\nfloat getRandomFloat() {\n    static std::random_device rd;\n    static std::mt19937 gen(rd());\n "
  },
  {
    "path": "GalaxyEngine/src/UX/saveSystem.cpp",
    "chars": 39155,
    "preview": "#include \"UX/saveSystem.h\"\n\nvoid SaveSystem::saveSystem(const std::string& filename, UpdateVariables& myVar, UpdateParam"
  },
  {
    "path": "GalaxyEngine/src/UX/screenCapture.cpp",
    "chars": 33084,
    "preview": "#include \"UI/UI.h\"\n\n#include \"UX/screenCapture.h\"\n\n#include \"parameters.h\"\n\nextern \"C\" {\n#include <libavcodec/avcodec.h>"
  },
  {
    "path": "GalaxyEngine/src/globalLogic.cpp",
    "chars": 80683,
    "preview": "#include \"globalLogic.h\"\n\nUpdateParameters myParam;\nUpdateVariables myVar;\nUI myUI;\nPhysics physics;\nPhysics3D physics3D"
  },
  {
    "path": "GalaxyEngine/src/main.cpp",
    "chars": 16617,
    "preview": "#include \"globalLogic.h\"\n\n#if defined(PLATFORM_DESKTOP)\n#define GLSL_VERSION            330\n#else   // PLATFORM_ANDROID,"
  },
  {
    "path": "GalaxyEngine/src/parameters.cpp",
    "chars": 2175,
    "preview": "#include \"parameters.h\"\n\n// Background color\nImVec4 UpdateVariables::colWindowBg = ImVec4(0.05f, 0.043f, 0.071f, 0.9f);\n"
  },
  {
    "path": "GalaxyEngine/src/resources.rc",
    "chars": 38,
    "preview": "IDI_ICON1 ICON \"GalaxyEngineIcon.ico\"\n"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2025 Narcis Calin\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 4496,
    "preview": "# Galaxy Engine\n\n## About\n\nJoin Galaxy Engine's [Discord community!](https://discord.gg/Xd5JUqNFPM)\n\nCheck out the offic"
  },
  {
    "path": "cmake/ffmpeg.cmake",
    "chars": 752,
    "preview": "set(FFMPEG_URL_BASE https://github.com/BtbN/FFmpeg-Builds/releases/download/latest)\n\nif(WIN32)\n    set(FFMPEG_ARCHIVE ff"
  },
  {
    "path": "cmake/glm.cmake",
    "chars": 365,
    "preview": "set(GLM_VERSION 1.0.1)\nset(GLM_URL_BASE https://github.com/g-truc/glm/releases/download)\n\nFetchContent_Declare(glm-fetch"
  },
  {
    "path": "cmake/imgui.cmake",
    "chars": 1229,
    "preview": "FetchContent_Declare(\n    imgui-fetch\n    GIT_REPOSITORY https://github.com/ocornut/imgui.git\n    GIT_TAG        docking"
  },
  {
    "path": "cmake/openmp.cmake",
    "chars": 744,
    "preview": "if(${CMAKE_VERSION} LESS 3.30 AND ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} STREQUAL MSVC)\n\tadd_library(openmp INTERFACE)\n\n"
  },
  {
    "path": "cmake/raylib.cmake",
    "chars": 383,
    "preview": "set(RL_VERSION 5.5)\nset(RL_URL_BASE https://github.com/raysan5/raylib/releases/download)\n\nFetchContent_Declare(raylib\nGI"
  },
  {
    "path": "cmake/yaml.cmake",
    "chars": 175,
    "preview": "include(FetchContent)\n\nFetchContent_Declare(\n  yaml-cpp\n  GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git\n  GIT_TA"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the NarcisCalin/Galaxy-Engine GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 84 files (898.3 KB), approximately 277.3k tokens, and a symbol index with 222 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!