C++20 or later is required. Ensure your CMake toolchain targets C++20:
set(CMAKE_CXX_STANDARD 20)
Build with CMake:
cmake -B build
cmake --build build
Include the main header in your source files:
#include <libgolf.hpp>
The library uses a phase-based flight simulation architecture that automatically transitions between aerial, bounce, and roll phases. A complete simulation requires two inputs: launch data and atmospheric conditions.
const LaunchData ball{
.ballSpeedMph = 160.0f,
.launchAngleDeg = 11.0f,
.directionDeg = 0.0f,
.backspinRpm = 3000.0f,
.sidespinRpm = 0.0f,
};
These fields match the output of a typical launch monitor. An optional start position can be provided in feet:
LaunchData ball{
.ballSpeedMph = 160.0f,
.launchAngleDeg = 11.0f,
.directionDeg = 0.0f,
.backspinRpm = 3000.0f,
.sidespinRpm = 0.0f,
};
ball.startX = 0.0f; // feet, lateral
ball.startY = 0.0f; // feet, downrange
ball.startZ = 0.0f; // feet, height above ground
const AtmosphericData atmos{
.temp = 70.0f,
.elevation = 0.0f,
.vWind = 0.0f,
.phiWind = 0.0f,
.hWind = 0.0f,
.relHumidity = 50.0f,
.pressure = 29.92f,
};
Field definitions are documented in include/atmospheric_data.hpp.
GroundSurface ground; // Uses default fairway properties
// Or with custom values:
// GroundSurface green{0.0f, 0.35f, 0.4f, 0.12f, 0.95f, 0.85f};
// {height, restitution, frictionStatic, frictionDynamic, firmness, spinRetention}
For position-dependent surfaces (e.g., fairway → rough → green), implement TerrainInterface:
class MyTerrain : public TerrainInterface {
public:
float getHeight(float x, float y) const override { return 0.0f; }
Vector3D getNormal(float x, float y) const override { return {0.0f, 0.0f, 1.0f}; }
const GroundSurface& getSurfaceProperties(float x, float y) const override {
// Return surface based on position
}
};
FlightSimulator sim(ball, atmos, std::make_shared<MyTerrain>());
See Terrain System for details.
FlightSimulator sim(ball, atmos, ground);
sim.run(); // uses default 10ms time step
LandingResult result = sim.getLandingResult();
printf("Distance: %.1f yards\n", result.distance);
printf("Bearing: %.1f degrees\n", result.bearing);
LandingResult contains:
xF, yF, zF — final position in yardsdistance — total distance in yardsbearing — direction in degreestimeOfFlight — total simulation time in secondsTo capture the complete flight path for visualization or analysis:
FlightSimulator sim(ball, atmos, ground);
auto trajectory = sim.runAndGetTrajectory(); // vector of BallState
for (const auto& state : trajectory) {
printf("%.1f %.1f %.1f\n",
state.position[0] / physics_constants::YARDS_TO_FEET,
state.position[1] / physics_constants::YARDS_TO_FEET,
state.position[2]);
}
See examples/calculate_ball_trajectory.cpp for a complete implementation.
Both run() and runAndGetTrajectory() accept an optional time step in seconds (default 0.01f):
sim.run(0.005f); // 5ms time step for higher resolution
After run(), the final ball state is available via getState():
const BallState& finalState = sim.getState();
// finalState.position — Vector3D in feet
// finalState.velocity — Vector3D in ft/s
// finalState.spinVector — Vector3D in rad/s (|spinVector| * ballRadius = r·ω, ft/s)
// finalState.currentTime — seconds
Physics variables computed at launch (air density, Reynolds number, etc.) are accessible via:
const ShotPhysicsContext& vars = sim.getPhysicsVariables();
float rho = vars.getRhoImperial(); // slugs/ft³
The library uses a right-handed coordinate system:
All position and velocity components are in feet. Use physics_constants::YARDS_TO_FEET for unit conversion when needed.
The simulator automatically manages three flight phases:
The current phase can be queried using sim.getCurrentPhaseName(), which returns "aerial", "bounce", "roll", or "complete".
When passing a TerrainInterface instead of a flat GroundSurface, the simulator queries terrain properties at the ball’s position during phase transitions. This enables fairways, roughs, greens, slopes, and elevated surfaces. See the Terrain System for implementation details.
By default, the simulator uses a built-in drag/lift model for a standard golf ball. You can replace it by implementing AerodynamicModel and passing it to FlightSimulator:
#include <libgolf.hpp>
class MyModel : public AerodynamicModel {
public:
Vector3D computeAcceleration(const AerodynamicState& s) const override {
float vRelX = s.velocity[0] - s.windVelocity[0];
float vRelY = s.velocity[1] - s.windVelocity[1];
float vRelZ = s.velocity[2] - s.windVelocity[2];
float vw = std::sqrt(vRelX*vRelX + vRelY*vRelY + vRelZ*vRelZ);
if (vw < 0.01f) return {0.0f, 0.0f, 0.0f};
// Constant drag, no lift
float scale = -s.c0 * 0.30f * vw;
return { scale * vRelX, scale * vRelY, scale * vRelZ };
}
float computeSpinDecayTau(const AerodynamicState& s) const override {
float v = std::sqrt(s.velocity[0]*s.velocity[0] +
s.velocity[1]*s.velocity[1] +
s.velocity[2]*s.velocity[2]);
return 1.0F / (0.00002F * v / s.ballRadius);
}
};
auto model = std::make_shared<MyModel>();
FlightSimulator sim(ball, atmos, ground, model);
See Aerodynamic Models for full details and worked examples.
Replace ball-ground bounce physics by implementing BounceModel. Pass it as the fifth argument to FlightSimulator; pass nullptr for the aero slot to keep the default:
auto bounce = std::make_shared<MyBounceModel>();
FlightSimulator sim(ball, atmos, ground, /*aero*/ nullptr, bounce);
See Bounce Models for the interface, the default algorithm, and a worked example.
Replace ball-on-ground roll physics (friction law, integrator, stop criterion) by implementing RollModel. Pass it as the sixth argument:
auto roll = std::make_shared<MyRollModel>();
FlightSimulator sim(ball, atmos, ground, /*aero*/ nullptr, /*bounce*/ nullptr, roll);
See Roll Models for the interface and a worked example.
The examples/ directory contains complete working implementations:
AerodynamicModelBounceModelRollModel