Physical Destruction

Monday’s post Fast Spaceship Physics achieved an efficient physics prototype utilising a spring energy tracking system. That energy component allows detection of problems in the simulated ships, long before the crazy elastic explosions that are sometimes seen when spring based simulations fail. These energy spikes strike me as a natural entry point for programming the destruction of spaceships. In this post I will talk through the structure of a destruction system, work through the difficulties of implementing it in the physics prototype, and attempt to resolve issues that arise from the flexible corners of the spring system.

Breakage

The initial destruction prototype applied across different thrust levels

The initial destruction prototype applied across different thrust levels

Starting from Monday’s simulation, the idea of breakage is that when connections are stretched too far, or too quickly, they should snap. Examining the energy propagation system from that first prototype, we see that energy spikes occur with rapid stretching. This means the energy values stored at the points can alert us to joints that are stretched quickly, whilst the lengths of those joints can be used to calculate the stretching factors. These properties are relatively succinct, and so by using them breakage situations can be identified without requiring the evaluation of complex conditions.

The question then is what circumstance should cause breakage from amongst these values. To design the answer to this question we can consider a single connection in our physics model. This might be thought of as a steel bar, welded between two halves of a spaceship. For this single connection, we might store some unchanging information such as the uncompressed length of the connection, and we can measure the energy build up at both end points, and the current actual length of the connection. The energy system was designed to pass movement from high to low energy joints, so we should expect energy to be equalising across the connection. If a break would be triggered by one end having high energy, this energy would not be being passed into the other half of the system, creating a very local damage model. I will therefore specify that breaks can only occur when the energy at both ends of a connection passes some threshold. Driving breaks on energy alone would be a highly homogeneous system, and would not differentiate between breaking long and short connections. This creates a problem, as we expect a long connection to create larger movements, and so amass larger energies. To counteract this, the compression of the connection, the ratio between its initial length and its measured length, is added as an additional breakage condition. For small connections this condition will be universal, whilst long connections will more often be protected.

Introducing breakage to the structure of the prototype code is straightforward. Once each frame, after the calculation pass for the physics system, the breakage conditions are checked for each edge. Where a break is triggered, a point is picked along the breaking connection. Two new points are added at this intermediate location, and two joins between the end points and these new points replace the original connection.

Rigid joints

The rigid destruction prototype applied across different thrust levels

The rigid destruction prototype applied across different thrust levels

The initial destruction prototype is not flawless, with two evident errors. The first is visual, whilst in my imagination the ship represents some sort of welded scaffold, when corners break off the remnants connected to them rotate freely. The second problem is with energy, particularly focused on these newly separated corner pieces. We can often see energy being increased and increased without input, causing increasingly erratic movement. Given the concurrence of these phenomena I suspect tackling one may have an impact on the other. Thus some method is sought to lock the rotation of free floating connections.

An immediate concern when addressing this rotation issue is efficiency. It is easy to come up with solutions that involve evaluating the entire structure of the ship a second time. The first pass, already shown, would update the physics, and a second pass would calculate all the rotation numbers and set things straight there. This second pass would structurally involve the code looking at all of the different parts of the simulation, where within a single pass we only look at one area at a time. In short, this second pass could nearly double the computation time required to simulate a ship. Obviously we are not yet encountering any issues for example with frame rate, so a more impactful way to view this cost is that we could be halving the size of the ships that would be possible to have in gameplay. Rather than commit to this, I would like to attempt a solution that does not require a full second pass of the data.

None of the joints or corners of the simulation are made with fixed angles; yet because the ship is constructed from a rigid framework the joints naturally behave with some rigidity. This behaviour only departs when points become connected to only one other place. We can see this as the cross and star pattern connections of snapped corners spiral and twirl, where the original structure of the ship seems solid. This means a solution could be applied only to those unsupported points, and potentially be effective. As the ship starts fully connected, we can store in memory that every one of the points is supported. When a connection breaks, two new unsupported points are being added to the simulation. We can append to their information a flag that tells us these are unsupported. Now in a single pass we know which points should be locked into rotation, and which ones should be governed just by the existing connection force.

