Install Asset
Install via Godot
To maintain one source of truth, Godot Asset Library is just a mirror of the old asset library so you can download directly on Godot via the integrated asset library browser
Quick Information
The worlds first open source golf physics simulator library. Realistic golf ball physics engine for Godot 4. Provides force, torque, bounce, and surface interaction calculations usable from both C# and GDScript. Lets ball react more natural using Jolt physics. Surface backspin on green surface type, proper total distance (rollout), high rpm shots, etc.
OpenFairway Physics
Realistic golf ball physics engine for Godot 4.5+ C# projects. Usable from both C# and GDScript. The same runtime path powers the in-game GolfBall node and the headless PhysicsAdapter.
Table of Contents
- Requirements
- Installation
- Quick Start (GDScript)
- Runtime Architecture
- Game Integration: Ball and Surface Ownership
- Surface Authoring
- API Reference - GDScript Usage
- C# Runtime Helpers
- Physics Flow
- Bounce and Rollout
- Surface Tuning
- Diagrams
- Units Convention
- References
- License
Requirements
- Godot 4.5+ with .NET support
- .NET 9.0 SDK (or later)
- The addon is written in C#, but GDScript projects can consume it through Godot interop
Installing .NET 9.0 SDK
Windows
- Download the .NET 9.0 SDK from https://dotnet.microsoft.com/download/dotnet/9.0
- Run the installer
- Verify with
dotnet --version
Linux (Ubuntu/Debian)
sudo apt update
sudo apt install dotnet-sdk-9.0
dotnet --version
Linux (Fedora)
sudo dnf install dotnet-sdk-9.0
dotnet --version
Installation
- Copy
addons/openfairway/into your project'saddons/directory. - Ensure your project has a C# solution. In Godot, use Project > Tools > C# > Create C# Solution if needed.
- Build the project in Godot or run
dotnet build YourProject.csproj. - Enable OpenFairway Physics in Project Settings > Plugins.
Quick Start (GDScript)
var physics = BallPhysics.new()
var aero = Aerodynamics.new()
var surface = Surface.new()
var params = PhysicsParams.new()
params.air_density = aero.get_air_density(0.0, 75.0, PhysicsEnums.Units.IMPERIAL)
params.air_viscosity = aero.get_dynamic_viscosity(75.0, PhysicsEnums.Units.IMPERIAL)
params.drag_scale = 1.0
params.lift_scale = 1.0
params.surface_type = PhysicsEnums.SurfaceType.Fairway
params.floor_normal = Vector3.UP
var fairway = surface.get_params(PhysicsEnums.SurfaceType.Fairway)
params.kinetic_friction = fairway["u_k"]
params.rolling_friction = fairway["u_kr"]
params.grass_viscosity = fairway["nu_g"]
params.critical_angle = fairway["theta_c"]
params.spinback_response_scale = fairway["spinback_response_scale"]
params.spinback_theta_boost_max = fairway["spinback_theta_boost_max"]
params.spinback_spin_start_rpm = fairway["spinback_spin_start_rpm"]
params.spinback_spin_end_rpm = fairway["spinback_spin_end_rpm"]
params.spinback_speed_start_mps = fairway["spinback_speed_start_mps"]
params.spinback_speed_end_mps = fairway["spinback_speed_end_mps"]
var velocity = Vector3(40.0, 15.0, 0.0)
var omega = Vector3(0.0, 0.0, 300.0)
var force = physics.calculate_forces(velocity, omega, false, params)
print("Force: ", force)
Runtime Architecture
BallPhysicsowns force, torque, integration, bounce math, and the shared flight-coefficient sampling path.Aerodynamicscomputes air density, viscosity, and exposes the shared drag/lift coefficient model.PhysicsParamsFactoryis the canonical C# runtime path for combining environment, resolved surface, floor normal, rollout spin, and optionalBallPhysicsProfile.SurfacePhysicsCatalogis the single source of truth for surface tuning.Surfaceis the GDScript-friendly wrapper over the same catalog values.PhysicsAdapterreuses the same parameter assembly path and the same flight-coefficient sampling path for headless regression runs.
Source: assets/diagrams/physics-runtime-components.puml
Game Integration: Ball and Surface Ownership
The runtime split is intentional:
GolfBallowns launch state, collision handling, floor normal, activeSurfaceType, and calls intoBallPhysics.HoleSceneControllerBasewires the ball, applies the default surface from settings, registersSurfaceZones and surfaceGridMaps, and supplies theResolveLieSurfacedelegate.LieSurfaceResolverowns surface precedence. It resolves in this order: active zone override, registeredGridMapworld-point lookup, collider ancestryGridMap, then default surface.PhysicsParamsFactoryandSurfacePhysicsCatalogdecide how a surface changes bounce, spinback, and rollout.GolfBallshould not branch on mesh names or surface-specific rules.
At log level Info, first impact prints a compact line like:
[LandingSurface] surface=Green ... source=gridmap_world_point
That line is the quickest way to confirm the resolved lie surface and the bounce parameters used on landing.
Surface Authoring
Use one of these two authoring paths:
- Base lie surface from
GridMap- Name
MeshLibraryitems with the plain surface names used byres://assets/meshes/surface_types.tres. - Supported item names:
Fairway,Green,Rough. LieSurfaceResolverreads those MeshLibrary item names at the ball's contact point.
- Name
- Local override from
SurfaceZone- Add
res://game/SurfaceZone.csto anArea3D. - Set its
SurfaceType. - Use this for patches that should override the base
GridMapresult.
- Add
PhysicsEnums.SurfaceType.FairwaySoft and PhysicsEnums.SurfaceType.Firm remain available for settings and SurfaceZone overrides. They are not currently authored through surface_types.tres.
If you build a custom ball/controller flow, keep the same ownership boundary: resolve a SurfaceType outside the ball, then pass that surface into the physics parameter path.
API Reference - GDScript Usage
Godot converts C# PascalCase members to GDScript snake_case. The classes below are available after building the project.
BallPhysics
Core force, torque, and bounce calculations.
var physics = BallPhysics.new()
Useful exported constants:
| GDScript property | Value | Description |
|---|---|---|
physics.ball_mass |
0.04592623 kg |
Regulation golf ball mass |
physics.ball_radius |
0.021335 m |
Regulation golf ball radius |
physics.ball_cross_section |
pi * r^2 |
Cross-sectional area |
physics.ball_moment_of_inertia |
0.4 * m * r^2 |
Moment of inertia |
physics.simulation_dt |
1 / 120.0 s |
Internal simulation timestep |
physics.spin_decay_tau |
5.0 s |
Air spin decay time constant |
var force: Vector3 = physics.calculate_forces(velocity, omega, on_ground, params)
var torque: Vector3 = physics.calculate_torques(velocity, omega, on_ground, params)
var bounce: BounceResult = physics.calculate_bounce(vel, omega, normal, state, params)
var cor: float = physics.get_coefficient_of_restitution(speed_normal)
PhysicsParams
PhysicsParams is the runtime resource passed to BallPhysics.
var params = PhysicsParams.new()
params.air_density = 1.225
params.air_viscosity = 1.81e-05
params.drag_scale = 1.0
params.lift_scale = 1.0
params.kinetic_friction = 0.50
params.rolling_friction = 0.050
params.grass_viscosity = 0.0017
params.critical_angle = 0.29
params.surface_type = PhysicsEnums.SurfaceType.Fairway
params.floor_normal = Vector3.UP
params.rollout_impact_spin = 0.0
params.spinback_response_scale = 0.78
params.spinback_theta_boost_max = 0.0
params.spinback_spin_start_rpm = 0.0
params.spinback_spin_end_rpm = 0.0
params.spinback_speed_start_mps = 0.0
params.spinback_speed_end_mps = 0.0
Key fields:
surface_typetracks the resolved lie surface used for the step.floor_normalshould be a unit vector at the ground contact point.rollout_impact_spinstores the spin captured at first landing.initial_launch_angle_degcarries the original VLA into the shared flight model for low-launch lift recovery and diagnostics.- The
spinback_*fields enable surface-weighted check and spinback behavior on steep, high-spin impacts.
BounceResult
Returned by calculate_bounce().
var result: BounceResult = physics.calculate_bounce(vel, omega, normal, state, params)
var new_vel: Vector3 = result.new_velocity
var new_omega: Vector3 = result.new_omega
var new_state: PhysicsEnums.BallState = result.new_state
Aerodynamics
Air density, viscosity, and drag/lift helpers.
var aero = Aerodynamics.new()
var density: float = aero.get_air_density(altitude, temp, PhysicsEnums.Units.IMPERIAL)
var viscosity: float = aero.get_dynamic_viscosity(temp, PhysicsEnums.Units.IMPERIAL)
var cd: float = aero.get_cd(reynolds_number)
var cl: float = aero.get_cl(reynolds_number, spin_ratio)
print(aero.cl_max)
Surface
Compatibility helper for GDScript consumers. Internally it forwards to SurfacePhysicsCatalog.
var surface = Surface.new()
var p: Dictionary = surface.get_params(PhysicsEnums.SurfaceType.Green)
Returned dictionary keys:
u_ku_krnu_gtheta_cspinback_response_scalespinback_theta_boost_maxspinback_spin_start_rpmspinback_spin_end_rpmspinback_speed_start_mpsspinback_speed_end_mps
Available surface types:
| GDScript enum | Description |
|---|---|
PhysicsEnums.SurfaceType.Fairway |
Standard fairway baseline |
PhysicsEnums.SurfaceType.FairwaySoft |
Softer fairway with more check and less rollout |
PhysicsEnums.SurfaceType.Rough |
Higher friction and drag |
PhysicsEnums.SurfaceType.Firm |
Lower friction and more forward release |
PhysicsEnums.SurfaceType.Green |
Strongest spinback/check response |
PhysicsEnums
The addon ships physics_enums.gd so enums are always available in GDScript.
PhysicsEnums.BallState.Rest
PhysicsEnums.BallState.Flight
PhysicsEnums.BallState.Rollout
PhysicsEnums.Units.Metric
PhysicsEnums.Units.Imperial
PhysicsEnums.SurfaceType.Fairway
PhysicsEnums.SurfaceType.FairwaySoft
PhysicsEnums.SurfaceType.Rough
PhysicsEnums.SurfaceType.Firm
PhysicsEnums.SurfaceType.Green
ShotSetup
Shared launch parsing and vector building utilities.
var setup = ShotSetup.new()
var spin: Dictionary = setup.parse_spin({
"BackSpin": 6399.0,
"SideSpin": 793.0
})
var launch: Dictionary = setup.build_launch_vectors(
150.0,
12.5,
-2.0,
2800.0,
5.0
)
ShotSetup.parse_spin() prefers measured BackSpin / SideSpin when present and only falls back to TotalSpin / SpinAxis when component spin is missing.
PhysicsAdapter
Headless simulation helper. It uses the same BallPhysics + PhysicsParamsFactory path as the runtime ball.
var adapter = PhysicsAdapter.new()
var result_default: Dictionary = adapter.simulate_shot_from_json(shot_dict)
var result_green: Dictionary = adapter.simulate_shot_from_json(
shot_dict,
PhysicsEnums.SurfaceType.Green,
Vector3.UP
)
Returned keys include:
carry_ydtotal_ydapex_fthang_time_sinitial_spin_ratioinitial_reinitial_launch_angle_deginitial_low_launch_lift_scaleinitial_spin_drag_multiplierinitial_backspin_rpminitial_sidespin_rpminitial_cdinitial_clpeak_clsurfacefirst_impact_spinbackfirst_impact_tangent_in_mpsfirst_impact_tangent_out_mps
PhysicsLogger
Controls physics console output for both runtime and headless paths.
| Value | C# name | GDScript int | Output |
|---|---|---|---|
0 |
Off |
0 |
No output |
1 |
Error |
1 |
Errors only |
2 |
Info |
2 |
Launch summaries, first impact, [LandingSurface] diagnostics |
3 |
Verbose |
3 |
Per-step bounce and rollout detail |
PhysicsLogger.set_level(2)
PhysicsLogger.set_level(3)
var current: int = PhysicsLogger.get_level()
C# Runtime Helpers
These are the runtime helpers the game layer uses directly:
PhysicsParamsFactorybuildsResolvedPhysicsParamsfrom environment, surface, floor normal, rollout spin, and optional ball profile.ResolvedPhysicsParamsis a plain C# object you can inspect in tests before converting toPhysicsParams.BallPhysicsProfileis the seam for ball-specific modifiers. Defaults are neutral so behavior stays unchanged unless you opt in.
var factory = new PhysicsParamsFactory();
ResolvedPhysicsParams resolved = factory.Create(
airDensity,
airViscosity,
dragScale,
liftScale,
PhysicsEnums.SurfaceType.Green,
Vector3.Up,
rolloutImpactSpin: 5024.0f,
ballProfile: new BallPhysicsProfile(),
initialLaunchAngleDeg: 12.5f
);
PhysicsParams parameters = resolved.ToPhysicsParams();
Physics Flow
The current flow is:
- Parse launch monitor data with
ShotSetup, preferring measuredBackSpin/SideSpin. - Resolve environment with
Aerodynamics. - Resolve lie surface outside the ball.
- Build runtime parameters through
PhysicsParamsFactory. - Sample shared flight coefficients (
spin ratio,Re, drag multiplier, lift recovery,Cd,Cl). - Integrate forces and torques in
BallPhysics. - On first impact, use the resolved surface to drive bounce, check, spinback, and rollout.
Carry-sensitive flight behavior in that shared path is:
- Reynolds-aware drag coefficient with low-Re smoothing for dimpled-ball flight.
- Spin-ratio lift coefficient with a reduced-gain mid-spin high-Re band.
- Transitional-Re high-spin drag relief for wedge carry.
- Ultra-high-spin drag rebound for checked/flop trajectories.
- Low-launch lift recovery for wood/topped-style low-VLA shots.
Core calculations:
- Gravity:
g = (0, -9.81 * mass, 0) - Drag:
Fd = -0.5 * Cd * rho * A * v * |v| - Magnus:
Fm = 0.5 * Cl * rho * A * (omega x v) * |v| / |omega| - Grass drag:
Fgrass = -6 * pi * R * nu_g * v - Contact velocity:
v_contact = v + omega x (-n * R)
BallPhysics uses PhysicsParams.FloorNormal for ground calculations, so slope-sensitive ground response and surface-sensitive rollout share the same parameter object.
PhysicsAdapter reads the same coefficient path for initial_cd, initial_cl, peak_cl, initial_spin_drag_multiplier, and the related carry diagnostics, so runtime and headless analysis stay aligned.
Bounce and Rollout
BallPhysics.CalculateBounce() decomposes the impact into normal and tangential components, then applies retention, COR, and spin updates.
- Low-energy impacts keep the simple tangential retention path.
- Steep, high-energy impacts can enter the Penner-style tangential reversal branch.
CriticalAnglecontrols when a surface crosses from shallow to steep behavior.SpinbackResponseScaleweights the reverse tangential term by surface.SpinbackThetaBoostMaxadds extra steep-impact help when the surface, spin window, and speed window allow it.RolloutImpactSpincarries the first-landing spin into the rollout friction model.
Green behavior is no longer hard-coded in GolfBall. If a green reacts differently, it is because the resolved SurfaceType maps to different physics parameters.
Source: assets/diagrams/landing-surface-sequence.puml
Surface Tuning
SurfacePhysicsCatalog is the single source of truth for the built-in surfaces:
| Surface | u_k |
u_kr |
theta_c rad |
spin_scale |
theta_boost rad |
|---|---|---|---|---|---|
| Fairway | 0.50 | 0.050 | 0.29 | 0.78 | 0.00 |
| FairwaySoft | 0.56 | 0.070 | 0.32 | 0.92 | 0.00 |
| Rough | 0.62 | 0.095 | 0.35 | 0.70 | 0.00 |
| Firm | 0.30 | 0.030 | 0.25 | 0.60 | 0.00 |
| Green | 0.58 | 0.028 | 0.36 | 1.12 | 0.12 |
Green also enables a spinback ramp:
spinback_spin_start_rpm = 3500spinback_spin_end_rpm = 5500spinback_speed_start_mps = 8spinback_speed_end_mps = 20
Tune these files when behavior changes:
addons/openfairway/physics/SurfacePhysicsCatalog.csfor per-surface valuesaddons/openfairway/physics/BallPhysics.csfor shared formulas and physical constantsaddons/openfairway/physics/BallPhysicsProfile.csfor ball-specific modifiers
Diagrams
assets/images/physics-runtime-components.pngassets/images/landing-surface-sequence.pngassets/diagrams/physics-runtime-components.pumlassets/diagrams/landing-surface-sequence.puml
Render with:
cd addons/openfairway/assets/diagrams
java -Djava.awt.headless=true -jar /usr/share/plantuml/plantuml.jar -tpng -o ../images physics-runtime-components.puml landing-surface-sequence.puml
Units Convention
| Context | Units |
|---|---|
| Physics engine | SI: meters, m/s, rad/s |
| JSON or TCP input | Imperial: mph, degrees, RPM |
| Display conversion | Consumer responsibility |
Conversion constants:
1 mph = 0.44704 m/s1 RPM = 0.10472 rad/s1 meter = 1.09361 yards
References
Primary Sources
- A.R. Penner - "The Physics of Golf"
https://raypenner.com/golf-physics.pdf - P.W. Bearman and J.K. Harvey - golf ball aerodynamics data
- NASA Glenn Research Center - Sutherland's Law
https://www.grc.nasa.gov/www/BGH/viscosity.html - Barometric Formula reference
https://en.wikipedia.org/wiki/Barometric_formula
Calibration References
- Launch monitor, range, and simulator comparison runs used for tuning carry, apex, bounce, and rollout.
License
MIT - see LICENSE.
The worlds first open source golf physics simulator library. Realistic golf ball physics engine for Godot 4. Provides force, torque, bounce, and surface interaction calculations usable from both C# and GDScript. Lets ball react more natural using Jolt physics. Surface backspin on green surface type, proper total distance (rollout), high rpm shots, etc.
Reviews
Quick Information
The worlds first open source golf physics simulator library. Realistic golf ball physics engine for Godot 4. Provides force, torque, bounce, and surface interaction calculations usable from both C# and GDScript. Lets ball react more natural using Jolt physics. Surface backspin on green surface type, proper total distance (rollout), high rpm shots, etc.