Merge remote-tracking branch 'origin/master' into 1.8

# Conflicts:
#	Flax.flaxproj
#	Source/Editor/Cooker/Steps/DeployDataStep.cpp
This commit is contained in:
Wojtek Figat
2023-12-20 00:39:15 +01:00
92 changed files with 1509 additions and 776 deletions

BIN
Content/Shaders/SSR.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -5,6 +5,7 @@
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Content/Upgraders/TextureAssetUpgrader.h"
@@ -92,15 +93,12 @@ SpriteHandle PreviewsCache::FindSlot(const Guid& id)
{
if (WaitForLoaded())
return SpriteHandle::Invalid;
// Find entry
int32 index;
if (_assets.Find(id, index))
{
const String spriteName = StringUtils::ToString(index);
return FindSprite(spriteName);
}
return SpriteHandle::Invalid;
}
@@ -114,6 +112,17 @@ Asset::LoadResult PreviewsCache::load()
return LoadResult::Failed;
_assets.Set(previewsMetaChunk->Get<Guid>(), ASSETS_ICONS_PER_ATLAS);
// Verify if cached assets still exist (don't store thumbnails for removed files)
AssetInfo assetInfo;
for (Guid& id : _assets)
{
if (id.IsValid() && Content::GetAsset(id) == nullptr && !Content::GetAssetInfo(id, assetInfo))
{
// Free slot (no matter the texture contents)
id = Guid::Empty;
}
}
// Setup atlas sprites array
Sprite sprite;
sprite.Area.Size = static_cast<float>(ASSET_ICON_SIZE) / ASSETS_ICONS_ATLAS_SIZE;
@@ -162,7 +171,7 @@ SpriteHandle PreviewsCache::OccupySlot(GPUTexture* source, const Guid& id)
if (WaitForLoaded())
return SpriteHandle::Invalid;
// Find free slot and for that asset
// Find this asset slot or use the first empty
int32 index = _assets.Find(id);
if (index == INVALID_INDEX)
index = _assets.Find(Guid::Empty);
@@ -201,14 +210,12 @@ bool PreviewsCache::ReleaseSlot(const Guid& id)
{
bool result = false;
ScopeLock lock(Locker);
int32 index = _assets.Find(id);
if (index != INVALID_INDEX)
{
_assets[index] = Guid::Empty;
result = true;
}
return result;
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine;
@@ -68,5 +69,15 @@ namespace FlaxEditor.Content
{
return new SceneItem(path, id);
}
/// <inheritdoc />
public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
{
var id = ((SceneItem)item).ID;
if (Level.FindScene(id) == null)
{
menu.AddButton("Open (additive)", () => { Editor.Instance.Scene.OpenScene(id, true); });
}
}
}
}

View File

@@ -35,6 +35,11 @@ namespace FlaxEditor.Content.Thumbnails
/// The finalized state.
/// </summary>
Disposed,
/// <summary>
/// The request has failed (eg. asset cannot be loaded).
/// </summary>
Failed,
};
/// <summary>
@@ -78,6 +83,14 @@ namespace FlaxEditor.Content.Thumbnails
Proxy = proxy;
}
internal void Update()
{
if (State == States.Prepared && (!Asset || Asset.LastLoadFailed))
{
State = States.Failed;
}
}
/// <summary>
/// Prepares this request.
/// </summary>
@@ -85,11 +98,8 @@ namespace FlaxEditor.Content.Thumbnails
{
if (State != States.Created)
throw new InvalidOperationException();
// Prepare
Asset = FlaxEngine.Content.LoadAsync(Item.Path);
Proxy.OnThumbnailDrawPrepare(this);
State = States.Prepared;
}
@@ -101,9 +111,7 @@ namespace FlaxEditor.Content.Thumbnails
{
if (State != States.Prepared)
throw new InvalidOperationException();
Item.Thumbnail = icon;
State = States.Rendered;
}

View File

@@ -21,15 +21,11 @@ namespace FlaxEditor.Content.Thumbnails
/// </summary>
public const float MinimumRequiredResourcesQuality = 0.8f;
// TODO: free atlas slots for deleted assets
private readonly List<PreviewsCache> _cache = new List<PreviewsCache>(4);
private readonly string _cacheFolder;
private DateTime _lastFlushTime;
private readonly List<ThumbnailRequest> _requests = new List<ThumbnailRequest>(128);
private readonly PreviewRoot _guiRoot = new PreviewRoot();
private DateTime _lastFlushTime;
private RenderTask _task;
private GPUTexture _output;
@@ -88,7 +84,6 @@ namespace FlaxEditor.Content.Thumbnails
}
}
// Add request
AddRequest(assetItem, proxy);
}
}
@@ -118,15 +113,15 @@ namespace FlaxEditor.Content.Thumbnails
for (int i = 0; i < _cache.Count; i++)
{
if (_cache[i].ReleaseSlot(assetItem.ID))
{
break;
}
}
}
}
internal static bool HasMinimumQuality(TextureBase asset)
{
if (asset.HasStreamingError)
return true; // Don't block thumbnails queue when texture fails to stream in (eg. unsupported format)
var mipLevels = asset.MipLevels;
var minMipLevels = Mathf.Min(mipLevels, 7);
return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality));
@@ -198,13 +193,7 @@ namespace FlaxEditor.Content.Thumbnails
/// <inheritdoc />
void IContentItemOwner.OnItemDeleted(ContentItem item)
{
if (item is AssetItem assetItem)
{
lock (_requests)
{
RemoveRequest(assetItem);
}
}
DeletePreview(item);
}
/// <inheritdoc />
@@ -494,10 +483,7 @@ namespace FlaxEditor.Content.Thumbnails
{
// Wait some frames before start generating previews (late init feature)
if (Time.TimeSinceStartup < 1.0f || HasAllAtlasesLoaded() == false)
{
// Back
return;
}
lock (_requests)
{
@@ -515,6 +501,7 @@ namespace FlaxEditor.Content.Thumbnails
var request = _requests[i];
try
{
request.Update();
if (request.IsReady)
{
isAnyReady = true;
@@ -523,6 +510,10 @@ namespace FlaxEditor.Content.Thumbnails
{
request.Prepare();
}
else if (request.State == ThumbnailRequest.States.Failed)
{
_requests.RemoveAt(i--);
}
}
catch (Exception ex)
{

View File

@@ -169,6 +169,30 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
permissions += String::Format(TEXT("\n <uses-permission android:name=\"{0}\" />"), e.Item);
}
// Setup default Android screen orientation
auto defaultOrienation = platformSettings->DefaultOrientation;
String orientation = String("fullSensor");
switch (defaultOrienation)
{
case AndroidPlatformSettings::ScreenOrientation::Portrait:
orientation = String("portrait");
break;
case AndroidPlatformSettings::ScreenOrientation::PortraitReverse:
orientation = String("reversePortrait");
break;
case AndroidPlatformSettings::ScreenOrientation::LandscapeRight:
orientation = String("landscape");
break;
case AndroidPlatformSettings::ScreenOrientation::LandscapeLeft:
orientation = String("reverseLandscape");
break;
case AndroidPlatformSettings::ScreenOrientation::AutoRotation:
orientation = String("fullSensor");
break;
default:
break;
}
// Setup Android application attributes
String attributes;
if (data.Configuration != BuildConfiguration::Release)
@@ -223,6 +247,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${PackageName}"), packageName);
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${ProjectVersion}"), projectVersion);
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidPermissions}"), permissions);
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${DefaultOrientation}"), orientation);
EditorUtilities::ReplaceInFile(manifestPath, TEXT("${AndroidAttributes}"), attributes);
const String stringsPath = data.OriginalOutputPath / TEXT("app/src/main/res/values/strings.xml");
EditorUtilities::ReplaceInFile(stringsPath, TEXT("${ProjectName}"), gameSettings->ProductName);

View File

@@ -48,21 +48,21 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (buildSettings.SkipDotnetPackaging && data.Tools->UseSystemDotnet())
{
// Use system-installed .Net Runtime
// Use system-installed .NET Runtime
FileSystem::DeleteDirectory(dstDotnet);
}
else
{
// Deploy .Net Runtime files
// Deploy .NET Runtime files
FileSystem::CreateDirectory(dstDotnet);
String srcDotnet = depsRoot / TEXT("Dotnet");
if (FileSystem::DirectoryExists(srcDotnet))
{
// Use prebuilt .Net installation for that platform
LOG(Info, "Using .Net Runtime {} at {}", data.Tools->GetName(), srcDotnet);
// Use prebuilt .NET installation for that platform
LOG(Info, "Using .NET Runtime {} at {}", data.Tools->GetName(), srcDotnet);
if (EditorUtilities::CopyDirectoryIfNewer(dstDotnet, srcDotnet, true))
{
data.Error(TEXT("Failed to copy .Net runtime data files."));
data.Error(TEXT("Failed to copy .NET runtime data files."));
return true;
}
}
@@ -85,7 +85,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (canUseSystemDotnet && (aotMode == DotNetAOTModes::None || aotMode == DotNetAOTModes::ILC))
{
// Ask Flax.Build to provide .Net SDK location for the current platform
// Ask Flax.Build to provide .NET SDK location for the current platform
String sdks;
bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
@@ -101,7 +101,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (failed || !FileSystem::DirectoryExists(srcDotnet))
{
data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
data.Error(TEXT("Failed to get .NET SDK location for the current host platform."));
return true;
}
@@ -110,7 +110,7 @@ bool DeployDataStep::Perform(CookingData& data)
FileSystem::GetChildDirectories(versions, srcDotnet / TEXT("host/fxr"));
if (versions.Count() == 0)
{
data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
data.Error(TEXT("Failed to find any .NET hostfxr versions for the current host platform."));
return true;
}
for (String& version : versions)
@@ -121,8 +121,14 @@ bool DeployDataStep::Perform(CookingData& data)
}
Sorting::QuickSort(versions);
const String version = versions.Last();
if (version.IsEmpty())
{
data.Error(TEXT("Failed to find supported .NET hostfxr version for the current host platform."));
return true;
}
FileSystem::NormalizePath(srcDotnet);
LOG(Info, "Using .Net Runtime {} at {}", version, srcDotnet);
LOG(Info, "Using .NET Runtime {} at {}", version, srcDotnet);
// Check if previously deployed files are valid (eg. system-installed .NET was updated from version 7.0.3 to 7.0.5)
{
@@ -158,13 +164,13 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (failed)
{
data.Error(TEXT("Failed to copy .Net runtime data files."));
data.Error(TEXT("Failed to copy .NET runtime data files."));
return true;
}
}
else
{
// Ask Flax.Build to provide .Net Host Runtime location for the target platform
// Ask Flax.Build to provide .NET Host Runtime location for the target platform
String sdks;
const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName);
@@ -180,11 +186,11 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (failed || !FileSystem::DirectoryExists(srcDotnet))
{
data.Error(TEXT("Failed to get .Net SDK location for a current platform."));
data.Error(TEXT("Failed to get .NET SDK location for the current host platform."));
return true;
}
FileSystem::NormalizePath(srcDotnet);
LOG(Info, "Using .Net Runtime {} at {}", TEXT("Host"), srcDotnet);
LOG(Info, "Using .NET Runtime {} at {}", TEXT("Host"), srcDotnet);
// Deploy runtime files
const Char* corlibPrivateName = TEXT("System.Private.CoreLib.dll");
@@ -249,7 +255,7 @@ bool DeployDataStep::Perform(CookingData& data)
DEPLOY_NATIVE_FILE("libmonosgen-2.0.dylib");
DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.dylib");
DEPLOY_NATIVE_FILE("libSystem.Native.dylib");
DEPLOY_NATIVE_FILE("libSystem.Net.Security.Native.dylib");
DEPLOY_NATIVE_FILE("libSystem.NET.Security.Native.dylib");
DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Apple.dylib");
break;
#undef DEPLOY_NATIVE_FILE
@@ -257,7 +263,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (failed)
{
data.Error(TEXT("Failed to copy .Net runtime data files."));
data.Error(TEXT("Failed to copy .NET runtime data files."));
return true;
}
}
@@ -278,7 +284,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
if (ScriptsBuilder::RunBuildTool(args))
{
data.Error(TEXT("Failed to optimize .Net class library."));
data.Error(TEXT("Failed to optimize .NET class library."));
return true;
}
}

View File

@@ -7,9 +7,8 @@ using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using FlaxEngine.Utilities;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.CustomEditors
{
@@ -386,22 +385,22 @@ namespace FlaxEditor.CustomEditors
LinkedLabel = label;
}
private void RevertDiffToDefault(CustomEditor editor)
{
if (editor.ChildrenEditors.Count == 0)
{
// Skip if no change detected
if (!editor.Values.IsDefaultValueModified)
return;
/// <summary>
/// If true, the value reverting to default/reference will be handled via iteration over children editors, instead of for a whole object at once.
/// </summary>
public virtual bool RevertValueWithChildren => ChildrenEditors.Count != 0;
editor.SetValueToDefault();
private void RevertDiffToDefault()
{
if (RevertValueWithChildren)
{
foreach (var child in ChildrenEditors)
child.RevertDiffToDefault();
}
else
{
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
{
RevertDiffToDefault(editor.ChildrenEditors[i]);
}
if (Values.IsDefaultValueModified)
SetValueToDefault();
}
}
@@ -414,11 +413,6 @@ namespace FlaxEditor.CustomEditors
{
if (!Values.IsDefaultValueModified)
return false;
// Skip array items (show diff only on a bottom level properties and fields)
if (ParentEditor is Editors.ArrayEditor)
return false;
return true;
}
}
@@ -430,7 +424,7 @@ namespace FlaxEditor.CustomEditors
{
if (!Values.HasDefaultValue)
return;
RevertDiffToDefault(this);
RevertDiffToDefault();
}
/// <summary>
@@ -468,22 +462,17 @@ namespace FlaxEditor.CustomEditors
}
}
private void RevertDiffToReference(CustomEditor editor)
private void RevertDiffToReference()
{
if (editor.ChildrenEditors.Count == 0)
if (RevertValueWithChildren)
{
// Skip if no change detected
if (!editor.Values.IsReferenceValueModified)
return;
editor.SetValueToReference();
foreach (var child in ChildrenEditors)
child.RevertDiffToReference();
}
else
{
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
{
RevertDiffToReference(editor.ChildrenEditors[i]);
}
if (Values.IsReferenceValueModified)
SetValueToReference();
}
}
@@ -496,11 +485,6 @@ namespace FlaxEditor.CustomEditors
{
if (!Values.IsReferenceValueModified)
return false;
// Skip array items (show diff only on a bottom level properties and fields)
if (ParentEditor is Editors.ArrayEditor)
return false;
return true;
}
}
@@ -512,7 +496,7 @@ namespace FlaxEditor.CustomEditors
{
if (!Values.HasReferenceValue)
return;
RevertDiffToReference(this);
RevertDiffToReference();
}
/// <summary>
@@ -657,7 +641,7 @@ namespace FlaxEditor.CustomEditors
// Default
try
{
obj = JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings);
obj = Newtonsoft.Json.JsonConvert.DeserializeObject(text, TypeUtils.GetType(Values.Type), JsonSerializer.Settings);
}
catch
{
@@ -762,7 +746,7 @@ namespace FlaxEditor.CustomEditors
/// </summary>
public void SetValueToDefault()
{
SetValue(Values.DefaultValue);
SetValueCloned(Values.DefaultValue);
}
/// <summary>
@@ -799,7 +783,19 @@ namespace FlaxEditor.CustomEditors
return;
}
SetValue(Values.ReferenceValue);
SetValueCloned(Values.ReferenceValue);
}
private void SetValueCloned(object value)
{
// For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor
if (value != null && !value.GetType().IsValueType)
{
var json = JsonSerializer.Serialize(value);
value = JsonSerializer.Deserialize(json, value.GetType());
}
SetValue(value);
}
/// <summary>
@@ -811,7 +807,6 @@ namespace FlaxEditor.CustomEditors
{
if (_isSetBlocked)
return;
if (OnDirty(this, value, token))
{
_hasValueDirty = true;

View File

@@ -31,6 +31,11 @@ public class ModelPrefabEditor : GenericEditor
_prefabId = modelPrefab.PrefabID;
while (true)
{
if (_prefabId == Guid.Empty)
{
break;
}
var prefab = FlaxEngine.Content.Load<Prefab>(_prefabId);
if (prefab)
{

View File

@@ -87,6 +87,7 @@ namespace FlaxEditor.CustomEditors.Editors
protected bool NotNullItems;
private IntegerValueElement _size;
private PropertyNameLabel _sizeLabel;
private Color _background;
private int _elementsCount;
private bool _readOnly;
@@ -109,6 +110,9 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
/// <inheritdoc />
public override bool RevertValueWithChildren => false; // Always revert value for a whole collection
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
@@ -174,7 +178,9 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
_size = dragArea.IntegerValue("Size");
var sizeProperty = dragArea.AddPropertyItem("Size");
_sizeLabel = sizeProperty.Labels.Last();
_size = sizeProperty.IntegerValue();
_size.IntValue.MinValue = 0;
_size.IntValue.MaxValue = ushort.MaxValue;
_size.IntValue.Value = size;
@@ -274,6 +280,15 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
/// <inheritdoc />
protected override void Deinitialize()
{
_size = null;
_sizeLabel = null;
base.Deinitialize();
}
/// <summary>
/// Rebuilds the parent layout if its collection.
/// </summary>
@@ -296,7 +311,6 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (IsSetBlocked)
return;
Resize(_size.IntValue.Value);
}
@@ -311,11 +325,9 @@ namespace FlaxEditor.CustomEditors.Editors
return;
var cloned = CloneValues();
var tmp = cloned[dstIndex];
cloned[dstIndex] = cloned[srcIndex];
cloned[srcIndex] = tmp;
SetValue(cloned);
}
@@ -371,6 +383,17 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentValues || HasDifferentTypes)
return;
// Update reference/default value indicator
if (_sizeLabel != null)
{
var color = Color.Transparent;
if (Values.HasReferenceValue && Values.ReferenceValue is IList referenceValue && referenceValue.Count != Count)
color = FlaxEngine.GUI.Style.Current.BackgroundSelected;
else if (Values.HasDefaultValue && Values.DefaultValue is IList defaultValue && defaultValue.Count != Count)
color = Color.Yellow * 0.8f;
_sizeLabel.HighlightStripColor = color;
}
// Check if collection has been resized (by UI or from external source)
if (Count != _elementsCount)
{

View File

@@ -404,13 +404,23 @@ int32 Editor::LoadProduct()
// Create new project option
if (CommandLine::Options.NewProject)
{
Array<String> projectFiles;
FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly);
if (projectFiles.Count() == 1)
{
// Skip creating new project if it already exists
LOG(Info, "Skip creatinng new project because it already exists");
CommandLine::Options.NewProject.Reset();
}
}
if (CommandLine::Options.NewProject)
{
if (projectPath.IsEmpty())
projectPath = Platform::GetWorkingDirectory();
else if (!FileSystem::DirectoryExists(projectPath))
FileSystem::CreateDirectory(projectPath);
FileSystem::NormalizePath(projectPath);
String folderName = StringUtils::GetFileName(projectPath);
String tmpName;
for (int32 i = 0; i < folderName.Length(); i++)

View File

@@ -1343,108 +1343,6 @@ namespace FlaxEditor
public float AutoRebuildNavMeshTimeoutMs;
}
[StructLayout(LayoutKind.Sequential)]
[NativeMarshalling(typeof(VisualScriptLocalMarshaller))]
internal struct VisualScriptLocal
{
public string Value;
public string ValueTypeName;
public uint NodeId;
public int BoxId;
}
[CustomMarshaller(typeof(VisualScriptLocal), MarshalMode.Default, typeof(VisualScriptLocalMarshaller))]
internal static class VisualScriptLocalMarshaller
{
[StructLayout(LayoutKind.Sequential)]
internal struct VisualScriptLocalNative
{
public IntPtr Value;
public IntPtr ValueTypeName;
public uint NodeId;
public int BoxId;
}
internal static VisualScriptLocal ConvertToManaged(VisualScriptLocalNative unmanaged) => ToManaged(unmanaged);
internal static VisualScriptLocalNative ConvertToUnmanaged(VisualScriptLocal managed) => ToNative(managed);
internal static VisualScriptLocal ToManaged(VisualScriptLocalNative managed)
{
return new VisualScriptLocal()
{
Value = ManagedString.ToManaged(managed.Value),
ValueTypeName = ManagedString.ToManaged(managed.ValueTypeName),
NodeId = managed.NodeId,
BoxId = managed.BoxId,
};
}
internal static VisualScriptLocalNative ToNative(VisualScriptLocal managed)
{
return new VisualScriptLocalNative()
{
Value = ManagedString.ToNative(managed.Value),
ValueTypeName = ManagedString.ToNative(managed.ValueTypeName),
NodeId = managed.NodeId,
BoxId = managed.BoxId,
};
}
internal static void Free(VisualScriptLocalNative unmanaged)
{
ManagedString.Free(unmanaged.Value);
ManagedString.Free(unmanaged.ValueTypeName);
}
}
[StructLayout(LayoutKind.Sequential)]
[NativeMarshalling(typeof(VisualScriptStackFrameMarshaller))]
internal struct VisualScriptStackFrame
{
public VisualScript Script;
public uint NodeId;
public int BoxId;
}
[CustomMarshaller(typeof(VisualScriptStackFrame), MarshalMode.Default, typeof(VisualScriptStackFrameMarshaller))]
internal static class VisualScriptStackFrameMarshaller
{
[StructLayout(LayoutKind.Sequential)]
internal struct VisualScriptStackFrameNative
{
public IntPtr Script;
public uint NodeId;
public int BoxId;
}
internal static VisualScriptStackFrame ConvertToManaged(VisualScriptStackFrameNative unmanaged) => ToManaged(unmanaged);
internal static VisualScriptStackFrameNative ConvertToUnmanaged(VisualScriptStackFrame managed) => ToNative(managed);
internal static VisualScriptStackFrame ToManaged(VisualScriptStackFrameNative managed)
{
return new VisualScriptStackFrame()
{
Script = VisualScriptMarshaller.ConvertToManaged(managed.Script),
NodeId = managed.NodeId,
BoxId = managed.BoxId,
};
}
internal static VisualScriptStackFrameNative ToNative(VisualScriptStackFrame managed)
{
return new VisualScriptStackFrameNative()
{
Script = VisualScriptMarshaller.ConvertToUnmanaged(managed.Script),
NodeId = managed.NodeId,
BoxId = managed.BoxId,
};
}
internal static void Free(VisualScriptStackFrameNative unmanaged)
{
}
}
internal void BuildCommand(string arg)
{
if (TryBuildCommand(arg))
@@ -1723,21 +1621,6 @@ namespace FlaxEditor
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_RunVisualScriptBreakpointLoopTick", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_RunVisualScriptBreakpointLoopTick(float deltaTime);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptLocals", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "localsCount")]
internal static partial VisualScriptLocal[] Internal_GetVisualScriptLocals(out int localsCount);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptStackFrames", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "stackFrameCount")]
internal static partial VisualScriptStackFrame[] Internal_GetVisualScriptStackFrames(out int stackFrameCount);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetVisualScriptPreviousScopeFrame", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial VisualScriptStackFrame Internal_GetVisualScriptPreviousScopeFrame();
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_EvaluateVisualScriptLocal", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_EvaluateVisualScriptLocal(IntPtr script, ref VisualScriptLocal local);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_DeserializeSceneObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_DeserializeSceneObject(IntPtr sceneObject, string json);

