Conflicts:
	Source/Editor/Viewport/EditorViewport.cs
This commit is contained in:
Jean-Baptiste Perrier
2021-03-01 16:21:58 +01:00
68 changed files with 4625 additions and 703 deletions

View File

@@ -0,0 +1,14 @@
## Mono
Custom fork: [https://github.com/FlaxEngine/mono](https://github.com/FlaxEngine/mono) with custom features for C# assemblies hot-reloading at runtime without domain unload (more: [https://flaxengine.com/blog/flax-facts-16-scripts-hot-reload/](https://flaxengine.com/blog/flax-facts-16-scripts-hot-reload/)).
### Notes
Some useful notes and tips for devs:
* When working with mono fork set `localRepoPath` to local repo location in `Source\Tools\Flax.Build\Deps\Dependencies\mono.cs`
* To update mono deps when developing/updating use `.\Development\Scripts\Windows\CallBuildTool.bat -log -ReBuildDeps -verbose -depsToBuild=mono -platform=Windows`, then build engine and run it
* `MONO_GC_DEBUG=check-remset-consistency` - it will do additional checks at each collection to see if there are any missing write barriers
* `MONO_GC_DEBUG=nursery-canaries` - it might catch some buffer overflows in case of problems in code.
* `MONO_GC_DEBUG=<log-level>:<log-file>` - will print GC debug to the log file (eg. `4:sgen-gc`).
* Methods `mono_custom_attrs_from_property` and `mono_custom_attrs_get_attr` are internally cached
* If C++ mono call a method in c# that will throw an error, error will be handled but, not completly. Calling relase domain will return random `Access memory violation`. First search for error in c# code. No workaround yet.

View File

@@ -1081,12 +1081,12 @@ bool CookAssetsStep::Perform(CookingData& data)
data.StepProgress(TEXT("Creating assets cache"), Step2ProgressEnd);
// Create asset paths mapping for the root assets.
// Create asset paths mapping for the assets.
// Assets mapping is use to convert paths used in Content::Load(path) into the asset id.
// It fixes the issues when in build game all assets are in the packages while engine parts are requesting in-build assets by name.
// It fixes the issues when in build game all assets are in the packages and are requested by path.
// E.g. game settings are loaded from `Content/GameSettings.json` file which is packages in one of the packages.
// Additionally it improves the in-build assets loading performance.
for (auto i = data.RootAssets.Begin(); i.IsNotEnd(); ++i)
// Additionally it improves the in-build assets loading performance (no more registry linear lookup for path by dictionary access).
for (auto i = data.Assets.Begin(); i.IsNotEnd(); ++i)
{
if (Content::GetAssetInfo(i->Item, assetInfo))
{

View File

@@ -43,10 +43,18 @@ namespace FlaxEditor.CustomEditors.Editors
// Copy old values
Array.Copy(array, 0, newValues, 0, sharedCount);
// Fill new entries with the last value
for (int i = oldSize; i < newSize; i++)
if (elementType.IsValueType)
{
Array.Copy(array, oldSize - 1, newValues, i, 1);
// Fill new entries with the last value
for (int i = oldSize; i < newSize; i++)
Array.Copy(array, oldSize - 1, newValues, i, 1);
}
else
{
// Initialize new entries with default values
var defaultValue = TypeUtils.GetDefaultValue(new ScriptType(elementType));
for (int i = oldSize; i < newSize; i++)
newValues.SetValue(defaultValue, i);
}
}
else if (newSize > 0)
@@ -54,9 +62,7 @@ namespace FlaxEditor.CustomEditors.Editors
// Initialize new entries with default values
var defaultValue = TypeUtils.GetDefaultValue(new ScriptType(elementType));
for (int i = 0; i < newSize; i++)
{
newValues.SetValue(defaultValue, i);
}
}
SetValue(newValues);

View File

@@ -434,17 +434,19 @@ namespace FlaxEditor.CustomEditors.Editors
{
Text = "+",
TooltipText = "Create a new instance of the object",
Height = ButtonSize,
Width = ButtonSize,
X = layout.ContainerControl.Width - ButtonSize - 4,
Size = new Vector2(ButtonSize, ButtonSize),
AnchorPreset = AnchorPresets.MiddleRight,
Parent = layout.ContainerControl
Parent = layout.ContainerControl,
Location = new Vector2(layout.ContainerControl.Width - ButtonSize - 4, (layout.ContainerControl.Height - ButtonSize) * 0.5f),
};
button.Clicked += () =>
{
var newType = Values.Type;
SetValue(newType.CreateInstance());
RebuildLayoutOnRefresh();
if (ParentEditor != null)
ParentEditor.RebuildLayoutOnRefresh();
else
RebuildLayoutOnRefresh();
};
}

View File

@@ -40,30 +40,35 @@ namespace FlaxEditor.CustomEditors.Editors
// Allocate new list
var listType = Values.Type;
var newValues = (IList)listType.CreateInstance();
var elementType = ElementType;
var sharedCount = Mathf.Min(oldSize, newSize);
if (list != null && sharedCount > 0)
{
// Copy old values
for (int i = 0; i < sharedCount; i++)
{
newValues.Add(list[i]);
}
// Fill new entries with the last value
for (int i = oldSize; i < newSize; i++)
if (elementType.IsValueType)
{
newValues.Add(list[oldSize - 1]);
// Fill new entries with the last value
for (int i = oldSize; i < newSize; i++)
newValues.Add(list[oldSize - 1]);
}
else
{
// Initialize new entries with default values
var defaultValue = Scripting.TypeUtils.GetDefaultValue(elementType);
for (int i = oldSize; i < newSize; i++)
newValues.Add(defaultValue);
}
}
else if (newSize > 0)
{
// Fill new entries with default value
var defaultValue = Scripting.TypeUtils.GetDefaultValue(ElementType);
var defaultValue = Scripting.TypeUtils.GetDefaultValue(elementType);
for (int i = oldSize; i < newSize; i++)
{
newValues.Add(defaultValue);
}
}
SetValue(newValues);

View File

@@ -983,6 +983,103 @@ namespace FlaxEditor.Surface.Archetypes
}
}
private class RerouteNode : SurfaceNode
{
public static readonly Vector2 DefaultSize = new Vector2(16);
/// <inheritdoc />
protected override bool ShowTooltip => false;
/// <inheritdoc />
public RerouteNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
Size = DefaultSize;
Title = string.Empty;
BackgroundColor = Color.Transparent;
}
/// <inheritdoc />
public override void OnSurfaceLoaded()
{
base.OnSurfaceLoaded();
var inputBox = GetBox(0);
var outputBox = GetBox(1);
inputBox.Location = Vector2.Zero;
outputBox.Location = Vector2.Zero;
UpdateBoxes();
}
/// <inheritdoc />
public override void ConnectionTick(Box box)
{
base.ConnectionTick(box);
UpdateBoxes();
}
private void UpdateBoxes()
{
var inputBox = GetBox(0);
var outputBox = GetBox(1);
inputBox.Visible = !inputBox.HasAnyConnection;
outputBox.Visible = !outputBox.HasAnyConnection;
}
/// <inheritdoc />
public override bool CanSelect(ref Vector2 location)
{
return new Rectangle(Location, DefaultSize).Contains(ref location);
}
/// <inheritdoc />
protected override void UpdateRectangles()
{
_headerRect = Rectangle.Empty;
_closeButtonRect = Rectangle.Empty;
_footerRect = Rectangle.Empty;
}
public override void Draw()
{
var style = Surface.Style;
var inputBox = GetBox(0);
var outputBox = GetBox(1);
var connectionColor = style.Colors.Default;
float barHorizontalOffset = -2;
float barHeight = 3;
if (inputBox.HasAnyConnection)
{
var hints = inputBox.Connections[0].ParentNode.Archetype.ConnectionsHints;
Surface.Style.GetConnectionColor(inputBox.Connections[0].CurrentType, hints, out connectionColor);
}
if (!inputBox.HasAnyConnection)
{
Render2D.FillRectangle(new Rectangle(-barHorizontalOffset - barHeight * 2, (DefaultSize.Y - barHeight) / 2, barHeight * 2, barHeight), connectionColor);
}
if (!outputBox.HasAnyConnection)
{
Render2D.FillRectangle(new Rectangle(DefaultSize.X + barHorizontalOffset, (DefaultSize.Y - barHeight) / 2, barHeight * 2, barHeight), connectionColor);
}
if (inputBox.HasAnyConnection && outputBox.HasAnyConnection)
{
var hints = inputBox.Connections[0].ParentNode.Archetype.ConnectionsHints;
Surface.Style.GetConnectionColor(inputBox.Connections[0].CurrentType, hints, out connectionColor);
SpriteHandle icon = style.Icons.BoxClose;
Render2D.DrawSprite(icon, new Rectangle(Vector2.Zero, DefaultSize), connectionColor);
}
base.Draw();
}
}
/// <summary>
/// The nodes for that group.
/// </summary>
@@ -1438,6 +1535,23 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(object), 1),
}
},
new NodeArchetype
{
TypeID = 29,
Title = "Reroute",
Create = (id, context, arch, groupArch) => new RerouteNode(id, context, arch, groupArch),
Description = "Reroute a connection.",
Flags = NodeFlags.NoCloseButton | NodeFlags.NoSpawnViaGUI | NodeFlags.AllGraphs,
Size = RerouteNode.DefaultSize,
ConnectionsHints = ConnectionsHint.All,
IndependentBoxes = new int[] { 0 },
DependentBoxes = new int[] { 1 },
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, string.Empty, true, null, 0),
NodeElementArchetype.Factory.Output(0, string.Empty, null, 1, true),
}
},
};
}
}

View File

@@ -159,6 +159,7 @@ namespace FlaxEditor.Surface.ContextMenu
var start = font.GetCharPosition(_archetype.Title, 0);
var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1);
_highlights.Add(new Rectangle(start.X + 2, 0, end.X - start.X, Height));
_isFullMatch = true;
Visible = true;
}
else if (NodeArchetype.TryParseText != null && NodeArchetype.TryParseText(filterText, out var data))

View File

