This commit is contained in:
Wojtek Figat
2025-07-19 11:14:58 +02:00
203 changed files with 3133 additions and 1177 deletions

Binary file not shown.

View File

@@ -28,6 +28,13 @@ TextureCube SkyLightTexture : register(t__SRV__);
Buffer<float4> ShadowsBuffer : register(t__SRV__); Buffer<float4> ShadowsBuffer : register(t__SRV__);
Texture2D<float> ShadowMap : register(t__SRV__); Texture2D<float> ShadowMap : register(t__SRV__);
@4// Forward Shading: Utilities @4// Forward Shading: Utilities
// Public accessors for lighting data, use them as data binding might change but those methods will remain.
LightData GetDirectionalLight() { return DirectionalLight; }
LightData GetSkyLight() { return SkyLight; }
ProbeData GetEnvironmentProbe() { return EnvironmentProbe; }
ExponentialHeightFogData GetExponentialHeightFog() { return ExponentialHeightFog; }
uint GetLocalLightsCount() { return LocalLightsCount; }
LightData GetLocalLight(uint i) { return LocalLights[i]; }
@5// Forward Shading: Shaders @5// Forward Shading: Shaders
// Pixel Shader function for Forward Pass // Pixel Shader function for Forward Pass
@@ -76,9 +83,8 @@ void PS_Forward(
gBuffer.ShadingModel = MATERIAL_SHADING_MODEL; gBuffer.ShadingModel = MATERIAL_SHADING_MODEL;
// Calculate lighting from a single directional light // Calculate lighting from a single directional light
float4 shadowMask = 1.0f;
ShadowSample shadow = SampleDirectionalLightShadow(DirectionalLight, ShadowsBuffer, ShadowMap, gBuffer); ShadowSample shadow = SampleDirectionalLightShadow(DirectionalLight, ShadowsBuffer, ShadowMap, gBuffer);
shadowMask = GetShadowMask(shadow); float4 shadowMask = GetShadowMask(shadow);
float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false);
// Calculate lighting from sky light // Calculate lighting from sky light
@@ -143,9 +149,9 @@ void PS_Forward(
#endif #endif
#if USE_FOG #if USE_FOG && MATERIAL_SHADING_MODEL != SHADING_MODEL_UNLIT
// Calculate exponential height fog // Calculate exponential height fog
float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0); float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0, gBuffer.ViewPos.z);
// Apply fog to the output color // Apply fog to the output color
#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE #if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE

View File

@@ -15,6 +15,7 @@
#include "./Flax/Common.hlsl" #include "./Flax/Common.hlsl"
#include "./Flax/MaterialCommon.hlsl" #include "./Flax/MaterialCommon.hlsl"
#include "./Flax/GBufferCommon.hlsl" #include "./Flax/GBufferCommon.hlsl"
#include "./Flax/TerrainCommon.hlsl"
@7 @7
// Primary constant buffer (with additional material parameters) // Primary constant buffer (with additional material parameters)
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
@@ -334,7 +335,7 @@ VertexOutput VS(TerrainVertexInput input)
float lodValue = CurrentLOD; float lodValue = CurrentLOD;
float morphAlpha = lodCalculated - CurrentLOD; float morphAlpha = lodCalculated - CurrentLOD;
// Sample heightmap // Sample heightmap and splatmaps
float2 heightmapUVs = input.TexCoord * HeightmapUVScaleBias.xy + HeightmapUVScaleBias.zw; float2 heightmapUVs = input.TexCoord * HeightmapUVScaleBias.xy + HeightmapUVScaleBias.zw;
#if USE_SMOOTH_LOD_TRANSITION #if USE_SMOOTH_LOD_TRANSITION
float4 heightmapValueThisLOD = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue); float4 heightmapValueThisLOD = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue);
@@ -342,7 +343,6 @@ VertexOutput VS(TerrainVertexInput input)
float2 heightmapUVsNextLOD = nextLODPos * HeightmapUVScaleBias.xy + HeightmapUVScaleBias.zw; float2 heightmapUVsNextLOD = nextLODPos * HeightmapUVScaleBias.xy + HeightmapUVScaleBias.zw;
float4 heightmapValueNextLOD = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVsNextLOD, lodValue + 1); float4 heightmapValueNextLOD = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVsNextLOD, lodValue + 1);
float4 heightmapValue = lerp(heightmapValueThisLOD, heightmapValueNextLOD, morphAlpha); float4 heightmapValue = lerp(heightmapValueThisLOD, heightmapValueNextLOD, morphAlpha);
bool isHole = max(heightmapValueThisLOD.b + heightmapValueThisLOD.a, heightmapValueNextLOD.b + heightmapValueNextLOD.a) >= 1.9f;
#if USE_TERRAIN_LAYERS #if USE_TERRAIN_LAYERS
float4 splatmapValueThisLOD = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue); float4 splatmapValueThisLOD = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue);
float4 splatmapValueNextLOD = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVsNextLOD, lodValue + 1); float4 splatmapValueNextLOD = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVsNextLOD, lodValue + 1);
@@ -355,7 +355,6 @@ VertexOutput VS(TerrainVertexInput input)
#endif #endif
#else #else
float4 heightmapValue = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue); float4 heightmapValue = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue);
bool isHole = (heightmapValue.b + heightmapValue.a) >= 1.9f;
#if USE_TERRAIN_LAYERS #if USE_TERRAIN_LAYERS
float4 splatmap0Value = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue); float4 splatmap0Value = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue);
#if TERRAIN_LAYERS_DATA_SIZE > 1 #if TERRAIN_LAYERS_DATA_SIZE > 1
@@ -363,12 +362,11 @@ VertexOutput VS(TerrainVertexInput input)
#endif #endif
#endif #endif
#endif #endif
float height = (float)((int)(heightmapValue.x * 255.0) + ((int)(heightmapValue.y * 255) << 8)) / 65535.0; float height = DecodeHeightmapHeight(heightmapValue);
// Extract normal and the holes mask // Extract normal and the holes mask
float2 normalTemp = float2(heightmapValue.b, heightmapValue.a) * 2.0f - 1.0f; bool isHole;
float3 normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y); float3 normal = DecodeHeightmapNormal(heightmapValue, isHole);
normal = normalize(normal);
output.Geometry.HolesMask = isHole ? 0 : 1; output.Geometry.HolesMask = isHole ? 0 : 1;
if (isHole) if (isHole)
{ {

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@@ -267,6 +267,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=comperand/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=comperand/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=coord/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=coord/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=cubemap/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=cubemap/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=DDGI/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deformer/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Deformer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=deformers/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=deformers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=defragmentation/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=defragmentation/@EntryIndexedValue">True</s:Boolean>

View File

@@ -20,13 +20,6 @@ class PlatformTools;
#define GAME_BUILD_DOTNET_RUNTIME_MAX_VER 9 #define GAME_BUILD_DOTNET_RUNTIME_MAX_VER 9
#endif #endif
#if OFFICIAL_BUILD
// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=" MACRO_TO_STR(GAME_BUILD_DOTNET_RUNTIME_MIN_VER))
#else
#define GAME_BUILD_DOTNET_VER TEXT("")
#endif
/// <summary> /// <summary>
/// Game building options. Used as flags. /// Game building options. Used as flags.
/// </summary> /// </summary>
@@ -374,6 +367,8 @@ public:
/// </summary> /// </summary>
void GetBuildPlatformName(const Char*& platform, const Char*& architecture) const; void GetBuildPlatformName(const Char*& platform, const Char*& architecture) const;
String GetDotnetCommandArg() const;
public: public:
/// <summary> /// <summary>

View File

@@ -312,6 +312,14 @@ void CookingData::GetBuildPlatformName(const Char*& platform, const Char*& archi
} }
} }
String CookingData::GetDotnetCommandArg() const
{
int32 version = Tools->GetDotnetVersion();
if (version == 0)
return String::Empty;
return String::Format(TEXT("-dotnet={}"), version);
}
void CookingData::StepProgress(const String& info, const float stepProgress) const void CookingData::StepProgress(const String& info, const float stepProgress) const
{ {
const float singleStepProgress = 1.0f / (StepsCount + 1); const float singleStepProgress = 1.0f / (StepsCount + 1);

View File

@@ -195,4 +195,9 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
return false; return false;
} }
int32 GDKPlatformTools::GetDotnetVersion() const
{
return GAME_BUILD_DOTNET_RUNTIME_MIN_VER;
}
#endif #endif

View File

@@ -26,6 +26,7 @@ public:
public: public:
// [PlatformTools] // [PlatformTools]
int32 GetDotnetVersion() const override;
DotNetAOTModes UseAOT() const override; DotNetAOTModes UseAOT() const override;
bool OnDeployBinaries(CookingData& data) override; bool OnDeployBinaries(CookingData& data) override;
}; };

View File

@@ -70,6 +70,20 @@ public:
/// </summary> /// </summary>
virtual ArchitectureType GetArchitecture() const = 0; virtual ArchitectureType GetArchitecture() const = 0;
/// <summary>
/// Gets the .Net version to use for the cooked game.
/// </summary>
virtual int32 GetDotnetVersion() const
{
#if OFFICIAL_BUILD
// Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it)
return GAME_BUILD_DOTNET_RUNTIME_MIN_VER;
#else
// Use the highest version found on a system (Flax.Build will decide)
return 0;
#endif
}
/// <summary> /// <summary>
/// Gets the value indicating whenever platform requires AOT (needs C# assemblies to be precompiled). /// Gets the value indicating whenever platform requires AOT (needs C# assemblies to be precompiled).
/// </summary> /// </summary>

View File

@@ -189,7 +189,7 @@ bool CompileScriptsStep::Perform(CookingData& data)
const String logFile = data.CacheDirectory / TEXT("CompileLog.txt"); const String logFile = data.CacheDirectory / TEXT("CompileLog.txt");
auto args = String::Format( auto args = String::Format(
TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5} {6}"), TEXT("-log -logfile=\"{4}\" -build -mutex -buildtargets={0} -platform={1} -arch={2} -configuration={3} -aotMode={5} {6}"),
target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()), GAME_BUILD_DOTNET_VER); target, platform, architecture, configuration, logFile, ToString(data.Tools->UseAOT()), data.GetDotnetCommandArg());
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
if (data.Platform == BuildPlatform::LinuxX64) if (data.Platform == BuildPlatform::LinuxX64)
#elif PLATFORM_LINUX #elif PLATFORM_LINUX

View File

@@ -88,7 +88,7 @@ bool DeployDataStep::Perform(CookingData& data)
{ {
// Ask Flax.Build to provide .NET SDK location for the current platform // Ask Flax.Build to provide .NET SDK location for the current platform
String sdks; String sdks;
bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), GAME_BUILD_DOTNET_VER), data.CacheDirectory); bool failed = ScriptsBuilder::RunBuildTool(String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printSDKs {}"), data.GetDotnetCommandArg()), data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks); failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
int32 idx = sdks.Find(TEXT("DotNetSdk, "), StringSearchCase::CaseSensitive); int32 idx = sdks.Find(TEXT("DotNetSdk, "), StringSearchCase::CaseSensitive);
if (idx != -1) if (idx != -1)
@@ -200,7 +200,7 @@ bool DeployDataStep::Perform(CookingData& data)
String sdks; String sdks;
const Char *platformName, *archName; const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName); data.GetBuildPlatformName(platformName, archName);
String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={} {}"), platformName, archName, GAME_BUILD_DOTNET_VER); String args = String::Format(TEXT("-log -logMessagesOnly -logFileWithConsole -logfile=SDKs.txt -printDotNetRuntime -platform={} -arch={} {}"), platformName, archName, data.GetDotnetCommandArg());
bool failed = ScriptsBuilder::RunBuildTool(args, data.CacheDirectory); bool failed = ScriptsBuilder::RunBuildTool(args, data.CacheDirectory);
failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks); failed |= File::ReadAllText(data.CacheDirectory / TEXT("SDKs.txt"), sdks);
Array<String> parts; Array<String> parts;
@@ -244,10 +244,13 @@ bool DeployDataStep::Perform(CookingData& data)
} }
if (version.IsEmpty()) if (version.IsEmpty())
{ {
int32 minVer = GAME_BUILD_DOTNET_RUNTIME_MIN_VER, maxVer = GAME_BUILD_DOTNET_RUNTIME_MAX_VER;
if (srcDotnetFromEngine) if (srcDotnetFromEngine)
{ {
// Detect version from runtime files inside Engine Platform folder // Detect version from runtime files inside Engine Platform folder
for (int32 i = GAME_BUILD_DOTNET_RUNTIME_MAX_VER; i >= GAME_BUILD_DOTNET_RUNTIME_MIN_VER; i--) if (data.Tools->GetDotnetVersion() != 0)
minVer = maxVer = data.Tools->GetDotnetVersion();
for (int32 i = maxVer; i >= minVer; i--)
{ {
// Check runtime files inside Engine Platform folder // Check runtime files inside Engine Platform folder
String testPath1 = srcDotnet / String::Format(TEXT("lib/net{}.0"), i); String testPath1 = srcDotnet / String::Format(TEXT("lib/net{}.0"), i);
@@ -262,7 +265,7 @@ bool DeployDataStep::Perform(CookingData& data)
} }
if (version.IsEmpty()) if (version.IsEmpty())
{ {
data.Error(String::Format(TEXT("Failed to find supported .NET {} version for the current host platform."), GAME_BUILD_DOTNET_RUNTIME_MIN_VER)); data.Error(String::Format(TEXT("Failed to find supported .NET {} version (min {}) for the current host platform."), maxVer, minVer));
return true; return true;
} }
} }
@@ -364,7 +367,7 @@ bool DeployDataStep::Perform(CookingData& data)
const String logFile = data.CacheDirectory / TEXT("StripDotnetLibs.txt"); const String logFile = data.CacheDirectory / TEXT("StripDotnetLibs.txt");
String args = String::Format( String args = String::Format(
TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\" {}"), TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\" {}"),
logFile, data.DataOutputPath, GAME_BUILD_DOTNET_VER); logFile, data.DataOutputPath, data.GetDotnetCommandArg());
for (const String& define : data.CustomDefines) for (const String& define : data.CustomDefines)
{ {
args += TEXT(" -D"); args += TEXT(" -D");

View File

@@ -69,7 +69,7 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
const String logFile = data.CacheDirectory / TEXT("AOTLog.txt"); const String logFile = data.CacheDirectory / TEXT("AOTLog.txt");
String args = String::Format( String args = String::Format(
TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\" {}"), TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\" {}"),
logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath, GAME_BUILD_DOTNET_VER); logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath, data.GetDotnetCommandArg());
if (!buildSettings.SkipUnusedDotnetLibsPackaging) if (!buildSettings.SkipUnusedDotnetLibsPackaging)
args += TEXT(" -skipUnusedDotnetLibs=false"); // Run AOT on whole class library (not just used libs) args += TEXT(" -skipUnusedDotnetLibs=false"); // Run AOT on whole class library (not just used libs)
for (const String& define : data.CustomDefines) for (const String& define : data.CustomDefines)

View File

@@ -1,6 +1,5 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using FlaxEditor.Content.Settings; using FlaxEditor.Content.Settings;
using FlaxEngine; using FlaxEngine;
@@ -16,6 +15,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
{ {
private int _layersCount; private int _layersCount;
private List<CheckBox> _checkBoxes; private List<CheckBox> _checkBoxes;
private VerticalPanel _upperRightCell;
private VerticalPanel _bottomLeftCell;
private UniformGridPanel _grid;
private Border _horizontalHighlight;
private Border _verticalHighlight;
/// <inheritdoc /> /// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.InlineIntoParent; public override DisplayStyle Style => DisplayStyle.InlineIntoParent;
@@ -37,12 +41,29 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = panel, Parent = panel,
}; };
var style = FlaxEngine.GUI.Style.Current;
_horizontalHighlight = new Border()
{
Parent = panel,
BorderColor = style.Foreground,
BorderWidth = 1.0f,
Visible = false,
};
_verticalHighlight = new Border()
{
Parent = panel,
BorderColor = style.Foreground,
BorderWidth = 1.0f,
Visible = false,
};
var upperLeftCell = new Label var upperLeftCell = new Label
{ {
Parent = gridPanel, Parent = gridPanel,
}; };
var upperRightCell = new VerticalPanel _upperRightCell = new VerticalPanel
{ {
ClipChildren = false, ClipChildren = false,
Pivot = new Float2(0.00001f, 0.0f), Pivot = new Float2(0.00001f, 0.0f),
@@ -54,7 +75,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = gridPanel, Parent = gridPanel,
}; };
var bottomLeftCell = new VerticalPanel _bottomLeftCell = new VerticalPanel
{ {
Pivot = Float2.Zero, Pivot = Float2.Zero,
Spacing = 0, Spacing = 0,
@@ -63,7 +84,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = gridPanel, Parent = gridPanel,
}; };
var grid = new UniformGridPanel(0) _grid = new UniformGridPanel(0)
{ {
SlotsHorizontally = layersCount, SlotsHorizontally = layersCount,
SlotsVertically = layersCount, SlotsVertically = layersCount,
@@ -74,13 +95,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
int layerIndex = 0; int layerIndex = 0;
for (; layerIndex < layerNames.Length; layerIndex++) for (; layerIndex < layerNames.Length; layerIndex++)
{ {
upperRightCell.AddChild(new Label _upperRightCell.AddChild(new Label
{ {
Height = labelsHeight, Height = labelsHeight,
Text = layerNames[layerNames.Length - layerIndex - 1], Text = layerNames[layerNames.Length - layerIndex - 1],
HorizontalAlignment = TextAlignment.Near, HorizontalAlignment = TextAlignment.Near,
}); });
bottomLeftCell.AddChild(new Label _bottomLeftCell.AddChild(new Label
{ {
Height = labelsHeight, Height = labelsHeight,
Text = layerNames[layerIndex], Text = layerNames[layerIndex],
@@ -90,13 +111,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
for (; layerIndex < layersCount; layerIndex++) for (; layerIndex < layersCount; layerIndex++)
{ {
string name = "Layer " + layerIndex; string name = "Layer " + layerIndex;
upperRightCell.AddChild(new Label _upperRightCell.AddChild(new Label
{ {
Height = labelsHeight, Height = labelsHeight,
Text = name, Text = name,
HorizontalAlignment = TextAlignment.Near, HorizontalAlignment = TextAlignment.Near,
}); });
bottomLeftCell.AddChild(new Label _bottomLeftCell.AddChild(new Label
{ {
Height = labelsHeight, Height = labelsHeight,
Text = name, Text = name,
@@ -118,7 +139,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var box = new CheckBox(0, 0, true) var box = new CheckBox(0, 0, true)
{ {
Tag = new Float2(_layersCount - column - 1, row), Tag = new Float2(_layersCount - column - 1, row),
Parent = grid, Parent = _grid,
Checked = GetBit(column, row), Checked = GetBit(column, row),
}; };
box.StateChanged += OnCheckBoxChanged; box.StateChanged += OnCheckBoxChanged;
@@ -126,7 +147,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
} }
for (; column < layersCount; column++) for (; column < layersCount; column++)
{ {
grid.AddChild(new Label()); _grid.AddChild(new Label());
} }
} }
} }
@@ -141,6 +162,18 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// <inheritdoc /> /// <inheritdoc />
public override void Refresh() public override void Refresh()
{ {
int selectedColumn = -1;
int selectedRow = -1;
var style = FlaxEngine.GUI.Style.Current;
bool mouseOverGrid = _grid.IsMouseOver;
// Only hide highlights if mouse is not over the grid to reduce flickering
if (!mouseOverGrid)
{
_horizontalHighlight.Visible = false;
_verticalHighlight.Visible = false;
}
// Sync check boxes // Sync check boxes
for (int i = 0; i < _checkBoxes.Count; i++) for (int i = 0; i < _checkBoxes.Count; i++)
{ {
@@ -148,6 +181,39 @@ namespace FlaxEditor.CustomEditors.Dedicated
int column = (int)((Float2)box.Tag).X; int column = (int)((Float2)box.Tag).X;
int row = (int)((Float2)box.Tag).Y; int row = (int)((Float2)box.Tag).Y;
box.Checked = GetBit(column, row); box.Checked = GetBit(column, row);
if (box.IsMouseOver)
{
selectedColumn = column;
selectedRow = row;
_horizontalHighlight.X = _grid.X - _bottomLeftCell.Width;
_horizontalHighlight.Y = _grid.Y + box.Y;
_horizontalHighlight.Width = _bottomLeftCell.Width + box.Width + box.X;
_horizontalHighlight.Height = box.Height;
_horizontalHighlight.Visible = true;
_verticalHighlight.X = _grid.X + box.X;
_verticalHighlight.Y = _grid.Y - _upperRightCell.Height;
_verticalHighlight.Width = box.Width;
_verticalHighlight.Height = _upperRightCell.Height + box.Height + box.Y;
_verticalHighlight.Visible = true;
}
}
for (int i = 0; i < _checkBoxes.Count; i++)
{
var box = _checkBoxes[i];
int column = (int)((Float2)box.Tag).X;
int row = (int)((Float2)box.Tag).Y;
if (!mouseOverGrid)
box.ImageColor = style.BorderSelected;
else if (selectedColumn > -1 && selectedRow > -1)
{
bool isRowOrColumn = column == selectedColumn || row == selectedRow;
box.ImageColor = style.BorderSelected * (isRowOrColumn ? 1.2f : 0.75f);
}
} }
} }

View File

@@ -26,6 +26,7 @@ namespace FlaxEditor.CustomEditors.Editors
new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)), new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)),
new OptionType("Sprite 9-Slicing", typeof(Sprite9SlicingBrush)), new OptionType("Sprite 9-Slicing", typeof(Sprite9SlicingBrush)),
new OptionType("Video", typeof(VideoBrush)), new OptionType("Video", typeof(VideoBrush)),
new OptionType("UI Brush", typeof(UIBrush)),
}; };
} }
} }

