JSim 2026.06.01-p(1)
Loading...
Searching...
No Matches
ball_physics.hpp
Go to the documentation of this file.
1// Copyright (c) JSim contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the LGPLv3 license file in the root directory of this project.
4
5#pragma once
6
7#include <algorithm>
8#include <cmath>
9#include <limits>
10
13
14namespace frcsim {
15
23 public:
25 struct Config {
27 Vector3 gravity_mps2{0.0, 0.0, -9.81};
31 double air_density_kgpm3{1.225};
33 double drag_scale{1.0};
35 double magnus_coefficient{1e-4};
37 double magnus_scale{1.0};
39 double ground_height_m{0.0};
45 double max_substep_s{0.01};
46 };
47
51 double mass_kg{0.27};
53 double radius_m{0.12};
55 double drag_coefficient{0.47};
57 double reference_area_m2{0.045};
59 double restitution{0.45};
60
63 };
64
76
86
87 BallPhysicsSim3D() = default;
88
90 : config_(sanitizeConfig(config)) {}
91
92 BallPhysicsSim3D(const Config& config, const BallProperties& ball_properties)
93 : config_(sanitizeConfig(config)),
94 ball_properties_(sanitizeBallProperties(ball_properties)) {}
95
98 const Config& config() const { return config_; }
101 void setConfig(const Config& config) { config_ = sanitizeConfig(config); }
102
105 const BallProperties& ballProperties() const { return ball_properties_; }
109 ball_properties_ = sanitizeBallProperties(props);
110 }
111
114 const BallState& state() const { return state_; }
117 void setState(const BallState& state) {
118 state_ = state;
119 sanitizeState(state_);
120 }
121
127 void setCarrierPose(const Vector3& carrier_position_m,
128 const Vector3& carrier_velocity_mps = Vector3::zero()) {
129 carrier_position_m_ = carrier_position_m;
130 carrier_velocity_mps_ = carrier_velocity_mps;
131 }
132
138 bool requestPickup(const PickupRequest& pickup_request) {
139 if (state_.held) {
140 return true;
141 }
142 const double capture_radius_m =
143 sanitizeNonNegative(pickup_request.capture_radius_m, 0.2);
144 const double distance_m =
145 (state_.position_m - pickup_request.intake_position_m).norm();
146 if (!std::isfinite(distance_m) || distance_m > capture_radius_m) {
147 return false;
148 }
149
150 state_.held = true;
151 carry_offset_m_ = pickup_request.carry_offset_m;
152 state_.position_m = pickup_request.intake_position_m + carry_offset_m_;
153 state_.velocity_mps = carrier_velocity_mps_;
154 state_.spin_radps = Vector3::zero();
155 sanitizeState(state_);
156 return true;
157 }
158
160 void release() { state_.held = false; }
161
168 void shoot(const Vector3& muzzle_position_m,
169 const Vector3& muzzle_velocity_mps,
170 const Vector3& muzzle_spin_radps = Vector3::zero()) {
171 state_.held = false;
172 state_.position_m = muzzle_position_m;
173 state_.velocity_mps = muzzle_velocity_mps;
174 state_.spin_radps = muzzle_spin_radps;
175 sanitizeState(state_);
176 }
177
182 void step(double dt_s) {
183 if (!std::isfinite(dt_s) || dt_s <= 0.0) {
184 return;
185 }
186
187 sanitizeState(state_);
188
189 if (state_.held) {
190 state_.position_m = carrier_position_m_ + carry_offset_m_;
191 state_.velocity_mps = carrier_velocity_mps_;
192 sanitizeState(state_);
193 return;
194 }
195
196 const double max_substep_s = std::clamp(config_.max_substep_s, 1e-4, 0.05);
197 double remaining_s = dt_s;
198 while (remaining_s > 0.0) {
199 const double substep_s = std::min(remaining_s, max_substep_s);
200 const Vector3 accel0_mps2 = computeAcceleration(state_.velocity_mps);
201 const Vector3 mid_velocity_mps =
202 state_.velocity_mps + accel0_mps2 * (0.5 * substep_s);
203 const Vector3 accel_mid_mps2 = computeAcceleration(mid_velocity_mps);
204
205 state_.velocity_mps += accel_mid_mps2 * substep_s;
206 state_.position_m += mid_velocity_mps * substep_s;
207 sanitizeState(state_);
208
209 remaining_s -= substep_s;
210 }
211
212 resolveGroundContact(dt_s);
213 sanitizeState(state_);
214 }
215
216 private:
218 static bool finiteVector(const Vector3& v) {
219 return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z);
220 }
221
224 static double sanitizeNonNegative(double value, double fallback) {
225 if (!std::isfinite(value)) {
226 return fallback;
227 }
228 return std::max(0.0, value);
229 }
230
231 static Config sanitizeConfig(const Config& config) {
232 Config sanitized = config;
233 if (!finiteVector(sanitized.gravity_mps2)) {
234 sanitized.gravity_mps2 = Vector3(0.0, 0.0, -9.81);
235 }
236 sanitized.effective_gravity_scale = std::clamp(
237 sanitizeNonNegative(sanitized.effective_gravity_scale, 1.0), 0.0, 5.0);
238 sanitized.air_density_kgpm3 = std::clamp(
239 sanitizeNonNegative(sanitized.air_density_kgpm3, 1.225), 0.0, 5.0);
240 sanitized.drag_scale =
241 std::clamp(sanitizeNonNegative(sanitized.drag_scale, 1.0), 0.0, 10.0);
242 sanitized.magnus_coefficient = std::isfinite(sanitized.magnus_coefficient)
243 ? sanitized.magnus_coefficient
244 : 1e-4;
245 sanitized.magnus_scale =
246 std::clamp(sanitizeNonNegative(sanitized.magnus_scale, 1.0), 0.0, 10.0);
247 sanitized.ground_height_m = std::isfinite(sanitized.ground_height_m)
248 ? sanitized.ground_height_m
249 : 0.0;
250 sanitized.rolling_friction_per_s =
251 sanitizeNonNegative(sanitized.rolling_friction_per_s, 1.2);
252 sanitized.min_bounce_speed_mps =
253 sanitizeNonNegative(sanitized.min_bounce_speed_mps, 0.1);
254 sanitized.max_substep_s = std::clamp(
255 sanitizeNonNegative(sanitized.max_substep_s, 0.01), 1e-4, 0.05);
256 return sanitized;
257 }
258
259 static BallProperties sanitizeBallProperties(const BallProperties& props) {
260 BallProperties sanitized = props;
261 sanitized.mass_kg =
262 std::max(1e-6, sanitizeNonNegative(sanitized.mass_kg, 0.27));
263 sanitized.radius_m =
264 std::max(1e-4, sanitizeNonNegative(sanitized.radius_m, 0.12));
265 sanitized.drag_coefficient = std::clamp(
266 sanitizeNonNegative(sanitized.drag_coefficient, 0.47), 0.0, 5.0);
267 sanitized.reference_area_m2 = sanitizeNonNegative(
268 sanitized.reference_area_m2,
269 std::acos(-1.0) * sanitized.radius_m * sanitized.radius_m);
270 if (sanitized.reference_area_m2 <= 0.0) {
271 sanitized.reference_area_m2 =
272 std::acos(-1.0) * sanitized.radius_m * sanitized.radius_m;
273 }
274 sanitized.restitution = std::clamp(
275 std::isfinite(sanitized.restitution) ? sanitized.restitution : 0.45,
276 0.0, 1.0);
277 return sanitized;
278 }
279
281 static void sanitizeState(BallState& state) {
282 if (!finiteVector(state.position_m)) {
283 state.position_m = Vector3::zero();
284 }
285 if (!finiteVector(state.velocity_mps)) {
286 state.velocity_mps = Vector3::zero();
287 }
288 if (!finiteVector(state.spin_radps)) {
289 state.spin_radps = Vector3::zero();
290 }
291 }
292
298 Vector3 computeAcceleration(const Vector3& velocity_mps) const {
299 const Vector3 gravity_mps2 =
300 config_.gravity_mps2 * config_.effective_gravity_scale;
301 const Vector3 drag_force_n =
302 Vector3::dragForce(velocity_mps, ball_properties_.drag_coefficient,
303 ball_properties_.reference_area_m2,
304 config_.air_density_kgpm3) *
305 config_.drag_scale;
306 const Vector3 magnus_force_n =
307 Vector3::magnusForce(velocity_mps, state_.spin_radps,
308 config_.magnus_coefficient) *
309 config_.magnus_scale;
310 const Vector3 accel_mps2 =
311 gravity_mps2 + (drag_force_n + magnus_force_n) *
312 (1.0 / std::max(1e-9, ball_properties_.mass_kg));
313 return finiteVector(accel_mps2) ? accel_mps2 : gravity_mps2;
314 }
315
321 void resolveGroundContact(double dt_s) {
322 const double floor_z = config_.ground_height_m + ball_properties_.radius_m;
323 if (!std::isfinite(floor_z) || state_.position_m.z > floor_z) {
324 return;
325 }
326
327 state_.position_m.z = floor_z;
328 if (state_.velocity_mps.z < -config_.min_bounce_speed_mps) {
329 state_.velocity_mps.z =
330 -state_.velocity_mps.z *
331 std::clamp(ball_properties_.restitution, 0.0, 1.0);
332 } else {
333 state_.velocity_mps.z = 0.0;
334 }
335
336 const double planar_speed = state_.velocity_mps.planarSpeed();
337 if (planar_speed <= 1e-9) {
338 state_.velocity_mps.x = 0.0;
339 state_.velocity_mps.y = 0.0;
340 return;
341 }
342
343 const double decay =
344 std::max(0.0, 1.0 - config_.rolling_friction_per_s * dt_s);
345 state_.velocity_mps.x *= decay;
346 state_.velocity_mps.y *= decay;
347 state_.spin_radps = Vector3::zero();
348 }
349
350 Config config_{};
351 BallProperties ball_properties_{};
352 BallState state_{};
353
354 Vector3 carrier_position_m_{};
355 Vector3 carrier_velocity_mps_{};
356 Vector3 carry_offset_m_{};
357};
358
359} // namespace frcsim
void setBallProperties(const BallProperties &props)
Replaces ball properties after sanitization.
Definition ball_physics.hpp:108
void setCarrierPose(const Vector3 &carrier_position_m, const Vector3 &carrier_velocity_mps=Vector3::zero())
Updates carrier pose used when the ball is in held mode.
Definition ball_physics.hpp:127
const BallState & state() const
Returns current ball state.
Definition ball_physics.hpp:114
void setState(const BallState &state)
Replaces state and sanitizes non-finite values.
Definition ball_physics.hpp:117
void shoot(const Vector3 &muzzle_position_m, const Vector3 &muzzle_velocity_mps, const Vector3 &muzzle_spin_radps=Vector3::zero())
Places the ball at a muzzle pose and sets launch velocity/spin.
Definition ball_physics.hpp:168
void setConfig(const Config &config)
Replaces configuration after value sanitization and clamping.
Definition ball_physics.hpp:101
BallPhysicsSim3D(const Config &config, const BallProperties &ball_properties)
Definition ball_physics.hpp:92
bool requestPickup(const PickupRequest &pickup_request)
Attempts to pick up and hold the ball.
Definition ball_physics.hpp:138
BallPhysicsSim3D(const Config &config)
Definition ball_physics.hpp:89
const BallProperties & ballProperties() const
Returns active, sanitized ball properties.
Definition ball_physics.hpp:105
void release()
Releases the ball from held mode without changing velocity.
Definition ball_physics.hpp:160
void step(double dt_s)
Advances simulation by dt seconds.
Definition ball_physics.hpp:182
const Config & config() const
Returns active, sanitized configuration.
Definition ball_physics.hpp:98
Definition vector.hpp:13
Physical parameters for the ball body.
Definition ball_physics.hpp:49
double drag_coefficient
Definition ball_physics.hpp:55
double restitution
Definition ball_physics.hpp:59
double mass_kg
Definition ball_physics.hpp:51
double reference_area_m2
Definition ball_physics.hpp:57
double foam_compression_stiffness
Definition ball_physics.hpp:62
double radius_m
Definition ball_physics.hpp:53
Dynamic state advanced by step().
Definition ball_physics.hpp:66
bool held
Definition ball_physics.hpp:74
Vector3 velocity_mps
Definition ball_physics.hpp:70
Vector3 position_m
Definition ball_physics.hpp:68
Vector3 spin_radps
Definition ball_physics.hpp:72
Runtime physics environment parameters.
Definition ball_physics.hpp:25
double magnus_scale
Definition ball_physics.hpp:37
double rolling_friction_per_s
Definition ball_physics.hpp:41
double effective_gravity_scale
Definition ball_physics.hpp:29
double air_density_kgpm3
Definition ball_physics.hpp:31
double min_bounce_speed_mps
Definition ball_physics.hpp:43
Vector3 gravity_mps2
Definition ball_physics.hpp:27
double ground_height_m
Definition ball_physics.hpp:39
double drag_scale
Definition ball_physics.hpp:33
double magnus_coefficient
Definition ball_physics.hpp:35
double max_substep_s
Definition ball_physics.hpp:45
Request payload used to capture a ball into a carrier.
Definition ball_physics.hpp:78
Vector3 intake_position_m
Definition ball_physics.hpp:80
Vector3 carry_offset_m
Definition ball_physics.hpp:84
double capture_radius_m
Definition ball_physics.hpp:82
3D vector utility used throughout JSim physics.
Definition vector.hpp:22
double z
Z component.
Definition vector.hpp:28
static Vector3 dragForce(const Vector3 &v, double Cd, double A, double rho=1.225) noexcept
Computes the total drag force vector for a velocity and body.
Definition vector.hpp:337
static constexpr Vector3 zero() noexcept
Returns the zero vector.
Definition vector.hpp:360
double x
X component.
Definition vector.hpp:24
static Vector3 magnusForce(const Vector3 &velocity, const Vector3 &omega, double k=1e-4) noexcept
Computes the Magnus lift force omega x v scaled by k.
Definition vector.hpp:226
double y
Y component.
Definition vector.hpp:26