@@ -45,7 +45,10 @@ namespace custom_plugins
4545 alpha3_ = _sdf->Get <double >(" alpha3" , gaussian_noise_coeff_).first ;
4646 alpha4_ = _sdf->Get <double >(" alpha4" , gaussian_noise_coeff_).first ;
4747
48- slip_factor_ = _sdf->Get <double >(" slip_factor" , 0.02 ).first ;
48+ slip_factor_ = _sdf->Get <double >(" slip_factor" , 0.02 ).first ;
49+ block_ratio_threshold_ = _sdf->Get <double >(" block_ratio_threshold" , 0.15 ).first ;
50+ lateral_slip_ratio_ = _sdf->Get <double >(" lateral_slip_ratio" , 0.20 ).first ;
51+ blocked_hysteresis_ticks_ = _sdf->Get <int > (" blocked_hysteresis_ticks" , 3 ).first ;
4952
5053 gz_node_.Subscribe (gz_cmd_vel_topic_, &NoisyOdometryPlugin::OnCmdVel, this );
5154
@@ -123,18 +126,34 @@ namespace custom_plugins
123126 // In that case a small fraction of the commanded motion leaks into the
124127 // odometry, simulating wheel slip against the obstacle — exactly what real
125128 // encoders would observe when the wheels spin without moving the chassis.
126- const double trans_cmd = v_cmd * dt;
129+ const double trans_cmd = v_cmd * dt;
127130 const double delta_rot_cmd = w_cmd * dt;
128131
129- const bool is_blocked =
130- (std::abs (trans_cmd) > 1e-4 || std::abs (delta_rot_cmd) > 1e-4 ) &&
131- (trans_true < 1e-4 && std::abs (delta_yaw_true) < 1e-4 );
132+ // Ratio-based detection: blocked when a commanded axis achieves less than
133+ // block_ratio_threshold_ of the expected increment. More robust than a fixed
134+ // absolute threshold, which can misfire on slow robots or tiny commands.
135+ const bool is_commanded = std::abs (trans_cmd) > 1e-4 || std::abs (delta_rot_cmd) > 1e-4 ;
136+ const bool linear_stalled = std::abs (trans_cmd) > 1e-4 &&
137+ trans_true < block_ratio_threshold_ * std::abs (trans_cmd);
138+ const bool angular_stalled = std::abs (delta_rot_cmd) > 1e-4 &&
139+ std::abs (delta_yaw_true) < block_ratio_threshold_ * std::abs (delta_rot_cmd);
140+ const bool blocked_raw = is_commanded && (linear_stalled || angular_stalled);
141+
142+ // Hysteresis: require several consecutive blocked detections before
143+ // activating slip, and reset immediately when motion resumes.
144+ blocked_ticks_ = blocked_raw
145+ ? std::min (blocked_ticks_ + 1 , blocked_hysteresis_ticks_)
146+ : 0 ;
147+ const bool is_blocked = (blocked_ticks_ >= blocked_hysteresis_ticks_);
132148
133149 const double trans = is_blocked ? trans_cmd * slip_factor_ : trans_true;
134150 const double delta_yaw = is_blocked ? delta_rot_cmd * slip_factor_ : delta_yaw_true;
135151
152+ // When blocked, delta_body is near-zero simulator noise so atan2 returns a
153+ // random angle. Force rot1=0 so the slip integrates along the robot's
154+ // current heading, which is the only physically meaningful direction.
136155 const double rot1 =
137- (trans > 1e-9 ) ? NormalizeAngle (std::atan2 (delta_body.Y (), delta_body.X ())) : 0.0 ;
156+ (!is_blocked && trans > 1e-9 ) ? NormalizeAngle (std::atan2 (delta_body.Y (), delta_body.X ())) : 0.0 ;
138157 const double rot2 = NormalizeAngle (delta_yaw - rot1);
139158
140159 // Odometry motion model:
@@ -169,6 +188,19 @@ namespace custom_plugins
169188 noisy_pose_internal_.Pos ().Y () + trans_noisy * std::sin (heading_after_rot1));
170189 noisy_pose_internal_.Pos ().Z (current_true_pose.Pos ().Z ());
171190
191+ // When blocked, wheels spinning against a wall generate small random
192+ // perpendicular micro-displacements (lateral slip). The direction is
193+ // always 90° to the heading, so it cannot reverse the longitudinal slip.
194+ if (is_blocked && lateral_slip_ratio_ > 0.0 )
195+ {
196+ const double lateral = gz::math::Rand::DblNormal (
197+ 0.0 , std::abs (trans) * lateral_slip_ratio_);
198+ noisy_pose_internal_.Pos ().X (
199+ noisy_pose_internal_.Pos ().X () - lateral * std::sin (heading_after_rot1));
200+ noisy_pose_internal_.Pos ().Y (
201+ noisy_pose_internal_.Pos ().Y () + lateral * std::cos (heading_after_rot1));
202+ }
203+
172204 noisy_pose_internal_.Rot () = gz::math::Quaterniond (0.0 , 0.0 , new_noisy_yaw);
173205 noisy_pose_internal_.Rot ().Normalize ();
174206
0 commit comments