-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMPMGrid.cpp
More file actions
316 lines (287 loc) · 8.78 KB
/
MPMGrid.cpp
File metadata and controls
316 lines (287 loc) · 8.78 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
#include "MPMGrid.h"
#include "Constants.h"
#include "Particle.h"
static GLfloat NX(GLfloat x)
{
x = fabs(x);
if (x < 1.0f)
return x * x * (x * 0.5f - 1.0f) + 2.0f / 3.0f;
else if (x < 2.0f)
return x * (x * (-x / 6.0f + 1.0f) - 2.0f) + 4.0f / 3.0f;
else
return 0.0f;
}
static GLfloat dNX(const GLfloat x)
{
GLfloat absx = fabs(x);
if (absx < 1.0f)
return x * (1.5f * absx - 2.0f);
else if (absx < 2.0f)
return x * (-0.5f * absx + 2.0f - 2.0f / absx);
else
return 0.0f;
}
static GLfloat weight(const glm::vec2 pos) { return NX(pos.x) * NX(pos.y); }
glm::vec2 MPMGrid::gradWeight(const glm::vec2 pos) { return glm::vec2(dNX(pos.x) * NX(pos.y), dNX(pos.y) * NX(pos.x)) * invCellSize; }
MPMGrid::~MPMGrid()
{
if (nodes != nullptr)
delete[] nodes;
}
void MPMGrid::initGrid(glm::vec2 origin, glm::vec2 size, int width, int height)
{
MPMGrid::size = size;
gridWidth = width;
gridHeight = height;
nodeCount = gridWidth * gridHeight;
nodes = new GridNode[nodeCount];
cellSize = size / glm::vec2(gridWidth, gridHeight);
invCellSize = 1.0f / cellSize;
MPMGrid::origin = origin;
bounds[0] = origin.x;
bounds[1] = origin.x + size.x;
bounds[2] = origin.y;
bounds[3] = origin.y + size.y;
// Buffer the bounds by 1 cell
bounds[0] += cellSize.x;
bounds[1] -= cellSize.x;
bounds[2] += cellSize.y;
bounds[3] -= cellSize.y;
}
void MPMGrid::initParticles(Particle* particles, UINT count)
{
MPMGrid::particles = particles;
pointCount = count;
initMass();
// Calculate densities and volumes
GLfloat invNodeArea = 1.0f / (cellSize.x * cellSize.y);
for (UINT i = 0; i < pointCount; i++)
{
Particle& p = particles[i];
p.density = p.volume = 0.0f;
for (UINT y = p.startY; y < p.endY; y++)
{
for (UINT x = p.startX; x < p.endX; x++)
{
UINT index = y * gridWidth + x;
glm::vec2 diff = (p.getPos() - nodes[index].pos) * invCellSize;
p.density += weight(diff) * nodes[index].mass;
}
}
p.density *= invNodeArea;
p.volume = p.mass / p.density;
}
}
void MPMGrid::initMass()
{
// Clear the nodes (values are accumulated on particles)
for (int y = 0; y < gridHeight; y++)
{
for (int x = 0; x < gridWidth; x++)
{
// Physical coordinates of the node, spaced like |-x--x--x--x--x-|
nodes[y * gridWidth + x] = GridNode(glm::vec2(x, y) * cellSize + cellSize * 0.5f + origin);
}
}
// Weight the mass of the particles into the nodes
for (UINT i = 0; i < pointCount; i++)
{
Particle& p = particles[i];
glm::vec2 pPos = p.getPos();
int gridPosX = static_cast<int>((pPos.x - origin.x) * invCellSize.x);
int gridPosY = static_cast<int>((pPos.y - origin.y) * invCellSize.y);
p.startX = static_cast<UINT>(MathHelp::clamp(gridPosX - 2, 0, gridWidth));
p.endX = static_cast<UINT>(MathHelp::clamp(gridPosX + 3, 0, gridWidth));
p.startY = static_cast<UINT>(MathHelp::clamp(gridPosY - 2, 0, gridHeight));
p.endY = static_cast<UINT>(MathHelp::clamp(gridPosY + 3, 0, gridHeight));
p.inBounds = p.startX != p.endX || p.startY != p.endY;
for (UINT y = p.startY; y < p.endY; y++)
{
for (UINT x = p.startX; x < p.endX; x++)
{
UINT i = y * gridWidth + x;
// Diff between particle's and grid nodes physical position, converted back to grid coordinates
glm::vec2 diff = (pPos - nodes[i].pos) * invCellSize;
nodes[i].mass += p.mass * weight(diff);
}
}
}
}
// Puts particle momentums onto the grid (as velocity)
void MPMGrid::initVelocities()
{
// Sum all the particles momentum to their nearby nodes (weighted)
for (UINT i = 0; i < pointCount; i++)
{
Particle& p = particles[i];
glm::vec2 pPos = p.getPos();
for (UINT y = p.startY; y < p.endY; y++)
{
for (UINT x = p.startX; x < p.endX; x++)
{
UINT i = y * gridWidth + x;
// Diff between particle's and grid nodes physical position, converted back to grid coordinates
glm::vec2 diff = (pPos - nodes[i].pos) * invCellSize;
nodes[i].velocity += p.velocity * p.mass * weight(diff);
// As long as the mass isn't 0 and it has some velocity it is active
nodes[i].active = (nodes[i].mass != 0.0f);
}
}
}
// Calculate the actual velocity. Mark zero mass nodes as non-active
for (int i = 0; i < nodeCount; i++)
{
if (nodes[i].mass != 0.0f)
nodes[i].velocity /= nodes[i].mass;
}
}
// Updates velocities based on stress and gravity
void MPMGrid::updateGridVelocities(GLfloat dt)
{
for (UINT i = 0; i < pointCount; i++)
{
Particle& p = particles[i];
// Initial volume * cauchy. Energy derivative is force.
glm::mat2 energyDerivative = -p.volume * p.calcCauchyStress();
for (UINT y = p.startY; y < p.endY; y++)
{
for (UINT x = p.startX; x < p.endX; x++)
{
UINT i = y * gridWidth + x;
// Diff between particle's and grid nodes physical position, converted back to grid coordinates
glm::vec2 diff = (p.getPos() - nodes[i].pos) * invCellSize;
nodes[i].force += energyDerivative * gradWeight(diff);
}
}
}
glm::vec2 g = glm::vec2(0.0f, -9.8f);
for (int i = 0; i < nodeCount; i++)
{
// Only update the node velocity if it is active
if (nodes[i].mass != 0.0f)
{
//nodes[i].force += g * nodes[i].mass; // Use for correct forces stored post-operation
// Update node velocity given force
nodes[i].newVelocity = nodes[i].velocity + (nodes[i].force / nodes[i].mass + g) * dt;
}
}
}
void MPMGrid::updateParticleVelocities()
{
GLfloat invNodeArea = 1.0f / (cellSize.x * cellSize.y);
// We calculate both the pic and flip velocity and interpolate between the two
// We also calculate a velocity gradient to integrate the deformation gradient with
for (UINT i = 0; i < pointCount; i++)
{
Particle& p = particles[i];
p.vG = glm::mat2(0.0f);
p.density = 0.0f;
glm::vec2 picVelocity = glm::vec2(0.0f);
glm::vec2 flipVelocity = p.velocity;
for (UINT y = p.startY; y < p.endY; y++)
{
for (UINT x = p.startX; x < p.endX; x++)
{
UINT i = y * gridWidth + x;
GridNode node = nodes[i];
// Diff between particle's and grid nodes physical position, converted back to grid coordinates
glm::vec2 diff = (p.getPos() - node.pos) * invCellSize;
GLfloat w = weight(diff);
glm::vec2 wG = gradWeight(diff);
picVelocity += node.newVelocity * w;
flipVelocity += (node.newVelocity - node.velocity) * w;
p.vG += MathHelp::outer(node.newVelocity, wG);
p.density += node.mass * w;
}
}
// A mix between flip and pic velocity (node velocity already multiplied with dt)
p.velocity = flipVelocity * FLIP_PERCENT + picVelocity * (1.0f - FLIP_PERCENT);
// Not required but useful for visualization
p.density *= invNodeArea;
}
}
void MPMGrid::collision(glm::vec2 pos, glm::vec2& v, GLfloat dt)
{
// Collision
glm::vec2 normal = glm::vec2(0.0f);
bool collision = false;
glm::vec2 vt = glm::vec2(0.0f);
GLfloat vn = 0.0f;
for (UINT i = 0; i < 2; i++)
{
collision = false;
if (i == 0)
{
if (pos.x <= bounds[0])
{
collision = true;
normal = glm::vec2(1.0f, 0.0f);
}
else if (pos.x >= bounds[1])
{
collision = true;
normal = glm::vec2(-1.0f, 0.0f);
}
}
if (i == 1)
{
if (pos.y <= bounds[2])
{
collision = true;
normal = glm::vec2(0.0f, 1.0f);
}
else if (pos.y >= bounds[3])
{
collision = true;
normal = glm::vec2(0.0f, -1.0f);
}
}
if (collision)
{
// Get velocity along normal
vn = glm::dot(v, normal);
// If separating, do nothing
if (vn >= 0)
continue;
// Get tangent velocity by removing velocity in normal dir
vt = v - vn * normal;
// Until vt surpasses this value don't let it move (static friction)
if (glm::length(vt) <= -FRICTION * vn)
{
v = glm::vec3(0.0f);
return;
}
// Apply dynamic friction, add back tangent velocity with only a fraction of normal velocity
v = vt + FRICTION * vn * glm::normalize(vt);
}
}
}
void MPMGrid::projectToGrid()
{
initMass();
initVelocities();
}
void MPMGrid::update(GLfloat dt)
{
// Calculates node velocities from particles
updateGridVelocities(dt);
// Solve velocities on node level
for (int i = 0; i < nodeCount; i++)
{
// For small timesteps the velocities will be so small they will never be able to escape the cellSize
// In these cases it should only be handled on particle level
// I question what this is really doing for me
collision(nodes[i].pos + nodes[i].newVelocity * dt, nodes[i].newVelocity, dt);
}
// Calculates particle velocities from grid velocities (also calc velocity gradient)
updateParticleVelocities();
for (UINT i = 0; i < pointCount; i++)
{
// Solve velocities per particle (doesn't introduce anything to velocity gradient until next iteration)
collision(particles[i].getPos() + particles[i].velocity * dt, particles[i].velocity, dt);
// Update the particle position using the velocity
particles[i].updatePos(dt);
// Update the deformation gradient using the velocity gradient (which was calculated from the velocity)
particles[i].updateGradient(dt);
}
}