@@ -2,6 +2,7 @@
using FlaxEngine;
using FlaxEngine.GUI;
using System;
namespace FlaxEditor.Surface.Elements
{
@@ -12,6 +13,11 @@ namespace FlaxEditor.Surface.Elements
[HideInEditor]
public class OutputBox : Box
{
/// <summary>
/// Distance for the mouse to be considered above the connection
/// </summary>
public float MouseOverConnectionDistance => 100f / Surface.ViewScale;
/// <inheritdoc />
public OutputBox(SurfaceNode parentNode, NodeElementArchetype archetype)
: base(parentNode, archetype, archetype.Position + new Vector2(parentNode.Archetype.Size.X, 0))
@@ -43,23 +49,96 @@ namespace FlaxEditor.Surface.Elements
*/
}
/// <summary>
/// Checks if a point intersects a connection
/// </summary>
/// <param name="targetBox">The other box.</param>
/// <param name="mousePosition">The mouse position</param>
public bool IntersectsConnection(Box targetBox, ref Vector2 mousePosition)
{
var startPos = Parent.PointToParent(Center);
Vector2 endPos = targetBox.Parent.PointToParent(targetBox.Center);
return IntersectsConnection(ref startPos, ref endPos, ref mousePosition, MouseOverConnectionDistance);
}
/// <summary>
/// Checks if a point intersects a bezier curve
/// </summary>
/// <param name="start">The start location.</param>
/// <param name="end">The end location.</param>
/// <param name="point">The point</param>
/// <param name="distance">Distance at which its an intersection</param>
public static bool IntersectsConnection(ref Vector2 start, ref Vector2 end, ref Vector2 point, float distance)
{
// Pretty much a point in rectangle check
if ((point.X - start.X) * (end.X - point.X) < 0) return false;
float offset = Mathf.Sign(end.Y - start.Y) * distance;
if ((point.Y - (start.Y - offset)) * ((end.Y + offset) - point.Y) < 0) return false;
// Taken from the Render2D.DrawBezier code
float squaredDistance = distance;
var dst = (end - start) * new Vector2(0.5f, 0.05f);
Vector2 control1 = new Vector2(start.X + dst.X, start.Y + dst.Y);
Vector2 control2 = new Vector2(end.X - dst.X, end.Y + dst.Y);
Vector2 d1 = control1 - start;
Vector2 d2 = control2 - control1;
Vector2 d3 = end - control2;
float len = d1.Length + d2.Length + d3.Length;
int segmentCount = Math.Min(Math.Max(Mathf.CeilToInt(len * 0.05f), 1), 100);
float segmentCountInv = 1.0f / segmentCount;
Bezier(ref start, ref control1, ref control2, ref end, 0, out Vector2 p);
for (int i = 1; i <= segmentCount; i++)
{
Vector2 oldp = p;
float t = i * segmentCountInv;
Bezier(ref start, ref control1, ref control2, ref end, t, out p);
// Maybe it would be reasonable to return the point?
CollisionsHelper.ClosestPointPointLine(ref point, ref oldp, ref p, out Vector2 result);
if (Vector2.DistanceSquared(point, result) <= squaredDistance)
{
return true;
}
}
return false;
}
private static void Bezier(ref Vector2 p0, ref Vector2 p1, ref Vector2 p2, ref Vector2 p3, float alpha, out Vector2 result)
{
Vector2.Lerp(ref p0, ref p1, alpha, out var p01);
Vector2.Lerp(ref p1, ref p2, alpha, out var p12);
Vector2.Lerp(ref p2, ref p3, alpha, out var p23);
Vector2.Lerp(ref p01, ref p12, alpha, out var p012);
Vector2.Lerp(ref p12, ref p23, alpha, out var p123);
Vector2.Lerp(ref p012, ref p123, alpha, out result);
}
/// <summary>
/// Draw all connections coming from this box.
/// </summary>
public void DrawConnections()
public void DrawConnections(ref Vector2 mousePosition)
{
float mouseOverDistance = MouseOverConnectionDistance;
// Draw all the connections
var center = Size * 0.5f;
var tmp = PointToParent(ref center);
var startPos = Parent.PointToParent(ref tmp);
var startPos = Parent.PointToParent(Center);
var startHighlight = ConnectionsHighlightIntensity;
for (int i = 0; i < Connections.Count; i++)
{
Box targetBox = Connections[i];
tmp = targetBox.PointToParent(ref center);
Vector2 endPos = targetBox.Parent.PointToParent(ref tmp);
Vector2 endPos = targetBox.Parent.PointToParent(targetBox.Center);
var highlight = 1 + Mathf.Max(startHighlight, targetBox.ConnectionsHighlightIntensity);
var color = _currentTypeColor * highlight;
// TODO: Figure out how to only draw the topmost connection
if (IntersectsConnection(ref startPos, ref endPos, ref mousePosition, mouseOverDistance))
{
highlight += 0.5f;
}
DrawConnection(ref startPos, ref endPos, ref color, highlight);
}
}
@@ -70,12 +149,9 @@ namespace FlaxEditor.Surface.Elements
public void DrawSelectedConnection(Box targetBox)
{
// Draw all the connections
var center = Size * 0.5f;
var tmp = PointToParent(ref center);
var startPos = Parent.PointToParent(ref tmp);
tmp = targetBox.PointToParent(ref center);
Vector2 endPos = targetBox.Parent.PointToParent(ref tmp);
DrawConnection(ref startPos, ref endPos, ref _currentTypeColor, 2);
var startPos = Parent.PointToParent(Center);
Vector2 endPos = targetBox.Parent.PointToParent(targetBox.Center);
DrawConnection(ref startPos, ref endPos, ref _currentTypeColor, 2.5f);
}
/// <inheritdoc />

View File

@@ -125,6 +125,7 @@ namespace FlaxEditor.Surface
AutoFocus = false;
TooltipText = nodeArch.Description;
CullChildren = false;
BackgroundColor = Style.Current.BackgroundNormal;
if (Archetype.DefaultValues != null)
{
@@ -552,19 +553,22 @@ namespace FlaxEditor.Surface
internal Box GetNextBox(Box box)
{
int i = 0;
for (; i < Elements.Count; i++)
// Get the one after it
for (int i = box.IndexInParent + 1; i < Elements.Count; i++)
{
if (Elements[i] == box)
if (Elements[i] is Box b)
{
// We found the box
break;
return b;
}
}
// Get the one after it
i++;
for (; i < Elements.Count; i++)
return null;
}
internal Box GetPreviousBox(Box box)
{
// Get the one before it
for (int i = box.IndexInParent - 1; i >= 0; i--)
{
if (Elements[i] is Box b)
{
@@ -754,29 +758,33 @@ namespace FlaxEditor.Surface
{
if (Elements[j] is OutputBox ob && ob.HasAnyConnection)
{
ob.DrawConnections();
ob.DrawConnections(ref mousePosition);
}
}
}
/// <summary>
/// Draws all selected connections between surface objects related to this node.
/// </summary>
/// <param name="selectedConnectionIndex">The index of the currently selected connection.</param>
public void DrawSelectedConnections(int selectedConnectionIndex)
{
if (_isSelected)
{
bool hasBoxesSelection = HasBoxesSelection;
for (int j = 0; j < Elements.Count; j++)
if (HasBoxesSelection)
{
if (Elements[j] is Box box && box.HasAnyConnection && (!hasBoxesSelection || box.IsSelected))
for (int j = 0; j < Elements.Count; j++)
{
if (box is OutputBox ob)
if (Elements[j] is Box box && box.IsSelected && selectedConnectionIndex < box.Connections.Count)
{
for (int i = 0; i < ob.Connections.Count; i++)
if (box is OutputBox ob)
{
ob.DrawSelectedConnection(ob.Connections[i]);
ob.DrawSelectedConnection(ob.Connections[selectedConnectionIndex]);
}
}
else
{
for (int i = 0; i < box.Connections.Count; i++)
else
{
if (box.Connections[i] is OutputBox outputBox)
if (box.Connections[selectedConnectionIndex] is OutputBox outputBox)
{
outputBox.DrawSelectedConnection(box);
}
@@ -784,6 +792,32 @@ namespace FlaxEditor.Surface
}
}
}
else
{
for (int j = 0; j < Elements.Count; j++)
{
if (Elements[j] is Box box)
{
if (box is OutputBox ob)
{
for (int i = 0; i < ob.Connections.Count; i++)
{
ob.DrawSelectedConnection(ob.Connections[i]);
}
}
else
{
for (int i = 0; i < box.Connections.Count; i++)
{
if (box.Connections[i] is OutputBox outputBox)
{
outputBox.DrawSelectedConnection(box);
}
}
}
}
}
}
}
}
@@ -943,7 +977,7 @@ namespace FlaxEditor.Surface
// Background
var backgroundRect = new Rectangle(Vector2.Zero, Size);
Render2D.FillRectangle(backgroundRect, style.BackgroundNormal);
Render2D.FillRectangle(backgroundRect, BackgroundColor);
// Breakpoint hit
if (Breakpoint.Hit)
@@ -1006,7 +1040,7 @@ namespace FlaxEditor.Surface
}
// Secondary Context Menu
if (button == MouseButton.Right && false)
if (button == MouseButton.Right)
{
if (!IsSelected)
Surface.Select(this);

View File

@@ -10,7 +10,7 @@ namespace FlaxEditor.Surface.Undo
/// The helper structure for Surface node box handle.
/// </summary>
[HideInEditor]
public struct BoxHandle
public struct BoxHandle : IEquatable<BoxHandle>
{
private readonly uint _nodeId;
private readonly int _boxId;
@@ -51,5 +51,27 @@ namespace FlaxEditor.Surface.Undo
throw new Exception("Missing box.");
return box;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is BoxHandle handle && Equals(handle);
}
/// <inheritdoc />
public bool Equals(BoxHandle other)
{
return _nodeId == other._nodeId &&
_boxId == other._boxId;
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return (_nodeId.GetHashCode() * 397) ^ _boxId.GetHashCode();
}
}
}
}

View File

@@ -106,9 +106,12 @@ namespace FlaxEditor.Surface.Undo
}
for (int i = 0; i < output.Length; i++)
{
var box = output[i].Get(context);
oB.Connections.Add(box);
box.Connections.Add(oB);
if (!output[i].Equals(_input))
{
var box = output[i].Get(context);
oB.Connections.Add(box);
box.Connections.Add(oB);
}
}
toUpdate.AddRange(iB.Connections);

View File

@@ -103,7 +103,7 @@ namespace FlaxEditor.Surface
{
for (int j = 0; j < node.Elements.Count; j++)
{
if (node.Elements[j] is Box box)
if (node.Elements[j] is Box box && box.Connections.Count > 0)
{
var dataModelBox = new DataModelBox
{

View File

@@ -103,6 +103,7 @@ namespace FlaxEditor.Surface
for (int i = 0; i < Nodes.Count; i++)
{
Nodes[i].DrawConnections(ref mousePosition);
Nodes[i].DrawSelectedConnections(_selectedConnectionIndex);
}
}

View File

@@ -274,12 +274,47 @@ namespace FlaxEditor.Surface
bool handled = base.OnMouseDoubleClick(location, button);
if (!handled)
CustomMouseDoubleClick?.Invoke(ref location, button, ref handled);
if (handled)
if (!handled)
{
return true;
var mousePos = _rootControl.PointFromParent(ref _mousePos);
if (IntersectsConnection(mousePos, out InputBox inputBox, out OutputBox outputBox))
{
if (Undo != null)
{
bool undoEnabled = Undo.Enabled;
Undo.Enabled = false;
var rerouteNode = Context.SpawnNode(7, 29, mousePos);
Undo.Enabled = undoEnabled;
var spawnNodeAction = new AddRemoveNodeAction(rerouteNode, true);
var disconnectBoxesAction = new ConnectBoxesAction(inputBox, outputBox, false);
inputBox.BreakConnection(outputBox);
disconnectBoxesAction.End();
var addConnectionsAction = new EditNodeConnections(Context, rerouteNode);
outputBox.CreateConnection(rerouteNode.GetBoxes().First(b => !b.IsOutput));
rerouteNode.GetBoxes().First(b => b.IsOutput).CreateConnection(inputBox);
addConnectionsAction.End();
Undo.AddAction(new MultiUndoAction(spawnNodeAction, disconnectBoxesAction, addConnectionsAction));
}
else
{
var rerouteNode = Context.SpawnNode(7, 29, mousePos);
inputBox.BreakConnection(outputBox);
outputBox.CreateConnection(rerouteNode.GetBoxes().First(b => !b.IsOutput));
rerouteNode.GetBoxes().First(b => b.IsOutput).CreateConnection(inputBox);
}
MarkAsEdited();
handled = true;
}
}
return false;
return handled;
}
/// <inheritdoc />
@@ -527,6 +562,81 @@ namespace FlaxEditor.Surface
}
return true;
}
if (key == KeyboardKeys.ArrowUp || key == KeyboardKeys.ArrowDown)
{
Box selectedBox = GetSelectedBox(SelectedNodes);
if (selectedBox == null) return true;
Box toSelect = (key == KeyboardKeys.ArrowUp) ?
selectedBox?.ParentNode.GetPreviousBox(selectedBox) :
selectedBox?.ParentNode.GetNextBox(selectedBox);
if (toSelect != null && toSelect.IsOutput == selectedBox.IsOutput)
{
Select(toSelect.ParentNode);
toSelect.ParentNode.SelectBox(toSelect);
}
}
if (key == KeyboardKeys.Tab)
{
Box selectedBox = GetSelectedBox(SelectedNodes);
if (selectedBox == null) return true;
int connectionCount = selectedBox.Connections.Count;
if (connectionCount == 0) return true;
if (Root.GetKey(KeyboardKeys.Shift))
{
_selectedConnectionIndex = ((_selectedConnectionIndex - 1) % connectionCount + connectionCount) % connectionCount;
}
else
{
_selectedConnectionIndex = (_selectedConnectionIndex + 1) % connectionCount;
}
}
if (key == KeyboardKeys.ArrowRight || key == KeyboardKeys.ArrowLeft)
{
Box selectedBox = GetSelectedBox(SelectedNodes);
if (selectedBox == null) return true;
Box toSelect = null;
if ((key == KeyboardKeys.ArrowRight && selectedBox.IsOutput) || (key == KeyboardKeys.ArrowLeft && !selectedBox.IsOutput))
{
if (_selectedConnectionIndex < 0 || _selectedConnectionIndex >= selectedBox.Connections.Count)
{
_selectedConnectionIndex = 0;
}
toSelect = selectedBox.Connections[_selectedConnectionIndex];
}
else
{
// Use the node with the closest Y-level
// Since there are cases like 3 nodes on one side and only 1 node on the other side
var elements = selectedBox.ParentNode.Elements;
float minDistance = float.PositiveInfinity;
for (int i = 0; i < elements.Count; i++)
{
if (elements[i] is Box box && box.IsOutput != selectedBox.IsOutput && Mathf.Abs(box.Y - selectedBox.Y) < minDistance)
{
toSelect = box;
minDistance = Mathf.Abs(box.Y - selectedBox.Y);
}
}
}
if (toSelect != null)
{
Select(toSelect.ParentNode);
toSelect.ParentNode.SelectBox(toSelect);
}
return true;
}
}
return false;
@@ -720,5 +830,31 @@ namespace FlaxEditor.Surface
yLocation
);
}
private bool IntersectsConnection(Vector2 mousePosition, out InputBox inputBox, out OutputBox outputBox)
{
for (int i = 0; i < Nodes.Count; i++)
{
for (int j = 0; j < Nodes[i].Elements.Count; j++)
{
if (Nodes[i].Elements[j] is OutputBox ob)
{
for (int k = 0; k < ob.Connections.Count; k++)
{
if (ob.IntersectsConnection(ob.Connections[k], ref mousePosition))
{
outputBox = ob;
inputBox = ob.Connections[k] as InputBox;
return true;
}
}
}
}
}
outputBox = null;
inputBox = null;
return false;
}
}
}

View File

@@ -39,6 +39,7 @@ namespace FlaxEditor.Surface
private GroupArchetype _customNodesGroup;
private List<NodeArchetype> _customNodes;
private Action _onSave;
private int _selectedConnectionIndex;
internal int _isUpdatingBoxTypes;
@@ -362,6 +363,8 @@ namespace FlaxEditor.Surface
Context.ControlSpawned += OnSurfaceControlSpawned;
Context.ControlDeleted += OnSurfaceControlDeleted;
SelectionChanged += () => { _selectedConnectionIndex = 0; };
// Init drag handlers
DragHandlers.Add(_dragAssets = new DragAssets<DragDropEventArgs>(ValidateDragItem));
DragHandlers.Add(_dragParameters = new DragNames<DragDropEventArgs>(SurfaceParameter.DragPrefix, ValidateDragParameter));

View File

@@ -27,6 +27,7 @@ const Char* SplashScreenQuotes[] =
TEXT("Removing 'C:\\Windows\\'"),
#elif PLATFORM_LINUX
TEXT("Time to switch to Windows?"),
TEXT("Installing Windows 10..."),
#endif
TEXT("Kappa!"),
TEXT("How you doin'?"),
@@ -116,6 +117,9 @@ const Char* SplashScreenQuotes[] =
TEXT("Compiling Shaders (93,788)"),
TEXT("Hi There"),
TEXT("BAGUETTE"),
TEXT("All we had to do was follow the damn train, CJ"),
TEXT("28 stab wounds"),
TEXT("Here we go again"),
};
SplashScreen::~SplashScreen()

View File

@@ -245,12 +245,22 @@ bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info)
{
return FindAsset(id, info);
}
#if !USE_EDITOR
if (FileSystem::IsRelative(path))
{
// Additional check if user provides path relative to the project folder (eg. Content/SomeAssets/MyFile.json)
const String absolutePath = Globals::ProjectFolder / *path;
if (_pathsMapping.TryGet(absolutePath, id))
{
return FindAsset(id, info);
}
}
#endif
// Find asset in registry
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
auto& e = i->Value;
if (e.Info.Path == path)
{
// Validate file exists

View File

@@ -7,6 +7,7 @@ namespace FlaxEditor.Content.Settings
{
partial class BuildSettings
{
#if FLAX_EDITOR
/// <summary>
/// The build presets.
/// </summary>
@@ -60,5 +61,6 @@ namespace FlaxEditor.Content.Settings
}
return null;
}
#endif
}
}

View File

@@ -90,41 +90,53 @@ namespace FlaxEditor.Content.Settings
[EditorOrder(1100), EditorDisplay("Other Settings"), Tooltip("The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair).")]
public Dictionary<string, JsonAsset> CustomSettings;
#if FLAX_EDITOR || PLATFORM_WINDOWS
/// <summary>
/// Reference to <see cref="WindowsPlatformSettings"/> asset. Used to apply configuration on Windows platform.
/// </summary>
[EditorOrder(2010), EditorDisplay("Platform Settings", "Windows"), AssetReference(typeof(WindowsPlatformSettings), true), Tooltip("Reference to Windows Platform Settings asset")]
public JsonAsset WindowsPlatform;
#endif
#if FLAX_EDITOR || PLATFORM_UWP || PLATFORM_XBOX_ONE
/// <summary>
/// Reference to <see cref="UWPPlatformSettings"/> asset. Used to apply configuration on Universal Windows Platform.
/// </summary>
[EditorOrder(2020), EditorDisplay("Platform Settings", "Universal Windows Platform"), AssetReference(typeof(UWPPlatformSettings), true), Tooltip("Reference to Universal Windows Platform Settings asset")]
public JsonAsset UWPPlatform;
#endif
#if FLAX_EDITOR || PLATFORM_LINUX
/// <summary>
/// Reference to <see cref="LinuxPlatformSettings"/> asset. Used to apply configuration on Linux platform.
/// </summary>
[EditorOrder(2030), EditorDisplay("Platform Settings", "Linux"), AssetReference(typeof(LinuxPlatformSettings), true), Tooltip("Reference to Linux Platform Settings asset")]
public JsonAsset LinuxPlatform;
#endif
#if FLAX_EDITOR || PLATFORM_PS4
/// <summary>
/// Reference to PS4 Platform Settings asset. Used to apply configuration on PS4 platform.
/// </summary>
[EditorOrder(2040), EditorDisplay("Platform Settings", "PlayStation 4"), AssetReference(PS4PlatformSettingsTypename, true), Tooltip("Reference to PS4 Platform Settings asset")]
public JsonAsset PS4Platform;
#endif
#if FLAX_EDITOR || PLATFORM_XBOX_SCARLETT
/// <summary>
/// Reference to Xbox Scarlett Platform Settings asset. Used to apply configuration on Xbox Scarlett platform.
/// </summary>
[EditorOrder(2050), EditorDisplay("Platform Settings", "Xbox Scarlett"), AssetReference(XboxScarlettPlatformSettingsTypename, true), Tooltip("Reference to Xbox Scarlett Platform Settings asset")]
public JsonAsset XboxScarlettPlatform;
#endif
#if FLAX_EDITOR || PLATFORM_ANDROID
/// <summary>
/// Reference to <see cref="AndroidPlatformSettings"/> asset. Used to apply configuration on Android platform.
/// </summary>
[EditorOrder(2060), EditorDisplay("Platform Settings", "Android"), AssetReference(typeof(AndroidPlatformSettings), true), Tooltip("Reference to Android Platform Settings asset")]
public JsonAsset AndroidPlatform;
#endif
/// <summary>
/// Gets the absolute path to the game settings asset file.
@@ -202,20 +214,32 @@ namespace FlaxEditor.Content.Settings
return LoadAsset<BuildSettings>(gameSettings.GameCooking) as T;
if (type == typeof(InputSettings))
return LoadAsset<InputSettings>(gameSettings.Input) as T;
if (type == typeof(WindowsPlatformSettings))
return LoadAsset<WindowsPlatformSettings>(gameSettings.WindowsPlatform) as T;
if (type == typeof(UWPPlatformSettings))
return LoadAsset<UWPPlatformSettings>(gameSettings.UWPPlatform) as T;
if (type == typeof(LinuxPlatformSettings))
return LoadAsset<LinuxPlatformSettings>(gameSettings.LinuxPlatform) as T;
if (type.FullName == PS4PlatformSettingsTypename)
return LoadAsset(gameSettings.PS4Platform, PS4PlatformSettingsTypename) as T;
if (type.FullName == XboxScarlettPlatformSettingsTypename)
return LoadAsset(gameSettings.XboxScarlettPlatform, XboxScarlettPlatformSettingsTypename) as T;
if (type == typeof(AndroidPlatformSettings))
return LoadAsset<AndroidPlatformSettings>(gameSettings.AndroidPlatform) as T;
if (type == typeof(AudioSettings))
return LoadAsset<AudioSettings>(gameSettings.Audio) as T;
#if FLAX_EDITOR || PLATFORM_WINDOWS
if (type == typeof(WindowsPlatformSettings))
return LoadAsset<WindowsPlatformSettings>(gameSettings.WindowsPlatform) as T;
#endif
#if FLAX_EDITOR || PLATFORM_UWP || PLATFORM_XBOX_ONE
if (type == typeof(UWPPlatformSettings))
return LoadAsset<UWPPlatformSettings>(gameSettings.UWPPlatform) as T;
#endif
#if FLAX_EDITOR || PLATFORM_LINUX
if (type == typeof(LinuxPlatformSettings))
return LoadAsset<LinuxPlatformSettings>(gameSettings.LinuxPlatform) as T;
#endif
#if FLAX_EDITOR || PLATFORM_PS4
if (type.FullName == PS4PlatformSettingsTypename)
return LoadAsset(gameSettings.PS4Platform, PS4PlatformSettingsTypename) as T;
#endif
#if FLAX_EDITOR || PLATFORM_XBOX_SCARLETT
if (type.FullName == XboxScarlettPlatformSettingsTypename)
return LoadAsset(gameSettings.XboxScarlettPlatform, XboxScarlettPlatformSettingsTypename) as T;
#endif
#if FLAX_EDITOR || PLATFORM_ANDROID
if (type == typeof(AndroidPlatformSettings))
return LoadAsset<AndroidPlatformSettings>(gameSettings.AndroidPlatform) as T;
#endif
if (gameSettings.CustomSettings != null)
{
@@ -233,6 +257,7 @@ namespace FlaxEditor.Content.Settings
return null;
}
#if FLAX_EDITOR
private static bool SaveAsset<T>(GameSettings gameSettings, ref JsonAsset asset, T obj) where T : SettingsBase
{
if (asset)
@@ -337,5 +362,6 @@ namespace FlaxEditor.Content.Settings
/// </summary>
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void Apply();
#endif
}
}

