Merge remote-tracking branch 'origin/1.9'

This commit is contained in:
Wojtek Figat
2024-09-23 14:11:41 +02:00
694 changed files with 40054 additions and 25220 deletions

View File

@@ -1,3 +1,5 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.IO;
using FlaxEditor.Scripting;
@@ -94,30 +96,8 @@ public class AssetPickerValidator : IContentItemOwner
/// </summary>
public string SelectedPath
{
get
{
string path = _selectedItem?.Path ?? _selected?.Path;
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
set
{
if (string.IsNullOrEmpty(value))
{
SelectedItem = null;
}
else
{
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
}
}
get => Utilities.Utils.ToPathProject(_selectedItem?.Path ?? _selected?.Path);
set => SelectedItem = string.IsNullOrEmpty(value) ? null : Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(value));
}
/// <summary>
@@ -242,7 +222,7 @@ public class AssetPickerValidator : IContentItemOwner
/// <summary>
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
/// </summary>
/// <param name="assetType">The assets types that this picker accepts.</param>
/// <param name="assetType">The asset types that this picker accepts.</param>
public AssetPickerValidator(ScriptType assetType)
{
_type = assetType;

View File

@@ -388,7 +388,7 @@ namespace FlaxEditor.Content
{
sb.Append("Type: ").Append(TypeDescription).AppendLine();
if (File.Exists(Path))
sb.Append("Size: ").Append(Utilities.Utils.FormatBytesCount((int)new FileInfo(Path).Length)).AppendLine();
sb.Append("Size: ").Append(Utilities.Utils.FormatBytesCount((ulong)new FileInfo(Path).Length)).AppendLine();
sb.Append("Path: ").Append(Utilities.Utils.GetAssetNamePathWithExt(Path)).AppendLine();
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.Content
{
/// <summary>
/// Content item that contains video media file.
/// </summary>
/// <seealso cref="FlaxEditor.Content.JsonAssetItem" />
public sealed class VideoItem : FileItem
{
/// <summary>
/// Initializes a new instance of the <see cref="VideoItem"/> class.
/// </summary>
/// <param name="path">The file path.</param>
public VideoItem(string path)
: base(path)
{
}
/// <inheritdoc />
public override string TypeDescription => "Video";
/// <inheritdoc />
public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128;
}
}

View File

@@ -30,9 +30,7 @@ namespace FlaxEditor.Content
/// <summary>
/// Determines whether [is virtual proxy].
/// </summary>
/// <returns>
/// <c>true</c> if [is virtual proxy]; otherwise, <c>false</c>.
/// </returns>
/// <returns><c>true</c> if [is virtual proxy]; otherwise, <c>false</c>.</returns>
public bool IsVirtualProxy()
{
return IsVirtual && CanExport == false;

View File

@@ -31,6 +31,12 @@ namespace FlaxEditor.Content
/// <param name="path">The path to the template</param>
protected abstract void GetTemplatePath(out string path);
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new CSharpScriptItem(path);
}
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{

View File

@@ -39,6 +39,16 @@ namespace FlaxEditor.Content
return false;
}
/// <summary>
/// Constructs the item for the file.
/// </summary>
/// <param name="path">The file path.</param>
/// <returns>Created item or null.</returns>
public virtual ContentItem ConstructItem(string path)
{
return null;
}
/// <summary>
/// Gets a value indicating whether this proxy if for assets.
/// </summary>

View File

@@ -87,6 +87,12 @@ namespace FlaxEditor.Content
return item is CppScriptItem;
}
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new CppScriptItem(path);
}
/// <inheritdoc />
protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate)
{

View File

@@ -20,6 +20,12 @@ namespace FlaxEditor.Content
return item is FileItem;
}
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new FileItem(path);
}
/// <inheritdoc />
public override string FileExtension => string.Empty;

View File

@@ -94,7 +94,10 @@ namespace FlaxEditor.Content
_preview.Model = (Model)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.ViewportCamera.SetArcBallView(_preview.Model.GetBox());
var bounds = _preview.Model.GetBox();
var maxSize = Math.Max(0.001f, (float)bounds.Size.MaxValue);
_preview.ViewportCamera.SetArcBallView(bounds);
_preview.FarPlane = Mathf.Max(1000.0f, maxSize * 2 + 100.0f);
_preview.Task.OnDraw();
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
namespace FlaxEditor.Content
{
/// <summary>
/// A video media file proxy object.
/// </summary>
public class VideoProxy : ContentProxy
{
private readonly string _extension;
internal VideoProxy(string extension)
{
_extension = extension;
}
/// <inheritdoc />
public override string Name => "Video";
/// <inheritdoc />
public override string FileExtension => _extension;
/// <inheritdoc />
public override Color AccentColor => Color.FromRGB(0x11f7f1);
/// <inheritdoc />
public override bool IsProxyFor(ContentItem item)
{
return item is VideoItem;
}
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new VideoItem(path);
}
/// <inheritdoc />
public override EditorWindow Open(Editor editor, ContentItem item)
{
return new VideoWindow(editor, (VideoItem)item);
}
}
}

View File

