Simulated Buoyancy in Unity

The next step for creating my very own personal surfboard simulator for the oculus rift is to tackle the missing support of  buoyancy forces in Unity. The principle is pretty straightforward:

The buoyancy is equivalent to the weight of the displaced fluid.

So after sending a raycast from the buoyant object in negative Y direction we get a surface point on the water mesh. 

Calculating the submersed volume and resulting buoyancy for complex meshes would need too much performance. At simpler solution is to approximate the volume by placing multiple "buoyancy probes" inside the mesh which are then connected using rigid joints. For each probe, the submersion depth can be easily calculated by using position, radius and surface point.  

void FixedUpdate () {
    ... 
    force += calcBuoyancyForce (submergedDepth);
    rigidbody.AddForce (force);
    ...
}

private Vector3 calcBuoyancyForce (float submergedDepth) {
    float submergedVolume = area * submergedDepth;
    Vector3 force = Vector3.up * gravity * liquidDensity * submergedVolume;
    return force;
}

Because objects traveling trough water are subject to drag forces, we have to incorporate these, else the object will just keep on happily bobbing up and down forever. As the drag depends on submersion depth and directionality we can't use unity's default drag mechanism. Also because we approximated the actual volume of the submersed mesh by using multiple probes, this is the only way to account for the actual shape of the mesh (i.e. large drag resistance perpendicular to broad faces and low drag resistance perpendicular to narrow faces). For each buoyancy probe it's current velocity is decomposed into local X,Y and Z direction. Using the Drag Equation we can calculate drag force for each direction.

Cd is the drag coefficient which can be set for each local axis of each buoyancy probe. Combining the different local axis drag forces results in an object which can move easier in one direction, but harder in another.

Finally, because of the discrete time step nature of the calculations, it is important to clamp the resulting drag force above a certain threshold. Basically, drag should always only decrease the velocity, we don't want the drag force to cause velocity to jump from positive to negative in between time steps. Else we get small-scale jittering instead of smooth motion.

Vector3 clampMaxDragForce (Vector3 dragForce) {
    float dragForceMax = velocity.magnitude / (mass * Time.fixedDeltaTime);
    if (dragForce.magnitude > dragForceMax) {
        dragForce = dragForce.normalized * dragForceMax;
    }
    return force;
}