View File

@@ -604,6 +604,9 @@ namespace FlaxEditor.CustomEditors.Editors
root.SortChildrenRecursive(); root.SortChildrenRecursive();
root.Expand(true); root.Expand(true);
if (Input.GetKey(KeyboardKeys.Shift))
root.ExpandAll(true);
return menu; return menu;
} }
} }

View File

@@ -287,10 +287,7 @@ namespace FlaxEditor.CustomEditors
/// <returns>The created element.</returns> /// <returns>The created element.</returns>
public ImageElement Image(SpriteHandle sprite) public ImageElement Image(SpriteHandle sprite)
{ {
var element = new ImageElement(); return Image(new SpriteBrush(sprite));
element.Image.Brush = new SpriteBrush(sprite);
OnAddElement(element);
return element;
} }
/// <summary> /// <summary>
@@ -300,10 +297,7 @@ namespace FlaxEditor.CustomEditors
/// <returns>The created element.</returns> /// <returns>The created element.</returns>
public ImageElement Image(Texture texture) public ImageElement Image(Texture texture)
{ {
var element = new ImageElement(); return Image(new TextureBrush(texture));
element.Image.Brush = new TextureBrush(texture);
OnAddElement(element);
return element;
} }
/// <summary> /// <summary>
@@ -312,9 +306,19 @@ namespace FlaxEditor.CustomEditors
/// <param name="texture">The GPU texture.</param> /// <param name="texture">The GPU texture.</param>
/// <returns>The created element.</returns> /// <returns>The created element.</returns>
public ImageElement Image(GPUTexture texture) public ImageElement Image(GPUTexture texture)
{
return Image(new GPUTextureBrush(texture));
}
/// <summary>
/// Adds brush image to the layout.
/// </summary>
/// <param name="brush">The brush.</param>
/// <returns>The created element.</returns>
public ImageElement Image(IBrush brush)
{ {
var element = new ImageElement(); var element = new ImageElement();
element.Image.Brush = new GPUTextureBrush(texture); element.Image.Brush = brush;
OnAddElement(element); OnAddElement(element);
return element; return element;
} }

View File

@@ -670,6 +670,8 @@ namespace FlaxEditor
{ {
FlaxEngine.Networking.NetworkManager.Stop(); // Shutdown any multiplayer from playmode FlaxEngine.Networking.NetworkManager.Stop(); // Shutdown any multiplayer from playmode
PlayModeEnding?.Invoke(); PlayModeEnding?.Invoke();
for (int i = 0; i < _modules.Count; i++)
_modules[i].OnPlayEnding();
} }
internal void OnPlayEnd() internal void OnPlayEnd()

View File