Knowing which points need to be made rigid is one thing, but the required rotations also need to be calculated. At the end of each physics frame, as the physics simulation is using derivative information, a rough velocity number is stored for each point. This means at the end of a single pass, we store for every point both its location and its single frame movement value. These two values mean we actually know, simultaneously, where each point is, and through the difference between its position and velocity, where it was before the physics frame. Passing this information through the connections, every point can know the before and after positions of itself and each point it is connected to. This is the information we would need to deduce the rotation of the connected points about each point! Specifically, we can calculate the angle between the connected points before the frame, and then calculate the changed angle after the frame, each using the sector arcus tangent function. The change between these angles can then be averaged for all the points connected to a point, using an angle averaging function analogous to the argument of the total of the corresponding vectors. This average tells us how much the connected ship has been rotated around each point. Where a point is unsupported, which is information we have stored, we discard this angle, and replace it with the angle of the single point it is connected to. This means each unsupported point now has stored the amount it has rotated about its connection in the preceding physics frame.

This is a tricky juncture. What we could do here is add in all of the counter rotations to the free points, and update the physics again. Yet this would mean distributing the information between the points, which constitutes a full pass of the connection system, which would be costly. Yet when we compare the impact of the first pass, the full physics update, and this second pass, a rotational update to unsupported points, the later seems inconsequential. This is a powerful observation. The rotation pass is so slight, that we might get away with ignoring it. The rotation information is kept with the unsupported points, and the scene is drawn as before, even though the unsupported points are technically freewheeling. At the beginning of the next physics pass, each unsupported point is updated. As these unsupported points are known to have a minimal input on the physics system, the error between the location we expect they should be in, and their uncorrected stored locations, has to be fairly small. We know, for example, that no engines or collisions can be pushing through these points, as they are literally not connected to anything. What this means is that whilst small anomalies are being fed into the next physics frame, the rotation updates are being bundled into that frames system update, and so the rotation information is being updated without causing an extra update.

Asserting stability

As seen in the visualisation, the result of this rotation solution is pretty stable. Stability is difficult to assert in an iterative system such as this. Time is being moved forwards in little jumps. Imagine falling towards the ground, and time jumps forwards one second. If we miss a second of fall, we probably find things much as we expect them, still falling. What happens if we miss the ground? Are we now falling through solid earth? Perhaps we suddenly find ourselves close enough to the ground to interfere with physics. Maybe our adjusted density as we warp into solid rock creates a microscopic black hole that eats the planet. Suffice to say there are issues with ignoring periods of time. Similarly, we’re collecting forces one at a time and then adding them up, where we really should be simultaneously resolving those forces. If two forces each speed us up to going one meter a second, and we add those, we’re now going two meters a second. This is not too alarming. If two forces each speed us up to going half the speed of light, and we add those, we’re now travelling at the speed of light, and perceive our entire future trajectory in space time as one simultaneous instant, whilst our infinite kinetic energy eats the universe. These shortcuts, taken to make the physics system cheap, provide a cornucopia of potential pitfalls. Whilst I have covered the core logic of the system, various parameters, fudges, and limits are required to properly prove that the system cannot go wrong. This is a critical assertion. It is incredibly easy to identify the circumstance of failures, and then to simply make those circumstance less likely. This is not good enough. In designing a system like this for a game, the designer may be exposed to an hour of the system in a working day, likely in some prescribed format such as the test ship in my renders. Your players may run these systems for every second they play. They might strive to push these systems to their limits. If you can reduce observations of failure to one percent, then you might never observe those failures in your development. Yet that one percent could imply daily failures for players.

At this prototyping stage I will not go over the specifics of parameterising this simulation, nor the logic behind assertions on an iterative system. The logical structure of the simulation is defensible, and I think it should be emplaced into the intended format of the Rust engine before we get too involved tweaking and tuning it.

Next week I will work through a basic rendering implementation in Rust.

Previous
Previous

Rendering in Rust

Next
Next

Fast Spaceship Physics