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

# Conflicts:
#	Flax.flaxproj
This commit is contained in:
Wojtek Figat
2023-10-09 12:40:47 +02:00
86 changed files with 1406 additions and 686 deletions

View File

@@ -3,7 +3,7 @@
"Version": { "Version": {
"Major": 1, "Major": 1,
"Minor": 7, "Minor": 7,
"Build": 6400 "Build": 6401
}, },
"Company": "Flax", "Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.", "Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",

View File

@@ -520,8 +520,8 @@ namespace FlaxEditor.Content.GUI
{ {
int min = _selection.Min(x => x.IndexInParent); int min = _selection.Min(x => x.IndexInParent);
int max = _selection.Max(x => x.IndexInParent); int max = _selection.Max(x => x.IndexInParent);
min = Mathf.Min(min, item.IndexInParent); min = Mathf.Max(Mathf.Min(min, item.IndexInParent), 0);
max = Mathf.Max(max, item.IndexInParent); max = Mathf.Min(Mathf.Max(max, item.IndexInParent), _children.Count - 1);
var selection = new List<ContentItem>(_selection); var selection = new List<ContentItem>(_selection);
for (int i = min; i <= max; i++) for (int i = min; i <= max; i++)
{ {

View File

@@ -1,144 +1,52 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.ComponentModel; using System.Collections.Generic;
using System.Reflection; using FlaxEditor.CustomEditors.Editors;
using System.Runtime.InteropServices; using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.Interop; using FlaxEngine.Tools;
namespace FlaxEngine.Tools
{
partial class AudioTool
{
partial struct Options
{
private bool ShowBtiDepth => Format != AudioFormat.Vorbis;
}
}
}
namespace FlaxEditor.CustomEditors.Dedicated
{
/// <summary>
/// Custom editor for <see cref="FlaxEngine.Tools.AudioTool.Options"/>.
/// </summary>
[CustomEditor(typeof(FlaxEngine.Tools.AudioTool.Options)), DefaultEditor]
public class AudioToolOptionsEditor : GenericEditor
{
/// <inheritdoc />
protected override List<ItemInfo> GetItemsForType(ScriptType type)
{
// Show both fields and properties
return GetItemsForType(type, true, true);
}
}
}
namespace FlaxEditor.Content.Import namespace FlaxEditor.Content.Import
{ {
/// <summary> /// <summary>
/// Proxy object to present audio import settings in <see cref="ImportFilesDialog"/>. /// Proxy object to present audio import settings in <see cref="ImportFilesDialog"/>.
/// </summary> /// </summary>
[HideInEditor]
public class AudioImportSettings public class AudioImportSettings
{ {
/// <summary> /// <summary>
/// A custom set of bit depth audio import sizes. /// The settings data.
/// </summary> /// </summary>
public enum CustomBitDepth [EditorDisplay(null, EditorDisplayAttribute.InlineStyle)]
{ public AudioTool.Options Settings = AudioTool.Options.Default;
/// <summary>
/// The 8.
/// </summary>
_8 = 8,
/// <summary>
/// The 16.
/// </summary>
_16 = 16,
/// <summary>
/// The 24.
/// </summary>
_24 = 24,
/// <summary>
/// The 32.
/// </summary>
_32 = 32,
}
/// <summary>
/// Converts the bit depth to enum.
/// </summary>
/// <param name="f">The bit depth.</param>
/// <returns>The converted enum.</returns>
public static CustomBitDepth ConvertBitDepth(int f)
{
FieldInfo[] fields = typeof(CustomBitDepth).GetFields();
for (int i = 0; i < fields.Length; i++)
{
var field = fields[i];
if (field.Name.Equals("value__"))
continue;
if (f == (int)field.GetRawConstantValue())
return (CustomBitDepth)f;
}
return CustomBitDepth._16;
}
/// <summary>
/// The audio data format to import the audio clip as.
/// </summary>
[EditorOrder(10), DefaultValue(AudioFormat.Vorbis), Tooltip("The audio data format to import the audio clip as.")]
public AudioFormat Format { get; set; } = AudioFormat.Vorbis;
/// <summary>
/// The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.
/// </summary>
[EditorOrder(15), DefaultValue(0.4f), Limit(0, 1, 0.01f), Tooltip("The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.")]
public float CompressionQuality { get; set; } = 0.4f;
/// <summary>
/// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).
/// </summary>
[EditorOrder(20), DefaultValue(false), Tooltip("Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).")]
public bool DisableStreaming { get; set; } = false;
/// <summary>
/// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.
/// </summary>
[EditorOrder(30), DefaultValue(false), EditorDisplay(null, "Is 3D"), Tooltip("Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.")]
public bool Is3D { get; set; } = false;
/// <summary>
/// The size of a single sample in bits. The clip will be converted to this bit depth on import.
/// </summary>
[EditorOrder(40), DefaultValue(CustomBitDepth._16), Tooltip("The size of a single sample in bits. The clip will be converted to this bit depth on import.")]
public CustomBitDepth BitDepth { get; set; } = CustomBitDepth._16;
[StructLayout(LayoutKind.Sequential)]
internal struct InternalOptions
{
[MarshalAs(UnmanagedType.I1)]
public AudioFormat Format;
public byte DisableStreaming;
public byte Is3D;
public int BitDepth;
public float Quality;
}
internal void ToInternal(out InternalOptions options)
{
options = new InternalOptions
{
Format = Format,
DisableStreaming = (byte)(DisableStreaming ? 1 : 0),
Is3D = (byte)(Is3D ? 1 : 0),
Quality = CompressionQuality,
BitDepth = (int)BitDepth,
};
}
internal void FromInternal(ref InternalOptions options)
{
Format = options.Format;
DisableStreaming = options.DisableStreaming != 0;
Is3D = options.Is3D != 0;
CompressionQuality = options.Quality;
BitDepth = ConvertBitDepth(options.BitDepth);
}
/// <summary>
/// Tries the restore the asset import options from the target resource file.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="assetPath">The asset path.</param>
/// <returns>True settings has been restored, otherwise false.</returns>
public static bool TryRestore(ref AudioImportSettings options, string assetPath)
{
if (AudioImportEntry.Internal_GetAudioImportOptions(assetPath, out var internalOptions))
{
// Restore settings
options.FromInternal(ref internalOptions);
return true;
}
return false;
}
} }
/// <summary> /// <summary>
@@ -147,7 +55,7 @@ namespace FlaxEditor.Content.Import
/// <seealso cref="AssetImportEntry" /> /// <seealso cref="AssetImportEntry" />
public partial class AudioImportEntry : AssetImportEntry public partial class AudioImportEntry : AssetImportEntry
{ {
private AudioImportSettings _settings = new AudioImportSettings(); private AudioImportSettings _settings = new();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AudioImportEntry"/> class. /// Initializes a new instance of the <see cref="AudioImportEntry"/> class.
@@ -157,7 +65,7 @@ namespace FlaxEditor.Content.Import
: base(ref request) : base(ref request)
{ {
// Try to restore target asset Audio import options (useful for fast reimport) // Try to restore target asset Audio import options (useful for fast reimport)
AudioImportSettings.TryRestore(ref _settings, ResultUrl); Editor.TryRestoreImportOptions(ref _settings.Settings, ResultUrl);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -166,27 +74,23 @@ namespace FlaxEditor.Content.Import
/// <inheritdoc /> /// <inheritdoc />
public override bool TryOverrideSettings(object settings) public override bool TryOverrideSettings(object settings)
{ {
if (settings is AudioImportSettings o) if (settings is AudioImportSettings s)
{ {
_settings = o; _settings.Settings = s.Settings;
return true;
}
if (settings is AudioTool.Options o)
{
_settings.Settings = o;
return true; return true;
} }
return false; return false;
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Import() public override bool Import()
{ {
return Editor.Import(SourceUrl, ResultUrl, _settings); return Editor.Import(SourceUrl, ResultUrl, _settings.Settings);
} }
#region Internal Calls
[LibraryImport("FlaxEngine", EntryPoint = "AudioImportEntryInternal_GetAudioImportOptions", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_GetAudioImportOptions(string path, out AudioImportSettings.InternalOptions result);
#endregion
} }
} }

View File

@@ -54,12 +54,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request) public override bool CanDrawThumbnail(ThumbnailRequest request)
{ {
if (!_preview.HasLoadedAssets) return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Texture)request.Asset);
return false;
// Check if all mip maps are streamed
var asset = (CubeTexture)request.Asset;
return asset.ResidentMipLevels >= Mathf.Max(1, (int)(asset.MipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -62,7 +62,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request) public override bool CanDrawThumbnail(ThumbnailRequest request)
{ {
return _preview.HasLoadedAssets; return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -106,7 +106,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request) public override bool CanDrawThumbnail(ThumbnailRequest request)
{ {
return _preview.HasLoadedAssets; return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -82,12 +82,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request) public override bool CanDrawThumbnail(ThumbnailRequest request)
{ {
if (!_preview.HasLoadedAssets) return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Model)request.Asset);
return false;
// Check if asset is streamed enough
var asset = (Model)request.Asset;
return asset.LoadedLODs >= Mathf.Max(1, (int)(asset.LODs.Length * ThumbnailsModule.MinimumRequiredResourcesQuality));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -54,15 +54,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request) public override bool CanDrawThumbnail(ThumbnailRequest request)
{ {
if (!_preview.HasLoadedAssets) return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((SkinnedModel)request.Asset);
return false;
// Check if asset is streamed enough
var asset = (SkinnedModel)request.Asset;
var lods = asset.LODs.Length;
if (asset.IsLoaded && lods == 0)
return true; // Skeleton-only model
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * ThumbnailsModule.MinimumRequiredResourcesQuality));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -57,11 +57,7 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request) public override bool CanDrawThumbnail(ThumbnailRequest request)
{ {
// Check if asset is streamed enough return ThumbnailsModule.HasMinimumQuality((Texture)request.Asset);
var asset = (Texture)request.Asset;
var mipLevels = asset.MipLevels;
var minMipLevels = Mathf.Min(mipLevels, 7);
return asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * ThumbnailsModule.MinimumRequiredResourcesQuality));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -125,6 +125,74 @@ namespace FlaxEditor.Content.Thumbnails
} }
} }
internal static bool HasMinimumQuality(TextureBase asset)
{
var mipLevels = asset.MipLevels;
var minMipLevels = Mathf.Min(mipLevels, 7);
return asset.IsLoaded && asset.ResidentMipLevels >= Mathf.Max(minMipLevels, (int)(mipLevels * MinimumRequiredResourcesQuality));
}
internal static bool HasMinimumQuality(Model asset)
{
if (!asset.IsLoaded)
return false;
var lods = asset.LODs.Length;
var slots = asset.MaterialSlots;
foreach (var slot in slots)
{
if (slot.Material && !HasMinimumQuality(slot.Material))
return false;
}
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
}
internal static bool HasMinimumQuality(SkinnedModel asset)
{
var lods = asset.LODs.Length;
if (asset.IsLoaded && lods == 0)
return true; // Skeleton-only model
var slots = asset.MaterialSlots;
foreach (var slot in slots)
{
if (slot.Material && !HasMinimumQuality(slot.Material))
return false;
}
return asset.LoadedLODs >= Mathf.Max(1, (int)(lods * MinimumRequiredResourcesQuality));
}
internal static bool HasMinimumQuality(MaterialBase asset)
{
if (asset is MaterialInstance asInstance)
return HasMinimumQuality(asInstance);
return HasMinimumQualityInternal(asset);
}
internal static bool HasMinimumQuality(Material asset)
{
return HasMinimumQualityInternal(asset);
}
internal static bool HasMinimumQuality(MaterialInstance asset)
{
if (!HasMinimumQualityInternal(asset))
return false;
var baseMaterial = asset.BaseMaterial;
return baseMaterial == null || HasMinimumQualityInternal(baseMaterial);
}
private static bool HasMinimumQualityInternal(MaterialBase asset)
{
if (!asset.IsLoaded)
return false;
var parameters = asset.Parameters;
foreach (var parameter in parameters)
{
if (parameter.Value is TextureBase asTexture && !HasMinimumQuality(asTexture))
return false;
}
return true;
}
#region IContentItemOwner #region IContentItemOwner
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -8,7 +8,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling; using System.Runtime.InteropServices.Marshalling;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.Content.Settings; using FlaxEditor.Content.Settings;
using FlaxEditor.Content.Thumbnails; using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Modules; using FlaxEditor.Modules;
@@ -154,12 +153,12 @@ namespace FlaxEditor
public ContentFindingModule ContentFinding; public ContentFindingModule ContentFinding;
/// <summary> /// <summary>
/// The scripts editing /// The scripts editing.
/// </summary> /// </summary>
public CodeEditingModule CodeEditing; public CodeEditingModule CodeEditing;
/// <summary> /// <summary>
/// The scripts documentation /// The scripts documentation.
/// </summary> /// </summary>
public CodeDocsModule CodeDocs; public CodeDocsModule CodeDocs;
@@ -179,7 +178,7 @@ namespace FlaxEditor
public ProjectCacheModule ProjectCache; public ProjectCacheModule ProjectCache;
/// <summary> /// <summary>
/// The undo/redo /// The undo/redo.
/// </summary> /// </summary>
public EditorUndo Undo; public EditorUndo Undo;
@@ -939,21 +938,6 @@ namespace FlaxEditor
BehaviorTree = 12, BehaviorTree = 12,
} }
/// <summary>
/// Imports the audio asset file to the target location.
/// </summary>
/// <param name="inputPath">The source file path.</param>
/// <param name="outputPath">The result asset file path.</param>
/// <param name="settings">The settings.</param>
/// <returns>True if importing failed, otherwise false.</returns>
public static bool Import(string inputPath, string outputPath, AudioImportSettings settings)
{
if (settings == null)
throw new ArgumentNullException();
settings.ToInternal(out var internalOptions);
return Internal_ImportAudio(inputPath, outputPath, ref internalOptions);
}
/// <summary> /// <summary>
/// Serializes the given object to json asset. /// Serializes the given object to json asset.
/// </summary> /// </summary>
@@ -1671,10 +1655,6 @@ namespace FlaxEditor
[return: MarshalAs(UnmanagedType.U1)] [return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId); internal static partial bool Internal_CloneAssetFile(string dstPath, string srcPath, ref Guid dstId);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_ImportAudio", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_ImportAudio(string inputPath, string outputPath, ref AudioImportSettings.InternalOptions options);
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAudioClipMetadata", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAudioClipMetadata", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize); internal static partial void Internal_GetAudioClipMetadata(IntPtr obj, out int originalSize, out int importedSize);

View File

@@ -125,6 +125,8 @@ namespace FlaxEditor.GUI.Timeline.Tracks
fov = cam.FieldOfView; fov = cam.FieldOfView;
customAspectRatio = cam.CustomAspectRatio; customAspectRatio = cam.CustomAspectRatio;
view.RenderLayersMask = cam.RenderLayersMask; view.RenderLayersMask = cam.RenderLayersMask;
view.Flags = cam.RenderFlags;
view.Mode = cam.RenderMode;
} }
// Try to evaluate camera properties based on the animated tracks // Try to evaluate camera properties based on the animated tracks

View File

@@ -50,43 +50,6 @@
Guid ManagedEditor::ObjectID(0x91970b4e, 0x99634f61, 0x84723632, 0x54c776af); Guid ManagedEditor::ObjectID(0x91970b4e, 0x99634f61, 0x84723632, 0x54c776af);
// Disable warning C4800: 'const byte': forcing value to bool 'true' or 'false' (performance warning)
#if defined(_MSC_VER)
#pragma warning( push )
#pragma warning( disable : 4800)
#endif
struct InternalAudioOptions
{
AudioFormat Format;
byte DisableStreaming;
byte Is3D;
int32 BitDepth;
float Quality;
static void Convert(InternalAudioOptions* from, ImportAudio::Options* to)
{
to->Format = from->Format;
to->DisableStreaming = from->DisableStreaming;
to->Is3D = from->Is3D;
to->BitDepth = from->BitDepth;
to->Quality = from->Quality;
}
static void Convert(ImportAudio::Options* from, InternalAudioOptions* to)
{
to->Format = from->Format;
to->DisableStreaming = from->DisableStreaming;
to->Is3D = from->Is3D;
to->BitDepth = from->BitDepth;
to->Quality = from->Quality;
}
};
#if defined(_MSC_VER)
#pragma warning( pop )
#endif
// Pack log messages into a single scratch buffer to reduce dynamic memory allocations // Pack log messages into a single scratch buffer to reduce dynamic memory allocations
CriticalSection CachedLogDataLocker; CriticalSection CachedLogDataLocker;
Array<byte> CachedLogData; Array<byte> CachedLogData;
@@ -299,16 +262,6 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_CanImport(MString* extensionObj)
return importer ? MUtils::ToString(importer->ResultExtension) : nullptr; return importer ? MUtils::ToString(importer->ResultExtension) : nullptr;
} }
DEFINE_INTERNAL_CALL(bool) EditorInternal_ImportAudio(MString* inputPathObj, MString* outputPathObj, InternalAudioOptions* optionsObj)
{
ImportAudio::Options options;
InternalAudioOptions::Convert(optionsObj, &options);
String inputPath, outputPath;
MUtils::ToString(inputPathObj, inputPath);
MUtils::ToString(outputPathObj, outputPath);
return ManagedEditor::Import(inputPath, outputPath, &options);
}
DEFINE_INTERNAL_CALL(void) EditorInternal_GetAudioClipMetadata(AudioClip* clip, int32* originalSize, int32* importedSize) DEFINE_INTERNAL_CALL(void) EditorInternal_GetAudioClipMetadata(AudioClip* clip, int32* originalSize, int32* importedSize)
{ {
INTERNAL_CALL_CHECK(clip); INTERNAL_CALL_CHECK(clip);
@@ -769,24 +722,6 @@ DEFINE_INTERNAL_CALL(MTypeObject*) CustomEditorsUtilInternal_GetCustomEditor(MTy
return CustomEditorsUtil::GetCustomEditor(targetType); return CustomEditorsUtil::GetCustomEditor(targetType);
} }
DEFINE_INTERNAL_CALL(bool) AudioImportEntryInternal_GetAudioImportOptions(MString* pathObj, InternalAudioOptions* result)
{
String path;
MUtils::ToString(pathObj, path);
FileSystem::NormalizePath(path);
ImportAudio::Options options;
if (ImportAudio::TryGetImportOptions(path, options))
{
// Convert into managed storage
InternalAudioOptions::Convert(&options, result);
return true;
}
return false;
}
DEFINE_INTERNAL_CALL(MArray*) LayersAndTagsSettingsInternal_GetCurrentLayers(int* layersCount) DEFINE_INTERNAL_CALL(MArray*) LayersAndTagsSettingsInternal_GetCurrentLayers(int* layersCount)
{ {
*layersCount = Math::Max(1, Level::GetNonEmptyLayerNamesCount()); *layersCount = Math::Max(1, Level::GetNonEmptyLayerNamesCount());
@@ -834,3 +769,15 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String
FileSystem::NormalizePath(assetPath); FileSystem::NormalizePath(assetPath);
return ImportModelFile::TryGetImportOptions(assetPath, options); return ImportModelFile::TryGetImportOptions(assetPath, options);
} }
bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options)
{
return Import(inputPath, outputPath, (void*)&options);
}
bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath)
{
// Get options from model
FileSystem::NormalizePath(assetPath);
return ImportAudio::TryGetImportOptions(assetPath, options);
}

View File