View File

@@ -269,6 +269,8 @@ public:
{
return String(_data.Get(), _data.Count());
}
StringView ToStringView() const;
};
inline uint32 GetHash(const StringBuilder& key)

View File

@@ -2,6 +2,12 @@
#include "StringView.h"
#include "String.h"
#include "StringBuilder.h"
StringView StringBuilder::ToStringView() const
{
return StringView(_data.Get(), _data.Count());
}
StringView StringView::Empty;

View File

@@ -22,6 +22,9 @@
#include "Engine/Debug/DebugLog.h"
#include "Engine/Render2D/Render2D.h"
#include "Engine/Render2D/FontAsset.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#endif
// Debug draw service configuration
#define DEBUG_DRAW_INITIAL_VB_CAPACITY (4 * 1024)
@@ -514,7 +517,11 @@ void DebugDrawService::Update()
PROFILE_CPU();
// Update lists
const float deltaTime = Time::Update.DeltaTime.GetTotalSeconds();
float deltaTime = Time::Update.DeltaTime.GetTotalSeconds();
#if USE_EDITOR
if (!Editor::IsPlayMode)
deltaTime = Time::Update.UnscaledDeltaTime.GetTotalSeconds();
#endif
GlobalContext.DebugDrawDefault.Update(deltaTime);
GlobalContext.DebugDrawDepthTest.Update(deltaTime);

View File

@@ -121,8 +121,11 @@ public:
API_FIELD() static float PhysicsFPS;
/// <summary>
/// The target amount of the frames rendered per second (actual game FPS).
/// The target amount of the frames rendered per second (target game FPS).
/// </summary>
/// <remarks>
/// To get the actual game FPS use <see cref="Engine.FramesPerSecond"/>
/// </remarks>
API_FIELD() static float DrawFPS;
/// <summary>

View File

@@ -4,7 +4,7 @@
#include "NavigationSettings.h"
#include "NavMesh.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Matrix.h"
#include "Engine/Core/Random.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include <ThirdParty/recastnavigation/DetourNavMesh.h>
@@ -231,6 +231,71 @@ bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const
return true;
}
bool NavMeshRuntime::FindRandomPoint(Vector3& result) const
{
ScopeLock lock(Locker);
const auto query = GetNavMeshQuery();
if (!query || !_navMesh)
{
return false;
}
dtQueryFilter filter;
InitFilter(filter);
dtPolyRef randomPoly = 0;
query->findRandomPoint(&filter, Random::Rand, &randomPoly, &result.X);
if (!randomPoly)
{
return false;
}
Quaternion invRotation;
Quaternion::Invert(Properties.Rotation, invRotation);
Vector3::Transform(result, invRotation, result);
return true;
}
bool NavMeshRuntime::FindRandomPointAroundCircle(const Vector3& center, float radius, Vector3& result) const
{
ScopeLock lock(Locker);
const auto query = GetNavMeshQuery();
if (!query || !_navMesh)
{
return false;
}
dtQueryFilter filter;
InitFilter(filter);
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
Vector3 centerNavMesh;
Vector3::Transform(center, Properties.Rotation, centerNavMesh);
dtPolyRef centerPoly = 0;
query->findNearestPoly(&centerNavMesh.X, &extent.X, &filter, &centerPoly, nullptr);
if (!centerPoly)
{
return false;
}
dtPolyRef randomPoly = 0;
query->findRandomPointAroundCircle(centerPoly, &centerNavMesh.X, radius, &filter, Random::Rand, &randomPoly, &result.X);
if (!randomPoly)
{
return false;
}
Quaternion invRotation;
Quaternion::Invert(Properties.Rotation, invRotation);
Vector3::Transform(result, invRotation, result);
return true;
}
bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const
{
ScopeLock lock(Locker);
@@ -522,6 +587,7 @@ void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMe
#if COMPILE_WITH_DEBUG_DRAW
#include "Engine/Debug/DebugDraw.h"
#include "Engine/Core/Math/Matrix.h"
void DrawPoly(NavMeshRuntime* navMesh, const Matrix& navMeshToWorld, const dtMeshTile& tile, const dtPoly& poly)
{

View File

@@ -127,6 +127,22 @@ public:
/// <returns>True if found valid location on the navmesh, otherwise false.</returns>
bool ProjectPoint(const Vector3& point, Vector3& result) const;
/// <summary>
/// Finds random location on nav mesh.
/// </summary>
/// <param name="result">The result position on the navmesh (valid only if method returns true).</param>
/// <returns>True if found valid location on the navmesh, otherwise false.</returns>
bool FindRandomPoint(Vector3& result) const;
/// <summary>
/// Finds random location on nav mesh within the reach of specified location.
/// </summary>
/// <param name="center">The source point to find random location around it.</param>
/// <param name="radius">The search distance for a random point. Maximum distance for a result point from the center of the circle.</param>
/// <param name="result">The result position on the navmesh (valid only if method returns true).</param>
/// <returns>True if found valid location on the navmesh, otherwise false.</returns>
bool FindRandomPointAroundCircle(const Vector3& center, float radius, Vector3& result) const;
/// <summary>
/// Casts a 'walkability' ray along the surface of the navigation mesh from the start position toward the end position.
/// </summary>

View File

@@ -315,6 +315,20 @@ bool Navigation::ProjectPoint(const Vector3& point, Vector3& result)
return NavMeshes.First()->ProjectPoint(point, result);
}
bool Navigation::FindRandomPoint(Vector3& result)
{
if (NavMeshes.IsEmpty())
return false;
return NavMeshes.First()->FindRandomPoint(result);
}
bool Navigation::FindRandomPointAroundCircle(const Vector3& center, float radius, Vector3& result)
{
if (NavMeshes.IsEmpty())
return false;
return NavMeshes.First()->FindRandomPointAroundCircle(center, radius, result);
}
bool Navigation::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo)
{
if (NavMeshes.IsEmpty())

View File

@@ -48,6 +48,22 @@ public:
/// <returns>True if found valid location on the navmesh, otherwise false.</returns>
API_FUNCTION() static bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result);
/// <summary>
/// Finds random location on nav mesh.
/// </summary>
/// <param name="result">The result position on the navmesh (valid only if method returns true).</param>
/// <returns>True if found valid location on the navmesh, otherwise false.</returns>
API_FUNCTION() static bool FindRandomPoint(API_PARAM(Out) Vector3& result);
/// <summary>
/// Finds random location on nav mesh within the reach of specified location.
/// </summary>
/// <param name="center">The source point to find random location around it.</param>
/// <param name="radius">The search distance for a random point. Maximum distance for a result point from the center of the circle.</param>
/// <param name="result">The result position on the navmesh (valid only if method returns true).</param>
/// <returns>True if found valid location on the navmesh, otherwise false.</returns>
API_FUNCTION() static bool FindRandomPointAroundCircle(const Vector3& center, float radius, API_PARAM(Out) Vector3& result);
/// <summary>
/// Casts a 'walkability' ray along the surface of the navigation mesh from the start position toward the end position.
/// </summary>

View File

@@ -9,7 +9,9 @@ namespace FlaxEditor.Content.Settings
/// <summary>
/// The collision layers masks. Used to define layer-based collision detection.
/// </summary>
[EditorOrder(1040), EditorDisplay("Layers Matrix"), CustomEditor(typeof(FlaxEditor.CustomEditors.Dedicated.LayersMatrixEditor))]
#if FLAX_EDITOR
[EditorOrder(1040), EditorDisplay("Layers Matrix"), CustomEditor(typeof(CustomEditors.Dedicated.LayersMatrixEditor))]
#endif
public uint[] LayerMasks = new uint[32];
/// <summary>

View File

@@ -317,64 +317,58 @@ bool AndroidFileSystem::CopyFile(const StringView& dst, const StringView& src)
{
const StringAsANSI<> srcANSI(*src, src.Length());
const StringAsANSI<> dstANSI(*dst, dst.Length());
const char* from = srcANSI.Get();
const char* to = dstANSI.Get();
int fd_to, fd_from;
char buf[4096];
ssize_t nread;
int saved_errno;
int srcFile, dstFile;
char buffer[4096];
ssize_t readSize;
int cachedError;
fd_from = open(from, O_RDONLY);
if (fd_from < 0)
srcFile = open(srcANSI.Get(), O_RDONLY);
if (srcFile < 0)
return true;
fd_to = open(to, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd_to < 0)
dstFile = open(dstANSI.Get(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (dstFile < 0)
goto out_error;
while (nread = read(fd_from, buf, sizeof buf), nread > 0)
while (readSize = read(srcFile, buffer, sizeof(buffer)), readSize > 0)
{
char* out_ptr = buf;
ssize_t nwritten;
char* ptr = buffer;
ssize_t writeSize;
do
{
nwritten = write(fd_to, out_ptr, nread);
if (nwritten >= 0)
writeSize = write(dstFile, ptr, readSize);
if (writeSize >= 0)
{
nread -= nwritten;
out_ptr += nwritten;
readSize -= writeSize;
ptr += writeSize;
}
else if (errno != EINTR)
{
goto out_error;
}
} while (nread > 0);
} while (readSize > 0);
}
if (nread == 0)
if (readSize == 0)
{
if (close(fd_to) < 0)
if (close(dstFile) < 0)
{
fd_to = -1;
dstFile = -1;
goto out_error;
}
close(fd_from);
close(srcFile);
// Success
return false;
}
out_error:
saved_errno = errno;
close(fd_from);
if (fd_to >= 0)
close(fd_to);
errno = saved_errno;
cachedError = errno;
close(srcFile);
if (dstFile >= 0)
close(dstFile);
errno = cachedError;
return true;
}

View File

@@ -98,11 +98,11 @@ void FileSystemBase::NormalizePath(String& path)
}
}
bool FileSystemBase::IsRelative(const String& path)
bool FileSystemBase::IsRelative(const StringView& path)
{
const bool isRooted =
(path.Length() >= 2 && StringUtils::IsAlpha(path[0]) && path[1] == ':') ||
path.StartsWith(TEXT("\\\\")) ||
path.StartsWith(StringView(TEXT("\\\\"), 2), StringSearchCase::CaseSensitive) ||
path.StartsWith('/') ||
path.StartsWith('\\') ||
path.StartsWith('/');

View File

@@ -104,7 +104,7 @@ public:
/// </summary>
/// <param name="path">Input path to check</param>
/// <returns>True if input path is relative one, otherwise false</returns>
static bool IsRelative(const String& path);
static bool IsRelative(const StringView& path);
/// <summary>
/// Retrieves file extension (without a dot)

View File

@@ -417,7 +417,7 @@ void WindowBase::OnClosed()
RenderTask->Enabled = false;
// Delete object
DeleteObject(60);
DeleteObject(1);
}
void WindowBase::OnGotFocus()

View File

@@ -47,6 +47,15 @@ bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView&
return false;
}
bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
{
const StringAsANSI<> pathAnsi(*path, path.Length());
char cmd[2048];
sprintf(cmd, "nautilus %s &", pathAnsi.Get());
system(cmd);
return false;
}
bool LinuxFileSystem::CreateDirectory(const StringView& path)
{
const StringAsANSI<> pathAnsi(*path, path.Length());
@@ -309,64 +318,58 @@ bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src)
{
const StringAsANSI<> srcANSI(*src, src.Length());
const StringAsANSI<> dstANSI(*dst, dst.Length());
const char* from = srcANSI.Get();
const char* to = dstANSI.Get();
int fd_to, fd_from;
char buf[4096];
ssize_t nread;
int saved_errno;
int srcFile, dstFile;
char buffer[4096];
ssize_t readSize;
int cachedError;
fd_from = open(from, O_RDONLY);
if (fd_from < 0)
srcFile = open(srcANSI.Get(), O_RDONLY);
if (srcFile < 0)
return true;
fd_to = open(to, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd_to < 0)
dstFile = open(dstANSI.Get(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (dstFile < 0)
goto out_error;
while (nread = read(fd_from, buf, sizeof buf), nread > 0)
while (readSize = read(srcFile, buffer, sizeof(buffer)), readSize > 0)
{
char* out_ptr = buf;
ssize_t nwritten;
char* ptr = buffer;
ssize_t writeSize;
do
{
nwritten = write(fd_to, out_ptr, nread);
if (nwritten >= 0)
writeSize = write(dstFile, ptr, readSize);
if (writeSize >= 0)
{
nread -= nwritten;
out_ptr += nwritten;
readSize -= writeSize;
ptr += writeSize;
}
else if (errno != EINTR)
{
goto out_error;
}
} while (nread > 0);
} while (readSize > 0);
}
if (nread == 0)
if (readSize == 0)
{
if (close(fd_to) < 0)
if (close(dstFile) < 0)
{
fd_to = -1;
dstFile = -1;
goto out_error;
}
close(fd_from);
close(srcFile);
// Success
return false;
}
out_error:
saved_errno = errno;
close(fd_from);
if (fd_to >= 0)
close(fd_to);
errno = saved_errno;
cachedError = errno;
close(srcFile);
if (dstFile >= 0)
close(dstFile);
errno = cachedError;
return true;
}

View File

@@ -15,6 +15,7 @@ public:
// [FileSystemBase]
static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames);
static bool ShowFileExplorer(const StringView& path);
static bool CreateDirectory(const StringView& path);
static bool DeleteDirectory(const String& path, bool deleteContents = true);
static bool DirectoryExists(const StringView& path);

View File

