Merge remote-tracking branch 'origin/1.10' into sdl_platform

# Conflicts:
#	Source/Editor/GUI/ContextMenu/ContextMenuBase.cs
#	Source/Engine/Platform/Linux/LinuxPlatform.cpp
This commit is contained in:
2025-03-09 13:16:25 +02:00
594 changed files with 19183 additions and 15772 deletions

View File

@@ -73,8 +73,11 @@ jobs:
.\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget
dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo
- name: Test
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
.\Binaries\Editor\Win64\Development\FlaxTests.exe
if(!$?) { Write-Host "Tests failed with exit code $LastExitCode" -ForegroundColor Red; Exit $LastExitCode }
dotnet test -f net8.0 Binaries\Tests\Flax.Build.Tests.dll
xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests
xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests

BIN
Content/Editor/Camera/M_Camera.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Camera/O_Camera.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/Gizmo/Material.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Gizmo/MaterialWire.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/Gizmo/RotationAxis.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Gizmo/ScaleAxis.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/Gizmo/TranslationAxis.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/Gizmo/WireBox.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Highlight Material.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/AudioListener.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/AudioSource.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/Decal.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/DirectionalLight.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/EnvironmentProbe.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/IconsMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/ParticleEffect.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/PointLight.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/Icons/SkyLight.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Icons/Skybox.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View File

@@ -83,6 +83,12 @@ float3 GetObjectSize(MaterialInput input)
return float3(1, 1, 1);
}
// Gets the current object scale (supports instancing)
float3 GetObjectScale(MaterialInput input)
{
return float3(1, 1, 1);
}
// Get the current object random value supports instancing)
float GetPerInstanceRandom(MaterialInput input)
{

View File

@@ -207,6 +207,20 @@ float3 GetObjectSize(MaterialInput input)
return GeometrySize * float3(world._m00, world._m11, world._m22);
}
// Gets the current object scale (supports instancing)
float3 GetObjectScale(MaterialInput input)
{
float4x4 world = WorldMatrix;
// Extract scale from the world matrix
float3 scale;
scale.x = length(float3(world._11, world._12, world._13));
scale.y = length(float3(world._21, world._22, world._23));
scale.z = length(float3(world._31, world._32, world._33));
return scale;
}
// Get the current object random value
float GetPerInstanceRandom(MaterialInput input)
{
@@ -297,7 +311,7 @@ VertexOutput VS_SplineModel(ModelInput input)
output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix);
// Pass vertex attributes
output.Geometry.TexCoord = input.TexCoord;
output.Geometry.TexCoord = input.TexCoord0;
#if USE_VERTEX_COLOR
output.Geometry.VertexColor = input.Color;
#endif

View File

