100 Commits
Author SHA1 Message Date
Simon Lübeß 2ce357cd12 Basic material serialisation 2023-03-05 16:38:50 +01:00
Simon Lübeß 2324ae852d Moved material editing from component window to asset editor
- EditorContentManager now returns null if asset wasn't found (instead of crashing)
- FixUp asset identifier when dragging from asset browser
- Lock current asset in properties editor
2023-01-08 20:31:43 +01:00
Simon Lübeß d3d284256c Fixed WorldEnumerator crashes if one of the components doesn't exist 2023-01-08 20:29:06 +01:00
Simon Lübeß e38373dccf Serialize Mesh and MeshRenderer in scene + fixed Light serialization 2023-01-08 19:31:01 +01:00
Simon Lübeß b5ca86d8de Fixed scope of full path 2023-01-08 16:48:38 +01:00
Simon Lübeß 4589030da6 Load effects via content manager + virtual InitContentManager in App 2023-01-08 00:17:04 +01:00
Simon Lübeß 3da60355df Model and Material loading via content manager 2023-01-07 12:15:14 +01:00
Simon Lübeß ee30920b56 Crappy hot reloading of textures and Properties editor for textures 2023-01-03 22:21:26 +01:00
Simon Lübeß 427323744b Fixed Sampler State filters + Documentation 2023-01-03 22:18:43 +01:00
Simon Lübeß 9d97579857 More content browser stuff
- Seperate AssetHierarchy from EditorContentManager
- Make files selectable and context menu
2023-01-02 20:00:48 +01:00
Simon Lübeß fdf680508d Don't recreate the entire asset tree 2023-01-01 18:25:36 +01:00
Simon Lübeß 0b3f242082 Added Submodule "Beef.Linq" 2023-01-01 18:18:27 +01:00
Simon Lübeß 502cee47e7 EditorContentManager Part 2 2023-01-01 16:53:58 +01:00
Simon Lübeß c389a77274 EditorContentManager Part 1 2023-01-01 16:53:45 +01:00
Simon Lübeß dffd43a36f RenderAPI changes and removed Context parameter from Effect and Material 2022-11-27 17:18:16 +01:00
Simon Lübeß d47ac23e1b Raw Input and some ImGuiExtension changes 2022-11-26 15:24:33 +01:00
Simon Lübeß 8f22cbb8bc Fixed circle rendering and added 2D circle collider 2022-11-26 12:17:31 +01:00
Simon Lübeß 0155a38ae6 UUID 2022-11-16 13:09:26 +01:00
Simon Lübeß a42544f171 2D Physics 2022-10-08 16:56:15 +02:00
Simon Lübeß 202083930a Play Button 2022-10-06 16:33:33 +02:00
Simon Lübeß 6958d27c30 Fixed editor camera for real 2022-09-29 16:39:58 +02:00
Simon Lübeß f8544c8f64 Updated imgui 2022-09-28 22:02:37 +02:00
Simon Lübeß e95c5731b6 Basic Model drag'n'drop 2022-09-27 20:24:03 +02:00
Simon Lübeß 3ec537762a Overload '->' operator for TreeNode 2022-09-27 00:01:05 +02:00
Simon Lübeß e035832ab0 Fixed mouse wrapping when scene viewport is inactive 2022-09-27 00:00:17 +02:00
Simon Lübeß 0f1eddad5f Updated submodule DirectX 2022-09-07 18:28:40 +02:00
Simon Lübeß d2dca12d0c Use corrent RefCounter 2022-08-19 20:01:05 +02:00
Simon Lübeß 0588a93ec4 Use ACES tone mapping 2022-08-18 14:03:33 +02:00
Simon Lübeß 42e386a142 Fixed component name 2022-08-18 13:51:40 +02:00
Simon Lübeß 21c8d839ef Fixed mouse picking 2022-08-18 13:51:12 +02:00
Simon Lübeß bc1f466d18 Drag drop Texture 2022-08-13 13:47:58 +02:00
Simon Lübeß 8495c6db28 Drag Texture 2022-07-26 08:00:17 +02:00
Simon Lübeß bff4418b84 Content browser 2022-07-25 17:03:28 +02:00
Simon Lübeß 0339ae4422 Adjust to fix in beef mixins 2022-06-15 15:31:44 +02:00
Simon Lübeß eb03eca4dd Manual constant buffer binding 2022-06-13 14:10:54 +02:00
Simon Lübeß a926ef4af2 Updated gltf 2022-06-13 13:26:04 +02:00
Simon Lübeß 866353a157 Refactored Effects + start of EngineBuffers (I hate it) 2022-06-07 13:12:03 +02:00
Simon Lübeß a80018e80c Removed redundant Point struct and SceneViewport improvements
- Keyboard shortcuts for Gizmo control
- Improved mousepick-blocking
2022-06-02 12:17:57 +02:00
Simon Lübeß e41645e12a Composit Target and gamma correct rendering 2022-05-31 22:41:20 +02:00
Simon Lübeß 8db2ce1d05 Mouse picking 2022-05-30 14:06:52 +02:00
Simon Lübeß 47fdce0a7e Triangle fullscreen quad 2022-05-28 15:53:35 +02:00
Simon Lübeß d212576719 Render EntityIDs (breaks post processing) 2022-05-28 15:42:48 +02:00
Simon Lübeß 164d420a25 DX11: Automatically validate input layouts against vertex shaders 2022-05-26 11:27:02 +02:00
Simon Lübeß 28f1fad464 RenderTargetGroup GetData, Shader uint support 2022-05-25 22:00:11 +02:00
Simon Lübeß 1418063d68 RenderTargetGroup 2022-05-20 18:37:59 +02:00
Simon Lübeß 4cd0bff91d Fixed editorcamera stuttering, Added editor icons 2022-05-18 14:12:26 +02:00
Simon Lübeß 5d8a4ad3a7 Editor camera and Editor/Runtime scene update separation 2022-05-17 19:06:28 +02:00
Simon Lübeß ccea61fb79 Updated bon 2022-05-16 19:23:45 +02:00
Simon Lübeß de3da8ded8 Gizmo angle snapping 2022-05-16 10:11:33 +02:00
Simon Lübeß 62bb5baac9 Gizmo improvements 2022-05-15 14:36:07 +02:00
Simon Lübeß 3956080520 Start of serializaation
- Allow enumerating over unregistered components
- Refactored Editor a bit
2022-05-14 23:01:08 +02:00
Simon Lübeß 1d3d30f88d Desketchified DX11 shader binding 2022-05-13 15:13:09 +02:00
Simon Lübeß b1a1268319 Terrible postprocessing and start of Editor GUI for lights 2022-05-11 22:49:36 +02:00
Simon Lübeß 56e92a7d67 Added Lights 2022-05-11 11:39:59 +02:00
Simon Lübeß f4c9dd8ade MeshRendererComponentEditor
- Explicit conversion from VectorX to float[X]
- Expose "Columns" and "Rows" properties of BufferVariable
- Effects: VariableDescriptors
2022-05-10 22:29:40 +02:00
Simon Lübeß 2e0cc289df Material.GetVariable and (maybe) fixed SetVariable for Matrix3x3 2022-05-10 22:06:07 +02:00
Simon Lübeß 336e52fbac Fix Modelloader 2022-05-10 11:48:16 +02:00
Simon Lübeß f527af13c3 EnsureTypeMatch speedup 2022-05-09 22:58:22 +02:00
Simon Lübeß fe0d5fa529 More profiling 2022-05-09 22:53:48 +02:00
Simon Lübeß b37b1b4bf9 UnbindTexture, PBR changes, Test scene 2022-05-09 22:11:46 +02:00
Simon Lübeß 3c2c37b270 Shader #include + Remved unused Dx11Effect 2022-05-09 18:19:24 +02:00
Simon Lübeß dc235399c8 Start of PBR 2022-05-09 16:45:16 +02:00
Simon Lübeß 2817c5b1a1 Start of 3d renderer integration 2022-05-04 18:38:59 +02:00
Simon Lübeß 412d0ea965 Fixed release build and automatic compiling of CPP Helper 2022-05-04 18:34:56 +02:00
Simon Lübeß 9e23ac0fcf Removed dangling DXTK-project dependencie 2022-05-04 18:33:43 +02:00
Simon Lübeß a43572817d Vector tweaks 2022-05-04 17:46:15 +02:00
Simon Lübeß 805423a64e Rendertarget inherits from texture 2022-05-04 14:53:31 +02:00
Simon Lübeß 9c93ccee47 Removed redundant submodule-entry 2022-04-13 12:32:42 +02:00
Simon Lübeß f1ce63ee3b Removed submodule GlitchyEngine/vendor/DirectXTK 2022-04-13 00:01:05 +02:00
Simon Lübeß e685b4fbba Added DirectXTK to GlitchyEngineHelper 2022-04-13 00:00:41 +02:00
Simon Lübeß 9105c12e33 Added buildcommand 2022-04-12 14:32:00 +02:00
Simon Lübeß 863c264001 UI Tweaks, ChildEnumerator, ComponentAdded-Handlers 2022-04-12 00:48:20 +02:00
Simon Lübeß bec202e23a Settings / SettingsWindow 2022-04-10 13:12:09 +02:00
Simon Lübeß bca72cf0d4 Add / Remove Components 2022-04-10 12:18:43 +02:00
Simon Lübeß af37154071 Better Transform component and edit gui 2022-04-09 12:35:36 +02:00
Simon Lübeß c7fde476e8 Updated DX11 Bindings 2022-04-09 12:34:28 +02:00
Simon Lübeß 604e34a12a Start of ContentManager 2022-04-09 12:34:09 +02:00
Simon Lübeß b94c84d304 GlitchyEngineHelper project 2022-04-09 12:33:05 +02:00
Simon Lübeß d47bc37886 SpriteRenderer Editor, enabled alpha-blending and depth 2022-04-02 00:10:00 +02:00
Simon Lübeß c13b0a26d1 Overhauled component editors 2022-04-01 23:29:35 +02:00
Simon Lübeß e82f70ba02 Native scripting 2022-03-03 01:39:04 +01:00
Simon Lübeß cbee9f59d8 Many ecs, component and editor changes 2022-03-02 01:59:51 +01:00
Simon Lübeß afe36b19df Removed unnecessary "override extern"s 2022-03-02 01:57:04 +01:00
Simon Lübeß 9197a209d6 Ecs changes
- BitArray has always at least sizeof(uint) bits
- Renamed Entity to EcsEntity
- Added value parameter to EcsWorld.AssignComponent
- Matched EcsWorld generic parameters
- Added Scene
- Added Entity
2022-02-28 23:34:52 +01:00
Simon Lübeß ba5ccd7107 Editor window 2022-02-28 18:08:13 +01:00
Simon Lübeß c80ca4d9a1 Cleaned up DX11 report 2022-02-28 12:13:44 +01:00
Simon Lübeß 92fd8bb572 Removed Bind from Texture 2022-02-27 12:30:09 +01:00
Simon Lübeß b341c70a26 Editor changes
- Updated Renderer2D shaders
- Fixed scene viewport having resolution
- Show Grid even if no entity is selected
- Explicit conversion from ImGui.VecX <-> VectorX
- Simplified EditorLayer
2022-02-27 12:21:30 +01:00
Simon Lübeß 8c8f0d9fc7 Basic tilemap 2022-02-26 18:05:56 +01:00
Simon Lübeß 8638b7ac3b ImGuiLayer cleanup 2022-02-26 18:05:47 +01:00
Simon Lübeß be7c3d1d8b Subtextures 2022-02-23 13:41:59 +01:00
Simon Lübeß 699522ff67 Fixed textureviewer 2022-02-22 20:18:37 +01:00
Simon Lübeß 6f999da1b8 Fixed fallback font rendering 2022-02-22 20:18:06 +01:00
Simon Lübeß 373980d5ad SwizzleVectorAttribute no longer generates invalid setters 2022-02-21 17:06:48 +01:00
Simon Lübeß 0db85de067 Release build fixes + ImGui build flag
Fonts still not working
2022-02-18 16:03:24 +01:00
Simon Lübeß fae2bdf084 Updated submodules
Updated submodule GlitchyEngine/vendor/imgui
Updated submodule GlitchyEngine/vendor/msdfgen
2022-02-17 01:12:02 +01:00
Simon Lübeß 0f0afc2a90 Renderer2D improvements + stats 2022-02-16 20:44:31 +01:00
Simon Lübeß 66dad82dc6 Added ColorHSV 2022-02-14 20:40:50 +01:00
Simon Lübeß 0e684af17d Added Window.SetIcon 2022-02-14 20:38:01 +01:00
Simon Lübeß 82898f1d5c Added DeltaTime 2022-02-14 20:37:31 +01:00
217 changed files with 15647 additions and 3055 deletions
+15 -6
View File
@@ -1,15 +1,9 @@
[submodule "GlitchyEngine/vendor/directx"] [submodule "GlitchyEngine/vendor/directx"]
path = GlitchyEngine/vendor/directx path = GlitchyEngine/vendor/directx
url = https://github.com/aharabada/directx-beef.git url = https://github.com/aharabada/directx-beef.git
[submodule "GlitchyEngine/vendor/DirectXTK"]
path = GlitchyEngine/vendor/DirectXTK
url = https://github.com/aharabada/DirectXTK-beef.git
[submodule "vendor/lodepng-beef"] [submodule "vendor/lodepng-beef"]
path = vendor/lodepng-beef path = vendor/lodepng-beef
url = https://github.com/aharabada/lodepng-beef.git url = https://github.com/aharabada/lodepng-beef.git
[submodule "GlitchyEngine\\vendor\\freetype"]
path = GlitchyEngine\\vendor\\freetype
url = https://github.com/aharabada/FreeType-beef.git
[submodule "GlitchyEngine/vendor/freetype"] [submodule "GlitchyEngine/vendor/freetype"]
path = GlitchyEngine/vendor/freetype path = GlitchyEngine/vendor/freetype
url = https://github.com/aharabada/FreeType-beef.git url = https://github.com/aharabada/FreeType-beef.git
@@ -22,3 +16,18 @@
[submodule "GlitchyEngine/vendor/imgui"] [submodule "GlitchyEngine/vendor/imgui"]
path = GlitchyEngine/vendor/imgui path = GlitchyEngine/vendor/imgui
url = https://github.com/aharabada/ImGui_GlitchyEngine.git url = https://github.com/aharabada/ImGui_GlitchyEngine.git
[submodule "GlitchyEngineHelper/vendor/xxHash"]
path = GlitchyEngineHelper/vendor/xxHash
url = https://github.com/Cyan4973/xxHash.git
[submodule "GlitchyEngine/vendor/bon"]
path = GlitchyEngine/vendor/bon
url = https://github.com/EinScott/bon.git
[submodule "GlitchyEngineHelper/vendor/DirectXTK"]
path = GlitchyEngineHelper/vendor/DirectXTK
url = https://github.com/microsoft/DirectXTK.git
[submodule "GlitchyEngine/vendor/box2D"]
path = GlitchyEngine/vendor/box2D
url = https://github.com/jazzbre/box2d-beef.git
[submodule "GlitchyEngine/vendor/Beef.Linq"]
path = GlitchyEngine/vendor/Beef.Linq
url = https://github.com/aharabada/Beef.Linq.git
+6 -2
View File
@@ -1,5 +1,9 @@
FileVersion = 1 FileVersion = 1
Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}} Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}, bon = {Path = "GlitchyEngine/vendor/bon"}, box2d-beef = {Path = "GlitchyEngine/vendor/box2D"}, "Beef.Linq" = {Path = "GlitchyEngine/vendor/Beef.Linq/src"}}
WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq"]}
[Workspace] [Workspace]
StartupProject = "Sandbox" StartupProject = "GlitchyEditor"
[Configs.Debug.Win64]
AllocStackTraceDepth = 12
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
{
AssetLoader = "ModelAssetLoader",
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
}
Binary file not shown.
@@ -0,0 +1,4 @@
{
AssetLoader = "ModelAssetLoader",
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
}
+154
View File
@@ -0,0 +1,154 @@
{
Name = "Scene name here pls!!!",
Entities = [
{
Id = 5169765174113462770,
NameComponent = {
Name = "Sphere"
},
TransformComponent = {
Position = {
X = 0,
Y = 0.5,
Z = 0
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 1,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
MeshComponent = {
Mesh = "Models\\sphere.glb"
},
MeshRendererComponent = {
Material = "Textures\\TestMaterial.mat"
}
},
{
Id = 17158420331978163131,
NameComponent = {
Name = "Light"
},
TransformComponent = {
Position = {
X = -1,
Y = 4,
Z = -4
},
Rotation = {
X = 0.614328,
Y = 0.239776,
Z = 0.031567,
W = 0.751074
},
Scale = {
X = 0.999999,
Y = 1,
Z = 1.000001
},
EditorEulerRotation = {
X = 1.308998,
Y = 0.349066,
Z = 0.349066
}
},
LightComponent = {
LightType = .Directional,
Illuminance = 10,
Color = {
R = 1,
G = 0.991772,
B = 0.740862
}
}
},
{
Id = 5556050645816939548,
NameComponent = {
Name = "Plane"
},
TransformComponent = {
Position = {
X = 0,
Y = 0,
Z = 0
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 10,
Y = 1,
Z = 10
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
MeshComponent = {
Mesh = "Models\\plane.glb"
},
MeshRendererComponent = {
Material = "Textures\\TestMaterial.mat"
}
},
{
Id = 3947673900993587516,
NameComponent = {
Name = "Camera"
},
TransformComponent = {
Position = {
X = 0,
Y = 2,
Z = -5
},
Rotation = {
X = 0.21644,
Y = 0,
Z = 0,
W = 0.976296
},
Scale = {
X = 1,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0.436332,
Y = 0,
Z = 0
}
},
CameraComponent = {
Primary = true,
ProjectionType = .InfinitePerspective,
PerspectiveFovY = 1.047198,
PerspectiveNearPlane = 0.1,
PerspectiveFarPlane = 10000,
OrthographicHeight = 10,
OrthographicNearPlane = 0,
OrthographicFarPlane = 10,
AspectRatio = 2.156692,
FixedAspectRatio = false
}
}
]
}
@@ -0,0 +1,212 @@
{
Name = "Scene name here pls!!!",
Entities = [
{
Id = 6000868443984780499,
NameComponent = {
Name = "Child"
},
SpriterRendererComponent = {
Color = {
R = 1,
G = 0.388184,
B = 0.090109,
A = 1
},
UvTransform = {
X = 0,
Y = 0,
Z = 1,
W = 1
}
},
TransformComponent = {
ParentId = 820806682740348099,
Position = {
X = 0,
Y = 0.75,
Z = 0
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 0.5,
Y = 0.5,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
}
},
{
Id = 820806682740348099,
NameComponent = {
Name = "Quad"
},
SpriterRendererComponent = {
Color = {
R = 0,
G = 0.551178,
B = 0.328787,
A = 1
},
UvTransform = {
X = 0,
Y = 0,
Z = 1,
W = 1
}
},
TransformComponent = {
Position = {
X = 0,
Y = 2,
Z = 0
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 1,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
Rigidbody2D = {
BodyType = .Dynamic,
FixedRotation = false
},
BoxCollider2D = {
Offset = {
X = 0,
Y = 0
},
Size = {
X = 0.5,
Y = 0.5
},
Density = 1,
Friction = 0.5,
Restitution = 0,
RestitutionThreshold = 0.5
}
},
{
Id = 15398726361722237419,
NameComponent = {
Name = "Floor"
},
SpriterRendererComponent = {
Color = {
R = 1,
G = 0.941886,
B = 0.401485,
A = 1
},
UvTransform = {
X = 0,
Y = 0,
Z = 1,
W = 1
}
},
TransformComponent = {
Position = {
X = 0,
Y = -0.5,
Z = 0
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 10,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
Rigidbody2D = {
BodyType = .Static,
FixedRotation = false
},
BoxCollider2D = {
Offset = {
X = 0,
Y = 0
},
Size = {
X = 0.5,
Y = 0.5
},
Density = 1,
Friction = 0.5,
Restitution = 0,
RestitutionThreshold = 0.5
}
},
{
Id = 1491484622542812645,
NameComponent = {
Name = "Camera"
},
TransformComponent = {
Position = {
X = 0,
Y = 1,
Z = -5
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 1,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
CameraComponent = {
Primary = true,
ProjectionType = .InfinitePerspective,
PerspectiveFovY = 1.308997,
PerspectiveNearPlane = 0.1,
PerspectiveFarPlane = 10000,
OrthographicHeight = 10,
OrthographicNearPlane = 0,
OrthographicFarPlane = 10,
AspectRatio = 2.116827,
FixedAspectRatio = false
}
}
]
}
@@ -0,0 +1,305 @@
{
Name = "Scene name here pls!!!",
Entities = [
{
Id = 820806682740348099,
NameComponent = {
Name = "Quad"
},
SpriterRendererComponent = {
Color = {
R = 0,
G = 0.551178,
B = 0.328787,
A = 1
},
IsCircle = false,
Sprite = "\\Textures\\TestMat\\rustediron2_albedo.png",
UvTransform = {
X = 0,
Y = 0,
Z = 1,
W = 1
}
},
TransformComponent = {
Position = {
X = 0,
Y = 2,
Z = 0
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 1,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
Rigidbody2D = {
BodyType = .Dynamic,
FixedRotation = false
},
BoxCollider2D = {
Offset = {
X = 0,
Y = 0
},
Size = {
X = 0.5,
Y = 0.5
},
Density = 1,
Friction = 0.5,
Restitution = 0,
RestitutionThreshold = 0.5
}
},
{
Id = 15398726361722237419,
NameComponent = {
Name = "Floor"
},
SpriterRendererComponent = {
Color = {
R = 1,
G = 0.941886,
B = 0.401485,
A = 1
},
IsCircle = false,
Sprite = null,
UvTransform = {
X = 0,
Y = 0,
Z = 1,
W = 1
}
},
TransformComponent = {
Position = {
X = 0,
Y = -0.5,
Z = 0
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 10,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
Rigidbody2D = {
BodyType = .Static,
FixedRotation = false
},
BoxCollider2D = {
Offset = {
X = 0,
Y = 0
},
Size = {
X = 0.5,
Y = 0.5
},
Density = 1,
Friction = 0.5,
Restitution = 0,
RestitutionThreshold = 0.5
}
},
{
Id = 1491484622542812645,
NameComponent = {
Name = "Camera"
},
TransformComponent = {
Position = {
X = 0,
Y = 1,
Z = -5
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 1,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
CameraComponent = {
Primary = true,
ProjectionType = .InfinitePerspective,
PerspectiveFovY = 1.308997,
PerspectiveNearPlane = 0.1,
PerspectiveFarPlane = 10000,
OrthographicHeight = 10,
OrthographicNearPlane = 0,
OrthographicFarPlane = 10,
AspectRatio = 2.969697,
FixedAspectRatio = false
}
},
{
Id = 15710354273720487680,
NameComponent = {
Name = "Circle"
},
SpriterRendererComponent = {
Color = {
R = 1,
G = 1,
B = 1,
A = 1
},
IsCircle = true,
Sprite = "\\Textures\\rocket.png",
UvTransform = {
X = 0,
Y = 0,
Z = 1,
W = 1
}
},
TransformComponent = {
Position = {
X = -0.121203,
Y = 0.66016,
Z = 0
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 1,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
Rigidbody2D = {
BodyType = .Dynamic,
FixedRotation = false
},
CircleCollider2D = {
Offset = {
X = 0,
Y = 0
},
Radius = 0.5,
Density = 1,
Friction = 0.5,
Restitution = 0,
RestitutionThreshold = 0.5
}
},
{
Id = 935640822766280888,
NameComponent = {
Name = "Light"
},
TransformComponent = {
Position = {
X = 3.883276,
Y = 0,
Z = -0.808795
},
Rotation = {
X = 0.497987,
Y = -0.103421,
Z = 0.15538,
W = 0.846859
},
Scale = {
X = 0.999998,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 1.090894,
Y = -0.340794,
Z = 0.160858
}
},
LightComponent = {
LightType = .Directional,
Illuminance = 12.9,
Color = {
R = 0.985467,
G = 1,
B = 0.569979
}
}
},
{
Id = 721949523193525565,
NameComponent = {
Name = "Sphere"
},
TransformComponent = {
Position = {
X = 0,
Y = 0,
Z = -1.523338
},
Rotation = {
X = 0,
Y = 0,
Z = 0,
W = 1
},
Scale = {
X = 1,
Y = 1,
Z = 1
},
EditorEulerRotation = {
X = 0,
Y = 0,
Z = 0
}
},
MeshComponent = {
Mesh = "\\Models\\sphere.glb"
},
MeshRendererComponent = {
Material = "\\Textures\\TestMaterial.mat"
}
}
]
}
+52
View File
@@ -0,0 +1,52 @@
//=================================================================================================
//
// Baking Lab
// by MJP and David Neubelt
// http://mynameismjp.wordpress.com/
//
// All code licensed under the MIT license
//
//=================================================================================================
// The code in this file was originally written by Stephen Hill (@self_shadow), who deserves all
// credit for coming up with this fit and implementing it. Buy him a beer next time you see him. :)
// Source: https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl
// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
static const float3x3 ACESInputMat =
{
{0.59719, 0.35458, 0.04823},
{0.07600, 0.90834, 0.01566},
{0.02840, 0.13383, 0.83777}
};
// ODT_SAT => XYZ => D60_2_D65 => sRGB
static const float3x3 ACESOutputMat =
{
{ 1.60475, -0.53108, -0.07367},
{-0.10208, 1.10813, -0.00605},
{-0.00327, -0.07276, 1.07602}
};
float3 RRTAndODTFit(float3 v)
{
float3 a = v * (v + 0.0245786f) - 0.000090537f;
float3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
return a / b;
}
float3 ACESFitted(float3 color)
{
color = mul(ACESInputMat, color);
// Apply RRT and ODT
color = RRTAndODTFit(color);
color = mul(ACESOutputMat, color);
// Clamp to [0, 1]
color = saturate(color);
return color;
}
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -0,0 +1,16 @@
cbuffer Constants : register(b0)
{
uint ClearValue;
}
float4 VS(float2 input : POSITION) : SV_Position
{
return float4(input, 0.0f, 1.0f);
}
uint PS(float4 input : SV_Position) : SV_Target0
{
return ClearValue;
}
#pragma Effect[VS = VS; PS = PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -0,0 +1,33 @@
Texture2D Texture : register(t0);
SamplerState TextureSampler : register(s0);
struct VS_IN
{
float2 Position : POSITION;
float2 TexCoord : TEXCOORD0;
};
struct PS_IN
{
float4 Position : SV_POSITION;
float2 TexCoord : TEXCOORD;
};
PS_IN VS(VS_IN input)
{
PS_IN output;
output.Position = float4(input.Position, 0, 1);
output.TexCoord = input.TexCoord;
return output;
}
float4 PS(PS_IN input) : SV_TARGET
{
float4 color = Texture.Sample(TextureSampler, input.TexCoord);
return float4(pow(color.rgb, 1.0f / 2.2f), color.a);
}
#pragma Effect[VS = VS; PS = PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -0,0 +1,28 @@
#define PI 3.14159265358979323846f
/**
* Calculates the diffuse lighting of a lambertian surface
* @param diffuseColor (rho/ pi) * C_diffuse
* @param illuminanceColor The product of the "brightness" and the light color.
* @param n_dot_l The dot product of the surface normal and the light direction.
*/
float3 CalculateDiffuseReflection(float3 diffuseColor, float3 illuminanceColor, float3 n_dot_l)
{
float3 directColor = illuminanceColor * saturate(n_dot_l);
return (directColor * diffuseColor);
}
/**
* Calculates the blinn-phong-specular reflection
* @param n The normalized surface normal
* @param h The normalized half way vector (nrm(l + v))
* @param alpha The reflections alpha-value
* @param illuminanceColor The product of the "brightness" and the light color.
* @param n_dot_l The dot product of the surface normal and the light direction.
*/
float3 CalculateSpecularReflection(float3 n, float3 h, float alpha, float3 illuminanceColor, float n_dot_l)
{
float highlight = pow(saturate(dot(n, h)), alpha) * float(n_dot_l > 0.0);
return (illuminanceColor * highlight); // Todo: * SpecularColor
}
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
+78
View File
@@ -0,0 +1,78 @@
/*
* This File contains Function for PBR.
*/
#ifndef __PBR_HLSL__
#define __PBR_HLSL__
#include "ShaderHelpers.hlsl"
// #define PBR_IBL
/**
* Normal Distribution Function. (Trowbridge-Reits GGX)
* Calculates the relative surface area of microfacets exactly aligned to the halfway vector.
* @param normal The surface normal.
* @param halfway The halfway vector between the surface normal and the view direction.
* @param roughness Roughness value.
* @returns The relative surface area of microfacets exactly aligned to the halfway vector.
*/
float NormalDistributionGGX(float3 normal, float3 halfway, float roughness)
{
// Square roughness because it looks better
float a = roughness * roughness;
float aa = a * a;
float n_dot_h = max(dot(normal, halfway), 0.0f);
float denom = (n_dot_h * n_dot_h) * (aa - 1.0f) + 1.0f;
denom = PI * denom * denom;
return aa / denom;
}
/**
* Geometry Function calculating the overshadowing of microfacets based on roughness. (Schlick-Beckmann GGX).
* @param dot-product of normal vector and vector from surface to camera.
* @param k Roughness value.
*/
float GeometrySchlickGGX(float n_dot_v, float k)
{
return n_dot_v / (n_dot_v * (1 - k) + k);
}
/**
* Geometry Function calculating the overshadowing of microfacets based on roughness. (Smith)
* @param normal The surface normal.
* @param viewDir Vector from surface to viewer.
* @param lightDir Vector from surface to light source.
* @param roughness Roughness value.
*/
float GeometrySmith(float3 normal, float3 viewDir, float3 lightDir, float roughness)
{
#ifdef PBR_IBL
// IBL
float k = roughness * roughness / 2;
#else
// Direct lighting
float k = (roughness + 1.0f);
k = (k * k) / 8;
#endif
const float n_dot_v = max(dot(normal, viewDir), 0.0f);
float n_dot_l = max(dot(normal, lightDir), 0.0f);
return GeometrySchlickGGX(n_dot_v, k) * GeometrySchlickGGX(n_dot_l, k);
}
/**
* Calculates the fresnel value.
* @param n_dot_v Dot product of the normal and view direction
* @param F0 base reflectivity.
*/
float3 FresnelSchlick(float n_dot_v, float3 F0)
{
return F0 + (1.0 - F0) * pow(clamp(1.0 - n_dot_v, 0.0, 1.0), 5.0);
}
#endif // __PBR_HLSL__
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -0,0 +1,90 @@
#ifndef __SHADER_HELPERS_HLSL__
#define __SHADER_HELPERS_HLSL__
#define PI 3.14159265358979323846f
/*
* Calculates the weighted sum of two normal vectors.
* nrm1: The first normal vector
* nrm2: The second normal vector
* a: The weight factor for nrm1
* b: The weight factor for nrm2
*/
float3 BlendNormals(float3 nrm1, float3 nrm2, float a, float b)
{
return normalize(float3(a * nrm1.x / nrm1.z + b * nrm2.x / nrm2.z,
a * nrm1.y / nrm1.z + b * nrm2.y / nrm2.z,
1.0f));
}
/*
* Scales a normal vector by a factor where 0 results in the vector (0, 0, 1)
* nrm: The normal vector
* a: The scaling factor
*/
float3 ScaleNormal(float3 nrm1, float a)
{
return normalize(float3(a * nrm1.x / nrm1.z,
a * nrm1.y / nrm1.z,
1.0f));
}
/*
* Scales a normal vector by a factor where 0 results in the vector (0, 0, 1)
* nrm: The normal vector
* a: The scaling factor
*/
float3 ScaleNormal(float3 nrm1, float2 a)
{
return normalize(float3(a.x * nrm1.x / nrm1.z,
a.y * nrm1.y / nrm1.z,
1.0f));
}
/*
* Reconstructs the z-component of a normalized normal vector from a two-component value
* cnrm: The x- and y-components of a normalized normal vector
*/
float3 DecompressNormal(float2 cnrm)
{
return float3(cnrm, sqrt(1.0 - cnrm.x * cnrm.x - cnrm.y * cnrm.y));
}
/*
* Reconstructs the tangent space from a normal and a tangent
* normal: The surface normal
* tangent: The surface tangent
* sigma: Defines the handedness of the tangent space matrix. 1.0 if it is right handend. -1.0 if it is left handed
*/
float3x3 ConstructTangentSpace(float3 normal, float3 tangent, float3 sigma)
{
float3 n = normalize(normal);
float3 t = normalize(tangent - n * dot(tangent, n));
float3 b = cross(n, t) * sigma;
return float3x3(t, b, n);
}
/**
* Calculates the luminance of an rgb-value.
* @param rgb The rgb color.
* @return The luminance of the given rgb color.
*/
float ColorToLuminance(float3 rgb)
{
return rgb.r * 0.212639 + rgb.g * 0.715169 + rgb.b * 0.072192;
}
/**
* Extrancts the handedness of the bitangent that is encoded in the z-component of the tangent.
* @param tangentz The z-component of the tangent with the handedness encoded.
* @return The handedness of the bitangent (bitangent = handedness * tangent x normal)
*/
float GetBitangentHandedness(float tangentz)
{
// handedness is in least significant bit of tangent.z
uint z = asuint(tangentz);
return (z & 1) > 0 ? 1.0 : -1.0;
}
#endif // __SHADER_HELPERS_HLSL__
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -0,0 +1,39 @@
#include "ACES.hlsl"
Texture2D CameraTarget : register(t0);
SamplerState CameraTargetSampler : register(s0);
struct VS_IN
{
float2 Position : POSITION;
float2 TexCoord : TEXCOORD0;
};
struct PS_IN
{
float4 Position : SV_POSITION;
float2 TexCoord : TEXCOORD;
};
PS_IN VS(VS_IN input)
{
PS_IN output;
output.Position = float4(input.Position, 0, 1);
output.TexCoord = input.TexCoord;
return output;
}
float4 PS(PS_IN input) : SV_TARGET
{
float4 rawColor = CameraTarget.Sample(CameraTargetSampler, input.TexCoord);
// float3 color = rawColor.rgb / (rawColor.rgb + 1.0f);
float3 color = ACESFitted(rawColor.rgb);
return float4(color, 1);
}
#pragma Effect[VS = VS; PS = PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -0,0 +1,88 @@
#define EDITOR
Texture2D Texture : register(t0);
SamplerState Sampler : register(s0);
cbuffer Constants : register(b0)
{
float4x4 ViewProjection;
};
struct VS_Input
{
float2 Position : POSITION;
float2 Texcoord : TEXCOORD0;
float4x4 Transform : TRANSFORM;
float4 Color : COLOR;
float4 UVTransform : TEXCOORD1;
float InnerRadius : TEXCOORD2;
#ifdef EDITOR
uint EntityId : ENTITYID;
#endif
};
struct PS_Input
{
float4 Position : SV_Position;
float2 RawPos : TEXCOORD0;
float2 Texcoord : TEXCOORD1;
float4 Color : COLOR;
#ifdef EDITOR
nointerpolation uint EntityId : ENTITYID;
#endif
float InnerRadius : TEXCOORD2;
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(ViewProjection, mul(input.Transform, float4(input.Position, 0.0f, 1.0f)));
output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
output.RawPos = input.Position;
output.Color = input.Color;
output.InnerRadius = input.InnerRadius;
#ifdef EDITOR
output.EntityId = input.EntityId;
#endif
return output;
}
struct PS_Output
{
float4 Color : SV_Target0;
#ifdef EDITOR
uint EntityId : SV_TARGET1;
#endif
};
PS_Output PS(PS_Input input)
{
PS_Output output;
float2 uv = input.RawPos * 2;
float distance = 1.0f - length(uv);
// Smoothing edges based on derivative (not sure if RawPos is the best value for this)
float f = fwidth(input.RawPos.x) + fwidth(input.RawPos.y);
float amount = smoothstep(0.0f, f, distance);
amount *= smoothstep(input.InnerRadius + f, input.InnerRadius, distance);
// Discard invisible pixels
clip(amount - 0.5f);
output.Color = Texture.Sample(Sampler, input.Texcoord) * input.Color;
output.Color.a *= amount;
#ifdef EDITOR
output.EntityId = input.EntityId;
#endif
return output;
}
#pragma Effect[VS=VS; PS=PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -28,4 +28,4 @@ float4 PS(PS_Input input) : SV_Target0
return Color; return Color;
} }
#effect[VS=VS, PS=PS] #pragma Effect[VS=VS; PS=PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -0,0 +1,93 @@
Texture2D<float3> Texture : register(t0);
SamplerState Sampler : register(s0);
cbuffer Constants
{
float4x4 ViewProjection;
float2 UnitRange;
float screenPixelRange = 2;
}
struct VS_Input
{
float2 Position : POSITION;
float2 Texcoord : TEXCOORD0;
float4x4 Tranform : TRANSFORM;
float4 Color : COLOR;
float4 UVTransform : TEXCOORD1;
};
struct PS_Input
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
float4 Color : COLOR;
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(ViewProjection, mul(input.Tranform, float4(input.Position, 0.0f, 1.0f)));
output.TexCoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
output.Color = input.Color;
return output;
}
/*
in vec2 texCoord;
out vec4 color;
uniform sampler2D msdf;
uniform vec4 bgColor;
uniform vec4 fgColor;
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
void main() {
vec3 msd = texture(msdf, texCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPxRange()*(sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
color = mix(bgColor, fgColor, opacity);
}
*/
float median(float r, float g, float b)
{
return max(min(r, g), min(max(r, g), b));
}
float ScreenPxRange(float2 texcoord)
{
float2 screenTexSize = 1.0f / fwidth(texcoord);
return max(0.5f * dot(UnitRange, screenTexSize), 1.0f);
}
float4 PS(PS_Input input) : SV_Target0
{
float3 msd = Texture.Sample(Sampler, input.TexCoord);
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = ScreenPxRange(input.TexCoord) * (sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
return float4(input.Color.rgb, opacity * input.Color.a);
}
/*
// 2D
float4 PS(PS_Input input) : SV_Target0
{
float3 msd = Texture.Sample(Sampler, input.TexCoord).rgb;
float sd = median(msd.r, msd.g, msd.b);
float screenPxDistance = screenPixelRange*(sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
return float4(input.Color.rgb, opacity * input.Color.a);
}
*/
#pragma Effect[VS=VS; PS=PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
+159
View File
@@ -0,0 +1,159 @@
#define OutputEntityId
#include "ShaderHelpers.hlsl"
Texture2D AlbedoTexture : register(t0);
SamplerState AlbedoSampler : register(s0);
Texture2D<float3> NormalTexture : register(t1);
SamplerState NormalSampler : register(s1);
Texture2D<float> MetallicTexture : register(t2);
SamplerState MetallicSampler : register(s2);
Texture2D<float> RoughnessTexture : register(t3);
SamplerState RoughnessSampler : register(s3);
// Texture2D<float> AmbientTexture : register(t4);
// SamplerState AmbientSampler : register(s4);
#pragma EngineBuffer[ Name = "SceneConstants"; Binding = "Scene" ]
cbuffer SceneConstants : register(b0)
{
float4x4 ViewProjection;
}
#pragma EngineBuffer[ Name = "ObjectConstants"; Binding = "Object" ]
cbuffer ObjectConstants : register(b1)
{
float4x4 Transform;
/**
* \brief Inverted and transposed transform matrix.
* \remarks This matrix is used in order to correctly transform normal vectors.
*/
float4x3 Transform_InvT;
#ifdef OutputEntityId
uint EntityId;
#endif
}
cbuffer MaterialConstants : register(b2)
{
#pragma EditorVariable[ Name = "AlbedoColor"; Preview = "Albedo Color"; Type="Color" ]
float4 AlbedoColor = float4(1.0, 1.0, 1.0, 1.0);
#pragma EditorVariable[ Name = "NormalScaling"; Preview = "Normal Scaling" ]
float2 NormalScaling = float2(1.0, 1.0);
#pragma EditorVariable[ Name = "MetallicFactor"; Preview = "Metallic Factor"; Min = 0.0f; Max = 1.0f ]
float MetallicFactor = 1.0;
#pragma EditorVariable[ Name = "RoughnessFactor"; Preview = "Rougness Factor"; Min = 0.0f; Max = 1.0f ]
float RoughnessFactor = 1.0;
// float AmbientFactor = 1.0;
}
struct VS_IN
{
float3 Position : POSITION;
float3 Normal : NORMAL;
// Todo: Tangent.w... handedness
float3 Tangent : TANGENT;
float2 TexCoord : TEXCOORD;
};
struct PS_IN
{
float4 Position : SV_POSITION;
float3 WorldPosition : WORLDPOSITION;
float3 Normal : NORMAL;
float3 Tangent : TANGENT;
float2 TexCoord : TEXCOORD;
//nointerpolation float Handedness : HANDEDNESS;
#ifdef OutputEntityId
nointerpolation uint EntityId : ENTITYID;
#endif
};
PS_IN VS(VS_IN input)
{
PS_IN output;
float4 worldPosition = mul(Transform, float4(input.Position, 1));
output.Position = mul(ViewProjection, worldPosition);
output.WorldPosition = worldPosition.xyz / worldPosition.w;
output.Normal = mul(Transform_InvT, input.Normal);
output.Tangent = mul((float3x3)Transform, input.Tangent);
// TODO: output.Handedness = input.Tangent.w
output.TexCoord = input.TexCoord;
output.EntityId = EntityId;
return output;
}
struct PS_OUT
{
float4 Albedo : SV_TARGET0;
// RG: TextureNormal.XY BA: GeoNrm.XY
float4 Normal : SV_TARGET1;
// R: GeoNrm.Z GBA: GeoTan.XYZ
float4 Tangent : SV_TARGET2;
float4 Position : SV_TARGET3;
// R: Metallicity G: Roughness B: Ambient
float4 Material : SV_TARGET4;
#ifdef OutputEntityId
uint EntityId : SV_TARGET5;
#endif
};
PS_OUT PS(PS_IN input)
{
// Build tangent space
float3 normal = normalize(input.Normal);
float3 tangent = normalize(input.Tangent - dot(input.Tangent, normal) * input.Normal);
// TODO: float3 bitangent = input.Handedness * cross(normal, tangent);
float3 bitangent = -cross(normal, tangent);
//float3x3 tangentTransform = float3x3(tangent, bitangent, normal);
//tangentTransform = transpose(tangentTransform);
float4 texAlbedo = AlbedoTexture.Sample(AlbedoSampler, input.TexCoord);
float3 texNormal = NormalTexture.Sample(NormalSampler, input.TexCoord);
texNormal.xy = texNormal.xy * 2.0 - 1.0;
float texMetallic = MetallicTexture.Sample(MetallicSampler, input.TexCoord);
float texRoughness = RoughnessTexture.Sample(RoughnessSampler, input.TexCoord);
//float3 objectNormal = mul(tangentTransform, texNormal);
//float3 worldNormal = mul(objectNormal, (float3x3)Transform);
float4 finalAlbedo = texAlbedo * AlbedoColor;
float3 finalNormal = ScaleNormal(texNormal, NormalScaling);
float finalMetallic = texMetallic * MetallicFactor;
float finalRoughness = texRoughness * RoughnessFactor;
/////////////TODO: REMOVEME
//worldNormal = max(worldNormal - 10000000, normal);
//texAlbedo = max(texAlbedo - 10000000, 1.0);
//texMetallic = max(texMetallic - 10000000, 0.0);
//texRoughness = max(texRoughness - 10000000, 0.1);
/////////////TODO: END_REMOVEME
PS_OUT output;
output.Albedo = finalAlbedo;
//output.Normal = float4(objectNormal, 1.0);
output.Normal = float4(finalNormal.xy, normal.xy);
output.Tangent = float4(normal.z, tangent.xyz);
output.Position = float4(input.WorldPosition, 1.0);
output.Material = float4(finalMetallic, finalRoughness, 1.0, 0);
#ifdef OutputEntityId
output.EntityId = input.EntityId;
#endif
return output;
}
#pragma Effect[VS = VS; PS = PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,37 +0,0 @@
Texture2D Texture : register(t0);
SamplerState Sampler : register(s0);
struct VS_Input
{
float2 Position : POSITION;
float4x4 Tranform : TRANSFORM;
float4 Color : COLOR;
float4 UVTransform : TEXCOORD;
};
struct PS_Input
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
float4 Color : COLOR;
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(float4(input.Position, 0.0f, 1.0f), input.Tranform);
output.TexCoord = input.UVTransform.xy + input.UVTransform.zw * input.Position;
output.Color = input.Color;
return output;
}
float4 PS(PS_Input input) : SV_Target0
{
float4 color = input.Color * Texture.Sample(Sampler, input.TexCoord);
return color;
}
#effect[VS=VS, PS=PS]
@@ -0,0 +1,126 @@
#include "ShaderHelpers.hlsl"
#include "PBR.hlsl"
#define Render 0
#define Inspect_NormalDistribution 1
#define Inspect_GeometryFunction 2
#define Inspect_Fresnel 3
#define Inspect_Normal 4
#define OUTPUT Render
SamplerState Sampler : register(s0);
Texture2D GBuffer_Albedo : register(t0);
Texture2D GBuffer_Normal : register(t1);
Texture2D GBuffer_Tangent : register(t2);
Texture2D GBuffer_Position : register(t3);
Texture2D GBuffer_Material : register(t4);
cbuffer Constants
{
float3 CameraPos;
float2 Scaling;
}
cbuffer LightConstants
{
float3 LightColor;
float Illuminance;
float3 LightDir;
}
struct VS_IN
{
float2 Position : POSITION;
float2 TexCoord : TEXCOORD0;
};
struct PS_IN
{
float4 Position : SV_POSITION;
float2 TexCoord : TEXCOORD;
};
PS_IN VS(VS_IN input)
{
PS_IN output;
output.Position = float4(input.Position, 0, 1);
output.TexCoord = input.TexCoord * Scaling;
return output;
}
float4 PS(PS_IN input) : SV_TARGET
{
// Load Data from GBuffer
float4 rawAlbedo = GBuffer_Albedo.Sample(Sampler, input.TexCoord);
float4 rawNormal = GBuffer_Normal.Sample(Sampler, input.TexCoord);
float4 rawTangent = GBuffer_Tangent.Sample(Sampler, input.TexCoord);
float4 rawPosition = GBuffer_Position.Sample(Sampler, input.TexCoord);
float4 rawMaterial = GBuffer_Material.Sample(Sampler, input.TexCoord);
// Extract data from GBuffer
float3 albedo = rawAlbedo.rgb;
//float3 surfaceNormal = normalize(rawNormal.xyz);
float3 worldPosition = rawPosition.xyz;
float metallic = rawMaterial.r;
float roughness = rawMaterial.g;
float3 textureNormal = DecompressNormal(rawNormal.rg);
float3 rawGeoNrm = float3(rawNormal.ba, rawTangent.r);
float3 rawGeoTan = rawTangent.gba;
// Reconstruct normal space
float3 normal = normalize(rawGeoNrm);
float3 tangent = normalize(rawGeoTan - dot(rawGeoTan, normal) * normal);
float3 bitangent = -cross(normal, tangent);
float3x3 tangentTransform = float3x3(tangent, bitangent, normal);
float3 surfaceNormal = mul(textureNormal, tangentTransform);
float3 lightDir = normalize(LightDir);
float3 viewDir = normalize(CameraPos - worldPosition.xyz);
float3 halfway = normalize(lightDir + viewDir);
float n_dot_v = max(dot(surfaceNormal, viewDir), 0.0f);
float n_dot_h = max(dot(surfaceNormal, halfway), 0.0f);
float n_dot_l = max(dot(surfaceNormal, lightDir), 0.0f);
float nrmDist = NormalDistributionGGX(surfaceNormal, halfway, roughness);
float geo = GeometrySmith(surfaceNormal, viewDir, lightDir, roughness);
float3 F0 = 0.04f;
F0 = lerp(F0, albedo, metallic);
float3 fresnel = FresnelSchlick(n_dot_h, F0);
float3 ks = fresnel;
float3 kd = 1.0f - ks;
// Metals have no diffuse light
kd *= 1.0f - metallic;
float3 diffuse = albedo / PI;
float3 specular = (nrmDist * fresnel * geo) / max(4 * n_dot_v * n_dot_l, 0.0001f);
float3 luminanceColor = LightColor * Illuminance;
float3 final = (kd * diffuse + specular) * luminanceColor * n_dot_l;
#if OUTPUT == Inspect_NormalDistribution
final = max(final - 10000000, nrmDist.xxx);
#elif OUTPUT == Inspect_GeometryFunction
final = max(final - 10000000, geo.xxx);
#elif OUTPUT == Inspect_Fresnel
final = max(final - 10000000, fresnel);
#elif OUTPUT == Inspect_Normal
final = max(final - 10000000, surfaceNormal / 2 + 0.5f);
#endif
return float4(final, 1);
}
#pragma Effect[VS = VS; PS = PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -0,0 +1,74 @@
#define EDITOR
Texture2D Texture : register(t0);
SamplerState Sampler : register(s0);
cbuffer Constants : register(b0)
{
float4x4 ViewProjection;
};
struct VS_Input
{
float2 Position : POSITION;
float2 Texcoord : TEXCOORD0;
float4x4 Transform : TRANSFORM;
float4 Color : COLOR;
float4 UVTransform : TEXCOORD1;
#ifdef EDITOR
uint EntityId : ENTITYID;
#endif
};
struct PS_Input
{
float4 Position : SV_Position;
float2 Texcoord : TEXCOORD;
float4 Color : COLOR;
#ifdef EDITOR
nointerpolation uint EntityId : ENTITYID;
#endif
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(ViewProjection, mul(input.Transform, float4(input.Position, 0.0f, 1.0f)));
output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
output.Color = input.Color;
// Premultiply Alpha
output.Color.rgb *= output.Color.a;
#ifdef EDITOR
output.EntityId = input.EntityId;
#endif
return output;
}
struct PS_Output
{
float4 Color : SV_Target0;
#ifdef EDITOR
uint EntityId : SV_TARGET1;
#endif
};
PS_Output PS(PS_Input input)
{
PS_Output output;
output.Color = Texture.Sample(Sampler, input.Texcoord) * input.Color;
clip(output.Color.a - 0.001f);
#ifdef EDITOR
output.EntityId = input.EntityId;
#endif
return output;
}
#pragma Effect[VS = VS; PS = PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,84 +0,0 @@
cbuffer SceneConstants
{
float4x4 ViewProjection = float4x4(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
}
cbuffer ObjectConstants
{
float4x4 Transform;
}
cbuffer Constants
{
float4 BaseColor;
float3 LightDir;
}
cbuffer SkinningMatrices
{
matrix SkinningMatrices[255];
float3x3 InvTransSkinningMatrices[255];
}
struct VS_IN
{
float3 Position : POSITION;
float3 Normal : NORMAL;
uint4 JointIndices : JOINTS_0;
float4 JointWeights : WEIGHTS_0;
};
struct PS_IN
{
float4 Position : SV_POSITION;
float3 Normal : NORMAL;
};
PS_IN VS(VS_IN input)
{
PS_IN output;
// Unanimated position in modelspace
float4 positionRaw = float4(input.Position, 1);
// Calculate animated position in modelspace
float4 position =
mul(SkinningMatrices[input.JointIndices.x], positionRaw) * input.JointWeights.x +
mul(SkinningMatrices[input.JointIndices.y], positionRaw) * input.JointWeights.y +
mul(SkinningMatrices[input.JointIndices.z], positionRaw) * input.JointWeights.z +
mul(SkinningMatrices[input.JointIndices.w], positionRaw) * input.JointWeights.w;
float3 normal =
mul(InvTransSkinningMatrices[input.JointIndices.x], input.Normal) * input.JointWeights.x +
mul(InvTransSkinningMatrices[input.JointIndices.y], input.Normal) * input.JointWeights.y +
mul(InvTransSkinningMatrices[input.JointIndices.z], input.Normal) * input.JointWeights.z +
mul(InvTransSkinningMatrices[input.JointIndices.w], input.Normal) * input.JointWeights.w;
//position = saturate(position - 1000) + positionRaw;
//float4 position = mul(SkinningMatrices[input.JointIndices.x], positionRaw);
float4 worldPosition = mul(Transform, position);
output.Position = mul(ViewProjection, worldPosition);
output.Normal = mul((float3x3)Transform, normal);
return output;
}
float4 PS(PS_IN input) : SV_TARGET
{
input.Normal = normalize(input.Normal);
float shading = dot(LightDir, input.Normal);
//float shading = dot(LightDir, LightDir) + 1;
shading = clamp(shading, 0.0f, 1.0f);
return BaseColor * shading;
}
#effect[VS=VS,PS=PS]
@@ -0,0 +1,52 @@
Texture2D Texture : register(t0);
SamplerState Sampler : register(s0);
cbuffer Constants
{
float4x4 ViewProjection;
float ColorOffset = 0.5f;
float AlphaOffset = 0.0f;
float ColorScale = 0.5f;
float AlphaScale = 1.0f;
float2 TextureSizeInPixels;
}
struct VS_Input
{
float2 Position : POSITION;
float2 Texcoord : TEXCOORD0;
float4x4 Transform : TRANSFORM;
float4 Color : COLOR;
float4 UVTransform : TEXCOORD1;
};
struct PS_Input
{
float4 Position : SV_Position;
float2 Texcoord : TEXCOORD0;
float4 Color : COLOR;
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(ViewProjection, mul(input.Transform, float4(input.Position, 0.0f, 1.0f)));
output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
output.Color = input.Color;
return output;
}
float4 PS(PS_Input input) : SV_Target0
{
float4 color = Texture.Sample(Sampler, input.Texcoord);
float4 final = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale);
return final;
}
#pragma Effect[VS=VS; PS=PS]
@@ -0,0 +1,4 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

@@ -0,0 +1,23 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_samplerStateDescription = {
MinFilter = .Linear,
MagFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
Binary file not shown.
@@ -0,0 +1,23 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_samplerStateDescription = {
MinFilter = .Linear,
MagFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

@@ -0,0 +1,23 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_samplerStateDescription = {
MinFilter = .Linear,
MagFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

@@ -0,0 +1,23 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_generateMipMaps = true,
_isSrgb = true,
_samplerStateDescription = {
MinFilter = .Anisotropic,
MagFilter = .Anisotropic,
MipFilter = .Anisotropic,
ComparisonFunction = .Never,
AddressModeU = .Wrap,
AddressModeV = .Border,
AddressModeW = .Clamp,
MipMaxLOD = 160,
MaxAnisotropy = 16,
BorderColor = {
R = 0.756863,
G = 0.2,
A = 0.956863
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

@@ -0,0 +1,23 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_samplerStateDescription = {
MinFilter = .Linear,
MagFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 MiB

@@ -0,0 +1,23 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_samplerStateDescription = {
MinFilter = .Linear,
MagFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

@@ -0,0 +1,24 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_isSrgb = true,
_samplerStateDescription = {
MinFilter = .Linear,
MagFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
@@ -0,0 +1,28 @@
{
Effect = "content/Shaders/myEffect.hlsl",
Textures = [
"AlbedoTexture": "Textures/TestMat/rustediron2_albedo.png"
],
Variables = [
"AlbedoColor": .ColorRGBA{
Value = {
R = 1,
G = 1,
B = 1,
A = 1
}
},
"NormalScaling": .Float2{
Value = {
X = 1,
Y = 1
}
},
"MetallicFactor": .Float{
Value = 1
},
"RoughnessFactor": .Float{
Value = 1
}
]
}
@@ -0,0 +1,4 @@
{
AssetLoader = "MaterialAssetLoader",
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

@@ -0,0 +1,23 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_samplerStateDescription = {
MinFilter = .Linear,
MagFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
@@ -0,0 +1,23 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_isSrgb = true,
_samplerStateDescription = {
MinFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

@@ -0,0 +1,22 @@
{
AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_isSrgb = true,
_samplerStateDescription = {
MinFilter = .Linear,
ComparisonFunction = .Never,
AddressModeU = .Clamp,
AddressModeV = .Clamp,
AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440,
MipMaxLOD = 340282346638528859811704183484516925440,
MaxAnisotropy = 1,
BorderColor = {
R = 1,
G = 1,
B = 1,
A = 1
}
}
}
}
+121
View File
@@ -0,0 +1,121 @@
using System;
using GlitchyEngine;
using System.IO;
using Bon;
using GlitchyEngine.Content;
namespace GlitchyEditor;
[BonTarget]
class AssetConfig
{
[BonIgnore]
public bool IgnoreFile = false;
[BonInclude]
public String AssetLoader ~ delete _;
[BonInclude]
public AssetLoaderConfig Config ~ delete _;
}
class AssetFile
{
private EditorContentManager _contentManager;
private String _path;
private String _identifier;
private String _assetConfigPath;
private AssetConfig _assetConfig ~ delete _;
private bool _isDirectory;
private Object _loadedAsset;
public bool IsDirectory => _isDirectory;
public StringView FilePath => _path;
public StringView Identifier => _identifier;
public const String ConfigFileExtension = ".ass";
public AssetConfig AssetConfig => _assetConfig;
public Object LoadedAsset => _loadedAsset;
[AllowAppend]
public this(EditorContentManager contentManager, StringView identifier, StringView path, bool isDirectory)
{
String identifierBuffer = append String(identifier);
String pathBuffer = append String(path);
String configPathBuffer = append String(path.Length + ConfigFileExtension.Length);
_identifier = identifierBuffer;
_path = pathBuffer;
configPathBuffer..Append(path).Append(ConfigFileExtension);
_assetConfigPath = configPathBuffer;
_contentManager = contentManager;
_isDirectory = isDirectory;
Log.EngineLogger.AssertDebug(File.Exists(_path), "File doesn't exist.");
FindAssetConfig();
}
// Loads the asset config (.ass) file or creates it.
private void FindAssetConfig()
{
if (File.Exists(_assetConfigPath))
{
LoadAssetConfig();
}
else
{
CreateDefaultAssetLoader();
}
}
private void CreateDefaultAssetLoader()
{
String fileExtension = Path.GetExtension(_path, .. scope .());
_assetConfig = new AssetConfig();
var assetLoader = _contentManager.GetDefaultAssetLoader(fileExtension);
// We don't have a loader -> we don't need a config
if (assetLoader == null)
return;
_assetConfig.AssetLoader = new String();
assetLoader.GetType().GetName(_assetConfig.AssetLoader);
_assetConfig.Config = assetLoader?.GetDefaultConfig();
_assetConfig.Config?.[Friend]_changed = true;
SaveAssetConfig();
}
private void LoadAssetConfig()
{
if (Bon.DeserializeFromFile(ref _assetConfig, _assetConfigPath) case .Err)
{
Log.EngineLogger.Error($"Failed to load asset config {_assetConfigPath}");
// TODO: Handle failure of asset config loading
Runtime.NotImplemented();
}
}
public void SaveAssetConfig()
{
gBonEnv.serializeFlags |= .Verbose;
Bon.SerializeIntoFile(_assetConfig, _assetConfigPath);
_assetConfig.Config.[Friend]_changed = false;
}
}
+343
View File
@@ -0,0 +1,343 @@
using GlitchyEngine;
using GlitchyEngine.Collections;
using GlitchyEngine.Renderer;
using System;
using System.Collections;
using System.IO;
using System.Linq;
namespace GlitchyEditor.Assets;
public class AssetNode
{
public String Name ~ delete _;
public String Path ~ delete _;
public bool IsDirectory;
public AssetFile AssetFile ~ delete _;
public List<SubAsset> SubAssets ~ {
SubAssets?.ClearAndDeleteItems();
delete SubAssets;
}
public Texture2D PreviewImage ~ _?.ReleaseRef();
}
public class SubAsset
{
public AssetNode Asset;
public String Name ~ delete _;
//public String AssetInternalPath ~ delete _;
public Texture2D PreviewImage ~ _?.ReleaseRef();
}
class AssetHierarchy
{
FileSystemWatcher fsw ~ {
_.StopRaisingEvents();
delete _;
};
bool _fileSystemDirty = false;
internal TreeNode<AssetNode> _assetHierarchy = null ~ DeleteTreeAndChildren!(_);
private append Dictionary<StringView, TreeNode<AssetNode>> _pathToAssetNode = .();
private append String _contentDirectory = .();
private EditorContentManager _contentManager;
public StringView ContentDirectory
{
get => _contentDirectory;
private set
{
_contentDirectory.Clear();
_contentDirectory.Append(value);
Path.Fixup(_contentDirectory);
}
}
public this(EditorContentManager contentManager)
{
_contentManager = contentManager;
}
public void SetContentDirectory(StringView contentDirectory)
{
ContentDirectory = contentDirectory;
_fileSystemDirty = true;
SetupFileSystemWatcher();
Update();
}
/// Initializes the FSW for the current ContentDirectory and registers the events.
private void SetupFileSystemWatcher()
{
delete fsw;
fsw = new FileSystemWatcher(_contentDirectory);
fsw.IncludeSubdirectories = true;
fsw.OnChanged.Add(new (filename) => {
// Note: Gets fired for a directory if a file inside it is created/removed
Log.EngineLogger.Trace($"File content changed (\"{filename}\")");
//_fileSystemDirty = true;
FileContentChanged(filename);
});
fsw.OnCreated.Add(new (filename) => {
Log.EngineLogger.Trace($"File created (\"{filename}\")");
_fileSystemDirty = true;
});
fsw.OnDeleted.Add(new (filename) => {
Log.EngineLogger.Trace($"File deleted (\"{filename}\")");
_fileSystemDirty = true;
});
fsw.OnRenamed.Add(new (oldName, newName) => {
Log.EngineLogger.Trace($"File renamed (From \"{oldName}\" to \"{newName}\")");
_fileSystemDirty = true;
/*String contentFilePath = scope String();
Path.InternalCombine(contentFilePath, ContentDirectory, oldName);
//_fileSystemDirty = true;
TreeNode<AssetNode> fileNode = GetNodeFromPath(contentFilePath);
fileNode->*/
});
fsw.StartRaisingEvents();
}
/// Gets the tree node for the given filePath or null, if the file/directory doesn't exist.
/// @param filePath the path for which to return the tree node.
/// @remarks Do not hold a reference to the TreeNode because it can become invalid when the file hierarchy changes.
public Result<TreeNode<AssetNode>> GetNodeFromPath(StringView filePath)
{
if (_pathToAssetNode.TryGetValue(filePath, let treeNode))
{
return treeNode;
}
return .Err;
}
public bool FileExists(StringView filePath)
{
return _pathToAssetNode.ContainsKey(filePath);
}
public void Update()
{
// TODO: do we really need to do this in the update loop?
if (_fileSystemDirty)
{
UpdateFiles();
}
}
/// Rebuilds the asset file hierarchy.
private void UpdateFiles()
{
Log.EngineLogger.Trace($"Updating asset hierarchy");
if (_assetHierarchy == null)
{
// TODO: move to init?
_assetHierarchy = new TreeNode<AssetNode>(new AssetNode());
_assetHierarchy->Path = new String(ContentDirectory);
_assetHierarchy->Name = new String("Content");
Log.EngineLogger.Trace($"Created directory node for: \"{_assetHierarchy->Path}\"");
_pathToAssetNode.Add(ContentDirectory, _assetHierarchy);
}
void HandleFile(AssetNode node)
{
String identifier = scope .(node.Path.Length);
Path.GetRelativePath(node.Path, _contentDirectory, identifier);
node.AssetFile = new AssetFile(_contentManager, identifier, node.Path, node.IsDirectory);
}
/// Determines the files that belong to the given directory and adds them to the tree.
void AddFilesOfDirectory(TreeNode<AssetNode> directory)
{
// Filter that accepts all files.
String filter = scope $"{directory->Path}/*";
// Buffer used to hold the path of the files iterated below.
String filepathBuffer = scope String(256);
// Buffer used to hold the file extension of the files iterated below.
String extensionBuffer = scope String(16);
for (var entry in Directory.Enumerate(filter, .Files))
{
entry.GetFilePath(filepathBuffer..Clear());
Path.GetExtension(filepathBuffer, .. extensionBuffer..Clear());
// Ignore meta files.
if (extensionBuffer.Equals(AssetFile.ConfigFileExtension, .OrdinalIgnoreCase))
continue;
TreeNode<AssetNode> treeNode = directory.Children.Where(scope (node) => node.Value.Path == filepathBuffer).FirstOrDefault();
if (treeNode == null)
{
AssetNode assetNode = new AssetNode();
assetNode.Name = new String();
Path.GetFileName(filepathBuffer, assetNode.Name);
filepathBuffer.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
assetNode.Path = new String(filepathBuffer);
assetNode.IsDirectory = false;
treeNode = directory.AddChild(assetNode);
_pathToAssetNode.Add(assetNode.Path, treeNode);
//GrabSubAssets(node);
HandleFile(treeNode.Value);
Log.EngineLogger.Trace($"Created file node for: \"{assetNode.Path}\"");
}
}
}
void RemoveOrphanedEntries(TreeNode<AssetNode> node)
{
/// Removes the node and its children from _pathToAssetNode
void RemoveSubtree(TreeNode<AssetNode> tree)
{
_pathToAssetNode.Remove(tree->Path);
for (var child in tree.Children)
{
RemoveSubtree(child);
}
}
for (TreeNode<AssetNode> child in node.Children)
{
if (!Directory.Exists(child->Path) && !File.Exists(child->Path))
{
Log.EngineLogger.Trace($"Removed orphaned node for: \"{child->Path}\"");
@child.Remove();
RemoveSubtree(child);
DeleteTreeAndChildren!(child);
}
}
}
/// Adds the given directory to the specified tree.
/// Recursively adds all Files and Subdirectories.
void AddDirectoryToTree(String path, TreeNode<AssetNode> parentNode)
{
path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// Try to find the node for the specified path in the given parent
TreeNode<AssetNode> treeNode = parentNode.Children.Where(scope (node) => node.Value.Path == path).FirstOrDefault();
// Create new Node for the Directory, if no TreeNode exists.
if (treeNode == null)
{
AssetNode assetNode = new AssetNode();
assetNode.Path = new String(path);
assetNode.Name = new String();
assetNode.IsDirectory = true;
Path.GetFileName(assetNode.Path, assetNode.Name);
treeNode = parentNode.AddChild(assetNode);
_pathToAssetNode.Add(assetNode.Path, treeNode);
Log.EngineLogger.Trace($"Created directory node for: \"{assetNode.Path}\"");
}
String directoryNameBuffer = scope String(256);
// Filter that finds all entries of a directory.
String filter = scope $"{path}/*";
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(directoryNameBuffer..Clear());
AddDirectoryToTree(directoryNameBuffer, treeNode);
}
AddFilesOfDirectory(treeNode);
RemoveOrphanedEntries(treeNode);
}
String filter = scope $"{ContentDirectory}/*";
String directoryNameBuffer = scope String(256);
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(directoryNameBuffer..Clear());
AddDirectoryToTree(directoryNameBuffer, _assetHierarchy);
}
RemoveOrphanedEntries(_assetHierarchy);
_fileSystemDirty = false;
}
private void FileContentChanged(StringView fileName)
{
var fileName;
// Config files aren't really tracked but changing them effectively changes the corresponding file
// so we fire the event for them.
if (fileName.EndsWith(AssetFile.ConfigFileExtension))
fileName.RemoveFromEnd(AssetFile.ConfigFileExtension.Length);
String fileNameWithContentRoot = scope .();
Path.InternalCombine(fileNameWithContentRoot, _contentDirectory, fileName);
var nodeResult = GetNodeFromPath(fileNameWithContentRoot);
TreeNode<AssetNode> node = null;
if (!(nodeResult case .Ok(out node)))
{
// This happens, when we create new files.
Log.EngineLogger.Trace($"Could not find node for file \"{fileNameWithContentRoot}\"");
return;
}
// Don't fire event for directories.
if (node->IsDirectory)
return;
OnFileContentChanged(node.Value);
// TODO: Handle file changes (reload asset, etc...)
}
public delegate void FileContentChangedFunc(AssetNode node);
public Event<FileContentChangedFunc> OnFileContentChanged ~ _.Dispose();
}
@@ -0,0 +1,15 @@
namespace GlitchyEditor.Assets;
abstract class AssetPropertiesEditor
{
private AssetFile _asset;
public AssetFile Asset => _asset;
public this(AssetFile asset)
{
_asset = asset;
}
public abstract void ShowEditor();
}
@@ -0,0 +1,101 @@
using Bon;
using GlitchyEngine.Content;
using System;
using System.Collections;
using System.IO;
using GlitchyEngine;
using GlitchyEngine.Renderer;
namespace GlitchyEditor.Assets;
class EffectAssetPropertiesEditor : AssetPropertiesEditor
{
public this(AssetFile asset) : base(asset)
{
}
public override void ShowEditor()
{
}
public static AssetPropertiesEditor Factory(AssetFile assetFile)
{
return new Self(assetFile);
}
}
[BonTarget, BonPolyRegister]
class EffectAssetLoaderConfig : AssetLoaderConfig
{
}
class EffectAssetLoader : IAssetLoader //, IReloadingAssetLoader
{
private static readonly List<StringView> _fileExtensions = new .(){".hlsl"} ~ delete _;
public static List<StringView> FileExtensions => _fileExtensions;
public AssetLoaderConfig GetDefaultConfig()
{
return new EffectAssetLoaderConfig();
}
public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
Effect effect = new Effect(file, assetIdentifier, contentManager);
return effect;
/*StreamReader reader = scope .(file);
String text = scope .();
reader.ReadToEnd(text);
MaterialFile materialFile = scope .();
var result = Bon.Deserialize<MaterialFile>(ref materialFile, text);
if (result case .Err)
{
Log.EngineLogger.Error("Failed to load material.");
return null;
// TODO: return error material
}
Effect fx = new Effect(materialFile.Effect);
Material material = new Material(fx);
for (let (slotName, textureIdentifier) in materialFile.Textures)
{
Texture texture = contentManager.LoadAsset(textureIdentifier) as Texture;
if (texture == null)
{
Log.EngineLogger.Error("Failed to load texture.");
// TODO: LoadAsset should return an error texture.
}
material.SetTexture(slotName, texture);
}
fx.ReleaseRef();
/*for (let (slotName, textureIdentifier) in materialFile.Variables)
{
if (texture == null)
{
Log.EngineLogger.Error("Failed to load texture.");
// TODO: LoadAsset should return an error texture.
}
material.SetVariable(slotName, );
}*/
return material; //ModelLoader.LoadMesh(file, subAsset.Value, 0);*/
}
}
+10
View File
@@ -0,0 +1,10 @@
using GlitchyEngine.Content;
using System;
using System.IO;
namespace GlitchyEditor.Assets;
interface IAssetSaver
{
Result<void> EditorSaveAsset(Stream file, Asset asset, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager);
}
@@ -0,0 +1,8 @@
using System.IO;
namespace GlitchyEditor.Assets;
interface IReloadingAssetLoader
{
public void ReloadAsset(AssetFile assetFile, Stream data);
}
@@ -0,0 +1,530 @@
using Bon;
using GlitchyEngine.Content;
using System;
using System.Collections;
using System.IO;
using GlitchyEngine;
using GlitchyEngine.Renderer;
using ImGui;
using GlitchyEngine.Math;
using System.Diagnostics;
using Bon.Integrated;
namespace GlitchyEditor.Assets;
class MaterialAssetPropertiesEditor : AssetPropertiesEditor
{
public static bool TryGetValue(Dictionary<String, Variant> parameters, String name, out Variant value)
{
if (parameters.TryGetValue(name, let param))
{
value = param;
return true;
}
value = ?;
return false;
}
mixin DropAssetTarget<T>() where T : Asset
{
Asset asset = null;
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM");
if (payload != null)
{
StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize);
asset = Content.LoadAsset<Asset>(fullpath);
}
ImGui.EndDragDropTarget();
}
asset
}
public this(AssetFile asset) : base(asset)
{
}
public override void ShowEditor()
{
Material material = Asset.LoadedAsset as Material;
if (material == null)
return;
Effect effect = material?.Effect;
if (effect == null)
return;
ShowTextures(material, effect);
ShowVariables(material, effect);
}
private void ShowTextures(Material material, Effect effect)
{
for (let texture in effect.Textures)
{
ImGui.Button(texture.key);
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM");
if (payload != null)
{
StringView path = .((char8*)payload.Data, (int)payload.DataSize);
using (Texture2D newTexture = Content.LoadAsset<Texture2D>(path))//new Texture2D(path, true))
{
newTexture.SamplerState = SamplerStateManager.AnisotropicWrap;
material.SetTexture(texture.key, newTexture);
}
}
ImGui.EndDragDropTarget();
}
}
}
private void ShowVariables(Material material, Effect effect)
{
for (let (name, arguments) in effect.[Friend]_variableDescriptions)
{
let variable = effect.Variables[name];
bool hasPreviewName = TryGetValue(arguments, "Preview", var previewName);
StringView displayName = hasPreviewName ? previewName.Get<String>() : name;
bool hasPreviewType = TryGetValue(arguments, "Type", var previewType);
if (hasPreviewType && previewType.Get<String>() == "Color")
{
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
if (variable.Columns == 3)
{
material.GetVariable<Vector3>(variable.Name, var value);
value = (Vector3)ColorRGB.LinearToSRGB((ColorRGB)value);
if (ImGui.ColorEdit3(displayName.Ptr, *(float[3]*)&value))
{
value = (Vector3)ColorRGB.SRgbToLinear((ColorRGB)value);
material.SetVariable(variable.Name, value);
}
}
else if (variable.Columns == 4)
{
material.GetVariable<Vector4>(variable.Name, var value);
value = (Vector4)ColorRGBA.LinearToSRGB((ColorRGBA)value);
if (ImGui.ColorEdit4(displayName.Ptr, *(float[4]*)&value))
{
value = (Vector4)ColorRGBA.SRgbToLinear((ColorRGBA)value);
material.SetVariable(variable.Name, value);
}
}
}
else if (variable.Type == .Float && variable.Rows == 1)
{
bool hasMin = TryGetValue(arguments, "Min", var min);
bool hasMax = TryGetValue(arguments, "Max", var max);
for (int r < variable.Rows)
{
switch (variable.Columns)
{
case 1:
material.GetVariable<float>(variable.Name, var value);
float[1] minV = hasMin ? min.Get<float[1]>() : .(float.MinValue);
float[1] maxV = hasMax ? max.Get<float[1]>() : .(float.MaxValue);
if (ImGui.EditVector<1>(displayName, ref *(float[1]*)&value, .(), 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 2:
material.GetVariable<Vector2>(variable.Name, var value);
Vector2 minV = hasMin ? min.Get<Vector2>() : .(float.MinValue);
Vector2 maxV = hasMax ? max.Get<Vector2>() : .(float.MaxValue);
if (ImGui.EditVector2(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 3:
material.GetVariable<Vector3>(variable.Name, var value);
Vector3 minV = hasMin ? min.Get<Vector3>() : .(float.MinValue);
Vector3 maxV = hasMax ? max.Get<Vector3>() : .(float.MaxValue);
if (ImGui.EditVector3(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 4:
material.GetVariable<Vector4>(variable.Name, var value);
Vector4 minV = hasMin ? min.Get<Vector4>() : .(float.MinValue);
Vector4 maxV = hasMax ? max.Get<Vector4>() : .(float.MaxValue);
if (ImGui.EditVector4(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
}
}
}
}
}
public static AssetPropertiesEditor Factory(AssetFile assetFile)
{
return new Self(assetFile);
}
}
[BonTarget, BonPolyRegister]
class MaterialAssetLoaderConfig : AssetLoaderConfig
{
}
[BonTarget]
public enum VariableValue
{
case Float(float Value);
case Float2(Vector2 Value);
case Float3(Vector3 Value);
case Float4(Vector4 Value);
case Int(int Value);
case Int2(Int2 Value);
case Int3(Int3 Value);
case Int4(Int4 Value);
case ColorRGB(ColorRGB Value);
case ColorRGBA(ColorRGBA Value);
case None;
/*static this()
{
gBonEnv.typeHandlers.Add(typeof(Self),
((.)new => VariableValueSerialize, (.)new => VariableValueDeserialize));
}
static void VariableValueSerialize(BonWriter writer, ValueView value, BonEnvironment env)
{
Log.EngineLogger.Assert(value.type == typeof(Self));
let variableValue = value.Get<Self>();
writer.Type(variableValue)
using (writer.ObjectBlock())
{
Serialize.Value(writer, nameof(MaterialFile.Effect), materialFile.Effect, env);
Serialize.Value(writer, nameof(MaterialFile.Textures), materialFile.Textures, env);
}
}
static Result<void> VariableValueDeserialize(BonReader reader, ValueView val, BonEnvironment env)
{
return .Ok;
}*/
}
[BonTarget]
class MaterialFile
{
public String Effect ~ delete _;
public Dictionary<String, String> Textures ~ DeleteDictionaryAndKeysAndValues!(_);
public Dictionary<String, VariableValue> Variables ~
{
if (_ != null)
{
for (var entry in _)
{
delete entry.key;
//delete entry.value;
/*if (entry.value.HasValue)
entry.value->Dispose();*/
}
delete _;
}
};
/*static this()
{
gBonEnv.typeHandlers.Add(typeof(Self),
((.)new => MaterialSerialize, (.)new => MaterialDeserialize));
}
static void MaterialSerialize(BonWriter writer, ValueView value, BonEnvironment env)
{
Log.EngineLogger.Assert(value.type == typeof(Self));
let materialFile = value.Get<Self>();
using (writer.ObjectBlock())
{
Serialize.Value(writer, nameof(MaterialFile.Effect), materialFile.Effect, env);
Serialize.Value(writer, nameof(MaterialFile.Textures), materialFile.Textures, env);
}
}
private static void SerializeVariablesDictionary(BonWriter writer, MaterialFile materialFile, BonEnvironment env)
{
using (writer.ArrayBlock())
{
for (let (name, value) in materialFile.Variables)
{
let keyVal = ValueView(typeof(String), name);
Serialize.Value(writer, keyVal, env);
writer.Pair();
ValueView valueVal;// = ValueView(, entriesPtr + (currentIndex * entryStride) + entryValueOffset);
switch(value.GetType())
{
case typeof(ColorRGBA):
writer.Identifier("ColorRGBA");
default:
}
Serialize.Value(writer, valueVal, env);
}
}
}
static Result<void> MaterialDeserialize(BonReader reader, ValueView val, BonEnvironment env)
{
return .Ok;
}*/
}
class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
{
private static readonly List<StringView> _fileExtensions = new .(){".mat"} ~ delete _;
public static List<StringView> FileExtensions => _fileExtensions;
public AssetLoaderConfig GetDefaultConfig()
{
return new ModelAssetLoaderConfig();
}
public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
StreamReader reader = scope .(file);
String text = scope .();
reader.ReadToEnd(text);
MaterialFile materialFile = scope .();
var result = Bon.Deserialize<MaterialFile>(ref materialFile, text);
if (result case .Err)
{
Log.EngineLogger.Error("Failed to load material.");
Debug.SafeBreak();
return null;
// TODO: return error material
}
Effect fx = new Effect(materialFile.Effect);
Material material = new Material(fx);
for (let (slotName, textureIdentifier) in materialFile.Textures)
{
using (Texture texture = contentManager.LoadAsset(textureIdentifier) as Texture)
{
if (texture == null)
{
Log.EngineLogger.Error($"Failed to load texture \"{textureIdentifier}\".");
// TODO: LoadAsset should return an error texture.
}
material.SetTexture(slotName, texture);
}
}
fx.ReleaseRef();
for (let (slotName, variableValue) in materialFile.Variables)
{
switch (variableValue)
{
case .ColorRGBA(let value):
material.SetVariable(slotName, value);
case .ColorRGB(let value):
material.SetVariable(slotName, value);
case .Float(let value):
material.SetVariable(slotName, value);
case .Float2(let value):
material.SetVariable(slotName, value);
case .Float3(let value):
material.SetVariable(slotName, value);
case .Float4(let value):
material.SetVariable(slotName, value);
case .None:
default:
Log.EngineLogger.Error("Errorre");
}
}
return material;
}
public Result<void> EditorSaveAsset(Stream file, Asset asset, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
Material material = asset as Material;
if (material == null)
{
Log.EngineLogger.Error("Asset must be a Material!");
return .Err;
}
MaterialFile materialFile = scope .();
materialFile.Effect = new String(material.Effect.Identifier);
materialFile.Textures = new .();
materialFile.Variables = new .();
//material.SetTexture();
for (let (slotName, textureViewBinding) in material.[Friend]_textures)
{
//materialFile.Textures.Add(slotName, textureViewBinding.)
}
Effect effect = material.Effect;
if (effect == null)
return .Ok;
for (let (name, arguments) in effect.[Friend]_variableDescriptions)
{
VariableValue variableValue = .None;
//Object variantValue = null;
let variable = effect.Variables[name];
bool hasPreviewType = MaterialAssetPropertiesEditor.TryGetValue(arguments, "Type", var previewType);
if (hasPreviewType && previewType.Get<String>() == "Color")
{
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
if (variable.Columns == 3)
{
material.GetVariable<ColorRGB>(variable.Name, var value);
value = ColorRGB.LinearToSRGB((ColorRGB)value);
//variantValue = new box value;
variableValue = .ColorRGB(value);
}
else if (variable.Columns == 4)
{
material.GetVariable<ColorRGBA>(variable.Name, var value);
value = ColorRGBA.LinearToSRGB((ColorRGBA)value);
variableValue = .ColorRGBA(value);
//variantValue = new box value;
}
}
else if (variable.Type == .Float && variable.Rows == 1)
{
switch (variable.Columns)
{
case 1:
material.GetVariable<float>(variable.Name, let value);
variableValue = .Float(value);
case 2:
material.GetVariable<Vector2>(variable.Name, let value);
variableValue = .Float2(value);
case 3:
material.GetVariable<Vector3>(variable.Name, let value);
variableValue = .Float3(value);
case 4:
material.GetVariable<Vector4>(variable.Name, let value);
variableValue = .Float4(value);
}
/*bool hasMin = TryGetValue(arguments, "Min", var min);
bool hasMax = TryGetValue(arguments, "Max", var max);
for (int r < variable.Rows)
{
switch (variable.Columns)
{
case 1:
material.GetVariable<float>(variable.Name, var value);
float[1] minV = hasMin ? min.Get<float[1]>() : .(float.MinValue);
float[1] maxV = hasMax ? max.Get<float[1]>() : .(float.MaxValue);
if (ImGui.EditVector<1>(displayName, ref *(float[1]*)&value, .(), 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 2:
material.GetVariable<Vector2>(variable.Name, var value);
Vector2 minV = hasMin ? min.Get<Vector2>() : .(float.MinValue);
Vector2 maxV = hasMax ? max.Get<Vector2>() : .(float.MaxValue);
if (ImGui.EditVector2(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 3:
material.GetVariable<Vector3>(variable.Name, var value);
Vector3 minV = hasMin ? min.Get<Vector3>() : .(float.MinValue);
Vector3 maxV = hasMax ? max.Get<Vector3>() : .(float.MaxValue);
if (ImGui.EditVector3(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 4:
material.GetVariable<Vector4>(variable.Name, var value);
Vector4 minV = hasMin ? min.Get<Vector4>() : .(float.MinValue);
Vector4 maxV = hasMax ? max.Get<Vector4>() : .(float.MaxValue);
if (ImGui.EditVector4(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
}
}*/
}
materialFile.Variables.Add(name, variableValue);
}
String text = scope .();
gBonEnv.serializeFlags |= .IncludeDefault | .Verbose;
Bon.Serialize<MaterialFile>(materialFile, text);
StreamWriter writer = scope .(file, .UTF8, 1024);
writer.Write(text);
return .Ok;
}
}
@@ -0,0 +1,51 @@
using Bon;
using GlitchyEngine.Content;
using System;
using System.Collections;
using System.IO;
using GlitchyEngine;
namespace GlitchyEditor.Assets;
class ModelAssetPropertiesEditor : AssetPropertiesEditor
{
public this(AssetFile asset) : base(asset)
{
}
public override void ShowEditor()
{
}
public static AssetPropertiesEditor Factory(AssetFile assetFile)
{
return new ModelAssetPropertiesEditor(assetFile);
}
}
[BonTarget, BonPolyRegister]
class ModelAssetLoaderConfig : AssetLoaderConfig
{
}
class ModelAssetLoader : IAssetLoader //, IReloadingAssetLoader
{
private static readonly List<StringView> _fileExtensions = new .(){".gltf", ".glb"} ~ delete _;
public static List<StringView> FileExtensions => _fileExtensions;
public AssetLoaderConfig GetDefaultConfig()
{
return new ModelAssetLoaderConfig();
}
public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
Log.EngineLogger.Assert(subAsset != null);
return ModelLoader.LoadMesh(file, subAsset.Value, 0);
}
}
@@ -0,0 +1,6 @@
using System;
namespace Bon.Integrated;
extension Serialize
{
}
@@ -0,0 +1,352 @@
using System;
using System.Collections;
using Bon;
using System.IO;
using GlitchyEngine;
using GlitchyEngine.Content;
using GlitchyEngine.Renderer;
using GlitchyEngine.Math;
using DirectXTK;
using ImGui;
namespace GlitchyEditor.Assets;
class TextureAssetPropertiesEditor : AssetPropertiesEditor
{
EditorTextureAssetLoaderConfig _textureConfig;
public this(AssetFile asset) : base(asset)
{
_textureConfig = asset.AssetConfig.Config as EditorTextureAssetLoaderConfig;
}
static char8*[3] _filterFuncNames = char8*[]("Point", "Linear", "Anisotropic");
public override void ShowEditor()
{
if (_textureConfig == null)
return;
bool generateMips = _textureConfig.GenerateMipMaps;
if (ImGui.Checkbox("Generate Mip Maps", &generateMips))
_textureConfig.GenerateMipMaps = generateMips;
bool isSrgb = _textureConfig.IsSRGB;
if (ImGui.Checkbox("Is sRGB", &isSrgb))
_textureConfig.IsSRGB = isSrgb;
SamplerStateDescription samplerStateDescription = _textureConfig.SamplerStateDescription;
void ShowFilterCombo(String label, ref FilterFunction filterFunction)
{
int32 selectedFilter = filterFunction.Underlying;
if (ImGui.Combo(label, &selectedFilter, &_filterFuncNames, 3))
filterFunction = (.)selectedFilter;
}
ImGui.Separator();
ImGui.TextUnformatted("Texture Filtering:");
ImGui.Separator();
ImGui.EnumCombo("Min Filter", ref samplerStateDescription.MinFilter);
ImGui.AttachTooltip("""
Sampling method used for minification.
If set to "Anisotropic" all Filters are set to "Anisotropic" internally.
""");
ImGui.EnumCombo("Mag Filter", ref samplerStateDescription.MagFilter);
ImGui.AttachTooltip("""
Sampling method used for magnification.
If set to "Anisotropic" all Filters are set to "Anisotropic" internally.
""");
ImGui.EnumCombo("Mip Map Filter", ref samplerStateDescription.MipFilter);
ImGui.AttachTooltip("""
Method used for mip-level sampling.
If set to "Anisotropic" all Filters are set to "Anisotropic" internally.
""");
if (samplerStateDescription.MagFilter == .Anisotropic ||
samplerStateDescription.MinFilter == .Anisotropic ||
samplerStateDescription.MipFilter == .Anisotropic)
{
ImGui.SliderScalar("Anisotropy Level", ref samplerStateDescription.MaxAnisotropy, 1, 16);
}
ImGui.NewLine();
ImGui.EnumCombo("Filter Mode", ref samplerStateDescription.FilterMode);
ImGui.AttachTooltip("Filtering method to use when sampling a texture.");
if (samplerStateDescription.FilterMode == .Comparison)
{
ImGui.EnumCombo("Comparison Function", ref samplerStateDescription.ComparisonFunction);
ImGui.AttachTooltip("""
The function that is used to compare the sampled data against the existing sampled data.
Only applies if Filter Mode is set to FilterMode.Comparison.
""");
}
ImGui.Separator();
ImGui.TextUnformatted("Wrapping");
ImGui.Separator();
ImGui.EnumCombo("Wrap Mode U", ref samplerStateDescription.AddressModeU);
ImGui.AttachTooltip("Method to use for resolving a u texture coordinate that is outside the 0 to 1 range.");
ImGui.EnumCombo("Wrap Mode V", ref samplerStateDescription.AddressModeV);
ImGui.AttachTooltip("Method to use for resolving a v texture coordinate that is outside the 0 to 1 range.");
ImGui.EnumCombo("Wrap Mode W", ref samplerStateDescription.AddressModeW);
ImGui.AttachTooltip("Method to use for resolving a w texture coordinate that is outside the 0 to 1 range.");
if (samplerStateDescription.AddressModeU == .Border ||
samplerStateDescription.AddressModeV == .Border ||
samplerStateDescription.AddressModeW == .Border)
{
ImGui.ColorEdit4("Border Color", ref samplerStateDescription.BorderColor);
}
ImGui.Separator();
ImGui.TextUnformatted("Mip Maps");
ImGui.Separator();
ImGui.DragFloat("Mip LOD Bias", &samplerStateDescription.MipLODBias, 0.1f);
ImGui.AttachTooltip("""
Offset from the calculated mipmap level.
For example, if the GPU calculates that a texture should be sampled at mipmap level 3 and "Mip LOD Bias" is 2, then the texture will be sampled at mipmap level 5.
""");
ImGui.DragFloat("Min Mip LOD", &samplerStateDescription.MipMinLOD);
ImGui.AttachTooltip("Lower end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed.");
ImGui.DragFloat("Max LOD Bias", &samplerStateDescription.MipMaxLOD);
ImGui.AttachTooltip("""
Upper end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed.
This value must be greater than or equal to "Min Mip LOD". To have no upper limit on LOD set this to a large value.
""");
_textureConfig.SamplerStateDescription = samplerStateDescription;
}
public static AssetPropertiesEditor Factory(AssetFile assetFile)
{
return new TextureAssetPropertiesEditor(assetFile);
}
}
[BonTarget, BonPolyRegister]
class EditorTextureAssetLoaderConfig : AssetLoaderConfig
{
[BonInclude]
private bool _generateMipMaps;
[BonInclude]
private bool _isSrgb;
[BonInclude]
private SamplerStateDescription _samplerStateDescription = .();
public bool GenerateMipMaps
{
get => _generateMipMaps;
set => SetIfChanged(ref _generateMipMaps, value);
}
public bool IsSRGB
{
get => _isSrgb;
set => SetIfChanged(ref _isSrgb, value);
}
public SamplerStateDescription SamplerStateDescription
{
get => _samplerStateDescription;
set => SetIfChanged(ref _samplerStateDescription, value);
}
}
class EditorTextureAssetLoader : IAssetLoader, IReloadingAssetLoader
{
private static readonly List<StringView> _fileExtensions = new .(){".png", ".dds"} ~ delete _; // ".jpg", ".bmp"
public static List<StringView> FileExtensions => _fileExtensions;
public AssetLoaderConfig GetDefaultConfig()
{
return new EditorTextureAssetLoaderConfig();
}
public Asset LoadAsset(Stream data, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
var config;
if (config == null)
{
config = GetDefaultConfig();
defer:: delete config;
}
Log.EngineLogger.AssertDebug(config is EditorTextureAssetLoaderConfig, "config has wrong type.");
return LoadTexture(data, (EditorTextureAssetLoaderConfig)config);
}
const String PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
const String DdsMagicWord = "DDS ";
enum TextureType
{
Unknown,
DDS,
PNG
}
private static TextureType GetTextureType(Stream data)
{
int64 position = data.Position;
var readResult = data.Read<char8[8]>();
data.Position = position;
char8[8] magicWord;
if (readResult case .Ok(out magicWord))
{
StringView strView = .(&magicWord, magicWord.Count);
if (strView.StartsWith(PngMagicWord))
{
return .PNG;
}
else if (strView.StartsWith(DdsMagicWord))
{
return .DDS;
}
else
{
Runtime.FatalError("Unknown image format.");
}
}
return .Unknown;
}
private static Texture LoadTexture(Stream data, EditorTextureAssetLoaderConfig config)
{
Debug.Profiler.ProfileResourceFunction!();
Texture texture = null;
switch(GetTextureType(data))
{
case .DDS:
texture = LoadDds(data, config);
case .PNG:
texture = LoadPng(data, config);
case .Unknown:
Runtime.FatalError("Unknown image format.");
}
Log.EngineLogger.AssertDebug(texture != null);
SetSampler(texture, config);
return texture;
}
public void ReloadAsset(AssetFile assetFile, Stream data)
{
Texture reloadingTexture = assetFile.LoadedAsset as Texture;
if (reloadingTexture == null)
{
Log.EngineLogger.Error($"{nameof(Self)}: Requested reload of \"{assetFile.FilePath}\" but it's not a Texture!");
return;
}
EditorTextureAssetLoaderConfig config = assetFile.AssetConfig.Config as EditorTextureAssetLoaderConfig;
if (config == null)
{
Log.EngineLogger.Error($"{nameof(Self)}: Config of asset \"{assetFile.FilePath}\" doesn't have the correct type!");
return;
}
switch(GetTextureType(data))
{
case .DDS:
ReloadDds(reloadingTexture as Texture2D, data, config);
case .PNG:
ReloadPng(reloadingTexture as Texture2D, data, config);
case .Unknown:
Runtime.FatalError("Unknown image format.");
}
SetSampler(reloadingTexture, config);
}
private static void SetSampler(Texture texture, EditorTextureAssetLoaderConfig config)
{
using (SamplerState samplerState = SamplerStateManager.GetSampler(config.SamplerStateDescription))
{
texture.SamplerState = samplerState;
}
}
private static void ReloadPng(Texture2D reloadingTexture, Stream data, EditorTextureAssetLoaderConfig config)
{
Debug.Profiler.ProfileResourceFunction!();
using (Texture2D newTexture = LoadPng(data, config))
{
reloadingTexture.[Friend]SneakySwappyTexture(newTexture);
}
}
private static Texture2D LoadPng(Stream data, EditorTextureAssetLoaderConfig config)
{
Debug.Profiler.ProfileResourceFunction!();
uint8[] pngData = new:ScopedAlloc! uint8[data.Length];
var result = data.TryRead(pngData);
if (result case .Err(let err))
{
Log.EngineLogger.Error($"Failed to read data from stream. Texture: Error: {err}");
}
uint8* rawData = ?;
uint32 width = 0, height = 0;
uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count);
Log.EngineLogger.Assert(errorCode == 0, "Failed to load png File");
// TODO: load as SRGB because PNGs are usually not stored as linear
//Texture2DDesc desc = .(width, height, srgb? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
Texture2DDesc desc = .(width, height, config.IsSRGB ? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
Texture2D texture = new Texture2D(desc);
texture.SetData<Color>((.)rawData);
// TODO: Generate mip maps
LodePng.LodePng.Free(rawData);
return texture;
}
private static void ReloadDds(Texture2D reloadingTexture, Stream data, EditorTextureAssetLoaderConfig config)
{
using (Texture2D newTexture = new [Friend]Texture2D(data))
{
reloadingTexture.[Friend]SneakySwappyTexture(newTexture);
}
}
private static Texture LoadDds(Stream data, EditorTextureAssetLoaderConfig config)
{
Texture2D texture = new [Friend]Texture2D(data);
return texture;
}
}
@@ -2,65 +2,127 @@ using ImGui;
using GlitchyEngine.World; using GlitchyEngine.World;
using System; using System;
using GlitchyEngine.Math; using GlitchyEngine.Math;
using System.Collections;
using GlitchyEngine.Renderer;
using GlitchyEngine;
using GlitchyEngine.Content;
namespace GlitchyEditor.EditWindows namespace GlitchyEditor.EditWindows
{ {
class ComponentEditWindow using internal GlitchyEngine.World.TransformComponent;
class ComponentEditWindow : EditorWindow
{ {
public const String s_WindowTitle = "Components"; public const String s_WindowTitle = "Components";
private Editor _editor; private EntityHierarchyWindow _entityHierarchyWindow;
private bool _open = true; private List<(Type, ComponentAttribute attribute)> _componentTypes = new .() ~ delete _;
public Editor Editor => _editor; public this(EntityHierarchyWindow entityHierarchyWindow)
public bool Open
{ {
get => _open; _entityHierarchyWindow = entityHierarchyWindow;
set => _open = value;
for (let type in Type.Types)
{
if (let componentAttribute = type.GetCustomAttribute<ComponentAttribute>())
{
_componentTypes.Add((type, componentAttribute));
}
}
} }
public this(Editor editor) protected override void InternalShow()
{ {
_editor = editor; ImGui.PushStyleVar(.WindowMinSize, ImGui.Vec2(1000, 100));
}
public void Show()
{
if(!_open)
return;
if(!ImGui.Begin(s_WindowTitle, &_open, .None)) if(!ImGui.Begin(s_WindowTitle, &_open, .None))
{ {
ImGui.PopStyleVar();
ImGui.End(); ImGui.End();
return; return;
} }
if(_editor.SelectedEntities.Count == 1) if(_entityHierarchyWindow.SelectedEntities.Count == 1)
{ {
Entity entity = _editor.SelectedEntities.Front; Entity entity = _entityHierarchyWindow.SelectedEntities.Front;
ShowComponents(entity); ShowComponents(entity);
} }
ImGui.PopStyleVar();
ImGui.End(); ImGui.End();
} }
private void ShowComponents(Entity entity) private void ShowComponents(Entity entity)
{ {
NameComponentEditor.Show(_editor.World, entity); ShowNameComponentEditor(entity);
TransformComponentEditor.Show(_editor.World, entity);
ShowComponentEditor<TransformComponent>("Transform", entity, => ShowTransformComponentEditor);
ShowComponentEditor<CameraComponent>("Camera", entity, => ShowCameraComponentEditor, => ShowComponentContextMenu<CameraComponent>);
ShowComponentEditor<SpriterRendererComponent>("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu<SpriterRendererComponent>);
ShowComponentEditor<MeshRendererComponent>("Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu<MeshRendererComponent>);
ShowComponentEditor<LightComponent>("Light", entity, => ShowLightComponentEditor, => ShowComponentContextMenu<LightComponent>);
ShowComponentEditor<MeshComponent>("Mesh", entity, => ShowMeshComponentEditor, => ShowComponentContextMenu<MeshComponent>);
ShowComponentEditor<Rigidbody2DComponent>("Rigidbody 2D", entity, => ShowRigidBody2DComponentEditor, => ShowComponentContextMenu<Rigidbody2DComponent>);
ShowComponentEditor<BoxCollider2DComponent>("Box collider 2D", entity, => ShowBoxCollider2DComponentEditor, => ShowComponentContextMenu<BoxCollider2DComponent>);
ShowComponentEditor<CircleCollider2DComponent>("Circle collider 2D", entity, => ShowCircleCollider2DComponentEditor, => ShowComponentContextMenu<CircleCollider2DComponent>);
ShowAddComponentButton(entity);
}
private static void ShowComponentContextMenu<TComponent>(Entity entity, TComponent* component) where TComponent: struct, new
{
if (ImGui.Selectable("Remove Component"))
entity.RemoveComponent<TComponent>();
}
private static void ShowComponentEditor<TComponent>(String header, Entity entity, function void(Entity, TComponent*) showComponentEditor, function void(Entity, TComponent*) showComponentContextMenu = null) where TComponent: struct, new
{
if (!entity.HasComponent<TComponent>())
return;
TComponent* component = entity.GetComponent<TComponent>();
ImGui.PushID(header);
bool nodeOpen = ImGui.TreeNodeEx(header.CStr(), .DefaultOpen | .AllowItemOverlap | .Framed | .SpanFullWidth);
if (showComponentContextMenu != null)
{
ImGui.SameLine(ImGui.GetWindowContentRegionMax().x - ImGui.CalcTextSize("...").x - 2 * ImGui.GetStyle().FramePadding.x);
if (ImGui.SmallButton("..."))
{
ImGui.OpenPopup("component_popup");
}
if (ImGui.BeginPopup("component_popup"))
{
showComponentContextMenu(entity, component);
ImGui.EndPopup();
} }
} }
static class NameComponentEditor if (nodeOpen)
{ {
public static void Show(EcsWorld world, Entity entity) showComponentEditor(entity, component);
{
char8[128] nameBuffer = default;
DebugNameComponent* component = world.GetComponent<DebugNameComponent>(entity); ImGui.TreePop();
}
ImGui.PopID();
}
private static void ShowNameComponentEditor(Entity entity)
{
if (!entity.HasComponent<DebugNameComponent>())
return;
char8[256] nameBuffer = default;
DebugNameComponent* component = entity.GetComponent<DebugNameComponent>();
String name = null; String name = null;
@@ -70,7 +132,7 @@ namespace GlitchyEditor.EditWindows
} }
else else
{ {
name = scope:: $"Entity {entity.[Friend]Index}"; name = scope:: $"Entity {entity.Handle.[Friend]Index}";
} }
// Copy name to buffer // Copy name to buffer
@@ -80,78 +142,433 @@ namespace GlitchyEditor.EditWindows
{ {
if(component == null) if(component == null)
{ {
component = world.AssignComponent<DebugNameComponent>(entity); component = entity.AddComponent<DebugNameComponent>();
} }
component.DebugName.Clear(); component.DebugName.Clear();
component.DebugName.Append(&nameBuffer); component.DebugName.Append(&nameBuffer);
} }
} }
private static void ShowTransformComponentEditor(Entity entity, TransformComponent* transform)
{
float textWidth = ImGui.CalcTextSize("Position".CStr()).x;
textWidth = Math.Max(textWidth, ImGui.CalcTextSize("Rotation".CStr()).x);
textWidth = Math.Max(textWidth, ImGui.CalcTextSize("Scale".CStr()).x);
textWidth += ImGui.GetStyle().FramePadding.x * 3.0f;
Vector3 position = transform.Position;
if (ImGui.EditVector3("Position", ref position, .Zero, 0.1f, textWidth))
transform.Position = position;
Vector3 rotationEuler = MathHelper.ToDegrees(transform.EditorRotationEuler);
if (ImGui.EditVector3("Rotation", ref rotationEuler, .Zero, 0.1f, textWidth))
transform.EditorRotationEuler = MathHelper.ToRadians(rotationEuler);
Vector3 scale = transform.Scale;
if (ImGui.EditVector3("Scale", ref scale, .One, 0.1f, textWidth))
transform.Scale = scale;
} }
static class TransformComponentEditor static String[] strings = new String[]("Orthographic", "Perspective", "Perspective (Infinite)") ~ delete _;
{
public static void Show(EcsWorld world, Entity entity)
{
TransformComponent* component = world.GetComponent<TransformComponent>(entity);
if(component == null) private static void ShowCameraComponentEditor(Entity entity, CameraComponent* cameraComponent)
{
ImGui.Checkbox("Primary", &cameraComponent.Primary);
var camera = ref cameraComponent.Camera;
String typeName = strings[camera.ProjectionType.Underlying];
if (ImGui.BeginCombo("Projection", typeName.CStr()))
{
for (int i = 0; i < 3; i++)
{
bool isSelected = (typeName == strings[i]);
if (ImGui.Selectable(strings[i], isSelected))
{
camera.ProjectionType = (.)i;
}
if (isSelected)
ImGui.SetItemDefaultFocus();
}
ImGui.EndCombo();
}
if (camera.ProjectionType == .Perspective)
{
float fovY = MathHelper.ToDegrees(camera.PerspectiveFovY);
if (ImGui.DragFloat("Fov Y", &fovY, 0.1f))
camera.PerspectiveFovY = MathHelper.ToRadians(fovY);
float near = camera.PerspectiveNearPlane;
if (ImGui.DragFloat("Near", &near, 0.1f))
camera.PerspectiveNearPlane = near;
float far = camera.PerspectiveFarPlane;
if (ImGui.DragFloat("Far", &far, 0.1f))
camera.PerspectiveFarPlane = far;
}
else if (camera.ProjectionType == .InfinitePerspective)
{
float fovY = MathHelper.ToDegrees(camera.PerspectiveFovY);
if (ImGui.DragFloat("Vertical FOV", &fovY, 0.1f))
camera.PerspectiveFovY = MathHelper.ToRadians(fovY);
float near = camera.PerspectiveNearPlane;
if (ImGui.DragFloat("Near", &near, 0.1f))
camera.PerspectiveNearPlane = near;
}
else if (camera.ProjectionType == .Orthographic)
{
float size = camera.OrthographicHeight;
if (ImGui.DragFloat("Size", &size, 0.1f))
camera.OrthographicHeight = size;
float near = camera.OrthographicNearPlane;
if (ImGui.DragFloat("Near", &near, 0.1f))
camera.OrthographicNearPlane = near;
float far = camera.OrthographicFarPlane;
if (ImGui.DragFloat("Far", &far, 0.1f))
camera.OrthographicFarPlane = far;
}
bool fixedAspectRatio = camera.FixedAspectRatio;
if (ImGui.Checkbox("Fixed Aspect Ratio", &fixedAspectRatio))
camera.FixedAspectRatio = fixedAspectRatio;
if (fixedAspectRatio)
{
float aspect = camera.AspectRatio;
if (ImGui.DragFloat("Aspect Ratio", &aspect, 0.1f))
camera.AspectRatio = aspect;
}
}
private static void ShowSpriteRendererComponentEditor(Entity entity, SpriterRendererComponent* spriteRendererComponent)
{
ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(spriteRendererComponent.Color);
if (ImGui.ColorEdit4("Color", ref spriteColor))
spriteRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor);
ImGui.Button("Texture");
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM");
if (payload != null)
{
Log.EngineLogger.Warning("");
StringView path = .((char8*)payload.Data, (int)payload.DataSize);
using (Texture2D texture = Content.LoadAsset<Texture2D>(path))
{
spriteRendererComponent.Sprite = texture;
}
}
ImGui.EndDragDropTarget();
}
ImGui.EditVector<4>("UV Transform", ref *(float[4]*)&spriteRendererComponent.UvTransform);
ImGui.Checkbox("Is Circle", &spriteRendererComponent.IsCircle);
}
private static void ShowMeshRendererComponentEditor(Entity entity, MeshRendererComponent* meshRendererComponent)
{
ImGui.TextUnformatted("Material:");
ImGui.SameLine();
Material material = meshRendererComponent.Material;
StringView identifier = material?.Identifier ?? "None";
ImGui.Button(identifier.ToScopeCStr!());
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM");
if (payload != null)
{
StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize);
using (Material loadedMaterial = Content.LoadAsset<Material>(fullpath))
{
meshRendererComponent.Material = loadedMaterial;
}
}
ImGui.EndDragDropTarget();
}
/*Effect effect = material?.Effect;
if (effect == null)
return;*/
// Show a preview of the material here!
}
private static void ShowRigidBody2DComponentEditor(Entity entity, Rigidbody2DComponent* rigidBodyComponent)
{
const String[?] bodyTypeStrings = .("Static", "Dynamic", "Kinematic");
String bodyTypeName = bodyTypeStrings[rigidBodyComponent.BodyType.Underlying];
if (ImGui.BeginCombo("Type", bodyTypeName.CStr()))
{
for (int i = 0; i < 3; i++)
{
bool isSelected = (bodyTypeName == bodyTypeStrings[i]);
if (ImGui.Selectable(bodyTypeStrings[i], isSelected))
{
rigidBodyComponent.BodyType = (.)i;
}
if (isSelected)
ImGui.SetItemDefaultFocus();
}
ImGui.EndCombo();
}
ImGui.Checkbox("Fixed Rotation", &rigidBodyComponent.FixedRotation);
}
private static void ShowBoxCollider2DComponentEditor(Entity entity, BoxCollider2DComponent* boxCollider)
{
float textWidth = ImGui.CalcTextSize("Offset".CStr()).x;
textWidth += ImGui.GetStyle().FramePadding.x * 3.0f;
Vector2 offset = boxCollider.Offset;
if (ImGui.EditVector2("Offset", ref offset, .Zero, 0.1f, textWidth))
boxCollider.Offset = offset;
Vector2 size = boxCollider.Size;
if (ImGui.EditVector2("Size", ref size, .Zero, 0.1f, textWidth))
boxCollider.Size = size;
float density = boxCollider.Density;
if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth))
boxCollider.Density = density;
float friction = boxCollider.Friction;
if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth))
boxCollider.Friction = friction;
float restitution = boxCollider.Restitution;
if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth))
boxCollider.Restitution = restitution;
float restitutionThreshold = boxCollider.RestitutionThreshold;
if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth))
boxCollider.RestitutionThreshold = restitutionThreshold;
}
private static void ShowCircleCollider2DComponentEditor(Entity entity, CircleCollider2DComponent* circleCollider)
{
float textWidth = ImGui.CalcTextSize("Offset".CStr()).x;
textWidth += ImGui.GetStyle().FramePadding.x * 3.0f;
Vector2 offset = circleCollider.Offset;
if (ImGui.EditVector2("Offset", ref offset, .Zero, 0.1f, textWidth))
circleCollider.Offset = offset;
float radius = circleCollider.Radius;
if (ImGui.DragFloat("Radius", &radius, 0.0f, 0.1f, textWidth))
circleCollider.Radius = radius;
float density = circleCollider.Density;
if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth))
circleCollider.Density = density;
float friction = circleCollider.Friction;
if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth))
circleCollider.Friction = friction;
float restitution = circleCollider.Restitution;
if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth))
circleCollider.Restitution = restitution;
float restitutionThreshold = circleCollider.RestitutionThreshold;
if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth))
circleCollider.RestitutionThreshold = restitutionThreshold;
}
private static void LabelColumn(StringView label)
{
ImGui.TextUnformatted(label);
ImGui.NextColumn();
}
private static void ShowLightComponentEditor(Entity entity, LightComponent* lightComponent)
{
ImGui.Columns(2);
defer ImGui.Columns(1);
const String[?] strings = String[]("Directional", "Point", "Spot");
var light = ref lightComponent.SceneLight;
String typeName = strings[light.LightType.Underlying];
LabelColumn("Type");
if (ImGui.BeginCombo("##Type", typeName.CStr()))
{
for (int i = 0; i < 3; i++)
{
bool isSelected = (typeName == strings[i]);
if (ImGui.Selectable(strings[i], isSelected))
{
light.LightType = (.)i;
}
if (isSelected)
ImGui.SetItemDefaultFocus();
}
ImGui.EndCombo();
}
ImGui.NextColumn();
LabelColumn("Color");
ColorRGB color = ColorRGB.LinearToSRGB(light.Color);
if (ImGui.ColorEdit3("##Color", ref color))
light.Color = ColorRGB.SRgbToLinear(color);
ImGui.NextColumn();
LabelColumn("Illuminance");
float illuminance = light.Illuminance;
if (ImGui.DragFloat("##Illuminance", &illuminance, 0.1f, 0.0f, float.MaxValue))
light.Illuminance = illuminance;
}
private static void ShowMeshComponentEditor(Entity entity, MeshComponent* meshComponent)
{
ImGui.TextUnformatted("Mesh:");
ImGui.SameLine();
GeometryBinding mesh = meshComponent.Mesh;
StringView identifier = mesh?.Identifier ?? "None";
ImGui.Button(identifier.ToScopeCStr!());
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);*/
using (GeometryBinding geometry = Content.LoadAsset<GeometryBinding>(fullpath))
{
meshComponent.Mesh = geometry;
}
// TODO: support multiple primitives (treat every primitive as a single mesh? or: mesh can have multiple primitives)
/*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)
{
static char8[128] searchBuffer = .();
static StringView searchFilter = .();
static float buttonWidth = 100;
ImGui.NewLine();
ImGui.Separator();
ImGui.NewLine();
void ShowComponentButton<TComponent>(String name) where TComponent : struct, new
{
// If the entity already has this component, don't show the option to add it
if (entity.HasComponent<TComponent>())
return; return;
if(ImGui.TreeNodeEx("Transform", .DefaultOpen)) float textWidth = ImGui.CalcTextSize(name.CStr()).x;
buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2);
if (name.Contains(searchFilter, true) && ImGui.Selectable(name))
{ {
Vector3 position = component.Position; if (!entity.HasComponent<TComponent>())
bool positionChanged = false; entity.AddComponent<TComponent>();
}
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) float textWidth = ImGui.CalcTextSize("Add Component...").x;
buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2);
ImGui.PushItemWidth(buttonWidth);
ImGui.NewLine();
ImGui.SameLine(ImGui.GetContentRegionMax().x / 2 - buttonWidth / 2);
if (ImGui.BeginCombo("##add_component_combo", "Add Component...", .NoArrowButton))
{ {
ImGui.TableNextColumn(); if (ImGui.InputText("##search_component_name", &searchBuffer, (.)searchBuffer.Count))
{
ImGui.Text(name); searchFilter = .(&searchBuffer);
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); ImGui.Separator();
ShowTableRow("Position", ref position, ref positionChanged); ShowComponentButton<CameraComponent>("Camera");
ShowTableRow("Rotation", ref rotationEuler, ref rotationChanged); ShowComponentButton<SpriterRendererComponent>("Sprite Renderer");
ShowTableRow("Scale", ref scale, ref scaleChanged); ShowComponentButton<LightComponent>("Light");
ShowComponentButton<Rigidbody2DComponent>("Rigidbody 2D");
ShowComponentButton<BoxCollider2DComponent>("Box collider 2D");
ShowComponentButton<CircleCollider2DComponent>("Circle collider 2D");
ShowComponentButton<MeshComponent>("Mesh");
ShowComponentButton<MeshRendererComponent>("Mesh Renderer");
ImGui.EndTable(); ImGui.EndCombo();
if(positionChanged)
component.Position = position;
if(rotationChanged)
component.RotationEuler = MathHelper.ToRadians(rotationEuler);
if(scaleChanged)
component.Scale = scale;
ImGui.TreePop();
} }
ImGui.PopItemWidth();
} }
} }
} }
@@ -0,0 +1,338 @@
using ImGui;
using System;
using System.IO;
using GlitchyEngine.Collections;
using System.Collections;
using GlitchyEngine.Renderer;
using GlitchyEngine.Math;
using GlitchyEngine;
using GlitchyEditor.Assets;
namespace GlitchyEditor.EditWindows
{
using internal GlitchyEditor.EditorContentManager;
class ContentBrowserWindow : EditorWindow
{
// TODO: Get from project
//const String ContentDirectory = "./content";
private append String _currentDirectory = .();
private append String _selectedFile = .();
public static SubTexture2D s_FolderTexture;
public static SubTexture2D s_FileTexture;
public EditorContentManager _manager;
public StringView SelectedFile => _selectedFile;
public this(EditorContentManager contentManager)
{
_manager = contentManager;
}
protected override void InternalShow()
{
_manager.Update();
// Make sure we are in an existing directory.
if (!_manager.AssetHierarchy.FileExists(_currentDirectory))
{
_currentDirectory.Set(_manager.ContentDirectory);
}
if(!ImGui.Begin("Content Browser", &_open, .None))
{
ImGui.End();
return;
}
// Context menu when clicking on the background.
if (ImGui.BeginPopupContextWindow())
{
ShowCurrentFolderContextMenu();
ImGui.EndPopup();
}
ImGui.Columns(2);
DrawDirectorySideBar();
ImGui.NextColumn();
DrawCurrentDirectory();
ImGui.Columns(1);
ImGui.End();
}
/// Renders the context menu that is shown when the user right clicks on the background of the file browser.
private void ShowCurrentFolderContextMenu()
{
if (ImGui.MenuItem("Open in file browser..."))
{
if (Path.OpenFolder(_currentDirectory) case .Err)
Log.EngineLogger.Error("Failed to open directory in file browser.");
}
}
/// Renders a sidebar that shows a tree of all directories in the asset folder.
private void DrawDirectorySideBar()
{
for(var child in _manager.AssetHierarchy.[Friend]_assetHierarchy.Children)
{
ImGuiPrintEntityTree(child);
}
}
/// Renders an ImGui tree of all directories in the given tree.
/// @param tree The file hierarchy of which to render all directories.
private void ImGuiPrintEntityTree(TreeNode<AssetNode> tree)
{
if (!tree->IsDirectory)
return;
String name = tree->Name;
ImGui.TreeNodeFlags flags = .OpenOnArrow | .SpanAvailWidth;
if(tree.Children.Count == 0)
flags |= .Leaf;
if (tree->Path == _currentDirectory)
{
flags |= .Selected;
}
bool isOpen = ImGui.TreeNodeEx(name, flags, $"{name}");
if (ImGui.IsItemClicked(.Left))
{
_currentDirectory.Set(tree->Path);
}
if(isOpen)
{
for(var child in tree.Children)
{
ImGuiPrintEntityTree(child);
}
ImGui.TreePop();
}
}
private static Vector2 DirectoryItemSize = .(110, 110);
const Vector2 padding = .(24, 24);
/// Renders the contents of _currentDirectory.
private void DrawCurrentDirectory()
{
if (_currentDirectory.IsEmpty)
return;
ImGui.Style* style = ImGui.GetStyle();
float window_visible_x2 = ImGui.GetWindowPos().x + ImGui.GetWindowContentRegionMax().x;
// Get the node of the current directory.
var currentDirectoryNode = _manager.AssetHierarchy.GetNodeFromPath(_currentDirectory);
if (currentDirectoryNode case .Err)
{
Log.EngineLogger.Error($"No node exists for {_currentDirectory}.");
ImGui.TextUnformatted("Failed to display contents of directory.");
return;
}
for (var entry in currentDirectoryNode->Children)
{
ImGui.PushID(entry->Name);
DrawDirectoryItem(entry);
// X-Coordinate of the right side of the current entry.
float currentButtonRight = ImGui.GetItemRectMax().x;
// Expected right-Coordinate if next entry was on the same line.
float expectedButtonRight = currentButtonRight + style.ItemSpacing.x + DirectoryItemSize.X;
// If we aren't the last entry and the next button won't fit on the same line we start a new line.
if (entry != currentDirectoryNode->Children.Back && expectedButtonRight < window_visible_x2)
ImGui.SameLine();
ImGui.PopID();
}
}
/// Renders the button for the given directory item.
private void DrawDirectoryItem(TreeNode<AssetNode> entry)
{
ImGui.BeginChild("item", (.)DirectoryItemSize);
if (entry->Path == _selectedFile)
{
var color = ImGui.GetStyleColorVec4(.ButtonHovered);
ImGui.PushStyleColor(.Button, *color);
}
else
{
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
}
// TODO: preview images
SubTexture2D image = entry->IsDirectory ? s_FolderTexture : s_FileTexture;
ImGui.ImageButton(image, (.)(DirectoryItemSize - padding));
ImGui.PopStyleColor();
if (ImGui.BeginDragDropSource())
{
String fullpath = scope String(entry->Path);
// TODO: this is dirty
if (fullpath.StartsWith(_manager.ContentDirectory, .OrdinalIgnoreCase))
fullpath.Remove(0, _manager.ContentDirectory.Length);
Path.Fixup(fullpath);
ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once);
ImGui.EndDragDropSource();
}
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(.Left))
{
if (_selectedFile != entry->Path)
{
_selectedFile.Set(entry->Path);
}
}
if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(.Left))
{
EntryDoubleClicked(entry);
}
ImGui.TextUnformatted(entry->Name);
if (ImGui.BeginPopupContextWindow())
{
ShowItemContextMenu(entry);
ImGui.EndPopup();
}
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 = scope String(entry->Path);
fullpath.AppendF($"#{subAsset.Name}");
ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once);
ImGui.EndDragDropSource();
}
}
ImGui.EndPopup();
}
DeleteItemPopup(entry);
ImGui.EndChild();
}
private void DeleteItemPopup(TreeNode<AssetNode> fileOrFolder)
{
// Always center this window when appearing
ImGui.Vec2 center = ImGui.GetMainViewport().GetCenter();
ImGui.SetNextWindowPos(center, .Appearing, ImGui.Vec2(0.5f, 0.5f));
// TODO: fix delete popup
if (ImGui.BeginPopupModal("Delete?", null, .AlwaysAutoResize))
{
ImGui.Text($"""
Delete "{fileOrFolder->Name}"?
""");
ImGui.Separator();
if (ImGui.Button("Yes", ImGui.Vec2(120, 0)))
{
ImGui.CloseCurrentPopup();
}
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Cancel", ImGui.Vec2(120, 0)))
{
ImGui.CloseCurrentPopup();
}
ImGui.EndPopup();
}
}
/// Shows the context menu for the given file/folder.
private void ShowItemContextMenu(TreeNode<AssetNode> fileOrFolder)
{
bool isFile = !fileOrFolder->IsDirectory;
if (ImGui.MenuItem("Show in file browser..."))
{
if (Path.OpenFolderAndSelectItem(fileOrFolder->Path) case .Err)
{
Log.EngineLogger.Error("Failed to show path in file browser.");
}
}
if (isFile && ImGui.MenuItem("Open file with..."))
{
if (Path.OpenWithDialog(fileOrFolder->Path) case .Err)
{
Log.EngineLogger.Error("Failed to show \"Open with...\" dialog.");
}
}
if (ImGui.MenuItem("Delete"))
{
ImGui.OpenPopup("Delete?");
}
}
private void EntryDoubleClicked(TreeNode<AssetNode> entry)
{
if (entry->IsDirectory)
{
_currentDirectory.Set(entry->Path);
}
else
{
if (Path.OpenFolder(entry->Path) case .Err)
Log.EngineLogger.Error("Failed to open directory in file browser.");
}
}
}
}
@@ -0,0 +1,32 @@
using System;
namespace GlitchyEditor.EditWindows
{
abstract class EditorWindow
{
protected Editor _editor;
protected bool _open = true;
protected bool _hasFocus;
public bool Open
{
get => _open;
set => _open = value;
}
public bool HasFocus
{
get => _hasFocus;
}
public void Show()
{
if(!_open)
return;
InternalShow();
}
protected abstract void InternalShow();
}
}
@@ -10,35 +10,73 @@ namespace GlitchyEditor.EditWindows
using internal GlitchyEditor; using internal GlitchyEditor;
/// A window for viewing and editing the scene hierarchy /// A window for viewing and editing the scene hierarchy
class EntityHierarchyWindow class EntityHierarchyWindow : EditorWindow
{ {
public const String s_WindowTitle = "Entity Hierarchy"; public const String s_WindowTitle = "Entity Hierarchy";
private Editor _editor;
/// Buffer for the entity search string. /// Buffer for the entity search string.
private char8[64] _entitySearchChars; private char8[64] _entitySearchChars;
private bool _open = true; private Scene _scene;
public Editor Editor => _editor; private List<Entity> _selectedEntities = new .() ~ delete _;
public bool Open public List<Entity> SelectedEntities => _selectedEntities;
{
get => _open;
set => _open = value;
}
public this(Editor editor) public this(Editor editor, Scene scene)
{ {
_editor = editor; _editor = editor;
SetContext(scene);
} }
public void Show() public void SetContext(Scene scene)
{ {
if(!_open) ClearEntitySelection();
return; _scene = scene;
}
/*public bool SelectEntityWithId(uint32 id, bool addToSelection = false)
{
if (!addToSelection)
_selectedEntities.Clear();
_scene.[Friend]_ecsWorld.IsValid(id);
_selectedEntities.Add();
}*/
/// Deselects all entities.
public void ClearEntitySelection()
{
_selectedEntities.Clear();
}
/// Selects the given entity.
/// @param entity The entity to select.
/// @param clearOldSelection If true the previously selected entities will be deselected. If false, the given entity will be added to the current selection.
public void SelectEntity(Entity entity, bool clearOldSelection = false)
{
if (clearOldSelection)
ClearEntitySelection();
_selectedEntities.Add(entity);
}
/// Deselects the given entity.
/// @param entity The entity to deselect.
public bool DeselectEntity(Entity entity)
{
return _selectedEntities.Remove(entity);
}
/// Returns whether or not the given entity is currently selected.
public bool IsEntitySelected(Entity entity)
{
return _selectedEntities.Contains(entity);
}
protected override void InternalShow()
{
if(!ImGui.Begin(s_WindowTitle, &_open, .MenuBar)) if(!ImGui.Begin(s_WindowTitle, &_open, .MenuBar))
{ {
ImGui.End(); ImGui.End();
@@ -47,70 +85,97 @@ namespace GlitchyEditor.EditWindows
ShowEntityHierarchyMenuBar(); ShowEntityHierarchyMenuBar();
if (ImGui.BeginPopupContextWindow(s_WindowTitle))
{
Show_ContextMenu_Create(true, false, false);
ImGui.EndPopup();
}
if (_editor.SceneViewportWindow.SelectionChanged)
{
var handle = _scene.[Friend]_ecsWorld.GetCurrentVersion(EcsEntity.[Friend]CreateEntityID(_editor.SceneViewportWindow.SelectedEntityId, 0));
if (handle case .Ok(let h))
{
Entity e = .(h, _scene);
SelectEntity(e, Input.IsKeyReleased(.Control));
}
else if (Input.IsKeyReleased(.Control))
{
ClearEntitySelection();
}
}
ShowEntityHierarchy(); ShowEntityHierarchy();
if ((ImGui.IsMouseDown(.Left) || ImGui.IsMouseDown(.Right)) && !ImGui.IsAnyItemHovered() && !ImGui.GetIO().KeyCtrl && ImGui.IsWindowHovered(.AllowWhenBlockedByPopup))
ClearEntitySelection();
ImGui.End(); ImGui.End();
} }
/// Returns whether or not all selected entities have the same parent.
internal bool AllSelectionsOnSameLevel()
{
EcsEntity? parent = .InvalidEntity;
for(var selectedEntity in _selectedEntities)
{
var transformComponent = selectedEntity.GetComponent<TransformComponent>();
if(parent == .InvalidEntity)
{
parent = transformComponent.Parent;
}
else if(transformComponent.Parent != parent)
{
return false;
}
}
return true;
}
/// Finds all children of the given entity and stores their IDs in the given list.
internal void FindChildren(EcsEntity entity, List<EcsEntity> entities)
{
for(var (child, childTransform) in _scene.[Friend]_ecsWorld.Enumerate<TransformComponent>())
{
if(childTransform.Parent == entity)
{
if(!entities.Contains(child))
entities.Add(child);
FindChildren(child, entities);
}
}
}
/// Deletes all selected entities and their children.
internal void DeleteSelectedEntities()
{
for (var entity in _selectedEntities)
{
_scene.DestroyEntity(entity, true);
}
_selectedEntities.Clear();
}
private void ShowEntityHierarchyMenuBar() private void ShowEntityHierarchyMenuBar()
{ {
if(ImGui.BeginMenuBar()) if(ImGui.BeginMenuBar())
{ {
if(ImGui.BeginMenu("Create")) Show_ContextMenu_Create(true, true, true);
Show_ContextMenu_Delete();
if(ImGui.MenuItem("Delete", null, false, !_selectedEntities.IsEmpty) ||
(Input.IsKeyPressed(.Delete) && ImGui.IsWindowHovered()))
{ {
if(ImGui.MenuItem("Empty Entity")) DeleteSelectedEntities();
{
_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()) if(ImGui.IsItemHovered())
@@ -124,11 +189,119 @@ namespace GlitchyEditor.EditWindows
} }
} }
/// Creates a new entity that is a child of the given entity.
private void CreateChild(Entity? entity)
{
var newEntity = _scene.CreateEntity();
var transformCmp = newEntity.GetComponent<TransformComponent>();
// Last entity in list is the entity that has been selected last.
transformCmp.Parent = entity?.Handle ?? .InvalidEntity;
}
/// Creates a new entity that is a parent of the selected entities.
private void CreateParent()
{
if (_selectedEntities.IsEmpty || !AllSelectionsOnSameLevel())
{
Log.EngineLogger.Error("Cannot create parent entity.");
return;
}
var commonParent = _selectedEntities.Front.GetComponent<TransformComponent>();
var newEntity = _scene.CreateEntity();
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 newEntityTransform = newEntity.GetComponent<TransformComponent>();
newEntityTransform.Parent = commonParent.Parent;
}
// new entity is parent of all selected entities.
for(var selectedEntity in _selectedEntities)
{
var selectedTransform = selectedEntity.GetComponent<TransformComponent>();
selectedTransform.Parent = newEntity.Handle;
}
}
private void Show_ContextMenu_Create(bool allowEmpty = true, bool allowChild = true, bool allowParent = true)
{
if (ImGui.BeginMenu("Create"))
{
if (allowEmpty)
{
if(ImGui.MenuItem("Empty Entity"))
{
if (_selectedEntities.IsEmpty)
_scene.CreateEntity();
else
{
Entity? parent = _selectedEntities.Back.Parent;
CreateChild(parent);
}
}
if(ImGui.IsItemHovered())
{
ImGui.SetTooltip("Create a new Entity.");
}
}
if (allowChild)
{
if(ImGui.MenuItem("Empty Child", null, false, !_selectedEntities.IsEmpty))
{
CreateChild(_selectedEntities.Back);
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new Entity that is a child of the selected entity.");
}
if (allowParent)
{
if(ImGui.MenuItem("Parent", null, false, !_selectedEntities.IsEmpty && AllSelectionsOnSameLevel()))
{
CreateParent();
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new Entity that is the parent of the selected entities.");
}
ImGui.EndMenu();
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new Entity.");
}
private bool Show_ContextMenu_Delete()
{
bool deleted = false;
if(ImGui.MenuItem("Delete", null, false, !_selectedEntities.IsEmpty))
{
DeleteSelectedEntities();
deleted = true;
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Deletes the selected Entities and their children.");
return deleted;
}
private void ImGuiPrintEntityTree(TreeNode<Entity> tree) private void ImGuiPrintEntityTree(TreeNode<Entity> tree)
{ {
String name = null; String name = null;
var nameComponent = _editor.World.GetComponent<DebugNameComponent>(tree.Value); var nameComponent = tree.Value.GetComponent<DebugNameComponent>();
if(nameComponent != null) if(nameComponent != null)
{ {
@@ -136,20 +309,37 @@ namespace GlitchyEditor.EditWindows
} }
else else
{ {
name = scope:: $"Entity {(tree.Value.[Friend]Index)}"; name = scope:: $"Entity {(tree.Value.Handle.[Friend]Index)}";
} }
ImGui.TreeNodeFlags flags = .OpenOnArrow; ImGui.TreeNodeFlags flags = .OpenOnArrow | .DefaultOpen | .SpanAvailWidth;
if(tree.Children.Count == 0) if(tree.Children.Count == 0)
flags |= .Leaf; flags |= .Leaf;
bool inSelectedList = _editor.SelectedEntities.Contains(tree.Value); bool inSelectedList = IsEntitySelected(tree.Value);
if(inSelectedList) if(inSelectedList)
flags |= .Selected; flags |= .Selected;
bool isOpen = ImGui.TreeNodeEx(name, flags); bool isOpen = ImGui.TreeNodeEx((void*)(uint)tree.Value.Handle.[Friend]Index, flags, $"{name}");
ImGui.PushID((void*)(uint)tree.Value.Handle.[Friend]Index);
bool deleted = false;
if (ImGui.BeginPopupContextItem("treeNodePopup"))
{
Show_ContextMenu_Create(true, true, true);
deleted = Show_ContextMenu_Delete();
ImGui.EndPopup();
}
ImGui.PopID();
if (deleted)
return;
if(ImGui.BeginDragDropSource()) if(ImGui.BeginDragDropSource())
{ {
@@ -177,33 +367,34 @@ namespace GlitchyEditor.EditWindows
// make sure the dropped entity is not a parent of the entity we dropped it on. // make sure the dropped entity is not a parent of the entity we dropped it on.
while(true) while(true)
{ {
var walkerParent = _editor.World.GetComponent<ParentComponent>(walker); var parentTransform = walker.GetComponent<TransformComponent>();
if(walkerParent == null) if(parentTransform.Parent == .InvalidEntity)
{ {
dropLegal = true; dropLegal = true;
break; break;
} }
else if(walkerParent.Entity == movedEntity) else if(parentTransform.Parent == movedEntity.Handle)
{ {
dropLegal = false; dropLegal = false;
break; break;
} }
walker = walkerParent.Entity; walker = .(parentTransform.Parent, _scene);
} }
if(dropLegal) if(dropLegal)
{ {
var movedEntityParent = _editor.World.AssignComponent<ParentComponent>(movedEntity); var movedEntityTransform = movedEntity.GetComponent<TransformComponent>();
movedEntityParent.Entity = tree.Value; movedEntityTransform.Parent = tree.Value.Handle;
} }
} }
ImGui.EndDragDropTarget(); ImGui.EndDragDropTarget();
} }
bool clicked = ImGui.IsItemClicked(); bool clicked = ImGui.IsItemClicked(.Left);
bool clickedRight = ImGui.IsItemClicked(.Right);
if(isOpen) if(isOpen)
{ {
@@ -215,23 +406,18 @@ namespace GlitchyEditor.EditWindows
ImGui.TreePop(); ImGui.TreePop();
} }
if(clicked) if (clicked || clickedRight)
{ {
if(inSelectedList) if (inSelectedList && !clickedRight)
{ {
_editor.SelectedEntities.Remove(tree.Value); DeselectEntity(tree.Value);
inSelectedList = false;
} }
else else
{ {
if(!ImGui.GetIO().KeyCtrl) SelectEntity(tree.Value, !ImGui.GetIO().KeyCtrl && !clickedRight);
{ inSelectedList = true;
_editor.SelectedEntities.Clear();
} }
_editor.SelectedEntities.Add(tree.Value);
}
inSelectedList = !inSelectedList;
} }
} }
@@ -239,34 +425,39 @@ namespace GlitchyEditor.EditWindows
{ {
StringView searchString = StringView(&_entitySearchChars); StringView searchString = StringView(&_entitySearchChars);
if(searchString.Length == 0) if(searchString.IsWhiteSpace)
{ {
// Show entity hierarchy as tree // Show entity hierarchy as tree
TreeNode<Entity> root = scope .(.InvalidEntity); TreeNode<Entity> root = scope .(Entity(.InvalidEntity, _scene));
TreeNode<Entity> AddEntity(Entity entity) TreeNode<Entity> InsertIntoTree(Entity entity)
{ {
var parent = _editor.World.GetComponent<ParentComponent>(entity); var transform = entity.GetComponent<TransformComponent>();
if(parent == null) if(transform == null)
{ {
return root.AddChild(entity); return root.AddChild(entity);
} }
else else
{ {
var parentNode = root.FindNode(parent.Entity); var parentEntity = Entity(transform.Parent, _scene);
var parentNode = root.FindNode(parentEntity);
if(parentNode == null) if(parentNode == null)
parentNode = AddEntity(parent.Entity); parentNode = InsertIntoTree(parentEntity);
return parentNode.AddChild(entity); return parentNode.AddChild(entity);
} }
} }
for(var entity in _editor.World.Enumerate()) for(var entityId in _scene.[Friend]_ecsWorld.Enumerate())
{ {
AddEntity(entity); Entity entity = .(entityId, _scene);
//if (!entity.HasComponent<EditorComponent>())
InsertIntoTree(entity);
} }
if(ImGui.TreeNodeEx("Scene", .DefaultOpen)) if(ImGui.TreeNodeEx("Scene", .DefaultOpen))
@@ -281,11 +472,10 @@ namespace GlitchyEditor.EditWindows
Entity movedEntity = *(Entity*)payload.Data; Entity movedEntity = *(Entity*)payload.Data;
_editor.World.RemoveComponent<ParentComponent>(movedEntity);
// Also mark transform as dirty // Also mark transform as dirty
var transformComponent = _editor.World.GetComponent<TransformComponent>(movedEntity); var transformComponent = movedEntity.GetComponent<TransformComponent>();
transformComponent?.IsDirty = true; transformComponent.Parent = .InvalidEntity;
//transformComponent?.IsDirty = true;
} }
ImGui.EndDragDropTarget(); ImGui.EndDragDropTarget();
@@ -307,11 +497,13 @@ namespace GlitchyEditor.EditWindows
List<StringView> searchTokens = new:ScopedAlloc! .(searchString.Split(' ', .RemoveEmptyEntries)); List<StringView> searchTokens = new:ScopedAlloc! .(searchString.Split(' ', .RemoveEmptyEntries));
worldEnumeration: worldEnumeration:
for(var entity in _editor.World.Enumerate()) for(var entityId in _scene.[Friend]_ecsWorld.Enumerate())
{ {
Entity entity = .(entityId, _scene);
String name = null; String name = null;
var nameComponent = _editor.World.GetComponent<DebugNameComponent>(entity); var nameComponent = entity.GetComponent<DebugNameComponent>();
if(nameComponent != null) if(nameComponent != null)
{ {
@@ -319,7 +511,7 @@ namespace GlitchyEditor.EditWindows
} }
else else
{ {
name = scope:worldEnumeration $"Entity {entity.[Friend]Index}"; name = scope:worldEnumeration $"Entity {entityId.[Friend]Index}";
} }
StringView nameView = StringView(name); StringView nameView = StringView(name);
@@ -0,0 +1,123 @@
using ImGui;
using System;
using GlitchyEngine.Collections;
using GlitchyEngine.Content;
using System.Reflection;
using GlitchyEngine;
using GlitchyEditor.Assets;
namespace GlitchyEditor.EditWindows;
class PropertiesWindow : EditorWindow
{
private AssetPropertiesEditor _currentPropertiesEditor ~ delete _;
private bool _lockCurrentAsset;
private bool _selectedNewAsset;
private append String _selectedFileName = .();
private Asset _currentAsset ~ _?.ReleaseRef();
public this(Editor editor)
{
_editor = editor;
}
protected override void InternalShow()
{
defer { ImGui.End(); }
if(!ImGui.Begin("Properties", &_open, .None))
return;
// TODO: make a little button in title bar?
ImGui.Checkbox("Lock", &_lockCurrentAsset);
ImGui.Separator();
ShowAssetProperties();
}
/// Gets the AssetFile for the asset currently selected in the ContentBrowserWindow
/// @returns the AssetFile for the currently selected asset of null, if no file is selected.
private AssetFile GetCurrentAssetFile()
{
// Only grab the currently selected file if we aren't locked
if (!_lockCurrentAsset)
{
StringView selectedInFileBrowser = _editor.ContentBrowserWindow.SelectedFile;
if (_selectedFileName != selectedInFileBrowser)
{
_selectedFileName.Set(_editor.ContentBrowserWindow.SelectedFile);
}
}
Result<TreeNode<AssetNode>> treeNode = _editor.ContentManager.AssetHierarchy.GetNodeFromPath(_selectedFileName);
if (treeNode case .Ok(let assetNode))
return assetNode->AssetFile;
return null;
}
private void ShowAssetProperties()
{
AssetFile assetFile = GetCurrentAssetFile();
if (_currentPropertiesEditor?.Asset != assetFile)
{
delete _currentPropertiesEditor;
_currentPropertiesEditor = _editor.ContentManager.GetNewPropertiesEditor(assetFile);
}
if (assetFile == null)
return;
// We need the actual asset for preview and sometimes for editing
if (_currentAsset?.Identifier != assetFile.Identifier)
{
_currentAsset?.ReleaseRef();
_currentAsset = _editor.ContentManager.LoadAsset(assetFile.Identifier);
}
// TODO: allow changing AssetLoader
// assetFile.AssetConfig.AssetLoade
// TODO: ignore file
/*ImGui.Checkbox("Ignore", &assetFile.AssetConfig.IgnoreFile);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("If checked this file will be ignored and not treated as an asset.");*/
ShowPropertiesEditor(assetFile);
ImGui.Separator();
// TODO: preview asset
}
private void ShowPropertiesEditor(AssetFile assetFile)
{
if (_currentPropertiesEditor == null)
return;
_currentPropertiesEditor.ShowEditor();
if (ImGui.Button("Save Asset"))
{
_editor.ContentManager.SaveAsset(_currentAsset);
}
if (!assetFile.AssetConfig.Config.Changed)
{
ImGui.BeginDisabled();
defer:: { ImGui.EndDisabled(); }
}
ImGui.Separator();
if (ImGui.Button("Apply"))
assetFile.SaveAssetConfig();
}
}
@@ -5,36 +5,39 @@ using GlitchyEngine.Math;
using GlitchyEngine; using GlitchyEngine;
using GlitchyEngine.World; using GlitchyEngine.World;
using ImGuizmo; using ImGuizmo;
using GlitchyEngine.Events;
namespace GlitchyEditor.EditWindows namespace GlitchyEditor.EditWindows
{ {
class SceneViewportWindow class SceneViewportWindow : EditorWindow
{ {
private Editor _editor;
public Camera _camera;
public const String s_WindowTitle = "Scene"; public const String s_WindowTitle = "Scene";
private bool _open = true; private ImGui.Vec2 _oldViewportSize = .(100, 100);
private bool _viewPortChanged;
private RenderTarget2D _renderTarget ~ _?.ReleaseRef(); /// True if the cursor wrapped from one side of the viewport to the other last frame.
private bool _wrappedCursor;
private bool _hasFocus; private RenderTargetGroup _renderTarget ~ _?.ReleaseRef();
public Event<EventHandler<Vector2>> ViewportSizeChangedEvent ~ _.Dispose(); private ImGuizmo.OPERATION _gizmoType = .TRANSLATE;
private ImGuizmo.MODE _gizmoMode = .LOCAL;
private float _snap = 0.5f;
private float _angleSnap = 45.0f;
private bool _doSnap = false;
public bool Open public uint32 SelectedEntityId {get; private set; }
{ public bool SelectionChanged { get; private set; }
get => _open;
set => _open = value;
}
public bool HasFocus public Vector2 ViewportSize => (Vector2)_oldViewportSize;
{ // Occurs when the viewport is resized.
get => _hasFocus; public Event<EventHandler<Vector2>> ViewportSizeChanged ~ _.Dispose();
} // Occurs when an entity was clicked.
public Event<EventHandler<uint32>> EntityClicked ~ _.Dispose();
public RenderTarget2D RenderTarget /// The render target that is shown in the viewport window.
public RenderTargetGroup RenderTarget
{ {
get => _renderTarget; get => _renderTarget;
set set
@@ -46,85 +49,282 @@ namespace GlitchyEditor.EditWindows
} }
} }
/// Gets or sets whether the editor functionality (gizmo, picking etc.) is enabled.
public bool EditorMode { get; set; } = true
public this(Editor editor) public this(Editor editor)
{ {
_editor = editor; _editor = editor;
} }
private ImGui.Vec2 oldViewportSize; protected override void InternalShow()
private bool viewPortChanged;
public void Show()
{ {
if(!_open)
return;
ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(1, 1)); ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(1, 1));
defer ImGui.PopStyleVar(); defer ImGui.PopStyleVar();
if(!ImGui.Begin(s_WindowTitle, &_open, .NoScrollbar)) if(!ImGui.Begin(s_WindowTitle, &_open, .NoScrollbar | .MenuBar))
{ {
ImGui.End(); ImGui.End();
return; return;
} }
let viewportSize = ImGui.GetContentRegionAvail();
if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton)) if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton))
{ {
var currentWindow = ImGui.GetCurrentWindow(); let currentWindow = ImGui.GetCurrentWindow();
ImGui.FocusWindow(currentWindow); ImGui.FocusWindow(currentWindow);
} }
_hasFocus = ImGui.IsWindowFocused(); _hasFocus = ImGui.IsWindowFocused();
var viewportSize = ImGui.GetContentRegionAvail(); if (EditorMode)
{
ShowMenuBar();
if (_editor.CurrentCamera.[Friend]BindMouse && _hasFocus)
WrapMouseInViewport();
// If we wrapped this frame we weren't hovering because the cursor has to be be out of bounds to wrap
_editor.CurrentCamera.AllowMove = _hasFocus && (ImGui.IsWindowHovered() || _wrappedCursor);
if (_hasFocus && !_editor.CurrentCamera.InUse)
{
if (Input.IsKeyPressing(.Q))
_gizmoType = .TRANSLATE;
if (Input.IsKeyPressing(.W))
_gizmoType = .ROTATE;
if (Input.IsKeyPressing(.E))
_gizmoType = .SCALE;
if (Input.IsKeyPressing(.G))
_gizmoMode = .WORLD;
if (Input.IsKeyPressing(.L))
_gizmoMode = .LOCAL;
}
}
if(_renderTarget != null) if(_renderTarget != null)
{ {
ImGui.Image(_renderTarget, viewportSize); ImGui.Image(_renderTarget.GetViewBinding(0), viewportSize);
//ImGui.Image(_editor.CurrentCamera.RenderTarget.GetViewBinding(0), viewportSize);
//ImGui.Image(_editor.CurrentScene.[Friend]_compositeTarget.GetViewBinding(0), viewportSize);
} }
if(_editor.SelectedEntities.Count > 0) if (EditorMode)
{ {
ImGuizmo.SetDrawlist(); HandleDropTarget();
var entity = _editor.SelectedEntities.Back; bool gizmoUsed = DrawImGuizmo(viewportSize);
var transformCmp = _editor.World.GetComponent<TransformComponent>(entity); MousePicking(viewportSize, gizmoUsed);
var transform = transformCmp.LocalTransform;
var view = _camera.View;
var projection = _camera.Projection;
var v = ImGui.GetWindowPos();
var cntMin = ImGui.GetWindowContentRegionMin();
v.x += cntMin.x;
v.y += cntMin.y;
ImGuizmo.SetRect(v.x, v.y, viewportSize.x, viewportSize.y);
Color c = .(0,0,0,255);
//ImGuizmo.DrawCubes((.)&view, (.)&projection, (.)&transform, 1);
ImGuizmo.Manipulate((.)&view, (.)&projection, .TRANSLATE, .LOCAL, (.)&transform);
Matrix mat = .Identity;
ImGuizmo.DrawGrid((.)&view, (.)&projection, (.)&mat, 10);
transformCmp.LocalTransform = transform;
//ImGuizmo.ViewManipulate((.)&view, , .TRANSLATE, .LOCAL, (.)&transform);
} }
ImGui.End(); ImGui.End();
if(oldViewportSize != viewportSize) if(_oldViewportSize != viewportSize)
{ {
ViewportSizeChangedEvent.Invoke(this, *(Vector2*)&oldViewportSize); ViewportSizeChanged.Invoke(this, (Vector2)viewportSize);
viewPortChanged = true; _viewPortChanged = true;
oldViewportSize = viewportSize; _oldViewportSize = viewportSize;
} }
} }
/// Provides the ImGui Drop target and handles dropped payload.
private void HandleDropTarget()
{
if (ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM");
if (payload != null)
{
StringView path = .((char8*)payload.Data, (int)payload.DataSize);
_editor.RequestOpenScene(this, path);
}
ImGui.EndDragDropTarget();
}
}
/// Wraps the mouse, so that it always stays in the viewport.
private void WrapMouseInViewport()
{
_wrappedCursor = false;
let mousePos = (Vector2)ImGui.GetMousePos();
let winPos = (Vector2)ImGui.GetWindowPos();
let regionMin = winPos + (Vector2)ImGui.GetWindowContentRegionMin();
let regionMax = winPos + (Vector2)ImGui.GetWindowContentRegionMax();
Vector2 newMousePos = mousePos;
if (mousePos.X < regionMin.X)
{
newMousePos.X = regionMax.X - 1;
}
else if (mousePos.X > regionMax.X - 1)
{
newMousePos.X = regionMin.X + 1;
}
if (mousePos.Y < regionMin.Y)
{
newMousePos.Y = regionMax.Y - 1;
}
else if (mousePos.Y > regionMax.Y - 1)
{
newMousePos.Y = regionMin.Y + 1;
}
if (newMousePos != mousePos)
{
Input.SetMousePosition((Int2)newMousePos);
// After wrapping the cursor the the other side, the camera controller must not compare the positions,
// because the delta doesn't represent the correct movement of the cursor.
// TODO: can be solved by using direct mouse movement instead of comparing positions
_editor.CurrentCamera.[Friend]MouseCooldown = 2;
_wrappedCursor = true;
}
}
/// If the user clicks, the entity beneath the cursor will be selected.
private void MousePicking(ImGui.Vec2 viewportSize, bool gizmoUsed)
{
Vector2 relativeMouse = (Vector2)ImGui.GetMousePos() - (Vector2)ImGui.GetItemRectMin();
int rtWidth = _editor.CurrentScene.[Friend]_compositeTarget.Width;
int rtHeight = _editor.CurrentScene.[Friend]_compositeTarget.Height;
if (Input.IsMouseButtonPressing(.LeftButton) &&
ImGui.IsWindowHovered() && !gizmoUsed && !_editor.CurrentCamera.InUse &&
relativeMouse.X >= 0 && relativeMouse.Y >= 0 &&
relativeMouse.X < viewportSize.x && relativeMouse.Y < viewportSize.y &&
relativeMouse.X < rtWidth && relativeMouse.Y < rtHeight)
{
uint32 id = uint32.MaxValue;
_editor.CurrentScene.[Friend]_compositeTarget.GetData<uint32>(&id, 1, (.)relativeMouse.X, (.)relativeMouse.Y, 1, 1);
SelectionChanged = true;
SelectedEntityId = id;
EntityClicked(this, id);
}
else
{
SelectionChanged = false;
}
}
private void ShowMenuBar()
{
if(ImGui.BeginMenuBar())
{
if (ImGui.RadioButton("Position", _gizmoType.HasFlag(.TRANSLATE)))
{
if (Input.IsKeyPressed(Key.Control))
_gizmoType ^= .TRANSLATE;
else
_gizmoType = .TRANSLATE;
}
if (ImGui.RadioButton("Rotation", _gizmoType.HasFlag(.ROTATE)))
{
if (Input.IsKeyPressed(Key.Control))
_gizmoType ^= .ROTATE;
else
_gizmoType = .ROTATE;
}
if (ImGui.RadioButton("Scale", _gizmoType.HasFlag(.SCALE)))
{
if (Input.IsKeyPressed(Key.Control))
_gizmoType ^= .SCALE;
else
_gizmoType = .SCALE;
}
if (ImGui.RadioButton("All", _gizmoType == .TRANSLATE | .ROTATE | .SCALE))
_gizmoType = .TRANSLATE | .ROTATE | .SCALE;
// If we scale, the mode must be local otherwise we could skew the matrix.
if (_gizmoType.HasFlag(.SCALE))
_gizmoMode = .LOCAL;
if (ImGui.MenuItem(_gizmoMode == .WORLD ? "Global" : "Local", null, true, !_gizmoType.HasFlag(.SCALE)))
{
if (_gizmoMode == .WORLD)
_gizmoMode = .LOCAL;
else
_gizmoMode = .WORLD;
}
_doSnap = Input.IsKeyPressed(.Shift);
ImGui.PushItemWidth(100);
if (_gizmoType.HasFlag(.ROTATE))
{
ImGui.DragFloat("Angle Snap", &_angleSnap, 1.0f, 0.0f, 180.0f);
}
if (_gizmoType.HasFlag(.TRANSLATE) || _gizmoType.HasFlag(.SCALE))
{
ImGui.DragFloat("Snap", &_snap, 0.1f, 0.0f, float.MaxValue);
}
ImGui.PopItemWidth();
ImGui.EndMenuBar();
}
}
private bool DrawImGuizmo(ImGui.Vec2 viewportSize)
{
// TODO: Needed if we have a orthographic editor-camera (or support gizmos in the Play-Window, where we can also have ortho projections)
ImGuizmo.SetOrthographic(false);
ImGuizmo.SetDrawlist();
var topLeft = ImGui.GetWindowPos();
var cntMin = ImGui.GetWindowContentRegionMin();
topLeft.x += cntMin.x;
topLeft.y += cntMin.y;
ImGuizmo.SetRect(topLeft.x, topLeft.y, viewportSize.x, viewportSize.y);
var view = _editor.CurrentCamera.View;
var projection = _editor.CurrentCamera.Projection;
if(_editor.EntityHierarchyWindow.SelectedEntities.Count == 0)
return false;
var entity = _editor.EntityHierarchyWindow.SelectedEntities.Back;
var transformCmp = entity.GetComponent<TransformComponent>();
var worldTransform = transformCmp.WorldTransform;
Matrix parentView = .Identity;
if (transformCmp.Parent != .InvalidEntity)
{
var parentTransformCmp = Entity(transformCmp.Parent, entity.Scene).GetComponent<TransformComponent>();
parentView = parentTransformCmp.WorldTransform.Invert();
}
Vector3 snap = .(_snap);
if (_gizmoType.HasFlag(.ROTATE))
snap = .(_angleSnap);
if (ImGuizmo.Manipulate((.)&view, (.)&projection, _gizmoType, _gizmoMode, (.)&worldTransform, null, _doSnap ? (.)&snap : null))
{
// TODO: Fix when parent is scaled
// Seems to work fine for parent rotation and translation but scaled parent ruins everything
// (probably because scaling a rotated matrix results in a skewed matrix, but unity can do it and so should we)
transformCmp.LocalTransform = parentView * worldTransform;
}
return ImGuizmo.IsUsing();
}
} }
} }
+42 -83
View File
@@ -4,111 +4,70 @@ using System;
using System.Collections; using System.Collections;
using GlitchyEngine.Collections; using GlitchyEngine.Collections;
using GlitchyEditor.EditWindows; using GlitchyEditor.EditWindows;
using GlitchyEngine;
using GlitchyEditor.Assets;
namespace GlitchyEditor namespace GlitchyEditor
{ {
class Editor class Editor
{ {
private EcsWorld _world; private Scene _scene;
private EntityHierarchyWindow _entityHierarchyWindow = new .(this) ~ delete _; private EditorContentManager _contentManager;
private ComponentEditWindow _componentEditWindow = new .(this) ~ delete _;
private SceneViewportWindow _sceneViewportWindow = new .(this) ~ delete _;
private List<Entity> _selectedEntities = new .() ~ delete _; private EntityHierarchyWindow _entityHierarchyWindow ~ delete _;
private ComponentEditWindow _componentEditWindow ~ delete _;
private SceneViewportWindow _sceneViewportWindow~ delete _;
private ContentBrowserWindow _contentBrowserWindow ~ delete _;
private PropertiesWindow _propertiesWindow ~ delete _;
public EcsWorld World => _world; public Scene CurrentScene
{
get => _scene;
set
{
_scene = value;
_entityHierarchyWindow.SetContext(_scene);
}
}
public List<Entity> SelectedEntities => _selectedEntities; public EditorContentManager ContentManager => _contentManager;
public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow; public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow;
public ComponentEditWindow ComponentEditWindow => _componentEditWindow; public ComponentEditWindow ComponentEditWindow => _componentEditWindow;
public SceneViewportWindow SceneViewportWindow => _sceneViewportWindow; public SceneViewportWindow SceneViewportWindow => _sceneViewportWindow;
public ContentBrowserWindow ContentBrowserWindow => _contentBrowserWindow;
public PropertiesWindow PropertiesWindow => _propertiesWindow;
public EditorCamera* CurrentCamera { get; set; }
public Event<EventHandler<StringView>> RequestOpenScene ~ _.Dispose();
/// Creates a new editor for the given world /// Creates a new editor for the given world
public this(EcsWorld world) public this(Scene scene, EditorContentManager contentManager)
{ {
_world = world; _scene = scene;
_contentManager = contentManager;
InitWindows();
}
private void InitWindows()
{
_sceneViewportWindow = new SceneViewportWindow(this);
_entityHierarchyWindow = new EntityHierarchyWindow(this, _scene);
_componentEditWindow = new ComponentEditWindow(_entityHierarchyWindow);
_contentBrowserWindow = new ContentBrowserWindow((.)Application.Get().ContentManager);
_propertiesWindow = new PropertiesWindow(this);
} }
public void Update() public void Update()
{ {
_sceneViewportWindow.Show();
_entityHierarchyWindow.Show(); _entityHierarchyWindow.Show();
_componentEditWindow.Show(); _componentEditWindow.Show();
_sceneViewportWindow.Show(); _contentBrowserWindow.Show();
_propertiesWindow.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();
}
} }
} }
+29 -1
View File
@@ -1,13 +1,41 @@
using System; using System;
using GlitchyEngine; using GlitchyEngine;
using GlitchyEngine.Content;
using GlitchyEditor.Assets;
namespace GlitchyEditor namespace GlitchyEditor
{ {
class EditorApp : Application class EditorApp : Application
{ {
EditorContentManager _contentManager;
public this() public this()
{ {
PushLayer(new EditorLayer()); PushLayer(new EditorLayer(_contentManager));
}
protected override IContentManager InitContentManager()
{
_contentManager = new EditorContentManager();
_contentManager.RegisterAssetLoader<EditorTextureAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<EditorTextureAssetLoader>(".png", ".dds");
_contentManager.SetAssetPropertiesEditor<EditorTextureAssetLoader>(=> TextureAssetPropertiesEditor.Factory);
_contentManager.RegisterAssetLoader<ModelAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<ModelAssetLoader>(".glb", ".gltf");
_contentManager.SetAssetPropertiesEditor<ModelAssetLoader>(=> ModelAssetPropertiesEditor.Factory);
_contentManager.RegisterAssetLoader<MaterialAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<MaterialAssetLoader>(".mat");
_contentManager.SetAssetPropertiesEditor<MaterialAssetLoader>(=> MaterialAssetPropertiesEditor.Factory);
_contentManager.RegisterAssetLoader<EffectAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<EffectAssetLoader>(".hlsl");
_contentManager.SetAssetPropertiesEditor<EffectAssetLoader>(=> EffectAssetPropertiesEditor.Factory);
_contentManager.SetContentDirectory("./content");
return _contentManager;
} }
[Export, LinkName("CreateApplication")] [Export, LinkName("CreateApplication")]
+449
View File
@@ -0,0 +1,449 @@
using System;
using System.IO;
using GlitchyEngine.Collections;
using System.Collections;
using GlitchyEngine.Renderer;
using System.Threading;
using GlitchyEngine.Content;
using GlitchyEditor.Assets;
using GlitchyEngine;
using System.Linq;
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 : IContentManager
{
private append String _contentDirectory = .();
public StringView ContentDirectory => _contentDirectory;
//private append List<String> _identifiers = .() ~ _.ClearAndDeleteItems();
private append Dictionary<StringView, Asset> _loadedAssets = .(); // Check if all resources are unloaded
private append AssetHierarchy _assetHierarchy = .(this);
public AssetHierarchy AssetHierarchy => _assetHierarchy;
public this()
{
_assetHierarchy.OnFileContentChanged.Add(new => OnFileContentChanged);
}
public ~this()
{
UnmanageAllAssets();
}
private void OnFileContentChanged(AssetNode assetNode)
{
// TODO: Subassets break reloading because we can't find them when we only receive the file that changed...
// Asset isn't loaded so we don't need to reload it.
if (assetNode.AssetFile.LoadedAsset == null)
return;
String neededAssetLoaderName = assetNode.AssetFile.AssetConfig?.AssetLoader;
if (String.IsNullOrWhiteSpace(neededAssetLoaderName))
return;
IAssetLoader assetLoader = null;
String loaderNameBuffer = scope String(64);
for (IAssetLoader loader in _assetLoaders)
{
loader.GetType().GetName(loaderNameBuffer..Clear());
if (loaderNameBuffer == neededAssetLoaderName)
{
assetLoader = loader;
break;
}
}
if (assetLoader == null)
{
Log.EngineLogger.Error($"Could not find asset loader \"{neededAssetLoaderName}\"");
return;
}
if (var assetReloader = assetLoader as IReloadingAssetLoader)
{
Stream stream = GetStream(assetNode.Path);
assetReloader.ReloadAsset(assetNode.AssetFile, stream);
delete stream;
}
}
public void SetContentDirectory(StringView contentDirectory)
{
_contentDirectory.Clear();
_contentDirectory.Append(contentDirectory);
Path.Fixup(_contentDirectory);
_assetHierarchy.SetContentDirectory(contentDirectory);
}
public void Update()
{
_assetHierarchy.Update();
}
public IAssetLoader GetDefaultAssetLoader(StringView fileExtension)
{
if (_defaultAssetLoaders.TryGetValue(fileExtension, let value))
return value;
return null;
}
private append List<String> _supportedExtensions = .() ~ ClearAndDeleteItems!(_);
private append List<IAssetLoader> _assetLoaders = .() ~ ClearAndDeleteItems!(_);
private append Dictionary<StringView, IAssetLoader> _defaultAssetLoaders = .();
private append Dictionary<String, function AssetPropertiesEditor(AssetFile)> _assetPropertiesEditors = .() ~ {
for (String key in _.Keys)
{
delete key;
}
};
public void RegisterAssetLoader<T>() where T : new, class, IAssetLoader
{
//Log.EngineLogger.AssertDebug(!_assetLoaders.Any((l) => l.GetType() == typeof(T)), "Asset loader already registered.");
_assetLoaders.Add(new T());
for (StringView ext in T.FileExtensions)
_supportedExtensions.Add(new String(ext));
}
public void SetAsDefaultAssetLoader<T>(params Span<StringView> fileExtensions) where T : IAssetLoader
{
for (var ext in fileExtensions)
{
// Find file extension in registered file extensions
String foundExtension = null;
for (var supportedExt in _supportedExtensions)
{
if (supportedExt == ext)
{
foundExtension = supportedExt;
break;
}
}
Log.EngineLogger.Assert(foundExtension != null, "File Extension is not registered.");
for (var loader in _assetLoaders)
{
if (loader.GetType() == typeof(T))
{
_defaultAssetLoaders[foundExtension] = loader;
break;
}
}
}
}
public void SetAssetPropertiesEditor(Type assetLoaderType, function AssetPropertiesEditor(AssetFile) editorFactory)
{
String loaderTypeName = new String();
assetLoaderType.GetName(loaderTypeName);
_assetPropertiesEditors[loaderTypeName] = editorFactory;
}
public void SetAssetPropertiesEditor<TAssetLoader>(function AssetPropertiesEditor(AssetFile) editorFactory) where TAssetLoader : IAssetLoader
{
SetAssetPropertiesEditor(typeof(TAssetLoader), editorFactory);
}
public AssetPropertiesEditor GetNewPropertiesEditor(AssetFile assetFile)
{
if (assetFile?.AssetConfig.AssetLoader == null)
return null;
if (_assetPropertiesEditors.TryGetValue(assetFile.AssetConfig.AssetLoader, let propertiesEditorfactory))
return propertiesEditorfactory(assetFile);
return null;
}
public bool IsLoaded(StringView identifier)
{
return _loadedAssets.ContainsKey(identifier);
}
public Asset LoadAsset(StringView identifier)
{
if (_loadedAssets.TryGetValue(identifier, let asset))
{
return asset..AddRef();
}
// Find subasset name
int poundIndex = identifier.IndexOf('#');
StringView resourceName = poundIndex == -1 ? identifier : identifier.Substring(0, poundIndex);
StringView? subassetName = identifier.Substring(poundIndex + 1);
String filePath = scope String(resourceName.Length + _contentDirectory.Length + 2);
Path.Combine(filePath, _contentDirectory, resourceName);
Path.Fixup(filePath);
//filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath);
if (resultNode case .Err)
{
Log.EngineLogger.Error($"Could not find asset \"{filePath}\".");
return null;
}
AssetFile file = resultNode->Value.AssetFile;
IAssetLoader assetLoader = null;
String loaderTypeName = scope .(128);
for (IAssetLoader loader in _assetLoaders)
{
loader.GetType().GetName(loaderTypeName..Clear());
if (loaderTypeName == file.AssetConfig.AssetLoader)
{
assetLoader = loader;
break;
}
}
Log.EngineLogger.AssertDebug(assetLoader != null);
Stream stream = GetStream(filePath);
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
delete stream;
if (loadedAsset == null)
return null;
//String identifierString = new .(identifier);
//_identifiers.Add(identifierString);
//_loadedAssets[identifierString] = loadedAsset;
loadedAsset.Identifier = identifier;
ManageAsset(loadedAsset);
file.[Friend]_loadedAsset = loadedAsset;
return loadedAsset;
}
/// Saves the asset.
public Result<void> SaveAsset(Asset asset)
{
// Find subasset name
int poundIndex = asset.Identifier.IndexOf('#');
StringView resourceName = poundIndex == -1 ? asset.Identifier : asset.Identifier.Substring(0, poundIndex);
StringView? subassetName = asset.Identifier.Substring(poundIndex + 1);
String filePath = scope String(resourceName.Length + _contentDirectory.Length + 2);
Path.Combine(filePath, _contentDirectory, resourceName);
Path.Fixup(filePath);
//filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
TreeNode<AssetNode> assetNode = Try!(AssetHierarchy.GetNodeFromPath(filePath));
AssetFile file = assetNode->AssetFile;
IAssetLoader assetLoader = null;
String loaderTypeName = scope .(128);
for (IAssetLoader loader in _assetLoaders)
{
loader.GetType().GetName(loaderTypeName..Clear());
if (loaderTypeName == file.AssetConfig.AssetLoader)
{
assetLoader = loader;
break;
}
}
IAssetSaver assetSaver = assetLoader as IAssetSaver;
if (assetSaver == null)
{
Log.EngineLogger.Error("The asset loader can't save!");
return .Err;
}
Stream stream = OpenStream(filePath, false, true);
assetSaver.EditorSaveAsset(stream, asset, file.AssetConfig.Config, resourceName, subassetName, this);
delete stream;
return .Ok;
}
private Stream OpenStream(StringView assetIdentifier, bool openOnly, bool truncate = false)
{
var assetIdentifier;
if (!assetIdentifier.StartsWith(_contentDirectory))
{
String filePath = scope:: String(assetIdentifier.Length + _contentDirectory.Length + 2);
Path.Combine(filePath, _contentDirectory, assetIdentifier);
assetIdentifier = filePath;
}
FileStream fs = new FileStream();
FileMode fileMode = openOnly ? FileMode.Open : FileMode.OpenOrCreate;
if (truncate)
fileMode |= .Truncate;
var result = fs.Open(assetIdentifier, fileMode, openOnly ? .Read : .ReadWrite, .ReadWrite);
if (result case .Err)
return null;
return fs;
}
// TODO: probably not needed
public Stream GetStream(StringView assetIdentifier)
{
return OpenStream(assetIdentifier, true);
/*var assetIdentifier;
if (!assetIdentifier.StartsWith(_contentDirectory))
{
String filePath = scope:: String(assetIdentifier.Length + _contentDirectory.Length + 2);
Path.Combine(filePath, _contentDirectory, assetIdentifier);
assetIdentifier = filePath;
}
FileStream fs = new FileStream();
var result = fs.Open(assetIdentifier, .Open, .Read, .ReadWrite);
if (result case .Err)
return null;
return fs;*/
}
public void ManageAsset(Asset asset)
{
_loadedAssets.Add(asset.Identifier, asset);
asset.[Friend]_contentManager = this;
}
public void UnmanageAsset(Asset asset)
{
_loadedAssets.Remove(asset.Identifier);
asset.[Friend]_contentManager = null;
}
/// This will unregister all assets from this content manager.
/// Note: This will not release any assets.
private void UnmanageAllAssets()
{
for (let (_, asset) in _loadedAssets)
{
UnmanageAsset(asset);
}
}
public void UpdateAssetIdentifier(Asset asset, StringView oldIdentifier, StringView newIdentifier)
{
if (oldIdentifier == newIdentifier)
return;
Log.EngineLogger.Assert(_loadedAssets.ContainsKey(newIdentifier), "An asset with the same identifier is already managed by this content manager.");
// Since all we do in order to track assets is add them to a dictionary we can simply unmanage and manage it again.
UnmanageAsset(asset);
ManageAsset(asset);
}
}
+56
View File
@@ -0,0 +1,56 @@
using System;
using GlitchyEngine.Renderer;
using GlitchyEngine.Math;
using GlitchyEngine;
namespace GlitchyEditor
{
class EditorIcons : RefCounted
{
Texture2D _texture ~ _.ReleaseRef();
public SubTexture2D DirectionalLight ~ _.ReleaseRef();
public SubTexture2D Camera ~ _.ReleaseRef();
public SubTexture2D Folder ~ _.ReleaseRef();
public SubTexture2D File ~ _.ReleaseRef();
public SubTexture2D Play ~ _.ReleaseRef();
public SubTexture2D Stop ~ _.ReleaseRef();
public SamplerState SamplerState
{
get => _texture.SamplerState;
set => _texture.SamplerState = value;
}
public this(String texturePath, Vector2 iconSize)
{
_texture = Content.LoadAsset<Texture2D>(texturePath);//new Texture2D(texturePath);
Vector2 pen = .();
DirectionalLight = GetNextGridTexture(ref pen, iconSize);
Camera = GetNextGridTexture(ref pen, iconSize);
Folder = GetNextGridTexture(ref pen, iconSize);
File = GetNextGridTexture(ref pen, iconSize);
Play = GetNextGridTexture(ref pen, iconSize);
Stop = GetNextGridTexture(ref pen, iconSize);
}
private SubTexture2D GetNextGridTexture(ref Vector2 pen, Vector2 iconSize)
{
SubTexture2D subTexture = .CreateFromGrid(_texture, pen, iconSize);
pen.X += 1.0f;
if (pen.X >= (_texture.Width / iconSize.X))
{
pen.X = 0;
pen.Y += 1.0f;
}
Log.EngineLogger.AssertDebug(pen.Y <=(_texture.Height / iconSize.Y));
return subTexture;
}
}
}
+553 -69
View File
@@ -7,6 +7,12 @@ using GlitchyEngine.ImGui;
using GlitchyEngine.Math; using GlitchyEngine.Math;
using GlitchyEngine.Renderer; using GlitchyEngine.Renderer;
using GlitchyEngine.World; using GlitchyEngine.World;
using GlitchyEngine.Content;
using System.Collections;
using GlitchyEngine.Renderer.Animation;
using System.IO;
using GlitchyEngine.Core;
using GlitchyEditor.Assets;
namespace GlitchyEditor namespace GlitchyEditor
{ {
@@ -15,35 +21,102 @@ namespace GlitchyEditor
RasterizerState _rasterizerState ~ _?.ReleaseRef(); RasterizerState _rasterizerState ~ _?.ReleaseRef();
RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef(); RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef();
GraphicsContext _context ~ _?.ReleaseRef(); GraphicsContext _context ~ _.ReleaseRef();
DepthStencilTarget _swapchainDepthBuffer ~ _?.ReleaseRef();
BlendState _alphaBlendState ~ _?.ReleaseRef(); BlendState _alphaBlendState ~ _.ReleaseRef();
BlendState _opaqueBlendState ~ _?.ReleaseRef(); BlendState _opaqueBlendState ~ _.ReleaseRef();
DepthStencilState _depthStencilState ~ _.ReleaseRef();
EcsWorld _world = new EcsWorld() ~ delete _; Scene _scene ~ delete _;
String _sceneFilePath = new String() ~ delete _;
public String SceneFilePath
{
get => _sceneFilePath;
set
{
_sceneFilePath.Clear();
if (value != null)
_sceneFilePath.Append(value);
}
}
Editor _editor ~ delete _; Editor _editor ~ delete _;
PerspectiveCameraController _cameraController ~ delete _; RenderTargetGroup _cameraTarget ~ _.ReleaseRef();
RenderTargetGroup _viewportTarget ~ _.ReleaseRef();
RenderTarget2D _renderTarget2D ~ _?.ReleaseRef(); SettingsWindow _settingsWindow = new .() ~ delete _;
public this() : base("Example") EditorCamera _camera ~ _.Dispose();
/*Texture2D _editorIcons ~ _.ReleaseRef();
SubTexture2D _iconDirectionalLight ~ _.ReleaseRef();
SubTexture2D _iconCamera ~ _.ReleaseRef();*/
EditorIcons _editorIcons ~ _.ReleaseRef();
EditorContentManager _contentManager;
enum SceneState
{
Edit,
Play,
// Pause,
// Simulate
}
SceneState _sceneState = .Edit;
public this(EditorContentManager contentManager) : base("Example")
{ {
Application.Get().Window.IsVSync = false; Application.Get().Window.IsVSync = false;
//InitContentManager();
_contentManager = contentManager;
InitGraphics(); InitGraphics();
InitEcs();
_scene = new Scene();
_camera = EditorCamera(Vector3(3.5f, 1.25f, 2.75f), Quaternion.FromEulerAngles(MathHelper.ToRadians(40), MathHelper.ToRadians(25), 0), MathHelper.ToRadians(75), 0.1f, 1);
_camera.RenderTarget = _cameraTarget;
InitEditor(); InitEditor();
NewScene();
} }
/*private void InitContentManager()
{
_contentManager = new EditorContentManager();
_contentManager.RegisterAssetLoader<EditorTextureAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<EditorTextureAssetLoader>(".png", ".dds");
_contentManager.SetAssetPropertiesEditor<EditorTextureAssetLoader>(=> TextureAssetPropertiesEditor.Factory);
_contentManager.RegisterAssetLoader<ModelAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<ModelAssetLoader>(".glb", ".gltf");
_contentManager.SetAssetPropertiesEditor<ModelAssetLoader>(=> ModelAssetPropertiesEditor.Factory);
_contentManager.RegisterAssetLoader<MaterialAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<MaterialAssetLoader>(".mat");
_contentManager.SetAssetPropertiesEditor<MaterialAssetLoader>(=> MaterialAssetPropertiesEditor.Factory);
_contentManager.RegisterAssetLoader<EffectAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<EffectAssetLoader>(".hlsl");
_contentManager.SetAssetPropertiesEditor<EffectAssetLoader>(=> EffectAssetPropertiesEditor.Factory);
_contentManager.SetContentDirectory("./content");
// Todo: Sketchy...
Application.Get().[Friend]_contentManager = _contentManager;
}*/
private void InitGraphics() private void InitGraphics()
{ {
_context = Application.Get().Window.Context..AddRef(); _context = Application.Get().Window.Context..AddRef();
_swapchainDepthBuffer = new DepthStencilTarget(_context.SwapChain.Width, _context.SwapChain.Height);
RasterizerStateDescription rsDesc = .(.Solid, .Back, true); RasterizerStateDescription rsDesc = .(.Solid, .Back, true);
_rasterizerState = new RasterizerState(rsDesc); _rasterizerState = new RasterizerState(rsDesc);
@@ -55,108 +128,483 @@ namespace GlitchyEditor
_alphaBlendState = new BlendState(blendDesc); _alphaBlendState = new BlendState(blendDesc);
_opaqueBlendState = new BlendState(.Default); _opaqueBlendState = new BlendState(.Default);
_renderTarget2D = new RenderTarget2D(RenderTarget2DDescription(.R8G8B8A8_UNorm, 100, 100) {DepthStencilFormat = .D32_Float}); DepthStencilStateDescription dsDesc = .();
_depthStencilState = new DepthStencilState(dsDesc);
SamplerStateDescription desc = .(); _cameraTarget = new RenderTargetGroup(.(){
Width = 100,
Height = 100,
ColorTargetDescriptions = TargetDescription[](
.(.R16G16B16A16_Float),
.(.R32_UInt)
),
DepthTargetDescription = .(.D24_UNorm_S8_UInt)
});
SamplerState sampler = new SamplerState(desc); _viewportTarget = new RenderTargetGroup(.()
_renderTarget2D.SamplerState = sampler;
sampler.ReleaseRef();
}
private void InitEcs()
{ {
_world.Register<DebugNameComponent>(); Width = 100,
_world.Register<TransformComponent>(); Height = 100,
_world.Register<ParentComponent>(); ColorTargetDescriptions = TargetDescription[](
_world.Register<MeshComponent>(); .(.R8G8B8A8_UNorm))
_world.Register<MeshRendererComponent>(); });
_world.Register<SkinnedMeshRendererComponent>();
_world.Register<CameraComponent>(); _editorIcons = new EditorIcons("Textures/EditorIcons.dds", .(64, 64));
_world.Register<AnimationComponent>(); _editorIcons.SamplerState = SamplerStateManager.AnisotropicClamp;
ContentBrowserWindow.s_FolderTexture = _editorIcons.Folder;
ContentBrowserWindow.s_FileTexture = _editorIcons.File;
/*_editorIcons = new Texture2D("Textures/EditorIcons.dds");
_editorIcons.SamplerState = SamplerStateManager.AnisotropicClamp;
_iconDirectionalLight = .CreateFromGrid(_editorIcons, .(0, 0), .(64, 64));
_iconCamera = .CreateFromGrid(_editorIcons, .(1, 0), .(64, 64));*/
} }
private void InitEditor() private void InitEditor()
{ {
_editor = new Editor(_world); _editor = new Editor(_scene, _contentManager);
_editor.SceneViewportWindow.ViewportSizeChangedEvent.Add(new (s, e) => ViewportSizeChanged(s, e)); _editor.SceneViewportWindow.ViewportSizeChanged.Add(new (s, e) => ViewportSizeChanged(s, e));
_editor.CurrentCamera = &_camera;
_cameraController = new .(Application.Get().Window.Context.SwapChain.BackbufferViewport.Width / _editor.RequestOpenScene.Add(new (s, fileName) => {
Application.Get().Window.Context.SwapChain.BackbufferViewport.Height); LoadSceneFile(fileName);
_cameraController.CameraPosition = .(0, 0, -5); });
_cameraController.TranslationSpeed = 10;
_editor.[Friend]CreateEntityWithTransform();
_editor.SceneViewportWindow._camera = _cameraController.Camera;
} }
public override void Update(GameTime gameTime) public override void Update(GameTime gameTime)
{ {
if(_editor.SceneViewportWindow.HasFocus && Input.IsMouseButtonPressed(.RightButton)) _camera.Update(gameTime);
_cameraController.Update(gameTime);
TransformSystem.Update(_world); // Clear the swapchain-buffer
RenderCommand.Clear(null, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
RenderCommand.Clear(_renderTarget2D, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0); RenderCommand.Clear(_viewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
_context.SetRenderTarget(_renderTarget2D); RenderCommand.SetBlendState(_alphaBlendState);
_context.BindRenderTargets(); RenderCommand.SetDepthStencilState(_depthStencilState);
RenderCommand.SetViewport(Viewport(0, 0, _renderTarget2D.Width, _renderTarget2D.Height)); if (_sceneState == .Edit)
_scene.UpdateEditor(gameTime, _camera, _viewportTarget, scope => DebugDraw3D, scope => DebugDraw2D);
else if (_sceneState == .Play)
_scene.UpdateRuntime(gameTime, _viewportTarget);
RenderCommand.SetBlendState(_opaqueBlendState); RenderCommand.UnbindRenderTargets();
RenderCommand.SetRenderTarget(null, 0, true);
Renderer.BeginScene(_cameraController.Camera); RenderCommand.BindRenderTargets();
DebugRenderer.Render(_world);
Renderer.EndScene();
RenderCommand.Clear(null, .(0.2f, 0.2f, 0.2f));
RenderCommand.Clear(_swapchainDepthBuffer, .Depth, 1.0f, 0);
_context.SetRenderTarget(null);
_context.SetDepthStencilTarget(_swapchainDepthBuffer);
_context.BindRenderTargets();
RenderCommand.SetViewport(_context.SwapChain.BackbufferViewport); RenderCommand.SetViewport(_context.SwapChain.BackbufferViewport);
} }
private void DebugDraw3D()
{
/*for (var (entity, transform, camera) in _scene.[Friend]_ecsWorld.Enumerate<TransformComponent, CameraComponent>())
{
if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _scene)))
{
DebugRenderer.DrawViewFrustum(transform.WorldTransform, camera.Camera.Projection);
}
}*/
}
private void DebugDraw2D()
{
RenderCommand.SetBlendState(_alphaBlendState);
Matrix billboard = _camera.View.Invert();
billboard.Translation = .Zero;
Matrix Billboard(Matrix transform)
{
Vector3 worldPos = transform.Translation;
return Matrix.Translation(worldPos) * billboard;
}
float CalculateAlpha(Vector3 pos)
{
return Math.Clamp(1.5f - Vector3.Distance(_editor.CurrentCamera.Position, pos) / 50, 0, 1);
}
for (var (entity, transform, camera) in _scene.[Friend]_ecsWorld.Enumerate<TransformComponent, CameraComponent>())
{
if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _scene)))
{
DebugRenderer.DrawViewFrustum(transform.WorldTransform, camera.Camera.Projection, _camera.Projection * _camera.View, .White);
}
Matrix world = Billboard(transform.WorldTransform);
float alpha = CalculateAlpha(transform.WorldTransform.Translation);
Renderer2D.DrawQuad(world, _editorIcons.Camera, ColorRGBA(alpha, alpha, alpha, alpha), .(0, 0, 1, 1), entity.Index);
//Renderer2D.DrawQuad(world, _iconCamera, .White, .(0, 0, 1, 1), entity.Index);
}
for (var (entity, transform, light) in _scene.[Friend]_ecsWorld.Enumerate<TransformComponent, LightComponent>())
{
if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _scene)))
{
Renderer.DrawRay(.Zero, .(0, 0, 20), ColorRGBA(light.SceneLight.Color, 1.0f), transform.WorldTransform);
for (float angle = 0; angle < MathHelper.TwoPi; angle += MathHelper.TwoPi / 5.0f)
{
Vector2 pos = MathHelper.CirclePoint(angle, 0.5f);
Renderer.DrawRay(.(pos, 0), .(pos, 20), .White, transform.WorldTransform);
}
}
Matrix world = Billboard(transform.WorldTransform);
float alpha = CalculateAlpha(transform.WorldTransform.Translation);
Renderer2D.DrawQuad(world, _editorIcons.DirectionalLight, ColorRGBA(light.SceneLight.Color.R * alpha, light.SceneLight.Color.G * alpha, light.SceneLight.Color.B * alpha, alpha), .(0, 0, 1, 1), entity.Index);
//Renderer2D.DrawQuad(world, _iconDirectionalLight, ColorRGBA(light.SceneLight.Color, alpha), .(0, 0, 1, 1), entity.Index);
}
}
public override void OnEvent(Event event) public override void OnEvent(Event event)
{ {
_cameraController.OnEvent(event);
EventDispatcher dispatcher = EventDispatcher(event); EventDispatcher dispatcher = EventDispatcher(event);
dispatcher.Dispatch<ImGuiRenderEvent>(scope (e) => OnImGuiRender(e)); dispatcher.Dispatch<ImGuiRenderEvent>(scope (e) => OnImGuiRender(e));
dispatcher.Dispatch<WindowResizeEvent>(scope (e) => OnWindowResize(e)); dispatcher.Dispatch<WindowResizeEvent>(scope (e) => OnWindowResize(e));
dispatcher.Dispatch<KeyPressedEvent>(scope (e) => OnKeyPressed(e));
dispatcher.Dispatch<MouseScrolledEvent>(scope (e) => OnMouseScrolled(e));
} }
ImGui.ID _mainDockspaceId; ImGui.ID _mainDockspaceId;
TextureViewer viewer = new TextureViewer() ~ delete _;
private bool OnImGuiRender(ImGuiRenderEvent event) private bool OnImGuiRender(ImGuiRenderEvent event)
{ {
Input.ImGuiDebugDraw();
//viewer.ViewTexture(Renderer.[Friend]_gBuffer.Target);
ImGui.Viewport* viewport = ImGui.GetMainViewport(); ImGui.Viewport* viewport = ImGui.GetMainViewport();
ImGui.DockSpaceOverViewport(viewport); ImGui.DockSpaceOverViewport(viewport);
DrawMainMenuBar(); DrawMainMenuBar();
_editor.SceneViewportWindow.RenderTarget = _renderTarget2D; _editor.SceneViewportWindow.RenderTarget = _viewportTarget;
_editor.Update(); _editor.Update();
_settingsWindow.Show();
UI_Toolbar();
return false; return false;
} }
private void UI_Toolbar()
{
ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(0, 2));
ImGui.PushStyleVar(.ItemInnerSpacing, ImGui.Vec2(0, 0));
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
let colors = ImGui.GetStyle().Colors;
ImGui.Vec4 hoveredColor = colors[(int)ImGui.Col.ButtonHovered];
hoveredColor.w = 0.5f;
ImGui.Vec4 activeColor = colors[(int)ImGui.Col.ButtonActive];
activeColor.w = 0.5f;
ImGui.PushStyleColor(.ButtonHovered, hoveredColor);
ImGui.PushStyleColor(.ButtonActive, activeColor);
ImGui.Begin("##toolbar", null, .NoDecoration | .NoScrollbar | .NoScrollWithMouse);
SubTexture2D icon = _sceneState == .Edit ? _editorIcons.Play : _editorIcons.Stop;
float size = ImGui.GetWindowHeight() - 4.0f;
ImGui.SameLine((ImGui.GetContentRegionMax().x / 2 - size / 2));
if (ImGui.ImageButton(icon, .(size, size), .Zero, .Ones, 0))
{
if (_sceneState == .Edit)
{
OnScenePlay();
}
else if (_sceneState == .Play)
{
OnSceneStop();
}
}
ImGui.End();
ImGui.PopStyleColor(3);
ImGui.PopStyleVar(2);
}
private void OnScenePlay()
{
_sceneState = .Play;
_editor.SceneViewportWindow.EditorMode = false;
_scene.OnRuntimeStart();
}
private void OnSceneStop()
{
_scene.OnRuntimeStop();
_editor.SceneViewportWindow.EditorMode = true;
_sceneState = .Edit;
}
// Just for testing
private void TestEntitiesWithModels()
{
/*{
var lightNtt = _scene.CreateEntity("My Sexy Sun 2");
let transform = lightNtt.GetComponent<TransformComponent>();
transform.Position = .(0, 0, 0);
transform.RotationEuler = .(MathHelper.ToRadians(70), MathHelper.ToRadians(-30), 0);
let light = lightNtt.AddComponent<LightComponent>();
light.SceneLight.Illuminance = 10.0f;
light.SceneLight.Color = .(1.0f, 0.95f, 0.8f);
}
{
var lightNtt = _scene.CreateEntity("My Sexy Sun 3");
let transform = lightNtt.GetComponent<TransformComponent>();
transform.Position = .(0, 0, 0);
transform.RotationEuler = .(MathHelper.ToRadians(20), MathHelper.ToRadians(-55), 0);
let light = lightNtt.AddComponent<LightComponent>();
light.SceneLight.Illuminance = 10.0f;
light.SceneLight.Color = .(1.0f, 0.95f, 0.8f);
}
{
var cameraNtt = _scene.CreateEntity("My Camera");
let transform = cameraNtt.GetComponent<TransformComponent>();
transform.Position = .(5, 5, -5);
transform.RotationEuler = .(MathHelper.ToRadians(45), MathHelper.ToRadians(-45), 0);
let camera = cameraNtt.AddComponent<CameraComponent>();
camera.Primary = true;
camera.Camera.SetPerspective(MathHelper.ToRadians(45), 0.1f, 10.0f);
camera.RenderTarget = _cameraTarget;
}
var fxLib = Application.Get().EffectLibrary;
using (Effect myEffect = fxLib.Load("content/Shaders/myEffect.hlsl"))
/*using (Texture2D albedo = new Texture2D("Textures/White.png", true))
using (Texture2D normal = new Texture2D("Textures/White.png"))
using (Texture2D rough = new Texture2D("Textures/White.png"))
using (Texture2D metal = new Texture2D("Textures/White.png"))*/
using (Texture2D albedo = new Texture2D("Textures/TestMat/rustediron2_albedo.png", true))
using (Texture2D normal = new Texture2D("Textures/TestMat/rustediron2_normal.png"))
using (Texture2D rough = new Texture2D("Textures/TestMat/rustediron2_roughness.png"))
using (Texture2D metal = new Texture2D("Textures/TestMat/rustediron2_metallic.png"))
{
albedo.SamplerState = SamplerStateManager.AnisotropicWrap;
normal.SamplerState = SamplerStateManager.AnisotropicWrap;
rough.SamplerState = SamplerStateManager.AnisotropicWrap;
metal.SamplerState = SamplerStateManager.AnisotropicWrap;
List<AnimationClip> clips = scope .();
using (Material mat = new .(myEffect))
{
mat.SetTexture("AlbedoTexture", albedo);
mat.SetTexture("NormalTexture", normal);
mat.SetTexture("MetallicTexture", metal);
mat.SetTexture("RoughnessTexture", rough);
mat.SetVariable("AlbedoColor", ColorRGBA.White);
mat.SetVariable("NormalScaling", Vector2.One);
mat.SetVariable("MetallicFactor", 1.0f);
mat.SetVariable("RoughnessFactor", 1.0f);
// TODO: completely wrong!
EcsEntity e = ModelLoader.LoadModel("content/Models/sphere.glb", mat, _scene.[Friend]_ecsWorld, clips, "Sphere 1");
Entity entity = .(e, _scene);
var transform = entity.GetComponent<TransformComponent>();
transform.Position = .(5, 0, 5);
}
/*using (Material mat = new .(myEffect))
{
mat.SetTexture("AlbedoTexture", albedo);
mat.SetTexture("NormalTexture", normal);
mat.SetTexture("MetallicTexture", metal);
mat.SetTexture("RoughnessTexture", rough);
//mat.SetVariable("BaseColor", Vector4(1, 1, 0, 1));
//mat.SetVariable("LightDir", Vector3(0, 1, 0).Normalized());
ModelLoader.LoadModel("content/Models/sphere.glb", myEffect, mat, _scene.[Friend]_ecsWorld, clips);
}*/
ClearAndReleaseItems!(clips);
}
using (Effect myEffect = fxLib.Get("myEffect"))
using (Texture2D white = new Texture2D("Textures/White.png"))
using (Texture2D normal = new Texture2D("Textures/DefaultNormal.png"))
{
white.SamplerState = SamplerStateManager.PointClamp;
normal.SamplerState = SamplerStateManager.PointClamp;
List<AnimationClip> clips = scope .();
for (int x < 10)
for (int y < 10)
{
using (Material mat = new .(myEffect))
{
mat.SetTexture("AlbedoTexture", white);
mat.SetTexture("NormalTexture", normal);
mat.SetTexture("MetallicTexture", white);
mat.SetTexture("RoughnessTexture", white);
mat.SetVariable("AlbedoColor", Vector4(1, 0, 0, 1));
mat.SetVariable("NormalScaling", Vector2(1.0f));
mat.SetVariable("RoughnessFactor", (x + 1) / 10.0f);
mat.SetVariable("MetallicFactor", y / 9.0f);
EcsEntity e = ModelLoader.LoadModel("content/Models/sphere.glb", mat, _scene.[Friend]_ecsWorld, clips, scope $"Sphere {x} {y}");
Entity entity = .(e, _scene);
var transform = entity.GetComponent<TransformComponent>();
transform.Position = .(x * 1.5f, y * 1.5f, 0);
}
}
ClearAndReleaseItems!(clips);
}*/
}
/// Creates a new scene.
private void NewScene()
{
SceneFilePath = null;
delete _scene;
_scene = new Scene();
_editor.CurrentScene = _scene;
var vpSize = _editor.SceneViewportWindow.ViewportSize;
_scene.OnViewportResize((.)vpSize.X, (.)vpSize.Y);
_camera.Position = .(-1.5f, 1.5f, -2.5f);
_camera.RotationEuler = .(MathHelper.ToRadians(25), MathHelper.ToRadians(35), 0);
// Create default camera
/*{
let camEntity = _scene.CreateEntity("Camera");
let transform = camEntity.Transform;
transform.Position =
}*/
/*// Create the default light source
{
let lightNtt = _scene.CreateEntity("Light");
let transform = lightNtt.Transform;
transform.Position = .(0, 0, 0);
transform.RotationEuler = .(MathHelper.ToRadians(45), MathHelper.ToRadians(-100), 0);
let light = lightNtt.AddComponent<LightComponent>();
light.SceneLight.Illuminance = 10.0f;
light.SceneLight.Color = .(1.0f, 0.95f, 0.8f);
}
TestEntitiesWithModels();*/
}
/// Saves the scene in the file that is was loaded from or saved to last. If there is no such path (i.e. it is a new scene) the save file dialog will open.
private void SaveScene()
{
if (String.IsNullOrWhiteSpace(SceneFilePath))
{
SaveSceneAs();
return;
}
SceneSerializer serializer = scope .(_scene);
serializer.Serialize(SceneFilePath);
}
/// Opens a save file dialog and saves the scene at the user specified location.
private void SaveSceneAs()
{
SaveFileDialog sfd = scope .();
if (sfd.ShowDialog() case .Ok(let val))
{
if (val == .OK)
{
SceneFilePath = sfd.FileNames[0];
SaveScene();
}
}
}
/// Opens a open file dialog and load the scene selected by the user specified.
private void OpenScene()
{
OpenFileDialog ofd = scope .();
if (ofd.ShowDialog() case .Ok(let val))
{
if (val == .OK)
{
LoadSceneFile(ofd.FileNames[0]);
}
}
}
/// Loads the given scene file.
private void LoadSceneFile(StringView filename)
{
SceneFilePath = scope String(filename);
delete _scene;
_scene = new Scene();
_editor.CurrentScene = _scene;
var vpSize = _editor.SceneViewportWindow.ViewportSize;
_scene.OnViewportResize((.)vpSize.X, (.)vpSize.Y);
SceneSerializer serializer = scope .(_scene);
serializer.Deserialize(SceneFilePath);
}
private void DrawMainMenuBar() private void DrawMainMenuBar()
{ {
ImGui.BeginMainMenuBar(); ImGui.BeginMainMenuBar();
if(ImGui.BeginMenu("File", false)) if(ImGui.BeginMenu("File", true))
{ {
if (ImGui.MenuItem("New", "Ctrl+N"))
NewScene();
if (ImGui.MenuItem("Save", "Ctrl+S"))
SaveScene();
if (ImGui.MenuItem("Save as...", "Ctrl+Shift+N"))
SaveSceneAs();
if (ImGui.MenuItem("Open...", "Ctrl+O"))
OpenScene();
ImGui.Separator();
if (ImGui.MenuItem("Settings"))
_settingsWindow.Open = true;
ImGui.Separator();
if (ImGui.MenuItem("Exit"))
Application.Get().Close();
ImGui.EndMenu(); ImGui.EndMenu();
} }
@@ -186,9 +634,6 @@ namespace GlitchyEditor
private bool OnWindowResize(WindowResizeEvent e) private bool OnWindowResize(WindowResizeEvent e)
{ {
_swapchainDepthBuffer.ReleaseRef();
_swapchainDepthBuffer = new DepthStencilTarget(_context.SwapChain.Width, _context.SwapChain.Height);
return false; return false;
} }
@@ -200,9 +645,48 @@ namespace GlitchyEditor
if(sizeX == 0 || sizeY == 0) if(sizeX == 0 || sizeY == 0)
return; return;
_renderTarget2D.Resize(sizeX, sizeY); _viewportTarget.Resize(sizeX, sizeY);
_cameraTarget.Resize(sizeX, sizeY);
_cameraController.AspectRatio = (float)sizeX / (float)sizeY; _scene.OnViewportResize(sizeX, sizeY);
_camera.OnViewportResize(sizeX, sizeY);
}
private bool OnKeyPressed(KeyPressedEvent e)
{
bool control = Input.IsKeyPressed(Key.Control);
bool shift = Input.IsKeyPressed(Key.Shift);
if (!_camera.[Friend]BindMouse && control)
{
switch (e.KeyCode)
{
case .N:
NewScene();
return true;
case .O:
OpenScene();
return true;
case .S:
if (shift)
SaveSceneAs();
else
SaveScene();
return true;
default:
}
}
return false;
}
private bool OnMouseScrolled(MouseScrolledEvent e)
{
if (_camera.OnMouseScrolled(e))
return true;
return false;
} }
} }
} }
+253
View File
@@ -0,0 +1,253 @@
using GlitchyEditor.EditWindows;
using ImGui;
using GlitchyEngine;
using System;
using System.Collections;
using System.Reflection;
namespace GlitchyEditor
{
class SettingsWindow : EditorWindow
{
class Binding
{
public Object SettingsObject;
public String Name ~ delete _;
public String FieldName ~ delete _;
public this(StringView name, StringView fieldName, Object settingsObject)
{
Name = new String(name);
FieldName = new String(fieldName);
SettingsObject = settingsObject;
}
}
class Category
{
public List<Binding> _bindings = new .() ~ DeleteContainerAndItems!(_);
public String Header ~ delete _;
public Object SettingsObject;
public this(String header)
{
Header = new String(header);
}
public void AddSetting(StringView name, StringView fieldName, Object settingsObject = null)
{
Binding binding = new .(name, fieldName, settingsObject ?? SettingsObject);
_bindings.Add(binding);
}
}
Settings _settings;
bool _settingsChanged = false;
private Dictionary<String, Category> _categories = new .() ~ DeleteDictionaryAndValues!(_);
public this()
{
_open = false;
_settings = Application.Get().Settings;
Create();
}
void Create()
{
ScanForSettings(_settings);
/*for (ISettings settings in _settings.[Friend]_userSettings)
{
ScanForSettings(settings);
}*/
}
Category AddCategory(String header)
{
if (_categories.TryGetValue(header, let category))
return category;
Category newCat = new Category(header);
_categories.Add(newCat.Header, newCat);
return newCat;
}
void ScanForSettings(Object container)
{
Type type = container.GetType();
for (var field in type.GetFields())
{
Result<SettingAttribute> settingResult = field.GetCustomAttribute<SettingAttribute>();
if (settingResult case .Ok(let settingInfo))
{
AddSetting(settingInfo.Category, settingInfo.Name, field.Name, container);
}
Result<SettingContainerAttribute> containerResult = field.GetCustomAttribute<SettingContainerAttribute>();
if (containerResult case .Ok(let containerInfo))
{
var res = field.GetValue<Object>(container, let childContainer);
if (res case .Err(let error))
{
Log.EngineLogger.Error($"Failed to get settings container. Error: {error}");
continue;
}
ScanForSettings(childContainer);
}
}
}
void AddSetting(String categoryName, String name, StringView fieldName, Object container)
{
Category category = AddCategory(categoryName);
category.AddSetting(name, fieldName, container);
}
protected override void InternalShow()
{
ImGui.Begin("Settings", &_open, .NoDocking);
defer ImGui.End();
// Leave room for 1 line below us
ImGui.BeginChild("item view", ImGui.Vec2(0, -ImGui.GetFrameHeightWithSpacing()));
ImGui.BeginTabBar("##Tabs");
for (Category category in _categories.Values)
{
if (ImGui.BeginTabItem(category.Header))
{
ImGui.Columns(2);
defer ImGui.Columns(1);
ImGui.SetColumnWidth(0, 100);
for (Binding setting in category._bindings)
{
ImGui.TextUnformatted(setting.Name);
ImGui.NextColumn();
Type settingsObjectType = setting.SettingsObject.GetType();
Result<FieldInfo> result = settingsObjectType.GetField(setting.FieldName);
if (result case .Err)
{
ImGui.PushStyleColor(.Text, ImGui.Vec4(1f, 0f, 0f, 1f));
ImGui.Text($"Field {setting.FieldName} not found.");
ImGui.PopStyleColor();
continue;
}
FieldInfo fieldInfo = result.Get();
Type fieldType = fieldInfo.FieldType;
mixin GetSettingValue<T>()
{
var error = fieldInfo.GetValue<T>(setting.SettingsObject, var value);
if (error case .Err(let err))
{
Log.EngineLogger.Error($"Could not get value of setting {setting.FieldName}. Error: {err}");
ImGui.PushStyleColor(.Text, ImGui.Vec4(1f, 0f, 0f, 1f));
ImGui.Text($"Could not get value of setting {setting.FieldName}. Error: {err}");
ImGui.PopStyleColor();
break;
}
value
}
mixin SetSettingValue<T>(T value)
{
var error = fieldInfo.SetValue(setting.SettingsObject, value);
if (error case .Err(let err))
{
Log.EngineLogger.Error($"Could not set value of setting {setting.FieldName}. Error: {err}");
}
}
switch (fieldType)
{
case typeof(int32):
int32 value = GetSettingValue!<int32>();
if (!ImGui.InputInt(scope $"##{setting.Name}", &value))
break;
SetSettingValue!(value);
_settingsChanged = true;
case typeof(String):
String value = GetSettingValue!<String>();
char8[256] buffer = .();
value.CopyTo(buffer);
if (ImGui.InputText(scope $"##{setting.Name}", &buffer, buffer.Count))
{
value..Clear().Append(&buffer);
_settingsChanged = true;
}
}
ImGui.NextColumn();
}
ImGui.EndTabItem();
}
}
ImGui.EndTabBar();
ImGui.EndChild();
ImGui.BeginDisabled(!_settingsChanged);
if (ImGui.Button("Save"))
{
_settings.Save();
_settings.Apply();
_open = false;
}
ImGui.SameLine();
if (ImGui.Button("Apply"))
{
_settings.Apply();
}
ImGui.EndDisabled();
ImGui.SameLine();
if (ImGui.Button("Cancel"))
{
Settings.Load();
_open = false;
}
}
}
}
+246
View File
@@ -0,0 +1,246 @@
using GlitchyEngine.Renderer;
using GlitchyEngine;
using ImGui;
using GlitchyEngine.Math;
using System;
namespace GlitchyEditor
{
class TextureViewer
{
enum BackgroundMode : int32
{
White,
Black,
Checkerboard
}
enum SampleMode : int32
{
Point,
Linear
}
GraphicsContext _context ~ _.ReleaseRef();
Effect _effect ~ _.ReleaseRef();
float _zoom = 1.0f;
BackgroundMode _backgroundMode = .Checkerboard;
SampleMode _sampleMode = .Linear;
RenderTarget2D _target ~ _?.ReleaseRef();
// TODO: we don't need depth!
DepthStencilTarget _depth ~ _?.ReleaseRef();
SamplerState _samplerPoint ~ _.ReleaseRef();
SamplerState _samplerLinear ~ _.ReleaseRef();
public this()
{
_context = Application.Get().Window.Context..AddRef();
InitEffect();
InitState();
// TODO: rasterizerstate and depthstencilstate
}
private void InitEffect()
{
_effect = new Effect("content\\Shaders\\textureViewerShader.hlsl");
}
private void InitState()
{
SamplerStateDescription desc = .();
desc.MagFilter = .Linear;
desc.MinFilter = .Linear;
_samplerLinear = SamplerStateManager.GetSampler(desc);
desc.MagFilter = .Point;
desc.MinFilter = .Point;
_samplerPoint = SamplerStateManager.GetSampler(desc);
}
Vector2 _position;
bool _moving;
float _colorOffset = 0;
float _colorScale = 1;
float _alphaOffset = 0;
float _alphaScale = 1;
public void ViewTexture(Texture viewedTexture)
{
ImGui.Begin("Texture Viewer");
ImGui.SliderFloat("Zoom", &_zoom, 0.01f, 100.0f);
char8*[] items = scope .("White", "Black", "Checkerboard");
ImGui.Combo("Background", (.)&_backgroundMode, items.Ptr, (.)items.Count);
items = scope .("Point", "Linear");
ImGui.Combo("Sampler", (.)&_sampleMode, items.Ptr, (.)items.Count);
ImGui.SliderFloat2("Color offset and scale", *(float[2]*)&_colorOffset, -1.0f, 1.0f);
ImGui.SliderFloat2("Alpha offset and scale", *(float[2]*)&_alphaOffset, -1.0f, 1.0f);
ImGui.SliderFloat2("Position", *(float[2]*)&_position, 2 * -Math.Max(viewedTexture.Width, viewedTexture.Height) * _zoom, 2 * Math.Max(viewedTexture.Width, viewedTexture.Height) * _zoom);
ImGui.BeginChild("imageChild");
UpdateInput();
var viewportSize = ImGui.GetContentRegionAvail();
viewportSize.x = Math.Max(viewportSize.x, 1);
viewportSize.y = Math.Max(viewportSize.y, 1);
if(_target == null || viewportSize.x != _target.Width || viewportSize.y != _target.Height)
{
_target?.ReleaseRef();
_target = new RenderTarget2D(.(.R8G8B8A8_UNorm, (.)viewportSize.x, (.)viewportSize.y));
_depth?.ReleaseRef();
_depth = new DepthStencilTarget((.)viewportSize.x, (.)viewportSize.y, .D16_UNorm);
}
RenderTexture(viewedTexture);
ImGui.Image(_target, viewportSize);
ImGui.EndChild();
ImGui.End();
}
float lastWheel;
private void UpdateInput()
{
var windowPos = ImGui.GetWindowPos();
var mousePos = ImGui.GetIO().MousePos;
Vector2 mouseInWindow = .(mousePos.x - windowPos.x, mousePos.y - windowPos.y);
bool windowHovered = ImGui.IsWindowHovered();
if(windowHovered && Input.IsMouseButtonPressing(.MiddleButton))
{
_moving = true;
}
else if(Input.IsMouseButtonReleased(.MiddleButton))
{
_moving = false;
}
if(windowHovered || _moving)
{
float mouseWheel = ImGui.GetIO().MouseWheel;
float delta = mouseWheel - lastWheel;
if(delta != 0)
{
_position -= mouseInWindow;
_position /= _zoom;
_zoom *= Math.Pow(1.1f, delta);
_position *= _zoom;
_position += mouseInWindow;
}
}
if(_moving)
{
Int2 movement = Input.GetMouseMovement();
_position.X += movement.X;
_position.Y += movement.Y;
}
}
OrthographicCamera _camera = new OrthographicCamera() ~ delete _;
private void RenderTexture(Texture viewedTexture)
{
Viewport vp = .(0, 0, _target.Width, _target.Height);
RenderCommand.SetViewport(vp);
// TODO: don't clear pink!
RenderCommand.Clear(_target, .Pink);
RenderCommand.Clear(_depth, .Depth, 1.0f, 0);
//_target.Bind();
_context.SetRenderTarget(_target);
_context.SetDepthStencilTarget(_depth);
_context.BindRenderTargets();
Vector2 textureSize = Vector2(viewedTexture.Width, viewedTexture.Height);
Vector2 zoomedTextureSize = textureSize * _zoom;
Vector2 targetSize = Vector2(_target.Width, _target.Height);
_effect.Variables["ColorOffset"].SetData(_colorOffset);
_effect.Variables["ColorScale"].SetData(_colorScale);
_effect.Variables["AlphaOffset"].SetData(_alphaOffset);
_effect.Variables["AlphaScale"].SetData(_alphaScale);
_camera.Left = 0;
_camera.Top = 0;
_camera.Right = targetSize.X;
_camera.Bottom = -targetSize.Y;
_camera.NearPlane = -5;
_camera.FarPlane = 5;
_camera.Update();
Renderer2D.BeginScene(_camera);
switch(_backgroundMode)
{
case .Black:
Renderer2D.DrawQuadPivotCorner(Vector3(0, 0, 1), targetSize, 0, .Black);
case .White:
Renderer2D.DrawQuadPivotCorner(Vector3(0, 0, 1), targetSize, 0, .White);
case .Checkerboard:
float quadSize = 50.0f;
Vector2 numQuads = (targetSize / 500f) * 10f;
for(float x = 0; x < numQuads.X; x++)
{
for(float y = 0; y < numQuads.Y; y++)
{
Renderer2D.DrawQuadPivotCorner(Vector3(x * quadSize, -y * quadSize, 1), quadSize.XX, 0, ((x + y) % 2 == 0) ? .White : .Gray);
}
}
break;
}
Renderer2D.EndScene();
var sampler = viewedTexture.SamplerState;
switch(_sampleMode)
{
case .Point:
viewedTexture.SamplerState = _samplerPoint;
case .Linear:
viewedTexture.SamplerState = _samplerLinear;
}
Renderer2D.BeginScene(_camera, .SortByTexture, _effect);
Renderer2D.DrawQuad(Vector3(_position * .(1, -1), 0), zoomedTextureSize, 0, viewedTexture);
Renderer2D.EndScene();
viewedTexture.SamplerState = sampler;
}
}
}
+3 -3
View File
@@ -1,5 +1,5 @@
FileVersion = 1 FileVersion = 1
Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", DirectXTK = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*"} Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*", box2d-beef = "*", "Beef.Linq" = "*"}
[Project] [Project]
Name = "GlitchyEngine" Name = "GlitchyEngine"
@@ -15,7 +15,7 @@ PreprocessorMacros = ["DEBUG", "PARANOID", "GE_WINDOWS"]
PreprocessorMacros = ["RELEASE", "GE_WINDOWS"] PreprocessorMacros = ["RELEASE", "GE_WINDOWS"]
[Configs.Release.Win64] [Configs.Release.Win64]
PreprocessorMacros = ["RELEASE", "GE_PROFILE"] PreprocessorMacros = ["IMGUI", "RELEASE", "GE_PROFILE"]
[Configs.Test.Win32] [Configs.Test.Win32]
PreprocessorMacros = ["TEST", "GE_WINDOWS"] PreprocessorMacros = ["TEST", "GE_WINDOWS"]
@@ -24,4 +24,4 @@ PreprocessorMacros = ["TEST", "GE_WINDOWS"]
PreprocessorMacros = ["TEST", "GE_WINDOWS"] PreprocessorMacros = ["TEST", "GE_WINDOWS"]
[Configs.Debug.Win64] [Configs.Debug.Win64]
PreprocessorMacros = ["DEBUG", "GE_PROFILE", "GE_PROFILE_RENDERER", "GE_PROFILE_RESOURCES"] PreprocessorMacros = ["DEBUG", "GE_PROFILE", "GE_PROFILE_RENDERER", "GE_PROFILE_RESOURCES", "IMGUI"]
+50 -11
View File
@@ -3,36 +3,42 @@ using GlitchyEngine.Events;
using GlitchyEngine.ImGui; using GlitchyEngine.ImGui;
using GlitchyEngine.Renderer; using GlitchyEngine.Renderer;
using GlitchyEngine.Debug; using GlitchyEngine.Debug;
using GlitchyEngine.Content;
namespace GlitchyEngine namespace GlitchyEngine
{ {
public class Application public abstract class Application
{ {
static Application s_Instance = null; static Application s_Instance = null;
private Window _window ~ delete _; private Window _window;
private RendererAPI _rendererApi ~ delete _; private RendererAPI _rendererApi;
private EffectLibrary _effectLibrary ~ delete _;
private bool _running = true; private bool _running = true;
private bool _isMinimized = false; private bool _isMinimized = false;
private LayerStack _layerStack = new LayerStack(); private LayerStack _layerStack;
#if IMGUI
private ImGuiLayer _imGuiLayer; private ImGuiLayer _imGuiLayer;
#endif
private GameTime _gameTime = new GameTime(true); private GameTime _gameTime;
private IContentManager _contentManager;
public bool IsRunning => _running; public bool IsRunning => _running;
public Window Window => _window; public Window Window => _window;
public EffectLibrary EffectLibrary => _effectLibrary; public IContentManager ContentManager => _contentManager;
public bool IsMinimized => _isMinimized; public bool IsMinimized => _isMinimized;
[Inline] [Inline]
public static Application Get() => s_Instance; public static Application Get() => s_Instance;
public Settings Settings {get; private set;} = new .() ~ delete _;
public this() public this()
{ {
Profiler.ProfileFunction!(); Profiler.ProfileFunction!();
@@ -40,32 +46,57 @@ namespace GlitchyEngine
Log.EngineLogger.Assert(s_Instance == null, "Tried to create a second application."); Log.EngineLogger.Assert(s_Instance == null, "Tried to create a second application.");
s_Instance = this; s_Instance = this;
_layerStack = new LayerStack();
_gameTime = new GameTime(true);
_window = new Window(.Default); _window = new Window(.Default);
_window.EventCallback = new => OnEvent; _window.EventCallback = new => OnEvent;
Input.Init();
_contentManager = InitContentManager();
// TODO: RenderAPI in RenderCommand initialisieren?
_rendererApi = new RendererAPI(); _rendererApi = new RendererAPI();
_rendererApi.Context = _window.Context; _rendererApi.Context = _window.Context;
SamplerStateManager.Init(); SamplerStateManager.Init();
// TODO: Rendercommmand in Renderer initialisieren?
RenderCommand.RendererAPI = _rendererApi; RenderCommand.RendererAPI = _rendererApi;
_effectLibrary = new EffectLibrary(); Renderer.Init();
Renderer.Init(_window.Context, _effectLibrary);
#if IMGUI
_imGuiLayer = new ImGuiLayer(); _imGuiLayer = new ImGuiLayer();
PushOverlay(_imGuiLayer); PushOverlay(_imGuiLayer);
#endif
GlitchyEngine.Settings.Load();
Settings.Apply();
} }
/// Initializes the content manager.
protected abstract IContentManager InitContentManager();
//{
// TODO: init default content manager?
//_contentManager = new ContentManager("./content");
//}
public ~this() public ~this()
{ {
Profiler.ProfileFunction!(); Profiler.ProfileFunction!();
delete _layerStack;
SamplerStateManager.Uninit(); SamplerStateManager.Uninit();
Renderer.Deinit(); Renderer.Deinit();
delete _contentManager;
delete _rendererApi;
delete _window;
delete _gameTime; delete _gameTime;
delete _layerStack;
} }
public void OnEvent(Event e) public void OnEvent(Event e)
@@ -131,13 +162,21 @@ namespace GlitchyEngine
if(allowFrame) if(allowFrame)
{ {
#if IMGUI
_imGuiLayer.ImGuiRender(); _imGuiLayer.ImGuiRender();
#endif
_window.Context.SwapChain.Present(); _window.Context.SwapChain.Present();
} }
} }
} }
/// Closes the applcation.
public void Close()
{
_running = false;
}
public void PushLayer(Layer ownLayer) public void PushLayer(Layer ownLayer)
{ {
Profiler.ProfileFunction!(); Profiler.ProfileFunction!();
+32
View File
@@ -2,6 +2,11 @@ using System.Collections;
namespace GlitchyEngine.Collections namespace GlitchyEngine.Collections
{ {
// TODO: TreeNode is very bare minimum
// Destructor?
// RemoveChild?
// Remove in enumerator?
public class TreeNode<T> public class TreeNode<T>
{ {
public T Value; public T Value;
@@ -44,5 +49,32 @@ namespace GlitchyEngine.Collections
return null; return null;
} }
public static ref T operator ->(TreeNode<T> node)
{
return ref node.Value;
}
}
static
{
public static mixin DeleteTreeAndChildren<T>(TreeNode<T> tree) where T : class, delete
{
InternalDeleteTreeAndChildren(tree);
}
private static void InternalDeleteTreeAndChildren<T>(TreeNode<T> tree) where T : class, delete
{
for (var child in tree.Children)
{
InternalDeleteTreeAndChildren(child);
}
delete tree.Value;
tree.Children.Clear();
delete tree;
}
} }
} }
+83
View File
@@ -0,0 +1,83 @@
using GlitchyEngine.Core;
using System;
using Bon;
using Bon.Integrated;
using System.Reflection;
using System.IO;
namespace GlitchyEngine.Content;
[BonTarget]
class Asset : RefCounter
{
private append String _identifier;
internal IContentManager _contentManager;
/// Gets the identifier of this asset.
/// @remarks The identifier is the name with which the asset was registered in the content manager.
/// This identifier can be used to request the Asset from the content manager.
public StringView Identifier
{
get => _identifier;
set
{
_contentManager?.UpdateAssetIdentifier(this, _identifier, value);
_identifier.Set(value);
// TODO: do we need to tell the content manager, that the name changed?
}
}
// TODO: do we need unmanaged assets? Probably not...
/// Gets the content manager that manages this asset; or null if this asset isn't managed.
public IContentManager ContentManager => _contentManager;
static this
{
gBonEnv.typeHandlers.Add(typeof(Asset),
((.)new => AssetSerialize, new => AssetDeserialize));
}
protected ~this()
{
// TODO: crash when _contentManager is deleted first...
// TODO: unregister from content manager
_contentManager?.UnmanageAsset(this);
}
static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state)
{
Log.EngineLogger.Assert(value.type == typeof(Asset));
let identifier = value.Get<Asset>().Identifier;
writer.String(identifier);
}
static Result<void> AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment, DeserializeValueState state)
{
Log.EngineLogger.Assert(value.type == typeof(Asset));
String identifier = scope .();
Deserialize.String!(reader, ref identifier, environment);
Asset asset = Application.Get().ContentManager.LoadAsset(identifier);
if (asset != null)
{
Asset oldAsset = value.Get<Asset>();
oldAsset.ReleaseRef();
value.Assign(asset);
return .Ok;
}
else
{
Deserialize.Error!("Invalid resource path", reader, value.type);
}
}
//gBonEnv.typeHandlers.Add(typeof(Resource<>),
// ((.)new => ResourceSerialize, (.)new => ResourceDeserialize));
}
+181
View File
@@ -0,0 +1,181 @@
using System;
using System.IO;
using xxHash;
using System.Collections;
using Bon;
namespace GlitchyEngine.Content
{
class ContentId
{
private readonly String _string;
private readonly XXH64_hash _hash;
public String String => _string;
public XXH64_hash Hash => _hash;
[AllowAppend]
public this(StringView id)
{
String str = append String(id);
_string = str;
_hash = xxHash.ComputeHash(id);
}
}
[BonTarget, BonPolyRegister]
abstract class AssetLoaderConfig
{
[BonIgnore]
protected bool _changed;
public bool Changed => _changed;
protected bool SetIfChanged<T>(ref T field, T value)
{
if (field == value)
return false;
field = value;
_changed = true;
return true;
}
}
interface IAssetLoader
{
static List<StringView> FileExtensions { get; }
AssetLoaderConfig GetDefaultConfig();
/// Loads the asset from the given data stream with the specified config.
/// @param file The stream containing the asset.
/// @param config The configuration which specifies the settings used to load the asset.
/// @param contentManager The content manager used to load the asset.
/// @returns The loaded asset.
Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager);
}
static
{
/// Loads the specified asset with the given contentManager or the current applications content manager.
public static T LoadAsset<T>(StringView assetIdentifier, IContentManager contentManager = null) where T : Asset
{
var contentManager;
if (contentManager == null)
contentManager = Application.Get().ContentManager;
Asset asset = contentManager.LoadAsset(assetIdentifier);
Log.EngineLogger.AssertDebug(asset is T);
return (T)asset;
}
}
interface IContentManager
{
/// Loads the given asset.
Asset LoadAsset(StringView assetIdentifier);
/// The content manager will manage the asset (e.g. provide it when LoadAsset is called with the assets identifier)
void ManageAsset(Asset asset);
/// The content manager will no longer manage the asset.
void UnmanageAsset(Asset asset);
// TODO: Maybe calling UnmanageAsset -> ManageAsset is enough....
/// Provides a method for the asset to tell its content manager that the identifer changed.
void UpdateAssetIdentifier(Asset asset, StringView oldIdentifier, StringView newIdentifier);
/// Returns a data stream for the given asset.
Stream GetStream(StringView assetIdentifier);
void RegisterAssetLoader<T>() where T : new, class, IAssetLoader;
void SetAsDefaultAssetLoader<T>(params Span<StringView> fileExtensions) where T : IAssetLoader;
//void GetFilePath(String outFilename, String filename);
//Stream GetFile(String filename);
}
class RuntimeContentManager : IContentManager
{
public this()
{
Runtime.NotImplemented();
}
public Asset LoadAsset(StringView assetIdentifier)
{
Runtime.NotImplemented();
}
public void ManageAsset(Asset asset)
{
Runtime.NotImplemented();
}
public void UnmanageAsset(Asset asset)
{
Runtime.NotImplemented();
}
public void UpdateAssetIdentifier(Asset asset, StringView oldIdentifier, StringView newIdentifier)
{
Runtime.NotImplemented();
}
public Stream GetStream(StringView assetIdentifier)
{
Runtime.NotImplemented();
}
public void RegisterAssetLoader<T>() where T : IAssetLoader where T : class where T : new
{
Runtime.NotImplemented();
}
public void SetAsDefaultAssetLoader<T>(params Span<StringView> fileExtensions) where T : IAssetLoader
{
Runtime.NotImplemented();
}
/*private String _contentRoot;
[AllowAppend]
public this(String contentRoot)
{
String cntRoot = append String(contentRoot);
_contentRoot = cntRoot;
Runtime.Assert(Directory.Exists(contentRoot), "Content root directory doesn't exist.");
}
public void GetFilePath(String outFilename, String filename)
{
Path.InternalCombine(outFilename, _contentRoot, filename);
}
public Stream GetFile(String filename)
{
String fullpath = scope .(_contentRoot.Length + 1 + filename.Length);
GetFilePath(fullpath, filename);
Log.EngineLogger.AssertDebug(File.Exists(fullpath), "File doesn't exist!");
FileStream stream = new FileStream();
var result = stream.Open(fullpath, .Read, .Read);
if (result case .Err(let error))
{
Log.EngineLogger.Error($"Failed to open file \"{fullpath}\". Error: {error}");
return null;
}
return stream;
}*/
}
}
+248 -22
View File
@@ -5,6 +5,7 @@ using GlitchyEngine.Math;
using GlitchyEngine.Renderer; using GlitchyEngine.Renderer;
using GlitchyEngine.Renderer.Animation; using GlitchyEngine.Renderer.Animation;
using GlitchyEngine.World; using GlitchyEngine.World;
using System.IO;
namespace GlitchyEngine.Content namespace GlitchyEngine.Content
{ {
@@ -12,8 +13,224 @@ namespace GlitchyEngine.Content
{ {
static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1); static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1);
public static void LoadModel(String filename, Effect validationEffect, Material material, EcsWorld world, public static Result<void> GetMeshNames(String filename, List<String> meshNames)
List<AnimationClip> outClips) {
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 GeometryBinding LoadMesh(Stream data, 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.
uint8[] rawData = new:ScopedAlloc! uint8[data.Length];
var dataReadResult = data.TryRead(rawData);
if (dataReadResult case .Err(let err))
{
Log.EngineLogger.Error($"Failed to read data from stream. Error: {err}");
}
CGLTF.Options options = .();
CGLTF.Data* modelData;
CGLTF.Result result = CGLTF.Parse(options, (Span<uint8>)rawData, out modelData);
if (!(result case .Success))
return null;
// TODO: one buffer can be used by multiple primitives, the content manager could manage the buffers
// TODO: load with content manager
result = CGLTF.LoadBuffers(options, modelData, (char8*)null);
//result = LoadBuffersWithContentManager(options, modelData, meshName, Application.Get().ContentManager);
GeometryBinding geoBinding = null;
for (var mesh in modelData.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(modelData);
return geoBinding;
}
private static CGLTF.Result LoadBuffersWithContentManager(CGLTF.Options options, CGLTF.Data* data, StringView fileName, IContentManager contentManager)
{
if (data.Buffers.Length > 0 && data.Buffers[0].Data == null && data.Buffers[0].Uri == null && !data.Bin.IsEmpty)
{
if ((uint)data.Bin.Length < data.Buffers[0].Size)
return .DataTooShort;
data.Buffers[0].Data = data.Bin.Ptr;
data.Buffers[0].DataFreeMethod = .None;
}
for (ref CGLTF.Buffer buffer in ref data.Buffers)
{
if (buffer.Data != null)
continue;
if (buffer.Uri == null)
continue;
StringView uri = StringView(buffer.Uri);
if (uri.StartsWith("data:"))
{
int commaIndex = uri.IndexOf(',');
//char* comma = strchr(uri, ',');
if (commaIndex == -1 || commaIndex >= 7 || uri.StartsWith(";base64"))
return .UnknownFormat;
StringView dataView = uri.Substring(commaIndex + 1);
#unwarn
CGLTF.Result loadBufferResult = CGLTF.LoadBuffersBase64(&options, buffer.Size, dataView.Ptr, &buffer.Data);
buffer.DataFreeMethod = .MemoryFree;
return loadBufferResult;
}
else
{
Runtime.NotImplemented();
// TODO: Request Buffer from Content Manager
//int index = uri.IndexOf("://");
//if (index == -1)
// return .UnknownFormat;
// TODO: load buffer file...
//CGLTF.Result res = //cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data);
//buffer.DataFreeMethod = cgltf_data_free_method_file_release;
/*if (res != cgltf_result_success)
{
return res;
}*/
}
}
/*
for (cgltf_size i = 0; i < data->buffers_count; ++i)
{
if (data->buffers[i].data)
{
continue;
}
const char* uri = data->buffers[i].uri;
if (uri == NULL)
{
continue;
}
if (strncmp(uri, "data:", 5) == 0)
{
const char* comma = strchr(uri, ',');
if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0)
{
cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data);
data->buffers[i].data_free_method = cgltf_data_free_method_memory_free;
if (res != cgltf_result_success)
{
return res;
}
}
else
{
return cgltf_result_unknown_format;
}
}
else if (strstr(uri, "://") == NULL && gltf_path)
{
cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data);
data->buffers[i].data_free_method = cgltf_data_free_method_file_release;
if (res != cgltf_result_success)
{
return res;
}
}
else
{
return cgltf_result_unknown_format;
}
}
*/
return .Success;
}
public static EcsEntity LoadModel(String filename, Material material, EcsWorld world,
List<AnimationClip> outClips, StringView entityName = StringView())
{ {
CGLTF.Options options = .(); CGLTF.Options options = .();
CGLTF.Data* data; CGLTF.Data* data;
@@ -25,38 +242,47 @@ namespace GlitchyEngine.Content
Log.EngineLogger.Assert(result == .Success, "Failed to load buffers"); Log.EngineLogger.Assert(result == .Success, "Failed to load buffers");
(EcsEntity entity, ?) = CreateEntity(world, entityName, .InvalidEntity);
for(var node in data.Scenes[0].Nodes) for(var node in data.Scenes[0].Nodes)
{ {
NodesToEntities(data, node, null, world, validationEffect, material, outClips); NodesToEntities(data, node, entity, world, material, outClips);
} }
CGLTF.Free(data); CGLTF.Free(data);
return entity;
} }
private static void NodesToEntities(CGLTF.Data* data, CGLTF.Node* node, Entity? parentEntity, EcsWorld world, Effect validationEffect, Material material, List<AnimationClip> clips) private static (EcsEntity Entity, TransformComponent* Transform) CreateEntity(EcsWorld world, StringView? name, EcsEntity parent)
{ {
Entity entity = world.NewEntity(); EcsEntity entity = world.NewEntity();
#if DEBUG
var nameComponent = world.AssignComponent<DebugNameComponent>(entity); var nameComponent = world.AssignComponent<DebugNameComponent>(entity);
if (node.Name != null) if (name != null && name.Value.Ptr != null)
{ {
nameComponent.SetName(StringView(node.Name)); nameComponent.SetName(name.Value);
} }
else else
{ {
nameComponent.SetName("Unnamed Node"); nameComponent.SetName("Unnamed Node");
} }
#endif
if(parentEntity.HasValue) /////TODO: !!!!!!!!REPORT!!!!!!!!!!!!!
{ // This works
var childParent = world.AssignComponent<ParentComponent>(entity); TransformComponent cmp = .();
childParent.Entity = parentEntity.Value; var childTransform = world.AssignComponent<TransformComponent>(entity, cmp);
// This trashes the stack
//var childTransform = world.AssignComponent<TransformComponent>(entity);
childTransform.Parent = parent;
return (entity, childTransform);
} }
var childTransform = world.AssignComponent<TransformComponent>(entity); private static void NodesToEntities(CGLTF.Data* data, CGLTF.Node* node, EcsEntity parentEntity, EcsWorld world, Material material, List<AnimationClip> clips)
{
(EcsEntity entity, TransformComponent* childTransform) = CreateEntity(world, node.Name == null ? null : StringView(node.Name), parentEntity);
if(node.HasMatrix) if(node.HasMatrix)
{ {
@@ -81,7 +307,7 @@ namespace GlitchyEngine.Content
} }
// Invert the Z-Axis of the root Node to convert the coordinate system from right-handed to left-handed // Invert the Z-Axis of the root Node to convert the coordinate system from right-handed to left-handed
if(parentEntity == null) if(parentEntity == .InvalidEntity)
childTransform.Scale *= .(1, 1, -1); childTransform.Scale *= .(1, 1, -1);
Skeleton skeleton = null; Skeleton skeleton = null;
@@ -100,7 +326,7 @@ namespace GlitchyEngine.Content
{ {
var mesh = world.AssignComponent<MeshComponent>(entity); var mesh = world.AssignComponent<MeshComponent>(entity);
using (var geo = PrimitiveToGeoBinding(node.Mesh.Primitives[0], validationEffect)) using (var geo = PrimitiveToGeoBinding(node.Mesh.Primitives[0]))
{ {
mesh.Mesh = geo; mesh.Mesh = geo;
} }
@@ -122,13 +348,13 @@ namespace GlitchyEngine.Content
{ {
for(var primitive in node.Mesh.Primitives) for(var primitive in node.Mesh.Primitives)
{ {
Entity meshEntity = world.NewEntity(); EcsEntity meshEntity = world.NewEntity();
var meshParent = world.AssignComponent<ParentComponent>(meshEntity); var meshParent = world.AssignComponent<ParentComponent>(meshEntity);
meshParent.Entity = entity; meshParent.Entity = entity;
var mesh = world.AssignComponent<MeshComponent>(meshEntity); var mesh = world.AssignComponent<MeshComponent>(meshEntity);
mesh.Mesh = PrimitiveToGeoBinding(primitive, validationEffect); mesh.Mesh = PrimitiveToGeoBinding(primitive);
if(skeleton == null) if(skeleton == null)
{ {
@@ -149,11 +375,11 @@ namespace GlitchyEngine.Content
for(var child in node.Children) for(var child in node.Children)
{ {
NodesToEntities(data, child, entity, world, validationEffect, material, clips); NodesToEntities(data, child, entity, world, material, clips);
} }
} }
public static GeometryBinding PrimitiveToGeoBinding(CGLTF.Primitive primitive, Effect validationEffect) public static GeometryBinding PrimitiveToGeoBinding(CGLTF.Primitive primitive)
{ {
GeometryBinding binding = new GeometryBinding(); GeometryBinding binding = new GeometryBinding();
@@ -265,7 +491,7 @@ namespace GlitchyEngine.Content
StringView strView = .(attribute.Name); StringView strView = .(attribute.Name);
// Remove number from end of name // Remove number from end of name
while((*(strView.EndPtr - 1)).IsDigit) while((*(strView.EndPtr - 1)).IsDigit || (*(strView.EndPtr - 1)) == '_')
{ {
strView.Length--; strView.Length--;
} }
@@ -318,7 +544,7 @@ namespace GlitchyEngine.Content
vertexElements[i] = elements[i]; vertexElements[i] = elements[i];
} }
VertexLayout layout = new VertexLayout(vertexElements, true, validationEffect.VertexShader); VertexLayout layout = new VertexLayout(vertexElements, true);
binding.SetVertexLayout(layout..ReleaseRefNoDelete()); binding.SetVertexLayout(layout..ReleaseRefNoDelete());
} }
+112
View File
@@ -0,0 +1,112 @@
using System;
using System.IO;
namespace GlitchyEngine.Core;
// TODO: make usable
class FilePath : IHashable
{
append String _path = .();
public bool IsRooted => Path.IsPathRooted(_path);
public this()
{
}
public this(StringView path)
{
Set(path);
}
public static implicit operator StringView(FilePath filePath) => filePath._path;
/// @param fixDirectorySeperators If true all alternative directory seperators will be replaced by the primary seperator.
/// @param resolveRelativeDirectories If true relative directories ('.' and '..') will be removed from the path.
public enum CanonicalizationFlags
{
FixDirectorySeperators = 1,
ResolveRelativeDirectories = _ << 1,
MakeFullPath = _ << 1
}
public void Set(StringView path, CanonicalizationFlags canonicalizationFlags = .FixDirectorySeperators | .ResolveRelativeDirectories)
{
_path.Append(path);
Canonicalize(canonicalizationFlags);
}
/// Converts the path to a canonicalized path.
public void Canonicalize(CanonicalizationFlags canonicalizationFlags = .FixDirectorySeperators | .ResolveRelativeDirectories)
{
if (canonicalizationFlags.HasFlag(.FixDirectorySeperators))
{
FixDirectorySeperators();
}
if (canonicalizationFlags.HasFlag(.ResolveRelativeDirectories))
{
ResolveRelativeDirectories();
}
if (canonicalizationFlags.HasFlag(.MakeFullPath))
{
MakeFullPath();
}
}
public void MakeFullPath()
{
if (IsRooted)
return;
String buffer = scope String(Path.[Friend]MaxPath);
Path.GetFullPath(_path, buffer);
_path..Clear().Append(buffer);
}
public void FixDirectorySeperators()
{
_path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
public void ResolveRelativeDirectories()
{
// find .
// find entire entry name
// remove entry name (if its only .)
/*for (char32 c in _path.DecodedChars)
{
if (c == '.')
{
}
}
for (StringView component in _path.Split(Path.DirectorySeparatorChar))
{
if (component == ".")
{
// . can be removed without replacement
}
else if (component == "..")
{
// .. can only be removed when not at the start of after another ..
// e.g. "../foo" and "../../foo" can't be changed
// but "foo/.." can become "foo"
// "foo/../.." can become ".."
}
}*/
}
public void Append(StringView newPath)
{
}
public int GetHashCode() => _path.GetHashCode();
}
+5 -1
View File
@@ -7,8 +7,12 @@ namespace GlitchyEngine.Core
* Implements the IDisposable interface so that it can be used with a using-Block so that the counter * Implements the IDisposable interface so that it can be used with a using-Block so that the counter
* will be decremented automatically after leaving the block. * will be decremented automatically after leaving the block.
*/ */
public class RefCounter : System.RefCounted, IDisposable public class RefCounter : RefCounted, IDisposable
{ {
protected ~this()
{
}
public void Dispose() public void Dispose()
{ {
ReleaseRef(); ReleaseRef();
+53
View File
@@ -0,0 +1,53 @@
using Bon;
using System;
using Bon.Integrated;
using System.Collections;
namespace GlitchyEngine.Core
{
[BonTarget]
struct UUID : IHashable
{
[BonInclude]
private uint64 _uuid;
private static Random s_Random = new .() ~ delete _;
static this()
{
gBonEnv.typeHandlers.Add(typeof(UUID),
((.)new => Serialize, (.)new => Deserialize));
}
/// Creates a new random UUID.
public this()
{
_uuid = s_Random.NextU64();
}
/// Creates a new UUID with the given value.
public this(uint64 uuid)
{
_uuid = uuid;
}
public int GetHashCode()
{
return (int)_uuid;
}
static void Serialize(BonWriter writer, ValueView val, BonEnvironment env, SerializeValueState state)
{
UUID uuid = *(UUID*)val.dataPtr;
Bon.Integrated.Serialize.[Friend]Integer(typeof(uint64), writer, ValueView(typeof(uint64), &uuid._uuid));
}
public static Result<void> Deserialize(BonReader reader, ValueView val, BonEnvironment env, DeserializeValueState state)
{
Bon.Integrated.Deserialize.[Friend]Integer!(typeof(uint64), reader, val);
return .Ok;
}
}
}
+27
View File
@@ -29,6 +29,33 @@ namespace GlitchyEngine.Events
} }
} }
public class RawMouseMovedEvent : Event, IEvent
{
private int32 _mouseX, _mouseY;
public override EventType EventType => .MouseMoved;
public override StringView Name => "RawMouseMoved";
public override EventCategory Category => .Input | .Mouse;
public static EventType StaticType => .MouseMoved;
public int32 PositionX => _mouseX;
public int32 PositionY => _mouseY;
public this(int32 x, int32 y)
{
_mouseX = x;
_mouseY = y;
}
public override void ToString(String strBuffer)
{
strBuffer.AppendF("RawMouseMovedEvent: Position: ({}, {})", _mouseX, _mouseY);
}
}
public class MouseScrolledEvent : Event, IEvent public class MouseScrolledEvent : Event, IEvent
{ {
private int32 _xOffset, _yOffset; private int32 _xOffset, _yOffset;
@@ -0,0 +1,37 @@
using System;
namespace Bon.Integrated
{
extension Serialize
{
public static void Value<T>(BonWriter writer, StringView identifier, in T value, BonEnvironment env = gBonEnv)
{
writer.Identifier(identifier);
Serialize.Value(writer, ValueView(typeof(T), &value), env);
}
public static void Value<T>(BonWriter writer, in T value, BonEnvironment env = gBonEnv)
{
Serialize.Value(writer, ValueView(typeof(T), &value), env);
}
}
extension Deserialize
{
public static Result<void> Value<T>(BonReader reader, StringView identifier, out T value, BonEnvironment env = gBonEnv)
{
value = ?;
if (Try!(reader.Identifier()) != identifier)
return .Err;
return Deserialize.Value(reader, ValueView(typeof(T), &value), env);
}
public static Result<void> Value<T>(BonReader reader, out T value, BonEnvironment env = gBonEnv)
{
value = ?;
return Deserialize.Value(reader, ValueView(typeof(T), &value), env);
}
}
}
@@ -0,0 +1,47 @@
using System.Diagnostics;
namespace System.IO;
extension Path
{
public static mixin GetScopedFullPath(String path)
{
String fullPath = scope:: String(Path.MaxPath);
Path.GetFullPath(path, fullPath);
fullPath
}
/// Opens the file browser and selects the specified file.
/// @param path The path of the file to select.
public static extern Result<void> OpenFolderAndSelectItem(String path);
/// Opens the file browser in the given directory.
/// @param directory The directory to show in the file browser.
public static extern Result<void> OpenFolder(String directory);
/// Shows a dialog in which the user can select which program to open the given file with.
/// @param The Path of the file to open.
public static extern Result<void> OpenWithDialog(String filePath);
public static void Fixup(String path)
{
path.Replace(AltDirectorySeparatorChar, DirectorySeparatorChar);
path.Replace(scope $".{DirectorySeparatorChar}", "");
path.Replace(scope $"{DirectorySeparatorChar}.", "");
if (path.StartsWith(DirectorySeparatorChar))
path.Remove(0, 1);
}
public static void Combine(String target, params StringView[] components)
{
for (var component in components)
{
if ((target.Length > 0) && (!target.EndsWith("\\")) && (!target.EndsWith("/")) &&
(!component.StartsWith("\\")) && (!component.StartsWith("/")))
target.Append(Path.DirectorySeparatorChar);
target.Append(component);
}
}
}
@@ -0,0 +1,13 @@
namespace System
{
extension Runtime
{
[NoReturn, Warn("The method is not implemented.")]
#unwarn
public static void NotImplemented(String filePath = Compiler.CallerFilePath, int line = Compiler.CallerLineNum)
{
String failStr = scope .()..AppendF("Not Implemented at line {} in {}", line, filePath);
Internal.FatalError(failStr, 1);
}
}
}
@@ -0,0 +1,27 @@
namespace System
{
extension String
{
[Inline]
public void CopyTo(Span<char8> target)
{
CopyTo(target.Ptr, target.Length);
}
[Inline]
public void CopyTo(char8* target, int targetLength)
{
int copiedChars = Math.Min(targetLength - 1, Length);
Internal.MemCpy(target, Ptr, copiedChars);
target[copiedChars] = '\0';
}
/// Converts camel case and delimiter-separated words to normal words.
public void ToHumanReadable()
{
// TODO!
}
}
}

Some files were not shown because too many files have changed in this diff Show More