├── .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 | 
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 |
--------------------------------------------------------------------------------