@@ -299,6 +299,7 @@ namespace FlaxEditor.GUI
{ {
// Select asset // Select asset
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem); Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
} }
} }
else if (Button1Rect.Contains(location)) else if (Button1Rect.Contains(location))
@@ -312,6 +313,7 @@ namespace FlaxEditor.GUI
{ {
// Select asset // Select asset
Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem); Editor.Instance.Windows.ContentWin.Select(Validator.SelectedItem);
Editor.Instance.Windows.ContentWin.ClearItemsSearch();
} }
else if (Button3Rect.Contains(location)) else if (Button3Rect.Contains(location))
{ {

View File

@@ -114,9 +114,10 @@ namespace FlaxEditor.GUI.ContextMenu
public ContextMenuBase() public ContextMenuBase()
: base(0, 0, 120, 32) : base(0, 0, 120, 32)
{ {
_direction = ContextMenuDirection.RightDown;
Visible = false; Visible = false;
AutoFocus = true;
_direction = ContextMenuDirection.RightDown;
_isSubMenu = true; _isSubMenu = true;
} }

View File

@@ -76,6 +76,8 @@ namespace FlaxEditor.GUI.Dialogs
public ColorSelector(float wheelSize) public ColorSelector(float wheelSize)
: base(0, 0, wheelSize, wheelSize) : base(0, 0, wheelSize, wheelSize)
{ {
AutoFocus = true;
_colorWheelSprite = Editor.Instance.Icons.ColorWheel128; _colorWheelSprite = Editor.Instance.Icons.ColorWheel128;
_wheelRect = new Rectangle(0, 0, wheelSize, wheelSize); _wheelRect = new Rectangle(0, 0, wheelSize, wheelSize);
} }

View File

@@ -65,8 +65,6 @@ namespace FlaxEditor.GUI.Docking
internal DockPanelProxy(DockPanel panel) internal DockPanelProxy(DockPanel panel)
: base(0, 0, 64, 64) : base(0, 0, 64, 64)
{ {
AutoFocus = false;
_panel = panel; _panel = panel;
AnchorPreset = AnchorPresets.StretchAll; AnchorPreset = AnchorPresets.StretchAll;
Offsets = Margin.Zero; Offsets = Margin.Zero;

View File

@@ -368,6 +368,8 @@ namespace FlaxEditor.GUI.Input
public SliderControl(float value, float x = 0, float y = 0, float width = 120, float min = float.MinValue, float max = float.MaxValue) public SliderControl(float value, float x = 0, float y = 0, float width = 120, float min = float.MinValue, float max = float.MaxValue)
: base(x, y, width, TextBox.DefaultHeight) : base(x, y, width, TextBox.DefaultHeight)
{ {
AutoFocus = true;
_min = min; _min = min;
_max = max; _max = max;
_value = Mathf.Clamp(value, min, max); _value = Mathf.Clamp(value, min, max);

View File

@@ -227,9 +227,8 @@ namespace FlaxEditor.GUI
{ {
int order = -1 * SortScore.CompareTo(otherItem.SortScore); int order = -1 * SortScore.CompareTo(otherItem.SortScore);
if (order == 0) if (order == 0)
{
order = string.Compare(Name, otherItem.Name, StringComparison.Ordinal); order = string.Compare(Name, otherItem.Name, StringComparison.Ordinal);
}
return order; return order;
} }
return base.Compare(other); return base.Compare(other);
@@ -509,7 +508,7 @@ namespace FlaxEditor.GUI
OnSearchFilterChanged(); OnSearchFilterChanged();
} }
private List<Item> GetVisibleItems() private List<Item> GetVisibleItems(bool ignoreFoldedCategories)
{ {
var result = new List<Item>(); var result = new List<Item>();
var items = ItemsPanel.Children; var items = ItemsPanel.Children;
@@ -523,7 +522,7 @@ namespace FlaxEditor.GUI
for (int i = 0; i < _categoryPanels.Count; i++) for (int i = 0; i < _categoryPanels.Count; i++)
{ {
var category = _categoryPanels[i]; var category = _categoryPanels[i];
if (!category.Visible) if (!category.Visible || (ignoreFoldedCategories && category is DropPanel panel && panel.IsClosed))
continue; continue;
for (int j = 0; j < category.Children.Count; j++) for (int j = 0; j < category.Children.Count; j++)
{ {
@@ -535,6 +534,12 @@ namespace FlaxEditor.GUI
return result; return result;
} }
private void ExpandToItem(Item item)
{
if (item.Parent is DropPanel dropPanel)
dropPanel.Open(false);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnShow() protected override void OnShow()
{ {
@@ -563,8 +568,17 @@ namespace FlaxEditor.GUI
case KeyboardKeys.Escape: case KeyboardKeys.Escape:
Hide(); Hide();
return true; return true;
case KeyboardKeys.Backspace:
// Allow the user to quickly focus the searchbar
if (_searchBox != null && !_searchBox.IsFocused)
{
_searchBox.Focus();
_searchBox.SelectAll();
return true;
}
break;
case KeyboardKeys.ArrowDown: case KeyboardKeys.ArrowDown:
{ case KeyboardKeys.ArrowUp:
if (RootWindow.FocusedControl == null) if (RootWindow.FocusedControl == null)
{ {
// Focus search box if nothing is focused // Focus search box if nothing is focused
@@ -572,39 +586,28 @@ namespace FlaxEditor.GUI
return true; return true;
} }
// Focus the first visible item or then next one // Get the next item
var items = GetVisibleItems(); bool controlDown = Root.GetKey(KeyboardKeys.Control);
var items = GetVisibleItems(!controlDown);
var focusedIndex = items.IndexOf(focusedItem); var focusedIndex = items.IndexOf(focusedItem);
if (focusedIndex == -1)
focusedIndex = -1; // If the user hasn't selected anything yet and is holding control, focus first folded item
if (focusedIndex + 1 < items.Count) if (focusedIndex == -1 && controlDown)
{ focusedIndex = GetVisibleItems(true).Count - 1;
var item = items[focusedIndex + 1];
item.Focus(); int delta = key == KeyboardKeys.ArrowDown ? -1 : 1;
_scrollPanel.ScrollViewTo(item); int nextIndex = Mathf.Wrap(focusedIndex - delta, 0, items.Count - 1);
return true; var nextItem = items[nextIndex];
}
break; // Focus the next item
} nextItem.Focus();
case KeyboardKeys.ArrowUp:
if (focusedItem != null) // Allow the user to expand groups while scrolling
{ if (controlDown)
// Focus the previous visible item or the search box ExpandToItem(nextItem);
var items = GetVisibleItems();
var focusedIndex = items.IndexOf(focusedItem); _scrollPanel.ScrollViewTo(nextItem);
if (focusedIndex == 0) return true;
{
_searchBox?.Focus();
}
else if (focusedIndex > 0)
{
var item = items[focusedIndex - 1];
item.Focus();
_scrollPanel.ScrollViewTo(item);
return true;
}
}
break;
case KeyboardKeys.Return: case KeyboardKeys.Return:
if (focusedItem != null) if (focusedItem != null)
{ {
@@ -614,7 +617,7 @@ namespace FlaxEditor.GUI
else else
{ {
// Select first item if no item is focused (most likely to be the best result), saves the user from pressing arrow down first // Select first item if no item is focused (most likely to be the best result), saves the user from pressing arrow down first
var visibleItems = GetVisibleItems(); var visibleItems = GetVisibleItems(true);
if (visibleItems.Count > 0) if (visibleItems.Count > 0)
{ {
OnClickItem(visibleItems[0]); OnClickItem(visibleItems[0]);

View File

@@ -266,6 +266,19 @@ namespace FlaxEditor.GUI
return AddChild(new MainMenuButton(text)); return AddChild(new MainMenuButton(text));
} }
/// <summary>
/// Gets or adds a button.
/// </summary>
/// <param name="text">The button text</param>
/// <returns>The existing or created button control.</returns>
public MainMenuButton GetOrAddButton(string text)
{
MainMenuButton result = GetButton(text);
if (result == null)
result = AddButton(text);
return result;
}
/// <summary> /// <summary>
/// Gets the button. /// Gets the button.
/// </summary> /// </summary>

View File

@@ -122,6 +122,14 @@ namespace FlaxEditor.GUI
return this; return this;
} }
private void OnClicked()
{
if (AutoCheck)
Checked = !Checked;
Clicked?.Invoke();
(Parent as ToolStrip)?.OnButtonClicked(this);
}
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {
@@ -196,11 +204,7 @@ namespace FlaxEditor.GUI
if (button == MouseButton.Left && _primaryMouseDown) if (button == MouseButton.Left && _primaryMouseDown)
{ {
_primaryMouseDown = false; _primaryMouseDown = false;
if (AutoCheck) OnClicked();
Checked = !Checked;
Clicked?.Invoke();
(Parent as ToolStrip)?.OnButtonClicked(this);
return true; return true;
} }
if (button == MouseButton.Right && _secondaryMouseDown) if (button == MouseButton.Right && _secondaryMouseDown)
@@ -215,6 +219,18 @@ namespace FlaxEditor.GUI
return base.OnMouseUp(location, button); return base.OnMouseUp(location, button);
} }
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
if (button == MouseButton.Left)
{
OnClicked();
return true;
}
return false;
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnMouseLeave() public override void OnMouseLeave()
{ {

View File

@@ -73,6 +73,11 @@ namespace FlaxEditor.GUI.Tree
/// </summary> /// </summary>
public bool DrawRootTreeLine = true; public bool DrawRootTreeLine = true;
/// <summary>
/// Occurs when the deferred layout operation was performed.
/// </summary>
public event Action AfterDeferredLayout;
/// <summary> /// <summary>
/// Gets or sets the margin for the child tree nodes. /// Gets or sets the margin for the child tree nodes.
/// </summary> /// </summary>
@@ -375,6 +380,7 @@ namespace FlaxEditor.GUI.Tree
if (_deferLayoutUpdate) if (_deferLayoutUpdate)
{ {
base.PerformLayout(); base.PerformLayout();
AfterDeferredLayout?.Invoke();
_deferLayoutUpdate = false; _deferLayoutUpdate = false;
} }

View File

@@ -319,6 +319,8 @@ namespace FlaxEditor.GUI.Tree
public TreeNode(bool canChangeOrder, SpriteHandle iconCollapsed, SpriteHandle iconOpened) public TreeNode(bool canChangeOrder, SpriteHandle iconCollapsed, SpriteHandle iconOpened)
: base(0, 0, 64, 16) : base(0, 0, 64, 16)
{ {
AutoFocus = true;
_canChangeOrder = canChangeOrder; _canChangeOrder = canChangeOrder;
_animationProgress = 1.0f; _animationProgress = 1.0f;
_cachedHeight = _headerHeight; _cachedHeight = _headerHeight;

View File

@@ -155,6 +155,7 @@ namespace FlaxEditor.Gizmo
// Ensure player is not moving objects // Ensure player is not moving objects
if (ActiveAxis != Axis.None) if (ActiveAxis != Axis.None)
return; return;
Profiler.BeginEvent("Pick");
// Get mouse ray and try to hit any object // Get mouse ray and try to hit any object
var ray = Owner.MouseRay; var ray = Owner.MouseRay;
@@ -243,6 +244,8 @@ namespace FlaxEditor.Gizmo
{ {
sceneEditing.Deselect(); sceneEditing.Deselect();
} }
Profiler.EndEvent();
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -13,6 +13,7 @@
#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h" #include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
#include "Engine/Content/Assets/VisualScript.h" #include "Engine/Content/Assets/VisualScript.h"
#include "Engine/Content/Content.h" #include "Engine/Content/Content.h"
#include "Engine/Level/Actor.h"
#include "Engine/CSG/CSGBuilder.h" #include "Engine/CSG/CSGBuilder.h"
#include "Engine/Engine/CommandLine.h" #include "Engine/Engine/CommandLine.h"
#include "Engine/Renderer/ProbesRenderer.h" #include "Engine/Renderer/ProbesRenderer.h"
@@ -74,7 +75,7 @@ void OnLightmapsBuildFinished(bool failed)
OnLightmapsBake(ShadowsOfMordor::BuildProgressStep::GenerateLightmapCharts, 0, 0, false); OnLightmapsBake(ShadowsOfMordor::BuildProgressStep::GenerateLightmapCharts, 0, 0, false);
} }
void OnBakeEvent(bool started, const ProbesRenderer::Entry& e) void OnBakeEvent(bool started, Actor* e)
{ {
if (Internal_EnvProbeBake == nullptr) if (Internal_EnvProbeBake == nullptr)
{ {
@@ -82,7 +83,7 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
ASSERT(Internal_EnvProbeBake); ASSERT(Internal_EnvProbeBake);
} }
MObject* probeObj = e.Actor ? e.Actor->GetManagedInstance() : nullptr; MObject* probeObj = e ? e->GetManagedInstance() : nullptr;
MainThreadManagedInvokeAction::ParamsBuilder params; MainThreadManagedInvokeAction::ParamsBuilder params;
params.AddParam(started); params.AddParam(started);
@@ -90,12 +91,12 @@ void OnBakeEvent(bool started, const ProbesRenderer::Entry& e)
MainThreadManagedInvokeAction::Invoke(Internal_EnvProbeBake, params); MainThreadManagedInvokeAction::Invoke(Internal_EnvProbeBake, params);
} }
void OnRegisterBake(const ProbesRenderer::Entry& e) void OnRegisterBake(Actor* e)
{ {
OnBakeEvent(true, e); OnBakeEvent(true, e);
} }
void OnFinishBake(const ProbesRenderer::Entry& e) void OnFinishBake(Actor* e)
{ {
OnBakeEvent(false, e); OnBakeEvent(false, e);
} }

View File

@@ -76,6 +76,13 @@ namespace FlaxEditor.Modules
{ {
} }
/// <summary>
/// Called when Editor will leave the play mode.
/// </summary>
public virtual void OnPlayEnding()
{
}
/// <summary> /// <summary>
/// Called when Editor leaves the play mode. /// Called when Editor leaves the play mode.
/// </summary> /// </summary>

View File

@@ -711,7 +711,11 @@ namespace FlaxEditor.Modules
private void OnActorChildNodesDispose(ActorNode node) private void OnActorChildNodesDispose(ActorNode node)
{ {
if (Selection.Count == 0)
return;
// TODO: cache if selection contains any actor child node and skip this loop if no need to iterate // TODO: cache if selection contains any actor child node and skip this loop if no need to iterate
// TODO: or build a hash set with selected nodes for quick O(1) checks (cached until selection changes)
// Deselect child nodes // Deselect child nodes
for (int i = 0; i < node.ChildNodes.Count; i++) for (int i = 0; i < node.ChildNodes.Count; i++)

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors; using FlaxEditor.SceneGraph.Actors;
using FlaxEngine; using FlaxEngine;
@@ -658,6 +659,48 @@ namespace FlaxEditor.Modules
//node?.TreeNode.OnActiveChanged(); //node?.TreeNode.OnActiveChanged();
} }
private void OnActorDestroyChildren(Actor actor)
{
// Instead of doing OnActorParentChanged for every child lets remove all of them at once from that actor
ActorNode node = GetActorNode(actor);
if (node != null)
{
if (Editor.SceneEditing.HasSthSelected)
{
// Clear selection if one of the removed actors is selected
var selection = new HashSet<Actor>();
foreach (var e in Editor.SceneEditing.Selection)
{
if (e is ActorNode q && q.Actor)
selection.Add(q.Actor);
}
var count = actor.ChildrenCount;
for (int i = 0; i < count; i++)
{
var child = actor.GetChild(i);
if (selection.Contains(child))
{
Editor.SceneEditing.Deselect();
break;
}
}
}
// Remove all child nodes (upfront remove all nodes to run faster)
for (int i = 0; i < node.ChildNodes.Count; i++)
{
if (node.ChildNodes[i] is ActorNode child)
child.parentNode = null;
}
node.TreeNode.DisposeChildren();
for (int i = 0; i < node.ChildNodes.Count; i++)
{
node.ChildNodes[i].Dispose();
}
node.ChildNodes.Clear();
}
}
/// <summary> /// <summary>
/// Gets the actor node. /// Gets the actor node.
/// </summary> /// </summary>
@@ -709,6 +752,7 @@ namespace FlaxEditor.Modules
Level.ActorOrderInParentChanged += OnActorOrderInParentChanged; Level.ActorOrderInParentChanged += OnActorOrderInParentChanged;
Level.ActorNameChanged += OnActorNameChanged; Level.ActorNameChanged += OnActorNameChanged;
Level.ActorActiveChanged += OnActorActiveChanged; Level.ActorActiveChanged += OnActorActiveChanged;
Level.ActorDestroyChildren += OnActorDestroyChildren;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -726,6 +770,7 @@ namespace FlaxEditor.Modules
Level.ActorOrderInParentChanged -= OnActorOrderInParentChanged; Level.ActorOrderInParentChanged -= OnActorOrderInParentChanged;
Level.ActorNameChanged -= OnActorNameChanged; Level.ActorNameChanged -= OnActorNameChanged;
Level.ActorActiveChanged -= OnActorActiveChanged; Level.ActorActiveChanged -= OnActorActiveChanged;
Level.ActorDestroyChildren -= OnActorDestroyChildren;
// Cleanup graph // Cleanup graph
Root.Dispose(); Root.Dispose();

View File

@@ -1223,6 +1223,13 @@ namespace FlaxEditor.Modules
Windows[i].OnPlayBegin(); Windows[i].OnPlayBegin();
} }
/// <inheritdoc />
public override void OnPlayEnding()
{
for (int i = 0; i < Windows.Count; i++)
Windows[i].OnPlayEnding();
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnPlayEnd() public override void OnPlayEnd()
{ {

View File

@@ -139,6 +139,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Common"), EditorOrder(240)] [EditorDisplay("Common"), EditorOrder(240)]
public InputBinding ToggleFullscreen = new InputBinding(KeyboardKeys.F11); public InputBinding ToggleFullscreen = new InputBinding(KeyboardKeys.F11);
[DefaultValue(typeof(InputBinding), "Ctrl+BackQuote")]
[EditorDisplay("Common"), EditorOrder(250)]
public InputBinding FocusConsoleCommand = new InputBinding(KeyboardKeys.BackQuote, KeyboardKeys.Control);
#endregion #endregion
#region File #region File
@@ -647,5 +651,45 @@ namespace FlaxEditor.Options
public InputBinding VisualScriptDebuggerWindow = new InputBinding(KeyboardKeys.None); public InputBinding VisualScriptDebuggerWindow = new InputBinding(KeyboardKeys.None);
#endregion #endregion
#region Node editors
[DefaultValue(typeof(InputBinding), "Shift+W")]
[EditorDisplay("Node editors"), EditorOrder(4500)]
public InputBinding NodesAlignTop = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+A")]
[EditorDisplay("Node editors"), EditorOrder(4510)]
public InputBinding NodesAlignLeft = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+S")]
[EditorDisplay("Node editors"), EditorOrder(4520)]
public InputBinding NodesAlignBottom = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Shift+D")]
[EditorDisplay("Node editors"), EditorOrder(4530)]
public InputBinding NodesAlignRight = new InputBinding(KeyboardKeys.D, KeyboardKeys.Shift);
[DefaultValue(typeof(InputBinding), "Alt+Shift+W")]
[EditorDisplay("Node editors"), EditorOrder(4540)]
public InputBinding NodesAlignMiddle = new InputBinding(KeyboardKeys.W, KeyboardKeys.Shift, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Alt+Shift+S")]
[EditorDisplay("Node editors"), EditorOrder(4550)]
public InputBinding NodesAlignCenter = new InputBinding(KeyboardKeys.S, KeyboardKeys.Shift, KeyboardKeys.Alt);
[DefaultValue(typeof(InputBinding), "Q")]
[EditorDisplay("Node editors"), EditorOrder(4560)]
public InputBinding NodesAutoFormat = new InputBinding(KeyboardKeys.Q);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Node editors"), EditorOrder(4570)]
public InputBinding NodesDistributeHorizontal = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "None")]
[EditorDisplay("Node editors"), EditorOrder(4580)]
public InputBinding NodesDistributeVertical = new InputBinding(KeyboardKeys.None);
#endregion
} }
} }

View File

@@ -76,9 +76,13 @@ namespace FlaxEditor.SceneGraph.Actors
// Skip removing this terrain file sif it's still referenced // Skip removing this terrain file sif it's still referenced
var sceneReferences = Editor.GetAssetReferences(e.SceneId); var sceneReferences = Editor.GetAssetReferences(e.SceneId);
if (sceneReferences != null && sceneReferences.Contains(e.TerrainId)) if (sceneReferences != null && sceneReferences.Contains(e.TerrainId))
{
Debug.Log($"Skip removing files used by terrain {e.TerrainId} on scene {e.SceneId} as it's still in use");
continue; continue;
}
// Delete files // Delete files
Debug.Log($"Removing files used by removed terrain {e.TerrainId} on scene {e.SceneId}");
foreach (var file in e.Files) foreach (var file in e.Files)
{ {
if (file != null && File.Exists(file)) if (file != null && File.Exists(file))

View File

@@ -97,13 +97,16 @@ namespace FlaxEditor.SceneGraph
/// <returns>Hit object or null if there is no intersection at all.</returns> /// <returns>Hit object or null if there is no intersection at all.</returns>
public SceneGraphNode RayCast(ref Ray ray, ref Ray view, out Real distance, RayCastData.FlagTypes flags = RayCastData.FlagTypes.None) public SceneGraphNode RayCast(ref Ray ray, ref Ray view, out Real distance, RayCastData.FlagTypes flags = RayCastData.FlagTypes.None)
{ {
Profiler.BeginEvent("RayCastScene");
var data = new RayCastData var data = new RayCastData
{ {
Ray = ray, Ray = ray,
View = view, View = view,
Flags = flags Flags = flags
}; };
return RayCast(ref data, out distance, out _); var result = RayCast(ref data, out distance, out _);
Profiler.EndEvent();
return result;
} }
/// <summary> /// <summary>
@@ -117,13 +120,16 @@ namespace FlaxEditor.SceneGraph
/// <returns>Hit object or null if there is no intersection at all.</returns> /// <returns>Hit object or null if there is no intersection at all.</returns>
public SceneGraphNode RayCast(ref Ray ray, ref Ray view, out Real distance, out Vector3 normal, RayCastData.FlagTypes flags = RayCastData.FlagTypes.None) public SceneGraphNode RayCast(ref Ray ray, ref Ray view, out Real distance, out Vector3 normal, RayCastData.FlagTypes flags = RayCastData.FlagTypes.None)
{ {
Profiler.BeginEvent("RayCastScene");
var data = new RayCastData var data = new RayCastData
{ {
Ray = ray, Ray = ray,
View = view, View = view,
Flags = flags Flags = flags
}; };
return RayCast(ref data, out distance, out normal); var result = RayCast(ref data, out distance, out normal);
Profiler.EndEvent();
return result;
} }
internal static Quaternion RaycastNormalRotation(ref Vector3 normal) internal static Quaternion RaycastNormalRotation(ref Vector3 normal)

View File

@@ -27,7 +27,7 @@ namespace FlaxEditor.SceneGraph
/// <summary> /// <summary>
/// The parent node. /// The parent node.
/// </summary> /// </summary>
protected SceneGraphNode parentNode; internal SceneGraphNode parentNode;
/// <summary> /// <summary>
/// Gets the children list. /// Gets the children list.

View File

@@ -19,6 +19,7 @@ namespace FlaxEditor.States
private readonly List<Guid> _scenesToLoad = new List<Guid>(); private readonly List<Guid> _scenesToLoad = new List<Guid>();
private readonly List<Scene> _scenesToUnload = new List<Scene>(); private readonly List<Scene> _scenesToUnload = new List<Scene>();
private Guid _lastSceneFromRequest; private Guid _lastSceneFromRequest;
private bool _sameSceneReload = false;
internal ChangingScenesState(Editor editor) internal ChangingScenesState(Editor editor)
: base(editor) : base(editor)
@@ -164,10 +165,22 @@ namespace FlaxEditor.States
{ {
Assert.AreEqual(Guid.Empty, _lastSceneFromRequest, "Invalid state."); Assert.AreEqual(Guid.Empty, _lastSceneFromRequest, "Invalid state.");
// Bind events // Bind events, only bind loading event and error if re-loading the same scene to avoid issues.
Level.SceneLoaded += OnSceneEvent; if (_scenesToUnload.Count == 1 && _scenesToLoad.Count == 1)
Level.SceneLoadError += OnSceneEvent; {
Level.SceneUnloaded += OnSceneEvent; if (_scenesToLoad[0] == _scenesToUnload[0].ID)
{
Level.SceneLoaded += OnSceneEvent;
Level.SceneLoadError += OnSceneEvent;
_sameSceneReload = true;
}
}
if (!_sameSceneReload)
{
Level.SceneLoaded += OnSceneEvent;
Level.SceneLoadError += OnSceneEvent;
Level.SceneUnloaded += OnSceneEvent;
}
// Push scenes changing requests // Push scenes changing requests
for (int i = 0; i < _scenesToUnload.Count; i++) for (int i = 0; i < _scenesToUnload.Count; i++)
@@ -210,9 +223,18 @@ namespace FlaxEditor.States
} }
// Unbind events // Unbind events
Level.SceneLoaded -= OnSceneEvent; if (_sameSceneReload)
Level.SceneLoadError -= OnSceneEvent; {
Level.SceneUnloaded -= OnSceneEvent; Level.SceneLoaded -= OnSceneEvent;
Level.SceneLoadError -= OnSceneEvent;
_sameSceneReload = false;
}
else
{
Level.SceneLoaded -= OnSceneEvent;
Level.SceneLoadError -= OnSceneEvent;
Level.SceneUnloaded -= OnSceneEvent;
}
} }
private void OnSceneEvent(Scene scene, Guid sceneId) private void OnSceneEvent(Scene scene, Guid sceneId)

View File

@@ -233,6 +233,8 @@ namespace FlaxEditor.Surface.Archetypes
public BlendPointsEditor(Animation.MultiBlend node, bool is2D, float x, float y, float width, float height) public BlendPointsEditor(Animation.MultiBlend node, bool is2D, float x, float y, float width, float height)
: base(x, y, width, height) : base(x, y, width, height)
{ {
AutoFocus = true;
_node = node; _node = node;
_is2D = is2D; _is2D = is2D;
} }

View File

@@ -6,6 +6,7 @@ using FlaxEditor.Scripting;
using FlaxEditor.Surface.Elements; using FlaxEditor.Surface.Elements;
using FlaxEditor.Windows.Assets; using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Surface.Archetypes namespace FlaxEditor.Surface.Archetypes
{ {
@@ -123,7 +124,8 @@ namespace FlaxEditor.Surface.Archetypes
case MaterialDomain.Particle: case MaterialDomain.Particle:
case MaterialDomain.Deformable: case MaterialDomain.Deformable:
{ {
bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit; bool isNotUnlit = info.ShadingModel != MaterialShadingModel.Unlit && info.ShadingModel != MaterialShadingModel.CustomLit;
bool isOpaque = info.BlendMode == MaterialBlendMode.Opaque;
bool withTess = info.TessellationMode != TessellationMethod.None; bool withTess = info.TessellationMode != TessellationMethod.None;
GetBox(MaterialNodeBoxes.Color).IsActive = isNotUnlit; GetBox(MaterialNodeBoxes.Color).IsActive = isNotUnlit;
@@ -134,8 +136,8 @@ namespace FlaxEditor.Surface.Archetypes
GetBox(MaterialNodeBoxes.Roughness).IsActive = isNotUnlit; GetBox(MaterialNodeBoxes.Roughness).IsActive = isNotUnlit;
GetBox(MaterialNodeBoxes.AmbientOcclusion).IsActive = isNotUnlit; GetBox(MaterialNodeBoxes.AmbientOcclusion).IsActive = isNotUnlit;
GetBox(MaterialNodeBoxes.Normal).IsActive = isNotUnlit; GetBox(MaterialNodeBoxes.Normal).IsActive = isNotUnlit;
GetBox(MaterialNodeBoxes.Opacity).IsActive = info.ShadingModel == MaterialShadingModel.Subsurface || info.ShadingModel == MaterialShadingModel.Foliage || info.BlendMode != MaterialBlendMode.Opaque; GetBox(MaterialNodeBoxes.Opacity).IsActive = info.ShadingModel == MaterialShadingModel.Subsurface || info.ShadingModel == MaterialShadingModel.Foliage || !isOpaque;
GetBox(MaterialNodeBoxes.Refraction).IsActive = info.BlendMode != MaterialBlendMode.Opaque; GetBox(MaterialNodeBoxes.Refraction).IsActive = !isOpaque;
GetBox(MaterialNodeBoxes.PositionOffset).IsActive = true; GetBox(MaterialNodeBoxes.PositionOffset).IsActive = true;
GetBox(MaterialNodeBoxes.TessellationMultiplier).IsActive = withTess; GetBox(MaterialNodeBoxes.TessellationMultiplier).IsActive = withTess;
GetBox(MaterialNodeBoxes.WorldDisplacement).IsActive = withTess; GetBox(MaterialNodeBoxes.WorldDisplacement).IsActive = withTess;
@@ -260,6 +262,211 @@ namespace FlaxEditor.Surface.Archetypes
} }
} }
#if false // TODO: finish code editor based on RichTextBoxBase with text block parsing for custom styling
internal sealed class CustomCodeTextBox : RichTextBoxBase
{
protected override void OnParseTextBlocks()
{
base.OnParseTextBlocks();
// Single block for a whole text
// TODO: implement code parsing with HLSL syntax
var font = Style.Current.FontMedium;
var style = new TextBlockStyle
{
Font = new FontReference(font),
Color = Style.Current.Foreground,
BackgroundSelectedBrush = new SolidColorBrush(Style.Current.BackgroundSelected),
};
_textBlocks.Clear();
_textBlocks.Add(new TextBlock
{
Range = new TextRange
{
StartIndex = 0,
EndIndex = TextLength,
},
Style = style,
Bounds = new Rectangle(Float2.Zero, font.MeasureText(Text)),
});
}
#else
internal sealed class CustomCodeTextBox : TextBox
{
#endif
public override void Draw()
{
base.Draw();
// Draw border
if (!IsFocused)
Render2D.DrawRectangle(new Rectangle(Float2.Zero, Size), Style.Current.BorderNormal);
}
}
internal sealed class CustomCodeNode : SurfaceNode
{
private Rectangle _resizeButtonRect;
private Float2 _startResizingSize;
private Float2 _startResizingCornerOffset;
private bool _isResizing;
private CustomCodeTextBox _textBox;
private int SizeValueIndex => Archetype.TypeID == 8 ? 1 : 3; // Index of the Size stored in Values array
private Float2 SizeValue
{
get => (Float2)Values[SizeValueIndex];
set => SetValue(SizeValueIndex, value, false);
}
public CustomCodeNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
Float2 pos = new Float2(FlaxEditor.Surface.Constants.NodeMarginX, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize), size;
if (nodeArch.TypeID == 8)
{
pos += new Float2(60, 0);
size = new Float2(172, 200);
}
else
{
pos += new Float2(0, 40);
size = new Float2(300, 200);
}
_textBox = new CustomCodeTextBox
{
IsMultiline = true,
Location = pos,
Size = size,
Parent = this,
AnchorMax = Float2.One,
};
_textBox.EditEnd += () => SetValue(0, _textBox.Text);
}
public override bool CanSelect(ref Float2 location)
{
return base.CanSelect(ref location) && !_resizeButtonRect.MakeOffsetted(Location).Contains(ref location);
}
public override void OnSurfaceLoaded(SurfaceNodeActions action)
{
base.OnSurfaceLoaded(action);
_textBox.Text = (string)Values[0];
var size = SizeValue;
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Resize(size.X, size.Y);
}
public override void OnValuesChanged()
{
base.OnValuesChanged();
var size = SizeValue;
Resize(size.X, size.Y);
_textBox.Text = (string)Values[0];
}
protected override void UpdateRectangles()
{
base.UpdateRectangles();
const float buttonMargin = FlaxEditor.Surface.Constants.NodeCloseButtonMargin;
const float buttonSize = FlaxEditor.Surface.Constants.NodeCloseButtonSize;
_resizeButtonRect = new Rectangle(_closeButtonRect.Left, Height - buttonSize - buttonMargin - 4, buttonSize, buttonSize);
}
public override void Draw()
{
base.Draw();
var style = Style.Current;
if (_isResizing)
{
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
}
public override void OnLostFocus()
{
if (_isResizing)
EndResizing();
base.OnLostFocus();
}
public override void OnEndMouseCapture()
{
if (_isResizing)
EndResizing();
base.OnEndMouseCapture();
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (base.OnMouseDown(location, button))
return true;
if (button == MouseButton.Left && _resizeButtonRect.Contains(ref location) && Surface.CanEdit)
{
// Start sliding
_isResizing = true;
_startResizingSize = Size;
_startResizingCornerOffset = Size - location;
StartMouseCapture();
Cursor = CursorType.SizeNWSE;
return true;
}
return false;
}
public override void OnMouseMove(Float2 location)
{
if (_isResizing)
{
var emptySize = CalculateNodeSize(0, 0);
var size = Float2.Max(location - emptySize + _startResizingCornerOffset, new Float2(240, 160));
Resize(size.X, size.Y);
}
else
{
base.OnMouseMove(location);
}
}
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isResizing)
{
EndResizing();
return true;
}
return base.OnMouseUp(location, button);
}
private void EndResizing()
{
Cursor = CursorType.Default;
EndMouseCapture();
_isResizing = false;
if (_startResizingSize != Size)
{
var emptySize = CalculateNodeSize(0, 0);
SizeValue = Size - emptySize;
Surface.MarkAsEdited(false);
}
}
}
internal enum MaterialTemplateInputsMapping internal enum MaterialTemplateInputsMapping
{ {
/// <summary> /// <summary>
@@ -410,13 +617,15 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype new NodeArchetype
{ {
TypeID = 8, TypeID = 8,
Create = (id, context, arch, groupArch) => new CustomCodeNode(id, context, arch, groupArch),
Title = "Custom Code", Title = "Custom Code",
Description = "Custom HLSL shader code expression", Description = "Custom HLSL shader code expression",
Flags = NodeFlags.MaterialGraph, Flags = NodeFlags.MaterialGraph,
Size = new Float2(300, 200), Size = new Float2(300, 200),
DefaultValues = new object[] DefaultValues = new object[]
{ {
"// Here you can add HLSL code\nOutput0 = Input0;" "// Here you can add HLSL code\nOutput0 = Input0;",
new Float2(300, 200),
}, },
Elements = new[] Elements = new[]
{ {
@@ -433,8 +642,6 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(1, "Output1", typeof(Float4), 9), NodeElementArchetype.Factory.Output(1, "Output1", typeof(Float4), 9),
NodeElementArchetype.Factory.Output(2, "Output2", typeof(Float4), 10), NodeElementArchetype.Factory.Output(2, "Output2", typeof(Float4), 10),
NodeElementArchetype.Factory.Output(3, "Output3", typeof(Float4), 11), NodeElementArchetype.Factory.Output(3, "Output3", typeof(Float4), 11),
NodeElementArchetype.Factory.TextBox(60, 0, 175, 200, 0),
} }
}, },
new NodeArchetype new NodeArchetype
@@ -874,6 +1081,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype new NodeArchetype
{ {
TypeID = 38, TypeID = 38,
Create = (id, context, arch, groupArch) => new CustomCodeNode(id, context, arch, groupArch),
Title = "Custom Global Code", Title = "Custom Global Code",
Description = "Custom global HLSL shader code expression (placed before material shader code). Can contain includes to shader utilities or declare functions to reuse later.", Description = "Custom global HLSL shader code expression (placed before material shader code). Can contain includes to shader utilities or declare functions to reuse later.",
Flags = NodeFlags.MaterialGraph, Flags = NodeFlags.MaterialGraph,
@@ -883,6 +1091,7 @@ namespace FlaxEditor.Surface.Archetypes
"// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}", "// Here you can add HLSL code\nfloat4 GetCustomColor()\n{\n\treturn float4(1, 0, 0, 1);\n}",
true, true,
(int)MaterialTemplateInputsMapping.Utilities, (int)MaterialTemplateInputsMapping.Utilities,
new Float2(300, 240),
}, },
Elements = new[] Elements = new[]
{ {
@@ -890,7 +1099,6 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Text(20, 0, "Enabled"), NodeElementArchetype.Factory.Text(20, 0, "Enabled"),
NodeElementArchetype.Factory.Text(0, 20, "Location"), NodeElementArchetype.Factory.Text(0, 20, "Location"),
NodeElementArchetype.Factory.Enum(50, 20, 120, 2, typeof(MaterialTemplateInputsMapping)), NodeElementArchetype.Factory.Enum(50, 20, 120, 2, typeof(MaterialTemplateInputsMapping)),
NodeElementArchetype.Factory.TextBox(0, 40, 300, 200, 0),
} }
}, },
new NodeArchetype new NodeArchetype

View File

@@ -459,7 +459,7 @@ namespace FlaxEditor.Surface.Archetypes
AlternativeTitles = new string[] { "Lightmap TexCoord" }, AlternativeTitles = new string[] { "Lightmap TexCoord" },
Description = "Lightmap UVs", Description = "Lightmap UVs",
Flags = NodeFlags.MaterialGraph, Flags = NodeFlags.MaterialGraph,
Size = new Float2(110, 30), Size = new Float2(110, 20),
Elements = new [] Elements = new []
{ {
NodeElementArchetype.Factory.Output(0, "UVs", typeof(Float2), 0) NodeElementArchetype.Factory.Output(0, "UVs", typeof(Float2), 0)
@@ -493,6 +493,19 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4), NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4),
} }
}, },
new NodeArchetype
{
TypeID = 24,
Title = "Texture Size",
Description = "Gets the size of the texture (in pixels). If texture is during streaming, then returns size of the highest resident mip.",
Flags = NodeFlags.ParticleEmitterGraph,
Size = new Float2(160, 20),
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0),
NodeElementArchetype.Factory.Output(0, "Size", typeof(Float3), 1),
}
},
}; };
} }
} }