@@ -163,6 +163,12 @@ float3 GetObjectSize(MaterialInput input)
return float3(1, 1, 1);
}
// Gets the current object scale (supports instancing)
float3 GetObjectScale(MaterialInput input)
{
return float3(1, 1, 1);
}
// Get the current object random value supports instancing)
float GetPerInstanceRandom(MaterialInput input)
{

View File

@@ -299,24 +299,22 @@ half3x3 CalcTangentToLocal(ModelInput input)
float3 normal = input.Normal.xyz * 2.0 - 1.0;
float3 tangent = input.Tangent.xyz * 2.0 - 1.0;
float3 bitangent = cross(normal, tangent) * bitangentSign;
return float3x3(tangent, bitangent, normal);
return (half3x3)float3x3(tangent, bitangent, normal);
}
half3x3 CalcTangentToWorld(in float4x4 world, in half3x3 tangentToLocal)
{
half3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world);
half3x3 localToWorld = (half3x3)RemoveScaleFromLocalToWorld((float3x3)world);
return mul(tangentToLocal, localToWorld);
}
float3 GetParticlePosition(uint ParticleIndex)
float3 GetParticlePosition(uint particleIndex)
{
return TransformParticlePosition(GetParticleVec3(ParticleIndex, PositionOffset));
return TransformParticlePosition(GetParticleVec3(particleIndex, PositionOffset));
}
// Vertex Shader function for Sprite Rendering
META_VS(true, FEATURE_LEVEL_ES2)
META_VS_IN_ELEMENT(POSITION, 0, R32G32_FLOAT, 0, 0, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(TEXCOORD, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
VertexOutput VS_Sprite(SpriteInput input, uint particleIndex : SV_InstanceID)
{
VertexOutput output;
@@ -407,7 +405,7 @@ VertexOutput VS_Sprite(SpriteInput input, uint particleIndex : SV_InstanceID)
output.InstanceParams = PerInstanceRandom;
// Calculate tanget space to world space transformation matrix for unit vectors
half3x3 tangentToLocal = float3x3(axisX, axisY, axisZ);
half3x3 tangentToLocal = half3x3(axisX, axisY, axisZ);
half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal);
output.TBN = tangentToWorld;
@@ -516,7 +514,7 @@ VertexOutput VS_Model(ModelInput input, uint particleIndex : SV_InstanceID)
output.Position = mul(float4(output.WorldPosition, 1), ViewProjectionMatrix);
// Pass vertex attributes
output.TexCoord = input.TexCoord;
output.TexCoord = input.TexCoord0;
output.ParticleIndex = particleIndex;
#if USE_VERTEX_COLOR
output.VertexColor = input.Color;
@@ -612,7 +610,7 @@ VertexOutput VS_Ribbon(RibbonInput input, uint vertexIndex : SV_VertexID)
{
output.TexCoord.x = (float)input.Order / (float)RibbonSegmentCount;
}
output.TexCoord.y = (vertexIndex + 1) & 0x1;
output.TexCoord.y = (float)((vertexIndex + 1) & 0x1);
output.TexCoord = output.TexCoord * RibbonUVScale + RibbonUVOffset;
// Compute world space vertex position
@@ -631,7 +629,7 @@ VertexOutput VS_Ribbon(RibbonInput input, uint vertexIndex : SV_VertexID)
output.InstanceParams = PerInstanceRandom;
// Calculate tanget space to world space transformation matrix for unit vectors
half3x3 tangentToLocal = float3x3(tangentRight, tangentUp, cross(tangentRight, tangentUp));
half3x3 tangentToLocal = half3x3(tangentRight, tangentUp, cross(tangentRight, tangentUp));
half3x3 tangentToWorld = CalcTangentToWorld(world, tangentToLocal);
output.TBN = tangentToWorld;

View File

@@ -2,6 +2,7 @@
// Version: @0
#define MATERIAL 1
#define MATERIAL_TEXCOORDS 4
#define USE_PER_VIEW_CONSTANTS 1
#define USE_PER_DRAW_CONSTANTS 1
@3
@@ -24,21 +25,29 @@ Buffer<float4> BoneMatrices : register(t1);
Buffer<float4> PrevBoneMatrices : register(t2);
#endif
#endif
// Geometry data passed though the graphics rendering stages up to the pixel shader
struct GeometryData
{
float3 WorldPosition : TEXCOORD0;
float2 TexCoord : TEXCOORD1;
float2 LightmapUV : TEXCOORD2;
float4 TexCoords01 : TEXCOORD1;
float4 TexCoords23 : TEXCOORD2;
float2 LightmapUV : TEXCOORD3;
#if USE_VERTEX_COLOR
half4 VertexColor : COLOR;
#endif
float3 WorldNormal : TEXCOORD3;
float4 WorldTangent : TEXCOORD4;
float3 WorldNormal : TEXCOORD4;
float4 WorldTangent : TEXCOORD5;
float3 PrevWorldPosition : TEXCOORD7;
nointerpolation uint ObjectIndex : TEXCOORD8;
};
float3 DecodeNormal(float4 normalMap)
{
float2 xy = normalMap.rg * 2.0 - 1.0;
return float3(xy, sqrt(1.0 - saturate(dot(xy, xy))));
}
// Interpolants passed from the vertex shader
struct VertexOutput
{
@@ -68,7 +77,7 @@ struct MaterialInput
{
float3 WorldPosition;
float TwoSidedSign;
float2 TexCoord;
float2 TexCoords[MATERIAL_TEXCOORDS];
#if USE_LIGHTMAP
float2 LightmapUV;
#endif
@@ -86,12 +95,18 @@ struct MaterialInput
#endif
};
// Map access to the main texure coordinate channel as UV0
#define TexCoord TexCoords[0]
// Extracts geometry data to the material input
MaterialInput GetGeometryMaterialInput(GeometryData geometry)
{
MaterialInput output = (MaterialInput)0;
output.WorldPosition = geometry.WorldPosition;
output.TexCoord = geometry.TexCoord;
output.TexCoords[0] = geometry.TexCoords01.xy;
output.TexCoords[1] = geometry.TexCoords01.zw;
output.TexCoords[2] = geometry.TexCoords23.xy;
output.TexCoords[3] = geometry.TexCoords23.zw;
#if USE_LIGHTMAP
output.LightmapUV = geometry.LightmapUV;
#endif
@@ -126,8 +141,8 @@ MaterialInput GetGeometryMaterialInput(GeometryData geometry)
GeometryData InterpolateGeometry(GeometryData p0, float w0, GeometryData p1, float w1, GeometryData p2, float w2)
{
GeometryData output = (GeometryData)0;
output.TexCoord = p0.TexCoord * w0 + p1.TexCoord * w1 + p2.TexCoord * w2;
output.LightmapUV = p0.LightmapUV * w0 + p1.LightmapUV * w1 + p2.LightmapUV * w2;
output.TexCoords01 = p0.TexCoords01 * w0 + p1.TexCoords01 * w1 + p2.TexCoords01 * w2;
output.TexCoords23 = p0.TexCoords23 * w0 + p1.TexCoords23 * w1 + p2.TexCoords23 * w2;
#if USE_VERTEX_COLOR
output.VertexColor = p0.VertexColor * w0 + p1.VertexColor * w1 + p2.VertexColor * w2;
#endif
@@ -223,6 +238,24 @@ float3 GetObjectSize(MaterialInput input)
return input.Object.GeometrySize * float3(world._m00, world._m11, world._m22);
}
// Gets the current object scale (supports instancing)
float3 GetObjectScale(MaterialInput input)
{
float4x4 world = input.Object.WorldMatrix;
// Get the squares of the scale factors
float scaleXSquared = dot(world[0].xyz, world[0].xyz);
float scaleYSquared = dot(world[1].xyz, world[1].xyz);
float scaleZSquared = dot(world[2].xyz, world[2].xyz);
// Take square root to get actual scales
return float3(
sqrt(scaleXSquared),
sqrt(scaleYSquared),
sqrt(scaleZSquared)
);
}
// Get the current object random value (supports instancing)
float GetPerInstanceRandom(MaterialInput input)
{
@@ -312,14 +345,15 @@ VertexOutput VS(ModelInput input)
output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix);
// Pass vertex attributes
output.Geometry.TexCoord = input.TexCoord;
output.Geometry.TexCoords01 = float4(input.TexCoord0, input.TexCoord1);
output.Geometry.TexCoords23 = float4(input.TexCoord2, input.TexCoord3);
#if USE_VERTEX_COLOR
output.Geometry.VertexColor = input.Color;
#endif
#if CAN_USE_LIGHTMAP
output.Geometry.LightmapUV = input.LightmapUV * object.LightmapArea.zw + object.LightmapArea.xy;
#else
output.Geometry.LightmapUV = input.LightmapUV;
output.Geometry.LightmapUV = float2(0, 0);
#endif
// Calculate tanget space to world space transformation matrix for unit vectors
@@ -459,7 +493,7 @@ META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0,
META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(BLENDWEIGHT, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(BLENDWEIGHTS, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true)
VertexOutput VS_Skinned(ModelInput_Skinned input)
{
VertexOutput output;
@@ -486,9 +520,10 @@ VertexOutput VS_Skinned(ModelInput_Skinned input)
output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix);
// Pass vertex attributes
output.Geometry.TexCoord = input.TexCoord;
output.Geometry.TexCoords01 = float4(input.TexCoord0, input.TexCoord1);
output.Geometry.TexCoords23 = float4(input.TexCoord2, input.TexCoord3);
#if USE_VERTEX_COLOR
output.Geometry.VertexColor = float4(0, 0, 0, 1);
output.Geometry.VertexColor = input.Color;
#endif
output.Geometry.LightmapUV = float2(0, 0);

View File

@@ -236,6 +236,12 @@ float3 GetObjectSize(MaterialInput input)
return float3(1, 1, 1);
}
// Gets the current object scale (supports instancing)
float3 GetObjectScale(MaterialInput input)
{
return float3(1, 1, 1);
}
// Get the current object random value
float GetPerInstanceRandom(MaterialInput input)
{
@@ -319,8 +325,6 @@ struct TerrainVertexInput
// Vertex Shader function for terrain rendering
META_VS(true, FEATURE_LEVEL_ES2)
META_VS_IN_ELEMENT(TEXCOORD, 0, R32G32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true)
META_VS_IN_ELEMENT(TEXCOORD, 1, R8G8B8A8_UNORM, 0, ALIGN, PER_VERTEX, 0, true)
VertexOutput VS(TerrainVertexInput input)
{
VertexOutput output;

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/Editor/Primitives/Capsule.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Cone.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Cube.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Cylinder.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Plane.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/Primitives/Sphere.flax (Stored with Git LFS)

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

BIN
Content/Editor/Wires Debug Material.flax (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
Content/Engine/DefaultMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/DefaultRadialMenu.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/DefaultTerrainMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/Models/Box.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/Models/Quad.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/Models/SimpleBox.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/Models/Sphere.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/Models/SphereLowPoly.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/SingleColorMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/SkyboxMaterial.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Engine/WhiteMaterial.flax (Stored with Git LFS)

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@@ -2,9 +2,9 @@
"Name": "Flax",
"Version": {
"Major": 1,
"Minor": 9,
"Minor": 10,
"Revision": 0,
"Build": 6606
"Build": 6702
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.",

View File

@@ -237,6 +237,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/GrammarAndSpelling/GrammarChecking/Exceptions/=Try_0020to_0020scripting/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/GrammarAndSpelling/GrammarChecking/RulesStates/=LanguageTool_002EEN_002EE_005FG/@EntryIndexedValue">DisabledByUser</s:String>
<s:Boolean x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/Color/@EntryValue">Blue</s:String>

View File

@@ -8,6 +8,7 @@
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Editor/Utilities/EditorUtilities.h"
#include "Engine/Graphics/PixelFormatSampler.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Content/Content.h"
@@ -240,7 +241,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon)
const TextureMipData* srcPixels = icon->GetData(0, srcPixelsMip);
const Color32* srcPixelsData = (Color32*)srcPixels->Data.Get();
const Int2 srcPixelsSize(Math::Max(1, icon->Width >> srcPixelsMip), Math::Max(1, icon->Height >> srcPixelsMip));
const auto sampler = TextureTool::GetSampler(icon->Format);
const auto sampler = PixelFormatSampler::Get(icon->Format);
ASSERT_LOW_LAYER(sampler);
// Write colors
@@ -252,7 +253,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon)
for (uint32 x = 0; x < width; x++)
{
float u = (float)x / width;
const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch);
const Color c = sampler->SampleLinear(srcPixelsData, Float2(u, v), srcPixelsSize, srcPixels->RowPitch);
colorData[idx++] = Color32(c).GetAsBGRA();
}
}
@@ -271,7 +272,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon)
{
uint32 x = packedX * 8 + pixelIdx;
float u = (float)x / width;
const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch);
const Color c = sampler->SampleLinear(srcPixelsData, Float2(u, v), srcPixelsSize, srcPixels->RowPitch);
if (c.A < 0.25f)
mask |= 1 << (7 - pixelIdx);
}
@@ -322,7 +323,7 @@ bool UpdateExeIcon(const String& path, const TextureData& icon)
const TextureData* iconRGBA8 = &icon;
TextureData tmpData1;
//if (icon.Format != PixelFormat::R8G8B8A8_UNorm)
if (TextureTool::GetSampler(icon.Format) == nullptr)
if (PixelFormatSampler::Get(icon.Format) == nullptr)
{
if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm))
{

View File

@@ -137,10 +137,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
if (!FileSystem::DirectoryExists(CacheFolder))
FileSystem::CreateDirectory(CacheFolder);
if (!FileSystem::FileExists(HeaderFilePath))
{
LOG(Warning, "Missing incremental build cooking assets cache.");
return;
}
auto file = FileReadStream::Open(HeaderFilePath);
if (file == nullptr)
@@ -158,7 +155,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
LOG(Info, "Loading incremental build cooking cache (entries count: {0})", entriesCount);
file->ReadBytes(&Settings, sizeof(Settings));
Entries.EnsureCapacity(Math::RoundUpToPowerOf2(static_cast<int32>(entriesCount * 3.0f)));
Entries.EnsureCapacity(entriesCount);
Array<Pair<String, DateTime>> fileDependencies;
for (int32 i = 0; i < entriesCount; i++)
@@ -166,7 +163,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
Guid id;
file->Read(id);
String typeName;
file->ReadString(&typeName);
file->Read(typeName);
DateTime fileModified;
file->Read(fileModified);
int32 fileDependenciesCount;
@@ -176,7 +173,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
for (int32 j = 0; j < fileDependenciesCount; j++)
{
Pair<String, DateTime>& f = fileDependencies[j];
file->ReadString(&f.First, 10);
file->Read(f.First, 10);
file->Read(f.Second);
}
@@ -311,9 +308,9 @@ void CookAssetsStep::CacheData::Save(CookingData& data)
{
auto& e = i->Value;
file->Write(e.ID);
file->WriteString(e.TypeName);
file->Write(e.TypeName);
file->Write(e.FileModified);
file->WriteInt32(e.FileDependencies.Count());
file->Write(e.FileDependencies.Count());
for (auto& f : e.FileDependencies)
{
file->Write(f.First, 10);
@@ -365,17 +362,27 @@ bool CookAssetsStep::ProcessDefaultAsset(AssetCookData& options)
bool CookAssetsStep::Process(CookingData& data, CacheData& cache, Asset* asset)
{
// Validate asset
PROFILE_CPU_ASSET(asset);
if (asset->IsVirtual())
{
// Virtual assets are not included into the build
return false;
}
const bool wasLoaded = asset->IsLoaded();
if (asset->WaitForLoaded())
{
LOG(Error, "Failed to load asset \'{0}\'", asset->ToString());
return true;
}
if (!wasLoaded)
{
// HACK: give some time to resave any old assets in Asset::onLoad after it's loaded
// This assumes that if Load Thread enters Asset::Save then it will get asset lock and hold it until asset is saved
// So we can take the same lock to wait for save end but first we need to wait for it to get that lock
// (in future try to handle it in a better way)
Platform::Sleep(5);
}
ScopeLock lock(asset->Locker);
// Switch based on an asset type
const auto asBinaryAsset = dynamic_cast<BinaryAsset*>(asset);
@@ -793,7 +800,10 @@ bool CookAssetsStep::Process(CookingData& data, CacheData& cache, BinaryAsset* a
// Prepare asset data
AssetInitData initData;
if (asset->Storage->LoadAssetHeader(asset->GetID(), initData))
{
LOG(Warning, "Failed to load asset {} header from storage '{}'", asset->GetID(), asset->Storage->GetPath());
return true;
}
initData.Header.UnlinkChunks();
initData.Metadata.Release();
for (auto& e : initData.Dependencies)
@@ -1165,7 +1175,7 @@ bool CookAssetsStep::Perform(CookingData& data)
assetRef = Content::LoadAsync<Asset>(assetId);
if (assetRef == nullptr)
{
data.Error(TEXT("Failed to load asset included in build."));
LOG(Error, "Failed to load asset {} included in build", assetId);
return true;
}
e.Info.TypeName = assetRef->GetTypeName();
@@ -1173,6 +1183,7 @@ bool CookAssetsStep::Perform(CookingData& data)
// Cook asset
if (Process(data, cache, assetRef.Get()))
{
LOG(Error, "Failed to process asset {}", assetRef->ToString());
cache.Save(data);
return true;
}
@@ -1205,10 +1216,17 @@ bool CookAssetsStep::Perform(CookingData& data)
// Copy file
if (!FileSystem::FileExists(cookedPath) || FileSystem::GetFileLastEditTime(cookedPath) >= FileSystem::GetFileLastEditTime(filePath))
{
if (FileSystem::CreateDirectory(StringUtils::GetDirectoryName(cookedPath)))
const String cookedFolder = StringUtils::GetDirectoryName(cookedPath);
if (FileSystem::CreateDirectory(cookedFolder))
{
LOG(Error, "Failed to create directory '{}'", cookedFolder);
return true;
}
if (FileSystem::CopyFile(cookedPath, filePath))
{
LOG(Error, "Failed to copy file from '{}' to '{}'", filePath, cookedPath);
return true;
}
}
// Count stats of file extension
@@ -1249,7 +1267,7 @@ bool CookAssetsStep::Perform(CookingData& data)
*(int32*)(bytes.Get() + 804) = contentKey;
*(Guid*)(bytes.Get() + 808) = gameSettings->SplashScreen;
Encryption::EncryptBytes(bytes.Get(), bytes.Count());
stream->WriteArray(bytes);
stream->Write(bytes);
Delete(stream);
}

View File

@@ -39,14 +39,9 @@ CustomEditorsUtilService CustomEditorsUtilServiceInstance;
struct Entry
{
MClass* DefaultEditor;
MClass* CustomEditor;
Entry()
{
DefaultEditor = nullptr;
CustomEditor = nullptr;
}
MClass* DefaultEditor = nullptr;
MClass* CustomEditor = nullptr;
MType* CustomEditorType = nullptr;
};
Dictionary<MType*, Entry> Cache(512);
@@ -63,11 +58,11 @@ MTypeObject* CustomEditorsUtil::GetCustomEditor(MTypeObject* refType)
Entry result;
if (Cache.TryGet(type, result))
{
if (result.CustomEditorType)
return INTERNAL_TYPE_GET_OBJECT(result.CustomEditorType);
MClass* editor = result.CustomEditor ? result.CustomEditor : result.DefaultEditor;
if (editor)
{
return MUtils::GetType(editor);
}
}
return nullptr;
}
@@ -157,7 +152,7 @@ void OnAssemblyLoaded(MAssembly* assembly)
else if (typeClass)
{
auto& entry = Cache[mclass->GetType()];
entry.CustomEditor = typeClass;
entry.CustomEditorType = type;
//LOG(Info, "Custom Editor {0} for type {1}", String(typeClass->GetFullName()), String(mclass->GetFullName()));
}

View File

@@ -405,20 +405,9 @@ namespace FlaxEditor.CustomEditors.Editors
// Select object
if (_value is Actor actor)
{
if (PresenterContext is PropertiesWindow)
Editor.Instance.SceneEditing.Select(actor);
else if (PresenterContext is PrefabWindow prefabWindow)
prefabWindow.Select(prefabWindow.Graph.Root.Find(actor));
}
Select(actor);
else if (_value is Script script && script.Actor)
{
var a = script.Actor;
if (PresenterContext is PropertiesWindow)
Editor.Instance.SceneEditing.Select(a);
else if (PresenterContext is PrefabWindow prefabWindow)
prefabWindow.Select(prefabWindow.Graph.Root.Find(a));
}
Select(script.Actor);
else if (_value is Asset asset)
Editor.Instance.Windows.ContentWin.Select(asset);
}
@@ -436,6 +425,14 @@ namespace FlaxEditor.CustomEditors.Editors
ShowDropDownMenu();
}
private void Select(Actor actor)
{
if (PresenterContext is PropertiesWindow)
Editor.Instance.SceneEditing.Select(actor);
else if (PresenterContext is PrefabWindow prefabWindow)
prefabWindow.Select(prefabWindow.Graph.Root.Find(actor));
}
private void DoDrag()
{
// Do the drag drop operation if has selected element

View File

@@ -42,6 +42,8 @@ public class Editor : EditorModule
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
options.ScriptingAPI.SystemReferences.Add("System.IO.Compression.ZipFile");
options.ScriptingAPI.SystemReferences.Add("System.Diagnostics.Process");
if (Profiler.Use(options))
options.ScriptingAPI.Defines.Add("USE_PROFILER");
// Enable optimizations for Editor, disable this for debugging the editor
if (options.Configuration == TargetConfiguration.Development)

View File

@@ -79,10 +79,6 @@ bool Editor::CheckProjectUpgrade()
Delete(file);
}
}
else
{
LOG(Warning, "Missing version cache file");
}
// Check if project is in the old, deprecated layout
if (EditorImpl::IsOldProjectXmlFormat)
@@ -403,7 +399,7 @@ int32 Editor::LoadProduct()
}
// Create new project option
if (CommandLine::Options.NewProject)
if (CommandLine::Options.NewProject.IsTrue())
{
Array<String> projectFiles;
FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly);
@@ -428,7 +424,7 @@ int32 Editor::LoadProduct()
}
}
}
if (CommandLine::Options.NewProject)
if (CommandLine::Options.NewProject.IsTrue())
{
if (projectPath.IsEmpty())
projectPath = Platform::GetWorkingDirectory();
@@ -529,7 +525,7 @@ int32 Editor::LoadProduct()
if (projectPath.IsEmpty())
{
#if PLATFORM_HAS_HEADLESS_MODE
if (CommandLine::Options.Headless)
if (CommandLine::Options.Headless.IsTrue())
{
Platform::Fatal(TEXT("Missing project path."));
return -1;
@@ -612,7 +608,7 @@ int32 Editor::LoadProduct()
// Validate project min supported version (older engine may try to load newer project)
// Special check if project specifies only build number, then major/minor fields are set to 0
const auto engineVersion = FLAXENGINE_VERSION;
for (auto e : projects)
for (const auto& e : projects)
{
const auto project = e.Item;
if (project->MinEngineVersion > engineVersion ||
@@ -657,7 +653,7 @@ Window* Editor::CreateMainWindow()
bool Editor::Init()
{
// Scripts project files generation from command line
if (CommandLine::Options.GenProjectFiles)
if (CommandLine::Options.GenProjectFiles.IsTrue())
{
const String customArgs = TEXT("-verbose -log -logfile=\"Cache/Intermediate/ProjectFileLog.txt\"");
const bool failed = ScriptsBuilder::GenerateProject(customArgs);

View File

@@ -355,14 +355,14 @@ namespace FlaxEditor.GUI.ContextMenu
}
/// <inheritdoc />
public override void Show(Control parent, Float2 location)
public override void Show(Control parent, Float2 location, ContextMenuDirection? direction = null)
{
// Remove last separator to make context menu look better
int lastIndex = _panel.Children.Count - 1;
if (lastIndex >= 0 && _panel.Children[lastIndex] is ContextMenuSeparator separator)
separator.Dispose();
base.Show(parent, location);
base.Show(parent, location, direction);
}
/// <inheritdoc />

View File

@@ -101,6 +101,16 @@ namespace FlaxEditor.GUI.ContextMenu
/// </summary>
public List<Window> ExternalPopups = new List<Window>();
/// <summary>
/// Optional flag that can disable popup visibility based on window focus and use external control via Hide.
/// </summary>
public bool UseVisibilityControl = true;
/// <summary>
/// Optional flag that can disable popup input capturing. Useful for transparent or visual-only popups.
/// </summary>
public bool UseInput = true;
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuBase"/> class.
/// </summary>
@@ -137,21 +147,26 @@ namespace FlaxEditor.GUI.ContextMenu
/// </summary>
/// <param name="parent">Parent control to attach to it.</param>
/// <param name="location">Popup menu origin location in parent control coordinates.</param>
public virtual void Show(Control parent, Float2 location)
/// <param name="direction">The custom popup direction. Null to use automatic direction.</param>
public virtual void Show(Control parent, Float2 location, ContextMenuDirection? direction = null)
{
Assert.IsNotNull(parent);
// Ensure to be closed
Hide();
bool isAlreadyVisible = Visible && _window;
if (!isAlreadyVisible)
Hide();
// Peek parent control window
var parentWin = parent.RootWindow;
if (parentWin == null)
{
Hide();
return;
}
// Check if show menu inside the other menu - then link as a child to prevent closing the calling menu window on lost focus
if (_parentCM == null && parentWin.ChildrenCount == 1 && parentWin.Children[0] is ContextMenuBase parentCM)
{
Hide();
parentCM.ShowChild(this, parentCM.PointFromScreen(parent.PointToScreen(location)), false);
return;
}
@@ -169,7 +184,7 @@ namespace FlaxEditor.GUI.ContextMenu
var monitorBounds = Platform.GetMonitorBounds(locationSS);
var rightBottomLocationSS = locationSS + dpiSize;
bool isUp = false, isLeft = false;
if (UseAutomaticDirectionFix)
if (UseAutomaticDirectionFix && direction == null)
{
var parentMenu = parent as ContextMenu;
if (monitorBounds.Bottom < rightBottomLocationSS.Y)
@@ -196,6 +211,26 @@ namespace FlaxEditor.GUI.ContextMenu
locationSS.X -= dpiSize.X;
}
}
else if (direction.HasValue)
{
switch (direction.Value)
{
case ContextMenuDirection.RightUp:
isUp = true;
break;
case ContextMenuDirection.LeftDown:
isLeft = true;
break;
case ContextMenuDirection.LeftUp:
isLeft = true;
isUp = true;
break;
}
if (isLeft)
locationSS.X -= dpiSize.X;
if (isUp)
locationSS.Y -= dpiSize.Y;
}
// Update direction flag
if (isUp)
@@ -203,47 +238,62 @@ namespace FlaxEditor.GUI.ContextMenu
else
_direction = isLeft ? ContextMenuDirection.LeftDown : ContextMenuDirection.RightDown;
// Create window
var desc = CreateWindowSettings.Default;
desc.Position = locationSS;
desc.StartPosition = WindowStartPosition.Manual;
desc.Size = dpiSize;
desc.Fullscreen = false;
desc.HasBorder = false;
desc.SupportsTransparency = false;
desc.ShowInTaskbar = false;
desc.ActivateWhenFirstShown = true;
desc.AllowInput = true;
desc.AllowMinimize = false;
desc.AllowMaximize = false;
desc.AllowDragAndDrop = false;
desc.IsTopmost = true;
desc.Type = WindowType.Popup;
desc.Parent = parentWin.Window;
desc.Title = "ContextMenu";
desc.HasSizingFrame = false;
OnWindowCreating(ref desc);
_window = Platform.CreateWindow(ref desc);
_window.GotFocus += OnWindowGotFocus;
_window.LostFocus += OnWindowLostFocus;
if (isAlreadyVisible)
{
_window.ClientBounds = new Rectangle(locationSS, dpiSize);
}
else
{
// Create window
var desc = CreateWindowSettings.Default;
desc.Position = locationSS;
desc.StartPosition = WindowStartPosition.Manual;
desc.Size = dpiSize;
desc.Fullscreen = false;
desc.HasBorder = false;
desc.SupportsTransparency = false;
desc.ShowInTaskbar = false;
desc.ActivateWhenFirstShown = UseInput;
desc.AllowInput = UseInput;
desc.AllowMinimize = false;
desc.AllowMaximize = false;
desc.AllowDragAndDrop = false;
desc.IsTopmost = true;
desc.Type = WindowType.Popup;
desc.Parent = parentWin.Window;
desc.Title = "ContextMenu";
desc.HasSizingFrame = false;
OnWindowCreating(ref desc);
_window = Platform.CreateWindow(ref desc);
if (UseVisibilityControl)
{
_window.GotFocus += OnWindowGotFocus;
_window.LostFocus += OnWindowLostFocus;
}
#if USE_IS_FOREGROUND && USE_SDL_WORKAROUNDS
// The focus between popup and parent windows doesn't change, force hide the popup when clicked on parent
parentWin.Window.MouseDown += OnWindowMouseDown;
_window.Closed += () => parentWin.Window.MouseDown -= OnWindowMouseDown;
// The focus between popup and parent windows doesn't change, force hide the popup when clicked on parent
parentWin.Window.MouseDown += OnWindowMouseDown;
_window.Closed += () => parentWin.Window.MouseDown -= OnWindowMouseDown;
#endif
// Attach to the window
_parentCM = parent as ContextMenuBase;
Parent = _window.GUI;
// Attach to the window
_parentCM = parent as ContextMenuBase;
Parent = _window.GUI;
}
// Show
Visible = true;
if (_window == null)
return;
_window.Show();
PerformLayout();
_previouslyFocused = parentWin.FocusedControl;
Focus();
OnShow();
if (UseVisibilityControl)
{
_previouslyFocused = parentWin.FocusedControl;
Focus();
OnShow();
}
}
/// <summary>
@@ -508,7 +558,7 @@ namespace FlaxEditor.GUI.ContextMenu
base.Update(deltaTime);
// Let root context menu to check if none of the popup windows
if (_parentCM == null && !IsForeground)
if (_parentCM == null && UseVisibilityControl && !IsForeground)
{
#if USE_SDL_WORKAROUNDS
if (!IsMouseOver)

View File

@@ -56,6 +56,11 @@ namespace FlaxEditor.GUI
/// </summary>
public event Action<Item> Clicked;
/// <summary>
/// Occurs when items gets focused.
/// </summary>
public event Action<Item> Focused;
/// <summary>
/// The tint color of the text.
/// </summary>
@@ -141,6 +146,10 @@ namespace FlaxEditor.GUI
protected virtual void GetTextRect(out Rectangle rect)
{
rect = new Rectangle(2, 0, Width - 4, Height);
// Indent for drop panel items is handled by drop panel margin
if (Parent is not DropPanel)
rect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0);
}
/// <inheritdoc />
@@ -155,10 +164,6 @@ namespace FlaxEditor.GUI
if (IsMouseOver || IsFocused)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted);
// Indent for drop panel items is handled by drop panel margin
if (Parent is not DropPanel)
textRect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0);
// Draw all highlights
if (_highlights != null)
{
@@ -207,6 +212,14 @@ namespace FlaxEditor.GUI
base.OnMouseLeave();
}
/// <inheritdoc />
public override void OnGotFocus()
{
base.OnGotFocus();
Focused?.Invoke(this);
}
/// <inheritdoc />
public override int Compare(Control other)
{
@@ -227,6 +240,7 @@ namespace FlaxEditor.GUI
private readonly Panel _scrollPanel;
private List<DropPanel> _categoryPanels;
private bool _waitingForInput;
private string _customSearch;
/// <summary>
/// Event fired when any item in this popup menu gets clicked.
@@ -248,26 +262,30 @@ namespace FlaxEditor.GUI
/// </summary>
/// <param name="width">The control width.</param>
/// <param name="height">The control height.</param>
public ItemsListContextMenu(float width = 320, float height = 220)
/// <param name="withSearch">Enables search field.</param>
public ItemsListContextMenu(float width = 320, float height = 220, bool withSearch = true)
{
// Context menu dimensions
Size = new Float2(width, height);
// Search box
_searchBox = new SearchBox(false, 1, 1)
if (withSearch)
{
Parent = this,
Width = Width - 3,
};
_searchBox.TextChanged += OnSearchFilterChanged;
_searchBox.ClearSearchButton.Clicked += () => PerformLayout();
// Search box
_searchBox = new SearchBox(false, 1, 1)
{
Parent = this,
Width = Width - 3,
};
_searchBox.TextChanged += OnSearchFilterChanged;
_searchBox.ClearSearchButton.Clicked += () => PerformLayout();
}
// Panel with scrollbar
_scrollPanel = new Panel(ScrollBars.Vertical)
{
Parent = this,
AnchorPreset = AnchorPresets.StretchAll,
Bounds = new Rectangle(0, _searchBox.Bottom + 1, Width, Height - _searchBox.Bottom - 2),
Bounds = withSearch ? new Rectangle(0, _searchBox.Bottom + 1, Width, Height - _searchBox.Bottom - 2) : new Rectangle(Float2.Zero, Size),
};
// Items list panel
@@ -286,12 +304,13 @@ namespace FlaxEditor.GUI
LockChildrenRecursive();
var searchText = _searchBox?.Text ?? _customSearch;
var items = ItemsPanel.Children;
for (int i = 0; i < items.Count; i++)
{
if (items[i] is Item item)
{
item.UpdateFilter(_searchBox.Text);
item.UpdateFilter(searchText);
item.UpdateScore();
}
}
@@ -305,13 +324,13 @@ namespace FlaxEditor.GUI
{
if (category.Children[j] is Item item2)
{
item2.UpdateFilter(_searchBox.Text);
item2.UpdateFilter(searchText);
item2.UpdateScore();
anyVisible |= item2.Visible;
}
}
category.Visible = anyVisible;
if (string.IsNullOrEmpty(_searchBox.Text))
if (string.IsNullOrEmpty(searchText))
category.Close(false);
else
category.Open(false);
@@ -322,8 +341,8 @@ namespace FlaxEditor.GUI
UnlockChildrenRecursive();
PerformLayout(true);
_searchBox.Focus();
TextChanged?.Invoke(_searchBox.Text);
_searchBox?.Focus();
TextChanged?.Invoke(searchText);
}
/// <summary>
@@ -355,6 +374,14 @@ namespace FlaxEditor.GUI
}
}
/// <summary>
/// Removes all added items.
/// </summary>
public void ClearItems()
{
ItemsPanel.DisposeChildren();
}
/// <summary>
/// Sorts the items list (by item name by default).
/// </summary>
@@ -368,6 +395,34 @@ namespace FlaxEditor.GUI
}
}
/// <summary>
/// Focuses and scroll to the given item to be selected.
/// </summary>
/// <param name="item">The item to select.</param>
public void SelectItem(Item item)
{
item.Focus();
ScrollViewTo(item);
}
/// <summary>
/// Applies custom search text query on the items list. Works even if search field is disabled
/// </summary>
/// <param name="text">The custom search text. Null to clear search.</param>
public void Search(string text)
{
if (_searchBox != null)
{
_searchBox.SetText(text);
}
else
{
_customSearch = text;
if (VisibleInHierarchy)
OnSearchFilterChanged();
}
}
/// <summary>
/// Adds the item to the view and registers for the click event.
/// </summary>
@@ -446,9 +501,11 @@ namespace FlaxEditor.GUI
}
}
_searchBox.Clear();
_searchBox?.Clear();
UnlockChildrenRecursive();
PerformLayout(true);
if (_customSearch != null)
OnSearchFilterChanged();
}
private List<Item> GetVisibleItems()
@@ -510,7 +567,7 @@ namespace FlaxEditor.GUI
if (RootWindow.FocusedControl == null)
{
// Focus search box if nothing is focused
_searchBox.Focus();
_searchBox?.Focus();
return true;
}
@@ -536,7 +593,7 @@ namespace FlaxEditor.GUI
var focusedIndex = items.IndexOf(focusedItem);
if (focusedIndex == 0)
{
_searchBox.Focus();
_searchBox?.Focus();
}
else if (focusedIndex > 0)
{
@@ -556,7 +613,7 @@ namespace FlaxEditor.GUI
break;
}
if (_waitingForInput)
if (_waitingForInput && _searchBox != null)
{
_waitingForInput = false;
_searchBox.Focus();

View File

@@ -68,7 +68,8 @@ namespace FlaxEditor.Gizmo
if (_vertexBuffer == null)
{
_vertexBuffer = new GPUBuffer();
var desc = GPUBufferDescription.Vertex(sizeof(Float3), 4);
var layout = GPUVertexLayout.Get([new VertexElement(VertexElement.Types.Position, 0, 0, false, PixelFormat.R32G32B32_Float)]);
var desc = GPUBufferDescription.Vertex(layout, sizeof(Float3), 4);
_vertexBuffer.Init(ref desc);
}
if (_indexBuffer == null)

View File

@@ -192,7 +192,7 @@ namespace FlaxEditor.Modules
/// Removes a quick action by name.
/// </summary>
/// <param name="name">The action's name.</param>
/// <returns>True when it succeed, false if there is no Quick Action with this name.</returns>
/// <returns>True when it succeeds, false if there is no Quick Action with this name.</returns>
public bool RemoveQuickAction(string name)
{
if (_quickActions == null)
@@ -288,6 +288,16 @@ namespace FlaxEditor.Modules
Profiler.EndEvent();
}
// Editor window
foreach (var window in Editor.Windows.Windows)
{
if (window is Windows.Assets.AssetEditorWindow)
continue;
var windowName = window.Title + " (window)";
if (nameRegex.Match(windowName).Success)
matches.Add(new SearchResult { Name = windowName, Type = "Window", Item = window });
}
Profiler.EndEvent();
return matches;
}
@@ -407,6 +417,9 @@ namespace FlaxEditor.Modules
Editor.Instance.Windows.EditWin.Viewport.FocusSelection();
}
break;
case Windows.EditorWindow window:
window.FocusOrShow();
break;
}
}

