Boats and Submarines#

A hull floats when the water it pushes aside weighs more than the hull does. Flight.displacement is the volume of water the vessel pushes aside when it is fully submerged, and Flight.buoyant_force_at() is the force that volume pushes back with:

body = vessel.orbit.body
flight = vessel.flight()
displacement = flight.displacement
buoyancy = flight.buoyant_force_at(body.ocean_density, body.surface_gravity)

Compare that force with the vessel’s weight. A hull with more buoyancy than its weight floats. A submarine trims its tanks to move between the two. Part.displacement and Part.buoyant_force_at() give the same figures for one part.

Note

Flight.buoyant_force_at() applies SpaceCenter.buoyancy_scalar, which is 1.2 in stock KSP. A buoyant force in the game is a fifth larger than Archimedes’ principle alone gives.

Trim#

A hull at rest carries its center of buoyancy on the same vertical line as its center of mass. A horizontal offset between the two turns the weight and the buoyancy into a couple that rolls or pitches the hull. That offset is the trim.

Read both in the vessel’s surface reference frame. Its x axis points up, so the trim is what the other two axes carry:

flight = vessel.flight(vessel.surface_reference_frame)
center_of_buoyancy = flight.center_of_buoyancy
center_of_mass = flight.center_of_mass

Flight.center_of_buoyancy is None when no part of the vessel is in the water.

The couple is also available as a torque about the center of mass. A hull trimmed level carries zero torque:

torque = flight.buoyant_torque_at(body.ocean_density, body.surface_gravity)

Flight.buoyant_torque_at() is the torque with the hull fully submerged. It holds steady while the hull rides the waves, where Flight.buoyant_torque follows the live waterline.

In the Editor#

A hull can be trimmed before it is launched. EditorVessel.flight() returns a SimulatedFlight, which has the fully submerged members of Flight. Its SimulatedFlight.surface_reference_frame sets which way is up. Here a hull built nose-up in the vehicle assembly building is simulated floating on its bottom:

vessel = space_center.editor.vessel
flight = vessel.flight(vessel.reference_frame)
# The vessel frame's z axis points out of the bottom, so up is its -z axis
side = math.sqrt(0.5)
flight.surface_reference_frame = space_center.ReferenceFrame.create_relative(
    vessel.reference_frame, rotation=(0, side, 0, side)
)
torque = flight.buoyant_torque_at(body.ocean_density, body.surface_gravity)

Gravity acts along the negative x axis of the surface reference frame. A frame created relative to EditorVessel.reference_frame follows the hull as it is edited.

Submersion#

Flight.submerged_portion is the fraction of the vessel below the waterline, and Flight.depth is how far its center of mass sits below the surface. The same pair is available per part, alongside Part.splashed and the depth of the part’s shallowest and deepest points.

Pressure#

Below the waterline, Flight.static_pressure and Part.static_pressure include the weight of the water. With part pressure limits enabled in the difficulty settings, a part is destroyed when its pressure exceeds Part.max_pressure:

for part in vessel.parts.all:
    if part.static_pressure + part.dynamic_pressure > part.max_pressure:
        print(part.title, "is over its pressure limit")

The loop reads the parts with separate calls, which can land in different physics ticks. A server side function reads every part in the same tick, with a single call:

over_limit = conn.run(
    lambda: [
        part.title
        for part in vessel.parts.all
        if part.static_pressure + part.dynamic_pressure > part.max_pressure
    ]
)

CelestialBody.pressure_at() takes a negative altitude for a point under the sea, to plan how deep a dive can go.

Example#

This script reports whether the active vessel floats, and how far its center of buoyancy sits from its center of mass:

import math

import krpc

conn = krpc.connect(name="Buoyancy Trim")
vessel = conn.space_center.active_vessel
body = vessel.orbit.body

if not body.has_ocean:
    raise RuntimeError(body.name + " has no ocean")

flight = vessel.flight(vessel.surface_reference_frame)

# The fully submerged figures, which do not depend on where the vessel is
buoyancy = flight.buoyant_force_at(body.ocean_density, body.surface_gravity)
torque = flight.buoyant_torque_at(body.ocean_density, body.surface_gravity)
weight = vessel.mass * body.surface_gravity

print("Displacement:  %.1f m^3" % flight.displacement)
print("Buoyancy:      %.0f N fully submerged" % buoyancy)
print("Weight:        %.0f N" % weight)
print("Floats:        %s" % (buoyancy > weight))
print("Torque:        %.0f N m fully submerged" % math.hypot(*torque))

# The trim, read in the surface reference frame. Its x axis points up, so the y and z
# axes span the horizontal. A horizontal offset between the two centers tips the hull
# until they line up again.
center_of_buoyancy = flight.center_of_buoyancy

if center_of_buoyancy is None:
    print("Trim:          vessel is out of the water")
else:
    center_of_mass = flight.center_of_mass
    north = center_of_buoyancy[1] - center_of_mass[1]
    east = center_of_buoyancy[2] - center_of_mass[2]
    print("Submerged:     %.0f%%" % (100 * flight.submerged_portion))
    print("Trim:          %.2f m horizontally" % math.hypot(north, east))
    print("Trim north:    %+.2f m" % north)
    print("Trim east:     %+.2f m" % east)