@@ -7,6 +7,7 @@
#include "Engine/ShadowsOfMordor/Types.h" #include "Engine/ShadowsOfMordor/Types.h"
#include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Tools/ModelTool/ModelTool.h" #include "Engine/Tools/ModelTool/ModelTool.h"
#include "Engine/Tools/AudioTool/AudioTool.h"
namespace CSG namespace CSG
{ {
@@ -190,6 +191,25 @@ public:
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) ModelTool::Options& options, String assetPath); API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) ModelTool::Options& options, String assetPath);
#endif #endif
#if COMPILE_WITH_AUDIO_TOOL
/// <summary>
/// Imports the audio asset file to the target location.
/// </summary>
/// <param name="inputPath">The source file path.</param>
/// <param name="outputPath">The result asset file path.</param>
/// <param name="options">The import settings.</param>
/// <returns>True if importing failed, otherwise false.</returns>
API_FUNCTION() static bool Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options);
/// <summary>
/// Tries the restore the asset import options from the target resource file.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="assetPath">The asset path.</param>
/// <returns>True settings has been restored, otherwise false.</returns>
API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath);
#endif
private: private:
void OnEditorAssemblyLoaded(MAssembly* assembly); void OnEditorAssemblyLoaded(MAssembly* assembly);

View File

@@ -37,11 +37,6 @@ namespace FlaxEditor.Modules
/// </summary> /// </summary>
public event Action<Prefab, Actor> PrefabApplied; public event Action<Prefab, Actor> PrefabApplied;
/// <summary>
/// Locally cached actor for prefab creation.
/// </summary>
private Actor _prefabCreationActor;
internal PrefabsModule(Editor editor) internal PrefabsModule(Editor editor)
: base(editor) : base(editor)
{ {
@@ -65,13 +60,14 @@ namespace FlaxEditor.Modules
/// To create prefab manually (from code) use <see cref="PrefabManager.CreatePrefab"/> method. /// To create prefab manually (from code) use <see cref="PrefabManager.CreatePrefab"/> method.
/// </remarks> /// </remarks>
/// <param name="selection">The scene selection to use.</param> /// <param name="selection">The scene selection to use.</param>
public void CreatePrefab(List<SceneGraphNode> selection) /// <param name="prefabWindow">The prefab window that creates it.</param>
public void CreatePrefab(List<SceneGraphNode> selection, Windows.Assets.PrefabWindow prefabWindow = null)
{ {
if (selection == null) if (selection == null)
selection = Editor.SceneEditing.Selection; selection = Editor.SceneEditing.Selection;
if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.CanCreatePrefab) if (selection.Count == 1 && selection[0] is ActorNode actorNode && actorNode.CanCreatePrefab)
{ {
CreatePrefab(actorNode.Actor); CreatePrefab(actorNode.Actor, true, prefabWindow);
} }
} }
@@ -92,7 +88,8 @@ namespace FlaxEditor.Modules
/// </summary> /// </summary>
/// <param name="actor">The root prefab actor.</param> /// <param name="actor">The root prefab actor.</param>
/// <param name="rename">Allow renaming or not</param> /// <param name="rename">Allow renaming or not</param>
public void CreatePrefab(Actor actor, bool rename) /// <param name="prefabWindow">The prefab window that creates it.</param>
public void CreatePrefab(Actor actor, bool rename, Windows.Assets.PrefabWindow prefabWindow = null)
{ {
// Skip in invalid states // Skip in invalid states
if (!Editor.StateMachine.CurrentState.CanEditContent) if (!Editor.StateMachine.CurrentState.CanEditContent)
@@ -105,42 +102,47 @@ namespace FlaxEditor.Modules
PrefabCreating?.Invoke(actor); PrefabCreating?.Invoke(actor);
var proxy = Editor.ContentDatabase.GetProxy<Prefab>(); var proxy = Editor.ContentDatabase.GetProxy<Prefab>();
_prefabCreationActor = actor; Editor.Windows.ContentWin.NewItem(proxy, actor, contentItem => OnPrefabCreated(contentItem, actor, prefabWindow), actor.Name, rename);
Editor.Windows.ContentWin.NewItem(proxy, actor, OnPrefabCreated, actor.Name, rename);
} }
private void OnPrefabCreated(ContentItem contentItem) private void OnPrefabCreated(ContentItem contentItem, Actor actor, Windows.Assets.PrefabWindow prefabWindow)
{ {
if (contentItem is PrefabItem prefabItem) if (contentItem is PrefabItem prefabItem)
{ {
PrefabCreated?.Invoke(prefabItem); PrefabCreated?.Invoke(prefabItem);
} }
// Skip in invalid states Undo undo = null;
if (!Editor.StateMachine.CurrentState.CanEditScene) if (prefabWindow != null)
return; {
prefabWindow.MarkAsEdited();
undo = prefabWindow.Undo;
}
else
{
// Skip in invalid states
if (!Editor.StateMachine.CurrentState.CanEditScene)
return;
undo = Editor.Undo;
}
// Record undo for prefab creating (backend links the target instance with the prefab) // Record undo for prefab creating (backend links the target instance with the prefab)
if (Editor.Undo.Enabled) if (undo.Enabled)
{ {
if (!_prefabCreationActor) if (!actor)
return; return;
var actorsList = new List<Actor>(); var actorsList = new List<Actor>();
Utilities.Utils.GetActorsTree(actorsList, _prefabCreationActor); Utilities.Utils.GetActorsTree(actorsList, actor);
var actions = new IUndoAction[actorsList.Count]; var actions = new IUndoAction[actorsList.Count];
for (int i = 0; i < actorsList.Count; i++) for (int i = 0; i < actorsList.Count; i++)
{ actions[i] = BreakPrefabLinkAction.Linked(actorsList[i]);
var action = BreakPrefabLinkAction.Linked(actorsList[i]); undo.AddAction(new MultiUndoAction(actions));
actions[i] = action;
}
Undo.AddAction(new MultiUndoAction(actions));
_prefabCreationActor = null;
} }
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); Editor.Windows.PropertiesWin.Presenter.BuildLayout();
if (prefabWindow != null)
prefabWindow.Presenter.BuildLayout();
} }
/// <summary> /// <summary>

View File

@@ -2,8 +2,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using FlaxEditor.Options; using FlaxEditor.Options;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
@@ -330,6 +332,78 @@ namespace FlaxEditor.Modules.SourceCodeEditing
Editor.Instance.CodeEditing.SelectedEditor = editor; Editor.Instance.CodeEditing.SelectedEditor = editor;
} }
/// <summary>
/// Starts creating a new module
/// </summary>
internal void CreateModule(string path, string moduleName, bool editorModule, bool cpp)
{
if (string.IsNullOrEmpty(moduleName) || string.IsNullOrEmpty(path))
{
Editor.LogWarning("Failed to create module due to no name");
return;
}
// Create folder
var moduleFolderPath = Path.Combine(path, moduleName);
Directory.CreateDirectory(moduleFolderPath);
// Create module
var moduleText = "using Flax.Build;\n" +
"using Flax.Build.NativeCpp;\n" +
$"\npublic class {moduleName} : Game{(editorModule ? "Editor" : "")}Module\n" +
"{\n " +
"/// <inheritdoc />\n" +
" public override void Init()\n" +
" {\n" +
" base.Init();\n" +
"\n" +
" // C#-only scripting if false\n" +
$" BuildNativeCode = {(cpp ? "true" : "false")};\n" +
" }\n" +
"\n" +
" /// <inheritdoc />\n" +
" public override void Setup(BuildOptions options)\n" +
" {" +
"\n" +
" base.Setup(options);\n" +
"\n" +
" options.ScriptingAPI.IgnoreMissingDocumentationWarnings = true;\n" +
"\n" +
" // Here you can modify the build options for your game module\n" +
" // To reference another module use: options.PublicDependencies.Add(\"Audio\");\n" +
" // To add C++ define use: options.PublicDefinitions.Add(\"COMPILE_WITH_FLAX\");\n" +
" // To learn more see scripting documentation.\n" +
" }\n" +
"}";
moduleText = Encoding.UTF8.GetString(Encoding.Default.GetBytes(moduleText));
var modulePath = Path.Combine(moduleFolderPath, $"{moduleName}.Build.cs");
File.WriteAllText(modulePath, moduleText);
Editor.Log($"Module created at {modulePath}");
// Get editor target and target files and add module
var files = Directory.GetFiles(path);
var targetModuleText = $"Modules.Add(\"{moduleName}\");\n ";
foreach (var file in files)
{
if (!file.Contains(".Build.cs", StringComparison.OrdinalIgnoreCase))
continue;
var targetText = File.ReadAllText(file);
// Skip game project if it is suppose to be an editor module
if (editorModule && targetText.Contains("GameProjectTarget", StringComparison.Ordinal))
continue;
// TODO: Handle edge case when there are no modules in a target
var index = targetText.IndexOf("Modules.Add");
if (index != -1)
{
var newText = targetText.Insert(index, targetModuleText);
File.WriteAllText(file, newText);
Editor.Log($"Module added to Target: {file}");
}
}
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnUpdate() public override void OnUpdate()
{ {

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors namespace FlaxEditor.SceneGraph.Actors
@@ -22,9 +23,9 @@ namespace FlaxEditor.SceneGraph.Actors
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu) public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{ {
base.OnContextMenu(contextMenu); base.OnContextMenu(contextMenu, window);
var actor = (AnimatedModel)Actor; var actor = (AnimatedModel)Actor;
if (actor && actor.SkinnedModel) if (actor && actor.SkinnedModel)

View File

@@ -6,6 +6,8 @@ using Real = System.Double;
using Real = System.Single; using Real = System.Single;
#endif #endif
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors namespace FlaxEditor.SceneGraph.Actors
@@ -23,6 +25,25 @@ namespace FlaxEditor.SceneGraph.Actors
{ {
} }
/// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{
base.OnContextMenu(contextMenu, window);
if (window is not SceneTreeWindow win)
return;
var button = new ContextMenuButton(contextMenu, "Move Camera to View");
button.Parent = contextMenu.ItemsContainer;
contextMenu.ItemsContainer.Children.Remove(button);
contextMenu.ItemsContainer.Children.Insert(4, button);
button.Clicked += () =>
{
var c = Actor as Camera;
var viewport = Editor.Instance.Windows.EditWin.Viewport;
c.Position = viewport.ViewPosition;
c.Orientation = viewport.ViewOrientation;
};
}
/// <inheritdoc /> /// <inheritdoc />
public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal) public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal)
{ {

View File

@@ -3,6 +3,7 @@
using System.IO; using System.IO;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.SceneGraph.GUI; using FlaxEditor.SceneGraph.GUI;
using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors namespace FlaxEditor.SceneGraph.Actors
@@ -65,7 +66,7 @@ namespace FlaxEditor.SceneGraph.Actors
public override SceneNode ParentScene => this; public override SceneNode ParentScene => this;
/// <inheritdoc /> /// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu) public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{ {
contextMenu.AddSeparator(); contextMenu.AddSeparator();
var path = Scene.Path; var path = Scene.Path;
@@ -80,7 +81,7 @@ namespace FlaxEditor.SceneGraph.Actors
if (Level.ScenesCount > 1) if (Level.ScenesCount > 1)
contextMenu.AddButton("Unload all but this scene", OnUnloadAllButSelectedScene).LinkTooltip("Unloads all of the active scenes except for the selected scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene; contextMenu.AddButton("Unload all but this scene", OnUnloadAllButSelectedScene).LinkTooltip("Unloads all of the active scenes except for the selected scene.").Enabled = Editor.Instance.StateMachine.CurrentState.CanChangeScene;
base.OnContextMenu(contextMenu); base.OnContextMenu(contextMenu, window);
} }
private void OnSelect() private void OnSelect()

View File

@@ -9,6 +9,7 @@ using Real = System.Single;
using System; using System;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Modules; using FlaxEditor.Modules;
using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.Json; using FlaxEngine.Json;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
@@ -203,9 +204,9 @@ namespace FlaxEditor.SceneGraph.Actors
} }
} }
public override void OnContextMenu(ContextMenu contextMenu) public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{ {
ParentNode.OnContextMenu(contextMenu); ParentNode.OnContextMenu(contextMenu, window);
} }
public static SceneGraphNode Create(StateData state) public static SceneGraphNode Create(StateData state)
@@ -272,9 +273,9 @@ namespace FlaxEditor.SceneGraph.Actors
DebugDraw.DrawSphere(new BoundingSphere(pos, tangentSize), Color.YellowGreen, 0, false); DebugDraw.DrawSphere(new BoundingSphere(pos, tangentSize), Color.YellowGreen, 0, false);
} }
public override void OnContextMenu(ContextMenu contextMenu) public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{ {
ParentNode.OnContextMenu(contextMenu); ParentNode.OnContextMenu(contextMenu, window);
} }
public override void OnDispose() public override void OnDispose()
@@ -354,9 +355,9 @@ namespace FlaxEditor.SceneGraph.Actors
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu) public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{ {
base.OnContextMenu(contextMenu); base.OnContextMenu(contextMenu, window);
contextMenu.AddButton("Add spline model", OnAddSplineModel); contextMenu.AddButton("Add spline model", OnAddSplineModel);
contextMenu.AddButton("Add spline collider", OnAddSplineCollider); contextMenu.AddButton("Add spline collider", OnAddSplineCollider);

View File

@@ -3,6 +3,7 @@
using System; using System;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.SceneGraph.Actors namespace FlaxEditor.SceneGraph.Actors
@@ -21,9 +22,9 @@ namespace FlaxEditor.SceneGraph.Actors
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnContextMenu(ContextMenu contextMenu) public override void OnContextMenu(ContextMenu contextMenu, EditorWindow window)
{ {
base.OnContextMenu(contextMenu); base.OnContextMenu(contextMenu, window);
contextMenu.AddButton("Add collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null; contextMenu.AddButton("Add collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null;
} }

View File

@@ -599,7 +599,7 @@ namespace FlaxEditor.SceneGraph.GUI
// Drag scripts // Drag scripts
else if (_dragScripts != null && _dragScripts.HasValidDrag) else if (_dragScripts != null && _dragScripts.HasValidDrag)
{ {
foreach(var script in _dragScripts.Objects) foreach (var script in _dragScripts.Objects)
{ {
var customAction = script.HasPrefabLink ? new ReparentAction(script) : null; var customAction = script.HasPrefabLink ? new ReparentAction(script) : null;
using (new UndoBlock(ActorNode.Root.Undo, script, "Change script parent", customAction)) using (new UndoBlock(ActorNode.Root.Undo, script, "Change script parent", customAction))
@@ -616,7 +616,7 @@ namespace FlaxEditor.SceneGraph.GUI
var spawnParent = myActor; var spawnParent = myActor;
if (DragOverMode == DragItemPositioning.Above || DragOverMode == DragItemPositioning.Below) if (DragOverMode == DragItemPositioning.Above || DragOverMode == DragItemPositioning.Below)
spawnParent = newParent; spawnParent = newParent;
for (int i = 0; i < _dragAssets.Objects.Count; i++) for (int i = 0; i < _dragAssets.Objects.Count; i++)
{ {
var item = _dragAssets.Objects[i]; var item = _dragAssets.Objects[i];
@@ -720,7 +720,7 @@ namespace FlaxEditor.SceneGraph.GUI
for (var i = 0; i < tree.Selection.Count; i++) for (var i = 0; i < tree.Selection.Count; i++)
{ {
var e = tree.Selection[i]; var e = tree.Selection[i];
// Skip if parent is already selected to keep correct parenting // Skip if parent is already selected to keep correct parenting
if (tree.Selection.Contains(e.Parent)) if (tree.Selection.Contains(e.Parent))
continue; continue;

View File

@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.Modules; using FlaxEditor.Modules;
using FlaxEditor.SceneGraph.Actors; using FlaxEditor.SceneGraph.Actors;
using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.SceneGraph namespace FlaxEditor.SceneGraph
@@ -339,7 +340,7 @@ namespace FlaxEditor.SceneGraph
/// <summary> /// <summary>
/// Called when scene tree window wants to show the context menu. Allows to add custom options. /// Called when scene tree window wants to show the context menu. Allows to add custom options.
/// </summary> /// </summary>
public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu) public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu, EditorWindow window)
{ {
} }

View File

@@ -745,12 +745,12 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy(); base.OnDestroy();
} }
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
return false; return false;
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
return inputType.IsVoid; return inputType.IsVoid;
} }
@@ -1162,7 +1162,7 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy(); base.OnDestroy();
} }
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
if (nodeArch.Tag is not ScriptMemberInfo memberInfo) if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
return false; return false;
@@ -1188,7 +1188,7 @@ namespace FlaxEditor.Surface.Archetypes
return false; return false;
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
if (nodeArch.Tag is not ScriptMemberInfo memberInfo) if (nodeArch.Tag is not ScriptMemberInfo memberInfo)
return false; return false;
@@ -1836,12 +1836,12 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy(); base.OnDestroy();
} }
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
return false; return false;
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
return inputType.IsVoid; return inputType.IsVoid;
} }
@@ -1982,7 +1982,7 @@ namespace FlaxEditor.Surface.Archetypes
UpdateSignature(); UpdateSignature();
} }
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
if (scriptType == ScriptType.Null) if (scriptType == ScriptType.Null)
@@ -2011,7 +2011,7 @@ namespace FlaxEditor.Surface.Archetypes
return false; return false;
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]); var scriptType = TypeUtils.GetType((string)nodeArch.DefaultValues[0]);
if (scriptType == ScriptType.Null) if (scriptType == ScriptType.Null)
@@ -2093,7 +2093,7 @@ namespace FlaxEditor.Surface.Archetypes
UpdateSignature(); UpdateSignature();
} }
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
if (outputType.IsVoid) if (outputType.IsVoid)
return true; return true;
@@ -2130,7 +2130,7 @@ namespace FlaxEditor.Surface.Archetypes
return false; return false;
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
return inputType.IsVoid; return inputType.IsVoid;
} }
@@ -2353,7 +2353,7 @@ namespace FlaxEditor.Surface.Archetypes
base.OnDestroy(); base.OnDestroy();
} }
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
// Event based nodes always have a pulse input, so it's always compatible with void // Event based nodes always have a pulse input, so it's always compatible with void
if (outputType.IsVoid) if (outputType.IsVoid)
@@ -2373,7 +2373,7 @@ namespace FlaxEditor.Surface.Archetypes
return false; return false;
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
// Event based nodes always have a pulse output, so it's always compatible with void // Event based nodes always have a pulse output, so it's always compatible with void
if (inputType.IsVoid) if (inputType.IsVoid)

View File

