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