From 19c83e3b6e71d292309fe40e953f3b2fae2b1c9e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 23 Feb 2021 13:23:22 +0100 Subject: [PATCH] Add Sprite Render actor for sprites drawing --- Content/Editor/SpriteMaterial.flax | 3 + Source/Editor/EditorAssets.cs | 15 ++ .../SceneGraph/Actors/SpriteRenderNode.cs | 63 ++++++ Source/Editor/SceneGraph/SceneGraphFactory.cs | 1 + Source/Editor/SceneGraph/SceneGraphNode.cs | 4 +- .../Editor/Windows/SceneTreeWindow.Actors.cs | 1 + .../Windows/SceneTreeWindow.ContextMenu.cs | 1 - Source/Editor/Windows/ToolboxWindow.cs | 1 + Source/Engine/Graphics/Models/Mesh.cpp | 6 +- Source/Engine/Graphics/Models/Mesh.h | 4 +- Source/Engine/Graphics/Models/ModelLOD.h | 6 +- Source/Engine/UI/SpriteRender.cpp | 188 ++++++++++++++++++ Source/Engine/UI/SpriteRender.h | 93 +++++++++ 13 files changed, 377 insertions(+), 9 deletions(-) create mode 100644 Content/Editor/SpriteMaterial.flax create mode 100644 Source/Editor/SceneGraph/Actors/SpriteRenderNode.cs create mode 100644 Source/Engine/UI/SpriteRender.cpp create mode 100644 Source/Engine/UI/SpriteRender.h 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; +};