View File

@@ -56,6 +56,11 @@ namespace FlaxEditor.Modules
/// </summary>
public readonly GenerateScriptsProjectFilesProgress GenerateScriptsProjectFiles = new GenerateScriptsProjectFilesProgress();
/// <summary>
/// The assets loading progress handler.
/// </summary>
public readonly LoadAssetsProgress LoadAssets = new LoadAssetsProgress();
/// <summary>
/// Gets the first active handler.
/// </summary>
@@ -80,6 +85,7 @@ namespace FlaxEditor.Modules
RegisterHandler(CodeEditorOpen);
RegisterHandler(NavMeshBuilding);
RegisterHandler(GenerateScriptsProjectFiles);
RegisterHandler(LoadAssets);
}
/// <summary>

View File

@@ -447,6 +447,8 @@ namespace FlaxEditor.Modules
private void StateMachineOnStateChanged()
{
if (Editor.StateMachine.CurrentState is States.ClosingState)
return;
UpdateToolstrip();
UpdateStatusBar();
}

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
namespace FlaxEditor.Progress.Handlers
{
/// <summary>
/// Loading assets progress reporting handler.
/// </summary>
/// <seealso cref="FlaxEditor.Progress.ProgressHandler" />
public sealed class LoadAssetsProgress : ProgressHandler
{
private int _loadingAssetsCount;
/// <summary>
/// Initializes a new instance of the <see cref="LoadAssetsProgress"/> class.
/// </summary>
public LoadAssetsProgress()
{
Editor.Instance.EditorUpdate += OnEditorUpdate;
}
private void OnEditorUpdate()
{
var contentStats = FlaxEngine.Content.Stats;
if (_loadingAssetsCount == contentStats.LoadingAssetsCount)
return;
if (contentStats.LoadingAssetsCount == 0)
OnEnd();
else if (_loadingAssetsCount == 0)
OnStart();
if (contentStats.LoadingAssetsCount > 0)
{
var progress = (float)contentStats.LoadedAssetsCount / contentStats.AssetsCount;
var text = contentStats.LoadingAssetsCount == 1 ? "Loading 1 asset..." : $"Loading {contentStats.LoadingAssetsCount} assets...";
OnUpdate(progress, text);
}
_loadingAssetsCount = contentStats.LoadingAssetsCount;
}
}
}

