Skip to content
cfd-lab:~/en/posts/2026-08-13-metacentric-h…online
NOTE #130DAY THU 유체역학DATE 2026.08.13READ 8 min read#Metacenter#Buoyancy#Free-Surface#Fluid-Mechanics#FSI

The Weight Sits on Top and She Still Rights Herself — Metacentric Height and the Free Surface Effect

GM is the margin the waterplane inertia buys you. A single free surface in a tank can take the whole margin away.

The centre of gravity of a container ship sits several metres above the centroid of her submerged volume. The heavy end is on top, and she still stands up by herself. Meanwhile a barge whose centre of gravity is below the buoyancy centre can roll over. Up versus down settles nothing. This post is about the quantity that actually decides the case — the metacentric height GMGM — where it comes from, and why liquid in a tank can erase it completely.

The centre of gravity is above the centre of buoyancy#

Symbols first. KK is the keel, GG is the centre of gravity, BB is the centre of buoyancy — the centroid of the submerged volume. Archimedes only fixed the magnitude: buoyancy equals the weight of the displaced water, and its line of action passes through the centroid of that displaced volume.

Upright, GG and BB share the same vertical line. Weight and buoyancy are equal and opposite, so the moment is zero. In that state it makes no difference whether GG is high or low. Stability is not defined by the state at rest. It is defined by what appears when you tip the body slightly.

Heel her, and things change. BB is the centroid of the submerged shape, so when the shape changes, BB moves with it. GG rotates with the ship but stays put inside it. The two verticals separate and a lever arm appears. That arm is the righting arm GZGZ, and the righting moment is M=ΔGZM = \Delta \cdot GZ for a displacement Δ\Delta.

Heel her and the submerged shape changes#

Tip her yourself in the simulation below. Change the beam, the draft and the height of the centre of gravity, then press release at 15 deg and watch whether she comes back.

phi = 15.0 deg
Raise KG past 7.33 m and the barge no longer comes back — it rolls out to an angle of loll and stays there. Hold the heel and drag it past 27 deg: the deck edge goes under, the waterplane stops widening, and the green GZ curve peels away from the dashed straight line GM sin phi. That gap is why a single number cannot certify a hull.

BB (green) is dragged toward the low side, and the gap that opens against the vertical through GG (yellow) is GZGZ. The purple MM is where the buoyancy line crosses the ship's centreline. Push the KG slider above 7.33 m and MM drops below GG: instead of returning, she lies over at a fixed angle and stays there.

BM=I/BM = I/\nabla — the waterplane inertia is the whole story#

At a small heel ϕ\phi the change in the submerged shape reduces to two wedges: one that emerges on the high side and one that immerses on the low side. Their volumes are equal, because the displacement cannot change, and they are separated horizontally. That transfer of volume is what moves BB.

Take a strip dAdA of the waterplane (the section the free surface cuts through the hull) at a distance xx from the centreline. It contributes a volume change xϕdAx\phi\,dA and a moment x2ϕdAx^2\phi\,dA. Summing over the waterplane gives a second moment of area.