View File

@@ -3,6 +3,8 @@
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using System.Collections.Generic;
namespace FlaxEditor.GUI.Dialogs
{
@@ -30,6 +32,8 @@ namespace FlaxEditor.GUI.Dialogs
private const float HSVMargin = 0.0f;
private const float ChannelsMargin = 4.0f;
private const float ChannelTextWidth = 12.0f;
private const float SavedColorButtonWidth = 20.0f;
private const float SavedColorButtonHeight = 20.0f;
private Color _initialValue;
private Color _value;
@@ -52,6 +56,9 @@ namespace FlaxEditor.GUI.Dialogs
private Button _cOK;
private Button _cEyedropper;
private List<Color> _savedColors = new List<Color>();
private List<Button> _savedColorButtons = new List<Button>();
/// <summary>
/// Gets the selected color.
/// </summary>
@@ -111,6 +118,12 @@ namespace FlaxEditor.GUI.Dialogs
_onChanged = colorChanged;
_onClosed = pickerClosed;
// Get saved colors if they exist
if (Editor.Instance.ProjectCache.TryGetCustomData("ColorPickerSavedColors", out var savedColors))
{
_savedColors = JsonSerializer.Deserialize<List<Color>>(savedColors);
}
// Selector
_cSelector = new ColorSelectorWithSliders(180, 18)
{
@@ -195,6 +208,9 @@ namespace FlaxEditor.GUI.Dialogs
};
_cOK.Clicked += OnSubmit;
// Create saved color buttons
CreateAllSaveButtons();
// Eyedropper button
var style = Style.Current;
_cEyedropper = new Button(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin)
@@ -216,6 +232,50 @@ namespace FlaxEditor.GUI.Dialogs
SelectedColor = initialValue;
}
private void OnSavedColorButtonClicked(Button button)
{
if (button.Tag == null)
{
// Prevent setting same color 2 times... because why...
foreach (var color in _savedColors)
{
if (color == _value)
{
return;
}
}
// Set color of button to current value;
button.BackgroundColor = _value;
button.BackgroundColorHighlighted = _value;
button.BackgroundColorSelected = _value.RGBMultiplied(0.8f);
button.Text = "";
button.Tag = _value;
// Save new colors
_savedColors.Add(_value);
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
// create new + button
if (_savedColorButtons.Count < 8)
{
var savedColorButton = new Button(PickerMargin * (_savedColorButtons.Count + 1) + SavedColorButtonWidth * _savedColorButtons.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
Tag = null,
};
savedColorButton.ButtonClicked += (b) => OnSavedColorButtonClicked(b);
_savedColorButtons.Add(savedColorButton);
}
}
else
{
SelectedColor = (Color)button.Tag;
}
}
private void OnColorPicked(Color32 colorPicked)
{
if (_activeEyedropper)
@@ -340,6 +400,111 @@ namespace FlaxEditor.GUI.Dialogs
return base.OnKeyDown(key);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (base.OnMouseUp(location, button))
{
return true;
}
var child = GetChildAtRecursive(location);
if (button == MouseButton.Right && child is Button b && b.Tag is Color c)
{
// Show menu
var menu = new ContextMenu.ContextMenu();
var replaceButton = menu.AddButton("Replace");
replaceButton.Clicked += () => OnSavedColorReplace(b);
var deleteButton = menu.AddButton("Delete");
deleteButton.Clicked += () => OnSavedColorDelete(b);
_disableEvents = true;
menu.Show(this, location);
menu.VisibleChanged += (c) => _disableEvents = false;
return true;
}
return false;
}
private void OnSavedColorReplace(Button button)
{
// Prevent setting same color 2 times... because why...
foreach (var color in _savedColors)
{
if (color == _value)
{
return;
}
}
// Set new Color in spot
for (int i = 0; i < _savedColors.Count; i++)
{
var color = _savedColors[i];
if (color == (Color)button.Tag)
{
color = _value;
}
}
// Set color of button to current value;
button.BackgroundColor = _value;
button.BackgroundColorHighlighted = _value;
button.Text = "";
button.Tag = _value;
// Save new colors
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
}
private void OnSavedColorDelete(Button button)
{
_savedColors.Remove((Color)button.Tag);
foreach (var b in _savedColorButtons)
{
Children.Remove(b);
}
_savedColorButtons.Clear();
CreateAllSaveButtons();
// Save new colors
var savedColors = JsonSerializer.Serialize(_savedColors, typeof(List<Color>));
Editor.Instance.ProjectCache.SetCustomData("ColorPickerSavedColors", savedColors);
}
private void CreateAllSaveButtons()
{
// Create saved color buttons
for (int i = 0; i < _savedColors.Count; i++)
{
var savedColor = _savedColors[i];
var savedColorButton = new Button(PickerMargin * (i + 1) + SavedColorButtonWidth * i, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
{
Parent = this,
Tag = savedColor,
BackgroundColor = savedColor,
BackgroundColorHighlighted = savedColor,
BackgroundColorSelected = savedColor.RGBMultiplied(0.8f),
};
savedColorButton.ButtonClicked += (b) => OnSavedColorButtonClicked(b);
_savedColorButtons.Add(savedColorButton);
}
if (_savedColors.Count < 8)
{
var savedColorButton = new Button(PickerMargin * (_savedColors.Count + 1) + SavedColorButtonWidth * _savedColors.Count, Height - SavedColorButtonHeight - PickerMargin, SavedColorButtonWidth, SavedColorButtonHeight)
{
Text = "+",
Parent = this,
Tag = null,
};
savedColorButton.ButtonClicked += (b) => OnSavedColorButtonClicked(b);
_savedColorButtons.Add(savedColorButton);
}
}
/// <inheritdoc />
public override void OnSubmit()
{

View File

@@ -526,133 +526,6 @@ DEFINE_INTERNAL_CALL(void) EditorInternal_RunVisualScriptBreakpointLoopTick(floa
Engine::OnDraw();
}
struct VisualScriptLocalManaged
{
MString* Value;
MString* ValueTypeName;
uint32 NodeId;
int32 BoxId;
};
DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptLocals(int* localsCount)
{
MArray* result = nullptr;
*localsCount = 0;
const auto stack = VisualScripting::GetThreadStackTop();
if (stack && stack->Scope)
{
const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count();
const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptLocal");
ASSERT(mclass);
result = MCore::Array::New(mclass, count);
VisualScriptLocalManaged local;
local.NodeId = MAX_uint32;
if (stack->Scope->Parameters.Length() != 0)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
if (s)
local.NodeId = s->Node->ID;
}
VisualScriptLocalManaged* resultPtr = MCore::Array::GetAddress<VisualScriptLocalManaged>(result);
for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++)
{
auto& v = stack->Scope->Parameters[i];
local.BoxId = i + 1;
local.Value = MUtils::ToString(v.ToString());
local.ValueTypeName = MUtils::ToString(v.Type.GetTypeName());
resultPtr[i] = local;
}
for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++)
{
auto& v = stack->Scope->ReturnedValues[i];
local.NodeId = v.NodeId;
local.BoxId = v.BoxId;
local.Value = MUtils::ToString(v.Value.ToString());
local.ValueTypeName = MUtils::ToString(v.Value.Type.GetTypeName());
resultPtr[stack->Scope->Parameters.Length() + i] = local;
}
*localsCount = count;
}
return result;
}
struct VisualScriptStackFrameManaged
{
MObject* Script;
uint32 NodeId;
int32 BoxId;
};
DEFINE_INTERNAL_CALL(MArray*) EditorInternal_GetVisualScriptStackFrames(int* stackFramesCount)
{
MArray* result = nullptr;
*stackFramesCount = 0;
const auto stack = VisualScripting::GetThreadStackTop();
if (stack)
{
int32 count = 0;
auto s = stack;
while (s)
{
s = s->PreviousFrame;
count++;
}
const MClass* mclass = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly->GetClass("FlaxEditor.Editor+VisualScriptStackFrame");
ASSERT(mclass);
result = MCore::Array::New(mclass, count);
VisualScriptStackFrameManaged* resultPtr = MCore::Array::GetAddress<VisualScriptStackFrameManaged>(result);
s = stack;
count = 0;
while (s)
{
VisualScriptStackFrameManaged frame;
frame.Script = s->Script->GetOrCreateManagedInstance();
frame.NodeId = s->Node->ID;
frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
resultPtr[count] = frame;
s = s->PreviousFrame;
count++;
}
*stackFramesCount = count;
}
return result;
}
DEFINE_INTERNAL_CALL(VisualScriptStackFrameManaged) EditorInternal_GetVisualScriptPreviousScopeFrame()
{
VisualScriptStackFrameManaged frame;
Platform::MemoryClear(&frame, sizeof(frame));
const auto stack = VisualScripting::GetThreadStackTop();
if (stack)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
if (s && s->PreviousFrame)
{
s = s->PreviousFrame;
frame.Script = s->Script->GetOrCreateManagedInstance();
frame.NodeId = s->Node->ID;
frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
}
}
return frame;
}
DEFINE_INTERNAL_CALL(bool) EditorInternal_EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocalManaged* local)
{
Variant v;
if (VisualScripting::Evaluate(script, VisualScripting::GetThreadStackTop()->Instance, local->NodeId, local->BoxId, v))
{
local->Value = MUtils::ToString(v.ToString());
local->ValueTypeName = MUtils::ToString(v.Type.GetTypeName());
return true;
}
return false;
}
DEFINE_INTERNAL_CALL(void) EditorInternal_DeserializeSceneObject(SceneObject* sceneObject, MString* jsonObj)
{
PROFILE_CPU_NAMED("DeserializeSceneObject");

View File

@@ -489,6 +489,95 @@ void ManagedEditor::RequestStartPlayOnEditMode()
Internal_RequestStartPlayOnEditMode->Invoke(GetManagedInstance(), nullptr, nullptr);
}
Array<ManagedEditor::VisualScriptStackFrame> ManagedEditor::GetVisualScriptStackFrames()
{
Array<VisualScriptStackFrame> result;
const auto stack = VisualScripting::GetThreadStackTop();
auto s = stack;
while (s)
{
VisualScriptStackFrame& frame = result.AddOne();
frame.Script = s->Script;
frame.NodeId = s->Node->ID;
frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
s = s->PreviousFrame;
}
return result;
}
ManagedEditor::VisualScriptStackFrame ManagedEditor::GetVisualScriptPreviousScopeFrame()
{
VisualScriptStackFrame frame;
Platform::MemoryClear(&frame, sizeof(frame));
const auto stack = VisualScripting::GetThreadStackTop();
if (stack)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
if (s && s->PreviousFrame)
{
s = s->PreviousFrame;
frame.Script = s->Script;
frame.NodeId = s->Node->ID;
frame.BoxId = s->Box ? s->Box->ID : MAX_uint32;
}
}
return frame;
}
Array<ManagedEditor::VisualScriptLocal> ManagedEditor::GetVisualScriptLocals()
{
Array<VisualScriptLocal> result;
const auto stack = VisualScripting::GetThreadStackTop();
if (stack && stack->Scope)
{
const int32 count = stack->Scope->Parameters.Length() + stack->Scope->ReturnedValues.Count();
result.Resize(count);
VisualScriptLocal local;
local.NodeId = MAX_uint32;
if (stack->Scope->Parameters.Length() != 0)
{
auto s = stack;
while (s->PreviousFrame && s->PreviousFrame->Scope == stack->Scope)
s = s->PreviousFrame;
if (s)
local.NodeId = s->Node->ID;
}
for (int32 i = 0; i < stack->Scope->Parameters.Length(); i++)
{
auto& v = stack->Scope->Parameters[i];
local.BoxId = i + 1;
local.Value = v.ToString();
local.ValueTypeName = v.Type.GetTypeName();
result[i] = local;
}
for (int32 i = 0; i < stack->Scope->ReturnedValues.Count(); i++)
{
auto& v = stack->Scope->ReturnedValues[i];
local.NodeId = v.NodeId;
local.BoxId = v.BoxId;
local.Value = v.Value.ToString();
local.ValueTypeName = v.Value.Type.GetTypeName();
result[stack->Scope->Parameters.Length() + i] = local;
}
}
return result;
}
bool ManagedEditor::EvaluateVisualScriptLocal(VisualScript* script, VisualScriptLocal& local)
{
Variant v;
const auto stack = VisualScripting::GetThreadStackTop();
if (stack && VisualScripting::Evaluate(script, stack->Instance, local.NodeId, local.BoxId, v))
{
local.Value = v.ToString();
local.ValueTypeName = v.Type.GetTypeName();
return true;
}
return false;
}
void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly)
{
ASSERT(!HasManagedInstance());

View File

@@ -210,6 +210,31 @@ public:
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath);
#endif
public:
API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame
{
DECLARE_SCRIPTING_TYPE_MINIMAL(VisualScriptStackFrame);
API_FIELD() class VisualScript* Script;
API_FIELD() uint32 NodeId;
API_FIELD() int32 BoxId;
};
API_STRUCT(Internal, NoDefault) struct VisualScriptLocal
{
DECLARE_SCRIPTING_TYPE_MINIMAL(VisualScriptLocal);
API_FIELD() String Value;
API_FIELD() String ValueTypeName;
API_FIELD() uint32 NodeId;
API_FIELD() int32 BoxId;
};
API_FUNCTION(Internal) static Array<VisualScriptStackFrame> GetVisualScriptStackFrames();
API_FUNCTION(Internal) static VisualScriptStackFrame GetVisualScriptPreviousScopeFrame();
API_FUNCTION(Internal) static Array<VisualScriptLocal> GetVisualScriptLocals();
API_FUNCTION(Internal) static bool EvaluateVisualScriptLocal(VisualScript* script, API_PARAM(Ref) VisualScriptLocal& local);
private:
void OnEditorAssemblyLoaded(MAssembly* assembly);