View File

@@ -80,19 +80,23 @@ namespace FlaxEditor.SceneGraph.Actors
// Get vertex data for each mesh
var meshes = model.LODs[0].Meshes;
var meshesData = new SkinnedMesh.Vertex0[meshes.Length][];
var bonesVertices = new List<SkinnedMesh.Vertex0>[bones.Length];
var bonesVertices = new List<Float3>[bones.Length];
var indicesLimit = new Int4(bones.Length - 1);
for (int i = 0; i < meshes.Length; i++)
{
meshesData[i] = meshes[i].DownloadVertexBuffer0();
var meshData = meshes[i].DownloadVertexBuffer0();
for (int j = 0; j < meshData.Length; j++)
var assessor = new MeshAccessor();
if (assessor.LoadMesh(meshes[i]))
return;
var positionStream = assessor.Position();
var blendIndicesStream = assessor.BlendIndices();
var blendWeightsStream = assessor.BlendWeights();
if (!positionStream.IsValid || !blendIndicesStream.IsValid || !blendWeightsStream.IsValid)
continue;
var count = positionStream.Count;
for (int j = 0; j < count; j++)
{
ref var v = ref meshData[j];
var weights = (Float4)v.BlendWeights;
var indices = Int4.Min((Int4)v.BlendIndices, indicesLimit);
var weights = blendWeightsStream.GetFloat4(j);
var indices = Int4.Min((Int4)blendIndicesStream.GetFloat4(j), indicesLimit);
// Find the bone with the highest influence on the vertex
var maxWeightIndex = 0;
@@ -104,17 +108,18 @@ namespace FlaxEditor.SceneGraph.Actors
var maxWeightBone = indices[maxWeightIndex];
// Skin vertex position with the current pose
Float3.Transform(ref v.Position, ref skinningMatrices[indices[0]], out Float3 pos0);
Float3.Transform(ref v.Position, ref skinningMatrices[indices[1]], out Float3 pos1);
Float3.Transform(ref v.Position, ref skinningMatrices[indices[2]], out Float3 pos2);
Float3.Transform(ref v.Position, ref skinningMatrices[indices[3]], out Float3 pos3);
v.Position = pos0 * weights[0] + pos1 * weights[1] + pos2 * weights[2] + pos3 * weights[3];
var position = positionStream.GetFloat3(j);
Float3.Transform(ref position, ref skinningMatrices[indices[0]], out Float3 pos0);
Float3.Transform(ref position, ref skinningMatrices[indices[1]], out Float3 pos1);
Float3.Transform(ref position, ref skinningMatrices[indices[2]], out Float3 pos2);
Float3.Transform(ref position, ref skinningMatrices[indices[3]], out Float3 pos3);
position = pos0 * weights[0] + pos1 * weights[1] + pos2 * weights[2] + pos3 * weights[3];
// Add vertex to the bone list
ref var boneVertices = ref bonesVertices[maxWeightBone];
if (boneVertices == null)
boneVertices = new List<SkinnedMesh.Vertex0>();
boneVertices.Add(v);
boneVertices = new List<Float3>();
boneVertices.Add(position);
}
}
@@ -128,10 +133,10 @@ namespace FlaxEditor.SceneGraph.Actors
continue; // Skip not used bones
// Compute bounds of the vertices using this bone (in local space of the actor)
Float3 boneBoundsMin = boneVertices[0].Position, boneBoundsMax = boneVertices[0].Position;
Float3 boneBoundsMin = boneVertices[0], boneBoundsMax = boneVertices[0];
for (int i = 1; i < boneVertices.Count; i++)
{
var pos = boneVertices[i].Position;
var pos = boneVertices[i];
boneBoundsMin = Float3.Min(boneBoundsMin, pos);
boneBoundsMax = Float3.Max(boneBoundsMax, pos);
}
@@ -165,10 +170,10 @@ namespace FlaxEditor.SceneGraph.Actors
var boneBounds = BoundingBox.Zero;
if (boneVertices != null)
{
boneBounds = new BoundingBox(boneVertices[0].Position, boneVertices[0].Position);
boneBounds = new BoundingBox(boneVertices[0], boneVertices[0]);
for (int i = 1; i < boneVertices.Count; i++)
{
var pos = boneVertices[i].Position;
var pos = boneVertices[i];
boneBounds.Minimum = Float3.Min(boneBounds.Minimum, pos);
boneBounds.Minimum = Float3.Max(boneBounds.Maximum, pos);
}
@@ -263,7 +268,7 @@ namespace FlaxEditor.SceneGraph.Actors
var boneLocalBounds = BoundingBox.Zero;
for (int i = 0; i < boneVertices.Count; i++)
{
var pos = boneTransform.WorldToLocal(boneVertices[i].Position);
var pos = boneTransform.WorldToLocal(boneVertices[i]);
Vector3.Min(ref boneLocalBounds.Minimum, ref pos, out boneLocalBounds.Minimum);
Vector3.Max(ref boneLocalBounds.Maximum, ref pos, out boneLocalBounds.Maximum);
}
@@ -360,20 +365,20 @@ namespace FlaxEditor.SceneGraph.Actors
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
}
private static unsafe Matrix CalculateCovarianceMatrix(List<SkinnedMesh.Vertex0> vertices)
private static unsafe Matrix CalculateCovarianceMatrix(List<Float3> vertices)
{
// [Reference: https://en.wikipedia.org/wiki/Covariance_matrix]
// Calculate average point
var avg = Float3.Zero;
for (int i = 0; i < vertices.Count; i++)
avg += vertices[i].Position;
avg += vertices[i];
avg /= vertices.Count;
// Calculate distance to average for every point
var errors = new Float3[vertices.Count];
for (int i = 0; i < vertices.Count; i++)
errors[i] = vertices[i].Position - avg;
errors[i] = vertices[i] - avg;
var covariance = Matrix.Identity;
var cj = stackalloc float[3];
@@ -393,15 +398,9 @@ namespace FlaxEditor.SceneGraph.Actors
var row = new Float4(cj[0], cj[1], cj[2], 0.0f);
switch (j)
{
case 0:
covariance.Row1 = row;
break;
case 1:
covariance.Row2 = row;
break;
case 2:
covariance.Row3 = row;
break;
case 0: covariance.Row1 = row; break;
case 1: covariance.Row2 = row; break;
case 2: covariance.Row3 = row; break;
}
}
return covariance;

