fyrox_impl/scene/dim2/
rectangle.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//! Rectangle is the simplest "2D" node, it can be used to create "2D" graphics. 2D is in quotes
22//! here because the node is actually a 3D node, like everything else in the engine.
23//!
24//! See [`Rectangle`] docs for more info.
25
26use crate::{
27    core::{
28        algebra::{Point3, Vector2, Vector3},
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::prelude::*,
38    },
39    graph::{constructor::ConstructorProvider, BaseSceneGraph},
40    material::{self, 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::{
57    hash::{Hash, Hasher},
58    ops::{Deref, DerefMut},
59};
60
61/// A vertex for static meshes.
62#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
63#[repr(C)] // OpenGL expects this structure packed as in C
64pub struct RectangleVertex {
65    /// Position of vertex in local coordinates.
66    pub position: Vector3<f32>,
67    /// Texture coordinates.
68    pub tex_coord: Vector2<f32>,
69    /// Diffuse color.
70    pub color: Color,
71}
72
73impl VertexTrait for RectangleVertex {
74    fn layout() -> &'static [VertexAttributeDescriptor] {
75        &[
76            VertexAttributeDescriptor {
77                usage: VertexAttributeUsage::Position,
78                data_type: VertexAttributeDataType::F32,
79                size: 3,
80                divisor: 0,
81                shader_location: 0,
82                normalized: false,
83            },
84            VertexAttributeDescriptor {
85                usage: VertexAttributeUsage::TexCoord0,
86                data_type: VertexAttributeDataType::F32,
87                size: 2,
88                divisor: 0,
89                shader_location: 1,
90                normalized: false,
91            },
92            VertexAttributeDescriptor {
93                usage: VertexAttributeUsage::Color,
94                data_type: VertexAttributeDataType::U8,
95                size: 4,
96                divisor: 0,
97                shader_location: 2,
98                normalized: true,
99            },
100        ]
101    }
102}
103
104impl PartialEq for RectangleVertex {
105    fn eq(&self, other: &Self) -> bool {
106        self.position == other.position
107            && self.tex_coord == other.tex_coord
108            && self.color == other.color
109    }
110}
111
112// This is safe because Vertex is tightly packed struct with C representation
113// there is no padding bytes which may contain garbage data. This is strictly
114// required because vertices will be directly passed on GPU.
115impl Hash for RectangleVertex {
116    fn hash<H: Hasher>(&self, state: &mut H) {
117        #[allow(unsafe_code)]
118        unsafe {
119            let bytes = self as *const Self as *const u8;
120            state.write(std::slice::from_raw_parts(
121                bytes,
122                std::mem::size_of::<Self>(),
123            ))
124        }
125    }
126}
127
128/// Rectangle is the simplest "2D" node, it can be used to create "2D" graphics. 2D is in quotes
129/// here because the node is actually a 3D node, like everything else in the engine.
130///
131/// # Flipping
132///
133/// It is possible to flip the sprite on both axes, vertical and horizontal. Use [`Rectangle::set_flip_x`]
134/// and [`Rectangle::set_flip_y`] methods to flip the sprite on desired axes.
135///
136/// ## Material
137///
138/// Rectangles could use an arbitrary material for rendering, which means that you have full control
139/// on how the rectangle will be rendered on screen.
140///
141/// By default, the rectangle uses standard 2D material which has only one property - `diffuseTexture`.
142/// You could use it to set a texture for your rectangle:
143///
144/// ```rust
145/// # use fyrox_impl::{
146/// #     core::sstorage::ImmutableString,
147/// #     material::{shader::SamplerFallback, MaterialProperty},
148/// #     resource::texture::TextureResource,
149/// #     scene::dim2::rectangle::Rectangle,
150/// # };
151/// #
152/// fn set_texture(rect: &mut Rectangle, texture: Option<TextureResource>) {
153///     rect.material()
154///         .data_ref()
155///         .bind("diffuseTexture", texture);
156/// }
157/// ```
158///
159/// The same property could also be changed in the editor using the Material Editor invoked from
160/// the `Material` property in the Inspector.
161///
162/// ## Performance
163///
164/// Rectangles use batching to let you draw tons of rectangles with high performance.
165///
166/// ## Specifying region for rendering
167///
168/// You can specify a portion of the texture that will be used for rendering using [`Self::set_uv_rect`]
169/// method. This is especially useful if you need to create sprite sheet animation, you use the single
170/// image, but just changing portion for rendering. Keep in mind that the coordinates are normalized
171/// which means `[0; 0]` corresponds to top-left corner of the texture and `[1; 1]` corresponds to
172/// right-bottom corner.
173#[derive(Reflect, Debug, Clone, ComponentProvider)]
174#[reflect(derived_type = "Node")]
175pub struct Rectangle {
176    base: Base,
177
178    #[reflect(setter = "set_color")]
179    color: InheritableVariable<Color>,
180
181    #[reflect(setter = "set_uv_rect")]
182    uv_rect: InheritableVariable<Rect<f32>>,
183
184    material: InheritableVariable<MaterialResource>,
185
186    #[reflect(setter = "set_flip_x")]
187    flip_x: InheritableVariable<bool>,
188
189    #[reflect(setter = "set_flip_y")]
190    flip_y: InheritableVariable<bool>,
191}
192
193impl Visit for Rectangle {
194    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
195        let mut region = visitor.enter_region(name)?;
196
197        if region.is_reading() {
198            if let Some(material) =
199                material::visit_old_texture_as_material(&mut region, Material::standard_2d)
200            {
201                self.material = material.into();
202            } else {
203                self.material.visit("Material", &mut region)?;
204            }
205        } else {
206            self.material.visit("Material", &mut region)?;
207        }
208
209        self.base.visit("Base", &mut region)?;
210        self.color.visit("Color", &mut region)?;
211
212        // Backward compatibility.
213        let _ = self.flip_x.visit("FlipX", &mut region);
214        let _ = self.flip_y.visit("FlipY", &mut region);
215        let _ = self.uv_rect.visit("UvRect", &mut region);
216
217        Ok(())
218    }
219}
220
221impl Default for Rectangle {
222    fn default() -> Self {
223        Self {
224            base: Default::default(),
225            color: Default::default(),
226            uv_rect: InheritableVariable::new_modified(Rect::new(0.0, 0.0, 1.0, 1.0)),
227            material: InheritableVariable::new_modified(MaterialResource::new_ok(
228                Uuid::new_v4(),
229                Default::default(),
230                Material::standard_2d(),
231            )),
232            flip_x: Default::default(),
233            flip_y: Default::default(),
234        }
235    }
236}
237
238impl Deref for Rectangle {
239    type Target = Base;
240
241    fn deref(&self) -> &Self::Target {
242        &self.base
243    }
244}
245
246impl DerefMut for Rectangle {
247    fn deref_mut(&mut self) -> &mut Self::Target {
248        &mut self.base
249    }
250}
251
252impl TypeUuidProvider for Rectangle {
253    fn type_uuid() -> Uuid {
254        uuid!("bb57b5e0-367a-4490-bf30-7f547407d5b5")
255    }
256}
257
258impl Rectangle {
259    /// Returns current color of the rectangle.
260    pub fn color(&self) -> Color {
261        *self.color
262    }
263
264    /// Returns a reference to the current material used by the rectangle.
265    pub fn material(&self) -> &InheritableVariable<MaterialResource> {
266        &self.material
267    }
268
269    /// Returns a reference to the current material used by the rectangle.
270    pub fn material_mut(&mut self) -> &mut InheritableVariable<MaterialResource> {
271        &mut self.material
272    }
273
274    /// Sets color of the rectangle.
275    pub fn set_color(&mut self, color: Color) -> Color {
276        self.color.set_value_and_mark_modified(color)
277    }
278
279    /// Returns a rectangle that defines the region in texture which will be rendered. The coordinates are normalized
280    /// which means `[0; 0]` corresponds to top-left corner of the texture and `[1; 1]` corresponds to right-bottom
281    /// corner.
282    pub fn uv_rect(&self) -> Rect<f32> {
283        *self.uv_rect
284    }
285
286    /// Sets a rectangle that defines the region in texture which will be rendered. The coordinates are normalized
287    /// which means `[0; 0]` corresponds to top-left corner of the texture and `[1; 1]` corresponds to right-bottom
288    /// corner.
289    ///
290    /// The coordinates can exceed `[1; 1]` boundary to create tiling effect (keep in mind that tiling should be
291    /// enabled in texture options).
292    ///
293    /// The default value is `(0, 0, 1, 1)` rectangle which corresponds to entire texture.
294    pub fn set_uv_rect(&mut self, uv_rect: Rect<f32>) -> Rect<f32> {
295        self.uv_rect.set_value_and_mark_modified(uv_rect)
296    }
297
298    /// Enables (`true`) or disables (`false`) horizontal flipping of the rectangle.
299    pub fn set_flip_x(&mut self, flip: bool) -> bool {
300        self.flip_x.set_value_and_mark_modified(flip)
301    }
302
303    /// Returns `true` if the rectangle is flipped horizontally, `false` - otherwise.
304    pub fn is_flip_x(&self) -> bool {
305        *self.flip_x
306    }
307
308    /// Enables (`true`) or disables (`false`) vertical flipping of the rectangle.
309    pub fn set_flip_y(&mut self, flip: bool) -> bool {
310        self.flip_y.set_value_and_mark_modified(flip)
311    }
312
313    /// Returns `true` if the rectangle is flipped vertically, `false` - otherwise.
314    pub fn is_flip_y(&self) -> bool {
315        *self.flip_y
316    }
317}
318
319impl ConstructorProvider<Node, Graph> for Rectangle {
320    fn constructor() -> NodeConstructor {
321        NodeConstructor::new::<Self>()
322            .with_variant("Rectangle (2D Sprite)", |_| {
323                RectangleBuilder::new(BaseBuilder::new().with_name("Sprite (2D)"))
324                    .build_node()
325                    .into()
326            })
327            .with_group("2D")
328    }
329}
330
331impl NodeTrait for Rectangle {
332    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
333        AxisAlignedBoundingBox::unit()
334    }
335
336    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
337        self.local_bounding_box()
338            .transform(&self.global_transform())
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) {
351            return RdcControlFlow::Continue;
352        }
353
354        let global_transform = self.global_transform();
355
356        type Vertex = RectangleVertex;
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: global_transform
366                    .transform_point(&Point3::new(0.5, 0.5, 0.0))
367                    .coords,
368                tex_coord: Vector2::new(
369                    if *self.flip_x { rx } else { lx },
370                    if *self.flip_y { by } else { ty },
371                ),
372                color: *self.color,
373            },
374            Vertex {
375                position: global_transform
376                    .transform_point(&Point3::new(-0.5, 0.5, 0.0))
377                    .coords,
378                tex_coord: Vector2::new(
379                    if *self.flip_x { lx } else { rx },
380                    if *self.flip_y { by } else { ty },
381                ),
382                color: *self.color,
383            },
384            Vertex {
385                position: global_transform
386                    .transform_point(&Point3::new(-0.5, -0.5, 0.0))
387                    .coords,
388                tex_coord: Vector2::new(
389                    if *self.flip_x { lx } else { rx },
390                    if *self.flip_y { ty } else { by },
391                ),
392                color: *self.color,
393            },
394            Vertex {
395                position: global_transform
396                    .transform_point(&Point3::new(0.5, -0.5, 0.0))
397                    .coords,
398                tex_coord: Vector2::new(
399                    if *self.flip_x { rx } else { lx },
400                    if *self.flip_y { ty } else { by },
401                ),
402                color: *self.color,
403            },
404        ];
405
406        let triangles = [TriangleDefinition([0, 1, 2]), TriangleDefinition([0, 2, 3])];
407
408        let sort_index = ctx.calculate_sorting_index(self.global_position());
409
410        ctx.storage.push_triangles(
411            ctx.dynamic_surface_cache,
412            Vertex::layout(),
413            &self.material,
414            RenderPath::Forward,
415            sort_index,
416            self.handle(),
417            &mut move |mut vertex_buffer, mut triangle_buffer| {
418                let start_vertex_index = vertex_buffer.vertex_count();
419
420                for vertex in vertices.iter() {
421                    vertex_buffer
422                        .push_vertex_raw(value_as_u8_slice(vertex))
423                        .unwrap();
424                }
425
426                triangle_buffer
427                    .push_triangles_iter_with_offset(start_vertex_index, triangles.into_iter());
428            },
429        );
430
431        RdcControlFlow::Continue
432    }
433}
434
435/// Allows you to create rectangle in declarative manner.
436pub struct RectangleBuilder {
437    base_builder: BaseBuilder,
438    color: Color,
439    uv_rect: Rect<f32>,
440    material: MaterialResource,
441    flip_x: bool,
442    flip_y: bool,
443}
444
445impl RectangleBuilder {
446    /// Creates new rectangle builder.
447    pub fn new(base_builder: BaseBuilder) -> Self {
448        Self {
449            base_builder,
450            color: Color::WHITE,
451            uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
452            material: MaterialResource::new_ok(
453                Uuid::new_v4(),
454                Default::default(),
455                Material::standard_2d(),
456            ),
457            flip_x: false,
458            flip_y: false,
459        }
460    }
461
462    /// Sets desired color of the rectangle.
463    pub fn with_color(mut self, color: Color) -> Self {
464        self.color = color;
465        self
466    }
467
468    /// Sets desired portion of the texture for the rectangle. See [`Rectangle::set_uv_rect`]
469    /// for more info.
470    pub fn with_uv_rect(mut self, uv_rect: Rect<f32>) -> Self {
471        self.uv_rect = uv_rect;
472        self
473    }
474
475    /// Sets the desired material of the rectangle.
476    pub fn with_material(mut self, material: MaterialResource) -> Self {
477        self.material = material;
478        self
479    }
480
481    /// Flips the rectangle horizontally.
482    pub fn with_flip_x(mut self, flip_x: bool) -> Self {
483        self.flip_x = flip_x;
484        self
485    }
486
487    /// Flips the rectangle vertically.
488    pub fn with_flip_y(mut self, flip_y: bool) -> Self {
489        self.flip_y = flip_y;
490        self
491    }
492
493    /// Creates new [`Rectangle`] instance.
494    pub fn build_rectangle(self) -> Rectangle {
495        Rectangle {
496            base: self.base_builder.build_base(),
497            color: self.color.into(),
498            uv_rect: self.uv_rect.into(),
499            material: self.material.into(),
500            flip_x: self.flip_x.into(),
501            flip_y: self.flip_y.into(),
502        }
503    }
504
505    /// Creates new [`Rectangle`] instance.
506    pub fn build_node(self) -> Node {
507        Node::new(self.build_rectangle())
508    }
509
510    /// Creates new [`Rectangle`] instance and adds it to the graph.
511    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
512        graph.add_node(self.build_node())
513    }
514}