View File

@@ -20,7 +20,7 @@ namespace FlaxEditor.Modules
private bool _updateOrFixedUpdateWasCalled;
private long _breakpointHangFlag;
private EditorWindow _enterPlayFocusedWindow;
private Scene[] _scenesToReload;
private Guid[] _scenesToReload;
internal SimulationModule(Editor editor)
: base(editor)
@@ -138,8 +138,15 @@ namespace FlaxEditor.Modules
Editor.Simulation.RequestStartPlayScenes();
return;
}
if (!FlaxEngine.Content.GetAssetInfo(firstScene.ID, out var info))
{
Editor.LogWarning("Invalid First Scene in Game Settings.");
}
_scenesToReload = Level.Scenes;
// Load scenes after entering the play mode
_scenesToReload = new Guid[Level.ScenesCount];
for (int i = 0; i < _scenesToReload.Length; i++)
_scenesToReload[i] = Level.GetScene(i).ID;
Level.UnloadAllScenes();
Level.LoadScene(firstScene);
@@ -153,8 +160,8 @@ namespace FlaxEditor.Modules
Level.UnloadAllScenes();
foreach (var scene in _scenesToReload)
Level.LoadScene(scene.ID);
foreach (var sceneId in _scenesToReload)
Level.LoadScene(sceneId);
}
/// <summary>

View File

@@ -7,9 +7,37 @@ using Real = System.Single;
#endif
using FlaxEngine;
using FlaxEditor.CustomEditors.Dedicated;
using FlaxEditor.CustomEditors;
using FlaxEditor.Scripting;
namespace FlaxEditor.SceneGraph.Actors
{
/// <summary>
/// Dedicated custom editor for BoxCollider objects.
/// </summary>
[CustomEditor(typeof(BoxCollider)), DefaultEditor]
public class BoxColliderEditor : ActorEditor
{
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
layout.Space(20f);
layout.Button("Resize to Fit", Editor.Instance.CodeDocs.GetTooltip(new ScriptMemberInfo(typeof(BoxCollider).GetMethod("AutoResize")))).Button.Clicked += OnResizeClicked;
}
private void OnResizeClicked()
{
foreach (var value in Values)
{
if (value is BoxCollider collider)
collider.AutoResize();
}
}
}
/// <summary>
/// Scene tree node for <see cref="BoxCollider"/> actor type.
/// </summary>
@@ -37,5 +65,18 @@ namespace FlaxEditor.SceneGraph.Actors
return base.RayCastSelf(ref ray, out distance, out normal);
}
/// <inheritdoc />
public override void PostSpawn()
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
((BoxCollider)Actor).AutoResize();
}
}
}

View File

@@ -334,6 +334,11 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
// Setup for an initial spline
var spline = (Spline)Actor;
spline.AddSplineLocalPoint(Vector3.Zero, false);

View File

@@ -61,10 +61,15 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
// Setup for default values
var text = (SpriteRender)Actor;
text.Material = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.DefaultSpriteMaterial);
text.Image = FlaxEngine.Content.LoadAsyncInternal<Texture>(EditorAssets.FlaxIconTexture);
var sprite = (SpriteRender)Actor;
sprite.Material = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.DefaultSpriteMaterial);
sprite.Image = FlaxEngine.Content.LoadAsyncInternal<Texture>(EditorAssets.FlaxIconTexture);
}
}
}

View File

@@ -22,6 +22,11 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
// Setup for default values
var text = (TextRender)Actor;
text.Text = "My Text";

View File

@@ -29,6 +29,11 @@ namespace FlaxEditor.SceneGraph.Actors
{
base.PostSpawn();
if (Actor.HasPrefabLink)
{
return;
}
// Rotate to match the space (GUI uses upper left corner as a root)
Actor.LocalOrientation = Quaternion.Euler(0, -180, -180);
bool canSpawn = true;

View File

@@ -698,6 +698,10 @@ namespace FlaxEditor.SceneGraph.GUI
if (thisHasScene != otherHasScene)
return false;
// Reject dragging actors between prefab windows (different roots)
if (!thisHasScene && ActorNode.Root != actorNode.Root)
return false;
// Reject dragging parents and itself
return actorNode.Actor != null && actorNode != ActorNode && actorNode.Find(Actor) == null;
}

View File

@@ -493,7 +493,15 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 10,
Title = "Blend Additive",
Description = "Blend animation poses (with additive mode)",
Description =
"Blend animation poses (with additive mode)" +
"\n" +
"\nNote: " +
"\nOrder of nodes matters, because Additive animation is appplayed on top of curent frame." +
"\n" +
"\nTip for blender users:" +
"\nInside NLA the the order is bottom (first node in flax) to the top (last node in flax)" +
"\nu need to place it in this order to get correct resoults",
Flags = NodeFlags.AnimGraph,
Size = new Float2(170, 80),
DefaultValues = new object[]

View File

@@ -755,6 +755,29 @@ namespace FlaxEditor.Surface.Archetypes
}
}
/// <inheritdoc />
public override void OnDeleted(SurfaceNodeActions action)
{
// Unlink from the current parent (when deleted by user)
var node = Node;
if (node != null)
{
if (action == SurfaceNodeActions.User)
{
var decorators = node.DecoratorIds;
decorators.Remove(ID);
node.DecoratorIds = decorators;
}
else
{
node._decorators = null;
node.ResizeAuto();
}
}
base.OnDeleted(action);
}
public override void OnSurfaceCanEditChanged(bool canEdit)
{
base.OnSurfaceCanEditChanged(canEdit);

View File

@@ -19,6 +19,7 @@ namespace FlaxEditor.Surface.Undo
private ushort _typeId;
private Float2 _nodeLocation;
private object[] _nodeValues;
private SurfaceNodeActions _actionType = SurfaceNodeActions.User; // Action usage flow is first to apply user effect such as removing/adding node, then we use Undo type so node can react to this
public AddRemoveNodeAction(SurfaceNode node, bool isAdd)
{
@@ -38,6 +39,7 @@ namespace FlaxEditor.Surface.Undo
Add();
else
Remove();
_actionType = SurfaceNodeActions.Undo;
}
/// <inheritdoc />
@@ -67,8 +69,8 @@ namespace FlaxEditor.Surface.Undo
else if (_nodeValues != null && _nodeValues.Length != 0)
throw new InvalidOperationException("Invalid node values.");
node.Location = _nodeLocation;
context.OnControlLoaded(node, SurfaceNodeActions.Undo);
node.OnSurfaceLoaded(SurfaceNodeActions.Undo);
context.OnControlLoaded(node, _actionType);
node.OnSurfaceLoaded(_actionType);
context.MarkAsModified();
}
@@ -89,7 +91,7 @@ namespace FlaxEditor.Surface.Undo
// Remove node
context.Nodes.Remove(node);
context.OnControlDeleted(node, SurfaceNodeActions.Undo);
context.OnControlDeleted(node, _actionType);
context.MarkAsModified();
}

View File

@@ -837,7 +837,7 @@ namespace FlaxEditor.Surface
actions.Add(action);
}
Undo.AddAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes"));
AddBatchedUndoAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Remove node" : "Remove nodes"));
}
}

View File

@@ -62,6 +62,19 @@ namespace FlaxEditor.Surface
return true;
}
private string GetBoxDebuggerTooltip(ref Editor.VisualScriptLocal local)
{
if (string.IsNullOrEmpty(local.ValueTypeName))
{
if (string.IsNullOrEmpty(local.Value))
return string.Empty;
return local.Value;
}
if (string.IsNullOrEmpty(local.Value))
return $"({local.ValueTypeName})";
return $"{local.Value}\n({local.ValueTypeName})";
}
/// <inheritdoc />
public override void OnNodeBreakpointEdited(SurfaceNode node)
{
@@ -95,7 +108,7 @@ namespace FlaxEditor.Surface
ref var local = ref state.Locals[i];
if (local.BoxId == box.ID && local.NodeId == box.ParentNode.ID)
{
text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})";
text = GetBoxDebuggerTooltip(ref local);
return true;
}
}
@@ -107,7 +120,7 @@ namespace FlaxEditor.Surface
ref var local = ref state.Locals[i];
if (local.BoxId == connectedBox.ID && local.NodeId == connectedBox.ParentNode.ID)
{
text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})";
text = GetBoxDebuggerTooltip(ref local);
return true;
}
}
@@ -123,9 +136,33 @@ namespace FlaxEditor.Surface
BoxId = box.ID,
};
var script = ((Windows.Assets.VisualScriptWindow)box.Surface.Owner).Asset;
if (Editor.Internal_EvaluateVisualScriptLocal(Object.GetUnmanagedPtr(script), ref local))
if (Editor.EvaluateVisualScriptLocal(script, ref local))
{
text = $"{local.Value ?? string.Empty} ({local.ValueTypeName})";
// Check if got no value (null)
if (string.IsNullOrEmpty(local.ValueTypeName) && string.Equals(local.Value, "null", StringComparison.Ordinal))
{
var connections = box.Connections;
if (connections.Count == 0 && box.Archetype.ValueIndex >= 0 && box.ParentNode.Values != null && box.Archetype.ValueIndex < box.ParentNode.Values.Length)
{
// Special case when there is no value but the box has no connection and uses default value
var defaultValue = box.ParentNode.Values[box.Archetype.ValueIndex];
if (defaultValue != null)
{
local.Value = defaultValue.ToString();
local.ValueTypeName = defaultValue.GetType().FullName;
}
}
else if (connections.Count == 1)
{
// Special case when there is no value but the box has a connection with valid value to try to use it instead
box = connections[0];
local.NodeId = box.ParentNode.ID;
local.BoxId = box.ID;
Editor.EvaluateVisualScriptLocal(script, ref local);
}
}
text = GetBoxDebuggerTooltip(ref local);
return true;
}
}

View File

@@ -368,7 +368,7 @@ namespace FlaxEditor.Windows.Assets
actor.Layer = parentActor.Layer;
// Rename actor to identify it easily
actor.Name = Utilities.Utils.IncrementNameNumber(actor.GetType().Name, x => parentActor.GetChild(x) == null);
actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parentActor.GetChild(x) == null);
}
// Spawn it

View File

@@ -797,11 +797,12 @@ namespace FlaxEditor.Windows.Assets
}
// Check if any breakpoint was hit
for (int i = 0; i < Surface.Breakpoints.Count; i++)
var breakpoints = Surface.Breakpoints;
for (int i = 0; i < breakpoints.Count; i++)
{
if (Surface.Breakpoints[i].ID == flowInfo.NodeId)
if (breakpoints[i].ID == flowInfo.NodeId)
{
OnDebugBreakpointHit(ref flowInfo, Surface.Breakpoints[i]);
OnDebugBreakpointHit(ref flowInfo, breakpoints[i]);
break;
}
}
@@ -820,7 +821,7 @@ namespace FlaxEditor.Windows.Assets
var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag;
if (state.Locals == null)
{
state.Locals = Editor.Internal_GetVisualScriptLocals(out var _);
state.Locals = Editor.GetVisualScriptLocals();
Editor.Instance.Simulation.BreakpointHangTag = state;
}
return state;
@@ -831,7 +832,7 @@ namespace FlaxEditor.Windows.Assets
var state = (BreakpointHangState)Editor.Instance.Simulation.BreakpointHangTag;
if (state.StackFrames == null)
{
state.StackFrames = Editor.Internal_GetVisualScriptStackFrames(out var _);
state.StackFrames = Editor.GetVisualScriptStackFrames();
Editor.Instance.Simulation.BreakpointHangTag = state;
}
return state;
@@ -976,7 +977,7 @@ namespace FlaxEditor.Windows.Assets
return;
// Break on any of the output connects from the previous scope node
var frame = Editor.Internal_GetVisualScriptPreviousScopeFrame();
var frame = Editor.GetVisualScriptPreviousScopeFrame();
if (frame.Script != null)
{
if (_debugStepOutNodesIds == null)

View File

@@ -381,6 +381,7 @@ namespace FlaxEditor.Windows
Arguments = $"clone {gitPath} \"{clonePath}\"",
ShellExecute = false,
LogOutput = true,
WaitForEnd = true
};
Platform.CreateProcess(ref settings);
}
@@ -402,6 +403,7 @@ namespace FlaxEditor.Windows
Arguments = "submodule update --init",
ShellExecute = false,
LogOutput = true,
WaitForEnd = true
};
Platform.CreateProcess(ref settings);
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph;
using FlaxEngine;
@@ -146,6 +148,31 @@ namespace FlaxEditor.Windows
contextMenu.AddButton("Break Prefab Link", Editor.Prefabs.BreakLinks);
}
// Load additional scenes option
if (!hasSthSelected)
{
var allScenes = FlaxEngine.Content.GetAllAssetsByType(typeof(SceneAsset));
var loadedSceneIds = Editor.Instance.Scene.Root.ChildNodes.Select(node => node.ID).ToList();
var unloadedScenes = allScenes.Where(sceneId => !loadedSceneIds.Contains(sceneId)).ToList();
if (unloadedScenes.Count > 0)
{
contextMenu.AddSeparator();
var childCM = contextMenu.GetOrAddChildMenu("Open Scene");
foreach (var sceneGuid in unloadedScenes)
{
if (FlaxEngine.Content.GetAssetInfo(sceneGuid, out var unloadedScene))
{
var splitPath = unloadedScene.Path.Split('/');
var sceneName = splitPath[^1];
if (splitPath[^1].EndsWith(".scene"))
sceneName = sceneName[..^6];
childCM.ContextMenu.AddButton(sceneName, () => { Editor.Instance.Scene.OpenScene(sceneGuid, true); });
}
}
}
}
// Spawning actors options
contextMenu.AddSeparator();

View File

@@ -144,7 +144,10 @@ BehaviorKnowledge::~BehaviorKnowledge()
void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
{
ASSERT_LOW_LAYER(!Tree && tree);
if (Tree)
FreeMemory();
if (!tree)
return;
Tree = tree;
Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType);
RelevantNodes.Resize(tree->Graph.NodesCount, false);

View File

