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");