Created editor project and moved Editor there

This commit is contained in:
Simon Lübeß
2021-09-16 13:19:25 +02:00
parent f6b073557a
commit 0079b80b1c
11 changed files with 279 additions and 10 deletions
@@ -0,0 +1,155 @@
using ImGui;
using GlitchyEngine.World;
using System;
using GlitchyEngine.Math;
namespace GlitchyEditor.EditWindows
{
class ComponentEditWindow
{
private Editor _editor;
private bool _show = true;
public Editor Editor => _editor;
public bool Show
{
get => _show;
set => _show = value;
}
public this(Editor editor)
{
_editor = editor;
}
public void Show()
{
if(!_show)
return;
if(!ImGui.Begin("Components"))
{
ImGui.End();
return;
}
if(_editor.SelectedEntities.Count == 1)
{
Entity entity = _editor.SelectedEntities.Front;
ShowComponents(entity);
}
ImGui.End();
}
private void ShowComponents(Entity entity)
{
NameComponentEditor.Show(_editor.World, entity);
TransformComponentEditor.Show(_editor.World, entity);
}
}
static class NameComponentEditor
{
public static void Show(EcsWorld world, Entity entity)
{
char8[128] nameBuffer = default;
DebugNameComponent* component = world.GetComponent<DebugNameComponent>(entity);
String name = null;
if(component != null)
{
name = component.DebugName;
}
else
{
name = scope:: $"Entity {entity.[Friend]Index}";
}
// Copy name to buffer
Internal.MemCpy(&nameBuffer, name.Ptr, Math.Min(nameBuffer.Count, name.Length));
if(ImGui.InputText("Name", &nameBuffer, nameBuffer.Count))
{
if(component == null)
{
component = world.AssignComponent<DebugNameComponent>(entity);
}
component.DebugName.Clear();
component.DebugName.Append(&nameBuffer);
}
}
}
static class TransformComponentEditor
{
public static void Show(EcsWorld world, Entity entity)
{
TransformComponent* component = world.GetComponent<TransformComponent>(entity);
if(component == null)
return;
if(ImGui.TreeNodeEx("Transform", .DefaultOpen))
{
Vector3 position = component.Position;
bool positionChanged = false;
Vector3 rotationEuler = MathHelper.ToDegrees(component.RotationEuler);
bool rotationChanged = false;
Vector3 scale = component.Scale;
bool scaleChanged = false;
void ShowValue(String text, ref float value, ref bool valueChanged, String id)
{
ImGui.Text(text);
ImGui.SameLine();
ImGui.PushID(id);
valueChanged |= ImGui.DragFloat(String.Empty, &value, 0.1f);
ImGui.PopID();
}
void ShowTableRow(String name, ref Vector3 value, ref bool valueChanged)
{
ImGui.TableNextColumn();
ImGui.Text(name);
ImGui.TableNextColumn();
ShowValue("X: ", ref value.X, ref valueChanged, scope $"{name}X");
ImGui.TableNextColumn();
ShowValue("Y: ", ref value.Y, ref valueChanged, scope $"{name}Y");
ImGui.TableNextColumn();
ShowValue("Z: ", ref value.Z, ref valueChanged, scope $"{name}Z");
ImGui.TableNextRow();
}
ImGui.BeginTable("posRotScaleTable", 4);
ShowTableRow("Position", ref position, ref positionChanged);
ShowTableRow("Rotation", ref rotationEuler, ref rotationChanged);
ShowTableRow("Scale", ref scale, ref scaleChanged);
ImGui.EndTable();
if(positionChanged)
component.Position = position;
if(rotationChanged)
component.RotationEuler = MathHelper.ToRadians(rotationEuler);
if(scaleChanged)
component.Scale = scale;
ImGui.TreePop();
}
}
}
}
@@ -0,0 +1,338 @@
using GlitchyEngine.Collections;
using GlitchyEngine.World;
using ImGui;
using System;
using System.Collections;
using GlitchyEngine;
namespace GlitchyEditor.EditWindows
{
using internal GlitchyEditor;
/// A window for viewing and editing the scene hierarchy
class EntityHierarchyWindow
{
private Editor _editor;
/// Buffer for the entity search string.
private char8[64] _entitySearchChars;
private bool _show = true;
public Editor Editor => _editor;
public bool Show
{
get => _show;
set => _show = value;
}
public this(Editor editor)
{
_editor = editor;
}
public void Show()
{
if(!_show)
return;
if(!ImGui.Begin("Entity Hierarchy", null, .MenuBar))
{
ImGui.End();
return;
}
ShowEntityHierarchyMenuBar();
ShowEntityHierarchy();
ImGui.End();
}
private void ShowEntityHierarchyMenuBar()
{
if(ImGui.BeginMenuBar())
{
if(ImGui.BeginMenu("Create"))
{
if(ImGui.MenuItem("Empty Entity"))
{
_editor.CreateEntityWithTransform();
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new Entity with a transform component.");
if(ImGui.MenuItem("Empty Child", null, false, !_editor.SelectedEntities.IsEmpty))
{
var newEntity = _editor.CreateEntityWithTransform();
var parent = _editor.World.AssignComponent<ParentComponent>(newEntity);
// Last entity in list is the entity that has been selected last.
parent.Entity = _editor.SelectedEntities.Back;
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new Entity that is a child of the currently selected entity.");
if(ImGui.MenuItem("Empty Parent", null, false, !_editor.SelectedEntities.IsEmpty && _editor.AllSelectionsOnSameLevel()))
{
var commonParent = _editor.World.GetComponent<ParentComponent>(_editor.SelectedEntities.Front);
var newEntity = _editor.CreateEntityWithTransform();
if(commonParent != null)
{
// parent of selected entities is parent of the new entity.
// (which is why this doesn't work if the entities don't have the same parent)
var newEntityParent = _editor.World.AssignComponent<ParentComponent>(newEntity);
newEntityParent.Entity = commonParent.Entity;
}
// new entity is parent of all selected entities.
for(var selectedEntity in _editor.SelectedEntities)
{
var selectedEntityParent = _editor.World.AssignComponent<ParentComponent>(selectedEntity);
selectedEntityParent.Entity = newEntity;
}
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new Entity that is the parent of the currently selected entities.");
ImGui.EndMenu();
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new Entity.");
if(ImGui.MenuItem("Delete", null, false, !_editor.SelectedEntities.IsEmpty) || Input.IsKeyPressed(.Delete))
{
_editor.DeleteSelectedEntities();
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Deletes the selected Entities and their children.");
ImGui.Text("Search:");
ImGui.InputText(String.Empty, &_entitySearchChars, (.)_entitySearchChars.Count);
ImGui.EndMenuBar();
}
}
private void ImGuiPrintEntityTree(TreeNode<Entity> tree)
{
String name = null;
var nameComponent = _editor.World.GetComponent<DebugNameComponent>(tree.Value);
if(nameComponent != null)
{
name = nameComponent.DebugName;
}
else
{
name = scope:: $"Entity {(tree.Value.[Friend]Index)}";
}
ImGui.TreeNodeFlags flags = .OpenOnArrow;
if(tree.Children.Count == 0)
flags |= .Leaf;
bool inSelectedList = _editor.SelectedEntities.Contains(tree.Value);
if(inSelectedList)
flags |= .Selected;
bool isOpen = ImGui.TreeNodeEx(name, flags);
if(ImGui.BeginDragDropSource())
{
ImGui.SetDragDropPayload("DND_Entity", &tree.Value, sizeof(Entity));
ImGui.Text(name);
ImGui.EndDragDropSource();
}
if(ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = &ImGui.AcceptDragDropPayload("DND_Entity");
if(payload != null)
{
Log.ClientLogger.AssertDebug(payload.DataSize == sizeof(Entity));
Entity movedEntity = *(Entity*)payload.Data;
bool dropLegal = true;
Entity walker = tree.Value;
// make sure the dropped entity is not a parent of the entity we dropped it on.
while(true)
{
var walkerParent = _editor.World.GetComponent<ParentComponent>(walker);
if(walkerParent == null)
{
dropLegal = true;
break;
}
else if(walkerParent.Entity == movedEntity)
{
dropLegal = false;
break;
}
walker = walkerParent.Entity;
}
if(dropLegal)
{
var movedEntityParent = _editor.World.AssignComponent<ParentComponent>(movedEntity);
movedEntityParent.Entity = tree.Value;
}
}
ImGui.EndDragDropTarget();
}
bool clicked = ImGui.IsItemClicked();
if(isOpen)
{
for(var child in tree.Children)
{
ImGuiPrintEntityTree(child);
}
ImGui.TreePop();
}
if(clicked)
{
if(inSelectedList)
{
_editor.SelectedEntities.Remove(tree.Value);
}
else
{
if(!ImGui.GetIO().KeyCtrl)
{
_editor.SelectedEntities.Clear();
}
_editor.SelectedEntities.Add(tree.Value);
}
inSelectedList = !inSelectedList;
}
}
private void ShowEntityHierarchy()
{
StringView searchString = StringView(&_entitySearchChars);
if(searchString.Length == 0)
{
// Show entity hierarchy as tree
TreeNode<Entity> root = scope .(.InvalidEntity);
TreeNode<Entity> AddEntity(Entity entity)
{
var parent = _editor.World.GetComponent<ParentComponent>(entity);
if(parent == null)
{
return root.AddChild(entity);
}
else
{
var parentNode = root.FindNode(parent.Entity);
if(parentNode == null)
parentNode = AddEntity(parent.Entity);
return parentNode.AddChild(entity);
}
}
for(var entity in _editor.World.Enumerate())
{
AddEntity(entity);
}
if(ImGui.TreeNodeEx("Scene", .DefaultOpen))
{
if(ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = &ImGui.AcceptDragDropPayload("DND_Entity");
if(payload != null)
{
Log.ClientLogger.AssertDebug(payload.DataSize == sizeof(Entity));
Entity movedEntity = *(Entity*)payload.Data;
_editor.World.RemoveComponent<ParentComponent>(movedEntity);
// Also mark transform as dirty
var transformComponent = _editor.World.GetComponent<TransformComponent>(movedEntity);
transformComponent?.IsDirty = true;
}
ImGui.EndDragDropTarget();
}
for(var child in root.Children)
{
ImGuiPrintEntityTree(child);
}
ImGui.TreePop();
}
}
// Otherwise
else
{
// Show search results as flat list
List<StringView> searchTokens = new:ScopedAlloc! .(searchString.Split(' ', .RemoveEmptyEntries));
worldEnumeration:
for(var entity in _editor.World.Enumerate())
{
String name = null;
var nameComponent = _editor.World.GetComponent<DebugNameComponent>(entity);
if(nameComponent != null)
{
name = nameComponent.DebugName;
}
else
{
name = scope:worldEnumeration $"Entity {entity.[Friend]Index}";
}
StringView nameView = StringView(name);
for(var token in searchTokens)
{
if(nameView.IndexOf(token, true) == -1)
{
continue worldEnumeration;
}
}
ImGuiPrintEntityTree(scope .(entity));
}
}
}
}
}
+108
View File
@@ -0,0 +1,108 @@
using GlitchyEngine.World;
using ImGui;
using System;
using System.Collections;
using GlitchyEngine.Collections;
using GlitchyEditor.EditWindows;
namespace GlitchyEditor
{
class Editor
{
private EcsWorld _world;
private EntityHierarchyWindow _entityHierarchyWindow = new .(this) ~ delete _;
private ComponentEditWindow _componentEditWindow = new .(this) ~ delete _;
private List<Entity> _selectedEntities = new .() ~ delete _;
public EcsWorld World => _world;
public List<Entity> SelectedEntities => _selectedEntities;
/// Creates a new editor for the given world
public this(EcsWorld world)
{
_world = world;
}
public void Update()
{
_entityHierarchyWindow.Show();
_componentEditWindow.Show();
}
/// Creates a new entity with a transform component.
internal Entity CreateEntityWithTransform()
{
var entity = _world.NewEntity();
var transformComponent = ref *_world.AssignComponent<TransformComponent>(entity);
transformComponent = TransformComponent();
var nameComponent = ref *_world.AssignComponent<DebugNameComponent>(entity);
nameComponent.SetName("Entity");
return entity;
}
/// Returns whether or not all selected entities have the same parent.
internal bool AllSelectionsOnSameLevel()
{
Entity? parent = .InvalidEntity;
for(var selectedEntity in _selectedEntities)
{
var parentComponent = _world.GetComponent<ParentComponent>(selectedEntity);
if(parent == .InvalidEntity)
{
parent = parentComponent?.Entity;
}
else if(parentComponent?.Entity != parent)
{
return false;
}
}
return true;
}
/// Finds all children of the given entity and stores their IDs in the given list.
internal void FindChildren(Entity entity, List<Entity> entities)
{
for(var (child, childParent) in _world.Enumerate<ParentComponent>())
{
if(childParent.Entity == entity)
{
if(!entities.Contains(child))
entities.Add(child);
FindChildren(child, entities);
}
}
}
/// Deletes all selected entities and their children.
internal void DeleteSelectedEntities()
{
List<Entity> entities = scope .();
for(var entity in _selectedEntities)
{
entities.Add(entity);
FindChildren(entity, entities);
}
for(var entity in entities)
{
_world.RemoveEntity(entity);
}
_selectedEntities.Clear();
}
}
}
+19
View File
@@ -0,0 +1,19 @@
using System;
using GlitchyEngine;
namespace GlitchyEditor
{
class SandboxApp : Application
{
public this()
{
PushLayer(new EditorLayer());
}
[Export, LinkName("CreateApplication")]
public static Application CreateApplication()
{
return new SandboxApp();
}
}
}
+90
View File
@@ -0,0 +1,90 @@
using GlitchyEditor.EditWindows;
using GlitchyEngine;
using GlitchyEngine.Events;
using GlitchyEngine.ImGui;
using GlitchyEngine.Renderer;
using GlitchyEngine.World;
using ImGui;
namespace GlitchyEditor
{
class EditorLayer : Layer
{
RasterizerState _rasterizerState ~ _?.ReleaseRef();
RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef();
GraphicsContext _context ~ _?.ReleaseRef();
DepthStencilTarget _depthTarget ~ _?.ReleaseRef();
BlendState _alphaBlendState ~ _?.ReleaseRef();
BlendState _opaqueBlendState ~ _?.ReleaseRef();
EcsWorld _world = new EcsWorld() ~ delete _;
Editor _editor = new Editor(_world) ~ delete _;
public this() : base("Example")
{
Application.Get().Window.IsVSync = false;
InitGraphics();
InitEcs();
}
private void InitGraphics()
{
_context = Application.Get().Window.Context..AddRef();
_depthTarget = new DepthStencilTarget(_context, _context.SwapChain.Width, _context.SwapChain.Height);
RasterizerStateDescription rsDesc = .(.Solid, .Back, true);
_rasterizerState = new RasterizerState(_context, rsDesc);
rsDesc.FrontCounterClockwise = false;
_rasterizerStateClockWise = new RasterizerState(_context, rsDesc);
BlendStateDescription blendDesc = .();
blendDesc.RenderTarget[0] = .(true, .SourceAlpha, .InvertedSourceAlpha, .Add, .SourceAlpha, .InvertedSourceAlpha, .Add, .All);
_alphaBlendState = new BlendState(_context, blendDesc);
_opaqueBlendState = new BlendState(_context, .Default);
}
private void InitEcs()
{
_world.Register<DebugNameComponent>();
_world.Register<TransformComponent>();
_world.Register<ParentComponent>();
_world.Register<MeshComponent>();
_world.Register<MeshRendererComponent>();
_world.Register<SkinnedMeshRendererComponent>();
_world.Register<CameraComponent>();
_world.Register<AnimationComponent>();
}
public override void Update(GameTime gameTime)
{
RenderCommand.Clear(null, .(0.2f, 0.2f, 0.2f));
_depthTarget.Clear(1.0f, 0, .Depth);
_context.SetRenderTarget(null);
_depthTarget.Bind();
_context.BindRenderTargets();
_context.SetViewport(_context.SwapChain.BackbufferViewport);
}
public override void OnEvent(Event event)
{
EventDispatcher dispatcher = EventDispatcher(event);
dispatcher.Dispatch<ImGuiRenderEvent>(scope (e) => OnImGuiRender(e));
}
private bool OnImGuiRender(ImGuiRenderEvent event)
{
_editor.Update();
return false;
}
}
}