From 52b8b55c2d0a631d091eee4d4c1fc6e867e766df Mon Sep 17 00:00:00 2001 From: Lynn <62256001+lynn-lumen@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:12:31 +0100 Subject: [PATCH 01/40] Implement `Measured2d` for `Arc2d`-based primitives. (#16213) # Objective - `CircularSegment` and `CircularSector` are well defined 2D shapes with both an area and a perimeter. # Solution - This PR implements `perimeter` for both and moves the existsing `area` functions into the `Measured2d` implementations. ## Testing - The `arc_tests` have been extended to also check for perimeters. --- crates/bevy_math/src/primitives/dim2.rs | 54 +++++++++++++++++++------ 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index d734a1879d..62d1633622 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -292,6 +292,22 @@ impl Default for CircularSector { } } +impl Measured2d for CircularSector { + #[inline(always)] + fn area(&self) -> f32 { + self.arc.radius.squared() * self.arc.half_angle + } + + #[inline(always)] + fn perimeter(&self) -> f32 { + if self.half_angle() >= PI { + self.arc.radius * 2.0 * PI + } else { + 2.0 * self.radius() + self.arc_length() + } + } +} + impl CircularSector { /// Create a new [`CircularSector`] from a `radius` and an `angle` #[inline(always)] @@ -382,12 +398,6 @@ impl CircularSector { pub fn sagitta(&self) -> f32 { self.arc.sagitta() } - - /// Returns the area of this sector - #[inline(always)] - pub fn area(&self) -> f32 { - self.arc.radius.squared() * self.arc.half_angle - } } /// A primitive representing a circular segment: @@ -425,6 +435,17 @@ impl Default for CircularSegment { } } +impl Measured2d for CircularSegment { + #[inline(always)] + fn area(&self) -> f32 { + 0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle())) + } + + #[inline(always)] + fn perimeter(&self) -> f32 { + self.chord_length() + self.arc_length() + } +} impl CircularSegment { /// Create a new [`CircularSegment`] from a `radius`, and an `angle` #[inline(always)] @@ -515,17 +536,12 @@ impl CircularSegment { pub fn sagitta(&self) -> f32 { self.arc.sagitta() } - - /// Returns the area of this segment - #[inline(always)] - pub fn area(&self) -> f32 { - 0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle())) - } } #[cfg(test)] mod arc_tests { use core::f32::consts::FRAC_PI_4; + use core::f32::consts::SQRT_2; use approx::assert_abs_diff_eq; @@ -548,7 +564,9 @@ mod arc_tests { is_minor: bool, is_major: bool, sector_area: f32, + sector_perimeter: f32, segment_area: f32, + segment_perimeter: f32, } impl ArcTestCase { @@ -581,6 +599,7 @@ mod arc_tests { assert_abs_diff_eq!(self.apothem, sector.apothem()); assert_abs_diff_eq!(self.sagitta, sector.sagitta()); assert_abs_diff_eq!(self.sector_area, sector.area()); + assert_abs_diff_eq!(self.sector_perimeter, sector.perimeter()); } fn check_segment(&self, segment: CircularSegment) { @@ -593,6 +612,7 @@ mod arc_tests { assert_abs_diff_eq!(self.apothem, segment.apothem()); assert_abs_diff_eq!(self.sagitta, segment.sagitta()); assert_abs_diff_eq!(self.segment_area, segment.area()); + assert_abs_diff_eq!(self.segment_perimeter, segment.perimeter()); } } @@ -615,7 +635,9 @@ mod arc_tests { is_minor: true, is_major: false, sector_area: 0.0, + sector_perimeter: 2.0, segment_area: 0.0, + segment_perimeter: 0.0, }; tests.check_arc(Arc2d::new(1.0, 0.0)); @@ -642,7 +664,9 @@ mod arc_tests { is_minor: true, is_major: false, sector_area: 0.0, + sector_perimeter: 0.0, segment_area: 0.0, + segment_perimeter: 0.0, }; tests.check_arc(Arc2d::new(0.0, FRAC_PI_4)); @@ -670,7 +694,9 @@ mod arc_tests { is_minor: true, is_major: false, sector_area: FRAC_PI_4, + sector_perimeter: FRAC_PI_2 + 2.0, segment_area: FRAC_PI_4 - 0.5, + segment_perimeter: FRAC_PI_2 + SQRT_2, }; tests.check_arc(Arc2d::from_turns(1.0, 0.25)); @@ -697,7 +723,9 @@ mod arc_tests { is_minor: true, is_major: true, sector_area: FRAC_PI_2, + sector_perimeter: PI + 2.0, segment_area: FRAC_PI_2, + segment_perimeter: PI + 2.0, }; tests.check_arc(Arc2d::from_radians(1.0, PI)); @@ -724,7 +752,9 @@ mod arc_tests { is_minor: false, is_major: true, sector_area: PI, + sector_perimeter: 2.0 * PI, segment_area: PI, + segment_perimeter: 2.0 * PI, }; tests.check_arc(Arc2d::from_degrees(1.0, 360.0)); From 5edc23db41e21c9b8fc93bbd2013a70b0422cb02 Mon Sep 17 00:00:00 2001 From: MiniaczQ Date: Sun, 3 Nov 2024 17:14:26 +0100 Subject: [PATCH 02/40] Fix fallible param notes (#16218) I noticed one of the reflinks doesn't work correctly --- crates/bevy_ecs/src/system/query.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 110f6b0b33..b6a30b14ae 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1651,6 +1651,8 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> /// Use [`Option>`] instead if zero or one matching entities can exist. /// /// See [`Query`] for more details. +/// +/// [System parameter]: crate::system::SystemParam pub struct Single<'w, D: QueryData, F: QueryFilter = ()> { pub(crate) item: D::Item<'w>, pub(crate) _filter: PhantomData, @@ -1687,6 +1689,8 @@ impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> { /// which must individually check each query result for a match. /// /// See [`Query`] for more details. +/// +/// [System parameter]: crate::system::SystemParam pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = ()>(pub(crate) Query<'w, 's, D, F>); impl<'w, 's, D: QueryData, F: QueryFilter> Deref for Populated<'w, 's, D, F> { From 1e47604506929f2cb1d356eef436885fe5aee31e Mon Sep 17 00:00:00 2001 From: urben1680 <55257931+urben1680@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:16:24 +0100 Subject: [PATCH 03/40] Adding `ScheduleGraph::contains_set` (#16206) # Objective The schedule graph can easily confirm whether a set is contained or not. This helps me in my personal project where I write an extension trait for `Schedule` and I want to configure a specific set in its methods. The set in question has a run condition though and I don't want to add that condition to the same schedule as many times as the trait methods are called. Since the non-pub set is unknown to the schedule until then, a `contains_set` is sufficient. It is probably trivial to add a method that returns an `Option` as well but as I personally don't need it I did not add that. If it is desired I can do so here though. It might be unneeded to have a `contains_set` then because one could check `is_some` on the returned id in that case. An argument against that is that future changes may be easier if only a `contains_set` needs to be ported. ## Solution Added `ScheduleGraph::contains_set`. ## Testing I put the below showcase code into a temporary unit test and it worked. If wanted I add it as a test too but I did not see that other more somewhat complicated methods have tests --- ## Showcase ```rs #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] struct MySchedule; #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] struct MySet; let mut schedule = Schedule::new(MySchedule); assert_eq!(schedule.graph().contains_set(MySet), false); schedule.configure_sets(MySet); assert_eq!(schedule.graph().contains_set(MySet), true); ``` --- crates/bevy_ecs/src/schedule/schedule.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 129f8e97a7..5ec6621572 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -650,6 +650,11 @@ impl ScheduleGraph { .and_then(|system| system.inner.as_deref()) } + /// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`. + pub fn contains_set(&self, set: impl SystemSet) -> bool { + self.system_set_ids.contains_key(&set.intern()) + } + /// Returns the system at the given [`NodeId`]. /// /// Panics if it doesn't exist. From 4e02d3cdb9a27e61f0af290fdb421849d5f5959e Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 4 Nov 2024 15:14:03 +0000 Subject: [PATCH 04/40] Improved `UiImage` and `Sprite` scaling and slicing APIs (#16088) # Objective 1. UI texture slicing chops and scales an image to fit the size of a node and isn't meant to place any constraints on the size of the node itself, but because the required components changes required `ImageSize` and `ContentSize` for nodes with `UiImage`, texture sliced nodes are laid out using an `ImageMeasure`. 2. In 0.14 users could spawn a `(UiImage, NodeBundle)` which would display an image stretched to fill the UI node's bounds ignoring the image's instrinsic size. Now that `UiImage` requires `ContentSize`, there's no option to display an image without its size placing constrains on the UI layout (unless you force the `Node` to a fixed size, but that's not a solution). 3. It's desirable that the `Sprite` and `UiImage` share similar APIs. Fixes #16109 ## Solution * Remove the `Component` impl from `ImageScaleMode`. * Add a `Stretch` variant to `ImageScaleMode`. * Add a field `scale_mode: ImageScaleMode` to `Sprite`. * Add a field `mode: UiImageMode` to `UiImage`. * Add an enum `UiImageMode` similar to `ImageScaleMode` but with additional UI specific variants. * Remove the queries for `ImageScaleMode` from Sprite and UI extraction, and refer to the new fields instead. * Change `ui_layout_system` to update measure funcs on any change to `ContentSize`s to enable manual clearing without removing the component. * Don't add a measure unless `UiImageMode::Auto` is set in `update_image_content_size_system`. Mutably deref the `Mut` if the `UiImage` is changed to force removal of any existing measure func. ## Testing Remove all the constraints from the ui_texture_slice example: ```rust //! This example illustrates how to create buttons with their textures sliced //! and kept in proportion instead of being stretched by the button dimensions use bevy::{ color::palettes::css::{GOLD, ORANGE}, prelude::*, winit::WinitSettings, }; fn main() { App::new() .add_plugins(DefaultPlugins) // Only run the app when there is user input. This will significantly reduce CPU/GPU use. .insert_resource(WinitSettings::desktop_app()) .add_systems(Startup, setup) .add_systems(Update, button_system) .run(); } fn button_system( mut interaction_query: Query< (&Interaction, &Children, &mut UiImage), (Changed, With