Basic Model drag'n'drop

This commit is contained in:
Simon Lübeß
2022-09-27 20:24:03 +02:00
parent 3ec537762a
commit e95c5731b6
4 changed files with 415 additions and 139 deletions
@@ -5,6 +5,7 @@ using GlitchyEngine.Math;
using System.Collections; using System.Collections;
using GlitchyEngine.Renderer; using GlitchyEngine.Renderer;
using GlitchyEngine; using GlitchyEngine;
using GlitchyEngine.Content;
namespace GlitchyEditor.EditWindows namespace GlitchyEditor.EditWindows
{ {
@@ -62,6 +63,7 @@ namespace GlitchyEditor.EditWindows
ShowComponentEditor<SpriterRendererComponent>("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu<SpriterRendererComponent>); ShowComponentEditor<SpriterRendererComponent>("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu<SpriterRendererComponent>);
ShowComponentEditor<MeshRendererComponent>("Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu<MeshRendererComponent>); ShowComponentEditor<MeshRendererComponent>("Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu<MeshRendererComponent>);
ShowComponentEditor<LightComponent>("Light", entity, => ShowLightComponentEditor, => ShowComponentContextMenu<LightComponent>); ShowComponentEditor<LightComponent>("Light", entity, => ShowLightComponentEditor, => ShowComponentContextMenu<LightComponent>);
ShowComponentEditor<MeshComponent>("Mesh", entity, => ShowMeshComponentEditor, => ShowComponentContextMenu<MeshComponent>);
ShowAddComponentButton(entity); ShowAddComponentButton(entity);
} }
@@ -461,6 +463,47 @@ namespace GlitchyEditor.EditWindows
light.Illuminance = illuminance; light.Illuminance = illuminance;
} }
private static void ShowMeshComponentEditor(Entity entity, MeshComponent* meshComponent)
{
ImGui.Button("Drag Mesh here!");
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM");
if (payload != null)
{
StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize);
int idx = fullpath.IndexOf('#');
if (idx == -1)
{
// Doesn't make sense here, we NEED a sub asset
Runtime.NotImplemented();
}
StringView filePath = fullpath.Substring(0, idx);
StringView meshName = fullpath.Substring(idx + 1);
// TODO: support multiple primitives (treat every primitive as a single mesh?)
using (GeometryBinding binding = ModelLoader.LoadMesh(filePath, meshName, 0))
{
meshComponent.Mesh = binding;
}
//ModelLoader.LoadModel(scope .(path), )
/*using (Texture2D newTexture = new Texture2D(path, true))
{
newTexture.SamplerState = SamplerStateManager.AnisotropicWrap;
material.SetTexture(texture.key, newTexture);
}*/
}
ImGui.EndDragDropTarget();
}
}
private static void ShowAddComponentButton(Entity entity) private static void ShowAddComponentButton(Entity entity)
{ {
static char8[128] searchBuffer = .(); static char8[128] searchBuffer = .();
@@ -8,70 +8,38 @@ using GlitchyEngine.Math;
namespace GlitchyEditor.EditWindows namespace GlitchyEditor.EditWindows
{ {
using internal GlitchyEditor.EditorContentManager;
class ContentBrowserWindow : EditorWindow class ContentBrowserWindow : EditorWindow
{ {
// TODO: Get from project // TODO: Get from project
const String ContentDirectory = "./content"; const String ContentDirectory = "./content";
FileSystemWatcher fsw ~ { //private String _currentDirectory ~ delete _;
_.StopRaisingEvents(); private TreeNode<EditorContentManager.AssetNode> _currentDirectoryNode;
delete _;
};
private String _currentDirectory ~ delete _;
public static SubTexture2D s_FolderTexture; public static SubTexture2D s_FolderTexture;
public static SubTexture2D s_FileTexture; public static SubTexture2D s_FileTexture;
public EditorContentManager _manager ~ delete _;
public this() public this()
{ {
fsw = new FileSystemWatcher(ContentDirectory); _manager = new EditorContentManager();
fsw.IncludeSubdirectories = true;
fsw.OnChanged.Add(new (filename) => {
_fileSystemDirty = true;
_currentDirectoryDirty = true;
});
fsw.OnCreated.Add(new (filename) => {
_fileSystemDirty = true;
_currentDirectoryDirty = true;
});
fsw.OnDeleted.Add(new (filename) => {
_fileSystemDirty = true;
_currentDirectoryDirty = true;
});
fsw.OnRenamed.Add(new (newName, oldName) => {
_fileSystemDirty = true;
_currentDirectoryDirty = true;
});
fsw.StartRaisingEvents();
} }
protected override void InternalShow() protected override void InternalShow()
{ {
_manager.Update();
_currentDirectoryNode ??= _manager._assetHierarchy;
if(!ImGui.Begin("Content Browser", &_open, .None)) if(!ImGui.Begin("Content Browser", &_open, .None))
{ {
ImGui.End(); ImGui.End();
return; return;
} }
if (_fileSystemDirty)
{
BuildDirectoryTree();
_fileSystemDirty = false;
}
if (_currentDirectoryDirty)
{
BuildCurrentDirectory();
}
ImGui.Columns(2); ImGui.Columns(2);
DrawDirectorySideBar(); DrawDirectorySideBar();
@@ -85,101 +53,27 @@ namespace GlitchyEditor.EditWindows
ImGui.End(); ImGui.End();
} }
private bool _fileSystemDirty = true;
private bool _currentDirectoryDirty = true;
class DirectoryNode
{
public String Name ~ delete _;
public String Path ~ delete _;
}
TreeNode<DirectoryNode> directoryNames = new TreeNode<DirectoryNode>() ~ DeleteTreeAndChildren!(_);
class Entry
{
public String Name ~ delete _;
public bool IsDirectory;
}
List<Entry> _currentDirContent = new .() ~ DeleteContainerAndItems!(_);
private void BuildDirectoryTree()
{
DeleteTreeAndChildren!(directoryNames);
directoryNames = new TreeNode<DirectoryNode>();
String str = scope .(ContentDirectory);
void AddDirectoryToTree(String path, TreeNode<DirectoryNode> parentNode)
{
DirectoryNode node = new DirectoryNode();
node.Path = new String(path);
node.Name = new String();
Path.GetFileName(node.Path, node.Name);
var newNode = parentNode.AddChild(node);
String filter = scope $"{path}/*";
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(str..Clear());
AddDirectoryToTree(str, newNode);
}
}
String filter = scope $"{ContentDirectory}/*";
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(str..Clear());
AddDirectoryToTree(str, directoryNames);
}
}
private void BuildCurrentDirectory()
{
ClearAndDeleteItems!(_currentDirContent);
String filter = scope $"{_currentDirectory}/*";
String buffer = scope String();
for (var entry in Directory.Enumerate(filter, .Directories | .Files))
{
entry.GetFilePath(buffer..Clear());
Entry e = new Entry();
e.Name = new String();
Path.GetFileName(buffer, e.Name);
e.IsDirectory = entry.IsDirectory;
_currentDirContent.Add(e);
}
}
private void DrawDirectorySideBar() private void DrawDirectorySideBar()
{ {
for(var child in directoryNames.Children) for(var child in _manager._assetHierarchy.Children)
{ {
ImGuiPrintEntityTree(child); ImGuiPrintEntityTree(child);
} }
} }
private void ImGuiPrintEntityTree(TreeNode<DirectoryNode> tree) private void ImGuiPrintEntityTree(TreeNode<EditorContentManager.AssetNode> tree)
{ {
String name = tree.Value.Name; if (!tree->IsDirectory)
return;
String name = tree->Name;
ImGui.TreeNodeFlags flags = .OpenOnArrow | .SpanAvailWidth; ImGui.TreeNodeFlags flags = .OpenOnArrow | .SpanAvailWidth;
if(tree.Children.Count == 0) if(tree.Children.Count == 0)
flags |= .Leaf; flags |= .Leaf;
if (tree.Value.Path == _currentDirectory) if (tree == _currentDirectoryNode)
{ {
flags |= .Selected; flags |= .Selected;
} }
@@ -188,10 +82,11 @@ namespace GlitchyEditor.EditWindows
if (ImGui.IsItemClicked(.Left)) if (ImGui.IsItemClicked(.Left))
{ {
if (_currentDirectory != null) /*if (_currentDirectory != null)
delete _currentDirectory; delete _currentDirectory;
_currentDirectory = new String(tree.Value.Path); _currentDirectory = new String(tree.Value.Path);*/
_currentDirectoryNode = tree;
} }
if(isOpen) if(isOpen)
@@ -205,35 +100,38 @@ namespace GlitchyEditor.EditWindows
} }
} }
private static Vector2 DirectoryItemSize = .(100, 100); private static Vector2 DirectoryItemSize = .(110, 110);
const Vector2 padding = .(24, 24); const Vector2 padding = .(24, 24);
private void DrawCurrentDirectory() private void DrawCurrentDirectory()
{ {
if (_currentDirectoryNode == null)
return;
ImGui.Style* style = ImGui.GetStyle(); ImGui.Style* style = ImGui.GetStyle();
float window_visible_x2 = ImGui.GetWindowPos().x + ImGui.GetWindowContentRegionMax().x; float window_visible_x2 = ImGui.GetWindowPos().x + ImGui.GetWindowContentRegionMax().x;
for (var entry in _currentDirContent) for (var entry in _currentDirectoryNode.Children)
{ {
ImGui.PushID(entry.Name); ImGui.PushID(entry->Name);
DrawDirectoryItem(entry); DrawDirectoryItem(entry);
float last_button_x2 = ImGui.GetItemRectMax().x; float last_button_x2 = ImGui.GetItemRectMax().x;
float next_button_x2 = last_button_x2 + style.ItemSpacing.x + DirectoryItemSize.X; // Expected position if next button was on same line float next_button_x2 = last_button_x2 + style.ItemSpacing.x + DirectoryItemSize.X; // Expected position if next button was on same line
if (entry != _currentDirContent.Back && next_button_x2 < window_visible_x2) if (entry != _currentDirectoryNode.Children.Back && next_button_x2 < window_visible_x2)
ImGui.SameLine(); ImGui.SameLine();
ImGui.PopID(); ImGui.PopID();
} }
} }
private void DrawDirectoryItem(Entry entry) private void DrawDirectoryItem(TreeNode<EditorContentManager.AssetNode> entry)
{ {
ImGui.BeginChild("item", (.)DirectoryItemSize); ImGui.BeginChild("item", (.)DirectoryItemSize);
SubTexture2D image = entry.IsDirectory ? s_FolderTexture : s_FileTexture; SubTexture2D image = entry->IsDirectory ? s_FolderTexture : s_FileTexture;
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0)); ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
@@ -241,7 +139,7 @@ namespace GlitchyEditor.EditWindows
if (ImGui.BeginDragDropSource()) if (ImGui.BeginDragDropSource())
{ {
String fullpath = scope $"{_currentDirectory}{Path.DirectorySeparatorChar}{entry.Name}"; String fullpath = scope $"{_currentDirectoryNode->Path}{Path.DirectorySeparatorChar}{entry->Name}";
// TODO: this is dirty // TODO: this is dirty
if (fullpath.StartsWith(ContentDirectory, .OrdinalIgnoreCase)) if (fullpath.StartsWith(ContentDirectory, .OrdinalIgnoreCase))
@@ -259,17 +157,51 @@ namespace GlitchyEditor.EditWindows
ImGui.PopStyleColor(); ImGui.PopStyleColor();
ImGui.TextUnformatted(entry.Name); ImGui.TextUnformatted(entry->Name);
if (entry->SubAssets?.Count > 0)
{
// Button for revealing sub assets (e.g. Meshes in 3D-Model)
ImGui.SameLine();
if (ImGui.Button(">"))
ImGui.OpenPopup("SubAssets");
}
if (ImGui.BeginPopup("SubAssets", .Popup))
{
for (var subAsset in entry->SubAssets)
{
ImGui.Button(subAsset.Name);
if (ImGui.BeginDragDropSource())
{
String fullpath = Path.InternalCombine(.. scope String(), _currentDirectoryNode->Path, entry->Name);
fullpath.AppendF($"#{subAsset.Name}");
//scope $"{_currentDirectoryNode->Path}{Path.DirectorySeparatorChar}{entry->Name}";
//String fullpath = scope $"{subAsset.Asset.Path}#{subAsset.Name}";
// TODO: this is dirty
//if (fullpath.StartsWith(ContentDirectory, .OrdinalIgnoreCase))
// fullpath.Remove(0, ContentDirectory.Length);
ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once);
ImGui.EndDragDropSource();
}
}
ImGui.EndPopup();
}
ImGui.EndChild(); ImGui.EndChild();
} }
private void EntryDoubleClicked(Entry entry) private void EntryDoubleClicked(TreeNode<EditorContentManager.AssetNode> entry)
{ {
if (entry.IsDirectory) if (entry->IsDirectory)
{ {
_currentDirectory.Append(Path.DirectorySeparatorChar); _currentDirectoryNode = entry;
_currentDirectory.Append(entry.Name);
} }
} }
} }
+247
View File
@@ -0,0 +1,247 @@
using System;
using System.IO;
using GlitchyEngine.Collections;
using System.Collections;
using GlitchyEngine.Renderer;
using System.Threading;
using GlitchyEngine.Content;
namespace GlitchyEditor;
/*class PreviewImageManager
{
/// Thread that loads/generates preview images in the background
//private Thread _loaderThread;
/// Set to true to notify the loader to stop
//private bool _stopLoader;
private append Dictionary<String, Texture2D> _previewImages = .() ~ {
for (var v in _)
{
delete v.key;
v.value.ReleaseRef();
}
delete _;
};
public Texture2D GetPreviewImage(String assetName)
{
if (_previewImages.TryGetValue(assetName, let image))
{
return image..AddRef();
}
if (assetName.EndsWith(".png", .OrdinalIgnoreCase))
{
/*using (Texture2D texture = new Texture2D(assetName, true))
{
Texture2DDesc desc = .(128, 128, .R8G8B8A8_UNorm_SRGB)
{
Usage = .Immutable
};
Texture2D myActualTexture = new Texture2D(desc);
// TODO: Scale down texture (on GPU?)
}*/
Texture2D texture = new Texture2D(assetName, true);
if (texture.Width <= 128 && texture.Height <= 128)
{
}
if ( texture.MipLevels > 1)
{
}
_previewImages.Add(new String(assetName), texture);
return texture..AddRef();
}
else
{
Runtime.NotImplemented();
}
}
}*/
class EditorContentManager
{
// TODO: Get from project
const String ContentDirectory = "./content";
FileSystemWatcher fsw ~ {
_.StopRaisingEvents();
delete _;
};
bool _fileSystemDirty = true;
public class AssetNode
{
public String Name ~ delete _;
public String Path ~ delete _;
public bool IsDirectory;
public List<Asset> SubAssets ~ {
SubAssets?.ClearAndDeleteItems();
delete SubAssets;
}
public Texture2D PreviewImage ~ _?.ReleaseRef();
}
public class Asset
{
public AssetNode Asset;
public String Name ~ delete _;
//public String AssetInternalPath ~ delete _;
public Texture2D PreviewImage ~ _?.ReleaseRef();
}
internal TreeNode<AssetNode> _assetHierarchy = new TreeNode<AssetNode>() ~ DeleteTreeAndChildren!(_);
public this()
{
SetupFileSystemWatcher();
}
/// Initializes the FSW for the current ContentDirectory and registers the events.
private void SetupFileSystemWatcher()
{
fsw = new FileSystemWatcher(ContentDirectory);
fsw.IncludeSubdirectories = true;
fsw.OnChanged.Add(new (filename) => {
_fileSystemDirty = true;
});
fsw.OnCreated.Add(new (filename) => {
_fileSystemDirty = true;
});
fsw.OnDeleted.Add(new (filename) => {
_fileSystemDirty = true;
});
fsw.OnRenamed.Add(new (newName, oldName) => {
_fileSystemDirty = true;
});
fsw.StartRaisingEvents();
}
public void Update()
{
if (_fileSystemDirty)
{
UpdateFiles();
}
}
private void UpdateFiles()
{
DeleteTreeAndChildren!(_assetHierarchy);
_assetHierarchy = new TreeNode<AssetNode>(new AssetNode());
_assetHierarchy->Path = new String(ContentDirectory);
_assetHierarchy->Name = new String("Content");
String str = scope .(ContentDirectory);
void GrabSubAssets(TreeNode<AssetNode> assetNode)
{
// TODO: Dedicated asset loaders that register their extensions, etc....!
if (assetNode->Name.EndsWith(".png", .OrdinalIgnoreCase))
{
assetNode->SubAssets = new List<Asset>();
assetNode->SubAssets.Add(new Asset()
{
Asset = assetNode.Value,
Name = new String(assetNode->Name)
});
}
else if (assetNode->Name.EndsWith(".gltf", .OrdinalIgnoreCase) || assetNode->Name.EndsWith(".glb", .OrdinalIgnoreCase))
{
List<String> meshNames = scope List<String>();
ModelLoader.GetMeshNames(assetNode->Path, meshNames);
assetNode->SubAssets = new List<Asset>();
for (var meshName in meshNames)
{
assetNode->SubAssets.Add(new Asset()
{
Asset = assetNode.Value,
// Pass ownership of meshName to SubAsset -> save a copy and delete
Name = meshName
});
}
}
}
void AddFilesOfDirectory(TreeNode<AssetNode> directory)
{
String filter = scope $"{directory->Path}/*";
String buffer = scope String();
for (var entry in Directory.Enumerate(filter, .Files))
{
entry.GetFilePath(buffer..Clear());
AssetNode e = new AssetNode();
e.Name = new String();
Path.GetFileName(buffer, e.Name);
e.Path = new String(buffer);
e.IsDirectory = entry.IsDirectory;
var node = directory.AddChild(e);
GrabSubAssets(node);
}
}
void AddDirectoryToTree(String path, TreeNode<AssetNode> parentNode)
{
AssetNode node = new AssetNode();
node.Path = new String(path);
node.Name = new String();
node.IsDirectory = true;
Path.GetFileName(node.Path, node.Name);
var newNode = parentNode.AddChild(node);
String filter = scope $"{path}/*";
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(str..Clear());
AddDirectoryToTree(str, newNode);
}
AddFilesOfDirectory(newNode);
}
String filter = scope $"{ContentDirectory}/*";
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(str..Clear());
AddDirectoryToTree(str, _assetHierarchy);
}
_fileSystemDirty = false;
}
}
+54
View File
@@ -12,6 +12,60 @@ namespace GlitchyEngine.Content
{ {
static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1); static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1);
public static Result<void> GetMeshNames(String filename, List<String> meshNames)
{
CGLTF.Options options = .();
CGLTF.Data* data;
CGLTF.Result result = CGLTF.ParseFile(options, filename, out data);
if (!(result case .Success))
return .Err;
for (var mesh in data.Meshes)
{
meshNames.Add(new String(mesh.Name));
}
CGLTF.Free(data);
return .Ok;
}
public static GeometryBinding LoadMesh(StringView fileName, StringView meshName, int primitiveIndex)
{
// TODO: add a context to remember which buffers were loaded before so that we don't load the same data multiple times.
char8* scopedFileName = fileName.ToScopeCStr!();
CGLTF.Options options = .();
CGLTF.Data* data;
CGLTF.Result result = CGLTF.ParseFile(options, scopedFileName, out data);
if (!(result case .Success))
return null;
result = CGLTF.LoadBuffers(options, data, scopedFileName);
GeometryBinding geoBinding = null;
for (var mesh in data.Meshes)
{
var name = StringView(mesh.Name);
if (name == meshName)
{
Log.EngineLogger.AssertDebug(primitiveIndex >= 0 && primitiveIndex < mesh.Primitives.Length);
geoBinding = PrimitiveToGeoBinding(mesh.Primitives[primitiveIndex]);
break;
}
}
CGLTF.Free(data);
return geoBinding;
}
public static EcsEntity LoadModel(String filename, Material material, EcsWorld world, public static EcsEntity LoadModel(String filename, Material material, EcsWorld world,
List<AnimationClip> outClips, StringView entityName = StringView()) List<AnimationClip> outClips, StringView entityName = StringView())
{ {