@@ -134,6 +134,12 @@ API_ENUM() enum class BuildPlatform
/// </summary>
API_ENUM(Attributes="EditorDisplay(null, \"iOS ARM64\")")
iOSARM64 = 14,
/// <summary>
/// Windows (ARM64)
/// </summary>
API_ENUM(Attributes = "EditorDisplay(null, \"Windows ARM64\")")
WindowsARM64 = 15,
};
/// <summary>
@@ -285,24 +291,22 @@ public:
/// <summary>
/// The total assets amount in the build.
/// </summary>
int32 TotalAssets;
int32 TotalAssets = 0;
/// <summary>
/// The cooked assets (TotalAssets - CookedAssets is amount of reused cached assets).
/// </summary>
int32 CookedAssets;
int32 CookedAssets = 0;
/// <summary>
/// The final output content size in MB.
/// The final output content size (in bytes).
/// </summary>
int32 ContentSizeMB;
uint64 ContentSize = 0;
/// <summary>
/// The asset type stats. Key is the asset typename, value is the stats container.
/// </summary>
Dictionary<String, AssetTypeStatistics> AssetStats;
Statistics();
};
/// <summary>
@@ -328,6 +332,11 @@ public:
/// </summary>
HashSet<Guid> Assets;
/// <summary>
/// The final files collection to include in build (valid only after CollectAssetsStep).
/// </summary>
HashSet<String> Files;
struct BinaryModuleInfo
{
String Name;

View File

@@ -148,6 +148,8 @@ const Char* ToString(const BuildPlatform platform)
return TEXT("Mac ARM64");
case BuildPlatform::iOSARM64:
return TEXT("iOS ARM64");
case BuildPlatform::WindowsARM64:
return TEXT("Windows ARM64");
default:
return TEXT("");
}
@@ -202,13 +204,6 @@ bool CookingData::AssetTypeStatistics::operator<(const AssetTypeStatistics& othe
return Count > other.Count;
}
CookingData::Statistics::Statistics()
{
TotalAssets = 0;
CookedAssets = 0;
ContentSizeMB = 0;
}
CookingData::CookingData(const SpawnParams& params)
: ScriptingObject(params)
{
@@ -307,6 +302,10 @@ void CookingData::GetBuildPlatformName(const Char*& platform, const Char*& archi
platform = TEXT("iOS");
architecture = TEXT("ARM64");
break;
case BuildPlatform::WindowsARM64:
platform = TEXT("Windows");
architecture = TEXT("ARM64");
break;
default:
LOG(Fatal, "Unknown or unsupported build platform.");
}
@@ -393,6 +392,9 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform)
case BuildPlatform::Windows64:
result = New<WindowsPlatformTools>(ArchitectureType::x64);
break;
case BuildPlatform::WindowsARM64:
result = New<WindowsPlatformTools>(ArchitectureType::ARM64);
break;
#endif
#if PLATFORM_TOOLS_UWP
case BuildPlatform::UWPx86:
@@ -554,7 +556,12 @@ void GameCooker::GetCurrentPlatform(PlatformType& platform, BuildPlatform& build
switch (PLATFORM_TYPE)
{
case PlatformType::Windows:
buildPlatform = PLATFORM_64BITS ? BuildPlatform::Windows64 : BuildPlatform::Windows32;
if (PLATFORM_ARCH == ArchitectureType::x64)
buildPlatform = BuildPlatform::Windows64;
else if (PLATFORM_ARCH == ArchitectureType::ARM64)
buildPlatform = BuildPlatform::WindowsARM64;
else
buildPlatform = BuildPlatform::Windows32;
break;
case PlatformType::XboxOne:
buildPlatform = BuildPlatform::XboxOne;

View File

@@ -325,9 +325,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
const auto buildSettings = BuildSettings::Get();
if (buildSettings->SkipPackaging)
{
return false;
}
GameCooker::PackageFiles();
// Validate environment variables

View File

@@ -146,7 +146,7 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
sb.Append(TEXT(" </ExecutableList>\n"));
sb.AppendFormat(TEXT(" <ShellVisuals DefaultDisplayName=\"{0}\"\n"), gameSettings->ProductName);
sb.AppendFormat(TEXT(" PublisherDisplayName=\"{0}\"\n"), platformSettings->PublisherDisplayName.HasChars() ? platformSettings->PublisherDisplayName : gameSettings->CompanyName);
sb.AppendFormat(TEXT(" BackgroundColor=\"#{0}\"\n"), platformSettings->BackgroundColor.ToHexString());
sb.AppendFormat(TEXT(" BackgroundColor=\"#{0}\"\n"), platformSettings->BackgroundColor.ToHexString().Left(6));
sb.AppendFormat(TEXT(" ForegroundText=\"{0}\"\n"), platformSettings->ForegroundText);
sb.Append(TEXT(" Square150x150Logo=\"Assets\\Square150x150Logo.png\"\n"));
sb.Append(TEXT(" Square480x480Logo=\"Assets\\Square480x480Logo.png\"\n"));

View File

@@ -10,47 +10,26 @@
#include "Engine/Content/Assets/Shader.h"
#include "Engine/Content/Cache/AssetsCache.h"
bool CollectAssetsStep::Process(CookingData& data, Asset* asset)
{
// Skip virtual/temporary assets
if (asset->IsVirtual())
return false;
// Keep reference to the asset
AssetReference<Asset> ref(asset);
// Asset should have loaded data
if (asset->WaitForLoaded())
return false;
// Gather asset references
_references.Clear();
asset->Locker.Lock();
asset->GetReferences(_references);
asset->Locker.Unlock();
_assetsQueue.Add(_references);
return false;
}
bool CollectAssetsStep::Perform(CookingData& data)
{
LOG(Info, "Searching for assets to include in a build. Using {0} root assets.", data.RootAssets.Count());
data.StepProgress(TEXT("Collecting assets"), 0);
// Initialize assets queue
_assetsQueue.Clear();
_assetsQueue.EnsureCapacity(1024);
Array<Guid> assetsQueue;
assetsQueue.Clear();
assetsQueue.EnsureCapacity(1024);
for (auto i = data.RootAssets.Begin(); i.IsNotEnd(); ++i)
_assetsQueue.Add(i->Item);
assetsQueue.Add(i->Item);
// Iterate through the assets graph
AssetInfo assetInfo;
while (_assetsQueue.HasItems())
Array<Guid> references;
Array<String> files;
while (assetsQueue.HasItems())
{
BUILD_STEP_CANCEL_CHECK;
const auto assetId = _assetsQueue.Dequeue();
const Guid assetId = assetsQueue.Dequeue();
// Skip already processed or invalid assets
if (!assetId.IsValid()
@@ -69,14 +48,31 @@ bool CollectAssetsStep::Perform(CookingData& data)
}
// Load asset
const auto asset = Content::LoadAsync<Asset>(assetId);
AssetReference<Asset> asset = Content::LoadAsync<Asset>(assetId);
if (asset == nullptr)
continue;
// Process that asset
LOG_STR(Info, asset->GetPath());
data.Assets.Add(assetId);
Process(data, asset);
// Skip virtual/temporary assets
if (asset->IsVirtual())
continue;
// Asset should have loaded data
if (asset->WaitForLoaded())
continue;
// Gather asset references
references.Clear();
asset->Locker.Lock();
asset->GetReferences(references, files);
asset->Locker.Unlock();
assetsQueue.Add(references);
for (String& file : files)
{
if (file.HasChars())
data.Files.Add(MoveTemp(file));
}
}
data.Stats.TotalAssets = data.Assets.Count();

View File

@@ -12,15 +12,7 @@ class Asset;
/// <seealso cref="GameCooker::BuildStep" />
class CollectAssetsStep : public GameCooker::BuildStep
{
private:
Array<Guid> _assetsQueue;
Array<Guid> _references;
bool Process(CookingData& data, Asset* asset);
public:
// [BuildStep]
bool Perform(CookingData& data) override;
};

View File

@@ -447,6 +447,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
#if PLATFORM_TOOLS_WINDOWS
case BuildPlatform::Windows32:
case BuildPlatform::Windows64:
case BuildPlatform::WindowsARM64:
{
const char* platformDefineName = "PLATFORM_WINDOWS";
const auto settings = WindowsPlatformSettings::Get();
@@ -891,7 +892,6 @@ bool CookAssetsStep::Process(CookingData& data, CacheData& cache, JsonAssetBase*
class PackageBuilder : public NonCopyable
{
private:
int32 _packageIndex;
int32 MaxAssetsPerPackage;
int32 MaxPackageSize;
@@ -904,7 +904,6 @@ private:
uint64 packagesSizeTotal;
public:
/// <summary>
/// Initializes a new instance of the <see cref="PackageBuilder" /> class.
/// </summary>
@@ -933,7 +932,6 @@ public:
}
public:
uint64 GetPackagesSizeTotal() const
{
return packagesSizeTotal;
@@ -1042,8 +1040,11 @@ bool CookAssetsStep::Perform(CookingData& data)
float Step1ProgressEnd = 0.6f;
String Step1Info = TEXT("Cooking assets");
float Step2ProgressStart = Step1ProgressEnd;
float Step2ProgressEnd = 0.9f;
String Step2Info = TEXT("Packaging assets");
float Step2ProgressEnd = 0.8f;
String Step2Info = TEXT("Cooking files");
float Step3ProgressStart = Step2ProgressStart;
float Step3ProgressEnd = 0.9f;
String Step3Info = TEXT("Packaging assets");
data.StepProgress(TEXT("Loading build cache"), 0);
@@ -1100,11 +1101,14 @@ bool CookAssetsStep::Perform(CookingData& data)
#endif
int32 subStepIndex = 0;
AssetReference<Asset> assetRef;
assetRef.Unload.Bind([]() { LOG(Error, "Asset gets unloaded while cooking it!"); Platform::Sleep(100); });
assetRef.Unload.Bind([]
{
LOG(Error, "Asset got unloaded while cooking it!");
Platform::Sleep(100);
});
for (auto i = data.Assets.Begin(); i.IsNotEnd(); ++i)
{
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step1Info, Math::Lerp(Step1ProgressStart, Step1ProgressEnd, static_cast<float>(subStepIndex++) / data.Assets.Count()));
const Guid assetId = i->Item;
@@ -1184,6 +1188,35 @@ bool CookAssetsStep::Perform(CookingData& data)
// Save build cache header
cache.Save(data);
// Process all files
for (auto i = data.Files.Begin(); i.IsNotEnd(); ++i)
{
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step2Info, Math::Lerp(Step2ProgressStart, Step2ProgressEnd, (float)subStepIndex++ / data.Files.Count()));
const String& filePath = i->Item;
// Calculate destination path
String cookedPath = data.DataOutputPath;
if (FileSystem::IsRelative(filePath))
cookedPath /= filePath;
else
cookedPath /= String(TEXT("Content")) / StringUtils::GetFileName(filePath);
// Copy file
if (!FileSystem::FileExists(cookedPath) || FileSystem::GetFileLastEditTime(cookedPath) >= FileSystem::GetFileLastEditTime(filePath))
{
if (FileSystem::CreateDirectory(StringUtils::GetDirectoryName(cookedPath)))
return true;
if (FileSystem::CopyFile(cookedPath, filePath))
return true;
}
// Count stats of file extension
auto& assetStats = data.Stats.AssetStats[FileSystem::GetExtension(cookedPath)];
assetStats.Count++;
assetStats.ContentSize += FileSystem::GetFileSize(cookedPath);
}
// Create build game header
{
GameHeaderFlags gameFlags = GameHeaderFlags::None;
@@ -1229,13 +1262,11 @@ bool CookAssetsStep::Perform(CookingData& data)
for (auto i = AssetsRegistry.Begin(); i.IsNotEnd(); ++i)
{
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step2Info, Math::Lerp(Step2ProgressStart, Step2ProgressEnd, static_cast<float>(subStepIndex++) / AssetsRegistry.Count()));
data.StepProgress(Step3Info, Math::Lerp(Step3ProgressStart, Step3ProgressEnd, (float)subStepIndex++ / AssetsRegistry.Count()));
const auto assetId = i->Key;
String cookedFilePath;
cache.GetFilePath(assetId, cookedFilePath);
if (!FileSystem::FileExists(cookedFilePath))
{
LOG(Warning, "Missing cooked file for asset \'{0}\'", assetId);
@@ -1253,12 +1284,12 @@ bool CookAssetsStep::Perform(CookingData& data)
return true;
for (auto& e : data.Stats.AssetStats)
e.Value.TypeName = e.Key;
data.Stats.ContentSizeMB = static_cast<int32>(packageBuilder.GetPackagesSizeTotal() / (1024 * 1024));
data.Stats.ContentSize += packageBuilder.GetPackagesSizeTotal();
}
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(TEXT("Creating assets cache"), Step2ProgressEnd);
data.StepProgress(TEXT("Creating assets cache"), Step3ProgressEnd);
// Create asset paths mapping for the assets.
// Assets mapping is use to convert paths used in Content::Load(path) into the asset id.
@@ -1291,7 +1322,7 @@ bool CookAssetsStep::Perform(CookingData& data)
}
// Print stats
LOG(Info, "Cooked {0} assets, total assets: {1}, total content packages size: {2} MB", data.Stats.CookedAssets, AssetsRegistry.Count(), data.Stats.ContentSizeMB);
LOG(Info, "Cooked {0} assets, total assets: {1}, total content packages size: {2} MB", data.Stats.CookedAssets, AssetsRegistry.Count(), (int32)(data.Stats.ContentSize / (1024 * 1024)));
{
Array<CookingData::AssetTypeStatistics> assetTypes;
data.Stats.AssetStats.GetValues(assetTypes);

View File

@@ -73,6 +73,7 @@ bool DeployDataStep::Perform(CookingData& data)
{
case BuildPlatform::Windows32:
case BuildPlatform::Windows64:
case BuildPlatform::WindowsARM64:
canUseSystemDotnet = PLATFORM_TYPE == PlatformType::Windows;
break;
case BuildPlatform::LinuxX64:
@@ -159,7 +160,24 @@ bool DeployDataStep::Perform(CookingData& data)
}
else
{
#if 1
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("host/fxr") / version, srcDotnet / TEXT("host/fxr") / version, true);
#else
// TODO: hostfxr for target platform should be copied from nuget package location: microsoft.netcore.app.runtime.<RID>/<VERSION>/runtimes/<RID>/native/hostfxr.dll
String dstHostfxr = dstDotnet / TEXT("host/fxr") / version;
if (!FileSystem::DirectoryExists(dstHostfxr))
FileSystem::CreateDirectory(dstHostfxr);
const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName);
if (data.Platform == BuildPlatform::Windows64 || data.Platform == BuildPlatform::WindowsARM64 || data.Platform == BuildPlatform::Windows32)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.dll"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.dll"));
else if (data.Platform == BuildPlatform::LinuxX64)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.so"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.so"));
else if (data.Platform == BuildPlatform::MacOSx64 || data.Platform == BuildPlatform::MacOSARM64)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.dylib"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.dylib"));
else
failed |= true;
#endif
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("shared/Microsoft.NETCore.App") / version, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true);
}
if (failed)
@@ -355,6 +373,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(TEXT("Shaders/Sky"));
data.AddRootEngineAsset(TEXT("Shaders/SSAO"));
data.AddRootEngineAsset(TEXT("Shaders/SSR"));
data.AddRootEngineAsset(TEXT("Shaders/SDF"));
data.AddRootEngineAsset(TEXT("Shaders/VolumetricFog"));
data.AddRootEngineAsset(TEXT("Engine/DefaultMaterial"));
data.AddRootEngineAsset(TEXT("Engine/DefaultDeformableMaterial"));

