30 Commits
Author SHA1 Message Date
Simon Lübeß 65e2248b30 Implemented basic ECS 2021-04-09 19:09:32 +02:00
Simon Lübeß a89657591f Removed epsilon value from camera
(bug fixed in DirectX-beef)
2021-04-09 19:08:23 +02:00
Simon Lübeß cd929080f4 Added TextureCube 2021-03-26 19:12:53 +01:00
Simon Lübeß 79e160348b Added larger epsilon to perspective camera 2021-03-26 19:12:41 +01:00
Simon Lübeß 5a6abc9b8b Added explicit conversion from float to Vector 2021-03-26 19:12:08 +01:00
Simon Lübeß 0f62637662 Generate fallback entries for missing block textures 2021-03-25 20:43:43 +01:00
Simon Lübeß 1d7029eb0f Made TextureAtlas platform independent 2021-03-25 16:33:33 +01:00
Simon Lübeß ceb4cef034 Added protected function to create Texture2D from raw data 2021-03-25 16:31:03 +01:00
Simon Lübeß 5873f12643 Added textures for Blocks 2021-03-24 14:44:02 +01:00
Simon Lübeß 1b00fa67a5 Added GE_D3D11 flag 2021-03-23 19:07:57 +01:00
Simon Lübeß dfeed4a5c9 Moved ImplBind implementation from Texture2D to Texture 2021-03-23 19:07:41 +01:00
Simon Lübeß ec83dfa21d Added lodepng-beef 2021-03-23 18:25:30 +01:00
Simon Lübeß d8f6be1358 Added refcounting to RasterizerState 2021-03-22 17:58:57 +01:00
Simon Lübeß 72400e873d Decoupled Chunk data loading/generation and geometry generation 2021-03-19 18:06:40 +01:00
Simon Lübeß a0373366f6 Workaround for CompTime crash 2021-03-18 17:23:05 +01:00
Simon Lübeß 8320689d3e Geometry Generation now in Block 2021-03-18 17:22:41 +01:00
Simon Lübeß 4e87f5f144 Blocks now represented by Objects instead of ints 2021-03-06 20:46:08 +01:00
Simon Lübeß 3449463282 Added EffectLibrary 2021-03-06 18:54:46 +01:00
Simon Lübeß 623ada4b2f Added chunk editing 2021-03-04 13:05:57 +01:00
Simon Lübeß 4e377bb117 Added basic raw input support 2021-03-04 13:05:26 +01:00
Simon Lübeß 08613b9e82 Overcomplicated chunk loading
Implemented an overly complicated mechanism into LoadChunk to be able to passively wait for chunks to be loaded by another thread.
2021-03-03 19:00:34 +01:00
Simon Lübeß 4b032d2c7e Added Semaphore and Mutex 2021-03-03 18:57:42 +01:00
Simon Lübeß 9d52f77b43 Switched to Int32 vector from Point 2021-03-03 16:34:25 +01:00
Simon Lübeß 7a777bb55e Added Int32 vectors 2021-03-03 16:33:58 +01:00
Simon Lübeß f87c0e2ecd Raycasting world now returns intersection location and face 2021-03-02 22:25:38 +01:00
Simon Lübeß 488d2a55c4 Added voxel raycasting and voxel chunk access 2021-03-02 22:07:21 +01:00
Simon Lübeß cbfbd449ff Start of block raycasting 2021-02-26 10:58:11 +01:00
Simon Lübeß 4dcf1b5bec Updated .gitignore and added Chunk loading and saving 2021-02-24 20:07:02 +01:00
Simon Lübeß afc7594df9 initial VoxelGame commit 2021-02-24 14:55:12 +01:00
Simon Lübeß 0461669e13 Added rudimentary DepthStencilTarget 2021-02-21 14:45:50 +01:00
311 changed files with 7707 additions and 26354 deletions
+2 -1
View File
@@ -4,4 +4,5 @@ build/
.vs/
recovery/
vendor/directx
imgui.ini
Sandbox/imgui.ini
Sandbox/worlds
+6 -27
View File
@@ -1,33 +1,12 @@
[submodule "GlitchyEngine/vendor/directx"]
path = GlitchyEngine/vendor/directx
url = https://github.com/aharabada/directx-beef.git
[submodule "GlitchyEngine/vendor/imgui-beef"]
path = GlitchyEngine/vendor/imgui-beef
url = https://github.com/aharabada/imgui-beef
[submodule ".\\GlitchyEngine\\vendor\\DirectXTK"]
path = .\\GlitchyEngine\\vendor\\DirectXTK
url = https://github.com/aharabada/DirectXTK-beef.git
[submodule "vendor/lodepng-beef"]
path = vendor/lodepng-beef
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/gltf"]
path = GlitchyEngine/vendor/gltf
url = https://github.com/aharabada/cgltf-beef.git
[submodule "GlitchyEngine/vendor/msdfgen"]
path = GlitchyEngine/vendor/msdfgen
url = https://github.com/aharabada/msdfgen-beef.git
[submodule "GlitchyEngine/vendor/imgui"]
path = GlitchyEngine/vendor/imgui
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
+2 -6
View File
@@ -1,9 +1,5 @@
FileVersion = 1
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"]}
Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, ImGui = {Path = "GlitchyEngine/vendor/imgui-beef/ImGui"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplWin32"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui-beef/ImGuiImplDX11"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}}
[Workspace]
StartupProject = "GlitchyEditor"
[Configs.Debug.Win64]
AllocStackTraceDepth = 12
StartupProject = "Sandbox"
+6 -6
View File
@@ -50,10 +50,10 @@ namespace GlitchLog
public this()
{
//Debug.Assert(Debug.IsDebuggerPresent, "The DebugLogger requires a debugger to be present.");
Runtime.Assert(Debug.IsDebuggerPresent, "The DebugLogger requires a debugger to be present.");
}
#if GL_NOLOG || GL_LOG_NOTRACE
#if GL_NOLOG || GL_NOTRACE
[SkipCall]
#endif
[Inline]
@@ -62,7 +62,7 @@ namespace GlitchLog
InternalLog(.Trace, format, params args);
}
#if GL_NOLOG || GL_LOG_NOINFO
#if GL_NOLOG || GL_NOINFO
[SkipCall]
#endif
[Inline]
@@ -71,7 +71,7 @@ namespace GlitchLog
InternalLog(.Info, format, params args);
}
#if GL_NOLOG || GL_LOG_NOWARNING
#if GL_NOLOG || GL_NOWARNING
[SkipCall]
#endif
[Inline]
@@ -80,7 +80,7 @@ namespace GlitchLog
InternalLog(.Warning, format, params args);
}
#if GL_NOLOG || GL_LOG_NOERROR
#if GL_NOLOG || GL_NOERROR
[SkipCall]
#endif
[Inline]
@@ -89,7 +89,7 @@ namespace GlitchLog
InternalLog(.Error, format, params args);
}
#if GL_NOLOG || GL_LOG_NOCRITICAL
#if GL_NOLOG || GL_NOCRITICAL
[SkipCall]
#endif
[Inline]
-7
View File
@@ -1,7 +0,0 @@
FileVersion = 1
Dependencies = {corlib = "*", GlitchLog = "*", GlitchyEngine = "*"}
[Project]
Name = "GlitchyEditor"
TargetType = "BeefGUIApplication"
StartupObject = "GlitchyEngine.Program"
Binary file not shown.
Binary file not shown.
@@ -1,4 +0,0 @@
{
AssetLoader = "ModelAssetLoader",
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
}
Binary file not shown.
@@ -1,4 +0,0 @@
{
AssetLoader = "ModelAssetLoader",
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
}
-154
View File
@@ -1,154 +0,0 @@
{
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
}
}
]
}
@@ -1,212 +0,0 @@
{
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
}
}
]
}
@@ -1,305 +0,0 @@
{
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
@@ -1,52 +0,0 @@
//=================================================================================================
//
// 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;
}
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,16 +0,0 @@
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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,33 +0,0 @@
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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,28 +0,0 @@
#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
}
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
-78
View File
@@ -1,78 +0,0 @@
/*
* 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__
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,90 +0,0 @@
#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__
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,39 +0,0 @@
#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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,88 +0,0 @@
#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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,31 +0,0 @@
cbuffer Constants : register(b0)
{
float4x4 ViewProjection;
float4 Color;
};
struct VS_Input
{
float4 Position : POSITION;
};
struct PS_Input
{
float4 Position : SV_Position;
};
PS_Input VS(VS_Input input)
{
PS_Input output;
output.Position = mul(ViewProjection, input.Position);
return output;
}
float4 PS(PS_Input input) : SV_Target0
{
return Color;
}
#pragma Effect[VS=VS; PS=PS]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,93 +0,0 @@
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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
-159
View File
@@ -1,159 +0,0 @@
#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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,126 +0,0 @@
#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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,74 +0,0 @@
#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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
@@ -1,52 +0,0 @@
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]
@@ -1,4 +0,0 @@
{
AssetLoader = "EffectAssetLoader",
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

@@ -1,23 +0,0 @@
{
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.
@@ -1,23 +0,0 @@
{
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.

Before

Width:  |  Height:  |  Size: 119 B

@@ -1,23 +0,0 @@
{
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.

Before

Width:  |  Height:  |  Size: 10 MiB

@@ -1,23 +0,0 @@
{
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.

Before

Width:  |  Height:  |  Size: 3.3 MiB

@@ -1,23 +0,0 @@
{
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.

Before

Width:  |  Height:  |  Size: 7.4 MiB

@@ -1,23 +0,0 @@
{
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.

Before

Width:  |  Height:  |  Size: 3.0 MiB

@@ -1,24 +0,0 @@
{
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
}
}
}
}
@@ -1,28 +0,0 @@
{
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
}
]
}
@@ -1,4 +0,0 @@
{
AssetLoader = "MaterialAssetLoader",
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

@@ -1,23 +0,0 @@
{
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.
@@ -1,23 +0,0 @@
{
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.

Before

Width:  |  Height:  |  Size: 258 B

@@ -1,22 +0,0 @@
{
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
@@ -1,121 +0,0 @@
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
@@ -1,343 +0,0 @@
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();
}
@@ -1,15 +0,0 @@
namespace GlitchyEditor.Assets;
abstract class AssetPropertiesEditor
{
private AssetFile _asset;
public AssetFile Asset => _asset;
public this(AssetFile asset)
{
_asset = asset;
}
public abstract void ShowEditor();
}
@@ -1,101 +0,0 @@
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
@@ -1,10 +0,0 @@
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);
}
@@ -1,8 +0,0 @@
using System.IO;
namespace GlitchyEditor.Assets;
interface IReloadingAssetLoader
{
public void ReloadAsset(AssetFile assetFile, Stream data);
}
@@ -1,530 +0,0 @@
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;
}
}
@@ -1,51 +0,0 @@
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);
}
}
@@ -1,6 +0,0 @@
using System;
namespace Bon.Integrated;
extension Serialize
{
}
@@ -1,352 +0,0 @@
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;
}
}
@@ -1,574 +0,0 @@
using ImGui;
using GlitchyEngine.World;
using System;
using GlitchyEngine.Math;
using System.Collections;
using GlitchyEngine.Renderer;
using GlitchyEngine;
using GlitchyEngine.Content;
namespace GlitchyEditor.EditWindows
{
using internal GlitchyEngine.World.TransformComponent;
class ComponentEditWindow : EditorWindow
{
public const String s_WindowTitle = "Components";
private EntityHierarchyWindow _entityHierarchyWindow;
private List<(Type, ComponentAttribute attribute)> _componentTypes = new .() ~ delete _;
public this(EntityHierarchyWindow entityHierarchyWindow)
{
_entityHierarchyWindow = entityHierarchyWindow;
for (let type in Type.Types)
{
if (let componentAttribute = type.GetCustomAttribute<ComponentAttribute>())
{
_componentTypes.Add((type, componentAttribute));
}
}
}
protected override void InternalShow()
{
ImGui.PushStyleVar(.WindowMinSize, ImGui.Vec2(1000, 100));
if(!ImGui.Begin(s_WindowTitle, &_open, .None))
{
ImGui.PopStyleVar();
ImGui.End();
return;
}
if(_entityHierarchyWindow.SelectedEntities.Count == 1)
{
Entity entity = _entityHierarchyWindow.SelectedEntities.Front;
ShowComponents(entity);
}
ImGui.PopStyleVar();
ImGui.End();
}
private void ShowComponents(Entity entity)
{
ShowNameComponentEditor(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();
}
}
if (nodeOpen)
{
showComponentEditor(entity, component);
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;
if(component != null)
{
name = component.DebugName;
}
else
{
name = scope:: $"Entity {entity.Handle.[Friend]Index}";
}
// Copy name to buffer
Internal.MemCpy(&nameBuffer, name.Ptr, Math.Min(nameBuffer.Count, name.Length));
if(ImGui.InputText("Name", &nameBuffer, nameBuffer.Count))
{
if(component == null)
{
component = entity.AddComponent<DebugNameComponent>();
}
component.DebugName.Clear();
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 String[] strings = new String[]("Orthographic", "Perspective", "Perspective (Infinite)") ~ delete _;
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;
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))
{
if (!entity.HasComponent<TComponent>())
entity.AddComponent<TComponent>();
}
}
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))
{
if (ImGui.InputText("##search_component_name", &searchBuffer, (.)searchBuffer.Count))
{
searchFilter = .(&searchBuffer);
}
ImGui.Separator();
ShowComponentButton<CameraComponent>("Camera");
ShowComponentButton<SpriterRendererComponent>("Sprite Renderer");
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.EndCombo();
}
ImGui.PopItemWidth();
}
}
}
@@ -1,338 +0,0 @@
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.");
}
}
}
}
@@ -1,32 +0,0 @@
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();
}
}
@@ -1,532 +0,0 @@
using GlitchyEngine.Collections;
using GlitchyEngine.World;
using ImGui;
using System;
using System.Collections;
using GlitchyEngine;
namespace GlitchyEditor.EditWindows
{
using internal GlitchyEditor;
/// A window for viewing and editing the scene hierarchy
class EntityHierarchyWindow : EditorWindow
{
public const String s_WindowTitle = "Entity Hierarchy";
/// Buffer for the entity search string.
private char8[64] _entitySearchChars;
private Scene _scene;
private List<Entity> _selectedEntities = new .() ~ delete _;
public List<Entity> SelectedEntities => _selectedEntities;
public this(Editor editor, Scene scene)
{
_editor = editor;
SetContext(scene);
}
public void SetContext(Scene scene)
{
ClearEntitySelection();
_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))
{
ImGui.End();
return;
}
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();
if ((ImGui.IsMouseDown(.Left) || ImGui.IsMouseDown(.Right)) && !ImGui.IsAnyItemHovered() && !ImGui.GetIO().KeyCtrl && ImGui.IsWindowHovered(.AllowWhenBlockedByPopup))
ClearEntitySelection();
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()
{
if(ImGui.BeginMenuBar())
{
Show_ContextMenu_Create(true, true, true);
Show_ContextMenu_Delete();
if(ImGui.MenuItem("Delete", null, false, !_selectedEntities.IsEmpty) ||
(Input.IsKeyPressed(.Delete) && ImGui.IsWindowHovered()))
{
DeleteSelectedEntities();
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Deletes the selected Entities and their children.");
ImGui.Text("Search:");
ImGui.InputText(String.Empty, &_entitySearchChars, (.)_entitySearchChars.Count);
ImGui.EndMenuBar();
}
}
/// 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)
{
String name = null;
var nameComponent = tree.Value.GetComponent<DebugNameComponent>();
if(nameComponent != null)
{
name = nameComponent.DebugName;
}
else
{
name = scope:: $"Entity {(tree.Value.Handle.[Friend]Index)}";
}
ImGui.TreeNodeFlags flags = .OpenOnArrow | .DefaultOpen | .SpanAvailWidth;
if(tree.Children.Count == 0)
flags |= .Leaf;
bool inSelectedList = IsEntitySelected(tree.Value);
if(inSelectedList)
flags |= .Selected;
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())
{
ImGui.SetDragDropPayload("DND_Entity", &tree.Value, sizeof(Entity));
ImGui.Text(name);
ImGui.EndDragDropSource();
}
if(ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("DND_Entity");
if(payload != null)
{
Log.ClientLogger.AssertDebug(payload.DataSize == sizeof(Entity));
Entity movedEntity = *(Entity*)payload.Data;
bool dropLegal = true;
Entity walker = tree.Value;
// make sure the dropped entity is not a parent of the entity we dropped it on.
while(true)
{
var parentTransform = walker.GetComponent<TransformComponent>();
if(parentTransform.Parent == .InvalidEntity)
{
dropLegal = true;
break;
}
else if(parentTransform.Parent == movedEntity.Handle)
{
dropLegal = false;
break;
}
walker = .(parentTransform.Parent, _scene);
}
if(dropLegal)
{
var movedEntityTransform = movedEntity.GetComponent<TransformComponent>();
movedEntityTransform.Parent = tree.Value.Handle;
}
}
ImGui.EndDragDropTarget();
}
bool clicked = ImGui.IsItemClicked(.Left);
bool clickedRight = ImGui.IsItemClicked(.Right);
if(isOpen)
{
for(var child in tree.Children)
{
ImGuiPrintEntityTree(child);
}
ImGui.TreePop();
}
if (clicked || clickedRight)
{
if (inSelectedList && !clickedRight)
{
DeselectEntity(tree.Value);
inSelectedList = false;
}
else
{
SelectEntity(tree.Value, !ImGui.GetIO().KeyCtrl && !clickedRight);
inSelectedList = true;
}
}
}
private void ShowEntityHierarchy()
{
StringView searchString = StringView(&_entitySearchChars);
if(searchString.IsWhiteSpace)
{
// Show entity hierarchy as tree
TreeNode<Entity> root = scope .(Entity(.InvalidEntity, _scene));
TreeNode<Entity> InsertIntoTree(Entity entity)
{
var transform = entity.GetComponent<TransformComponent>();
if(transform == null)
{
return root.AddChild(entity);
}
else
{
var parentEntity = Entity(transform.Parent, _scene);
var parentNode = root.FindNode(parentEntity);
if(parentNode == null)
parentNode = InsertIntoTree(parentEntity);
return parentNode.AddChild(entity);
}
}
for(var entityId in _scene.[Friend]_ecsWorld.Enumerate())
{
Entity entity = .(entityId, _scene);
//if (!entity.HasComponent<EditorComponent>())
InsertIntoTree(entity);
}
if(ImGui.TreeNodeEx("Scene", .DefaultOpen))
{
if(ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("DND_Entity");
if(payload != null)
{
Log.ClientLogger.AssertDebug(payload.DataSize == sizeof(Entity));
Entity movedEntity = *(Entity*)payload.Data;
// Also mark transform as dirty
var transformComponent = movedEntity.GetComponent<TransformComponent>();
transformComponent.Parent = .InvalidEntity;
//transformComponent?.IsDirty = true;
}
ImGui.EndDragDropTarget();
}
for(var child in root.Children)
{
ImGuiPrintEntityTree(child);
}
ImGui.TreePop();
}
}
// Otherwise
else
{
// Show search results as flat list
List<StringView> searchTokens = new:ScopedAlloc! .(searchString.Split(' ', .RemoveEmptyEntries));
worldEnumeration:
for(var entityId in _scene.[Friend]_ecsWorld.Enumerate())
{
Entity entity = .(entityId, _scene);
String name = null;
var nameComponent = entity.GetComponent<DebugNameComponent>();
if(nameComponent != null)
{
name = nameComponent.DebugName;
}
else
{
name = scope:worldEnumeration $"Entity {entityId.[Friend]Index}";
}
StringView nameView = StringView(name);
for(var token in searchTokens)
{
if(nameView.IndexOf(token, true) == -1)
{
continue worldEnumeration;
}
}
ImGuiPrintEntityTree(scope .(entity));
}
}
}
}
}
@@ -1,123 +0,0 @@
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();
}
}
@@ -1,330 +0,0 @@
using System;
using ImGui;
using GlitchyEngine.Renderer;
using GlitchyEngine.Math;
using GlitchyEngine;
using GlitchyEngine.World;
using ImGuizmo;
using GlitchyEngine.Events;
namespace GlitchyEditor.EditWindows
{
class SceneViewportWindow : EditorWindow
{
public const String s_WindowTitle = "Scene";
private ImGui.Vec2 _oldViewportSize = .(100, 100);
private bool _viewPortChanged;
/// True if the cursor wrapped from one side of the viewport to the other last frame.
private bool _wrappedCursor;
private RenderTargetGroup _renderTarget ~ _?.ReleaseRef();
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 uint32 SelectedEntityId {get; private set; }
public bool SelectionChanged { get; private set; }
public Vector2 ViewportSize => (Vector2)_oldViewportSize;
// Occurs when the viewport is resized.
public Event<EventHandler<Vector2>> ViewportSizeChanged ~ _.Dispose();
// Occurs when an entity was clicked.
public Event<EventHandler<uint32>> EntityClicked ~ _.Dispose();
/// The render target that is shown in the viewport window.
public RenderTargetGroup RenderTarget
{
get => _renderTarget;
set
{
if(_renderTarget == value)
return;
SetReference!(_renderTarget, value);
}
}
/// Gets or sets whether the editor functionality (gizmo, picking etc.) is enabled.
public bool EditorMode { get; set; } = true
public this(Editor editor)
{
_editor = editor;
}
protected override void InternalShow()
{
ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(1, 1));
defer ImGui.PopStyleVar();
if(!ImGui.Begin(s_WindowTitle, &_open, .NoScrollbar | .MenuBar))
{
ImGui.End();
return;
}
let viewportSize = ImGui.GetContentRegionAvail();
if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton))
{
let currentWindow = ImGui.GetCurrentWindow();
ImGui.FocusWindow(currentWindow);
}
_hasFocus = ImGui.IsWindowFocused();
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)
{
ImGui.Image(_renderTarget.GetViewBinding(0), viewportSize);
//ImGui.Image(_editor.CurrentCamera.RenderTarget.GetViewBinding(0), viewportSize);
//ImGui.Image(_editor.CurrentScene.[Friend]_compositeTarget.GetViewBinding(0), viewportSize);
}
if (EditorMode)
{
HandleDropTarget();
bool gizmoUsed = DrawImGuizmo(viewportSize);
MousePicking(viewportSize, gizmoUsed);
}
ImGui.End();
if(_oldViewportSize != viewportSize)
{
ViewportSizeChanged.Invoke(this, (Vector2)viewportSize);
_viewPortChanged = true;
_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();
}
}
}
-73
View File
@@ -1,73 +0,0 @@
using GlitchyEngine.World;
using ImGui;
using System;
using System.Collections;
using GlitchyEngine.Collections;
using GlitchyEditor.EditWindows;
using GlitchyEngine;
using GlitchyEditor.Assets;
namespace GlitchyEditor
{
class Editor
{
private Scene _scene;
private EditorContentManager _contentManager;
private EntityHierarchyWindow _entityHierarchyWindow ~ delete _;
private ComponentEditWindow _componentEditWindow ~ delete _;
private SceneViewportWindow _sceneViewportWindow~ delete _;
private ContentBrowserWindow _contentBrowserWindow ~ delete _;
private PropertiesWindow _propertiesWindow ~ delete _;
public Scene CurrentScene
{
get => _scene;
set
{
_scene = value;
_entityHierarchyWindow.SetContext(_scene);
}
}
public EditorContentManager ContentManager => _contentManager;
public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow;
public ComponentEditWindow ComponentEditWindow => _componentEditWindow;
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
public this(Scene scene, EditorContentManager contentManager)
{
_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()
{
_sceneViewportWindow.Show();
_entityHierarchyWindow.Show();
_componentEditWindow.Show();
_contentBrowserWindow.Show();
_propertiesWindow.Show();
}
}
}
-47
View File
@@ -1,47 +0,0 @@
using System;
using GlitchyEngine;
using GlitchyEngine.Content;
using GlitchyEditor.Assets;
namespace GlitchyEditor
{
class EditorApp : Application
{
EditorContentManager _contentManager;
public this()
{
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")]
public static Application CreateApplication()
{
return new EditorApp();
}
}
}
-449
View File
@@ -1,449 +0,0 @@
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
@@ -1,56 +0,0 @@
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;
}
}
}
-692
View File
@@ -1,692 +0,0 @@
using System;
using ImGui;
using GlitchyEngine;
using GlitchyEditor.EditWindows;
using GlitchyEngine.Events;
using GlitchyEngine.ImGui;
using GlitchyEngine.Math;
using GlitchyEngine.Renderer;
using GlitchyEngine.World;
using GlitchyEngine.Content;
using System.Collections;
using GlitchyEngine.Renderer.Animation;
using System.IO;
using GlitchyEngine.Core;
using GlitchyEditor.Assets;
namespace GlitchyEditor
{
class EditorLayer : Layer
{
RasterizerState _rasterizerState ~ _?.ReleaseRef();
RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef();
GraphicsContext _context ~ _.ReleaseRef();
BlendState _alphaBlendState ~ _.ReleaseRef();
BlendState _opaqueBlendState ~ _.ReleaseRef();
DepthStencilState _depthStencilState ~ _.ReleaseRef();
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 _;
RenderTargetGroup _cameraTarget ~ _.ReleaseRef();
RenderTargetGroup _viewportTarget ~ _.ReleaseRef();
SettingsWindow _settingsWindow = new .() ~ delete _;
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;
//InitContentManager();
_contentManager = contentManager;
InitGraphics();
_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();
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()
{
_context = Application.Get().Window.Context..AddRef();
RasterizerStateDescription rsDesc = .(.Solid, .Back, true);
_rasterizerState = new RasterizerState(rsDesc);
rsDesc.FrontCounterClockwise = false;
_rasterizerStateClockWise = new RasterizerState(rsDesc);
BlendStateDescription blendDesc = .();
blendDesc.RenderTarget[0] = .(true, .SourceAlpha, .InvertedSourceAlpha, .Add, .SourceAlpha, .InvertedSourceAlpha, .Add, .All);
_alphaBlendState = new BlendState(blendDesc);
_opaqueBlendState = new BlendState(.Default);
DepthStencilStateDescription dsDesc = .();
_depthStencilState = new DepthStencilState(dsDesc);
_cameraTarget = new RenderTargetGroup(.(){
Width = 100,
Height = 100,
ColorTargetDescriptions = TargetDescription[](
.(.R16G16B16A16_Float),
.(.R32_UInt)
),
DepthTargetDescription = .(.D24_UNorm_S8_UInt)
});
_viewportTarget = new RenderTargetGroup(.()
{
Width = 100,
Height = 100,
ColorTargetDescriptions = TargetDescription[](
.(.R8G8B8A8_UNorm))
});
_editorIcons = new EditorIcons("Textures/EditorIcons.dds", .(64, 64));
_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()
{
_editor = new Editor(_scene, _contentManager);
_editor.SceneViewportWindow.ViewportSizeChanged.Add(new (s, e) => ViewportSizeChanged(s, e));
_editor.CurrentCamera = &_camera;
_editor.RequestOpenScene.Add(new (s, fileName) => {
LoadSceneFile(fileName);
});
}
public override void Update(GameTime gameTime)
{
_camera.Update(gameTime);
// Clear the swapchain-buffer
RenderCommand.Clear(null, .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);
RenderCommand.SetBlendState(_alphaBlendState);
RenderCommand.SetDepthStencilState(_depthStencilState);
if (_sceneState == .Edit)
_scene.UpdateEditor(gameTime, _camera, _viewportTarget, scope => DebugDraw3D, scope => DebugDraw2D);
else if (_sceneState == .Play)
_scene.UpdateRuntime(gameTime, _viewportTarget);
RenderCommand.UnbindRenderTargets();
RenderCommand.SetRenderTarget(null, 0, true);
RenderCommand.BindRenderTargets();
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)
{
EventDispatcher dispatcher = EventDispatcher(event);
dispatcher.Dispatch<ImGuiRenderEvent>(scope (e) => OnImGuiRender(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;
TextureViewer viewer = new TextureViewer() ~ delete _;
private bool OnImGuiRender(ImGuiRenderEvent event)
{
Input.ImGuiDebugDraw();
//viewer.ViewTexture(Renderer.[Friend]_gBuffer.Target);
ImGui.Viewport* viewport = ImGui.GetMainViewport();
ImGui.DockSpaceOverViewport(viewport);
DrawMainMenuBar();
_editor.SceneViewportWindow.RenderTarget = _viewportTarget;
_editor.Update();
_settingsWindow.Show();
UI_Toolbar();
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()
{
ImGui.BeginMainMenuBar();
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();
}
if(ImGui.BeginMenu("View", true))
{
if(ImGui.MenuItem(EntityHierarchyWindow.s_WindowTitle))
{
_editor.EntityHierarchyWindow.Open = true;
}
if(ImGui.MenuItem(ComponentEditWindow.s_WindowTitle))
{
_editor.ComponentEditWindow.Open = true;
}
if(ImGui.MenuItem(SceneViewportWindow.s_WindowTitle))
{
_editor.SceneViewportWindow.Open = true;
}
ImGui.EndMenu();
}
ImGui.EndMainMenuBar();
}
private bool OnWindowResize(WindowResizeEvent e)
{
return false;
}
private void ViewportSizeChanged(Object sender, Vector2 viewportSize)
{
uint32 sizeX = (uint32)viewportSize.X;
uint32 sizeY = (uint32)viewportSize.Y;
if(sizeX == 0 || sizeY == 0)
return;
_viewportTarget.Resize(sizeX, sizeY);
_cameraTarget.Resize(sizeX, 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
@@ -1,253 +0,0 @@
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
@@ -1,246 +0,0 @@
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;
}
}
}
-7
View File
@@ -1,7 +0,0 @@
FileVersion = 1
Dependencies = {corlib = "*", GlitchyEngine = "*"}
[Project]
Name = "GlitchyEngine.Test"
TargetType = "BeefTest"
StartupObject = "GlitchyEngine.Test.Program"
-5
View File
@@ -1,5 +0,0 @@
FileVersion = 1
Projects = {"GlitchyEngine.Test" = {Path = "."}, GlitchyEngine = {Path = "../GlitchyEngine"}, GlitchLog = {Path = "../GlitchLog"}, DirectX = {Path = "../GlitchyEngine/vendor/directx/DirectX"}, ImGui = {Path = "../GlitchyEngine/vendor/imgui-beef/ImGui"}, ImGuiImplWin32 = {Path = "../GlitchyEngine/vendor/imgui-beef/ImGuiImplWin32"}, ImGuiImplDX11 = {Path = "../GlitchyEngine/vendor/imgui-beef/ImGuiImplDX11"}, DirectXTK = {Path = "../GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, FreeType = {Path = "../GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "../GlitchyEngine/vendor/gltf/cgltf-beef"}}
[Workspace]
StartupProject = "GlitchyEngine.Test"
@@ -1,64 +0,0 @@
using System;
using GlitchyEngine.Math;
namespace GlitchyEngine.Test.Math
{
static class MathHelperTest
{
[Test]
public static void TestDegToRad()
{
{
float deg = 0;
float rad = MathHelper.ToRadians(deg);
Test.Assert(rad == 0.0f);
}
{
float deg = 45;
float rad = MathHelper.ToRadians(deg);
Test.Assert(rad == MathHelper.PiOverFour);
}
{
float deg = 180;
float rad = MathHelper.ToRadians(deg);
Test.Assert(rad == MathHelper.Pi);
}
{
float deg = -360;
float rad = MathHelper.ToRadians(deg);
Test.Assert(rad == -MathHelper.TwoPi);
}
}
[Test]
public static void TestRadToDeg()
{
{
float rad = 0;
float deg = MathHelper.ToDegrees(rad);
Test.Assert(deg == 0.0f);
}
{
float rad = MathHelper.PiOverFour;
float deg = MathHelper.ToDegrees(rad);
Test.Assert(deg == 45);
}
{
float rad = MathHelper.Pi;
float deg = MathHelper.ToDegrees(rad);
Test.Assert(deg == 180);
}
{
float rad = -MathHelper.TwoPi;
float deg = MathHelper.ToDegrees(rad);
Test.Assert(deg == -360);
}
}
}
}
@@ -1,199 +0,0 @@
using System;
using GlitchyEngine.Math;
namespace GlitchyEngine.Test.Math
{
/**
* @Note Testing 180° Angles is quite stupid as 180° = -180° and thus the result is quite dependant of floating point precision...
*/
static class QuaternionTest
{
static bool QuatEquals(Quaternion q1, Quaternion q2, float delta = 0.0001f)
{
return Math.Abs(q1.X - q2.X) < delta && Math.Abs(q1.Y - q2.Y) < delta &&
Math.Abs(q1.Z - q2.Z) < delta && Math.Abs(q1.W - q2.W) < delta;
}
static bool Vector3Equals(Vector3 v1, Vector3 v2, float delta = 0.0001f)
{
return Math.Abs(v1.X - v2.X) < delta && Math.Abs(v1.Y - v2.Y) < delta &&
Math.Abs(v1.Z - v2.Z) < delta;
}
static bool FloatEquals(float l, float r, float delta = 0.0001f)
{
return Math.Abs(l - r) < delta;
}
[Test]
public static void TestAxisAngleToQuaternion()
{
// angle in degrees
void Test(Vector3 axis, float angle, Quaternion expectedQuat)
{
float angleRad = MathHelper.ToRadians(angle);
Quaternion result = .FromAxisAngle(axis, angleRad);
Quaternion resultNormalized = .Normalize(result);
Quaternion expectedNormalized = .Normalize(expectedQuat);
Test.Assert(QuatEquals(resultNormalized, expectedNormalized));
}
// Reference for conversions: https://www.andre-gaschler.com/rotationconverter/
Test(.(0, 0, 0), 55, Quaternion.Identity);
// x: 90°
Test(.(1, 0, 0), 90, Quaternion(1, 0, 0, 1));
// y: 90°
Test(.(0, 1, 0), 90, Quaternion(0, 1, 0, 1));
// z: 90°
Test(.(0, 0, 1), 90, Quaternion(0, 0, 1, 1));
Test(.(1, 1, 0), 90, Quaternion(0.5f, 0.5f, 0, 0.707107f));
Test(.(1, 1, 1), 90, Quaternion(0.4082483f, 0.4082483f, 0.4082483f, 0.7071068f));
Test(.(0.3f, 0.5f, 0.4f), -25, Quaternion(-0.0918276f, -0.1530459f, -0.1224367f, 0.976296f));
Test(.(0.3f, -0.5f, 0.4f), -25, Quaternion(-0.0918276f, 0.1530459f, -0.1224367f, 0.976296f));
}
[Test]
public static void TestQuaternionToAxisAngle()
{
// angle in degrees
void Test(Quaternion quat, Vector3 expectedAxis, float expectedAngle)
{
Quaternion quatNrm = .Normalize(quat);
(Vector3 resultAxis, float resultAngle) = quatNrm.ToAxisAngle();
resultAxis.Normalize();
resultAngle = MathHelper.ToDegrees(resultAngle);
Vector3 nrmExpectedAxis = .Normalize(expectedAxis);
Test.Assert(Vector3Equals(resultAxis, nrmExpectedAxis) && FloatEquals(expectedAngle, resultAngle));
}
// Reference for conversions: https://www.andre-gaschler.com/rotationconverter/
Test(Quaternion.Identity, .(0, 0, 0), 0);
// x: 90°
Test(Quaternion(1, 0, 0, 1), .(1, 0, 0), 90);
// y: 90°
Test(Quaternion(0, 1, 0, 1), .(0, 1, 0), 90);
// z: 90°
Test(Quaternion(0, 0, 1, 1), .(0, 0, 1), 90);
Test(Quaternion(0.5f, 0.5f, 0, 0.707107f), .(1, 1, 0), 90);
Test(Quaternion(0.4082483f, 0.4082483f, 0.4082483f, 0.7071068f), .(1, 1, 1), 90);
Test(Quaternion(-0.0918276f, -0.1530459f, -0.1224367f, 0.976296f), .(-0.4242643f, -0.7071067f, -0.5656853f), 24.9999988f);
Test(Quaternion(-0.0918276f, 0.1530459f, -0.1224367f, 0.976296f), .(-0.4242643f, 0.7071067f, -0.5656853f), 24.9999988f);
}
[Test]
public static void TestEulerToQuaternion()
{
void Test(Vector3 inEuler, Quaternion expectedQuat)
{
Quaternion result = .FromEulerAngles(MathHelper.ToRadians(inEuler.Y), MathHelper.ToRadians(inEuler.X), MathHelper.ToRadians(inEuler.Z));
Test.Assert(QuatEquals(result, expectedQuat));
}
Test(Vector3.Zero, Quaternion.Identity);
// X: 90°
Test(.(90, 0, 0), Quaternion(1, 0, 0, 1)..Normalize());
// Y: 90°
Test(.(0, 90, 0), Quaternion(0, 1, 0, 1)..Normalize());
// Z: 90°
Test(.(0, 0, 90), Quaternion(0, 0, 1, 1)..Normalize());
// X: 90° Y: 90°
Test(.(90, 90, 0), Quaternion(1, 1, -1, 1)..Normalize());
// 90° 45° 30°
Test(.(90, 45, 30), Quaternion(0.7010574f, 0.4304593f, -0.092296f, 0.5609855f)..Normalize());
// 105° 90° 0°
Test(.(105, 90, 0), Quaternion(0.560986f, 0.430459f, -0.560986f, 0.430459f)..Normalize());
// 180° 90° -75°
Test(.(180, 90, -75), Quaternion(0.5609855f, -0.4304593f, -0.5609855f, 0.4304593f)..Normalize());
}
[Test]
public static void TestQuaternionToEuler()
{
void Test(Quaternion inQuat, Vector3 expectedEuler)
{
Quaternion nrmInQuat = .Normalize(inQuat);
Vector3 result = Quaternion.ToEulerAngles(nrmInQuat);
Vector3 resultDeg = MathHelper.ToDegrees(result);
Test.Assert(Vector3Equals(resultDeg, expectedEuler));
}
Test(Quaternion.Identity, Vector3.Zero);
// X: 90°
Test(Quaternion(1, 0, 0, 1), .(90, 0, 0));
// Y: 90°
Test(Quaternion(0, 1, 0, 1), .(0, 90, 0));
// Z: 90°
Test(Quaternion(0, 0, 1, 1), .(0, 0, 90));
// X: 90° Y: 90°
Test(Quaternion(1, 1, -1, 1), .(90, 90, 0));
// 90° 45° 30°
Test(Quaternion(0.7010574f, 0.4304593f, -0.092296f, 0.5609855f), .(90, 45, 30));
// 105° 90° 0°
Test(Quaternion(0.560986f, 0.430459f, -0.560986f, 0.430459f), .(105, 90, 0));
// 180° 90° -75°
Test(Quaternion(0.5609855f, -0.4304593f, -0.5609855f, 0.4304593f), .(180, 90, -75));
}
[Test]
public static void TestEulerToQautToEuler()
{
void Test(float yaw, float pitch, float roll)
{
Quaternion quat = .FromEulerAngles(MathHelper.ToRadians(yaw), MathHelper.ToRadians(pitch), MathHelper.ToRadians(roll));
Vector3 result = Quaternion.ToEulerAngles(quat);
result = .(MathHelper.ToDegrees(result.X), MathHelper.ToDegrees(result.Y), MathHelper.ToDegrees(result.Z));
Test.Assert(Vector3Equals(.(pitch, yaw, roll), result));
}
Test(0, 90, 0);
Test(90, 0, 0);
Test(0, 0, 90);
Test(90, 90, 0);
Test(90, 45, 0);
Test(90, 45, 30);
Test(105, 90, 0);
Test(179, 90, -75);
}
}
}
+15 -11
View File
@@ -1,9 +1,22 @@
FileVersion = 1
Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*", box2d-beef = "*", "Beef.Linq" = "*"}
Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", ImGui = "*", ImGuiImplWin32 = "*", ImGuiImplDX11 = "*", DirectXTK = "*"}
[Project]
Name = "GlitchyEngine"
ProcessorMacros = ["GE_GRAPHICS_DX11", "GE_SHADER_MATRIX_MISMATCH_IS_ERROR", "GE_SHADER_VAR_TYPE_MISMATCH_IS_ERROR", "GE_SHADER_UNUSED_VARIABLE_IS_WARNING", "GE_WINDOWS"]
ProcessorMacros = ["GE_ERROR_SHADER_MATRIX_MISMATCH", "GE_ERROR_SHADER_VAR_TYPE_MISMATCH"]
[Configs.Debug.Win32]
PreprocessorMacros = ["DEBUG", "GE_WINDOWS"]
[Configs.Debug.Win64]
CLibType = "DynamicDebug"
PreprocessorMacros = ["DEBUG", "GE_WINDOWS", "GE_D3D11"]
[Configs.Release.Win32]
PreprocessorMacros = ["RELEASE", "GE_WINDOWS"]
[Configs.Release.Win64]
PreprocessorMacros = ["RELEASE", "GE_WINDOWS"]
[Configs.Paranoid.Win32]
PreprocessorMacros = ["DEBUG", "PARANOID", "GE_WINDOWS"]
@@ -11,17 +24,8 @@ PreprocessorMacros = ["DEBUG", "PARANOID", "GE_WINDOWS"]
[Configs.Paranoid.Win64]
PreprocessorMacros = ["DEBUG", "PARANOID", "GE_WINDOWS"]
[Configs.Release.Win32]
PreprocessorMacros = ["RELEASE", "GE_WINDOWS"]
[Configs.Release.Win64]
PreprocessorMacros = ["IMGUI", "RELEASE", "GE_PROFILE"]
[Configs.Test.Win32]
PreprocessorMacros = ["TEST", "GE_WINDOWS"]
[Configs.Test.Win64]
PreprocessorMacros = ["TEST", "GE_WINDOWS"]
[Configs.Debug.Win64]
PreprocessorMacros = ["DEBUG", "GE_PROFILE", "GE_PROFILE_RENDERER", "GE_PROFILE_RESOURCES", "IMGUI"]
+15 -134
View File
@@ -2,115 +2,60 @@ using System;
using GlitchyEngine.Events;
using GlitchyEngine.ImGui;
using GlitchyEngine.Renderer;
using GlitchyEngine.Debug;
using GlitchyEngine.Content;
namespace GlitchyEngine
{
public abstract class Application
public class Application
{
static Application s_Instance = null;
private Window _window;
private RendererAPI _rendererApi;
private Window _window ~ delete _;
private RendererAPI _rendererApi ~ delete _;
private bool _running = true;
private bool _isMinimized = false;
private LayerStack _layerStack;
private LayerStack _layerStack = new LayerStack() ~ delete _;
#if IMGUI
private ImGuiLayer _imGuiLayer;
#endif
private GameTime _gameTime;
private IContentManager _contentManager;
private GameTime _gameTime = new GameTime(true) ~ delete _;
public bool IsRunning => _running;
public Window Window => _window;
public IContentManager ContentManager => _contentManager;
public bool IsMinimized => _isMinimized;
[Inline]
public static Application Get() => s_Instance;
public Settings Settings {get; private set;} = new .() ~ delete _;
public this()
{
Profiler.ProfileFunction!();
Log.EngineLogger.Assert(s_Instance == null, "Tried to create a second application.");
s_Instance = this;
_layerStack = new LayerStack();
_gameTime = new GameTime(true);
_window = new Window(.Default);
_window.EventCallback = new => OnEvent;
Input.Init();
_contentManager = InitContentManager();
// TODO: RenderAPI in RenderCommand initialisieren?
_rendererApi = new RendererAPI();
_rendererApi.Context = _window.Context;
SamplerStateManager.Init();
SamplerStateManager.Init(_window.Context);
// TODO: Rendercommmand in Renderer initialisieren?
RenderCommand.RendererAPI = _rendererApi;
Renderer.Init();
Renderer.Init(_window.Context);
#if IMGUI
_imGuiLayer = new 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()
{
Profiler.ProfileFunction!();
SamplerStateManager.Uninit();
Renderer.Deinit();
delete _contentManager;
delete _rendererApi;
delete _window;
delete _gameTime;
delete _layerStack;
}
public void OnEvent(Event e)
{
Debug.Profiler.ProfileFunction!();
if(!_running)
return;
EventDispatcher dispatcher = EventDispatcher(e);
EventDispatcher dispatcher = scope .(e);
dispatcher.Dispatch<WindowCloseEvent>(scope => OnWindowClose);
dispatcher.Dispatch<WindowResizeEvent>(scope => OnWindowResize);
for(Layer layer in _layerStack)
{
layer.OnEvent(e);
@@ -119,97 +64,33 @@ namespace GlitchyEngine
}
}
#if GE_APP_SINGLESTEP
bool allowFrame = false;
bool disableSingleFrame = false;
#else
const bool allowFrame = true;
#endif
public void Run()
{
Debug.Profiler.ProfileFunction!();
while(_running)
{
Debug.Profiler.ProfileScope!("Loop");
if(allowFrame)
{
_gameTime.NewFrame();
}
_gameTime.NewFrame();
Input.NewFrame();
#if GE_APP_SINGLESTEP
allowFrame = disableSingleFrame || Input.IsKeyPressing(Key.F10);
if(Input.IsKeyPressing(Key.F11))
{
disableSingleFrame = !disableSingleFrame;
}
#endif
if(allowFrame && !_isMinimized)
{
Debug.Profiler.ProfileScope!("Update Layers");
for(Layer layer in _layerStack)
layer.Update(_gameTime);
}
for(Layer layer in _layerStack)
layer.Update(_gameTime);
_window.Update();
if(allowFrame)
{
#if IMGUI
_imGuiLayer.ImGuiRender();
#endif
_imGuiLayer.ImGuiRender();
_window.Context.SwapChain.Present();
}
_window.Context.SwapChain.Present();
}
}
/// Closes the applcation.
public void Close()
{
_running = false;
}
public void PushLayer(Layer ownLayer) => _layerStack.PushLayer(ownLayer);
public void PushLayer(Layer ownLayer)
{
Profiler.ProfileFunction!();
_layerStack.PushLayer(ownLayer);
ownLayer.OnAttach();
}
public void PushOverlay(Layer ownOverlay)
{
Profiler.ProfileFunction!();
_layerStack.PushOverlay(ownOverlay);
ownOverlay.OnAttach();
}
public void PushOverlay(Layer ownOverlay) => _layerStack.PushOverlay(ownOverlay);
public bool OnWindowClose(WindowCloseEvent e)
{
_running = false;
return true;
}
public bool OnWindowResize(WindowResizeEvent e)
{
if(e.Width == 0 || e.Height == 0)
{
_isMinimized = true;
return false;
}
_isMinimized = false;
return false;
}
}
}
-80
View File
@@ -1,80 +0,0 @@
using System.Collections;
namespace GlitchyEngine.Collections
{
// TODO: TreeNode is very bare minimum
// Destructor?
// RemoveChild?
// Remove in enumerator?
public class TreeNode<T>
{
public T Value;
public List<Self> Children = new .() ~ DeleteContainerAndItems!(_);
public this() {}
public this(T value)
{
Value = value;
}
public Self AddChild(T value)
{
for (var child in Children)
{
if(child.Value == value)
return child;
}
Self newChild = new .(value);
Children.Add(newChild);
return newChild;
}
public Self FindNode(T value)
{
if (Value == value)
return this;
for (var child in Children)
{
var childResult = child.FindNode(value);
if (childResult != null)
return childResult;
}
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
@@ -1,83 +0,0 @@
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
@@ -1,181 +0,0 @@
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;
}*/
}
}
-861
View File
@@ -1,861 +0,0 @@
using System;
using System.Collections;
using cgltf;
using GlitchyEngine.Math;
using GlitchyEngine.Renderer;
using GlitchyEngine.Renderer.Animation;
using GlitchyEngine.World;
using System.IO;
namespace GlitchyEngine.Content
{
public static class ModelLoader
{
static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1);
public static Result<void> GetMeshNames(String filename, List<String> meshNames)
{
CGLTF.Options options = .();
CGLTF.Data* data;
CGLTF.Result result = CGLTF.ParseFile(options, filename, out data);
if (!(result case .Success))
return .Err;
for (var mesh in data.Meshes)
{
meshNames.Add(new String(mesh.Name));
}
CGLTF.Free(data);
return .Ok;
}
public static GeometryBinding LoadMesh(StringView fileName, StringView meshName, int primitiveIndex)
{
// TODO: add a context to remember which buffers were loaded before so that we don't load the same data multiple times.
char8* scopedFileName = fileName.ToScopeCStr!();
CGLTF.Options options = .();
CGLTF.Data* data;
CGLTF.Result result = CGLTF.ParseFile(options, scopedFileName, out data);
if (!(result case .Success))
return null;
result = CGLTF.LoadBuffers(options, data, scopedFileName);
GeometryBinding geoBinding = null;
for (var mesh in data.Meshes)
{
var name = StringView(mesh.Name);
if (name == meshName)
{
Log.EngineLogger.AssertDebug(primitiveIndex >= 0 && primitiveIndex < mesh.Primitives.Length);
geoBinding = PrimitiveToGeoBinding(mesh.Primitives[primitiveIndex]);
break;
}
}
CGLTF.Free(data);
return geoBinding;
}
public static 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.Data* data;
CGLTF.Result result = CGLTF.ParseFile(options, filename, out data);
Log.EngineLogger.Assert(result == .Success, "Failed to load model.");
result = CGLTF.LoadBuffers(options, data, filename);
Log.EngineLogger.Assert(result == .Success, "Failed to load buffers");
(EcsEntity entity, ?) = CreateEntity(world, entityName, .InvalidEntity);
for(var node in data.Scenes[0].Nodes)
{
NodesToEntities(data, node, entity, world, material, outClips);
}
CGLTF.Free(data);
return entity;
}
private static (EcsEntity Entity, TransformComponent* Transform) CreateEntity(EcsWorld world, StringView? name, EcsEntity parent)
{
EcsEntity entity = world.NewEntity();
var nameComponent = world.AssignComponent<DebugNameComponent>(entity);
if (name != null && name.Value.Ptr != null)
{
nameComponent.SetName(name.Value);
}
else
{
nameComponent.SetName("Unnamed Node");
}
/////TODO: !!!!!!!!REPORT!!!!!!!!!!!!!
// This works
TransformComponent cmp = .();
var childTransform = world.AssignComponent<TransformComponent>(entity, cmp);
// This trashes the stack
//var childTransform = world.AssignComponent<TransformComponent>(entity);
childTransform.Parent = parent;
return (entity, childTransform);
}
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)
{
childTransform.LocalTransform = *(Matrix*)&node.Matrix;
}
else
{
if(node.HasTranslation)
childTransform.Position = *(Vector3*)&node.Translation;
else
childTransform.Position = .Zero;
if(node.HasRotation)
childTransform.Rotation = *(Quaternion*)&node.Rotation;
else
childTransform.Rotation = .Identity;
if(node.HasScale)
childTransform.Scale = *(Vector3*)&node.Scale;
else
childTransform.Scale = .(1, 1, 1);
}
// Invert the Z-Axis of the root Node to convert the coordinate system from right-handed to left-handed
if(parentEntity == .InvalidEntity)
childTransform.Scale *= .(1, 1, -1);
Skeleton skeleton = null;
if(node.Skin != null)
{
skeleton = ExtractSkeleton(node.Skin);
LoadAnimationClips(data, node.Skin, skeleton, clips);
}
if(node.Mesh != null)
{
// If we have only one primitive, add it directly to the entity
if(node.Mesh.Primitives.Length == 1)
{
var mesh = world.AssignComponent<MeshComponent>(entity);
using (var geo = PrimitiveToGeoBinding(node.Mesh.Primitives[0]))
{
mesh.Mesh = geo;
}
if(skeleton == null)
{
var meshRenderer = world.AssignComponent<MeshRendererComponent>(entity);
meshRenderer.Material = material;
}
else
{
var meshRenderer = world.AssignComponent<SkinnedMeshRendererComponent>(entity);
meshRenderer.Material = material;
meshRenderer.Skeleton = skeleton;
}
}
// otherwise one child-entity per primitive
else
{
for(var primitive in node.Mesh.Primitives)
{
EcsEntity meshEntity = world.NewEntity();
var meshParent = world.AssignComponent<ParentComponent>(meshEntity);
meshParent.Entity = entity;
var mesh = world.AssignComponent<MeshComponent>(meshEntity);
mesh.Mesh = PrimitiveToGeoBinding(primitive);
if(skeleton == null)
{
var meshRenderer = world.AssignComponent<MeshRendererComponent>(meshEntity);
meshRenderer.Material = material;
}
else
{
var meshRenderer = world.AssignComponent<SkinnedMeshRendererComponent>(meshEntity);
meshRenderer.Material = material;
meshRenderer.Skeleton = skeleton;
}
}
}
}
skeleton?.ReleaseRef();
for(var child in node.Children)
{
NodesToEntities(data, child, entity, world, material, clips);
}
}
public static GeometryBinding PrimitiveToGeoBinding(CGLTF.Primitive primitive)
{
GeometryBinding binding = new GeometryBinding();
// primitive topology
switch(primitive.Type)
{
case .Points:
binding.SetPrimitiveTopology(.PointList);
case .Lines:
binding.SetPrimitiveTopology(.LineList);
case .LineStrip:
binding.SetPrimitiveTopology(.LineStrip);
case .Triangles:
binding.SetPrimitiveTopology(.TriangleList);
case .TriangleStrip:
binding.SetPrimitiveTopology(.TriangleStrip);
default:
Log.EngineLogger.Assert(false, scope $"{primitive.Type} not supported.");
}
// indices
{
CGLTF.Accessor* indices = primitive.Indices;
if(indices != null)
{
bool is16bit = indices.ComponentType == .R_16u || indices.ComponentType == .R_16;
IndexBuffer ib = new IndexBuffer((.)indices.Count, .Immutable, .None, is16bit ? .Index16Bit : .Index32Bit);
uint8* bufferData = (uint8*)indices.BufferView.Buffer.Data;
bufferData += indices.BufferView.Offset;
// TODO: this is a mess
ib.SetData<uint8>(bufferData, (.)indices.BufferView.Size);
binding.SetIndexBuffer(ib);
ib.ReleaseRef();
}
}
bool hasNormals = false;
bool hasTangents = false;
// vertices
{
List<VertexElement> elements = scope .(primitive.Attributes.Length);
Dictionary<void*, VertexBuffer> buffers = scope .();
List<VertexBufferBinding> bindings = scope .();
for(var attribute in primitive.Attributes)
{
{
StringView attributeName = StringView(attribute.Name);
if(attributeName.Equals("NORMAL"))
{
hasNormals = true;
}
else if(attributeName.Equals("TANGENT"))
{
hasTangents = true;
}
}
// Get Input Element format
Format format = FormatFromVectorComponent(attribute.Data.Type, attribute.Data.ComponentType);
Log.EngineLogger.AssertDebug(format != .Unknown, "Vertex element format must not be \"Unknown.\"");
VertexBuffer vertexBuffer = null;
binding.VertexCount = (uint32)attribute.Data.Count;
// Get vertex buffer
{
CGLTF.BufferView* bufferView = attribute.Data.BufferView;
// if buffer doesn't exist -> create
if(!buffers.TryGetValue(bufferView, out vertexBuffer))
{
vertexBuffer = new VertexBuffer(1, (uint32)bufferView.Size, .Immutable)..ReleaseRefNoDelete();
uint8* bufferData = (uint8*)bufferView.Buffer.Data;
bufferData += bufferView.Offset;
vertexBuffer.SetData(bufferData, (.)bufferView.Size);
buffers.Add(attribute.Data.BufferView, vertexBuffer);
}
Log.EngineLogger.AssertDebug(vertexBuffer != null);
}
VertexBufferBinding bufferBinding = .(vertexBuffer, (.)attribute.Data.Stride, (.)attribute.Data.Offset);
// get slot of bufferBinding
int bindingSlot = bindings.IndexOf(bufferBinding);
// binding has no slot -> add to list
if(bindingSlot == -1)
{
bindingSlot = bindings.Count;
bindings.Add(bufferBinding);
binding.SetVertexBufferSlot(bufferBinding, (.)bindingSlot);
}
StringView strView = .(attribute.Name);
// Remove number from end of name
while((*(strView.EndPtr - 1)).IsDigit || (*(strView.EndPtr - 1)) == '_')
{
strView.Length--;
}
VertexElement element = .(format, new String(strView), true, (.)attribute.Index, (.)bindingSlot);
elements.Add(element);
}
// Generate normals if missing
if(!hasNormals)
{
CGLTF.Accessor* positions = null;
// Find position accessor
for(var attribute in primitive.Attributes)
{
if(StringView(attribute.Name).Equals("POSITION"))
{
positions = attribute.Data;
break;
}
}
Log.EngineLogger.AssertDebug(positions != null, "The model appears to have no position data?!");
Vector3[] normals = new Vector3[positions.Count];
if(primitive.Indices != null)
GenerateNormals(primitive.Indices, positions, normals);
else
GenerateNormals(positions, normals);
VertexBuffer vertexBuffer = new VertexBuffer((uint32)sizeof(Vector3), (uint32)normals.Count, .Immutable);
vertexBuffer.SetData<Vector3>(normals);
bindings.Add(vertexBuffer.Binding);
uint32 bindingSlot = (.)bindings.Count - 1;
binding.SetVertexBufferSlot(vertexBuffer.Binding, (.)bindingSlot);
VertexElement element = .(.R32G32B32_Float, "NORMAL", false, 0, bindingSlot);
elements.Add(element);
}
// TODO: validate vertex layout somewhere else
VertexElement[] vertexElements = new VertexElement[elements.Count];
for(int i < elements.Count)
{
vertexElements[i] = elements[i];
}
VertexLayout layout = new VertexLayout(vertexElements, true);
binding.SetVertexLayout(layout..ReleaseRefNoDelete());
}
// TODO: calculate tangents if missing (MikkTSpace algorithm)
// Note: Bitangent = cross(normal, tangent.xyz) * tangent.w
return binding;
}
/// Generates the normals for one triangle
static mixin GenerateTriangleNormals(int index0, int index1, int index2, CGLTF.Accessor* positions, Vector3[] normals)
{
Vector3 position0 = GetEntry<Vector3>(positions, (.)index0);
Vector3 position1 = GetEntry<Vector3>(positions, (.)index1);
Vector3 position2 = GetEntry<Vector3>(positions, (.)index2);
ref Vector3 normal0 = ref normals[(.)index0];
ref Vector3 normal1 = ref normals[(.)index1];
ref Vector3 normal2 = ref normals[(.)index2];
Vector3 e0 = position1 - position0;
Vector3 e1 = position2 - position0;
Vector3 normal = Vector3.Cross(e0, e1);
normal0 += normal;
normal1 += normal;
normal2 += normal;
}
[Inline]
static void NormalizeNormals(Vector3[] normals)
{
for(int i < normals.Count)
{
normals[i].Normalize();
}
}
/// Generates normals for the given model using indexed geometry
static void GenerateNormals(CGLTF.Accessor* indices, CGLTF.Accessor* positions, Vector3[] normals)
{
/// Enumerate triangle-wise
for(uint t = 0; t < indices.Count; t += 3)
{
int index0 = (.)CGLTF.AccessorReadIndex(indices, t);
int index1 = (.)CGLTF.AccessorReadIndex(indices, t + 1);
int index2 = (.)CGLTF.AccessorReadIndex(indices, t + 2);
GenerateTriangleNormals!(index0, index1, index2, positions, normals);
}
NormalizeNormals(normals);
}
/// Generates normals for the given model using nonindexed geometry
static void GenerateNormals(CGLTF.Accessor* positions, Vector3[] normals)
{
/// Enumerate triangle-wise
for(int i = 0; i < (.)positions.Count; i += 3)
{
GenerateTriangleNormals!(i, i + 1, i + 2, positions, normals);
}
NormalizeNormals(normals);
}
static Skeleton ExtractSkeleton(CGLTF.Skin* skin)
{
Skeleton skeleton = new Skeleton();
skeleton.Joints = new Joint[skin.Joints.Length];
for(int i < skin.Joints.Length)
{
ref Joint joint = ref skeleton.Joints[i];
joint.InverseBindPose = GetEntry<Matrix>(skin.InverseBindMatrices, i);
if (skin.Joints[i].Name != null)
joint.Name = new String(skin.Joints[i].Name);
int parentId = skin.Joints.IndexOf(skin.Joints[i].Parent);
Log.EngineLogger.AssertDebug(parentId < uint8.MaxValue, scope $"A skeleton must not have more than {uint8.MaxValue - 1} bones.");
if(parentId == -1)
joint.ParentID = uint8.MaxValue;
else
joint.ParentID = (uint8)parentId;
}
return skeleton;
}
static bool AnimationBelongsToSkeleton(CGLTF.Animation animation, CGLTF.Skin* skin)
{
for(var channel in animation.Channels)
{
if(skin.Joints.IndexOf(channel.TargetNode) == -1)
{
return false;
}
}
return true;
}
static void LoadAnimationClips(CGLTF.Data* data, CGLTF.Skin* skin, Skeleton skeleton, List<AnimationClip> clips)
{
for(var animation in data.Animations)
{
if(!AnimationBelongsToSkeleton(animation, skin))
continue;
AnimationClip clip = new AnimationClip(skeleton);
clips.Add(clip);
clip.IsLooping = true;
for(var channel in animation.Channels)
{
int nodeIndex = skin.Joints.IndexOf(channel.TargetNode);
Log.EngineLogger.AssertDebug(nodeIndex != -1);
ref JointAnimation jointAnimation = ref clip.JointAnimations[nodeIndex];
if(jointAnimation == null)
jointAnimation = new JointAnimation();
Log.EngineLogger.AssertDebug(channel.Sampler.Input.Count == channel.Sampler.Output.Count);
int samples = (int)channel.Sampler.Input.Count;
Log.EngineLogger.AssertDebug(channel.Sampler.Input.ComponentType == .R_32f);
Log.EngineLogger.AssertDebug(channel.Sampler.Input.Type == .Scalar);
InterpolationMode interpolationMode;
switch(channel.Sampler.Interpolation)
{
case .Step:
interpolationMode = .Step;
case .Linear:
interpolationMode = .Linear;
case .CubicSpline:
interpolationMode = .CubicSpline;
}
switch(channel.TargetPath)
{
case .Translation:
jointAnimation.TranslationChannel = new .(samples, interpolationMode);
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec3);
for(int i < samples)
{
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
Vector3 sample = GetEntry<Vector3>(channel.Sampler.Output, i);
jointAnimation.TranslationChannel.TimeStamps[i] = timeStamp;
jointAnimation.TranslationChannel.Values[i] = sample;
}
case .Rotation:
jointAnimation.RotationChannel = new .(samples, interpolationMode);
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec4);
for(int i < samples)
{
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
Quaternion sample = GetEntry<Quaternion>(channel.Sampler.Output, i);
jointAnimation.RotationChannel.TimeStamps[i] = timeStamp;
jointAnimation.RotationChannel.Values[i] = sample;
}
case .Scale:
jointAnimation.ScaleChannel = new .(samples, interpolationMode);
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec3);
for(int i < samples)
{
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
Vector3 sample = GetEntry<Vector3>(channel.Sampler.Output, i);
jointAnimation.ScaleChannel.TimeStamps[i] = timeStamp;
jointAnimation.ScaleChannel.Values[i] = sample;
}
default:
Log.EngineLogger.Error($"Unknown channel target path \"{channel.TargetPath}\"");
}
clip.Duration = Math.Max(clip.Duration, jointAnimation.Duration);
}
}
}
static T GetEntry<T>(CGLTF.Accessor* accessor, int index)
{
Log.EngineLogger.AssertDebug((uint)index < accessor.Count);
T result = ?;
switch(typeof(T))
{
case typeof(float):
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 1);
case typeof(Vector2):
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 2);
case typeof(Vector3):
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 3);
case typeof(Vector4), typeof(Quaternion):
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 4);
case typeof(Matrix):
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 16);
case typeof(uint16):
CGLTF.AccessorReadUint(accessor, (uint)index, (uint32*)&result, 2);
case default:
uint8* data = (uint8*)accessor.BufferView.Buffer.Data;
data += accessor.BufferView.Offset;
data += accessor.Offset;
data += accessor.Stride * (uint)index;
result = *(T*)data;
}
return result;
/*
uint8* data = (uint8*)accessor.BufferView.Buffer.Data;
data += accessor.BufferView.Offset;
data += accessor.Offset;
data += accessor.Stride * (uint)index;
return *(T*)data;
*/
}
/**
* Converts the vector and component type to the corresponding Format.
*/
static Format FormatFromVectorComponent(CGLTF.Type vectorType, CGLTF.ComponentType componentType)
{
switch((vectorType, componentType))
{
case (.Scalar, .R_8):
return .R8_SInt;
case (.Scalar, .R_8u):
return .R8_UInt;
case (.Scalar, .R_16):
return .R16_SInt;
case (.Scalar, .R_16u):
return .R16_UInt;
case (.Scalar, .R_32u):
return .R32_UInt;
case (.Scalar, .R_32f):
return .R32_Float;
case (.Vec2, .R_8):
return .R8G8_SInt;
case (.Vec2, .R_8u):
return .R8G8_UInt;
case (.Vec2, .R_16):
return .R16G16_SInt;
case (.Vec2, .R_16u):
return .R16G16_UInt;
case (.Vec2, .R_32u):
return .R32G32_UInt;
case (.Vec2, .R_32f):
return .R32G32_Float;
//case (.Vec3, .R_8):
//case (.Vec3, .R_8u):
//case (.Vec3, .R_16):
//case (.Vec3, .R_16u):
case (.Vec3, .R_32u):
return .R32G32B32_UInt;
case (.Vec3, .R_32f):
return .R32G32B32_Float;
case (.Vec4, .R_8):
return .R8G8B8A8_SInt;
case (.Vec4, .R_8u):
return .R8G8B8A8_UInt;
case (.Vec4, .R_16):
return .R16G16B16A16_SInt;
case (.Vec4, .R_16u):
return .R16G16B16A16_UInt;
case (.Vec4, .R_32u):
return .R32G32B32A32_UInt;
case (.Vec4, .R_32f):
return .R32G32B32A32_Float;
case (.Mat4, .R_32f):
return .R32G32B32A32_Float;
default:
Log.EngineLogger.Assert(false, scope $"Unhandled vector - componenttype combination. ({vectorType}, {componentType})");
}
return .Unknown;
}
}
}
-112
View File
@@ -1,112 +0,0 @@
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();
}
-21
View File
@@ -1,21 +0,0 @@
using System;
namespace GlitchyEngine.Core
{
/**
* Provides a reference counter that, when reaching 0, automatically deletes itself.
* 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.
*/
public class RefCounter : RefCounted, IDisposable
{
protected ~this()
{
}
public void Dispose()
{
ReleaseRef();
}
}
}
-53
View File
@@ -1,53 +0,0 @@
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;
}
}
}

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