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 (-600 to -1200 RPM) and sidespin (+300 to +500 RPM) so the ball stays low (SLIDING regime) 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 (+800 to +1800 RPM) produces a dipping parabola over the net that kicks forward upon floor contact (ROLLING transition).

  • Name
    shot_backhand
    Type
    BACKHAND · Groundstroke & Defensive Slice
    Description

    Left-side groundstroke or defensive backhand slice off the back glass. Uses either flat/topspin (+500 to +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 (-800 to -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 (-1800 to -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 (±1800 to ±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, apex z > 5.5 m) struck from behind the service line to push the net players back to the baseline without hitting the 4.0 m back 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 (+2600 to +3400 RPM) so the ball kicks high off the 3.0 m back glass and exits over the 3.0 m side 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

guide
animation_library_dir
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

schema
manifest.json
{
  "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:

  1. Each .glb entry in manifest.json stores contact_swing_phase (phi_c = f_contact / (N_frames - 1) in [0, 1]).
  2. During the preparation / backswing window (t <= t_hit, where PlayerPose.swing_phase rises from 0 -> 1), PadelVisualizer scrubs the .glb clip from 0 to phi_c * T_clip.
  3. At the exact impact frame (t = t_hit, swing_phase == 1.0), the skeleton's hand bone (mixamorig:RightHand) is guaranteed to be at f_contact.
  4. During the follow-through window (t > t_hit, where swing_phase decays from 1 -> 0), the clip continues smoothly from phi_c * T_clip to T_clip.

Generating New Clips with padelkit-animator

cli
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

Was this page helpful?