yucad3d

Formula reference

Every engineering formula the program computes — with variables, units, and where it lives in the source.

Units throughout: speed km/h, lengths m, rates as fractions (0.06 = 6%), K-values m per 1% of algebraic grade change.

Horizontal geometry

Curvature & bearing

κ = 1 / R  ·  heading = (sin b, cos b)  ·  right perp = (cos b, −sin b)
κ signed curvature (1/m); κ>0 turns right, κ<0 left, κ=0 tangent. b = bearing (deg, 0°=North, clockwise).
src/geom/include/yu3d/geom/HorizontalAlignment.hpp

Clothoid (Euler spiral) — derivation & integration

A clothoid is the transition whose curvature changes linearly with arc-length, so lateral acceleration ramps smoothly. yu3d stores it by its start point, start bearing, start/end curvatures (κ₀, κ₁, signed) and length L.

κ(s) = κ₀ + (κ₁ − κ₀) · s / L
Signed curvature at arc-length s along the spiral (0 ≤ s ≤ L). A “tangent end” is just κ = 0.

Heading is the integral of curvature (heading turns at the rate of the curvature), plus the start bearing θ₀:

θ(s) = ∫₀ˢ κ(t) dt + θ₀ = θ₀ + κ₀·s + (κ₁ − κ₀)·s² / (2L)
Heading varies quadratically in s (a closed form). This one has an exact expression — the position below does not.

Position integrates the unit heading vector. In the project's bearing convention (0° = North, heading = (sinθ, cosθ)):

x(s) = x₀ + ∫₀ˢ sin θ(t) dt  ·  y(s) = y₀ + ∫₀ˢ cos θ(t) dt
Because θ(t) is quadratic, these are Fresnel-type integrals. They have no elementary closed form for general κ₀, κ₁ (only the special tangent-start case κ₀ = 0 reduces to the standard Fresnel integrals C(s), S(s)).

How the integrals are taken

yu3d evaluates x(s), y(s) by adaptive Gauss–Kronrod quadrature — Boost.Math gauss_kronrod<double, 15> (the G7–K15 rule: a 7-point Gauss estimate embedded in a 15-point Kronrod estimate), recursed to max_depth = 10.

Curvature at s is returned exactly from the linear law; only the position needs quadrature. This is why a clothoid is more expensive to sample than a tangent or arc (which have closed-form positions).

Sample at s: position via G7–K15 of (sinθ, cosθ) over [0, s]; bearing = θ(s); curvature = κ(s)
src/geom/src/HorizontalAlignment.cpp · HClothoid::theta_rad / HClothoid::evaluate (Boost gauss_kronrod)

Transition family (Bloss, cosine, sine, Helmert)

The clothoid is the linear member of a family: every transition ramps the curvature from κ₀ to κ₁ over the length L, and only the shape of the ramp differs. Write u = s/L (0…1) and let f(u) be the shape function:

κ(u) = κ₀ + (κ₁ − κ₀)·f(u)
f(0) = 0 and f(1) = 1 for every member, so all of them start and end at the required curvature.
src/geom/src/HorizontalAlignment.cpp
Lawf(u)f′(0), f′(1)Character
Clothoidu1, 1 curvature changes at a constant rate; a corner in the rate at each end
Bloss3u² − 2u³0, 0 rate starts and ends at zero — no jolt at the joints
Cosine(1 − cos πu) / 20, 0 smooth like Bloss, slightly gentler in the middle
Sineu − sin(2πu)/(2π)0, 0 smoothest ends, steepest middle
Helmert2u²  (u ≤ ½)
1 − 2(1−u)²  (u > ½)
0, 0 two parabolas meeting at mid-length; the classic German railway ramp

The invariant worth knowing: whatever the law, the total deflection over the transition is the same:

Δθ = L · (κ₀ + κ₁) / 2
The area under κ only depends on the endpoints, so swapping one law for another moves the curve's SHAPE but not where it ends up pointing. Which is exactly why a mis-mapped law is invisible at the joints and shows up in the middle.

Cubic parabola

The classical railway transition, defined in Cartesian form rather than by arc length:

y = x³ / (6·R·X)
X = the transition's extent along the entry tangent, R = the radius reached at its end. Curvature therefore grows with x, and the arc length is obtained by integrating √(1 + y′²); mapping a station back to x is a Newton inversion.
src/geom/src/HorizontalAlignment.cpp

Because the shape is an approximation by construction, the curvature at the end stays slightly below 1/R — that is the classical definition, and yucad3d keeps it rather than “fixing” a curve the standards define this way.

Minimum curve radius

Rmin = V² / (127 · (emax + f))
V design speed (km/h); emax max superelevation; f side-friction factor (speed-dependent); 127 = unit/gravity constant.
src/geom/src/DesignStandard.cpp · min_radius()

