Skip to content
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

Add integer equivalents for Rect #7984

Merged
merged 21 commits into from
Jun 12, 2023
Merged

Add integer equivalents for Rect #7984

merged 21 commits into from
Jun 12, 2023

Conversation

LiamGallagher737
Copy link
Member

@LiamGallagher737 LiamGallagher737 commented Mar 8, 2023

Objective

Add integer equivalents for the Rect type.

Closes #7967

Solution

  • Add IRect and URect

Changelog

Added IRect and URect types.

@LiamGallagher737
Copy link
Member Author

I have put this as a draft as I am unsure of how to implement the doc tests for the associated methods. The tests require used of the arguments passed into the macro, however I cannot find an easy way to do this. I can use the #[doc = ...] attribute to do this but it results in very unreadable code, if someone knows a better way to achieve this, please let me know.

Example of #[doc = ...] use.

#[doc = concat!(" is empty, this method returns an empty rectangle ([`", stringify!($type_name), "::is_empty()`] returns `true`), but")]
#[doc = concat!(" the actual values of [`", stringify!($type_name), "::min`] and [`", stringify!($type_name), "::max`] are implementation-dependent.")]

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-Math Fundamental domain-agnostic mathematical operations labels Mar 8, 2023
@james7132 james7132 self-requested a review March 9, 2023 03:27
@LiamGallagher737
Copy link
Member Author

macro_rules! just wasn't the right tool for the job so I've resorted to just manually implementing both types.

