Fix #117927: Limit rotation constraint flipping #118502
No reviewers
Labels
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Code Documentation
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Viewport & EEVEE
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Asset Browser Project
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Asset System
Module
Asset System
Module
Core
Module
Development Management
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Module
Viewport & EEVEE
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Severity
High
Severity
Low
Severity
Normal
Severity
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
4 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#118502
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "ChrisLend/blender:fix_limit_rot_constraint"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The issue from the bug report mentions that the
Limit Rotation
constraint snapsback to 0 when reaching 180 degrees with a min and max of 0 / 180.
The root cause of this goes a bit deeper though. Because the following also snaps back to 0.
The reason for this is that the clamping logic in the constraint was very simple.
It just took the matrix, decomposed it to euler and clamped the values directly.
However in that process, the euler angles are bound to a range of -180 / 180,
which means that any angle >= 180 would snap back to the min.
The example uses a bone, but the same code is used for objects too!
@blender-bot package
Package build started. Download here when ready.
Thanks for the fix, tested!
The rest of my points are probably off-topic, so feel free to ignore, but I'll just leave it here anyways. If nothing else, it can be good to acknowledge these as known limitations of the system.
@ -1661,0 +1663,4 @@
* considered a valid range.*/
static float clamp_angle(const float angle, const float min, const float max)
{
if (min == max) {
I think this can be
if (min >= max)
.I think it would also be good to add the case
if ((max - min) >= (2 * PI))
which just always returnsangle
as-is.The intuition here is that if min and max constrain the angle to 0 degrees or less, it can't move, but if they constraint it to 360 degrees or more that's the same as being unconstrained.
With this PR that's not actually true. Given min and max of 0 and 370, the rotation will be constrained between 0 and 10 degrees.
This is a bit different than the previous behavior, where it would allow values until 180 and then jump back to 0.
Conceptually the constraint doesn't clamp euler angles even though the GUI makes it seem like that.
It decomposes the matrix which will lead to a range of -pi/pi no matter the euler values.
That means the constraint will also work for quaternions, but of course means we can't simply clamp euler values.
What I am trying to do with this PR is to remove the assumption in the code that we have simple euler values.
For context: what initially prompted this suggestion was @Mets confusion about a 0 to 360 range now immobilizing the rotation. I was thinking about what could conceptually make sense to avoid that, and what I came to was the idea that
max - min
represents the magnitude of the restricted range.Unless I'm missing something, I think that still preserves all of the expressive power (everything that was representable before can still be represented) while also making things more intuitive for users.
I'm aware that it works by first decomposing the transform matrix to euler, and I think(?) I recognize the various implications of that. And I'm not suggesting clamping the euler values, but rather giving the distance between min and max in their own space semantic meaning. Which we're already doing anyway, otherwise we wouldn't be able to distinguish between an allowed range > 180 degrees vs < 180 degrees.
ah ok I think I missed your point, yes indeed that's a good idea.
Changed that now
@ -1661,0 +1669,4 @@
float angle_unit_circle[2];
angle_unit_circle[0] = cos(angle);
angle_unit_circle[1] = sin(angle);
My gut is saying that going through sin/cos and using dot products etc. might be overkill here. This is definitely doable with just angles, but the thing I'm unsure of is whether that might actually make this more complex with the special cases that come up. (Although I suspect it would at least be computationally cheaper.)
the reason for going through the unit circle is that it avoids the jump around -180/180 (or -pi/pi)
While the values are far apart their actual rotation is identical.
About performance, I assume it will still be super cheap. But I will run a test to get hard numbers.
Yeah, that jump and related things are the special cases that would need to be handled if it's done in terms of angles.
Performance numbers are in. It's about 23% percent slower than pre PR (1730ns to 2100ns).
I assume that trying to do the same fix without
sin
andcos
will have it's cost too though.I could try to do it only angle based but I'd need your help on this. I thought about it already and don't know how this would work
I've worked out at least one way to do it on Desmos:
https://www.desmos.com/calculator/ptmipo2cmf
However, the limitations of math notation make it look pretty dense. And it also uses a kind of modulus that I'm unsure exists in e.g. the C++ standard library. I'll poke at actually implementing this in C++ the next chance I get (probably tomorrow morning), and then we can see how it compares.
@ -1661,0 +1668,4 @@
}
if (max <= min) {
return angle;
I think this case should still return
min
like before, since this is the case that represents immobilizing due to there being no (or negative) space between min and max.ah yeah you are right
Extra polish suggestions unless they're already in there:
@ChrisLend Here's a purely angle-based implementation of
clamp_angle()
you can test out:@nathanvegdahl I used your code and it works as expected.
In terms of performance it's slightly faster.
yours: ~2200ns
mine: ~2500ns
though with such low numbers I am getting run to run fluctuations that are relatively large.
Did you mean to have those comments in the final code?
also what does the "t" in
tclamped
stand for?I just wanted to get you something to test against, so there's several things I didn't put any thought into. I'll do another review pass to criticize my own code and comments. ;-)
@ -1661,0 +1662,4 @@
/**
* Wraps a number to be in [-PI, +PI].
*
* Can also be implemented as `std::remainder(a, M_PI * 2.0)`.
This note about
std::remainder
was just for you, to also test. When I looked intostd::remainder
's implementation it looked pretty involved, so I suspected it would be quite a bit slower. But I'm not really sure.I'm also just generally wondering if there might be a faster way to implement this. But again, not sure.
I changed the argument name to
angle
For
b
I am not sure if there is a good nameYou could rename
b
toangle_normalized
or something like that, maybe? Basically it's mapping [-pi, pi] to [0, 1] so that we can wrap by subtracting floor. And then the mapping is inverted to bring it back to [-pi, pi] again.But I don't know if that will really make much difference for comprehensibility here. So it's up to you.
@ -1661,0 +1665,4 @@
* Can also be implemented as `std::remainder(a, M_PI * 2.0)`.
* Not sure which is faster.
*/
static inline float pi_wrap(const float a)
I don't know if
pi_wrap
is really a good name for this. Maybewrap_angle
? The idea is that it puts an angle into a canonical space where every angle has precisely one representation, rather than an infinite number from looping.used
wrap_rad_angle
. Felt clearer to me@ -1661,0 +1673,4 @@
}
/**
* Clamps an angle between min and max. ...
We can definitely document this better, ha ha. Maybe this:
Clamps an angle between
min
andmax
.All angles are in radians.
This function treats angles as existing in a looping (cyclic) space, and is therefore specifically not equivalent to a simple
clamp(angle, min, max)
.min
andmax
are treated as a directed range on the unit circle andangle
is treated as a point on the unit circle.angle
is then clamped to be within the directed range defined bymin
andmax
.@ -1661,0 +1695,4 @@
/* Some shenanigans to handle the various special cases of
* clamping in a cyclic space. */
float tclamped = 0.0;
if (tmin < tmax) {
I think these cases can be better commented if they're split up to return early. And as I did that, I just generally cleaned up the function a bit (sans the
min_relative
renames and such). I also discovered thatstd::clamp
exists:I changed the name of
tmin
andtmax
tomin_wrapped
andmax_wrapped
I also inverted the "zero outside of the range" condition and instead return
angle
earlyAlso I renamed
pi_wrap
->wrap_rad_angle
In theory yes, but I'd like to not do that in this PR to not go down any rabbit holes
Hm I don't see why not, @nathanvegdahl do you have any reservations about that?
Nope, that seems fine to me.
Also, I'm realizing I got a little over-obsessed with getting this ready to merge. Personally, I really want to see this merged. But @dr.sybren prodded me in-person about double checking that we should merge it.
I think this behavior is obviously better than the old behavior, and frankly the old behavior seems buggy to me. But the question is: are there any rigs that depend on the old behavior, and is it worth risking breakage there?
My suspicion is that people are unlikely to be knowingly depending on the old behavior, but it's possible they might be accidentally depending on it (e.g. someone getting a rig setup working by just playing with stuff until it seems to work, rather than really understanding what's going on).
So I'm going to add this to module meeting agenda for today to discuss.
@ -1661,0 +1714,4 @@
if (std::fabs(max_wrapped) < std::fabs(min_wrapped)) {
return angle + max_wrapped;
}
return angle + min_wrapped;
I'm specifically not asking for a change here, since this follows Blender's style guidelines. Just a personal note: to me this is a good example of a case where the "no else after return" rule actually makes code less clear. We have have two alternate cases here that conceptually are on the same level and symmetric with each other, but that's no longer clearly reflected in the structure of the code. Alas.
I know and I feel the same, but I just was corrected on this recently....
now targeting main since it was discussed in yesterdays Animation & Rigging module meeting that we shouldn't land this that late in the 4.1 release cycle
Added soft limits of -360 to 360 to the properties
@ -2658,2 +2661,3 @@
RNA_def_property_range(prop, -1000.0, 1000.0f);
RNA_def_property_ui_text(prop, "Maximum X", "Highest X value to allow");
RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f);
RNA_def_property_ui_text(prop, "Maximum X", "Higher X angle bound");
Nit: instead of "higher" I think "upper" is the more common word to use when talking about bounds.
Just left one nit that you can change while landing. Otherwise looks good to me!
@blender-bot build
Nathan Vegdahl referenced this pull request2024-06-18 15:09:17 +02:00