View File

@@ -392,6 +392,10 @@ namespace FlaxEditor.CustomEditors
else if (Values.HasDefaultValue && CanRevertDefaultValue)
color = Color.Yellow * 0.8f;
LinkedLabel.HighlightStripColor = color;
// Grey out deprecated members
if (Values.IsObsolete)
LinkedLabel.TextColor = LinkedLabel.TextColorHighlighted = FlaxEngine.GUI.Style.Current.ForegroundGrey;
}
}

View File

@@ -24,7 +24,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Show instanced parameters to view/edit at runtime
if (Values.IsSingleObject && Editor.Instance.StateMachine.IsPlayMode)
{
var group = layout.Group("Parameters");
var group = SurfaceUtils.InitGraphParametersGroup(layout);
group.Panel.Open(false);
group.Panel.IndexInParent -= 2;

View File

@@ -6,6 +6,7 @@ using FlaxEditor.Content;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
@@ -50,7 +51,6 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentTypes)
return;
Picker = layout.Custom<AssetPicker>().CustomControl;
var value = Values[0];
_valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value);
var assetType = _valueType;
@@ -58,37 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
assetType = new ScriptType(typeof(Asset));
else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name)
assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]);
float height = 48;
var attributes = Values.GetAttributes();
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
if (assetReference.UseSmallPicker)
height = 32;
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
// Generic file picker
assetType = ScriptType.Null;
Picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
assetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
else
assetType = ScriptType.Void;
}
}
Picker.Validator.AssetType = assetType;
ApplyAssetReferenceAttribute(Values, out var height, Picker.Validator);
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
@@ -115,6 +86,37 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(Picker.Validator.SelectedAsset);
}
internal static void ApplyAssetReferenceAttribute(ValueContainer values, out float height, AssetPickerValidator validator)
{
height = 48;
var attributes = values.GetAttributes();
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
if (assetReference.UseSmallPicker)
height = 32;
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
// Generic file picker
validator.AssetType = ScriptType.Null;
validator.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
validator.AssetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
else
validator.AssetType = ScriptType.Void;
}
}
}
/// <inheritdoc />
public override void Refresh()
{
@@ -140,4 +142,155 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
}
/// <summary>
/// Default implementation of the inspector used to edit reference to the files via path (absolute or relative to the project).
/// </summary>
/// <remarks>Supports editing reference to the asset via path using various containers: <see cref="Asset"/> or <see cref="AssetItem"/> or <see cref="System.String"/>.</remarks>
public class FilePathEditor : CustomEditor
{
private sealed class TextBoxWithPicker : TextBox
{
private const float DropdownIconMargin = 3.0f;
private const float DropdownIconSize = 12.0f;
private Rectangle DropdownRect => new Rectangle(Width - DropdownIconSize - DropdownIconMargin, DropdownIconMargin, DropdownIconSize, DropdownIconSize);
public Action ShowPicker;
public override void Draw()
{
base.Draw();
var style = FlaxEngine.GUI.Style.Current;
var dropdownRect = DropdownRect;
Render2D.DrawSprite(style.ArrowDown, dropdownRect, Enabled ? (DropdownRect.Contains(PointFromWindow(RootWindow.MousePosition)) ? style.BorderSelected : style.Foreground) : style.ForegroundDisabled);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (DropdownRect.Contains(ref location))
{
Focus();
ShowPicker();
return true;
}
return base.OnMouseDown(location, button);
}
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
if (DropdownRect.Contains(ref location))
Cursor = CursorType.Default;
else
Cursor = CursorType.IBeam;
}
protected override Rectangle TextRectangle
{
get
{
var result = base.TextRectangle;
result.Size.X -= DropdownIconSize + DropdownIconMargin * 2;
return result;
}
}
protected override Rectangle TextClipRectangle
{
get
{
var result = base.TextClipRectangle;
result.Size.X -= DropdownIconSize + DropdownIconMargin * 2;
return result;
}
}
}
private TextBoxWithPicker _textBox;
private AssetPickerValidator _validator;
private bool _isRefreshing;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
if (HasDifferentTypes)
return;
_textBox = layout.Custom<TextBoxWithPicker>().CustomControl;
_textBox.ShowPicker = OnShowPicker;
_textBox.EditEnd += OnEditEnd;
_validator = new AssetPickerValidator(ScriptType.Null);
AssetRefEditor.ApplyAssetReferenceAttribute(Values, out _, _validator);
}
private void OnShowPicker()
{
if (_validator.AssetType != ScriptType.Null)
AssetSearchPopup.Show(_textBox, _textBox.BottomLeft, _validator.IsValid, SetPickerPath);
else
ContentSearchPopup.Show(_textBox, _textBox.BottomLeft, _validator.IsValid, SetPickerPath);
}
private void SetPickerPath(ContentItem item)
{
var path = Utilities.Utils.ToPathProject(item.Path);
SetPath(path);
_isRefreshing = true;
_textBox.Defocus();
_textBox.Text = path;
_isRefreshing = false;
_textBox.RootWindow.Focus();
_textBox.Focus();
}
private void OnEditEnd()
{
SetPath(_textBox.Text);
}
private string GetPath()
{
var value = Values[0];
if (value is AssetItem assetItem)
return Utilities.Utils.ToPathProject(assetItem.Path);
if (value is Asset asset)
return Utilities.Utils.ToPathProject(asset.Path);
if (value is string str)
return str;
return null;
}
private void SetPath(string path)
{
if (_isRefreshing)
return;
var value = Values[0];
if (value is AssetItem)
SetValue(Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(path)));
else if (value is Asset)
SetValue(FlaxEngine.Content.LoadAsync(path));
else if (value is string)
SetValue(path);
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (!HasDifferentValues)
{
_isRefreshing = true;
_textBox.Text = GetPath();
_isRefreshing = false;
}
}
}
}

