Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_image/src/dynamic_texture_atlas_builder.rs
6595 views
1
use crate::{Image, TextureAccessError, TextureAtlasLayout, TextureFormatPixelInfo as _};
2
use bevy_asset::RenderAssetUsages;
3
use bevy_math::{URect, UVec2};
4
use guillotiere::{size2, Allocation, AtlasAllocator};
5
use thiserror::Error;
6
use tracing::error;
7
8
/// An error produced by [`DynamicTextureAtlasBuilder`] when trying to add a new
9
/// texture to a [`TextureAtlasLayout`].
10
#[derive(Debug, Error)]
11
pub enum DynamicTextureAtlasBuilderError {
12
/// Unable to allocate space within the atlas for the new texture
13
#[error("Couldn't allocate space to add the image requested")]
14
FailedToAllocateSpace,
15
/// Attempted to add a texture to an uninitialized atlas
16
#[error("cannot add texture to uninitialized atlas texture")]
17
UninitializedAtlas,
18
/// Attempted to add an uninitialized texture to an atlas
19
#[error("cannot add uninitialized texture to atlas")]
20
UninitializedSourceTexture,
21
/// A texture access error occurred
22
#[error("texture access error: {0}")]
23
TextureAccess(#[from] TextureAccessError),
24
}
25
26
/// Helper utility to update [`TextureAtlasLayout`] on the fly.
27
///
28
/// Helpful in cases when texture is created procedurally,
29
/// e.g: in a font glyph [`TextureAtlasLayout`], only add the [`Image`] texture for letters to be rendered.
30
pub struct DynamicTextureAtlasBuilder {
31
atlas_allocator: AtlasAllocator,
32
padding: u32,
33
}
34
35
impl DynamicTextureAtlasBuilder {
36
/// Create a new [`DynamicTextureAtlasBuilder`]
37
///
38
/// # Arguments
39
///
40
/// * `size` - total size for the atlas
41
/// * `padding` - gap added between textures in the atlas, both in x axis and y axis
42
pub fn new(size: UVec2, padding: u32) -> Self {
43
Self {
44
atlas_allocator: AtlasAllocator::new(to_size2(size)),
45
padding,
46
}
47
}
48
49
/// Add a new texture to `atlas_layout`.
50
///
51
/// It is the user's responsibility to pass in the correct [`TextureAtlasLayout`].
52
/// Also, the asset that `atlas_texture_handle` points to must have a usage matching
53
/// [`RenderAssetUsages::MAIN_WORLD`].
54
///
55
/// # Arguments
56
///
57
/// * `atlas_layout` - The atlas layout to add the texture to.
58
/// * `texture` - The source texture to add to the atlas.
59
/// * `atlas_texture` - The destination atlas texture to copy the source texture to.
60
pub fn add_texture(
61
&mut self,
62
atlas_layout: &mut TextureAtlasLayout,
63
texture: &Image,
64
atlas_texture: &mut Image,
65
) -> Result<usize, DynamicTextureAtlasBuilderError> {
66
let allocation = self.atlas_allocator.allocate(size2(
67
(texture.width() + self.padding).try_into().unwrap(),
68
(texture.height() + self.padding).try_into().unwrap(),
69
));
70
if let Some(allocation) = allocation {
71
assert!(
72
atlas_texture.asset_usage.contains(RenderAssetUsages::MAIN_WORLD),
73
"The atlas_texture image must have the RenderAssetUsages::MAIN_WORLD usage flag set"
74
);
75
76
self.place_texture(atlas_texture, allocation, texture)?;
77
let mut rect: URect = to_rect(allocation.rectangle);
78
rect.max = rect.max.saturating_sub(UVec2::splat(self.padding));
79
Ok(atlas_layout.add_texture(rect))
80
} else {
81
Err(DynamicTextureAtlasBuilderError::FailedToAllocateSpace)
82
}
83
}
84
85
fn place_texture(
86
&mut self,
87
atlas_texture: &mut Image,
88
allocation: Allocation,
89
texture: &Image,
90
) -> Result<(), DynamicTextureAtlasBuilderError> {
91
let mut rect = allocation.rectangle;
92
rect.max.x -= self.padding as i32;
93
rect.max.y -= self.padding as i32;
94
let atlas_width = atlas_texture.width() as usize;
95
let rect_width = rect.width() as usize;
96
let format_size = atlas_texture.texture_descriptor.format.pixel_size()?;
97
98
let Some(ref mut atlas_data) = atlas_texture.data else {
99
return Err(DynamicTextureAtlasBuilderError::UninitializedAtlas);
100
};
101
let Some(ref data) = texture.data else {
102
return Err(DynamicTextureAtlasBuilderError::UninitializedSourceTexture);
103
};
104
for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() {
105
let begin = (bound_y * atlas_width + rect.min.x as usize) * format_size;
106
let end = begin + rect_width * format_size;
107
let texture_begin = texture_y * rect_width * format_size;
108
let texture_end = texture_begin + rect_width * format_size;
109
atlas_data[begin..end].copy_from_slice(&data[texture_begin..texture_end]);
110
}
111
Ok(())
112
}
113
}
114
115
fn to_rect(rectangle: guillotiere::Rectangle) -> URect {
116
URect {
117
min: UVec2::new(
118
rectangle.min.x.try_into().unwrap(),
119
rectangle.min.y.try_into().unwrap(),
120
),
121
max: UVec2::new(
122
rectangle.max.x.try_into().unwrap(),
123
rectangle.max.y.try_into().unwrap(),
124
),
125
}
126
}
127
128
fn to_size2(vec2: UVec2) -> guillotiere::Size {
129
guillotiere::Size::new(vec2.x as i32, vec2.y as i32)
130
}
131
132