diff --git a/Content/Editor/SpriteMaterial.flax b/Content/Editor/SpriteMaterial.flax
new file mode 100644
index 000000000..733249713
--- /dev/null
+++ b/Content/Editor/SpriteMaterial.flax
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c6f3a619d8003855d59f24f3c74958c758a92e778a7e455193e10fc13ec3293b
+size 30480
diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs
index 8ffe9fe35..1a10f35ce 100644
--- a/Source/Editor/EditorAssets.cs
+++ b/Source/Editor/EditorAssets.cs
@@ -97,6 +97,11 @@ namespace FlaxEditor
///
public static string DefaultSkyCubeTexture = "Editor/SimplySky";
+ ///
+ /// The default sprite material.
+ ///
+ public static string DefaultSpriteMaterial = "Editor/SpriteMaterial";
+
///
/// The IES Profile assets preview material.
///
@@ -112,6 +117,16 @@ namespace FlaxEditor
///
public static string VertexColorsPreviewMaterial = "Editor/Gizmo/VertexColorsPreviewMaterial";
+ ///
+ /// The Flax icon texture.
+ ///
+ public static string FlaxIconTexture = "Engine/Textures/FlaxIcon";
+
+ ///
+ /// The Flax icon (blue) texture.
+ ///
+ public static string FlaxIconBlueTexture = "Engine/Textures/FlaxIconBlue";
+
///
/// The icon lists used by editor from the SegMDL2 font.
///
diff --git a/Source/Editor/SceneGraph/Actors/SpriteRenderNode.cs b/Source/Editor/SceneGraph/Actors/SpriteRenderNode.cs
new file mode 100644
index 000000000..33a4174e4
--- /dev/null
+++ b/Source/Editor/SceneGraph/Actors/SpriteRenderNode.cs
@@ -0,0 +1,63 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using FlaxEngine;
+
+namespace FlaxEditor.SceneGraph.Actors
+{
+ ///
+ /// Scene tree node for actor type.
+ ///
+ ///
+ [HideInEditor]
+ public sealed class SpriteRenderNode : ActorNode
+ {
+ ///
+ public SpriteRenderNode(Actor actor)
+ : base(actor)
+ {
+ }
+
+ ///
+ public override bool RayCastSelf(ref RayCastData ray, out float distance, out Vector3 normal)
+ {
+ SpriteRender sprite = (SpriteRender)Actor;
+ Vector3 viewPosition = ray.View.Position;
+ Vector3 viewDirection = ray.View.Direction;
+ Matrix m1, m2, m3, world;
+ var size = sprite.Size;
+ Matrix.Scaling(size.X, size.Y, 1.0f, out m1);
+ var transform = sprite.Transform;
+ if (sprite.FaceCamera)
+ {
+ var up = Vector3.Up;
+ Matrix.Billboard(ref transform.Translation, ref viewPosition, ref up, ref viewDirection, out m2);
+ Matrix.Multiply(ref m1, ref m2, out m3);
+ Matrix.Scaling(ref transform.Scale, out m1);
+ Matrix.Multiply(ref m1, ref m3, out world);
+ }
+ else
+ {
+ transform.GetWorld(out m2);
+ Matrix.Multiply(ref m1, ref m2, out world);
+ }
+
+ OrientedBoundingBox bounds;
+ bounds.Extents = Vector3.Half;
+ bounds.Transformation = world;
+
+ normal = -ray.Ray.Direction;
+ return bounds.Intersects(ref ray.Ray, out distance);
+ }
+
+ ///
+ public override void PostSpawn()
+ {
+ base.PostSpawn();
+
+ // Setup for default values
+ var text = (SpriteRender)Actor;
+ text.Material = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.DefaultSpriteMaterial);
+ text.Image = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.FlaxIconTexture);
+ }
+ }
+}
diff --git a/Source/Editor/SceneGraph/SceneGraphFactory.cs b/Source/Editor/SceneGraph/SceneGraphFactory.cs
index 8103f5f05..28286187d 100644
--- a/Source/Editor/SceneGraph/SceneGraphFactory.cs
+++ b/Source/Editor/SceneGraph/SceneGraphFactory.cs
@@ -70,6 +70,7 @@ namespace FlaxEditor.SceneGraph
CustomNodesTypes.Add(typeof(SplineCollider), typeof(ColliderNode));
CustomNodesTypes.Add(typeof(SplineRopeBody), typeof(ActorNode));
CustomNodesTypes.Add(typeof(NavMesh), typeof(ActorNode));
+ CustomNodesTypes.Add(typeof(SpriteRender), typeof(SpriteRenderNode));
}
///
diff --git a/Source/Editor/SceneGraph/SceneGraphNode.cs b/Source/Editor/SceneGraph/SceneGraphNode.cs
index 835295147..01a832c5a 100644
--- a/Source/Editor/SceneGraph/SceneGraphNode.cs
+++ b/Source/Editor/SceneGraph/SceneGraphNode.cs
@@ -197,12 +197,12 @@ namespace FlaxEditor.SceneGraph
}
///
- /// The ray.
+ /// The ray (for intersection raycasting).
///
public Ray Ray;
///
- /// The camera view ray.
+ /// The camera view ray (camera position and direction).
///
public Ray View;
diff --git a/Source/Editor/Windows/SceneTreeWindow.Actors.cs b/Source/Editor/Windows/SceneTreeWindow.Actors.cs
index 8970b6a48..04d5a3ced 100644
--- a/Source/Editor/Windows/SceneTreeWindow.Actors.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.Actors.cs
@@ -110,6 +110,7 @@ namespace FlaxEditor.Windows
new KeyValuePair("UI Control", typeof(UIControl)),
new KeyValuePair("UI Canvas", typeof(UICanvas)),
new KeyValuePair("Text Render", typeof(TextRender)),
+ new KeyValuePair("Sprite Render", typeof(SpriteRender)),
}
},
};
diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
index 262bfd0bb..a00043c76 100644
--- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
-using System.Collections.Generic;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph;
using FlaxEngine;
diff --git a/Source/Editor/Windows/ToolboxWindow.cs b/Source/Editor/Windows/ToolboxWindow.cs
index 63e1f1f05..0119233c1 100644
--- a/Source/Editor/Windows/ToolboxWindow.cs
+++ b/Source/Editor/Windows/ToolboxWindow.cs
@@ -173,6 +173,7 @@ namespace FlaxEditor.Windows
groupGui.AddChild(CreateActorItem("UI Control", typeof(UIControl)));
groupGui.AddChild(CreateActorItem("UI Canvas", typeof(UICanvas)));
groupGui.AddChild(CreateActorItem("Text Render", typeof(TextRender)));
+ groupGui.AddChild(CreateActorItem("Sprite Render", typeof(SpriteRender)));
actorGroups.SelectedTabIndex = 1;
}
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index 8285883ae..5d7f712a8 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -372,7 +372,7 @@ void Mesh::Render(GPUContext* context) const
context->DrawIndexedInstanced(_triangles * 3, 1, 0, 0, 0);
}
-void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals) const
+void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom) const
{
if (!material || !material->IsSurface())
return;
@@ -399,8 +399,8 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons
drawCall.Surface.Skinning = nullptr;
drawCall.Surface.LODDitherFactor = 0.0f;
drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1);
- drawCall.PerInstanceRandom = 0.0f;
- renderContext.List->AddDrawCall(DrawPass::Default, flags, drawCall, receiveDecals);
+ drawCall.PerInstanceRandom = perInstanceRandom;
+ renderContext.List->AddDrawCall(drawModes, flags, drawCall, receiveDecals);
}
void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const
diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h
index 94d4979a3..83c37c530 100644
--- a/Source/Engine/Graphics/Models/Mesh.h
+++ b/Source/Engine/Graphics/Models/Mesh.h
@@ -459,7 +459,9 @@ public:
/// The world transformation of the model.
/// The object static flags.
/// True if rendered geometry can receive decals, otherwise false.
- API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true) const;
+ /// The draw passes to use for rendering this object.
+ /// The random per-instance value (normalized to range 0-1).
+ API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true, DrawPass drawModes = DrawPass::Default, float perInstanceRandom = 0.0f) const;
///
/// Draws the mesh.
diff --git a/Source/Engine/Graphics/Models/ModelLOD.h b/Source/Engine/Graphics/Models/ModelLOD.h
index 91d728aa7..94cb8d8ce 100644
--- a/Source/Engine/Graphics/Models/ModelLOD.h
+++ b/Source/Engine/Graphics/Models/ModelLOD.h
@@ -122,11 +122,13 @@ public:
/// The world transformation of the model.
/// The object static flags.
/// True if rendered geometry can receive decals, otherwise false.
- API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true) const
+ /// The draw passes to use for rendering this object.
+ /// The random per-instance value (normalized to range 0-1).
+ API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true, DrawPass drawModes = DrawPass::Default, float perInstanceRandom = 0.0f) const
{
for (int32 i = 0; i < Meshes.Count(); i++)
{
- Meshes[i].Draw(renderContext, material, world, flags, receiveDecals);
+ Meshes[i].Draw(renderContext, material, world, flags, receiveDecals, drawModes, perInstanceRandom);
}
}
diff --git a/Source/Engine/UI/SpriteRender.cpp b/Source/Engine/UI/SpriteRender.cpp
new file mode 100644
index 000000000..8929b8371
--- /dev/null
+++ b/Source/Engine/UI/SpriteRender.cpp
@@ -0,0 +1,188 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#include "SpriteRender.h"
+#include "Engine/Core/Types/Variant.h"
+#include "Engine/Core/Math/OrientedBoundingBox.h"
+#include "Engine/Level/Scene/SceneRendering.h"
+#include "Engine/Content/Content.h"
+#include "Engine/Content/Assets/Model.h"
+#include "Engine/Content/Assets/MaterialInstance.h"
+#include "Engine/Level/Actors/Camera.h"
+#include "Engine/Serialization/Serialization.h"
+
+SpriteRender::SpriteRender(const SpawnParams& params)
+ : Actor(params)
+ , _color(Color::White)
+ , _size(100.0f)
+{
+ _quadModel = Content::LoadAsyncInternal(TEXT("Engine/Models/Quad"));
+ Material.Loaded.Bind(this);
+ Image.Changed.Bind(this);
+}
+
+Vector2 SpriteRender::GetSize() const
+{
+ return _size;
+}
+
+void SpriteRender::SetSize(const Vector2& value)
+{
+ if (_size == value)
+ return;
+ _size = value;
+ OnTransformChanged();
+}
+
+Color SpriteRender::GetColor() const
+{
+ return _color;
+}
+
+void SpriteRender::SetColor(const Color& value)
+{
+ _color = value;
+ if (_paramColor)
+ _paramColor->SetValue(value);
+}
+
+void SpriteRender::OnMaterialLoaded()
+{
+ // Setup material instance
+ if (!_materialInstance)
+ {
+ _materialInstance = Content::CreateVirtualAsset();
+ _materialInstance->AddReference();
+ }
+ _materialInstance->SetBaseMaterial(Material);
+
+ // Cache parameters
+ _paramImage = _materialInstance->GetParameter(TEXT("Image"));
+ if (_paramImage && _paramImage->GetParameterType() != MaterialParameterType::Texture)
+ _paramImage = nullptr;
+ else if (_paramImage)
+ _paramImage->SetValue(Image.Get());
+ _paramColor = _materialInstance->GetParameter(TEXT("Color"));
+ if (_paramColor && _paramColor->GetParameterType() != MaterialParameterType::Color && _paramColor->GetParameterType() != MaterialParameterType::Vector4 && _paramColor->GetParameterType() != MaterialParameterType::Vector3)
+ _paramColor = nullptr;
+ else if (_paramColor)
+ _paramColor->SetValue(_color);
+}
+
+void SpriteRender::OnImageChanged()
+{
+ if (_paramImage)
+ _paramImage->SetValue(Image.Get());
+}
+
+bool SpriteRender::HasContentLoaded() const
+{
+ return (Material == nullptr || Material->IsLoaded()) && (Image == nullptr || Image->IsLoaded());
+}
+
+void SpriteRender::Draw(RenderContext& renderContext)
+{
+ if (!Material || !Material->IsLoaded() || !_quadModel || !_quadModel->IsLoaded())
+ return;
+ auto model = _quadModel.As();
+ if (model->GetLoadedLODs() == 0)
+ return;
+ auto& view = renderContext.View;
+ Matrix m1, m2, m3, world;
+ Matrix::Scaling(_size.X, _size.Y, 1.0f, m2);
+ Matrix::RotationY(PI, m3);
+ Matrix::Multiply(m2, m3, m1);
+ if (FaceCamera)
+ {
+ Matrix::Billboard(_transform.Translation, view.Position, Vector3::Up, view.Direction, m2);
+ Matrix::Multiply(m1, m2, m3);
+ Matrix::Scaling(_transform.Scale, m1);
+ Matrix::Multiply(m1, m3, world);
+ }
+ else
+ {
+ _transform.GetWorld(m2);
+ Matrix::Multiply(m1, m2, world);
+ }
+ model->LODs[0].Draw(renderContext, _materialInstance, world, GetStaticFlags(), false, DrawModes, GetPerInstanceRandom());
+}
+
+void SpriteRender::DrawGeneric(RenderContext& renderContext)
+{
+ Draw(renderContext);
+}
+
+void SpriteRender::Serialize(SerializeStream& stream, const void* otherObj)
+{
+ // Base
+ Actor::Serialize(stream, otherObj);
+
+ SERIALIZE_GET_OTHER_OBJ(SpriteRender);
+
+ SERIALIZE_MEMBER(Size, _size);
+ SERIALIZE_MEMBER(Color, _color);
+ SERIALIZE(Image);
+ SERIALIZE(Material);
+ SERIALIZE(FaceCamera);
+ SERIALIZE(DrawModes);
+}
+
+void SpriteRender::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
+{
+ // Base
+ Actor::Deserialize(stream, modifier);
+
+ DESERIALIZE_MEMBER(Size, _size);
+ DESERIALIZE_MEMBER(Color, _color);
+ DESERIALIZE(Image);
+ DESERIALIZE(Material);
+ DESERIALIZE(FaceCamera);
+ DESERIALIZE(DrawModes);
+
+ if (_paramImage)
+ _paramImage->SetValue(Image.Get());
+ if (_paramColor)
+ _paramColor->SetValue(_color);
+}
+
+void SpriteRender::OnEndPlay()
+{
+ // Base
+ Actor::OnEndPlay();
+
+ // Release material instance
+ if (_materialInstance)
+ {
+ _materialInstance->SetBaseMaterial(nullptr);
+ _materialInstance->Params.Resize(0);
+ _materialInstance->RemoveReference();
+ _materialInstance = nullptr;
+ }
+}
+
+void SpriteRender::OnEnable()
+{
+ GetSceneRendering()->AddGeometry(this);
+
+ // Base
+ Actor::OnEnable();
+}
+
+void SpriteRender::OnDisable()
+{
+ GetSceneRendering()->RemoveGeometry(this);
+
+ // Base
+ Actor::OnDisable();
+}
+
+void SpriteRender::OnTransformChanged()
+{
+ // Base
+ Actor::OnTransformChanged();
+
+ const BoundingSphere localSphere(Vector3::Zero, _size.Length());
+ Matrix world;
+ _transform.GetWorld(world);
+ BoundingSphere::Transform(localSphere, world, _sphere);
+ BoundingBox::FromSphere(_sphere, _box);
+}
diff --git a/Source/Engine/UI/SpriteRender.h b/Source/Engine/UI/SpriteRender.h
new file mode 100644
index 000000000..6a994b188
--- /dev/null
+++ b/Source/Engine/UI/SpriteRender.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Level/Actor.h"
+#include "Engine/Content/Assets/MaterialBase.h"
+#include "Engine/Content/Assets/Texture.h"
+
+///
+/// Sprite rendering object.
+///
+API_CLASS() class FLAXENGINE_API SpriteRender : public Actor
+{
+DECLARE_SCENE_OBJECT(SpriteRender);
+private:
+
+ Color _color;
+ Vector2 _size;
+ MaterialInstance* _materialInstance = nullptr;
+ MaterialParameter* _paramImage = nullptr;
+ MaterialParameter* _paramColor = nullptr;
+ AssetReference _quadModel;
+
+public:
+
+ ///
+ /// Gets the size of the sprite.
+ ///
+ API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Sprite\")")
+ Vector2 GetSize() const;
+
+ ///
+ /// Sets the size of the sprite.
+ ///
+ API_PROPERTY() void SetSize(const Vector2& value);
+
+ ///
+ /// Gets the color of the sprite. Passed to the sprite material in parameter named `Color`.
+ ///
+ API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Color), \"1,1,1,1\"), EditorDisplay(\"Sprite\")")
+ Color GetColor() const;
+
+ ///
+ /// Sets the color of the sprite. Passed to the sprite material in parameter named `Color`.
+ ///
+ API_PROPERTY() void SetColor(const Color& value);
+
+ ///
+ /// The sprite texture to draw.
+ ///
+ API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Sprite\")")
+ AssetReference Image;
+
+ ///
+ /// The material used for the sprite rendering. It should contain texture parameter named Image and color parameter named Color.
+ ///
+ API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Sprite\")")
+ AssetReference Material;
+
+ ///
+ /// If checked, the sprite will automatically face the view camera, otherwise it will be oriented as an actor.
+ ///
+ API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Sprite\")")
+ bool FaceCamera = true;
+
+ ///
+ /// The draw passes to use for rendering this object. Uncheck `Depth` to disable sprite casting shadows.
+ ///
+ API_FIELD(Attributes="EditorOrder(40), DefaultValue(DrawPass.Default), EditorDisplay(\"Sprite\")")
+ DrawPass DrawModes = DrawPass::Default;
+
+private:
+
+ void OnMaterialLoaded();
+ void OnImageChanged();
+
+public:
+
+ // [Actor]
+ bool HasContentLoaded() const override;
+ void Draw(RenderContext& renderContext) override;
+ void DrawGeneric(RenderContext& renderContext) override;
+ void Serialize(SerializeStream& stream, const void* otherObj) override;
+ void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
+ void OnEndPlay() override;
+
+protected:
+
+ // [Actor]
+ void OnEnable() override;
+ void OnDisable() override;
+ void OnTransformChanged() override;
+};