View File

@@ -64,6 +64,7 @@ namespace FlaxEditor.Surface.Elements
{ {
ParentNode = parentNode; ParentNode = parentNode;
Archetype = archetype; Archetype = archetype;
AutoFocus = true;
var back = Style.Current.TextBoxBackground; var back = Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f; var grayOutFactor = 0.6f;

View File

@@ -0,0 +1,43 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.Surface
{
/// <summary>
/// Node Alignment type
/// </summary>
[HideInEditor]
public enum NodeAlignmentType
{
/// <summary>
/// Align nodes vertically to top, matching top-most node
/// </summary>
Top,
/// <summary>
/// Align nodes vertically to middle, using average of all nodes
/// </summary>
Middle,
/// <summary>
/// Align nodes vertically to bottom, matching bottom-most node
/// </summary>
Bottom,
/// <summary>
/// Align nodes horizontally to left, matching left-most node
/// </summary>
Left,
/// <summary>
/// Align nodes horizontally to center, using average of all nodes
/// </summary>
Center,
/// <summary>
/// Align nodes horizontally to right, matching right-most node
/// </summary>
Right,
}
}

View File

@@ -59,6 +59,7 @@ namespace FlaxEditor.Surface
protected SurfaceControl(VisjectSurfaceContext context, float width, float height) protected SurfaceControl(VisjectSurfaceContext context, float width, float height)
: base(0, 0, width, height) : base(0, 0, width, height)
{ {
AutoFocus = true;
ClipChildren = false; ClipChildren = false;
Surface = context.Surface; Surface = context.Surface;

View File

@@ -40,6 +40,11 @@ namespace FlaxEditor.Surface
[HideInEditor] [HideInEditor]
public class SurfaceNode : SurfaceControl public class SurfaceNode : SurfaceControl
{ {
/// <summary>
/// The box to draw a highlight around. Drawing will be skipped if null.
/// </summary>
internal Box highlightBox;
/// <summary> /// <summary>
/// Flag used to discard node values setting during event sending for node UI flushing. /// Flag used to discard node values setting during event sending for node UI flushing.
/// </summary> /// </summary>
@@ -912,7 +917,7 @@ namespace FlaxEditor.Surface
/// <inheritdoc /> /// <inheritdoc />
public override bool OnTestTooltipOverControl(ref Float2 location) public override bool OnTestTooltipOverControl(ref Float2 location)
{ {
return _headerRect.Contains(ref location) && ShowTooltip; return _headerRect.Contains(ref location) && ShowTooltip && !Surface.IsConnecting && !Surface.IsBoxSelecting;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -1070,7 +1075,7 @@ namespace FlaxEditor.Surface
// Header // Header
var headerColor = style.BackgroundHighlighted; var headerColor = style.BackgroundHighlighted;
if (_headerRect.Contains(ref _mousePosition)) if (_headerRect.Contains(ref _mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting)
headerColor *= 1.07f; headerColor *= 1.07f;
Render2D.FillRectangle(_headerRect, headerColor); Render2D.FillRectangle(_headerRect, headerColor);
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center); Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
@@ -1078,7 +1083,8 @@ namespace FlaxEditor.Surface
// Close button // Close button
if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit) if ((Archetype.Flags & NodeFlags.NoCloseButton) == 0 && Surface.CanEdit)
{ {
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey); bool highlightClose = _closeButtonRect.Contains(_mousePosition) && !Surface.IsConnecting && !Surface.IsBoxSelecting;
Render2D.DrawSprite(style.Cross, _closeButtonRect, highlightClose ? style.Foreground : style.ForegroundGrey);
} }
// Footer // Footer
@@ -1101,6 +1107,9 @@ namespace FlaxEditor.Surface
Render2D.DrawSprite(icon, new Rectangle(-7, -7, 16, 16), new Color(0.9f, 0.9f, 0.9f)); Render2D.DrawSprite(icon, new Rectangle(-7, -7, 16, 16), new Color(0.9f, 0.9f, 0.9f));
Render2D.DrawSprite(icon, new Rectangle(-6, -6, 14, 14), new Color(0.894117647f, 0.0784313725f, 0.0f)); Render2D.DrawSprite(icon, new Rectangle(-6, -6, 14, 14), new Color(0.894117647f, 0.0784313725f, 0.0f));
} }
if (highlightBox != null)
Render2D.DrawRectangle(highlightBox.Bounds, style.BorderHighlighted, 2f);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -1123,8 +1132,9 @@ namespace FlaxEditor.Surface
if (base.OnMouseUp(location, button)) if (base.OnMouseUp(location, button))
return true; return true;
// Close // Close/ delete
if (button == MouseButton.Left && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location)) bool canDelete = !Surface.IsConnecting && !Surface.WasBoxSelecting && !Surface.WasMovingSelection;
if (button == MouseButton.Left && canDelete && (Archetype.Flags & NodeFlags.NoCloseButton) == 0 && _closeButtonRect.Contains(ref location))
{ {
Surface.Delete(this); Surface.Delete(this);
return true; return true;

View File

@@ -191,7 +191,16 @@ namespace FlaxEditor.Surface
private ContextMenuButton _cmCopyButton; private ContextMenuButton _cmCopyButton;
private ContextMenuButton _cmDuplicateButton; private ContextMenuButton _cmDuplicateButton;
private ContextMenuChildMenu _cmFormatNodesMenu;
private ContextMenuButton _cmFormatNodesConnectionButton; private ContextMenuButton _cmFormatNodesConnectionButton;
private ContextMenuButton _cmAlignNodesTopButton;
private ContextMenuButton _cmAlignNodesMiddleButton;
private ContextMenuButton _cmAlignNodesBottomButton;
private ContextMenuButton _cmAlignNodesLeftButton;
private ContextMenuButton _cmAlignNodesCenterButton;
private ContextMenuButton _cmAlignNodesRightButton;
private ContextMenuButton _cmDistributeNodesHorizontallyButton;
private ContextMenuButton _cmDistributeNodesVerticallyButton;
private ContextMenuButton _cmRemoveNodeConnectionsButton; private ContextMenuButton _cmRemoveNodeConnectionsButton;
private ContextMenuButton _cmRemoveBoxConnectionsButton; private ContextMenuButton _cmRemoveBoxConnectionsButton;
private readonly Float2 ContextMenuOffset = new Float2(5); private readonly Float2 ContextMenuOffset = new Float2(5);
@@ -399,10 +408,26 @@ namespace FlaxEditor.Surface
} }
menu.AddSeparator(); menu.AddSeparator();
_cmFormatNodesConnectionButton = menu.AddButton("Format node(s)", () => { FormatGraph(SelectedNodes); }); _cmFormatNodesMenu = menu.AddChildMenu("Format node(s)");
_cmFormatNodesConnectionButton.Enabled = CanEdit && HasNodesSelection; _cmFormatNodesMenu.Enabled = CanEdit && HasNodesSelection;
_cmRemoveNodeConnectionsButton = menu.AddButton("Remove all connections to that node(s)", () => _cmFormatNodesConnectionButton = _cmFormatNodesMenu.ContextMenu.AddButton("Auto format", Editor.Instance.Options.Options.Input.NodesAutoFormat, () => { FormatGraph(SelectedNodes); });
_cmFormatNodesMenu.ContextMenu.AddSeparator();
_cmAlignNodesTopButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align top", Editor.Instance.Options.Options.Input.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); });
_cmAlignNodesMiddleButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align middle", Editor.Instance.Options.Options.Input.NodesAlignMiddle, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Middle); });
_cmAlignNodesBottomButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align bottom", Editor.Instance.Options.Options.Input.NodesAlignBottom, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Bottom); });
_cmFormatNodesMenu.ContextMenu.AddSeparator();
_cmAlignNodesLeftButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align left", Editor.Instance.Options.Options.Input.NodesAlignLeft, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Left); });
_cmAlignNodesCenterButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align center", Editor.Instance.Options.Options.Input.NodesAlignCenter, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); });
_cmAlignNodesRightButton = _cmFormatNodesMenu.ContextMenu.AddButton("Align right", Editor.Instance.Options.Options.Input.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); });
_cmFormatNodesMenu.ContextMenu.AddSeparator();
_cmDistributeNodesHorizontallyButton = _cmFormatNodesMenu.ContextMenu.AddButton("Distribute horizontally", Editor.Instance.Options.Options.Input.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); });
_cmDistributeNodesVerticallyButton = _cmFormatNodesMenu.ContextMenu.AddButton("Distribute vertically", Editor.Instance.Options.Options.Input.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); });
_cmRemoveNodeConnectionsButton = menu.AddButton("Remove all connections", () =>
{ {
var nodes = ((List<SurfaceNode>)menu.Tag); var nodes = ((List<SurfaceNode>)menu.Tag);
@@ -428,8 +453,10 @@ namespace FlaxEditor.Surface
MarkAsEdited(); MarkAsEdited();
}); });
_cmRemoveNodeConnectionsButton.Enabled = CanEdit; bool anyConnection = SelectedNodes.Any(n => n.GetBoxes().Any(b => b.HasAnyConnection));
_cmRemoveBoxConnectionsButton = menu.AddButton("Remove all connections to that box", () => _cmRemoveNodeConnectionsButton.Enabled = CanEdit && anyConnection;
_cmRemoveBoxConnectionsButton = menu.AddButton("Remove all socket connections", () =>
{ {
var boxUnderMouse = (Box)_cmRemoveBoxConnectionsButton.Tag; var boxUnderMouse = (Box)_cmRemoveBoxConnectionsButton.Tag;
if (Undo != null) if (Undo != null)
@@ -450,6 +477,16 @@ namespace FlaxEditor.Surface
var boxUnderMouse = GetChildAtRecursive(location) as Box; var boxUnderMouse = GetChildAtRecursive(location) as Box;
_cmRemoveBoxConnectionsButton.Enabled = boxUnderMouse != null && boxUnderMouse.HasAnyConnection; _cmRemoveBoxConnectionsButton.Enabled = boxUnderMouse != null && boxUnderMouse.HasAnyConnection;
_cmRemoveBoxConnectionsButton.Tag = boxUnderMouse; _cmRemoveBoxConnectionsButton.Tag = boxUnderMouse;
if (boxUnderMouse != null)
{
boxUnderMouse.ParentNode.highlightBox = boxUnderMouse;
menu.VisibleChanged += (c) =>
{
if (!c.Visible)
boxUnderMouse.ParentNode.highlightBox = null;
};
}
} }
controlUnderMouse?.OnShowSecondaryContextMenu(menu, controlUnderMouse.PointFromParent(location)); controlUnderMouse?.OnShowSecondaryContextMenu(menu, controlUnderMouse.PointFromParent(location));

View File

@@ -225,7 +225,11 @@ namespace FlaxEditor.Surface
_rootControl.DrawComments(); _rootControl.DrawComments();
if (IsSelecting) // Reset input flags here because this is the closest to Update we have
WasBoxSelecting = IsBoxSelecting;
WasMovingSelection = IsMovingSelection;
if (IsBoxSelecting)
{ {
DrawSelection(); DrawSelection();
} }

View File

@@ -282,5 +282,122 @@ namespace FlaxEditor.Surface
return maxOffset; return maxOffset;
} }
/// <summary>
/// Align given nodes on a graph using the given alignment type.
/// Ignores any potential overlap.
/// </summary>
/// <param name="nodes">List of nodes</param>
/// <param name="alignmentType">Alignemnt type</param>
public void AlignNodes(List<SurfaceNode> nodes, NodeAlignmentType alignmentType)
{
if(nodes.Count <= 1)
return;
var undoActions = new List<MoveNodesAction>();
var boundingBox = GetNodesBounds(nodes);
for(int i = 0; i < nodes.Count; i++)
{
var centerY = boundingBox.Center.Y - (nodes[i].Height / 2);
var centerX = boundingBox.Center.X - (nodes[i].Width / 2);
var newLocation = alignmentType switch
{
NodeAlignmentType.Top => new Float2(nodes[i].Location.X, boundingBox.Top),
NodeAlignmentType.Middle => new Float2(nodes[i].Location.X, centerY),
NodeAlignmentType.Bottom => new Float2(nodes[i].Location.X, boundingBox.Bottom - nodes[i].Height),
NodeAlignmentType.Left => new Float2(boundingBox.Left, nodes[i].Location.Y),
NodeAlignmentType.Center => new Float2(centerX, nodes[i].Location.Y),
NodeAlignmentType.Right => new Float2(boundingBox.Right - nodes[i].Width, nodes[i].Location.Y),
_ => throw new NotImplementedException($"Unsupported node alignment type: {alignmentType}"),
};
var locationDelta = newLocation - nodes[i].Location;
nodes[i].Location = newLocation;
if(Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta));
}
MarkAsEdited(false);
Undo?.AddAction(new MultiUndoAction(undoActions, $"Align nodes ({alignmentType})"));
}
/// <summary>
/// Distribute the given nodes as equally as possible inside the bounding box, if no fit can be done it will use a default pad of 10 pixels between nodes.
/// </summary>
/// <param name="nodes">List of nodes</param>
/// <param name="vertically">If false will be done horizontally, if true will be done vertically</param>
public void DistributeNodes(List<SurfaceNode> nodes, bool vertically)
{
if(nodes.Count <= 1)
return;
var undoActions = new List<MoveNodesAction>();
var boundingBox = GetNodesBounds(nodes);
float padding = 10;
float totalSize = 0;
for (int i = 0; i < nodes.Count; i++)
{
if (vertically)
{
totalSize += nodes[i].Height;
}
else
{
totalSize += nodes[i].Width;
}
}
if(vertically)
{
nodes.Sort((leftValue, rightValue) => { return leftValue.Y.CompareTo(rightValue.Y); });
float position = boundingBox.Top;
if(totalSize < boundingBox.Height)
{
padding = (boundingBox.Height - totalSize) / nodes.Count;
}
for(int i = 0; i < nodes.Count; i++)
{
var newLocation = new Float2(nodes[i].X, position);
var locationDelta = newLocation - nodes[i].Location;
nodes[i].Location = newLocation;
position += nodes[i].Height + padding;
if (Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta));
}
}
else
{
nodes.Sort((leftValue, rightValue) => { return leftValue.X.CompareTo(rightValue.X); });
float position = boundingBox.Left;
if(totalSize < boundingBox.Width)
{
padding = (boundingBox.Width - totalSize) / nodes.Count;
}
for(int i = 0; i < nodes.Count; i++)
{
var newLocation = new Float2(position, nodes[i].Y);
var locationDelta = newLocation - nodes[i].Location;
nodes[i].Location = newLocation;
position += nodes[i].Width + padding;
if (Undo != null)
undoActions.Add(new MoveNodesAction(Context, new[] { nodes[i].ID }, locationDelta));
}
}
MarkAsEdited(false);
Undo?.AddAction(new MultiUndoAction(undoActions, vertically ? "Distribute nodes vertically" : "Distribute nodes horizontally"));
}
} }
} }

View File