View File

@@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors
[HideInEditor]
public sealed class StaticModelNode : ActorNode
{
private Dictionary<IntPtr, Mesh.Vertex[]> _vertices;
private Dictionary<IntPtr, Float3[]> _vertices;
/// <inheritdoc />
public StaticModelNode(Actor actor)
@@ -53,14 +53,17 @@ namespace FlaxEditor.SceneGraph.Actors
var key = FlaxEngine.Object.GetUnmanagedPtr(mesh);
if (!_vertices.TryGetValue(key, out var verts))
{
verts = mesh.DownloadVertexBuffer();
var accessor = new MeshAccessor();
if (accessor.LoadMesh(mesh))
continue;
verts = accessor.Positions;
if (verts == null)
continue;
_vertices.Add(key, verts);
}
for (int i = 0; i < verts.Length; i++)
{
var v = verts[i].Position;
ref var v = ref verts[i];
var distance = Float3.DistanceSquared(ref pointLocal, ref v);
if (distance <= minDistance)
{

View File

@@ -586,7 +586,7 @@ bool ScriptsBuilderService::Init()
auto project = Editor::Project;
HashSet<ProjectInfo*> projects;
project->GetAllProjects(projects);
for (auto e : projects)
for (const auto& e : projects)
{
ProjectInfo* project = e.Item;
if (project->Name == TEXT("Flax"))

View File

@@ -919,6 +919,17 @@ namespace FlaxEditor.Surface.Archetypes
_selectedAnimation.SelectedIndex = 0;
}
/// <inheritdoc />
public override void SetValuesPaste(object[] values)
{
// Fix Guids pasted as string
// TODO: let copy/paste system in Visject handle value types to be strongly typed
for (int i = 5; i < values.Length; i += 2)
values[i] = Guid.Parse((string)values[i]);
base.SetValuesPaste(values);
}
/// <inheritdoc />
public override void OnValuesChanged()
{

View File

@@ -225,8 +225,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc />
public override void OnDestroy()
{
if (Surface != null)
Surface.RemoveContext(this);
Surface?.RemoveContext(this);
_maxTransitionsPerUpdate = null;
_reinitializeOnBecomingRelevant = null;
@@ -717,9 +716,12 @@ namespace FlaxEditor.Surface.Archetypes
LoadTransitions();
// Register for surface mouse events to handle transition arrows interactions
Surface.CustomMouseUp += OnSurfaceMouseUp;
Surface.CustomMouseDoubleClick += OnSurfaceMouseDoubleClick;
if (Surface != null)
{
// Register for surface mouse events to handle transition arrows interactions
Surface.CustomMouseUp += OnSurfaceMouseUp;
Surface.CustomMouseDoubleClick += OnSurfaceMouseDoubleClick;
}
}
private void OnSurfaceMouseUp(ref Float2 mouse, MouseButton buttons, ref bool handled)
@@ -1398,7 +1400,8 @@ namespace FlaxEditor.Surface.Archetypes
if (context.FindNode(9, 21) == null)
{
var wasEnabled = true;
if (Surface.Undo != null)
var undo = Surface?.Undo;
if (undo != null)
{
wasEnabled = Surface.Undo.Enabled;
Surface.Undo.Enabled = false;
@@ -1406,7 +1409,7 @@ namespace FlaxEditor.Surface.Archetypes
context.SpawnNode(9, 21, new Float2(100.0f));
if (Surface.Undo != null)
if (undo != null)
{
Surface.Undo.Enabled = wasEnabled;
}
@@ -1492,7 +1495,7 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc />
public override void OnDestroy()
{
Surface.RemoveContext(this);
Surface?.RemoveContext(this);
base.OnDestroy();
}
@@ -1886,7 +1889,7 @@ namespace FlaxEditor.Surface.Archetypes
if (context.FindNode(9, 22) == null)
{
var wasEnabled = true;
var undo = SourceState.Surface.Undo;
var undo = SourceState.Surface?.Undo;
if (undo != null)
{
wasEnabled = undo.Enabled;

View File

@@ -15,6 +15,29 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Material
{
/// <summary>
/// Blend modes (each enum item value maps to box ID).
/// </summary>
internal enum BlendMode
{
Normal,
Add,
Subtract,
Multiply,
Screen,
Overlay,
LinearBurn,
LinearLight,
Darken,
Lighten,
Difference,
Exclusion,
Divide,
HardLight,
PinLight,
HardMix
};
/// <summary>
/// Customized <see cref="SurfaceNode"/> for main material node.
/// </summary>
@@ -1073,6 +1096,49 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4),
]
},
new NodeArchetype
{
TypeID = 50,
Title = "Shift HSV",
Description = "Modifies the HSV of a color, values are from -1:1, preserves alpha",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(175, 80),
DefaultValues =
[
0.0f, // For Hue (index 0)
0.0f, // For Sat (index 1)
0.0f, // For Val (index 2)
],
Elements =
[
NodeElementArchetype.Factory.Input(0, "RGBA", true, typeof(Float4), 0), // No default
NodeElementArchetype.Factory.Input(1, "Hue", true, typeof(float), 1, 0), // Uses DefaultValues[0]
NodeElementArchetype.Factory.Input(2, "Sat", true, typeof(float), 2, 1), // Uses DefaultValues[1]
NodeElementArchetype.Factory.Input(3, "Val", true, typeof(float), 3, 2), // Uses DefaultValues[2]
NodeElementArchetype.Factory.Output(0, "RGBA", typeof(Float4), 4),
]
},
new NodeArchetype
{
TypeID = 51,
Title = "Color Blend",
Description = "Blends two colors using various blend modes. Passes base alpha through.",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(180, 80),
DefaultValues = new object[]
{
BlendMode.Normal, // Default blend mode
1.0f, // Default blend amount
},
Elements = new[]
{
NodeElementArchetype.Factory.Input(1, "Base Color", true, typeof(Float4), 0),
NodeElementArchetype.Factory.Input(2, "Blend Color", true, typeof(Float4), 1),
NodeElementArchetype.Factory.Input(3, "Intensity", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Enum(0, 0, 120, 0, typeof(BlendMode)), // Blend mode selector
NodeElementArchetype.Factory.Output(0, "Result", typeof(Float4), 3),
}
},
};
}
}

View File

@@ -123,9 +123,15 @@ namespace FlaxEditor.Surface.Archetypes
AlternativeTitles = new string[] { "UV", "UVs" },
Description = "Texture coordinates",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(110, 30),
Size = new Float2(150, 30),
DefaultValues = new object[]
{
0u
},
Elements = new[]
{
NodeElementArchetype.Factory.Text(0, 1, "Channel:"),
NodeElementArchetype.Factory.UnsignedInteger(50, 0, 0, -1, 0, 3),
NodeElementArchetype.Factory.Output(0, "UVs", typeof(Float2), 0)
}
},
@@ -396,21 +402,29 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 16,
Title = "World Triplanar Texture",
Description = "Projects a texture using world-space coordinates instead of UVs.",
Title = "Triplanar Texture",
Description = "Projects a texture using world-space coordinates with triplanar mapping.",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(240, 60),
Size = new Float2(280, 100),
DefaultValues = new object[]
{
1.0f,
1.0f
1.0f, // Scale
1.0f, // Blend
Float2.Zero, // Offset
2, // Sampler
false, // Local
},
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0),
NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(Float4), 1, 0),
NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 3)
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 3, 2),
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 5),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4 - 1, 100, 3, typeof(CommonSamplerType)),
NodeElementArchetype.Factory.Text(155, Surface.Constants.LayoutOffsetY * 4, "Local"),
NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4),
}
},
new NodeArchetype
@@ -450,7 +464,35 @@ namespace FlaxEditor.Surface.Archetypes
{
NodeElementArchetype.Factory.Output(0, "UVs", typeof(Float2), 0)
}
}
},
new NodeArchetype
{
TypeID = 23,
Title = "Triplanar Normal Map",
Description = "Projects a normal map texture using world-space coordinates with triplanar mapping.",
Flags = NodeFlags.MaterialGraph,
Size = new Float2(280, 100),
DefaultValues = new object[]
{
1.0f, // Scale
1.0f, // Blend
Float2.Zero, // Offset
2, // Sampler
false, // Local
},
Elements = new[]
{
NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0),
NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0),
NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 3, 2),
NodeElementArchetype.Factory.Output(0, "Vector", typeof(Float3), 5),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4 - 1, 100, 3, typeof(CommonSamplerType)),
NodeElementArchetype.Factory.Text(155, Surface.Constants.LayoutOffsetY * 4, "Local"),
NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4),
}
},
};
}
}

