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::node::constructor::NodeConstructor;
26use crate::scene::node::RdcControlFlow;
27use crate::{
28    core::{
29        algebra::{Vector2, Vector3},
30        color::Color,
31        math::{aabb::AxisAlignedBoundingBox, Rect, TriangleDefinition},
32        pool::Handle,
33        reflect::prelude::*,
34        type_traits::prelude::*,
35        uuid::{uuid, Uuid},
36        variable::InheritableVariable,
37        visitor::{Visit, VisitResult, Visitor},
38    },
39    material,
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::{Node, NodeTrait},
53    },
54};
55use bytemuck::{Pod, Zeroable};
56use fyrox_core::value_as_u8_slice;
57use fyrox_graph::constructor::ConstructorProvider;
58use fyrox_graph::BaseSceneGraph;
59use std::ops::{Deref, DerefMut};
60
61/// A vertex for sprites.
62#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
63#[repr(C)] // OpenGL expects this structure packed as in C
64pub struct SpriteVertex {
65    /// Position of vertex in local coordinates.
66    pub position: Vector3<f32>,
67    /// Texture coordinates.
68    pub tex_coord: Vector2<f32>,
69    /// Sprite parameters: x - size, y - rotation.
70    pub params: Vector2<f32>,
71    /// Diffuse color.
72    pub color: Color,
73}
74
75impl VertexTrait for SpriteVertex {
76    fn layout() -> &'static [VertexAttributeDescriptor] {
77        &[
78            VertexAttributeDescriptor {
79                usage: VertexAttributeUsage::Position,
80                data_type: VertexAttributeDataType::F32,
81                size: 3,
82                divisor: 0,
83                shader_location: 0,
84                normalized: false,
85            },
86            VertexAttributeDescriptor {
87                usage: VertexAttributeUsage::TexCoord0,
88                data_type: VertexAttributeDataType::F32,
89                size: 2,
90                divisor: 0,
91                shader_location: 1,
92                normalized: false,
93            },
94            VertexAttributeDescriptor {
95                usage: VertexAttributeUsage::Custom0,
96                data_type: VertexAttributeDataType::F32,
97                size: 2,
98                divisor: 0,
99                shader_location: 2,
100                normalized: false,
101            },
102            VertexAttributeDescriptor {
103                usage: VertexAttributeUsage::Color,
104                data_type: VertexAttributeDataType::U8,
105                size: 4,
106                divisor: 0,
107                shader_location: 3,
108                normalized: true,
109            },
110        ]
111    }
112}
113
114/// Sprite is a billboard which always faces towards camera. It can be used as a "model" for bullets,
115/// and so on.
116///
117/// # Depth sorting
118///
119/// Sprites are **not** depth-sorted so there could be some blending issues if multiple sprites are
120/// stacked one behind another.
121///
122/// # Performance
123///
124/// Sprites rendering uses batching to reduce amount of draw calls - it basically merges multiple
125/// sprites with the same material into one mesh and renders it in a single draw call which is quite
126/// fast and can handle tens of thousands sprites with ease. You should not, however, use sprites to
127/// make particle systems, use [ParticleSystem](super::particle_system::ParticleSystem) instead.
128///
129/// # Example
130///
131/// The following example creates a new sprite node with a material, that uses a simple smoke
132/// texture:
133///
134/// ```rust
135/// # use fyrox_impl::{
136/// #     asset::manager::ResourceManager,
137/// #     core::pool::Handle,
138/// #     material::{Material, MaterialResource},
139/// #     resource::texture::Texture,
140/// #     scene::{base::BaseBuilder, graph::Graph, node::Node, sprite::SpriteBuilder},
141/// # };
142/// #
143/// fn create_smoke(resource_manager: ResourceManager, graph: &mut Graph) -> Handle<Node> {
144///     let mut material = Material::standard_sprite();
145///
146///     material
147///         .bind("smoke.png", resource_manager.request::<Texture>("smoke.png"));
148///
149///     SpriteBuilder::new(BaseBuilder::new())
150///         .with_material(MaterialResource::new_ok(Default::default(), material))
151///         .build(graph)
152/// }
153/// ```
154///
155/// Keep in mind, that this example creates new material instance each call of the method and
156/// **does not** reuse it. Ideally, you should reuse the shared material across multiple instances
157/// to get best possible performance. Otherwise, each your sprite will be put in a separate batch
158/// which will force your GPU to render a single sprite in dedicated draw call which is quite slow.
159#[derive(Debug, Reflect, Clone, ComponentProvider)]
160pub struct Sprite {
161    base: Base,
162
163    #[reflect(setter = "set_uv_rect")]
164    uv_rect: InheritableVariable<Rect<f32>>,
165
166    material: InheritableVariable<MaterialResource>,
167
168    #[reflect(setter = "set_color")]
169    color: InheritableVariable<Color>,
170
171    #[reflect(min_value = 0.0, step = 0.1)]
172    #[reflect(setter = "set_size")]
173    size: InheritableVariable<f32>,
174
175    #[reflect(setter = "set_rotation")]
176    rotation: InheritableVariable<f32>,
177}
178
179impl Visit for Sprite {
180    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
181        let mut region = visitor.enter_region(name)?;
182
183        if region.is_reading() {
184            if let Some(material) =
185                material::visit_old_texture_as_material(&mut region, Material::standard_sprite)
186            {
187                self.material = material.into();
188            } else {
189                self.material.visit("Material", &mut region)?;
190            }
191        } else {
192            self.material.visit("Material", &mut region)?;
193        }
194
195        self.base.visit("Base", &mut region)?;
196        self.color.visit("Color", &mut region)?;
197        self.size.visit("Size", &mut region)?;
198        self.rotation.visit("Rotation", &mut region)?;
199
200        // Backward compatibility.
201        let _ = self.uv_rect.visit("UvRect", &mut region);
202
203        Ok(())
204    }
205}
206
207impl Deref for Sprite {
208    type Target = Base;
209
210    fn deref(&self) -> &Self::Target {
211        &self.base
212    }
213}
214
215impl DerefMut for Sprite {
216    fn deref_mut(&mut self) -> &mut Self::Target {
217        &mut self.base
218    }
219}
220
221impl Default for Sprite {
222    fn default() -> Self {
223        SpriteBuilder::new(BaseBuilder::new()).build_sprite()
224    }
225}
226
227impl TypeUuidProvider for Sprite {
228    fn type_uuid() -> Uuid {
229        uuid!("60fd7e34-46c1-4ae9-8803-1f5f4c341518")
230    }
231}
232
233impl Sprite {
234    /// Sets new size of sprite. Since sprite is always square, size defines half of width or height, so actual size
235    /// will be doubled. Default value is 0.2.    
236    ///
237    /// Negative values could be used to "inverse" the image on the sprite.
238    pub fn set_size(&mut self, size: f32) -> f32 {
239        self.size.set_value_and_mark_modified(size)
240    }
241
242    /// Returns current size of sprite.
243    pub fn size(&self) -> f32 {
244        *self.size
245    }
246
247    /// Sets new color of sprite. Default is White.
248    pub fn set_color(&mut self, color: Color) -> Color {
249        self.color.set_value_and_mark_modified(color)
250    }
251
252    /// Returns current color of sprite.
253    pub fn color(&self) -> Color {
254        *self.color
255    }
256
257    /// Sets rotation around "look" axis in radians. Default is 0.0.
258    pub fn set_rotation(&mut self, rotation: f32) -> f32 {
259        self.rotation.set_value_and_mark_modified(rotation)
260    }
261
262    /// Returns rotation in radians.
263    pub fn rotation(&self) -> f32 {
264        *self.rotation
265    }
266
267    /// Returns a reference to the current material used by the sprite.
268    pub fn material(&self) -> &InheritableVariable<MaterialResource> {
269        &self.material
270    }
271
272    /// Returns a reference to the current material used by the sprite.
273    pub fn material_mut(&mut self) -> &mut InheritableVariable<MaterialResource> {
274        &mut self.material
275    }
276
277    /// Returns a rectangle that defines the region in texture which will be rendered. The coordinates are normalized
278    /// which means `[0; 0]` corresponds to top-left corner of the texture and `[1; 1]` corresponds to right-bottom
279    /// corner.
280    pub fn uv_rect(&self) -> Rect<f32> {
281        *self.uv_rect
282    }
283
284    /// Sets a rectangle that defines the region in texture which will be rendered. The coordinates are normalized
285    /// which means `[0; 0]` corresponds to top-left corner of the texture and `[1; 1]` corresponds to right-bottom
286    /// corner.
287    ///
288    /// The coordinates can exceed `[1; 1]` boundary to create tiling effect (keep in mind that tiling should be
289    /// enabled in texture options).
290    ///
291    /// The default value is `(0, 0, 1, 1)` rectangle which corresponds to entire texture.
292    pub fn set_uv_rect(&mut self, uv_rect: Rect<f32>) -> Rect<f32> {
293        self.uv_rect.set_value_and_mark_modified(uv_rect)
294    }
295}
296
297impl ConstructorProvider<Node, Graph> for Sprite {
298    fn constructor() -> NodeConstructor {
299        NodeConstructor::new::<Self>().with_variant("Sprite (3D)", |_| {
300            SpriteBuilder::new(BaseBuilder::new().with_name("Sprite"))
301                .build_node()
302                .into()
303        })
304    }
305}
306
307impl NodeTrait for Sprite {
308    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
309        AxisAlignedBoundingBox::from_radius(*self.size)
310    }
311
312    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
313        self.base.world_bounding_box()
314    }
315
316    fn id(&self) -> Uuid {
317        Self::type_uuid()
318    }
319
320    fn collect_render_data(&self, ctx: &mut RenderContext) -> RdcControlFlow {
321        if !self.should_be_rendered(ctx.frustum) {
322            return RdcControlFlow::Continue;
323        }
324
325        if renderer::is_shadow_pass(ctx.render_pass_name) || !self.cast_shadows() {
326            return RdcControlFlow::Continue;
327        }
328
329        let position = self.global_position();
330        let params = Vector2::new(*self.size, *self.rotation);
331
332        type Vertex = SpriteVertex;
333
334        let vertices = [
335            Vertex {
336                position,
337                tex_coord: self.uv_rect.right_top_corner(),
338                params,
339                color: *self.color,
340            },
341            Vertex {
342                position,
343                tex_coord: self.uv_rect.left_top_corner(),
344                params,
345                color: *self.color,
346            },
347            Vertex {
348                position,
349                tex_coord: self.uv_rect.left_bottom_corner(),
350                params,
351                color: *self.color,
352            },
353            Vertex {
354                position,
355                tex_coord: self.uv_rect.right_bottom_corner(),
356                params,
357                color: *self.color,
358            },
359        ];
360
361        let triangles = [TriangleDefinition([0, 1, 2]), TriangleDefinition([2, 3, 0])];
362
363        let sort_index = ctx.calculate_sorting_index(self.global_position());
364
365        ctx.storage.push_triangles(
366            Vertex::layout(),
367            &self.material,
368            RenderPath::Forward,
369            sort_index,
370            self.handle(),
371            &mut move |mut vertex_buffer, mut triangle_buffer| {
372                let start_vertex_index = vertex_buffer.vertex_count();
373
374                for vertex in vertices.iter() {
375                    vertex_buffer
376                        .push_vertex_raw(value_as_u8_slice(vertex))
377                        .unwrap();
378                }
379
380                triangle_buffer
381                    .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
382            },
383        );
384
385        RdcControlFlow::Continue
386    }
387}
388
389/// Sprite builder allows you to construct sprite in declarative manner.
390/// This is typical implementation of Builder pattern.
391pub struct SpriteBuilder {
392    base_builder: BaseBuilder,
393    uv_rect: Rect<f32>,
394    material: MaterialResource,
395    color: Color,
396    size: f32,
397    rotation: f32,
398}
399
400impl SpriteBuilder {
401    /// Creates new builder with default state (white opaque color, 0.2 size, zero rotation).
402    pub fn new(base_builder: BaseBuilder) -> Self {
403        Self {
404            base_builder,
405            material: MaterialResource::new_ok(Default::default(), Material::standard_sprite()),
406            uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
407            color: Color::WHITE,
408            size: 0.2,
409            rotation: 0.0,
410        }
411    }
412
413    /// Sets desired portion of the texture for the sprite. See [`Sprite::set_uv_rect`]
414    /// for more info.
415    pub fn with_uv_rect(mut self, uv_rect: Rect<f32>) -> Self {
416        self.uv_rect = uv_rect;
417        self
418    }
419
420    /// Sets the desired material of the sprite.
421    pub fn with_material(mut self, material: MaterialResource) -> Self {
422        self.material = material;
423        self
424    }
425
426    /// Sets desired color.
427    pub fn with_color(mut self, color: Color) -> Self {
428        self.color = color;
429        self
430    }
431
432    /// Sets desired size.
433    pub fn with_size(mut self, size: f32) -> Self {
434        self.size = size;
435        self
436    }
437
438    /// Sets desired rotation.
439    pub fn with_rotation(mut self, rotation: f32) -> Self {
440        self.rotation = rotation;
441        self
442    }
443
444    fn build_sprite(self) -> Sprite {
445        Sprite {
446            base: self.base_builder.build_base(),
447            material: self.material.into(),
448            uv_rect: self.uv_rect.into(),
449            color: self.color.into(),
450            size: self.size.into(),
451            rotation: self.rotation.into(),
452        }
453    }
454
455    /// Creates new sprite instance.
456    pub fn build_node(self) -> Node {
457        Node::new(self.build_sprite())
458    }
459
460    /// Creates new sprite instance and adds it to the graph.
461    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
462        graph.add_node(self.build_node())
463    }
464}