@@ -1070,38 +1070,26 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
else
{
const auto nodes = node->GetNodes(this);
const auto nodesA = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
const auto nodesB = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
const auto& baseNodes = _graph.BaseModel.Get()->GetNodes();
Transform t, tA, tB;
const auto basePoseNodes = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
const auto blendPoseNodes = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
const auto& refrenceNodes = _graph.BaseModel.Get()->GetNodes();
Transform t, basePoseTransform, blendPoseTransform, refrenceTransform;
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
tA = nodesA->Nodes[i];
tB = nodesB->Nodes[i];
const auto& baseNode = baseNodes[i];
basePoseTransform = basePoseNodes->Nodes[i];
blendPoseTransform = blendPoseNodes->Nodes[i];
refrenceTransform = refrenceNodes[i].LocalTransform;
t.Translation = tA.Translation + (tB.Translation - baseNode.LocalTransform.Translation) * alpha;
// base + (blend - refrence) = transform
t.Translation = basePoseTransform.Translation + (blendPoseTransform.Translation - refrenceTransform.Translation);
auto diff = Quaternion::Invert(refrenceTransform.Orientation) * blendPoseTransform.Orientation;
t.Orientation = basePoseTransform.Orientation * diff;
t.Scale = basePoseTransform.Scale + (blendPoseTransform.Scale - refrenceTransform.Scale);
//auto baseOrientation = tA.Orientation;
//Quaternion additiveOrientation = alpha * (tB.Orientation - baseNode.LocalTransform.Orientation);
//t.Orientation = baseOrientation + additiveOrientation;
auto m1 = Matrix::RotationQuaternion(tA.Orientation);
auto m2 = Matrix::RotationQuaternion(alpha * tB.Orientation);
auto m3 = Matrix::RotationQuaternion(alpha * baseNode.LocalTransform.Orientation);
Matrix m4;
Matrix::Subtract(m2, m3, m4);
Matrix m5;
Matrix::Add(m1, m4, m5);
t.SetRotation(m5);
t.Orientation.Normalize();
t.Scale = tA.Scale * tB.Scale;
//nodes->Nodes[i] = t;
Transform::Lerp(tA, t, alpha, nodes->Nodes[i]);
//lerp base and transform
Transform::Lerp(basePoseTransform, t, alpha, nodes->Nodes[i]);
}
Transform::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
Transform::Lerp(basePoseNodes->RootMotion, basePoseNodes->RootMotion + blendPoseNodes->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
}

View File

@@ -310,6 +310,10 @@ void Asset::ChangeID(const Guid& newId)
if (!IsVirtual())
CRASH;
// ID has to be unique
if (Content::GetAsset(newId) != nullptr)
CRASH;
const Guid oldId = _id;
ManagedScriptingObject::ChangeID(newId);
Content::onAssetChangeId(this, oldId, newId);
@@ -438,12 +442,15 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
// Note: to reproduce this case just include material into material (use layering).
// So during loading first material it will wait for child materials loaded calling this function
const double timeoutInSeconds = timeoutInMilliseconds * 0.001;
const double startTime = Platform::GetTimeSeconds();
Task* task = loadingTask;
Array<ContentLoadTask*, InlinedAllocation<64>> localQueue;
while (!Engine::ShouldExit())
#define CHECK_CONDITIONS() (!Engine::ShouldExit() && (timeoutInSeconds <= 0.0 || Platform::GetTimeSeconds() - startTime < timeoutInSeconds))
do
{
// Try to execute content tasks
while (task->IsQueued() && !Engine::ShouldExit())
while (task->IsQueued() && CHECK_CONDITIONS())
{
// Dequeue task from the loading queue
ContentLoadTask* tmp;
@@ -494,7 +501,8 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
break;
}
}
}
} while (CHECK_CONDITIONS());
#undef CHECK_CONDITIONS
}
else
{

View File

@@ -892,6 +892,11 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
PrintStack(LogType::Error);
break;
}
if (boxBase->ID == 1)
{
value = instance;
break;
}
// TODO: check if instance is of event type (including inheritance)
// Add Visual Script method to the event bindings table

View File

@@ -230,6 +230,7 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
// Find asset in registry
if (Cache.FindAsset(id, info))
return true;
PROFILE_CPU();
// Locking injects some stalls but we need to make it safe (only one thread can pass though it at once)
ScopeLock lock(WorkspaceDiscoveryLocker);
@@ -276,6 +277,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
// Find asset in registry
if (Cache.FindAsset(path, info))
return true;
PROFILE_CPU();
const auto extension = FileSystem::GetExtension(path).ToLower();
@@ -538,6 +540,8 @@ void Content::DeleteAsset(Asset* asset)
void Content::DeleteAsset(const StringView& path)
{
PROFILE_CPU();
// Try to delete already loaded asset
Asset* asset = GetAsset(path);
if (asset != nullptr)
@@ -566,12 +570,12 @@ void Content::DeleteAsset(const StringView& path)
void Content::deleteFileSafety(const StringView& path, const Guid& id)
{
// Check if given id is invalid
if (!id.IsValid())
{
LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path);
return;
}
PROFILE_CPU();
// Ensure that file has the same ID (prevent from deleting different assets)
auto storage = ContentStorageManager::TryGetStorage(path);
@@ -678,6 +682,7 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath)
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
{
PROFILE_CPU();
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
@@ -796,6 +801,7 @@ Asset* Content::CreateVirtualAsset(MClass* type)
Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
{
PROFILE_CPU();
auto& assetType = type.GetType();
// Init mock asset info

View File

@@ -1305,6 +1305,8 @@ FileReadStream* FlaxStorage::OpenFile()
bool FlaxStorage::CloseFileHandles()
{
PROFILE_CPU();
// Note: this is usually called by the content manager when this file is not used or on exit
// In those situations all the async tasks using this storage should be cancelled externally

View File

@@ -8,7 +8,6 @@
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/Textures/TextureUtils.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/ContentImporters/ImportIES.h"

View File

@@ -734,9 +734,7 @@ public:
}
else
{
Array tmp = MoveTemp(other);
other = *this;
*this = MoveTemp(tmp);
::Swap(other, *this);
}
}

View File

@@ -616,9 +616,7 @@ public:
}
else
{
Dictionary tmp = MoveTemp(other);
other = *this;
*this = MoveTemp(tmp);
::Swap(other, *this);
}
}

View File

@@ -304,7 +304,7 @@ template<typename T>
inline void Swap(T& a, T& b) noexcept
{
T tmp = MoveTemp(a);
a = b;
a = MoveTemp(b);
b = MoveTemp(tmp);
}

View File

@@ -104,7 +104,7 @@ bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime)
if (targetFps > ZeroTolerance)
{
int skip = (int)(1 + (time - NextBegin) / (1.0 / targetFps));
int skip = (int)(1 + (time - NextBegin) * targetFps);
NextBegin += (1.0 / targetFps) * skip;
}
}
@@ -160,7 +160,7 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime)
if (targetFps > ZeroTolerance)
{
int skip = (int)(1 + (time - NextBegin) / (1.0 / targetFps));
int skip = (int)(1 + (time - NextBegin) * targetFps);
NextBegin += (1.0 / targetFps) * skip;
}
}

View File

@@ -660,6 +660,37 @@ int PixelFormatExtensions::ComputeComponentsCount(const PixelFormat format)
}
}
int32 PixelFormatExtensions::ComputeBlockSize(PixelFormat format)
{
switch (format)
{
case PixelFormat::BC1_Typeless:
case PixelFormat::BC1_UNorm:
case PixelFormat::BC1_UNorm_sRGB:
case PixelFormat::BC2_Typeless:
case PixelFormat::BC2_UNorm:
case PixelFormat::BC2_UNorm_sRGB:
case PixelFormat::BC3_Typeless:
case PixelFormat::BC3_UNorm:
case PixelFormat::BC3_UNorm_sRGB:
case PixelFormat::BC4_Typeless:
case PixelFormat::BC4_UNorm:
case PixelFormat::BC4_SNorm:
case PixelFormat::BC5_Typeless:
case PixelFormat::BC5_UNorm:
case PixelFormat::BC5_SNorm:
case PixelFormat::BC6H_Typeless:
case PixelFormat::BC6H_Uf16:
case PixelFormat::BC6H_Sf16:
case PixelFormat::BC7_Typeless:
case PixelFormat::BC7_UNorm:
case PixelFormat::BC7_UNorm_sRGB:
return 4;
default:
return 1;
}
}
PixelFormat PixelFormatExtensions::TosRGB(const PixelFormat format)
{
switch (format)

View File

@@ -173,6 +173,13 @@ public:
/// <returns>The components count.</returns>
API_FUNCTION() static int ComputeComponentsCount(PixelFormat format);
/// <summary>
/// Computes the amount of pixels per-axis stored in the a single block of the format (eg. 4 for BC-family). Returns 1 for uncompressed formats.
/// </summary>
/// <param name="format">The <see cref="PixelFormat"/>.</param>
/// <returns>The block pixels count.</returns>
API_FUNCTION() static int32 ComputeBlockSize(PixelFormat format);
/// <summary>
/// Finds the equivalent sRGB format to the provided format.
/// </summary>

View File

@@ -13,7 +13,7 @@ struct RenderContext;
/// Custom PostFx which can modify final image by processing it with material based filters. The base class for all post process effects used by the graphics pipeline. Allows to extend frame rendering logic and apply custom effects such as outline, night vision, contrast etc.
/// </summary>
/// <remarks>
/// Override this class and implement custom post fx logic. Use <b>MainRenderTask.Instance.CustomPostFx.Add(myPostFx)</b> to attach your script to rendering or add script to camera actor.
/// Override this class and implement custom post fx logic. Use <b>MainRenderTask.Instance.AddCustomPostFx(myPostFx)</b> to attach your script to rendering or add script to camera actor.
/// </remarks>
API_CLASS(Abstract) class FLAXENGINE_API PostProcessEffect : public Script
{

View File

@@ -192,6 +192,9 @@ bool RenderBuffers::Init(int32 width, int32 height)
_viewport = Viewport(0, 0, static_cast<float>(width), static_cast<float>(height));
LastEyeAdaptationTime = 0;
// Flush any pool render targets to prevent over-allocating GPU memory when resizing game viewport
RenderTargetPool::Flush(false, 4);
return result;
}

View File

@@ -7,35 +7,31 @@
struct Entry
{
bool IsOccupied;
GPUTexture* RT;
uint64 LastFrameTaken;
uint64 LastFrameReleased;
uint32 DescriptionHash;
bool IsOccupied;
};
namespace
{
Array<Entry> TemporaryRTs(64);
Array<Entry> TemporaryRTs;
}
void RenderTargetPool::Flush(bool force)
void RenderTargetPool::Flush(bool force, int32 framesOffset)
{
const uint64 framesOffset = 3 * 60;
if (framesOffset < 0)
framesOffset = 3 * 60; // For how many frames RTs should be cached (by default)
const uint64 maxReleaseFrame = Engine::FrameCount - framesOffset;
force |= Engine::ShouldExit();
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (!tmp.IsOccupied && (force || (tmp.LastFrameReleased < maxReleaseFrame)))
const auto& e = TemporaryRTs[i];
if (!e.IsOccupied && (force || e.LastFrameReleased < maxReleaseFrame))
{
// Release
tmp.RT->DeleteObjectNow();
TemporaryRTs.RemoveAt(i);
i--;
e.RT->DeleteObjectNow();
TemporaryRTs.RemoveAt(i--);
if (TemporaryRTs.IsEmpty())
break;
}
@@ -48,19 +44,14 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc)
const uint32 descHash = GetHash(desc);
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (!tmp.IsOccupied && tmp.DescriptionHash == descHash)
auto& e = TemporaryRTs[i];
if (!e.IsOccupied && e.DescriptionHash == descHash)
{
ASSERT(tmp.RT);
// Mark as used
tmp.IsOccupied = true;
tmp.LastFrameTaken = Engine::FrameCount;
return tmp.RT;
e.IsOccupied = true;
return e.RT;
}
}
#if !BUILD_RELEASE
if (TemporaryRTs.Count() > 2000)
{
@@ -71,24 +62,23 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc)
// Create new rt
const String name = TEXT("TemporaryRT_") + StringUtils::ToString(TemporaryRTs.Count());
auto newRenderTarget = GPUDevice::Instance->CreateTexture(name);
if (newRenderTarget->Init(desc))
GPUTexture* rt = GPUDevice::Instance->CreateTexture(name);
if (rt->Init(desc))
{
Delete(newRenderTarget);
Delete(rt);
LOG(Error, "Cannot create temporary render target. Description: {0}", desc.ToString());
return nullptr;
}
// Create temporary rt entry
Entry entry;
entry.IsOccupied = true;
entry.LastFrameReleased = 0;
entry.LastFrameTaken = Engine::FrameCount;
entry.RT = newRenderTarget;
entry.DescriptionHash = descHash;
TemporaryRTs.Add(entry);
Entry e;
e.IsOccupied = true;
e.LastFrameReleased = 0;
e.RT = rt;
e.DescriptionHash = descHash;
TemporaryRTs.Add(e);
return newRenderTarget;
return rt;
}
void RenderTargetPool::Release(GPUTexture* rt)
@@ -98,14 +88,13 @@ void RenderTargetPool::Release(GPUTexture* rt)
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (tmp.RT == rt)
auto& e = TemporaryRTs[i];
if (e.RT == rt)
{
// Mark as free
ASSERT(tmp.IsOccupied);
tmp.IsOccupied = false;
tmp.LastFrameReleased = Engine::FrameCount;
ASSERT(e.IsOccupied);
e.IsOccupied = false;
e.LastFrameReleased = Engine::FrameCount;
return;
}
}

View File

@@ -15,7 +15,8 @@ public:
/// Flushes the temporary render targets.
/// </summary>
/// <param name="force">True if release unused render targets by force, otherwise will use a few frames of delay.</param>
static void Flush(bool force = false);
/// <param name="framesOffset">Amount of previous frames that should persist in the pool after flush. Resources used more than given value wil be freed. Use value of -1 to auto pick default duration.</param>
static void Flush(bool force = false, int32 framesOffset = -1);
/// <summary>
/// Gets a temporary render target.

View File

@@ -115,27 +115,20 @@ bool ShaderAssetBase::Save()
bool IsValidShaderCache(DataContainer<byte>& shaderCache, Array<String>& includes)
{
if (shaderCache.Length() == 0)
{
return false;
}
MemoryReadStream stream(shaderCache.Get(), shaderCache.Length());
// Read cache format version
int32 version;
stream.ReadInt32(&version);
if (version != GPU_SHADER_CACHE_VERSION)
{
return false;
}
// Read the location of additional data that contains list of included source files
int32 additionalDataStart;
stream.ReadInt32(&additionalDataStart);
stream.SetPosition(additionalDataStart);
bool result = true;
// Read all includes
int32 includesCount;
stream.ReadInt32(&includesCount);
@@ -144,28 +137,16 @@ bool IsValidShaderCache(DataContainer<byte>& shaderCache, Array<String>& include
{
String& include = includes.AddOne();
stream.ReadString(&include, 11);
include = ShadersCompilation::ResolveShaderPath(include);
DateTime lastEditTime;
stream.Read(lastEditTime);
// Check if included file exists locally and has been modified since last compilation
if (FileSystem::FileExists(include) && FileSystem::GetFileLastEditTime(include) > lastEditTime)
{
result = false;
}
return false;
}
#if 0
// Check duplicates
for (int32 i = 0; i < includes.Count(); i++)
{
if (includes.FindLast(includes[i]) != i)
{
CRASH;
}
}
#endif
return result;
return true;
}
#endif

View File

@@ -480,6 +480,16 @@ bool GPUTexture::Init(const GPUTextureDescription& desc)
break;
}
}
const bool isCompressed = PixelFormatExtensions::IsCompressed(desc.Format);
if (isCompressed)
{
const int32 blockSize = PixelFormatExtensions::ComputeBlockSize(desc.Format);
if (desc.Width < blockSize || desc.Height < blockSize)
{
LOG(Warning, "Cannot create texture. Invalid dimensions. Description: {0}", desc.ToString());
return true;
}
}
// Release previous data
ReleaseGPU();
@@ -487,7 +497,7 @@ bool GPUTexture::Init(const GPUTextureDescription& desc)
// Initialize
_desc = desc;
_sRGB = PixelFormatExtensions::IsSRGB(desc.Format);
_isBlockCompressed = PixelFormatExtensions::IsCompressed(desc.Format);
_isBlockCompressed = isCompressed;
if (OnInit())
{
ReleaseGPU();

View File

@@ -114,9 +114,9 @@ bool StreamingTexture::Create(const TextureHeader& header)
{
// Ensure that streaming doesn't go too low because the hardware expects the texture to be min in size of compressed texture block
int32 lastMip = header.MipLevels - 1;
while ((header.Width >> lastMip) < 4 && (header.Height >> lastMip) < 4)
while ((header.Width >> lastMip) < 4 && (header.Height >> lastMip) < 4 && lastMip > 0)
lastMip--;
_minMipCountBlockCompressed = header.MipLevels - lastMip + 1;
_minMipCountBlockCompressed = Math::Min(header.MipLevels - lastMip + 1, header.MipLevels);
}
// Request resource streaming
@@ -296,6 +296,7 @@ Task* StreamingTexture::UpdateAllocation(int32 residency)
// Setup texture
if (texture->Init(desc))
{
Streaming.Error = true;
LOG(Error, "Cannot allocate texture {0}.", ToString());
}
if (allocatedResidency != 0)

View File

@@ -223,6 +223,11 @@ void TextureBase::SetTextureGroup(int32 textureGroup)
}
}
bool TextureBase::HasStreamingError() const
{
return _texture.Streaming.Error;
}
BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch)
{
BytesContainer result;

View File

@@ -148,6 +148,11 @@ public:
/// </summary>
API_PROPERTY() void SetTextureGroup(int32 textureGroup);
/// <summary>
/// Returns true if texture streaming failed (eg. pixel format is unsupported or texture data cannot be uploaded to GPU due to memory limit).
/// </summary>
API_PROPERTY() bool HasStreamingError() const;
public:
/// <summary>
/// Gets the mip data.

View File

@@ -1,61 +0,0 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/BaseTypes.h"
#include "Types.h"
/// <summary>
/// Texture utilities class
/// </summary>
class TextureUtils
{
public:
static PixelFormat ToPixelFormat(const TextureFormatType format, int32 width, int32 height, bool canCompress)
{
const bool canUseBlockCompression = width % 4 == 0 && height % 4 == 0;
if (canCompress && canUseBlockCompression)
{
switch (format)
{
case TextureFormatType::ColorRGB:
return PixelFormat::BC1_UNorm;
case TextureFormatType::ColorRGBA:
return PixelFormat::BC3_UNorm;
case TextureFormatType::NormalMap:
return PixelFormat::BC5_UNorm;
case TextureFormatType::GrayScale:
return PixelFormat::BC4_UNorm;
case TextureFormatType::HdrRGBA:
return PixelFormat::BC7_UNorm;
case TextureFormatType::HdrRGB:
#if PLATFORM_LINUX
// TODO: support BC6H compression for Linux Editor
return PixelFormat::BC7_UNorm;
#else
return PixelFormat::BC6H_Uf16;
#endif
default:
return PixelFormat::Unknown;
}
}
switch (format)
{
case TextureFormatType::ColorRGB:
return PixelFormat::R8G8B8A8_UNorm;
case TextureFormatType::ColorRGBA:
return PixelFormat::R8G8B8A8_UNorm;
case TextureFormatType::NormalMap:
return PixelFormat::R16G16_UNorm;
case TextureFormatType::GrayScale:
return PixelFormat::R8_UNorm;
case TextureFormatType::HdrRGBA:
return PixelFormat::R16G16B16A16_Float;
case TextureFormatType::HdrRGB:
return PixelFormat::R11G11B10_Float;
default:
return PixelFormat::Unknown;
}
}
};

View File

@@ -206,10 +206,18 @@ void Actor::OnDeleteObject()
#endif
for (int32 i = 0; i < Scripts.Count(); i++)
{
auto e = Scripts[i];
ASSERT(e->_parent == this);
e->_parent = nullptr;
e->DeleteObject();
auto script = Scripts[i];
ASSERT(script->_parent == this);
if (script->_wasAwakeCalled)
{
script->_wasAwakeCalled = false;
CHECK_EXECUTE_IN_EDITOR
{
script->OnDestroy();
}
}
script->_parent = nullptr;
script->DeleteObject();
}
#if BUILD_DEBUG
ASSERT(callsCheck == Scripts.Count());
@@ -431,6 +439,7 @@ Array<Actor*> Actor::GetChildren(const MClass* type) const
void Actor::DestroyChildren(float timeLeft)
{
PROFILE_CPU();
Array<Actor*> children = Children;
const bool useGameTime = timeLeft > ZeroTolerance;
for (Actor* child : children)
@@ -889,9 +898,13 @@ void Actor::EndPlay()
for (auto* script : Scripts)
{
CHECK_EXECUTE_IN_EDITOR
if (script->_wasAwakeCalled)
{
script->OnDestroy();
script->_wasAwakeCalled = false;
CHECK_EXECUTE_IN_EDITOR
{
script->OnDestroy();
}
}
}
@@ -1034,7 +1047,10 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
}
else if (!parent && parentId.IsValid())
{
LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
if (_prefabObjectID.IsValid())
LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID);
else
LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
}
}
}

