Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_sprite/src/sprite.rs
9377 views
1
use bevy_asset::{AsAssetId, AssetId, Assets, Handle};
2
use bevy_camera::visibility::{self, Visibility, VisibilityClass};
3
use bevy_color::Color;
4
use bevy_derive::{Deref, DerefMut};
5
use bevy_ecs::{component::Component, reflect::ReflectComponent};
6
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout};
7
use bevy_math::{Rect, UVec2, Vec2};
8
use bevy_reflect::{std_traits::ReflectDefault, PartialReflect, Reflect};
9
use bevy_transform::components::Transform;
10
11
use crate::TextureSlicer;
12
use core::hash::Hash;
13
14
/// Describes a sprite to be rendered to a 2D camera
15
#[derive(Component, Debug, Default, Clone, Reflect)]
16
#[require(Transform, Visibility, VisibilityClass, Anchor)]
17
#[reflect(Component, Default, Debug, Clone)]
18
#[component(on_add = visibility::add_visibility_class::<Sprite>)]
19
pub struct Sprite {
20
/// The image used to render the sprite
21
pub image: Handle<Image>,
22
/// The (optional) texture atlas used to render the sprite
23
pub texture_atlas: Option<TextureAtlas>,
24
/// The sprite's color tint
25
pub color: Color,
26
/// Flip the sprite along the `X` axis
27
pub flip_x: bool,
28
/// Flip the sprite along the `Y` axis
29
pub flip_y: bool,
30
/// An optional custom size for the sprite that will be used when rendering, instead of the size
31
/// of the sprite's image
32
pub custom_size: Option<Vec2>,
33
/// An optional rectangle representing the region of the sprite's image to render, instead of rendering
34
/// the full image. This is an easy one-off alternative to using a [`TextureAtlas`].
35
///
36
/// When used with a [`TextureAtlas`], the rect
37
/// is offset by the atlas's minimal (top-left) corner position.
38
pub rect: Option<Rect>,
39
/// How the sprite's image will be scaled.
40
pub image_mode: SpriteImageMode,
41
}
42
43
impl Sprite {
44
/// Create a Sprite with a custom size
45
pub fn sized(custom_size: Vec2) -> Self {
46
Sprite {
47
custom_size: Some(custom_size),
48
..Default::default()
49
}
50
}
51
52
/// Create a sprite from an image
53
pub fn from_image(image: Handle<Image>) -> Self {
54
Self {
55
image,
56
..Default::default()
57
}
58
}
59
60
/// Create a sprite from an image, with an associated texture atlas
61
pub fn from_atlas_image(image: Handle<Image>, atlas: TextureAtlas) -> Self {
62
Self {
63
image,
64
texture_atlas: Some(atlas),
65
..Default::default()
66
}
67
}
68
69
/// Create a sprite from a solid color
70
pub fn from_color(color: impl Into<Color>, size: Vec2) -> Self {
71
Self {
72
color: color.into(),
73
custom_size: Some(size),
74
..Default::default()
75
}
76
}
77
78
/// Computes the pixel point where `point_relative_to_sprite` is sampled
79
/// from in this sprite. `point_relative_to_sprite` must be in the sprite's
80
/// local frame. Returns an Ok if the point is inside the bounds of the
81
/// sprite (not just the image), and returns an Err otherwise.
82
pub fn compute_pixel_space_point(
83
&self,
84
point_relative_to_sprite: Vec2,
85
anchor: Anchor,
86
images: &Assets<Image>,
87
texture_atlases: &Assets<TextureAtlasLayout>,
88
) -> Result<Vec2, Vec2> {
89
let image_size = images
90
.get(&self.image)
91
.map(Image::size)
92
.unwrap_or(UVec2::ONE);
93
94
let atlas_rect = self
95
.texture_atlas
96
.as_ref()
97
.and_then(|s| s.texture_rect(texture_atlases))
98
.map(|r| r.as_rect());
99
let texture_rect = match (atlas_rect, self.rect) {
100
(None, None) => Rect::new(0.0, 0.0, image_size.x as f32, image_size.y as f32),
101
(None, Some(sprite_rect)) => sprite_rect,
102
(Some(atlas_rect), None) => atlas_rect,
103
(Some(atlas_rect), Some(mut sprite_rect)) => {
104
// Make the sprite rect relative to the atlas rect.
105
sprite_rect.min += atlas_rect.min;
106
sprite_rect.max += atlas_rect.min;
107
sprite_rect
108
}
109
};
110
111
let sprite_size = self.custom_size.unwrap_or_else(|| texture_rect.size());
112
let sprite_center = -anchor.as_vec() * sprite_size;
113
114
let mut point_relative_to_sprite_center = point_relative_to_sprite - sprite_center;
115
116
if self.flip_x {
117
point_relative_to_sprite_center.x *= -1.0;
118
}
119
// Texture coordinates start at the top left, whereas world coordinates start at the bottom
120
// left. So flip by default, and then don't flip if `flip_y` is set.
121
if !self.flip_y {
122
point_relative_to_sprite_center.y *= -1.0;
123
}
124
125
if sprite_size.x == 0.0 || sprite_size.y == 0.0 {
126
return Err(point_relative_to_sprite_center);
127
}
128
129
let sprite_to_texture_ratio = {
130
let texture_size = texture_rect.size();
131
Vec2::new(
132
texture_size.x / sprite_size.x,
133
texture_size.y / sprite_size.y,
134
)
135
};
136
137
let point_relative_to_texture =
138
point_relative_to_sprite_center * sprite_to_texture_ratio + texture_rect.center();
139
140
// TODO: Support `SpriteImageMode`.
141
142
if texture_rect.contains(point_relative_to_texture) {
143
Ok(point_relative_to_texture)
144
} else {
145
Err(point_relative_to_texture)
146
}
147
}
148
}
149
150
impl From<Handle<Image>> for Sprite {
151
fn from(image: Handle<Image>) -> Self {
152
Self::from_image(image)
153
}
154
}
155
156
impl AsAssetId for Sprite {
157
type Asset = Image;
158
159
fn as_asset_id(&self) -> AssetId<Self::Asset> {
160
self.image.id()
161
}
162
}
163
164
/// Controls how the image is altered when scaled.
165
#[derive(Default, Debug, Clone, Reflect, PartialEq)]
166
#[reflect(Debug, Default, Clone)]
167
pub enum SpriteImageMode {
168
/// The sprite will take on the size of the image by default, and will be stretched or shrunk if [`Sprite::custom_size`] is set.
169
#[default]
170
Auto,
171
/// The texture will be scaled to fit the rect bounds defined in [`Sprite::custom_size`].
172
/// Otherwise no scaling will be applied.
173
Scale(SpriteScalingMode),
174
/// The texture will be cut in 9 slices, keeping the texture in proportions on resize
175
Sliced(TextureSlicer),
176
/// The texture will be repeated if stretched beyond `stretched_value`
177
Tiled {
178
/// Should the image repeat horizontally
179
tile_x: bool,
180
/// Should the image repeat vertically
181
tile_y: bool,
182
/// The texture will repeat when the ratio between the *drawing dimensions* of texture and the
183
/// *original texture size* are above this value.
184
stretch_value: f32,
185
},
186
}
187
188
impl SpriteImageMode {
189
/// Returns true if this mode uses slices internally ([`SpriteImageMode::Sliced`] or [`SpriteImageMode::Tiled`])
190
#[inline]
191
pub fn uses_slices(&self) -> bool {
192
matches!(
193
self,
194
SpriteImageMode::Sliced(..) | SpriteImageMode::Tiled { .. }
195
)
196
}
197
198
/// Returns [`SpriteScalingMode`] if scale is presented or [`Option::None`] otherwise.
199
#[inline]
200
#[must_use]
201
pub const fn scale(&self) -> Option<SpriteScalingMode> {
202
if let SpriteImageMode::Scale(scale) = self {
203
Some(*scale)
204
} else {
205
None
206
}
207
}
208
}
209
210
/// Represents various modes for proportional scaling of a texture.
211
///
212
/// Can be used in [`SpriteImageMode::Scale`].
213
#[derive(Debug, Clone, Copy, PartialEq, Default, Reflect)]
214
#[reflect(Debug, Default, Clone)]
215
pub enum SpriteScalingMode {
216
/// Scale the texture uniformly (maintain the texture's aspect ratio)
217
/// so that both dimensions (width and height) of the texture will be equal
218
/// to or larger than the corresponding dimension of the target rectangle.
219
/// Fill sprite with a centered texture.
220
#[default]
221
FillCenter,
222
/// Scales the texture to fill the target rectangle while maintaining its aspect ratio.
223
/// One dimension of the texture will match the rectangle's size,
224
/// while the other dimension may exceed it.
225
/// The exceeding portion is aligned to the start:
226
/// * Horizontal overflow is left-aligned if the width exceeds the rectangle.
227
/// * Vertical overflow is top-aligned if the height exceeds the rectangle.
228
FillStart,
229
/// Scales the texture to fill the target rectangle while maintaining its aspect ratio.
230
/// One dimension of the texture will match the rectangle's size,
231
/// while the other dimension may exceed it.
232
/// The exceeding portion is aligned to the end:
233
/// * Horizontal overflow is right-aligned if the width exceeds the rectangle.
234
/// * Vertical overflow is bottom-aligned if the height exceeds the rectangle.
235
FillEnd,
236
/// Scaling the texture will maintain the original aspect ratio
237
/// and ensure that the original texture fits entirely inside the rect.
238
/// At least one axis (x or y) will fit exactly. The result is centered inside the rect.
239
FitCenter,
240
/// Scaling the texture will maintain the original aspect ratio
241
/// and ensure that the original texture fits entirely inside rect.
242
/// At least one axis (x or y) will fit exactly.
243
/// Aligns the result to the left and top edges of rect.
244
FitStart,
245
/// Scaling the texture will maintain the original aspect ratio
246
/// and ensure that the original texture fits entirely inside rect.
247
/// At least one axis (x or y) will fit exactly.
248
/// Aligns the result to the right and bottom edges of rect.
249
FitEnd,
250
}
251
252
/// Normalized (relative to its size) offset of a 2d renderable entity from its [`Transform`].
253
#[derive(Component, Debug, Clone, Copy, PartialEq, Deref, DerefMut, Reflect)]
254
#[reflect(Component, Default, Debug, PartialEq, Clone)]
255
#[doc(alias = "pivot")]
256
pub struct Anchor(pub Vec2);
257
258
impl Eq for Anchor {}
259
impl Hash for Anchor {
260
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
261
self.0.reflect_hash().hash(state);
262
}
263
}
264
265
impl Anchor {
266
pub const BOTTOM_LEFT: Self = Self(Vec2::new(-0.5, -0.5));
267
pub const BOTTOM_CENTER: Self = Self(Vec2::new(0.0, -0.5));
268
pub const BOTTOM_RIGHT: Self = Self(Vec2::new(0.5, -0.5));
269
pub const CENTER_LEFT: Self = Self(Vec2::new(-0.5, 0.0));
270
pub const CENTER: Self = Self(Vec2::ZERO);
271
pub const CENTER_RIGHT: Self = Self(Vec2::new(0.5, 0.0));
272
pub const TOP_LEFT: Self = Self(Vec2::new(-0.5, 0.5));
273
pub const TOP_CENTER: Self = Self(Vec2::new(0.0, 0.5));
274
pub const TOP_RIGHT: Self = Self(Vec2::new(0.5, 0.5));
275
276
pub fn as_vec(&self) -> Vec2 {
277
self.0
278
}
279
}
280
281
impl Default for Anchor {
282
fn default() -> Self {
283
Self::CENTER
284
}
285
}
286
287
impl From<Vec2> for Anchor {
288
fn from(value: Vec2) -> Self {
289
Self(value)
290
}
291
}
292
293
#[cfg(test)]
294
mod tests {
295
use bevy_asset::{Assets, RenderAssetUsages};
296
use bevy_color::Color;
297
use bevy_image::{Image, ToExtents};
298
use bevy_image::{TextureAtlas, TextureAtlasLayout};
299
use bevy_math::{Rect, URect, UVec2, Vec2};
300
use wgpu_types::{TextureDimension, TextureFormat};
301
302
use crate::Anchor;
303
304
use super::Sprite;
305
306
/// Makes a new image of the specified size.
307
fn make_image(size: UVec2) -> Image {
308
Image::new_fill(
309
size.to_extents(),
310
TextureDimension::D2,
311
&[0, 0, 0, 255],
312
TextureFormat::Rgba8Unorm,
313
RenderAssetUsages::all(),
314
)
315
}
316
317
#[test]
318
fn compute_pixel_space_point_for_regular_sprite() {
319
let mut image_assets = Assets::<Image>::default();
320
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
321
322
let image = image_assets.add(make_image(UVec2::new(5, 10)));
323
324
let sprite = Sprite {
325
image,
326
..Default::default()
327
};
328
329
let compute = |point| {
330
sprite.compute_pixel_space_point(
331
point,
332
Anchor::default(),
333
&image_assets,
334
&texture_atlas_assets,
335
)
336
};
337
assert_eq!(compute(Vec2::new(-2.0, -4.5)), Ok(Vec2::new(0.5, 9.5)));
338
assert_eq!(compute(Vec2::new(0.0, 0.0)), Ok(Vec2::new(2.5, 5.0)));
339
assert_eq!(compute(Vec2::new(0.0, 4.5)), Ok(Vec2::new(2.5, 0.5)));
340
assert_eq!(compute(Vec2::new(3.0, 0.0)), Err(Vec2::new(5.5, 5.0)));
341
assert_eq!(compute(Vec2::new(-3.0, 0.0)), Err(Vec2::new(-0.5, 5.0)));
342
}
343
344
#[test]
345
fn compute_pixel_space_point_for_color_sprite() {
346
let image_assets = Assets::<Image>::default();
347
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
348
349
// This also tests the `custom_size` field.
350
let sprite = Sprite::from_color(Color::BLACK, Vec2::new(50.0, 100.0));
351
352
let compute = |point| {
353
sprite
354
.compute_pixel_space_point(
355
point,
356
Anchor::default(),
357
&image_assets,
358
&texture_atlas_assets,
359
)
360
// Round to remove floating point errors.
361
.map(|x| (x * 1e5).round() / 1e5)
362
.map_err(|x| (x * 1e5).round() / 1e5)
363
};
364
assert_eq!(compute(Vec2::new(-20.0, -40.0)), Ok(Vec2::new(0.1, 0.9)));
365
assert_eq!(compute(Vec2::new(0.0, 10.0)), Ok(Vec2::new(0.5, 0.4)));
366
assert_eq!(compute(Vec2::new(75.0, 100.0)), Err(Vec2::new(2.0, -0.5)));
367
assert_eq!(compute(Vec2::new(-75.0, -100.0)), Err(Vec2::new(-1.0, 1.5)));
368
assert_eq!(compute(Vec2::new(-30.0, -40.0)), Err(Vec2::new(-0.1, 0.9)));
369
}
370
371
#[test]
372
fn compute_pixel_space_point_for_sprite_with_anchor_bottom_left() {
373
let mut image_assets = Assets::<Image>::default();
374
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
375
376
let image = image_assets.add(make_image(UVec2::new(5, 10)));
377
378
let sprite = Sprite {
379
image,
380
..Default::default()
381
};
382
let anchor = Anchor::BOTTOM_LEFT;
383
384
let compute = |point| {
385
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
386
};
387
assert_eq!(compute(Vec2::new(0.5, 9.5)), Ok(Vec2::new(0.5, 0.5)));
388
assert_eq!(compute(Vec2::new(2.5, 5.0)), Ok(Vec2::new(2.5, 5.0)));
389
assert_eq!(compute(Vec2::new(2.5, 9.5)), Ok(Vec2::new(2.5, 0.5)));
390
assert_eq!(compute(Vec2::new(5.5, 5.0)), Err(Vec2::new(5.5, 5.0)));
391
assert_eq!(compute(Vec2::new(-0.5, 5.0)), Err(Vec2::new(-0.5, 5.0)));
392
}
393
394
#[test]
395
fn compute_pixel_space_point_for_sprite_with_anchor_top_right() {
396
let mut image_assets = Assets::<Image>::default();
397
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
398
399
let image = image_assets.add(make_image(UVec2::new(5, 10)));
400
401
let sprite = Sprite {
402
image,
403
..Default::default()
404
};
405
let anchor = Anchor::TOP_RIGHT;
406
407
let compute = |point| {
408
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
409
};
410
assert_eq!(compute(Vec2::new(-4.5, -0.5)), Ok(Vec2::new(0.5, 0.5)));
411
assert_eq!(compute(Vec2::new(-2.5, -5.0)), Ok(Vec2::new(2.5, 5.0)));
412
assert_eq!(compute(Vec2::new(-2.5, -0.5)), Ok(Vec2::new(2.5, 0.5)));
413
assert_eq!(compute(Vec2::new(0.5, -5.0)), Err(Vec2::new(5.5, 5.0)));
414
assert_eq!(compute(Vec2::new(-5.5, -5.0)), Err(Vec2::new(-0.5, 5.0)));
415
}
416
417
#[test]
418
fn compute_pixel_space_point_for_sprite_with_anchor_flip_x() {
419
let mut image_assets = Assets::<Image>::default();
420
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
421
422
let image = image_assets.add(make_image(UVec2::new(5, 10)));
423
424
let sprite = Sprite {
425
image,
426
flip_x: true,
427
..Default::default()
428
};
429
let anchor = Anchor::BOTTOM_LEFT;
430
431
let compute = |point| {
432
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
433
};
434
assert_eq!(compute(Vec2::new(0.5, 9.5)), Ok(Vec2::new(4.5, 0.5)));
435
assert_eq!(compute(Vec2::new(2.5, 5.0)), Ok(Vec2::new(2.5, 5.0)));
436
assert_eq!(compute(Vec2::new(2.5, 9.5)), Ok(Vec2::new(2.5, 0.5)));
437
assert_eq!(compute(Vec2::new(5.5, 5.0)), Err(Vec2::new(-0.5, 5.0)));
438
assert_eq!(compute(Vec2::new(-0.5, 5.0)), Err(Vec2::new(5.5, 5.0)));
439
}
440
441
#[test]
442
fn compute_pixel_space_point_for_sprite_with_anchor_flip_y() {
443
let mut image_assets = Assets::<Image>::default();
444
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
445
446
let image = image_assets.add(make_image(UVec2::new(5, 10)));
447
448
let sprite = Sprite {
449
image,
450
flip_y: true,
451
..Default::default()
452
};
453
let anchor = Anchor::TOP_RIGHT;
454
455
let compute = |point| {
456
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
457
};
458
assert_eq!(compute(Vec2::new(-4.5, -0.5)), Ok(Vec2::new(0.5, 9.5)));
459
assert_eq!(compute(Vec2::new(-2.5, -5.0)), Ok(Vec2::new(2.5, 5.0)));
460
assert_eq!(compute(Vec2::new(-2.5, -0.5)), Ok(Vec2::new(2.5, 9.5)));
461
assert_eq!(compute(Vec2::new(0.5, -5.0)), Err(Vec2::new(5.5, 5.0)));
462
assert_eq!(compute(Vec2::new(-5.5, -5.0)), Err(Vec2::new(-0.5, 5.0)));
463
}
464
465
#[test]
466
fn compute_pixel_space_point_for_sprite_with_rect() {
467
let mut image_assets = Assets::<Image>::default();
468
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
469
470
let image = image_assets.add(make_image(UVec2::new(5, 10)));
471
472
let sprite = Sprite {
473
image,
474
rect: Some(Rect::new(1.5, 3.0, 3.0, 9.5)),
475
..Default::default()
476
};
477
let anchor = Anchor::BOTTOM_LEFT;
478
479
let compute = |point| {
480
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
481
};
482
assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(2.0, 9.0)));
483
// The pixel is outside the rect, but is still a valid pixel in the image.
484
assert_eq!(compute(Vec2::new(2.0, 2.5)), Err(Vec2::new(3.5, 7.0)));
485
}
486
487
#[test]
488
fn compute_pixel_space_point_for_texture_atlas_sprite() {
489
let mut image_assets = Assets::<Image>::default();
490
let mut texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
491
492
let image = image_assets.add(make_image(UVec2::new(5, 10)));
493
let texture_atlas = texture_atlas_assets.add(TextureAtlasLayout {
494
size: UVec2::new(5, 10),
495
textures: vec![URect::new(1, 1, 4, 4)],
496
});
497
498
let sprite = Sprite {
499
image,
500
texture_atlas: Some(TextureAtlas {
501
layout: texture_atlas,
502
index: 0,
503
}),
504
..Default::default()
505
};
506
let anchor = Anchor::BOTTOM_LEFT;
507
508
let compute = |point| {
509
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
510
};
511
assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(1.5, 3.5)));
512
// The pixel is outside the texture atlas, but is still a valid pixel in the image.
513
assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(5.0, 1.5)));
514
}
515
516
#[test]
517
fn compute_pixel_space_point_for_texture_atlas_sprite_with_rect() {
518
let mut image_assets = Assets::<Image>::default();
519
let mut texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
520
521
let image = image_assets.add(make_image(UVec2::new(5, 10)));
522
let texture_atlas = texture_atlas_assets.add(TextureAtlasLayout {
523
size: UVec2::new(5, 10),
524
textures: vec![URect::new(1, 1, 4, 4)],
525
});
526
527
let sprite = Sprite {
528
image,
529
texture_atlas: Some(TextureAtlas {
530
layout: texture_atlas,
531
index: 0,
532
}),
533
// The rect is relative to the texture atlas sprite.
534
rect: Some(Rect::new(1.5, 1.5, 3.0, 3.0)),
535
..Default::default()
536
};
537
let anchor = Anchor::BOTTOM_LEFT;
538
539
let compute = |point| {
540
sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets)
541
};
542
assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(3.0, 3.5)));
543
// The pixel is outside the texture atlas, but is still a valid pixel in the image.
544
assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(6.5, 1.5)));
545
}
546
547
#[test]
548
fn compute_pixel_space_point_for_sprite_with_custom_size_and_rect() {
549
let mut image_assets = Assets::<Image>::default();
550
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
551
552
let image = image_assets.add(make_image(UVec2::new(5, 10)));
553
554
let sprite = Sprite {
555
image,
556
custom_size: Some(Vec2::new(100.0, 50.0)),
557
rect: Some(Rect::new(0.0, 0.0, 5.0, 5.0)),
558
..Default::default()
559
};
560
561
let compute = |point| {
562
sprite.compute_pixel_space_point(
563
point,
564
Anchor::default(),
565
&image_assets,
566
&texture_atlas_assets,
567
)
568
};
569
assert_eq!(compute(Vec2::new(30.0, 15.0)), Ok(Vec2::new(4.0, 1.0)));
570
assert_eq!(compute(Vec2::new(-10.0, -15.0)), Ok(Vec2::new(2.0, 4.0)));
571
// The pixel is outside the texture atlas, but is still a valid pixel in the image.
572
assert_eq!(compute(Vec2::new(0.0, 35.0)), Err(Vec2::new(2.5, -1.0)));
573
}
574
575
#[test]
576
fn compute_pixel_space_point_for_sprite_with_zero_custom_size() {
577
let mut image_assets = Assets::<Image>::default();
578
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
579
580
let image = image_assets.add(make_image(UVec2::new(5, 10)));
581
582
let sprite = Sprite {
583
image,
584
custom_size: Some(Vec2::new(0.0, 0.0)),
585
..Default::default()
586
};
587
588
let compute = |point| {
589
sprite.compute_pixel_space_point(
590
point,
591
Anchor::default(),
592
&image_assets,
593
&texture_atlas_assets,
594
)
595
};
596
assert_eq!(compute(Vec2::new(30.0, 15.0)), Err(Vec2::new(30.0, -15.0)));
597
assert_eq!(
598
compute(Vec2::new(-10.0, -15.0)),
599
Err(Vec2::new(-10.0, 15.0))
600
);
601
// The pixel is outside the texture atlas, but is still a valid pixel in the image.
602
assert_eq!(compute(Vec2::new(0.0, 35.0)), Err(Vec2::new(0.0, -35.0)));
603
}
604
}
605
606