@@ -27,6 +27,7 @@ namespace FlaxEditor.Surface
private Float2 _movingNodesDelta; private Float2 _movingNodesDelta;
private Float2 _gridRoundingDelta; private Float2 _gridRoundingDelta;
private HashSet<SurfaceNode> _movingNodes; private HashSet<SurfaceNode> _movingNodes;
private HashSet<SurfaceNode> _temporarySelectedNodes;
private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>(); private readonly Stack<InputBracket> _inputBrackets = new Stack<InputBracket>();
private class InputBracket private class InputBracket
@@ -130,13 +131,34 @@ namespace FlaxEditor.Surface
if (_rootControl.Children[i] is SurfaceControl control) if (_rootControl.Children[i] is SurfaceControl control)
{ {
var select = control.IsSelectionIntersecting(ref selectionRect); var select = control.IsSelectionIntersecting(ref selectionRect);
if (select != control.IsSelected)
if (Root.GetKey(KeyboardKeys.Shift))
{ {
control.IsSelected = select; if (select == control.IsSelected && _temporarySelectedNodes.Contains(control))
selectionChanged = true; {
control.IsSelected = !select;
selectionChanged = true;
}
}
else if (Root.GetKey(KeyboardKeys.Control))
{
if (select != control.IsSelected && !_temporarySelectedNodes.Contains(control))
{
control.IsSelected = select;
selectionChanged = true;
}
}
else
{
if (select != control.IsSelected)
{
control.IsSelected = select;
selectionChanged = true;
}
} }
} }
} }
if (selectionChanged) if (selectionChanged)
SelectionChanged?.Invoke(); SelectionChanged?.Invoke();
} }
@@ -461,6 +483,19 @@ namespace FlaxEditor.Surface
// Cache data // Cache data
_isMovingSelection = false; _isMovingSelection = false;
_mousePos = location; _mousePos = location;
if(_temporarySelectedNodes == null)
_temporarySelectedNodes = new HashSet<SurfaceNode>();
else
_temporarySelectedNodes.Clear();
for (int i = 0; i < _rootControl.Children.Count; i++)
{
if (_rootControl.Children[i] is SurfaceNode node && node.IsSelected)
{
_temporarySelectedNodes.Add(node);
}
}
if (button == MouseButton.Left) if (button == MouseButton.Left)
{ {
_leftMouseDown = true; _leftMouseDown = true;
@@ -488,9 +523,11 @@ namespace FlaxEditor.Surface
// Check if user is pressing control // Check if user is pressing control
if (Root.GetKey(KeyboardKeys.Control)) if (Root.GetKey(KeyboardKeys.Control))
{ {
// Add/remove from selection AddToSelection(controlUnderMouse);
controlUnderMouse.IsSelected = !controlUnderMouse.IsSelected; }
SelectionChanged?.Invoke(); else if (Root.GetKey(KeyboardKeys.Shift))
{
RemoveFromSelection(controlUnderMouse);
} }
// Check if node isn't selected // Check if node isn't selected
else if (!controlUnderMouse.IsSelected) else if (!controlUnderMouse.IsSelected)
@@ -500,10 +537,14 @@ namespace FlaxEditor.Surface
} }
// Start moving selected nodes // Start moving selected nodes
StartMouseCapture(); if (!Root.GetKey(KeyboardKeys.Shift))
_movingSelectionViewPos = _rootControl.Location; {
_movingNodesDelta = Float2.Zero; StartMouseCapture();
OnGetNodesToMove(); _movingSelectionViewPos = _rootControl.Location;
_movingNodesDelta = Float2.Zero;
OnGetNodesToMove();
}
Focus(); Focus();
return true; return true;
} }
@@ -515,7 +556,12 @@ namespace FlaxEditor.Surface
{ {
// Start selecting or commenting // Start selecting or commenting
StartMouseCapture(); StartMouseCapture();
ClearSelection();
if (!Root.GetKey(KeyboardKeys.Control) && !Root.GetKey(KeyboardKeys.Shift))
{
ClearSelection();
}
Focus(); Focus();
return true; return true;
} }

View File

@@ -232,15 +232,25 @@ namespace FlaxEditor.Surface
} }
/// <summary> /// <summary>
/// Gets a value indicating whether user is selecting nodes. /// Gets a value indicating whether user is box selecting nodes.
/// </summary> /// </summary>
public bool IsSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null; public bool IsBoxSelecting => _leftMouseDown && !_isMovingSelection && _connectionInstigator == null;
/// <summary>
/// Gets a value indicating whether user was previously box selecting nodes.
/// </summary>
public bool WasBoxSelecting { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether user is moving selected nodes. /// Gets a value indicating whether user is moving selected nodes.
/// </summary> /// </summary>
public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigator == null; public bool IsMovingSelection => _leftMouseDown && _isMovingSelection && _connectionInstigator == null;
/// <summary>
/// Gets a value indicating whether user was previously moving selected nodes.
/// </summary>
public bool WasMovingSelection { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether user is connecting nodes. /// Gets a value indicating whether user is connecting nodes.
/// </summary> /// </summary>
@@ -405,6 +415,15 @@ namespace FlaxEditor.Surface
new InputActionsContainer.Binding(options => options.Paste, Paste), new InputActionsContainer.Binding(options => options.Paste, Paste),
new InputActionsContainer.Binding(options => options.Cut, Cut), new InputActionsContainer.Binding(options => options.Cut, Cut),
new InputActionsContainer.Binding(options => options.Duplicate, Duplicate), new InputActionsContainer.Binding(options => options.Duplicate, Duplicate),
new InputActionsContainer.Binding(options => options.NodesAutoFormat, () => { FormatGraph(SelectedNodes); }),
new InputActionsContainer.Binding(options => options.NodesAlignTop, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Top); }),
new InputActionsContainer.Binding(options => options.NodesAlignMiddle, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Middle); }),
new InputActionsContainer.Binding(options => options.NodesAlignBottom, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Bottom); }),
new InputActionsContainer.Binding(options => options.NodesAlignLeft, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Left); }),
new InputActionsContainer.Binding(options => options.NodesAlignCenter, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Center); }),
new InputActionsContainer.Binding(options => options.NodesAlignRight, () => { AlignNodes(SelectedNodes, NodeAlignmentType.Right); }),
new InputActionsContainer.Binding(options => options.NodesDistributeHorizontal, () => { DistributeNodes(SelectedNodes, false); }),
new InputActionsContainer.Binding(options => options.NodesDistributeVertical, () => { DistributeNodes(SelectedNodes, true); }),
}); });
Context.ControlSpawned += OnSurfaceControlSpawned; Context.ControlSpawned += OnSurfaceControlSpawned;
@@ -710,6 +729,18 @@ namespace FlaxEditor.Surface
SelectionChanged?.Invoke(); SelectionChanged?.Invoke();
} }
/// <summary>
/// Removes the specified control from the selection.
/// </summary>
/// <param name="control">The control.</param>
public void RemoveFromSelection(SurfaceControl control)
{
if (!control.IsSelected)
return;
control.IsSelected = false;
SelectionChanged?.Invoke();
}
/// <summary> /// <summary>
/// Selects the specified control. /// Selects the specified control.
/// </summary> /// </summary>

View File

@@ -1518,6 +1518,7 @@ namespace FlaxEditor.Utilities
inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution()); inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());
inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync()); inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync());
inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile); inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile);
inputActions.Add(options => options.FocusConsoleCommand, () => Editor.Instance.Windows.OutputLogWin.FocusCommand());
} }
internal static string ToPathProject(string path) internal static string ToPathProject(string path)

View File

@@ -340,6 +340,13 @@ namespace FlaxEditor.Viewport
{ {
_debugDrawData.Clear(); _debugDrawData.Clear();
if (task is SceneRenderTask sceneRenderTask)
{
// Sync debug view to avoid lag on culling/LODing
var view = sceneRenderTask.View;
DebugDraw.SetView(ref view);
}
// Collect selected objects debug shapes and visuals // Collect selected objects debug shapes and visuals
var selectedParents = TransformGizmo.SelectedParents; var selectedParents = TransformGizmo.SelectedParents;
if (selectedParents.Count > 0) if (selectedParents.Count > 0)
@@ -374,14 +381,7 @@ namespace FlaxEditor.Viewport
// Draw selected objects debug shapes and visuals // Draw selected objects debug shapes and visuals
if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw) if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw)
{ {
unsafe _debugDrawData.DrawActors(true);
{
fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)
{
DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, true);
}
}
DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true); DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true);
} }
} }

View File

@@ -243,7 +243,12 @@ namespace FlaxEditor.Viewport
_tempDebugDrawContext = DebugDraw.AllocateContext(); _tempDebugDrawContext = DebugDraw.AllocateContext();
DebugDraw.SetContext(_tempDebugDrawContext); DebugDraw.SetContext(_tempDebugDrawContext);
DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f); DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f);
if (task is SceneRenderTask sceneRenderTask)
{
// Sync debug view to avoid lag on culling/LODing
var view = sceneRenderTask.View;
DebugDraw.SetView(ref view);
}
for (int i = 0; i < selectedParents.Count; i++) for (int i = 0; i < selectedParents.Count; i++)
{ {
if (selectedParents[i].IsActiveInHierarchy) if (selectedParents[i].IsActiveInHierarchy)
@@ -643,14 +648,7 @@ namespace FlaxEditor.Viewport
if (selectedParents[i].IsActiveInHierarchy) if (selectedParents[i].IsActiveInHierarchy)
selectedParents[i].OnDebugDraw(_debugDrawData); selectedParents[i].OnDebugDraw(_debugDrawData);
} }
_debugDrawData.DrawActors();
unsafe
{
fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)
{
DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, false);
}
}
// Debug draw all actors in prefab and collect actors // Debug draw all actors in prefab and collect actors
var view = Task.View; var view = Task.View;

View File

@@ -264,6 +264,7 @@ namespace FlaxEditor.Viewport.Previews
{ {
DebugDraw.SetContext(_debugDrawContext); DebugDraw.SetContext(_debugDrawContext);
DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1)); DebugDraw.UpdateContext(_debugDrawContext, 1.0f / Mathf.Max(Engine.FramesPerSecond, 1));
DebugDraw.SetView(ref renderContext.View);
CustomDebugDraw?.Invoke(context, ref renderContext); CustomDebugDraw?.Invoke(context, ref renderContext);
OnDebugDraw(context, ref renderContext); OnDebugDraw(context, ref renderContext);
DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true); DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true);

View File

@@ -246,6 +246,14 @@ namespace FlaxEditor.Viewport.Previews
} }
} }
/// <inheritdoc />
protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext)
{
base.OnDebugDraw(context, ref renderContext);
_previewEffect.OnDebugDraw();
}
/// <inheritdoc /> /// <inheritdoc />
public override void Draw() public override void Draw()
{ {
@@ -295,7 +303,8 @@ namespace FlaxEditor.Viewport.Previews
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
// Cleanup objects if (IsDisposing)
return;
_previewEffect.ParticleSystem = null; _previewEffect.ParticleSystem = null;
Object.Destroy(ref _previewEffect); Object.Destroy(ref _previewEffect);
Object.Destroy(ref _boundsModel); Object.Destroy(ref _boundsModel);

View File

@@ -88,6 +88,18 @@ namespace FlaxEditor
} }
} }
/// <summary>
/// Draws the collected actors via <see cref="DebugDraw"/>.
/// </summary>
/// <param name="drawScenes">True if draw all loaded scenes too, otherwise will draw only provided actors.</param>
public unsafe void DrawActors(bool drawScenes = false)
{
fixed (IntPtr* actors = ActorsPtrs)
{
DebugDraw.DrawActors(new IntPtr(actors), _actors.Count, drawScenes);
}
}
/// <summary> /// <summary>
/// Called when task calls <see cref="SceneRenderTask.CollectDrawCalls" /> event. /// Called when task calls <see cref="SceneRenderTask.CollectDrawCalls" /> event.
/// </summary> /// </summary>

View File

@@ -236,6 +236,7 @@ namespace FlaxEditor.Windows.Assets
var group = layout.Group("General"); var group = layout.Group("General");
var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature."); var minScreenSize = group.FloatValue("Min Screen Size", "The minimum screen size to draw model (the bottom limit). Used to cull small models. Set to 0 to disable this feature.");
minScreenSize.ValueBox.SlideSpeed = 0.005f;
minScreenSize.ValueBox.MinValue = 0.0f; minScreenSize.ValueBox.MinValue = 0.0f;
minScreenSize.ValueBox.MaxValue = 1.0f; minScreenSize.ValueBox.MaxValue = 1.0f;
minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize; minScreenSize.ValueBox.Value = proxy.Asset.MinScreenSize;
@@ -476,12 +477,12 @@ namespace FlaxEditor.Windows.Assets
} }
} }
[EditorOrder(1), EditorDisplay(null, "LOD"), Limit(0, Model.MaxLODs), VisibleIf("ShowUVs")] [EditorOrder(1), EditorDisplay(null, "LOD"), Limit(0, Model.MaxLODs, 0.01f), VisibleIf("ShowUVs")]
[Tooltip("Level Of Detail index to preview UVs layout.")] [Tooltip("Level Of Detail index to preview UVs layout at.")]
public int LOD = 0; public int LOD = 0;
[EditorOrder(2), EditorDisplay(null, "Mesh"), Limit(-1, 1000000), VisibleIf("ShowUVs")] [EditorOrder(2), EditorDisplay(null, "Mesh"), Limit(-1, 1000000, 0.01f), VisibleIf("ShowUVs")]
[Tooltip("Mesh index to preview UVs layout. Use -1 for all meshes")] [Tooltip("Mesh index to show UVs layout for. Use -1 to display all UVs of all meshes")]
public int Mesh = -1; public int Mesh = -1;
private bool ShowUVs => _uvChannel != UVChannel.None; private bool ShowUVs => _uvChannel != UVChannel.None;

View File

@@ -81,6 +81,7 @@ namespace FlaxEditor.Windows.Assets
} }
var resolution = group.FloatValue("Resolution Scale", Window.Editor.CodeDocs.GetTooltip(typeof(ModelTool.Options), nameof(ModelImportSettings.Settings.SDFResolution))); var resolution = group.FloatValue("Resolution Scale", Window.Editor.CodeDocs.GetTooltip(typeof(ModelTool.Options), nameof(ModelImportSettings.Settings.SDFResolution)));
resolution.ValueBox.SlideSpeed = 0.001f;
resolution.ValueBox.MinValue = 0.0001f; resolution.ValueBox.MinValue = 0.0001f;
resolution.ValueBox.MaxValue = 100.0f; resolution.ValueBox.MaxValue = 100.0f;
resolution.ValueBox.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f; resolution.ValueBox.Value = sdf.Texture != null ? sdf.ResolutionScale : 1.0f;

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.GUI;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEditor.Surface; using FlaxEditor.Surface;
using FlaxEditor.Viewport.Previews; using FlaxEditor.Viewport.Previews;
@@ -112,8 +113,56 @@ namespace FlaxEditor.Windows.Assets
} }
} }
private sealed class LayoutTabProxy
{
[EditorDisplay("Layout"), CustomEditor(typeof(Editor)), NoSerialize]
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public ParticleEmitterWindow Window;
private class Editor : CustomEditor
{
public override DisplayStyle Style => DisplayStyle.InlineIntoParent;
public override void Initialize(LayoutElementsContainer layout)
{
var window = (ParticleEmitterWindow)Values[0];
var emitter = window.Preview.Emitter;
if (emitter == null || !emitter.IsLoaded)
return;
var attributes = emitter.Layout;
var size = 0;
var height = 14;
foreach (var attribute in attributes)
{
layout.Label($" - {GetAttributeType(attribute.Format)} {attribute.Name}").Label.Height = height;
size += PixelFormatExtensions.SizeInBytes(attribute.Format);
}
var capacity = 0;
if (window.Surface != null && window.Surface.RootNode != null && window.Surface.RootNode.Values.Length > 0)
capacity = (int)window.Surface.RootNode.Values[0];
layout.Space(10);
layout.Label($"Particle size: {size} bytes\nParticle buffer size: {Utilities.Utils.FormatBytesCount((ulong)(size * capacity))}").Label.Height = height * 2;
}
private static string GetAttributeType(PixelFormat format)
{
switch (format)
{
case PixelFormat.R32_Float: return "float";
case PixelFormat.R32G32_Float: return "Float2";
case PixelFormat.R32G32B32_Float: return "Float3";
case PixelFormat.R32G32B32A32_Float: return "Float4";
case PixelFormat.R32_SInt: return "int";
case PixelFormat.R32_UInt: return "uint";
default: return format.ToString();
}
}
}
}
private readonly PropertiesProxy _properties; private readonly PropertiesProxy _properties;
private Tab _previewTab; private Tab _previewTab, _layoutTab;
private ToolStripButton _showSourceCodeButton;
/// <inheritdoc /> /// <inheritdoc />
public ParticleEmitterWindow(Editor editor, AssetItem item) public ParticleEmitterWindow(Editor editor, AssetItem item)
@@ -125,18 +174,22 @@ namespace FlaxEditor.Windows.Assets
PlaySimulation = true, PlaySimulation = true,
Parent = _split2.Panel1 Parent = _split2.Panel1
}; };
_preview.PreviewActor.ShowDebugDraw = true;
_preview.ShowDebugDraw = true;
// Asset properties proxy // Asset properties proxy
_properties = new PropertiesProxy(); _properties = new PropertiesProxy();
// Preview properties editor // Preview properties editor
_previewTab = new Tab("Preview"); _previewTab = new Tab("Preview");
_previewTab.Presenter.Select(new PreviewProxy _previewTab.Presenter.Select(new PreviewProxy { Window = this });
{
Window = this,
});
_tabs.AddTab(_previewTab); _tabs.AddTab(_previewTab);
// Particle data layout
_layoutTab = new Tab("Layout");
_layoutTab.Presenter.Select(new LayoutTabProxy { Window = this });
_tabs.AddTab(_layoutTab);
// Surface // Surface
_surface = new ParticleEmitterSurface(this, Save, _undo) _surface = new ParticleEmitterSurface(this, Save, _undo)
{ {
@@ -146,7 +199,8 @@ namespace FlaxEditor.Windows.Assets
// Toolstrip // Toolstrip
SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton); SurfaceUtils.PerformCommonSetup(this, _toolstrip, _surface, out _saveButton, out _undoButton, out _redoButton);
_toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode).LinkTooltip("Show generated shader source code"); _showSourceCodeButton = _toolstrip.AddButton(editor.Icons.Code64, ShowSourceCode);
_showSourceCodeButton.LinkTooltip("Show generated shader source code");
_toolstrip.AddSeparator(); _toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more"); _toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/particles/index.html")).LinkTooltip("See documentation to learn more");
} }
@@ -234,6 +288,7 @@ namespace FlaxEditor.Windows.Assets
_asset.WaitForLoaded(); _asset.WaitForLoaded();
_preview.PreviewActor.ResetSimulation(); _preview.PreviewActor.ResetSimulation();
_previewTab.Presenter.BuildLayoutOnUpdate(); _previewTab.Presenter.BuildLayoutOnUpdate();
_layoutTab.Presenter.BuildLayoutOnUpdate();
} }
} }
@@ -250,6 +305,7 @@ namespace FlaxEditor.Windows.Assets
// Init asset properties and parameters proxy // Init asset properties and parameters proxy
_properties.OnLoad(this); _properties.OnLoad(this);
_previewTab.Presenter.BuildLayoutOnUpdate(); _previewTab.Presenter.BuildLayoutOnUpdate();
_layoutTab.Presenter.BuildLayoutOnUpdate();
return false; return false;
} }
@@ -285,5 +341,15 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc /> /// <inheritdoc />
public SearchAssetTypes AssetType => SearchAssetTypes.ParticleEmitter; public SearchAssetTypes AssetType => SearchAssetTypes.ParticleEmitter;
/// <inheritdoc />
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (_asset == null)
return;
_showSourceCodeButton.Enabled = _asset.HasShaderCode;
}
} }
} }

