Padel Stroke & Skeletal Animation Library
PadelKit unifies 9D ball aerodynamics (BallState.from_shot) with 3D Mixamo skeletal animations (padelkit-animator .glb assets) through a standardized stroke taxonomy and impact-phase synchronization contract (contact_swing_phase).
Stroke Taxonomy & Physics Reference
Every stroke in a simulated or reconstructed padel rally maps to a canonical padelkit_animation_id on PlayerPose.animation and a physical launch signature (velocity, topspin_rpm, sidespin_rpm) in BallState.from_shot().
- Name
serve- Type
- SERVE · Underhand Slice
- Description
Executed below waist height (
z ≈ 0.85–0.98 m) after a single floor bounce in the server's box. Typically imparted with carved backspin (-600to-1200 RPM) and sidespin (+300to+500 RPM) so the ball stays low (SLIDINGregime) after rebounding off the receiver's side or back glass.
- Name
shot_forehand- Type
- FOREHAND · Groundstroke & Wall Exit
- Description
Standard right-side drive or bajada de pared struck at
z ≈ 0.75–1.10 m. Flat or topspin (+800to+1800 RPM) produces a dipping parabola over the net that kicks forward upon floor contact (ROLLINGtransition).
- Name
shot_backhand- Type
- BACKHAND · Groundstroke & Defensive Slice
- Description
Left-side groundstroke or defensive backhand slice off the back glass. Uses either flat/topspin (
+500to+1400 RPM) for passing drives or backspin (-900 RPM) for low cross-court chiquitas to the opponent's feet.
- Name
shot_volley- Type
- VOLLEY · Net Intercept
- Description
Compact punch stroke executed close to the net (
y ≈ ±2.5–3.8 m) with minimal backswing and crisp underspin (-800to-1500 RPM) to keep the ball skidding deep into the corners.
- Name
shot_bandeja- Type
- BANDEJA · Defensive/Tactical Overhead
- Description
Signature padel overhead struck at shoulder-to-head height (
z ≈ 1.75–2.10 m) with a flat-to-open racket face and heavy backspin (-1800to-2500 RPM). Designed to retain net position by producing a low, slow rebound off the back glass.
- Name
shot_vibora- Type
- VIBORA · Aggressive Side-Spin Overhead
- Description
Whipped lateral overhead struck with combined underspin (
-1200 RPM) and heavy sidespin (±1800to±2600 RPM). After hitting the corner glass, the lateral tangential velocity causes the ball to bite sideways away from the defender.
- Name
shot_lob- Type
- LOB · High Tactical Reset
- Description
High launch angle (
vz ≈ 8.5–10.5 m/s, apexz > 5.5 m) struck from behind the service line to push the net players back to the baseline without hitting the4.0 mback glass on the fly.
- Name
shot_smash- Type
- SMASH · Power Flat / Kick X3 / X4
- Description
High-point overhead (
z ≈ 2.35–2.75 m) struck with either flat power (> 130 km/h) to bring the ball back to the hitter's side or heavy topspin (+2600to+3400 RPM) so the ball kicks high off the3.0 mback glass and exits over the3.0 mside fence (por 3).
MotionBERT Captured Stroke Previews
Animations in the library are extracted from real padel footage using padelkit-animator (RTMPose 2D tracking → MotionBERT 243-frame 3D lifting → 1.78 m metric normalization → Mixamo 18-bone .glb retargeting).
Each preview card below shows the normalized Z-up in-place skeleton (in_place=True), the dominant hitting arm & 3D racket trajectory, and the exact ball contact frame (contact_swing_phase, highlighted by the emerald flash ring and bottom timeline tick).
1. Underhand Slice Serve (forehand_serve.glb)
2. Forehand Drive (forehand_drive.glb)
Loading GLB Animations in PadelVisualizer
from padelkit import Court, MatchTimeline, PadelVisualizer
court = Court.fip_standard()
timeline = MatchTimeline.demo_rally(court=court, fps=60.0)
# Connect your padelkit-animator GLB library directory
viz = PadelVisualizer(
court=court,
timeline=timeline,
animation_library_dir="../padelkit-animator/data/animations",
)
# Export a self-contained HTML studio (embeds GLBs as Data URIs)
viz.export_html("rally_with_mixamo_strokes.html")
# Or launch the local interactive server
# viz.serve(port=8765, open_browser=True)
manifest.json Entry Contract
{
"clip_id": "forehand_drive",
"glb_path": "forehand_drive.glb",
"padelkit_animation_id": "shot_forehand",
"shot_category": "FOREHAND",
"shot_variant": "STANDARD",
"spin": "FLAT",
"handedness": "RIGHT",
"racket_socket_bone": "mixamorig:RightHand",
"in_place": true,
"fps": 60.0,
"total_frames": 48,
"duration_s": 0.8,
"contact_frame_relative": 29,
"contact_swing_phase": 0.617
}
Impact-Phase Synchronization (contact_swing_phase)
When simulating a rally in MatchTimeline, the physics engine determines the exact timestamp t_hit at which the ball leaves the strings. If a skeletal animation clip were simply triggered at t_hit, the player would start their backswing after the ball is already in flight.
To align the 3D racket swing with the physical ball launch:
- Each
.glbentry inmanifest.jsonstorescontact_swing_phase(phi_c = f_contact / (N_frames - 1)in[0, 1]). - During the preparation / backswing window (
t <= t_hit, wherePlayerPose.swing_phaserises from0 -> 1),PadelVisualizerscrubs the.glbclip from0tophi_c * T_clip. - At the exact impact frame (
t = t_hit,swing_phase == 1.0), the skeleton's hand bone (mixamorig:RightHand) is guaranteed to be atf_contact. - During the follow-through window (
t > t_hit, whereswing_phasedecays from1 -> 0), the clip continues smoothly fromphi_c * T_cliptoT_clip.
Generating New Clips with padelkit-animator
# 1. Launch the browser clipper UI to trim a stroke & mark impact frame
uv run padelkit-animator ui --port 8765
# 2. Run 2D RTMPose -> 3D MotionBERT -> Blender Mixamo GLB export
uv run padelkit-animator pipeline --clip-id bandeja_01
# 3. Export a studio MP4 preview card for documentation
uv run padelkit-animator preview-card \
--clip-id bandeja_01 \
--output ../padelkit/docs/images/strokes/bandeja_01.mp4