-
Notifications
You must be signed in to change notification settings - Fork 285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generalize the way properties of a contact constraints are computed, allow for custom implementations #1626
Conversation
… customizing the whole constraint generation process. Signed-off-by: Martin Pecka <[email protected]>
Signed-off-by: Martin Pecka <[email protected]>
… customizing the whole constraint generation process. Signed-off-by: Martin Pecka <[email protected]>
Signed-off-by: Martin Pecka <[email protected]>
Signed-off-by: Martin Pecka <[email protected]>
Signed-off-by: Martin Pecka <[email protected]>
This PR also does not add the generalized approach to soft contacts. I've never worked with them, so I don't feel too confident editing around. But their API seems similar in many parts, so maybe it'd make sense to include them, too. |
void ConstraintSolver::addContactSurfaceHandler( | ||
ContactSurfaceHandlerPtr handler) | ||
{ | ||
handler->setParent(mContactSurfaceHandler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you let me know what's the motivation for having the handlers in a chain structure? It seems there is no advantage of using the chain structure because ConstraintSolver uses the lastly "added" handler anyway. Why not simply set handler (instead of adding)?
If the user wants to use a sophisticated handler that utilizes multiple handlers (e.g., to use different handlers by BodyNode), they could create a composite handler like dart::utils::CompositeResourceRetriever
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The motivation was mainly to ease creating "small" contact handlers that just do one or two things more than the default one. It could alternatively be solved by subclassing the default handler, but the chain-like structure allows to not care what handler is already set. This should theoretically allow mutliple plugins add multiple handlers without conflicts (as long as there is no logical conflict in their tasks).
If the goal is to create a very customized handler and disable the default one, the custom handler will just not call ContactSurfaceHandler::createParams()
in the override, which will effectively terminate the chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think the chain-like structure would be more frequently used than the other case? If not, I would prefer subclassing the default handler because addContactSurfaceHandler()
could confuse the user without understanding of how this function "add"s handler
(i.e., adding back to the existing handler) and how the chain works.
If the user wants to have the same effect of the current chain-like structure, they could achieve it through subclass, as you mentioned, or creating an wrapper class that works exactly the same as the current ContactSurfaceHandler
something like:
class ContactSurfaceHandler {
public:
ContactSurfaceHandler() = default; // no parent parameter
virtual ContactSurfaceParams createParams(
const collision::Contact& contact,
size_t numContactsOnCollisionObject) const = 0; // pure virtual
};
class DefaultContactSurfaceHandler : public ContactSurfaceHandler {
public:
DefaultContactSurfaceHandler() = default;
ContactSurfaceParams DefaultContactSurfaceHandler::createParams(
const collision::Contact& contact,
size_t numContactsOnCollisionObject) const override {
// ...
}
};
class ChainContactSurfaceHandler : public ContactSurfaceHandler {
public:
ChainContactSurfaceHandler(ContactSurfaceHandlerPtr parent = nullptr);
ContactSurfaceParams createParams(
const collision::Contact& contact,
size_t numContactsOnCollisionObject) const override {
if (mParent != nullptr)
return mParent->createParams(contact, numContactsOnCollisionObject);
return {};
}
protected:
ContactSurfaceHandlerPtr mParent;
};
class CustomHandler : public ChainContactSurfaceHandler {
public:
ChainContactSurfaceHandler(ContactSurfaceHandlerPtr parent = nullptr)
: ChainContactSurfaceHandler(std::move(parent)) {}
ContactSurfaceParams createParams(
const collision::Contact& contact,
size_t numContactsOnCollisionObject) const override {
// Utilize mParent as needed
}
};
// If you need chain-like structure:
auto defaultHandler = std::make_shared<DefaultContactSurfaceHandler>()
auto customHandler = std::make_shared<CustomHandler>(defaultHandler);
constraintSolver->setContactSurfaceHandler(customHandler);
It could alternatively be solved by subclassing the default handler, but the chain-like structure allows to not care what handler is already set. This should theoretically allow mutliple plugins add multiple handlers without conflicts (as long as there is no logical conflict in their tasks).
I'm afraid that I'm not quite following this statement. To me, the chain-like structure should care what the handler is already set to be able to decide whether to utilize or/and how to utilize. Let me know if I'm missing something here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Imagine I'm writing a 3rd party plugin that wants to add a handler. Now I don't know anything about which other plugins set what other handlers, so I can't be sure subclassing DefaultContactSurfaceHandler is the right thing to do. That would mean almost all plugin developers would need to come with the chaining handlers to be sure they do not break other plugins (unless they really want to get rid of whatever is there).
Providing ChainContactSurfaceHandler as you suggested would help that and give developers an easy-to-use base class. However, I'm not sure how would removing such handler work (if other handlers are hooked to it).
That is why I think the chaining logic should be implemented in DART.
addContactSurfaceHandler()
could confuse the user without understanding of how this function "add"shandler
(i.e., adding back to the existing handler) and how the chain works.
I was hoping the method names and the attached documentation are good enough to explain how the chain works. If we agree on keeping the chaining logic, I could try to improve it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that a large part of 3rd party handlers could be just "observers" or "single-value-changers". This is why I think the default behavior should be passing the work to the previous handler and why I want such simple handlers to be written as simple as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, now I see your points. Thanks for adding the tests, which are also helpful understanding the motivation!
Thanks for adding this feature!
ABI compatibility is nice-to-have, but it's not required for major version updates for DART at the moment. It's currently not easy to keep it when adding new features unless DART adopts the PIMPL idiom, which is not planned. So I think it's okay.
Yeah, more unit tests would be appreciated!
The soft body is not actively maintained. Let's save it for future changes (#1631). |
Co-authored-by: Jeongseok Lee <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the thorough and prompt review and comments. I've commited most of your suggestions.
Signed-off-by: Martin Pecka <[email protected]>
Signed-off-by: Martin Pecka <[email protected]>
I extended |
Signed-off-by: Martin Pecka <[email protected]>
I added a test for adding/removing the contact handlers. Maybe it will help you assess the usefulness of the chaining approach. |
Signed-off-by: Martin Pecka <[email protected]>
Signed-off-by: Martin Pecka <[email protected]>
void ConstraintSolver::addContactSurfaceHandler( | ||
ContactSurfaceHandlerPtr handler) | ||
{ | ||
handler->setParent(mContactSurfaceHandler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, now I see your points. Thanks for adding the tests, which are also helpful understanding the motivation!
Thanks! |
Before creating a pull request
clang-format
Before merging a pull request
CHANGELOG.md
Upstreaming gazebo-forks#22 . Here's the copied PR description:
This PR adds the possibility to adjust properties of contact constraints (joints) before they are added to the simulation loop.
It resolves a TODO from ContactConstraint.cpp that was reading:
The contact surface structure was inspired by the existing contact constraint class members and by http://ode.org/wiki/index.php?title=Manual#Contact .
This PR also adds support for specifying friction-unrelated contact velocity (
dContactMotion1
etc. in ODE words). This is needed for simulation of tracks or conveyor belts. It basically specifies that the velocity LCP tries to achieve should not be zero-up-to-friction-coefficients, but the specified velocity (very similar to how bouncing works). This velocity target can be set using the custom contact surface handler this PR adds.Adding the custom contact surface handler also allowed to separate the odd-placed force-dependent slip settings from ConstraintSolver to the handler class, which looks to me like a desired separation of concerns.
PR gazebo-forks#22 in the Ignition fork of DART allowed adding more proper support for tracked vehicles to Ignition Gazebo with DART: gazebosim/gz-sim#869 .
I did not take much care about keeping ABI compatibility. API compatibility should remain untouched regarding public members of classes. There are some new classes and I deprecated a few static protected members of ContactConstraint and moved their implementation to DefaultContactSurfaceHandler. I also moved a few .cpp-only constants from ContactConstraint.cpp to ContactSurface.hpp (making them publicly available). If ABI compatibility is wanted, the commits are prepared in gazebo-forks#22 .
The
ContactSurfaceParams
class has a future-proofvoid*
member that should allow adding more contact surface properties without breaking ABI. That should be good for downstream applications using DART. However, I'm not 100% sure whether raw pointer is the correct approach (it is null for now). Maybe a unique_ptr should be there together with custom copy and move constructors.All existing tests passed on my computer. I can add a few more regarding the addition/removal of contact handlers.
The example API usage is like this:
contactMotion.mp4