Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81ae5f1135 | ||
|
|
b76d4cd558 | ||
|
|
5893cfb74a | ||
|
|
ed952155b6 | ||
|
|
d54cdf43ee | ||
|
|
f16fbc3984 | ||
|
|
184480243f | ||
|
|
fc1875ed96 | ||
|
|
5733166b74 | ||
|
|
2ba4b68e09 | ||
|
|
edcec02e94 | ||
|
|
c164aeecaa | ||
|
|
793aa40975 | ||
|
|
50714976e9 | ||
|
|
7b6662cbc4 | ||
|
|
cfa046d2ea | ||
|
|
b22f73a156 | ||
|
|
c3fb9bbd5d | ||
|
|
c22d122921 | ||
|
|
ad14c9f6ed | ||
|
|
2ce357cd12 | ||
|
|
2324ae852d | ||
|
|
d3d284256c | ||
|
|
e38373dccf | ||
|
|
b5ca86d8de | ||
|
|
4589030da6 | ||
|
|
3da60355df | ||
|
|
ee30920b56 | ||
|
|
427323744b | ||
|
|
9d97579857 | ||
|
|
fdf680508d | ||
|
|
0b3f242082 | ||
|
|
502cee47e7 | ||
|
|
c389a77274 | ||
|
|
dffd43a36f | ||
|
|
d47ac23e1b | ||
|
|
8f22cbb8bc | ||
|
|
0155a38ae6 | ||
|
|
a42544f171 | ||
|
|
202083930a | ||
|
|
6958d27c30 | ||
|
|
f8544c8f64 | ||
|
|
e95c5731b6 | ||
|
|
3ec537762a | ||
|
|
e035832ab0 | ||
|
|
0f1eddad5f | ||
|
|
d2dca12d0c | ||
|
|
0588a93ec4 | ||
|
|
42e386a142 | ||
|
|
21c8d839ef | ||
|
|
bc1f466d18 | ||
|
|
8495c6db28 | ||
|
|
bff4418b84 | ||
|
|
0339ae4422 | ||
|
|
eb03eca4dd | ||
|
|
a926ef4af2 | ||
|
|
866353a157 | ||
|
|
a80018e80c | ||
|
|
e41645e12a | ||
|
|
8db2ce1d05 | ||
|
|
47fdce0a7e | ||
|
|
d212576719 | ||
|
|
164d420a25 | ||
|
|
28f1fad464 | ||
|
|
1418063d68 | ||
|
|
4cd0bff91d | ||
|
|
5d8a4ad3a7 | ||
|
|
ccea61fb79 | ||
|
|
de3da8ded8 | ||
|
|
62bb5baac9 | ||
|
|
3956080520 | ||
|
|
1d3d30f88d | ||
|
|
b1a1268319 | ||
|
|
56e92a7d67 | ||
|
|
f4c9dd8ade | ||
|
|
2e0cc289df | ||
|
|
336e52fbac | ||
|
|
f527af13c3 | ||
|
|
fe0d5fa529 | ||
|
|
b37b1b4bf9 | ||
|
|
3c2c37b270 | ||
|
|
dc235399c8 | ||
|
|
2817c5b1a1 | ||
|
|
412d0ea965 | ||
|
|
9e23ac0fcf | ||
|
|
a43572817d | ||
|
|
805423a64e |
@@ -25,3 +25,9 @@
|
|||||||
[submodule "GlitchyEngineHelper/vendor/DirectXTK"]
|
[submodule "GlitchyEngineHelper/vendor/DirectXTK"]
|
||||||
path = GlitchyEngineHelper/vendor/DirectXTK
|
path = GlitchyEngineHelper/vendor/DirectXTK
|
||||||
url = https://github.com/microsoft/DirectXTK.git
|
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
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
FileVersion = 1
|
FileVersion = 1
|
||||||
Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}, bon = {Path = "GlitchyEngine/vendor/bon"}}
|
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", "DirectXTK", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon"]}
|
Unlocked = ["corlib"]
|
||||||
|
WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq"]}
|
||||||
|
|
||||||
[Workspace]
|
[Workspace]
|
||||||
StartupProject = "GlitchyEditor"
|
StartupProject = "GlitchyEditor"
|
||||||
|
|
||||||
|
[Configs.Debug.Win64]
|
||||||
|
AllocStackTraceDepth = 12
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
///
|
||||||
|
/// Loading and usage of an asset.
|
||||||
|
///
|
||||||
|
// Shows the usage of the Content Manager.
|
||||||
|
// An asset is loaded with Content.LoadAsset(AssetName)
|
||||||
|
// Loading an asset returns an AssetHandle.
|
||||||
|
// Usually you want to hold on to the AssetHandle.
|
||||||
|
// If you need the actual asset you can use Content.GetAsset<AssetType>(AssetHandle) to retrieve the actual asset.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only hold on to AssetHandle.
|
||||||
|
* Do NOT hold actual Assets! The Asset might get invalidated
|
||||||
|
* (e.g. reloading after the source-file changed).
|
||||||
|
*/
|
||||||
|
AssetHandle _lineEffect;
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Loading an asset returns a handle to that asset.
|
||||||
|
*/
|
||||||
|
_lineEffect = Content.LoadAsset("Shaders\\LineEffect.hlsl");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
// Use case 1:
|
||||||
|
{
|
||||||
|
// Retrieve the actual asset. Note that this Asset is only guaranteed to persist
|
||||||
|
// for the current frame.
|
||||||
|
Effect fx = Content.GetAsset<Effect>(_lineEffect);
|
||||||
|
|
||||||
|
// Note: Assets are ref counted, however since they are not inteded to be held GetAsset
|
||||||
|
// does NOT increment the reference counter so you usually should NOT decrement it.
|
||||||
|
// The exception is when you manually hold on to an asset (see Example below)
|
||||||
|
|
||||||
|
// Use asset
|
||||||
|
fx.Variables["ViewProjection"].SetData(viewProjection * transform);
|
||||||
|
fx.Variables["Color"].SetData(color);
|
||||||
|
fx.ApplyChanges();
|
||||||
|
fx.Bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use case 2:
|
||||||
|
{
|
||||||
|
// Retrieve the actual asset.
|
||||||
|
Effect fx = _lineEffect.Get<Effect>();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
// Same as case 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Holding on to an actual asset.
|
||||||
|
///
|
||||||
|
// Shows the usage of the Content Manager when you hold a reference to the actual Asset instead of the handle.
|
||||||
|
// This should usually not be done. Eventhough it is technically fine, it has the implication, that if the
|
||||||
|
// content manager decides to reload the asset the changes will not be reflected in the held reference
|
||||||
|
// (which they would if you used GetAsset before every usage.)
|
||||||
|
// Also the content manager can't unload the asset because the reference counter wouldn't be zero.
|
||||||
|
// Though there currently is no way for the content manager to unload an asset unless it is specifically told to do so.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Field to hold the asset.
|
||||||
|
*/
|
||||||
|
Effect _lineEffect;
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
// Load the Asset.
|
||||||
|
AssetHandle handle = Content.LoadAsset("Shaders\\LineEffect.hlsl");
|
||||||
|
// Get the actual asset.
|
||||||
|
_lineEffect = Content.GetAsset<Effect>(handle); // Alternative: _lineEffect = handle.Get<Effect>();
|
||||||
|
// Increment the reference counter so that the asset doesn't get unloaded.
|
||||||
|
_lineEffect.AddRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Destroy()
|
||||||
|
{
|
||||||
|
// Make sure to release the asset once you don't need it anymore. Otherwise it will leak.
|
||||||
|
_lineEffect.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
_lineEffect.Variables["ViewProjection"].SetData(viewProjection * transform);
|
||||||
|
_lineEffect.Variables["Color"].SetData(color);
|
||||||
|
_lineEffect.ApplyChanges();
|
||||||
|
_lineEffect.Bind();
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
///
|
||||||
|
/// This shows the usage of a smart/typed Asset Handle.
|
||||||
|
///
|
||||||
|
// In the basic usage-patterns the user has two options:
|
||||||
|
// Either he/she holds a handle and has to query for the actual asset every time
|
||||||
|
// or he/she hold the actual asset and loses reloading capabilities.
|
||||||
|
// The idea of the auto asset is to provide a way that can provide both.
|
||||||
|
|
||||||
|
AssetHandle<Effect> _lineEffect;
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Loading an asset returns a handle to that asset.
|
||||||
|
* The asset handle is implicitly converted to a smart AssetHandle.
|
||||||
|
*/
|
||||||
|
_lineEffect = Content.LoadAsset("Shaders\\LineEffect.hlsl");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
// Use case 1 (Convenience)
|
||||||
|
{
|
||||||
|
_lineEffect.Variables["ViewProjection"].SetData(viewProjection * transform);
|
||||||
|
_lineEffect.Variables["Color"].SetData(color);
|
||||||
|
_lineEffect.ApplyChanges();
|
||||||
|
_lineEffect.Bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use case 2 (Technically more efficient)
|
||||||
|
{
|
||||||
|
Effect fx = _lineEffect.Get();
|
||||||
|
fx.Variables["ViewProjection"].SetData(viewProjection * transform);
|
||||||
|
fx.Variables["Color"].SetData(color);
|
||||||
|
fx.ApplyChanges();
|
||||||
|
fx.Bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Auto Assets use comp time to expose all the fields, Methods and properties of the actual asset.
|
||||||
|
// They will automatically check if the asset for the handle changed and update accordingly.
|
||||||
|
// This means it is more convenient to use than both basic patterns and has all benefits of both.
|
||||||
|
//
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Design Code Examples
|
||||||
|
|
||||||
|
This directory (and its subdirectories) contains non functional code snippets showing the intended usage of certain systems of the engine.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "ModelAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "ModelAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
{
|
||||||
|
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.614328027,
|
||||||
|
Y = 0.239776045,
|
||||||
|
Z = 0.0315669999,
|
||||||
|
W = 0.751073837
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 0.999998868,
|
||||||
|
Y = 1,
|
||||||
|
Z = 1.00000095
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 1.30899811,
|
||||||
|
Y = 0.349065989,
|
||||||
|
Z = 0.349065989
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LightComponent = {
|
||||||
|
LightType = .Directional,
|
||||||
|
Illuminance = 10,
|
||||||
|
Color = {
|
||||||
|
R = 1,
|
||||||
|
G = 0.991771996,
|
||||||
|
B = 0.74086225
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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.216440007,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0,
|
||||||
|
W = 0.976296008
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 1,
|
||||||
|
Y = 1,
|
||||||
|
Z = 1
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 0.436332017,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CameraComponent = {
|
||||||
|
Primary = true,
|
||||||
|
ProjectionType = .InfinitePerspective,
|
||||||
|
PerspectiveFovY = 1.30899692,
|
||||||
|
PerspectiveNearPlane = 0.100000001,
|
||||||
|
PerspectiveFarPlane = 10000,
|
||||||
|
OrthographicHeight = 10,
|
||||||
|
OrthographicNearPlane = 0,
|
||||||
|
OrthographicFarPlane = 10,
|
||||||
|
AspectRatio = 2.19888878,
|
||||||
|
FixedAspectRatio = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id = 420718992779301759,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Entity"
|
||||||
|
},
|
||||||
|
TransformComponent = {
|
||||||
|
Position = {
|
||||||
|
X = 3,
|
||||||
|
Y = 0.501076996,
|
||||||
|
Z = -0.669378757
|
||||||
|
},
|
||||||
|
Rotation = {
|
||||||
|
X = 0.18301262,
|
||||||
|
Y = -0.683013558,
|
||||||
|
Z = 0.683012128,
|
||||||
|
W = 0.183013007
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 1,
|
||||||
|
Y = 1,
|
||||||
|
Z = 1
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 1.57079446,
|
||||||
|
Y = -2.61799312,
|
||||||
|
Z = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MeshComponent = {
|
||||||
|
Mesh = "Models/plane.glb"
|
||||||
|
},
|
||||||
|
MeshRendererComponent = {
|
||||||
|
Material = ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id = 8080288871271083510,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Rocket"
|
||||||
|
},
|
||||||
|
TransformComponent = {
|
||||||
|
Position = {
|
||||||
|
X = 2.37353587,
|
||||||
|
Y = 0.629615188,
|
||||||
|
Z = -1.34831977
|
||||||
|
},
|
||||||
|
Rotation = {
|
||||||
|
X = 0.270598024,
|
||||||
|
Y = -0.65328145,
|
||||||
|
Z = 0.65328151,
|
||||||
|
W = 0.270598054
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 0.999999881,
|
||||||
|
Y = 0.999999821,
|
||||||
|
Z = 0.999999702
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 1.57079637,
|
||||||
|
Y = -2.3561945,
|
||||||
|
Z = 8.94069743e-08
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MeshComponent = {
|
||||||
|
Mesh = "Models/plane.glb"
|
||||||
|
},
|
||||||
|
MeshRendererComponent = {
|
||||||
|
Material = "Textures/RocketMaterial.mat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
{
|
||||||
|
Name = "Scene name here pls!!!",
|
||||||
|
Entities = [
|
||||||
|
{
|
||||||
|
Id = 6000868443984780499,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Child"
|
||||||
|
},
|
||||||
|
SpriterRendererComponent = {
|
||||||
|
Color = {
|
||||||
|
R = 1,
|
||||||
|
G = 0.388184,
|
||||||
|
B = 0.090109,
|
||||||
|
A = 1
|
||||||
|
},
|
||||||
|
UvTransform = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 1,
|
||||||
|
W = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransformComponent = {
|
||||||
|
ParentId = 820806682740348099,
|
||||||
|
Position = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0.75,
|
||||||
|
Z = 0
|
||||||
|
},
|
||||||
|
Rotation = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0,
|
||||||
|
W = 1
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 0.5,
|
||||||
|
Y = 0.5,
|
||||||
|
Z = 1
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id = 820806682740348099,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Quad"
|
||||||
|
},
|
||||||
|
SpriterRendererComponent = {
|
||||||
|
Color = {
|
||||||
|
R = 0,
|
||||||
|
G = 0.551178,
|
||||||
|
B = 0.328787,
|
||||||
|
A = 1
|
||||||
|
},
|
||||||
|
UvTransform = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 1,
|
||||||
|
W = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransformComponent = {
|
||||||
|
Position = {
|
||||||
|
X = 0,
|
||||||
|
Y = 2,
|
||||||
|
Z = 0
|
||||||
|
},
|
||||||
|
Rotation = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0,
|
||||||
|
W = 1
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 1,
|
||||||
|
Y = 1,
|
||||||
|
Z = 1
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Rigidbody2D = {
|
||||||
|
BodyType = .Dynamic,
|
||||||
|
FixedRotation = false
|
||||||
|
},
|
||||||
|
BoxCollider2D = {
|
||||||
|
Offset = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0
|
||||||
|
},
|
||||||
|
Size = {
|
||||||
|
X = 0.5,
|
||||||
|
Y = 0.5
|
||||||
|
},
|
||||||
|
Density = 1,
|
||||||
|
Friction = 0.5,
|
||||||
|
Restitution = 0,
|
||||||
|
RestitutionThreshold = 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id = 15398726361722237419,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Floor"
|
||||||
|
},
|
||||||
|
SpriterRendererComponent = {
|
||||||
|
Color = {
|
||||||
|
R = 1,
|
||||||
|
G = 0.941886,
|
||||||
|
B = 0.401485,
|
||||||
|
A = 1
|
||||||
|
},
|
||||||
|
UvTransform = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 1,
|
||||||
|
W = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransformComponent = {
|
||||||
|
Position = {
|
||||||
|
X = 0,
|
||||||
|
Y = -0.5,
|
||||||
|
Z = 0
|
||||||
|
},
|
||||||
|
Rotation = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0,
|
||||||
|
W = 1
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 10,
|
||||||
|
Y = 1,
|
||||||
|
Z = 1
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Rigidbody2D = {
|
||||||
|
BodyType = .Static,
|
||||||
|
FixedRotation = false
|
||||||
|
},
|
||||||
|
BoxCollider2D = {
|
||||||
|
Offset = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0
|
||||||
|
},
|
||||||
|
Size = {
|
||||||
|
X = 0.5,
|
||||||
|
Y = 0.5
|
||||||
|
},
|
||||||
|
Density = 1,
|
||||||
|
Friction = 0.5,
|
||||||
|
Restitution = 0,
|
||||||
|
RestitutionThreshold = 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id = 1491484622542812645,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Camera"
|
||||||
|
},
|
||||||
|
TransformComponent = {
|
||||||
|
Position = {
|
||||||
|
X = 0,
|
||||||
|
Y = 1,
|
||||||
|
Z = -5
|
||||||
|
},
|
||||||
|
Rotation = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0,
|
||||||
|
W = 1
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 1,
|
||||||
|
Y = 1,
|
||||||
|
Z = 1
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CameraComponent = {
|
||||||
|
Primary = true,
|
||||||
|
ProjectionType = .InfinitePerspective,
|
||||||
|
PerspectiveFovY = 1.308997,
|
||||||
|
PerspectiveNearPlane = 0.1,
|
||||||
|
PerspectiveFarPlane = 10000,
|
||||||
|
OrthographicHeight = 10,
|
||||||
|
OrthographicNearPlane = 0,
|
||||||
|
OrthographicFarPlane = 10,
|
||||||
|
AspectRatio = 2.116827,
|
||||||
|
FixedAspectRatio = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
{
|
||||||
|
Name = "Scene name here pls!!!",
|
||||||
|
Entities = [
|
||||||
|
{
|
||||||
|
Id = 820806682740348099,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Quad"
|
||||||
|
},
|
||||||
|
SpriteRendererComponent = {
|
||||||
|
Color = {
|
||||||
|
R = 0,
|
||||||
|
G = 0.55117774,
|
||||||
|
B = 0.32878688,
|
||||||
|
A = 1
|
||||||
|
},
|
||||||
|
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"
|
||||||
|
},
|
||||||
|
SpriteRendererComponent = {
|
||||||
|
Color = {
|
||||||
|
R = 1,
|
||||||
|
G = 0.941886365,
|
||||||
|
B = 0.401484847,
|
||||||
|
A = 1
|
||||||
|
},
|
||||||
|
Sprite = "",
|
||||||
|
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.30899692,
|
||||||
|
PerspectiveNearPlane = 0.100000001,
|
||||||
|
PerspectiveFarPlane = 10000,
|
||||||
|
OrthographicHeight = 10,
|
||||||
|
OrthographicNearPlane = 0,
|
||||||
|
OrthographicFarPlane = 10,
|
||||||
|
AspectRatio = 3.22169805,
|
||||||
|
FixedAspectRatio = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id = 15710354273720487680,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Circle"
|
||||||
|
},
|
||||||
|
CircleRendererComponent = {
|
||||||
|
Color = {
|
||||||
|
R = 1,
|
||||||
|
G = 0,
|
||||||
|
B = 0,
|
||||||
|
A = 1
|
||||||
|
},
|
||||||
|
InnerRadius = 0.300000012,
|
||||||
|
Sprite = "",
|
||||||
|
UvTransform = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = 1,
|
||||||
|
W = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransformComponent = {
|
||||||
|
Position = {
|
||||||
|
X = -0.12120308,
|
||||||
|
Y = 0.660160363,
|
||||||
|
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.88327599,
|
||||||
|
Y = 0,
|
||||||
|
Z = -0.808795214
|
||||||
|
},
|
||||||
|
Rotation = {
|
||||||
|
X = 0.497987002,
|
||||||
|
Y = -0.103420995,
|
||||||
|
Z = 0.155380026,
|
||||||
|
W = 0.846859217
|
||||||
|
},
|
||||||
|
Scale = {
|
||||||
|
X = 0.999997795,
|
||||||
|
Y = 1,
|
||||||
|
Z = 1
|
||||||
|
},
|
||||||
|
EditorEulerRotation = {
|
||||||
|
X = 1.0908947,
|
||||||
|
Y = -0.340793997,
|
||||||
|
Z = 0.160857916
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LightComponent = {
|
||||||
|
LightType = .Directional,
|
||||||
|
Illuminance = 12.8999996,
|
||||||
|
Color = {
|
||||||
|
R = 0.985467017,
|
||||||
|
G = 1,
|
||||||
|
B = 0.569978654
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id = 721949523193525565,
|
||||||
|
NameComponent = {
|
||||||
|
Name = "Sphere"
|
||||||
|
},
|
||||||
|
TransformComponent = {
|
||||||
|
Position = {
|
||||||
|
X = 0,
|
||||||
|
Y = 0,
|
||||||
|
Z = -1.47202551
|
||||||
|
},
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
//=================================================================================================
|
||||||
|
//
|
||||||
|
// Baking Lab
|
||||||
|
// by MJP and David Neubelt
|
||||||
|
// http://mynameismjp.wordpress.com/
|
||||||
|
//
|
||||||
|
// All code licensed under the MIT license
|
||||||
|
//
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
|
// The code in this file was originally written by Stephen Hill (@self_shadow), who deserves all
|
||||||
|
// credit for coming up with this fit and implementing it. Buy him a beer next time you see him. :)
|
||||||
|
|
||||||
|
// Source: https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl
|
||||||
|
|
||||||
|
// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
|
||||||
|
static const float3x3 ACESInputMat =
|
||||||
|
{
|
||||||
|
{0.59719, 0.35458, 0.04823},
|
||||||
|
{0.07600, 0.90834, 0.01566},
|
||||||
|
{0.02840, 0.13383, 0.83777}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ODT_SAT => XYZ => D60_2_D65 => sRGB
|
||||||
|
static const float3x3 ACESOutputMat =
|
||||||
|
{
|
||||||
|
{ 1.60475, -0.53108, -0.07367},
|
||||||
|
{-0.10208, 1.10813, -0.00605},
|
||||||
|
{-0.00327, -0.07276, 1.07602}
|
||||||
|
};
|
||||||
|
|
||||||
|
float3 RRTAndODTFit(float3 v)
|
||||||
|
{
|
||||||
|
float3 a = v * (v + 0.0245786f) - 0.000090537f;
|
||||||
|
float3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 ACESFitted(float3 color)
|
||||||
|
{
|
||||||
|
color = mul(ACESInputMat, color);
|
||||||
|
|
||||||
|
// Apply RRT and ODT
|
||||||
|
color = RRTAndODTFit(color);
|
||||||
|
|
||||||
|
color = mul(ACESOutputMat, color);
|
||||||
|
|
||||||
|
// Clamp to [0, 1]
|
||||||
|
color = saturate(color);
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
cbuffer Constants : register(b0)
|
||||||
|
{
|
||||||
|
uint ClearValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 VS(float2 input : POSITION) : SV_Position
|
||||||
|
{
|
||||||
|
return float4(input, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint PS(float4 input : SV_Position) : SV_Target0
|
||||||
|
{
|
||||||
|
return ClearValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma Effect[VS = VS; PS = PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
Texture2D Texture : register(t0);
|
||||||
|
SamplerState TextureSampler : register(s0);
|
||||||
|
|
||||||
|
struct VS_IN
|
||||||
|
{
|
||||||
|
float2 Position : POSITION;
|
||||||
|
float2 TexCoord : TEXCOORD0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PS_IN
|
||||||
|
{
|
||||||
|
float4 Position : SV_POSITION;
|
||||||
|
float2 TexCoord : TEXCOORD;
|
||||||
|
};
|
||||||
|
|
||||||
|
PS_IN VS(VS_IN input)
|
||||||
|
{
|
||||||
|
PS_IN output;
|
||||||
|
|
||||||
|
output.Position = float4(input.Position, 0, 1);
|
||||||
|
output.TexCoord = input.TexCoord;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS(PS_IN input) : SV_TARGET
|
||||||
|
{
|
||||||
|
float4 color = Texture.Sample(TextureSampler, input.TexCoord);
|
||||||
|
|
||||||
|
return float4(pow(color.rgb, 1.0f / 2.2f), color.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma Effect[VS = VS; PS = PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#define PI 3.14159265358979323846f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the diffuse lighting of a lambertian surface
|
||||||
|
* @param diffuseColor (rho/ pi) * C_diffuse
|
||||||
|
* @param illuminanceColor The product of the "brightness" and the light color.
|
||||||
|
* @param n_dot_l The dot product of the surface normal and the light direction.
|
||||||
|
*/
|
||||||
|
float3 CalculateDiffuseReflection(float3 diffuseColor, float3 illuminanceColor, float3 n_dot_l)
|
||||||
|
{
|
||||||
|
float3 directColor = illuminanceColor * saturate(n_dot_l);
|
||||||
|
|
||||||
|
return (directColor * diffuseColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the blinn-phong-specular reflection
|
||||||
|
* @param n The normalized surface normal
|
||||||
|
* @param h The normalized half way vector (nrm(l + v))
|
||||||
|
* @param alpha The reflections alpha-value
|
||||||
|
* @param illuminanceColor The product of the "brightness" and the light color.
|
||||||
|
* @param n_dot_l The dot product of the surface normal and the light direction.
|
||||||
|
*/
|
||||||
|
float3 CalculateSpecularReflection(float3 n, float3 h, float alpha, float3 illuminanceColor, float n_dot_l)
|
||||||
|
{
|
||||||
|
float highlight = pow(saturate(dot(n, h)), alpha) * float(n_dot_l > 0.0);
|
||||||
|
return (illuminanceColor * highlight); // Todo: * SpecularColor
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* This File contains Function for PBR.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __PBR_HLSL__
|
||||||
|
#define __PBR_HLSL__
|
||||||
|
|
||||||
|
#include "ShaderHelpers.hlsl"
|
||||||
|
|
||||||
|
// #define PBR_IBL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal Distribution Function. (Trowbridge-Reits GGX)
|
||||||
|
* Calculates the relative surface area of microfacets exactly aligned to the halfway vector.
|
||||||
|
* @param normal The surface normal.
|
||||||
|
* @param halfway The halfway vector between the surface normal and the view direction.
|
||||||
|
* @param roughness Roughness value.
|
||||||
|
* @returns The relative surface area of microfacets exactly aligned to the halfway vector.
|
||||||
|
*/
|
||||||
|
float NormalDistributionGGX(float3 normal, float3 halfway, float roughness)
|
||||||
|
{
|
||||||
|
// Square roughness because it looks better
|
||||||
|
float a = roughness * roughness;
|
||||||
|
float aa = a * a;
|
||||||
|
|
||||||
|
float n_dot_h = max(dot(normal, halfway), 0.0f);
|
||||||
|
|
||||||
|
float denom = (n_dot_h * n_dot_h) * (aa - 1.0f) + 1.0f;
|
||||||
|
denom = PI * denom * denom;
|
||||||
|
|
||||||
|
return aa / denom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geometry Function calculating the overshadowing of microfacets based on roughness. (Schlick-Beckmann GGX).
|
||||||
|
* @param dot-product of normal vector and vector from surface to camera.
|
||||||
|
* @param k Roughness value.
|
||||||
|
*/
|
||||||
|
float GeometrySchlickGGX(float n_dot_v, float k)
|
||||||
|
{
|
||||||
|
return n_dot_v / (n_dot_v * (1 - k) + k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geometry Function calculating the overshadowing of microfacets based on roughness. (Smith)
|
||||||
|
* @param normal The surface normal.
|
||||||
|
* @param viewDir Vector from surface to viewer.
|
||||||
|
* @param lightDir Vector from surface to light source.
|
||||||
|
* @param roughness Roughness value.
|
||||||
|
*/
|
||||||
|
float GeometrySmith(float3 normal, float3 viewDir, float3 lightDir, float roughness)
|
||||||
|
{
|
||||||
|
#ifdef PBR_IBL
|
||||||
|
// IBL
|
||||||
|
float k = roughness * roughness / 2;
|
||||||
|
#else
|
||||||
|
// Direct lighting
|
||||||
|
float k = (roughness + 1.0f);
|
||||||
|
k = (k * k) / 8;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const float n_dot_v = max(dot(normal, viewDir), 0.0f);
|
||||||
|
float n_dot_l = max(dot(normal, lightDir), 0.0f);
|
||||||
|
|
||||||
|
return GeometrySchlickGGX(n_dot_v, k) * GeometrySchlickGGX(n_dot_l, k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the fresnel value.
|
||||||
|
* @param n_dot_v Dot product of the normal and view direction
|
||||||
|
* @param F0 base reflectivity.
|
||||||
|
*/
|
||||||
|
float3 FresnelSchlick(float n_dot_v, float3 F0)
|
||||||
|
{
|
||||||
|
return F0 + (1.0 - F0) * pow(clamp(1.0 - n_dot_v, 0.0, 1.0), 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __PBR_HLSL__
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#ifndef __SHADER_HELPERS_HLSL__
|
||||||
|
#define __SHADER_HELPERS_HLSL__
|
||||||
|
|
||||||
|
#define PI 3.14159265358979323846f
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculates the weighted sum of two normal vectors.
|
||||||
|
* nrm1: The first normal vector
|
||||||
|
* nrm2: The second normal vector
|
||||||
|
* a: The weight factor for nrm1
|
||||||
|
* b: The weight factor for nrm2
|
||||||
|
*/
|
||||||
|
float3 BlendNormals(float3 nrm1, float3 nrm2, float a, float b)
|
||||||
|
{
|
||||||
|
return normalize(float3(a * nrm1.x / nrm1.z + b * nrm2.x / nrm2.z,
|
||||||
|
a * nrm1.y / nrm1.z + b * nrm2.y / nrm2.z,
|
||||||
|
1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scales a normal vector by a factor where 0 results in the vector (0, 0, 1)
|
||||||
|
* nrm: The normal vector
|
||||||
|
* a: The scaling factor
|
||||||
|
*/
|
||||||
|
float3 ScaleNormal(float3 nrm1, float a)
|
||||||
|
{
|
||||||
|
return normalize(float3(a * nrm1.x / nrm1.z,
|
||||||
|
a * nrm1.y / nrm1.z,
|
||||||
|
1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scales a normal vector by a factor where 0 results in the vector (0, 0, 1)
|
||||||
|
* nrm: The normal vector
|
||||||
|
* a: The scaling factor
|
||||||
|
*/
|
||||||
|
float3 ScaleNormal(float3 nrm1, float2 a)
|
||||||
|
{
|
||||||
|
return normalize(float3(a.x * nrm1.x / nrm1.z,
|
||||||
|
a.y * nrm1.y / nrm1.z,
|
||||||
|
1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reconstructs the z-component of a normalized normal vector from a two-component value
|
||||||
|
* cnrm: The x- and y-components of a normalized normal vector
|
||||||
|
*/
|
||||||
|
float3 DecompressNormal(float2 cnrm)
|
||||||
|
{
|
||||||
|
return float3(cnrm, sqrt(1.0 - cnrm.x * cnrm.x - cnrm.y * cnrm.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reconstructs the tangent space from a normal and a tangent
|
||||||
|
* normal: The surface normal
|
||||||
|
* tangent: The surface tangent
|
||||||
|
* sigma: Defines the handedness of the tangent space matrix. 1.0 if it is right handend. -1.0 if it is left handed
|
||||||
|
*/
|
||||||
|
float3x3 ConstructTangentSpace(float3 normal, float3 tangent, float3 sigma)
|
||||||
|
{
|
||||||
|
float3 n = normalize(normal);
|
||||||
|
float3 t = normalize(tangent - n * dot(tangent, n));
|
||||||
|
float3 b = cross(n, t) * sigma;
|
||||||
|
|
||||||
|
return float3x3(t, b, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the luminance of an rgb-value.
|
||||||
|
* @param rgb The rgb color.
|
||||||
|
* @return The luminance of the given rgb color.
|
||||||
|
*/
|
||||||
|
float ColorToLuminance(float3 rgb)
|
||||||
|
{
|
||||||
|
return rgb.r * 0.212639 + rgb.g * 0.715169 + rgb.b * 0.072192;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extrancts the handedness of the bitangent that is encoded in the z-component of the tangent.
|
||||||
|
* @param tangentz The z-component of the tangent with the handedness encoded.
|
||||||
|
* @return The handedness of the bitangent (bitangent = handedness * tangent x normal)
|
||||||
|
*/
|
||||||
|
float GetBitangentHandedness(float tangentz)
|
||||||
|
{
|
||||||
|
// handedness is in least significant bit of tangent.z
|
||||||
|
uint z = asuint(tangentz);
|
||||||
|
return (z & 1) > 0 ? 1.0 : -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __SHADER_HELPERS_HLSL__
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include "ACES.hlsl"
|
||||||
|
|
||||||
|
Texture2D CameraTarget : register(t0);
|
||||||
|
SamplerState CameraTargetSampler : register(s0);
|
||||||
|
|
||||||
|
struct VS_IN
|
||||||
|
{
|
||||||
|
float2 Position : POSITION;
|
||||||
|
float2 TexCoord : TEXCOORD0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PS_IN
|
||||||
|
{
|
||||||
|
float4 Position : SV_POSITION;
|
||||||
|
float2 TexCoord : TEXCOORD;
|
||||||
|
};
|
||||||
|
|
||||||
|
PS_IN VS(VS_IN input)
|
||||||
|
{
|
||||||
|
PS_IN output;
|
||||||
|
|
||||||
|
output.Position = float4(input.Position, 0, 1);
|
||||||
|
output.TexCoord = input.TexCoord;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS(PS_IN input) : SV_TARGET
|
||||||
|
{
|
||||||
|
float4 rawColor = CameraTarget.Sample(CameraTargetSampler, input.TexCoord);
|
||||||
|
|
||||||
|
// float3 color = rawColor.rgb / (rawColor.rgb + 1.0f);
|
||||||
|
|
||||||
|
float3 color = ACESFitted(rawColor.rgb);
|
||||||
|
|
||||||
|
return float4(color, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma Effect[VS = VS; PS = PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#define EDITOR
|
||||||
|
|
||||||
Texture2D Texture : register(t0);
|
Texture2D Texture : register(t0);
|
||||||
SamplerState Sampler : register(s0);
|
SamplerState Sampler : register(s0);
|
||||||
|
|
||||||
@@ -14,6 +16,9 @@ struct VS_Input
|
|||||||
float4 Color : COLOR;
|
float4 Color : COLOR;
|
||||||
float4 UVTransform : TEXCOORD1;
|
float4 UVTransform : TEXCOORD1;
|
||||||
float InnerRadius : TEXCOORD2;
|
float InnerRadius : TEXCOORD2;
|
||||||
|
#ifdef EDITOR
|
||||||
|
uint EntityId : ENTITYID;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PS_Input
|
struct PS_Input
|
||||||
@@ -22,6 +27,9 @@ struct PS_Input
|
|||||||
float2 RawPos : TEXCOORD0;
|
float2 RawPos : TEXCOORD0;
|
||||||
float2 Texcoord : TEXCOORD1;
|
float2 Texcoord : TEXCOORD1;
|
||||||
float4 Color : COLOR;
|
float4 Color : COLOR;
|
||||||
|
#ifdef EDITOR
|
||||||
|
nointerpolation uint EntityId : ENTITYID;
|
||||||
|
#endif
|
||||||
float InnerRadius : TEXCOORD2;
|
float InnerRadius : TEXCOORD2;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,11 +43,25 @@ PS_Input VS(VS_Input input)
|
|||||||
output.Color = input.Color;
|
output.Color = input.Color;
|
||||||
output.InnerRadius = input.InnerRadius;
|
output.InnerRadius = input.InnerRadius;
|
||||||
|
|
||||||
|
#ifdef EDITOR
|
||||||
|
output.EntityId = input.EntityId;
|
||||||
|
#endif
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 PS(PS_Input input) : SV_Target0
|
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;
|
float2 uv = input.RawPos * 2;
|
||||||
|
|
||||||
float distance = 1.0f - length(uv);
|
float distance = 1.0f - length(uv);
|
||||||
@@ -53,10 +75,14 @@ float4 PS(PS_Input input) : SV_Target0
|
|||||||
// Discard invisible pixels
|
// Discard invisible pixels
|
||||||
clip(amount - 0.5f);
|
clip(amount - 0.5f);
|
||||||
|
|
||||||
float4 color = Texture.Sample(Sampler, input.Texcoord) * input.Color;
|
output.Color = Texture.Sample(Sampler, input.Texcoord) * input.Color;
|
||||||
color.a *= amount;
|
output.Color.a *= amount;
|
||||||
|
|
||||||
return color;
|
#ifdef EDITOR
|
||||||
|
output.EntityId = input.EntityId;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
#effect[VS=VS, PS=PS]
|
#pragma Effect[VS=VS; PS=PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -28,4 +28,4 @@ float4 PS(PS_Input input) : SV_Target0
|
|||||||
return Color;
|
return Color;
|
||||||
}
|
}
|
||||||
|
|
||||||
#effect[VS=VS, PS=PS]
|
#pragma Effect[VS=VS; PS=PS]
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#define EDITOR
|
||||||
|
|
||||||
|
cbuffer Constants : register(b0)
|
||||||
|
{
|
||||||
|
float4x4 ViewProjection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VS_Input
|
||||||
|
{
|
||||||
|
float4 Position : POSITION;
|
||||||
|
float4 Color : COLOR;
|
||||||
|
#ifdef EDITOR
|
||||||
|
uint EntityId : ENTITYID;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct PS_Input
|
||||||
|
{
|
||||||
|
float4 Position : SV_Position;
|
||||||
|
float4 Color : COLOR;
|
||||||
|
#ifdef EDITOR
|
||||||
|
nointerpolation uint EntityId : ENTITYID;
|
||||||
|
#endif
|
||||||
|
} VS_Output;
|
||||||
|
|
||||||
|
VS_Output VS(VS_Input input)
|
||||||
|
{
|
||||||
|
VS_Output output;
|
||||||
|
|
||||||
|
output.Position = mul(ViewProjection, input.Position);
|
||||||
|
output.Color = input.Color;
|
||||||
|
|
||||||
|
#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 = input.Color;
|
||||||
|
|
||||||
|
#ifdef EDITOR
|
||||||
|
output.EntityId = input.EntityId;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma Effect[VS=VS; PS=PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.EffectAssetLoaderConfig. Add [BonTarget] or force it */
|
||||||
|
}
|
||||||
@@ -90,4 +90,4 @@ float4 PS(PS_Input input) : SV_Target0
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#effect[VS=VS, PS=PS]
|
#pragma Effect[VS=VS; PS=PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
#define OutputEntityId
|
||||||
|
|
||||||
|
#include "ShaderHelpers.hlsl"
|
||||||
|
|
||||||
|
Texture2D AlbedoTexture : register(t0);
|
||||||
|
SamplerState AlbedoSampler : register(s0);
|
||||||
|
|
||||||
|
Texture2D<float3> NormalTexture : register(t1);
|
||||||
|
SamplerState NormalSampler : register(s1);
|
||||||
|
|
||||||
|
Texture2D<float> MetallicTexture : register(t2);
|
||||||
|
SamplerState MetallicSampler : register(s2);
|
||||||
|
|
||||||
|
Texture2D<float> RoughnessTexture : register(t3);
|
||||||
|
SamplerState RoughnessSampler : register(s3);
|
||||||
|
|
||||||
|
// Texture2D<float> AmbientTexture : register(t4);
|
||||||
|
// SamplerState AmbientSampler : register(s4);
|
||||||
|
|
||||||
|
#pragma EngineBuffer[ Name = "SceneConstants"; Binding = "Scene" ]
|
||||||
|
cbuffer SceneConstants : register(b0)
|
||||||
|
{
|
||||||
|
float4x4 ViewProjection;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma EngineBuffer[ Name = "ObjectConstants"; Binding = "Object" ]
|
||||||
|
cbuffer ObjectConstants : register(b1)
|
||||||
|
{
|
||||||
|
float4x4 Transform;
|
||||||
|
/**
|
||||||
|
* \brief Inverted and transposed transform matrix.
|
||||||
|
* \remarks This matrix is used in order to correctly transform normal vectors.
|
||||||
|
*/
|
||||||
|
float4x3 Transform_InvT;
|
||||||
|
#ifdef OutputEntityId
|
||||||
|
uint EntityId;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
cbuffer MaterialConstants : register(b2)
|
||||||
|
{
|
||||||
|
#pragma EditorVariable[ Name = "AlbedoColor"; Preview = "Albedo Color"; Type="Color" ]
|
||||||
|
float4 AlbedoColor = float4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
#pragma EditorVariable[ Name = "NormalScaling"; Preview = "Normal Scaling" ]
|
||||||
|
float2 NormalScaling = float2(1.0, 1.0);
|
||||||
|
#pragma EditorVariable[ Name = "MetallicFactor"; Preview = "Metallic Factor"; Min = 0.0f; Max = 1.0f ]
|
||||||
|
float MetallicFactor = 1.0;
|
||||||
|
#pragma EditorVariable[ Name = "RoughnessFactor"; Preview = "Rougness Factor"; Min = 0.0f; Max = 1.0f ]
|
||||||
|
float RoughnessFactor = 1.0;
|
||||||
|
// float AmbientFactor = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VS_IN
|
||||||
|
{
|
||||||
|
float3 Position : POSITION;
|
||||||
|
float3 Normal : NORMAL;
|
||||||
|
// Todo: Tangent.w... handedness
|
||||||
|
float3 Tangent : TANGENT;
|
||||||
|
float2 TexCoord : TEXCOORD;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PS_IN
|
||||||
|
{
|
||||||
|
float4 Position : SV_POSITION;
|
||||||
|
float3 WorldPosition : WORLDPOSITION;
|
||||||
|
float3 Normal : NORMAL;
|
||||||
|
float3 Tangent : TANGENT;
|
||||||
|
float2 TexCoord : TEXCOORD;
|
||||||
|
//nointerpolation float Handedness : HANDEDNESS;
|
||||||
|
#ifdef OutputEntityId
|
||||||
|
nointerpolation uint EntityId : ENTITYID;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
PS_IN VS(VS_IN input)
|
||||||
|
{
|
||||||
|
PS_IN output;
|
||||||
|
|
||||||
|
float4 worldPosition = mul(Transform, float4(input.Position, 1));
|
||||||
|
|
||||||
|
output.Position = mul(ViewProjection, worldPosition);
|
||||||
|
output.WorldPosition = worldPosition.xyz / worldPosition.w;
|
||||||
|
|
||||||
|
output.Normal = mul(Transform_InvT, input.Normal);
|
||||||
|
output.Tangent = mul((float3x3)Transform, input.Tangent);
|
||||||
|
// TODO: output.Handedness = input.Tangent.w
|
||||||
|
|
||||||
|
output.TexCoord = input.TexCoord;
|
||||||
|
|
||||||
|
output.EntityId = EntityId;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PS_OUT
|
||||||
|
{
|
||||||
|
float4 Albedo : SV_TARGET0;
|
||||||
|
// RG: TextureNormal.XY BA: GeoNrm.XY
|
||||||
|
float4 Normal : SV_TARGET1;
|
||||||
|
// R: GeoNrm.Z GBA: GeoTan.XYZ
|
||||||
|
float4 Tangent : SV_TARGET2;
|
||||||
|
float4 Position : SV_TARGET3;
|
||||||
|
// R: Metallicity G: Roughness B: Ambient
|
||||||
|
float4 Material : SV_TARGET4;
|
||||||
|
#ifdef OutputEntityId
|
||||||
|
uint EntityId : SV_TARGET5;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
PS_OUT PS(PS_IN input)
|
||||||
|
{
|
||||||
|
// Build tangent space
|
||||||
|
float3 normal = normalize(input.Normal);
|
||||||
|
float3 tangent = normalize(input.Tangent - dot(input.Tangent, normal) * input.Normal);
|
||||||
|
// TODO: float3 bitangent = input.Handedness * cross(normal, tangent);
|
||||||
|
float3 bitangent = -cross(normal, tangent);
|
||||||
|
|
||||||
|
//float3x3 tangentTransform = float3x3(tangent, bitangent, normal);
|
||||||
|
//tangentTransform = transpose(tangentTransform);
|
||||||
|
|
||||||
|
float4 texAlbedo = AlbedoTexture.Sample(AlbedoSampler, input.TexCoord);
|
||||||
|
float3 texNormal = NormalTexture.Sample(NormalSampler, input.TexCoord);
|
||||||
|
texNormal.xy = texNormal.xy * 2.0 - 1.0;
|
||||||
|
float texMetallic = MetallicTexture.Sample(MetallicSampler, input.TexCoord);
|
||||||
|
float texRoughness = RoughnessTexture.Sample(RoughnessSampler, input.TexCoord);
|
||||||
|
|
||||||
|
//float3 objectNormal = mul(tangentTransform, texNormal);
|
||||||
|
//float3 worldNormal = mul(objectNormal, (float3x3)Transform);
|
||||||
|
|
||||||
|
float4 finalAlbedo = texAlbedo * AlbedoColor;
|
||||||
|
float3 finalNormal = ScaleNormal(texNormal, NormalScaling);
|
||||||
|
float finalMetallic = texMetallic * MetallicFactor;
|
||||||
|
float finalRoughness = texRoughness * RoughnessFactor;
|
||||||
|
|
||||||
|
/////////////TODO: REMOVEME
|
||||||
|
|
||||||
|
//worldNormal = max(worldNormal - 10000000, normal);
|
||||||
|
//texAlbedo = max(texAlbedo - 10000000, 1.0);
|
||||||
|
//texMetallic = max(texMetallic - 10000000, 0.0);
|
||||||
|
//texRoughness = max(texRoughness - 10000000, 0.1);
|
||||||
|
|
||||||
|
/////////////TODO: END_REMOVEME
|
||||||
|
|
||||||
|
PS_OUT output;
|
||||||
|
output.Albedo = finalAlbedo;
|
||||||
|
//output.Normal = float4(objectNormal, 1.0);
|
||||||
|
output.Normal = float4(finalNormal.xy, normal.xy);
|
||||||
|
output.Tangent = float4(normal.z, tangent.xyz);
|
||||||
|
output.Position = float4(input.WorldPosition, 1.0);
|
||||||
|
output.Material = float4(finalMetallic, finalRoughness, 1.0, 0);
|
||||||
|
|
||||||
|
#ifdef OutputEntityId
|
||||||
|
output.EntityId = input.EntityId;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma Effect[VS = VS; PS = PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#include "ShaderHelpers.hlsl"
|
||||||
|
#include "PBR.hlsl"
|
||||||
|
|
||||||
|
#define Render 0
|
||||||
|
#define Inspect_NormalDistribution 1
|
||||||
|
#define Inspect_GeometryFunction 2
|
||||||
|
#define Inspect_Fresnel 3
|
||||||
|
#define Inspect_Normal 4
|
||||||
|
|
||||||
|
#define OUTPUT Render
|
||||||
|
|
||||||
|
SamplerState Sampler : register(s0);
|
||||||
|
|
||||||
|
Texture2D GBuffer_Albedo : register(t0);
|
||||||
|
Texture2D GBuffer_Normal : register(t1);
|
||||||
|
Texture2D GBuffer_Tangent : register(t2);
|
||||||
|
Texture2D GBuffer_Position : register(t3);
|
||||||
|
Texture2D GBuffer_Material : register(t4);
|
||||||
|
|
||||||
|
cbuffer Constants
|
||||||
|
{
|
||||||
|
float3 CameraPos;
|
||||||
|
float2 Scaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
cbuffer LightConstants
|
||||||
|
{
|
||||||
|
float3 LightColor;
|
||||||
|
float Illuminance;
|
||||||
|
float3 LightDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VS_IN
|
||||||
|
{
|
||||||
|
float2 Position : POSITION;
|
||||||
|
float2 TexCoord : TEXCOORD0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PS_IN
|
||||||
|
{
|
||||||
|
float4 Position : SV_POSITION;
|
||||||
|
float2 TexCoord : TEXCOORD;
|
||||||
|
};
|
||||||
|
|
||||||
|
PS_IN VS(VS_IN input)
|
||||||
|
{
|
||||||
|
PS_IN output;
|
||||||
|
|
||||||
|
output.Position = float4(input.Position, 0, 1);
|
||||||
|
output.TexCoord = input.TexCoord * Scaling;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS(PS_IN input) : SV_TARGET
|
||||||
|
{
|
||||||
|
// Load Data from GBuffer
|
||||||
|
float4 rawAlbedo = GBuffer_Albedo.Sample(Sampler, input.TexCoord);
|
||||||
|
float4 rawNormal = GBuffer_Normal.Sample(Sampler, input.TexCoord);
|
||||||
|
float4 rawTangent = GBuffer_Tangent.Sample(Sampler, input.TexCoord);
|
||||||
|
float4 rawPosition = GBuffer_Position.Sample(Sampler, input.TexCoord);
|
||||||
|
float4 rawMaterial = GBuffer_Material.Sample(Sampler, input.TexCoord);
|
||||||
|
|
||||||
|
// Extract data from GBuffer
|
||||||
|
float3 albedo = rawAlbedo.rgb;
|
||||||
|
//float3 surfaceNormal = normalize(rawNormal.xyz);
|
||||||
|
float3 worldPosition = rawPosition.xyz;
|
||||||
|
|
||||||
|
float metallic = rawMaterial.r;
|
||||||
|
float roughness = rawMaterial.g;
|
||||||
|
|
||||||
|
float3 textureNormal = DecompressNormal(rawNormal.rg);
|
||||||
|
float3 rawGeoNrm = float3(rawNormal.ba, rawTangent.r);
|
||||||
|
float3 rawGeoTan = rawTangent.gba;
|
||||||
|
|
||||||
|
// Reconstruct normal space
|
||||||
|
float3 normal = normalize(rawGeoNrm);
|
||||||
|
float3 tangent = normalize(rawGeoTan - dot(rawGeoTan, normal) * normal);
|
||||||
|
float3 bitangent = -cross(normal, tangent);
|
||||||
|
|
||||||
|
float3x3 tangentTransform = float3x3(tangent, bitangent, normal);
|
||||||
|
|
||||||
|
float3 surfaceNormal = mul(textureNormal, tangentTransform);
|
||||||
|
|
||||||
|
float3 lightDir = normalize(LightDir);
|
||||||
|
float3 viewDir = normalize(CameraPos - worldPosition.xyz);
|
||||||
|
float3 halfway = normalize(lightDir + viewDir);
|
||||||
|
|
||||||
|
float n_dot_v = max(dot(surfaceNormal, viewDir), 0.0f);
|
||||||
|
float n_dot_h = max(dot(surfaceNormal, halfway), 0.0f);
|
||||||
|
float n_dot_l = max(dot(surfaceNormal, lightDir), 0.0f);
|
||||||
|
|
||||||
|
float nrmDist = NormalDistributionGGX(surfaceNormal, halfway, roughness);
|
||||||
|
float geo = GeometrySmith(surfaceNormal, viewDir, lightDir, roughness);
|
||||||
|
|
||||||
|
float3 F0 = 0.04f;
|
||||||
|
F0 = lerp(F0, albedo, metallic);
|
||||||
|
float3 fresnel = FresnelSchlick(n_dot_h, F0);
|
||||||
|
|
||||||
|
float3 ks = fresnel;
|
||||||
|
float3 kd = 1.0f - ks;
|
||||||
|
|
||||||
|
// Metals have no diffuse light
|
||||||
|
kd *= 1.0f - metallic;
|
||||||
|
|
||||||
|
float3 diffuse = albedo / PI;
|
||||||
|
float3 specular = (nrmDist * fresnel * geo) / max(4 * n_dot_v * n_dot_l, 0.0001f);
|
||||||
|
|
||||||
|
float3 luminanceColor = LightColor * Illuminance;
|
||||||
|
|
||||||
|
float3 final = (kd * diffuse + specular) * luminanceColor * n_dot_l;
|
||||||
|
|
||||||
|
#if OUTPUT == Inspect_NormalDistribution
|
||||||
|
final = max(final - 10000000, nrmDist.xxx);
|
||||||
|
#elif OUTPUT == Inspect_GeometryFunction
|
||||||
|
final = max(final - 10000000, geo.xxx);
|
||||||
|
#elif OUTPUT == Inspect_Fresnel
|
||||||
|
final = max(final - 10000000, fresnel);
|
||||||
|
#elif OUTPUT == Inspect_Normal
|
||||||
|
final = max(final - 10000000, surfaceNormal / 2 + 0.5f);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return float4(final, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma Effect[VS = VS; PS = PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#define EDITOR
|
||||||
|
|
||||||
Texture2D Texture : register(t0);
|
Texture2D Texture : register(t0);
|
||||||
SamplerState Sampler : register(s0);
|
SamplerState Sampler : register(s0);
|
||||||
|
|
||||||
@@ -13,6 +15,9 @@ struct VS_Input
|
|||||||
float4x4 Transform : TRANSFORM;
|
float4x4 Transform : TRANSFORM;
|
||||||
float4 Color : COLOR;
|
float4 Color : COLOR;
|
||||||
float4 UVTransform : TEXCOORD1;
|
float4 UVTransform : TEXCOORD1;
|
||||||
|
#ifdef EDITOR
|
||||||
|
uint EntityId : ENTITYID;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PS_Input
|
struct PS_Input
|
||||||
@@ -20,6 +25,9 @@ struct PS_Input
|
|||||||
float4 Position : SV_Position;
|
float4 Position : SV_Position;
|
||||||
float2 Texcoord : TEXCOORD;
|
float2 Texcoord : TEXCOORD;
|
||||||
float4 Color : COLOR;
|
float4 Color : COLOR;
|
||||||
|
#ifdef EDITOR
|
||||||
|
nointerpolation uint EntityId : ENTITYID;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
PS_Input VS(VS_Input input)
|
PS_Input VS(VS_Input input)
|
||||||
@@ -30,12 +38,37 @@ PS_Input VS(VS_Input input)
|
|||||||
output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
|
output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
|
||||||
output.Color = input.Color;
|
output.Color = input.Color;
|
||||||
|
|
||||||
|
// Premultiply Alpha
|
||||||
|
output.Color.rgb *= output.Color.a;
|
||||||
|
|
||||||
|
#ifdef EDITOR
|
||||||
|
output.EntityId = input.EntityId;
|
||||||
|
#endif
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 PS(PS_Input input) : SV_Target0
|
struct PS_Output
|
||||||
{
|
{
|
||||||
return Texture.Sample(Sampler, input.Texcoord) * input.Color;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
#effect[VS=VS, PS=PS]
|
#pragma Effect[VS = VS; PS = PS]
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
Texture2D Texture : register(t0);
|
||||||
|
SamplerState Sampler : register(s0);
|
||||||
|
|
||||||
|
cbuffer Constants
|
||||||
|
{
|
||||||
|
float4x4 ViewProjection;
|
||||||
|
|
||||||
|
float ColorOffset = 0.5f;
|
||||||
|
float AlphaOffset = 0.0f;
|
||||||
|
float ColorScale = 0.5f;
|
||||||
|
float AlphaScale = 1.0f;
|
||||||
|
|
||||||
|
float2 TextureSizeInPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VS_Input
|
||||||
|
{
|
||||||
|
float2 Position : POSITION;
|
||||||
|
float2 Texcoord : TEXCOORD0;
|
||||||
|
float4x4 Transform : TRANSFORM;
|
||||||
|
float4 Color : COLOR;
|
||||||
|
float4 UVTransform : TEXCOORD1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PS_Input
|
||||||
|
{
|
||||||
|
float4 Position : SV_Position;
|
||||||
|
float2 Texcoord : TEXCOORD0;
|
||||||
|
float4 Color : COLOR;
|
||||||
|
};
|
||||||
|
|
||||||
|
PS_Input VS(VS_Input input)
|
||||||
|
{
|
||||||
|
PS_Input output;
|
||||||
|
|
||||||
|
output.Position = mul(ViewProjection, mul(input.Transform, float4(input.Position, 0.0f, 1.0f)));
|
||||||
|
output.Texcoord = input.UVTransform.xy + input.UVTransform.zw * input.Texcoord;
|
||||||
|
output.Color = input.Color;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS(PS_Input input) : SV_Target0
|
||||||
|
{
|
||||||
|
float4 color = Texture.Sample(Sampler, input.Texcoord);
|
||||||
|
|
||||||
|
float4 final = float4(ColorOffset.xxx, AlphaOffset) + color * float4(ColorScale.xxx, AlphaScale);
|
||||||
|
|
||||||
|
return final;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma Effect[VS=VS; PS=PS]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EffectAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EffectAssetLoaderConfig){}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 119 B |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
MagFilter = .Linear,
|
||||||
|
MipFilter = .Linear,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Clamp,
|
||||||
|
AddressModeV = .Clamp,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipMinLOD = -340282346638528859811704183484516925440,
|
||||||
|
MipMaxLOD = 340282346638528859811704183484516925440,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
MagFilter = .Linear,
|
||||||
|
MipFilter = .Linear,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Clamp,
|
||||||
|
AddressModeV = .Clamp,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipMinLOD = -340282346638528859811704183484516925440,
|
||||||
|
MipMaxLOD = 340282346638528859811704183484516925440,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
Effect = "Shaders/myEffect.hlsl",
|
||||||
|
Textures = [
|
||||||
|
"AlbedoTexture": "Textures/rocket.png",
|
||||||
|
"NormalTexture": "Textures/TestMat/rustediron2_normal.png",
|
||||||
|
"MetallicTexture": "Textures/TestMat/rustediron2_metallic.png",
|
||||||
|
"RoughnessTexture": "Textures/TestMat/rustediron2_roughness.png"
|
||||||
|
],
|
||||||
|
Variables = [
|
||||||
|
"AlbedoColor": .ColorRGBA{
|
||||||
|
Value = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NormalScaling": .Float2{
|
||||||
|
Value = {
|
||||||
|
X = 1,
|
||||||
|
Y = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MetallicFactor": .Float{
|
||||||
|
Value = 0
|
||||||
|
},
|
||||||
|
"RoughnessFactor": .Float{
|
||||||
|
Value = 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "MaterialAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}/* No reflection data for GlitchyEditor.Assets.ModelAssetLoaderConfig. Add [BonTarget] or force it */
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 119 B |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
MagFilter = .Linear,
|
||||||
|
MipFilter = .Linear,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Clamp,
|
||||||
|
AddressModeV = .Clamp,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipMinLOD = -340282346638528859811704183484516925440,
|
||||||
|
MipMaxLOD = 340282346638528859811704183484516925440,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 10 MiB |
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_generateMipMaps = true,
|
||||||
|
_isSrgb = true,
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Anisotropic,
|
||||||
|
MagFilter = .Anisotropic,
|
||||||
|
MipFilter = .Anisotropic,
|
||||||
|
FilterMode = .Default,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Wrap,
|
||||||
|
AddressModeV = .Wrap,
|
||||||
|
AddressModeW = .Wrap,
|
||||||
|
MipLODBias = 0,
|
||||||
|
MipMinLOD = 0,
|
||||||
|
MipMaxLOD = 3,
|
||||||
|
MaxAnisotropy = 16,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.3 MiB |
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_generateMipMaps = false,
|
||||||
|
_isSrgb = false,
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
MagFilter = .Linear,
|
||||||
|
MipFilter = .Linear,
|
||||||
|
FilterMode = .Default,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Wrap,
|
||||||
|
AddressModeV = .Wrap,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipLODBias = 0,
|
||||||
|
MipMinLOD = -3.40282347e+38,
|
||||||
|
MipMaxLOD = 3.40282347e+38,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 7.4 MiB |
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_generateMipMaps = false,
|
||||||
|
_isSrgb = false,
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
MagFilter = .Linear,
|
||||||
|
MipFilter = .Linear,
|
||||||
|
FilterMode = .Default,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Wrap,
|
||||||
|
AddressModeV = .Wrap,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipLODBias = 2.5999999,
|
||||||
|
MipMinLOD = -Infinity,
|
||||||
|
MipMaxLOD = Infinity,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.0 MiB |
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_generateMipMaps = false,
|
||||||
|
_isSrgb = true,
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
MagFilter = .Linear,
|
||||||
|
MipFilter = .Linear,
|
||||||
|
FilterMode = .Default,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Wrap,
|
||||||
|
AddressModeV = .Wrap,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipLODBias = 0,
|
||||||
|
MipMinLOD = -3.40282347e+38,
|
||||||
|
MipMaxLOD = 3.40282347e+38,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
Effect = "Shaders\\myEffect.hlsl",
|
||||||
|
Textures = [
|
||||||
|
"AlbedoTexture": "Textures\\TestMat\\rustediron2_albedo.png",
|
||||||
|
"NormalTexture": "Textures\\TestMat\\rustediron2_normal.png",
|
||||||
|
"MetallicTexture": "Textures\\TestMat\\rustediron2_metallic.png",
|
||||||
|
"RoughnessTexture": "Textures\\TestMat\\rustediron2_roughness.png"
|
||||||
|
],
|
||||||
|
Variables = [
|
||||||
|
"AlbedoColor": .ColorRGBA{
|
||||||
|
Value = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NormalScaling": .Float2{
|
||||||
|
Value = {
|
||||||
|
X = 1,
|
||||||
|
Y = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MetallicFactor": .Float{
|
||||||
|
Value = 1
|
||||||
|
},
|
||||||
|
"RoughnessFactor": .Float{
|
||||||
|
Value = 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "MaterialAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.ModelAssetLoaderConfig){}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 119 B |
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
MagFilter = .Linear,
|
||||||
|
MipFilter = .Linear,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Clamp,
|
||||||
|
AddressModeV = .Clamp,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipMinLOD = -340282346638528859811704183484516925440,
|
||||||
|
MipMaxLOD = 340282346638528859811704183484516925440,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_isSrgb = true,
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
MagFilter = .Linear,
|
||||||
|
MipFilter = .Linear,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Clamp,
|
||||||
|
AddressModeV = .Clamp,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipMinLOD = -3.40282347e+38,
|
||||||
|
MipMaxLOD = 3.40282347e+38,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
AssetLoader = "EditorTextureAssetLoader",
|
||||||
|
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
|
||||||
|
_isSrgb = true,
|
||||||
|
_samplerStateDescription = {
|
||||||
|
MinFilter = .Linear,
|
||||||
|
ComparisonFunction = .Never,
|
||||||
|
AddressModeU = .Clamp,
|
||||||
|
AddressModeV = .Clamp,
|
||||||
|
AddressModeW = .Clamp,
|
||||||
|
MipMinLOD = -340282346638528859811704183484516925440,
|
||||||
|
MipMaxLOD = 340282346638528859811704183484516925440,
|
||||||
|
MaxAnisotropy = 1,
|
||||||
|
BorderColor = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using System;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using System.IO;
|
||||||
|
using Bon;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
|
||||||
|
namespace GlitchyEditor;
|
||||||
|
|
||||||
|
[BonTarget]
|
||||||
|
class AssetConfig
|
||||||
|
{
|
||||||
|
[BonIgnore]
|
||||||
|
public bool IgnoreFile = false;
|
||||||
|
|
||||||
|
[BonInclude]
|
||||||
|
public String AssetLoader ~ delete _;
|
||||||
|
|
||||||
|
[BonInclude]
|
||||||
|
public AssetLoaderConfig Config ~ delete _;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetFile
|
||||||
|
{
|
||||||
|
private EditorContentManager _contentManager;
|
||||||
|
|
||||||
|
private String _path;
|
||||||
|
private String _identifier;
|
||||||
|
private String _assetConfigPath;
|
||||||
|
|
||||||
|
private AssetConfig _assetConfig ~ delete _;
|
||||||
|
|
||||||
|
private bool _isDirectory;
|
||||||
|
|
||||||
|
private Asset _loadedAsset;
|
||||||
|
|
||||||
|
public bool IsDirectory => _isDirectory;
|
||||||
|
|
||||||
|
public StringView FilePath => _path;
|
||||||
|
public StringView Identifier => _identifier;
|
||||||
|
|
||||||
|
public const String ConfigFileExtension = ".ass";
|
||||||
|
|
||||||
|
public AssetConfig AssetConfig => _assetConfig;
|
||||||
|
|
||||||
|
public Asset 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,426 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AssetIdentifier
|
||||||
|
{
|
||||||
|
public const char8 DirectorySeparatorChar = '/';
|
||||||
|
|
||||||
|
public static void Fixup(String assetIdentifier)
|
||||||
|
{
|
||||||
|
const String DotSeperator = $".{DirectorySeparatorChar}";
|
||||||
|
const String SeperatorDot = $"{DirectorySeparatorChar}.";
|
||||||
|
|
||||||
|
assetIdentifier.Replace('\\', DirectorySeparatorChar);
|
||||||
|
assetIdentifier.Replace(DotSeperator, "");
|
||||||
|
assetIdentifier.Replace(SeperatorDot, "");
|
||||||
|
|
||||||
|
if (assetIdentifier.StartsWith(DirectorySeparatorChar))
|
||||||
|
assetIdentifier.Remove(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
FileRenamed(oldName, newName);
|
||||||
|
/*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 .Err, 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");
|
||||||
|
_assetHierarchy->IsDirectory = true;
|
||||||
|
|
||||||
|
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);
|
||||||
|
AssetIdentifier.Fixup(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FileRenamed(StringView oldFilePath, StringView newFilePath)
|
||||||
|
{
|
||||||
|
var oldFilePath;
|
||||||
|
|
||||||
|
// Ignore Config files.
|
||||||
|
if (oldFilePath.EndsWith(AssetFile.ConfigFileExtension))
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
String oldFileNameWithContentRoot = scope .();
|
||||||
|
Path.InternalCombine(oldFileNameWithContentRoot, _contentDirectory, oldFilePath);
|
||||||
|
|
||||||
|
String newFileNameWithContentRoot = scope .();
|
||||||
|
Path.InternalCombine(newFileNameWithContentRoot, _contentDirectory, newFilePath);
|
||||||
|
|
||||||
|
// Rename config file
|
||||||
|
{
|
||||||
|
String oldConfigFileName = scope $"{oldFileNameWithContentRoot}{AssetFile.ConfigFileExtension}";
|
||||||
|
String newConfigFileName = scope $"{newFileNameWithContentRoot}{AssetFile.ConfigFileExtension}";
|
||||||
|
|
||||||
|
if (File.Exists(oldConfigFileName) && !File.Exists(newConfigFileName))
|
||||||
|
{
|
||||||
|
if (File.Move(oldConfigFileName, newConfigFileName) case .Err(let value))
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to move file {oldConfigFileName} to {newConfigFileName}. Code: {value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeResult = GetNodeFromPath(oldFileNameWithContentRoot);
|
||||||
|
|
||||||
|
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 \"{oldFileNameWithContentRoot}\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pathToAssetNode.Remove(oldFileNameWithContentRoot);
|
||||||
|
|
||||||
|
node->Path.Set(newFileNameWithContentRoot);
|
||||||
|
_pathToAssetNode.Add(node->Path, node);
|
||||||
|
|
||||||
|
node->Name.Clear();
|
||||||
|
Path.GetFileName(newFileNameWithContentRoot, node->Name);
|
||||||
|
|
||||||
|
String oldIdentifier = scope .(node->AssetFile.[Friend]_identifier);
|
||||||
|
|
||||||
|
node->AssetFile.[Friend]_path.Set(node->Path);
|
||||||
|
|
||||||
|
node->AssetFile.[Friend]_identifier.Set(newFilePath);
|
||||||
|
AssetIdentifier.Fixup(node->AssetFile.[Friend]_identifier);
|
||||||
|
|
||||||
|
node->AssetFile.[Friend]_assetConfigPath..Set(node->Path).Append(AssetFile.ConfigFileExtension);
|
||||||
|
|
||||||
|
OnFileRenamed(node.Value, oldIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void FileContentChangedFunc(AssetNode node);
|
||||||
|
|
||||||
|
public Event<FileContentChangedFunc> OnFileContentChanged ~ _.Dispose();
|
||||||
|
|
||||||
|
public delegate void FileRenamedFunc(AssetNode node, StringView oldName);
|
||||||
|
|
||||||
|
public Event<FileRenamedFunc> OnFileRenamed ~ _.Dispose();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace GlitchyEditor.Assets;
|
||||||
|
|
||||||
|
abstract class AssetPropertiesEditor
|
||||||
|
{
|
||||||
|
private AssetFile _asset;
|
||||||
|
|
||||||
|
public AssetFile Asset => _asset;
|
||||||
|
|
||||||
|
public this(AssetFile asset)
|
||||||
|
{
|
||||||
|
_asset = asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void ShowEditor();
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset GetPlaceholderAsset(Type assetType)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset GetErrorAsset(Type assetType)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using GlitchyEngine.Content;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Assets;
|
||||||
|
|
||||||
|
interface IAssetSaver
|
||||||
|
{
|
||||||
|
Result<void> EditorSaveAsset(Stream file, Asset asset, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Assets;
|
||||||
|
|
||||||
|
interface IReloadingAssetLoader
|
||||||
|
{
|
||||||
|
public void ReloadAsset(AssetFile assetFile, Stream data);
|
||||||
|
}
|
||||||
@@ -0,0 +1,491 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
AssetHandle handle = .Invalid;
|
||||||
|
|
||||||
|
if (ImGui.BeginDragDropTarget())
|
||||||
|
{
|
||||||
|
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM");
|
||||||
|
|
||||||
|
if (payload != null)
|
||||||
|
{
|
||||||
|
StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize);
|
||||||
|
|
||||||
|
handle = Content.LoadAsset(fullpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
AssetHandle<Texture2D> newTexture = Content.LoadAsset(path);
|
||||||
|
|
||||||
|
//newTexture.Get().SamplerState = SamplerStateManager.AnisotropicWrap;
|
||||||
|
material.SetTexture(texture.key, newTexture.Cast<Texture>());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect fx = Content.GetAsset<Effect>(contentManager.LoadAsset(materialFile.Effect, true), contentManager);
|
||||||
|
|
||||||
|
Material material = new Material(fx);
|
||||||
|
|
||||||
|
for (let (slotName, textureIdentifier) in materialFile.Textures)
|
||||||
|
{
|
||||||
|
AssetHandle<Texture> texture = contentManager.LoadAsset(textureIdentifier);
|
||||||
|
|
||||||
|
if (texture.IsInvalid)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to load texture \"{textureIdentifier}\".");
|
||||||
|
}
|
||||||
|
|
||||||
|
material.SetTexture(slotName, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
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($"Unknown variable type of variable {slotName}: {variableValue}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 .();
|
||||||
|
|
||||||
|
for (let (slotName, texture) in material.[Friend]_textures)
|
||||||
|
{
|
||||||
|
Texture textureAsset = texture.Get();
|
||||||
|
|
||||||
|
materialFile.Textures.Add(new String(slotName), new String(textureAsset?.Identifier ?? ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect effect = material.Effect;
|
||||||
|
|
||||||
|
if (effect == null)
|
||||||
|
return .Ok;
|
||||||
|
|
||||||
|
for (let (name, arguments) in effect.[Friend]_variableDescriptions)
|
||||||
|
{
|
||||||
|
VariableValue variableValue = .None;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
materialFile.Variables.Add(new String(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material _placeholder;
|
||||||
|
Material _error;
|
||||||
|
|
||||||
|
public Asset GetPlaceholderAsset(Type assetType)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset GetErrorAsset(Type assetType)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
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 ?? assetIdentifier, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset GetPlaceholderAsset(Type assetType)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset GetErrorAsset(Type assetType)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bon.Integrated;
|
||||||
|
|
||||||
|
extension Serialize
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
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:
|
||||||
|
Log.EngineLogger.Error("Unknown texture format.");
|
||||||
|
texture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
SetSampler(texture, config);
|
||||||
|
texture.[Friend]Complete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8* rawData = null;
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
if (rawData != null)
|
||||||
|
LodePng.LodePng.Free(rawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 width = 0, height = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceScope!("LodePng.LodePng.Decode32");
|
||||||
|
uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count);
|
||||||
|
if (errorCode != 0)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to decode PNG file {errorCode}.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Texture LoadDds(Stream data, EditorTextureAssetLoaderConfig config)
|
||||||
|
{
|
||||||
|
// TODO: Move the loading of Dds files here.
|
||||||
|
Texture2D texture = new [Friend]Texture2D(data);
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetSampler(Texture texture, EditorTextureAssetLoaderConfig config)
|
||||||
|
{
|
||||||
|
using (SamplerState samplerState = SamplerStateManager.GetSampler(config.SamplerStateDescription))
|
||||||
|
{
|
||||||
|
texture.SamplerState = samplerState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Texture2D _placeholder2D;
|
||||||
|
private static Texture2D _error2D;
|
||||||
|
|
||||||
|
public Asset GetPlaceholderAsset(Type assetType)
|
||||||
|
{
|
||||||
|
switch (assetType)
|
||||||
|
{
|
||||||
|
case typeof(Texture2D):
|
||||||
|
fallthrough;
|
||||||
|
default:
|
||||||
|
if (_placeholder2D == null)
|
||||||
|
{
|
||||||
|
Texture2DDesc desc = .(1, 1, .R8G8B8A8_UNorm, 1, 1, .Immutable, .None);
|
||||||
|
|
||||||
|
_placeholder2D = new Texture2D(desc);
|
||||||
|
_placeholder2D.SamplerState = SamplerStateManager.PointWrap;
|
||||||
|
Color color = Color.Cyan;
|
||||||
|
_placeholder2D.SetData<Color>(&color);
|
||||||
|
|
||||||
|
Content.ManageAsset(_placeholder2D);
|
||||||
|
_placeholder2D.ReleaseRef();
|
||||||
|
|
||||||
|
_placeholder2D.[Friend]Complete = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _placeholder2D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset GetErrorAsset(Type assetType)
|
||||||
|
{
|
||||||
|
switch (assetType)
|
||||||
|
{
|
||||||
|
case typeof(Texture2D):
|
||||||
|
fallthrough;
|
||||||
|
default:
|
||||||
|
if (_error2D == null)
|
||||||
|
{
|
||||||
|
Texture2DDesc desc = .(2, 2, .R8G8B8A8_UNorm, 1, 1, .Immutable, .None);
|
||||||
|
|
||||||
|
_error2D = new Texture2D(desc);
|
||||||
|
_error2D.SamplerState = SamplerStateManager.PointWrap;
|
||||||
|
Color[4] color = .(Color.HotPink, Color.Black, Color.Black, Color.HotPink);
|
||||||
|
_error2D.SetData<Color>(&color);
|
||||||
|
|
||||||
|
Content.ManageAsset(_error2D);
|
||||||
|
_error2D.ReleaseRef();
|
||||||
|
|
||||||
|
_placeholder2D.[Friend]Complete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _error2D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ using GlitchyEngine.World;
|
|||||||
using System;
|
using System;
|
||||||
using GlitchyEngine.Math;
|
using GlitchyEngine.Math;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
|
||||||
namespace GlitchyEditor.EditWindows
|
namespace GlitchyEditor.EditWindows
|
||||||
{
|
{
|
||||||
@@ -57,7 +60,14 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
ShowComponentEditor<TransformComponent>("Transform", entity, => ShowTransformComponentEditor);
|
ShowComponentEditor<TransformComponent>("Transform", entity, => ShowTransformComponentEditor);
|
||||||
ShowComponentEditor<CameraComponent>("Camera", entity, => ShowCameraComponentEditor, => ShowComponentContextMenu<CameraComponent>);
|
ShowComponentEditor<CameraComponent>("Camera", entity, => ShowCameraComponentEditor, => ShowComponentContextMenu<CameraComponent>);
|
||||||
ShowComponentEditor<SpriterRendererComponent>("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu<SpriterRendererComponent>);
|
ShowComponentEditor<SpriteRendererComponent>("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu<SpriteRendererComponent>);
|
||||||
|
ShowComponentEditor<CircleRendererComponent>("Circle Renderer", entity, => ShowCircleRendererComponentEditor, => ShowComponentContextMenu<CircleRendererComponent>);
|
||||||
|
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);
|
ShowAddComponentButton(entity);
|
||||||
}
|
}
|
||||||
@@ -108,18 +118,18 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
private static void ShowNameComponentEditor(Entity entity)
|
private static void ShowNameComponentEditor(Entity entity)
|
||||||
{
|
{
|
||||||
if (!entity.HasComponent<DebugNameComponent>())
|
if (!entity.HasComponent<NameComponent>())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char8[256] nameBuffer = default;
|
char8[256] nameBuffer = default;
|
||||||
|
|
||||||
DebugNameComponent* component = entity.GetComponent<DebugNameComponent>();
|
NameComponent* component = entity.GetComponent<NameComponent>();
|
||||||
|
|
||||||
String name = null;
|
StringView name = null;
|
||||||
|
|
||||||
if(component != null)
|
if(component != null)
|
||||||
{
|
{
|
||||||
name = component.DebugName;
|
name = component.Name;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -133,11 +143,10 @@ namespace GlitchyEditor.EditWindows
|
|||||||
{
|
{
|
||||||
if(component == null)
|
if(component == null)
|
||||||
{
|
{
|
||||||
component = entity.AddComponent<DebugNameComponent>();
|
component = entity.AddComponent<NameComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
component.DebugName.Clear();
|
component.Name = StringView(&nameBuffer);
|
||||||
component.DebugName.Append(&nameBuffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,9 +250,285 @@ namespace GlitchyEditor.EditWindows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ShowSpriteRendererComponentEditor(Entity entity, SpriterRendererComponent* spriteRendererComponent)
|
private static void ShowSpriteRendererComponentEditor(Entity entity, SpriteRendererComponent* spriteRendererComponent)
|
||||||
{
|
{
|
||||||
ImGui.ColorEdit4("Color", ref spriteRendererComponent.Color);
|
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);
|
||||||
|
|
||||||
|
spriteRendererComponent.Sprite = Content.LoadAsset(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ImGui.EditVector<4>("UV Transform", ref *(float[4]*)&spriteRendererComponent.UvTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShowCircleRendererComponentEditor(Entity entity, CircleRendererComponent* circleRendererComponent)
|
||||||
|
{
|
||||||
|
ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(circleRendererComponent.Color);
|
||||||
|
if (ImGui.ColorEdit4("Color", ref spriteColor))
|
||||||
|
circleRendererComponent.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);
|
||||||
|
|
||||||
|
circleRendererComponent.Sprite = Content.LoadAsset(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ImGui.EditVector<4>("UV Transform", ref *(float[4]*)&circleRendererComponent.UvTransform);
|
||||||
|
|
||||||
|
ImGui.DragFloat("Inner Radius", &circleRendererComponent.InnerRadius, 0.1f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
meshRendererComponent.Material = Content.LoadAsset(fullpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);*/
|
||||||
|
|
||||||
|
meshComponent.Mesh = Content.LoadAsset(fullpath);
|
||||||
|
|
||||||
|
// 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)
|
private static void ShowAddComponentButton(Entity entity)
|
||||||
@@ -259,6 +544,10 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
void ShowComponentButton<TComponent>(String name) where TComponent : struct, new
|
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;
|
float textWidth = ImGui.CalcTextSize(name.CStr()).x;
|
||||||
buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2);
|
buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2);
|
||||||
|
|
||||||
@@ -287,7 +576,14 @@ namespace GlitchyEditor.EditWindows
|
|||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
ShowComponentButton<CameraComponent>("Camera");
|
ShowComponentButton<CameraComponent>("Camera");
|
||||||
ShowComponentButton<SpriterRendererComponent>("Sprite Renderer");
|
ShowComponentButton<SpriteRendererComponent>("Sprite Renderer");
|
||||||
|
ShowComponentButton<CircleRendererComponent>("Circle 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.EndCombo();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,402 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
public const String s_WindowTitle = "Content Browser";
|
||||||
|
|
||||||
|
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(s_WindowTitle, &_open, .None))
|
||||||
|
{
|
||||||
|
ImGui.End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context menu when clicking on the background.
|
||||||
|
if (ImGui.BeginPopupContextWindow())
|
||||||
|
{
|
||||||
|
ShowCurrentFolderContextMenu();
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Columns(2);
|
||||||
|
|
||||||
|
ImGui.BeginChild("Sidebar");
|
||||||
|
|
||||||
|
DrawDirectorySideBar();
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.NextColumn();
|
||||||
|
|
||||||
|
ImGui.BeginChild("Files");
|
||||||
|
|
||||||
|
DrawCurrentDirectory();
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDirectoryNode->Parent != null)
|
||||||
|
{
|
||||||
|
ImGui.PushID("Back");
|
||||||
|
|
||||||
|
DrawBackButton(currentDirectoryNode->Parent);
|
||||||
|
|
||||||
|
// 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 the next button won't fit on the same line we start a new line.
|
||||||
|
if (expectedButtonRight < window_visible_x2)
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 DrawBackButton(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
SubTexture2D image = s_FolderTexture;
|
||||||
|
|
||||||
|
ImGui.ImageButton(image, (.)(DirectoryItemSize - padding));
|
||||||
|
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(.Left))
|
||||||
|
{
|
||||||
|
if (_selectedFile != entry->Path)
|
||||||
|
{
|
||||||
|
_selectedFile.Set(entry->Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(.Left))
|
||||||
|
{
|
||||||
|
EntryDoubleClicked(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TextUnformatted("..");
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the button for the given directory item.
|
||||||
|
private void DrawDirectoryItem(TreeNode<AssetNode> entry)
|
||||||
|
{
|
||||||
|
ImGui.BeginChild("item", (.)DirectoryItemSize);
|
||||||
|
|
||||||
|
if (entry->Path == _selectedFile)
|
||||||
|
{
|
||||||
|
var color = ImGui.GetStyleColorVec4(.ButtonHovered);
|
||||||
|
ImGui.PushStyleColor(.Button, *color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: preview images
|
||||||
|
SubTexture2D image = entry->IsDirectory ? s_FolderTexture : s_FileTexture;
|
||||||
|
|
||||||
|
ImGui.ImageButton(image, (.)(DirectoryItemSize - padding));
|
||||||
|
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
if (ImGui.BeginDragDropSource())
|
||||||
|
{
|
||||||
|
String fullpath = scope String(entry->Path);
|
||||||
|
|
||||||
|
// TODO: this is dirty
|
||||||
|
if (fullpath.StartsWith(_manager.ContentDirectory, .OrdinalIgnoreCase))
|
||||||
|
fullpath.Remove(0, _manager.ContentDirectory.Length);
|
||||||
|
|
||||||
|
Path.Fixup(fullpath);
|
||||||
|
|
||||||
|
ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once);
|
||||||
|
|
||||||
|
ImGui.EndDragDropSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(.Left))
|
||||||
|
{
|
||||||
|
if (_selectedFile != entry->Path)
|
||||||
|
{
|
||||||
|
_selectedFile.Set(entry->Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(.Left))
|
||||||
|
{
|
||||||
|
EntryDoubleClicked(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TextUnformatted(entry->Name);
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupContextWindow())
|
||||||
|
{
|
||||||
|
ShowItemContextMenu(entry);
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->SubAssets?.Count > 0)
|
||||||
|
{
|
||||||
|
// Button for revealing sub assets (e.g. Meshes in 3D-Model)
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button(">"))
|
||||||
|
ImGui.OpenPopup("SubAssets");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginPopup("SubAssets", .Popup))
|
||||||
|
{
|
||||||
|
for (var subAsset in entry->SubAssets)
|
||||||
|
{
|
||||||
|
ImGui.Button(subAsset.Name);
|
||||||
|
|
||||||
|
if (ImGui.BeginDragDropSource())
|
||||||
|
{
|
||||||
|
String fullpath = scope String(entry->Path);
|
||||||
|
fullpath.AppendF($"#{subAsset.Name}");
|
||||||
|
|
||||||
|
ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once);
|
||||||
|
|
||||||
|
ImGui.EndDragDropSource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteItemPopup(entry);
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteItemPopup(TreeNode<AssetNode> fileOrFolder)
|
||||||
|
{
|
||||||
|
// Always center this window when appearing
|
||||||
|
ImGui.Vec2 center = ImGui.GetMainViewport().GetCenter();
|
||||||
|
ImGui.SetNextWindowPos(center, .Appearing, ImGui.Vec2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
// TODO: fix delete popup
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupModal("Delete?", null, .AlwaysAutoResize))
|
||||||
|
{
|
||||||
|
ImGui.Text($"""
|
||||||
|
Delete "{fileOrFolder->Name}"?
|
||||||
|
|
||||||
|
|
||||||
|
""");
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (ImGui.Button("Yes", ImGui.Vec2(120, 0)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Cancel", ImGui.Vec2(120, 0)))
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows the context menu for the given file/folder.
|
||||||
|
private void ShowItemContextMenu(TreeNode<AssetNode> fileOrFolder)
|
||||||
|
{
|
||||||
|
bool isFile = !fileOrFolder->IsDirectory;
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Show in file browser..."))
|
||||||
|
{
|
||||||
|
if (Path.OpenFolderAndSelectItem(fileOrFolder->Path) case .Err)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("Failed to show path in file browser.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFile && ImGui.MenuItem("Open file with..."))
|
||||||
|
{
|
||||||
|
if (Path.OpenWithDialog(fileOrFolder->Path) case .Err)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("Failed to show \"Open with...\" dialog.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Delete"))
|
||||||
|
{
|
||||||
|
ImGui.OpenPopup("Delete?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EntryDoubleClicked(TreeNode<AssetNode> entry)
|
||||||
|
{
|
||||||
|
if (entry->IsDirectory)
|
||||||
|
{
|
||||||
|
_currentDirectory.Set(entry->Path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Path.OpenFolder(entry->Path) case .Err)
|
||||||
|
Log.EngineLogger.Error("Failed to open directory in file browser.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
using System;
|
||||||
|
using ImGui;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using GlitchyEngine.Math;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEngine.World;
|
||||||
|
using ImGuizmo;
|
||||||
|
using GlitchyEngine.Events;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.EditWindows
|
||||||
|
{
|
||||||
|
class EditorViewportWindow : 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;
|
||||||
|
|
||||||
|
private bool _visible;
|
||||||
|
|
||||||
|
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 bool Visible => _visible;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
_visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_visible = true;
|
||||||
|
|
||||||
|
let viewportSize = ImGui.GetContentRegionAvail();
|
||||||
|
|
||||||
|
if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton))
|
||||||
|
{
|
||||||
|
let currentWindow = ImGui.GetCurrentWindow();
|
||||||
|
ImGui.FocusWindow(currentWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasFocus = ImGui.IsWindowFocused();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
ImGui.DrawRect((.)regionMin, (.)regionMax, .(0, 255, 0));
|
||||||
|
|
||||||
|
Vector2 newMousePos = mousePos;
|
||||||
|
|
||||||
|
if (mousePos.X < regionMin.X + 1)
|
||||||
|
{
|
||||||
|
newMousePos.X = regionMax.X - 2;
|
||||||
|
}
|
||||||
|
else if (mousePos.X > regionMax.X - 1)
|
||||||
|
{
|
||||||
|
newMousePos.X = regionMin.X + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mousePos.Y < regionMin.Y + 1)
|
||||||
|
{
|
||||||
|
newMousePos.Y = regionMax.Y - 100;
|
||||||
|
}
|
||||||
|
else if (mousePos.Y > regionMax.Y - 1)
|
||||||
|
{
|
||||||
|
newMousePos.Y = regionMin.Y + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.EditorSceneRenderer.CompositeTarget.Width;
|
||||||
|
int rtHeight = _editor.EditorSceneRenderer.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.EditorSceneRenderer.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
namespace GlitchyEditor.EditWindows
|
namespace GlitchyEditor.EditWindows
|
||||||
{
|
{
|
||||||
abstract class EditorWindow
|
abstract class EditorWindow
|
||||||
|
|||||||
@@ -23,16 +23,58 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
public List<Entity> SelectedEntities => _selectedEntities;
|
public List<Entity> SelectedEntities => _selectedEntities;
|
||||||
|
|
||||||
public this(Scene scene)
|
public this(Editor editor, Scene scene)
|
||||||
{
|
{
|
||||||
|
_editor = editor;
|
||||||
SetContext(scene);
|
SetContext(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetContext(Scene scene)
|
public void SetContext(Scene scene)
|
||||||
{
|
{
|
||||||
|
ClearEntitySelection();
|
||||||
_scene = scene;
|
_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()
|
protected override void InternalShow()
|
||||||
{
|
{
|
||||||
if(!ImGui.Begin(s_WindowTitle, &_open, .MenuBar))
|
if(!ImGui.Begin(s_WindowTitle, &_open, .MenuBar))
|
||||||
@@ -50,10 +92,26 @@ namespace GlitchyEditor.EditWindows
|
|||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_editor.SceneViewportWindow.SelectionChanged)
|
||||||
|
{
|
||||||
|
var handle = _scene.[Friend]_ecsWorld.GetCurrentVersion(EcsEntity.[Friend]CreateEntityID(_editor.SceneViewportWindow.SelectedEntityId, 0));
|
||||||
|
|
||||||
|
if (handle case .Ok(let h))
|
||||||
|
{
|
||||||
|
Entity e = .(h, _scene);
|
||||||
|
|
||||||
|
SelectEntity(e, Input.IsKeyReleased(.Control));
|
||||||
|
}
|
||||||
|
else if (Input.IsKeyReleased(.Control))
|
||||||
|
{
|
||||||
|
ClearEntitySelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ShowEntityHierarchy();
|
ShowEntityHierarchy();
|
||||||
|
|
||||||
if ((ImGui.IsMouseDown(.Left) || ImGui.IsMouseDown(.Right)) && !ImGui.IsAnyItemHovered() && !ImGui.GetIO().KeyCtrl && ImGui.IsWindowHovered(.AllowWhenBlockedByPopup))
|
if ((ImGui.IsMouseDown(.Left) || ImGui.IsMouseDown(.Right)) && !ImGui.IsAnyItemHovered() && !ImGui.GetIO().KeyCtrl && ImGui.IsWindowHovered(.AllowWhenBlockedByPopup))
|
||||||
_selectedEntities.Clear();
|
ClearEntitySelection();
|
||||||
|
|
||||||
ImGui.End();
|
ImGui.End();
|
||||||
}
|
}
|
||||||
@@ -102,6 +160,8 @@ namespace GlitchyEditor.EditWindows
|
|||||||
{
|
{
|
||||||
_scene.DestroyEntity(entity, true);
|
_scene.DestroyEntity(entity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_selectedEntities.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowEntityHierarchyMenuBar()
|
private void ShowEntityHierarchyMenuBar()
|
||||||
@@ -241,11 +301,11 @@ namespace GlitchyEditor.EditWindows
|
|||||||
{
|
{
|
||||||
String name = null;
|
String name = null;
|
||||||
|
|
||||||
var nameComponent = tree.Value.GetComponent<DebugNameComponent>();
|
var nameComponent = tree.Value.GetComponent<NameComponent>();
|
||||||
|
|
||||||
if(nameComponent != null)
|
if(nameComponent != null)
|
||||||
{
|
{
|
||||||
name = nameComponent.DebugName;
|
name = scope:: .(nameComponent.Name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -257,7 +317,7 @@ namespace GlitchyEditor.EditWindows
|
|||||||
if(tree.Children.Count == 0)
|
if(tree.Children.Count == 0)
|
||||||
flags |= .Leaf;
|
flags |= .Leaf;
|
||||||
|
|
||||||
bool inSelectedList = _selectedEntities.Contains(tree.Value);
|
bool inSelectedList = IsEntitySelected(tree.Value);
|
||||||
|
|
||||||
if(inSelectedList)
|
if(inSelectedList)
|
||||||
flags |= .Selected;
|
flags |= .Selected;
|
||||||
@@ -350,17 +410,12 @@ namespace GlitchyEditor.EditWindows
|
|||||||
{
|
{
|
||||||
if (inSelectedList && !clickedRight)
|
if (inSelectedList && !clickedRight)
|
||||||
{
|
{
|
||||||
_selectedEntities.Remove(tree.Value);
|
DeselectEntity(tree.Value);
|
||||||
inSelectedList = false;
|
inSelectedList = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!ImGui.GetIO().KeyCtrl && !clickedRight)
|
SelectEntity(tree.Value, !ImGui.GetIO().KeyCtrl && !clickedRight);
|
||||||
{
|
|
||||||
_selectedEntities.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectedEntities.Add(tree.Value);
|
|
||||||
inSelectedList = true;
|
inSelectedList = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,13 +501,13 @@ namespace GlitchyEditor.EditWindows
|
|||||||
{
|
{
|
||||||
Entity entity = .(entityId, _scene);
|
Entity entity = .(entityId, _scene);
|
||||||
|
|
||||||
String name = null;
|
StringView name = null;
|
||||||
|
|
||||||
var nameComponent = entity.GetComponent<DebugNameComponent>();
|
var nameComponent = entity.GetComponent<NameComponent>();
|
||||||
|
|
||||||
if(nameComponent != null)
|
if(nameComponent != null)
|
||||||
{
|
{
|
||||||
name = nameComponent.DebugName;
|
name = nameComponent.Name;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using ImGui;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using GlitchyEngine.Math;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEngine.World;
|
||||||
|
using ImGuizmo;
|
||||||
|
using GlitchyEngine.Events;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.EditWindows
|
||||||
|
{
|
||||||
|
class GameViewportWindow : EditorWindow
|
||||||
|
{
|
||||||
|
public const String s_WindowTitle = "Game";
|
||||||
|
|
||||||
|
private ImGui.Vec2 _oldViewportSize = .(100, 100);
|
||||||
|
private bool _viewPortChanged;
|
||||||
|
|
||||||
|
private bool _visible;
|
||||||
|
|
||||||
|
private RenderTargetGroup _renderTarget ~ _?.ReleaseRef();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Visible => _visible;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
_visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_visible = true;
|
||||||
|
|
||||||
|
let viewportSize = ImGui.GetContentRegionAvail();
|
||||||
|
|
||||||
|
if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton))
|
||||||
|
{
|
||||||
|
let currentWindow = ImGui.GetCurrentWindow();
|
||||||
|
ImGui.FocusWindow(currentWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasFocus = ImGui.IsWindowFocused();
|
||||||
|
|
||||||
|
if(_renderTarget != null)
|
||||||
|
{
|
||||||
|
ImGui.Image(_renderTarget.GetViewBinding(0), viewportSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
using ImGui;
|
||||||
|
using System;
|
||||||
|
using GlitchyEngine.Collections;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
using System.Reflection;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEditor.Assets;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.EditWindows;
|
||||||
|
|
||||||
|
class PropertiesWindow : EditorWindow
|
||||||
|
{
|
||||||
|
public const String s_WindowTitle = "Properties";
|
||||||
|
|
||||||
|
private AssetPropertiesEditor _currentPropertiesEditor ~ delete _;
|
||||||
|
|
||||||
|
private bool _lockCurrentAsset;
|
||||||
|
|
||||||
|
private bool _selectedNewAsset;
|
||||||
|
|
||||||
|
private append String _selectedFileName = .();
|
||||||
|
|
||||||
|
private AssetHandle _currentAssetHandle;
|
||||||
|
|
||||||
|
public this(Editor editor)
|
||||||
|
{
|
||||||
|
_editor = editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InternalShow()
|
||||||
|
{
|
||||||
|
defer { ImGui.End(); }
|
||||||
|
if(!ImGui.Begin(s_WindowTitle, &_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;
|
||||||
|
|
||||||
|
Asset asset = _editor.ContentManager.GetAsset(null, _currentAssetHandle);
|
||||||
|
|
||||||
|
// We need the actual asset for preview and sometimes for editing
|
||||||
|
if (asset?.Identifier != assetFile.Identifier)
|
||||||
|
{
|
||||||
|
_currentAssetHandle = _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"))
|
||||||
|
{
|
||||||
|
Asset asset = _editor.ContentManager.GetAsset(null, _currentAssetHandle);
|
||||||
|
_editor.ContentManager.SaveAsset(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!assetFile.AssetConfig.Config.Changed)
|
||||||
|
{
|
||||||
|
ImGui.BeginDisabled();
|
||||||
|
defer:: { ImGui.EndDisabled(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (ImGui.Button("Apply"))
|
||||||
|
assetFile.SaveAssetConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
using System;
|
|
||||||
using ImGui;
|
|
||||||
using GlitchyEngine.Renderer;
|
|
||||||
using GlitchyEngine.Math;
|
|
||||||
using GlitchyEngine;
|
|
||||||
using GlitchyEngine.World;
|
|
||||||
using ImGuizmo;
|
|
||||||
|
|
||||||
namespace GlitchyEditor.EditWindows
|
|
||||||
{
|
|
||||||
class SceneViewportWindow : EditorWindow
|
|
||||||
{
|
|
||||||
//public OldCamera _camera;
|
|
||||||
|
|
||||||
public const String s_WindowTitle = "Scene";
|
|
||||||
|
|
||||||
private RenderTarget2D _renderTarget ~ _?.ReleaseRef();
|
|
||||||
|
|
||||||
public Event<EventHandler<Vector2>> ViewportSizeChangedEvent ~ _.Dispose();
|
|
||||||
|
|
||||||
public RenderTarget2D RenderTarget
|
|
||||||
{
|
|
||||||
get => _renderTarget;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if(_renderTarget == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SetReference!(_renderTarget, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public this(Editor editor)
|
|
||||||
{
|
|
||||||
_editor = editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImGui.Vec2 oldViewportSize;
|
|
||||||
private bool viewPortChanged;
|
|
||||||
|
|
||||||
public Entity CameraEntity { get; set; }
|
|
||||||
|
|
||||||
protected override void InternalShow()
|
|
||||||
{
|
|
||||||
ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(1, 1));
|
|
||||||
defer ImGui.PopStyleVar();
|
|
||||||
|
|
||||||
if(!ImGui.Begin(s_WindowTitle, &_open, .NoScrollbar))
|
|
||||||
{
|
|
||||||
ImGui.End();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton))
|
|
||||||
{
|
|
||||||
var currentWindow = ImGui.GetCurrentWindow();
|
|
||||||
ImGui.FocusWindow(currentWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
_hasFocus = ImGui.IsWindowFocused();
|
|
||||||
|
|
||||||
var viewportSize = ImGui.GetContentRegionAvail();
|
|
||||||
|
|
||||||
if(_renderTarget != null)
|
|
||||||
{
|
|
||||||
ImGui.Image(_renderTarget, viewportSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawImGuizmo(viewportSize);
|
|
||||||
|
|
||||||
ImGui.End();
|
|
||||||
|
|
||||||
if(oldViewportSize != viewportSize)
|
|
||||||
{
|
|
||||||
ViewportSizeChangedEvent.Invoke(this, (Vector2)viewportSize);
|
|
||||||
viewPortChanged = true;
|
|
||||||
oldViewportSize = viewportSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawImGuizmo(ImGui.Vec2 viewportSize)
|
|
||||||
{
|
|
||||||
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 cameraTransformCmp = CameraEntity.GetComponent<TransformComponent>();
|
|
||||||
var view = cameraTransformCmp.WorldTransform.Invert();
|
|
||||||
|
|
||||||
var cameraCmp = CameraEntity.GetComponent<CameraComponent>();
|
|
||||||
var projection = cameraCmp.Camera.Projection;
|
|
||||||
|
|
||||||
Matrix mat = .Identity;
|
|
||||||
ImGuizmo.DrawGrid((.)&view, (.)&projection, (.)&mat, 10);
|
|
||||||
|
|
||||||
if(_editor.SelectedEntities.Count > 0)
|
|
||||||
{
|
|
||||||
var entity = _editor.SelectedEntities.Front;
|
|
||||||
|
|
||||||
var transformCmp = _editor.World.GetComponent<TransformComponent>(entity);
|
|
||||||
|
|
||||||
var transform = transformCmp.LocalTransform;
|
|
||||||
|
|
||||||
ImGuizmo.SetRect(topLeft.x, topLeft.y, viewportSize.x, viewportSize.y);
|
|
||||||
|
|
||||||
ImGuizmo.Manipulate((.)&view, (.)&projection, .TRANSLATE, .LOCAL, (.)&transform);
|
|
||||||
|
|
||||||
transformCmp.LocalTransform = transform;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,115 +4,80 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using GlitchyEngine.Collections;
|
using GlitchyEngine.Collections;
|
||||||
using GlitchyEditor.EditWindows;
|
using GlitchyEditor.EditWindows;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEditor.Assets;
|
||||||
|
|
||||||
namespace GlitchyEditor
|
namespace GlitchyEditor
|
||||||
{
|
{
|
||||||
class Editor
|
class Editor
|
||||||
{
|
{
|
||||||
private EcsWorld _ecsWorld;
|
|
||||||
private Scene _scene;
|
private Scene _scene;
|
||||||
|
|
||||||
|
private EditorContentManager _contentManager;
|
||||||
|
|
||||||
private EntityHierarchyWindow _entityHierarchyWindow ~ delete _;
|
private EntityHierarchyWindow _entityHierarchyWindow ~ delete _;
|
||||||
private ComponentEditWindow _componentEditWindow ~ delete _;
|
private ComponentEditWindow _componentEditWindow ~ delete _;
|
||||||
private SceneViewportWindow _sceneViewportWindow = new .(this) ~ delete _;
|
private EditorViewportWindow _sceneViewportWindow ~ delete _;
|
||||||
|
private GameViewportWindow _gameViewportWindow ~ delete _;
|
||||||
|
private ContentBrowserWindow _contentBrowserWindow ~ delete _;
|
||||||
|
private PropertiesWindow _propertiesWindow ~ delete _;
|
||||||
|
|
||||||
private List<EcsEntity> _selectedEntities = new .() ~ delete _;
|
public Scene CurrentScene
|
||||||
|
{
|
||||||
|
get => _scene;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_scene == value)
|
||||||
|
return;
|
||||||
|
|
||||||
public EcsWorld World => _ecsWorld;
|
_scene = value;
|
||||||
|
_entityHierarchyWindow.SetContext(_scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<EcsEntity> SelectedEntities => _selectedEntities;
|
public EditorContentManager ContentManager => _contentManager;
|
||||||
|
|
||||||
public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow;
|
public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow;
|
||||||
public ComponentEditWindow ComponentEditWindow => _componentEditWindow;
|
public ComponentEditWindow ComponentEditWindow => _componentEditWindow;
|
||||||
public SceneViewportWindow SceneViewportWindow => _sceneViewportWindow;
|
public EditorViewportWindow SceneViewportWindow => _sceneViewportWindow;
|
||||||
|
public GameViewportWindow GameViewportWindow => _gameViewportWindow;
|
||||||
|
public ContentBrowserWindow ContentBrowserWindow => _contentBrowserWindow;
|
||||||
|
public PropertiesWindow PropertiesWindow => _propertiesWindow;
|
||||||
|
|
||||||
|
public EditorCamera* CurrentCamera { get; set; }
|
||||||
|
|
||||||
|
public Event<EventHandler<StringView>> RequestOpenScene ~ _.Dispose();
|
||||||
|
|
||||||
|
public SceneRenderer GameSceneRenderer {get; set;}
|
||||||
|
public SceneRenderer EditorSceneRenderer {get; set;}
|
||||||
|
|
||||||
/// Creates a new editor for the given world
|
/// Creates a new editor for the given world
|
||||||
public this(Scene scene)
|
public this(Scene scene, EditorContentManager contentManager)
|
||||||
{
|
{
|
||||||
_scene = scene;
|
_scene = scene;
|
||||||
_ecsWorld = _scene.[Friend]_ecsWorld;
|
_contentManager = contentManager;
|
||||||
|
|
||||||
_entityHierarchyWindow = new EntityHierarchyWindow(_scene);
|
InitWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitWindows()
|
||||||
|
{
|
||||||
|
_sceneViewportWindow = new EditorViewportWindow(this);
|
||||||
|
_gameViewportWindow = new GameViewportWindow(this);
|
||||||
|
_entityHierarchyWindow = new EntityHierarchyWindow(this, _scene);
|
||||||
_componentEditWindow = new ComponentEditWindow(_entityHierarchyWindow);
|
_componentEditWindow = new ComponentEditWindow(_entityHierarchyWindow);
|
||||||
|
_contentBrowserWindow = new ContentBrowserWindow((.)Application.Get().ContentManager);
|
||||||
|
_propertiesWindow = new PropertiesWindow(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
|
_sceneViewportWindow.Show();
|
||||||
|
_gameViewportWindow.Show();
|
||||||
_entityHierarchyWindow.Show();
|
_entityHierarchyWindow.Show();
|
||||||
_componentEditWindow.Show();
|
_componentEditWindow.Show();
|
||||||
_sceneViewportWindow.Show();
|
_contentBrowserWindow.Show();
|
||||||
}
|
_propertiesWindow.Show();
|
||||||
|
|
||||||
/// Creates a new entity with a transform component.
|
|
||||||
internal EcsEntity CreateEntityWithTransform()
|
|
||||||
{
|
|
||||||
var entity = _ecsWorld.NewEntity();
|
|
||||||
|
|
||||||
var transformComponent = ref *_ecsWorld.AssignComponent<TransformComponent>(entity);
|
|
||||||
transformComponent = TransformComponent();
|
|
||||||
|
|
||||||
var nameComponent = ref *_ecsWorld.AssignComponent<DebugNameComponent>(entity);
|
|
||||||
nameComponent.SetName("Entity");
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Returns whether or not all selected entities have the same parent.
|
|
||||||
internal bool AllSelectionsOnSameLevel()
|
|
||||||
{
|
|
||||||
EcsEntity? parent = .InvalidEntity;
|
|
||||||
|
|
||||||
for(var selectedEntity in _selectedEntities)
|
|
||||||
{
|
|
||||||
var parentComponent = _ecsWorld.GetComponent<ParentComponent>(selectedEntity);
|
|
||||||
|
|
||||||
if(parent == .InvalidEntity)
|
|
||||||
{
|
|
||||||
parent = parentComponent?.Entity;
|
|
||||||
}
|
|
||||||
else if(parentComponent?.Entity != parent)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds all children of the given entity and stores their IDs in the given list.
|
|
||||||
internal void FindChildren(EcsEntity entity, List<EcsEntity> entities)
|
|
||||||
{
|
|
||||||
for(var (child, childParent) in _ecsWorld.Enumerate<ParentComponent>())
|
|
||||||
{
|
|
||||||
if(childParent.Entity == entity)
|
|
||||||
{
|
|
||||||
if(!entities.Contains(child))
|
|
||||||
entities.Add(child);
|
|
||||||
|
|
||||||
FindChildren(child, entities);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all selected entities and their children.
|
|
||||||
internal void DeleteSelectedEntities()
|
|
||||||
{
|
|
||||||
List<EcsEntity> entities = scope .();
|
|
||||||
|
|
||||||
for(var entity in _selectedEntities)
|
|
||||||
{
|
|
||||||
entities.Add(entity);
|
|
||||||
|
|
||||||
FindChildren(entity, entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(var entity in entities)
|
|
||||||
{
|
|
||||||
_ecsWorld.RemoveEntity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectedEntities.Clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,41 @@
|
|||||||
using System;
|
using System;
|
||||||
using GlitchyEngine;
|
using GlitchyEngine;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
using GlitchyEditor.Assets;
|
||||||
|
|
||||||
namespace GlitchyEditor
|
namespace GlitchyEditor
|
||||||
{
|
{
|
||||||
class EditorApp : Application
|
class EditorApp : Application
|
||||||
{
|
{
|
||||||
|
EditorContentManager _contentManager;
|
||||||
|
|
||||||
public this()
|
public this()
|
||||||
{
|
{
|
||||||
PushLayer(new EditorLayer());
|
PushLayer(new EditorLayer(_contentManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IContentManager InitContentManager()
|
||||||
|
{
|
||||||
|
_contentManager = new EditorContentManager();
|
||||||
|
_contentManager.RegisterAssetLoader<EditorTextureAssetLoader>();
|
||||||
|
_contentManager.SetAsDefaultAssetLoader<EditorTextureAssetLoader>(".png", ".dds");
|
||||||
|
_contentManager.SetAssetPropertiesEditor<EditorTextureAssetLoader>(=> TextureAssetPropertiesEditor.Factory);
|
||||||
|
|
||||||
|
_contentManager.RegisterAssetLoader<ModelAssetLoader>();
|
||||||
|
_contentManager.SetAsDefaultAssetLoader<ModelAssetLoader>(".glb", ".gltf");
|
||||||
|
_contentManager.SetAssetPropertiesEditor<ModelAssetLoader>(=> ModelAssetPropertiesEditor.Factory);
|
||||||
|
|
||||||
|
_contentManager.RegisterAssetLoader<MaterialAssetLoader>();
|
||||||
|
_contentManager.SetAsDefaultAssetLoader<MaterialAssetLoader>(".mat");
|
||||||
|
_contentManager.SetAssetPropertiesEditor<MaterialAssetLoader>(=> MaterialAssetPropertiesEditor.Factory);
|
||||||
|
|
||||||
|
_contentManager.RegisterAssetLoader<EffectAssetLoader>();
|
||||||
|
_contentManager.SetAsDefaultAssetLoader<EffectAssetLoader>(".hlsl");
|
||||||
|
_contentManager.SetAssetPropertiesEditor<EffectAssetLoader>(=> EffectAssetPropertiesEditor.Factory);
|
||||||
|
|
||||||
|
_contentManager.SetContentDirectory("./content");
|
||||||
|
|
||||||
|
return _contentManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Export, LinkName("CreateApplication")]
|
[Export, LinkName("CreateApplication")]
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
using GlitchyEngine;
|
|
||||||
using GlitchyEngine.Math;
|
|
||||||
using GlitchyEngine.World;
|
|
||||||
|
|
||||||
namespace GlitchyEditor
|
|
||||||
{
|
|
||||||
class EditorCameraController : ScriptableEntity
|
|
||||||
{
|
|
||||||
private float _cameraTranslationSpeed = 2.0f;
|
|
||||||
private float _cameraRotationSpeedX = 0.001f;
|
|
||||||
private float _cameraRotationSpeedY = 0.001f;
|
|
||||||
|
|
||||||
public bool IsEnabled = false;
|
|
||||||
|
|
||||||
protected override void OnUpdate(GameTime gt)
|
|
||||||
{
|
|
||||||
if (!IsEnabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Debug.Profiler.ProfileFunction!();
|
|
||||||
|
|
||||||
Vector3 movement = .();
|
|
||||||
|
|
||||||
if(Input.IsKeyPressed(Key.W))
|
|
||||||
movement.Z += 1;
|
|
||||||
if(Input.IsKeyPressed(Key.S))
|
|
||||||
movement.Z -= 1;
|
|
||||||
|
|
||||||
if(Input.IsKeyPressed(Key.A))
|
|
||||||
movement.X -= 1;
|
|
||||||
if(Input.IsKeyPressed(Key.D))
|
|
||||||
movement.X += 1;
|
|
||||||
|
|
||||||
if(Input.IsKeyPressed(Key.Space))
|
|
||||||
movement.Y += 1;
|
|
||||||
if(Input.IsKeyPressed(Key.Control))
|
|
||||||
movement.Y -= 1;
|
|
||||||
|
|
||||||
var transformComponent = transform;
|
|
||||||
|
|
||||||
if(movement != .Zero)
|
|
||||||
{
|
|
||||||
movement.Normalize();
|
|
||||||
|
|
||||||
movement *= (float)(gt.FrameTime.TotalSeconds) * _cameraTranslationSpeed;
|
|
||||||
|
|
||||||
Matrix view = transformComponent.WorldTransform.Invert();
|
|
||||||
|
|
||||||
Vector4 delta = Vector4(movement, 1.0f) * view;
|
|
||||||
|
|
||||||
transformComponent.Position = transformComponent.Position + delta.XYZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Camera rotation
|
|
||||||
var mouseDelta = Input.GetMouseMovement();
|
|
||||||
|
|
||||||
float rotY = mouseDelta.X * _cameraRotationSpeedX;
|
|
||||||
float rotX = mouseDelta.Y * _cameraRotationSpeedY;
|
|
||||||
|
|
||||||
transformComponent.RotationEuler = transformComponent.RotationEuler + .(rotX, rotY, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,639 @@
|
|||||||
|
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;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using internal GlitchyEngine.Content.Asset;
|
||||||
|
|
||||||
|
namespace GlitchyEditor;
|
||||||
|
|
||||||
|
class EditorContentManager : IContentManager
|
||||||
|
{
|
||||||
|
private append String _contentDirectory = .();
|
||||||
|
|
||||||
|
public StringView ContentDirectory => _contentDirectory;
|
||||||
|
|
||||||
|
//private append List<String> _identifiers = .() ~ _.ClearAndDeleteItems();
|
||||||
|
|
||||||
|
private append Dictionary<StringView, AssetHandle> _identiferToHandle = .(); // TODO: Check if all resources are unloaded
|
||||||
|
|
||||||
|
private append Dictionary<AssetHandle, Asset> _handleToAsset = .();
|
||||||
|
|
||||||
|
private append AssetHierarchy _assetHierarchy = .(this);
|
||||||
|
|
||||||
|
public AssetHierarchy AssetHierarchy => _assetHierarchy;
|
||||||
|
|
||||||
|
private append List<AssetHandle> _reloadQueue = .();
|
||||||
|
|
||||||
|
public this()
|
||||||
|
{
|
||||||
|
_assetHierarchy.OnFileContentChanged.Add(new => OnFileContentChanged);
|
||||||
|
_assetHierarchy.OnFileRenamed.Add(new => OnFileRenamed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ~this()
|
||||||
|
{
|
||||||
|
UnmanageAllAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFileContentChanged(AssetNode assetNode)
|
||||||
|
{
|
||||||
|
// Asset isn't loaded so we don't need to reload it.
|
||||||
|
if (assetNode.AssetFile.LoadedAsset == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_reloadQueue.Add(assetNode.AssetFile.LoadedAsset.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnFileRenamed(AssetNode assetNode, StringView oldIdentifier)
|
||||||
|
{
|
||||||
|
// Asset isn't loaded so we don't need to reload it.
|
||||||
|
if (assetNode.AssetFile.LoadedAsset == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Asset asset = assetNode.AssetFile.LoadedAsset;
|
||||||
|
|
||||||
|
_identiferToHandle.Remove(oldIdentifier);
|
||||||
|
asset.Identifier = assetNode.AssetFile.Identifier;
|
||||||
|
_identiferToHandle.Add(asset.Identifier, asset.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContentDirectory(StringView contentDirectory)
|
||||||
|
{
|
||||||
|
_contentDirectory.Clear();
|
||||||
|
_contentDirectory.Append(contentDirectory);
|
||||||
|
Path.Fixup(_contentDirectory);
|
||||||
|
|
||||||
|
_assetHierarchy.SetContentDirectory(contentDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
SwapInLoadedAssets();
|
||||||
|
|
||||||
|
if (!_reloadQueue.IsEmpty)
|
||||||
|
{
|
||||||
|
for (AssetHandle handle in _reloadQueue)
|
||||||
|
{
|
||||||
|
ReloadAsset(handle);
|
||||||
|
}
|
||||||
|
_reloadQueue.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_assetHierarchy.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces placeholders with the loaded assets
|
||||||
|
private void SwapInLoadedAssets()
|
||||||
|
{
|
||||||
|
// Don't take the lock if we have nothing to do.
|
||||||
|
if (_finishedEntries.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (_finishedEntriesLock.Enter())
|
||||||
|
{
|
||||||
|
while (_finishedEntries.Count > 0)
|
||||||
|
{
|
||||||
|
let (placeholder, asset) = _finishedEntries[0];
|
||||||
|
|
||||||
|
delete placeholder.LoadingTask;
|
||||||
|
|
||||||
|
if (asset == null)
|
||||||
|
placeholder.PlaceholderType = .Error;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: I'm not sure whether AssetFiles are guaranteed to persist.
|
||||||
|
// Get the reference here because placeholder wont survive SwapAsset.
|
||||||
|
AssetFile file = placeholder.AssetFile;
|
||||||
|
file.[Friend]_loadedAsset = asset;
|
||||||
|
|
||||||
|
SwapAsset(placeholder, asset);
|
||||||
|
// SwapAsset increases RefCount, but this scope also holds a reference.
|
||||||
|
asset.ReleaseRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
_finishedEntries.RemoveAtFast(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
|
||||||
|
T assetLoader = new T();
|
||||||
|
|
||||||
|
_assetLoaders.Add(assetLoader);
|
||||||
|
|
||||||
|
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 _identiferToHandle.ContainsKey(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset GetAsset(Type assetType, AssetHandle handle)
|
||||||
|
{
|
||||||
|
Asset asset = null;
|
||||||
|
|
||||||
|
_handleToAsset.TryGetValue(handle, out asset);
|
||||||
|
|
||||||
|
if (var placeholder = asset as PlaceholderAsset)
|
||||||
|
{
|
||||||
|
if (placeholder.PlaceholderType == .Loading)
|
||||||
|
return placeholder.AssetLoader.GetPlaceholderAsset(assetType);
|
||||||
|
else if (placeholder.PlaceholderType == .Error)
|
||||||
|
return placeholder.AssetLoader.GetErrorAsset(assetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetType == null)
|
||||||
|
{
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
else if (asset?.GetType().IsSubtypeOf(assetType) ?? false)
|
||||||
|
{
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: get default asset
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReloadAsset(AssetHandle handle)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
Asset oldAsset = null;
|
||||||
|
|
||||||
|
if (!_handleToAsset.TryGetValue(handle, out oldAsset))
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("Can't reload! No asset exists for handle.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(oldAsset != null);
|
||||||
|
|
||||||
|
StringView oldIdentifier = oldAsset.Identifier;
|
||||||
|
|
||||||
|
GetResourceAndSubassetName(oldIdentifier, let resourceName, let subassetName);
|
||||||
|
|
||||||
|
String filePath = scope .();
|
||||||
|
GetResourceFilePath(resourceName, filePath);
|
||||||
|
|
||||||
|
Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath);
|
||||||
|
|
||||||
|
if (resultNode case .Err)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Could not find asset \"{filePath}\".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetFile file = resultNode->Value.AssetFile;
|
||||||
|
|
||||||
|
IAssetLoader assetLoader = GetAssetLoader(file);
|
||||||
|
|
||||||
|
Stream stream = GetStream(filePath);
|
||||||
|
|
||||||
|
// TODO: Add async loading!
|
||||||
|
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
|
||||||
|
|
||||||
|
delete stream;
|
||||||
|
|
||||||
|
if (loadedAsset == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
file.[Friend]_loadedAsset = loadedAsset;
|
||||||
|
|
||||||
|
SwapAsset(oldAsset, loadedAsset);
|
||||||
|
// SwapAsset increases RefCount, but this scope also holds a reference.
|
||||||
|
loadedAsset.ReleaseRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the resource name and, if it exists, the subasset name.
|
||||||
|
private void GetResourceAndSubassetName(StringView identifier, out StringView resourceName, out StringView? subassetName)
|
||||||
|
{
|
||||||
|
int poundIndex = identifier.IndexOf('#');
|
||||||
|
|
||||||
|
resourceName = (poundIndex != -1) ? identifier.Substring(0, poundIndex) : identifier;
|
||||||
|
subassetName = (poundIndex != -1) ? identifier.Substring(poundIndex + 1) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetResourceFilePath(StringView resourceName, String filePath)
|
||||||
|
{
|
||||||
|
Path.Combine(filePath, _contentDirectory, resourceName);
|
||||||
|
|
||||||
|
Path.Fixup(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum PlaceholderType
|
||||||
|
{
|
||||||
|
Loading,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PlaceholderAsset : Asset
|
||||||
|
{
|
||||||
|
public AssetFile AssetFile {get; private set;}
|
||||||
|
public Task LoadingTask {get;set;}
|
||||||
|
public IAssetLoader AssetLoader {get; private set;}
|
||||||
|
public PlaceholderType PlaceholderType {get; set;}
|
||||||
|
|
||||||
|
public this(AssetFile assetFile, IAssetLoader assetLoader, PlaceholderType placeholderType)
|
||||||
|
{
|
||||||
|
AssetFile = assetFile;
|
||||||
|
AssetLoader = assetLoader;
|
||||||
|
PlaceholderType = placeholderType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private append Monitor _finishedEntriesLock = .();
|
||||||
|
private append List<(PlaceholderAsset placeholder, Asset newAsset)> _finishedEntries = .();
|
||||||
|
|
||||||
|
private class MissingAsset : Asset {}
|
||||||
|
|
||||||
|
public AssetHandle LoadAsset(StringView identifier, bool blocking = false)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
// Todo: How strict should we be on paths?
|
||||||
|
String fixedIdentifier = scope String(identifier);
|
||||||
|
AssetIdentifier.Fixup(fixedIdentifier);
|
||||||
|
|
||||||
|
if (_identiferToHandle.TryGetValue(fixedIdentifier, let asset))
|
||||||
|
return asset;
|
||||||
|
|
||||||
|
GetResourceAndSubassetName(fixedIdentifier, let resourceName, let subassetName);
|
||||||
|
|
||||||
|
String filePath = scope .();
|
||||||
|
GetResourceFilePath(resourceName, filePath);
|
||||||
|
|
||||||
|
Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath);
|
||||||
|
|
||||||
|
if (resultNode case .Err)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Could not find asset \"{filePath}\".");
|
||||||
|
return .Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetFile file = resultNode->Value.AssetFile;
|
||||||
|
|
||||||
|
IAssetLoader assetLoader = GetAssetLoader(file);
|
||||||
|
|
||||||
|
// TODO: what are we supposed to do if we don't find a loader? Sure not crash...
|
||||||
|
Log.EngineLogger.AssertDebug(assetLoader != null);
|
||||||
|
|
||||||
|
Asset loadedAsset;
|
||||||
|
|
||||||
|
// TODO: Support lazy loading for all asset types
|
||||||
|
if (!(assetLoader is EditorTextureAssetLoader) || blocking)
|
||||||
|
{
|
||||||
|
Stream stream = GetStream(filePath);
|
||||||
|
|
||||||
|
loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
|
||||||
|
|
||||||
|
delete stream;
|
||||||
|
|
||||||
|
if (loadedAsset == null)
|
||||||
|
return .Invalid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlaceholderAsset placeholder = new PlaceholderAsset(file, assetLoader, .Loading);
|
||||||
|
|
||||||
|
String filePath2 = new String(filePath);
|
||||||
|
String newResourceName = new String(resourceName);
|
||||||
|
String newSesourceName = subassetName == null ? null : new String(subassetName.Value);
|
||||||
|
|
||||||
|
placeholder.LoadingTask = new Task(new () => {
|
||||||
|
AsyncLoadAsset(placeholder, filePath2, assetLoader, file,
|
||||||
|
newResourceName, newSesourceName);
|
||||||
|
});
|
||||||
|
|
||||||
|
ThreadPool.QueueUserWorkItem(placeholder.LoadingTask);
|
||||||
|
|
||||||
|
loadedAsset = placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedAsset.Identifier = fixedIdentifier;
|
||||||
|
AssetHandle handle = ManageAsset(loadedAsset);
|
||||||
|
// ManageAsset increases RefCount, but this scope also holds a reference.
|
||||||
|
loadedAsset.ReleaseRef();
|
||||||
|
|
||||||
|
// Add to Identifier -> Handle map
|
||||||
|
_identiferToHandle.Add(loadedAsset.Identifier, handle);
|
||||||
|
|
||||||
|
file.[Friend]_loadedAsset = loadedAsset;
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AsyncLoadAsset(PlaceholderAsset placeholder, String filePath, IAssetLoader assetLoader, AssetFile file, String resourceName, String subassetName)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
Stream stream = GetStream(filePath);
|
||||||
|
|
||||||
|
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
|
||||||
|
|
||||||
|
delete stream;
|
||||||
|
delete filePath;
|
||||||
|
delete resourceName;
|
||||||
|
delete subassetName;
|
||||||
|
|
||||||
|
using (_finishedEntriesLock.Enter())
|
||||||
|
{
|
||||||
|
_finishedEntries.Add((placeholder, loadedAsset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the asset loader that has to be used for the given file.
|
||||||
|
IAssetLoader GetAssetLoader(AssetFile file)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SaveAssetError
|
||||||
|
{
|
||||||
|
case Unknown;
|
||||||
|
case Unsavable;
|
||||||
|
case PathNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Saves the asset.
|
||||||
|
public Result<void, SaveAssetError> SaveAsset(Asset asset)
|
||||||
|
{
|
||||||
|
if (asset == null)
|
||||||
|
return .Err(.Unknown);
|
||||||
|
|
||||||
|
if (asset.Identifier.IsWhiteSpace)
|
||||||
|
return .Err(.Unsavable);
|
||||||
|
|
||||||
|
GetResourceAndSubassetName(asset.Identifier, let resourceName, let subassetName);
|
||||||
|
|
||||||
|
if (subassetName != null)
|
||||||
|
{
|
||||||
|
// TODO: should this ever be allowed? Couldn't we just save the entire asset?
|
||||||
|
// Would this ever be necessary?
|
||||||
|
Runtime.NotImplemented("Saving subassets is not currently allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
String filePath = scope String();
|
||||||
|
GetResourceFilePath(resourceName, filePath);
|
||||||
|
|
||||||
|
Result<TreeNode<AssetNode>> assetNode = AssetHierarchy.GetNodeFromPath(filePath);
|
||||||
|
|
||||||
|
if (assetNode case .Err)
|
||||||
|
return .Err(.PathNotFound);
|
||||||
|
|
||||||
|
AssetFile file = assetNode.Get()->AssetFile;
|
||||||
|
|
||||||
|
IAssetLoader assetLoader = GetAssetLoader(file);
|
||||||
|
|
||||||
|
IAssetSaver assetSaver = assetLoader as IAssetSaver;
|
||||||
|
|
||||||
|
if (assetSaver == null)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("The asset loader can't save!");
|
||||||
|
return .Err(.Unsavable);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream stream = OpenStream(filePath, false);
|
||||||
|
|
||||||
|
assetSaver.EditorSaveAsset(stream, asset, file.AssetConfig.Config, resourceName, subassetName, this);
|
||||||
|
|
||||||
|
// Trim off the end of the file.
|
||||||
|
stream.SetLength(stream.Position);
|
||||||
|
|
||||||
|
delete stream;
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream OpenStream(StringView assetIdentifier, bool openOnly)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 AssetHandle ManageAsset(Asset asset)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(asset.Handle == .Invalid, "Asset is already managed.");
|
||||||
|
Log.EngineLogger.AssertDebug(asset.ContentManager == null, "Asset is already managed.");
|
||||||
|
|
||||||
|
AssetHandle handle = .();
|
||||||
|
|
||||||
|
// Generate until we find a unique key (shouldn't happen too often)
|
||||||
|
while (handle.IsInvalid || _handleToAsset.ContainsKey(handle))
|
||||||
|
{
|
||||||
|
handle = .();
|
||||||
|
Log.EngineLogger.Trace("Handle was invalid or already taken.");
|
||||||
|
// TODO: perhaps test how often this happens.
|
||||||
|
// If this happens too often we could use a different random generator
|
||||||
|
}
|
||||||
|
|
||||||
|
//_handles.Add(asset.Identifier, handle);
|
||||||
|
_handleToAsset.Add(handle, asset);
|
||||||
|
|
||||||
|
asset.[Friend]_contentManager = this;
|
||||||
|
asset.[Friend]_handle = handle;
|
||||||
|
asset.AddRef();
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SwapAsset(Asset oldAsset, Asset newAsset)
|
||||||
|
{
|
||||||
|
newAsset.Identifier = oldAsset.Identifier;
|
||||||
|
newAsset._contentManager = this;
|
||||||
|
newAsset._handle = oldAsset.Handle;
|
||||||
|
|
||||||
|
_handleToAsset[oldAsset.Handle] = newAsset;
|
||||||
|
|
||||||
|
if (_identiferToHandle.ContainsKey(oldAsset.Identifier))
|
||||||
|
{
|
||||||
|
_identiferToHandle.Remove(oldAsset.Identifier);
|
||||||
|
_identiferToHandle.Add(newAsset.Identifier, newAsset.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
newAsset.AddRef();
|
||||||
|
oldAsset.ReleaseRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnmanageAsset(AssetHandle handle)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(_handleToAsset.ContainsKey(handle), "Handle doesn't correspond to an asset.");
|
||||||
|
|
||||||
|
Asset asset = _handleToAsset[handle];
|
||||||
|
|
||||||
|
if (_identiferToHandle.ContainsKey(asset.Identifier))
|
||||||
|
_identiferToHandle.Remove(asset.Identifier);
|
||||||
|
|
||||||
|
_handleToAsset.Remove(handle);
|
||||||
|
asset.[Friend]_contentManager = null;
|
||||||
|
|
||||||
|
asset.ReleaseRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This will unregister all assets from this content manager.
|
||||||
|
/// Note: This will not release any assets.
|
||||||
|
private void UnmanageAllAssets()
|
||||||
|
{
|
||||||
|
for (let (handle, _) in _handleToAsset)
|
||||||
|
{
|
||||||
|
UnmanageAsset(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AssetMoved(Asset asset, StringView oldIdentifier, StringView newIdentifier)
|
||||||
|
{
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
|
||||||
|
if (oldIdentifier == newIdentifier)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.EngineLogger.Assert(_identiferToHandle.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using GlitchyEngine.Math;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
|
||||||
|
namespace GlitchyEditor
|
||||||
|
{
|
||||||
|
class EditorIcons : RefCounted
|
||||||
|
{
|
||||||
|
AssetHandle<Texture2D> _texture;
|
||||||
|
|
||||||
|
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 SubTexture2D Simulate ~ _.ReleaseRef();
|
||||||
|
public SubTexture2D Pause ~ _.ReleaseRef();
|
||||||
|
|
||||||
|
public SamplerState SamplerState
|
||||||
|
{
|
||||||
|
get => _texture.Get().SamplerState;
|
||||||
|
set => _texture.Get().SamplerState = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(String texturePath, Vector2 iconSize)
|
||||||
|
{
|
||||||
|
_texture = Content.LoadAsset(texturePath, null, true);
|
||||||
|
|
||||||
|
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);
|
||||||
|
Simulate = GetNextGridTexture(ref pen, iconSize);
|
||||||
|
Pause = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,103 +7,102 @@ using GlitchyEngine.ImGui;
|
|||||||
using GlitchyEngine.Math;
|
using GlitchyEngine.Math;
|
||||||
using GlitchyEngine.Renderer;
|
using GlitchyEngine.Renderer;
|
||||||
using GlitchyEngine.World;
|
using GlitchyEngine.World;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
using System.Collections;
|
||||||
|
using GlitchyEngine.Renderer.Animation;
|
||||||
|
using System.IO;
|
||||||
|
using GlitchyEngine.Core;
|
||||||
|
using GlitchyEditor.Assets;
|
||||||
|
|
||||||
namespace GlitchyEditor
|
namespace GlitchyEditor
|
||||||
{
|
{
|
||||||
class EditorLayer : Layer
|
class EditorLayer : Layer
|
||||||
{
|
{
|
||||||
|
enum SceneState
|
||||||
|
{
|
||||||
|
Edit,
|
||||||
|
Play,
|
||||||
|
Simulate
|
||||||
|
}
|
||||||
|
|
||||||
RasterizerState _rasterizerState ~ _?.ReleaseRef();
|
RasterizerState _rasterizerState ~ _?.ReleaseRef();
|
||||||
RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef();
|
RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef();
|
||||||
|
|
||||||
|
// TODO: we shouldn't hold a reference to the context
|
||||||
GraphicsContext _context ~ _.ReleaseRef();
|
GraphicsContext _context ~ _.ReleaseRef();
|
||||||
|
|
||||||
BlendState _alphaBlendState ~ _.ReleaseRef();
|
BlendState _alphaBlendState ~ _.ReleaseRef();
|
||||||
BlendState _opaqueBlendState ~ _.ReleaseRef();
|
BlendState _opaqueBlendState ~ _.ReleaseRef();
|
||||||
DepthStencilState _depthStencilState ~ _.ReleaseRef();
|
DepthStencilState _depthStencilState ~ _.ReleaseRef();
|
||||||
|
|
||||||
Scene _scene = new Scene() ~ delete _;
|
/// Reference to the scene that is currently being played and worked on.
|
||||||
|
Scene _activeScene ~ _?.ReleaseRef();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Referece to the editor scene.
|
||||||
|
* We hold a reference to the editor scene because we need it in order
|
||||||
|
* to restore the original state once we stop the game/simulation.
|
||||||
|
* Before starting the simulation the editor scene will be copied and
|
||||||
|
* the reference in _activeScene will be replaced with the new scene.
|
||||||
|
*/
|
||||||
|
Scene _editorScene ~ _?.ReleaseRef();
|
||||||
|
|
||||||
|
SceneRenderer _gameSceneRenderer ~ delete _;
|
||||||
|
SceneRenderer _editorSceneRenderer ~ delete _;
|
||||||
|
|
||||||
|
/// Path of the current scene.
|
||||||
|
append String _sceneFilePath = .();
|
||||||
|
|
||||||
Editor _editor ~ delete _;
|
Editor _editor ~ delete _;
|
||||||
|
|
||||||
RenderTarget2D _viewportTarget ~ _?.ReleaseRef();
|
RenderTargetGroup _cameraTarget ~ _.ReleaseRef();
|
||||||
|
RenderTargetGroup _editorViewportTarget ~ _.ReleaseRef();
|
||||||
|
RenderTargetGroup _gameViewportTarget ~ _.ReleaseRef();
|
||||||
|
|
||||||
SettingsWindow _settingsWindow = new .() ~ delete _;
|
SettingsWindow _settingsWindow = new .() ~ delete _;
|
||||||
|
|
||||||
Entity _cameraEntity;
|
EditorCamera _camera ~ _.Dispose();
|
||||||
Entity _otherCameraEntity;
|
|
||||||
|
|
||||||
class CameraController : ScriptableEntity
|
EditorIcons _editorIcons ~ _.ReleaseRef();
|
||||||
{
|
|
||||||
protected override void OnCreate()
|
|
||||||
{
|
|
||||||
Log.EngineLogger.Trace("Cam controller created!");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnUpdate(GameTime gameTime)
|
EditorContentManager _contentManager;
|
||||||
{
|
|
||||||
var transformCmp = GetComponent<TransformComponent>();
|
|
||||||
|
|
||||||
Vector3 position = transformCmp.Position;
|
SceneState _sceneState = .Edit;
|
||||||
|
bool _isPaused = false;
|
||||||
|
|
||||||
if (Input.IsKeyPressed(Key.A))
|
/// Gets or sets the path of the current scene.
|
||||||
|
public StringView SceneFilePath
|
||||||
{
|
{
|
||||||
position.X -= gameTime.DeltaTime;
|
get => _sceneFilePath;
|
||||||
}
|
set
|
||||||
if (Input.IsKeyPressed(Key.D))
|
|
||||||
{
|
{
|
||||||
position.X += gameTime.DeltaTime;
|
_sceneFilePath.Clear();
|
||||||
}
|
|
||||||
if (Input.IsKeyPressed(Key.W))
|
|
||||||
{
|
|
||||||
position.Y += gameTime.DeltaTime;
|
|
||||||
}
|
|
||||||
if (Input.IsKeyPressed(Key.S))
|
|
||||||
{
|
|
||||||
position.Y -= gameTime.DeltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
transformCmp.Position = position;
|
if (!value.IsWhiteSpace)
|
||||||
}
|
_sceneFilePath.Append(value);
|
||||||
|
|
||||||
protected override void OnDestroy()
|
|
||||||
{
|
|
||||||
Log.EngineLogger.Trace("Cam controller destroyed!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public this() : base("Example")
|
public this(EditorContentManager contentManager) : base("Editor")
|
||||||
{
|
{
|
||||||
Application.Get().Window.IsVSync = false;
|
Application.Get().Window.IsVSync = false;
|
||||||
|
|
||||||
|
_contentManager = contentManager;
|
||||||
|
|
||||||
InitGraphics();
|
InitGraphics();
|
||||||
|
|
||||||
{
|
_editorScene = new Scene();
|
||||||
_cameraEntity = _scene.CreateEntity("Camera Entity");
|
SetReference!(_activeScene, _editorScene);
|
||||||
let camera = _cameraEntity.AddComponent<CameraComponent>();
|
|
||||||
camera.Camera.SetPerspective(MathHelper.ToRadians(75), 0.1f, 10000.0f);
|
|
||||||
camera.Primary = true;
|
|
||||||
camera.FixedAspectRatio = false;
|
|
||||||
let transform = _cameraEntity.GetComponent<TransformComponent>();
|
|
||||||
transform.Position = .(0, 0, -5);
|
|
||||||
|
|
||||||
_cameraEntity.AddComponent<NativeScriptComponent>().Bind<EditorCameraController>();
|
_gameSceneRenderer = new SceneRenderer();
|
||||||
_cameraEntity.AddComponent<EditorComponent>();
|
_editorSceneRenderer = new SceneRenderer();
|
||||||
}
|
|
||||||
|
|
||||||
{
|
_camera = EditorCamera(Vector3(3.5f, 1.25f, 2.75f), Quaternion.FromEulerAngles(MathHelper.ToRadians(40), MathHelper.ToRadians(25), 0), MathHelper.ToRadians(75), 0.1f, 1);
|
||||||
_otherCameraEntity = _scene.CreateEntity("Other Camera Entity");
|
_camera.RenderTarget = _cameraTarget;
|
||||||
let camera = _otherCameraEntity.AddComponent<CameraComponent>();
|
|
||||||
camera.Camera.SetPerspective(MathHelper.ToRadians(45), 0.1f, 1000.0f);
|
|
||||||
camera.Primary = false;
|
|
||||||
camera.FixedAspectRatio = false;
|
|
||||||
let transform = _otherCameraEntity.GetComponent<TransformComponent>();
|
|
||||||
transform.Position = .(0, 0, -5);
|
|
||||||
|
|
||||||
_otherCameraEntity.AddComponent<NativeScriptComponent>().Bind<EditorCameraController>();
|
|
||||||
_otherCameraEntity.AddComponent<EditorComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
InitEditor();
|
InitEditor();
|
||||||
|
|
||||||
|
NewScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitGraphics()
|
private void InitGraphics()
|
||||||
@@ -124,128 +123,560 @@ namespace GlitchyEditor
|
|||||||
DepthStencilStateDescription dsDesc = .();
|
DepthStencilStateDescription dsDesc = .();
|
||||||
_depthStencilState = new DepthStencilState(dsDesc);
|
_depthStencilState = new DepthStencilState(dsDesc);
|
||||||
|
|
||||||
_viewportTarget = new RenderTarget2D(RenderTarget2DDescription(.R8G8B8A8_UNorm, 100, 100) {DepthStencilFormat = .D32_Float});
|
_cameraTarget = new RenderTargetGroup(.(){
|
||||||
_viewportTarget.SamplerState = SamplerStateManager.LinearClamp;
|
Width = 100,
|
||||||
|
Height = 100,
|
||||||
|
ColorTargetDescriptions = TargetDescription[](
|
||||||
|
.(.R16G16B16A16_Float),
|
||||||
|
.(.R32_UInt)
|
||||||
|
),
|
||||||
|
DepthTargetDescription = .(.D24_UNorm_S8_UInt)
|
||||||
|
});
|
||||||
|
|
||||||
|
_editorViewportTarget = new RenderTargetGroup(.()
|
||||||
|
{
|
||||||
|
Width = 100,
|
||||||
|
Height = 100,
|
||||||
|
ColorTargetDescriptions = TargetDescription[](
|
||||||
|
.(.R8G8B8A8_UNorm))
|
||||||
|
});
|
||||||
|
|
||||||
|
_gameViewportTarget = 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitEditor()
|
private void InitEditor()
|
||||||
{
|
{
|
||||||
_editor = new Editor(_scene);
|
_editor = new Editor(_editorScene, _contentManager);
|
||||||
_editor.SceneViewportWindow.ViewportSizeChangedEvent.Add(new (s, e) => ViewportSizeChanged(s, e));
|
_editor.SceneViewportWindow.ViewportSizeChanged.Add(new (s, e) => EditorViewportSizeChanged(s, e));
|
||||||
|
_editor.GameViewportWindow.ViewportSizeChanged.Add(new (s, e) => GameViewportSizeChanged(s, e));
|
||||||
|
_editor.CurrentCamera = &_camera;
|
||||||
|
_editor.GameSceneRenderer = _gameSceneRenderer;
|
||||||
|
_editor.EditorSceneRenderer = _editorSceneRenderer;
|
||||||
|
|
||||||
//_editor.[Friend]CreateEntityWithTransform();
|
_editor.RequestOpenScene.Add(new (s, fileName) => {
|
||||||
|
LoadSceneFile(fileName);
|
||||||
_editor.SceneViewportWindow.CameraEntity = _cameraEntity;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(GameTime gameTime)
|
public override void Update(GameTime gameTime)
|
||||||
{
|
{
|
||||||
var scriptComponent = _cameraEntity.GetComponent<NativeScriptComponent>();
|
Debug.Profiler.ProfileFunction!();
|
||||||
|
|
||||||
if (var camController = scriptComponent.Instance as EditorCameraController)
|
_editor.CurrentScene = _activeScene;
|
||||||
|
|
||||||
|
Scene.UpdateMode updateMode;
|
||||||
|
|
||||||
|
switch (_sceneState)
|
||||||
{
|
{
|
||||||
camController.IsEnabled = (_editor.SceneViewportWindow.HasFocus && Input.IsMouseButtonPressed(.RightButton));
|
case .Edit:
|
||||||
|
updateMode = .Editor;
|
||||||
|
case .Play:
|
||||||
|
updateMode = .Runtime;
|
||||||
|
case .Simulate:
|
||||||
|
updateMode = .Physics;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TransformSystem.Update(_world);
|
if (_sceneState != .Edit && _isPaused)
|
||||||
|
updateMode = .None;
|
||||||
|
|
||||||
RenderCommand.Clear(_viewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
|
|
||||||
|
|
||||||
RenderCommand.SetRenderTarget(_viewportTarget, 0, true);
|
_activeScene.Update(gameTime, updateMode);
|
||||||
RenderCommand.BindRenderTargets();
|
|
||||||
|
|
||||||
RenderCommand.SetViewport(Viewport(0, 0, _viewportTarget.Width, _viewportTarget.Height));
|
// Clear the swapchain-buffer
|
||||||
|
RenderCommand.Clear(null, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
|
||||||
|
|
||||||
RenderCommand.SetBlendState(_alphaBlendState);
|
RenderCommand.SetBlendState(_alphaBlendState);
|
||||||
RenderCommand.SetDepthStencilState(_depthStencilState);
|
RenderCommand.SetDepthStencilState(_depthStencilState);
|
||||||
|
|
||||||
//Renderer.BeginScene(_cameraController.Camera);
|
if (_editor.SceneViewportWindow.Visible)
|
||||||
|
{
|
||||||
|
_camera.Update(gameTime);
|
||||||
|
|
||||||
//DebugRenderer.Render(_scene.[Friend]_ecsWorld);
|
_editorSceneRenderer.Scene = _activeScene;
|
||||||
|
|
||||||
//Renderer.EndScene();
|
RenderCommand.Clear(_editorViewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
|
||||||
|
_editorSceneRenderer.RenderEditor(gameTime, _camera, _editorViewportTarget, scope => DebugDraw3D, scope => DebugDraw2D);
|
||||||
|
}
|
||||||
|
|
||||||
_scene.Update(gameTime);
|
RenderCommand.SetBlendState(_alphaBlendState);
|
||||||
|
RenderCommand.SetDepthStencilState(_depthStencilState);
|
||||||
|
|
||||||
RenderCommand.Clear(null, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
|
if (_editor.GameViewportWindow.Visible)
|
||||||
|
{
|
||||||
|
_gameSceneRenderer.Scene = _activeScene;
|
||||||
|
|
||||||
|
RenderCommand.Clear(_gameViewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
|
||||||
|
_gameSceneRenderer.RenderRuntime(gameTime, _gameViewportTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCommand.UnbindRenderTargets();
|
||||||
RenderCommand.SetRenderTarget(null, 0, true);
|
RenderCommand.SetRenderTarget(null, 0, true);
|
||||||
RenderCommand.BindRenderTargets();
|
RenderCommand.BindRenderTargets();
|
||||||
|
|
||||||
RenderCommand.SetViewport(_context.SwapChain.BackbufferViewport);
|
RenderCommand.SetViewport(_context.SwapChain.BackbufferViewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DebugDraw3D()
|
||||||
|
{
|
||||||
|
/*for (var (entity, transform, camera) in _scene.[Friend]_ecsWorld.Enumerate<TransformComponent, CameraComponent>())
|
||||||
|
{
|
||||||
|
if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _scene)))
|
||||||
|
{
|
||||||
|
DebugRenderer.DrawViewFrustum(transform.WorldTransform, camera.Camera.Projection);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DebugDraw2D()
|
||||||
|
{
|
||||||
|
RenderCommand.SetBlendState(_alphaBlendState);
|
||||||
|
|
||||||
|
Matrix billboard = _camera.View.Invert();
|
||||||
|
billboard.Translation = .Zero;
|
||||||
|
|
||||||
|
Matrix Billboard(Matrix transform)
|
||||||
|
{
|
||||||
|
Vector3 worldPos = transform.Translation;
|
||||||
|
|
||||||
|
return Matrix.Translation(worldPos) * billboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
float CalculateAlpha(Vector3 pos)
|
||||||
|
{
|
||||||
|
return Math.Clamp(1.5f - Vector3.Distance(_editor.CurrentCamera.Position, pos) / 50, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var (entity, transform, camera) in _activeScene.GetEntities<TransformComponent, CameraComponent>())
|
||||||
|
{
|
||||||
|
if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _activeScene)))
|
||||||
|
{
|
||||||
|
DebugRenderer.DrawViewFrustum(transform.WorldTransform, camera.Camera.Projection, .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 _activeScene.GetEntities<TransformComponent, LightComponent>())
|
||||||
|
{
|
||||||
|
if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _activeScene)))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var (entity, transform, collider) in _activeScene.GetEntities<TransformComponent, BoxCollider2DComponent>())
|
||||||
|
{
|
||||||
|
Renderer2D.DrawRect(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Size.X * 2, collider.Size.Y * 2, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var (entity, transform, collider) in _activeScene.GetEntities<TransformComponent, CircleCollider2DComponent>())
|
||||||
|
{
|
||||||
|
Renderer2D.DrawCircle(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Radius * 2), (Texture2D)null, ColorRGBA(0f, 1f, 0f), 0.01f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnEvent(Event event)
|
public override void OnEvent(Event event)
|
||||||
{
|
{
|
||||||
EventDispatcher dispatcher = EventDispatcher(event);
|
EventDispatcher dispatcher = EventDispatcher(event);
|
||||||
|
|
||||||
dispatcher.Dispatch<ImGuiRenderEvent>(scope (e) => OnImGuiRender(e));
|
dispatcher.Dispatch<ImGuiRenderEvent>(scope (e) => OnImGuiRender(e));
|
||||||
dispatcher.Dispatch<WindowResizeEvent>(scope (e) => OnWindowResize(e));
|
dispatcher.Dispatch<WindowResizeEvent>(scope (e) => OnWindowResize(e));
|
||||||
|
dispatcher.Dispatch<KeyPressedEvent>(scope (e) => OnKeyPressed(e));
|
||||||
|
dispatcher.Dispatch<MouseScrolledEvent>(scope (e) => OnMouseScrolled(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.ID _mainDockspaceId;
|
ImGui.ID _mainDockspaceId;
|
||||||
|
|
||||||
|
TextureViewer viewer = new TextureViewer() ~ delete _;
|
||||||
|
|
||||||
private bool OnImGuiRender(ImGuiRenderEvent event)
|
private bool OnImGuiRender(ImGuiRenderEvent event)
|
||||||
{
|
{
|
||||||
ImGui.Begin("Test");
|
Input.ImGuiDebugDraw();
|
||||||
|
|
||||||
static bool cameraA = true;
|
//viewer.ViewTexture(Renderer.[Friend]_gBuffer.Target);
|
||||||
|
|
||||||
if (ImGui.Checkbox("Camera A", &cameraA))
|
|
||||||
{
|
|
||||||
_cameraEntity.GetComponent<CameraComponent>().Primary = cameraA;
|
|
||||||
_otherCameraEntity.GetComponent<CameraComponent>().Primary = !cameraA;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.End();
|
|
||||||
|
|
||||||
ImGui.Viewport* viewport = ImGui.GetMainViewport();
|
ImGui.Viewport* viewport = ImGui.GetMainViewport();
|
||||||
ImGui.DockSpaceOverViewport(viewport);
|
ImGui.DockSpaceOverViewport(viewport);
|
||||||
|
|
||||||
DrawMainMenuBar();
|
DrawMainMenuBar();
|
||||||
|
|
||||||
_editor.SceneViewportWindow.RenderTarget = _viewportTarget;
|
_editor.SceneViewportWindow.RenderTarget = _editorViewportTarget;
|
||||||
|
_editor.GameViewportWindow.RenderTarget = _gameViewportTarget;
|
||||||
|
|
||||||
_editor.Update();
|
_editor.Update();
|
||||||
|
|
||||||
_settingsWindow.Show();
|
_settingsWindow.Show();
|
||||||
|
|
||||||
|
UI_Toolbar();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UI_Toolbar()
|
||||||
|
{
|
||||||
|
ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(0, 2));
|
||||||
|
ImGui.PushStyleVar(.ItemInnerSpacing, ImGui.Vec2(0, 0));
|
||||||
|
ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));
|
||||||
|
|
||||||
|
let colors = ImGui.GetStyle().Colors;
|
||||||
|
|
||||||
|
ImGui.Vec4 hoveredColor = colors[(int)ImGui.Col.ButtonHovered];
|
||||||
|
hoveredColor.w = 0.5f;
|
||||||
|
|
||||||
|
ImGui.Vec4 activeColor = colors[(int)ImGui.Col.ButtonActive];
|
||||||
|
activeColor.w = 0.5f;
|
||||||
|
|
||||||
|
ImGui.PushStyleColor(.ButtonHovered, hoveredColor);
|
||||||
|
ImGui.PushStyleColor(.ButtonActive, activeColor);
|
||||||
|
|
||||||
|
ImGui.Begin("##toolbar", null, .NoDecoration | .NoScrollbar | .NoScrollWithMouse);
|
||||||
|
|
||||||
|
float padding = 2.0f;
|
||||||
|
|
||||||
|
float size = ImGui.GetWindowHeight() - 2 * padding;
|
||||||
|
|
||||||
|
float centerX = ImGui.GetContentRegionMax().x / 2;
|
||||||
|
|
||||||
|
|
||||||
|
if (_sceneState == .Edit)
|
||||||
|
EditorButtons:
|
||||||
|
{
|
||||||
|
// Display the buttons for edit state
|
||||||
|
|
||||||
|
float totalWidth = size * 3 + padding * 4;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosX(centerX - totalWidth / 2);
|
||||||
|
|
||||||
|
if (ImGui.ImageButton(_editorIcons.Play, .(size, size), .Zero, .Ones, 0))
|
||||||
|
OnScenePlay();
|
||||||
|
|
||||||
|
ImGui.AttachTooltip("Play the game.");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.PushID(1);
|
||||||
|
|
||||||
|
if (ImGui.ImageButton(_editorIcons.Simulate, .(size, size), .Zero, .Ones, 0))
|
||||||
|
OnSceneSimulate();
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
|
|
||||||
|
ImGui.AttachTooltip("Enter simulation mode.\nThis only runs the physics engine.");
|
||||||
|
|
||||||
|
if (_isPaused)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(.Button, *ImGui.GetStyleColorVec4(.ButtonActive));
|
||||||
|
|
||||||
|
defer:EditorButtons { ImGui.PopStyleColor(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushID(2);
|
||||||
|
|
||||||
|
//ImGui.SameLine(penX += size + 2 * padding);
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.ImageButton(_editorIcons.Pause, .(size, size), .Zero, .Ones, 0))
|
||||||
|
_isPaused = !_isPaused;
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
|
|
||||||
|
ImGui.AttachTooltip("If enabled the game or simulation will be started in paused state.");
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Display the buttons for play/simulation state
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosX(centerX - size - padding);
|
||||||
|
|
||||||
|
SubTexture2D pauseButtonIcon = _isPaused ? _editorIcons.Play : _editorIcons.Pause;
|
||||||
|
|
||||||
|
if (ImGui.ImageButton(pauseButtonIcon, .(size, size), .Zero, .Ones, 0))
|
||||||
|
{
|
||||||
|
if (_isPaused)
|
||||||
|
OnSceneResume();
|
||||||
|
else
|
||||||
|
OnScenePause();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.AttachTooltip(_isPaused ? "Resume" : "Pause");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.PushID(1);
|
||||||
|
|
||||||
|
if (ImGui.ImageButton(_editorIcons.Stop, .(size, size), .Zero, .Ones, 0))
|
||||||
|
OnSceneStop();
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
|
|
||||||
|
ImGui.AttachTooltip("Stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
|
||||||
|
ImGui.PopStyleColor(3);
|
||||||
|
ImGui.PopStyleVar(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScenePlay()
|
||||||
|
{
|
||||||
|
_editor.SceneViewportWindow.EditorMode = false;
|
||||||
|
_sceneState = .Play;
|
||||||
|
|
||||||
|
using (Scene runtimeScene = new Scene())
|
||||||
|
{
|
||||||
|
_editorScene.CopyTo(runtimeScene);
|
||||||
|
|
||||||
|
runtimeScene.OnRuntimeStart();
|
||||||
|
|
||||||
|
SetReference!(_activeScene, runtimeScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
_editor.CurrentScene = _activeScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSceneSimulate()
|
||||||
|
{
|
||||||
|
_editor.SceneViewportWindow.EditorMode = false;
|
||||||
|
_sceneState = .Simulate;
|
||||||
|
|
||||||
|
using (Scene simulationScene = new Scene())
|
||||||
|
{
|
||||||
|
_editorScene.CopyTo(simulationScene);
|
||||||
|
|
||||||
|
simulationScene.OnSimulationStart();
|
||||||
|
|
||||||
|
SetReference!(_activeScene, simulationScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
_editor.CurrentScene = _activeScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScenePause()
|
||||||
|
{
|
||||||
|
_isPaused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSceneResume()
|
||||||
|
{
|
||||||
|
_isPaused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSceneStop()
|
||||||
|
{
|
||||||
|
if (_sceneState == .Play)
|
||||||
|
_activeScene.OnRuntimeStop();
|
||||||
|
else
|
||||||
|
_activeScene.OnSimulationStop();
|
||||||
|
|
||||||
|
SetReference!(_activeScene, _editorScene);
|
||||||
|
|
||||||
|
_editor.SceneViewportWindow.EditorMode = true;
|
||||||
|
_sceneState = .Edit;
|
||||||
|
|
||||||
|
_editor.CurrentScene = _activeScene;
|
||||||
|
|
||||||
|
_isPaused = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update the viewport size because if the game windows size changed in
|
||||||
|
* "Game"-mode the updated aspect-rations will reset once we go back
|
||||||
|
* into "Editor"-mode (because Game-Mode works on a copy of the scene).
|
||||||
|
*/
|
||||||
|
GameViewportSizeChanged(null, _editor.GameViewportWindow.ViewportSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new scene.
|
||||||
|
private void NewScene()
|
||||||
|
{
|
||||||
|
OnSceneStop();
|
||||||
|
|
||||||
|
SceneFilePath = null;
|
||||||
|
|
||||||
|
Scene newScene = new Scene();
|
||||||
|
|
||||||
|
_camera.Position = .(-1.5f, 1.5f, -2.5f);
|
||||||
|
_camera.RotationEuler = .(MathHelper.ToRadians(25), MathHelper.ToRadians(35), 0);
|
||||||
|
|
||||||
|
// Create a default camera
|
||||||
|
{
|
||||||
|
let cameraEntity = newScene.CreateEntity("Camera");
|
||||||
|
let transform = cameraEntity.Transform;
|
||||||
|
transform.Position = Vector3(0, 2, -5);
|
||||||
|
transform.RotationEuler = Vector3(0, MathHelper.ToRadians(25), 0);
|
||||||
|
|
||||||
|
let camera = cameraEntity.AddComponent<CameraComponent>();
|
||||||
|
camera.Primary = true;
|
||||||
|
camera.Camera.ProjectionType = .InfinitePerspective;
|
||||||
|
camera.Camera.PerspectiveFovY = MathHelper.ToRadians(75);
|
||||||
|
camera.Camera.PerspectiveNearPlane = 0.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a default light source
|
||||||
|
{
|
||||||
|
let lightEntity = newScene.CreateEntity("Light");
|
||||||
|
let transform = lightEntity.Transform;
|
||||||
|
transform.Position = .(-3, 4, -1.5f);
|
||||||
|
transform.RotationEuler = .(MathHelper.ToRadians(20), MathHelper.ToRadians(75), MathHelper.ToRadians(20));
|
||||||
|
|
||||||
|
let light = lightEntity.AddComponent<LightComponent>();
|
||||||
|
light.SceneLight.Illuminance = 10.0f;
|
||||||
|
light.SceneLight.Color = .(1.0f, 0.95f, 0.8f);
|
||||||
|
}
|
||||||
|
|
||||||
|
_editorScene.ReleaseRef();
|
||||||
|
_editorScene = newScene;
|
||||||
|
_editor.CurrentScene = _editorScene;
|
||||||
|
var vpSize = _editor.SceneViewportWindow.ViewportSize;
|
||||||
|
_editorScene.OnViewportResize((.)vpSize.X, (.)vpSize.Y);
|
||||||
|
|
||||||
|
SetReference!(_activeScene, _editorScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 (SceneFilePath.IsWhiteSpace)
|
||||||
|
{
|
||||||
|
SaveSceneAs();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneSerializer serializer = scope .(_editorScene);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
OnSceneStop();
|
||||||
|
|
||||||
|
SceneFilePath = scope String(filename);
|
||||||
|
|
||||||
|
_editorScene.ReleaseRef();
|
||||||
|
_editorScene = new Scene();
|
||||||
|
_editor.CurrentScene = _editorScene;
|
||||||
|
var vpSize = _editor.SceneViewportWindow.ViewportSize;
|
||||||
|
_editorScene.OnViewportResize((.)vpSize.X, (.)vpSize.Y);
|
||||||
|
|
||||||
|
SceneSerializer serializer = scope .(_editorScene);
|
||||||
|
serializer.Deserialize(SceneFilePath);
|
||||||
|
|
||||||
|
SetReference!(_activeScene, _editorScene);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawMainMenuBar()
|
private void DrawMainMenuBar()
|
||||||
{
|
{
|
||||||
ImGui.BeginMainMenuBar();
|
ImGui.BeginMainMenuBar();
|
||||||
|
|
||||||
if(ImGui.BeginMenu("File", true))
|
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"))
|
if (ImGui.MenuItem("Settings"))
|
||||||
_settingsWindow.Open = true;
|
_settingsWindow.Open = true;
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Exit"))
|
||||||
|
Application.Get().Close();
|
||||||
|
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ImGui.BeginMenu("View", true))
|
if(ImGui.BeginMenu("View", true))
|
||||||
{
|
{
|
||||||
if(ImGui.MenuItem(EntityHierarchyWindow.s_WindowTitle))
|
|
||||||
{
|
|
||||||
_editor.EntityHierarchyWindow.Open = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ImGui.MenuItem(ComponentEditWindow.s_WindowTitle))
|
if(ImGui.MenuItem(ComponentEditWindow.s_WindowTitle))
|
||||||
{
|
|
||||||
_editor.ComponentEditWindow.Open = true;
|
_editor.ComponentEditWindow.Open = true;
|
||||||
}
|
|
||||||
|
|
||||||
if(ImGui.MenuItem(SceneViewportWindow.s_WindowTitle))
|
if(ImGui.MenuItem(ContentBrowserWindow.s_WindowTitle))
|
||||||
{
|
_editor.ComponentEditWindow.Open = true;
|
||||||
|
|
||||||
|
if(ImGui.MenuItem(EditorViewportWindow.s_WindowTitle))
|
||||||
_editor.SceneViewportWindow.Open = true;
|
_editor.SceneViewportWindow.Open = true;
|
||||||
}
|
|
||||||
|
if(ImGui.MenuItem(EntityHierarchyWindow.s_WindowTitle))
|
||||||
|
_editor.EntityHierarchyWindow.Open = true;
|
||||||
|
|
||||||
|
if(ImGui.MenuItem(GameViewportWindow.s_WindowTitle))
|
||||||
|
_editor.GameViewportWindow.Open = true;
|
||||||
|
|
||||||
|
if(ImGui.MenuItem(PropertiesWindow.s_WindowTitle))
|
||||||
|
_editor.PropertiesWindow.Open = true;
|
||||||
|
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui.EndMainMenuBar();
|
ImGui.EndMainMenuBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +685,7 @@ namespace GlitchyEditor
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ViewportSizeChanged(Object sender, Vector2 viewportSize)
|
private void EditorViewportSizeChanged(Object sender, Vector2 viewportSize)
|
||||||
{
|
{
|
||||||
uint32 sizeX = (uint32)viewportSize.X;
|
uint32 sizeX = (uint32)viewportSize.X;
|
||||||
uint32 sizeY = (uint32)viewportSize.Y;
|
uint32 sizeY = (uint32)viewportSize.Y;
|
||||||
@@ -262,9 +693,64 @@ namespace GlitchyEditor
|
|||||||
if(sizeX == 0 || sizeY == 0)
|
if(sizeX == 0 || sizeY == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_viewportTarget.Resize(sizeX, sizeY);
|
_editorViewportTarget.Resize(sizeX, sizeY);
|
||||||
|
_cameraTarget.Resize(sizeX, sizeY);
|
||||||
|
|
||||||
_scene.OnViewportResize(sizeX, sizeY);
|
_camera.OnViewportResize(sizeX, sizeY);
|
||||||
|
|
||||||
|
_editorSceneRenderer.OnViewportResize(sizeX, sizeY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GameViewportSizeChanged(Object sender, Vector2 viewportSize)
|
||||||
|
{
|
||||||
|
uint32 sizeX = (uint32)viewportSize.X;
|
||||||
|
uint32 sizeY = (uint32)viewportSize.Y;
|
||||||
|
|
||||||
|
if(sizeX == 0 || sizeY == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_gameViewportTarget.Resize(sizeX, sizeY);
|
||||||
|
|
||||||
|
_activeScene.OnViewportResize(sizeX, sizeY);
|
||||||
|
|
||||||
|
_gameSceneRenderer.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using ImGui;
|
||||||
|
using GlitchyEngine.Math;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GlitchyEditor
|
||||||
|
{
|
||||||
|
class TextureViewer
|
||||||
|
{
|
||||||
|
enum BackgroundMode : int32
|
||||||
|
{
|
||||||
|
White,
|
||||||
|
Black,
|
||||||
|
Checkerboard
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SampleMode : int32
|
||||||
|
{
|
||||||
|
Point,
|
||||||
|
Linear
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsContext _context ~ _.ReleaseRef();
|
||||||
|
|
||||||
|
Effect _effect ~ _.ReleaseRef();
|
||||||
|
|
||||||
|
float _zoom = 1.0f;
|
||||||
|
|
||||||
|
BackgroundMode _backgroundMode = .Checkerboard;
|
||||||
|
|
||||||
|
SampleMode _sampleMode = .Linear;
|
||||||
|
|
||||||
|
RenderTarget2D _target ~ _?.ReleaseRef();
|
||||||
|
// TODO: we don't need depth!
|
||||||
|
DepthStencilTarget _depth ~ _?.ReleaseRef();
|
||||||
|
|
||||||
|
SamplerState _samplerPoint ~ _.ReleaseRef();
|
||||||
|
SamplerState _samplerLinear ~ _.ReleaseRef();
|
||||||
|
|
||||||
|
public this()
|
||||||
|
{
|
||||||
|
_context = Application.Get().Window.Context..AddRef();
|
||||||
|
|
||||||
|
InitEffect();
|
||||||
|
InitState();
|
||||||
|
// TODO: rasterizerstate and depthstencilstate
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitEffect()
|
||||||
|
{
|
||||||
|
_effect = new Effect("content\\Shaders\\textureViewerShader.hlsl");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitState()
|
||||||
|
{
|
||||||
|
SamplerStateDescription desc = .();
|
||||||
|
desc.MagFilter = .Linear;
|
||||||
|
desc.MinFilter = .Linear;
|
||||||
|
_samplerLinear = SamplerStateManager.GetSampler(desc);
|
||||||
|
|
||||||
|
desc.MagFilter = .Point;
|
||||||
|
desc.MinFilter = .Point;
|
||||||
|
_samplerPoint = SamplerStateManager.GetSampler(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 _position;
|
||||||
|
|
||||||
|
bool _moving;
|
||||||
|
|
||||||
|
float _colorOffset = 0;
|
||||||
|
float _colorScale = 1;
|
||||||
|
float _alphaOffset = 0;
|
||||||
|
float _alphaScale = 1;
|
||||||
|
|
||||||
|
public void ViewTexture(Texture viewedTexture)
|
||||||
|
{
|
||||||
|
ImGui.Begin("Texture Viewer");
|
||||||
|
|
||||||
|
ImGui.SliderFloat("Zoom", &_zoom, 0.01f, 100.0f);
|
||||||
|
|
||||||
|
char8*[] items = scope .("White", "Black", "Checkerboard");
|
||||||
|
|
||||||
|
ImGui.Combo("Background", (.)&_backgroundMode, items.Ptr, (.)items.Count);
|
||||||
|
|
||||||
|
items = scope .("Point", "Linear");
|
||||||
|
|
||||||
|
ImGui.Combo("Sampler", (.)&_sampleMode, items.Ptr, (.)items.Count);
|
||||||
|
|
||||||
|
ImGui.SliderFloat2("Color offset and scale", *(float[2]*)&_colorOffset, -1.0f, 1.0f);
|
||||||
|
ImGui.SliderFloat2("Alpha offset and scale", *(float[2]*)&_alphaOffset, -1.0f, 1.0f);
|
||||||
|
|
||||||
|
ImGui.SliderFloat2("Position", *(float[2]*)&_position, 2 * -Math.Max(viewedTexture.Width, viewedTexture.Height) * _zoom, 2 * Math.Max(viewedTexture.Width, viewedTexture.Height) * _zoom);
|
||||||
|
|
||||||
|
ImGui.BeginChild("imageChild");
|
||||||
|
|
||||||
|
UpdateInput();
|
||||||
|
|
||||||
|
var viewportSize = ImGui.GetContentRegionAvail();
|
||||||
|
|
||||||
|
viewportSize.x = Math.Max(viewportSize.x, 1);
|
||||||
|
viewportSize.y = Math.Max(viewportSize.y, 1);
|
||||||
|
|
||||||
|
if(_target == null || viewportSize.x != _target.Width || viewportSize.y != _target.Height)
|
||||||
|
{
|
||||||
|
_target?.ReleaseRef();
|
||||||
|
_target = new RenderTarget2D(.(.R8G8B8A8_UNorm, (.)viewportSize.x, (.)viewportSize.y));
|
||||||
|
_depth?.ReleaseRef();
|
||||||
|
_depth = new DepthStencilTarget((.)viewportSize.x, (.)viewportSize.y, .D16_UNorm);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTexture(viewedTexture);
|
||||||
|
|
||||||
|
ImGui.Image(_target, viewportSize);
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
float lastWheel;
|
||||||
|
|
||||||
|
private void UpdateInput()
|
||||||
|
{
|
||||||
|
var windowPos = ImGui.GetWindowPos();
|
||||||
|
var mousePos = ImGui.GetIO().MousePos;
|
||||||
|
Vector2 mouseInWindow = .(mousePos.x - windowPos.x, mousePos.y - windowPos.y);
|
||||||
|
|
||||||
|
bool windowHovered = ImGui.IsWindowHovered();
|
||||||
|
|
||||||
|
if(windowHovered && Input.IsMouseButtonPressing(.MiddleButton))
|
||||||
|
{
|
||||||
|
_moving = true;
|
||||||
|
}
|
||||||
|
else if(Input.IsMouseButtonReleased(.MiddleButton))
|
||||||
|
{
|
||||||
|
_moving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(windowHovered || _moving)
|
||||||
|
{
|
||||||
|
float mouseWheel = ImGui.GetIO().MouseWheel;
|
||||||
|
|
||||||
|
float delta = mouseWheel - lastWheel;
|
||||||
|
|
||||||
|
if(delta != 0)
|
||||||
|
{
|
||||||
|
_position -= mouseInWindow;
|
||||||
|
_position /= _zoom;
|
||||||
|
|
||||||
|
_zoom *= Math.Pow(1.1f, delta);
|
||||||
|
|
||||||
|
_position *= _zoom;
|
||||||
|
_position += mouseInWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_moving)
|
||||||
|
{
|
||||||
|
Int2 movement = Input.GetMouseMovement();
|
||||||
|
|
||||||
|
_position.X += movement.X;
|
||||||
|
_position.Y += movement.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthographicCamera _camera = new OrthographicCamera() ~ delete _;
|
||||||
|
|
||||||
|
private void RenderTexture(Texture viewedTexture)
|
||||||
|
{
|
||||||
|
Viewport vp = .(0, 0, _target.Width, _target.Height);
|
||||||
|
RenderCommand.SetViewport(vp);
|
||||||
|
|
||||||
|
// TODO: don't clear pink!
|
||||||
|
RenderCommand.Clear(_target, .Pink);
|
||||||
|
RenderCommand.Clear(_depth, .Depth, 1.0f, 0);
|
||||||
|
|
||||||
|
//_target.Bind();
|
||||||
|
_context.SetRenderTarget(_target);
|
||||||
|
_context.SetDepthStencilTarget(_depth);
|
||||||
|
_context.BindRenderTargets();
|
||||||
|
|
||||||
|
Vector2 textureSize = Vector2(viewedTexture.Width, viewedTexture.Height);
|
||||||
|
Vector2 zoomedTextureSize = textureSize * _zoom;
|
||||||
|
|
||||||
|
Vector2 targetSize = Vector2(_target.Width, _target.Height);
|
||||||
|
|
||||||
|
_effect.Variables["ColorOffset"].SetData(_colorOffset);
|
||||||
|
_effect.Variables["ColorScale"].SetData(_colorScale);
|
||||||
|
_effect.Variables["AlphaOffset"].SetData(_alphaOffset);
|
||||||
|
_effect.Variables["AlphaScale"].SetData(_alphaScale);
|
||||||
|
|
||||||
|
_camera.Left = 0;
|
||||||
|
_camera.Top = 0;
|
||||||
|
_camera.Right = targetSize.X;
|
||||||
|
_camera.Bottom = -targetSize.Y;
|
||||||
|
_camera.NearPlane = -5;
|
||||||
|
_camera.FarPlane = 5;
|
||||||
|
_camera.Update();
|
||||||
|
|
||||||
|
Renderer2D.BeginScene(_camera);
|
||||||
|
|
||||||
|
switch(_backgroundMode)
|
||||||
|
{
|
||||||
|
case .Black:
|
||||||
|
Renderer2D.DrawQuadPivotCorner(Vector3(0, 0, 1), targetSize, 0, .Black);
|
||||||
|
case .White:
|
||||||
|
Renderer2D.DrawQuadPivotCorner(Vector3(0, 0, 1), targetSize, 0, .White);
|
||||||
|
case .Checkerboard:
|
||||||
|
float quadSize = 50.0f;
|
||||||
|
|
||||||
|
Vector2 numQuads = (targetSize / 500f) * 10f;
|
||||||
|
|
||||||
|
for(float x = 0; x < numQuads.X; x++)
|
||||||
|
{
|
||||||
|
for(float y = 0; y < numQuads.Y; y++)
|
||||||
|
{
|
||||||
|
Renderer2D.DrawQuadPivotCorner(Vector3(x * quadSize, -y * quadSize, 1), quadSize.XX, 0, ((x + y) % 2 == 0) ? .White : .Gray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer2D.EndScene();
|
||||||
|
|
||||||
|
var sampler = viewedTexture.SamplerState;
|
||||||
|
|
||||||
|
switch(_sampleMode)
|
||||||
|
{
|
||||||
|
case .Point:
|
||||||
|
viewedTexture.SamplerState = _samplerPoint;
|
||||||
|
case .Linear:
|
||||||
|
viewedTexture.SamplerState = _samplerLinear;
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer2D.BeginScene(_camera, .SortByTexture, _effect);
|
||||||
|
|
||||||
|
Renderer2D.DrawQuad(Vector3(_position * .(1, -1), 0), zoomedTextureSize, 0, viewedTexture);
|
||||||
|
|
||||||
|
Renderer2D.EndScene();
|
||||||
|
|
||||||
|
viewedTexture.SamplerState = sampler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
FileVersion = 1
|
FileVersion = 1
|
||||||
Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*"}
|
Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*", box2d-beef = "*", "Beef.Linq" = "*"}
|
||||||
|
|
||||||
[Project]
|
[Project]
|
||||||
Name = "GlitchyEngine"
|
Name = "GlitchyEngine"
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ using GlitchyEngine.Content;
|
|||||||
|
|
||||||
namespace GlitchyEngine
|
namespace GlitchyEngine
|
||||||
{
|
{
|
||||||
public class Application
|
public abstract class Application
|
||||||
{
|
{
|
||||||
static Application s_Instance = null;
|
static Application s_Instance = null;
|
||||||
|
|
||||||
private Window _window;
|
private Window _window;
|
||||||
private RendererAPI _rendererApi;
|
private RendererAPI _rendererApi;
|
||||||
private EffectLibrary _effectLibrary;
|
|
||||||
|
|
||||||
private bool _running = true;
|
private bool _running = true;
|
||||||
private bool _isMinimized = false;
|
private bool _isMinimized = false;
|
||||||
@@ -31,12 +30,12 @@ namespace GlitchyEngine
|
|||||||
public bool IsRunning => _running;
|
public bool IsRunning => _running;
|
||||||
public Window Window => _window;
|
public Window Window => _window;
|
||||||
|
|
||||||
public EffectLibrary EffectLibrary => _effectLibrary;
|
|
||||||
|
|
||||||
public IContentManager ContentManager => _contentManager;
|
public IContentManager ContentManager => _contentManager;
|
||||||
|
|
||||||
public bool IsMinimized => _isMinimized;
|
public bool IsMinimized => _isMinimized;
|
||||||
|
|
||||||
|
public GameTime GameTime => _gameTime;
|
||||||
|
|
||||||
[Inline]
|
[Inline]
|
||||||
public static Application Get() => s_Instance;
|
public static Application Get() => s_Instance;
|
||||||
|
|
||||||
@@ -55,18 +54,20 @@ namespace GlitchyEngine
|
|||||||
_window = new Window(.Default);
|
_window = new Window(.Default);
|
||||||
_window.EventCallback = new => OnEvent;
|
_window.EventCallback = new => OnEvent;
|
||||||
|
|
||||||
|
Input.Init();
|
||||||
|
|
||||||
|
_contentManager = InitContentManager();
|
||||||
|
|
||||||
|
// TODO: RenderAPI in RenderCommand initialisieren?
|
||||||
_rendererApi = new RendererAPI();
|
_rendererApi = new RendererAPI();
|
||||||
_rendererApi.Context = _window.Context;
|
_rendererApi.Context = _window.Context;
|
||||||
|
|
||||||
_contentManager = new ContentManager("./content");
|
|
||||||
|
|
||||||
SamplerStateManager.Init();
|
SamplerStateManager.Init();
|
||||||
|
|
||||||
|
// TODO: Rendercommmand in Renderer initialisieren?
|
||||||
RenderCommand.RendererAPI = _rendererApi;
|
RenderCommand.RendererAPI = _rendererApi;
|
||||||
|
|
||||||
_effectLibrary = new EffectLibrary();
|
Renderer.Init();
|
||||||
|
|
||||||
Renderer.Init(_window.Context, _effectLibrary);
|
|
||||||
|
|
||||||
#if IMGUI
|
#if IMGUI
|
||||||
_imGuiLayer = new ImGuiLayer();
|
_imGuiLayer = new ImGuiLayer();
|
||||||
@@ -77,6 +78,13 @@ namespace GlitchyEngine
|
|||||||
Settings.Apply();
|
Settings.Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes the content manager.
|
||||||
|
protected abstract IContentManager InitContentManager();
|
||||||
|
//{
|
||||||
|
// TODO: init default content manager?
|
||||||
|
//_contentManager = new ContentManager("./content");
|
||||||
|
//}
|
||||||
|
|
||||||
public ~this()
|
public ~this()
|
||||||
{
|
{
|
||||||
Profiler.ProfileFunction!();
|
Profiler.ProfileFunction!();
|
||||||
@@ -84,8 +92,6 @@ namespace GlitchyEngine
|
|||||||
SamplerStateManager.Uninit();
|
SamplerStateManager.Uninit();
|
||||||
Renderer.Deinit();
|
Renderer.Deinit();
|
||||||
|
|
||||||
delete _effectLibrary;
|
|
||||||
|
|
||||||
delete _contentManager;
|
delete _contentManager;
|
||||||
|
|
||||||
delete _rendererApi;
|
delete _rendererApi;
|
||||||
@@ -167,6 +173,12 @@ namespace GlitchyEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Closes the applcation.
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
public void PushLayer(Layer ownLayer)
|
public void PushLayer(Layer ownLayer)
|
||||||
{
|
{
|
||||||
Profiler.ProfileFunction!();
|
Profiler.ProfileFunction!();
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ using System.Collections;
|
|||||||
|
|
||||||
namespace GlitchyEngine.Collections
|
namespace GlitchyEngine.Collections
|
||||||
{
|
{
|
||||||
|
// TODO: TreeNode is very bare minimum
|
||||||
|
// Destructor?
|
||||||
|
// RemoveChild?
|
||||||
|
// Remove in enumerator?
|
||||||
|
|
||||||
public class TreeNode<T>
|
public class TreeNode<T>
|
||||||
{
|
{
|
||||||
public T Value;
|
public T Value;
|
||||||
|
|
||||||
|
public Self Parent;
|
||||||
public List<Self> Children = new .() ~ DeleteContainerAndItems!(_);
|
public List<Self> Children = new .() ~ DeleteContainerAndItems!(_);
|
||||||
|
|
||||||
public this() {}
|
public this() {}
|
||||||
@@ -24,6 +30,7 @@ namespace GlitchyEngine.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self newChild = new .(value);
|
Self newChild = new .(value);
|
||||||
|
newChild.Parent = this;
|
||||||
|
|
||||||
Children.Add(newChild);
|
Children.Add(newChild);
|
||||||
|
|
||||||
@@ -44,5 +51,32 @@ namespace GlitchyEngine.Collections
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ref T operator ->(TreeNode<T> node)
|
||||||
|
{
|
||||||
|
return ref node.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
public static mixin DeleteTreeAndChildren<T>(TreeNode<T> tree) where T : class, delete
|
||||||
|
{
|
||||||
|
InternalDeleteTreeAndChildren(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InternalDeleteTreeAndChildren<T>(TreeNode<T> tree) where T : class, delete
|
||||||
|
{
|
||||||
|
for (var child in tree.Children)
|
||||||
|
{
|
||||||
|
InternalDeleteTreeAndChildren(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete tree.Value;
|
||||||
|
|
||||||
|
tree.Children.Clear();
|
||||||
|
|
||||||
|
delete tree;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using GlitchyEngine.Core;
|
||||||
|
using System;
|
||||||
|
using Bon;
|
||||||
|
using Bon.Integrated;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Content;
|
||||||
|
|
||||||
|
[BonTarget]
|
||||||
|
abstract class Asset : RefCounter
|
||||||
|
{
|
||||||
|
internal AssetHandle _handle = .Invalid;
|
||||||
|
|
||||||
|
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;
|
||||||
|
internal set => _identifier.Set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If true the asset is completely loaded. If false it is only partially loaded (if at all).
|
||||||
|
public bool Complete { get; internal set; }
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
public AssetHandle Handle => _handle;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
AssetHandle handle = Content.LoadAsset(identifier);
|
||||||
|
|
||||||
|
if (handle == .Invalid)
|
||||||
|
{
|
||||||
|
value.Assign<Asset>(null);
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
Asset asset = Content.GetAsset<Asset>(handle);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
using Bon;
|
||||||
|
using Bon.Integrated;
|
||||||
|
using System;
|
||||||
|
using xxHash;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using GlitchyEngine.Core;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Content;
|
||||||
|
|
||||||
|
struct AssetHandle : IHashable
|
||||||
|
{
|
||||||
|
private UUID _uuid;
|
||||||
|
|
||||||
|
/// Defines an asset that is invalid.
|
||||||
|
public const AssetHandle Invalid = .(UUID(0));
|
||||||
|
|
||||||
|
public bool IsValid => this != .Invalid;
|
||||||
|
public bool IsInvalid => this == .Invalid;
|
||||||
|
|
||||||
|
/// Create a new random AssetHandle
|
||||||
|
public this()
|
||||||
|
{
|
||||||
|
_uuid = UUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
private this(UUID uuid)
|
||||||
|
{
|
||||||
|
_uuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static this
|
||||||
|
{
|
||||||
|
gBonEnv.typeHandlers.Add(typeof(AssetHandle),
|
||||||
|
((.)new => AssetSerialize, new => AssetDeserialize));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Inline]
|
||||||
|
public T Get<T>(IContentManager contentManager = null) where T : Asset
|
||||||
|
{
|
||||||
|
return Content.GetAsset<T>(this, contentManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode() => _uuid.GetHashCode();
|
||||||
|
|
||||||
|
static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Assert(value.type == typeof(AssetHandle));
|
||||||
|
|
||||||
|
AssetHandle handle = value.Get<AssetHandle>();
|
||||||
|
|
||||||
|
if (handle.IsInvalid)
|
||||||
|
writer.String("");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let identifier = handle.Get<Asset>().Identifier;
|
||||||
|
writer.String(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result<void> AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment, DeserializeValueState state)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Assert(value.type == typeof(AssetHandle));
|
||||||
|
|
||||||
|
String identifier = scope .();
|
||||||
|
|
||||||
|
Deserialize.String!(reader, ref identifier, environment);
|
||||||
|
|
||||||
|
AssetHandle handle = Content.LoadAsset(identifier);
|
||||||
|
|
||||||
|
value.Assign<AssetHandle>(handle);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AssetHandle<T> where T : Asset
|
||||||
|
{
|
||||||
|
private AssetHandle _handle;
|
||||||
|
private IContentManager _contentManager;
|
||||||
|
/*
|
||||||
|
* We don't increment/decrement the reference counter since we guarantee that we query for the asset every frame.
|
||||||
|
*/
|
||||||
|
private T _asset;
|
||||||
|
//private uint8 _currentFrame;
|
||||||
|
//private uint64 _actualCurrentFrame = 0;
|
||||||
|
|
||||||
|
public const Self Invalid = .();
|
||||||
|
|
||||||
|
public bool IsValid => this != .Invalid;
|
||||||
|
public bool IsInvalid => this == .Invalid;
|
||||||
|
|
||||||
|
public this(AssetHandle handle, IContentManager contentManager = null)
|
||||||
|
{
|
||||||
|
_handle = handle;
|
||||||
|
|
||||||
|
_asset = handle.Get<T>(contentManager);
|
||||||
|
_contentManager = _asset?.ContentManager;
|
||||||
|
if (_contentManager == null)
|
||||||
|
_contentManager = contentManager;
|
||||||
|
//_currentFrame = (uint8)Application.Get().GameTime.FrameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new invalid asset handle
|
||||||
|
private this()
|
||||||
|
{
|
||||||
|
_handle = .Invalid;
|
||||||
|
//_currentFrame = 0;
|
||||||
|
_contentManager = null;
|
||||||
|
_asset = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static this
|
||||||
|
{
|
||||||
|
gBonEnv.typeHandlers.Add(typeof(AssetHandle<T>),
|
||||||
|
((.)new => AssetSerialize, new => AssetDeserialize));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Self(AssetHandle handle)
|
||||||
|
{
|
||||||
|
return Self(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator AssetHandle(Self handle)
|
||||||
|
{
|
||||||
|
return handle._handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator T(ref Self handle)
|
||||||
|
{
|
||||||
|
return handle.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Get(IContentManager contentManager = null)
|
||||||
|
{
|
||||||
|
return Content.GetAsset<T>(_handle, contentManager == null ? _contentManager : contentManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Comptime, OnCompile(.TypeInit)]
|
||||||
|
static void Init()
|
||||||
|
{
|
||||||
|
for (var field in typeof(T).GetFields(BindingFlags.FlattenHierarchy))
|
||||||
|
{
|
||||||
|
if (field.IsStatic || !field.IsPublic)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String modifier = field.IsPublic ? "public" : "private";
|
||||||
|
|
||||||
|
String code = scope $"""
|
||||||
|
{modifier} {field.FieldType} {field.Name}
|
||||||
|
{{
|
||||||
|
get mut
|
||||||
|
{{
|
||||||
|
return Get().{field.Name};
|
||||||
|
}}
|
||||||
|
set mut
|
||||||
|
{{
|
||||||
|
Get().{field.Name} = value;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
""";
|
||||||
|
|
||||||
|
Compiler.EmitTypeBody(typeof(Self), code);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<StringView, (MethodInfo? Getter, MethodInfo? Setter)> properties = scope .();
|
||||||
|
|
||||||
|
for (var method in typeof(T).GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Public))
|
||||||
|
{
|
||||||
|
if (method.IsStatic || !method.IsPublic || method.IsConstructor || method.IsDestructor)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Filter out destructors
|
||||||
|
if (method.Name == "~this")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (method.Name.StartsWith("get__"))
|
||||||
|
{
|
||||||
|
StringView name = method.Name;
|
||||||
|
name.RemoveFromStart(5);
|
||||||
|
|
||||||
|
if (!properties.TryGetValue(name, var propertyInfo))
|
||||||
|
{
|
||||||
|
propertyInfo = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyInfo.Getter = method;
|
||||||
|
|
||||||
|
properties[name] = propertyInfo;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (method.Name.StartsWith("set__"))
|
||||||
|
{
|
||||||
|
StringView name = method.Name;
|
||||||
|
name.RemoveFromStart(5);
|
||||||
|
|
||||||
|
if (!properties.TryGetValue(name, var propertyInfo))
|
||||||
|
{
|
||||||
|
propertyInfo = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyInfo.Setter = method;
|
||||||
|
|
||||||
|
properties[name] = propertyInfo;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String modifier = method.IsPublic ? "public" : "private";
|
||||||
|
|
||||||
|
String parameters = scope String();
|
||||||
|
String arguments = scope String();
|
||||||
|
|
||||||
|
for (int param < method.ParamCount)
|
||||||
|
{
|
||||||
|
Type paramType = method.GetParamType(param);
|
||||||
|
StringView paramName = method.GetParamName(param);
|
||||||
|
/*String buffer = scope .();
|
||||||
|
method.GetParamsDecl(buffer);*/
|
||||||
|
|
||||||
|
if (param != 0)
|
||||||
|
{
|
||||||
|
parameters.Append(", ");
|
||||||
|
arguments.Append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Default value
|
||||||
|
parameters.AppendF($"{paramType} {paramName}");
|
||||||
|
|
||||||
|
/*if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
parameters.AppendF($"/*{buffer}*/");
|
||||||
|
}*/
|
||||||
|
|
||||||
|
arguments.AppendF($" {paramName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
String code = scope $"""
|
||||||
|
{modifier} {method.ReturnType} {method.Name}({parameters}) mut
|
||||||
|
{{
|
||||||
|
return Get().{method.Name}({arguments});
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
""";
|
||||||
|
|
||||||
|
Compiler.EmitTypeBody(typeof(Self), code);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var (propertyName, property) in properties)
|
||||||
|
{
|
||||||
|
Type propertyType = property.Getter?.ReturnType ?? property.Setter?.GetParamType(0);
|
||||||
|
|
||||||
|
String getter = scope .();
|
||||||
|
String setter = scope .();
|
||||||
|
|
||||||
|
if (property.Getter != null)
|
||||||
|
{
|
||||||
|
getter.AppendF($"""
|
||||||
|
get mut
|
||||||
|
{{
|
||||||
|
return Get().{propertyName};
|
||||||
|
}}
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
if (property.Setter != null)
|
||||||
|
{
|
||||||
|
getter.AppendF($"""
|
||||||
|
|
||||||
|
set mut
|
||||||
|
{{
|
||||||
|
Get().{propertyName} = value;
|
||||||
|
}}
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
String code = scope $"""
|
||||||
|
public {propertyType} {propertyName}
|
||||||
|
{{
|
||||||
|
{getter}{setter}
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
""";
|
||||||
|
|
||||||
|
Compiler.EmitTypeBody(typeof(Self), code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetHandle<NewT> Cast<NewT>()
|
||||||
|
where NewT : Asset
|
||||||
|
where T : NewT
|
||||||
|
{
|
||||||
|
return AssetHandle<NewT>(this._handle, this._contentManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cast up?
|
||||||
|
public AssetHandle<NewT> Cast<NewT>() where NewT : T
|
||||||
|
{
|
||||||
|
return AssetHandle<NewT>(this._handle, this._contentManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Assert(value.type == typeof(AssetHandle<T>));
|
||||||
|
|
||||||
|
AssetHandle handle = value.Get<AssetHandle<T>>();
|
||||||
|
|
||||||
|
if (handle.IsInvalid)
|
||||||
|
writer.String("");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let identifier = handle.Get<Asset>().Identifier;
|
||||||
|
writer.String(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result<void> AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment, DeserializeValueState state)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Assert(value.type == typeof(AssetHandle<T>));
|
||||||
|
|
||||||
|
String identifier = scope .();
|
||||||
|
|
||||||
|
Deserialize.String!(reader, ref identifier, environment);
|
||||||
|
|
||||||
|
AssetHandle<T> handle = Content.LoadAsset(identifier);
|
||||||
|
|
||||||
|
value.Assign<AssetHandle<T>>(handle);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using xxHash;
|
using xxHash;
|
||||||
|
using System.Collections;
|
||||||
|
using Bon;
|
||||||
|
|
||||||
namespace GlitchyEngine.Content
|
namespace GlitchyEngine.Content
|
||||||
{
|
{
|
||||||
@@ -23,16 +25,167 @@ namespace GlitchyEngine.Content
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IContentManager
|
[BonTarget, BonPolyRegister]
|
||||||
|
abstract class AssetLoaderConfig
|
||||||
{
|
{
|
||||||
void GetFilePath(String outFilename, String filename);
|
[BonIgnore]
|
||||||
|
protected bool _changed;
|
||||||
|
|
||||||
Stream GetFile(String filename);
|
public bool Changed => _changed;
|
||||||
|
|
||||||
|
protected bool SetIfChanged<T>(ref T field, T value)
|
||||||
|
{
|
||||||
|
if (field == value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
field = value;
|
||||||
|
_changed = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContentManager : IContentManager
|
interface IAssetLoader
|
||||||
{
|
{
|
||||||
private String _contentRoot;
|
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);
|
||||||
|
|
||||||
|
/// Returns the placeholder asset.
|
||||||
|
Asset GetPlaceholderAsset(Type assetType);
|
||||||
|
|
||||||
|
/// Returns the error asset.
|
||||||
|
Asset GetErrorAsset(Type assetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Content
|
||||||
|
{
|
||||||
|
/// Loads the specified asset with the given contentManager or the current applications content manager.
|
||||||
|
public static AssetHandle LoadAsset(StringView assetIdentifier, IContentManager contentManager = null, bool blocking = false)
|
||||||
|
{
|
||||||
|
var contentManager;
|
||||||
|
|
||||||
|
if (contentManager == null)
|
||||||
|
contentManager = Application.Get().ContentManager;
|
||||||
|
|
||||||
|
AssetHandle handle = contentManager.LoadAsset(assetIdentifier, blocking);
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads the specified asset with the given contentManager or the current applications content manager.
|
||||||
|
public static T GetAsset<T>(AssetHandle handle, IContentManager contentManager = null) where T : Asset
|
||||||
|
{
|
||||||
|
var contentManager;
|
||||||
|
|
||||||
|
if (contentManager == null)
|
||||||
|
contentManager = Application.Get().ContentManager;
|
||||||
|
|
||||||
|
Asset asset = contentManager.GetAsset(typeof(T), handle);
|
||||||
|
|
||||||
|
return (T)asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AssetHandle ManageAsset(Asset asset, IContentManager contentManager = null)
|
||||||
|
{
|
||||||
|
var contentManager;
|
||||||
|
|
||||||
|
if (contentManager == null)
|
||||||
|
contentManager = Application.Get().ContentManager;
|
||||||
|
|
||||||
|
return contentManager.ManageAsset(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public static AssetHandle<T> ManageAsset<T>(T asset, IContentManager contentManager = null) where T : Asset
|
||||||
|
{
|
||||||
|
var contentManager;
|
||||||
|
|
||||||
|
if (contentManager == null)
|
||||||
|
contentManager = Application.Get().ContentManager;
|
||||||
|
|
||||||
|
contentManager.ManageAsset(asset);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IContentManager
|
||||||
|
{
|
||||||
|
/// Loads the Asset with the given handle and returns the handle.
|
||||||
|
AssetHandle LoadAsset(StringView assetIdentifier, bool blocking = false);
|
||||||
|
|
||||||
|
/// Returns the asset for the given handle or null, if it isn't loaded.
|
||||||
|
Asset GetAsset(AssetHandle handle)
|
||||||
|
{
|
||||||
|
return GetAsset(null, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the asset for the given handle or the default asset of the given type.
|
||||||
|
Asset GetAsset(Type assetType, AssetHandle handle);
|
||||||
|
|
||||||
|
/// The content manager will manage the asset (e.g. provide it when LoadAsset is called with the assets identifier)
|
||||||
|
AssetHandle ManageAsset(Asset asset);
|
||||||
|
|
||||||
|
/// The content manager will no longer manage the asset.
|
||||||
|
void UnmanageAsset(AssetHandle asset);
|
||||||
|
|
||||||
|
/// 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 AssetHandle LoadAsset(StringView assetIdentifier, bool blocking = false)
|
||||||
|
{
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset GetAsset(Type assetType, AssetHandle handle)
|
||||||
|
{
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetHandle ManageAsset(Asset asset)
|
||||||
|
{
|
||||||
|
Runtime.NotImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnmanageAsset(AssetHandle asset)
|
||||||
|
{
|
||||||
|
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]
|
[AllowAppend]
|
||||||
public this(String contentRoot)
|
public this(String contentRoot)
|
||||||
@@ -53,6 +206,8 @@ namespace GlitchyEngine.Content
|
|||||||
String fullpath = scope .(_contentRoot.Length + 1 + filename.Length);
|
String fullpath = scope .(_contentRoot.Length + 1 + filename.Length);
|
||||||
GetFilePath(fullpath, filename);
|
GetFilePath(fullpath, filename);
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(File.Exists(fullpath), "File doesn't exist!");
|
||||||
|
|
||||||
FileStream stream = new FileStream();
|
FileStream stream = new FileStream();
|
||||||
var result = stream.Open(fullpath, .Read, .Read);
|
var result = stream.Open(fullpath, .Read, .Read);
|
||||||
|
|
||||||
@@ -63,6 +218,6 @@ namespace GlitchyEngine.Content
|
|||||||
}
|
}
|
||||||
|
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ using GlitchyEngine.Math;
|
|||||||
using GlitchyEngine.Renderer;
|
using GlitchyEngine.Renderer;
|
||||||
using GlitchyEngine.Renderer.Animation;
|
using GlitchyEngine.Renderer.Animation;
|
||||||
using GlitchyEngine.World;
|
using GlitchyEngine.World;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace GlitchyEngine.Content
|
namespace GlitchyEngine.Content
|
||||||
{
|
{
|
||||||
@@ -12,148 +13,223 @@ namespace GlitchyEngine.Content
|
|||||||
{
|
{
|
||||||
static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1);
|
static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1);
|
||||||
|
|
||||||
public static void LoadModel(String filename, Effect validationEffect, Material material, EcsWorld world,
|
public static Result<void> GetMeshNames(String filename, List<String> meshNames)
|
||||||
List<AnimationClip> outClips)
|
|
||||||
{
|
{
|
||||||
CGLTF.Options options = .();
|
CGLTF.Options options = .();
|
||||||
CGLTF.Data* data;
|
CGLTF.Data* data;
|
||||||
CGLTF.Result result = CGLTF.ParseFile(options, filename, out data);
|
CGLTF.Result result = CGLTF.ParseFile(options, filename, out data);
|
||||||
|
|
||||||
Log.EngineLogger.Assert(result == .Success, "Failed to load model.");
|
if (!(result case .Success))
|
||||||
|
return .Err;
|
||||||
|
|
||||||
result = CGLTF.LoadBuffers(options, data, filename);
|
for (var mesh in data.Meshes)
|
||||||
|
|
||||||
Log.EngineLogger.Assert(result == .Success, "Failed to load buffers");
|
|
||||||
|
|
||||||
for(var node in data.Scenes[0].Nodes)
|
|
||||||
{
|
{
|
||||||
NodesToEntities(data, node, null, world, validationEffect, material, outClips);
|
meshNames.Add(new String(mesh.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
CGLTF.Free(data);
|
CGLTF.Free(data);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void NodesToEntities(CGLTF.Data* data, CGLTF.Node* node, EcsEntity? parentEntity, EcsWorld world, Effect validationEffect, Material material, List<AnimationClip> clips)
|
public static GeometryBinding LoadMesh(StringView fileName, StringView meshName, int primitiveIndex)
|
||||||
{
|
{
|
||||||
EcsEntity entity = world.NewEntity();
|
// TODO: add a context to remember which buffers were loaded before so that we don't load the same data multiple times.
|
||||||
|
|
||||||
#if DEBUG
|
char8* scopedFileName = fileName.ToScopeCStr!();
|
||||||
var nameComponent = world.AssignComponent<DebugNameComponent>(entity);
|
|
||||||
|
|
||||||
if (node.Name != null)
|
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)
|
||||||
{
|
{
|
||||||
nameComponent.SetName(StringView(node.Name));
|
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
|
else
|
||||||
{
|
{
|
||||||
nameComponent.SetName("Unnamed Node");
|
Runtime.NotImplemented();
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(parentEntity.HasValue)
|
// 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)
|
||||||
{
|
{
|
||||||
var childParent = world.AssignComponent<ParentComponent>(entity);
|
return res;
|
||||||
childParent.Entity = parentEntity.Value;
|
}*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var childTransform = world.AssignComponent<TransformComponent>(entity);
|
/*
|
||||||
|
|
||||||
if(node.HasMatrix)
|
for (cgltf_size i = 0; i < data->buffers_count; ++i)
|
||||||
{
|
{
|
||||||
childTransform.LocalTransform = *(Matrix*)&node.Matrix;
|
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
|
else
|
||||||
{
|
{
|
||||||
if(node.HasTranslation)
|
return cgltf_result_unknown_format;
|
||||||
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 == null)
|
|
||||||
childTransform.Scale *= .(1, 1, -1);
|
|
||||||
|
|
||||||
Skeleton skeleton = null;
|
|
||||||
|
|
||||||
if(node.Skin != null)
|
|
||||||
{
|
|
||||||
skeleton = ExtractSkeleton(node.Skin);
|
|
||||||
|
|
||||||
LoadAnimationClips(data, node.Skin, skeleton, clips);
|
|
||||||
}
|
}
|
||||||
|
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(node.Mesh != null)
|
if (res != cgltf_result_success)
|
||||||
{
|
{
|
||||||
// If we have only one primitive, add it directly to the entity
|
return res;
|
||||||
if(node.Mesh.Primitives.Length == 1)
|
|
||||||
{
|
|
||||||
var mesh = world.AssignComponent<MeshComponent>(entity);
|
|
||||||
|
|
||||||
using (var geo = PrimitiveToGeoBinding(node.Mesh.Primitives[0], validationEffect))
|
|
||||||
{
|
|
||||||
mesh.Mesh = geo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(skeleton == null)
|
|
||||||
{
|
|
||||||
var meshRenderer = world.AssignComponent<MeshRendererComponent>(entity);
|
|
||||||
meshRenderer.Material = material;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var meshRenderer = world.AssignComponent<SkinnedMeshRendererComponent>(entity);
|
return cgltf_result_unknown_format;
|
||||||
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);
|
return .Success;
|
||||||
meshParent.Entity = entity;
|
|
||||||
|
|
||||||
var mesh = world.AssignComponent<MeshComponent>(meshEntity);
|
|
||||||
mesh.Mesh = PrimitiveToGeoBinding(primitive, validationEffect);
|
|
||||||
|
|
||||||
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();
|
public static GeometryBinding PrimitiveToGeoBinding(CGLTF.Primitive primitive)
|
||||||
|
|
||||||
for(var child in node.Children)
|
|
||||||
{
|
|
||||||
NodesToEntities(data, child, entity, world, validationEffect, material, clips);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GeometryBinding PrimitiveToGeoBinding(CGLTF.Primitive primitive, Effect validationEffect)
|
|
||||||
{
|
{
|
||||||
GeometryBinding binding = new GeometryBinding();
|
GeometryBinding binding = new GeometryBinding();
|
||||||
|
|
||||||
@@ -265,7 +341,7 @@ namespace GlitchyEngine.Content
|
|||||||
StringView strView = .(attribute.Name);
|
StringView strView = .(attribute.Name);
|
||||||
|
|
||||||
// Remove number from end of name
|
// Remove number from end of name
|
||||||
while((*(strView.EndPtr - 1)).IsDigit)
|
while((*(strView.EndPtr - 1)).IsDigit || (*(strView.EndPtr - 1)) == '_')
|
||||||
{
|
{
|
||||||
strView.Length--;
|
strView.Length--;
|
||||||
}
|
}
|
||||||
@@ -318,7 +394,7 @@ namespace GlitchyEngine.Content
|
|||||||
vertexElements[i] = elements[i];
|
vertexElements[i] = elements[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexLayout layout = new VertexLayout(vertexElements, true, validationEffect.VertexShader);
|
VertexLayout layout = new VertexLayout(vertexElements, true);
|
||||||
binding.SetVertexLayout(layout..ReleaseRefNoDelete());
|
binding.SetVertexLayout(layout..ReleaseRefNoDelete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Core;
|
||||||
|
|
||||||
|
// TODO: make usable
|
||||||
|
class FilePath : IHashable
|
||||||
|
{
|
||||||
|
append String _path = .();
|
||||||
|
|
||||||
|
public bool IsRooted => Path.IsPathRooted(_path);
|
||||||
|
|
||||||
|
public this()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(StringView path)
|
||||||
|
{
|
||||||
|
Set(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator StringView(FilePath filePath) => filePath._path;
|
||||||
|
|
||||||
|
/// @param fixDirectorySeperators If true all alternative directory seperators will be replaced by the primary seperator.
|
||||||
|
/// @param resolveRelativeDirectories If true relative directories ('.' and '..') will be removed from the path.
|
||||||
|
public enum CanonicalizationFlags
|
||||||
|
{
|
||||||
|
FixDirectorySeperators = 1,
|
||||||
|
ResolveRelativeDirectories = _ << 1,
|
||||||
|
MakeFullPath = _ << 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(StringView path, CanonicalizationFlags canonicalizationFlags = .FixDirectorySeperators | .ResolveRelativeDirectories)
|
||||||
|
{
|
||||||
|
_path.Append(path);
|
||||||
|
Canonicalize(canonicalizationFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the path to a canonicalized path.
|
||||||
|
public void Canonicalize(CanonicalizationFlags canonicalizationFlags = .FixDirectorySeperators | .ResolveRelativeDirectories)
|
||||||
|
{
|
||||||
|
if (canonicalizationFlags.HasFlag(.FixDirectorySeperators))
|
||||||
|
{
|
||||||
|
FixDirectorySeperators();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canonicalizationFlags.HasFlag(.ResolveRelativeDirectories))
|
||||||
|
{
|
||||||
|
ResolveRelativeDirectories();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canonicalizationFlags.HasFlag(.MakeFullPath))
|
||||||
|
{
|
||||||
|
MakeFullPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MakeFullPath()
|
||||||
|
{
|
||||||
|
if (IsRooted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String buffer = scope String(Path.[Friend]MaxPath);
|
||||||
|
|
||||||
|
Path.GetFullPath(_path, buffer);
|
||||||
|
|
||||||
|
_path..Clear().Append(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FixDirectorySeperators()
|
||||||
|
{
|
||||||
|
_path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResolveRelativeDirectories()
|
||||||
|
{
|
||||||
|
// find .
|
||||||
|
// find entire entry name
|
||||||
|
// remove entry name (if its only .)
|
||||||
|
|
||||||
|
/*for (char32 c in _path.DecodedChars)
|
||||||
|
{
|
||||||
|
if (c == '.')
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (StringView component in _path.Split(Path.DirectorySeparatorChar))
|
||||||
|
{
|
||||||
|
if (component == ".")
|
||||||
|
{
|
||||||
|
// . can be removed without replacement
|
||||||
|
}
|
||||||
|
else if (component == "..")
|
||||||
|
{
|
||||||
|
// .. can only be removed when not at the start of after another ..
|
||||||
|
|
||||||
|
// e.g. "../foo" and "../../foo" can't be changed
|
||||||
|
// but "foo/.." can become "foo"
|
||||||
|
// "foo/../.." can become ".."
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Append(StringView newPath)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode() => _path.GetHashCode();
|
||||||
|
}
|
||||||
@@ -7,8 +7,12 @@ namespace GlitchyEngine.Core
|
|||||||
* Implements the IDisposable interface so that it can be used with a using-Block so that the counter
|
* Implements the IDisposable interface so that it can be used with a using-Block so that the counter
|
||||||
* will be decremented automatically after leaving the block.
|
* will be decremented automatically after leaving the block.
|
||||||
*/
|
*/
|
||||||
public class RefCounter : System.RefCounted, IDisposable
|
public class RefCounter : RefCounted, IDisposable
|
||||||
{
|
{
|
||||||
|
protected ~this()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ReleaseRef();
|
ReleaseRef();
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using Bon;
|
||||||
|
using System;
|
||||||
|
using Bon.Integrated;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Core
|
||||||
|
{
|
||||||
|
[BonTarget]
|
||||||
|
struct UUID : IHashable
|
||||||
|
{
|
||||||
|
[BonInclude]
|
||||||
|
private uint64 _uuid;
|
||||||
|
|
||||||
|
private static Random s_Random = new .() ~ delete _;
|
||||||
|
|
||||||
|
static this()
|
||||||
|
{
|
||||||
|
gBonEnv.typeHandlers.Add(typeof(UUID),
|
||||||
|
((.)new => Serialize, (.)new => Deserialize));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new random UUID.
|
||||||
|
public this()
|
||||||
|
{
|
||||||
|
_uuid = s_Random.NextU64();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new UUID with the given value.
|
||||||
|
public this(uint64 uuid)
|
||||||
|
{
|
||||||
|
_uuid = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode()
|
||||||
|
{
|
||||||
|
return (int)_uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Serialize(BonWriter writer, ValueView val, BonEnvironment env, SerializeValueState state)
|
||||||
|
{
|
||||||
|
UUID uuid = *(UUID*)val.dataPtr;
|
||||||
|
|
||||||
|
Bon.Integrated.Serialize.[Friend]Integer(typeof(uint64), writer, ValueView(typeof(uint64), &uuid._uuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<void> Deserialize(BonReader reader, ValueView val, BonEnvironment env, DeserializeValueState state)
|
||||||
|
{
|
||||||
|
Bon.Integrated.Deserialize.[Friend]Integer!(typeof(uint64), reader, val);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||