View File

@@ -97,10 +97,7 @@ namespace FlaxEditor.Windows.Assets
// For single node selected scroll view so user can see it // For single node selected scroll view so user can see it
if (nodes.Count == 1) if (nodes.Count == 1)
{ ScrollToSelectedNode();
nodes[0].ExpandAllParents(true);
ScrollViewTo(nodes[0]);
}
} }
// Update properties editor // Update properties editor

View File

@@ -352,24 +352,12 @@ namespace FlaxEditor.Windows
editor.Options.Apply(editor.Options.Options); editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Performs auto pause on error"); }).SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
toolstrip.AddSeparator(); toolstrip.AddSeparator();
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => _groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => { OnGroupButtonPressed(0); })
{ .SetAutoCheck(true).LinkTooltip("Shows/hides error messages");
UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked); _groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () => { OnGroupButtonPressed(1); })
editor.Options.Options.Interface.DebugLogShowErrorMessages = _groupButtons[0].Checked; .SetAutoCheck(true).LinkTooltip("Shows/hides warning messages");
editor.Options.Apply(editor.Options.Options); _groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () => { OnGroupButtonPressed(2); })
}).SetAutoCheck(true).LinkTooltip("Shows/hides error messages"); .SetAutoCheck(true).LinkTooltip("Shows/hides info messages");
_groupButtons[1] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Warning32, () =>
{
UpdateLogTypeVisibility(LogGroup.Warning, _groupButtons[1].Checked);
editor.Options.Options.Interface.DebugLogShowWarningMessages = _groupButtons[1].Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Shows/hides warning messages");
_groupButtons[2] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Info32, () =>
{
UpdateLogTypeVisibility(LogGroup.Info, _groupButtons[2].Checked);
editor.Options.Options.Interface.DebugLogShowInfoMessages = _groupButtons[2].Checked;
editor.Options.Apply(editor.Options.Options);
}).SetAutoCheck(true).LinkTooltip("Shows/hides info messages");
UpdateCount(); UpdateCount();
// Split panel // Split panel
@@ -418,6 +406,27 @@ namespace FlaxEditor.Windows
OnEditorOptionsChanged(Editor.Options.Options); OnEditorOptionsChanged(Editor.Options.Options);
} }
private void OnGroupButtonPressed(int index)
{
UpdateLogTypeVisibility((LogGroup)index, _groupButtons[index].Checked);
if (Input.GetKey(KeyboardKeys.Shift))
{
for (int i = 0; i < (int)LogGroup.Max; i++)
{
if (i == index)
continue;
_groupButtons[i].Checked = !_groupButtons[index].Checked;
UpdateLogTypeVisibility((LogGroup)i, _groupButtons[i].Checked);
}
}
var options = Editor.Options.Options.Interface;
options.DebugLogShowErrorMessages = _groupButtons[0].Checked;
options.DebugLogShowWarningMessages = _groupButtons[1].Checked;
options.DebugLogShowInfoMessages = _groupButtons[2].Checked;
Editor.Options.Apply(Editor.Options.Options);
}
private void OnEditorOptionsChanged(EditorOptions options) private void OnEditorOptionsChanged(EditorOptions options)
{ {
_timestampsFormats = options.Interface.DebugLogTimestampsFormat; _timestampsFormats = options.Interface.DebugLogTimestampsFormat;
@@ -438,6 +447,10 @@ namespace FlaxEditor.Windows
/// </summary> /// </summary>
public void Clear() public void Clear()
{ {
lock (_locker)
{
_pendingEntries.Clear();
}
if (_entriesPanel == null) if (_entriesPanel == null)
return; return;
RemoveEntries(); RemoveEntries();
@@ -455,15 +468,9 @@ namespace FlaxEditor.Windows
// Create new entry // Create new entry
switch (_timestampsFormats) switch (_timestampsFormats)
{ {
case InterfaceOptions.TimestampsFormats.Utc: case InterfaceOptions.TimestampsFormats.Utc: desc.Title = $"[{DateTime.UtcNow}] {desc.Title}"; break;
desc.Title = $"[{DateTime.UtcNow}] {desc.Title}"; case InterfaceOptions.TimestampsFormats.LocalTime: desc.Title = $"[{DateTime.Now}] {desc.Title}"; break;
break; case InterfaceOptions.TimestampsFormats.TimeSinceStartup: desc.Title = string.Format("[{0:g}] ", TimeSpan.FromSeconds(Time.TimeSinceStartup)) + desc.Title; break;
case InterfaceOptions.TimestampsFormats.LocalTime:
desc.Title = $"[{DateTime.Now}] {desc.Title}";
break;
case InterfaceOptions.TimestampsFormats.TimeSinceStartup:
desc.Title = string.Format("[{0:g}] ", TimeSpan.FromSeconds(Time.TimeSinceStartup)) + desc.Title;
break;
} }
var newEntry = new LogEntry(this, ref desc); var newEntry = new LogEntry(this, ref desc);
@@ -732,10 +739,10 @@ namespace FlaxEditor.Windows
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnPlayBegin() public override void OnPlayBeginning()
{ {
// Clear on Play // Clear on Play
if (_clearOnPlayButton.Checked) if (Editor.Options.Options.Interface.DebugLogClearOnPlay)
{ {
Clear(); Clear();
} }

View File

@@ -219,6 +219,13 @@ namespace FlaxEditor.Windows
{ {
} }
/// <summary>
/// Called when Editor will leave the play mode.
/// </summary>
public virtual void OnPlayEnding()
{
}
/// <summary> /// <summary>
/// Called when Editor leaves the play mode. /// Called when Editor leaves the play mode.
/// </summary> /// </summary>

View File

@@ -405,6 +405,7 @@ namespace FlaxEditor.Windows
return; return;
Editor.Instance.SceneEditing.Delete(); Editor.Instance.SceneEditing.Delete();
}); });
InputActions.Add(options => options.FocusConsoleCommand, () => Editor.Instance.Windows.OutputLogWin.FocusCommand());
} }
private void ChangeViewportRatio(ViewportScaleOptions v) private void ChangeViewportRatio(ViewportScaleOptions v)
@@ -509,13 +510,7 @@ namespace FlaxEditor.Windows
selectedParents[i].OnDebugDraw(drawDebugData); selectedParents[i].OnDebugDraw(drawDebugData);
} }
} }
unsafe drawDebugData.DrawActors(true);
{
fixed (IntPtr* actors = drawDebugData.ActorsPtrs)
{
DebugDraw.DrawActors(new IntPtr(actors), drawDebugData.ActorsCount, true);
}
}
} }
DebugDraw.Draw(ref renderContext, task.OutputView); DebugDraw.Draw(ref renderContext, task.OutputView);
@@ -1181,6 +1176,12 @@ namespace FlaxEditor.Windows
if (!_cursorVisible) if (!_cursorVisible)
Screen.CursorVisible = true; Screen.CursorVisible = true;
} }
if (Editor.IsPlayMode && IsDocked && IsSelected && RootWindow.FocusedControl == null)
{
// Game UI cleared focus so regain it to maintain UI navigation just like game window does
FlaxEngine.Scripting.InvokeOnUpdate(Focus);
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -830,6 +830,15 @@ namespace FlaxEditor.Windows
OnOutputTextChanged(); OnOutputTextChanged();
} }
/// <summary>
/// Focus the debug command line and ensure that the output log window is visible.
/// </summary>
public void FocusCommand()
{
FocusOrShow();
_commandLineBox.Focus();
}
/// <inheritdoc /> /// <inheritdoc />
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {

View File

@@ -71,7 +71,7 @@ namespace FlaxEditor.Windows.Profiler
{ {
_warningText = new Label _warningText = new Label
{ {
Text = "Detailed memory profiling is disabled. Run with command line: -mem", Text = "Detailed memory profiling is disabled. Run with command line '-mem'",
TextColor = Color.Red, TextColor = Color.Red,
Visible = false, Visible = false,
Parent = layout, Parent = layout,

View File

@@ -146,6 +146,8 @@ namespace FlaxEditor.Windows.Profiler
{ {
var gpuResource = _gpuResourcesCached[i]; var gpuResource = _gpuResourcesCached[i];
ref var resource = ref resources[i]; ref var resource = ref resources[i];
if (!gpuResource)
continue;
// Try to reuse cached resource info // Try to reuse cached resource info
var gpuResourceId = gpuResource.ID; var gpuResourceId = gpuResource.ID;

View File

@@ -27,6 +27,7 @@ namespace FlaxEditor.Windows
private Panel _sceneTreePanel; private Panel _sceneTreePanel;
private bool _isUpdatingSelection; private bool _isUpdatingSelection;
private bool _isMouseDown; private bool _isMouseDown;
private bool _blockSceneTreeScroll = false;
private DragAssets _dragAssets; private DragAssets _dragAssets;
private DragActorType _dragActorType; private DragActorType _dragActorType;
@@ -34,6 +35,7 @@ namespace FlaxEditor.Windows
private DragScriptItems _dragScriptItems; private DragScriptItems _dragScriptItems;
private DragHandlers _dragHandlers; private DragHandlers _dragHandlers;
private bool _isDropping = false; private bool _isDropping = false;
private bool _forceScrollNodeToView = false;
/// <summary> /// <summary>
/// Scene tree panel. /// Scene tree panel.
@@ -91,6 +93,15 @@ namespace FlaxEditor.Windows
_tree.SelectedChanged += Tree_OnSelectedChanged; _tree.SelectedChanged += Tree_OnSelectedChanged;
_tree.RightClick += OnTreeRightClick; _tree.RightClick += OnTreeRightClick;
_tree.Parent = _sceneTreePanel; _tree.Parent = _sceneTreePanel;
_tree.AfterDeferredLayout += () =>
{
if (_forceScrollNodeToView)
{
_forceScrollNodeToView = false;
ScrollToSelectedNode();
}
};
headerPanel.Parent = this; headerPanel.Parent = this;
// Setup input actions // Setup input actions
@@ -102,6 +113,34 @@ namespace FlaxEditor.Windows
InputActions.Add(options => options.Rename, RenameSelection); InputActions.Add(options => options.Rename, RenameSelection);
} }
/// <inheritdoc />
public override void OnPlayBeginning()
{
base.OnPlayBeginning();
_blockSceneTreeScroll = true;
}
/// <inheritdoc />
public override void OnPlayBegin()
{
base.OnPlayBegin();
_blockSceneTreeScroll = false;
}
/// <inheritdoc />
public override void OnPlayEnding()
{
base.OnPlayEnding();
_blockSceneTreeScroll = true;
}
/// <inheritdoc />
public override void OnPlayEnd()
{
base.OnPlayEnd();
_blockSceneTreeScroll = true;
}
/// <summary> /// <summary>
/// Enables or disables vertical and horizontal scrolling on the scene tree panel. /// Enables or disables vertical and horizontal scrolling on the scene tree panel.
/// </summary> /// </summary>
@@ -142,6 +181,16 @@ namespace FlaxEditor.Windows
root.TreeNode.UpdateFilter(query); root.TreeNode.UpdateFilter(query);
_tree.UnlockChildrenRecursive(); _tree.UnlockChildrenRecursive();
// When keep the selected nodes in a view
var nodeSelection = _tree.Selection;
if (nodeSelection.Count != 0)
{
var node = nodeSelection[nodeSelection.Count - 1];
node.Expand(true);
_forceScrollNodeToView = true;
}
PerformLayout(); PerformLayout();
PerformLayout(); PerformLayout();
} }
@@ -250,7 +299,7 @@ namespace FlaxEditor.Windows
_tree.Select(nodes); _tree.Select(nodes);
// For single node selected scroll view so user can see it // For single node selected scroll view so user can see it
if (nodes.Count == 1) if (nodes.Count == 1 && !_blockSceneTreeScroll)
{ {
nodes[0].ExpandAllParents(true); nodes[0].ExpandAllParents(true);
_sceneTreePanel.ScrollViewTo(nodes[0]); _sceneTreePanel.ScrollViewTo(nodes[0]);
@@ -260,6 +309,12 @@ namespace FlaxEditor.Windows
_isUpdatingSelection = false; _isUpdatingSelection = false;
} }
/// <inheritdoc />
public override void OnEditorStateChanged()
{
_blockSceneTreeScroll = Editor.StateMachine.ReloadingScriptsState.IsActive;
}
private bool ValidateDragAsset(AssetItem assetItem) private bool ValidateDragAsset(AssetItem assetItem)
{ {
if (assetItem.IsOfType<SceneAsset>()) if (assetItem.IsOfType<SceneAsset>())

View File

@@ -42,6 +42,7 @@ namespace FlaxEditor.Windows.Search
if (value == _selectedItem || (value != null && !_matchedItems.Contains(value))) if (value == _selectedItem || (value != null && !_matchedItems.Contains(value)))
return; return;
// Restore the previous selected item to the non-selected color
if (_selectedItem != null) if (_selectedItem != null)
{ {
_selectedItem.BackgroundColor = Color.Transparent; _selectedItem.BackgroundColor = Color.Transparent;
@@ -54,6 +55,7 @@ namespace FlaxEditor.Windows.Search
_selectedItem.BackgroundColor = Style.Current.BackgroundSelected; _selectedItem.BackgroundColor = Style.Current.BackgroundSelected;
if (_matchedItems.Count > VisibleItemCount) if (_matchedItems.Count > VisibleItemCount)
{ {
_selectedItem.Focus();
_resultPanel.ScrollViewTo(_selectedItem, true); _resultPanel.ScrollViewTo(_selectedItem, true);
} }
} }
@@ -180,39 +182,17 @@ namespace FlaxEditor.Windows.Search
switch (key) switch (key)
{ {
case KeyboardKeys.ArrowDown: case KeyboardKeys.ArrowDown:
{
if (_matchedItems.Count == 0)
return true;
int currentPos;
if (_selectedItem != null)
{
currentPos = _matchedItems.IndexOf(_selectedItem) + 1;
if (currentPos >= _matchedItems.Count)
currentPos--;
}
else
{
currentPos = 0;
}
SelectedItem = _matchedItems[currentPos];
return true;
}
case KeyboardKeys.ArrowUp: case KeyboardKeys.ArrowUp:
{ {
if (_matchedItems.Count == 0) if (_matchedItems.Count == 0)
return true; return true;
int currentPos;
if (_selectedItem != null) var focusedIndex = _matchedItems.IndexOf(_selectedItem);
{ int delta = key == KeyboardKeys.ArrowDown ? -1 : 1;
currentPos = _matchedItems.IndexOf(_selectedItem) - 1; int nextIndex = Mathf.Wrap(focusedIndex - delta, 0, _matchedItems.Count - 1);
if (currentPos < 0) var nextItem = _matchedItems[nextIndex];
currentPos = 0;
} SelectedItem = nextItem;
else
{
currentPos = 0;
}
SelectedItem = _matchedItems[currentPos];
return true; return true;
} }
case KeyboardKeys.Return: case KeyboardKeys.Return:
@@ -234,6 +214,17 @@ namespace FlaxEditor.Windows.Search
Hide(); Hide();
return true; return true;
} }
case KeyboardKeys.Backspace:
{
// Alow the user to quickly focus the searchbar
if (_searchBox != null && !_searchBox.IsFocused)
{
_searchBox.Focus();
_searchBox.SelectAll();
return true;
}
break;
}
} }
return base.OnKeyDown(key); return base.OnKeyDown(key);

View File

@@ -20,6 +20,11 @@ namespace FlaxEditor.Windows.Search
/// </summary> /// </summary>
protected Image _icon; protected Image _icon;
/// <summary>
/// The color of the accent strip.
/// </summary>
protected Color _accentColor;
/// <summary> /// <summary>
/// The item name. /// The item name.
/// </summary> /// </summary>
@@ -56,7 +61,7 @@ namespace FlaxEditor.Windows.Search
var icon = new Image var icon = new Image
{ {
Size = new Float2(logoSize), Size = new Float2(logoSize),
Location = new Float2(5, (height - logoSize) / 2) Location = new Float2(7, (height - logoSize) / 2)
}; };
_icon = icon; _icon = icon;
@@ -74,6 +79,20 @@ namespace FlaxEditor.Windows.Search
typeLabel.TextColor = Style.Current.ForegroundGrey; typeLabel.TextColor = Style.Current.ForegroundGrey;
} }
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
// Select and focus the item on right click to prevent the search from being cleared
if (button == MouseButton.Right)
{
_finder.SelectedItem = this;
_finder.Hand = true;
Focus();
return true;
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button) public override bool OnMouseUp(Float2 location, MouseButton button)
{ {
@@ -86,6 +105,15 @@ namespace FlaxEditor.Windows.Search
return base.OnMouseUp(location, button); return base.OnMouseUp(location, button);
} }
/// <inheritdoc />
public override void Draw()
{
if (IsMouseOver)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Style.Current.BackgroundHighlighted);
base.Draw();
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnMouseEnter(Float2 location) public override void OnMouseEnter(Float2 location)
{ {
@@ -93,12 +121,7 @@ namespace FlaxEditor.Windows.Search
var root = RootWindow; var root = RootWindow;
if (root != null) if (root != null)
{
root.Cursor = CursorType.Hand; root.Cursor = CursorType.Hand;
}
_finder.SelectedItem = this;
_finder.Hand = true;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -128,6 +151,7 @@ namespace FlaxEditor.Windows.Search
{ {
_asset = item; _asset = item;
_asset.AddReference(this); _asset.AddReference(this);
_accentColor = Editor.Instance.ContentDatabase.GetProxy(item).AccentColor;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -176,9 +200,7 @@ namespace FlaxEditor.Windows.Search
{ {
string importLocation = System.IO.Path.GetDirectoryName(importPath); string importLocation = System.IO.Path.GetDirectoryName(importPath);
if (!string.IsNullOrEmpty(importLocation) && System.IO.Directory.Exists(importLocation)) if (!string.IsNullOrEmpty(importLocation) && System.IO.Directory.Exists(importLocation))
{
cm.AddButton("Show import location", () => FileSystem.ShowFileExplorer(importLocation)); cm.AddButton("Show import location", () => FileSystem.ShowFileExplorer(importLocation));
}
} }
} }
cm.AddSeparator(); cm.AddSeparator();
@@ -212,6 +234,10 @@ namespace FlaxEditor.Windows.Search
// Draw icon // Draw icon
var iconRect = _icon.Bounds; var iconRect = _icon.Bounds;
_asset.DrawThumbnail(ref iconRect); _asset.DrawThumbnail(ref iconRect);
// Draw color strip
var rect = iconRect with { Width = 2, Height = Height, Location = new Float2(2, 0) };
Render2D.FillRectangle(rect, _accentColor);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -242,9 +242,8 @@ void Asset::AddReference(IAssetReference* ref, bool week)
if (ref) if (ref)
{ {
//PROFILE_MEM(EngineDelegate); // Include references tracking memory within Delegate memory //PROFILE_MEM(EngineDelegate); // Include references tracking memory within Delegate memory
Locker.Lock(); ScopeLock lock(_referencesLocker);
_references.Add(ref); _references.Add(ref);
Locker.Unlock();
} }
} }
@@ -257,9 +256,8 @@ void Asset::RemoveReference(IAssetReference* ref, bool week)
{ {
if (ref) if (ref)
{ {
Locker.Lock(); ScopeLock lock(_referencesLocker);
_references.Remove(ref); _references.Remove(ref);
Locker.Unlock();
} }
if (!week) if (!week)
Platform::InterlockedDecrement(&_refCount); Platform::InterlockedDecrement(&_refCount);
@@ -681,6 +679,7 @@ void Asset::onLoaded_MainThread()
ASSERT(IsInMainThread()); ASSERT(IsInMainThread());
// Send event // Send event
ScopeLock lock(_referencesLocker);
for (const auto& e : _references) for (const auto& e : _references)
e.Item->OnAssetLoaded(this, this); e.Item->OnAssetLoaded(this, this);
OnLoaded(this); OnLoaded(this);
@@ -696,6 +695,7 @@ void Asset::onUnload_MainThread()
CancelStreaming(); CancelStreaming();
// Send event // Send event
ScopeLock lock(_referencesLocker);
for (const auto& e : _references) for (const auto& e : _references)
e.Item->OnAssetUnloaded(this, this); e.Item->OnAssetUnloaded(this, this);
OnUnloaded(this); OnUnloaded(this);

View File

@@ -7,6 +7,7 @@
#include "Engine/Core/Types/String.h" #include "Engine/Core/Types/String.h"
#include "Engine/Platform/CriticalSection.h" #include "Engine/Platform/CriticalSection.h"
#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Threading/ConcurrentSystemLocker.h"
#include "Config.h" #include "Config.h"
#include "Types.h" #include "Types.h"
@@ -63,6 +64,7 @@ protected:
int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved) int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved)
HashSet<IAssetReference*> _references; HashSet<IAssetReference*> _references;
CriticalSection _referencesLocker; // TODO: convert into a single interlocked exchange for the current thread owning lock
public: public:
/// <summary> /// <summary>

View File

@@ -414,16 +414,18 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
// Prepare // Prepare
auto& info = _shaderHeader.Material.Info; auto& info = _shaderHeader.Material.Info;
const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable; const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable;
const bool isOpaque = info.BlendMode == MaterialBlendMode::Opaque;
const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage; const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage;
const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && !isOpaque) || info.Domain == MaterialDomain::Particle;
const bool useTess = const bool useTess =
info.TessellationMode != TessellationMethod::None && info.TessellationMode != TessellationMethod::None &&
RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable; RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable;
const bool useDistortion = const bool useDistortion =
(info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) && (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) &&
info.BlendMode != MaterialBlendMode::Opaque && !isOpaque &&
EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseRefraction) && EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseRefraction) &&
(info.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None; (info.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None;
const MaterialShadingModel shadingModel = info.ShadingModel == MaterialShadingModel::CustomLit ? MaterialShadingModel::Unlit : info.ShadingModel;
// @formatter:off // @formatter:off
static const char* Numbers[] = static const char* Numbers[] =
@@ -435,7 +437,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
// Setup shader macros // Setup shader macros
options.Macros.Add({ "MATERIAL_DOMAIN", Numbers[(int32)info.Domain] }); options.Macros.Add({ "MATERIAL_DOMAIN", Numbers[(int32)info.Domain] });
options.Macros.Add({ "MATERIAL_BLEND", Numbers[(int32)info.BlendMode] }); options.Macros.Add({ "MATERIAL_BLEND", Numbers[(int32)info.BlendMode] });
options.Macros.Add({ "MATERIAL_SHADING_MODEL", Numbers[(int32)info.ShadingModel] }); options.Macros.Add({ "MATERIAL_SHADING_MODEL", Numbers[(int32)shadingModel] });
options.Macros.Add({ "MATERIAL_MASKED", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseMask) ? 1 : 0] }); options.Macros.Add({ "MATERIAL_MASKED", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseMask) ? 1 : 0] });
options.Macros.Add({ "DECAL_BLEND_MODE", Numbers[(int32)info.DecalBlendingMode] }); options.Macros.Add({ "DECAL_BLEND_MODE", Numbers[(int32)info.DecalBlendingMode] });
options.Macros.Add({ "USE_EMISSIVE", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseEmissive) ? 1 : 0] }); options.Macros.Add({ "USE_EMISSIVE", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseEmissive) ? 1 : 0] });
@@ -492,7 +494,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] }); options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] });
options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] }); options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] });
options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] }); options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] });
options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && isOpaque ? 1 : 0] });
options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] }); options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] });
#endif #endif
} }