View File

@@ -535,6 +535,15 @@ namespace FlaxEditor.CustomEditors.Editors
_groupsPool.Add(groups);
}
internal static GroupElement OnGroup(LayoutElementsContainer layout, string name)
{
// Add new group
var group = layout.Group(name);
group.Panel.Tag = group;
group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked;
return group;
}
internal static LayoutElementsContainer OnGroup(LayoutElementsContainer layout, EditorDisplayAttribute display)
{
if (display?.Group != null)

View File

@@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Editors
new OptionType("Linear Gradient", typeof(LinearGradientBrush)),
new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)),
new OptionType("Sprite 9-Slicing", typeof(Sprite9SlicingBrush)),
new OptionType("Video", typeof(VideoBrush)),
};
}
}

View File

@@ -33,11 +33,25 @@ namespace FlaxEditor.CustomEditors.Editors
names.Add(mapping.Name);
}
_comboBox.Items = names;
if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name))
var prev = GetValue();
if (prev is InputEvent inputEvent && names.Contains(inputEvent.Name))
_comboBox.SelectedItem = inputEvent.Name;
else if (prev is string name && names.Contains(name))
_comboBox.SelectedItem = name;
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
private object GetValue()
{
if (Values[0] is InputEvent inputEvent)
return inputEvent;
if (Values[0] is string str)
return str;
if (Values.Type.Type == typeof(string))
return string.Empty;
return null;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
{
var button = menu.AddButton("Set to null");
@@ -46,7 +60,16 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnSelectedIndexChanged(ComboBox comboBox)
{
SetValue(comboBox.SelectedItem == null ? null : new InputEvent(comboBox.SelectedItem));
object value = null;
if (comboBox.SelectedItem != null)
{
var prev = GetValue();
if (prev is InputEvent)
value = new InputEvent(comboBox.SelectedItem);
else if (prev is string)
value = comboBox.SelectedItem;
}
SetValue(value);
}
/// <inheritdoc />
@@ -59,8 +82,11 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
if (Values[0] is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name))
var prev = GetValue();
if (prev is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name))
_comboBox.SelectedItem = inputEvent.Name;
else if (prev is string name && _comboBox.Items.Contains(name))
_comboBox.SelectedItem = name;
else
_comboBox.SelectedItem = null;
}