@@ -217,7 +217,7 @@ namespace FlaxEditor.Surface.Archetypes
{ {
} }
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
var typeName = (string)nodeArch.DefaultValues[0]; var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName); var type = TypeUtils.GetType(typeName);
@@ -234,7 +234,7 @@ namespace FlaxEditor.Surface.Archetypes
return false; return false;
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
var typeName = (string)nodeArch.DefaultValues[0]; var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName); var type = TypeUtils.GetType(typeName);
@@ -255,7 +255,7 @@ namespace FlaxEditor.Surface.Archetypes
{ {
} }
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint) internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
var typeName = (string)nodeArch.DefaultValues[0]; var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName); var type = TypeUtils.GetType(typeName);
@@ -267,7 +267,7 @@ namespace FlaxEditor.Surface.Archetypes
return false; return false;
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint) internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{ {
var typeName = (string)nodeArch.DefaultValues[0]; var typeName = (string)nodeArch.DefaultValues[0];
var type = TypeUtils.GetType(typeName); var type = TypeUtils.GetType(typeName);

View File

@@ -490,6 +490,56 @@ namespace FlaxEditor.Surface.Archetypes
_combobox.Width = Width - 30; _combobox.Width = Width - 30;
} }
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (inputType == ScriptType.Object)
return true;
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
ScriptType type = parameter?.Type ?? ScriptType.Null;
if (parameter == null || type == ScriptType.Null || parameter.Type.Type == null)
return false;
if (DefaultPrototypes == null || !DefaultPrototypes.TryGetValue(parameter.Type.Type, out var elements))
{
return VisjectSurface.FullCastCheck(inputType, type, hint);
}
if (elements == null)
return false;
for (var i = 0; i < elements.Length; i++)
{
if(elements[i].Type != NodeElementType.Output)
continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
return true;
}
return false;
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
ScriptType type = parameter?.Type ?? ScriptType.Null;
if (parameter == null || type == ScriptType.Null)
return false;
if (parameter.Type.Type == null || DefaultPrototypes == null || !DefaultPrototypes.TryGetValue(parameter.Type.Type, out var elements))
return false;
if (elements == null)
return false;
for (var i = 0; i < elements.Length; i++)
{
if(elements[i].Type != NodeElementType.Input)
continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
return true;
}
return false;
}
} }
/// <summary> /// <summary>
@@ -675,6 +725,53 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc /> /// <inheritdoc />
protected override bool UseNormalMaps => false; protected override bool UseNormalMaps => false;
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (inputType == ScriptType.Object)
return true;
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
ScriptType type = parameter?.Type ?? ScriptType.Null;
if (parameter == null || type == ScriptType.Null)
return false;
if (parameter.Type.Type == null || DefaultPrototypesParticleEmitter == null || !DefaultPrototypesParticleEmitter.TryGetValue(parameter.Type.Type, out var elements))
return false;
if (elements == null)
return false;
for (var i = 0; i < elements.Length; i++)
{
if(elements[i].Type != NodeElementType.Output)
continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, inputType, hint))
return true;
}
return false;
}
internal new static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
ScriptType type = parameter?.Type ?? ScriptType.Null;
if (parameter == null || type == ScriptType.Null)
return false;
if (parameter.Type.Type == null || DefaultPrototypesParticleEmitter == null || !DefaultPrototypesParticleEmitter.TryGetValue(parameter.Type.Type, out var elements))
return false;
if (elements == null)
return false;
for (var i = 0; i < elements.Length; i++)
{
if(elements[i].Type != NodeElementType.Input)
continue;
if (VisjectSurface.FullCastCheck(elements[i].ConnectionsType, outputType, hint))
return true;
}
return false;
}
} }
/// <summary> /// <summary>
@@ -692,6 +789,22 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc /> /// <inheritdoc />
protected override bool UseNormalMaps => false; protected override bool UseNormalMaps => false;
internal new static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (inputType == ScriptType.Object)
return true;
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
ScriptType type = parameter?.Type ?? ScriptType.Null;
return VisjectSurface.FullCastCheck(inputType, type, hint);
}
internal new static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
return false;
}
} }
/// <summary> /// <summary>
@@ -874,6 +987,22 @@ namespace FlaxEditor.Surface.Archetypes
_combobox.Width = Width - 50; _combobox.Width = Width - 50;
} }
} }
internal static bool IsOutputCompatible(NodeArchetype nodeArch, ScriptType inputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
return inputType == ScriptType.Void;
}
internal static bool IsInputCompatible(NodeArchetype nodeArch, ScriptType outputType, ConnectionsHint hint, VisjectSurfaceContext context)
{
if (outputType == ScriptType.Void)
return true;
SurfaceParameter parameter = context.GetParameter((Guid)nodeArch.DefaultValues[0]);
ScriptType type = parameter?.Type ?? ScriptType.Null;
return VisjectSurface.FullCastCheck(outputType, type, hint);
}
} }
/// <summary> /// <summary>
@@ -885,6 +1014,8 @@ namespace FlaxEditor.Surface.Archetypes
{ {
TypeID = 1, TypeID = 1,
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGet(id, context, arch, groupArch), Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGet(id, context, arch, groupArch),
IsInputCompatible = SurfaceNodeParamsGet.IsInputCompatible,
IsOutputCompatible = SurfaceNodeParamsGet.IsOutputCompatible,
Title = "Get Parameter", Title = "Get Parameter",
Description = "Parameter value getter", Description = "Parameter value getter",
Flags = NodeFlags.MaterialGraph | NodeFlags.AnimGraph, Flags = NodeFlags.MaterialGraph | NodeFlags.AnimGraph,
@@ -902,6 +1033,8 @@ namespace FlaxEditor.Surface.Archetypes
{ {
TypeID = 2, TypeID = 2,
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGetParticleEmitter(id, context, arch, groupArch), Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGetParticleEmitter(id, context, arch, groupArch),
IsInputCompatible = SurfaceNodeParamsGetParticleEmitter.IsInputCompatible,
IsOutputCompatible = SurfaceNodeParamsGetParticleEmitter.IsOutputCompatible,
Title = "Get Parameter", Title = "Get Parameter",
Description = "Parameter value getter", Description = "Parameter value getter",
Flags = NodeFlags.ParticleEmitterGraph, Flags = NodeFlags.ParticleEmitterGraph,
@@ -919,6 +1052,8 @@ namespace FlaxEditor.Surface.Archetypes
{ {
TypeID = 3, TypeID = 3,
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGetVisualScript(id, context, arch, groupArch), Create = (id, context, arch, groupArch) => new SurfaceNodeParamsGetVisualScript(id, context, arch, groupArch),
IsInputCompatible = SurfaceNodeParamsGetVisualScript.IsInputCompatible,
IsOutputCompatible = SurfaceNodeParamsGetVisualScript.IsOutputCompatible,
Title = "Get Parameter", Title = "Get Parameter",
Description = "Parameter value getter", Description = "Parameter value getter",
Flags = NodeFlags.VisualScriptGraph, Flags = NodeFlags.VisualScriptGraph,
@@ -936,6 +1071,8 @@ namespace FlaxEditor.Surface.Archetypes
{ {
TypeID = 4, TypeID = 4,
Create = (id, context, arch, groupArch) => new SurfaceNodeParamsSet(id, context, arch, groupArch), Create = (id, context, arch, groupArch) => new SurfaceNodeParamsSet(id, context, arch, groupArch),
IsInputCompatible = SurfaceNodeParamsSet.IsInputCompatible,
IsOutputCompatible = SurfaceNodeParamsSet.IsOutputCompatible,
Title = "Set Parameter", Title = "Set Parameter",
Description = "Parameter value setter", Description = "Parameter value setter",
Flags = NodeFlags.VisualScriptGraph, Flags = NodeFlags.VisualScriptGraph,

View File

@@ -787,7 +787,7 @@ namespace FlaxEditor.Surface.Archetypes
} }
} }
private class AsNode : SurfaceNode internal class AsNode : SurfaceNode
{ {
private TypePickerControl _picker; private TypePickerControl _picker;
@@ -838,6 +838,15 @@ namespace FlaxEditor.Surface.Archetypes
box.CurrentType = type ? type : ScriptType.FlaxObject; box.CurrentType = type ? type : ScriptType.FlaxObject;
} }
/// <summary>
/// Sets the type of the picker and the type of the output box
/// </summary>
/// <param name="type">Target Type</param>
public void SetPickerValue(ScriptType type)
{
_picker.Value = type;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
@@ -999,6 +1008,15 @@ namespace FlaxEditor.Surface.Archetypes
GetBox(4).CurrentType = type ? type : _picker.Type; GetBox(4).CurrentType = type ? type : _picker.Type;
} }
/// <summary>
/// Sets the type of the picker and the type of the output box
/// </summary>
/// <param name="type">Target Type</param>
public void SetPickerValue(ScriptType type)
{
_picker.Value = type;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {

View File

@@ -623,7 +623,7 @@ namespace FlaxEditor.Surface.ContextMenu
Archetypes = archetypes Archetypes = archetypes
}; };
var group = CreateGroup(groupArchetype); var group = CreateGroup(groupArchetype, false);
group.ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown); group.ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown);
group.ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight); group.ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight);
group.Close(false); group.Close(false);
@@ -726,9 +726,7 @@ namespace FlaxEditor.Surface.ContextMenu
SelectedItem = previousSelectedItem; SelectedItem = previousSelectedItem;
// Scroll into view (without smoothing) // Scroll into view (without smoothing)
_panel1.VScrollBar.SmoothingScale = 0; _panel1.ScrollViewTo(SelectedItem, true);
_panel1.ScrollViewTo(SelectedItem);
_panel1.VScrollBar.SmoothingScale = 1;
} }
return true; return true;
} }

View File

@@ -110,11 +110,11 @@ namespace FlaxEditor.Surface.ContextMenu
bool isCompatible = false; bool isCompatible = false;
if (startBox.IsOutput && _archetype.IsInputCompatible != null) if (startBox.IsOutput && _archetype.IsInputCompatible != null)
{ {
isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints); isCompatible |= _archetype.IsInputCompatible.Invoke(_archetype, startBox.CurrentType, _archetype.ConnectionsHints, startBox.ParentNode.Context);
} }
else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null) else if (!startBox.IsOutput && _archetype.IsOutputCompatible != null)
{ {
isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints); isCompatible |= _archetype.IsOutputCompatible.Invoke(_archetype, startBox.CurrentType, startBox.ParentNode.Archetype.ConnectionsHints, startBox.ParentNode.Context);
} }
else if (_archetype.Elements != null) else if (_archetype.Elements != null)
{ {

View File

@@ -792,8 +792,62 @@ namespace FlaxEditor.Surface.Elements
if (useCaster) if (useCaster)
{ {
// Connect via Caster // Connect via Caster
//AddCaster(oB, iB); const float casterXOffset = 250;
throw new NotImplementedException("AddCaster(..) function"); if (Surface.Undo != null && Surface.Undo.Enabled)
{
bool undoEnabled = Surface.Undo.Enabled;
Surface.Undo.Enabled = false;
SurfaceNode node = Surface.Context.SpawnNode(7, 22, Float2.Zero); // 22 AsNode, 25 CastNode
Surface.Undo.Enabled = undoEnabled;
if (node is not Archetypes.Tools.AsNode castNode)
throw new Exception("Node is not a casting node!");
// Set the type of the casting node
undoEnabled = castNode.Surface.Undo.Enabled;
castNode.Surface.Undo.Enabled = false;
castNode.SetPickerValue(iB.CurrentType);
castNode.Surface.Undo.Enabled = undoEnabled;
if (node.GetBox(0) is not OutputBox castOutputBox || node.GetBox(1) is not InputBox castInputBox)
throw new NullReferenceException("Casting failed. Cast node is invalid!");
// We set the position of the cast node here to set it relative to the target nodes input box
undoEnabled = castNode.Surface.Undo.Enabled;
castNode.Surface.Undo.Enabled = false;
var wantedOffset = iB.ParentNode.Location - new Float2(casterXOffset, -(iB.LocalY - castOutputBox.LocalY));
castNode.Location = Surface.Root.PointFromParent(ref wantedOffset);
castNode.Surface.Undo.Enabled = undoEnabled;
var spawnNodeAction = new AddRemoveNodeAction(castNode, true);
var connectToCastNodeAction = new ConnectBoxesAction(castInputBox, oB, true);
castInputBox.CreateConnection(oB);
connectToCastNodeAction.End();
var connectCastToTargetNodeAction = new ConnectBoxesAction(iB, castOutputBox, true);
iB.CreateConnection(castOutputBox);
connectCastToTargetNodeAction.End();
Surface.AddBatchedUndoAction(new MultiUndoAction(spawnNodeAction, connectToCastNodeAction, connectCastToTargetNodeAction));
}
else
{
SurfaceNode node = Surface.Context.SpawnNode(7, 22, Float2.Zero); // 22 AsNode, 25 CastNode
if (node is not Archetypes.Tools.AsNode castNode)
throw new Exception("Node is not a casting node!");
// Set the type of the casting node
castNode.SetPickerValue(iB.CurrentType);
if (node.GetBox(0) is not OutputBox castOutputBox || node.GetBox(1) is not InputBox castInputBox)
throw new NullReferenceException("Casting failed. Cast node is invalid!");
// We set the position of the cast node here to set it relative to the target nodes input box
var wantedOffset = iB.ParentNode.Location - new Float2(casterXOffset, -(iB.LocalY - castOutputBox.LocalY));
castNode.Location = Surface.Root.PointFromParent(ref wantedOffset);
castInputBox.CreateConnection(oB);
iB.CreateConnection(castOutputBox);
}
Surface.MarkAsEdited();
} }
else else
{ {

View File

@@ -92,7 +92,7 @@ namespace FlaxEditor.Surface
/// <summary> /// <summary>
/// Checks if the given type is compatible with the given node archetype. Used for custom nodes /// Checks if the given type is compatible with the given node archetype. Used for custom nodes
/// </summary> /// </summary>
public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint); public delegate bool IsCompatible(NodeArchetype nodeArch, ScriptType portType, ConnectionsHint hint, VisjectSurfaceContext context);
/// <summary> /// <summary>
/// Unique node type ID within a single group. /// Unique node type ID within a single group.

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.Surface.Elements;
using FlaxEngine; using FlaxEngine;
namespace FlaxEditor.Surface namespace FlaxEditor.Surface
@@ -135,8 +136,25 @@ namespace FlaxEditor.Surface
endPos = _lastInstigatorUnderMouse.ConnectionOrigin; endPos = _lastInstigatorUnderMouse.ConnectionOrigin;
} }
Float2 actualStartPos = startPos;
Float2 actualEndPos = endPos;
if (_connectionInstigator is Archetypes.Tools.RerouteNode)
{
if (endPos.X < startPos.X && _lastInstigatorUnderMouse is null or Box { IsOutput: true})
{
actualStartPos = endPos;
actualEndPos = startPos;
}
}
else if (_connectionInstigator is Box { IsOutput: false })
{
actualStartPos = endPos;
actualEndPos = startPos;
}
// Draw connection // Draw connection
_connectionInstigator.DrawConnectingLine(ref startPos, ref endPos, ref lineColor); _connectionInstigator.DrawConnectingLine(ref actualStartPos, ref actualEndPos, ref lineColor);
} }
/// <summary> /// <summary>

View File