View File

@@ -439,7 +439,7 @@ public:
bool Do() const override
{
auto scene = Scripting::FindObject<Scene>(TargetScene);
auto scene = Level::FindScene(TargetScene);
if (!scene)
return true;
return unloadScene(scene);
@@ -934,13 +934,13 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
// Loaded scene objects list
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
const int32 objectsCount = (int32)data.Size();
sceneObjects->Resize(objectsCount);
const int32 dataCount = (int32)data.Size();
sceneObjects->Resize(dataCount);
sceneObjects->At(0) = scene;
// Spawn all scene objects
SceneObjectsFactory::Context context(modifier.Value);
context.Async = JobSystem::GetThreadsCount() > 1 && objectsCount > 10;
context.Async = JobSystem::GetThreadsCount() > 1 && dataCount > 10;
{
PROFILE_CPU_NAMED("Spawn");
SceneObject** objects = sceneObjects->Get();
@@ -963,12 +963,12 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
}
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}, objectsCount - 1);
}, dataCount - 1);
ScenesLock.Lock();
}
else
{
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene
{
auto& stream = data[i];
auto obj = SceneObjectsFactory::Spawn(context, stream);
@@ -1012,13 +1012,13 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
SceneObjectsFactory::Deserialize(context, obj, data[i]);
idMapping = nullptr;
}
}, objectsCount - 1);
}, dataCount - 1);
ScenesLock.Lock();
}
else
{
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
for (int32 i = 1; i < objectsCount; i++) // start from 1. at index [0] was scene
for (int32 i = 1; i < dataCount; i++) // start from 1. at index [0] was scene
{
auto& objData = data[i];
auto obj = objects[i];
@@ -1049,7 +1049,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
PROFILE_CPU_NAMED("Initialize");
SceneObject** objects = sceneObjects->Get();
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount; i++)
{
SceneObject* obj = objects[i];
if (obj)

View File

@@ -1230,14 +1230,14 @@ bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData)
{
ScopeLock lock(Locker);
_isCreatingDefaultInstance = true;
_defaultInstance = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, &ObjectsCache, true);
_defaultInstance = PrefabManager::SpawnPrefab(this, nullptr, &ObjectsCache, true);
_isCreatingDefaultInstance = false;
}
// Instantiate prefab instance from prefab (default spawning logic)
// Note: it will get any added or removed objects from the nested prefabs
// TODO: try to optimize by using recreated default instance to ApplyAllInternal (will need special path there if apply is done with default instance to unlink it instead of destroying)
const auto targetActor = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, nullptr, true);
const auto targetActor = PrefabManager::SpawnPrefab(this, nullptr, nullptr, true);
if (targetActor == nullptr)
{
LOG(Warning, "Failed to instantiate default prefab instance from changes synchronization.");

View File

@@ -76,7 +76,7 @@ Actor* Prefab::GetDefaultInstance()
_isCreatingDefaultInstance = true;
// Instantiate objects from prefab (default spawning logic)
_defaultInstance = PrefabManager::SpawnPrefab(this, Transform::Identity, nullptr, &ObjectsCache);
_defaultInstance = PrefabManager::SpawnPrefab(this, nullptr, &ObjectsCache);
_isCreatingDefaultInstance = false;
return _defaultInstance;
@@ -87,17 +87,12 @@ SceneObject* Prefab::GetDefaultInstance(const Guid& objectId)
const auto result = GetDefaultInstance();
if (!result)
return nullptr;
if (objectId.IsValid())
{
SceneObject* object;
if (ObjectsCache.TryGet(objectId, object))
{
// Actor or Script
return object;
}
}
return result;
}

View File

@@ -94,8 +94,8 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
LOG(Warning, "Waiting for prefab asset be loaded failed. {0}", prefab->ToString());
return nullptr;
}
const int32 objectsCount = prefab->ObjectsCount;
if (objectsCount == 0)
const int32 dataCount = prefab->ObjectsCount;
if (dataCount == 0)
{
LOG(Warning, "Prefab has no objects. {0}", prefab->ToString());
return nullptr;
@@ -107,7 +107,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
// Prepare
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
sceneObjects->Resize(dataCount);
CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback>::ScopeCache modifier = Cache::ISerializeModifier.Get();
modifier->EngineBuild = prefab->DataEngineBuild;
modifier->IdsMapping.EnsureCapacity(prefab->ObjectsIds.Count() * 4);
@@ -126,7 +126,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
// Deserialize prefab objects
auto prevIdMapping = Scripting::ObjectsLookupIdMapping.Get();
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount; i++)
{
auto& stream = data[i];
SceneObject* obj = SceneObjectsFactory::Spawn(context, stream);
@@ -145,7 +145,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
SceneObjectsFactory::SynchronizeNewPrefabInstances(context, prefabSyncData);
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
}
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount; i++)
{
auto& stream = data[i];
SceneObject* obj = sceneObjects->At(i);
@@ -154,28 +154,6 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
}
Scripting::ObjectsLookupIdMapping.Set(prevIdMapping);
// Pick prefab root object
if (sceneObjects->IsEmpty())
{
LOG(Warning, "No valid objects in prefab.");
return nullptr;
}
Actor* root = nullptr;
const Guid prefabRootObjectId = prefab->GetRootObjectId();
for (int32 i = 0; i < objectsCount; i++)
{
if (JsonTools::GetGuid(data[i], "ID") == prefabRootObjectId)
{
root = dynamic_cast<Actor*>(sceneObjects->At(i));
break;
}
}
if (!root)
{
LOG(Warning, "Missing prefab root object.");
return nullptr;
}
// Synchronize prefab instances (prefab may have new objects added or some removed so deserialized instances need to synchronize with it)
if (withSynchronization)
{
@@ -183,6 +161,30 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
}
// Pick prefab root object
Actor* root = nullptr;
const Guid prefabRootObjectId = prefab->GetRootObjectId();
for (int32 i = 0; i < dataCount && !root; i++)
{
if (JsonTools::GetGuid(data[i], "ID") == prefabRootObjectId)
root = dynamic_cast<Actor*>(sceneObjects->At(i));
}
if (!root)
{
// Fallback to the first actor that has no parent
for (int32 i = 0; i < sceneObjects->Count() && !root; i++)
{
SceneObject* obj = sceneObjects->At(i);
if (obj && !obj->GetParent())
root = dynamic_cast<Actor*>(obj);
}
}
if (!root)
{
LOG(Warning, "Missing prefab root object. {0}", prefab->ToString());
return nullptr;
}
// Prepare parent linkage for prefab root actor
if (root->_parent)
root->_parent->Children.Remove(root);
@@ -264,7 +266,7 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Ac
}
// Link objects to prefab (only deserialized from prefab data)
for (int32 i = 0; i < objectsCount; i++)
for (int32 i = 0; i < dataCount; i++)
{
auto& stream = data[i];
SceneObject* obj = sceneObjects->At(i);

View File

@@ -89,7 +89,7 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager
/// <param name="objectsCache">The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script).</param>
/// <param name="withSynchronization">True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications.</param>
/// <returns>The created actor (root) or null if failed.</returns>
static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = false);
static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = true);
/// <summary>
/// Spawns the instance of the prefab objects. If parent actor is specified then created actors are fully initialized (OnLoad event and BeginPlay is called if parent actor is already during gameplay).
@@ -100,7 +100,7 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager
/// <param name="objectsCache">The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script).</param>
/// <param name="withSynchronization">True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications.</param>
/// <returns>The created actor (root) or null if failed.</returns>
static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = false);
static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = true);
#if USE_EDITOR

View File