Superelevation & runoff

Design rate by radius (AASHTO Method 2)

e(R) = emax · Rmin / |R|, clamped to [0, emax]
e and side-friction both vary in proportion to 1/R between Rmin (full e) and the no-superelevation radius. Sign banks into the curve.
src/geom/src/SuperelevationDesign.cpp · design_rate_magnitude()

Runoff length

Lr = w · |e| / Δ
w = width of rotated lanes = lane_width × rotated_lanes (m); |e| design rate; Δ max relative gradient from the standard (e.g. 0.005 = 0.5% at 80 km/h).
src/geom/src/SuperelevationDesign.cpp · compute_superelevation_runoff()

Runoff placement (no-spiral curve)

At a direct tangent↔curve boundary the runoff is split across the PC/PT:

entry: 0 at (B − f·Lr) → e at (B + (1−f)·Lr)
exit: e at (B − (1−f)·Lr) → 0 at (B + f·Lr)
B = boundary station; f = runoff_tangent_fraction ≈ 0.67 (~2/3 on the tangent). Where a clothoid exists instead, the cant ramps linearly along the spiral.
src/geom/src/SuperelevationDesign.cpp

Banking a point

dz = dzraw + offset · e  (axis-of-rotation points only)
offset = lateral distance from CL (m, + right); e = superelevation at the station. Non-AOR (auxiliary) points are not rotated.
src/geom/src/FlowGraph.cpp · emit_point()

Effective rate at a station

e(s) = interp over the diagram control points (clamped)
The diagram is the manual override if set, else the standard-aware auto runoff diagram when auto-super is on, else 0.
src/geom/src/Corridor.cpp · superelevation_at()

A simple legacy helper also exists — proportional to curvature, e = −κ × 6.0 clamped to ±8% (superelevation_rate()) — but the corridor's auto banking uses the Method-2 runoff diagram above.

Curve widening

w = n · (R − √(R² − L²)) + 0.1 · V / √R
n lanes; R radius (m); L wheelbase (m); V speed (km/h). First term = mechanical off-tracking, second = dynamic allowance. Capped at max_widening; ~0 above max_radius.
src/geom/src/CurveWidening.cpp

The widening tapers over a transition length and is interpolated per station, the same way superelevation runs off.

Vertical curves & sight distance

Parabolic vertical curve

z(x) = zVPC + gin·x + (gout − gin)/(2L) · x²
x = distance from VPC (0..L); zVPC = eVPI − gin·(L/2); g grades (decimal); L curve length. Grade: g(x) = gin + (gout−gin)·x/L.
src/geom/src/VerticalAlignment.cpp

Circular vertical curve (exact)

Some standards specify a circular arc rather than a parabola. yucad3d evaluates it exactly — no small-angle shortcut — which matters on steep grades where the two differ visibly.

φ = arctan g  ·  τ = L / (cos φin + cos φout)  ·  R = τ / tan(Δφ / 2)
φ = the grade as an ANGLE; τ = the tangent length from the VPI, equal on both sides. The horizontal span is therefore split ASYMMETRICALLY: a = τ·cos φin before the VPI, b = τ·cos φout after it.
src/geom/src/VerticalAlignment.cpp
sin φ(s) = sin φin + (s − sVPC) / R  ·  grade(s) = tan φ(s)
Walking the arc: the SINE of the slope angle changes linearly with distance along the curve — that is what makes it a circle, and what a parabola only approximates.

Not implemented on purpose: the vertical clothoid that IFC 4.3 also allows. Its exact z(s) definition is not published unambiguously, so importing one is refused with a reason rather than approximated by a parabola.

Algebraic grade change & K

A = (gout − gin) × 100  (percent)  ·  K = L / |A|
A > 0 crest, A < 0 sag, |A| < 0.1% no curve needed. K = curve length per 1% grade change.
src/algo/src/DesignAudit.cpp

Stopping sight distance (SSD)

SSD = 0.278 · V · t + V² / (254 · (a/g ± grade))
V speed (km/h); t perception/reaction time (s); a deceleration (m/s²); g = 9.81; grade signed decimal (downgrade lengthens SSD). 0.278 = km/h→m/s; 254 = 3.6²/(2g).
src/geom/src/DesignStandard.cpp · stopping_sight_distance()

Crest & sag K-values

Kcrest = SSD² / (200 · (√heye + √hobj)²)
Sight line over a summit; heye driver eye height, hobj object height (m).
src/geom/src/DesignStandard.cpp · crest_k()
Ksag = SSD² / (200 · (hheadlight + SSD · tan β))
Headlight criterion in a valley; hheadlight beam height (m), β beam up-angle (deg).
src/geom/src/DesignStandard.cpp · sag_k()

Railway cant

Equilibrium cant

