Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,5 @@ clang-tidy.sh
points.txt
TODO
*.html
*.md
!README.md
examples/visualize_trajectory.cpp
issues
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ set(HEADERS
${PROJECT_INCLUDE_DIR}/FlightSimulator.hpp
${PROJECT_INCLUDE_DIR}/BallState.hpp
${PROJECT_INCLUDE_DIR}/ground_surface.hpp
${PROJECT_INCLUDE_DIR}/GroundProvider.hpp
${PROJECT_INCLUDE_DIR}/physics_constants.hpp
)

Expand Down Expand Up @@ -91,6 +92,7 @@ set(TEST_SOURCES
${PROJECT_TEST_DIR}/test_bounce_phase.cpp
${PROJECT_TEST_DIR}/test_roll_phase.cpp
${PROJECT_TEST_DIR}/test_flight_simulator.cpp
${PROJECT_TEST_DIR}/test_ground_provider.cpp
${PROJECT_SRC_DIR}/GolfBallPhysicsVariables.cpp
${PROJECT_SRC_DIR}/math_utils.cpp
)
Expand All @@ -106,6 +108,9 @@ target_link_libraries(calculate_ball_landing ${PROJECT_NAME})
add_executable(calculate_ball_trajectory examples/calculate_ball_trajectory.cpp)
target_link_libraries(calculate_ball_trajectory ${PROJECT_NAME})

add_executable(multi_ground_simulation examples/multi_ground_simulation.cpp)
target_link_libraries(multi_ground_simulation ${PROJECT_NAME})

# Discover tests
include(GoogleTest)
gtest_discover_tests(libshotscope_tests)
Expand Down
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# libshotscope
`libshotscope` is a C++ library designed to simulate golf ball trajectories based on initial conditions like velocity, atmospheric data, and more. It provides easy-to-use functions to visualize or calculate the balls flight path and landing point (now including bounces & rolling!).
`libshotscope` is a C++ library designed to simulate golf ball trajectories based on initial conditions like velocity, atmospheric data, and more. It provides easy-to-use functions to visualize or calculate the ball's flight path and landing point (now including bounces, rolling, and dynamic ground surfaces!).

The in-air math here is based on [work done](http://baseball.physics.illinois.edu/trajectory-calculator-golf.html) by Prof. Alan M. Nathan at the University of Illinois Urbana-Champaign.

Expand All @@ -14,6 +14,35 @@ chmod +x build.sh
./build.sh
```

## docs
## Features

[How to use](/docs/how.md) `libshotscope`
- Full trajectory simulation with automatic phase transitions (aerial → bounce → roll)
- Dynamic ground surfaces - fairways, roughs, greens, elevation changes
- Efficient step-by-step numerical integration

## Documentation

- [Getting Started](/docs/how.md) - Basic usage
- [Ground Providers](/docs/ground_providers.md) - Dynamic ground surfaces (fairways, roughs, greens)

### Quick Example

```cpp
#include <libshotscope.hpp>

// Basic simulation with single ground type
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};
GroundSurface ground; // Default fairway

GolfBallPhysicsVariables physVars(ball, atmos);
FlightSimulator sim(physVars, ball, atmos, ground);

// Initialize and run
sim.initialize(initialState);
while (!sim.isComplete()) {
sim.step(0.01f);
}
```

For dynamic ground surfaces (fairway/rough/green), see the [Ground Providers Guide](/docs/ground_providers.md).
284 changes: 284 additions & 0 deletions docs/ground_providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
# Ground Providers

## Overview

The `GroundProvider` interface lets you change ground properties based on position - model fairways, roughs, greens, elevation changes, etc.

```cpp
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

## Basic Example

```cpp
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);
```

## Ground Surface Parameters

```cpp
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:**

```cpp
// 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};
```

### Parameter Details

**height**: Ground elevation in feet
- `0.0f`: Ground level
- `3.0f`: Elevated green
- `-1.0f`: Sunken bunker

**restitution**: 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 friction
- `0.5f`: Fairway
- `0.8f`: Sand

**frictionDynamic**: Rolling resistance - deceleration = friction × gravity
- `0.12f`: Fast green (stimp 12+)
- `0.2f`: Medium fairway
- `0.5f`: Thick rough

**firmness**: Ground hardness - softer ground applies more friction on impact
- `0.15f`: Soft sand
- `0.8f`: Firm fairway
- `0.95f`: Hard green

**spinRetention**: Fraction of spin remaining after impact
- `0.3f`: Sand bunker
- `0.75f`: Fairway
- `0.85f`: Soft green

## Implementation Patterns

### Distance-based

```cpp
GroundSurface getGroundAt(float x, float y) const override {
float yards = y / physics_constants::YARDS_TO_FEET;
if (yards >= 250.0f) return greenSurface;
return fairwaySurface;
}
```

### With lateral rough

```cpp
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{};
}
```

### Circular features

```cpp
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;
}
```

### Grid-based

```cpp
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;
};
```

## When Ground is Queried

Ground is checked:

1. Before each bounce (when ball contacts ground while moving downward)
2. Every 0.1 seconds during roll phase
3. At phase transitions (aerial → bounce, bounce → roll)

Your `getGroundAt()` is called 10-30 times per trajectory, not every simulation step (which would be 500+).

This means:
- Each bounce uses the ground properties at that location
- Rolling ball picks up surface changes every 0.1 seconds
- No queries during aerial phase (doesn't matter what's below)


## Performance

- Called 10-30 times per trajectory (not every step)
- Pre-compute data in constructor, not in `getGroundAt()`
- Avoid file I/O or database queries in `getGroundAt()`
- Simple logic is fine - optimization rarely needed

If you have an expensive ground provider (database queries, etc.), cache results based on position.

## Complete Example

See `examples/multi_ground_simulation.cpp`:

```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);
}
```

## Coordinate System

- **Origin (0, 0)**: Ball starting position
- **X-axis**: Lateral (negative = left, positive = right)
- **Y-axis**: Downrange (positive = toward target)
- **Units**: Feet (use `physics_constants::YARDS_TO_FEET` for conversion)

```cpp
// Yards to feet
float feet = yards * physics_constants::YARDS_TO_FEET; // 3.0

// Feet to yards
float yards = feet / physics_constants::YARDS_TO_FEET;
```

## Backward Compatibility

Old code still works:

```cpp
// Single ground surface - still works
GroundSurface ground;
FlightSimulator sim(physVars, ball, atmos, ground);
```

Internally creates a `UniformGroundProvider` that returns the same surface everywhere.
Loading