View File

@@ -1003,7 +1003,7 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath); FileSystem::DeleteFile(tmpPath);
// Reload storage // Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath)) if (auto storage = ContentStorageManager::GetStorage(dstPath, false))
{ {
storage->Reload(); storage->Reload();
} }

View File

@@ -18,6 +18,7 @@
#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/Platform/MessageBox.h"
bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options) bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options)
{ {
@@ -118,6 +119,7 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
} }
#else #else
#define HANDLE_VORBIS(chunkIndex, dataPtr, dataSize) \ #define HANDLE_VORBIS(chunkIndex, dataPtr, dataSize) \
MessageBox::Show(TEXT("Vorbis format is not supported."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "Vorbis format is not supported."); \ LOG(Warning, "Vorbis format is not supported."); \
return CreateAssetResult::Error; return CreateAssetResult::Error;
#endif #endif
@@ -140,6 +142,7 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
break; \ break; \
default: \ default: \
{ \ { \
MessageBox::Show(TEXT("Unknown audio format."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning); \
LOG(Warning, "Unknown audio format."); \ LOG(Warning, "Unknown audio format."); \
return CreateAssetResult::Error; \ return CreateAssetResult::Error; \
} \ } \

View File

@@ -658,13 +658,7 @@ public:
--_count; --_count;
T* data = _allocation.Get(); T* data = _allocation.Get();
if (index < _count) if (index < _count)
{ Memory::MoveAssignItems(data + index, data + (index + 1), _count - index);
T* dst = data + index;
T* src = data + (index + 1);
const int32 count = _count - index;
for (int32 i = 0; i < count; ++i)
dst[i] = MoveTemp(src[i]);
}
Memory::DestructItems(data + _count, 1); Memory::DestructItems(data + _count, 1);
} }
@@ -709,7 +703,7 @@ public:
--_count; --_count;
T* data = _allocation.Get(); T* data = _allocation.Get();
if (_count) if (_count)
data[index] = data[_count]; data[index] = MoveTemp(data[_count]);
Memory::DestructItems(data + _count, 1); Memory::DestructItems(data + _count, 1);
} }

View File

@@ -209,8 +209,8 @@ public:
bool Get(const int32 index) const bool Get(const int32 index) const
{ {
ASSERT(index >= 0 && index < _count); ASSERT(index >= 0 && index < _count);
const ItemType offset = index / sizeof(ItemType); const ItemType offset = index / 64;
const ItemType bitMask = (ItemType)(int32)(1 << (index & ((int32)sizeof(ItemType) - 1))); const ItemType bitMask = 1ull << (index & 63ull);
const ItemType item = ((ItemType*)_allocation.Get())[offset]; const ItemType item = ((ItemType*)_allocation.Get())[offset];
return (item & bitMask) != 0; return (item & bitMask) != 0;
} }
@@ -223,13 +223,13 @@ public:
void Set(const int32 index, const bool value) void Set(const int32 index, const bool value)
{ {
ASSERT(index >= 0 && index < _count); ASSERT(index >= 0 && index < _count);
const ItemType offset = index / sizeof(ItemType); const ItemType offset = index / 64;
const ItemType bitMask = (ItemType)(int32)(1 << (index & ((int32)sizeof(ItemType) - 1))); const ItemType bitMask = 1ull << (index & 63ull);
ItemType& item = ((ItemType*)_allocation.Get())[offset]; ItemType& item = ((ItemType*)_allocation.Get())[offset];
if (value) if (value)
item |= bitMask; item |= bitMask; // Set the bit
else else
item &= ~bitMask; // Clear the bit item &= ~bitMask; // Unset the bit
} }
public: public:

View File

@@ -163,6 +163,15 @@ public:
{ {
} }
/// <summary>
/// Initializes an empty <see cref="Dictionary"/> without reserving any space.
/// </summary>
/// <param name="tag">The custom allocation tag.</param>
Dictionary(typename Base::AllocationTag tag)
: Base(tag)
{
}
/// <summary> /// <summary>
/// Initializes <see cref="Dictionary"/> by reserving space. /// Initializes <see cref="Dictionary"/> by reserving space.
/// </summary> /// </summary>

View File

@@ -140,6 +140,15 @@ public:
{ {
} }
/// <summary>
/// Initializes an empty <see cref="HashSet"/> without reserving any space.
/// </summary>
/// <param name="tag">The custom allocation tag.</param>
HashSet(typename Base::AllocationTag tag)
: Base(tag)
{
}
/// <summary> /// <summary>
/// Initializes <see cref="HashSet"/> by reserving space. /// Initializes <see cref="HashSet"/> by reserving space.
/// </summary> /// </summary>

View File

@@ -59,6 +59,7 @@ class HashSetBase
public: public:
// Type of allocation data used to store hash set buckets. // Type of allocation data used to store hash set buckets.
using AllocationData = typename AllocationType::template Data<BucketType>; using AllocationData = typename AllocationType::template Data<BucketType>;
using AllocationTag = typename AllocationType::Tag;
protected: protected:
int32 _elementsCount = 0; int32 _elementsCount = 0;
@@ -70,6 +71,11 @@ protected:
{ {
} }
HashSetBase(AllocationTag tag)
: _allocation(tag)
{
}
void MoveToEmpty(HashSetBase&& other) void MoveToEmpty(HashSetBase&& other)
{ {
_elementsCount = other._elementsCount; _elementsCount = other._elementsCount;

View File

@@ -26,6 +26,8 @@
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS \ #define PRAGMA_ENABLE_DEPRECATION_WARNINGS \
_Pragma("clang diagnostic pop") _Pragma("clang diagnostic pop")
#define PRAGMA_DISABLE_OPTIMIZATION
#define PRAGMA_ENABLE_OPTIMIZATION
#pragma clang diagnostic ignored "-Wswitch" #pragma clang diagnostic ignored "-Wswitch"
#pragma clang diagnostic ignored "-Wmacro-redefined" #pragma clang diagnostic ignored "-Wmacro-redefined"
@@ -54,6 +56,8 @@
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y) #define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS #define PRAGMA_DISABLE_DEPRECATION_WARNINGS
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS #define PRAGMA_ENABLE_DEPRECATION_WARNINGS
#define PRAGMA_DISABLE_OPTIMIZATION
#define PRAGMA_ENABLE_OPTIMIZATION
#elif defined(_MSC_VER) #elif defined(_MSC_VER)
@@ -86,6 +90,8 @@
__pragma(warning(disable: 4996)) __pragma(warning(disable: 4996))
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS \ #define PRAGMA_ENABLE_DEPRECATION_WARNINGS \
__pragma (warning(pop)) __pragma (warning(pop))
#define PRAGMA_DISABLE_OPTIMIZATION __pragma(optimize("", off))
#define PRAGMA_ENABLE_OPTIMIZATION __pragma(optimize("", on))
#pragma warning(disable: 4251) #pragma warning(disable: 4251)

View File

@@ -84,7 +84,7 @@ public:
bool EnableGlobalSDF = false; bool EnableGlobalSDF = false;
/// <summary> /// <summary>
/// Draw distance of the Global SDF. Actual value can be large when using DDGI. /// Draw distance of the Global SDF. Actual value can be larger when using DDGI.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(2001), EditorDisplay(\"Global SDF\"), Limit(1000), ValueCategory(Utils.ValueCategory.Distance)") API_FIELD(Attributes="EditorOrder(2001), EditorDisplay(\"Global SDF\"), Limit(1000), ValueCategory(Utils.ValueCategory.Distance)")
float GlobalSDFDistance = 15000.0f; float GlobalSDFDistance = 15000.0f;

View File

@@ -13,7 +13,6 @@ void ArenaAllocator::Free()
#if COMPILE_WITH_PROFILER #if COMPILE_WITH_PROFILER
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1); ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1);
#endif #endif
Allocator::Free(page->Memory);
Page* next = page->Next; Page* next = page->Next;
Allocator::Free(page); Allocator::Free(page);
page = next; page = next;
@@ -33,22 +32,94 @@ void* ArenaAllocator::Allocate(uint64 size, uint64 alignment)
// Create a new page if need to // Create a new page if need to
if (!page) if (!page)
{ {
uint64 pageSize = Math::Max<uint64>(_pageSize, size); uint64 pageSize = Math::Max<uint64>(_pageSize, size + alignment + sizeof(Page));
#if COMPILE_WITH_PROFILER #if COMPILE_WITH_PROFILER
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1); ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1);
#endif #endif
page = (Page*)Allocator::Allocate(sizeof(Page)); page = (Page*)Allocator::Allocate(pageSize);
page->Memory = Allocator::Allocate(pageSize);
page->Next = _first; page->Next = _first;
page->Offset = 0; page->Offset = sizeof(Page);
page->Size = (uint32)pageSize; page->Size = (uint32)pageSize;
_first = page; _first = page;
} }
// Allocate within a page // Allocate within a page
page->Offset = Math::AlignUp(page->Offset, (uint32)alignment); page->Offset = Math::AlignUp(page->Offset, (uint32)alignment);
void* mem = (byte*)page->Memory + page->Offset; void* mem = (byte*)page + page->Offset;
page->Offset += (uint32)size; page->Offset += (uint32)size;
return mem; return mem;
} }
void ConcurrentArenaAllocator::Free()
{
_locker.Lock();
// Free all pages
Page* page = (Page*)_first;
while (page)
{
#if COMPILE_WITH_PROFILER
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1);
#endif
Page* next = page->Next;
if (_free1)
_free1(page);
else
_free2(page, page->Size);
page = next;
}
// Unlink
_first = 0;
_totalBytes = 0;
_locker.Unlock();
}
void* ConcurrentArenaAllocator::Allocate(uint64 size, uint64 alignment)
{
RETRY:
// Check if the current page has some space left
Page* page = (Page*)Platform::AtomicRead(&_first);
if (page)
{
int64 offset = Platform::AtomicRead(&page->Offset);
int64 offsetAligned = Math::AlignUp(offset, (int64)alignment);
int64 end = offsetAligned + size;
if (end <= page->Size)
{
// Try to allocate within a page
if (Platform::InterlockedCompareExchange(&page->Offset, end, offset) != offset)
{
// Someone else changed allocated so retry (new offset might mismatch alignment)
goto RETRY;
}
Platform::InterlockedAdd(&_totalBytes, (int64)size);
return (byte*)page + offsetAligned;
}
}
// Page allocation is thread-synced
_locker.Lock();
// Check if page was unchanged by any other thread
if ((Page*)Platform::AtomicRead(&_first) == page)
{
uint64 pageSize = Math::Max<uint64>(_pageSize, size + alignment + sizeof(Page));
#if COMPILE_WITH_PROFILER
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1);
#endif
page = (Page*)(_allocate1 ? _allocate1(pageSize, 16) : _allocate2(pageSize));
page->Next = (Page*)_first;
page->Offset = sizeof(Page);
page->Size = (int64)pageSize;
Platform::AtomicStore(&_first, (intptr)page);
}
_locker.Unlock();
// Use a single cde for allocation
goto RETRY;
}

View File

@@ -3,6 +3,7 @@
#pragma once #pragma once
#include "Allocation.h" #include "Allocation.h"
#include "Engine/Platform/CriticalSection.h"
/// <summary> /// <summary>
/// Allocator that uses pages for stack-based allocs without freeing memory during it's lifetime. /// Allocator that uses pages for stack-based allocs without freeing memory during it's lifetime.
@@ -12,7 +13,6 @@ class ArenaAllocator
private: private:
struct Page struct Page
{ {
void* Memory;
Page* Next; Page* Next;
uint32 Offset, Size; uint32 Offset, Size;
}; };
@@ -66,21 +66,91 @@ public:
} }
}; };
/// <summary>
/// Allocator that uses pages for stack-based allocs without freeing memory during it's lifetime. Thread-safe to allocate memory from multiple threads at once.
/// </summary>
class ConcurrentArenaAllocator
{
private:
struct Page
{
Page* Next;
volatile int64 Offset;
int64 Size;
};
int32 _pageSize;
volatile int64 _first = 0;
volatile int64 _totalBytes = 0;
void*(*_allocate1)(uint64 size, uint64 alignment) = nullptr;
void(*_free1)(void* ptr) = nullptr;
void*(*_allocate2)(uint64 size) = nullptr;
void(*_free2)(void* ptr, uint64 size) = nullptr;
CriticalSection _locker;
public:
ConcurrentArenaAllocator(int32 pageSizeBytes, void* (*customAllocate)(uint64 size, uint64 alignment), void(*customFree)(void* ptr))
: _pageSize(pageSizeBytes)
, _allocate1(customAllocate)
, _free1(customFree)
{
}
ConcurrentArenaAllocator(int32 pageSizeBytes, void* (*customAllocate)(uint64 size), void(*customFree)(void* ptr, uint64 size))
: _pageSize(pageSizeBytes)
, _allocate2(customAllocate)
, _free2(customFree)
{
}
ConcurrentArenaAllocator(int32 pageSizeBytes = 1024 * 1024) // 1 MB by default
: ConcurrentArenaAllocator(pageSizeBytes, Allocator::Allocate, Allocator::Free)
{
}
~ConcurrentArenaAllocator()
{
Free();
}
// Gets the total amount of bytes allocated in arena (excluding alignment).
int64 GetTotalBytes() const
{
return Platform::AtomicRead(&_totalBytes);
}
// Allocates a chunk of unitialized memory.
void* Allocate(uint64 size, uint64 alignment = 1);
// Frees all memory allocations within allocator.
void Free();
// Creates a new object within the arena allocator.
template<class T, class... Args>
inline T* New(Args&&...args)
{
T* ptr = (T*)Allocate(sizeof(T));
new(ptr) T(Forward<Args>(args)...);
return ptr;
}
};
/// <summary> /// <summary>
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op. /// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
/// </summary> /// </summary>
class ArenaAllocation template<typename ArenaType>
class ArenaAllocationBase
{ {
public: public:
enum { HasSwap = true }; enum { HasSwap = true };
typedef ArenaAllocator* Tag; typedef ArenaType* Tag;
template<typename T> template<typename T>
class Data class Data
{ {
private: private:
T* _data = nullptr; T* _data = nullptr;
ArenaAllocator* _arena = nullptr; ArenaType* _arena = nullptr;
public: public:
FORCE_INLINE Data() FORCE_INLINE Data()
@@ -138,7 +208,17 @@ public:
FORCE_INLINE void Swap(Data& other) FORCE_INLINE void Swap(Data& other)
{ {
::Swap(_data, other._data); ::Swap(_data, other._data);
::Swap(_arena, other._arena); _arena = other._arena; // TODO: find a better way to move allocation with AllocationUtils::MoveToEmpty to preserve/maintain allocation tag ownership
} }
}; };
}; };
/// <summary>
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
/// </summary>
typedef ArenaAllocationBase<ArenaAllocator> ArenaAllocation;
/// <summary>
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
/// </summary>
typedef ArenaAllocationBase<ConcurrentArenaAllocator> ConcurrentArenaAllocation;

View File

