Softbody physics without using the built-in physics engine. It works on any mesh and is fairly robust (length and volume constraints). Here it is working on some random meshes from the toolbox:
How it works:
The softbodies are simulated using particles and constraints (which preserve lengths and volumes with some lenience). A previous softbody simulation I made only preserved lengths which made it less robust (no volume constraints meant it could collapse into a flat object and wouldn’t be able to recover). It also didn’t support arbitrary meshes.
Particle trajectory is calculated using verlet integration with
local velocity = self.position - self.previousPosition
self.previousPosition = self.position
self.position += velocity * self.damping + gravity * dt * dt / 2
-- damping value depends on if the point is colliding with something (friction) or if its not (air resistance)
and collision is resolved with
local normal = raycast.Normal
local correction = -velocity:Dot(normal) * normal * (1 + restitution)
velocity += correction
which gets rid of the projected component of velocity onto the normal of whatever its colliding with which makes its velocity tangent to the surface (not going into it in any way). Restitution (0 to 1) is just how bouncy the collision is.
Constraints generally take the form
constraint(point1, point2, ...) = quantity_to_be_preserved(point1, point2...) - initialQuantity
and solving them involves finding their gradients (direction to move the points in) and how much to move them based on the discrepancy / value of the constraint, the stiffness of the softbody, and the weight of each point.
For length constraints the form is
constraint(a, b) = (a - b).Magnitude - initialLength
gradient_a = (a - b).Unit
gradient_b = (b - a).Unit
or in code:
function Edge:solve(inv_stiffness, dt)
if self.a.inv_weight + self.b.inv_weight == 0 then return end
local displacement = self.a.position - self.b.position
local discrepancy = self.length - displacement.Magnitude -- the constraint
local lambda = discrepancy / (self.a.inv_weight + self.b.inv_weight + inv_stiffness / (dt ^ 2))
local gradient = displacement.Unit
self.a.position += gradient * lambda * self.a.inv_weight
self.b.position -= gradient * lambda * self.b.inv_weight
end
and for volume constraints the form is
constraint(a, b, c, d) = ((b - a):Cross(c - a)):Dot(d - a) - initialVolume
gradient_a = (d - b):Cross(c - b)
gradient_b = (c - a):Cross(d - a)
gradient_c = (d - a):Cross(b - a)
gradient_d = (b - a):Cross(c - a)
or in code
local function volume(a, b, c, d)
return ((b - a):Cross(c - a)):Dot(d - a)
end
function Tetrahedron:solve(inv_stiffness, dt)
if self.a.inv_weight + self.b.inv_weight + self.c.inv_weight + self.d.inv_weight == 0 then return end
local a, b, c, d = self.a.position, self.b.position, self.c.position, self.d.position
local a_w, b_w, c_w, d_w = self.a.inv_weight, self.b.inv_weight, self.c.inv_weight, self.d.inv_weight
local discrepancy = self.volume - volume(a, b, c, d) -- the constraint
local a_grad = (d - b):Cross(c - b)
local b_grad = (c - a):Cross(d - a)
local c_grad = (d - a):Cross(b - a)
local d_grad = (b - a):Cross(c - a)
local lambda = discrepancy / (a_w * a_grad.Magnitude^2 + b_w * b_grad.Magnitude^2 + c_w * c_grad.Magnitude^2 + d_w * d_grad.Magnitude^2 + inv_stiffness / (dt^2))
self.a.position += lambda * a_w * a_grad
self.b.position += lambda * b_w * b_grad
self.c.position += lambda * c_w * c_grad
self.d.position += lambda * d_w * d_grad
end
The overall method is called XPBD softbody simulation, with the original paper and simplified explanation (same author) available here:
- https://matthias-research.github.io/pages/publications/XPBD.pdf
- and https://matthias-research.github.io/pages/tenMinutePhysics/10-softBodies.pdf
The main areas of potential improvements are:
- Mesh tetrahedralization, since right now the tetrahedrons are generated by just connecting all the triangles in the mesh to the center, which means the volumes they preserve aren’t necessarily the volumes of the mesh
- Collision, since right now collision is handled by the vertices which means the softbody won’t detect collision with small objects that fit in the gaps between points