@@ -285,6 +285,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
StringAnsi TerrainTools::SerializePatch(Terrain* terrain, const Int2& patchCoord) StringAnsi TerrainTools::SerializePatch(Terrain* terrain, const Int2& patchCoord)
{ {
CHECK_RETURN(terrain, StringAnsi::Empty);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK_RETURN(patch, StringAnsi::Empty); CHECK_RETURN(patch, StringAnsi::Empty);
@@ -300,6 +301,7 @@ StringAnsi TerrainTools::SerializePatch(Terrain* terrain, const Int2& patchCoord
void TerrainTools::DeserializePatch(Terrain* terrain, const Int2& patchCoord, const StringAnsiView& value) void TerrainTools::DeserializePatch(Terrain* terrain, const Int2& patchCoord, const StringAnsiView& value)
{ {
CHECK(terrain);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK(patch); CHECK(patch);
@@ -317,27 +319,31 @@ void TerrainTools::DeserializePatch(Terrain* terrain, const Int2& patchCoord, co
bool TerrainTools::InitializePatch(Terrain* terrain, const Int2& patchCoord) bool TerrainTools::InitializePatch(Terrain* terrain, const Int2& patchCoord)
{ {
CHECK_RETURN(terrain, true);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK_RETURN(patch, true); CHECK_RETURN(patch, true);
return patch->InitializeHeightMap(); return patch->InitializeHeightMap();
} }
bool TerrainTools::ModifyHeightMap(Terrain* terrain, const Int2& patchCoord, float* samples, const Int2& offset, const Int2& size) bool TerrainTools::ModifyHeightMap(Terrain* terrain, const Int2& patchCoord, const float* samples, const Int2& offset, const Int2& size)
{ {
CHECK_RETURN(terrain, true);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK_RETURN(patch, true); CHECK_RETURN(patch, true);
return patch->ModifyHeightMap(samples, offset, size); return patch->ModifyHeightMap(samples, offset, size);
} }
bool TerrainTools::ModifyHolesMask(Terrain* terrain, const Int2& patchCoord, byte* samples, const Int2& offset, const Int2& size) bool TerrainTools::ModifyHolesMask(Terrain* terrain, const Int2& patchCoord, const byte* samples, const Int2& offset, const Int2& size)
{ {
CHECK_RETURN(terrain, true);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK_RETURN(patch, true); CHECK_RETURN(patch, true);
return patch->ModifyHolesMask(samples, offset, size); return patch->ModifyHolesMask(samples, offset, size);
} }
bool TerrainTools::ModifySplatMap(Terrain* terrain, const Int2& patchCoord, int32 index, Color32* samples, const Int2& offset, const Int2& size) bool TerrainTools::ModifySplatMap(Terrain* terrain, const Int2& patchCoord, int32 index, const Color32* samples, const Int2& offset, const Int2& size)
{ {
CHECK_RETURN(terrain, true);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK_RETURN(patch, true); CHECK_RETURN(patch, true);
CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, true); CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, true);
@@ -346,6 +352,7 @@ bool TerrainTools::ModifySplatMap(Terrain* terrain, const Int2& patchCoord, int3
float* TerrainTools::GetHeightmapData(Terrain* terrain, const Int2& patchCoord) float* TerrainTools::GetHeightmapData(Terrain* terrain, const Int2& patchCoord)
{ {
CHECK_RETURN(terrain, nullptr);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK_RETURN(patch, nullptr); CHECK_RETURN(patch, nullptr);
return patch->GetHeightmapData(); return patch->GetHeightmapData();
@@ -353,6 +360,7 @@ float* TerrainTools::GetHeightmapData(Terrain* terrain, const Int2& patchCoord)
byte* TerrainTools::GetHolesMaskData(Terrain* terrain, const Int2& patchCoord) byte* TerrainTools::GetHolesMaskData(Terrain* terrain, const Int2& patchCoord)
{ {
CHECK_RETURN(terrain, nullptr);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK_RETURN(patch, nullptr); CHECK_RETURN(patch, nullptr);
return patch->GetHolesMaskData(); return patch->GetHolesMaskData();
@@ -360,6 +368,7 @@ byte* TerrainTools::GetHolesMaskData(Terrain* terrain, const Int2& patchCoord)
Color32* TerrainTools::GetSplatMapData(Terrain* terrain, const Int2& patchCoord, int32 index) Color32* TerrainTools::GetSplatMapData(Terrain* terrain, const Int2& patchCoord, int32 index)
{ {
CHECK_RETURN(terrain, nullptr);
auto patch = terrain->GetPatch(patchCoord); auto patch = terrain->GetPatch(patchCoord);
CHECK_RETURN(patch, nullptr); CHECK_RETURN(patch, nullptr);
return patch->GetSplatMapData(index); return patch->GetSplatMapData(index);

View File

@@ -68,7 +68,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(TerrainTools);
/// <param name="offset">The offset from the first row and column of the heightmap data (offset destination x and z start position).</param> /// <param name="offset">The offset from the first row and column of the heightmap data (offset destination x and z start position).</param>
/// <param name="size">The size of the heightmap to modify (x and z). Amount of samples in each direction.</param> /// <param name="size">The size of the heightmap to modify (x and z). Amount of samples in each direction.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() static bool ModifyHeightMap(Terrain* terrain, API_PARAM(Ref) const Int2& patchCoord, float* samples, API_PARAM(Ref) const Int2& offset, API_PARAM(Ref) const Int2& size); API_FUNCTION() static bool ModifyHeightMap(Terrain* terrain, API_PARAM(Ref) const Int2& patchCoord, const float* samples, API_PARAM(Ref) const Int2& offset, API_PARAM(Ref) const Int2& size);
/// <summary> /// <summary>
/// Modifies the terrain patch holes mask with the given samples. /// Modifies the terrain patch holes mask with the given samples.
@@ -79,7 +79,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(TerrainTools);
/// <param name="offset">The offset from the first row and column of the mask data (offset destination x and z start position).</param> /// <param name="offset">The offset from the first row and column of the mask data (offset destination x and z start position).</param>
/// <param name="size">The size of the mask to modify (x and z). Amount of samples in each direction.</param> /// <param name="size">The size of the mask to modify (x and z). Amount of samples in each direction.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() static bool ModifyHolesMask(Terrain* terrain, API_PARAM(Ref) const Int2& patchCoord, byte* samples, API_PARAM(Ref) const Int2& offset, API_PARAM(Ref) const Int2& size); API_FUNCTION() static bool ModifyHolesMask(Terrain* terrain, API_PARAM(Ref) const Int2& patchCoord, const byte* samples, API_PARAM(Ref) const Int2& offset, API_PARAM(Ref) const Int2& size);
/// <summary> /// <summary>
/// Modifies the terrain patch splat map (layers mask) with the given samples. /// Modifies the terrain patch splat map (layers mask) with the given samples.
@@ -91,7 +91,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(TerrainTools);
/// <param name="offset">The offset from the first row and column of the splatmap data (offset destination x and z start position).</param> /// <param name="offset">The offset from the first row and column of the splatmap data (offset destination x and z start position).</param>
/// <param name="size">The size of the splatmap to modify (x and z). Amount of samples in each direction.</param> /// <param name="size">The size of the splatmap to modify (x and z). Amount of samples in each direction.</param>
/// <returns>True if failed, otherwise false.</returns> /// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() static bool ModifySplatMap(Terrain* terrain, API_PARAM(Ref) const Int2& patchCoord, int32 index, Color32* samples, API_PARAM(Ref) const Int2& offset, API_PARAM(Ref) const Int2& size); API_FUNCTION() static bool ModifySplatMap(Terrain* terrain, API_PARAM(Ref) const Int2& patchCoord, int32 index, const Color32* samples, API_PARAM(Ref) const Int2& offset, API_PARAM(Ref) const Int2& size);
/// <summary> /// <summary>
/// Gets the raw pointer to the heightmap data (cached internally by the c++ core in editor). /// Gets the raw pointer to the heightmap data (cached internally by the c++ core in editor).

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.Linq; using System.Linq;
using FlaxEditor.Content.Settings;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input; using FlaxEditor.GUI.Input;
using FlaxEditor.Options; using FlaxEditor.Options;
@@ -9,6 +10,8 @@ using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Widgets; using FlaxEditor.Viewport.Widgets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.Viewport namespace FlaxEditor.Viewport
{ {
@@ -486,10 +489,63 @@ namespace FlaxEditor.Viewport
} }
} }
// View Layers
{
var viewLayers = ViewWidgetButtonMenu.AddChildMenu("View Layers").ContextMenu;
viewLayers.AddButton("Copy layers", () => Clipboard.Text = JsonSerializer.Serialize(Task.View.RenderLayersMask));
viewLayers.AddButton("Paste layers", () =>
{
try
{
Task.ViewLayersMask = JsonSerializer.Deserialize<LayersMask>(Clipboard.Text);
}
catch
{
}
});
viewLayers.AddButton("Reset layers", () => Task.ViewLayersMask = LayersMask.Default).Icon = Editor.Instance.Icons.Rotate32;
viewLayers.AddButton("Disable layers", () => Task.ViewLayersMask = new LayersMask(0)).Icon = Editor.Instance.Icons.Rotate32;
viewLayers.AddSeparator();
var layers = LayersAndTagsSettings.GetCurrentLayers();
if (layers != null && layers.Length > 0)
{
for (int i = 0; i < layers.Length; i++)
{
var layer = layers[i];
var button = viewLayers.AddButton(layer);
button.CloseMenuOnClick = false;
button.Tag = 1 << i;
}
}
viewLayers.ButtonClicked += button =>
{
if (button.Tag != null)
{
int layerIndex = (int)button.Tag;
LayersMask mask = new LayersMask(layerIndex);
Task.ViewLayersMask ^= mask;
button.Icon = (Task.ViewLayersMask & mask) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
};
viewLayers.VisibleChanged += WidgetViewLayersShowHide;
}
// View Flags // View Flags
{ {
var viewFlags = ViewWidgetButtonMenu.AddChildMenu("View Flags").ContextMenu; var viewFlags = ViewWidgetButtonMenu.AddChildMenu("View Flags").ContextMenu;
viewFlags.AddButton("Copy flags", () => Clipboard.Text = JsonSerializer.Serialize(Task.ViewFlags));
viewFlags.AddButton("Paste flags", () =>
{
try
{
Task.ViewFlags = JsonSerializer.Deserialize<ViewFlags>(Clipboard.Text);
}
catch
{
}
});
viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32;
viewFlags.AddSeparator(); viewFlags.AddSeparator();
for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++) for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++)
{ {
@@ -504,7 +560,7 @@ namespace FlaxEditor.Viewport
{ {
var v = (ViewFlags)button.Tag; var v = (ViewFlags)button.Tag;
Task.ViewFlags ^= v; Task.ViewFlags ^= v;
button.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; button.Icon = (Task.ViewFlags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
} }
}; };
viewFlags.VisibleChanged += WidgetViewFlagsShowHide; viewFlags.VisibleChanged += WidgetViewFlagsShowHide;
@@ -513,6 +569,18 @@ namespace FlaxEditor.Viewport
// Debug View // Debug View
{ {
var debugView = ViewWidgetButtonMenu.AddChildMenu("Debug View").ContextMenu; var debugView = ViewWidgetButtonMenu.AddChildMenu("Debug View").ContextMenu;
debugView.AddButton("Copy view", () => Clipboard.Text = JsonSerializer.Serialize(Task.ViewMode));
debugView.AddButton("Paste view", () =>
{
try
{
Task.ViewMode = JsonSerializer.Deserialize<ViewMode>(Clipboard.Text);
}
catch
{
}
});
debugView.AddSeparator();
for (int i = 0; i < EditorViewportViewModeValues.Length; i++) for (int i = 0; i < EditorViewportViewModeValues.Length; i++)
{ {
ref var v = ref EditorViewportViewModeValues[i]; ref var v = ref EditorViewportViewModeValues[i];
@@ -954,7 +1022,7 @@ namespace FlaxEditor.Viewport
// Center mouse position if it's too close to the edge // Center mouse position if it's too close to the edge
var size = Size; var size = Size;
var center = size * 0.5f; var center = Float2.Round(size * 0.5f);
if (Mathf.Abs(_viewMousePos.X - center.X) > center.X * 0.8f || Mathf.Abs(_viewMousePos.Y - center.Y) > center.Y * 0.8f) if (Mathf.Abs(_viewMousePos.X - center.X) > center.X * 0.8f || Mathf.Abs(_viewMousePos.Y - center.Y) > center.Y * 0.8f)
{ {
_viewMousePos = center; _viewMousePos = center;
@@ -1084,9 +1152,9 @@ namespace FlaxEditor.Viewport
_isVirtualMouseRightDown = false; // Cancel when mouse right or escape is pressed _isVirtualMouseRightDown = false; // Cancel when mouse right or escape is pressed
if (_wasVirtualMouseRightDown) if (_wasVirtualMouseRightDown)
wasControllingMouse = true; wasControllingMouse = true;
if (_isVirtualMouseRightDown) if (_isVirtualMouseRightDown)
_isControllingMouse = _isVirtualMouseRightDown; _isControllingMouse = _isVirtualMouseRightDown;
if (wasControllingMouse != _isControllingMouse) if (wasControllingMouse != _isControllingMouse)
{ {
if (_isControllingMouse) if (_isControllingMouse)
@@ -1591,6 +1659,24 @@ namespace FlaxEditor.Viewport
} }
} }
private void WidgetViewLayersShowHide(Control cm)
{
if (cm.Visible == false)
return;
var ccm = (ContextMenu)cm;
var layersMask = Task.ViewLayersMask;
foreach (var e in ccm.Items)
{
if (e is ContextMenuButton b && b != null && b.Tag != null)
{
int layerIndex = (int)b.Tag;
LayersMask mask = new LayersMask(layerIndex);
b.Icon = (layersMask & mask) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
}
}
}
private float GetGamepadAxis(GamepadAxis axis) private float GetGamepadAxis(GamepadAxis axis)
{ {
var value = FlaxEngine.Input.GetGamepadAxis(InputGamepadIndex.All, axis); var value = FlaxEngine.Input.GetGamepadAxis(InputGamepadIndex.All, axis);

View File

@@ -116,7 +116,7 @@ namespace FlaxEditor.Windows.Assets
_window = window; _window = window;
// Try to restore target asset AudioClip import options (useful for fast reimport) // Try to restore target asset AudioClip import options (useful for fast reimport)
AudioImportSettings.TryRestore(ref ImportSettings, window.Item.Path); Editor.TryRestoreImportOptions(ref ImportSettings.Settings, window.Item.Path);
// Prepare restore data // Prepare restore data
PeekState(); PeekState();
@@ -134,6 +134,11 @@ namespace FlaxEditor.Windows.Assets
/// </summary> /// </summary>
public void Reimport() public void Reimport()
{ {
if (_window?._previewSource != null)
{
_window._previewSource.Stop();
_window.UpdateToolstrip();
}
Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)_window.Item, ImportSettings, true); Editor.Instance.ContentImporting.Reimport((BinaryAssetItem)_window.Item, ImportSettings, true);
} }

View File

@@ -80,11 +80,8 @@ namespace FlaxEditor.Windows.Assets
{ {
if (!IsEdited) if (!IsEdited)
return; return;
if (_asset.WaitForLoaded()) if (_asset.WaitForLoaded())
{
return; return;
}
if (Editor.SaveJsonAsset(_item.Path, _object)) if (Editor.SaveJsonAsset(_item.Path, _object))
{ {
@@ -137,7 +134,7 @@ namespace FlaxEditor.Windows.Assets
} }
} }
_presenter.Select(_object); _presenter.Select(_object);
if (_typeText != null) if (_typeText != null)
_typeText.Dispose(); _typeText.Dispose();
var typeText = new ClickableLabel var typeText = new ClickableLabel
@@ -152,7 +149,7 @@ namespace FlaxEditor.Windows.Assets
typeText.LocalY += (_toolstrip.Height - typeText.Height) * 0.5f; typeText.LocalY += (_toolstrip.Height - typeText.Height) * 0.5f;
typeText.RightClick = () => Clipboard.Text = Asset.DataTypeName; typeText.RightClick = () => Clipboard.Text = Asset.DataTypeName;
_typeText = typeText; _typeText = typeText;
_undo.Clear(); _undo.Clear();
ClearEditedFlag(); ClearEditedFlag();

View File

@@ -233,7 +233,7 @@ namespace FlaxEditor.Windows.Assets
contextMenu.AddSeparator(); contextMenu.AddSeparator();
b = contextMenu.AddButton("Create Prefab", () => Editor.Prefabs.CreatePrefab(Selection)); b = contextMenu.AddButton("Create Prefab", () => Editor.Prefabs.CreatePrefab(Selection, this));
b.Enabled = isSingleActorSelected && b.Enabled = isSingleActorSelected &&
(Selection[0] as ActorNode).CanCreatePrefab && (Selection[0] as ActorNode).CanCreatePrefab &&
Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets; Editor.Windows.ContentWin.CurrentViewFolder.CanHaveAssets;
@@ -313,7 +313,7 @@ namespace FlaxEditor.Windows.Assets
} }
if (showCustomNodeOptions) if (showCustomNodeOptions)
{ {
Selection[0].OnContextMenu(contextMenu); Selection[0].OnContextMenu(contextMenu, this);
} }
ContextMenuShow?.Invoke(contextMenu); ContextMenuShow?.Invoke(contextMenu);

View File

@@ -53,6 +53,11 @@ namespace FlaxEditor.Windows.Assets
/// </summary> /// </summary>
public PrefabWindowViewport Viewport => _viewport; public PrefabWindowViewport Viewport => _viewport;
/// <summary>
/// Gets the prefab objects properties editor.
/// </summary>
public CustomEditorPresenter Presenter => _propertiesEditor;
/// <summary> /// <summary>
/// Gets the undo system used by this window for changes tracking. /// Gets the undo system used by this window for changes tracking.
/// </summary> /// </summary>
@@ -146,7 +151,7 @@ namespace FlaxEditor.Windows.Assets
Graph = new LocalSceneGraph(new CustomRootNode(this)); Graph = new LocalSceneGraph(new CustomRootNode(this));
_tree = new PrefabTree _tree = new PrefabTree
{ {
Margin = new Margin(0.0f, 0.0f, -16.0f, 0.0f), // Hide root node Margin = new Margin(0.0f, 0.0f, -16.0f, _treePanel.ScrollBarsSize), // Hide root node
IsScrollable = true, IsScrollable = true,
}; };
_tree.AddChild(Graph.Root.TreeNode); _tree.AddChild(Graph.Root.TreeNode);

View File

@@ -1,11 +1,13 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System; using System;
using System.IO;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.Assertions; using FlaxEngine.Assertions;
using FlaxEngine.GUI;
using FlaxEngine.Json; using FlaxEngine.Json;
namespace FlaxEditor.Windows namespace FlaxEditor.Windows
@@ -145,9 +147,18 @@ namespace FlaxEditor.Windows
cm.AddButton("Refresh all thumbnails", RefreshViewItemsThumbnails); cm.AddButton("Refresh all thumbnails", RefreshViewItemsThumbnails);
} }
cm.AddSeparator();
// Check if is source folder to add new module
if (folder?.ParentFolder?.Node is ProjectTreeNode parentFolderNode && folder.Node == parentFolderNode.Source)
{
var button = cm.AddButton("New module");
button.CloseMenuOnClick = false;
button.Clicked += () => NewModule(button, parentFolderNode.Source.Path);
}
if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode)) if (!isRootFolder && !(item is ContentFolder projectFolder && projectFolder.Node is ProjectTreeNode))
{ {
cm.AddSeparator();
cm.AddButton("New folder", NewFolder); cm.AddButton("New folder", NewFolder);
} }
@@ -311,5 +322,138 @@ namespace FlaxEditor.Windows
return; return;
} }
} }
private void NewModule(ContextMenuButton button, string path)
{
var popup = new ContextMenuBase
{
Size = new Float2(230, 125),
ClipChildren = false,
CullChildren = false,
};
popup.Show(button, new Float2(button.Width, 0));
var nameLabel = new Label
{
Parent = popup,
AnchorPreset = AnchorPresets.TopLeft,
Text = "Name",
HorizontalAlignment = TextAlignment.Near,
};
nameLabel.LocalX += 10;
nameLabel.LocalY += 10;
var nameTextBox = new TextBox
{
Parent = popup,
WatermarkText = "Module Name",
AnchorPreset = AnchorPresets.TopLeft,
IsMultiline = false,
};
nameTextBox.LocalX += 100;
nameTextBox.LocalY += 10;
var defaultTextBoxBorderColor = nameTextBox.BorderColor;
var defaultTextBoxBorderSelectedColor = nameTextBox.BorderSelectedColor;
nameTextBox.TextChanged += () =>
{
if (string.IsNullOrEmpty(nameTextBox.Text))
{
nameTextBox.BorderColor = defaultTextBoxBorderColor;
nameTextBox.BorderSelectedColor = defaultTextBoxBorderSelectedColor;
return;
}
var pluginPath = Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text);
if (Directory.Exists(pluginPath))
{
nameTextBox.BorderColor = Color.Red;
nameTextBox.BorderSelectedColor = Color.Red;
}
else
{
nameTextBox.BorderColor = defaultTextBoxBorderColor;
nameTextBox.BorderSelectedColor = defaultTextBoxBorderSelectedColor;
}
};
var editorLabel = new Label
{
Parent = popup,
AnchorPreset = AnchorPresets.TopLeft,
Text = "Editor",
HorizontalAlignment = TextAlignment.Near,
};
editorLabel.LocalX += 10;
editorLabel.LocalY += 35;
var editorCheckBox = new CheckBox
{
Parent = popup,
AnchorPreset = AnchorPresets.TopLeft,
};
editorCheckBox.LocalY += 35;
editorCheckBox.LocalX += 100;
var cppLabel = new Label
{
Parent = popup,
AnchorPreset = AnchorPresets.TopLeft,
Text = "C++",
HorizontalAlignment = TextAlignment.Near,
};
cppLabel.LocalX += 10;
cppLabel.LocalY += 60;
var cppCheckBox = new CheckBox
{
Parent = popup,
AnchorPreset = AnchorPresets.TopLeft,
};
cppCheckBox.LocalY += 60;
cppCheckBox.LocalX += 100;
var submitButton = new Button
{
Parent = popup,
AnchorPreset = AnchorPresets.TopLeft,
Text = "Create",
Width = 70,
};
submitButton.LocalX += 40;
submitButton.LocalY += 90;
submitButton.Clicked += () =>
{
// TODO: Check all modules in project including plugins
if (Directory.Exists(Path.Combine(Globals.ProjectFolder, "Source", nameTextBox.Text)))
{
Editor.LogWarning("Cannot create module due to name conflict.");
return;
}
Editor.CodeEditing.CreateModule(path, nameTextBox.Text, editorCheckBox.Checked, cppCheckBox.Checked);
nameTextBox.Clear();
editorCheckBox.Checked = false;
cppCheckBox.Checked = false;
popup.Hide();
button.ParentContextMenu.Hide();
};
var cancelButton = new Button
{
Parent = popup,
AnchorPreset = AnchorPresets.TopLeft,
Text = "Cancel",
Width = 70,
};
cancelButton.LocalX += 120;
cancelButton.LocalY += 90;
cancelButton.Clicked += () =>
{
nameTextBox.Clear();
editorCheckBox.Checked = false;
cppCheckBox.Checked = false;
popup.Hide();
button.ParentContextMenu.Hide();
};
}
} }
} }

View File

@@ -329,7 +329,7 @@ namespace FlaxEditor.Windows
b.Checked = ShowPluginsFiles; b.Checked = ShowPluginsFiles;
b.CloseMenuOnClick = false; b.CloseMenuOnClick = false;
b.AutoCheck = true; b.AutoCheck = true;
b = show.ContextMenu.AddButton("Generated files", () => ShowGeneratedFiles = !ShowGeneratedFiles); b = show.ContextMenu.AddButton("Generated files", () => ShowGeneratedFiles = !ShowGeneratedFiles);
b.TooltipText = "Shows generated files"; b.TooltipText = "Shows generated files";
b.Checked = ShowGeneratedFiles; b.Checked = ShowGeneratedFiles;
@@ -755,7 +755,7 @@ namespace FlaxEditor.Windows
} }
/// <summary> /// <summary>
/// Stars creating the folder. /// Starts creating the folder.
/// </summary> /// </summary>
public void NewFolder() public void NewFolder()
{ {

View File

@@ -215,7 +215,7 @@ namespace FlaxEditor.Windows
} }
if (showCustomNodeOptions) if (showCustomNodeOptions)
{ {
Editor.SceneEditing.Selection[0].OnContextMenu(contextMenu); Editor.SceneEditing.Selection[0].OnContextMenu(contextMenu, this);
} }
ContextMenuShow?.Invoke(contextMenu); ContextMenuShow?.Invoke(contextMenu);