@LiamGallagher737 LiamGallagher737 marked this pull request as ready for review March 11, 2023 06:20
crates/bevy_math/src/rects/irect.rs Outdated Show resolved Hide resolved
/// ```
#[inline]
pub fn from_center_size(origin: IVec2, size: IVec2) -> Self {
assert!(size.cmpge(IVec2::ZERO).all());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we only want this to be a debug_assert or not. I prefer keeping asserts out of potentially hot math code. Especially since we assert in from_center_half_size as well.

@LiamGallagher737
Copy link
Member Author

Removed the assert for checking if the size is positive in URect's from size and half size methods as UVec2 is always positive.

assert!(size.cmpge(UVec2::ZERO).all());

@nicoburns
Copy link
Contributor

nicoburns commented May 9, 2023

#8540 suggests creating a generic Rect<T> instead. These types could then become aliases. Might that be a better path here to avoid code duplication? We have such types in Taffy https://github.com/DioxusLabs/taffy/blob/main/src/geometry.rs and they work pretty well.

@nicopap
Copy link
Contributor

nicopap commented May 9, 2023

Note that glam "solves" the issue of code duplication (think of Vec2, IVec2 and UVec2) by using the tera templating engine to generate code.

For bevy, I'd be in favor of what nicoburns describes. Having a Rect<T> and then re-exporting a pub type IRect = Rect<i32> allows having one implementation of Rect, one doc string per method, one set of methods. So when changing something Rect-related, only one place needs to be changed and checked by reviewers later.

The disadvantage is that I always found type aliases a bit confusing in rustdoc (which is what makes glam better than nalgebra imo) but I think a Rect<T> would be the better solution long term

@james7132
Copy link
Member

I think the problem with Rect<T> is the need to return glam types (i.e. Vec2, IVec2, UVec2), as well as the associated type level assertions needed for each one potentially being different. This is potentially resolvable by using a trait with associated types, but even then it's a bit rough around the edges in terms of UX when working with the types.

crates/bevy_math/src/rects/irect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/irect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/irect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/urect.rs Outdated Show resolved Hide resolved
crates/bevy_math/src/rects/urect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/urect.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@djeedai djeedai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few minor things, but I think it's almost ready for merge. Really good contribution, thanks!

crates/bevy_math/src/rects/irect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/irect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/irect.rs Show resolved Hide resolved
@LiamGallagher737
Copy link
Member Author

For URect I've created another method outset as the inset method takes a positive only integer, however to me it makes more sense for inset to create a smaller rect but the orginal f32 rect implementation has it creating a larger rect and only when a negative number was passed did it create a smaller rect.

For URect I have it so inset creates a smaller rect and outset creates a larger one but this makes it out of sync with IRect and Rect. I'm happy to change IRect to create a smaller rect with a positive inset but as Rect is already in use anyone using the inset method would now have to invert what they are passing in, I think changing this is still the correct way forward but would appreciate some input on this.

@LiamGallagher737
Copy link
Member Author

Did a quick poll on Discord and from this I think the best solution is to have URect's inset method take an i32

@LiamGallagher737
Copy link
Member Author

LiamGallagher737 commented Jun 9, 2023

Someone else also pointed out than inset could return an option instead of an empty rect when a negative insets absolute value is larger than the half size. Personally I think this should be done in a separate PR as it would be a breaking change for Rect and so far, this PR isn't breaking.

Infact all panicking methods should really return an option or result, I can do this in a follow up PR

@djeedai
Copy link
Contributor

djeedai commented Jun 10, 2023

it makes more sense for inset to create a smaller rect but the orginal f32 rect implementation has it creating a larger rect and only when a negative number was passed did it create a smaller rect.

The API is inspired by Druid/Kurbo. It takes the current rectangle and adds (positive) an inset, creating a new larger rectangle.
https://docs.rs/druid/latest/druid/struct.Rect.html#method.inset

I'm happy to change IRect to create a smaller rect with a positive inset but as Rect is already in use anyone using the inset method would now have to invert what they are passing in, I think changing this is still the correct way forward but would appreciate some input on this.

I'm not. I think the current API is correct. And as you pointed it will break everyone in possibly subtle ways.

Someone else also pointed out than inset could return an option instead of an empty rect when a negative insets absolute value is larger than the half size.

This is redundant. If the user wants to know if the resulting rectangle is empty, they can call is_empty() on the result. There's no need to make things more annoying with an Option in the common case. In general for UI you want to apply all styles and options, then at the very end possibly test if empty as an optimization. Having to check the result of Option at each step makes the API really "heavy" to deal with.

crates/bevy_math/src/rects/irect.rs Outdated Show resolved Hide resolved
crates/bevy_math/src/rects/irect.rs Outdated Show resolved Hide resolved
crates/bevy_math/src/rects/irect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/irect.rs Outdated Show resolved Hide resolved
crates/bevy_math/src/rects/irect.rs Outdated Show resolved Hide resolved
crates/bevy_math/src/rects/rect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/urect.rs Outdated Show resolved Hide resolved
crates/bevy_math/src/rects/urect.rs Show resolved Hide resolved
crates/bevy_math/src/rects/urect.rs Outdated Show resolved Hide resolved
let mut r = Self {
min: self.min - inset,
max: self.max + inset,
min: (self.min.as_ivec2() - inset).as_uvec2(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather avoid the conversion to signed, although it's a bit unlikely it still loses some precision and will return wrong results for many u32 values which can't fit into an i32.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think expand and shrink methods for URect is the correct solution?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, although that makes the API different from IRect and Rect which is probably confusing. But you can just calculate the new size here without conversion, just need to fall back to per-component operation because there's no good way to do it directly with the UVec2 interface I think.

Also about naming I'd expect expand to expand the current object in-place, vs. expanded to return a new expanded object.


/// Returns self as [`Rect`] (f32)
#[inline]
pub fn as_rect(&self) -> Rect {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good idea 👍

Copy link
Contributor

@djeedai djeedai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for following through, looks good for me!

@djeedai djeedai added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Jun 11, 2023
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-made! I'm happy enough to have this in the engine even without an internal use for now: it's a natural extension and a useful primitive for game dev.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 12, 2023
Merged via the queue into bevyengine:main with commit 942766c Jun 12, 2023
github-merge-queue bot pushed a commit that referenced this pull request Jul 15, 2023
# Objective

Some of the conversion methods on the new rect types introduced in #7984
have misleading names.

## Solution

Rename all methods returning an `IRect` to `as_irect` and all methods
returning a `URect` to `as_urect`.

## Migration Guide

Replace uses of the old method names with the new method names.
github-merge-queue bot pushed a commit that referenced this pull request Jul 15, 2023
…ect (#9085)

# Objective

Continue #7867 now that we have URect #7984
- Return `URect` instead of `(UVec2, UVec2)` in
`Camera::physical_viewport_rect`
 - Add `URect` and `IRect` to prelude

## Changelog

- Changed `Camera::physical_viewport_rect` return type from `(UVec2,
UVec2)` to `URect`
- `URect` and `IRect` were added to prelude

## Migration Guide

Before:

```rust
fn view_physical_camera_rect(camera_query: Query<&Camera>) {
    let camera = camera_query.single();
    let Some((min, max)) = camera.physical_viewport_rect() else { return };
    dbg!(min, max);
}
```

After:

```rust
fn view_physical_camera_rect(camera_query: Query<&Camera>) {
    let camera = camera_query.single();
    let Some(URect { min, max }) = camera.physical_viewport_rect() else { return };
    dbg!(min, max);
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Math Fundamental domain-agnostic mathematical operations C-Feature A new feature, making something new possible S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add integer equivalents for Rect
6 participants