@@ -808,6 +808,8 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
int X11ErrorHandler(X11::Display* display, X11::XErrorEvent* event)
{
if (event->error_code == 5)
return 0; // BadAtom (invalid Atom parameter)
char buffer[256];
XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
LOG(Error, "X11 Error: {0}", String(buffer));
@@ -2055,12 +2057,15 @@ bool LinuxPlatform::GetHasFocus()
bool LinuxPlatform::CanOpenUrl(const StringView& url)
{
return false;
return true;
}
void LinuxPlatform::OpenUrl(const StringView& url)
{
// TODO: add support for OpenUrl on Linux
const StringAsANSI<> urlAnsi(*url, url.Length());
char cmd[2048];
sprintf(cmd, "xdg-open %s", urlAnsi.Get());
system(cmd);
}
Vector2 LinuxPlatform::GetMousePosition()

View File

@@ -50,11 +50,32 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
: WindowBase(settings)
{
// Cache data
int32 x = Math::TruncToInt(settings.Position.X);
int32 y = Math::TruncToInt(settings.Position.Y);
int32 width = Math::TruncToInt(settings.Size.X);
int32 height = Math::TruncToInt(settings.Size.Y);
_clientSize = Vector2((float)width, (float)height);
int32 x = 0, y = 0;
switch (settings.StartPosition)
{
case WindowStartPosition::CenterParent:
if (settings.Parent)
{
Rectangle parentBounds = settings.Parent->GetClientBounds();
x = Math::TruncToInt(parentBounds.Location.X + (parentBounds.Size.X - _clientSize.X) * 0.5f);
y = Math::TruncToInt(parentBounds.Location.Y + (parentBounds.Size.Y - _clientSize.Y) * 0.5f);
}
break;
case WindowStartPosition::CenterScreen:
{
Vector2 desktopSize = Platform::GetDesktopSize();
x = Math::TruncToInt((desktopSize.X - _clientSize.X) * 0.5f);
y = Math::TruncToInt((desktopSize.Y - _clientSize.Y) * 0.5f);
}
break;
case WindowStartPosition::Manual:
x = Math::TruncToInt(settings.Position.X);
y = Math::TruncToInt(settings.Position.Y);
break;
}
_resizeDisabled = !settings.HasSizingFrame;
auto display = (X11::Display*)LinuxPlatform::GetXDisplay();
@@ -78,25 +99,12 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
// TODO: implement all window settings
/*
WindowStartPosition StartPosition;
bool Fullscreen;
bool AllowInput;
bool AllowMinimize;
bool AllowMaximize;
bool AllowDragAndDrop;
*/
switch (settings.StartPosition)
{
case WindowStartPosition::CenterParent:
break;
case WindowStartPosition::CenterScreen:
break;
case WindowStartPosition::Manual:
break;
default: ;
}
const X11::Window window = X11::XCreateWindow(
display, X11::XRootWindow(display, screen), x, y,
width, height, 0, visualInfo->depth, InputOutput,
@@ -161,17 +169,19 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
// Adjust style for borderless windows
if (!settings.HasBorder)
{
// [Reference: https://www.tonyobryan.com//index.php?article=9]
typedef struct X11Hints
{
unsigned long flags = 0;
unsigned long functions = 0;
unsigned long decorations = 0;
long inputMode = 0;
unsigned long status = 0;
unsigned long flags;
unsigned long functions;
unsigned long decorations;
long inputMode;
unsigned long status;
} X11Hints;
X11Hints hints;
hints.flags = 2;
X11::Atom wmHints = X11::XInternAtom(display, "_MOTIF_WM_HINTS", 1);
hints.decorations = 0;
X11::Atom wmHints = X11::XInternAtom(display, "_MOTIF_WM_HINTS", 0);
X11::Atom property;
if (wmHints)
X11::XChangeProperty(display, window, property, property, 32, PropModeReplace, (unsigned char*)&hints, 5);
@@ -180,13 +190,13 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
// Adjust type for utility windows
if (!settings.IsRegularWindow)
{
X11::Atom wmTypeUtility = X11::XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", 0);
X11::Atom wmType = X11::XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0);
if (wmTypeUtility && wmType)
X11::XChangeProperty(display, window, wmTypeUtility, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)&wmType, 1);
X11::Atom value = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", 0);
X11::Atom wmType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0);
if (value && wmType)
X11::XChangeProperty(display, window, wmType, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)&value, 1);
}
// Initialize statee
// Initialize state
X11::Atom wmState = X11::XInternAtom(display, "_NET_WM_STATE", 0);
X11::Atom wmStateAbove = X11::XInternAtom(display, "_NET_WM_STATE_ABOVE", 0);
X11::Atom wmSateSkipTaskbar = X11::XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", 0);
@@ -231,6 +241,7 @@ void LinuxWindow::Show()
{
if (!_visible)
{
InitSwapChain();
if (_showAfterFirstPaint)
{
if (RenderTask)

View File

@@ -209,6 +209,7 @@ namespace
Matrix3x3 TransformCached;
Array<ClipMask, InlinedAllocation<64>> ClipLayersStack;
Array<Color, InlinedAllocation<64>> TintLayersStack;
// Shader
AssetReference<Shader> GUIShader;
@@ -252,9 +253,9 @@ FORCE_INLINE Render2DVertex MakeVertex(const Vector2& pos, const Vector2& uv, co
{
point,
Half2(uv),
color,
color * TintLayersStack.Peek(),
{ 0.0f, (float)Render2D::Features },
ClipLayersStack.Peek().Mask,
ClipLayersStack.Peek().Mask
};
}
@@ -270,6 +271,18 @@ FORCE_INLINE Render2DVertex MakeVertex(const Vector2& point, const Vector2& uv,
};
}
FORCE_INLINE Render2DVertex MakeVertex(const Vector2& point, const Vector2& uv, const Color& color, const RotatedRectangle& mask, const Vector2& customData, const Color& tint)
{
return
{
point,
Half2(uv),
color * tint,
customData,
mask
};
}
void WriteTri(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& uv0, const Vector2& uv1, const Vector2& uv2, const Color& color0, const Color& color1, const Color& color2)
{
Render2DVertex tris[3];
@@ -283,7 +296,7 @@ void WriteTri(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vec
indices[1] = VBIndex + 1;
indices[2] = VBIndex + 2;
IB.Write(indices, sizeof(indices));
VBIndex += 3;
IBIndex += 3;
}
@@ -548,6 +561,7 @@ bool Render2DService::Init()
void Render2DService::Dispose()
{
TintLayersStack.Resize(0);
ClipLayersStack.Resize(0);
DrawCalls.Resize(0);
Lines.Resize(0);
@@ -620,6 +634,10 @@ void Render2D::Begin(GPUContext* context, GPUTextureView* output, GPUTextureView
ClipLayersStack.Clear();
ClipLayersStack.Add({ defaultMask, defaultBounds });
// Initialize default tint stack
TintLayersStack.Clear();
TintLayersStack.Add({ 1, 1, 1, 1 });
// Scissors can be enabled only for 2D orthographic projections
IsScissorsRectEnabled = false;
@@ -795,6 +813,25 @@ void Render2D::PopClip()
OnClipScissors();
}
void Render2D::PushTint(const Color& tint, bool inherit)
{
RENDER2D_CHECK_RENDERING_STATE;
TintLayersStack.Push(inherit ? tint * TintLayersStack.Peek() : tint);
}
void Render2D::PeekTint(Color& tint)
{
tint = TintLayersStack.Peek();
}
void Render2D::PopTint()
{
RENDER2D_CHECK_RENDERING_STATE;
TintLayersStack.Pop();
}
void CalculateKernelSize(float strength, int32& kernelSize, int32& downSample)
{
kernelSize = Math::RoundToInt(strength * 3.0f);
@@ -1263,12 +1300,32 @@ void Render2D::DrawText(Font* font, const StringView& text, const TextRange& tex
DrawText(font, StringView(text.Get() + textRange.StartIndex, textRange.Length()), color, layout, customMaterial);
}
FORCE_INLINE bool NeedAlphaWithTint(const Color& color)
{
return (color.A * TintLayersStack.Peek().A) < 1.0f;
}
FORCE_INLINE bool NeedAlphaWithTint(const Color& color1, const Color& color2)
{
return (color1.A * TintLayersStack.Peek().A) < 1.0f || (color2.A * TintLayersStack.Peek().A) < 1.0f;
}
FORCE_INLINE bool NeedAlphaWithTint(const Color& color1, const Color& color2, const Color& color3)
{
return (color1.A * TintLayersStack.Peek().A) < 1.0f || (color2.A * TintLayersStack.Peek().A) < 1.0f || (color3.A * TintLayersStack.Peek().A) < 1.0f;
}
FORCE_INLINE bool NeedAlphaWithTint(const Color& color1, const Color& color2, const Color& color3, const Color& color4)
{
return (color1.A * TintLayersStack.Peek().A) < 1.0f || (color2.A * TintLayersStack.Peek().A) < 1.0f || (color3.A * TintLayersStack.Peek().A) < 1.0f || (color4.A * TintLayersStack.Peek().A) < 1.0f;
}
void Render2D::FillRectangle(const Rectangle& rect, const Color& color)
{
RENDER2D_CHECK_RENDERING_STATE;
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.Type = color.A < 1.0f ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.Type = NeedAlphaWithTint(color) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.StartIB = IBIndex;
drawCall.CountIB = 6;
WriteRect(rect, color);
@@ -1279,7 +1336,7 @@ void Render2D::FillRectangle(const Rectangle& rect, const Color& color1, const C
RENDER2D_CHECK_RENDERING_STATE;
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.Type = color1.A < 1.0f || color2.A < 1.0f || color3.A < 1.0f || color4.A < 1.0f ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.Type = NeedAlphaWithTint(color1, color2, color3, color4) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.StartIB = IBIndex;
drawCall.CountIB = 6;
WriteRect(rect, color1, color2, color3, color4);
@@ -1374,7 +1431,7 @@ void Render2D::DrawRectangle(const Rectangle& rect, const Color& color1, const C
}
#else
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.Type = color1.A < 1.0f || color2.A < 1.0f ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.Type = NeedAlphaWithTint(color1, color2) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.StartIB = IBIndex;
drawCall.CountIB = 4 * (6 + 3);
@@ -1638,7 +1695,7 @@ void DrawLines(const Vector2* points, int32 pointsCount, const Color& color1, co
#else
const float thicknessHalf = thickness * 0.5f;
drawCall.Type = color1.A < 1.0f || color2.A < 1.0f ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.Type = NeedAlphaWithTint(color1, color2) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.CountIB = 0;
ApplyTransform(points[0], p1t);
@@ -1748,9 +1805,9 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength)
void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Vector2>& vertices, const Span<Vector2>& uvs)
{
CHECK(vertices.Length() == uvs.Length())
RENDER2D_CHECK_RENDERING_STATE;
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.Type = DrawCallType::FillTexture;
drawCall.StartIB = IBIndex;
@@ -1764,9 +1821,9 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span<Vector2>& vertice
void Render2D::FillTriangles(const Span<Vector2>& vertices, const Span<Color>& colors, bool useAlpha)
{
CHECK(vertices.Length() == colors.Length());
RENDER2D_CHECK_RENDERING_STATE;
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.Type = useAlpha ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.StartIB = IBIndex;
@@ -1779,9 +1836,9 @@ void Render2D::FillTriangles(const Span<Vector2>& vertices, const Span<Color>& c
void Render2D::FillTriangle(const Vector2& p0, const Vector2& p1, const Vector2& p2, const Color& color)
{
RENDER2D_CHECK_RENDERING_STATE;
Render2DDrawCall& drawCall = DrawCalls.AddOne();
drawCall.Type = color.A < 1.0f ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.Type = NeedAlphaWithTint(color) ? DrawCallType::FillRect : DrawCallType::FillRectNoAlpha;
drawCall.StartIB = IBIndex;
drawCall.CountIB = 3;
WriteTri(p0, p1, p2, color, color, color);

View File

@@ -153,6 +153,24 @@ public:
/// </summary>
API_FUNCTION() static void PopClip();
/// <summary>
/// Pushes tint color.
/// </summary>
/// <param name="tint">The tint color.</param>
/// <param name="inherit">Multiply <paramref ref="tint"/> by the last tint on the stack.</param>
API_FUNCTION() static void PushTint(API_PARAM(Ref) const Color& tint, bool inherit = true);
/// <summary>
/// Peeks the current tint color.
/// </summary>
/// <param name="tint">The output tint color.</param>
API_FUNCTION() static void PeekTint(API_PARAM(Out) Color& tint);
/// <summary>
/// Pops tint color.
/// </summary>
API_FUNCTION() static void PopTint();
public:
/// <summary>
@@ -348,7 +366,7 @@ public:
/// <param name="vertices">The vertices array.</param>
/// <param name="uvs">The uvs array.</param>
API_FUNCTION() static void DrawTexturedTriangles(GPUTexture* t, const Span<Vector2>& vertices, const Span<Vector2>& uvs);
/// <summary>
/// Draws vertices array.
/// </summary>

View File

@@ -118,6 +118,8 @@ void* MonoCalloc(size_t count, size_t size)
#if USE_MONO_PROFILER
#include "Engine/Core/Types/StringBuilder.h"
struct FlaxMonoProfiler
{
};
@@ -126,7 +128,7 @@ FlaxMonoProfiler Profiler;
struct StackWalkDataResult
{
StringAnsi Buffer;
StringBuilder Buffer;
};
mono_bool OnStackWalk(MonoMethod* method, int32_t native_offset, int32_t il_offset, mono_bool managed, void* data)
@@ -138,16 +140,16 @@ mono_bool OnStackWalk(MonoMethod* method, int32_t native_offset, int32_t il_offs
auto mName = mono_method_get_name(method);
auto mKlassNameSpace = mono_class_get_namespace(mono_method_get_class(method));
auto mKlassName = mono_class_get_name(mono_method_get_class(method));
result->Buffer += mKlassNameSpace;
result->Buffer += ".";
result->Buffer += mKlassName;
result->Buffer += "::";
result->Buffer += mName;
result->Buffer += "\n";
result->Buffer.Append(mKlassNameSpace);
result->Buffer.Append(TEXT("."));
result->Buffer.Append(mKlassName);
result->Buffer.Append(TEXT("::"));
result->Buffer.Append(mName);
result->Buffer.Append(TEXT("\n"));
}
else if (!managed)
{
result->Buffer += "<unmanaged>\n";
result->Buffer.Append(TEXT("<unmanaged>\n"));
}
return 0;
@@ -170,10 +172,10 @@ void OnGCAllocation(MonoProfiler* profiler, MonoObject* obj)
if (details)
{
StackWalkDataResult stackTrace;
stackTrace.Buffer.reserve(1024);
stackTrace.Buffer.SetCapacity(1024);
mono_stack_walk(&OnStackWalk, &stackTrace);
LOG(Info, "GC new: {0}.{1} ({2} bytes). Stack Trace:\n{3}", name_space, name, size, stackTrace.Buffer.c_str());
LOG(Info, "GC new: {0}.{1} ({2} bytes). Stack Trace:\n{3}", String(name_space), String(name), size, stackTrace.Buffer.ToStringView());
}
}
#endif

View File

@@ -77,14 +77,12 @@ void Script::SetParent(Actor* value, bool canBreakPrefabLink)
// Check if value won't change
if (_parent == value)
return;
if (!IsInMainThread())
if (IsDuringPlay() && !IsInMainThread())
{
LOG(Error, "Editing scene hierarchy is only allowed on a main thread.");
return;
}
Level::ScenesLock.Lock();
// Unlink from the old one
if (_parent)
{
@@ -107,8 +105,6 @@ void Script::SetParent(Actor* value, bool canBreakPrefabLink)
_parent->Scripts.Add(this);
}
Level::ScenesLock.Unlock();
// Break prefab link for prefab instance objects
if (HasPrefabLink() && IsDuringPlay() && canBreakPrefabLink)
{
@@ -137,7 +133,7 @@ void Script::SetParent(Actor* value, bool canBreakPrefabLink)
Enable();
}
}
else if (!previous && value->IsDuringPlay() && value->IsActiveInHierarchy() && GetEnabled())
else if (!previous && value && value->IsDuringPlay() && value->IsActiveInHierarchy() && GetEnabled())
{
// Call enable when script is added to actor (previous actor was null)
Enable();

View File

@@ -569,11 +569,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
{
auto& tmpImg = GET_TMP_IMG();
LOG(Info, "Resizing texture from {0}x{1} to {2}x{3}.", sourceWidth, sourceHeight, width, height);
// During resizing we need to keep texture aspect ratio
const bool keepAspectRation = false; // TODO: expose as import option
if (keepAspectRation)
const bool keepAspectRatio = false; // TODO: expose as import option
if (keepAspectRatio)
{
const float aspectRatio = static_cast<float>(sourceWidth) / sourceHeight;
if (width >= height)
@@ -583,6 +581,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
}
// Resize source texture
LOG(Info, "Resizing texture from {0}x{1} to {2}x{3}.", sourceWidth, sourceHeight, width, height);
result = DirectX::Resize(*currentImage->GetImages(), width, height, DirectX::TEX_FILTER_LINEAR, tmpImg);
if (FAILED(result))
{

View File

@@ -354,6 +354,8 @@ bool TextureTool::Resize(TextureData& dst, const TextureData& src, int32 dstWidt
#if COMPILE_WITH_DIRECTXTEX
return ResizeDirectXTex(dst, src, dstWidth, dstHeight);
#elif COMPILE_WITH_STB
return ResizeStb(dst, src, dstWidth, dstHeight);
#else
LOG(Warning, "Resizing textures is not supported on this platform.");
return true;

View File

@@ -291,6 +291,7 @@ private:
#if COMPILE_WITH_STB
static bool ExportTextureStb(ImageType type, const StringView& path, const TextureData& textureData);
static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha);
static bool ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight);
#endif
};

View File

@@ -15,10 +15,6 @@
#define STBI_REALLOC(p, newsz) AllocatorExt::Realloc(p, newsz)
#define STBI_REALLOC_SIZED(p, oldsz, newsz) AllocatorExt::Realloc(p, oldsz, newsz)
#define STBI_FREE(p) Allocator::Free(p)
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <ThirdParty/stb/stb_image_write.h>
#define STBI_NO_PSD
#define STBI_NO_PIC
#define STBI_NO_PNM
@@ -27,6 +23,20 @@
#define STB_IMAGE_IMPLEMENTATION
#include <ThirdParty/stb/stb_image.h>
#define STBIW_ASSERT(x) ASSERT(x)
#define STBIW_MALLOC(sz) Allocator::Allocate(sz)
#define STBIW_REALLOC(p, newsz) AllocatorExt::Realloc(p, newsz)
#define STBIW_REALLOC_SIZED(p, oldsz, newsz) AllocatorExt::Realloc(p, oldsz, newsz)
#define STBIW_FREE(p) Allocator::Free(p)
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <ThirdParty/stb/stb_image_write.h>
#define STBIR_ASSERT(x) ASSERT(x)
#define STBIR_MALLOC(sz, c) Allocator::Allocate(sz)
#define STBIR_FREE(p, c) Allocator::Free(p)
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <ThirdParty/stb/stb_image_resize.h>
static void stbWrite(void* context, void* data, int size)
{
auto file = (FileWriteStream*)context;
@@ -171,8 +181,8 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
case ImageType::HDR:
case ImageType::TGA:
{
int x, y, comp;
stbi_uc* stbData = stbi_load_from_memory(fileData.Get(), fileData.Count(), &x, &y, &comp, 4);
int width, height, components;
stbi_uc* stbData = stbi_load_from_memory(fileData.Get(), fileData.Count(), &width, &height, &components, 4);
if (!stbData)
{
LOG(Warning, "Failed to load image.");
@@ -180,20 +190,29 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
}
// Setup texture data
textureData.Width = x;
textureData.Height = y;
textureData.Width = width;
textureData.Height = height;
textureData.Depth = 1;
textureData.Format = PixelFormat::R8G8B8A8_UNorm;
textureData.Items.Resize(1);
textureData.Items[0].Mips.Resize(1);
auto& mip = textureData.Items[0].Mips[0];
mip.RowPitch = sizeof(Color32) * x;
mip.DepthPitch = mip.RowPitch * y;
mip.Lines = y;
mip.RowPitch = sizeof(Color32) * width;
mip.DepthPitch = mip.RowPitch * height;
mip.Lines = height;
mip.Data.Copy(stbData, mip.DepthPitch);
#if USE_EDITOR
// TODO: detect hasAlpha
// Detect alpha channel usage
auto ptrAlpha = (Color32*)mip.Data.Get();
for (int32 y = 0; y < height && !hasAlpha; y++)
{
for (int32 x = 0; x < width && !hasAlpha; x++)
{
hasAlpha |= ptrAlpha->A < 255;
ptrAlpha++;
}
}
#endif
stbi_image_free(stbData);
@@ -241,4 +260,100 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
return false;
}
bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight)
{
// Setup
auto arraySize = src.GetArraySize();
dst.Width = dstWidth;
dst.Height = dstHeight;
dst.Depth = src.Depth;
dst.Format = src.Format;
dst.Items.Resize(arraySize);
auto formatSize = PixelFormatExtensions::SizeInBytes(src.Format);
auto components = PixelFormatExtensions::ComputeComponentsCount(src.Format);
// Resize all array slices
for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)
{
const auto& srcSlice = src.Items[arrayIndex];
auto& dstSlice = dst.Items[arrayIndex];
auto mipLevels = srcSlice.Mips.Count();
dstSlice.Mips.Resize(mipLevels);
// Resize all mip levels
for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
{
const auto& srcMip = srcSlice.Mips[mipIndex];
auto& dstMip = dstSlice.Mips[mipIndex];
auto srcMipWidth = srcMip.RowPitch / formatSize;
auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch;
auto dstMipWidth = Math::Max(dstWidth << mipIndex, 1);
auto dstMipHeight = Math::Max(dstHeight << mipIndex, 1);
// Allocate memory
dstMip.RowPitch = dstMipWidth * formatSize;
dstMip.DepthPitch = dstMip.RowPitch * dstMipHeight;
dstMip.Lines = dstMipHeight;
dstMip.Data.Allocate(dstMip.DepthPitch);
// Resize texture
switch (src.Format)
{
case PixelFormat::R8_Typeless:
case PixelFormat::R8_SInt:
case PixelFormat::R8_SNorm:
case PixelFormat::R8G8_Typeless:
case PixelFormat::R8G8_SInt:
case PixelFormat::R8G8_SNorm:
case PixelFormat::R8G8B8A8_Typeless:
case PixelFormat::R8G8B8A8_UNorm:
case PixelFormat::R8G8B8A8_UInt:
case PixelFormat::R8G8B8A8_SNorm:
case PixelFormat::R8G8B8A8_SInt:
case PixelFormat::B8G8R8A8_UNorm:
case PixelFormat::B8G8R8X8_Typeless:
case PixelFormat::B8G8R8X8_UNorm:
{
if (!stbir_resize_uint8((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
case PixelFormat::R8G8B8A8_UNorm_sRGB:
case PixelFormat::B8G8R8A8_UNorm_sRGB:
case PixelFormat::B8G8R8X8_UNorm_sRGB:
{
auto alphaChannel = src.Format == PixelFormat::B8G8R8X8_UNorm_sRGB ? STBIR_ALPHA_CHANNEL_NONE : 3;
if (!stbir_resize_uint8_srgb((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components, alphaChannel, 0))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
case PixelFormat::R32_Typeless:
case PixelFormat::R32_Float:
case PixelFormat::R32G32_Float:
case PixelFormat::R32G32B32_Float:
case PixelFormat::R32G32B32A32_Float:
{
if (!stbir_resize_float((const float*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (float*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
default:
LOG(Warning, "Cannot resize image. Unsupported format {0}", static_cast<int32>(src.Format));
return true;
}
}
}
return false;
}
#endif

View File

@@ -770,6 +770,11 @@ void ShaderGenerator::ProcessGroupTools(Box* box, Node* node, Value& value)
#undef PLATFORM_CASE
break;
}
case 29:
{
value = tryGetValue(node->GetBox(0), Value::Zero);
break;
}
default:
break;
}

View File

@@ -738,6 +738,12 @@ void VisjectExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
value = Scripting::FindObject<Actor>((Guid)node->Values[0]);
break;
}
case 29:
{
value = tryGetValue(node->GetBox(0), Value::Zero);
break;
}
default:
break;
}

View File

@@ -183,6 +183,13 @@ float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, floa
return dx*dx + dz*dz;
}
float dtDistancePtPtSqr2D(const float* pt, const float* p)
{
float dx = pt[0] - p[0];
float dz = pt[2] - p[2];
return dx*dx + dz*dz;
}
void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const float* verts)
{
tc[0] = 0.0f;
@@ -203,14 +210,18 @@ void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const floa
bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h)
{
const float EPS = 1e-6f;
float v0[3], v1[3], v2[3];
dtVsub(v0, c,a);
dtVsub(v1, b,a);
dtVsub(v2, p,a);
dtVsub(v0, c, a);
dtVsub(v1, b, a);
dtVsub(v2, p, a);
// Compute scaled barycentric coordinates
float denom = v0[0] * v1[2] - v0[2] * v1[0];
if (fabsf(denom) < EPS)
return false;
float u = v1[2] * v2[0] - v1[0] * v2[2];
float v = v0[0] * v2[2] - v0[2] * v2[0];
@@ -220,13 +231,9 @@ bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b
v = -v;
}
// The (sloppy) epsilon is needed to allow to get height of points which
// are interpolated along the edges of the triangles.
float epsilon = - 1e-4f * denom;
// If point lies inside the triangle, return interpolated ycoord.
if (u >= epsilon && v >= epsilon && (u+v) <= denom - epsilon) {
h = a[1] + (v0[1]*u + v1[1]*v) / denom;
if (u >= 0.0f && v >= 0.0f && (u + v) <= denom) {
h = a[1] + (v0[1] * u + v1[1] * v) / denom;
return true;
}
return false;

View File

@@ -283,6 +283,28 @@ inline bool dtVequal(const float* p0, const float* p1)
return d < thr;
}
/// Checks that the specified vector's components are all finite.
/// @param[in] v A point. [(x, y, z)]
/// @return True if all of the point's components are finite, i.e. not NaN
/// or any of the infinities.
inline bool dtVisfinite(const float* v)
{
bool result =
dtMathIsfinite(v[0]) &&
dtMathIsfinite(v[1]) &&
dtMathIsfinite(v[2]);
return result;
}
/// Checks that the specified vector's 2D components are finite.
/// @param[in] v A point. [(x, y, z)]
inline bool dtVisfinite2D(const float* v)
{
bool result = dtMathIsfinite(v[0]) && dtMathIsfinite(v[2]);
return result;
}
/// Derives the dot product of two vectors on the xz-plane. (@p u . @p v)
/// @param[in] u A vector [(x, y, z)]
/// @param[in] v A vector [(x, y, z)]
@@ -395,6 +417,8 @@ bool dtDistancePtPolyEdgesSqr(const float* pt, const float* verts, const int nve
float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, float& t);
float dtDistancePtPtSqr2D(const float* pt, const float* p);
/// Derives the centroid of a convex polygon.
/// @param[out] tc The centroid of the polgyon. [(x, y, z)]
/// @param[in] idx The polygon indices. [(vertIndex) * @p nidx]

View File

@@ -1409,12 +1409,14 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug)
}
// Update agents using off-mesh connection.
for (int i = 0; i < m_maxAgents; ++i)
for (int i = 0; i < nagents; ++i)
{
dtCrowdAgentAnimation* anim = &m_agentAnims[i];
dtCrowdAgent* ag = agents[i];
const int idx = (int)(ag - m_agents);
dtCrowdAgentAnimation* anim = &m_agentAnims[idx];
if (!anim->active)
continue;
dtCrowdAgent* ag = agents[i];
anim->t += dt;
if (anim->t > anim->tmax)

View File

@@ -16,5 +16,6 @@ inline float dtMathCeilf(float x) { return ceilf(x); }
inline float dtMathCosf(float x) { return cosf(x); }
inline float dtMathSinf(float x) { return sinf(x); }
inline float dtMathAtan2f(float y, float x) { return atan2f(y, x); }
inline bool dtMathIsfinite(float x) { return isfinite(x); }
#endif

View File

@@ -616,63 +616,84 @@ void dtNavMesh::baseOffMeshLinks(dtMeshTile* tile)
}
}
void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const
namespace
{
const dtMeshTile* tile = 0;
const dtPoly* poly = 0;
getTileAndPolyByRefUnsafe(ref, &tile, &poly);
// Off-mesh connections don't have detail polygons.
if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
template<bool onlyBoundary>
void closestPointOnDetailEdges(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* closest)
{
const float* v0 = &tile->verts[poly->verts[0]*3];
const float* v1 = &tile->verts[poly->verts[1]*3];
const float d0 = dtVdist(pos, v0);
const float d1 = dtVdist(pos, v1);
const float u = d0 / (d0+d1);
dtVlerp(closest, v0, v1, u);
if (posOverPoly)
*posOverPoly = false;
return;
const unsigned int ip = (unsigned int)(poly - tile->polys);
const dtPolyDetail* pd = &tile->detailMeshes[ip];
float dmin = FLT_MAX;
float tmin = 0;
const float* pmin = 0;
const float* pmax = 0;
for (int i = 0; i < pd->triCount; i++)
{
const unsigned char* tris = &tile->detailTris[(pd->triBase + i) * 4];
const int ANY_BOUNDARY_EDGE =
(DT_DETAIL_EDGE_BOUNDARY << 0) |
(DT_DETAIL_EDGE_BOUNDARY << 2) |
(DT_DETAIL_EDGE_BOUNDARY << 4);
if (onlyBoundary && (tris[3] & ANY_BOUNDARY_EDGE) == 0)
continue;
const float* v[3];
for (int j = 0; j < 3; ++j)
{
if (tris[j] < poly->vertCount)
v[j] = &tile->verts[poly->verts[tris[j]] * 3];
else
v[j] = &tile->detailVerts[(pd->vertBase + (tris[j] - poly->vertCount)) * 3];
}
for (int k = 0, j = 2; k < 3; j = k++)
{
if ((dtGetDetailTriEdgeFlags(tris[3], j) & DT_DETAIL_EDGE_BOUNDARY) == 0 &&
(onlyBoundary || tris[j] < tris[k]))
{
// Only looking at boundary edges and this is internal, or
// this is an inner edge that we will see again or have already seen.
continue;
}
float t;
float d = dtDistancePtSegSqr2D(pos, v[j], v[k], t);
if (d < dmin)
{
dmin = d;
tmin = t;
pmin = v[j];
pmax = v[k];
}
}
}
dtVlerp(closest, pmin, pmax, tmin);
}
}
bool dtNavMesh::getPolyHeight(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* height) const
{
// Off-mesh connections do not have detail polys and getting height
// over them does not make sense.
if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
return false;
const unsigned int ip = (unsigned int)(poly - tile->polys);
const dtPolyDetail* pd = &tile->detailMeshes[ip];
// Clamp point to be inside the polygon.
float verts[DT_VERTS_PER_POLYGON*3];
float edged[DT_VERTS_PER_POLYGON];
float edget[DT_VERTS_PER_POLYGON];
const int nv = poly->vertCount;
for (int i = 0; i < nv; ++i)
dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]);
dtVcopy(closest, pos);
if (!dtDistancePtPolyEdgesSqr(pos, verts, nv, edged, edget))
{
// Point is outside the polygon, dtClamp to nearest edge.
float dmin = edged[0];
int imin = 0;
for (int i = 1; i < nv; ++i)
{
if (edged[i] < dmin)
{
dmin = edged[i];
imin = i;
}
}
const float* va = &verts[imin*3];
const float* vb = &verts[((imin+1)%nv)*3];
dtVlerp(closest, va, vb, edget[imin]);
if (posOverPoly)
*posOverPoly = false;
}
else
{
if (posOverPoly)
*posOverPoly = true;
}
if (!dtPointInPolygon(pos, verts, nv))
return false;
if (!height)
return true;
// Find height at the location.
for (int j = 0; j < pd->triCount; ++j)
@@ -687,12 +708,53 @@ void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* close
v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3];
}
float h;
if (dtClosestHeightPointTriangle(closest, v[0], v[1], v[2], h))
if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h))
{
closest[1] = h;
break;
*height = h;
return true;
}
}
// If all triangle checks failed above (can happen with degenerate triangles
// or larger floating point values) the point is on an edge, so just select
// closest. This should almost never happen so the extra iteration here is
// ok.
float closest[3];
closestPointOnDetailEdges<false>(tile, poly, pos, closest);
*height = closest[1];
return true;
}
void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const
{
const dtMeshTile* tile = 0;
const dtPoly* poly = 0;
getTileAndPolyByRefUnsafe(ref, &tile, &poly);
dtVcopy(closest, pos);
if (getPolyHeight(tile, poly, pos, &closest[1]))
{
if (posOverPoly)
*posOverPoly = true;
return;
}
if (posOverPoly)
*posOverPoly = false;
// Off-mesh connections don't have detail polygons.
if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
{
const float* v0 = &tile->verts[poly->verts[0]*3];
const float* v1 = &tile->verts[poly->verts[1]*3];
float t;
dtDistancePtSegSqr2D(pos, v0, v1, t);
dtVlerp(closest, v0, v1, t);
return;
}
// Outside poly that is not an offmesh connection.
closestPointOnDetailEdges<true>(tile, poly, pos, closest);
}
dtPolyRef dtNavMesh::findNearestPolyInTile(const dtMeshTile* tile,
@@ -852,6 +914,13 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags,
return DT_FAILURE | DT_WRONG_MAGIC;
if (header->version != DT_NAVMESH_VERSION)
return DT_FAILURE | DT_WRONG_VERSION;
#ifndef DT_POLYREF64
// Do not allow adding more polygons than specified in the NavMesh's maxPolys constraint.
// Otherwise, the poly ID cannot be represented with the given number of bits.
if (m_polyBits < dtIlog2(dtNextPow2((unsigned int)header->polyCount)))
return DT_FAILURE | DT_INVALID_PARAM;
#endif
// Make sure the location is free.
if (getTileAt(header->x, header->y, header->layer))

View File

@@ -130,6 +130,11 @@ enum dtRaycastOptions
DT_RAYCAST_USE_COSTS = 0x01, ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost
};
enum dtDetailTriEdgeFlags
{
DT_DETAIL_EDGE_BOUNDARY = 0x01, ///< Detail triangle edge is part of the poly boundary
};
/// Limit raycasting during any angle pahfinding
/// The limit is given as a multiple of the character radius
@@ -287,7 +292,8 @@ struct dtMeshTile
/// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount]
float* detailVerts;
/// The detail mesh's triangles. [(vertA, vertB, vertC) * dtMeshHeader::detailTriCount]
/// The detail mesh's triangles. [(vertA, vertB, vertC, triFlags) * dtMeshHeader::detailTriCount].
/// See dtDetailTriEdgeFlags and dtGetDetailTriEdgeFlags.
unsigned char* detailTris;
/// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount]
@@ -305,6 +311,15 @@ private:
dtMeshTile& operator=(const dtMeshTile&);
};
/// Get flags for edge in detail triangle.
/// @param triFlags[in] The flags for the triangle (last component of detail vertices above).
/// @param edgeIndex[in] The index of the first vertex of the edge. For instance, if 0,
/// returns flags for edge AB.
inline int dtGetDetailTriEdgeFlags(unsigned char triFlags, int edgeIndex)
{
return (triFlags >> (edgeIndex * 2)) & 0x3;
}
/// Configuration parameters used to define multi-tile navigation meshes.
/// The values are used to allocate space during the initialization of a navigation mesh.
/// @see dtNavMesh::init()
@@ -314,8 +329,8 @@ struct dtNavMeshParams
float orig[3]; ///< The world space origin of the navigation mesh's tile space. [(x, y, z)]
float tileWidth; ///< The width of each tile. (Along the x-axis.)
float tileHeight; ///< The height of each tile. (Along the z-axis.)
int maxTiles; ///< The maximum number of tiles the navigation mesh can contain.
int maxPolys; ///< The maximum number of polygons each tile can contain.
int maxTiles; ///< The maximum number of tiles the navigation mesh can contain. This and maxPolys are used to calculate how many bits are needed to identify tiles and polygons uniquely.
int maxPolys; ///< The maximum number of polygons each tile can contain. This and maxTiles are used to calculate how many bits are needed to identify tiles and polygons uniquely.
};
/// A navigation mesh based on tiles of convex polygons.
@@ -636,6 +651,8 @@ private:
/// Find nearest polygon within a tile.
dtPolyRef findNearestPolyInTile(const dtMeshTile* tile, const float* center,
const float* halfExtents, float* nearestPt) const;
/// Returns whether position is over the poly and the height at the position if so.
bool getPolyHeight(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* height) const;
/// Returns closest point on polygon.
void closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const;
@@ -655,6 +672,8 @@ private:
unsigned int m_tileBits; ///< Number of tile bits in the tile ID.
unsigned int m_polyBits; ///< Number of poly bits in the tile ID.
#endif
friend class dtNavMeshQuery;
};
/// Allocates a navigation mesh object using the Detour allocator.