View File

@@ -74,7 +74,7 @@ namespace FlaxEditor.Windows
root.TreeNode.Expand(); root.TreeNode.Expand();
_tree = new Tree(true) _tree = new Tree(true)
{ {
Margin = new Margin(0.0f, 0.0f, -16.0f, 0.0f), // Hide root node Margin = new Margin(0.0f, 0.0f, -16.0f, _sceneTreePanel.ScrollBarsSize), // Hide root node
IsScrollable = true, IsScrollable = true,
}; };
_tree.AddChild(root.TreeNode); _tree.AddChild(root.TreeNode);

View File

@@ -54,9 +54,7 @@ namespace FlaxEditor.Windows.Search
_selectedItem.BackgroundColor = Style.Current.BackgroundSelected; _selectedItem.BackgroundColor = Style.Current.BackgroundSelected;
if (_matchedItems.Count > VisibleItemCount) if (_matchedItems.Count > VisibleItemCount)
{ {
_resultPanel.VScrollBar.SmoothingScale = 0; _resultPanel.ScrollViewTo(_selectedItem, true);
_resultPanel.ScrollViewTo(_selectedItem);
_resultPanel.VScrollBar.SmoothingScale = 1;
} }
} }
} }

View File

@@ -127,6 +127,14 @@ const Char* SplashScreenQuotes[] =
TEXT("Who is signing all these integers?!"), TEXT("Who is signing all these integers?!"),
TEXT("Flax fact: Flax was called Celelej once."), TEXT("Flax fact: Flax was called Celelej once."),
TEXT("Changing text overflow setti-"), TEXT("Changing text overflow setti-"),
TEXT("Testing tests"),
TEXT("Free hugs"),
TEXT("Think outside the box"),
TEXT("Let's make something fantastic"),
TEXT("Be brave"),
TEXT("Drum roll please"),
TEXT("Good Luck Have Fun"),
TEXT("GG Well Played"),
}; };
SplashScreen::~SplashScreen() SplashScreen::~SplashScreen()

View File

@@ -348,7 +348,7 @@ Asset::LoadResult AudioClip::load()
#if !BUILD_RELEASE #if !BUILD_RELEASE
// Validate buffer start times // Validate buffer start times
if (Math::NotNearEqual(_buffersStartTimes[_totalChunks], GetLength())) if (!Math::NearEqual(_buffersStartTimes[_totalChunks], GetLength(), 1.0f / 60.0f))
{ {
LOG(Warning, "Invalid audio buffers data size. Expected length: {0}s", GetLength()); LOG(Warning, "Invalid audio buffers data size. Expected length: {0}s", GetLength());
for (int32 i = 0; i < _totalChunks + 1; i++) for (int32 i = 0; i < _totalChunks + 1; i++)
@@ -454,14 +454,12 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
} }
break; break;
case AudioFormat::Raw: case AudioFormat::Raw:
{
data = Span<byte>(chunk->Get(), chunk->Size()); data = Span<byte>(chunk->Get(), chunk->Size());
} break;
break;
default: default:
return true; return true;
} }
info.NumSamples = data.Length() / bytesPerSample; info.NumSamples = Math::AlignDown(data.Length() / bytesPerSample, info.NumChannels * bytesPerSample);
// Convert to Mono if used as 3D source and backend doesn't support it // Convert to Mono if used as 3D source and backend doesn't support it
if (Is3D() && info.NumChannels > 1 && EnumHasNoneFlags(AudioBackend::Features(), AudioBackend::FeatureFlags::SpatialMultiChannel)) if (Is3D() && info.NumChannels > 1 && EnumHasNoneFlags(AudioBackend::Features(), AudioBackend::FeatureFlags::SpatialMultiChannel))

View File

@@ -183,7 +183,7 @@ void AudioSource::Stop()
float AudioSource::GetTime() const float AudioSource::GetTime() const
{ {
if (_state == States::Stopped || SourceIDs.IsEmpty()) if (_state == States::Stopped || SourceIDs.IsEmpty() || !Clip->IsLoaded())
return 0.0f; return 0.0f;
float time = AudioBackend::Source::GetCurrentBufferTime(this); float time = AudioBackend::Source::GetCurrentBufferTime(this);
@@ -265,6 +265,7 @@ void AudioSource::Cleanup()
void AudioSource::OnClipChanged() void AudioSource::OnClipChanged()
{ {
Stop(); Stop();
_clipChanged = true;
} }
void AudioSource::OnClipLoaded() void AudioSource::OnClipLoaded()
@@ -318,6 +319,12 @@ void AudioSource::SetNonStreamingBuffer()
void AudioSource::PlayInternal() void AudioSource::PlayInternal()
{ {
if (_clipChanged && SourceIDs.HasItems())
{
// If clip was changed between source setup (OnEnable) and actual playback start then ensure to flush any runtime properties with the audio backend
_clipChanged = false;
AudioBackend::Source::SpatialSetupChanged(this);
}
AudioBackend::Source::Play(this); AudioBackend::Source::Play(this);
_isActuallyPlayingSth = true; _isActuallyPlayingSth = true;
@@ -482,6 +489,7 @@ void AudioSource::OnEnable()
{ {
_prevPos = GetPosition(); _prevPos = GetPosition();
_velocity = Vector3::Zero; _velocity = Vector3::Zero;
_clipChanged = false;
Audio::OnAddSource(this); Audio::OnAddSource(this);
GetScene()->Ticking.Update.AddTick<AudioSource, &AudioSource::Update>(this); GetScene()->Ticking.Update.AddTick<AudioSource, &AudioSource::Update>(this);

View File

@@ -53,6 +53,7 @@ private:
bool _loop; bool _loop;
bool _playOnStart; bool _playOnStart;
bool _allowSpatialization; bool _allowSpatialization;
bool _clipChanged = false;
bool _isActuallyPlayingSth = false; bool _isActuallyPlayingSth = false;
bool _needToUpdateStreamingBuffers = false; bool _needToUpdateStreamingBuffers = false;

View File

@@ -228,11 +228,9 @@ namespace ALC
ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth) ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
{ {
// TODO: cache enum values in Init()?? // TODO: cache enum values in Init()??
switch (bitDepth) switch (bitDepth)
{ {
case 8: case 8:
{
switch (numChannels) switch (numChannels)
{ {
case 1: case 1:
@@ -247,13 +245,8 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
return alGetEnumValue("AL_FORMAT_61CHN8"); return alGetEnumValue("AL_FORMAT_61CHN8");
case 8: case 8:
return alGetEnumValue("AL_FORMAT_71CHN8"); return alGetEnumValue("AL_FORMAT_71CHN8");
default:
CRASH;
return 0;
} }
}
case 16: case 16:
{
switch (numChannels) switch (numChannels)
{ {
case 1: case 1:
@@ -268,19 +261,22 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
return alGetEnumValue("AL_FORMAT_61CHN16"); return alGetEnumValue("AL_FORMAT_61CHN16");
case 8: case 8:
return alGetEnumValue("AL_FORMAT_71CHN16"); return alGetEnumValue("AL_FORMAT_71CHN16");
default:
CRASH;
return 0;
} }
}
case 32: case 32:
{
switch (numChannels) switch (numChannels)
{ {
case 1: case 1:
#ifdef AL_FORMAT_MONO_FLOAT32
return AL_FORMAT_MONO_FLOAT32;
#else
return alGetEnumValue("AL_FORMAT_MONO_FLOAT32"); return alGetEnumValue("AL_FORMAT_MONO_FLOAT32");
#endif
case 2: case 2:
#ifdef AL_FORMAT_STEREO_FLOAT32
return AL_FORMAT_STEREO_FLOAT32;
#else
return alGetEnumValue("AL_FORMAT_STEREO_FLOAT32"); return alGetEnumValue("AL_FORMAT_STEREO_FLOAT32");
#endif
case 4: case 4:
return alGetEnumValue("AL_FORMAT_QUAD32"); return alGetEnumValue("AL_FORMAT_QUAD32");
case 6: case 6:
@@ -289,15 +285,9 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
return alGetEnumValue("AL_FORMAT_61CHN32"); return alGetEnumValue("AL_FORMAT_61CHN32");
case 8: case 8:
return alGetEnumValue("AL_FORMAT_71CHN32"); return alGetEnumValue("AL_FORMAT_71CHN32");
default:
CRASH;
return 0;
} }
} }
default: return 0;
CRASH;
return 0;
}
} }
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener) void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
@@ -607,7 +597,8 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
{ {
PROFILE_CPU(); PROFILE_CPU();
// TODO: maybe use temporary buffers per thread to reduce dynamic allocations when uploading data to OpenAL? // Pick the format for the audio data (it might not be supported natively)
ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
// Mono or stereo // Mono or stereo
if (info.NumChannels <= 2) if (info.NumChannels <= 2)
@@ -618,28 +609,23 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
{ {
const uint32 bufferSize = info.NumSamples * sizeof(float); const uint32 bufferSize = info.NumSamples * sizeof(float);
float* sampleBufferFloat = (float*)Allocator::Allocate(bufferSize); float* sampleBufferFloat = (float*)Allocator::Allocate(bufferSize);
AudioTool::ConvertToFloat(samples, info.BitDepth, sampleBufferFloat, info.NumSamples); AudioTool::ConvertToFloat(samples, info.BitDepth, sampleBufferFloat, info.NumSamples);
const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth); format = GetOpenALBufferFormat(info.NumChannels, 32);
alBufferData(bufferId, format, sampleBufferFloat, bufferSize, info.SampleRate); alBufferData(bufferId, format, sampleBufferFloat, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBufferFloat); Allocator::Free(sampleBufferFloat);
} }
else else
{ {
LOG(Warning, "OpenAL doesn't support bit depth larger than 16. Your audio data will be truncated."); LOG(Warning, "OpenAL doesn't support bit depth larger than 16. Your audio data will be truncated.");
const uint32 bufferSize = info.NumSamples * 2; const uint32 bufferSize = info.NumSamples * 2;
byte* sampleBuffer16 = (byte*)Allocator::Allocate(bufferSize); byte* sampleBuffer16 = (byte*)Allocator::Allocate(bufferSize);
AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer16, 16, info.NumSamples); AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer16, 16, info.NumSamples);
const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16); format = GetOpenALBufferFormat(info.NumChannels, 16);
alBufferData(bufferId, format, sampleBuffer16, bufferSize, info.SampleRate); alBufferData(bufferId, format, sampleBuffer16, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer16); Allocator::Free(sampleBuffer16);
} }
} }
@@ -648,19 +634,15 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
// OpenAL expects unsigned 8-bit data, but engine stores it as signed, so convert // OpenAL expects unsigned 8-bit data, but engine stores it as signed, so convert
const uint32 bufferSize = info.NumSamples * (info.BitDepth / 8); const uint32 bufferSize = info.NumSamples * (info.BitDepth / 8);
byte* sampleBuffer = (byte*)Allocator::Allocate(bufferSize); byte* sampleBuffer = (byte*)Allocator::Allocate(bufferSize);
for (uint32 i = 0; i < info.NumSamples; i++) for (uint32 i = 0; i < info.NumSamples; i++)
sampleBuffer[i] = ((int8*)samples)[i] + 128; sampleBuffer[i] = ((int8*)samples)[i] + 128;
const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16);
alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate); alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer); Allocator::Free(sampleBuffer);
} }
else else if (format)
{ {
const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate); alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
} }
@@ -675,10 +657,9 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
{ {
const uint32 bufferSize = info.NumChannels * sizeof(int32); const uint32 bufferSize = info.NumChannels * sizeof(int32);
byte* sampleBuffer32 = (byte*)Allocator::Allocate(bufferSize); byte* sampleBuffer32 = (byte*)Allocator::Allocate(bufferSize);
AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer32, 32, info.NumSamples); AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer32, 32, info.NumSamples);
const ALenum format = GetOpenALBufferFormat(info.NumChannels, 32); format = GetOpenALBufferFormat(info.NumChannels, 32);
alBufferData(bufferId, format, sampleBuffer32, bufferSize, info.SampleRate); alBufferData(bufferId, format, sampleBuffer32, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
@@ -693,19 +674,23 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
for (uint32 i = 0; i < info.NumSamples; i++) for (uint32 i = 0; i < info.NumSamples; i++)
sampleBuffer[i] = ((int8*)samples)[i] + 128; sampleBuffer[i] = ((int8*)samples)[i] + 128;
const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16); format = GetOpenALBufferFormat(info.NumChannels, 16);
alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate); alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer); Allocator::Free(sampleBuffer);
} }
else else if (format)
{ {
const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate); alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
ALC_CHECK_ERROR(alBufferData); ALC_CHECK_ERROR(alBufferData);
} }
} }
if (!format)
{
LOG(Error, "Not suppported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels);
}
} }
const Char* AudioBackendOAL::Base_Name() const Char* AudioBackendOAL::Base_Name()

View File

@@ -15,15 +15,8 @@
#include "Engine/Engine/Globals.h" #include "Engine/Engine/Globals.h"
#include "FlaxEngine.Gen.h" #include "FlaxEngine.Gen.h"
AssetsCache::AssetsCache()
: _isDirty(false)
, _registry(4096)
{
}
void AssetsCache::Init() void AssetsCache::Init()
{ {
// Cache data
Entry e; Entry e;
int32 count; int32 count;
const DateTime loadStartTime = DateTime::Now(); const DateTime loadStartTime = DateTime::Now();
@@ -32,13 +25,11 @@ void AssetsCache::Init()
#else #else
_path = Globals::ProjectContentFolder / TEXT("AssetsCache.dat"); _path = Globals::ProjectContentFolder / TEXT("AssetsCache.dat");
#endif #endif
LOG(Info, "Loading Asset Cache {0}...", _path); LOG(Info, "Loading Asset Cache {0}...", _path);
// Check if assets registry exists // Check if assets registry exists
if (!FileSystem::FileExists(_path)) if (!FileSystem::FileExists(_path))
{ {
// Back
_isDirty = true; _isDirty = true;
LOG(Warning, "Cannot find assets cache file"); LOG(Warning, "Cannot find assets cache file");
return; return;
@@ -49,7 +40,6 @@ void AssetsCache::Init()
DeleteMe<FileReadStream> deleteStream(stream); DeleteMe<FileReadStream> deleteStream(stream);
// Load version // Load version
// Note: every Engine build is using different assets cache
int32 version; int32 version;
stream->ReadInt32(&version); stream->ReadInt32(&version);
if (version != FLAXENGINE_VERSION_BUILD) if (version != FLAXENGINE_VERSION_BUILD)
@@ -58,24 +48,28 @@ void AssetsCache::Init()
return; return;
} }
// Load Engine workspace path // Load paths
String workspacePath; String enginePath, projectPath;
stream->ReadString(&workspacePath, -410); stream->ReadString(&enginePath, -410);
stream->ReadString(&projectPath, -410);
// Flags // Flags
AssetsCacheFlags flags; AssetsCacheFlags flags;
stream->ReadInt32((int32*)&flags); stream->ReadInt32((int32*)&flags);
// Check if other engine instance used this cache (cache depends on engine build and install location) // Check if other workspace instance used this cache
// Skip it for relative paths mode if (EnumHasNoneFlags(flags, AssetsCacheFlags::RelativePaths) && enginePath != Globals::StartupFolder)
if (!(flags & AssetsCacheFlags::RelativePaths) && workspacePath != Globals::StartupFolder)
{ {
LOG(Warning, "Assets cache generated by the different engine installation in \'{0}\'", workspacePath); LOG(Warning, "Assets cache generated by the different {1} installation in \'{0}\'", enginePath, TEXT("engine"));
return;
}
if (EnumHasNoneFlags(flags, AssetsCacheFlags::RelativePaths) && projectPath != Globals::ProjectFolder)
{
LOG(Warning, "Assets cache generated by the different {1} installation in \'{0}\'", projectPath, TEXT("project"));
return; return;
} }
ScopeLock lock(_locker); ScopeLock lock(_locker);
_isDirty = false; _isDirty = false;
// Load elements count // Load elements count
@@ -103,15 +97,11 @@ void AssetsCache::Init()
e.Info.Path = Globals::StartupFolder / e.Info.Path; e.Info.Path = Globals::StartupFolder / e.Info.Path;
} }
// Validate entry // Use only valid entries
if (!IsEntryValid(e)) if (IsEntryValid(e))
{ _registry.Add(e.Info.ID, e);
// Reject else
rejectedCount++; rejectedCount++;
continue;
}
_registry.Add(e.Info.ID, e);
} }
// Paths mapping // Paths mapping
@@ -148,7 +138,6 @@ void AssetsCache::Init()
} }
} }
// End
const int32 loadTimeInMs = static_cast<int32>((DateTime::Now() - loadStartTime).GetTotalMilliseconds()); const int32 loadTimeInMs = static_cast<int32>((DateTime::Now() - loadStartTime).GetTotalMilliseconds());
LOG(Info, "Asset Cache loaded {0} entries in {1} ms ({2} rejected)", _registry.Count(), loadTimeInMs, rejectedCount); LOG(Info, "Asset Cache loaded {0} entries in {1} ms ({2} rejected)", _registry.Count(), loadTimeInMs, rejectedCount);
} }
@@ -188,8 +177,9 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
// Version // Version
stream->WriteInt32(FLAXENGINE_VERSION_BUILD); stream->WriteInt32(FLAXENGINE_VERSION_BUILD);
// Engine workspace path // Paths
stream->WriteString(Globals::StartupFolder, -410); stream->WriteString(Globals::StartupFolder, -410);
stream->WriteString(Globals::ProjectFolder, -410);
// Flags // Flags
stream->WriteInt32((int32)flags); stream->WriteInt32((int32)flags);
@@ -202,7 +192,6 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
for (auto i = entries.Begin(); i.IsNotEnd(); ++i) for (auto i = entries.Begin(); i.IsNotEnd(); ++i)
{ {
auto& e = i->Value; auto& e = i->Value;
stream->Write(e.Info.ID); stream->Write(e.Info.ID);
stream->WriteString(e.Info.TypeName, index - 13); stream->WriteString(e.Info.TypeName, index - 13);
stream->WriteString(e.Info.Path, index); stream->WriteString(e.Info.Path, index);
@@ -211,7 +200,6 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
#else #else
stream->WriteInt64(0); stream->WriteInt64(0);
#endif #endif
index++; index++;
} }
@@ -222,7 +210,6 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
{ {
stream->Write(i->Value); stream->Write(i->Value);
stream->WriteString(i->Key, index + 73); stream->WriteString(i->Key, index + 73);
index++; index++;
} }

View File

