Bezier Generator¶
The bezier generator is the default and most configurable of the centerline generators.
It samples a set of random grid-jittered corner anchors, sorts them by angle around
their centroid to form a simple star-shaped polygon, and assembles a closed cubic
Bézier spline through those corners. Handle lengths are proportional to
rad * chord and adaptively clamped so that no handle overshoots a nearby corner.
An optional per-environment style draw broadens the shape family within a single batch.
Any smooth curve that still self-crosses after clamping is rescued by its provably
simple corner polygon, which the downstream XPBD solver re-rounds.
Sample tracks produced by the Bezier generator at the default configuration.¶
How It Works¶
The generator runs a fixed sequence of Warp kernels in a single pass — no host-side retry loop, no per-environment Python branching:
Optional style draw. When
style_sampling=True, a dedicated kernel draws per-environmentrad,scale, andhandle_clamp_fracvalues from their respective*_rangefields using RNG salt2741. A range ofNonecollapses to the corresponding scalar, so individual knobs can stay fixed while others vary. This Python branch is resolved at CUDA-graph capture time; the per-environment values are device arrays and the captured graph remains static.Corner count.
_corner_count_sample_kdrawscount[e]uniformly from[min_num_points, max_num_points]for each environment using RNG salt6151.Corner positions.
_corner_sample_k(or_corner_sample_style_kwhen style-sampling is active) samplesmax_num_pointsgrid cells from anum_cells × num_cellsgrid wherenum_cells = int(1 / (min_point_distance * 2)). Each cell selection uses bounded duplicate rejection (up to eight retries per corner) to spread corners across distinct cells, then adds per-axis sub-cell jitter. Coordinates are multiplied byscale(or by the per-environmentscale[e]in style mode). RNG salt for positions:9781.Prune-then-sort.
_ccw_sort_ksorts only the firstcount[e]corners byatan2angle around the centroid of those same kept corners (not allmax_num_pointsslots), writing NaN past the live count. Sorting after pruning matters: sorting allmax_num_pointspoints and keeping a prefix would compute the centroid over unpruned slots, producing angular wedges around the wrong centroid that can close as figure-eight loops.Vertex tangents.
_vertex_tangents_kcomputes each corner’s tangent as a normalized blend of the outgoing unit edge directionu_outand the incoming unit edge directionu_in, weighted byp = atan(edgy) / π + 0.5. At the defaultedgy=0,p=0.5gives a symmetric bisector tangent. It also records each corner’s shorter incident edge length for the adaptive clamp (step 6).Cubic Bézier assembly.
_assemble_kemitsnum_points_per_segmentdense samples for each closed edge (cornerito corner(i+1) % count[e]), including the wrap-around closing edge. For each segment the handle lengths are computed as:\[h_0 = \min\!\bigl(\texttt{rad} \cdot \text{chord},\; \texttt{handle\_clamp\_frac} \cdot \min_{\text{adj}}(e_0)\bigr)\]\[h_1 = \min\!\bigl(\texttt{rad} \cdot \text{chord},\; \texttt{handle\_clamp\_frac} \cdot \min_{\text{adj}}(e_1)\bigr)\]where \(\min_{\text{adj}}(e)\) is the length of the shorter incident edge at that corner. The cubic control points are then:
\[P_1 = c_0 + \hat{t}_0 \cdot h_0, \qquad P_2 = c_1 - \hat{t}_1 \cdot h_1\]and the segment is evaluated via the standard Bernstein basis (see Math section). In style mode
_assemble_style_kreadsrad[e]andhandle_clamp_frac[e]from the per-environment device arrays drawn in step 1.Dense-to-N arc-resample.
_arc_resample_inplaceconverts the densemax_num_points × num_points_per_segmentloop to exactlynum_pointsarc-uniformly spaced samples, building cumulative arc length infloat64and interpolating with a searchsorted lookup.Self-intersection check and fallback.
self_intersections_inplacecounts proper crossings on thenum_points-point resampled loop. For environments where the Bézier curve self-crosses,_assemble_polygon_selected_kemits the straight corner polygon (equivalent tohandle_clamp_frac=0) and_arc_resample_selected_inplaceoverwrites only those rows._select_vec2_kthen writes the Bézier result for non-crossers and the polygon result for crossers into the output buffer.Generation flag. All environments are marked
out_valid_wp=1unconditionally. The generation gate is always True; geometric validity is decided later by the shared post-relax inflation gate.
Math¶
Cubic Bézier segment. Each corner-to-corner segment is a standard degree-3 Bézier curve parameterised by \(t \in [0, 1]\):
where \(P_0 = c_0\) and \(P_3 = c_1\) are the adjacent corner anchors, and \(P_1\), \(P_2\) are the interior control (handle) points constructed below.
Vertex tangent blend. The tangent direction at corner \(i\) is:
where \(\hat{u}^{\text{out}}_i\) is the unit vector from corner \(i\) to
corner \(i{+}1\) and \(\hat{u}^{\text{in}}_i\) is the unit vector from corner
\(i{-}1\) to corner \(i\). At edgy=0, \(p = 0.5\) and the tangent
bisects the two edge directions symmetrically. As edgy grows, \(p \to 1\)
and the tangent aligns with the outgoing edge, sharpening the exit from each corner.
Handle construction. For each segment from \(c_0\) to \(c_1\) with chord \(\|c_1 - c_0\|\):
where \(l_{\min,i}\) is the shorter of the two edges incident at corner \(i\). The interior control points follow:
The clamp prevents \(h_i\) from exceeding handle_clamp_frac times the
shorter adjacent edge, so a long handle cannot overshoot a nearby corner and cause
a self-crossing.
Parameters¶
All fields are part of TrackGenConfig (see the Configuration reference for the
full list, defaults, and validation rules).
min_num_pointsMinimum corner-anchor count per environment. Must be >= 2.
max_num_pointsMaximum corner-anchor count per environment. A wider range adds more per-environment shape diversity. Also sets the buffer stride
P.num_points_per_segmentDense Bézier samples emitted per corner-to-corner segment before arc-resampling. Must be >= 2; higher values give the arc-resampler a finer cumulative-arc-length grid, reducing interpolation error.
min_point_distanceMinimum grid-cell spacing for corner sampling. Controls the corner grid as
num_cells = int(1 / (min_point_distance * 2)). Must be in(0, 0.5].min_angleMinimum interior angle (radians) at constrained corners. Used by the gate-angle validity kernel; not applied directly to centerline generation on the current single-pass path.
radHandle length as a fraction of the segment chord — the primary curviness dial. Only live when
rad <= handle_clamp_frac; ifradexceeds the clamp,handle_clamp_fracbinds every segment andradhas no independent effect.edgyCorner-tangent blend weight. Converted as
p = atan(edgy) / π + 0.5. At the default0.0,p = 0.5gives a symmetric bisector tangent; larger positive values pushptoward 1, making turns asymmetrically sharper (outgoing-edge-dominant).scaleIsotropic scale multiplier applied to sampled corner positions. Scales all track coordinates; interacts with
spacingandhalf_widththrough the coordinate range.handle_clamp_fracAdaptive handle clamp. Each corner’s handle is capped at
handle_clamp_frac × (its shorter incident edge)to prevent overshoot past a nearby corner. Set equal torad(the default0.4) so the clamp only trims genuine overshoot; set belowradto bind every handle regardless ofrad(near-polygonal tracks); set very large to disable.style_samplingOpt-in per-environment Bézier style randomisation (default
False). WhenTrue, each environment draws its ownrad,scale, andhandle_clamp_fracfrom the*_rangefields, so a single batch spans a family of styles.rad_range(lo, hi)range for per-environmentradwhenstyle_sampling=True.Nonecollapses to the scalarradfor all environments.scale_range(lo, hi)range for per-environmentscalewhenstyle_sampling=True.Nonecollapses to the scalarscalefor all environments.handle_clamp_frac_range(lo, hi)range for per-environmenthandle_clamp_fracwhenstyle_sampling=True.Nonecollapses to the scalarhandle_clamp_fracfor all environments.
What Makes It Distinct¶
The Bezier generator is the default and most knob-rich of the six generators:
Widest style control. Nine primary knobs (
min_num_points,max_num_points,num_points_per_segment,min_point_distance,min_angle,rad,edgy,scale,handle_clamp_frac) plus the opt-instyle_samplingfamily are all exclusive to the corner-Bézier representation. No other generator exposes per-env handle geometry or a per-batch style sweep.Cubic Bézier representation. Compared to the hull generator (which uses a closed uniform Catmull-Rom spline) and the polar generator (also Catmull-Rom), the Bézier formulation gives explicit, per-corner handle control through
radandedgy. The hull generator adds radial midpoint displacement for pronounced lobes and pinches; the Bézier generator keeps the shape entirely corner-determined.Star-shaped corner families. Like the hull generator the Bézier generator samples Cartesian corners and angle-sorts them. Unlike the polar generator, which starts from polar control knots and is smooth and centered by construction, and unlike the voronoi generator, which snaps anchors to a pre-sampled site field, the Bézier generator’s shapes are anchored to sampled grid corners and are roughly star-shaped around the corner centroid. Unlike the checkpoint generator — which steers a bounded-turn path through radial checkpoints and produces organically curving loops — the Bézier generator is purely geometric and not trajectory-based.
Lowest relaxation burden at the default config. At the default
handle_clamp_frac=0.4(matchingrad=0.4) and fat-band settings the valid yield is approximately 99.9 % end-to-end, and the generator produces consistently smooth, well-spaced centerlines before relaxation begins.
Fallback and Validity¶
Local polygon fallback. After arc-resampling to num_points, any environment
whose Bézier centerline contains at least one proper self-crossing is redirected to its
provably simple corner polygon (straight line segments between the sorted corners).
Only the affected environments are reprocessed; non-crossing environments are
unaffected. The XPBD bead-chain solver then re-rounds the polygon fallback through its
bending and separation corrections.
Generation-stage validity. The generator writes out_valid_wp=1 for every
environment unconditionally. The generation stage does not gate on geometry. Turning
number, track thickness, NaN presence, and width-floor checks are all evaluated later
by the shared post-relax inflation validity gate (_validity_k inside
inflate_warp). The final Track.valid field therefore reflects post-relax
geometric quality, not generation success.