Merge remote-tracking branch 'origin/master' into sdl_platform
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@
|
||||
META_CB_BEGIN(0, Data)
|
||||
float4x4 WorldMatrix;
|
||||
float4x4 InvWorld;
|
||||
float4x4 SVPositionToWorld;
|
||||
float4x4 SvPositionToWorld;
|
||||
@1META_CB_END
|
||||
|
||||
// Use depth buffer for per-pixel decal layering
|
||||
@@ -27,12 +27,63 @@ struct MaterialInput
|
||||
float3 WorldPosition;
|
||||
float TwoSidedSign;
|
||||
float2 TexCoord;
|
||||
float4 TexCoord_DDX_DDY;
|
||||
float3x3 TBN;
|
||||
float4 SvPosition;
|
||||
float3 PreSkinnedPosition;
|
||||
float3 PreSkinnedNormal;
|
||||
};
|
||||
|
||||
// Calculates decal texcoords for a given pixel position (sampels depth buffer and projects value to decal space).
|
||||
float2 SvPositionToDecalUV(float4 svPosition)
|
||||
{
|
||||
float2 screenUV = svPosition.xy * ScreenSize.zw;
|
||||
svPosition.z = SAMPLE_RT(DepthBuffer, screenUV).r;
|
||||
float4 positionHS = mul(float4(svPosition.xyz, 1), SvPositionToWorld);
|
||||
float3 positionWS = positionHS.xyz / positionHS.w;
|
||||
float3 positionOS = mul(float4(positionWS, 1), InvWorld).xyz;
|
||||
return positionOS.xz + 0.5f;
|
||||
}
|
||||
|
||||
// Manually compute ddx/ddy for decal texture cooordinates to avoid the 2x2 pixels artifacts on the edges of geometry under decal
|
||||
// [Reference: https://www.humus.name/index.php?page=3D&ID=84]
|
||||
float4 CalculateTextureDerivatives(float4 svPosition, float2 texCoord)
|
||||
{
|
||||
float4 svDiffX = float4(1, 0, 0, 0);
|
||||
float2 uvDiffX0 = texCoord - SvPositionToDecalUV(svPosition - svDiffX);
|
||||
float2 uvDiffX1 = SvPositionToDecalUV(svPosition + svDiffX) - texCoord;
|
||||
float2 dx = dot(uvDiffX0, uvDiffX0) < dot(uvDiffX1, uvDiffX1) ? uvDiffX0 : uvDiffX1;
|
||||
|
||||
float4 svDiffY = float4(0, 1, 0, 0);
|
||||
float2 uvDiffY0 = texCoord - SvPositionToDecalUV(svPosition - svDiffY);
|
||||
float2 uvDiffY1 = SvPositionToDecalUV(svPosition + svDiffY) - texCoord;
|
||||
float2 dy = dot(uvDiffY0, uvDiffY0) < dot(uvDiffY1, uvDiffY1) ? uvDiffY0 : uvDiffY1;
|
||||
|
||||
return float4(dx, dy);
|
||||
}
|
||||
|
||||
// Computes the mipmap level for a specific texture dimensions to be sampled at decal texture cooordinates.
|
||||
// [Reference: https://hugi.scene.org/online/coding/hugi%2014%20-%20comipmap.htm]
|
||||
float CalculateTextureMipmap(MaterialInput input, float2 textureSize)
|
||||
{
|
||||
float2 dx = input.TexCoord_DDX_DDY.xy * textureSize;
|
||||
float2 dy = input.TexCoord_DDX_DDY.zw * textureSize;
|
||||
float d = max(dot(dx, dx), dot(dy, dy));
|
||||
return (0.5 * 0.5) * log2(d); // Hardcoded half-mip rate reduction to avoid artifacts when decal is moved over dither texture
|
||||
}
|
||||
float CalculateTextureMipmap(MaterialInput input, Texture2D t)
|
||||
{
|
||||
float2 textureSize;
|
||||
t.GetDimensions(textureSize.x, textureSize.y);
|
||||
return CalculateTextureMipmap(input, textureSize);
|
||||
}
|
||||
float CalculateTextureMipmap(MaterialInput input, TextureCube t)
|
||||
{
|
||||
float2 textureSize;
|
||||
t.GetDimensions(textureSize.x, textureSize.y);
|
||||
return CalculateTextureMipmap(input, textureSize);
|
||||
}
|
||||
|
||||
// Transforms a vector from tangent space to world space
|
||||
float3 TransformTangentVectorToWorld(MaterialInput input, float3 tangentVector)
|
||||
{
|
||||
@@ -116,7 +167,6 @@ Material GetMaterialPS(MaterialInput input)
|
||||
}
|
||||
|
||||
// Input macro specified by the material: DECAL_BLEND_MODE
|
||||
|
||||
#define DECAL_BLEND_MODE_TRANSLUCENT 0
|
||||
#define DECAL_BLEND_MODE_STAIN 1
|
||||
#define DECAL_BLEND_MODE_NORMAL 2
|
||||
@@ -153,7 +203,7 @@ void PS_Decal(
|
||||
float2 screenUV = SvPosition.xy * ScreenSize.zw;
|
||||
SvPosition.z = SAMPLE_RT(DepthBuffer, screenUV).r;
|
||||
|
||||
float4 positionHS = mul(float4(SvPosition.xyz, 1), SVPositionToWorld);
|
||||
float4 positionHS = mul(float4(SvPosition.xyz, 1), SvPositionToWorld);
|
||||
float3 positionWS = positionHS.xyz / positionHS.w;
|
||||
float3 positionOS = mul(float4(positionWS, 1), InvWorld).xyz;
|
||||
|
||||
@@ -166,8 +216,9 @@ void PS_Decal(
|
||||
materialInput.TexCoord = decalUVs;
|
||||
materialInput.TwoSidedSign = 1;
|
||||
materialInput.SvPosition = SvPosition;
|
||||
|
||||
// Build tangent to world transformation matrix
|
||||
materialInput.TexCoord_DDX_DDY = CalculateTextureDerivatives(materialInput.SvPosition, materialInput.TexCoord);
|
||||
|
||||
// Calculate tangent-space
|
||||
float3 ddxWp = ddx(positionWS);
|
||||
float3 ddyWp = ddy(positionWS);
|
||||
materialInput.TBN[0] = normalize(ddyWp);
|
||||
|
||||
@@ -20,6 +20,8 @@ float TimeParam;
|
||||
float4 ViewInfo;
|
||||
float4 ScreenSize;
|
||||
float4 ViewSize;
|
||||
float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
@1META_CB_END
|
||||
|
||||
// Shader resources
|
||||
|
||||
@@ -19,6 +19,8 @@ float4 ViewInfo;
|
||||
float4 ScreenSize;
|
||||
float4 TemporalAAJitter;
|
||||
float4x4 InverseViewProjectionMatrix;
|
||||
float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
@1META_CB_END
|
||||
|
||||
// Shader resources
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/Shaders/SSAO.flax
LFS
BIN
Content/Shaders/SSAO.flax
LFS
Binary file not shown.
@@ -528,7 +528,11 @@ namespace FlaxEditor
|
||||
var timeSinceLastSave = Time.UnscaledGameTime - _lastAutoSaveTimer;
|
||||
var timeToNextSave = options.AutoSaveFrequency * 60.0f - timeSinceLastSave;
|
||||
|
||||
if (timeToNextSave <= 0.0f || _autoSaveNow)
|
||||
if (timeToNextSave <= 0.0f && GetWindows().Any(x => x.GUI.Children.Any(c => c is GUI.ContextMenu.ContextMenuBase)))
|
||||
{
|
||||
// Skip aut-save if any context menu is opened to wait for user to end interaction
|
||||
}
|
||||
else if (timeToNextSave <= 0.0f || _autoSaveNow)
|
||||
{
|
||||
Log("Auto save");
|
||||
_lastAutoSaveTimer = Time.UnscaledGameTime;
|
||||
|
||||
@@ -514,20 +514,15 @@ namespace FlaxEditor.GUI
|
||||
var items = ItemsPanel.Children;
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (items[i] is Item item && item.Visible)
|
||||
var currentItem = items[i];
|
||||
if (currentItem is Item item && item.Visible)
|
||||
result.Add(item);
|
||||
}
|
||||
if (_categoryPanels != null)
|
||||
{
|
||||
for (int i = 0; i < _categoryPanels.Count; i++)
|
||||
else if (currentItem is DropPanel category && (!ignoreFoldedCategories || !category.IsClosed) && currentItem.Visible)
|
||||
{
|
||||
var category = _categoryPanels[i];
|
||||
if (!category.Visible || (ignoreFoldedCategories && category is DropPanel panel && panel.IsClosed))
|
||||
continue;
|
||||
for (int j = 0; j < category.Children.Count; j++)
|
||||
{
|
||||
if (category.Children[j] is Item item2 && item2.Visible)
|
||||
result.Add(item2);
|
||||
if (category.Children[j] is Item categoryItem && categoryItem.Visible)
|
||||
result.Add(categoryItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,10 +586,6 @@ namespace FlaxEditor.GUI
|
||||
var items = GetVisibleItems(!controlDown);
|
||||
var focusedIndex = items.IndexOf(focusedItem);
|
||||
|
||||
// If the user hasn't selected anything yet and is holding control, focus first folded item
|
||||
if (focusedIndex == -1 && controlDown)
|
||||
focusedIndex = GetVisibleItems(true).Count - 1;
|
||||
|
||||
int delta = key == KeyboardKeys.ArrowDown ? -1 : 1;
|
||||
int nextIndex = Mathf.Wrap(focusedIndex - delta, 0, items.Count - 1);
|
||||
var nextItem = items[nextIndex];
|
||||
|
||||
@@ -130,6 +130,10 @@ namespace FlaxEditor.GUI
|
||||
/// <returns>Created popup.</returns>
|
||||
public static RenamePopup Show(Control control, Rectangle area, string value, bool isMultiline)
|
||||
{
|
||||
// hardcoded flushing layout for tree controls
|
||||
if (control is Tree.TreeNode treeNode && treeNode.ParentTree != null)
|
||||
treeNode.ParentTree.FlushPendingPerformLayout();
|
||||
|
||||
// Calculate the control size in the window space to handle scaled controls
|
||||
var upperLeft = control.PointToWindow(area.UpperLeft);
|
||||
var bottomRight = control.PointToWindow(area.BottomRight);
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
private Margin _margin;
|
||||
private bool _autoSize = true;
|
||||
private bool _deferLayoutUpdate = false;
|
||||
private TreeNode _lastSelectedNode;
|
||||
|
||||
/// <summary>
|
||||
/// The TreeNode that is being dragged over. This could have a value when not dragging.
|
||||
@@ -67,7 +68,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
/// Gets the first selected node or null.
|
||||
/// </summary>
|
||||
public TreeNode SelectedNode => Selection.Count > 0 ? Selection[0] : null;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Allow nodes to Draw the root tree line.
|
||||
/// </summary>
|
||||
@@ -364,6 +365,19 @@ namespace FlaxEditor.GUI.Tree
|
||||
BulkSelectUpdateExpanded(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any pending layout perming action that has been delayed until next update to optimize performance of the complex tree hierarchy.
|
||||
/// </summary>
|
||||
public void FlushPendingPerformLayout()
|
||||
{
|
||||
if (_deferLayoutUpdate)
|
||||
{
|
||||
base.PerformLayout();
|
||||
AfterDeferredLayout?.Invoke();
|
||||
_deferLayoutUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PerformLayout(bool force = false)
|
||||
{
|
||||
@@ -378,25 +392,31 @@ namespace FlaxEditor.GUI.Tree
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (_deferLayoutUpdate)
|
||||
{
|
||||
base.PerformLayout();
|
||||
AfterDeferredLayout?.Invoke();
|
||||
_deferLayoutUpdate = false;
|
||||
}
|
||||
FlushPendingPerformLayout();
|
||||
var window = Root;
|
||||
bool shiftDown = window.GetKey(KeyboardKeys.Shift);
|
||||
bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp);
|
||||
bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown);
|
||||
|
||||
var node = SelectedNode;
|
||||
// Use last selection for last selected node if sift is down
|
||||
if (Selection.Count < 2)
|
||||
_lastSelectedNode = null;
|
||||
else if (shiftDown)
|
||||
_lastSelectedNode ??= Selection[^1];
|
||||
|
||||
// Skip root to prevent blocking input
|
||||
if (_lastSelectedNode != null && _lastSelectedNode.IsRoot)
|
||||
_lastSelectedNode = null;
|
||||
|
||||
var node = _lastSelectedNode ?? SelectedNode;
|
||||
|
||||
// Check if has focus and if any node is focused and it isn't a root
|
||||
if (ContainsFocus && node != null && node.AutoFocus)
|
||||
{
|
||||
var window = Root;
|
||||
if (window.GetKeyDown(KeyboardKeys.ArrowUp) || window.GetKeyDown(KeyboardKeys.ArrowDown))
|
||||
_keyUpdateTime = KeyUpdateTimeout;
|
||||
if (_keyUpdateTime >= KeyUpdateTimeout && window is WindowRootControl windowRoot && windowRoot.Window.IsFocused)
|
||||
{
|
||||
bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp);
|
||||
bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown);
|
||||
|
||||
// Check if arrow flags are different
|
||||
if (keyDownArrow != keyUpArrow)
|
||||
{
|
||||
@@ -406,24 +426,38 @@ namespace FlaxEditor.GUI.Tree
|
||||
Assert.AreNotEqual(-1, myIndex);
|
||||
|
||||
// Up
|
||||
TreeNode toSelect = null;
|
||||
List<TreeNode> toSelect = new List<TreeNode>();
|
||||
if (shiftDown && _supportMultiSelect)
|
||||
{
|
||||
toSelect.AddRange(Selection);
|
||||
}
|
||||
if (keyUpArrow)
|
||||
{
|
||||
if (myIndex == 0)
|
||||
{
|
||||
// Select parent
|
||||
toSelect = parentNode;
|
||||
if (toSelect.Contains(parentNode))
|
||||
toSelect.Remove(node);
|
||||
else if (parentNode != null)
|
||||
toSelect.Add(parentNode);
|
||||
_lastSelectedNode = parentNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select previous parent child
|
||||
toSelect = nodeParent.GetChild(myIndex - 1) as TreeNode;
|
||||
var select = nodeParent.GetChild(myIndex - 1) as TreeNode;
|
||||
|
||||
// Select last child if is valid and expanded and has any children
|
||||
if (toSelect != null && toSelect.IsExpanded && toSelect.HasAnyVisibleChild)
|
||||
if (select != null && select.IsExpanded && select.HasAnyVisibleChild)
|
||||
{
|
||||
toSelect = toSelect.GetChild(toSelect.ChildrenCount - 1) as TreeNode;
|
||||
select = select.GetChild(select.ChildrenCount - 1) as TreeNode;
|
||||
}
|
||||
|
||||
if (select == null || toSelect.Contains(select))
|
||||
toSelect.Remove(node);
|
||||
else
|
||||
toSelect.Add(select);
|
||||
_lastSelectedNode = select;
|
||||
}
|
||||
}
|
||||
// Down
|
||||
@@ -432,32 +466,48 @@ namespace FlaxEditor.GUI.Tree
|
||||
if (node.IsExpanded && node.HasAnyVisibleChild)
|
||||
{
|
||||
// Select the first child
|
||||
toSelect = node.GetChild(0) as TreeNode;
|
||||
var select = node.GetChild(0) as TreeNode;
|
||||
if (select == null || toSelect.Contains(select))
|
||||
toSelect.Remove(node);
|
||||
else
|
||||
toSelect.Add(select);
|
||||
_lastSelectedNode = select;
|
||||
}
|
||||
else if (myIndex == nodeParent.ChildrenCount - 1)
|
||||
{
|
||||
// Select next node after parent
|
||||
while (parentNode != null && toSelect == null)
|
||||
TreeNode select = null;
|
||||
while (parentNode != null && select == null)
|
||||
{
|
||||
int parentIndex = parentNode.IndexInParent;
|
||||
if (parentIndex != -1 && parentIndex < parentNode.Parent.ChildrenCount - 1)
|
||||
{
|
||||
toSelect = parentNode.Parent.GetChild(parentIndex + 1) as TreeNode;
|
||||
select = parentNode.Parent.GetChild(parentIndex + 1) as TreeNode;
|
||||
}
|
||||
parentNode = parentNode.Parent as TreeNode;
|
||||
}
|
||||
if (select == null || toSelect.Contains(select))
|
||||
toSelect.Remove(node);
|
||||
else
|
||||
toSelect.Add(select);
|
||||
_lastSelectedNode = select;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select next parent child
|
||||
toSelect = nodeParent.GetChild(myIndex + 1) as TreeNode;
|
||||
var select = nodeParent.GetChild(myIndex + 1) as TreeNode;
|
||||
if (select == null || toSelect.Contains(select))
|
||||
toSelect.Remove(node);
|
||||
else
|
||||
toSelect.Add(select);
|
||||
_lastSelectedNode = select;
|
||||
}
|
||||
}
|
||||
if (toSelect != null && toSelect.AutoFocus)
|
||||
if (toSelect.Count > 0)
|
||||
{
|
||||
// Select
|
||||
Select(toSelect);
|
||||
toSelect.Focus();
|
||||
_lastSelectedNode?.Focus();
|
||||
}
|
||||
|
||||
// Reset time
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MException.h"
|
||||
#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
|
||||
#include "Engine/Platform/WindowsManager.h"
|
||||
#include "Engine/Content/Assets/VisualScript.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/CSG/CSGBuilder.h"
|
||||
@@ -622,6 +623,14 @@ void ManagedEditor::WipeOutLeftoverSceneObjects()
|
||||
ObjectsRemovalService::Flush();
|
||||
}
|
||||
|
||||
Array<Window*> ManagedEditor::GetWindows()
|
||||
{
|
||||
WindowsManager::WindowsLocker.Lock();
|
||||
auto result = WindowsManager::Windows;
|
||||
WindowsManager::WindowsLocker.Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
|
||||
{
|
||||
ASSERT(!HasManagedInstance());
|
||||
|
||||
@@ -259,6 +259,7 @@ public:
|
||||
API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals();
|
||||
API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local);
|
||||
API_FUNCTION(Internal) static void WipeOutLeftoverSceneObjects();
|
||||
API_FUNCTION(Internal) static Array<Window*> GetWindows();
|
||||
|
||||
private:
|
||||
void OnEditorAssemblyLoaded(MAssembly* assembly);
|
||||
|
||||
@@ -652,43 +652,47 @@ namespace FlaxEditor.Options
|
||||
|
||||
#endregion
|
||||
|
||||
#region Node editors
|
||||
#region Node Editors
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+W")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4500)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4500)]
|
||||
public InputBinding NodesAlignTop = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+A")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4510)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4510)]
|
||||
public InputBinding NodesAlignLeft = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+S")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4520)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4520)]
|
||||
public InputBinding NodesAlignBottom = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Shift+D")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4530)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4530)]
|
||||
public InputBinding NodesAlignRight = new InputBinding(KeyboardKeys.D, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Alt+Shift+W")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4540)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4540)]
|
||||
public InputBinding NodesAlignMiddle = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift, KeyboardKeys.Alt);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Alt+Shift+S")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4550)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4550)]
|
||||
public InputBinding NodesAlignCenter = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift, KeyboardKeys.Alt);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Q")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4560)]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4560)]
|
||||
public InputBinding NodesAutoFormat = new InputBinding(KeyboardKeys.Q);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "None")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4570)]
|
||||
public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.None);
|
||||
[DefaultValue(typeof(InputBinding), "Shift+Q")]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4560)]
|
||||
public InputBinding NodesStraightenConnections = new InputBinding(KeyboardKeys.Q, KeyboardKeys.Shift);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "None")]
|
||||
[EditorDisplay("Node editors"), EditorOrder(4580)]
|
||||
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.None);
|
||||
[DefaultValue(typeof(InputBinding), "Alt+W")]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4570)]
|
||||
public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.W, KeyboardKeys.Alt);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Alt+A")]
|
||||
[EditorDisplay("Node Editors"), EditorOrder(4580)]
|
||||
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.A, KeyboardKeys.Alt);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -446,6 +446,12 @@ namespace FlaxEditor.Options
|
||||
[DefaultValue(1), Range(1, 4)]
|
||||
[EditorDisplay("Cook & Run"), EditorOrder(600)]
|
||||
public int NumberOfGameClientsToLaunch = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the build configuration to use when using Cook and Run option in the editor.
|
||||
/// </summary>
|
||||
[EditorDisplay("Cook & Run"), EditorOrder(601), ExpandGroups, Tooltip("The build configuration to use when using Cook and Run option in the editor.")]
|
||||
public BuildConfiguration CookAndRunBuildConfiguration { get; set; } = BuildConfiguration.Development;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the curvature of the line connecting to connected visject nodes.
|
||||
|
||||
@@ -810,7 +810,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Create = (id, context, arch, groupArch) => new StateMachineState(id, context, arch, groupArch),
|
||||
Title = "State",
|
||||
Description = "The animation states machine state node",
|
||||
Flags = NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI | NodeFlags.NoSpawnViaPaste,
|
||||
Flags = NodeFlags.AnimGraph | NodeFlags.NoSpawnViaGUI,
|
||||
Size = new Float2(100, 0),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
private Label _labelValue;
|
||||
private FloatValueBox _timeValue;
|
||||
private ColorValueBox _colorValue;
|
||||
private const int MaxStops = 8;
|
||||
private const int MaxStops = 12;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ColorGradientNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||
@@ -1386,10 +1386,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Title = "Time",
|
||||
Description = "Game time constant",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(110, 20),
|
||||
Size = new Float2(110, 40),
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, "", typeof(float), 0),
|
||||
NodeElementArchetype.Factory.Output(0, "Time", typeof(float), 0),
|
||||
NodeElementArchetype.Factory.Output(1, "Unscaled Time", typeof(float), 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -1506,7 +1507,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
0.95f,
|
||||
Color.White,
|
||||
|
||||
// Empty stops 2-7
|
||||
// Empty stops 2-11
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
0.0f, Color.Black,
|
||||
|
||||
@@ -5,39 +5,39 @@ using FlaxEngine;
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
/// <summary>
|
||||
/// Node Alignment type
|
||||
/// Node Alignment type.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public enum NodeAlignmentType
|
||||
{
|
||||
/// <summary>
|
||||
/// Align nodes vertically to top, matching top-most node
|
||||
/// Align nodes vertically to top, matching top-most node.
|
||||
/// </summary>
|
||||
Top,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes vertically to middle, using average of all nodes
|
||||
/// Align nodes vertically to middle, using average of all nodes.
|
||||
/// </summary>
|
||||
Middle,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes vertically to bottom, matching bottom-most node
|
||||
/// Align nodes vertically to bottom, matching bottom-most node.
|
||||
/// </summary>
|
||||
Bottom,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes horizontally to left, matching left-most node
|
||||
/// Align nodes horizontally to left, matching left-most node.
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes horizontally to center, using average of all nodes
|
||||
/// Align nodes horizontally to center, using average of all nodes.
|
||||
/// </summary>
|
||||
Center,
|
||||
|
||||
/// <summary>
|
||||
/// Align nodes horizontally to right, matching right-most node
|
||||
/// Align nodes horizontally to right, matching right-most node.
|
||||
/// </summary>
|
||||
Right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
/// <param name="scriptType">The input type to process.</param>
|
||||
/// <param name="cache">Node groups cache that can be used for reusing groups for different nodes.</param>
|
||||
/// <param name="version">The cache version number. Can be used to reject any cached data after <see cref="NodesCache"/> rebuilt.</param>
|
||||
/// <param name="version">The cache version number. Can be used to reject any cached data after.<see cref="NodesCache"/> rebuilt.</param>
|
||||
public delegate void IterateType(ScriptType scriptType, Dictionary<KeyValuePair<string, ushort>, GroupArchetype> cache, int version);
|
||||
|
||||
internal static readonly List<NodesCache> Caches = new List<NodesCache>(8);
|
||||
@@ -412,6 +412,7 @@ namespace FlaxEditor.Surface
|
||||
_cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
|
||||
|
||||
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
|
||||
_cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Straighten connections", Editor.Instance.Options.Options.Input.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); });
|
||||
|
||||
_cmFormatNodesMenu.ContextMenu.AddSeparator();
|
||||
_cmAlignNodesTopButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align top", Editor.Instance.Options.Options.Input.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); });
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
using FlaxEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEngine;
|
||||
using FlaxEditor.Surface.Elements;
|
||||
using FlaxEditor.Surface.Undo;
|
||||
|
||||
namespace FlaxEditor.Surface
|
||||
{
|
||||
@@ -14,26 +14,26 @@ namespace FlaxEditor.Surface
|
||||
private class NodeFormattingData
|
||||
{
|
||||
/// <summary>
|
||||
/// Starting from 0 at the main nodes
|
||||
/// Starting from 0 at the main nodes.
|
||||
/// </summary>
|
||||
public int Layer;
|
||||
|
||||
/// <summary>
|
||||
/// Position in the layer
|
||||
/// Position in the layer.
|
||||
/// </summary>
|
||||
public int Offset;
|
||||
|
||||
/// <summary>
|
||||
/// How far the subtree needs to be moved additionally
|
||||
/// How far the subtree needs to be moved additionally.
|
||||
/// </summary>
|
||||
public int SubtreeOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a graph where the nodes can be disjointed.
|
||||
/// Uses the Sugiyama method
|
||||
/// Uses the Sugiyama method.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of nodes</param>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
public void FormatGraph(List<SurfaceNode> nodes)
|
||||
{
|
||||
if (nodes.Count <= 1)
|
||||
@@ -78,9 +78,9 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a graph where all nodes are connected
|
||||
/// Formats a graph where all nodes are connected.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of connected nodes</param>
|
||||
/// <param name="nodes">List of connected nodes.</param>
|
||||
protected void FormatConnectedGraph(List<SurfaceNode> nodes)
|
||||
{
|
||||
if (nodes.Count <= 1)
|
||||
@@ -160,11 +160,71 @@ namespace FlaxEditor.Surface
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a layer to every node
|
||||
/// Straightens every connection between nodes in <paramref name="nodes"/>.
|
||||
/// </summary>
|
||||
/// <param name="nodeData">The exta node data</param>
|
||||
/// <param name="endNodes">The end nodes</param>
|
||||
/// <returns>The number of the maximum layer</returns>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
public void StraightenGraphConnections(List<SurfaceNode> nodes)
|
||||
{
|
||||
if (nodes.Count <= 1)
|
||||
return;
|
||||
|
||||
List<MoveNodesAction> undoActions = new List<MoveNodesAction>();
|
||||
|
||||
// Only process nodes that have any connection
|
||||
List<SurfaceNode> connectedNodes = nodes.Where(n => n.GetBoxes().Any(b => b.HasAnyConnection)).ToList();
|
||||
|
||||
if (connectedNodes.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < connectedNodes.Count - 1; i++)
|
||||
{
|
||||
SurfaceNode nodeA = connectedNodes[i];
|
||||
List<Box> connectedOutputBoxes = nodeA.GetBoxes().Where(b => b.IsOutput && b.HasAnyConnection).ToList();
|
||||
|
||||
for (int j = 0; j < connectedOutputBoxes.Count; j++)
|
||||
{
|
||||
Box boxA = connectedOutputBoxes[j];
|
||||
|
||||
for (int b = 0; b < boxA.Connections.Count; b++)
|
||||
{
|
||||
Box boxB = boxA.Connections[b];
|
||||
|
||||
// Ensure the other node is selected
|
||||
if (!connectedNodes.Contains(boxB.ParentNode))
|
||||
continue;
|
||||
|
||||
// Node with no outgoing connections reached. Advance to next node in list
|
||||
if (boxA == null || boxB == null)
|
||||
continue;
|
||||
|
||||
SurfaceNode nodeB = boxB.ParentNode;
|
||||
|
||||
// Calculate the Y offset needed for nodeB to align boxB's Y to boxA's Y
|
||||
float boxASurfaceY = boxA.PointToParent(this, Float2.Zero).Y;
|
||||
float boxBSurfaceY = boxB.PointToParent(this, Float2.Zero).Y;
|
||||
float deltaY = (boxASurfaceY - boxBSurfaceY) / ViewScale;
|
||||
Float2 delta = new Float2(0f, deltaY);
|
||||
|
||||
nodeB.Location += delta;
|
||||
|
||||
if (Undo != null)
|
||||
undoActions.Add(new MoveNodesAction(Context, new[] { nodeB.ID }, delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (undoActions.Count > 0)
|
||||
Undo?.AddAction(new MultiUndoAction(undoActions, "Straightned "));
|
||||
|
||||
MarkAsEdited(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a layer to every node.
|
||||
/// </summary>
|
||||
/// <param name="nodeData">The exta node data.</param>
|
||||
/// <param name="endNodes">The end nodes.</param>
|
||||
/// <returns>The number of the maximum layer.</returns>
|
||||
private int SetLayers(Dictionary<SurfaceNode, NodeFormattingData> nodeData, List<SurfaceNode> endNodes)
|
||||
{
|
||||
// Longest path layering
|
||||
@@ -201,12 +261,12 @@ namespace FlaxEditor.Surface
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node offsets
|
||||
/// Sets the node offsets.
|
||||
/// </summary>
|
||||
/// <param name="nodeData">The exta node data</param>
|
||||
/// <param name="endNodes">The end nodes</param>
|
||||
/// <param name="maxLayer">The number of the maximum layer</param>
|
||||
/// <returns>The number of the maximum offset</returns>
|
||||
/// <param name="nodeData">The exta node data.</param>
|
||||
/// <param name="endNodes">The end nodes.</param>
|
||||
/// <param name="maxLayer">The number of the maximum layer.</param>
|
||||
/// <returns>The number of the maximum offset.</returns>
|
||||
private int SetOffsets(Dictionary<SurfaceNode, NodeFormattingData> nodeData, List<SurfaceNode> endNodes, int maxLayer)
|
||||
{
|
||||
int maxOffset = 0;
|
||||
@@ -287,10 +347,10 @@ namespace FlaxEditor.Surface
|
||||
/// Align given nodes on a graph using the given alignment type.
|
||||
/// Ignores any potential overlap.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of nodes</param>
|
||||
/// <param name="alignmentType">Alignemnt type</param>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
/// <param name="alignmentType">Alignemnt type.</param>
|
||||
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
|
||||
{
|
||||
{
|
||||
if(nodes.Count <= 1)
|
||||
return;
|
||||
|
||||
@@ -328,8 +388,8 @@ namespace FlaxEditor.Surface
|
||||
/// <summary>
|
||||
/// Distribute the given nodes as equally as possible inside the bounding box, if no fit can be done it will use a default pad of 10 pixels between nodes.
|
||||
/// </summary>
|
||||
/// <param name="nodes">List of nodes</param>
|
||||
/// <param name="vertically">If false will be done horizontally, if true will be done vertically</param>
|
||||
/// <param name="nodes">List of nodes.</param>
|
||||
/// <param name="vertically">If false will be done horizontally, if true will be done vertically.</param>
|
||||
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
|
||||
{
|
||||
if(nodes.Count <= 1)
|
||||
|
||||
@@ -416,6 +416,7 @@ namespace FlaxEditor.Surface
|
||||
new InputActionsContainer.Binding(options => options.Cut, Cut),
|
||||
new InputActionsContainer.Binding(options => options.Duplicate, Duplicate),
|
||||
new InputActionsContainer.Binding(options => options.NodesAutoFormat, () => { FormatGraph(SelectedNodes); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesStraightenConnections, () => { StraightenGraphConnections(SelectedNodes); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignMiddle, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Middle); }),
|
||||
new InputActionsContainer.Binding(options => options.NodesAlignBottom, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Bottom); }),
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
@@ -471,7 +470,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
private void OnOverrideMethodClicked()
|
||||
{
|
||||
var cm = new ContextMenu();
|
||||
var cm = new ItemsListContextMenu(235);
|
||||
var window = (VisualScriptWindow)Values[0];
|
||||
var scriptMeta = window.Asset.Meta;
|
||||
var baseType = TypeUtils.GetType(scriptMeta.BaseTypename);
|
||||
@@ -499,27 +498,39 @@ namespace FlaxEditor.Windows.Assets
|
||||
if (isAlreadyAdded)
|
||||
continue;
|
||||
|
||||
var cmButton = cm.AddButton($"{name} (in {member.DeclaringType.Name})");
|
||||
cmButton.TooltipText = Editor.Instance.CodeDocs.GetTooltip(member);
|
||||
cmButton.Clicked += () =>
|
||||
var item = new ItemsListContextMenu.Item
|
||||
{
|
||||
var surface = ((VisualScriptWindow)Values[0]).Surface;
|
||||
var surfaceBounds = surface.AllNodesBounds;
|
||||
surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Float2(200, 150)).MakeExpanded(400.0f));
|
||||
var node = surface.Context.SpawnNode(16, 3, surfaceBounds.BottomLeft + new Float2(0, 50), new object[]
|
||||
{
|
||||
name,
|
||||
parameters.Length,
|
||||
Utils.GetEmptyArray<byte>()
|
||||
});
|
||||
surface.Select(node);
|
||||
Name = $"{name} (in {member.DeclaringType.Name})",
|
||||
TooltipText = Editor.Instance.CodeDocs.GetTooltip(member),
|
||||
Tag = new object[] { name, parameters.Length, Utils.GetEmptyArray<byte>() },
|
||||
// Do some basic sorting based on if the method is defined directly in the script base class
|
||||
SortScore = member.DeclaringType == member.Type.ReflectedType ? 1 : 0,
|
||||
};
|
||||
cm.AddItem(item);
|
||||
}
|
||||
}
|
||||
if (!cm.Items.Any())
|
||||
|
||||
cm.ItemClicked += (item) =>
|
||||
{
|
||||
cm.AddButton("Nothing to override");
|
||||
var surface = ((VisualScriptWindow)Values[0]).Surface;
|
||||
var surfaceBounds = surface.AllNodesBounds;
|
||||
surface.ShowArea(new Rectangle(surfaceBounds.BottomLeft, new Float2(200, 150)).MakeExpanded(400.0f));
|
||||
var node = surface.Context.SpawnNode(16, 3, surfaceBounds.BottomLeft + new Float2(0, 50), item.Tag as object[]);
|
||||
surface.Select(node);
|
||||
};
|
||||
|
||||
if (cm.ItemsPanel.ChildrenCount == 0)
|
||||
{
|
||||
var item = new ItemsListContextMenu.Item
|
||||
{
|
||||
Name = "Nothing to override"
|
||||
};
|
||||
item.Enabled = false;
|
||||
|
||||
cm.AddItem(item);
|
||||
}
|
||||
|
||||
cm.SortItems();
|
||||
cm.Show(_overrideButton, new Float2(0, _overrideButton.Height));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,12 +335,12 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Parent = this,
|
||||
};
|
||||
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries");
|
||||
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries.");
|
||||
_clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play", () =>
|
||||
{
|
||||
editor.Options.Options.Interface.DebugLogClearOnPlay = _clearOnPlayButton.Checked;
|
||||
editor.Options.Apply(editor.Options.Options);
|
||||
}).SetAutoCheck(true).LinkTooltip("Clears all log entries on enter playmode");
|
||||
}).SetAutoCheck(true).LinkTooltip("Clears all log entries on enter playmode.");
|
||||
_collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse", () =>
|
||||
{
|
||||
editor.Options.Options.Interface.DebugLogCollapse = _collapseLogsButton.Checked;
|
||||
@@ -350,14 +350,14 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
editor.Options.Options.Interface.DebugLogPauseOnError = _pauseOnErrorButton.Checked;
|
||||
editor.Options.Apply(editor.Options.Options);
|
||||
}).SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
|
||||
}).SetAutoCheck(true).LinkTooltip("Performs auto pause on error.");
|
||||
toolstrip.AddSeparator();
|
||||
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => { OnGroupButtonPressed(0); })
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides error messages");
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides error messages.");
|
||||
_groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () => { OnGroupButtonPressed(1); })
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides warning messages");
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides warning messages.");
|
||||
_groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () => { OnGroupButtonPressed(2); })
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides info messages");
|
||||
.SetAutoCheck(true).LinkTooltip("Shows/hides info messages.");
|
||||
UpdateCount();
|
||||
|
||||
// Split panel
|
||||
@@ -495,6 +495,7 @@ namespace FlaxEditor.Windows
|
||||
// Pause on Error (we should do it as fast as possible)
|
||||
if (newEntry.Group == LogGroup.Error && _pauseOnErrorButton.Checked && Editor.StateMachine.CurrentState == Editor.StateMachine.PlayingState)
|
||||
{
|
||||
Editor.Log("Pause Play mode on error (toggle this behaviour in the Debug Log panel)");
|
||||
Editor.Simulation.RequestPausePlay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,8 +974,9 @@ namespace FlaxEditor.Windows
|
||||
public void BuildAndRun()
|
||||
{
|
||||
Editor.Log("Building and running");
|
||||
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration);
|
||||
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out _);
|
||||
var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
|
||||
var buildConfig = Editor.Options.Options.Interface.CookAndRunBuildConfiguration;
|
||||
for (int i = 0; i < numberOfClients; i++)
|
||||
{
|
||||
var buildOptions = BuildOptions.AutoRun;
|
||||
@@ -988,7 +989,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Output = _buildTabProxy.PerPlatformOptions[platform].Output,
|
||||
Platform = buildPlatform,
|
||||
Mode = buildConfiguration,
|
||||
Mode = buildConfig,
|
||||
},
|
||||
Options = buildOptions,
|
||||
});
|
||||
@@ -1001,8 +1002,9 @@ namespace FlaxEditor.Windows
|
||||
public void RunCooked()
|
||||
{
|
||||
Editor.Log("Running cooked build");
|
||||
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out var buildConfiguration);
|
||||
GameCooker.GetCurrentPlatform(out var platform, out var buildPlatform, out _);
|
||||
var numberOfClients = Editor.Options.Options.Interface.NumberOfGameClientsToLaunch;
|
||||
var buildConfig = Editor.Options.Options.Interface.CookAndRunBuildConfiguration;
|
||||
for (int i = 0; i < numberOfClients; i++)
|
||||
{
|
||||
_buildingQueue.Enqueue(new QueueItem
|
||||
@@ -1011,7 +1013,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Output = _buildTabProxy.PerPlatformOptions[platform].Output,
|
||||
Platform = buildPlatform,
|
||||
Mode = buildConfiguration,
|
||||
Mode = buildConfig,
|
||||
},
|
||||
Options = BuildOptions.AutoRun | BuildOptions.NoCook,
|
||||
});
|
||||
|
||||
@@ -63,6 +63,16 @@ namespace FlaxEditor.Windows
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the game window audio is muted.
|
||||
/// </summary>
|
||||
public event Action MuteAudio;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the game window master audio volume is changed.
|
||||
/// </summary>
|
||||
public event Action<float> MasterVolumeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the viewport.
|
||||
/// </summary>
|
||||
@@ -120,6 +130,7 @@ namespace FlaxEditor.Windows
|
||||
{
|
||||
Audio.MasterVolume = value ? 0 : AudioVolume;
|
||||
_audioMuted = value;
|
||||
MuteAudio?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +145,7 @@ namespace FlaxEditor.Windows
|
||||
if (!AudioMuted)
|
||||
Audio.MasterVolume = value;
|
||||
_audioVolume = value;
|
||||
MasterVolumeChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,18 +229,12 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
||||
auto& context = *Context.Get();
|
||||
float eventTimeMin = animPrevPos;
|
||||
float eventTimeMax = animPos;
|
||||
if (loop && context.DeltaTime * speed < 0)
|
||||
|
||||
if (eventTimeMin > eventTimeMax)
|
||||
{
|
||||
// Check if animation looped (for anim events shooting during backwards playback)
|
||||
//const float posNotLooped = startTimePos + oldTimePos;
|
||||
//if (posNotLooped < 0.0f || posNotLooped > length)
|
||||
//const int32 animPosCycle = Math::CeilToInt(animPos / anim->GetDuration());
|
||||
//const int32 animPrevPosCycle = Math::CeilToInt(animPrevPos / anim->GetDuration());
|
||||
//if (animPosCycle != animPrevPosCycle)
|
||||
{
|
||||
Swap(eventTimeMin, eventTimeMax);
|
||||
}
|
||||
Swap(eventTimeMin, eventTimeMax);
|
||||
}
|
||||
|
||||
const float eventTime = (float)(animPos / anim->Data.FramesPerSecond);
|
||||
const float eventDeltaTime = (float)((animPos - animPrevPos) / anim->Data.FramesPerSecond);
|
||||
for (const auto& track : anim->Events)
|
||||
@@ -251,7 +245,13 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
||||
continue;
|
||||
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
||||
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
|
||||
if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration)
|
||||
if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration
|
||||
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
|
||||
// Handle the edge case of an event on 0 or on max animation duration during looping
|
||||
|| (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration())
|
||||
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||
)
|
||||
{
|
||||
int32 stateIndex = -1;
|
||||
if (duration > 1)
|
||||
|
||||
@@ -531,7 +531,7 @@ bool Animation::SaveHeader(const ModelData& modelData, WriteStream& stream, int3
|
||||
// Nested animations
|
||||
stream.WriteInt32(0); // Empty list
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
void Animation::GetReferences(Array<Guid>& assets, Array<String>& files) const
|
||||
|
||||
@@ -395,7 +395,7 @@ bool Model::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
@@ -454,7 +454,7 @@ bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
bool Model::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk) const
|
||||
|
||||
@@ -329,7 +329,7 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
stream.Read(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
|
||||
|
||||
@@ -656,7 +656,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
@@ -686,7 +686,7 @@ bool SkinnedModel::SaveHeader(WriteStream& stream) const
|
||||
const int32 blendShapes = mesh.BlendShapes.Count();
|
||||
stream.Write((uint16)blendShapes);
|
||||
for (const auto& blendShape : mesh.BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
blendShape.SaveHeader(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "StringView.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
String String::Empty;
|
||||
const String String::Empty;
|
||||
|
||||
String::String(const StringAnsi& str)
|
||||
{
|
||||
|
||||
@@ -548,7 +548,7 @@ public:
|
||||
/// <summary>
|
||||
/// Instance of the empty string.
|
||||
/// </summary>
|
||||
static String Empty;
|
||||
static const String Empty;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -9,7 +9,7 @@ StringView StringBuilder::ToStringView() const
|
||||
return StringView(_data.Get(), _data.Count());
|
||||
}
|
||||
|
||||
StringView StringView::Empty;
|
||||
const StringView StringView::Empty;
|
||||
|
||||
StringView::StringView(const String& str)
|
||||
: StringViewBase<Char>(str.Get(), str.Length())
|
||||
|
||||
@@ -219,7 +219,7 @@ public:
|
||||
/// <summary>
|
||||
/// Instance of the empty string.
|
||||
/// </summary>
|
||||
static StringView Empty;
|
||||
static const StringView Empty;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
PACK_STRUCT(struct DecalMaterialShaderData {
|
||||
Matrix WorldMatrix;
|
||||
Matrix InvWorld;
|
||||
Matrix SVPositionToWorld;
|
||||
Matrix SvPositionToWorld;
|
||||
});
|
||||
|
||||
DrawPass DecalMaterialShader::GetDrawModes() const
|
||||
@@ -47,7 +47,9 @@ void DecalMaterialShader::Bind(BindParameters& params)
|
||||
MaterialParams::Bind(params.ParamsLink, bindMeta);
|
||||
|
||||
// Decals use depth buffer to draw on top of the objects
|
||||
context->BindSR(0, GET_TEXTURE_VIEW_SAFE(params.RenderContext.Buffers->DepthBuffer));
|
||||
GPUTexture* depthBuffer = params.RenderContext.Buffers->DepthBuffer;
|
||||
GPUTextureView* depthBufferView = EnumHasAnyFlags(depthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View();
|
||||
context->BindSR(0, depthBufferView);
|
||||
|
||||
// Setup material constants
|
||||
{
|
||||
@@ -65,7 +67,7 @@ void DecalMaterialShader::Bind(BindParameters& params)
|
||||
0, 0, 1, 0,
|
||||
-1.0f, 1.0f, 0, 1);
|
||||
const Matrix svPositionToWorld = offsetMatrix * view.IVP;
|
||||
Matrix::Transpose(svPositionToWorld, materialData->SVPositionToWorld);
|
||||
Matrix::Transpose(svPositionToWorld, materialData->SvPositionToWorld);
|
||||
}
|
||||
|
||||
// Bind constants
|
||||
@@ -90,16 +92,20 @@ void DecalMaterialShader::Unload()
|
||||
bool DecalMaterialShader::Load()
|
||||
{
|
||||
GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc0.VS = _shader->GetVS("VS_Decal");
|
||||
psDesc0.VS = _shader->GetVS("VS_Decal"); // TODO: move VS_Decal to be shared (eg. in GBuffer.shader)
|
||||
if (psDesc0.VS == nullptr)
|
||||
return true;
|
||||
psDesc0.PS = _shader->GetPS("PS_Decal");
|
||||
psDesc0.CullMode = CullMode::Normal;
|
||||
if (GPUDevice::Instance->Limits.HasReadOnlyDepth)
|
||||
{
|
||||
psDesc0.DepthEnable = true;
|
||||
psDesc0.DepthWriteEnable = false;
|
||||
}
|
||||
|
||||
switch (_info.DecalBlendingMode)
|
||||
{
|
||||
case MaterialDecalBlendingMode::Translucent:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::SrcAlpha;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -107,9 +113,7 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Stain:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::DestColor;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -117,9 +121,7 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Normal:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::SrcAlpha;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -127,13 +129,10 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Emissive:
|
||||
{
|
||||
psDesc0.BlendMode = BlendingMode::Additive;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_cache.Outside = GPUDevice::Instance->CreatePipelineState();
|
||||
if (_cache.Outside->Init(psDesc0))
|
||||
@@ -143,6 +142,7 @@ bool DecalMaterialShader::Load()
|
||||
}
|
||||
|
||||
psDesc0.CullMode = CullMode::Inverted;
|
||||
psDesc0.DepthEnable = false;
|
||||
_cache.Inside = GPUDevice::Instance->CreatePipelineState();
|
||||
if (_cache.Inside->Init(psDesc0))
|
||||
{
|
||||
|
||||
@@ -23,6 +23,8 @@ PACK_STRUCT(struct GUIMaterialShaderData {
|
||||
Float4 ViewInfo;
|
||||
Float4 ScreenSize;
|
||||
Float4 ViewSize;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
void GUIMaterialShader::Bind(BindParameters& params)
|
||||
@@ -55,7 +57,8 @@ void GUIMaterialShader::Bind(BindParameters& params)
|
||||
materialData->ViewPos = Float3::Zero;
|
||||
materialData->ViewFar = 0.0f;
|
||||
materialData->ViewDir = Float3::Forward;
|
||||
materialData->TimeParam = params.TimeParam;
|
||||
materialData->TimeParam = params.Time;
|
||||
materialData->UnscaledTimeParam = params.UnscaledTime;
|
||||
materialData->ViewInfo = Float4::Zero;
|
||||
auto& viewport = Render2D::GetViewport();
|
||||
materialData->ScreenSize = Float4(viewport.Width, viewport.Height, 1.0f / viewport.Width, 1.0f / viewport.Height);
|
||||
|
||||
@@ -148,7 +148,7 @@ public:
|
||||
const ::DrawCall* DrawCall = nullptr;
|
||||
MaterialParamsLink* ParamsLink = nullptr;
|
||||
void* CustomData = nullptr;
|
||||
float TimeParam;
|
||||
float Time, UnscaledTime;
|
||||
bool Instanced = false;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -36,12 +36,15 @@ GPU_CB_STRUCT(MaterialShaderDataPerView {
|
||||
Float4 TemporalAAJitter;
|
||||
Float3 LargeWorldsChunkIndex;
|
||||
float LargeWorldsChunkSize;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderContext& renderContext)
|
||||
: GPUContext(context)
|
||||
, RenderContext(renderContext)
|
||||
, TimeParam(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Time(Time::Draw.Time.GetTotalSeconds())
|
||||
, UnscaledTime(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -49,7 +52,8 @@ IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderC
|
||||
: GPUContext(context)
|
||||
, RenderContext(renderContext)
|
||||
, DrawCall(&drawCall)
|
||||
, TimeParam(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Time(Time::Draw.Time.GetTotalSeconds())
|
||||
, UnscaledTime(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Instanced(instanced)
|
||||
{
|
||||
}
|
||||
@@ -77,7 +81,8 @@ void IMaterial::BindParameters::BindViewData()
|
||||
cb.ViewPos = view.Position;
|
||||
cb.ViewFar = view.Far;
|
||||
cb.ViewDir = view.Direction;
|
||||
cb.TimeParam = TimeParam;
|
||||
cb.TimeParam = Time;
|
||||
cb.UnscaledTimeParam = UnscaledTime;
|
||||
cb.ViewInfo = view.ViewInfo;
|
||||
cb.ScreenSize = view.ScreenSize;
|
||||
cb.TemporalAAJitter = view.TemporalAAJitter;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// <summary>
|
||||
/// Current materials shader version.
|
||||
/// </summary>
|
||||
#define MATERIAL_GRAPH_VERSION 173
|
||||
#define MATERIAL_GRAPH_VERSION 174
|
||||
|
||||
class Material;
|
||||
class GPUShader;
|
||||
|
||||
@@ -21,6 +21,8 @@ PACK_STRUCT(struct PostFxMaterialShaderData {
|
||||
Float4 ScreenSize;
|
||||
Float4 TemporalAAJitter;
|
||||
Matrix InverseViewProjectionMatrix;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
void PostFxMaterialShader::Bind(BindParameters& params)
|
||||
@@ -51,7 +53,8 @@ void PostFxMaterialShader::Bind(BindParameters& params)
|
||||
materialData->ViewPos = view.Position;
|
||||
materialData->ViewFar = view.Far;
|
||||
materialData->ViewDir = view.Direction;
|
||||
materialData->TimeParam = params.TimeParam;
|
||||
materialData->TimeParam = params.Time;
|
||||
materialData->UnscaledTimeParam = params.UnscaledTime;
|
||||
materialData->ViewInfo = view.ViewInfo;
|
||||
materialData->ScreenSize = view.ScreenSize;
|
||||
materialData->TemporalAAJitter = view.TemporalAAJitter;
|
||||
|
||||
@@ -176,6 +176,14 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no
|
||||
GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
|
||||
}
|
||||
|
||||
void AnimatedModel::GetNodeTransformation(Array<NodeTransformation>& nodeTransformations, bool worldSpace) const
|
||||
{
|
||||
for (NodeTransformation& item : nodeTransformations)
|
||||
{
|
||||
GetNodeTransformation(item.NodeIndex, item.NodeMatrix, worldSpace);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace)
|
||||
{
|
||||
if (GraphInstance.NodesPose.IsEmpty())
|
||||
@@ -193,6 +201,33 @@ void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTra
|
||||
OnAnimationUpdated();
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(const Array<NodeTransformation>& nodeTransformations, bool worldSpace)
|
||||
{
|
||||
if (GraphInstance.NodesPose.IsEmpty())
|
||||
const_cast<AnimatedModel*>(this)->PreInitSkinningData(); // Ensure to have valid nodes pose to return
|
||||
|
||||
// Calculate it once, outside loop
|
||||
Matrix invWorld;
|
||||
if (worldSpace)
|
||||
{
|
||||
Matrix world;
|
||||
GetLocalToWorldMatrix(world);
|
||||
Matrix::Invert(world, invWorld);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeTransformations.Count(); i++)
|
||||
{
|
||||
int nodeIndex = nodeTransformations[i].NodeIndex;
|
||||
CHECK(nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count());
|
||||
GraphInstance.NodesPose[nodeIndex] = nodeTransformations[i].NodeMatrix;
|
||||
if (worldSpace)
|
||||
{
|
||||
GraphInstance.NodesPose[nodeIndex] = GraphInstance.NodesPose[nodeIndex] * invWorld;
|
||||
}
|
||||
}
|
||||
OnAnimationUpdated();
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace)
|
||||
{
|
||||
SetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
|
||||
@@ -821,7 +856,10 @@ void AnimatedModel::OnAnimationUpdated_Async()
|
||||
_skinningData.OnDataChanged(!PerBoneMotionBlur);
|
||||
}
|
||||
|
||||
UpdateBounds();
|
||||
if (UpdateWhenOffscreen)
|
||||
{
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedModel::OnAnimationUpdated_Sync()
|
||||
|
||||
@@ -18,6 +18,24 @@ class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
|
||||
DECLARE_SCENE_OBJECT(AnimatedModel);
|
||||
friend class AnimationsSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps the data of a Node and its relevant Transform Matrix together when passing it between functions.
|
||||
/// </summary>
|
||||
API_STRUCT() struct NodeTransformation
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NodeTransformation);
|
||||
|
||||
/// <summary>
|
||||
/// The index of the node in the node hierarchy.
|
||||
/// </summary>
|
||||
API_FIELD() uint32 NodeIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The transformation matrix of the node
|
||||
/// </summary>
|
||||
API_FIELD() Matrix NodeMatrix;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes the animation graph updates frequency for the animated model.
|
||||
/// </summary>
|
||||
@@ -236,6 +254,14 @@ public:
|
||||
/// <param name="worldSpace">True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.</param>
|
||||
API_FUNCTION() void GetNodeTransformation(const StringView& nodeName, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node final transformation for a series of nodes.
|
||||
/// </summary>
|
||||
/// <param name="nodeTransformations">The series of nodes that will be returned</param>
|
||||
/// <param name="worldSpace">True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.</param>
|
||||
/// <returns></returns>
|
||||
API_FUNCTION() void GetNodeTransformation(API_PARAM(Ref) Array<NodeTransformation>& nodeTransformations, bool worldSpace = false) const;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices.
|
||||
/// </summary>
|
||||
@@ -252,6 +278,14 @@ public:
|
||||
/// <param name="worldSpace">True if convert matrices from world-space, otherwise values will be in local-space of the actor.</param>
|
||||
API_FUNCTION() void SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace = false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a group of nodes final transformation.
|
||||
/// </summary>
|
||||
/// <param name="nodeTransformations">Array of the final node transformation matrix.</param>
|
||||
/// <param name="worldSpace">True if convert matrices from world-space, otherwise values will be in local-space of the actor.</param>
|
||||
/// <returns></returns>
|
||||
API_FUNCTION() void SetNodeTransformation(const Array<NodeTransformation>& nodeTransformations, bool worldSpace = false);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the closest node to a given location.
|
||||
/// </summary>
|
||||
|
||||
@@ -546,6 +546,19 @@ void ParticleEffect::OnParticleSystemModified()
|
||||
}
|
||||
|
||||
void ParticleEffect::OnParticleSystemLoaded()
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
#if USE_EDITOR
|
||||
// When one of the emitters gets edited, cached parameters need to be applied
|
||||
auto& emitters = ParticleSystem.Get()->Emitters;
|
||||
for (auto& emitter : emitters)
|
||||
{
|
||||
emitter.Loaded.BindUnique<ParticleEffect, &ParticleEffect::OnParticleEmitterLoaded>(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ParticleEffect::OnParticleEmitterLoaded()
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
}
|
||||
@@ -823,6 +836,10 @@ void ParticleEffect::OnActiveInTreeChanged()
|
||||
CacheModifiedParameters();
|
||||
Instance.ClearState();
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffect::OnTransformChanged()
|
||||
|
||||
@@ -394,6 +394,7 @@ private:
|
||||
void ApplyModifiedParameters();
|
||||
void OnParticleSystemModified();
|
||||
void OnParticleSystemLoaded();
|
||||
void OnParticleEmitterLoaded();
|
||||
|
||||
public:
|
||||
// [Actor]
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -18,281 +14,9 @@
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
const DateTime UnixEpoch(1970, 1, 1);
|
||||
|
||||
bool AppleFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeletePathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeletePathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
return DeletePathTree(pathANSI.Get());
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsANSI<> searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool AppleFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exist or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 AppleFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(StringAsANSI<>(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(StringAsANSI<>(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::CopyFile(const StringView& dst, const StringView& src)
|
||||
{
|
||||
const StringAsANSI<> srcANSI(*src, src.Length());
|
||||
@@ -352,156 +76,6 @@ out_error:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
void AppleFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
|
||||
{
|
||||
String home;
|
||||
|
||||
@@ -4,33 +4,17 @@
|
||||
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
#include "Engine/Platform/Unix/UnixFileSystem.h"
|
||||
|
||||
/// <summary>
|
||||
/// Apple platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API AppleFileSystem : public FileSystemBase
|
||||
class FLAXENGINE_API AppleFileSystem : public UnixFileSystem
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool CreateDirectory(const StringView& path);
|
||||
static bool DeleteDirectory(const String& path, bool deleteContents = true);
|
||||
static bool DirectoryExists(const StringView& path);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static bool CopyFile(const StringView& dst, const StringView& src);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
static void GetSpecialFolderPath(const SpecialFolder type, String& result);
|
||||
|
||||
private:
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,22 +4,16 @@
|
||||
|
||||
#include "LinuxFileSystem.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Platform/StringUtils.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringBuilder.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
@@ -165,280 +159,6 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeleteUnixPathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeleteUnixPathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
{
|
||||
return DeleteUnixPathTree(pathANSI.Get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 LinuxFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(StringAsUTF8<>(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(StringAsUTF8<>(*src, src.Length()).Get(), StringAsUTF8<>(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(StringAsUTF8<>(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src)
|
||||
{
|
||||
const StringAsUTF8<> srcANSI(*src, src.Length());
|
||||
@@ -612,156 +332,6 @@ bool LinuxFileSystem::UrnEncodePath(const char *path, char *result, const int ma
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
void LinuxFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
|
||||
{
|
||||
const String& home = LinuxPlatform::GetHomeDirectory();
|
||||
|
||||
@@ -4,38 +4,24 @@
|
||||
|
||||
#if PLATFORM_LINUX
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
#include "Engine/Platform/Unix/UnixFileSystem.h"
|
||||
|
||||
/// <summary>
|
||||
/// Linux platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API LinuxFileSystem : public FileSystemBase
|
||||
class FLAXENGINE_API LinuxFileSystem : public UnixFileSystem
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames);
|
||||
static bool ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path);
|
||||
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);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static bool CopyFile(const StringView& dst, const StringView& src);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static void GetSpecialFolderPath(const SpecialFolder type, String& result);
|
||||
|
||||
private:
|
||||
static bool UrnEncodePath(const char *path, char *result, int maxLength);
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static String getBaseName(const StringView& path);
|
||||
static String getNameWithoutExtension(const StringView& path);
|
||||
};
|
||||
|
||||
470
Source/Engine/Platform/Unix/UnixFileSystem.cpp
Normal file
470
Source/Engine/Platform/Unix/UnixFileSystem.cpp
Normal file
@@ -0,0 +1,470 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#if PLATFORM_UNIX
|
||||
|
||||
#include "UnixFileSystem.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
typedef StringAsANSI<> UnixString;
|
||||
#else
|
||||
typedef StringAsUTF8<> UnixString;
|
||||
#endif
|
||||
|
||||
const DateTime UnixEpoch(1970, 1, 1);
|
||||
|
||||
bool UnixFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const UnixString pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeleteUnixPathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeleteUnixPathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
{
|
||||
return DeleteUnixPathTree(pathANSI.Get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
const UnixString searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool UnixFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 UnixFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(UnixString(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(UnixString(*src, src.Length()).Get(), UnixString(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(UnixString(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
const int32 pathLength = strlen(entry->d_name);
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 ||
|
||||
StringUtils::Compare(searchPattern, "*") == 0 ||
|
||||
StringUtils::Compare(searchPattern, "*.*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength > 2 && searchPattern[searchPatternLength-1] == '*')
|
||||
{
|
||||
// Contains pattern
|
||||
bool match = false;
|
||||
for (int32 i = 0; i < pathLength - searchPatternLength - 1; i++)
|
||||
{
|
||||
int32 len = Math::Min(searchPatternLength - 2, pathLength - i);
|
||||
if (StringUtils::Compare(&entry->d_name[i], &searchPattern[1], len) == 0)
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
LOG(Warning, "DirectoryGetFiles: Wildcard filter is not implemented");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime UnixFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
#endif
|
||||
35
Source/Engine/Platform/Unix/UnixFileSystem.h
Normal file
35
Source/Engine/Platform/Unix/UnixFileSystem.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_UNIX
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
|
||||
/// <summary>
|
||||
/// Unix platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API UnixFileSystem : public FileSystemBase
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool CreateDirectory(const StringView& path);
|
||||
static bool DeleteDirectory(const String& path, bool deleteContents = true);
|
||||
static bool DirectoryExists(const StringView& path);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
|
||||
private:
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -434,6 +434,7 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light
|
||||
PROFILE_GPU_CPU("Decals");
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
auto buffers = renderContext.Buffers;
|
||||
GPUTextureView* depthBuffer = EnumHasAnyFlags(buffers->DepthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? buffers->DepthBuffer->ViewReadOnlyDepth() : nullptr;
|
||||
|
||||
// Sort decals from the lowest order to the highest order
|
||||
Sorting::QuickSort(decals.Get(), decals.Count(), &SortDecal);
|
||||
@@ -484,22 +485,22 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light
|
||||
count++;
|
||||
targetBuffers[2] = buffers->GBuffer1->View();
|
||||
}
|
||||
context->SetRenderTarget(nullptr, ToSpan(targetBuffers, count));
|
||||
context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, count));
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Stain:
|
||||
{
|
||||
context->SetRenderTarget(buffers->GBuffer0->View());
|
||||
context->SetRenderTarget(depthBuffer, buffers->GBuffer0->View());
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Normal:
|
||||
{
|
||||
context->SetRenderTarget(buffers->GBuffer1->View());
|
||||
context->SetRenderTarget(depthBuffer, buffers->GBuffer1->View());
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Emissive:
|
||||
{
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
context->SetRenderTarget(depthBuffer, lightBuffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ namespace
|
||||
MMethod* _method_LateFixedUpdate = nullptr;
|
||||
MMethod* _method_Draw = nullptr;
|
||||
MMethod* _method_Exit = nullptr;
|
||||
Array<Function<void()>> UpdateActions;
|
||||
Dictionary<StringAnsi, BinaryModule*, InlinedAllocation<64>> _nonNativeModules;
|
||||
#if USE_EDITOR
|
||||
bool LastBinariesLoadTriggeredCompilation = false;
|
||||
@@ -242,6 +243,27 @@ void ScriptingService::Update()
|
||||
PROFILE_CPU_NAMED("Scripting::Update");
|
||||
INVOKE_EVENT(Update);
|
||||
|
||||
// Flush update actions
|
||||
_objectsLocker.Lock();
|
||||
int32 count = UpdateActions.Count();
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
UpdateActions[i]();
|
||||
}
|
||||
int32 newlyAdded = UpdateActions.Count() - count;
|
||||
if (newlyAdded == 0)
|
||||
UpdateActions.Clear();
|
||||
else
|
||||
{
|
||||
// Someone added another action within current callback
|
||||
Array<Function<void()>> tmp;
|
||||
for (int32 i = newlyAdded; i < UpdateActions.Count(); i++)
|
||||
tmp.Add(UpdateActions[i]);
|
||||
UpdateActions.Clear();
|
||||
UpdateActions.Add(tmp);
|
||||
}
|
||||
_objectsLocker.Unlock();
|
||||
|
||||
#ifdef USE_NETCORE
|
||||
// Force GC to run in background periodically to avoid large blocking collections causing hitches
|
||||
if (Time::Update.TicksCount % 60 == 0)
|
||||
@@ -303,6 +325,13 @@ void Scripting::ProcessBuildInfoPath(String& path, const String& projectFolderPa
|
||||
path = projectFolderPath / path;
|
||||
}
|
||||
|
||||
void Scripting::InvokeOnUpdate(const Function<void()>& action)
|
||||
{
|
||||
_objectsLocker.Lock();
|
||||
UpdateActions.Add(action);
|
||||
_objectsLocker.Unlock();
|
||||
}
|
||||
|
||||
bool Scripting::LoadBinaryModules(const String& path, const String& projectFolderPath)
|
||||
{
|
||||
PROFILE_CPU_NAMED("LoadBinaryModules");
|
||||
|
||||
@@ -230,6 +230,11 @@ public:
|
||||
|
||||
static void ProcessBuildInfoPath(String& path, const String& projectFolderPath);
|
||||
|
||||
/// <summary>
|
||||
/// Calls the given action on the next scripting update.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to invoke.</param>
|
||||
static void InvokeOnUpdate(const Function<void()>& action);
|
||||
private:
|
||||
|
||||
static bool LoadBinaryModules(const String& path, const String& projectFolderPath);
|
||||
|
||||
@@ -61,7 +61,11 @@ void MemoryReadStream::ReadBytes(void* data, uint32 bytes)
|
||||
{
|
||||
if (bytes > 0)
|
||||
{
|
||||
ASSERT(data && GetLength() - GetPosition() >= bytes);
|
||||
if (!data || GetLength() - GetPosition() < bytes)
|
||||
{
|
||||
_hasError = true;
|
||||
return;
|
||||
}
|
||||
Platform::MemoryCopy(data, _position, bytes);
|
||||
_position += bytes;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
|
||||
const bool isArray = texture->Type == MaterialParameterType::GPUTextureArray;
|
||||
const bool isVolume = texture->Type == MaterialParameterType::GPUTextureVolume;
|
||||
const bool isNormalMap = texture->Type == MaterialParameterType::NormalMap;
|
||||
const bool canUseSample = CanUseSample(_treeType);
|
||||
MaterialGraphBox* valueBox = parent->GetBox(1);
|
||||
|
||||
// Check if has variable assigned
|
||||
@@ -63,6 +62,16 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
|
||||
// Check if hasn't been sampled during that tree eating
|
||||
if (valueBox->Cache.IsInvalid())
|
||||
{
|
||||
bool canUseSample = CanUseSample(_treeType);
|
||||
String mipLevel = TEXT("0");
|
||||
const auto layer = GetRootLayer();
|
||||
if (layer && layer->Domain == MaterialDomain::Decal && _treeType == MaterialTreeType::PixelShader)
|
||||
{
|
||||
// Decals use computed mip level due to ddx/ddy being unreliable
|
||||
canUseSample = false;
|
||||
mipLevel = String::Format(TEXT("CalculateTextureMipmap(input, {})"), texture->ShaderName);
|
||||
}
|
||||
|
||||
// Check if use custom UVs
|
||||
String uv;
|
||||
MaterialGraphBox* uvBox = parent->GetBox(0);
|
||||
@@ -94,10 +103,10 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
|
||||
// Sample texture
|
||||
if (isNormalMap)
|
||||
{
|
||||
const Char* format = canUseSample ? TEXT("{0}.Sample({1}, {2}).xyz") : TEXT("{0}.SampleLevel({1}, {2}, 0).xyz");
|
||||
const Char* format = canUseSample ? TEXT("{0}.Sample({1}, {2}).xyz") : TEXT("{0}.SampleLevel({1}, {2}, {3}).xyz");
|
||||
|
||||
// Sample encoded normal map
|
||||
const String sampledValue = String::Format(format, texture->ShaderName, sampler, uv);
|
||||
const String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, mipLevel);
|
||||
const auto normalVector = writeLocal(VariantType::Float3, sampledValue, parent);
|
||||
|
||||
// Decode normal vector
|
||||
@@ -123,12 +132,12 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
|
||||
}
|
||||
else*/
|
||||
{
|
||||
format = canUseSample ? TEXT("{0}.Sample({1}, {2})") : TEXT("{0}.SampleLevel({1}, {2}, 0)");
|
||||
format = canUseSample ? TEXT("{0}.Sample({1}, {2})") : TEXT("{0}.SampleLevel({1}, {2}, {3})");
|
||||
}
|
||||
}
|
||||
|
||||
// Sample texture
|
||||
String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, _ddx.Value, _ddy.Value);
|
||||
String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, mipLevel);
|
||||
valueBox->Cache = writeLocal(VariantType::Float4, sampledValue, parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +48,8 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value)
|
||||
}
|
||||
// Time
|
||||
case 3:
|
||||
{
|
||||
value = getTime;
|
||||
value = box->ID == 1 ? getUnscaledTime : getTime;
|
||||
break;
|
||||
}
|
||||
// Panner
|
||||
case 6:
|
||||
{
|
||||
|
||||
@@ -107,6 +107,7 @@ bool FeatureData::Init()
|
||||
|
||||
MaterialValue MaterialGenerator::getUVs(VariantType::Float2, TEXT("input.TexCoord"));
|
||||
MaterialValue MaterialGenerator::getTime(VariantType::Float, TEXT("TimeParam"));
|
||||
MaterialValue MaterialGenerator::getUnscaledTime(VariantType::Float, TEXT("UnscaledTimeParam"));
|
||||
MaterialValue MaterialGenerator::getNormal(VariantType::Float3, TEXT("input.TBN[2]"));
|
||||
MaterialValue MaterialGenerator::getNormalZero(VariantType::Float3, TEXT("float3(0, 0, 1)"));
|
||||
MaterialValue MaterialGenerator::getVertexColor(VariantType::Float4, TEXT("GetVertexColor(input)"));
|
||||
|
||||
@@ -120,7 +120,6 @@ private:
|
||||
MaterialValue _ddx, _ddy, _cameraVector;
|
||||
|
||||
public:
|
||||
|
||||
MaterialGenerator();
|
||||
~MaterialGenerator();
|
||||
|
||||
@@ -211,6 +210,7 @@ public:
|
||||
|
||||
static MaterialValue getUVs;
|
||||
static MaterialValue getTime;
|
||||
static MaterialValue getUnscaledTime;
|
||||
static MaterialValue getNormal;
|
||||
static MaterialValue getNormalZero;
|
||||
static MaterialValue getVertexColor;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace FlaxEngine.GUI
|
||||
/// Initializes a new instance of the <see cref="MaterialBrush"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="material">The material.</param>
|
||||
public MaterialBrush(Material material)
|
||||
public MaterialBrush(MaterialBase material)
|
||||
{
|
||||
Material = material;
|
||||
}
|
||||
|
||||
@@ -134,6 +134,10 @@ namespace FlaxEngine.Utilities
|
||||
if (isLeadingSlash)
|
||||
Move();
|
||||
|
||||
// Dont process if wrong slash is used.
|
||||
if (c =='\\')
|
||||
return false;
|
||||
|
||||
// Parse tag
|
||||
bool result = ParseTag(ref tag, name);
|
||||
|
||||
@@ -204,6 +208,10 @@ namespace FlaxEngine.Utilities
|
||||
SkipWhitespace();
|
||||
while (Peek() != '>')
|
||||
{
|
||||
// Return false if start of new html tag is detected.
|
||||
if (Peek() == '<')
|
||||
return false;
|
||||
|
||||
if (Peek() == '/')
|
||||
{
|
||||
// Handle trailing forward slash
|
||||
|
||||
@@ -122,6 +122,10 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f
|
||||
|
||||
// Calculate shadow
|
||||
ShadowSample shadow = GetShadow(lightData, gBuffer, shadowMask);
|
||||
#if !LIGHTING_NO_DIRECTIONAL
|
||||
// Directional shadowing
|
||||
shadow.SurfaceShadow *= NoL;
|
||||
#endif
|
||||
|
||||
// Calculate attenuation
|
||||
if (isRadial)
|
||||
@@ -135,11 +139,6 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f
|
||||
shadow.TransmissionShadow *= attenuation;
|
||||
}
|
||||
|
||||
#if !LIGHTING_NO_DIRECTIONAL
|
||||
// Reduce shadow mapping artifacts
|
||||
shadow.SurfaceShadow *= saturate(NoL * 6.0f - 0.2f) * NoL;
|
||||
#endif
|
||||
|
||||
BRANCH
|
||||
if (shadow.SurfaceShadow + shadow.TransmissionShadow > 0)
|
||||
{
|
||||
|
||||
@@ -176,6 +176,8 @@ cbuffer ViewData : register(b1)
|
||||
float4 TemporalAAJitter;
|
||||
float3 LargeWorldsChunkIndex;
|
||||
float LargeWorldsChunkSize;
|
||||
float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ static const uint g_numTaps[4] = { 3, 5, 8, 12 };
|
||||
#define SSAO_NORMAL_BASED_EDGES_ENABLE_AT_QUALITY_PRESET (2) // to disable simply set to 99 or similar
|
||||
#define SSAO_NORMAL_BASED_EDGES_DOT_THRESHOLD (0.5) // use 0-0.1 for super-sharp normal-based edges
|
||||
//
|
||||
#define SSAO_DETAIL_AO_ENABLE_AT_QUALITY_PRESET (1) // whether to use DetailAOStrength; to disable simply set to 99 or similar
|
||||
#define SSAO_DETAIL_AO_ENABLE_AT_QUALITY_PRESET (99) // whether to use DetailAOStrength; to disable simply set to 99 or similar
|
||||
//
|
||||
#define SSAO_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET (99) // !!warning!! the MIP generation on the C++ side will be enabled on quality preset 2 regardless of this value, so if changing here, change the C++ side too
|
||||
#define SSAO_DEPTH_MIPS_GLOBAL_OFFSET (-4.3) // best noise/quality/performance tradeoff, found empirically
|
||||
|
||||
@@ -513,6 +513,7 @@ namespace Flax.Build
|
||||
// Combine build options from this module
|
||||
project.CSharp.SystemReferences.AddRange(moduleBuildOptions.ScriptingAPI.SystemReferences);
|
||||
project.CSharp.FileReferences.AddRange(moduleBuildOptions.ScriptingAPI.FileReferences);
|
||||
project.CSharp.NugetPackageReferences.AddRange(moduleBuildOptions.NugetPackageReferences);
|
||||
|
||||
// Find references based on the modules dependencies (external or from projects)
|
||||
foreach (var dependencyName in moduleBuildOptions.PublicDependencies.Concat(moduleBuildOptions.PrivateDependencies))
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace Flax.Build
|
||||
// Merge module into target environment
|
||||
buildData.TargetOptions.LinkEnv.InputFiles.AddRange(moduleOptions.OutputFiles);
|
||||
buildData.TargetOptions.DependencyFiles.AddRange(moduleOptions.DependencyFiles);
|
||||
buildData.TargetOptions.NugetPackageReferences.AddRange(moduleOptions.NugetPackageReferences);
|
||||
buildData.TargetOptions.OptionalDependencyFiles.AddRange(moduleOptions.OptionalDependencyFiles);
|
||||
buildData.TargetOptions.Libraries.AddRange(moduleOptions.Libraries);
|
||||
buildData.TargetOptions.DelayLoadLibraries.AddRange(moduleOptions.DelayLoadLibraries);
|
||||
@@ -141,6 +142,19 @@ namespace Flax.Build
|
||||
var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile));
|
||||
graph.AddCopyFile(dstFile, srcFile);
|
||||
}
|
||||
|
||||
if (targetBuildOptions.NugetPackageReferences.Any())
|
||||
{
|
||||
var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
|
||||
foreach (var reference in targetBuildOptions.NugetPackageReferences)
|
||||
{
|
||||
var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll");
|
||||
if (!File.Exists(path))
|
||||
Utilities.RestoreNugetPackages(graph, target);
|
||||
var dstFile = Path.Combine(outputPath, Path.GetFileName(path));
|
||||
graph.AddCopyFile(dstFile, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +297,18 @@ namespace Flax.Build
|
||||
args.Add(string.Format("/reference:\"{0}{1}.dll\"", referenceAssemblies, reference));
|
||||
foreach (var reference in fileReferences)
|
||||
args.Add(string.Format("/reference:\"{0}\"", reference));
|
||||
|
||||
// Reference Nuget package
|
||||
if (buildData.TargetOptions.NugetPackageReferences.Any())
|
||||
{
|
||||
var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
|
||||
foreach (var reference in buildOptions.NugetPackageReferences)
|
||||
{
|
||||
var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll");
|
||||
args.Add(string.Format("/reference:\"{0}\"", path));
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_NETCORE
|
||||
foreach (var systemAnalyzer in buildOptions.ScriptingAPI.SystemAnalyzers)
|
||||
args.Add(string.Format("/analyzer:\"{0}{1}.dll\"", referenceAnalyzers, systemAnalyzer));
|
||||
|
||||
@@ -220,6 +220,7 @@ namespace Flax.Build
|
||||
exeBuildOptions.LinkEnv.InputLibraries.Add(Path.Combine(buildOptions.OutputFolder, buildOptions.Platform.GetLinkOutputFileName(LibraryName, engineLibraryType)));
|
||||
exeBuildOptions.LinkEnv.InputFiles.AddRange(mainModuleOptions.OutputFiles);
|
||||
exeBuildOptions.DependencyFiles.AddRange(mainModuleOptions.DependencyFiles);
|
||||
exeBuildOptions.NugetPackageReferences.AddRange(mainModuleOptions.NugetPackageReferences);
|
||||
exeBuildOptions.OptionalDependencyFiles.AddRange(mainModuleOptions.OptionalDependencyFiles);
|
||||
exeBuildOptions.Libraries.AddRange(mainModuleOptions.Libraries);
|
||||
exeBuildOptions.DelayLoadLibraries.AddRange(mainModuleOptions.DelayLoadLibraries);
|
||||
|
||||
@@ -70,6 +70,40 @@ namespace Flax.Build.NativeCpp
|
||||
Annotations,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a Nuget Package
|
||||
/// </summary>
|
||||
public struct NugetPackage
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the nuget package.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// The version of the nuget package.
|
||||
/// </summary>
|
||||
public string Version;
|
||||
|
||||
/// <summary>
|
||||
/// The target framework. ex. net8.0, netstandard2.1
|
||||
/// </summary>
|
||||
public string Framework;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the nuget package.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the package.</param>
|
||||
/// <param name="version">The version of the package.</param>
|
||||
/// <param name="framework">The target framework. ex. net8.0, netstandard2.1, etc.</param>
|
||||
public NugetPackage(string name, string version, string framework)
|
||||
{
|
||||
Name = name;
|
||||
Version = version;
|
||||
Framework = framework;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The native C++ module build settings container.
|
||||
/// </summary>
|
||||
@@ -129,6 +163,11 @@ namespace Flax.Build.NativeCpp
|
||||
/// The collection of the modules that are required by this module (for linking).
|
||||
/// </summary>
|
||||
public List<string> PrivateDependencies = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The nuget package references.
|
||||
/// </summary>
|
||||
public List<NugetPackage> NugetPackageReferences = new List<NugetPackage>();
|
||||
|
||||
/// <summary>
|
||||
/// The collection of defines with preprocessing symbol for a source files of this module. Inherited by the modules that include it.
|
||||
|
||||
@@ -425,6 +425,7 @@ namespace Flax.Build
|
||||
moduleOptions.LinkEnv.InputFiles.AddRange(dependencyOptions.OutputFiles);
|
||||
moduleOptions.DependencyFiles.AddRange(dependencyOptions.DependencyFiles);
|
||||
moduleOptions.OptionalDependencyFiles.AddRange(dependencyOptions.OptionalDependencyFiles);
|
||||
moduleOptions.NugetPackageReferences.AddRange(dependencyOptions.NugetPackageReferences);
|
||||
moduleOptions.PrivateIncludePaths.AddRange(dependencyOptions.PublicIncludePaths);
|
||||
moduleOptions.Libraries.AddRange(dependencyOptions.Libraries);
|
||||
moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries);
|
||||
@@ -440,6 +441,7 @@ namespace Flax.Build
|
||||
moduleOptions.LinkEnv.InputFiles.AddRange(dependencyOptions.OutputFiles);
|
||||
moduleOptions.DependencyFiles.AddRange(dependencyOptions.DependencyFiles);
|
||||
moduleOptions.OptionalDependencyFiles.AddRange(dependencyOptions.OptionalDependencyFiles);
|
||||
moduleOptions.NugetPackageReferences.AddRange(dependencyOptions.NugetPackageReferences);
|
||||
moduleOptions.PublicIncludePaths.AddRange(dependencyOptions.PublicIncludePaths);
|
||||
moduleOptions.Libraries.AddRange(dependencyOptions.Libraries);
|
||||
moduleOptions.DelayLoadLibraries.AddRange(dependencyOptions.DelayLoadLibraries);
|
||||
@@ -934,6 +936,7 @@ namespace Flax.Build
|
||||
buildData.TargetOptions.LinkEnv.InputFiles.AddRange(moduleOptions.OutputFiles);
|
||||
buildData.TargetOptions.DependencyFiles.AddRange(moduleOptions.DependencyFiles);
|
||||
buildData.TargetOptions.OptionalDependencyFiles.AddRange(moduleOptions.OptionalDependencyFiles);
|
||||
buildData.TargetOptions.NugetPackageReferences.AddRange(moduleOptions.NugetPackageReferences);
|
||||
buildData.TargetOptions.Libraries.AddRange(moduleOptions.Libraries);
|
||||
buildData.TargetOptions.DelayLoadLibraries.AddRange(moduleOptions.DelayLoadLibraries);
|
||||
buildData.TargetOptions.ScriptingAPI.Add(moduleOptions.ScriptingAPI);
|
||||
@@ -1054,6 +1057,19 @@ namespace Flax.Build
|
||||
var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile));
|
||||
graph.AddCopyFile(dstFile, srcFile);
|
||||
}
|
||||
|
||||
if (targetBuildOptions.NugetPackageReferences.Any())
|
||||
{
|
||||
var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
|
||||
foreach (var reference in targetBuildOptions.NugetPackageReferences)
|
||||
{
|
||||
var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll");
|
||||
if (!File.Exists(path))
|
||||
Utilities.RestoreNugetPackages(graph, target);
|
||||
var dstFile = Path.Combine(outputPath, Path.GetFileName(path));
|
||||
graph.AddCopyFile(dstFile, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1192,6 +1208,7 @@ namespace Flax.Build
|
||||
buildData.TargetOptions.ExternalModules.AddRange(moduleOptions.ExternalModules);
|
||||
buildData.TargetOptions.DependencyFiles.AddRange(moduleOptions.DependencyFiles);
|
||||
buildData.TargetOptions.OptionalDependencyFiles.AddRange(moduleOptions.OptionalDependencyFiles);
|
||||
buildData.TargetOptions.NugetPackageReferences.AddRange(moduleOptions.NugetPackageReferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1253,6 +1270,19 @@ namespace Flax.Build
|
||||
var dstFile = Path.Combine(outputPath, Path.GetFileName(srcFile));
|
||||
graph.AddCopyFile(dstFile, srcFile);
|
||||
}
|
||||
|
||||
if (targetBuildOptions.NugetPackageReferences.Any())
|
||||
{
|
||||
var nugetPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
|
||||
foreach (var reference in targetBuildOptions.NugetPackageReferences)
|
||||
{
|
||||
var path = Path.Combine(nugetPath, reference.Name, reference.Version, "lib", reference.Framework, $"{reference.Name}.dll");
|
||||
if (!File.Exists(path))
|
||||
Utilities.RestoreNugetPackages(graph, target);
|
||||
var dstFile = Path.Combine(outputPath, Path.GetFileName(path));
|
||||
graph.AddCopyFile(dstFile, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +229,11 @@ namespace Flax.Build.Projects
|
||||
/// The .Net libraries references (dll or exe files paths).
|
||||
/// </summary>
|
||||
public HashSet<string> FileReferences;
|
||||
|
||||
/// <summary>
|
||||
/// The nuget references.
|
||||
/// </summary>
|
||||
public HashSet<NugetPackage> NugetPackageReferences;
|
||||
|
||||
/// <summary>
|
||||
/// The output folder path (optional).
|
||||
@@ -248,6 +253,7 @@ namespace Flax.Build.Projects
|
||||
{
|
||||
SystemReferences = new HashSet<string>(),
|
||||
FileReferences = new HashSet<string>(),
|
||||
NugetPackageReferences = new HashSet<NugetPackage>(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -175,6 +175,19 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
csProjectFileContent.AppendLine(" </PropertyGroup>");
|
||||
}
|
||||
|
||||
// Nuget
|
||||
if (project.CSharp.NugetPackageReferences.Any())
|
||||
{
|
||||
csProjectFileContent.AppendLine(" <ItemGroup>");
|
||||
|
||||
foreach (var reference in project.CSharp.NugetPackageReferences)
|
||||
{
|
||||
csProjectFileContent.AppendLine(string.Format(" <PackageReference Include=\"{0}\" Version=\"{1}\" />", reference.Name, reference.Version));
|
||||
}
|
||||
|
||||
csProjectFileContent.AppendLine(" </ItemGroup>");
|
||||
}
|
||||
|
||||
// References
|
||||
|
||||
csProjectFileContent.AppendLine(" <ItemGroup>");
|
||||
|
||||
@@ -134,6 +134,20 @@ namespace Flax.Build.Projects.VisualStudio
|
||||
}
|
||||
|
||||
// References
|
||||
|
||||
// Nuget
|
||||
if (project.CSharp.NugetPackageReferences.Any())
|
||||
{
|
||||
csProjectFileContent.AppendLine(" <ItemGroup>");
|
||||
|
||||
foreach (var reference in project.CSharp.NugetPackageReferences)
|
||||
{
|
||||
csProjectFileContent.AppendLine(string.Format(" <PackageReference Include=\"{0}\" Version=\"{1}\" />", reference.Name, reference.Version));
|
||||
}
|
||||
|
||||
csProjectFileContent.AppendLine(" </ItemGroup>");
|
||||
csProjectFileContent.AppendLine("");
|
||||
}
|
||||
|
||||
csProjectFileContent.AppendLine(" <ItemGroup>");
|
||||
|
||||
|
||||
@@ -16,6 +16,47 @@ namespace Flax.Build
|
||||
/// </summary>
|
||||
public static class Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the .Net SDK path.
|
||||
/// </summary>
|
||||
/// <returns>The path.</returns>
|
||||
public static string GetDotNetPath()
|
||||
{
|
||||
var buildPlatform = Platform.BuildTargetPlatform;
|
||||
var dotnetSdk = DotNetSdk.Instance;
|
||||
if (!dotnetSdk.IsValid)
|
||||
throw new DotNetSdk.MissingException();
|
||||
var dotnetPath = "dotnet";
|
||||
switch (buildPlatform)
|
||||
{
|
||||
case TargetPlatform.Windows:
|
||||
dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet.exe");
|
||||
break;
|
||||
case TargetPlatform.Linux: break;
|
||||
case TargetPlatform.Mac:
|
||||
dotnetPath = Path.Combine(dotnetSdk.RootPath, "dotnet");
|
||||
break;
|
||||
default: throw new InvalidPlatformException(buildPlatform);
|
||||
}
|
||||
return dotnetPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores a targets nuget packages.
|
||||
/// </summary>
|
||||
/// <param name="graph">The task graph.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="dotNetPath">The dotnet path.</param>
|
||||
public static void RestoreNugetPackages(Graph.TaskGraph graph, Target target)
|
||||
{
|
||||
var dotNetPath = GetDotNetPath();
|
||||
var task = graph.Add<Graph.Task>();
|
||||
task.WorkingDirectory = target.FolderPath;
|
||||
task.InfoMessage = $"Restoring Nuget Packages for {target.Name}";
|
||||
task.CommandPath = dotNetPath;
|
||||
task.CommandArguments = $"restore";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hash code for the string (the same for all platforms). Matches Engine algorithm for string hashing.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user