@@ -74,7 +74,7 @@ public:
typedef Dictionary<String, Guid> PathsMapping; typedef Dictionary<String, Guid> PathsMapping;
private: private:
bool _isDirty; bool _isDirty = false;
CriticalSection _locker; CriticalSection _locker;
Registry _registry; Registry _registry;
PathsMapping _pathsMapping; PathsMapping _pathsMapping;
@@ -82,15 +82,8 @@ private:
public: public:
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AssetsCache"/> class. /// Gets amount of registered assets.
/// </summary> /// </summary>
AssetsCache();
public:
/// <summary>
/// Gets amount of registered assets
/// </summary>
/// <returns>Registry size</returns>
int32 Size() const int32 Size() const
{ {
_locker.Lock(); _locker.Lock();

View File

@@ -125,16 +125,12 @@ void ContentService::LateUpdate()
if (timeNow - LastUnloadCheckTime < Content::AssetsUpdateInterval) if (timeNow - LastUnloadCheckTime < Content::AssetsUpdateInterval)
return; return;
LastUnloadCheckTime = timeNow; LastUnloadCheckTime = timeNow;
AssetsLocker.Lock();
Asset* asset;
ScopeLock lock(AssetsLocker);
// TODO: maybe it would be better to link for asset remove ref event and cache only assets with no references - test it with millions of assets?
// Verify all assets // Verify all assets
for (auto i = Assets.Begin(); i.IsNotEnd(); ++i) for (auto i = Assets.Begin(); i.IsNotEnd(); ++i)
{ {
asset = i->Value; Asset* asset = i->Value;
// Check if has no references and is not during unloading // Check if has no references and is not during unloading
if (asset->GetReferencesCount() <= 0 && !UnloadQueue.ContainsKey(asset)) if (asset->GetReferencesCount() <= 0 && !UnloadQueue.ContainsKey(asset))
@@ -158,7 +154,7 @@ void ContentService::LateUpdate()
// Unload marked assets // Unload marked assets
for (int32 i = 0; i < ToUnload.Count(); i++) for (int32 i = 0; i < ToUnload.Count(); i++)
{ {
asset = ToUnload[i]; Asset* asset = ToUnload[i];
// Check if has no references // Check if has no references
if (asset->GetReferencesCount() <= 0) if (asset->GetReferencesCount() <= 0)
@@ -170,6 +166,8 @@ void ContentService::LateUpdate()
UnloadQueue.Remove(asset); UnloadQueue.Remove(asset);
} }
AssetsLocker.Unlock();
// Update cache (for longer sessions it will help to reduce cache misses) // Update cache (for longer sessions it will help to reduce cache misses)
Cache.Save(); Cache.Save();
} }
@@ -212,7 +210,6 @@ bool FindAssets(const ProjectInfo* project, HashSet<const ProjectInfo*>& project
{ {
if (projects.Contains(project)) if (projects.Contains(project))
return false; return false;
projects.Add(project); projects.Add(project);
bool found = findAsset(id, project->ProjectFolderPath / TEXT("Content"), tmpCache, info); bool found = findAsset(id, project->ProjectFolderPath / TEXT("Content"), tmpCache, info);
for (const auto& reference : project->References) for (const auto& reference : project->References)
@@ -220,7 +217,6 @@ bool FindAssets(const ProjectInfo* project, HashSet<const ProjectInfo*>& project
if (reference.Project) if (reference.Project)
found |= FindAssets(reference.Project, projects, id, tmpCache, info); found |= FindAssets(reference.Project, projects, id, tmpCache, info);
} }
return found; return found;
} }
@@ -232,7 +228,6 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
return false; return false;
#if ENABLE_ASSETS_DISCOVERY #if ENABLE_ASSETS_DISCOVERY
// Find asset in registry // Find asset in registry
if (Cache.FindAsset(id, info)) if (Cache.FindAsset(id, info))
return true; return true;
@@ -270,19 +265,15 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
//LOG(Warning, "Cannot find {0}.", id); //LOG(Warning, "Cannot find {0}.", id);
return false; return false;
#else #else
// Find asset in registry // Find asset in registry
return Cache.FindAsset(id, info); return Cache.FindAsset(id, info);
#endif #endif
} }
bool Content::GetAssetInfo(const StringView& path, AssetInfo& info) bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
{ {
#if ENABLE_ASSETS_DISCOVERY #if ENABLE_ASSETS_DISCOVERY
// Find asset in registry // Find asset in registry
if (Cache.FindAsset(path, info)) if (Cache.FindAsset(path, info))
return true; return true;
@@ -326,12 +317,9 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
} }
return false; return false;
#else #else
// Find asset in registry // Find asset in registry
return Cache.FindAsset(path, info); return Cache.FindAsset(path, info);
#endif #endif
} }

View File

@@ -18,32 +18,6 @@
#include "Engine/Tools/AudioTool/OggVorbisDecoder.h" #include "Engine/Tools/AudioTool/OggVorbisDecoder.h"
#include "Engine/Tools/AudioTool/OggVorbisEncoder.h" #include "Engine/Tools/AudioTool/OggVorbisEncoder.h"
#include "Engine/Serialization/JsonWriters.h" #include "Engine/Serialization/JsonWriters.h"
#include "Engine/Scripting/Enums.h"
String ImportAudio::Options::ToString() const
{
return String::Format(TEXT("Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"), ScriptingEnum::ToString(Format), DisableStreaming, Is3D, Quality, BitDepth);
}
void ImportAudio::Options::Serialize(SerializeStream& stream, const void* otherObj)
{
SERIALIZE_GET_OTHER_OBJ(ImportAudio::Options);
SERIALIZE(Format);
SERIALIZE(DisableStreaming);
SERIALIZE(Is3D);
SERIALIZE(Quality);
SERIALIZE(BitDepth);
}
void ImportAudio::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(Format);
DESERIALIZE(DisableStreaming);
DESERIALIZE(Is3D);
DESERIALIZE(Quality);
DESERIALIZE(BitDepth);
}
bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options) bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options)
{ {
@@ -90,6 +64,10 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
} }
} }
// Vorbis uses fixed 16-bit depth
if (options.Format == AudioFormat::Vorbis)
options.BitDepth = AudioTool::BitDepth::_16;
LOG_STR(Info, options.ToString()); LOG_STR(Info, options.ToString());
// Open the file // Open the file
@@ -112,16 +90,14 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
sampleBuffer.Link(audioData.Get()); sampleBuffer.Link(audioData.Get());
// Convert bit depth if need to // Convert bit depth if need to
if (options.BitDepth != static_cast<int32>(info.BitDepth)) uint32 outputBitDepth = (uint32)options.BitDepth;
if (outputBitDepth != info.BitDepth)
{ {
const uint32 outBufferSize = info.NumSamples * (options.BitDepth / 8); const uint32 outBufferSize = info.NumSamples * (outputBitDepth / 8);
sampleBuffer.Allocate(outBufferSize); sampleBuffer.Allocate(outBufferSize);
AudioTool::ConvertBitDepth(audioData.Get(), info.BitDepth, sampleBuffer.Get(), outputBitDepth, info.NumSamples);
AudioTool::ConvertBitDepth(audioData.Get(), info.BitDepth, sampleBuffer.Get(), options.BitDepth, info.NumSamples); info.BitDepth = outputBitDepth;
info.BitDepth = options.BitDepth;
bytesPerSample = info.BitDepth / 8; bytesPerSample = info.BitDepth / 8;
bufferSize = outBufferSize; bufferSize = outBufferSize;
} }
@@ -149,7 +125,7 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
context.Data.Header.Chunks[chunkIndex]->Data.Copy(dataPtr, dataSize); context.Data.Header.Chunks[chunkIndex]->Data.Copy(dataPtr, dataSize);
#define WRITE_DATA(chunkIndex, dataPtr, dataSize) \ #define WRITE_DATA(chunkIndex, dataPtr, dataSize) \
samplesPerChunk[chunkIndex] = (dataSize) / (options.BitDepth / 8); \ samplesPerChunk[chunkIndex] = (dataSize) / (outputBitDepth / 8); \
switch (options.Format) \ switch (options.Format) \
{ \ { \
case AudioFormat::Raw: \ case AudioFormat::Raw: \
@@ -183,8 +159,9 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
else else
{ {
// Split audio data into a several chunks (uniform data spread) // Split audio data into a several chunks (uniform data spread)
const int32 MinChunkSize = 1 * 1024 * 1024; // 1 MB const int32 minChunkSize = 1 * 1024 * 1024; // 1 MB
const int32 chunkSize = Math::Max<int32>(MinChunkSize, (int32)Math::AlignUp<uint32>(bufferSize / ASSET_FILE_DATA_CHUNKS, 256)); const int32 dataAlignment = info.NumChannels * bytesPerSample; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes)
const int32 chunkSize = Math::Max<int32>(minChunkSize, (int32)Math::AlignUp<uint32>(bufferSize / ASSET_FILE_DATA_CHUNKS, dataAlignment));
const int32 chunksCount = Math::CeilToInt((float)bufferSize / chunkSize); const int32 chunksCount = Math::CeilToInt((float)bufferSize / chunkSize);
ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS); ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS);

View File

@@ -3,12 +3,12 @@
#pragma once #pragma once
#include "Types.h" #include "Types.h"
#include "Engine/Tools/AudioTool/AudioDecoder.h"
#include "Engine/Core/ISerializable.h"
#include "Engine/Audio/Config.h"
#if COMPILE_WITH_ASSETS_IMPORTER #if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Tools/AudioTool/AudioTool.h"
#include "Engine/Tools/AudioTool/AudioDecoder.h"
/// <summary> /// <summary>
/// Enable/disable caching audio import options /// Enable/disable caching audio import options
/// </summary> /// </summary>
@@ -20,23 +20,7 @@
class ImportAudio class ImportAudio
{ {
public: public:
/// <summary> typedef AudioTool::Options Options;
/// Importing audio options
/// </summary>
struct Options : public ISerializable
{
AudioFormat Format = AudioFormat::Vorbis;
bool DisableStreaming = false;
bool Is3D = false;
int32 BitDepth = 16;
float Quality = 0.4f;
String ToString() const;
// [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};
public: public:
/// <summary> /// <summary>

View File

@@ -136,13 +136,11 @@ public:
public: public:
bool IsEnd() const bool IsEnd() const
{ {
ASSERT(_collection);
return Index() == _collection->Count(); return Index() == _collection->Count();
} }
bool IsNotEnd() const bool IsNotEnd() const
{ {
ASSERT(_collection);
return Index() != _collection->Count(); return Index() != _collection->Count();
} }
@@ -171,8 +169,6 @@ public:
public: public:
Iterator& operator++() Iterator& operator++()
{ {
ASSERT(_collection);
// Check if it is not at end // Check if it is not at end
const int32 end = _collection->Count(); const int32 end = _collection->Count();
if (Index() != end) if (Index() != end)
@@ -188,38 +184,18 @@ public:
_index = 0; _index = 0;
} }
} }
return *this; return *this;
} }
Iterator operator++(int) Iterator operator++(int)
{ {
ASSERT(_collection);
Iterator temp = *this; Iterator temp = *this;
++temp;
// Check if it is not at end
const int32 end = _collection->Count();
if (Index() != end)
{
// Move forward within chunk
_index++;
// Check if need to change chunk
if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1)
{
// Move to next chunk
_chunkIndex++;
_index = 0;
}
}
return temp; return temp;
} }
Iterator& operator--() Iterator& operator--()
{ {
ASSERT(_collection);
// Check if it's not at beginning // Check if it's not at beginning
if (_index != 0 || _chunkIndex != 0) if (_index != 0 || _chunkIndex != 0)
{ {
@@ -236,32 +212,13 @@ public:
_index--; _index--;
} }
} }
return *this; return *this;
} }
Iterator operator--(int) Iterator operator--(int)
{ {
ASSERT(_collection);
Iterator temp = *this; Iterator temp = *this;
--temp;
// Check if it's not at beginning
if (_index != 0 || _chunkIndex != 0)
{
// Check if need to change chunk
if (_index == 0)
{
// Move to previous chunk
_chunkIndex--;
_index = ChunkSize - 1;
}
else
{
// Move backward within chunk
_index--;
}
}
return temp; return temp;
} }
}; };

View File

@@ -309,7 +309,7 @@ public:
FORCE_INLINE bool operator==(const Iterator& v) const FORCE_INLINE bool operator==(const Iterator& v) const
{ {
return _index == v._index && &_collection == &v._collection; return _index == v._index && _collection == v._collection;
} }
FORCE_INLINE bool operator!=(const Iterator& v) const FORCE_INLINE bool operator!=(const Iterator& v) const

View File

@@ -73,28 +73,22 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
PoolLocker.Lock(); PoolLocker.Lock();
int32 itemsLeft; // Update timeouts and delete objects that timed out
do for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{ {
// Update timeouts and delete objects that timed out auto& bucket = *i;
itemsLeft = Pool.Count(); Object* obj = bucket.Key;
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) const float ttl = bucket.Value - ((obj->Flags & ObjectFlags::UseGameTimeForDelete) != ObjectFlags::None ? gameDelta : dt);
if (ttl <= 0.0f)
{ {
Object* obj = i->Key; Pool.Remove(i);
const float ttl = i->Value - ((obj->Flags & ObjectFlags::UseGameTimeForDelete) != ObjectFlags::None ? gameDelta : dt); obj->OnDeleteObject();
if (ttl <= 0.0f) }
{ else
Pool.Remove(i); {
obj->OnDeleteObject(); bucket.Value = ttl;
itemsLeft--;
}
else
{
i->Value = ttl;
}
} }
} }
while (itemsLeft != Pool.Count()); // Continue removing if any new item was added during removing (eg. sub-object delete with 0 timeout)
PoolLocker.Unlock(); PoolLocker.Unlock();
} }

View File

@@ -171,6 +171,8 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3
const Float3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Float3::Zero; const Float3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Float3::Zero;
const Float3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Float3::Zero; const Float3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Float3::Zero;
const Float2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Float2::Zero; const Float2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Float2::Zero;
const Color vColor = mesh.Colors.HasItems() ? mesh.Colors[vertexIndex] : Color::Black; // Assuming Color::Black as a default color
const int32 end = startIndex + searchRange; const int32 end = startIndex + searchRange;
for (size_t i = 0; i < sparialSortCache.size(); i++) for (size_t i = 0; i < sparialSortCache.size(); i++)
@@ -184,6 +186,8 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3
const Float3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Float3::Zero; const Float3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Float3::Zero;
const Float3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Float3::Zero; const Float3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Float3::Zero;
const Float2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Float2::Zero; const Float2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Float2::Zero;
const Color vColor = mesh.Colors.HasItems() ? mesh.Colors[vertexIndex] : Color::Black; // Assuming Color::Black as a default color
const int32 end = startIndex + searchRange; const int32 end = startIndex + searchRange;
for (int32 v = startIndex; v < end; v++) for (int32 v = startIndex; v < end; v++)
@@ -201,6 +205,8 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3
continue; continue;
if (mesh.LightmapUVs.HasItems() && (vLightmapUV - mesh.LightmapUVs[v]).LengthSquared() > uvEpsSqr) if (mesh.LightmapUVs.HasItems() && (vLightmapUV - mesh.LightmapUVs[v]).LengthSquared() > uvEpsSqr)
continue; continue;
if (mesh.Colors.HasItems() && vColor != mesh.Colors[v])
continue;
// TODO: check more components? // TODO: check more components?
return v; return v;

View File

@@ -39,5 +39,19 @@ namespace FlaxEngine
View = view; View = view;
} }
} }
/// <summary>
/// The rendering mask for layers. Used to exclude objects from rendering (via <see cref="View"/> property).
/// </summary>
public LayersMask ViewLayersMask
{
get => View.RenderLayersMask;
set
{
var view = View;
view.RenderLayersMask = value;
View = view;
}
}
} }
} }

View File

@@ -176,7 +176,7 @@ void RenderView::SetProjector(float nearPlane, float farPlane, const Float3& pos
CullingFrustum = Frustum; CullingFrustum = Frustum;
} }
void RenderView::CopyFrom(Camera* camera, Viewport* viewport) void RenderView::CopyFrom(const Camera* camera, const Viewport* viewport)
{ {
const Vector3 cameraPos = camera->GetPosition(); const Vector3 cameraPos = camera->GetPosition();
LargeWorlds::UpdateOrigin(Origin, cameraPos); LargeWorlds::UpdateOrigin(Origin, cameraPos);
@@ -192,6 +192,8 @@ void RenderView::CopyFrom(Camera* camera, Viewport* viewport)
Frustum.GetInvMatrix(IVP); Frustum.GetInvMatrix(IVP);
CullingFrustum = Frustum; CullingFrustum = Frustum;
RenderLayersMask = camera->RenderLayersMask; RenderLayersMask = camera->RenderLayersMask;
Flags = camera->RenderFlags;
Mode = camera->RenderMode;
} }
void RenderView::GetWorldMatrix(const Transform& transform, Matrix& world) const void RenderView::GetWorldMatrix(const Transform& transform, Matrix& world) const

View File

@@ -102,6 +102,8 @@ namespace FlaxEngine
NonJitteredProjection = Projection; NonJitteredProjection = Projection;
TemporalAAJitter = Float4.Zero; TemporalAAJitter = Float4.Zero;
RenderLayersMask = camera.RenderLayersMask; RenderLayersMask = camera.RenderLayersMask;
Flags = camera.RenderFlags;
Mode = camera.RenderMode;
UpdateCachedData(); UpdateCachedData();
} }

View File

@@ -295,10 +295,12 @@ public:
/// <param name="angle">Camera's FOV angle (in degrees)</param> /// <param name="angle">Camera's FOV angle (in degrees)</param>
void SetProjector(float nearPlane, float farPlane, const Float3& position, const Float3& direction, const Float3& up, float angle); void SetProjector(float nearPlane, float farPlane, const Float3& position, const Float3& direction, const Float3& up, float angle);
// Copy view data from camera /// <summary>
// @param camera Camera to copy its data /// Copies view data from camera to the view.
// @param camera The custom viewport to use for view/projection matrices override. /// </summary>
void CopyFrom(Camera* camera, Viewport* viewport = nullptr); /// <param name="camera">The camera to copy its data.</param>
/// <param name="viewport">The custom viewport to use for view/projection matrices override.</param>
void CopyFrom(const Camera* camera, const Viewport* viewport = nullptr);
public: public:
FORCE_INLINE DrawPass GetShadowsDrawPassMask(ShadowsCastingMode shadowsMode) const FORCE_INLINE DrawPass GetShadowsDrawPassMask(ShadowsCastingMode shadowsMode) const

View File