View File

@@ -1499,12 +1499,12 @@ namespace FlaxEditor.Surface.Archetypes
2,
// Stop 0
0.1f,
Color.CornflowerBlue,
0.05f,
Color.Black,
// Stop 1
0.9f,
Color.GreenYellow,
0.95f,
Color.White,
// Empty stops 2-7
0.0f, Color.Black,

View File

@@ -759,7 +759,7 @@ namespace FlaxEditor.Surface.ContextMenu
}
/// <inheritdoc />
public override void Show(Control parent, Float2 location)
public override void Show(Control parent, Float2 location, ContextMenuDirection? direction = null)
{
Show(parent, location, null);
}

View File

@@ -147,6 +147,11 @@ namespace FlaxEditor.Surface.Elements
{
value = (double)toSet;
}
else if (parentNode.GroupArchetype.GroupID != 2)
{
// Per-component editing is used only by nodes from Constant group, otherwise use float
value = toSet;
}
else if (value is Vector2 asVector2)
{
if (arch.BoxID == 0)

View File

@@ -87,7 +87,7 @@ namespace FlaxEditor.Surface
Title = TitleValue;
Color = ColorValue;
var size = SizeValue;
if (Surface.GridSnappingEnabled)
if (Surface != null && Surface.GridSnappingEnabled)
size = Surface.SnapToGrid(size, true);
Size = size;

View File

@@ -1001,6 +1001,15 @@ namespace FlaxEditor.Surface
_isDuringValuesEditing = value;
}
/// <summary>
/// Sets teh node values from the given pasted source. Can be overriden to perform validation or custom values processing.
/// </summary>
/// <param name="values">The input values array.</param>
public virtual void SetValuesPaste(object[] values)
{
Values = values;
}
/// <summary>
/// Called when node values set gets changed.
/// </summary>

