The method QueryObject::ComputeSignedDistanceToPoint returns results in type SignedDistanceToPoint. Part of that declared struct is the field SignedDistanceToPoint::is_grad_W_unique, which, if true, indicates that the stored gradient vector is unique for the signed distance field at the query point.
In support of that, we have code like this, to compute signed distance and gradient in a box. Particular care is used to determine if the query point lies at one of the box's vertices or on its edges. In these cases, the signed distance does not have a unique gradient and the returned result reflects that.
However, those are not the only cases where the gradient is not unique. If the point lies inside the box anywhere on its medial axis, the gradient is likewise not unique. The same is true for a cylinder (and has been explicitly called out) and all shapes, generally. The signed distance field has a discontinuity in its spatial gradient on the medial axis. The problem is that the code in distance_to_point_callback.h is ignoring the internal medial axis.
Essentially, the flag is an inconsistent lie.
Naively, the solution is to directly correct this so that the flag is always meaningful. Detect the points proximity to all shapes' medial axes with comparable tolerance.
There's a problem with this solution:
So, we are left with several options:
The code, as is, lies. That's not a good thing. The logical step to take is to make it stop lying. That can be done by adding or removing code. Given the presumption that no one cares about the current promise, the code would be simpler if we go with option 2 and clean it out.
@hongkai-dai Anything to add?
Sorry I made a mistake in our video chat about computing the subgradient
For discrete surfaces (e.g., boxes), this would usually be a space consisting of the convex combination of a finite enumerated set of vectors
Since the term grad_W always has a unit length, and a convex combination of unit length vectors doesn't have a unit length, I believe we only need to report the finite set of vectors, instead of its convex combinations.
Apart from this minor issue, I agree with everything you mentioned above.
Option 2 sounds great to me! As I understand it: always return a valid gradient or subgradient, nuke the flag.
Leaving the flag would be marginally better, I think, if it can be made correct since it could be used as an error condition or a hint as to why some computation failed ("penetration was too deep").
@hongkai-dai Yeah. I know what you mean. I cringed when I wrote it. Even with what I had in mind it wasn't truly a "convex combination" in the lerp sense. At best I meant it in the slerp sense. But even that's not true. I've edited the text above.
@sherm1 Two thoughts on leaving the flag:
I thought we talk about this before, long time ago. Unfortunately we didn't document that (or I couldn't find it). I'm not even sure that I remember it correctly or not. This may not be 100% true. At that time, we were worried that there was no perturbation scheme that was always consistent. There was one version that I wrote (I'm not sure whether it got into master or not) that always return one fixed vector, say Gx, as the gradient, i.e. grad_W = X_WG * Gx. Then, someone was worried that an arbitrary X_WG will change grad_W arbitrarily, or something like that (which I did not completely understand). In the end, @SeanCurtis-TRI (or @hongkai-dai ) said that we will return that flag to warn users to be careful.
I think this is a good lesson in software engineering that we should engineer the solution according to users needs, and document it, instead of speculating. Gather users requirement first.
I believe when we discussed computing grad_W, I raised the concern that sometimes grad_W is not unique, so if we report a single grad_W, the user might be fooled to think that is the unique gradient, hence we add the flag is_grad_W_unique. I feel I didn't think through this carefully, that the flag is_grad_W_unique is not very useful, as it only provides partial information.
Thank you @hongkai-dai .
It think option 2 (Clean out the flag reporting uniqueness entirely) is the best for now. It will probably simplify the code and the unit tests too.
I've got an implementation that does number 3 - leave the flag and make it so it doesn't lie. The testing is incomplete because we don't actually test the point-to-cylinder case (which is derived from the box case). So, I'd need to flesh that out.
The branch is here: https://github.com/SeanCurtis-TRI/drake/tree/PR_point_box_distance_fix
So, it comes down to voting. Do we cull or patch? If you want me to finish the branch and leave the flag vote thumbs up, if you want me to deprecate the field, vote thumbs down.
:-1: I vote to deprecate the field. Wise men said Code is Liability.
I still like the _idea_ of that flag but @DamrongGuoy does make a strong point.
And thanks to @SeanCurtis-TRI for discovering this. That code has been with us for so long that I forgot how it worked.
So, it seems like there's no strong vote in favor of keeping the flag and enough negative sentiment to suggest that it's going to get cut. I'll do it.
BTW The SignedDistancePair has the same kind of flag. I'll be ~removing~ deprecating it for the same reason.