@@ -649,22 +649,6 @@ void GPUContextVulkan::UpdateDescriptorSets(ComputePipelineStateVulkan* pipeline
} }
} }
void GPUContextVulkan::BindPipeline()
{
if (_psDirtyFlag && _currentState && (_rtDepth || _rtCount))
{
// Clear flag
_psDirtyFlag = false;
// Change state
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
const auto pipeline = _currentState->GetState(_renderPass);
vkCmdBindPipeline(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
RENDER_STAT_PS_STATE_CHANGE();
}
}
void GPUContextVulkan::OnDrawCall() void GPUContextVulkan::OnDrawCall()
{ {
GPUPipelineStateVulkan* pipelineState = _currentState; GPUPipelineStateVulkan* pipelineState = _currentState;
@@ -704,7 +688,15 @@ void GPUContextVulkan::OnDrawCall()
BeginRenderPass(); BeginRenderPass();
} }
BindPipeline(); // Bind pipeline
if (_psDirtyFlag && _currentState && (_rtDepth || _rtCount))
{
_psDirtyFlag = false;
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
const auto pipeline = _currentState->GetState(_renderPass);
vkCmdBindPipeline(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
RENDER_STAT_PS_STATE_CHANGE();
}
//UpdateDynamicStates(); //UpdateDynamicStates();
@@ -1115,9 +1107,7 @@ void GPUContextVulkan::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCo
EndRenderPass(); EndRenderPass();
auto pipelineState = shaderVulkan->GetOrCreateState(); auto pipelineState = shaderVulkan->GetOrCreateState();
UpdateDescriptorSets(pipelineState); UpdateDescriptorSets(pipelineState);
FlushBarriers(); FlushBarriers();
// Bind pipeline // Bind pipeline
@@ -1152,10 +1142,8 @@ void GPUContextVulkan::DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* b
EndRenderPass(); EndRenderPass();
auto pipelineState = shaderVulkan->GetOrCreateState(); auto pipelineState = shaderVulkan->GetOrCreateState();
UpdateDescriptorSets(pipelineState); UpdateDescriptorSets(pipelineState);
AddBufferBarrier(bufferForArgsVulkan, VK_ACCESS_INDIRECT_COMMAND_READ_BIT); AddBufferBarrier(bufferForArgsVulkan, VK_ACCESS_INDIRECT_COMMAND_READ_BIT);
FlushBarriers(); FlushBarriers();
// Bind pipeline // Bind pipeline

View File

@@ -161,7 +161,6 @@ private:
void UpdateDescriptorSets(const struct SpirvShaderDescriptorInfo& descriptorInfo, class DescriptorSetWriterVulkan& dsWriter, bool& needsWrite); void UpdateDescriptorSets(const struct SpirvShaderDescriptorInfo& descriptorInfo, class DescriptorSetWriterVulkan& dsWriter, bool& needsWrite);
void UpdateDescriptorSets(GPUPipelineStateVulkan* pipelineState); void UpdateDescriptorSets(GPUPipelineStateVulkan* pipelineState);
void UpdateDescriptorSets(ComputePipelineStateVulkan* pipelineState); void UpdateDescriptorSets(ComputePipelineStateVulkan* pipelineState);
void BindPipeline();
void OnDrawCall(); void OnDrawCall();
public: public:

View File

@@ -886,6 +886,14 @@ void Actor::EndPlay()
OnDisable(); OnDisable();
} }
for (auto* script : Scripts)
{
CHECK_EXECUTE_IN_EDITOR
{
script->OnDestroy();
}
}
OnEndPlay(); OnEndPlay();
// Clear flag // Clear flag
@@ -899,15 +907,6 @@ void Actor::EndPlay()
e->EndPlay(); e->EndPlay();
} }
// Fire event for scripting
for (auto* script : Scripts)
{
CHECK_EXECUTE_IN_EDITOR
{
script->OnDestroy();
}
}
// Inform attached scripts // Inform attached scripts
for (int32 i = 0; i < Scripts.Count(); i++) for (int32 i = 0; i < Scripts.Count(); i++)
{ {

View File

@@ -416,6 +416,8 @@ void Camera::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(Far, _far); SERIALIZE_MEMBER(Far, _far);
SERIALIZE_MEMBER(OrthoScale, _orthoScale); SERIALIZE_MEMBER(OrthoScale, _orthoScale);
SERIALIZE(RenderLayersMask); SERIALIZE(RenderLayersMask);
SERIALIZE(RenderFlags);
SERIALIZE(RenderMode);
} }
void Camera::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) void Camera::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -430,6 +432,8 @@ void Camera::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier
DESERIALIZE_MEMBER(Far, _far); DESERIALIZE_MEMBER(Far, _far);
DESERIALIZE_MEMBER(OrthoScale, _orthoScale); DESERIALIZE_MEMBER(OrthoScale, _orthoScale);
DESERIALIZE(RenderLayersMask); DESERIALIZE(RenderLayersMask);
DESERIALIZE(RenderFlags);
DESERIALIZE(RenderMode);
} }
void Camera::OnEnable() void Camera::OnEnable()

View File

@@ -7,6 +7,7 @@
#include "Engine/Core/Math/Viewport.h" #include "Engine/Core/Math/Viewport.h"
#include "Engine/Core/Math/Ray.h" #include "Engine/Core/Math/Ray.h"
#include "Engine/Core/Types/LayersMask.h" #include "Engine/Core/Types/LayersMask.h"
#include "Engine/Graphics/Enums.h"
#include "Engine/Scripting/ScriptingObjectReference.h" #include "Engine/Scripting/ScriptingObjectReference.h"
#if USE_EDITOR #if USE_EDITOR
#include "Engine/Content/AssetReference.h" #include "Engine/Content/AssetReference.h"
@@ -134,6 +135,18 @@ public:
API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Camera\")") API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Camera\")")
LayersMask RenderLayersMask; LayersMask RenderLayersMask;
/// <summary>
/// Frame rendering flags used to switch between graphics features for this camera.
/// </summary>
API_FIELD(Attributes = "EditorOrder(110), EditorDisplay(\"Camera\")")
ViewFlags RenderFlags = ViewFlags::DefaultGame;
/// <summary>
/// Describes frame rendering modes for this camera.
/// </summary>
API_FIELD(Attributes = "EditorOrder(120), EditorDisplay(\"Camera\")")
ViewMode RenderMode = ViewMode::Default;
public: public:
/// <summary> /// <summary>
/// Projects the point from 3D world-space to game window coordinates (in screen pixels for default viewport calculated from <see cref="Viewport"/>). /// Projects the point from 3D world-space to game window coordinates (in screen pixels for default viewport calculated from <see cref="Viewport"/>).

View File

@@ -30,18 +30,18 @@ public:
API_FIELD(Attributes="EditorOrder(3), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)") API_FIELD(Attributes="EditorOrder(3), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
float SourceLength = 0.0f; float SourceLength = 0.0f;
/// <summary>
/// Controls the radial falloff of light when UseInverseSquaredFalloff is disabled.
/// </summary>
API_FIELD(Attributes="EditorOrder(13), DefaultValue(8.0f), EditorDisplay(\"Light\"), Limit(2, 16, 0.01f), VisibleIf(nameof(UseInverseSquaredFalloff), true)")
float FallOffExponent = 8.0f;
/// <summary> /// <summary>
/// Whether to use physically based inverse squared distance falloff, where Radius is only clamping the light's contribution. /// Whether to use physically based inverse squared distance falloff, where Radius is only clamping the light's contribution.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(14), DefaultValue(false), EditorDisplay(\"Light\")") API_FIELD(Attributes = "EditorOrder(13), DefaultValue(false), EditorDisplay(\"Light\")")
bool UseInverseSquaredFalloff = false; bool UseInverseSquaredFalloff = false;
/// <summary>
/// Controls the radial falloff of light when UseInverseSquaredFalloff is disabled.
/// </summary>
API_FIELD(Attributes="EditorOrder(14), DefaultValue(8.0f), EditorDisplay(\"Light\"), Limit(2, 16, 0.01f), VisibleIf(nameof(UseInverseSquaredFalloff), true)")
float FallOffExponent = 8.0f;
/// <summary> /// <summary>
/// IES texture (light profiles from real world measured data) /// IES texture (light profiles from real world measured data)
/// </summary> /// </summary>

View File

@@ -29,18 +29,18 @@ public:
API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)") API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
float SourceRadius = 0.0f; float SourceRadius = 0.0f;
/// <summary>
/// Controls the radial falloff of light when UseInverseSquaredFalloff is disabled.
/// </summary>
API_FIELD(Attributes="EditorOrder(13), DefaultValue(8.0f), EditorDisplay(\"Light\"), Limit(2, 16, 0.01f), VisibleIf(nameof(UseInverseSquaredFalloff), true)")
float FallOffExponent = 8.0f;
/// <summary> /// <summary>
/// Whether to use physically based inverse squared distance falloff, where Radius is only clamping the light's contribution. /// Whether to use physically based inverse squared distance falloff, where Radius is only clamping the light's contribution.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(14), DefaultValue(false), EditorDisplay(\"Light\")") API_FIELD(Attributes = "EditorOrder(13), DefaultValue(false), EditorDisplay(\"Light\")")
bool UseInverseSquaredFalloff = false; bool UseInverseSquaredFalloff = false;
/// <summary>
/// Controls the radial falloff of light when UseInverseSquaredFalloff is disabled.
/// </summary>
API_FIELD(Attributes="EditorOrder(14), DefaultValue(8.0f), EditorDisplay(\"Light\"), Limit(2, 16, 0.01f), VisibleIf(nameof(UseInverseSquaredFalloff), true)")
float FallOffExponent = 8.0f;
/// <summary> /// <summary>
/// IES texture (light profiles from real world measured data) /// IES texture (light profiles from real world measured data)
/// </summary> /// </summary>

View File

@@ -332,6 +332,8 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
// Serialize to json data // Serialize to json data
ASSERT(!IsCreatingPrefab); ASSERT(!IsCreatingPrefab);
IsCreatingPrefab = true; IsCreatingPrefab = true;
const Guid targetPrefabId = targetActor->GetPrefabID();
const bool hasTargetPrefabId = targetPrefabId.IsValid();
rapidjson_flax::StringBuffer actorsDataBuffer; rapidjson_flax::StringBuffer actorsDataBuffer;
{ {
CompactJsonWriter writerObj(actorsDataBuffer); CompactJsonWriter writerObj(actorsDataBuffer);
@@ -340,7 +342,27 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
for (int32 i = 0; i < sceneObjects->Count(); i++) for (int32 i = 0; i < sceneObjects->Count(); i++)
{ {
SceneObject* obj = sceneObjects->At(i); SceneObject* obj = sceneObjects->At(i);
// Detect when creating prefab from object that is already part of prefab then serialize it as unlinked
const Guid prefabId = obj->GetPrefabID();
const Guid prefabObjectId = obj->GetPrefabObjectID();
bool isObjectFromPrefab = targetPrefabId == prefabId && prefabId.IsValid(); // Allow to use other nested prefabs properly (ignore only root object's prefab link)
if (isObjectFromPrefab)
{
//obj->BreakPrefabLink();
obj->_prefabID = Guid::Empty;
obj->_prefabObjectID = Guid::Empty;
}
writer.SceneObject(obj); writer.SceneObject(obj);
// Restore broken link
if (hasTargetPrefabId)
{
//obj->LinkPrefab(prefabId, prefabObjectId);
obj->_prefabID = prefabId;
obj->_prefabObjectID = prefabObjectId;
}
} }
writer.EndArray(); writer.EndArray();
} }
@@ -406,7 +428,6 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
{ {
SceneObject* obj = sceneObjects->At(i); SceneObject* obj = sceneObjects->At(i);
Guid prefabObjectId; Guid prefabObjectId;
if (objectInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId)) if (objectInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId))
{ {
obj->LinkPrefab(assetInfo.ID, prefabObjectId); obj->LinkPrefab(assetInfo.ID, prefabObjectId);

View File

@@ -945,7 +945,7 @@ const char* ButtonCodeToKeyName(KeyboardKeys code)
// Row #6 // Row #6
case KeyboardKeys::Control: return "LCTL"; // Left Control case KeyboardKeys::Control: return "LCTL"; // Left Control
case KeyboardKeys::LeftWindows: return "LWIN"; case KeyboardKeys::LeftWindows: return "LWIN";
case KeyboardKeys::LeftMenu: return "LALT"; case KeyboardKeys::Alt: return "LALT";
case KeyboardKeys::Spacebar: return "SPCE"; case KeyboardKeys::Spacebar: return "SPCE";
case KeyboardKeys::RightMenu: return "RALT"; case KeyboardKeys::RightMenu: return "RALT";
case KeyboardKeys::RightWindows: return "RWIN"; case KeyboardKeys::RightWindows: return "RWIN";
@@ -2700,6 +2700,8 @@ Float2 LinuxPlatform::GetDesktopSize()
if (screenIdx >= count) if (screenIdx >= count)
return Float2::Zero; return Float2::Zero;
// this function is used as a fallback to place a window at the center of
// a screen so we report only one screen instead of the real desktop
Float2 size((float)xsi[screenIdx].width, (float)xsi[screenIdx].height); Float2 size((float)xsi[screenIdx].width, (float)xsi[screenIdx].height);
X11::XFree(xsi); X11::XFree(xsi);
return size; return size;
@@ -2707,14 +2709,72 @@ Float2 LinuxPlatform::GetDesktopSize()
Rectangle LinuxPlatform::GetMonitorBounds(const Float2& screenPos) Rectangle LinuxPlatform::GetMonitorBounds(const Float2& screenPos)
{ {
// TODO: do it in a proper way if (!xDisplay)
return Rectangle(Float2::Zero, GetDesktopSize()); return Rectangle::Empty;
int event, err;
const bool ok = X11::XineramaQueryExtension(xDisplay, &event, &err);
if (!ok)
return Rectangle::Empty;
int count;
int screenIdx = 0;
X11::XineramaScreenInfo* xsi = X11::XineramaQueryScreens(xDisplay, &count);
if (screenIdx >= count)
return Rectangle::Empty;
// find the screen for this screenPos
for (int i = 0; i < count; i++)
{
if (screenPos.X >= xsi[i].x_org && screenPos.X < xsi[i].x_org+xsi[i].width
&& screenPos.Y >= xsi[i].y_org && screenPos.Y < xsi[i].y_org+xsi[i].height)
{
screenIdx = i;
break;
}
}
Float2 org((float)xsi[screenIdx].x_org, (float)xsi[screenIdx].y_org);
Float2 size((float)xsi[screenIdx].width, (float)xsi[screenIdx].height);
X11::XFree(xsi);
return Rectangle(org, size);
} }
Rectangle LinuxPlatform::GetVirtualDesktopBounds() Rectangle LinuxPlatform::GetVirtualDesktopBounds()
{ {
// TODO: do it in a proper way if (!xDisplay)
return Rectangle(Float2::Zero, GetDesktopSize()); return Rectangle::Empty;
int event, err;
const bool ok = X11::XineramaQueryExtension(xDisplay, &event, &err);
if (!ok)
return Rectangle::Empty;
int count;
X11::XineramaScreenInfo* xsi = X11::XineramaQueryScreens(xDisplay, &count);
if (count <= 0)
return Rectangle::Empty;
// get all screen dimensions and assume the monitors form a rectangle
// as you can arrange monitors to your liking this is not necessarily the case
int minX = INT32_MAX, minY = INT32_MAX;
int maxX = 0, maxY = 0;
for (int i = 0; i < count; i++)
{
int maxScreenX = xsi[i].x_org + xsi[i].width;
int maxScreenY = xsi[i].y_org + xsi[i].height;
if (maxScreenX > maxX)
maxX = maxScreenX;
if (maxScreenY > maxY)
maxY = maxScreenY;
if (minX > xsi[i].x_org)
minX = xsi[i].x_org;
if (minY > xsi[i].y_org)
minY = xsi[i].y_org;
}
Float2 org(static_cast<float>(minX), static_cast<float>(minY));
Float2 size(static_cast<float>(maxX - minX), static_cast<float>(maxY - minY));
X11::XFree(xsi);
return Rectangle(org, size);
} }
String LinuxPlatform::GetMainDirectory() String LinuxPlatform::GetMainDirectory()

View File

@@ -41,8 +41,10 @@ extern Dictionary<StringAnsi, X11::KeyCode> KeyNameMap;
extern Array<KeyboardKeys> KeyCodeMap; extern Array<KeyboardKeys> KeyCodeMap;
extern X11::Cursor Cursors[(int32)CursorType::MAX]; extern X11::Cursor Cursors[(int32)CursorType::MAX];
static const uint32 MouseDoubleClickTime = 500; static constexpr uint32 MouseDoubleClickTime = 500;
static constexpr uint32 MaxDoubleClickDistanceSquared = 10;
static X11::Time MouseLastButtonPressTime = 0; static X11::Time MouseLastButtonPressTime = 0;
static Float2 OldMouseClickPosition;
LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
: WindowBase(settings) : WindowBase(settings)
@@ -153,7 +155,11 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
hints.max_height = (int)settings.MaximumSize.Y; hints.max_height = (int)settings.MaximumSize.Y;
hints.flags |= USSize; hints.flags |= USSize;
} }
X11::XSetNormalHints(display, window, &hints); // honor the WM placement except for manual (overriding) placements
if (settings.StartPosition == WindowStartPosition::Manual)
{
X11::XSetNormalHints(display, window, &hints);
}
// Ensures the child window is always on top of the parent window // Ensures the child window is always on top of the parent window
if (settings.Parent) if (settings.Parent)
@@ -595,15 +601,19 @@ void LinuxWindow::OnButtonPress(void* event)
// Handle double-click // Handle double-click
if (buttonEvent->button == Button1) if (buttonEvent->button == Button1)
{ {
if (buttonEvent->time < (MouseLastButtonPressTime + MouseDoubleClickTime)) if (
buttonEvent->time < (MouseLastButtonPressTime + MouseDoubleClickTime) &&
Float2::DistanceSquared(mousePos, OldMouseClickPosition) < MaxDoubleClickDistanceSquared)
{ {
Input::Mouse->OnMouseDoubleClick(ClientToScreen(mousePos), mouseButton, this); Input::Mouse->OnMouseDoubleClick(ClientToScreen(mousePos), mouseButton, this);
MouseLastButtonPressTime = 0; MouseLastButtonPressTime = 0;
OldMouseClickPosition = mousePos;
return; return;
} }
else else
{ {
MouseLastButtonPressTime = buttonEvent->time; MouseLastButtonPressTime = buttonEvent->time;
OldMouseClickPosition = mousePos;
} }
} }

View File

