-
Notifications
You must be signed in to change notification settings - Fork 321
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
Restrict maximum zoom in Graph Editor #3273
Conversation
Also enhance changelog entry
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 have 4 main issues with the code:
- Poorly named variables (see the comment in the code). Always take extra care about naming of variables, please. It helps so much with code maintainability.
- Not explained equations. There are some parts of the equation that I'm especially worried about, like the
1/something
part. Such things tend to blow up with extreme values, sometimes cause by fast mouse/fingers movement or OS-specific value interpretation. Maybe it's correct, I don't know – I was not able to easily understand the code as it is not refactored to nicely named vars. - The video has several issues:
- It's in really low-quality, its hard to see how things behave there. For example, I'm not sure whether the camera "jumps" when its hitting its zoom limits.
- It does not show all possible corner cases, including:
- I would like to see how the scene behaves with slow zoom to the limit or very fast zoom to the limit. Also, not only zooming in the middle of the screen, but also more to a corner of a screen.
- What we should check is also how the zooming animation behaves when we are zooming to the limit while constantly zooming with our fingers (e.g. on touchpad) and how it behaves when we started zooming really fast, stopped touching touchpad, and the zooming increases because of inertia operator. It is very important that in both cases, when the zoom hits its limits, the scene is not accidentally paned.
- Also, how zooming behaves when its done just after the scene was panned (when the panning animation is still happening)?
- On the video, the zoom "jumps" between second 10 and 11 (it zooms in, then zooms out). It's really hard to see if its an error or video artefact, because the video is so pixelated, but I feel its kind of artifact. It may be caused by the settings of our inertia animation, but in order to understand if it behaves correctly, the video should present all kind of corner cases, including slow and fast zooms. See:
Screen.Recording.2022-02-15.at.00.04.19.mov
// Usage of min/max prefixes might be confusing here. Remember that the max zoom means | ||
// the maximum visible size, thus the minimal distance from the camera and vice versa. | ||
let fovy_slope = camera.half_fovy_slope(); | ||
let distance_to_show_full_ui = scene.shape().value().height / 2.0 / fovy_slope; | ||
let max_zoom = max_zoom_limit.get().unwrap_or(max_zoom); | ||
let min_distance = 1.0 / max_zoom * distance_to_show_full_ui + camera.clipping().near; | ||
let max_distance = (1.0 / min_zoom * distance_to_show_full_ui).min(camera.clipping().far); | ||
|
||
let max_translation_limit = max_distance - position.z; | ||
let min_translation_limit = min_distance - position.z; | ||
let too_far = translation.z > max_translation_limit; | ||
let too_close = translation.z < min_translation_limit; | ||
let limiting_factor = if too_far { max_translation_limit / translation.z } | ||
else if too_close { min_translation_limit / translation.z } | ||
else { 1.0 }; | ||
position += translation * limiting_factor; | ||
simulator.set_target_value(position); |
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 see several issues with this code:
- The comment about min/max prefixes is added to a
fovy_slope
var, which is a little bit confusing. - The code is more repetitive than it was before this change. There are lines which are just copy-pasted from a code few lines above these lines.
- Some variables are poorly named, like
let max_zoom = max_zoom_limit.get().unwrap_or(max_zoom);
. Somax_zoom
is eithermax_zoom_limit
ormax_zoom
? If you already havemax_zoom
, why are you checking the limit? I don't understand the logic of the code, probably because of var names (not only in this place). - There are other poorly named variables, like
limiting_factor
- whylimiting_factor
multiplied bytranslation
gives us position? In the old version of the code that waszoom_factor
multiplied bydirection
. Wile I agree thattranslation
is a way better name than the olddirection
(which was logically incorrect), thelimiting_factor
is not understandable. - Another name to be reconsidered is
distance_to_show_full_ui
- I know it was used in the previous code, but it is logically incorrect. I'm surprised we did not catch it in the review back when it was introduced. EnsoGL is a rendering framework for vector shapes – there is noui
abstraction here. There an UI library build on top of EnsoGL, but this library does not know about it. - There are other var names that should be improved (revisit all of them, please). This time I'm talking about
max_translation_limit
- earlier you are usingtranslation
for a geometric vector, a 3D value. Now, you definemax_translation_limit
asmax_distance - position.z
which seems like 1D value or a number. Geometric representation of a translation (3D) limit, should be a point in space (3D) or a plane (3D), not a number (1D). This makes the code strange in other places too. For example thetranslation
var clearly refers to a geometric vector (3D variable), so writingtranslation.z > max_translation_limit
does not make sense. In previous codedirection.z > max_zoom_limit
was way better, becausezoom
is a number,direction
is 3D variable, so you can comparedirection.z
to a number. - I do not understand where the equations for
min_distance
andmax_distance
come from. Please refactor them well named variables that explain what all the components of the equation do. I'm especially afraid of the1/max_zoom
part – my intuition tells me that something is wrong there. Anyway, I'm not sure because these equations are not explained. - The code is not aligned – the if-then-else block is messy.
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.
- Fair point, fixed it. The comment was left for the block of code (separated by blank lines) which is responsible for a distinct part of the computation.
- Fixed, by introducing a closure in scope of the function. There are no places in the codebase where this equation is used, as far as I'm aware of.
- This is a common pattern in Rust codebases, to shadow variable name with a new binding applying some additional operations on it. However, I slightly refactored that part of the code, as Adam suggested in his review.
- I don't understand why
zoom_factor
is a better name thanlimiting_factor
. We limit the movements of the camera near min/max distance along the z-axis. I've left it as it is (added a small comment), please suggest a better name if you want to change it.zoom_factor
is extremely confusing, because we already hadzoom_amount
and we've already multiplied it by direction/translation. - Makes sense, however, it's hard to come up with a short name here. I choose
distance_to_zoom_factor_of_1
which is rather long but looks self-explanatory to me. Please suggest a better name if you have an idea. - It's not clear to me, what other variable names I should revisit. You covered every one of them in this message, with an exception of
too_far
andtoo_close
. Please ping me if you want to change anything you didn't explicitly mention. I renamedmax(min)_translation_limit
topositive(negative)_z_translation_limit
. However, I don't agree thatdirection.z > max_zoom_limit
was any better, mainly becausemax_zoom_limit
was representing distance rather than actual zoom limit. - You're right, it doesn't make much sense from math standpoint, I was playing with different equations while trying to understand this code, and completely missed that
1.0 / x * y
can obviously be replaced withy / x
. I changed it. I'm not sure I should (and I can) explain whymin_distance = distance_to_zoom_factor_of_1 / max_zoom
- it's just how zooming works, we have a proportion of1.0 / distance_1 = max_zoom / min_distance
. - Fair point, however, we do not have such requirements in our Code Style guide. I've left a comment about it in Update Rust style guide #3172
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.
Hi, thanks for the update:
-
I know that this is possible in Rust, but it just logically does not make sense to me. When reading the codebase I am trying to understand what the variables contain. You already have a variable
max_zoom
, so why are you computing it again? If the variablemax_zoom
does not contain real "max zoom", it should be renamed. -
If I ask anyone from the team without a context what is
zoom_factor
, they will have intuition what it is. If I ask anyone what islimiting_factor
no one would understand that this is a z-axis / depth translation limit. This var name just does not tell what it is about. If you really want to uselimiting
word, you can usez_axis_movement_limiting_factor
as the var name instead. I just care a lot about how semantically clear var names are. -
IMO
distance_to_zoom_factor_of_1
is perfect. Sure, its kind of ugly, but semantically clear vars are way better than short vars. -
I was just mentioning all vars in general - if we covered almost all of them, then probably not much is left. I was unsure if there are any left TBH.
-
I'll re-review the code in a second and I will check it again :)
-
OH WOW, I completely missed that PR. We should finish it and close it. I will check it out today.
Rc::clone(&zoom_speed), | ||
Rc::clone(&pan_speed), | ||
Rc::clone(&disable_events), | ||
Rc::clone(&max_zoom_limit), |
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.
Why do we use Rc::clone(
notations instead of just .clone()
?
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.
To clearly disambiguate cloning of pointer from cloning of the underlying data. It's a pretty common pattern in the Rust codebases I've worked with: https://rust-lang.github.io/rust-clippy/master/#clone_on_ref_ptr
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.
We are using clone_ref
here instead everywhere to show the same thing :)
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.
Apparently, not everywhere, as you can see in this code :) I can surely change it if you say so.
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.
@vitvakatu absolutely! Also, clone_ref
was not always available, so some old code can use different patterns. Sorry if my message was not clear enough – I wanted t write that nowadays we normally use clone_ref
:)
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.
Changed
@wdanilo I've updated video with a more high-res one:
Regarding corned cases:
|
let distance_to_zoom_factor_of_1 = distance_to_zoom_factor_of_1(&scene, &camera); | ||
let max_zoom = max_zoom.get().unwrap_or(DEFAULT_MAX_ZOOM); | ||
// Usage of min/max prefixes might be confusing here. Remember that the max zoom means | ||
// the maximum visible size, thus the minimal distance from the camera and vice versa. | ||
let min_distance = distance_to_zoom_factor_of_1 / max_zoom + camera.clipping().near; | ||
let max_distance = (distance_to_zoom_factor_of_1 / MIN_ZOOM).min(camera.clipping().far); | ||
|
||
// Smothly limit camera movements along z-axis near min/max distance. | ||
let positive_z_translation_limit = max_distance - position.z; | ||
let negative_z_translation_limit = min_distance - position.z; | ||
let too_far = translation.z > positive_z_translation_limit; | ||
let too_close = translation.z < negative_z_translation_limit; | ||
let limiting_factor = if too_far { positive_z_translation_limit / translation.z } | ||
else if too_close { negative_z_translation_limit / translation.z } | ||
else { 1.0 }; | ||
position += translation * limiting_factor; |
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.
As our formatter does not align =
in code, I would resign from alignment in macro blocks too.
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.
Please make a consolidated decision with @wdanilo on that matter, as he explicitly asked to align the code in #3273 (comment)
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.
@farmaazon is right. The code should be aligned as our formatter does. What I told in my PR is that the code is partially aligned and looks messy (some {
are aligned, =
are aligned, some {
are not aligned). But basically Adam is right:
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.
Changed
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 code is super clean now and the vars are very understandable! Thank you for making it this way!
This reverts commit dbcc254.
[Task link](#181181203). This is a reincarnation of PR [3273](#3273). The maximum zoom factor of Graph Editor is limited to 1.0x. It is not possible to zoom in from the default camera position. Debug Mode (activated with `ctrl-shift-d` shortcut) allows to zoom up to 100.0x (the previous behavior of Graph Editor). If you enable Debug Mode, then zoom in and disable Debug Mode - you won't see the immediate change of zoom factor back to 1.0x. But it will "jump" (with animation) back once you make a zoom in/out event with your controls. Video: https://user-images.githubusercontent.com/6566674/154037310-1d166737-353e-4ae6-aca1-f7840571ab16.mp4 # Important Notes This is a reincarnation of PR [3273](#3273). There are two changes since that PR: 1. Fixed bug with GeoMap zooming described [here](#3290). This is done by restricting `ZoomEvent` API so that it will never contain `amount` which is equal to `0.0`. 2. A few refactoring changes from #3289 to simplify code a bit.
Pull Request Description
Task link.
The maximum zoom factor of Graph Editor is limited to 1.0x. It is not possible to zoom in from the default camera position.
Debug Mode (activated with
ctrl-shift-d
shortcut) allows to zoom up to 100.0x (the previous behavior of Graph Editor).If you enable Debug Mode, then zoom in and disable Debug Mode - you won't see the immediate change of zoom factor back to 1.0x. But it will "jump" (with animation) back once you make a zoom in/out event with your controls.
Video(updated):
2022-02-15.12-49-03.mp4
I was also confused by variable names in Navigator implementation, so the code was significantly refactored.
direction
withtranslation
, becausedirection
was used for both the normalized direction vector and translation vector of the camera.MIN_ZOOM
andMAX_ZOOM
constants instead of oldmax_zoom
andmin_zoom
respectively (yes, they are reversed). This is because we operate on zoom factors now, not on camera distance values.Important Notes
Checklist
Please include the following checklist in your PR: