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 <lipm_walking/Contact.h>
31 
32 namespace lipm_walking
33 {
34 
35 inline double floorn(double x, int n)
36 {
37  return floor(pow(10, n) * x) / pow(10, n);
38 }
39 
41 {
42  using namespace mc_rtc::gui;
43 
44  gui_->addElement(
45  {"Markers", "Footsteps"},
46  Trajectory("Support_Path", [this]() -> const std::vector<Eigen::Vector3d> & { return supportPathDisplay_; }),
47  XYTheta("World target [m, rad]",
48  [this]() -> Eigen::VectorXd {
49  Eigen::Vector3d targetLocal;
50  targetLocal << targetPose_.pos(), 0.;
51  Eigen::Matrix3d rotLocal = mc_rbdyn::rpyToMat({0., 0., targetPose_.theta});
52  sva::PTransformd targetWorld = sva::PTransformd(rotLocal, targetLocal) * worldReference_;
53  double thetaWorld = mc_rbdyn::rpyFromMat(targetWorld.rotation()).z();
54  Eigen::VectorXd vec(4);
55  vec << floorn(targetWorld.translation().x(), 4), floorn(targetWorld.translation().y(), 4),
56  floorn(thetaWorld, 4), worldReference_.translation().z();
57  return vec;
58  },
59  [this](const Eigen::VectorXd & desired) { updateWorldTarget_(desired.head<3>()); }));
60 
61  gui_->addElement(
62  {"Walking", "Footsteps"}, Label("Plan name", [this]() { return customPlan_.name; }),
63  ComboInput("Gait", {"Walk", "Shuffle", "Turn"}, [this]() { return gait(); },
64  [this](const std::string & dir) { gait(dir); }),
65  ComboInput("Lead foot", {"Left", "Right"}, [this]() { return (startWithRightFootstep_) ? "Right" : "Left"; },
66  [this](const std::string & footName) {
67  startWithRightFootstep_ = (footName == "Right");
68  run();
69  }),
70  NumberInput("Desired step angle [deg]", [this]() { return desiredStepAngle_ * 180. / M_PI; },
71  [this](double angleDeg) {
72  desiredStepAngle_ = clamp(angleDeg, 0., IN_PLACE_MAX_STEP_ANGLE) * M_PI / 180.;
73  run();
74  }),
75  NumberInput("Desired step length [m]", [this]() { return desiredStepLength_; },
76  [this](double length) {
77  bool isLateral = (customPlan_.name == "custom_lateral");
78  double maxLength = (isLateral) ? MAX_LATERAL_STEP_LENGTH : MAX_SAGITTAL_STEP_LENGTH;
79  desiredStepLength_ = clamp(length, MIN_STEP_LENGTH, maxLength);
80  run();
81  }),
82  NumberInput("Extra step width [m]", [this]() { return extraStepWidth_; },
83  [this](double width) {
84  extraStepWidth_ = clamp(width, 0., MAX_EXTRA_STEP_WIDTH);
85  run();
86  }),
87  NumberInput("Initial tangent [deg]", [this]() { return -initPose_.theta * 180. / M_PI; },
88  [this](double angle) {
89  initPose_.theta = clamp(-angle * M_PI / 180., -2 * M_PI, 2 * M_PI);
90  run();
91  }),
92  NumberInput("Scale initial tangent", [this]() { return supportPath_.extraInitVelScaling(); },
93  [this](double s) {
94  supportPath_.extraInitVelScaling(s);
95  run();
96  }),
97  NumberInput("Scale target tangent", [this]() { return supportPath_.extraTargetVelScaling(); },
98  [this](double s) {
99  supportPath_.extraTargetVelScaling(s);
100  run();
101  }),
102  ArrayInput("Walk target from current", {"x [m]", "y [m]", "theta [deg]"},
103  [this]() { return targetPose_.vectorDegrees(); },
104  [this](const Eigen::Vector3d & desired) {
105  updateLocalTarget_(SE2d(desired.x(), desired.y(), desired.z() * M_PI / 180.));
106  run();
107  }),
108  Label("Number of steps", [this]() { return nbFootsteps_; }),
109  Label("Step angle [deg]", [this]() { return std::round(stepAngle_ * 180. / M_PI * 10.) / 10.; }),
110  Label("Step length [m]", [this]() { return std::round(stepLength_ * 1000.) / 1000.; }),
111  Label("Total length [m]", [this]() { return std::round(supportPath_.arcLength(0., 1.) * 1000.) / 1000.; }));
112 }
113 
115 {
116  gui_->removeCategory({"Walking", "Footsteps"});
117  gui_->removeElement({"Markers", "Footsteps"}, "Support_Path");
118  gui_->removeElement({"Markers", "Footsteps"}, "World target [m, rad]");
119 }
120 
121 void PlanInterpolator::updateWorldTarget_(const Eigen::Vector3d & desired)
122 {
123  Eigen::Vector3d posWorld;
124  posWorld << desired(0), desired(1), worldReference_.translation().z();
125  Eigen::Matrix3d rotWorld = mc_rbdyn::rpyToMat({0., 0., desired(2)});
126  sva::PTransformd X_desired(rotWorld, posWorld);
127 
128  sva::PTransformd targetLocal = X_desired * worldReference_.inv();
129  double thetaLocal = mc_rbdyn::rpyFromMat(targetLocal.rotation()).z();
130  const Eigen::Vector3d & posLocal = targetLocal.translation();
131  updateLocalTarget_(SE2d(posLocal.x(), posLocal.y(), thetaLocal));
132  run();
133 }
134 
135 void PlanInterpolator::updateLocalTarget_(const SE2d & target)
136 {
137  targetPose_.x = floorn(clamp(target.pos().x(), -5., 5.), 4);
138  targetPose_.y = floorn(clamp(target.pos().y(), -5., 5.), 4);
139  targetPose_.theta = floorn(clamp(target.theta, -M_PI, M_PI), 4);
140  suggestGait();
141 }
142 
144 {
145  double absX = std::abs(targetPose_.x);
146  double absY = std::abs(targetPose_.y);
147  if(absX > 2. * absY)
148  {
149  gait_ = Gait::Walk;
150  }
151  else // (absY >= 0.5 * absX)
152  {
153  gait_ = Gait::Shuffle;
154  }
155 }
156 
157 void PlanInterpolator::configure(const mc_rtc::Configuration & plans)
158 {
159  plans_ = plans;
160  for(auto name : {"custom_backward", "custom_forward", "custom_lateral"})
161  {
162  if(!plans_.has(name))
163  {
164  LOG_ERROR("[PlanInterpolator] Configuration lacks \"" << name << "\" plan, skipping...");
165  continue;
166  }
167  customPlan_ = plans_(name);
168  const Contact & leftFoot = customPlan_.contacts()[1];
169  const Contact & rightFoot = customPlan_.contacts()[0];
170  if(leftFoot.surfaceName != "LeftFootCenter" || rightFoot.surfaceName != "RightFootCenter")
171  {
172  LOG_ERROR("Wrong initial foothold order in \"" << name << "\" plan");
173  }
174  sva::PTransformd X_0_mid = sva::interpolate(leftFoot.pose, rightFoot.pose, 0.5);
175  if(!X_0_mid.rotation().isIdentity() || X_0_mid.translation().norm() > 1e-4)
176  {
177  LOG_ERROR("Invalid X_0_mid(\"" << name << "\") = " << X_0_mid);
178  }
179  }
180 
181  // start with custom forward plan
182  desiredStepLength_ = plans_("custom_forward")("step_length");
183  customPlan_ = plans_("custom_forward");
184  customPlan_.name = "custom_forward";
185  run();
186 }
187 
189 {
190  if(targetPose_.pos().norm() < 2e-3)
191  {
192  if(gait_ != Gait::Turn)
193  {
194  extraStepWidth_ = IN_PLACE_EXTRA_STEP_WIDTH;
195  gait_ = Gait::Turn;
196  }
197  }
198  else if(gait_ == Gait::Turn)
199  {
200  extraStepWidth_ = DEFAULT_EXTRA_STEP_WIDTH;
201  gait_ = Gait::Shuffle;
202  }
203  if(gait_ == Gait::Walk)
204  {
205  double dx = targetPose_.x;
206  if(dx < 0. && customPlan_.name != "custom_backward")
207  {
208  desiredStepLength_ = plans_("custom_backward")("step_length");
209  restoreDefaults();
210  }
211  else if(dx >= 0. && customPlan_.name != "custom_forward")
212  {
213  desiredStepLength_ = plans_("custom_forward")("step_length");
214  restoreDefaults();
215  }
216  runWalking_();
217  }
218  else if(gait_ == Gait::Shuffle)
219  {
220  if(customPlan_.name != "custom_lateral")
221  {
222  desiredStepLength_ = plans_("custom_lateral")("step_length");
223  restoreDefaults();
224  }
225  runShuffling_();
226  }
227  else // (gait_ == Gait::Turn)
228  {
229  if(customPlan_.name != "custom_lateral")
230  {
231  desiredStepLength_ = plans_("custom_lateral")("step_length");
232  extraStepWidth_ = IN_PLACE_EXTRA_STEP_WIDTH;
233  }
234  runTurning_();
235  }
236  nbIter++;
237 }
238 
239 void PlanInterpolator::runWalking_()
240 {
241  bool goingBackward = (targetPose_.pos().x() < 0.);
242  if(goingBackward)
243  {
244  customPlan_ = plans_("custom_backward");
245  customPlan_.name = "custom_backward";
246  supportPath_.reset(initPose_.pos(), -initPose_.ori(), targetPose_.pos(), -targetPose_.ori());
247  lastBackwardTarget_ = targetPose_;
248  }
249  else // going forward
250  {
251  customPlan_ = plans_("custom_forward");
252  customPlan_.name = "custom_forward";
253  supportPath_.reset(initPose_.pos(), initPose_.ori(), targetPose_.pos(), targetPose_.ori());
254  lastForwardTarget_ = targetPose_;
255  }
256  if(!startWithRightFootstep_)
257  {
258  customPlan_.swapFirstTwoContacts();
259  }
260 
261  double T = customPlan_.doubleSupportDuration() + customPlan_.singleSupportDuration();
262  double totalLength = supportPath_.arcLength(0., 1.);
263  unsigned nbInnerSteps = static_cast<unsigned>(std::max(0., std::round(totalLength / desiredStepLength_) - 1));
264  double maxStepLength = 1.1 * desiredStepLength_;
265  if(totalLength / (nbInnerSteps + 1) > maxStepLength)
266  {
267  nbInnerSteps++;
268  }
269  double innerStepLength = totalLength / (nbInnerSteps + 1);
270  double innerVel = (goingBackward ? -1. : +1.) * innerStepLength / T;
271  double outerStepLength;
272  double outerVel;
273  if(nbInnerSteps > 0)
274  {
275  outerStepLength = 0.5 * innerStepLength;
276  outerVel = 0.5 * innerVel;
277  stepAngle_ = 0.;
278  stepLength_ = innerStepLength;
279  }
280  else
281  {
282  outerStepLength = totalLength;
283  outerVel = innerVel;
284  stepAngle_ = 0.;
285  stepLength_ = totalLength;
286  }
287 
288  nbFootsteps_ = 0;
289  bool isRightFootstep = startWithRightFootstep_;
290  unsigned nbFinalSteps = 0;
291  double freeLength = outerStepLength;
292  double curVel = outerVel;
293  while(nbFinalSteps < 2)
294  {
295  double length = freeLength;
296  double curStepWidth = stepWidth_ + extraStepWidth_;
297  if(length > totalLength - outerStepLength - 1e-3)
298  {
299  curVel = outerVel;
300  }
301  if(length >= totalLength - 1e-3)
302  {
303  curStepWidth = stepWidth_;
304  curVel = 0.;
305  length = totalLength;
306  nbFinalSteps++;
307  }
308 
309  double t = supportPath_.arcLengthInverse(0., length);
310  Eigen::Vector2d supportPoint = supportPath_.pos(t);
311  Eigen::Vector2d supportTangent = supportPath_.tangent(t);
312  if(goingBackward)
313  {
314  supportTangent *= -1.;
315  }
316  Eigen::Vector2d supportNormal = {-supportTangent.y(), supportTangent.x()};
317  double sign = (isRightFootstep) ? -1. : +1.;
318  double dy = 0.5 * sign * curStepWidth;
319  double theta = atan2(supportTangent.y(), supportTangent.x());
320  Eigen::Vector2d p = supportPoint + dy * supportNormal;
321  SE2d stepPose = {p.x(), p.y(), theta};
322 
323  Contact contact;
324  contact.pose = stepPose.asPTransform();
325  contact.refVel = curVel * Eigen::Vector3d{supportTangent.x(), supportTangent.y(), 0.};
326  contact.surfaceName = (isRightFootstep) ? "RightFootCenter" : "LeftFootCenter";
327  customPlan_.appendContact(contact);
328  isRightFootstep = !isRightFootstep;
329  nbFootsteps_++;
330  freeLength += innerStepLength;
331  curVel = innerVel;
332  }
333 
334  if(nbInnerSteps > 0)
335  {
336  if(nbFootsteps_ - 3 != nbInnerSteps)
337  {
338  LOG_ERROR("[PlanInterpolator] Footstep count check failed");
339  }
340  if(std::abs(2 * outerStepLength + nbInnerSteps * innerStepLength - totalLength) > 1e-4)
341  {
342  LOG_ERROR("[PlanInterpolator] Total length check failed");
343  }
344  }
345  else // (nbInnerSteps == 0)
346  {
347  if(nbFootsteps_ != 2)
348  {
349  LOG_ERROR("[PlanInterpolator] Footstep count check failed");
350  }
351  if(std::abs(outerStepLength - totalLength) > 1e-4)
352  {
353  LOG_ERROR("[PlanInterpolator] Total length check failed");
354  }
355  }
356 }
357 
358 void PlanInterpolator::runShuffling_()
359 {
360  customPlan_ = plans_("custom_lateral");
361  customPlan_.name = "custom_lateral";
362  lastLateralTarget_ = targetPose_;
363  supportPath_.reset(initPose_.pos(), initPose_.ori(), targetPose_.pos(), targetPose_.ori());
364 
365  std::string leadFootSurfaceName, followFootSurfaceName;
366  bool goingToTheRight = (targetPose_.pos().y() < 0.);
367  if(goingToTheRight)
368  {
369  leadFootSurfaceName = "RightFootCenter";
370  followFootSurfaceName = "LeftFootCenter";
371  startWithRightFootstep_ = true;
372  }
373  else // first footstep is left foot
374  {
375  leadFootSurfaceName = "LeftFootCenter";
376  followFootSurfaceName = "RightFootCenter";
377  customPlan_.swapFirstTwoContacts();
378  startWithRightFootstep_ = false;
379  }
380 
381  double totalLength = supportPath_.arcLength(0., 1.);
382  double nbSteps = std::max(1., std::round(totalLength / desiredStepLength_));
383  double maxStepLength = 1.1 * desiredStepLength_;
384  if(totalLength / nbSteps > maxStepLength)
385  {
386  nbSteps++;
387  }
388  double stepLength = totalLength / nbSteps;
389 
390  nbFootsteps_ = 0;
391  for(unsigned i = 1; i <= nbSteps; i++)
392  {
393  double curStepWidth = stepWidth_;
394  if(i < nbSteps)
395  {
396  curStepWidth += extraStepWidth_;
397  }
398  double t = supportPath_.arcLengthInverse(0., i * stepLength);
399  Eigen::Vector2d supportPoint = supportPath_.pos(t);
400  double theta = t * targetPose_.theta;
401  Eigen::Vector2d lateralVec = {-std::sin(theta), std::cos(theta)};
402  lateralVec *= (goingToTheRight ? -1. : +1.) * 0.5 * curStepWidth;
403  Eigen::Vector2d leadPos = supportPoint + lateralVec;
404  Eigen::Vector2d followPos = supportPoint - lateralVec;
405  SE2d leadStepPose = {leadPos.x(), leadPos.y(), theta};
406  SE2d followStepPose = {followPos.x(), followPos.y(), theta};
407 
408  Contact followStep, leadStep;
409  followStep.pose = followStepPose.asPTransform();
410  followStep.surfaceName = followFootSurfaceName;
411  leadStep.pose = leadStepPose.asPTransform();
412  leadStep.surfaceName = leadFootSurfaceName;
413  customPlan_.appendContact(leadStep);
414  customPlan_.appendContact(followStep);
415  nbFootsteps_ += 2;
416  }
417 
418  stepAngle_ = 0.;
419  stepLength_ = stepLength;
420 }
421 
422 void PlanInterpolator::runTurning_()
423 {
424  customPlan_ = plans_("custom_lateral");
425  customPlan_.name = "custom_lateral";
426  supportPath_.reset(initPose_.pos(), initPose_.ori(), targetPose_.pos(), targetPose_.ori());
427 
428  std::string leadFootSurfaceName, followFootSurfaceName;
429  bool goingToTheRight = (targetPose_.theta < 0.);
430  if(goingToTheRight)
431  {
432  leadFootSurfaceName = "RightFootCenter";
433  followFootSurfaceName = "LeftFootCenter";
434  startWithRightFootstep_ = true;
435  }
436  else // first footstep is left foot
437  {
438  leadFootSurfaceName = "LeftFootCenter";
439  followFootSurfaceName = "RightFootCenter";
440  customPlan_.swapFirstTwoContacts();
441  startWithRightFootstep_ = false;
442  }
443 
444  double totalAngle = std::abs(targetPose_.theta);
445  double nbSteps = std::max(1., std::round(totalAngle / desiredStepAngle_));
446  double maxStepAngle = 1.0 * desiredStepAngle_;
447  if(totalAngle / nbSteps > maxStepAngle)
448  {
449  nbSteps++;
450  }
451  double stepAngle = totalAngle / nbSteps;
452 
453  nbFootsteps_ = 0;
454  for(unsigned i = 1; i <= nbSteps; i++)
455  {
456  double curStepWidth = stepWidth_;
457  if(i < nbSteps)
458  {
459  curStepWidth += extraStepWidth_;
460  }
461  double theta = (goingToTheRight ? -1. : +1.) * i * stepAngle;
462  Eigen::Vector2d supportPoint = targetPose_.pos();
463  Eigen::Vector2d lateralVec = {-std::sin(theta), std::cos(theta)};
464  lateralVec *= (goingToTheRight ? -1. : +1.) * 0.5 * curStepWidth;
465  Eigen::Vector2d leadPos = supportPoint + lateralVec;
466  Eigen::Vector2d followPos = supportPoint - lateralVec;
467  SE2d leadStepPose = {leadPos.x(), leadPos.y(), theta};
468  SE2d followStepPose = {followPos.x(), followPos.y(), theta};
469 
470  Contact followStep, leadStep;
471  followStep.pose = followStepPose.asPTransform();
472  followStep.surfaceName = followFootSurfaceName;
473  leadStep.pose = leadStepPose.asPTransform();
474  leadStep.surfaceName = leadFootSurfaceName;
475  customPlan_.appendContact(leadStep);
476  customPlan_.appendContact(followStep);
477  nbFootsteps_ += 2;
478  }
479 
480  stepAngle_ = stepAngle;
481  stepLength_ = 0.;
482 }
483 
484 void PlanInterpolator::updateSupportPath(const sva::PTransformd & X_0_lf, const sva::PTransformd & X_0_rf)
485 {
486  constexpr double PATH_STEP = 0.05;
487  sva::PTransformd X_0_mid = sva::interpolate(X_0_lf, X_0_rf, 0.5);
488  supportPathDisplay_.clear();
489  for(double s = 0.; s <= 1.; s += PATH_STEP)
490  {
491  Eigen::Vector2d p = supportPath_.pos(s);
492  SE2d pose = {p.x(), p.y(), /* theta = */ 0.};
493  sva::PTransformd X_0_p = pose * X_0_mid;
494  supportPathDisplay_.push_back(X_0_p.translation());
495  }
496 }
497 
498 void PlanInterpolator::restoreDefaults()
499 {
500  extraStepWidth_ = DEFAULT_EXTRA_STEP_WIDTH;
501  initPose_.theta = 0.; // [rad]
502  startWithRightFootstep_ = true;
503  supportPath_.extraInitVelScaling(1.);
504  supportPath_.extraTargetVelScaling(1.);
505 }
506 
507 } // namespace lipm_walking
void removeGUIElements()
Remove GUI panel.
std::string surfaceName
Name of the contact surface in robot model.
Definition: Contact.h:320
void addGUIElements()
Add GUI panel.
void updateSupportPath(const sva::PTransformd &X_0_lf, const sva::PTransformd &X_0_rf)
Update support path display.
std::string gait() const
Get current gait as string.
double clamp(double v, double vMin, double vMax)
Clamp a value in a given interval.
Definition: clamp.h:47
static constexpr double MAX_SAGITTAL_STEP_LENGTH
static EIGEN_MAKE_ALIGNED_OPERATOR_NEW constexpr double DEFAULT_EXTRA_STEP_WIDTH
void configure(const mc_rtc::Configuration &config)
Read configuration from dictionary.
Eigen::Vector2d pos() const
Get 2D position.
Definition: SE2d.h:79
Eigen::Vector3d refVel
Desired CoM velocity when the robot is supporting itself on this contact.
Definition: Contact.h:314
static constexpr double IN_PLACE_EXTRA_STEP_WIDTH
sva::PTransformd asPTransform() const
Convert to Plucker transform.
Definition: SE2d.h:69
unsigned nbIter
Number of times the interpolator was called.
static constexpr double MAX_EXTRA_STEP_WIDTH
Main controller namespace.
Definition: build.dox:1
double x
First translation coordinate.
Definition: SE2d.h:115
SE2 transform.
Definition: SE2d.h:40
void run()
Generate a new footstep plan.
double theta
Orientation coordinate.
Definition: SE2d.h:117
Contacts wrap foot frames with extra info from the footstep plan.
Definition: Contact.h:58
double floorn(double x, int n)
void suggestGait()
Suggest gait based on local target coordinates.
static constexpr double IN_PLACE_MAX_STEP_ANGLE
sva::PTransformd pose
Plücker transform of the contact in the inertial frame.
Definition: Contact.h:321
static constexpr double MIN_STEP_LENGTH
static constexpr double MAX_LATERAL_STEP_LENGTH