@@ -104,12 +104,6 @@ public:
{ {
new(dst) T(); new(dst) T();
} }
/// <summary>
/// Constructs the item in the memory.
/// </summary>
/// <remarks>The optimized version is noop.</remarks>
/// <param name="dst">The address of the memory location to construct.</param>
template<typename T> template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyConstructible<T>::Value>::Type ConstructItem(T* dst) FORCE_INLINE static typename TEnableIf<TIsTriviallyConstructible<T>::Value>::Type ConstructItem(T* dst)
{ {
@@ -132,13 +126,6 @@ public:
++(T*&)dst; ++(T*&)dst;
} }
} }
/// <summary>
/// Constructs the range of items in the memory.
/// </summary>
/// <remarks>The optimized version is noop.</remarks>
/// <param name="dst">The address of the first memory location to construct.</param>
/// <param name="count">The number of element to construct. Can be equal 0.</param>
template<typename T> template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyConstructible<T>::Value>::Type ConstructItems(T* dst, int32 count) FORCE_INLINE static typename TEnableIf<TIsTriviallyConstructible<T>::Value>::Type ConstructItems(T* dst, int32 count)
{ {
@@ -163,14 +150,6 @@ public:
++src; ++src;
} }
} }
/// <summary>
/// Constructs the range of items in the memory from the set of arguments.
/// </summary>
/// <remarks>The optimized version uses low-level memory copy.</remarks>
/// <param name="dst">The address of the first memory location to construct.</param>
/// <param name="src">The address of the first memory location to pass to the constructor.</param>
/// <param name="count">The number of element to construct. Can be equal 0.</param>
template<typename T, typename U> template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type ConstructItems(T* dst, const U* src, int32 count) FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type ConstructItems(T* dst, const U* src, int32 count)
{ {
@@ -187,12 +166,6 @@ public:
{ {
dst->~T(); dst->~T();
} }
/// <summary>
/// Destructs the item in the memory.
/// </summary>
/// <remarks>The optimized version is noop.</remarks>
/// <param name="dst">The address of the memory location to destruct.</param>
template<typename T> template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyDestructible<T>::Value>::Type DestructItem(T* dst) FORCE_INLINE static typename TEnableIf<TIsTriviallyDestructible<T>::Value>::Type DestructItem(T* dst)
{ {
@@ -213,13 +186,6 @@ public:
++dst; ++dst;
} }
} }
/// <summary>
/// Destructs the range of items in the memory.
/// </summary>
/// <remarks>The optimized version is noop.</remarks>
/// <param name="dst">The address of the first memory location to destruct.</param>
/// <param name="count">The number of element to destruct. Can be equal 0.</param>
template<typename T> template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyDestructible<T>::Value>::Type DestructItems(T* dst, int32 count) FORCE_INLINE static typename TEnableIf<TIsTriviallyDestructible<T>::Value>::Type DestructItems(T* dst, int32 count)
{ {
@@ -242,15 +208,7 @@ public:
++src; ++src;
} }
} }
template<typename T, typename U>
/// <summary>
/// Copies the range of items using the assignment operator.
/// </summary>
/// <remarks>The optimized version is low-level memory copy.</remarks>
/// <param name="dst">The address of the first memory location to start assigning to.</param>
/// <param name="src">The address of the first memory location to assign from.</param>
/// <param name="count">The number of element to assign. Can be equal 0.</param>
template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyCopyAssignable<T>::Value>::Type CopyItems(T* dst, const T* src, int32 count) FORCE_INLINE static typename TEnableIf<TIsTriviallyCopyAssignable<T>::Value>::Type CopyItems(T* dst, const T* src, int32 count)
{ {
Platform::MemoryCopy(dst, src, count * sizeof(T)); Platform::MemoryCopy(dst, src, count * sizeof(T));
@@ -273,16 +231,31 @@ public:
++src; ++src;
} }
} }
template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveItems(T* dst, U* src, int32 count)
{
Platform::MemoryCopy(dst, src, count * sizeof(U));
}
/// <summary> /// <summary>
/// Moves the range of items in the memory from the set of arguments. /// Moves the range of items using the assignment operator.
/// </summary> /// </summary>
/// <remarks>The optimized version uses low-level memory copy.</remarks> /// <remarks>The optimized version uses low-level memory copy.</remarks>
/// <param name="dst">The address of the first memory location to move.</param> /// <param name="dst">The address of the first memory location to move.</param>
/// <param name="src">The address of the first memory location to pass to the move constructor.</param> /// <param name="src">The address of the first memory location to pass to the move constructor.</param>
/// <param name="count">The number of element to move. Can be equal 0.</param> /// <param name="count">The number of element to move. Can be equal 0.</param>
template<typename T, typename U> template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveItems(T* dst, U* src, int32 count) FORCE_INLINE static typename TEnableIf<!TIsBitwiseConstructible<T, U>::Value>::Type MoveAssignItems(T* dst, U* src, int32 count)
{
while (count--)
{
*dst = MoveTemp(*src);
++(T*&)dst;
++src;
}
}
template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveAssignItems(T* dst, U* src, int32 count)
{ {
Platform::MemoryCopy(dst, src, count * sizeof(U)); Platform::MemoryCopy(dst, src, count * sizeof(U));
} }

View File

@@ -154,9 +154,9 @@ void ObjectsRemoval::Dispose()
Object::~Object() Object::~Object()
{ {
#if BUILD_DEBUG #if BUILD_DEBUG && 0
// Prevent removing object that is still reverenced by the removal service // Prevent removing object that is still reverenced by the removal service
ASSERT(!ObjectsRemovalService::IsInPool(this)); //ASSERT(!ObjectsRemovalService::IsInPool(this));
#endif #endif
} }

View File

@@ -357,7 +357,8 @@ struct DebugDrawContext
DebugDrawData DebugDrawDefault; DebugDrawData DebugDrawDefault;
DebugDrawData DebugDrawDepthTest; DebugDrawData DebugDrawDepthTest;
Float3 LastViewPos = Float3::Zero; Float3 LastViewPos = Float3::Zero;
Matrix LastViewProj = Matrix::Identity; Matrix LastViewProjection = Matrix::Identity;
BoundingFrustum LastViewFrustum;
inline int32 Count() const inline int32 Count() const
{ {
@@ -779,9 +780,23 @@ Vector3 DebugDraw::GetViewPos()
return Context->LastViewPos; return Context->LastViewPos;
} }
BoundingFrustum DebugDraw::GetViewFrustum()
{
return Context->LastViewFrustum;
}
void DebugDraw::SetView(const RenderView& view)
{
Context->LastViewPos = view.Position;
Context->LastViewProjection = view.Projection;
Context->LastViewFrustum = view.Frustum;
}
void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTextureView* depthBuffer, bool enableDepthTest) void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTextureView* depthBuffer, bool enableDepthTest)
{ {
PROFILE_GPU_CPU("Debug Draw"); PROFILE_GPU_CPU("Debug Draw");
const RenderView& view = renderContext.View;
SetView(view);
// Ensure to have shader loaded and any lines to render // Ensure to have shader loaded and any lines to render
const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count(); const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count();
@@ -791,7 +806,6 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
if (renderContext.Buffers == nullptr || !DebugDrawVB) if (renderContext.Buffers == nullptr || !DebugDrawVB)
return; return;
auto context = GPUDevice::Instance->GetMainContext(); auto context = GPUDevice::Instance->GetMainContext();
const RenderView& view = renderContext.View;
if (Context->Origin != view.Origin) if (Context->Origin != view.Origin)
{ {
// Teleport existing debug shapes to maintain their location // Teleport existing debug shapes to maintain their location
@@ -800,8 +814,6 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
Context->DebugDrawDepthTest.Teleport(delta); Context->DebugDrawDepthTest.Teleport(delta);
Context->Origin = view.Origin; Context->Origin = view.Origin;
} }
Context->LastViewPos = view.Position;
Context->LastViewProj = view.Projection;
TaaJitterRemoveContext taaJitterRemove(view); TaaJitterRemoveContext taaJitterRemove(view);
// Fallback to task buffers // Fallback to task buffers
@@ -1383,7 +1395,7 @@ void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color,
int32 index; int32 index;
const Float3 centerF = sphere.Center - Context->Origin; const Float3 centerF = sphere.Center - Context->Origin;
const float radiusF = (float)sphere.Radius; const float radiusF = (float)sphere.Radius;
const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(centerF, radiusF, Context->LastViewPos, Context->LastViewProj); const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(centerF, radiusF, Context->LastViewPos, Context->LastViewProjection);
if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * 0.25f) if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * 0.25f)
index = 0; index = 0;
else if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * 0.25f) else if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * 0.25f)

View File

@@ -76,6 +76,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
// Gets the last view position when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes. // Gets the last view position when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static Vector3 GetViewPos(); static Vector3 GetViewPos();
// Gets the last view frustum when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static BoundingFrustum GetViewFrustum();
// Sets the rendering view information beforehand.
API_FUNCTION() static void SetView(API_PARAM(ref) const RenderView& view);
/// <summary> /// <summary>
/// Draws the collected debug shapes to the output. /// Draws the collected debug shapes to the output.

View File

@@ -103,17 +103,17 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{ {
auto& drawCall = drawCallsLists[lod][meshIndex]; auto& drawCall = drawCallsLists[lod][meshIndex];
if (!drawCall.DrawCall.Material) if (!drawCall.Material)
continue; continue;
DrawKey key; DrawKey key;
key.Mat = drawCall.DrawCall.Material; key.Mat = drawCall.Material;
key.Geo = &meshes.Get()[meshIndex]; key.Geo = &meshes.Get()[meshIndex];
key.Lightmap = instance.Lightmap.TextureIndex; key.Lightmap = instance.Lightmap.TextureIndex;
auto* e = result.TryGet(key); auto* e = result.TryGet(key);
if (!e) if (!e)
{ {
e = &result[key]; e = &result.Add(key, BatchedDrawCall(renderContext.List))->Value;
ASSERT_LOW_LAYER(key.Mat); ASSERT_LOW_LAYER(key.Mat);
e->DrawCall.Material = key.Mat; e->DrawCall.Material = key.Mat;
e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr; e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr;
@@ -127,7 +127,7 @@ void Foliage::DrawInstance(RenderContext& renderContext, FoliageInstance& instan
const Float3 translation = transform.Translation - renderContext.View.Origin; const Float3 translation = transform.Translation - renderContext.View.Origin;
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world); Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
constexpr float worldDeterminantSign = 1.0f; constexpr float worldDeterminantSign = 1.0f;
instanceData.Store(world, world, instance.Lightmap.UVsArea, drawCall.DrawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, lodDitherFactor); instanceData.Store(world, world, instance.Lightmap.UVsArea, drawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, lodDitherFactor);
} }
} }
@@ -430,7 +430,7 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr
{ {
const auto& mesh = meshes.Get()[meshIndex]; const auto& mesh = meshes.Get()[meshIndex];
auto& drawCall = drawCallsList.Get()[meshIndex]; auto& drawCall = drawCallsList.Get()[meshIndex];
drawCall.DrawCall.Material = nullptr; drawCall.Material = nullptr; // DrawInstance skips draw calls from meshes with unset material
// Check entry visibility // Check entry visibility
const auto& entry = type.Entries[mesh.GetMaterialSlotIndex()]; const auto& entry = type.Entries[mesh.GetMaterialSlotIndex()];
@@ -455,13 +455,13 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Dr
if (drawModes == DrawPass::None) if (drawModes == DrawPass::None)
continue; continue;
drawCall.DrawCall.Material = material; drawCall.Material = material;
drawCall.DrawCall.Surface.GeometrySize = mesh.GetBox().GetSize(); drawCall.Surface.GeometrySize = mesh.GetBox().GetSize();
} }
} }
// Draw instances of the foliage type // Draw instances of the foliage type
BatchedDrawCalls result; BatchedDrawCalls result(&renderContext.List->Memory);
DrawCluster(renderContext, type.Root, type, drawCallsLists, result); DrawCluster(renderContext, type.Root, type, drawCallsLists, result);
// Submit draw calls with valid instances added // Submit draw calls with valid instances added
@@ -998,6 +998,12 @@ void Foliage::RemoveAllInstances()
RebuildClusters(); RebuildClusters();
} }
void Foliage::RemoveLightmap()
{
for (auto& e : Instances)
e.RemoveLightmap();
}
static float GlobalDensityScale = 1.0f; static float GlobalDensityScale = 1.0f;
float Foliage::GetGlobalDensityScale() float Foliage::GetGlobalDensityScale()

View File

@@ -6,6 +6,7 @@
#include "FoliageInstance.h" #include "FoliageInstance.h"
#include "FoliageCluster.h" #include "FoliageCluster.h"
#include "FoliageType.h" #include "FoliageType.h"
#include "Engine/Core/Memory/ArenaAllocation.h"
#include "Engine/Level/Actor.h" #include "Engine/Level/Actor.h"
/// <summary> /// <summary>
@@ -139,6 +140,11 @@ public:
/// </summary> /// </summary>
API_FUNCTION() void RemoveAllInstances(); API_FUNCTION() void RemoveAllInstances();
/// <summary>
/// Removes the lightmap data from the foliage instances.
/// </summary>
API_FUNCTION() void RemoveLightmap();
public: public:
/// <summary> /// <summary>
/// Gets the global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices. /// Gets the global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices.
@@ -173,8 +179,8 @@ private:
} }
}; };
typedef Array<struct BatchedDrawCall, InlinedAllocation<8>> DrawCallsList; typedef Array<struct DrawCall, InlinedAllocation<8>> DrawCallsList;
typedef Dictionary<DrawKey, struct BatchedDrawCall, class RendererAllocation> BatchedDrawCalls; typedef Dictionary<DrawKey, struct BatchedDrawCall, ConcurrentArenaAllocation> BatchedDrawCalls;
void DrawInstance(RenderContext& renderContext, FoliageInstance& instance, const FoliageType& type, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const; void DrawInstance(RenderContext& renderContext, FoliageInstance& instance, const FoliageType& type, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, const FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const; void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, const FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
#else #else

View File

@@ -29,7 +29,7 @@ bool DeferredMaterialShader::CanUseLightmap() const
bool DeferredMaterialShader::CanUseInstancing(InstancingHandler& handler) const bool DeferredMaterialShader::CanUseInstancing(InstancingHandler& handler) const
{ {
handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, }; handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, };
return true; return _instanced;
} }
void DeferredMaterialShader::Bind(BindParameters& params) void DeferredMaterialShader::Bind(BindParameters& params)
@@ -42,6 +42,8 @@ void DeferredMaterialShader::Bind(BindParameters& params)
// Setup features // Setup features
const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv); const bool useLightmap = _info.BlendMode == MaterialBlendMode::Opaque && LightmapFeature::Bind(params, cb, srv);
if (_info.ShadingModel == MaterialShadingModel::CustomLit)
ForwardShadingFeature::Bind(params, cb, srv);
// Setup parameters // Setup parameters
MaterialParameter::BindMeta bindMeta; MaterialParameter::BindMeta bindMeta;
@@ -112,6 +114,9 @@ void DeferredMaterialShader::Unload()
bool DeferredMaterialShader::Load() bool DeferredMaterialShader::Load()
{ {
// TODO: support instancing when using ForwardShadingFeature
_instanced = _info.BlendMode == MaterialBlendMode::Opaque && _info.ShadingModel != MaterialShadingModel::CustomLit;
bool failed = false; bool failed = false;
auto psDesc = GPUPipelineState::Description::Default; auto psDesc = GPUPipelineState::Description::Default;
psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None; psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None;

View File

@@ -65,6 +65,7 @@ private:
private: private:
Cache _cache; Cache _cache;
Cache _cacheInstanced; Cache _cacheInstanced;
bool _instanced;
public: public:
DeferredMaterialShader(const StringView& name) DeferredMaterialShader(const StringView& name)

View File

@@ -25,7 +25,7 @@ DrawPass ForwardMaterialShader::GetDrawModes() const
bool ForwardMaterialShader::CanUseInstancing(InstancingHandler& handler) const bool ForwardMaterialShader::CanUseInstancing(InstancingHandler& handler) const
{ {
handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, }; handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, };
return true; return false; // TODO: support instancing when using ForwardShadingFeature
} }
void ForwardMaterialShader::Bind(BindParameters& params) void ForwardMaterialShader::Bind(BindParameters& params)

View File

@@ -103,6 +103,11 @@ API_ENUM() enum class MaterialShadingModel : byte
/// The foliage material. Intended for foliage materials like leaves and grass that need light scattering to transport simulation through the thin object. /// The foliage material. Intended for foliage materials like leaves and grass that need light scattering to transport simulation through the thin object.
/// </summary> /// </summary>
Foliage = 3, Foliage = 3,
/// <summary>
/// The custom lit shader that calculates own lighting such as Cel Shading. It has access to the scene lights data during both GBuffer and Forward pass rendering.
/// </summary>
CustomLit = 5,
}; };
/// <summary> /// <summary>

View File

@@ -605,10 +605,11 @@ int32 MaterialParams::GetVersionHash() const
void MaterialParams::Bind(MaterialParamsLink* link, MaterialParameter::BindMeta& meta) void MaterialParams::Bind(MaterialParamsLink* link, MaterialParameter::BindMeta& meta)
{ {
ASSERT(link && link->This); ASSERT(link && link->This);
for (int32 i = 0; i < link->This->Count(); i++) const int32 count = link->This->Count();
for (int32 i = 0; i < count; i++)
{ {
MaterialParamsLink* l = link; MaterialParamsLink* l = link;
while (l->Down && !l->This->At(i).IsOverride()) while (l->Down && !l->This->At(i).IsOverride() && l->Down->This->Count() == count)
{ {
l = l->Down; l = l->Down;
} }

View File

@@ -12,6 +12,7 @@
#include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h" #include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Platform/Platform.h" #include "Engine/Platform/Platform.h"
#include "Engine/Platform/MessageBox.h"
#define USE_MIKKTSPACE 1 #define USE_MIKKTSPACE 1
#include "ThirdParty/MikkTSpace/mikktspace.h" #include "ThirdParty/MikkTSpace/mikktspace.h"
#if USE_ASSIMP #if USE_ASSIMP
@@ -181,6 +182,7 @@ bool MeshData::GenerateLightmapUVs()
for (int32 i = 0; i < (int32)vb.size(); i++) for (int32 i = 0; i < (int32)vb.size(); i++)
lightmapChannel.Get()[i] = *(Float2*)&vb[i].uv; lightmapChannel.Get()[i] = *(Float2*)&vb[i].uv;
#else #else
MessageBox::Show(TEXT("Model lightmap UVs generation is not supported on this platform."), TEXT("Import error"), MessageBoxButtons::OK, MessageBoxIcon::Error);
LOG(Error, "Model lightmap UVs generation is not supported on this platform."); LOG(Error, "Model lightmap UVs generation is not supported on this platform.");
#endif #endif

View File

@@ -200,12 +200,19 @@ void SceneRenderTask::RemoveGlobalCustomPostFx(PostProcessEffect* fx)
void SceneRenderTask::CollectPostFxVolumes(RenderContext& renderContext) void SceneRenderTask::CollectPostFxVolumes(RenderContext& renderContext)
{ {
PROFILE_CPU();
// Cache WorldPosition used for PostFx volumes blending (RenderView caches it later on) // Cache WorldPosition used for PostFx volumes blending (RenderView caches it later on)
renderContext.View.WorldPosition = renderContext.View.Origin + renderContext.View.Position; renderContext.View.WorldPosition = renderContext.View.Origin + renderContext.View.Position;
if (EnumHasAllFlags(ActorsSource, ActorsSources::Scenes)) if (EnumHasAllFlags(ActorsSource, ActorsSources::Scenes))
{ {
Level::CollectPostFxVolumes(renderContext); //ScopeLock lock(Level::ScenesLock);
for (Scene* scene : Level::Scenes)
{
if (scene->IsActiveInHierarchy())
scene->Rendering.CollectPostFxVolumes(renderContext);
}
} }
if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomActors)) if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomActors))
{ {

Some files were not shown because too many files have changed in this diff Show More