Fix Anchor
component inconsistancies (#18393)
## Objective Fix the misleading 2d anchor API where `Anchor` is a component and required by `Text2d` but is stored on a field for sprites. Fixes #18367 ## Solution Remove the `anchor` field from `Sprite` and require `Anchor` instead. ## Migration Guide The `anchor` field has been removed from `Sprite`. Instead the `Anchor` component is now a required component on `Sprite`.
This commit is contained in:
parent
bf20c630a8
commit
2b59ab8e2d
@ -169,9 +169,9 @@ pub fn calculate_bounds_2d(
|
||||
atlases: Res<Assets<TextureAtlasLayout>>,
|
||||
meshes_without_aabb: Query<(Entity, &Mesh2d), (Without<Aabb>, Without<NoFrustumCulling>)>,
|
||||
sprites_to_recalculate_aabb: Query<
|
||||
(Entity, &Sprite),
|
||||
(Entity, &Sprite, &Anchor),
|
||||
(
|
||||
Or<(Without<Aabb>, Changed<Sprite>)>,
|
||||
Or<(Without<Aabb>, Changed<Sprite>, Changed<Anchor>)>,
|
||||
Without<NoFrustumCulling>,
|
||||
),
|
||||
>,
|
||||
@ -183,7 +183,7 @@ pub fn calculate_bounds_2d(
|
||||
}
|
||||
}
|
||||
}
|
||||
for (entity, sprite) in &sprites_to_recalculate_aabb {
|
||||
for (entity, sprite, anchor) in &sprites_to_recalculate_aabb {
|
||||
if let Some(size) = sprite
|
||||
.custom_size
|
||||
.or_else(|| sprite.rect.map(|rect| rect.size()))
|
||||
@ -197,7 +197,7 @@ pub fn calculate_bounds_2d(
|
||||
})
|
||||
{
|
||||
let aabb = Aabb {
|
||||
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
|
||||
center: (-anchor.as_vec() * size).extend(0.0).into(),
|
||||
half_extents: (0.5 * size).extend(0.0).into(),
|
||||
};
|
||||
commands.entity(entity).try_insert(aabb);
|
||||
@ -334,12 +334,14 @@ mod test {
|
||||
// Add entities
|
||||
let entity = app
|
||||
.world_mut()
|
||||
.spawn(Sprite {
|
||||
rect: Some(Rect::new(0., 0., 0.5, 1.)),
|
||||
anchor: Anchor::TOP_RIGHT,
|
||||
image: image_handle,
|
||||
..default()
|
||||
})
|
||||
.spawn((
|
||||
Sprite {
|
||||
rect: Some(Rect::new(0., 0., 0.5, 1.)),
|
||||
image: image_handle,
|
||||
..default()
|
||||
},
|
||||
Anchor::TOP_RIGHT,
|
||||
))
|
||||
.id();
|
||||
|
||||
// Create AABB
|
||||
|
@ -10,7 +10,7 @@
|
||||
//! - The `position` reported in `HitData` in in world space, and the `normal` is a normalized
|
||||
//! vector provided by the target's `GlobalTransform::back()`.
|
||||
|
||||
use crate::Sprite;
|
||||
use crate::{Anchor, Sprite};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::prelude::*;
|
||||
use bevy_color::Alpha;
|
||||
@ -100,6 +100,7 @@ fn sprite_picking(
|
||||
Entity,
|
||||
&Sprite,
|
||||
&GlobalTransform,
|
||||
&Anchor,
|
||||
&Pickable,
|
||||
&ViewVisibility,
|
||||
)>,
|
||||
@ -107,9 +108,9 @@ fn sprite_picking(
|
||||
) {
|
||||
let mut sorted_sprites: Vec<_> = sprite_query
|
||||
.iter()
|
||||
.filter_map(|(entity, sprite, transform, pickable, vis)| {
|
||||
.filter_map(|(entity, sprite, transform, anchor, pickable, vis)| {
|
||||
if !transform.affine().is_nan() && vis.get() {
|
||||
Some((entity, sprite, transform, pickable))
|
||||
Some((entity, sprite, transform, anchor, pickable))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -117,7 +118,7 @@ fn sprite_picking(
|
||||
.collect();
|
||||
|
||||
// radsort is a stable radix sort that performed better than `slice::sort_by_key`
|
||||
radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _)| {
|
||||
radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _, _)| {
|
||||
-transform.translation().z
|
||||
});
|
||||
|
||||
@ -159,7 +160,7 @@ fn sprite_picking(
|
||||
let picks: Vec<(Entity, HitData)> = sorted_sprites
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(|(entity, sprite, sprite_transform, pickable)| {
|
||||
.filter_map(|(entity, sprite, sprite_transform, anchor, pickable)| {
|
||||
if blocked {
|
||||
return None;
|
||||
}
|
||||
@ -192,6 +193,7 @@ fn sprite_picking(
|
||||
|
||||
let Ok(cursor_pixel_space) = sprite.compute_pixel_space_point(
|
||||
cursor_pos_sprite,
|
||||
*anchor,
|
||||
&images,
|
||||
&texture_atlas_layout,
|
||||
) else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use core::ops::Range;
|
||||
|
||||
use crate::{ComputedTextureSlices, ScalingMode, Sprite, SPRITE_SHADER_HANDLE};
|
||||
use crate::{Anchor, ComputedTextureSlices, ScalingMode, Sprite, SPRITE_SHADER_HANDLE};
|
||||
use bevy_asset::{AssetEvent, AssetId, Assets};
|
||||
use bevy_color::{ColorToComponents, LinearRgba};
|
||||
use bevy_core_pipeline::{
|
||||
@ -394,13 +394,14 @@ pub fn extract_sprites(
|
||||
&ViewVisibility,
|
||||
&Sprite,
|
||||
&GlobalTransform,
|
||||
&Anchor,
|
||||
Option<&ComputedTextureSlices>,
|
||||
)>,
|
||||
>,
|
||||
) {
|
||||
extracted_sprites.sprites.clear();
|
||||
extracted_slices.slices.clear();
|
||||
for (main_entity, render_entity, view_visibility, sprite, transform, slices) in
|
||||
for (main_entity, render_entity, view_visibility, sprite, transform, anchor, slices) in
|
||||
sprite_query.iter()
|
||||
{
|
||||
if !view_visibility.get() {
|
||||
@ -411,7 +412,7 @@ pub fn extract_sprites(
|
||||
let start = extracted_slices.slices.len();
|
||||
extracted_slices
|
||||
.slices
|
||||
.extend(slices.extract_slices(sprite));
|
||||
.extend(slices.extract_slices(sprite, anchor.as_vec()));
|
||||
let end = extracted_slices.slices.len();
|
||||
extracted_sprites.sprites.push(ExtractedSprite {
|
||||
main_entity,
|
||||
@ -451,7 +452,7 @@ pub fn extract_sprites(
|
||||
flip_y: sprite.flip_y,
|
||||
image_handle_id: sprite.image.id(),
|
||||
kind: ExtractedSpriteKind::Single {
|
||||
anchor: sprite.anchor.as_vec(),
|
||||
anchor: anchor.as_vec(),
|
||||
rect,
|
||||
scaling_mode: sprite.image_mode.scale(),
|
||||
// Pass the custom size
|
||||
|
@ -15,7 +15,7 @@ use crate::TextureSlicer;
|
||||
|
||||
/// Describes a sprite to be rendered to a 2D camera
|
||||
#[derive(Component, Debug, Default, Clone, Reflect)]
|
||||
#[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass)]
|
||||
#[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass, Anchor)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
#[component(on_add = view::add_visibility_class::<Sprite>)]
|
||||
pub struct Sprite {
|
||||
@ -38,8 +38,6 @@ pub struct Sprite {
|
||||
/// When used with a [`TextureAtlas`], the rect
|
||||
/// is offset by the atlas's minimal (top-left) corner position.
|
||||
pub rect: Option<Rect>,
|
||||
/// [`Anchor`] point of the sprite in the world
|
||||
pub anchor: Anchor,
|
||||
/// How the sprite's image will be scaled.
|
||||
pub image_mode: SpriteImageMode,
|
||||
}
|
||||
@ -86,6 +84,7 @@ impl Sprite {
|
||||
pub fn compute_pixel_space_point(
|
||||
&self,
|
||||
point_relative_to_sprite: Vec2,
|
||||
anchor: Anchor,
|
||||
images: &Assets<Image>,
|
||||
texture_atlases: &Assets<TextureAtlasLayout>,
|
||||
) -> Result<Vec2, Vec2> {
|
||||
@ -112,7 +111,7 @@ impl Sprite {
|
||||
};
|
||||
|
||||
let sprite_size = self.custom_size.unwrap_or_else(|| texture_rect.size());
|
||||
let sprite_center = -self.anchor.as_vec() * sprite_size;
|
||||
let sprite_center = -anchor.as_vec() * sprite_size;
|
||||
|
||||
let mut point_relative_to_sprite_center = point_relative_to_sprite - sprite_center;
|
||||
|
||||
@ -315,8 +314,14 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(
|
||||
point,
|
||||
Anchor::default(),
|
||||
&image_assets,
|
||||
&texture_atlas_assets,
|
||||
)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(-2.0, -4.5)), Ok(Vec2::new(0.5, 9.5)));
|
||||
assert_eq!(compute(Vec2::new(0.0, 0.0)), Ok(Vec2::new(2.5, 5.0)));
|
||||
assert_eq!(compute(Vec2::new(0.0, 4.5)), Ok(Vec2::new(2.5, 0.5)));
|
||||
@ -334,7 +339,12 @@ mod tests {
|
||||
|
||||
let compute = |point| {
|
||||
sprite
|
||||
.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets)
|
||||
.compute_pixel_space_point(
|
||||
point,
|
||||
Anchor::default(),
|
||||
&image_assets,
|
||||
&texture_atlas_assets,
|
||||
)
|
||||
// Round to remove floating point errors.
|
||||
.map(|x| (x * 1e5).round() / 1e5)
|
||||
.map_err(|x| (x * 1e5).round() / 1e5)
|
||||
@ -355,12 +365,13 @@ mod tests {
|
||||
|
||||
let sprite = Sprite {
|
||||
image,
|
||||
anchor: Anchor::BOTTOM_LEFT,
|
||||
..Default::default()
|
||||
};
|
||||
let anchor = Anchor::BOTTOM_LEFT;
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(0.5, 9.5)), Ok(Vec2::new(0.5, 0.5)));
|
||||
assert_eq!(compute(Vec2::new(2.5, 5.0)), Ok(Vec2::new(2.5, 5.0)));
|
||||
assert_eq!(compute(Vec2::new(2.5, 9.5)), Ok(Vec2::new(2.5, 0.5)));
|
||||
@ -377,12 +388,13 @@ mod tests {
|
||||
|
||||
let sprite = Sprite {
|
||||
image,
|
||||
anchor: Anchor::TOP_RIGHT,
|
||||
..Default::default()
|
||||
};
|
||||
let anchor = Anchor::TOP_RIGHT;
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(-4.5, -0.5)), Ok(Vec2::new(0.5, 0.5)));
|
||||
assert_eq!(compute(Vec2::new(-2.5, -5.0)), Ok(Vec2::new(2.5, 5.0)));
|
||||
assert_eq!(compute(Vec2::new(-2.5, -0.5)), Ok(Vec2::new(2.5, 0.5)));
|
||||
@ -399,13 +411,14 @@ mod tests {
|
||||
|
||||
let sprite = Sprite {
|
||||
image,
|
||||
anchor: Anchor::BOTTOM_LEFT,
|
||||
flip_x: true,
|
||||
..Default::default()
|
||||
};
|
||||
let anchor = Anchor::BOTTOM_LEFT;
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(0.5, 9.5)), Ok(Vec2::new(4.5, 0.5)));
|
||||
assert_eq!(compute(Vec2::new(2.5, 5.0)), Ok(Vec2::new(2.5, 5.0)));
|
||||
assert_eq!(compute(Vec2::new(2.5, 9.5)), Ok(Vec2::new(2.5, 0.5)));
|
||||
@ -422,13 +435,14 @@ mod tests {
|
||||
|
||||
let sprite = Sprite {
|
||||
image,
|
||||
anchor: Anchor::TOP_RIGHT,
|
||||
flip_y: true,
|
||||
..Default::default()
|
||||
};
|
||||
let anchor = Anchor::TOP_RIGHT;
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(-4.5, -0.5)), Ok(Vec2::new(0.5, 9.5)));
|
||||
assert_eq!(compute(Vec2::new(-2.5, -5.0)), Ok(Vec2::new(2.5, 5.0)));
|
||||
assert_eq!(compute(Vec2::new(-2.5, -0.5)), Ok(Vec2::new(2.5, 9.5)));
|
||||
@ -446,12 +460,13 @@ mod tests {
|
||||
let sprite = Sprite {
|
||||
image,
|
||||
rect: Some(Rect::new(1.5, 3.0, 3.0, 9.5)),
|
||||
anchor: Anchor::BOTTOM_LEFT,
|
||||
..Default::default()
|
||||
};
|
||||
let anchor = Anchor::BOTTOM_LEFT;
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(2.0, 9.0)));
|
||||
// The pixel is outside the rect, but is still a valid pixel in the image.
|
||||
assert_eq!(compute(Vec2::new(2.0, 2.5)), Err(Vec2::new(3.5, 7.0)));
|
||||
@ -470,16 +485,17 @@ mod tests {
|
||||
|
||||
let sprite = Sprite {
|
||||
image,
|
||||
anchor: Anchor::BOTTOM_LEFT,
|
||||
texture_atlas: Some(TextureAtlas {
|
||||
layout: texture_atlas,
|
||||
index: 0,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let anchor = Anchor::BOTTOM_LEFT;
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(1.5, 3.5)));
|
||||
// The pixel is outside the texture atlas, but is still a valid pixel in the image.
|
||||
assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(5.0, 1.5)));
|
||||
@ -498,7 +514,6 @@ mod tests {
|
||||
|
||||
let sprite = Sprite {
|
||||
image,
|
||||
anchor: Anchor::BOTTOM_LEFT,
|
||||
texture_atlas: Some(TextureAtlas {
|
||||
layout: texture_atlas,
|
||||
index: 0,
|
||||
@ -507,9 +522,11 @@ mod tests {
|
||||
rect: Some(Rect::new(1.5, 1.5, 3.0, 3.0)),
|
||||
..Default::default()
|
||||
};
|
||||
let anchor = Anchor::BOTTOM_LEFT;
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(3.0, 3.5)));
|
||||
// The pixel is outside the texture atlas, but is still a valid pixel in the image.
|
||||
assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(6.5, 1.5)));
|
||||
@ -529,8 +546,14 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let compute =
|
||||
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets);
|
||||
let compute = |point| {
|
||||
sprite.compute_pixel_space_point(
|
||||
point,
|
||||
Anchor::default(),
|
||||
&image_assets,
|
||||
&texture_atlas_assets,
|
||||
)
|
||||
};
|
||||
assert_eq!(compute(Vec2::new(30.0, 15.0)), Ok(Vec2::new(4.0, 1.0)));
|
||||
assert_eq!(compute(Vec2::new(-10.0, -15.0)), Ok(Vec2::new(2.0, 4.0)));
|
||||
// The pixel is outside the texture atlas, but is still a valid pixel in the image.
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::{ExtractedSlice, Sprite, SpriteImageMode, TextureAtlasLayout};
|
||||
|
||||
use super::TextureSlice;
|
||||
use crate::{ExtractedSlice, Sprite, SpriteImageMode, TextureAtlasLayout};
|
||||
use bevy_asset::{AssetEvent, Assets};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_image::Image;
|
||||
@ -23,6 +22,7 @@ impl ComputedTextureSlices {
|
||||
pub(crate) fn extract_slices<'a>(
|
||||
&'a self,
|
||||
sprite: &'a Sprite,
|
||||
anchor: Vec2,
|
||||
) -> impl ExactSizeIterator<Item = ExtractedSlice> + 'a {
|
||||
let mut flip = Vec2::ONE;
|
||||
if sprite.flip_x {
|
||||
@ -31,7 +31,7 @@ impl ComputedTextureSlices {
|
||||
if sprite.flip_y {
|
||||
flip.y *= -1.0;
|
||||
}
|
||||
let anchor = sprite.anchor.as_vec()
|
||||
let anchor = anchor
|
||||
* sprite
|
||||
.custom_size
|
||||
.unwrap_or(sprite.rect.unwrap_or_default().size());
|
||||
|
@ -72,9 +72,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
image: asset_server.load("branding/bevy_bird_dark.png"),
|
||||
custom_size: Some(sprite_size),
|
||||
color: Color::srgb(1.0, 0.0, 0.0),
|
||||
anchor: anchor.to_owned(),
|
||||
..default()
|
||||
},
|
||||
anchor.to_owned(),
|
||||
// 3x3 grid of anchor examples by changing transform
|
||||
Transform::from_xyz(i * len - len, j * len - len, 0.0)
|
||||
.with_scale(Vec3::splat(1.0 + (i - 1.0) * 0.2))
|
||||
|
@ -246,10 +246,10 @@ mod text {
|
||||
Sprite {
|
||||
color: palettes::tailwind::GRAY_900.into(),
|
||||
custom_size: Some(Vec2::new(bounds.width.unwrap(), bounds.height.unwrap())),
|
||||
anchor,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::from_translation(dest - Vec3::Z),
|
||||
anchor,
|
||||
DespawnOnExitState(super::Scene::Text),
|
||||
));
|
||||
}
|
||||
@ -273,12 +273,12 @@ mod sprite {
|
||||
commands.spawn((
|
||||
Sprite {
|
||||
image: asset_server.load("branding/bevy_logo_dark.png"),
|
||||
anchor,
|
||||
flip_x,
|
||||
flip_y,
|
||||
color,
|
||||
..default()
|
||||
},
|
||||
anchor,
|
||||
DespawnOnExitState(super::Scene::Sprite),
|
||||
));
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
title: `Anchor` is now a required component on `Sprite`
|
||||
pull_requests: [18393]
|
||||
---
|
||||
|
||||
The `anchor` field has been removed from `Sprite`. Instead the `Anchor` component is now a required component on `Sprite`.
|
Loading…
Reference in New Issue
Block a user