View File

@@ -2,8 +2,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Surface.Undo;
using FlaxEngine;
namespace FlaxEditor.Surface
{
@@ -57,6 +57,28 @@ namespace FlaxEditor.Surface
return null;
}
/// <summary>
/// Opens the surface context with the given owning nodes IDs path.
/// </summary>
/// <param name="nodePath">The node ids path.</param>
/// <returns>Found context or null if cannot.</returns>
public VisjectSurfaceContext OpenContext(Span<uint> nodePath)
{
OpenContext(RootContext.Context);
if (nodePath != null && nodePath.Length != 0)
{
for (int i = 0; i < nodePath.Length; i++)
{
var node = Context.FindNode(nodePath[i]);
if (node is ISurfaceContext context)
OpenContext(context);
else
return null;
}
}
return Context;
}
/// <summary>
/// Creates the Visject surface context for the given surface data source context.
/// </summary>
@@ -101,7 +123,12 @@ namespace FlaxEditor.Surface
if (_root == null)
_root = surfaceContext;
else if (ContextStack.Contains(surfaceContext))
throw new ArgumentException("Context has been already added to the stack.");
{
// Go up until the given context
while (ContextStack.First() != surfaceContext)
CloseContext();
return;
}
// Change stack
ContextStack.Push(surfaceContext);

