ExtractedSprites
slice buffer (#17041)
# Objective Instead of extracting an individual sprite per glyph of a text spawn or slice of a nine-patched sprite, add a buffer to store the extracted slice geometry. Fixes #16972 ## Solution * New struct `ExtractedSlice` to hold sprite slice size, position and atlas info (for text each glyph is a slice). * New resource `ExtractedSlices` that wraps the `ExtractedSlice` buffer. This is a separate resource so it can be used without sprites (with a text material, for example). * New enum `ExtractedSpriteKind` with variants `Single` and `Slices`. `Single` represents a single sprite, `Slices` contains a range into the `ExtractedSlice` buffer. * Only queue a single `ExtractedSprite` for sets of glyphs or slices and push the geometry for each individual slice or glyph into the `ExtractedSlice` buffer. * Modify `ComputedTextureSlices` to return an `ExtractedSlice` iterator instead of `ExtractedSprites`. * Modify `extract_text2d_sprite` to only queue new `ExtractedSprite`s on font changes and otherwise push slices. I don't like the name `ExtractedSpriteKind` much, it's a bit redundant and too haskellish. But although it's exported, it's not something users will interact with most of the time so don't want to overthink it. ## Testing yellow = this pr, red = main ```cargo run --example many_glyphs --release --features "trace_tracy" -- --no-ui``` <img width="454" alt="many-glyphs" src="https://github.com/user-attachments/assets/711b52c9-2d4d-43c7-b154-e81a69c94dce" /> ```cargo run --example many_text2d --release --features "trace_tracy"``` <img width="415" alt="many-text2d" src="https://github.com/user-attachments/assets/5ea2480a-52e0-4cd0-9f12-07405cf6b8fa" /> ## Migration Guide * `ExtractedSprite` has a new `kind: ExtractedSpriteKind` field with variants `Single` and `Slices`. - `Single` represents a single sprite. `ExtractedSprite`'s `anchor`, `rect`, `scaling_mode` and `custom_size` fields have been moved into `Single`. - `Slices` contains a range that indexes into a new resource `ExtractedSlices`. Slices are used to draw elements composed from multiple sprites such as text or nine-patched borders. * `ComputedTextureSlices::extract_sprites` has been renamed to `extract_slices`. Its `transform` and `original_entity` parameters have been removed. --------- Co-authored-by: Kristoffer Søholm <k.soeholm@gmail.com>
This commit is contained in:
parent
16dacb0fb6
commit
390877cdae
@ -118,6 +118,7 @@ impl Plugin for SpritePlugin {
|
||||
.init_resource::<SpecializedRenderPipelines<SpritePipeline>>()
|
||||
.init_resource::<SpriteMeta>()
|
||||
.init_resource::<ExtractedSprites>()
|
||||
.init_resource::<ExtractedSlices>()
|
||||
.init_resource::<SpriteAssetEvents>()
|
||||
.add_render_command::<Transparent2d, DrawSprite>()
|
||||
.add_systems(
|
||||
|
@ -323,24 +323,37 @@ impl SpecializedRenderPipeline for SpritePipeline {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractedSlice {
|
||||
pub offset: Vec2,
|
||||
pub rect: Rect,
|
||||
pub size: Vec2,
|
||||
}
|
||||
|
||||
pub struct ExtractedSprite {
|
||||
pub main_entity: Entity,
|
||||
pub render_entity: Entity,
|
||||
pub transform: GlobalTransform,
|
||||
pub color: LinearRgba,
|
||||
/// Select an area of the texture
|
||||
pub rect: Option<Rect>,
|
||||
/// Change the on-screen size of the sprite
|
||||
pub custom_size: Option<Vec2>,
|
||||
/// Asset ID of the [`Image`] of this sprite
|
||||
/// PERF: storing an `AssetId` instead of `Handle<Image>` enables some optimizations (`ExtractedSprite` becomes `Copy` and doesn't need to be dropped)
|
||||
pub image_handle_id: AssetId<Image>,
|
||||
pub flip_x: bool,
|
||||
pub flip_y: bool,
|
||||
pub anchor: Vec2,
|
||||
/// For cases where additional [`ExtractedSprites`] are created during extraction, this stores the
|
||||
/// entity that caused that creation for use in determining visibility.
|
||||
pub original_entity: Entity,
|
||||
pub scaling_mode: Option<ScalingMode>,
|
||||
pub render_entity: Entity,
|
||||
pub kind: ExtractedSpriteKind,
|
||||
}
|
||||
|
||||
pub enum ExtractedSpriteKind {
|
||||
/// A single sprite with custom sizing and scaling options
|
||||
Single {
|
||||
anchor: Vec2,
|
||||
rect: Option<Rect>,
|
||||
scaling_mode: Option<ScalingMode>,
|
||||
custom_size: Option<Vec2>,
|
||||
},
|
||||
/// Indexes into the list of [`ExtractedSlice`]s stored in the [`ExtractedSlices`] resource
|
||||
/// Used for elements composed from multiple sprites such as text or nine-patched borders
|
||||
Slices { indices: Range<usize> },
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
@ -348,6 +361,11 @@ pub struct ExtractedSprites {
|
||||
pub sprites: Vec<ExtractedSprite>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ExtractedSlices {
|
||||
pub slices: Vec<ExtractedSlice>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct SpriteAssetEvents {
|
||||
pub images: Vec<AssetEvent<Image>>,
|
||||
@ -366,8 +384,8 @@ pub fn extract_sprite_events(
|
||||
}
|
||||
|
||||
pub fn extract_sprites(
|
||||
mut commands: Commands,
|
||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||
mut extracted_slices: ResMut<ExtractedSlices>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
sprite_query: Extract<
|
||||
Query<(
|
||||
@ -381,19 +399,32 @@ pub fn extract_sprites(
|
||||
>,
|
||||
) {
|
||||
extracted_sprites.sprites.clear();
|
||||
for (original_entity, entity, view_visibility, sprite, transform, slices) in sprite_query.iter()
|
||||
extracted_slices.slices.clear();
|
||||
for (main_entity, render_entity, view_visibility, sprite, transform, slices) in
|
||||
sprite_query.iter()
|
||||
{
|
||||
if !view_visibility.get() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(slices) = slices {
|
||||
extracted_sprites.sprites.extend(slices.extract_sprites(
|
||||
&mut commands,
|
||||
transform,
|
||||
original_entity,
|
||||
sprite,
|
||||
));
|
||||
let start = extracted_slices.slices.len();
|
||||
extracted_slices
|
||||
.slices
|
||||
.extend(slices.extract_slices(sprite));
|
||||
let end = extracted_slices.slices.len();
|
||||
extracted_sprites.sprites.push(ExtractedSprite {
|
||||
main_entity,
|
||||
render_entity,
|
||||
color: sprite.color.into(),
|
||||
transform: *transform,
|
||||
flip_x: sprite.flip_x,
|
||||
flip_y: sprite.flip_y,
|
||||
image_handle_id: sprite.image.id(),
|
||||
kind: ExtractedSpriteKind::Slices {
|
||||
indices: start..end,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
let atlas_rect = sprite
|
||||
.texture_atlas
|
||||
@ -406,25 +437,26 @@ pub fn extract_sprites(
|
||||
(Some(atlas_rect), Some(mut sprite_rect)) => {
|
||||
sprite_rect.min += atlas_rect.min;
|
||||
sprite_rect.max += atlas_rect.min;
|
||||
|
||||
Some(sprite_rect)
|
||||
}
|
||||
};
|
||||
|
||||
// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
|
||||
extracted_sprites.sprites.push(ExtractedSprite {
|
||||
render_entity: entity,
|
||||
main_entity,
|
||||
render_entity,
|
||||
color: sprite.color.into(),
|
||||
transform: *transform,
|
||||
rect,
|
||||
// Pass the custom size
|
||||
custom_size: sprite.custom_size,
|
||||
flip_x: sprite.flip_x,
|
||||
flip_y: sprite.flip_y,
|
||||
image_handle_id: sprite.image.id(),
|
||||
anchor: sprite.anchor.as_vec(),
|
||||
original_entity,
|
||||
scaling_mode: sprite.image_mode.scale(),
|
||||
kind: ExtractedSpriteKind::Single {
|
||||
anchor: sprite.anchor.as_vec(),
|
||||
rect,
|
||||
scaling_mode: sprite.image_mode.scale(),
|
||||
// Pass the custom size
|
||||
custom_size: sprite.custom_size,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -553,7 +585,7 @@ pub fn queue_sprites(
|
||||
.reserve(extracted_sprites.sprites.len());
|
||||
|
||||
for (index, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() {
|
||||
let view_index = extracted_sprite.original_entity.index();
|
||||
let view_index = extracted_sprite.main_entity.index();
|
||||
|
||||
if !view_entities.contains(view_index as usize) {
|
||||
continue;
|
||||
@ -568,7 +600,7 @@ pub fn queue_sprites(
|
||||
pipeline,
|
||||
entity: (
|
||||
extracted_sprite.render_entity,
|
||||
extracted_sprite.original_entity.into(),
|
||||
extracted_sprite.main_entity.into(),
|
||||
),
|
||||
sort_key,
|
||||
// `batch_range` is calculated in `prepare_sprite_image_bind_groups`
|
||||
@ -622,6 +654,7 @@ pub fn prepare_sprite_image_bind_groups(
|
||||
mut image_bind_groups: ResMut<ImageBindGroups>,
|
||||
gpu_images: Res<RenderAssets<GpuImage>>,
|
||||
extracted_sprites: Res<ExtractedSprites>,
|
||||
extracted_slices: Res<ExtractedSlices>,
|
||||
mut phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
||||
events: Res<SpriteAssetEvents>,
|
||||
mut batches: ResMut<SpriteBatches>,
|
||||
@ -701,112 +734,170 @@ pub fn prepare_sprite_image_bind_groups(
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
// By default, the size of the quad is the size of the texture
|
||||
let mut quad_size = batch_image_size;
|
||||
|
||||
// Texture size is the size of the image
|
||||
let mut texture_size = batch_image_size;
|
||||
|
||||
// If a rect is specified, adjust UVs and the size of the quad
|
||||
let mut uv_offset_scale = if let Some(rect) = extracted_sprite.rect {
|
||||
let rect_size = rect.size();
|
||||
quad_size = rect_size;
|
||||
// Update texture size to the rect size
|
||||
// It will help scale properly only portion of the image
|
||||
texture_size = rect_size;
|
||||
Vec4::new(
|
||||
rect.min.x / batch_image_size.x,
|
||||
rect.max.y / batch_image_size.y,
|
||||
rect_size.x / batch_image_size.x,
|
||||
-rect_size.y / batch_image_size.y,
|
||||
)
|
||||
} else {
|
||||
Vec4::new(0.0, 1.0, 1.0, -1.0)
|
||||
};
|
||||
|
||||
// Override the size if a custom one is specified
|
||||
if let Some(custom_size) = extracted_sprite.custom_size {
|
||||
quad_size = custom_size;
|
||||
}
|
||||
|
||||
// Used for translation of the quad if `TextureScale::Fit...` is specified.
|
||||
let mut quad_translation = Vec2::ZERO;
|
||||
|
||||
// Scales the texture based on the `texture_scale` field.
|
||||
if let Some(scaling_mode) = extracted_sprite.scaling_mode {
|
||||
apply_scaling(
|
||||
match extracted_sprite.kind {
|
||||
ExtractedSpriteKind::Single {
|
||||
anchor,
|
||||
rect,
|
||||
scaling_mode,
|
||||
texture_size,
|
||||
&mut quad_size,
|
||||
&mut quad_translation,
|
||||
&mut uv_offset_scale,
|
||||
);
|
||||
custom_size,
|
||||
} => {
|
||||
// By default, the size of the quad is the size of the texture
|
||||
let mut quad_size = batch_image_size;
|
||||
let mut texture_size = batch_image_size;
|
||||
|
||||
// Calculate vertex data for this item
|
||||
// If a rect is specified, adjust UVs and the size of the quad
|
||||
let mut uv_offset_scale = if let Some(rect) = rect {
|
||||
let rect_size = rect.size();
|
||||
quad_size = rect_size;
|
||||
// Update texture size to the rect size
|
||||
// It will help scale properly only portion of the image
|
||||
texture_size = rect_size;
|
||||
Vec4::new(
|
||||
rect.min.x / batch_image_size.x,
|
||||
rect.max.y / batch_image_size.y,
|
||||
rect_size.x / batch_image_size.x,
|
||||
-rect_size.y / batch_image_size.y,
|
||||
)
|
||||
} else {
|
||||
Vec4::new(0.0, 1.0, 1.0, -1.0)
|
||||
};
|
||||
|
||||
if extracted_sprite.flip_x {
|
||||
uv_offset_scale.x += uv_offset_scale.z;
|
||||
uv_offset_scale.z *= -1.0;
|
||||
}
|
||||
if extracted_sprite.flip_y {
|
||||
uv_offset_scale.y += uv_offset_scale.w;
|
||||
uv_offset_scale.w *= -1.0;
|
||||
}
|
||||
|
||||
// Override the size if a custom one is specified
|
||||
quad_size = custom_size.unwrap_or(quad_size);
|
||||
|
||||
// Used for translation of the quad if `TextureScale::Fit...` is specified.
|
||||
let mut quad_translation = Vec2::ZERO;
|
||||
|
||||
// Scales the texture based on the `texture_scale` field.
|
||||
if let Some(scaling_mode) = scaling_mode {
|
||||
apply_scaling(
|
||||
scaling_mode,
|
||||
texture_size,
|
||||
&mut quad_size,
|
||||
&mut quad_translation,
|
||||
&mut uv_offset_scale,
|
||||
);
|
||||
}
|
||||
|
||||
if extracted_sprite.flip_x {
|
||||
uv_offset_scale.x += uv_offset_scale.z;
|
||||
uv_offset_scale.z *= -1.0;
|
||||
}
|
||||
if extracted_sprite.flip_y {
|
||||
uv_offset_scale.y += uv_offset_scale.w;
|
||||
uv_offset_scale.w *= -1.0;
|
||||
}
|
||||
|
||||
let transform = extracted_sprite.transform.affine()
|
||||
* Affine3A::from_scale_rotation_translation(
|
||||
quad_size.extend(1.0),
|
||||
Quat::IDENTITY,
|
||||
((quad_size + quad_translation) * (-anchor - Vec2::splat(0.5)))
|
||||
.extend(0.0),
|
||||
);
|
||||
|
||||
// Store the vertex data and add the item to the render phase
|
||||
sprite_meta
|
||||
.sprite_instance_buffer
|
||||
.push(SpriteInstance::from(
|
||||
&transform,
|
||||
&extracted_sprite.color,
|
||||
&uv_offset_scale,
|
||||
));
|
||||
|
||||
current_batch.as_mut().unwrap().get_mut().range.end += 1;
|
||||
index += 1;
|
||||
}
|
||||
ExtractedSpriteKind::Slices { ref indices } => {
|
||||
for i in indices.clone() {
|
||||
let slice = &extracted_slices.slices[i];
|
||||
let rect = slice.rect;
|
||||
let rect_size = rect.size();
|
||||
|
||||
// Calculate vertex data for this item
|
||||
let mut uv_offset_scale: Vec4;
|
||||
|
||||
// If a rect is specified, adjust UVs and the size of the quad
|
||||
uv_offset_scale = Vec4::new(
|
||||
rect.min.x / batch_image_size.x,
|
||||
rect.max.y / batch_image_size.y,
|
||||
rect_size.x / batch_image_size.x,
|
||||
-rect_size.y / batch_image_size.y,
|
||||
);
|
||||
|
||||
if extracted_sprite.flip_x {
|
||||
uv_offset_scale.x += uv_offset_scale.z;
|
||||
uv_offset_scale.z *= -1.0;
|
||||
}
|
||||
if extracted_sprite.flip_y {
|
||||
uv_offset_scale.y += uv_offset_scale.w;
|
||||
uv_offset_scale.w *= -1.0;
|
||||
}
|
||||
|
||||
let transform = extracted_sprite.transform.affine()
|
||||
* Affine3A::from_scale_rotation_translation(
|
||||
slice.size.extend(1.0),
|
||||
Quat::IDENTITY,
|
||||
(slice.size * -Vec2::splat(0.5) + slice.offset).extend(0.0),
|
||||
);
|
||||
|
||||
// Store the vertex data and add the item to the render phase
|
||||
sprite_meta
|
||||
.sprite_instance_buffer
|
||||
.push(SpriteInstance::from(
|
||||
&transform,
|
||||
&extracted_sprite.color,
|
||||
&uv_offset_scale,
|
||||
));
|
||||
|
||||
current_batch.as_mut().unwrap().get_mut().range.end += 1;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if extracted_sprite.flip_x {
|
||||
uv_offset_scale.x += uv_offset_scale.z;
|
||||
uv_offset_scale.z *= -1.0;
|
||||
}
|
||||
if extracted_sprite.flip_y {
|
||||
uv_offset_scale.y += uv_offset_scale.w;
|
||||
uv_offset_scale.w *= -1.0;
|
||||
}
|
||||
|
||||
let transform = extracted_sprite.transform.affine()
|
||||
* Affine3A::from_scale_rotation_translation(
|
||||
quad_size.extend(1.0),
|
||||
Quat::IDENTITY,
|
||||
((quad_size + quad_translation)
|
||||
* (-extracted_sprite.anchor - Vec2::splat(0.5)))
|
||||
.extend(0.0),
|
||||
);
|
||||
|
||||
// Store the vertex data and add the item to the render phase
|
||||
sprite_meta
|
||||
.sprite_instance_buffer
|
||||
.push(SpriteInstance::from(
|
||||
&transform,
|
||||
&extracted_sprite.color,
|
||||
&uv_offset_scale,
|
||||
));
|
||||
|
||||
transparent_phase.items[batch_item_index]
|
||||
.batch_range_mut()
|
||||
.end += 1;
|
||||
current_batch.as_mut().unwrap().get_mut().range.end += 1;
|
||||
index += 1;
|
||||
}
|
||||
sprite_meta
|
||||
.sprite_instance_buffer
|
||||
.write_buffer(&render_device, &render_queue);
|
||||
|
||||
if sprite_meta.sprite_index_buffer.len() != 6 {
|
||||
sprite_meta.sprite_index_buffer.clear();
|
||||
|
||||
// NOTE: This code is creating 6 indices pointing to 4 vertices.
|
||||
// The vertices form the corners of a quad based on their two least significant bits.
|
||||
// 10 11
|
||||
//
|
||||
// 00 01
|
||||
// The sprite shader can then use the two least significant bits as the vertex index.
|
||||
// The rest of the properties to transform the vertex positions and UVs (which are
|
||||
// implicit) are baked into the instance transform, and UV offset and scale.
|
||||
// See bevy_sprite/src/render/sprite.wgsl for the details.
|
||||
sprite_meta.sprite_index_buffer.push(2);
|
||||
sprite_meta.sprite_index_buffer.push(0);
|
||||
sprite_meta.sprite_index_buffer.push(1);
|
||||
sprite_meta.sprite_index_buffer.push(1);
|
||||
sprite_meta.sprite_index_buffer.push(3);
|
||||
sprite_meta.sprite_index_buffer.push(2);
|
||||
|
||||
sprite_meta
|
||||
.sprite_index_buffer
|
||||
.write_buffer(&render_device, &render_queue);
|
||||
}
|
||||
}
|
||||
sprite_meta
|
||||
.sprite_instance_buffer
|
||||
.write_buffer(&render_device, &render_queue);
|
||||
|
||||
if sprite_meta.sprite_index_buffer.len() != 6 {
|
||||
sprite_meta.sprite_index_buffer.clear();
|
||||
|
||||
// NOTE: This code is creating 6 indices pointing to 4 vertices.
|
||||
// The vertices form the corners of a quad based on their two least significant bits.
|
||||
// 10 11
|
||||
//
|
||||
// 00 01
|
||||
// The sprite shader can then use the two least significant bits as the vertex index.
|
||||
// The rest of the properties to transform the vertex positions and UVs (which are
|
||||
// implicit) are baked into the instance transform, and UV offset and scale.
|
||||
// See bevy_sprite/src/render/sprite.wgsl for the details.
|
||||
sprite_meta.sprite_index_buffer.push(2);
|
||||
sprite_meta.sprite_index_buffer.push(0);
|
||||
sprite_meta.sprite_index_buffer.push(1);
|
||||
sprite_meta.sprite_index_buffer.push(1);
|
||||
sprite_meta.sprite_index_buffer.push(3);
|
||||
sprite_meta.sprite_index_buffer.push(2);
|
||||
|
||||
sprite_meta
|
||||
.sprite_index_buffer
|
||||
.write_buffer(&render_device, &render_queue);
|
||||
}
|
||||
}
|
||||
|
||||
/// [`RenderCommand`] for sprite rendering.
|
||||
pub type DrawSprite = (
|
||||
SetItemPipeline,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{ExtractedSprite, Sprite, SpriteImageMode, TextureAtlasLayout};
|
||||
use crate::{ExtractedSlice, Sprite, SpriteImageMode, TextureAtlasLayout};
|
||||
|
||||
use super::TextureSlice;
|
||||
use bevy_asset::{AssetEvent, Assets};
|
||||
@ -6,8 +6,6 @@ use bevy_ecs::prelude::*;
|
||||
use bevy_image::Image;
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_render::sync_world::TemporaryRenderEntity;
|
||||
use bevy_transform::prelude::*;
|
||||
|
||||
/// Component storing texture slices for tiled or sliced sprite entities
|
||||
///
|
||||
@ -16,61 +14,33 @@ use bevy_transform::prelude::*;
|
||||
pub struct ComputedTextureSlices(Vec<TextureSlice>);
|
||||
|
||||
impl ComputedTextureSlices {
|
||||
/// Computes [`ExtractedSprite`] iterator from the sprite slices
|
||||
/// Computes [`ExtractedSlice`] iterator from the sprite slices
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `transform` - the sprite entity global transform
|
||||
/// * `original_entity` - the sprite entity
|
||||
/// * `sprite` - The sprite component
|
||||
/// * `handle` - The sprite texture handle
|
||||
#[must_use]
|
||||
pub(crate) fn extract_sprites<'a, 'w, 's>(
|
||||
pub(crate) fn extract_slices<'a>(
|
||||
&'a self,
|
||||
commands: &'a mut Commands<'w, 's>,
|
||||
transform: &'a GlobalTransform,
|
||||
original_entity: Entity,
|
||||
sprite: &'a Sprite,
|
||||
) -> impl ExactSizeIterator<Item = ExtractedSprite> + 'a + use<'a, 'w, 's> {
|
||||
) -> impl ExactSizeIterator<Item = ExtractedSlice> + 'a {
|
||||
let mut flip = Vec2::ONE;
|
||||
let [mut flip_x, mut flip_y] = [false; 2];
|
||||
if sprite.flip_x {
|
||||
flip.x *= -1.0;
|
||||
flip_x = true;
|
||||
}
|
||||
if sprite.flip_y {
|
||||
flip.y *= -1.0;
|
||||
flip_y = true;
|
||||
}
|
||||
self.0.iter().map(move |slice| {
|
||||
let offset = (slice.offset * flip).extend(0.0);
|
||||
let transform = transform.mul_transform(Transform::from_translation(offset));
|
||||
ExtractedSprite {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
original_entity,
|
||||
color: sprite.color.into(),
|
||||
transform,
|
||||
rect: Some(slice.texture_rect),
|
||||
custom_size: Some(slice.draw_size),
|
||||
flip_x,
|
||||
flip_y,
|
||||
image_handle_id: sprite.image.id(),
|
||||
anchor: Self::redepend_anchor_from_sprite_to_slice(sprite, slice),
|
||||
scaling_mode: sprite.image_mode.scale(),
|
||||
}
|
||||
let anchor = sprite.anchor.as_vec()
|
||||
* sprite
|
||||
.custom_size
|
||||
.unwrap_or(sprite.rect.unwrap_or_default().size());
|
||||
self.0.iter().map(move |slice| ExtractedSlice {
|
||||
offset: slice.offset * flip - anchor,
|
||||
rect: slice.texture_rect,
|
||||
size: slice.draw_size,
|
||||
})
|
||||
}
|
||||
|
||||
fn redepend_anchor_from_sprite_to_slice(sprite: &Sprite, slice: &TextureSlice) -> Vec2 {
|
||||
let sprite_size = sprite
|
||||
.custom_size
|
||||
.unwrap_or(sprite.rect.unwrap_or_default().size());
|
||||
if sprite_size == Vec2::ZERO {
|
||||
sprite.anchor.as_vec()
|
||||
} else {
|
||||
sprite.anchor.as_vec() * sprite_size / slice.draw_size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates sprite slices for a [`Sprite`] with [`SpriteImageMode::Sliced`] or [`SpriteImageMode::Sliced`]. The slices
|
||||
|
@ -26,7 +26,9 @@ use bevy_render::{
|
||||
view::{NoFrustumCulling, ViewVisibility},
|
||||
Extract,
|
||||
};
|
||||
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, Sprite};
|
||||
use bevy_sprite::{
|
||||
Anchor, ExtractedSlice, ExtractedSlices, ExtractedSprite, ExtractedSprites, Sprite,
|
||||
};
|
||||
use bevy_transform::components::Transform;
|
||||
use bevy_transform::prelude::GlobalTransform;
|
||||
use bevy_window::{PrimaryWindow, Window};
|
||||
@ -136,6 +138,7 @@ pub type Text2dWriter<'w, 's> = TextWriter<'w, 's, Text2d>;
|
||||
pub fn extract_text2d_sprite(
|
||||
mut commands: Commands,
|
||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||
mut extracted_slices: ResMut<ExtractedSlices>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||
text2d_query: Extract<
|
||||
@ -149,8 +152,11 @@ pub fn extract_text2d_sprite(
|
||||
&GlobalTransform,
|
||||
)>,
|
||||
>,
|
||||
text_styles: Extract<Query<(&TextFont, &TextColor)>>,
|
||||
text_colors: Extract<Query<&TextColor>>,
|
||||
) {
|
||||
let mut start = extracted_slices.slices.len();
|
||||
let mut end = start + 1;
|
||||
|
||||
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
|
||||
let scale_factor = windows
|
||||
.single()
|
||||
@ -159,7 +165,7 @@ pub fn extract_text2d_sprite(
|
||||
let scaling = GlobalTransform::from_scale(Vec2::splat(scale_factor.recip()).extend(1.));
|
||||
|
||||
for (
|
||||
original_entity,
|
||||
main_entity,
|
||||
view_visibility,
|
||||
computed_block,
|
||||
text_layout_info,
|
||||
@ -182,15 +188,19 @@ pub fn extract_text2d_sprite(
|
||||
*global_transform * GlobalTransform::from_translation(bottom_left.extend(0.)) * scaling;
|
||||
let mut color = LinearRgba::WHITE;
|
||||
let mut current_span = usize::MAX;
|
||||
for PositionedGlyph {
|
||||
position,
|
||||
atlas_info,
|
||||
span_index,
|
||||
..
|
||||
} in &text_layout_info.glyphs
|
||||
|
||||
for (
|
||||
i,
|
||||
PositionedGlyph {
|
||||
position,
|
||||
atlas_info,
|
||||
span_index,
|
||||
..
|
||||
},
|
||||
) in text_layout_info.glyphs.iter().enumerate()
|
||||
{
|
||||
if *span_index != current_span {
|
||||
color = text_styles
|
||||
color = text_colors
|
||||
.get(
|
||||
computed_block
|
||||
.entities()
|
||||
@ -198,25 +208,41 @@ pub fn extract_text2d_sprite(
|
||||
.map(|t| t.entity)
|
||||
.unwrap_or(Entity::PLACEHOLDER),
|
||||
)
|
||||
.map(|(_, text_color)| LinearRgba::from(text_color.0))
|
||||
.map(|text_color| LinearRgba::from(text_color.0))
|
||||
.unwrap_or_default();
|
||||
current_span = *span_index;
|
||||
}
|
||||
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
||||
|
||||
extracted_sprites.sprites.push(ExtractedSprite {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
transform: transform * GlobalTransform::from_translation(position.extend(0.)),
|
||||
color,
|
||||
rect: Some(atlas.textures[atlas_info.location.glyph_index].as_rect()),
|
||||
custom_size: None,
|
||||
image_handle_id: atlas_info.texture.id(),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
anchor: Anchor::CENTER.as_vec(),
|
||||
original_entity,
|
||||
scaling_mode: None,
|
||||
let rect = texture_atlases
|
||||
.get(&atlas_info.texture_atlas)
|
||||
.unwrap()
|
||||
.textures[atlas_info.location.glyph_index]
|
||||
.as_rect();
|
||||
extracted_slices.slices.push(ExtractedSlice {
|
||||
offset: *position,
|
||||
rect,
|
||||
size: rect.size(),
|
||||
});
|
||||
|
||||
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| {
|
||||
info.span_index != current_span || info.atlas_info.texture != atlas_info.texture
|
||||
}) {
|
||||
let render_entity = commands.spawn(TemporaryRenderEntity).id();
|
||||
extracted_sprites.sprites.push(ExtractedSprite {
|
||||
main_entity,
|
||||
render_entity,
|
||||
transform,
|
||||
color,
|
||||
image_handle_id: atlas_info.texture.id(),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
kind: bevy_sprite::ExtractedSpriteKind::Slices {
|
||||
indices: start..end,
|
||||
},
|
||||
});
|
||||
start = end;
|
||||
}
|
||||
|
||||
end += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user