├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── benchmark.txt ├── external └── glad │ ├── include │ ├── KHR │ │ └── khrplatform.h │ └── glad │ │ └── glad.h │ └── src │ └── glad.c ├── papers ├── Efficient Neighbor Search for Particle-based Fluids.pdf ├── Optimized Spatial Hashing for Collision Detection of Deformable Objects.pdf ├── Particle-based Viscoelastic Fluid Simulation.pdf └── Real-Time Physics 103 Fluid Simulation in Games – Brandon Pelfrey.maff └── src └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/glm"] 2 | path = external/glm 3 | url = https://github.com/g-truc/glm.git 4 | shallow = true 5 | [submodule "external/glfw"] 6 | path = external/glfw 7 | url = https://github.com/glfw/glfw.git 8 | shallow = true 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required( VERSION 3.10.0 ) 2 | project( sph-benchmark ) 3 | 4 | # external glfw 5 | set( GLFW_BUILD_DOCS OFF CACHE INTERNAL "" ) 6 | set( GLFW_BUILD_TESTS OFF CACHE INTERNAL "" ) 7 | set( GLFW_BUILD_EXAMPLES OFF CACHE INTERNAL "" ) 8 | add_subdirectory( external/glfw ) 9 | 10 | # make sure we have OpenMP C++ support 11 | find_package( OpenMP REQUIRED CXX ) 12 | 13 | # external includes 14 | include_directories( SYSTEM "external/glm" ) 15 | include_directories( SYSTEM "external/glad/include" ) 16 | 17 | # application code 18 | include_directories( "include" ) 19 | add_executable( 20 | sph-benchmark 21 | "src/main.cpp" 22 | "external/glad/src/glad.c" ) 23 | target_compile_features( 24 | sph-benchmark 25 | PUBLIC cxx_std_11 ) 26 | target_link_libraries( 27 | sph-benchmark 28 | PRIVATE glfw 29 | PRIVATE OpenMP::OpenMP_CXX ) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genpfault/sph-tutorial/373cbf849e3053d83592cb9572f53ef32b578086/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sph-tutorial 2 | ============ 3 | 4 | Experimenting with performance evaluation and improvements to Brandon Pelfrey's [smoothed-particle hydrodynamics (SPH)](https://en.wikipedia.org/wiki/Smoothed-particle_hydrodynamics) fluid simulation tutorial: 5 | 6 | * [Real-time Physics 101: Let’s Talk Particles](https://web.archive.org/web/20090530024753/http://blog.brandonpelfrey.com/?p=58) 7 | * [Real-time Physics 102: Springboard into Constraints](https://web.archive.org/web/20090531100829/http://blog.brandonpelfrey.com/?p=242) 8 | * [Real-Time Physics 103: Fluid Simulation in Games](https://web.archive.org/web/20090722233436/http://blog.brandonpelfrey.com/?p=303) 9 | 10 | ##### Requirements 11 | 12 | * CMake >= 3.10.0 13 | * C++11 compiler with OpenMP support 14 | * OpenGL runtime support 15 | 16 | ##### Controls 17 | 18 | * Q/Escape: Exit 19 | * Space: Add more particles 20 | * Mouse button: Attract nearby particles 21 | * +/-: Increase/decrease the number of simulation steps per frame 22 | 23 | You can use the [`OMP_NUM_THREADS` environment variable](https://gcc.gnu.org/onlinedocs/libgomp/OMP_005fNUM_005fTHREADS.html#OMP_005fNUM_005fTHREADS) to limit the number of threads used by OpenMP. 24 | 25 | ##### Dependencies 26 | 27 | Appropriate versions of [GLFW](https://www.glfw.org/) and [GLM](https://glm.g-truc.net/) are included as submodules so there's no need to install them separately. 28 | 29 | GLFW has some platform-specific build dependencies: 30 | 31 | * Linux 32 | 33 | # Debian & derivatives: 34 | sudo apt install \ 35 | libx11-dev \ 36 | libxrandr-dev \ 37 | libxinerama-dev \ 38 | libxcursor-dev \ 39 | libxi-dev \ 40 | 41 | * Windows 42 | 43 | TBD 44 | 45 | * macOS 46 | 47 | TBD 48 | 49 | ##### Building 50 | 51 | git clone --recurse-submodules https://github.com/genpfault/sph-tutorial.git 52 | cd sph-tutorial 53 | mkdir build 54 | cd build 55 | cmake ../ -DCMAKE_BUILD_TYPE=RelWithDebInfo 56 | cmake --build . 57 | -------------------------------------------------------------------------------- /benchmark.txt: -------------------------------------------------------------------------------- 1 | -------------------------------- 2 | Number of steps: 3000 3 | Number of particles: 1024 4 | Elapsed time: 823 milliseconds 5 | Microseconds per step: 274.374 6 | 7 | Number of particles: 2048 8 | Elapsed time: 1500 milliseconds 9 | Microseconds per step: 500.297 10 | 11 | Number of particles: 4096 12 | Elapsed time: 3013 milliseconds 13 | Microseconds per step: 1004.65 14 | 15 | Number of particles: 8192 16 | Elapsed time: 6871 milliseconds 17 | Microseconds per step: 2290.52 18 | 19 | -------------------------------------------------------------------------------- /external/glad/include/KHR/khrplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __khrplatform_h_ 2 | #define __khrplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2008-2018 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Khronos platform-specific types and definitions. 28 | * 29 | * The master copy of khrplatform.h is maintained in the Khronos EGL 30 | * Registry repository at https://github.com/KhronosGroup/EGL-Registry 31 | * The last semantic modification to khrplatform.h was at commit ID: 32 | * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 33 | * 34 | * Adopters may modify this file to suit their platform. Adopters are 35 | * encouraged to submit platform specific modifications to the Khronos 36 | * group so that they can be included in future versions of this file. 37 | * Please submit changes by filing pull requests or issues on 38 | * the EGL Registry repository linked above. 39 | * 40 | * 41 | * See the Implementer's Guidelines for information about where this file 42 | * should be located on your system and for more details of its use: 43 | * http://www.khronos.org/registry/implementers_guide.pdf 44 | * 45 | * This file should be included as 46 | * #include 47 | * by Khronos client API header files that use its types and defines. 48 | * 49 | * The types in khrplatform.h should only be used to define API-specific types. 50 | * 51 | * Types defined in khrplatform.h: 52 | * khronos_int8_t signed 8 bit 53 | * khronos_uint8_t unsigned 8 bit 54 | * khronos_int16_t signed 16 bit 55 | * khronos_uint16_t unsigned 16 bit 56 | * khronos_int32_t signed 32 bit 57 | * khronos_uint32_t unsigned 32 bit 58 | * khronos_int64_t signed 64 bit 59 | * khronos_uint64_t unsigned 64 bit 60 | * khronos_intptr_t signed same number of bits as a pointer 61 | * khronos_uintptr_t unsigned same number of bits as a pointer 62 | * khronos_ssize_t signed size 63 | * khronos_usize_t unsigned size 64 | * khronos_float_t signed 32 bit floating point 65 | * khronos_time_ns_t unsigned 64 bit time in nanoseconds 66 | * khronos_utime_nanoseconds_t unsigned time interval or absolute time in 67 | * nanoseconds 68 | * khronos_stime_nanoseconds_t signed time interval in nanoseconds 69 | * khronos_boolean_enum_t enumerated boolean type. This should 70 | * only be used as a base type when a client API's boolean type is 71 | * an enum. Client APIs which use an integer or other type for 72 | * booleans cannot use this as the base type for their boolean. 73 | * 74 | * Tokens defined in khrplatform.h: 75 | * 76 | * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. 77 | * 78 | * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. 79 | * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. 80 | * 81 | * Calling convention macros defined in this file: 82 | * KHRONOS_APICALL 83 | * KHRONOS_APIENTRY 84 | * KHRONOS_APIATTRIBUTES 85 | * 86 | * These may be used in function prototypes as: 87 | * 88 | * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( 89 | * int arg1, 90 | * int arg2) KHRONOS_APIATTRIBUTES; 91 | */ 92 | 93 | /*------------------------------------------------------------------------- 94 | * Definition of KHRONOS_APICALL 95 | *------------------------------------------------------------------------- 96 | * This precedes the return type of the function in the function prototype. 97 | */ 98 | #if defined(_WIN32) && !defined(__SCITECH_SNAP__) 99 | # define KHRONOS_APICALL __declspec(dllimport) 100 | #elif defined (__SYMBIAN32__) 101 | # define KHRONOS_APICALL IMPORT_C 102 | #elif defined(__ANDROID__) 103 | # define KHRONOS_APICALL __attribute__((visibility("default"))) 104 | #else 105 | # define KHRONOS_APICALL 106 | #endif 107 | 108 | /*------------------------------------------------------------------------- 109 | * Definition of KHRONOS_APIENTRY 110 | *------------------------------------------------------------------------- 111 | * This follows the return type of the function and precedes the function 112 | * name in the function prototype. 113 | */ 114 | #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) 115 | /* Win32 but not WinCE */ 116 | # define KHRONOS_APIENTRY __stdcall 117 | #else 118 | # define KHRONOS_APIENTRY 119 | #endif 120 | 121 | /*------------------------------------------------------------------------- 122 | * Definition of KHRONOS_APIATTRIBUTES 123 | *------------------------------------------------------------------------- 124 | * This follows the closing parenthesis of the function prototype arguments. 125 | */ 126 | #if defined (__ARMCC_2__) 127 | #define KHRONOS_APIATTRIBUTES __softfp 128 | #else 129 | #define KHRONOS_APIATTRIBUTES 130 | #endif 131 | 132 | /*------------------------------------------------------------------------- 133 | * basic type definitions 134 | *-----------------------------------------------------------------------*/ 135 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) 136 | 137 | 138 | /* 139 | * Using 140 | */ 141 | #include 142 | typedef int32_t khronos_int32_t; 143 | typedef uint32_t khronos_uint32_t; 144 | typedef int64_t khronos_int64_t; 145 | typedef uint64_t khronos_uint64_t; 146 | #define KHRONOS_SUPPORT_INT64 1 147 | #define KHRONOS_SUPPORT_FLOAT 1 148 | 149 | #elif defined(__VMS ) || defined(__sgi) 150 | 151 | /* 152 | * Using 153 | */ 154 | #include 155 | typedef int32_t khronos_int32_t; 156 | typedef uint32_t khronos_uint32_t; 157 | typedef int64_t khronos_int64_t; 158 | typedef uint64_t khronos_uint64_t; 159 | #define KHRONOS_SUPPORT_INT64 1 160 | #define KHRONOS_SUPPORT_FLOAT 1 161 | 162 | #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) 163 | 164 | /* 165 | * Win32 166 | */ 167 | typedef __int32 khronos_int32_t; 168 | typedef unsigned __int32 khronos_uint32_t; 169 | typedef __int64 khronos_int64_t; 170 | typedef unsigned __int64 khronos_uint64_t; 171 | #define KHRONOS_SUPPORT_INT64 1 172 | #define KHRONOS_SUPPORT_FLOAT 1 173 | 174 | #elif defined(__sun__) || defined(__digital__) 175 | 176 | /* 177 | * Sun or Digital 178 | */ 179 | typedef int khronos_int32_t; 180 | typedef unsigned int khronos_uint32_t; 181 | #if defined(__arch64__) || defined(_LP64) 182 | typedef long int khronos_int64_t; 183 | typedef unsigned long int khronos_uint64_t; 184 | #else 185 | typedef long long int khronos_int64_t; 186 | typedef unsigned long long int khronos_uint64_t; 187 | #endif /* __arch64__ */ 188 | #define KHRONOS_SUPPORT_INT64 1 189 | #define KHRONOS_SUPPORT_FLOAT 1 190 | 191 | #elif 0 192 | 193 | /* 194 | * Hypothetical platform with no float or int64 support 195 | */ 196 | typedef int khronos_int32_t; 197 | typedef unsigned int khronos_uint32_t; 198 | #define KHRONOS_SUPPORT_INT64 0 199 | #define KHRONOS_SUPPORT_FLOAT 0 200 | 201 | #else 202 | 203 | /* 204 | * Generic fallback 205 | */ 206 | #include 207 | typedef int32_t khronos_int32_t; 208 | typedef uint32_t khronos_uint32_t; 209 | typedef int64_t khronos_int64_t; 210 | typedef uint64_t khronos_uint64_t; 211 | #define KHRONOS_SUPPORT_INT64 1 212 | #define KHRONOS_SUPPORT_FLOAT 1 213 | 214 | #endif 215 | 216 | 217 | /* 218 | * Types that are (so far) the same on all platforms 219 | */ 220 | typedef signed char khronos_int8_t; 221 | typedef unsigned char khronos_uint8_t; 222 | typedef signed short int khronos_int16_t; 223 | typedef unsigned short int khronos_uint16_t; 224 | 225 | /* 226 | * Types that differ between LLP64 and LP64 architectures - in LLP64, 227 | * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears 228 | * to be the only LLP64 architecture in current use. 229 | */ 230 | #ifdef _WIN64 231 | typedef signed long long int khronos_intptr_t; 232 | typedef unsigned long long int khronos_uintptr_t; 233 | typedef signed long long int khronos_ssize_t; 234 | typedef unsigned long long int khronos_usize_t; 235 | #else 236 | typedef signed long int khronos_intptr_t; 237 | typedef unsigned long int khronos_uintptr_t; 238 | typedef signed long int khronos_ssize_t; 239 | typedef unsigned long int khronos_usize_t; 240 | #endif 241 | 242 | #if KHRONOS_SUPPORT_FLOAT 243 | /* 244 | * Float type 245 | */ 246 | typedef float khronos_float_t; 247 | #endif 248 | 249 | #if KHRONOS_SUPPORT_INT64 250 | /* Time types 251 | * 252 | * These types can be used to represent a time interval in nanoseconds or 253 | * an absolute Unadjusted System Time. Unadjusted System Time is the number 254 | * of nanoseconds since some arbitrary system event (e.g. since the last 255 | * time the system booted). The Unadjusted System Time is an unsigned 256 | * 64 bit value that wraps back to 0 every 584 years. Time intervals 257 | * may be either signed or unsigned. 258 | */ 259 | typedef khronos_uint64_t khronos_utime_nanoseconds_t; 260 | typedef khronos_int64_t khronos_stime_nanoseconds_t; 261 | #endif 262 | 263 | /* 264 | * Dummy value used to pad enum types to 32 bits. 265 | */ 266 | #ifndef KHRONOS_MAX_ENUM 267 | #define KHRONOS_MAX_ENUM 0x7FFFFFFF 268 | #endif 269 | 270 | /* 271 | * Enumerated boolean type 272 | * 273 | * Values other than zero should be considered to be true. Therefore 274 | * comparisons should not be made against KHRONOS_TRUE. 275 | */ 276 | typedef enum { 277 | KHRONOS_FALSE = 0, 278 | KHRONOS_TRUE = 1, 279 | KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM 280 | } khronos_boolean_enum_t; 281 | 282 | #endif /* __khrplatform_h_ */ 283 | -------------------------------------------------------------------------------- /papers/Efficient Neighbor Search for Particle-based Fluids.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genpfault/sph-tutorial/373cbf849e3053d83592cb9572f53ef32b578086/papers/Efficient Neighbor Search for Particle-based Fluids.pdf -------------------------------------------------------------------------------- /papers/Optimized Spatial Hashing for Collision Detection of Deformable Objects.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genpfault/sph-tutorial/373cbf849e3053d83592cb9572f53ef32b578086/papers/Optimized Spatial Hashing for Collision Detection of Deformable Objects.pdf -------------------------------------------------------------------------------- /papers/Particle-based Viscoelastic Fluid Simulation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genpfault/sph-tutorial/373cbf849e3053d83592cb9572f53ef32b578086/papers/Particle-based Viscoelastic Fluid Simulation.pdf -------------------------------------------------------------------------------- /papers/Real-Time Physics 103 Fluid Simulation in Games – Brandon Pelfrey.maff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genpfault/sph-tutorial/373cbf849e3053d83592cb9572f53ef32b578086/papers/Real-Time Physics 103 Fluid Simulation in Games – Brandon Pelfrey.maff -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Real-Time Physics Tutorials 2 | // Brandon Pelfrey 3 | // SPH Fluid Simulation 4 | #include 5 | #define GLFW_INCLUDE_NONE 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // -------------------------------------------------------------------- 17 | // Between [0,1] 18 | float rand01() 19 | { 20 | return (float)rand() * (1.f / RAND_MAX); 21 | } 22 | 23 | // -------------------------------------------------------------------- 24 | // Between [a,b] 25 | float randab(float a, float b) 26 | { 27 | return a + (b-a)*rand01(); 28 | } 29 | 30 | // -------------------------------------------------------------------- 31 | // A structure for holding two neighboring particles and their weighted distances 32 | struct Particle; 33 | struct Neighbor 34 | { 35 | Particle* j; 36 | float q, q2; 37 | }; 38 | 39 | // The Particle structure holding all of the relevant information. 40 | struct Particle 41 | { 42 | glm::vec2 pos; 43 | float r, g, b; 44 | 45 | glm::vec2 pos_old; 46 | glm::vec2 vel; 47 | glm::vec2 force; 48 | float mass; 49 | float rho; 50 | float rho_near; 51 | float press; 52 | float press_near; 53 | float sigma; 54 | float beta; 55 | std::vector< Neighbor > neighbors; 56 | }; 57 | 58 | // Our collection of particles 59 | std::vector< Particle > particles; 60 | 61 | // -------------------------------------------------------------------- 62 | const float G = .02f * .25; // Gravitational Constant for our simulation 63 | const float spacing = 2.f; // Spacing of particles 64 | const float k = spacing / 1000.0f; // Far pressure weight 65 | const float k_near = k * 10; // Near pressure weight 66 | const float rest_density = 3; // Rest Density 67 | const float r = spacing * 1.25f; // Radius of Support 68 | const float rsq = r * r; // ... squared for performance stuff 69 | const float SIM_W = 50; // The size of the world 70 | const float bottom = 0; // The floor of the world 71 | 72 | // -------------------------------------------------------------------- 73 | void init( const unsigned int N ) 74 | { 75 | // Initialize particles 76 | // We will make a block of particles with a total width of 1/4 of the screen. 77 | float w = SIM_W / 4; 78 | for( float y = bottom + 1; y <= 10000; y += r * 0.5f ) 79 | { 80 | for(float x = -w; x <= w; x += r * 0.5f ) 81 | { 82 | if( particles.size() > N ) 83 | { 84 | break; 85 | } 86 | 87 | Particle p; 88 | p.pos = glm::vec2(x, y); 89 | p.pos_old = p.pos + 0.001f * glm::vec2(rand01(), rand01()); 90 | p.force = glm::vec2(0,0); 91 | p.sigma = 3.f; 92 | p.beta = 4.f; 93 | particles.push_back(p); 94 | } 95 | } 96 | } 97 | 98 | // Mouse attractor 99 | glm::vec2 attractor(999,999); 100 | bool attracting = false; 101 | 102 | // -------------------------------------------------------------------- 103 | template< typename T > 104 | class SpatialIndex 105 | { 106 | public: 107 | typedef std::vector< T* > NeighborList; 108 | 109 | SpatialIndex 110 | ( 111 | const unsigned int numBuckets, // number of hash buckets 112 | const float cellSize, // grid cell size 113 | const bool twoDeeNeighborhood // true == 3x3 neighborhood, false == 3x3x3 114 | ) 115 | : mHashMap( numBuckets ) 116 | , mInvCellSize( 1.0f / cellSize ) 117 | { 118 | // initialize neighbor offsets 119 | for( int i = -1; i <= 1; i++ ) 120 | for( int j = -1; j <= 1; j++ ) 121 | if( twoDeeNeighborhood ) 122 | mOffsets.push_back( glm::ivec3( i, j, 0 ) ); 123 | else 124 | for( int k = -1; k <= 1; k++ ) 125 | mOffsets.push_back( glm::ivec3( i, j, k ) ); 126 | } 127 | 128 | void Insert( const glm::vec3& pos, T* thing ) 129 | { 130 | mHashMap[ Discretize( pos, mInvCellSize ) ].push_back( thing ); 131 | } 132 | 133 | void Neighbors( const glm::vec3& pos, NeighborList& ret ) const 134 | { 135 | const glm::ivec3 ipos = Discretize( pos, mInvCellSize ); 136 | for( const auto& offset : mOffsets ) 137 | { 138 | typename HashMap::const_iterator it = mHashMap.find( offset + ipos ); 139 | if( it != mHashMap.end() ) 140 | { 141 | ret.insert( ret.end(), it->second.begin(), it->second.end() ); 142 | } 143 | } 144 | } 145 | 146 | void Clear() 147 | { 148 | mHashMap.clear(); 149 | } 150 | 151 | private: 152 | // "Optimized Spatial Hashing for Collision Detection of Deformable Objects" 153 | // Teschner, Heidelberger, et al. 154 | // returns a hash between 0 and 2^32-1 155 | struct TeschnerHash : std::unary_function< glm::ivec3, std::size_t > 156 | { 157 | std::size_t operator()( glm::ivec3 const& pos ) const 158 | { 159 | const unsigned int p1 = 73856093; 160 | const unsigned int p2 = 19349663; 161 | const unsigned int p3 = 83492791; 162 | return size_t( ( pos.x * p1 ) ^ ( pos.y * p2 ) ^ ( pos.z * p3 ) ); 163 | }; 164 | }; 165 | 166 | // returns the indexes of the cell pos is in, assuming a cellSize grid 167 | // invCellSize is the inverse of the desired cell size 168 | static inline glm::ivec3 Discretize( const glm::vec3& pos, const float invCellSize ) 169 | { 170 | return glm::ivec3( glm::floor( pos * invCellSize ) ); 171 | } 172 | 173 | typedef std::unordered_map< glm::ivec3, NeighborList, TeschnerHash > HashMap; 174 | HashMap mHashMap; 175 | 176 | std::vector< glm::ivec3 > mOffsets; 177 | 178 | const float mInvCellSize; 179 | }; 180 | 181 | typedef SpatialIndex< Particle > IndexType; 182 | IndexType indexsp( 4093, r, true ); 183 | 184 | // -------------------------------------------------------------------- 185 | void step() 186 | { 187 | // UPDATE 188 | // This modified verlet integrator has dt = 1 and calculates the velocity 189 | // For later use in the simulation. 190 | #pragma omp parallel for 191 | for( int i = 0; i < (int)particles.size(); ++i ) 192 | { 193 | // Apply the currently accumulated forces 194 | particles[i].pos += particles[i].force; 195 | 196 | // Restart the forces with gravity only. We'll add the rest later. 197 | particles[i].force = glm::vec2( 0.0f, -::G ); 198 | 199 | // Calculate the velocity for later. 200 | particles[i].vel = particles[i].pos - particles[i].pos_old; 201 | 202 | // If the velocity is really high, we're going to cheat and cap it. 203 | // This will not damp all motion. It's not physically-based at all. Just 204 | // a little bit of a hack. 205 | const float max_vel = 2.0f; 206 | const float vel_mag = glm::dot( particles[i].vel, particles[i].vel ); 207 | // If the velocity is greater than the max velocity, then cut it in half. 208 | if( vel_mag > max_vel * max_vel ) 209 | { 210 | particles[i].vel *= .5f; 211 | } 212 | 213 | // Normal verlet stuff 214 | particles[i].pos_old = particles[i].pos; 215 | particles[i].pos += particles[i].vel; 216 | 217 | // If the Particle is outside the bounds of the world, then 218 | // Make a little spring force to push it back in. 219 | if( particles[i].pos.x < -SIM_W ) particles[i].force.x -= ( particles[i].pos.x - -SIM_W ) / 8; 220 | if( particles[i].pos.x > SIM_W ) particles[i].force.x -= ( particles[i].pos.x - SIM_W ) / 8; 221 | if( particles[i].pos.y < bottom ) particles[i].force.y -= ( particles[i].pos.y - bottom ) / 8; 222 | //if( particles[i].pos.y > SIM_W * 2 ) particles[i].force.y -= ( particles[i].pos.y - SIM_W * 2 ) / 8; 223 | 224 | // Handle the mouse attractor. 225 | // It's a simple spring based attraction to where the mouse is. 226 | const float attr_dist2 = glm::dot( particles[i].pos - attractor, particles[i].pos - attractor ); 227 | const float attr_l = SIM_W / 4; 228 | if( attracting ) 229 | { 230 | if( attr_dist2 < attr_l * attr_l ) 231 | { 232 | particles[i].force -= ( particles[i].pos - attractor ) / 256.0f; 233 | } 234 | } 235 | 236 | // Reset the nessecary items. 237 | particles[i].rho = 0; 238 | particles[i].rho_near = 0; 239 | particles[i].neighbors.clear(); 240 | } 241 | 242 | // update spatial index 243 | indexsp.Clear(); 244 | for( auto& particle : particles ) 245 | { 246 | indexsp.Insert( glm::vec3( particle.pos, 0.0f ), &particle ); 247 | } 248 | 249 | // DENSITY 250 | // Calculate the density by basically making a weighted sum 251 | // of the distances of neighboring particles within the radius of support (r) 252 | #pragma omp parallel for 253 | for( int i = 0; i < (int)particles.size(); ++i ) 254 | { 255 | particles[i].rho = 0; 256 | particles[i].rho_near = 0; 257 | 258 | // We will sum up the 'near' and 'far' densities. 259 | float d = 0; 260 | float dn = 0; 261 | 262 | IndexType::NeighborList neigh; 263 | neigh.reserve( 64 ); 264 | indexsp.Neighbors( glm::vec3( particles[i].pos, 0.0f ), neigh ); 265 | for( int j = 0; j < (int)neigh.size(); ++j ) 266 | { 267 | if( neigh[j] == &particles[i] ) 268 | { 269 | // do not calculate an interaction for a Particle with itself! 270 | continue; 271 | } 272 | 273 | // The vector seperating the two particles 274 | const glm::vec2 rij = neigh[j]->pos - particles[i].pos; 275 | 276 | // Along with the squared distance between 277 | const float rij_len2 = glm::dot( rij, rij ); 278 | 279 | // If they're within the radius of support ... 280 | if( rij_len2 < rsq ) 281 | { 282 | // Get the actual distance from the squared distance. 283 | float rij_len = sqrt( rij_len2 ); 284 | 285 | // And calculated the weighted distance values 286 | const float q = 1 - ( rij_len / r ); 287 | const float q2 = q * q; 288 | const float q3 = q2 * q; 289 | 290 | d += q2; 291 | dn += q3; 292 | 293 | // Set up the Neighbor list for faster access later. 294 | Neighbor n; 295 | n.j = neigh[j]; 296 | n.q = q; 297 | n.q2 = q2; 298 | particles[i].neighbors.push_back(n); 299 | } 300 | } 301 | 302 | particles[i].rho += d; 303 | particles[i].rho_near += dn; 304 | } 305 | 306 | // PRESSURE 307 | // Make the simple pressure calculation from the equation of state. 308 | #pragma omp parallel for 309 | for( int i = 0; i < (int)particles.size(); ++i ) 310 | { 311 | particles[i].press = k * ( particles[i].rho - rest_density ); 312 | particles[i].press_near = k_near * particles[i].rho_near; 313 | } 314 | 315 | // PRESSURE FORCE 316 | // We will force particles in or out from their neighbors 317 | // based on their difference from the rest density. 318 | #pragma omp parallel for 319 | for( int i = 0; i < (int)particles.size(); ++i ) 320 | { 321 | // For each of the neighbors 322 | glm::vec2 dX( 0 ); 323 | for( const Neighbor& n : particles[i].neighbors ) 324 | { 325 | // The vector from Particle i to Particle j 326 | const glm::vec2 rij = (*n.j).pos - particles[i].pos; 327 | 328 | // calculate the force from the pressures calculated above 329 | const float dm 330 | = n.q * ( particles[i].press + (*n.j).press ) 331 | + n.q2 * ( particles[i].press_near + (*n.j).press_near ); 332 | 333 | // Get the direction of the force 334 | const glm::vec2 D = glm::normalize( rij ) * dm; 335 | dX += D; 336 | } 337 | 338 | particles[i].force -= dX; 339 | } 340 | 341 | // VISCOSITY 342 | // This simulation actually may look okay if you don't compute 343 | // the viscosity section. The effects of numerical damping and 344 | // surface tension will give a smooth appearance on their own. 345 | // Try it. 346 | #pragma omp parallel for 347 | for( int i = 0; i < (int)particles.size(); ++i ) 348 | { 349 | // We'll let the color be determined by 350 | // ... x-velocity for the red component 351 | // ... y-velocity for the green-component 352 | // ... pressure for the blue component 353 | particles[i].r = 0.3f + (20 * fabs(particles[i].vel.x) ); 354 | particles[i].g = 0.3f + (20 * fabs(particles[i].vel.y) ); 355 | particles[i].b = 0.3f + (0.1f * particles[i].rho ); 356 | 357 | // For each of that particles neighbors 358 | for( const Neighbor& n : particles[i].neighbors ) 359 | { 360 | const glm::vec2 rij = (*n.j).pos - particles[i].pos; 361 | const float l = glm::length( rij ); 362 | const float q = l / r; 363 | 364 | const glm::vec2 rijn = ( rij / l ); 365 | // Get the projection of the velocities onto the vector between them. 366 | const float u = glm::dot( particles[i].vel - (*n.j).vel, rijn ); 367 | if( u > 0 ) 368 | { 369 | // Calculate the viscosity impulse between the two particles 370 | // based on the quadratic function of projected length. 371 | const glm::vec2 I 372 | = ( 1 - q ) 373 | * ( (*n.j).sigma * u + (*n.j).beta * u * u ) 374 | * rijn; 375 | 376 | // Apply the impulses on the current particle 377 | particles[i].vel -= I * 0.5f; 378 | } 379 | } 380 | } 381 | } 382 | 383 | // -------------------------------------------------------------------- 384 | void display( GLFWwindow* window ) 385 | { 386 | glClearColor( 0, 0, 0, 1 ); 387 | glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 388 | 389 | int w, h; 390 | glfwGetWindowSize( window, &w, &h ); 391 | glViewport( 0, 0, w, h ); 392 | 393 | // create a world with dimensions x:[-SIM_W,SIM_W] and y:[0,SIM_W*2] 394 | glMatrixMode( GL_PROJECTION ); 395 | glLoadIdentity(); 396 | const double ar = w / static_cast< double >( h ); 397 | glOrtho( ar * -SIM_W, ar * SIM_W, 0, 2*SIM_W, -1, 1 ); 398 | 399 | glMatrixMode( GL_MODELVIEW ); 400 | glLoadIdentity(); 401 | 402 | // Draw Fluid Particles 403 | glPointSize( r*2 ); 404 | glVertexPointer( 2, GL_FLOAT, sizeof(Particle), &particles[0].pos ); 405 | glColorPointer( 3, GL_FLOAT, sizeof(Particle), &particles[0].r ); 406 | glEnableClientState( GL_VERTEX_ARRAY ); 407 | glEnableClientState( GL_COLOR_ARRAY ); 408 | glDrawArrays( GL_POINTS, 0, static_cast< GLsizei >( particles.size() ) ); 409 | glDisableClientState( GL_VERTEX_ARRAY ); 410 | glDisableClientState( GL_COLOR_ARRAY ); 411 | } 412 | 413 | // -------------------------------------------------------------------- 414 | unsigned int stepsPerFrame = 1; 415 | void keyboard( GLFWwindow* window, int key, int scancode, int action, int mods ) 416 | { 417 | if( action == GLFW_PRESS ) 418 | { 419 | return; 420 | } 421 | 422 | const float radius = SIM_W / 8; 423 | switch( key ) 424 | { 425 | case GLFW_KEY_ESCAPE: 426 | case GLFW_KEY_Q: 427 | glfwSetWindowShouldClose( window, 1 ); 428 | break; 429 | case GLFW_KEY_SPACE: 430 | // add some particles. 431 | for( float y = SIM_W * 2 - radius; y <= SIM_W * 2 + radius; y += r * .5f ) 432 | { 433 | for( float x = -radius; x <= radius; x += r * .5f ) 434 | { 435 | Particle p; 436 | p.pos = p.pos_old = glm::vec2(x , y) + glm::vec2(rand01(), rand01()); 437 | p.force = glm::vec2(0,0); 438 | p.sigma = 3.f; 439 | p.beta = 4.f; 440 | 441 | const glm::vec2 temp( p.pos - glm::vec2( 0, SIM_W * 2 ) ); 442 | if( glm::dot( temp, temp ) < radius * radius ) 443 | { 444 | particles.push_back(p); 445 | } 446 | } 447 | } 448 | break; 449 | case GLFW_KEY_MINUS: 450 | case GLFW_KEY_KP_SUBTRACT: 451 | if( stepsPerFrame > 1 ) stepsPerFrame--; 452 | break; 453 | case GLFW_KEY_EQUAL: 454 | case GLFW_KEY_KP_ADD: 455 | stepsPerFrame++; 456 | break; 457 | } 458 | } 459 | 460 | // -------------------------------------------------------------------- 461 | void motion( GLFWwindow* window, double xpos, double ypos ) 462 | { 463 | // This simply updates the location of the mouse attractor. 464 | int window_w, window_h; 465 | glfwGetWindowSize( window, &window_w, &window_h ); 466 | float relx = (float)(xpos - window_w/2) / window_w; 467 | float rely = -(float)(ypos - window_h) / window_h; 468 | glm::vec2 mouse = glm::vec2(relx*SIM_W*2, rely*SIM_W*2); 469 | attractor = mouse; 470 | } 471 | 472 | // -------------------------------------------------------------------- 473 | void mouse( GLFWwindow* window, int button, int action, int mods ) 474 | { 475 | if( action == GLFW_PRESS ) 476 | { 477 | attracting = true; 478 | } 479 | else 480 | { 481 | attracting = false; 482 | attractor = glm::vec2(SIM_W * 99, SIM_W * 99); 483 | } 484 | } 485 | 486 | // -------------------------------------------------------------------- 487 | int main( int argc, char** argv ) 488 | { 489 | #if 0 490 | const int steps = 3000; 491 | std::cout << "--------------------------------" << std::endl; 492 | std::cout << "Number of steps: " << steps << std::endl; 493 | for( unsigned int size = 10; size <= 13; ++size ) 494 | { 495 | const unsigned int count = ( 1 << size ); 496 | std::cout << "Number of particles: " << count << std::endl; 497 | 498 | init( count ); 499 | 500 | const auto beg = std::chrono::high_resolution_clock::now(); 501 | for( unsigned int i = 0; i < steps; ++i ) 502 | { 503 | step(); 504 | } 505 | const auto end = std::chrono::high_resolution_clock::now(); 506 | 507 | const auto duration( end - beg ); 508 | std::cout << "Elapsed time: " << std::chrono::duration_cast< std::chrono::milliseconds >( duration ).count() << " milliseconds" << std::endl; 509 | std::cout << "Microseconds per step: " << std::chrono::duration_cast< std::chrono::microseconds >( duration ).count() / (double)steps << std::endl; 510 | std::cout << std::endl; 511 | } 512 | 513 | return 0; 514 | #else 515 | init( 2048 ); 516 | 517 | glfwInit(); 518 | GLFWwindow* window = glfwCreateWindow( 512, 512, "SPH", NULL, NULL ); 519 | glfwMakeContextCurrent( window ); 520 | gladLoadGLLoader( (GLADloadproc)glfwGetProcAddress ); 521 | 522 | glfwSwapInterval( 1 ); 523 | 524 | glfwSetKeyCallback( window, keyboard ); 525 | glfwSetCursorPosCallback( window, motion ); 526 | glfwSetMouseButtonCallback( window, mouse ); 527 | 528 | while( !glfwWindowShouldClose( window ) ) 529 | { 530 | display( window ); 531 | 532 | glfwPollEvents(); 533 | 534 | for( size_t i = 0; i < stepsPerFrame; ++i ) 535 | { 536 | step(); 537 | } 538 | 539 | glfwSwapBuffers( window ); 540 | } 541 | 542 | glfwTerminate(); 543 | return 0; 544 | #endif 545 | } 546 | --------------------------------------------------------------------------------