@@ -81,7 +81,6 @@ void JsonTools::ChangeIds(Document& doc, const Dictionary<Guid, Guid>& mapping)
::ChangeIds(doc, doc, mapping); ::ChangeIds(doc, doc, mapping);
} }
Float2 JsonTools::GetFloat2(const Value& value) Float2 JsonTools::GetFloat2(const Value& value)
{ {
Float2 result; Float2 result;

View File

@@ -301,7 +301,7 @@ void ShadersCompilation::RegisterForShaderReloads(Asset* asset, const String& in
{ {
// Create a directory watcher to track the included file changes // Create a directory watcher to track the included file changes
const String directory = StringUtils::GetDirectoryName(includedPath); const String directory = StringUtils::GetDirectoryName(includedPath);
if (!ShaderIncludesWatcher.ContainsKey(directory)) if (FileSystem::DirectoryExists(directory) && !ShaderIncludesWatcher.ContainsKey(directory))
{ {
auto watcher = New<FileSystemWatcher>(directory, false); auto watcher = New<FileSystemWatcher>(directory, false);
watcher->OnEvent.Bind<OnShaderIncludesWatcherEvent>(); watcher->OnEvent.Bind<OnShaderIncludesWatcherEvent>();

View File

@@ -1126,12 +1126,9 @@ bool TerrainPatch::InitializeHeightMap()
float* TerrainPatch::GetHeightmapData() float* TerrainPatch::GetHeightmapData()
{ {
PROFILE_CPU_NAMED("Terrain.GetHeightmapData"); PROFILE_CPU_NAMED("Terrain.GetHeightmapData");
if (_cachedHeightMap.HasItems()) if (_cachedHeightMap.HasItems())
return _cachedHeightMap.Get(); return _cachedHeightMap.Get();
CacheHeightData(); CacheHeightData();
return _cachedHeightMap.Get(); return _cachedHeightMap.Get();
} }
@@ -1144,12 +1141,9 @@ void TerrainPatch::ClearHeightmapCache()
byte* TerrainPatch::GetHolesMaskData() byte* TerrainPatch::GetHolesMaskData()
{ {
PROFILE_CPU_NAMED("Terrain.GetHolesMaskData"); PROFILE_CPU_NAMED("Terrain.GetHolesMaskData");
if (_cachedHolesMask.HasItems()) if (_cachedHolesMask.HasItems())
return _cachedHolesMask.Get(); return _cachedHolesMask.Get();
CacheHeightData(); CacheHeightData();
return _cachedHolesMask.Get(); return _cachedHolesMask.Get();
} }
@@ -1161,15 +1155,11 @@ void TerrainPatch::ClearHolesMaskCache()
Color32* TerrainPatch::GetSplatMapData(int32 index) Color32* TerrainPatch::GetSplatMapData(int32 index)
{ {
ASSERT(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT); CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, nullptr);
PROFILE_CPU_NAMED("Terrain.GetSplatMapData"); PROFILE_CPU_NAMED("Terrain.GetSplatMapData");
if (_cachedSplatMap[index].HasItems()) if (_cachedSplatMap[index].HasItems())
return _cachedSplatMap[index].Get(); return _cachedSplatMap[index].Get();
CacheSplatData(); CacheSplatData();
return _cachedSplatMap[index].Get(); return _cachedSplatMap[index].Get();
} }

View File

@@ -1,14 +1,49 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_AUDIO_TOOL
#include "AudioTool.h" #include "AudioTool.h"
#include "Engine/Core/Core.h" #include "Engine/Core/Core.h"
#include "Engine/Core/Memory/Allocation.h" #include "Engine/Core/Memory/Allocation.h"
#if USE_EDITOR
#include "Engine/Serialization/Serialization.h"
#include "Engine/Scripting/Enums.h"
#endif
#define CONVERT_TO_MONO_AVG 1 #define CONVERT_TO_MONO_AVG 1
#if !CONVERT_TO_MONO_AVG #if !CONVERT_TO_MONO_AVG
#include "Engine/Core/Math/Math.h" #include "Engine/Core/Math/Math.h"
#endif #endif
#if USE_EDITOR
String AudioTool::Options::ToString() const
{
return String::Format(TEXT("Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"), ScriptingEnum::ToString(Format), DisableStreaming, Is3D, Quality, (int32)BitDepth);
}
void AudioTool::Options::Serialize(SerializeStream& stream, const void* otherObj)
{
SERIALIZE_GET_OTHER_OBJ(AudioTool::Options);
SERIALIZE(Format);
SERIALIZE(DisableStreaming);
SERIALIZE(Is3D);
SERIALIZE(Quality);
SERIALIZE(BitDepth);
}
void AudioTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(Format);
DESERIALIZE(DisableStreaming);
DESERIALIZE(Is3D);
DESERIALIZE(Quality);
DESERIALIZE(BitDepth);
}
#endif
void ConvertToMono8(const int8* input, uint8* output, uint32 numSamples, uint32 numChannels) void ConvertToMono8(const int8* input, uint8* output, uint32 numSamples, uint32 numChannels)
{ {
for (uint32 i = 0; i < numSamples; i++) for (uint32 i = 0; i < numSamples; i++)
@@ -231,8 +266,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp
for (uint32 i = 0; i < numSamples; i++) for (uint32 i = 0; i < numSamples; i++)
{ {
const int8 sample = *(int8*)input; const int8 sample = *(int8*)input;
output[i] = sample / 127.0f; output[i] = sample * (1.0f / 127.0f);
input++; input++;
} }
} }
@@ -241,8 +275,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp
for (uint32 i = 0; i < numSamples; i++) for (uint32 i = 0; i < numSamples; i++)
{ {
const int16 sample = *(int16*)input; const int16 sample = *(int16*)input;
output[i] = sample / 32767.0f; output[i] = sample * (1.0f / 32767.0f);
input += 2; input += 2;
} }
} }
@@ -251,8 +284,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp
for (uint32 i = 0; i < numSamples; i++) for (uint32 i = 0; i < numSamples; i++)
{ {
const int32 sample = Convert24To32Bits(input); const int32 sample = Convert24To32Bits(input);
output[i] = sample / 2147483647.0f; output[i] = sample * (1.0f / 2147483647.0f);
input += 3; input += 3;
} }
} }
@@ -261,8 +293,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp
for (uint32 i = 0; i < numSamples; i++) for (uint32 i = 0; i < numSamples; i++)
{ {
const int32 sample = *(int32*)input; const int32 sample = *(int32*)input;
output[i] = sample / 2147483647.0f; output[i] = sample * (1.0f / 2147483647.0f);
input += 4; input += 4;
} }
} }
@@ -278,7 +309,8 @@ void AudioTool::ConvertFromFloat(const float* input, int32* output, uint32 numSa
{ {
const float sample = *(float*)input; const float sample = *(float*)input;
output[i] = static_cast<int32>(sample * 2147483647.0f); output[i] = static_cast<int32>(sample * 2147483647.0f);
input++; input++;
} }
} }
#endif

View File

@@ -2,16 +2,86 @@
#pragma once #pragma once
#if COMPILE_WITH_AUDIO_TOOL
#include "Engine/Core/Config.h" #include "Engine/Core/Config.h"
#include "Engine/Core/Types/BaseTypes.h" #include "Engine/Core/Types/BaseTypes.h"
#if USE_EDITOR
#include "Engine/Core/ISerializable.h"
#endif
#include "Engine/Audio/Types.h"
/// <summary> /// <summary>
/// Audio data importing and processing utilities. /// Audio data importing and processing utilities.
/// </summary> /// </summary>
class FLAXENGINE_API AudioTool API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API AudioTool
{ {
public: DECLARE_SCRIPTING_TYPE_MINIMAL(AudioTool);
#if USE_EDITOR
public:
/// <summary>
/// Declares the imported audio clip bit depth.
/// </summary>
API_ENUM(Attributes="HideInEditor") enum class BitDepth : int32
{
// 8-bits per sample.
_8 = 8,
// 16-bits per sample.
_16 = 16,
// 24-bits per sample.
_24 = 24,
// 32-bits per sample.
_32 = 32,
};
/// <summary>
/// Audio import options.
/// </summary>
API_STRUCT(Attributes="HideInEditor") struct FLAXENGINE_API Options : public ISerializable
{
DECLARE_SCRIPTING_TYPE_MINIMAL(Options);
/// <summary>
/// The audio data format to import the audio clip as.
/// </summary>
API_FIELD(Attributes="EditorOrder(10)")
AudioFormat Format = AudioFormat::Vorbis;
/// <summary>
/// The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Compression Quality\"), Limit(0, 1, 0.01f)")
float Quality = 0.4f;
/// <summary>
/// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).
/// </summary>
API_FIELD(Attributes="EditorOrder(30)")
bool DisableStreaming = false;
/// <summary>
/// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.
/// </summary>
API_FIELD(Attributes="EditorOrder(40), EditorDisplay(null, \"Is 3D\")")
bool Is3D = false;
/// <summary>
/// The size of a single sample in bits. The clip will be converted to this bit depth on import.
/// </summary>
API_FIELD(Attributes="EditorOrder(50), VisibleIf(nameof(ShowBtiDepth))")
BitDepth BitDepth = BitDepth::_16;
String ToString() const;
// [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};
#endif
public:
/// <summary> /// <summary>
/// Converts a set of audio samples using multiple channels into a set of mono samples. /// Converts a set of audio samples using multiple channels into a set of mono samples.
/// </summary> /// </summary>
@@ -59,3 +129,5 @@ public:
return (input[2] << 24) | (input[1] << 16) | (input[0] << 8); return (input[2] << 24) | (input[1] << 16) | (input[0] << 8);
} }
}; };
#endif

View File

@@ -314,6 +314,28 @@ namespace FlaxEngine.GUI
return false; return false;
} }
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (base.OnMouseDoubleClick(location, button))
return true;
if (button == MouseButton.Left && _isPressed)
{
OnPressEnd();
OnClick();
return true;
}
if (button == MouseButton.Left && !_isPressed)
{
OnPressBegin();
return true;
}
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnTouchDown(Float2 location, int pointerId) public override bool OnTouchDown(Float2 location, int pointerId)
{ {

View File

@@ -277,6 +277,27 @@ namespace FlaxEngine.GUI
return base.OnMouseDown(location, button); return base.OnMouseDown(location, button);
} }
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && !_isPressed)
{
OnPressBegin();
return true;
}
if (button == MouseButton.Left && _isPressed)
{
OnPressEnd();
if (_box.Contains(ref location))
{
OnClick();
return true;
}
}
return base.OnMouseDoubleClick(location, button);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {

View File

@@ -666,6 +666,30 @@ namespace FlaxEngine.GUI
return false; return false;
} }
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (base.OnMouseDoubleClick(location, button))
return true;
if (_touchDown && button == MouseButton.Left)
{
_touchDown = false;
ShowPopup();
return true;
}
if (button == MouseButton.Left)
{
_touchDown = true;
if (!IsPopupOpened)
Focus();
return true;
}
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnTouchDown(Float2 location, int pointerId) public override bool OnTouchDown(Float2 location, int pointerId)
{ {

View File

@@ -769,13 +769,13 @@ namespace FlaxEngine.GUI
{ {
SetSelection(SelectionLeft); SetSelection(SelectionLeft);
} }
else if (SelectionLeft > 0) else if (SelectionLeft >= 0)
{ {
int position; int position;
if (ctrl) if (ctrl)
position = FindPrevWordBegin(); position = FindPrevWordBegin();
else else
position = _selectionEnd - 1; position = Mathf.Max(_selectionEnd - 1, 0);
if (shift) if (shift)
{ {

View File

@@ -501,6 +501,29 @@ namespace FlaxEngine.GUI
return false; return false;
} }
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (base.OnMouseDoubleClick(location, button))
return true;
_mouseOverHeader = HeaderRectangle.Contains(location);
if (button == MouseButton.Left && _mouseOverHeader)
{
_mouseButtonLeftDown = true;
return true;
}
if (button == MouseButton.Left && _mouseButtonLeftDown)
{
_mouseButtonLeftDown = false;
if (_mouseOverHeader)
Toggle();
return true;
}
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnMouseLeave() public override void OnMouseLeave()
{ {

View File

@@ -23,9 +23,9 @@ namespace FlaxEngine.GUI
// Scrolling // Scrolling
private float _clickChange = 20, _scrollChange = 100; private float _clickChange = 20, _scrollChange = 50;
private float _minimum, _maximum = 100; private float _minimum, _maximum = 100;
private float _value, _targetValue; private float _startValue, _value, _targetValue;
private readonly Orientation _orientation; private readonly Orientation _orientation;
private RootControl.UpdateDelegate _update; private RootControl.UpdateDelegate _update;
@@ -42,6 +42,7 @@ namespace FlaxEngine.GUI
// Smoothing // Smoothing
private float _thumbOpacity = DefaultMinimumOpacity; private float _thumbOpacity = DefaultMinimumOpacity;
private float _scrollAnimationProgress = 0f;
/// <summary> /// <summary>
/// Gets the orientation. /// Gets the orientation.
@@ -59,14 +60,19 @@ namespace FlaxEngine.GUI
public float TrackThickness { get; set; } = 2.0f; public float TrackThickness { get; set; } = 2.0f;
/// <summary> /// <summary>
/// Gets or sets the value smoothing scale (0 to not use it). /// The maximum time it takes to animate from current to target scroll position
/// </summary> /// </summary>
public float SmoothingScale { get; set; } = 0.6f; public float ScrollAnimationDuration { get; set; } = 0.18f;
/// <summary> /// <summary>
/// Gets a value indicating whether use scroll value smoothing. /// Gets a value indicating whether use scroll value smoothing.
/// </summary> /// </summary>
public bool UseSmoothing => !Mathf.IsZero(SmoothingScale); public bool UseSmoothing => EnableSmoothing && !Mathf.IsZero(ScrollAnimationDuration);
/// <summary>
/// Enables scroll smoothing
/// </summary>
public bool EnableSmoothing { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the minimum value. /// Gets or sets the minimum value.
@@ -112,11 +118,15 @@ namespace FlaxEngine.GUI
if (!Mathf.NearEqual(value, _targetValue)) if (!Mathf.NearEqual(value, _targetValue))
{ {
_targetValue = value; _targetValue = value;
_startValue = _value;
_scrollAnimationProgress = 0f;
// Check if skip smoothing // Check if skip smoothing
if (!UseSmoothing) if (!UseSmoothing)
{ {
_value = value; _value = value;
_startValue = value;
_scrollAnimationProgress = 1f;
OnValueChanged(); OnValueChanged();
} }
else else
@@ -208,7 +218,8 @@ namespace FlaxEngine.GUI
{ {
if (!Mathf.NearEqual(_value, _targetValue)) if (!Mathf.NearEqual(_value, _targetValue))
{ {
_value = _targetValue; _value = _targetValue = _startValue;
_scrollAnimationProgress = 0f;
SetUpdate(ref _update, null); SetUpdate(ref _update, null);
OnValueChanged(); OnValueChanged();
} }
@@ -274,7 +285,8 @@ namespace FlaxEngine.GUI
internal void Reset() internal void Reset()
{ {
_value = _targetValue = 0; _value = _targetValue = _startValue = 0;
_scrollAnimationProgress = 0f;
} }
/// <summary> /// <summary>
@@ -296,22 +308,39 @@ namespace FlaxEngine.GUI
_thumbOpacity = isDeltaSlow ? targetOpacity : Mathf.Lerp(_thumbOpacity, targetOpacity, deltaTime * 10.0f); _thumbOpacity = isDeltaSlow ? targetOpacity : Mathf.Lerp(_thumbOpacity, targetOpacity, deltaTime * 10.0f);
bool needUpdate = Mathf.Abs(_thumbOpacity - targetOpacity) > 0.001f; bool needUpdate = Mathf.Abs(_thumbOpacity - targetOpacity) > 0.001f;
// Ensure scroll bar is visible // Ensure scroll bar is visible and smoothing is required
if (Visible) if (Visible && Mathf.Abs(_targetValue - _value) > 0.01f)
{ {
// Value smoothing // Interpolate or not if running slow
if (Mathf.Abs(_targetValue - _value) > 0.01f) float value;
if (!isDeltaSlow && UseSmoothing)
{ {
// Interpolate or not if running slow // percentage of scroll from 0 to _scrollChange, ex. 0.5 at _scrollChange / 2
float value; var minScrollChangeRatio = Mathf.Clamp(Mathf.Abs(_targetValue - _startValue) / _scrollChange, 0, 1);
if (!isDeltaSlow && UseSmoothing)
value = Mathf.Lerp(_value, _targetValue, deltaTime * 20.0f * SmoothingScale); // shorten the duration if we scrolled less than _scrollChange
else var actualDuration = ScrollAnimationDuration * minScrollChangeRatio;
value = _targetValue; var step = deltaTime / actualDuration;
_value = value;
OnValueChanged(); var progress = _scrollAnimationProgress;
needUpdate = true; progress = Mathf.Clamp(progress + step, 0, 1);
// https://easings.net/#easeOutSine
var easedProgress = Mathf.Sin((progress * Mathf.Pi) / 2);
value = Mathf.Lerp(_startValue, _targetValue, easedProgress);
_scrollAnimationProgress = progress;
} }
else
{
value = _targetValue;
_startValue = _targetValue;
_scrollAnimationProgress = 0f;
}
_value = value;
OnValueChanged();
needUpdate = true;
} }
// End updating if all animations are done // End updating if all animations are done
@@ -371,7 +400,7 @@ namespace FlaxEngine.GUI
float mousePosition = _orientation == Orientation.Vertical ? slidePosition.Y : slidePosition.X; float mousePosition = _orientation == Orientation.Vertical ? slidePosition.Y : slidePosition.X;
float percentage = (mousePosition - _mouseOffset - _thumbSize / 2) / (TrackSize - _thumbSize); float percentage = (mousePosition - _mouseOffset - _thumbSize / 2) / (TrackSize - _thumbSize);
Value = _minimum + percentage * (_maximum - _minimum); TargetValue = _minimum + percentage * (_maximum - _minimum);
} }
} }
@@ -381,7 +410,7 @@ namespace FlaxEngine.GUI
if (ThumbEnabled) if (ThumbEnabled)
{ {
// Scroll // Scroll
Value = _value - delta * _scrollChange; Value = _targetValue - delta * _scrollChange;
} }
return true; return true;
} }

View File

@@ -7,7 +7,7 @@
float ComputeLightProfileMultiplier(Texture2D tex, float3 worldPosition, float3 lightPosition, float3 lightDirection) float ComputeLightProfileMultiplier(Texture2D tex, float3 worldPosition, float3 lightPosition, float3 lightDirection)
{ {
float3 l = normalize(worldPosition - lightPosition); float3 l = normalize(worldPosition - lightPosition);
float d = dot(lightPosition, lightDirection); float d = dot(l, lightDirection);
float angle = asin(d) / PI + 0.5f; float angle = asin(d) / PI + 0.5f;
return tex.SampleLevel(SamplerLinearClamp, float2(angle, 0), 0).r; return tex.SampleLevel(SamplerLinearClamp, float2(angle, 0), 0).r;
} }

View File

@@ -154,7 +154,7 @@ namespace Flax.Build
// Run the compilation // Run the compilation
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
CSharpParseOptions parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9).WithPreprocessorSymbols(PreprocessorSymbols); CSharpParseOptions parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest).WithPreprocessorSymbols(PreprocessorSymbols);
var syntaxTrees = new List<SyntaxTree>(); var syntaxTrees = new List<SyntaxTree>();
foreach (var sourceFile in SourceFiles) foreach (var sourceFile in SourceFiles)
{ {

View File

@@ -641,6 +641,44 @@ namespace Flax.Build.Projects.VisualStudio
File.WriteAllText(e.Key, profile.ToString(), Encoding.UTF8); File.WriteAllText(e.Key, profile.ToString(), Encoding.UTF8);
} }
} }
// Generate Rider-specific configuration files
{
StringBuilder dotSettingsFileContent = new StringBuilder();
string dotSettingsUserFilePath = solution.Path + ".DotSettings.user";
// Solution settings (user layer)
bool useResharperBuild = false; // This needs to be disabled for custom build steps to run properly
if (File.Exists(dotSettingsUserFilePath))
{
foreach (var line in File.ReadAllLines(dotSettingsUserFilePath))
{
if (line.Contains(@"/UseMsbuildSolutionBuilder/@EntryValue"))
{
if (!useResharperBuild)
{
dotSettingsFileContent.Append("\t").Append(@"<s:String x:Key=""/Default/Environment/Hierarchy/Build/SolBuilderDuo/UseMsbuildSolutionBuilder/@EntryValue"">No</s:String>");
if (line.Contains("</wpf:ResourceDictionary>"))
dotSettingsFileContent.Append("</wpf:ResourceDictionary>\n");
else
dotSettingsFileContent.Append("\n");
}
continue;
}
dotSettingsFileContent.Append(line).Append("\n");
}
}
else
{
dotSettingsFileContent.Append(@"<wpf:ResourceDictionary xml:space=""preserve"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:s=""clr-namespace:System;assembly=mscorlib"" xmlns:ss=""urn:shemas-jetbrains-com:settings-storage-xaml"" xmlns:wpf=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">").Append("\n");
if (!useResharperBuild)
dotSettingsFileContent.Append("\t").Append(@"<s:String x:Key=""/Default/Environment/Hierarchy/Build/SolBuilderDuo/UseMsbuildSolutionBuilder/@EntryValue"">No</s:String>");
dotSettingsFileContent.Append("</wpf:ResourceDictionary>\n");
}
Utilities.WriteFileIfChanged(dotSettingsUserFilePath, dotSettingsFileContent.ToString());
}
} }
/// <inheritdoc /> /// <inheritdoc />