View File

@@ -286,13 +286,14 @@ namespace FlaxEditor.Surface
// Initialize
if (nodeData.Values != null && node.Values.Length > 0)
{
if (node.Values != null && node.Values.Length == nodeData.Values.Length)
var nodeValues = (object[])node.Values?.Clone();
if (nodeValues != null && nodeValues.Length == nodeData.Values.Length)
{
// Copy and fix values (Json deserializes may output them in a different format)
for (int l = 0; l < node.Values.Length; l++)
for (int l = 0; l < nodeData.Values.Length; l++)
{
var src = nodeData.Values[l].Value;
var dst = node.Values[l];
var dst = nodeValues[l];
try
{
@@ -364,13 +365,24 @@ namespace FlaxEditor.Surface
Editor.LogWarning(ex);
}
node.Values[l] = src;
nodeValues[l] = src;
}
}
else if (node.Archetype.Flags.HasFlag(NodeFlags.VariableValuesSize))
{
// Copy values
nodeValues = new object[nodeData.Values.Length];
for (int l = 0; l < nodeData.Values.Length; l++)
{
nodeValues[l] = nodeData.Values[l].Value;
}
}
else
{
Editor.LogWarning("Invalid node custom values.");
}
if (nodeValues != null)
node.SetValuesPaste(nodeValues);
}
Context.OnControlLoaded(node, SurfaceNodeActions.Paste);
@@ -445,7 +457,8 @@ namespace FlaxEditor.Surface
// Select those nodes
Select(nodes.Values);
MarkAsEdited();
if (nodes.Count > 0)
MarkAsEdited();
}
catch (Exception ex)
{

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