Merge remote-tracking branch 'origin/master' into 1.8
# Conflicts: # Flax.flaxproj # Source/Editor/Cooker/Steps/DeployDataStep.cpp
This commit is contained in:
BIN
Content/Shaders/SSR.flax
(Stored with Git LFS)
BIN
Content/Shaders/SSR.flax
(Stored with Git LFS)
Binary file not shown.
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -734,9 +734,7 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
Array tmp = MoveTemp(other);
|
||||
other = *this;
|
||||
*this = MoveTemp(tmp);
|
||||
::Swap(other, *this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -616,9 +616,7 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary tmp = MoveTemp(other);
|
||||
other = *this;
|
||||
*this = MoveTemp(tmp);
|
||||
::Swap(other, *this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user