View File

@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary>
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
/// </summary>
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
[System.Obsolete("Use ValueBox instead")]
public DoubleValueBox DoubleValue => ValueBox;
/// <summary>

View File

@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary>
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
/// </summary>
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
[System.Obsolete("Use ValueBox instead")]
public FloatValueBox FloatValue => ValueBox;
/// <summary>

View File

@@ -139,6 +139,11 @@ namespace FlaxEditor.CustomEditors
/// </summary>
public bool IsArray => Type != ScriptType.Null && Type.IsArray;
/// <summary>
/// True if member or type has <see cref="System.ObsoleteAttribute"/> that marks it as obsolete.
/// </summary>
public bool IsObsolete { get; }
/// <summary>
/// Gets the values types array (without duplicates).
/// </summary>
@@ -160,6 +165,7 @@ namespace FlaxEditor.CustomEditors
{
Info = info;
Type = Info.ValueType;
IsObsolete = Info.HasAttribute(typeof(ObsoleteAttribute), true);
}
/// <summary>

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.Content.Settings;
using FlaxEditor.Content.Thumbnails;
@@ -856,7 +857,7 @@ namespace FlaxEditor
/// New asset types allowed to create.
/// [Deprecated in v1.8]
/// </summary>
[Obsolete("Use CreateAsset with named tag.")]
[Obsolete("Use CreateAsset with named tag instead")]
public enum NewAssetType
{
/// <summary>
@@ -1037,7 +1038,7 @@ namespace FlaxEditor
/// </summary>
/// <param name="type">New asset type.</param>
/// <param name="outputPath">Output asset path.</param>
[Obsolete("Use CreateAsset with named tag.")]
[Obsolete("Use CreateAsset with named tag instead")]
public static bool CreateAsset(NewAssetType type, string outputPath)
{
// [Deprecated on 18.02.2024, expires on 18.02.2025]
@@ -1345,20 +1346,33 @@ namespace FlaxEditor
/// </summary>
public void BuildAllMeshesSDF()
{
// TODO: async maybe with progress reporting?
var models = new List<Model>();
Scene.ExecuteOnGraph(node =>
{
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
{
if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null)
var model = staticModel.Model;
if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) &&
model != null &&
!models.Contains(model) &&
!model.IsVirtual &&
model.SDF.Texture == null)
{
Log("Generating SDF for " + staticModel.Model);
if (!staticModel.Model.GenerateSDF())
staticModel.Model.Save();
models.Add(model);
}
}
return true;
});
Task.Run(() =>
{
for (int i = 0; i < models.Count; i++)
{
var model = models[i];
Log($"[{i}/{models.Count}] Generating SDF for {model}");
if (!model.GenerateSDF())
model.Save();
}
});
}
#endregion

View File

@@ -5,7 +5,6 @@ using System.IO;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -151,7 +150,7 @@ namespace FlaxEditor.GUI
TextAlignment.Center);
Render2D.DrawText(
style.FontSmall,
$"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}",
$"{Validator.AssetType.Type.GetTypeDisplayName()}",
new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize),
style.ForegroundGrey,
TextAlignment.Near,
@@ -180,7 +179,7 @@ namespace FlaxEditor.GUI
TextAlignment.Center);
Render2D.DrawText(
style.FontSmall,
$"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}",
$"{Validator.AssetType.Type.GetTypeDisplayName()}",
new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize),
style.ForegroundGrey,
TextAlignment.Near,
@@ -204,7 +203,7 @@ namespace FlaxEditor.GUI
TextAlignment.Center);
Render2D.DrawText(
style.FontSmall,
$"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}",
$"{Validator.AssetType.Type.GetTypeDisplayName()}",
new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize),
style.ForegroundGrey,
TextAlignment.Near,

View File

@@ -14,6 +14,11 @@ namespace FlaxEditor.GUI.Docking
{
private DockPanel _panel;
private double _dragEnterTime = -1;
#if PLATFORM_WINDOWS
private const bool HideTabForSingleTab = true;
#else
private const bool HideTabForSingleTab = false;
#endif
/// <summary>
/// The is mouse down flag (left button).
@@ -51,7 +56,7 @@ namespace FlaxEditor.GUI.Docking
public DockWindow StartDragAsyncWindow;
private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight);
private bool IsSingleFloatingWindow => _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0;
private bool IsSingleFloatingWindow => HideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0;
/// <summary>
/// Initializes a new instance of the <see cref="DockPanelProxy"/> class.

View File

@@ -610,7 +610,8 @@ Array<Guid> ManagedEditor::GetAssetReferences(const Guid& assetId)
Array<Guid> result;
if (auto* asset = Content::Load<Asset>(assetId))
{
asset->GetReferences(result);
Array<String> files;
asset->GetReferences(result, files);
}
return result;
}

View File

@@ -129,12 +129,9 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i].IsProxyFor(item))
{
return Proxy[i];
}
}
}
return null;
}
@@ -147,11 +144,8 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i].IsProxyFor<T>())
{
return Proxy[i];
}
}
return null;
}
@@ -164,17 +158,12 @@ namespace FlaxEditor.Modules
{
if (string.IsNullOrEmpty(extension))
throw new ArgumentNullException();
extension = StringUtils.NormalizeExtension(extension);
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i].FileExtension == extension)
{
if (string.Equals(Proxy[i].FileExtension, extension, StringComparison.Ordinal))
return Proxy[i];
}
}
return null;
}
@@ -189,29 +178,23 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i] is AssetProxy proxy && proxy.AcceptsAsset(typeName, path))
{
return proxy;
}
}
return null;
}
/// <summary>
/// Gets the virtual proxy object from given path.
/// <br></br>Useful if the asset that needs to be displayed is not a Flax asset but needs custom functionality.
/// </summary>
/// <param name="path">The asset path.</param>
/// <returns>Asset proxy or null if not found.</returns>
/// <returns>Asset proxy or null if cannot find.</returns>
public AssetProxy GetAssetVirtualProxy(string path)
{
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i] is AssetProxy proxy && proxy.IsVirtualProxy() && path.EndsWith(proxy.FileExtension, StringComparison.OrdinalIgnoreCase))
{
return proxy;
}
}
return null;
}
@@ -1026,7 +1009,9 @@ namespace FlaxEditor.Modules
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
if (item == null)
{
item = new FileItem(path);
item = GetProxy(Path.GetExtension(path))?.ConstructItem(path);
if (item == null)
item = new FileItem(path);
}
}
@@ -1113,6 +1098,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new VisualScriptProxy());
Proxy.Add(new BehaviorTreeProxy());
Proxy.Add(new LocalizedStringTableProxy());
Proxy.Add(new VideoProxy("mp4"));
Proxy.Add(new WidgetProxy());
Proxy.Add(new FileProxy());
Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>());

View File

@@ -841,7 +841,6 @@ namespace FlaxEditor.Modules
{
// Open project, then close it
Editor.OpenProject(Editor.GameProject.ProjectPath);
Editor.Windows.MainWindow.Close(ClosingReason.User);
}
private void OnMenuFileShowHide(Control control)

