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:
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
@@ -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
|
||||
|
||||
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.
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.
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/Icons/Decal.flax
LFS
BIN
Content/Editor/Icons/Decal.flax
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/Icons/Skybox.flax
LFS
BIN
Content/Editor/Icons/Skybox.flax
LFS
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
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.
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/Engine/Models/Box.flax
LFS
BIN
Content/Engine/Models/Box.flax
LFS
Binary file not shown.
BIN
Content/Engine/Models/Quad.flax
LFS
BIN
Content/Engine/Models/Quad.flax
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.
Binary file not shown.
Binary file not shown.
BIN
Content/Shaders/Shadows.flax
LFS
BIN
Content/Shaders/Shadows.flax
LFS
Binary file not shown.
@@ -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.",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -447,6 +447,8 @@ namespace FlaxEditor.Modules
|
||||
|
||||
private void StateMachineOnStateChanged()
|
||||
{
|
||||
if (Editor.StateMachine.CurrentState is States.ClosingState)
|
||||
return;
|
||||
UpdateToolstrip();
|
||||
UpdateStatusBar();
|
||||
}
|
||||
|
||||
40
Source/Editor/Progress/Handlers/LoadAssetsProgress.cs
Normal file
40
Source/Editor/Progress/Handlers/LoadAssetsProgress.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user