-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFluid.cpp
More file actions
406 lines (348 loc) · 11.6 KB
/
Copy pathFluid.cpp
File metadata and controls
406 lines (348 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/*****************************************************************************/
/*
CS391 Project 3 Profiler Scenario
This is adapted from Cody Pritchard's PHY350/399 Fluid Simulation Work
This sample provides a nice, juicy, CPU intensive application to test
your profiler on for project 3.
*/
/*****************************************************************************/
#include "Fluid.h"
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <xmmintrin.h>
using namespace DirectX;
inline XMVECTOR XMLoadFloat2Alternate
(
const XMFLOAT2* pSource
)
{
return _mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<const __m64 *>(pSource));
}
inline void XMStoreFloat2Alternate
(
XMFLOAT2* pDestination,
FXMVECTOR V
)
{
return _mm_storel_pi(reinterpret_cast<__m64 *>(pDestination), V);
}
inline float XMSqrtApprox
(
float X
)
{
return _mm_cvtss_f32(_mm_rcp_ss(_mm_rsqrt_ss(_mm_set_ss(X))));
}
using namespace std;
// Zero the fluid simulation member variables for sanity
Fluid::Fluid() {
step = 0;
paused = false;
pause_step = 0xFFFFFFFF;
width = 0;
height = 0;
grid_w = 0;
grid_h = 0;
num_particles = 0;
pos = NULL;
vel = NULL;
acc = NULL;
density = NULL;
pressure = NULL;
gridindices = NULL;
gridoffsets = NULL;
num_neighbors = 0;
// If this value is too small, ExpandNeighbors will fix it
neighbors_capacity = 256 * 1024;
neighbors = new FluidNeighborRecord[ neighbors_capacity ];
// Precompute kernel coefficients
// See "Particle-Based Fluid Simulation for Interactive Applications"
// "Poly 6" Kernel - Used for Density
poly6_coef = 315.0f / (64.0f * XM_PI * pow(FluidSmoothLen, 9));
// Gradient of the "Spikey" Kernel - Used for Pressure
grad_spiky_coef = -45.0f / (XM_PI * pow(FluidSmoothLen, 6));
// Laplacian of the "Viscosity" Kernel - Used for Viscosity
lap_vis_coef = 45.0f / (XM_PI * pow(FluidSmoothLen, 6));
}
// Destructor
Fluid::~Fluid() {
Clear();
delete[] gridoffsets; gridoffsets = NULL;
num_neighbors = 0;
neighbors_capacity = 0;
delete[] neighbors; neighbors = neighbors;
}
// Create the fluid simulation
// width/height is the simulation world maximum size
void Fluid::Create(float w, float h) {
width = w;
height = h;
grid_w = (int)(width / FluidSmoothLen);
grid_h = (int)(height / FluidSmoothLen);
delete[] gridoffsets;
gridoffsets = new FluidGridOffset[ (unsigned int)(grid_w * grid_h) ];
}
// Fill a region in the lower left with evenly spaced particles
void Fluid::Fill(float size) {
Clear();
unsigned int w = (unsigned int)(size / FluidInitialSpacing);
// Allocate
pos = new XMFLOAT2[ w * w ];
vel = new XMFLOAT2[ w * w ];
acc = new XMFLOAT2[ w * w ];
density = new float[ w * w ];
pressure = new float[ w * w ];
gridindices = new unsigned int[ w * w ];
// Populate
for (unsigned int y = 0 ; y < w ; y++) {
for (unsigned int x = 0 ; x < w ; x++) {
pos[ y*w+x ] = XMFLOAT2(x * FluidInitialSpacing, Height() - y * FluidInitialSpacing);
vel[ y*w+x ] = XMFLOAT2(0, 0);
acc[ y*w+x ] = XMFLOAT2(0, 0);
density[ y*w+x ] = 0;
pressure[ y*w+x ] = 0;
gridindices[ y*w+x ] = 0;
}
}
num_particles = w * w;
}
// Remove all particles
void Fluid::Clear() {
step = 0;
num_particles = 0;
delete[] pos; pos = NULL;
delete[] vel; vel = NULL;
delete[] acc; acc = NULL;
delete[] density; density = NULL;
delete[] pressure; pressure = NULL;
delete[] gridindices; gridindices = NULL;
}
// Expand the Neighbors list if necessary
// This function is rarely called
__declspec(noinline) void Fluid::ExpandNeighbors() {
// Double the size of the neighbors array because it is full
neighbors_capacity *= 2;
FluidNeighborRecord* new_neighbors = new FluidNeighborRecord[ neighbors_capacity ];
memcpy( new_neighbors, neighbors, sizeof(FluidNeighborRecord) * num_neighbors );
delete[] neighbors;
neighbors = new_neighbors;
}
// Simulation Update
// Build the grid of neighbors
// Imagine an evenly space grid. All of our neighbors will be
// in our cell and the 8 adjacent cells
void Fluid::UpdateGrid() {
// Cell size is the smoothing length
float invh = 1.0f / FluidSmoothLen;
// Clear the offsets
for (int O = 0 ; O < (grid_w * grid_h) ; O++) {
gridoffsets[O].count = 0;
}
// Count the number of particles in each cell
for (unsigned int P = 0 ; P < num_particles ; P++) {
// Find where this particle is in the grid
int p_gx = min(max((int)(pos[P].x * invh), 0), grid_w - 1);
int p_gy = min(max((int)(pos[P].y * invh), 0), grid_h - 1);
int cell = p_gy * grid_w + p_gx ;
gridoffsets[ cell ].count++;
}
// Prefix sum all of the cells
unsigned int sum = 0;
for (int O = 0 ; O < (grid_w * grid_h) ; O++) {
gridoffsets[O].offset = sum;
sum += gridoffsets[O].count;
gridoffsets[O].count = 0;
}
// Insert the particles into the grid
for (unsigned int P = 0 ; P < num_particles ; P++) {
// Find where this particle is in the grid
int p_gx = min(max((int)(pos[P].x * invh), 0), grid_w - 1);
int p_gy = min(max((int)(pos[P].y * invh), 0), grid_h - 1);
int cell = p_gy * grid_w + p_gx ;
gridindices[ gridoffsets[ cell ].offset + gridoffsets[ cell ].count ] = P;
gridoffsets[ cell ].count++;
}
}
// Simulation Update
// Build a list of neighbors (particles from adjacent grid locations) for every particle
void Fluid::GetNeighbors() {
// Search radius is the smoothing length
float h2 = FluidSmoothLen*FluidSmoothLen;
float invh = 1.0f / FluidSmoothLen;
num_neighbors = 0;
for (unsigned int P = 0 ; P < num_particles ; P++) {
// Find where this particle is in the grid
int p_gx = min(max((int)(pos[P].x * invh), 0), grid_w - 1);
int p_gy = min(max((int)(pos[P].y * invh), 0), grid_h - 1);
int cell = p_gy * grid_w + p_gx ;
XMFLOAT2 pos_P = pos[P];
// For every adjacent grid cell (9 cells total for 2D)
for (int d_gy = ((p_gy<1)?0:-1); d_gy <= ((p_gy<grid_h-1)?1:0); d_gy++) {
for (int d_gx = ((p_gx<1)?0:-1); d_gx <= ((p_gx<grid_w-1)?1:0); d_gx++) {
// Neighboring cell
int n_cell = cell + d_gy * grid_w + d_gx;
// Loop over ever particle in the neighboring cell
unsigned int* E = gridindices + gridoffsets[n_cell].offset;
unsigned int* END = E + gridoffsets[n_cell].count;
for ( ; E != END ; ++E) {
unsigned int N = *E;
// Only record particle "pairs" once
if (P > N) {
// Distance squared
XMFLOAT2 d = XMFLOAT2(pos_P.x - pos[N].x, pos_P.y - pos[N].y);
// Although this looks like a good candidate for XMVector2Dot
// In practice it usually isn't. This is such a small block
// of math followed by a comparison that it isn't well suited
// to SSE. Perhaps the SSE4.1 PTEST instruction would be of use
float distsq = d.x * d.x + d.y * d.y;
// Check that the particle is within the smoothing length
if (distsq < h2) {
if (num_neighbors >= neighbors_capacity) {
ExpandNeighbors();
}
// Record the ID of the two particles
// And record the squared distance
FluidNeighborRecord& record = neighbors[ num_neighbors ];
record.p = P;
record.n = N;
record.distsq = distsq;
num_neighbors++;
}
}
}
}
}
}
}
// Simulation Update
// Compute the density for each particle based on its neighbors within the smoothing length
void Fluid::ComputeDensity() {
// Smoothing length squared
float h2 = FluidSmoothLen*FluidSmoothLen;
for (unsigned int P = 0 ; P < num_particles ; P++) {
// This is r = 0
density[P] = h2 * h2 * h2 * FluidWaterMass;
}
// foreach neighboring pair of particles
for (unsigned int i = 0; i < num_neighbors ; i++) {
unsigned int P = neighbors[i].p;
unsigned int N = neighbors[i].n;
// distance squared
float r2 = neighbors[i].distsq;
// Density is based on proximity and mass
// Density is:
// M_n * W(h, r)
// Where the smoothing kernel is:
// The the "Poly6" kernel
float h2_r2 = h2 - r2;
float dens = h2_r2*h2_r2*h2_r2;
float P_mass = FluidWaterMass;
float N_mass = FluidWaterMass;
density[P] += N_mass * dens;
density[N] += P_mass * dens;
}
// Approximate pressure as an ideal compressible gas
// based on a spring eqation relating the rest density
for (unsigned int P = 0 ; P < num_particles ; ++P) {
density[P] *= poly6_coef;
float density_ratio = density[P] / FluidRestDensity;
pressure[P] = FluidStiff * max((density_ratio*density_ratio*density_ratio) - 1, 0.0f);
}
}
// Simulation Update
// Perform a batch of sqrts to turn distance squared into distance
// Why is this noinline? So that it shows up as a function to sampling profilers
__declspec(noinline) void Fluid::SqrtDist() {
for (unsigned int i = 0; i < num_neighbors ; i++) {
neighbors[i].distsq = XMSqrtApprox(neighbors[i].distsq);
}
}
// Simulation Update
// Compute the forces based on the Navier-Stokes equations for laminer fluid flow
// Follows is lots more voodoo
void Fluid::ComputeForce() {
float h = FluidSmoothLen;
// foreach neighboring pair of particles
for (unsigned int i = 0; i < num_neighbors ; i++) {
unsigned int P = neighbors[i].p;
unsigned int N = neighbors[i].n;
// distance
float r = neighbors[i].distsq;
// Compute force due to pressure and viscosity
float h_r = h - r;
XMVECTOR pos_P = XMLoadFloat2Alternate(&pos[P]);
XMVECTOR pos_N = XMLoadFloat2Alternate(&pos[N]);
XMVECTOR diff = pos_N - pos_P;
// Forces is dependant upon the average pressure and the inverse distance
// Force due to pressure is:
// 1/rho_p * 1/rho_n * Pavg * W(h, r)
// Where the smoothing kernel is:
// The gradient of the "Spikey" kernel
XMVECTOR force = (0.5f * (pressure[P] + pressure[N])* grad_spiky_coef * h_r / r) * diff;
// Viscosity is based on relative velocity
// Viscosity is:
// 1/rho_p * 1/rho_n * Vrel * mu * W(h, r)
// Where the smoothing kernel is:
// The laplacian of the "Viscosity" kernel
XMVECTOR vel_P = XMLoadFloat2Alternate(&vel[P]);
XMVECTOR vel_N = XMLoadFloat2Alternate(&vel[N]);
force += ((FluidViscosity * lap_vis_coef) * (vel_N - vel_P));
// Throw in the common (h-r) * 1/rho_p * 1/rho_n
force *= h_r * 1.0f / (density[P] * density[N]);
// Apply force - equal and opposite to both particles
XMVECTOR acc_P = XMLoadFloat2Alternate(&acc[P]);
XMVECTOR acc_N = XMLoadFloat2Alternate(&acc[N]);
acc_P += FluidWaterMass * force;
acc_N -= FluidWaterMass * force;
XMStoreFloat2Alternate(&acc[P], acc_P);
XMStoreFloat2Alternate(&acc[N], acc_N);
}
}
// Simulation Update
// Integration
void Fluid::Integrate(float dt) {
// Walls
const XMVECTOR planes[4] = {
{1, 0, 0, 0},
{0, 1, 0, 0},
{-1, 0, 0, width},
{0, -1, 0, height}
};
const XMVECTOR gravity = XMVectorSet(0, 1, 0, 0);
for (unsigned int P = 0 ; P < num_particles ; ++P) {
XMVECTOR pos_P = XMLoadFloat2Alternate(&pos[P]);
XMVECTOR vel_P = XMLoadFloat2Alternate(&vel[P]);
XMVECTOR acc_P = XMLoadFloat2Alternate(&acc[P]);
// Walls
for (unsigned int i = 0 ; i < _countof(planes) ; i++) {
//float dist = pos_P.x * planes[i].x + pos_P.y * planes[i].y + planes[i].z;
//acc_P += min(dist, 0.0f) * -FluidStaticStiff * D3DXVECTOR2(planes[i].x, planes[i].y);
XMVECTOR dist = XMPlaneDotCoord(planes[i], pos_P);
acc_P += -FluidStaticStiff * XMVectorMin(dist, XMVectorZero()) * planes[i];
}
// Acceleration
acc_P += gravity;
// Integration - Euler-Cromer
vel_P += dt * acc_P;
pos_P += dt * vel_P;
XMStoreFloat2Alternate(&pos[P], pos_P);
XMStoreFloat2Alternate(&vel[P], vel_P);
XMStoreFloat2Alternate(&acc[P], XMVectorZero());
}
}
// Simulation Update
void Fluid::Update(float dt) {
// Pause runs the simulation standing still for profiling
if (paused || step == pause_step) { dt = 0.0f; }
else { step++; }
// Create neighbor information
UpdateGrid();
GetNeighbors();
// Calculate the forces for all of the particles
ComputeDensity();
SqrtDist();
ComputeForce();
// And integrate
Integrate(dt);
}