├── .gitattributes ├── .gitignore ├── Gepe3D.sln ├── Gepe3D ├── Gepe3D.csproj ├── res │ ├── Kernels │ │ ├── common_funcs.cl │ │ ├── fluid_project.cl │ │ ├── pbd_common.cl │ │ └── solid_project.cl │ └── Shaders │ │ ├── point_sphere.frag │ │ ├── point_sphere.vert │ │ ├── skybox.frag │ │ └── skybox.vert └── src │ ├── BallCharacter.cs │ ├── CLUtils.cs │ ├── Camera.cs │ ├── GLUtils.cs │ ├── MainWindow.cs │ ├── ParticleSystem.cs │ ├── Shader.cs │ ├── SkyBox.cs │ └── Spike.cs ├── LICENSE.md └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/obj/ 2 | **/bin/ 3 | .vscode/ 4 | .vs/ 5 | .idea/ -------------------------------------------------------------------------------- /Gepe3D.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gepe3D", "Gepe3D\Gepe3D.csproj", "{9A168EB8-BD2D-4E15-A451-59E575D92E57}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Debug|x64.Build.0 = Debug|Any CPU 25 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Debug|x86.Build.0 = Debug|Any CPU 27 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Release|x64.ActiveCfg = Release|Any CPU 30 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Release|x64.Build.0 = Release|Any CPU 31 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Release|x86.ActiveCfg = Release|Any CPU 32 | {9A168EB8-BD2D-4E15-A451-59E575D92E57}.Release|x86.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /Gepe3D/Gepe3D.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Gepe3D/res/Kernels/common_funcs.cl: -------------------------------------------------------------------------------- 1 | 2 | // #define variables added in the C# code - don't use these as var names 3 | // CELLCOUNT_X, CELLCOUNT_Y, CELLCOUNT_Z, CELL_WIDTH 4 | // MAX_X, MAX_Y, MAX_Z 5 | // KERNEL_SIZE, REST_DENSITY 6 | // PHASE_LIQUID, PHASE_SOLID, PHASE_STATIC 7 | 8 | 9 | // code to gather neighbours, must match input buffer names for particle ids, try not to use var names that might overlap 10 | #define FOREACH_NEIGHBOUR_j \ 11 | int cellID = cellIDsOfParticles[i]; \ 12 | int neighbourCellIDs[3 * 3 * 3]; \ 13 | int neighbourCellCount = 0; \ 14 | int3 cellCoords = cell_id_2_coords(cellID); \ 15 | for ( int cx = max( cellCoords.x - 1, 0 ); cx <= min( cellCoords.x + 1, CELLCOUNT_X - 1 ); cx++ ) { \ 16 | for ( int cy = max( cellCoords.y - 1, 0 ); cy <= min( cellCoords.y + 1, CELLCOUNT_Y - 1 ); cy++ ) { \ 17 | for ( int cz = max( cellCoords.z - 1, 0 ); cz <= min( cellCoords.z + 1, CELLCOUNT_Z - 1 ); cz++ ) { \ 18 | neighbourCellIDs[ neighbourCellCount++ ] = cell_coords_2_id( (int3) (cx, cy, cz)); \ 19 | }}} \ 20 | for (int nc = 0; nc < neighbourCellCount; nc++) { \ 21 | int nCellID = neighbourCellIDs[nc]; \ 22 | for (int g = cellStartAndEndIDs[nCellID * 2 + 0]; g < cellStartAndEndIDs[nCellID * 2 + 1]; g++) { \ 23 | int j = sortedParticleIDs[g]; 24 | 25 | #define END_FOREACH_NEIGHBOUR_j }} 26 | 27 | 28 | 29 | float3 getVec(global float *buffer, int i) { 30 | return (float3) ( buffer[i * 3 + 0], buffer[i * 3 + 1], buffer[i * 3 + 2] ); 31 | } 32 | 33 | void setVec(global float *buffer, int i, float3 val) { 34 | buffer[i * 3 + 0] = val.x; 35 | buffer[i * 3 + 1] = val.y; 36 | buffer[i * 3 + 2] = val.z; 37 | } 38 | 39 | 40 | int3 cell_id_2_coords(int id) { 41 | 42 | int x = id / (CELLCOUNT_Y * CELLCOUNT_Z); 43 | int y = ( id % (CELLCOUNT_Y * CELLCOUNT_Z) ) / CELLCOUNT_Z; 44 | int z = ( id % (CELLCOUNT_Y * CELLCOUNT_Z) ) % CELLCOUNT_Z; 45 | return (int3) (x, y, z); 46 | } 47 | 48 | int cell_coords_2_id(int3 coords) { 49 | 50 | return 51 | coords.x * CELLCOUNT_Y * CELLCOUNT_Z + 52 | coords.y * CELLCOUNT_Z + 53 | coords.z; 54 | } 55 | 56 | int get_cell_id(float3 pos) { 57 | int3 cellCoords = (int3) ( 58 | (int) (pos.x / CELL_WIDTH), 59 | (int) (pos.y / CELL_WIDTH), 60 | (int) (pos.z / CELL_WIDTH) 61 | ); 62 | // MUST CLAMP or else a bunch of bugs occur (id out of bounds, reading random areas of memory, etc) 63 | // often particles end up right on the boundary, 1 over the max allowed coord 64 | cellCoords.x = clamp( cellCoords.x, 0, (int) CELLCOUNT_X - 1 ); 65 | cellCoords.y = clamp( cellCoords.y, 0, (int) CELLCOUNT_Y - 1 ); 66 | cellCoords.z = clamp( cellCoords.z, 0, (int) CELLCOUNT_Z - 1 ); 67 | return cell_coords_2_id(cellCoords); 68 | } 69 | 70 | void atomic_add_global_float(volatile global float *source, const float operand) { 71 | union { 72 | unsigned int intVal; 73 | float floatVal; 74 | } newVal; 75 | union { 76 | unsigned int intVal; 77 | float floatVal; 78 | } prevVal; 79 | 80 | do { 81 | prevVal.floatVal = *source; 82 | newVal.floatVal = prevVal.floatVal + operand; 83 | } while (atomic_cmpxchg((volatile global unsigned int *)source, prevVal.intVal, newVal.intVal) != prevVal.intVal); 84 | } -------------------------------------------------------------------------------- /Gepe3D/res/Kernels/fluid_project.cl: -------------------------------------------------------------------------------- 1 | 2 | // #define variables added in the C# code - don't use these as var names 3 | // CELLCOUNT_X, CELLCOUNT_Y, CELLCOUNT_Z, CELL_WIDTH 4 | // MAX_X, MAX_Y, MAX_Z 5 | // KERNEL_SIZE, REST_DENSITY 6 | // PHASE_LIQUID, PHASE_SOLID, PHASE_STATIC 7 | 8 | 9 | 10 | 11 | #define PI 3.1415926f 12 | 13 | // Epsilon in gamma correction denominator 14 | #define RELAXATION 0.01f 15 | 16 | // Pressure terms 17 | #define K_P 0.1f 18 | #define E_P 4.0f 19 | #define DQ_P 0.2f 20 | 21 | #define VISCOSITY_COEFF 0.001f 22 | 23 | 24 | float w_poly6(float dist, float h) { 25 | dist = clamp(dist, (float) 0, (float) h); 26 | float tmp = h * h - dist * dist; 27 | return ( 315.0f / (64.0f * PI * pow(h, 9)) ) * tmp * tmp * tmp; 28 | } 29 | 30 | 31 | float w_spikygrad(float dist, float h) { 32 | dist = clamp(dist, (float) 0, (float) h); 33 | if (dist < FLT_EPSILON) return 0; // too close = same particle = can't use this scalar kernel 34 | return ( -45 / (PI * pow(h, 6)) ) * (h - dist) * (h - dist); 35 | } 36 | 37 | 38 | 39 | kernel void calculate_lambdas( 40 | global float *eposBuffer, 41 | global float *imasses, 42 | global float *lambdas, 43 | global int *cellIDsOfParticles, 44 | global int *cellStartAndEndIDs, 45 | global int *sortedParticleIDs, 46 | global int *phase 47 | ) { 48 | 49 | int i = get_global_id(0); 50 | 51 | if (phase[i] != PHASE_LIQUID) { 52 | lambdas[i] = 0; 53 | return; 54 | } 55 | 56 | float3 epos1 = getVec(eposBuffer, i); 57 | float density = 0; 58 | float gradN = 0; // gradient sum when other particle is neighbour 59 | float3 gradS = 0; // gradient sum when other particle is self 60 | 61 | 62 | FOREACH_NEIGHBOUR_j 63 | 64 | float3 epos2 = getVec(eposBuffer, j); 65 | float3 diff = epos1 - epos2; 66 | float dist = length(diff); 67 | if (dist > KERNEL_SIZE) continue; 68 | 69 | // the added bit should be multiplied by an extra scalar if its a solid 70 | if (imasses[j] > 0) density += (1.0 / imasses[j]) * w_poly6(dist, KERNEL_SIZE); 71 | 72 | if (i != j) { 73 | 74 | float kgrad = w_spikygrad(dist, KERNEL_SIZE); 75 | float tmp = kgrad / REST_DENSITY; 76 | // the added bit should be multiplied by an extra scalar if its a solid 77 | gradN += tmp * tmp; 78 | // the added bit should be multiplied by an extra scalar if its a solid 79 | gradS += normalize(diff) * kgrad; 80 | } 81 | 82 | END_FOREACH_NEIGHBOUR_j 83 | 84 | 85 | gradS /= REST_DENSITY; 86 | float denominator = gradN + dot(gradS, gradS); 87 | 88 | lambdas[i] = -(density / REST_DENSITY - 1.0) / (denominator + RELAXATION); 89 | 90 | } 91 | 92 | 93 | 94 | kernel void calc_fluid_corrections( 95 | global float *eposBuffer, 96 | global float *imasses, 97 | global float *lambdas, 98 | global float *corrections, 99 | global int *cellIDsOfParticles, 100 | global int *cellStartAndEndIDs, 101 | global int *sortedParticleIDs, 102 | global int *phase 103 | ) { 104 | int i = get_global_id(0); 105 | 106 | if (phase[i] != PHASE_LIQUID) return; 107 | 108 | float3 epos1 = getVec(eposBuffer, i); 109 | 110 | float3 correction = (float3) (0, 0, 0); 111 | 112 | int numNeighbours = 1; // start at 1 to prevent divide by zero 113 | 114 | FOREACH_NEIGHBOUR_j 115 | 116 | if (i == j) continue; 117 | 118 | float3 epos2 = getVec(eposBuffer, j); 119 | float3 diff = epos1 - epos2; 120 | float dist = length(diff); 121 | 122 | if (dist > KERNEL_SIZE) continue; 123 | numNeighbours++; 124 | 125 | float3 grad = w_spikygrad(dist, KERNEL_SIZE) * normalize(diff); 126 | 127 | float artificialPressure = -K_P * pow( w_poly6(dist, KERNEL_SIZE) / w_poly6(DQ_P * KERNEL_SIZE, KERNEL_SIZE), E_P ); 128 | 129 | correction += (lambdas[i] + lambdas[j] + artificialPressure) * grad; 130 | 131 | END_FOREACH_NEIGHBOUR_j 132 | 133 | 134 | correction /= REST_DENSITY; 135 | correction /= numNeighbours; 136 | 137 | setVec(corrections, i, correction); 138 | 139 | } 140 | 141 | 142 | 143 | kernel void calculate_vorticities ( 144 | global float *posBuffer, 145 | global float *velBuffer, 146 | global float *vorticities, 147 | global int *cellIDsOfParticles, 148 | global int *cellStartAndEndIDs, 149 | global int *sortedParticleIDs, 150 | global int *phase 151 | ) { 152 | 153 | int i = get_global_id(0); 154 | 155 | if (phase[i] != PHASE_LIQUID) { 156 | setVec(vorticities, i, (float3) (0, 0, 0) ); 157 | return; 158 | } 159 | 160 | float3 pos = getVec(posBuffer, i); 161 | float3 vel = getVec(velBuffer, i); 162 | 163 | float3 vorticity = (float3) (0, 0, 0); 164 | 165 | FOREACH_NEIGHBOUR_j 166 | float3 velDiff = getVec(velBuffer, j) - vel; 167 | float3 posDiff = pos - getVec(posBuffer, j); 168 | float3 grad = w_spikygrad( length(posDiff), KERNEL_SIZE ) * normalize(posDiff); 169 | 170 | vorticity += cross(velDiff, grad); 171 | 172 | END_FOREACH_NEIGHBOUR_j 173 | 174 | setVec(vorticities, i, vorticity); 175 | } 176 | 177 | 178 | kernel void apply_vorticity_viscosity ( 179 | global float *posBuffer, 180 | global float *velBuffer, 181 | global float *vorticities, 182 | global float *velCorrect, 183 | global float *imasses, 184 | float delta, 185 | global int *cellIDsOfParticles, 186 | global int *cellStartAndEndIDs, 187 | global int *sortedParticleIDs, 188 | global int *phase 189 | ) { 190 | 191 | int i = get_global_id(0); 192 | 193 | if (phase[i] != PHASE_LIQUID) { 194 | setVec(velCorrect, i, (float3) (0, 0, 0) ); 195 | return; 196 | } 197 | 198 | float3 pos = getVec(posBuffer, i); 199 | float3 vel = getVec(velBuffer, i); 200 | float3 vort_i = getVec(vorticities, i); 201 | 202 | // gradient direction of the magnitude of vorticities around this point (scalar field) 203 | // gradient of a function is calculated by summing function values multiplied by spikygrad 204 | float3 vortMagGrad = (float3) (0, 0, 0); 205 | 206 | float3 avgNeighbourVelDiff = (float3) (0, 0, 0); 207 | 208 | FOREACH_NEIGHBOUR_j 209 | 210 | float3 velDiff = getVec(velBuffer, j) - vel; 211 | float3 posDiff = pos - getVec(posBuffer, j); 212 | float3 vort_j = getVec(vorticities, j); 213 | vortMagGrad += length(vort_j) * w_spikygrad( length(posDiff), KERNEL_SIZE ) * normalize(posDiff); 214 | 215 | avgNeighbourVelDiff += velDiff * w_poly6( length(posDiff), KERNEL_SIZE ); 216 | 217 | END_FOREACH_NEIGHBOUR_j 218 | 219 | float3 vorticity_force = RELAXATION * cross( normalize(vortMagGrad), vort_i ); 220 | 221 | float3 correction = 222 | (vorticity_force * delta * imasses[i]) + 223 | (avgNeighbourVelDiff * VISCOSITY_COEFF); 224 | 225 | setVec(velCorrect, i, correction); 226 | } 227 | 228 | 229 | kernel void correct_fluid_vel( 230 | global float *velBuffer, 231 | global float *velCorrect 232 | ) { 233 | 234 | int i = get_global_id(0); 235 | float3 vel = getVec(velBuffer, i); 236 | float3 correction = getVec(velCorrect, i); 237 | vel += correction; 238 | setVec(velBuffer, i, vel); 239 | } -------------------------------------------------------------------------------- /Gepe3D/res/Kernels/pbd_common.cl: -------------------------------------------------------------------------------- 1 | 2 | // #define variables added in the C# code - don't use these as var names 3 | // CELLCOUNT_X, CELLCOUNT_Y, CELLCOUNT_Z, CELL_WIDTH 4 | // MAX_X, MAX_Y, MAX_Z 5 | // KERNEL_SIZE, REST_DENSITY 6 | // PHASE_LIQUID, PHASE_SOLID, PHASE_STATIC 7 | 8 | #define MAX_VEL 5 9 | 10 | 11 | 12 | kernel void predict_positions( 13 | float delta, 14 | global float *posBuffer, 15 | global float *velBuffer, 16 | global float *eposBuffer, 17 | global int *phase, 18 | float gravityX, 19 | float gravityY, 20 | float gravityZ 21 | ) { 22 | int i = get_global_id(0); 23 | // if (phase[i] == PHASE_STATIC) return; 24 | 25 | float3 pos = getVec( posBuffer, i); 26 | float3 vel = getVec( velBuffer, i); 27 | 28 | vel.x += gravityX * delta; 29 | vel.y += gravityY * delta; 30 | vel.z += gravityZ * delta; 31 | float3 epos = pos + vel * delta; 32 | 33 | setVec( velBuffer, i, vel); 34 | setVec(eposBuffer, i, epos); 35 | } 36 | 37 | 38 | 39 | kernel void correct_predictions( 40 | global float *posBuffer, 41 | global float *eposBuffer, 42 | global float *corrections, 43 | global int *phase 44 | ) { 45 | int i = get_global_id(0); 46 | 47 | float3 correction = getVec(corrections, i); 48 | float3 epos = getVec(eposBuffer, i); 49 | epos += correction; 50 | setVec(eposBuffer, i, epos); 51 | } 52 | 53 | 54 | 55 | kernel void update_velocity( 56 | float delta, 57 | global float *posBuffer, 58 | global float *velBuffer, 59 | global float *eposBuffer, 60 | global int *phase, 61 | float shiftX 62 | ) { 63 | int i = get_global_id(0); 64 | 65 | float3 pos = getVec( posBuffer, i); 66 | float3 vel = getVec( velBuffer, i); 67 | float3 epos = getVec(eposBuffer, i); 68 | 69 | if (phase[i] == PHASE_STATIC) epos = pos; 70 | 71 | vel = (epos - pos) / delta; 72 | pos = epos; 73 | pos.x += shiftX; 74 | 75 | if (phase[i] == PHASE_LIQUID) { 76 | if (pos.x < 0) pos.x = MAX_X - 0.01f; 77 | if (pos.x > MAX_X) pos.x = 0 + 0.01f; 78 | } 79 | 80 | if (pos.y < 0) { pos.y = 0; vel.y = fmax( (float) 0, (float) vel.y); } 81 | else if (pos.y > MAX_Y) { pos.y = MAX_Y; vel.y = fmin( (float) 0, (float) vel.y); } 82 | 83 | if (pos.z < 0) { pos.z = 0; vel.z = fmax( (float) 0, (float) vel.z); } 84 | else if (pos.z > MAX_Z) { pos.z = MAX_Z; vel.z = fmin( (float) 0, (float) vel.z); } 85 | 86 | if (length(vel) > MAX_VEL) vel = normalize(vel) * MAX_VEL; 87 | 88 | setVec( posBuffer, i, pos); 89 | setVec( velBuffer, i, vel); 90 | setVec(eposBuffer, i, epos); 91 | } 92 | 93 | 94 | 95 | kernel void assign_particle_cells ( 96 | global float *eposBuffer, 97 | global int *numParticlesPerCell, 98 | global int *cellIDsOfParticles, 99 | global int *particleIDinCell 100 | ) { 101 | int i = get_global_id(0); 102 | float3 epos = getVec(eposBuffer, i); 103 | int cellID = get_cell_id(epos); 104 | cellIDsOfParticles[i] = cellID; 105 | particleIDinCell[i] = atomic_inc( &numParticlesPerCell[cellID] ); 106 | } 107 | 108 | 109 | 110 | 111 | kernel void find_cells_start_and_end ( 112 | global int *numParticlesPerCell, 113 | global int *cellStartAndEndIDs 114 | ) { 115 | int cellID = get_global_id(0); 116 | 117 | int startPos = 0; 118 | for (int i = 0; i < cellID; i++) { 119 | startPos += numParticlesPerCell[i]; 120 | } 121 | int endPos = startPos + numParticlesPerCell[cellID]; 122 | 123 | cellStartAndEndIDs[cellID * 2 + 0] = startPos; 124 | cellStartAndEndIDs[cellID * 2 + 1] = endPos; 125 | } 126 | 127 | 128 | kernel void sort_particle_ids_by_cell ( 129 | global int *particleIDinCell, 130 | global int *cellStartAndEndIDs, 131 | global int *cellIDsOfParticles, 132 | global int *sortedParticleIDs 133 | ) { 134 | 135 | int i = get_global_id(0); 136 | int cellID = cellIDsOfParticles[i]; 137 | int cellStartPos = cellStartAndEndIDs[cellID * 2 + 0]; 138 | int idInCell = particleIDinCell[i]; 139 | int sortedID = cellStartPos + idInCell; 140 | sortedParticleIDs[sortedID] = i; 141 | } -------------------------------------------------------------------------------- /Gepe3D/res/Kernels/solid_project.cl: -------------------------------------------------------------------------------- 1 | 2 | // #define variables added in the C# code - don't use these as var names 3 | // CELLCOUNT_X, CELLCOUNT_Y, CELLCOUNT_Z, CELL_WIDTH 4 | // MAX_X, MAX_Y, MAX_Z 5 | // KERNEL_SIZE, REST_DENSITY 6 | // PHASE_LIQUID, PHASE_SOLID, PHASE_STATIC 7 | 8 | kernel void calc_solid_corrections ( 9 | global float *eposBuffer, 10 | global float *imasses, 11 | global float *corrections, 12 | global int *cellIDsOfParticles, 13 | global int *cellStartAndEndIDs, 14 | global int *sortedParticleIDs, 15 | global int *phase 16 | ) { 17 | int i = get_global_id(0); 18 | 19 | if (phase[i] != PHASE_SOLID) return; 20 | 21 | float3 epos1 = getVec(eposBuffer, i); 22 | 23 | float imass1 = imasses[i]; 24 | if (imass1 == 0) return; 25 | 26 | float3 correction = (float3) (0, 0, 0); 27 | 28 | FOREACH_NEIGHBOUR_j 29 | 30 | if (i == j) continue; 31 | 32 | float3 epos2 = getVec(eposBuffer, j); 33 | float3 diff = epos1 - epos2; 34 | float dist = length(diff); 35 | 36 | float imass2 = imasses[j]; 37 | 38 | if (dist < 0.2f) { 39 | float displacement = dist - 0.2f; 40 | float w = imass1 / (imass1 + imass2); 41 | correction -= w * displacement * normalize(diff); 42 | } 43 | 44 | END_FOREACH_NEIGHBOUR_j 45 | 46 | 47 | setVec(corrections, i, correction); 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /Gepe3D/res/Shaders/point_sphere.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec3 lightPos; 4 | uniform float particleRadius; 5 | uniform float maxX; 6 | 7 | out vec4 FragColor; 8 | 9 | in vec3 texCoords; 10 | in vec3 viewSpaceSphereCenter; 11 | in vec4 instanceAlbedo; 12 | in float xPos; 13 | 14 | uniform mat4 viewMatrix; 15 | uniform mat4 projectionMatrix; 16 | 17 | void main() 18 | { 19 | if (xPos < 0 || xPos > maxX) discard; 20 | 21 | float d2 = texCoords.x * texCoords.x + texCoords.y * texCoords.y; 22 | 23 | if (d2 > 1) discard; 24 | 25 | float nx = texCoords.x; 26 | float ny = texCoords.y; 27 | float nz = sqrt(1.0 - d2); 28 | vec3 normal = vec3(nx, ny, nz); 29 | 30 | // view space 31 | vec3 vLightPos = ( viewMatrix * vec4(lightPos, 1.0) ).xyz; 32 | vec3 vFragPos = viewSpaceSphereCenter + normal * particleRadius; 33 | vec3 lightDir = normalize( vLightPos.xyz - vFragPos.xyz ); 34 | 35 | float NdL = max( 0.0, dot(normal, lightDir) ); 36 | NdL = NdL * 0.8 + 0.2; 37 | 38 | vec3 col = instanceAlbedo.xyz * NdL; 39 | FragColor = vec4( col, 1 ); 40 | } -------------------------------------------------------------------------------- /Gepe3D/res/Shaders/point_sphere.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec3 vertexPosition; 4 | layout(location = 1) in vec3 instancePosition; 5 | layout(location = 2) in vec3 instanceColour; 6 | 7 | uniform mat4 viewMatrix; 8 | uniform mat4 projectionMatrix; 9 | 10 | out vec3 texCoords; 11 | out vec3 viewSpaceSphereCenter; 12 | out vec4 instanceAlbedo; 13 | out float xPos; 14 | 15 | void main() 16 | { 17 | mat4 model = mat4(1.0, 0.0, 0.0, 0.0, // 1. column 18 | 0.0, 1.0, 0.0, 0.0, // 2. column 19 | 0.0, 0.0, 1.0, 0.0, // 3. column 20 | instancePosition.x, instancePosition.y, instancePosition.z, 1.0); // 4. column 21 | 22 | mat4 viewModelMat = viewMatrix * model; 23 | viewModelMat[0][0] = 1; viewModelMat[1][0] = 0; viewModelMat[2][0] = 0; 24 | viewModelMat[0][1] = 0; viewModelMat[1][1] = 1; viewModelMat[2][1] = 0; 25 | viewModelMat[0][2] = 0; viewModelMat[1][2] = 0; viewModelMat[2][2] = 1; 26 | 27 | gl_Position = projectionMatrix * viewModelMat * vec4(vertexPosition, 1.0); 28 | 29 | texCoords = normalize(vertexPosition) * sqrt(2); // square from (-1, -1) to (1, 1) 30 | viewSpaceSphereCenter = ( viewMatrix * vec4(instancePosition, 1.0) ).xyz; 31 | 32 | instanceAlbedo = vec4(instanceColour, 1); 33 | xPos = instancePosition.x; 34 | } -------------------------------------------------------------------------------- /Gepe3D/res/Shaders/skybox.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 FragColor; 4 | 5 | in vec3 fragPos; 6 | 7 | vec4 skyTop = vec4(0.65, 0.84, 0.95, 1.0); 8 | vec4 skyBottom = vec4(0.84, 0.92, 0.98, 1.0); 9 | vec4 groundTop = vec4(0.42, 0.40, 0.37, 1.0); 10 | vec4 groundBottom = vec4(0.16, 0.18, 0.21, 1.0); 11 | 12 | 13 | vec4 skyGradient(float height) 14 | { 15 | if (height < 0.48) 16 | { 17 | float fac = 1 - (height / 0.48); 18 | fac = 1 - exp( -10 * fac ); 19 | 20 | return mix(groundTop, groundBottom, fac); 21 | } 22 | else if (height < 0.52) 23 | { 24 | float fac = (height - 0.48) / (0.52 - 0.48); 25 | fac = fac * fac * (3.0 - 2.0 * fac); 26 | 27 | return mix(groundTop, skyBottom, fac); 28 | } 29 | else 30 | { 31 | float fac = (height - 0.52) / (1 - 0.52); 32 | fac = 1 - exp( -6 * fac ); 33 | 34 | return mix(skyBottom, skyTop, fac); 35 | } 36 | } 37 | 38 | 39 | void main() 40 | { 41 | vec3 normPos = normalize(fragPos); // project cube onto unit sphere 42 | float val = (normPos.y + 1) * 0.5; 43 | FragColor = skyGradient(val); 44 | } -------------------------------------------------------------------------------- /Gepe3D/res/Shaders/skybox.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec3 vertexPosition; 4 | 5 | uniform vec3 cameraPos; 6 | uniform mat4 cameraMatrix; 7 | 8 | out vec3 fragPos; 9 | 10 | void main() 11 | { 12 | gl_Position = cameraMatrix * vec4(vertexPosition + cameraPos, 1.0); 13 | fragPos = vertexPosition; 14 | } -------------------------------------------------------------------------------- /Gepe3D/src/BallCharacter.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Collections.Generic; 4 | using OpenTK.Mathematics; 5 | using OpenTK.Windowing.GraphicsLibraryFramework; 6 | 7 | namespace Gepe3D 8 | { 9 | public class BallCharacter 10 | { 11 | 12 | /////////////////////////// 13 | // ball generation utils // 14 | /////////////////////////// 15 | 16 | private static readonly (Vector3i, Vector3i)[] connections = 17 | { 18 | // 1 axis 19 | ( new Vector3i(0, 0, 0), new Vector3i(1, 0, 0) ) , 20 | ( new Vector3i(0, 0, 0), new Vector3i(0, 1, 0) ) , 21 | ( new Vector3i(0, 0, 0), new Vector3i(0, 0, 1) ) , 22 | 23 | // 2 axes 24 | ( new Vector3i(0, 0, 0), new Vector3i(1, 1, 0) ) , 25 | ( new Vector3i(0, 0, 0), new Vector3i(0, 1, 1) ) , 26 | ( new Vector3i(0, 0, 0), new Vector3i(1, 0, 1) ) , 27 | 28 | // 2 axes other 29 | ( new Vector3i(1, 0, 0), new Vector3i(0, 1, 0) ) , 30 | ( new Vector3i(1, 0, 0), new Vector3i(0, 0, 1) ) , 31 | ( new Vector3i(0, 1, 0), new Vector3i(0, 0, 1) ) , 32 | 33 | // 3 axes 34 | ( new Vector3i(0, 0, 0), new Vector3i(1, 1, 1) ) , 35 | ( new Vector3i(1, 0, 0), new Vector3i(0, 1, 1) ) , 36 | ( new Vector3i(0, 1, 0), new Vector3i(1, 0, 1) ) , 37 | ( new Vector3i(0, 0, 1), new Vector3i(1, 1, 0) ) , 38 | }; 39 | 40 | private static int coord2id(Vector3i coord, int xRes, int yRes, int zRes) 41 | { 42 | return 43 | coord.X * yRes * zRes + 44 | coord.Y * zRes + 45 | coord.Z; 46 | } 47 | 48 | private static Vector3i id2coord(int id, int xRes, int yRes, int zRes) 49 | { 50 | int x = id / (yRes * zRes); 51 | int y = ( id % (yRes * zRes) ) / zRes; 52 | int z = ( id % (yRes * zRes) ) % zRes; 53 | return new Vector3i(x, y, z); 54 | } 55 | 56 | ////////////////////////// 57 | // ball character class // 58 | ////////////////////////// 59 | 60 | private readonly ParticleSystem particleSystem; 61 | private readonly int centreParticleID; 62 | private Vector3 centrePos; 63 | public Camera activeCam = new Camera( new Vector3(), 16f / 9f); 64 | 65 | // initially points in the positive X 66 | private float pitch = 0; 67 | private float yaw = 0; 68 | public float Sensitivity = 0.2f; 69 | 70 | int[] particleIDs; 71 | 72 | 73 | public BallCharacter( ParticleSystem particleSystem, float x, float y, float z, float radius, int resolution ) { 74 | 75 | this.particleSystem = particleSystem; 76 | 77 | Dictionary coord2id = new Dictionary(); 78 | 79 | List particlesList = new List(); 80 | int centreParticleTemp = 0; 81 | float closestDist = float.MaxValue; 82 | 83 | int currentID = 0; 84 | for (int px = 0; px < resolution; px++) 85 | { 86 | for (int py = 0; py < resolution; py++) 87 | { 88 | for (int pz = 0; pz < resolution; pz++) 89 | { 90 | float offsetX = MathHelper.Lerp( -radius, +radius, px / (resolution - 1f) ); 91 | float offsetY = MathHelper.Lerp( -radius, +radius, py / (resolution - 1f) ); 92 | float offsetZ = MathHelper.Lerp( -radius, +radius, pz / (resolution - 1f) ); 93 | float dist = MathF.Sqrt(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ); 94 | 95 | if (dist <= radius) { 96 | 97 | particleSystem.SetPhase(currentID, ParticleSystem.PHASE_SOLID); 98 | particleSystem.SetColour(currentID, 1, 0.6f, 0); 99 | 100 | particleSystem.SetPos( 101 | currentID, 102 | x + offsetX, 103 | y + offsetY, 104 | z + offsetZ 105 | ); 106 | coord2id[ new Vector3i(px, py, pz) ] = currentID; 107 | 108 | if (dist < closestDist) { 109 | closestDist = dist; 110 | centreParticleTemp = currentID; 111 | } 112 | particlesList.Add(currentID); 113 | 114 | currentID++; 115 | } 116 | } 117 | } 118 | } 119 | 120 | foreach (KeyValuePair pair in coord2id) 121 | { 122 | Vector3i coord = pair.Key; 123 | foreach ( (Vector3i, Vector3i) connect in connections) 124 | { 125 | Vector3i c1 = coord + connect.Item1; 126 | Vector3i c2 = coord + connect.Item2; 127 | if (coord2id.ContainsKey(c1) && coord2id.ContainsKey(c2)) 128 | { 129 | int p1 = coord2id[c1]; 130 | int p2 = coord2id[c2]; 131 | Vector3 pos1 = particleSystem.GetPos(p1); 132 | Vector3 pos2 = particleSystem.GetPos(p2); 133 | float dist = (pos1 - pos2).Length; 134 | particleSystem.AddDistConstraint(p1, p2, dist); 135 | } 136 | } 137 | } 138 | 139 | this.centreParticleID = centreParticleTemp; 140 | this.particleIDs = particlesList.ToArray(); 141 | } 142 | 143 | public void MouseMovementUpdate(Vector2 mouseDelta) { 144 | 145 | yaw += mouseDelta.X * Sensitivity; 146 | pitch -= mouseDelta.Y * Sensitivity; 147 | pitch = MathHelper.Clamp(pitch, -89.9f, 89.9f); 148 | } 149 | 150 | public void Update(float delta, KeyboardState keyboardState) { 151 | 152 | 153 | Vector3 camOffset = new Vector3(7, 0, 0); 154 | camOffset = Vector3.TransformColumn( Matrix3.CreateRotationZ( MathHelper.DegreesToRadians(pitch) ), camOffset ); 155 | camOffset = Vector3.TransformColumn( Matrix3.CreateRotationY( MathHelper.DegreesToRadians(yaw) ), camOffset ); 156 | 157 | centrePos = particleSystem.GetPos(centreParticleID); 158 | activeCam.SetPos(centrePos + camOffset); 159 | activeCam.LookAt(centrePos); 160 | activeCam.UpdateLocalVectors(); 161 | 162 | Vector3 movement = new Vector3(); 163 | if ( keyboardState.IsKeyDown(Keys.W) ) { movement.X += 1; } 164 | if ( keyboardState.IsKeyDown(Keys.S) ) { movement.X -= 1; } 165 | if ( keyboardState.IsKeyDown(Keys.A) ) { movement.Z -= 1; } 166 | if ( keyboardState.IsKeyDown(Keys.D) ) { movement.Z += 1; } 167 | if ( keyboardState.IsKeyDown(Keys.Space) ) { movement.Y += 1; } 168 | if ( keyboardState.IsKeyDown(Keys.LeftShift) ) { movement.Y -= 1; } 169 | if (movement.Length != 0) movement.Normalize(); 170 | 171 | Matrix4 rotation = Matrix4.CreateRotationY( MathHelper.DegreesToRadians(-yaw) ); 172 | rotation.Transpose(); // OpenTK matrices are transposed by default for some reason 173 | 174 | Vector4 rotatedMovement = rotation * new Vector4(movement, 1); 175 | movement.X = -rotatedMovement.X; 176 | movement.Y = rotatedMovement.Y; 177 | movement.Z = -rotatedMovement.Z; 178 | 179 | movement *= 0.1f; 180 | 181 | foreach (int pID in particleIDs) { 182 | particleSystem.AddVel(pID, movement.X, movement.Y, movement.Z); 183 | } 184 | 185 | } 186 | 187 | public float GetCenterX() 188 | { 189 | return centrePos.X; 190 | } 191 | 192 | 193 | } 194 | } -------------------------------------------------------------------------------- /Gepe3D/src/CLUtils.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using OpenTK.Compute.OpenCL; 4 | using System.Linq; 5 | using System.IO; 6 | 7 | namespace Gepe3D 8 | { 9 | public class CLUtils 10 | { 11 | 12 | 13 | public static string LoadSource(string filePath) 14 | { 15 | filePath = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), filePath); 16 | return File.ReadAllText(filePath); 17 | } 18 | 19 | public static CLProgram BuildClProgram(CLContext context, CLDevice[] devices, string source) 20 | { 21 | CLResultCode result; 22 | CLProgram program = CL.CreateProgramWithSource(context, source, out result); 23 | result = CL.BuildProgram(program, 1, devices, null, new IntPtr(), new IntPtr()); 24 | 25 | if (result != CLResultCode.Success) { 26 | System.Console.WriteLine(result); 27 | byte[] logParam; 28 | CL.GetProgramBuildInfo(program, devices[0], ProgramBuildInfo.Log, out logParam); 29 | string error = System.Text.ASCIIEncoding.Default.GetString(logParam); 30 | System.Console.WriteLine(error); 31 | } 32 | return program; 33 | } 34 | 35 | public static void EnqueueKernel(CLCommandQueue queue, CLKernel kernel, int numWorkUnits, params object[] args) 36 | { 37 | CLResultCode result = CLResultCode.Success; 38 | uint argID = 0; 39 | foreach (object arg in args) 40 | { 41 | Type argType = arg.GetType(); 42 | if (argType == typeof(float)) { 43 | result = CL.SetKernelArg(kernel, argID++, (float) arg); 44 | } else if (argType == typeof(int)) { 45 | result = CL.SetKernelArg(kernel, argID++, (int) arg); 46 | } else if (argType == typeof(CLBuffer)) { 47 | result = CL.SetKernelArg(kernel, argID++, (CLBuffer) arg); 48 | } else { 49 | System.Console.WriteLine("Invalid type of kernel argument! Must be float, int or CLBuffer"); 50 | } 51 | if (result != CLResultCode.Success) 52 | System.Console.WriteLine("Kernel argument error: " + result); 53 | } 54 | UIntPtr[] workDim = new UIntPtr[] { new UIntPtr( (uint) numWorkUnits) }; 55 | CLEvent @event = new CLEvent(); 56 | CL.EnqueueNDRangeKernel(queue, kernel, 1, null, workDim, null, 0, null, out @event); 57 | } 58 | 59 | public static CLBuffer EnqueueMakeIntBuffer(CLContext context, CLCommandQueue queue, int numEntries, int defaultValue) 60 | { 61 | CLResultCode result; 62 | CLEvent @event; 63 | UIntPtr bufferSize = new UIntPtr( (uint) numEntries * sizeof(int) ); 64 | int[] fillArray = new int[] {defaultValue}; 65 | CLBuffer buffer = CL.CreateBuffer(context, MemoryFlags.ReadWrite, bufferSize, new IntPtr(), out result); 66 | CL.EnqueueFillBuffer(queue, buffer, fillArray, new UIntPtr(), bufferSize, null, out @event); 67 | return buffer; 68 | } 69 | 70 | public static CLBuffer EnqueueMakeFloatBuffer(CLContext context, CLCommandQueue queue, int numEntries, float defaultValue) 71 | { 72 | CLResultCode result; 73 | CLEvent @event; 74 | UIntPtr bufferSize = new UIntPtr( (uint) numEntries * sizeof(float) ); 75 | float[] fillArray = new float[] {defaultValue}; 76 | CLBuffer buffer = CL.CreateBuffer(context, MemoryFlags.ReadWrite, bufferSize, new IntPtr(), out result); 77 | CL.EnqueueFillBuffer(queue, buffer, fillArray, new UIntPtr(), bufferSize, null, out @event); 78 | return buffer; 79 | } 80 | 81 | public static void EnqueueFillIntBuffer(CLCommandQueue queue, CLBuffer buffer, int fillValue, int numEntries) 82 | { 83 | CLEvent @event; 84 | UIntPtr bufferSize = new UIntPtr( (uint) numEntries * sizeof(int) ); 85 | int[] fillArray = new int[] {fillValue}; 86 | CL.EnqueueFillBuffer(queue, buffer, fillArray, new UIntPtr(), bufferSize, null, out @event); 87 | } 88 | 89 | public static void EnqueueFillFloatBuffer(CLCommandQueue queue, CLBuffer buffer, float fillValue, int numEntries) 90 | { 91 | CLEvent @event; 92 | UIntPtr bufferSize = new UIntPtr( (uint) numEntries * sizeof(float) ); 93 | float[] fillArray = new float[] {fillValue}; 94 | CL.EnqueueFillBuffer(queue, buffer, fillArray, new UIntPtr(), bufferSize, null, out @event); 95 | } 96 | 97 | 98 | } 99 | } -------------------------------------------------------------------------------- /Gepe3D/src/Camera.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK.Mathematics; 3 | using OpenTK.Windowing.GraphicsLibraryFramework; 4 | 5 | namespace Gepe3D 6 | { 7 | public class Camera 8 | { 9 | 10 | public Vector3 Position { get; set; } 11 | public float AspectRatio { private get; set; } 12 | public float FovDegrees = 70; 13 | public float NearClip = 0.01f, FarClip = 500; 14 | public float MovementSpeed = 1.5f; 15 | public float Sensitivity = 0.2f; 16 | 17 | // initially points in the positive X 18 | private float pitch = 0; 19 | private float yaw = 0; 20 | 21 | private Vector3 _localForward = -Vector3.UnitZ; 22 | private Vector3 _localUp = Vector3.UnitY; 23 | private Vector3 _localRight = Vector3.UnitX; 24 | 25 | public Camera(Vector3 position, float aspectRatio) 26 | { 27 | Position = position; 28 | AspectRatio = aspectRatio; 29 | } 30 | 31 | public Matrix4 GetViewMatrix() 32 | { 33 | Matrix4 viewMatrix = Matrix4.LookAt(Position, Position + _localForward, _localUp); 34 | viewMatrix.Transpose(); 35 | return viewMatrix; 36 | } 37 | 38 | public Matrix4 GetProjectionMatrix() 39 | { 40 | Matrix4 projMatrix = Matrix4.CreatePerspectiveFieldOfView( MathHelper.DegreesToRadians(FovDegrees), AspectRatio, NearClip, FarClip ); 41 | projMatrix.Transpose(); 42 | return projMatrix; 43 | } 44 | 45 | public Matrix4 GetMatrix() 46 | { 47 | // OpenTK matrices are transposed by default for some reason 48 | Matrix4 viewMatrix = Matrix4.LookAt(Position, Position + _localForward, _localUp); 49 | viewMatrix.Transpose(); 50 | Matrix4 projMatrix = Matrix4.CreatePerspectiveFieldOfView( MathHelper.DegreesToRadians(FovDegrees), AspectRatio, NearClip, FarClip ); 51 | projMatrix.Transpose(); 52 | 53 | // transformations go from right to left 54 | return projMatrix * viewMatrix; 55 | } 56 | 57 | public void Update(float delta, KeyboardState keyboardState) 58 | { 59 | Vector3 movement = new Vector3(); 60 | if ( keyboardState.IsKeyDown(Keys.W) ) { movement.X += 1; } 61 | if ( keyboardState.IsKeyDown(Keys.S) ) { movement.X -= 1; } 62 | if ( keyboardState.IsKeyDown(Keys.A) ) { movement.Z -= 1; } 63 | if ( keyboardState.IsKeyDown(Keys.D) ) { movement.Z += 1; } 64 | if ( keyboardState.IsKeyDown(Keys.Space) ) { movement.Y += 1; } 65 | if ( keyboardState.IsKeyDown(Keys.LeftShift) ) { movement.Y -= 1; } 66 | if (movement.Length != 0) movement.Normalize(); 67 | 68 | Matrix4 rotation = Matrix4.CreateRotationY( MathHelper.DegreesToRadians(-yaw) ); 69 | rotation.Transpose(); // OpenTK matrices are transposed by default for some reason 70 | 71 | Vector4 rotatedMovement = rotation * new Vector4(movement, 1); 72 | movement.X = rotatedMovement.X; 73 | movement.Y = rotatedMovement.Y; 74 | movement.Z = rotatedMovement.Z; 75 | 76 | Position += movement * MovementSpeed * delta; 77 | UpdateLocalVectors(); 78 | } 79 | 80 | public void MouseInput(Vector2 mouseDelta) 81 | { 82 | yaw += mouseDelta.X * Sensitivity; 83 | pitch -= mouseDelta.Y * Sensitivity; 84 | pitch = MathHelper.Clamp(pitch, -89.9f, 89.9f); 85 | } 86 | 87 | 88 | public void LookAt(float x, float y, float z) 89 | { 90 | float dx = x - Position.X; 91 | float dy = y - Position.Y; 92 | float dz = z - Position.Z; 93 | float horizontalDist = MathF.Sqrt(dx * dx + dz * dz); 94 | if (horizontalDist == 0) return; 95 | 96 | pitch = MathHelper.RadiansToDegrees( MathF.Atan2(dy, horizontalDist) ); 97 | yaw = MathHelper.RadiansToDegrees( MathF.Atan2(dz, dx) ); 98 | } 99 | 100 | public void LookAt(Vector3 lookAt) 101 | { 102 | float dx = lookAt.X - Position.X; 103 | float dy = lookAt.Y - Position.Y; 104 | float dz = lookAt.Z - Position.Z; 105 | float horizontalDist = MathF.Sqrt(dx * dx + dz * dz); 106 | if (horizontalDist == 0) return; 107 | 108 | pitch = MathHelper.RadiansToDegrees( MathF.Atan2(dy, horizontalDist) ); 109 | yaw = MathHelper.RadiansToDegrees( MathF.Atan2(dz, dx) ); 110 | } 111 | 112 | public void SetPos(Vector3 pos) 113 | { 114 | Position = pos; 115 | } 116 | 117 | public void UpdateLocalVectors() 118 | { 119 | _localForward.X = MathF.Cos( MathHelper.DegreesToRadians(pitch) ) * MathF.Cos( MathHelper.DegreesToRadians(yaw) ); 120 | _localForward.Y = MathF.Sin( MathHelper.DegreesToRadians(pitch) ); 121 | _localForward.Z = MathF.Cos( MathHelper.DegreesToRadians(pitch) ) * MathF.Sin( MathHelper.DegreesToRadians(yaw) ); 122 | 123 | _localForward = Vector3.Normalize( _localForward ); 124 | _localRight = Vector3.Normalize( Vector3.Cross(_localForward, Vector3.UnitY) ); 125 | _localUp = Vector3.Normalize( Vector3.Cross(_localRight , _localForward) ); 126 | } 127 | 128 | } 129 | } -------------------------------------------------------------------------------- /Gepe3D/src/GLUtils.cs: -------------------------------------------------------------------------------- 1 | 2 | using OpenTK.Graphics.OpenGL4; 3 | using System; 4 | 5 | namespace Gepe3D 6 | { 7 | public class GLUtils 8 | { 9 | 10 | public static int GenVAO() 11 | { 12 | return GL.GenVertexArray(); 13 | } 14 | 15 | public static int GenVBO(float[] data) 16 | { 17 | int vboID = GL.GenBuffer(); 18 | GL.BindBuffer(BufferTarget.ArrayBuffer, vboID); 19 | GL.BufferData(BufferTarget.ArrayBuffer, data.Length * sizeof(float), data, BufferUsageHint.StaticDraw); 20 | return vboID; 21 | } 22 | 23 | public static void ReplaceBufferData(int vboID, float[] data) 24 | { 25 | GL.BindBuffer(BufferTarget.ArrayBuffer, vboID); 26 | GL.BufferSubData(BufferTarget.ArrayBuffer, new IntPtr(0), data.Length * sizeof(float), data); 27 | } 28 | 29 | public static int AttachEBO(int vaoID, uint[] indices) 30 | { 31 | int eboID = GL.GenBuffer(); 32 | GL.BindVertexArray(vaoID); 33 | GL.BindBuffer(BufferTarget.ElementArrayBuffer, eboID); 34 | GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw); 35 | return eboID; 36 | } 37 | 38 | 39 | public static void VaoFloatAttrib(int vaoID, int vboID, int attribID, int attribSize, int floatsPerVertex, int startOffset) 40 | { 41 | GL.BindBuffer(BufferTarget.ArrayBuffer, vboID); 42 | GL.BindVertexArray(vaoID); 43 | GL.VertexAttribPointer(attribID, attribSize, VertexAttribPointerType.Float, false, floatsPerVertex * sizeof(float), startOffset * sizeof(float)); 44 | GL.EnableVertexAttribArray(attribID); 45 | } 46 | 47 | public static void VaoInstanceFloatAttrib(int vaoID, int vboID, int attribID, int attribSize, int floatsPerVertex, int startOffset) 48 | { 49 | GL.BindBuffer(BufferTarget.ArrayBuffer, vboID); 50 | GL.BindVertexArray(vaoID); 51 | GL.VertexAttribPointer(attribID, attribSize, VertexAttribPointerType.Float, false, floatsPerVertex * sizeof(float), startOffset * sizeof(float)); 52 | GL.VertexAttribDivisor(attribID, 1); 53 | GL.EnableVertexAttribArray(attribID); 54 | } 55 | 56 | public static void DrawVAO(int vaoID, int vertexCount) 57 | { 58 | GL.BindVertexArray(vaoID); 59 | GL.DrawArrays(PrimitiveType.Triangles, 0, vertexCount); 60 | } 61 | 62 | public static void DrawIndexedVAO(int vaoID, int indexCount) 63 | { 64 | GL.BindVertexArray(vaoID); 65 | GL.DrawElements(PrimitiveType.Triangles, indexCount, DrawElementsType.UnsignedInt, 0); 66 | } 67 | 68 | public static void DrawInstancedVAO(int vaoID, int vertexCount, int instanceCount) 69 | { 70 | GL.BindVertexArray(vaoID); 71 | GL.DrawArraysInstanced(PrimitiveType.Triangles, 0, vertexCount, instanceCount); 72 | } 73 | 74 | public static int GenFBO() 75 | { 76 | return GL.GenFramebuffer(); 77 | } 78 | 79 | public static void BindFBO(int fboID) 80 | { 81 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, fboID); 82 | } 83 | 84 | public static int FboAddTexture(int fboID, int width, int height) 85 | { 86 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, fboID); 87 | 88 | int texture = GL.GenTexture(); 89 | GL.BindTexture(TextureTarget.Texture2D, texture); 90 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32f, width, height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, new IntPtr()); 91 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); 92 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); 93 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge); 94 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge); 95 | GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, texture, 0); 96 | 97 | if (GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != FramebufferErrorCode.FramebufferComplete) 98 | throw new Exception("Frame Buffer Error!"); 99 | 100 | return texture; 101 | } 102 | 103 | public static int FboAddDepthStencilRBO(int fboID, int width, int height) 104 | { 105 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, fboID); 106 | 107 | int rbo = GL.GenRenderbuffer(); 108 | GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, rbo); 109 | GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, width, height); 110 | GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); 111 | GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, rbo); 112 | 113 | return rbo; 114 | } 115 | 116 | public static void CopyStencilBuffer(int fromFBO, int toFBO, int width, int height) 117 | { 118 | GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, fromFBO); 119 | GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, toFBO); 120 | GL.BlitFramebuffer(0, 0, width, height, 0, 0, width, height, ClearBufferMask.StencilBufferBit, BlitFramebufferFilter.Nearest); 121 | } 122 | 123 | public static void DrawPostProcessing(int screenTexture, int quadVAO) 124 | { 125 | GL.Disable(EnableCap.DepthTest); 126 | 127 | GL.BindVertexArray(quadVAO); 128 | GL.BindTexture(TextureTarget.Texture2D, screenTexture); 129 | GL.DrawArrays(PrimitiveType.Triangles, 0, 6); 130 | 131 | GL.Enable(EnableCap.DepthTest); 132 | } 133 | 134 | public static void StencilWriteMode() 135 | { 136 | GL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Replace); 137 | GL.StencilFunc(StencilFunction.Always, 1, 0xFF); 138 | GL.StencilMask(0xFF); 139 | } 140 | 141 | public static void StencilReadMode() 142 | { 143 | GL.StencilFunc(StencilFunction.Equal, 1, 0xFF); 144 | GL.StencilMask(0x00); 145 | } 146 | 147 | 148 | 149 | 150 | } 151 | } -------------------------------------------------------------------------------- /Gepe3D/src/MainWindow.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using OpenTK.Graphics.OpenGL4; 4 | using OpenTK.Mathematics; 5 | using OpenTK.Windowing.Common; 6 | using OpenTK.Windowing.GraphicsLibraryFramework; 7 | using OpenTK.Windowing.Desktop; 8 | 9 | namespace Gepe3D 10 | { 11 | public class MainWindow : GameWindow 12 | { 13 | 14 | static void Main(string[] args) 15 | { 16 | 17 | GameWindowSettings settings = GameWindowSettings.Default; 18 | settings.UpdateFrequency = 100; 19 | 20 | MainWindow game = new MainWindow( 21 | settings, 22 | new NativeWindowSettings() 23 | { 24 | Size = new Vector2i(1280, 720), 25 | Title = "Gepe3D", 26 | // WindowBorder = WindowBorder.Hidden 27 | } 28 | ); 29 | 30 | game.CenterWindow(); 31 | game.Run(); 32 | } 33 | 34 | 35 | public Vector3 ambientLight = new Vector3(0.2f, 0.2f, 0.2f); 36 | public Vector3 lightPos = new Vector3(0f, 10f, 0f); 37 | public SkyBox skyBox; 38 | public ParticleSystem particleSystem; 39 | public BallCharacter character; 40 | public Spike[] spikes; 41 | 42 | public MainWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings) 43 | : base(gameWindowSettings, nativeWindowSettings) 44 | { 45 | } 46 | 47 | protected override void OnLoad() 48 | { 49 | base.OnLoad(); 50 | GL.ClearColor(1, 0, 1, 1); 51 | GL.Enable(EnableCap.DepthTest); 52 | GL.Enable(EnableCap.CullFace); 53 | GL.CullFace(CullFaceMode.Back); 54 | GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); 55 | GL.Enable(EnableCap.Blend); 56 | 57 | CursorGrabbed = true; 58 | 59 | skyBox = new SkyBox(); 60 | particleSystem = new ParticleSystem(7000); 61 | 62 | 63 | /////////////////////////////////////// 64 | // Setting up fluid, ball and spikes // 65 | /////////////////////////////////////// 66 | 67 | Random rand = new Random(); 68 | for (int i = 0; i < particleSystem.ParticleCount; i++) { 69 | float x = (float) rand.NextDouble() * ParticleSystem.MAX_X; 70 | float y = (float) rand.NextDouble() * ParticleSystem.MAX_Y * 0.7f; 71 | float z = (float) rand.NextDouble() * ParticleSystem.MAX_Z * 0.3f + ParticleSystem.MAX_Z * 0.7f; 72 | particleSystem.SetPos(i, x, y, z); 73 | particleSystem.SetPhase(i, ParticleSystem.PHASE_LIQUID); 74 | 75 | particleSystem.SetColour( i, 0, 0.5f, 1 ); 76 | } 77 | 78 | character = new BallCharacter( 79 | particleSystem, 80 | ParticleSystem.MAX_X * 0.5f, 81 | 1.1f, 82 | ParticleSystem.MAX_Z * 0.3f, 83 | 1, 12 84 | ); 85 | 86 | spikes = new Spike[3]; 87 | 88 | float radius = 1f; 89 | float x1 = radius - (ParticleSystem.MAX_X + radius * 2) * 1.333f; 90 | float x2 = radius - (ParticleSystem.MAX_X + radius * 2) * 1.667f; 91 | float x3 = radius - (ParticleSystem.MAX_X + radius * 2) * 2.000f; 92 | spikes[0] = new Spike(particleSystem, x1, Spike.RandZ(radius), 2f, radius, 800); 93 | spikes[1] = new Spike(particleSystem, x2, Spike.RandZ(radius), 2f, radius, 2000); 94 | spikes[2] = new Spike(particleSystem, x3, Spike.RandZ(radius), 2f, radius, 3000); 95 | 96 | } 97 | 98 | protected override void OnUpdateFrame(FrameEventArgs e) 99 | { 100 | if (KeyboardState.IsKeyDown(Keys.Escape)) Close(); 101 | 102 | float delta = 0.01f; 103 | character.Update(delta, KeyboardState); 104 | foreach (Spike s in spikes) s.Update(); 105 | float shiftX = 4.5f * delta; 106 | particleSystem.Update(delta, shiftX); 107 | } 108 | 109 | protected override void OnRenderFrame(FrameEventArgs e) 110 | { 111 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 112 | 113 | character.MouseMovementUpdate(MouseState.Delta); 114 | skyBox.Render(this); 115 | particleSystem.Render(this); 116 | 117 | SwapBuffers(); 118 | } 119 | 120 | 121 | } 122 | } -------------------------------------------------------------------------------- /Gepe3D/src/ParticleSystem.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Collections.Generic; 4 | using OpenTK.Compute.OpenCL; 5 | using OpenTK.Mathematics; 6 | 7 | namespace Gepe3D 8 | { 9 | public class ParticleSystem 10 | { 11 | 12 | // Render 13 | private readonly int quad_VAO; 14 | private readonly int quad_VBO; 15 | private readonly int instancePositions_VBO; 16 | private readonly int instanceColours_VBO; 17 | private readonly Shader particleShader; 18 | 19 | // Update 20 | CLCommandQueue queue; 21 | CLEvent @event = new CLEvent(); 22 | 23 | public readonly int ParticleCount; 24 | private readonly int cellCount; 25 | private readonly float[] posData; 26 | private readonly float[] ePosData; 27 | private readonly float[] velData; 28 | private readonly float[] colourData; 29 | private readonly int[] phaseData; 30 | 31 | public static Vector3 GRAVITY = new Vector3(-3, -6, 0); 32 | public static float PARTICLE_RADIUS = 0.2f; 33 | public static float GRID_CELL_WIDTH = 0.6f; 34 | public static float KERNEL_SIZE = 0.6f; 35 | public static float REST_DENSITY = 80f; 36 | 37 | public static int 38 | GridRowsX = 16, 39 | GridRowsY = 10, 40 | GridRowsZ = 12; 41 | 42 | public static float 43 | MAX_X = GRID_CELL_WIDTH * GridRowsX, 44 | MAX_Y = GRID_CELL_WIDTH * GridRowsY, 45 | MAX_Z = GRID_CELL_WIDTH * GridRowsZ; 46 | 47 | public static int 48 | PHASE_LIQUID = 0, 49 | PHASE_SOLID = 1, 50 | PHASE_STATIC = 2; 51 | 52 | 53 | private readonly CLKernel 54 | k_PredictPos, 55 | k_CalcLambdas, 56 | k_FluidCorrect, 57 | k_CorrectPredictions, 58 | k_UpdateVel, 59 | k_CalcVorticity, 60 | k_ApplyVortVisc, 61 | k_CorrectVel, 62 | k_AssignParticleCells, 63 | k_FindCellsStartAndEnd, 64 | k_SortParticleIDsByCell, 65 | k_SolidCorrect; 66 | 67 | private readonly CLBuffer 68 | b_Pos, // positions 69 | b_Vel, // velocities 70 | b_ePos, // estimated positions 71 | b_imass, // inverse masses 72 | b_lambdas, // fluid correction scalar 73 | b_phase, // particle phase 74 | b_Vorticities, // fluid vorticities 75 | b_PosCorrection, // position correction 76 | b_VelCorrection, // velocity correction 77 | 78 | // buffers for neighbour search 79 | b_sortedParticleIDs, 80 | b_cellStartAndEndIDs, 81 | b_cellIDsOfParticles, 82 | b_numParticlesPerCell, 83 | b_particleIDinCell; 84 | 85 | 86 | private bool 87 | posDirty = false, 88 | velDirty = false, 89 | phaseDirty = false, 90 | colourDirty = false; 91 | 92 | List<(int, int, float)> distanceConstraints = new List<(int, int, float)>(); 93 | 94 | 95 | 96 | public ParticleSystem(int particleCount) 97 | { 98 | this.ParticleCount = particleCount; 99 | this.cellCount = GridRowsX * GridRowsY * GridRowsZ; 100 | posData = new float[particleCount * 3]; 101 | velData = new float[particleCount * 3]; 102 | colourData = new float[particleCount * 3]; 103 | ePosData = new float[particleCount * 3]; 104 | phaseData = new int [particleCount]; 105 | 106 | 107 | /////////////////// 108 | // Set up OpenCL // 109 | /////////////////// 110 | 111 | // set up context & queue 112 | CLResultCode result; 113 | CLPlatform[] platforms; 114 | CLDevice[] devices; 115 | result = CL.GetPlatformIds(out platforms); 116 | result = CL.GetDeviceIds(platforms[0], DeviceType.Gpu, out devices); 117 | if (result == CLResultCode.DeviceNotFound) { 118 | CL.GetDeviceIds(platforms[0], DeviceType.All, out devices); 119 | System.Console.WriteLine("Failed to initiate OpenCL using GPU, cannot use hardware acceleration"); 120 | } 121 | CLContext context = CL.CreateContext(new IntPtr(), 1, devices, new IntPtr(), new IntPtr(), out result); 122 | this.queue = CL.CreateCommandQueueWithProperties(context, devices[0], new IntPtr(), out result); 123 | 124 | // load kernels 125 | string varDefines = GenerateDefines(); // combine with other source strings to add common functions 126 | string commonFuncSource = CLUtils.LoadSource("res/Kernels/common_funcs.cl"); 127 | string pbdCommonSource = CLUtils.LoadSource("res/Kernels/pbd_common.cl"); 128 | string fluidProjectSource = CLUtils.LoadSource("res/Kernels/fluid_project.cl"); 129 | string solidProjectSource = CLUtils.LoadSource("res/Kernels/solid_project.cl"); 130 | CLProgram pbdProgram = CLUtils.BuildClProgram(context, devices, varDefines + commonFuncSource + pbdCommonSource ); 131 | CLProgram fluidProgram = CLUtils.BuildClProgram(context, devices, varDefines + commonFuncSource + fluidProjectSource); 132 | CLProgram solidProgram = CLUtils.BuildClProgram(context, devices, varDefines + commonFuncSource + solidProjectSource); 133 | 134 | this.k_AssignParticleCells = CL.CreateKernel( pbdProgram , "assign_particle_cells" , out result); 135 | this.k_FindCellsStartAndEnd = CL.CreateKernel( pbdProgram , "find_cells_start_and_end" , out result); 136 | this.k_SortParticleIDsByCell = CL.CreateKernel( pbdProgram , "sort_particle_ids_by_cell" , out result); 137 | this.k_PredictPos = CL.CreateKernel( pbdProgram , "predict_positions" , out result); 138 | this.k_CorrectPredictions = CL.CreateKernel( pbdProgram , "correct_predictions" , out result); 139 | this.k_UpdateVel = CL.CreateKernel( pbdProgram , "update_velocity" , out result); 140 | this.k_CalcLambdas = CL.CreateKernel( fluidProgram , "calculate_lambdas" , out result); 141 | this.k_FluidCorrect = CL.CreateKernel( fluidProgram , "calc_fluid_corrections" , out result); 142 | this.k_CalcVorticity = CL.CreateKernel( fluidProgram , "calculate_vorticities" , out result); 143 | this.k_ApplyVortVisc = CL.CreateKernel( fluidProgram , "apply_vorticity_viscosity" , out result); 144 | this.k_CorrectVel = CL.CreateKernel( fluidProgram , "correct_fluid_vel" , out result); 145 | this.k_SolidCorrect = CL.CreateKernel( solidProgram , "calc_solid_corrections" , out result); 146 | 147 | // create buffers 148 | this.b_Pos = CLUtils.EnqueueMakeFloatBuffer(context, queue, particleCount * 3 , 0); 149 | this.b_Vel = CLUtils.EnqueueMakeFloatBuffer(context, queue, particleCount * 3 , 0); 150 | this.b_ePos = CLUtils.EnqueueMakeFloatBuffer(context, queue, particleCount * 3 , 0); 151 | this.b_imass = CLUtils.EnqueueMakeFloatBuffer(context, queue, particleCount , 1); 152 | this.b_lambdas = CLUtils.EnqueueMakeFloatBuffer(context, queue, particleCount , 0); 153 | this.b_PosCorrection = CLUtils.EnqueueMakeFloatBuffer(context, queue, particleCount * 3 , 0); 154 | this.b_Vorticities = CLUtils.EnqueueMakeFloatBuffer(context, queue, particleCount * 3 , 0); 155 | this.b_VelCorrection = CLUtils.EnqueueMakeFloatBuffer(context, queue, particleCount * 3 , 0); 156 | this.b_phase = CLUtils.EnqueueMakeIntBuffer (context, queue, particleCount , 0); 157 | this.b_sortedParticleIDs = CLUtils.EnqueueMakeIntBuffer (context, queue, particleCount , 0); 158 | this.b_cellIDsOfParticles = CLUtils.EnqueueMakeIntBuffer (context, queue, particleCount , 0); 159 | this.b_particleIDinCell = CLUtils.EnqueueMakeIntBuffer (context, queue, particleCount , 0); 160 | this.b_cellStartAndEndIDs = CLUtils.EnqueueMakeIntBuffer (context, queue, cellCount * 2 , 0); 161 | this.b_numParticlesPerCell = CLUtils.EnqueueMakeIntBuffer (context, queue, cellCount , 0); 162 | 163 | // ensure fills are completed 164 | CL.Flush(queue); 165 | CL.Finish(queue); 166 | 167 | 168 | ///////////////////////////////// 169 | // Set up OpenGL for rendering // 170 | ///////////////////////////////// 171 | 172 | particleShader = new Shader("res/Shaders/point_sphere.vert", "res/Shaders/point_sphere.frag"); 173 | 174 | float[] vertexData = new float[] 175 | { 176 | // X value Y value Z value 177 | -PARTICLE_RADIUS / 2, -PARTICLE_RADIUS / 2, 0, 178 | PARTICLE_RADIUS / 2, -PARTICLE_RADIUS / 2, 0, // triangle 1 179 | PARTICLE_RADIUS / 2, PARTICLE_RADIUS / 2, 0, 180 | 181 | -PARTICLE_RADIUS / 2, -PARTICLE_RADIUS / 2, 0, 182 | PARTICLE_RADIUS / 2, PARTICLE_RADIUS / 2, 0, // triangle 2 183 | -PARTICLE_RADIUS / 2, PARTICLE_RADIUS / 2, 0, 184 | }; 185 | 186 | quad_VAO = GLUtils.GenVAO(); 187 | quad_VBO = GLUtils.GenVBO(vertexData); 188 | instancePositions_VBO = GLUtils.GenVBO( posData ); 189 | instanceColours_VBO = GLUtils.GenVBO( colourData ); 190 | GLUtils.VaoFloatAttrib (quad_VAO, quad_VBO , 0, 3, 3, 0); // vertex positions 191 | GLUtils.VaoInstanceFloatAttrib(quad_VAO, instancePositions_VBO, 1, 3, 3, 0); 192 | GLUtils.VaoInstanceFloatAttrib(quad_VAO, instanceColours_VBO , 2, 3, 3, 0); 193 | } 194 | 195 | // set pos, phase, colour, constraints, 196 | 197 | public void SetPos(int id, float x, float y, float z) 198 | { 199 | posData[id * 3 + 0] = x; 200 | posData[id * 3 + 1] = y; 201 | posData[id * 3 + 2] = z; 202 | posDirty = true; 203 | } 204 | 205 | public void AddPos(int id, float x, float y, float z) 206 | { 207 | posData[id * 3 + 0] += x; 208 | posData[id * 3 + 1] += y; 209 | posData[id * 3 + 2] += z; 210 | posDirty = true; 211 | } 212 | 213 | public Vector3 GetPos(int id) 214 | { 215 | return new Vector3( posData[id * 3 + 0], posData[id * 3 + 1], posData[id * 3 + 2] ); 216 | } 217 | 218 | public void SetVel(int id, float x, float y, float z) 219 | { 220 | velData[id * 3 + 0] = x; 221 | velData[id * 3 + 1] = y; 222 | velData[id * 3 + 2] = z; 223 | velDirty = true; 224 | } 225 | 226 | public void AddVel(int id, float x, float y, float z) 227 | { 228 | velData[id * 3 + 0] += x; 229 | velData[id * 3 + 1] += y; 230 | velData[id * 3 + 2] += z; 231 | velDirty = true; 232 | } 233 | 234 | public Vector3 GetVel(int id) 235 | { 236 | return new Vector3( velData[id * 3 + 0], velData[id * 3 + 1], velData[id * 3 + 2] ); 237 | } 238 | 239 | public void SetPhase(int id, int phase) 240 | { 241 | phaseData[id] = phase; 242 | phaseDirty = true; 243 | } 244 | 245 | public void SetColour(int id, float r, float g, float b) 246 | { 247 | colourData[id * 3 + 0] = r; 248 | colourData[id * 3 + 1] = g; 249 | colourData[id * 3 + 2] = b; 250 | colourDirty = true; 251 | } 252 | 253 | public void AddDistConstraint(int id1, int id2, float dist) 254 | { 255 | distanceConstraints.Add( (id1, id2, dist) ); 256 | } 257 | 258 | 259 | private string GenerateDefines() 260 | { 261 | string defines = ""; 262 | 263 | defines += "\n" + "#define MAX_X " + MAX_X.ToString("0.0000") + "f"; 264 | defines += "\n" + "#define MAX_Y " + MAX_Y.ToString("0.0000") + "f"; 265 | defines += "\n" + "#define MAX_Z " + MAX_Z.ToString("0.0000") + "f"; 266 | defines += "\n" + "#define CELLCOUNT_X " + GridRowsX; 267 | defines += "\n" + "#define CELLCOUNT_Y " + GridRowsY; 268 | defines += "\n" + "#define CELLCOUNT_Z " + GridRowsZ; 269 | defines += "\n" + "#define CELL_WIDTH " + GRID_CELL_WIDTH.ToString("0.0000") + "f"; 270 | defines += "\n" + "#define KERNEL_SIZE " + KERNEL_SIZE.ToString("0.0000") + "f"; 271 | defines += "\n" + "#define REST_DENSITY " + REST_DENSITY.ToString("0.0000") + "f"; 272 | defines += "\n" + "#define PHASE_LIQUID " + PHASE_LIQUID; 273 | defines += "\n" + "#define PHASE_SOLID " + PHASE_SOLID; 274 | defines += "\n" + "#define PHASE_STATIC " + PHASE_STATIC; 275 | defines += "\n"; 276 | 277 | return defines; 278 | } 279 | 280 | 281 | public void Render(MainWindow world) 282 | { 283 | if (colourDirty) { 284 | GLUtils.ReplaceBufferData(instanceColours_VBO, colourData ); 285 | colourDirty = false; 286 | } 287 | GLUtils.ReplaceBufferData(instancePositions_VBO, posData ); 288 | 289 | particleShader.Use(); 290 | particleShader.SetVector3("lightPos", world.lightPos); 291 | particleShader.SetMatrix4("viewMatrix", world.character.activeCam.GetViewMatrix()); 292 | particleShader.SetMatrix4("projectionMatrix", world.character.activeCam.GetProjectionMatrix()); 293 | particleShader.SetFloat("particleRadius", PARTICLE_RADIUS); 294 | particleShader.SetFloat("maxX", MAX_X); 295 | 296 | GLUtils.DrawInstancedVAO(quad_VAO, 6, ParticleCount); 297 | } 298 | 299 | 300 | public void Update(float delta, float shiftX) 301 | { 302 | if (posDirty) { 303 | CL.EnqueueWriteBuffer(queue, b_Pos, false, new UIntPtr(), posData, null, out @event); 304 | posDirty = false; 305 | } 306 | 307 | if (velDirty) { 308 | CL.EnqueueWriteBuffer(queue, b_Vel, false, new UIntPtr(), velData, null, out @event); 309 | velDirty = false; 310 | } 311 | 312 | if (phaseDirty) { 313 | CL.EnqueueWriteBuffer(queue, b_phase, false, new UIntPtr(), phaseData, null, out @event); 314 | phaseDirty = false; 315 | } 316 | 317 | CLUtils.EnqueueFillIntBuffer(queue, b_sortedParticleIDs, 0, ParticleCount); 318 | CLUtils.EnqueueFillIntBuffer(queue, b_numParticlesPerCell, 0, cellCount); 319 | CLUtils.EnqueueFillFloatBuffer(queue, b_PosCorrection, 0, ParticleCount * 3); 320 | 321 | // predict particle positions, then sort particle IDs for neighbour finding accordingly 322 | CLUtils.EnqueueKernel(queue, k_PredictPos , ParticleCount , delta, b_Pos, b_Vel, b_ePos, b_phase, GRAVITY.X, GRAVITY.Y, GRAVITY.Z); 323 | CLUtils.EnqueueKernel(queue, k_AssignParticleCells , ParticleCount , b_ePos, b_numParticlesPerCell, b_cellIDsOfParticles, b_particleIDinCell); 324 | CLUtils.EnqueueKernel(queue, k_FindCellsStartAndEnd , cellCount , b_numParticlesPerCell, b_cellStartAndEndIDs); 325 | CLUtils.EnqueueKernel(queue, k_SortParticleIDsByCell , ParticleCount , b_particleIDinCell, b_cellStartAndEndIDs, b_cellIDsOfParticles, b_sortedParticleIDs); 326 | 327 | // correct the predicted positions to satisfy fluid and contact constraints 328 | CLUtils.EnqueueKernel(queue, k_CalcLambdas , ParticleCount , b_ePos, b_imass, b_lambdas, b_cellIDsOfParticles, b_cellStartAndEndIDs, b_sortedParticleIDs, b_phase); 329 | CLUtils.EnqueueKernel(queue, k_FluidCorrect , ParticleCount , b_ePos, b_imass, b_lambdas, b_PosCorrection, b_cellIDsOfParticles, b_cellStartAndEndIDs, b_sortedParticleIDs, b_phase); 330 | CLUtils.EnqueueKernel(queue, k_SolidCorrect , ParticleCount , b_ePos, b_imass, b_PosCorrection, b_cellIDsOfParticles, b_cellStartAndEndIDs, b_sortedParticleIDs, b_phase); 331 | CLUtils.EnqueueKernel(queue, k_CorrectPredictions , ParticleCount , b_Pos, b_ePos, b_PosCorrection, b_phase); 332 | 333 | CpuSolveDistConstraints(0.2f, 2); // parameters: stiffness, iterations 334 | 335 | // update particle velocities using corrected predictions, then correct fluid velocities for vorticity & viscosity 336 | CLUtils.EnqueueKernel(queue, k_UpdateVel , ParticleCount , delta, b_Pos, b_Vel, b_ePos, b_phase, shiftX); 337 | CLUtils.EnqueueKernel(queue, k_CalcVorticity , ParticleCount , b_Pos, b_Vel, b_Vorticities, b_cellIDsOfParticles, b_cellStartAndEndIDs, b_sortedParticleIDs, b_phase); 338 | CLUtils.EnqueueKernel(queue, k_ApplyVortVisc , ParticleCount , b_Pos, b_Vel, b_Vorticities, b_VelCorrection, b_imass, delta, b_cellIDsOfParticles, b_cellStartAndEndIDs, b_sortedParticleIDs, b_phase); 339 | CLUtils.EnqueueKernel(queue, k_CorrectVel , ParticleCount , b_Vel, b_VelCorrection); 340 | 341 | // read position and velocity data from GPU 342 | CL.EnqueueReadBuffer(queue, b_Pos, false, new UIntPtr(), posData, null, out @event); 343 | CL.EnqueueReadBuffer(queue, b_Vel, false, new UIntPtr(), velData, null, out @event); 344 | 345 | CL.Flush(queue); 346 | CL.Finish(queue); 347 | 348 | } 349 | 350 | // distant constraint projection is performed on CPU because i can't figure out how to do it in OpenCL 351 | private void CpuSolveDistConstraints(float stiffness, int iterations) 352 | { 353 | stiffness = 1 - MathF.Pow( 1 - stiffness, 1f / (float) iterations ); 354 | 355 | // read estimated positions from the GPU 356 | CL.EnqueueReadBuffer(queue, b_ePos, false, new UIntPtr(), ePosData, null, out @event); 357 | CL.Flush(queue); 358 | CL.Finish(queue); 359 | 360 | for (int i = 0; i < iterations; i++) 361 | { 362 | foreach( (int, int, float) constraint in distanceConstraints ) 363 | { 364 | int p1 = constraint.Item1; 365 | int p2 = constraint.Item2; 366 | float restDist = constraint.Item3; 367 | float imass1 = 1; 368 | float imass2 = 1; 369 | if (imass1 == 0 && imass2 == 0) return; 370 | 371 | Vector3 epos1 = new Vector3(ePosData[p1 * 3 + 0], ePosData[p1 * 3 + 1], ePosData[p1 * 3 + 2]); 372 | Vector3 epos2 = new Vector3(ePosData[p2 * 3 + 0], ePosData[p2 * 3 + 1], ePosData[p2 * 3 + 2]); 373 | 374 | Vector3 dir = epos1 - epos2; 375 | float displacement = dir.Length - restDist; 376 | dir.Normalize(); 377 | 378 | float w1 = imass1 / (imass1 + imass2); 379 | float w2 = imass2 / (imass1 + imass2); 380 | 381 | Vector3 correction1 = -w1 * displacement * dir; 382 | Vector3 correction2 = +w2 * displacement * dir; 383 | 384 | ePosData[p1 * 3 + 0] += correction1.X * stiffness; 385 | ePosData[p1 * 3 + 1] += correction1.Y * stiffness; 386 | ePosData[p1 * 3 + 2] += correction1.Z * stiffness; 387 | 388 | ePosData[p2 * 3 + 0] += correction2.X * stiffness; 389 | ePosData[p2 * 3 + 1] += correction2.Y * stiffness; 390 | ePosData[p2 * 3 + 2] += correction2.Z * stiffness; 391 | } 392 | } 393 | 394 | // write estimated positions back to the GPU 395 | CL.EnqueueWriteBuffer(queue, b_ePos, false, new UIntPtr(), ePosData, null, out @event); 396 | CL.Flush(queue); 397 | CL.Finish(queue); 398 | } 399 | 400 | } 401 | } -------------------------------------------------------------------------------- /Gepe3D/src/Shader.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.IO; 4 | using System.Collections.Generic; 5 | using OpenTK.Graphics.OpenGL4; 6 | using OpenTK.Mathematics; 7 | 8 | namespace Gepe3D 9 | { 10 | 11 | /* 12 | This whole class came from the OpenTK tutorial repository, pls dont come after me 13 | */ 14 | 15 | 16 | public class Shader 17 | { 18 | public readonly int Handle; 19 | 20 | private readonly Dictionary _uniformLocations; 21 | 22 | public Shader(string vertPath, string fragPath) 23 | { 24 | vertPath = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), vertPath); 25 | fragPath = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory), fragPath); 26 | 27 | string shaderSource = File.ReadAllText(vertPath); 28 | 29 | int vertexShader = GL.CreateShader(ShaderType.VertexShader); 30 | 31 | GL.ShaderSource(vertexShader, shaderSource); 32 | 33 | CompileShader(vertexShader); 34 | 35 | shaderSource = File.ReadAllText(fragPath); 36 | int fragmentShader = GL.CreateShader(ShaderType.FragmentShader); 37 | GL.ShaderSource(fragmentShader, shaderSource); 38 | CompileShader(fragmentShader); 39 | 40 | Handle = GL.CreateProgram(); 41 | 42 | GL.AttachShader(Handle, vertexShader); 43 | GL.AttachShader(Handle, fragmentShader); 44 | 45 | LinkProgram(Handle); 46 | 47 | GL.DetachShader(Handle, vertexShader); 48 | GL.DetachShader(Handle, fragmentShader); 49 | GL.DeleteShader(fragmentShader); 50 | GL.DeleteShader(vertexShader); 51 | 52 | GL.GetProgram(Handle, GetProgramParameterName.ActiveUniforms, out var numberOfUniforms); 53 | 54 | _uniformLocations = new Dictionary(); 55 | 56 | for (int i = 0; i < numberOfUniforms; i++) 57 | { 58 | string key = GL.GetActiveUniform(Handle, i, out _, out _); 59 | 60 | int location = GL.GetUniformLocation(Handle, key); 61 | 62 | _uniformLocations.Add(key, location); 63 | } 64 | } 65 | 66 | private static void CompileShader(int shader) 67 | { 68 | GL.CompileShader(shader); 69 | 70 | GL.GetShader(shader, ShaderParameter.CompileStatus, out var code); 71 | if (code != (int)All.True) 72 | { 73 | var infoLog = GL.GetShaderInfoLog(shader); 74 | throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}"); 75 | } 76 | } 77 | 78 | private static void LinkProgram(int program) 79 | { 80 | GL.LinkProgram(program); 81 | 82 | GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var code); 83 | if (code != (int)All.True) 84 | { 85 | throw new Exception($"Error occurred whilst linking Program({program})"); 86 | } 87 | } 88 | 89 | public void Use() 90 | { 91 | GL.UseProgram(Handle); 92 | } 93 | 94 | public int GetAttribLocation(string attribName) 95 | { 96 | return GL.GetAttribLocation(Handle, attribName); 97 | } 98 | 99 | public void SetInt(string name, int data) 100 | { 101 | GL.UseProgram(Handle); 102 | GL.Uniform1(_uniformLocations[name], data); 103 | } 104 | 105 | public void SetBool(string name, bool flag) 106 | { 107 | GL.UseProgram(Handle); 108 | GL.Uniform1(_uniformLocations[name], flag ? 1 : 0 ); 109 | } 110 | 111 | public void SetFloat(string name, float data) 112 | { 113 | GL.UseProgram(Handle); 114 | GL.Uniform1(_uniformLocations[name], data); 115 | } 116 | 117 | public void SetMatrix4(string name, Matrix4 data) 118 | { 119 | GL.UseProgram(Handle); 120 | GL.UniformMatrix4(_uniformLocations[name], true, ref data); 121 | } 122 | 123 | public void SetMatrix3(string name, Matrix3 data) 124 | { 125 | GL.UseProgram(Handle); 126 | GL.UniformMatrix3(_uniformLocations[name], true, ref data); 127 | } 128 | 129 | public void SetVector3(string name, Vector3 data) 130 | { 131 | GL.UseProgram(Handle); 132 | GL.Uniform3(_uniformLocations[name], data); 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /Gepe3D/src/SkyBox.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace Gepe3D 4 | { 5 | public class SkyBox 6 | { 7 | 8 | private static readonly float SIDE_LENGTH = 200; 9 | 10 | private readonly float[] vertices = new float[] 11 | { 12 | -SIDE_LENGTH / 2, -SIDE_LENGTH / 2, -SIDE_LENGTH / 2, 13 | -SIDE_LENGTH / 2, -SIDE_LENGTH / 2, SIDE_LENGTH / 2, 14 | SIDE_LENGTH / 2, -SIDE_LENGTH / 2, SIDE_LENGTH / 2, 15 | SIDE_LENGTH / 2, -SIDE_LENGTH / 2, -SIDE_LENGTH / 2, 16 | -SIDE_LENGTH / 2, SIDE_LENGTH / 2, -SIDE_LENGTH / 2, 17 | -SIDE_LENGTH / 2, SIDE_LENGTH / 2, SIDE_LENGTH / 2, 18 | SIDE_LENGTH / 2, SIDE_LENGTH / 2, SIDE_LENGTH / 2, 19 | SIDE_LENGTH / 2, SIDE_LENGTH / 2, -SIDE_LENGTH / 2 20 | }; 21 | 22 | // counter clockwise specification, faces facing inward 23 | private readonly uint[] indices = new uint[] 24 | { 25 | 0, 1, 2, 0, 2, 3, 0, 5, 1, 0, 4, 5, 26 | 1, 6, 2, 1, 5, 6, 2, 7, 3, 2, 6, 7, 27 | 3, 4, 0, 3, 7, 4, 4, 6, 5, 4, 7, 6 28 | }; 29 | 30 | private int _vboID, _vaoID; 31 | 32 | 33 | private readonly Shader skyboxShader; 34 | 35 | public SkyBox() 36 | { 37 | _vaoID = GLUtils.GenVAO(); 38 | _vboID = GLUtils.GenVBO(vertices); 39 | GLUtils.VaoFloatAttrib(_vaoID, _vboID, 0, 3, 3, 0); 40 | GLUtils.AttachEBO(_vaoID, indices); 41 | 42 | skyboxShader = new Shader("res/Shaders/skybox.vert", "res/Shaders/skybox.frag"); 43 | } 44 | 45 | 46 | public void Render(MainWindow world) 47 | { 48 | skyboxShader.Use(); 49 | skyboxShader.SetVector3("cameraPos", world.character.activeCam.Position); 50 | skyboxShader.SetMatrix4("cameraMatrix", world.character.activeCam.GetMatrix()); 51 | 52 | GLUtils.DrawIndexedVAO(_vaoID, indices.Length); 53 | } 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /Gepe3D/src/Spike.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using OpenTK.Mathematics; 4 | using System.Collections.Generic; 5 | 6 | namespace Gepe3D 7 | { 8 | public class Spike 9 | { 10 | 11 | static Random random = new Random(); 12 | int centreParticleID; 13 | Vector3 centrePos; 14 | int[] particleIDs; 15 | float radius; 16 | 17 | ParticleSystem particleSystem; 18 | 19 | public Spike(ParticleSystem particleSystem, float x, float z, float height, float radius, int startID) 20 | { 21 | this.particleSystem = particleSystem; 22 | this.radius = radius; 23 | 24 | float gap = 0.15f; 25 | int xzRes = (int) (radius * 2 / gap); 26 | int yRes = (int) (height / gap); 27 | 28 | List particlesList = new List(); 29 | int centreParticleTemp = 0; 30 | float closestDist = float.MaxValue; 31 | 32 | int currentID = startID; 33 | for (int py = 0; py < yRes; py++) 34 | { 35 | for (int px = 0; px < xzRes; px++) 36 | { 37 | for (int pz = 0; pz < xzRes; pz++) 38 | { 39 | float offsetY = MathHelper.Lerp( 0, height, py / (yRes - 1f) ); 40 | float offsetX = MathHelper.Lerp( -radius, +radius, px / (xzRes - 1f) ); 41 | float offsetZ = MathHelper.Lerp( -radius, +radius, pz / (xzRes - 1f) ); 42 | float horDist = MathF.Sqrt(offsetX * offsetX + offsetZ * offsetZ); 43 | float dist = MathF.Sqrt(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ); 44 | 45 | if (horDist <= MathHelper.Lerp(radius, 0, offsetY / height)) { 46 | 47 | particleSystem.SetPhase(currentID, ParticleSystem.PHASE_STATIC); 48 | particleSystem.SetColour(currentID, 0.4f, 0.4f, 0.4f); 49 | 50 | particleSystem.SetPos( 51 | currentID, 52 | x + offsetX, 53 | offsetY, 54 | z + offsetZ 55 | ); 56 | 57 | if (dist < closestDist) { 58 | closestDist = dist; 59 | centreParticleTemp = currentID; 60 | } 61 | particlesList.Add(currentID); 62 | 63 | currentID++; 64 | } 65 | } 66 | } 67 | } 68 | this.centreParticleID = centreParticleTemp; 69 | this.particleIDs = particlesList.ToArray(); 70 | } 71 | 72 | public void Update() 73 | { 74 | centrePos = particleSystem.GetPos(centreParticleID); 75 | 76 | if (centrePos.X > ParticleSystem.MAX_X + radius) 77 | { 78 | float targetX = 0 - radius + 0.01f; 79 | MoveTo(targetX); 80 | } 81 | } 82 | 83 | 84 | private void MoveTo(float targetX) 85 | { 86 | float randZ = RandZ(radius); 87 | 88 | Vector3 targetPos = new Vector3(targetX, 0, randZ); 89 | Vector3 diff = targetPos - centrePos; 90 | 91 | foreach (int pID in particleIDs) 92 | { 93 | particleSystem.AddPos(pID, diff.X, diff.Y, diff.Z); 94 | } 95 | 96 | } 97 | 98 | public static float RandZ(float radius) 99 | { 100 | float randFac = (float) random.NextDouble(); 101 | randFac = randFac * randFac * (3 - 2 * randFac); 102 | return MathHelper.Lerp( 0 + radius, ParticleSystem.MAX_Z - radius, randFac ); 103 | } 104 | 105 | } 106 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gonkee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gepe3D 2 | Gonkee's Epic Physics Engine 3D (Gepe3D) 3 | 4 | Computational fluid dynamics + rigid bodies + soft bodies, made in C# + OpenGL + OpenCL 5 | 6 | ![fluid sim](https://github.com/Gonkee/Gepe3D/assets/41216697/e4c93a39-be19-41bf-9f24-3dfac0cfa6e3) 7 | 8 | [Click here to download ZIP file containing EXE](https://github.com/Gonkee/Gepe3D/releases/download/breh/Gepe3D_win-x64.zip) 9 | 10 | (or click the releases section on the right) 11 | 12 | ### Code snippet (update function for player controller in C#) 13 | ```c# 14 | public void Update(float delta, KeyboardState keyboardState) { 15 | Vector3 camOffset = new Vector3(7, 0, 0); 16 | camOffset = Vector3.TransformColumn( Matrix3.CreateRotationZ( MathHelper.DegreesToRadians(pitch) ), camOffset ); 17 | camOffset = Vector3.TransformColumn( Matrix3.CreateRotationY( MathHelper.DegreesToRadians(yaw) ), camOffset ); 18 | 19 | centrePos = particleSystem.GetPos(centreParticleID); 20 | activeCam.SetPos(centrePos + camOffset); 21 | activeCam.LookAt(centrePos); 22 | activeCam.UpdateLocalVectors(); 23 | 24 | Vector3 movement = new Vector3(); 25 | if ( keyboardState.IsKeyDown(Keys.W) ) { movement.X += 1; } 26 | if ( keyboardState.IsKeyDown(Keys.S) ) { movement.X -= 1; } 27 | if ( keyboardState.IsKeyDown(Keys.A) ) { movement.Z -= 1; } 28 | if ( keyboardState.IsKeyDown(Keys.D) ) { movement.Z += 1; } 29 | if ( keyboardState.IsKeyDown(Keys.Space) ) { movement.Y += 1; } 30 | if ( keyboardState.IsKeyDown(Keys.LeftShift) ) { movement.Y -= 1; } 31 | if (movement.Length != 0) movement.Normalize(); 32 | 33 | Matrix4 rotation = Matrix4.CreateRotationY( MathHelper.DegreesToRadians(-yaw) ); 34 | rotation.Transpose(); // OpenTK matrices are transposed by default for some reason 35 | 36 | Vector4 rotatedMovement = rotation * new Vector4(movement, 1); 37 | movement.X = -rotatedMovement.X; 38 | movement.Y = rotatedMovement.Y; 39 | movement.Z = -rotatedMovement.Z; 40 | 41 | movement *= 0.1f; 42 | 43 | foreach (int pID in particleIDs) { 44 | particleSystem.AddVel(pID, movement.X, movement.Y, movement.Z); 45 | } 46 | } 47 | ``` 48 | 49 | ### Code snippet (calculation functions in OpenCL) 50 | ```opencl 51 | float w_poly6(float dist, float h) { 52 | dist = clamp(dist, (float) 0, (float) h); 53 | float tmp = h * h - dist * dist; 54 | return ( 315.0f / (64.0f * PI * pow(h, 9)) ) * tmp * tmp * tmp; 55 | } 56 | 57 | float w_spikygrad(float dist, float h) { 58 | dist = clamp(dist, (float) 0, (float) h); 59 | if (dist < FLT_EPSILON) return 0; // too close = same particle = can't use this scalar kernel 60 | return ( -45 / (PI * pow(h, 6)) ) * (h - dist) * (h - dist); 61 | } 62 | 63 | kernel void calculate_lambdas( 64 | global float *eposBuffer, 65 | global float *imasses, 66 | global float *lambdas, 67 | global int *cellIDsOfParticles, 68 | global int *cellStartAndEndIDs, 69 | global int *sortedParticleIDs, 70 | global int *phase 71 | ) { 72 | int i = get_global_id(0); 73 | 74 | if (phase[i] != PHASE_LIQUID) { 75 | lambdas[i] = 0; 76 | return; 77 | } 78 | 79 | float3 epos1 = getVec(eposBuffer, i); 80 | float density = 0; 81 | float gradN = 0; // gradient sum when other particle is neighbour 82 | float3 gradS = 0; // gradient sum when other particle is self 83 | 84 | FOREACH_NEIGHBOUR_j 85 | 86 | float3 epos2 = getVec(eposBuffer, j); 87 | float3 diff = epos1 - epos2; 88 | float dist = length(diff); 89 | if (dist > KERNEL_SIZE) continue; 90 | 91 | // the added bit should be multiplied by an extra scalar if its a solid 92 | if (imasses[j] > 0) density += (1.0 / imasses[j]) * w_poly6(dist, KERNEL_SIZE); 93 | 94 | if (i != j) { 95 | 96 | float kgrad = w_spikygrad(dist, KERNEL_SIZE); 97 | float tmp = kgrad / REST_DENSITY; 98 | // the added bit should be multiplied by an extra scalar if its a solid 99 | gradN += tmp * tmp; 100 | // the added bit should be multiplied by an extra scalar if its a solid 101 | gradS += normalize(diff) * kgrad; 102 | } 103 | 104 | END_FOREACH_NEIGHBOUR_j 105 | 106 | gradS /= REST_DENSITY; 107 | float denominator = gradN + dot(gradS, gradS); 108 | 109 | lambdas[i] = -(density / REST_DENSITY - 1.0) / (denominator + RELAXATION); 110 | } 111 | ``` 112 | 113 | Keybinds: 114 | - W: move forward 115 | - S: move backward 116 | - A: move left 117 | - D: move right 118 | - Space: move up 119 | - Left Shift: move down 120 | - Escape: close program 121 | 122 | References: 123 | 124 | - [Matthias Müller et al - "Position Based Dynamics"](https://matthias-research.github.io/pages/publications/posBasedDyn.pdf) 125 | - [Miles Macklin and Matthias Müller - "Position based fluids"](http://mmacklin.com/pbf_sig_preprint.pdf) 126 | - [Miles Macklin et al - "Unified Particle Physics for Real-Time Applications"](https://mmacklin.com/uppfrta_preprint.pdf) 127 | - [Simon Green - "Particle Simulation using CUDA"](http://developer.download.nvidia.com/assets/cuda/files/particles.pdf) 128 | --------------------------------------------------------------------------------