View File

@@ -222,7 +222,10 @@ dtStatus dtNavMeshQuery::findRandomPoint(const dtQueryFilter* filter, float (*fr
dtPolyRef* randomRef, float* randomPt) const
{
dtAssert(m_nav);
if (!filter || !frand || !randomRef || !randomPt)
return DT_FAILURE | DT_INVALID_PARAM;
// Randomly pick one tile. Assume that all tiles cover roughly the same area.
const dtMeshTile* tile = 0;
float tsum = 0.0f;
@@ -319,8 +322,13 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f
dtAssert(m_openList);
// Validate input
if (!startRef || !m_nav->isValidPolyRef(startRef))
if (!m_nav->isValidPolyRef(startRef) ||
!centerPos || !dtVisfinite(centerPos) ||
maxRadius < 0 || !dtMathIsfinite(maxRadius) ||
!filter || !frand || !randomRef || !randomPt)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
const dtMeshTile* startTile = 0;
const dtPoly* startPoly = 0;
@@ -474,12 +482,15 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f
dtVcopy(&verts[j*3],v);
}
const float s = frand();
const float t = frand();
float pt[3];
dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt);
do
{
const float s = frand();
const float t = frand();
dtRandomPointInConvexPoly(verts, randomPoly->vertCount, areas, s, t, pt);
}
while (dtDistancePtPtSqr2D(centerPos, pt) > radiusSqr);
float h = 0.0f;
dtStatus stat = getPolyHeight(randomPolyRef, pt, &h);
if (dtStatusFailed(status))
@@ -506,85 +517,14 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f
dtStatus dtNavMeshQuery::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const
{
dtAssert(m_nav);
const dtMeshTile* tile = 0;
const dtPoly* poly = 0;
if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly)))
if (!m_nav->isValidPolyRef(ref) ||
!pos || !dtVisfinite(pos) ||
!closest)
{
return DT_FAILURE | DT_INVALID_PARAM;
if (!tile)
return DT_FAILURE | DT_INVALID_PARAM;
// Off-mesh connections don't have detail polygons.
if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
{
const float* v0 = &tile->verts[poly->verts[0]*3];
const float* v1 = &tile->verts[poly->verts[1]*3];
const float d0 = dtVdist(pos, v0);
const float d1 = dtVdist(pos, v1);
const float u = d0 / (d0+d1);
dtVlerp(closest, v0, v1, u);
if (posOverPoly)
*posOverPoly = false;
return DT_SUCCESS;
}
const unsigned int ip = (unsigned int)(poly - tile->polys);
const dtPolyDetail* pd = &tile->detailMeshes[ip];
// Clamp point to be inside the polygon.
float verts[DT_VERTS_PER_POLYGON*3];
float edged[DT_VERTS_PER_POLYGON];
float edget[DT_VERTS_PER_POLYGON];
const int nv = poly->vertCount;
for (int i = 0; i < nv; ++i)
dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]);
dtVcopy(closest, pos);
if (!dtDistancePtPolyEdgesSqr(pos, verts, nv, edged, edget))
{
// Point is outside the polygon, dtClamp to nearest edge.
float dmin = edged[0];
int imin = 0;
for (int i = 1; i < nv; ++i)
{
if (edged[i] < dmin)
{
dmin = edged[i];
imin = i;
}
}
const float* va = &verts[imin*3];
const float* vb = &verts[((imin+1)%nv)*3];
dtVlerp(closest, va, vb, edget[imin]);
if (posOverPoly)
*posOverPoly = false;
}
else
{
if (posOverPoly)
*posOverPoly = true;
}
// Find height at the location.
for (int j = 0; j < pd->triCount; ++j)
{
const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4];
const float* v[3];
for (int k = 0; k < 3; ++k)
{
if (t[k] < poly->vertCount)
v[k] = &tile->verts[poly->verts[t[k]]*3];
else
v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3];
}
float h;
if (dtClosestHeightPointTriangle(closest, v[0], v[1], v[2], h))
{
closest[1] = h;
break;
}
}
m_nav->closestPointOnPoly(ref, pos, closest, posOverPoly);
return DT_SUCCESS;
}
@@ -607,6 +547,9 @@ dtStatus dtNavMeshQuery::closestPointOnPolyBoundary(dtPolyRef ref, const float*
const dtPoly* poly = 0;
if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly)))
return DT_FAILURE | DT_INVALID_PARAM;
if (!pos || !dtVisfinite(pos) || !closest)
return DT_FAILURE | DT_INVALID_PARAM;
// Collect vertices.
float verts[DT_VERTS_PER_POLYGON*3];
@@ -648,7 +591,7 @@ dtStatus dtNavMeshQuery::closestPointOnPolyBoundary(dtPolyRef ref, const float*
/// @par
///
/// Will return #DT_FAILURE if the provided position is outside the xz-bounds
/// Will return #DT_FAILURE | DT_INVALID_PARAM if the provided position is outside the xz-bounds
/// of the polygon.
///
dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* height) const
@@ -659,44 +602,28 @@ dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* h
const dtPoly* poly = 0;
if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly)))
return DT_FAILURE | DT_INVALID_PARAM;
if (!pos || !dtVisfinite2D(pos))
return DT_FAILURE | DT_INVALID_PARAM;
// We used to return success for offmesh connections, but the
// getPolyHeight in DetourNavMesh does not do this, so special
// case it here.
if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
{
const float* v0 = &tile->verts[poly->verts[0]*3];
const float* v1 = &tile->verts[poly->verts[1]*3];
const float d0 = dtVdist2D(pos, v0);
const float d1 = dtVdist2D(pos, v1);
const float u = d0 / (d0+d1);
float t;
dtDistancePtSegSqr2D(pos, v0, v1, t);
if (height)
*height = v0[1] + (v1[1] - v0[1]) * u;
*height = v0[1] + (v1[1] - v0[1])*t;
return DT_SUCCESS;
}
else
{
const unsigned int ip = (unsigned int)(poly - tile->polys);
const dtPolyDetail* pd = &tile->detailMeshes[ip];
for (int j = 0; j < pd->triCount; ++j)
{
const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4];
const float* v[3];
for (int k = 0; k < 3; ++k)
{
if (t[k] < poly->vertCount)
v[k] = &tile->verts[poly->verts[t[k]]*3];
else
v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3];
}
float h;
if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h))
{
if (height)
*height = h;
return DT_SUCCESS;
}
}
}
return DT_FAILURE | DT_INVALID_PARAM;
return m_nav->getPolyHeight(tile, poly, pos, height)
? DT_SUCCESS
: DT_FAILURE | DT_INVALID_PARAM;
}
class dtFindNearestPolyQuery : public dtPolyQuery
@@ -706,15 +633,17 @@ class dtFindNearestPolyQuery : public dtPolyQuery
float m_nearestDistanceSqr;
dtPolyRef m_nearestRef;
float m_nearestPoint[3];
bool m_overPoly;
public:
dtFindNearestPolyQuery(const dtNavMeshQuery* query, const float* center)
: m_query(query), m_center(center), m_nearestDistanceSqr(FLT_MAX), m_nearestRef(0), m_nearestPoint()
: m_query(query), m_center(center), m_nearestDistanceSqr(FLT_MAX), m_nearestRef(0), m_nearestPoint(), m_overPoly(false)
{
}
dtPolyRef nearestRef() const { return m_nearestRef; }
const float* nearestPoint() const { return m_nearestPoint; }
bool isOverPoly() const { return m_overPoly; }
void process(const dtMeshTile* tile, dtPoly** polys, dtPolyRef* refs, int count)
{
@@ -748,6 +677,7 @@ public:
m_nearestDistanceSqr = d;
m_nearestRef = ref;
m_overPoly = posOverPoly;
}
}
}
@@ -762,11 +692,22 @@ public:
dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* halfExtents,
const dtQueryFilter* filter,
dtPolyRef* nearestRef, float* nearestPt) const
{
return findNearestPoly(center, halfExtents, filter, nearestRef, nearestPt, NULL);
}
// If center and nearestPt point to an equal position, isOverPoly will be true;
// however there's also a special case of climb height inside the polygon (see dtFindNearestPolyQuery)
dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* halfExtents,
const dtQueryFilter* filter,
dtPolyRef* nearestRef, float* nearestPt, bool* isOverPoly) const
{
dtAssert(m_nav);
if (!nearestRef)
return DT_FAILURE | DT_INVALID_PARAM;
// queryPolygons below will check rest of params
dtFindNearestPolyQuery query(this, center);
@@ -778,7 +719,11 @@ dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* halfE
// Only override nearestPt if we actually found a poly so the nearest point
// is valid.
if (nearestPt && *nearestRef)
{
dtVcopy(nearestPt, query.nearestPoint());
if (isOverPoly)
*isOverPoly = query.isOverPoly();
}
return DT_SUCCESS;
}
@@ -972,8 +917,12 @@ dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* halfExt
{
dtAssert(m_nav);
if (!center || !halfExtents || !filter || !query)
if (!center || !dtVisfinite(center) ||
!halfExtents || !dtVisfinite(halfExtents) ||
!filter || !query)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
float bmin[3], bmax[3];
dtVsub(bmin, center, halfExtents);
@@ -1021,14 +970,20 @@ dtStatus dtNavMeshQuery::findPath(dtPolyRef startRef, dtPolyRef endRef,
dtAssert(m_nav);
dtAssert(m_nodePool);
dtAssert(m_openList);
if (pathCount)
*pathCount = 0;
if (!pathCount)
return DT_FAILURE | DT_INVALID_PARAM;
*pathCount = 0;
// Validate input
if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef) ||
!startPos || !endPos || !filter || maxPath <= 0 || !path || !pathCount)
!startPos || !dtVisfinite(startPos) ||
!endPos || !dtVisfinite(endPos) ||
!filter || !path || maxPath <= 0)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
if (startRef == endRef)
{
@@ -1263,18 +1218,21 @@ dtStatus dtNavMeshQuery::initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef
m_query.status = DT_FAILURE;
m_query.startRef = startRef;
m_query.endRef = endRef;
dtVcopy(m_query.startPos, startPos);
dtVcopy(m_query.endPos, endPos);
if (startPos)
dtVcopy(m_query.startPos, startPos);
if (endPos)
dtVcopy(m_query.endPos, endPos);
m_query.filter = filter;
m_query.options = options;
m_query.raycastLimitSqr = FLT_MAX;
if (!startRef || !endRef)
return DT_FAILURE | DT_INVALID_PARAM;
// Validate input
if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef))
if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef) ||
!startPos || !dtVisfinite(startPos) ||
!endPos || !dtVisfinite(endPos) || !filter)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
// trade quality with performance?
if (options & DT_FINDPATH_ANY_ANGLE)
@@ -1530,7 +1488,13 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters)
dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, const int maxPath)
{
if (!pathCount)
return DT_FAILURE | DT_INVALID_PARAM;
*pathCount = 0;
if (!path || maxPath <= 0)
return DT_FAILURE | DT_INVALID_PARAM;
if (dtStatusFailed(m_query.status))
{
@@ -1615,12 +1579,13 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount,
dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing, const int existingSize,
dtPolyRef* path, int* pathCount, const int maxPath)
{
if (!pathCount)
return DT_FAILURE | DT_INVALID_PARAM;
*pathCount = 0;
if (existingSize == 0)
{
return DT_FAILURE;
}
if (!existing || existingSize <= 0 || !path || !pathCount || maxPath <= 0)
return DT_FAILURE | DT_INVALID_PARAM;
if (dtStatusFailed(m_query.status))
{
@@ -1823,14 +1788,19 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en
int* straightPathCount, const int maxStraightPath, const int options) const
{
dtAssert(m_nav);
if (!straightPathCount)
return DT_FAILURE | DT_INVALID_PARAM;
*straightPathCount = 0;
if (!maxStraightPath)
return DT_FAILURE | DT_INVALID_PARAM;
if (!path[0])
if (!startPos || !dtVisfinite(startPos) ||
!endPos || !dtVisfinite(endPos) ||
!path || pathSize <= 0 || !path[0] ||
maxStraightPath <= 0)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
dtStatus stat = 0;
@@ -2070,13 +2040,19 @@ dtStatus dtNavMeshQuery::moveAlongSurface(dtPolyRef startRef, const float* start
dtAssert(m_nav);
dtAssert(m_tinyNodePool);
if (!visitedCount)
return DT_FAILURE | DT_INVALID_PARAM;
*visitedCount = 0;
// Validate input
if (!startRef)
return DT_FAILURE | DT_INVALID_PARAM;
if (!m_nav->isValidPolyRef(startRef))
if (!m_nav->isValidPolyRef(startRef) ||
!startPos || !dtVisfinite(startPos) ||
!endPos || !dtVisfinite(endPos) ||
!filter || !resultPos || !visited ||
maxVisitedSize <= 0)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
dtStatus status = DT_SUCCESS;
@@ -2484,16 +2460,23 @@ dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, cons
dtRaycastHit* hit, dtPolyRef prevRef) const
{
dtAssert(m_nav);
if (!hit)
return DT_FAILURE | DT_INVALID_PARAM;
hit->t = 0;
hit->pathCount = 0;
hit->pathCost = 0;
// Validate input
if (!startRef || !m_nav->isValidPolyRef(startRef))
return DT_FAILURE | DT_INVALID_PARAM;
if (prevRef && !m_nav->isValidPolyRef(prevRef))
if (!m_nav->isValidPolyRef(startRef) ||
!startPos || !dtVisfinite(startPos) ||
!endPos || !dtVisfinite(endPos) ||
!filter ||
(prevRef && !m_nav->isValidPolyRef(prevRef)))
{
return DT_FAILURE | DT_INVALID_PARAM;
}
float dir[3], curPos[3], lastPos[3];
float verts[DT_VERTS_PER_POLYGON*3+3];
@@ -2735,11 +2718,18 @@ dtStatus dtNavMeshQuery::findPolysAroundCircle(dtPolyRef startRef, const float*
dtAssert(m_nodePool);
dtAssert(m_openList);
*resultCount = 0;
// Validate input
if (!startRef || !m_nav->isValidPolyRef(startRef))
if (!resultCount)
return DT_FAILURE | DT_INVALID_PARAM;
*resultCount = 0;
if (!m_nav->isValidPolyRef(startRef) ||
!centerPos || !dtVisfinite(centerPos) ||
radius < 0 || !dtMathIsfinite(radius) ||
!filter || maxResult < 0)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
m_nodePool->clear();
m_openList->clear();
@@ -2901,8 +2891,18 @@ dtStatus dtNavMeshQuery::findPolysAroundShape(dtPolyRef startRef, const float* v
dtAssert(m_nav);
dtAssert(m_nodePool);
dtAssert(m_openList);
if (!resultCount)
return DT_FAILURE | DT_INVALID_PARAM;
*resultCount = 0;
if (!m_nav->isValidPolyRef(startRef) ||
!verts || nverts < 3 ||
!filter || maxResult < 0)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
// Validate input
if (!startRef || !m_nav->isValidPolyRef(startRef))
@@ -3088,13 +3088,20 @@ dtStatus dtNavMeshQuery::findLocalNeighbourhood(dtPolyRef startRef, const float*
{
dtAssert(m_nav);
dtAssert(m_tinyNodePool);
if (!resultCount)
return DT_FAILURE | DT_INVALID_PARAM;
*resultCount = 0;
// Validate input
if (!startRef || !m_nav->isValidPolyRef(startRef))
if (!m_nav->isValidPolyRef(startRef) ||
!centerPos || !dtVisfinite(centerPos) ||
radius < 0 || !dtMathIsfinite(radius) ||
!filter || maxResult < 0)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
static const int MAX_STACK = 48;
dtNode* stack[MAX_STACK];
int nstack = 0;
@@ -3301,13 +3308,19 @@ dtStatus dtNavMeshQuery::getPolyWallSegments(dtPolyRef ref, const dtQueryFilter*
const int maxSegments) const
{
dtAssert(m_nav);
if (!segmentCount)
return DT_FAILURE | DT_INVALID_PARAM;
*segmentCount = 0;
const dtMeshTile* tile = 0;
const dtPoly* poly = 0;
if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly)))
return DT_FAILURE | DT_INVALID_PARAM;
if (!filter || !segmentVerts || maxSegments < 0)
return DT_FAILURE | DT_INVALID_PARAM;
int n = 0;
static const int MAX_INTERVAL = 16;
@@ -3455,8 +3468,13 @@ dtStatus dtNavMeshQuery::findDistanceToWall(dtPolyRef startRef, const float* cen
dtAssert(m_openList);
// Validate input
if (!startRef || !m_nav->isValidPolyRef(startRef))
if (!m_nav->isValidPolyRef(startRef) ||
!centerPos || !dtVisfinite(centerPos) ||
maxRadius < 0 || !dtMathIsfinite(maxRadius) ||
!filter || !hitDist || !hitPos || !hitNormal)
{
return DT_FAILURE | DT_INVALID_PARAM;
}
m_nodePool->clear();
m_openList->clear();

View File

@@ -120,8 +120,6 @@ public:
};
/// Provides information about raycast hit
/// filled by dtNavMeshQuery::raycast
/// @ingroup detour
@@ -316,15 +314,31 @@ public:
///@{
/// Finds the polygon nearest to the specified center point.
/// [opt] means the specified parameter can be a null pointer, in that case the output parameter will not be set.
///
/// @param[in] center The center of the search box. [(x, y, z)]
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
/// @param[in] filter The polygon filter to apply to the query.
/// @param[out] nearestRef The reference id of the nearest polygon.
/// @param[out] nearestPt The nearest point on the polygon. [opt] [(x, y, z)]
/// @param[out] nearestRef The reference id of the nearest polygon. Will be set to 0 if no polygon is found.
/// @param[out] nearestPt The nearest point on the polygon. Unchanged if no polygon is found. [opt] [(x, y, z)]
/// @returns The status flags for the query.
dtStatus findNearestPoly(const float* center, const float* halfExtents,
const dtQueryFilter* filter,
dtPolyRef* nearestRef, float* nearestPt) const;
/// Finds the polygon nearest to the specified center point.
/// [opt] means the specified parameter can be a null pointer, in that case the output parameter will not be set.
///
/// @param[in] center The center of the search box. [(x, y, z)]
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
/// @param[in] filter The polygon filter to apply to the query.
/// @param[out] nearestRef The reference id of the nearest polygon. Will be set to 0 if no polygon is found.
/// @param[out] nearestPt The nearest point on the polygon. Unchanged if no polygon is found. [opt] [(x, y, z)]
/// @param[out] isOverPoly Set to true if the point's X/Z coordinate lies inside the polygon, false otherwise. Unchanged if no polygon is found. [opt]
/// @returns The status flags for the query.
dtStatus findNearestPoly(const float* center, const float* halfExtents,
const dtQueryFilter* filter,
dtPolyRef* nearestRef, float* nearestPt, bool* isOverPoly) const;
/// Finds polygons that overlap the search box.
/// @param[in] center The center of the search box. [(x, y, z)]

View File

@@ -106,6 +106,8 @@ class rcVectorBase {
// Creates an array of the given size, copies all of this vector's data into it, and returns it.
T* allocate_and_copy(rcSizeType size);
void resize_impl(rcSizeType size, const T* value);
// Requires: min_capacity > m_cap.
rcSizeType get_new_capacity(rcSizeType min_capacity);
public:
typedef rcSizeType size_type;
typedef T value_type;
@@ -196,8 +198,7 @@ void rcVectorBase<T, H>::push_back(const T& value) {
return;
}
rcAssert(RC_SIZE_MAX / 2 >= m_size);
rcSizeType new_cap = m_size ? 2*m_size : 1;
const rcSizeType new_cap = get_new_capacity(m_cap + 1);
T* data = allocate_and_copy(new_cap);
// construct between allocate and destroy+free in case value is
// in this vector.
@@ -208,25 +209,44 @@ void rcVectorBase<T, H>::push_back(const T& value) {
rcFree(m_data);
m_data = data;
}
template <typename T, rcAllocHint H>
rcSizeType rcVectorBase<T, H>::get_new_capacity(rcSizeType min_capacity) {
rcAssert(min_capacity <= RC_SIZE_MAX);
if (rcUnlikely(m_cap >= RC_SIZE_MAX / 2))
return RC_SIZE_MAX;
return 2 * m_cap > min_capacity ? 2 * m_cap : min_capacity;
}
template <typename T, rcAllocHint H>
void rcVectorBase<T, H>::resize_impl(rcSizeType size, const T* value) {
if (size < m_size) {
destroy_range(size, m_size);
m_size = size;
} else if (size > m_size) {
T* new_data = allocate_and_copy(size);
// We defer deconstructing/freeing old data until after constructing
// new elements in case "value" is there.
if (value) {
construct_range(new_data + m_size, new_data + size, *value);
if (size <= m_cap) {
if (value) {
construct_range(m_data + m_size, m_data + size, *value);
} else {
construct_range(m_data + m_size, m_data + size);
}
m_size = size;
} else {
construct_range(new_data + m_size, new_data + size);
const rcSizeType new_cap = get_new_capacity(size);
T* new_data = allocate_and_copy(new_cap);
// We defer deconstructing/freeing old data until after constructing
// new elements in case "value" is there.
if (value) {
construct_range(new_data + m_size, new_data + size, *value);
} else {
construct_range(new_data + m_size, new_data + size);
}
destroy_range(0, m_size);
rcFree(m_data);
m_data = new_data;
m_cap = new_cap;
m_size = size;
}
destroy_range(0, m_size);
rcFree(m_data);
m_data = new_data;
m_cap = size;
m_size = size;
}
}
template <typename T, rcAllocHint H>
@@ -303,6 +323,7 @@ public:
rcIntArray(int n) : m_impl(n, 0) {}
void push(int item) { m_impl.push_back(item); }
void resize(int size) { m_impl.resize(size); }
void clear() { m_impl.clear(); }
int pop()
{
int v = m_impl.back();

View File

@@ -921,8 +921,8 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
continue;
const unsigned char area = chf.areas[i];
verts.resize(0);
simplified.resize(0);
verts.clear();
simplified.clear();
ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE);
walkContour(x, y, i, chf, flags, verts);
@@ -1009,7 +1009,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
if (cset.nconts > 0)
{
// Calculate winding of all polygons.
rcScopedDelete<char> winding((char*)rcAlloc(sizeof(char)*cset.nconts, RC_ALLOC_TEMP));
rcScopedDelete<signed char> winding((signed char*)rcAlloc(sizeof(signed char)*cset.nconts, RC_ALLOC_TEMP));
if (!winding)
{
ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'hole' (%d).", cset.nconts);

View File

@@ -653,8 +653,8 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
for (int i = 0; i < nin; ++i)
rcVcopy(&verts[i*3], &in[i*3]);
edges.resize(0);
tris.resize(0);
edges.clear();
tris.clear();
const float cs = chf.cs;
const float ics = 1.0f/cs;
@@ -803,7 +803,7 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
int x1 = (int)ceilf(bmax[0]/sampleDist);
int z0 = (int)floorf(bmin[2]/sampleDist);
int z1 = (int)ceilf(bmax[2]/sampleDist);
samples.resize(0);
samples.clear();
for (int z = z0; z < z1; ++z)
{
for (int x = x0; x < x1; ++x)
@@ -864,8 +864,8 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin,
// Create new triangulation.
// TODO: Incremental add instead of full rebuild.
edges.resize(0);
tris.resize(0);
edges.clear();
tris.clear();
delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges);
}
}
@@ -935,7 +935,7 @@ static void seedArrayWithPolyCenter(rcContext* ctx, const rcCompactHeightfield&
pcy /= npoly;
// Use seeds array as a stack for DFS
array.resize(0);
array.clear();
array.push(startCellX);
array.push(startCellY);
array.push(startSpanIndex);
@@ -1001,7 +1001,7 @@ static void seedArrayWithPolyCenter(rcContext* ctx, const rcCompactHeightfield&
rcSwap(dirs[directDir], dirs[3]);
}
array.resize(0);
array.clear();
// getHeightData seeds are given in coordinates with borders
array.push(cx+bs);
array.push(cy+bs);
@@ -1030,7 +1030,7 @@ static void getHeightData(rcContext* ctx, const rcCompactHeightfield& chf,
// Note: Reads to the compact heightfield are offset by border size (bs)
// since border size offset is already removed from the polymesh vertices.
queue.resize(0);
queue.clear();
// Set all heights to RC_UNSET_HEIGHT.
memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);
@@ -1141,7 +1141,8 @@ static void getHeightData(rcContext* ctx, const rcCompactHeightfield& chf,
static unsigned char getEdgeFlags(const float* va, const float* vb,
const float* vpoly, const int npoly)
{
// Return true if edge (va,vb) is part of the polygon.
// The flag returned by this function matches dtDetailTriEdgeFlags in Detour.
// Figure out if edge (va,vb) is part of the polygon boundary.
static const float thrSqr = rcSqr(0.001f);
for (int i = 0, j = npoly-1; i < npoly; j=i++)
{

View File

@@ -650,7 +650,7 @@ static bool mergeRegions(rcRegion& rega, rcRegion& regb)
return false;
// Merge neighbours.
rega.connections.resize(0);
rega.connections.clear();
for (int i = 0, ni = acon.size(); i < ni-1; ++i)
rega.connections.push(acon[(insa+1+i) % ni]);
@@ -876,8 +876,8 @@ static bool mergeAndFilterRegions(rcContext* ctx, int minRegionArea, int mergeRe
// Also keep track of the regions connects to a tile border.
bool connectsToBorder = false;
int spanCount = 0;
stack.resize(0);
trace.resize(0);
stack.clear();
trace.clear();
reg.visited = true;
stack.push(i);
@@ -1068,7 +1068,7 @@ static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea,
{
const rcCompactCell& c = chf.cells[x+y*w];
lregs.resize(0);
lregs.clear();
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
@@ -1139,7 +1139,7 @@ static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea,
// Start search.
root.id = layerId;
stack.resize(0);
stack.clear();
stack.push(i);
while (stack.size() > 0)

View File

@@ -14,7 +14,7 @@ public class stb : HeaderOnlyModule
base.Init();
LicenseType = LicenseTypes.MIT;
LicenseFilePath = "LICENSE.txt";
LicenseFilePath = "LICENSE";
// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";

File diff suppressed because it is too large Load Diff

2631
Source/ThirdParty/stb/stb_image_resize.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* stb_image_write - v1.13 - public domain - http://nothings.org/stb
/* stb_image_write - v1.15 - public domain - http://nothings.org/stb
writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015
no warranty implied; use at your own risk
@@ -105,7 +105,7 @@ USAGE:
TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed
data, set the global variable 'stbi_write_tga_with_rle' to 0.
JPEG does ignore alpha channels in input data; quality is between 1 and 100.
Higher quality looks better but results in a bigger image.
JPEG baseline (no JPEG progressive).
@@ -113,7 +113,7 @@ USAGE:
CREDITS:
Sean Barrett - PNG/BMP/TGA
Sean Barrett - PNG/BMP/TGA
Baldur Karlsson - HDR
Jean-Sebastien Guay - TGA monochrome
Tim Kelsey - misc enhancements
@@ -247,17 +247,17 @@ STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean);
#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff)
#ifdef STB_IMAGE_WRITE_STATIC
static int stbi__flip_vertically_on_write=0;
static int stbi_write_png_compression_level = 8;
static int stbi_write_tga_with_rle = 1;
static int stbi_write_force_png_filter = -1;
#else
int stbi_write_png_compression_level = 8;
int stbi__flip_vertically_on_write=0;
int stbi_write_tga_with_rle = 1;
int stbi_write_force_png_filter = -1;
#endif
static int stbi__flip_vertically_on_write = 0;
STBIWDEF void stbi_flip_vertically_on_write(int flag)
{
stbi__flip_vertically_on_write = flag;
@@ -267,6 +267,8 @@ typedef struct
{
stbi_write_func *func;
void *context;
unsigned char buffer[64];
int buf_used;
} stbi__write_context;
// initialize a callback-based context
@@ -306,7 +308,7 @@ static FILE *stbiw__fopen(char const *filename, char const *mode)
wchar_t wFilename[1024];
if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)))
return 0;
if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)))
return 0;
@@ -380,16 +382,36 @@ static void stbiw__writef(stbi__write_context *s, const char *fmt, ...)
va_end(v);
}
static void stbiw__write_flush(stbi__write_context *s)
{
if (s->buf_used) {
s->func(s->context, &s->buffer, s->buf_used);
s->buf_used = 0;
}
}
static void stbiw__putc(stbi__write_context *s, unsigned char c)
{
s->func(s->context, &c, 1);
}
static void stbiw__write1(stbi__write_context *s, unsigned char a)
{
if (s->buf_used + 1 > sizeof(s->buffer))
stbiw__write_flush(s);
s->buffer[s->buf_used++] = a;
}
static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c)
{
unsigned char arr[3];
arr[0] = a; arr[1] = b; arr[2] = c;
s->func(s->context, arr, 3);
int n;
if (s->buf_used + 3 > sizeof(s->buffer))
stbiw__write_flush(s);
n = s->buf_used;
s->buf_used = n+3;
s->buffer[n+0] = a;
s->buffer[n+1] = b;
s->buffer[n+2] = c;
}
static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d)
@@ -398,7 +420,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in
int k;
if (write_alpha < 0)
s->func(s->context, &d[comp - 1], 1);
stbiw__write1(s, d[comp - 1]);
switch (comp) {
case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case
@@ -406,7 +428,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in
if (expand_mono)
stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp
else
s->func(s->context, d, 1); // monochrome TGA
stbiw__write1(s, d[0]); // monochrome TGA
break;
case 4:
if (!write_alpha) {
@@ -422,7 +444,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in
break;
}
if (write_alpha > 0)
s->func(s->context, &d[comp - 1], 1);
stbiw__write1(s, d[comp - 1]);
}
static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono)
@@ -447,6 +469,7 @@ static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, i
unsigned char *d = (unsigned char *) data + (j*x+i)*comp;
stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d);
}
stbiw__write_flush(s);
s->func(s->context, &zero, scanline_pad);
}
}
@@ -476,7 +499,7 @@ static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, c
STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
stbi__start_write_callbacks(&s, func, context);
return stbi_write_bmp_core(&s, x, y, comp, data);
}
@@ -484,7 +507,7 @@ STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x,
#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
if (stbi__start_write_file(&s,filename)) {
int r = stbi_write_bmp_core(&s, x, y, comp, data);
stbi__end_write_file(&s);
@@ -557,24 +580,25 @@ static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, v
if (diff) {
unsigned char header = STBIW_UCHAR(len - 1);
s->func(s->context, &header, 1);
stbiw__write1(s, header);
for (k = 0; k < len; ++k) {
stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp);
}
} else {
unsigned char header = STBIW_UCHAR(len - 129);
s->func(s->context, &header, 1);
stbiw__write1(s, header);
stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin);
}
}
}
stbiw__write_flush(s);
}
return 1;
}
STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
stbi__start_write_callbacks(&s, func, context);
return stbi_write_tga_core(&s, x, y, comp, (void *) data);
}
@@ -582,7 +606,7 @@ STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x,
#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
if (stbi__start_write_file(&s,filename)) {
int r = stbi_write_tga_core(&s, x, y, comp, (void *) data);
stbi__end_write_file(&s);
@@ -615,7 +639,6 @@ static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear)
}
}
#ifndef STBI_WRITE_NO_STDIO
static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte)
{
unsigned char lengthbyte = STBIW_UCHAR(length+128);
@@ -749,14 +772,15 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f
STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
stbi__start_write_callbacks(&s, func, context);
return stbi_write_hdr_core(&s, x, y, comp, (float *) data);
}
#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
if (stbi__start_write_file(&s,filename)) {
int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data);
stbi__end_write_file(&s);
@@ -774,7 +798,7 @@ STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const
#ifndef STBIW_ZLIB_COMPRESS
// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size()
#define stbiw__sbraw(a) ((int *) (a) - 2)
#define stbiw__sbraw(a) ((int *) (void *) (a) - 2)
#define stbiw__sbm(a) stbiw__sbraw(a)[0]
#define stbiw__sbn(a) stbiw__sbraw(a)[1]
@@ -1044,13 +1068,13 @@ static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int
int type = mymap[filter_type];
unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y);
int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes;
if (type==0) {
memcpy(line_buffer, z, width*n);
return;
}
// first loop isn't optimized since it's just one pixel
// first loop isn't optimized since it's just one pixel
for (i = 0; i < n; ++i) {
switch (type) {
case 1: line_buffer[i] = z[i]; break;
@@ -1271,26 +1295,31 @@ static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) {
bits[0] = val & ((1<<bits[1])-1);
}
static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, int du_stride, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] };
const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] };
int dataOff, i, diff, end0pos;
int dataOff, i, j, n, diff, end0pos, x, y;
int DU[64];
// DCT rows
for(dataOff=0; dataOff<64; dataOff+=8) {
for(dataOff=0, n=du_stride*8; dataOff<n; dataOff+=du_stride) {
stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+1], &CDU[dataOff+2], &CDU[dataOff+3], &CDU[dataOff+4], &CDU[dataOff+5], &CDU[dataOff+6], &CDU[dataOff+7]);
}
// DCT columns
for(dataOff=0; dataOff<8; ++dataOff) {
stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+8], &CDU[dataOff+16], &CDU[dataOff+24], &CDU[dataOff+32], &CDU[dataOff+40], &CDU[dataOff+48], &CDU[dataOff+56]);
stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+du_stride], &CDU[dataOff+du_stride*2], &CDU[dataOff+du_stride*3], &CDU[dataOff+du_stride*4],
&CDU[dataOff+du_stride*5], &CDU[dataOff+du_stride*6], &CDU[dataOff+du_stride*7]);
}
// Quantize/descale/zigzag the coefficients
for(i=0; i<64; ++i) {
float v = CDU[i]*fdtbl[i];
// DU[stbiw__jpg_ZigZag[i]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
// ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway?
DU[stbiw__jpg_ZigZag[i]] = (int)(v < 0 ? v - 0.5f : v + 0.5f);
for(y = 0, j=0; y < 8; ++y) {
for(x = 0; x < 8; ++x,++j) {
float v;
i = y*du_stride+x;
v = CDU[i]*fdtbl[j];
// DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
// ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway?
DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? v - 0.5f : v + 0.5f);
}
}
// Encode DC
@@ -1405,10 +1434,10 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99};
static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,
99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99};
static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f,
static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f,
1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f };
int row, col, i, k;
int row, col, i, k, subsample;
float fdtbl_Y[64], fdtbl_UV[64];
unsigned char YTable[64], UVTable[64];
@@ -1417,6 +1446,7 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
}
quality = quality ? quality : 90;
subsample = quality <= 90 ? 1 : 0;
quality = quality < 1 ? 1 : quality > 100 ? 100 : quality;
quality = quality < 50 ? 5000 / quality : 200 - quality * 2;
@@ -1439,7 +1469,7 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 };
static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 };
const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width),
3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
s->func(s->context, (void*)head0, sizeof(head0));
s->func(s->context, (void*)YTable, sizeof(YTable));
stbiw__putc(s, 1);
@@ -1462,36 +1492,74 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
// Encode 8x8 macroblocks
{
static const unsigned short fillBits[] = {0x7F, 7};
const unsigned char *imageData = (const unsigned char *)data;
int DCY=0, DCU=0, DCV=0;
int bitBuf=0, bitCnt=0;
// comp == 2 is grey+alpha (alpha is ignored)
int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0;
const unsigned char *dataR = (const unsigned char *)data;
const unsigned char *dataG = dataR + ofsG;
const unsigned char *dataB = dataR + ofsB;
int x, y, pos;
for(y = 0; y < height; y += 8) {
for(x = 0; x < width; x += 8) {
float YDU[64], UDU[64], VDU[64];
for(row = y, pos = 0; row < y+8; ++row) {
// row >= height => use last input row
int clamped_row = (row < height) ? row : height - 1;
int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
for(col = x; col < x+8; ++col, ++pos) {
float r, g, b;
// if col >= width => use pixel from last input column
int p = base_p + ((col < width) ? col : (width-1))*comp;
if(subsample) {
for(y = 0; y < height; y += 16) {
for(x = 0; x < width; x += 16) {
float Y[256], U[256], V[256];
for(row = y, pos = 0; row < y+16; ++row) {
// row >= height => use last input row
int clamped_row = (row < height) ? row : height - 1;
int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
for(col = x; col < x+16; ++col, ++pos) {
// if col >= width => use pixel from last input column
int p = base_p + ((col < width) ? col : (width-1))*comp;
float r = dataR[p], g = dataG[p], b = dataB[p];
Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
}
}
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
r = imageData[p+0];
g = imageData[p+ofsG];
b = imageData[p+ofsB];
YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128;
UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b;
VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b;
// subsample U,V
{
float subU[64], subV[64];
int yy, xx;
for(yy = 0, pos = 0; yy < 8; ++yy) {
for(xx = 0; xx < 8; ++xx, ++pos) {
int j = yy*32+xx*2;
subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f;
subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f;
}
}
DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
}
}
}
} else {
for(y = 0; y < height; y += 8) {
for(x = 0; x < width; x += 8) {
float Y[64], U[64], V[64];
for(row = y, pos = 0; row < y+8; ++row) {
// row >= height => use last input row
int clamped_row = (row < height) ? row : height - 1;
int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
for(col = x; col < x+8; ++col, ++pos) {
// if col >= width => use pixel from last input column
int p = base_p + ((col < width) ? col : (width-1))*comp;
float r = dataR[p], g = dataG[p], b = dataB[p];
Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
}
}
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
}
}
}
@@ -1508,7 +1576,7 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality)
{
stbi__write_context s;
stbi__write_context s = { 0 };
stbi__start_write_callbacks(&s, func, context);
return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality);
}
@@ -1517,7 +1585,7 @@ STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x,
#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality)
{
stbi__write_context s;
stbi__write_context s = { 0 };
if (stbi__start_write_file(&s,filename)) {
int r = stbi_write_jpg_core(&s, x, y, comp, data, quality);
stbi__end_write_file(&s);
@@ -1530,10 +1598,13 @@ STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const
#endif // STB_IMAGE_WRITE_IMPLEMENTATION
/* Revision history
1.14 (2020-02-02) updated JPEG writer to downsample chroma channels
1.13
1.12
1.11 (2019-08-11)
1.10 (2019-02-07)
support utf8 filenames in Windows; fix warnings and platform ifdefs
support utf8 filenames in Windows; fix warnings and platform ifdefs
1.09 (2018-02-11)
fix typo in zlib quality API, improve STB_I_W_STATIC in C++
1.08 (2018-01-29)
@@ -1582,38 +1653,38 @@ This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/

View File

@@ -12,8 +12,9 @@ namespace Flax.Build.Bindings
partial class BindingsGenerator
{
private static readonly bool[] CppParamsThatNeedLocalVariable = new bool[64];
private static readonly bool[] CppParamsThatNeedConvertion = new bool[64];
private static readonly string[] CppParamsThatNeedConvertionWrappers = new string[64];
private static readonly bool[] CppParamsThatNeedConversion = new bool[64];
private static readonly string[] CppParamsThatNeedConversionWrappers = new string[64];
private static readonly string[] CppParamsThatNeedConversionTypes = new string[64];
private static readonly string[] CppParamsWrappersCache = new string[64];
public static readonly List<ApiTypeInfo> CppUsedNonPodTypes = new List<ApiTypeInfo>();
private static readonly List<ApiTypeInfo> CppUsedNonPodTypesList = new List<ApiTypeInfo>();
@@ -620,7 +621,7 @@ namespace Flax.Build.Bindings
contents.Append(", ");
separator = true;
CppParamsThatNeedConvertion[i] = false;
CppParamsThatNeedConversion[i] = false;
CppParamsWrappersCache[i] = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, functionInfo, out CppParamsThatNeedLocalVariable[i]);
contents.Append(managedType);
if (parameterInfo.IsRef || parameterInfo.IsOut || UsePassByReference(buildData, parameterInfo.Type, caller))
@@ -654,8 +655,8 @@ namespace Flax.Build.Bindings
if (convertOutputParameter)
{
useInlinedReturn = false;
CppParamsThatNeedConvertion[i] = true;
CppParamsThatNeedConvertionWrappers[i] = GenerateCppWrapperNativeToManaged(buildData, parameterInfo.Type, caller, out _, functionInfo);
CppParamsThatNeedConversion[i] = true;
CppParamsThatNeedConversionWrappers[i] = GenerateCppWrapperNativeToManaged(buildData, parameterInfo.Type, caller, out CppParamsThatNeedConversionTypes[i], functionInfo);
}
}
}
@@ -717,7 +718,7 @@ namespace Flax.Build.Bindings
callParams += ", ";
separator = true;
var name = parameterInfo.Name;
if (CppParamsThatNeedConvertion[i] && (!FindApiTypeInfo(buildData, parameterInfo.Type, caller)?.IsStruct ?? false))
if (CppParamsThatNeedConversion[i] && (!FindApiTypeInfo(buildData, parameterInfo.Type, caller)?.IsStruct ?? false))
name = '*' + name;
string param = string.Empty;
@@ -735,7 +736,7 @@ namespace Flax.Build.Bindings
}
// Special case for output result parameters that needs additional converting from native to managed format (such as non-POD structures or output array parameter)
if (CppParamsThatNeedConvertion[i])
if (CppParamsThatNeedConversion[i])
{
var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller);
if (apiType != null)
@@ -792,9 +793,41 @@ namespace Flax.Build.Bindings
var parameterInfo = functionInfo.Parameters[i];
// Special case for output result parameters that needs additional converting from native to managed format (such as non-POD structures or output array parameter)
if (CppParamsThatNeedConvertion[i])
if (CppParamsThatNeedConversion[i])
{
contents.AppendFormat(" *{0} = {1};", parameterInfo.Name, string.Format(CppParamsThatNeedConvertionWrappers[i], parameterInfo.Name + "Temp")).AppendLine();
var value = string.Format(CppParamsThatNeedConversionWrappers[i], parameterInfo.Name + "Temp");
// MonoObject* parameters returned by reference need write barrier for GC
if (parameterInfo.IsOut)
{
var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller);
if (apiType != null)
{
if (apiType.IsClass)
{
contents.AppendFormat(" mono_gc_wbarrier_generic_store({0}, (MonoObject*){1});", parameterInfo.Name, value).AppendLine();
continue;
}
if (apiType.IsStruct && !apiType.IsPod)
{
CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h");
contents.AppendFormat(" {{ auto _temp = {1}; mono_gc_wbarrier_value_copy({0}, &_temp, 1, {2}::TypeInitializer.GetType().ManagedClass->GetNative()); }}", parameterInfo.Name, value, apiType.FullNameNative).AppendLine();
continue;
}
}
else
{
// BytesContainer
if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null)
{
contents.AppendFormat(" mono_gc_wbarrier_generic_store({0}, (MonoObject*){1});", parameterInfo.Name, value).AppendLine();
continue;
}
throw new Exception($"Unsupported type of parameter '{parameterInfo}' in method '{functionInfo}' to be passed using 'out'");
}
}
contents.AppendFormat(" *{0} = {1};", parameterInfo.Name, value).AppendLine();
}
}
}

View File

@@ -196,7 +196,7 @@ namespace Flax.Build.Platforms
}
/// <inheritdoc />
public override string DllExport => "__attribute__ ((__visibility__ (\"default\")))";
public override string DllExport => "__attribute__((__visibility__(\\\"default\\\")))";
/// <inheritdoc />
public override string DllImport => "";

View File

@@ -214,7 +214,7 @@ namespace Flax.Build.Projects.VisualStudioCode
}
case TargetPlatform.Linux:
{
json.AddField("command", "mono");
json.AddField("command", Path.Combine(Globals.EngineRoot, "Source/Platforms/Editor/Linux/Mono/bin/mono"));
json.BeginArray("args");
{
json.AddUnnamedField(buildToolPath);
@@ -480,11 +480,13 @@ namespace Flax.Build.Projects.VisualStudioCode
// File and folders excludes
json.BeginObject("files.exclude");
json.AddField("**/.git", true);
json.AddField("**/.svn", true);
json.AddField("**/.hg", true);
json.AddField("**/.vs", true);
json.AddField("**/Binaries", true);
json.AddField("**/Cache", true);
json.AddField("**/packages", true);
json.AddField("**/Content", true);
json.AddField("**/Logs", true);
json.AddField("**/Screenshots", true);
json.AddField("**/Output", true);