PlanInterpolator.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018-2019, CNRS-UM LIRMM
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25  * POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <mc_rtc/gui.h>
29 
30 #include <lipm_walking/Contact.h>
33 
34 namespace lipm_walking
35 {
36 
37 inline double floorn(double x, int n)
38 {
39  return floor(pow(10, n) * x) / pow(10, n);
40 }
41 
43 {
44  using namespace mc_rtc::gui;
45 
46  gui_->addElement(
47  {"Markers", "Footsteps", "PlanInterpolator"},
48  Trajectory("Support_Path", [this]() -> const std::vector<Eigen::Vector3d> & { return supportPathDisplay_; }),
49  XYTheta(
50  "World target [m, rad]",
51  [this]() -> Eigen::VectorXd
52  {
53  Eigen::Vector3d targetLocal;
54  targetLocal << targetPose_.pos(), 0.;
55  Eigen::Matrix3d rotLocal = mc_rbdyn::rpyToMat({0., 0., targetPose_.theta});
56  sva::PTransformd targetWorld = sva::PTransformd(rotLocal, targetLocal) * worldReference_;
57  double thetaWorld = mc_rbdyn::rpyFromMat(targetWorld.rotation()).z();
58  Eigen::VectorXd vec(4);
59  vec << floorn(targetWorld.translation().x(), 4), floorn(targetWorld.translation().y(), 4),
60  floorn(thetaWorld, 4), worldReference_.translation().z();
61  return vec;
62  },
63  [this](const Eigen::VectorXd & desired) { updateWorldTarget_(desired.head<3>()); }));
64 
65  gui_->addElement(
66  {"Walking", "Footsteps", "PlanInterpolator"},
67  ComboInput(
68  "Gait", {"Walk", "Shuffle", "Turn"}, [this]() { return gait(); },
69  [this](const std::string & dir) { gait(dir); }),
70  ComboInput(
71  "Lead foot", {"Left", "Right"},
72  [this]() -> std::string { return (startWithRightFootstep_) ? "Right" : "Left"; },
73  [this](const std::string & footName)
74  {
75  startWithRightFootstep_ = (footName == "Right");
76  run();
77  }),
78  NumberInput(
79  "Desired step angle [deg]", [this]() { return desiredStepAngle_ * 180. / M_PI; },
80  [this](double angleDeg)
81  {
82  desiredStepAngle_ = clamp(angleDeg, 0., IN_PLACE_MAX_STEP_ANGLE) * M_PI / 180.;
83  run();
84  }),
85  NumberInput(
86  "Desired step length [m]", [this]() { return desiredStepLength_; },
87  [this](double length)
88  {
89  bool isLateral = (customPlan_.name == "custom_lateral");
90  double maxLength = (isLateral) ? MAX_LATERAL_STEP_LENGTH : MAX_SAGITTAL_STEP_LENGTH;
91  desiredStepLength_ = clamp(length, MIN_STEP_LENGTH, maxLength);
92  run();
93  }),
94  NumberInput(
95  "Extra step width [m]", [this]() { return extraStepWidth_; },
96  [this](double width)
97  {
98  extraStepWidth_ = clamp(width, 0., MAX_EXTRA_STEP_WIDTH);
99  run();
100  }),
101  NumberInput(
102  "Initial tangent [deg]", [this]() { return -initPose_.theta * 180. / M_PI; },
103  [this](double angle)
104  {
105  initPose_.theta = clamp(-angle * M_PI / 180., -2 * M_PI, 2 * M_PI);
106  run();
107  }),
108  NumberInput(
109  "Scale initial tangent", [this]() { return supportPath_.extraInitVelScaling(); },
110  [this](double s)
111  {
112  supportPath_.extraInitVelScaling(s);
113  run();
114  }),
115  NumberInput(
116  "Scale target tangent", [this]() { return supportPath_.extraTargetVelScaling(); },
117  [this](double s)
118  {
119  supportPath_.extraTargetVelScaling(s);
120  run();
121  }),
122  ArrayInput(
123  "Walk target from current", {"x [m]", "y [m]", "theta [deg]"},
124  [this]() { return targetPose_.vectorDegrees(); },
125  [this](const Eigen::Vector3d & desired)
126  {
127  updateLocalTarget_(SE2d(desired.x(), desired.y(), desired.z() * M_PI / 180.));
128  run();
129  }),
130  Label("Number of steps", [this]() { return nbFootsteps_; }),
131  Label("Step angle [deg]", [this]() { return std::round(stepAngle_ * 180. / M_PI * 10.) / 10.; }),
132  Label("Step length [m]", [this]() { return std::round(stepLength_ * 1000.) / 1000.; }),
133  Label("Total length [m]", [this]() { return std::round(supportPath_.arcLength(0., 1.) * 1000.) / 1000.; }));
134 }
135 
137 {
138  gui_->removeCategory({"Walking", "Footsteps", "PlanInterpolator"});
139  gui_->removeCategory({"Markers", "Footsteps", "PlanInterpolator"});
140  gui_->removeCategory({"Walking", "Footsteps", "PlanInterpolator", "Online"});
141 }
142 
143 void PlanInterpolator::updateWorldTarget_(const Eigen::Vector3d & desired)
144 {
145  Eigen::Vector3d posWorld;
146  posWorld << desired(0), desired(1), worldReference_.translation().z();
147  Eigen::Matrix3d rotWorld = mc_rbdyn::rpyToMat({0., 0., desired(2)});
148  sva::PTransformd X_desired(rotWorld, posWorld);
149 
150  sva::PTransformd targetLocal = X_desired * worldReference_.inv();
151  double thetaLocal = mc_rbdyn::rpyFromMat(targetLocal.rotation()).z();
152  const Eigen::Vector3d & posLocal = targetLocal.translation();
153  updateLocalTarget_(SE2d(posLocal.x(), posLocal.y(), thetaLocal));
154  run();
155 }
156 
158 {
159  targetPose_.x = floorn(clamp(target.pos().x(), -5., 5.), 4);
160  targetPose_.y = floorn(clamp(target.pos().y(), -5., 5.), 4);
161  targetPose_.theta = floorn(clamp(target.theta, -M_PI, M_PI), 4);
162  suggestGait();
163 }
164 
166 {
167  double absX = std::abs(targetPose_.x);
168  double absY = std::abs(targetPose_.y);
169  if(absX > 2. * absY)
170  {
171  gait_ = Gait::Walk;
172  }
173  else // (absY >= 0.5 * absX)
174  {
175  gait_ = Gait::Shuffle;
176  }
177 }
178 
179 void PlanInterpolator::configure(const mc_rtc::Configuration & plans)
180 {
181  plans_ = plans;
182  for(auto name : {"custom_backward", "custom_forward", "custom_lateral", "external"})
183  {
184  if(!plans_.has(name))
185  {
186  mc_rtc::log::error("[PlanInterpolator] Configuration lacks \"{}\" plan, skipping...", name);
187  continue;
188  }
189  customPlan_ = plans_(name);
190  const Contact & leftFoot = customPlan_.contacts()[1];
191  const Contact & rightFoot = customPlan_.contacts()[0];
192  if(leftFoot.surfaceName != "LeftFootCenter" || rightFoot.surfaceName != "RightFootCenter")
193  {
194  mc_rtc::log::error("Wrong initial foothold order in \"{}\" plan", name);
195  }
196  sva::PTransformd X_0_mid = sva::interpolate(leftFoot.pose, rightFoot.pose, 0.5);
197  if(!X_0_mid.rotation().isIdentity() || X_0_mid.translation().norm() > 1e-4)
198  {
199  mc_rtc::log::error("Invalid X_0_mid(\"{}\") = {}", name, X_0_mid);
200  }
201  }
202 
203  // start with custom forward plan
204  desiredStepLength_ = plans_("custom_forward")("step_length");
205  customPlan_ = plans_("custom_forward");
206  customPlan_.name = "custom_forward";
207  run();
208 }
209 
211 {
212  if(targetPose_.pos().norm() < 2e-3)
213  {
214  if(gait_ != Gait::Turn)
215  {
216  extraStepWidth_ = IN_PLACE_EXTRA_STEP_WIDTH;
217  gait_ = Gait::Turn;
218  }
219  }
220  else if(gait_ == Gait::Turn)
221  {
222  extraStepWidth_ = DEFAULT_EXTRA_STEP_WIDTH;
223  gait_ = Gait::Shuffle;
224  }
225  if(gait_ == Gait::Walk)
226  {
227  double dx = targetPose_.x;
228  if(dx < 0. && customPlan_.name != "custom_backward")
229  {
230  desiredStepLength_ = plans_("custom_backward")("step_length");
231  restoreDefaults();
232  }
233  else if(dx >= 0. && customPlan_.name != "custom_forward")
234  {
235  desiredStepLength_ = plans_("custom_forward")("step_length");
236  restoreDefaults();
237  }
238  runWalking_();
239  }
240  else if(gait_ == Gait::Shuffle)
241  {
242  if(customPlan_.name != "custom_lateral")
243  {
244  desiredStepLength_ = plans_("custom_lateral")("step_length");
245  restoreDefaults();
246  }
247  runShuffling_();
248  }
249  else // (gait_ == Gait::Turn)
250  {
251  if(customPlan_.name != "custom_lateral")
252  {
253  desiredStepLength_ = plans_("custom_lateral")("step_length");
254  extraStepWidth_ = IN_PLACE_EXTRA_STEP_WIDTH;
255  }
256  runTurning_();
257  }
258  hasRun_ = true;
259 }
260 
261 void PlanInterpolator::runWalking_()
262 {
263  bool goingBackward = (targetPose_.pos().x() < 0.);
264  if(goingBackward)
265  {
266  customPlan_ = plans_("custom_backward");
267  customPlan_.name = "custom_backward";
268  supportPath_.reset(initPose_.pos(), -initPose_.ori(), targetPose_.pos(), -targetPose_.ori());
269  lastBackwardTarget_ = targetPose_;
270  }
271  else // going forward
272  {
273  customPlan_ = plans_("custom_forward");
274  customPlan_.name = "custom_forward";
275  supportPath_.reset(initPose_.pos(), initPose_.ori(), targetPose_.pos(), targetPose_.ori());
276  lastForwardTarget_ = targetPose_;
277  }
278  if(!startWithRightFootstep_)
279  {
280  customPlan_.swapFirstTwoContacts();
281  }
282 
283  double T = customPlan_.doubleSupportDuration() + customPlan_.singleSupportDuration();
284  double totalLength = supportPath_.arcLength(0., 1.);
285  unsigned nbInnerSteps = static_cast<unsigned>(std::max(0., std::round(totalLength / desiredStepLength_) - 1));
286  double maxStepLength = 1.1 * desiredStepLength_;
287  if(totalLength / (nbInnerSteps + 1) > maxStepLength)
288  {
289  nbInnerSteps++;
290  }
291  double innerStepLength = totalLength / (nbInnerSteps + 1);
292  double innerVel = (goingBackward ? -1. : +1.) * innerStepLength / T;
293  double outerStepLength;
294  double outerVel;
295  if(nbInnerSteps > 0)
296  {
297  outerStepLength = 0.5 * innerStepLength;
298  outerVel = 0.5 * innerVel;
299  stepAngle_ = 0.;
300  stepLength_ = innerStepLength;
301  }
302  else
303  {
304  outerStepLength = totalLength;
305  outerVel = innerVel;
306  stepAngle_ = 0.;
307  stepLength_ = totalLength;
308  }
309 
310  nbFootsteps_ = 0;
311  bool isRightFootstep = startWithRightFootstep_;
312  unsigned nbFinalSteps = 0;
313  double freeLength = outerStepLength;
314  double curVel = outerVel;
315  while(nbFinalSteps < 2)
316  {
317  double length = freeLength;
318  double curStepWidth = stepWidth_ + extraStepWidth_;
319  if(length > totalLength - outerStepLength - 1e-3)
320  {
321  curVel = outerVel;
322  }
323  if(length >= totalLength - 1e-3)
324  {
325  curStepWidth = stepWidth_;
326  curVel = 0.;
327  length = totalLength;
328  nbFinalSteps++;
329  }
330 
331  double t = supportPath_.arcLengthInverse(0., length);
332  Eigen::Vector2d supportPoint = supportPath_.pos(t);
333  Eigen::Vector2d supportTangent = supportPath_.tangent(t);
334  if(goingBackward)
335  {
336  supportTangent *= -1.;
337  }
338  Eigen::Vector2d supportNormal = {-supportTangent.y(), supportTangent.x()};
339  double sign = (isRightFootstep) ? -1. : +1.;
340  double dy = 0.5 * sign * curStepWidth;
341  double theta = atan2(supportTangent.y(), supportTangent.x());
342  Eigen::Vector2d p = supportPoint + dy * supportNormal;
343  SE2d stepPose = {p.x(), p.y(), theta};
344 
345  Contact contact;
346  contact.pose = stepPose.asPTransform();
347  contact.refVel = curVel * Eigen::Vector3d{supportTangent.x(), supportTangent.y(), 0.};
348  contact.surfaceName = (isRightFootstep) ? "RightFootCenter" : "LeftFootCenter";
349  customPlan_.appendContact(contact);
350  isRightFootstep = !isRightFootstep;
351  nbFootsteps_++;
352  freeLength += innerStepLength;
353  curVel = innerVel;
354  }
355 
356  if(nbInnerSteps > 0)
357  {
358  if(nbFootsteps_ - 3 != nbInnerSteps)
359  {
360  mc_rtc::log::error("[PlanInterpolator] Footstep count check failed");
361  }
362  if(std::abs(2 * outerStepLength + nbInnerSteps * innerStepLength - totalLength) > 1e-4)
363  {
364  mc_rtc::log::error("[PlanInterpolator] Total length check failed");
365  }
366  }
367  else // (nbInnerSteps == 0)
368  {
369  if(nbFootsteps_ != 2)
370  {
371  mc_rtc::log::error("[PlanInterpolator] Footstep count check failed");
372  }
373  if(std::abs(outerStepLength - totalLength) > 1e-4)
374  {
375  mc_rtc::log::error("[PlanInterpolator] Total length check failed");
376  }
377  }
378 }
379 
380 void PlanInterpolator::runShuffling_()
381 {
382  customPlan_ = plans_("custom_lateral");
383  customPlan_.name = "custom_lateral";
384  lastLateralTarget_ = targetPose_;
385  supportPath_.reset(initPose_.pos(), initPose_.ori(), targetPose_.pos(), targetPose_.ori());
386 
387  std::string leadFootSurfaceName, followFootSurfaceName;
388  bool goingToTheRight = (targetPose_.pos().y() < 0.);
389  if(goingToTheRight)
390  {
391  leadFootSurfaceName = "RightFootCenter";
392  followFootSurfaceName = "LeftFootCenter";
393  startWithRightFootstep_ = true;
394  }
395  else // first footstep is left foot
396  {
397  leadFootSurfaceName = "LeftFootCenter";
398  followFootSurfaceName = "RightFootCenter";
399  customPlan_.swapFirstTwoContacts();
400  startWithRightFootstep_ = false;
401  }
402 
403  double totalLength = supportPath_.arcLength(0., 1.);
404  double nbSteps = std::max(1., std::round(totalLength / desiredStepLength_));
405  double maxStepLength = 1.1 * desiredStepLength_;
406  if(totalLength / nbSteps > maxStepLength)
407  {
408  nbSteps++;
409  }
410  double stepLength = totalLength / nbSteps;
411 
412  nbFootsteps_ = 0;
413  for(unsigned i = 1; i <= nbSteps; i++)
414  {
415  double curStepWidth = stepWidth_;
416  if(i < nbSteps)
417  {
418  curStepWidth += extraStepWidth_;
419  }
420  double t = supportPath_.arcLengthInverse(0., i * stepLength);
421  Eigen::Vector2d supportPoint = supportPath_.pos(t);
422  double theta = t * targetPose_.theta;
423  Eigen::Vector2d lateralVec = {-std::sin(theta), std::cos(theta)};
424  lateralVec *= (goingToTheRight ? -1. : +1.) * 0.5 * curStepWidth;
425  Eigen::Vector2d leadPos = supportPoint + lateralVec;
426  Eigen::Vector2d followPos = supportPoint - lateralVec;
427  SE2d leadStepPose = {leadPos.x(), leadPos.y(), theta};
428  SE2d followStepPose = {followPos.x(), followPos.y(), theta};
429 
430  Contact followStep, leadStep;
431  followStep.pose = followStepPose.asPTransform();
432  followStep.surfaceName = followFootSurfaceName;
433  leadStep.pose = leadStepPose.asPTransform();
434  leadStep.surfaceName = leadFootSurfaceName;
435  customPlan_.appendContact(leadStep);
436  customPlan_.appendContact(followStep);
437  nbFootsteps_ += 2;
438  }
439 
440  stepAngle_ = 0.;
441  stepLength_ = stepLength;
442 }
443 
444 void PlanInterpolator::runTurning_()
445 {
446  lastLateralTarget_ = targetPose_;
447  customPlan_ = plans_("custom_lateral");
448  customPlan_.name = "custom_lateral";
449  supportPath_.reset(initPose_.pos(), initPose_.ori(), targetPose_.pos(), targetPose_.ori());
450 
451  std::string leadFootSurfaceName, followFootSurfaceName;
452  bool goingToTheRight = (targetPose_.theta < 0.);
453  if(goingToTheRight)
454  {
455  leadFootSurfaceName = "RightFootCenter";
456  followFootSurfaceName = "LeftFootCenter";
457  startWithRightFootstep_ = true;
458  }
459  else // first footstep is left foot
460  {
461  leadFootSurfaceName = "LeftFootCenter";
462  followFootSurfaceName = "RightFootCenter";
463  customPlan_.swapFirstTwoContacts();
464  startWithRightFootstep_ = false;
465  }
466 
467  double totalAngle = std::abs(targetPose_.theta);
468  double nbSteps = std::max(1., std::round(totalAngle / desiredStepAngle_));
469  double maxStepAngle = 1.0 * desiredStepAngle_;
470  if(totalAngle / nbSteps > maxStepAngle)
471  {
472  nbSteps++;
473  }
474  double stepAngle = totalAngle / nbSteps;
475 
476  nbFootsteps_ = 0;
477  for(unsigned i = 1; i <= nbSteps; i++)
478  {
479  double curStepWidth = stepWidth_;
480  if(i < nbSteps)
481  {
482  curStepWidth += extraStepWidth_;
483  }
484  double theta = (goingToTheRight ? -1. : +1.) * i * stepAngle;
485  Eigen::Vector2d supportPoint = targetPose_.pos();
486  Eigen::Vector2d lateralVec = {-std::sin(theta), std::cos(theta)};
487  lateralVec *= (goingToTheRight ? -1. : +1.) * 0.5 * curStepWidth;
488  Eigen::Vector2d leadPos = supportPoint + lateralVec;
489  Eigen::Vector2d followPos = supportPoint - lateralVec;
490  SE2d leadStepPose = {leadPos.x(), leadPos.y(), theta};
491  SE2d followStepPose = {followPos.x(), followPos.y(), theta};
492 
493  Contact followStep, leadStep;
494  followStep.pose = followStepPose.asPTransform();
495  followStep.surfaceName = followFootSurfaceName;
496  leadStep.pose = leadStepPose.asPTransform();
497  leadStep.surfaceName = leadFootSurfaceName;
498  customPlan_.appendContact(leadStep);
499  customPlan_.appendContact(followStep);
500  nbFootsteps_ += 2;
501  }
502 
503  stepAngle_ = stepAngle;
504  stepLength_ = 0.;
505 }
506 
507 void PlanInterpolator::updateSupportPath(const sva::PTransformd & X_0_lf, const sva::PTransformd & X_0_rf)
508 {
509  constexpr double PATH_STEP = 0.05;
510  sva::PTransformd X_0_mid = sva::interpolate(X_0_lf, X_0_rf, 0.5);
511  supportPathDisplay_.clear();
512  for(double s = 0.; s <= 1.; s += PATH_STEP)
513  {
514  Eigen::Vector2d p = supportPath_.pos(s);
515  SE2d pose = {p.x(), p.y(), /* theta = */ 0.};
516  sva::PTransformd X_0_p = pose * X_0_mid;
517  supportPathDisplay_.push_back(X_0_p.translation());
518  }
519 }
520 
521 void PlanInterpolator::restoreDefaults()
522 {
523  extraStepWidth_ = DEFAULT_EXTRA_STEP_WIDTH;
524  initPose_.theta = 0.; // [rad]
525  startWithRightFootstep_ = true;
526  supportPath_.extraInitVelScaling(1.);
527  supportPath_.extraTargetVelScaling(1.);
528 }
529 
530 } // namespace lipm_walking
double floorn(double x, int n)
double clamp(double v, double vMin, double vMax)
Definition: clamp.h:47
std::string surfaceName
Definition: Contact.h:313
sva::PTransformd pose
Definition: Contact.h:314
double doubleSupportDuration() const
Definition: FootstepPlan.h:154
double singleSupportDuration() const
Definition: FootstepPlan.h:294
const std::vector< Contact > & contacts() const
Definition: FootstepPlan.h:146
void appendContact(Contact step)
Definition: FootstepPlan.h:122
void configure(const mc_rtc::Configuration &config)
static constexpr double DEFAULT_EXTRA_STEP_WIDTH
void updateLocalTarget_(const SE2d &target)
static constexpr double IN_PLACE_MAX_STEP_ANGLE
static constexpr double MIN_STEP_LENGTH
std::string gait() const noexcept
static constexpr double MAX_SAGITTAL_STEP_LENGTH
static constexpr double MAX_LATERAL_STEP_LENGTH
static constexpr double MAX_EXTRA_STEP_WIDTH
static constexpr double IN_PLACE_EXTRA_STEP_WIDTH
void updateSupportPath(const sva::PTransformd &X_0_lf, const sva::PTransformd &X_0_rf)
void updateWorldTarget_(const Eigen::Vector3d &desired)
lipm_walking::utils::SE2d SE2d
Eigen::Vector2d ori() const
Definition: SE2d.h:109
Eigen::Vector2d pos() const
Definition: SE2d.h:99
T pos(double t) const
Definition: polynomials.h:177
double extraInitVelScaling()
Definition: polynomials.h:383
void reset() override
Definition: polynomials.h:353
double extraTargetVelScaling()
Definition: polynomials.h:399
T tangent(double t) const
Definition: polynomials.h:69
double arcLengthInverse(double t_start, double length, double t_guess=-1.) const
Definition: polynomials.h:128
double arcLength(double t_start, double t_end) const
Definition: polynomials.h:93