Skip to main content

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