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); };