The GroundProvider interface lets you change ground properties based on position - model fairways, roughs, greens, elevation changes, etc.
class GroundProvider {
public:
virtual GroundSurface getGroundAt(float x, float y) const = 0;
};
Parameters:
x: Lateral position in feet (perpendicular to target, positive = right)y: Downrange position in feet (along target line, positive = forward)Returns: GroundSurface struct with physical properties
class SimpleHole : public GroundProvider {
public:
GroundSurface getGroundAt(float x, float y) const override {
float yards = y / physics_constants::YARDS_TO_FEET;
// Green at 250+ yards, elevated 3 feet
if (yards >= 250.0f) {
return GroundSurface{3.0f, 0.35f, 0.4f, 0.12f, 0.95f, 0.85f};
}
// Fairway
return GroundSurface{}; // Default values
}
};
// Usage
SimpleHole provider;
FlightSimulator sim(physVars, ball, atmos, provider);
GroundSurface{
height, // feet (0.0 = ground level)
restitution, // bounce coefficient (0.0-1.0)
frictionStatic, // impact friction (0.0-1.0+)
frictionDynamic, // rolling resistance (0.0-1.0+)
firmness, // ground hardness (0.0-1.0+)
spinRetention // spin after impact (0.0-1.0)
};
Common presets:
// Fairway
GroundSurface{0.0f, 0.4f, 0.5f, 0.2f, 0.8f, 0.75f};
// Fast green
GroundSurface{0.0f, 0.35f, 0.4f, 0.12f, 0.95f, 0.85f};
// Thick rough
GroundSurface{0.0f, 0.25f, 0.6f, 0.5f, 0.4f, 0.55f};
// Sand bunker
GroundSurface{-0.5f, 0.1f, 0.8f, 0.9f, 0.15f, 0.3f};
height: Ground elevation in feet
0.0f: Ground level3.0f: Elevated green-1.0f: Sunken bunkerrestitution: Coefficient of restitution (COR) - energy retained = COR²
0.1f: Sand bunker (retains 1% of energy)0.4f: Fairway (retains 16% of energy)0.5f: Hard surface (retains 25% of energy)frictionStatic: Impact friction (combined with firmness)
0.3f: Low friction0.5f: Fairway0.8f: SandfrictionDynamic: Rolling resistance - deceleration = friction × gravity
0.12f: Fast green (stimp 12+)0.2f: Medium fairway0.5f: Thick roughfirmness: Ground hardness - softer ground applies more friction on impact
0.15f: Soft sand0.8f: Firm fairway0.95f: Hard greenspinRetention: Fraction of spin remaining after impact
0.3f: Sand bunker0.75f: Fairway0.85f: Soft greenGroundSurface getGroundAt(float x, float y) const override {
float yards = y / physics_constants::YARDS_TO_FEET;
if (yards >= 250.0f) return greenSurface;
return fairwaySurface;
}
GroundSurface getGroundAt(float x, float y) const override {
float lateralYards = std::abs(x) / physics_constants::YARDS_TO_FEET;
float downrangeYards = y / physics_constants::YARDS_TO_FEET;
// Green
if (downrangeYards >= 250.0f && downrangeYards <= 270.0f) {
return GroundSurface{3.0f, 0.35f, 0.4f, 0.12f, 0.95f, 0.85f};
}
// Rough
if (lateralYards > 20.0f) {
return GroundSurface{0.0f, 0.25f, 0.6f, 0.5f, 0.4f, 0.55f};
}
// Fairway
return GroundSurface{};
}
GroundSurface getGroundAt(float x, float y) const override {
float dx = x - centerX;
float dy = y - centerY;
float distance = std::sqrt(dx*dx + dy*dy);
if (distance <= radius) return greenSurface;
return fairwaySurface;
}
class GridTerrain : public GroundProvider {
public:
GridTerrain(const std::vector<std::vector<GroundSurface>>& grid,
float cellSizeYards)
: grid_(grid),
cellSize_(cellSizeYards * physics_constants::YARDS_TO_FEET) {}
GroundSurface getGroundAt(float x, float y) const override {
int col = static_cast<int>(x / cellSize_);
int row = static_cast<int>(y / cellSize_);
if (row < 0 || row >= grid_.size() ||
col < 0 || col >= grid_[0].size()) {
return defaultSurface;
}
return grid_[row][col];
}
private:
std::vector<std::vector<GroundSurface>> grid_;
float cellSize_;
GroundSurface defaultSurface;
};
Ground is checked:
Your getGroundAt() is called 10-30 times per trajectory, not every simulation step (which would be 500+).
This means:
getGroundAt()getGroundAt()If you have an expensive ground provider (database queries, etc.), cache results based on position.
See examples/multi_ground_simulation.cpp:
class GolfHoleProvider : public GroundProvider {
public:
GroundSurface getGroundAt(float x, float y) const override {
const float lateralYards = x / physics_constants::YARDS_TO_FEET;
const float downrangeYards = y / physics_constants::YARDS_TO_FEET;
// Green: 250-270 yards, elevated 3 feet
if (downrangeYards >= 250.0f && downrangeYards <= 270.0f) {
return GroundSurface{3.0f, 0.35f, 0.4f, 0.12f, 0.95f, 0.85f};
}
// Rough: beyond ±20 yards from centerline
if (std::abs(lateralYards) > 20.0f) {
return GroundSurface{0.0f, 0.25f, 0.6f, 0.5f, 0.4f, 0.55f};
}
// Fairway
return GroundSurface{0.0f, 0.4f, 0.5f, 0.2f, 0.8f, 0.75f};
}
};
int main() {
const golfBall ball{0.0, 0.0, 0.0, 160.0, 11.0, 0.0, 3000.0, 0.0};
const atmosphericData atmos{70.0, 0.0, 0.0, 0.0, 0.0, 50.0, 29.92};
GolfHoleProvider provider;
GolfBallPhysicsVariables physVars(ball, atmos);
FlightSimulator sim(physVars, ball, atmos, provider);
const float v0_fps = ball.exitSpeed * physics_constants::MPH_TO_FT_PER_S;
BallState initialState = BallState::fromLaunchParameters(
v0_fps, ball.launchAngle, ball.direction,
Vector3D{0.0f, 0.0f, 0.0f},
physics_constants::GRAVITY_FT_PER_S2,
physVars.getROmega()
);
sim.initialize(initialState);
while (!sim.isComplete()) {
sim.step(0.01f);
}
const BallState& final = sim.getState();
printf("Landed at: %.1f yards\n",
final.position[1] / physics_constants::YARDS_TO_FEET);
}
physics_constants::YARDS_TO_FEET for conversion)// Yards to feet
float feet = yards * physics_constants::YARDS_TO_FEET; // 3.0
// Feet to yards
float yards = feet / physics_constants::YARDS_TO_FEET;
Old code still works:
// Single ground surface - still works
GroundSurface ground;
FlightSimulator sim(physVars, ball, atmos, ground);
Internally creates a UniformGroundProvider that returns the same surface everywhere.