From d4145179a90db18dd3b0ea27ff4c9e9511771231 Mon Sep 17 00:00:00 2001
From: Norite SC <162097313+cNori@users.noreply.github.com>
Date: Sun, 2 Jun 2024 02:23:48 +0200
Subject: [PATCH 1/3] RadialMenu and material nodes
---
Source/Editor/Surface/Archetypes/Material.cs | 136 ++++++
.../MaterialGenerator.Material.cpp | 104 +++++
Source/Engine/UI/GUI/Special/RadialMenu.cs | 402 ++++++++++++++++++
3 files changed, 642 insertions(+)
create mode 100644 Source/Engine/UI/GUI/Special/RadialMenu.cs
diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs
index 58d1b854d..4b2978ceb 100644
--- a/Source/Editor/Surface/Archetypes/Material.cs
+++ b/Source/Editor/Surface/Archetypes/Material.cs
@@ -936,6 +936,142 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
}
},
+ new NodeArchetype
+ {
+ TypeID = 43,
+ Title = "Rotate UV [Simple Rotator]",
+ Description = "Rotates 2d vector",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Float2(250, 40),
+ ConnectionsHints = ConnectionsHint.Vector,
+ DefaultValues =
+ [
+ 0.0f,
+ ],
+ Elements =
+ [
+ NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 2),
+ ]
+ },
+ new NodeArchetype
+ {
+ TypeID = 44,
+ Title = "Cone Gradient",
+ Description = "",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Float2(175, 40),
+ ConnectionsHints = ConnectionsHint.Vector,
+ DefaultValues =
+ [
+ 0.0f,
+ ],
+ Elements =
+ [
+ NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
+ ]
+ },
+ new NodeArchetype
+ {
+ TypeID = 45,
+ Title = "Cycle Gradient",
+ Description = "2d verison of sphere mask",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Float2(175, 20),
+ ConnectionsHints = ConnectionsHint.Vector,
+ Elements =
+ [
+ NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 1),
+ ]
+ },
+ new NodeArchetype
+ {
+ TypeID = 46,
+ Title = "Falloff and Offset",
+ Description = "",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Float2(175, 60),
+ ConnectionsHints = ConnectionsHint.Numeric,
+ DefaultValues =
+ [
+ 0.0f,
+ 0.9f
+ ],
+ Elements =
+ [
+ NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0),
+ NodeElementArchetype.Factory.Input(1, "Offset", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Input(2, "Falloff", true, typeof(float), 2,1),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 3),
+ ]
+ },
+ new NodeArchetype
+ {
+ TypeID = 47,
+ Title = "Linear Gradient",
+ Description = "x = Gradient along X axis, y = Gradient along Y axis",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Float2(175, 60),
+ ConnectionsHints = ConnectionsHint.Vector,
+ DefaultValues =
+ [
+ 0.0f,
+ false
+ ],
+ Elements =
+ [
+ NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Input(2, "Mirror", true, typeof(bool), 2,1),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 3),
+ ]
+ },
+ new NodeArchetype
+ {
+ TypeID = 48,
+ Title = "Radial Gradient",
+ Description = "",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Float2(175, 40),
+ ConnectionsHints = ConnectionsHint.Vector,
+ DefaultValues =
+ [
+ 0.0f,
+ ],
+ Elements =
+ [
+ NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
+ ]
+ },
+ new NodeArchetype
+ {
+ TypeID = 49,
+ Title = "Ring Gradient",
+ Description = "x = InnerMask,y = OuterMask,z = Mask",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Float2(175, 80),
+ ConnectionsHints = ConnectionsHint.Vector,
+ DefaultValues =
+ [
+ 1.0f,
+ 0.8f,
+ 0.05f,
+ ],
+ Elements =
+ [
+ NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "OuterBounds", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Input(2, "InnerBounds", true, typeof(float), 2,1),
+ NodeElementArchetype.Factory.Input(3, "Falloff", true, typeof(float), 3,2),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4),
+ ]
+ },
};
}
}
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
index 66d24d4f3..c8961e1e0 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
@@ -569,6 +569,110 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
// Do the inverse interpolation and saturate it
value = writeLocal(ValueType::Float, String::Format(TEXT("saturate((({0} - {1}) / ({2} - {1})))"), gradient.Value, lowerEdge.Value, upperEdge.Value), node);
}
+ // Rotate UV [Rotator Simple]
+ case 43:
+ {
+ //cosine = cos(rotation);
+ //sine = sin(rotation);
+ //float2 out = float2(cosine * uv.x + sine * uv.y,cosine * uv.y - sine * uv.x);
+ const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
+ const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
+ auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
+ auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
+ value = writeLocal(ValueType::Float2, String::Format(TEXT("float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
+ break;
+ }
+ // Cone Gradient
+ case 44:
+ {
+ //float gradient = angle - abs(atan2(uv.x,uv.y));
+ const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
+ const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
+ value = writeLocal(ValueType::Float, String::Format(TEXT("{1} - abs(atan2({0}.x,{0}.y))"), uv.Value, rotationAngle.Value), node);
+ break;
+ }
+ // Cycle Gradient
+ case 45:
+ {
+ //float gradient = 1 - lenght(uv * 2);
+ const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
+ value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node);
+ break;
+ }
+ //Falloff and Offset
+ case 46:
+ {
+ //float out = clamp((((Value - (1 - Offset)) + Falloff) / Falloff),0,1)
+ const auto Value = tryGetValue(node->GetBox(0), ShaderGraphValue(0.0f));
+ const auto Offset = tryGetValue(node->GetBox(1), node->Values[0].AsFloat);
+ const auto Falloff = tryGetValue(node->GetBox(2), node->Values[1].AsFloat);
+
+ value = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - {1})) + {2}) / {2}),0,1)"), Value.Value, Offset.Value, Falloff.Value), node);
+ break;
+ }
+ //Linear Gradient
+ case 47:
+ {
+ // float2 uv = Input0.xy;
+ // float r = Input0.z;
+ // float2 A = 1.0 - float2(cos(r) * uv.x + sin(r) * uv.y, cos(r) * uv.y - sin(r) * uv.x);
+ // float2 out = float2(Mirror ? abs(A.x < 1.0 ? (A.x - 0.5) * 2 : (2 - ((A.x - 0.5) * 2)) * -1) : A.x < 1.0 ? (A.x - 0.5) * 2 : 1,Mirror ? abs(A.y < 1.0 ? (A.y - 0.5) * 2 : (2 - ((A.y - 0.5) * 2)) * -1) : A.y < 1.0 ? (A.y - 0.5) * 2 : 1);
+
+ const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
+ const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
+ const auto Mirror = tryGetValue(node->GetBox(2), node->Values[1].AsBool).AsBool();
+
+ auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
+ auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
+ auto A = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
+ value = writeLocal(
+ ValueType::Float2,
+ String::Format(TEXT
+ (
+ "float2({0} ? abs({1}.x < 1.0 ? ({1}.x - 0.5) * 2 : (2 - (({1}.x - 0.5) * 2)) * -1) : {1}.x < 1.0 ? ({1}.x - 0.5) * 2 : 1,{0} ? abs({1}.y < 1.0 ? ({1}.y - 0.5) * 2 : (2 - (({1}.y - 0.5) * 2)) * -1) : {1}.y < 1.0 ? ({1}.y - 0.5) * 2 : 1)"
+ ),
+ Mirror.Value,
+ A.Value),
+ node);
+ break;
+ }
+ //Radial Gradient
+ case 48:
+ {
+ //float gradient = clamp(atan2(uv.x,uv.y) - angle,0.0,1.0);
+ const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
+ const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
+ value = writeLocal(ValueType::Float, String::Format(TEXT("clamp(atan2({0}.x,{0}.y) - {1},0.0,1.0)"), uv.Value, rotationAngle.Value), node);
+ break;
+ }
+ //Ring Gradient
+ case 49:
+ {
+ // Nodes:
+ // float c = CycleGradient(uv)
+ // float InnerMask = FalloffAndOffset(c,(OuterBounds - Falloff),Falloff)
+ // float OuterMask = FalloffAndOffset(1-c,1-InnerBounds,Falloff)
+ // float Mask = OuterMask * InnerMask;
+
+ //ToDo: cheak if there is some useless operetors
+
+ //expanded
+ //float cycleGradient = 1 - lenght(uv * 2);
+ //float InnerMask = clamp((((c - (1 - (OuterBounds - Falloff))) + Falloff) / Falloff),0,1)
+ //float OuterMask = clamp(((((1-c) - (1 - (1-InnerBounds))) + Falloff) / Falloff),0,1)
+ //float Mask = OuterMask * InnerMask;
+
+ const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
+ const auto OuterBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
+ const auto InnerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat();
+ const auto Falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat();
+ auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node);
+ auto InnerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - ({1} - {2}))) + {2}) / {2}),0,1)"), c.Value, OuterBounds.Value, Falloff.Value), node);
+ auto OuterMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((((1-{0}) - (1 - (1-{1}))) + {2}) / {2}),0,1)"), c.Value, InnerBounds.Value, Falloff.Value), node);
+ auto Mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), InnerMask.Value, OuterMask.Value), node);
+ value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0},{1},{2})"), InnerMask.Value, OuterMask.Value, Mask.Value), node);
+ break;
+ }
default:
break;
}
diff --git a/Source/Engine/UI/GUI/Special/RadialMenu.cs b/Source/Engine/UI/GUI/Special/RadialMenu.cs
new file mode 100644
index 000000000..1c7d8c174
--- /dev/null
+++ b/Source/Engine/UI/GUI/Special/RadialMenu.cs
@@ -0,0 +1,402 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FlaxEngine.GUI
+{
+ ///
+ ///
+ ///
+ ///
+ public class RadialMenu : ContainerControl
+ {
+ [NoSerialize] private bool IsDirty = true;
+ [NoSerialize] private float m_Angle;
+ [NoSerialize] private float m_SelectedSegment;
+ [NoSerialize] private int m_F_SelectedSegment = -1;
+
+ private MaterialInstance MaterialInstance;
+ private sbyte m_SegmentCount;
+ private Color highlightColor;
+ private Color forgraundColor;
+ private Color selectionColor;
+ private float m_EdgeOffset;
+ private float m_Thickness = 0.0f;
+ private float USize => Size.X < Size.Y ? Size.X : Size.Y;
+ private bool ShowMatProp => MaterialInstance != null;
+ private MaterialBase material;
+
+ ///
+ /// The material
+ ///
+ [EditorOrder(1)]
+ public MaterialBase Material
+ {
+ get => material;
+ set
+ {
+ material = value;
+ if (material == null)
+ {
+ FlaxEngine.Object.DestroyNow(MaterialInstance);
+ MaterialInstance = null;
+ }
+ else
+ {
+ IsDirty = true;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the edge offset.
+ ///
+ ///
+ /// The edge offset.
+ ///
+ [EditorOrder(2), Range(0, 1)]
+ public float EdgeOffset
+ {
+ get
+ {
+ return m_EdgeOffset;
+ }
+ set
+ {
+ m_EdgeOffset = Math.Clamp(value, 0, 1);
+ IsDirty = true;
+ this.PerformLayout();
+ }
+ }
+ ///
+ /// Gets or sets the thickness.
+ ///
+ ///
+ /// The thickness.
+ ///
+ [EditorOrder(3), Range(0, 1), VisibleIf("ShowMatProp")]
+ public float Thickness
+ {
+ get
+ {
+ return m_Thickness;
+ }
+ set
+ {
+ m_Thickness = Math.Clamp(value, 0, 1);
+ IsDirty = true;
+ this.PerformLayout();
+ }
+ }
+ ///
+ /// Gets or sets control background color (transparent color (alpha=0) means no background rendering)
+ ///
+ [VisibleIf("ShowMatProp")]
+ public new Color BackgroundColor //force overload
+ {
+ get => base.BackgroundColor;
+ set
+ {
+ IsDirty = true;
+ base.BackgroundColor = value;
+ }
+ }
+ ///
+ /// Gets or sets the color of the highlight.
+ ///
+ ///
+ /// The color of the highlight.
+ ///
+ [VisibleIf("ShowMatProp")]
+ public Color HighlightColor { get => highlightColor; set { IsDirty = true; highlightColor = value; } }
+ ///
+ /// Gets or sets the color of the forgraund.
+ ///
+ ///
+ /// The color of the forgraund.
+ ///
+ [VisibleIf("ShowMatProp")]
+ public Color ForgraundColor { get => forgraundColor; set { IsDirty = true; forgraundColor = value; } }
+ ///
+ /// Gets or sets the color of the selection.
+ ///
+ ///
+ /// The color of the selection.
+ ///
+ [VisibleIf("ShowMatProp")]
+ public Color SelectionColor { get => selectionColor; set { IsDirty = true; selectionColor = value; } }
+
+ ///
+ /// The selected callback
+ ///
+ [HideInEditor]
+ public Action Selected;
+
+ ///
+ /// The allow change selection when inside
+ ///
+ [VisibleIf("ShowMatProp")]
+ public bool AllowChangeSelectionWhenInside;
+ ///
+ /// The center as button
+ ///
+ [VisibleIf("ShowMatProp")]
+ public bool CenterAsButton;
+
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RadialMenu()
+ : this(0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Position X coordinate
+ /// Position Y coordinate
+ /// Width
+ /// Height
+ public RadialMenu(float x, float y, float width = 100, float height = 100)
+ : base(x, y, width, height)
+ {
+ var style = Style.Current;
+ if (style != null)
+ {
+ BackgroundColor = style.BackgroundNormal;
+ HighlightColor = style.BackgroundSelected;
+ ForgraundColor = style.BackgroundHighlighted;
+ SelectionColor = style.BackgroundSelected;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Position
+ /// Size
+ public RadialMenu(Float2 location, Float2 size)
+ : this(location.X, location.Y, size.X, size.Y)
+ {
+ }
+
+ ///
+ /// Draws the control.
+ ///
+ public override void DrawSelf()
+ {
+ if (MaterialInstance != null)
+ {
+ if (IsDirty)
+ {
+ MaterialInstance.SetParameterValue("RadialPieMenu_EdgeOffset", Math.Clamp(1 - m_EdgeOffset, 0, 1));
+ MaterialInstance.SetParameterValue("RadialPieMenu_Thickness", Math.Clamp(m_Thickness, 0, 1));
+ MaterialInstance.SetParameterValue("RadialPieMenu_Angle", ((float)1 / m_SegmentCount) * Mathf.Pi);
+ MaterialInstance.SetParameterValue("RadialPieMenu_SCount", m_SegmentCount);
+
+ MaterialInstance.SetParameterValue("RadialPieMenu_HighlightColor", HighlightColor);
+ MaterialInstance.SetParameterValue("RadialPieMenu_ForgraundColor", ForgraundColor);
+ MaterialInstance.SetParameterValue("RadialPieMenu_BackgroundColor", BackgroundColor);
+ MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi);
+ UpdateFSS();
+ IsDirty = false;
+ }
+ Render2D.DrawMaterial(MaterialInstance, new Rectangle(Float2.Zero, new Float2(Size.X < Size.Y ? Size.X : Size.Y)));
+ }
+ else
+ {
+ if (Material != null)
+ {
+ MaterialInstance = Material.CreateVirtualInstance();
+ }
+ }
+ }
+ ///
+ public override void OnMouseMove(Float2 location)
+ {
+ if (MaterialInstance != null)
+ {
+ if (m_F_SelectedSegment == -1)
+ {
+ var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f;
+ var max = (1 - m_EdgeOffset) * USize * 0.5f;
+ var val = ((USize * 0.5f) - location).Length;
+ if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
+ {
+ var size = new Float2(USize);
+ var p = (size * 0.5f) - location;
+ var Sa = ((float)1 / m_SegmentCount) * Mathf.TwoPi;
+ m_Angle = Mathf.Atan2(p.X, p.Y);
+ m_Angle = Mathf.Ceil((m_Angle - (Sa * 0.5f)) / Sa) * Sa;
+ m_SelectedSegment = m_Angle;
+ m_SelectedSegment = Mathf.RoundToInt((m_SelectedSegment < 0 ? Mathf.TwoPi + m_SelectedSegment : m_SelectedSegment) / Sa);
+ if (float.IsNaN(m_Angle) || float.IsInfinity(m_Angle))
+ m_Angle = 0;
+ MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi);
+ }
+ }
+ }
+ base.OnMouseMove(location);
+ }
+ ///
+ public override bool OnMouseDown(Float2 location, MouseButton button)
+ {
+ if (MaterialInstance == null)
+ return base.OnMouseDown(location, button);
+
+ var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f;
+ var max = (1 - m_EdgeOffset) * USize * 0.5f;
+ var val = ((USize * 0.5f) - location).Length;
+ var c = val < min && CenterAsButton;
+ var selected = (int)m_SelectedSegment;
+ selected++;
+ if (Mathf.IsInRange(val, min, max) || c)
+ {
+ if (c)
+ {
+ m_F_SelectedSegment = 0;
+ }
+ else
+ {
+ m_F_SelectedSegment = selected;
+ }
+ UpdateFSS();
+ return true;
+ }
+ else
+ {
+ m_F_SelectedSegment = -1;
+ UpdateFSS();
+ }
+ return base.OnMouseDown(location, button);
+ }
+ ///
+ public override bool OnMouseUp(Float2 location, MouseButton button)
+ {
+ if (MaterialInstance == null)
+ return base.OnMouseDown(location, button);
+
+ if (m_F_SelectedSegment >= 0)
+ {
+ if (Selected != null)
+ {
+ Selected(m_F_SelectedSegment);
+ }
+ m_F_SelectedSegment = -1;
+ UpdateFSS();
+ var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f;
+ var max = (1 - m_EdgeOffset) * USize * 0.5f;
+ var val = ((USize * 0.5f) - location).Length;
+ if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
+ {
+ var size = new Float2(USize);
+ var p = (size * 0.5f) - location;
+ var Sa = ((float)1 / m_SegmentCount) * Mathf.TwoPi;
+ m_Angle = Mathf.Atan2(p.X, p.Y);
+ m_Angle = Mathf.Ceil((m_Angle - (Sa * 0.5f)) / Sa) * Sa;
+ m_SelectedSegment = m_Angle;
+ m_SelectedSegment = Mathf.RoundToInt((m_SelectedSegment < 0 ? Mathf.TwoPi + m_SelectedSegment : m_SelectedSegment) / Sa);
+ if (float.IsNaN(m_Angle) || float.IsInfinity(m_Angle))
+ m_Angle = 0;
+ MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi);
+ }
+ }
+ return base.OnMouseUp(location, button);
+ }
+ private void UpdateFSS()
+ {
+ if (m_F_SelectedSegment == -1)
+ {
+ MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", ForgraundColor);
+ }
+ else
+ {
+ if (CenterAsButton)
+ {
+ if (m_F_SelectedSegment > 0)
+ {
+ MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", SelectionColor);
+ }
+ else
+ {
+ MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", ForgraundColor);
+ }
+ }
+ else
+ {
+ MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", SelectionColor);
+ }
+ }
+ }
+ ///
+ public override void OnMouseLeave()
+ {
+ if (MaterialInstance == null)
+ return;
+
+ m_SelectedSegment = 0;
+ m_F_SelectedSegment = -1;
+ if (Selected != null)
+ {
+ Selected(m_F_SelectedSegment);
+ }
+ UpdateFSS();
+ }
+ ///
+ public override void OnChildrenChanged()
+ {
+ m_SegmentCount = 0;
+ for (int i = 0; i < Children.Count; i++)
+ {
+ if (Children[i] is Image)
+ {
+ m_SegmentCount++;
+ }
+ }
+ IsDirty = true;
+ base.OnChildrenChanged();
+ }
+ ///
+ public override void PerformLayout(bool force = false)
+ {
+ var Sa = -1.0f / m_SegmentCount * Mathf.TwoPi;
+ var midp = USize * 0.5f;
+ var mp = ((1 - m_EdgeOffset) - (m_Thickness * 0.5f)) * midp;
+ float f = 0;
+ if (m_SegmentCount % 2 != 0)
+ {
+ f += Sa * 0.5f;
+ }
+ if (MaterialInstance == null)
+ {
+ for (int i = 0; i < Children.Count; i++)
+ {
+ Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp;
+ f += Sa;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Children.Count; i++)
+ {
+ if (Children[i] is Image)
+ {
+ Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp;
+ f += Sa;
+ }
+ }
+ }
+
+ base.PerformLayout(force);
+ }
+ private Float2 Rotate2D(Float2 point, float angle)
+ {
+ return new Float2(Mathf.Cos(angle) * point.X + Mathf.Sin(angle) * point.Y,
+ Mathf.Cos(angle) * point.Y - Mathf.Sin(angle) * point.X);
+ }
+ }
+}
From f95e7e96bf29d25b2bec6f53484228c3269ab05b Mon Sep 17 00:00:00 2001
From: Wojtek Figat
Date: Tue, 11 Jun 2024 08:43:31 +0200
Subject: [PATCH 2/3] Codestyle fixes and some nodes docs
---
Source/Editor/Surface/Archetypes/Material.cs | 44 ++++++++--------
.../MaterialGenerator.Material.cpp | 50 +++++++++----------
2 files changed, 45 insertions(+), 49 deletions(-)
diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs
index 4b2978ceb..f1da861c1 100644
--- a/Source/Editor/Surface/Archetypes/Material.cs
+++ b/Source/Editor/Surface/Archetypes/Material.cs
@@ -939,8 +939,8 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 43,
- Title = "Rotate UV [Simple Rotator]",
- Description = "Rotates 2d vector",
+ Title = "Rotate UV",
+ Description = "Rotates 2D vector by given angle around (0,0) origin",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(250, 40),
ConnectionsHints = ConnectionsHint.Vector,
@@ -950,8 +950,8 @@ namespace FlaxEditor.Surface.Archetypes
],
Elements =
[
- NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
- NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 2),
]
},
@@ -959,7 +959,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 44,
Title = "Cone Gradient",
- Description = "",
+ Description = "Creates cone gradient around normalized UVs (range [-1; 1]), angle is in radians (range [0; TwoPi])",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 40),
ConnectionsHints = ConnectionsHint.Vector,
@@ -969,8 +969,8 @@ namespace FlaxEditor.Surface.Archetypes
],
Elements =
[
- NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
- NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
]
},
@@ -978,13 +978,13 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 45,
Title = "Cycle Gradient",
- Description = "2d verison of sphere mask",
+ Description = "Creates 2D sphere mask gradient around normalized UVs (range [-1; 1])",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 20),
ConnectionsHints = ConnectionsHint.Vector,
Elements =
[
- NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 1),
]
},
@@ -1004,8 +1004,8 @@ namespace FlaxEditor.Surface.Archetypes
Elements =
[
NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0),
- NodeElementArchetype.Factory.Input(1, "Offset", true, typeof(float), 1,0),
- NodeElementArchetype.Factory.Input(2, "Falloff", true, typeof(float), 2,1),
+ NodeElementArchetype.Factory.Input(1, "Offset", true, typeof(float), 1, 0),
+ NodeElementArchetype.Factory.Input(2, "Falloff", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 3),
]
},
@@ -1024,9 +1024,9 @@ namespace FlaxEditor.Surface.Archetypes
],
Elements =
[
- NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
- NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0),
- NodeElementArchetype.Factory.Input(2, "Mirror", true, typeof(bool), 2,1),
+ NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
+ NodeElementArchetype.Factory.Input(2, "Mirror", true, typeof(bool), 2, 1),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 3),
]
},
@@ -1042,10 +1042,10 @@ namespace FlaxEditor.Surface.Archetypes
[
0.0f,
],
- Elements =
+ Elements =
[
- NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
- NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0),
+ NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2),
]
},
@@ -1063,12 +1063,12 @@ namespace FlaxEditor.Surface.Archetypes
0.8f,
0.05f,
],
- Elements =
+ Elements =
[
- NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0),
- NodeElementArchetype.Factory.Input(1, "OuterBounds", true, typeof(float), 1,0),
- NodeElementArchetype.Factory.Input(2, "InnerBounds", true, typeof(float), 2,1),
- NodeElementArchetype.Factory.Input(3, "Falloff", true, typeof(float), 3,2),
+ NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0),
+ NodeElementArchetype.Factory.Input(1, "OuterBounds", true, typeof(float), 1, 0),
+ NodeElementArchetype.Factory.Input(2, "InnerBounds", true, typeof(float), 2, 1),
+ NodeElementArchetype.Factory.Input(3, "Falloff", true, typeof(float), 3, 2),
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4),
]
},
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
index c8961e1e0..a9c983eec 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
@@ -594,23 +594,22 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
// Cycle Gradient
case 45:
{
- //float gradient = 1 - lenght(uv * 2);
+ //float gradient = 1 - length(uv * 2);
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node);
break;
}
- //Falloff and Offset
+ // Falloff and Offset
case 46:
{
//float out = clamp((((Value - (1 - Offset)) + Falloff) / Falloff),0,1)
- const auto Value = tryGetValue(node->GetBox(0), ShaderGraphValue(0.0f));
- const auto Offset = tryGetValue(node->GetBox(1), node->Values[0].AsFloat);
- const auto Falloff = tryGetValue(node->GetBox(2), node->Values[1].AsFloat);
-
- value = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - {1})) + {2}) / {2}),0,1)"), Value.Value, Offset.Value, Falloff.Value), node);
+ const auto in = tryGetValue(node->GetBox(0), ShaderGraphValue::Zero);
+ const auto graphValue = tryGetValue(node->GetBox(1), node->Values[0].AsFloat);
+ const auto falloff = tryGetValue(node->GetBox(2), node->Values[1].AsFloat);
+ value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((({0} - (1.0 - {1})) + {2}) / {2}))"), in.Value, graphValue.Value, falloff.Value), node);
break;
}
- //Linear Gradient
+ // Linear Gradient
case 47:
{
// float2 uv = Input0.xy;
@@ -620,19 +619,16 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
- const auto Mirror = tryGetValue(node->GetBox(2), node->Values[1].AsBool).AsBool();
+ const auto mirror = tryGetValue(node->GetBox(2), node->Values[1].AsBool).AsBool();
auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
- auto A = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
+ auto a = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
value = writeLocal(
- ValueType::Float2,
- String::Format(TEXT
- (
- "float2({0} ? abs({1}.x < 1.0 ? ({1}.x - 0.5) * 2 : (2 - (({1}.x - 0.5) * 2)) * -1) : {1}.x < 1.0 ? ({1}.x - 0.5) * 2 : 1,{0} ? abs({1}.y < 1.0 ? ({1}.y - 0.5) * 2 : (2 - (({1}.y - 0.5) * 2)) * -1) : {1}.y < 1.0 ? ({1}.y - 0.5) * 2 : 1)"
- ),
- Mirror.Value,
- A.Value),
+ ValueType::Float2, String::Format(TEXT
+ (
+ "float2({0} ? abs({1}.x < 1.0 ? ({1}.x - 0.5) * 2 : (2 - (({1}.x - 0.5) * 2)) * -1) : {1}.x < 1.0 ? ({1}.x - 0.5) * 2 : 1,{0} ? abs({1}.y < 1.0 ? ({1}.y - 0.5) * 2 : (2 - (({1}.y - 0.5) * 2)) * -1) : {1}.y < 1.0 ? ({1}.y - 0.5) * 2 : 1)"
+ ), mirror.Value, a.Value),
node);
break;
}
@@ -654,23 +650,23 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
// float OuterMask = FalloffAndOffset(1-c,1-InnerBounds,Falloff)
// float Mask = OuterMask * InnerMask;
- //ToDo: cheak if there is some useless operetors
+ // TODO: check if there is some useless operators
//expanded
- //float cycleGradient = 1 - lenght(uv * 2);
+ //float cycleGradient = 1 - length(uv * 2);
//float InnerMask = clamp((((c - (1 - (OuterBounds - Falloff))) + Falloff) / Falloff),0,1)
//float OuterMask = clamp(((((1-c) - (1 - (1-InnerBounds))) + Falloff) / Falloff),0,1)
//float Mask = OuterMask * InnerMask;
- const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
- const auto OuterBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
- const auto InnerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat();
- const auto Falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat();
+ const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
+ const auto outerBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
+ const auto innerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat();
+ const auto falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat();
auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node);
- auto InnerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - ({1} - {2}))) + {2}) / {2}),0,1)"), c.Value, OuterBounds.Value, Falloff.Value), node);
- auto OuterMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((((1-{0}) - (1 - (1-{1}))) + {2}) / {2}),0,1)"), c.Value, InnerBounds.Value, Falloff.Value), node);
- auto Mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), InnerMask.Value, OuterMask.Value), node);
- value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0},{1},{2})"), InnerMask.Value, OuterMask.Value, Mask.Value), node);
+ auto innerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - ({1} - {2}))) + {2}) / {2}),0,1)"), c.Value, outerBounds.Value, falloff.Value), node);
+ auto outerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((((1-{0}) - (1 - (1-{1}))) + {2}) / {2}),0,1)"), c.Value, innerBounds.Value, falloff.Value), node);
+ auto mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), innerMask.Value, outerMask.Value), node);
+ value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0},{1},{2})"), innerMask.Value, outerMask.Value, mask.Value), node);
break;
}
default:
From 210c443b30c4f647cfe7f649b392481b1a0c39bc Mon Sep 17 00:00:00 2001
From: Wojtek Figat
Date: Tue, 11 Jun 2024 09:48:53 +0200
Subject: [PATCH 3/3] Cleanup code
---
.../MaterialGenerator.Material.cpp | 22 +-
Source/Engine/UI/GUI/Special/RadialMenu.cs | 369 +++++++++---------
2 files changed, 189 insertions(+), 202 deletions(-)
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
index a9c983eec..03b27bfa1 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
@@ -579,7 +579,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
- value = writeLocal(ValueType::Float2, String::Format(TEXT("float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
+ value = writeLocal(ValueType::Float2, String::Format(TEXT("float2({1} * {0}.x + {2} * {0}.y, {1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
break;
}
// Cone Gradient
@@ -588,7 +588,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
//float gradient = angle - abs(atan2(uv.x,uv.y));
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
- value = writeLocal(ValueType::Float, String::Format(TEXT("{1} - abs(atan2({0}.x,{0}.y))"), uv.Value, rotationAngle.Value), node);
+ value = writeLocal(ValueType::Float, String::Format(TEXT("{1} - abs(atan2({0}.x, {0}.y))"), uv.Value, rotationAngle.Value), node);
break;
}
// Cycle Gradient
@@ -596,7 +596,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
{
//float gradient = 1 - length(uv * 2);
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
- value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node);
+ value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2.0)"), uv.Value), node);
break;
}
// Falloff and Offset
@@ -623,7 +623,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node);
auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node);
- auto a = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
+ auto a = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y, {1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node);
value = writeLocal(
ValueType::Float2, String::Format(TEXT
(
@@ -632,16 +632,16 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
node);
break;
}
- //Radial Gradient
+ // Radial Gradient
case 48:
{
//float gradient = clamp(atan2(uv.x,uv.y) - angle,0.0,1.0);
const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2();
const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
- value = writeLocal(ValueType::Float, String::Format(TEXT("clamp(atan2({0}.x,{0}.y) - {1},0.0,1.0)"), uv.Value, rotationAngle.Value), node);
+ value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(atan2({0}.x, {0}.y) - {1})"), uv.Value, rotationAngle.Value), node);
break;
}
- //Ring Gradient
+ // Ring Gradient
case 49:
{
// Nodes:
@@ -662,11 +662,11 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
const auto outerBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat();
const auto innerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat();
const auto falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat();
- auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node);
- auto innerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - ({1} - {2}))) + {2}) / {2}),0,1)"), c.Value, outerBounds.Value, falloff.Value), node);
- auto outerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((((1-{0}) - (1 - (1-{1}))) + {2}) / {2}),0,1)"), c.Value, innerBounds.Value, falloff.Value), node);
+ auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2.0)"), uv.Value), node);
+ auto innerMask = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((({0} - (1.0 - ({1} - {2}))) + {2}) / {2}))"), c.Value, outerBounds.Value, falloff.Value), node);
+ auto outerMask = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((((1.0 - {0}) - (1.0 - (1.0 - {1}))) + {2}) / {2}))"), c.Value, innerBounds.Value, falloff.Value), node);
auto mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), innerMask.Value, outerMask.Value), node);
- value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0},{1},{2})"), innerMask.Value, outerMask.Value, mask.Value), node);
+ value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, {1}, {2})"), innerMask.Value, outerMask.Value, mask.Value), node);
break;
}
default:
diff --git a/Source/Engine/UI/GUI/Special/RadialMenu.cs b/Source/Engine/UI/GUI/Special/RadialMenu.cs
index 1c7d8c174..2ffe8006a 100644
--- a/Source/Engine/UI/GUI/Special/RadialMenu.cs
+++ b/Source/Engine/UI/GUI/Special/RadialMenu.cs
@@ -1,132 +1,134 @@
+// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
+
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace FlaxEngine.GUI
{
///
- ///
+ /// Radial menu control that arranges child controls (of type Image) in a circle.
///
///
public class RadialMenu : ContainerControl
{
- [NoSerialize] private bool IsDirty = true;
- [NoSerialize] private float m_Angle;
- [NoSerialize] private float m_SelectedSegment;
- [NoSerialize] private int m_F_SelectedSegment = -1;
+ private bool _materialIsDirty = true;
+ private float _angle;
+ private float _selectedSegment;
+ private int _highlightSegment = -1;
+ private MaterialBase _material;
+ private MaterialInstance _materialInstance;
+ private int _segmentCount;
+ private Color _highlightColor;
+ private Color _foregroundColor;
+ private Color _selectionColor;
+ private float _edgeOffset;
+ private float _thickness = 0.2f;
- private MaterialInstance MaterialInstance;
- private sbyte m_SegmentCount;
- private Color highlightColor;
- private Color forgraundColor;
- private Color selectionColor;
- private float m_EdgeOffset;
- private float m_Thickness = 0.0f;
private float USize => Size.X < Size.Y ? Size.X : Size.Y;
- private bool ShowMatProp => MaterialInstance != null;
- private MaterialBase material;
+ private bool ShowMatProp => _material != null;
///
- /// The material
+ /// The material to use for menu background drawing.
///
[EditorOrder(1)]
public MaterialBase Material
{
- get => material;
+ get => _material;
set
{
- material = value;
- if (material == null)
- {
- FlaxEngine.Object.DestroyNow(MaterialInstance);
- MaterialInstance = null;
- }
- else
- {
- IsDirty = true;
- }
+ if (_material == value)
+ return;
+ _material = value;
+ Object.DestroyNow(_materialInstance);
+ _materialInstance = null;
+ _materialIsDirty = true;
}
}
///
/// Gets or sets the edge offset.
///
- ///
- /// The edge offset.
- ///
[EditorOrder(2), Range(0, 1)]
public float EdgeOffset
{
- get
- {
- return m_EdgeOffset;
- }
+ get => _edgeOffset;
set
{
- m_EdgeOffset = Math.Clamp(value, 0, 1);
- IsDirty = true;
- this.PerformLayout();
+ _edgeOffset = Math.Clamp(value, 0, 1);
+ _materialIsDirty = true;
+ PerformLayout();
}
}
+
///
/// Gets or sets the thickness.
///
- ///
- /// The thickness.
- ///
- [EditorOrder(3), Range(0, 1), VisibleIf("ShowMatProp")]
+ [EditorOrder(3), Range(0, 1), VisibleIf(nameof(ShowMatProp))]
public float Thickness
{
- get
- {
- return m_Thickness;
- }
+ get => _thickness;
set
{
- m_Thickness = Math.Clamp(value, 0, 1);
- IsDirty = true;
- this.PerformLayout();
+ _thickness = Math.Clamp(value, 0, 1);
+ _materialIsDirty = true;
+ PerformLayout();
}
}
+
///
- /// Gets or sets control background color (transparent color (alpha=0) means no background rendering)
+ /// Gets or sets control background color (transparent color (alpha=0) means no background rendering).
///
- [VisibleIf("ShowMatProp")]
- public new Color BackgroundColor //force overload
+ [VisibleIf(nameof(ShowMatProp))]
+ public new Color BackgroundColor
{
get => base.BackgroundColor;
set
{
- IsDirty = true;
+ _materialIsDirty = true;
base.BackgroundColor = value;
}
}
+
///
/// Gets or sets the color of the highlight.
///
- ///
- /// The color of the highlight.
- ///
- [VisibleIf("ShowMatProp")]
- public Color HighlightColor { get => highlightColor; set { IsDirty = true; highlightColor = value; } }
+ [VisibleIf(nameof(ShowMatProp))]
+ public Color HighlightColor
+ {
+ get => _highlightColor;
+ set
+ {
+ _materialIsDirty = true;
+ _highlightColor = value;
+ }
+ }
+
///
- /// Gets or sets the color of the forgraund.
+ /// Gets or sets the color of the foreground.
///
- ///
- /// The color of the forgraund.
- ///
- [VisibleIf("ShowMatProp")]
- public Color ForgraundColor { get => forgraundColor; set { IsDirty = true; forgraundColor = value; } }
+ [VisibleIf(nameof(ShowMatProp))]
+ public Color ForegroundColor
+ {
+ get => _foregroundColor;
+ set
+ {
+ _materialIsDirty = true;
+ _foregroundColor = value;
+ }
+ }
+
///
/// Gets or sets the color of the selection.
///
- ///
- /// The color of the selection.
- ///
- [VisibleIf("ShowMatProp")]
- public Color SelectionColor { get => selectionColor; set { IsDirty = true; selectionColor = value; } }
+ [VisibleIf(nameof(ShowMatProp))]
+ public Color SelectionColor
+ {
+ get => _selectionColor;
+ set
+ {
+ _materialIsDirty = true;
+ _selectionColor = value;
+ }
+ }
///
/// The selected callback
@@ -137,15 +139,15 @@ namespace FlaxEngine.GUI
///
/// The allow change selection when inside
///
- [VisibleIf("ShowMatProp")]
+ [VisibleIf(nameof(ShowMatProp))]
public bool AllowChangeSelectionWhenInside;
+
///
/// The center as button
///
- [VisibleIf("ShowMatProp")]
+ [VisibleIf(nameof(ShowMatProp))]
public bool CenterAsButton;
-
///
/// Initializes a new instance of the class.
///
@@ -169,7 +171,7 @@ namespace FlaxEngine.GUI
{
BackgroundColor = style.BackgroundNormal;
HighlightColor = style.BackgroundSelected;
- ForgraundColor = style.BackgroundHighlighted;
+ ForegroundColor = style.BackgroundHighlighted;
SelectionColor = style.BackgroundSelected;
}
}
@@ -184,199 +186,148 @@ namespace FlaxEngine.GUI
{
}
- ///
- /// Draws the control.
- ///
+ ///
public override void DrawSelf()
{
- if (MaterialInstance != null)
+ if (_materialInstance == null && Material != null)
{
- if (IsDirty)
- {
- MaterialInstance.SetParameterValue("RadialPieMenu_EdgeOffset", Math.Clamp(1 - m_EdgeOffset, 0, 1));
- MaterialInstance.SetParameterValue("RadialPieMenu_Thickness", Math.Clamp(m_Thickness, 0, 1));
- MaterialInstance.SetParameterValue("RadialPieMenu_Angle", ((float)1 / m_SegmentCount) * Mathf.Pi);
- MaterialInstance.SetParameterValue("RadialPieMenu_SCount", m_SegmentCount);
-
- MaterialInstance.SetParameterValue("RadialPieMenu_HighlightColor", HighlightColor);
- MaterialInstance.SetParameterValue("RadialPieMenu_ForgraundColor", ForgraundColor);
- MaterialInstance.SetParameterValue("RadialPieMenu_BackgroundColor", BackgroundColor);
- MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi);
- UpdateFSS();
- IsDirty = false;
- }
- Render2D.DrawMaterial(MaterialInstance, new Rectangle(Float2.Zero, new Float2(Size.X < Size.Y ? Size.X : Size.Y)));
+ _materialInstance = Material.CreateVirtualInstance();
+ _materialIsDirty = true;
}
- else
+ if (_materialInstance != null)
{
- if (Material != null)
+ if (_materialIsDirty)
{
- MaterialInstance = Material.CreateVirtualInstance();
+ _materialInstance.SetParameterValue("RadialMenu_EdgeOffset", Math.Clamp(1 - _edgeOffset, 0, 1));
+ _materialInstance.SetParameterValue("RadialMenu_Thickness", Math.Clamp(_thickness, 0, 1));
+ _materialInstance.SetParameterValue("RadialMenu_Angle", (1.0f / _segmentCount) * Mathf.Pi);
+ _materialInstance.SetParameterValue("RadialMenu_SegmentCount", _segmentCount);
+ _materialInstance.SetParameterValue("RadialMenu_HighlightColor", _highlightColor);
+ _materialInstance.SetParameterValue("RadialMenu_ForegroundColor", _foregroundColor);
+ _materialInstance.SetParameterValue("RadialMenu_BackgroundColor", BackgroundColor);
+ _materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
+ UpdateSelectionColor();
+ _materialIsDirty = false;
}
+ Render2D.DrawMaterial(_materialInstance, new Rectangle(Float2.Zero, new Float2(Size.X < Size.Y ? Size.X : Size.Y)));
}
}
+
///
public override void OnMouseMove(Float2 location)
{
- if (MaterialInstance != null)
+ if (_materialInstance != null)
{
- if (m_F_SelectedSegment == -1)
+ if (_highlightSegment == -1)
{
- var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f;
- var max = (1 - m_EdgeOffset) * USize * 0.5f;
+ var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
+ var max = (1 - _edgeOffset) * USize * 0.5f;
var val = ((USize * 0.5f) - location).Length;
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
{
- var size = new Float2(USize);
- var p = (size * 0.5f) - location;
- var Sa = ((float)1 / m_SegmentCount) * Mathf.TwoPi;
- m_Angle = Mathf.Atan2(p.X, p.Y);
- m_Angle = Mathf.Ceil((m_Angle - (Sa * 0.5f)) / Sa) * Sa;
- m_SelectedSegment = m_Angle;
- m_SelectedSegment = Mathf.RoundToInt((m_SelectedSegment < 0 ? Mathf.TwoPi + m_SelectedSegment : m_SelectedSegment) / Sa);
- if (float.IsNaN(m_Angle) || float.IsInfinity(m_Angle))
- m_Angle = 0;
- MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi);
+ UpdateAngle(ref location);
}
}
}
+
base.OnMouseMove(location);
}
+
///
public override bool OnMouseDown(Float2 location, MouseButton button)
{
- if (MaterialInstance == null)
+ if (_materialInstance == null)
return base.OnMouseDown(location, button);
- var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f;
- var max = (1 - m_EdgeOffset) * USize * 0.5f;
+ var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
+ var max = (1 - _edgeOffset) * USize * 0.5f;
var val = ((USize * 0.5f) - location).Length;
var c = val < min && CenterAsButton;
- var selected = (int)m_SelectedSegment;
+ var selected = (int)_selectedSegment;
selected++;
if (Mathf.IsInRange(val, min, max) || c)
{
- if (c)
- {
- m_F_SelectedSegment = 0;
- }
- else
- {
- m_F_SelectedSegment = selected;
- }
- UpdateFSS();
+ _highlightSegment = c ? 0 : selected;
+ UpdateSelectionColor();
return true;
}
else
{
- m_F_SelectedSegment = -1;
- UpdateFSS();
+ _highlightSegment = -1;
+ UpdateSelectionColor();
}
+
return base.OnMouseDown(location, button);
}
+
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
- if (MaterialInstance == null)
+ if (_materialInstance == null)
return base.OnMouseDown(location, button);
- if (m_F_SelectedSegment >= 0)
+ if (_highlightSegment >= 0)
{
- if (Selected != null)
- {
- Selected(m_F_SelectedSegment);
- }
- m_F_SelectedSegment = -1;
- UpdateFSS();
- var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f;
- var max = (1 - m_EdgeOffset) * USize * 0.5f;
+ Selected?.Invoke(_highlightSegment);
+ _highlightSegment = -1;
+ UpdateSelectionColor();
+ var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
+ var max = (1 - _edgeOffset) * USize * 0.5f;
var val = ((USize * 0.5f) - location).Length;
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
{
- var size = new Float2(USize);
- var p = (size * 0.5f) - location;
- var Sa = ((float)1 / m_SegmentCount) * Mathf.TwoPi;
- m_Angle = Mathf.Atan2(p.X, p.Y);
- m_Angle = Mathf.Ceil((m_Angle - (Sa * 0.5f)) / Sa) * Sa;
- m_SelectedSegment = m_Angle;
- m_SelectedSegment = Mathf.RoundToInt((m_SelectedSegment < 0 ? Mathf.TwoPi + m_SelectedSegment : m_SelectedSegment) / Sa);
- if (float.IsNaN(m_Angle) || float.IsInfinity(m_Angle))
- m_Angle = 0;
- MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi);
+ UpdateAngle(ref location);
}
}
+
return base.OnMouseUp(location, button);
}
- private void UpdateFSS()
- {
- if (m_F_SelectedSegment == -1)
- {
- MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", ForgraundColor);
- }
- else
- {
- if (CenterAsButton)
- {
- if (m_F_SelectedSegment > 0)
- {
- MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", SelectionColor);
- }
- else
- {
- MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", ForgraundColor);
- }
- }
- else
- {
- MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", SelectionColor);
- }
- }
- }
+
///
public override void OnMouseLeave()
{
- if (MaterialInstance == null)
+ if (_materialInstance == null)
return;
- m_SelectedSegment = 0;
- m_F_SelectedSegment = -1;
- if (Selected != null)
- {
- Selected(m_F_SelectedSegment);
- }
- UpdateFSS();
+ _selectedSegment = 0;
+ _highlightSegment = -1;
+ Selected?.Invoke(_highlightSegment);
+ UpdateSelectionColor();
}
+
///
public override void OnChildrenChanged()
{
- m_SegmentCount = 0;
+ _segmentCount = 0;
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] is Image)
{
- m_SegmentCount++;
+ _segmentCount++;
}
}
- IsDirty = true;
+ _materialIsDirty = true;
+
base.OnChildrenChanged();
}
+
///
public override void PerformLayout(bool force = false)
{
- var Sa = -1.0f / m_SegmentCount * Mathf.TwoPi;
+ var sa = -1.0f / _segmentCount * Mathf.TwoPi;
var midp = USize * 0.5f;
- var mp = ((1 - m_EdgeOffset) - (m_Thickness * 0.5f)) * midp;
+ var mp = ((1 - _edgeOffset) - (_thickness * 0.5f)) * midp;
float f = 0;
- if (m_SegmentCount % 2 != 0)
+ if (_segmentCount % 2 != 0)
{
- f += Sa * 0.5f;
+ f += sa * 0.5f;
}
- if (MaterialInstance == null)
+ if (_materialInstance == null)
{
for (int i = 0; i < Children.Count; i++)
{
Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp;
- f += Sa;
+ f += sa;
}
}
else
@@ -386,17 +337,53 @@ namespace FlaxEngine.GUI
if (Children[i] is Image)
{
Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp;
- f += Sa;
+ f += sa;
}
}
}
base.PerformLayout(force);
}
- private Float2 Rotate2D(Float2 point, float angle)
+
+ private void UpdateSelectionColor()
+ {
+ Color color;
+ if (_highlightSegment == -1)
+ {
+ color = _foregroundColor;
+ }
+ else
+ {
+ if (CenterAsButton)
+ {
+ color = _highlightSegment > 0 ? SelectionColor : _foregroundColor;
+ }
+ else
+ {
+ color = SelectionColor;
+ }
+ }
+ _materialInstance.SetParameterValue("RadialMenu_SelectionColor", color);
+ }
+
+ private void UpdateAngle(ref Float2 location)
+ {
+ var size = new Float2(USize);
+ var p = (size * 0.5f) - location;
+ var sa = (1.0f / _segmentCount) * Mathf.TwoPi;
+ _angle = Mathf.Atan2(p.X, p.Y);
+ _angle = Mathf.Ceil((_angle - (sa * 0.5f)) / sa) * sa;
+ _selectedSegment = _angle;
+ _selectedSegment = Mathf.RoundToInt((_selectedSegment < 0 ? Mathf.TwoPi + _selectedSegment : _selectedSegment) / sa);
+ if (float.IsNaN(_angle) || float.IsInfinity(_angle))
+ _angle = 0;
+ _materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
+ }
+
+ private static Float2 Rotate2D(Float2 point, float angle)
{
return new Float2(Mathf.Cos(angle) * point.X + Mathf.Sin(angle) * point.Y,
- Mathf.Cos(angle) * point.Y - Mathf.Sin(angle) * point.X);
+ Mathf.Cos(angle) * point.Y - Mathf.Sin(angle) * point.X);
}
}
}