Check out our latest project ✨ OpenChapter.io: free ebooks the way its meant to be 📖

OpenFairWay Golf Physics Simulator Library

An asset by jesseincode
The page banner background of a mountain and forest
OpenFairWay Golf Physics Simulator Library hero image

Quick Information

0 ratings
OpenFairWay Golf Physics Simulator Library icon image
jesseincode
OpenFairWay Golf Physics Simulator Library

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.

Supported Engine Version
4.5
Version String
1.0.6
License Version
MIT
Support Level
community
Modified Date
2 days ago
Git URL
Issue URL

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

  • 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

  1. Download the .NET 9.0 SDK from https://dotnet.microsoft.com/download/dotnet/9.0
  2. Run the installer
  3. 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

  1. Copy addons/openfairway/ into your project's addons/ directory.
  2. Ensure your project has a C# solution. In Godot, use Project > Tools > C# > Create C# Solution if needed.
  3. Build the project in Godot or run dotnet build YourProject.csproj.
  4. 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

  • BallPhysics owns force, torque, integration, bounce math, and the shared flight-coefficient sampling path.
  • Aerodynamics computes air density, viscosity, and exposes the shared drag/lift coefficient model.
  • PhysicsParamsFactory is the canonical C# runtime path for combining environment, resolved surface, floor normal, rollout spin, and optional BallPhysicsProfile.
  • SurfacePhysicsCatalog is the single source of truth for surface tuning.
  • Surface is the GDScript-friendly wrapper over the same catalog values.
  • PhysicsAdapter reuses the same parameter assembly path and the same flight-coefficient sampling path for headless regression runs.

README Physics runtime components

Source: assets/diagrams/physics-runtime-components.puml

Game Integration: Ball and Surface Ownership

The runtime split is intentional:

  • GolfBall owns launch state, collision handling, floor normal, active SurfaceType, and calls into BallPhysics.
  • HoleSceneControllerBase wires the ball, applies the default surface from settings, registers SurfaceZones and surface GridMaps, and supplies the ResolveLieSurface delegate.
  • LieSurfaceResolver owns surface precedence. It resolves in this order: active zone override, registered GridMap world-point lookup, collider ancestry GridMap, then default surface.
  • PhysicsParamsFactory and SurfacePhysicsCatalog decide how a surface changes bounce, spinback, and rollout. GolfBall should 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:

  1. Base lie surface from GridMap
    • Name MeshLibrary items with the plain surface names used by res://assets/meshes/surface_types.tres.
    • Supported item names: Fairway, Green, Rough.
    • LieSurfaceResolver reads those MeshLibrary item names at the ball's contact point.
  2. Local override from SurfaceZone
    • Add res://game/SurfaceZone.cs to an Area3D.
    • Set its SurfaceType.
    • Use this for patches that should override the base GridMap result.

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_type tracks the resolved lie surface used for the step.
  • floor_normal should be a unit vector at the ground contact point.
  • rollout_impact_spin stores the spin captured at first landing.
  • initial_launch_angle_deg carries 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_k
  • u_kr
  • nu_g
  • theta_c
  • spinback_response_scale
  • spinback_theta_boost_max
  • spinback_spin_start_rpm
  • spinback_spin_end_rpm
  • spinback_speed_start_mps
  • spinback_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_yd
  • total_yd
  • apex_ft
  • hang_time_s
  • initial_spin_ratio
  • initial_re
  • initial_launch_angle_deg
  • initial_low_launch_lift_scale
  • initial_spin_drag_multiplier
  • initial_backspin_rpm
  • initial_sidespin_rpm
  • initial_cd
  • initial_cl
  • peak_cl
  • surface
  • first_impact_spinback
  • first_impact_tangent_in_mps
  • first_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:

  • PhysicsParamsFactory builds ResolvedPhysicsParams from environment, surface, floor normal, rollout spin, and optional ball profile.
  • ResolvedPhysicsParams is a plain C# object you can inspect in tests before converting to PhysicsParams.
  • BallPhysicsProfile is 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:

  1. Parse launch monitor data with ShotSetup, preferring measured BackSpin / SideSpin.
  2. Resolve environment with Aerodynamics.
  3. Resolve lie surface outside the ball.
  4. Build runtime parameters through PhysicsParamsFactory.
  5. Sample shared flight coefficients (spin ratio, Re, drag multiplier, lift recovery, Cd, Cl).
  6. Integrate forces and torques in BallPhysics.
  7. 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.
  • CriticalAngle controls when a surface crosses from shallow to steep behavior.
  • SpinbackResponseScale weights the reverse tangential term by surface.
  • SpinbackThetaBoostMax adds extra steep-impact help when the surface, spin window, and speed window allow it.
  • RolloutImpactSpin carries 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.

README Landing surface and bounce sequence

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 = 3500
  • spinback_spin_end_rpm = 5500
  • spinback_speed_start_mps = 8
  • spinback_speed_end_mps = 20

Tune these files when behavior changes:

  • addons/openfairway/physics/SurfacePhysicsCatalog.cs for per-surface values
  • addons/openfairway/physics/BallPhysics.cs for shared formulas and physical constants
  • addons/openfairway/physics/BallPhysicsProfile.cs for ball-specific modifiers

Diagrams

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/s
  • 1 RPM = 0.10472 rad/s
  • 1 meter = 1.09361 yards

References

Primary Sources

  1. A.R. Penner - "The Physics of Golf"
    https://raypenner.com/golf-physics.pdf
  2. P.W. Bearman and J.K. Harvey - golf ball aerodynamics data
  3. NASA Glenn Research Center - Sutherland's Law
    https://www.grc.nasa.gov/www/BGH/viscosity.html
  4. Barometric Formula reference
    https://en.wikipedia.org/wiki/Barometric_formula

Calibration References

  1. 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

0 ratings

Your Rating

Headline must be at least 3 characters but not more than 50
Review must be at least 5 characters but not more than 500
Please sign in to add a review

Quick Information

0 ratings
OpenFairWay Golf Physics Simulator Library icon image
jesseincode
OpenFairWay Golf Physics Simulator Library

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.

Supported Engine Version
4.5
Version String
1.0.6
License Version
MIT
Support Level
community
Modified Date
2 days ago
Git URL
Issue URL

Open Source

Released under the AGPLv3 license

Plug and Play

Browse assets directly from Godot

Community Driven

Created by developers for developers