View File

@@ -0,0 +1,69 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated
{
/// <summary>
/// Custom editor for <see cref="VideoPlayer"/>.
/// </summary>
[CustomEditor(typeof(VideoPlayer)), DefaultEditor]
public class VideoPlayerEditor : ActorEditor
{
private Label _infoLabel;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
// Show playback options during simulation
if (Editor.IsPlayMode)
{
var playbackGroup = layout.Group("Playback");
playbackGroup.Panel.Open();
_infoLabel = playbackGroup.Label(string.Empty).Label;
_infoLabel.AutoHeight = true;
var grid = playbackGroup.CustomContainer<UniformGridPanel>();
var gridControl = grid.CustomControl;
gridControl.ClipChildren = false;
gridControl.Height = Button.DefaultHeight;
gridControl.SlotsHorizontally = 3;
gridControl.SlotsVertically = 1;
grid.Button("Play").Button.Clicked += () => Foreach(x => x.Play());
grid.Button("Pause").Button.Clicked += () => Foreach(x => x.Pause());
grid.Button("Stop").Button.Clicked += () => Foreach(x => x.Stop());
}
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (_infoLabel != null)
{
var text = string.Empty;
foreach (var value in Values)
{
if (value is VideoPlayer player)
text += $"Time: {player.Time:##0.0}s / {player.Duration:##0.0}s\nResolution: {player.Size.X}x{player.Size.Y}, Frame Rate: {player.FrameRate}";
}
_infoLabel.Text = text;
}
}
private void Foreach(Action<VideoPlayer> func)
{
foreach (var value in Values)
{
if (value is VideoPlayer player)
func(player);
}
}
}
}

View File

@@ -368,11 +368,13 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Sample Global SDF",
Description = "Samples the Global SDF to get the distance to the closest surface (in world-space). Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.",
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Float2(200, 20),
Size = new Float2(200, 40),
DefaultValues = new object[] { 0 },
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Distance", typeof(float), 0),
NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Float3), 1),
NodeElementArchetype.Factory.Input(1, "Start Cascade", true, typeof(int), 2, 0),
}
},
new NodeArchetype
@@ -382,11 +384,13 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Samples the Global SDF to get the gradient and distance to the closest surface (in world-space). Normalize gradient to get SDF surface normal vector. Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.",
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Float2(260, 40),
DefaultValues = new object[] { 0 },
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Gradient", typeof(Float3), 0),
NodeElementArchetype.Factory.Output(1, "Distance", typeof(float), 2),
NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Float3), 1),
NodeElementArchetype.Factory.Input(1, "Start Cascade", true, typeof(int), 3, 0),
}
},
new NodeArchetype

View File

@@ -104,6 +104,11 @@ namespace FlaxEditor.Surface
}
}
internal static GroupElement InitGraphParametersGroup(LayoutElementsContainer layout)
{
return CustomEditors.Editors.GenericEditor.OnGroup(layout, "Parameters");
}
private sealed class DummyMaterialSurfaceOwner : IVisjectSurfaceOwner
{
public Asset SurfaceAsset => null;

View File

@@ -146,19 +146,14 @@ namespace FlaxEditor.Utilities
/// <summary>
/// Formats the amount of bytes to get a human-readable data size in bytes with abbreviation. Eg. 32 kB
/// [Deprecated in v1.9]
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <returns>The formatted amount of bytes.</returns>
[Obsolete("Use FormatBytesCount with ulong instead")]
public static string FormatBytesCount(int bytes)
{
int order = 0;
while (bytes >= 1024 && order < MemorySizePostfixes.Length - 1)
{
order++;
bytes /= 1024;
}
return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]);
return FormatBytesCount((ulong)bytes);
}
/// <summary>
@@ -169,12 +164,15 @@ namespace FlaxEditor.Utilities
public static string FormatBytesCount(ulong bytes)
{
int order = 0;
ulong bytesPrev = bytes;
while (bytes >= 1024 && order < MemorySizePostfixes.Length - 1)
{
bytesPrev = bytes;
order++;
bytes /= 1024;
}
if (order >= 3) // GB or higher use up to 2 decimal places for more precision
return string.Format("{0:0.##} {1}", FlaxEngine.Utils.RoundTo2DecimalPlaces(bytesPrev / 1024.0f), MemorySizePostfixes[order]);
return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]);
}
@@ -1471,5 +1469,27 @@ namespace FlaxEditor.Utilities
inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync());
inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile);
}
internal static string ToPathProject(string path)
{
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
internal static string ToPathAbsolute(string path)
{
if (path != null)
{
// Convert into global path to if relative to the project
path = StringUtils.IsRelative(path) ? Path.Combine(Globals.ProjectFolder, path) : path;
}
return path;
}
}
}

View File

@@ -21,6 +21,7 @@
#include "Engine/Level/Actors/Sky.h"
#include "Engine/Level/Actors/SkyLight.h"
#include "Engine/Level/Actors/SpotLight.h"
#include "Engine/Video/VideoPlayer.h"
#define ICON_RADIUS 7.0f
@@ -283,6 +284,7 @@ bool ViewportIconsRendererService::Init()
MAP_TYPE(Sky, Skybox);
MAP_TYPE(SkyLight, SkyLight);
MAP_TYPE(SpotLight, PointLight);
MAP_TYPE(VideoPlayer, SceneAnimationPlayer);
#undef MAP_TYPE
return false;

View File

@@ -193,7 +193,7 @@ namespace FlaxEditor.Windows.Assets
group.Label("Frames: " + info.FramesCount);
group.Label("Channels: " + info.ChannelsCount);
group.Label("Keyframes: " + info.KeyframesCount);
group.Label("Memory Usage: " + Utilities.Utils.FormatBytesCount(info.MemoryUsage));
group.Label("Memory Usage: " + Utilities.Utils.FormatBytesCount((ulong)info.MemoryUsage));
}
base.Initialize(layout);

View File

@@ -249,7 +249,7 @@ namespace FlaxEditor.Windows.Assets
if (parameters.Length == 0)
return;
var parametersGroup = layout.Group("Parameters");
var parametersGroup = SurfaceUtils.InitGraphParametersGroup(layout);
var settingButton = parametersGroup.AddSettingsButton();
settingButton.Clicked += (image, button) => OnSettingsButtonClicked(image, button, proxy.Window);
var baseMaterial = materialInstance.BaseMaterial;

View File

