Skip to main content

fyrox_impl/scene/
sprite.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Contains all structures and methods to create and manage sprites.
22//!
23//! For more info see [`Sprite`].
24
25use crate::scene::animation::spritesheet::SpriteSheetAnimation;
26use crate::{
27    core::{
28        algebra::{Vector2, Vector3, Vector4},
29        color::Color,
30        math::{aabb::AxisAlignedBoundingBox, Rect, TriangleDefinition},
31        pool::Handle,
32        reflect::prelude::*,
33        type_traits::prelude::*,
34        uuid::{uuid, Uuid},
35        value_as_u8_slice,
36        variable::InheritableVariable,
37        visitor::{Visit, VisitResult, Visitor},
38    },
39    graph::{constructor::ConstructorProvider, SceneGraph},
40    material::{Material, MaterialResource},
41    renderer::{self, bundle::RenderContext},
42    scene::{
43        base::{Base, BaseBuilder},
44        graph::Graph,
45        mesh::{
46            buffer::{
47                VertexAttributeDataType, VertexAttributeDescriptor, VertexAttributeUsage,
48                VertexTrait,
49            },
50            RenderPath,
51        },
52        node::{constructor::NodeConstructor, Node, NodeTrait, RdcControlFlow},
53    },
54};
55use bytemuck::{Pod, Zeroable};
56use std::ops::{Deref, DerefMut};
57
58/// A vertex for sprites.
59#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
60#[repr(C)] // OpenGL expects this structure packed as in C
61pub struct SpriteVertex {
62    /// Position of vertex in local coordinates.
63    pub position: Vector3<f32>,
64    /// Texture coordinates.
65    pub tex_coord: Vector2<f32>,
66    /// Sprite parameters: x - size, y - rotation, z - dx, w - dy.
67    pub params: Vector4<f32>,
68    /// Diffuse color.
69    pub color: Color,
70}
71
72impl VertexTrait for SpriteVertex {
73    fn layout() -> &'static [VertexAttributeDescriptor] {
74        &[
75            VertexAttributeDescriptor {
76                usage: VertexAttributeUsage::Position,
77                data_type: VertexAttributeDataType::F32,
78                size: 3,
79                divisor: 0,
80                shader_location: 0,
81                normalized: false,
82            },
83            VertexAttributeDescriptor {
84                usage: VertexAttributeUsage::TexCoord0,
85                data_type: VertexAttributeDataType::F32,
86                size: 2,
87                divisor: 0,
88                shader_location: 1,
89                normalized: false,
90            },
91            VertexAttributeDescriptor {
92                usage: VertexAttributeUsage::Custom0,
93                data_type: VertexAttributeDataType::F32,
94                size: 4,
95                divisor: 0,
96                shader_location: 2,
97                normalized: false,
98            },
99            VertexAttributeDescriptor {
100                usage: VertexAttributeUsage::Color,
101                data_type: VertexAttributeDataType::U8,
102                size: 4,
103                divisor: 0,
104                shader_location: 3,
105                normalized: true,
106            },
107        ]
108    }
109}
110
111/// Sprite is a billboard which always faces towards the camera. It can be used as a "model" for bullets,
112/// and so on.
113///
114/// # Performance
115///
116/// Sprites rendering uses batching to reduce the number of draw calls - it basically merges multiple
117/// sprites with the same material into one mesh and renders it in a single draw call which is quite
118/// fast and can handle tens of thousands of sprites with ease. You should not, however, use sprites to
119/// make particle systems, use [ParticleSystem](super::particle_system::ParticleSystem) instead.
120///
121/// # Flipping
122///
123/// It is possible to flip the sprite on both axes, vertical and horizontal. Use [`Sprite::set_flip_x`]
124/// and [`Sprite::set_flip_y`] methods to flip the sprite on desired axes.
125///
126/// # Example
127///
128/// The following example creates a new sprite node with a material, that uses a simple smoke
129/// texture:
130///
131/// ```rust
132/// # use fyrox_impl::{
133/// #     asset::manager::ResourceManager,
134/// #     core::pool::Handle,
135/// #     material::{Material, MaterialResource},
136/// #     resource::texture::Texture,
137/// #     scene::{base::BaseBuilder, graph::Graph, node::Node, sprite::SpriteBuilder},
138/// # };
139/// # use fyrox_impl::scene::sprite::Sprite;
140/// #
141/// fn create_smoke(resource_manager: ResourceManager, graph: &mut Graph) -> Handle<Sprite> {
142///     let mut material = Material::standard_sprite();
143///
144///     material
145///         .bind("smoke.png", resource_manager.request::<Texture>("smoke.png"));
146///
147///     SpriteBuilder::new(BaseBuilder::new())
148///         .with_material(MaterialResource::new_embedded(material))
149///         .build(graph)
150/// }
151/// ```
152///
153/// Keep in mind, that this example creates new material instance each call of the method and
154/// **does not** reuse it. Ideally, you should reuse the shared material across multiple instances
155/// to get the best possible performance. Otherwise, each your sprite will be put in a separate batch
156/// which will force your GPU to render a single sprite in dedicated draw call which is quite slow.
157#[derive(Debug, Reflect, Clone, ComponentProvider, Visit)]
158#[reflect(derived_type = "Node")]
159pub struct Sprite {
160    base: Base,
161
162    #[reflect(setter = "set_uv_rect")]
163    uv_rect: InheritableVariable<Rect<f32>>,
164
165    material: InheritableVariable<MaterialResource>,
166
167    #[reflect(setter = "set_color")]
168    color: InheritableVariable<Color>,
169
170    #[reflect(min_value = 0.0, step = 0.1)]
171    #[reflect(setter = "set_size")]
172    size: InheritableVariable<f32>,
173
174    #[reflect(setter = "set_rotation")]
175    rotation: InheritableVariable<f32>,
176
177    #[reflect(setter = "set_flip_x")]
178    flip_x: InheritableVariable<bool>,
179
180    #[reflect(setter = "set_flip_y")]
181    flip_y: InheritableVariable<bool>,
182}
183
184impl Deref for Sprite {
185    type Target = Base;
186
187    fn deref(&self) -> &Self::Target {
188        &self.base
189    }
190}
191
192impl DerefMut for Sprite {
193    fn deref_mut(&mut self) -> &mut Self::Target {
194        &mut self.base
195    }
196}
197
198impl Default for Sprite {
199    fn default() -> Self {
200        SpriteBuilder::new(BaseBuilder::new()).build_sprite()
201    }
202}
203
204impl TypeUuidProvider for Sprite {
205    fn type_uuid() -> Uuid {
206        uuid!("60fd7e34-46c1-4ae9-8803-1f5f4c341518")
207    }
208}
209
210impl Sprite {
211    /// Sets new size of sprite. Since sprite is always square, size defines half of width or height, so actual size
212    /// will be doubled. Default value is 0.2.
213    ///
214    /// Negative values could be used to "inverse" the image on the sprite.
215    pub fn set_size(&mut self, size: f32) -> f32 {
216        self.size.set_value_and_mark_modified(size)
217    }
218
219    /// Returns current size of sprite.
220    pub fn size(&self) -> f32 {
221        *self.size
222    }
223
224    /// Sets new color of sprite. Default is White.
225    pub fn set_color(&mut self, color: Color) -> Color {
226        self.color.set_value_and_mark_modified(color)
227    }
228
229    /// Returns current color of sprite.
230    pub fn color(&self) -> Color {
231        *self.color
232    }
233
234    /// Sets rotation around "look" axis in radians. Default is 0.0.
235    pub fn set_rotation(&mut self, rotation: f32) -> f32 {
236        self.rotation.set_value_and_mark_modified(rotation)
237    }
238
239    /// Returns rotation in radians.
240    pub fn rotation(&self) -> f32 {
241        *self.rotation
242    }
243
244    /// Returns a reference to the current material used by the sprite.
245    pub fn material(&self) -> &InheritableVariable<MaterialResource> {
246        &self.material
247    }
248
249    /// Returns a reference to the current material used by the sprite.
250    pub fn material_mut(&mut self) -> &mut InheritableVariable<MaterialResource> {
251        &mut self.material
252    }
253
254    /// Returns a rectangle that defines the region in texture which will be rendered. The coordinates are normalized
255    /// which means `[0; 0]` corresponds to top-left corner of the texture and `[1; 1]` corresponds to right-bottom
256    /// corner.
257    pub fn uv_rect(&self) -> Rect<f32> {
258        *self.uv_rect
259    }
260
261    /// Sets a rectangle that defines the region in texture which will be rendered. The coordinates are normalized
262    /// which means `[0; 0]` corresponds to top-left corner of the texture and `[1; 1]` corresponds to right-bottom
263    /// corner.
264    ///
265    /// The coordinates can exceed `[1; 1]` boundary to create tiling effect (keep in mind that tiling should be
266    /// enabled in texture options).
267    ///
268    /// The default value is `(0, 0, 1, 1)` rectangle which corresponds to entire texture.
269    pub fn set_uv_rect(&mut self, uv_rect: Rect<f32>) -> Rect<f32> {
270        self.uv_rect.set_value_and_mark_modified(uv_rect)
271    }
272
273    /// Enables (`true`) or disables (`false`) horizontal flipping of the sprite.
274    pub fn set_flip_x(&mut self, flip: bool) -> bool {
275        self.flip_x.set_value_and_mark_modified(flip)
276    }
277
278    /// Returns `true` if the sprite is flipped horizontally, `false` - otherwise.
279    pub fn is_flip_x(&self) -> bool {
280        *self.flip_x
281    }
282
283    /// Enables (`true`) or disables (`false`) vertical flipping of the sprite.
284    pub fn set_flip_y(&mut self, flip: bool) -> bool {
285        self.flip_y.set_value_and_mark_modified(flip)
286    }
287
288    /// Returns `true` if the sprite is flipped vertically, `false` - otherwise.
289    pub fn is_flip_y(&self) -> bool {
290        *self.flip_y
291    }
292
293    /// Applies the given sprite sheet animation. This method assumes that the rectangle's material
294    /// has the `diffuseTexture` resource.
295    pub fn apply_animation(&mut self, animation: &SpriteSheetAnimation) {
296        self.material()
297            .data_ref()
298            .bind("diffuseTexture", animation.texture());
299        self.set_uv_rect(animation.current_frame_uv_rect().unwrap_or_default());
300    }
301}
302
303impl ConstructorProvider<Node, Graph> for Sprite {
304    fn constructor() -> NodeConstructor {
305        NodeConstructor::new::<Self>().with_variant("Sprite (3D)", |_| {
306            SpriteBuilder::new(BaseBuilder::new().with_name("Sprite"))
307                .build_node()
308                .into()
309        })
310    }
311}
312
313impl NodeTrait for Sprite {
314    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
315        AxisAlignedBoundingBox::from_radius(*self.size)
316    }
317
318    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
319        self.base.world_bounding_box()
320    }
321
322    fn id(&self) -> Uuid {
323        Self::type_uuid()
324    }
325
326    fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
327        if !self.should_be_rendered(ctx.frustum, ctx.render_mask) {
328            return RdcControlFlow::Continue;
329        }
330
331        if renderer::is_shadow_pass(ctx.render_pass_name) || !self.cast_shadows() {
332            return RdcControlFlow::Continue;
333        }
334
335        let position = self.global_position();
336
337        type Vertex = SpriteVertex;
338
339        let lx = self.uv_rect.position.x;
340        let rx = self.uv_rect.position.x + self.uv_rect.size.x;
341        let ty = self.uv_rect.position.y;
342        let by = self.uv_rect.position.y + self.uv_rect.size.y;
343
344        let vertices = [
345            Vertex {
346                position,
347                tex_coord: Vector2::new(
348                    if *self.flip_x { lx } else { rx },
349                    if *self.flip_y { by } else { ty },
350                ),
351                params: Vector4::new(*self.size, *self.rotation, 0.5, 0.5),
352                color: *self.color,
353            },
354            Vertex {
355                position,
356                tex_coord: Vector2::new(
357                    if *self.flip_x { rx } else { lx },
358                    if *self.flip_y { by } else { ty },
359                ),
360                params: Vector4::new(*self.size, *self.rotation, -0.5, 0.5),
361                color: *self.color,
362            },
363            Vertex {
364                position,
365                tex_coord: Vector2::new(
366                    if *self.flip_x { rx } else { lx },
367                    if *self.flip_y { ty } else { by },
368                ),
369                params: Vector4::new(*self.size, *self.rotation, -0.5, -0.5),
370                color: *self.color,
371            },
372            Vertex {
373                position,
374                tex_coord: Vector2::new(
375                    if *self.flip_x { lx } else { rx },
376                    if *self.flip_y { ty } else { by },
377                ),
378                params: Vector4::new(*self.size, *self.rotation, 0.5, -0.5),
379                color: *self.color,
380            },
381        ];
382
383        let triangles = [TriangleDefinition([0, 1, 2]), TriangleDefinition([0, 2, 3])];
384
385        let sort_index = ctx.calculate_sorting_index(self.global_position());
386
387        ctx.storage.push_triangles(
388            ctx.dynamic_surface_cache,
389            Vertex::layout(),
390            &self.material,
391            RenderPath::Forward,
392            sort_index,
393            self.handle(),
394            &mut move |mut vertex_buffer, mut triangle_buffer| {
395                let start_vertex_index = vertex_buffer.vertex_count();
396
397                for vertex in vertices.iter() {
398                    vertex_buffer
399                        .push_vertex_raw(value_as_u8_slice(vertex))
400                        .unwrap();
401                }
402
403                triangle_buffer
404                    .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
405            },
406        );
407
408        RdcControlFlow::Continue
409    }
410}
411
412/// Sprite builder allows you to construct sprite in declarative manner.
413/// This is typical implementation of Builder pattern.
414pub struct SpriteBuilder {
415    base_builder: BaseBuilder,
416    uv_rect: Rect<f32>,
417    material: MaterialResource,
418    color: Color,
419    size: f32,
420    rotation: f32,
421    flip_x: bool,
422    flip_y: bool,
423}
424
425impl SpriteBuilder {
426    /// Creates new builder with default state (white opaque color, 0.2 size, zero rotation).
427    pub fn new(base_builder: BaseBuilder) -> Self {
428        Self {
429            base_builder,
430            material: MaterialResource::new_ok(
431                Uuid::new_v4(),
432                Default::default(),
433                Material::standard_sprite(),
434            ),
435            uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
436            color: Color::WHITE,
437            size: 0.2,
438            rotation: 0.0,
439            flip_x: false,
440            flip_y: false,
441        }
442    }
443
444    /// Sets desired portion of the texture for the sprite. See [`Sprite::set_uv_rect`]
445    /// for more info.
446    pub fn with_uv_rect(mut self, uv_rect: Rect<f32>) -> Self {
447        self.uv_rect = uv_rect;
448        self
449    }
450
451    /// Sets the desired material of the sprite.
452    pub fn with_material(mut self, material: MaterialResource) -> Self {
453        self.material = material;
454        self
455    }
456
457    /// Sets desired color.
458    pub fn with_color(mut self, color: Color) -> Self {
459        self.color = color;
460        self
461    }
462
463    /// Sets desired size.
464    pub fn with_size(mut self, size: f32) -> Self {
465        self.size = size;
466        self
467    }
468
469    /// Sets desired rotation.
470    pub fn with_rotation(mut self, rotation: f32) -> Self {
471        self.rotation = rotation;
472        self
473    }
474
475    /// Flips the sprite horizontally.
476    pub fn with_flip_x(mut self, flip_x: bool) -> Self {
477        self.flip_x = flip_x;
478        self
479    }
480
481    /// Flips the sprite vertically.
482    pub fn with_flip_y(mut self, flip_y: bool) -> Self {
483        self.flip_y = flip_y;
484        self
485    }
486
487    fn build_sprite(self) -> Sprite {
488        Sprite {
489            base: self.base_builder.build_base(),
490            material: self.material.into(),
491            uv_rect: self.uv_rect.into(),
492            color: self.color.into(),
493            size: self.size.into(),
494            rotation: self.rotation.into(),
495            flip_x: self.flip_x.into(),
496            flip_y: self.flip_y.into(),
497        }
498    }
499
500    /// Creates new sprite instance.
501    pub fn build_node(self) -> Node {
502        Node::new(self.build_sprite())
503    }
504
505    /// Creates new sprite instance and adds it to the graph.
506    pub fn build(self, graph: &mut Graph) -> Handle<Sprite> {
507        graph.add_node(self.build_node()).to_variant()
508    }
509}