@@ -138,7 +138,7 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
LOG(Warning, "Missing prefab with id={0}.", prefabId);
LOG(Warning, "Missing prefab {0}.", prefabId);
return nullptr;
}
if (prefab->WaitForLoaded())
@@ -264,7 +264,7 @@ void SceneObjectsFactory::Deserialize(Context& context, SceneObject* obj, ISeria
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
LOG(Warning, "Missing prefab with id={0}.", prefabId);
LOG(Warning, "Missing prefab {0}.", prefabId);
return;
}
if (prefab->WaitForLoaded())
@@ -317,11 +317,13 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
{
#if USE_EDITOR
// Add dummy script
auto* dummyScript = parent->AddScript<MissingScript>();
const auto typeNameMember = value.FindMember("TypeName");
if (typeNameMember != value.MemberEnd() && typeNameMember->value.IsString())
{
auto* dummyScript = parent->AddScript<MissingScript>();
dummyScript->MissingTypeName = typeNameMember->value.GetString();
dummyScript->Data = MoveTemp(bufferStr);
dummyScript->Data = MoveTemp(bufferStr);
}
#endif
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());
}
@@ -424,9 +426,6 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn
ASSERT(count <= data.SceneObjects.Count());
for (int32 i = 0; i < count; i++)
{
const SceneObject* obj = data.SceneObjects[i];
if (!obj)
continue;
const auto& stream = data.Data[i];
Guid prefabObjectId, prefabId;
if (!JsonTools::GetGuidIfValid(prefabObjectId, stream, "PrefabObjectID"))
@@ -436,15 +435,18 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn
Guid parentId = JsonTools::GetGuid(stream, "ParentID");
for (int32 j = i - 1; j >= 0; j--)
{
// Find instance ID of the parent to this object (use data in json for relationship)
// Find ID of the parent to this object (use data in json for relationship)
if (parentId == JsonTools::GetGuid(data.Data[j], "ID") && data.SceneObjects[j])
{
parentId = data.SceneObjects[j]->GetID();
break;
}
}
const Guid id = obj->GetID();
const SceneObject* obj = data.SceneObjects[i];
const Guid id = obj ? obj->GetID() : JsonTools::GetGuid(stream, "ID");
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (!prefab)
continue;
// Check if it's parent is in the same prefab
int32 index;
@@ -459,6 +461,8 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn
auto& e = context.Instances.AddOne();
e.Prefab = prefab;
e.RootId = id;
e.RootIndex = i;
e.StatIndex = i;
}
context.ObjectToInstance[id] = index;
@@ -490,6 +494,85 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
data.InitialCount = data.SceneObjects.Count();
// Recreate any missing prefab root objects that were deleted (eg. spawned prefab got its root changed and deleted so old prefab instance needs to respawn it)
for (int32 instanceIndex = 0; instanceIndex < context.Instances.Count(); instanceIndex++)
{
PrefabInstance& instance = context.Instances[instanceIndex];
SceneObject* root = data.SceneObjects[instance.RootIndex];
if (!root && instance.Prefab)
{
instance.FixRootParent = true;
// Check if current prefab root existed in the deserialized data
const auto& oldRootData = data.Data[instance.RootIndex];
const Guid oldRootId = JsonTools::GetGuid(oldRootData, "ID");
const Guid prefabObjectId = JsonTools::GetGuid(oldRootData, "PrefabObjectID");
const Guid prefabRootId = instance.Prefab->GetRootObjectId();
Guid id;
int32 idInstance = -1;
bool syncNewRoot = false;
if (instance.IdsMapping.TryGet(prefabRootId, id) && context.ObjectToInstance.TryGet(id, idInstance) && idInstance == instanceIndex)
{
// Update the missing root with the valid object from this prefab instance
LOG(Warning, "Changed prefab instance root from ID={0}, PrefabObjectID={1} to ID={2}, PrefabObjectID={3} ({4})", instance.RootId, prefabObjectId, id, prefabRootId, instance.Prefab->ToString());
}
else
{
LOG(Warning, "Missing prefab instance root (ID={0}, PrefabObjectID={1}, {2})", instance.RootId, prefabObjectId, instance.Prefab->ToString());
// Get prefab object data from the prefab
const ISerializable::DeserializeStream* prefabData;
if (!instance.Prefab->ObjectsDataCache.TryGet(prefabRootId, prefabData))
{
LOG(Warning, "Missing object {1} data in prefab {0}.", instance.Prefab->ToString(), prefabObjectId);
continue;
}
// Map prefab object ID to the new prefab object instance
id = Guid::New();
data.Modifier->IdsMapping[prefabRootId] = id;
// Create prefab instance (recursive prefab loading to support nested prefabs)
root = Spawn(context, *prefabData);
if (!root)
{
LOG(Warning, "Failed to create object {1} from prefab {0}.", instance.Prefab->ToString(), prefabRootId);
continue;
}
// Register object
root->RegisterObject();
data.SceneObjects.Add(root);
auto& newObj = data.NewObjects.AddOne();
newObj.Prefab = instance.Prefab;
newObj.PrefabData = prefabData;
newObj.PrefabObjectId = prefabRootId;
newObj.Id = id;
context.ObjectToInstance[id] = instanceIndex;
syncNewRoot = true;
}
// Update prefab root info
instance.RootId = id;
instance.RootIndex = data.SceneObjects.Find(Scripting::FindObject<SceneObject>(id));
CHECK(instance.RootIndex != -1);
// Remap removed prefab root into the current root (can be different type but is needed for proper hierarchy linkage)
instance.IdsMapping[prefabObjectId] = id;
instance.IdsMapping[prefabRootId] = id;
instance.IdsMapping[oldRootId] = id;
data.Modifier->IdsMapping[prefabObjectId] = id;
data.Modifier->IdsMapping[prefabRootId] = id;
data.Modifier->IdsMapping[oldRootId] = id;
// Add any sub-objects that are missing (in case new root was created)
if (syncNewRoot)
{
SynchronizeNewPrefabInstances(context, data, instance.Prefab, (Actor*)root, prefabRootId, instance.RootIndex, oldRootData);
}
}
}
// Check all actors with prefab linkage for adding missing objects
for (int32 i = 0; i < data.InitialCount; i++)
{
@@ -504,13 +587,12 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
continue;
if (!JsonTools::GetGuidIfValid(actorId, stream, "ID"))
continue;
const Guid actorParentId = JsonTools::GetGuid(stream, "ParentID");
// Load prefab
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
LOG(Warning, "Missing prefab with id={0}.", prefabId);
LOG(Warning, "Missing prefab {0}.", prefabId);
continue;
}
if (prefab->WaitForLoaded())
@@ -519,66 +601,7 @@ void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, Prefab
continue;
}
// Check for RemovedObjects list
const auto removedObjects = SERIALIZE_FIND_MEMBER(stream, "RemovedObjects");
// Check if the given actor has new children or scripts added (inside the prefab that it uses)
// TODO: consider caching prefab objects structure maybe to boost this logic?
for (auto it = prefab->ObjectsDataCache.Begin(); it.IsNotEnd(); ++it)
{
// Use only objects that are linked to the current actor
const Guid parentId = JsonTools::GetGuid(*it->Value, "ParentID");
if (parentId != actorPrefabObjectId)
continue;
// Skip if object was marked to be removed per instance
const Guid prefabObjectId = JsonTools::GetGuid(*it->Value, "ID");
if (removedObjects != stream.MemberEnd())
{
auto& list = removedObjects->value;
const int32 size = static_cast<int32>(list.Size());
bool removed = false;
for (int32 j = 0; j < size; j++)
{
if (JsonTools::GetGuid(list[j]) == prefabObjectId)
{
removed = true;
break;
}
}
if (removed)
continue;
}
// Use only objects that are missing
bool spawned = false;
for (int32 j = i + 1; j < data.InitialCount; j++)
{
const auto& jData = data.Data[j];
const Guid jParentId = JsonTools::GetGuid(jData, "ParentID");
//if (jParentId == actorParentId)
// break;
//if (jParentId != actorId)
// continue;
const Guid jPrefabObjectId = JsonTools::GetGuid(jData, "PrefabObjectID");
if (jPrefabObjectId != prefabObjectId)
continue;
// This object exists in the saved scene objects list
spawned = true;
break;
}
if (spawned)
continue;
// Map prefab object id to this actor's prefab instance so the new objects gets added to it
context.SetupIdsMapping(actor, data.Modifier);
data.Modifier->IdsMapping[actorPrefabObjectId] = actor->GetID();
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
// Create instance (including all children)
SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId);
}
SynchronizeNewPrefabInstances(context, data, prefab, actor, actorPrefabObjectId, i, stream);
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
@@ -605,7 +628,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
auto prefab = Content::LoadAsync<Prefab>(prefabId);
if (prefab == nullptr)
{
LOG(Warning, "Missing prefab with id={0}.", prefabId);
LOG(Warning, "Missing prefab {0}.", prefabId);
continue;
}
if (prefab->WaitForLoaded())
@@ -626,7 +649,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
// Invalid connection object found!
LOG(Info, "Object {0} has invalid parent object {4} -> {5} (PrefabObjectID: {1}, PrefabID: {2}, Path: {3})", obj->GetSceneObjectId(), prefabObjectId, prefab->GetID(), prefab->GetPath(), parentPrefabObjectId, actualParentPrefabId);
// Map actual prefab object id to the current scene objects collection
// Map actual prefab object ID to the current scene objects collection
context.SetupIdsMapping(obj, data.Modifier);
data.Modifier->IdsMapping.TryGet(actualParentPrefabId, actualParentPrefabId);
@@ -646,15 +669,12 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
if (i != 0)
{
const auto defaultInstance = prefab->GetDefaultInstance(obj->GetPrefabObjectID());
if (defaultInstance)
{
obj->SetOrderInParent(defaultInstance->GetOrderInParent());
}
const int32 order = defaultInstance ? defaultInstance->GetOrderInParent() : -1;
if (order != -1)
obj->SetOrderInParent(order);
}
}
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
// Synchronize new prefab objects
for (int32 i = 0; i < data.NewObjects.Count(); i++)
{
@@ -662,20 +682,97 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
auto& newObj = data.NewObjects[i];
// Deserialize object with prefab data
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
Deserialize(context, obj, *(ISerializable::DeserializeStream*)newObj.PrefabData);
obj->LinkPrefab(newObj.Prefab->GetID(), newObj.PrefabObjectId);
// Preserve order in parent (values from prefab are used)
const auto defaultInstance = newObj.Prefab->GetDefaultInstance(newObj.PrefabObjectId);
if (defaultInstance)
const int32 order = defaultInstance ? defaultInstance->GetOrderInParent() : -1;
if (order != -1)
obj->SetOrderInParent(order);
}
// Setup hierarchy for the prefab instances (ensure any new objects are connected)
for (const auto& instance : context.Instances)
{
const auto& prefabStartData = data.Data[instance.StatIndex];
Guid prefabStartParentId;
if (instance.FixRootParent && JsonTools::GetGuidIfValid(prefabStartParentId, prefabStartData, "ParentID"))
{
obj->SetOrderInParent(defaultInstance->GetOrderInParent());
auto* root = data.SceneObjects[instance.RootIndex];
const auto rootParent = Scripting::FindObject<Actor>(prefabStartParentId);
root->SetParent(rootParent, false);
}
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
}
void SceneObjectsFactory::SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream)
{
// Check for RemovedObjects list
const auto removedObjects = SERIALIZE_FIND_MEMBER(stream, "RemovedObjects");
// Check if the given actor has new children or scripts added (inside the prefab that it uses)
// TODO: consider caching prefab objects structure maybe to boost this logic?
for (auto it = prefab->ObjectsDataCache.Begin(); it.IsNotEnd(); ++it)
{
// Use only objects that are linked to the current actor
const Guid parentId = JsonTools::GetGuid(*it->Value, "ParentID");
if (parentId != actorPrefabObjectId)
continue;
// Skip if object was marked to be removed per instance
const Guid prefabObjectId = JsonTools::GetGuid(*it->Value, "ID");
if (removedObjects != stream.MemberEnd())
{
auto& list = removedObjects->value;
const int32 size = static_cast<int32>(list.Size());
bool removed = false;
for (int32 j = 0; j < size; j++)
{
if (JsonTools::GetGuid(list[j]) == prefabObjectId)
{
removed = true;
break;
}
}
if (removed)
continue;
}
// Use only objects that are missing
bool spawned = false;
int32 childSearchStart = i + 1; // Objects are serialized with parent followed by its children
int32 instanceIndex = -1;
if (context.ObjectToInstance.TryGet(actor->GetID(), instanceIndex) && context.Instances[instanceIndex].Prefab == prefab)
{
// Start searching from the beginning of that prefab instance (eg. in case prefab objects were reordered)
childSearchStart = Math::Min(childSearchStart, context.Instances[instanceIndex].StatIndex);
}
for (int32 j = childSearchStart; j < data.InitialCount; j++)
{
if (JsonTools::GetGuid(data.Data[j], "PrefabObjectID") == prefabObjectId)
{
// This object exists in the saved scene objects list
spawned = true;
break;
}
}
if (spawned)
continue;
// Map prefab object ID to this actor's prefab instance so the new objects gets added to it
context.SetupIdsMapping(actor, data.Modifier);
data.Modifier->IdsMapping[actorPrefabObjectId] = actor->GetID();
Scripting::ObjectsLookupIdMapping.Set(&data.Modifier->IdsMapping);
// Create instance (including all children)
SynchronizeNewPrefabInstance(context, data, prefab, actor, prefabObjectId);
}
}
void SceneObjectsFactory::SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId)
{
PROFILE_CPU_NAMED("SynchronizeNewPrefabInstance");

View File

@@ -15,8 +15,11 @@ class FLAXENGINE_API SceneObjectsFactory
public:
struct PrefabInstance
{
int32 StatIndex;
int32 RootIndex;
Guid RootId;
Prefab* Prefab;
bool FixRootParent = false;
Dictionary<Guid, Guid> IdsMapping;
};
@@ -70,6 +73,8 @@ public:
struct PrefabSyncData
{
friend SceneObjectsFactory;
friend class PrefabManager;
// The created scene objects. Collection can be modified (eg. for spawning missing objects).
Array<SceneObject*>& SceneObjects;
// The scene objects data.
@@ -124,5 +129,6 @@ public:
static void SynchronizePrefabInstances(Context& context, PrefabSyncData& data);
private:
static void SynchronizeNewPrefabInstances(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& actorPrefabObjectId, int32 i, const ISerializable::DeserializeStream& stream);
static void SynchronizeNewPrefabInstance(Context& context, PrefabSyncData& data, Prefab* prefab, Actor* actor, const Guid& prefabObjectId);
};

View File

@@ -45,7 +45,7 @@ namespace FlaxEngine
/// <returns>True if both values are not equal, otherwise false.</returns>
public static bool operator !=(Tag left, Tag right)
{
return left.Index == right.Index;
return left.Index != right.Index;
}
/// <summary>
@@ -213,11 +213,11 @@ namespace FlaxEngine
}
/// <summary>
/// Checks if the list of tags contains all of the given tags (including parent tags check). For example, HasAll({"A.B", "C"}, {"A", "C"}) returns true, for exact check use HasAllExact.
/// Checks if the list of tags contains all the given tags (including parent tags check). For example, HasAll({"A.B", "C"}, {"A", "C"}) returns true, for exact check use HasAllExact.
/// </summary>
/// <param name="list">The tags list to use.</param>
/// <param name="tags">The tags to check.</param>
/// <returns>True if all of the given tags are contained by the list of tags. Returns true for empty list.</returns>
/// <returns>True if all the given tags are contained by the list of tags. Returns true for empty list.</returns>
public static bool HasAll(this Tag[] list, Tag[] tags)
{
if (tags == null || tags.Length == 0)
@@ -233,11 +233,11 @@ namespace FlaxEngine
}
/// <summary>
/// Checks if the list of tags contains all of the given tags (exact match). For example, HasAllExact({"A.B", "C"}, {"A", "C"}) returns false, for parents check use HasAll.
/// Checks if the list of tags contains all the given tags (exact match). For example, HasAllExact({"A.B", "C"}, {"A", "C"}) returns false, for parents check use HasAll.
/// </summary>
/// <param name="list">The tags list to use.</param>
/// <param name="tags">The tags to check.</param>
/// <returns>True if all of the given tags are contained by the list of tags. Returns true for empty list.</returns>
/// <returns>True if all the given tags are contained by the list of tags. Returns true for empty list.</returns>
public static bool HasAllExact(this Tag[] list, Tag[] tags)
{
if (tags == null || tags.Length == 0)

View File

@@ -36,7 +36,7 @@ NavMeshRuntime::NavMeshRuntime(const NavMeshProperties& properties)
NavMeshRuntime::~NavMeshRuntime()
{
dtFreeNavMesh(_navMesh);
Dispose();
dtFreeNavMeshQuery(_navMeshQuery);
}
@@ -336,9 +336,7 @@ void NavMeshRuntime::SetTileSize(float tileSize)
// Dispose the existing mesh (its invalid)
if (_navMesh)
{
dtFreeNavMesh(_navMesh);
_navMesh = nullptr;
_tiles.Clear();
Dispose();
}
_tileSize = tileSize;
@@ -363,14 +361,9 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount)
// Ensure to have size assigned
ASSERT(_tileSize != 0);
// Fre previous data (if any)
if (_navMesh)
{
dtFreeNavMesh(_navMesh);
}
// Allocate new navmesh
_navMesh = dtAllocNavMesh();
// Allocate navmesh and initialize the default query
if (!_navMesh)
_navMesh = dtAllocNavMesh();
if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES)))
{
LOG(Error, "Failed to initialize navmesh {0}.", Properties.Name);
@@ -517,7 +510,7 @@ void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer)
}
}
void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData)
void NavMeshRuntime::RemoveTiles(bool (*prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData)
{
ScopeLock lock(Locker);
ASSERT(prediction);

View File

@@ -817,7 +817,7 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString());
return;
}
prefabInstance = PrefabManager::SpawnPrefab(prefab, Transform::Identity, nullptr, nullptr);
prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr);
if (!prefabInstance)
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString());

View File

@@ -2,6 +2,7 @@
#include "BoxCollider.h"
#include "Engine/Physics/PhysicsBackend.h"
#include "Engine/Level/Scene/Scene.h"
BoxCollider::BoxCollider(const SpawnParams& params)
: Collider(params)
@@ -19,6 +20,32 @@ void BoxCollider::SetSize(const Float3& value)
UpdateBounds();
}
void BoxCollider::AutoResize()
{
Actor* parent = GetParent();
if (Cast<Scene>(parent))
return;
// Get bounds of all siblings (excluding itself)
const Vector3 parentScale = parent->GetScale();
if (parentScale.IsAnyZero())
return; // Avoid division by zero
BoundingBox parentBox = parent->GetBox();
for (const Actor* sibling : parent->Children)
{
if (sibling != this)
BoundingBox::Merge(parentBox, sibling->GetBoxWithChildren(), parentBox);
}
const Vector3 parentSize = parentBox.GetSize();
const Vector3 parentCenter = parentBox.GetCenter() - parent->GetPosition();
// Update bounds
SetLocalPosition(Vector3::Zero);
SetSize(parentSize / parentScale);
SetCenter(parentCenter / parentScale);
SetOrientation(GetOrientation() * Quaternion::Invert(GetOrientation()));
}
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"

View File

@@ -43,6 +43,11 @@ public:
return _bounds;
}
/// <summary>
/// Resizes the collider based on the bounds of it's parent to contain it whole (including any siblings).
/// </summary>
API_FUNCTION() void AutoResize();
public:
// [Collider]
#if USE_EDITOR

View File

@@ -17,6 +17,37 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API
DECLARE_SCRIPTING_TYPE_MINIMAL(AndroidPlatformSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// Android screen orientation options.
/// </summary>
API_ENUM() enum class FLAXENGINE_API ScreenOrientation
{
/// <summary>
/// "portrait" mode
/// </summary>
Portrait,
/// <summary>
/// "reversePortrait" mode
/// </summary>
PortraitReverse,
/// <summary>
/// "landscape" mode
/// </summary>
LandscapeRight,
/// <summary>
/// "reverseLandscape" mode
/// </summary>
LandscapeLeft,
/// <summary>
/// "fullSensor" mode
/// </summary>
AutoRotation,
};
/// <summary>
/// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}.
/// </summary>
@@ -29,6 +60,12 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API
API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"General\")")
Array<String> Permissions;
/// <summary>
/// The default screen orientation.
/// </summary>
API_FIELD(Attributes = "EditorOrder(110), EditorDisplay(\"General\")")
ScreenOrientation DefaultOrientation = ScreenOrientation::AutoRotation;
/// <summary>
/// Custom icon texture to use for the application (overrides the default one).
/// </summary>

View File

@@ -94,6 +94,7 @@ X11::XcursorImage* CursorsImg[(int32)CursorType::MAX];
Dictionary<StringAnsi, X11::KeyCode> KeyNameMap;
Array<KeyboardKeys> KeyCodeMap;
Delegate<void*> LinuxPlatform::xEventRecieved;
Window* MouseTrackingWindow = nullptr;
// Message boxes configuration
#define LINUX_DIALOG_MIN_BUTTON_WIDTH 64
@@ -1916,7 +1917,6 @@ bool LinuxPlatform::Init()
{
if (PlatformBase::Init())
return true;
char fileNameBuffer[1024];
// Init timing
@@ -2398,7 +2398,7 @@ void LinuxPlatform::Tick()
// Update input context focus
X11::XSetICFocus(IC);
window = WindowsManager::GetByNativePtr((void*)event.xfocus.window);
if (window)
if (window && MouseTrackingWindow == nullptr)
{
window->OnGotFocus();
}
@@ -2407,7 +2407,7 @@ void LinuxPlatform::Tick()
// Update input context focus
X11::XUnsetICFocus(IC);
window = WindowsManager::GetByNativePtr((void*)event.xfocus.window);
if (window)
if (window && MouseTrackingWindow == nullptr)
{
window->OnLostFocus();
}
@@ -2514,23 +2514,32 @@ void LinuxPlatform::Tick()
break;
case ButtonPress:
window = WindowsManager::GetByNativePtr((void*)event.xbutton.window);
if (window)
if (MouseTrackingWindow)
MouseTrackingWindow->OnButtonPress(&event.xbutton);
else if (window)
window->OnButtonPress(&event.xbutton);
break;
case ButtonRelease:
window = WindowsManager::GetByNativePtr((void*)event.xbutton.window);
if (window)
if (MouseTrackingWindow)
MouseTrackingWindow->OnButtonRelease(&event.xbutton);
else if (window)
window->OnButtonRelease(&event.xbutton);
break;
case MotionNotify:
window = WindowsManager::GetByNativePtr((void*)event.xmotion.window);
if (window)
if (MouseTrackingWindow)
MouseTrackingWindow->OnMotionNotify(&event.xmotion);
else if (window)
window->OnMotionNotify(&event.xmotion);
break;
case EnterNotify:
// nothing?
break;
case LeaveNotify:
window = WindowsManager::GetByNativePtr((void*)event.xcrossing.window);
if (MouseTrackingWindow)
MouseTrackingWindow->OnLeaveNotify(&event.xcrossing);
if (window)
window->OnLeaveNotify(&event.xcrossing);
break;

View File

@@ -40,6 +40,7 @@ extern X11::Atom xAtomWmName;
extern Dictionary<StringAnsi, X11::KeyCode> KeyNameMap;
extern Array<KeyboardKeys> KeyCodeMap;
extern X11::Cursor Cursors[(int32)CursorType::MAX];
extern Window* MouseTrackingWindow;
static constexpr uint32 MouseDoubleClickTime = 500;
static constexpr uint32 MaxDoubleClickDistanceSquared = 10;
@@ -54,7 +55,7 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
return;
auto screen = XDefaultScreen(display);
// Cache data
// Cache data
int32 width = Math::TruncToInt(settings.Size.X);
int32 height = Math::TruncToInt(settings.Size.Y);
_clientSize = Float2((float)width, (float)height);
@@ -111,6 +112,12 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
windowAttributes.border_pixel = XBlackPixel(display, screen);
windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
if (!settings.IsRegularWindow)
{
windowAttributes.save_under = true;
windowAttributes.override_redirect = true;
}
// TODO: implement all window settings
/*
bool Fullscreen;
@@ -118,11 +125,16 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
bool AllowMaximize;
*/
unsigned long valueMask = CWBackPixel | CWBorderPixel | CWEventMask | CWColormap;
if (!settings.IsRegularWindow)
{
valueMask |= CWOverrideRedirect | CWSaveUnder;
}
const X11::Window window = X11::XCreateWindow(
display, X11::XRootWindow(display, screen), x, y,
width, height, 0, visualInfo->depth, InputOutput,
visualInfo->visual,
CWBackPixel | CWBorderPixel | CWEventMask | CWColormap, &windowAttributes);
valueMask, &windowAttributes);
_window = window;
LinuxWindow::SetTitle(settings.Title);
@@ -811,12 +823,13 @@ void LinuxWindow::SetTitle(const StringView& title)
void LinuxWindow::StartTrackingMouse(bool useMouseScreenOffset)
{
// TODO: impl this
MouseTrackingWindow = this;
}
void LinuxWindow::EndTrackingMouse()
{
// TODO: impl this
if (MouseTrackingWindow == this)
MouseTrackingWindow = nullptr;
}
void LinuxWindow::SetCursor(CursorType type)