@@ -1,7 +1,9 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors;
@@ -11,6 +13,7 @@ using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using FlaxEngine.Tools;
using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
@@ -185,6 +188,7 @@ namespace FlaxEditor.Windows.Assets
// SDF
{
var group = layout.Group("SDF");
var sdfOptions = proxy.Window._sdfOptions;
var sdf = proxy.Asset.SDF;
if (sdf.Texture != null)
@@ -204,11 +208,26 @@ namespace FlaxEditor.Windows.Assets
resolution.ValueBox.BoxValueChanged += b => { proxy.Window._importSettings.Settings.SDFResolution = b.Value; };
proxy.Window._importSettings.Settings.SDFResolution = sdf.ResolutionScale;
var backfacesThreshold = group.FloatValue("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage).");
gpu.CheckBox.Checked = sdfOptions.GPU;
var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
var backfacesThreshold = backfacesThresholdProp.FloatValue();
var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last();
backfacesThreshold.ValueBox.MinValue = 0.001f;
backfacesThreshold.ValueBox.MaxValue = 1.0f;
backfacesThreshold.ValueBox.Value = proxy.Window._backfacesThreshold;
backfacesThreshold.ValueBox.BoxValueChanged += b => { proxy.Window._backfacesThreshold = b.Value; };
backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold;
backfacesThreshold.ValueBox.BoxValueChanged += b => { proxy.Window._sdfOptions.BackfacesThreshold = b.Value; };
// Toggle Backfaces Threshold visibility (CPU-only option)
gpu.CheckBox.StateChanged += c =>
{
proxy.Window._sdfOptions.GPU = c.Checked;
backfacesThresholdLabel.Visible = !c.Checked;
backfacesThreshold.ValueBox.Visible = !c.Checked;
};
backfacesThresholdLabel.Visible = !gpu.CheckBox.Checked;
backfacesThreshold.ValueBox.Visible = !gpu.CheckBox.Checked;
var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building.");
lodIndex.IntValue.MinValue = 0;
@@ -294,9 +313,22 @@ namespace FlaxEditor.Windows.Assets
private void OnRebuildSDF()
{
var proxy = (MeshesPropertiesProxy)Values[0];
proxy.Asset.GenerateSDF(proxy.Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, proxy.Window._backfacesThreshold);
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
proxy.Window.Enabled = false;
Task.Run(() =>
{
var sdfOptions = proxy.Window._sdfOptions;
bool failed = proxy.Asset.GenerateSDF(proxy.Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, sdfOptions.BackfacesThreshold, sdfOptions.GPU);
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
proxy.Window.Enabled = true;
if (!failed)
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
// Save some SDF options locally in the project cache
proxy.Window.Editor.ProjectCache.SetCustomData(JsonSerializer.GetStringID(proxy.Window.Item.ID) + ".SDF", JsonSerializer.Serialize(sdfOptions));
});
});
}
private void OnRemoveSDF()
@@ -774,17 +806,33 @@ namespace FlaxEditor.Windows.Assets
}
}
private struct ModelSdfOptions
{
public bool GPU;
public float BackfacesThreshold;
}
private readonly ModelPreview _preview;
private StaticModel _highlightActor;
private MeshDataCache _meshData;
private ModelImportSettings _importSettings = new ModelImportSettings();
private float _backfacesThreshold = 0.6f;
private ModelSdfOptions _sdfOptions;
private ToolStripButton _showCurrentLODButton;
/// <inheritdoc />
public ModelWindow(Editor editor, AssetItem item)
: base(editor, item)
{
// Try to restore SDF options from project cache (not saved in the asset)
if (Editor.ProjectCache.TryGetCustomData(JsonSerializer.GetStringID(Item.ID) + ".SDF", out string sdOptionsStr))
_sdfOptions = JsonSerializer.Deserialize<ModelSdfOptions>(sdOptionsStr);
else
_sdfOptions = new ModelSdfOptions
{
GPU = true,
BackfacesThreshold = 0.6f,
};
// Toolstrip
_toolstrip.AddSeparator();
_showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics");

View File

@@ -0,0 +1,234 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.Content;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Windows.Assets
{
/// <summary>
/// Editor window to view video media.
/// </summary>
public sealed class VideoWindow : EditorWindow, IContentItemOwner
{
private VideoItem _item;
private Image _frame;
private VideoPlayer _videoPlayer;
private Image _seekBegin, _seekEnd, _seekLeft, _seekRight, _playPause, _stop;
/// <inheritdoc />
public VideoWindow(Editor editor, VideoItem item)
: base(editor, false, ScrollBars.None)
{
_item = item;
_item.AddReference(this);
Title = _item.ShortName;
// Setup video player
_videoPlayer = new VideoPlayer
{
PlayOnStart = false,
Url = item.Path,
};
// Setup UI
var style = Style.Current;
var icons = Editor.Icons;
var playbackButtonsSize = 24.0f;
var playbackButtonsMouseOverColor = Color.FromBgra(0xFFBBBBBB);
_frame = new Image
{
Brush = new VideoBrush(_videoPlayer),
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0.0f, 0.0f, 0.0f, playbackButtonsSize),
Parent = this,
};
var playbackButtonsArea = new ContainerControl
{
AutoFocus = false,
ClipChildren = false,
BackgroundColor = style.LightBackground,
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
Offsets = new Margin(0, 0, -playbackButtonsSize, playbackButtonsSize),
Parent = this
};
var playbackButtonsPanel = new ContainerControl
{
AutoFocus = false,
ClipChildren = false,
AnchorPreset = AnchorPresets.VerticalStretchCenter,
Offsets = Margin.Zero,
Parent = playbackButtonsArea,
};
_seekBegin = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Rewind to timeline start (Home)",
Brush = new SpriteBrush(icons.Skip64),
MouseOverColor = playbackButtonsMouseOverColor,
Rotation = 180.0f,
Parent = playbackButtonsPanel
};
_seekBegin.Clicked += (image, button) => SeekBegin();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekLeft = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Move one frame back (Left Arrow)",
Brush = new SpriteBrush(icons.Left32),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekLeft.Clicked += (image, button) => SeekLeft();
playbackButtonsPanel.Width += playbackButtonsSize;
_stop = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Stop playback",
Brush = new SpriteBrush(icons.Stop64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_stop.Clicked += (image, button) => Stop();
playbackButtonsPanel.Width += playbackButtonsSize;
_playPause = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Play/pause playback (Space)",
Brush = new SpriteBrush(icons.Play64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_playPause.Clicked += (image, button) => PlayPause();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekRight = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Move one frame forward (Right Arrow)",
Brush = new SpriteBrush(icons.Right32),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekRight.Clicked += (image, button) => SeekRight();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekEnd = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Rewind to timeline end (End)",
Brush = new SpriteBrush(icons.Skip64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekEnd.Clicked += (image, button) => SeekEnd();
playbackButtonsPanel.Width += playbackButtonsSize;
playbackButtonsPanel.X = (playbackButtonsPanel.Parent.Width - playbackButtonsPanel.Width) * 0.5f;
}
private void PlayPause()
{
if (_videoPlayer.State == VideoPlayer.States.Playing)
_videoPlayer.Pause();
else
_videoPlayer.Play();
}
private void Stop()
{
_videoPlayer.Stop();
}
private void SeekBegin()
{
_videoPlayer.Time = 0.0f;
}
private void SeekEnd()
{
_videoPlayer.Time = _videoPlayer.Duration;
}
private void SeekLeft()
{
if (_videoPlayer.State == VideoPlayer.States.Paused)
_videoPlayer.Time -= 1.0f / _videoPlayer.FrameRate;
}
private void SeekRight()
{
if (_videoPlayer.State == VideoPlayer.States.Paused)
_videoPlayer.Time += 1.0f / _videoPlayer.FrameRate;
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (base.OnKeyDown(key))
return true;
switch (key)
{
case KeyboardKeys.ArrowLeft:
SeekLeft();
return true;
case KeyboardKeys.ArrowRight:
SeekRight();
return true;
case KeyboardKeys.Home:
SeekBegin();
return true;
case KeyboardKeys.End:
SeekEnd();
return true;
case KeyboardKeys.Spacebar:
PlayPause();
return true;
}
return false;
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
base.Update(deltaTime);
// Update UI
var state = _videoPlayer.State;
var icons = Editor.Icons;
_stop.Enabled = state != VideoPlayer.States.Stopped;
_seekLeft.Enabled = _seekRight.Enabled = state != VideoPlayer.States.Playing;
((SpriteBrush)_playPause.Brush).Sprite = state == VideoPlayer.States.Playing ? icons.Pause64 : icons.Play64;
}
/// <inheritdoc />
public override void OnDestroy()
{
if (IsDisposing)
return;
_videoPlayer.Stop();
Object.Destroy(ref _videoPlayer);
_item.RemoveReference(this);
_item = null;
base.OnDestroy();
}
/// <inheritdoc />
public void OnItemDeleted(ContentItem item)
{
if (item == _item)
Close();
}
/// <inheritdoc />
public void OnItemRenamed(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemReimported(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemDispose(ContentItem item)
{
if (item == _item)
Close();
}
}
}

View File

@@ -767,13 +767,6 @@ namespace FlaxEditor.Windows
Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Development,
},
new BuildTarget
{
Name = "Windows 32bit",
Output = "Output\\Win32",
Platform = BuildPlatform.Windows32,
Mode = BuildConfiguration.Development,
},
}
};
_data = presets;
@@ -793,9 +786,9 @@ namespace FlaxEditor.Windows
Array.Copy(_data[_selectedPresetIndex].Targets, targets, count);
targets[count] = new BuildTarget
{
Name = "Xbox One",
Output = "Output\\XboxOne",
Platform = BuildPlatform.XboxOne,
Name = "Windows 64bit",
Output = "Output\\Win64",
Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Development,
};
_data[_selectedPresetIndex].Targets = targets;

View File

@@ -95,14 +95,6 @@ namespace FlaxEditor.Windows
set => Graphics.ShadowMapsQuality = value;
}
[DefaultValue(false)]
[EditorOrder(1320), EditorDisplay("Quality", "Allow CSM Blending"), Tooltip("Enables cascades splits blending for directional light shadows.")]
public bool AllowCSMBlending
{
get => Graphics.AllowCSMBlending;
set => Graphics.AllowCSMBlending = value;
}
[NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)]
[EditorOrder(1400), EditorDisplay("Quality")]
[Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")]

View File

@@ -366,13 +366,13 @@ namespace FlaxEditor.Windows
}
var clonePath = Path.Combine(Globals.ProjectFolder, "Plugins", pluginName);
if (!Directory.Exists(clonePath))
Directory.CreateDirectory(clonePath);
else
if (Directory.Exists(clonePath))
{
Editor.LogError("Plugin Name is already used. Pick a different Name.");
return;
}
Directory.CreateDirectory(clonePath);
try
{
// Start git clone
@@ -384,7 +384,32 @@ namespace FlaxEditor.Windows
LogOutput = true,
WaitForEnd = true
};
Platform.CreateProcess(ref settings);
var asSubmodule = Directory.Exists(Path.Combine(Globals.ProjectFolder, ".git"));
if (asSubmodule)
{
// Clone as submodule to the existing repo
settings.Arguments = $"submodule add {gitPath} \"Plugins/{pluginName}\"";
// Submodule add need the target folder to not exist
Directory.Delete(clonePath);
}
int result = Platform.CreateProcess(ref settings);
if (result != 0)
throw new Exception($"'{settings.FileName} {settings.Arguments}' failed with result {result}");
// Ensure that cloned repo exists
var checkPath = Path.Combine(clonePath, ".git");
if (asSubmodule)
{
if (!File.Exists(checkPath))
throw new Exception("Failed to clone repo.");
}
else
{
if (!Directory.Exists(checkPath))
throw new Exception("Failed to clone repo.");
}
}
catch (Exception e)
{

View File

@@ -50,7 +50,9 @@ namespace FlaxEditor.Windows.Profiler
{
Title = "Assets Memory Usage (CPU)",
AnchorPreset = AnchorPresets.HorizontalStretchTop,
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
Offsets = Margin.Zero,
Height = SingleChart.DefaultHeight,
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = mainPanel,
};
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;

View File

@@ -69,6 +69,8 @@ namespace FlaxEditor.Windows.Profiler
{
Title = "Update",
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero,
Height = SingleChart.DefaultHeight,
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
Parent = mainPanel,
};
@@ -173,7 +175,7 @@ namespace FlaxEditor.Windows.Profiler
private string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount((int)x);
return Utilities.Utils.FormatBytesCount((ulong)x);
}
/// <inheritdoc />

