Advanced Buoyancy, Movement and Physics Networking - DevBlog #9 Part I

Just before the end of an exciting year, we share the advanced buoyancy, movement, and networking that Sails of War has.

Posted by on

Ahoy, Pirates, and Coalition Sailors - Battle-Stations!

Tobias here - Programmer for Sails of War! It’s been a while - I know again!

Last year in may we shared an article about our new buoyancy system, that’s been scrapped - things can and will change! Lukas has been busy and has also moved again - but hey congratulations on the new job! So on the Art front, there isn’t much to discuss. However, there’s plenty on other fronts to talk about. In this blog, we have a lot to go over. A lot of my time has been spent maintaining, overhauling, and updating our systems in our game. But there are several new things to show you! In this article, I’ll be talking about our new buoyancy system (yes, a new one), and our physics networking.

### Buoyancy

Here’s a video of our current buoyancy and movement in action:

After further testing and adjustments, we concluded that the previous kinematic system for ship movement just didn’t suit all our needs. So I looked at other sources, too many sources. Down the line, I’d love to make this hull and buoyancy simulation into its project but for now, we focused on one source - Jacques Kerner's set of articles on Just Cause 3's implementation. This became my main inspiration for our system. It’s a great set of articles and I highly recommend reading them. You can find links to them in the Appendix at the bottom of this article.

Our buoyancy algorithm is split into five sections in chronological order they are - water queries, updating our mesh data, cutting the mesh’s triangles, splitting those triangles, and finally the force calculations on those triangles. Because of the change from kinematics to physics, we have utilized Unreal 4’s ability to substep each frame for its physics simulations. This is worth noting because the algorithm may be run more than once per frame if the frame rate is lower than our threshold. This means our costs per frame can double with our threshold of 30hz and a target simulation rate of 60hz. So any framerate below 60 will result in sub-stepping. If you’re interested in sub-stepping in Unreal 4 our implementation can be found in the appendix.

Intersection Algorithm:

Visualization of the intersection algorithm in use.

A delicate balance between efficiency and accuracy must be maintained to obtain optimal results for the intersection algorithm. This balance is described by the two inputs - the triangle size and the grid size. Failure to maintain a balance between the two can result in dramatically different outputs at a less than optimal cost. The algorithm itself is an approximation of an object with many triangles intersecting a moving and dynamic plane of fluid. The reason this algorithm is an approximation is that real-time usage would be impractical. Instead of running the wave queries on every single vertex of the object, we create a grid of points that we update each sub-step. Using this we approximate the wave height for a given vertex by projecting the vertex onto the cell of the grid that it is above. Several optimizations and a few large refactors of code were required to obtain this level of performance. As shown in the charts below.

The primary and largest contribution to performance is the number of iterations the algorithm has to do for each triangle. By refactoring this input in the various stages of the algorithm we can increase performance substantially. Below is a chart detailing the inputs for each stage of the algorithm, refactored and optimized on the left and unoptimized on the right.

Unoptimized algorithm benchmarks below:

The X-Axis is the time in the simulation step when the section is run (in ms).
Y-Axis is the how long (cost) that section takes to run.

Optimized Algorithm benchmarks below:

The X-Axis is the time in the simulation step when the section is run (in ms).
Y-Axis is the how long (cost) that section takes to run.

The above graphs are generated from data we collected on one of our hulls. What’s great about the graphs is that they are themselves an equation - an equation that we can use to very accurately estimate the cost to run any given hull without even implementing it in Unreal. This means it’s effortless and efficient for us to roughly benchmark any mesh we’re contemplating on using. On top of this, the equation allows us to figure out roughly what our optimal parameters should be to get the best results for our simulation. After a refactor of the code for optimization purposes the performance was increased by a magnitude of ten - from 5ms per frame to 0.5ms!

In the future, I’d like to challenge myself to lower this even further. This has the potential to allow us to locally simulate the physics of other player’s ships rather than utilizing solely an interpolation buffer system. There’s a bunch of tricks we can utilize down the road!

A primary concern is the number of objects being simulated. The more objects the more water queries being requested, and triangles that need to be intersected. As the number of objects increases the cost increases as well. The simulation is run on a single thread even though the separate sets of inputs and outputs for the simulation do not interact with each other. A performance optimization here would be to run these buoyancy calculations on parallel threads rather than one after another. This would decrease the cost to the most complex and costly object being simulated.

The grid update requires us to make water queries, this makes up roughly anywhere between 25% to 50% of the total cost. An optimization here would be to refactor the grids and put them into our Ocean Actor class. Storing them there rather than individually on the player’s movement component. This itself does not necessarily mean a performance increase, however, it does allow for a performance increase when objects are adjacent to each other and have overlapping grids.

Further optimization with water height queries is running asynchronous tasks post physics simulation on individual threads while waiting for the rendering thread to finish. Doing so would result in a zero cost case in the performance of obtaining water heights. This zero-cost is only obtainable if the cost of the water height queries is lower than the cost of the rendering thread to finish its calculations. If the time the rendering thread takes is less than or equal to the performance cost of the water queries the cost may still be reduced, however, this also could provide the opposite effect of forcing the game thread to wait for these tasks to finish before allowing the simulation to begin.

### Physics Networking

Before delving into this I’d recommend reading and or watching the related links in the Appendix. Glenn Fielder's GDC talk is fantastic.

We’ve partially fleshed out our physics networking system for Sails of War, our buffer system is up and running, however, the aspect of the player on player collision reconciliation from those collisions still needs to be implemented. I’ve followed Glenn Fielder’s articles and talks on the concept of a linear interpolation buffer system for physics. Currently, the owning client simulates their physics and notifies the server of its current physics state - client authoritative physics. The server then replicates that information to other clients. The server and those clients interpolate the physics state on their end. Here’s a diagram showing what is going on:

There are a couple of severe caveats to this approach of networking our simulation. All clients and the server are technically in the past when it comes to the position, rotation, and simulation of the ship! How far they are in the past is determined by how large our interpolation buffer is - the maximum buffer size times the interval between snapshots being sent. The client is in control of their physics simulation, which means they can cheat! The Player on player collision is especially not easy to reconcile because each ship is in charge of its physics state and position. It’s an issue of he said she said. Both are “right”, but they’re both wrong! A future article will address these issues and future expansions of the networking system.

Thanks for reading, until next time
- Tobias

Don’t forget to join the discussions of Sails of War and stay up to date with developments on our social media platforms!
Discord: Sails of War Discord
Website: Sailsofwargame.com

### Appendix

Buoyancy:
While interacting with Unreal 4 Community's Ocean Project (now Environment Project) I have helped them overhaul and provide the new groundwork for a new buoyancy system. With the addition of Gerstner waves being standardized in Unreal 4.26, I have no doubt someone reading this will find this project useful. This is their discord:
Unreal Environment Project Discord

Jacques Kerner's Gamasutra articles on Just Cause 3:
Gamasutra Part I
Gamasutra Part II
Implementation of Sub-Stepping in Unreal 4:
Sub-Stepping in Unreal 4

Physics Networking:
Glenn Fiedler’s GDC talk on physics interpolation:
Article Presentation
GDC Video Presentation