diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
index 91a573f1a..c23730375 100644
--- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs
+++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs
@@ -46,24 +46,47 @@ namespace FlaxEditor.Content
/// Create collision data from model.
///
/// The associated model.
- public void CreateCollisionDataFromModel(Model model)
+ /// The action to call once the collision data gets created (or reused from existing).
+ public void CreateCollisionDataFromModel(Model model, Action created = null)
{
- Action created = contentItem =>
+ // Check if there already is collision data for that model to reuse
+ var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID);
+ if (modelItem?.ParentFolder != null)
{
- var ai = (AssetItem)contentItem;
- var cd = FlaxEngine.Content.LoadAsync(ai.ID);
- if (cd == null || cd.WaitForLoaded())
+ foreach (var child in modelItem.ParentFolder.Children)
+ {
+ if (child is BinaryAssetItem b && b.IsOfType())
+ {
+ var collisionData = FlaxEngine.Content.Load(b.ID);
+ if (collisionData && collisionData.Options.Model == model.ID)
+ {
+ Editor.Instance.Windows.ContentWin.Select(b);
+ if (created != null)
+ FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData));
+ return;
+ }
+ }
+ }
+ }
+
+ // Create new item so user can name it and then generate collision for it in async
+ Action create = contentItem =>
+ {
+ var assetItem = (AssetItem)contentItem;
+ var collisionData = FlaxEngine.Content.LoadAsync(assetItem.ID);
+ if (collisionData == null || collisionData.WaitForLoaded())
{
Editor.LogError("Failed to load created collision data.");
return;
}
-
Task.Run(() =>
{
- Editor.CookMeshCollision(ai.Path, CollisionDataType.TriangleMesh, model);
- });
+ Editor.CookMeshCollision(assetItem.Path, CollisionDataType.TriangleMesh, model);
+ if (created != null)
+ FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData));
+ });
};
- Editor.Instance.Windows.ContentWin.NewItem(this, null, created);
+ Editor.Instance.Windows.ContentWin.NewItem(this, null, create);
}
}
}
diff --git a/Source/Editor/Content/Proxy/ModelProxy.cs b/Source/Editor/Content/Proxy/ModelProxy.cs
index 1c58139db..00caa1f07 100644
--- a/Source/Editor/Content/Proxy/ModelProxy.cs
+++ b/Source/Editor/Content/Proxy/ModelProxy.cs
@@ -45,11 +45,11 @@ namespace FlaxEditor.Content
{
base.OnContentWindowContextMenu(menu, item);
- menu.AddButton("Generate collision data", () =>
+ menu.AddButton("Create collision data", () =>
{
var model = FlaxEngine.Content.LoadAsync(((ModelAssetItem)item).ID);
- var cdProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy();
- cdProxy.CreateCollisionDataFromModel(model);
+ var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy();
+ collisionDataProxy.CreateCollisionDataFromModel(model);
});
}
diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
index 96f8bfad2..f90658cab 100644
--- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp
+++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp
@@ -97,6 +97,8 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(PRE_INTEGRATED_GF_ASSET_NAME);
data.AddRootEngineAsset(SMAA_AREA_TEX);
data.AddRootEngineAsset(SMAA_SEARCH_TEX);
+ if (data.Configuration != BuildConfiguration::Release)
+ data.AddRootEngineAsset(TEXT("Editor/Fonts/Roboto-Regular"));
// Register game assets
data.StepProgress(TEXT("Deploying game data"), 50);
diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs
index 8c0914d58..5a19693a1 100644
--- a/Source/Editor/Modules/SceneEditingModule.cs
+++ b/Source/Editor/Modules/SceneEditingModule.cs
@@ -195,6 +195,55 @@ namespace FlaxEditor.Modules
OnSelectionChanged();
}
+ private void OnDirty(Actor actor)
+ {
+ var options = Editor.Options.Options;
+ var isPlayMode = Editor.StateMachine.IsPlayMode;
+
+ // Auto CSG mesh rebuild
+ if (!isPlayMode && options.General.AutoRebuildCSG)
+ {
+ if (actor is BoxBrush && actor.Scene)
+ actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs);
+ }
+
+ // Auto NavMesh rebuild
+ if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && (actor.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
+ {
+ var bounds = actor.BoxWithChildren;
+ Navigation.BuildNavMesh(actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
+ }
+ }
+
+ private void OnDirty(IEnumerable objects)
+ {
+ var options = Editor.Options.Options;
+ var isPlayMode = Editor.StateMachine.IsPlayMode;
+
+ // Auto CSG mesh rebuild
+ if (!isPlayMode && options.General.AutoRebuildCSG)
+ {
+ foreach (var obj in objects)
+ {
+ if (obj is ActorNode node && node.Actor is BoxBrush)
+ node.Actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs);
+ }
+ }
+
+ // Auto NavMesh rebuild
+ if (!isPlayMode && options.General.AutoRebuildNavMesh)
+ {
+ foreach (var obj in objects)
+ {
+ if (obj is ActorNode node && node.Actor.Scene && (node.Actor.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
+ {
+ var bounds = node.Actor.BoxWithChildren;
+ Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
+ }
+ }
+ }
+ }
+
///
/// Spawns the specified actor to the game (with undo).
///
@@ -242,21 +291,92 @@ namespace FlaxEditor.Modules
SpawnEnd?.Invoke();
- var options = Editor.Options.Options;
+ OnDirty(actor);
+ }
- // Auto CSG mesh rebuild
- if (!isPlayMode && options.General.AutoRebuildCSG)
+ ///
+ /// Converts the selected actor to another type.
+ ///
+ /// The type to convert in.
+ public void Convert(Type to)
+ {
+ if (!Editor.SceneEditing.HasSthSelected || !(Editor.SceneEditing.Selection[0] is ActorNode))
+ return;
+
+ if (Level.IsAnySceneLoaded == false)
+ throw new InvalidOperationException("Cannot spawn actor when no scene is loaded.");
+
+ var actionList = new IUndoAction[4];
+ Actor old = ((ActorNode)Editor.SceneEditing.Selection[0]).Actor;
+ Actor actor = (Actor)FlaxEngine.Object.New(to);
+ var parent = old.Parent;
+ var orderInParent = old.OrderInParent;
+
+ SelectionDeleteBegin?.Invoke();
+
+ actionList[0] = new SelectionChangeAction(Selection.ToArray(), new SceneGraphNode[0], OnSelectionUndo);
+ actionList[0].Do();
+
+ actionList[1] = new DeleteActorsAction(new List
{
- if (actor is BoxBrush && actor.Scene)
- actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs);
+ Editor.Instance.Scene.GetActorNode(old)
+ });
+ actionList[1].Do();
+
+ SelectionDeleteEnd?.Invoke();
+
+ SpawnBegin?.Invoke();
+
+ // Copy properties
+ actor.Transform = old.Transform;
+ actor.StaticFlags = old.StaticFlags;
+ actor.HideFlags = old.HideFlags;
+ actor.Layer = old.Layer;
+ actor.Tag = old.Tag;
+ actor.Name = old.Name;
+ actor.IsActive = old.IsActive;
+
+ // Spawn actor
+ Level.SpawnActor(actor, parent);
+ if (parent != null)
+ actor.OrderInParent = orderInParent;
+ if (Editor.StateMachine.IsPlayMode)
+ actor.StaticFlags = StaticFlags.None;
+
+ // Move children
+ for (var i = old.ScriptsCount - 1; i >= 0; i--)
+ {
+ var script = old.Scripts[i];
+ script.Actor = actor;
+ Guid newid = Guid.NewGuid();
+ FlaxEngine.Object.Internal_ChangeID(FlaxEngine.Object.GetUnmanagedPtr(script), ref newid);
+ }
+ for (var i = old.Children.Length - 1; i >= 0; i--)
+ {
+ old.Children[i].Parent = actor;
}
- // Auto NavMesh rebuild
- if (!isPlayMode && options.General.AutoRebuildNavMesh && actor.Scene && (actor.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
+ var actorNode = Editor.Instance.Scene.GetActorNode(actor);
+ if (actorNode == null)
+ throw new InvalidOperationException("Failed to create scene node for the spawned actor.");
+
+ actorNode.PostSpawn();
+ Editor.Scene.MarkSceneEdited(actor.Scene);
+
+ actionList[2] = new DeleteActorsAction(new List
{
- var bounds = actor.BoxWithChildren;
- Navigation.BuildNavMesh(actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
- }
+ actorNode
+ }, true);
+
+ actionList[3] = new SelectionChangeAction(new SceneGraphNode[0], new SceneGraphNode[] { actorNode }, OnSelectionUndo);
+ actionList[3].Do();
+
+ var actions = new MultiUndoAction(actionList);
+ Undo.AddAction(actions);
+
+ SpawnEnd?.Invoke();
+
+ OnDirty(actor);
}
///
@@ -269,8 +389,6 @@ namespace FlaxEditor.Modules
if (objects.Count == 0)
return;
- bool isPlayMode = Editor.StateMachine.IsPlayMode;
-
SelectionDeleteBegin?.Invoke();
// Change selection
@@ -290,30 +408,7 @@ namespace FlaxEditor.Modules
SelectionDeleteEnd?.Invoke();
- var options = Editor.Options.Options;
-
- // Auto CSG mesh rebuild
- if (!isPlayMode && options.General.AutoRebuildCSG)
- {
- foreach (var obj in objects)
- {
- if (obj is ActorNode node && node.Actor is BoxBrush)
- node.Actor.Scene.BuildCSG(options.General.AutoRebuildCSGTimeoutMs);
- }
- }
-
- // Auto NavMesh rebuild
- if (!isPlayMode && options.General.AutoRebuildNavMesh)
- {
- foreach (var obj in objects)
- {
- if (obj is ActorNode node && node.Actor.Scene && (node.Actor.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
- {
- var bounds = node.Actor.BoxWithChildren;
- Navigation.BuildNavMesh(node.Actor.Scene, bounds, options.General.AutoRebuildNavMeshTimeoutMs);
- }
- }
- }
+ OnDirty(objects);
}
///
diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
index 845b4fa35..cfd2c754e 100644
--- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
+++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs
@@ -1,5 +1,8 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+using System;
+using FlaxEditor.Content;
+using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors
@@ -16,5 +19,32 @@ namespace FlaxEditor.SceneGraph.Actors
: base(actor)
{
}
+
+ ///
+ public override void OnContextMenu(ContextMenu contextMenu)
+ {
+ base.OnContextMenu(contextMenu);
+
+ contextMenu.AddButton("Add mesh collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null;
+ }
+
+ private void OnAddMeshCollider()
+ {
+ var model = ((StaticModel)Actor).Model;
+ if (!model)
+ return;
+ Action created = collisionData =>
+ {
+ var actor = new MeshCollider
+ {
+ StaticFlags = Actor.StaticFlags,
+ Transform = Actor.Transform,
+ CollisionData = collisionData,
+ };
+ Editor.Instance.SceneEditing.Spawn(actor, Actor);
+ };
+ var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy();
+ collisionDataProxy.CreateCollisionDataFromModel(model, created);
+ }
}
}
diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
index f407017df..262bfd0bb 100644
--- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs
@@ -56,6 +56,30 @@ namespace FlaxEditor.Windows
b = contextMenu.AddButton("Duplicate", Editor.SceneEditing.Duplicate);
b.Enabled = hasSthSelected;
+ if (Editor.SceneEditing.SelectionCount == 1)
+ {
+ var convertMenu = contextMenu.AddChildMenu("Convert");
+ var convertActorCm = convertMenu.ContextMenu;
+ for (int i = 0; i < SpawnActorsGroups.Length; i++)
+ {
+ var group = SpawnActorsGroups[i];
+
+ if (group.Types.Length == 1)
+ {
+ var type = group.Types[0].Value;
+ convertActorCm.AddButton(group.Types[0].Key, () => Editor.SceneEditing.Convert(type));
+ }
+ else
+ {
+ var groupCm = convertActorCm.AddChildMenu(group.Name).ContextMenu;
+ for (int j = 0; j < group.Types.Length; j++)
+ {
+ var type = group.Types[j].Value;
+ groupCm.AddButton(group.Types[j].Key, () => Editor.SceneEditing.Convert(type));
+ }
+ }
+ }
+ }
b = contextMenu.AddButton("Delete", Editor.SceneEditing.Delete);
b.Enabled = hasSthSelected;
diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp
index 7a6444a89..0373b9f5c 100644
--- a/Source/Engine/Debug/DebugDraw.cpp
+++ b/Source/Engine/Debug/DebugDraw.cpp
@@ -20,6 +20,8 @@
#include "Engine/Animations/AnimationUtils.h"
#include "Engine/Profiler/Profiler.h"
#include "Engine/Debug/DebugLog.h"
+#include "Engine/Render2D/Render2D.h"
+#include "Engine/Render2D/FontAsset.h"
// Debug draw service configuration
#define DEBUG_DRAW_INITIAL_VB_CAPACITY (4 * 1024)
@@ -54,6 +56,25 @@ struct DebugTriangle
float TimeLeft;
};
+struct DebugText2D
+{
+ Array> Text;
+ Vector2 Position;
+ int32 Size;
+ Color Color;
+ float TimeLeft;
+};
+
+struct DebugText3D
+{
+ Array> Text;
+ Transform Transform;
+ bool FaceCamera;
+ int32 Size;
+ Color Color;
+ float TimeLeft;
+};
+
PACK_STRUCT(struct Vertex {
Vector3 Position;
Color32 Color;
@@ -133,10 +154,14 @@ struct DebugDrawData
Array OneFrameTriangles;
Array DefaultWireTriangles;
Array OneFrameWireTriangles;
+ Array DefaultText2D;
+ Array OneFrameText2D;
+ Array DefaultText3D;
+ Array OneFrameText3D;
inline int32 Count() const
{
- return LinesCount() + TrianglesCount();
+ return LinesCount() + TrianglesCount() + TextCount();
}
inline int32 LinesCount() const
@@ -149,6 +174,11 @@ struct DebugDrawData
return DefaultTriangles.Count() + OneFrameTriangles.Count() + DefaultWireTriangles.Count() + OneFrameWireTriangles.Count();
}
+ inline int32 TextCount() const
+ {
+ return DefaultText2D.Count() + OneFrameText2D.Count() + DefaultText3D.Count() + OneFrameText3D.Count();
+ }
+
inline void Add(const DebugLine& l)
{
if (l.TimeLeft > 0)
@@ -178,10 +208,14 @@ struct DebugDrawData
UpdateList(deltaTime, DefaultLines);
UpdateList(deltaTime, DefaultTriangles);
UpdateList(deltaTime, DefaultWireTriangles);
+ UpdateList(deltaTime, DefaultText2D);
+ UpdateList(deltaTime, DefaultText3D);
OneFrameLines.Clear();
OneFrameTriangles.Clear();
OneFrameWireTriangles.Clear();
+ OneFrameText2D.Clear();
+ OneFrameText3D.Clear();
}
inline void Clear()
@@ -192,6 +226,10 @@ struct DebugDrawData
OneFrameTriangles.Clear();
DefaultWireTriangles.Clear();
OneFrameWireTriangles.Clear();
+ DefaultText2D.Clear();
+ OneFrameText2D.Clear();
+ DefaultText3D.Clear();
+ OneFrameText3D.Clear();
}
inline void Release()
@@ -202,6 +240,10 @@ struct DebugDrawData
OneFrameTriangles.Resize(0);
DefaultWireTriangles.Resize(0);
OneFrameWireTriangles.Resize(0);
+ DefaultText2D.Resize(0);
+ OneFrameText2D.Resize(0);
+ DefaultText3D.Resize(0);
+ OneFrameText3D.Resize(0);
}
};
@@ -216,6 +258,7 @@ namespace
DebugDrawContext GlobalContext;
DebugDrawContext* Context;
AssetReference DebugDrawShader;
+ AssetReference DebugDrawFont;
PsData DebugDrawPsLinesDefault;
PsData DebugDrawPsLinesDepthTest;
PsData DebugDrawPsWireTrianglesDefault;
@@ -299,6 +342,21 @@ DebugDrawCall WriteLists(int32& vertexCounter, const Array& listA, const Arra
return drawCall;
}
+inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext, const Vector3& viewUp, const Matrix& f, const Matrix& vp, const Viewport& viewport, GPUContext* context, GPUTextureView* target, GPUTextureView* depthBuffer)
+{
+ Matrix w, fw, m;
+ if (t.FaceCamera)
+ Matrix::CreateWorld(t.Transform.Translation, renderContext.View.Direction, viewUp, w);
+ else
+ t.Transform.GetWorld(w);
+ Matrix::Multiply(f, w, fw);
+ Matrix::Multiply(fw, vp, m);
+ Render2D::Begin(context, target, depthBuffer, viewport, m);
+ const StringView text(t.Text.Get(), t.Text.Count() - 1);
+ Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, Vector2::Zero);
+ Render2D::End();
+}
+
class DebugDrawService : public EngineService
{
public:
@@ -569,7 +627,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
return;
auto context = GPUDevice::Instance->GetMainContext();
- // Fallback to task backbuffer
+ // Fallback to task buffers
if (target == nullptr && renderContext.Task)
target = renderContext.Task->GetOutputView();
@@ -601,8 +659,6 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
context->BindCB(0, cb);
auto vb = DebugDrawVB->GetBuffer();
-#define DRAW(drawCall) if (drawCall.VertexCount)
-
// Draw with depth test
if (depthTestLines.VertexCount + depthTestTriangles.VertexCount + depthTestWireTriangles.VertexCount > 0)
{
@@ -673,7 +729,50 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
}
}
-#undef DRAW
+ // Text
+ if (Context->DebugDrawDefault.TextCount() + Context->DebugDrawDepthTest.TextCount())
+ {
+ PROFILE_GPU_CPU_NAMED("Text");
+ auto features = Render2D::Features;
+ Render2D::Features = (Render2D::RenderingFeatures)((uint32)features & ~(uint32)Render2D::RenderingFeatures::VertexSnapping);
+
+ if (!DebugDrawFont)
+ DebugDrawFont = Content::LoadAsyncInternal(TEXT("Editor/Fonts/Roboto-Regular"));
+ if (DebugDrawFont && DebugDrawFont->IsLoaded())
+ {
+ Viewport viewport = renderContext.Task->GetViewport();
+
+ if (Context->DebugDrawDefault.DefaultText2D.Count() + Context->DebugDrawDefault.OneFrameText2D.Count())
+ {
+ Render2D::Begin(context, target, nullptr, viewport);
+ for (auto& t : Context->DebugDrawDefault.DefaultText2D)
+ {
+ const StringView text(t.Text.Get(), t.Text.Count() - 1);
+ Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, t.Position);
+ }
+ for (auto& t : Context->DebugDrawDefault.OneFrameText2D)
+ {
+ const StringView text(t.Text.Get(), t.Text.Count() - 1);
+ Render2D::DrawText(DebugDrawFont->CreateFont(t.Size), text, t.Color, t.Position);
+ }
+ Render2D::End();
+ }
+
+ if (Context->DebugDrawDefault.DefaultText3D.Count() + Context->DebugDrawDefault.OneFrameText3D.Count())
+ {
+ Matrix f;
+ Matrix::RotationZ(PI, f);
+ Vector3 viewUp;
+ Vector3::Transform(Vector3::Up, Quaternion::LookRotation(renderContext.View.Direction, Vector3::Up), viewUp);
+ for (auto& t : Context->DebugDrawDefault.DefaultText3D)
+ DrawText3D(t, renderContext, viewUp, f, vp, viewport, context, target, nullptr);
+ for (auto& t : Context->DebugDrawDefault.OneFrameText3D)
+ DrawText3D(t, renderContext, viewUp, f, vp, viewport, context, target, nullptr);
+ }
+ }
+
+ Render2D::Features = features;
+ }
}
void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bool drawScenes)
@@ -1276,4 +1375,51 @@ void DebugDraw::DrawBox(const OrientedBoundingBox& box, const Color& color, floa
}
}
+void DebugDraw::DrawText(const StringView& text, const Vector2& position, const Color& color, int32 size, float duration)
+{
+ if (text.Length() == 0 || size < 4)
+ return;
+ Array* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText2D : &Context->DebugDrawDefault.OneFrameText2D;
+ auto& t = list->AddOne();
+ t.Text.Resize(text.Length() + 1);
+ Platform::MemoryCopy(t.Text.Get(), text.Get(), text.Length() * sizeof(Char));
+ t.Text[text.Length()] = 0;
+ t.Position = position;
+ t.Size = size;
+ t.Color = color;
+ t.TimeLeft = duration;
+}
+
+void DebugDraw::DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size, float duration)
+{
+ if (text.Length() == 0 || size < 4)
+ return;
+ Array* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
+ auto& t = list->AddOne();
+ t.Text.Resize(text.Length() + 1);
+ Platform::MemoryCopy(t.Text.Get(), text.Get(), text.Length() * sizeof(Char));
+ t.Text[text.Length()] = 0;
+ t.Transform = position;
+ t.FaceCamera = true;
+ t.Size = size;
+ t.Color = color;
+ t.TimeLeft = duration;
+}
+
+void DebugDraw::DrawText(const StringView& text, const Transform& transform, const Color& color, int32 size, float duration)
+{
+ if (text.Length() == 0 || size < 4)
+ return;
+ Array* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
+ auto& t = list->AddOne();
+ t.Text.Resize(text.Length() + 1);
+ Platform::MemoryCopy(t.Text.Get(), text.Get(), text.Length() * sizeof(Char));
+ t.Text[text.Length()] = 0;
+ t.Transform = transform;
+ t.FaceCamera = false;
+ t.Size = size;
+ t.Color = color;
+ t.TimeLeft = duration;
+}
+
#endif
diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h
index a2681e1f2..13aa5c3ac 100644
--- a/Source/Engine/Debug/DebugDraw.h
+++ b/Source/Engine/Debug/DebugDraw.h
@@ -15,6 +15,7 @@ class GPUContext;
class RenderTask;
class SceneRenderTask;
class Actor;
+struct Transform;
///
/// The debug shapes rendering service. Not available in final game. For use only in the editor.
@@ -307,6 +308,36 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw);
/// The duration (in seconds). Use 0 to draw it only once.
/// If set to true depth test will be performed, otherwise depth will be ignored.
API_FUNCTION() static void DrawBox(const OrientedBoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true);
+
+ ///
+ /// Draws the text on a screen (2D).
+ ///
+ /// The text.
+ /// The position of the text on the screen (in screen-space coordinates).
+ /// The color.
+ /// The font size.
+ /// The duration (in seconds). Use 0 to draw it only once.
+ API_FUNCTION() static void DrawText(const StringView& text, const Vector2& position, const Color& color, int32 size = 20, float duration = 0.0f);
+
+ ///
+ /// Draws the text (3D) that automatically faces the camera.
+ ///
+ /// The text.
+ /// The position of the text (world-space).
+ /// The color.
+ /// The font size.
+ /// The duration (in seconds). Use 0 to draw it only once.
+ API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f);
+
+ ///
+ /// Draws the text (3D).
+ ///
+ /// The text.
+ /// The transformation of the text (world-space).
+ /// The color.
+ /// The font size.
+ /// The duration (in seconds). Use 0 to draw it only once.
+ API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color, int32 size = 32, float duration = 0.0f);
};
#define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) DebugDraw::DrawLine(start, end, color, duration, depthTest)
@@ -326,7 +357,8 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw);
#define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest)
#define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, radius, length, color, duration, depthTest)
#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, radius, height, color, duration, depthTest)
-#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, scale, color, duration, depthTest)
+#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, color, duration, depthTest)
+#define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration)
#else
@@ -348,5 +380,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw);
#define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest)
#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest)
#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest)
+#define DEBUG_DRAW_TEXT(text, position, color, size, duration)
#endif
diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp
index 6e6936cc5..7bf23756c 100644
--- a/Source/Engine/Render2D/Render2D.cpp
+++ b/Source/Engine/Render2D/Render2D.cpp
@@ -270,6 +270,34 @@ FORCE_INLINE Render2DVertex MakeVertex(const Vector2& point, const Vector2& uv,
};
}
+void WriteTri(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& uv0, const Vector2& uv1, const Vector2& uv2, const Color& color0, const Color& color1, const Color& color2)
+{
+ Render2DVertex tris[3];
+ tris[0] = MakeVertex(p0, uv0, color0);
+ tris[1] = MakeVertex(p1, uv1, color1);
+ tris[2] = MakeVertex(p2, uv2, color2);
+ VB.Write(tris, sizeof(tris));
+
+ uint32 indices[3];
+ indices[0] = VBIndex + 0;
+ indices[1] = VBIndex + 1;
+ indices[2] = VBIndex + 2;
+ IB.Write(indices, sizeof(indices));
+
+ VBIndex += 3;
+ IBIndex += 3;
+}
+
+void WriteTri(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Color& color0, const Color& color1, const Color& color2)
+{
+ WriteTri(p0, p1, p2, Vector2::Zero, Vector2::Zero, Vector2::Zero, color0, color1, color2);
+}
+
+void WriteTri(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& uv0, const Vector2& uv1, const Vector2& uv2)
+{
+ WriteTri(p0, p1, p2, uv0, uv1, uv2, Color::Black, Color::Black, Color::Black);
+}
+
void WriteRect(const Rectangle& rect, const Color& color1, const Color& color2, const Color& color3, const Color& color4)
{
const Vector2 uvUpperLeft = Vector2::Zero;
@@ -1716,3 +1744,45 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength)
drawCall.AsBlur.BottomRightY = p.Y;
WriteRect(rect, Color::White);
}
+
+void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs)
+{
+ CHECK(vertices.Length() == uvs.Length())
+
+ RENDER2D_CHECK_RENDERING_STATE;
+
+ Render2DDrawCall& drawCall = DrawCalls.AddOne();
+ drawCall.Type = DrawCallType::FillTexture;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = vertices.Length();
+ drawCall.AsTexture.Ptr = t;
+
+ for (int32 i = 0; i < vertices.Length(); i += 3)
+ WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], uvs[i], uvs[i + 1], uvs[i + 2]);
+}
+
+void Render2D::FillTriangles(const Span& vertices, const Span& colors, bool useAlpha)
+{
+ CHECK(vertices.Length() == colors.Length());
+
+ RENDER2D_CHECK_RENDERING_STATE;
+
+ Render2DDrawCall& drawCall = DrawCalls.AddOne();
+ drawCall.Type = useAlpha ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = vertices.Length();
+
+ for (int32 i = 0; i < vertices.Length(); i += 3)
+ WriteTri(vertices[i], vertices[i + 1], vertices[i + 2], colors[i], colors[i + 1], colors[i + 2]);
+}
+
+void Render2D::FillTriangle(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Color& color)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+
+ Render2DDrawCall& drawCall = DrawCalls.AddOne();
+ drawCall.Type = color.A < 1.0f ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = 3;
+ WriteTri(p0, p1, p2, color, color, color);
+}
diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h
index a421c56a6..a6eba94e4 100644
--- a/Source/Engine/Render2D/Render2D.h
+++ b/Source/Engine/Render2D/Render2D.h
@@ -4,6 +4,7 @@
#include "Engine/Core/Math/Color.h"
#include "Engine/Scripting/ScriptingType.h"
+#include "Engine/Core/Types/Span.h"
struct SpriteHandle;
struct TextLayoutOptions;
@@ -339,4 +340,29 @@ public:
/// The target rectangle to draw (blurs its background).
/// The blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.
API_FUNCTION() static void DrawBlur(const Rectangle& rect, float blurStrength);
+
+ ///
+ /// Draws vertices array.
+ ///
+ /// The texture.
+ /// The vertices array.
+ /// The uvs array.
+ API_FUNCTION() static void DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs);
+
+ ///
+ /// Draws vertices array.
+ ///
+ /// The vertices array.
+ /// The colors array.
+ /// If true alpha blending will be enabled.
+ API_FUNCTION() static void FillTriangles(const Span& vertices, const Span& colors, bool useAlpha);
+
+ ///
+ /// Fills a triangular area.
+ ///
+ /// The first point.
+ /// The second point.
+ /// The third point.
+ /// The color.
+ API_FUNCTION() static void FillTriangle(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Color& color);
};