View File

@@ -37,6 +37,8 @@ namespace FlaxEditor.Windows.Profiler
{
Title = "Draw (CPU)",
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero,
Height = SingleChart.DefaultHeight,
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
Parent = mainPanel,
};

View File

@@ -36,14 +36,14 @@ namespace FlaxEditor.Windows.Profiler
_nativeAllocationsChart = new SingleChart
{
Title = "Native Memory Allocation",
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = layout,
};
_nativeAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;
_managedAllocationsChart = new SingleChart
{
Title = "Managed Memory Allocation",
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = layout,
};
_managedAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;

View File

@@ -51,7 +51,9 @@ namespace FlaxEditor.Windows.Profiler
{
Title = "GPU Memory Usage",
AnchorPreset = AnchorPresets.HorizontalStretchTop,
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
Offsets = Margin.Zero,
Height = SingleChart.DefaultHeight,
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = mainPanel,
};
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;

View File

@@ -319,7 +319,7 @@ namespace FlaxEditor.Windows.Profiler
private static string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount((int)x);
return Utilities.Utils.FormatBytesCount((ulong)x);
}
private static int SortRows(Control x, Control y)

View File

@@ -92,7 +92,7 @@ namespace FlaxEditor.Windows.Profiler
Parent = this,
};
_liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64);
_liveRecordingButton.LinkTooltip("Live profiling events recording");
//_liveRecordingButton.LinkTooltip("Live profiling events recording");
_liveRecordingButton.AutoCheck = true;
_liveRecordingButton.Clicked += OnLiveRecordingChanged;
_clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear);

View File

@@ -12,6 +12,7 @@ namespace FlaxEditor.Windows.Profiler
/// <seealso cref="FlaxEngine.GUI.Control" />
internal class SingleChart : Control
{
internal const float DefaultHeight = TitleHeight + 60;
private const float TitleHeight = 20;
private const float PointsOffset = 4;
private readonly SamplesBuffer<float> _samples;
@@ -63,7 +64,7 @@ namespace FlaxEditor.Windows.Profiler
/// </summary>
/// <param name="maxSamples">The maximum samples to collect.</param>
public SingleChart(int maxSamples = ProfilerMode.MaxSamples)
: base(0, 0, 100, 60 + TitleHeight)
: base(0, 0, 100, DefaultHeight)
{
_samples = new SamplesBuffer<float>(maxSamples);
_sample = string.Empty;