View File

@@ -1235,17 +1235,17 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
const bool withInterfaces = !mMethod->IsStatic() && mMethod->GetParentClass()->IsInterface();
if (!mMethod->IsStatic())
{
// Box instance into C# object
// Box instance into C# object (and validate the type)
MObject* instanceObject = MUtils::BoxVariant(instance);
const MClass* instanceObjectClass = MCore::Object::GetClass(instanceObject);
// Validate instance
if (!instanceObject || !instanceObjectClass->IsSubClassOf(mMethod->GetParentClass(), withInterfaces))
if (!instanceObject)
{
if (!instanceObject)
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
else
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid object instance of type '{3}'", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, String(MUtils::GetClassFullname(instanceObject)));
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
return true;
}
const MClass* instanceObjectClass = MCore::Object::GetClass(instanceObject);
if (!instanceObjectClass->IsSubClassOf(mMethod->GetParentClass(), withInterfaces))
{
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid object instance of type '{3}'", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, String(MUtils::GetClassFullname(instanceObject)));
return true;
}

View File

@@ -27,6 +27,7 @@ Script::Script(const SpawnParams& params)
, _tickUpdate(false)
, _tickLateUpdate(false)
, _tickLateFixedUpdate(false)
, _wasAwakeCalled(false)
, _wasStartCalled(false)
, _wasEnableCalled(false)
{
@@ -86,7 +87,7 @@ void Script::SetParent(Actor* value, bool canBreakPrefabLink)
// Unlink from the old one
if (_parent)
{
if (!value && _parent->IsDuringPlay() && _parent->IsActiveInHierarchy() && GetEnabled())
if (!value && _parent->IsDuringPlay() && _parent->IsActiveInHierarchy() && GetEnabled() && _wasEnableCalled)
{
// Call disable when script is removed from actor (new actor is null)
Disable();
@@ -241,19 +242,31 @@ String Script::ToString() const
void Script::OnDeleteObject()
{
// Ensure to unlink from the parent (it will call Disable event if required)
SetParent(nullptr);
// Check if remove object from game
if (IsDuringPlay())
// Call OnDisable
if (_wasEnableCalled)
{
Disable();
}
// Call OnDestroy
if (_wasAwakeCalled)
{
_wasAwakeCalled = false;
CHECK_EXECUTE_IN_EDITOR
{
OnDestroy();
}
}
// End play
if (IsDuringPlay())
{
EndPlay();
}
// Unlink from parent
SetParent(nullptr);
// Base
SceneObject::OnDeleteObject();
}
@@ -274,9 +287,14 @@ void Script::Initialize()
if (!IsRegistered())
RegisterObject();
CHECK_EXECUTE_IN_EDITOR
// Call OnAwake
if (!_wasAwakeCalled)
{
OnAwake();
_wasAwakeCalled = true;
CHECK_EXECUTE_IN_EDITOR
{
OnAwake();
}
}
}

View File

@@ -20,6 +20,7 @@ protected:
uint16 _tickUpdate : 1;
uint16 _tickLateUpdate : 1;
uint16 _tickLateFixedUpdate : 1;
uint16 _wasAwakeCalled : 1;
uint16 _wasStartCalled : 1;
uint16 _wasEnableCalled : 1;
#if USE_EDITOR

View File

@@ -481,7 +481,7 @@ void JsonWriter::SceneObject(::SceneObject* obj)
}
else
{
LOG(Warning, "Missing prefab with id={0}.", obj->GetPrefabID());
LOG(Warning, "Missing prefab {0}.", obj->GetPrefabID());
}
}

View File

@@ -111,8 +111,9 @@ public:
struct StreamingCache
{
int64 LastUpdate = 0;
int32 TargetResidency = 0;
int64 TargetResidencyChange = 0;
int32 TargetResidency = 0;
bool Error = false;
SamplesBuffer<float, 5> QualitySamples;
};
@@ -131,7 +132,8 @@ public:
/// <summary>
/// Stops the streaming (eg. on streaming fail).
/// </summary>
void ResetStreaming();
/// <param name="error">True if streaming failed.</param>
void ResetStreaming(bool error = true);
protected:

View File

@@ -84,8 +84,9 @@ void StreamableResource::RequestStreamingUpdate()
Streaming.LastUpdate = 0;
}
void StreamableResource::ResetStreaming()
void StreamableResource::ResetStreaming(bool error)
{
Streaming.Error = error;
Streaming.TargetResidency = 0;
Streaming.LastUpdate = DateTime::MaxValue().Ticks;
}

View File

@@ -84,6 +84,8 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const
DrawCall drawCall;
if (TerrainManager::GetChunkGeometry(drawCall, chunkSize, lod))
return;
if (!_neighbors[0])
const_cast<TerrainChunk*>(this)->CacheNeighbors();
drawCall.InstanceCount = 1;
drawCall.Material = _cachedDrawMaterial;
renderContext.View.GetWorldMatrix(_transform, drawCall.World);

View File

@@ -412,4 +412,149 @@ TEST_CASE("Prefabs")
Content::DeleteAsset(prefabNested);
Content::DeleteAsset(prefabBase);
}
SECTION("Test Loading Nested Prefab After Changing and Deleting Root")
{
// https://github.com/FlaxEngine/FlaxEngine/issues/2050
// Create base prefab with 1 object
AssetReference<Prefab> prefabBase = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabBase);
Guid id;
Guid::Parse("3b3334524c696dcfa93cabacd2a4f404", id);
prefabBase->ChangeID(id);
auto prefabBaseInit = prefabBase->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\","
"\"TypeName\": \"FlaxEngine.DirectionalLight\","
"\"Name\": \"New Root\""
"},"
"{"
"\"ID\": \"f8fbee1349f749396ab6c2ad34f3afec\","
"\"TypeName\": \"FlaxEngine.Camera\","
"\"Name\": \"Child 1\","
"\"ParentID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\""
"},"
"{"
"\"ID\": \"5632561847cf96fe2e8919848b7eca79\","
"\"TypeName\": \"FlaxEngine.EmptyActor\","
"\"Name\": \"Child 1.Child\","
"\"ParentID\": \"f8fbee1349f749396ab6c2ad34f3afec\""
"},"
"{"
"\"ID\": \"4e4f3a1847cf96fe2e8919848b7eca79\","
"\"TypeName\": \"FlaxEngine.UICanvas\","
"\"Name\": \"Child 2\","
"\"ParentID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\""
"}"
"]");
REQUIRE(!prefabBaseInit);
// Create nested prefab but with 'old' state where root object is different
AssetReference<Prefab> prefabNested1 = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabNested1);
Guid::Parse("671447e947cbd2deea018a8377636ce6", id);
prefabNested1->ChangeID(id);
auto prefabNestedInit1 = prefabNested1->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"589bcfaa4bd1a53435129480e5bbdb3b\","
"\"Name\": \"Old Root\""
"},"
"{"
"\"ID\": \"1a6228d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"f8fbee1349f749396ab6c2ad34f3afec\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\""
"},"
"{"
"\"ID\": \"1212124f4d913e58eb35ab8b0b7e2eef\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"82ce814f4d913e58eb35ab8b0b7e2eef\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"Name\": \"New Root\""
"},"
"{"
"\"ID\": \"468028d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"2468902349f749396ab6c2ad34f3afec\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"Name\": \"Old Child\""
"}"
"]");
REQUIRE(!prefabNestedInit1);
// Create nested prefab but with 'old' state where root object is different and doesn't exist anymore
AssetReference<Prefab> prefabNested2 = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabNested2);
Guid::Parse("b71447e947cbd2deea018a8377636ce6", id);
prefabNested2->ChangeID(id);
auto prefabNestedInit2 = prefabNested2->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"589bcfaa4bd1a53435129480e5bbdb3b\","
"\"Name\": \"Old Root\""
"},"
"{"
"\"ID\": \"1a6228d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"f8fbee1349f749396ab6c2ad34f3afec\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\""
"},"
"{"
"\"ID\": \"468028d84897ff3b2f444ea263c3657e\","
"\"PrefabID\": \"3b3334524c696dcfa93cabacd2a4f404\","
"\"PrefabObjectID\": \"2468902349f749396ab6c2ad34f3afec\","
"\"ParentID\": \"597ab8ea43a5c58b8d06f58f9364d261\","
"\"Name\": \"Old Child\""
"}"
"]");
REQUIRE(!prefabNestedInit2);
// Spawn test instances of both prefabs
ScriptingObjectReference<Actor> instanceBase = PrefabManager::SpawnPrefab(prefabBase);
ScriptingObjectReference<Actor> instanceNested1 = PrefabManager::SpawnPrefab(prefabNested1);
ScriptingObjectReference<Actor> instanceNested2 = PrefabManager::SpawnPrefab(prefabNested2);
// Verify scenario
REQUIRE(instanceBase);
REQUIRE(instanceBase->GetName() == TEXT("New Root"));
REQUIRE(instanceBase->GetChildrenCount() == 2);
REQUIRE(instanceBase->Children[0]->GetName() == TEXT("Child 1"));
REQUIRE(instanceBase->Children[0]->GetChildrenCount() == 1);
REQUIRE(instanceBase->Children[1]->GetName() == TEXT("Child 2"));
REQUIRE(instanceBase->Children[1]->GetChildrenCount() == 0);
REQUIRE(instanceBase->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child"));
REQUIRE(instanceBase->Children[0]->Children[0]->GetChildrenCount() == 0);
REQUIRE(instanceNested1);
REQUIRE(instanceNested1->GetName() == TEXT("New Root"));
REQUIRE(instanceNested1->GetChildrenCount() == 2);
REQUIRE(instanceNested1->Children[0]->GetName() == TEXT("Child 1"));
REQUIRE(instanceNested1->Children[0]->GetChildrenCount() == 1);
REQUIRE(instanceNested1->Children[1]->GetName() == TEXT("Child 2"));
REQUIRE(instanceNested1->Children[1]->GetChildrenCount() == 0);
REQUIRE(instanceNested1->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child"));
REQUIRE(instanceNested1->Children[0]->Children[0]->GetChildrenCount() == 0);
REQUIRE(instanceNested2);
REQUIRE(instanceNested2->GetName() == TEXT("New Root"));
REQUIRE(instanceNested2->GetChildrenCount() == 2);
REQUIRE(instanceNested2->Children[0]->GetName() == TEXT("Child 1"));
REQUIRE(instanceNested2->Children[0]->GetChildrenCount() == 1);
REQUIRE(instanceNested2->Children[1]->GetName() == TEXT("Child 2"));
REQUIRE(instanceNested2->Children[1]->GetChildrenCount() == 0);
REQUIRE(instanceNested2->Children[0]->Children[0]->GetName() == TEXT("Child 1.Child"));
REQUIRE(instanceNested2->Children[0]->Children[0]->GetChildrenCount() == 0);
// Cleanup
instanceNested2->DeleteObject();
instanceNested1->DeleteObject();
instanceBase->DeleteObject();
Content::DeleteAsset(prefabNested2);
Content::DeleteAsset(prefabNested1);
Content::DeleteAsset(prefabBase);
}
}

View File

@@ -10,7 +10,6 @@
#include "Engine/Platform/ConditionVariable.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Async/GPUTask.h"
#include "Engine/Graphics/Textures/TextureUtils.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#if USE_EDITOR
@@ -318,7 +317,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
textureData.Width = (int32)meta.width;
textureData.Height = (int32)meta.height;
textureData.Depth = (int32)meta.depth;
textureData.Format = ToPixelFormat(meta.format);
textureData.Format = ::ToPixelFormat(meta.format);
textureData.Items.Resize(1);
textureData.Items.Resize((int32)meta.arraySize);
for (int32 arrayIndex = 0; arrayIndex < (int32)meta.arraySize; arrayIndex++)
@@ -598,7 +597,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
float alphaThreshold = 0.3f;
bool isPowerOfTwo = Math::IsPowerOfTwo(width) && Math::IsPowerOfTwo(height);
DXGI_FORMAT sourceDxgiFormat = currentImage->GetMetadata().format;
PixelFormat targetFormat = TextureUtils::ToPixelFormat(options.Type, width, height, options.Compress);
PixelFormat targetFormat = TextureTool::ToPixelFormat(options.Type, width, height, options.Compress);
if (options.sRGB)
targetFormat = PixelFormatExtensions::TosRGB(targetFormat);
DXGI_FORMAT targetDxgiFormat = ToDxgiFormat(targetFormat);
@@ -625,19 +624,26 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
}
bool keepAsIs = false;
if (!options.FlipY && options.Compress && type == ImageType::DDS && mipLevels == sourceMipLevels && DirectX::IsCompressed(sourceDxgiFormat) && !DirectX::IsSRGB(sourceDxgiFormat))
if (!options.FlipY &&
!options.InvertGreenChannel &&
options.Compress &&
type == ImageType::DDS &&
mipLevels == sourceMipLevels &&
DirectX::IsCompressed(sourceDxgiFormat) &&
!DirectX::IsSRGB(sourceDxgiFormat) &&
width >= 4 &&
height >= 4)
{
// Keep image in the current compressed format (artist choice) so we don't have to run the slow mipmap generation
keepAsIs = true;
targetDxgiFormat = sourceDxgiFormat;
targetFormat = ToPixelFormat(currentImage->GetMetadata().format);
targetFormat = ::ToPixelFormat(currentImage->GetMetadata().format);
}
// Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing)
if (!keepAsIs && DirectX::IsCompressed(sourceDxgiFormat))
{
auto& tmpImg = GET_TMP_IMG();
sourceDxgiFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
result = Decompress(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), sourceDxgiFormat, tmpImg);
if (FAILED(result))
@@ -645,14 +651,13 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
errorMsg = String::Format(TEXT("Cannot decompress texture, error: {0:x}"), static_cast<uint32>(result));
return true;
}
SET_CURRENT_IMG(tmpImg);
}
// Fix sRGB problem
if (!keepAsIs && DirectX::IsSRGB(sourceDxgiFormat))
{
sourceDxgiFormat = ToDxgiFormat(PixelFormatExtensions::ToNonsRGB(ToPixelFormat(sourceDxgiFormat)));
sourceDxgiFormat = ToDxgiFormat(PixelFormatExtensions::ToNonsRGB(::ToPixelFormat(sourceDxgiFormat)));
((DirectX::TexMetadata&)currentImage->GetMetadata()).format = sourceDxgiFormat;
for (size_t i = 0; i < currentImage->GetImageCount(); i++)
((DirectX::Image*)currentImage->GetImages())[i].format = sourceDxgiFormat;
@@ -662,12 +667,10 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
if (!keepAsIs && DirectX::HasAlpha(sourceDxgiFormat) && options.Type == TextureFormatType::ColorRGB && options.Compress)
{
auto& tmpImg = GET_TMP_IMG();
TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(),
result = TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(),
[](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t width, size_t y)
{
UNREFERENCED_PARAMETER(y);
for (size_t j = 0; j < width; j++)
{
outPixels[j] = DirectX::XMVectorSelect(DirectX::g_XMOne, inPixels[j], DirectX::g_XMSelect1110);
@@ -678,8 +681,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
errorMsg = String::Format(TEXT("Cannot transform texture to remove unwanted alpha channel, error: {0:x}"), static_cast<uint32>(result));
return true;
}
// Use converted image
SET_CURRENT_IMG(tmpImg);
}
@@ -687,7 +688,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
if (!keepAsIs && options.FlipY)
{
auto& tmpImg = GET_TMP_IMG();
DWORD flags = DirectX::TEX_FR_FLIP_VERTICAL;
result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg);
if (FAILED(result))
@@ -695,11 +695,37 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
errorMsg = String::Format(TEXT("Cannot rotate/flip texture, error: {0:x}"), static_cast<uint32>(result));
return true;
}
// Use converted image
SET_CURRENT_IMG(tmpImg);
}
// Check if it invert green channel
if (!keepAsIs && options.InvertGreenChannel)
{
auto& timage = GET_TMP_IMG();
result = TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(),
[&](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t w, size_t y)
{
static const DirectX::XMVECTORU32 s_selecty = { { { DirectX::XM_SELECT_0, DirectX::XM_SELECT_1, DirectX::XM_SELECT_0, DirectX::XM_SELECT_0 } } };
UNREFERENCED_PARAMETER(y);
for (size_t j = 0; j < w; ++j)
{
const DirectX::XMVECTOR value = inPixels[j];
const DirectX::XMVECTOR inverty = DirectX::XMVectorSubtract(DirectX::g_XMOne, value);
outPixels[j] = DirectX::XMVectorSelect(value, inverty, s_selecty);
}
}, timage);
if (FAILED(result))
{
errorMsg = String::Format(TEXT("Cannot invert green channel in texture, error: {0:x}"), static_cast<uint32>(result));
return true;
}
SET_CURRENT_IMG(timage);
}
// Generate mip maps chain
if (!keepAsIs && useMipLevels && options.GenerateMipMaps)
{
@@ -719,7 +745,6 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
errorMsg = String::Format(TEXT("Cannot generate texture mip maps chain, error: {1:x}"), *path, static_cast<uint32>(result));
return true;
}
SET_CURRENT_IMG(tmpImg);
}

