From b0f6ce518ec2db33110b6215cbdcac6c56e2b856 Mon Sep 17 00:00:00 2001
From: Juna <46632782+JunaMeinhold@users.noreply.github.com>
Date: Wed, 8 May 2024 15:43:10 +0200
Subject: [PATCH] Added local post processing volumes with transitions, added
parallel scripts and order of execution.
---
HexaEngine.Core/Graphics/Texture2D.cs | 2 +-
.../IO/Binary/Terrains/TerrainLayerGroup.cs | 2 +-
.../Editors/VolumeObjectEditor.cs | 36 +-
.../Widgets/InputManagerWindow.cs | 9 +-
.../Widgets/ScriptBehaviorWidget.cs | 4 -
HexaEngine.Mathematics/MathUtil.cs | 233 +++++++++++
HexaEngine.Tests/MathematicsTests.cs | 97 +++++
HexaEngine/AppConfig.cs | 13 +
HexaEngine/Collections/FlaggedList.cs | 41 +-
HexaEngine/Collections/TopologicalSorter.cs | 53 +++
HexaEngine/Editor/ProxyBase.cs | 6 +-
HexaEngine/Input/Input.cs | 2 +-
HexaEngine/Input/InputMap.cs | 5 +
HexaEngine/Input/InputSystem.cs | 12 +-
HexaEngine/PostFx/CopyPass.cs | 5 +
HexaEngine/PostFx/IPostFx.cs | 2 +
HexaEngine/PostFx/PostFxBase.cs | 12 +
HexaEngine/PostFx/PostFxComposeTarget.cs | 5 +
HexaEngine/PostFx/PostProcessingManager.cs | 54 +--
HexaEngine/Queries/Generic/ObjectTypeQuery.cs | 2 +-
HexaEngine/Resources/TerrainMaterial.cs | 2 +-
HexaEngine/Scenes/GameObject.cs | 1 -
HexaEngine/Scenes/IComponent.cs | 3 +
HexaEngine/Scripts/GlobalScriptManager.cs | 44 +++
HexaEngine/Scripts/ScriptComponent.cs | 5 +
HexaEngine/Scripts/ScriptDependencyBuilder.cs | 48 ++-
.../Scripts/ScriptExecutionOrderComparer.cs | 12 +
HexaEngine/Scripts/ScriptGraph.cs | 65 ++-
HexaEngine/Scripts/ScriptGroup.cs | 135 +++++++
HexaEngine/Scripts/ScriptManager.cs | 217 ++++++----
HexaEngine/Scripts/ScriptNode.cs | 25 +-
HexaEngine/Scripts/ScriptPriorityAttribute.cs | 13 -
HexaEngine/Volumes/IVolume.cs | 24 ++
HexaEngine/Volumes/PostFxProxy.cs | 98 +++++
HexaEngine/Volumes/PostFxSettingsContainer.cs | 8 +
HexaEngine/Volumes/Volume.cs | 37 +-
HexaEngine/Volumes/VolumeMode.cs | 28 +-
HexaEngine/Volumes/VolumeSystem.cs | 370 +++++++++++++++++-
.../shared/shaders/deferred/brdf/light.hlsl | 30 +-
.../shaders/forward/sky/preethamSky.hlsl | 2 +-
40 files changed, 1542 insertions(+), 220 deletions(-)
create mode 100644 HexaEngine/Scripts/GlobalScriptManager.cs
create mode 100644 HexaEngine/Scripts/ScriptExecutionOrderComparer.cs
create mode 100644 HexaEngine/Scripts/ScriptGroup.cs
delete mode 100644 HexaEngine/Scripts/ScriptPriorityAttribute.cs
create mode 100644 HexaEngine/Volumes/IVolume.cs
diff --git a/HexaEngine.Core/Graphics/Texture2D.cs b/HexaEngine.Core/Graphics/Texture2D.cs
index ff71ab80..15ebdd8a 100644
--- a/HexaEngine.Core/Graphics/Texture2D.cs
+++ b/HexaEngine.Core/Graphics/Texture2D.cs
@@ -414,7 +414,7 @@ public static Texture2D LoadFromAssets(AssetRef assetRef, TextureDimension force
///
/// The used to create the texture.
/// A task that represents the asynchronous operation and contains the created .
- [Obsolete("")]
+ [Obsolete("For legacy code, don't use.")]
public static Task CreateTextureAsync(TextureFileDescription description)
{
return Task.Factory.StartNew((object state) =>
diff --git a/HexaEngine.Core/IO/Binary/Terrains/TerrainLayerGroup.cs b/HexaEngine.Core/IO/Binary/Terrains/TerrainLayerGroup.cs
index 5f5fb6a0..efc0c642 100644
--- a/HexaEngine.Core/IO/Binary/Terrains/TerrainLayerGroup.cs
+++ b/HexaEngine.Core/IO/Binary/Terrains/TerrainLayerGroup.cs
@@ -125,7 +125,7 @@ public void RemoveAt(int index)
return;
}
- Array.Copy(layers, index + 1, layers, index, count - index);
+ Array.Copy(layers, index + 1, layers, index, count - index - 1);
count--;
return;
}
diff --git a/HexaEngine.Editor/Editors/VolumeObjectEditor.cs b/HexaEngine.Editor/Editors/VolumeObjectEditor.cs
index a13d2f98..fbb8d947 100644
--- a/HexaEngine.Editor/Editors/VolumeObjectEditor.cs
+++ b/HexaEngine.Editor/Editors/VolumeObjectEditor.cs
@@ -7,6 +7,7 @@
using HexaEngine.PostFx;
using HexaEngine.Volumes;
using System;
+ using System.Numerics;
using System.Reflection;
public class VolumeObjectEditor : IObjectEditor
@@ -70,13 +71,44 @@ public unsafe bool Draw(IGraphicsContext context)
switch (shape)
{
case VolumeShape.Box:
-
+ Vector3 min = volume.BoundingBox.Min;
+ Vector3 max = volume.BoundingBox.Max;
+ if (ImGui.InputFloat3("Min", ref min))
+ {
+ volume.BoundingBox = new(min, max);
+ }
+ if (ImGui.InputFloat3("Max", ref max))
+ {
+ volume.BoundingBox = new(min, max);
+ }
break;
case VolumeShape.Sphere:
+ Vector3 center = volume.BoundingSphere.Center;
+ float radius = volume.BoundingSphere.Radius;
+ if (ImGui.InputFloat3("Center", ref center))
+ {
+ volume.BoundingSphere = new(center, radius);
+ }
+ if (ImGui.InputFloat("Radius", ref radius))
+ {
+ volume.BoundingSphere = new(center, radius);
+ }
break;
}
+ VolumeTransitionMode transitionMode = volume.TransitionMode;
+ if (ComboEnumHelper.Combo("Transition Mode", ref transitionMode))
+ {
+ volume.TransitionMode = transitionMode;
+ }
+
+ int transitionDuration = volume.TransitionDuration;
+ if (ImGui.InputInt("Transition Duration", ref transitionDuration))
+ {
+ volume.TransitionDuration = transitionDuration;
+ }
+
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
@@ -179,7 +211,7 @@ private static void DrawEnable(int id, IPostFx effect, PostFxProxy proxy, bool i
if ((effect.Flags & (PostFxFlags.AlwaysEnabled | PostFxFlags.Optional)) == 0)
{
- bool enabled = effect.Enabled;
+ bool enabled = proxy.Enabled;
if (ImGui.Checkbox($"{effect.DisplayName.Id}Enable", ref enabled))
{
effect.Enabled = enabled;
diff --git a/HexaEngine.Editor/Widgets/InputManagerWindow.cs b/HexaEngine.Editor/Widgets/InputManagerWindow.cs
index e73003f9..faaf52cf 100644
--- a/HexaEngine.Editor/Widgets/InputManagerWindow.cs
+++ b/HexaEngine.Editor/Widgets/InputManagerWindow.cs
@@ -98,12 +98,12 @@ private void OnKeyDown(object? sender, Core.Input.Events.KeyboardEventArgs e)
private void Load()
{
- if (Platform.AppConfig == null || !Platform.AppConfig.Variables.TryGetValue("InputMap", out var xml))
+ if (Platform.AppConfig == null)
{
return;
}
- inputMap = InputMap.LoadFromText(xml);
+ inputMap = Platform.AppConfig.InputMap;
}
private void Save()
@@ -113,7 +113,6 @@ private void Save()
return;
}
- Platform.AppConfig.Variables["InputMap"] = inputMap.SaveAsText();
Platform.AppConfig.Save();
}
@@ -185,10 +184,6 @@ public override unsafe void DrawContent(IGraphicsContext context)
return;
}
- {
- // AxisContextMenu(axisName, currentAxis);
- }
-
ImGui.SeparatorText("Bindings");
ImGui.BeginTable("##Table", 2, ImGuiTableFlags.SizingFixedFit);
diff --git a/HexaEngine.Editor/Widgets/ScriptBehaviorWidget.cs b/HexaEngine.Editor/Widgets/ScriptBehaviorWidget.cs
index 8aa49532..d4bfed5b 100644
--- a/HexaEngine.Editor/Widgets/ScriptBehaviorWidget.cs
+++ b/HexaEngine.Editor/Widgets/ScriptBehaviorWidget.cs
@@ -2,10 +2,6 @@
{
using HexaEngine.Core.Graphics;
using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
public class ScriptBehaviorWidget : EditorWindow
{
diff --git a/HexaEngine.Mathematics/MathUtil.cs b/HexaEngine.Mathematics/MathUtil.cs
index 8e664e08..98e157d9 100644
--- a/HexaEngine.Mathematics/MathUtil.cs
+++ b/HexaEngine.Mathematics/MathUtil.cs
@@ -1241,6 +1241,19 @@ public static float Lerp(float x, float y, float s)
return x * (1 - s) + y * s;
}
+ ///
+ /// Performs linear interpolation between two values.
+ ///
+ /// The first value.
+ /// The second value.
+ /// The interpolation factor (0 to 1).
+ /// The interpolated value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double Lerp(double x, double y, double s)
+ {
+ return x * (1 - s) + y * s;
+ }
+
///
/// Linearly interpolates between two 2D vectors.
///
@@ -1859,6 +1872,26 @@ public static float Clamp01(float value)
return value;
}
+ ///
+ /// Clamps a value to the range [0, 1].
+ ///
+ /// The input value to be clamped.
+ /// The clamped value within the range [0, 1].
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double Clamp01(double value)
+ {
+ if (value < 0)
+ {
+ return 0;
+ }
+ else if (value > 1)
+ {
+ return 1;
+ }
+
+ return value;
+ }
+
///
/// Clamps each component of a to a specified range.
///
@@ -1869,6 +1902,18 @@ public static float Clamp01(float value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 Clamp(Vector2 v, Vector2 min, Vector2 max)
{
+ if (Sse.IsSupported)
+ {
+ Vector128 vec = v.AsVector128();
+ Vector128 vecMin = min.AsVector128();
+ Vector128 vecMax = max.AsVector128();
+
+ vec = Sse.Max(vec, vecMin);
+ vec = Sse.Min(vec, vecMax);
+
+ return vec.AsVector2();
+ }
+
return Vector2.Clamp(v, min, max);
}
@@ -1882,6 +1927,18 @@ public static Vector2 Clamp(Vector2 v, Vector2 min, Vector2 max)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 Clamp(Vector3 v, Vector3 min, Vector3 max)
{
+ if (Sse.IsSupported)
+ {
+ Vector128 vec = v.AsVector128();
+ Vector128 vecMin = min.AsVector128();
+ Vector128 vecMax = max.AsVector128();
+
+ vec = Sse.Max(vec, vecMin);
+ vec = Sse.Min(vec, vecMax);
+
+ return vec.AsVector3();
+ }
+
return Vector3.Clamp(v, min, max);
}
@@ -1895,6 +1952,18 @@ public static Vector3 Clamp(Vector3 v, Vector3 min, Vector3 max)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Clamp(Vector4 v, Vector4 min, Vector4 max)
{
+ if (Sse.IsSupported)
+ {
+ Vector128 vec = v.AsVector128();
+ Vector128 vecMin = min.AsVector128();
+ Vector128 vecMax = max.AsVector128();
+
+ vec = Sse.Max(vec, vecMin);
+ vec = Sse.Min(vec, vecMax);
+
+ return vec.AsVector4();
+ }
+
return Vector4.Clamp(v, min, max);
}
@@ -1906,6 +1975,16 @@ public static Vector4 Clamp(Vector4 v, Vector4 min, Vector4 max)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 Clamp01(Vector2 v)
{
+ if (Sse.IsSupported)
+ {
+ Vector128 vec = v.AsVector128();
+
+ vec = Sse.MaxScalar(vec, Vector128.Zero);
+ vec = Sse.MinScalar(vec, Vector128.One);
+
+ return vec.AsVector2();
+ }
+
return Vector2.Clamp(v, Vector2.Zero, Vector2.One);
}
@@ -1917,6 +1996,16 @@ public static Vector2 Clamp01(Vector2 v)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 Clamp01(Vector3 v)
{
+ if (Sse.IsSupported)
+ {
+ Vector128 vec = v.AsVector128();
+
+ vec = Sse.MaxScalar(vec, Vector128.Zero);
+ vec = Sse.MinScalar(vec, Vector128.One);
+
+ return vec.AsVector3();
+ }
+
return Vector3.Clamp(v, Vector3.Zero, Vector3.One);
}
@@ -1928,6 +2017,16 @@ public static Vector3 Clamp01(Vector3 v)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Clamp01(Vector4 v)
{
+ if (Sse.IsSupported)
+ {
+ Vector128 vec = v.AsVector128();
+
+ vec = Sse.MaxScalar(vec, Vector128.Zero);
+ vec = Sse.MinScalar(vec, Vector128.One);
+
+ return vec.AsVector4();
+ }
+
return Vector4.Clamp(v, Vector4.Zero, Vector4.One);
}
@@ -2964,5 +3063,139 @@ public static Vector4 Remap(Vector4 value, Vector4 low1, Vector4 high1, Vector4
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
}
+
+ ///
+ /// Performs smooth Hermite interpolation between two values.
+ ///
+ ///
+ /// This method interpolates smoothly between edge0 and edge1, based on the input parameter s.
+ /// The return value is clamped between 0 and 1. Edge0 and edge1 values are expected to be between 0 and 1.
+ ///
+ /// The lower edge.
+ /// The upper edge.
+ /// The interpolation value.
+ /// A smooth Hermite interpolation between edge0 and edge1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float SmoothStep(float edge0, float edge1, float s)
+ {
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+ return edge0 + (edge1 - edge0) * s;
+ }
+
+ ///
+ /// Performs smooth Hermite interpolation between two values.
+ ///
+ ///
+ /// This method interpolates smoothly between edge0 and edge1, based on the input parameter s.
+ /// The return value is clamped between 0 and 1. Edge0 and edge1 values are expected to be between 0 and 1.
+ ///
+ /// The lower edge.
+ /// The upper edge.
+ /// The interpolation value.
+ /// A smooth Hermite interpolation between edge0 and edge1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double SmoothStep(double edge0, double edge1, double s)
+ {
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+ return edge0 + (edge1 - edge0) * s;
+ }
+
+ ///
+ /// Performs smooth Hermite interpolation between two values.
+ ///
+ ///
+ /// This method interpolates smoothly between edge0 and edge1, based on the input parameter s.
+ /// The return value is clamped between 0 and 1. Edge0 and edge1 values are expected to be between 0 and 1.
+ ///
+ /// The lower edge.
+ /// The upper edge.
+ /// The interpolation value.
+ /// A smooth Hermite interpolation between edge0 and edge1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector2 SmoothStep(Vector2 edge0, Vector2 edge1, float s)
+ {
+ if (Sse.IsSupported)
+ {
+ Vector128 vecEdge0 = edge0.AsVector128();
+ Vector128 vecEdge1 = edge1.AsVector128();
+
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+
+ Vector128 vecResult = Sse.Add(vecEdge0, Sse.Multiply(Sse.Subtract(vecEdge1, vecEdge0), Vector128.Create(s)));
+
+ return vecResult.AsVector2();
+ }
+
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+ return edge0 + (edge1 - edge0) * s;
+ }
+
+ ///
+ /// Performs smooth Hermite interpolation between two values.
+ ///
+ ///
+ /// This method interpolates smoothly between edge0 and edge1, based on the input parameter s.
+ /// The return value is clamped between 0 and 1. Edge0 and edge1 values are expected to be between 0 and 1.
+ ///
+ /// The lower edge.
+ /// The upper edge.
+ /// The interpolation value.
+ /// A smooth Hermite interpolation between edge0 and edge1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector3 SmoothStep(Vector3 edge0, Vector3 edge1, float s)
+ {
+ if (Sse.IsSupported)
+ {
+ Vector128 vecEdge0 = edge0.AsVector128();
+ Vector128 vecEdge1 = edge1.AsVector128();
+
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+
+ Vector128 vecResult = Sse.Add(vecEdge0, Sse.Multiply(Sse.Subtract(vecEdge1, vecEdge0), Vector128.Create(s)));
+
+ return vecResult.AsVector3();
+ }
+
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+ return edge0 + (edge1 - edge0) * s;
+ }
+
+ ///
+ /// Performs smooth Hermite interpolation between two values.
+ ///
+ ///
+ /// This method interpolates smoothly between edge0 and edge1, based on the input parameter s.
+ /// The return value is clamped between 0 and 1. Edge0 and edge1 values are expected to be between 0 and 1.
+ ///
+ /// The lower edge.
+ /// The upper edge.
+ /// The interpolation value.
+ /// A smooth Hermite interpolation between edge0 and edge1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector4 SmoothStep(Vector4 edge0, Vector4 edge1, float s)
+ {
+ if (Sse.IsSupported)
+ {
+ Vector128 vecEdge0 = edge0.AsVector128();
+ Vector128 vecEdge1 = edge1.AsVector128();
+
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+
+ Vector128 vecResult = Sse.Add(vecEdge0, Sse.Multiply(Sse.Subtract(vecEdge1, vecEdge0), Vector128.Create(s)));
+
+ return vecResult.AsVector4();
+ }
+
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+ return edge0 + (edge1 - edge0) * s;
+ }
}
}
\ No newline at end of file
diff --git a/HexaEngine.Tests/MathematicsTests.cs b/HexaEngine.Tests/MathematicsTests.cs
index 3b9f81c3..4a2761c5 100644
--- a/HexaEngine.Tests/MathematicsTests.cs
+++ b/HexaEngine.Tests/MathematicsTests.cs
@@ -251,5 +251,102 @@ public void DotVec2D()
Assert.That(dot0, Is.EqualTo(dot1));
}
+
+ [TestCase(0, ExpectedResult = 0f)]
+ [TestCase(0.5f, ExpectedResult = 0.5f)]
+ [TestCase(1, ExpectedResult = 1f)]
+ [Test]
+ public float Smoothstep(float s)
+ {
+ float edge0 = 0;
+ float edge1 = 1;
+
+ float result = MathUtil.SmoothStep(edge0, edge1, s);
+
+ return result;
+ }
+
+ [TestCase(0, ExpectedResult = -2f)]
+ [TestCase(0.5f, ExpectedResult = 0f)]
+ [TestCase(1, ExpectedResult = 2f)]
+ [Test]
+ public float Smoothstep2(float s)
+ {
+ float edge0 = -2;
+ float edge1 = 2;
+
+ float result = MathUtil.SmoothStep(edge0, edge1, s);
+
+ return result;
+ }
+
+ [Test]
+ public void SmoothstepVec2()
+ {
+ Vector2 edge0 = new(0);
+ Vector2 edge1 = new(1);
+ float s = 0.5f;
+
+ Vector2 resultSSE = MathUtil.SmoothStep(edge0, edge1, s);
+ Vector2 resultNonSSE = SmoothStepNonSSE(edge0, edge1, s);
+
+ Assert.That(resultSSE, Is.EqualTo(resultNonSSE));
+ }
+
+ [Test]
+ public void SmoothstepVec3()
+ {
+ Vector3 edge0 = new(0);
+ Vector3 edge1 = new(1);
+ float s = 0.5f;
+
+ Vector3 resultSSE = MathUtil.SmoothStep(edge0, edge1, s);
+ Vector3 resultNonSSE = SmoothStepNonSSE(edge0, edge1, s);
+
+ Assert.That(resultSSE, Is.EqualTo(resultNonSSE));
+ }
+
+ [Test]
+ public void SmoothstepVec4()
+ {
+ Vector4 edge0 = new(0);
+ Vector4 edge1 = new(1);
+ float s = 0.5f;
+
+ Vector4 resultSSE = MathUtil.SmoothStep(edge0, edge1, s);
+ Vector4 resultNonSSE = SmoothStepNonSSE(edge0, edge1, s);
+
+ Assert.That(resultSSE, Is.EqualTo(resultNonSSE));
+ }
+
+ private static float Clamp01(float s)
+ {
+ if (s < 0.0f)
+ return 0.0f;
+ if (s > 1.0f)
+ return 1.0f;
+ return s;
+ }
+
+ private static Vector2 SmoothStepNonSSE(Vector2 edge0, Vector2 edge1, float s)
+ {
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+ return edge0 + (edge1 - edge0) * s;
+ }
+
+ private static Vector3 SmoothStepNonSSE(Vector3 edge0, Vector3 edge1, float s)
+ {
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+ return edge0 + (edge1 - edge0) * s;
+ }
+
+ private static Vector4 SmoothStepNonSSE(Vector4 edge0, Vector4 edge1, float s)
+ {
+ s = Clamp01(s);
+ s = s * s * (3f - 2f * s);
+ return edge0 + (edge1 - edge0) * s;
+ }
}
}
\ No newline at end of file
diff --git a/HexaEngine/AppConfig.cs b/HexaEngine/AppConfig.cs
index 3d971e80..55a1b649 100644
--- a/HexaEngine/AppConfig.cs
+++ b/HexaEngine/AppConfig.cs
@@ -1,5 +1,6 @@
namespace HexaEngine
{
+ using HexaEngine.Input;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
@@ -22,6 +23,8 @@ public AppConfig(string path)
public Dictionary Variables { get; set; } = [];
+ public InputMap InputMap { get; set; } = new();
+
public XmlSchema? GetSchema()
{
return null;
@@ -46,6 +49,14 @@ public void ReadXml(XmlReader reader)
if (reader.MoveToContent() == XmlNodeType.Element)
{
string key = reader.Name;
+
+ if (key == "InputMap" && string.IsNullOrEmpty(reader.Value))
+ {
+ InputMap.ReadXml(reader);
+
+ continue;
+ }
+
string value = reader.ReadElementContentAsString();
Variables.Add(key, value);
reader.ReadEndElement();
@@ -71,6 +82,8 @@ public void WriteXml(XmlWriter writer)
writer.WriteAttributeString("ScriptAssembly", ScriptAssembly);
}
+ InputMap.WriteXml(writer);
+
foreach (var kvp in Variables)
{
writer.WriteStartElement(kvp.Key);
diff --git a/HexaEngine/Collections/FlaggedList.cs b/HexaEngine/Collections/FlaggedList.cs
index c740a699..beae8d28 100644
--- a/HexaEngine/Collections/FlaggedList.cs
+++ b/HexaEngine/Collections/FlaggedList.cs
@@ -2,10 +2,13 @@
{
using HexaEngine.Core.Extensions;
using HexaEngine.Scenes;
+ using Silk.NET.Vulkan;
using System;
using System.Collections;
using System.Collections.Generic;
+ using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
+ using System.Reflection;
public interface IHasFlags where T : Enum
{
@@ -17,7 +20,7 @@ public interface INotifyFlagsChanged : IHasFlags where T : Enum
public event Action, T>? FlagsChanged;
}
- public class FlaggedList : IList, IDictionary> where TFlags : unmanaged, Enum where TItem : IHasFlags
+ public class FlaggedList : IList, IDictionary>, IReadOnlyDictionary> where TFlags : unmanaged, Enum where TItem : IHasFlags
{
private readonly Dictionary> lists = new();
private readonly List values = new();
@@ -43,6 +46,10 @@ public FlaggedList()
public ICollection> Values => lists.Values;
+ IEnumerable IReadOnlyDictionary>.Keys => lists.Keys;
+
+ IEnumerable> IReadOnlyDictionary>.Values => lists.Values;
+
public IList this[TFlags index]
{
get { return lists[index]; }
@@ -260,5 +267,37 @@ public bool Remove(KeyValuePair> item)
{
return ((IDictionary>)lists).GetEnumerator();
}
+
+ public void Sort(IComparer? comparer)
+ {
+ for (int i = 0; i < flags.Length; i++)
+ {
+ ((List)lists[flags[i]]).Sort(comparer);
+ }
+ }
+
+ public void Sort(Comparison comparison)
+ {
+ for (int i = 0; i < flags.Length; i++)
+ {
+ ((List)lists[flags[i]]).Sort(comparison);
+ }
+ }
+
+ public void Sort(int index, int count, IComparer? comparer)
+ {
+ for (int i = 0; i < flags.Length; i++)
+ {
+ ((List)lists[flags[i]]).Sort(index, count, comparer);
+ }
+ }
+
+ public void Sort()
+ {
+ for (int i = 0; i < flags.Length; i++)
+ {
+ ((List)lists[flags[i]]).Sort();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/HexaEngine/Collections/TopologicalSorter.cs b/HexaEngine/Collections/TopologicalSorter.cs
index 660a9b3b..2d04a73c 100644
--- a/HexaEngine/Collections/TopologicalSorter.cs
+++ b/HexaEngine/Collections/TopologicalSorter.cs
@@ -35,6 +35,28 @@ public List TopologicalSort(List nodes)
return sortedList;
}
+ public List TopologicalSort(List nodes, List sortedList)
+ {
+ visitedNodes.Clear();
+ recursionStack.Clear();
+ cycleStack.Clear();
+
+ for (int i = 0; i < nodes.Count; i++)
+ {
+ T node = nodes[i];
+ if (!visitedNodes.Contains(node))
+ {
+ if (!TopologicalSortRecursive(node, nodes, sortedList))
+ {
+ // A cycle is detected
+ throw new Exception("The graph contains a cycle: " + GetCyclePath());
+ }
+ }
+ }
+
+ return sortedList;
+ }
+
private bool TopologicalSortRecursive(T node, List nodes)
{
visitedNodes.Add(node);
@@ -66,6 +88,37 @@ private bool TopologicalSortRecursive(T node, List nodes)
return true;
}
+ private bool TopologicalSortRecursive(T node, List nodes, List sortedList)
+ {
+ visitedNodes.Add(node);
+ recursionStack.Add(node);
+ cycleStack.Push(node);
+
+ for (int i = 0; i < node.Dependencies.Count; i++)
+ {
+ T dependency = (T)node.Dependencies[i];
+ if (!visitedNodes.Contains(dependency))
+ {
+ if (!TopologicalSortRecursive(dependency, nodes))
+ {
+ return false;
+ }
+ }
+ else if (recursionStack.Contains(dependency))
+ {
+ cycleStack.Push(dependency);
+ // A cycle is detected
+ return false;
+ }
+ }
+
+ sortedList.Add(node);
+ recursionStack.Remove(node);
+ cycleStack.Pop();
+
+ return true;
+ }
+
private string GetCyclePath()
{
if (cycleStack.Count == 0)
diff --git a/HexaEngine/Editor/ProxyBase.cs b/HexaEngine/Editor/ProxyBase.cs
index 8a17e49d..e861a40a 100644
--- a/HexaEngine/Editor/ProxyBase.cs
+++ b/HexaEngine/Editor/ProxyBase.cs
@@ -5,11 +5,11 @@
public class ProxyBase : IProxy
{
- private readonly List properties = [];
- private readonly Dictionary propertyData = [];
+ protected readonly List properties = [];
+ protected readonly Dictionary propertyData = [];
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
- private Type? targetType;
+ protected Type? targetType;
public ProxyBase(object target)
{
diff --git a/HexaEngine/Input/Input.cs b/HexaEngine/Input/Input.cs
index a7e1b4be..168d1520 100644
--- a/HexaEngine/Input/Input.cs
+++ b/HexaEngine/Input/Input.cs
@@ -4,7 +4,7 @@
public static class Input
{
- public static IInputManager Current { get; set; } = new InputManager();
+ public static IInputManager Current { get; internal set; }
public static float GetAxis(string name)
{
diff --git a/HexaEngine/Input/InputMap.cs b/HexaEngine/Input/InputMap.cs
index ade78965..d798e28b 100644
--- a/HexaEngine/Input/InputMap.cs
+++ b/HexaEngine/Input/InputMap.cs
@@ -45,6 +45,11 @@ public void ReadXml(XmlReader reader)
if (reader.NodeType == XmlNodeType.Element && reader.Name == "VirtualAxes")
{
+ if (reader.IsEmptyElement)
+ {
+ continue;
+ }
+
reader.ReadStartElement("VirtualAxes");
while (reader.Read())
diff --git a/HexaEngine/Input/InputSystem.cs b/HexaEngine/Input/InputSystem.cs
index 808a85f6..dd69395c 100644
--- a/HexaEngine/Input/InputSystem.cs
+++ b/HexaEngine/Input/InputSystem.cs
@@ -5,12 +5,15 @@
public class InputSystem : ISceneSystem
{
+ private InputManager inputManager;
+
public string Name { get; } = "Input System";
public SystemFlags Flags { get; } = SystemFlags.Awake | SystemFlags.EarlyUpdate | SystemFlags.Destroy;
public void Awake(Scene scene)
{
+ Input.Current = inputManager = new();
if (!scene.Variables.TryGetValue("InputMapAsset", out var guidString) || !Guid.TryParse(guidString, out var assetGuid))
{
LoadFromAppConfig();
@@ -28,12 +31,12 @@ public void Awake(Scene scene)
private static void LoadFromAppConfig()
{
- if (Platform.AppConfig == null || !Platform.AppConfig.Variables.TryGetValue("InputMap", out var xml))
+ if (Platform.AppConfig == null)
{
return;
}
- InputMap? map = InputMap.LoadFromText(xml);
+ InputMap map = Platform.AppConfig.InputMap;
if (map != null)
{
@@ -43,11 +46,14 @@ private static void LoadFromAppConfig()
public void Update(float delta)
{
- Input.Current.Update();
+ inputManager.Update();
}
public void Destroy()
{
+ var tmp = Input.Current;
+ Input.Current = null;
+ tmp.Dispose();
}
}
}
\ No newline at end of file
diff --git a/HexaEngine/PostFx/CopyPass.cs b/HexaEngine/PostFx/CopyPass.cs
index 6aaf7df7..aa7c464c 100644
--- a/HexaEngine/PostFx/CopyPass.cs
+++ b/HexaEngine/PostFx/CopyPass.cs
@@ -75,5 +75,10 @@ public void Dispose()
{
// nothing to dispose.
}
+
+ public void ResetSettings()
+ {
+ // nothing to do.
+ }
}
}
\ No newline at end of file
diff --git a/HexaEngine/PostFx/IPostFx.cs b/HexaEngine/PostFx/IPostFx.cs
index 7e25a3b8..f6509e85 100644
--- a/HexaEngine/PostFx/IPostFx.cs
+++ b/HexaEngine/PostFx/IPostFx.cs
@@ -117,5 +117,7 @@ void PrePassDraw(IGraphicsContext context, GraphResourceBuilder creator)
///
/// The graphics context.
public void Update(IGraphicsContext context);
+
+ public void ResetSettings();
}
}
\ No newline at end of file
diff --git a/HexaEngine/PostFx/PostFxBase.cs b/HexaEngine/PostFx/PostFxBase.cs
index 16e8b5d7..88a64784 100644
--- a/HexaEngine/PostFx/PostFxBase.cs
+++ b/HexaEngine/PostFx/PostFxBase.cs
@@ -2,6 +2,7 @@
{
using HexaEngine.Core.Graphics;
using HexaEngine.Core.UI;
+ using HexaEngine.Editor;
using HexaEngine.Editor.Attributes;
using HexaEngine.Graphics.Graph;
using HexaEngine.Mathematics;
@@ -18,6 +19,7 @@ public abstract class PostFxBase : IPostFx
{
private bool initialized = false;
private bool enabled = false;
+ private readonly ProxyBase proxy;
///
/// Indicates whether the post-processing effect is dirty and needs an update.
@@ -51,6 +53,11 @@ public abstract class PostFxBase : IPostFx
private ImGuiName? displayName;
+ public PostFxBase()
+ {
+ proxy = new(this);
+ }
+
///
public abstract string Name { get; }
@@ -262,5 +269,10 @@ private string GetDebuggerDisplay()
{
return $"{Name}, {(Initialized ? "I" : "")}{(Enabled ? "E" : "")}{(dirty ? "D" : "")}, {Flags}";
}
+
+ public virtual void ResetSettings()
+ {
+ proxy.Apply(this);
+ }
}
}
\ No newline at end of file
diff --git a/HexaEngine/PostFx/PostFxComposeTarget.cs b/HexaEngine/PostFx/PostFxComposeTarget.cs
index b3e6af4a..22a20caf 100644
--- a/HexaEngine/PostFx/PostFxComposeTarget.cs
+++ b/HexaEngine/PostFx/PostFxComposeTarget.cs
@@ -85,5 +85,10 @@ public void Update(IGraphicsContext context)
public void Dispose()
{
}
+
+ public void ResetSettings()
+ {
+ // nothing to do.
+ }
}
}
\ No newline at end of file
diff --git a/HexaEngine/PostFx/PostProcessingManager.cs b/HexaEngine/PostFx/PostProcessingManager.cs
index 2f27cf5d..41b4b917 100644
--- a/HexaEngine/PostFx/PostProcessingManager.cs
+++ b/HexaEngine/PostFx/PostProcessingManager.cs
@@ -321,6 +321,17 @@ public void Remove(IPostFx effect)
}
}
+ public void ResetSettings()
+ {
+ using (SupressReload())
+ {
+ for (int i = 0; i < effects.Count; i++)
+ {
+ effects[i].ResetSettings();
+ }
+ }
+ }
+
public void Invalidate()
{
for (int i = 0; i < groups.Count; i++)
@@ -568,36 +579,33 @@ public void Draw(IGraphicsContext context, GraphResourceBuilder creator, ICPUPro
return;
}
- lock (_lock)
- {
- postContext.Clear(context);
+ postContext.Clear(context);
- if (isDirty)
+ if (isDirty)
+ {
+ postContext.Reset();
+ for (int i = 0; i < groups.Count; i++)
{
- postContext.Reset();
- for (int i = 0; i < groups.Count; i++)
- {
- groups[i].SetupInputOutputs(postContext);
- }
- isDirty = false;
+ groups[i].SetupInputOutputs(postContext);
}
+ isDirty = false;
+ }
- for (int i = 0; i < activeEffects.Count; i++)
+ for (int i = 0; i < activeEffects.Count; i++)
+ {
+ var effect = activeEffects[i];
+ if (!effect.Enabled || (effect.Flags & PostFxFlags.PrePass) != 0 || (effect.Flags & PostFxFlags.ComposeTarget) != 0)
{
- var effect = activeEffects[i];
- if (!effect.Enabled || (effect.Flags & PostFxFlags.PrePass) != 0 || (effect.Flags & PostFxFlags.ComposeTarget) != 0)
- {
- continue;
- }
- profiler?.Begin(effect.Name);
- effect.Update(context);
- profiler?.End(effect.Name);
+ continue;
}
+ profiler?.Begin(effect.Name);
+ effect.Update(context);
+ profiler?.End(effect.Name);
+ }
- for (int i = 0; i < groups.Count; i++)
- {
- groups[i].Execute(context, deferredContext, creator);
- }
+ for (int i = 0; i < groups.Count; i++)
+ {
+ groups[i].Execute(context, deferredContext, creator);
}
}
diff --git a/HexaEngine/Queries/Generic/ObjectTypeQuery.cs b/HexaEngine/Queries/Generic/ObjectTypeQuery.cs
index 3a24f105..57e8faf9 100644
--- a/HexaEngine/Queries/Generic/ObjectTypeQuery.cs
+++ b/HexaEngine/Queries/Generic/ObjectTypeQuery.cs
@@ -8,7 +8,7 @@ public class ObjectTypeQuery : IQuery, IReadOnlyList where T : GameObject
private readonly List cache = new();
private bool disposedValue;
- public ObjectTypeQuery(QueryFlags flags = QueryFlags.Default)
+ public ObjectTypeQuery(QueryFlags flags = QueryFlags.ObjectAdded | QueryFlags.ObjectRemoved)
{
Flags = flags;
}
diff --git a/HexaEngine/Resources/TerrainMaterial.cs b/HexaEngine/Resources/TerrainMaterial.cs
index 74e8b621..55479d49 100644
--- a/HexaEngine/Resources/TerrainMaterial.cs
+++ b/HexaEngine/Resources/TerrainMaterial.cs
@@ -184,7 +184,7 @@ private static MaterialShaderPassDesc[] GetMaterialShaderPasses(bool alphaBlend,
BlendDescription blend = BlendDescription.Opaque;
if (blendFunc)
{
- blend = BlendDescription.AlphaBlend;
+ blend = BlendDescription.Additive;
}
GraphicsPipelineDesc pipelineDescForward = new()
diff --git a/HexaEngine/Scenes/GameObject.cs b/HexaEngine/Scenes/GameObject.cs
index b7a837f6..5506106e 100644
--- a/HexaEngine/Scenes/GameObject.cs
+++ b/HexaEngine/Scenes/GameObject.cs
@@ -30,7 +30,6 @@ public partial class GameObject : EntityNotifyBase, IHierarchyObject, IEditorSel
private string name = string.Empty;
private string? fullName;
- private GCHandle gcHandle;
private object? tag;
private readonly EventHandlers enabledChangedList = new();
diff --git a/HexaEngine/Scenes/IComponent.cs b/HexaEngine/Scenes/IComponent.cs
index 779f4b76..1496ccd1 100644
--- a/HexaEngine/Scenes/IComponent.cs
+++ b/HexaEngine/Scenes/IComponent.cs
@@ -28,6 +28,9 @@ public interface IAudioComponent : IComponent
public interface IScriptComponent : IComponent, INotifyFlagsChanged
{
+ int ExecutionOrderIndex { get; set; }
+ Type? ScriptType { get; }
+
void ScriptCreate();
void ScriptLoad();
diff --git a/HexaEngine/Scripts/GlobalScriptManager.cs b/HexaEngine/Scripts/GlobalScriptManager.cs
new file mode 100644
index 00000000..89edfb64
--- /dev/null
+++ b/HexaEngine/Scripts/GlobalScriptManager.cs
@@ -0,0 +1,44 @@
+namespace HexaEngine.Scripts
+{
+ using HexaEngine.Core;
+ using HexaEngine.Scenes;
+ using System.Reflection;
+
+ public class GlobalScriptManager
+ {
+ private List scripts = new();
+
+ static GlobalScriptManager()
+ {
+ ScriptAssemblyManager.AssembliesUnloaded += AssembliesUnloaded;
+ ScriptAssemblyManager.AssemblyLoaded += AssemblyLoaded;
+ Application.OnEditorPlayStateTransition += EditorPlayStateTransition;
+ Application.OnEditorPlayStateChanged += EditorPlayStateChanged;
+ SceneManager.SceneChanged += SceneChanged;
+ }
+
+ private static void SceneChanged(object? sender, SceneChangedEventArgs e)
+ {
+ }
+
+ private static void EditorPlayStateTransition(EditorPlayStateTransitionEventArgs args)
+ {
+ }
+
+ private static void EditorPlayStateChanged(EditorPlayState newState)
+ {
+ }
+
+ private static void AssemblyLoaded(object? sender, Assembly e)
+ {
+ }
+
+ private static void AssembliesUnloaded(object? sender, EventArgs? e)
+ {
+ }
+
+ public void Load()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/HexaEngine/Scripts/ScriptComponent.cs b/HexaEngine/Scripts/ScriptComponent.cs
index f45e8f4b..af54b38d 100644
--- a/HexaEngine/Scripts/ScriptComponent.cs
+++ b/HexaEngine/Scripts/ScriptComponent.cs
@@ -50,6 +50,8 @@ public Guid Guid
set => guid = value;
}
+ public bool Enabled { get; set; }
+
[JsonIgnore]
public bool IsSerializable { get; } = true;
@@ -161,6 +163,9 @@ public GameObject GameObject
[JsonIgnore]
public Type? ScriptType => scriptType;
+ [JsonIgnore]
+ public int ExecutionOrderIndex { get; set; }
+
public event Action, ScriptFlags>? FlagsChanged;
public void ScriptCreate()
diff --git a/HexaEngine/Scripts/ScriptDependencyBuilder.cs b/HexaEngine/Scripts/ScriptDependencyBuilder.cs
index 45d8032f..09e40a0f 100644
--- a/HexaEngine/Scripts/ScriptDependencyBuilder.cs
+++ b/HexaEngine/Scripts/ScriptDependencyBuilder.cs
@@ -1,11 +1,8 @@
namespace HexaEngine.Scripts
{
- using HexaEngine.PostFx;
using System;
using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
+ using System.Reflection;
public class ScriptDependencyBuilder
{
@@ -25,8 +22,49 @@ public void Clear()
Dependencies.Clear();
}
- public void Build(IReadOnlyList others, ScriptFlags stage)
+ private static ScriptNode? ResolveNode(Type scriptType, IReadOnlyList others)
{
+ for (int i = 0; i < others.Count; i++)
+ {
+ var other = others[i];
+ if (other.ScriptType == scriptType)
+ {
+ return other;
+ }
+ }
+ return null;
+ }
+
+ public void Build(IReadOnlyList others)
+ {
+ foreach (var after in node.ScriptType.GetCustomAttributes())
+ {
+ var runAfter = after.Type;
+ var afterNode = ResolveNode(runAfter, others);
+ if (afterNode is null)
+ {
+ continue;
+ }
+ if (!node.Dependencies.Contains(afterNode))
+ {
+ node.Dependencies.Add(afterNode);
+ afterNode.Dependants.Add(node);
+ }
+ }
+
+ foreach (var before in node.ScriptType.GetCustomAttributes())
+ {
+ var beforeNode = ResolveNode(before.Type, others);
+ if (beforeNode is null)
+ {
+ continue;
+ }
+ if (!beforeNode.Dependencies.Contains(node))
+ {
+ beforeNode.Dependencies.Add(node);
+ node.Dependants.Add(beforeNode);
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/HexaEngine/Scripts/ScriptExecutionOrderComparer.cs b/HexaEngine/Scripts/ScriptExecutionOrderComparer.cs
new file mode 100644
index 00000000..0cb9195b
--- /dev/null
+++ b/HexaEngine/Scripts/ScriptExecutionOrderComparer.cs
@@ -0,0 +1,12 @@
+namespace HexaEngine.Scripts
+{
+ using HexaEngine.Scenes;
+
+ public struct ScriptExecutionOrderComparer : IComparer
+ {
+ public readonly int Compare(IScriptComponent? x, IScriptComponent? y)
+ {
+ return x == null || y == null ? 0 : x.ExecutionOrderIndex.CompareTo(y.ExecutionOrderIndex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HexaEngine/Scripts/ScriptGraph.cs b/HexaEngine/Scripts/ScriptGraph.cs
index e1d5262d..a7e11782 100644
--- a/HexaEngine/Scripts/ScriptGraph.cs
+++ b/HexaEngine/Scripts/ScriptGraph.cs
@@ -2,22 +2,22 @@
{
using HexaEngine.Collections;
using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
public class ScriptGraph
{
- private readonly List nodes = new();
- private readonly Dictionary scriptToNode = new();
- private readonly ScriptNode root;
+ private readonly List nodes = [];
+ private readonly List sorted = [];
+ private readonly Dictionary scriptToNode = [];
private readonly ScriptTypeRegistry scriptTypeRegistry = new();
private readonly TopologicalSorter sorter = new();
public ScriptGraph()
{
- root = new ScriptNode(typeof(ScriptRoot), ScriptFlags.Awake | ScriptFlags.Destroy | ScriptFlags.Update | ScriptFlags.FixedUpdate, scriptTypeRegistry);
- nodes.Add(root);
}
- public void AddNode(ScriptComponent script)
+ public void AddNode(Type script)
{
ScriptNode node = new(script, scriptTypeRegistry);
nodes.Add(node);
@@ -25,7 +25,7 @@ public void AddNode(ScriptComponent script)
scriptTypeRegistry.Add(script, node);
}
- public bool RemoveNode(ScriptComponent script)
+ public bool RemoveNode(Type script)
{
if (!scriptToNode.TryGetValue(script, out var node))
{
@@ -40,7 +40,6 @@ public bool RemoveNode(ScriptComponent script)
public void Clear()
{
- root.Reset();
for (int i = 0; i < nodes.Count; i++)
{
nodes[i].Reset();
@@ -49,17 +48,59 @@ public void Clear()
scriptTypeRegistry.Clear();
nodes.Clear();
scriptToNode.Clear();
- nodes.Add(root);
}
- public void Build(IList scriptsSorted, ScriptFlags stage)
+ public List> GroupNodesForParallelExecution()
+ {
+ var depthGroups = nodes.GroupBy(node => (node.Depth, node.RunInParallel)).OrderBy(group => group.Key.Depth);
+ var parallelNodeLists = depthGroups.Select(group => group.ToList()).ToList();
+ return parallelNodeLists;
+ }
+
+ private void CalculateDepths()
+ {
+ var visited = new HashSet();
+ foreach (var node in nodes)
+ {
+ CalculateDepth(node, visited);
+ }
+ }
+
+ private static void CalculateDepth(ScriptNode node, HashSet visited)
+ {
+ if (visited.Contains(node))
+ return;
+
+ visited.Add(node);
+
+ if (node.Dependencies.Count == 0)
+ {
+ node.Depth = 0;
+ }
+ else
+ {
+ foreach (var dependency in node.Dependencies)
+ {
+ CalculateDepth(dependency, visited);
+ node.Depth = Math.Max(node.Depth, dependency.Depth + 1);
+ }
+ }
+ }
+
+ public void Build()
{
for (int i = 0; i < nodes.Count; i++)
{
ScriptNode node = nodes[i];
- node.Builder.Dependencies.Add(root.ScriptType); // prevent nodes from being culled away by topological sort.
- node.Builder.Build(nodes, stage);
+
+ node.Builder.Build(nodes);
}
+
+ sorted.Clear();
+
+ sorter.TopologicalSort(nodes, sorted);
+
+ CalculateDepths();
}
}
}
\ No newline at end of file
diff --git a/HexaEngine/Scripts/ScriptGroup.cs b/HexaEngine/Scripts/ScriptGroup.cs
new file mode 100644
index 00000000..6be38e4c
--- /dev/null
+++ b/HexaEngine/Scripts/ScriptGroup.cs
@@ -0,0 +1,135 @@
+namespace HexaEngine.Scripts
+{
+ using HexaEngine.Collections;
+ using HexaEngine.Scenes;
+
+ public class ScriptGroup
+ {
+ private readonly Dictionary map = [];
+ private readonly List nodes;
+ private readonly bool parallel;
+ private readonly FlaggedList instances = [];
+ private readonly object _lock = new();
+
+ public ScriptGroup(List nodes, bool parallel)
+ {
+ this.nodes = nodes;
+ this.parallel = parallel;
+ for (int i = 0; i < nodes.Count; i++)
+ {
+ map.Add(nodes[i].ScriptType, i);
+ }
+ }
+
+ public bool AddInstance(IScriptComponent component)
+ {
+ lock (_lock)
+ {
+ if (map.TryGetValue(component.ScriptType, out int index))
+ {
+ component.ExecutionOrderIndex = index;
+ instances.Add(component);
+ instances.Sort(new ScriptExecutionOrderComparer());
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public bool RemoveInstance(IScriptComponent component)
+ {
+ lock (_lock)
+ {
+ return instances.Remove(component);
+ }
+ }
+
+ public IReadOnlyList Nodes => nodes;
+
+ public IReadOnlyDictionary> Instances => instances;
+
+ public void ExecuteAwake()
+ {
+ var scriptList = instances[ScriptFlags.Awake];
+
+ if (parallel)
+ {
+ Parallel.ForEach(scriptList, script =>
+ {
+ script.ScriptAwake();
+ });
+ return;
+ }
+
+ for (int i = 0; i < scriptList.Count; i++)
+ {
+ scriptList[i].ScriptAwake();
+ }
+ }
+
+ public void ExecuteDestroy()
+ {
+ var scriptList = instances[ScriptFlags.Destroy];
+
+ if (parallel)
+ {
+ Parallel.ForEach(scriptList, script =>
+ {
+ script.Destroy();
+ });
+ return;
+ }
+
+ for (int i = 0; i < scriptList.Count; i++)
+ {
+ scriptList[i].Destroy();
+ }
+ }
+
+ public void ExecuteUpdate()
+ {
+ var scriptList = instances[ScriptFlags.Update];
+
+ if (parallel)
+ {
+ Parallel.ForEach(scriptList, script =>
+ {
+ script.Update();
+ });
+ return;
+ }
+
+ for (int i = 0; i < scriptList.Count; i++)
+ {
+ scriptList[i].Update();
+ }
+ }
+
+ public void ExecuteFixedUpdate()
+ {
+ var scriptList = instances[ScriptFlags.FixedUpdate];
+
+ if (parallel)
+ {
+ Parallel.ForEach(scriptList, script =>
+ {
+ script.FixedUpdate();
+ });
+ return;
+ }
+
+ for (int i = 0; i < scriptList.Count; i++)
+ {
+ scriptList[i].FixedUpdate();
+ }
+ }
+
+ public void Clear()
+ {
+ instances.Clear();
+ nodes.Clear();
+ map.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/HexaEngine/Scripts/ScriptManager.cs b/HexaEngine/Scripts/ScriptManager.cs
index b6c2ed3d..10b64e14 100644
--- a/HexaEngine/Scripts/ScriptManager.cs
+++ b/HexaEngine/Scripts/ScriptManager.cs
@@ -1,96 +1,134 @@
namespace HexaEngine.Scripts
{
- using HexaEngine.Collections;
using HexaEngine.Core;
using HexaEngine.Queries.Generic;
using HexaEngine.Scenes;
- using System.Reflection;
- public class GlobalScriptManager
+ public class ScriptManager : ISceneSystem
{
- private List scripts = new();
+ private readonly ComponentTypeQuery components = new();
+ private readonly ScriptGraph graph = new();
+ private readonly List groups = [];
+ private readonly object _lock = new();
+ private bool awaked;
- static GlobalScriptManager()
- {
- ScriptAssemblyManager.AssembliesUnloaded += AssembliesUnloaded;
- ScriptAssemblyManager.AssemblyLoaded += AssemblyLoaded;
- Application.OnEditorPlayStateTransition += EditorPlayStateTransition;
- Application.OnEditorPlayStateChanged += EditorPlayStateChanged;
- SceneManager.SceneChanged += SceneChanged;
- }
+ public string Name => "Scripts";
- private static void SceneChanged(object? sender, SceneChangedEventArgs e)
- {
- }
+ public SystemFlags Flags { get; } = SystemFlags.Awake | SystemFlags.Update | SystemFlags.FixedUpdate | SystemFlags.Destroy;
- private static void EditorPlayStateTransition(EditorPlayStateTransitionEventArgs args)
- {
- }
+ public ScriptGraph Graph => graph;
- private static void EditorPlayStateChanged(EditorPlayState newState)
- {
- }
+ public IReadOnlyList Groups => groups;
- private static void AssemblyLoaded(object? sender, Assembly e)
+ private void AssemblyLoaded(object? sender, System.Reflection.Assembly e)
{
+ lock (_lock)
+ {
+ UpdateGraphInternal();
+ }
}
- private static void AssembliesUnloaded(object? sender, EventArgs? e)
+ private void AssembliesUnloaded(object? sender, EventArgs? e)
{
+ lock (_lock)
+ {
+ ClearGroups();
+ graph.Clear();
+ }
}
- public void Load()
+ public void Awake(Scene scene)
{
- }
- }
+ lock (_lock)
+ {
+ ScriptAssemblyManager.AssemblyLoaded += AssemblyLoaded;
+ ScriptAssemblyManager.AssembliesUnloaded += AssembliesUnloaded;
- public class ScriptManager : ISceneSystem
- {
- private readonly ComponentTypeQuery components = new();
- private readonly FlaggedList scripts = new();
- private bool awaked;
+ scene.QueryManager.AddQuery(components);
- public string Name => "Scripts";
+ for (int i = 0; i < components.Count; i++)
+ {
+ components[i].ScriptCreate();
+ }
- public SystemFlags Flags { get; } = SystemFlags.Awake | SystemFlags.Update | SystemFlags.FixedUpdate | SystemFlags.Destroy;
+ for (int i = 0; i < components.Count; i++)
+ {
+ components[i].ScriptLoad();
+ }
- public IReadOnlyList Scripts => (IReadOnlyList)scripts;
+ UpdateGraphInternal();
- public void Awake(Scene scene)
- {
- scene.QueryManager.AddQuery(components);
+ components.OnAdded += OnAdded;
+ components.OnRemoved += OnRemoved;
- components.OnAdded += OnAdded;
- components.OnRemoved -= OnRemoved;
+ if (Application.InEditMode)
+ {
+ return;
+ }
- for (int i = 0; i < components.Count; i++)
+ awaked = true;
+
+ for (int i = 0; i < groups.Count; i++)
+ {
+ groups[i].ExecuteAwake();
+ }
+ }
+ }
+
+ public void UpdateGraph()
+ {
+ lock (_lock)
{
- scripts.Add(components[i]);
- components[i].ScriptCreate();
+ UpdateGraphInternal();
}
+ }
- for (int i = 0; i < components.Count; i++)
+ private void UpdateGraphInternal()
+ {
+ ClearGroups();
+
+ graph.Clear();
+
+ IList types = ScriptAssemblyManager.GetAssignableTypes();
+ foreach (var item in types)
{
- components[i].ScriptLoad();
+ graph.AddNode(item);
}
- if (Application.InEditMode)
+ graph.Build();
+
+ var nodeGroups = graph.GroupNodesForParallelExecution();
+
+ for (int i = 0; i < nodeGroups.Count; i++)
{
- return;
+ var nodeGroup = nodeGroups[i];
+ bool runInParallel = nodeGroup[0].RunInParallel;
+ ScriptGroup group = new(nodeGroup, runInParallel);
+ groups.Add(group);
}
- awaked = true;
+ for (int i = 0; i < components.Count; i++)
+ {
+ AddToGroup(components[i]);
+ }
+ }
- var scriptList = scripts[ScriptFlags.Awake];
- for (int i = 0; i < scriptList.Count; i++)
+ private void ClearGroups()
+ {
+ for (int i = 0; i < groups.Count; i++)
{
- scriptList[i].ScriptAwake();
+ groups[i].Clear();
}
+
+ groups.Clear();
}
private void OnRemoved(GameObject gameObject, IScriptComponent component)
{
- scripts.Remove(component);
+ lock (_lock)
+ {
+ RemoveFromGroup(component);
+ }
if (Application.InEditMode || !awaked)
{
@@ -102,7 +140,10 @@ private void OnRemoved(GameObject gameObject, IScriptComponent component)
private void OnAdded(GameObject gameObject, IScriptComponent component)
{
- scripts.Add(component);
+ lock (_lock)
+ {
+ AddToGroup(component);
+ }
if (Application.InEditMode || !awaked)
{
@@ -118,24 +159,56 @@ private void OnAdded(GameObject gameObject, IScriptComponent component)
}
}
- public void Destroy()
+ private void AddToGroup(IScriptComponent component)
{
- components.OnAdded -= OnAdded;
- components.OnRemoved -= OnRemoved;
-
- components.Dispose();
-
- if (Application.InEditMode)
+ for (int i = 0; i < groups.Count; i++)
{
- return;
+ if (groups[i].AddInstance(component))
+ {
+ break;
+ }
}
+ }
- awaked = false;
+ private void RemoveFromGroup(IScriptComponent component)
+ {
+ for (int i = 0; i < groups.Count; i++)
+ {
+ if (groups[i].RemoveInstance(component))
+ {
+ break;
+ }
+ }
+ }
- var scriptList = scripts[ScriptFlags.Destroy];
- for (int i = 0; i < scriptList.Count; i++)
+ public void Destroy()
+ {
+ lock (_lock)
{
- scriptList[i].Destroy();
+ ScriptAssemblyManager.AssemblyLoaded -= AssemblyLoaded;
+ ScriptAssemblyManager.AssembliesUnloaded -= AssembliesUnloaded;
+
+ graph.Clear();
+
+ components.OnAdded -= OnAdded;
+ components.OnRemoved -= OnRemoved;
+
+ components.Dispose();
+
+ if (Application.InEditMode)
+ {
+ ClearGroups();
+ return;
+ }
+
+ awaked = false;
+
+ for (int i = 0; i < groups.Count; i++)
+ {
+ groups[i].ExecuteDestroy();
+ }
+
+ ClearGroups();
}
}
@@ -146,10 +219,12 @@ public void Update(float delta)
return;
}
- var scriptList = scripts[ScriptFlags.Update];
- for (int i = 0; i < scriptList.Count; i++)
+ lock (_lock)
{
- scriptList[i].Update();
+ for (int i = 0; i < groups.Count; i++)
+ {
+ groups[i].ExecuteUpdate();
+ }
}
}
@@ -160,10 +235,12 @@ public void FixedUpdate()
return;
}
- var scriptList = scripts[ScriptFlags.FixedUpdate];
- for (int i = 0; i < scriptList.Count; i++)
+ lock (_lock)
{
- scriptList[i].FixedUpdate();
+ for (int i = 0; i < groups.Count; i++)
+ {
+ groups[i].ExecuteFixedUpdate();
+ }
}
}
}
diff --git a/HexaEngine/Scripts/ScriptNode.cs b/HexaEngine/Scripts/ScriptNode.cs
index 09829c6f..188d9a81 100644
--- a/HexaEngine/Scripts/ScriptNode.cs
+++ b/HexaEngine/Scripts/ScriptNode.cs
@@ -3,36 +3,23 @@
using HexaEngine.Collections;
using System.Collections.Generic;
using System.Linq;
+ using System.Reflection;
public class ScriptNode : INode
{
private readonly List dependencies = [];
private readonly List dependants = [];
private readonly ScriptDependencyBuilder builder;
- private readonly ScriptComponent? script;
- private readonly ScriptFlags flags;
private readonly Type scriptType;
- public ScriptNode(ScriptComponent script, ScriptTypeRegistry registry)
- {
- this.script = script;
- flags = script.Flags;
-
- builder = new(this, registry);
- }
-
- public ScriptNode(Type scriptType, ScriptFlags flags, ScriptTypeRegistry registry)
+ public ScriptNode(Type scriptType, ScriptTypeRegistry registry)
{
builder = new(this, registry);
this.scriptType = scriptType;
- this.flags = flags;
+ RunInParallel = scriptType.GetCustomAttribute() != null;
}
- public ScriptComponent? Script => script;
-
- public Type ScriptType => script?.ScriptType ?? scriptType;
-
- public ScriptFlags Flags => flags;
+ public Type ScriptType => scriptType;
List INode.Dependencies => dependencies.Cast().ToList();
@@ -40,6 +27,10 @@ public ScriptNode(Type scriptType, ScriptFlags flags, ScriptTypeRegistry registr
public List Dependants => dependants;
+ public int Depth { get; internal set; }
+
+ public bool RunInParallel { get; }
+
public ScriptDependencyBuilder Builder => builder;
public void Reset()
diff --git a/HexaEngine/Scripts/ScriptPriorityAttribute.cs b/HexaEngine/Scripts/ScriptPriorityAttribute.cs
deleted file mode 100644
index e44bd25c..00000000
--- a/HexaEngine/Scripts/ScriptPriorityAttribute.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace HexaEngine.Scripts
-{
- [AttributeUsage(AttributeTargets.Class)]
- public class ScriptPriorityAttribute : Attribute
- {
- public ScriptPriorityAttribute(int index)
- {
- Index = index;
- }
-
- public int Index { get; }
- }
-}
\ No newline at end of file
diff --git a/HexaEngine/Volumes/IVolume.cs b/HexaEngine/Volumes/IVolume.cs
new file mode 100644
index 00000000..3ea1507f
--- /dev/null
+++ b/HexaEngine/Volumes/IVolume.cs
@@ -0,0 +1,24 @@
+using HexaEngine.Mathematics;
+using HexaEngine.PostFx;
+
+namespace HexaEngine.Volumes
+{
+ public interface IVolume
+ {
+ BoundingBox BoundingBox { get; set; }
+
+ BoundingSphere BoundingSphere { get; set; }
+
+ PostFxSettingsContainer Container { get; }
+
+ VolumeMode Mode { get; set; }
+
+ VolumeShape Shape { get; set; }
+
+ void Apply(PostProcessingManager manager);
+
+ void Apply(PostProcessingManager manager, IVolume baseVolume, float blend, VolumeTransitionMode mode);
+
+ void Init(PostProcessingManager manager);
+ }
+}
\ No newline at end of file
diff --git a/HexaEngine/Volumes/PostFxProxy.cs b/HexaEngine/Volumes/PostFxProxy.cs
index 8ef75912..ca4667b3 100644
--- a/HexaEngine/Volumes/PostFxProxy.cs
+++ b/HexaEngine/Volumes/PostFxProxy.cs
@@ -1,8 +1,11 @@
namespace HexaEngine.Volumes
{
using HexaEngine.Editor;
+ using HexaEngine.Mathematics;
using HexaEngine.PostFx;
using System.Collections.Generic;
+ using System.Management;
+ using System.Numerics;
///
/// Represents a proxy for an IPostFx object, allowing dynamic access to its properties.
@@ -20,5 +23,100 @@ public PostFxProxy(Dictionary data, string typeName) : base(dat
[JsonIgnore]
public bool Enabled { get => (bool)Data["Enabled"]; set => Data["Enabled"] = value; }
+
+ public void Apply(object target, PostFxProxy proxyBase, float blend, VolumeTransitionMode mode)
+ {
+ foreach (var property in properties)
+ {
+ if (propertyData.TryGetValue(property.Name, out var value) && proxyBase.propertyData.TryGetValue(property.Name, out var baseValue) && value != null)
+ {
+ if (property.CanWrite)
+ {
+ try
+ {
+ value = mode switch
+ {
+ VolumeTransitionMode.Constant => value,
+ VolumeTransitionMode.Linear => BlendValueLerp(baseValue, value, blend),
+ VolumeTransitionMode.Smoothstep => BlendValueSmoothStep(baseValue, value, blend),
+ _ => value
+ };
+ property.SetValue(target, value);
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+ }
+ }
+
+ private static object BlendValueLerp(object? baseValue, object value, float blend)
+ {
+ if (baseValue is Vector2 v20 && value is Vector2 v21)
+ {
+ return MathUtil.Lerp(v20, v21, blend);
+ }
+ else if (baseValue is Vector3 v30 && value is Vector3 v31)
+ {
+ return MathUtil.Lerp(v30, v31, blend);
+ }
+ else if (baseValue is Vector4 v40 && value is Vector4 v41)
+ {
+ return MathUtil.Lerp(v40, v41, blend);
+ }
+ else if (baseValue is float f0 && value is float f1)
+ {
+ return MathUtil.Lerp(f0, f1, blend);
+ }
+ else if (baseValue is int i0 && value is int i1)
+ {
+ return (int)MathUtil.Lerp(i0, i1, blend);
+ }
+ else if (baseValue is double d0 && value is double d1)
+ {
+ return MathUtil.Lerp(d0, d1, blend);
+ }
+ else if (baseValue is bool b0 && value is bool b1)
+ {
+ return blend >= 0.5f ? b0 : b1;
+ }
+
+ return value;
+ }
+
+ private static object BlendValueSmoothStep(object? baseValue, object value, float blend)
+ {
+ if (baseValue is Vector2 v20 && value is Vector2 v21)
+ {
+ return MathUtil.SmoothStep(v20, v21, blend);
+ }
+ else if (baseValue is Vector3 v30 && value is Vector3 v31)
+ {
+ return MathUtil.SmoothStep(v30, v31, blend);
+ }
+ else if (baseValue is Vector4 v40 && value is Vector4 v41)
+ {
+ return MathUtil.SmoothStep(v40, v41, blend);
+ }
+ else if (baseValue is float f0 && value is float f1)
+ {
+ return MathUtil.SmoothStep(f0, f1, blend);
+ }
+ else if (baseValue is int i0 && value is int i1)
+ {
+ return (int)MathUtil.SmoothStep(i0, i1, blend);
+ }
+ else if (baseValue is double d0 && value is double d1)
+ {
+ return MathUtil.SmoothStep(d0, d1, blend);
+ }
+ else if (baseValue is bool b0 && value is bool b1)
+ {
+ return blend <= 0.5f ? b0 : b1;
+ }
+
+ return value;
+ }
}
}
\ No newline at end of file
diff --git a/HexaEngine/Volumes/PostFxSettingsContainer.cs b/HexaEngine/Volumes/PostFxSettingsContainer.cs
index 6c5511bf..0df2d6f8 100644
--- a/HexaEngine/Volumes/PostFxSettingsContainer.cs
+++ b/HexaEngine/Volumes/PostFxSettingsContainer.cs
@@ -63,6 +63,14 @@ public void Apply(IReadOnlyList effects)
}
}
+ public void Apply(IReadOnlyList effects, PostFxSettingsContainer baseContainer, float blend, VolumeTransitionMode mode)
+ {
+ foreach (var effect in effects)
+ {
+ proxiesDictionary[effect].Apply(effect, baseContainer.proxiesDictionary[effect], blend, mode);
+ }
+ }
+
public void Clear()
{
proxies.Clear();
diff --git a/HexaEngine/Volumes/Volume.cs b/HexaEngine/Volumes/Volume.cs
index af1a2304..6214b033 100644
--- a/HexaEngine/Volumes/Volume.cs
+++ b/HexaEngine/Volumes/Volume.cs
@@ -9,7 +9,7 @@
/// Represents a Volume in 3D space and controls post-processing and weather effects.
///
[EditorGameObject("Volume")]
- public class Volume : GameObject
+ public class Volume : GameObject, IVolume
{
///
/// Gets or sets the Volume mode.
@@ -21,6 +21,16 @@ public class Volume : GameObject
///
public VolumeShape Shape { get; set; }
+ ///
+ /// Gets or sets the Volume transition mode.
+ ///
+ public VolumeTransitionMode TransitionMode { get; set; }
+
+ ///
+ /// The volume transition duration in milliseconds.
+ ///
+ public int TransitionDuration { get; set; } = 1000;
+
///
/// Gets or sets the bounding box of the Volume.
///
@@ -33,17 +43,24 @@ public class Volume : GameObject
public PostFxSettingsContainer Container { get; } = new();
- public override void Initialize()
+ void IVolume.Init(PostProcessingManager manager)
+ {
+ Container.Build(manager.Effects);
+ }
+
+ void IVolume.Apply(PostProcessingManager manager)
+ {
+ using (manager.SupressReload())
+ {
+ Container.Apply(manager.Effects);
+ }
+ }
+
+ void IVolume.Apply(PostProcessingManager manager, IVolume baseVolume, float blend, VolumeTransitionMode mode)
{
- base.Initialize();
- PostProcessingManager postManager = PostProcessingManager.Current ?? throw new();
- Container.Build(postManager.Effects);
- if (Mode == VolumeMode.Global)
+ using (manager.SupressReload())
{
- using (postManager.SupressReload())
- {
- Container.Apply(postManager.Effects);
- }
+ Container.Apply(manager.Effects, baseVolume.Container, blend, mode);
}
}
}
diff --git a/HexaEngine/Volumes/VolumeMode.cs b/HexaEngine/Volumes/VolumeMode.cs
index dabb2f2d..73ec2a35 100644
--- a/HexaEngine/Volumes/VolumeMode.cs
+++ b/HexaEngine/Volumes/VolumeMode.cs
@@ -14,34 +14,8 @@ public enum VolumeShape
public enum VolumeTransitionMode
{
- Hard,
+ Constant,
Linear,
Smoothstep,
- CustomCurve
- }
-
- public enum CurveMode
- {
- Curve1ControlPoint,
- Curve2ControlPoint,
- Curve3ControlPoint,
- Curve4ControlPoint,
- Curve5ControlPoint,
- Curve6ControlPoint,
- Curve7ControlPoint,
- Curve8ControlPoint,
- }
-
- public struct Curve
- {
- public CurveMode Mode;
- public float ControlPoint1;
- public float ControlPoint2;
- public float ControlPoint3;
- public float ControlPoint4;
- public float ControlPoint5;
- public float ControlPoint6;
- public float ControlPoint7;
- public float ControlPoint8;
}
}
\ No newline at end of file
diff --git a/HexaEngine/Volumes/VolumeSystem.cs b/HexaEngine/Volumes/VolumeSystem.cs
index 322f7eff..9ba67426 100644
--- a/HexaEngine/Volumes/VolumeSystem.cs
+++ b/HexaEngine/Volumes/VolumeSystem.cs
@@ -1,11 +1,379 @@
namespace HexaEngine.Volumes
{
+ using HexaEngine.Core.Debugging;
+ using HexaEngine.Mathematics;
+ using HexaEngine.PostFx;
+ using HexaEngine.Queries.Generic;
using HexaEngine.Scenes;
+ using HexaEngine.Scenes.Managers;
+ using System.Diagnostics;
+ using System.Numerics;
+
+ public struct VolumeTransition : IEquatable
+ {
+ public long Start;
+ public long Duration;
+ public IVolume? From;
+ public IVolume? To;
+ public VolumeTransitionMode Mode;
+
+ public VolumeTransition(long start, long duration, Volume? from, Volume? to, VolumeTransitionMode mode)
+ {
+ Start = start;
+ Duration = duration;
+ From = from;
+ To = to;
+ Mode = mode;
+ }
+
+ public override readonly bool Equals(object? obj)
+ {
+ return obj is VolumeTransition transition && Equals(transition);
+ }
+
+ public readonly bool Equals(VolumeTransition other)
+ {
+ return Start == other.Start &&
+ Duration.Equals(other.Duration) &&
+ EqualityComparer.Default.Equals(From, other.From) &&
+ EqualityComparer.Default.Equals(To, other.To);
+ }
+
+ public readonly float GetBlendValue(long now)
+ {
+ long elapsed = now - Start;
+ float blendValue = elapsed / (float)Duration;
+ return MathUtil.Clamp01(blendValue);
+ }
+
+ public override readonly int GetHashCode()
+ {
+ return HashCode.Combine(Start, Duration, From, To);
+ }
+
+ public static bool operator ==(VolumeTransition left, VolumeTransition right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(VolumeTransition left, VolumeTransition right)
+ {
+ return !(left == right);
+ }
+ }
+
+ public delegate void VolumeTransitionEventHandler(VolumeTransition transition);
+
+ public delegate void VolumeTransitionTickEventHandler(VolumeTransition transition, float value);
+
+ public class DelegateList where T : Delegate
+ {
+ private readonly List handlers = new();
+
+ public IReadOnlyList Handlers => handlers;
+
+ public int Count => handlers.Count;
+
+ public T this[int index]
+ {
+ get => handlers[index];
+ }
+
+ public void AddHandler(T handler)
+ {
+ lock (handlers)
+ {
+ handlers.Add(handler);
+ }
+ }
+
+ public void RemoveHandler(T handler)
+ {
+ lock (handlers)
+ {
+ handlers.Remove(handler);
+ }
+ }
+
+ public void Clear()
+ {
+ handlers.Clear();
+ }
+ }
public class VolumeSystem : ISceneSystem
{
+ private static readonly ILogger Logger = LoggerFactory.GetLogger(nameof(VolumeSystem));
+ private PostProcessingManager postManager;
+ private readonly ObjectTypeQuery volumes = new();
+ private Volume? activeVolume;
+
+ private readonly List transitions = [];
+
public string Name { get; } = "VolumeSystem";
- public SystemFlags Flags { get; }
+ public SystemFlags Flags { get; } = SystemFlags.Awake | SystemFlags.Destroy | SystemFlags.Update;
+
+ public Volume? ActiveVolume => activeVolume;
+
+ private readonly DelegateList transitionStartList = new();
+ private readonly DelegateList transitionEndList = new();
+ private readonly DelegateList transitionAbortedList = new();
+ private readonly DelegateList transitionTickList = new();
+
+ public event VolumeTransitionEventHandler TransitionStart
+ {
+ add => transitionStartList.AddHandler(value);
+ remove => transitionStartList.RemoveHandler(value);
+ }
+
+ public event VolumeTransitionEventHandler TransitionEnd
+ {
+ add => transitionEndList.AddHandler(value);
+ remove => transitionEndList.RemoveHandler(value);
+ }
+
+ public event VolumeTransitionEventHandler TransitionAborted
+ {
+ add => transitionAbortedList.AddHandler(value);
+ remove => transitionAbortedList.RemoveHandler(value);
+ }
+
+ public event VolumeTransitionTickEventHandler TransitionTick
+ {
+ add => transitionTickList.AddHandler(value);
+ remove => transitionTickList.RemoveHandler(value);
+ }
+
+ public void Awake(Scene scene)
+ {
+ scene.QueryManager.AddQuery(volumes);
+
+ volumes.OnAdded += OnAdded;
+ volumes.OnRemoved += OnRemoved;
+
+ postManager = PostProcessingManager.Current ?? throw new Exception("Cannot initialize without post fx");
+
+ //SetDefaults();
+
+ bool hasGlobal = false;
+ for (int i = 0; i < volumes.Count; i++)
+ {
+ var volume = volumes[i];
+ ((IVolume)volume).Init(postManager);
+ if (volume.Mode == VolumeMode.Global)
+ {
+ if (hasGlobal)
+ {
+ Logger.Warn("Multiple global Volumes where found, first found will be used.");
+ }
+ hasGlobal = true;
+ }
+ else
+ {
+ for (int j = i + 1; j < volumes.Count; j++)
+ {
+ bool intersects = false;
+ var other = volumes[j];
+ switch (other.Shape)
+ {
+ case VolumeShape.Box:
+ switch (volume.Shape)
+ {
+ case VolumeShape.Box:
+ intersects = other.BoundingBox.Intersects(volume.BoundingBox);
+ break;
+
+ case VolumeShape.Sphere:
+ intersects = other.BoundingBox.Intersects(volume.BoundingSphere);
+ break;
+ }
+ break;
+
+ case VolumeShape.Sphere:
+ switch (volume.Shape)
+ {
+ case VolumeShape.Box:
+ intersects = other.BoundingSphere.Intersects(volume.BoundingBox);
+ break;
+
+ case VolumeShape.Sphere:
+ intersects = other.BoundingSphere.Intersects(volume.BoundingSphere);
+ break;
+ }
+ break;
+ }
+
+ if (intersects)
+ {
+ Logger.Warn($"Volume '{volume}' and Volume '{other}' are overlapping, this can cause visual issues.");
+ }
+ }
+ }
+ }
+ }
+
+ private void OnRemoved(Volume volume)
+ {
+ }
+
+ private void OnAdded(Volume volume)
+ {
+ ((IVolume)volume).Init(postManager);
+ }
+
+ public void TransitionTo(long now, int duration, VolumeTransitionMode transitionMode, Volume? from, Volume? to)
+ {
+ int index = -1;
+ VolumeTransition existingTransition = default;
+ for (int i = 0; i < transitions.Count; i++)
+ {
+ var trans = transitions[i];
+ if (trans.From == to && trans.To == from)
+ {
+ index = i;
+ existingTransition = trans;
+ break;
+ }
+ }
+
+ if (existingTransition != default)
+ {
+ float reverseBlend = 1 - existingTransition.GetBlendValue(now);
+ long remainingTicks = (long)(existingTransition.Duration * reverseBlend);
+ now -= remainingTicks;
+ transitions.RemoveAt(index);
+ for (int i = 0; i < transitionAbortedList.Count; i++)
+ {
+ transitionAbortedList[i](existingTransition);
+ }
+ }
+
+ long durationInTicks = (long)(duration / 1000.0f * Stopwatch.Frequency);
+
+ if (transitionMode == VolumeTransitionMode.Constant)
+ {
+ durationInTicks = 0; // simply set the duration to 0 this simply disables blending.
+ }
+
+ VolumeTransition transition = new(now, durationInTicks, from, to, transitionMode);
+
+ transitions.Add(transition);
+ for (int i = 0; i < transitionStartList.Count; i++)
+ {
+ transitionStartList[i](transition);
+ }
+ }
+
+ public void Update(float delta)
+ {
+ var camera = CameraManager.Current;
+
+ if (camera == null)
+ {
+ return;
+ }
+
+ Vector3 camPos = camera.Transform.GlobalPosition;
+
+ Volume? global = null;
+ Volume? local = null;
+ for (int i = 0; i < volumes.Count; i++)
+ {
+ var volume = volumes[i];
+
+ switch (volume.Mode)
+ {
+ case VolumeMode.Global:
+ global ??= volume;
+ break;
+
+ case VolumeMode.Local:
+ break;
+ }
+
+ bool intersects = false;
+ switch (volume.Shape)
+ {
+ case VolumeShape.Box:
+ var box = BoundingBox.Transform(volume.BoundingBox, volume.Transform);
+ intersects = box.Contains(camPos) != ContainmentType.Disjoint;
+ break;
+
+ case VolumeShape.Sphere:
+ var sphere = BoundingSphere.Transform(volume.BoundingSphere, volume.Transform);
+ intersects = sphere.Contains(camPos) != ContainmentType.Disjoint;
+ break;
+ }
+
+ if (intersects)
+ {
+ local = volume;
+ break;
+ }
+ }
+
+ Volume? newActiveVolume = local ?? global;
+
+ long now = Stopwatch.GetTimestamp();
+
+ if (newActiveVolume != activeVolume)
+ {
+ TransitionTo(now, newActiveVolume?.TransitionDuration ?? 0, newActiveVolume?.TransitionMode ?? 0, activeVolume, newActiveVolume);
+ activeVolume = newActiveVolume;
+ }
+
+ for (int i = 0; i < transitions.Count; i++)
+ {
+ var transition = transitions[i];
+ var blend = transition.GetBlendValue(now);
+
+ for (int j = 0; j < transitionTickList.Count; i++)
+ {
+ transitionTickList[j](transition, blend);
+ }
+
+ bool remove;
+ if (transition.To == null)
+ {
+ postManager.ResetSettings();
+ remove = true;
+ }
+ else
+ {
+ if (transition.From == null)
+ {
+ transition.To.Apply(postManager);
+ remove = true;
+ }
+ else
+ {
+ transition.To.Apply(postManager, transition.From, blend, transition.Mode);
+ remove = blend == 1;
+ }
+ }
+
+ if (remove)
+ {
+ transitions.RemoveAt(i);
+ i--;
+
+ for (int j = 0; j < transitionEndList.Count; i++)
+ {
+ transitionEndList[j](transition);
+ }
+ }
+ }
+ }
+
+ public void Destroy()
+ {
+ volumes.Dispose();
+ postManager = null;
+ transitionStartList.Clear();
+ transitionEndList.Clear();
+ transitionAbortedList.Clear();
+ transitionTickList.Clear();
+ }
}
}
\ No newline at end of file
diff --git a/HexaEngine/assets/shared/shaders/deferred/brdf/light.hlsl b/HexaEngine/assets/shared/shaders/deferred/brdf/light.hlsl
index 14e92bea..a250c44c 100644
--- a/HexaEngine/assets/shared/shaders/deferred/brdf/light.hlsl
+++ b/HexaEngine/assets/shared/shaders/deferred/brdf/light.hlsl
@@ -8,27 +8,27 @@ Texture2D Depth : register(t15);
struct VSOut
{
- float4 Pos : SV_Position;
- float2 Tex : TEXCOORD;
+ float4 Pos : SV_Position;
+ float2 Tex : TEXCOORD;
};
float4 main(VSOut pin) : SV_TARGET
{
- float depth = Depth.Sample(linearWrapSampler, pin.Tex);
- if (depth == 1)
- discard;
- float3 position = GetPositionWS(pin.Tex, depth);
- GeometryAttributes attrs;
- ExtractGeometryData(pin.Tex, GBufferA, GBufferB, GBufferC, GBufferD, linearWrapSampler, attrs);
+ float depth = Depth.Sample(linearWrapSampler, pin.Tex);
+ if (depth == 1)
+ discard;
+ float3 position = GetPositionWS(pin.Tex, depth);
+ GeometryAttributes attrs;
+ ExtractGeometryData(pin.Tex, GBufferA, GBufferB, GBufferC, GBufferD, linearWrapSampler, attrs);
- float3 N = normalize(attrs.normal);
- float3 VN = camPos - position;
- float3 V = normalize(VN);
+ float3 N = normalize(attrs.normal);
+ float3 VN = camPos - position;
+ float3 V = normalize(VN);
- PixelParams pixel = ComputeSurfaceProps(position, V, N, attrs.baseColor, attrs.roughness, attrs.metallic, attrs.reflectance);
+ PixelParams pixel = ComputeSurfaceProps(position, V, N, attrs.baseColor, attrs.roughness, attrs.metallic, attrs.reflectance);
- float3 direct = ComputeDirectLightning(depth, pixel);
- float3 ambient = ComputeIndirectLightning(pin.Tex, pixel, attrs.ao, attrs.emission);
+ float3 direct = ComputeDirectLightning(depth, pixel);
+ float3 ambient = ComputeIndirectLightning(pin.Tex, pixel, attrs.ao, attrs.emission);
- return float4(ambient + direct, 1);
+ return float4(ambient + direct, 1);
}
\ No newline at end of file
diff --git a/HexaEngine/assets/shared/shaders/forward/sky/preethamSky.hlsl b/HexaEngine/assets/shared/shaders/forward/sky/preethamSky.hlsl
index 3ece4b09..854e15df 100644
--- a/HexaEngine/assets/shared/shaders/forward/sky/preethamSky.hlsl
+++ b/HexaEngine/assets/shared/shaders/forward/sky/preethamSky.hlsl
@@ -79,5 +79,5 @@ float4 main(VertexOut pin) : SV_TARGET
float3 L0 = sunColor * brightness * sunIntensity;
float3 finalColor = skyLuminance + L0;
- return float4(finalColor, 1.0);
+ return float4(finalColor, 0.0);
}
\ No newline at end of file