- 1. Introduction and Aims
- 2. System Requirements
- 3. Input Parameters
- 4. Algorithms Explained
- 5. Output Format
- 6. Example and Usage
- 7. Project Structure
- 8. Future Work / Roadmap
- 9. Troubleshooting
- 10. References
f1tenth-racetrack-generator is a tool for automatically generating racetracks for the F1Tenth autonomous racing competition. Real F1Tenth tracks are constructed using bendable ducts (plastic conduits) as walls. This tool produces tracks with the following characteristics:
- Realistic F1Tenth track properties: Reflects the use of bendable ducts as walls.
- Space‑partitioning: Generates a track that occupies a given rectangular space (unlike Formula 1 tracks).
- Single‑loop structure: Produces a single closed loop with no forks.
- Minimised dead‑ends: The shape may resemble a maze or brain folding but contains no complete dead‑ends.
- Shared physical walls: Left and right walls are not necessarily separate obstacles; one duct can serve multiple wall sections.
The project is designed in three main stages:
- Centerline generation – creates a smooth, closed-loop centerline.
- Wall generation – adds physical walls (ducts) around the centerline, respecting duct properties.
- Global path optimisation – (future) computes an optimised racing line.
- Python 3.13+
- uv (>= 0.9.17) for dependency management
- Dependencies listed in
pyproject.toml:numpyscipymatplotlibpyyamlshapelyconcave-hulljsonschema
All configuration is done via a YAML file (default: conf/config.yaml).
The configuration schema is the single source of truth for all parameters, their defaults, and documentation.
The complete schema with detailed descriptions, default values, and constraints is maintained in:
conf/config.schema.json
This JSON Schema file:
- 📋 Documents every parameter with descriptions
- 🎯 Defines default values
- 🔒 Specifies constraints (min/max, allowed values)
- 🔗 Serves as the validation source for the Python code
A minimal working configuration looks like:
config:
space:
width: 20.0
height: 10.0
centerline:
n_points: 60
max_lloyd_iterations: 10
curvature_threshold: 0.3
walls:
duct_thickness: 0.30
min_track_width: 3.00
width_model:
components:
- type: constant
value_dist:
type: uniform
low: 1.0
high: 2.0
- type: sinusoidal
amplitude_dist:
type: halfnormal
sigma: 0.3
frequency_dist:
type: uniform
low: 1
high: 5
phase_dist:
type: uniform
low: 0
high: 6.283
output:
visualize: true
output_dir: "output"
debug_output_dir: "debug"Status: ✅ Almost complete (small refinements possible).
The centerline is generated using a Voronoi‑based approach that creates a smooth, closed loop with controlled curvature. The steps are:
-
Generate initial random points inside the rectangle (with a small margin).
Number of points =n_points. -
Run Lloyd’s algorithm (for
max_lloyd_iterations) to relax the points, making their spacing more uniform.
This produces a Centroidal Voronoi Tessellation (CVT). Thelloyd.pymodule implements this. -
Select Voronoi regions using a virtual grid.
- Divide the rectangle into cells of size
virtual_grid_width. - For each cell (with probability
virtual_grid_coverage), consider theknearest seed points (k randomly chosen from 3–5). - Pick one seed point from these
kwith probability inversely proportional to distance (closer points are more likely). - Collect selected region indices (remove duplicates).
- Divide the rectangle into cells of size
-
Build a concave hull from the selected seed points.
- Use the
concave_hulllibrary with a fixedconcavityvalue (currently 0.33). - The hull becomes the initial polygon that approximates the track shape.
- Use the
-
Interpolate the polygon using Voronoi vertices while respecting the curvature limit.
- For each polygon edge, collect nearby Voronoi vertices (within a distance proportional to edge length).
- Insert those vertices to make the path follow the Voronoi structure more closely.
- Then refine the point set by checking curvature: if maximum curvature exceeds
curvature_threshold, remove the point causing the highest curvature and re‑interpolate (iteratively up to 10 times).
-
Final interpolation using a cubic spline (
scipy.interpolate.splprepwithper=Truefor closed curves).
The number of output points is scaled to achieve approximately 0.5 m spacing along the track (or finer if needed).
The result is a smooth, closed centerline that respects the curvature constraint and fills the space in an organic way.
Status: 🚧 Under development / needs update.
This stage builds physical walls (ducts) around the centerline. The approach is duct‑based, inspired by how real F1Tenth tracks are built: a series of bendable ducts, each with a fixed thickness, are placed along the track. One duct can serve both the left and right walls in a given segment.
The algorithm proceeds as follows:
-
Parse the centerline into a list of
CenterlinePointobjects, each containing:x, y– coordinatest– normalised parameter [0,1]cumulative_distance– distance along the trackcurvature– curvature at that pointleft_width,right_width– maximum allowed raycast distances to the left/right (computed from the space boundaries and/or obstacles)
-
Plan duct layout by dividing the centerline into segments.
- The total track length is split into roughly equal segments whose lengths lie between
min_duct_lengthandmax_duct_length. - Each segment will later become a
DuctSegmentobject that stores its own portion of the centerline.
- The total track length is split into roughly equal segments whose lengths lie between
-
Generate wall point clouds using a lateral offset function.
For each centerline point, compute the left and right normals (perpendicular to the tangent). Then obtain an offset distance from a user‑supplied function:def lateral_offset_fn(x, y, t, cumulative_distance, curvature, left_width, right_width, base_width, is_left, **kwargs): # returns a float (offset distance) ...
This function can implement arbitrary logic (e.g., constant width, sinusoidal variation, curvature‑dependent width).
The offset is clamped toleft_widthorright_width(to stay within the space).
For each point, a small cluster of points is generated around the nominal wall location to mimic sensor noise or duct flexibility (density controlled bywall_points_per_meter). -
Assign wall points to the nearest duct segment.
- Build a KD‑tree of all wall points.
- For each duct segment, query points within a radius (segment length/2 + margin).
- Compute the perpendicular distance from each candidate point to the duct’s centerline; keep points within
1.5 * duct_thickness. - Store the assigned points in the duct segment.
-
Create an occupancy grid at resolution
resolution.- Determine the bounding box of all wall points (with margin).
- Rasterise the wall points into the grid, filling a circle of radius
duct_thickness/2around each point. - Optionally mark duct‑assigned points with a separate value for debugging.
-
Validate the result:
- Enough wall points generated? (>100)
- Most points assigned to a duct? (>80%)
- Track width at every centerline point ≥
min_track_width? (using left/right raycast lengths).
The output includes:
- The full list of generated wall points.
- The occupancy grid.
- The list of duct segments with their assigned points.
- Metadata (statistics, parameters used).
Status: 🚧 Planned (future work).
This module will take the generated track (centerline + walls) and compute an optimal racing line. The optimised path should be:
- Collision‑free (inside the track boundaries)
- Smooth (respect curvature limits of the vehicle)
- Possibly time‑optimal (minimum curvature / maximum speed)
The output will be a new set of points (N x 7) with the same format as the centerline (x, y, t, cumulative distance, curvature, left_width, right_width) but representing the racing line.
Ideas for implementation:
- Use an optimisation‑based approach (e.g., spline fitting with constraints).
- Employ the concept of “raceline” derived from the track boundaries (e.g., minimum curvature, clipping corners).
- Or implement a pure pursuit / model predictive control based path planning.
After running the complete pipeline, the following files are saved in the output/ directory:
| File | Description |
|---|---|
centerline.npy |
(N, 7) array: [x, y, t, cumulative_distance, curvature, left_width, right_width] |
wall_points.npy |
(M, 2) array of raw wall point coordinates |
wall.npy |
Occupancy grid (2D numpy array, values 0 = free, 1 = wall, optionally 2 = duct‑assigned) |
metadata.json |
Generation parameters and statistics |
The centerline format uses Frenet‑style coordinates: t is the normalised parameter (0 to 1), cumulative_distance is the actual distance along the track from a chosen start. The widths (left_width, right_width) are the maximum available space to the left/right at that point, derived from ray‑casting to the space boundaries (and later possibly to walls).
- Clone the repository and navigate to the project root.
- Install dependencies using
uv:uv sync
- Edit the configuration in
conf/config.yamlto your liking. - Run the generator:
uv run python main.py
- Check the output in the
output/folder and debug images in thedebug/folder (ifvisualize: true).
A typical main.py does:
import yaml
from centerline import CenterlineGenerator
from walls import WallGenerator
config = yaml.safe_load(open("conf/config.yaml"))["config"]
config["visualize"] = True
config["debug_output_dir"] = "./debug"
cg = CenterlineGenerator(config)
centerline = cg.generate() # returns Nx2 array (x,y)
# (metadata like cumulative distance is added later in main)
wg = WallGenerator(config)
result = wg.generate(centerline_with_metadata) # returns dict with points, grid, etc.
# Save outputs....
├── conf
│ └── config.yaml # Main configuration file
├── debug/ # Debug visualisations (created at runtime)
├── output/ # Generated output files (created at runtime)
├── src
│ ├── centerline.py # CenterlineGenerator class
│ └── walls.py # WallGenerator class (duct‑based)
├── main.py # Main entry point
├── pyproject.toml # Project dependencies and metadata
└── README.md # This file
├── track_width_profile_functions.py # Example lateral offset functions
- centerline.py – Implements the Voronoi‑based centerline generation.
- walls.py – Duct‑based wall generation (currently under development).
- track_width_profile_functions.py – Contains example lateral offset functions (constant, sinusoidal, chicane) to be used with wall generation.
- main.py – Orchestrates the pipeline and saves results.
- Complete and stabilise wall generation (
walls.py):- Integrate ray‑casting to obtain accurate left/right maximum widths.
- Implement a robust duct connection mechanism.
- Add more sophisticated lateral offset functions (e.g., learning from real track data).
- Implement global path optimisation (
global_path.pyor similar). - Add unit tests and CI.
- Provide a graphical user interface (optional) for interactive track design.
- Benchmark generated tracks against real F1Tenth tracks.
uv syncfails – ensure you have Python 3.13+ installed anduvis up‑to‑date.
- Random Track Generator (mvanlobensels)
- DBSCAN (Wikipedia)
- Lloyd library by duhaime
- concave‑hull library by cubao
- Assisted by LLM: DeepSeek
Note: This README is a living document. As the project evolves, please update it to reflect the current state of development.