BB=ϕAx2dA=Iϕ,BM=I\overline{BB'} = \frac{\phi \int_A x^{2}\,dA}{\nabla} = \frac{I\,\phi}{\nabla}, \qquad BM = \frac{I}{\nabla}

II is the second moment of the waterplane about the centreline and \nabla is the displaced volume. For a rectangular section of beam bb, per unit length I=b3/12I = b^3/12 and =bT\nabla = b\,T, so

BM=b212TBM = \frac{b^{2}}{12\,T}

The beam enters cubed, then the \nabla in the denominator cancels one power and leaves a square. Widen the hull by 20% and BMBM grows by 44%. That is why a canoe is twitchy and a raft is not. The verdict itself is one line.

GM=KB+BMKGGM = KB + BM - KG

KBKB is the height of the buoyancy centre and KGKG the height of the centre of gravity. GM>0GM > 0 means she rights herself, GM<0GM < 0 means she goes over. For a barge with beam b=16b = 16 m and draft T=4T = 4 m, KB=2.0KB = 2.0 m and BM=5.333BM = 5.333 m, so KM=7.333KM = 7.333 m. If KGKG is 6.6 m, the whole margin is 0.733 m.

The angle where GMsinϕGM \sin\phi betrays you#

GMGM is a slope at ϕ0\phi \to 0. At small angles GZGMsinϕGZ \approx GM \sin\phi holds well. The interesting question is when it stops holding.

Geometry sets that point, because II stays constant only while the waterplane stays the same. The barge above has 4 m of freeboard, so the deck edge goes under at tanϕ=2×4/16\tan\phi = 2 \times 4/16, that is 26.6 degrees. From there the waterplane stops widening and starts to shrink. BMBM collapses and the GZGZ curve rolls over.

Press hold heel in the simulation and drag past 30 degrees: the solid green curve (the exact value) peels away from the dashed purple line (GMsinϕGM\sin\phi). The heel where the two disagree in sign is the angle of vanishing stability.

Liquid in a tank takes the whole of GM#

Now put a tank of liquid inside the hull. Ballast tanks, fuel tanks, cargo tanks, a car deck flooded with firefighting water — all the same problem.

The weight of the liquid does not change when she heels. It only moves. And that movement drags GG toward the low side. The bookkeeping is identical to BMBM: the free surface inside the tank is a waterplane too, and heeling swaps two wedges across it.

δGM=ρfiρ\delta GM = \frac{\rho_f\, i}{\rho\, \nabla}

ii is the second moment of the tank's free surface, ρf\rho_f the density of the liquid inside and ρ\rho that of the water outside. The loss is not subtracted from the hull; it is treated as a virtual rise of GG by that amount. How much liquid the tank holds does not appear in the formula. Unless the tank is pressed full so there is no free surface at all, or completely empty, 20 cm of liquid costs exactly what 2 m of it costs.

Move the bulkhead slider below.

phi = 10.0 deg
Start at n = 1 and press release: the liquid slides to the low side and the barge never comes back. Now drag the bulkhead slider. The liquid volume never changes, the centre of gravity of the ship at rest never changes — only the width of each free surface does, and at n = 2 the red loss bar has already dropped to a quarter.

Yellow is the liquid. Its surface stays horizontal no matter what the hull does. At n = 1, release her and she never returns — she settles at an angle of loll. Add one bulkhead and the red loss bar drops to a quarter, and she stands up again.

One bulkhead cuts the loss to a quarter#

Divide a tank of width btb_t into nn compartments and each is bt/nb_t/n wide, with nn of them.

i=n(bt/n)312=bt312n2i = n \cdot \frac{(b_t/n)^{3}}{12} = \frac{b_t^{3}}{12\,n^{2}}

The loss falls as 1/n21/n^2, not 1/n1/n: the cube on the width beats the single power on the count. One 12 m tank in the barge above gives i=144i = 144 m⁴ against =64\nabla = 64 m³/m, so δGM=2.195\delta GM = 2.195 m. The 0.733 m of GMGM was gone long before that. A single centreline bulkhead brings the loss down to 0.549 m and she stands again.

That is what the longitudinal bulkhead down the middle of a tanker is for. The cargo does not change and neither does the height of the centre of gravity. Only the width of each free surface is halved.

The GZ curve and the vanishing angle, in Python#

GMGM is a single tangent slope. At large angles it is quicker to cut the submerged polygon directly and take its centroid. Represent the hull section as a polygon, bisect for the waterline height that makes the submerged area equal the displaced area, and compute the centroid of what is left.

import math
 
def poly_area_centroid(poly):
    a = cx = cy = 0.0
    n = len(poly)
    for i in range(n):
        x0, y0 = poly[i]
        x1, y1 = poly[(i + 1) % n]
        cr = x0 * y1 - x1 * y0
        a += cr
        cx += (x0 + x1) * cr
        cy += (y0 + y1) * cr
    a *= 0.5
    if abs(a) < 1e-14:
        return 0.0, 0.0, 0.0
    return a, cx / (6 * a), cy / (6 * a)
 
def clip_below(poly, phi, h):
    """keep the part of the polygon whose earth-frame height is below h"""
    def f(p):
        return p[1] * math.cos(phi) - p[0] * math.sin(phi) - h
    out = []
    n = len(poly)
    for i in range(n):
        p, q = poly[i], poly[(i + 1) % n]
        fp, fq = f(p), f(q)
        if fp <= 0.0:
            out.append(p)
        if (fp < 0.0) != (fq < 0.0):
            t = fp / (fp - fq)
            out.append((p[0] + t * (q[0] - p[0]), p[1] + t * (q[1] - p[1])))
    return out
 
def waterline_offset(poly, phi, area0):
    """bisect for the waterline height that submerges exactly area0"""
    lo, hi = -60.0, 60.0
    for _ in range(80):
        mid = 0.5 * (lo + hi)
        a, _, _ = poly_area_centroid(clip_below(poly, phi, mid))
        if a < area0:
            lo = mid
        else:
            hi = mid
    return 0.5 * (lo + hi)
 
def righting_arm(beam, depth, draft, kg, phi):
    hull = [(-beam / 2, 0.0), (beam / 2, 0.0), (beam / 2, depth), (-beam / 2, depth)]
    h = waterline_offset(hull, phi, beam * draft)
    _, xb, yb = poly_area_centroid(clip_below(hull, phi, h))
    return xb * math.cos(phi) + (yb - kg) * math.sin(phi), xb, yb
 
def gz_curve(beam, depth, draft, kg, deg_max=70, step=1):
    return [(d, righting_arm(beam, depth, draft, kg, math.radians(d))[0])
            for d in range(0, deg_max + 1, step)]
 
BEAM, DEPTH, DRAFT = 16.0, 8.0, 4.0
KG = 6.6
KB = DRAFT / 2
BM = BEAM ** 2 / (12 * DRAFT)
GM = KB + BM - KG
print(f"KB={KB:.3f} m  BM=B^2/12T={BM:.3f} m  KM={KB+BM:.3f} m  KG={KG:.3f} m  GM={GM:.3f} m")
print(f"deck edge immerses at phi = {math.degrees(math.atan(2*(DEPTH-DRAFT)/BEAM)):.1f} deg")
print()
print(" phi[deg]   GZ[m]   GM*sin(phi)[m]   error[%]")
for d, gz in gz_curve(BEAM, DEPTH, DRAFT, KG, 60, 5):
    lin = GM * math.sin(math.radians(d))
    err = f"{100 * (lin - gz) / gz:8.1f}" if abs(gz) > 0.01 else "       -"
    print(f"  {d:5.1f}   {gz:7.4f}   {lin:11.4f}   {err}")
 
curve = gz_curve(BEAM, DEPTH, DRAFT, KG, 70, 1)
dmax, gzmax = max(curve, key=lambda t: t[1])
vanish = next((d for d, g in curve if d > 5 and g <= 0.0), None)
print()
print(f"max GZ = {gzmax:.3f} m at {dmax} deg,  vanishing stability at {vanish} deg")
KB=2.000 m  BM=B^2/12T=5.333 m  KM=7.333 m  KG=6.600 m  GM=0.733 m
deck edge immerses at phi = 26.6 deg
 
 phi[deg]   GZ[m]   GM*sin(phi)[m]   error[%]
    0.0    0.0000        0.0000          -
    5.0    0.0657        0.0639       -2.7
   10.0    0.1417        0.1273      -10.2
   15.0    0.2394        0.1898      -20.7
   20.0    0.3716        0.2508      -32.5
   25.0    0.5550        0.3099      -44.2
   30.0    0.7207        0.3667      -49.1
   35.0    0.6823        0.4206      -38.4
   40.0    0.5196        0.4714       -9.3
   45.0    0.2828        0.5185       83.3
   50.0    0.0001        0.5618          -
   55.0   -0.3116        0.6007     -292.8
   60.0   -0.6406        0.6351     -199.1
 
max GZ = 0.727 m at 31 deg,  vanishing stability at 51 deg

At 5 degrees the error is 2.7%. At 30 degrees GMsinϕGM\sin\phi delivers barely half the true GZGZ, because the waterplane keeps widening until the deck goes under and the real righting arm outruns the linear estimate. Past 45 degrees the sign of the error flips and the linear estimate starts lying in the dangerous direction: at 50 degrees the true GZGZ is zero while GMsinϕGM\sin\phi still promises 0.56 m.

The free surface loss can be checked without cutting any polygon. Below deck immersion the sides are vertical, and the righting arm has a closed form. The loss is a virtual rise, so subtract δGM\delta GM from GMGM and feed that in.

import math
 
BEAM, DRAFT, KG = 16.0, 4.0, 6.6
NABLA = BEAM * DRAFT
BM = BEAM ** 2 / (12 * DRAFT)
GM = DRAFT / 2 + BM - KG
 
def free_surface_loss(rho_f, rho, b, n, nabla):
    """free surface loss [m] for a tank of width b split by n bulkheads"""
    i_free = n * (b / n) ** 3 / 12
    return rho_f * i_free / (rho * nabla)
 
def wall_sided_gz(gm, bm, phi):
    """righting arm while the sides are still vertical, before deck immersion"""
    return (gm + 0.5 * bm * math.tan(phi) ** 2) * math.sin(phi)
 
print(" tanks   i[m^4]   dGM[m]   GM_eff[m]   GZ(20deg)[m]")
for n in (1, 2, 3, 4):
    d_gm = free_surface_loss(1000.0, 1025.0, 12.0, n, NABLA)
    gz20 = wall_sided_gz(GM - d_gm, BM, math.radians(20))
    print(f"  {n:4d}  {n*(12.0/n)**3/12:7.1f}  {d_gm:7.3f}   {GM-d_gm:8.3f}   {gz20:11.3f}")
 tanks   i[m^4]   dGM[m]   GM_eff[m]   GZ(20deg)[m]
     1    144.0    2.195     -1.462        -0.379
     2     36.0    0.549      0.185         0.184
     3     16.0    0.244      0.489         0.288
     4      9.0    0.137      0.596         0.325

One bulkhead takes ii from 144 to 36, exactly a quarter. The righting arm at 20 degrees flips sign, from −0.379 m to +0.184 m. The closed form used here, GZ=(GM+12BMtan2ϕ)sinϕGZ = (GM + \tfrac{1}{2}BM\tan^2\phi)\sin\phi, agrees with the polygon calculation as long as the deck stays dry: with no loss at all it gives 0.3716 m at 20 degrees, the same number as the table above.

Before you float a hull in a 6-DOF VOF run#

The moment you solve a floating body with CFD, every one of these numbers becomes a check on the solver.

First, work out GMGM by hand before you set the initial draft. Capture the free surface with VOF (comparing interface capturing schemes) and attach 6-DOF rigid body motion (quaternion-based rigid body FEM) and she will roll on her own. If the period of that damped free decay, Tϕ=2πk/gGMT_\phi = 2\pi k/\sqrt{g\,GM} with kk the roll radius of gyration, disagrees with the hand value, suspect the grid or the inertia you typed in.

Second, grid resolution eats BMBM directly. BMBM is an x2x^2-weighted integral over the waterplane, so the cells near the sides contribute the most. If the free surface smears over two or three cells, the effective waterplane width blurs and the righting moment is underpredicted. Refining locally around the waterline is the cheap fix.

Third, if you run with the tanks filled, the free surface loss appears by itself — together with sloshing. When the liquid runs out of phase with the hull motion the result can be worse or better than the quasi-static δGM\delta GM. This is also where implicit treatments of the free surface earn their keep (Casulli–Zanolli's nested Newton). The hand calculation still holds there. It is just no longer the answer — it is the baseline you use to distrust the result.

Share if you found it helpful.