The first success I'd like to highlight is that all of the position update and constraint satisfaction code runs on the GPU. This makes simulating hundreds of ropes at once feasible. For each rope, the CPU runs a physics trace that finds every PhysicsShape within 32 units of that rope's AABB. The transform of that PhysicsShape along with whatever information necessary to define that shape are stored in a DTO, which gets sent to the GPU for collision resolution.
public struct GpuSphereCollisionInfo
{
public Vector3 Center;
public float Radius;
public Matrix LocalToWorld;
public Matrix WorldToLocal;
}
Other data that must be sent to the GPU include overrides to point data. For example, it may be necessary for the purposes of interactivity to make a point on the GPU follow the mouse, as seen in the pachinko test scene. To minimize blocking due to readbacks from the GPU, the CPU doesn't actually "know" the position of any of the points on the GPU. In the case of updating a point, the CPU is just saying "here's what the position of the rope should be," and the compute shader on the GPU acts accordingly.
One example of data that must frequently be read back to the CPU is the bounds of the rope/cloth simulation. This is because both frustum culling and the detection of nearby PhysicsShapes depend on the rope having accurate bounds.
When creating meshes for rope or cloth simulated on the GPU, the vertex and index buffers are never read back to the CPU. I'll talk more about this later when I discuss cloth.
Each rope point is treated as a sphere, having a position and a radius. To resolve a collision between a rope point and any other shape, the point is instantly moved to the outside of the shape. To do this, the only questions we need answered are:
Is the position of the rope inside the shape?
What is the direction from the rope's position to the surface of the shape?
How far must we push the rope point along that direction to ensure that the rope is positioned just barely outside of the shape?
This is easy to answer for most primitives. If there is no easy answer, you can fall back on using a signed distance function and its gradient to get the information you need.
But what if the shape is a complicated concave mesh for which you wouldn't be able to make a signed distance function?
"Simply" create a volume texture that represents the signed distance field of the mesh. Each texel represents the signed distance at the center of an object space voxel. The gradient may be precomputed and stored in the texel as well.
The hard parts are voxelizing the mesh, detecting whether a point is inside or outside the mesh, accounting for bad geometry such as gaps and flipped normals, and figuring out how to do it all without a lengthy offline process of raytracing.
I'll speak more about the implementation details in a separate news post, but the end result is that ropes are able to collide with meshes. It's not perfect, but I'm proud of having managed to get it working at all.
If you were making a game engine or tech demo in C++, there's a pretty good chance you would want to use the Dear ImGui library.
Making UI in s&box is great because you can use Razor and SCSS to make pretty much whatever you want. But this is excessively general if all you need are debug menus for a tech demo. In the weeks leading up to the start of the tech jam, I made a library that clones the API and style of Dear ImGui as best I could.
The resulting library is janky and lacking most features, but using the parts that do work was a tremendous time saver. Here's a link to that library, but I'd suggest not using it for now, until I update it with a few important fixes and features.
I'll write a news article when that's ready. Stay tuned!
Once I had a way to move and constrain a chain of particles that form a rope, making a grid of particles that behaved as cloth was a relatively simple task. Roughly speaking, all I had to do was:
Set the initial state of the simulation to be a grid of particles rather than a chain.
Use springs to constrain each particle to their sideways and diagonal neighbors (ropes are constrained merely lengthwise).
Treating each particle of the simulation as a vertex in a triangulated plane, use a compute shader to output an up-to-date arrangement of vertices to a vertex buffer every frame.
Not everything works exactly the way I'd like it to. I'm optimistic that any features I'm currently missing can be added in the future, but there are some bugs I'll have to fix.
Small timestep sizes cause particles to bounce and roil as they rest on the floor.
Certain types of mesh geometry may cause voids or interior leaks in the mesh distance field.
Rope vs. mesh collision resolution is jittery.
There is a crash to desktop bug that occurs for reasons I don't yet understand.
Various UI bugs still exist.
There are also performance and scalability issues with collision detection and especially mesh distance field generation. I need to spend some more time with these parts of the code.
I plan on shipping a library that includes the physics simulation code used by this demo. This will require lots of polish and new features. Here's a rough and somewhat wishful roadmap of what I plan to offer a few game dev disciplines.
For the sake of brevity, I'll use "sims" here to describe both rope simulations and cloth simulations.
Environment Artist Goals
Make sims inexpensive enough to place all around the map.
Allow sims to be simulated inside the editor.
Support wind and gravity.
Allow multiple ropes to be chained in to one continuous mesh.
Allow the geometry of sims to be baked and treated as a mere static mesh with no simulation.
For example, you may wish to leave various plastic tarps crumpled or draped about the level.
Character Artist Goals
Allow sims to be parented to specific bones, or drive specific bones.
Add constraints that allow sims to remain somewhat stiffly in place, like antennae or tails.
Allow cloth sims to have arbitrary shapes and anchor points.
Gameplay Programmer Goals
Sims contain keyframed points with motion determined by a supplied position rather than being changed by the simulation.
Find a way to fake two-way coupling between GPU sims and s&box's built-in CPU rigid body simulation.
Allow sims to be cut, either deactivating constraints or splitting the sims in to two smaller sims.
Allow the size of sims to grow, like a rope coiled around a spool that can unwind endlessly.
Bear in mind that the code is currently messy and unoptimized, and has never been tested outside of small demos. Also expect for many classes to be renamed, refactored, or removed.
I'll throw in a proper license once I feel that the code is able to benefit other people's projects.
Looking back at this project, I've done a lot that I'm proud of. There are also many things that I feel are missing, or could be greatly improved. Now that the Tech Jam is over, no deadline prevents me from setting priorities as I please.
Rather than pushing flashy features out the door for a demo, I now have the freedom to develop this project in a more considered way. To that end, I'll make editor tools, refactor the stuff that's bad, and do whatever I can to make sure that whoever ends up using my code feels joy rather than frustration.
The largest and most daunting task is that of strengthening my math skills so that I can reason about my code with the rigor and intuition expected of someone who dares to make a physics simulation.
In conclusion: I've been winging it a lot for the past month and a half - and now my wings are tired!
So, it'll be good to spend some time on the ground doing what I can to make my next flight even higher and more graceful than the last.
Comments