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