E = G · V² / (127 · R)
E = cant in mm; G = dynamic gauge (mm, 1500 for standard gauge); V = speed (km/h); R = radius (m). The applied cant is E capped at the line's maximum; the shortfall (E − applied) is the cant DEFICIENCY a faster train feels.
src/modules/rail/src/Cant.cpp

Computed cant follows the curvature, so it ramps naturally through a transition and steps at a bare tangent–arc joint. Sign convention: positive = right-hand curve (the left rail is raised).

Stored cant profiles

A cant profile that came from a file (IFC, LandXML) is data, not a calculation, and yucad3d keeps it verbatim: segments of (start station, length, start/end cant, law). The laws are the same shape family as the horizontal transitions:

cant(u) = c₀ + (c₁ − c₀)·f(u)
f as in the transition table above: constant (f ≡ 0), linear, Bloss, cosine, sine, Helmert. Gaps between segments are unspecified rather than assumed to be zero.
src/geom/src/CantProfile.cpp

Where a stored profile exists it OUTRANKS the formula — the file's numbers are the engineer's decision. The Viennese bend is deliberately not implemented: its published definition is a 7th-order form we have not been able to verify, so importing one is refused rather than guessed.

Surfaces (TIN)

Delaunay triangulation

Incremental Bowyer–Watson insertion: for each new point, remove every triangle whose circumcircle contains it (the in-circle predicate), then re-triangulate the cavity. Breaklines are enforced by Lawson edge flips.

orient2d(a,b,c) = (b.x−a.x)(c.y−a.y) − (b.y−a.y)(c.x−a.x)
> 0 CCW, < 0 CW, = 0 collinear. The in-circle test lifts points to z = x²+y² and tests a 3D determinant.
src/geom/src/Surface.cpp

Elevation by barycentric interpolation

z = u·za + v·zb + w·zc,   u+v+w = 1
u, v, w barycentric weights of (x,y) in the containing triangle (a,b,c). Outside the hull / inside a clip hole → no value.
src/geom/src/Surface.cpp · elevation_at()

Cut / fill volumes

cut = Σ(zexist − zdesign)·cell²  (where exist > design)
fill = Σ(zdesign − zexist)·cell²  (where design > exist)
net = fill − cut
A regular XY grid over the surfaces' overlap; elevations compared at cell centres (midpoint rule). Accuracy O(cell²).
src/algo/src/CutFill.cpp

Georeferencing & scale factors

combined = kgrid · kheight  ·  kheight = R / (R + H)
kgrid projection distortion (from PROJ); R Earth radius (≈ 6 371 008 m); H elevation (m). grid = ground × combined.
src/core/include/yu3d/core/Geodesy.hpp

A line's combined factor is the mean of its endpoints' factors.

Design-standard constants

See Design standards for the per-standard table (emax, cross slope, perception time, deceleration, sight heights). Source: src/geom/src/DesignStandard.cpp.

Side friction f (AASHTO, km/h → f)

30→0.28, 40→0.23, 50→0.19, 60→0.17, 70→0.15, 80→0.14, 90→0.13, 100→0.12, 110→0.11, 120→0.09, 130→0.08 (linearly interpolated).

Max relative gradient Δ (AASHTO, km/h → Δ)

30→0.75%, 40→0.70%, 50→0.65%, 60→0.60%, 70→0.55%, 80→0.50%, 90→0.47%, 100→0.44%, 110→0.41%, 120→0.38%, 130→0.35%.

Worked examples

Minimum radius @ 80 km/h (AASHTO)

Rmin = 80² / (127 × (0.08 + 0.14)) = 6400 / 27.94 ≈ 229 m
emax = 0.08, f(80) = 0.14. A curve sharper than ~229 m is flagged at 80 km/h.

Superelevation runoff length

Lr = w · e / Δ = (3.6 × 1) · 0.06 / 0.005 = 43.2 m
One 3.6 m lane rotated, design rate e = 6%, Δ(80) = 0.5%. ~2/3 (29 m) sits on the tangent before the PC, ~1/3 (14 m) into the curve.

Stopping sight distance @ 80 km/h (AASHTO, level)

SSD = 0.278·80·2.5 + 80²/(254·(3.4/9.81)) ≈ 55.6 + 91.0 ≈ 147 m
t = 2.5 s, a = 3.4 m/s². A downgrade lengthens it; an upgrade shortens it.

Crest K @ 80 km/h (AASHTO)

Kcrest = 147² / (200 · (√1.08 + √0.60)²) ≈ 21610 / 638 ≈ 34
heye = 1.08 m, hobj = 0.60 m. A crest curve joining a +2% / −2% grade break (A = 4) then needs L = K·|A| ≈ 136 m.
Keep this honest When a formula or constant changes in the source, update this page in the same commit (see help/README.md). The source paths above are the single source of truth; the worked numbers are illustrative and rounded.