View File

@@ -26,7 +26,7 @@ namespace
String TextureTool::Options::ToString() const
{
return String::Format(TEXT("Type: {}, IsAtlas: {}, NeverStream: {}, IndependentChannels: {}, sRGB: {}, GenerateMipMaps: {}, FlipY: {}, Scale: {}, MaxSize: {}, Resize: {}, PreserveAlphaCoverage: {}, PreserveAlphaCoverageReference: {}, SizeX: {}, SizeY: {}"),
return String::Format(TEXT("Type: {}, IsAtlas: {}, NeverStream: {}, IndependentChannels: {}, sRGB: {}, GenerateMipMaps: {}, FlipY: {}, InvertGreen: {} Scale: {}, MaxSize: {}, Resize: {}, PreserveAlphaCoverage: {}, PreserveAlphaCoverageReference: {}, SizeX: {}, SizeY: {}"),
ScriptingEnum::ToString(Type),
IsAtlas,
NeverStream,
@@ -34,6 +34,7 @@ String TextureTool::Options::ToString() const
sRGB,
GenerateMipMaps,
FlipY,
InvertGreenChannel,
Scale,
MaxSize,
MaxSize,
@@ -71,6 +72,9 @@ void TextureTool::Options::Serialize(SerializeStream& stream, const void* otherO
stream.JKEY("FlipY");
stream.Bool(FlipY);
stream.JKEY("InvertGreenChannel");
stream.Bool(InvertGreenChannel);
stream.JKEY("Resize");
stream.Bool(Resize);
@@ -128,6 +132,7 @@ void TextureTool::Options::Deserialize(DeserializeStream& stream, ISerializeModi
sRGB = JsonTools::GetBool(stream, "sRGB", sRGB);
GenerateMipMaps = JsonTools::GetBool(stream, "GenerateMipMaps", GenerateMipMaps);
FlipY = JsonTools::GetBool(stream, "FlipY", FlipY);
InvertGreenChannel = JsonTools::GetBool(stream, "InvertGreenChannel", InvertGreenChannel);
Resize = JsonTools::GetBool(stream, "Resize", Resize);
PreserveAlphaCoverage = JsonTools::GetBool(stream, "PreserveAlphaCoverage", PreserveAlphaCoverage);
PreserveAlphaCoverageReference = JsonTools::GetFloat(stream, "PreserveAlphaCoverageReference", PreserveAlphaCoverageReference);
@@ -674,6 +679,54 @@ Color TextureTool::SampleLinear(const PixelFormatSampler* sampler, const Float2&
return Color::Lerp(Color::Lerp(v00, v01, uvFraction.X), Color::Lerp(v10, v11, uvFraction.X), uvFraction.Y);
}
PixelFormat TextureTool::ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress)
{
const bool canUseBlockCompression = width % 4 == 0 && height % 4 == 0;
if (canCompress && canUseBlockCompression)
{
switch (format)
{
case TextureFormatType::ColorRGB:
return PixelFormat::BC1_UNorm;
case TextureFormatType::ColorRGBA:
return PixelFormat::BC3_UNorm;
case TextureFormatType::NormalMap:
return PixelFormat::BC5_UNorm;
case TextureFormatType::GrayScale:
return PixelFormat::BC4_UNorm;
case TextureFormatType::HdrRGBA:
return PixelFormat::BC7_UNorm;
case TextureFormatType::HdrRGB:
#if PLATFORM_LINUX
// TODO: support BC6H compression for Linux Editor
return PixelFormat::BC7_UNorm;
#else
return PixelFormat::BC6H_Uf16;
#endif
default:
return PixelFormat::Unknown;
}
}
switch (format)
{
case TextureFormatType::ColorRGB:
return PixelFormat::R8G8B8A8_UNorm;
case TextureFormatType::ColorRGBA:
return PixelFormat::R8G8B8A8_UNorm;
case TextureFormatType::NormalMap:
return PixelFormat::R16G16_UNorm;
case TextureFormatType::GrayScale:
return PixelFormat::R8_UNorm;
case TextureFormatType::HdrRGBA:
return PixelFormat::R16G16B16A16_Float;
case TextureFormatType::HdrRGB:
return PixelFormat::R11G11B10_Float;
default:
return PixelFormat::Unknown;
}
}
bool TextureTool::GetImageType(const StringView& path, ImageType& type)
{
const auto extension = FileSystem::GetExtension(path).ToLower();

View File

@@ -57,6 +57,10 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(70)")
bool FlipY = false;
// True if to invert the green channel on a normal map. Good for OpenGL to DirectX conversion.
API_FIELD(Attributes = "EditorOrder(71)")
bool InvertGreenChannel = false;
// Texture size scale. Allows increasing or decreasing the imported texture resolution. Default is 1.
API_FIELD(Attributes="EditorOrder(80), Limit(0.0001f, 1000.0f, 0.01f)")
float Scale = 1.0f;
@@ -106,14 +110,12 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
public:
#if USE_EDITOR
/// <summary>
/// Checks whenever the given texture file contains alpha channel data with values different than solid fill of 1 (non fully opaque).
/// </summary>
/// <param name="path">The file path.</param>
/// <returns>True if has alpha channel, otherwise false.</returns>
static bool HasAlpha(const StringView& path);
#endif
/// <summary>
@@ -236,6 +238,8 @@ public:
/// <returns>The sampled color (linear).</returns>
static Color SampleLinear(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch);
static PixelFormat ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress);
private:
enum class ImageType
{

View File

@@ -9,7 +9,6 @@
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/Textures/TextureUtils.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Platform/File.h"
@@ -459,7 +458,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
// Cache data
float alphaThreshold = 0.3f;
bool isPowerOfTwo = Math::IsPowerOfTwo(width) && Math::IsPowerOfTwo(height);
PixelFormat targetFormat = TextureUtils::ToPixelFormat(options.Type, width, height, options.Compress);
PixelFormat targetFormat = ToPixelFormat(options.Type, width, height, options.Compress);
if (options.sRGB)
targetFormat = PixelFormatExtensions::TosRGB(targetFormat);

View File

@@ -469,7 +469,13 @@ namespace FlaxEngine.GUI
var itemsHeight = 20.0f;
var itemsMargin = 20.0f;
// Scale height and margive with text height if needed
var textHeight = Font.GetFont().Height;
if (textHeight > itemsHeight)
{
itemsHeight = textHeight;
itemsMargin = textHeight;
}
/*
var itemsWidth = 40.0f;
var font = Font.GetFont();

View File

@@ -448,6 +448,8 @@ namespace FlaxEngine.GUI
internal virtual void AddChildInternal(Control child)
{
Assert.IsNotNull(child, "Invalid control.");
if (Parent == child)
throw new InvalidOperationException();
// Add child
_children.Add(child);
@@ -820,13 +822,14 @@ namespace FlaxEngine.GUI
protected virtual void DrawChildren()
{
// Draw all visible child controls
var children = _children;
if (_cullChildren)
{
Render2D.PeekClip(out var globalClipping);
Render2D.PeekTransform(out var globalTransform);
for (int i = 0; i < _children.Count; i++)
for (int i = 0; i < children.Count; i++)
{
var child = _children[i];
var child = children[i];
if (child.Visible)
{
Matrix3x3.Multiply(ref child._cachedTransform, ref globalTransform, out var globalChildTransform);
@@ -842,9 +845,9 @@ namespace FlaxEngine.GUI
}
else
{
for (int i = 0; i < _children.Count; i++)
for (int i = 0; i < children.Count; i++)
{
var child = _children[i];
var child = children[i];
if (child.Visible)
{
Render2D.PushTransform(ref child._cachedTransform);

View File

@@ -58,10 +58,11 @@ namespace FlaxEngine
if (containerControl != null && IsActiveInHierarchy)
{
var children = ChildrenCount;
var parent = Parent;
for (int i = 0; i < children; i++)
{
var child = GetChild(i) as UIControl;
if (child != null && child.IsActiveInHierarchy && child.HasControl)
if (child != null && child.IsActiveInHierarchy && child.HasControl && child != parent)
{
child.Control.Parent = containerControl;
}

View File

@@ -10,7 +10,7 @@
<activity android:name="com.flaxengine.GameActivity"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="fullSensor">
android:screenOrientation="${DefaultOrientation}">
<meta-data android:name="android.app.lib_name" android:value="FlaxGame" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -106,17 +106,20 @@ float4 PS_RayTracePass(Quad_VS2PS input) : SV_Target0
// SRV 7-8 Global SDF
// SRV 9-13 Global Surface Atlas
// Base layer color with reflections from probes but empty alpha so SSR blur will have valid bacground values to smooth with
float4 base = float4(Texture0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0).rgb, 0);
// Sample GBuffer
GBufferData gBufferData = GetGBufferData();
GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord);
// Reject invalid pixels
if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT || gBuffer.Roughness > RoughnessFade || gBuffer.ViewPos.z > FadeOutDistance)
return 0;
return base;
// Trace depth buffer to find intersection
float3 screenHit = TraceScreenSpaceReflection(input.TexCoord, gBuffer, Depth, gBufferData.ViewPos, ViewMatrix, ViewProjectionMatrix, RayTraceStep, MaxTraceSamples, TemporalEffect, TemporalTime, WorldAntiSelfOcclusionBias, BRDFBias, FadeOutDistance, RoughnessFade, EdgeFadeFactor);
float4 result = 0;
float4 result = base;
if (screenHit.z > 0)
{
float3 viewVector = normalize(gBufferData.ViewPos - gBuffer.WorldPos);

View File

@@ -208,21 +208,13 @@ dtNavMesh::dtNavMesh() :
dtNavMesh::~dtNavMesh()
{
for (int i = 0; i < m_maxTiles; ++i)
{
if (m_tiles[i].flags & DT_TILE_FREE_DATA)
{
dtFree(m_tiles[i].data);
m_tiles[i].data = 0;
m_tiles[i].dataSize = 0;
}
}
dtFree(m_posLookup);
dtFree(m_tiles);
purge();
}
dtStatus dtNavMesh::init(const dtNavMeshParams* params)
{
if (m_tiles)
purge();
memcpy(&m_params, params, sizeof(dtNavMeshParams));
dtVcopy(m_orig, params->orig);
m_tileWidth = params->tileWidth;
@@ -1178,6 +1170,24 @@ int dtNavMesh::getMaxTiles() const
return m_maxTiles;
}
void dtNavMesh::purge()
{
for (int i = 0; i < m_maxTiles; ++i)
{
if (m_tiles[i].flags & DT_TILE_FREE_DATA)
{
dtFree(m_tiles[i].data);
m_tiles[i].data = 0;
m_tiles[i].dataSize = 0;
}
}
m_maxTiles = 0;
dtFree(m_posLookup);
m_posLookup = 0;
dtFree(m_tiles);
m_tiles = 0;
}
dtMeshTile* dtNavMesh::getTile(int i)
{
return &m_tiles[i];

View File

@@ -613,6 +613,9 @@ private:
dtNavMesh(const dtNavMesh&);
dtNavMesh& operator=(const dtNavMesh&);
/// Clears all tiles from memory. Can be called before init to rebuild navigation mesh with different parameters.
void purge();
/// Returns pointer to tile in the tile array.
dtMeshTile* getTile(int i);

View File

@@ -91,11 +91,6 @@ namespace Flax.Build.Projects.VisualStudio
var baseConfiguration = project.Configurations.First();
var baseOutputDir = Utilities.MakePathRelativeTo(project.CSharp.OutputPath ?? baseConfiguration.TargetBuildOptions.OutputFolder, projectDirectory);
var baseIntermediateOutputPath = Utilities.MakePathRelativeTo(project.CSharp.IntermediateOutputPath ?? Path.Combine(baseConfiguration.TargetBuildOptions.IntermediateFolder, "CSharp"), projectDirectory);
bool isMainProject = Globals.Project.ProjectFolderPath == project.WorkspaceRootPath && project.Name != "BuildScripts" && (Globals.Project.Name != "Flax" || project.Name != "FlaxEngine");
var flaxBuildTargetsFilename = isMainProject ? "Flax.Build.CSharp.targets" : "Flax.Build.CSharp.SkipBuild.targets";
var cacheProjectsPath = Utilities.MakePathRelativeTo(Path.Combine(Globals.Root, "Cache", "Projects"), projectDirectory);
var flaxBuildTargetsPath = !string.IsNullOrEmpty(cacheProjectsPath) ? Path.Combine(cacheProjectsPath, flaxBuildTargetsFilename) : flaxBuildTargetsFilename;
csProjectFileContent.AppendLine($" <TargetFramework>net{dotnetSdk.Version.Major}.{dotnetSdk.Version.Minor}</TargetFramework>");
csProjectFileContent.AppendLine(" <ImplicitUsings>disable</ImplicitUsings>");
@@ -114,7 +109,11 @@ namespace Flax.Build.Projects.VisualStudio
//csProjectFileContent.AppendLine(" <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>"); // TODO: use it to reduce burden of framework libs
// Custom .targets file for overriding MSBuild build tasks
// Custom .targets file for overriding MSBuild build tasks, only invoke Flax.Build once per Flax project
bool isMainProject = Globals.Project.IsCSharpOnlyProject && Globals.Project.ProjectFolderPath == project.WorkspaceRootPath && project.Name != "BuildScripts" && (Globals.Project.Name != "Flax" || project.Name != "FlaxEngine");
var flaxBuildTargetsFilename = isMainProject ? "Flax.Build.CSharp.targets" : "Flax.Build.CSharp.SkipBuild.targets";
var cacheProjectsPath = Utilities.MakePathRelativeTo(Path.Combine(Globals.Root, "Cache", "Projects"), projectDirectory);
var flaxBuildTargetsPath = !string.IsNullOrEmpty(cacheProjectsPath) ? Path.Combine(cacheProjectsPath, flaxBuildTargetsFilename) : flaxBuildTargetsFilename;
csProjectFileContent.AppendLine(string.Format(" <CustomAfterMicrosoftCommonTargets>$(MSBuildThisFileDirectory){0}</CustomAfterMicrosoftCommonTargets>", flaxBuildTargetsPath));
// Hide annoying warnings during build

View File

@@ -702,7 +702,7 @@ namespace Flax.Build.Projects.VisualStudio
// Override MSBuild build tasks to run Flax.Build in C#-only projects
{
// Build command for the build tool
var buildToolPath = Path.ChangeExtension(Utilities.MakePathRelativeTo(typeof(Builder).Assembly.Location, Path.GetDirectoryName(solution.MainProject.Path)), null);
var buildToolPath = Path.ChangeExtension(typeof(Builder).Assembly.Location, null);
var targetsFileContent = new StringBuilder();
targetsFileContent.AppendLine("<Project>");
@@ -724,16 +724,16 @@ namespace Flax.Build.Projects.VisualStudio
{
foreach (var configuration in solution.MainProject.Configurations)
{
var cmdLine = string.Format("{0} -log -mutex -workspace={1} -arch={2} -configuration={3} -platform={4} -buildTargets={5}",
FixPath(buildToolPath),
FixPath(solution.MainProject.WorkspaceRootPath),
var cmdLine = string.Format("\"{0}\" -log -mutex -workspace=\"{1}\" -arch={2} -configuration={3} -platform={4} -buildTargets={5}",
buildToolPath,
solution.MainProject.WorkspaceRootPath,
configuration.Architecture,
configuration.Configuration,
configuration.Platform,
configuration.Target);
Configuration.PassArgs(ref cmdLine);
str.AppendLine(string.Format(" <Exec Command=\"{0} {1}\" Condition=\"'$(Configuration)|$(Platform)'=='{2}'\"/>", cmdLine, extraArgs, configuration.Name));
str.AppendLine(string.Format(" <Exec Command='{0} {1}' Condition=\"'$(Configuration)|$(Platform)'=='{2}'\"/>", cmdLine, extraArgs, configuration.Name));
}
}
}
@@ -774,14 +774,5 @@ namespace Flax.Build.Projects.VisualStudio
projects.Add(project);
}
}
private static string FixPath(string path)
{
if (path.Contains(' '))
{
path = "\"" + path + "\"";
}
return path;
}
}
}