diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..e8eb1eb47
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,32 @@
+# How to contribute to the FlaxEngine
+
+For any questions, suggestions or help join our discord!
+
+
+
+Want to see whats planned for Flax?
+
+Go check out our [Trello](https://trello.com/b/NQjLXRCP/flax-roadmap).
+
+## **Found a bug?**
+
+* Avoid opening any new issues without having checked if your problem has already been reported. If there are no currently open issues that fit your problem's description, feel free to [add it](https://github.com/FlaxEngine/FlaxEngine/issues/new).
+
+* When writing an issue make sure to include a clear title and description as well as having filled out all the necessary information, depending on the severity of the issue also include the necessary log files and minidump.
+
+* Try to following the given template when writing a new issue if possible.
+
+## **Want to contribute?**
+
+* When creating a PR for fixing an issue/bug make sure to describe as to what led to the fix for better understanding, for small and obvious fixes this is not really needed.
+ However make sure to mention the relevant issue where it was first reported if possible.
+
+* For feature PR's the first thing you should evaluate is the value of your contribution, as in, what would it bring to this engine? Is it really required?
+ If its a small change you could preferably suggest it to us on our discord, else feel free to open up a PR for it.
+
+* Ensure when creating a PR that your contribution is well explained with a adequate description and title.
+
+* Generally, good code quality is expected, make sure your contribution works as intended and is appropriately commented where necessary.
+
+
+Thank you for taking interest in contributing to Flax!
diff --git a/Source/Editor/CustomEditors/Editors/ActorStaticFlagsEditor.cs b/Source/Editor/CustomEditors/Editors/ActorStaticFlagsEditor.cs
index 86dd45812..cf649552a 100644
--- a/Source/Editor/CustomEditors/Editors/ActorStaticFlagsEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/ActorStaticFlagsEditor.cs
@@ -23,7 +23,7 @@ namespace FlaxEditor.CustomEditors.Editors
///
protected override void OnValueChanged()
{
- var value = (StaticFlags)element.EnumComboBox.EnumTypeValue;
+ var value = (StaticFlags)element.ComboBox.EnumTypeValue;
// If selected is single actor that has children, ask if apply flags to the sub objects as well
if (Values.IsSingleObject && (StaticFlags)Values[0] != value && ParentEditor.Values[0] is Actor actor && actor.HasChildren)
diff --git a/Source/Editor/CustomEditors/Editors/EnumEditor.cs b/Source/Editor/CustomEditors/Editors/EnumEditor.cs
index e277324e5..7954e18bf 100644
--- a/Source/Editor/CustomEditors/Editors/EnumEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/EnumEditor.cs
@@ -40,7 +40,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
var enumType = Values.Type.Type != typeof(object) || Values[0] == null ? TypeUtils.GetType(Values.Type) : Values[0].GetType();
element = layout.Enum(enumType, null, mode);
- element.EnumComboBox.ValueChanged += OnValueChanged;
+ element.ComboBox.ValueChanged += OnValueChanged;
}
}
@@ -49,7 +49,7 @@ namespace FlaxEditor.CustomEditors.Editors
///
protected virtual void OnValueChanged()
{
- SetValue(element.EnumComboBox.EnumTypeValue);
+ SetValue(element.ComboBox.EnumTypeValue);
}
///
@@ -63,7 +63,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
- element.EnumComboBox.EnumTypeValue = Values[0];
+ element.ComboBox.EnumTypeValue = Values[0];
}
}
}
diff --git a/Source/Editor/CustomEditors/Elements/EnumElement.cs b/Source/Editor/CustomEditors/Elements/EnumElement.cs
index 27b4dfdb9..be4085f40 100644
--- a/Source/Editor/CustomEditors/Elements/EnumElement.cs
+++ b/Source/Editor/CustomEditors/Elements/EnumElement.cs
@@ -16,7 +16,7 @@ namespace FlaxEditor.CustomEditors.Elements
///
/// The combo box used to show enum values.
///
- public EnumComboBox EnumComboBox;
+ public EnumComboBox ComboBox;
///
/// Initializes a new instance of the class.
@@ -26,10 +26,10 @@ namespace FlaxEditor.CustomEditors.Elements
/// The formatting mode.
public EnumElement(Type type, EnumComboBox.BuildEntriesDelegate customBuildEntriesDelegate = null, EnumDisplayAttribute.FormatMode formatMode = EnumDisplayAttribute.FormatMode.Default)
{
- EnumComboBox = new EnumComboBox(type, customBuildEntriesDelegate, formatMode);
+ ComboBox = new EnumComboBox(type, customBuildEntriesDelegate, formatMode);
}
///
- public override Control Control => EnumComboBox;
+ public override Control Control => ComboBox;
}
}
diff --git a/Source/Editor/GUI/Input/UIntValueBox.cs b/Source/Editor/GUI/Input/UIntValueBox.cs
index 9f1b2c1f4..d691be116 100644
--- a/Source/Editor/GUI/Input/UIntValueBox.cs
+++ b/Source/Editor/GUI/Input/UIntValueBox.cs
@@ -143,6 +143,8 @@ namespace FlaxEditor.GUI.Input
try
{
var value = ShuntingYard.Parse(Text);
+ if (value < 0)
+ value = 0;
Value = (uint)value;
}
catch (Exception ex)
diff --git a/Source/Editor/Options/ViewportOptions.cs b/Source/Editor/Options/ViewportOptions.cs
index 6e1f8a9d6..5558ac89d 100644
--- a/Source/Editor/Options/ViewportOptions.cs
+++ b/Source/Editor/Options/ViewportOptions.cs
@@ -45,5 +45,12 @@ namespace FlaxEditor.Options
[DefaultValue(60.0f), Limit(35.0f, 160.0f, 0.1f)]
[EditorDisplay("Defaults", "Default Field Of View"), EditorOrder(140), Tooltip("The default field of view angle (in degrees) for the viewport camera.")]
public float DefaultFieldOfView { get; set; } = 60.0f;
+
+ ///
+ /// Gets or sets if the panning direction is inverted for the viewport camera.
+ ///
+ [DefaultValue(false)]
+ [EditorDisplay("Defaults"), EditorOrder(150), Tooltip( "Invert the panning direction for the viewport camera." )]
+ public bool DefaultInvertPanning { get; set; } = false;
}
}
diff --git a/Source/Editor/Surface/Archetypes/Animation.cs b/Source/Editor/Surface/Archetypes/Animation.cs
index 2ec196e13..e067dceb4 100644
--- a/Source/Editor/Surface/Archetypes/Animation.cs
+++ b/Source/Editor/Surface/Archetypes/Animation.cs
@@ -791,7 +791,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Transform Node (local space)",
Description = "Transforms the skeleton node",
Flags = NodeFlags.AnimGraph,
- Size = new Vector2(270, 130),
+ Size = new Vector2(280, 130),
DefaultValues = new object[]
{
string.Empty,
@@ -816,7 +816,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Transform Node (model space)",
Description = "Transforms the skeleton node",
Flags = NodeFlags.AnimGraph,
- Size = new Vector2(270, 130),
+ Size = new Vector2(280, 130),
DefaultValues = new object[]
{
string.Empty,
@@ -872,7 +872,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Get Node Transform (model space)",
Description = "Samples the skeleton node transformation (in model space)",
Flags = NodeFlags.AnimGraph,
- Size = new Vector2(250, 40),
+ Size = new Vector2(324, 40),
DefaultValues = new object[]
{
string.Empty,
@@ -880,9 +880,10 @@ namespace FlaxEditor.Surface.Archetypes
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0),
- NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 1, 120, 0),
+ NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 1, 160, 0),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 1, "Node:"),
- NodeElementArchetype.Factory.Output(0, "Transform", typeof(Transform), 1),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0),
+ NodeElementArchetype.Factory.Output(1, "Transform", typeof(Transform), 1),
}
},
new NodeArchetype
@@ -903,7 +904,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 1),
NodeElementArchetype.Factory.Input(1, "Target", true, typeof(Vector3), 2),
NodeElementArchetype.Factory.Input(2, "Weight", true, typeof(float), 3, 1),
- NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 3, 120, 0),
+ NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 3, 160, 0),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 3, "Node:"),
}
},
@@ -913,7 +914,7 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Get Node Transform (local space)",
Description = "Samples the skeleton node transformation (in local space)",
Flags = NodeFlags.AnimGraph,
- Size = new Vector2(250, 40),
+ Size = new Vector2(316, 40),
DefaultValues = new object[]
{
string.Empty,
@@ -923,7 +924,8 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0),
NodeElementArchetype.Factory.SkeletonNodeNameSelect(40, Surface.Constants.LayoutOffsetY * 1, 120, 0),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 1, "Node:"),
- NodeElementArchetype.Factory.Output(0, "Transform", typeof(Transform), 1),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0),
+ NodeElementArchetype.Factory.Output(1, "Transform", typeof(Transform), 1),
}
},
new NodeArchetype
diff --git a/Source/Editor/Surface/Archetypes/Constants.cs b/Source/Editor/Surface/Archetypes/Constants.cs
index 63cec366b..3ae9d6a8a 100644
--- a/Source/Editor/Surface/Archetypes/Constants.cs
+++ b/Source/Editor/Surface/Archetypes/Constants.cs
@@ -379,7 +379,7 @@ namespace FlaxEditor.Surface.Archetypes
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Value", typeof(uint), 0),
- NodeElementArchetype.Factory.Integer(0, 0, 0)
+ NodeElementArchetype.Factory.UnsignedInteger(0, 0, 0, -1, 0, int.MaxValue)
}
},
};
diff --git a/Source/Editor/Surface/Archetypes/Function.cs b/Source/Editor/Surface/Archetypes/Function.cs
index 80b3d07b9..c5707d1dd 100644
--- a/Source/Editor/Surface/Archetypes/Function.cs
+++ b/Source/Editor/Surface/Archetypes/Function.cs
@@ -868,8 +868,15 @@ namespace FlaxEditor.Surface.Archetypes
for (int i = 0; i < signature.Params.Length; i++)
{
ref var param = ref signature.Params[i];
- if (param.Type != memberParameters[i].Type || param.IsOut != memberParameters[i].IsOut)
+ ref var paramMember = ref memberParameters[i];
+ if (param.Type != paramMember.Type || param.IsOut != paramMember.IsOut)
{
+ // Special case: param.Type is serialized as just a type while paramMember.Type might be a reference for output parameters (eg. `out Int32` vs `out Int32&`)
+ var paramMemberTypeName = paramMember.Type.TypeName;
+ if (param.IsOut && param.IsOut == paramMember.IsOut && paramMember.Type.IsReference && !param.Type.IsReference &&
+ paramMemberTypeName.Substring(0, paramMemberTypeName.Length - 1) == param.Type.TypeName)
+ continue;
+
isInvalid = true;
break;
}
diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs
index a007db7d7..09fd8e5fa 100644
--- a/Source/Editor/Surface/Archetypes/Material.cs
+++ b/Source/Editor/Surface/Archetypes/Material.cs
@@ -642,6 +642,85 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, "XYZ", typeof(Vector3), 0),
}
},
+ new NodeArchetype
+ {
+ TypeID = 26,
+ Title = "Blend Normals",
+ Description = "Blend two normal maps to create a single normal map",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Vector2(170, 40),
+ ConnectionsHints = ConnectionsHint.Vector,
+ Elements = new[]
+ {
+ NodeElementArchetype.Factory.Input(0, "Base Normal", true, typeof(Vector3), 0),
+ NodeElementArchetype.Factory.Input(1, "Additional Normal", true, typeof(Vector3), 1),
+ NodeElementArchetype.Factory.Output(0, "Result", typeof(Vector3), 2)
+ }
+ },
+ new NodeArchetype
+ {
+ TypeID = 27,
+ Title = "Rotator",
+ Description = "Rotates UV coordinates according to a scalar angle (0-1)",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Vector2(150, 55),
+ Elements = new[]
+ {
+ NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Vector2), 0),
+ NodeElementArchetype.Factory.Input(1, "Center", true, typeof(Vector2), 1),
+ NodeElementArchetype.Factory.Input(2, "Rotation Angle", true, typeof(float), 2),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Vector2), 3),
+ }
+ },
+ new NodeArchetype
+ {
+ TypeID = 28,
+ Title = "Sphere Mask",
+ Description = "Creates a sphere mask",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Vector2(150, 100),
+ ConnectionsHints = ConnectionsHint.Vector,
+ IndependentBoxes = new[]
+ {
+ 0,
+ 1
+ },
+ DefaultValues = new object[]
+ {
+ 0.3f,
+ 0.5f,
+ false
+ },
+ Elements = new[]
+ {
+ NodeElementArchetype.Factory.Input(0, "A", true, null, 0),
+ NodeElementArchetype.Factory.Input(1, "B", true, null, 1),
+ NodeElementArchetype.Factory.Input(2, "Radius", true, typeof(float), 2, 0),
+ NodeElementArchetype.Factory.Input(3, "Hardness", true, typeof(float), 3, 1),
+ NodeElementArchetype.Factory.Input(4, "Invert", true, typeof(bool), 4, 2),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 5),
+ }
+ },
+ new NodeArchetype
+ {
+ TypeID = 29,
+ Title = "UV Tiling & Offset",
+ Description = "Takes UVs and applies tiling and offset",
+ Flags = NodeFlags.MaterialGraph,
+ Size = new Vector2(175, 60),
+ DefaultValues = new object[]
+ {
+ Vector2.One,
+ Vector2.Zero
+ },
+ Elements = new[]
+ {
+ NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Vector2), 0),
+ NodeElementArchetype.Factory.Input(1, "Tiling", true, typeof(Vector2), 1, 0),
+ NodeElementArchetype.Factory.Input(2, "Offset", true, typeof(Vector2), 2, 1),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Vector2), 3),
+ }
+ }
};
}
}
diff --git a/Source/Editor/Surface/Archetypes/Math.cs b/Source/Editor/Surface/Archetypes/Math.cs
index 92ae69a12..7416ea498 100644
--- a/Source/Editor/Surface/Archetypes/Math.cs
+++ b/Source/Editor/Surface/Archetypes/Math.cs
@@ -404,6 +404,29 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, "A | B", null, 2),
}
},
+ new NodeArchetype
+ {
+ TypeID = 48,
+ Title = "Remap",
+ Description = "Remaps a value from one range to another, so for example having 25 in a range of 0 to 100 being remapped to 0 to 1 would return 0.25",
+ Flags = NodeFlags.AllGraphs,
+ Size = new Vector2(175, 75),
+ DefaultValues = new object[]
+ {
+ 25.0f,
+ new Vector2(0.0f, 100.0f),
+ new Vector2(0.0f, 1.0f),
+ false
+ },
+ Elements = new[]
+ {
+ NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0, 0),
+ NodeElementArchetype.Factory.Input(1, "In Range", true, typeof(Vector2), 1, 1),
+ NodeElementArchetype.Factory.Input(2, "Out Range", true, typeof(Vector2), 2, 2),
+ NodeElementArchetype.Factory.Input(3, "Clamp", true, typeof(bool), 3, 3),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 4),
+ }
+ },
};
}
}
diff --git a/Source/Editor/Surface/Archetypes/Packing.cs b/Source/Editor/Surface/Archetypes/Packing.cs
index 04f3819ea..f5b466ab3 100644
--- a/Source/Editor/Surface/Archetypes/Packing.cs
+++ b/Source/Editor/Surface/Archetypes/Packing.cs
@@ -486,7 +486,7 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Unpack X component from Vector",
Flags = NodeFlags.AllGraphs,
ConnectionsHints = ConnectionsHint.Vector,
- Size = new Vector2(160, 30),
+ Size = new Vector2(110, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
@@ -500,7 +500,7 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Unpack Y component from Vector",
Flags = NodeFlags.AllGraphs,
ConnectionsHints = ConnectionsHint.Vector,
- Size = new Vector2(160, 30),
+ Size = new Vector2(110, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
@@ -514,7 +514,7 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Unpack Z component from Vector",
Flags = NodeFlags.AllGraphs,
ConnectionsHints = ConnectionsHint.Vector,
- Size = new Vector2(160, 30),
+ Size = new Vector2(110, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
@@ -528,7 +528,7 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Unpack W component from Vector",
Flags = NodeFlags.AllGraphs,
ConnectionsHints = ConnectionsHint.Vector,
- Size = new Vector2(160, 30),
+ Size = new Vector2(110, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
@@ -544,7 +544,7 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Unpack XY components from Vector",
Flags = NodeFlags.AllGraphs,
ConnectionsHints = ConnectionsHint.Vector,
- Size = new Vector2(160, 30),
+ Size = new Vector2(110, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
@@ -558,7 +558,7 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Unpack XZ components from Vector",
Flags = NodeFlags.AllGraphs,
ConnectionsHints = ConnectionsHint.Vector,
- Size = new Vector2(160, 30),
+ Size = new Vector2(110, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
@@ -572,7 +572,7 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Unpack YZ components from Vector",
Flags = NodeFlags.AllGraphs,
ConnectionsHints = ConnectionsHint.Vector,
- Size = new Vector2(160, 30),
+ Size = new Vector2(110, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
@@ -588,7 +588,7 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Unpack XYZ components from Vector",
Flags = NodeFlags.AllGraphs,
ConnectionsHints = ConnectionsHint.Vector,
- Size = new Vector2(160, 30),
+ Size = new Vector2(110, 30),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Value", true, null, 0),
diff --git a/Source/Editor/Surface/Elements/Box.cs b/Source/Editor/Surface/Elements/Box.cs
index 886ab1e37..b8d88741f 100644
--- a/Source/Editor/Surface/Elements/Box.cs
+++ b/Source/Editor/Surface/Elements/Box.cs
@@ -97,7 +97,7 @@ namespace FlaxEditor.Surface.Elements
var connections = Connections.ToArray();
for (int i = 0; i < connections.Length; i++)
{
- var targetBox = Connections[i];
+ var targetBox = connections[i];
// Break connection
Connections.Remove(targetBox);
@@ -565,7 +565,28 @@ namespace FlaxEditor.Surface.Elements
{
_isMouseDown = false;
if (Surface.CanEdit)
- Surface.ConnectingStart(this);
+ {
+ if (!IsOutput && HasSingleConnection)
+ {
+ var connectedBox = Connections[0];
+ if (Surface.Undo != null)
+ {
+ var action = new ConnectBoxesAction((InputBox)this, (OutputBox)connectedBox, false);
+ BreakConnection(connectedBox);
+ action.End();
+ Surface.Undo.AddAction(action);
+ }
+ else
+ {
+ BreakConnection(connectedBox);
+ }
+ Surface.ConnectingStart(connectedBox);
+ }
+ else
+ {
+ Surface.ConnectingStart(this);
+ }
+ }
}
base.OnMouseLeave();
}
diff --git a/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs b/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs
index 9668105f1..a1822a268 100644
--- a/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs
+++ b/Source/Editor/Surface/Elements/UnsignedIntegerValue.cs
@@ -85,7 +85,7 @@ namespace FlaxEditor.Surface.Elements
else if (value is Vector4 valueVec4)
result = (uint)(arch.BoxID == 0 ? valueVec4.X : arch.BoxID == 1 ? valueVec4.Y : arch.BoxID == 2 ? valueVec4.Z : valueVec4.W);
else
- result = 0;
+ result = 0u;
return result;
}
diff --git a/Source/Editor/Surface/NodeElementArchetype.cs b/Source/Editor/Surface/NodeElementArchetype.cs
index 07ea35875..cc2f41106 100644
--- a/Source/Editor/Surface/NodeElementArchetype.cs
+++ b/Source/Editor/Surface/NodeElementArchetype.cs
@@ -239,6 +239,32 @@ namespace FlaxEditor.Surface
};
}
+ ///
+ /// Creates new Unsigned Integer value element description.
+ ///
+ /// The x location (in node area space).
+ /// The y location (in node area space).
+ /// The index of the node variable linked as the input. Useful to make a physical connection between input box and default value for it.
+ /// The index of the component to edit. For vectors this can be set to modify only single component of it. Eg. for vec2 value component set to 1 will edit only Y component. Default value -1 will be used to edit whole value.
+ /// The minimum value range.
+ /// The maximum value range.
+ /// The archetype.
+ public static NodeElementArchetype UnsignedInteger(float x, float y, int valueIndex = -1, int component = -1, uint valueMin = 0, uint valueMax = 1000000)
+ {
+ return new NodeElementArchetype
+ {
+ Type = NodeElementType.UnsignedIntegerValue,
+ Position = new Vector2(Constants.NodeMarginX + x, Constants.NodeMarginY + Constants.NodeHeaderSize + y),
+ Text = null,
+ Single = false,
+ ValueIndex = valueIndex,
+ ValueMin = valueMin,
+ ValueMax = valueMax,
+ BoxID = -1,
+ ConnectionsType = ScriptType.Null
+ };
+ }
+
///
/// Creates new Float value element description.
///
diff --git a/Source/Editor/Surface/NodeElementType.cs b/Source/Editor/Surface/NodeElementType.cs
index 48b2356dd..f104b234c 100644
--- a/Source/Editor/Surface/NodeElementType.cs
+++ b/Source/Editor/Surface/NodeElementType.cs
@@ -94,5 +94,10 @@ namespace FlaxEditor.Surface
/// The actor picker.
///
Actor = 19,
+
+ ///
+ /// The unsigned integer value.
+ ///
+ UnsignedIntegerValue = 20,
}
}
diff --git a/Source/Editor/Surface/SurfaceNode.cs b/Source/Editor/Surface/SurfaceNode.cs
index ae094c2be..bdce94513 100644
--- a/Source/Editor/Surface/SurfaceNode.cs
+++ b/Source/Editor/Surface/SurfaceNode.cs
@@ -278,6 +278,9 @@ namespace FlaxEditor.Surface
case NodeElementType.Actor:
element = new ActorSelect(this, arch);
break;
+ case NodeElementType.UnsignedIntegerValue:
+ element = new UnsignedIntegerValue(this, arch);
+ break;
//default: throw new NotImplementedException("Unknown node element type: " + arch.Type);
}
if (element != null)
@@ -314,12 +317,16 @@ namespace FlaxEditor.Surface
{
if (type == ScriptType.Null)
type = new ScriptType(typeof(object));
+
+ // Try to reuse box
var box = GetBox(id);
if ((isOut && box is InputBox) || (!isOut && box is OutputBox))
{
box.Dispose();
box = null;
}
+
+ // Create new if missing
if (box == null)
{
if (isOut)
@@ -330,10 +337,15 @@ namespace FlaxEditor.Surface
}
else
{
+ // Sync properties for exiting box
box.Text = text;
box.CurrentType = type;
box.Y = Constants.NodeMarginY + Constants.NodeHeaderSize + yLevel * Constants.LayoutOffsetY;
}
+
+ // Update box
+ box.OnConnectionsChanged();
+
return box;
}
diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
index 6b38ffdfc..c2b0d1887 100644
--- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
+++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs
@@ -15,6 +15,7 @@ namespace FlaxEditor.Surface
{
private ContextMenuButton _cmCopyButton;
private ContextMenuButton _cmDuplicateButton;
+ private ContextMenuButton _cmFormatNodesConnectionButton;
private ContextMenuButton _cmRemoveNodeConnectionsButton;
private ContextMenuButton _cmRemoveBoxConnectionsButton;
private readonly Vector2 ContextMenuOffset = new Vector2(5);
@@ -216,6 +217,13 @@ namespace FlaxEditor.Surface
}
}).Enabled = Nodes.Any(x => x.Breakpoint.Set && x.Breakpoint.Enabled);
}
+ menu.AddSeparator();
+
+ _cmFormatNodesConnectionButton = menu.AddButton("Format node(s)", () =>
+ {
+ FormatGraph(SelectedNodes);
+ });
+ _cmFormatNodesConnectionButton.Enabled = HasNodesSelection;
menu.AddSeparator();
_cmRemoveNodeConnectionsButton = menu.AddButton("Remove all connections to that node(s)", () =>
diff --git a/Source/Editor/Surface/VisjectSurface.Formatting.cs b/Source/Editor/Surface/VisjectSurface.Formatting.cs
new file mode 100644
index 000000000..848f79157
--- /dev/null
+++ b/Source/Editor/Surface/VisjectSurface.Formatting.cs
@@ -0,0 +1,287 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using FlaxEngine;
+using FlaxEditor.Surface.Elements;
+using FlaxEditor.Surface.Undo;
+
+namespace FlaxEditor.Surface
+{
+ public partial class VisjectSurface
+ {
+ // Reference https://github.com/stefnotch/xnode-graph-formatter/blob/812e08e71c7b9b7eb0810dbdfb0a9a1034da6941/Assets/Examples/MathGraph/Editor/MathGraphEditor.cs
+
+ private class NodeFormattingData
+ {
+ ///
+ /// Starting from 0 at the main nodes
+ ///
+ public int Layer;
+
+ ///
+ /// Position in the layer
+ ///
+ public int Offset;
+
+ ///
+ /// How far the subtree needs to be moved additionally
+ ///
+ public int SubtreeOffset;
+ }
+
+ ///
+ /// Formats a graph where the nodes can be disjointed.
+ /// Uses the Sugiyama method
+ ///
+ /// List of nodes
+ protected void FormatGraph(List nodes)
+ {
+ if (nodes.Count <= 1 || !CanEdit) return;
+
+ var nodesToVisit = new HashSet(nodes);
+
+ // While we haven't formatted every node
+ while (nodesToVisit.Count > 0)
+ {
+ // Run a search in both directions
+ var connectedNodes = new List();
+ var queue = new Queue();
+
+ var startNode = nodesToVisit.First();
+ nodesToVisit.Remove(startNode);
+ queue.Enqueue(startNode);
+
+ while (queue.Count > 0)
+ {
+ var node = queue.Dequeue();
+ connectedNodes.Add(node);
+
+ for (int i = 0; i < node.Elements.Count; i++)
+ {
+ if (node.Elements[i] is Box box)
+ {
+ for (int j = 0; j < box.Connections.Count; j++)
+ {
+ if (nodesToVisit.Contains(box.Connections[j].ParentNode))
+ {
+ nodesToVisit.Remove(box.Connections[j].ParentNode);
+ queue.Enqueue(box.Connections[j].ParentNode);
+ }
+ }
+
+ }
+ }
+ }
+
+ FormatConnectedGraph(connectedNodes);
+ }
+ }
+
+ ///
+ /// Formats a graph where all nodes are connected
+ ///
+ /// List of connected nodes
+ protected void FormatConnectedGraph(List nodes)
+ {
+ if (nodes.Count <= 1 || !CanEdit) return;
+
+ var boundingBox = GetNodesBounds(nodes);
+
+ var nodeData = nodes.ToDictionary(n => n, n => new NodeFormattingData { });
+
+ // Rightmost nodes with none of our nodes to the right of them
+ var endNodes = nodes
+ .Where(n => !n.GetBoxes().Any(b => b.IsOutput && b.Connections.Any(c => nodeData.ContainsKey(c.ParentNode))))
+ .OrderBy(n => n.Top) // Keep their relative order
+ .ToList();
+
+ // Longest path layering
+ int maxLayer = SetLayers(nodeData, endNodes);
+
+ // Set the vertical offsets
+ int maxOffset = SetOffsets(nodeData, endNodes, maxLayer);
+
+ // Layout the nodes
+
+ // Get the largest nodes in the Y and X direction
+ float[] widths = new float[maxLayer + 1];
+ float[] heights = new float[maxOffset + 1];
+ for (int i = 0; i < nodes.Count; i++)
+ {
+ if (nodeData.TryGetValue(nodes[i], out var data))
+ {
+ if (nodes[i].Width > widths[data.Layer])
+ {
+ widths[data.Layer] = nodes[i].Width;
+ }
+ if (nodes[i].Height > heights[data.Offset])
+ {
+ heights[data.Offset] = nodes[i].Height;
+ }
+ }
+ }
+
+ Vector2 minDistanceBetweenNodes = new Vector2(30, 30);
+
+ // Figure out the node positions (aligned to a grid)
+ float[] nodeXPositions = new float[widths.Length];
+ for (int i = 1; i < widths.Length; i++)
+ {
+ // Go from right to left (backwards) through the nodes
+ nodeXPositions[i] = nodeXPositions[i - 1] + minDistanceBetweenNodes.X + widths[i];
+ }
+
+ float[] nodeYPositions = new float[heights.Length];
+ for (int i = 1; i < heights.Length; i++)
+ {
+ // Go from top to bottom through the nodes
+ nodeYPositions[i] = nodeYPositions[i - 1] + heights[i - 1] + minDistanceBetweenNodes.Y;
+ }
+
+ // Set the node positions
+ var undoActions = new List();
+ var topRightPosition = endNodes[0].Location;
+ for (int i = 0; i < nodes.Count; i++)
+ {
+ if (nodeData.TryGetValue(nodes[i], out var data))
+ {
+ Vector2 newLocation = new Vector2(-nodeXPositions[data.Layer], nodeYPositions[data.Offset]) + topRightPosition;
+ Vector2 locationDelta = newLocation - nodes[i].Location;
+ nodes[i].Location = newLocation;
+
+ if (Undo != null)
+ undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta));
+ }
+ }
+
+ MarkAsEdited(false);
+ Undo?.AddAction(new MultiUndoAction(undoActions, "Format nodes"));
+ }
+
+ ///
+ /// Assigns a layer to every node
+ ///
+ /// The exta node data
+ /// The end nodes
+ /// The number of the maximum layer
+ private int SetLayers(Dictionary nodeData, List endNodes)
+ {
+ // Longest path layering
+ int maxLayer = 0;
+ var stack = new Stack(endNodes);
+
+ while (stack.Count > 0)
+ {
+ var node = stack.Pop();
+ int layer = nodeData[node].Layer;
+
+ for (int i = 0; i < node.Elements.Count; i++)
+ {
+ if (node.Elements[i] is InputBox box && box.HasAnyConnection)
+ {
+ var childNode = box.Connections[0].ParentNode;
+
+ if (nodeData.TryGetValue(childNode, out var data))
+ {
+ int nodeLayer = Math.Max(data.Layer, layer + 1);
+ data.Layer = nodeLayer;
+ if (nodeLayer > maxLayer)
+ {
+ maxLayer = nodeLayer;
+ }
+
+ stack.Push(childNode);
+ }
+ }
+ }
+ }
+ return maxLayer;
+ }
+
+
+ ///
+ /// Sets the node offsets
+ ///
+ /// The exta node data
+ /// The end nodes
+ /// The number of the maximum layer
+ /// The number of the maximum offset
+ private int SetOffsets(Dictionary nodeData, List endNodes, int maxLayer)
+ {
+ int maxOffset = 0;
+
+ // Keeps track of the largest offset (Y axis) for every layer
+ int[] offsets = new int[maxLayer + 1];
+
+ var visitedNodes = new HashSet();
+
+ void SetOffsets(SurfaceNode node, NodeFormattingData straightParentData)
+ {
+ if (!nodeData.TryGetValue(node, out var data)) return;
+
+ // If we realize that the current node would collide with an already existing node in this layer
+ if (data.Layer >= 0 && offsets[data.Layer] > data.Offset)
+ {
+ // Move the entire sub-tree down
+ straightParentData.SubtreeOffset = Math.Max(straightParentData.SubtreeOffset, offsets[data.Layer] - data.Offset);
+ }
+
+ // Keeps track of the offset of the last direct child we visited
+ int childOffset = data.Offset;
+ bool straightChild = true;
+
+ // Run the algorithm for every child
+ for (int i = 0; i < node.Elements.Count; i++)
+ {
+ if (node.Elements[i] is InputBox box && box.HasAnyConnection)
+ {
+ var childNode = box.Connections[0].ParentNode;
+ if (!visitedNodes.Contains(childNode) && nodeData.TryGetValue(childNode, out var childData))
+ {
+ visitedNodes.Add(childNode);
+ childData.Offset = childOffset;
+ SetOffsets(childNode, straightChild ? straightParentData : childData);
+ childOffset = childData.Offset + 1;
+ straightChild = false;
+ }
+ }
+ }
+
+ if (data.Layer >= 0)
+ {
+ // When coming out of the recursion, apply the extra subtree offsets
+ data.Offset += straightParentData.SubtreeOffset;
+ if (data.Offset > maxOffset)
+ {
+ maxOffset = data.Offset;
+ }
+ offsets[data.Layer] = data.Offset + 1;
+ }
+ }
+
+ {
+ // An imaginary final node
+ var endNodeData = new NodeFormattingData { Layer = -1 };
+ int childOffset = 0;
+ bool straightChild = true;
+
+ for (int i = 0; i < endNodes.Count; i++)
+ {
+ if (nodeData.TryGetValue(endNodes[i], out var childData))
+ {
+ visitedNodes.Add(endNodes[i]);
+ childData.Offset = childOffset;
+ SetOffsets(endNodes[i], straightChild ? endNodeData : childData);
+ childOffset = childData.Offset + 1;
+ straightChild = false;
+ }
+ }
+ }
+
+ return maxOffset;
+ }
+
+ }
+}
diff --git a/Source/Editor/Surface/VisjectSurface.Input.cs b/Source/Editor/Surface/VisjectSurface.Input.cs
index 68966370c..dec89e143 100644
--- a/Source/Editor/Surface/VisjectSurface.Input.cs
+++ b/Source/Editor/Surface/VisjectSurface.Input.cs
@@ -24,7 +24,9 @@ namespace FlaxEditor.Surface
private class InputBracket
{
+ private readonly float DefaultWidth = 120f;
private readonly Margin _padding = new Margin(10f);
+
public Box Box { get; }
public Vector2 EndBracketPosition { get; }
public List Nodes { get; } = new List();
@@ -33,7 +35,7 @@ namespace FlaxEditor.Surface
public InputBracket(Box box, Vector2 nodePosition)
{
Box = box;
- EndBracketPosition = nodePosition;
+ EndBracketPosition = nodePosition + new Vector2(DefaultWidth, 0);
Update();
}
@@ -47,11 +49,10 @@ namespace FlaxEditor.Surface
}
else
{
- area = new Rectangle(EndBracketPosition, new Vector2(120f, 80f));
+ area = new Rectangle(EndBracketPosition, new Vector2(DefaultWidth, 80f));
}
_padding.ExpandRectangle(ref area);
- Vector2 endPoint = area.Location + new Vector2(area.Width, area.Height / 2f);
- Vector2 offset = EndBracketPosition - endPoint;
+ Vector2 offset = EndBracketPosition - area.UpperRight;
area.Location += offset;
Area = area;
if (!offset.IsZero)
@@ -99,18 +100,6 @@ namespace FlaxEditor.Surface
///
public event Window.MouseWheelDelegate CustomMouseWheel;
- ///
- /// Gets the node under the mouse location.
- ///
- /// The node or null if no intersection.
- public SurfaceNode GetNodeUnderMouse()
- {
- var pos = _rootControl.PointFromParent(ref _mousePos);
- if (_rootControl.GetChildAt(pos) is SurfaceNode node)
- return node;
- return null;
- }
-
///
/// Gets the control under the mouse location.
///
@@ -118,9 +107,7 @@ namespace FlaxEditor.Surface
public SurfaceControl GetControlUnderMouse()
{
var pos = _rootControl.PointFromParent(ref _mousePos);
- if (_rootControl.GetChildAtRecursive(pos) is SurfaceControl control)
- return control;
- return null;
+ return _rootControl.GetChildAt(pos) as SurfaceControl;
}
private void UpdateSelectionRectangle()
@@ -464,15 +451,7 @@ namespace FlaxEditor.Surface
{
// Check if any control is under the mouse
_cmStartPos = location;
- if (controlUnderMouse != null)
- {
- if (!HasNodesSelection)
- Select(controlUnderMouse);
-
- // Show secondary context menu
- ShowSecondaryCM(_cmStartPos, controlUnderMouse);
- }
- else
+ if (controlUnderMouse == null)
{
// Show primary context menu
ShowPrimaryMenu(_cmStartPos);
@@ -708,31 +687,38 @@ namespace FlaxEditor.Surface
private Vector2 FindEmptySpace(Box box)
{
- int boxIndex = 0;
+ Vector2 distanceBetweenNodes = new Vector2(30, 30);
var node = box.ParentNode;
+
+ // Same height as node
+ float yLocation = node.Top;
+
for (int i = 0; i < node.Elements.Count; i++)
{
- // Box on the same side above the current box
if (node.Elements[i] is Box nodeBox && nodeBox.IsOutput == box.IsOutput && nodeBox.Y < box.Y)
{
- boxIndex++;
+ // Below connected node
+ yLocation = Mathf.Max(yLocation, nodeBox.ParentNode.Bottom + distanceBetweenNodes.Y);
}
}
+
// TODO: Dodge the other nodes
- Vector2 distanceBetweenNodes = new Vector2(40, 20);
- const float NodeHeight = 120;
+ float xLocation = node.Location.X;
+ if (box.IsOutput)
+ {
+ xLocation += node.Width + distanceBetweenNodes.X;
+ }
+ else
+ {
+ xLocation += -120 - distanceBetweenNodes.X;
+ }
- float direction = box.IsOutput ? 1 : -1;
-
- Vector2 newNodeLocation = node.Location +
- new Vector2(
- (node.Width + distanceBetweenNodes.X) * direction,
- boxIndex * (NodeHeight + distanceBetweenNodes.Y)
- );
-
- return newNodeLocation;
+ return new Vector2(
+ xLocation,
+ yLocation
+ );
}
}
}
diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs
index 8ed219449..b5098696d 100644
--- a/Source/Editor/Viewport/Cameras/FPSCamera.cs
+++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs
@@ -188,8 +188,16 @@ namespace FlaxEditor.Viewport.Cameras
if (input.IsPanning)
{
var panningSpeed = 0.8f;
- position -= right * (mouseDelta.X * panningSpeed);
- position -= up * (mouseDelta.Y * panningSpeed);
+ if (Viewport.InvertPanning)
+ {
+ position += up * (mouseDelta.Y * panningSpeed);
+ position += right * (mouseDelta.X * panningSpeed);
+ }
+ else
+ {
+ position -= right * (mouseDelta.X * panningSpeed);
+ position -= up * (mouseDelta.Y * panningSpeed);
+ }
}
// Move
diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs
index 0b810e891..a9a10c2d7 100644
--- a/Source/Editor/Viewport/EditorViewport.cs
+++ b/Source/Editor/Viewport/EditorViewport.cs
@@ -180,6 +180,7 @@ namespace FlaxEditor.Viewport
private float _orthoSize = 1.0f;
private bool _isOrtho = false;
private float _wheelMovementChangeDeltaSum = 0;
+ private bool _invertPanning;
///
/// Speed of the mouse.
@@ -403,6 +404,15 @@ namespace FlaxEditor.Viewport
set => _isOrtho = value;
}
+ ///
+ /// Gets or sets if the panning direction is inverted.
+ ///
+ public bool InvertPanning
+ {
+ get => _invertPanning;
+ set => _invertPanning = value;
+ }
+
///
/// The input actions collection to processed during user input.
///
@@ -434,6 +444,7 @@ namespace FlaxEditor.Viewport
_nearPlane = options.Viewport.DefaultNearPlane;
_farPlane = options.Viewport.DefaultFarPlane;
_fieldOfView = options.Viewport.DefaultFieldOfView;
+ _invertPanning = options.Viewport.DefaultInvertPanning;
Editor.Instance.Options.OptionsChanged += OnEditorOptionsChanged;
OnEditorOptionsChanged(options);
@@ -454,7 +465,7 @@ namespace FlaxEditor.Viewport
var button = camSpeedCM.AddButton(v.ToString());
button.Tag = v;
}
- camSpeedCM.ButtonClicked += (button) => MovementSpeed = (float)button.Tag;
+ camSpeedCM.ButtonClicked += button => MovementSpeed = (float)button.Tag;
camSpeedCM.VisibleChanged += WidgetCamSpeedShowHide;
camSpeedButton.Parent = camSpeed;
camSpeed.Parent = this;
@@ -515,9 +526,9 @@ namespace FlaxEditor.Viewport
// Orthographic
{
var ortho = ViewWidgetButtonMenu.AddButton("Orthographic");
- var orthoValue = new CheckBox(75, 2, _isOrtho);
+ var orthoValue = new CheckBox(90, 2, _isOrtho);
orthoValue.Parent = ortho;
- orthoValue.StateChanged += (checkBox) =>
+ orthoValue.StateChanged += checkBox =>
{
if (checkBox.Checked != _isOrtho)
{
@@ -543,10 +554,10 @@ namespace FlaxEditor.Viewport
// Field of View
{
var fov = ViewWidgetButtonMenu.AddButton("Field Of View");
- var fovValue = new FloatValueBox(1, 75, 2, 50.0f, 35.0f, 160.0f, 0.1f);
+ var fovValue = new FloatValueBox(1, 90, 2, 70.0f, 35.0f, 160.0f, 0.1f);
fovValue.Parent = fov;
fovValue.ValueChanged += () => _fieldOfView = fovValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += (control) =>
+ ViewWidgetButtonMenu.VisibleChanged += control =>
{
fov.Visible = !_isOrtho;
fovValue.Value = _fieldOfView;
@@ -556,10 +567,10 @@ namespace FlaxEditor.Viewport
// Ortho Scale
{
var orthoSize = ViewWidgetButtonMenu.AddButton("Ortho Scale");
- var orthoSizeValue = new FloatValueBox(_orthoSize, 75, 2, 50.0f, 0.001f, 100000.0f, 0.01f);
+ var orthoSizeValue = new FloatValueBox(_orthoSize, 90, 2, 70.0f, 0.001f, 100000.0f, 0.01f);
orthoSizeValue.Parent = orthoSize;
orthoSizeValue.ValueChanged += () => _orthoSize = orthoSizeValue.Value;
- ViewWidgetButtonMenu.VisibleChanged += (control) =>
+ ViewWidgetButtonMenu.VisibleChanged += control =>
{
orthoSize.Visible = _isOrtho;
orthoSizeValue.Value = _orthoSize;
@@ -569,7 +580,7 @@ namespace FlaxEditor.Viewport
// Near Plane
{
var nearPlane = ViewWidgetButtonMenu.AddButton("Near Plane");
- var nearPlaneValue = new FloatValueBox(2.0f, 75, 2, 50.0f, 0.001f, 1000.0f);
+ var nearPlaneValue = new FloatValueBox(2.0f, 90, 2, 70.0f, 0.001f, 1000.0f);
nearPlaneValue.Parent = nearPlane;
nearPlaneValue.ValueChanged += () => _nearPlane = nearPlaneValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => nearPlaneValue.Value = _nearPlane;
@@ -578,7 +589,7 @@ namespace FlaxEditor.Viewport
// Far Plane
{
var farPlane = ViewWidgetButtonMenu.AddButton("Far Plane");
- var farPlaneValue = new FloatValueBox(1000, 75, 2, 50.0f, 10.0f);
+ var farPlaneValue = new FloatValueBox(1000, 90, 2, 70.0f, 10.0f);
farPlaneValue.Parent = farPlane;
farPlaneValue.ValueChanged += () => _farPlane = farPlaneValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => farPlaneValue.Value = _farPlane;
@@ -587,7 +598,7 @@ namespace FlaxEditor.Viewport
// Brightness
{
var brightness = ViewWidgetButtonMenu.AddButton("Brightness");
- var brightnessValue = new FloatValueBox(1.0f, 75, 2, 50.0f, 0.001f, 10.0f, 0.001f);
+ var brightnessValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.001f, 10.0f, 0.001f);
brightnessValue.Parent = brightness;
brightnessValue.ValueChanged += () => Brightness = brightnessValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => brightnessValue.Value = Brightness;
@@ -596,11 +607,26 @@ namespace FlaxEditor.Viewport
// Resolution
{
var resolution = ViewWidgetButtonMenu.AddButton("Resolution");
- var resolutionValue = new FloatValueBox(1.0f, 75, 2, 50.0f, 0.1f, 4.0f, 0.001f);
+ var resolutionValue = new FloatValueBox(1.0f, 90, 2, 70.0f, 0.1f, 4.0f, 0.001f);
resolutionValue.Parent = resolution;
resolutionValue.ValueChanged += () => ResolutionScale = resolutionValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => resolutionValue.Value = ResolutionScale;
}
+
+ // Invert Panning
+ {
+ var invert = ViewWidgetButtonMenu.AddButton("Invert Panning");
+ var invertValue = new CheckBox(90, 2, _invertPanning);
+ invertValue.Parent = invert;
+ invertValue.StateChanged += checkBox =>
+ {
+ if (checkBox.Checked != _invertPanning)
+ {
+ _invertPanning = checkBox.Checked;
+ }
+ };
+ ViewWidgetButtonMenu.VisibleChanged += control => invertValue.Checked = _invertPanning;
+ }
}
// Link for task event
diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
index 24ae53261..b85895528 100644
--- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs
@@ -84,7 +84,7 @@ namespace FlaxEditor.Viewport.Previews
// Preview LOD
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
- var previewLODValue = new IntValueBox(-1, 75, 2, 50.0f, -1, 10, 0.02f);
+ var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f);
previewLODValue.Parent = previewLOD;
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
diff --git a/Source/Editor/Viewport/Previews/ModelPreview.cs b/Source/Editor/Viewport/Previews/ModelPreview.cs
index e4789bdb8..aa03d0ee2 100644
--- a/Source/Editor/Viewport/Previews/ModelPreview.cs
+++ b/Source/Editor/Viewport/Previews/ModelPreview.cs
@@ -53,7 +53,7 @@ namespace FlaxEditor.Viewport.Previews
// Preview LOD
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
- var previewLODValue = new IntValueBox(-1, 75, 2, 50.0f, -1, 10, 0.02f);
+ var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f);
previewLODValue.Parent = previewLOD;
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
diff --git a/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs b/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs
index e7021050b..638c8324c 100644
--- a/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs
+++ b/Source/Editor/Viewport/Previews/ParticleEmitterPreview.cs
@@ -67,7 +67,7 @@ namespace FlaxEditor.Viewport.Previews
if (useWidgets)
{
var playbackDuration = ViewWidgetButtonMenu.AddButton("Duration");
- var playbackDurationValue = new FloatValueBox(_playbackDuration, 75, 2, 50.0f, 0.1f, 1000000.0f, 0.1f);
+ var playbackDurationValue = new FloatValueBox(_playbackDuration, 90, 2, 70.0f, 0.1f, 1000000.0f, 0.1f);
playbackDurationValue.Parent = playbackDuration;
playbackDurationValue.ValueChanged += () => PlaybackDuration = playbackDurationValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => playbackDurationValue.Value = PlaybackDuration;
diff --git a/Source/Editor/Windows/Assets/JsonAssetWindow.cs b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
index 670414241..da463c501 100644
--- a/Source/Editor/Windows/Assets/JsonAssetWindow.cs
+++ b/Source/Editor/Windows/Assets/JsonAssetWindow.cs
@@ -40,6 +40,12 @@ namespace FlaxEditor.Windows.Assets
_presenter.Modified += MarkAsEdited;
}
+ private void OnScriptsReloadBegin()
+ {
+ Close();
+ ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
+ }
+
///
public override void Save()
{
@@ -76,6 +82,10 @@ namespace FlaxEditor.Windows.Assets
_presenter.Select(_object);
ClearEditedFlag();
+ // Auto-close on scripting reload if json asset is from game scripts (it might be reloaded)
+ if (_object != null && FlaxEngine.Scripting.IsTypeFromGameScripts(_object.GetType()))
+ ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
+
base.OnAssetLoaded();
}
diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
index 1b4d2be31..f81220ac4 100644
--- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs
+++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs
@@ -224,7 +224,7 @@ namespace FlaxEditor.Windows
///
private void ExportSelection()
{
- if(FileSystem.ShowBrowseFolderDialog(Editor.Windows.MainWindow, null, "Select the output folder", out var outputFolder))
+ if (FileSystem.ShowBrowseFolderDialog(Editor.Windows.MainWindow, null, "Select the output folder", out var outputFolder))
return;
var selection = _view.Selection;
diff --git a/Source/Engine/Core/Math/CollisionsHelper.cpp b/Source/Engine/Core/Math/CollisionsHelper.cpp
index 48e7472d1..d485e5fba 100644
--- a/Source/Engine/Core/Math/CollisionsHelper.cpp
+++ b/Source/Engine/Core/Math/CollisionsHelper.cpp
@@ -15,7 +15,7 @@ void CollisionsHelper::ClosestPointPointLine(const Vector2& point, const Vector2
Vector2 n = p1 - p0;
const float length = n.Length();
- if (length < 1e-10)
+ if (length < 1e-10f)
{
// Both points are the same, just give any
result = p0;
@@ -24,7 +24,7 @@ void CollisionsHelper::ClosestPointPointLine(const Vector2& point, const Vector2
n /= length;
const float dot = Vector2::Dot(n, p);
- if (dot <= 0.0)
+ if (dot <= 0.0f)
{
// Before first point
result = p0;
@@ -41,6 +41,45 @@ void CollisionsHelper::ClosestPointPointLine(const Vector2& point, const Vector2
}
}
+Vector2 CollisionsHelper::ClosestPointPointLine(const Vector2& point, const Vector2& p0, const Vector2& p1)
+{
+ Vector2 result;
+ ClosestPointPointLine(point, p0, p1, result);
+ return result;
+}
+
+void CollisionsHelper::ClosestPointPointLine(const Vector3& point, const Vector3& p0, const Vector3& p1, Vector3& result)
+{
+ const Vector3 p = point - p0;
+ Vector3 n = p1 - p0;
+ const float length = n.Length();
+ if (length < 1e-10f)
+ {
+ result = p0;
+ return;
+ }
+ n /= length;
+ const float dot = Vector3::Dot(n, p);
+ if (dot <= 0.0f)
+ {
+ result = p0;
+ return;
+ }
+ if (dot >= length)
+ {
+ result = p1;
+ return;
+ }
+ result = p0 + n * dot;
+}
+
+Vector3 CollisionsHelper::ClosestPointPointLine(const Vector3& point, const Vector3& p0, const Vector3& p1)
+{
+ Vector3 result;
+ ClosestPointPointLine(point, p0, p1, result);
+ return result;
+}
+
void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Vector3& result)
{
// Source: Real-Time Collision Detection by Christer Ericson
@@ -101,6 +140,13 @@ void CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vec
result = vertex1 + ab * v2 + ac * w2; //= u*vertex1 + v*vertex2 + w*vertex3, u = va * denom = 1.0f - v - w
}
+Vector3 CollisionsHelper::ClosestPointPointTriangle(const Vector3& point, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3)
+{
+ Vector3 result;
+ ClosestPointPointTriangle(point, vertex1, vertex2, vertex3, result);
+ return result;
+}
+
void CollisionsHelper::ClosestPointPlanePoint(const Plane& plane, const Vector3& point, Vector3& result)
{
// Source: Real-Time Collision Detection by Christer Ericson
@@ -112,6 +158,13 @@ void CollisionsHelper::ClosestPointPlanePoint(const Plane& plane, const Vector3&
result = point - t * plane.Normal;
}
+Vector3 CollisionsHelper::ClosestPointPlanePoint(const Plane& plane, const Vector3& point)
+{
+ Vector3 result;
+ ClosestPointPlanePoint(plane, point, result);
+ return result;
+}
+
void CollisionsHelper::ClosestPointBoxPoint(const BoundingBox& box, const Vector3& point, Vector3& result)
{
// Source: Real-Time Collision Detection by Christer Ericson
@@ -122,6 +175,13 @@ void CollisionsHelper::ClosestPointBoxPoint(const BoundingBox& box, const Vector
Vector3::Min(temp, box.Maximum, result);
}
+Vector3 CollisionsHelper::ClosestPointBoxPoint(const BoundingBox& box, const Vector3& point)
+{
+ Vector3 result;
+ ClosestPointBoxPoint(box, point, result);
+ return result;
+}
+
void CollisionsHelper::ClosestPointRectanglePoint(const Rectangle& rect, const Vector2& point, Vector2& result)
{
Vector2 temp, end;
@@ -130,6 +190,13 @@ void CollisionsHelper::ClosestPointRectanglePoint(const Rectangle& rect, const V
Vector2::Min(temp, end, result);
}
+Vector2 CollisionsHelper::ClosestPointRectanglePoint(const Rectangle& rect, const Vector2& point)
+{
+ Vector2 result;
+ ClosestPointRectanglePoint(rect, point, result);
+ return result;
+}
+
void CollisionsHelper::ClosestPointSpherePoint(const BoundingSphere& sphere, const Vector3& point, Vector3& result)
{
// Source: Jorgy343
@@ -147,6 +214,13 @@ void CollisionsHelper::ClosestPointSpherePoint(const BoundingSphere& sphere, con
result += sphere.Center;
}
+Vector3 CollisionsHelper::ClosestPointSpherePoint(const BoundingSphere& sphere, const Vector3& point)
+{
+ Vector3 result;
+ ClosestPointSpherePoint(sphere, point, result);
+ return result;
+}
+
void CollisionsHelper::ClosestPointSphereSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2, Vector3& result)
{
// Source: Jorgy343
@@ -164,6 +238,13 @@ void CollisionsHelper::ClosestPointSphereSphere(const BoundingSphere& sphere1, c
result += sphere1.Center;
}
+Vector3 CollisionsHelper::ClosestPointSphereSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2)
+{
+ Vector3 result;
+ ClosestPointSphereSphere(sphere1, sphere2, result);
+ return result;
+}
+
float CollisionsHelper::DistancePlanePoint(const Plane& plane, const Vector3& point)
{
// Source: Real-Time Collision Detection by Christer Ericson
@@ -821,7 +902,6 @@ bool CollisionsHelper::RayIntersectsBox(const Ray& ray, const BoundingBox& box,
d = Math::Abs(size.Z - Math::Abs(localPoint.Z));
if (d < dMin)
{
- dMin = d;
normal = Vector3(0, 0, Math::Sign(localPoint.Z));
}
@@ -990,15 +1070,11 @@ PlaneIntersectionType CollisionsHelper::PlaneIntersectsBox(const Plane& plane, c
min.Z = plane.Normal.Z >= 0.0f ? box.Maximum.Z : box.Minimum.Z;
float distance = Vector3::Dot(plane.Normal, max);
-
if (distance + plane.D > Plane::DistanceEpsilon)
return PlaneIntersectionType::Front;
-
distance = Vector3::Dot(plane.Normal, min);
-
if (distance + plane.D < Plane::DistanceEpsilon)
return PlaneIntersectionType::Back;
-
return PlaneIntersectionType::Intersecting;
}
@@ -1012,10 +1088,8 @@ PlaneIntersectionType CollisionsHelper::PlaneIntersectsSphere(const Plane& plane
if (distance > sphere.Radius)
return PlaneIntersectionType::Front;
-
if (distance < -sphere.Radius)
return PlaneIntersectionType::Back;
-
return PlaneIntersectionType::Intersecting;
}
@@ -1023,13 +1097,10 @@ bool CollisionsHelper::BoxIntersectsBox(const BoundingBox& box1, const BoundingB
{
if (box1.Minimum.X > box2.Maximum.X || box2.Minimum.X > box1.Maximum.X)
return false;
-
if (box1.Minimum.Y > box2.Maximum.Y || box2.Minimum.Y > box1.Maximum.Y)
return false;
-
if (box1.Minimum.Z > box2.Maximum.Z || box2.Minimum.Z > box1.Maximum.Z)
return false;
-
return true;
}
diff --git a/Source/Engine/Core/Math/CollisionsHelper.h b/Source/Engine/Core/Math/CollisionsHelper.h
index 33631a682..4bebf2492 100644
--- a/Source/Engine/Core/Math/CollisionsHelper.h
+++ b/Source/Engine/Core/Math/CollisionsHelper.h
@@ -72,6 +72,33 @@ public:
/// When the method completes, contains the closest point between the two objects.
static void ClosestPointPointLine(const Vector2& point, const Vector2& p0, const Vector2& p1, Vector2& result);
+ ///
+ /// Determines the closest point between a point and a line.
+ ///
+ /// The point to test.
+ /// The line first point.
+ /// The line second point.
+ /// The closest point between the two objects.
+ static Vector2 ClosestPointPointLine(const Vector2& point, const Vector2& p0, const Vector2& p1);
+
+ ///
+ /// Determines the closest point between a point and a line.
+ ///
+ /// The point to test.
+ /// The line first point.
+ /// The line second point.
+ /// When the method completes, contains the closest point between the two objects.
+ static void ClosestPointPointLine(const Vector3& point, const Vector3& p0, const Vector3& p1, Vector3& result);
+
+ ///
+ /// Determines the closest point between a point and a line.
+ ///
+ /// The point to test.
+ /// The line first point.
+ /// The line second point.
+ /// The closest point between the two objects.
+ static Vector3 ClosestPointPointLine(const Vector3& point, const Vector3& p0, const Vector3& p1);
+
///
/// Determines the closest point between a point and a triangle.
///
@@ -82,6 +109,16 @@ public:
/// When the method completes, contains the closest point between the two objects.
static void ClosestPointPointTriangle(const Vector3& point, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Vector3& result);
+ ///
+ /// Determines the closest point between a point and a triangle.
+ ///
+ /// The point to test.
+ /// The first vertex to test.
+ /// The second vertex to test.
+ /// The third vertex to test.
+ /// The closest point between the two objects.
+ static Vector3 ClosestPointPointTriangle(const Vector3& point, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3);
+
///
/// Determines the closest point between a and a point.
///
@@ -90,6 +127,14 @@ public:
/// When the method completes, contains the closest point between the two objects.
static void ClosestPointPlanePoint(const Plane& plane, const Vector3& point, Vector3& result);
+ ///
+ /// Determines the closest point between a and a point.
+ ///
+ /// The plane to test.
+ /// The point to test.
+ /// The closest point between the two objects.
+ static Vector3 ClosestPointPlanePoint(const Plane& plane, const Vector3& point);
+
///
/// Determines the closest point between a and a point.
///
@@ -98,6 +143,14 @@ public:
/// When the method completes, contains the closest point between the two objects.
static void ClosestPointBoxPoint(const BoundingBox& box, const Vector3& point, Vector3& result);
+ ///
+ /// Determines the closest point between a and a point.
+ ///
+ /// The box to test.
+ /// The point to test.
+ /// The closest point between the two objects.
+ static Vector3 ClosestPointBoxPoint(const BoundingBox& box, const Vector3& point);
+
///
/// Determines the closest point between a and a point.
///
@@ -106,6 +159,14 @@ public:
/// When the method completes, contains the closest point between the two objects.
static void ClosestPointRectanglePoint(const Rectangle& rect, const Vector2& point, Vector2& result);
+ ///
+ /// Determines the closest point between a and a point.
+ ///
+ /// The rectangle to test.
+ /// The point to test.
+ /// The closest point between the two objects.
+ static Vector2 ClosestPointRectanglePoint(const Rectangle& rect, const Vector2& point);
+
///
/// Determines the closest point between a and a point.
///
@@ -114,6 +175,14 @@ public:
/// When the method completes, contains the closest point between the two objects; or, if the point is directly in the center of the sphere, contains .
static void ClosestPointSpherePoint(const BoundingSphere& sphere, const Vector3& point, Vector3& result);
+ ///
+ /// Determines the closest point between a and a point.
+ ///
+ /// The sphere to test.
+ /// The point to test.
+ /// The closest point between the two objects; or, if the point is directly in the center of the sphere, contains .
+ static Vector3 ClosestPointSpherePoint(const BoundingSphere& sphere, const Vector3& point);
+
///
/// Determines the closest point between a and a .
///
@@ -127,6 +196,19 @@ public:
///
static void ClosestPointSphereSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2, Vector3& result);
+ ///
+ /// Determines the closest point between a and a .
+ ///
+ /// The first sphere to test.
+ /// The second sphere to test.
+ /// The closest point between the two objects; or, if the point is directly in the center of the sphere, contains .
+ ///
+ /// If the two spheres are overlapping, but not directly on top of each other, the closest point
+ /// is the 'closest' point of intersection. This can also be considered is the deepest point of
+ /// intersection.
+ ///
+ static Vector3 ClosestPointSphereSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2);
+
///
/// Determines the distance between a and a point.
///
diff --git a/Source/Engine/Graphics/Mesh.cs b/Source/Engine/Graphics/Mesh.cs
index 22579eb07..f96b4ce63 100644
--- a/Source/Engine/Graphics/Mesh.cs
+++ b/Source/Engine/Graphics/Mesh.cs
@@ -117,7 +117,7 @@ namespace FlaxEngine
/// Mesh data will be cached and uploaded to the GPU with a delay.
///
/// The mesh vertices positions. Cannot be null.
- /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null.
+ /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.
/// The normal vectors (per vertex).
/// The normal vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
@@ -163,7 +163,7 @@ namespace FlaxEngine
/// Mesh data will be cached and uploaded to the GPU with a delay.
///
/// The mesh vertices positions. Cannot be null.
- /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null.
+ /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.
/// The normal vectors (per vertex).
/// The normal vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
@@ -210,7 +210,7 @@ namespace FlaxEngine
/// Mesh data will be cached and uploaded to the GPU with a delay.
///
/// The mesh vertices positions. Cannot be null.
- /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null.
+ /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null.
/// The normal vectors (per vertex).
/// The tangent vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
@@ -257,7 +257,7 @@ namespace FlaxEngine
/// Mesh data will be cached and uploaded to the GPU with a delay.
///
/// The mesh vertices positions. Cannot be null.
- /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null.
+ /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null.
/// The normal vectors (per vertex).
/// The tangent vectors (per vertex). Use null to compute them from normal vectors.
/// The texture coordinates (per vertex).
diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp
index 0ba51b2d7..444d60540 100644
--- a/Source/Engine/Graphics/Models/Mesh.cpp
+++ b/Source/Engine/Graphics/Models/Mesh.cpp
@@ -10,6 +10,127 @@
#include "Engine/Serialization/MemoryReadStream.h"
#include
+namespace
+{
+ template
+ bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, Vector3* vertices, IndexType* triangles, Vector3* normals, Vector3* tangents, Vector2* uvs, Color32* colors)
+ {
+ auto model = mesh->GetModel();
+ CHECK_RETURN(model && model->IsVirtual(), true);
+ CHECK_RETURN(triangles && vertices, true);
+
+ // Pack mesh data into vertex buffers
+ Array vb1;
+ Array vb2;
+ vb1.Resize(vertexCount);
+ if (normals)
+ {
+ if (tangents)
+ {
+ for (uint32 i = 0; i < vertexCount; i++)
+ {
+ const Vector3 normal = normals[i];
+ const Vector3 tangent = tangents[i];
+
+ // Calculate bitangent sign
+ Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
+ byte sign = static_cast(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
+
+ // Set tangent frame
+ vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
+ vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0);
+ }
+ }
+ else
+ {
+ for (uint32 i = 0; i < vertexCount; i++)
+ {
+ const Vector3 normal = normals[i];
+
+ // Calculate tangent
+ Vector3 c1 = Vector3::Cross(normal, Vector3::UnitZ);
+ Vector3 c2 = Vector3::Cross(normal, Vector3::UnitY);
+ Vector3 tangent;
+ if (c1.LengthSquared() > c2.LengthSquared())
+ tangent = c1;
+ else
+ tangent = c2;
+
+ // Calculate bitangent sign
+ Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
+ byte sign = static_cast(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
+
+ // Set tangent frame
+ vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
+ vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0);
+ }
+ }
+ }
+ else
+ {
+ // Set default tangent frame
+ const auto n = Float1010102(Vector3::UnitZ);
+ const auto t = Float1010102(Vector3::UnitX);
+ for (uint32 i = 0; i < vertexCount; i++)
+ {
+ vb1[i].Normal = n;
+ vb1[i].Tangent = t;
+ }
+ }
+ if (uvs)
+ {
+ for (uint32 i = 0; i < vertexCount; i++)
+ vb1[i].TexCoord = Half2(uvs[i]);
+ }
+ else
+ {
+ auto v = Half2(0, 0);
+ for (uint32 i = 0; i < vertexCount; i++)
+ vb1[i].TexCoord = v;
+ }
+ {
+ auto v = Half2(0, 0);
+ for (uint32 i = 0; i < vertexCount; i++)
+ vb1[i].LightmapUVs = v;
+ }
+ if (colors)
+ {
+ vb2.Resize(vertexCount);
+ for (uint32 i = 0; i < vertexCount; i++)
+ vb2[i].Color = colors[i];
+ }
+
+ return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vertices, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, triangles);
+ }
+
+ template
+ bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
+ {
+ ASSERT((uint32)mono_array_length(verticesObj) >= vertexCount);
+ ASSERT((uint32)mono_array_length(trianglesObj) / 3 >= triangleCount);
+ auto vertices = (Vector3*)(void*)mono_array_addr_with_size(verticesObj, sizeof(Vector3), 0);
+ auto triangles = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0);
+ const auto normals = normalsObj ? (Vector3*)(void*)mono_array_addr_with_size(normalsObj, sizeof(Vector3), 0) : nullptr;
+ const auto tangents = tangentsObj ? (Vector3*)(void*)mono_array_addr_with_size(tangentsObj, sizeof(Vector3), 0) : nullptr;
+ const auto uvs = uvObj ? (Vector2*)(void*)mono_array_addr_with_size(uvObj, sizeof(Vector2), 0) : nullptr;
+ const auto colors = colorsObj ? (Color32*)(void*)mono_array_addr_with_size(colorsObj, sizeof(Color32), 0) : nullptr;
+ return UpdateMesh(mesh, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
+ }
+
+ template
+ bool UpdateTriangles(Mesh* mesh, int32 triangleCount, MonoArray* trianglesObj)
+ {
+ const auto model = mesh->GetModel();
+ ASSERT(model && model->IsVirtual() && trianglesObj);
+
+ // Get buffer data
+ ASSERT((int32)mono_array_length(trianglesObj) / 3 >= triangleCount);
+ auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0);
+
+ return mesh->UpdateTriangles(triangleCount, ib);
+ }
+}
+
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices)
{
Unload();
@@ -31,6 +152,16 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType*
return failed;
}
+bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint16* triangles, Vector3* normals, Vector3* tangents, Vector2* uvs, Color32* colors)
+{
+ return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
+}
+
+bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint32* triangles, Vector3* normals, Vector3* tangents, Vector2* uvs, Color32* colors)
+{
+ return ::UpdateMesh(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
+}
+
bool Mesh::UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices)
{
// Cache data
@@ -384,108 +515,9 @@ ScriptingObject* Mesh::GetParentModel()
return _model;
}
-template
-bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
-{
- auto model = mesh->GetModel();
- ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj);
-
- // Get buffers data
- ASSERT((uint32)mono_array_length(verticesObj) >= vertexCount);
- ASSERT((uint32)mono_array_length(trianglesObj) / 3 >= triangleCount);
- auto vb0 = (Vector3*)(void*)mono_array_addr_with_size(verticesObj, sizeof(Vector3), 0);
- auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0);
- Array vb1;
- Array vb2;
- vb1.Resize(vertexCount);
- if (normalsObj)
- {
- const auto normals = (Vector3*)(void*)mono_array_addr_with_size(normalsObj, sizeof(Vector3), 0);
- if (tangentsObj)
- {
- const auto tangents = (Vector3*)(void*)mono_array_addr_with_size(tangentsObj, sizeof(Vector3), 0);
- for (uint32 i = 0; i < vertexCount; i++)
- {
- // Peek normal and tangent
- const Vector3 normal = normals[i];
- const Vector3 tangent = tangents[i];
-
- // Calculate bitangent sign
- Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
- byte sign = static_cast(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
-
- // Set tangent frame
- vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
- vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0);
- }
- }
- else
- {
- for (uint32 i = 0; i < vertexCount; i++)
- {
- // Peek normal
- const Vector3 normal = normals[i];
-
- // Calculate tangent
- Vector3 c1 = Vector3::Cross(normal, Vector3::UnitZ);
- Vector3 c2 = Vector3::Cross(normal, Vector3::UnitY);
- Vector3 tangent;
- if (c1.LengthSquared() > c2.LengthSquared())
- tangent = c1;
- else
- tangent = c2;
-
- // Calculate bitangent sign
- Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
- byte sign = static_cast(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
-
- // Set tangent frame
- vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
- vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0);
- }
- }
- }
- else
- {
- const auto n = Float1010102(Vector3::UnitZ);
- const auto t = Float1010102(Vector3::UnitX);
- for (uint32 i = 0; i < vertexCount; i++)
- {
- vb1[i].Normal = n;
- vb1[i].Tangent = t;
- }
- }
- if (uvObj)
- {
- const auto uvs = (Vector2*)(void*)mono_array_addr_with_size(uvObj, sizeof(Vector2), 0);
- for (uint32 i = 0; i < vertexCount; i++)
- vb1[i].TexCoord = Half2(uvs[i]);
- }
- else
- {
- auto v = Half2(0, 0);
- for (uint32 i = 0; i < vertexCount; i++)
- vb1[i].TexCoord = v;
- }
- {
- auto v = Half2(0, 0);
- for (uint32 i = 0; i < vertexCount; i++)
- vb1[i].LightmapUVs = v;
- }
- if (colorsObj)
- {
- vb2.Resize(vertexCount);
- const auto colors = (Color32*)(void*)mono_array_addr_with_size(colorsObj, sizeof(Color32), 0);
- for (uint32 i = 0; i < vertexCount; i++)
- vb2[i].Color = colors[i];
- }
-
- return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vb0, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, ib);
-}
-
bool Mesh::UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
{
- return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
+ return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
}
bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
@@ -493,22 +525,9 @@ bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* v
return ::UpdateMesh(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
}
-template
-bool UpdateTriangles(Mesh* mesh, int32 triangleCount, MonoArray* trianglesObj)
-{
- auto model = mesh->GetModel();
- ASSERT(model && model->IsVirtual() && trianglesObj);
-
- // Get buffer data
- ASSERT((int32)mono_array_length(trianglesObj) / 3 >= triangleCount);
- auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0);
-
- return mesh->UpdateTriangles(triangleCount, ib);
-}
-
bool Mesh::UpdateTrianglesInt(int32 triangleCount, MonoArray* trianglesObj)
{
- return ::UpdateTriangles(this, triangleCount, trianglesObj);
+ return ::UpdateTriangles(this, triangleCount, trianglesObj);
}
bool Mesh::UpdateTrianglesUShort(int32 triangleCount, MonoArray* trianglesObj)
diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h
index c8311a1f3..80d566fa3 100644
--- a/Source/Engine/Graphics/Models/Mesh.h
+++ b/Source/Engine/Graphics/Models/Mesh.h
@@ -216,9 +216,9 @@ public:
/// The first vertex buffer data.
/// The second vertex buffer data.
/// The third vertex buffer data.
- /// The index buffer.
+ /// The index buffer in clockwise order.
/// True if failed, otherwise false.
- FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, int32* ib)
+ FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint32* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false);
}
@@ -231,7 +231,7 @@ public:
/// The first vertex buffer data.
/// The second vertex buffer data.
/// The third vertex buffer data.
- /// The index buffer.
+ /// The index buffer in clockwise order.
/// True if failed, otherwise false.
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint16* ib)
{
@@ -240,17 +240,51 @@ public:
///
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
+ /// Can be used only for virtual assets (see and ).
+ /// Mesh data will be cached and uploaded to the GPU with a delay.
///
/// The amount of vertices in the vertex buffer.
/// The amount of triangles in the index buffer.
/// The first vertex buffer data.
/// The second vertex buffer data.
/// The third vertex buffer data.
- /// The index buffer.
+ /// The index buffer in clockwise order.
/// True if index buffer uses 16-bit index buffer, otherwise 32-bit.
/// True if failed, otherwise false.
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices);
+ ///
+ /// Updates the model mesh (used by the virtual models created with Init rather than Load).
+ /// Can be used only for virtual assets (see and ).
+ /// Mesh data will be cached and uploaded to the GPU with a delay.
+ ///
+ /// The amount of vertices in the vertex buffer.
+ /// The amount of triangles in the index buffer.
+ /// The mesh vertices positions. Cannot be null.
+ /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.
+ /// The normal vectors (per vertex).
+ /// The normal vectors (per vertex). Use null to compute them from normal vectors.
+ /// The texture coordinates (per vertex).
+ /// The vertex colors (per vertex).
+ /// True if failed, otherwise false.
+ bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint16* triangles, Vector3* normals = nullptr, Vector3* tangents = nullptr, Vector2* uvs = nullptr, Color32* colors = nullptr);
+
+ ///
+ /// Updates the model mesh (used by the virtual models created with Init rather than Load).
+ /// Can be used only for virtual assets (see and ).
+ /// Mesh data will be cached and uploaded to the GPU with a delay.
+ ///
+ /// The amount of vertices in the vertex buffer.
+ /// The amount of triangles in the index buffer.
+ /// The mesh vertices positions. Cannot be null.
+ /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.
+ /// The normal vectors (per vertex).
+ /// The normal vectors (per vertex). Use null to compute them from normal vectors.
+ /// The texture coordinates (per vertex).
+ /// The vertex colors (per vertex).
+ /// True if failed, otherwise false.
+ bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint32* triangles, Vector3* normals = nullptr, Vector3* tangents = nullptr, Vector2* uvs = nullptr, Color32* colors = nullptr);
+
public:
///
@@ -259,7 +293,7 @@ public:
/// The amount of triangles in the index buffer.
/// The index buffer.
/// True if failed, otherwise false.
- FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, int32* ib)
+ FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, uint32* ib)
{
return UpdateTriangles(triangleCount, ib, false);
}
diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h
index 2c168980b..bbdf38014 100644
--- a/Source/Engine/Graphics/Models/SkinnedMesh.h
+++ b/Source/Engine/Graphics/Models/SkinnedMesh.h
@@ -163,7 +163,7 @@ public:
/// The amount of vertices in the vertex buffer.
/// The amount of triangles in the index buffer.
/// The vertex buffer data.
- /// The index buffer.
+ /// The index buffer in clockwise order.
/// True if failed, otherwise false.
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, int32* ib)
{
@@ -176,7 +176,7 @@ public:
/// The amount of vertices in the vertex buffer.
/// The amount of triangles in the index buffer.
/// The vertex buffer data.
- /// The index buffer.
+ /// The index buffer, clockwise order.
/// True if failed, otherwise false.
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint16* ib)
{
@@ -189,7 +189,7 @@ public:
/// The amount of vertices in the vertex buffer.
/// The amount of triangles in the index buffer.
/// The vertex buffer data.
- /// The index buffer.
+ /// The index buffer in clockwise order.
/// True if index buffer uses 16-bit index buffer, otherwise 32-bit.
/// True if failed, otherwise false.
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices);
diff --git a/Source/Engine/Graphics/SkinnedMesh.cs b/Source/Engine/Graphics/SkinnedMesh.cs
index e8e0d8353..ea4cf283e 100644
--- a/Source/Engine/Graphics/SkinnedMesh.cs
+++ b/Source/Engine/Graphics/SkinnedMesh.cs
@@ -104,7 +104,7 @@ namespace FlaxEngine
/// Mesh data will be cached and uploaded to the GPU with a delay.
///
/// The mesh vertices positions. Cannot be null.
- /// The mesh index buffer (triangles). Uses 32-bit stride buffer. Cannot be null.
+ /// The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.
/// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null.
/// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null.
/// The normal vectors (per vertex).
@@ -140,7 +140,7 @@ namespace FlaxEngine
/// Mesh data will be cached and uploaded to the GPU with a delay.
///
/// The mesh vertices positions. Cannot be null.
- /// The mesh index buffer (triangles). Uses 16-bit stride buffer. Cannot be null.
+ /// The mesh index buffer (clockwise triangles). Uses 16-bit stride buffer. Cannot be null.
/// The skinned mesh blend indices buffer. Contains indices of the skeleton bones (up to 4 bones per vertex) to use for vertex position blending. Cannot be null.
/// The skinned mesh blend weights buffer (normalized). Contains weights per blend bone (up to 4 bones per vertex) of the skeleton bones to mix for vertex position blending. Cannot be null.
/// The normal vectors (per vertex).
diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp
index 6c75879ad..73b653ee4 100644
--- a/Source/Engine/Physics/Colliders/CharacterController.cpp
+++ b/Source/Engine/Physics/Colliders/CharacterController.cpp
@@ -55,7 +55,7 @@ void CharacterController::SetHeight(const float value)
void CharacterController::SetSlopeLimit(float value)
{
- value = Math::Clamp(value, 0.0f, 90.0f);
+ value = Math::Clamp(value, 0.0f, 89.0f);
if (Math::NearEqual(value, _slopeLimit))
return;
@@ -177,7 +177,7 @@ void CharacterController::CreateActor()
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float minSize = 0.001f;
desc.height = Math::Max(Math::Abs(_height) * scaling, minSize);
- desc.radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
+ desc.radius = Math::Max(Math::Abs(_radius) * scaling - desc.contactOffset, minSize);
// Create controller
_controller = (PxCapsuleController*)Physics::GetControllerManager()->createController(desc);
@@ -202,7 +202,7 @@ void CharacterController::UpdateSize() const
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float minSize = 0.001f;
- const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
+ const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), minSize);
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
_controller->setRadius(radius);
diff --git a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
index 7c6b04fef..1816c9552 100644
--- a/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
+++ b/Source/Engine/Platform/Windows/WindowsFileSystem.cpp
@@ -5,6 +5,7 @@
#include "WindowsFileSystem.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/Window.h"
+#include "Engine/Platform/Windows/ComPtr.h"
#include "Engine/Core/Types/StringView.h"
#include "../Win32/IncludeWindowsHeaders.h"
@@ -293,41 +294,39 @@ bool WindowsFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const Strin
{
bool result = true;
- // Allocate memory for the filenames
- int32 maxPathSize = 2 * MAX_PATH;
- Array pathBuffer;
- pathBuffer.Resize(maxPathSize);
- pathBuffer[0] = 0;
+ // Randomly generated GUID used for storing the last location of this dialog
+ const Guid folderGuid(0x53890ed9, 0xa55e47ba, 0xa970bdae, 0x72acedff);
- // Setup description
- BROWSEINFOW bi;
- ZeroMemory(&bi, sizeof(bi));
- if (parentWindow)
- bi.hwndOwner = static_cast(parentWindow->GetNativePtr());
- bi.lpszTitle = title.HasChars() ? title.Get() : nullptr;
- bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
- bi.lpfn = BrowseCallbackProc;
- bi.lParam = (LPARAM)(initialDirectory.HasChars() ? initialDirectory.Get() : nullptr);
-
- LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
-
- if (pidl != nullptr)
+ ComPtr fd;
+ if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&fd))))
{
- // Get the name of the folder and put it in path
- SHGetPathFromIDList(pidl, pathBuffer.Get());
+ DWORD options;
+ fd->GetOptions(&options);
+ fd->SetOptions(options | FOS_PICKFOLDERS | FOS_NOCHANGEDIR);
- if (pathBuffer[0] != 0)
- {
- path = pathBuffer.Get();
- result = false;
- }
+ if (title.HasChars())
+ fd->SetTitle(title.Get());
- // Free memory used
- IMalloc* imalloc = 0;
- if (SUCCEEDED(SHGetMalloc(&imalloc)))
+ // Associate the last selected folder with this GUID instead of overwriting the global one
+ fd->SetClientGuid(*reinterpret_cast(&folderGuid));
+
+ ComPtr defaultFolder;
+ if (SUCCEEDED(SHCreateItemFromParsingName(initialDirectory.Get(), NULL, IID_PPV_ARGS(&defaultFolder))))
+ fd->SetFolder(defaultFolder);
+
+ if (SUCCEEDED(fd->Show(parentWindow->GetHWND())))
{
- imalloc->Free(pidl);
- imalloc->Release();
+ ComPtr si;
+ if (SUCCEEDED(fd->GetResult(&si)))
+ {
+ LPWSTR resultPath;
+ if (SUCCEEDED(si->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &resultPath)))
+ {
+ path = resultPath;
+ CoTaskMemFree(resultPath);
+ result = false;
+ }
+ }
}
}
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
index 998c3f3a3..b042336ff 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
@@ -274,8 +274,10 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
// Compute depth difference
auto depthDiff = writeLocal(VariantType::Float, String::Format(TEXT("{0} * ViewFar - {1}"), sceneDepth.Value, posVS.Value), node);
+ auto fadeDistance = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat();
+
// Apply smoothing factor and clamp the result
- value = writeLocal(VariantType::Float, String::Format(TEXT("saturate({0} / {1})"), depthDiff.Value, node->Values[0].AsFloat), node);
+ value = writeLocal(VariantType::Float, String::Format(TEXT("saturate({0} / {1})"), depthDiff.Value, fadeDistance.Value), node);
break;
}
// Material Function
@@ -337,6 +339,59 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
case 25:
value = Value(VariantType::Vector3, TEXT("GetObjectSize(input)"));
break;
+ // Blend Normals
+ case 26:
+ {
+ const auto baseNormal = tryGetValue(node->GetBox(0), Value::Zero).AsVector3();
+ const auto additionalNormal = tryGetValue(node->GetBox(1), Value::Zero).AsVector3();
+ const String text = String::Format(TEXT("float3((float2({0}.xy) + float2({1}.xy) * 2.0), sqrt(saturate(1.0 - dot((float2({0}.xy) + float2({1}.xy) * 2.0).xy, (float2({0}.xy) + float2({1}.xy) * 2.0).xy))))"), baseNormal.Value, additionalNormal.Value);
+ value = writeLocal(ValueType::Vector3, text, node);
+ break;
+ }
+ // Rotator
+ case 27:
+ {
+ auto uv = tryGetValue(node->GetBox(0), getUVs).AsVector2();
+ auto center = tryGetValue(node->GetBox(1), Value::Zero).AsVector2();
+ auto rotationAngle = tryGetValue(node->GetBox(2), Value::Zero).AsFloat();
+
+ const auto x1 = writeLocal(ValueType::Vector2, String::Format(TEXT("({0} * -1) + {1}"), center.Value, uv.Value), node);
+ const auto raCosSin = writeLocal(ValueType::Vector2, String::Format(TEXT("float2(cos({0}), sin({0}))"), rotationAngle.Value), node);
+
+ const auto dotB1 = writeLocal(ValueType::Vector2, String::Format(TEXT("float2({0}.x, {0}.y * -1)"), raCosSin.Value), node);
+ const auto dotB2 = writeLocal(ValueType::Vector2, String::Format(TEXT("float2({0}.y, {0}.x)"), raCosSin.Value), node);
+
+ value = writeLocal(ValueType::Vector2, String::Format(TEXT("{3} + float2(dot({0},{1}), dot({0},{2}))"), x1.Value, dotB1.Value, dotB2.Value, center.Value), node);
+ break;
+ }
+ // Sphere Mask
+ case 28:
+ {
+ Value a = tryGetValue(node->GetBox(0), 0, Value::Zero);
+ Value b = tryGetValue(node->GetBox(1), 1, Value::Zero).Cast(a.Type);
+ Value radius = tryGetValue(node->GetBox(2), node->Values[0]).AsFloat();
+ Value hardness = tryGetValue(node->GetBox(3), node->Values[1]).AsFloat();
+ Value invert = tryGetValue(node->GetBox(4), node->Values[2]).AsBool();
+
+ // Get distance and apply radius
+ auto x1 = writeLocal(ValueType::Float, String::Format(TEXT("distance({0},{1}) * (1 / {2})"), a.Value, b.Value, radius.Value), node);
+
+ // Apply hardness, use 0.991 as max since any value above will result in harsh aliasing
+ auto x2 = writeLocal(ValueType::Float, String::Format(TEXT("saturate((1 - {0}) * (1 / (1 - clamp({1}, 0, 0.991f))))"), x1.Value, hardness.Value), node);
+
+ value = writeLocal(ValueType::Float, String::Format(TEXT("{0} ? (1 - {1}) : {1}"), invert.Value, x2.Value), node);
+ break;
+ }
+ // Tiling & Offset
+ case 29:
+ {
+ auto uv = tryGetValue(node->GetBox(0), getUVs).AsVector2();
+ auto tiling = tryGetValue(node->GetBox(1), node->Values[0]).AsVector2();
+ auto offset = tryGetValue(node->GetBox(2), node->Values[1]).AsVector2();
+
+ value = writeLocal(ValueType::Vector2, String::Format(TEXT("{0} * {1} + {2}"), uv.Value, tiling.Value, offset.Value), node);
+ break;
+ }
default:
break;
}
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
index dbf51e965..0c423fed1 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
+++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp
@@ -190,6 +190,16 @@ bool TextureTool::ExportTextureDirectXTex(ImageType type, const StringView& path
}
img = tmp.GetImage(0, 0, 0);
}
+ else if (image.format == DXGI_FORMAT_R10G10B10A2_UNORM || image.format == DXGI_FORMAT_R11G11B10_FLOAT)
+ {
+ result = DirectX::Convert(image, DXGI_FORMAT_R8G8B8A8_UNORM, DirectX::TEX_FILTER_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, tmp);
+ if (FAILED(result))
+ {
+ LOG(Error, "Cannot convert texture, error: {0:x}", static_cast(result));
+ return true;
+ }
+ img = tmp.GetImage(0, 0, 0);
+ }
DirectX::WICCodecs codec;
switch (type)
diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp
index 7b3c51f3e..c8a8782e9 100644
--- a/Source/Engine/Visject/ShaderGraph.cpp
+++ b/Source/Engine/Visject/ShaderGraph.cpp
@@ -401,6 +401,18 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value)
{
value = writeFunction1(node, tryGetValue(node->GetBox(0), Value::Zero), TEXT("radians"));
break;
+ }
+ // Remap
+ case 48:
+ {
+ const auto inVal = tryGetValue(node->GetBox(0), node->Values[0].AsFloat);
+ const auto rangeA = tryGetValue(node->GetBox(1), node->Values[1].AsVector2());
+ const auto rangeB = tryGetValue(node->GetBox(2), node->Values[2].AsVector2());
+ const auto clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool();
+
+ const auto mapFunc = String::Format(TEXT("{2}.x + ({0} - {1}.x) * ({2}.y - {2}.x) / ({1}.y - {1}.x)"), inVal.Value, rangeA.Value, rangeB.Value);
+ value = writeLocal(ValueType::Float, String::Format(TEXT("{2} ? clamp({0}, {1}.x, {1}.y) : {0}"), mapFunc, rangeB.Value, clamp.Value), node);
+ break;
}
default:
break;
diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp
index eb2b8bf61..939013ebd 100644
--- a/Source/Engine/Visject/VisjectGraph.cpp
+++ b/Source/Engine/Visject/VisjectGraph.cpp
@@ -371,6 +371,20 @@ void VisjectExecutor::ProcessGroupMath(Box* box, Node* node, Value& value)
if (value.Type.Type == VariantType::Enum)
value.AsUint64 = value.AsUint64 | (uint64)tryGetValue(node->GetBox(1), Value::Zero);
break;
+ // Remap
+ case 48:
+ {
+ const float inVal = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat;
+ const Vector2 rangeA = tryGetValue(node->GetBox(1), node->Values[1]).AsVector2();
+ const Vector2 rangeB = tryGetValue(node->GetBox(2), node->Values[2]).AsVector2();
+ const bool clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool;
+
+ auto mapFunc = rangeB.X + (inVal - rangeA.X) * (rangeB.Y - rangeB.X) / (rangeA.Y - rangeA.X);
+
+ // Clamp value?
+ value = clamp ? Math::Clamp(mapFunc, rangeB.X, rangeB.Y) : mapFunc;
+ break;
+ }
default:
break;
}
diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs
index b1159db1c..9939cea53 100644
--- a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs
+++ b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs
@@ -56,6 +56,11 @@ namespace Flax.Build.NativeCpp
///
public bool Optimization = false;
+ ///
+ /// Enables the whole program optimization.
+ ///
+ public bool WholeProgramOptimization = false;
+
///
/// Enables functions level linking support.
///
@@ -131,6 +136,7 @@ namespace Flax.Build.NativeCpp
RuntimeTypeInfo = RuntimeTypeInfo,
Inlining = Inlining,
Optimization = Optimization,
+ WholeProgramOptimization = WholeProgramOptimization,
FunctionLevelLinking = FunctionLevelLinking,
DebugInformation = DebugInformation,
UseDebugCRT = UseDebugCRT,
diff --git a/Source/Tools/Flax.Build/Build/Target.cs b/Source/Tools/Flax.Build/Build/Target.cs
index ebf1d36c9..239578d51 100644
--- a/Source/Tools/Flax.Build/Build/Target.cs
+++ b/Source/Tools/Flax.Build/Build/Target.cs
@@ -256,6 +256,7 @@ namespace Flax.Build
options.CompileEnv.IntrinsicFunctions = false;
options.CompileEnv.BufferSecurityCheck = true;
options.CompileEnv.Inlining = false;
+ options.CompileEnv.WholeProgramOptimization = false;
options.LinkEnv.DebugInformation = true;
options.LinkEnv.LinkTimeCodeGeneration = false;
@@ -273,11 +274,11 @@ namespace Flax.Build
options.CompileEnv.IntrinsicFunctions = true;
options.CompileEnv.BufferSecurityCheck = true;
options.CompileEnv.Inlining = true;
- //options.CompileEnv.WholeProgramOptimization = true;
+ options.CompileEnv.WholeProgramOptimization = false;
options.LinkEnv.DebugInformation = true;
- options.LinkEnv.LinkTimeCodeGeneration = true;
- options.LinkEnv.UseIncrementalLinking = false;
+ options.LinkEnv.LinkTimeCodeGeneration = false;
+ options.LinkEnv.UseIncrementalLinking = true;
options.LinkEnv.Optimization = true;
break;
case TargetConfiguration.Release:
@@ -291,7 +292,7 @@ namespace Flax.Build
options.CompileEnv.IntrinsicFunctions = true;
options.CompileEnv.BufferSecurityCheck = false;
options.CompileEnv.Inlining = true;
- //options.CompileEnv.WholeProgramOptimization = true;
+ options.CompileEnv.WholeProgramOptimization = true;
options.LinkEnv.DebugInformation = false;
options.LinkEnv.LinkTimeCodeGeneration = true;
diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs
index 0917b5027..4d3289a86 100644
--- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs
+++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs
@@ -467,8 +467,11 @@ namespace Flax.Build.Platforms
// Frame-Pointer Omission
commonArgs.Add("/Oy");
- // Whole Program Optimization
- commonArgs.Add("/GL");
+ if (compileEnvironment.WholeProgramOptimization)
+ {
+ // Whole Program Optimization
+ commonArgs.Add("/GL");
+ }
}
else
{
@@ -721,7 +724,7 @@ namespace Flax.Build.Platforms
args.Add("/PDBALTPATH:%_PDB%");
// Optimize
- if (linkEnvironment.Optimization)
+ if (linkEnvironment.Optimization && !linkEnvironment.UseIncrementalLinking)
{
// Generate an EXE checksum
args.Add("/RELEASE");