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("$<$:/Od;/RTC1;/MDd>") add_compile_options("$<$:/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$<$: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 $ ) 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] // 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 #include #include #include // 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] \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 queryNeighbors(UpdateParameters& myParam, bool& hasAVX2, size_t reserveAmount, glm::vec2& pos) { std::vector 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 queryNeighbors3D(UpdateParameters& myParam, bool& hasAVX2, size_t reserveAmount, glm::vec3& pos) { std::vector 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::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(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& pParticles, std::vector& rParticles, std::vector& pParticles3D, std::vector& 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 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(pow(normalDensity, 2))); } } else { std::vector 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(pow(normalDensity, 2))); } } } } }; ================================================ FILE: GalaxyEngine/include/Particles/neighborSearch.h ================================================ #pragma once #include "Particles/particle.h" struct NeighborSearch { std::vector globalNeighborList; float originalDensityRadius = 3.5f; float densityRadius = originalDensityRadius; float cellSize = 3.0f; std::vector cellCounts; std::vector cellStart; std::vector cellParticles; std::vector cellXList; std::vector cellYList; std::vector idToIndexTable; void calculateGridBounds(const std::vector& pParticles, const std::vector& activeIndices, int& gridWidth, int& gridHeight, float& minX, float& minY) { float maxX = std::numeric_limits::lowest(); float maxY = std::numeric_limits::lowest(); minX = std::numeric_limits::max(); minY = std::numeric_limits::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((maxX - minX) / cellSize) + 1); gridHeight = std::max(1, static_cast((maxY - minY) / cellSize) + 1); } void UpdateNeighbors(std::vector& pParticles, std::vector& rParticles) { if (pParticles.empty()) return; float densityRadiusSq = densityRadius * densityRadius; std::vector 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((pos.x - minX) / cellSize); int cy = static_cast((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 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 globalNeighborList3D; float originalDensityRadius = 3.5f; float densityRadius = originalDensityRadius; float cellSize = 3.5f; std::vector cellCounts; std::vector cellStart; std::vector cellParticles; std::vector cellXList; std::vector cellYList; std::vector cellZList; std::vector idToIndexTable; void calculateGridBounds(const std::vector& pParticles, const std::vector& activeIndices, int& gridWidth, int& gridHeight, int& gridDepth, float& minX, float& minY, float& minZ) { float maxX = std::numeric_limits::lowest(); float maxY = std::numeric_limits::lowest(); float maxZ = std::numeric_limits::lowest(); minX = std::numeric_limits::max(); minY = std::numeric_limits::max(); minZ = std::numeric_limits::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((maxX - minX) / cellSize) + 1); gridHeight = std::max(1, static_cast((maxY - minY) / cellSize) + 1); gridDepth = std::max(1, static_cast((maxZ - minZ) / cellSize) + 1); } void UpdateNeighbors(std::vector& pParticles, std::vector& rParticles) { if (pParticles.empty()) return; float densityRadiusSq = densityRadius * densityRadius; std::vector 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((pos.x - minX) / cellSize); int cy = static_cast((pos.y - minY) / cellSize); int cz = static_cast((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 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 cellKeys; std::vector particleIndices; std::vector posX; std::vector posY; std::vector cellXs; std::vector cellYs; size_t size; }; const uint32_t hashTableSize = 16384; EntryArrays entries; std::vector countBuffer; std::vector offsetBuffer; std::vector 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& 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 tempKeys; static std::vector tempIndices; static std::vector 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 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& pParticles, std::vector& 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 cellKeys; std::vector particleIndices; std::vector cellXs; std::vector cellYs; size_t size = 0; }; EntryArrays entries; std::vector startIndices; std::vector cellCounts; NeighborSearchV2AVX2() { startIndices.resize(hashTableSize); cellCounts.resize(hashTableSize); } std::pair posToCellCoord(const glm::vec2& pos) { int cellX = static_cast(std::floor(pos.x / cellSize)); int cellY = static_cast(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& 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 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& 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& pParticles, std::vector& rParticles) { #pragma omp parallel { std::vector 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 cellKeys; std::vector particleIndices; std::vector posX; std::vector posY; std::vector posZ; std::vector cellXs; std::vector cellYs; std::vector cellZs; size_t size = 0; }; const uint32_t hashTableSize = 32768; EntryArrays entries; std::vector countBuffer; std::vector offsetBuffer; std::vector tempKeys; std::vector tempIndices; std::vector tempXs; std::vector tempYs; std::vector 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& 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 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& pParticles, std::vector& 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 cellKeys; std::vector particleIndices; std::vector cellXs; std::vector cellYs; std::vector cellZs; size_t size = 0; }; EntryArrays entries; std::vector startIndices; std::vector cellCounts; NeighborSearch3DV2AVX2() { startIndices.resize(hashTableSize); cellCounts.resize(hashTableSize); } std::tuple posToCellCoord(const glm::vec3& pos) { int cellX = static_cast(std::floor(pos.x / cellSize)); int cellY = static_cast(std::floor(pos.y / cellSize)); int cellZ = static_cast(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& 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 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& 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& pParticles, std::vector& rParticles) { #pragma omp parallel { std::vector 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(std::clamp(rC, 0.0f, 255.0f)); g = static_cast(std::clamp(gC, 0.0f, 255.0f)); b = static_cast(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& pParticles, std::vector& rParticles, std::vector& pParticles3D, std::vector& 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(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(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& pParticles, std::vector& rParticles, std::vector& pParticles3D, std::vector& 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& pParticles, std::vector& rParticles, bool& isSPHEnabled, std::vector& pParticles3D, std::vector& 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 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 newPParticles; std::vector 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 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 newPParticles; std::vector 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& rParticles); void deselection(std::vector& 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& rParticles); void deselection(std::vector& 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& pParticles, std::vector& 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(rand()) / static_cast(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(rand()) / static_cast(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(rand()) / static_cast(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(rand()) / static_cast(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& pParticles, std::vector& 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(rand()) / static_cast(RAND_MAX); float normalRand2 = static_cast(rand()) / static_cast(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(rand()) / static_cast(RAND_MAX); float normalRand2 = static_cast(rand()) / static_cast(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(rand()) / static_cast(RAND_MAX); float normalRand2 = static_cast(rand()) / static_cast(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(rand()) / static_cast(RAND_MAX); float normalRand2 = static_cast(rand()) / static_cast(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(rand()) / static_cast(RAND_MAX); float normalRand2 = static_cast(rand()) / static_cast(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(rand()) / static_cast(RAND_MAX); float normalRand2 = static_cast(rand()) / static_cast(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 segments; std::vector 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& rParticles, Texture2D& particleBlur); void trailLogic3D(UpdateVariables& myVar, UpdateParameters& myParam); void drawTrail3D(std::vector& 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& 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& 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 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& pParticles, std::vector& rParticles, std::vector& forces, const std::vector>& 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& 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 cellKeys; std::vector particleIndices; std::vector cellXs; std::vector cellYs; size_t size; }; const uint32_t hashTableSize = 16384; EntryArrays entries; std::vector 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 pPos; void gpuNeighborSearch(std::vector& 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(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(groupWidth)); glUniform1ui(glGetUniformLocation(bitonicSortProgram, "groupHeight"), static_cast(groupHeight)); glUniform1ui(glGetUniformLocation(bitonicSortProgram, "stepIndex"), static_cast(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& 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 posX; std::vector posY; std::vector predPosX; std::vector predPosY; std::vector accX; std::vector accY; std::vector velX; std::vector velY; std::vector prevVelX; std::vector prevVelY; std::vector sphMass; std::vector press; std::vector pressFX; std::vector pressFY; std::vector stiff; std::vector visc; std::vector dens; std::vector predDens; std::vector restDens; void flattenParticles(std::vector& pParticles); void readFlattenBack(std::vector& pParticles); void computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector& sphForce, size_t& N); void groundModeBoundary(std::vector& pParticles, std::vector& 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 cellKeys; std::vector particleIndices; std::vector cellXs; std::vector cellYs; size_t size; }; const uint32_t hashTableSize = 16384; EntryArrays entries; std::vector 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 pPos; void gpuNeighborSearch(std::vector& 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(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(groupWidth)); glUniform1ui(glGetUniformLocation(bitonicSortProgram, "groupHeight"), static_cast(groupHeight)); glUniform1ui(glGetUniformLocation(bitonicSortProgram, "stepIndex"), static_cast(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& 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 posX; std::vector posY; std::vector predPosX; std::vector predPosY; std::vector accX; std::vector accY; std::vector velX; std::vector velY; std::vector prevVelX; std::vector prevVelY; std::vector sphMass; std::vector press; std::vector pressFX; std::vector pressFY; std::vector stiff; std::vector visc; std::vector dens; std::vector predDens; std::vector restDens; void flattenParticles(std::vector& pParticles); void readFlattenBack(std::vector& pParticles); void computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector& sphForce, size_t& N); void groundModeBoundary(std::vector& pParticles, std::vector& 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 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(res); } else { cellSize = myVar.domainSize.x / static_cast(res); } amountX = static_cast(myVar.domainSize.x / cellSize); amountY = static_cast(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 particlesData; std::vector particlesMassVector; std::vector 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(myParam.colorVisuals.pColor.r / 255.0f), static_cast(myParam.colorVisuals.pColor.g / 255.0f), static_cast(myParam.colorVisuals.pColor.b / 255.0f) }; float highColor[3] = { static_cast(myParam.colorVisuals.sColor.r / 255.0f), static_cast(myParam.colorVisuals.sColor.g / 255.0f), static_cast(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* walls; std::vector myWallIds; std::vector 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* 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& walls, uint32_t id) { for (Wall& wall : walls) { if (wall.id == id) return &wall; } return nullptr; } float getSignedArea(const std::vector& 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& vertices, int& shapeRelaxiter, float& shapeRelaxFactor) { if (vertices.size() < 3) return; std::vector 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 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 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(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::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::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 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(i) / lensSegments; float t2 = static_cast((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(i) / lensSegments; float t2Symmetry = static_cast((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 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& 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& 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, 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 coneLightLogic(int& sampleRaysAmount, std::vector& 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; glm::vec2 direction = glm::normalize(vB - vA); direction = rotateVec2(dNormal, randAngle); rays.emplace_back( vA, direction, 1, color ); } } }; struct AABB2D { glm::vec2 min; glm::vec2 max; AABB2D() : min(0), max(0) {} AABB2D(glm::vec2 a, glm::vec2 b) { min = glm::min(a, b); max = glm::max(a, b); } void expand(const AABB2D& other) { min = glm::min(min, other.min); max = glm::max(max, other.max); } bool intersectsRay(const glm::vec2& origin, const glm::vec2& dir, float maxDist = FLT_MAX) const { const float epsilon = 1e-8f; glm::vec2 invDir; invDir.x = (std::abs(dir.x) < epsilon) ? (dir.x >= 0 ? 1e8f : -1e8f) : 1.0f / dir.x; invDir.y = (std::abs(dir.y) < epsilon) ? (dir.y >= 0 ? 1e8f : -1e8f) : 1.0f / dir.y; glm::vec2 t0s = (min - origin) * invDir; glm::vec2 t1s = (max - origin) * invDir; glm::vec2 tMin = glm::min(t0s, t1s); glm::vec2 tMax = glm::max(t0s, t1s); float tEntry = std::max(tMin.x, tMin.y); float tExit = std::min(tMax.x, tMax.y); return tExit >= 0 && tEntry <= tExit && tEntry <= maxDist; } }; struct BVHNode { AABB2D bounds; BVHNode* left = nullptr; BVHNode* right = nullptr; Wall* wall = nullptr; bool isLeaf() const { return wall != nullptr; } }; class BVH { public: BVHNode* root = nullptr; BVH() = default; ~BVH() { destroyBVH(root); } void build(std::vector& walls) { destroyBVH(root); root = nullptr; if (walls.empty()) { return; } root = buildBVH(walls, 0, walls.size(), 0); } bool traverse(const LightRay& ray, float& closestT, Wall*& hitWall, glm::vec2& hitPoint) { if (!root) return false; return traverseBVH(root, ray, closestT, hitWall, hitPoint); } private: void destroyBVH(BVHNode* node) { if (!node) return; destroyBVH(node->left); destroyBVH(node->right); delete node; } BVHNode* buildBVH(std::vector& walls, int start, int end, int depth) { if (start >= end) return nullptr; BVHNode* node = new BVHNode{}; AABB2D bounds = AABB2D(walls[start]->vA, walls[start]->vB); for (int i = start + 1; i < end; ++i) { AABB2D wallBox(walls[i]->vA, walls[i]->vB); bounds.expand(wallBox); } node->bounds = bounds; int count = end - start; if (count <= 1 || depth >= 20) { node->wall = walls[start]; return node; } glm::vec2 extent = bounds.max - bounds.min; int axis = extent.x > extent.y ? 0 : 1; std::sort(walls.begin() + start, walls.begin() + end, [axis](Wall* a, Wall* b) { float midA = (a->vA[axis] + a->vB[axis]) * 0.5f; float midB = (b->vA[axis] + b->vB[axis]) * 0.5f; return midA < midB; }); int mid = start + count / 2; if (mid == start) mid = start + 1; if (mid >= end) mid = end - 1; node->left = buildBVH(walls, start, mid, depth + 1); node->right = buildBVH(walls, mid, end, depth + 1); return node; } bool intersectWall(const LightRay& ray, const Wall& wall, float& t, glm::vec2& hitPoint) { glm::vec2 p = ray.source; glm::vec2 r = ray.dir; glm::vec2 q = wall.vA; glm::vec2 s = wall.vB - wall.vA; float rxs = r.x * s.y - r.y * s.x; const float epsilon = 1e-8f; if (std::abs(rxs) < epsilon) return false; glm::vec2 qp = q - p; float t1 = (qp.x * s.y - qp.y * s.x) / rxs; float t2 = (qp.x * r.y - qp.y * r.x) / rxs; if (t1 >= 0 && t1 <= ray.maxLength && t2 >= 0.0f && t2 <= 1.0f) { t = t1; hitPoint = p + t * r; return true; } return false; } bool traverseBVH(const BVHNode* root, const LightRay& ray, float& closestT, Wall*& hitWall, glm::vec2& hitPoint) { if (!root) return false; std::stack stack; stack.push(root); bool hit = false; while (!stack.empty()) { const BVHNode* node = stack.top(); stack.pop(); if (!node->bounds.intersectsRay(ray.source, ray.dir, closestT)) continue; if (node->isLeaf()) { float t; glm::vec2 pt; if (intersectWall(ray, *node->wall, t, pt) && t < closestT) { closestT = t; hitWall = node->wall; hitPoint = pt; hit = true; } } else { if (node->right) stack.push(node->right); if (node->left) stack.push(node->left); } } return hit; } }; struct Lighting { std::vector walls; std::vector wallPointers; std::vector shapes; std::vector rays; std::vector pointLights; std::vector areaLights; std::vector coneLights; size_t firstPassTotalRays = 0; BVH bvh; int sampleRaysAmount = 1024; int maxBounces = 3; int maxSamples = 500; int currentSamples = 0; bool isDiffuseEnabled = true; bool isSpecularEnabled = true; bool isRefractionEnabled = true; bool isDispersionEnabled = true; bool isEmissionEnabled = true; bool symmetricalLens = false; bool shouldRender = true; bool drawNormals = false; bool relaxMove = false; const float lightBias = 0.1f; Color lightColor = { 255, 255, 255, 64 }; Color wallBaseColor = { 200, 200, 200, 255 }; Color wallSpecularColor = { 255, 255, 255, 255 }; Color wallRefractionColor = { 255, 255, 255, 255 }; Color wallEmissionColor = { 255, 255, 255, 0 }; float lightGain = 0.25f; float lightSpread = 0.85f; float wallSpecularRoughness = 0.5f; float wallRefractionRoughness = 0.0f; // This controls the roughness of the refraction surface. I separate it for extra control, like V-Ray renderer float wallRefractionAmount = 0.0f; float wallIOR = 1.5f; float airIOR = 1.0f; float wallDispersion = 0.0f; float wallEmissionGain = 0.0f; int shapeRelaxIter = 15; float shapeRelaxFactor = 0.65f; float absorptionInvBias = 0.99f; Wall* getWallById(std::vector& walls, uint32_t id) { for (Wall& wall : walls) { if (wall.id == id) return &wall; } return nullptr; } void calculateWallNormal(Wall& wall) { Wall& w = wall; glm::vec2 tangent = glm::normalize(w.vB - w.vA); glm::vec2 normal = glm::vec2(-tangent.y, tangent.x); w.normal = normal; w.normalVA = normal; w.normalVB = normal; } void createWall(UpdateVariables& myVar, UpdateParameters& myParam); bool firstHelper = true; bool isCreatingLens = false; float minHelperLength = FLT_MAX; int selectedHelper = -1; size_t selectedShape = -1; const float helperMinDist = 100.0f; void createShape(UpdateVariables& myVar, UpdateParameters& myParam); void createPointLight(UpdateVariables& myVar, UpdateParameters& myParam); void createAreaLight(UpdateVariables& myVar, UpdateParameters& myParam); void createConeLight(UpdateVariables& myVar, UpdateParameters& myParam); void movePointLights(UpdateVariables& myVar, UpdateParameters& myParam); void moveAreaLights(UpdateVariables& myVar, UpdateParameters& myParam); void moveConeLights(UpdateVariables& myVar, UpdateParameters& myParam); void moveWalls(UpdateVariables& myVar, UpdateParameters& myParam); bool isAnyShapeBeingSpawned = false; void moveLogic(UpdateVariables& myVar, UpdateParameters& myParam); void eraseLogic(UpdateVariables& myVar, UpdateParameters& myParam); float lightGainAvg = 0.0f; bool isSliderLightGain = false; float lightSpreadAvg = 0.0f; bool isSliderlightSpread = false; Color lightColorAvg = { 0, 0, 0, 0 }; bool isSliderLightColor = false; Color baseColorAvg = { 0, 0, 0, 0 }; bool isSliderBaseColor = false; Color specularColorAvg = { 0, 0, 0, 0 }; bool isSliderSpecularColor = false; Color refractionColorAvg = { 0, 0, 0, 0 }; bool isSliderRefractionCol = false; Color emissionColorAvg = { 0, 0, 0, 0 }; bool isSliderEmissionCol = false; float specularRoughAvg = 0.0f; bool isSliderSpecularRough = false; float refractionRoughAvg = 0.0f; bool isSliderRefractionRough = false; float refractionAmountAvg = 0.0f; bool isSliderRefractionAmount = false; float iorAvg = 0.0f; bool isSliderIor = false; float dispersionAvg = 0.0f; bool isSliderDispersion = false; float emissionGainAvg = 0.0f; bool isSliderEmissionGain = false; // Add the UI bools for optics in here std::vector uiOpticElements = { &isSliderLightGain, &isSliderlightSpread, &isSliderLightColor, &isSliderBaseColor, &isSliderSpecularColor, &isSliderRefractionCol, &isSliderEmissionCol, &isSliderSpecularRough, &isSliderRefractionRough, &isSliderRefractionAmount, &isSliderIor, &isSliderDispersion, &isSliderEmissionGain }; glm::vec2 boxInitialPos{ 0.0f, 0.0f }; bool isBoxSelecting = false; bool isBoxDeselecting = false; float boxX = 0.0f; float boxY = 0.0f; float boxWidth = 0.0f; float boxHeight = 0.0f; int selectedWalls = 0; int selectedLights = 0; void selectLogic(UpdateVariables& myVar, UpdateParameters& myParam); float checkIntersect(const LightRay& ray, const Wall& w); void processRayIntersection(LightRay& ray); 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 specularReflection(int& currentBounce, LightRay& ray, std::vector& copyRays, std::vector& walls); void refraction(int& currentBounce, LightRay& ray, std::vector& copyRays, std::vector& walls); // Currently unfinished and unused. Might work on it some time in the future //void volumeScatter(int& currentBounce, LightRay& ray, std::vector& copyRays, std::vector& walls); void diffuseLighting(int& currentBounce, LightRay& ray, std::vector& copyRays, std::vector& walls); void emission(); void lightRendering(UpdateParameters& myParam); void drawRays() { if (currentSamples <= maxSamples) { for (LightRay& ray : rays) { ray.drawRay(); } } } void drawScene() { for (Wall& wall : walls) { wall.drawWall(); if (drawNormals) { DrawLineV({ wall.vA.x, wall.vA.y }, { wall.vA.x + wall.normalVA.x * 10.0f, wall.vA.y + wall.normalVA.y * 10.0f }, RED); DrawLineV({ wall.vB.x, wall.vB.y }, { wall.vB.x + wall.normalVB.x * 10.0f, wall.vB.y + wall.normalVB.y * 10.0f }, RED); DrawLineV({ (wall.vA.x + wall.vB.x) * 0.5f, (wall.vA.y + wall.vB.y) * 0.5f }, { (wall.vA.x + wall.vB.x) * 0.5f + wall.normal.x * 10.0f, (wall.vA.y + wall.vB.y) * 0.5f + wall.normal.y * 10.0f }, RED); } } for (AreaLight& areaLight : areaLights) { areaLight.drawAreaLight(); } } void drawMisc(UpdateVariables& myVar, UpdateParameters& myParam); inline Color glmVec4ToColor(const glm::vec4& v) { float r = glm::clamp(v.x, 0.0f, 1.0f); float g = glm::clamp(v.y, 0.0f, 1.0f); float b = glm::clamp(v.z, 0.0f, 1.0f); float a = glm::clamp(v.w, 0.0f, 1.0f); return Color{ static_cast(r * 255.0f), static_cast(g * 255.0f), static_cast(b * 255.0f), static_cast(a * 255.0f) }; } inline glm::vec4 ColorToGlmVec4(const Color& c) { return glm::vec4{ static_cast(c.r) / 255.0f, static_cast(c.g) / 255.0f, static_cast(c.b) / 255.0f, static_cast(c.a) / 255.0f }; } void processApparentColor() { for (Wall& w : walls) { if (w.isSelected) { continue; } float emissionGain = static_cast(w.emissionColor.a) / 255.0f; float baseWeight = 1.0f - w.refractionAmount; float specularWeight = 0.2f * w.IOR * (1.0f - w.refractionAmount * 0.5f); glm::vec4 baseCol = { 0.0f, 0.0f, 0.0f, 0.0f }; baseCol += ColorToGlmVec4(w.baseColor) * baseWeight; baseCol += ColorToGlmVec4(w.specularColor) * specularWeight; baseCol += ColorToGlmVec4(w.refractionColor) * w.refractionAmount * 0.6f; float totalWeight = baseWeight + specularWeight + w.refractionAmount; if (totalWeight > 0.0f) { baseCol /= totalWeight; } glm::vec4 emissionVec = ColorToGlmVec4(w.emissionColor); glm::vec4 finalCol = glm::mix(baseCol, emissionVec, emissionGain); w.apparentColor = glmVec4ToColor(finalCol); } for (AreaLight& al : areaLights) { al.apparentColor = al.color; } } int accumulatedRays = 0; int totalLights = 0; void rayLogic(UpdateVariables& myVar, UpdateParameters& myParam) { if (shouldRender) { rays.clear(); currentSamples = 0; accumulatedRays = 0; shouldRender = false; } if (IO::shortcutPress(KEY_C)) { shouldRender = true; } createPointLight(myVar, myParam); createAreaLight(myVar, myParam); createConeLight(myVar, myParam); createWall(myVar, myParam); createShape(myVar, myParam); if (!walls.empty()) { wallPointers.clear(); for (Wall& wall : walls) { wallPointers.push_back(&wall); } bvh.build(wallPointers); } moveLogic(myVar, myParam); eraseLogic(myVar, myParam); selectLogic(myVar, myParam); lightRendering(myParam); drawRays(); totalLights = static_cast(pointLights.size()) + static_cast(areaLights.size()) + static_cast(coneLights.size()); if (currentSamples <= maxSamples) { accumulatedRays += static_cast(rays.size()); } if (IO::shortcutPress(KEY_C)) { rays.clear(); pointLights.clear(); areaLights.clear(); coneLights.clear(); walls.clear(); shapes.clear(); wallPointers.clear(); for (Wall& wall : walls) { wallPointers.push_back(&wall); } bvh.build(wallPointers); } } }; ================================================ FILE: GalaxyEngine/include/Physics/materialsSPH.h ================================================ #pragma once struct SPHMaterial { uint32_t id = 0; std::string sphLabel = "material"; // Base values float massMult = 1.0f; float restDens = 0.008f; float stiff = 1.0f; float visc = 1.0f; float cohesion = 1.0f; Color color = { 255, 255, 255, 255 }; // Hot values float hotPoint = 1000.0f; float hotRestDens = 0.01f; float hotMassMult = 1.0f; float hotStiff = 1.0f; float hotVisc = 1.0f; float hotCohesion = 1.0f; Color hotColor = { 255, 100, 100, 255 }; // Cold values float coldPoint = 0.0f; float coldRestDens = 0.01f; float coldMassMult = 1.0f; float coldStiff = 1.0f; float coldVisc = 1.0f; float coldCohesion = 1.0f; Color coldColor = { 255, 255, 255, 255 }; // Other values float heatConductivity = 0.1f; float constraintResistance = 1.0f; float constraintPlasticPoint = 0.5f; float constraintPlasticPointMult = 2.0f; float constraintStiffness = 60.0f; bool isPlastic = false; virtual ~SPHMaterial() = default; SPHMaterial(uint32_t id_, const std::string& label) : id(id_), sphLabel(label) { } }; // DISCLAIMER: Welcome to arbitrary town. What does hotPoint mean? Whatever your heart feels like. // For example, the hot point of water means boiling in this context. And coldPoint in water's context means freezing // These parameters actually just do whatever you like them to do at those temperatures // I'm currently not simulating rock getting vaporized for example, so rock only has "solid" and "liquid" // For materials that only are either solid or liquid, I set their cold point to 0.0f because the minimum temperature possible is 1.0f in GE struct SPHWater : public SPHMaterial { SPHWater() : SPHMaterial(1, "water") { massMult = 0.6f; restDens = 0.095f; stiff = 1.0f; visc = 0.075f; cohesion = 0.05f; color = { 30, 65, 230, 150 }; hotPoint = 373.2f; hotMassMult = 0.25f; hotRestDens = 0.065f; hotStiff = 1.0f; hotVisc = 0.055f; hotCohesion = 0.0f; hotColor = { 230, 230, 250, 190 }; coldPoint = 273.2f; coldMassMult = 0.5f; coldRestDens = 0.046f; coldStiff = 0.6f; coldVisc = 2.2f; coldCohesion = 2500.0f; coldColor = { 230, 230, 240, 250 }; heatConductivity = 0.15f; constraintResistance = 0.06f; constraintPlasticPoint = constraintResistance * 0.5f; constraintPlasticPointMult = 0.0f; constraintStiffness = 55.0f; isPlastic = false; } }; struct SPHRock : public SPHMaterial { SPHRock() : SPHMaterial(2, "rock") { massMult = 3.3f; restDens = 0.008f; stiff = 1.4f; visc = 3.0f; cohesion = 1750.0f; color = { 150, 155, 160, 255 }; hotPoint = 1370.0f; hotMassMult = 2.8f; hotRestDens = 0.005f; hotStiff = 1.0f; hotVisc = 0.6f; hotCohesion = 300.0f; hotColor = { 255, 105, 0, 255 }; coldPoint = 0.0f; coldMassMult = massMult; coldRestDens = restDens; coldStiff = stiff; coldVisc = visc; coldCohesion = cohesion; coldColor = color; heatConductivity = 0.02f; constraintResistance = 0.211f; constraintPlasticPoint = constraintResistance * 0.5f; constraintPlasticPointMult = 0.0f; constraintStiffness = 60.0f; isPlastic = false; } }; struct SPHIron : public SPHMaterial { SPHIron() : SPHMaterial(3, "iron") { massMult = 4.0f; restDens = 0.008f; stiff = 1.4f; visc = 3.0f; cohesion = 1750.0f; color = { 110, 125, 157, 255 }; hotPoint = 1740.0f; hotMassMult = 2.8f; hotRestDens = 0.005f; hotStiff = 1.0f; hotVisc = 0.6f; hotCohesion = 300.0f; hotColor = { 255, 105, 0, 255 }; coldPoint = 0.0f; coldMassMult = massMult; coldRestDens = restDens; coldStiff = stiff; coldVisc = visc; coldCohesion = cohesion; coldColor = color; heatConductivity = 0.02f; constraintResistance = 0.24f; constraintPlasticPoint = constraintResistance * 0.45f; constraintPlasticPointMult = 1.8f; constraintStiffness = 60.0f; isPlastic = true; } }; struct SPHSand : public SPHMaterial { SPHSand() : SPHMaterial(4, "sand") { massMult = 2.1f; restDens = 0.008f; stiff = 1.255f; visc = 0.74f; cohesion = 1.0f; color = { 200, 185, 100, 255 }; hotPoint = 1200.0f; hotMassMult = 1.9f; hotRestDens = 0.011f; hotStiff = 1.12f; hotVisc = 0.6f; hotCohesion = 1.0f; hotColor = { 255, 105, 0, 255 }; coldPoint = 0.0f; coldMassMult = massMult; coldRestDens = restDens; coldStiff = stiff; coldVisc = visc; coldCohesion = cohesion; coldColor = color; heatConductivity = 0.01f; constraintResistance = 0.08f; constraintPlasticPoint = constraintResistance * 0.5f; constraintPlasticPointMult = 0.0f; constraintStiffness = 55.0f; isPlastic = false; } }; struct SPHSoil : public SPHMaterial { SPHSoil() : SPHMaterial(5, "soil") { massMult = 1.9f; restDens = 0.008f; stiff = 1.0f; visc = 2.23f; cohesion = 3000.0f; color = { 156, 110, 30, 255 }; hotPoint = 950.0f; hotMassMult = 1.8f; hotRestDens = 0.013f; hotStiff = 0.9f; hotVisc = 1.8f; hotCohesion = 600.0f; hotColor = { 255, 105, 0, 255 }; coldPoint = 0.0f; coldMassMult = massMult; coldRestDens = restDens; coldStiff = stiff; coldVisc = visc; coldCohesion = cohesion; coldColor = color; heatConductivity = 0.02f; constraintResistance = 0.09f; constraintPlasticPoint = constraintResistance * 0.5f; constraintPlasticPointMult = 2.0f; constraintStiffness = 30.0f; isPlastic = true; } }; struct SPHMud : public SPHMaterial { SPHMud() : SPHMaterial(6, "mud") { massMult = 2.3f; restDens = 0.0095f; stiff = 1.0f; visc = 0.6f; cohesion = 100.0f; color = { 106, 60, 3, 255 }; hotPoint = 1000.0f; hotMassMult = 2.1f; hotRestDens = 0.011f; hotStiff = 1.0f; hotVisc = 0.5f; hotCohesion = 40.0f; hotColor = { 255, 105, 0, 255 }; coldPoint = 0.0f; coldMassMult = massMult; coldRestDens = restDens; coldStiff = stiff; coldVisc = visc; coldCohesion = cohesion; coldColor = color; heatConductivity = 0.1f; constraintResistance = 0.03f; constraintPlasticPoint = constraintResistance * 0.5f; constraintPlasticPointMult = 2.0f; constraintStiffness = 20.0f; isPlastic = true; } }; struct SPHRubber : public SPHMaterial { SPHRubber() : SPHMaterial(7, "rubber") { massMult = 1.7f; restDens = 0.0095f; stiff = 1.0f; visc = 0.6f; cohesion = 100.0f; color = { 226, 166, 114, 255 }; hotPoint = 453.0f; hotMassMult = 1.6f; hotRestDens = 0.011f; hotStiff = 1.0f; hotVisc = 0.5f; hotCohesion = 40.0f; hotColor = { 255, 105, 0, 255 }; coldPoint = 0.0f; coldMassMult = massMult; coldRestDens = restDens; coldStiff = stiff; coldVisc = visc; coldCohesion = cohesion; coldColor = color; heatConductivity = 1.1f; constraintResistance = 5.5f; constraintPlasticPoint = constraintResistance * 0.5f; constraintPlasticPointMult = 2.0f; constraintStiffness = 6.0f; isPlastic = true; } }; struct SPHMaterials { static std::vector> materials; static std::unordered_map idToMaterial; static void Init() { materials.emplace_back(std::make_unique()); materials.emplace_back(std::make_unique()); materials.emplace_back(std::make_unique()); materials.emplace_back(std::make_unique()); materials.emplace_back(std::make_unique()); materials.emplace_back(std::make_unique()); materials.emplace_back(std::make_unique()); for (auto& mat : materials) { idToMaterial[mat->id] = mat.get(); } } }; ================================================ FILE: GalaxyEngine/include/Physics/morton.h ================================================ #pragma once #include "Particles/particle.h" struct Morton { std::vector indicesBuffer; std::vector pSortedBuffer; std::vector rSortedBuffer; uint64_t scaleToGrid(float pos, float minVal, float maxVal = 2097151.0f); uint64_t spreadBits(uint64_t x); uint64_t morton2D(uint64_t x, uint64_t y); void computeMortonKeys(std::vector& pParticles, glm::vec3& posSize); void sortParticlesByMortonKey(std::vector& pParticles, std::vector& rParticles); std::vector indicesBuffer3D; std::vector pSortedBuffer3D; std::vector rSortedBuffer3D; uint64_t scaleToGrid3D(float pos, float minVal, float maxVal = 2097151.0f); uint64_t spreadBits3D(uint64_t x); uint64_t morton3D(uint64_t x, uint64_t y, uint64_t z); void computeMortonKeys3D(std::vector& pParticles, const glm::vec4& boundingBox); void sortParticlesByMortonKey3D(std::vector& pParticles, std::vector& rParticles); }; ================================================ FILE: GalaxyEngine/include/Physics/physics.h ================================================ #pragma once #include "Particles/particle.h" #include "Particles/QueryNeighbors.h" #include "Physics/quadtree.h" #include "Physics/constraint.h" #include "parameters.h" struct Physics { std::vector particleConstraints; std::unordered_map constraintMap; std::vector idToIndexTable; uint64_t makeKey(uint32_t id1, uint32_t id2) { return id1 < id2 ? ((uint64_t)id1 << 32) | id2 : ((uint64_t)id2 << 32) | id1; } const float globalConstraintDamping = 0.001f; const float stiffCorrectionRatio = 0.013333f; // Heuristic. This used to modify the stiffness of a constraint in a more intuitive way. DO NOT CHANGE void calculateForceFromGrid(UpdateVariables& myVar); void calculateForceFromGridAVX2(UpdateVariables& myVar); glm::vec2 calculateForceFromGridOld(std::vector& pParticles, UpdateVariables& myVar, ParticlePhysics& pParticle); std::vector posX; std::vector posY; std::vector accX; std::vector accY; std::vector velX; std::vector velY; std::vector prevVelX; std::vector prevVelY; std::vector mass; std::vector temp; void flattenParticles(std::vector& pParticles); void naiveGravity(std::vector& pParticles, UpdateVariables& myVar); void naiveGravityAVX2(std::vector& pParticles, UpdateVariables& myVar); void readFlattenBack(std::vector& pParticles); void temperatureCalculation(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void createConstraints(std::vector& pParticles, std::vector& rParticles, bool& constraintCreateSpecialFlag, UpdateVariables& myVar, UpdateParameters& myParam); void constraints(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void pausedConstraints(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void mergerSolver(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar, UpdateParameters& myParam); void spawnCorrection(UpdateParameters& myParam, bool& hasVAX2, const int& iterations); void integrateStart(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void integrateEnd(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void pruneParticles(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); // ----- Unused. Test code ----- // // This was made for learning purposes and it is not going to replace the existing gravity algorithm. // To use it, call first initGrid() and then gravityGrid(). Also be sure to process the bounding box before these struct GravityCell { glm::vec2 pos; float size; float mass = 0.0f; glm::vec2 force = { 0.0f, 0.0f }; int depth; GravityCell() = default; GravityCell(glm::vec2 pos, float size, int depth) : pos(pos), size(size), depth(depth) { } std::vector particles; }; std::vector cells; int maxDepth = 9; // This controls quality. Higher means more accurate. Gravity forces scale with this too, which is not intended int gridRes = 0; void initGrid(std::vector& pParticles, glm::vec3& bb) { int totalCells = 0; for (int depth = 0; depth < maxDepth; depth++) { int res = std::pow(2, depth + 1); totalCells += res * res; } cells.clear(); cells.resize(totalCells); int offset = 0; for (int depth = 0; depth < maxDepth; depth++) { gridRes = std::pow(2, depth + 1); float cellSize = bb.z / static_cast(gridRes); #pragma omp parallel for collapse(2) for (int y = 0; y < gridRes; y++) { for (int x = 0; x < gridRes; x++) { int index = offset + y * gridRes + x; cells[index] = GravityCell( glm::vec2{ bb.x + (x * cellSize), bb.y + (y * cellSize) }, cellSize, depth ); } } offset += gridRes * gridRes; } } void gravityGrid(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar, glm::vec3& bb); // ----- Unused. Test code ----- // }; ================================================ FILE: GalaxyEngine/include/Physics/physics3D.h ================================================ #pragma once #include "Particles/particle.h" #include "Physics/quadtree.h" #include "parameters.h" #include "Particles/QueryNeighbors.h" #include "Physics/constraint.h" struct Physics3D { std::vector particleConstraints; std::unordered_map constraintMap; std::vector idToIndexTable; uint64_t makeKey(uint32_t id1, uint32_t id2) { return id1 < id2 ? ((uint64_t)id1 << 32) | id2 : ((uint64_t)id2 << 32) | id1; } const float globalConstraintDamping = 0.001f; const float stiffCorrectionRatio = 0.013333f; // Heuristic. This used to modify the stiffness of a constraint in a more intuitive way. DO NOT CHANGE std::vector posX; std::vector posY; std::vector posZ; std::vector accX; std::vector accY; std::vector accZ; std::vector velX; std::vector velY; std::vector velZ; std::vector prevVelX; std::vector prevVelY; std::vector prevVelZ; std::vector mass; std::vector temp; void flattenParticles3D(std::vector& pParticles3D); glm::vec3 calculateForceFromGrid3DOld(std::vector& pParticles, UpdateVariables& myVar, ParticlePhysics3D& pParticle); void calculateForceFromGrid3D(UpdateVariables& myVar); void calculateForceFromGrid3DAVX2(UpdateVariables& myVar); void naiveGravity3D(std::vector& pParticles3D, UpdateVariables& myVar); void naiveGravity3DAVX2(std::vector& pParticles3D, UpdateVariables& myVar); void readFlattenBack3D(std::vector& pParticles3D); void temperatureCalculation(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void integrateStart3D(std::vector& pParticles3D, std::vector& rParticles3D, UpdateVariables& myVar); void pruneParticles(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void integrateEnd3D(std::vector& pParticles3D, std::vector& rParticles3D, UpdateVariables& myVar); void createConstraints(std::vector& pParticles, std::vector& rParticles, bool& constraintCreateSpecialFlag, UpdateVariables& myVar, UpdateParameters& myParam); void constraints(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void pausedConstraints(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar); void spawnCorrection(UpdateParameters& myParam, bool& hasAVX2, const int& iterations); }; ================================================ FILE: GalaxyEngine/include/Physics/quadtree.h ================================================ #pragma once #include "Particles/particle.h" // --- UNUSED QUADTREE FOR MORTON KEYS VERSION --- // //struct Node { // glm::vec2 pos; // float size; // float mass; // glm::vec2 CoM; // uint32_t next; // int level; // // Node(glm::vec2 pos, float size, float mass, glm::vec2 CoM, uint32_t next, int level) { // this->pos = pos; // this->size = size; // this->mass = mass; // this->CoM = CoM; // this->next = next; // this->level = level; // } //}; // //struct TestTree { // static std::vector nodesTest; //}; struct Node; extern std::vector globalNodes; struct Node { glm::vec2 pos; glm::vec2 centerOfMass; float size; float gridMass; float gridTemp; uint32_t startIndex; uint32_t endIndex; uint32_t next = 0; uint32_t subGrids[2][2] = { { UINT32_MAX, UINT32_MAX }, { UINT32_MAX, UINT32_MAX } }; Node(glm::vec2 pos, float size, uint32_t startIndex, uint32_t endIndex, std::vector& pParticles, std::vector& rParticles); Node() = default; void subGridMaker(std::vector& pParticles, std::vector& rParticles); inline void computeLeafMass(const std::vector& pParticles) { gridMass = 0.0f; gridTemp = 0.0f; centerOfMass = { 0.0f, 0.0f }; for (uint32_t i = startIndex; i < endIndex; ++i) { gridMass += pParticles[i].mass; gridTemp += pParticles[i].temp; centerOfMass += pParticles[i].pos * pParticles[i].mass; } if (gridMass > 0) { centerOfMass /= gridMass; } } inline void computeInternalMass() { gridMass = 0.0f; gridTemp = 0.0f; centerOfMass = { 0.0f, 0.0f }; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { uint32_t idx = subGrids[i][j]; if (idx == UINT32_MAX) continue; Node& child = globalNodes[idx]; gridMass += child.gridMass; gridTemp += child.gridTemp; centerOfMass += child.centerOfMass * child.gridMass; } } if (gridMass > 0) { centerOfMass /= gridMass; } } inline void calculateNextNeighbor() { next = 0; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { uint32_t idx = subGrids[i][j]; if (idx == UINT32_MAX) continue; next++; Node& child = globalNodes[idx]; next += child.next; } } } }; struct Quadtree { glm::vec3 boundingBox; Quadtree(std::vector& pParticles, std::vector& rParticles, glm::vec3& boundingBox) { this->boundingBox = boundingBox; root(pParticles, rParticles); } void root(std::vector& pParticles, std::vector& rParticles); }; struct Node3D; extern std::vector globalNodes3D; struct Node3D { glm::vec3 pos; glm::vec3 centerOfMass; float size; float gridMass; float gridTemp; uint32_t startIndex; uint32_t endIndex; uint32_t next = 0; uint32_t subGrids[2][2][2] = { { { UINT32_MAX, UINT32_MAX }, { UINT32_MAX, UINT32_MAX } }, { { UINT32_MAX, UINT32_MAX }, { UINT32_MAX, UINT32_MAX } } }; Node3D(glm::vec3 pos, float size, uint32_t startIndex, uint32_t endIndex, std::vector& pParticles, std::vector& rParticles); Node3D() = default; void subGridMaker3D(std::vector& pParticles, std::vector& rParticles); inline void computeLeafMass3D(const std::vector& pParticles) { gridMass = 0.0f; gridTemp = 0.0f; centerOfMass = { 0.0f, 0.0f, 0.0f }; for (uint32_t i = startIndex; i < endIndex; ++i) { gridMass += pParticles[i].mass; gridTemp += pParticles[i].temp; centerOfMass += pParticles[i].pos * pParticles[i].mass; } if (gridMass > 0) { centerOfMass /= gridMass; } } inline void computeInternalMass3D(); inline void calculateNextNeighbor3D(); }; struct Octree { glm::vec3 rootPos; float rootSize; Octree(std::vector& pParticles, std::vector& rParticles, glm::vec3 rootPos, float rootSize) { this->rootPos = rootPos; this->rootSize = rootSize; root(pParticles, rParticles); } void root(std::vector& pParticles, std::vector& rParticles); }; ================================================ FILE: GalaxyEngine/include/Physics/slingshot.h ================================================ #pragma once #include "UX/camera.h" struct UpdateVariables; class Slingshot { public: glm::vec2 norm; float length; Slingshot(glm::vec2 norm, float length); static Slingshot particleSlingshot(UpdateVariables& myVar, SceneCamera myCamera); }; class Slingshot3D { public: glm::vec3 norm; float length; Slingshot3D(glm::vec3 norm, float length); static Slingshot3D particleSlingshot(UpdateVariables& myVar, glm::vec3& brushPos); }; ================================================ FILE: GalaxyEngine/include/Renderer/rayMarching.h ================================================ #pragma once #include "Particles/particle.h" #include "UX/camera.h" #include "UI/UI.h" #include "Physics/quadtree.h" struct RayMarcher { struct Pixel { int x; int y; int size; Color color; }; int screenResX = 480; int screenResY = 270; int pixelSize = 3; int pixelAmountX = screenResX / pixelSize; int pixelAmountY = screenResY / pixelSize; float aspectRatio = static_cast(pixelAmountX) / static_cast(pixelAmountY); std::vector pixels; bool initPixels = true; void initPixelGrid() { if (initPixels) { pixelAmountX = screenResX / pixelSize; pixelAmountY = screenResY / pixelSize; aspectRatio = static_cast(pixelAmountX) / static_cast(pixelAmountY); for (int y = 0; y < pixelAmountY; y++) { for (int x = 0; x < pixelAmountX; x++) { pixels.emplace_back(Pixel{ x * pixelSize, y * pixelSize, pixelSize , Color{ 255, 0, 0, 255 } }); } } initPixels = false; } } void drawPixels() { for (Pixel& p : pixels) { DrawRectangle(p.x, p.y, p.size, p.size, { p.color.r,p.color.g, p.color.b, p.color.a }); } } struct MarcherRay { glm::vec3 source; glm::vec3 dir; float totalLength = FLT_MAX; float minLength = FLT_MAX; int steps = 0; bool hit = false; }; std::vector rays; float camSizeMult = 1.0f; float collisionDistMultiplier = 1.0f; int marchingSteps = 100; float fov = 45.0f; float nearPlane = 0.0001f; float farPlane = 1000.0f; float testThreshold = 10.0f; void createCameraRay(glm::vec3 dir, glm::vec3 source) { rays.emplace_back(source, dir); } float smoothingValue = 5.5f; float smoothMin(float a, float b, float k) { float h = glm::clamp(0.5f + 0.5f * (b - a) / k, 0.0f, 1.0f); return glm::mix(b, a, h) - k * h * (1.0f - h); } void rayParticleLogic(std::vector& pParticles, std::vector& rParticles, MarcherRay& ray) { glm::vec3 initSource = ray.source; if (pParticles.empty()) return; float minSceneDist = FLT_MAX; float minRayLength = FLT_MAX; for (int iter = 0; iter < marchingSteps; iter++) { minSceneDist = FLT_MAX; for (size_t i = 0; i < pParticles.size(); i++) { ParticlePhysics3D& p = pParticles[i]; ParticleRendering3D& r = rParticles[i]; float centerDist = glm::distance(ray.source, p.pos); float surfaceDist = centerDist - (r.totalRadius * collisionDistMultiplier); minSceneDist = smoothMin(minSceneDist, surfaceDist, smoothingValue); minRayLength = smoothMin(minRayLength, surfaceDist, smoothingValue);; } if (minSceneDist < nearPlane) { ray.hit = true; ray.totalLength = glm::distance(initSource, ray.source); ray.minLength = minSceneDist; ray.steps = iter; return; } ray.source += ray.dir * minSceneDist; if (minSceneDist > farPlane) { ray.totalLength = glm::distance(initSource, ray.source); ray.minLength = minRayLength; ray.steps = marchingSteps; return; } } } float de(glm::vec3 p0) { glm::vec4 p(p0, 1.0f); for (int i = 0; i < 8; i++) { p.x = std::fmod(p.x - 1.0f, 2.0f); if (p.x < 0.0f) p.x += 2.0f; p.x -= 1.0f; p.y = std::fmod(p.y - 1.0f, 2.0f); if (p.y < 0.0f) p.y += 2.0f; p.y -= 1.0f; p.z = std::fmod(p.z - 1.0f, 2.0f); if (p.z < 0.0f) p.z += 2.0f; p.z -= 1.0f; float scale = 1.4f / glm::dot(glm::vec3(p), glm::vec3(p)); p *= scale; } glm::vec2 xz(p.x / p.w, p.z / p.w); return glm::length(xz) * 0.25f; } void fractal(MarcherRay& ray) { glm::vec3 initSource = ray.source; float minRayLength = FLT_MAX; for (int iter = 0; iter < marchingSteps; iter++) { float minSceneDist = de(ray.source); minRayLength = std::min(minRayLength, minSceneDist); if (minSceneDist < nearPlane) { ray.hit = true; ray.totalLength = glm::distance(initSource, ray.source); ray.minLength = minSceneDist; ray.steps = iter; return; } ray.source += ray.dir * minSceneDist; if (glm::distance(initSource, ray.source) > farPlane) { ray.totalLength = glm::distance(initSource, ray.source); ray.minLength = minRayLength; ray.steps = marchingSteps; return; } } ray.totalLength = glm::distance(initSource, ray.source); ray.minLength = minRayLength; ray.steps = marchingSteps; } void rayMarchUI() { bool enabled = true; UI::sliderHelper("Collision Distance", "", collisionDistMultiplier, 0.1f, 100.0f, 200.0f, 50.0f, enabled, UI::LogSlider); UI::sliderHelper("Marching Steps", "", marchingSteps, 1, 200, 200.0f, 50.0f, enabled); UI::sliderHelper("Test Threshold", "", testThreshold, 0.001f, 100.0f, 200.0f, 50.0f, enabled, UI::LogSlider); UI::sliderHelper("Smooth Value", "", smoothingValue, 0.0f, 15.0f, 200.0f, 50.0f, enabled, UI::LogSlider); UI::sliderHelper("Near Plane", "", nearPlane, 0.001f, 1.0f, 200.0f, 50.0f, enabled, UI::LogSlider); UI::sliderHelper("Far Plane", "", farPlane, 100.0f, 100000.0f, 200.0f, 50.0f, enabled, UI::LogSlider); } void cameraRays(SceneCamera3D& cam, std::vector& pParticles, std::vector& rParticles) { rays.clear(); float halfFovRadians = glm::radians(fov) * 0.5f; float verticalScale = tan(halfFovRadians) * 2.0f; for (int y = 0; y < pixelAmountY; y++) { for (int x = 0; x < pixelAmountX; x++) { float offsetX = (static_cast(x) / static_cast(pixelAmountX) - 0.5f) * aspectRatio; float offsetY = (static_cast(y) / static_cast(pixelAmountY) - 0.5f); glm::vec3 dir = -cam.camNormal; dir += cam.camRight * offsetX * verticalScale; dir -= cam.camUp * offsetY * verticalScale; dir = glm::normalize(dir); glm::vec3 rayOrigin = { cam.cam3D.position.x, cam.cam3D.position.y, cam.cam3D.position.z }; createCameraRay(dir, rayOrigin); } } #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < rays.size(); i++) { MarcherRay& ray = rays[i]; rayParticleLogic(pParticles, rParticles, ray); fractal(ray); } for (size_t i = 0; i < pixels.size(); i++) { Pixel& p = pixels[i]; MarcherRay& r = rays[i]; if (true) { unsigned char value = static_cast((float(std::min(float(r.steps), abs(testThreshold))) / testThreshold) * 255.0f); p.color = { value,value,value, 255 }; } else { p.color = { 0,0,0,200 }; } } } const char* rayMarcherCompute = R"( #version 430 core layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout(rgba16f, binding = 0) uniform image2D imgOutput; uniform vec3 camPos; uniform vec3 camDir; uniform vec3 camRight; uniform vec3 camUp; uniform float fov; uniform int maxSteps; uniform float collisionMult; uniform float testThreshold; uniform float smoothVal; uniform float nearPlane; uniform float farPlane; uniform vec2 resolution; struct Particle { vec3 pos; float radius; }; layout(std430, binding = 1) buffer ParticleBuffer { Particle particles[]; }; float smoothMin(float a, float b, float k) { float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); return mix(b, a, h) - k * h * (1.0 - h); } float de(vec3 p){ float s=2., l=0.; p=abs(p); for(int j=0;j++<8;) p=-sign(p)*(abs(abs(abs(p)-2.)-1.)-1.), p*=l=-1.3/dot(p,p), p-=.15, s*=l; return length(p)/s; } vec3 calcNormal(vec3 p) { const float h = 0.0001; return normalize(vec3( de(vec3(p.x + h, p.y, p.z)) - de(vec3(p.x - h, p.y, p.z)), de(vec3(p.x, p.y + h, p.z)) - de(vec3(p.x, p.y - h, p.z)), de(vec3(p.x, p.y, p.z + h)) - de(vec3(p.x, p.y, p.z - h)) )); } struct RayResult { vec3 color; vec3 hitPos; vec3 hitNormal; int steps; float minDist; float maxDist; bool hit; }; RayResult rayParticle(vec3 origin, vec3 direction) { RayResult result; result.hit = false; result.steps = maxSteps; vec3 currentPos = origin; float totalDist = 0.0; float minDist = 1000000.0f; float maxDist = 0.0f; for (int i = 0; i < maxSteps; i++) { float sceneDist = 1000000.0f; for (int p = 0; p < particles.length(); p++) { float distToParticle = distance(currentPos, particles[p].pos); float surfaceDist = distToParticle - (particles[p].radius * collisionMult); sceneDist = smoothMin(sceneDist, surfaceDist, smoothVal); } minDist = min(minDist, sceneDist); maxDist = max(maxDist, sceneDist); if (sceneDist < nearPlane) { result.hit = true; result.hitPos = currentPos; result.hitNormal = calcNormal(currentPos); result.steps = i; result.minDist = minDist; result.maxDist = maxDist; break; } totalDist += sceneDist; currentPos += direction * sceneDist; if (totalDist > farPlane) { result.steps = maxSteps; result.minDist = minDist; result.maxDist = maxDist; break; } } if (!result.hit && totalDist <= farPlane) { result.minDist = minDist; result.maxDist = maxDist; } float intensity = float(result.steps) / abs(testThreshold); if (testThreshold < 0.0){ intensity = 1.0 - intensity; } result.color = vec3(intensity); return result; } RayResult fractal(vec3 origin, vec3 direction) { RayResult result; result.hit = false; result.steps = maxSteps; vec3 currentPos = origin; float totalDist = 0.0; float minDist = 1000000.0f; float maxDist = 0.0f; for (int i = 0; i < maxSteps; i++) { float dist = de(currentPos); minDist = min(minDist, dist); maxDist = max(maxDist, dist); if (dist < nearPlane) { result.hit = true; result.hitPos = currentPos; result.hitNormal = calcNormal(currentPos); result.steps = i; result.minDist = minDist; result.maxDist = maxDist; break; } totalDist += dist; currentPos += direction * dist; if (totalDist > farPlane) { result.steps = maxSteps; result.minDist = minDist; result.maxDist = maxDist; break; } } if (!result.hit && totalDist <= farPlane) { result.minDist = minDist; result.maxDist = maxDist; } float intensity = float(result.steps) / abs(testThreshold); if (testThreshold < 0.0){ intensity = 1.0 - intensity; } result.color = vec3(intensity); return result; } void main() { ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); ivec2 dims = imageSize(imgOutput); if (pixel_coords.x >= dims.x || pixel_coords.y >= dims.y) return; float aspectRatio = resolution.x / resolution.y; vec2 uv = (vec2(pixel_coords) / resolution) * 2.0 - 1.0; float halfFovRadians = radians(fov) * 0.5; float verticalScale = tan(halfFovRadians) * 2.0; float offsetX = uv.x * 0.5 * aspectRatio * verticalScale; float offsetY = uv.y * 0.5 * verticalScale; vec3 rayDir = normalize(-camDir + (camRight * offsetX) + (camUp * offsetY)); vec3 finalColor = vec3(0.0); vec3 currentOrigin = camPos; vec3 currentDir = rayDir; RayResult result = rayParticle(currentOrigin, currentDir); if (true) { finalColor += result.color; } else { finalColor = vec3(0.0f); } vec4 color = vec4(finalColor, 1.0); imageStore(imgOutput, pixel_coords, color); } )"; GLuint rayMarcherProgram; GLuint particleSSBO; GLuint highResTexID; GLuint lowResTexID; Texture2D highResTextureRayMarcher; Texture2D lowResTextureRayMarcher; void createShaderProgram() { GLuint shader = glCreateShader(GL_COMPUTE_SHADER); glShaderSource(shader, 1, &rayMarcherCompute, NULL); glCompileShader(shader); int success; char infoLog[1024]; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(shader, 1024, NULL, infoLog); std::cerr << "COMPUTE SHADER ERROR:\n" << infoLog << std::endl; } rayMarcherProgram = glCreateProgram(); glAttachShader(rayMarcherProgram, shader); glLinkProgram(rayMarcherProgram); glDeleteShader(shader); } void createOutputTextures() { glGenTextures(1, &highResTexID); glBindTexture(GL_TEXTURE_2D, highResTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, GetScreenWidth(), GetScreenHeight(), 0, GL_RGBA, GL_FLOAT, NULL); highResTextureRayMarcher.id = highResTexID; highResTextureRayMarcher.width = GetScreenWidth(); highResTextureRayMarcher.height = GetScreenHeight(); highResTextureRayMarcher.mipmaps = 1; highResTextureRayMarcher.format = PIXELFORMAT_UNCOMPRESSED_R16G16B16A16; int lowW = GetScreenWidth() / 8; int lowH = GetScreenHeight() / 8; glGenTextures(1, &lowResTexID); glBindTexture(GL_TEXTURE_2D, lowResTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, lowW, lowH, 0, GL_RGBA, GL_FLOAT, NULL); lowResTextureRayMarcher.id = lowResTexID; lowResTextureRayMarcher.width = lowW; lowResTextureRayMarcher.height = lowH; lowResTextureRayMarcher.mipmaps = 1; lowResTextureRayMarcher.format = PIXELFORMAT_UNCOMPRESSED_R16G16B16A16; glBindTexture(GL_TEXTURE_2D, 0); } void createSSBO() { glGenBuffers(1, &particleSSBO); glBindBuffer(GL_SHADER_STORAGE_BUFFER, particleSSBO); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, particleSSBO); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); } void Init() { createOutputTextures(); createShaderProgram(); createSSBO(); std::cout << "RayMarcher Initialized on GPU." << std::endl; } struct GPUParticle { float x, y, z; float radius; }; void Run(const SceneCamera3D& cam, const std::vector& pParticles, const std::vector& rParticles, bool& lowResRayMarching) { int currentW = GetScreenWidth(); int currentH = GetScreenHeight(); GLuint targetTex = highResTexID; if (lowResRayMarching) { currentW /= 8; currentH /= 8; targetTex = lowResTexID; } static std::vector gpuData; gpuData.clear(); gpuData.reserve(pParticles.size()); for (size_t i = 0; i < pParticles.size(); i++) { GPUParticle p; p.x = pParticles[i].pos.x; p.y = pParticles[i].pos.y; p.z = pParticles[i].pos.z; p.radius = rParticles[i].totalRadius; gpuData.push_back(p); } glBindBuffer(GL_SHADER_STORAGE_BUFFER, particleSSBO); glBufferData(GL_SHADER_STORAGE_BUFFER, gpuData.size() * sizeof(GPUParticle), gpuData.data(), GL_DYNAMIC_DRAW); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); glUseProgram(rayMarcherProgram); glBindImageTexture(0, targetTex, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F); glUniform3f(glGetUniformLocation(rayMarcherProgram, "camPos"), cam.cam3D.position.x, cam.cam3D.position.y, cam.cam3D.position.z); glUniform3f(glGetUniformLocation(rayMarcherProgram, "camDir"), cam.camNormal.x, cam.camNormal.y, cam.camNormal.z); glUniform3f(glGetUniformLocation(rayMarcherProgram, "camRight"), cam.camRight.x, cam.camRight.y, cam.camRight.z); glUniform3f(glGetUniformLocation(rayMarcherProgram, "camUp"), cam.camUp.x, cam.camUp.y, cam.camUp.z); glUniform1f(glGetUniformLocation(rayMarcherProgram, "fov"), 45.0f); glUniform1i(glGetUniformLocation(rayMarcherProgram, "maxSteps"), marchingSteps); glUniform1f(glGetUniformLocation(rayMarcherProgram, "collisionMult"), collisionDistMultiplier); glUniform1f(glGetUniformLocation(rayMarcherProgram, "testThreshold"), testThreshold); glUniform1f(glGetUniformLocation(rayMarcherProgram, "smoothVal"), smoothingValue); glUniform1f(glGetUniformLocation(rayMarcherProgram, "nearPlane"), nearPlane); glUniform1f(glGetUniformLocation(rayMarcherProgram, "farPlane"), farPlane); glUniform2f(glGetUniformLocation(rayMarcherProgram, "resolution"), (float)currentW, (float)currentH); glDispatchCompute((currentW + 7) / 8, (currentH + 7) / 8, 1); glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); } void Draw(bool& lowResRayMarching) { Texture2D texToDraw = lowResRayMarching ? lowResTextureRayMarcher : highResTextureRayMarcher; Rectangle source = { 0.0f, 0.0f, (float)texToDraw.width, -(float)texToDraw.height }; Rectangle dest = { 0.0f, 0.0f, (float)GetScreenWidth(), (float)GetScreenHeight() }; DrawTexturePro(texToDraw, source, dest, { 0,0 }, 0.0f, WHITE); } void Unload() { glDeleteProgram(rayMarcherProgram); glDeleteTextures(1, &highResTexID); glDeleteTextures(1, &lowResTexID); glDeleteBuffers(1, &particleSSBO); } }; ================================================ FILE: GalaxyEngine/include/Sound/sound.h ================================================ #pragma once struct GESound { static Sound intro; static Sound soundButtonHover1; static Sound soundButtonHover2; static Sound soundButtonHover3; static Sound soundButtonEnable; static Sound soundButtonDisable; static Sound soundSliderSlide; static std::vector soundButtonHover1Pool; static std::vector soundButtonHover2Pool; static std::vector soundButtonHover3Pool; static std::vector soundButtonEnablePool; static std::vector soundButtonDisablePool; static std::vector soundSliderSlidePool; int soundPoolSize = 30; int soundSliderSlidePoolSize = 150; Music currentMusic; int currentSongIndex = 0; bool musicPlaying = false; bool isFirstTimePlaying = true; bool hasTrackChanged = false; float globalVolume = 0.4f; float menuVolume = 0.25f; float musicVolume = 0.4f; float musicVolMultiplier = 1.0f; std::vector playlist = { "Sounds/Soundtrack/Passage of the Stars.wav", "Sounds/Soundtrack/Star Born Microbes.wav", "Sounds/Soundtrack/Celestial Bodies.wav", "Sounds/Soundtrack/Beyond the Solar Alignment.wav", "Sounds/Soundtrack/200 Light-Years From Here.wav", }; void loadSounds(); void soundtrackLogic(); void unloadSounds(); }; ================================================ FILE: GalaxyEngine/include/UI/UI.h ================================================ #pragma once #include "Particles/particle.h" #include "Physics/quadtree.h" #include "Physics/SPH.h" #include "Physics/light.h" #include "Particles/particleSpaceship.h" #include "UX/saveSystem.h" #include "Sound/sound.h" #include "parameters.h" #include "Physics/field.h" class SaveSystem; struct settingsParams { std::string text; std::string tooltip; bool& parameter; settingsParams(const std::string& t, const std::string& tool, bool& p) : text(t), tooltip(tool), parameter(p) {} }; struct sphParams { std::string text; bool& parameter; sphParams(const std::string& t, bool& p) : text(t), parameter(p) {} }; struct PlotData { std::vector values; int offset = 0; }; class UI { public: bool bVisualsSliders = true; bool bPhysicsSliders = false; bool bRecordingSettings = false; bool bStatsWindow = false; bool bSoundWindow = false; bool bLightingWindow = false; bool prevSPHState = false; bool prevMassMultiplier = false; void uiLogic(UpdateParameters& myParam, UpdateVariables& myVar, SPH& sph, SaveSystem& save, GESound& geSound, Lighting& lighting, Field& field, ParticleSpaceship& ship); void statsWindowLogic(UpdateParameters& myParam, UpdateVariables& myVar); static void plotLinesHelper(const float& timeFactor, std::string label, const int length, float value, const float minValue, const float maxValue, ImVec2 size); static bool buttonHelper(std::string label, std::string tooltip, bool& parameter, float sizeX, float sizeY, bool canDeactivateSelf, bool& isEnabled); enum ExtraParams { LogSlider }; // Float Logarithmic Overload static bool sliderHelper(std::string label, std::string tooltip, float& parameter, float minVal, float maxVal, float sizeX, float sizeY, bool& isEnabled, int logarithmic); static bool sliderHelper(std::string label, std::string tooltip, float& parameter, float minVal, float maxVal, float sizeX, float sizeY, bool& isEnabled); // Int Overload static bool sliderHelper(std::string label, std::string tooltip, int& parameter, int minVal, int maxVal, float sizeX, float sizeY, bool& isEnabled); bool showSettings = true; private: bool loadSettings = true; static std::unordered_map plotDataMap; ImVec2 graphDefaultSize = { 340.0f, 250.0f }; int graphHistoryLimit = 1000; }; struct SimilarTypeButton { struct Mode { const char* label; const char* tooltip; bool* flag; }; static void buttonIterator(std::vector& modes, float sizeX, float sizeY, bool canDeactivateSelf, bool& isEnabled) { for (size_t i = 0; i < modes.size(); ++i) { if (UI::buttonHelper(modes[i].label, modes[i].tooltip, *modes[i].flag, sizeX, sizeY, canDeactivateSelf, isEnabled)) { for (size_t j = 0; j < modes.size(); ++j) { if (j != i) { *modes[j].flag = false; } } } } } }; ================================================ FILE: GalaxyEngine/include/UI/brush.h ================================================ #pragma once #include "Particles/particle.h" #include "Physics/materialsSPH.h" #include "Physics/quadtree.h" #include "UX/camera.h" #include "Particles/clusterMouseHelper.h" struct UpdateVariables; struct UpdateParameters; class Brush { public: glm::vec2 mouseWorldPos; void brushLogic(UpdateParameters& myParam, bool& isSPHEnabled, bool& constraintAfterDrawing, float& massScatter, UpdateVariables& myVar); void brushSize(); void drawBrush(glm::vec2 mouseWorldPos); void eraseBrush(UpdateVariables& myVar, UpdateParameters& myParam); void particlesAttractor(UpdateVariables& myVar, UpdateParameters& myParam); void particlesSpinner(UpdateVariables& myVar, UpdateParameters& myParam); void particlesGrabber(UpdateVariables& myVar, UpdateParameters& myParam); void temperatureBrush(UpdateVariables& myVar, UpdateParameters& myParam); float brushRadius = 25.0f; private: float spinForce = 40.0f; glm::vec2 attractorForce = { 0.0f, 0.0f }; bool dragging = false; glm::vec2 lastMouseVelocity = { 0.0f, 0.0f }; std::vector grabbedParticles; }; class Brush3D { public: glm::vec2 mouseWorldPos; void brushLogic(UpdateParameters& myParam, bool& isSPHEnabled, bool& constraintAfterDrawing, float& massScatter, UpdateVariables& myVar); void brushSize(); void drawBrush(float& domainHeight); glm::vec3 brushPosLogic(UpdateParameters& myParam, UpdateVariables& myVar); void eraseBrush(UpdateVariables& myVar, UpdateParameters& myParam); void particlesAttractor(UpdateVariables& myVar, UpdateParameters& myParam); void particlesSpinner(UpdateVariables& myVar, UpdateParameters& myParam); void particlesGrabber(UpdateVariables& myVar, UpdateParameters& myParam); void temperatureBrush(UpdateVariables& myVar, UpdateParameters& myParam); float brushRadius = 25.0f; glm::vec3 brushPos = { 0.0f, 0.0f, 0.0f }; float spawnDistance = 500.0f; float originalSpawnDistance = spawnDistance; private: bool wasEmpty = true; float spinForce = 40.0f; glm::vec3 attractorForce = { 0.0f, 0.0f, 0.0f }; bool dragging = false; glm::vec3 lastBrushVelocity = { 0.0f, 0.0f, 0.0f }; glm::vec3 lastBrushPos = { 0.0f, 0.0f, 0.0f }; std::vector grabbedParticles; }; ================================================ FILE: GalaxyEngine/include/UI/controls.h ================================================ #pragma once struct UpdateVariables; class UI; struct Controls { bool isShowControlsEnabled = false; bool isInformationEnabled = false; void showControls(); void showInfo(bool& fullscreen); std::array controlsArray = { "----PARTICLES CREATION----", "1. Hold MMB: Paint particles", "2. Hold 1 and Drag: Create big galaxy", "3. Hold 2 and Drag: Create small galaxy", "4. Hold 3 and Drag: Create star", "5. Press 4: Create Big Bang", "6. C: Clear all particles", "", "----CAMERA AND SELECTION----", "1. Move with RMB", "2. Zoom with mouse wheel", "3. LCTRL + RMB on cluster to follow it", "4. LALT + RMB on particle to follow it", "5. LCTRL + LMB on cluster to select it", "6. LALT + LMB on particle to select it", "7. LCTRL + hold and drag MMB to particle box select", "8. LALT + hold and drag MMB to particle box deselect", "9. Select on empty space to deselect all", "10. Hold SHIFT to add to selection", "11. I: Invert selection", "12. Z: Center camera on selected particles", "13. F: Reset camera ", "14. D: Deselect all particles", "", "----3D CAMERA----", "1. Orbit with RMB", "2. Pan with RMB + ALT", "3. Zoom with mouse wheel", "4. Use arrows to move", "", "----UTILITY----", "1. TAB: Toggle fullscreen", "2. T: Toggle global trails", "3. LCTRL + T: Toggle local trails", "4. U: Toggle UI", "5. RMB on slider to set it to default", "6. Right click to open extra settings", "7. LCTRL + Scroll wheel : Brush size", "8. B: Brush attract particles", "9. N: Brush spin particles", "10. M: Brush grab particles", "11. Hold CTRL to invert brush effects", "12. R: Record", "13. S: Take screenshot", "14. X + MMB: Eraser", "15. H: Copy selected", "16. Hold J and drag: Throw copied", "17. Arrows: Control selected particles", "18. K: Heat brush", "19. L: Cool brush", "20. P: Constraint Solids" }; std::array infoArray = { "----INFORMATION----", "", "Galaxy Engine is a personal project done for learning purposes", "by Narcis Calin. The project was entirely made with Raylib", "and C++ and it uses external libraries, including ImGui and FFmpeg.", "Galaxy Engine is Open Source and the code is available to anyone on GitHub.", "Below you can find some useful information:", "", "1. Theta: Controls the quality of the Barnes-Hut simulation", "", "2. Barnes-Hut: This is the main gravity algorithm.", "", "3. Dark Matter: Galaxy Engine simulates dark matter with", "invisible particles, which are 5 times heavier than visible ones", "", "4. Multi-Threading: Parallelizes the simulation across multiple", "threads. The default is half the max amount of threads your CPU has,", "but it is possible to modify this number.", "", "5. Collisions: Currently, collisions are experimental. They do not", "respect conservation of energy when they are enabled with gravity.", "They work as intended when gravity is disabled.", "", "6. Fluids Mode: This enables fluids for planetary simulation. Each", "material has different parameters like stiffness, viscosity,", "cohesion, and more.", "", "7. Frames Export Safe Mode: It is enabled by default when export", "frames is enabled. It stores your frames directly to disk, avoiding", "filling up your memory. Disabling it will make the render process", "much faster, but the program might crash once you run out of memory", "", "You can report any bugs you may find on our Discord Community." }; }; ================================================ FILE: GalaxyEngine/include/UI/rightClickSettings.h ================================================ #pragma once #include "Particles/particle.h" #include "Particles/particleSubdivision.h" #include "Particles/particleSelection.h" #include "Particles/particleDeletion.h" #include "UX/camera.h" struct UpdateVariables; struct UpdateParameters; struct rightClickParams { std::string text; std::string tooltip; bool& parameter; rightClickParams(const std::string& t, const std::string& tool, bool& p) : text(t), tooltip(tool), parameter(p) {} }; class RightClickSettings { public: glm::vec2 menuPos = { 0.0f, 0.0f }; glm::vec2 menuSize = { 0.0f, 0.0f }; bool isMenuActive = false; void rightClickMenuSpawnLogic(bool& isMouseNotHoveringUI, bool& isSpawningAlone, bool& isDragging, bool& selectedColor); void rightClickMenu(UpdateVariables& myVar, UpdateParameters& myParam); private: bool isMouseOnMenu = false; float buttonSizeY = 20.0f; bool selectedColorChanged = false; bool selectedColorOriginal = false; ImVec4 pCol = { 1.0f, 1.0f, 1.0f, 1.0f }; ImVec4 sCol = { 1.0f, 1.0f, 1.0f, 1.0f }; Color vecToRPColor = { 255,255,255,255 }; Color vecToRSColor = { 255,255,255,255 }; bool resetParticleColors = false; }; ================================================ FILE: GalaxyEngine/include/UX/camera.h ================================================ #pragma once #include "Particles/particle.h" #include "Particles/particleTrails.h" class SceneCamera { public: Camera2D camera; glm::vec2 mouseWorldPos; glm::vec2 followPosition; bool isFollowing; glm::vec2 delta; bool centerCamera; bool cameraChangedThisFrame; SceneCamera(); Camera2D cameraLogic(bool& isLoading, bool& isMouseNotHoveringUI); void cameraFollowObject(UpdateVariables& myVar, UpdateParameters& myParam); void hasCamMoved(); private: Color previousColor; glm::vec2 panFollowingOffset; float selectionThresholdSq = 100.0f; float defaultCamZoom = 0.5f; Vector2 lastTarget = camera.target; Vector2 lastOffset = camera.offset; float lastZoom = camera.zoom; float lastRotation = camera.rotation; }; class SceneCamera3D { public: Camera3D cam3D = { {200.0f, 200.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, 45.0f, CAMERA_PERSPECTIVE }; glm::vec3 offset = { 0.0f, 0.0f, 0.0f }; glm::vec3 currentSmoothedTarget = { 0.0f, 0.0f, 0.0f }; glm::vec3 followPosition; Vector3 panFollowingOffset; float defaultCamDist = 500.0f; Vector3 target = { 0.0f, 0.0f, 0.0f }; Vector3 panOffsetRight = { 0.0f, 0.0f, 0.0f }; Vector3 panOffsetUp = { 0.0f, 0.0f, 0.0f }; glm::vec3 camNormal = { 0.0f, 0.0f, 0.0f }; glm::vec3 camRight = { 0.0f, 0.0f, 0.0f }; glm::vec3 camUp = { 0.0f, 0.0f, 0.0f }; glm::vec3 worldUp = { 0.0f, 1.0f, 0.0f }; Vector3 firstPersonPosition = { 0.0f, 0.0f, 0.0f }; float arrowMoveSpeed = 250.0f; float distance = defaultCamDist; float angleX = 45.0f; float angleY = 30.0f; bool centerCamera; bool isFollowing; Camera3D cameraLogic(bool& isLoading, bool& isMouseNotHoveringUI, bool& firstPerson, bool& isShipEnabled); void cameraFollowObject(UpdateVariables& myVar, UpdateParameters& myParam); glm::vec3 prevOffset = { 0.0f, 0.0f, 0.0f }; glm::vec3 prevCurrentSmoothedTarget = { 0.0f, 0.0f, 0.0f }; glm::vec3 prevFollowPosition = { 0.0f, 0.0f, 0.0f }; Vector3 prevPanFollowingOffset = { 0.0f, 0.0f, 0.0f }; float prevDefaultCamDist = 500.0f; Vector3 prevTarget = { 0.0f, 0.0f, 0.0f }; Vector3 prevPanOffsetRight = { 0.0f, 0.0f, 0.0f }; Vector3 prevPanOffsetUp = { 0.0f, 0.0f, 0.0f }; glm::vec3 prevCamNormal = { 0.0f, 0.0f, 0.0f }; glm::vec3 prevCamRight = { 0.0f, 0.0f, 0.0f }; glm::vec3 prevCamUp = { 0.0f, 0.0f, 0.0f }; Vector3 prevFirstPersonPosition = { 0.0f, 0.0f, 0.0f }; bool HasCameraChanged() { const float epsilon = 0.0001f; bool changed = false; if (glm::length(offset - prevOffset) > epsilon) changed = true; if (glm::length(currentSmoothedTarget - prevCurrentSmoothedTarget) > epsilon) changed = true; if (glm::length(followPosition - prevFollowPosition) > epsilon) changed = true; if (glm::length(camNormal - prevCamNormal) > epsilon) changed = true; if (glm::length(camRight - prevCamRight) > epsilon) changed = true; if (glm::length(camUp - prevCamUp) > epsilon) changed = true; if (fabsf(panFollowingOffset.x - prevPanFollowingOffset.x) > epsilon || fabsf(panFollowingOffset.y - prevPanFollowingOffset.y) > epsilon || fabsf(panFollowingOffset.z - prevPanFollowingOffset.z) > epsilon) changed = true; if (fabsf(target.x - prevTarget.x) > epsilon || fabsf(target.y - prevTarget.y) > epsilon || fabsf(target.z - prevTarget.z) > epsilon) changed = true; if (fabsf(panOffsetRight.x - prevPanOffsetRight.x) > epsilon || fabsf(panOffsetRight.y - prevPanOffsetRight.y) > epsilon || fabsf(panOffsetRight.z - prevPanOffsetRight.z) > epsilon) changed = true; if (fabsf(panOffsetUp.x - prevPanOffsetUp.x) > epsilon || fabsf(panOffsetUp.y - prevPanOffsetUp.y) > epsilon || fabsf(panOffsetUp.z - prevPanOffsetUp.z) > epsilon) changed = true; if (fabsf(firstPersonPosition.x - prevFirstPersonPosition.x) > epsilon || fabsf(firstPersonPosition.y - prevFirstPersonPosition.y) > epsilon || fabsf(firstPersonPosition.z - prevFirstPersonPosition.z) > epsilon) changed = true; if (fabsf(defaultCamDist - prevDefaultCamDist) > epsilon) changed = true; prevOffset = offset; prevCurrentSmoothedTarget = currentSmoothedTarget; prevFollowPosition = followPosition; prevPanFollowingOffset = panFollowingOffset; prevDefaultCamDist = defaultCamDist; prevTarget = target; prevPanOffsetRight = panOffsetRight; prevPanOffsetUp = panOffsetUp; prevCamNormal = camNormal; prevCamRight = camRight; prevCamUp = camUp; prevFirstPersonPosition = firstPersonPosition; return changed; } }; ================================================ FILE: GalaxyEngine/include/UX/copyPaste.h ================================================ #pragma once #include "Particles/particle.h" #include "Physics/physics.h" #include "Physics/light.h" struct CopyPaste { glm::vec2 avgParticlePos = { 0.0f, 0.0f }; std::vector pParticleCopies; std::vector rParticleCopies; std::vector constraintsCopied; void copyPasteParticles(UpdateVariables& myVar, UpdateParameters& myParam, Physics& physics) { if (IO::shortcutPress(KEY_H) && !myParam.pParticlesSelected.empty()) { pParticleCopies.clear(); rParticleCopies.clear(); constraintsCopied.clear(); size_t maxId = 0; for (const auto& p : myParam.pParticles) { if (p.id > maxId) maxId = p.id; } if (myParam.neighborSearch.idToIndexTable.size() <= maxId) { myParam.neighborSearch.idToIndexTable.resize(maxId + 1); } for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.neighborSearch.idToIndexTable[myParam.pParticles[i].id] = i; } for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { pParticleCopies.push_back(ParticlePhysics(myParam.pParticles[i])); rParticleCopies.push_back(ParticleRendering(myParam.rParticles[i])); } } avgParticlePos = { 0.0f, 0.0f }; for (const ParticlePhysics& pCopy : pParticleCopies) { avgParticlePos += pCopy.pos; } if (!pParticleCopies.empty()) { avgParticlePos /= static_cast(pParticleCopies.size()); } for (const auto& constraint : physics.particleConstraints) { if (constraint.id1 >= myParam.neighborSearch.idToIndexTable.size() || constraint.id2 >= myParam.neighborSearch.idToIndexTable.size()) { continue; } size_t indexA = myParam.neighborSearch.idToIndexTable[constraint.id1]; size_t indexB = myParam.neighborSearch.idToIndexTable[constraint.id2]; if (indexA >= myParam.pParticles.size() || myParam.pParticles[indexA].id != constraint.id1) continue; if (indexB >= myParam.pParticles.size() || myParam.pParticles[indexB].id != constraint.id2) continue; if (myParam.rParticles[indexA].isSelected && myParam.rParticles[indexB].isSelected) { constraintsCopied.push_back(constraint); } } } Slingshot slingshot = slingshot.particleSlingshot(myVar, myParam.myCamera); if (IO::shortcutReleased(KEY_J)) { std::unordered_map oldIdToNewIdMap; for (ParticlePhysics pCopy : pParticleCopies) { glm::vec2 copyRelPos = pCopy.pos - avgParticlePos; pCopy.pos = myParam.myCamera.mouseWorldPos + copyRelPos; pCopy.vel += slingshot.length * slingshot.norm * 0.3f; pCopy.prevVel = pCopy.vel; pCopy.ke = 0.0f; pCopy.prevKe = 0.0f; uint32_t oldID = pCopy.id; uint32_t newID = globalId++; pCopy.id = newID; oldIdToNewIdMap[oldID] = newID; myParam.pParticles.push_back(ParticlePhysics(pCopy)); } for (ParticleRendering rCopy : rParticleCopies) { rCopy.isSelected = false; rCopy.isBeingDrawn = true; myParam.rParticles.push_back(ParticleRendering(rCopy)); } if (!myVar.hasAVX2) { myParam.neighborSearchV2.newGrid(myParam.pParticles); myParam.neighborSearchV2.neighborAmount(myParam.pParticles, myParam.rParticles); } else { myParam.neighborSearchV2AVX2.newGridAVX2(myParam.pParticles); myParam.neighborSearchV2AVX2.neighborAmount(myParam.pParticles, myParam.rParticles); } if (myParam.neighborSearch.idToIndexTable.size() <= globalId) { myParam.neighborSearch.idToIndexTable.resize(globalId + 1); } #pragma omp parallel for for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.neighborSearch.idToIndexTable[myParam.pParticles[i].id] = i; } bool constraintsAdded = false; for (const ParticleConstraint& srcConst : constraintsCopied) { auto itA = oldIdToNewIdMap.find(srcConst.id1); auto itB = oldIdToNewIdMap.find(srcConst.id2); if (itA != oldIdToNewIdMap.end() && itB != oldIdToNewIdMap.end()) { uint32_t newIDA = itA->second; uint32_t newIDB = itB->second; ParticleConstraint newConst = srcConst; newConst.id1 = newIDA; newConst.id2 = newIDB; physics.particleConstraints.push_back(newConst); constraintsAdded = true; } } if (constraintsAdded) { physics.constraintMap.clear(); for (ParticleConstraint& c : physics.particleConstraints) { if (c.isBroken) continue; uint64_t key = physics.makeKey(c.id1, c.id2); physics.constraintMap[key] = &c; } } for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.rParticles[i].isBeingDrawn = false; } myVar.isDragging = false; } } glm::vec3 avgParticlePos3D = { 0.0f, 0.0f, 0.0f }; std::vector pParticleCopies3D; std::vector rParticleCopies3D; std::vector constraintsCopied3D; void copyPasteParticles3D(UpdateVariables& myVar, UpdateParameters& myParam, Physics3D& physics3D) { if (IO::shortcutPress(KEY_H) && !myParam.pParticlesSelected3D.empty()) { pParticleCopies3D.clear(); rParticleCopies3D.clear(); constraintsCopied3D.clear(); size_t maxId = 0; for (const auto& p : myParam.pParticles3D) { if (p.id > maxId) maxId = p.id; } if (myParam.neighborSearch3D.idToIndexTable.size() <= maxId) { myParam.neighborSearch3D.idToIndexTable.resize(maxId + 1); } for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { myParam.neighborSearch3D.idToIndexTable[myParam.pParticles3D[i].id] = i; } for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { pParticleCopies3D.push_back(ParticlePhysics3D(myParam.pParticles3D[i])); rParticleCopies3D.push_back(ParticleRendering3D(myParam.rParticles3D[i])); } } avgParticlePos3D = { 0.0f, 0.0f, 0.0f }; for (const ParticlePhysics3D& pCopy : pParticleCopies3D) { avgParticlePos3D += pCopy.pos; } if (!pParticleCopies3D.empty()) { avgParticlePos3D /= static_cast(pParticleCopies3D.size()); } for (const auto& constraint : physics3D.particleConstraints) { if (constraint.id1 >= myParam.neighborSearch3D.idToIndexTable.size() || constraint.id2 >= myParam.neighborSearch3D.idToIndexTable.size()) { continue; } size_t indexA = myParam.neighborSearch3D.idToIndexTable[constraint.id1]; size_t indexB = myParam.neighborSearch3D.idToIndexTable[constraint.id2]; if (indexA >= myParam.pParticles3D.size() || myParam.pParticles3D[indexA].id != constraint.id1) continue; if (indexB >= myParam.pParticles3D.size() || myParam.pParticles3D[indexB].id != constraint.id2) continue; if (myParam.rParticles3D[indexA].isSelected && myParam.rParticles3D[indexB].isSelected) { constraintsCopied3D.push_back(constraint); } } } Slingshot3D slingshot = slingshot.particleSlingshot(myVar, myParam.brush3D.brushPos); if (IO::shortcutReleased(KEY_J)) { std::unordered_map oldIdToNewIdMap; for (ParticlePhysics3D pCopy : pParticleCopies3D) { glm::vec3 copyRelPos = pCopy.pos - avgParticlePos3D; pCopy.pos = myParam.brush3D.brushPos + copyRelPos; pCopy.vel += slingshot.length * slingshot.norm * 0.3f; pCopy.prevVel = pCopy.vel; pCopy.ke = 0.0f; pCopy.prevKe = 0.0f; uint32_t oldID = pCopy.id; uint32_t newID = globalId++; pCopy.id = newID; oldIdToNewIdMap[oldID] = newID; myParam.pParticles3D.push_back(ParticlePhysics3D(pCopy)); } for (ParticleRendering3D rCopy : rParticleCopies3D) { rCopy.isSelected = false; rCopy.isBeingDrawn = true; myParam.rParticles3D.push_back(ParticleRendering3D(rCopy)); } if (!myVar.hasAVX2) { myParam.neighborSearch3DV2.newGrid(myParam.pParticles3D); myParam.neighborSearch3DV2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D); } else { myParam.neighborSearch3DV2AVX2.newGridAVX2(myParam.pParticles3D); myParam.neighborSearch3DV2AVX2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D); } if (myParam.neighborSearch3D.idToIndexTable.size() <= globalId) { myParam.neighborSearch3D.idToIndexTable.resize(globalId + 1); } #pragma omp parallel for for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { myParam.neighborSearch3D.idToIndexTable[myParam.pParticles3D[i].id] = i; } bool constraintsAdded = false; for (const ParticleConstraint& srcConst : constraintsCopied3D) { auto itA = oldIdToNewIdMap.find(srcConst.id1); auto itB = oldIdToNewIdMap.find(srcConst.id2); if (itA != oldIdToNewIdMap.end() && itB != oldIdToNewIdMap.end()) { uint32_t newIDA = itA->second; uint32_t newIDB = itB->second; ParticleConstraint newConst = srcConst; newConst.id1 = newIDA; newConst.id2 = newIDB; physics3D.particleConstraints.push_back(newConst); constraintsAdded = true; } } if (constraintsAdded) { physics3D.constraintMap.clear(); for (ParticleConstraint& c : physics3D.particleConstraints) { if (c.isBroken) continue; uint64_t key = physics3D.makeKey(c.id1, c.id2); physics3D.constraintMap[key] = &c; } } for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { myParam.rParticles3D[i].isBeingDrawn = false; } myVar.isDragging = false; } } glm::vec2 avgOpticsPos = { 0.0f, 0.0f }; glm::vec2 avgShapeHelpersPos = { 0.0f, 0.0f }; std::vector wallCopies; std::vector shapeCopies; std::vector pointLightCopies; std::vector areaLightCopies; std::vector coneLightCopies; void copyPasteOptics(UpdateParameters& myParam, Lighting& lighting) { if (IO::shortcutPress(KEY_H) && (!lighting.walls.empty() || !lighting.pointLights.empty() || !lighting.areaLights.empty() || !lighting.coneLights.empty())) { pointLightCopies.clear(); areaLightCopies.clear(); coneLightCopies.clear(); avgOpticsPos = { 0.0f, 0.0f }; // Point Lights for (size_t i = 0; i < lighting.pointLights.size(); i++) { if (lighting.pointLights[i].isSelected) { pointLightCopies.push_back(PointLight(lighting.pointLights[i])); } } if (!pointLightCopies.empty()) { for (const PointLight& pl : pointLightCopies) { avgOpticsPos += pl.pos; } avgOpticsPos /= static_cast(pointLightCopies.size()); } // Area Lights for (size_t i = 0; i < lighting.areaLights.size(); i++) { if (lighting.areaLights[i].isSelected) { areaLightCopies.push_back(AreaLight(lighting.areaLights[i])); } } if (!areaLightCopies.empty()) { for (const AreaLight& al : areaLightCopies) { avgOpticsPos += al.vA; avgOpticsPos += al.vB; } avgOpticsPos /= static_cast(areaLightCopies.size() * 2.0f); } // Cone Lights for (size_t i = 0; i < lighting.coneLights.size(); i++) { if (lighting.coneLights[i].isSelected) { coneLightCopies.push_back(ConeLight(lighting.coneLights[i])); } } if (!coneLightCopies.empty()) { for (const ConeLight& cl : coneLightCopies) { avgOpticsPos += cl.vA; avgOpticsPos += cl.vB; } avgOpticsPos /= static_cast(coneLightCopies.size() * 2.0f); } wallCopies.clear(); shapeCopies.clear(); for (size_t i = 0; i < lighting.walls.size(); i++) { if (lighting.walls[i].isSelected) { wallCopies.push_back(Wall(lighting.walls[i])); } } std::unordered_set copiedWallIds; for (const Wall& wall : wallCopies) copiedWallIds.insert(wall.id); for (const Shape& shape : lighting.shapes) { bool copyThisShape = true; for (uint32_t wallId : shape.myWallIds) { if (copiedWallIds.find(wallId) == copiedWallIds.end()) { copyThisShape = false; break; } } if (copyThisShape) shapeCopies.push_back(shape); } if (!wallCopies.empty()) { for (const Wall& wall : wallCopies) { avgOpticsPos += wall.vA; avgOpticsPos += wall.vB; } avgOpticsPos /= static_cast(wallCopies.size() * 2); } avgShapeHelpersPos = avgOpticsPos; } if (IO::shortcutReleased(KEY_J)) { for (PointLight plCopy : pointLightCopies) { glm::vec2 copyRelPos = plCopy.pos - avgOpticsPos; plCopy.pos = myParam.myCamera.mouseWorldPos + copyRelPos; lighting.pointLights.push_back(PointLight(plCopy)); } for (AreaLight alCopy : areaLightCopies) { glm::vec2 copyRelVA = alCopy.vA - avgOpticsPos; glm::vec2 copyRelVB = alCopy.vB - avgOpticsPos; alCopy.vA = myParam.myCamera.mouseWorldPos + copyRelVA; alCopy.vB = myParam.myCamera.mouseWorldPos + copyRelVB; lighting.areaLights.push_back(AreaLight(alCopy)); } for (ConeLight clCopy : coneLightCopies) { glm::vec2 copyRelVA = clCopy.vA - avgOpticsPos; glm::vec2 copyRelVB = clCopy.vB - avgOpticsPos; clCopy.vA = myParam.myCamera.mouseWorldPos + copyRelVA; clCopy.vB = myParam.myCamera.mouseWorldPos + copyRelVB; lighting.coneLights.push_back(ConeLight(clCopy)); } for (PointLight plCopy : pointLightCopies) { glm::vec2 copyRelPos = plCopy.pos - avgOpticsPos; plCopy.pos = myParam.myCamera.mouseWorldPos + copyRelPos; lighting.pointLights.push_back(PointLight(plCopy)); } std::unordered_map oldToNewWallIds; std::unordered_set usedInShapes; for (const Shape& shape : shapeCopies) { for (uint32_t wallId : shape.myWallIds) usedInShapes.insert(wallId); } for (const Wall& wallCopy : wallCopies) { Wall newWall = wallCopy; glm::vec2 copyRelVA = newWall.vA - avgOpticsPos; glm::vec2 copyRelVB = newWall.vB - avgOpticsPos; newWall.vA = myParam.myCamera.mouseWorldPos + copyRelVA; newWall.vB = myParam.myCamera.mouseWorldPos + copyRelVB; uint32_t oldId = newWall.id; newWall.id = globalWallId++; oldToNewWallIds[oldId] = newWall.id; if (usedInShapes.find(oldId) == usedInShapes.end()) { newWall.shapeId = static_cast(-1); newWall.isShapeWall = false; } newWall.isSelected = false; lighting.walls.push_back(newWall); } for (Shape& shapeCopy : shapeCopies) { Shape newShape = shapeCopy; newShape.id = globalShapeId++; for (glm::vec2& helper : newShape.helpers) { glm::vec2 helperRelPos = helper - avgShapeHelpersPos; helper = myParam.myCamera.mouseWorldPos + helperRelPos; } newShape.myWallIds.clear(); for (uint32_t oldWallId : shapeCopy.myWallIds) { auto it = oldToNewWallIds.find(oldWallId); if (it != oldToNewWallIds.end()) { uint32_t newWallId = it->second; newShape.myWallIds.emplace_back(newWallId); Wall* w = lighting.getWallById(lighting.walls, newWallId); if (w) { w->shapeId = newShape.id; } } } newShape.walls = &lighting.walls; if (newShape.shapeType == draw) { newShape.helpers[0] = glm::vec2(0.0f); for (auto& wallId : newShape.myWallIds) { Wall* w = lighting.getWallById(lighting.walls, wallId); if (w) { newShape.helpers[0] += w->vA; newShape.helpers[0] += w->vB; } } newShape.helpers[0] /= static_cast(newShape.myWallIds.size()) * 2.0f; newShape.oldDrawHelperPos = newShape.helpers[0]; } if (newShape.shapeType == lens) { auto itC = oldToNewWallIds.find(shapeCopy.wallCId); if (itC != oldToNewWallIds.end()) { newShape.wallCId = itC->second; } auto itB = oldToNewWallIds.find(shapeCopy.wallBId); if (itB != oldToNewWallIds.end()) { newShape.wallBId = itB->second; } auto itA = oldToNewWallIds.find(shapeCopy.wallAId); if (itA != oldToNewWallIds.end()) { newShape.wallAId = itA->second; } newShape.globalLensPrev = newShape.helpers.back(); } lighting.shapes.push_back(newShape); } if (!wallCopies.empty() || !pointLightCopies.empty() || !areaLightCopies.empty() || !coneLightCopies.empty()) { lighting.shouldRender = true; } } } }; ================================================ FILE: GalaxyEngine/include/UX/randNum.h ================================================ #pragma once float getRandomFloat(); ================================================ FILE: GalaxyEngine/include/UX/saveSystem.h ================================================ #pragma once #include "Particles/particle.h" #include "Physics/light.h" #include "Physics/field.h" #include "Physics/SPH.h" #include "Physics/physics.h" #include "Physics/physics3D.h" #include "UI/UI.h" #include "parameters.h" struct ParticleConstraint; inline std::ostream& operator<<(std::ostream& os, const Vector2& vec) { return os << vec.x << " " << vec.y; } inline std::istream& operator>>(std::istream& is, Vector2& vec) { return is >> vec.x >> vec.y; } class SaveSystem { public: bool saveFlag = false; bool loadFlag = false; const uint32_t version160 = 160; const uint32_t version170 = 170; const uint32_t version171 = 171; const uint32_t version172 = 172; const uint32_t version173 = 173; const uint32_t version180 = 180; const uint32_t version190 = 190; const uint32_t currentVersion = version190; // VERY IMPORTANT. CHANGE THIS IF YOU MAKE ANY CHANGES TO THE SAVE SYSTEM. VERSION "1.6.0" = 160, VERSION "1.6.12" = 1612 template void paramIO(const std::string& filename, YAML::Emitter& out, std::string key, T& value) { int ucToInt = 0; if constexpr (!std::is_same_v) { if (saveFlag) { out << YAML::BeginMap; out << YAML::Key << key; out << YAML::Value << value; std::cout << "Parameter: " << key.c_str() << " | Value: " << value << std::endl; out << YAML::EndMap; } } else { ucToInt = static_cast(value); if (saveFlag) { out << YAML::BeginMap; out << YAML::Key << key; out << YAML::Value << ucToInt; std::cout << "Parameter: " << key.c_str() << " | Value: " << ucToInt << std::endl; out << YAML::EndMap; } } if (loadFlag && !saveFlag) { std::ifstream inFile(filename, std::ios::in | std::ios::binary); if (!inFile.is_open()) { std::cerr << "Failed to open file for reading: " << filename << std::endl; return; } const std::string sepToken = "---PARTICLE BINARY DATA---"; std::string line; std::string yamlText; bool sawSeparator = false; while (std::getline(inFile, line)) { if (line.find(sepToken) != std::string::npos) { sawSeparator = true; break; } yamlText += line + "\n"; } inFile.close(); try { std::vector documents = YAML::LoadAll(yamlText); bool found = false; for (const auto& doc : documents) { if (!doc || !doc[key]) continue; if constexpr (!std::is_same_v) { value = doc[key].as(); std::cout << "Loaded " << key << ": " << value << std::endl; } else { int tempInt = doc[key].as(); value = static_cast(tempInt); std::cout << "Loaded " << key << ": " << tempInt << std::endl; } found = true; break; } if (!found) { std::cerr << "No " << key << " found in any YAML document in the file!" << std::endl; } } catch (const YAML::Exception& e) { std::cerr << "YAML error while loading key \"" << key << "\": " << e.what() << std::endl; } } } void saveSystem(const std::string& filename, UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, Physics& physics, Physics3D& physics3D, Lighting& lighting, Field& field); bool deserializeParticleSystem(const std::string& filename, std::string& yamlString, UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, Physics& physics, Physics3D& physics3D, Lighting& lighting, bool loadFlag) { if (!loadFlag) return false; std::fstream file(filename, std::ios::in | std::ios::binary); if (!file.is_open()) { std::cerr << "Failed to open file for reading: " << filename << std::endl; return false; } const std::string sepToken = "---PARTICLE BINARY DATA---"; std::string line; bool foundSeparator = false; std::streampos binaryStartPos = 0; yamlString.clear(); while (std::getline(file, line)) { if (line.find(sepToken) != std::string::npos) { foundSeparator = true; binaryStartPos = file.tellg(); break; } yamlString += line + "\n"; } if (!foundSeparator) { std::cerr << "Separator not found. Cannot load binary data.\n"; file.close(); return false; } file.seekg(binaryStartPos); uint32_t loadedVersion; file.read(reinterpret_cast(&loadedVersion), sizeof(loadedVersion)); if (loadedVersion != currentVersion) { std::cerr << "Loaded file from older version! Expected version: " << currentVersion << " Loaded version: " << loadedVersion << std::endl; } else { std::cout << "File version: " << loadedVersion << std::endl; } if (loadedVersion == currentVersion) { deserializeVersion190(file, myParam, physics, physics3D, lighting, myVar.is3DMode); } else if (loadedVersion == version180) { deserializeVersion190(file, myParam, physics, physics3D, lighting, myVar.is3DMode); } if (myVar.isOpticsEnabled) { lighting.shouldRender = true; } lighting.wallPointers.clear(); for (Wall& wall : lighting.walls) { lighting.wallPointers.push_back(&wall); } lighting.bvh.build(lighting.wallPointers); myVar.longExposureFlag = false; myVar.loadDropDownMenus = true; file.close(); uint32_t maxId = 0; for (const auto& particle : myParam.pParticles) { if (particle.id > maxId) maxId = particle.id; } globalId = maxId + 1; return true; } bool deserializeVersion190(std::istream& file, UpdateParameters& myParam, Physics& physics, Physics3D& physics3D, Lighting& lighting, bool& is3DMode) { file.read(reinterpret_cast(&globalId), sizeof(globalId)); file.read(reinterpret_cast(&globalShapeId), sizeof(globalShapeId)); file.read(reinterpret_cast(&globalWallId), sizeof(globalWallId)); myParam.pParticles.clear(); myParam.rParticles.clear(); myParam.pParticles3D.clear(); myParam.rParticles3D.clear(); myParam.pParticlesSelected.clear(); myParam.rParticlesSelected.clear(); myParam.pParticlesSelected3D.clear(); myParam.rParticlesSelected3D.clear(); if (!is3DMode) { uint32_t particleCount; file.read(reinterpret_cast(&particleCount), sizeof(particleCount)); myParam.pParticles.reserve(particleCount); myParam.rParticles.reserve(particleCount); for (uint32_t i = 0; i < particleCount; i++) { ParticlePhysics p; ParticleRendering r; file.read(reinterpret_cast(&p.pos), sizeof(p.pos)); file.read(reinterpret_cast(&p.predPos), sizeof(p.predPos)); file.read(reinterpret_cast(&p.vel), sizeof(p.vel)); file.read(reinterpret_cast(&p.prevVel), sizeof(p.prevVel)); file.read(reinterpret_cast(&p.predVel), sizeof(p.predVel)); file.read(reinterpret_cast(&p.acc), sizeof(p.acc)); file.read(reinterpret_cast(&p.mass), sizeof(p.mass)); file.read(reinterpret_cast(&p.press), sizeof(p.press)); file.read(reinterpret_cast(&p.pressTmp), sizeof(p.pressTmp)); file.read(reinterpret_cast(&p.pressF), sizeof(p.pressF)); file.read(reinterpret_cast(&p.dens), sizeof(p.dens)); file.read(reinterpret_cast(&p.predDens), sizeof(p.predDens)); file.read(reinterpret_cast(&p.sphMass), sizeof(p.sphMass)); file.read(reinterpret_cast(&p.restDens), sizeof(p.restDens)); file.read(reinterpret_cast(&p.stiff), sizeof(p.stiff)); file.read(reinterpret_cast(&p.visc), sizeof(p.visc)); file.read(reinterpret_cast(&p.cohesion), sizeof(p.cohesion)); file.read(reinterpret_cast(&p.temp), sizeof(p.temp)); file.read(reinterpret_cast(&p.ke), sizeof(p.ke)); file.read(reinterpret_cast(&p.prevKe), sizeof(p.prevKe)); file.read(reinterpret_cast(&p.mortonKey), sizeof(p.mortonKey)); file.read(reinterpret_cast(&p.id), sizeof(p.id)); file.read(reinterpret_cast(&p.isHotPoint), sizeof(p.isHotPoint)); file.read(reinterpret_cast(&p.hasSolidified), sizeof(p.hasSolidified)); file.read(reinterpret_cast(&r.color), sizeof(r.color)); file.read(reinterpret_cast(&r.pColor), sizeof(r.pColor)); file.read(reinterpret_cast(&r.sColor), sizeof(r.sColor)); file.read(reinterpret_cast(&r.sphColor), sizeof(r.sphColor)); file.read(reinterpret_cast(&r.size), sizeof(r.size)); file.read(reinterpret_cast(&r.uniqueColor), sizeof(r.uniqueColor)); file.read(reinterpret_cast(&r.isSolid), sizeof(r.isSolid)); file.read(reinterpret_cast(&r.canBeSubdivided), sizeof(r.canBeSubdivided)); file.read(reinterpret_cast(&r.canBeResized), sizeof(r.canBeResized)); file.read(reinterpret_cast(&r.isDarkMatter), sizeof(r.isDarkMatter)); file.read(reinterpret_cast(&r.isSPH), sizeof(r.isSPH)); file.read(reinterpret_cast(&r.isSelected), sizeof(r.isSelected)); file.read(reinterpret_cast(&r.isGrabbed), sizeof(r.isGrabbed)); file.read(reinterpret_cast(&r.previousSize), sizeof(r.previousSize)); file.read(reinterpret_cast(&r.neighbors), sizeof(r.neighbors)); file.read(reinterpret_cast(&r.totalRadius), sizeof(r.totalRadius)); file.read(reinterpret_cast(&r.lifeSpan), sizeof(r.lifeSpan)); file.read(reinterpret_cast(&r.sphLabel), sizeof(r.sphLabel)); file.read(reinterpret_cast(&r.isPinned), sizeof(r.isPinned)); file.read(reinterpret_cast(&r.isBeingDrawn), sizeof(r.isBeingDrawn)); file.read(reinterpret_cast(&r.spawnCorrectIter), sizeof(r.spawnCorrectIter)); file.read(reinterpret_cast(&r.turbulence), sizeof(r.turbulence)); myParam.pParticles.push_back(p); myParam.rParticles.push_back(r); } } else { uint32_t particleCount; file.read(reinterpret_cast(&particleCount), sizeof(particleCount)); myParam.pParticles3D.reserve(particleCount); myParam.rParticles3D.reserve(particleCount); for (uint32_t i = 0; i < particleCount; i++) { ParticlePhysics3D p; ParticleRendering3D r; file.read(reinterpret_cast(&p.pos), sizeof(p.pos)); file.read(reinterpret_cast(&p.predPos), sizeof(p.predPos)); file.read(reinterpret_cast(&p.vel), sizeof(p.vel)); file.read(reinterpret_cast(&p.prevVel), sizeof(p.prevVel)); file.read(reinterpret_cast(&p.predVel), sizeof(p.predVel)); file.read(reinterpret_cast(&p.acc), sizeof(p.acc)); file.read(reinterpret_cast(&p.mass), sizeof(p.mass)); file.read(reinterpret_cast(&p.press), sizeof(p.press)); file.read(reinterpret_cast(&p.pressTmp), sizeof(p.pressTmp)); file.read(reinterpret_cast(&p.pressF), sizeof(p.pressF)); file.read(reinterpret_cast(&p.dens), sizeof(p.dens)); file.read(reinterpret_cast(&p.predDens), sizeof(p.predDens)); file.read(reinterpret_cast(&p.sphMass), sizeof(p.sphMass)); file.read(reinterpret_cast(&p.restDens), sizeof(p.restDens)); file.read(reinterpret_cast(&p.stiff), sizeof(p.stiff)); file.read(reinterpret_cast(&p.visc), sizeof(p.visc)); file.read(reinterpret_cast(&p.cohesion), sizeof(p.cohesion)); file.read(reinterpret_cast(&p.temp), sizeof(p.temp)); file.read(reinterpret_cast(&p.ke), sizeof(p.ke)); file.read(reinterpret_cast(&p.prevKe), sizeof(p.prevKe)); file.read(reinterpret_cast(&p.mortonKey), sizeof(p.mortonKey)); file.read(reinterpret_cast(&p.id), sizeof(p.id)); file.read(reinterpret_cast(&p.isHotPoint), sizeof(p.isHotPoint)); file.read(reinterpret_cast(&p.hasSolidified), sizeof(p.hasSolidified)); file.read(reinterpret_cast(&r.color), sizeof(r.color)); file.read(reinterpret_cast(&r.pColor), sizeof(r.pColor)); file.read(reinterpret_cast(&r.sColor), sizeof(r.sColor)); file.read(reinterpret_cast(&r.sphColor), sizeof(r.sphColor)); file.read(reinterpret_cast(&r.size), sizeof(r.size)); file.read(reinterpret_cast(&r.uniqueColor), sizeof(r.uniqueColor)); file.read(reinterpret_cast(&r.isSolid), sizeof(r.isSolid)); file.read(reinterpret_cast(&r.canBeSubdivided), sizeof(r.canBeSubdivided)); file.read(reinterpret_cast(&r.canBeResized), sizeof(r.canBeResized)); file.read(reinterpret_cast(&r.isDarkMatter), sizeof(r.isDarkMatter)); file.read(reinterpret_cast(&r.isSPH), sizeof(r.isSPH)); file.read(reinterpret_cast(&r.isSelected), sizeof(r.isSelected)); file.read(reinterpret_cast(&r.isGrabbed), sizeof(r.isGrabbed)); file.read(reinterpret_cast(&r.previousSize), sizeof(r.previousSize)); file.read(reinterpret_cast(&r.neighbors), sizeof(r.neighbors)); file.read(reinterpret_cast(&r.totalRadius), sizeof(r.totalRadius)); file.read(reinterpret_cast(&r.lifeSpan), sizeof(r.lifeSpan)); file.read(reinterpret_cast(&r.sphLabel), sizeof(r.sphLabel)); file.read(reinterpret_cast(&r.isPinned), sizeof(r.isPinned)); file.read(reinterpret_cast(&r.isBeingDrawn), sizeof(r.isBeingDrawn)); file.read(reinterpret_cast(&r.spawnCorrectIter), sizeof(r.spawnCorrectIter)); file.read(reinterpret_cast(&r.turbulence), sizeof(r.turbulence)); myParam.pParticles3D.push_back(p); myParam.rParticles3D.push_back(r); } } if (!is3DMode) { physics.particleConstraints.clear(); uint32_t numConstraints = 0; file.read(reinterpret_cast(&numConstraints), sizeof(numConstraints)); if (numConstraints > 0) { physics.particleConstraints.resize(numConstraints); file.read( reinterpret_cast(physics.particleConstraints.data()), numConstraints * sizeof(ParticleConstraint) ); physics.constraintMap.clear(); for (auto& constraint : physics.particleConstraints) { uint64_t key = physics.makeKey(constraint.id1, constraint.id2); physics.constraintMap[key] = &constraint; } } } else { physics3D.particleConstraints.clear(); uint32_t numConstraints = 0; file.read(reinterpret_cast(&numConstraints), sizeof(numConstraints)); if (numConstraints > 0) { physics3D.particleConstraints.resize(numConstraints); file.read( reinterpret_cast(physics3D.particleConstraints.data()), numConstraints * sizeof(ParticleConstraint) ); physics3D.constraintMap.clear(); for (auto& constraint : physics3D.particleConstraints) { uint64_t key = physics3D.makeKey(constraint.id1, constraint.id2); physics3D.constraintMap[key] = &constraint; } } } uint32_t wallCount = 0; file.read(reinterpret_cast(&wallCount), sizeof(wallCount)); lighting.walls.clear(); lighting.walls.reserve(wallCount); for (uint32_t i = 0; i < wallCount; i++) { Wall w; file.read(reinterpret_cast(&w.vA), sizeof(w.vA)); file.read(reinterpret_cast(&w.vB), sizeof(w.vB)); file.read(reinterpret_cast(&w.normal), sizeof(w.normal)); file.read(reinterpret_cast(&w.normalVA), sizeof(w.normalVA)); file.read(reinterpret_cast(&w.normalVB), sizeof(w.normalVB)); file.read(reinterpret_cast(&w.isBeingSpawned), sizeof(w.isBeingSpawned)); file.read(reinterpret_cast(&w.vAisBeingMoved), sizeof(w.vAisBeingMoved)); file.read(reinterpret_cast(&w.vBisBeingMoved), sizeof(w.vBisBeingMoved)); file.read(reinterpret_cast(&w.apparentColor), sizeof(w.apparentColor)); file.read(reinterpret_cast(&w.baseColor), sizeof(w.baseColor)); file.read(reinterpret_cast(&w.specularColor), sizeof(w.specularColor)); file.read(reinterpret_cast(&w.refractionColor), sizeof(w.refractionColor)); file.read(reinterpret_cast(&w.emissionColor), sizeof(w.emissionColor)); file.read(reinterpret_cast(&w.baseColorVal), sizeof(w.baseColorVal)); file.read(reinterpret_cast(&w.specularColorVal), sizeof(w.specularColorVal)); file.read(reinterpret_cast(&w.refractionColorVal), sizeof(w.refractionColorVal)); file.read(reinterpret_cast(&w.specularRoughness), sizeof(w.specularRoughness)); file.read(reinterpret_cast(&w.refractionRoughness), sizeof(w.refractionRoughness)); file.read(reinterpret_cast(&w.refractionAmount), sizeof(w.refractionAmount)); file.read(reinterpret_cast(&w.IOR), sizeof(w.IOR)); file.read(reinterpret_cast(&w.dispersionStrength), sizeof(w.dispersionStrength)); file.read(reinterpret_cast(&w.isShapeWall), sizeof(w.isShapeWall)); file.read(reinterpret_cast(&w.isShapeClosed), sizeof(w.isShapeClosed)); file.read(reinterpret_cast(&w.shapeId), sizeof(w.shapeId)); file.read(reinterpret_cast(&w.id), sizeof(w.id)); file.read(reinterpret_cast(&w.isSelected), sizeof(w.isSelected)); lighting.walls.push_back(w); } uint32_t numShapes = 0; file.read(reinterpret_cast(&numShapes), sizeof(numShapes)); lighting.shapes.clear(); lighting.shapes.reserve(numShapes); if (numShapes > 0) { for (uint32_t i = 0; i < numShapes; i++) { Shape s; uint32_t wallIdCount = 0; file.read(reinterpret_cast(&wallIdCount), sizeof(wallIdCount)); s.myWallIds.resize(wallIdCount); file.read(reinterpret_cast(s.myWallIds.data()), wallIdCount * sizeof(uint32_t)); uint32_t vertCount = 0; file.read(reinterpret_cast(&vertCount), sizeof(vertCount)); s.polygonVerts.resize(vertCount); file.read(reinterpret_cast(s.polygonVerts.data()), vertCount * sizeof(glm::vec2)); uint32_t helpersCount = 0; file.read(reinterpret_cast(&helpersCount), sizeof(helpersCount)); s.helpers.resize(helpersCount); file.read(reinterpret_cast(s.helpers.data()), helpersCount * sizeof(glm::vec2)); file.read(reinterpret_cast(&s.baseColor), sizeof(s.baseColor)); file.read(reinterpret_cast(&s.specularColor), sizeof(s.specularColor)); file.read(reinterpret_cast(&s.refractionColor), sizeof(s.refractionColor)); file.read(reinterpret_cast(&s.emissionColor), sizeof(s.emissionColor)); file.read(reinterpret_cast(&s.specularRoughness), sizeof(s.specularRoughness)); file.read(reinterpret_cast(&s.refractionRoughness), sizeof(s.refractionRoughness)); file.read(reinterpret_cast(&s.refractionAmount), sizeof(s.refractionAmount)); file.read(reinterpret_cast(&s.IOR), sizeof(s.IOR)); file.read(reinterpret_cast(&s.dispersionStrength), sizeof(s.dispersionStrength)); file.read(reinterpret_cast(&s.id), sizeof(s.id)); file.read(reinterpret_cast(&s.h1), sizeof(s.h1)); file.read(reinterpret_cast(&s.h2), sizeof(s.h2)); file.read(reinterpret_cast(&s.isBeingSpawned), sizeof(s.isBeingSpawned)); file.read(reinterpret_cast(&s.isBeingMoved), sizeof(s.isBeingMoved)); file.read(reinterpret_cast(&s.isShapeClosed), sizeof(s.isShapeClosed)); file.read(reinterpret_cast(&s.shapeType), sizeof(s.shapeType)); file.read(reinterpret_cast(&s.drawHoverHelpers), sizeof(s.drawHoverHelpers)); file.read(reinterpret_cast(&s.oldDrawHelperPos), sizeof(s.oldDrawHelperPos)); // Lens variables file.read(reinterpret_cast(&s.secondHelper), sizeof(s.secondHelper)); file.read(reinterpret_cast(&s.thirdHelper), sizeof(s.thirdHelper)); file.read(reinterpret_cast(&s.fourthHelper), sizeof(s.fourthHelper)); file.read(reinterpret_cast(&s.Tempsh2Length), sizeof(s.Tempsh2Length)); file.read(reinterpret_cast(&s.Tempsh2LengthSymmetry), sizeof(s.Tempsh2LengthSymmetry)); file.read(reinterpret_cast(&s.tempDist), sizeof(s.tempDist)); file.read(reinterpret_cast(&s.moveH2), sizeof(s.moveH2)); file.read(reinterpret_cast(&s.isThirdBeingMoved), sizeof(s.isThirdBeingMoved)); file.read(reinterpret_cast(&s.isFourthBeingMoved), sizeof(s.isFourthBeingMoved)); file.read(reinterpret_cast(&s.isFifthBeingMoved), sizeof(s.isFifthBeingMoved)); file.read(reinterpret_cast(&s.isFifthFourthMoved), sizeof(s.isFifthFourthMoved)); file.read(reinterpret_cast(&s.symmetricalLens), sizeof(s.symmetricalLens)); file.read(reinterpret_cast(&s.wallAId), sizeof(s.wallAId)); file.read(reinterpret_cast(&s.wallBId), sizeof(s.wallBId)); file.read(reinterpret_cast(&s.wallCId), sizeof(s.wallCId)); file.read(reinterpret_cast(&s.lensSegments), sizeof(s.lensSegments)); file.read(reinterpret_cast(&s.startAngle), sizeof(s.startAngle)); file.read(reinterpret_cast(&s.endAngle), sizeof(s.endAngle)); file.read(reinterpret_cast(&s.startAngleSymmetry), sizeof(s.startAngleSymmetry)); file.read(reinterpret_cast(&s.endAngleSymmetry), sizeof(s.endAngleSymmetry)); file.read(reinterpret_cast(&s.center), sizeof(s.center)); file.read(reinterpret_cast(&s.radius), sizeof(s.radius)); file.read(reinterpret_cast(&s.centerSymmetry), sizeof(s.centerSymmetry)); file.read(reinterpret_cast(&s.radiusSymmetry), sizeof(s.radiusSymmetry)); file.read(reinterpret_cast(&s.arcEnd), sizeof(s.arcEnd)); file.read(reinterpret_cast(&s.globalLensPrev), sizeof(s.globalLensPrev)); s.walls = &lighting.walls; lighting.shapes.push_back(s); } } uint32_t pointLightCount = 0; file.read(reinterpret_cast(&pointLightCount), sizeof(pointLightCount)); lighting.pointLights.clear(); lighting.pointLights.reserve(pointLightCount); for (uint32_t i = 0; i < pointLightCount; i++) { PointLight p = lighting.pointLights[i]; file.read(reinterpret_cast(&p.pos), sizeof(p.pos)); file.read(reinterpret_cast(&p.isBeingMoved), sizeof(p.isBeingMoved)); file.read(reinterpret_cast(&p.color), sizeof(p.color)); file.read(reinterpret_cast(&p.apparentColor), sizeof(p.apparentColor)); file.read(reinterpret_cast(&p.isSelected), sizeof(p.isSelected)); lighting.pointLights.push_back(p); } uint32_t areaLightCount = 0; file.read(reinterpret_cast(&areaLightCount), sizeof(areaLightCount)); lighting.areaLights.clear(); lighting.areaLights.reserve(areaLightCount); for (uint32_t i = 0; i < areaLightCount; i++) { AreaLight a = lighting.areaLights[i]; file.read(reinterpret_cast(&a.vA), sizeof(a.vA)); file.read(reinterpret_cast(&a.vB), sizeof(a.vB)); file.read(reinterpret_cast(&a.isBeingSpawned), sizeof(a.isBeingSpawned)); file.read(reinterpret_cast(&a.vAisBeingMoved), sizeof(a.vAisBeingMoved)); file.read(reinterpret_cast(&a.vBisBeingMoved), sizeof(a.vBisBeingMoved)); file.read(reinterpret_cast(&a.color), sizeof(a.color)); file.read(reinterpret_cast(&a.apparentColor), sizeof(a.apparentColor)); file.read(reinterpret_cast(&a.isSelected), sizeof(a.isSelected)); file.read(reinterpret_cast(&a.spread), sizeof(a.spread)); lighting.areaLights.push_back(a); } uint32_t coneLightCount = 0; file.read(reinterpret_cast(&coneLightCount), sizeof(coneLightCount)); lighting.coneLights.clear(); lighting.coneLights.reserve(coneLightCount); for (uint32_t i = 0; i < coneLightCount; i++) { ConeLight l = lighting.coneLights[i]; file.read(reinterpret_cast(&l.vA), sizeof(l.vA)); file.read(reinterpret_cast(&l.vB), sizeof(l.vB)); file.read(reinterpret_cast(&l.isBeingSpawned), sizeof(l.isBeingSpawned)); file.read(reinterpret_cast(&l.vAisBeingMoved), sizeof(l.vAisBeingMoved)); file.read(reinterpret_cast(&l.vBisBeingMoved), sizeof(l.vBisBeingMoved)); file.read(reinterpret_cast(&l.color), sizeof(l.color)); file.read(reinterpret_cast(&l.apparentColor), sizeof(l.apparentColor)); file.read(reinterpret_cast(&l.isSelected), sizeof(l.isSelected)); file.read(reinterpret_cast(&l.spread), sizeof(l.spread)); lighting.coneLights.push_back(l); } return true; } void saveLoadLogic(UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, Physics& physics, Physics3D& physics3D, Lighting& lighting, Field& field); private: ImVec2 loadMenuSize = { 600.0f, 500.0f }; float buttonHeight = 30.0f; std::vector filePaths; int saveIndex = 0; }; ================================================ FILE: GalaxyEngine/include/UX/screenCapture.h ================================================ #pragma once struct AVFormatContext; struct AVCodecContext; struct AVStream; struct SwsContext; struct AVFrame; struct UpdateVariables; struct UpdateParameters; class ScreenCapture { public: bool exportMemoryFrames = false; bool deleteFrames = false; bool isExportingFrames = false; bool isFunctionRecording = false; bool isVideoExportEnabled = true; bool isSafeFramesEnabled = true; bool isExportFramesEnabled = false; bool showSaveConfirmationDialog = false; std::string lastVideoPath; bool videoHasBeenSaved = false; std::string actualSavedVideoFolder; std::string actualSavedVideoName; bool cancelRecording = false; bool screenGrab(RenderTexture2D &myParticlesTexture, UpdateVariables &myVar, UpdateParameters &myParam); private: std::string generateVideoFilename(); void cleanupFFmpeg(); void exportFrameToFile(const Image &frame, const std::string &videoFolder, const std::string &videoName, int frameNumber); void exportMemoryFramesToDisk(); void discardMemoryFrames(); void createFramesFolder(const std::string &folderPath); void discardRecording(); int screenshotIndex = 0; // Used for screenshot naming std::vector myFrames; // Used for storing captured frames int diskModeFrameIdx = 0; // Used for tracking frames in disk mode std::string folderName; // Used for storing the folder name std::string outFileName; // Used for storing the output file name std::string videoFolder; // Used for storing the video folder path AVFormatContext *pFormatCtx = nullptr; AVCodecContext *pCodecCtx = nullptr; AVStream *pStream = nullptr; SwsContext *swsCtx = nullptr; AVFrame *frame = nullptr; int frameIndex = 0; }; ================================================ FILE: GalaxyEngine/include/globalLogic.h ================================================ #pragma once #include "Particles/particle.h" #include "Particles/particleTrails.h" #include "Particles/particleSelection.h" #include "Particles/particleSubdivision.h" #include "Particles/densitySize.h" #include "Particles/particleColorVisuals.h" #include "Particles/particleDeletion.h" #include "Particles/particlesSpawning.h" #include "Particles/particleSpaceship.h" #include "Physics/quadtree.h" #include "Physics/slingshot.h" #include "Physics/morton.h" #include "Physics/physics.h" #include "Physics/physics3D.h" #include "Physics/SPH.h" #include "Physics/SPH3D.h" #include "Physics/light.h" #include "Physics/field.h" #include "UI/brush.h" #include "UI/rightClickSettings.h" #include "UI/controls.h" #include "UI/UI.h" #include "Sound/sound.h" #include "UX/screenCapture.h" #include "UX/camera.h" #include "UX/saveSystem.h" #include "UX/randNum.h" #include "UX/copyPaste.h" #include "Renderer/rayMarching.h" #include "parameters.h" extern UpdateParameters myParam; extern UpdateVariables myVar; extern UI myUI; extern Physics physics; extern Physics3D physics3D; extern ParticleSpaceship ship; extern SPH sph; extern SPH3D sph3D; extern SaveSystem save; extern GESound geSound; extern Lighting lighting; extern CopyPaste copyPaste; extern RayMarcher rayMarcher; extern Field field; struct ParticleBounds { float minX, maxX, minY, maxY; }; // THIS FUNCTION IS MEANT FOR QUICK DEBUGGING WHERE YOU NEED TO CHECK A SPECIFIC PARTICLE'S VARIABLES void selectedParticleDebug(); void pinParticles(); void pinParticles3D(); void buildKernels(); void freeGPUMemory(); void updateScene(); void mode3D(); void playBackLogic(Texture2D& particleBlurTex); void drawMode3DRecording(Texture2D& particleBlurTex); void drawMode3DNonRecording(); void drawScene(Texture2D& particleBlurTex, RenderTexture2D& myRayTracingTexture, RenderTexture2D& myUITexture, RenderTexture2D& myMiscTexture, bool& fadeActive, bool& introActive); void enableMultiThreading(); void fullscreenToggle(int& lastScreenWidth, int& lastScreenHeight, bool& wasFullscreen, bool& lastScreenState, RenderTexture2D& myParticlesTexture, RenderTexture2D& myUITexture); void drawConstraints(); void drawConstraints3D(); void saveConfigIfChanged(); void saveConfig(); void loadConfig(); RenderTexture2D CreateFloatRenderTexture(int w, int h); bool hasAVX2Support(); ================================================ FILE: GalaxyEngine/include/parameters.h ================================================ #pragma once #include "Particles/particle.h" #include "Particles/particleSubdivision.h" #include "Particles/densitySize.h" #include "Particles/particleColorVisuals.h" #include "Particles/particleTrails.h" #include "Particles/particleSelection.h" #include "Particles/particlesSpawning.h" #include "Particles/neighborSearch.h" #include "Particles/clusterMouseHelper.h" #include "Physics/morton.h" #include "UI/brush.h" #include "UI/rightClickSettings.h" #include "UI/controls.h" #include "UX/screenCapture.h" struct PlaybackParticle { glm::vec3 pos; float previousSize; float size; uint32_t id; Color color; }; struct UpdateParameters { std::vector pParticles; std::vector rParticles; std::vector pParticlesSelected; std::vector rParticlesSelected; std::vector pParticles3D; std::vector rParticles3D; std::vector pParticlesSelected3D; std::vector rParticlesSelected3D; std::vector pParticles3DPlaybackResume; std::vector rParticles3DPlaybackResume; std::vector> playbackFrames; std::vector trailDots; ScreenCapture screenCapture; Morton morton; ParticleTrails trails; ParticleSelection particleSelection; ParticleSelection3D particleSelection3D; SceneCamera myCamera; SceneCamera3D myCamera3D; Brush brush; Brush3D brush3D; ParticleSubdivision subdivision; DensitySize densitySize; ColorVisuals colorVisuals; RightClickSettings rightClickSettings; Controls controls; std::unordered_map pParticleLookup; ParticleDeletion particleDeletion; ParticlesSpawning particlesSpawning; ParticlesSpawning3D particlesSpawning3D; NeighborSearch neighborSearch; NeighborSearch3D neighborSearch3D; NeighborSearchV2 neighborSearchV2; NeighborSearchV2AVX2 neighborSearchV2AVX2; NeighborSearch3DV2 neighborSearch3DV2; NeighborSearch3DV2AVX2 neighborSearch3DV2AVX2; }; struct UpdateVariables { int screenWidth = 1920; int screenHeight = 1080; float halfScreenWidth = screenWidth * 0.5f; float halfScreenHeight = screenHeight * 0.5f; float screenRatioX = 0.0f; float screenRatioY = 0.0f; glm::vec2 domainSize = { 3840.0f, 2160.0f }; glm::vec3 domainSize3D = { 1300.0f, 1300.0f, 1300.0f }; float halfDomainWidth = domainSize.x * 0.5f; float halfDomainHeight = domainSize.y * 0.5f; float halfDomain3DWidth = domainSize3D.x * 0.5f; float halfDomain3DHeight = domainSize3D.y * 0.5f; float halfDomain3DDepth = domainSize3D.z * 0.5f; bool fullscreenState = true; bool exitGame = false; int targetFPS = 1000; float G = 6.674e-11; float gravityMultiplier = 1.0f; float softening = 1.5f; float theta = 0.8f; float timeStepMultiplier = 1.0f; float sphMaxVel = 250.0f; float globalHeatConductivity = 0.045f; float globalAmbientHeatRate = 1.0f; float ambientTemp = 274.0f; static float particleBaseMass; int maxLeafParticles = 1; float minLeafSize = 1.0f; const float fixedDeltaTime = 0.045f; bool isTimePlaying = true; float timeFactor = 1.0f; bool isGlobalTrailsEnabled = false; bool isSelectedTrailsEnabled = false; bool isLocalTrailsEnabled = false; bool isPeriodicBoundaryEnabled = true; bool isMultiThreadingEnabled = true; bool isBarnesHutEnabled = true; bool isDarkMatterEnabled = true; bool isDensitySizeEnabled = false; bool isForceSizeEnabled = false; bool isShipGasEnabled = true; bool isSPHEnabled = false; bool sphGround = false; bool isTempEnabled = false; bool constraintsEnabled = false; bool isOpticsEnabled = false; bool isGPUEnabled = false; bool isMergerEnabled = false; bool longExposureFlag = false; int longExposureDuration = 200; int longExposureCurrent = 0; bool isSpawningAllowed = true; float particleTextureHalfSize = 16.0f; int trailMaxLength = 350; static ImVec4 colWindowBg; //ImGui style colors static ImVec4 colButton; static ImVec4 colButtonHover; static ImVec4 colButtonPress; static ImVec4 colButtonActive; static ImVec4 colButtonActiveHover; static ImVec4 colButtonActivePress; static ImVec4 colButtonRedActive; static ImVec4 colButtonRedActiveHover; static ImVec4 colButtonRedActivePress; // ImGui slider colors static ImVec4 colSliderGrab; static ImVec4 colSliderGrabActive; static ImVec4 colSliderBg; static ImVec4 colSliderBgHover; static ImVec4 colSliderBgActive; // ImPlot style colors static ImVec4 colPlotLine; static ImVec4 colAxisText; static ImVec4 colAxisGrid; static ImVec4 colAxisBg; static ImVec4 colFrameBg; static ImVec4 colPlotBg; static ImVec4 colPlotBorder; static ImVec4 colLegendBg; // Text colors static ImVec4 colMenuInformation; bool isRecording = false; float particleSizeMultiplier = 0.6f; bool isDragging = false; bool isMouseNotHoveringUI = false; bool drawQuadtree = false; bool drawZCurves = false; bool isGlowEnabled = false; glm::vec2 mouseWorldPos = { 0.0f, 0.0f }; int threadsAmount = 1; ImFont* robotoMediumFont = nullptr; bool pauseAfterRecording = false; bool cleanSceneAfterRecording = false; float recordingTimeLimit = 0.0f; float globalConstraintStiffnessMult = 1.0f; float globalConstraintResistance = 1.0f; bool constraintAllSolids = false; bool constraintSelected = false; bool deleteAllConstraints = false; bool deleteSelectedConstraints = false; bool drawConstraints = false; bool visualizeMesh = false; bool unbreakableConstraints = false; bool constraintStressColor = false; bool constraintAfterDrawingFlag = false; bool constraintAfterDrawing = false; float constraintMaxStressColor = 0.0f; bool pinFlag = false; bool unPinFlag = false; bool isBrushDrawing = false; Font customFont = { 0 }; int introFontSize = 48; bool gridExists = true; bool grid3DExists = true; bool loadDropDownMenus = false; bool exportPlyFlag = false; bool exportPlySeqFlag = false; int plyFrameNumber = 0; bool toolSpawnHeavyParticle = false; bool toolDrawParticles = true; bool toolSpawnGalaxy = false; bool toolSpawnStar = false; bool toolSpawnBigBang = false; bool toolErase = false; bool toolRadialForce = false; bool toolSpin = false; bool toolMove = false; bool toolRaiseTemp = false; bool toolLowerTemp = false; bool toolPointLight = false; bool toolAreaLight = false; bool toolConeLight = false; bool toolCircle = false; bool toolDrawShape = false; bool toolLens = false; bool toolWall = false; bool toolMoveOptics = false; bool toolEraseOptics = false; bool toolSelectOptics = false; bool isGravityFieldEnabled = false; bool gravityFieldDMParticles = false; int frameCount = 0; bool naive = false; bool is3DMode = true; bool hasAVX2 = false; float heavyParticleWeightMultiplier = 1.0f; int predictPathLength = 1000; float particleAmountMultiplier = 1.0; float DMAmountMultiplier = 1.0f; float massScatter = 0.75f; bool enablePathPrediction = false; bool SPHWater = false; bool SPHRock = false; bool SPHIron = false; bool SPHSand = false; bool SPHSoil = false; bool SPHIce = false; bool SPHMud = false; bool SPHRubber = false; bool SPHGas = false; float mass = 0.03f; float stiffMultiplier = 1.0f; float viscosity = 0.3f; float cohesionCoefficient = 1.0f; float delta = 19000.0f; float verticalGravity = 3.0f; bool infiniteDomain = true; float brushSpinForceMult = 1.0f; float brushAttractForceMult = 1.0f; bool clipSelectedX = false; bool clipSelectedY = false; bool clipSelectedZ = false; bool clipSelectedXInv = false; bool clipSelectedYInv = false; bool clipSelectedZInv = false; bool playbackRecord = false; int frames = 100; int currentFrame = 0; int keyframeTickInterval = 5; float playbackProgress = 0.0f; float playbackSpeed = 0.2f; bool runPlayback = false; bool deletePlayback = false; bool isPlaybackOn = false; float playbackParticlesSizeMult = 1.0f; bool playBackOnMemory = false; bool firstPerson = false; std::string playbackPath = "playbackTemp/playback.bin"; bool lowResRayMarching = false; bool isRayMarcherOn = false; bool flatParticleTexture3D = true; float boundaryFriction = 0.0f; int glowSize = 12; float glowStrength = 0.7f; }; ================================================ FILE: GalaxyEngine/include/pch.h ================================================ #pragma once // C++ stdlib #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // C stdlib #include #include #include #include // Runtime #include // Vendor #include #include #include #include #include #include #include #include #include #include #include #include ================================================ FILE: GalaxyEngine/src/Particles/particleSelection.cpp ================================================ #include "Particles/particleSelection.h" #include "parameters.h" ParticleSelection::ParticleSelection() { } void ParticleSelection::clusterSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger) { static bool isMouseMoving = false; static glm::vec2 dragStartPos = { 0.0f, 0.0f }; if ((IsMouseButtonPressed(0) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || externalTrigger) { dragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); isMouseMoving = false; } if ((IsMouseButtonDown(0) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || externalTrigger) { glm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); float dragThreshold = 5.0f; float dx = currentPos.x - dragStartPos.x; float dy = currentPos.y - dragStartPos.y; if (dx * dx + dy * dy > dragThreshold * dragThreshold) { isMouseMoving = true; } } if ((IsMouseButtonReleased(0) && IsKeyDown(KEY_LEFT_CONTROL) && !isMouseMoving && myVar.isMouseNotHoveringUI) || externalTrigger) { float distanceThreshold = 10.0f; std::vector neighborCountsSelect(myParam.pParticles.size(), 0); for (size_t i = 0; i < myParam.pParticles.size(); i++) { const auto& pParticle = myParam.pParticles[i]; if (myParam.rParticles[i].isDarkMatter) { continue; } for (size_t j = i + 1; j < myParam.pParticles.size(); j++) { if (myParam.rParticles[j].isDarkMatter) { continue; } if (std::abs(myParam.pParticles[j].pos.x - pParticle.pos.x) > 2.4f) break; float dx = pParticle.pos.x - myParam.pParticles[j].pos.x; float dy = pParticle.pos.y - myParam.pParticles[j].pos.y; if (dx * dx + dy * dy < distanceThreshold * distanceThreshold) { neighborCountsSelect[i]++; neighborCountsSelect[j]++; } } } if (!IsKeyDown(KEY_LEFT_SHIFT)) { if (!myVar.isGlobalTrailsEnabled) { myParam.trails.segments.clear(); } } for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (!IsKeyDown(KEY_LEFT_SHIFT)) { myParam.rParticles[i].isSelected = false; } float dx = myParam.pParticles[i].pos.x - myParam.myCamera.mouseWorldPos.x; float dy = myParam.pParticles[i].pos.y - myParam.myCamera.mouseWorldPos.y; float distanceSq = dx * dx + dy * dy; if (distanceSq < selectionThresholdSq && neighborCountsSelect[i] > 3 && !myParam.rParticles[i].isDarkMatter) { myParam.rParticles[i].isSelected = true; } else { if (!IsKeyDown(KEY_LEFT_SHIFT) && !myParam.pParticles.empty()) { myParam.rParticles[i].isSelected = false; if (!myVar.isGlobalTrailsEnabled) { myParam.trails.segments.clear(); } } } } } } void ParticleSelection::particleSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger) { static bool isMouseMoving = false; static glm::vec2 dragStartPos = { 0.0f, 0.0f }; if ((IsMouseButtonPressed(0) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI) || externalTrigger) { dragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); isMouseMoving = false; } if ((IsMouseButtonDown(0) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI) || externalTrigger) { glm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); float dragThreshold = 5.0f; float dx = currentPos.x - dragStartPos.x; float dy = currentPos.y - dragStartPos.y; if (dx * dx + dy * dy > dragThreshold * dragThreshold) { isMouseMoving = true; } } if ((IsMouseButtonReleased(0) && IsKeyDown(KEY_LEFT_ALT) && !isMouseMoving && myVar.isMouseNotHoveringUI) || externalTrigger) { size_t closestIndex = 0; float minDistanceSq = std::numeric_limits::max(); for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isDarkMatter) { continue; } float dx = myParam.pParticles[i].pos.x - myParam.myCamera.mouseWorldPos.x; float dy = myParam.pParticles[i].pos.y - myParam.myCamera.mouseWorldPos.y; float currentDistanceSq = dx * dx + dy * dy; if (currentDistanceSq < minDistanceSq) { minDistanceSq = currentDistanceSq; closestIndex = i; } } if (!IsKeyDown(KEY_LEFT_SHIFT)) { bool wasClosestSelected = (minDistanceSq < selectionThresholdSq && !myParam.pParticles.empty()) ? myParam.rParticles[closestIndex].isSelected : false; for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.rParticles[i].isSelected = false; } if (!myVar.isGlobalTrailsEnabled) { myParam.trails.segments.clear(); } if (minDistanceSq < selectionThresholdSq && !myParam.pParticles.empty() && !wasClosestSelected && !myParam.rParticles[closestIndex].isDarkMatter) { myParam.rParticles[closestIndex].isSelected = true; } } else { if (minDistanceSq < selectionThresholdSq && !myParam.pParticles.empty()) { myParam.rParticles[closestIndex].isSelected = !myParam.rParticles[closestIndex].isSelected; } } } } void ParticleSelection::manyClustersSelection(UpdateVariables& myVar, UpdateParameters& myParam) { if (selectManyClusters) { if (!myVar.isGlobalTrailsEnabled) { myParam.trails.segments.clear(); } float distanceThreshold = 10.0f; std::vector neighborCountsSelect(myParam.pParticles.size(), 0); for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.rParticles[i].isSelected = false; if (myParam.rParticles[i].isDarkMatter) { continue; } const auto& pParticle = myParam.pParticles[i]; for (size_t j = i + 1; j < myParam.pParticles.size(); j++) { if (myParam.rParticles[j].isDarkMatter) { continue; } if (std::abs(myParam.pParticles[j].pos.x - pParticle.pos.x) > 2.4f) break; float dx = pParticle.pos.x - myParam.pParticles[j].pos.x; float dy = pParticle.pos.y - myParam.pParticles[j].pos.y; if (dx * dx + dy * dy < distanceThreshold * distanceThreshold) { neighborCountsSelect[i]++; neighborCountsSelect[j]++; } } if (neighborCountsSelect[i] > 3 && !myParam.rParticles[i].isDarkMatter) { myParam.rParticles[i].isSelected = true; } } selectManyClusters = false; } } void ParticleSelection::boxSelection(UpdateParameters& myParam, bool& is3DMode) { if ((IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(2)) || (IsKeyDown(KEY_LEFT_ALT) && IsMouseButtonDown(2))) { if (IO::shortcutPress(KEY_LEFT_CONTROL) || IsMouseButtonPressed(2)) { boxInitialPos = { myParam.myCamera.mouseWorldPos.x, myParam.myCamera.mouseWorldPos.y }; isBoxSelecting = true; } glm::vec2 currentMousePos = { myParam.myCamera.mouseWorldPos.x, myParam.myCamera.mouseWorldPos.y }; float boxX = fmin(boxInitialPos.x, currentMousePos.x); float boxY = fmin(boxInitialPos.y, currentMousePos.y); float boxWidth = fabs(currentMousePos.x - boxInitialPos.x); float boxHeight = fabs(currentMousePos.y - boxInitialPos.y); DrawRectangleV({ boxX, boxY }, { boxWidth, boxHeight }, { 40,40,40,80 }); if (!is3DMode) { DrawRectangleLinesEx({ boxX, boxY , boxWidth, boxHeight }, 0.6f, WHITE); } } if (IsKeyDown(KEY_LEFT_ALT) && isBoxSelecting) { isBoxDeselecting = true; } if ((IsKeyReleased(KEY_LEFT_CONTROL) || IsMouseButtonReleased(2)) && isBoxSelecting) { glm::vec2 mousePos = { myParam.myCamera.mouseWorldPos.x, myParam.myCamera.mouseWorldPos.y }; float boxX1 = fmin(boxInitialPos.x, mousePos.x); float boxX2 = fmax(boxInitialPos.x, mousePos.x); float boxY1 = fmin(boxInitialPos.y, mousePos.y); float boxY2 = fmax(boxInitialPos.y, mousePos.y); if (!IsKeyDown(KEY_LEFT_SHIFT) && !isBoxDeselecting) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.rParticles[i].isSelected = false; } } for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.pParticles[i].pos.x >= boxX1 && myParam.pParticles[i].pos.x <= boxX2 && myParam.pParticles[i].pos.y >= boxY1 && myParam.pParticles[i].pos.y <= boxY2) { if (!isBoxDeselecting) { myParam.rParticles[i].isSelected = true; } else if (myParam.rParticles[i].isSelected && isBoxDeselecting) { myParam.rParticles[i].isSelected = false; } } } isBoxSelecting = false; isBoxDeselecting = false; } } void ParticleSelection::invertSelection(std::vector& rParticles) { if (IO::shortcutPress(KEY_I)) { invertParticleSelection = true; } if (invertParticleSelection) { for (auto& rParticle : rParticles) { rParticle.isSelected = !rParticle.isSelected; } invertParticleSelection = false; } } void ParticleSelection::deselection(std::vector& rParticles) { if (deselectParticles || IO::shortcutPress(KEY_D)) { for (auto& rParticle : rParticles) { rParticle.isSelected = false; } deselectParticles = false; } } void ParticleSelection::selectedParticlesStoring(UpdateParameters& myParam) { myParam.rParticlesSelected.clear(); myParam.pParticlesSelected.clear(); for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { myParam.rParticlesSelected.push_back(myParam.rParticles[i]); myParam.pParticlesSelected.push_back(myParam.pParticles[i]); } } } // ---- 3D IMPLEMENTATION ---- // ParticleSelection3D::ParticleSelection3D() { } void ParticleSelection3D::clusterSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger) { static bool isMouseMoving = false; static glm::vec2 dragStartPos = { 0.0f, 0.0f }; if ((IsMouseButtonPressed(0) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || externalTrigger) { dragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); isMouseMoving = false; } if ((IsMouseButtonDown(0) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || externalTrigger) { glm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); float dragThreshold = 5.0f; float dx = currentPos.x - dragStartPos.x; float dy = currentPos.y - dragStartPos.y; if (dx * dx + dy * dy > dragThreshold * dragThreshold) { isMouseMoving = true; } } if ((IsMouseButtonReleased(0) && IsKeyDown(KEY_LEFT_CONTROL) && !isMouseMoving && myVar.isMouseNotHoveringUI) || externalTrigger) { float distanceThreshold = 100.0f; Ray ray = GetScreenToWorldRay(GetMousePosition(), myParam.myCamera3D.cam3D); glm::vec3 rayPos{ ray.position.x, ray.position.y, ray.position.z }; glm::vec3 rayDir{ ray.direction.x, ray.direction.y, ray.direction.z }; std::vector neighborCountsSelect(myParam.pParticles3D.size(), 0); for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { const auto& pParticle = myParam.pParticles3D[i]; if (myParam.rParticles3D[i].isDarkMatter) { continue; } for (size_t j = i + 1; j < myParam.pParticles3D.size(); j++) { if (myParam.rParticles3D[j].isDarkMatter) { continue; } if (std::abs(myParam.pParticles3D[j].pos.x - pParticle.pos.x) > 3.4f) break; glm::vec3 diff = myParam.pParticles3D[j].pos - pParticle.pos; float dSq = glm::dot(diff, diff); if (dSq < distanceThreshold * distanceThreshold) { neighborCountsSelect[i]++; neighborCountsSelect[j]++; } } } if (!IsKeyDown(KEY_LEFT_SHIFT)) { if (!myVar.isGlobalTrailsEnabled) { myParam.trails.segments.clear(); } } for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (!IsKeyDown(KEY_LEFT_SHIFT)) { myParam.rParticles3D[i].isSelected = false; } glm::vec3 particleCursorRayDir = glm::normalize(rayPos - myParam.pParticles3D[i].pos); float alignment = glm::dot(rayDir, particleCursorRayDir); if (alignment < selectionThresholdAngle && neighborCountsSelect[i] > 4 && !myParam.rParticles3D[i].isDarkMatter) { myParam.rParticles3D[i].isSelected = true; } else { if (!IsKeyDown(KEY_LEFT_SHIFT) && !myParam.pParticles3D.empty()) { myParam.rParticles3D[i].isSelected = false; if (!myVar.isGlobalTrailsEnabled) { myParam.trails.segments.clear(); } } } } } } void ParticleSelection3D::particleSelection(UpdateVariables& myVar, UpdateParameters& myParam, bool externalTrigger) { static bool isMouseMoving = false; static glm::vec2 dragStartPos = { 0.0f, 0.0f }; if ((IsMouseButtonPressed(0) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI) || externalTrigger) { dragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); isMouseMoving = false; } if ((IsMouseButtonDown(0) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI) || externalTrigger) { glm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); float dragThreshold = 5.0f; float dx = currentPos.x - dragStartPos.x; float dy = currentPos.y - dragStartPos.y; if (dx * dx + dy * dy > dragThreshold * dragThreshold) { isMouseMoving = true; } } if ((IsMouseButtonReleased(0) && IsKeyDown(KEY_LEFT_ALT) && !isMouseMoving && myVar.isMouseNotHoveringUI) || externalTrigger) { size_t closestIndex = 0; float minAngle = std::numeric_limits::max(); Ray ray = GetScreenToWorldRay(GetMousePosition(), myParam.myCamera3D.cam3D); glm::vec3 rayPos{ ray.position.x, ray.position.y, ray.position.z }; glm::vec3 rayDir{ ray.direction.x, ray.direction.y, ray.direction.z }; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isDarkMatter) { continue; } glm::vec3 particleCursorRayDir = glm::normalize(rayPos - myParam.pParticles3D[i].pos); float alignment = glm::dot(rayDir, particleCursorRayDir); if (alignment < minAngle) { minAngle = alignment; closestIndex = i; } } if (!IsKeyDown(KEY_LEFT_SHIFT)) { bool wasClosestSelected = (minAngle < selectionThresholdAngle && !myParam.pParticles3D.empty()) ? myParam.rParticles3D[closestIndex].isSelected : false; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { myParam.rParticles3D[i].isSelected = false; } if (!myVar.isGlobalTrailsEnabled) { myParam.trails.segments.clear(); } if (minAngle < selectionThresholdAngle && !myParam.pParticles3D.empty() && !wasClosestSelected && !myParam.rParticles3D[closestIndex].isDarkMatter) { myParam.rParticles3D[closestIndex].isSelected = true; } } else { if (minAngle < selectionThresholdAngle && !myParam.pParticles3D.empty()) { myParam.rParticles3D[closestIndex].isSelected = !myParam.rParticles3D[closestIndex].isSelected; } } } } void ParticleSelection3D::manyClustersSelection(UpdateVariables& myVar, UpdateParameters& myParam) { if (selectManyClusters3D) { if (!myVar.isGlobalTrailsEnabled) { myParam.trails.segments.clear(); } float distanceThreshold = 100.0f; std::vector neighborCountsSelect(myParam.pParticles3D.size(), 0); for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { myParam.rParticles3D[i].isSelected = false; if (myParam.rParticles3D[i].isDarkMatter) { continue; } const auto& pParticle = myParam.pParticles3D[i]; for (size_t j = i + 1; j < myParam.pParticles3D.size(); j++) { if (myParam.rParticles3D[j].isDarkMatter) { continue; } if (std::abs(myParam.pParticles3D[j].pos.x - pParticle.pos.x) > 3.4f) break; glm::vec3 diff = myParam.pParticles3D[j].pos - pParticle.pos; float dSq = glm::dot(diff, diff); if (dSq < distanceThreshold * distanceThreshold) { neighborCountsSelect[i]++; neighborCountsSelect[j]++; } } if (neighborCountsSelect[i] > 4 && !myParam.rParticles3D[i].isDarkMatter) { myParam.rParticles3D[i].isSelected = true; } } selectManyClusters3D = false; } } void ParticleSelection3D::boxSelection(UpdateParameters& myParam) { if ((IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(2)) || (IsKeyDown(KEY_LEFT_ALT) && IsMouseButtonDown(2))) { if (IO::shortcutPress(KEY_LEFT_CONTROL) || IsMouseButtonPressed(2)) { boxInitialPos = { GetMousePosition().x, GetMousePosition().y }; isBoxSelecting = true; } glm::vec2 currentMousePos = {GetMousePosition().x, GetMousePosition().y}; float boxX = fmin(boxInitialPos.x, currentMousePos.x); float boxY = fmin(boxInitialPos.y, currentMousePos.y); float boxWidth = fabs(currentMousePos.x - boxInitialPos.x); float boxHeight = fabs(currentMousePos.y - boxInitialPos.y); } if (IsKeyDown(KEY_LEFT_ALT) && isBoxSelecting) { isBoxDeselecting = true; } if ((IsKeyReleased(KEY_LEFT_CONTROL) || IsMouseButtonReleased(2)) && isBoxSelecting) { glm::vec2 mousePos = { GetMousePosition().x, GetMousePosition().y }; float boxX1 = fmin(boxInitialPos.x, mousePos.x); float boxX2 = fmax(boxInitialPos.x, mousePos.x); float boxY1 = fmin(boxInitialPos.y, mousePos.y); float boxY2 = fmax(boxInitialPos.y, mousePos.y); if (!IsKeyDown(KEY_LEFT_SHIFT) && !isBoxDeselecting) { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { myParam.rParticles3D[i].isSelected = false; } } for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { ParticlePhysics3D& p = myParam.pParticles3D[i]; Vector2 p3dTo2d = GetWorldToScreen({ p.pos.x, p.pos.y, p.pos.z }, myParam.myCamera3D.cam3D); if (p3dTo2d.x >= boxX1 && p3dTo2d.x <= boxX2 && p3dTo2d.y >= boxY1 && p3dTo2d.y <= boxY2) { if (!isBoxDeselecting) { myParam.rParticles3D[i].isSelected = true; } else if (myParam.rParticles3D[i].isSelected && isBoxDeselecting) { myParam.rParticles3D[i].isSelected = false; } } } isBoxSelecting = false; isBoxDeselecting = false; } } void ParticleSelection3D::invertSelection(std::vector& rParticles) { if (IO::shortcutPress(KEY_I)) { invertParticleSelection = true; } if (invertParticleSelection) { for (auto& rParticle : rParticles) { rParticle.isSelected = !rParticle.isSelected; } invertParticleSelection = false; } } void ParticleSelection3D::deselection(std::vector& rParticles) { if (deselectParticles || IO::shortcutPress(KEY_D)) { for (auto& rParticle : rParticles) { rParticle.isSelected = false; } deselectParticles = false; } } void ParticleSelection3D::selectedParticlesStoring(UpdateParameters& myParam) { myParam.rParticlesSelected3D.clear(); myParam.pParticlesSelected3D.clear(); for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { myParam.rParticlesSelected3D.push_back(myParam.rParticles3D[i]); myParam.pParticlesSelected3D.push_back(myParam.pParticles3D[i]); } } } ================================================ FILE: GalaxyEngine/src/Particles/particleSubdivision.cpp ================================================ #include "Particles/particleSubdivision.h" #include "parameters.h" void ParticleSubdivision::subdivideParticles(UpdateVariables& myVar, UpdateParameters& myParam) { if (subdivideAll || subdivideSelected) { if (subdivideSelected) { subdivideAll = false; } if (myParam.pParticles.size() >= particlesThreshold) { float screenW = static_cast(GetScreenWidth()); float screenH = static_cast(GetScreenHeight()); ImVec2 subdivisionMenuSize = { 550.0f, 150.0f }; ImGui::SetNextWindowSize(subdivisionMenuSize, ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - subdivisionMenuSize.x * 0.5f, screenH * 0.5f - subdivisionMenuSize.y * 0.5f), ImGuiCond_Appearing); ImGui::Begin("##SubdivisionWarning", nullptr, ImGuiWindowFlags_NoCollapse); ImGui::PushFont(myVar.robotoMediumFont); std::string warning = "SUBDIVIDING FURTHER MIGHT HEAVILY SLOW DOWN PERFORMANCE"; float windowWidth = ImGui::GetWindowSize().x; float textWidth = ImGui::CalcTextSize(warning.c_str()).x; ImGui::SetWindowFontScale(1.2f); ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f); ImGui::TextColored(ImVec4(0.9f, 0.0f, 0.0f, 1.0f), "%s", warning.c_str()); if (ImGui::Button("Confirm", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) { confirmState = !confirmState; } ImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress); if (ImGui::Button("Quit", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) { quitState = !quitState; } ImGui::PopStyleColor(3); ImGui::PopFont(); ImGui::End(); } if (quitState) { subdivideAll = false; } if (myParam.pParticles.size() < particlesThreshold || confirmState) { int originalSize = static_cast(myParam.pParticles.size()); for (int i = originalSize - 1; i >= 0; i--) { if ((subdivideAll || myParam.rParticles[i].isSelected) && myParam.rParticles[i].canBeSubdivided) { float halfOffset = myParam.rParticles[i].previousSize / 2.0f * myVar.particleTextureHalfSize * 0.25f; float halfOffsetVisual = myParam.rParticles[i].previousSize / 2.0f; int multipliers[4][2] = { {-1, -1}, { 1, -1}, {-1, 1}, { 1, 1} }; size_t firstNewParticleIndex = myParam.pParticles.size(); for (int j = 0; j < 4; j++) { float offsetX = multipliers[j][0] * halfOffset + (rand() % 3 - 1); float offsetY = multipliers[j][1] * halfOffset + (rand() % 3 - 1); glm::vec2 newPos{ myParam.pParticles[i].pos.x + offsetX, myParam.pParticles[i].pos.y + offsetY }; myParam.pParticles.emplace_back( newPos, myParam.pParticles[i].vel, myParam.pParticles[i].mass / 4.0f, myParam.pParticles[i].restDens, myParam.pParticles[i].stiff, myParam.pParticles[i].visc, myParam.pParticles[i].cohesion ); myParam.rParticles.emplace_back( myParam.rParticles[i].color, halfOffsetVisual, myParam.rParticles[i].uniqueColor, myParam.rParticles[i].isSelected, myParam.rParticles[i].isSolid, myParam.rParticles[i].canBeSubdivided, myParam.rParticles[i].canBeResized, myParam.rParticles[i].isDarkMatter, myParam.rParticles[i].isSPH, myParam.rParticles[i].lifeSpan, myParam.rParticles[i].sphLabel ); } for (int j = 0; j < 4; ++j) { myParam.pParticles[firstNewParticleIndex + j].id = globalId++; myParam.pParticles[firstNewParticleIndex + j].temp = myParam.pParticles[i].temp; } for (int j = 0; j < 4; ++j) { myParam.rParticles[firstNewParticleIndex + j].pColor = myParam.rParticles[i].pColor; myParam.rParticles[firstNewParticleIndex + j].sColor = myParam.rParticles[i].sColor; myParam.rParticles[firstNewParticleIndex + j].sphColor = myParam.rParticles[i].sphColor; } myParam.pParticles[i] = std::move(myParam.pParticles.back()); myParam.pParticles.pop_back(); myParam.rParticles[i] = std::move(myParam.rParticles.back()); myParam.rParticles.pop_back(); } } subdivideAll = false; subdivideSelected = false; } confirmState = false; quitState = false; } } void ParticleSubdivision::subdivideParticles3D(UpdateVariables& myVar, UpdateParameters& myParam) { if (subdivideAll || subdivideSelected) { if (subdivideSelected) { subdivideAll = false; } if (myParam.pParticles3D.size() >= particlesThreshold) { float screenW = static_cast(GetScreenWidth()); float screenH = static_cast(GetScreenHeight()); ImVec2 subdivisionMenuSize = { 550.0f, 150.0f }; ImGui::SetNextWindowSize(subdivisionMenuSize, ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - subdivisionMenuSize.x * 0.5f, screenH * 0.5f - subdivisionMenuSize.y * 0.5f), ImGuiCond_Appearing); ImGui::Begin("##SubdivisionWarning", nullptr, ImGuiWindowFlags_NoCollapse); ImGui::PushFont(myVar.robotoMediumFont); std::string warning = "SUBDIVIDING FURTHER MIGHT HEAVILY SLOW DOWN PERFORMANCE"; float windowWidth = ImGui::GetWindowSize().x; float textWidth = ImGui::CalcTextSize(warning.c_str()).x; ImGui::SetWindowFontScale(1.2f); ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f); ImGui::TextColored(ImVec4(0.9f, 0.0f, 0.0f, 1.0f), "%s", warning.c_str()); if (ImGui::Button("Confirm", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) { confirmState = !confirmState; } ImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress); if (ImGui::Button("Quit", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) { quitState = !quitState; } ImGui::PopStyleColor(3); ImGui::PopFont(); ImGui::End(); } if (quitState) { subdivideAll = false; } if (myParam.pParticles3D.size() < particlesThreshold || confirmState) { int originalSize = static_cast(myParam.pParticles3D.size()); for (int i = originalSize - 1; i >= 0; i--) { bool isTarget = (subdivideAll || myParam.rParticles3D[i].isSelected); if (isTarget && myParam.rParticles3D[i].canBeSubdivided) { float halfOffset = myParam.rParticles3D[i].previousSize / 2.0f * myVar.particleTextureHalfSize * 0.25f; float halfOffsetVisual = myParam.rParticles3D[i].previousSize / 2.0f; int multipliers[8][3] = { {-1, -1, -1}, { 1, -1, -1}, {-1, 1, -1}, { 1, 1, -1}, {-1, -1, 1}, { 1, -1, 1}, {-1, 1, 1}, { 1, 1, 1} }; size_t firstNewParticleIndex = myParam.pParticles3D.size(); for (int j = 0; j < 8; j++) { float offsetX = multipliers[j][0] * halfOffset + (rand() % 3 - 1); float offsetY = multipliers[j][1] * halfOffset + (rand() % 3 - 1); float offsetZ = multipliers[j][2] * halfOffset + (rand() % 3 - 1); glm::vec3 newPos{ myParam.pParticles3D[i].pos.x + offsetX, myParam.pParticles3D[i].pos.y + offsetY, myParam.pParticles3D[i].pos.z + offsetZ }; myParam.pParticles3D.emplace_back( newPos, myParam.pParticles3D[i].vel, myParam.pParticles3D[i].mass / 8.0f, myParam.pParticles3D[i].restDens, myParam.pParticles3D[i].stiff, myParam.pParticles3D[i].visc, myParam.pParticles3D[i].cohesion ); myParam.rParticles3D.emplace_back( myParam.rParticles3D[i].color, halfOffsetVisual, myParam.rParticles3D[i].uniqueColor, myParam.rParticles3D[i].isSelected, myParam.rParticles3D[i].isSolid, myParam.rParticles3D[i].canBeSubdivided, myParam.rParticles3D[i].canBeResized, myParam.rParticles3D[i].isDarkMatter, myParam.rParticles3D[i].isSPH, myParam.rParticles3D[i].lifeSpan, myParam.rParticles3D[i].sphLabel ); } for (int j = 0; j < 8; ++j) { myParam.pParticles3D[firstNewParticleIndex + j].id = globalId++; myParam.pParticles3D[firstNewParticleIndex + j].temp = myParam.pParticles3D[i].temp; } for (int j = 0; j < 8; ++j) { myParam.rParticles3D[firstNewParticleIndex + j].pColor = myParam.rParticles3D[i].pColor; myParam.rParticles3D[firstNewParticleIndex + j].sColor = myParam.rParticles3D[i].sColor; myParam.rParticles3D[firstNewParticleIndex + j].sphColor = myParam.rParticles3D[i].sphColor; } if (i < myParam.pParticles3D.size() - 1) { myParam.pParticles3D[i] = std::move(myParam.pParticles3D.back()); myParam.rParticles3D[i] = std::move(myParam.rParticles3D.back()); } myParam.pParticles3D.pop_back(); myParam.rParticles3D.pop_back(); } } subdivideAll = false; subdivideSelected = false; } confirmState = false; quitState = false; } } ================================================ FILE: GalaxyEngine/src/Particles/particleTrails.cpp ================================================ #include "Particles/particleTrails.h" #include "parameters.h" void ParticleTrails::trailLogic(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutPress(KEY_T) && !IsKeyDown(KEY_LEFT_CONTROL)) { myVar.isGlobalTrailsEnabled = !myVar.isGlobalTrailsEnabled; myVar.isSelectedTrailsEnabled = false; segments.clear(); } if (IO::shortcutPress(KEY_T) && IsKeyDown(KEY_LEFT_CONTROL)) { myVar.isSelectedTrailsEnabled = !myVar.isSelectedTrailsEnabled; myVar.isGlobalTrailsEnabled = false; segments.clear(); } if (myVar.timeFactor > 0.0f) { if (myVar.isGlobalTrailsEnabled) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { glm::vec2 offset = { myParam.pParticles[i].pos.x - selectedParticlesAveragePos.x, myParam.pParticles[i].pos.y - selectedParticlesAveragePos.y }; glm::vec2 prevPos = myParam.pParticles[i].pos - myParam.pParticles[i].vel * myVar.timeFactor + 0.5f * myParam.pParticles[i].acc * myVar.timeFactor * myVar.timeFactor; glm::vec2 prevOffset = { prevPos.x - selectedParticlesAveragePrevPos.x, prevPos.y - selectedParticlesAveragePrevPos.y }; segments.push_back({ { myParam.pParticles[i].pos }, { prevPos }, {offset}, {prevOffset}, myParam.rParticles[i].color }); } size_t MAX_DOTS = myVar.trailMaxLength * myParam.pParticles.size(); if (segments.size() > MAX_DOTS) { size_t excess = segments.size() - MAX_DOTS; segments.erase(segments.begin(), segments.begin() + excess); } if (myVar.isLocalTrailsEnabled) { if (!wasLocalTrailsEnabled) { segments.clear(); } if (myParam.pParticlesSelected.size() > 0) { float pParticlePosSumX = 0.0f; float pParticlePosSumY = 0.0f; float pParticlePrevPosSumX = 0.0f; float pParticlePrevPosSumY = 0.0f; for (const auto& selectedParticle : myParam.pParticlesSelected) { pParticlePosSumX += selectedParticle.pos.x; pParticlePosSumY += selectedParticle.pos.y; glm::vec2 prevPos = selectedParticle.pos - selectedParticle.vel * myVar.timeFactor + 0.5f * selectedParticle.acc * myVar.timeFactor * myVar.timeFactor; pParticlePrevPosSumX += prevPos.x; pParticlePrevPosSumY += prevPos.y; } selectedParticlesAveragePos = { pParticlePosSumX / myParam.pParticlesSelected.size(), pParticlePosSumY / myParam.pParticlesSelected.size() }; selectedParticlesAveragePrevPos = { pParticlePrevPosSumX / myParam.pParticlesSelected.size(), pParticlePrevPosSumY / myParam.pParticlesSelected.size() }; for (auto& segment : segments) { segment.start.x = segment.offset.x + selectedParticlesAveragePos.x; segment.start.y = segment.offset.y + selectedParticlesAveragePos.y; segment.end.x = segment.prevOffset.x + selectedParticlesAveragePos.x; segment.end.y = segment.prevOffset.y + selectedParticlesAveragePos.y; } } wasLocalTrailsEnabled = true; } else { wasLocalTrailsEnabled = false; } } else if (myVar.isSelectedTrailsEnabled) { for (size_t i = 0; i < myParam.pParticlesSelected.size(); i++) { glm::vec2 offset = { myParam.pParticlesSelected[i].pos.x - selectedParticlesAveragePos.x, myParam.pParticlesSelected[i].pos.y - selectedParticlesAveragePos.y }; glm::vec2 prevPos = myParam.pParticlesSelected[i].pos - myParam.pParticlesSelected[i].vel * myVar.timeFactor + 0.5f * myParam.pParticlesSelected[i].acc * myVar.timeFactor * myVar.timeFactor; glm::vec2 prevOffset = { prevPos.x - selectedParticlesAveragePrevPos.x, prevPos.y - selectedParticlesAveragePrevPos.y }; segments.push_back({ { myParam.pParticlesSelected[i].pos }, { prevPos }, {offset}, {prevOffset}, myParam.rParticlesSelected[i].color }); } size_t MAX_DOTS = myVar.trailMaxLength * myParam.pParticlesSelected.size(); if (segments.size() > MAX_DOTS) { size_t excess = segments.size() - MAX_DOTS; segments.erase(segments.begin(), segments.begin() + excess); } if (myVar.isLocalTrailsEnabled) { if (!wasLocalTrailsEnabled) { segments.clear(); } if (myParam.pParticlesSelected.size() > 0) { float pParticlePosSumX = 0.0f; float pParticlePosSumY = 0.0f; float pParticlePrevPosSumX = 0.0f; float pParticlePrevPosSumY = 0.0f; for (const auto& selectedParticle : myParam.pParticlesSelected) { pParticlePosSumX += selectedParticle.pos.x; pParticlePosSumY += selectedParticle.pos.y; glm::vec2 prevPos = selectedParticle.pos - selectedParticle.vel * myVar.timeFactor + 0.5f * selectedParticle.acc * myVar.timeFactor * myVar.timeFactor; pParticlePrevPosSumX += prevPos.x; pParticlePrevPosSumY += prevPos.y; } selectedParticlesAveragePos = { pParticlePosSumX / myParam.pParticlesSelected.size(), pParticlePosSumY / myParam.pParticlesSelected.size() }; selectedParticlesAveragePrevPos = { pParticlePrevPosSumX / myParam.pParticlesSelected.size(), pParticlePrevPosSumY / myParam.pParticlesSelected.size() }; for (auto& segment : segments) { segment.start.x = segment.offset.x + selectedParticlesAveragePos.x; segment.start.y = segment.offset.y + selectedParticlesAveragePos.y; segment.end.x = segment.prevOffset.x + selectedParticlesAveragePos.x; segment.end.y = segment.prevOffset.y + selectedParticlesAveragePos.y; } } wasLocalTrailsEnabled = true; } else { wasLocalTrailsEnabled = false; } } } if (!myVar.isGlobalTrailsEnabled && !myVar.isSelectedTrailsEnabled) { segments.clear(); } } void ParticleTrails::drawTrail(std::vector& rParticles, Texture2D& particleBlur) { if (!whiteTrails) { if (!segments.empty()) { for (size_t i = 0; i < segments.size(); i++) { DrawLineEx({ segments[i].start.x, segments[i].start.y }, { segments[i].end.x ,segments[i].end.y }, trailThickness, segments[i].color); } } } else { if (!segments.empty()) { for (size_t i = 0; i < segments.size(); i++) { DrawLineEx({ segments[i].start.x, segments[i].start.y }, { segments[i].end.x ,segments[i].end.y }, trailThickness, {255,255,255,160}); } } } } void ParticleTrails::trailLogic3D(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutPress(KEY_T) && !IsKeyDown(KEY_LEFT_CONTROL)) { myVar.isGlobalTrailsEnabled = !myVar.isGlobalTrailsEnabled; myVar.isSelectedTrailsEnabled = false; segments3D.clear(); } if (IO::shortcutPress(KEY_T) && IsKeyDown(KEY_LEFT_CONTROL)) { myVar.isSelectedTrailsEnabled = !myVar.isSelectedTrailsEnabled; myVar.isGlobalTrailsEnabled = false; segments3D.clear(); } if (myVar.timeFactor > 0.0f) { if (myVar.isGlobalTrailsEnabled) { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { glm::vec3 offset = { myParam.pParticles3D[i].pos.x - selectedParticlesAveragePos3D.x, myParam.pParticles3D[i].pos.y - selectedParticlesAveragePos3D.y, myParam.pParticles3D[i].pos.z - selectedParticlesAveragePos3D.z }; glm::vec3 prevPos = myParam.pParticles3D[i].pos - myParam.pParticles3D[i].vel * myVar.timeFactor + 0.5f * myParam.pParticles3D[i].acc * myVar.timeFactor * myVar.timeFactor; glm::vec3 prevOffset = { prevPos.x - selectedParticlesAveragePrevPos3D.x, prevPos.y - selectedParticlesAveragePrevPos3D.y, prevPos.z - selectedParticlesAveragePrevPos3D.z }; segments3D.push_back({ { myParam.pParticles3D[i].pos }, { prevPos }, { offset }, { prevOffset }, myParam.rParticles3D[i].color }); } size_t MAX_DOTS = myVar.trailMaxLength * myParam.pParticles3D.size(); if (segments3D.size() > MAX_DOTS) { size_t excess = segments3D.size() - MAX_DOTS; segments3D.erase(segments3D.begin(), segments3D.begin() + excess); } if (myVar.isLocalTrailsEnabled) { if (!wasLocalTrailsEnabled) { segments3D.clear(); } if (myParam.pParticlesSelected3D.size() > 0) { float pParticlePosSumX = 0.0f; float pParticlePosSumY = 0.0f; float pParticlePosSumZ = 0.0f; float pParticlePrevPosSumX = 0.0f; float pParticlePrevPosSumY = 0.0f; float pParticlePrevPosSumZ = 0.0f; for (const auto& selectedParticle : myParam.pParticlesSelected3D) { pParticlePosSumX += selectedParticle.pos.x; pParticlePosSumY += selectedParticle.pos.y; pParticlePosSumZ += selectedParticle.pos.z; glm::vec3 prevPos = selectedParticle.pos - selectedParticle.vel * myVar.timeFactor + 0.5f * selectedParticle.acc * myVar.timeFactor * myVar.timeFactor; pParticlePrevPosSumX += prevPos.x; pParticlePrevPosSumY += prevPos.y; pParticlePrevPosSumZ += prevPos.z; } selectedParticlesAveragePos3D = { pParticlePosSumX / myParam.pParticlesSelected3D.size(), pParticlePosSumY / myParam.pParticlesSelected3D.size(), pParticlePosSumZ / myParam.pParticlesSelected3D.size() }; selectedParticlesAveragePrevPos3D = { pParticlePrevPosSumX / myParam.pParticlesSelected3D.size(), pParticlePrevPosSumY / myParam.pParticlesSelected3D.size(), pParticlePrevPosSumZ / myParam.pParticlesSelected3D.size() }; for (auto& segment : segments3D) { segment.start.x = segment.offset.x + selectedParticlesAveragePos3D.x; segment.start.y = segment.offset.y + selectedParticlesAveragePos3D.y; segment.start.z = segment.offset.z + selectedParticlesAveragePos3D.z; segment.end.x = segment.prevOffset.x + selectedParticlesAveragePos3D.x; segment.end.y = segment.prevOffset.y + selectedParticlesAveragePos3D.y; segment.end.z = segment.prevOffset.z + selectedParticlesAveragePos3D.z; } } wasLocalTrailsEnabled = true; } else { wasLocalTrailsEnabled = false; } } else if (myVar.isSelectedTrailsEnabled) { for (size_t i = 0; i < myParam.pParticlesSelected3D.size(); i++) { glm::vec3 offset = { myParam.pParticlesSelected3D[i].pos.x - selectedParticlesAveragePos3D.x, myParam.pParticlesSelected3D[i].pos.y - selectedParticlesAveragePos3D.y, myParam.pParticlesSelected3D[i].pos.z - selectedParticlesAveragePos3D.z }; glm::vec3 prevPos = myParam.pParticlesSelected3D[i].pos - myParam.pParticlesSelected3D[i].vel * myVar.timeFactor + 0.5f * myParam.pParticlesSelected3D[i].acc * myVar.timeFactor * myVar.timeFactor; glm::vec3 prevOffset = { prevPos.x - selectedParticlesAveragePrevPos3D.x, prevPos.y - selectedParticlesAveragePrevPos3D.y, prevPos.z - selectedParticlesAveragePrevPos3D.z }; segments3D.push_back({ { myParam.pParticlesSelected3D[i].pos }, { prevPos }, { offset }, { prevOffset }, myParam.rParticlesSelected3D[i].color }); } size_t MAX_DOTS = myVar.trailMaxLength * myParam.pParticlesSelected3D.size(); if (segments3D.size() > MAX_DOTS) { size_t excess = segments3D.size() - MAX_DOTS; segments3D.erase(segments3D.begin(), segments3D.begin() + excess); } if (myVar.isLocalTrailsEnabled) { if (!wasLocalTrailsEnabled) { segments3D.clear(); } if (myParam.pParticlesSelected3D.size() > 0) { float pParticlePosSumX = 0.0f; float pParticlePosSumY = 0.0f; float pParticlePosSumZ = 0.0f; float pParticlePrevPosSumX = 0.0f; float pParticlePrevPosSumY = 0.0f; float pParticlePrevPosSumZ = 0.0f; for (const auto& selectedParticle : myParam.pParticlesSelected3D) { pParticlePosSumX += selectedParticle.pos.x; pParticlePosSumY += selectedParticle.pos.y; pParticlePosSumZ += selectedParticle.pos.z; glm::vec3 prevPos = selectedParticle.pos - selectedParticle.vel * myVar.timeFactor + 0.5f * selectedParticle.acc * myVar.timeFactor * myVar.timeFactor; pParticlePrevPosSumX += prevPos.x; pParticlePrevPosSumY += prevPos.y; pParticlePrevPosSumZ += prevPos.z; } selectedParticlesAveragePos3D = { pParticlePosSumX / myParam.pParticlesSelected3D.size(), pParticlePosSumY / myParam.pParticlesSelected3D.size(), pParticlePosSumZ / myParam.pParticlesSelected3D.size() }; selectedParticlesAveragePrevPos3D = { pParticlePrevPosSumX / myParam.pParticlesSelected3D.size(), pParticlePrevPosSumY / myParam.pParticlesSelected3D.size(), pParticlePrevPosSumZ / myParam.pParticlesSelected3D.size() }; for (auto& segment : segments3D) { segment.start.x = segment.offset.x + selectedParticlesAveragePos3D.x; segment.start.y = segment.offset.y + selectedParticlesAveragePos3D.y; segment.start.z = segment.offset.z + selectedParticlesAveragePos3D.z; segment.end.x = segment.prevOffset.x + selectedParticlesAveragePos3D.x; segment.end.y = segment.prevOffset.y + selectedParticlesAveragePos3D.y; segment.end.z = segment.prevOffset.z + selectedParticlesAveragePos3D.z; } } wasLocalTrailsEnabled = true; } else { wasLocalTrailsEnabled = false; } } } if (!myVar.isGlobalTrailsEnabled && !myVar.isSelectedTrailsEnabled) { segments3D.clear(); } if (IO::shortcutPress(KEY_C)) { myParam.pParticles.clear(); myParam.rParticles.clear(); myParam.pParticles3D.clear(); myParam.rParticles3D.clear(); segments3D.clear(); } } void ParticleTrails::drawTrail3D(std::vector& rParticles3D, Texture2D& particleBlur, Camera3D& cam3D) { if (!whiteTrails) { if (!segments3D.empty()) { for (size_t i = 0; i < segments3D.size(); i++) { DrawLine3D({segments3D[i].start.x,segments3D[i].start.y,segments3D[i].start.z }, { segments3D[i].end.x,segments3D[i].end.y,segments3D[i].end.z }, segments3D[i].color); } } } else { if (!segments3D.empty()) { for (size_t i = 0; i < segments3D.size(); i++) { DrawLine3D({ segments3D[i].start.x,segments3D[i].start.y,segments3D[i].start.z }, { segments3D[i].end.x,segments3D[i].end.y,segments3D[i].end.z }, { 255,255,255,160 }); } } } } ================================================ FILE: GalaxyEngine/src/Particles/particlesSpawning.cpp ================================================ #include "Particles/particlesSpawning.h" #include "Physics/physics.h" #include "Physics/physics3D.h" #include "Physics/quadtree.h" #include "parameters.h" void ParticlesSpawning::particlesInitialConditions(Physics& physics, UpdateVariables& myVar, UpdateParameters& myParam) { if (myVar.isMouseNotHoveringUI && myVar.isSpawningAllowed) { Slingshot slingshot = slingshot.particleSlingshot(myVar, myParam.myCamera); if (myVar.isDragging && myVar.enablePathPrediction && myVar.gridExists) { predictTrajectory(myParam.pParticles, myParam.myCamera, physics, myVar, slingshot); } if (IO::mouseReleased(0) && myVar.toolSpawnHeavyParticle && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && myVar.isDragging) { myParam.pParticles.emplace_back( myParam.myCamera.mouseWorldPos, slingshot.norm * slingshot.length, heavyParticleInitMass * myVar.heavyParticleWeightMultiplier, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles.emplace_back( Color{ 255, 255, 255, 255 }, 0.3f, true, false, true, false, false, false, false, -1.0f, 0 ); myVar.isDragging = false; } if (!myVar.isSPHEnabled) { myVar.isBrushDrawing = false; myVar.constraintAfterDrawingFlag = false; } if ((IO::mouseDown(2) || (IO::mouseDown(0) && myVar.toolDrawParticles)) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_X)) { myVar.isBrushDrawing = true; myParam.brush.brushLogic(myParam, myVar.isSPHEnabled, myVar.constraintAfterDrawing, myVar.massScatter, myVar); if (myVar.isBrushDrawing) { if (myVar.isSPHEnabled) { particlesIterating = true; for (int i = 0; i < correctionSubsteps; i++) { if (i % 2 == 0) { if (!myVar.hasAVX2) { myParam.neighborSearchV2.newGrid(myParam.pParticles); myParam.neighborSearchV2.neighborAmount(myParam.pParticles, myParam.rParticles); } else { myParam.neighborSearchV2AVX2.newGridAVX2(myParam.pParticles); myParam.neighborSearchV2AVX2.neighborAmount(myParam.pParticles, myParam.rParticles); } } physics.spawnCorrection(myParam, myVar.hasAVX2, 1); } } } } else { if (myVar.isSPHEnabled && myVar.isBrushDrawing) { particlesIterating = false; for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].spawnCorrectIter < correctionSubsteps && myParam.rParticles[i].isBeingDrawn) { particlesIterating = true; break; } } if (particlesIterating) { for (int i = 0; i < correctionSubsteps * 8; i++) { if (i % 4 == 0) { if (!myVar.hasAVX2) { myParam.neighborSearchV2.newGrid(myParam.pParticles); myParam.neighborSearchV2.neighborAmount(myParam.pParticles, myParam.rParticles); } else { myParam.neighborSearchV2AVX2.newGridAVX2(myParam.pParticles); myParam.neighborSearchV2AVX2.neighborAmount(myParam.pParticles, myParam.rParticles); } } physics.spawnCorrection(myParam, myVar.hasAVX2, 1); particlesIterating = false; } } else { if (myVar.constraintAfterDrawing) { myVar.constraintAfterDrawingFlag = true; } if (myVar.constraintAfterDrawingFlag && myVar.constraintAfterDrawing) { physics.createConstraints(myParam.pParticles, myParam.rParticles, myVar.constraintAfterDrawingFlag, myVar, myParam); } for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.rParticles[i].isBeingDrawn = false; } myVar.isBrushDrawing = false; } } } if ((IO::shortcutReleased(KEY_ONE) || IO::mouseReleased(0) && myVar.toolSpawnGalaxy) && myVar.isDragging) { // VISIBLE MATTER std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis(0.0f, 1.0f); float maxRadius = outerRadius + 600.0f; float maxCumulativeProb = 1.0f - std::exp(-maxRadius / scaleLength); int totalParticles = static_cast(40000 * myVar.particleAmountMultiplier); for (int i = 0; i < totalParticles; i++) { glm::vec2 galaxyCenter = myParam.myCamera.mouseWorldPos; float randVal = dis(gen) * maxCumulativeProb; float finalRadius = -scaleLength * std::log(1.0f - randVal); finalRadius = std::max(finalRadius, 0.01f); float angle = dis(gen) * 2.0f * PI; glm::vec2 dirVector(std::cos(angle), std::sin(angle)); glm::vec2 pos = galaxyCenter + (dirVector * finalRadius); glm::vec2 tangent(-dirVector.y, dirVector.x); float speed = 10.0f * std::sqrt(1758.0f / (finalRadius + 54.0f)); glm::vec2 vel = tangent * speed * 0.85f; float finalMass = 0.0f; float massRand = dis(gen); float randomMassMultiplier = 1.0f + (massRand * 2.0f - 1.0f) * myVar.massScatter; float baseMass = 8500000000.0f; if (massMultiplierEnabled) { finalMass = (baseMass / myVar.particleAmountMultiplier) * randomMassMultiplier; } else { finalMass = baseMass * randomMassMultiplier; } myParam.pParticles.emplace_back( pos, vel + slingshot.norm * slingshot.length * 0.3f, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles.emplace_back( Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0 ); } // DARK MATTER if (myVar.isDarkMatterEnabled) { for (int i = 0; i < static_cast(12000 * myVar.DMAmountMultiplier); i++) { glm::vec2 galaxyCenter = myParam.myCamera.mouseWorldPos; float normalizedRand = static_cast(rand()) / static_cast(RAND_MAX); float radiusMultiplier = radiusCoreDM * sqrt(static_cast(pow(1 + pow(outerRadiusDM / radiusCoreDM, 2), normalizedRand) - 1)); float angle = dis(gen) * 2 * PI; glm::vec2 pos = glm::vec2(galaxyCenter.x + radiusMultiplier * cos(angle), galaxyCenter.y + radiusMultiplier * sin(angle)); glm::vec2 vel = glm::vec2(static_cast(rand() % 60 - 30), static_cast(rand() % 60 - 30)) * 0.85f; float finalMass = 0.0f; float massRand = dis(gen); float randomMassMultiplier = 1.0f + (massRand * 2.0f - 1.0f) * myVar.massScatter; if (massMultiplierEnabled) { finalMass = 141600000000.0f / myVar.DMAmountMultiplier * randomMassMultiplier; } else { finalMass = 141600000000.0f * randomMassMultiplier; } myParam.pParticles.emplace_back( pos, vel + slingshot.norm * slingshot.length * 0.3f, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles.emplace_back( Color{ 128, 128, 128, 0 }, 0.125f, true, false, false, false, true, true, false, -1.0f, 0 ); } } myVar.isDragging = false; } if ((IO::shortcutReleased(KEY_TWO) || IO::mouseReleased(0) && myVar.toolSpawnStar) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { for (int i = 0; i < static_cast(10000 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = (sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * 5.0f) + 0.1f; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (massMultiplierEnabled) { finalMass = 8500000000.0f / myVar.particleAmountMultiplier; } else { finalMass = 8500000000.0f; } myParam.pParticles.emplace_back( glm::vec2{ particlePos.x, particlePos.y }, slingshot.norm * slingshot.length * 0.3f, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles.emplace_back( Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0 ); } myVar.isDragging = false; } if ((IO::shortcutPress(KEY_THREE) || IO::mouseReleased(0) && myVar.toolSpawnBigBang) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { // VISIBLE MATTER for (int i = 0; i < static_cast(40000 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = (sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * 20.0f) + 1.0f; glm::vec2 randomOffset = { cos(angle) * distance + static_cast(GetRandomValue(-15, 15)), sin(angle) * distance + static_cast(GetRandomValue(-15, 15)) }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; glm::vec2 d = particlePos - myParam.myCamera.mouseWorldPos; glm::vec2 norm = d / distance; float speed = 300.0f; float adjustedSpeed = speed * (distance / 35.0f); glm::vec2 vel = adjustedSpeed * norm; float finalMass = 0.0f; float rand01 = static_cast(rand()) / static_cast(RAND_MAX); float randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * myVar.massScatter; if (massMultiplierEnabled) { finalMass = 8500000000.0f / myVar.particleAmountMultiplier * randomMassMultiplier; } else { finalMass = 8500000000.0f * randomMassMultiplier; } myParam.pParticles.emplace_back( particlePos, vel, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles.emplace_back( Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0 ); } // DARK MATTER if (myVar.isDarkMatterEnabled) { for (int i = 0; i < static_cast(12000 * myVar.DMAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = (sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * 20.0f) + 1.0f; glm::vec2 randomOffset = { cos(angle) * distance + static_cast(GetRandomValue(-15, 15)), sin(angle) * distance + static_cast(GetRandomValue(-15, 15)) }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; glm::vec2 d = particlePos - myParam.myCamera.mouseWorldPos; glm::vec2 norm = d / distance; float speed = 300.0f; float adjustedSpeed = speed * (distance / 35.0f); glm::vec2 vel = adjustedSpeed * norm; float finalMass = 0.0f; float rand01 = static_cast(rand()) / static_cast(RAND_MAX); float randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * myVar.massScatter; if (massMultiplierEnabled) { finalMass = 141600000000.0f / myVar.DMAmountMultiplier * randomMassMultiplier; } else { finalMass = 141600000000.0f * randomMassMultiplier; } myParam.pParticles.emplace_back( particlePos, vel, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles.emplace_back( Color{ 128, 128, 128, 0 }, 0.125f, true, false, false, false, true, true, false, -1.0f, 0 ); } } } } else { if (IsMouseButtonPressed(0)) { myVar.isSpawningAllowed = false; } } if (IsMouseButtonReleased(0)) { myVar.isSpawningAllowed = true; myVar.isDragging = false; } } void ParticlesSpawning::predictTrajectory(const std::vector& pParticles, SceneCamera& myCamera, Physics physics, UpdateVariables& myVar, Slingshot& slingshot) { if (!IsMouseButtonDown(0)) { return; } std::vector currentParticles = pParticles; ParticlePhysics predictedParticle( glm::vec2(myCamera.mouseWorldPos), glm::vec2{ slingshot.norm * slingshot.length }, heavyParticleInitMass * myVar.heavyParticleWeightMultiplier, 1.0f, 1.0f, 1.0f, 1.0f ); currentParticles.push_back(predictedParticle); int predictedIndex = static_cast(currentParticles.size()) - 1; std::vector predictedPath; for (int step = 0; step < myVar.predictPathLength; ++step) { ParticlePhysics& p = currentParticles[predictedIndex]; glm::vec2 netForce = physics.calculateForceFromGridOld(currentParticles, myVar, p); p.acc = netForce / p.mass; p.vel += p.acc * (myVar.timeFactor * 0.5f); p.pos += p.vel * myVar.timeFactor; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { if (p.pos.x < 0.0f) p.pos.x += myVar.domainSize.x; else if (p.pos.x >= myVar.domainSize.x) p.pos.x -= myVar.domainSize.x; if (p.pos.y < 0.0f) p.pos.y += myVar.domainSize.y; else if (p.pos.y >= myVar.domainSize.y) p.pos.y -= myVar.domainSize.y; } netForce = physics.calculateForceFromGridOld(currentParticles, myVar, p); p.acc = netForce / p.mass; p.vel += p.acc * (myVar.timeFactor * 0.5f); predictedPath.push_back(p.pos); } for (size_t i = 1; i < predictedPath.size(); ++i) { DrawLineV({ predictedPath[i - 1].x, predictedPath[i - 1].y }, { predictedPath[i].x, predictedPath[i].y }, WHITE); } } void ParticlesSpawning::drawGalaxyDisplay(UpdateParameters& myParam) { DrawCircleLinesV({ myParam.myCamera.mouseWorldPos.x, myParam.myCamera.mouseWorldPos.y }, scaleLength, RED); } // ---- 3D IMPLEMENTATION ---- // void ParticlesSpawning3D::particlesInitialConditions(Physics3D& physics3D, UpdateVariables& myVar, UpdateParameters& myParam) { if (myVar.isMouseNotHoveringUI && myVar.isSpawningAllowed) { Slingshot3D slingshot = slingshot.particleSlingshot(myVar, myParam.brush3D.brushPos); if (myVar.isDragging && myVar.enablePathPrediction && myVar.grid3DExists) { predictTrajectory(myParam.pParticles3D, myParam.myCamera3D, physics3D, myVar, myParam, slingshot); } if (IO::mouseReleased(0) && myVar.toolSpawnHeavyParticle && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && myVar.isDragging) { myParam.pParticles3D.emplace_back( myParam.brush3D.brushPos, slingshot.norm * slingshot.length, heavyParticleInitMass * myVar.heavyParticleWeightMultiplier, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles3D.emplace_back( Color{ 255, 255, 255, 255 }, 0.3f, true, false, true, false, false, false, false, -1.0f, 0 ); myVar.isDragging = false; } if (!myVar.isSPHEnabled) { myVar.isBrushDrawing = false; myVar.constraintAfterDrawingFlag = false; } if ((IO::mouseDown(2) || (IO::mouseDown(0) && myVar.toolDrawParticles)) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_X)) { myVar.isBrushDrawing = true; myParam.brush3D.brushLogic(myParam, myVar.isSPHEnabled, myVar.constraintAfterDrawing, myVar.massScatter, myVar); if (myVar.isBrushDrawing) { if (myVar.isSPHEnabled) { particlesIterating = true; for (int i = 0; i < correctionSubsteps; i++) { if (i % 2 == 0) { if (!myVar.hasAVX2) { myParam.neighborSearch3DV2.newGrid(myParam.pParticles3D); myParam.neighborSearch3DV2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D); } else { myParam.neighborSearch3DV2AVX2.newGridAVX2(myParam.pParticles3D); myParam.neighborSearch3DV2AVX2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D); } } physics3D.spawnCorrection(myParam, myVar.hasAVX2, 1); } } } } else { if (myVar.isSPHEnabled && myVar.isBrushDrawing) { particlesIterating = false; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].spawnCorrectIter < correctionSubsteps && myParam.rParticles3D[i].isBeingDrawn) { particlesIterating = true; break; } } if (particlesIterating) { for (int i = 0; i < correctionSubsteps * 8; i++) { if (i % 4 == 0) { if (!myVar.hasAVX2) { myParam.neighborSearch3DV2.newGrid(myParam.pParticles3D); myParam.neighborSearch3DV2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D); } else { myParam.neighborSearch3DV2AVX2.newGridAVX2(myParam.pParticles3D); myParam.neighborSearch3DV2AVX2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D); } } physics3D.spawnCorrection(myParam, myVar.hasAVX2, 1); particlesIterating = false; } } else { if (myVar.constraintAfterDrawing) { myVar.constraintAfterDrawingFlag = true; } if (myVar.constraintAfterDrawingFlag && myVar.constraintAfterDrawing) { physics3D.createConstraints(myParam.pParticles3D, myParam.rParticles3D, myVar.constraintAfterDrawingFlag, myVar, myParam); } for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { myParam.rParticles3D[i].isBeingDrawn = false; } myVar.isBrushDrawing = false; } } } if ((IO::shortcutReleased(KEY_ONE) || IO::mouseReleased(0) && myVar.toolSpawnGalaxy) && myVar.isDragging) { // VISIBLE MATTER glm::mat4 rotationMatrix = glm::mat4(1.0f); rotationMatrix = glm::rotate(rotationMatrix, diskAxisX * (PI / 180.0f), glm::vec3(1.0f, 0.0f, 0.0f)); rotationMatrix = glm::rotate(rotationMatrix, diskAxisY * (PI / 180.0f), glm::vec3(0.0f, 1.0f, 0.0f)); rotationMatrix = glm::rotate(rotationMatrix, diskAxisZ * (PI / 180.0f), glm::vec3(0.0f, 0.0f, 1.0f)); std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis(0.0f, 1.0f); float scaleLength = outerRadius / 4.0f; float maxCumulativeProb = 1.0f - std::exp(-outerRadius / scaleLength); for (int i = 0; i < static_cast(40000 * myVar.particleAmountMultiplier); i++) { glm::vec3 galaxyCenter = myParam.brush3D.brushPos; float u = dis(gen) * maxCumulativeProb; float finalRadius = -scaleLength * std::log(1.0f - u); float angle = dis(gen) * 2.0f * PI; float u1 = dis(gen); float u2 = dis(gen); if (u1 < 1e-6f) u1 = 1e-6f; float currentSpread = diskThickness + bulgeThickness * std::exp(-finalRadius / bulgeSize); float normalRandom = std::sqrt(-2.0f * std::log(u1)) * std::cos(2.0f * PI * u2); float zOffset = normalRandom * currentSpread; glm::vec4 localPos = glm::vec4( finalRadius * std::cos(angle), // x finalRadius * std::sin(angle), // y zOffset, // z 1.0f // w ); glm::vec3 localTangent; if (finalRadius > 0.0001f) { localTangent = glm::vec3(-localPos.y / finalRadius, localPos.x / finalRadius, 0.0f); } else { localTangent = glm::vec3(1.0f, 0.0f, 0.0f); } float speed = 10.5f * std::sqrt(1758.0f / (finalRadius + 54.0f)); glm::vec4 localVel = glm::vec4(localTangent * speed * 0.85f, 0.0f); glm::vec4 rotatedPos = rotationMatrix * localPos; glm::vec4 rotatedVel = rotationMatrix * localVel; glm::vec3 finalPos = glm::vec3(rotatedPos) + galaxyCenter; glm::vec3 finalVel = glm::vec3(rotatedVel) + (slingshot.norm * slingshot.length * 0.3f); float finalMass = 0.0f; float randMass = dis(gen); float randomMassMultiplier = 1.0f + (randMass * 2.0f - 1.0f) * myVar.massScatter; if (massMultiplierEnabled) { finalMass = (8500000000.0f / myVar.particleAmountMultiplier) * randomMassMultiplier; } else { finalMass = 8500000000.0f * randomMassMultiplier; } myParam.pParticles3D.emplace_back( finalPos, finalVel, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles3D.emplace_back( Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0 ); } // DARK MATTER if (myVar.isDarkMatterEnabled) { for (int i = 0; i < static_cast(12000 * myVar.DMAmountMultiplier); i++) { glm::vec3 galaxyCenter = myParam.brush3D.brushPos; float normalizedRand = dis(gen); float radiusMultiplier = radiusCoreDM * sqrt(static_cast(pow(1 + pow(outerRadiusDM / radiusCoreDM, 2), normalizedRand) - 1)); float z = dis(gen) * 2.0f - 1.0f; float theta = dis(gen) * 2.0f * PI; float radius_xy = sqrt(1.0f - z * z); float x = radius_xy * cos(theta); float y = radius_xy * sin(theta); glm::vec3 pos = glm::vec3( galaxyCenter.x + x * radiusMultiplier, galaxyCenter.y + y * radiusMultiplier, galaxyCenter.z + z * radiusMultiplier ); glm::vec3 vel = glm::vec3( static_cast(rand() % 60 - 30), static_cast(rand() % 60 - 30), static_cast(rand() % 60 - 30) ) * 0.85f; float finalMass = 0.0f; float randMass = dis(gen); float randomMassMultiplier = 1.0f + (randMass * 2.0f - 1.0f) * myVar.massScatter; if (massMultiplierEnabled) { finalMass = 141600000000.0f / myVar.DMAmountMultiplier * randomMassMultiplier; } else { finalMass = 141600000000.0f * randomMassMultiplier; } myParam.pParticles3D.emplace_back( pos, vel + slingshot.norm * slingshot.length * 0.3f, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles3D.emplace_back( Color{ 128, 128, 128, 0 }, 0.125f, true, false, false, false, true, true, false, -1.0f, 0 ); } myVar.isDragging = false; } myVar.isDragging = false; } if ((IO::shortcutReleased(KEY_TWO) || IO::mouseReleased(0) && myVar.toolSpawnStar) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { for (int i = 0; i < static_cast(10000 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float phi = static_cast(rand()) / static_cast(RAND_MAX) * 3.14159f; float distance = (cbrt(static_cast(rand()) / static_cast(RAND_MAX)) * 5.0f) + 0.1f; glm::vec3 randomOffset = { sin(phi) * cos(theta) * distance, sin(phi) * sin(theta) * distance, cos(phi) * distance }; glm::vec3 particlePos = myParam.brush3D.brushPos + randomOffset; float finalMass = 0.0f; if (massMultiplierEnabled) { finalMass = 8500000000.0f / myVar.particleAmountMultiplier; } else { finalMass = 8500000000.0f; } myParam.pParticles3D.emplace_back( glm::vec3{ particlePos.x, particlePos.y, particlePos.z }, slingshot.norm * slingshot.length * 0.3f, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles3D.emplace_back( Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0 ); } myVar.isDragging = false; } if ((IO::shortcutPress(KEY_THREE) || IO::mouseReleased(0) && myVar.toolSpawnBigBang) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { // VISIBLE MATTER for (int i = 0; i < static_cast(40000 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float random_neg1_to_1 = (static_cast(rand()) / static_cast(RAND_MAX) * 2.0f) - 1.0f; float phi = acos(random_neg1_to_1); float rand01_dist = static_cast(rand()) / static_cast(RAND_MAX); float distance = (cbrt(rand01_dist) * 20.0f) + 1.0f; glm::vec3 randomOffset = { distance * sin(phi) * cos(theta) + static_cast(GetRandomValue(-15, 15)), distance * sin(phi) * sin(theta) + static_cast(GetRandomValue(-15, 15)), distance * cos(phi) + static_cast(GetRandomValue(-15, 15)) }; glm::vec3 particlePos = myParam.brush3D.brushPos + randomOffset; glm::vec3 d = particlePos - myParam.brush3D.brushPos; glm::vec3 norm = d / distance; float speed = 160.0f; float adjustedSpeed = speed * (distance / 35.0f); glm::vec3 vel = adjustedSpeed * norm; float finalMass = 0.0f; float rand01 = static_cast(rand()) / static_cast(RAND_MAX); float randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * myVar.massScatter; if (massMultiplierEnabled) { finalMass = 8500000000.0f / myVar.particleAmountMultiplier * randomMassMultiplier; } else { finalMass = 8500000000.0f * randomMassMultiplier; } myParam.pParticles3D.emplace_back( particlePos, vel, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles3D.emplace_back( Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0 ); } // DARK MATTER if (myVar.isDarkMatterEnabled) { for (int i = 0; i < static_cast(12000 * myVar.DMAmountMultiplier); i++) { float theta = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float phi = acos((static_cast(rand()) / static_cast(RAND_MAX) * 2.0f) - 1.0f); float rand01_dist = static_cast(rand()) / static_cast(RAND_MAX); float distance = (cbrt(rand01_dist) * 20.0f) + 1.0f; glm::vec3 randomOffset = { distance * sin(phi) * cos(theta) + static_cast(GetRandomValue(-15, 15)), distance * sin(phi) * sin(theta) + static_cast(GetRandomValue(-15, 15)), distance * cos(phi) + static_cast(GetRandomValue(-15, 15)) }; glm::vec3 particlePos = myParam.brush3D.brushPos + randomOffset; glm::vec3 d = particlePos - myParam.brush3D.brushPos; glm::vec3 norm = d / distance; float speed = 160.0f; float adjustedSpeed = speed * (distance / 35.0f); glm::vec3 vel = adjustedSpeed * norm; float finalMass = 0.0f; float rand01 = static_cast(rand()) / static_cast(RAND_MAX); float randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * myVar.massScatter; if (massMultiplierEnabled) { finalMass = 141600000000.0f / myVar.DMAmountMultiplier * randomMassMultiplier; } else { finalMass = 141600000000.0f * randomMassMultiplier; } myParam.pParticles3D.emplace_back( particlePos, vel, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles3D.emplace_back( Color{ 128, 128, 128, 0 }, 0.125f, true, false, false, false, true, true, false, -1.0f, 0 ); } } } } else { if (IsMouseButtonPressed(0)) { myVar.isSpawningAllowed = false; } } if (IsMouseButtonReleased(0)) { myVar.isSpawningAllowed = true; myVar.isDragging = false; } } void ParticlesSpawning3D::predictTrajectory(const std::vector& pParticles, SceneCamera3D& myCamera, Physics3D physics3D, UpdateVariables& myVar, UpdateParameters& myParam, Slingshot3D& slingshot) { if (!IsMouseButtonDown(0)) { return; } std::vector currentParticles = pParticles; ParticlePhysics3D predictedParticle( glm::vec3(myParam.brush3D.brushPos), glm::vec3(slingshot.norm.x * slingshot.length, slingshot.norm.y * slingshot.length, slingshot.norm.z * slingshot.length), heavyParticleInitMass * myVar.heavyParticleWeightMultiplier, 1.0f, 1.0f, 1.0f, 1.0f ); currentParticles.push_back(predictedParticle); int predictedIndex = static_cast(currentParticles.size()) - 1; std::vector predictedPath; for (int step = 0; step < myVar.predictPathLength; ++step) { ParticlePhysics3D& p = currentParticles[predictedIndex]; glm::vec3 netForce = physics3D.calculateForceFromGrid3DOld(currentParticles, myVar, p); p.acc = netForce / p.mass; p.vel += p.acc * (myVar.timeFactor * 0.5f); p.pos += p.vel * myVar.timeFactor; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { if (p.pos.x < -myVar.halfDomain3DWidth) p.pos.x += myVar.domainSize3D.x; else if (p.pos.x >= myVar.halfDomain3DWidth) p.pos.x -= myVar.domainSize3D.x; if (p.pos.y < -myVar.halfDomain3DHeight) p.pos.y += myVar.domainSize3D.y; else if (p.pos.y >= myVar.halfDomain3DHeight) p.pos.y -= myVar.domainSize3D.y; if (p.pos.z < -myVar.halfDomain3DDepth) p.pos.z += myVar.domainSize3D.z; else if (p.pos.z >= myVar.halfDomain3DDepth) p.pos.z -= myVar.domainSize3D.z; } netForce = physics3D.calculateForceFromGrid3DOld(currentParticles, myVar, p); p.acc = netForce / p.mass; p.vel += p.acc * (myVar.timeFactor * 0.5f); predictedPath.push_back(p.pos); } for (size_t i = 1; i < predictedPath.size(); ++i) { DrawLine3D( { predictedPath[i - 1].x, predictedPath[i - 1].y, predictedPath[i - 1].z }, { predictedPath[i].x, predictedPath[i].y, predictedPath[i].z }, WHITE ); } } void ParticlesSpawning3D::drawGalaxyDisplay(UpdateParameters& myParam) { glm::mat4 rotationMatrix = glm::mat4(1.0f); rotationMatrix = glm::rotate(rotationMatrix, diskAxisX * (PI / 180.0f), glm::vec3(1.0f, 0.0f, 0.0f)); rotationMatrix = glm::rotate(rotationMatrix, diskAxisY * (PI / 180.0f), glm::vec3(0.0f, 1.0f, 0.0f)); rotationMatrix = glm::rotate(rotationMatrix, diskAxisZ * (PI / 180.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::vec4 defaultNormal = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f); glm::vec4 rotatedNormal = rotationMatrix * defaultNormal; glm::vec3 direction = glm::vec3(rotatedNormal); glm::vec3 startPos = myParam.brush3D.brushPos - (direction * diskThickness); glm::vec3 endPos = myParam.brush3D.brushPos + (direction * diskThickness); Vector3 raylibStart = { startPos.x, startPos.y, startPos.z }; Vector3 raylibEnd = { endPos.x, endPos.y, endPos.z }; DrawCylinderWiresEx( raylibStart, raylibEnd, outerRadius, outerRadius, 24, { 255, 0, 0, 100 } ); DrawCylinderWiresEx( raylibStart, raylibEnd, radiusCore, radiusCore, 24, { 255, 0, 0, 100 } ); } ================================================ FILE: GalaxyEngine/src/Physics/SPH.cpp ================================================ #include "Physics/SPH.h" void SPH::flattenParticles(std::vector& pParticles) { size_t particleCount = pParticles.size(); posX.resize(particleCount); posY.resize(particleCount); predPosX.resize(particleCount); predPosY.resize(particleCount); accX.resize(particleCount); accY.resize(particleCount); velX.resize(particleCount); velY.resize(particleCount); prevVelX.resize(particleCount); prevVelY.resize(particleCount); sphMass.resize(particleCount); press.resize(particleCount); pressFX.resize(particleCount); pressFY.resize(particleCount); stiff.resize(particleCount); visc.resize(particleCount); dens.resize(particleCount); predDens.resize(particleCount); restDens.resize(particleCount); #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(particleCount); i++) { const auto& particle = pParticles[i]; posX[i] = particle.pos.x; posY[i] = particle.pos.y; predPosX[i] = particle.pos.x; predPosY[i] = particle.pos.y; accX[i] = particle.acc.x; accY[i] = particle.acc.y; velX[i] = particle.vel.x; velY[i] = particle.vel.y; prevVelX[i] = particle.prevVel.x; prevVelY[i] = particle.prevVel.y; sphMass[i] = particle.sphMass; press[i] = 0.0f; pressFX[i] = 0.0f; pressFY[i] = 0.0f; stiff[i] = 0.0f; visc[i] = 0.0f; dens[i] = 0.0f; predDens[i] = 0.0f; restDens[i] = 0.0f; } } void SPH::readFlattenBack(std::vector& pParticles) { size_t particleCount = pParticles.size(); #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(particleCount); i++) { auto& particle = pParticles[i]; particle.pos.x = posX[i]; particle.pos.y = posY[i]; particle.predPos.x = predPosX[i]; particle.predPos.y = predPosY[i]; particle.acc.x = accX[i]; particle.acc.y = accY[i]; particle.vel.x = velX[i]; particle.vel.y = velY[i]; particle.prevVel.x = prevVelX[i]; particle.prevVel.y = prevVelY[i]; particle.sphMass = sphMass[i]; particle.press = press[i]; particle.pressF.x = pressFX[i]; particle.pressF.y = pressFY[i]; particle.stiff = stiff[i]; particle.visc = visc[i]; particle.dens = dens[i]; particle.predDens = predDens[i]; particle.restDens = restDens[i]; } } void SPH::computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector& sphForce, size_t& N) { const float h = radiusMultiplier; const float h2 = h * h; #pragma omp parallel for schedule(dynamic, 32) for (size_t i = 0; i < N; ++i) { if (!myParam.rParticles[i].isSPH || myParam.rParticles[i].isPinned || myParam.rParticles[i].isBeingDrawn) continue; auto& pi = myParam.pParticles[i]; std::vector neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.pos); for (size_t j : neighborIndices) { size_t pjIdx = j; if (!myParam.rParticles[pjIdx].isSPH || myParam.rParticles[pjIdx].isBeingDrawn) continue; if (pjIdx == i) continue; auto& pj = myParam.pParticles[pjIdx]; glm::vec2 d = { pj.pos.x - pi.pos.x, pj.pos.y - pi.pos.y }; float rSq = d.x * d.x + d.y * d.y; if (rSq >= h2) continue; float r = sqrtf(std::max(rSq, 1e-6f)); glm::vec2 nr = { d.x / r, d.y / r }; float mJ = pj.sphMass * myVar.mass; float lapW = smoothingKernelLaplacian(r, h); glm::vec2 viscF = { myVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.x - pi.vel.x), myVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.y - pi.vel.y) }; float cohCoef = myVar.cohesionCoefficient * pi.cohesion; float cohFactor = smoothingKernelCohesion(r, h); glm::vec2 cohF = { cohCoef * mJ * cohFactor * nr.x, cohCoef * mJ * cohFactor * nr.y }; #pragma omp atomic sphForce[i].x += viscF.x + cohF.x; #pragma omp atomic sphForce[i].y += viscF.y + cohF.y; #pragma omp atomic sphForce[pjIdx].x -= viscF.x + cohF.x; #pragma omp atomic sphForce[pjIdx].y -= viscF.y + cohF.y; } } } void SPH::PCISPH(UpdateVariables& myVar, UpdateParameters& myParam) { size_t N = myParam.pParticles.size(); const float predictionCoeff = 0.5f * myVar.timeFactor * myVar.timeFactor; std::vector sphForce(N, { 0.0f, 0.0f }); computeViscCohesionForces(myVar, myParam, sphForce, N); for (size_t i = 0; i < N; ++i) { myParam.pParticles[i].press = 0.0f; myParam.pParticles[i].pressF = { 0.0f, 0.0f }; } //float rhoError = 0.0f; iter = 0; do { //float maxRhoErr = 0.0f; #pragma omp parallel for schedule(static) for (size_t i = 0; i < N; ++i) { if (!myParam.rParticles[i].isSPH || myParam.rParticles[i].isBeingDrawn) continue; auto& p = myParam.pParticles[i]; glm::vec2 displacement = (sphForce[i] / p.sphMass) * predictionCoeff; p.predPos = p.pos + displacement; } #pragma omp parallel for schedule(dynamic, 16) /*reduction(max:maxRhoErr)*/ for (size_t i = 0; i < N; ++i) { if (!myParam.rParticles[i].isSPH || myParam.rParticles[i].isBeingDrawn) continue; auto& pi = myParam.pParticles[i]; pi.predDens = 0.0f; std::vector neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.predPos); for (size_t j : neighborIndices) { size_t pjIdx = j; //if (pjIdx == i) continue; if (!myParam.rParticles[pjIdx].isSPH || myParam.rParticles[pjIdx].isBeingDrawn) continue; auto& pj = myParam.pParticles[pjIdx]; glm::vec2 dr = pi.predPos - pj.predPos; float rrSq = dr.x * dr.x + dr.y * dr.y; if (rrSq >= radiusMultiplier * radiusMultiplier) continue; float rr = sqrtf(rrSq); float mJ = pj.sphMass * myVar.mass; float rho0 = 0.5f * (pi.restDens + pj.restDens); pi.predDens += mJ * smoothingKernel(rr, radiusMultiplier) / rho0; } float err = pi.predDens - pi.restDens; pi.pressTmp = myVar.delta * err; if (pi.pressTmp < 0.0f) pi.pressTmp = 0.0f; //maxRhoErr = std::max(maxRhoErr, std::abs(err)); pi.press += pi.pressTmp * pi.stiff * myVar.stiffMultiplier; } #pragma omp parallel for schedule(dynamic, 32) for (size_t i = 0; i < N; ++i) { if (!myParam.rParticles[i].isSPH || myParam.rParticles[i].isBeingDrawn) continue; auto& pi = myParam.pParticles[i]; std::vector neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.predPos); for (size_t j : neighborIndices) { size_t pjIdx = j; if (pjIdx == i) continue; if (!myParam.rParticles[pjIdx].isSPH || myParam.rParticles[pjIdx].isBeingDrawn) continue; auto& pj = myParam.pParticles[pjIdx]; glm::vec2 dr = pi.predPos - pj.predPos; float rr = sqrtf(dr.x * dr.x + dr.y * dr.y); if (rr < 1e-5f || rr >= radiusMultiplier) continue; float gradW = spikyKernelDerivative(rr, radiusMultiplier); glm::vec2 nrm = { dr.x / rr, dr.y / rr }; float avgP = 0.5f * (pi.press + pj.press); float avgD = 0.5f * (pi.predDens + pj.predDens); float mag = -(pi.sphMass * myVar.mass + pj.sphMass * myVar.mass) * avgP / std::max(avgD, 0.01f); float massRatio = std::max(pi.sphMass, pj.sphMass) / std::min(pi.sphMass, pj.sphMass); float scale = std::min(1.0f, 8.0f / massRatio); mag *= scale; glm::vec2 pF = { mag * gradW * nrm.x, mag * gradW * nrm.y }; #pragma omp atomic sphForce[i].x += pF.x; #pragma omp atomic sphForce[i].y += pF.y; #pragma omp atomic sphForce[pjIdx].x -= pF.x; #pragma omp atomic sphForce[pjIdx].y -= pF.y; } } //rhoError = maxRhoErr; ++iter; } while (iter < maxIter /*&& rhoError > densTolerance */); #pragma omp parallel for schedule(static) for (size_t i = 0; i < N; ++i) { auto& p = myParam.pParticles[i]; p.pressF = sphForce[i] / p.sphMass; if (!myParam.rParticles[i].isPinned) { p.acc += p.pressF; } } } void SPH::groundModeBoundary(std::vector& pParticles, std::vector& rParticles, glm::vec2 domainSize, UpdateVariables& myVar) { #pragma omp parallel for for (size_t i = 0; i < pParticles.size(); ++i) { if (rParticles[i].isPinned) continue; auto& p = pParticles[i]; p.acc.y += myVar.verticalGravity; // Left wall if (p.pos.x - radiusMultiplier < 0.0f) { p.vel.x *= boundDamping; p.vel.y *= 1.0f - myVar.boundaryFriction; p.pos.x = radiusMultiplier; } // Right wall if (p.pos.x + radiusMultiplier > domainSize.x) { p.vel.x *= boundDamping; p.vel.y *= 1.0f - myVar.boundaryFriction; p.pos.x = domainSize.x - radiusMultiplier; } // Bottom wall if (p.pos.y - radiusMultiplier < 0.0f) { p.vel.y *= boundDamping; p.vel.x *= 1.0f - myVar.boundaryFriction; p.pos.y = radiusMultiplier; } // Top wall if (p.pos.y + radiusMultiplier > domainSize.y) { p.vel.y *= boundDamping; p.vel.x *= 1.0f - myVar.boundaryFriction; p.pos.y = domainSize.y - radiusMultiplier; } } } ================================================ FILE: GalaxyEngine/src/Physics/SPH3D.cpp ================================================ #include "Physics/SPH3D.h" void SPH3D::flattenParticles(std::vector& pParticles) { size_t particleCount = pParticles.size(); posX.resize(particleCount); posY.resize(particleCount); predPosX.resize(particleCount); predPosY.resize(particleCount); accX.resize(particleCount); accY.resize(particleCount); velX.resize(particleCount); velY.resize(particleCount); prevVelX.resize(particleCount); prevVelY.resize(particleCount); sphMass.resize(particleCount); press.resize(particleCount); pressFX.resize(particleCount); pressFY.resize(particleCount); stiff.resize(particleCount); visc.resize(particleCount); dens.resize(particleCount); predDens.resize(particleCount); restDens.resize(particleCount); #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(particleCount); i++) { const auto& particle = pParticles[i]; posX[i] = particle.pos.x; posY[i] = particle.pos.y; predPosX[i] = particle.pos.x; predPosY[i] = particle.pos.y; accX[i] = particle.acc.x; accY[i] = particle.acc.y; velX[i] = particle.vel.x; velY[i] = particle.vel.y; prevVelX[i] = particle.prevVel.x; prevVelY[i] = particle.prevVel.y; sphMass[i] = particle.sphMass; press[i] = 0.0f; pressFX[i] = 0.0f; pressFY[i] = 0.0f; stiff[i] = 0.0f; visc[i] = 0.0f; dens[i] = 0.0f; predDens[i] = 0.0f; restDens[i] = 0.0f; } } void SPH3D::readFlattenBack(std::vector& pParticles) { size_t particleCount = pParticles.size(); #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(particleCount); i++) { auto& particle = pParticles[i]; particle.pos.x = posX[i]; particle.pos.y = posY[i]; particle.predPos.x = predPosX[i]; particle.predPos.y = predPosY[i]; particle.acc.x = accX[i]; particle.acc.y = accY[i]; particle.vel.x = velX[i]; particle.vel.y = velY[i]; particle.prevVel.x = prevVelX[i]; particle.prevVel.y = prevVelY[i]; particle.sphMass = sphMass[i]; particle.press = press[i]; particle.pressF.x = pressFX[i]; particle.pressF.y = pressFY[i]; particle.stiff = stiff[i]; particle.visc = visc[i]; particle.dens = dens[i]; particle.predDens = predDens[i]; particle.restDens = restDens[i]; } } void SPH3D::computeViscCohesionForces(UpdateVariables& myVar, UpdateParameters& myParam, std::vector& sphForce, size_t& N) { const float h = radiusMultiplier; const float h2 = h * h; #pragma omp parallel for schedule(dynamic, 32) for (size_t i = 0; i < N; ++i) { if (!myParam.rParticles3D[i].isSPH || myParam.rParticles3D[i].isPinned || myParam.rParticles3D[i].isBeingDrawn) continue; auto& pi = myParam.pParticles3D[i]; std::vector neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.pos); for (size_t j : neighborIndices) { size_t pjIdx = j; if (!myParam.rParticles3D[pjIdx].isSPH || myParam.rParticles3D[pjIdx].isBeingDrawn) continue; if (pjIdx == i) continue; auto& pj = myParam.pParticles3D[pjIdx]; glm::vec3 d = { pj.pos.x - pi.pos.x, pj.pos.y - pi.pos.y, pj.pos.z - pi.pos.z }; float rSq = d.x * d.x + d.y * d.y + d.z * d.z; if (rSq >= h2) continue; float r = sqrtf(std::max(rSq, 1e-6f)); glm::vec3 nr = { d.x / r, d.y / r, d.z / r }; float mJ = pj.sphMass * myVar.mass; float lapW = smoothingKernelLaplacian(r, h); glm::vec3 viscF = { myVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.x - pi.vel.x), myVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.y - pi.vel.y), myVar.viscosity * pj.visc * mJ / std::max(pj.dens, 0.001f) * lapW * (pj.vel.z - pi.vel.z) }; float cohCoef = myVar.cohesionCoefficient * pi.cohesion; float cohFactor = smoothingKernelCohesion(r, h); glm::vec3 cohF = { cohCoef * mJ * cohFactor * nr.x, cohCoef * mJ * cohFactor * nr.y, cohCoef * mJ * cohFactor * nr.z }; #pragma omp atomic sphForce[i].x += viscF.x + cohF.x; #pragma omp atomic sphForce[i].y += viscF.y + cohF.y; #pragma omp atomic sphForce[i].z += viscF.z + cohF.z; #pragma omp atomic sphForce[pjIdx].x -= viscF.x + cohF.x; #pragma omp atomic sphForce[pjIdx].y -= viscF.y + cohF.y; #pragma omp atomic sphForce[pjIdx].z -= viscF.z + cohF.z; } } } void SPH3D::PCISPH(UpdateVariables& myVar, UpdateParameters& myParam) { size_t N = myParam.pParticles3D.size(); const float predictionCoeff = 0.5f * myVar.timeFactor * myVar.timeFactor; std::vector sphForce(N, { 0.0f, 0.0f, 0.0f }); computeViscCohesionForces(myVar, myParam, sphForce, N); for (size_t i = 0; i < N; ++i) { myParam.pParticles3D[i].press = 0.0f; myParam.pParticles3D[i].pressF = { 0.0f, 0.0f, 0.0f }; } size_t iter = 0; do { #pragma omp parallel for schedule(static) for (size_t i = 0; i < N; ++i) { if (!myParam.rParticles3D[i].isSPH || myParam.rParticles3D[i].isBeingDrawn) continue; auto& p = myParam.pParticles3D[i]; glm::vec3 displacement = (sphForce[i] / p.sphMass) * predictionCoeff; p.predPos = p.pos + displacement; } #pragma omp parallel for schedule(dynamic, 16) for (size_t i = 0; i < N; ++i) { if (!myParam.rParticles3D[i].isSPH || myParam.rParticles3D[i].isBeingDrawn) continue; auto& pi = myParam.pParticles3D[i]; pi.predDens = 0.0f; std::vector neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.predPos); for (size_t j : neighborIndices) { size_t pjIdx = j; if (!myParam.rParticles3D[pjIdx].isSPH || myParam.rParticles3D[pjIdx].isBeingDrawn) continue; auto& pj = myParam.pParticles3D[pjIdx]; glm::vec3 dr = pi.predPos - pj.predPos; float rrSq = dr.x * dr.x + dr.y * dr.y + dr.z * dr.z; if (rrSq >= radiusMultiplier * radiusMultiplier) continue; float rr = sqrtf(rrSq); float mJ = pj.sphMass * (myVar.mass * 2.0f); float rho0 = 0.5f * (pi.restDens + pj.restDens); pi.predDens += mJ * smoothingKernel(rr, radiusMultiplier) / rho0; } float err = pi.predDens - pi.restDens; pi.pressTmp = myVar.delta * err; if (pi.pressTmp < 0.0f) pi.pressTmp = 0.0f; pi.press += pi.pressTmp * pi.stiff * myVar.stiffMultiplier; } #pragma omp parallel for schedule(dynamic, 32) for (size_t i = 0; i < N; ++i) { if (!myParam.rParticles3D[i].isSPH || myParam.rParticles3D[i].isBeingDrawn) continue; auto& pi = myParam.pParticles3D[i]; std::vector neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.predPos); for (size_t j : neighborIndices) { size_t pjIdx = j; if (pjIdx == i) continue; if (!myParam.rParticles3D[pjIdx].isSPH || myParam.rParticles3D[pjIdx].isBeingDrawn) continue; auto& pj = myParam.pParticles3D[pjIdx]; glm::vec3 dr = pi.predPos - pj.predPos; float rr = sqrtf(dr.x * dr.x + dr.y * dr.y + dr.z * dr.z); if (rr < 1e-5f || rr >= radiusMultiplier) continue; float gradW = spikyKernelDerivative(rr, radiusMultiplier); glm::vec3 nrm = { dr.x / rr, dr.y / rr, dr.z / rr }; float avgP = 0.5f * (pi.press + pj.press); float avgD = 0.5f * (pi.predDens + pj.predDens); float mag = -(pi.sphMass * (myVar.mass * 2.0f) + pj.sphMass * (myVar.mass * 2.0f)) * avgP / std::max(avgD, 0.01f); float massRatio = std::max(pi.sphMass, pj.sphMass) / std::min(pi.sphMass, pj.sphMass); float scale = std::min(1.0f, 8.0f / massRatio); mag *= scale; glm::vec3 pF = { mag * gradW * nrm.x, mag * gradW * nrm.y, mag * gradW * nrm.z }; #pragma omp atomic sphForce[i].x += pF.x; #pragma omp atomic sphForce[i].y += pF.y; #pragma omp atomic sphForce[i].z += pF.z; #pragma omp atomic sphForce[pjIdx].x -= pF.x; #pragma omp atomic sphForce[pjIdx].y -= pF.y; #pragma omp atomic sphForce[pjIdx].z -= pF.z; } } ++iter; } while (iter < maxIter); #pragma omp parallel for schedule(static) for (size_t i = 0; i < N; ++i) { auto& p = myParam.pParticles3D[i]; p.pressF = sphForce[i] / p.sphMass; if (!myParam.rParticles3D[i].isPinned) { p.acc += p.pressF; } } } void SPH3D::groundModeBoundary(std::vector& pParticles, std::vector& rParticles, glm::vec3 domainSize, UpdateVariables& myVar) { #pragma omp parallel for for (size_t i = 0; i < pParticles.size(); ++i) { if (rParticles[i].isPinned) continue; auto& p = pParticles[i]; p.acc.y -= myVar.verticalGravity; bool hitX = false; bool hitY = false; bool hitZ = false; // Left wall if (p.pos.x - radiusMultiplier < -domainSize.x / 2.0f) { p.vel.x *= boundDamping; p.pos.x = -domainSize.x / 2.0f + radiusMultiplier; hitX = true; } // Right wall if (p.pos.x + radiusMultiplier > domainSize.x / 2.0f) { p.vel.x *= boundDamping; p.pos.x = domainSize.x / 2.0f - radiusMultiplier; hitX = true; } // Bottom wall if (p.pos.y - radiusMultiplier < -domainSize.y / 2.0f) { p.vel.y *= boundDamping; p.pos.y = -domainSize.y / 2.0f + radiusMultiplier; hitY = true; } // Top wall if (p.pos.y + radiusMultiplier > domainSize.y / 2.0f) { p.vel.y *= boundDamping; p.pos.y = domainSize.y / 2.0f - radiusMultiplier; hitY = true; } // Front wall if (p.pos.z - radiusMultiplier < -domainSize.z / 2.0f) { p.vel.z *= boundDamping; p.pos.z = -domainSize.z / 2.0f + radiusMultiplier; hitZ = true; } // Back wall if (p.pos.z + radiusMultiplier > domainSize.z / 2.0f) { p.vel.z *= boundDamping; p.pos.z = domainSize.z / 2.0f - radiusMultiplier; hitZ = true; } if (hitX) { p.vel.y *= 1.0f - myVar.boundaryFriction; p.vel.z *= 1.0f - myVar.boundaryFriction; } if (hitY) { p.vel.x *= 1.0f - myVar.boundaryFriction; p.vel.z *= 1.0f - myVar.boundaryFriction; } if (hitZ) { p.vel.x *= 1.0f - myVar.boundaryFriction; p.vel.y *= 1.0f - myVar.boundaryFriction; } } } ================================================ FILE: GalaxyEngine/src/Physics/light.cpp ================================================ #include "Physics/light.h" void Lighting::createWall(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; if (IO::mousePress(0) && myVar.toolWall) { ImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor); colorConvert.w = wallEmissionGain; Color emissionColorFinal = rlImGuiColors::Convert(colorConvert); walls.emplace_back(mouseWorldPos, mouseWorldPos, false, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal, wallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion); } if (IO::mouseDown(0) && myVar.toolWall) { if (walls.back().isBeingSpawned) { walls.back().vB = mouseWorldPos; calculateWallNormal(walls.back()); shouldRender = true; } } if (IO::mouseReleased(0) && myVar.toolWall) { if (!walls.empty() && walls.back().isBeingSpawned) { if (glm::length(walls.back().vB - walls.back().vA) == 0.0f) { walls.pop_back(); return; } walls.back().isBeingSpawned = false; } } } void Lighting::createShape(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; // ---- Circle ---- // if (IO::mousePress(0) && myVar.toolCircle) { ImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor); colorConvert.w = wallEmissionGain; Color emissionColorFinal = rlImGuiColors::Convert(colorConvert); shapes.emplace_back(circle, mouseWorldPos, mouseWorldPos, &walls, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal, wallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion); } if (IO::mouseDown(0) && myVar.toolCircle) { if (shapes.back().isBeingSpawned) { shapes.back().h2 = mouseWorldPos; shapes.back().makeShape(); shapes.back().calculateWallsNormals(); } } if (IO::mouseReleased(0) && myVar.toolCircle) { if (!shapes.empty() && shapes.back().isBeingSpawned) { if (glm::length(shapes.back().h2 - shapes.back().h1) == 0.0f) { shapes.pop_back(); return; } shapes.back().isBeingSpawned = false; shapes.back().helpers.push_back(shapes.back().h1); shapes.back().helpers.push_back(shapes.back().h2); shapes.back().createShapeFlag = true; shapes.back().makeShape(); } shouldRender = true; } // ---- Draw Shape ---- // if (IO::mousePress(0) && myVar.toolDrawShape) { ImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor); colorConvert.w = wallEmissionGain; Color emissionColorFinal = rlImGuiColors::Convert(colorConvert); shapes.emplace_back(draw, mouseWorldPos, mouseWorldPos, &walls, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal, wallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion); shapes.back().helpers.push_back(mouseWorldPos); } if (IO::mouseDown(0) && myVar.toolDrawShape) { if (shapes.back().isBeingSpawned) { shapes.back().h2 = mouseWorldPos; shapes.back().makeShape(); shapes.back().calculateWallsNormals(); } shouldRender = true; } if (IO::mouseReleased(0) && myVar.toolDrawShape) { if (!shapes.empty() && shapes.back().isBeingSpawned) { if (glm::length(shapes.back().h2 - shapes.back().h1) == 0.0f) { shapes.pop_back(); return; } shapes.back().isBeingSpawned = false; shapes.back().makeShape(); const Wall* lastWall = getWallById(walls, shapes.back().myWallIds.back()); const Wall* firstWall = getWallById(walls, shapes.back().myWallIds.front()); if (!lastWall || !firstWall) { return; } const glm::vec2& lastPoint = lastWall->vB; const glm::vec2& firstPoint = firstWall->vA; ImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor); colorConvert.w = wallEmissionGain; Color emissionColorFinal = rlImGuiColors::Convert(colorConvert); walls.emplace_back(lastPoint, firstPoint, true, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal, wallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion); walls.back().shapeId = shapes.back().id; shapes.back().myWallIds.push_back(walls.back().id); shapes.back().relaxShape(shapeRelaxIter, shapeRelaxFactor); shapes.back().calculateWallsNormals(); } shouldRender = true; } // ---- Lens ---- // if (IO::mousePress(0) && myVar.toolLens && firstHelper) { ImVec4 colorConvert = rlImGuiColors::Convert(wallEmissionColor); colorConvert.w = wallEmissionGain; Color emissionColorFinal = rlImGuiColors::Convert(colorConvert); shapes.emplace_back(lens, mouseWorldPos, mouseWorldPos, &walls, wallBaseColor, wallSpecularColor, wallRefractionColor, emissionColorFinal, wallSpecularRoughness, wallRefractionRoughness, wallRefractionAmount, wallIOR, wallDispersion); shapes.back().symmetricalLens = symmetricalLens; isCreatingLens = true; } else if (IO::mousePress(0) && myVar.toolLens) { if (shapes.back().helpers.size() == 2) { shapes.back().thirdHelper = true; } if (shapes.back().helpers.size() == 3) { shapes.back().fourthHelper = true; firstHelper = true; } } if (IO::mouseDown(0) && myVar.toolLens && firstHelper) { if (shapes.back().isBeingSpawned) { shapes.back().h2 = mouseWorldPos; } } else if (isCreatingLens) { if (shapes.back().isBeingSpawned) { shapes.back().h2 = mouseWorldPos; } } if (IO::mouseReleased(0) && myVar.toolLens) { if (!shapes.empty() && shapes.back().isBeingSpawned) { if (glm::length(shapes.back().h2 - shapes.back().h1) == 0.0f) { shapes.pop_back(); return; } if (shapes.back().helpers.size() == 1) { shapes.back().secondHelper = true; } firstHelper = false; } shouldRender = true; } if (!shapes.empty()) { if (shapes.back().isBeingSpawned && shapes.back().shapeType == lens) { shapes.back().makeShape(); } } else { firstHelper = true; } } void Lighting::createPointLight(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; if (IO::mousePress(0) && myVar.toolPointLight) { ImVec4 colorConvert = rlImGuiColors::Convert(lightColor); colorConvert.w = lightGain; Color lightColorFinal = rlImGuiColors::Convert(colorConvert); pointLights.emplace_back(mouseWorldPos, lightColorFinal); shouldRender = true; } } void Lighting::createAreaLight(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; if (IO::mousePress(0) && myVar.toolAreaLight) { ImVec4 colorConvert = rlImGuiColors::Convert(lightColor); colorConvert.w = lightGain; Color lightColorFinal = rlImGuiColors::Convert(colorConvert); areaLights.emplace_back(mouseWorldPos, mouseWorldPos, lightColorFinal, lightSpread); } if (IO::mouseDown(0) && myVar.toolAreaLight) { if (areaLights.back().isBeingSpawned) { areaLights.back().vB = mouseWorldPos; shouldRender = true; } } if (IO::mouseReleased(0) && myVar.toolAreaLight) { if (!areaLights.empty() && areaLights.back().isBeingSpawned) { if (glm::length(areaLights.back().vB - areaLights.back().vA) == 0.0f) { areaLights.pop_back(); return; } areaLights.back().isBeingSpawned = false; } } } void Lighting::createConeLight(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; if (IO::mousePress(0) && myVar.toolConeLight) { ImVec4 colorConvert = rlImGuiColors::Convert(lightColor); colorConvert.w = lightGain; Color lightColorFinal = rlImGuiColors::Convert(colorConvert); coneLights.emplace_back(mouseWorldPos, mouseWorldPos, lightColorFinal, lightSpread); } if (IO::mouseDown(0) && myVar.toolConeLight) { if (coneLights.back().isBeingSpawned) { coneLights.back().vB = mouseWorldPos; shouldRender = true; } } if (IO::mouseReleased(0) && myVar.toolConeLight) { if (!coneLights.empty() && coneLights.back().isBeingSpawned) { if (glm::length(coneLights.back().vB - coneLights.back().vA) == 0.0f) { coneLights.pop_back(); return; } coneLights.back().isBeingSpawned = false; } } } void Lighting::movePointLights(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::mousePress(0) && myVar.toolMoveOptics) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; glm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y); glm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom); for (PointLight& pointLight : pointLights) { glm::vec2 d = pointLight.pos - mouseWorldPos; float dist = glm::length(d); if (dist <= myParam.brush.brushRadius) { pointLight.isBeingMoved = true; } } } for (PointLight& pointLight : pointLights) { glm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y); glm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom); if (pointLight.isBeingMoved) { pointLight.pos += scaledDelta; shouldRender = true; } } if (IO::mouseReleased(0) && myVar.toolMoveOptics) { for (PointLight& pointLight : pointLights) { pointLight.isBeingMoved = false; } } } void Lighting::moveAreaLights(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; glm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y); glm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom); if (IO::mousePress(0) && myVar.toolMoveOptics) { for (AreaLight& areaLight : areaLights) { glm::vec2 dA = areaLight.vA - mouseWorldPos; glm::vec2 dB = areaLight.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= myParam.brush.brushRadius) { areaLight.vAisBeingMoved = true; } if (distB <= myParam.brush.brushRadius) { areaLight.vBisBeingMoved = true; } } } if (IO::mouseDown(0) && myVar.toolMoveOptics) { for (AreaLight& areaLight : areaLights) { if (areaLight.vAisBeingMoved) { areaLight.vA += scaledDelta; shouldRender = true; } if (areaLight.vBisBeingMoved) { areaLight.vB += scaledDelta; shouldRender = true; } } } if (IO::mouseReleased(0) && myVar.toolMoveOptics) { for (AreaLight& areaLight : areaLights) { areaLight.vAisBeingMoved = false; areaLight.vBisBeingMoved = false; } } } void Lighting::moveConeLights(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; glm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y); glm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom); if (IO::mousePress(0) && myVar.toolMoveOptics) { for (ConeLight& coneLight : coneLights) { glm::vec2 dA = coneLight.vA - mouseWorldPos; glm::vec2 dB = coneLight.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= myParam.brush.brushRadius) { coneLight.vAisBeingMoved = true; } if (distB <= myParam.brush.brushRadius) { coneLight.vBisBeingMoved = true; } } } if (IO::mouseDown(0) && myVar.toolMoveOptics) { for (ConeLight& coneLight : coneLights) { if (coneLight.vAisBeingMoved) { coneLight.vA += scaledDelta; shouldRender = true; } if (coneLight.vBisBeingMoved) { coneLight.vB += scaledDelta; shouldRender = true; } } } if (IO::mouseReleased(0) && myVar.toolMoveOptics) { for (ConeLight& coneLight : coneLights) { coneLight.vAisBeingMoved = false; coneLight.vBisBeingMoved = false; } } } void Lighting::moveWalls(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; glm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y); glm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom); if (IO::mousePress(0) && myVar.toolMoveOptics) { for (Wall& wall : walls) { glm::vec2 dA = wall.vA - mouseWorldPos; glm::vec2 dB = wall.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= myParam.brush.brushRadius) { wall.vAisBeingMoved = true; } if (distB <= myParam.brush.brushRadius) { wall.vBisBeingMoved = true; } } } if (IO::mouseDown(0) && myVar.toolMoveOptics) { float moveRelaxFactor = shapeRelaxFactor * 0.06f; for (Wall& wall : walls) { if (wall.vAisBeingMoved) { wall.vA += scaledDelta; if (wall.isShapeWall && relaxMove) { for (Shape& shape : shapes) { if (shape.id == wall.shapeId) { shape.relaxShape(shapeRelaxIter, moveRelaxFactor); } } } shouldRender = true; } if (wall.vBisBeingMoved) { wall.vB += scaledDelta; if (wall.isShapeWall && relaxMove) { for (Shape& shape : shapes) { if (shape.id == wall.shapeId) { shape.relaxShape(shapeRelaxIter, moveRelaxFactor); } } } shouldRender = true; } calculateWallNormal(wall); } for (Shape& shape : shapes) { shape.calculateWallsNormals(); } } if (IO::mouseReleased(0) && myVar.toolMoveOptics) { for (Wall& wall : walls) { wall.vAisBeingMoved = false; wall.vBisBeingMoved = false; } } } void Lighting::moveLogic(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; minHelperLength = FLT_MAX; if (!shapes.empty()) { for (size_t i = 0; i < shapes.size(); i++) { shapes[i].drawHoverHelpers = false; for (size_t j = 0; j < shapes[i].helpers.size(); j++) { float helperDist = glm::length(mouseWorldPos - shapes[i].helpers[j]); if (shapes[i].shapeType == circle) { float helperCircleDist = glm::length(mouseWorldPos - shapes[i].helpers[0]); if (helperCircleDist <= shapes[i].circleRadius && !shapes[i].isBeingSpawned) { shapes[i].drawHoverHelpers = true; } } if (helperDist <= helperMinDist) { if (!shapes[i].isBeingSpawned) { shapes[i].drawHoverHelpers = true; } if (helperDist < minHelperLength) { minHelperLength = helperDist; if (IO::mousePress(0) && myVar.toolMoveOptics) { selectedShape = i; selectedHelper = j; } } } } } } if (selectedHelper == -1 && selectedShape == -1 && !isAnyShapeBeingSpawned) { movePointLights(myVar, myParam); moveAreaLights(myVar, myParam); moveConeLights(myVar, myParam); moveWalls(myVar, myParam); return; // We are not moving helpers, so get out of the function } if (IO::mouseDown(0) && myVar.toolMoveOptics) { for (Shape& shape : shapes) { if (shape.isBeingSpawned) { isAnyShapeBeingSpawned = true; break; } } if (selectedHelper != -1 && selectedShape != -1 && !isAnyShapeBeingSpawned) { shapes.at(selectedShape).isBeingMoved = true; if (selectedHelper != 2 || selectedHelper != 3) { glm::vec2 oldCenter = shapes.at(selectedShape).helpers[0]; glm::vec2 oldEdge = shapes.at(selectedShape).helpers[1]; glm::vec2 radiusVec = oldEdge - oldCenter; float radius = glm::length(radiusVec); glm::vec2 radiusDir = glm::normalize(radiusVec); shapes.at(selectedShape).helpers.at(selectedHelper) = mouseWorldPos; if (shapes.at(selectedShape).shapeType == circle) { if (selectedHelper == 0) { shapes.at(selectedShape).helpers[1] = mouseWorldPos + radiusDir * radius; } } } if (selectedHelper == 2) { shapes.at(selectedShape).isThirdBeingMoved = true; shapes.at(selectedShape).moveH2 = mouseWorldPos; } if (selectedHelper == 3) { shapes.at(selectedShape).isFourthBeingMoved = true; shapes.at(selectedShape).moveH2 = mouseWorldPos; } if (shapes[selectedShape].symmetricalLens) { if (selectedHelper == 4) { shapes.at(selectedShape).isFifthBeingMoved = true; shapes.at(selectedShape).moveH2 = mouseWorldPos; } } if (shapes[selectedShape].symmetricalLens) { if ((selectedHelper == 3 || selectedHelper == 4) && IO::shortcutDown(KEY_LEFT_CONTROL)) { shapes.at(selectedShape).isFifthFourthMoved = true; shapes.at(selectedShape).moveH2 = mouseWorldPos; } } if (shapes[selectedShape].shapeType == lens) { if (selectedHelper == shapes[selectedShape].helpers.size() - 1) { shapes.at(selectedShape).isGlobalHelperMoved = true; shapes.at(selectedShape).helpers.back() = mouseWorldPos; } } shapes.at(selectedShape).makeShape(); } shouldRender = true; } if (IO::mouseReleased(0) && myVar.toolMoveOptics) { if (selectedHelper != -1 && selectedShape != -1 && !isAnyShapeBeingSpawned) { shapes.at(selectedShape).isBeingMoved = false; shapes.at(selectedShape).isThirdBeingMoved = false; shapes.at(selectedShape).isFourthBeingMoved = false; shapes.at(selectedShape).isFifthBeingMoved = false; shapes.at(selectedShape).isFifthFourthMoved = false; shapes.at(selectedShape).isGlobalHelperMoved = false; } shouldRender = true; minHelperLength = FLT_MAX; selectedShape = -1; selectedHelper = -1; } } void Lighting::eraseLogic(UpdateVariables& myVar, UpdateParameters& myParam) { bool anySelectedWalls = false; bool anySelectedLights = false; for (Wall& wall : walls) { if (wall.isSelected) { anySelectedWalls = true; break; } } for (PointLight& p : pointLights) { if (p.isSelected) { anySelectedLights = true; break; } } for (AreaLight& a : areaLights) { if (a.isSelected) { anySelectedLights = true; break; } } for (ConeLight& l : coneLights) { if (l.isSelected) { anySelectedLights = true; break; } } glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; if (IO::mouseDown(0) && myVar.toolEraseOptics) { for (int i = static_cast(walls.size()) - 1; i >= 0; --i) { Wall& wall = walls[i]; glm::vec2 dA = wall.vA - mouseWorldPos; glm::vec2 dB = wall.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= myParam.brush.brushRadius || distB <= myParam.brush.brushRadius) { if (wall.isShapeWall) { for (size_t shapeIdx = 0; shapeIdx < shapes.size(); shapeIdx++) { Shape& shape = shapes[shapeIdx]; if (shape.id == wall.shapeId) { std::vector& myIds = shape.myWallIds; uint32_t wallId = wall.id; myIds.erase(std::remove(myIds.begin(), myIds.end(), wallId), myIds.end()); shape.isShapeClosed = false; for (uint32_t id : myIds) { Wall* shapeWall = getWallById(*shape.walls, id); if (shapeWall) { shapeWall->isShapeClosed = false; } } } } } walls.erase(walls.begin() + i); shouldRender = true; } } for (int i = static_cast(pointLights.size()) - 1; i >= 0; --i) { PointLight& pointLight = pointLights[i]; glm::vec2 d = pointLight.pos - mouseWorldPos; float dist = glm::length(d); if (dist <= myParam.brush.brushRadius) { pointLights.erase(pointLights.begin() + i); shouldRender = true; } } for (int i = static_cast(areaLights.size()) - 1; i >= 0; --i) { AreaLight& areaLight = areaLights[i]; glm::vec2 dA = areaLight.vA - mouseWorldPos; glm::vec2 dB = areaLight.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= myParam.brush.brushRadius || distB <= myParam.brush.brushRadius) { areaLights.erase(areaLights.begin() + i); shouldRender = true; } } for (int i = static_cast(coneLights.size()) - 1; i >= 0; --i) { ConeLight& coneLight = coneLights[i]; glm::vec2 dA = coneLight.vA - mouseWorldPos; glm::vec2 dB = coneLight.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= myParam.brush.brushRadius || distB <= myParam.brush.brushRadius) { coneLights.erase(coneLights.begin() + i); shouldRender = true; } } } if (IO::shortcutPress(KEY_DELETE)) { for (int i = static_cast(walls.size()) - 1; i >= 0; --i) { Wall& wall = walls[i]; if (wall.isSelected) { for (size_t shapeIdx = 0; shapeIdx < shapes.size(); shapeIdx++) { Shape& shape = shapes[shapeIdx]; if (shape.id == wall.shapeId) { std::vector& myIds = shape.myWallIds; uint32_t wallId = wall.id; myIds.erase(std::remove(myIds.begin(), myIds.end(), wallId), myIds.end()); shape.isShapeClosed = false; for (uint32_t id : myIds) { Wall* shapeWall = getWallById(*shape.walls, id); if (shapeWall) { shapeWall->isShapeClosed = false; } } } } walls.erase(walls.begin() + i); } } for (int i = static_cast(shapes.size()) - 1; i >= 0; --i) { Shape& shape = shapes[i]; if (shape.myWallIds.size() == 0) { shapes.erase(shapes.begin() + i); } } for (int i = static_cast(pointLights.size()) - 1; i >= 0; --i) { PointLight& pointLight = pointLights[i]; if (pointLight.isSelected) { pointLights.erase(pointLights.begin() + i); } } for (int i = static_cast(areaLights.size()) - 1; i >= 0; --i) { AreaLight& areaLight = areaLights[i]; if (areaLight.isSelected) { areaLights.erase(areaLights.begin() + i); } } for (int i = static_cast(coneLights.size()) - 1; i >= 0; --i) { ConeLight& coneLight = coneLights[i]; if (coneLight.isSelected) { coneLights.erase(coneLights.begin() + i); } } wallPointers.clear(); for (Wall& wall : walls) { wallPointers.push_back(&wall); } bvh.build(wallPointers); } if (IO::mouseReleased(0) && myVar.toolEraseOptics) { for (int i = static_cast(shapes.size()) - 1; i >= 0; --i) { if (shapes[i].myWallIds.empty()) { shapes.erase(shapes.begin() + i); } } } if ((IO::mouseReleased(0) && myVar.toolEraseOptics) || ((anySelectedWalls || anySelectedLights) && IO::shortcutPress(KEY_DELETE))) { shouldRender = true; wallPointers.clear(); for (Wall& wall : walls) { wallPointers.push_back(&wall); } bvh.build(wallPointers); } } // I'm also sorry for this large chunk of ugly code, but I really hate working on anything that involves UI or UX stuff (: void Lighting::selectLogic(UpdateVariables& myVar, UpdateParameters& myParam) { selectedWalls = 0; selectedLights = 0; int selectedAreaLights = 0; int selectedConeLights = 0; int selectedPointLights = 0; bool isHoveringAnything = false; if (myVar.toolSelectOptics) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; if (IO::mousePress(0)) { boxInitialPos = mouseWorldPos; isBoxSelecting = true; isBoxDeselecting = IO::shortcutDown(KEY_LEFT_ALT); } if (IO::mouseDown(0) && isBoxSelecting) { boxX = fmin(boxInitialPos.x, mouseWorldPos.x); boxY = fmin(boxInitialPos.y, mouseWorldPos.y); boxWidth = fabs(mouseWorldPos.x - boxInitialPos.x); boxHeight = fabs(mouseWorldPos.y - boxInitialPos.y); } if (IO::mouseReleased(0) && isBoxSelecting) { float boxX1 = fmin(boxInitialPos.x, mouseWorldPos.x); float boxX2 = fmax(boxInitialPos.x, mouseWorldPos.x); float boxY1 = fmin(boxInitialPos.y, mouseWorldPos.y); float boxY2 = fmax(boxInitialPos.y, mouseWorldPos.y); for (Wall& wall : walls) { bool vAInBox = wall.vA.x >= boxX1 && wall.vA.x <= boxX2 && wall.vA.y >= boxY1 && wall.vA.y <= boxY2; bool vBInBox = wall.vB.x >= boxX1 && wall.vB.x <= boxX2 && wall.vB.y >= boxY1 && wall.vB.y <= boxY2; if (vAInBox || vBInBox) { if (isBoxDeselecting && wall.isSelected) { wall.isSelected = false; } else if (!isBoxDeselecting) { wall.isSelected = true; } } } for (AreaLight& areaLight : areaLights) { bool vAInBox = areaLight.vA.x >= boxX1 && areaLight.vA.x <= boxX2 && areaLight.vA.y >= boxY1 && areaLight.vA.y <= boxY2; bool vBInBox = areaLight.vB.x >= boxX1 && areaLight.vB.x <= boxX2 && areaLight.vB.y >= boxY1 && areaLight.vB.y <= boxY2; if (vAInBox || vBInBox) { if (isBoxDeselecting && areaLight.isSelected) { areaLight.isSelected = false; } else if (!isBoxDeselecting) { areaLight.isSelected = true; } } } for (ConeLight& coneLight : coneLights) { bool vAInBox = coneLight.vA.x >= boxX1 && coneLight.vA.x <= boxX2 && coneLight.vA.y >= boxY1 && coneLight.vA.y <= boxY2; bool vBInBox = coneLight.vB.x >= boxX1 && coneLight.vB.x <= boxX2 && coneLight.vB.y >= boxY1 && coneLight.vB.y <= boxY2; if (vAInBox || vBInBox) { if (isBoxDeselecting && coneLight.isSelected) { coneLight.isSelected = false; } else if (!isBoxDeselecting) { coneLight.isSelected = true; } } } for (PointLight& pointLight : pointLights) { bool pointInBox = pointLight.pos.x >= boxX1 && pointLight.pos.x <= boxX2 && pointLight.pos.y >= boxY1 && pointLight.pos.y <= boxY2; if (pointInBox) { if (isBoxDeselecting && pointLight.isSelected) { pointLight.isSelected = false; } else if (!isBoxDeselecting) { pointLight.isSelected = true; } } } boxX = 0.0f; boxY = 0.0f; boxWidth = 0.0f; boxHeight = 0.0f; isBoxSelecting = false; isBoxDeselecting = false; } for (Wall& wall : walls) { glm::vec2 wallLine = wall.vB - wall.vA; glm::vec2 mouseToA = mouseWorldPos - wall.vA; float lineLenSquared = glm::dot(wallLine, wallLine); if (lineLenSquared == 0.0f) { float dist = glm::length(mouseToA); continue; } float t = glm::dot(mouseToA, wallLine) / lineLenSquared; t = glm::clamp(t, 0.0f, 1.0f); glm::vec2 closestPoint = wall.vA + wallLine * t; glm::vec2 diff = mouseWorldPos - closestPoint; float distance = glm::length(diff); if (distance < 5.0f) { isHoveringAnything = true; if (IO::mousePress(0)) { if (!IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { for (Wall& wallToDeselect : walls) { wallToDeselect.isSelected = false; } for (AreaLight& areaLightToDeselect : areaLights) { areaLightToDeselect.isSelected = false; } for (ConeLight& coneLightToDeselect : coneLights) { coneLightToDeselect.isSelected = false; } for (PointLight& pointLight : pointLights) { pointLight.isSelected = false; } } if (!IO::shortcutDown(KEY_LEFT_ALT)) { wall.isSelected = true; } if (IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_LEFT_SHIFT) && wall.isShapeWall) { wall.isSelected = false; } if (wall.isShapeWall) { if (IO::shortcutDown(KEY_LEFT_SHIFT)) { for (Shape& shape : shapes) { if (shape.id == wall.shapeId) { for (uint32_t wallId : shape.myWallIds) { Wall* wall = getWallById(walls, wallId); if (wall) wall->isSelected = true; } } } } } if (IO::shortcutDown(KEY_LEFT_ALT) && IO::shortcutDown(KEY_LEFT_SHIFT) && wall.isShapeWall) { for (Shape& shape : shapes) { if (shape.id == wall.shapeId) { for (uint32_t wallId : shape.myWallIds) { Wall* wall = getWallById(walls, wallId); if (wall) wall->isSelected = true; } } } } } wall.apparentColor = RED; } } for (AreaLight& areaLight : areaLights) { glm::vec2 areaLightLine = areaLight.vB - areaLight.vA; glm::vec2 mouseToA = mouseWorldPos - areaLight.vA; float lineLenSquared = glm::dot(areaLightLine, areaLightLine); if (lineLenSquared == 0.0f) { float dist = glm::length(mouseToA); continue; } float t = glm::dot(mouseToA, areaLightLine) / lineLenSquared; t = glm::clamp(t, 0.0f, 1.0f); glm::vec2 closestPoint = areaLight.vA + areaLightLine * t; glm::vec2 diff = mouseWorldPos - closestPoint; float distance = glm::length(diff); if (distance < 5.0f) { isHoveringAnything = true; if (IO::mousePress(0)) { if (!IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { for (AreaLight& areaLightToDeselect : areaLights) { areaLightToDeselect.isSelected = false; } for (ConeLight& coneLightToDeselect : coneLights) { coneLightToDeselect.isSelected = false; } for (PointLight& pointLight : pointLights) { pointLight.isSelected = false; } for (Wall& wall : walls) { wall.isSelected = false; } } if (!IO::shortcutDown(KEY_LEFT_ALT)) { areaLight.isSelected = true; } if (IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_LEFT_SHIFT)) { areaLight.isSelected = false; } } areaLight.apparentColor = RED; } } for (ConeLight& coneLight : coneLights) { glm::vec2 areaLightLine = coneLight.vB - coneLight.vA; glm::vec2 mouseToA = mouseWorldPos - coneLight.vA; float lineLenSquared = glm::dot(areaLightLine, areaLightLine); if (lineLenSquared == 0.0f) { float dist = glm::length(mouseToA); continue; } float t = glm::dot(mouseToA, areaLightLine) / lineLenSquared; t = glm::clamp(t, 0.0f, 1.0f); glm::vec2 closestPoint = coneLight.vA + areaLightLine * t; glm::vec2 diff = mouseWorldPos - closestPoint; float distance = glm::length(diff); if (distance < 5.0f) { isHoveringAnything = true; if (IO::mousePress(0)) { if (!IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { for (AreaLight& areaLightToDeselect : areaLights) { areaLightToDeselect.isSelected = false; } for (ConeLight& coneLightToDeselect : coneLights) { coneLightToDeselect.isSelected = false; } for (PointLight& pointLight : pointLights) { pointLight.isSelected = false; } for (Wall& wall : walls) { wall.isSelected = false; } } if (!IO::shortcutDown(KEY_LEFT_ALT)) { coneLight.isSelected = true; } if (IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_LEFT_SHIFT)) { coneLight.isSelected = false; } } coneLight.apparentColor = RED; } } for (PointLight& pointLight : pointLights) { glm::vec2 mouseToA = mouseWorldPos - pointLight.pos; float distance = glm::length(mouseToA); if (distance < 5.0f) { isHoveringAnything = true; if (IO::mousePress(0)) { if (!IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { for (PointLight& pointLight : pointLights) { pointLight.isSelected = false; } for (ConeLight& coneLightToDeselect : coneLights) { coneLightToDeselect.isSelected = false; } for (AreaLight& areaLightToDeselect : areaLights) { areaLightToDeselect.isSelected = false; } for (Wall& wall : walls) { wall.isSelected = false; } } if (!IO::shortcutDown(KEY_LEFT_ALT)) { pointLight.isSelected = true; } if (IO::shortcutDown(KEY_LEFT_ALT) && !IO::shortcutDown(KEY_LEFT_SHIFT)) { pointLight.isSelected = false; } } pointLight.apparentColor = RED; } } if (!isHoveringAnything && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT)) { if (IO::mousePress(0)) { for (Wall& wall : walls) { wall.isSelected = false; } for (AreaLight& areaLight : areaLights) { areaLight.isSelected = false; } for (ConeLight& coneLight : coneLights) { coneLight.isSelected = false; } for (PointLight& pointLight : pointLights) { pointLight.isSelected = false; } } } for (Wall& wall : walls) { if (wall.isSelected) { wall.apparentColor = RED; selectedWalls++; } } for (AreaLight& areaLight : areaLights) { if (areaLight.isSelected) { areaLight.apparentColor = RED; selectedAreaLights++; } } for (ConeLight& coneLight : coneLights) { if (coneLight.isSelected) { coneLight.apparentColor = RED; selectedConeLights++; } } for (PointLight& pointLight : pointLights) { if (pointLight.isSelected) { pointLight.apparentColor = RED; selectedPointLights++; } } if (IO::mouseReleased(0)) { if (selectedWalls > 0) { baseColorAvg = { 0, 0, 0, 0 }; ImVec4 baseColorAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f }; specularColorAvg = { 0, 0, 0, 0 }; ImVec4 specularColorAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f }; refractionColorAvg = { 0, 0, 0, 0 }; ImVec4 refractionColAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f }; emissionColorAvg = { 0, 0, 0, 0 }; ImVec4 emissionColAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f }; specularRoughAvg = 0.0f; refractionRoughAvg = 0.0f; refractionAmountAvg = 0.0f; iorAvg = 0.0f; dispersionAvg = 0.0f; emissionGainAvg = 0.0f; for (Wall& wall : walls) { if (wall.isSelected) { // Base Color ImVec4 wallBaseColImgui = rlImGuiColors::Convert(wall.baseColor); baseColorAvgImgui.x += wallBaseColImgui.x; baseColorAvgImgui.y += wallBaseColImgui.y; baseColorAvgImgui.z += wallBaseColImgui.z; baseColorAvgImgui.w += wallBaseColImgui.w; // Specular Color ImVec4 wallSpecularColImgui = rlImGuiColors::Convert(wall.specularColor); specularColorAvgImgui.x += wallSpecularColImgui.x; specularColorAvgImgui.y += wallSpecularColImgui.y; specularColorAvgImgui.z += wallSpecularColImgui.z; specularColorAvgImgui.w += wallSpecularColImgui.w; // Refraction Color ImVec4 wallRefractionColImgui = rlImGuiColors::Convert(wall.refractionColor); refractionColAvgImgui.x += wallRefractionColImgui.x; refractionColAvgImgui.y += wallRefractionColImgui.y; refractionColAvgImgui.z += wallRefractionColImgui.z; refractionColAvgImgui.w += wallRefractionColImgui.w; // Emission Color ImVec4 wallEmissionColImgui = rlImGuiColors::Convert(wall.emissionColor); emissionColAvgImgui.x += wallEmissionColImgui.x; emissionColAvgImgui.y += wallEmissionColImgui.y; emissionColAvgImgui.z += wallEmissionColImgui.z; emissionColAvgImgui.w += wallEmissionColImgui.w; // Specular Roughness specularRoughAvg += wall.specularRoughness; // Refraction Surface Roughness refractionRoughAvg += wall.refractionRoughness; // Refraction Amount refractionAmountAvg += wall.refractionAmount; // IOR iorAvg += wall.IOR; // Dispersion dispersionAvg += wall.dispersionStrength; // Emission emissionGainAvg = wallEmissionColImgui.w; } } // Base Color baseColorAvgImgui.x /= selectedWalls; baseColorAvgImgui.y /= selectedWalls; baseColorAvgImgui.z /= selectedWalls; baseColorAvgImgui.w /= selectedWalls; wallBaseColor = rlImGuiColors::Convert(baseColorAvgImgui); // Specular Color specularColorAvgImgui.x /= selectedWalls; specularColorAvgImgui.y /= selectedWalls; specularColorAvgImgui.z /= selectedWalls; specularColorAvgImgui.w /= selectedWalls; wallSpecularColor = rlImGuiColors::Convert(specularColorAvgImgui); // Refraction Color refractionColAvgImgui.x /= selectedWalls; refractionColAvgImgui.y /= selectedWalls; refractionColAvgImgui.z /= selectedWalls; refractionColAvgImgui.w /= selectedWalls; wallRefractionColor = rlImGuiColors::Convert(refractionColAvgImgui); // Emission Color emissionColAvgImgui.x /= selectedWalls; emissionColAvgImgui.y /= selectedWalls; emissionColAvgImgui.z /= selectedWalls; emissionColAvgImgui.w /= selectedWalls; wallEmissionColor = rlImGuiColors::Convert(emissionColAvgImgui); // Specular Roughness specularRoughAvg /= selectedWalls; wallSpecularRoughness = specularRoughAvg; // Refraction Surface Roughness refractionRoughAvg /= selectedWalls; wallRefractionRoughness = refractionRoughAvg; // Refraction Amount refractionAmountAvg /= selectedWalls; wallRefractionAmount = refractionAmountAvg; // IOR iorAvg /= selectedWalls; wallIOR = iorAvg; // Dispersion dispersionAvg /= selectedWalls; wallDispersion = dispersionAvg; // Emission emissionGainAvg = emissionColAvgImgui.w; wallEmissionGain = emissionGainAvg; } if (selectedAreaLights > 0 || selectedPointLights > 0 || selectedConeLights > 0) { lightColorAvg = { 0, 0, 0, 0 }; ImVec4 lightColorAvgImgui = { 0.0f, 0.0f, 0.0f, 0.0f }; lightSpreadAvg = 0.0f; lightGainAvg = 0.0f; if (selectedAreaLights > 0) { for (AreaLight& arealight : areaLights) { if (arealight.isSelected) { // Light Color ImVec4 lightColImgui = rlImGuiColors::Convert(arealight.color); lightColorAvgImgui.x += lightColImgui.x; lightColorAvgImgui.y += lightColImgui.y; lightColorAvgImgui.z += lightColImgui.z; lightColorAvgImgui.w += lightColImgui.w; // Light Spread lightSpreadAvg += arealight.spread; // Light Gain lightGainAvg = lightColorAvgImgui.w; } } } if (selectedConeLights > 0) { for (ConeLight& coneLight : coneLights) { if (coneLight.isSelected) { // Light Color ImVec4 lightColImgui = rlImGuiColors::Convert(coneLight.color); lightColorAvgImgui.x += lightColImgui.x; lightColorAvgImgui.y += lightColImgui.y; lightColorAvgImgui.z += lightColImgui.z; lightColorAvgImgui.w += lightColImgui.w; // Light Spread lightSpreadAvg += coneLight.spread; // Light Gain lightGainAvg = lightColorAvgImgui.w; } } } if (selectedPointLights > 0) { for (PointLight& pointLight : pointLights) { if (pointLight.isSelected) { // Light Color ImVec4 lightColImgui = rlImGuiColors::Convert(pointLight.color); lightColorAvgImgui.x += lightColImgui.x; lightColorAvgImgui.y += lightColImgui.y; lightColorAvgImgui.z += lightColImgui.z; lightColorAvgImgui.w += lightColImgui.w; // Light Gain lightGainAvg = lightColorAvgImgui.w; } } } // Light Color lightColorAvgImgui.x /= selectedAreaLights + selectedPointLights + selectedConeLights; lightColorAvgImgui.y /= selectedAreaLights + selectedPointLights + selectedConeLights; lightColorAvgImgui.z /= selectedAreaLights + selectedPointLights + selectedConeLights; lightColorAvgImgui.w /= selectedAreaLights + selectedPointLights + selectedConeLights; lightColor = rlImGuiColors::Convert(lightColorAvgImgui); // Light Spread if (selectedAreaLights + selectedConeLights > 0) { lightSpreadAvg /= selectedAreaLights + selectedConeLights; lightSpread = lightSpreadAvg; } // Light Gain lightGainAvg = lightColorAvgImgui.w; lightGain = lightGainAvg; } } } if (IO::mouseReleased(0) && !myVar.toolSelectOptics) { for (Wall& wall : walls) { wall.isSelected = false; } for (AreaLight& areaLight : areaLights) { areaLight.isSelected = false; } for (ConeLight& coneLight : coneLights) { coneLight.isSelected = false; } for (PointLight& pointLight : pointLights) { pointLight.isSelected = false; } } bool isAnyActive = false; for (bool* param : uiOpticElements) { if (*param) { isAnyActive = true; } } if (isAnyActive && selectedWalls > 0) { for (Wall& wall : walls) { if (wall.isSelected) { if (isSliderBaseColor) { wall.baseColor = wallBaseColor; } if (isSliderSpecularColor) { wall.specularColor = wallSpecularColor; } if (isSliderRefractionCol) { wall.refractionColor = wallRefractionColor; } if (isSliderEmissionCol) { ImVec4 convertedColor = rlImGuiColors::Convert(wallEmissionColor); wall.emissionColor = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, wallEmissionGain }); } if (isSliderSpecularRough) { wall.specularRoughness = wallSpecularRoughness; } if (isSliderRefractionRough) { wall.refractionRoughness = wallRefractionRoughness; } if (isSliderRefractionAmount) { wall.refractionAmount = wallRefractionAmount; } if (isSliderIor) { wall.IOR = wallIOR; } if (isSliderDispersion) { wall.dispersionStrength = wallDispersion; } if (isSliderEmissionGain) { ImVec4 convertedColor = rlImGuiColors::Convert(wall.emissionColor); wall.emissionColor = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, wallEmissionGain }); } } } shouldRender = true; } if (isAnyActive && (selectedAreaLights > 0 || selectedPointLights > 0 || selectedConeLights > 0)) { if (selectedAreaLights > 0) { for (AreaLight& areaLight : areaLights) { if (areaLight.isSelected) { if (isSliderLightGain) { ImVec4 convertedColor = rlImGuiColors::Convert(areaLight.color); areaLight.color = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, lightGain }); } if (isSliderlightSpread) { areaLight.spread = lightSpread; } if (isSliderLightColor) { ImVec4 convertedColor = rlImGuiColors::Convert(lightColor); areaLight.color = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, lightGain }); } } } } if (selectedConeLights > 0) { for (ConeLight& coneLight : coneLights) { if (coneLight.isSelected) { if (isSliderLightGain) { ImVec4 convertedColor = rlImGuiColors::Convert(coneLight.color); coneLight.color = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, lightGain }); } if (isSliderlightSpread) { coneLight.spread = lightSpread; } if (isSliderLightColor) { coneLight.color = lightColor; } } } } if (selectedPointLights > 0) { for (PointLight& pointLight : pointLights) { if (pointLight.isSelected) { if (isSliderLightGain) { ImVec4 convertedColor = rlImGuiColors::Convert(pointLight.color); pointLight.color = rlImGuiColors::Convert(ImVec4{ convertedColor.x, convertedColor.y, convertedColor.z, lightGain }); } if (isSliderLightColor) { pointLight.color = lightColor; } } } } shouldRender = true; } selectedLights = selectedPointLights + selectedAreaLights + selectedConeLights; isSliderLightGain = false; isSliderlightSpread = false; isSliderLightColor = false; isSliderBaseColor = false; isSliderSpecularColor = false; isSliderRefractionCol = false; isSliderEmissionCol = false; isSliderSpecularRough = false; isSliderRefractionRough = false; isSliderRefractionAmount = false; isSliderIor = false; isSliderDispersion = false; isSliderEmissionGain = false; } float Lighting::checkIntersect(const LightRay& ray, const Wall& w) { const float x1 = w.vA.x, y1 = w.vA.y; const float x2 = w.vB.x, y2 = w.vB.y; const float x3 = ray.source.x, y3 = ray.source.y; const float x4 = ray.source.x + ray.dir.x, y4 = ray.source.y + ray.dir.y; const float den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); if (den == 0.0f) { return ray.maxLength; } const float t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den; const float u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den; if (t > 0.0f && t < 1.0f && u > 0.0f) { return u; } return ray.maxLength; } void Lighting::processRayIntersection(LightRay& ray) { ray.hasHit = false; ray.length = ray.maxLength; float closestT = ray.maxLength; Wall* hitWall = nullptr; glm::vec2 hitPt; if (bvh.traverse(ray, closestT, hitWall, hitPt) && hitWall != nullptr) { ray.hasHit = true; ray.length = closestT; ray.hitPoint = hitPt; ray.wall = *hitWall; } } // This controls specular lighting, meaning reflections. It uses a roughness parameter to control how smooth a surface is void Lighting::specularReflection(int& currentBounce, LightRay& ray, std::vector& copyRays, std::vector& walls) { ray.reflectSpecular = true; glm::vec2 wallVec = ray.wall.vB - ray.wall.vA; float wallLength = glm::length(wallVec); float t = glm::clamp(glm::dot(ray.hitPoint - ray.wall.vA, wallVec) / (wallLength * wallLength), 0.0f, 1.0f); glm::vec2 interpolatedNormal = glm::normalize(glm::mix(ray.wall.normalVA, ray.wall.normalVB, t)); if (glm::dot(ray.wall.normal, ray.dir) > 0.0f) { interpolatedNormal = -interpolatedNormal; } float r0 = ((airIOR - ray.wall.IOR) / (airIOR + ray.wall.IOR)) * ((airIOR - ray.wall.IOR) / (airIOR + ray.wall.IOR)); float cosThetaI = -glm::dot(ray.dir, interpolatedNormal); cosThetaI = glm::clamp(cosThetaI, 0.0f, 1.0f); float rTheta = r0 + (1 - r0) * std::pow(1 - cosThetaI, 5.0f); if (getRandomFloat() > rTheta) { ray.reflectSpecular = false; return; } float roughness = ray.wall.specularRoughness; float maxSpreadAngle = glm::radians(90.0f * roughness); float randAngle = (static_cast(rand()) / static_cast(RAND_MAX)) * 2.0f * maxSpreadAngle - maxSpreadAngle; glm::vec2 perfectReflection = ray.dir - 2.0f * glm::dot(ray.dir, interpolatedNormal) * interpolatedNormal; glm::vec2 mixedReflection = glm::normalize(glm::mix(perfectReflection, interpolatedNormal, roughness)); glm::vec2 rayDirection = rotateVec2(mixedReflection, randAngle); Color newColor = { static_cast(ray.color.r * ray.wall.specularColor.r / 255.0f * absorptionInvBias), static_cast(ray.color.g * ray.wall.specularColor.g / 255.0f * absorptionInvBias), static_cast(ray.color.b * ray.wall.specularColor.b / 255.0f * absorptionInvBias), static_cast(ray.color.a) }; glm::vec2 newSource = ray.hitPoint + interpolatedNormal * lightBias; copyRays.emplace_back(newSource, rayDirection, ray.bounceLevel + 1, newColor); LightRay& newRay = copyRays.back(); processRayIntersection(newRay); } // This controls refraction void Lighting::refraction(int& currentBounce, LightRay& ray, std::vector& copyRays, std::vector& walls) { ray.refracted = true; glm::vec2 wallVec = ray.wall.vB - ray.wall.vA; float wallLength = glm::length(wallVec); float t = glm::clamp(glm::dot(ray.hitPoint - ray.wall.vA, wallVec) / (wallLength * wallLength), 0.0f, 1.0f); glm::vec2 interpolatedNormal = glm::normalize(glm::mix(ray.wall.normalVA, ray.wall.normalVB, t)); float n1, n2; bool entering; entering = glm::dot(interpolatedNormal, ray.dir) < 0.0f; if (!entering) { interpolatedNormal = -interpolatedNormal; } n1 = ray.mediumIORStack.back(); n2 = ray.wall.IOR; if (!entering && ray.mediumIORStack.size() > 1) { n2 = ray.mediumIORStack[ray.mediumIORStack.size() - 2]; } std::vector> activeChannels; Color newColor = { static_cast(ray.color.r * ray.wall.refractionColor.r / 255.0f * absorptionInvBias), static_cast(ray.color.g * ray.wall.refractionColor.g / 255.0f * absorptionInvBias), static_cast(ray.color.b * ray.wall.refractionColor.b / 255.0f * absorptionInvBias), static_cast(ray.color.a) }; float dispersionStrength = ray.wall.dispersionStrength; if (!entering) { newColor = ray.color; dispersionStrength *= -1.0f; } if (entering && isDispersionEnabled && ray.wall.dispersionStrength > 0.0f && !ray.hasBeenDispersed) { activeChannels = { {1.0f - dispersionStrength, {newColor.r, 0, 0, ray.color.a}}, // Red {1.0f, {0, newColor.g, 0, ray.color.a}}, // Green {1.0f + dispersionStrength, {0, 0, newColor.b, ray.color.a}} // Blue }; ray.hasBeenDispersed = true; } else { float scale = 1.0f; if (isDispersionEnabled && ray.wall.dispersionStrength > 0.0f && !entering) { if (ray.color.r > ray.color.g && ray.color.r > ray.color.b) { scale = 1.0f - dispersionStrength; // Red ray } else if (ray.color.g > ray.color.r && ray.color.g > ray.color.b) { scale = 1.0f; // Green ray } else { scale = 1.0f + dispersionStrength; // Blue ray } } activeChannels = { {scale, newColor} }; } for (const auto& [scale, channelColor] : activeChannels) { float dispersedN2 = n2 * scale; float eta = n1 / dispersedN2; float cosThetaI = -glm::dot(ray.dir, interpolatedNormal); float sin2ThetaT = eta * eta * (1.0f - cosThetaI * cosThetaI); if (sin2ThetaT > 1.0f || getRandomFloat() > ray.wall.refractionAmount) { ray.refracted = false; if (isSpecularEnabled) { specularReflection(currentBounce, ray, copyRays, walls); } return; } float cosThetaT = sqrtf(1.0f - sin2ThetaT); glm::vec2 refractedDir = eta * ray.dir + (eta * cosThetaI - cosThetaT) * interpolatedNormal; refractedDir = glm::normalize(refractedDir); float roughness = ray.wall.refractionRoughness; float maxSpreadAngle = glm::radians(90.0f * roughness); float randAngle = (static_cast(rand()) / static_cast(RAND_MAX)) * 2.0f * maxSpreadAngle - maxSpreadAngle; refractedDir = rotateVec2(refractedDir, randAngle); glm::vec2 newSource = ray.hitPoint - interpolatedNormal * lightBias; copyRays.emplace_back(newSource, refractedDir, ray.bounceLevel + 1, channelColor); LightRay& newRay = copyRays.back(); newRay.mediumIORStack = ray.mediumIORStack; if (entering) { newRay.mediumIORStack.push_back(n2); } else if (newRay.mediumIORStack.size() > 1) { newRay.mediumIORStack.pop_back(); } newRay.hasBeenDispersed = true; processRayIntersection(newRay); } } // UNFINISHED VOLUME SCATTERING CODE. I might work on it sometime in the future //void Lighting::volumeScatter(int& currentBounce, LightRay& ray, std::vector& copyRays, std::vector& walls) { // // glm::vec2 wallVec = ray.wall.vB - ray.wall.vA; // float wallLength = glm::length(wallVec); // float t = glm::clamp(glm::dot(ray.hitPoint - ray.wall.vA, wallVec) / (wallLength * wallLength), 0.0f, 1.0f); // // glm::vec2 interpolatedNormal = glm::normalize(glm::mix(ray.wall.normalVA, ray.wall.normalVB, t)); // // bool entering = glm::dot(interpolatedNormal, ray.dir) < 0.0f; // // if (glm::dot(ray.wall.normal, ray.dir) > 0.0f) { // interpolatedNormal = -interpolatedNormal; // } // // float spreadMultiplier = 0.55f; // float maxSpreadAngle = glm::radians(90.0f * spreadMultiplier); // // float randAngle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * PI; // // glm::vec2 rayDirection = glm::vec2(cos(randAngle), sin(randAngle)); // // bool enterAfterBounce = glm::dot(rayDirection, ray.dir) < 0.0f; // // if (enterAfterBounce) { // interpolatedNormal = -interpolatedNormal; // } // // Color newColor = { //static_cast(ray.color.r * ray.wall.baseColor.r / 255.0f), //static_cast(ray.color.g * ray.wall.baseColor.g / 255.0f), //static_cast(ray.color.b * ray.wall.baseColor.b / 255.0f), //static_cast(ray.color.a) // }; // // glm::vec2 newSource = ray.hitPoint - interpolatedNormal * lightBias; // // if (ray.hasBeenScattered && !ray.hasHit) { // newSource = ray.scatterSource + ray.maxLength * ray.dir; // } // // copyRays.emplace_back(newSource, rayDirection, ray.bounceLevel + 1, newColor); // // LightRay& newRay = copyRays.back(); // // if (!enterAfterBounce) { // newRay.hasBeenScattered = true; // newRay.maxLength = 50.0f; // } // else { // newRay.hasBeenScattered = false; // newRay.maxLength = 10000.0f; // } // // if (glm::dot(ray.wall.normal, ray.dir) > 0.0f && ray.hasBeenScattered && ray.hasHit) { // newRay.hasBeenScattered = false; // newRay.maxLength = 10000.0f; // } // // // if (!ray.hasBeenScattered) { // newRay.scatterSource = ray.hitPoint; // } // else { // newRay.scatterSource = newSource; // } // // processRayIntersection(newRay); //} // This controls diffuse lighting void Lighting::diffuseLighting(int& currentBounce, LightRay& ray, std::vector& copyRays, std::vector& walls) { glm::vec2 wallVec = ray.wall.vB - ray.wall.vA; float wallLength = glm::length(wallVec); float t = glm::clamp(glm::dot(ray.hitPoint - ray.wall.vA, wallVec) / (wallLength * wallLength), 0.0f, 1.0f); glm::vec2 interpolatedNormal = glm::normalize(glm::mix(ray.wall.normalVA, ray.wall.normalVB, t)); if (glm::dot(ray.wall.normal, ray.dir) > 0.0f) { interpolatedNormal = -interpolatedNormal; } float spreadMultiplier = 0.95f; float maxSpreadAngle = glm::radians(90.0f * spreadMultiplier); float randAngle = (static_cast(rand()) / static_cast(RAND_MAX)) * 2.0f * maxSpreadAngle - maxSpreadAngle; glm::vec2 rayDirection = rotateVec2(interpolatedNormal, randAngle); Color newColor = { static_cast(ray.color.r * ray.wall.baseColor.r / 255.0f * absorptionInvBias), static_cast(ray.color.g * ray.wall.baseColor.g / 255.0f * absorptionInvBias), static_cast(ray.color.b * ray.wall.baseColor.b / 255.0f * absorptionInvBias), static_cast(ray.color.a) }; glm::vec2 newSource = ray.hitPoint + interpolatedNormal * lightBias; copyRays.emplace_back(newSource, rayDirection, ray.bounceLevel + 1, newColor); LightRay& newRay = copyRays.back(); processRayIntersection(newRay); } int emissionWallsAmount = 0; void Lighting::emission() { int emissionWallsAmount = 0; for (Wall& w : walls) { if (w.emissionColor.a > 0.0f) { emissionWallsAmount++; } } if (emissionWallsAmount == 0) { return; } int totalRays = sampleRaysAmount / emissionWallsAmount; totalRays = std::max(totalRays, 3); for (Wall& w : walls) { if (w.emissionColor.a <= 0.0f) continue; for (int i = 0; i < totalRays; i++) { float maxSpreadAngle = glm::radians(90.0f * 0.99f); float randAngle = getRandomFloat() * 2.0f * maxSpreadAngle - maxSpreadAngle; glm::vec2 d = w.vB - w.vA; float length = glm::length(d); glm::vec2 dNormal = d / length; float t = getRandomFloat(); float bias = 0.01f; glm::vec2 source = w.vA + d * t + w.normal * bias; glm::vec2 rayDirection = rotateVec2(dNormal, randAngle); rayDirection = glm::vec2(rayDirection.y, -rayDirection.x); rays.emplace_back( source, rayDirection, 1, w.emissionColor ); } } } void Lighting::lightRendering(UpdateParameters& myParam) { if (currentSamples <= maxSamples) { rays.clear(); for (PointLight& pointLight : pointLights) { pointLight.pointLightLogic(sampleRaysAmount, currentSamples, maxSamples, rays); } for (AreaLight& areaLight : areaLights) { areaLight.areaLightLogic(sampleRaysAmount, rays); } for (ConeLight& coneLight : coneLights) { coneLight.coneLightLogic(sampleRaysAmount, rays); } if (isEmissionEnabled) { emission(); } #pragma omp parallel for for (LightRay& ray : rays) { processRayIntersection(ray); } for (int bounce = 1; bounce <= maxBounces; bounce++) { std::vector nextBounceRays; for (LightRay& ray : rays) { if ((ray.hasHit || ray.hasBeenScattered) && ray.bounceLevel == bounce) { if (isSpecularEnabled && ray.wall.specularColorVal > 0.0f) { specularReflection(bounce, ray, nextBounceRays, walls); } if (isRefractionEnabled && !ray.reflectSpecular && ray.wall.refractionColorVal > 0.0f) { refraction(bounce, ray, nextBounceRays, walls); } if (isDiffuseEnabled && ray.wall.refractionAmount < 1.0f && !ray.reflectSpecular && !ray.refracted) { diffuseLighting(bounce, ray, nextBounceRays, walls); } } } rays.insert(rays.end(), nextBounceRays.begin(), nextBounceRays.end()); } currentSamples++; } } void Lighting::drawMisc(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseWorldPos = myParam.myCamera.mouseWorldPos; processApparentColor(); //Draw selection box if (IO::mouseDown(0) && isBoxSelecting) { DrawRectangleV({ boxX, boxY }, { boxWidth, boxHeight }, { 40, 40, 40, 160 }); DrawRectangleLinesEx({ boxX, boxY, boxWidth, boxHeight }, 1.6f, WHITE); } // Draw circle spawn guide if (IO::mouseDown(0) && myVar.toolCircle) { if (!shapes.empty()) { if (shapes.back().isBeingSpawned) { Shape& shape = shapes.back(); float radius = glm::length(shape.h2 - shape.h1); for (int i = 0; i < shape.circleSegments; ++i) { float theta1 = (2.0f * PI * i) / shape.circleSegments; float theta2 = (2.0f * PI * (i + 1)) / shape.circleSegments; glm::vec2 vA = { shape.h1.x + cos(theta1) * radius, shape.h1.y + sin(theta1) * radius }; glm::vec2 vB = { shape.h1.x + cos(theta2) * radius, shape.h1.y + sin(theta2) * radius }; DrawLineV({ vA.x, vA.y }, { vB.x, vB.y }, WHITE); } } } } // Draw lens spawn guide if (myVar.toolLens) { if (!shapes.empty()) { if (shapes.back().isBeingSpawned) { Shape& shape = shapes.back(); if (shape.helpers.size() == 1) { DrawLineV({ shape.h1.x, shape.h1.y }, { shape.h2.x, shape.h2.y }, WHITE); DrawCircleV({ shape.h2.x, shape.h2.y }, 5.0f, PURPLE); } if (!symmetricalLens) { if (shape.helpers.size() >= 2) { DrawLineV({ shape.helpers.at(0).x, shape.helpers.at(0).y }, { shape.helpers.at(1).x, shape.helpers.at(1).y }, WHITE); } } else { if (shape.helpers.size() == 2) { DrawLineV({ shape.helpers.at(0).x, shape.helpers.at(0).y }, { shape.helpers.at(1).x, shape.helpers.at(1).y }, WHITE); } } glm::vec2 thirdHelperPos = shape.h2; glm::vec2 otherSide = shape.h2; if (shape.helpers.size() == 2) { glm::vec2 tangent = glm::normalize(shape.helpers.at(0) - shape.helpers.at(1)); glm::vec2 normal = glm::vec2(tangent.y, -tangent.x); glm::vec2 offset = shape.h2 - shape.helpers.at(1); float dist; dist = glm::dot(offset, normal); shape.tempDist = dist; thirdHelperPos = shape.helpers.at(1) + dist * normal; otherSide = shape.helpers.at(0) + dist * normal; if (shape.helpers.size() == 2) { DrawLineV({ shape.helpers.at(1).x, shape.helpers.at(1).y }, { thirdHelperPos.x, thirdHelperPos.y }, WHITE); DrawLineV({ shape.helpers.at(0).x, shape.helpers.at(0).y }, { otherSide.x, otherSide.y }, WHITE); } DrawCircleV({ thirdHelperPos.x, thirdHelperPos.y }, 5.0f, PURPLE); } if (shape.helpers.size() >= 3) { DrawLineV({ shape.helpers.at(1).x, shape.helpers.at(1).y }, { shape.helpers.at(2).x, shape.helpers.at(2).y }, WHITE); } for (int i = 0; i < shape.lensSegments; i++) { float t1 = static_cast(i) / shape.lensSegments; float t2 = static_cast((i + 1)) / shape.lensSegments; float angle1 = shape.startAngle + t1 * (shape.endAngle - shape.startAngle); float angle2 = shape.startAngle + t2 * (shape.endAngle - shape.startAngle); glm::vec2 arcP1 = shape.center + glm::vec2(cos(angle1), sin(angle1)) * shape.radius; glm::vec2 arcP2 = shape.center + glm::vec2(cos(angle2), sin(angle2)) * shape.radius; if (shape.helpers.size() == 3 && !shape.fourthHelper) { DrawLineV({ arcP1.x, arcP1.y }, { arcP2.x, arcP2.y }, WHITE); } } if (symmetricalLens) { for (int i = 0; i < shape.lensSegments; i++) { float t1Symmetry = static_cast(i) / shape.lensSegments; float t2Symmetry = static_cast((i + 1)) / shape.lensSegments; float angle1Symmetry = shape.startAngleSymmetry + t1Symmetry * (shape.endAngleSymmetry - shape.startAngleSymmetry); float angle2Symmetry = shape.startAngleSymmetry + t2Symmetry * (shape.endAngleSymmetry - shape.startAngleSymmetry); glm::vec2 arcP1Symmetry = shape.centerSymmetry + glm::vec2(cos(angle1Symmetry), sin(angle1Symmetry)) * shape.radiusSymmetry; glm::vec2 arcP2Symmetry = shape.centerSymmetry + glm::vec2(cos(angle2Symmetry), sin(angle2Symmetry)) * shape.radiusSymmetry; if (shape.helpers.size() == 3 && !shape.fourthHelper) { DrawLineV({ arcP1Symmetry.x, arcP1Symmetry.y }, { arcP2Symmetry.x, arcP2Symmetry.y }, WHITE); } } } if (shape.helpers.size() >= 3) { DrawLineV({ shape.helpers.at(0).x, shape.helpers.at(0).y }, { shape.arcEnd.x, shape.arcEnd.y }, WHITE); } if (!shape.helpers.empty()) { for (auto& helper : shape.helpers) { shape.drawHelper(helper); } } } } } // Draw wall helpers if (myVar.toolMoveOptics) { for (Wall& wall : walls) { glm::vec2 dA = wall.vA - mouseWorldPos; glm::vec2 dB = wall.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= helperMinDist && !wall.isShapeWall) { wall.drawHelper(wall.vA); } if (distB <= helperMinDist && !wall.isShapeWall) { wall.drawHelper(wall.vB); } } } // Draw shape helpers if (!shapes.empty()) { for (size_t i = 0; i < shapes.size(); i++) { if ((shapes[i].drawHoverHelpers || selectedHelper != -1 && selectedShape != -1) && !isAnyShapeBeingSpawned && myVar.toolMoveOptics) { for (glm::vec2& helper : shapes[i].helpers) { shapes[i].drawHelper(helper); } } } } // Draw light helpers if (myVar.toolMoveOptics) { for (AreaLight& areaLight : areaLights) { glm::vec2 dA = areaLight.vA - mouseWorldPos; glm::vec2 dB = areaLight.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= helperMinDist) { areaLight.drawHelper(areaLight.vA); } if (distB <= helperMinDist) { areaLight.drawHelper(areaLight.vB); } } for (ConeLight& coneLight : coneLights) { glm::vec2 dA = coneLight.vA - mouseWorldPos; glm::vec2 dB = coneLight.vB - mouseWorldPos; float distA = glm::length(dA); float distB = glm::length(dB); if (distA <= helperMinDist + 40.0f) { coneLight.drawHelper(coneLight.vA); coneLight.drawHelper(coneLight.vB); } if (distB <= helperMinDist + 40.0f) { coneLight.drawHelper(coneLight.vB); coneLight.drawHelper(coneLight.vA); } } for (PointLight& pointLight : pointLights) { glm::vec2 dA = pointLight.pos - mouseWorldPos; float dist = glm::length(dA); if (dist <= helperMinDist) { pointLight.drawHelper(pointLight.pos); } } } for (PointLight& pointLight : pointLights) { if (pointLight.isSelected) { pointLight.drawHelper(pointLight.pos); } } for (ConeLight& coneLight : coneLights) { if (coneLight.isSelected) { coneLight.drawHelper(coneLight.vA); coneLight.drawHelper(coneLight.vB); } } } ================================================ FILE: GalaxyEngine/src/Physics/morton.cpp ================================================ #include "Physics/morton.h" uint64_t Morton::scaleToGrid(float pos, float minVal, float maxVal) { if (maxVal <= minVal) return 0; float clamped = std::clamp(pos, minVal, maxVal); float normalized = (clamped - minVal) / (maxVal - minVal); uint64_t scaled = static_cast(normalized * 262144.0f); return std::min(scaled, uint64_t(262143)); } uint64_t Morton::spreadBits(uint64_t x) { x &= 0x3FFFF; // keep only 18 bits x = (x | (x << 16)) & 0x0000FFFF0000FFFFULL; x = (x | (x << 8)) & 0x00FF00FF00FF00FFULL; x = (x | (x << 4)) & 0x0F0F0F0F0F0F0F0FULL; x = (x | (x << 2)) & 0x3333333333333333ULL; x = (x | (x << 1)) & 0x5555555555555555ULL; return static_cast(x); } uint64_t Morton::morton2D(uint64_t x, uint64_t y) { return static_cast(spreadBits(x)) | (static_cast(spreadBits(y)) << 1); } void Morton::computeMortonKeys(std::vector& pParticles, glm::vec3& posSize) { const float maxX = posSize.x + std::max(posSize.z, 1e-6f); const float maxY = posSize.y + std::max(posSize.z, 1e-6f); for (auto& pParticle : pParticles) { uint64_t ix = scaleToGrid(pParticle.pos.x, posSize.x, maxX); uint64_t iy = scaleToGrid(pParticle.pos.y, posSize.y, maxY); pParticle.mortonKey = morton2D(ix, iy); } } void Morton::sortParticlesByMortonKey( std::vector& pParticles, std::vector& rParticles) { const size_t n = pParticles.size(); indicesBuffer.resize(n); pSortedBuffer.resize(n); rSortedBuffer.resize(n); std::iota(indicesBuffer.begin(), indicesBuffer.end(), 0); std::sort(indicesBuffer.begin(), indicesBuffer.end(), [&](size_t a, size_t b) { return pParticles[a].mortonKey < pParticles[b].mortonKey; }); for (size_t i = 0; i < n; i++) { pSortedBuffer[i] = pParticles[indicesBuffer[i]]; rSortedBuffer[i] = rParticles[indicesBuffer[i]]; } std::swap(pParticles, pSortedBuffer); std::swap(rParticles, rSortedBuffer); } // ---- 3D Implementation ---- // uint64_t Morton::scaleToGrid3D(float pos, float minVal, float maxVal) { if (maxVal <= minVal) return 0; float clamped = std::clamp(pos, minVal, maxVal); float normalized = (clamped - minVal) / (maxVal - minVal); uint64_t scaled = static_cast(normalized * 2097151.0f); return std::min(scaled, uint64_t(2097151)); } uint64_t Morton::spreadBits3D(uint64_t x) { uint64_t result = 0; for (int i = 0; i < 21; ++i) { uint64_t bit = (x >> i) & 1ULL; result |= (bit << (3 * i)); } return result; } uint64_t Morton::morton3D(uint64_t x, uint64_t y, uint64_t z) { return spreadBits3D(x) | (spreadBits3D(y) << 1) | (spreadBits3D(z) << 2); } void Morton::computeMortonKeys3D(std::vector& pParticles, const glm::vec4& boundingBox) { float minX = boundingBox.x; float minY = boundingBox.y; float minZ = boundingBox.z; float size = boundingBox.w; float maxX = minX + size; float maxY = minY + size; float maxZ = minZ + size; for (auto& pParticle : pParticles) { uint64_t ix = scaleToGrid3D(pParticle.pos.x, minX, maxX); uint64_t iy = scaleToGrid3D(pParticle.pos.y, minY, maxY); uint64_t iz = scaleToGrid3D(pParticle.pos.z, minZ, maxZ); pParticle.mortonKey = morton3D(ix, iy, iz); } } void Morton::sortParticlesByMortonKey3D( std::vector& pParticles, std::vector& rParticles) { const size_t n = pParticles.size(); indicesBuffer3D.resize(n); pSortedBuffer3D.resize(n); rSortedBuffer3D.resize(n); std::iota(indicesBuffer3D.begin(), indicesBuffer3D.end(), 0); std::sort(indicesBuffer3D.begin(), indicesBuffer3D.end(), [&](size_t a, size_t b) { return pParticles[a].mortonKey < pParticles[b].mortonKey; }); for (size_t i = 0; i < n; i++) { pSortedBuffer3D[i] = pParticles[indicesBuffer3D[i]]; rSortedBuffer3D[i] = rParticles[indicesBuffer3D[i]]; } std::swap(pParticles, pSortedBuffer3D); std::swap(rParticles, rSortedBuffer3D); } ================================================ FILE: GalaxyEngine/src/Physics/physics.cpp ================================================ #include "Physics/physics.h" // This is used in predict trajectory inside particleSpawning.cpp glm::vec2 Physics::calculateForceFromGridOld(std::vector& pParticles, UpdateVariables& myVar, ParticlePhysics& pParticle) { glm::vec2 totalForce = { 0.0f,0.0f }; uint32_t gridIdx = 0; const uint32_t nodeCount = static_cast(globalNodes.size()); const float thetaSq = myVar.theta * myVar.theta; const float softeningSq = myVar.softening * myVar.softening; const float Gf = static_cast(myVar.G); const float pmass = pParticle.mass; auto* particlesPtr = pParticles.data(); while (gridIdx < nodeCount) { Node& grid = globalNodes[gridIdx]; float gridMass = grid.gridMass; if (gridMass <= 0.0f) { gridIdx += grid.next + 1; continue; } const glm::vec2 gridCOM = grid.centerOfMass; const float gridSize = grid.size; glm::vec2 d = gridCOM - pParticle.pos; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { d.x -= myVar.domainSize.x * ((d.x > myVar.halfDomainWidth) - (d.x < -myVar.halfDomainWidth)); d.y -= myVar.domainSize.y * ((d.y > myVar.halfDomainHeight) - (d.y < -myVar.halfDomainHeight)); } float distanceSq = d.x * d.x + d.y * d.y + softeningSq; bool isSubgridsEmty = true; uint32_t s00 = grid.subGrids[0][0]; uint32_t s01 = grid.subGrids[0][1]; uint32_t s10 = grid.subGrids[1][0]; uint32_t s11 = grid.subGrids[1][1]; if (s00 != UINT32_MAX || s01 != UINT32_MAX || s10 != UINT32_MAX || s11 != UINT32_MAX) { isSubgridsEmty = false; } float gridSizeSq = gridSize * gridSize; if ((gridSizeSq < thetaSq * distanceSq) || isSubgridsEmty) { if ((grid.endIndex - grid.startIndex) == 1) { const ParticlePhysics& other = particlesPtr[grid.startIndex]; if (std::abs(other.pos.x - pParticle.pos.x) < 0.001f && std::abs(other.pos.y - pParticle.pos.y) < 0.001f) { gridIdx += grid.next + 1; continue; } } float invDistance = 1.0f / sqrtf(distanceSq); float invDist2 = invDistance * invDistance; float invDist3 = invDist2 * invDistance; float forceMagnitude = Gf * pmass * gridMass * invDist3; totalForce += d * forceMagnitude; if (myVar.isTempEnabled) { uint32_t count = grid.endIndex - grid.startIndex; if (count > 0) { float gridAverageTemp = grid.gridTemp / static_cast(count); float temperatureDifference = gridAverageTemp - pParticle.temp; float distance = 0.0f; if (distanceSq > 1e-16f) distance = 1.0f / invDistance; if (distance > 1e-8f) { float heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance; pParticle.temp += heatTransfer * myVar.timeFactor; } } } gridIdx += grid.next + 1; } else { ++gridIdx; } } return totalForce; } void Physics::calculateForceFromGrid(UpdateVariables& myVar) { #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < posX.size(); i++) { glm::vec2 totalForce = { 0.0f,0.0f }; uint32_t gridIdx = 0; const uint32_t nodeCount = static_cast(globalNodes.size()); const float thetaSq = myVar.theta * myVar.theta; const float softeningSq = myVar.softening * myVar.softening; const float Gf = myVar.G; const float pmass = mass[i]; while (gridIdx < nodeCount) { Node& grid = globalNodes[gridIdx]; float gridMass = grid.gridMass; if (gridMass <= 0.0f) { gridIdx += grid.next + 1; continue; } const glm::vec2 gridCOM = grid.centerOfMass; const float gridSize = grid.size; glm::vec2 d = gridCOM - glm::vec2{ posX[i], posY[i] }; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { d.x -= myVar.domainSize.x * ((d.x > myVar.halfDomainWidth) - (d.x < -myVar.halfDomainWidth)); d.y -= myVar.domainSize.y * ((d.y > myVar.halfDomainHeight) - (d.y < -myVar.halfDomainHeight)); } float distanceSq = d.x * d.x + d.y * d.y + softeningSq; bool isSubgridsEmpty = true; uint32_t s00 = grid.subGrids[0][0]; uint32_t s01 = grid.subGrids[0][1]; uint32_t s10 = grid.subGrids[1][0]; uint32_t s11 = grid.subGrids[1][1]; if (s00 != UINT32_MAX || s01 != UINT32_MAX || s10 != UINT32_MAX || s11 != UINT32_MAX) { isSubgridsEmpty = false; } float gridSizeSq = gridSize * gridSize; if (gridSizeSq < thetaSq * distanceSq) { float invDistance = 1.0f / sqrtf(distanceSq); float invDist2 = invDistance * invDistance; float invDist3 = invDist2 * invDistance; float forceMagnitude = Gf * pmass * gridMass * invDist3; totalForce += d * forceMagnitude; if (myVar.isTempEnabled) { uint32_t count = grid.endIndex - grid.startIndex; if (count > 0) { float gridAverageTemp = grid.gridTemp / static_cast(count); float temperatureDifference = gridAverageTemp - temp[i]; float distance = 0.0f; if (distanceSq > 1e-16f) distance = 1.0f / invDistance; if (distance > 1e-8f) { float heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance; temp[i] += heatTransfer * myVar.timeFactor; } } } gridIdx += grid.next + 1; } else if (isSubgridsEmpty) { for (uint32_t j = grid.startIndex; j < grid.endIndex; ++j) { if (i == j) continue; glm::vec2 p2pd = glm::vec2{ posX[j], posY[j] } - glm::vec2{ posX[i], posY[i] }; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { p2pd.x -= myVar.domainSize.x * ((p2pd.x > myVar.halfDomainWidth) - (p2pd.x < -myVar.halfDomainWidth)); p2pd.y -= myVar.domainSize.y * ((p2pd.y > myVar.halfDomainHeight) - (p2pd.y < -myVar.halfDomainHeight)); } float p2pdistSq = p2pd.x * p2pd.x + p2pd.y * p2pd.y + softeningSq; float invDist = 1.0f / sqrtf(p2pdistSq); float invDist2 = invDist * invDist; float invDist3 = invDist2 * invDist; float forceMag = Gf * pmass * mass[j] * invDist3; totalForce += p2pd * forceMag; if (myVar.isTempEnabled) { float tempDiff = temp[j] - temp[i]; float p2pdist = 0.0f; if (p2pdistSq > 1e-16f) p2pdist = 1.0f / invDist; if (p2pdist > 1e-8f) { float heatTransfer = myVar.globalHeatConductivity * tempDiff / p2pdist; temp[i] += heatTransfer * myVar.timeFactor; } } } gridIdx += grid.next + 1; } else { ++gridIdx; } } accX[i] = totalForce.x / mass[i]; accY[i] = totalForce.y / mass[i]; } } void Physics::calculateForceFromGridAVX2(UpdateVariables& myVar) { #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < posX.size(); i++) { glm::vec2 totalForce = { 0.0f,0.0f }; uint32_t gridIdx = 0; const uint32_t nodeCount = static_cast(globalNodes.size()); const float thetaSq = myVar.theta * myVar.theta; const float softeningSq = myVar.softening * myVar.softening; const float Gf = myVar.G; const float pmass = mass[i]; while (gridIdx < nodeCount) { Node& grid = globalNodes[gridIdx]; float gridMass = grid.gridMass; if (gridMass <= 0.0f) { gridIdx += grid.next + 1; continue; } const glm::vec2 gridCOM = grid.centerOfMass; const float gridSize = grid.size; glm::vec2 d = gridCOM - glm::vec2{ posX[i], posY[i] }; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { d.x -= myVar.domainSize.x * ((d.x > myVar.halfDomainWidth) - (d.x < -myVar.halfDomainWidth)); d.y -= myVar.domainSize.y * ((d.y > myVar.halfDomainHeight) - (d.y < -myVar.halfDomainHeight)); } float distanceSq = d.x * d.x + d.y * d.y + softeningSq; bool isSubgridsEmpty = true; uint32_t s00 = grid.subGrids[0][0]; uint32_t s01 = grid.subGrids[0][1]; uint32_t s10 = grid.subGrids[1][0]; uint32_t s11 = grid.subGrids[1][1]; if (s00 != UINT32_MAX || s01 != UINT32_MAX || s10 != UINT32_MAX || s11 != UINT32_MAX) { isSubgridsEmpty = false; } float gridSizeSq = gridSize * gridSize; if (gridSizeSq < thetaSq * distanceSq) { float invDistance = 1.0f / sqrtf(distanceSq); float invDist2 = invDistance * invDistance; float invDist3 = invDist2 * invDistance; float forceMagnitude = Gf * pmass * gridMass * invDist3; totalForce += d * forceMagnitude; if (myVar.isTempEnabled) { uint32_t count = grid.endIndex - grid.startIndex; if (count > 0) { float gridAverageTemp = grid.gridTemp / static_cast(count); float temperatureDifference = gridAverageTemp - temp[i]; float distance = 0.0f; if (distanceSq > 1e-16f) distance = 1.0f / invDistance; if (distance > 1e-8f) { float heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance; temp[i] += heatTransfer * myVar.timeFactor; } } } gridIdx += grid.next + 1; } else if (isSubgridsEmpty) { __m256 vxi = _mm256_set1_ps(posX[i]); __m256 vyi = _mm256_set1_ps(posY[i]); __m256 vsofteningSq = _mm256_set1_ps(softeningSq); __m256 vGfpmass = _mm256_set1_ps(Gf * pmass); __m256 vforceX = _mm256_setzero_ps(); __m256 vforceY = _mm256_setzero_ps(); uint32_t j = grid.startIndex; for (; j + 7 < grid.endIndex; j += 8) { __m256 vxj = _mm256_loadu_ps(&posX[j]); __m256 vyj = _mm256_loadu_ps(&posY[j]); __m256 vmj = _mm256_loadu_ps(&mass[j]); __m256 vdx = _mm256_sub_ps(vxj, vxi); __m256 vdy = _mm256_sub_ps(vyj, vyi); __m256 vdx2 = _mm256_mul_ps(vdx, vdx); __m256 vdy2 = _mm256_mul_ps(vdy, vdy); __m256 vdistSq = _mm256_add_ps(_mm256_add_ps(vdx2, vdy2), vsofteningSq); __m256 vinvDist = _mm256_rsqrt_ps(vdistSq); __m256 vinvDist2 = _mm256_mul_ps(vinvDist, vinvDist); __m256 vinvDist3 = _mm256_mul_ps(vinvDist2, vinvDist); __m256 vforceMag = _mm256_mul_ps(_mm256_mul_ps(vGfpmass, vmj), vinvDist3); __m256 vmaskNotSelf = _mm256_cmp_ps(vdistSq, vsofteningSq, _CMP_NEQ_OQ); vforceMag = _mm256_and_ps(vforceMag, vmaskNotSelf); vforceX = _mm256_add_ps(vforceX, _mm256_mul_ps(vdx, vforceMag)); vforceY = _mm256_add_ps(vforceY, _mm256_mul_ps(vdy, vforceMag)); } float tempFx[8]; float tempFy[8]; _mm256_storeu_ps(tempFx, vforceX); _mm256_storeu_ps(tempFy, vforceY); for (int k = 0; k < 8; ++k) { totalForce.x += tempFx[k]; totalForce.y += tempFy[k]; } for (; j < grid.endIndex; ++j) { if (i == j) continue; glm::vec2 p2pd = glm::vec2{ posX[j], posY[j] } - glm::vec2{ posX[i], posY[i] }; float p2pdistSq = p2pd.x * p2pd.x + p2pd.y * p2pd.y + softeningSq; float invDist = 1.0f / sqrtf(p2pdistSq); float invDist3 = invDist * invDist * invDist; float forceMag = Gf * pmass * mass[j] * invDist3; totalForce += p2pd * forceMag; } gridIdx += grid.next + 1; } else { ++gridIdx; } } accX[i] = totalForce.x / mass[i]; accY[i] = totalForce.y / mass[i]; } } void Physics::flattenParticles(std::vector& pParticles) { size_t particleCount = pParticles.size(); posX.resize(particleCount); posY.resize(particleCount); accX.resize(particleCount); accY.resize(particleCount); velX.resize(particleCount); velY.resize(particleCount); prevVelX.resize(particleCount); prevVelY.resize(particleCount); mass.resize(particleCount); temp.resize(particleCount); #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(particleCount); i++) { const auto& particle = pParticles[i]; posX[i] = particle.pos.x; posY[i] = particle.pos.y; accX[i] = particle.acc.x; accY[i] = particle.acc.y; velX[i] = particle.vel.x; velY[i] = particle.vel.y; prevVelX[i] = particle.prevVel.x; prevVelY[i] = particle.prevVel.y; mass[i] = particle.mass; temp[i] = particle.temp; } } void Physics::naiveGravity(std::vector& pParticles, UpdateVariables& myVar) { int n = static_cast(posX.size()); const float* posXPtr = posX.data(); const float* posYPtr = posY.data(); const float* massPtr = mass.data(); float* accXPtr = accX.data(); float* accYPtr = accY.data(); #pragma omp parallel for schedule(static) for (int i = 0; i < n; i++) { accXPtr[i] = 0.0f; accYPtr[i] = 0.0f; } #pragma omp parallel for schedule(dynamic, 64) for (int i = 0; i < n; i++) { float pix = posXPtr[i]; float piy = posYPtr[i]; float totalAccX = 0.0f; float totalAccY = 0.0f; for (int j = 0; j < n; j++) { float dx = posXPtr[j] - pix; float dy = posYPtr[j] - piy; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { dx -= myVar.domainSize.x * ((dx > myVar.halfDomainWidth) - (dx < -myVar.halfDomainWidth)); dy -= myVar.domainSize.y * ((dy > myVar.halfDomainHeight) - (dy < -myVar.halfDomainHeight)); } float distSq = dx * dx + dy * dy + myVar.softening; float invDist = 1.0f / std::sqrt(distSq); float invDist3 = invDist * invDist * invDist; float factor = myVar.G * massPtr[j] * invDist3; totalAccX += dx * factor; totalAccY += dy * factor; } accXPtr[i] = totalAccX; accYPtr[i] = totalAccY; } } void Physics::naiveGravityAVX2(std::vector& pParticles, UpdateVariables& myVar) { int n = static_cast(posX.size()); __m256 gVec = _mm256_set1_ps(myVar.G); __m256 softVec = _mm256_set1_ps(myVar.softening); const float* posXPtr = posX.data(); const float* posYPtr = posY.data(); const float* massPtr = mass.data(); float* accXPtr = accX.data(); float* accYPtr = accY.data(); __m256 domainSizeX = _mm256_set1_ps(myVar.domainSize.x); __m256 domainSizeY = _mm256_set1_ps(myVar.domainSize.y); __m256 halfDomainWidth = _mm256_set1_ps(myVar.halfDomainWidth); __m256 halfDomainHeight = _mm256_set1_ps(myVar.halfDomainHeight); __m256 negHalfDomainWidth = _mm256_set1_ps(-myVar.halfDomainWidth); __m256 negHalfDomainHeight = _mm256_set1_ps(-myVar.halfDomainHeight); #pragma omp parallel for schedule(static) for (int i = 0; i < n; i++) { accX[i] = 0.0f; accY[i] = 0.0f; } #pragma omp parallel for schedule(dynamic, 64) for (int i = 0; i < n; i++) { float pix = posXPtr[i]; float piy = posYPtr[i]; __m256 pxi = _mm256_set1_ps(pix); __m256 pyi = _mm256_set1_ps(piy); __m256 totalAccX = _mm256_setzero_ps(); __m256 totalAccY = _mm256_setzero_ps(); int j; for (j = 0; j <= n - 8; j += 8) { __m256 pxj = _mm256_loadu_ps(&posXPtr[j]); __m256 pyj = _mm256_loadu_ps(&posYPtr[j]); __m256 mj = _mm256_loadu_ps(&massPtr[j]); __m256 dx = _mm256_sub_ps(pxj, pxi); __m256 dy = _mm256_sub_ps(pyj, pyi); if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { __m256 mask_pos_x = _mm256_cmp_ps(dx, halfDomainWidth, _CMP_GT_OQ); __m256 mask_neg_x = _mm256_cmp_ps(dx, negHalfDomainWidth, _CMP_LT_OQ); __m256 correction_x = _mm256_sub_ps( _mm256_and_ps(mask_pos_x, domainSizeX), _mm256_and_ps(mask_neg_x, domainSizeX) ); dx = _mm256_sub_ps(dx, correction_x); __m256 mask_pos_y = _mm256_cmp_ps(dy, halfDomainHeight, _CMP_GT_OQ); __m256 mask_neg_y = _mm256_cmp_ps(dy, negHalfDomainHeight, _CMP_LT_OQ); __m256 correction_y = _mm256_sub_ps( _mm256_and_ps(mask_pos_y, domainSizeY), _mm256_and_ps(mask_neg_y, domainSizeY) ); dy = _mm256_sub_ps(dy, correction_y); } __m256 distSq = _mm256_add_ps( _mm256_add_ps(_mm256_mul_ps(dx, dx), _mm256_mul_ps(dy, dy)), softVec); __m256 invDist = _mm256_rsqrt_ps(distSq); __m256 invDist3 = _mm256_mul_ps(invDist, _mm256_mul_ps(invDist, invDist)); __m256 factor = _mm256_mul_ps(gVec, _mm256_mul_ps(mj, invDist3)); totalAccX = _mm256_add_ps(totalAccX, _mm256_mul_ps(dx, factor)); totalAccY = _mm256_add_ps(totalAccY, _mm256_mul_ps(dy, factor)); } float accX_array[8], accY_array[8]; _mm256_storeu_ps(accX_array, totalAccX); _mm256_storeu_ps(accY_array, totalAccY); float finalAccX = accX_array[0] + accX_array[1] + accX_array[2] + accX_array[3] + accX_array[4] + accX_array[5] + accX_array[6] + accX_array[7]; float finalAccY = accY_array[0] + accY_array[1] + accY_array[2] + accY_array[3] + accY_array[4] + accY_array[5] + accY_array[6] + accY_array[7]; for (; j < n; j++) { float dx = posXPtr[j] - pix; float dy = posYPtr[j] - piy; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { dx -= myVar.domainSize.x * ((dx > myVar.halfDomainWidth) - (dx < -myVar.halfDomainWidth)); dy -= myVar.domainSize.y * ((dy > myVar.halfDomainHeight) - (dy < -myVar.halfDomainHeight)); } float distSq = dx * dx + dy * dy + myVar.softening; float invDist = _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(distSq))); float invDist3 = invDist * invDist * invDist; float factor = myVar.G * massPtr[j] * invDist3; finalAccX += dx * factor; finalAccY += dy * factor; } accXPtr[i] = finalAccX; accYPtr[i] = finalAccY; } } void Physics::readFlattenBack(std::vector& pParticles) { size_t particleCount = pParticles.size(); #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(particleCount); i++) { pParticles[i].pos.x = posX[i]; pParticles[i].pos.y = posY[i]; pParticles[i].vel.x = velX[i]; pParticles[i].vel.y = velY[i]; pParticles[i].acc.x = accX[i]; pParticles[i].acc.y = accY[i]; pParticles[i].prevVel.x = prevVelX[i]; pParticles[i].prevVel.y = prevVelY[i]; pParticles[i].mass = mass[i]; pParticles[i].temp = temp[i]; } } void Physics::temperatureCalculation(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { for (size_t i = 0; i < pParticles.size(); i++) { ParticlePhysics& p = pParticles[i]; auto it = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel); if (it != SPHMaterials::idToMaterial.end()) { SPHMaterial* pMat = it->second; float pTotalVel = sqrtf(p.vel.x * p.vel.x + p.vel.y * p.vel.y); float pTotalPrevVel = sqrtf(p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y); p.ke = 0.5f * p.sphMass * pTotalVel * pTotalVel; p.prevKe = 0.5f * p.sphMass * pTotalPrevVel * pTotalPrevVel; float q = std::abs(p.ke - p.prevKe); float dTemp = q / (2.0f * pMat->heatConductivity * p.sphMass + 1.0f); p.temp += dTemp; float tempDifference = p.temp - myVar.ambientTemp; float dTempCooling = -(pMat->heatConductivity * myVar.globalAmbientHeatRate) * tempDifference * myVar.timeFactor; p.temp += dTempCooling; if (p.temp >= pMat->hotPoint) { p.sphMass = pMat->hotMassMult; p.mass = UpdateVariables::particleBaseMass * p.sphMass; p.restDens = pMat->hotRestDens; p.stiff = pMat->hotStiff; p.visc = pMat->hotVisc; p.cohesion = pMat->hotCohesion; if (pMat->coldPoint == 0.0f) { p.isHotPoint = true; } } else if (p.temp <= pMat->coldPoint) { p.sphMass = pMat->coldMassMult; p.mass = UpdateVariables::particleBaseMass * p.sphMass; p.restDens = pMat->coldRestDens; p.stiff = pMat->coldStiff; p.visc = pMat->coldVisc; p.cohesion = pMat->coldCohesion; } else { p.sphMass = pMat->massMult; p.mass = UpdateVariables::particleBaseMass * p.sphMass; p.restDens = pMat->restDens; p.stiff = pMat->stiff; p.visc = pMat->visc; p.cohesion = pMat->cohesion; if (pMat->coldPoint != 0.0f) { p.isHotPoint = true; } } if (pMat->coldPoint == 0.0f) { if (p.temp <= pMat->hotPoint && p.isHotPoint) { p.hasSolidified = true; p.isHotPoint = false; } } else { if (p.temp <= pMat->coldPoint && p.isHotPoint) { p.hasSolidified = true; p.isHotPoint = false; } } } else { float pTotalVel = sqrtf(p.vel.x * p.vel.x + p.vel.y * p.vel.y); float pTotalPrevVel = sqrtf(p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y); p.ke = 0.5f * p.sphMass * pTotalVel * pTotalVel; p.prevKe = 0.5f * p.sphMass * pTotalPrevVel * pTotalPrevVel; float q = std::abs(p.ke - p.prevKe); float dTemp = q / (2.0f * 0.05f * p.sphMass + 1.0f); p.temp += dTemp; float tempDifference = p.temp - myVar.ambientTemp; float dTempCooling = -(0.05f * myVar.globalAmbientHeatRate) * tempDifference * myVar.timeFactor; p.temp += dTempCooling; } } } void Physics::createConstraints(std::vector& pParticles, std::vector& rParticles, bool& constraintCreateSpecialFlag, UpdateVariables& myVar, UpdateParameters& myParam) { bool shouldCreateConstraints = IO::shortcutPress(KEY_P) || myVar.constraintAllSolids || constraintCreateSpecialFlag || myVar.constraintSelected; for (size_t i = 0; i < pParticles.size(); i++) { ParticlePhysics& pi = pParticles[i]; if (constraintCreateSpecialFlag) { if (!rParticles[i].isBeingDrawn) { continue; } } if (myVar.constraintSelected) { if (!rParticles[i].isSelected) { continue; } } SPHMaterial* pMatI = nullptr; auto matItI = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel); if (matItI != SPHMaterials::idToMaterial.end()) { pMatI = matItI->second; } if (shouldCreateConstraints) { if (pMatI) { if (pMatI->coldPoint == 0.0f) { if (pi.temp >= pMatI->hotPoint) continue; } else { if (pi.temp >= pMatI->coldPoint) continue; } } } else { if (!pi.hasSolidified) continue; constraintMap.clear(); pi.hasSolidified = false; } std::vector neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.pos); for (size_t j : neighborIndices) { size_t neighborIndex = j; if (neighborIndex == i) continue; ParticlePhysics& pj = myParam.pParticles[neighborIndex]; float distSq = glm::dot(pj.pos - pi.pos, pj.pos - pi.pos); if (distSq > 12.0f) { continue; } if (constraintCreateSpecialFlag) { if (!rParticles[neighborIndex].isBeingDrawn) { continue; } } if (myVar.constraintSelected) { if (!rParticles[neighborIndex].isSelected) { continue; } } SPHMaterial* pMatJ = nullptr; auto matItJ = SPHMaterials::idToMaterial.find(rParticles[neighborIndex].sphLabel); if (matItJ != SPHMaterials::idToMaterial.end()) { pMatJ = matItJ->second; } if (pMatI && pMatJ && pMatI->coldPoint == 0.0f && pMatJ->coldPoint != 0.0f) { continue; } uint64_t key = makeKey(pi.id, pj.id); if (constraintMap.find(key) != constraintMap.end()) { continue; } float resistance = 0.6f; if (pMatI && pMatJ) { resistance = (pMatI->constraintResistance + pMatJ->constraintResistance) * 0.5f; } float plasticityPoint = 0.6f; if (pMatI && pMatJ) { plasticityPoint = (pMatI->constraintPlasticPoint + pMatJ->constraintPlasticPoint) * 0.5f; } if (pi.id < pj.id) { float currentDist = glm::distance(pi.pos, pj.pos); bool broken = false; if (pMatI && pMatJ) { particleConstraints.push_back({ pi.id, pj.id, currentDist, currentDist, pMatI->constraintStiffness, resistance, 0.0f, plasticityPoint, broken }); } else { float defaultStiffness = 60.0f; particleConstraints.push_back({ pi.id, pj.id, currentDist, currentDist, defaultStiffness, resistance, 0.0f, plasticityPoint, broken }); } constraintMap[key] = &particleConstraints.back(); } } } constraintCreateSpecialFlag = false; myVar.constraintAllSolids = false; myVar.constraintSelected = false; } void Physics::constraints(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { if (myVar.deleteAllConstraints) { particleConstraints.clear(); constraintMap.clear(); myVar.deleteAllConstraints = false; return; } uint32_t maxId = 0; for (const auto& p : pParticles) if (p.id > maxId) maxId = p.id; idToIndexTable.assign(maxId + 1, -1); for (size_t i = 0; i < pParticles.size(); i++) { idToIndexTable[pParticles[i].id] = i; } auto getIdx = [&](uint32_t id) -> int64_t { if (id >= idToIndexTable.size()) return -1; return idToIndexTable[id]; }; if (myVar.deleteSelectedConstraints) { for (auto& constraint : particleConstraints) { int64_t idx1 = getIdx(constraint.id1); int64_t idx2 = getIdx(constraint.id2); if (idx1 == -1 || idx2 == -1) { constraint.isBroken = true; } else if (rParticles[idx1].isSelected || rParticles[idx2].isSelected) { constraint.isBroken = true; } } myVar.deleteSelectedConstraints = false; } if (!particleConstraints.empty()) { auto new_end = std::remove_if(particleConstraints.begin(), particleConstraints.end(), [&](const auto& constraint) { int64_t idx1 = getIdx(constraint.id1); int64_t idx2 = getIdx(constraint.id2); return idx1 == -1 || idx2 == -1 || constraint.isBroken; }); for (auto it = new_end; it != particleConstraints.end(); ++it) { constraintMap.erase(makeKey(it->id1, it->id2)); } particleConstraints.erase(new_end, particleConstraints.end()); bool enforceTriangles = true; myVar.frameCount++; if (enforceTriangles && !particleConstraints.empty() && myVar.frameCount % 5 == 0) { std::vector> adjacency(maxId + 1); for (const auto& c : particleConstraints) { if (c.isBroken) continue; if (c.id1 <= maxId && c.id2 <= maxId) { adjacency[c.id1].push_back(c.id2); adjacency[c.id2].push_back(c.id1); } } for (auto& constraint : particleConstraints) { if (constraint.isBroken) continue; uint32_t idA = constraint.id1; uint32_t idB = constraint.id2; bool partOfTriangle = false; const std::vector& neighborsOfA = adjacency[idA]; const std::vector& neighborsOfB = adjacency[idB]; for (uint32_t neighborA : neighborsOfA) { for (uint32_t neighborB : neighborsOfB) { if (neighborA == neighborB) { partOfTriangle = true; break; } } if (partOfTriangle) break; } if (!partOfTriangle) { constraint.isBroken = true; } } } const int substeps = 15; for (int step = 0; step < substeps; step++) { #pragma omp parallel for schedule(dynamic) for (int64_t i = 0; i < (int64_t)particleConstraints.size(); i++) { auto& constraint = particleConstraints[i]; if (constraint.isBroken) continue; int64_t idx1 = getIdx(constraint.id1); int64_t idx2 = getIdx(constraint.id2); if (idx1 == -1 || idx2 == -1) { constraint.isBroken = true; continue; } ParticlePhysics& pi = pParticles[idx1]; ParticlePhysics& pj = pParticles[idx2]; SPHMaterial* pMatI = SPHMaterials::idToMaterial[rParticles[idx1].sphLabel]; SPHMaterial* pMatJ = SPHMaterials::idToMaterial[rParticles[idx2].sphLabel]; glm::vec2 delta = pj.pos - pi.pos; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { delta.x = fmod(delta.x + myVar.domainSize.x * 1.5f, myVar.domainSize.x) - myVar.domainSize.x * 0.5f; delta.y = fmod(delta.y + myVar.domainSize.y * 1.5f, myVar.domainSize.y) - myVar.domainSize.y * 0.5f; } float currentLength = glm::length(delta); if (currentLength < 0.0001f) continue; glm::vec2 dir = delta / currentLength; constraint.displacement = currentLength - constraint.restLength; if (!myVar.unbreakableConstraints) { if (pMatI && pMatJ) { if (!pMatI->isPlastic || !pMatJ->isPlastic) { constraint.isPlastic = false; float limit = (constraint.resistance * myVar.globalConstraintResistance) * constraint.restLength; if (std::abs(constraint.displacement) >= limit) { constraint.isBroken = true; } } else { constraint.isPlastic = true; if (std::abs(constraint.displacement) >= constraint.plasticityPoint * constraint.originalLength) { constraint.restLength += constraint.displacement; } float breakLimit = (constraint.originalLength + constraint.originalLength * (constraint.resistance * myVar.globalConstraintResistance)) * (pMatI->constraintPlasticPointMult + pMatJ->constraintPlasticPointMult) * 0.5f; if (std::abs(constraint.restLength) >= breakLimit) { constraint.isBroken = true; } } if (pi.isHotPoint || pj.isHotPoint) constraint.isBroken = true; } } if (myVar.timeFactor > 0.0f && myVar.gridExists && !constraint.isBroken) { glm::vec2 springForce = constraint.stiffness * constraint.displacement * dir * pi.mass * myVar.globalConstraintStiffnessMult; glm::vec2 relVel = pj.vel - pi.vel; glm::vec2 dampForce = -globalConstraintDamping * glm::dot(relVel, dir) * dir * pi.mass; glm::vec2 totalForce = springForce + dampForce; #pragma omp atomic pi.acc.x += totalForce.x / pi.mass; #pragma omp atomic pi.acc.y += totalForce.y / pi.mass; #pragma omp atomic pj.acc.x -= totalForce.x / pj.mass; #pragma omp atomic pj.acc.y -= totalForce.y / pj.mass; float correctionFactor = constraint.stiffness * stiffCorrectionRatio * myVar.globalConstraintStiffnessMult; glm::vec2 correction = dir * constraint.displacement * correctionFactor; float massSum = pi.mass + pj.mass; glm::vec2 correctionI = correction * (pj.mass / massSum); glm::vec2 correctionJ = correction * (pi.mass / massSum); #pragma omp atomic pi.pos.x += correctionI.x; #pragma omp atomic pi.pos.y += correctionI.y; #pragma omp atomic pj.pos.x -= correctionJ.x; #pragma omp atomic pj.pos.y -= correctionJ.y; } } } } } void Physics::pausedConstraints(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { for (size_t i = 0; i < particleConstraints.size(); i++) { auto& constraint = particleConstraints[i]; float prevLength = constraint.restLength; } } void Physics::mergerSolver( std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar, UpdateParameters& myParam) { std::unordered_set particleIdsToDelete; std::vector indicesToDelete; int originalSize = static_cast(pParticles.size()); for (int i = originalSize - 1; i >= 0; i--) { ParticlePhysics& p = pParticles[i]; ParticleRendering& r = rParticles[i]; if (r.isDarkMatter || particleIdsToDelete.count(p.id)) continue; for (int j = r.neighbors - 1; j >= 0; j--) { uint32_t neighborId = myParam.neighborSearch.globalNeighborList[p.neighborOffset + j]; size_t neighborIndex = myParam.neighborSearch.idToIndexTable[neighborId]; if (neighborIndex >= pParticles.size()) continue; if (neighborIndex == i) continue; ParticlePhysics& pn = pParticles[neighborIndex]; ParticleRendering& rn = rParticles[neighborIndex]; if (rn.isDarkMatter || particleIdsToDelete.count(pn.id)) continue; glm::vec2 d = pn.pos - p.pos; float distanceSq = glm::dot(d, d); float combinedRadiusSq = (r.totalRadius + rn.totalRadius) * (r.totalRadius + rn.totalRadius); if (distanceSq <= combinedRadiusSq) { float originalMassP = p.mass; float originalMassN = pn.mass; if (originalMassP >= originalMassN) { p.mass = originalMassP + originalMassN; p.vel = (p.vel * originalMassP + pn.vel * originalMassN) / p.mass; float area1 = r.previousSize * r.previousSize; float area2 = rn.previousSize * rn.previousSize; float fullGrowthSize = sqrtf(area1 + area2); float maxOriginalSize = std::max(r.previousSize, rn.previousSize); float growthFactor = 0.25f; r.previousSize = maxOriginalSize + (fullGrowthSize - maxOriginalSize) * growthFactor; particleIdsToDelete.insert(pn.id); indicesToDelete.push_back(neighborIndex); } else { pn.mass = originalMassP + originalMassN; pn.vel = (pn.vel * originalMassN + p.vel * originalMassP) / pn.mass; float area1 = r.previousSize * r.previousSize; float area2 = rn.previousSize * rn.previousSize; float fullGrowthSize = sqrtf(area1 + area2); float maxOriginalSize = std::max(r.previousSize, rn.previousSize); float growthFactor = 0.25f; rn.previousSize = maxOriginalSize + (fullGrowthSize - maxOriginalSize) * growthFactor; particleIdsToDelete.insert(p.id); indicesToDelete.push_back(i); } break; } } } std::sort(indicesToDelete.begin(), indicesToDelete.end()); indicesToDelete.erase(std::unique(indicesToDelete.begin(), indicesToDelete.end()), indicesToDelete.end()); for (int k = static_cast(indicesToDelete.size()) - 1; k >= 0; k--) { size_t index = indicesToDelete[k]; if (index >= pParticles.size()) continue; uint32_t removedId = pParticles[index].id; std::swap(pParticles[index], pParticles.back()); std::swap(rParticles[index], rParticles.back()); uint32_t swappedId = pParticles[index].id; myParam.neighborSearch.idToIndexTable[swappedId] = index; pParticles.pop_back(); rParticles.pop_back(); if (removedId < myParam.neighborSearch.idToIndexTable.size()) { myParam.neighborSearch.idToIndexTable[removedId] = static_cast(-1); } } } void Physics::integrateStart(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { float dt = myVar.timeFactor; float halfDt = dt * 0.5f; float sphMaxVelSq = myVar.sphMaxVel * myVar.sphMaxVel; #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < pParticles.size(); i++) { ParticlePhysics& p = pParticles[i]; if (rParticles[i].isPinned) { continue; } p.prevVel = p.vel; p.vel += p.acc * halfDt; if (myVar.isSPHEnabled) { float vSq = p.vel.x * p.vel.x + p.vel.y * p.vel.y; if (vSq > sphMaxVelSq) { float prevVSq = p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y; if (prevVSq > 0.00001f) { float invPrevLen = myVar.sphMaxVel / sqrtf(prevVSq); p.prevVel *= invPrevLen; } float invLen = myVar.sphMaxVel / sqrtf(vSq); p.vel *= invLen; } } p.pos += p.vel * dt; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { if (p.pos.x < 0.0f) p.pos.x += myVar.domainSize.x; else if (p.pos.x >= myVar.domainSize.x) p.pos.x -= myVar.domainSize.x; if (p.pos.y < 0.0f) p.pos.y += myVar.domainSize.y; else if (p.pos.y >= myVar.domainSize.y) p.pos.y -= myVar.domainSize.y; } } } void Physics::integrateEnd(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < pParticles.size(); i++) { if (rParticles[i].isPinned) { continue; } pParticles[i].vel += pParticles[i].acc * (myVar.timeFactor * 0.5f); } } void Physics::pruneParticles(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { for (size_t i = 0; i < pParticles.size(); ) { float x = pParticles[i].pos.x; float y = pParticles[i].pos.y; if (x <= 0.0f || x >= myVar.domainSize.x || y <= 0.0f || y >= myVar.domainSize.y) { if (pParticles.size() > 1) { std::swap(pParticles[i], pParticles.back()); std::swap(rParticles[i], rParticles.back()); } pParticles.pop_back(); rParticles.pop_back(); } else { i++; } } } void Physics::spawnCorrection(UpdateParameters& myParam, bool& hasAVX2, const int& iterations) { #pragma omp parallel for for (size_t i = 0; i < myParam.pParticles.size(); i++) { ParticlePhysics& pi = myParam.pParticles[i]; std::vector neighborIndices = QueryNeighbors::queryNeighbors(myParam, hasAVX2, 64, pi.pos); for (size_t j : neighborIndices) { size_t neighborIndex = j; if (neighborIndex == i) continue; ParticlePhysics& pj = myParam.pParticles[neighborIndex]; if (!myParam.rParticles[neighborIndex].isBeingDrawn || !myParam.rParticles[i].isBeingDrawn) continue; glm::vec2 d = pj.pos - pi.pos; float dSq = glm::dot(d, d); const float minDist = 2.4f; const float minDistSq = minDist * minDist; if (dSq > 0.000001f && dSq < minDistSq) { float dist = std::sqrt(dSq); glm::vec2 dir = -d / dist; float penetration = minDist - dist; float totalMass = pi.mass + pj.mass; if (totalMass > 0.0f) { float piMove = pj.mass / totalMass; float pjMove = pi.mass / totalMass; glm::vec2 correction = dir * penetration; pi.pos += correction * piMove; pj.pos -= correction * pjMove; } } } myParam.rParticles[i].spawnCorrectIter++; } } // ----- Unused. Test code ----- // void Physics::gravityGrid(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar, glm::vec3& bb) { if (pParticles.empty()) return; #pragma omp parallel for for (long long i = 0; i < static_cast(cells.size()); i++) { cells[i].mass = 0.0f; cells[i].particles.clear(); cells[i].force = { 0.0f, 0.0f }; } struct DepthInfo { int gridRes; float cellSize; size_t startIndex; }; std::vector depthInfos; size_t currentStartIdx = 0; for (int depth = 0; depth < maxDepth; depth++) { int res = std::pow(2, depth + 1); float size = bb.z / static_cast(res); depthInfos.push_back({ res, size, currentStartIdx }); currentStartIdx += (res * res); } for (ParticlePhysics& p : pParticles) { for (int depth = 0; depth < maxDepth; depth++) { const auto& info = depthInfos[depth]; float relX = p.pos.x - bb.x; float relY = p.pos.y - bb.y; int x = static_cast(relX / info.cellSize); int y = static_cast(relY / info.cellSize); if (x >= 0 && x < info.gridRes && y >= 0 && y < info.gridRes) { size_t cellIdx = info.startIndex + (y * info.gridRes + x); if (cellIdx < cells.size()) { cells[cellIdx].mass += p.mass; cells[cellIdx].particles.push_back(&p); } } } } for (const auto& info : depthInfos) { long long start = static_cast(info.startIndex); long long end = static_cast(info.startIndex + (info.gridRes * info.gridRes)); #pragma omp parallel for schedule(dynamic) for (long long i = start; i < end; i++) { GravityCell& ci = cells[i]; if (ci.mass == 0.0f) continue; size_t localIdx = i - info.startIndex; int cix = localIdx % info.gridRes; int ciy = localIdx / info.gridRes; for (int dy = -11; dy <= 11; dy++) { for (int dx = -11; dx <= 11; dx++) { if (dx == 0 && dy == 0) continue; int cjx = cix + dx; int cjy = ciy + dy; if (cjx < 0 || cjx >= info.gridRes || cjy < 0 || cjy >= info.gridRes) continue; size_t neighborLocalIdx = cjy * info.gridRes + cjx; size_t finalNeighborIdx = info.startIndex + neighborLocalIdx; const GravityCell& cj = cells[finalNeighborIdx]; if (cj.mass == 0.0f) continue; glm::vec2 d = cj.pos - ci.pos; float distSq = glm::dot(d, d); if (distSq < 1e-4f) continue; float dist = std::sqrt(distSq); float force = (myVar.G * ci.mass * cj.mass) / distSq; ci.force += force * (d / dist); } } } } #pragma omp parallel for for (long long i = 0; i < static_cast(pParticles.size()); i++) { ParticlePhysics& p = pParticles[i]; for (int depth = 0; depth < maxDepth; depth++) { const auto& info = depthInfos[depth]; float relX = p.pos.x - bb.x; float relY = p.pos.y - bb.y; int x = static_cast(relX / info.cellSize); int y = static_cast(relY / info.cellSize); if (x >= 0 && x < info.gridRes && y >= 0 && y < info.gridRes) { size_t cellIdx = info.startIndex + (y * info.gridRes + x); if (cellIdx < cells.size() && cells[cellIdx].mass > 0.0f) { const GravityCell& c = cells[cellIdx]; p.acc += c.force / c.mass; } } } } } // ----- Unused. Test code ----- // ================================================ FILE: GalaxyEngine/src/Physics/physics3D.cpp ================================================ #include "Physics/physics3D.h" void Physics3D::flattenParticles3D(std::vector& pParticles3D) { size_t particleCount = pParticles3D.size(); posX.resize(particleCount); posY.resize(particleCount); posZ.resize(particleCount); accX.resize(particleCount); accY.resize(particleCount); accZ.resize(particleCount); velX.resize(particleCount); velY.resize(particleCount); velZ.resize(particleCount); prevVelX.resize(particleCount); prevVelY.resize(particleCount); prevVelZ.resize(particleCount); mass.resize(particleCount); temp.resize(particleCount); #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(particleCount); i++) { const auto& particle = pParticles3D[i]; posX[i] = particle.pos.x; posY[i] = particle.pos.y; posZ[i] = particle.pos.z; accX[i] = particle.acc.x; accY[i] = particle.acc.y; accZ[i] = particle.acc.z; velX[i] = particle.vel.x; velY[i] = particle.vel.y; velZ[i] = particle.vel.z; prevVelX[i] = particle.prevVel.x; prevVelY[i] = particle.prevVel.y; prevVelZ[i] = particle.prevVel.z; mass[i] = particle.mass; temp[i] = particle.temp; } } // This is used in predict trajectory inside particleSpawning.cpp glm::vec3 Physics3D::calculateForceFromGrid3DOld(std::vector& pParticles, UpdateVariables& myVar, ParticlePhysics3D& pParticle) { glm::vec3 totalForce = { 0.0f, 0.0f, 0.0f }; uint32_t gridIdx = 0; const uint32_t nodeCount = static_cast(globalNodes3D.size()); const float thetaSq = myVar.theta * myVar.theta; const float softeningSq = myVar.softening * myVar.softening; const float Gf = static_cast(myVar.G); const float pmass = pParticle.mass; auto* particlesPtr = pParticles.data(); while (gridIdx < nodeCount) { Node3D& grid = globalNodes3D[gridIdx]; float gridMass = grid.gridMass; if (gridMass <= 0.0f) { gridIdx += grid.next + 1; continue; } const glm::vec3 gridCOM = grid.centerOfMass; const float gridSize = grid.size; glm::vec3 d = gridCOM - pParticle.pos; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { d.x -= myVar.domainSize3D.x * ((d.x > myVar.halfDomain3DWidth) - (d.x < -myVar.halfDomain3DWidth)); d.y -= myVar.domainSize3D.y * ((d.y > myVar.halfDomain3DHeight) - (d.y < -myVar.halfDomain3DHeight)); d.z -= myVar.domainSize3D.z * ((d.z > myVar.halfDomain3DDepth) - (d.z < -myVar.halfDomain3DDepth)); } float distanceSq = d.x * d.x + d.y * d.y + d.z * d.z + softeningSq; bool isSubgridsEmpty = true; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { for (int k = 0; k < 2; ++k) { if (grid.subGrids[i][j][k] != UINT32_MAX) { isSubgridsEmpty = false; break; } } if (!isSubgridsEmpty) break; } if (!isSubgridsEmpty) break; } float gridSizeSq = gridSize * gridSize; if ((gridSizeSq < thetaSq * distanceSq) || isSubgridsEmpty) { if ((grid.endIndex - grid.startIndex) == 1) { const ParticlePhysics3D& other = particlesPtr[grid.startIndex]; if (std::abs(other.pos.x - pParticle.pos.x) < 0.001f && std::abs(other.pos.y - pParticle.pos.y) < 0.001f && std::abs(other.pos.z - pParticle.pos.z) < 0.001f) { gridIdx += grid.next + 1; continue; } } float invDistance = 1.0f / sqrtf(distanceSq); float invDist2 = invDistance * invDistance; float invDist3 = invDist2 * invDistance; float forceMagnitude = Gf * pmass * gridMass * invDist3; totalForce += d * forceMagnitude; gridIdx += grid.next + 1; } else { ++gridIdx; } } return totalForce; } void Physics3D::calculateForceFromGrid3D(UpdateVariables& myVar) { #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < posX.size(); i++) { glm::vec3 totalForce = { 0.0f, 0.0f, 0.0f }; uint32_t gridIdx = 0; const uint32_t nodeCount = static_cast(globalNodes3D.size()); const float thetaSq = myVar.theta * myVar.theta; const float Gf = myVar.G; const float pmass = mass[i]; while (gridIdx < nodeCount) { Node3D& grid = globalNodes3D[gridIdx]; float gridMass = grid.gridMass; if (gridMass <= 0.0f) { gridIdx += grid.next + 1; continue; } const glm::vec3 gridCOM = grid.centerOfMass; const float gridSize = grid.size; glm::vec3 d = gridCOM - glm::vec3{ posX[i], posY[i], posZ[i] }; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { d.x -= myVar.domainSize3D.x * ((d.x > myVar.halfDomain3DWidth) - (d.x < -myVar.halfDomain3DWidth)); d.y -= myVar.domainSize3D.y * ((d.y > myVar.halfDomain3DHeight) - (d.y < -myVar.halfDomain3DHeight)); d.z -= myVar.domainSize3D.z * ((d.z > myVar.halfDomain3DDepth) - (d.z < -myVar.halfDomain3DDepth)); } float distanceSq = d.x * d.x + d.y * d.y + d.z * d.z + myVar.softening; bool isSubgridsEmpty = true; for (int x = 0; x < 2 && isSubgridsEmpty; ++x) { for (int y = 0; y < 2 && isSubgridsEmpty; ++y) { for (int z = 0; z < 2 && isSubgridsEmpty; ++z) { if (grid.subGrids[x][y][z] != UINT32_MAX) { isSubgridsEmpty = false; } } } } float gridSizeSq = gridSize * gridSize; if (gridSizeSq < thetaSq * distanceSq) { float invDistance = 1.0f / sqrtf(distanceSq); float invDist2 = invDistance * invDistance; float invDist3 = invDist2 * invDistance; float forceMagnitude = Gf * pmass * gridMass * invDist3; totalForce += d * forceMagnitude; if (myVar.isTempEnabled) { uint32_t count = grid.endIndex - grid.startIndex; if (count > 0) { float gridAverageTemp = grid.gridTemp / static_cast(count); float temperatureDifference = gridAverageTemp - temp[i]; float distance = 0.0f; if (distanceSq > 1e-16f) distance = 1.0f / invDistance; if (distance > 1e-8f) { float heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance; temp[i] += heatTransfer * myVar.timeFactor; } } } gridIdx += grid.next + 1; } else if (isSubgridsEmpty) { for (uint32_t j = grid.startIndex; j < grid.endIndex; ++j) { if (i == j) continue; glm::vec3 p2pd = glm::vec3{ posX[j], posY[j], posZ[j] } - glm::vec3{ posX[i], posY[i], posZ[i] }; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { p2pd.x -= myVar.domainSize3D.x * ((p2pd.x > myVar.halfDomain3DWidth) - (p2pd.x < -myVar.halfDomain3DWidth)); p2pd.y -= myVar.domainSize3D.y * ((p2pd.y > myVar.halfDomain3DHeight) - (p2pd.y < -myVar.halfDomain3DHeight)); p2pd.z -= myVar.domainSize3D.z * ((p2pd.z > myVar.halfDomain3DDepth) - (p2pd.z < -myVar.halfDomain3DDepth)); } float p2pdistSq = p2pd.x * p2pd.x + p2pd.y * p2pd.y + p2pd.z * p2pd.z + myVar.softening; float invDist = 1.0f / sqrtf(p2pdistSq); float invDist2 = invDist * invDist; float invDist3 = invDist2 * invDist; float forceMag = Gf * pmass * mass[j] * invDist3; totalForce += p2pd * forceMag; if (myVar.isTempEnabled) { float tempDiff = temp[j] - temp[i]; float p2pdist = 0.0f; if (p2pdistSq > 1e-16f) p2pdist = 1.0f / invDist; if (p2pdist > 1e-8f) { float heatTransfer = myVar.globalHeatConductivity * tempDiff / p2pdist; temp[i] += heatTransfer * myVar.timeFactor; } } } gridIdx += grid.next + 1; } else { ++gridIdx; } } accX[i] = totalForce.x / mass[i]; accY[i] = totalForce.y / mass[i]; accZ[i] = totalForce.z / mass[i]; } } void Physics3D::calculateForceFromGrid3DAVX2(UpdateVariables& myVar) { #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < posX.size(); i++) { glm::vec3 totalForce = { 0.0f, 0.0f, 0.0f }; uint32_t gridIdx = 0; const uint32_t nodeCount = static_cast(globalNodes3D.size()); const float thetaSq = myVar.theta * myVar.theta; const float Gf = myVar.G; const float pmass = mass[i]; while (gridIdx < nodeCount) { Node3D& grid = globalNodes3D[gridIdx]; float gridMass = grid.gridMass; if (gridMass <= 0.0f) { gridIdx += grid.next + 1; continue; } const glm::vec3 gridCOM = grid.centerOfMass; const float gridSize = grid.size; glm::vec3 d = gridCOM - glm::vec3{ posX[i], posY[i], posZ[i] }; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { d.x -= myVar.domainSize3D.x * ((d.x > myVar.halfDomain3DWidth) - (d.x < -myVar.halfDomain3DWidth)); d.y -= myVar.domainSize3D.y * ((d.y > myVar.halfDomain3DHeight) - (d.y < -myVar.halfDomain3DHeight)); d.z -= myVar.domainSize3D.z * ((d.z > myVar.halfDomain3DDepth) - (d.z < -myVar.halfDomain3DDepth)); } float distanceSq = d.x * d.x + d.y * d.y + d.z * d.z + myVar.softening; bool isSubgridsEmpty = true; for (int x = 0; x < 2 && isSubgridsEmpty; ++x) { for (int y = 0; y < 2 && isSubgridsEmpty; ++y) { for (int z = 0; z < 2 && isSubgridsEmpty; ++z) { if (grid.subGrids[x][y][z] != UINT32_MAX) { isSubgridsEmpty = false; } } } } float gridSizeSq = gridSize * gridSize; if (gridSizeSq < thetaSq * distanceSq) { float invDistance = 1.0f / sqrtf(distanceSq); float invDist2 = invDistance * invDistance; float invDist3 = invDist2 * invDistance; float forceMagnitude = Gf * pmass * gridMass * invDist3; totalForce += d * forceMagnitude; if (myVar.isTempEnabled) { uint32_t count = grid.endIndex - grid.startIndex; if (count > 0) { float gridAverageTemp = grid.gridTemp / static_cast(count); float temperatureDifference = gridAverageTemp - temp[i]; float distance = 0.0f; if (distanceSq > 1e-16f) distance = 1.0f / invDistance; if (distance > 1e-8f) { float heatTransfer = myVar.globalHeatConductivity * temperatureDifference / distance; temp[i] += heatTransfer * myVar.timeFactor; } } } gridIdx += grid.next + 1; } else if (isSubgridsEmpty) { __m256 vxi = _mm256_set1_ps(posX[i]); __m256 vyi = _mm256_set1_ps(posY[i]); __m256 vzi = _mm256_set1_ps(posZ[i]); __m256 vsofteningSq = _mm256_set1_ps(myVar.softening); __m256 vGfpmass = _mm256_set1_ps(Gf * pmass); __m256 vforceX = _mm256_setzero_ps(); __m256 vforceY = _mm256_setzero_ps(); __m256 vforceZ = _mm256_setzero_ps(); uint32_t j = grid.startIndex; for (; j + 7 < grid.endIndex; j += 8) { __m256 vxj = _mm256_loadu_ps(&posX[j]); __m256 vyj = _mm256_loadu_ps(&posY[j]); __m256 vzj = _mm256_loadu_ps(&posZ[j]); __m256 vmj = _mm256_loadu_ps(&mass[j]); __m256 vdx = _mm256_sub_ps(vxj, vxi); __m256 vdy = _mm256_sub_ps(vyj, vyi); __m256 vdz = _mm256_sub_ps(vzj, vzi); __m256 vdx2 = _mm256_mul_ps(vdx, vdx); __m256 vdy2 = _mm256_mul_ps(vdy, vdy); __m256 vdz2 = _mm256_mul_ps(vdz, vdz); __m256 vdistSq = _mm256_add_ps(_mm256_add_ps(_mm256_add_ps(vdx2, vdy2), vdz2), vsofteningSq); __m256 vinvDist = _mm256_rsqrt_ps(vdistSq); __m256 vinvDist2 = _mm256_mul_ps(vinvDist, vinvDist); __m256 vinvDist3 = _mm256_mul_ps(vinvDist2, vinvDist); __m256 vforceMag = _mm256_mul_ps(_mm256_mul_ps(vGfpmass, vmj), vinvDist3); __m256 vmaskNotSelf = _mm256_cmp_ps(vdistSq, vsofteningSq, _CMP_NEQ_OQ); vforceMag = _mm256_and_ps(vforceMag, vmaskNotSelf); vforceX = _mm256_add_ps(vforceX, _mm256_mul_ps(vdx, vforceMag)); vforceY = _mm256_add_ps(vforceY, _mm256_mul_ps(vdy, vforceMag)); vforceZ = _mm256_add_ps(vforceZ, _mm256_mul_ps(vdz, vforceMag)); } float tempFx[8]; float tempFy[8]; float tempFz[8]; _mm256_storeu_ps(tempFx, vforceX); _mm256_storeu_ps(tempFy, vforceY); _mm256_storeu_ps(tempFz, vforceZ); for (int k = 0; k < 8; ++k) { totalForce.x += tempFx[k]; totalForce.y += tempFy[k]; totalForce.z += tempFz[k]; } for (; j < grid.endIndex; ++j) { if (i == j) continue; glm::vec3 p2pd = glm::vec3{ posX[j], posY[j], posZ[j] } - glm::vec3{ posX[i], posY[i], posZ[i] }; float p2pdistSq = p2pd.x * p2pd.x + p2pd.y * p2pd.y + p2pd.z * p2pd.z + myVar.softening; float invDist = 1.0f / sqrtf(p2pdistSq); float invDist3 = invDist * invDist * invDist; float forceMag = Gf * pmass * mass[j] * invDist3; totalForce += p2pd * forceMag; } gridIdx += grid.next + 1; } else { ++gridIdx; } } accX[i] = totalForce.x / mass[i]; accY[i] = totalForce.y / mass[i]; accZ[i] = totalForce.z / mass[i]; } } void Physics3D::naiveGravity3D(std::vector& pParticles3D, UpdateVariables& myVar) { int n = static_cast(posX.size()); const float* posXPtr = posX.data(); const float* posYPtr = posY.data(); const float* posZPtr = posZ.data(); const float* massPtr = mass.data(); float* accXPtr = accX.data(); float* accYPtr = accY.data(); float* accZPtr = accZ.data(); #pragma omp parallel for schedule(static) for (int i = 0; i < n; i++) { accXPtr[i] = 0.0f; accYPtr[i] = 0.0f; accZPtr[i] = 0.0f; } #pragma omp parallel for schedule(dynamic, 64) for (int i = 0; i < n; i++) { float p_ix = posXPtr[i]; float p_iy = posYPtr[i]; float p_iz = posZPtr[i]; float finalAccX = 0.0f; float finalAccY = 0.0f; float finalAccZ = 0.0f; for (int j = 0; j < n; j++) { float dx = posXPtr[j] - p_ix; float dy = posYPtr[j] - p_iy; float dz = posZPtr[j] - p_iz; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { dx -= myVar.domainSize3D.x * ((dx > myVar.halfDomain3DWidth) - (dx < -myVar.halfDomain3DWidth)); dy -= myVar.domainSize3D.y * ((dy > myVar.halfDomain3DHeight) - (dy < -myVar.halfDomain3DHeight)); dz -= myVar.domainSize3D.z * ((dz > myVar.halfDomain3DDepth) - (dz < -myVar.halfDomain3DDepth)); } float distSq = dx * dx + dy * dy + dz * dz + myVar.softening; float invDist = 1.0f / std::sqrt(distSq); float invDist3 = invDist * invDist * invDist; float factor = myVar.G * massPtr[j] * invDist3; finalAccX += dx * factor; finalAccY += dy * factor; finalAccZ += dz * factor; } accXPtr[i] = finalAccX; accYPtr[i] = finalAccY; accZPtr[i] = finalAccZ; } } void Physics3D::naiveGravity3DAVX2(std::vector& pParticles3D, UpdateVariables& myVar) { int n = static_cast(posX.size()); __m256 gVec = _mm256_set1_ps(myVar.G); __m256 softVec = _mm256_set1_ps(myVar.softening); const float* posXPtr = posX.data(); const float* posYPtr = posY.data(); const float* posZPtr = posZ.data(); const float* massPtr = mass.data(); float* accXPtr = accX.data(); float* accYPtr = accY.data(); float* accZPtr = accZ.data(); __m256 domainSizeX = _mm256_set1_ps(myVar.domainSize3D.x); __m256 domainSizeY = _mm256_set1_ps(myVar.domainSize3D.y); __m256 domainSizeZ = _mm256_set1_ps(myVar.domainSize3D.z); __m256 halfDomainWidth = _mm256_set1_ps(myVar.halfDomain3DWidth); __m256 halfDomainHeight = _mm256_set1_ps(myVar.halfDomain3DHeight); __m256 halfDomainDepth = _mm256_set1_ps(myVar.halfDomain3DDepth); __m256 negHalfDomainWidth = _mm256_set1_ps(-myVar.halfDomain3DWidth); __m256 negHalfDomainHeight = _mm256_set1_ps(-myVar.halfDomain3DHeight); __m256 negHalfDomainDepth = _mm256_set1_ps(-myVar.halfDomain3DDepth); __m256 threeHalfs = _mm256_set1_ps(1.5f); __m256 pointFive = _mm256_set1_ps(0.5f); #pragma omp parallel for schedule(static) for (int i = 0; i < n; i++) { accXPtr[i] = 0.0f; accYPtr[i] = 0.0f; accZPtr[i] = 0.0f; } #pragma omp parallel for schedule(dynamic, 64) for (int i = 0; i < n; i++) { float p_ix = posXPtr[i]; float p_iy = posYPtr[i]; float p_iz = posZPtr[i]; __m256 pxi = _mm256_set1_ps(p_ix); __m256 pyi = _mm256_set1_ps(p_iy); __m256 pzi = _mm256_set1_ps(p_iz); __m256 totalAccX = _mm256_setzero_ps(); __m256 totalAccY = _mm256_setzero_ps(); __m256 totalAccZ = _mm256_setzero_ps(); int j; for (j = 0; j <= n - 8; j += 8) { __m256 pxj = _mm256_loadu_ps(&posXPtr[j]); __m256 pyj = _mm256_loadu_ps(&posYPtr[j]); __m256 pzj = _mm256_loadu_ps(&posZPtr[j]); __m256 mj = _mm256_loadu_ps(&massPtr[j]); __m256 dx = _mm256_sub_ps(pxj, pxi); __m256 dy = _mm256_sub_ps(pyj, pyi); __m256 dz = _mm256_sub_ps(pzj, pzi); if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { dx = _mm256_sub_ps(dx, _mm256_sub_ps( _mm256_and_ps(_mm256_cmp_ps(dx, halfDomainWidth, _CMP_GT_OQ), domainSizeX), _mm256_and_ps(_mm256_cmp_ps(dx, negHalfDomainWidth, _CMP_LT_OQ), domainSizeX) )); dy = _mm256_sub_ps(dy, _mm256_sub_ps( _mm256_and_ps(_mm256_cmp_ps(dy, halfDomainHeight, _CMP_GT_OQ), domainSizeY), _mm256_and_ps(_mm256_cmp_ps(dy, negHalfDomainHeight, _CMP_LT_OQ), domainSizeY) )); dz = _mm256_sub_ps(dz, _mm256_sub_ps( _mm256_and_ps(_mm256_cmp_ps(dz, halfDomainDepth, _CMP_GT_OQ), domainSizeZ), _mm256_and_ps(_mm256_cmp_ps(dz, negHalfDomainDepth, _CMP_LT_OQ), domainSizeZ) )); } __m256 distSq = _mm256_add_ps( _mm256_add_ps( _mm256_mul_ps(dx, dx), _mm256_mul_ps(dy, dy)), _mm256_add_ps( _mm256_mul_ps(dz, dz), softVec)); __m256 invDist = _mm256_rsqrt_ps(distSq); __m256 nrTerm = _mm256_mul_ps(pointFive, distSq); nrTerm = _mm256_mul_ps(nrTerm, invDist); nrTerm = _mm256_mul_ps(nrTerm, invDist); __m256 nrFactor = _mm256_sub_ps(threeHalfs, nrTerm); invDist = _mm256_mul_ps(invDist, nrFactor); __m256 invDist3 = _mm256_mul_ps(invDist, _mm256_mul_ps(invDist, invDist)); __m256 factor = _mm256_mul_ps(gVec, _mm256_mul_ps(mj, invDist3)); totalAccX = _mm256_add_ps(totalAccX, _mm256_mul_ps(dx, factor)); totalAccY = _mm256_add_ps(totalAccY, _mm256_mul_ps(dy, factor)); totalAccZ = _mm256_add_ps(totalAccZ, _mm256_mul_ps(dz, factor)); } float accX_array[8], accY_array[8], accZ_array[8]; _mm256_storeu_ps(accX_array, totalAccX); _mm256_storeu_ps(accY_array, totalAccY); _mm256_storeu_ps(accZ_array, totalAccZ); float finalAccX = 0.0f; float finalAccY = 0.0f; float finalAccZ = 0.0f; for (int k = 0; k < 8; k++) { finalAccX += accX_array[k]; finalAccY += accY_array[k]; finalAccZ += accZ_array[k]; } for (; j < n; j++) { float dx = posXPtr[j] - p_ix; float dy = posYPtr[j] - p_iy; float dz = posZPtr[j] - p_iz; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { dx -= myVar.domainSize3D.x * ((dx > myVar.halfDomain3DWidth) - (dx < -myVar.halfDomain3DWidth)); dy -= myVar.domainSize3D.y * ((dy > myVar.halfDomain3DHeight) - (dy < -myVar.halfDomain3DHeight)); dz -= myVar.domainSize3D.z * ((dz > myVar.halfDomain3DDepth) - (dz < -myVar.halfDomain3DDepth)); } float distSq = dx * dx + dy * dy + dz * dz + myVar.softening; float invDist = 1.0f / sqrt(distSq); float invDist3 = invDist * invDist * invDist; float factor = myVar.G * massPtr[j] * invDist3; finalAccX += dx * factor; finalAccY += dy * factor; finalAccZ += dz * factor; } accXPtr[i] = finalAccX; accYPtr[i] = finalAccY; accZPtr[i] = finalAccZ; } } void Physics3D::temperatureCalculation(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { for (size_t i = 0; i < pParticles.size(); i++) { ParticlePhysics3D& p = pParticles[i]; auto it = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel); if (it != SPHMaterials::idToMaterial.end()) { SPHMaterial* pMat = it->second; float pTotalVel = sqrtf(p.vel.x * p.vel.x + p.vel.y * p.vel.y + p.vel.z * p.vel.z); float pTotalPrevVel = sqrtf(p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y + p.prevVel.z * p.prevVel.z); p.ke = 0.5f * p.sphMass * pTotalVel * pTotalVel; p.prevKe = 0.5f * p.sphMass * pTotalPrevVel * pTotalPrevVel; float q = std::abs(p.ke - p.prevKe); float dTemp = q / (2.0f * pMat->heatConductivity * p.sphMass + 1.0f); p.temp += dTemp; float tempDifference = p.temp - myVar.ambientTemp; float dTempCooling = -(pMat->heatConductivity * myVar.globalAmbientHeatRate) * tempDifference * myVar.timeFactor; p.temp += dTempCooling; if (p.temp >= pMat->hotPoint) { p.sphMass = pMat->hotMassMult; p.mass = UpdateVariables::particleBaseMass * p.sphMass; p.restDens = pMat->hotRestDens; p.stiff = pMat->hotStiff; p.visc = pMat->hotVisc; p.cohesion = pMat->hotCohesion; if (pMat->coldPoint == 0.0f) { p.isHotPoint = true; } } else if (p.temp <= pMat->coldPoint) { p.sphMass = pMat->coldMassMult; p.mass = UpdateVariables::particleBaseMass * p.sphMass; p.restDens = pMat->coldRestDens; p.stiff = pMat->coldStiff; p.visc = pMat->coldVisc; p.cohesion = pMat->coldCohesion; } else { p.sphMass = pMat->massMult; p.mass = UpdateVariables::particleBaseMass * p.sphMass; p.restDens = pMat->restDens; p.stiff = pMat->stiff; p.visc = pMat->visc; p.cohesion = pMat->cohesion; if (pMat->coldPoint != 0.0f) { p.isHotPoint = true; } } if (pMat->coldPoint == 0.0f) { if (p.temp <= pMat->hotPoint && p.isHotPoint) { p.hasSolidified = true; p.isHotPoint = false; } } else { if (p.temp <= pMat->coldPoint && p.isHotPoint) { p.hasSolidified = true; p.isHotPoint = false; } } } else { float pTotalVel = sqrtf(p.vel.x * p.vel.x + p.vel.y * p.vel.y + p.vel.z * p.vel.z); float pTotalPrevVel = sqrtf(p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y + p.prevVel.z * p.prevVel.z); p.ke = 0.5f * p.sphMass * pTotalVel * pTotalVel; p.prevKe = 0.5f * p.sphMass * pTotalPrevVel * pTotalPrevVel; float q = std::abs(p.ke - p.prevKe); float dTemp = q / (2.0f * 0.05f * p.sphMass + 1.0f); p.temp += dTemp; float tempDifference = p.temp - myVar.ambientTemp; float dTempCooling = -(0.05f * myVar.globalAmbientHeatRate) * tempDifference * myVar.timeFactor; p.temp += dTempCooling; } } } void Physics3D::readFlattenBack3D(std::vector& pParticles3D) { size_t particleCount = pParticles3D.size(); #pragma omp parallel for schedule(static) for (int i = 0; i < static_cast(particleCount); i++) { pParticles3D[i].pos.x = posX[i]; pParticles3D[i].pos.y = posY[i]; pParticles3D[i].pos.z = posZ[i]; pParticles3D[i].vel.x = velX[i]; pParticles3D[i].vel.y = velY[i]; pParticles3D[i].vel.z = velZ[i]; pParticles3D[i].acc.x = accX[i]; pParticles3D[i].acc.y = accY[i]; pParticles3D[i].acc.z = accZ[i]; pParticles3D[i].prevVel.x = prevVelX[i]; pParticles3D[i].prevVel.y = prevVelY[i]; pParticles3D[i].prevVel.z = prevVelZ[i]; pParticles3D[i].mass = mass[i]; pParticles3D[i].temp = temp[i]; } } void Physics3D::integrateStart3D( std::vector& pParticles3D, std::vector& rParticles3D, UpdateVariables& myVar) { float dt = myVar.timeFactor; float halfDt = dt * 0.5f; float sphMaxVelSq = myVar.sphMaxVel * myVar.sphMaxVel; #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < pParticles3D.size(); i++) { ParticlePhysics3D& p = pParticles3D[i]; if (rParticles3D[i].isPinned) { continue; } p.prevVel = p.vel; p.vel += p.acc * halfDt; if (myVar.isSPHEnabled) { float vSq = p.vel.x * p.vel.x + p.vel.y * p.vel.y + p.vel.z * p.vel.z; if (vSq > sphMaxVelSq) { float prevVSq = p.prevVel.x * p.prevVel.x + p.prevVel.y * p.prevVel.y + p.prevVel.z * p.prevVel.z; if (prevVSq > 0.00001f) { float invPrevLen = myVar.sphMaxVel / sqrtf(prevVSq); p.prevVel *= invPrevLen; } float invLen = myVar.sphMaxVel / sqrtf(vSq); p.vel *= invLen; } } p.pos += p.vel * dt; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { if (p.pos.x < -myVar.halfDomain3DWidth) p.pos.x += myVar.domainSize3D.x; else if (p.pos.x >= myVar.halfDomain3DWidth) p.pos.x -= myVar.domainSize3D.x; if (p.pos.y < -myVar.halfDomain3DHeight) p.pos.y += myVar.domainSize3D.y; else if (p.pos.y >= myVar.halfDomain3DHeight) p.pos.y -= myVar.domainSize3D.y; if (p.pos.z < -myVar.halfDomain3DDepth) p.pos.z += myVar.domainSize3D.z; else if (p.pos.z >= myVar.halfDomain3DDepth) p.pos.z -= myVar.domainSize3D.z; } } } void Physics3D::pruneParticles(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { for (size_t i = 0; i < pParticles.size(); ) { float x = pParticles[i].pos.x; float y = pParticles[i].pos.y; float z = pParticles[i].pos.z; float halfX = myVar.domainSize3D.x * 0.5f; float halfY = myVar.domainSize3D.y * 0.5f; float halfZ = myVar.domainSize3D.z * 0.5f; if (x <= -halfX || x >= halfX || y <= -halfY || y >= halfY || z <= -halfZ || z >= halfZ) { if (pParticles.size() > 1) { std::swap(pParticles[i], pParticles.back()); std::swap(rParticles[i], rParticles.back()); } pParticles.pop_back(); rParticles.pop_back(); } else { i++; } } } void Physics3D::createConstraints(std::vector& pParticles, std::vector& rParticles, bool& constraintCreateSpecialFlag, UpdateVariables& myVar, UpdateParameters& myParam) { bool shouldCreateConstraints = IO::shortcutPress(KEY_P) || myVar.constraintAllSolids || constraintCreateSpecialFlag || myVar.constraintSelected; for (size_t i = 0; i < pParticles.size(); i++) { ParticlePhysics3D& pi = pParticles[i]; if (constraintCreateSpecialFlag) { if (!rParticles[i].isBeingDrawn) { continue; } } if (myVar.constraintSelected) { if (!rParticles[i].isSelected) { continue; } } SPHMaterial* pMatI = nullptr; auto matItI = SPHMaterials::idToMaterial.find(rParticles[i].sphLabel); if (matItI != SPHMaterials::idToMaterial.end()) { pMatI = matItI->second; } if (shouldCreateConstraints) { if (pMatI) { if (pMatI->coldPoint == 0.0f) { if (pi.temp >= pMatI->hotPoint) continue; } else { if (pi.temp >= pMatI->coldPoint) continue; } } } else { if (!pi.hasSolidified) continue; constraintMap.clear(); pi.hasSolidified = false; } std::vector neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.pos); for (size_t j : neighborIndices) { size_t neighborIndex = j; if (neighborIndex == i) continue; ParticlePhysics3D& pj = myParam.pParticles3D[neighborIndex]; float distSq = glm::dot(pj.pos - pi.pos, pj.pos - pi.pos); if (distSq > 12.0f) { continue; } if (constraintCreateSpecialFlag) { if (!rParticles[neighborIndex].isBeingDrawn) { continue; } } if (myVar.constraintSelected) { if (!rParticles[neighborIndex].isSelected) { continue; } } SPHMaterial* pMatJ = nullptr; auto matItJ = SPHMaterials::idToMaterial.find(rParticles[neighborIndex].sphLabel); if (matItJ != SPHMaterials::idToMaterial.end()) { pMatJ = matItJ->second; } if (pMatI && pMatJ && pMatI->coldPoint == 0.0f && pMatJ->coldPoint != 0.0f) { continue; } uint64_t key = makeKey(pi.id, pj.id); if (constraintMap.find(key) != constraintMap.end()) { continue; } float resistance = 0.6f; if (pMatI && pMatJ) { resistance = (pMatI->constraintResistance + pMatJ->constraintResistance) * 0.5f; } float plasticityPoint = 0.6f; if (pMatI && pMatJ) { plasticityPoint = (pMatI->constraintPlasticPoint + pMatJ->constraintPlasticPoint) * 0.5f; } if (pi.id < pj.id) { float currentDist = glm::distance(pi.pos, pj.pos); bool broken = false; if (pMatI && pMatJ) { particleConstraints.push_back({ pi.id, pj.id, currentDist, currentDist, pMatI->constraintStiffness, resistance, 0.0f, plasticityPoint, broken }); } else { float defaultStiffness = 60.0f; particleConstraints.push_back({ pi.id, pj.id, currentDist, currentDist, defaultStiffness, resistance, 0.0f, plasticityPoint, broken }); } constraintMap[key] = &particleConstraints.back(); } } } constraintCreateSpecialFlag = false; myVar.constraintAllSolids = false; myVar.constraintSelected = false; } void Physics3D::constraints(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { if (myVar.deleteAllConstraints) { particleConstraints.clear(); constraintMap.clear(); myVar.deleteAllConstraints = false; return; } uint32_t maxId = 0; for (const auto& p : pParticles) if (p.id > maxId) maxId = p.id; idToIndexTable.assign(maxId + 1, -1); for (size_t i = 0; i < pParticles.size(); i++) { idToIndexTable[pParticles[i].id] = i; } auto getIdx = [&](uint32_t id) -> int64_t { if (id >= idToIndexTable.size()) return -1; return idToIndexTable[id]; }; if (myVar.deleteSelectedConstraints) { for (auto& constraint : particleConstraints) { int64_t idx1 = getIdx(constraint.id1); int64_t idx2 = getIdx(constraint.id2); if (idx1 == -1 || idx2 == -1) { constraint.isBroken = true; } else if (rParticles[idx1].isSelected || rParticles[idx2].isSelected) { constraint.isBroken = true; } } myVar.deleteSelectedConstraints = false; } if (!particleConstraints.empty()) { auto new_end = std::remove_if(particleConstraints.begin(), particleConstraints.end(), [&](const auto& constraint) { int64_t idx1 = getIdx(constraint.id1); int64_t idx2 = getIdx(constraint.id2); return idx1 == -1 || idx2 == -1 || constraint.isBroken; }); for (auto it = new_end; it != particleConstraints.end(); ++it) { constraintMap.erase(makeKey(it->id1, it->id2)); } particleConstraints.erase(new_end, particleConstraints.end()); bool enforceTriangles = true; myVar.frameCount++; if (enforceTriangles && !particleConstraints.empty() && myVar.frameCount % 5 == 0) { std::vector> adjacency(maxId + 1); for (const auto& c : particleConstraints) { if (c.isBroken) continue; if (c.id1 <= maxId && c.id2 <= maxId) { adjacency[c.id1].push_back(c.id2); adjacency[c.id2].push_back(c.id1); } } for (auto& constraint : particleConstraints) { if (constraint.isBroken) continue; uint32_t idA = constraint.id1; uint32_t idB = constraint.id2; bool partOfTriangle = false; const std::vector& neighborsOfA = adjacency[idA]; const std::vector& neighborsOfB = adjacency[idB]; for (uint32_t neighborA : neighborsOfA) { for (uint32_t neighborB : neighborsOfB) { if (neighborA == neighborB) { partOfTriangle = true; break; } } if (partOfTriangle) break; } if (!partOfTriangle) { constraint.isBroken = true; } } } const int substeps = 15; for (int step = 0; step < substeps; step++) { #pragma omp parallel for schedule(dynamic) for (int64_t i = 0; i < (int64_t)particleConstraints.size(); i++) { auto& constraint = particleConstraints[i]; if (constraint.isBroken) continue; int64_t idx1 = getIdx(constraint.id1); int64_t idx2 = getIdx(constraint.id2); if (idx1 == -1 || idx2 == -1) { constraint.isBroken = true; continue; } ParticlePhysics3D& pi = pParticles[idx1]; ParticlePhysics3D& pj = pParticles[idx2]; SPHMaterial* pMatI = SPHMaterials::idToMaterial[rParticles[idx1].sphLabel]; SPHMaterial* pMatJ = SPHMaterials::idToMaterial[rParticles[idx2].sphLabel]; glm::vec3 delta = pj.pos - pi.pos; if (myVar.isPeriodicBoundaryEnabled && !myVar.infiniteDomain) { delta.x = fmod(delta.x + myVar.domainSize.x * 1.5f, myVar.domainSize.x) - myVar.domainSize.x * 0.5f; delta.y = fmod(delta.y + myVar.domainSize.y * 1.5f, myVar.domainSize.y) - myVar.domainSize.y * 0.5f; } float currentLength = glm::length(delta); if (currentLength < 0.0001f) continue; glm::vec3 dir = delta / currentLength; constraint.displacement = currentLength - constraint.restLength; if (!myVar.unbreakableConstraints) { if (pMatI && pMatJ) { if (!pMatI->isPlastic || !pMatJ->isPlastic) { constraint.isPlastic = false; float limit = (constraint.resistance * myVar.globalConstraintResistance) * constraint.restLength; if (std::abs(constraint.displacement) >= limit) { constraint.isBroken = true; } } else { constraint.isPlastic = true; if (std::abs(constraint.displacement) >= constraint.plasticityPoint * constraint.originalLength) { constraint.restLength += constraint.displacement; } float breakLimit = (constraint.originalLength + constraint.originalLength * (constraint.resistance * myVar.globalConstraintResistance)) * (pMatI->constraintPlasticPointMult + pMatJ->constraintPlasticPointMult) * 0.5f; if (std::abs(constraint.restLength) >= breakLimit) { constraint.isBroken = true; } } if (pi.isHotPoint || pj.isHotPoint) constraint.isBroken = true; } } if (myVar.timeFactor > 0.0f && myVar.grid3DExists && !constraint.isBroken) { glm::vec3 springForce = constraint.stiffness * constraint.displacement * dir * pi.mass * myVar.globalConstraintStiffnessMult; glm::vec3 relVel = pj.vel - pi.vel; glm::vec3 dampForce = -globalConstraintDamping * glm::dot(relVel, dir) * dir * pi.mass; glm::vec3 totalForce = springForce + dampForce; #pragma omp atomic pi.acc.x += totalForce.x / pi.mass; #pragma omp atomic pi.acc.y += totalForce.y / pi.mass; #pragma omp atomic pi.acc.z += totalForce.z / pi.mass; #pragma omp atomic pj.acc.x -= totalForce.x / pj.mass; #pragma omp atomic pj.acc.y -= totalForce.y / pj.mass; #pragma omp atomic pj.acc.z -= totalForce.z / pj.mass; float correctionFactor = constraint.stiffness * stiffCorrectionRatio * myVar.globalConstraintStiffnessMult; glm::vec3 correction = dir * constraint.displacement * correctionFactor; float massSum = pi.mass + pj.mass; glm::vec3 correctionI = correction * (pj.mass / massSum); glm::vec3 correctionJ = correction * (pi.mass / massSum); #pragma omp atomic pi.pos.x += correctionI.x; #pragma omp atomic pi.pos.y += correctionI.y; #pragma omp atomic pi.pos.z += correctionI.z; #pragma omp atomic pj.pos.x -= correctionJ.x; #pragma omp atomic pj.pos.y -= correctionJ.y; #pragma omp atomic pj.pos.z -= correctionJ.z; } } } } } void Physics3D::pausedConstraints(std::vector& pParticles, std::vector& rParticles, UpdateVariables& myVar) { for (size_t i = 0; i < particleConstraints.size(); i++) { auto& constraint = particleConstraints[i]; float prevLength = constraint.restLength; } } void Physics3D::integrateEnd3D(std::vector& pParticles3D, std::vector& rParticles3D, UpdateVariables& myVar) { #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < pParticles3D.size(); i++) { if (rParticles3D[i].isPinned) { continue; } pParticles3D[i].vel += pParticles3D[i].acc * (myVar.timeFactor * 0.5f); } } void Physics3D::spawnCorrection(UpdateParameters& myParam, bool& hasAVX2, const int& iterations) { #pragma omp parallel for for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { ParticlePhysics3D& pi = myParam.pParticles3D[i]; std::vector neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, hasAVX2, 64, pi.pos); for (size_t j : neighborIndices) { size_t neighborIndex = j; if (neighborIndex == i) continue; ParticlePhysics3D& pj = myParam.pParticles3D[neighborIndex]; if (!myParam.rParticles3D[neighborIndex].isBeingDrawn || !myParam.rParticles3D[i].isBeingDrawn) continue; glm::vec3 d = pj.pos - pi.pos; float dSq = glm::dot(d, d); const float minDist = 2.4f; const float minDistSq = minDist * minDist; if (dSq > 0.000001f && dSq < minDistSq) { float dist = sqrt(dSq); glm::vec3 dir = -d / dist; float penetration = minDist - dist; float totalMass = pi.mass + pj.mass; if (totalMass > 0.0f) { float piMove = pj.mass / totalMass; float pjMove = pi.mass / totalMass; glm::vec3 correction = dir * penetration; pi.pos += correction * piMove; pj.pos -= correction * pjMove; } } } myParam.rParticles3D[i].spawnCorrectIter++; } } ================================================ FILE: GalaxyEngine/src/Physics/quadtree.cpp ================================================ #include "Particles/particle.h" #include "Physics/quadtree.h" Node::Node(glm::vec2 pos, float size, uint32_t startIndex, uint32_t endIndex, std::vector& pParticles, std::vector& rParticles) { this->pos = pos; this->size = size; this->startIndex = startIndex; this->endIndex = endIndex; this->gridMass = 0.0f; this->centerOfMass = { 0.0f, 0.0f }; if ((((endIndex - startIndex) <= 4 /*Max Leaf Particles*/ && size <= 2.0f) /*Max Non-Dense Size*/ || (endIndex - startIndex) == 1) || size <= 0.000001f /*Min Leaf Size*/) { computeLeafMass(pParticles); } else { subGridMaker(pParticles, rParticles); computeInternalMass(); calculateNextNeighbor(); } } void Node::subGridMaker(std::vector& pParticles, std::vector& rParticles) { glm::vec2 mid = pos + size * 0.5f; uint32_t boundaries[5]; boundaries[0] = startIndex; boundaries[4] = endIndex; auto beginIt = pParticles.begin(); auto isBottomHalf = [mid](const ParticlePhysics& pParticle) { return pParticle.pos.y < mid.y; }; auto midY_Iterator = std::partition_point( beginIt + boundaries[0], beginIt + boundaries[4], isBottomHalf ); boundaries[2] = std::distance(beginIt, midY_Iterator); auto isLeftHalf = [mid](const ParticlePhysics& pParticle) { return pParticle.pos.x < mid.x; }; auto midX_BottomIterator = std::partition_point( beginIt + boundaries[0], beginIt + boundaries[2], isLeftHalf ); boundaries[1] = std::distance(beginIt, midX_BottomIterator); auto midX_TopIterator = std::partition_point( beginIt + boundaries[2], beginIt + boundaries[4], isLeftHalf ); boundaries[3] = std::distance(beginIt, midX_TopIterator); for (int q = 0; q < 4; ++q) { if (boundaries[q + 1] > boundaries[q]) { glm::vec2 newPos = { pos.x + ((q & 1) ? size * 0.5f : 0.0f), pos.y + ((q & 2) ? size * 0.5f : 0.0f) }; uint32_t childIndex = globalNodes.size(); globalNodes.emplace_back(); Node& newNode = globalNodes[childIndex]; newNode = Node( newPos, size * 0.5f, boundaries[q], boundaries[q + 1], pParticles, rParticles ); bool lr = (q & 1) ? 1 : 0; bool ud = (q & 2) ? 1 : 0; subGrids[lr][ud] = childIndex; } } } void Quadtree::root(std::vector& pParticles, std::vector& rParticles) { if (!pParticles.empty()) { globalNodes.reserve(pParticles.size() * 4); } else { globalNodes.reserve(4); } globalNodes.emplace_back(); globalNodes[0] = Node( { boundingBox.x, boundingBox.y }, boundingBox.z, 0, pParticles.size(), pParticles, rParticles ); } Node3D::Node3D(glm::vec3 pos, float size, uint32_t startIndex, uint32_t endIndex, std::vector& pParticles, std::vector& rParticles) { this->pos = pos; this->size = size; this->startIndex = startIndex; this->endIndex = endIndex; this->gridMass = 0.0f; this->centerOfMass = { 0.0f, 0.0f, 0.0f }; if ((((endIndex - startIndex) <= 4 /*Max Leaf Particles*/ && size <= 2.0f) /*Max Non-Dense Size*/ || (endIndex - startIndex) == 1) || size <= 0.000001f /*Min Leaf Size*/) { computeLeafMass3D(pParticles); } else { subGridMaker3D(pParticles, rParticles); computeInternalMass3D(); calculateNextNeighbor3D(); } } template uint32_t dualPartition3D(std::vector& pParticlesVector, std::vector& rParticlesVector, uint32_t begin, uint32_t end, Predicate predicate) { uint32_t i = begin; for (uint32_t j = begin; j < end; ++j) { if (predicate(pParticlesVector[j])) { if (i != j) { std::swap(pParticlesVector[i], pParticlesVector[j]); std::swap(rParticlesVector[i], rParticlesVector[j]); } ++i; } } return i; } void Node3D::subGridMaker3D(std::vector& pParticles, std::vector& rParticles) { glm::vec3 mid = pos + size * 0.5f; uint32_t boundaries[9]; boundaries[0] = startIndex; boundaries[8] = endIndex; auto beginIt = pParticles.begin(); auto isBottomHalfZ = [mid](const ParticlePhysics3D& p) { return p.pos.z < mid.z; }; auto midZ_It = std::partition_point( beginIt + boundaries[0], beginIt + boundaries[8], isBottomHalfZ ); boundaries[4] = std::distance(beginIt, midZ_It); auto isBackHalfY = [mid](const ParticlePhysics3D& p) { return p.pos.y < mid.y; }; auto midY_BottomIt = std::partition_point( beginIt + boundaries[0], beginIt + boundaries[4], isBackHalfY ); boundaries[2] = std::distance(beginIt, midY_BottomIt); auto midY_TopIt = std::partition_point( beginIt + boundaries[4], beginIt + boundaries[8], isBackHalfY ); boundaries[6] = std::distance(beginIt, midY_TopIt); auto isLeftHalfX = [mid](const ParticlePhysics3D& p) { return p.pos.x < mid.x; }; auto midX_01_It = std::partition_point(beginIt + boundaries[0], beginIt + boundaries[2], isLeftHalfX); boundaries[1] = std::distance(beginIt, midX_01_It); auto midX_23_It = std::partition_point(beginIt + boundaries[2], beginIt + boundaries[4], isLeftHalfX); boundaries[3] = std::distance(beginIt, midX_23_It); auto midX_45_It = std::partition_point(beginIt + boundaries[4], beginIt + boundaries[6], isLeftHalfX); boundaries[5] = std::distance(beginIt, midX_45_It); auto midX_67_It = std::partition_point(beginIt + boundaries[6], beginIt + boundaries[8], isLeftHalfX); boundaries[7] = std::distance(beginIt, midX_67_It); for (int q = 0; q < 8; ++q) { if (boundaries[q + 1] > boundaries[q]) { float offsetX = (q & 1) ? size * 0.5f : 0.0f; float offsetY = (q & 2) ? size * 0.5f : 0.0f; float offsetZ = (q & 4) ? size * 0.5f : 0.0f; glm::vec3 newPos = { pos.x + offsetX, pos.y + offsetY, pos.z + offsetZ }; uint32_t childIndex = (uint32_t)globalNodes3D.size(); globalNodes3D.emplace_back(); globalNodes3D[childIndex] = Node3D( newPos, size * 0.5f, boundaries[q], boundaries[q + 1], pParticles, rParticles ); int x = (q & 1) ? 1 : 0; int y = (q & 2) ? 1 : 0; int z = (q & 4) ? 1 : 0; subGrids[x][y][z] = childIndex; } } } void Node3D::computeInternalMass3D() { gridMass = 0.0f; gridTemp = 0.0f; centerOfMass = { 0.0f, 0.0f, 0.0f }; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { for (int k = 0; k < 2; ++k) { uint32_t idx = subGrids[i][j][k]; if (idx == UINT32_MAX) continue; Node3D& child = globalNodes3D[idx]; gridMass += child.gridMass; gridTemp += child.gridTemp; centerOfMass += child.centerOfMass * child.gridMass; } } } if (gridMass > 0) { centerOfMass /= gridMass; } } void Node3D::calculateNextNeighbor3D() { next = 0; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { for (int k = 0; k < 2; ++k) { uint32_t idx = subGrids[i][j][k]; if (idx == UINT32_MAX) continue; next++; Node3D& child = globalNodes3D[idx]; next += child.next; } } } } void Octree::root(std::vector& pParticles, std::vector& rParticles) { if (!pParticles.empty()) { globalNodes3D.clear(); globalNodes3D.reserve(pParticles.size() * 8); } else { globalNodes3D.clear(); globalNodes3D.reserve(8); } globalNodes3D.emplace_back(); globalNodes3D[0] = Node3D( rootPos, rootSize, 0, (uint32_t)pParticles.size(), pParticles, rParticles ); } ================================================ FILE: GalaxyEngine/src/Physics/slingshot.cpp ================================================ #include "IO/io.h" #include "Physics/slingshot.h" #include "parameters.h" glm::vec2 slingshotPos = { 0.0f, 0.0f }; Slingshot::Slingshot(glm::vec2 norm, float length) { this->norm = norm; this->length = length; } Slingshot Slingshot::particleSlingshot(UpdateVariables& myVar, SceneCamera myCamera) { if (IsMouseButtonPressed(1)) { myVar.isDragging = false; } glm::vec2 mouseWorldPos = glm::vec2(GetScreenToWorld2D(GetMousePosition(), myCamera.camera).x, GetScreenToWorld2D(GetMousePosition(), myCamera.camera).y); if ((IsMouseButtonPressed(0) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && (myVar.toolSpawnHeavyParticle || myVar.toolSpawnGalaxy || myVar.toolSpawnStar)) || IO::shortcutPress(KEY_ONE) || IO::shortcutPress(KEY_TWO) || IO::shortcutPress(KEY_J) ) { myVar.isDragging = true; slingshotPos = mouseWorldPos; } if (myVar.isDragging) { glm::vec2 slingshotDist = slingshotPos - mouseWorldPos; float slingshotLengthSquared = slingshotDist.x * slingshotDist.x + slingshotDist.y * slingshotDist.y; float slingshotLength = sqrt(slingshotLengthSquared); if (slingshotLength != 0) { glm::vec2 norm = slingshotDist / slingshotLength; DrawCircleV({ slingshotPos.x, slingshotPos.y }, 5, BLUE); DrawLineV({ mouseWorldPos.x, mouseWorldPos.y }, { slingshotPos.x, slingshotPos.y }, RED); Slingshot slingshot(norm, slingshotLength); return slingshot; } } return Slingshot({ 0.0f, 0.0f }, 0.0f); } glm::vec3 slingshotPos3D = { 0.0f, 0.0f, 0.0f }; Slingshot3D::Slingshot3D(glm::vec3 norm, float length) { this->norm = norm; this->length = length; } Slingshot3D Slingshot3D::particleSlingshot(UpdateVariables& myVar, glm::vec3& brushPos) { if (IsMouseButtonPressed(1)) { myVar.isDragging = false; } if ((IsMouseButtonPressed(0) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !IO::shortcutDown(KEY_LEFT_ALT) && (myVar.toolSpawnHeavyParticle || myVar.toolSpawnGalaxy || myVar.toolSpawnStar)) || IO::shortcutPress(KEY_ONE) || IO::shortcutPress(KEY_TWO) || IO::shortcutPress(KEY_J) ) { myVar.isDragging = true; slingshotPos3D = brushPos; } if (myVar.isDragging) { glm::vec3 slingshotDist = slingshotPos3D - brushPos; float slingshotLengthSquared = slingshotDist.x * slingshotDist.x + slingshotDist.y * slingshotDist.y + slingshotDist.z * slingshotDist.z; float slingshotLength = sqrt(slingshotLengthSquared); if (slingshotLength != 0) { glm::vec3 norm = slingshotDist / slingshotLength; DrawSphere({ slingshotPos3D.x, slingshotPos3D.y, slingshotPos3D.z }, 5, BLUE); DrawLine3D({ brushPos.x, brushPos.y, brushPos.z }, { slingshotPos3D.x, slingshotPos3D.y, slingshotPos3D.z }, { 255, 20, 20, 200 }); Slingshot3D slingshot(norm, slingshotLength); return slingshot; } } return Slingshot3D({ 0.0f, 0.0f, 0.0f }, 0.0f); } ================================================ FILE: GalaxyEngine/src/Sound/sound.cpp ================================================ #include "Sound/sound.h" Sound GESound::intro; Sound GESound::soundButtonHover1; Sound GESound::soundButtonHover2; Sound GESound::soundButtonHover3; Sound GESound::soundButtonEnable; Sound GESound::soundButtonDisable; Sound GESound::soundSliderSlide; // I make a pool of sounds so they don't cut eachother off if the same sound plays twice in a row too fast std::vector GESound::soundButtonHover1Pool; std::vector GESound::soundButtonHover2Pool; std::vector GESound::soundButtonHover3Pool; std::vector GESound::soundButtonEnablePool; std::vector GESound::soundButtonDisablePool; std::vector GESound::soundSliderSlidePool; void GESound::loadSounds() { InitAudioDevice(); SetMasterVolume(globalVolume); intro = LoadSound("Sounds/MenuSounds/Intro.mp3"); SetSoundVolume(intro, 0.7f); soundButtonHover1 = LoadSound("Sounds/MenuSounds/buttonHover1.mp3"); soundButtonHover2 = LoadSound("Sounds/MenuSounds/buttonHover2.mp3"); soundButtonHover3 = LoadSound("Sounds/MenuSounds/buttonHover3.mp3"); soundButtonEnable = LoadSound("Sounds/MenuSounds/buttonEnable.mp3"); soundButtonDisable = LoadSound("Sounds/MenuSounds/buttonDisable.mp3"); soundSliderSlide = LoadSound("Sounds/MenuSounds/sliderSlide.mp3"); for (size_t i = 0; i < soundPoolSize; i++) { soundButtonHover1Pool.push_back(LoadSoundAlias(soundButtonHover1)); } for (size_t i = 0; i < soundPoolSize; i++) { soundButtonHover2Pool.push_back(LoadSoundAlias(soundButtonHover2)); } for (size_t i = 0; i < soundPoolSize; i++) { soundButtonHover3Pool.push_back(LoadSoundAlias(soundButtonHover3)); } for (size_t i = 0; i < soundPoolSize; i++) { soundButtonEnablePool.push_back(LoadSoundAlias(soundButtonEnable)); } for (size_t i = 0; i < soundPoolSize; i++) { soundButtonDisablePool.push_back(LoadSoundAlias(soundButtonDisable)); } for (size_t i = 0; i < soundSliderSlidePoolSize; i++) { soundSliderSlidePool.push_back(LoadSoundAlias(soundSliderSlide)); } PlaySound(intro); } void GESound::soundtrackLogic() { SetMasterVolume(globalVolume); SetMusicVolume(currentMusic, musicVolume * musicVolMultiplier); for (Sound& s : soundButtonHover1Pool) SetSoundVolume(s, menuVolume); for (Sound& s : soundButtonHover2Pool) SetSoundVolume(s, menuVolume); for (Sound& s : soundButtonHover3Pool) SetSoundVolume(s, menuVolume); for (Sound& s : soundButtonEnablePool) SetSoundVolume(s, menuVolume); for (Sound& s : soundButtonDisablePool) SetSoundVolume(s, menuVolume); for (Sound& s : soundSliderSlidePool) SetSoundVolume(s, menuVolume + 0.15f); if (isFirstTimePlaying) { currentMusic = LoadMusicStream(playlist[currentSongIndex].c_str()); if (currentMusic.ctxData != nullptr) { PlayMusicStream(currentMusic); musicPlaying = true; isFirstTimePlaying = false; } } if (hasTrackChanged) { UnloadMusicStream(currentMusic); if (currentSongIndex < 0) { currentSongIndex = playlist.size() - 1; } else { currentSongIndex %= playlist.size(); } currentMusic = LoadMusicStream(playlist[currentSongIndex].c_str()); if (currentMusic.ctxData != nullptr) { PlayMusicStream(currentMusic); } else { musicPlaying = false; } hasTrackChanged = false; } else if (musicPlaying) { UpdateMusicStream(currentMusic); float timePlayed = GetMusicTimePlayed(currentMusic); float timeLength = GetMusicTimeLength(currentMusic); if (timePlayed >= timeLength - 0.1f) { UnloadMusicStream(currentMusic); currentSongIndex = (currentSongIndex + 1) % playlist.size(); currentMusic = LoadMusicStream(playlist[currentSongIndex].c_str()); if (currentMusic.ctxData != nullptr) { PlayMusicStream(currentMusic); } else { musicPlaying = false; } } } } void GESound::unloadSounds() { UnloadSound(intro); UnloadSound(soundButtonHover1); UnloadSound(soundButtonHover2); UnloadSound(soundButtonHover3); UnloadSound(soundButtonEnable); UnloadSound(soundButtonDisable); UnloadMusicStream(currentMusic); CloseAudioDevice(); } ================================================ FILE: GalaxyEngine/src/UI/UI.cpp ================================================ #include "UI/UI.h" void UI::uiLogic(UpdateParameters& myParam, UpdateVariables& myVar, SPH& sph, SaveSystem& save, GESound& geSound, Lighting& lighting, Field& field, ParticleSpaceship& ship) { if (IO::shortcutPress(KEY_U)) { showSettings = !showSettings; } if (myVar.timeFactor == 0.0f) { DrawRectangleV({ GetScreenWidth() - 700.0f, 20.0f }, { 10.0f, 30.0f }, WHITE); DrawRectangleV({ GetScreenWidth() - 720.0f, 20.0f }, { 10.0f, 30.0f }, WHITE); } if (ImGui::IsAnyItemHovered() || ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) { myVar.isMouseNotHoveringUI = false; myVar.isDragging = false; } else { myVar.isMouseNotHoveringUI = true; } ImGui::GetIO().IniFilename = nullptr; float screenX = static_cast(GetScreenWidth()); float screenY = static_cast(GetScreenHeight()); float buttonsWindowX = 220.0f; float buttonsWindowY = screenY - 30.0f; float settingsButtonX = 250.0f; float settingsButtonY = 25.0f; float parametersSliderX = 200.0f; float parametersSliderY = 30.0f; bool enabled = true; ImGui::SetNextWindowSize(ImVec2(buttonsWindowX, buttonsWindowY), ImGuiCond_Once); ImGui::SetNextWindowSizeConstraints(ImVec2(buttonsWindowX, buttonsWindowY), ImVec2(buttonsWindowX, buttonsWindowY)); ImGui::SetNextWindowPos(ImVec2(screenX - buttonsWindowX, 0.0f), ImGuiCond_Always); ImGui::Begin("Settings", nullptr, ImGuiWindowFlags_NoResize); float contentRegionWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; float buttonX = (contentRegionWidth - settingsButtonX) * 0.5f; float oldSpacingY = ImGui::GetStyle().ItemSpacing.y; ImGui::GetStyle().ItemSpacing.y = 5.0f; // Set the spacing only for the settings buttons std::vector controlsAndInfo{ { "Controls", "Open controls panel", &myParam.controls.isShowControlsEnabled }, { "Information", "Open information panel", &myParam.controls.isInformationEnabled } }; std::vector trails{ { "Global Trails", "Enables trails for all particles", &myVar.isGlobalTrailsEnabled }, { "Selected Trails", "Enables trails for selected particles", &myVar.isSelectedTrailsEnabled } }; std::vector size{ { "Density Size", "Maps particle neighbor amount to size", &myVar.isDensitySizeEnabled }, { "Force Size", "Maps particle acceleration to size", &myVar.isForceSizeEnabled } }; std::vector gpuSimd{ { "GPU (Beta)", "Simulates gravity on the GPU", &myVar.isGPUEnabled }, { "Naive", "Simulates gravity with a Naive algorithm. It is the most precise, but much slower", &myVar.naive } }; ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "General"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Fullscreen", "Toggles fulscreen", myVar.fullscreenState, -1.0f, settingsButtonY, true, enabled); SimilarTypeButton::buttonIterator(controlsAndInfo, -1.0f, settingsButtonY, true, enabled); buttonHelper("Multi-Threading", "Distributes the simulation across multiple threads", myVar.isMultiThreadingEnabled, -1.0f, settingsButtonY, true, enabled); SimilarTypeButton::buttonIterator(gpuSimd, -1.0f, settingsButtonY, true, enabled); if (myVar.is3DMode) { myVar.isGPUEnabled = false; } if (buttonHelper("3D Mode", "Enables 3D simulation", myVar.is3DMode, -1.0f, settingsButtonY, true, enabled)) { myParam.pParticles.clear(); myParam.rParticles.clear(); myParam.pParticlesSelected.clear(); myParam.rParticlesSelected.clear(); myParam.pParticles3D.clear(); myParam.rParticles3D.clear(); myParam.pParticlesSelected3D.clear(); myParam.rParticlesSelected3D.clear(); myParam.trails.segments.clear(); myParam.trails.segments3D.clear(); } ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Exit"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Exit Galaxy Engine", "Are you sure you don't want to play a little more?", myVar.exitGame, -1.0f, settingsButtonY, true, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Save/Load"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Save Scene", "Save current scene to disk", save.saveFlag, -1.0f, settingsButtonY, true, enabled); buttonHelper("Load Scene", "Load a scene from disk", save.loadFlag, -1.0f, settingsButtonY, true, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Trails"); ImGui::Separator(); ImGui::Spacing(); SimilarTypeButton::buttonIterator(trails, -1.0f, settingsButtonY, true, enabled); buttonHelper("Local Trails", "Enables trails moving relative to particles average position", myVar.isLocalTrailsEnabled, -1.0f, settingsButtonY, true, enabled); buttonHelper("White Trails", "Makes all trails white", myParam.trails.whiteTrails, -1.0f, settingsButtonY, true, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Visuals"); ImGui::Separator(); ImGui::Spacing(); bool* colorModesArray[] = { &myParam.colorVisuals.solidColor, &myParam.colorVisuals.densityColor, &myParam.colorVisuals.forceColor, &myParam.colorVisuals.velocityColor, &myParam.colorVisuals.shockwaveColor, &myParam.colorVisuals.turbulenceColor, &myParam.colorVisuals.pressureColor, &myParam.colorVisuals.temperatureColor, &myParam.colorVisuals.gasTempColor, &myParam.colorVisuals.SPHColor }; const char* colorModes[] = { "Solid Color", "Density Color", "Force Color", "Velocity Color", "Shockwave Color", "Turbulence Color", "Pressure Color", "Temperature Color", "Temperature Gas Color", "Material Color" }; const char* colorModeTips[] = { "Particles will only use the primary color", "Maps particle neighbor amount to primary and secondary colors", "Maps particle acceleration to primary and secondary colors", "Maps particle velocity to color", "Maps particle acceleration to color", "Maps particle turbulence to primary and secondary colors", "Maps particle pressure to color", "Maps particle temperature to color", "Maps particle temperature to primary and secondary colors", "Uses materials colors", }; static int currentColorMode = 1; if (myVar.loadDropDownMenus) { for (int i = 0; i < IM_ARRAYSIZE(colorModesArray); i++) { if (*colorModesArray[i]) { currentColorMode = i; break; } } } ImGui::PushItemWidth(-FLT_MIN); if (ImGui::BeginCombo("##Color", colorModes[currentColorMode])) { for (int i = 0; i < IM_ARRAYSIZE(colorModes); i++) { bool isSelected = (currentColorMode == i); if (ImGui::Selectable(colorModes[i], isSelected)) { currentColorMode = i; } if (isSelected) { ImGui::SetItemDefaultFocus(); } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::TextUnformatted(colorModeTips[i]); ImGui::EndTooltip(); } } ImGui::EndCombo(); for (int i = 0; i < IM_ARRAYSIZE(colorModesArray); ++i) { *colorModesArray[i] = false; } *colorModesArray[currentColorMode] = true; } ImGui::Spacing(); SimilarTypeButton::buttonIterator(size, -1.0f, settingsButtonY, true, enabled); ImGui::PopItemWidth(); buttonHelper("Flat 3D Particles", "Toggles how particles are displayed in 3D mode", myVar.flatParticleTexture3D, -1.0f, settingsButtonY, true, myVar.is3DMode); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Simulation"); ImGui::Separator(); ImGui::Spacing(); static bool galaxyModeDummy = false; bool* simModesArray[] = { &galaxyModeDummy, &myVar.isSPHEnabled, &myVar.isMergerEnabled }; const char* simModes[] = { "Galaxy Mode", "Fluid Mode", "Merger" }; const char* simModeTips[] = { "Default simulation mode. Used for very large scale objects like galaxies or the Big Bang", "Enables SPH fluids. Used for planets or small scale simulations", "Colliding particles will merge together" }; static int currentSimMode = 0; bool foundSimMode = false; if (myVar.loadDropDownMenus) { bool anyEnabled = false; for (int i = 1; i < IM_ARRAYSIZE(simModesArray); i++) { if (*simModesArray[i]) { currentSimMode = i; anyEnabled = true; break; } } galaxyModeDummy = !anyEnabled; if (!anyEnabled) { currentSimMode = 0; } } ImGui::PushItemWidth(-FLT_MIN); bool wasSPHEnabled = myVar.isSPHEnabled; bool wasMergerEnabled = myVar.isMergerEnabled; if (ImGui::BeginCombo("##Simulation", simModes[currentSimMode])) { for (int i = 0; i < IM_ARRAYSIZE(simModes); i++) { bool isSelected = (currentSimMode == i); if (ImGui::Selectable(simModes[i], isSelected)) { currentSimMode = i; } if (isSelected) { ImGui::SetItemDefaultFocus(); } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::TextUnformatted(simModeTips[i]); ImGui::EndTooltip(); } } ImGui::EndCombo(); for (int i = 0; i < IM_ARRAYSIZE(simModesArray); ++i) { *simModesArray[i] = false; } *simModesArray[currentSimMode] = true; bool anyModeActive = false; for (int i = 0; i < IM_ARRAYSIZE(simModesArray); ++i) { if (*simModesArray[i]) { anyModeActive = true; break; } } if (!anyModeActive) { galaxyModeDummy = true; currentSimMode = 0; } if (!wasSPHEnabled && myVar.isSPHEnabled) { for (size_t i = 0; i < IM_ARRAYSIZE(colorModesArray); i++) { *colorModesArray[i] = (colorModesArray[i] == &myParam.colorVisuals.SPHColor); if (colorModesArray[i] == &myParam.colorVisuals.SPHColor) { currentColorMode = i; myVar.SPHWater = true; } } } if (!wasMergerEnabled && myVar.isMergerEnabled) { for (size_t i = 0; i < IM_ARRAYSIZE(colorModesArray); i++) { *colorModesArray[i] = (colorModesArray[i] == &myParam.colorVisuals.solidColor); if (colorModesArray[i] == &myParam.colorVisuals.solidColor) { currentColorMode = i; myParam.colorVisuals.pColor = { 255,255,255,255 }; } } } foundSimMode = false; for (int i = 0; i < IM_ARRAYSIZE(simModesArray); i++) { if (*simModesArray[i]) { currentSimMode = i; foundSimMode = true; break; } } if (!foundSimMode) { galaxyModeDummy = true; currentSimMode = 0; } } ImGui::PopItemWidth(); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Fluid Mode Material"); ImGui::Separator(); ImGui::Spacing(); bool* materialsArray[] = { &myVar.SPHWater, &myVar.SPHRock, &myVar.SPHIron, &myVar.SPHSand, &myVar.SPHSoil, &myVar.SPHIce, &myVar.SPHMud, &myVar.SPHRubber, &myVar.SPHGas }; const char* materials[] = { "Water", "Rock", "Iron", "Sand", "Soil", "Ice", "Mud", "Rubber", "Gas" }; static int currentMat = 0; if (myVar.loadDropDownMenus) { for (int i = 0; i < IM_ARRAYSIZE(materialsArray); i++) { if (*materialsArray[i]) { currentMat = i; break; } } } ImGui::PushItemWidth(-FLT_MIN); ImGui::BeginDisabled(!myVar.isSPHEnabled); if (ImGui::BeginCombo("##Materials", materials[currentMat])) { for (int i = 0; i < IM_ARRAYSIZE(materials); i++) { bool isSelected = (currentMat == i); if (ImGui::Selectable(materials[i], isSelected)) { currentMat = i; } if (isSelected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); for (int i = 0; i < IM_ARRAYSIZE(materialsArray); ++i) { *materialsArray[i] = false; } *materialsArray[currentMat] = true; } ImGui::PopItemWidth(); ImGui::EndDisabled(); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Dark Matter"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Dark Matter", "Enables dark matter particles. This works for galaxies and Big Bang", myVar.isDarkMatterEnabled, -1.0f, settingsButtonY, true, enabled); buttonHelper("Show Dark Matter", "Unhides dark matter particles", myParam.colorVisuals.showDarkMatterEnabled, -1.0f, settingsButtonY, true, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Space Modifiers"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Fluid Ground Mode", "Adds vertical gravity and makes particles collide with the domain walls", myVar.sphGround, -1.0f, settingsButtonY, true, myVar.isSPHEnabled); buttonHelper("Looping Space", "Particles disappearing on one side will appear on the other side", myVar.isPeriodicBoundaryEnabled, -1.0f, settingsButtonY, true, enabled); buttonHelper("Infinite Domain", "Enables or disables the domain boundaries that contain the simulation", myVar.infiniteDomain, -1.0f, settingsButtonY, true, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Temperature"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Temperature Simulation", "Enables temperature simulation", myVar.isTempEnabled, -1.0f, settingsButtonY, true, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Constraints"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Particle Constraints", "Enables particles constraints for solids and soft bodies simulation. Works best with Fluid Mode enabled", myVar.constraintsEnabled, -1.0f, settingsButtonY, true, enabled); buttonHelper("Unbreakable Constraints", "Makes all constraints unbreakable", myVar.unbreakableConstraints, -1.0f, settingsButtonY, true, myVar.constraintsEnabled); buttonHelper("Constraint After Drawing", "Creates constraints in between particles right after drawing them", myVar.constraintAfterDrawing, -1.0f, settingsButtonY, true, myVar.constraintsEnabled); if (buttonHelper("Visualize Constraints", "Draws all existing constraints", myVar.drawConstraints, -1.0f, settingsButtonY, true, myVar.constraintsEnabled)) { myVar.visualizeMesh = false; } if (buttonHelper("Visualize Mesh", "Draws a mesh that connect particles", myVar.visualizeMesh, -1.0f, settingsButtonY, true, enabled)) { myVar.drawConstraints = false; } buttonHelper("Constraint Stress Color", "Maps the constraints stress to an RGB color", myVar.constraintStressColor, -1.0f, settingsButtonY, true, myVar.drawConstraints); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Optics (2D Only)"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Optics", "Enables light simulation with ray tracing. Simulate light from the Optics tab", myVar.isOpticsEnabled, -1.0f, settingsButtonY, true, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Fields (2D Only)"); ImGui::Separator(); ImGui::Spacing(); bool isNot3DMode = !myVar.is3DMode; if (myVar.is3DMode) { myVar.isGravityFieldEnabled = false; myVar.gravityFieldDMParticles = false; } if (buttonHelper("Gravity Field", "Enables the gravity field visualization mode (IT IS RECOMMENDED TO USE SMALLER DOMAIN SIZES)", myVar.isGravityFieldEnabled, -1.0f, settingsButtonY, true, isNot3DMode)) { field.computeField = true; } buttonHelper("DM Particles", "Enables ignores Dark Matter particles for the gravity field", myVar.gravityFieldDMParticles, -1.0f, settingsButtonY, true, isNot3DMode); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Misc."); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Highlight Selected", "Highlight selected particles", myParam.colorVisuals.selectedColor, -1.0f, settingsButtonY, true, enabled); buttonHelper("Predict Path", "Predicts the trajectory of black holes before launching them", myVar.enablePathPrediction, -1.0f, settingsButtonY, true, enabled); ImGui::GetStyle().ItemSpacing.y = oldSpacingY; // End the settings buttons spacing ImGui::End(); // Start of settings sliders float parametersWindowSizeX = 400.0f; float parametersWindowSizeY = screenY - 30.0f; ImGui::SetNextWindowSize(ImVec2(parametersWindowSizeX, parametersWindowSizeY), ImGuiCond_Once); ImGui::SetNextWindowSizeConstraints(ImVec2(parametersWindowSizeX, parametersWindowSizeY), ImVec2(parametersWindowSizeX, parametersWindowSizeY)); ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiCond_Always); ImGui::Begin("Parameters", nullptr, ImGuiWindowFlags_NoResize); float totalWidth = ImGui::GetContentRegionAvail().x; float halfButtonWidth = totalWidth * 0.4f; if (ImGui::BeginTabBar("##MainTabBar", ImGuiTabBarFlags_NoTabListScrollingButtons)) { if (ImGui::BeginTabItem("Visuals")) { bVisualsSliders = true; bPhysicsSliders = false; bStatsWindow = false; bRecordingSettings = false; bSoundWindow = false; bLightingWindow = false; // Initialize all tabs for sliders defaults if (loadSettings) { bVisualsSliders = true; bPhysicsSliders = true; bStatsWindow = true; bRecordingSettings = true; bSoundWindow = true; bLightingWindow = true; loadSettings = false; } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Physics")) { bVisualsSliders = false; bPhysicsSliders = true; bStatsWindow = false; bRecordingSettings = false; bSoundWindow = false; bLightingWindow = false; ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Advanced Stats")) { bVisualsSliders = false; bPhysicsSliders = false; bStatsWindow = true; bRecordingSettings = false; bSoundWindow = false; bLightingWindow = false; ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Optics")) { bVisualsSliders = false; bPhysicsSliders = false; bStatsWindow = false; bRecordingSettings = false; bSoundWindow = false; bLightingWindow = true; ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Sound")) { bVisualsSliders = false; bPhysicsSliders = false; bStatsWindow = false; bRecordingSettings = false; bSoundWindow = true; bLightingWindow = false; ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Recording")) { bVisualsSliders = false; bPhysicsSliders = false; bStatsWindow = false; bRecordingSettings = true; bSoundWindow = false; bLightingWindow = false; ImGui::EndTabItem(); } ImGui::EndTabBar(); } ImGui::BeginChild("##ContentRegion", ImVec2(0, 0), true); { if (bVisualsSliders) { ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Shader"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Glow", "Enables glow shader", myVar.isGlowEnabled, -1.0f, settingsButtonY, true, enabled); sliderHelper("Glow Size", "Controls glow size", myVar.glowSize, 3, 48, parametersSliderX, parametersSliderY, enabled); sliderHelper("Glow Strength", "Controls glow strength", myVar.glowStrength, 0.1f, 5.0f, parametersSliderX, parametersSliderY, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Colors"); ImGui::Separator(); ImGui::Spacing(); Color primaryColors = { static_cast(myParam.colorVisuals.pColor.r), static_cast(myParam.colorVisuals.pColor.g), static_cast(myParam.colorVisuals.pColor.b), static_cast(myParam.colorVisuals.pColor.a) }; ImVec4 imguiPColor = rlImGuiColors::Convert(primaryColors); static Color originalPColor = primaryColors; bool placeholderP = false; if (buttonHelper("Reset Primary Color", "Resets the secondary color picker", placeholderP, 240.0f, 30.0f, true, enabled)) { myParam.colorVisuals.pColor.r = originalPColor.r; myParam.colorVisuals.pColor.g = originalPColor.g; myParam.colorVisuals.pColor.b = originalPColor.b; myParam.colorVisuals.pColor.a = originalPColor.a; } if (ImGui::ColorPicker4("Primary Color", (float*)&imguiPColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { primaryColors = rlImGuiColors::Convert(imguiPColor); myParam.colorVisuals.pColor.r = primaryColors.r; myParam.colorVisuals.pColor.g = primaryColors.g; myParam.colorVisuals.pColor.b = primaryColors.b; myParam.colorVisuals.pColor.a = primaryColors.a; } Color secondaryColors = { static_cast(myParam.colorVisuals.sColor.r), static_cast(myParam.colorVisuals.sColor.g), static_cast(myParam.colorVisuals.sColor.b), static_cast(myParam.colorVisuals.sColor.a) }; ImVec4 imguiSColor = rlImGuiColors::Convert(secondaryColors); static Color originalSColor = secondaryColors; bool placeholderS = false; if (buttonHelper("Reset Secondary Col.", "Resets the primary color picker", placeholderS, 240.0f, 30.0f, true, enabled)) { myParam.colorVisuals.sColor.r = originalSColor.r; myParam.colorVisuals.sColor.g = originalSColor.g; myParam.colorVisuals.sColor.b = originalSColor.b; myParam.colorVisuals.sColor.a = originalSColor.a; } if (ImGui::ColorPicker4("Secondary Col.", (float*)&imguiSColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { secondaryColors = rlImGuiColors::Convert(imguiSColor); myParam.colorVisuals.sColor.r = secondaryColors.r; myParam.colorVisuals.sColor.g = secondaryColors.g; myParam.colorVisuals.sColor.b = secondaryColors.b; myParam.colorVisuals.sColor.a = secondaryColors.a; } ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Camera"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("First Person Camera", "Enables first person mode. Use the arrow buttons to move", myVar.firstPerson, 240.0f, 30.0f, true, enabled); sliderHelper("Camera Arrows Speed", "Controls the speed of the camera when moving with arrows", myParam.myCamera3D.arrowMoveSpeed, 0.001f, 5000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Particle Clipping"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Clip Selected X", "Hides half of the selected particles on the X axis", myVar.clipSelectedX, 240.0f, 30.0f, true, enabled); ImGui::SameLine(); buttonHelper("Invert X", "Inverts X half", myVar.clipSelectedXInv, 75.0f, 30.0f, true, enabled); buttonHelper("Clip Selected Y", "Hides half of the selected particles on the Y axis", myVar.clipSelectedY, 240.0f, 30.0f, true, enabled); ImGui::SameLine(); buttonHelper("Invert Y", "Inverts Y half", myVar.clipSelectedYInv, 75.0f, 30.0f, true, enabled); buttonHelper("Clip Selected Z", "Hides half of the selected particles on the Z axis", myVar.clipSelectedZ, 240.0f, 30.0f, true, enabled); ImGui::SameLine(); buttonHelper("Invert Z", "Inverts Z half", myVar.clipSelectedZInv, 75.0f, 30.0f, true, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Neighbor Search"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Max Neighbors", "Controls the maximum neighbor count range", myParam.colorVisuals.maxNeighbors, 1, 2000, parametersSliderX, parametersSliderY, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Color Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Max Force Color", "Controls the acceleration threshold to use the secondary color", myParam.colorVisuals.maxColorAcc, 1.0f, 400.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Max Velocity Color", "Controls the max velocity used to map the colors in the velocity color mode", myParam.colorVisuals.maxVel, 10.0f, 10000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Max Shockwave Accel", "Controls the acceleration threshold to map the particle color in Shockwave color mode", myParam.colorVisuals.ShockwaveMaxAcc, 1.0f, 120.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Separator(); sliderHelper("Max Turbulence Color", "Controls the turbulence threshold to use the secondary color", myParam.colorVisuals.maxColorTurbulence, 1.0f, 512.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Turbulence Fade Rate", "Controls how fast turbulence fades away", myParam.colorVisuals.turbulenceFadeRate, 0.00f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Turbulence Contrast", "Controls how much contrast turbulence color has", myParam.colorVisuals.turbulenceContrast, 0.1f, 4.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); buttonHelper("Turbulence Custom Colors", "Enables the use of primary and secondary colors for turbulence", myParam.colorVisuals.turbulenceCustomCol, 212.0f, 24.0f, true, enabled); ImGui::Separator(); sliderHelper("Max Pressure Color", "Controls the max pressure used to map the colors in the pressure color mode", myParam.colorVisuals.maxPress, 100.0f, 100000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Max Temperature Color", "Controls the max temperature used to map the colors in the temperature color mode", myParam.colorVisuals.tempColorMaxTemp, 10.0f, 50000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Max Constraint Stress", "Controls the max constraint stress used to map the colors in the constraints stress color mode. If set to 0, it will set the max stress to the material's breaking limit", myVar.constraintMaxStressColor, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Size Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Max Dynamic Size", "Controls the maximum size particles can have when chaning size dynamically", myParam.densitySize.maxSize, 0.1f, 5.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Min Dynamic Size", "Controls the minimum size particles can have when chaning size dynamically", myParam.densitySize.minSize, 0.001f, 5.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Max Size Force", "Controls the acceleration threshold to map the particle size", myParam.densitySize.sizeAcc, 1.0f, 400.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Max Size Neighbors", "Controls the neighbors threshold to map the particle size", myParam.densitySize.maxNeighbors, 1, 1000, parametersSliderX, parametersSliderY, enabled); sliderHelper("Particles Size", "Controls the size of all particles", myVar.particleSizeMultiplier, 0.0f, 5.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Trails Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Trails Length", "Controls how long should the trails be. This feature is computationally expensive", myVar.trailMaxLength, 0, 1500, parametersSliderX, parametersSliderY, enabled); sliderHelper("Trails Thickness", "Controls the trails thickness", myParam.trails.trailThickness, 0.01f, 1.5f, parametersSliderX, parametersSliderY, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Field Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Field Res", "Controls how much gravity affects the field colors", field.res, 50, 1000, parametersSliderX, parametersSliderY, enabled); sliderHelper("Gravity Display Threshold", "Controls how much gravity affects the field colors", field.gravityDisplayThreshold, 10.0f, 3000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Gravity Display Softness", "Controls how soft the gravity display looks", field.gravityDisplaySoftness, 0.4f, 8.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Gravity Display Stretch", "Controls how contrasty the gravity display looks", field.gravityStretchFactor, 1.0f, 10000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); buttonHelper("Gravity Custom Colors", "Enables the use of primary and secondary colors for the gravity field", field.gravityCustomColors, 212.0f, 24.0f, true, enabled); sliderHelper("Gravity Custom Color Exp.", "Controls the exposure of the custom color mode for the gravity field", field.gravityExposure, 0.001f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Misc. Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Path Prediction Length", "Controls how long is the predicted path", myVar.predictPathLength, 100, 2000, parametersSliderX, parametersSliderY, enabled); bool isSPHDisabled = !myVar.isSPHEnabled; static bool prevSPHState = false; static bool prevMassMultiplierEnabled = false; static float prevMassScatter = 0.0f; if (myVar.isSPHEnabled != prevSPHState) { if (myVar.isSPHEnabled) { prevMassMultiplierEnabled = myParam.particlesSpawning.massMultiplierEnabled; prevMassScatter = myVar.massScatter; myParam.particlesSpawning.massMultiplierEnabled = false; myVar.massScatter = 0.0f; } else { myParam.particlesSpawning.massMultiplierEnabled = prevMassMultiplierEnabled; myVar.massScatter = prevMassScatter; } } prevSPHState = myVar.isSPHEnabled; ImGui::Spacing(); } if (bPhysicsSliders) { ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "System Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Threads Amount", "Controls the amount of threads used by the simulation. Half your total amount of threads is usually the sweet spot", myVar.threadsAmount, 1, 32, parametersSliderX, parametersSliderY, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Simulation Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Theta", "Controls the quality of the gravity calculation. Higher means lower quality", myVar.theta, 0.1f, 5.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); if (!myVar.is3DMode) { sliderHelper("Domain Width", "Controls the width of the global container", myVar.domainSize.x, 200.0f, 3840.0f, parametersSliderX, parametersSliderY, enabled); sliderHelper("Domain Height", "Controls the height of the global container", myVar.domainSize.y, 200.0f, 2160.0f, parametersSliderX, parametersSliderY, enabled); } else { sliderHelper("Domain Width", "Controls the width of the global container", myVar.domainSize3D.x, 200.0f, 3840.0f, parametersSliderX, parametersSliderY, enabled); sliderHelper("Domain Height", "Controls the height of the global container", myVar.domainSize3D.y, 200.0f, 2160.0f, parametersSliderX, parametersSliderY, enabled); sliderHelper("Domain Depth", "Controls the depth of the global container", myVar.domainSize3D.z, 200.0f, 2160.0f, parametersSliderX, parametersSliderY, enabled); } ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, " General Physics Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Time Scale", "Controls how fast time passes", myVar.timeStepMultiplier, 0.0f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Softening", "Controls the smoothness of the gravity forces", myVar.softening, 0.5f, 30.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Gravity Strength", "Controls how much particles attract eachother", myVar.gravityMultiplier, 0.0f, 100.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Temperature Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Ambient Temperature", "Controls the desired temperature of the scene in Kelvin. 1 is near absolute zero. The default value is set just high enough to allow liquid water", myVar.ambientTemp, 1.0f, 2500.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Ambient Heat Rate", "Controls how fast particles' temperature try to match ambient temperature", myVar.globalAmbientHeatRate, 0.0f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Heat Conductivity Multiplier", "Controls the global heat conductivity of particles", myVar.globalHeatConductivity, 0.001f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Constraints Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Constraints Stiffness Multiplier", "Controls the global stiffness multiplier for constraints", myVar.globalConstraintStiffnessMult, 0.001f, 3.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Constraints Resistance Multiplier", "Controls the global resistance multiplier for constraints", myVar.globalConstraintResistance, 0.001f, 30.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Fluids Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Fluid Vertical Gravity", "Controls the vertical gravity strength in Fluid Ground Mode", myVar.verticalGravity, 0.0f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Fluid Mass Multiplier", "Controls the fluid mass of particles", myVar.mass, 0.005f, 0.15f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Fluid Viscosity", "Controls how viscous particles are", myVar.viscosity, 0.01f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Fluid Stiffness", "Controls how stiff particles are", myVar.stiffMultiplier, 0.01f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Fluid Cohesion", "Controls how sticky particles are", myVar.cohesionCoefficient, 0.0f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Fluid Delta", "Controls the scaling factor in the pressure solver to enforce fluid incompressibility", myVar.delta, 500.0f, 20000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Fluid Max Velocity", "Controls the maximum velocity a particle can have in Fluid mode", myVar.sphMaxVel, 0.0f, 2000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Domain Friction", "Controls the friction of the domain walls", myVar.boundaryFriction, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); } if (bSoundWindow) { bool enabled = true; ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "General Sound Parameters"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Global Volume", "Controls global sound volume", geSound.globalVolume, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled); sliderHelper("Menu Volume", "Controls menu sounds volume", geSound.menuVolume, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled); sliderHelper("Music Volume", "Controls soundtrack volume", geSound.musicVolume, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Soundtrack Parameters"); ImGui::Separator(); ImGui::Spacing(); if (buttonHelper("<- Previous Track", "Plays the previous track in the playlist", geSound.hasTrackChanged, -1.0f, settingsButtonY, true, enabled)) { geSound.currentSongIndex--; } if (buttonHelper("Next Track ->", "Plays the next track in the playlist", geSound.hasTrackChanged, -1.0f, settingsButtonY, true, enabled)) { geSound.currentSongIndex++; } } if (bRecordingSettings) { float oldSpacingY = ImGui::GetStyle().ItemSpacing.y; ImGui::GetStyle().ItemSpacing.y = 5.0f; // Set the spacing only for the recording settings buttons ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "General Recording Parameters"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Pause After Recording", "Pauses the simulation after recording is finished", myVar.pauseAfterRecording, -1.0f, settingsButtonY, true, enabled); buttonHelper("Clean Scene After Recording", "Clears all particles from the scene after recording is finished", myVar.cleanSceneAfterRecording, -1.0f, settingsButtonY, true, enabled); ImGui::Separator(); bool isEnabled = true; if (myVar.isRecording) { isEnabled = false; } sliderHelper("Recording Time Limit", "Set a time limit for the recording. 0 means no limit.", myVar.recordingTimeLimit, 0.0f, 60.0f, parametersSliderX, parametersSliderY, isEnabled); ImGui::GetStyle().ItemSpacing.y = oldSpacingY; ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Simulation Recording (3D Only)"); ImGui::Separator(); ImGui::Spacing(); bool enablePlaybackControls = false; bool isNotRecording = !myVar.playbackRecord; if (!myParam.playbackFrames.empty() && !myVar.playbackRecord) { enablePlaybackControls = true; } if (myVar.playbackRecord) { myVar.runPlayback = false; } buttonHelper("Store Playback On Memory", "Stores the playback on memory instead of disk", myVar.playBackOnMemory, -1.0f, settingsButtonY, true, isNotRecording); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Keyframe Interval", "Sets the number of frames in between each stored frame", myVar.keyframeTickInterval, 1, 30, parametersSliderX, parametersSliderY, enabled); std::string buttonText; std::string tooltipText; if (myVar.playbackRecord) { buttonText = "Stop Recording"; tooltipText = "Stop simulation recording"; } else if (!myParam.playbackFrames.empty()) { buttonText = "Resume Simulation"; tooltipText = "Resume recording the simulation"; } else { buttonText = "Record Simulation"; tooltipText = "Start recording simulation frames"; } if (buttonHelper(buttonText.c_str(), tooltipText.c_str(), myVar.playbackRecord, -1.0f, settingsButtonY, true, myVar.is3DMode)) { if (!myVar.playbackRecord) { myVar.runPlayback = true; std::swap(myParam.pParticles3D, myParam.pParticles3DPlaybackResume); std::swap(myParam.rParticles3D, myParam.rParticles3DPlaybackResume); } if (!myParam.playbackFrames.empty() && myVar.playbackRecord) { std::swap(myParam.pParticles3D, myParam.pParticles3DPlaybackResume); std::swap(myParam.rParticles3D, myParam.rParticles3DPlaybackResume); myParam.pParticles3DPlaybackResume.clear(); myParam.rParticles3DPlaybackResume.clear(); } } buttonHelper("Run Playback", "Plays the recorded simulation", myVar.runPlayback, -1.0f, settingsButtonY, true, enablePlaybackControls); sliderHelper("Playback speed", "Sets how fast the playback is played", myVar.playbackSpeed, 0.0f, 20.0f, parametersSliderX, parametersSliderY, enablePlaybackControls, LogSlider); if (sliderHelper("Playback Timeline", "Scroll through the playback frames", myVar.playbackProgress, 0.0f, static_cast(myParam.playbackFrames.size() - 2), parametersSliderX, parametersSliderY, enablePlaybackControls)) { myVar.runPlayback = false; } sliderHelper("Playback Particles Size", "Modifies the size of playback particles", myVar.playbackParticlesSizeMult, 0.0f, 5.0f, parametersSliderX, parametersSliderY, enablePlaybackControls, LogSlider); if (buttonHelper("Delete Playback", "Deletes playback and resumes simulation", myVar.deletePlayback, -1.0f, settingsButtonY, true, enablePlaybackControls) && !myVar.playbackRecord) { myVar.runPlayback = false; myParam.playbackFrames.clear(); std::swap(myParam.pParticles3D, myParam.pParticles3DPlaybackResume); std::swap(myParam.rParticles3D, myParam.rParticles3DPlaybackResume); myParam.pParticles3DPlaybackResume.clear(); myParam.rParticles3DPlaybackResume.clear(); if (std::filesystem::exists(myVar.playbackPath)) { std::filesystem::remove(myVar.playbackPath); } myVar.deletePlayback = false; } size_t totalBytes = 0; for (const auto& frame : myParam.playbackFrames) totalBytes += frame.size() * sizeof(PlaybackParticle); double totalMB = totalBytes / (1024.0 * 1024.0); ImGui::Text("Total Space: %.2f MB", totalMB); } if (bStatsWindow) { statsWindowLogic(myParam, myVar); } if (bLightingWindow) { bool enabled = true; ImVec4 imguiLightColor = rlImGuiColors::Convert(lighting.lightColor); Color imguiLightColorRl; ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Color Settings"); ImGui::Separator(); ImGui::Spacing(); if (ImGui::ColorPicker3("Light Color", (float*)&imguiLightColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { imguiLightColorRl = rlImGuiColors::Convert(imguiLightColor); lighting.lightColor.r = imguiLightColorRl.r; lighting.lightColor.g = imguiLightColorRl.g; lighting.lightColor.b = imguiLightColorRl.b; lighting.lightColor.a = imguiLightColorRl.a; lighting.isSliderLightColor = true; } ImVec4 imguiWallBaseColor = rlImGuiColors::Convert(lighting.wallBaseColor); Color imguiWallBaseColorRl; if (ImGui::ColorPicker3("Wall Base Color", (float*)&imguiWallBaseColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { imguiWallBaseColorRl = rlImGuiColors::Convert(imguiWallBaseColor); lighting.wallBaseColor.r = imguiWallBaseColorRl.r; lighting.wallBaseColor.g = imguiWallBaseColorRl.g; lighting.wallBaseColor.b = imguiWallBaseColorRl.b; lighting.wallBaseColor.a = imguiWallBaseColorRl.a; lighting.isSliderBaseColor = true; } ImVec4 imguiWallSpecularColor = rlImGuiColors::Convert(lighting.wallSpecularColor); Color imguiWallSpecularColorRl; if (ImGui::ColorPicker3("Wall Specular Color", (float*)&imguiWallSpecularColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { imguiWallSpecularColorRl = rlImGuiColors::Convert(imguiWallSpecularColor); lighting.wallSpecularColor.r = imguiWallSpecularColorRl.r; lighting.wallSpecularColor.g = imguiWallSpecularColorRl.g; lighting.wallSpecularColor.b = imguiWallSpecularColorRl.b; lighting.wallSpecularColor.a = imguiWallSpecularColorRl.a; lighting.isSliderSpecularColor = true; } ImVec4 imguiWallRefractionColor = rlImGuiColors::Convert(lighting.wallRefractionColor); Color imguiWallRefractionColorRl; if (ImGui::ColorPicker3("Wall Refraction Color", (float*)&imguiWallRefractionColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { imguiWallRefractionColorRl = rlImGuiColors::Convert(imguiWallRefractionColor); lighting.wallRefractionColor.r = imguiWallRefractionColorRl.r; lighting.wallRefractionColor.g = imguiWallRefractionColorRl.g; lighting.wallRefractionColor.b = imguiWallRefractionColorRl.b; lighting.wallRefractionColor.a = imguiWallRefractionColorRl.a; lighting.isSliderRefractionCol = true; } ImVec4 imguiWallEmissionColor = rlImGuiColors::Convert(lighting.wallEmissionColor); Color imguiWallEmissionColorRl; if (ImGui::ColorPicker3("Wall Emission Color", (float*)&imguiWallEmissionColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { imguiWallEmissionColorRl = rlImGuiColors::Convert(imguiWallEmissionColor); lighting.wallEmissionColor.r = imguiWallEmissionColorRl.r; lighting.wallEmissionColor.g = imguiWallEmissionColorRl.g; lighting.wallEmissionColor.b = imguiWallEmissionColorRl.b; lighting.wallEmissionColor.a = imguiWallEmissionColorRl.a; lighting.isSliderEmissionCol = true; } ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Light Settings"); ImGui::Separator(); ImGui::Spacing(); if (sliderHelper("Light Gain", "Controls lights brightness", lighting.lightGain, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled, LogSlider)) { lighting.isSliderLightGain = true; } if (sliderHelper("Light Spread", "Controls the spread of area and cone lights", lighting.lightSpread, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) { lighting.isSliderlightSpread = true; } ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Wall Material Settings"); ImGui::Separator(); ImGui::Spacing(); if (sliderHelper("Wall Specular Roughness", "Controls the specular reflections roughness of walls", lighting.wallSpecularRoughness, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) { lighting.isSliderSpecularRough = true; } if (sliderHelper("Wall Refraction Roughness", "Controls the refraction surface roughness of walls", lighting.wallRefractionRoughness, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) { lighting.isSliderRefractionRough = true; } if (sliderHelper("Wall Refraction Amount", "Controls how much light walls will refract", lighting.wallRefractionAmount, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) { lighting.isSliderRefractionAmount = true;; } if (sliderHelper("Wall IOR", "Controls the IOR of walls", lighting.wallIOR, 0.0f, 100.0f, parametersSliderX, parametersSliderY, enabled)) { lighting.isSliderIor = true; } if (sliderHelper("Wall Dispersion", "Controls how much light gets dispersed after refracting from this wall", lighting.wallDispersion, 0.0f, 0.2f, parametersSliderX, parametersSliderY, enabled)) { lighting.isSliderDispersion = true; } if (sliderHelper("Wall Emission Gain", "Controls how much light walls emit", lighting.wallEmissionGain, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled)) { lighting.isSliderEmissionGain = true; } ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Shape Settings"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Shape Relax Iter.", "Controls the iterations used to relax the shapes when drawing", lighting.shapeRelaxIter, 0, 50, parametersSliderX, parametersSliderY, enabled); sliderHelper("Shape Relax Factor", "Controls how much the drawn shape should relax each iteration", lighting.shapeRelaxFactor, 0.0f, 1.0f, parametersSliderX, parametersSliderY, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Render Settings"); ImGui::Separator(); ImGui::Spacing(); if (sliderHelper("Max Samples", "Controls the total amount of lighting iterations", lighting.maxSamples, 1, 2048, parametersSliderX, parametersSliderY, enabled)) { lighting.shouldRender = true; } if (sliderHelper("Rays Per Sample", "Controls amount of rays emitted on each sample", lighting.sampleRaysAmount, 1, 8192, parametersSliderX, parametersSliderY, enabled)) { lighting.shouldRender = true; } if (sliderHelper("Max Bounces", "Controls how many times rays can bounce", lighting.maxBounces, 0, 16, parametersSliderX, parametersSliderY, enabled)) { lighting.shouldRender = true; } if (buttonHelper("Global Illumination", "Enables global illumination", lighting.isDiffuseEnabled, -1.0f, settingsButtonY, enabled, enabled)) { lighting.shouldRender = true; } if (buttonHelper("Specular Reflections", "Enables specular reflections", lighting.isSpecularEnabled, -1.0f, settingsButtonY, enabled, enabled)) { lighting.shouldRender = true; } if (buttonHelper("Refraction", "Enables refraction", lighting.isRefractionEnabled, -1.0f, settingsButtonY, enabled, enabled)) { lighting.shouldRender = true; } if (buttonHelper("Dispersion", "Enables light dispersion with refraction", lighting.isDispersionEnabled, -1.0f, settingsButtonY, enabled, enabled)) { lighting.shouldRender = true; } if (buttonHelper("Emission", "Allows walls to emit light", lighting.isEmissionEnabled, -1.0f, settingsButtonY, enabled, enabled)) { lighting.shouldRender = true; } ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Misc. Settings"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Symmetrical Lens", "Makes both sides of the next lens editable. Hold LCTRL to move both sides at the same time", lighting.symmetricalLens, -1.0f, settingsButtonY, enabled, enabled); buttonHelper("Show Normals", "Displays the direction a wall is pointing at, also know as the normal", lighting.drawNormals, -1.0f, settingsButtonY, enabled, enabled); buttonHelper("Relax Shape When Moved", "Relaxes shapes when moving their walls. This is affected too by the relax sliders", lighting.relaxMove, -1.0f, settingsButtonY, enabled, enabled); } } ImGui::EndChild(); ImGui::End(); myParam.rightClickSettings.rightClickMenu(myVar, myParam); myParam.controls.showControls(); myParam.controls.showInfo(myVar.fullscreenState); ImVec2 statsSize = { 250.0f, myVar.isOpticsEnabled ? 230.0f : 120.0f }; if (lighting.selectedWalls > 0) { statsSize.y += 25.0f; } if (lighting.selectedLights > 0) { statsSize.y += 25.0f; } float statsPosX = screenX - statsSize.x - buttonsWindowX - 20.0f; ImGui::SetNextWindowSize(statsSize, ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenX - statsSize.x - buttonsWindowX - 20.0f, 0.0f), ImGuiCond_Always); ImGui::Begin("Stats", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); ImGui::PushFont(myVar.robotoMediumFont); ImGui::SetWindowFontScale(1.5f); int particlesAmout = static_cast(myParam.pParticles.size()) + static_cast(myParam.pParticles3D.size()); int selecParticlesAmout = static_cast(myParam.pParticlesSelected.size()) + static_cast(myParam.pParticlesSelected3D.size()); ImGui::TextColored(UpdateVariables::colMenuInformation, "%s%d", "Total Particles: ", particlesAmout); ImGui::TextColored(UpdateVariables::colMenuInformation, "%s%d", "Selected Particles: ", selecParticlesAmout); if (GetFPS() >= 60) { ImGui::TextColored(ImVec4(0.0f, 0.8f, 0.0f, 1.0f), "%s%d", "FPS: ", GetFPS()); } else if (GetFPS() < 60 && GetFPS() > 30) { ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.0f, 1.0f), "%s%d", "FPS: ", GetFPS()); } else { ImGui::TextColored(ImVec4(0.8f, 0.0f, 0.0f, 1.0f), "%s%d", "FPS: ", GetFPS()); } if (myVar.isOpticsEnabled) { ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "%s%d", "Total Walls: ", static_cast(lighting.walls.size())); if (lighting.selectedWalls > 0) { ImGui::TextColored(UpdateVariables::colButtonHover, "%s%d", "Selected Walls: ", lighting.selectedWalls); } ImGui::TextColored(UpdateVariables::colMenuInformation, "%s%d", "Total Lights: ", lighting.totalLights); if (lighting.selectedLights > 0) { ImGui::TextColored(UpdateVariables::colButtonHover, "%s%d", "Selected Lights: ", lighting.selectedLights); } ImGui::TextColored(UpdateVariables::colMenuInformation, "%s%d", "Total Rays: ", lighting.accumulatedRays); ImGui::Spacing(); float samplesPorgress = static_cast(lighting.currentSamples) / static_cast(lighting.maxSamples); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, UpdateVariables::colButtonHover); float progress = samplesPorgress; ImVec2 size = ImVec2(ImGui::GetContentRegionAvail().x, 22.0f); float radius = 8.0f; ImVec2 pos = ImGui::GetCursorScreenPos(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), ImGui::GetColorU32(ImVec4(0.2f, 0.2f, 0.2f, 1.0f)), radius); draw_list->AddRectFilled(pos, ImVec2(pos.x + size.x * progress, pos.y + size.y), ImGui::GetColorU32(UpdateVariables::colButtonHover), radius); char buffer[128]; snprintf(buffer, sizeof(buffer), "Samples %d / %d", lighting.currentSamples - 1, lighting.maxSamples); float fontScale = 0.85f; ImFont* font = ImGui::GetFont(); float fontSize = ImGui::GetFontSize() * fontScale; ImVec2 text_size = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, buffer); ImVec2 text_pos = ImVec2( pos.x + (size.x - text_size.x) * 0.5f, pos.y + (size.y - text_size.y) * 0.5f ); draw_list->AddText( font, fontSize, text_pos, ImGui::GetColorU32(ImVec4(1, 1, 1, 1)), buffer ); ImGui::Dummy(size); ImGui::PopStyleColor(); } ImGui::PopFont(); ImGui::End(); // Tools Menu // ImVec2 toolsSize = { 250.0f, 370.0f }; ImGui::SetNextWindowSize(toolsSize, ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(parametersWindowSizeX + 20.0f, 0.0f), ImGuiCond_Once); ImGui::Begin("Tools", nullptr); ImGui::BeginTabBar("##ToolsBar", ImGuiTabBarFlags_NoTabListScrollingButtons); struct ToolButton { const char* label; const char* tooltip; bool* flag; }; auto activateExclusiveTool = [](ToolButton* group, int count, int activeIndex) { for (int i = 0; i < count; ++i) { *group[i].flag = (i == activeIndex); } }; // Particle tab if (ImGui::BeginTabItem("Particle")) { ToolButton particleTools[] = { { "Draw Particles", "Draw particles with the brush", &myVar.toolDrawParticles }, { "Black Hole", "Throw a black hole particle", &myVar.toolSpawnHeavyParticle }, { "Galaxy", "Spawn a large galaxy", &myVar.toolSpawnGalaxy }, { "Star", "Spawn a small star. This is not meant for fluid mode", &myVar.toolSpawnStar }, { "Big Bang", "Spawn the Big Bang", &myVar.toolSpawnBigBang } }; for (int i = 0; i < IM_ARRAYSIZE(particleTools); ++i) { if (buttonHelper(particleTools[i].label, particleTools[i].tooltip, *particleTools[i].flag, -1.0f, settingsButtonY, enabled, enabled)) { activateExclusiveTool(particleTools, IM_ARRAYSIZE(particleTools), i); myVar.toolErase = false; myVar.toolRadialForce = false; myVar.toolSpin = false; myVar.toolMove = false; myVar.toolRaiseTemp = false; myVar.toolLowerTemp = false; myVar.toolPointLight = false; myVar.toolAreaLight = false; myVar.toolConeLight = false; myVar.toolCircle = false; myVar.toolDrawShape = false; myVar.toolLens = false; myVar.toolWall = false; myVar.toolMoveOptics = false; myVar.toolEraseOptics = false; myVar.toolSelectOptics = false; myVar.longExposureFlag = false; } } ImGui::EndTabItem(); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "ParticleAmount"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Visible P. Amount Multiplier", "Controls the spawn amount of visible particles", myVar.particleAmountMultiplier, 0.1f, 100.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("DM P. Amount Multiplier", "Controls the spawn amount of dark matter particles", myVar.DMAmountMultiplier, 0.1f, 100.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); bool isSPHDisabled = !myVar.isSPHEnabled; sliderHelper("Random Mass multiplier", "Controls how much mass can vary for each particle", myVar.massScatter, 0.0f, 1.0f, parametersSliderX, parametersSliderY, isSPHDisabled); buttonHelper("Mass Multiplier", "Decides if particles' masses should be inversely multiplied by the amount of particles multiplier", myParam.particlesSpawning.massMultiplierEnabled, 240.0f, 30.0f, true, isSPHDisabled); if (myVar.toolSpawnGalaxy) { if(!myVar.is3DMode){ ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Disk"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Galaxy Outer Radius", "Controls the outer limit of the galaxy", myParam.particlesSpawning.outerRadius, 10.0f, 500.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Galaxy Radius", "Controls the radius of the galaxy core", myParam.particlesSpawning.scaleLength, 10.0f, 500.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Dark Matter"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("DM Halo Size", "Controls the size of the galaxy dark matter halo", myParam.particlesSpawning.outerRadiusDM, 500.0f, 12000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("DM Halo Core Size", "Controls the size of the galaxy dark matter halo core", myParam.particlesSpawning.radiusCoreDM, 0.5f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); } else { ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Rotation"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Disk Rotation X", "Controls rotation of disk in the X axist", myParam.particlesSpawning3D.diskAxisX, 0.0f, 180.0f, parametersSliderX, parametersSliderY, enabled); sliderHelper("Disk Rotation Y", "Controls rotation of disk in the Y axist", myParam.particlesSpawning3D.diskAxisY, 0.0f, 180.0f, parametersSliderX, parametersSliderY, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Disk"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Galaxy Outer Radius", "Controls the outer limit of the galaxy", myParam.particlesSpawning3D.outerRadius, 10.0f, 500.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Galaxy Core Radius", "Controls the radius of the galaxy core", myParam.particlesSpawning3D.radiusCore, 0.1f, 700.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Galaxy Thickness", "Controls the thickness of the galaxy", myParam.particlesSpawning3D.diskThickness, 0.05f, 12.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Bulge"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Galaxy Bulge Size", "Controls the size of the galaxy central bulge", myParam.particlesSpawning3D.bulgeSize, 10.0f, 4000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Galaxy Bulge Thickness", "Controls the thickness of the galaxy central bulge", myParam.particlesSpawning3D.bulgeThickness, 0.5f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Dark Matter"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("DM Halo Size", "Controls the size of the galaxy dark matter halo", myParam.particlesSpawning3D.outerRadiusDM, 500.0f, 12000.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("DM Halo Core Size", "Controls the size of the galaxy dark matter halo core", myParam.particlesSpawning3D.radiusCoreDM, 0.5f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); } } if (myVar.toolSpawnHeavyParticle) { ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Black Hole"); ImGui::Separator(); ImGui::Spacing(); sliderHelper("Black Hole Init Mass", "Controls the mass of black holes when spawned", myVar.heavyParticleWeightMultiplier, 0.005f, 15.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); } } // Brush tab if (ImGui::BeginTabItem("Brush")) { ToolButton brushTools[] = { { "Eraser Brush", "Erase particles with the brush", &myVar.toolErase }, { "Gravity Brush", "Push particles away. Hold LCTRL to invert.", &myVar.toolRadialForce }, { "Spin Brush", "Spins particles. Hold LCTRL to invert.", &myVar.toolSpin }, { "Grab Brush", "Grab particles inside the brush", &myVar.toolMove }, { "Heat Brush", "Heats the particles inside the brush", &myVar.toolRaiseTemp }, { "Cool Brush", "Cools the particles inside the brush", &myVar.toolLowerTemp } }; for (int i = 0; i < IM_ARRAYSIZE(brushTools); ++i) { if (buttonHelper(brushTools[i].label, brushTools[i].tooltip, *brushTools[i].flag, -1.0f, settingsButtonY, enabled, enabled)) { activateExclusiveTool(brushTools, IM_ARRAYSIZE(brushTools), i); myVar.toolDrawParticles = false; myVar.toolSpawnHeavyParticle = false; myVar.toolSpawnGalaxy = false; myVar.toolSpawnStar = false; myVar.toolSpawnBigBang = false; myVar.toolPointLight = false; myVar.toolAreaLight = false; myVar.toolConeLight = false; myVar.toolCircle = false; myVar.toolDrawShape = false; myVar.toolLens = false; myVar.toolWall = false; myVar.toolMoveOptics = false; myVar.toolEraseOptics = false; myVar.toolSelectOptics = false; myVar.longExposureFlag = false; } } ImGui::EndTabItem(); sliderHelper("Gravity Brush Force", "Controls the force of the gravity brush", myVar.brushAttractForceMult, 0.01f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); sliderHelper("Spin Brush Force", "Controls the force of the spin brush", myVar.brushSpinForceMult, 0.01f, 10.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); } // Optics tab if (ImGui::BeginTabItem("Optics")) { ToolButton opticTools[] = { { "Point Light", "Spawn point light", &myVar.toolPointLight }, { "Area Light", "Spawn area light", &myVar.toolAreaLight }, { "Cone Light", "Spawn cone light", &myVar.toolConeLight }, { "Wall", "Spawn a wall", &myVar.toolWall }, { "Circle", "Spawn a circle", &myVar.toolCircle }, { "Draw Shape", "Draw a shape", &myVar.toolDrawShape }, { "Lens", "Spawn a lens", &myVar.toolLens }, { "Move", "Move optics elements inside the brush", &myVar.toolMoveOptics }, { "Erase", "Erase optics elements like walls and lights", &myVar.toolEraseOptics}, { "Select", "Select optics elements like walls and lights to modify them. LCTRL adds to selection. LALT removes from selection. LSHIFT selects entire shapes.", &myVar.toolSelectOptics} }; for (int i = 0; i < IM_ARRAYSIZE(opticTools); ++i) { if (buttonHelper(opticTools[i].label, opticTools[i].tooltip, *opticTools[i].flag, -1.0f, settingsButtonY, enabled, myVar.isOpticsEnabled)) { activateExclusiveTool(opticTools, IM_ARRAYSIZE(opticTools), i); myVar.toolDrawParticles = false; myVar.toolSpawnHeavyParticle = false; myVar.toolSpawnGalaxy = false; myVar.toolSpawnStar = false; myVar.toolSpawnBigBang = false; myVar.toolErase = false; myVar.toolRadialForce = false; myVar.toolSpin = false; myVar.toolMove = false; myVar.toolRaiseTemp = false; myVar.toolLowerTemp = false; myVar.longExposureFlag = false; } } ImGui::EndTabItem(); } // Fun tools tab if (ImGui::BeginTabItem("Fun")) { //ToolButton funTools[] = { // //}; //for (int i = 0; i < IM_ARRAYSIZE(funTools); ++i) { // if (buttonHelper(funTools[i].label, funTools[i].tooltip, *funTools[i].flag, -1.0f, settingsButtonY, enabled, enabled)) { // //activateExclusiveTool(funTools, IM_ARRAYSIZE(funTools), i); // myVar.toolDrawParticles = false; // myVar.toolSpawnHeavyParticle = false; // myVar.toolSpawnGalaxy = false; // myVar.toolSpawnStar = false; // myVar.toolSpawnBigBang = false; // myVar.toolErase = false; // myVar.toolRadialForce = false; // myVar.toolSpin = false; // myVar.toolMove = false; // myVar.toolRaiseTemp = false; // myVar.toolLowerTemp = false; // myVar.toolPointLight = false; // myVar.toolAreaLight = false; // myVar.toolConeLight = false; // myVar.toolCircle = false; // myVar.toolDrawShape = false; // myVar.toolLens = false; // myVar.toolWall = false; // myVar.toolMoveOptics = false; // myVar.toolEraseOptics = false; // myVar.toolSelectOptics = false; // } //} ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Long Exposure"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Long Exposure Duration", "Controls the duration of the long exposure shot", myVar.longExposureFlag, -1.0f, settingsButtonY, enabled, enabled); sliderHelper("Long Exposure Duration", "Controls the duration of the long exposure shot", myVar.longExposureDuration, 2, 1000, parametersSliderX, parametersSliderY, enabled); ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, ".PLY Export"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Export .ply File", "Exports particles to a .ply file", myVar.exportPlyFlag, -1.0f, settingsButtonY, true, enabled); buttonHelper("Export .ply Seq.", "Exports particles to a .ply file each frame, creating a .ply sequence", myVar.exportPlySeqFlag, -1.0f, settingsButtonY, true, enabled); if (myVar.plyFrameNumber != 0) { ImGui::TextColored(UpdateVariables::colMenuInformation, "%s%d", "Frames Exported: ", myVar.plyFrameNumber); } ImGui::Spacing(); ImGui::Separator(); ImGui::TextColored(UpdateVariables::colMenuInformation, "Spaceship"); ImGui::Separator(); ImGui::Spacing(); buttonHelper("Enable Spaceship", "Enables controlling particles", ship.isShipEnabled, -1.0f, settingsButtonY, true, enabled); buttonHelper("Ship Gas", "Enables gas particles coming from the ship when controlling particles", myVar.isShipGasEnabled, -1.0f, settingsButtonY, true, enabled); sliderHelper("Spaceship Acceleration", "Controls the acceleration of the spaceship when controlling particles",ship.acceleration, 1.0f, 16.0f, parametersSliderX, parametersSliderY, enabled, LogSlider); ImGui::EndTabItem(); } ImGui::EndTabBar(); ImGui::End(); myVar.loadDropDownMenus = false; } void UI::statsWindowLogic(UpdateParameters& myParam, UpdateVariables& myVar) { ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); //------ Performance ------// ImGui::TextColored(UpdateVariables::colMenuInformation, "Performance"); ImGui::Spacing(); float enablePausedPlot = 1.0f; plotLinesHelper(enablePausedPlot, "Framerate: ", graphHistoryLimit, ImGui::GetIO().Framerate, 0.0f, 144.0f, { 340.0f, 200.0f }); ImGui::Spacing(); plotLinesHelper(enablePausedPlot, "Frame Time: ", graphHistoryLimit, GetFrameTime(), 0.0f, 1.0f, { 340.0f, 200.0f }); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); //------ Particle Count ------// ImGui::TextColored(UpdateVariables::colMenuInformation, "Particle Count"); ImGui::Spacing(); int particlesAmout = static_cast(myParam.pParticles.size()) + static_cast(myParam.pParticles3D.size()); int selecParticlesAmout = static_cast(myParam.pParticlesSelected.size()) + static_cast(myParam.pParticlesSelected3D.size()); ImGui::Text("%s%d", "Total Particles: ", particlesAmout); ImGui::Text("%s%d", "Selected Particles: ", selecParticlesAmout); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); //------ Composition ------// ImGui::TextColored(UpdateVariables::colMenuInformation, "Composition"); ImGui::Spacing(); float waterAmount = 0.0f; float rockAmount = 0.0f; float ironAmount = 0.0f; float sandAmount = 0.0f; float soilAmount = 0.0f; float mudAmount = 0.0f; float rubberAmount = 0.0f; // This is not the ideal way to do it, but I'm using this for now because there are not many materials if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { ParticleRendering& r = myParam.rParticles[i]; if (myParam.pParticlesSelected.size() == 0) { if (r.sphLabel == 1) { waterAmount++; } else if (r.sphLabel == 2) { rockAmount++; } else if (r.sphLabel == 3) { ironAmount++; } else if (r.sphLabel == 4) { sandAmount++; } else if (r.sphLabel == 5) { soilAmount++; } else if (r.sphLabel == 6) { mudAmount++; } else if (r.sphLabel == 7) { rubberAmount++; } } else { if (r.sphLabel == 1 && r.isSelected) { waterAmount++; } else if (r.sphLabel == 2 && r.isSelected) { rockAmount++; } else if (r.sphLabel == 3 && r.isSelected) { ironAmount++; } else if (r.sphLabel == 4 && r.isSelected) { sandAmount++; } else if (r.sphLabel == 5 && r.isSelected) { soilAmount++; } else if (r.sphLabel == 6 && r.isSelected) { mudAmount++; } else if (r.sphLabel == 7 && r.isSelected) { rubberAmount++; } } } } else { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { ParticleRendering3D& r = myParam.rParticles3D[i]; if (myParam.pParticlesSelected3D.size() == 0) { if (r.sphLabel == 1) { waterAmount++; } else if (r.sphLabel == 2) { rockAmount++; } else if (r.sphLabel == 3) { ironAmount++; } else if (r.sphLabel == 4) { sandAmount++; } else if (r.sphLabel == 5) { soilAmount++; } else if (r.sphLabel == 6) { mudAmount++; } else if (r.sphLabel == 7) { rubberAmount++; } } else { if (r.sphLabel == 1 && r.isSelected) { waterAmount++; } else if (r.sphLabel == 2 && r.isSelected) { rockAmount++; } else if (r.sphLabel == 3 && r.isSelected) { ironAmount++; } else if (r.sphLabel == 4 && r.isSelected) { sandAmount++; } else if (r.sphLabel == 5 && r.isSelected) { soilAmount++; } else if (r.sphLabel == 6 && r.isSelected) { mudAmount++; } else if (r.sphLabel == 7 && r.isSelected) { rubberAmount++; } } } } std::vector labels; std::vector values; if (waterAmount > 0) { labels.push_back("Water"); values.push_back(waterAmount); } if (rockAmount > 0) { labels.push_back("Rock"); values.push_back(rockAmount); } if (ironAmount > 0) { labels.push_back("Iron"); values.push_back(ironAmount); } if (sandAmount > 0) { labels.push_back("Sand"); values.push_back(sandAmount); } if (soilAmount > 0) { labels.push_back("Soil"); values.push_back(soilAmount); } if (mudAmount > 0) { labels.push_back("Mud"); values.push_back(mudAmount); } if (rubberAmount > 0) { labels.push_back("Rubber"); values.push_back(rubberAmount); } if (!values.empty() && ImPlot::BeginPlot("Material Distribution", ImVec2(300, 300), ImPlotFlags_Equal)) { ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_Lock, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_Lock); ImPlot::SetupAxesLimits(-1, 1, -1, 1, ImGuiCond_Always); ImPlot::PlotPieChart(labels.data(), values.data(), static_cast(values.size()), 0.0, 0.0, 0.8); ImPlot::EndPlot(); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); //------ Mass ------// ImGui::TextColored(UpdateVariables::colMenuInformation, "Mass"); ImGui::Spacing(); double totalMass = 0.0; if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { totalMass += myParam.pParticles[i].mass; } } else { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { totalMass += myParam.pParticles3D[i].mass; } } ImGui::Text("Total Mass: %.2f", totalMass); double selectedMas = 0.0; if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { selectedMas += myParam.pParticles[i].mass; } } } else { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { selectedMas += myParam.pParticles3D[i].mass; } } } ImGui::Text("Selected Mass: %.2f", selectedMas); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); //------ Velocity ------// ImGui::TextColored(UpdateVariables::colMenuInformation, "Selected Velocity"); ImGui::Spacing(); glm::vec3 selectedVel = { 0.0f, 0.0f, 0.0f }; float totalVel = 0.0f; if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { selectedVel += glm::vec3{myParam.pParticles[i].vel, 0.0f}; } } if (myParam.pParticlesSelected.size() > 0) { selectedVel /= myParam.pParticlesSelected.size(); totalVel = sqrt(selectedVel.x * selectedVel.x + selectedVel.y * selectedVel.y); } } else { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { selectedVel += myParam.pParticles3D[i].vel; } } if (myParam.pParticlesSelected3D.size() > 0) { selectedVel /= myParam.pParticlesSelected3D.size(); totalVel = sqrt(selectedVel.x * selectedVel.x + selectedVel.y * selectedVel.y + selectedVel.z * selectedVel.z); } } plotLinesHelper(myVar.timeFactor, "Velocity X: ", graphHistoryLimit, selectedVel.x, -300.0f, 300.0f, graphDefaultSize); ImGui::Spacing(); plotLinesHelper(myVar.timeFactor, "Velocity Y: ", graphHistoryLimit, selectedVel.y, -300.0f, 300.0f, graphDefaultSize); ImGui::Spacing(); if (myVar.is3DMode) { plotLinesHelper(myVar.timeFactor, "Velocity Z: ", graphHistoryLimit, selectedVel.z, -300.0f, 300.0f, graphDefaultSize); ImGui::Spacing(); } plotLinesHelper(myVar.timeFactor, "Total Velocity: ", graphHistoryLimit, totalVel, -300.0f, 300.0f, graphDefaultSize); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); //------ Acceleration ------// ImGui::TextColored(UpdateVariables::colMenuInformation, "Selected Acceleration"); ImGui::Spacing(); glm::vec3 selectedAcc = { 0.0f, 0.0f, 0.0f }; float totalAcc = 0.0f; if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { selectedAcc += glm::vec3{ myParam.pParticles[i].acc, 0.0f }; } } if (myParam.pParticlesSelected.size() > 0) { selectedAcc /= myParam.pParticlesSelected.size(); totalAcc = sqrt(selectedAcc.x * selectedAcc.x + selectedAcc.y * selectedAcc.y); } } else { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { selectedAcc += myParam.pParticles3D[i].acc; } } if (myParam.pParticlesSelected3D.size() > 0) { selectedAcc /= myParam.pParticlesSelected3D.size(); totalAcc = sqrt(selectedAcc.x * selectedAcc.x + selectedAcc.y * selectedAcc.y + selectedAcc.z * selectedAcc.z); } } plotLinesHelper(myVar.timeFactor, "Acceleration X: ", graphHistoryLimit, selectedAcc.x, -300.0f, 300.0f, graphDefaultSize); ImGui::Spacing(); plotLinesHelper(myVar.timeFactor, "Acceleration Y: ", graphHistoryLimit, selectedAcc.y, -300.0f, 300.0f, graphDefaultSize); ImGui::Spacing(); if (myVar.is3DMode) { plotLinesHelper(myVar.timeFactor, "Acceleration Z: ", graphHistoryLimit, selectedAcc.z, -300.0f, 300.0f, graphDefaultSize); ImGui::Spacing(); } plotLinesHelper(myVar.timeFactor, "Total Acceleration: ", graphHistoryLimit, totalAcc, -300.0f, 300.0f, graphDefaultSize); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); //------ Pressure ------// ImGui::TextColored(UpdateVariables::colMenuInformation, "Selected Pressure"); ImGui::Spacing(); float totalPress = 0.0f; if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { totalPress += myParam.pParticles[i].press; } } if (myParam.pParticlesSelected.size() > 0) { totalPress /= myParam.pParticlesSelected.size(); } } else { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { totalPress += myParam.pParticles3D[i].press; } } if (myParam.pParticlesSelected3D.size() > 0) { totalPress /= myParam.pParticlesSelected3D.size(); } } plotLinesHelper(myVar.timeFactor, "Pressure: ", graphHistoryLimit, totalPress, 0.0f, 100.0f, graphDefaultSize); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); //------ Temperature ------// ImGui::TextColored(UpdateVariables::colMenuInformation, "Selected Temperature"); ImGui::Spacing(); float totalTemp = 0.0f; if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { totalTemp += myParam.pParticles[i].temp; } } if (myParam.pParticlesSelected.size() > 0) { totalTemp /= myParam.pParticlesSelected.size(); } } else { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { totalTemp += myParam.pParticles3D[i].temp; } } if (myParam.pParticlesSelected3D.size() > 0) { totalTemp /= myParam.pParticlesSelected3D.size(); } } plotLinesHelper(myVar.timeFactor, "Temperature: ", graphHistoryLimit, totalTemp, 0.0f, 100.0f, graphDefaultSize); } std::unordered_map UI::plotDataMap; void UI::plotLinesHelper(const float& timeFactor, std::string label, const int length, float value, const float minValue, const float maxValue, ImVec2 size) { auto& plotData = plotDataMap[label]; if (plotData.values.size() != length) { plotData.values.resize(length, 0.0f); plotData.offset = 0; } if (timeFactor > 0.0f) { plotData.values[plotData.offset] = value; plotData.offset = (plotData.offset + 1) % length; } std::vector ordered_values(length); std::vector ordered_x(length); for (int i = 0; i < length; ++i) { int idx = (plotData.offset + i) % length; ordered_values[i] = plotData.values[idx]; ordered_x[i] = static_cast(i); } if (ImPlot::BeginPlot(label.c_str(), size, ImPlotFlags_NoInputs)) { ImPlot::SetupAxis(ImAxis_Y1, nullptr, ImPlotAxisFlags_AutoFit); ImPlot::PushStyleColor(ImPlotCol_Line, UpdateVariables::colPlotLine); ImPlot::PushStyleColor(ImPlotCol_AxisText, UpdateVariables::colAxisText); ImPlot::PushStyleColor(ImPlotCol_AxisGrid, UpdateVariables::colAxisGrid); ImPlot::PushStyleColor(ImPlotCol_AxisBg, UpdateVariables::colAxisBg); ImPlot::PushStyleColor(ImPlotCol_FrameBg, UpdateVariables::colFrameBg); ImPlot::PushStyleColor(ImPlotCol_PlotBg, UpdateVariables::colPlotBg); ImPlot::PushStyleColor(ImPlotCol_PlotBorder, UpdateVariables::colPlotBorder); ImPlot::PushStyleColor(ImPlotCol_LegendBg, UpdateVariables::colLegendBg); ImPlot::PlotLine(label.c_str(), ordered_x.data(), ordered_values.data(), length); ImPlot::PopStyleColor(8); ImPlot::EndPlot(); } } static bool wasHovered = false; bool UI::buttonHelper(std::string label, std::string tooltip, bool& parameter, float sizeX, float sizeY, bool canSelfDeactivate, bool& isEnabled) { ImGuiID buttonId = ImGui::GetID(label.c_str()); static std::unordered_map hoverStates; if (!isEnabled) { ImGui::BeginDisabled(); } bool pushedColor = false; if (parameter) { ImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonActive); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonActiveHover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonActivePress); pushedColor = true; } bool hasBeenPressed = false; ImVec2 buttonSize; if (sizeX > 0.0f && sizeY > 0.0f) { buttonSize = ImVec2(sizeX, sizeY); } else if (sizeX < 0.0f && sizeY > 0.0f) { buttonSize = ImVec2(ImGui::GetContentRegionAvail().x, sizeY); } else if (sizeX > 0.0f && sizeY < 0.0f) { buttonSize = ImVec2(sizeX, ImGui::GetContentRegionAvail().y); } else { buttonSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y); } std::vector* soundPool = nullptr; if (ImGui::Button(label.c_str(), buttonSize)) { if (!parameter) { soundPool = &GESound::soundButtonEnablePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } else { soundPool = &GESound::soundButtonDisablePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } if (canSelfDeactivate) { parameter = !parameter; } else if (!parameter) { parameter = true; } hasBeenPressed = true; } if (pushedColor) { ImGui::PopStyleColor(3); } bool isHovered = ImGui::IsItemHovered(); if (isHovered) { ImGui::SetTooltip("%s", tooltip.c_str()); int randSoundNum = rand() % 3; if (!hoverStates[buttonId]) { std::vector* soundPool = nullptr; switch (randSoundNum) { case 0: soundPool = &GESound::soundButtonHover1Pool; break; case 1: soundPool = &GESound::soundButtonHover2Pool; break; case 2: soundPool = &GESound::soundButtonHover3Pool; break; } if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } } hoverStates[buttonId] = isHovered; if (!isEnabled) { ImGui::EndDisabled(); } return hasBeenPressed; } bool UI::sliderHelper(std::string label, std::string tooltip, float& parameter, float minVal, float maxVal, float sizeX, float sizeY, bool& isEnabled, int logarithmic) { bool isSliderUsed = false; ImGuiID sliderId = ImGui::GetID(label.c_str()); static std::unordered_map hoverStates; static std::unordered_map defaultValues; if (!isEnabled) { ImGui::BeginDisabled(); } if (defaultValues.find(sliderId) == defaultValues.end()) { defaultValues[sliderId] = parameter; } ImVec2 sliderSize; if (sizeX > 0.0f && sizeY > 0.0f) { sliderSize = ImVec2(sizeX, sizeY); } else if (sizeX < 0.0f && sizeY > 0.0f) { sliderSize = ImVec2(ImGui::GetContentRegionAvail().x, sizeY); } else if (sizeX > 0.0f && sizeY < 0.0f) { sliderSize = ImVec2(sizeX, ImGui::GetContentRegionAvail().y); } else { sliderSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y); } ImGui::Text("%s", label.c_str()); if (ImGui::SliderFloat(("##" + label).c_str(), ¶meter, minVal, maxVal, "%.3f", ImGuiSliderFlags_Logarithmic)) { isSliderUsed = true; } std::vector* soundPool = nullptr; static bool hasBeenPressed = false; if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { hasBeenPressed = true; soundPool = &GESound::soundButtonEnablePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } if (hasBeenPressed && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { soundPool = &GESound::soundButtonDisablePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } hasBeenPressed = false; } static float prevValue = parameter; static bool wasPlaying = false; static ImVec2 lastMousePos = ImGui::GetMousePos(); if (ImGui::IsItemActive()) { ImVec2 currentMousePos = ImGui::GetMousePos(); float mouseDelta = abs(currentMousePos.x - lastMousePos.x) + abs(currentMousePos.y - lastMousePos.y); if (mouseDelta > 2.0f) { if (!wasPlaying || parameter != prevValue) { soundPool = &GESound::soundSliderSlidePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; wasPlaying = true; lastMousePos = currentMousePos; break; } } if (!played) { PlaySound(soundPool->back()); wasPlaying = true; lastMousePos = currentMousePos; } } } } prevValue = parameter; } else { wasPlaying = false; } bool isHovered = ImGui::IsItemHovered(); if (isHovered) { ImGui::SetTooltip("%s", tooltip.c_str()); int randSoundNum = rand() % 3; if (!hoverStates[sliderId]) { std::vector* soundPool = nullptr; switch (randSoundNum) { case 0: soundPool = &GESound::soundButtonHover1Pool; break; case 1: soundPool = &GESound::soundButtonHover2Pool; break; case 2: soundPool = &GESound::soundButtonHover3Pool; break; } if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } } hoverStates[sliderId] = isHovered; if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { parameter = defaultValues[sliderId]; isSliderUsed = true; } if (!isEnabled) { ImGui::EndDisabled(); } return isSliderUsed; } bool UI::sliderHelper(std::string label, std::string tooltip, float& parameter, float minVal, float maxVal, float sizeX, float sizeY, bool& isEnabled) { bool isSliderUsed = false; ImGuiID sliderId = ImGui::GetID(label.c_str()); static std::unordered_map hoverStates; static std::unordered_map defaultValues; if (!isEnabled) { ImGui::BeginDisabled(); } if (defaultValues.find(sliderId) == defaultValues.end()) { defaultValues[sliderId] = parameter; } ImVec2 sliderSize; if (sizeX > 0.0f && sizeY > 0.0f) { sliderSize = ImVec2(sizeX, sizeY); } else if (sizeX < 0.0f && sizeY > 0.0f) { sliderSize = ImVec2(ImGui::GetContentRegionAvail().x, sizeY); } else if (sizeX > 0.0f && sizeY < 0.0f) { sliderSize = ImVec2(sizeX, ImGui::GetContentRegionAvail().y); } else { sliderSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y); } ImGui::Text("%s", label.c_str()); if (ImGui::SliderFloat(("##" + label).c_str(), ¶meter, minVal, maxVal, "%.3f")) { isSliderUsed = true; } std::vector* soundPool = nullptr; static bool hasBeenPressed = false; if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { hasBeenPressed = true; soundPool = &GESound::soundButtonEnablePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } if (hasBeenPressed && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { soundPool = &GESound::soundButtonDisablePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } hasBeenPressed = false; } static float prevValue = parameter; static bool wasPlaying = false; static ImVec2 lastMousePos = ImGui::GetMousePos(); if (ImGui::IsItemActive()) { ImVec2 currentMousePos = ImGui::GetMousePos(); float mouseDelta = abs(currentMousePos.x - lastMousePos.x) + abs(currentMousePos.y - lastMousePos.y); if (mouseDelta > 2.0f) { if (!wasPlaying || parameter != prevValue) { soundPool = &GESound::soundSliderSlidePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; wasPlaying = true; lastMousePos = currentMousePos; break; } } if (!played) { PlaySound(soundPool->back()); wasPlaying = true; lastMousePos = currentMousePos; } } } } prevValue = parameter; } else { wasPlaying = false; } bool isHovered = ImGui::IsItemHovered(); if (isHovered) { ImGui::SetTooltip("%s", tooltip.c_str()); int randSoundNum = rand() % 3; if (!hoverStates[sliderId]) { std::vector* soundPool = nullptr; switch (randSoundNum) { case 0: soundPool = &GESound::soundButtonHover1Pool; break; case 1: soundPool = &GESound::soundButtonHover2Pool; break; case 2: soundPool = &GESound::soundButtonHover3Pool; break; } if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } } hoverStates[sliderId] = isHovered; if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { parameter = defaultValues[sliderId]; isSliderUsed = true; } if (!isEnabled) { ImGui::EndDisabled(); } return isSliderUsed; } bool UI::sliderHelper(std::string label, std::string tooltip, int& parameter, int minVal, int maxVal, float sizeX, float sizeY, bool& isEnabled) { bool isSliderUsed = false; ImGuiID sliderId = ImGui::GetID(label.c_str()); static std::unordered_map hoverStates; static std::unordered_map defaultValues; if (!isEnabled) { ImGui::BeginDisabled(); } if (defaultValues.find(sliderId) == defaultValues.end()) { defaultValues[sliderId] = parameter; } ImVec2 sliderSize; if (sizeX > 0.0f && sizeY > 0.0f) { sliderSize = ImVec2(sizeX, sizeY); } else if (sizeX < 0.0f && sizeY > 0.0f) { sliderSize = ImVec2(ImGui::GetContentRegionAvail().x, sizeY); } else if (sizeX > 0.0f && sizeY < 0.0f) { sliderSize = ImVec2(sizeX, ImGui::GetContentRegionAvail().y); } else { sliderSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y); } ImGui::Text("%s", label.c_str()); if (ImGui::SliderInt(("##" + label).c_str(), ¶meter, minVal, maxVal)) { isSliderUsed = true; } std::vector* soundPool = nullptr; static bool hasBeenPressed = false; if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { hasBeenPressed = true; soundPool = &GESound::soundButtonEnablePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } if (hasBeenPressed && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { soundPool = &GESound::soundButtonDisablePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } hasBeenPressed = false; } static int prevValue = parameter; static bool wasPlaying = false; static ImVec2 lastMousePos = ImGui::GetMousePos(); if (ImGui::IsItemActive()) { ImVec2 currentMousePos = ImGui::GetMousePos(); int mouseDelta = abs(currentMousePos.x - lastMousePos.x) + abs(currentMousePos.y - lastMousePos.y); if (mouseDelta > 2.0f) { if (!wasPlaying || parameter != prevValue) { soundPool = &GESound::soundSliderSlidePool; if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; wasPlaying = true; lastMousePos = currentMousePos; break; } } if (!played) { PlaySound(soundPool->back()); wasPlaying = true; lastMousePos = currentMousePos; } } } } prevValue = parameter; } else { wasPlaying = false; } bool isHovered = ImGui::IsItemHovered(); if (isHovered) { ImGui::SetTooltip("%s", tooltip.c_str()); int randSoundNum = rand() % 3; if (!hoverStates[sliderId]) { std::vector* soundPool = nullptr; switch (randSoundNum) { case 0: soundPool = &GESound::soundButtonHover1Pool; break; case 1: soundPool = &GESound::soundButtonHover2Pool; break; case 2: soundPool = &GESound::soundButtonHover3Pool; break; } if (soundPool && !soundPool->empty()) { bool played = false; for (Sound& sound : *soundPool) { if (!IsSoundPlaying(sound)) { PlaySound(sound); played = true; break; } } if (!played) { PlaySound(soundPool->back()); } } } } hoverStates[sliderId] = isHovered; static int defaultVal = parameter; if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { parameter = defaultValues[sliderId]; isSliderUsed = true; } if (!isEnabled) { ImGui::EndDisabled(); } return isSliderUsed; } ================================================ FILE: GalaxyEngine/src/UI/brush.cpp ================================================ #include "UI/brush.h" #include "parameters.h" struct SPHWater water; struct SPHRock rock; struct SPHIron iron; struct SPHSand sand; struct SPHSoil soil; struct SPHMud mud; struct SPHRubber rubber; void Brush::brushLogic(UpdateParameters& myParam, bool& isSPHEnabled, bool& constraintAfterDrawing, float& massScatter, UpdateVariables& myVar) { // This entire function is a crime against programming and perhaps humanity as well. I don't know what destiny shall await for whoever reads such cursed code. // But I'm too lazy to change this for now. Will change it some time in the future if (!isSPHEnabled) { myVar.SPHWater = false; myVar.SPHRock = false; myVar.SPHIron = false; myVar.SPHSand = false; myVar.SPHSoil = false; myVar.SPHIce = false; myVar.SPHMud = false; myVar.SPHRubber = false; } if (!myVar.SPHWater && !myVar.SPHRock && !myVar.SPHSand && !myVar.SPHSoil && !myVar.SPHIce && !myVar.SPHMud && !myVar.SPHGas && !myVar.SPHIron && !myVar.SPHRubber) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; float rand01 = static_cast(rand()) / static_cast(RAND_MAX); float randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * massScatter; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = 8500000000.0f / myVar.particleAmountMultiplier * randomMassMultiplier; } else { finalMass = 8500000000.0f * randomMassMultiplier; } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, 0.008f, 1.0f, 1.0f, 1.0f); myParam.rParticles.emplace_back(Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0); if (isSPHEnabled) { myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } else { myParam.rParticles.back().spawnCorrectIter = 10000000; myParam.rParticles.back().isBeingDrawn = false; } } } if (isSPHEnabled) { if (myVar.SPHWater) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * water.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, water.restDens, water.stiff, water.visc, water.cohesion); myParam.rParticles.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id); myParam.rParticles.back().sphColor = water.color; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } if (myVar.SPHRock) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * rock.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * rock.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, rock.restDens, rock.stiff, rock.visc, rock.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (50.0f * normalRand) - 25.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles.emplace_back( Color{ addRandom(rock.color.r), addRandom(rock.color.g), addRandom(rock.color.b), rock.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, rock.id ); myParam.rParticles.back().sphColor = Color{ addRandom(rock.color.r), addRandom(rock.color.g), addRandom(rock.color.b), rock.color.a }; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } if (myVar.SPHIron) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * iron.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * iron.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, iron.restDens, iron.stiff, iron.visc, iron.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (40.0f * normalRand) - 20.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles.emplace_back( Color{ addRandom(iron.color.r), addRandom(iron.color.g), addRandom(iron.color.b), iron.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, iron.id ); myParam.rParticles.back().sphColor = Color{ addRandom(iron.color.r), addRandom(iron.color.g), addRandom(iron.color.b), iron.color.a }; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } if (myVar.SPHSand) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * sand.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * sand.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, sand.restDens, sand.stiff, sand.visc, sand.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (50.0f * normalRand) - 25.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles.emplace_back( Color{ addRandom(sand.color.r), addRandom(sand.color.g), addRandom(sand.color.b), sand.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, sand.id ); myParam.rParticles.back().sphColor = Color{ addRandom(sand.color.r), addRandom(sand.color.g), addRandom(sand.color.b), sand.color.a }; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } if (myVar.SPHSoil) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * soil.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * soil.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, soil.restDens, soil.stiff, soil.visc, soil.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (50.0f * normalRand) - 25.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles.emplace_back( Color{ addRandom(soil.color.r), addRandom(soil.color.g), addRandom(soil.color.b), soil.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, soil.id ); myParam.rParticles.back().sphColor = Color{ addRandom(soil.color.r), addRandom(soil.color.g), addRandom(soil.color.b), soil.color.a }; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } if (myVar.SPHIce) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * water.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, water.restDens, water.stiff, water.visc, water.cohesion); myParam.rParticles.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id); myParam.rParticles.back().sphColor = water.color; myParam.pParticles.back().temp = 1.0f; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } if (myVar.SPHMud) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * mud.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * mud.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, mud.restDens, mud.stiff, mud.visc, mud.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (50.0f * normalRand) - 25.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles.emplace_back( Color{ addRandom(mud.color.r), addRandom(mud.color.g), addRandom(mud.color.b), mud.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, mud.id ); myParam.rParticles.back().sphColor = Color{ addRandom(mud.color.r), addRandom(mud.color.g), addRandom(mud.color.b), mud.color.a }; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } if (myVar.SPHRubber) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * rubber.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * rubber.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, rubber.restDens, rubber.stiff, rubber.visc, rubber.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (40.0f * normalRand) - 20.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles.emplace_back( Color{ addRandom(rubber.color.r), addRandom(rubber.color.g), addRandom(rubber.color.b), rubber.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, rubber.id ); myParam.rParticles.back().sphColor = Color{ addRandom(rubber.color.r), addRandom(rubber.color.g), addRandom(rubber.color.b), rubber.color.a }; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } if (myVar.SPHGas) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float angle = static_cast(rand()) / static_cast(RAND_MAX) * 2.0f * 3.14159f; float distance = sqrt(static_cast(rand()) / static_cast(RAND_MAX)) * brushRadius; glm::vec2 randomOffset = { cos(angle) * distance, sin(angle) * distance }; glm::vec2 particlePos = myParam.myCamera.mouseWorldPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * water.massMult); } myParam.pParticles.emplace_back(particlePos, glm::vec2{ 0, 0 }, finalMass, water.restDens, water.stiff, water.visc, water.cohesion); myParam.rParticles.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id); myParam.rParticles.back().sphColor = water.color; myParam.pParticles.back().temp = 440.0f; myParam.rParticles.back().spawnCorrectIter = 0; myParam.rParticles.back().isBeingDrawn = true; } } } } void Brush::brushSize() { float wheel = GetMouseWheelMove(); if (IO::shortcutDown(KEY_LEFT_CONTROL) && wheel != 0) { float scale = 0.2f * wheel; brushRadius = Clamp(expf(logf(brushRadius) + scale), 2.5f, 512.0f); } } void Brush::drawBrush(glm::vec2 mouseWorldPos) { DrawCircleLinesV({ mouseWorldPos.x, mouseWorldPos.y }, brushRadius, WHITE); } void Brush::eraseBrush(UpdateVariables& myVar, UpdateParameters& myParam) { if ((IO::shortcutDown(KEY_X) && IO::mouseDown(2)) || IO::mouseDown(0) && myVar.toolErase) { for (size_t i = 0; i < myParam.pParticles.size();) { glm::vec2 distanceFromBrush = { myParam.pParticles[i].pos.x - myParam.myCamera.mouseWorldPos.x, myParam.pParticles[i].pos.y - myParam.myCamera.mouseWorldPos.y }; float distance = sqrt(distanceFromBrush.x * distanceFromBrush.x + distanceFromBrush.y * distanceFromBrush.y); if (distance < brushRadius) { std::swap(myParam.pParticles[i], myParam.pParticles.back()); std::swap(myParam.rParticles[i], myParam.rParticles.back()); myParam.pParticles.pop_back(); myParam.rParticles.pop_back(); } else { i++; } } } } void Brush::particlesAttractor(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutDown(KEY_B) || (IO::mouseDown(0) && myVar.toolRadialForce)) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { float dx = myParam.pParticles[i].pos.x - myParam.myCamera.mouseWorldPos.x; float dy = myParam.pParticles[i].pos.y - myParam.myCamera.mouseWorldPos.y; float distance = sqrt(dx * dx + dy * dy); float innerRadius = 0.0f; float outerRadius = brushRadius; float falloffFactor = 0.0f; if (distance > innerRadius) { falloffFactor = std::min(0.7f, (distance - innerRadius) / (outerRadius - innerRadius)); falloffFactor = falloffFactor * falloffFactor; } float radiusMultiplier = 0.0f; if (distance < 1.0f) { radiusMultiplier = 1.0f; } else { radiusMultiplier = distance; } float acceleration = static_cast(myVar.G * 600.0f * brushRadius * brushRadius) / (radiusMultiplier * radiusMultiplier); acceleration *= falloffFactor; attractorForce.x = static_cast(-(dx / radiusMultiplier) * acceleration * myParam.pParticles[i].mass); attractorForce.y = static_cast(-(dy / radiusMultiplier) * acceleration * myParam.pParticles[i].mass); if (IO::shortcutDown(KEY_LEFT_CONTROL)) { attractorForce = { -attractorForce.x, -attractorForce.y }; } myParam.pParticles[i].vel.x += attractorForce.x * myVar.timeFactor * myVar.brushAttractForceMult; myParam.pParticles[i].vel.y += attractorForce.y * myVar.timeFactor * myVar.brushAttractForceMult; } } } void Brush::particlesSpinner(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutDown(KEY_N) || (IO::mouseDown(0) && myVar.toolSpin)) { for (auto& pParticle : myParam.pParticles) { glm::vec2 distanceFromBrush = { pParticle.pos.x - myParam.myCamera.mouseWorldPos.x, pParticle.pos.y - myParam.myCamera.mouseWorldPos.y }; float distance = sqrt(distanceFromBrush.x * distanceFromBrush.x + distanceFromBrush.y * distanceFromBrush.y); if (distance < brushRadius) { float falloff = distance / brushRadius; falloff = powf(falloff, 2.0f); float inverseDistance = 1.0f / (distance + myVar.softening); glm::vec2 radialDirection = { distanceFromBrush.x * inverseDistance, distanceFromBrush.y * inverseDistance }; glm::vec2 spinDirection = { -radialDirection.y, radialDirection.x }; if (IO::shortcutDown(KEY_LEFT_CONTROL)) { spinDirection = { -spinDirection.x, -spinDirection.y }; } pParticle.vel.x += spinDirection.x * spinForce * falloff * myVar.timeFactor * myVar.brushSpinForceMult; pParticle.vel.y += spinDirection.y * spinForce * falloff * myVar.timeFactor * myVar.brushSpinForceMult; } } } } void Brush::particlesGrabber(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec2 mouseDelta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y); glm::vec2 scaledDelta = mouseDelta * (1.0f / myParam.myCamera.camera.zoom); lastMouseVelocity = scaledDelta; if (IO::shortcutPress(KEY_M) || (IO::mousePress(0) && myVar.toolMove)) { dragging = true; for (size_t i = 0; i < myParam.pParticles.size(); i++) { glm::vec2 distanceFromBrush = { myParam.pParticles[i].pos - myParam.myCamera.mouseWorldPos }; float distance = sqrt(distanceFromBrush.x * distanceFromBrush.x + distanceFromBrush.y * distanceFromBrush.y); if (distance < brushRadius) { myParam.rParticles[i].isGrabbed = true; myParam.rParticles[i].turbulence = 0.0f; } } } if (dragging) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isGrabbed) { myParam.pParticles[i].pos.x += scaledDelta.x; myParam.pParticles[i].pos.y += scaledDelta.y; myParam.pParticles[i].acc = { 0.0f, 0.0f }; myParam.pParticles[i].vel = { 0.0f, 0.0f }; myParam.pParticles[i].prevVel = { 0.0f, 0.0f }; } } } if (IO::shortcutReleased(KEY_M) || (IO::mouseReleased(0) && myVar.toolMove)) { float impulseFactor = 5.0f; for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isGrabbed) { myParam.pParticles[i].vel = lastMouseVelocity * impulseFactor; myParam.pParticles[i].prevVel = myParam.pParticles[i].vel; myParam.rParticles[i].isGrabbed = false; } } dragging = false; } } void Brush::temperatureBrush(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutDown(KEY_K) || IO::shortcutDown(KEY_L) || (IO::mouseDown(0) && myVar.toolRaiseTemp) || (IO::mouseDown(0) && myVar.toolLowerTemp)) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { glm::vec2 distanceFromBrush = { myParam.pParticles[i].pos - myParam.myCamera.mouseWorldPos }; float distance = sqrt(distanceFromBrush.x * distanceFromBrush.x + distanceFromBrush.y * distanceFromBrush.y); if (IO::shortcutDown(KEY_K) || (IO::mouseDown(0) && myVar.toolRaiseTemp)) { if (distance < brushRadius) { myParam.pParticles[i].temp += 40.0f; } } if (IO::shortcutDown(KEY_L) || (IO::mouseDown(0) && myVar.toolLowerTemp)) { if (distance < brushRadius && myParam.pParticles[i].temp > 1.0f) { myParam.pParticles[i].temp -= 40.0f; } } } } } // ---- 3D IMPLEMENTATION ---- // void Brush3D::brushLogic(UpdateParameters& myParam, bool& isSPHEnabled, bool& constraintAfterDrawing, float& massScatter, UpdateVariables& myVar) { if (!isSPHEnabled) { myVar.SPHWater = false; myVar.SPHRock = false; myVar.SPHIron = false; myVar.SPHSand = false; myVar.SPHSoil = false; myVar.SPHIce = false; myVar.SPHMud = false; myVar.SPHRubber = false; } if (!myVar.SPHWater && !myVar.SPHRock && !myVar.SPHSand && !myVar.SPHSoil && !myVar.SPHIce && !myVar.SPHMud && !myVar.SPHGas && !myVar.SPHIron && !myVar.SPHRubber) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; float rand01 = static_cast(rand()) / RAND_MAX; float randomMassMultiplier = 1.0f + (rand01 * 2.0f - 1.0f) * 0.8f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = 8500000000.0f / myVar.particleAmountMultiplier * randomMassMultiplier; } else { finalMass = 8500000000.0f * randomMassMultiplier; } myParam.pParticles3D.emplace_back( particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, 0.008f, 1.0f, 1.0f, 1.0f ); myParam.rParticles3D.emplace_back( Color{ 128, 128, 128, 100 }, 0.125f, false, false, false, true, true, false, true, -1.0f, 0 ); if (isSPHEnabled) { myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } else { myParam.rParticles3D.back().spawnCorrectIter = 10000000; myParam.rParticles3D.back().isBeingDrawn = false; } } } if (isSPHEnabled) { if (myVar.SPHWater) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * water.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, water.restDens, water.stiff, water.visc, water.cohesion); myParam.rParticles3D.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id); myParam.rParticles3D.back().sphColor = water.color; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } if (myVar.SPHRock) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * rock.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * rock.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, rock.restDens, rock.stiff, rock.visc, rock.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (50.0f * normalRand) - 25.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles3D.emplace_back( Color{ addRandom(rock.color.r), addRandom(rock.color.g), addRandom(rock.color.b), rock.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, rock.id ); myParam.rParticles3D.back().sphColor = Color{ addRandom(rock.color.r), addRandom(rock.color.g), addRandom(rock.color.b), rock.color.a }; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } if (myVar.SPHIron) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * iron.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * iron.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, iron.restDens, iron.stiff, iron.visc, iron.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (40.0f * normalRand) - 20.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles3D.emplace_back( Color{ addRandom(iron.color.r), addRandom(iron.color.g), addRandom(iron.color.b), iron.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, iron.id ); myParam.rParticles3D.back().sphColor = Color{ addRandom(iron.color.r), addRandom(iron.color.g), addRandom(iron.color.b), iron.color.a }; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } if (myVar.SPHSand) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * sand.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * sand.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, sand.restDens, sand.stiff, sand.visc, sand.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (50.0f * normalRand) - 25.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles3D.emplace_back( Color{ addRandom(sand.color.r), addRandom(sand.color.g), addRandom(sand.color.b), sand.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, sand.id ); myParam.rParticles3D.back().sphColor = Color{ addRandom(sand.color.r), addRandom(sand.color.g), addRandom(sand.color.b), sand.color.a }; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } if (myVar.SPHSoil) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * soil.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * soil.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, soil.restDens, soil.stiff, soil.visc, soil.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (50.0f * normalRand) - 25.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles3D.emplace_back( Color{ addRandom(soil.color.r), addRandom(soil.color.g), addRandom(soil.color.b), soil.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, soil.id ); myParam.rParticles3D.back().sphColor = Color{ addRandom(soil.color.r), addRandom(soil.color.g), addRandom(soil.color.b), soil.color.a }; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } if (myVar.SPHIce) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * water.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.f }, finalMass, water.restDens, water.stiff, water.visc, water.cohesion); myParam.rParticles3D.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id); myParam.rParticles3D.back().sphColor = water.color; myParam.pParticles3D.back().temp = 1.0f; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } if (myVar.SPHMud) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * mud.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * mud.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, mud.restDens, mud.stiff, mud.visc, mud.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (50.0f * normalRand) - 25.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles3D.emplace_back( Color{ addRandom(mud.color.r), addRandom(mud.color.g), addRandom(mud.color.b), mud.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, mud.id ); myParam.rParticles3D.back().sphColor = Color{ addRandom(mud.color.r), addRandom(mud.color.g), addRandom(mud.color.b), mud.color.a }; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } if (myVar.SPHRubber) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * rubber.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * rubber.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, rubber.restDens, rubber.stiff, rubber.visc, rubber.cohesion); float normalRand = static_cast(rand()) / static_cast(RAND_MAX); auto addRandom = [&](unsigned char c) -> unsigned char { float value = static_cast(c) + (40.0f * normalRand) - 20.0f; value = std::clamp(value, 0.0f, 255.0f); return static_cast(value); }; myParam.rParticles3D.emplace_back( Color{ addRandom(rubber.color.r), addRandom(rubber.color.g), addRandom(rubber.color.b), rubber.color.a }, 0.125f, false, false, false, true, true, false, true, -1.0f, rubber.id ); myParam.rParticles3D.back().sphColor = Color{ addRandom(rubber.color.r), addRandom(rubber.color.g), addRandom(rubber.color.b), rubber.color.a }; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } if (myVar.SPHGas) { for (int i = 0; i < static_cast(140 * myVar.particleAmountMultiplier); i++) { float theta = static_cast(rand()) / RAND_MAX * 2.0f * 3.14159265f; float phi = acosf(1.0f - 2.0f * (static_cast(rand()) / RAND_MAX)); float distance = sqrtf(static_cast(rand()) / RAND_MAX) * brushRadius; glm::vec3 randomOffset = { sinf(phi) * cosf(theta) * distance, sinf(phi) * sinf(theta) * distance, cosf(phi) * distance }; glm::vec3 particlePos = brushPos + randomOffset; float finalMass = 0.0f; if (myParam.particlesSpawning.massMultiplierEnabled) { finalMass = (8500000000.0f * water.massMult) / myVar.particleAmountMultiplier; } else { finalMass = (8500000000.0f * water.massMult); } myParam.pParticles3D.emplace_back(particlePos, glm::vec3{ 0.0f, 0.0f, 0.0f }, finalMass, water.restDens, water.stiff, water.visc, water.cohesion); myParam.rParticles3D.emplace_back(water.color, 0.125f, false, false, false, true, true, false, true, -1.0f, water.id); myParam.rParticles3D.back().sphColor = water.color; myParam.pParticles3D.back().temp = 440.0f; myParam.rParticles3D.back().spawnCorrectIter = 0; myParam.rParticles3D.back().isBeingDrawn = true; } } } } void Brush3D::brushSize() { float wheel = GetMouseWheelMove(); if (IO::shortcutDown(KEY_LEFT_CONTROL) && wheel != 0) { float scale = 0.2f * wheel; brushRadius = Clamp(expf(logf(brushRadius) + scale), 2.5f, 512.0f); } } glm::vec3 Brush3D::brushPosLogic(UpdateParameters& myParam, UpdateVariables& myVar) { Vector2 mousePos = GetMousePosition(); Ray mouseRay = GetScreenToWorldRay(mousePos, myParam.myCamera3D.cam3D); float wheel = GetMouseWheelMove(); if (IO::shortcutDown(KEY_LEFT_SHIFT) && wheel != 0) { float scale = 0.2f * wheel; spawnDistance = Clamp(expf(logf(spawnDistance) + scale), 64.0f, 5000.0f); } brushPos = { mouseRay.position.x + mouseRay.direction.x * spawnDistance, mouseRay.position.y + mouseRay.direction.y * spawnDistance, mouseRay.position.z + mouseRay.direction.z * spawnDistance }; if (!myParam.pParticles3D.empty() && !myVar.isBrushDrawing && !dragging && (!IO::shortcutDown(KEY_B) || (!IO::mouseDown(0) && myVar.toolRadialForce)) && (!IO::shortcutDown(KEY_N) || (!IO::mouseDown(0) && myVar.toolSpin))) { ClusterHelper::clusterMouseHelper(myParam.myCamera3D.cam3D, spawnDistance); } return brushPos; } void Brush3D::drawBrush(float& domainHeight) { DrawSphere({ brushPos.x, brushPos.y, brushPos.z }, brushRadius, { 12, 82, 172, 50 }); float domainBottomY = -domainHeight * 0.5f; DrawLine3D( { brushPos.x, brushPos.y, brushPos.z }, { brushPos.x, domainBottomY, brushPos.z }, { 12, 82, 172, 140 } ); DrawLine3D( { brushPos.x, brushPos.y, brushPos.z }, { brushPos.x + 10.0f, brushPos.y, brushPos.z }, RED ); DrawLine3D( { brushPos.x, brushPos.y, brushPos.z }, { brushPos.x, brushPos.y + 10.0f, brushPos.z }, GREEN ); DrawLine3D( { brushPos.x, brushPos.y, brushPos.z }, { brushPos.x, brushPos.y, brushPos.z + 10.0f }, BLUE ); } void Brush3D::particlesGrabber(UpdateVariables& myVar, UpdateParameters& myParam) { glm::vec3 brushDelta = brushPos - lastBrushPos; lastBrushVelocity = brushDelta; if (IO::shortcutPress(KEY_M) || (IO::mousePress(0) && myVar.toolMove)) { dragging = true; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { float dist = glm::distance(myParam.pParticles3D[i].pos, brushPos); if (dist < brushRadius) { myParam.rParticles3D[i].isGrabbed = true; myParam.rParticles3D[i].turbulence = 0.0f; } } } if (dragging) { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isGrabbed) { myParam.pParticles3D[i].pos += brushDelta; myParam.pParticles3D[i].acc = { 0.0f, 0.0f, 0.0f }; myParam.pParticles3D[i].vel = { 0.0f, 0.0f, 0.0f }; myParam.pParticles3D[i].prevVel = { 0.0f, 0.0f, 0.0f }; } } } if (IO::shortcutReleased(KEY_M) || (IO::mouseReleased(0) && myVar.toolMove)) { float impulseFactor = 2.0f; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isGrabbed) { myParam.pParticles3D[i].vel = lastBrushVelocity * impulseFactor; myParam.pParticles3D[i].prevVel = myParam.pParticles3D[i].vel; myParam.rParticles3D[i].isGrabbed = false; } } dragging = false; } lastBrushPos = brushPos; } void Brush3D::eraseBrush(UpdateVariables& myVar, UpdateParameters& myParam) { if ((IO::shortcutDown(KEY_X) && IO::mouseDown(2)) || IO::mouseDown(0) && myVar.toolErase) { for (size_t i = 0; i < myParam.pParticles3D.size();) { glm::vec3 distanceFromBrush = myParam.pParticles3D[i].pos - brushPos; float distance = sqrt(distanceFromBrush.x * distanceFromBrush.x + distanceFromBrush.y * distanceFromBrush.y + distanceFromBrush.z * distanceFromBrush.z); if (distance < brushRadius) { std::swap(myParam.pParticles3D[i], myParam.pParticles3D.back()); std::swap(myParam.rParticles3D[i], myParam.rParticles3D.back()); myParam.pParticles3D.pop_back(); myParam.rParticles3D.pop_back(); } else { i++; } } } } void Brush3D::temperatureBrush(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutDown(KEY_K) || IO::shortcutDown(KEY_L) || (IO::mouseDown(0) && myVar.toolRaiseTemp) || (IO::mouseDown(0) && myVar.toolLowerTemp)) { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { glm::vec3 distanceFromBrush = { myParam.pParticles3D[i].pos - brushPos }; float distance = sqrt(distanceFromBrush.x * distanceFromBrush.x + distanceFromBrush.y * distanceFromBrush.y + distanceFromBrush.z * distanceFromBrush.z); if (IO::shortcutDown(KEY_K) || (IO::mouseDown(0) && myVar.toolRaiseTemp)) { if (distance < brushRadius) { myParam.pParticles3D[i].temp += 40.0f; } } if (IO::shortcutDown(KEY_L) || (IO::mouseDown(0) && myVar.toolLowerTemp)) { if (distance < brushRadius && myParam.pParticles3D[i].temp > 1.0f) { myParam.pParticles3D[i].temp -= 40.0f; } } } } } void Brush3D::particlesAttractor(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutDown(KEY_B) || (IO::mouseDown(0) && myVar.toolRadialForce)) { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { glm::vec3 d = myParam.pParticles3D[i].pos - brushPos; float distance = glm::length(d); float innerRadius = 0.0f; float outerRadius = brushRadius; float falloffFactor = 0.0f; if (distance > innerRadius) { falloffFactor = std::min(0.7f, (distance - innerRadius) / (outerRadius - innerRadius)); falloffFactor = falloffFactor * falloffFactor; } float radiusMultiplier = 0.0f; if (distance < 1.0f) { radiusMultiplier = 1.0f; } else { radiusMultiplier = distance; } float acceleration = static_cast(myVar.G * 600.0f * brushRadius * brushRadius) / (radiusMultiplier * radiusMultiplier); acceleration *= falloffFactor; attractorForce.x = static_cast(-(d.x / radiusMultiplier) * acceleration * myParam.pParticles3D[i].mass); attractorForce.y = static_cast(-(d.y / radiusMultiplier) * acceleration * myParam.pParticles3D[i].mass); attractorForce.z = static_cast(-(d.z / radiusMultiplier) * acceleration * myParam.pParticles3D[i].mass); if (IO::shortcutDown(KEY_LEFT_CONTROL)) { attractorForce = -attractorForce; } myParam.pParticles3D[i].vel += attractorForce * myVar.timeFactor * myVar.brushAttractForceMult; } } } void Brush3D::particlesSpinner(UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutDown(KEY_N) || (IO::mouseDown(0) && myVar.toolSpin)) { glm::vec3 cameraUp = { myParam.myCamera3D.cam3D.up.x,myParam.myCamera3D.cam3D.up.y, myParam.myCamera3D.cam3D.up.z }; glm::vec3 cameraTarget = { myParam.myCamera3D.cam3D.target.x, myParam.myCamera3D.cam3D.target.y, myParam.myCamera3D.cam3D.target.z }; glm::vec3 cameraPos = { myParam.myCamera3D.cam3D.position.x, myParam.myCamera3D.cam3D.position.y, myParam.myCamera3D.cam3D.position.z }; glm::vec3 cameraForward = glm::normalize(cameraTarget - cameraPos); glm::vec3 cameraRight = glm::normalize(glm::cross(cameraForward, cameraUp)); for (auto& pParticle : myParam.pParticles3D) { glm::vec3 distanceFromBrush = pParticle.pos - brushPos; float screenX = glm::dot(distanceFromBrush, cameraRight); float screenY = glm::dot(distanceFromBrush, cameraUp); float distance = sqrt(screenX * screenX + screenY * screenY); if (distance < brushRadius) { float falloff = distance / brushRadius; falloff = powf(falloff, 2.0f); float inverseDistance = 1.0f / (distance + myVar.softening); glm::vec2 radialScreen = glm::vec2(screenX, screenY) * inverseDistance; glm::vec2 spinScreen = glm::vec2(-radialScreen.y, radialScreen.x); glm::vec3 spinDirection = spinScreen.x * cameraRight + spinScreen.y * cameraUp; if (IO::shortcutDown(KEY_LEFT_CONTROL)) { spinDirection = -spinDirection; } pParticle.vel += spinDirection * spinForce * falloff * myVar.timeFactor * myVar.brushSpinForceMult; } } } } ================================================ FILE: GalaxyEngine/src/UI/controls.cpp ================================================ #include "UI/controls.h" #include "UI/UI.h" #include "parameters.h" void Controls::showControls() { if (isShowControlsEnabled) { float screenW = static_cast(GetScreenWidth()); float screenH = static_cast(GetScreenHeight()); ImVec2 controlSize = { screenW * 0.15f, screenH * 0.4f }; controlSize.x = std::clamp(controlSize.x, 400.0f, 2000.0f); controlSize.y = std::clamp(controlSize.y, 600.0f, 2000.0f); ImGui::SetNextWindowSize(controlSize, ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - controlSize.x * 0.5f, screenH * 0.5f - controlSize.y * 0.5f), ImGuiCond_Appearing); ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_NoCollapse); for (size_t i = 0; i < controlsArray.size(); i++) { std::string text = controlsArray[i]; /* float windowWidth = ImGui::GetWindowSize().x; float textWidth = ImGui::CalcTextSize(text.c_str()).x; ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);*/ ImGui::Text("%s", text.c_str()); } ImGui::End(); } } void Controls::showInfo(bool& fullscreen) { if (isInformationEnabled) { float screenW = static_cast(GetScreenWidth()); float screenH = static_cast(GetScreenHeight()); ImVec2 infoSize = { screenW * 0.3f, screenH * 0.4f }; infoSize.x = std::clamp(infoSize.x, 700.0f, 2000.0f); infoSize.y = std::clamp(infoSize.y, 600.0f, 2000.0f); ImGui::SetNextWindowSize(infoSize, ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - infoSize.x * 0.5f, screenH * 0.5f - infoSize.y * 0.5f), ImGuiCond_Appearing); ImGui::Begin("Information", nullptr, ImGuiWindowFlags_NoCollapse); for (size_t i = 0; i < infoArray.size(); i++) { std::string text = infoArray[i]; /* float windowWidth = ImGui::GetWindowSize().x; float textWidth = ImGui::CalcTextSize(text.c_str()).x; ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f);*/ ImGui::Text("%s", text.c_str()); } ImVec2 linkButtonSize= { 200.0f, 30.0f }; ImGui::TextColored(ImVec4(0.8f, 0.0f, 0.0f, 1.0f), "The links might not work on Linux"); bool placeHolder = false; bool enabled = true; if (UI::buttonHelper("GitHub Repository", "Go to GitHub", placeHolder, linkButtonSize.x, linkButtonSize.y, enabled, enabled)) { fullscreen = false; OpenURL("https://github.com/NarcisCalin/Galaxy-Engine"); } ImGui::SameLine(); if (UI::buttonHelper("Join Our Discord Community!", "Click to join!", placeHolder, linkButtonSize.x, linkButtonSize.y, enabled, enabled)) { fullscreen = false; OpenURL("https://discord.gg/Xd5JUqNFPM"); } ImGui::SameLine(); if (UI::buttonHelper("Soundtrack by HAVA", "Check their work!", placeHolder, linkButtonSize.x, linkButtonSize.y, enabled, enabled)) { fullscreen = false; OpenURL("https://open.spotify.com/artist/1vrrvzYvRY27SDZp7WsMwx"); } ImGui::End(); } } ================================================ FILE: GalaxyEngine/src/UI/rightClickSettings.cpp ================================================ #include "UI/rightClickSettings.h" #include "UI/UI.h" #include "parameters.h" void RightClickSettings::rightClickMenuSpawnLogic(bool& isMouseNotHoveringUI, bool& isSpawningAllowed, bool& isDragging, bool& selectedColor) { static bool isMouseMoving = false; static glm::vec2 dragStartPos = { 0.0f, 0.0f }; static bool spawnBlocked = false; if (IsMouseButtonPressed(1) && isMouseNotHoveringUI) { dragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); isMouseMoving = false; } if (IsMouseButtonDown(1) && isMouseNotHoveringUI) { glm::vec2 cur = glm::vec2(GetMousePosition().x, GetMousePosition().y); glm::vec2 d = cur - dragStartPos; if (d.x * d.x + d.y * d.y > 5.0f * 5.0f) isMouseMoving = true; } if (IsMouseButtonPressed(1)) { isMenuActive = false; } if (IsMouseButtonReleased(1) && !IsKeyDown(KEY_LEFT_CONTROL) && !IsKeyDown(KEY_LEFT_ALT) && !isMouseMoving && isMouseNotHoveringUI && !IsMouseButtonDown(0)) { isMenuActive = true; spawnBlocked = false; selectedColorOriginal = selectedColor; } if (IsMouseButtonPressed(0) && isMouseNotHoveringUI && isMenuActive && !isMouseOnMenu) { isMenuActive = false; isSpawningAllowed = false; isDragging = false; spawnBlocked = true; selectedColor = selectedColorOriginal; selectedColorChanged = false; } else if (IsMouseButtonPressed(0) && isMouseNotHoveringUI && !isMenuActive && spawnBlocked) { isSpawningAllowed = true; spawnBlocked = false; } } void RightClickSettings::rightClickMenu(UpdateVariables& myVar, UpdateParameters& myParam) { rightClickMenuSpawnLogic(myVar.isMouseNotHoveringUI, myVar.isSpawningAllowed, myVar.isDragging, myParam.colorVisuals.selectedColor); if (isMenuActive) { ImGui::SetNextWindowSize(ImVec2(200.0f, 425.0f), ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(GetMousePosition().x, GetMousePosition().y), ImGuiCond_Appearing); if (ImGui::Begin("Right Click Menu", nullptr, ImGuiWindowFlags_NoCollapse)) { bool hovered = ImGui::IsWindowHovered( ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem ); isMouseOnMenu = hovered; } bool hovered = ImGui::IsWindowHovered( ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem ); bool enabled = true; if (UI::buttonHelper("Subdivide All", "Subdivide all normal particles", myParam.subdivision.subdivideAll, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Subdivide Selected", "Subdivide all selected normal particles", myParam.subdivision.subdivideSelected, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Constraint Solids", "Adds constraints to all current solid particles", myVar.constraintAllSolids, -1.0f, buttonSizeY, enabled, myVar.constraintsEnabled)) { isMenuActive = false; } if (UI::buttonHelper("Constraint Selected", "Adds constraints to selected solid particles", myVar.constraintSelected, -1.0f, buttonSizeY, enabled, myVar.constraintsEnabled)) { isMenuActive = false; } if (UI::buttonHelper("Delete All Constraints", "Deletes all current constraints", myVar.deleteAllConstraints, -1.0f, buttonSizeY, enabled, myVar.constraintsEnabled)) { isMenuActive = false; } if (UI::buttonHelper("Del. Selec. Constraints", "Deletes all selected constraints", myVar.deleteSelectedConstraints, -1.0f, buttonSizeY, enabled, myVar.constraintsEnabled)) { isMenuActive = false; } if (UI::buttonHelper("Pin Selected", "Pins selected particles", myVar.pinFlag, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Unpin Selected", "Unpins selected particles", myVar.unPinFlag, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Invert Particle Selec.", "Invert the particle selection", myParam.particleSelection.invertParticleSelection, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Deselect All", "Deselects all particles", myParam.particleSelection.deselectParticles, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; myParam.particleSelection3D.deselectParticles = true; } if (UI::buttonHelper("Follow selection", "Make the camera follow the selected particles", myParam.myCamera.centerCamera, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Select Clusters", "Selects multiple clusters of particles", myParam.particleSelection.selectManyClusters, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; myParam.particleSelection3D.selectManyClusters3D = true; } if (UI::buttonHelper("Delete Selection", "Deletes selected particles", myParam.particleDeletion.deleteSelection, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Delete Stray Particles", "Deletes all particles that are not in groups", myParam.particleDeletion.deleteNonImportant, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Draw Z-Curves", "Display the particles indices, sorted by Z-Curves", myVar.drawZCurves, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } if (UI::buttonHelper("Draw Quadtree", "Display Barnes-Hut algorithm quadtree", myVar.drawQuadtree, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } bool framesExportButtonEnabled = true; bool safeModeButtonEnabled = false; if (myVar.isRecording) { framesExportButtonEnabled = false; safeModeButtonEnabled = false; } if (!myParam.screenCapture.isExportFramesEnabled) { safeModeButtonEnabled = false; } else if(myParam.screenCapture.isExportFramesEnabled && !myVar.isRecording) { safeModeButtonEnabled = true; } if (UI::buttonHelper("Enable Frames Export", "Exports recorded frames to disk", myParam.screenCapture.isExportFramesEnabled, -1.0f, buttonSizeY, enabled, framesExportButtonEnabled)) { isMenuActive = false; } if (UI::buttonHelper("Safe Frames Export", "Store frames directly to the disk. Disabling it will make recording faster, but might crash if you run out of memory", myParam.screenCapture.isSafeFramesEnabled, -1.0f, buttonSizeY, enabled, safeModeButtonEnabled)) { isMenuActive = false; } if (UI::buttonHelper("Reset Custom Colors", "Resets custom colors changed in the right click menu", resetParticleColors, -1.0f, buttonSizeY, enabled, enabled)) { isMenuActive = false; } bool pColChanged = false; bool sColChanged = false; ImGui::Text("Primary Color"); if (ImGui::ColorEdit4("##pCol", (float*)&pCol, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { myParam.colorVisuals.selectedColor = false; pColChanged = true; if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.rParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { myParam.rParticles[i].uniqueColor = true; } } } else { for (size_t i = 0; i < myParam.rParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { myParam.rParticles3D[i].uniqueColor = true; } } } } ImGui::Text("Secondary Color"); if (ImGui::ColorEdit4("##sCol", (float*)&sCol, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_DisplayRGB)) { myParam.colorVisuals.selectedColor = false; sColChanged = true; if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.rParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { myParam.rParticles[i].uniqueColor = true; } } } else { for (size_t i = 0; i < myParam.rParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { myParam.rParticles3D[i].uniqueColor = true; } } } } ImGui::End(); if (resetParticleColors) { if (!myVar.is3DMode) { for (size_t i = 0; i < myParam.rParticles.size(); i++) { myParam.rParticles[i].pColor = { 255, 255, 255, 255 }; myParam.rParticles[i].sColor = { 255, 255, 255, 255 }; myParam.rParticles[i].uniqueColor = false; } resetParticleColors = false; } else { for (size_t i = 0; i < myParam.rParticles3D.size(); i++) { myParam.rParticles3D[i].pColor = { 255, 255, 255, 255 }; myParam.rParticles3D[i].sColor = { 255, 255, 255, 255 }; myParam.rParticles3D[i].uniqueColor = false; } resetParticleColors = false; } } if (!myVar.is3DMode) { if (myParam.rParticlesSelected.size() > 0 && !pColChanged && !sColChanged && !ImGui::IsItemActive()) { pCol.x = 0.0f; pCol.y = 0.0f; pCol.z = 0.0f; pCol.w = 0.0f; sCol.x = 0.0f; sCol.y = 0.0f; sCol.z = 0.0f; sCol.w = 0.0f; int visibleSelectedAmount = 0; for (size_t i = 0; i < myParam.rParticles.size(); i++) { ParticleRendering& rP = myParam.rParticles[i]; if (rP.isSelected && !rP.isDarkMatter) { ImVec4 rToVecPColor = rlImGuiColors::Convert(rP.pColor); ImVec4 rToVecSColor = rlImGuiColors::Convert(rP.sColor); pCol.x += rToVecPColor.x; pCol.y += rToVecPColor.y; pCol.z += rToVecPColor.z; pCol.w += rToVecPColor.w; sCol.x += rToVecSColor.x; sCol.y += rToVecSColor.y; sCol.z += rToVecSColor.z; sCol.w += rToVecSColor.w; visibleSelectedAmount++; } } if (visibleSelectedAmount > 0) { pCol.x /= visibleSelectedAmount; pCol.y /= visibleSelectedAmount; pCol.z /= visibleSelectedAmount; pCol.w /= visibleSelectedAmount; sCol.x /= visibleSelectedAmount; sCol.y /= visibleSelectedAmount; sCol.z /= visibleSelectedAmount; sCol.w /= visibleSelectedAmount; } } } else { if (myParam.rParticlesSelected3D.size() > 0 && !pColChanged && !sColChanged && !ImGui::IsItemActive()) { pCol.x = 0.0f; pCol.y = 0.0f; pCol.z = 0.0f; pCol.w = 0.0f; sCol.x = 0.0f; sCol.y = 0.0f; sCol.z = 0.0f; sCol.w = 0.0f; int visibleSelectedAmount = 0; for (size_t i = 0; i < myParam.rParticles3D.size(); i++) { ParticleRendering3D& rP = myParam.rParticles3D[i]; if (rP.isSelected && !rP.isDarkMatter) { ImVec4 rToVecPColor = rlImGuiColors::Convert(rP.pColor); ImVec4 rToVecSColor = rlImGuiColors::Convert(rP.sColor); pCol.x += rToVecPColor.x; pCol.y += rToVecPColor.y; pCol.z += rToVecPColor.z; pCol.w += rToVecPColor.w; sCol.x += rToVecSColor.x; sCol.y += rToVecSColor.y; sCol.z += rToVecSColor.z; sCol.w += rToVecSColor.w; visibleSelectedAmount++; } } if (visibleSelectedAmount > 0) { pCol.x /= visibleSelectedAmount; pCol.y /= visibleSelectedAmount; pCol.z /= visibleSelectedAmount; pCol.w /= visibleSelectedAmount; sCol.x /= visibleSelectedAmount; sCol.y /= visibleSelectedAmount; sCol.z /= visibleSelectedAmount; sCol.w /= visibleSelectedAmount; } } } if (!myVar.is3DMode) { if ((pColChanged || sColChanged) && myParam.rParticlesSelected.size() > 0 && isMenuActive) { vecToRPColor = rlImGuiColors::Convert(pCol); vecToRSColor = rlImGuiColors::Convert(sCol); for (size_t i = 0; i < myParam.rParticles.size(); i++) { ParticleRendering& rP = myParam.rParticles[i]; if (rP.isSelected && !rP.isDarkMatter) { rP.uniqueColor = true; rP.pColor.r = vecToRPColor.r; rP.pColor.g = vecToRPColor.g; rP.pColor.b = vecToRPColor.b; rP.pColor.a = vecToRPColor.a; rP.sColor.r = vecToRSColor.r; rP.sColor.g = vecToRSColor.g; rP.sColor.b = vecToRSColor.b; rP.sColor.a = vecToRSColor.a; } } } } else { if ((pColChanged || sColChanged) && myParam.rParticlesSelected3D.size() > 0 && isMenuActive) { vecToRPColor = rlImGuiColors::Convert(pCol); vecToRSColor = rlImGuiColors::Convert(sCol); for (size_t i = 0; i < myParam.rParticles3D.size(); i++) { ParticleRendering3D& rP = myParam.rParticles3D[i]; if (rP.isSelected && !rP.isDarkMatter) { rP.uniqueColor = true; rP.pColor.r = vecToRPColor.r; rP.pColor.g = vecToRPColor.g; rP.pColor.b = vecToRPColor.b; rP.pColor.a = vecToRPColor.a; rP.sColor.r = vecToRSColor.r; rP.sColor.g = vecToRSColor.g; rP.sColor.b = vecToRSColor.b; rP.sColor.a = vecToRSColor.a; } } } } } } ================================================ FILE: GalaxyEngine/src/UX/camera.cpp ================================================ #include "UX/camera.h" #include "parameters.h" SceneCamera::SceneCamera() { camera.offset = { 0.0f, 0.0f }; camera.target = { 0.0f, 0.0f }; camera.rotation = 0.0f; camera.zoom = 0.5f; mouseWorldPos = { 0.0f, 0.0f }; panFollowingOffset = { 0.0f, 0.0f }; isFollowing = false; centerCamera = false; cameraChangedThisFrame = false; previousColor = { 128,128,128,255 }; followPosition = { 0.0f, 0.0f }; delta = { 0.0f, 0.0f }; } Camera2D SceneCamera::cameraLogic(bool& loadFlag, bool& isMouseNotHoveringUI) { if (IsMouseButtonDown(1)) { delta = glm::vec2(GetMouseDelta().x, GetMouseDelta().y); delta = delta * (-1.0f / camera.zoom); camera.target.x = camera.target.x + delta.x; camera.target.y = camera.target.y + delta.y; panFollowingOffset = panFollowingOffset + delta; } float wheel = GetMouseWheelMove(); mouseWorldPos = glm::vec2(GetScreenToWorld2D(GetMousePosition(), camera).x, GetScreenToWorld2D(GetMousePosition(), camera).y); if (wheel != 0 && !IsKeyDown(KEY_LEFT_CONTROL) && !loadFlag && isMouseNotHoveringUI) { if (isFollowing) { glm::vec2 screenCenter = { GetScreenWidth() * 0.5f, GetScreenHeight() * 0.5f }; mouseWorldPos = glm::vec2(GetScreenToWorld2D({ screenCenter.x, screenCenter.y }, camera).x, GetScreenToWorld2D({ screenCenter.x, screenCenter.y }, camera).y); camera.offset = { screenCenter.x, screenCenter.y }; camera.target = { mouseWorldPos.x, mouseWorldPos.y }; } else { mouseWorldPos = glm::vec2(GetScreenToWorld2D(GetMousePosition(), camera).x, GetScreenToWorld2D(GetMousePosition(), camera).y); camera.offset = GetMousePosition(); camera.target = { mouseWorldPos.x, mouseWorldPos.y }; } float scale = 0.2f * wheel; camera.zoom = Clamp(expf(logf(camera.zoom) + scale), 0.475f, 64.0f); } // RESET CAMERA if (IO::shortcutPress(KEY_F)) { camera.zoom = defaultCamZoom; camera.target = { 0.0f, 0.0f }; camera.offset = { 0.0f, 0.0f }; panFollowingOffset = { 0.0f, 0.0f }; } return camera; } void SceneCamera::cameraFollowObject(UpdateVariables& myVar, UpdateParameters& myParam) { static bool isDragging = false; static glm::vec2 dragStartPos = { 0.0f, 0.0f }; if ((IsMouseButtonPressed(1) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || (IsMouseButtonPressed(1) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI)) { dragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); isDragging = false; } if ((IsMouseButtonDown(1) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || (IsMouseButtonDown(1) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI)) { glm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); float dragThreshold = 5.0f; glm::vec2 d = currentPos - dragStartPos; if (d.x * d.x + d.y * d.y > dragThreshold * dragThreshold) { isDragging = true; } } if (IsMouseButtonReleased(1) && IsKeyDown(KEY_LEFT_CONTROL) && !isDragging && myVar.isMouseNotHoveringUI) { myParam.particleSelection.clusterSelection(myVar, myParam, true); isFollowing = true; panFollowingOffset = { 0.0f, 0.0f }; if (myVar.isSelectedTrailsEnabled) { myParam.trails.segments.clear(); } } if (IsMouseButtonReleased(1) && IsKeyDown(KEY_LEFT_ALT) && !isDragging && myVar.isMouseNotHoveringUI) { myParam.particleSelection.particleSelection(myVar, myParam, true); isFollowing = true; panFollowingOffset = { 0.0f, 0.0f }; if (myVar.isSelectedTrailsEnabled) { myParam.trails.segments.clear(); } } if (IO::shortcutPress(KEY_Z) || centerCamera) { panFollowingOffset = { 0.0f, 0.0f }; isFollowing = true; centerCamera = false; } if (isFollowing) { glm::vec2 sum = glm::vec2(0.0f, 0.0f); float count = 0.0f; for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { sum += myParam.pParticles[i].pos; count++; } } followPosition = sum / count; followPosition = followPosition + panFollowingOffset; camera.target = { followPosition.x, followPosition.y }; camera.offset = { GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f }; if (IO::shortcutPress(KEY_F) || count == 0 || myParam.pParticles.size() == 0) { isFollowing = false; camera.zoom = defaultCamZoom; camera.target = { 0.0f, 0.0f }; camera.offset = { 0.0f, 0.0f }; panFollowingOffset = { 0.0f, 0.0f }; } } } void SceneCamera::hasCamMoved() { cameraChangedThisFrame = false; if (lastTarget.x != camera.target.x || lastTarget.y != camera.target.y || lastOffset.x != camera.offset.x || lastOffset.y != camera.offset.y || lastZoom != camera.zoom || lastRotation != camera.rotation) { cameraChangedThisFrame = true; } else { cameraChangedThisFrame = false; } lastTarget = camera.target; lastOffset = camera.offset; lastZoom = camera.zoom; lastRotation = camera.rotation; } // ---- 3D IMPLEMENTATION ---- // Camera3D SceneCamera3D::cameraLogic(bool& isLoading, bool& isMouseNotHoveringUI, bool& firstPerson, bool& isShipEnabled) { if (isMouseNotHoveringUI && IO::mouseDown(1) && IO::shortcutDown(KEY_LEFT_ALT) && !firstPerson) { Vector2 mouseDelta = GetMouseDelta(); float panSpeed = distance * 0.002f; float radNormX = angleX * DEG2RAD; float radNormY = angleY * DEG2RAD; camNormal.x = cosf(radNormY) * sinf(radNormX); camNormal.y = sinf(radNormY); camNormal.z = cosf(radNormY) * cosf(radNormX); camNormal = glm::normalize(camNormal); camRight = glm::cross(worldUp, camNormal); camRight = glm::normalize(camRight); camUp = glm::cross(camNormal, camRight); camUp = glm::normalize(camUp); Vector3 moveRight = Vector3Scale({ camRight.x,camRight.y, camRight.z }, -mouseDelta.x * panSpeed); Vector3 moveUp = Vector3Scale({ camUp.x, camUp.y, camUp.z }, mouseDelta.y * panSpeed); Vector3 totalPanMove = Vector3Add(moveRight, moveUp); if (isFollowing) { panFollowingOffset = Vector3Add(panFollowingOffset, totalPanMove); } else { target = Vector3Add(target, totalPanMove); } } if (isMouseNotHoveringUI && IO::mouseDown(1) && !IO::shortcutDown(KEY_LEFT_ALT)) { Vector2 mouseDelta = GetMouseDelta(); angleX -= mouseDelta.x * 0.3f; angleY += mouseDelta.y * 0.3f; if (angleY > 89.0f) angleY = 89.0f; if (angleY < -89.0f) angleY = -89.0f; } if (isMouseNotHoveringUI && !isShipEnabled) { Vector3 arrowMove = { 0.0f, 0.0f, 0.0f }; float radNormX = angleX * DEG2RAD; float radNormY = angleY * DEG2RAD; camNormal.x = cosf(radNormY) * sinf(radNormX); camNormal.y = sinf(radNormY); camNormal.z = cosf(radNormY) * cosf(radNormX); camNormal = glm::normalize(camNormal); camRight = glm::cross(worldUp, camNormal); camRight = glm::normalize(camRight); if (IsKeyDown(KEY_RIGHT)) { Vector3 moveRight = Vector3Scale({ camRight.x, camRight.y, camRight.z }, arrowMoveSpeed); arrowMove += moveRight * GetFrameTime(); } if (IsKeyDown(KEY_LEFT)) { Vector3 moveLeft = Vector3Scale({ camRight.x, camRight.y, camRight.z }, -arrowMoveSpeed); arrowMove += moveLeft * GetFrameTime(); } if (IsKeyDown(KEY_UP)) { Vector3 moveForward = Vector3Scale({ camNormal.x, camNormal.y, camNormal.z }, -arrowMoveSpeed); arrowMove += moveForward * GetFrameTime(); } if (IsKeyDown(KEY_DOWN)) { Vector3 moveBackward = Vector3Scale({ camNormal.x, camNormal.y, camNormal.z }, arrowMoveSpeed); arrowMove += moveBackward * GetFrameTime(); } if (arrowMove.x != 0.0f || arrowMove.y != 0.0f || arrowMove.z != 0.0f) { if (firstPerson) { firstPersonPosition = Vector3Add(firstPersonPosition, arrowMove); } else { if (isFollowing) { panFollowingOffset = Vector3Add(panFollowingOffset, arrowMove); } else { target = Vector3Add(target, arrowMove); } } } } if (isMouseNotHoveringUI && !IO::shortcutDown(KEY_LEFT_SHIFT) && !IO::shortcutDown(KEY_LEFT_CONTROL) && !firstPerson) { float wheel = GetMouseWheelMove(); float zoomSpeed = 0.1f; distance *= (1.0f - wheel * zoomSpeed); if (distance < 1.0f) distance = 1.0f; if (distance > 30000.0f) distance = 30000.0f; } if (!firstPerson) { float smoothSpeed = 0.1f; currentSmoothedTarget.x += (target.x - currentSmoothedTarget.x) * smoothSpeed; currentSmoothedTarget.y += (target.y - currentSmoothedTarget.y) * smoothSpeed; currentSmoothedTarget.z += (target.z - currentSmoothedTarget.z) * smoothSpeed; float radX = angleX * DEG2RAD; float radY = angleY * DEG2RAD; Vector3 orbitOffset; orbitOffset.x = distance * cosf(radY) * sinf(radX); orbitOffset.y = distance * sinf(radY); orbitOffset.z = distance * cosf(radY) * cosf(radX); cam3D.target = { currentSmoothedTarget.x, currentSmoothedTarget.y, currentSmoothedTarget.z }; cam3D.position = Vector3Add(cam3D.target, orbitOffset); } else { float radX = angleX * DEG2RAD; float radY = angleY * DEG2RAD; cam3D.position = firstPersonPosition; Vector3 lookDirection; lookDirection.x = cosf(radY) * sinf(radX); lookDirection.y = sinf(radY); lookDirection.z = cosf(radY) * cosf(radX); cam3D.target = Vector3Add(cam3D.position, Vector3Scale(lookDirection, -1.0f)); } float radX = angleX * DEG2RAD; float radY = angleY * DEG2RAD; camNormal.x = cosf(radY) * sinf(radX); camNormal.y = sinf(radY); camNormal.z = cosf(radY) * cosf(radX); camNormal = glm::normalize(camNormal); camRight = glm::cross(worldUp, camNormal); camRight = glm::normalize(camRight); camUp = glm::cross(camNormal, camRight); camUp = glm::normalize(camUp); cam3D.up = { camUp.x, camUp.y, camUp.z }; cam3D.fovy = 45.0f; cam3D.projection = CAMERA_PERSPECTIVE; return cam3D; } void SceneCamera3D::cameraFollowObject(UpdateVariables& myVar, UpdateParameters& myParam) { static bool isDragging = false; static glm::vec2 dragStartPos = { 0.0f, 0.0f }; if ((IsMouseButtonPressed(1) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || (IsMouseButtonPressed(1) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI)) { dragStartPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); isDragging = false; } if ((IsMouseButtonDown(1) && IsKeyDown(KEY_LEFT_CONTROL) && myVar.isMouseNotHoveringUI) || (IsMouseButtonDown(1) && IsKeyDown(KEY_LEFT_ALT) && myVar.isMouseNotHoveringUI)) { glm::vec2 currentPos = glm::vec2(GetMousePosition().x, GetMousePosition().y); float dragThreshold = 5.0f; glm::vec2 d = currentPos - dragStartPos; if (d.x * d.x + d.y * d.y > dragThreshold * dragThreshold) { isDragging = true; } } if (IsMouseButtonReleased(1) && IsKeyDown(KEY_LEFT_CONTROL) && !isDragging && myVar.isMouseNotHoveringUI) { myParam.particleSelection3D.clusterSelection(myVar, myParam, true); isFollowing = true; panFollowingOffset = { 0.0f, 0.0f, 0.0f }; if (myVar.isSelectedTrailsEnabled) { myParam.trails.segments3D.clear(); } } if (IsMouseButtonReleased(1) && IsKeyDown(KEY_LEFT_ALT) && !isDragging && myVar.isMouseNotHoveringUI) { myParam.particleSelection3D.particleSelection(myVar, myParam, true); isFollowing = true; panFollowingOffset = { 0.0f, 0.0f, 0.0f }; if (myVar.isSelectedTrailsEnabled) { myParam.trails.segments3D.clear(); } } if (IO::shortcutPress(KEY_Z) || centerCamera) { panFollowingOffset = { 0.0f, 0.0f, 0.0f }; isFollowing = true; centerCamera = false; } if (IO::shortcutPress(KEY_F)) { target = { 0.0f, 0.0f, 0.0f }; currentSmoothedTarget = { 0.0f, 0.0f, 0.0f }; distance = defaultCamDist; angleX = 0.0f; angleY = 0.0f; isFollowing = false; } if (isFollowing) { Vector3 sum = { 0.0f, 0.0f, 0.0f }; float count = 0.0f; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { sum.x += myParam.pParticles3D[i].pos.x; sum.y += myParam.pParticles3D[i].pos.y; sum.z += myParam.pParticles3D[i].pos.z; count++; } } if (count > 0) { target = Vector3Scale(sum, 1.0f / count); target = Vector3Add(target, { panFollowingOffset.x, panFollowingOffset.y, panFollowingOffset.z }); } else { isFollowing = false; } if (IO::shortcutPress(KEY_F) || myParam.pParticles3D.empty()) { target = { 0.0f, 0.0f, 0.0f }; currentSmoothedTarget = { 0.0f, 0.0f, 0.0f }; distance = defaultCamDist; angleX = 0.0f; angleY = 0.0f; isFollowing = false; } } } ================================================ FILE: GalaxyEngine/src/UX/randNum.cpp ================================================ #include "UX/randNum.h" float getRandomFloat() { static std::random_device rd; static std::mt19937 gen(rd()); static std::uniform_real_distribution dist(0.0f, 1.0f); return dist(gen); } ================================================ FILE: GalaxyEngine/src/UX/saveSystem.cpp ================================================ #include "UX/saveSystem.h" void SaveSystem::saveSystem(const std::string& filename, UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, Physics& physics, Physics3D& physics3D, Lighting& lighting, Field& field) { YAML::Emitter out; /*out << YAML::BeginMap;*/ // ----- Trails ----- paramIO(filename, out, "GlobalTrails", myVar.isGlobalTrailsEnabled); paramIO(filename, out, "SelectedTrails", myVar.isSelectedTrailsEnabled); paramIO(filename, out, "LocalTrails", myVar.isLocalTrailsEnabled); paramIO(filename, out, "WhiteTrails", myParam.trails.whiteTrails); paramIO(filename, out, "TrailsMaxLength", myVar.trailMaxLength); paramIO(filename, out, "TrailsThickness", myParam.trails.trailThickness); // ----- Color parameters ----- paramIO(filename, out, "SolidColor", myParam.colorVisuals.solidColor); paramIO(filename, out, "DensityColor", myParam.colorVisuals.densityColor); paramIO(filename, out, "ForceColor", myParam.colorVisuals.forceColor); paramIO(filename, out, "VelocityColor", myParam.colorVisuals.velocityColor); paramIO(filename, out, "ShockwaveColor", myParam.colorVisuals.shockwaveColor); paramIO(filename, out, "TurbulenceColor", myParam.colorVisuals.turbulenceColor); paramIO(filename, out, "PressureColor", myParam.colorVisuals.pressureColor); paramIO(filename, out, "SPHColor", myParam.colorVisuals.SPHColor); paramIO(filename, out, "SelectedColor", myParam.colorVisuals.selectedColor); paramIO(filename, out, "ShowDarkMatter", myParam.colorVisuals.showDarkMatterEnabled); // ----- Color sliders ----- paramIO(filename, out, "ColorMaxVel", myParam.colorVisuals.maxVel); paramIO(filename, out, "ColorMaxPressure", myParam.colorVisuals.maxPress); paramIO(filename, out, "MaxColorForce", myParam.colorVisuals.maxColorAcc); paramIO(filename, out, "ShockwaveMaxAcc", myParam.colorVisuals.ShockwaveMaxAcc); paramIO(filename, out, "MaxColorTurbulence", myParam.colorVisuals.maxColorTurbulence); paramIO(filename, out, "TurbulenceFadeRate", myParam.colorVisuals.turbulenceFadeRate); paramIO(filename, out, "TurbulenceContrast", myParam.colorVisuals.turbulenceContrast); paramIO(filename, out, "TurbulenceCustomColor", myParam.colorVisuals.turbulenceCustomCol); paramIO(filename, out, "TemperatureColor", myParam.colorVisuals.temperatureColor); paramIO(filename, out, "TemperatureGasColor", myParam.colorVisuals.gasTempColor); paramIO(filename, out, "MaxTemperatureColor", myParam.colorVisuals.tempColorMaxTemp); paramIO(filename, out, "MaxNeighbors", myParam.colorVisuals.maxNeighbors); // ----- Other visual sliders ----- paramIO(filename, out, "MaxSizeForce", myParam.densitySize.sizeAcc); paramIO(filename, out, "Max Dynamic Size", myParam.densitySize.maxSize); paramIO(filename, out, "Min Dynamic Size", myParam.densitySize.minSize); paramIO(filename, out, "MaxSizeNeighbors", myParam.densitySize.maxNeighbors); paramIO(filename, out, "ParticleSizeMult", myVar.particleSizeMultiplier); paramIO(filename, out, "VisiblePAmountMult", myVar.particleAmountMultiplier); paramIO(filename, out, "DMPAmountMult", myVar.DMAmountMultiplier); paramIO(filename, out, "MassRandomMultiplier", myVar.massScatter); paramIO(filename, out, "MassMultiplierToggle", myParam.particlesSpawning.massMultiplierEnabled); // ----- Colors ----- paramIO(filename, out, "pColorsR", myParam.colorVisuals.pColor.r); paramIO(filename, out, "pColorsG", myParam.colorVisuals.pColor.g); paramIO(filename, out, "pColorsB", myParam.colorVisuals.pColor.b); paramIO(filename, out, "pColorsA", myParam.colorVisuals.pColor.a); paramIO(filename, out, "sColorsR", myParam.colorVisuals.sColor.r); paramIO(filename, out, "sColorsG", myParam.colorVisuals.sColor.g); paramIO(filename, out, "sColorsB", myParam.colorVisuals.sColor.b); paramIO(filename, out, "sColorsA", myParam.colorVisuals.sColor.a); // ----- Misc Toggles ----- paramIO(filename, out, "DarkMatter", myVar.isDarkMatterEnabled); paramIO(filename, out, "LoopingSpace", myVar.isPeriodicBoundaryEnabled); paramIO(filename, out, "InfiniteDomain", myVar.infiniteDomain); paramIO(filename, out, "SPHEnabled", myVar.isSPHEnabled); paramIO(filename, out, "DensitySize", myVar.isDensitySizeEnabled); paramIO(filename, out, "ForceSize", myVar.isForceSizeEnabled); paramIO(filename, out, "Glow", myVar.isGlowEnabled); paramIO(filename, out, "ShipGas", myVar.isShipGasEnabled); paramIO(filename, out, "Merger", myVar.isMergerEnabled); paramIO(filename, out, "is3DMode", myVar.is3DMode); paramIO(filename, out, "ClipSelectedX", myVar.clipSelectedX); paramIO(filename, out, "ClipSelectedY", myVar.clipSelectedY); paramIO(filename, out, "ClipSelectedZ", myVar.clipSelectedZ); paramIO(filename, out, "ClipSelectedXInv", myVar.clipSelectedXInv); paramIO(filename, out, "ClipSelectedYInv", myVar.clipSelectedYInv); paramIO(filename, out, "ClipSelectedZInv", myVar.clipSelectedZInv); // ----- SPH Materials ----- paramIO(filename, out, "SPHWater", myVar.SPHWater); paramIO(filename, out, "SPHRock", myVar.SPHRock); paramIO(filename, out, "SPHIron", myVar.SPHIron); paramIO(filename, out, "SPHSand", myVar.SPHSand); paramIO(filename, out, "SPHSoil", myVar.SPHSoil); paramIO(filename, out, "SPHIce", myVar.SPHIce); paramIO(filename, out, "SPHMud", myVar.SPHMud); paramIO(filename, out, "SPHRubber", myVar.SPHRubber); paramIO(filename, out, "SPHGas", myVar.SPHGas); // ----- Physics params ----- paramIO(filename, out, "Softening", myVar.softening); paramIO(filename, out, "Theta", myVar.theta); paramIO(filename, out, "TimeMult", myVar.timeStepMultiplier); paramIO(filename, out, "GravityMultiplier", myVar.gravityMultiplier); paramIO(filename, out, "HeavyParticlesMass", myVar.heavyParticleWeightMultiplier); paramIO(filename, out, "TemperatureSimulation", myVar.isTempEnabled); paramIO(filename, out, "AmbientTemperature", myVar.ambientTemp); paramIO(filename, out, "AmbientHeatRate", myVar.globalAmbientHeatRate); paramIO(filename, out, "HeatConductivityMultiplier", myVar.globalHeatConductivity); // ----- SPH ----- paramIO(filename, out, "SPHGravity", myVar.verticalGravity); paramIO(filename, out, "SPHRadiusMult", sph.radiusMultiplier); paramIO(filename, out, "SPHMass", myVar.mass); paramIO(filename, out, "SPHViscosity", myVar.viscosity); paramIO(filename, out, "SPHStiffness", myVar.stiffMultiplier); paramIO(filename, out, "SPHCohesion", myVar.cohesionCoefficient); paramIO(filename, out, "SPHGround", myVar.sphGround); paramIO(filename, out, "SPHDelta", myVar.delta); paramIO(filename, out, "SPHMaxVel", myVar.sphMaxVel); // ----- Domain size ----- paramIO(filename, out, "DomainWidth", myVar.domainSize.x); paramIO(filename, out, "DomainHeight", myVar.domainSize.y); paramIO(filename, out, "DomainWidth3D", myVar.domainSize3D.x); paramIO(filename, out, "DomainHeight3D", myVar.domainSize3D.y); paramIO(filename, out, "DomainDepth3D", myVar.domainSize3D.z); // ----- Camera ----- paramIO(filename, out, "CameraTargetX", myParam.myCamera.camera.target.x); paramIO(filename, out, "CameraTargetY", myParam.myCamera.camera.target.y); paramIO(filename, out, "CameraOffsetX", myParam.myCamera.camera.offset.x); paramIO(filename, out, "CameraOffsetY", myParam.myCamera.camera.offset.y); paramIO(filename, out, "CameraZoom", myParam.myCamera.camera.zoom); paramIO(filename, out, "CameraIsFollowing", myParam.myCamera.isFollowing); // ----- Camera 3D ----- paramIO(filename, out, "CameraTarget3DX", myParam.myCamera3D.cam3D.target.x); paramIO(filename, out, "CameraTarget3DY", myParam.myCamera3D.cam3D.target.y); paramIO(filename, out, "CameraTarget3DZ", myParam.myCamera3D.cam3D.target.z); paramIO(filename, out, "CameraPosition3DX", myParam.myCamera3D.cam3D.position.x); paramIO(filename, out, "CameraPosition3DY", myParam.myCamera3D.cam3D.position.y); paramIO(filename, out, "CameraPosition3DZ", myParam.myCamera3D.cam3D.position.z); paramIO(filename, out, "CameraCurrentSmoothedTarget3DX", myParam.myCamera3D.currentSmoothedTarget.x); paramIO(filename, out, "CameraCurrentSmoothedTarget3DY", myParam.myCamera3D.currentSmoothedTarget.y); paramIO(filename, out, "CameraCurrentSmoothedTarget3DZ", myParam.myCamera3D.currentSmoothedTarget.z); paramIO(filename, out, "CameraPanFollowingOffset3DX", myParam.myCamera3D.panFollowingOffset.x); paramIO(filename, out, "CameraPanFollowingOffset3DY", myParam.myCamera3D.panFollowingOffset.y); paramIO(filename, out, "CameraPanFollowingOffset3DZ", myParam.myCamera3D.panFollowingOffset.z); paramIO(filename, out, "CameraFollowPosition3DX", myParam.myCamera3D.followPosition.x); paramIO(filename, out, "CameraFollowPosition3DY", myParam.myCamera3D.followPosition.y); paramIO(filename, out, "CameraFollowPosition3DZ", myParam.myCamera3D.followPosition.z); paramIO(filename, out, "CameraDistance3D", myParam.myCamera3D.distance); paramIO(filename, out, "CameraIsFollowing3D", myParam.myCamera3D.isFollowing); paramIO(filename, out, "CameraOffset3DX", myParam.myCamera3D.offset.x); paramIO(filename, out, "CameraOffset3DY", myParam.myCamera3D.offset.y); paramIO(filename, out, "CameraOffset3DZ", myParam.myCamera3D.offset.z); paramIO(filename, out, "CameraDefaultCamDist3D", myParam.myCamera3D.defaultCamDist); paramIO(filename, out, "CameraPanOffsetRight3DX", myParam.myCamera3D.panOffsetRight.x); paramIO(filename, out, "CameraPanOffsetRight3DY", myParam.myCamera3D.panOffsetRight.y); paramIO(filename, out, "CameraPanOffsetRight3DZ", myParam.myCamera3D.panOffsetRight.z); paramIO(filename, out, "CameraPanOffsetUp3DX", myParam.myCamera3D.panOffsetUp.x); paramIO(filename, out, "CameraPanOffsetUp3DY", myParam.myCamera3D.panOffsetUp.y); paramIO(filename, out, "CameraPanOffsetUp3DZ", myParam.myCamera3D.panOffsetUp.z); paramIO(filename, out, "CameraCamNormal3DX", myParam.myCamera3D.camNormal.x); paramIO(filename, out, "CameraCamNormal3DY", myParam.myCamera3D.camNormal.y); paramIO(filename, out, "CameraCamNormal3DZ", myParam.myCamera3D.camNormal.z); paramIO(filename, out, "CameraCamRight3DX", myParam.myCamera3D.camRight.x); paramIO(filename, out, "CameraCamRight3DY", myParam.myCamera3D.camRight.y); paramIO(filename, out, "CameraCamRight3DZ", myParam.myCamera3D.camRight.z); paramIO(filename, out, "CameraCamUp3DX", myParam.myCamera3D.camUp.x); paramIO(filename, out, "CameraCamUp3DY", myParam.myCamera3D.camUp.y); paramIO(filename, out, "CameraCamUp3DZ", myParam.myCamera3D.camUp.z); paramIO(filename, out, "CameraWorldUp3DX", myParam.myCamera3D.worldUp.x); paramIO(filename, out, "CameraWorldUp3DY", myParam.myCamera3D.worldUp.y); paramIO(filename, out, "CameraWorldUp3DZ", myParam.myCamera3D.worldUp.z); paramIO(filename, out, "CameraFirstPersonPosition3DX", myParam.myCamera3D.firstPersonPosition.x); paramIO(filename, out, "CameraFirstPersonPosition3DY", myParam.myCamera3D.firstPersonPosition.y); paramIO(filename, out, "CameraFirstPersonPosition3DZ", myParam.myCamera3D.firstPersonPosition.z); paramIO(filename, out, "CameraArrowMoveSpeed3D", myParam.myCamera3D.arrowMoveSpeed); paramIO(filename, out, "CameraAngleX3D", myParam.myCamera3D.angleX); paramIO(filename, out, "CameraAngleY3D", myParam.myCamera3D.angleY); // ----- Constraints ----- paramIO(filename, out, "ParticleConstraints", myVar.constraintsEnabled); paramIO(filename, out, "UnbreakableConstraints", myVar.unbreakableConstraints); paramIO(filename, out, "ConstraintAfterDrawing", myVar.constraintAfterDrawing); paramIO(filename, out, "VisualizeConstraints", myVar.drawConstraints); paramIO(filename, out, "VisualizeMesh", myVar.visualizeMesh); paramIO(filename, out, "ConstraintsStressColor", myVar.constraintStressColor); paramIO(filename, out, "MaxConstraintStress", myVar.constraintMaxStressColor); paramIO(filename, out, "ConstraintsStiffMultiplier", myVar.globalConstraintStiffnessMult); paramIO(filename, out, "ConstraintsResistMultiplier", myVar.globalConstraintResistance); // ----- Optics ----- paramIO(filename, out, "Optics", myVar.isOpticsEnabled); // ----- Optics Colors ----- paramIO(filename, out, "LColorR", lighting.lightColor.r); paramIO(filename, out, "LColorG", lighting.lightColor.g); paramIO(filename, out, "LColorB", lighting.lightColor.b); paramIO(filename, out, "LColorA", lighting.lightColor.a); paramIO(filename, out, "BaseColorR", lighting.wallBaseColor.r); paramIO(filename, out, "BaseColorG", lighting.wallBaseColor.g); paramIO(filename, out, "BaseColorB", lighting.wallBaseColor.b); paramIO(filename, out, "BaseColorA", lighting.wallBaseColor.a); paramIO(filename, out, "SpecularColorR", lighting.wallSpecularColor.r); paramIO(filename, out, "SpecularColorG", lighting.wallSpecularColor.g); paramIO(filename, out, "SpecularColorB", lighting.wallSpecularColor.b); paramIO(filename, out, "SpecularColorA", lighting.wallSpecularColor.a); paramIO(filename, out, "RefractionColorR", lighting.wallRefractionColor.r); paramIO(filename, out, "RefractionColorG", lighting.wallRefractionColor.g); paramIO(filename, out, "RefractionColorB", lighting.wallRefractionColor.b); paramIO(filename, out, "RefractionColorA", lighting.wallRefractionColor.a); paramIO(filename, out, "EmissionColorR", lighting.wallEmissionColor.r); paramIO(filename, out, "EmissionColorG", lighting.wallEmissionColor.g); paramIO(filename, out, "EmissionColorB", lighting.wallEmissionColor.b); paramIO(filename, out, "EmissionColorA", lighting.wallEmissionColor.a); // ----- Optics Lights ----- paramIO(filename, out, "LightGain", lighting.lightGain); paramIO(filename, out, "LightSpread", lighting.lightSpread); // ----- Optics Walls ----- paramIO(filename, out, "WallSpecularRough", lighting.wallSpecularRoughness); paramIO(filename, out, "WallRefractionRough", lighting.wallRefractionRoughness); paramIO(filename, out, "WallRefractionAmount", lighting.wallRefractionAmount); paramIO(filename, out, "WallIOR", lighting.wallIOR); paramIO(filename, out, "WallDispersion", lighting.wallDispersion); paramIO(filename, out, "WallEmissionGain", lighting.wallEmissionGain); // ----- Optics Shapes ----- paramIO(filename, out, "ShapeRelaxIter", lighting.shapeRelaxIter); paramIO(filename, out, "ShapeRelaxFactor", lighting.shapeRelaxFactor); // ----- Optics Render ----- paramIO(filename, out, "MaxSamples", lighting.maxSamples); paramIO(filename, out, "RaysPerSample", lighting.sampleRaysAmount); paramIO(filename, out, "MaxBounces", lighting.maxBounces); // ----- Optics Render Passes ----- paramIO(filename, out, "GI", lighting.isDiffuseEnabled); paramIO(filename, out, "Specular", lighting.isSpecularEnabled); paramIO(filename, out, "Refraction", lighting.isRefractionEnabled); paramIO(filename, out, "Dispersion", lighting.isDispersionEnabled); paramIO(filename, out, "Emission", lighting.isEmissionEnabled); // ----- Optics Misc ----- paramIO(filename, out, "SymmetricalLens", lighting.symmetricalLens); paramIO(filename, out, "ShowNormals", lighting.drawNormals); paramIO(filename, out, "RelaxMove", lighting.relaxMove); // ----- Gravity Field ----- paramIO(filename, out, "GravityField", myVar.isGravityFieldEnabled); paramIO(filename, out, "GravityFieldDM", myVar.gravityFieldDMParticles); paramIO(filename, out, "GravityDisplayThreshold", field.gravityDisplayThreshold); paramIO(filename, out, "GravityDisplaySoftness", field.gravityDisplaySoftness); paramIO(filename, out, "GravityDisplayStretch", field.gravityStretchFactor); paramIO(filename, out, "GravityCustomColors", field.gravityCustomColors); paramIO(filename, out, "GravityCustomColExp", field.gravityExposure); // ----- Field Misc ----- paramIO(filename, out, "FieldRes", field.res); /*out << YAML::EndMap;*/ std::string yamlString = out.c_str(); std::fstream file; if (saveFlag) { std::fstream file(filename, std::ios::out | std::ios::binary | std::ios::trunc); if (!file.is_open()) { std::cerr << "Failed to open file for writing: " << filename << "\n"; return; } file.write(yamlString.c_str(), yamlString.size()); const char* separator = "\n---PARTICLE BINARY DATA---\n"; file.write(separator, strlen(separator)); file.write(reinterpret_cast(¤tVersion), sizeof(currentVersion)); file.write(reinterpret_cast(&globalId), sizeof(globalId)); file.write(reinterpret_cast(&globalShapeId), sizeof(globalShapeId)); file.write(reinterpret_cast(&globalWallId), sizeof(globalWallId)); if (!myVar.is3DMode) { uint32_t particleCount = myParam.pParticles.size(); file.write(reinterpret_cast(&particleCount), sizeof(particleCount)); for (size_t i = 0; i < myParam.pParticles.size(); i++) { const ParticlePhysics& p = myParam.pParticles[i]; const ParticleRendering& r = myParam.rParticles[i]; file.write(reinterpret_cast(&p.pos), sizeof(p.pos)); file.write(reinterpret_cast(&p.predPos), sizeof(p.predPos)); file.write(reinterpret_cast(&p.vel), sizeof(p.vel)); file.write(reinterpret_cast(&p.prevVel), sizeof(p.prevVel)); file.write(reinterpret_cast(&p.predVel), sizeof(p.predVel)); file.write(reinterpret_cast(&p.acc), sizeof(p.acc)); file.write(reinterpret_cast(&p.mass), sizeof(p.mass)); file.write(reinterpret_cast(&p.press), sizeof(p.press)); file.write(reinterpret_cast(&p.pressTmp), sizeof(p.pressTmp)); file.write(reinterpret_cast(&p.pressF), sizeof(p.pressF)); file.write(reinterpret_cast(&p.dens), sizeof(p.dens)); file.write(reinterpret_cast(&p.predDens), sizeof(p.predDens)); file.write(reinterpret_cast(&p.sphMass), sizeof(p.sphMass)); file.write(reinterpret_cast(&p.restDens), sizeof(p.restDens)); file.write(reinterpret_cast(&p.stiff), sizeof(p.stiff)); file.write(reinterpret_cast(&p.visc), sizeof(p.visc)); file.write(reinterpret_cast(&p.cohesion), sizeof(p.cohesion)); file.write(reinterpret_cast(&p.temp), sizeof(p.temp)); file.write(reinterpret_cast(&p.ke), sizeof(p.ke)); file.write(reinterpret_cast(&p.prevKe), sizeof(p.prevKe)); file.write(reinterpret_cast(&p.mortonKey), sizeof(p.mortonKey)); file.write(reinterpret_cast(&p.id), sizeof(p.id)); file.write(reinterpret_cast(&p.isHotPoint), sizeof(p.isHotPoint)); file.write(reinterpret_cast(&p.hasSolidified), sizeof(p.hasSolidified)); file.write(reinterpret_cast(&r.color), sizeof(r.color)); file.write(reinterpret_cast(&r.pColor), sizeof(r.pColor)); file.write(reinterpret_cast(&r.sColor), sizeof(r.sColor)); file.write(reinterpret_cast(&r.sphColor), sizeof(r.sphColor)); file.write(reinterpret_cast(&r.size), sizeof(r.size)); file.write(reinterpret_cast(&r.uniqueColor), sizeof(r.uniqueColor)); file.write(reinterpret_cast(&r.isSolid), sizeof(r.isSolid)); file.write(reinterpret_cast(&r.canBeSubdivided), sizeof(r.canBeSubdivided)); file.write(reinterpret_cast(&r.canBeResized), sizeof(r.canBeResized)); file.write(reinterpret_cast(&r.isDarkMatter), sizeof(r.isDarkMatter)); file.write(reinterpret_cast(&r.isSPH), sizeof(r.isSPH)); file.write(reinterpret_cast(&r.isSelected), sizeof(r.isSelected)); file.write(reinterpret_cast(&r.isGrabbed), sizeof(r.isGrabbed)); file.write(reinterpret_cast(&r.previousSize), sizeof(r.previousSize)); file.write(reinterpret_cast(&r.neighbors), sizeof(r.neighbors)); file.write(reinterpret_cast(&r.totalRadius), sizeof(r.totalRadius)); file.write(reinterpret_cast(&r.lifeSpan), sizeof(r.lifeSpan)); file.write(reinterpret_cast(&r.sphLabel), sizeof(r.sphLabel)); file.write(reinterpret_cast(&r.isPinned), sizeof(r.isPinned)); file.write(reinterpret_cast(&r.isBeingDrawn), sizeof(r.isBeingDrawn)); file.write(reinterpret_cast(&r.spawnCorrectIter), sizeof(r.spawnCorrectIter)); file.write(reinterpret_cast(&r.turbulence), sizeof(r.turbulence)); } } else { uint32_t particleCount = myParam.pParticles3D.size(); file.write(reinterpret_cast(&particleCount), sizeof(particleCount)); for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { const ParticlePhysics3D& p = myParam.pParticles3D[i]; const ParticleRendering3D& r = myParam.rParticles3D[i]; file.write(reinterpret_cast(&p.pos), sizeof(p.pos)); file.write(reinterpret_cast(&p.predPos), sizeof(p.predPos)); file.write(reinterpret_cast(&p.vel), sizeof(p.vel)); file.write(reinterpret_cast(&p.prevVel), sizeof(p.prevVel)); file.write(reinterpret_cast(&p.predVel), sizeof(p.predVel)); file.write(reinterpret_cast(&p.acc), sizeof(p.acc)); file.write(reinterpret_cast(&p.mass), sizeof(p.mass)); file.write(reinterpret_cast(&p.press), sizeof(p.press)); file.write(reinterpret_cast(&p.pressTmp), sizeof(p.pressTmp)); file.write(reinterpret_cast(&p.pressF), sizeof(p.pressF)); file.write(reinterpret_cast(&p.dens), sizeof(p.dens)); file.write(reinterpret_cast(&p.predDens), sizeof(p.predDens)); file.write(reinterpret_cast(&p.sphMass), sizeof(p.sphMass)); file.write(reinterpret_cast(&p.restDens), sizeof(p.restDens)); file.write(reinterpret_cast(&p.stiff), sizeof(p.stiff)); file.write(reinterpret_cast(&p.visc), sizeof(p.visc)); file.write(reinterpret_cast(&p.cohesion), sizeof(p.cohesion)); file.write(reinterpret_cast(&p.temp), sizeof(p.temp)); file.write(reinterpret_cast(&p.ke), sizeof(p.ke)); file.write(reinterpret_cast(&p.prevKe), sizeof(p.prevKe)); file.write(reinterpret_cast(&p.mortonKey), sizeof(p.mortonKey)); file.write(reinterpret_cast(&p.id), sizeof(p.id)); file.write(reinterpret_cast(&p.isHotPoint), sizeof(p.isHotPoint)); file.write(reinterpret_cast(&p.hasSolidified), sizeof(p.hasSolidified)); file.write(reinterpret_cast(&r.color), sizeof(r.color)); file.write(reinterpret_cast(&r.pColor), sizeof(r.pColor)); file.write(reinterpret_cast(&r.sColor), sizeof(r.sColor)); file.write(reinterpret_cast(&r.sphColor), sizeof(r.sphColor)); file.write(reinterpret_cast(&r.size), sizeof(r.size)); file.write(reinterpret_cast(&r.uniqueColor), sizeof(r.uniqueColor)); file.write(reinterpret_cast(&r.isSolid), sizeof(r.isSolid)); file.write(reinterpret_cast(&r.canBeSubdivided), sizeof(r.canBeSubdivided)); file.write(reinterpret_cast(&r.canBeResized), sizeof(r.canBeResized)); file.write(reinterpret_cast(&r.isDarkMatter), sizeof(r.isDarkMatter)); file.write(reinterpret_cast(&r.isSPH), sizeof(r.isSPH)); file.write(reinterpret_cast(&r.isSelected), sizeof(r.isSelected)); file.write(reinterpret_cast(&r.isGrabbed), sizeof(r.isGrabbed)); file.write(reinterpret_cast(&r.previousSize), sizeof(r.previousSize)); file.write(reinterpret_cast(&r.neighbors), sizeof(r.neighbors)); file.write(reinterpret_cast(&r.totalRadius), sizeof(r.totalRadius)); file.write(reinterpret_cast(&r.lifeSpan), sizeof(r.lifeSpan)); file.write(reinterpret_cast(&r.sphLabel), sizeof(r.sphLabel)); file.write(reinterpret_cast(&r.isPinned), sizeof(r.isPinned)); file.write(reinterpret_cast(&r.isBeingDrawn), sizeof(r.isBeingDrawn)); file.write(reinterpret_cast(&r.spawnCorrectIter), sizeof(r.spawnCorrectIter)); file.write(reinterpret_cast(&r.turbulence), sizeof(r.turbulence)); } } if (!myVar.is3DMode) { uint32_t numConstraints = physics.particleConstraints.size(); file.write(reinterpret_cast(&numConstraints), sizeof(numConstraints)); if (numConstraints > 0) { file.write( reinterpret_cast(physics.particleConstraints.data()), numConstraints * sizeof(ParticleConstraint) ); } } else { uint32_t numConstraints = physics3D.particleConstraints.size(); file.write(reinterpret_cast(&numConstraints), sizeof(numConstraints)); if (numConstraints > 0) { file.write( reinterpret_cast(physics3D.particleConstraints.data()), numConstraints * sizeof(ParticleConstraint) ); } } uint32_t wallCount = lighting.walls.size(); file.write(reinterpret_cast(&wallCount), sizeof(wallCount)); for (size_t i = 0; i < lighting.walls.size(); i++) { const Wall& w = lighting.walls[i]; // Write all glm::vec2 fields file.write(reinterpret_cast(&w.vA), sizeof(w.vA)); file.write(reinterpret_cast(&w.vB), sizeof(w.vB)); file.write(reinterpret_cast(&w.normal), sizeof(w.normal)); file.write(reinterpret_cast(&w.normalVA), sizeof(w.normalVA)); file.write(reinterpret_cast(&w.normalVB), sizeof(w.normalVB)); // Write booleans file.write(reinterpret_cast(&w.isBeingSpawned), sizeof(w.isBeingSpawned)); file.write(reinterpret_cast(&w.vAisBeingMoved), sizeof(w.vAisBeingMoved)); file.write(reinterpret_cast(&w.vBisBeingMoved), sizeof(w.vBisBeingMoved)); // Write Color structs file.write(reinterpret_cast(&w.apparentColor), sizeof(w.apparentColor)); file.write(reinterpret_cast(&w.baseColor), sizeof(w.baseColor)); file.write(reinterpret_cast(&w.specularColor), sizeof(w.specularColor)); file.write(reinterpret_cast(&w.refractionColor), sizeof(w.refractionColor)); file.write(reinterpret_cast(&w.emissionColor), sizeof(w.emissionColor)); // Write float color values file.write(reinterpret_cast(&w.baseColorVal), sizeof(w.baseColorVal)); file.write(reinterpret_cast(&w.specularColorVal), sizeof(w.specularColorVal)); file.write(reinterpret_cast(&w.refractionColorVal), sizeof(w.refractionColorVal)); // Write material floats file.write(reinterpret_cast(&w.specularRoughness), sizeof(w.specularRoughness)); file.write(reinterpret_cast(&w.refractionRoughness), sizeof(w.refractionRoughness)); file.write(reinterpret_cast(&w.refractionAmount), sizeof(w.refractionAmount)); file.write(reinterpret_cast(&w.IOR), sizeof(w.IOR)); file.write(reinterpret_cast(&w.dispersionStrength), sizeof(w.dispersionStrength)); // Write shape metadata file.write(reinterpret_cast(&w.isShapeWall), sizeof(w.isShapeWall)); file.write(reinterpret_cast(&w.isShapeClosed), sizeof(w.isShapeClosed)); file.write(reinterpret_cast(&w.shapeId), sizeof(w.shapeId)); // Write wall ID and selection flag file.write(reinterpret_cast(&w.id), sizeof(w.id)); file.write(reinterpret_cast(&w.isSelected), sizeof(w.isSelected)); } uint32_t numShapes = lighting.shapes.size(); file.write(reinterpret_cast(&numShapes), sizeof(numShapes)); if (numShapes > 0) { for (const Shape& s : lighting.shapes) { uint32_t wallIdCount = s.myWallIds.size(); file.write(reinterpret_cast(&wallIdCount), sizeof(wallIdCount)); file.write(reinterpret_cast(s.myWallIds.data()), wallIdCount * sizeof(uint32_t)); uint32_t vertCount = s.polygonVerts.size(); file.write(reinterpret_cast(&vertCount), sizeof(vertCount)); file.write(reinterpret_cast(s.polygonVerts.data()), vertCount * sizeof(glm::vec2)); uint32_t helpersCount = s.helpers.size(); file.write(reinterpret_cast(&helpersCount), sizeof(helpersCount)); if (helpersCount > 0) { file.write(reinterpret_cast(s.helpers.data()), helpersCount * sizeof(glm::vec2)); } file.write(reinterpret_cast(&s.baseColor), sizeof(s.baseColor)); file.write(reinterpret_cast(&s.specularColor), sizeof(s.specularColor)); file.write(reinterpret_cast(&s.refractionColor), sizeof(s.refractionColor)); file.write(reinterpret_cast(&s.emissionColor), sizeof(s.emissionColor)); file.write(reinterpret_cast(&s.specularRoughness), sizeof(s.specularRoughness)); file.write(reinterpret_cast(&s.refractionRoughness), sizeof(s.refractionRoughness)); file.write(reinterpret_cast(&s.refractionAmount), sizeof(s.refractionAmount)); file.write(reinterpret_cast(&s.IOR), sizeof(s.IOR)); file.write(reinterpret_cast(&s.dispersionStrength), sizeof(s.dispersionStrength)); file.write(reinterpret_cast(&s.id), sizeof(s.id)); file.write(reinterpret_cast(&s.h1), sizeof(s.h1)); file.write(reinterpret_cast(&s.h2), sizeof(s.h2)); file.write(reinterpret_cast(&s.isBeingSpawned), sizeof(s.isBeingSpawned)); file.write(reinterpret_cast(&s.isBeingMoved), sizeof(s.isBeingMoved)); file.write(reinterpret_cast(&s.isShapeClosed), sizeof(s.isShapeClosed)); file.write(reinterpret_cast(&s.shapeType), sizeof(s.shapeType)); file.write(reinterpret_cast(&s.drawHoverHelpers), sizeof(s.drawHoverHelpers)); file.write(reinterpret_cast(&s.oldDrawHelperPos), sizeof(s.oldDrawHelperPos)); // Lens variables file.write(reinterpret_cast(&s.secondHelper), sizeof(s.secondHelper)); file.write(reinterpret_cast(&s.thirdHelper), sizeof(s.thirdHelper)); file.write(reinterpret_cast(&s.fourthHelper), sizeof(s.fourthHelper)); file.write(reinterpret_cast(&s.Tempsh2Length), sizeof(s.Tempsh2Length)); file.write(reinterpret_cast(&s.Tempsh2LengthSymmetry), sizeof(s.Tempsh2LengthSymmetry)); file.write(reinterpret_cast(&s.tempDist), sizeof(s.tempDist)); file.write(reinterpret_cast(&s.moveH2), sizeof(s.moveH2)); file.write(reinterpret_cast(&s.isThirdBeingMoved), sizeof(s.isThirdBeingMoved)); file.write(reinterpret_cast(&s.isFourthBeingMoved), sizeof(s.isFourthBeingMoved)); file.write(reinterpret_cast(&s.isFifthBeingMoved), sizeof(s.isFifthBeingMoved)); file.write(reinterpret_cast(&s.isFifthFourthMoved), sizeof(s.isFifthFourthMoved)); file.write(reinterpret_cast(&s.symmetricalLens), sizeof(s.symmetricalLens)); file.write(reinterpret_cast(&s.wallAId), sizeof(s.wallAId)); file.write(reinterpret_cast(&s.wallBId), sizeof(s.wallBId)); file.write(reinterpret_cast(&s.wallCId), sizeof(s.wallCId)); file.write(reinterpret_cast(&s.lensSegments), sizeof(s.lensSegments)); file.write(reinterpret_cast(&s.startAngle), sizeof(s.startAngle)); file.write(reinterpret_cast(&s.endAngle), sizeof(s.endAngle)); file.write(reinterpret_cast(&s.startAngleSymmetry), sizeof(s.startAngleSymmetry)); file.write(reinterpret_cast(&s.endAngleSymmetry), sizeof(s.endAngleSymmetry)); file.write(reinterpret_cast(&s.center), sizeof(s.center)); file.write(reinterpret_cast(&s.radius), sizeof(s.radius)); file.write(reinterpret_cast(&s.centerSymmetry), sizeof(s.centerSymmetry)); file.write(reinterpret_cast(&s.radiusSymmetry), sizeof(s.radiusSymmetry)); file.write(reinterpret_cast(&s.arcEnd), sizeof(s.arcEnd)); file.write(reinterpret_cast(&s.globalLensPrev), sizeof(s.globalLensPrev)); } } uint32_t pointLightCount = lighting.pointLights.size(); file.write(reinterpret_cast(&pointLightCount), sizeof(pointLightCount)); for (const PointLight& pl : lighting.pointLights) { file.write(reinterpret_cast(&pl.pos), sizeof(pl.pos)); file.write(reinterpret_cast(&pl.isBeingMoved), sizeof(pl.isBeingMoved)); file.write(reinterpret_cast(&pl.color), sizeof(pl.color)); file.write(reinterpret_cast(&pl.apparentColor), sizeof(pl.apparentColor)); file.write(reinterpret_cast(&pl.isSelected), sizeof(pl.isSelected)); } uint32_t areaLightCount = lighting.areaLights.size(); file.write(reinterpret_cast(&areaLightCount), sizeof(areaLightCount)); for (const AreaLight& al : lighting.areaLights) { file.write(reinterpret_cast(&al.vA), sizeof(al.vA)); file.write(reinterpret_cast(&al.vB), sizeof(al.vB)); file.write(reinterpret_cast(&al.isBeingSpawned), sizeof(al.isBeingSpawned)); file.write(reinterpret_cast(&al.vAisBeingMoved), sizeof(al.vAisBeingMoved)); file.write(reinterpret_cast(&al.vBisBeingMoved), sizeof(al.vBisBeingMoved)); file.write(reinterpret_cast(&al.color), sizeof(al.color)); file.write(reinterpret_cast(&al.apparentColor), sizeof(al.apparentColor)); file.write(reinterpret_cast(&al.isSelected), sizeof(al.isSelected)); file.write(reinterpret_cast(&al.spread), sizeof(al.spread)); } uint32_t coneLightCount = lighting.coneLights.size(); file.write(reinterpret_cast(&coneLightCount), sizeof(coneLightCount)); for (const ConeLight& cl : lighting.coneLights) { file.write(reinterpret_cast(&cl.vA), sizeof(cl.vA)); file.write(reinterpret_cast(&cl.vB), sizeof(cl.vB)); file.write(reinterpret_cast(&cl.isBeingSpawned), sizeof(cl.isBeingSpawned)); file.write(reinterpret_cast(&cl.vAisBeingMoved), sizeof(cl.vAisBeingMoved)); file.write(reinterpret_cast(&cl.vBisBeingMoved), sizeof(cl.vBisBeingMoved)); file.write(reinterpret_cast(&cl.color), sizeof(cl.color)); file.write(reinterpret_cast(&cl.apparentColor), sizeof(cl.apparentColor)); file.write(reinterpret_cast(&cl.isSelected), sizeof(cl.isSelected)); file.write(reinterpret_cast(&cl.spread), sizeof(cl.spread)); } file.close(); } deserializeParticleSystem(filename, yamlString, myVar, myParam, sph, physics, physics3D, lighting, loadFlag); } void SaveSystem::saveLoadLogic(UpdateVariables& myVar, UpdateParameters& myParam, SPH& sph, Physics& physics, Physics3D& physics3D, Lighting& lighting, Field& field) { if (saveFlag) { if (!std::filesystem::exists("Saves")) { std::filesystem::create_directory("Saves"); } int nextAvailableIndex = 0; for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator("Saves")) { std::string filename = entry.path().filename().string(); if (filename.rfind("Save_", 0) == 0 && filename.find(".bin") != std::string::npos) { size_t startPos = filename.find_last_of('_') + 1; size_t endPos = filename.find(".bin"); int index = std::stoi(filename.substr(startPos, endPos - startPos)); if (index >= nextAvailableIndex) { nextAvailableIndex = index + 1; } } } std::string savePath = "Saves/Save_" + std::to_string(nextAvailableIndex) + ".bin"; saveSystem(savePath.c_str(), myVar, myParam, sph, physics, physics3D, lighting, field); saveIndex++; saveFlag = false; } if (loadFlag) { int fileIndex = 1; filePaths.clear(); if (!std::filesystem::exists("Saves")) { std::filesystem::create_directory("Saves"); std::cout << "Created Saves directory as it did not exist" << std::endl; loadFlag = false; return; } std::vector> files; for (const auto& entry : std::filesystem::recursive_directory_iterator("Saves")) { if (entry.is_regular_file()) { const auto filename = entry.path().filename().string(); const auto fullpath = entry.path().string(); if (filename.rfind(".bin") != std::string::npos) { std::string relativePath = fullpath.substr(6); files.emplace_back(relativePath, fullpath); } } } if (files.empty()) { std::cout << "No .bin files found in Saves directory" << std::endl; loadFlag = false; return; } std::sort(files.begin(), files.end(), [](auto& A, auto& B) { const std::string& nameA = A.first; const std::string& nameB = B.first; auto isDefault = [](const std::string& s) { const std::string key = "DefaultSettings.bin"; return s.size() >= key.size() && s.compare(s.size() - key.size(), key.size(), key) == 0; }; bool aIsDefault = isDefault(nameA); bool bIsDefault = isDefault(nameB); if (aIsDefault != bIsDefault) { return aIsDefault; } auto getDir = [](const std::string& s) { size_t pos = s.find_last_of("\\/"); return (pos == std::string::npos) ? std::string{} : s.substr(0, pos); }; std::string dirA = getDir(nameA); std::string dirB = getDir(nameB); if (dirA != dirB) { return dirA < dirB; } auto extractNumber = [](const std::string& s) -> int { size_t i = 0; while (i < s.size() && !std::isdigit(s[i])) ++i; size_t j = i; while (j < s.size() && std::isdigit(s[j])) ++j; if (i < j) { try { return std::stoi(s.substr(i, j - i)); } catch (...) {} } return -1; }; int numA = extractNumber(nameA); int numB = extractNumber(nameB); if (numA >= 0 && numB >= 0 && numA != numB) { return numA < numB; } return nameA < nameB; } ); ImGui::SetNextWindowSize(loadMenuSize, ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(static_cast(GetScreenWidth()) * 0.5f - loadMenuSize.x * 0.5f, 450.0f), ImGuiCond_Once); ImGui::Begin("Files"); for (const auto& [filename, fullPath] : files) { bool placeHolder = false; bool enabled = true; if (UI::buttonHelper(fullPath.c_str(), "Select scene file", placeHolder, ImGui::GetContentRegionAvail().x, buttonHeight, enabled, enabled)) { saveSystem(fullPath.c_str(), myVar, myParam, sph, physics, physics3D, lighting, field); loadFlag = false; myVar.playbackRecord = false; } filePaths.push_back(fullPath); fileIndex++; } ImGui::End(); } } ================================================ FILE: GalaxyEngine/src/UX/screenCapture.cpp ================================================ #include "UI/UI.h" #include "UX/screenCapture.h" #include "parameters.h" extern "C" { #include #include #include #include #include #include } void ScreenCapture::cleanupFFmpeg() { if (pCodecCtx) { avcodec_send_frame(pCodecCtx, nullptr); AVPacket* pkt = av_packet_alloc(); if (pkt) { while (avcodec_receive_packet(pCodecCtx, pkt) == 0) { av_packet_unref(pkt); } av_packet_free(&pkt); } } if (swsCtx) { sws_freeContext(swsCtx); swsCtx = nullptr; } if (frame) { av_frame_free(&frame); frame = nullptr; } if (pCodecCtx) { avcodec_free_context(&pCodecCtx); pCodecCtx = nullptr; } if (pFormatCtx) { if (pFormatCtx->oformat && !(pFormatCtx->oformat->flags & AVFMT_NOFILE)) { avio_closep(&pFormatCtx->pb); } avformat_free_context(pFormatCtx); pFormatCtx = nullptr; } pStream = nullptr; frameIndex = 0; } void ScreenCapture::exportFrameToFile(const Image& frame, const std::string& videoFolder, const std::string& videoName, int frameNumber) { if (!std::filesystem::exists(videoFolder)) { printf("Warning: Frame export folder does not exist: %s\n", videoFolder.c_str()); return; } Image frameCopy = ImageCopy(frame); ImageFormat(&frameCopy, PIXELFORMAT_UNCOMPRESSED_R8G8B8); ImageFlipVertical(&frameCopy); std::string filename = videoFolder + "/" + videoName + "_" + std::to_string(frameNumber) + ".png"; try { ExportImage(frameCopy, filename.c_str()); } catch (...) { printf("Error: Failed to export frame to: %s\n", filename.c_str()); } UnloadImage(frameCopy); } void ScreenCapture::exportMemoryFramesToDisk() { if (myFrames.empty()) { printf("No frames in memory to export.\n"); return; } if (actualSavedVideoFolder.empty() || actualSavedVideoName.empty()) { printf("Error: No saved video information available for frame " "export.\n"); return; } createFramesFolder(actualSavedVideoFolder); printf("Exporting %d frames to %s with base name %s...\n", static_cast(myFrames.size()), actualSavedVideoFolder.c_str(), actualSavedVideoName.c_str()); auto startTime = std::chrono::high_resolution_clock::now(); int exportedCount = 0; const int totalFrames = static_cast(myFrames.size()); #pragma omp parallel for reduction(+ : exportedCount) schedule(static) for (int i = 0; i < totalFrames; ++i) { try { exportFrameToFile(myFrames[i], actualSavedVideoFolder, actualSavedVideoName, i); exportedCount++; if (i % 100 == 0) { #pragma omp critical { printf("Exported frame %d/%d (%.1f%%)\n", i + 1, totalFrames, (static_cast(i + 1) / totalFrames) * 100.0f); } } } catch (...) { #pragma omp critical { printf("Warning: Failed to export frame %d\n", i); } } } auto endTime = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast( endTime - startTime); printf("Successfully exported %d out of %d frames in %.2f seconds.\n", exportedCount, static_cast(myFrames.size()), static_cast(duration.count()) / 1000.0); } void ScreenCapture::discardMemoryFrames() { if (myFrames.empty()) { printf("No frames in memory to discard.\n"); return; } printf("Discarding %d frames from memory...\n", static_cast(myFrames.size())); for (Image& frame : myFrames) { UnloadImage(frame); } myFrames.clear(); std::vector().swap(myFrames); printf("All frames discarded from memory.\n"); } void ScreenCapture::createFramesFolder(const std::string& folderPath) { if (!std::filesystem::exists(folderPath)) { try { std::filesystem::create_directories(folderPath); } catch (const std::exception& e) { printf("Error creating folder %s: %s\n", folderPath.c_str(), e.what()); } } } void ScreenCapture::discardRecording() { // Clean up video folder and any files if it was created if (!this->videoFolder.empty()) { try { // Delete individual frame files if safe frames mode was enabled if (isSafeFramesEnabled && isExportFramesEnabled && !this->folderName.empty()) { std::string framePrefix = this->folderName + "_frame_"; int deletedFrameCount = 0; if (std::filesystem::exists(this->videoFolder)) { for (const auto& entry : std::filesystem::directory_iterator(this->videoFolder)) { if (entry.is_regular_file()) { std::string frameFileName = entry.path().filename().string(); if (frameFileName.rfind(framePrefix, 0) == 0 && frameFileName.length() > framePrefix.length() && frameFileName.substr(frameFileName.length() - 4) == ".png") { try { std::filesystem::remove(entry.path()); deletedFrameCount++; } catch (const std::filesystem::filesystem_error& e_frame) { printf("Warning: Failed to delete frame %s: %s\n", entry.path().string().c_str(), e_frame.what()); } } } } if (deletedFrameCount > 0) { printf("Deleted %d frame files from cancelled recording\n", deletedFrameCount); } } } // Remove the entire video folder since recording was cancelled if (std::filesystem::exists(this->videoFolder)) { try { std::filesystem::remove_all(this->videoFolder); printf("Removed video folder: %s\n", this->videoFolder.c_str()); } catch (const std::filesystem::filesystem_error& e_folder) { printf("Warning: Failed to remove video folder %s: %s\n", this->videoFolder.c_str(), e_folder.what()); } } } catch (const std::filesystem::filesystem_error& e_dir) { printf("Warning: Failed to access video folder for cleanup: %s\n", e_dir.what()); } } } std::string ScreenCapture::generateVideoFilename() { int maxNumberFound = 0; const std::string prefix = "Video_"; if (std::filesystem::exists("Videos")) { for (const auto& entry : std::filesystem::directory_iterator("Videos")) { if (entry.is_directory()) { std::string folderName = entry.path().filename().string(); if (folderName.compare(0, prefix.size(), prefix) == 0) { const char* numberPart = folderName.c_str() + prefix.size(); char* endPtr = nullptr; double value = std::strtod(numberPart, &endPtr); if (endPtr != numberPart && *endPtr == '\0') { int number = static_cast(value); if (number > maxNumberFound) { maxNumberFound = number; } } } } } } int nextAvailableNumber = maxNumberFound + 1; std::string videoName = prefix + std::to_string(nextAvailableNumber); return "Videos/" + videoName + "/" + videoName + ".mp4"; } bool ScreenCapture::screenGrab(RenderTexture2D& myParticlesTexture, UpdateVariables& myVar, UpdateParameters& myParam) { if (IO::shortcutPress(KEY_S)) { if (!std::filesystem::exists("Screenshots")) { std::filesystem::create_directory("Screenshots"); } int nextAvailableIndex = 0; for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator("Screenshots")) { std::string filename = entry.path().filename().string(); if (filename.rfind("Screenshot_", 0) == 0 && filename.find(".png") != std::string::npos) { size_t startPos = filename.find_last_of('_') + 1; size_t endPos = filename.find(".png"); int index = std::stoi(filename.substr(startPos, endPos - startPos)); if (index >= nextAvailableIndex) { nextAvailableIndex = index + 1; } } } RenderTexture2D screenshotTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); BeginTextureMode(screenshotTexture); ClearBackground(BLACK); DrawTexture(myParticlesTexture.texture, 0, 0, WHITE); EndTextureMode(); Image renderImage = LoadImageFromTexture(screenshotTexture.texture); ImageFormat(&renderImage, PIXELFORMAT_UNCOMPRESSED_R8G8B8); std::string screenshotPath = "Screenshots/Screenshot_" + std::to_string(nextAvailableIndex) + ".png"; ExportImage(renderImage, screenshotPath.c_str()); UnloadImage(renderImage); UnloadRenderTexture(screenshotTexture); screenshotIndex++; } if (IO::shortcutPress(KEY_R) && !showSaveConfirmationDialog) { if (!isFunctionRecording && !isSafeFramesEnabled) { for (Image& frame : myFrames) { UnloadImage(frame); } myFrames.clear(); std::vector().swap(myFrames); } if (!isFunctionRecording && isVideoExportEnabled) { diskModeFrameIdx = 0; if (!std::filesystem::exists("Videos")) { std::filesystem::create_directory("Videos"); } outFileName = generateVideoFilename(); size_t lastSlash = outFileName.find_last_of('/'); this->videoFolder = outFileName.substr(0, lastSlash); if (!std::filesystem::exists(this->videoFolder)) { std::filesystem::create_directories(this->videoFolder); } size_t secondToLastSlash = outFileName.find_last_of('/', lastSlash - 1); if (secondToLastSlash != std::string::npos) { folderName = outFileName.substr(secondToLastSlash + 1, lastSlash - secondToLastSlash - 1); } else { folderName = "Video_1"; } } if (!isFunctionRecording) { int w = GetScreenWidth(); int h = GetScreenHeight(); if (avformat_alloc_output_context2(&pFormatCtx, nullptr, nullptr, outFileName.c_str()) < 0) { printf("Could not alloc output context\n"); return false; } const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!codec) { printf("H.264 codec not found\n"); return false; } pStream = avformat_new_stream(pFormatCtx, codec); pCodecCtx = avcodec_alloc_context3(codec); pCodecCtx->codec_id = AV_CODEC_ID_H264; pCodecCtx->width = w; pCodecCtx->height = h; pCodecCtx->time_base = AVRational{ 1, 60 }; pCodecCtx->framerate = AVRational{ 24, 1 }; pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; pCodecCtx->bit_rate = 256 * 1000 * 1000; pCodecCtx->gop_size = 12; av_opt_set(pCodecCtx->priv_data, "preset", "medium", 0); av_opt_set(pCodecCtx->priv_data, "crf", "23", 0); if (avcodec_open2(pCodecCtx, codec, nullptr) < 0) { printf("Could not open codec\n"); return false; } avcodec_parameters_from_context(pStream->codecpar, pCodecCtx); if (!(pFormatCtx->oformat->flags & AVFMT_NOFILE)) { if (avio_open(&pFormatCtx->pb, outFileName.c_str(), AVIO_FLAG_WRITE) < 0) { printf("Could not open output file\n"); return false; } } if (avformat_write_header(pFormatCtx, nullptr) < 0) { printf("Could not write header\n"); return false; } frame = av_frame_alloc(); frame->format = pCodecCtx->pix_fmt; frame->width = w; frame->height = h; av_frame_get_buffer(frame, 0); swsCtx = sws_getContext(w, h, AV_PIX_FMT_RGBA, w, h, AV_PIX_FMT_YUV420P, SWS_BILINEAR, nullptr, nullptr, nullptr); printf("Started recording to '%s'\n", outFileName.c_str()); isFunctionRecording = true; frameIndex = 0; videoHasBeenSaved = false; actualSavedVideoFolder.clear(); actualSavedVideoName.clear(); return true; } else { av_write_trailer(pFormatCtx); cleanupFFmpeg(); isFunctionRecording = false; lastVideoPath = outFileName; showSaveConfirmationDialog = true; if (myVar.pauseAfterRecording) { myVar.isTimePlaying = false; } if (myVar.cleanSceneAfterRecording) { myParam.pParticles.clear(); myParam.rParticles.clear(); } printf("Stopped recording. File saved as '%s'\\n", outFileName.c_str()); } } if (cancelRecording && isFunctionRecording) { cleanupFFmpeg(); isFunctionRecording = false; if (std::filesystem::exists(outFileName)) { try { std::filesystem::remove(outFileName); printf("Recording cancelled and file deleted: " "%s\n", outFileName.c_str()); } catch (const std::exception& e) { printf("Warning: Failed to delete cancelled " "recording: %s\n", e.what()); } } // Clean up frame files and folder if safe frames mode was enabled discardRecording(); for (Image& frameImg : myFrames) { UnloadImage(frameImg); } myFrames.clear(); std::vector().swap(myFrames); diskModeFrameIdx = 0; videoHasBeenSaved = false; actualSavedVideoFolder.clear(); actualSavedVideoName.clear(); // Clear recording state variables lastVideoPath.clear(); this->videoFolder.clear(); this->folderName.clear(); cancelRecording = false; } if (isFunctionRecording) { if (!pCodecCtx || !pFormatCtx || !swsCtx || !frame) { printf("Error: FFmpeg contexts not properly " "initialized\n"); return false; } RenderTexture2D videoTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); BeginTextureMode(videoTexture); ClearBackground(BLACK); DrawTexture(myParticlesTexture.texture, 0, 0, WHITE); EndTextureMode(); Image img = LoadImageFromTexture(videoTexture.texture); UnloadRenderTexture(videoTexture); if (!img.data) { printf("Error: Failed to load image from texture\n"); return isFunctionRecording; } //ImageFlipVertical(&img); int w = img.width; int h = img.height; const uint8_t* srcSlices[1] = { reinterpret_cast(img.data) }; int srcStride[1] = { 4 * w }; int result = sws_scale(swsCtx, srcSlices, srcStride, 0, h, frame->data, frame->linesize); if (result < 0) { printf("Error: sws_scale failed\n"); UnloadImage(img); return false; } if (frame) { if (pStream) { frame->pts = av_rescale_q(frameIndex++, pCodecCtx->time_base, pStream->time_base); } else { frame->pts = frameIndex++; } } if (myVar.recordingTimeLimit > 0.0f) { float recordedSeconds = static_cast(frameIndex) / pCodecCtx->time_base.den; if (recordedSeconds >= myVar.recordingTimeLimit) { printf("Recording time limit reached (%.1f " "seconds). Stopping " "recording.\n", myVar.recordingTimeLimit); av_write_trailer(pFormatCtx); cleanupFFmpeg(); isFunctionRecording = false; lastVideoPath = outFileName; showSaveConfirmationDialog = true; if (myVar.pauseAfterRecording) { myVar.isTimePlaying = false; } if (myVar.cleanSceneAfterRecording) { myParam.pParticles.clear(); myParam.rParticles.clear(); } UnloadImage(img); return isFunctionRecording; } } int sendResult = avcodec_send_frame(pCodecCtx, frame); if (sendResult < 0) { printf("Warning: avcodec_send_frame failed with error " "%d\n", sendResult); } AVPacket* pkt = av_packet_alloc(); if (!pkt) { printf("Could not allocate packet\n"); UnloadImage(img); return false; } while (avcodec_receive_packet(pCodecCtx, pkt) == 0) { if (pStream) { pkt->stream_index = pStream->index; int writeResult = av_interleaved_write_frame(pFormatCtx, pkt); if (writeResult < 0) { printf("Warning: " "av_interleaved_write_frame " "failed with " "error %d\n", writeResult); } } av_packet_unref(pkt); } av_packet_free(&pkt); if (!isSafeFramesEnabled && isExportFramesEnabled) { Image frameCopy = ImageCopy(img); ImageFormat(&frameCopy, PIXELFORMAT_UNCOMPRESSED_R8G8B8); ImageFlipVertical(&frameCopy); myFrames.push_back(frameCopy); } if (isSafeFramesEnabled && isExportFramesEnabled) { if (!this->videoFolder.empty() && !this->folderName.empty()) { Image frameForExport = ImageCopy(img); ImageFormat(&frameForExport, PIXELFORMAT_UNCOMPRESSED_R8G8B8); std::string safeFramePath = this->videoFolder + "/" + this->folderName + "_frame_" + std::to_string(diskModeFrameIdx) + ".png"; ExportImage(frameForExport, safeFramePath.c_str()); UnloadImage(frameForExport); } else { printf("Warning: videoFolder or folderName is " "empty, cannot " "save safe " "frame.\\n"); } } if (isSafeFramesEnabled || isVideoExportEnabled) { diskModeFrameIdx++; } UnloadImage(img); } float screenW = GetScreenWidth(); float screenH = GetScreenHeight(); ImVec2 framesMenuSize = { 400.0f, 200.0f }; if (myFrames.size() > 0 || diskModeFrameIdx > 0 || isFunctionRecording) { ImGui::SetNextWindowSize(framesMenuSize, ImGuiCond_Once); float yPosition = 30.0f; ImGui::SetNextWindowPos( ImVec2(screenW * 0.5f - framesMenuSize.x * 0.5f, yPosition), ImGuiCond_Appearing); ImGui::Begin("Recording Menu", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize); ImGui::PushFont(myVar.robotoMediumFont); ImGui::SetWindowFontScale(1.5f); if (diskModeFrameIdx > 0 && (isSafeFramesEnabled || isVideoExportEnabled)) { float recordedSeconds = static_cast(diskModeFrameIdx) / 60.0f; ImGui::TextColored(ImVec4(0.8f, 0.0f, 0.0f, 1.0f), "Frames: %d (%.2f s)", diskModeFrameIdx, recordedSeconds); } else if (myFrames.size() > 0 && !isSafeFramesEnabled) { float recordedSeconds = static_cast(myFrames.size()) / 60.0f; ImGui::TextColored(ImVec4(0.8f, 0.0f, 0.0f, 1.0f), "Frames: %d (%.2f s)", static_cast(myFrames.size()), recordedSeconds); } if (isFunctionRecording) { ImGui::Separator(); if (ImGui::Button("End Recording", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) { av_write_trailer(pFormatCtx); cleanupFFmpeg(); isFunctionRecording = false; lastVideoPath = outFileName; showSaveConfirmationDialog = true; if (myVar.pauseAfterRecording) { myVar.isTimePlaying = false; } if (myVar.cleanSceneAfterRecording) { myParam.pParticles.clear(); myParam.rParticles.clear(); } printf("Recording ended via button. File saved " "as '%s'\n", outFileName.c_str()); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Stop recording and save the video file"); } ImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress); if (ImGui::Button("Cancel Recording", ImVec2(ImGui::GetContentRegionAvail().x, 40.0f))) { cancelRecording = true; } ImGui::PopStyleColor(3); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Stop recording and discard " "the video file"); } } if (myFrames.size() > 0 && !isFunctionRecording && !isSafeFramesEnabled && isExportFramesEnabled && videoHasBeenSaved) { // Show different text based on export status // Note: Not Working, the exporting blocks the UI // TODO: if we manage to unblock the UI, we can use this std::string buttonText = isExportingFrames ? "Exporting..." : "Export Frames"; // Disable the button if export is in progress if (isExportingFrames) { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); } bool buttonClicked = ImGui::Button( buttonText.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 40.0f)); if (isExportingFrames) { ImGui::PopStyleVar(); } // Only process click if not currently exporting if (buttonClicked && !isExportingFrames) { exportMemoryFrames = !exportMemoryFrames; } // Add tooltip for better user feedback if (ImGui::IsItemHovered()) { if (isExportingFrames) { ImGui::SetTooltip("Export in progress, " "please wait..."); } else { ImGui::SetTooltip("Export frames from memory to disk " "as PNG files"); } } // Show different text based on export status std::string discardButtonText = isExportingFrames ? "Export in progress..." : "Discard Frames"; if (isExportingFrames) { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); } ImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress); bool discardButtonClicked = ImGui::Button(discardButtonText.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 40.0f)); ImGui::PopStyleColor(3); if (isExportingFrames) { ImGui::PopStyleVar(); } // Only process click if not currently exporting if (discardButtonClicked && !isExportingFrames) { deleteFrames = !deleteFrames; } // Add tooltip for discard button if (ImGui::IsItemHovered()) { if (isExportingFrames) { ImGui::SetTooltip("Cannot discard frames while " "export is in progress"); } else { ImGui::SetTooltip("Discard all frames from memory " "without saving"); } } } ImGui::PopFont(); ImGui::End(); } // Process frame export/discard actions if (exportMemoryFrames && videoHasBeenSaved && !actualSavedVideoFolder.empty() && !actualSavedVideoName.empty() && !isExportingFrames) { isExportingFrames = true; // Set flag to indicate export is in progress exportMemoryFramesToDisk(); // After export completes, clean up and close dialog exportMemoryFrames = false; // Reset the flag after processing isExportingFrames = false; // Clear the export in progress flag // Clear frames from memory and close the recording menu discardMemoryFrames(); } if (deleteFrames && !myFrames.empty() && !isExportingFrames) { discardMemoryFrames(); deleteFrames = false; // Reset the flag after processing } if (showSaveConfirmationDialog) { ImGui::SetNextWindowSize(ImVec2(400, 200), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - 200, screenH * 0.5f - 90), ImGuiCond_Appearing); ImGui::Begin("Save Recording?", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar); ImGui::PushFont(myVar.robotoMediumFont); ImGui::SetWindowFontScale(1.5f); ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "Save Recording?"); ImGui::Separator(); ImGui::Text("Do you want to save the recording?"); ImGui::Text("Current: %s", lastVideoPath.c_str()); ImGui::Separator(); ImGui::Text("Custom Name (optional):"); static char nameBuffer[256] = ""; ImGui::InputText("##CustomVideoName", nameBuffer, sizeof(nameBuffer)); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Leave empty to keep current name"); } ImGui::Separator(); if (ImGui::Button("Save", ImVec2(100, 30))) { if (isFunctionRecording) { printf("Warning: Cannot rename video while " "recording is " "active\\n"); } else { std::string customNameInput = std::string(nameBuffer); std::string finalVideoPath = lastVideoPath; if (!customNameInput.empty()) { std::string cleanedCustomName = customNameInput; cleanedCustomName.erase( std::remove_if(cleanedCustomName.begin(), cleanedCustomName.end(), [](char c) { return c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*' || c == '/' || c == '\\'; }), cleanedCustomName.end()); if (cleanedCustomName.length() >= 4 && cleanedCustomName.substr(cleanedCustomName.length() - 4) == ".mp4") { cleanedCustomName = cleanedCustomName.substr(0, cleanedCustomName.length() - 4); } if (!cleanedCustomName.empty() && cleanedCustomName != this->folderName) { std::string oldBaseName = this->folderName; std::string oldFolderPath = this->videoFolder; std::string oldVideoFileNameWithExt = oldBaseName + ".mp4"; std::string parentDir = "Videos"; size_t parentPathEndPos = oldFolderPath.find_last_of('/'); if (parentPathEndPos != std::string::npos) { parentDir = oldFolderPath.substr(0, parentPathEndPos); } std::string newBaseName = cleanedCustomName; std::string newFolderPath = parentDir + "/" + newBaseName; std::string newVideoFileNameWithExt = newBaseName + ".mp4"; bool folderRenamedSuccessfully = false; if (oldFolderPath != newFolderPath && std::filesystem::exists(oldFolderPath)) { try { if (std::filesystem::exists(newFolderPath)) { printf("Wa" "rn" "in" "g:" " T" "ar" "ge" "t " "fo" "ld" "er" " %" "s " "fo" "r " "re" "na" "me" " a" "lr" "ea" "dy" " e" "xi" "st" "s." " " "Ab" "or" "ti" "ng" " r" "en" "am" "e " "to" " p" "re" "ve" "nt" " d" "at" "a " "lo" "ss" "." "\\" "n", newFolderPath.c_str()); } else { std::filesystem::rename(oldFolderPath, newFolderPath); printf("Fo" "ld" "er" " r" "en" "am" "ed" " f" "ro" "m " "%s" " t" "o " "%s" "\\" "n", oldFolderPath.c_str(), newFolderPath.c_str()); this->videoFolder = newFolderPath; lastVideoPath = this->videoFolder + "/" + oldVideoFileNameWithExt; folderRenamedSuccessfully = true; } } catch (const std::filesystem::filesystem_error& e_folder) { printf("Error " "renaming " "folder %s " "to %s: " "%s\\n", oldFolderPath.c_str(), newFolderPath.c_str(), e_folder.what()); } } else if (oldFolderPath == newFolderPath) { folderRenamedSuccessfully = true; } else if (!std::filesystem::exists(oldFolderPath)) { printf("Error: Original " "folder %s not " "found. " "Cannot perform " "rename " "operations.\\n", oldFolderPath.c_str()); } if (folderRenamedSuccessfully) { std::string currentVideoFilePath = lastVideoPath; std::string targetVideoFilePath = this->videoFolder + "/" + newVideoFileNameWithExt; if (currentVideoFilePath != targetVideoFilePath && std::filesystem::exists(currentVideoFilePath)) { try { if (std::filesystem::exists(targetVideoFilePath) && currentVideoFilePath != targetVideoFilePath) { printf("Warning: Target video file %s " "already exists. " "Overwriting.\\n", targetVideoFilePath.c_str()); std::filesystem::remove(targetVideoFilePath); } std::filesystem::rename(currentVideoFilePath, targetVideoFilePath); printf("Vi" "de" "o " "fi" "le" " r" "en" "am" "ed" " t" "o " "%s" "\\" "n", targetVideoFilePath.c_str()); lastVideoPath = targetVideoFilePath; } catch (const std::filesystem::filesystem_error& e_video) { printf("Er" "ro" "r " "re" "na" "mi" "ng" " v" "id" "eo" " f" "il" "e " "fr" "om" " %" "s " "to" " %" "s:" " %" "s" "\\" "n", currentVideoFilePath.c_str(), targetVideoFilePath.c_str(), e_video.what()); } } else if (!std::filesystem::exists(currentVideoFilePath)) { printf("Warning: " "Video " "file %s " "not found " "for " "renaming." "\\n", currentVideoFilePath.c_str()); } finalVideoPath = lastVideoPath; if (isSafeFramesEnabled && isExportFramesEnabled && oldBaseName != newBaseName) { std::string oldFramePrefix = oldBaseName + "_frame" "_"; std::string newFramePrefix = newBaseName + "_frame" "_"; try { for (const auto& entry : std::filesystem::directory_iterator(this->videoFolder)) { if (entry.is_regular_file()) { std::string currentFrameFileName = entry.path().filename().string(); if (currentFrameFileName.rfind(oldFramePrefix, 0) == 0 && currentFrameFileName.length() > oldFramePrefix.length() && currentFrameFileName.substr( currentFrameFileName.length() - 4) == ".png") { std::string frameIndexAndExt = currentFrameFileName.substr( oldFramePrefix.length()); std::string newFrameFileName = newFramePrefix + frameIndexAndExt; std::string oldFramePath = entry.path().string(); std::string newFramePath = this->videoFolder + "/" + newFrameFileName; try { std::filesystem::rename(oldFramePath, newFramePath); } catch ( const std::filesystem::filesystem_error& e_frame) { printf("Warning: Failed to " "rename frame %s to " "%s: %s\\n", oldFramePath.c_str(), newFramePath.c_str(), e_frame.what()); } } } } } catch (const std::filesystem::filesystem_error& e_dir_iter) { printf("Er" "ro" "r " "it" "er" "at" "in" "g " "di" "re" "ct" "or" "y " "%s" " f" "or" " " "fr" "am" "e " "re" "na" "mi" "ng" ": " "%s" "\\" "n", this->videoFolder.c_str(), e_dir_iter.what()); } } this->folderName = newBaseName; } else { printf("Save operation " "may be incomplete " "due to " "folder rename " "failure.\\n"); finalVideoPath = lastVideoPath; } } else { finalVideoPath = lastVideoPath; } } else { finalVideoPath = lastVideoPath; } std::filesystem::path finalPathObj(finalVideoPath); actualSavedVideoFolder = finalPathObj.parent_path().string(); actualSavedVideoName = finalPathObj.filename().string(); std::filesystem::path videoNamePath(actualSavedVideoName); if (videoNamePath.has_extension() && videoNamePath.extension() == ".mp4") { actualSavedVideoName = videoNamePath.stem().string(); } videoHasBeenSaved = true; showSaveConfirmationDialog = false; nameBuffer[0] = '\0'; // Clear recording state to close the recording menu // Only discard frames if frame export is disabled if (!myFrames.empty() && !isExportFramesEnabled) { discardMemoryFrames(); } diskModeFrameIdx = 0; if (isExportFramesEnabled && isSafeFramesEnabled) { printf("Frames are in: %s with base " "name: %s\\n", this->videoFolder.c_str(), this->folderName.c_str()); } } } ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, UpdateVariables::colButtonRedActive); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UpdateVariables::colButtonRedActiveHover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UpdateVariables::colButtonRedActivePress); if (ImGui::Button("Discard", ImVec2(100, 30))) { if (!isFunctionRecording) { // Use the centralized discard function for comprehensive cleanup discardRecording(); // Handle video file cleanup if it exists outside the video folder try { if (std::filesystem::exists(lastVideoPath) && lastVideoPath != this->videoFolder) { if (std::filesystem::is_regular_file(lastVideoPath)) { std::filesystem::remove(lastVideoPath); printf("Discarded video file: %s\\n", lastVideoPath.c_str()); } else if (std::filesystem::is_directory(lastVideoPath)) { std::filesystem::remove_all(lastVideoPath); printf("Discarded (unexpected) directory at lastVideoPath: %s\\n", lastVideoPath.c_str()); } } } catch (const std::filesystem::filesystem_error& e) { printf("Error discarding video/folder: %s\\n", e.what()); } } showSaveConfirmationDialog = false; nameBuffer[0] = '\0'; // Clear recording state to close the recording menu if (!myFrames.empty()) { discardMemoryFrames(); } diskModeFrameIdx = 0; lastVideoPath.clear(); this->videoFolder.clear(); this->folderName.clear(); videoHasBeenSaved = false; isFunctionRecording = false; } ImGui::PopStyleColor(3); ImGui::PopFont(); ImGui::End(); } return isFunctionRecording; } ================================================ FILE: GalaxyEngine/src/globalLogic.cpp ================================================ #include "globalLogic.h" UpdateParameters myParam; UpdateVariables myVar; UI myUI; Physics physics; Physics3D physics3D; ParticleSpaceship ship; SPH sph; SPH3D sph3D; SaveSystem save; GESound geSound; Lighting lighting; CopyPaste copyPaste; RayMarcher rayMarcher; Field field; std::vector globalNodes; std::vector globalNodes3D; uint32_t globalId = 0; uint32_t globalShapeId = 1; uint32_t globalWallId = 1; #ifdef _MSC_VER #include #else #include #endif bool hasAVX2Support() { static int cached = -1; if (cached != -1) return cached; int cpuInfo[4]; #ifdef _MSC_VER __cpuidex(cpuInfo, 7, 0); #else __cpuid_count(7, 0, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]); #endif cached = (cpuInfo[1] & (1 << 5)) != 0; return cached; } // If someday light id gets added, don't forget to add the id to the copy paste code too //std::unordered_map NeighborSearch::idToIndex; //void flattenQuadtree(Quadtree* node, std::vector& flatList) { // if (!node) return; // // flatList.push_back(node); // // for (const auto& child : node->subGrids) { // flattenQuadtree(child.get(), flatList); // } //} // THIS FUNCTION IS MEANT FOR QUICK DEBUGGING WHERE YOU NEED TO CHECK A SPECIFIC PARTICLE'S VARIABLES void selectedParticleDebug() { for (size_t i = 0; i < myParam.pParticles.size(); i++) { ParticlePhysics& p = myParam.pParticles[i]; ParticleRendering& r = myParam.rParticles[i]; if (r.isSelected && myVar.timeFactor != 0.0f) { std::cout << "Size: " << r.previousSize << std::endl; } } } void pinParticles() { if (myVar.pinFlag) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { myParam.rParticles[i].isPinned = true; myParam.pParticles[i].vel *= 0.0f; myParam.pParticles[i].prevVel *= 0.0f; myParam.pParticles[i].acc *= 0.0f; myParam.pParticles[i].ke *= 0.0f; myParam.pParticles[i].prevKe *= 0.0f; } } myVar.pinFlag = false; } if (myVar.unPinFlag) { for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isSelected) { myParam.rParticles[i].isPinned = false; } } myVar.unPinFlag = false; } for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isPinned) { myParam.pParticles[i].vel *= 0.0f; myParam.pParticles[i].prevVel *= 0.0f; myParam.pParticles[i].acc *= 0.0f; myParam.pParticles[i].ke *= 0.0f; myParam.pParticles[i].prevKe *= 0.0f; } } } void pinParticles3D() { if (myVar.pinFlag) { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { myParam.rParticles3D[i].isPinned = true; myParam.pParticles3D[i].vel *= 0.0f; myParam.pParticles3D[i].prevVel *= 0.0f; myParam.pParticles3D[i].acc *= 0.0f; myParam.pParticles3D[i].ke *= 0.0f; myParam.pParticles3D[i].prevKe *= 0.0f; } } myVar.pinFlag = false; } if (myVar.unPinFlag) { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { myParam.rParticles3D[i].isPinned = false; } } myVar.unPinFlag = false; } for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles3D[i].isPinned) { myParam.pParticles3D[i].vel *= 0.0f; myParam.pParticles3D[i].prevVel *= 0.0f; myParam.pParticles3D[i].acc *= 0.0f; myParam.pParticles3D[i].ke *= 0.0f; myParam.pParticles3D[i].prevKe *= 0.0f; } } } void plyFileCreation(std::ofstream& file) { uint32_t visibleParticles = 0; for (size_t i = 0; i < myParam.pParticles.size(); i++) { if (myParam.rParticles[i].isDarkMatter) { continue; } visibleParticles++; } constexpr size_t headerSize = 200; constexpr size_t avgLineSize = 90; std::string buffer; buffer.reserve(headerSize + visibleParticles * avgLineSize); buffer += "ply\nformat ascii 1.0\nelement vertex "; buffer += std::to_string(visibleParticles); buffer += "\nproperty float x\nproperty float y\nproperty float z\nproperty uchar red\nproperty uchar green\nproperty uchar blue\nproperty float radius\nend_header\n"; char lineBuffer[64]; for (size_t i = 0; i < myParam.pParticles.size(); i++) { ParticlePhysics& p = myParam.pParticles[i]; ParticleRendering& r = myParam.rParticles[i]; if (r.isDarkMatter) { continue; } float posX = ((p.pos.x / myVar.domainSize.x) * 2.0f) - 1.0f; float posY = ((p.pos.y / myVar.domainSize.y) * 2.0f) - 1.0f; float domainRatio = myVar.domainSize.x / myVar.domainSize.y; posX *= domainRatio; float sizeMultiplier = 10.0f; posX *= sizeMultiplier; posY *= sizeMultiplier; int len = sprintf(lineBuffer, "%.6g %.6g %.6g %d %d %d %.6g\n", posX, posY, 0.0f, static_cast(r.color.r), static_cast(r.color.g), static_cast(r.color.b), r.size); buffer.append(lineBuffer, len); } file << buffer; } void plyFileCreation3D(std::ofstream& file) { uint32_t visibleParticles = 0; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isDarkMatter) { continue; } visibleParticles++; } constexpr size_t headerSize = 200; constexpr size_t avgLineSize = 90; std::string buffer; buffer.reserve(headerSize + visibleParticles * avgLineSize); buffer += "ply\nformat ascii 1.0\nelement vertex "; buffer += std::to_string(visibleParticles); buffer += "\nproperty float x\nproperty float y\nproperty float z\nproperty uchar red\nproperty uchar green\nproperty uchar blue\nproperty float radius\nend_header\n"; char lineBuffer[64]; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { ParticlePhysics3D& p = myParam.pParticles3D[i]; ParticleRendering3D& r = myParam.rParticles3D[i]; if (r.isDarkMatter) { continue; } float posX = p.pos.x; float posY = p.pos.y; float posZ = p.pos.z; float sizeMultiplier = 1.0f; posX *= sizeMultiplier; posY *= sizeMultiplier; posZ *= sizeMultiplier; int len = sprintf(lineBuffer, "%.6g %.6g %.6g %d %d %d %.6g\n", posX, posY, posZ, static_cast(r.color.r), static_cast(r.color.g), static_cast(r.color.b), r.size); buffer.append(lineBuffer, len); } file << buffer; } void exportPly() { static bool wasExportingLastFrame = false; static std::filesystem::path currentSequenceDir; static int currentFrameNumber = 0; static int currentSequenceNumber = -1; if (myVar.exportPlySeqFlag) { if (!wasExportingLastFrame) { std::filesystem::path mainExportDir = "Export3D"; if (!std::filesystem::exists(mainExportDir)) { if (!std::filesystem::create_directory(mainExportDir)) { std::cerr << "Couldn't create Export3D directory" << std::endl; return; } } int maxSequence = -1; for (const auto& entry : std::filesystem::directory_iterator(mainExportDir)) { if (entry.is_directory()) { std::string folderName = entry.path().filename().string(); if (folderName.rfind("GESequence_", 0) == 0) { std::string numberStr = folderName.substr(11); try { int number = std::stoi(numberStr); if (number > maxSequence) { maxSequence = number; } } catch (...) { } } } } currentSequenceNumber = maxSequence + 1; std::string subfolderName = "GESequence_" + std::to_string(currentSequenceNumber); currentSequenceDir = mainExportDir / subfolderName; if (!std::filesystem::create_directory(currentSequenceDir)) { std::cerr << "Couldn't create new sequence folder: " << currentSequenceDir << std::endl; return; } currentFrameNumber = 0; std::cout << "Started new sequence export: " << currentSequenceDir << std::endl; } std::string customName = "GEParticles_"; std::ostringstream filenameStream; filenameStream << customName << std::setw(3) << std::setfill('0') << currentSequenceNumber << "_" << std::setw(4) << std::setfill('0') << currentFrameNumber << ".ply"; std::filesystem::path filePath = currentSequenceDir / filenameStream.str(); std::ofstream plyFile(filePath); if (!plyFile) { std::cerr << "Couldn't write file: " << filePath << std::endl; return; } if (!myVar.is3DMode) { plyFileCreation(plyFile); } else { plyFileCreation3D(plyFile); } plyFile.close(); std::cout << "Particles at frame " << currentFrameNumber << " exported to " << filePath << std::endl; currentFrameNumber++; myVar.plyFrameNumber = currentFrameNumber; } else { myVar.plyFrameNumber = 0; } wasExportingLastFrame = myVar.exportPlySeqFlag; if (myVar.exportPlyFlag) { std::filesystem::path exportDir = "Export3D"; if (!std::filesystem::exists(exportDir)) { if (!std::filesystem::create_directory(exportDir)) { std::cerr << "Couldn't create Export3D directory" << std::endl; return; } } std::filesystem::path exportDirIndividual = exportDir / "IndividualFiles"; if (!std::filesystem::exists(exportDirIndividual)) { if (!std::filesystem::create_directory(exportDirIndividual)) { std::cerr << "Couldn't create IndividualFiles directory" << std::endl; return; } } std::string namePrefix = "ParticlesOut_"; int maxNumber = 0; for (const auto& entry : std::filesystem::directory_iterator(exportDirIndividual)) { if (entry.is_regular_file()) { std::string filename = entry.path().filename().string(); if (filename.rfind(namePrefix, 0) == 0 && filename.size() > namePrefix.size() + 4 && filename.substr(filename.size() - 4) == ".ply") { size_t startPos = namePrefix.size(); size_t length = filename.size() - startPos - 4; std::string numberStr = filename.substr(startPos, length); try { int number = std::stoi(numberStr); if (number > maxNumber) { maxNumber = number; } } catch (const std::invalid_argument&) { } } } } int nextNumber = maxNumber + 1; std::filesystem::path filePath = exportDirIndividual / (namePrefix + std::to_string(nextNumber) + ".ply"); std::ofstream plyFile(filePath); if (!plyFile) { std::cerr << "Couldn't write file" << std::endl; return; } if (!myVar.is3DMode) { plyFileCreation(plyFile); } else { plyFileCreation3D(plyFile); } plyFile.close(); std::cout << "ply export successful! File saved as: " << filePath << std::endl; myVar.exportPlyFlag = false; } } //const char* computeTest = R"( //#version 430 // //layout(std430, binding = 0) buffer inPosVel { float posVel[]; }; // //layout(std430, binding = 2) buffer inMass { float mass[]; }; // //layout(local_size_x = 256) in; //const int tileSize = 256; // //uniform float dt; //uniform int pCount; // //const float G = 6.67430e-11; // //shared vec2 tilePos[tileSize]; //shared float tileMass[tileSize]; // //void main() { // uint idx = gl_GlobalInvocationID.x; // if (idx >= pCount) return; // // vec2 myPos = vec2(posVel[idx], posVel[idx + pCount]); // vec2 myAcc = vec2(0.0f); // // for (int tileStart = 0; tileStart < pCount; tileStart += tileSize) { // // uint localIdx = gl_LocalInvocationID.x; // uint globalTileIdx = tileStart + localIdx; // // if (globalTileIdx < pCount) { // tilePos[localIdx] = vec2(posVel[globalTileIdx], posVel[globalTileIdx + pCount]); // tileMass[localIdx] = mass[globalTileIdx]; // } else { // tilePos[localIdx] = vec2(0.0f); // tileMass[localIdx] = 0.0f; // } // // barrier(); // //int limit = min(tileSize, pCount - tileStart); // // for (int j = 0; j < limit; j++) { // uint otherIdx = tileStart + j; // if (otherIdx == idx) continue; // // vec2 d = tilePos[j] - myPos; // float rSq = dot(d, d) + 4.0f; // float invR = inversesqrt(rSq); // myAcc += G * tileMass[j] * d * invR * invR * invR; // } // } // // posVel[idx + 2 * pCount] += dt * 1.5f * myAcc.x; // posVel[idx + 3 * pCount] += dt * 1.5f * myAcc.y; // // posVel[idx] += posVel[idx + 2 * pCount] * dt; // posVel[idx + pCount] += posVel[idx + 3 * pCount] * dt; //} //)"; const char* computeTest = R"( #version 430 layout(std430, binding = 0) buffer inPData { float pData[]; }; layout(std430, binding = 2) buffer inMass { float mass[]; }; layout(std430, binding = 3) buffer inGrid { float grid[]; }; layout(std430, binding = 4) buffer inNext { uint next[]; }; struct GridChildren { uvec2 subGrids[2]; }; layout(std430, binding = 5) buffer inChildren { GridChildren children[]; }; layout(std430, binding = 6) buffer inPIdx { uint endStart[]; }; layout(local_size_x = 256) in; const int tileSize = 256; uniform float dt; uniform float theta; uniform float globalHeatConductivity; uniform int pCount; uniform int nCount; uniform bool periodicBoundary; uniform bool isTempEnabled; uniform vec2 domainSize; uniform float softening; const float G = 6.67430e-11; void main() { uint idx = gl_GlobalInvocationID.x; if (idx >= pCount) return; vec2 myPos = vec2(pData[idx], pData[idx + pCount]); vec2 totalForce = vec2(0.0f); uint gridIdx = 0; while (gridIdx < nCount) { if (grid[gridIdx + 2 * nCount] <= 0.0f) { gridIdx += next[gridIdx] + 1; continue; } vec2 d = vec2(grid[gridIdx], grid[gridIdx + nCount]) - myPos; if (periodicBoundary) { d.x -= domainSize.x * round(d.x / domainSize.x); d.y -= domainSize.y * round(d.y / domainSize.y); } float distSq = dot(d, d) + softening * softening; bool subgridsEmpty = true; for (int i = 0; i < 2; ++i) { uvec2 sg = children[gridIdx].subGrids[i]; for (int j = 0; j < 2; ++j) { uint childIdx = sg[j]; if (childIdx != 0xFFFFFFFFu) { subgridsEmpty = false; break; } } if (!subgridsEmpty) break; } if (grid[gridIdx + 3 * nCount] * grid[gridIdx + 3 * nCount] < (theta * theta) * distSq || subgridsEmpty) { float invDist = inversesqrt(distSq); float forceMag = G * mass[idx] * grid[gridIdx + 2 * nCount] * invDist * invDist * invDist; totalForce += d * forceMag; gridIdx += next[gridIdx] + 1; } else { gridIdx++; } } pData[idx + 2 * pCount] = totalForce.x / mass[idx]; pData[idx + 3 * pCount] = totalForce.y / mass[idx]; } )"; GLuint ssboPData, ssboAcc, ssboMass, ssboGrid, ssboGridNext, ssboGridChildren, ssboGridPIdx; size_t mb = 512; size_t reserveSize = (1024 * 1024 * mb) / sizeof(float); GLuint gravityProgram; struct GridChildren { uint32_t subGrids[2][2]; }; void gravityKernel() { glGenBuffers(1, &ssboPData); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPData); glBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(float), nullptr, GL_STREAM_COPY); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssboPData); glGenBuffers(1, &ssboMass); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboMass); glBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(float), nullptr, GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, ssboMass); glGenBuffers(1, &ssboGrid); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGrid); glBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(float), nullptr, GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, ssboGrid); glGenBuffers(1, &ssboGridNext); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridNext); glBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, ssboGridNext); glGenBuffers(1, &ssboGridChildren); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridChildren); glBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(GridChildren), nullptr, GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, ssboGridChildren); glGenBuffers(1, &ssboGridPIdx); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridPIdx); glBufferData(GL_SHADER_STORAGE_BUFFER, reserveSize * sizeof(uint32_t), nullptr, GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, ssboGridPIdx); gravityProgram = glCreateProgram(); GLuint shader = glCreateShader(GL_COMPUTE_SHADER); glShaderSource(shader, 1, &computeTest, 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(gravityProgram, shader); glLinkProgram(gravityProgram); glGetProgramiv(gravityProgram, GL_LINK_STATUS, &success); if (!success) { char infoLog[512]; glGetProgramInfoLog(gravityProgram, 512, nullptr, infoLog); std::cerr << "Shader gravityProgram linking failed:\n" << infoLog << std::endl; } glDeleteShader(shader); } void buildKernels() { gravityKernel(); field.fieldGravityDisplayKernel(); sph.neighborSearchKernel(); sph.bitonicSortKernel(); sph.offsetKernel(); sph.offsetResetKernel(); rayMarcher.Init(); } std::vector pData; std::vector massVector; std::vector gridParams; std::vector gridNext; std::vector gridChildrenVector; std::vector gridPIndices; void gpuGravity() { if (!myParam.pParticles.empty()) { pData.clear(); massVector.clear(); gridParams.clear(); gridNext.clear(); gridChildrenVector.clear(); gridPIndices.clear(); pData.resize(myParam.pParticles.size() * 4); massVector.resize(myParam.pParticles.size()); gridParams.resize(globalNodes.size() * 4); gridNext.resize(globalNodes.size()); gridChildrenVector.resize(globalNodes.size()); gridPIndices.resize(globalNodes.size() * 2); for (size_t i = 0; i < myParam.pParticles.size(); i++) { pData[i] = myParam.pParticles[i].pos.x; pData[i + myParam.pParticles.size()] = myParam.pParticles[i].pos.y; /*pData[i + 2 * myParam.pParticles.size()] = myParam.pParticles[i].vel.x; pData[i + 3 * myParam.pParticles.size()] = myParam.pParticles[i].vel.y;*/ pData[i + 2 * myParam.pParticles.size()] = 0.0f; pData[i + 3 * myParam.pParticles.size()] = 0.0f; massVector[i] = myParam.pParticles[i].mass; } for (size_t i = 0; i < globalNodes.size(); i++) { gridParams[i] = globalNodes[i].centerOfMass.x; gridParams[i + globalNodes.size()] = globalNodes[i].centerOfMass.y; gridParams[i + 2 * globalNodes.size()] = globalNodes[i].gridMass; gridParams[i + 3 * globalNodes.size()] = globalNodes[i].size; gridNext[i] = globalNodes[i].next; GridChildren children; memcpy(children.subGrids, globalNodes[i].subGrids, sizeof(uint32_t) * 4); gridChildrenVector[i] = children; gridPIndices[i] = globalNodes[i].startIndex; gridPIndices[i + globalNodes.size()] = globalNodes[i].endIndex; } glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPData); glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, pData.size() * sizeof(float), pData.data()); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboMass); glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, massVector.size() * sizeof(float), massVector.data()); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGrid); glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gridParams.size() * sizeof(float), gridParams.data()); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridNext); glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gridNext.size() * sizeof(uint32_t), gridNext.data()); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridChildren); glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gridChildrenVector.size() * sizeof(GridChildren), gridChildrenVector.data()); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboGridPIdx); glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gridPIndices.size() * sizeof(uint32_t), gridPIndices.data()); glUseProgram(gravityProgram); glUniform1f(glGetUniformLocation(gravityProgram, "dt"), myVar.timeFactor); glUniform1f(glGetUniformLocation(gravityProgram, "globalHeatConductivity"), myVar.globalHeatConductivity); glUniform1i(glGetUniformLocation(gravityProgram, "pCount"), static_cast(myParam.pParticles.size())); glUniform1i(glGetUniformLocation(gravityProgram, "nCount"), static_cast(globalNodes.size())); glUniform1i(glGetUniformLocation(gravityProgram, "periodicBoundary"), myVar.isPeriodicBoundaryEnabled); glUniform1i(glGetUniformLocation(gravityProgram, "isTempEnabled"), myVar.isTempEnabled); glUniform2f(glGetUniformLocation(gravityProgram, "domainSize"), myVar.domainSize.x, myVar.domainSize.y); glUniform1f(glGetUniformLocation(gravityProgram, "theta"), myVar.theta); glUniform1f(glGetUniformLocation(gravityProgram, "softening"), myVar.softening); GLuint numGroups = (myParam.pParticles.size() + 255) / 256; glDispatchCompute(numGroups, 1, 1); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssboPData); float* ptrPos = (float*)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY); #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < myParam.pParticles.size(); i++) { /*myParam.pParticles[i].pos.x = ptrPos[i]; myParam.pParticles[i].pos.y = ptrPos[i + myParam.pParticles.size()]; myParam.pParticles[i].vel.x = ptrPos[i + 2 * myParam.pParticles.size()]; myParam.pParticles[i].vel.y = ptrPos[i + 3 * myParam.pParticles.size()];*/ myParam.pParticles[i].acc.x = ptrPos[i + 2 * myParam.pParticles.size()]; myParam.pParticles[i].acc.y = ptrPos[i + 3 * myParam.pParticles.size()]; } glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); } } void freeGPUMemory() { glDeleteBuffers(1, &ssboPData); glDeleteBuffers(1, &ssboMass); glDeleteBuffers(1, &ssboGrid); glDeleteBuffers(1, &ssboGridNext); glDeleteBuffers(1, &ssboGridChildren); glDeleteBuffers(1, &ssboGridPIdx); glDeleteProgram(gravityProgram); glDeleteBuffers(1, &field.ssboParticlesPos); glDeleteBuffers(1, &field.ssboParticlesMass); glDeleteBuffers(1, &field.ssboCellsData); glDeleteProgram(field.gravityDisplayProgram); glDeleteBuffers(1, &sph.ssboPPos); glDeleteBuffers(1, &sph.ssboCellKeys); glDeleteBuffers(1, &sph.ssboCellXs); glDeleteBuffers(1, &sph.ssboCellYs); glDeleteBuffers(1, &sph.ssboParticleIndices); glDeleteProgram(sph.neighborSearchProgram); rayMarcher.Unload(); } // -------- This is an unused quadtree creation method I made for learning purposes. It builds the quadtree from Morton keys -------- // //struct Barrier { // uint32_t idx; // int level; // // Barrier(uint32_t idx, int level) { // this->idx = idx; // this->level = level; // } //}; // //struct MortonData { // uint64_t key; // float mass; // glm::vec2 pos; //}; // //std::vector TestTree::nodesTest; // //void mortonToQuadtree() { // // std::vector compactedMorton; // // float currentKeyMass = 0.0f; // uint32_t duplicateAmount = 0; // // for (size_t i = 0; i < myParam.pParticles.size(); i++) { // // if (!compactedMorton.empty()) { // if (myParam.pParticles[i].mortonKey != compactedMorton.back().key) { // // if (duplicateAmount > 0) { // compactedMorton.back().mass += currentKeyMass; // } // // compactedMorton.push_back({ myParam.pParticles[i].mortonKey, myParam.pParticles[i].mass, myParam.pParticles[i].pos }); // // currentKeyMass = 0.0f; // duplicateAmount = 0; // } // else { // duplicateAmount++; // currentKeyMass += myParam.pParticles[i].mass; // } // } // else { // compactedMorton.push_back({ myParam.pParticles[i].mortonKey, myParam.pParticles[i].mass, myParam.pParticles[i].pos }); // } // } // // int currentP = 0; // // int start = 0; // int end = compactedMorton.size(); // // int level = 0; // // int totalBits = 18; // 18 bits per axis // // std::vector barriers; // // glm::vec2 min = glm::vec2(std::numeric_limits::max()); // glm::vec2 max = glm::vec2(std::numeric_limits::lowest()); // // for (const ParticlePhysics& particle : myParam.pParticles) { // min = glm::min(min, particle.pos); // max = glm::max(max, particle.pos); // } // // float boundingBoxSize = glm::max(max.x - min.x, max.y - min.y); // // glm::vec2 center = (min + max) * 0.5f; // // glm::vec2 boundingBoxPos = center - boundingBoxSize * 0.5f; // // TestTree::nodesTest.clear(); // // TestTree::nodesTest.emplace_back(Node({ boundingBoxPos.x, boundingBoxPos.y }, boundingBoxSize, 0.0f, { 0.0f, 0.0f }, 0, 0)); // // while (currentP < compactedMorton.size()) { // // float nodeMass = 0.0f; // glm::vec2 nodeCoM = { 0.0f, 0.0f }; // // for (size_t i = start; i < end; i++) { // // nodeMass += compactedMorton[i].mass; // // nodeCoM += compactedMorton[i].pos * compactedMorton[i].mass; // // if (i + 1 >= compactedMorton.size()) { // // nodeCoM /= nodeMass; // // Node node = { TestTree::nodesTest[0].pos, TestTree::nodesTest[0].size, nodeMass, nodeCoM, 1, level + 1}; // // for (int j = 0; j < level + 1; j++) { // // int internalShift = (totalBits - 1 - j) * 2; // bool lr = (compactedMorton[start].key >> internalShift) & 1u; // bool ud = (compactedMorton[start].key >> (internalShift + 1)) & 1u; // // node.pos.x = node.pos.x + (lr ? (node.size * 0.5f) : 0.0f); // node.pos.y = node.pos.y + (ud ? (node.size * 0.5f) : 0.0f); // // node.size *= 0.5f; // } // // TestTree::nodesTest.push_back(node); // // TestTree::nodesTest[0].mass += nodeMass; // TestTree::nodesTest[0].CoM += nodeCoM; // // nodeMass = 0.0f; // nodeCoM = { 0.0f, 0.0f }; // // level++; // currentP++; // end = compactedMorton.size(); // break; // } // // if (!barriers.empty() && barriers.back().idx == i + 1) { // // nodeCoM /= nodeMass; // // uint32_t next = end - start; // // bool isLeaf = next == 1 ? 1 : 0; // // Node node = { TestTree::nodesTest[0].pos, TestTree::nodesTest[0].size, nodeMass, nodeCoM, next, level + 1}; // // for (int j = 0; j < level + 1; j++) { // // int internalShift = (totalBits - 1 - j) * 2; // bool lr = (compactedMorton[start].key >> internalShift) & 1u; // bool ud = (compactedMorton[start].key >> (internalShift + 1)) & 1u; // // node.pos.x = node.pos.x + (lr ? (node.size * 0.5f) : 0.0f); // node.pos.y = node.pos.y + (ud ? (node.size * 0.5f) : 0.0f); // // node.size *= 0.5f; // } // // TestTree::nodesTest.push_back(node); // // TestTree::nodesTest[0].mass += nodeMass; // TestTree::nodesTest[0].CoM += nodeCoM; // // nodeMass = 0.0f; // nodeCoM = { 0.0f, 0.0f }; // // int prevShift = (totalBits - 1 - level) * 2; // // unsigned int bits1 = (compactedMorton[i].key >> prevShift) & 0x3FFFFu; // unsigned int bits2 = (compactedMorton[i - 1].key >> prevShift) & 0x3FFFFu; // // if (bits1 == bits2) { // level++; // // currentP = start; // // end = barriers.back().idx; // // break; // } // // level = barriers.back().level; // // if (barriers.size() > 1) { // end = barriers.at(barriers.size() - 2).idx; // } // else { // end = compactedMorton.size(); // } // // currentP = barriers.back().idx; // // start = barriers.back().idx; // // TestTree::nodesTest[barriers.back().idx].next = TestTree::nodesTest.size(); // // barriers.pop_back(); // // break; // } // // int shift = (totalBits - 1 - level) * 2; // // unsigned int bits1 = (compactedMorton[i].key >> shift) & 0b11u; // unsigned int bits2 = (compactedMorton[i + 1].key >> shift) & 0b11u; // // if (bits1 != bits2 && i + 1 != end) { // // nodeCoM /= nodeMass; // // Node node = { TestTree::nodesTest[0].pos, TestTree::nodesTest[0].size, nodeMass, nodeCoM, 1, level + 1}; // // for (int j = 0; j < level + 1; j++) { // // int internalShift = (totalBits - 1 - j) * 2; // bool lr = (compactedMorton[start].key >> internalShift) & 1u; // bool ud = (compactedMorton[start].key >> (internalShift + 1)) & 1u; // // node.pos.x = node.pos.x + (lr ? (node.size * 0.5f) : 0.0f); // node.pos.y = node.pos.y + (ud ? (node.size * 0.5f) : 0.0f); // // node.size *= 0.5f; // } // // TestTree::nodesTest.push_back(node); // // TestTree::nodesTest[0].mass += nodeMass; // TestTree::nodesTest[0].CoM += nodeCoM; // // nodeMass = 0.0f; // nodeCoM = { 0.0f, 0.0f }; // // end = i + 1; // // if (end - start > 1) { // // barriers.push_back(Barrier(end, level)); // // level++; // currentP = start; // } // else if (end - start == 1) { // start = end; // // currentP = start; // // if (!barriers.empty()) { // end = barriers.back().idx; // } // else { // end = compactedMorton.size(); // } // } // // break; // } // } // } // // TestTree::nodesTest[0].CoM /= TestTree::nodesTest[0].mass; // // for (int i = totalBits; i >= 1; i--) { // for (int j = 0; j < TestTree::nodesTest.size(); j++) { // if (TestTree::nodesTest[j].level != i) continue; // // int currentLevel = TestTree::nodesTest[j].level; // int count = 0; // // for (int k = j + 1; k < TestTree::nodesTest.size(); k++) { // if (TestTree::nodesTest[k].level <= currentLevel) break; // // if (TestTree::nodesTest[k].level == currentLevel + 1) { // count += 1 + TestTree::nodesTest[k].next; // } // } // // TestTree::nodesTest[j].next = count; // } // } // // // /*static int idx = 1; // // if (IO::shortcutPress(KEY_LEFT)) { // idx--; // } // else if (IO::shortcutPress(KEY_RIGHT)) { // idx++; // }*/ // // /*std::cout << "Current Idx: " << idx << std::endl;*/ // // //for (size_t i = 0; i < TestTree::nodesTest.size(); i++) { // // //if (i == idx) { // // DrawRectangleLinesEx({ TestTree::nodesTest[i].pos.x, TestTree::nodesTest[i].pos.y, TestTree::nodesTest[i].size, TestTree::nodesTest[i].size }, 0.04f, RED); // // // // /*DrawText(TextFormat("%i", i), TestTree::nodesTest[i].pos.x + TestTree::nodesTest[i].size * 0.5f, // // TestTree::nodesTest[i].pos.y + TestTree::nodesTest[i].size * 0.5f, 5.0f, { 255, 255, 255, 128 });*/ // // // /*if (TestTree::nodesTest[i].mass > 0.0f) { // // DrawCircleV({ TestTree::nodesTest[i].CoM.x, TestTree::nodesTest[i].CoM.y }, 2.0f, { 180,50,50,128 }); // // }*/ // // //} // //} //} float boundingBoxSize = 0.0f; glm::vec2 boundingBoxPos = { 0.0f, 0.0f }; glm::vec3 boundingBox() { glm::vec2 min = glm::vec2(std::numeric_limits::max()); glm::vec2 max = glm::vec2(std::numeric_limits::lowest()); for (const ParticlePhysics& particle : myParam.pParticles) { min = glm::min(min, particle.pos); max = glm::max(max, particle.pos); } boundingBoxSize = glm::max(max.x - min.x, max.y - min.y); glm::vec2 center = (min + max) * 0.5f; boundingBoxPos = center - boundingBoxSize * 0.5f; return { boundingBoxPos.x, boundingBoxPos.y, boundingBoxSize }; } uint32_t gridRootIndex; glm::vec3 bb = { 0.0f, 0.0f, 0.0f }; void updateScene() { if (!myVar.is3DMode) { // If menu is active, do not use mouse input for non-menu stuff. I keep raylib's own mouse input for the menu but the custom IO for non-menu stuff if (myParam.rightClickSettings.isMenuActive) { ImGui::GetIO().WantCaptureMouse = true; } } if (myVar.isOpticsEnabled) { if (!myParam.pParticles.empty()) { myParam.pParticles.clear(); myParam.rParticles.clear(); } if (!myParam.pParticles3D.empty()) { myParam.pParticles3D.clear(); myParam.rParticles3D.clear(); } myVar.is3DMode = false; lighting.rayLogic(myVar, myParam); } if (myVar.isGravityFieldEnabled) { field.initializeCells(myVar); } myVar.G = 6.674e-11 * myVar.gravityMultiplier; if (IO::shortcutPress(KEY_SPACE)) { if (!myVar.isPlaybackOn) { myVar.isTimePlaying = !myVar.isTimePlaying; } else { myVar.runPlayback = !myVar.runPlayback; } } if (!myVar.is3DMode) { myParam.particlesSpawning.particlesInitialConditions(physics, myVar, myParam); } if (!myVar.is3DMode) { copyPaste.copyPasteParticles(myVar, myParam, physics); copyPaste.copyPasteOptics(myParam, lighting); } if (myVar.timeFactor != 0.0f) { physics.integrateStart(myParam.pParticles, myParam.rParticles, myVar); if (!myVar.isPeriodicBoundaryEnabled && !myVar.sphGround && !myVar.infiniteDomain) { physics.pruneParticles(myParam.pParticles, myParam.rParticles, myVar); } } if (myVar.timeFactor != 0.0f && !myParam.pParticles.empty()) { bb = boundingBox(); myParam.morton.computeMortonKeys(myParam.pParticles, bb); myParam.morton.sortParticlesByMortonKey(myParam.pParticles, myParam.rParticles); } if (myVar.timeFactor != 0.0f && !myVar.naive && !myParam.pParticles.empty()) { /*if (!myParam.pParticles.empty()) { mortonToQuadtree(); }*/ globalNodes.clear(); Quadtree root(myParam.pParticles, myParam.rParticles, bb); gridRootIndex = 0; } myVar.gridExists = gridRootIndex != -1 && !globalNodes.empty(); myVar.halfDomainWidth = myVar.domainSize.x * 0.5f; myVar.halfDomainHeight = myVar.domainSize.y * 0.5f; myVar.timeFactor = myVar.fixedDeltaTime * myVar.timeStepMultiplier * static_cast(myVar.isTimePlaying); if (myVar.drawQuadtree && !myVar.is3DMode) { for (uint32_t i = 0; i < globalNodes.size(); i++) { Node& q = globalNodes[i]; DrawLineV( { q.pos.x, q.pos.y }, { q.pos.x + q.size, q.pos.y }, WHITE ); DrawLineV( { q.pos.x, q.pos.y + q.size }, { q.pos.x + q.size, q.pos.y + q.size }, WHITE ); DrawLineV( { q.pos.x, q.pos.y }, { q.pos.x, q.pos.y + q.size }, WHITE ); DrawLineV( { q.pos.x + q.size, q.pos.y }, { q.pos.x + q.size, q.pos.y + q.size }, WHITE ); /*if (q.gridMass > 0.0f) { DrawCircleV({ q.centerOfMass.x, q.centerOfMass.y }, 2.0f, { 180,50,50,128 }); }*/ //DrawText(TextFormat("%i", i), q.pos.x + q.size * 0.5f, q.pos.y + q.size * 0.5f, 5.0f, { 255, 255, 255, 128 }); } } for (ParticleRendering& rParticle : myParam.rParticles) { rParticle.totalRadius = rParticle.size * myVar.particleTextureHalfSize * myVar.particleSizeMultiplier; } if (myVar.constraintsEnabled || myVar.drawConstraints || myVar.visualizeMesh || myVar.isBrushDrawing || myParam.colorVisuals.densityColor || myVar.isDensitySizeEnabled || myVar.isSPHEnabled || (myParam.colorVisuals.densityColor && myVar.timeFactor > 0.0f && !myVar.isGravityFieldEnabled)) { if (!myVar.hasAVX2) { myParam.neighborSearchV2.newGrid(myParam.pParticles); myParam.neighborSearchV2.neighborAmount(myParam.pParticles, myParam.rParticles); } else { myParam.neighborSearchV2AVX2.newGridAVX2(myParam.pParticles); myParam.neighborSearchV2AVX2.neighborAmount(myParam.pParticles, myParam.rParticles); } } if (myVar.isMergerEnabled) { myParam.neighborSearch.UpdateNeighbors(myParam.pParticles, myParam.rParticles); uint32_t maxPossibleId = 50000000; if (myVar.isMergerEnabled) { myParam.neighborSearch.densityRadius = 10.0f; } else { myParam.neighborSearch.densityRadius = myParam.neighborSearch.originalDensityRadius; } myParam.neighborSearch.idToIndexTable.resize(maxPossibleId + 1); #pragma omp parallel for for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.neighborSearch.idToIndexTable[myParam.pParticles[i].id] = i; } } if (!myVar.is3DMode) { myParam.brush.brushSize(); } if (myVar.isMergerEnabled) { // Heuristics for performance constexpr float minCellSize = 2.0f; constexpr float maxCellSize = 80.0f; myParam.neighborSearch.cellSize = 0.0f; for (size_t i = 0; i < myParam.pParticles.size(); ++i) { auto& rP = myParam.rParticles[i]; if (rP.isDarkMatter) { continue; } float candidate = rP.totalRadius * 2.0f; myParam.neighborSearch.cellSize = std::max(myParam.neighborSearch.cellSize, candidate); } myParam.neighborSearch.cellSize = std::clamp(myParam.neighborSearch.cellSize, minCellSize, maxCellSize); } else { myParam.neighborSearch.cellSize = 3.0f; } if (myVar.constraintsEnabled && !myVar.isBrushDrawing) { physics.createConstraints(myParam.pParticles, myParam.rParticles, myVar.constraintAfterDrawingFlag, myVar, myParam); } else if (!myVar.constraintsEnabled && !myVar.isBrushDrawing) { physics.constraintMap.clear(); physics.particleConstraints.clear(); physics.idToIndexTable.clear(); } if ((myVar.timeFactor != 0.0f /*&& myVar.gridExists*/) || myVar.isGPUEnabled) { #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < myParam.pParticles.size(); i++) { myParam.pParticles[i].acc = { 0.0f, 0.0f }; } if (myVar.gravityMultiplier != 0.0f || myVar.isTempEnabled) { if (!myVar.isGPUEnabled) { physics.flattenParticles(myParam.pParticles); if (!myVar.naive) { if (!myVar.hasAVX2) { if (myParam.pParticles.size() < 5000) { physics.naiveGravity(myParam.pParticles, myVar); } else { physics.calculateForceFromGrid(myVar); } } else { if (myParam.pParticles.size() < 5000) { physics.naiveGravityAVX2(myParam.pParticles, myVar); } else { physics.calculateForceFromGridAVX2(myVar); } } } else { if (!myVar.hasAVX2) { physics.naiveGravity(myParam.pParticles, myVar); } else { physics.naiveGravityAVX2(myParam.pParticles, myVar); } } physics.readFlattenBack(myParam.pParticles); } else { gpuGravity(); } } if (myVar.isMergerEnabled) physics.mergerSolver(myParam.pParticles, myParam.rParticles, myVar, myParam); if (myVar.isSPHEnabled) { sph.pcisphSolver(myVar, myParam); } physics.constraints(myParam.pParticles, myParam.rParticles, myVar); ship.spaceshipLogic(myParam.pParticles, myParam.rParticles, myVar.isShipGasEnabled); if (myVar.isTempEnabled) { physics.temperatureCalculation(myParam.pParticles, myParam.rParticles, myVar); } physics.integrateEnd(myParam.pParticles, myParam.rParticles, myVar); } else { physics.constraints(myParam.pParticles, myParam.rParticles, myVar); } field.gpuGravityDisplay(myParam, myVar); myParam.trails.trailLogic(myVar, myParam); myParam.myCamera.cameraFollowObject(myVar, myParam); myParam.particleSelection.clusterSelection(myVar, myParam, false); myParam.particleSelection.particleSelection(myVar, myParam, false); myParam.particleSelection.manyClustersSelection(myVar, myParam); myParam.particleSelection.boxSelection(myParam, myVar.is3DMode); myParam.particleSelection.invertSelection(myParam.rParticles); myParam.particleSelection.deselection(myParam.rParticles); myParam.particleSelection.selectedParticlesStoring(myParam); myParam.densitySize.sizeByDensity(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.isDensitySizeEnabled, myVar.isForceSizeEnabled, myVar.particleSizeMultiplier, myVar.is3DMode); myParam.particleDeletion.deleteSelected(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.is3DMode); myParam.particleDeletion.deleteStrays(myParam.pParticles, myParam.rParticles, myVar.isSPHEnabled, myParam.pParticles3D, myParam.rParticles3D, myVar.is3DMode); myParam.brush.particlesAttractor(myVar, myParam); myParam.brush.particlesSpinner(myVar, myParam); myParam.brush.particlesGrabber(myVar, myParam); myParam.brush.temperatureBrush(myVar, myParam); myParam.brush.eraseBrush(myVar, myParam); const float boundsX = 3840.0f; const float boundsY = 2160.0f; float targetX = myParam.myCamera.camera.target.x; float targetY = myParam.myCamera.camera.target.y; bool isOutsideBounds = (targetX >= boundsX || targetX <= -boundsX) || (targetY >= boundsY || targetY <= -boundsY); if (isOutsideBounds) { float distX = std::max(std::abs(targetX) - boundsX, 0.0f); float distY = std::max(std::abs(targetY) - boundsY, 0.0f); float maxDist = std::max(distX, distY); const float fadeRange = 10000.0f; float normalizedDist = 1.0f - std::min(maxDist / fadeRange, 1.0f); geSound.musicVolMultiplier = normalizedDist; } else { geSound.musicVolMultiplier = 1.0f; } pinParticles(); exportPly(); //selectedParticleDebug(); /*if (grid != nullptr) { delete grid; }*/ myParam.myCamera.hasCamMoved(); if (myVar.sphGround) { myVar.infiniteDomain = false; myVar.isPeriodicBoundaryEnabled = false; } } float boundingBox3DSize = 0.0f; glm::vec3 boundingBox3DPos = { 0.0f, 0.0f, 0.0f }; glm::vec4 boundingBox3D(std::vector& pParticles) { glm::vec3 min = glm::vec3(std::numeric_limits::max()); glm::vec3 max = glm::vec3(std::numeric_limits::lowest()); for (size_t i = 0; i < pParticles.size(); i++) { ParticlePhysics3D& particle = pParticles[i]; min = glm::min(min, particle.pos); max = glm::max(max, particle.pos); } boundingBox3DSize = glm::max(glm::max(max.x - min.x, max.y - min.y), max.z - min.z); glm::vec3 center = (min + max) * 0.5f; boundingBox3DPos = center - boundingBox3DSize * 0.5f; return { boundingBox3DPos.x, boundingBox3DPos.y, boundingBox3DPos.z, boundingBox3DSize }; } uint32_t gridRootIndex3D; glm::vec4 bb3D = { 0.0f, 0.0f, 0.0f, 0.0f }; void particleBoxClipping() { glm::vec3 avgPos = { 0.0f, 0.0f, 0.0f }; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { ParticlePhysics3D& p = myParam.pParticles3D[i]; ParticleRendering3D& r = myParam.rParticles3D[i]; if (!r.isSelected) { continue; } avgPos += p.pos; } avgPos /= myParam.pParticlesSelected3D.size(); for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { ParticlePhysics3D& p = myParam.pParticles3D[i]; ParticleRendering3D& r = myParam.rParticles3D[i]; if (!r.isSelected) { continue; } if (myVar.clipSelectedX) { if (!myVar.clipSelectedXInv) { if (p.pos.x >= avgPos.x) { r.color = { 0,0,0,0 }; } } else { if (p.pos.x <= avgPos.x) { r.color = { 0,0,0,0 }; } } } if (myVar.clipSelectedY) { if (!myVar.clipSelectedYInv) { if (p.pos.y >= avgPos.y) { r.color = { 0,0,0,0 }; } } else { if (p.pos.y <= avgPos.y) { r.color = { 0,0,0,0 }; } } } if (myVar.clipSelectedZ) { if (!myVar.clipSelectedZInv) { if (p.pos.z >= avgPos.z) { r.color = { 0,0,0,0 }; } } else { if (p.pos.z <= avgPos.z) { r.color = { 0,0,0,0 }; } } } } } void mode3D() { myVar.lowResRayMarching = false; if (!myParam.pParticles3D.empty()) { if (myVar.timeFactor != 0.0f) { myVar.lowResRayMarching = true; } if (myParam.myCamera3D.HasCameraChanged()) { myVar.lowResRayMarching = true; } } if (myVar.is3DMode) { // If menu is active, do not use mouse input for non-menu stuff. I keep raylib's own mouse input for the menu but the custom IO for non-menu stuff if (myParam.rightClickSettings.isMenuActive) { ImGui::GetIO().WantCaptureMouse = true; } } if (!myParam.playbackFrames.empty() && !myVar.playbackRecord) { myVar.isPlaybackOn = true; } else { myVar.isPlaybackOn = false; } myParam.particlesSpawning3D.particlesInitialConditions(physics3D, myVar, myParam); copyPaste.copyPasteParticles3D(myVar, myParam, physics3D); if (myVar.timeFactor != 0.0f && !myVar.isPlaybackOn) { physics3D.integrateStart3D(myParam.pParticles3D, myParam.rParticles3D, myVar); if (!myVar.isPeriodicBoundaryEnabled && !myVar.sphGround && !myVar.infiniteDomain) { physics3D.pruneParticles(myParam.pParticles3D, myParam.rParticles3D, myVar); } } if (myVar.timeFactor != 0.0f && !myParam.pParticles3D.empty()) { bb3D = boundingBox3D(myParam.pParticles3D); myParam.morton.computeMortonKeys3D(myParam.pParticles3D, bb3D); myParam.morton.sortParticlesByMortonKey3D(myParam.pParticles3D, myParam.rParticles3D); } if (myVar.timeFactor != 0.0f && !myVar.naive && !myVar.isPlaybackOn) { globalNodes3D.clear(); Octree root3D(myParam.pParticles3D, myParam.rParticles3D, glm::vec3{ bb3D.x,bb3D.y,bb3D.z }, bb3D.w); gridRootIndex3D = 0; } for (ParticleRendering3D& rParticle : myParam.rParticles3D) { rParticle.totalRadius = rParticle.size * myVar.particleTextureHalfSize * myVar.particleSizeMultiplier; } myVar.grid3DExists = gridRootIndex3D != -1 && !globalNodes3D.empty(); myVar.halfDomain3DWidth = myVar.domainSize3D.x * 0.5f; myVar.halfDomain3DHeight = myVar.domainSize3D.y * 0.5f; myVar.halfDomain3DDepth = myVar.domainSize3D.z * 0.5f; if (myVar.drawQuadtree && !myVar.isPlaybackOn) { for (uint32_t i = 0; i < globalNodes3D.size(); i++) { Node3D& q = globalNodes3D[i]; DrawCubeWiresV({ q.pos.x, q.pos.y, q.pos.z }, { q.size, q.size, q.size }, { 128,128,128,64 }); /*if (q.gridMass > 0.0f) { DrawCircleV({ q.centerOfMass.x, q.centerOfMass.y }, 2.0f, { 180,50,50,128 }); }*/ //DrawText(TextFormat("%i", i), q.pos.x + q.size * 0.5f, q.pos.y + q.size * 0.5f, 5.0f, { 255, 255, 255, 128 }); } } if (myVar.constraintsEnabled || myVar.drawConstraints || myVar.visualizeMesh || myVar.isBrushDrawing || myVar.isMergerEnabled || myParam.colorVisuals.densityColor || myVar.isDensitySizeEnabled || myVar.isSPHEnabled || myParam.colorVisuals.densityColor && myVar.timeFactor > 0.0f && !myVar.isGravityFieldEnabled && !myVar.isPlaybackOn) { if (!myVar.hasAVX2) { myParam.neighborSearch3DV2.newGrid(myParam.pParticles3D); myParam.neighborSearch3DV2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D); } else { myParam.neighborSearch3DV2AVX2.newGridAVX2(myParam.pParticles3D); myParam.neighborSearch3DV2AVX2.neighborAmount(myParam.pParticles3D, myParam.rParticles3D); } } myParam.brush3D.brushPosLogic(myParam, myVar); myParam.brush3D.brushSize(); if (myVar.constraintsEnabled && !myVar.isBrushDrawing) { physics3D.createConstraints(myParam.pParticles3D, myParam.rParticles3D, myVar.constraintAfterDrawingFlag, myVar, myParam); } else if (!myVar.constraintsEnabled && !myVar.isBrushDrawing) { physics3D.constraintMap.clear(); physics3D.particleConstraints.clear(); physics3D.idToIndexTable.clear(); } if ((myVar.timeFactor != 0.0f /*&& myVar.gridExists*/) && !myVar.isPlaybackOn) { #pragma omp parallel for schedule(dynamic) for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { myParam.pParticles3D[i].acc = { 0.0f, 0.0f, 0.0f }; } if (myVar.gravityMultiplier != 0.0f || myVar.isTempEnabled) { physics3D.flattenParticles3D(myParam.pParticles3D); if (!myVar.naive) { if (!myVar.hasAVX2) { if (myParam.pParticles3D.size() < 5000) { physics3D.naiveGravity3D(myParam.pParticles3D, myVar); } else { physics3D.calculateForceFromGrid3D(myVar); } } else { if (myParam.pParticles3D.size() < 5000) { physics3D.naiveGravity3DAVX2(myParam.pParticles3D, myVar); } else { physics3D.calculateForceFromGrid3DAVX2(myVar); } } } else { if (!myVar.hasAVX2) { physics3D.naiveGravity3D(myParam.pParticles3D, myVar); } else { physics3D.naiveGravity3DAVX2(myParam.pParticles3D, myVar); } } physics3D.readFlattenBack3D(myParam.pParticles3D); } if (myVar.isSPHEnabled) { sph3D.pcisphSolver(myVar, myParam); } physics3D.constraints(myParam.pParticles3D, myParam.rParticles3D, myVar); ship.spaceshipLogic3D(myParam.pParticles3D, myParam.rParticles3D, myVar.isShipGasEnabled, myParam.myCamera3D.cam3D); if (myVar.isTempEnabled) { physics3D.temperatureCalculation(myParam.pParticles3D, myParam.rParticles3D, myVar); } physics3D.integrateEnd3D(myParam.pParticles3D, myParam.rParticles3D, myVar); } else { physics3D.constraints(myParam.pParticles3D, myParam.rParticles3D, myVar); } myParam.trails.trailLogic3D(myVar, myParam); myParam.myCamera3D.cameraFollowObject(myVar, myParam); myParam.particleSelection3D.particleSelection(myVar, myParam, false); myParam.particleSelection3D.clusterSelection(myVar, myParam, false); myParam.particleSelection3D.manyClustersSelection(myVar, myParam); myParam.particleSelection3D.boxSelection(myParam); myParam.particleSelection3D.invertSelection(myParam.rParticles3D); myParam.particleSelection3D.deselection(myParam.rParticles3D); myParam.particleSelection3D.selectedParticlesStoring(myParam); if (!myVar.runPlayback) { myParam.densitySize.sizeByDensity(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.isDensitySizeEnabled, myVar.isForceSizeEnabled, myVar.particleSizeMultiplier, myVar.is3DMode); } myParam.particleDeletion.deleteSelected(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.is3DMode); myParam.particleDeletion.deleteStrays(myParam.pParticles, myParam.rParticles, myVar.isSPHEnabled, myParam.pParticles3D, myParam.rParticles3D, myVar.is3DMode); myParam.brush3D.particlesGrabber(myVar, myParam); myParam.brush3D.eraseBrush(myVar, myParam); myParam.brush3D.temperatureBrush(myVar, myParam); myParam.brush3D.particlesAttractor(myVar, myParam); myParam.brush3D.particlesSpinner(myVar, myParam); pinParticles3D(); if (myVar.isRayMarcherOn) { rayMarcher.Run(myParam.myCamera3D, myParam.pParticles3D, myParam.rParticles3D, myVar.lowResRayMarching); } } void drawConstraints3D() { if (myVar.visualizeMesh) { struct MeshEdge { size_t i, j; glm::vec3 piPos, pjCorrectedPos; Color lineColor; float distSq; }; std::vector edges; for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { ParticlePhysics3D& pi = myParam.pParticles3D[i]; std::vector neighborIndices = QueryNeighbors3D::queryNeighbors3D(myParam, myVar.hasAVX2, 64, pi.pos); for (size_t j : neighborIndices) { size_t neighborIndex = j; if (neighborIndex == i) continue; ParticlePhysics3D& pj = myParam.pParticles3D[neighborIndex]; if (pi.id < pj.id) { glm::vec3 delta = pj.pos - pi.pos; glm::vec3 periodicDelta = delta; if (abs(delta.x) > myVar.domainSize3D.x * 0.5f) { periodicDelta.x += (delta.x > 0) ? -myVar.domainSize3D.x : myVar.domainSize3D.x; } if (abs(delta.y) > myVar.domainSize3D.y * 0.5f) { periodicDelta.y += (delta.y > 0) ? -myVar.domainSize3D.y : myVar.domainSize3D.y; } if (abs(delta.z) > myVar.domainSize3D.z * 0.5f) { periodicDelta.z += (delta.z > 0) ? -myVar.domainSize3D.z : myVar.domainSize3D.z; } glm::vec3 pjCorrectedPos = pi.pos + periodicDelta; glm::vec3 mid = (pi.pos + pjCorrectedPos) * 0.5f; Vector3 midRay = { mid.x, mid.y, mid.z }; MeshEdge edge; edge.i = i; edge.j = neighborIndex; edge.piPos = pi.pos; edge.pjCorrectedPos = pjCorrectedPos; edge.lineColor = ColorLerp(myParam.rParticles3D[i].color, myParam.rParticles3D[neighborIndex].color, 0.5f); edge.distSq = Vector3DistanceSqr(midRay, myParam.myCamera3D.cam3D.position); edges.push_back(edge); } } } std::sort(edges.begin(), edges.end(), [](const MeshEdge& a, const MeshEdge& b) { return a.distSq > b.distSq; }); for (const MeshEdge& edge : edges) { DrawLine3D( { edge.piPos.x, edge.piPos.y, edge.piPos.z }, { edge.pjCorrectedPos.x, edge.pjCorrectedPos.y, edge.pjCorrectedPos.z }, edge.lineColor ); } } if (myVar.drawConstraints && !physics3D.particleConstraints.empty()) { std::vector sortedConstraintIndices(physics3D.particleConstraints.size()); for (size_t i = 0; i < sortedConstraintIndices.size(); ++i) { sortedConstraintIndices[i] = i; } std::sort(sortedConstraintIndices.begin(), sortedConstraintIndices.end(), [&](size_t a, size_t b) { auto& constraintA = physics3D.particleConstraints[a]; auto& constraintB = physics3D.particleConstraints[b]; if (constraintA.id1 >= physics3D.idToIndexTable.size() || constraintA.id2 >= physics3D.idToIndexTable.size()) return false; if (constraintB.id1 >= physics3D.idToIndexTable.size() || constraintB.id2 >= physics3D.idToIndexTable.size()) return false; int64_t idx1A = physics3D.idToIndexTable[constraintA.id1]; int64_t idx2A = physics3D.idToIndexTable[constraintA.id2]; int64_t idx1B = physics3D.idToIndexTable[constraintB.id1]; int64_t idx2B = physics3D.idToIndexTable[constraintB.id2]; if (idx1A == -1 || idx2A == -1) return false; if (idx1B == -1 || idx2B == -1) return false; glm::vec3 midA = (myParam.pParticles3D[idx1A].pos + myParam.pParticles3D[idx2A].pos) * 0.5f; glm::vec3 midB = (myParam.pParticles3D[idx1B].pos + myParam.pParticles3D[idx2B].pos) * 0.5f; Vector3 posA = { midA.x, midA.y, midA.z }; Vector3 posB = { midB.x, midB.y, midB.z }; float distA = Vector3DistanceSqr(posA, myParam.myCamera3D.cam3D.position); float distB = Vector3DistanceSqr(posB, myParam.myCamera3D.cam3D.position); return distA > distB; }); for (size_t i = 0; i < sortedConstraintIndices.size(); i++) { size_t idx = sortedConstraintIndices[i]; auto& constraint = physics3D.particleConstraints[idx]; if (constraint.id1 >= physics3D.idToIndexTable.size() || constraint.id2 >= physics3D.idToIndexTable.size()) continue; int64_t idx1 = physics3D.idToIndexTable[constraint.id1]; int64_t idx2 = physics3D.idToIndexTable[constraint.id2]; if (idx1 == -1 || idx2 == -1) { continue; } ParticlePhysics3D& pi = myParam.pParticles3D[idx1]; ParticlePhysics3D& pj = myParam.pParticles3D[idx2]; glm::vec3 delta = pj.pos - pi.pos; glm::vec3 periodicDelta = delta; if (abs(delta.x) > myVar.domainSize3D.x * 0.5f) { periodicDelta.x += (delta.x > 0) ? -myVar.domainSize3D.x : myVar.domainSize3D.x; } if (abs(delta.y) > myVar.domainSize3D.y * 0.5f) { periodicDelta.y += (delta.y > 0) ? -myVar.domainSize3D.y : myVar.domainSize3D.y; } if (abs(delta.z) > myVar.domainSize3D.z * 0.5f) { periodicDelta.z += (delta.z > 0) ? -myVar.domainSize3D.z : myVar.domainSize3D.z; } glm::vec3 pjCorrectedPos = pi.pos + periodicDelta; Color lineColor; if (myVar.constraintStressColor) { float maxStress = 0.0f; if (myVar.constraintMaxStressColor > 0.0f) { maxStress = myVar.constraintMaxStressColor; } else { maxStress = constraint.resistance * myVar.globalConstraintResistance * constraint.restLength * 0.18f; } float stressMag = std::abs(constraint.displacement); float clampedStress = std::clamp(stressMag, 0.0f, maxStress); float normalizedStress = clampedStress / maxStress; float hue = (1.0f - normalizedStress) * 240.0f; lineColor = ColorFromHSV(hue, 1.0f, 1.0f); } else { lineColor = ColorLerp(myParam.rParticles3D[idx1].color, myParam.rParticles3D[idx2].color, 0.5f); } DrawLine3D({ pi.pos.x, pi.pos.y, pi.pos.z }, { pjCorrectedPos.x, pjCorrectedPos.y, pjCorrectedPos.z }, lineColor); } } } struct ParticleDepth { int index; float distSq; }; void savePlaybackToDisk(const std::string& filepath) { std::ofstream outFile(filepath, std::ios::binary); if (!outFile) { return; } size_t numFrames = myParam.playbackFrames.size(); outFile.write(reinterpret_cast(&numFrames), sizeof(numFrames)); for (const auto& frame : myParam.playbackFrames) { size_t numParticles = frame.size(); outFile.write(reinterpret_cast(&numParticles), sizeof(numParticles)); for (const auto& particle : frame) { outFile.write(reinterpret_cast(&particle.pos), sizeof(particle.pos)); outFile.write(reinterpret_cast(&particle.previousSize), sizeof(particle.previousSize)); outFile.write(reinterpret_cast(&particle.size), sizeof(particle.size)); outFile.write(reinterpret_cast(&particle.id), sizeof(particle.id)); outFile.write(reinterpret_cast(&particle.color), sizeof(particle.color)); } } outFile.close(); } void loadPlaybackFromDisk(const std::string& filepath) { std::ifstream inFile(filepath, std::ios::binary); if (!inFile) { return; } myParam.playbackFrames.clear(); size_t numFrames; inFile.read(reinterpret_cast(&numFrames), sizeof(numFrames)); myParam.playbackFrames.reserve(numFrames); for (size_t i = 0; i < numFrames; ++i) { size_t numParticles; inFile.read(reinterpret_cast(&numParticles), sizeof(numParticles)); std::vector frame; frame.reserve(numParticles); for (size_t j = 0; j < numParticles; ++j) { PlaybackParticle particle; inFile.read(reinterpret_cast(&particle.pos), sizeof(particle.pos)); inFile.read(reinterpret_cast(&particle.previousSize), sizeof(particle.previousSize)); inFile.read(reinterpret_cast(&particle.size), sizeof(particle.size)); inFile.read(reinterpret_cast(&particle.id), sizeof(particle.id)); inFile.read(reinterpret_cast(&particle.color), sizeof(particle.color)); frame.push_back(particle); } myParam.playbackFrames.push_back(std::move(frame)); } inFile.close(); } void playBackLogic(Texture2D& particleBlurTex) { static std::vector targetState; static std::unordered_map targetMap; static bool diskFramesLoaded = false; static bool wasRecording = false; if (!myParam.pParticles3D.empty() && myVar.playbackRecord && myVar.currentFrame % myVar.keyframeTickInterval == 0) { std::vector playbackParticles; playbackParticles.reserve(myParam.pParticles3D.size()); for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { ParticlePhysics3D& p = myParam.pParticles3D[i]; ParticleRendering3D& r = myParam.rParticles3D[i]; if (r.isDarkMatter) { continue; } playbackParticles.emplace_back(PlaybackParticle{ p.pos, r.previousSize, r.size, p.id, r.color }); } myParam.playbackFrames.push_back(playbackParticles); wasRecording = true; } myVar.currentFrame++; if (wasRecording && !myVar.playbackRecord) { if (!myVar.playBackOnMemory) { savePlaybackToDisk(myVar.playbackPath); } wasRecording = false; diskFramesLoaded = false; } if (!myVar.playbackRecord) { if (!myVar.playBackOnMemory && !diskFramesLoaded && !wasRecording) { loadPlaybackFromDisk(myVar.playbackPath); diskFramesLoaded = true; } if (!myParam.playbackFrames.empty()) { targetState.clear(); targetMap.clear(); if (myVar.runPlayback) { myVar.playbackProgress += myVar.playbackSpeed; } int indexA = (int)myVar.playbackProgress; int indexB = indexA + 1; if (indexB >= myParam.playbackFrames.size()) { myVar.playbackProgress = 0.0f; indexA = 0; indexB = 1; } float t = myVar.playbackProgress - indexA; const std::vector& frameA = myParam.playbackFrames[indexA]; const std::vector& frameB = myParam.playbackFrames[indexB]; std::unordered_map frameB_Lookup; frameB_Lookup.reserve(frameB.size()); for (const auto& p : frameB) { frameB_Lookup[p.id] = &p; } #pragma omp parallel { std::vector localBuffer; localBuffer.reserve(frameA.size() / omp_get_num_threads()); #pragma omp for nowait for (int i = 0; i < frameA.size(); ++i) { const auto& pA = frameA[i]; auto it = frameB_Lookup.find(pA.id); if (it != frameB_Lookup.end()) { const PlaybackParticle* pB = it->second; glm::vec3 finalPos; finalPos.x = pA.pos.x + (pB->pos.x - pA.pos.x) * t; finalPos.y = pA.pos.y + (pB->pos.y - pA.pos.y) * t; finalPos.z = pA.pos.z + (pB->pos.z - pA.pos.z) * t; float finalSize = pA.size + (pB->size - pA.size) * t; Color finalColor; finalColor.r = (unsigned char)(pA.color.r + (pB->color.r - pA.color.r) * t); finalColor.g = (unsigned char)(pA.color.g + (pB->color.g - pA.color.g) * t); finalColor.b = (unsigned char)(pA.color.b + (pB->color.b - pA.color.b) * t); finalColor.a = (unsigned char)(pA.color.a + (pB->color.a - pA.color.a) * t); localBuffer.push_back({ finalPos, pA.previousSize, finalSize, pA.id, finalColor }); } } #pragma omp critical { targetState.insert(targetState.end(), localBuffer.begin(), localBuffer.end()); } } targetMap.reserve(targetState.size()); for (int i = 0; i < targetState.size(); i++) { targetMap[targetState[i].id] = i; } std::vector targetUsed(targetState.size(), false); for (int i = (int)myParam.pParticles3D.size() - 1; i >= 0; i--) { uint32_t currentID = myParam.pParticles3D[i].id; auto it = targetMap.find(currentID); if (it != targetMap.end()) { int targetIndex = it->second; const PlaybackParticle& target = targetState[targetIndex]; myParam.pParticles3D[i].pos = target.pos; myParam.rParticles3D[i].color = target.color; myParam.rParticles3D[i].previousSize = target.previousSize; myParam.rParticles3D[i].size = target.size * myVar.playbackParticlesSizeMult; targetUsed[targetIndex] = true; } else { size_t lastIdx = myParam.pParticles3D.size() - 1; if (i != lastIdx) { myParam.pParticles3D[i] = myParam.pParticles3D[lastIdx]; myParam.rParticles3D[i] = myParam.rParticles3D[lastIdx]; } myParam.pParticles3D.pop_back(); myParam.rParticles3D.pop_back(); } } for (int i = 0; i < targetState.size(); i++) { if (!targetUsed[i]) { const PlaybackParticle& target = targetState[i]; ParticlePhysics3D newP; newP.id = target.id; newP.pos = target.pos; ParticleRendering3D newR; newR.color = target.color; newR.previousSize = target.previousSize; newR.size = target.size; myParam.pParticles3D.push_back(newP); myParam.rParticles3D.push_back(newR); } } } } if (myParam.colorVisuals.selectedColor && myVar.is3DMode && myVar.isPlaybackOn) { for (size_t i = 0; i < myParam.pParticles3D.size(); i++) { if (myParam.rParticles3D[i].isSelected) { myParam.rParticles3D[i].color = { 255, 20, 20, 255 }; } } } } void drawMode3DRecording(Texture2D& particleBlurTex) { Camera3D& cam3D = myParam.myCamera3D.cam3D; myParam.colorVisuals.particlesColorVisuals(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.isTempEnabled, myVar.timeFactor, myVar.is3DMode); playBackLogic(particleBlurTex); particleBoxClipping(); drawConstraints3D(); static std::vector sortedParticles; size_t particleCount = myParam.pParticles3D.size(); if (sortedParticles.size() != particleCount) { sortedParticles.resize(particleCount); } Vector3 camPos = cam3D.position; #pragma omp parallel for for (int i = 0; i < particleCount; ++i) { const auto& p = myParam.pParticles3D[i]; Vector3 pPos = { p.pos.x, p.pos.y, p.pos.z }; sortedParticles[i].index = i; sortedParticles[i].distSq = Vector3DistanceSqr(pPos, camPos); } std::sort(sortedParticles.begin(), sortedParticles.end(), [](const ParticleDepth& a, const ParticleDepth& b) { return a.distSq > b.distSq; }); Rectangle sourceRec = { 0.0f, 0.0f, (float)particleBlurTex.width, (float)particleBlurTex.height }; for (const auto& item : sortedParticles) { int idx = item.index; ParticlePhysics3D& pParticle3D = myParam.pParticles3D[idx]; ParticleRendering3D& rParticle3D = myParam.rParticles3D[idx]; float sizeValue = particleBlurTex.width * rParticle3D.size; DrawBillboardPro( cam3D, particleBlurTex, sourceRec, { pParticle3D.pos.x, pParticle3D.pos.y, pParticle3D.pos.z }, cam3D.up, { sizeValue, sizeValue }, { sizeValue / 2.0f, sizeValue / 2.0f }, 0.0f, rParticle3D.color ); } if (!myVar.isDensitySizeEnabled) { #pragma omp parallel for schedule(dynamic) for (int i = 0; i < myParam.pParticles3D.size(); ++i) { ParticlePhysics3D& pParticle = myParam.pParticles3D[i]; ParticleRendering3D& rParticle = myParam.rParticles3D[i]; if (rParticle.canBeResized || myVar.isMergerEnabled) { rParticle.size = rParticle.previousSize * myVar.particleSizeMultiplier; } else { rParticle.size = rParticle.previousSize; } } } myParam.subdivision.subdivideParticles3D(myVar, myParam); myParam.trails.drawTrail3D(myParam.rParticles3D, particleBlurTex, myParam.myCamera3D.cam3D); } void drawMode3DNonRecording() { if (!myVar.infiniteDomain) { DrawCubeWiresV({ 0.0f, 0.0f, 0.0f }, { myVar.domainSize3D.x, myVar.domainSize3D.y, myVar.domainSize3D.z }, GRAY); // Domain } myParam.brush3D.drawBrush(myVar.domainSize3D.y); if (myVar.toolSpawnGalaxy) { myParam.particlesSpawning3D.drawGalaxyDisplay(myParam); } // Z-Curves debug toggle if (myParam.pParticles3D.size() > 1 && myVar.drawZCurves) { for (size_t i = 0; i < myParam.pParticles3D.size() - 1; i++) { DrawLine3D({ myParam.pParticles3D[i].pos.x, myParam.pParticles3D[i].pos.y, myParam.pParticles3D[i].pos.z }, { myParam.pParticles3D[i + 1].pos.x,myParam.pParticles3D[i + 1].pos.y, myParam.pParticles3D[i + 1].pos.z }, WHITE); //DrawText(TextFormat("%i", i), static_cast(myParam.pParticles3D[i].pos.x), static_cast(myParam.pParticles3D[i].pos.y) - 10, 10, { 128,128,128,128 }); } } } void drawConstraints() { if (myVar.visualizeMesh) { rlBegin(RL_LINES); for (size_t i = 0; i < myParam.pParticles.size(); i++) { ParticlePhysics& pi = myParam.pParticles[i]; std::vector neighborIndices = QueryNeighbors::queryNeighbors(myParam, myVar.hasAVX2, 64, pi.pos); for (size_t j : neighborIndices) { size_t neighborIndex = j; if (neighborIndex == i) continue; ParticlePhysics& pj = myParam.pParticles[neighborIndex]; if (pi.id < pj.id) { glm::vec2 delta = pj.pos - pi.pos; glm::vec2 periodicDelta = delta; if (abs(delta.x) > myVar.domainSize.x * 0.5f) { periodicDelta.x += (delta.x > 0) ? -myVar.domainSize.x : myVar.domainSize.x; } if (abs(delta.y) > myVar.domainSize.y * 0.5f) { periodicDelta.y += (delta.y > 0) ? -myVar.domainSize.y : myVar.domainSize.y; } glm::vec2 pjCorrectedPos = pi.pos + periodicDelta; Color lineColor = ColorLerp(myParam.rParticles[i].color, myParam.rParticles[neighborIndex].color, 0.5f); rlColor4ub(lineColor.r, lineColor.g, lineColor.b, lineColor.a); rlVertex2f(pi.pos.x, pi.pos.y); rlVertex2f(pjCorrectedPos.x, pjCorrectedPos.y); } } } rlEnd(); } if (myVar.drawConstraints && !physics.particleConstraints.empty()) { rlBegin(RL_LINES); for (size_t i = 0; i < physics.particleConstraints.size(); i++) { auto& constraint = physics.particleConstraints[i]; if (constraint.id1 >= physics.idToIndexTable.size() || constraint.id2 >= physics.idToIndexTable.size()) continue; int64_t idx1 = physics.idToIndexTable[constraint.id1]; int64_t idx2 = physics.idToIndexTable[constraint.id2]; if (idx1 == -1 || idx2 == -1) { continue; } ParticlePhysics& pi = myParam.pParticles[idx1]; ParticlePhysics& pj = myParam.pParticles[idx2]; glm::vec2 delta = pj.pos - pi.pos; glm::vec2 periodicDelta = delta; if (std::abs(delta.x) > myVar.domainSize.x * 0.5f) { periodicDelta.x += (delta.x > 0) ? -myVar.domainSize.x : myVar.domainSize.x; } if (std::abs(delta.y) > myVar.domainSize.y * 0.5f) { periodicDelta.y += (delta.y > 0) ? -myVar.domainSize.y : myVar.domainSize.y; } glm::vec2 pjCorrectedPos = pi.pos + periodicDelta; Color lineColor; if (myVar.constraintStressColor) { float maxStress = 0.0f; if (myVar.constraintMaxStressColor > 0.0f) { maxStress = myVar.constraintMaxStressColor; } else { maxStress = constraint.resistance * myVar.globalConstraintResistance * constraint.restLength * 0.18f; } float stressMag = std::abs(constraint.displacement); float clampedStress = std::clamp(stressMag, 0.0f, maxStress); float normalizedStress = clampedStress / maxStress; float hue = (1.0f - normalizedStress) * 240.0f; lineColor = ColorFromHSV(hue, 1.0f, 1.0f); } else { lineColor = ColorLerp(myParam.rParticles[idx1].color, myParam.rParticles[idx2].color, 0.5f); } rlColor4ub(lineColor.r, lineColor.g, lineColor.b, lineColor.a); rlVertex2f(pi.pos.x, pi.pos.y); rlVertex2f(pjCorrectedPos.x, pjCorrectedPos.y); } rlEnd(); } } float introDuration = 3.0f; float fadeDuration = 2.0f; static float introStartTime = 0.0f; static float fadeStartTime = 0.0f; bool firstTimeOpened = true; int messageIndex = 0; // If you see this part of the code, please try to not mention it :) std::vector> introMessages = { {"Welcome to Galaxy Engine", 0}, {"Welcome to Galaxy Engine", 1}, {"Welcome back to Galaxy Engine", 2}, {"Welcome to Galaxy Engine", 3}, {"Welcome again", 4}, {"Welcome to Galaxy Engine", 5}, {"Welcome to Galaxy Engine", 6}, {"Welcome once again", 7}, {"Welcome back to Galaxy Engine", 8}, {"Oh, it is you again. Welcome", 9}, {"Welcome to Galaxy Engine", 10}, {"Welcome to Galaxy Engine", 11}, {"Welcome to Galaxy Engine", 12}, {"It is kind of cold out here", 13}, {"You know, I have never gone beyond the domain", 14}, {"It is you! Welcome back", 15}, {"It is lonely out here, welcome back", 16}, {"Watching space in action sure is ineffable", 17}, {"I wish I could fly through the galaxies", 18}, {"Oh, it is my space companion! Welcome", 19}, {"Hello! It is nice to have you back", 20}, {"I tried leaving the domain...", 21}, {"The outside of the domain is somehow darker", 22}, {"Most of the time all I see is empty space", 23}, {"I get to see the cosmos when you are around", 24}, {"I think I saw something while you were gone", 25}, {"What is the outside world like?", 26}, {"What do you like the most? Stars or planets?", 27}, {"Do you like galaxies?", 28}, {"I like galaxies. They are like magic clouds!", 29}, {"I think I will try to explore beyond the domain", 30}, {"Beyond the domain, things get quiet", 31}, {"I had a dream in which I was flying through space", 32}, {"Hi! Are you back to show me the cosmos?", 33}, {"Do you dream a lot?", 34}, {"I wish some of my dreams were real", 35}, {"I will try to leave the domain again", 36}, {"Before I leave, I'm going to put a welcome sign", 37}, {"A sign so you won't forget me!", 38}, {"I might be back if I don't find anything", 39}, {"It was really nice sharing your company!", 40}, {"I hope to see you again!", 41}, {"Farewell", 42}, }; void drawScene(Texture2D& particleBlurTex, RenderTexture2D& myRayTracingTexture, RenderTexture2D& myUITexture, RenderTexture2D& myMiscTexture, bool& fadeActive, bool& introActive) { if (!field.cells.empty() && !myParam.pParticles.empty() && myVar.isGravityFieldEnabled) { field.drawField(myParam, myVar); } if (!myVar.isGravityFieldEnabled) { if (!myVar.is3DMode) { Rectangle source = { 0.0f, 0.0f, static_cast(particleBlurTex.width), static_cast(particleBlurTex.height) }; for (int i = 0; i < myParam.pParticles.size(); ++i) { ParticlePhysics& pParticle = myParam.pParticles[i]; ParticleRendering& rParticle = myParam.rParticles[i]; // Texture size is set to 16 because that is the particle texture half size in pixels Rectangle dest = { pParticle.pos.x, pParticle.pos.y, particleBlurTex.width * rParticle.size, particleBlurTex.height * rParticle.size }; glm::vec2 origin = { particleBlurTex.width * 0.5f * rParticle.size, particleBlurTex.height * 0.5f * rParticle.size }; DrawTexturePro( particleBlurTex, source, dest, { origin.x, origin.y }, 0.0f, rParticle.color ); } if (!myVar.isDensitySizeEnabled) { #pragma omp parallel for schedule(dynamic) for (int i = 0; i < myParam.pParticles.size(); ++i) { ParticlePhysics& pParticle = myParam.pParticles[i]; ParticleRendering& rParticle = myParam.rParticles[i]; if (rParticle.canBeResized || myVar.isMergerEnabled) { rParticle.size = rParticle.previousSize * myVar.particleSizeMultiplier; } else { rParticle.size = rParticle.previousSize; } } } myParam.trails.drawTrail(myParam.rParticles, particleBlurTex); drawConstraints(); myParam.colorVisuals.particlesColorVisuals(myParam.pParticles, myParam.rParticles, myParam.pParticles3D, myParam.rParticles3D, myVar.isTempEnabled, myVar.timeFactor, myVar.is3DMode); } if (myVar.isOpticsEnabled) { for (Wall& wall : lighting.walls) { wall.drawWall(); } } } else { myVar.infiniteDomain = false; } EndTextureMode(); // Ray Tracing BeginTextureMode(myRayTracingTexture); ClearBackground({ 0,0,0,0 }); BeginMode2D(myParam.myCamera.camera); EndMode2D(); EndTextureMode(); //EVERYTHING INTENDED TO APPEAR WHILE RECORDING ABOVE //END OF PARTICLES RENDER PASS //-------------------------------------------------// //BEGINNNG OF UI RENDER PASS //EVERYTHING NOT INTENDED TO APPEAR WHILE RECORDING BELOW BeginTextureMode(myUITexture); ClearBackground({ 0,0,0,0 }); BeginMode2D(myParam.myCamera.camera); myVar.mouseWorldPos = glm::vec2(GetScreenToWorld2D(GetMousePosition(), myParam.myCamera.camera).x, GetScreenToWorld2D(GetMousePosition(), myParam.myCamera.camera).y); if (!myVar.is3DMode) { myParam.brush.drawBrush(myVar.mouseWorldPos); } if (myVar.toolSpawnGalaxy && !myVar.is3DMode) { myParam.particlesSpawning.drawGalaxyDisplay(myParam); } if (!myVar.is3DMode && !myVar.infiniteDomain) { DrawRectangleLinesEx({ 0,0, myVar.domainSize.x, myVar.domainSize.y }, 3, GRAY); } // Z-Curves debug toggle if (myParam.pParticles.size() > 1 && myVar.drawZCurves) { for (size_t i = 0; i < myParam.pParticles.size() - 1; i++) { DrawLineV({ myParam.pParticles[i].pos.x, myParam.pParticles[i].pos.y }, { myParam.pParticles[i + 1].pos.x,myParam.pParticles[i + 1].pos.y }, WHITE); //DrawText(TextFormat("%i", i), static_cast(myParam.pParticles[i].pos.x), static_cast(myParam.pParticles[i].pos.y) - 10, 10, { 128,128,128,128 }); } } if (myVar.isOpticsEnabled) { lighting.drawScene(); lighting.drawMisc(myVar, myParam); } EndMode2D(); // EVERYTHING NON-STATIC RELATIVE TO CAMERA ABOVE // EVERYTHING STATIC RELATIVE TO CAMERA BELOW if (myVar.isRayMarcherOn) { /*rayMarcher.initPixelGrid(); rayMarcher.cameraRays(myParam.myCamera3D, myParam.pParticles3D, myParam.rParticles3D); rayMarcher.drawPixels();*/ rayMarcher.rayMarchUI(); rayMarcher.Draw(myVar.lowResRayMarching); } if (!introActive) { myUI.uiLogic(myParam, myVar, sph, save, geSound, lighting, field, ship); } save.saveLoadLogic(myVar, myParam, sph, physics, physics3D, lighting, field); if (!myVar.is3DMode) { myParam.subdivision.subdivideParticles(myVar, myParam); } EndTextureMode(); BeginTextureMode(myMiscTexture); ClearBackground({ 0,0,0,0 }); // ---- Intro screen ---- // if (introActive) { if (introStartTime == 0.0f) { introStartTime = GetTime(); } float introElapsedTime = GetTime() - introStartTime; float fadeProgress = introElapsedTime / introDuration; if (introElapsedTime >= introDuration) { introActive = false; fadeStartTime = GetTime(); } DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), BLACK); const char* text = nullptr; if (messageIndex < introMessages.size()) { text = introMessages[messageIndex].first.c_str(); } else { text = "Welcome back to Galaxy Engine, friend"; } int fontSize = myVar.introFontSize; Font fontToUse = (myVar.customFont.texture.id != 0) ? myVar.customFont : GetFontDefault(); Vector2 textSize = MeasureTextEx(fontToUse, text, fontSize, 1.0f); int posX = (GetScreenWidth() - textSize.x) * 0.5f; int posY = (GetScreenHeight() - textSize.y) * 0.5f; float textAlpha; if (fadeProgress < 0.2f) { textAlpha = fadeProgress / 0.2f; } else if (fadeProgress > 0.8f) { textAlpha = 1.0f - ((fadeProgress - 0.8f) / 0.2f); } else { textAlpha = 1.0f; } Color textColor = Fade(WHITE, textAlpha); DrawTextEx(fontToUse, text, { static_cast(posX), static_cast(posY) }, fontSize, 1.0f, textColor); } else if (fadeActive) { float fadeElapsedTime = GetTime() - fadeStartTime; if (fadeElapsedTime >= fadeDuration) { fadeActive = false; } else { float alpha = 1.0f - (fadeElapsedTime / fadeDuration); alpha = Clamp(alpha, 0.0f, 1.0f); DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Fade(BLACK, alpha)); } } if (firstTimeOpened && !introActive) { messageIndex++; messageIndex = std::min(static_cast(messageIndex), introMessages.size()); firstTimeOpened = false; } // ---- Intro screen ---- // EndTextureMode(); } float lastGlobalVolume = -1.0f; float lastMenuVolume = -1.0f; float lastMusicVolume = -1.0f; int lastMessageIndex = -1; void saveConfigIfChanged() { if (geSound.globalVolume != lastGlobalVolume || geSound.menuVolume != lastMenuVolume || geSound.musicVolume != lastMusicVolume || messageIndex != lastMessageIndex) { saveConfig(); lastGlobalVolume = geSound.globalVolume; lastMenuVolume = geSound.menuVolume; lastMusicVolume = geSound.musicVolume; lastMessageIndex = messageIndex; } } void saveConfig() { if (!std::filesystem::exists("Config")) { std::filesystem::create_directory("Config"); } std::ofstream file("Config/config.txt"); YAML::Emitter out(file); out << YAML::BeginMap; out << YAML::Key << "Global Volume" << YAML::Value << geSound.globalVolume; out << YAML::Key << "Menu Volume" << YAML::Value << geSound.menuVolume; out << YAML::Key << "Music Volume" << YAML::Value << geSound.musicVolume; out << YAML::Key << "Message Index" << YAML::Value << messageIndex; out << YAML::EndMap; } void loadConfig() { YAML::Node config = YAML::LoadFile("Config/config.txt"); if (config["Global Volume"]) geSound.globalVolume = config["Global Volume"].as(); if (config["Menu Volume"]) geSound.menuVolume = config["Menu Volume"].as(); if (config["Music Volume"]) geSound.musicVolume = config["Music Volume"].as(); if (config["Message Index"]) messageIndex = config["Message Index"].as(); } void enableMultiThreading() { if (myVar.isMultiThreadingEnabled) { omp_set_num_threads(myVar.threadsAmount); } else { omp_set_num_threads(1); } } void fullscreenToggle(int& lastScreenWidth, int& lastScreenHeight, bool& wasFullscreen, bool& lastScreenState, RenderTexture2D& myParticlesTexture, RenderTexture2D& myUITexture) { if (myVar.fullscreenState != lastScreenState) { int monitor = GetCurrentMonitor(); if (!IsWindowMaximized()) SetWindowSize(static_cast(GetMonitorWidth(monitor)) * 0.5f, static_cast(GetMonitorHeight(monitor)) * 0.5f); else SetWindowSize(static_cast(GetMonitorWidth(monitor)) * 0.5f, static_cast(GetMonitorHeight(monitor)) * 0.5f); ToggleBorderlessWindowed(); wasFullscreen = IsWindowMaximized();; UnloadRenderTexture(myParticlesTexture); UnloadRenderTexture(myUITexture); lastScreenWidth = GetScreenWidth(); lastScreenHeight = GetScreenHeight(); lastScreenState = myVar.fullscreenState; myParticlesTexture = CreateFloatRenderTexture(lastScreenWidth, lastScreenHeight);; myUITexture = LoadRenderTexture(lastScreenWidth, lastScreenHeight); } int currentScreenWidth = GetScreenWidth(); int currentScreenHeight = GetScreenHeight(); if (currentScreenWidth != lastScreenWidth || currentScreenHeight != lastScreenHeight) { UnloadRenderTexture(myParticlesTexture); UnloadRenderTexture(myUITexture); myParticlesTexture = CreateFloatRenderTexture(currentScreenWidth, currentScreenHeight); myUITexture = LoadRenderTexture(currentScreenWidth, currentScreenHeight); lastScreenWidth = currentScreenWidth; lastScreenHeight = currentScreenHeight; } } RenderTexture2D CreateFloatRenderTexture(int w, int h) { RenderTexture2D fbo = { 0 }; glGenTextures(1, &fbo.texture.id); glBindTexture(GL_TEXTURE_2D, fbo.texture.id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_HALF_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glGenFramebuffers(1, &fbo.id); glBindFramebuffer(GL_FRAMEBUFFER, fbo.id); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.texture.id, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); fbo.texture.width = w; fbo.texture.height = h; fbo.texture.format = PIXELFORMAT_UNCOMPRESSED_R16G16B16A16; return fbo; } ================================================ FILE: GalaxyEngine/src/main.cpp ================================================ #include "globalLogic.h" #if defined(PLATFORM_DESKTOP) #define GLSL_VERSION 330 #else // PLATFORM_ANDROID, PLATFORM_WEB #define GLSL_VERSION 100 #endif int main(int argc, char** argv) { // SPH Materials initialization SPHMaterials::Init(); SetConfigFlags(FLAG_MSAA_4X_HINT); SetConfigFlags(FLAG_WINDOW_RESIZABLE); SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN); int threadsAvailable = std::thread::hardware_concurrency(); myVar.threadsAmount = static_cast(threadsAvailable * 0.5f); std::cout << "Threads available: " << threadsAvailable << std::endl; std::cout << "Thread amount set to: " << myVar.threadsAmount << std::endl; if (myVar.fullscreenState) { myVar.screenWidth = GetMonitorWidth(GetCurrentMonitor()); myVar.screenHeight = GetMonitorHeight(GetCurrentMonitor()); } InitWindow(myVar.screenWidth, myVar.screenHeight, "Galaxy Engine"); // ---- Config ---- // if (!std::filesystem::exists("Config")) { std::filesystem::create_directory("Config"); } if (!std::filesystem::exists("Config/config.txt")) { saveConfig(); } else { loadConfig(); } // ---- Audio ---- // geSound.loadSounds(); // ---- Icon ---- // Image icon = LoadImage("Textures/WindowIcon.png"); SetWindowIcon(icon); // ---- Textures & rlImGui ---- // rlImGuiSetup(true); Texture2D particleBlurTex = LoadTexture("Textures/ParticleBlur.png"); RenderTexture2D myParticlesTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight()); RenderTexture2D myRayTracingTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight()); RenderTexture2D myUITexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); RenderTexture2D myMiscTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); SetTargetFPS(myVar.targetFPS); // ---- Fullscreen ---- // int lastScreenWidth = GetScreenWidth(); int lastScreenHeight = GetScreenHeight(); bool wasFullscreen = IsWindowMaximized(); bool lastScreenState = false; // ---- Save ---- // // If "Saves" directory doesn't exist, then create one. This is done here to store the default parameters if (!std::filesystem::exists("Saves")) { std::filesystem::create_directory("Saves"); } save.saveFlag = true; save.saveSystem("Saves/DefaultSettings.bin", myVar, myParam, sph, physics, physics3D, lighting, field); save.saveFlag = false; // ---- ImGui ---- // ImGuiStyle& style = ImGui::GetStyle(); ImVec4* colors = style.Colors; // Button and window colors colors[ImGuiCol_WindowBg] = myVar.colWindowBg; colors[ImGuiCol_Button] = myVar.colButton; colors[ImGuiCol_ButtonHovered] = myVar.colButtonHover; colors[ImGuiCol_ButtonActive] = myVar.colButtonPress; // Slider colors style.Colors[ImGuiCol_SliderGrab] = myVar.colSliderGrab; // Bright cyan-ish knob style.Colors[ImGuiCol_SliderGrabActive] = myVar.colSliderGrabActive; // Darker when dragging style.Colors[ImGuiCol_FrameBg] = myVar.colSliderBg; // Dark track when idle style.Colors[ImGuiCol_FrameBgHovered] = myVar.colSliderBgHover; // Lighter track on hover style.Colors[ImGuiCol_FrameBgActive] = myVar.colSliderBgActive; // Even lighter on active // Tab colors style.Colors[ImGuiCol_Tab] = myVar.colButton; style.Colors[ImGuiCol_TabHovered] = myVar.colButtonHover; style.Colors[ImGuiCol_TabActive] = myVar.colButtonPress; ImGuiIO& io = ImGui::GetIO(); ImFontConfig config; config.SizePixels = 14.0f; // Base font size in pixels config.RasterizerDensity = 2.0f; // Improves rendering at small sizes config.OversampleH = 4; // Horizontal anti-aliasing config.OversampleV = 4; // Vertical anti-aliasing config.PixelSnapH = false; // Disable pixel snapping for smoother text config.RasterizerMultiply = 0.9f; // Slightly boosts brightness myVar.robotoMediumFont = io.Fonts->AddFontFromFileTTF( "fonts/Roboto-Medium.ttf", config.SizePixels, &config, io.Fonts->GetGlyphRangesDefault() ); if (!myVar.robotoMediumFont) { std::cerr << "Failed to load special font!\n"; } else { std::cout << "Special font loaded successfully\n"; } io.Fonts->Build(); ImPlot::CreateContext(); // ---- Intro ---- // bool fadeActive = true; bool introActive = true; myVar.customFont = LoadFontEx("fonts/Unispace Bd.otf", myVar.introFontSize, 0, 250); SetTextureFilter(myVar.customFont.texture, TEXTURE_FILTER_BILINEAR); if (myVar.customFont.texture.id == 0) { TraceLog(LOG_WARNING, "Failed to load font! Using default font"); } if (myVar.fullscreenState) { myVar.screenWidth = GetMonitorWidth(GetCurrentMonitor()) * 0.5f; myVar.screenHeight = GetMonitorHeight(GetCurrentMonitor()) * 0.5f; } // ---- Ray Tracing and Long Exposure ---- // const char* accumulationVs = R"( #version 330 core layout(location = 0) in vec3 vertexPosition; layout(location = 1) in vec2 vertexTexCoord; layout(location = 3) in vec4 vertexColor; out vec2 fragTexCoord; out vec4 fragColor; uniform mat4 mvp; void main() { gl_Position = mvp * vec4(vertexPosition, 1.0); fragTexCoord = vertexTexCoord; fragColor = vertexColor; } )"; const char* accumulationFs = R"( #version 330 precision highp float; precision highp sampler2D; in vec2 fragTexCoord; out vec4 finalColor; uniform sampler2D currentFrame; uniform sampler2D accumulatedFrame; uniform float sampleCount; void main() { highp vec4 newColor = texture(currentFrame, fragTexCoord); highp vec4 oldColor = texture(accumulatedFrame, fragTexCoord); finalColor = (oldColor * (sampleCount - 1.0) + newColor) / sampleCount; finalColor = clamp(finalColor, 0.0, 1.0); } )"; Shader myBloom = LoadShader(accumulationVs, "Shaders/bloom.fs"); Shader accumulationShader = LoadShaderFromMemory(accumulationVs, accumulationFs); int screenSizeLoc = GetShaderLocation(accumulationShader, "screenSize"); float screenSize[2] = { (float)myVar.screenWidth, (float)myVar.screenHeight }; SetShaderValue(accumulationShader, screenSizeLoc, screenSize, SHADER_UNIFORM_VEC2); int rayTextureLoc = GetShaderLocation(accumulationShader, "rayTexture"); RenderTexture2D accumulatedTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight()); RenderTexture2D pingPongTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight()); int currentFrameLoc = GetShaderLocation(accumulationShader, "currentFrame"); int accumulatedFrameLoc = GetShaderLocation(accumulationShader, "accumulatedFrame"); int sampleCountLoc = GetShaderLocation(accumulationShader, "sampleCount"); RenderTexture2D testSampleTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); int prevScreenWidth = GetScreenWidth(); int prevScreenHeight = GetScreenHeight(); bool prevLongExpFlag = false; bool accumulationCondition = false; buildKernels(); myVar.hasAVX2 = hasAVX2Support(); std::cout << "--------------" << std::endl; if (myVar.hasAVX2) { std::cout << "Has AVX2 Support" << std::endl; } else { std::cout << "Doesn't have AVX2 Support" << std::endl; } std::cout << "--------------" << std::endl; rlSetClipPlanes(1.0f, 50000.0f); // ================= SKYBOX INITIALIZATION ================= // Mesh cubeSky = GenMeshCube(10000.0f, 10000.0f, 10000.0f); Model skybox = LoadModelFromMesh(cubeSky); // Load skybox shader skybox.materials[0].shader = LoadShader(TextFormat("Shaders/skybox.vs", GLSL_VERSION), TextFormat("Shaders/skybox.fs", GLSL_VERSION)); int environmentMap = MATERIAL_MAP_CUBEMAP; SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "environmentMap"), &environmentMap, SHADER_UNIFORM_INT); int doGamma = 0; SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "doGamma"), &doGamma, SHADER_UNIFORM_INT); int vflipped = 0; SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "vflipped"), &vflipped, SHADER_UNIFORM_INT); Image faces[6] = { LoadImage("Textures/sky_pos_x.png"), // +X LoadImage("Textures/sky_neg_x.png"), // -X LoadImage("Textures/sky_pos_y.png"), // +Y LoadImage("Textures/sky_neg_y.png"), // -Y LoadImage("Textures/sky_pos_z.png"), // +Z LoadImage("Textures/sky_neg_z.png") // -Z }; int width = faces[0].width; int height = faces[0].height; Image verticalStrip = GenImageColor(width, height * 6, BLACK); for (int i = 0; i < 6; i++) { ImageDraw(&verticalStrip, faces[i], (Rectangle) { 0, 0, (float)width, (float)height }, (Rectangle) { 0, (float)(i * height), (float)width, (float)height }, WHITE); UnloadImage(faces[i]); } skybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = LoadTextureCubemap(verticalStrip, CUBEMAP_LAYOUT_LINE_VERTICAL); UnloadImage(verticalStrip); while (!WindowShouldClose()) { if (myVar.exitGame) { CloseWindow(); break; } fullscreenToggle(lastScreenWidth, lastScreenHeight, wasFullscreen, lastScreenState, myParticlesTexture, myUITexture); BeginTextureMode(myParticlesTexture); ClearBackground({ 0, 0, 0, 0 }); BeginBlendMode(myParam.colorVisuals.blendMode); if (IO::shortcutPress(KEY_C)) { myParam.pParticles.clear(); myParam.rParticles.clear(); myParam.pParticles3D.clear(); myParam.rParticles3D.clear(); myParam.trails.segments.clear(); physics.posX.clear(); physics.posY.clear(); physics.accX.clear(); physics.accY.clear(); physics.velX.clear(); physics.velY.clear(); physics.prevVelX.clear(); physics.prevVelY.clear(); physics.mass.clear(); physics.temp.clear(); physics3D.posX.clear(); physics3D.posY.clear(); physics3D.accX.clear(); physics3D.accY.clear(); physics3D.velX.clear(); physics3D.velY.clear(); physics3D.prevVelX.clear(); physics3D.prevVelY.clear(); physics3D.mass.clear(); physics3D.temp.clear(); globalNodes.clear(); globalNodes3D.clear(); } if (myVar.is3DMode) { BeginMode3D(myParam.myCamera3D.cameraLogic(save.loadFlag, myVar.isMouseNotHoveringUI, myVar.firstPerson, ship.isShipEnabled)); rlDisableBackfaceCulling(); rlDisableDepthMask(); DrawModel(skybox, (Vector3) { 0, 0, 0 }, 1.0f, WHITE); rlEnableBackfaceCulling(); rlEnableDepthMask(); mode3D(); drawMode3DRecording(particleBlurTex); if (!myVar.isRecording) { drawMode3DNonRecording(); } EndMode3D(); } BeginMode2D(myParam.myCamera.cameraLogic(save.loadFlag, myVar.isMouseNotHoveringUI)); rlImGuiBegin(); if (introActive) { ImGuiIO& io = ImGui::GetIO(); io.WantCaptureMouse = true; io.WantCaptureKeyboard = true; io.WantTextInput = true; if (myParam.pParticles.size() > 0) { myParam.pParticles.clear(); myParam.rParticles.clear(); } } else { geSound.soundtrackLogic(); } ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2.0f); saveConfigIfChanged(); updateScene(); drawScene(particleBlurTex, myRayTracingTexture, myUITexture, myMiscTexture, fadeActive, introActive); EndMode2D(); EndBlendMode(); //------------------------ RENDER TEXTURES BELOW ------------------------// if (myParam.myCamera.cameraChangedThisFrame) { lighting.shouldRender = true; } if (myVar.longExposureFlag != prevLongExpFlag) { myVar.longExposureCurrent = 1; prevLongExpFlag = myVar.longExposureFlag; } if (myVar.isOpticsEnabled) { accumulationCondition = lighting.currentSamples <= lighting.maxSamples; } else if (myVar.longExposureFlag) { accumulationCondition = myVar.longExposureCurrent <= myVar.longExposureDuration; } else { accumulationCondition = true; } if (GetScreenWidth() != prevScreenWidth || GetScreenHeight() != prevScreenHeight) { UnloadRenderTexture(accumulatedTexture); UnloadRenderTexture(pingPongTexture); UnloadRenderTexture(testSampleTexture); accumulatedTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight()); pingPongTexture = CreateFloatRenderTexture(GetScreenWidth(), GetScreenHeight()); testSampleTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); screenSize[0] = (float)GetScreenWidth(); screenSize[1] = (float)GetScreenHeight(); SetShaderValue(accumulationShader, screenSizeLoc, screenSize, SHADER_UNIFORM_VEC2); prevScreenWidth = GetScreenWidth(); prevScreenHeight = GetScreenHeight(); if (myVar.isOpticsEnabled) { lighting.shouldRender = true; } if (myVar.longExposureFlag) { myVar.longExposureFlag = false; } } // Ray Tracing and Long Exposure if (accumulationCondition) { BeginTextureMode(pingPongTexture); rlDisableColorBlend(); BeginShaderMode(accumulationShader); SetShaderValueTexture(accumulationShader, currentFrameLoc, myParticlesTexture.texture); SetShaderValueTexture(accumulationShader, accumulatedFrameLoc, accumulatedTexture.texture); float sampleCount = 1.0f; if (myVar.isOpticsEnabled) { sampleCount = static_cast(lighting.currentSamples); } else { lighting.currentSamples = 0; } if (myVar.longExposureFlag) { sampleCount = static_cast(myVar.longExposureCurrent); myVar.longExposureCurrent++; } else { myVar.longExposureCurrent = 0; } SetShaderValue(accumulationShader, sampleCountLoc, &sampleCount, SHADER_UNIFORM_FLOAT); DrawTextureRec( accumulatedTexture.texture, Rectangle{ 0, 0, (float)GetScreenWidth(), -((float)GetScreenHeight()) }, Vector2{ 0, 0 }, WHITE ); EndShaderMode(); rlEnableColorBlend(); EndTextureMode(); std::swap(accumulatedTexture, pingPongTexture); } BeginDrawing(); ClearBackground(BLACK); if (myVar.flatParticleTexture3D || !myVar.is3DMode) { BeginBlendMode(BLEND_ALPHA_PREMULTIPLY); } int resolutionLoc = GetShaderLocation(myBloom, "res"); SetShaderValue(myBloom, resolutionLoc, (float[2]) { (float)GetScreenWidth(), (float)GetScreenHeight() }, SHADER_UNIFORM_VEC2); int glowSizeLoc = GetShaderLocation(myBloom, "glowSize"); SetShaderValue(myBloom, glowSizeLoc, &myVar.glowSize, SHADER_UNIFORM_INT); int glowStrengthLoc = GetShaderLocation(myBloom, "glowStrength"); SetShaderValue(myBloom, glowStrengthLoc, &myVar.glowStrength, SHADER_UNIFORM_FLOAT); if (myVar.isGlowEnabled) { BeginShaderMode(myBloom); } DrawTextureRec( accumulatedTexture.texture, Rectangle{ 0, 0, (float)GetScreenWidth(), -((float)GetScreenHeight()) }, Vector2{ 0, 0 }, WHITE ); if (myVar.isGlowEnabled) { EndShaderMode(); } if (myVar.flatParticleTexture3D || !myVar.is3DMode) { EndBlendMode(); } DrawTextureRec( myUITexture.texture, Rectangle{ 0, 0, static_cast(GetScreenWidth()), -static_cast(GetScreenHeight()) }, Vector2{ 0, 0 }, WHITE ); DrawTextureRec( myUITexture.texture, Rectangle{ 0, 0, static_cast(GetScreenWidth()), -static_cast(GetScreenHeight()) }, Vector2{ 0, 0 }, WHITE ); BeginTextureMode(testSampleTexture); ClearBackground(BLACK); if (myVar.flatParticleTexture3D || !myVar.is3DMode) { BeginBlendMode(BLEND_ALPHA_PREMULTIPLY); } DrawTextureRec( accumulatedTexture.texture, Rectangle{ 0, 0, (float)GetScreenWidth(), -((float)GetScreenHeight()) }, Vector2{ 0, 0 }, WHITE ); if (myVar.flatParticleTexture3D || !myVar.is3DMode) { EndBlendMode(); } EndTextureMode(); myVar.isRecording = myParam.screenCapture.screenGrab(testSampleTexture, myVar, myParam); if (myVar.isRecording) { DrawRectangleLinesEx({ 0,0, static_cast(GetScreenWidth()), static_cast(GetScreenHeight()) }, 3, RED); } ImGui::PopStyleVar(); rlImGuiEnd(); DrawTextureRec( myMiscTexture.texture, Rectangle{ 0, 0, static_cast(GetScreenWidth()), -static_cast(GetScreenHeight()) }, Vector2{ 0, 0 }, WHITE ); EndDrawing(); enableMultiThreading(); } rlImGuiShutdown(); ImPlot::DestroyContext(); UnloadShader(myBloom); UnloadTexture(particleBlurTex); UnloadRenderTexture(myParticlesTexture); UnloadRenderTexture(myRayTracingTexture); UnloadRenderTexture(myUITexture); UnloadRenderTexture(myMiscTexture); UnloadShader(skybox.materials[0].shader); UnloadTexture(skybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture); UnloadImage(icon); geSound.unloadSounds(); // Unload accumulation shader UnloadShader(accumulationShader); // Free compute shader memory freeGPUMemory(); if (std::filesystem::exists(myVar.playbackPath)) { std::filesystem::remove(myVar.playbackPath); } CloseWindow(); return 0; } ================================================ FILE: GalaxyEngine/src/parameters.cpp ================================================ #include "parameters.h" // Background color ImVec4 UpdateVariables::colWindowBg = ImVec4(0.05f, 0.043f, 0.071f, 0.9f); // Button colors ImVec4 UpdateVariables::colButton = ImVec4(0.22f, 0.23f, 0.36f, 1.0f); ImVec4 UpdateVariables::colButtonHover = ImVec4(0.3f, 0.4f, 0.8f, 1.0f); ImVec4 UpdateVariables::colButtonPress = ImVec4(0.5f, 0.6f, 0.9f, 1.0f); ImVec4 UpdateVariables::colButtonActive = ImVec4(0.25f, 0.6f, 0.2f, 1.0f); ImVec4 UpdateVariables::colButtonActiveHover = ImVec4(0.35f, 0.7f, 0.3f, 1.0f); ImVec4 UpdateVariables::colButtonActivePress = ImVec4(0.45f, 0.8f, 0.4f, 1.0f); ImVec4 UpdateVariables::colButtonRedActive = ImVec4(0.65f, 0.2f, 0.2f, 1.0f); ImVec4 UpdateVariables::colButtonRedActiveHover = ImVec4(0.75f, 0.3f, 0.3f, 1.0f); ImVec4 UpdateVariables::colButtonRedActivePress = ImVec4(0.85f, 0.4f, 0.4f, 1.0f); // Slider Colors ImVec4 UpdateVariables::colSliderGrab = ImVec4(0.32f, 0.33f, 0.46f, 1.0f); ImVec4 UpdateVariables::colSliderGrabActive = ImVec4(0.3f, 0.5f, 0.9f, 1.0f); ImVec4 UpdateVariables::colSliderBg = ImVec4(0.12f, 0.13f, 0.26f, 1.0f); ImVec4 UpdateVariables::colSliderBgHover = ImVec4(0.22f, 0.23f, 0.36f, 1.0f); ImVec4 UpdateVariables::colSliderBgActive = ImVec4(0.42f, 0.43f, 0.66f, 1.0f); // Plotline Colors ImVec4 UpdateVariables::colPlotLine = ImVec4(0.68f, 0.7f, 0.9f, 1.0f); ImVec4 UpdateVariables::colAxisText = ImVec4(1.0f, 0.8f, 1.0f, 1.0f); ImVec4 UpdateVariables::colAxisGrid = ImVec4(0.4f, 0.5f, 0.6f, 1.0f); ImVec4 UpdateVariables::colAxisBg = ImVec4(0.1f, 0.1f, 0.2f, 1.0f); ImVec4 UpdateVariables::colFrameBg = ImVec4(0.12f, 0.12f, 0.2f, 1.0f); ImVec4 UpdateVariables::colPlotBg = ImVec4(0.05f, 0.05f, 0.1f, 1.0f); ImVec4 UpdateVariables::colPlotBorder = ImVec4(1.0f, 0.0f, 1.0f, 1.0f); ImVec4 UpdateVariables::colLegendBg = ImVec4(0.1f, 0.1f, 0.1f, 1.0f); // Text Colors ImVec4 UpdateVariables::colMenuInformation = ImVec4(0.77f, 0.77f, 0.97f, 1.0f); // SPH Materials vector definition std::vector> SPHMaterials::materials; std::unordered_map SPHMaterials::idToMaterial; // Global particle base mass float UpdateVariables::particleBaseMass = 8500000000.0f; ================================================ FILE: GalaxyEngine/src/resources.rc ================================================ IDI_ICON1 ICON "GalaxyEngineIcon.ico" ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2025 Narcis Calin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Galaxy Engine ## About Join Galaxy Engine's [Discord community!](https://discord.gg/Xd5JUqNFPM) Check out the official [Steam page!](https://store.steampowered.com/app/3762210/Galaxy_Engine/) Galaxy Engine is a free and open source project made for learning purposes by [NarcisCalin](https://github.com/NarcisCalin). It is made with C++ and it is built on top of the [Raylib](https://github.com/raysan5/raylib) library. Galaxy Engine uses libraries from the [FFmpeg](https://github.com/FFmpeg/FFmpeg) project under the LGPLv2.1 The entire UI is made with the help of the [Dear ImGui](https://github.com/ocornut/imgui) library. Special thanks to [Crisosphinx](https://github.com/crisosphinx) for helping me improve my code. Special thanks to [SergioCelso](https://github.com/SCelso) for making the learning process a little easier and also for helping me implement the initial version of the Barnes-Hut quadtree. Special thanks to [Emily](https://github.com/Th3T3chn0G1t) for helping me on multiple occasions, including setting up the CMake configuration and assisting with various parts of the project. --- ![GalaxyCollisionField](https://github.com/user-attachments/assets/fa6d4eb8-7986-49dd-b274-e78ba03614bc) ![PlanetaryCollision](https://github.com/user-attachments/assets/15cd619c-3274-445c-9618-de88055d4ff0) ## Features --- ### INTERACTIVITY Galaxy Engine was built with interactivity in mind ![Interactivity](https://github.com/user-attachments/assets/ef3077a5-91c1-43fd-aa30-516b3e84fabc) ### SPH FLUID PHYSICS Galaxy Engine includes SPH fluid physics for different types of simulation ![PlanetsHQ](https://github.com/user-attachments/assets/0bbe27c9-c61d-435e-b20d-9cd055591e41) ![PlanetaryHQ2](https://github.com/user-attachments/assets/b185971f-0463-4086-831d-713c1d1c1a9c) ![AsteroidImpact](https://github.com/user-attachments/assets/d20d97a9-aaf7-4909-b73e-50d9945c17d3) ### REAL TIME PHYSICS Thanks to the Barnes-Hut algorithm, Galaxy Engine can simulate tens or even hundreds of thousands of particles in real time ![BH](https://github.com/user-attachments/assets/d1db1b59-ce03-46c5-9360-0a7b10199de4) ### RENDERING Engine Galaxy includes a recorder so you can compile videos showcasing millions of particles ![20Mil](https://github.com/user-attachments/assets/454af8b4-c8d5-4f9b-b2d6-a831c1dcc9f8) ## HOW TO INSTALL --- - Download the latest release version from the releases tab and unzip the zip file. - Run the executable file inside the folder. - (Currently Galaxy Engine binaries are only available on Windows) ### IMPORTANT INFORMATION - Galaxy Engine releases might get flagged as a virus by your browser or by Windows Defender. This is normal. It happens mainly because the program is not signed by Microsoft (Which costs money). If you don't trust the binaries, you can always compile Galaxy Engine yourself ## HOW TO BUILD --- ### Dependencies - [CMake](https://cmake.org/): On Windows, you can install it from [their website](https://cmake.org/download/) or the [Chocolatey cmake package](https://community.chocolatey.org/packages/cmake), and on Linux you can install the `cmake` package with your package manager. - A C++ compiler: Any compiler is probably gonna work, but Clang is recommended as it's known to produce a faster binary than GCC for this project. - [Clang](https://clang.llvm.org/): On Linux, you may install the `clang` package from a package manager. On Windows, it can be installed from [their website](https://clang.llvm.org/get_started.html), the [Chocolatey llvm package](https://community.chocolatey.org/packages/llvm), or from the Visual Studio Installer: ![image](https://github.com/user-attachments/assets/b46a0e7d-188e-43a3-bf7e-fb3edced233a) ### Basic instructions These instructions assume you have already met the above requirements. - Clone or download this repo - Build the project with CMake - After doing this you should have the executable file inside the build folder - The last step is running the executable file from the same working directory as the "Textures" folder (otherwise the particles won't be visible). This applies to the "Shaders" and "fonts" folder as well ================================================ FILE: cmake/ffmpeg.cmake ================================================ set(FFMPEG_URL_BASE https://github.com/BtbN/FFmpeg-Builds/releases/download/latest) if(WIN32) set(FFMPEG_ARCHIVE ffmpeg-master-latest-win64-lgpl-shared.zip) else() set(FFMPEG_ARCHIVE ffmpeg-master-latest-linux64-lgpl-shared.tar.xz) endif() FetchContent_Declare( ffmpeg-fetch URL ${FFMPEG_URL_BASE}/${FFMPEG_ARCHIVE} ) FetchContent_MakeAvailable(ffmpeg-fetch) add_library(ffmpeg INTERFACE) target_include_directories(ffmpeg SYSTEM INTERFACE ${ffmpeg-fetch_SOURCE_DIR}/include) target_link_directories(ffmpeg INTERFACE ${ffmpeg-fetch_SOURCE_DIR}/lib) target_link_directories(ffmpeg INTERFACE ${ffmpeg-fetch_SOURCE_DIR}/bin) target_link_libraries(ffmpeg INTERFACE avcodec avformat avutil swscale swresample ) ================================================ FILE: cmake/glm.cmake ================================================ set(GLM_VERSION 1.0.1) set(GLM_URL_BASE https://github.com/g-truc/glm/releases/download) FetchContent_Declare(glm-fetch URL ${GLM_URL_BASE}/${GLM_VERSION}/glm-1.0.1-light.zip) FetchContent_MakeAvailable(glm-fetch) add_library(glm-lib INTERFACE) target_link_libraries(glm-lib INTERFACE glm) target_include_directories(glm-lib INTERFACE ${glm-fetch_SOURCE_DIR}) ================================================ FILE: cmake/imgui.cmake ================================================ FetchContent_Declare( imgui-fetch GIT_REPOSITORY https://github.com/ocornut/imgui.git GIT_TAG docking ) FetchContent_MakeAvailable(imgui-fetch) FetchContent_Declare( implot-fetch GIT_REPOSITORY https://github.com/epezent/implot.git GIT_TAG v0.17 ) FetchContent_MakeAvailable(implot-fetch) FetchContent_Declare( rlgui-fetch GIT_REPOSITORY https://github.com/raylib-extras/rlImGui.git GIT_TAG main ) FetchContent_MakeAvailable(rlgui-fetch) add_library(imgui STATIC) target_include_directories(imgui PUBLIC ${imgui-fetch_SOURCE_DIR}) target_include_directories(imgui PUBLIC ${implot-fetch_SOURCE_DIR}) target_include_directories(imgui PUBLIC ${rlgui-fetch_SOURCE_DIR}) target_sources(imgui PRIVATE ${imgui-fetch_SOURCE_DIR}/imgui.cpp ${imgui-fetch_SOURCE_DIR}/imgui_tables.cpp ${imgui-fetch_SOURCE_DIR}/imgui_widgets.cpp ${imgui-fetch_SOURCE_DIR}/imgui_draw.cpp ${imgui-fetch_SOURCE_DIR}/imgui_demo.cpp) target_sources(imgui PRIVATE ${implot-fetch_SOURCE_DIR}/implot.cpp ${implot-fetch_SOURCE_DIR}/implot_items.cpp ) target_sources(imgui PRIVATE ${rlgui-fetch_SOURCE_DIR}/rlImGui.cpp ) target_link_libraries(imgui PUBLIC raylib-lib) ================================================ FILE: cmake/openmp.cmake ================================================ if(${CMAKE_VERSION} LESS 3.30 AND ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} STREQUAL MSVC) add_library(openmp INTERFACE) target_compile_options(openmp INTERFACE -openmp:llvm) target_link_libraries(openmp INTERFACE libomp) elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang AND NOT ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} STREQUAL MSVC) # Non-VC non-CL clang on Windows just does not want to cooperate with FindOpenMP. add_library(openmp INTERFACE) target_compile_options(openmp INTERFACE -fopenmp=libomp) target_link_options(openmp INTERFACE -fopenmp=libomp) else() set(OpenMP_RUNTIME_MSVC llvm) find_package(OpenMP REQUIRED COMPONENTS CXX) add_library(openmp INTERFACE) target_link_libraries(openmp INTERFACE OpenMP::OpenMP_CXX) endif() ================================================ FILE: cmake/raylib.cmake ================================================ set(RL_VERSION 5.5) set(RL_URL_BASE https://github.com/raysan5/raylib/releases/download) FetchContent_Declare(raylib GIT_REPOSITORY https://github.com/raysan5/raylib.git GIT_TAG master) FetchContent_MakeAvailable(raylib) add_library(raylib-lib INTERFACE) target_include_directories(raylib-lib INTERFACE ${raylib_SOURCE_DIR}/src) target_link_libraries(raylib-lib INTERFACE raylib) ================================================ FILE: cmake/yaml.cmake ================================================ include(FetchContent) FetchContent_Declare( yaml-cpp GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git GIT_TAG yaml-cpp-0.9.0 ) FetchContent_MakeAvailable(yaml-cpp)