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

This commit is contained in:
2025-06-02 18:15:16 +03:00
124 changed files with 1244 additions and 436 deletions

View File

@@ -15,6 +15,8 @@
#if USE_EDITOR
#include "Engine/Engine/Globals.h"
ThreadLocal<bool> ContentDeprecatedFlags;
void ContentDeprecated::Mark()
@@ -467,11 +469,13 @@ void Asset::CancelStreaming()
{
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
Locker.Lock();
auto loadTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
Locker.Unlock();
if (loadTask)
if (loadingTask)
{
loadTask->Cancel();
Platform::AtomicStore(&_loadingTask, 0);
LOG(Warning, "Cancel loading task for \'{0}\'", ToString());
loadingTask->Cancel();
}
}
@@ -590,7 +594,7 @@ bool Asset::onLoad(LoadAssetTask* task)
#if USE_EDITOR
// Auto-save deprecated assets to get rid of data in an old format
if (isDeprecated && isLoaded)
if (isDeprecated && isLoaded && !IsVirtual() && !GetPath().StartsWith(StringUtils::GetDirectoryName(Globals::TemporaryFolder)))
{
PROFILE_CPU_NAMED("Asset.Save");
LOG(Info, "Resaving asset '{}' that uses deprecated data format", ToString());
@@ -632,18 +636,11 @@ void Asset::onUnload_MainThread()
ASSERT(IsInMainThread());
// Cancel any streaming before calling OnUnloaded event
CancelStreaming();
// Send event
OnUnloaded(this);
// Check if is during loading
auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
if (loadingTask != nullptr)
{
// Cancel loading
Platform::AtomicStore(&_loadingTask, 0);
LOG(Warning, "Cancel loading task for \'{0}\'", ToString());
loadingTask->Cancel();
}
}
#if USE_EDITOR

View File

@@ -34,6 +34,10 @@ public:
public:
// [ContentLoadTask]
String ToString() const override
{
return String::Format(TEXT("Load Asset Data Task ({}, {}, {})"), (int32)GetState(), _chunks, _asset ? _asset->GetPath() : String::Empty);
}
bool HasReference(Object* obj) const override
{
return obj == _asset;

View File

@@ -22,7 +22,7 @@ public:
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(SkeletonMask, 1);
IMPORT_SETUP(SkeletonMask, 2);
// Chunk 0
if (context.AllocateChunk(0))

View File

@@ -93,6 +93,9 @@ public:
// Calculates the inverse of the specified matrix.
static void Invert(const Double4x4& value, Double4x4& result);
// Creates a left-handed, look-at matrix.
static void LookAt(const Double3& eye, const Double3& target, const Double3& up, Double4x4& result);
// Calculates the product of two matrices.
static void Multiply(const Double4x4& left, const Double4x4& right, Double4x4& result);

View File

@@ -1001,6 +1001,37 @@ void Double4x4::Invert(const Double4x4& value, Double4x4& result)
result.M44 = +d44 * det;
}
void Double4x4::LookAt(const Double3& eye, const Double3& target, const Double3& up, Double4x4& result)
{
Double3 xaxis, yaxis, zaxis;
Double3::Subtract(target, eye, zaxis);
zaxis.Normalize();
Double3::Cross(up, zaxis, xaxis);
xaxis.Normalize();
Double3::Cross(zaxis, xaxis, yaxis);
result.M11 = xaxis.X;
result.M21 = xaxis.Y;
result.M31 = xaxis.Z;
result.M12 = yaxis.X;
result.M22 = yaxis.Y;
result.M32 = yaxis.Z;
result.M13 = zaxis.X;
result.M23 = zaxis.Y;
result.M33 = zaxis.Z;
result.M14 = 0.0f;
result.M24 = 0.0f;
result.M34 = 0.0f;
result.M41 = -Double3::Dot(xaxis, eye);
result.M42 = -Double3::Dot(yaxis, eye);
result.M43 = -Double3::Dot(zaxis, eye);
result.M44 = 1.0f;
}
void Double4x4::Multiply(const Double4x4& left, const Double4x4& right, Double4x4& result)
{
result.M11 = left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31 + left.M14 * right.M41;

View File

@@ -1149,7 +1149,7 @@ namespace FlaxEngine
}
/// <summary>
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis. The input vectors don't need to be normalized.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
@@ -1179,7 +1179,7 @@ namespace FlaxEngine
}
/// <summary>
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
/// Gets the quaternion that will rotate the from into vector to, around their plan perpendicular axis. The input vectors don't need to be normalized.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>

View File

@@ -0,0 +1,36 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#pragma once
#include "Core.h"
template<typename FuncType>
struct ScopeExit
{
explicit ScopeExit(FuncType&& func)
: _func((FuncType&&)func)
{
}
~ScopeExit()
{
_func();
}
private:
FuncType _func;
};
namespace THelpers
{
struct ScopeExitInternal
{
template<typename FuncType>
ScopeExit<FuncType> operator*(FuncType&& func)
{
return ScopeExit<FuncType>((FuncType&&)func);
}
};
}
#define SCOPE_EXIT const auto CONCAT_MACROS(__scopeExit, __LINE__) = THelpers::ScopeExitInternal() * [&]()

View File

@@ -99,6 +99,7 @@ struct DebugGeometryBuffer
{
GPUBuffer* Buffer;
float TimeLeft;
bool Lines;
Matrix Transform;
};
@@ -234,6 +235,14 @@ void TeleportList(const Float3& delta, Array<DebugText3D>& list)
}
}
void TeleportList(const Float3& delta, Array<DebugGeometryBuffer>& list)
{
for (auto& v : list)
{
v.Transform.SetTranslation(v.Transform.GetTranslation() + delta);
}
}
struct DebugDrawData
{
Array<DebugGeometryBuffer> GeometryBuffers;
@@ -302,6 +311,7 @@ struct DebugDrawData
void Teleport(const Float3& delta)
{
TeleportList(delta, GeometryBuffers);
TeleportList(delta, DefaultLines);
TeleportList(delta, OneFrameLines);
TeleportList(delta, DefaultTriangles);
@@ -810,6 +820,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
defaultWireTriangles = WriteLists(vertexCounter, Context->DebugDrawDefault.DefaultWireTriangles, Context->DebugDrawDefault.OneFrameWireTriangles);
{
PROFILE_CPU_NAMED("Flush");
ZoneValue(DebugDrawVB->Data.Count() / 1024); // Size in kB
DebugDrawVB->Flush(context);
}
}
@@ -869,8 +880,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
Matrix mvp;
Matrix::Multiply(geometry.Transform, vp, mvp);
Matrix::Transpose(mvp, tmp.ViewProjection);
auto state = data.EnableDepthTest ? (geometry.Lines ? &DebugDrawPsLinesDepthTest : &DebugDrawPsTrianglesDepthTest) : (geometry.Lines ? &DebugDrawPsLinesDefault : &DebugDrawPsTrianglesDefault);
context->UpdateCB(cb, &tmp);
auto state = data.EnableDepthTest ? &DebugDrawPsLinesDepthTest : &DebugDrawPsLinesDefault;
context->SetState(state->Get(enableDepthWrite, true));
context->BindVB(ToSpan(&geometry.Buffer, 1));
context->Draw(0, geometry.Buffer->GetElementsCount());
@@ -918,8 +929,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
Matrix mvp;
Matrix::Multiply(geometry.Transform, vp, mvp);
Matrix::Transpose(mvp, tmp.ViewProjection);
auto state = geometry.Lines ? &DebugDrawPsLinesDefault : &DebugDrawPsTrianglesDefault;
context->UpdateCB(cb, &tmp);
context->SetState(DebugDrawPsLinesDefault.Get(false, false));
context->SetState(state->Get(false, false));
context->BindVB(ToSpan(&geometry.Buffer, 1));
context->Draw(0, geometry.Buffer->GetElementsCount());
}
@@ -1164,6 +1176,7 @@ void DebugDraw::DrawLines(GPUBuffer* lines, const Matrix& transform, float durat
auto& geometry = debugDrawData.GeometryBuffers.AddOne();
geometry.Buffer = lines;
geometry.TimeLeft = duration;
geometry.Lines = true;
geometry.Transform = transform * Matrix::Translation(-Context->Origin);
}
@@ -1520,6 +1533,23 @@ void DebugDraw::DrawTriangles(const Span<Float3>& vertices, const Matrix& transf
}
}
void DebugDraw::DrawTriangles(GPUBuffer* triangles, const Matrix& transform, float duration, bool depthTest)
{
if (triangles == nullptr || triangles->GetSize() == 0)
return;
if (triangles->GetSize() % (sizeof(Vertex) * 3) != 0)
{
DebugLog::ThrowException("Cannot draw debug lines with incorrect amount of items in array");
return;
}
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
auto& geometry = debugDrawData.GeometryBuffers.AddOne();
geometry.Buffer = triangles;
geometry.TimeLeft = duration;
geometry.Lines = false;
geometry.Transform = transform * Matrix::Translation(-Context->Origin);
}
void DebugDraw::DrawTriangles(const Array<Float3>& vertices, const Color& color, float duration, bool depthTest)
{
DrawTriangles(Span<Float3>(vertices.Get(), vertices.Count()), color, duration, depthTest);

View File

@@ -74,7 +74,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
API_FUNCTION() static bool CanClear(void* context = nullptr);
#endif
// Gets the last view position when rendering the current context. Can be sued for custom culling or LODing when drawing more complex shapes.
// Gets the last view position when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static Vector3 GetViewPos();
/// <summary>
@@ -296,12 +296,21 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// Draws the triangles.
/// </summary>
/// <param name="vertices">The triangle vertices list (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
API_FUNCTION() static void DrawTriangles(const Span<Float3>& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
/// <summary>
/// Draws the triangles using the provided vertex buffer that contains groups of 3 Vertex elements per-triangle.
/// </summary>
/// <param name="triangles">The GPU buffer with vertices for triangles (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
API_FUNCTION() static void DrawTriangles(GPUBuffer* triangles, const Matrix& transform, float duration = 0.0f, bool depthTest = true);
/// <summary>
/// Draws the triangles.
/// </summary>
@@ -315,7 +324,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// Draws the triangles.
/// </summary>
/// <param name="vertices">The triangle vertices list (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
@@ -336,7 +345,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// </summary>
/// <param name="vertices">The triangle vertices list.</param>
/// <param name="indices">The triangle indices list (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
@@ -357,7 +366,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// </summary>
/// <param name="vertices">The triangle vertices list.</param>
/// <param name="indices">The triangle indices list (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
@@ -376,7 +385,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// Draws the triangles.
/// </summary>
/// <param name="vertices">The triangle vertices list (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
@@ -395,7 +404,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// Draws the triangles.
/// </summary>
/// <param name="vertices">The triangle vertices list (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
@@ -416,7 +425,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// </summary>
/// <param name="vertices">The triangle vertices list.</param>
/// <param name="indices">The triangle indices list (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
@@ -437,7 +446,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// </summary>
/// <param name="vertices">The triangle vertices list.</param>
/// <param name="indices">The triangle indices list (must have multiple of 3 elements).</param>
/// <param name="transform">The custom matrix used to transform all line vertices.</param>
/// <param name="transform">The custom matrix used to transform all triangle vertices.</param>
/// <param name="color">The color.</param>
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>

View File

@@ -443,6 +443,15 @@ bool Engine::IsEditor()
#endif
}
bool Engine::IsPlayMode()
{
#if USE_EDITOR
return Editor::IsPlayMode;
#else
return true;
#endif
}
int32 Engine::GetFramesPerSecond()
{
return EngineImpl::Fps;

View File

@@ -178,6 +178,11 @@ public:
/// </summary>
API_PROPERTY() static bool IsEditor();
/// <summary>
/// Returns whether the editor is in play mode or will always return true in a shipped applications.
/// </summary>
API_PROPERTY() static bool IsPlayMode();
/// <summary>
/// Gets the amount of frames rendered during last second known as Frames Per Second. User scripts updates or fixed updates for physics may run at a different frequency than scene rendering. Use this property to get an accurate amount of frames rendered during the last second.
/// </summary>

View File

@@ -37,6 +37,21 @@ DEFINE_ENGINE_SERVICE_EVENT(LateFixedUpdate);
DEFINE_ENGINE_SERVICE_EVENT(Draw);
DEFINE_ENGINE_SERVICE_EVENT_INVERTED(BeforeExit);
#if TRACY_ENABLE
StringView FillEventNameBuffer(Char* buffer, StringView name, StringView postfix)
{
int32 size = 0;
for (int32 j = 0; j < name.Length(); j++)
if (name[j] != ' ')
buffer[size++] = name[j];
Platform::MemoryCopy(buffer + size, postfix.Get(), (postfix.Length() + 1) * sizeof(Char));
size += postfix.Length();
return StringView(buffer, size);
}
#endif
EngineService::EngineServicesArray& EngineService::GetServices()
{
static EngineServicesArray Services;
@@ -78,14 +93,9 @@ void EngineService::OnInit()
const StringView name(service->Name);
#if TRACY_ENABLE
ZoneScoped;
int32 nameBufferLength = 0;
Char nameBuffer[100];
for (int32 j = 0; j < name.Length(); j++)
if (name[j] != ' ')
nameBuffer[nameBufferLength++] = name[j];
Platform::MemoryCopy(nameBuffer + nameBufferLength, TEXT("::Init"), 7 * sizeof(Char));
nameBufferLength += 7;
ZoneName(nameBuffer, nameBufferLength);
StringView zoneName = FillEventNameBuffer(nameBuffer, name, StringView(TEXT("::Init"), 6));
ZoneName(zoneName.Get(), zoneName.Length());
#endif
LOG(Info, "Initialize {0}...", name);
service->IsInitialized = true;
@@ -114,15 +124,10 @@ void EngineService::OnDispose()
{
#if TRACY_ENABLE
ZoneScoped;
const StringView name(service->Name);
int32 nameBufferLength = 0;
Char nameBuffer[100];
for (int32 j = 0; j < name.Length(); j++)
if (name[j] != ' ')
nameBuffer[nameBufferLength++] = name[j];
Platform::MemoryCopy(nameBuffer + nameBufferLength, TEXT("::Dispose"), 10 * sizeof(Char));
nameBufferLength += 10;
ZoneName(nameBuffer, nameBufferLength);
const StringView name(service->Name);
StringView zoneName = FillEventNameBuffer(nameBuffer, name, StringView(TEXT("::Dispose"), 9));
ZoneName(zoneName.Get(), zoneName.Length());
#endif
service->IsInitialized = false;
service->Dispose();

View File

@@ -67,6 +67,14 @@ public:
return _type;
}
/// <summary>
/// Gets work synchronization start point
/// </summary>
FORCE_INLINE GPUSyncPoint GetSyncStart() const
{
return _syncPoint;
}
/// <summary>
/// Gets work finish synchronization point
/// </summary>

View File

@@ -36,7 +36,7 @@ GPUTasksContext::~GPUTasksContext()
if (task->GetSyncPoint() <= _currentSyncPoint && task->GetState() != TaskState::Finished)
{
if (!Engine::IsRequestingExit)
LOG(Warning, "{0} has been canceled before a sync", task->ToString());
LOG(Warning, "'{0}' has been canceled before a sync", task->ToString());
task->CancelSync();
}
}
@@ -51,18 +51,15 @@ void GPUTasksContext::Run(GPUTask* task)
ASSERT(task != nullptr);
task->Execute(this);
if (task->IsSyncing())
//if (task->GetSyncStart() != 0)
_tasksSyncing.Add(task);
}
void GPUTasksContext::OnCancelSync(GPUTask* task)
{
ASSERT(task != nullptr);
_tasksSyncing.Remove(task);
if (!Engine::IsRequestingExit)
LOG(Warning, "{0} has been canceled before a sync", task->ToString());
LOG(Warning, "'{0}' has been canceled before a sync", task->ToString());
}
void GPUTasksContext::OnFrameBegin()

View File

@@ -18,7 +18,7 @@ protected:
CriticalSection _locker;
GPUSyncPoint _currentSyncPoint;
int32 _totalTasksDoneCount = 0;
Array<GPUTask*, InlinedAllocation<64>> _tasksSyncing;
Array<GPUTask*> _tasksSyncing;
public:
/// <summary>

View File

@@ -54,9 +54,9 @@ void GPUTask::OnCancel()
if (IsSyncing())
{
// Task has been performed but is waiting for a CPU/GPU sync so we have to cancel that
ASSERT(_context != nullptr);
_context->OnCancelSync(this);
_context = nullptr;
SetState(TaskState::Canceled);
}
// Base

View File

@@ -475,6 +475,12 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
_collisionProxy.Init<uint32>(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData);
#endif
// Free old buffers
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]);
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]);
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[2]);
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
// Initialize
_vertexBuffers[0] = vertexBuffer0;
_vertexBuffers[1] = vertexBuffer1;

View File

@@ -322,15 +322,17 @@ class StreamTextureMipTask : public GPUUploadTextureMipTask
{
private:
StreamingTexture* _streamingTexture;
Task* _rootTask;
FlaxStorage::LockData _dataLock;
public:
StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex)
StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex, Task* rootTask)
: GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span<byte>(nullptr, 0), 0, 0, false)
, _streamingTexture(texture)
, _rootTask(rootTask ? rootTask : this)
, _dataLock(_streamingTexture->GetOwner()->LockData())
{
_streamingTexture->_streamingTasks.Add(this);
_streamingTexture->_streamingTasks.Add(_rootTask);
_texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this);
}
@@ -341,7 +343,7 @@ private:
if (_streamingTexture)
{
ScopeLock lock(_streamingTexture->GetOwner()->GetOwnerLocker());
_streamingTexture->_streamingTasks.Remove(this);
_streamingTexture->_streamingTasks.Remove(_rootTask);
_streamingTexture = nullptr;
}
}
@@ -393,7 +395,7 @@ protected:
if (_streamingTexture)
{
ScopeLock lock(_streamingTexture->GetOwner()->GetOwnerLocker());
_streamingTexture->_streamingTasks.Remove(this);
_streamingTexture->_streamingTasks.Remove(_rootTask);
_streamingTexture = nullptr;
}
@@ -443,7 +445,7 @@ Task* StreamingTexture::CreateStreamingTask(int32 residency)
// Add upload data task
const int32 allocatedMipIndex = TotalIndexToTextureMipIndex(mipIndex);
task = New<StreamTextureMipTask>(this, allocatedMipIndex);
task = New<StreamTextureMipTask>(this, allocatedMipIndex, result);
if (result)
result->ContinueWith(task);
else

View File

@@ -766,7 +766,7 @@ void AnimatedModel::UpdateBounds()
// Apply margin based on model dimensions
const Vector3 modelBoxSize = modelBox.GetSize();
const Vector3 center = box.GetCenter();
const Vector3 sizeHalf = Vector3::Max(box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
const Vector3 sizeHalf = Vector3::Max(box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * (0.5f * BoundsScale);
_box = BoundingBox(center - sizeHalf, center + sizeHalf);
}
else

View File

@@ -96,7 +96,7 @@ public:
bool PerBoneMotionBlur = true;
/// <summary>
/// If true, animation speed will be affected by the global time scale parameter.
/// If true, animation speed will be affected by the global timescale parameter.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
bool UseTimeScale = true;
@@ -108,7 +108,7 @@ public:
bool UpdateWhenOffscreen = false;
/// <summary>
/// The animation update delta time scale. Can be used to speed up animation playback or create slow motion effect.
/// The animation update delta timescale. Can be used to speed up animation playback or create slow motion effect.
/// </summary>
API_FIELD(Attributes="EditorOrder(45), EditorDisplay(\"Skinned Model\")")
float UpdateSpeed = 1.0f;
@@ -120,7 +120,7 @@ public:
AnimationUpdateMode UpdateMode = AnimationUpdateMode::Auto;
/// <summary>
/// The master scale parameter for the actor bounding box. Helps reducing mesh flickering effect on screen edges.
/// The master scale parameter for the actor bounding box. Helps to reduce mesh flickering effect on screen edges.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0), EditorDisplay(\"Skinned Model\")")
float BoundsScale = 1.5f;
@@ -388,7 +388,7 @@ public:
API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim);
/// <summary>
/// Checks if the any animation playback is active on the any slot in Anim Graph (not paused).
/// Checks if any animation playback is active on any slot in Anim Graph (not paused).
/// </summary>
API_FUNCTION() bool IsPlayingSlotAnimation();

View File

@@ -3,6 +3,7 @@
#include "Camera.h"
#include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Core/Math/Matrix.h"
#include "Engine/Core/Math/Double4x4.h"
#include "Engine/Core/Math/Viewport.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Content.h"
@@ -238,7 +239,7 @@ Ray Camera::ConvertMouseToRay(const Float2& mousePosition, const Viewport& viewp
viewport.Unproject(farPoint, ivp, farPoint);
Vector3 dir = Vector3::Normalize(farPoint - nearPoint);
if (dir.IsZero())
if (dir.IsZero() || dir.IsNanOrInfinity())
return Ray::Identity;
return Ray(nearPoint, dir);
}
@@ -302,12 +303,15 @@ void Camera::GetMatrices(Matrix& view, Matrix& projection, const Viewport& viewp
}
// Create view matrix
const Float3 direction = GetDirection();
const Float3 position = _transform.Translation - origin;
const Float3 target = position + direction;
Float3 up;
Float3::Transform(Float3::Up, GetOrientation(), up);
Matrix::LookAt(position, target, up, view);
const Vector3 direction = Vector3::Transform(Vector3::Forward, GetOrientation());
const Vector3 position = _transform.Translation - origin;
const Vector3 target = position + direction;
Vector3 up;
Vector3::Transform(Vector3::Up, GetOrientation(), up);
Real4x4 viewResult;
Real4x4::LookAt(position, target, up, viewResult);
view = Matrix(viewResult);
}
#if USE_EDITOR

View File

@@ -0,0 +1,22 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine.Json;
namespace FlaxEngine
{
partial class ModelInstanceActor
{
partial struct MeshReference : ICustomValueEquals
{
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (MeshReference)other;
return JsonSerializer.ValueEquals(Actor, o.Actor) &&
LODIndex == o.LODIndex &&
MeshIndex == o.MeshIndex;
}
}
}
}

View File

@@ -1090,7 +1090,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
root = dynamic_cast<Actor*>(sceneObjects.Value->At(targetActorIdx));
}
// Try using the first actor without a parent as a new ro0t
// Try using the first actor without a parent as a new root
for (int32 i = 1; i < sceneObjects->Count(); i++)
{
SceneObject* obj = sceneObjects.Value->At(i);

View File

@@ -223,7 +223,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa
#endif
}
#endif
ASSERT(!isnan(sphere.Radius) && !isinf(sphere.Radius) && !sphere.Center.IsNanOrInfinity());
CHECK_RETURN(!isnan(sphere.Radius) && !isinf(sphere.Radius) && !sphere.Center.IsNanOrInfinity(), false);
// Expand sphere based on the render modules rules (sprite or mesh size)
for (int32 moduleIndex = 0; moduleIndex < emitter->Graph.RenderModules.Count(); moduleIndex++)
@@ -244,7 +244,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa
Vector2::Max(*((Vector2*)spriteSize), maxSpriteSize, maxSpriteSize);
spriteSize += stride;
}
ASSERT(!maxSpriteSize.IsNanOrInfinity());
CHECK_RETURN(!maxSpriteSize.IsNanOrInfinity(), false);
// Enlarge the emitter bounds sphere
sphere.Radius += maxSpriteSize.MaxValue();
@@ -267,7 +267,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa
if (radius > maxRadius)
maxRadius = radius;
}
ASSERT(!isnan(maxRadius) && !isinf(maxRadius));
CHECK_RETURN(!isnan(maxRadius) && !isinf(maxRadius), false);
// Enlarge the emitter bounds sphere
sphere.Radius += maxRadius;
@@ -315,7 +315,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa
maxRibbonWidth = Math::Max(*((float*)ribbonWidth), maxRibbonWidth);
ribbonWidth += stride;
}
ASSERT(!isnan(maxRibbonWidth) && !isinf(maxRibbonWidth));
CHECK_RETURN(!isnan(maxRibbonWidth) && !isinf(maxRibbonWidth), false);
// Enlarge the emitter bounds sphere
sphere.Radius += maxRibbonWidth * 0.5f;
@@ -335,7 +335,7 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa
maxRadius = Math::Max(*((float*)radius), maxRadius);
radius += stride;
}
ASSERT(!isnan(maxRadius) && !isinf(maxRadius));
CHECK_RETURN(!isnan(maxRadius) && !isinf(maxRadius), false);
}
else
{

View File

@@ -335,6 +335,7 @@ void Cloth::DrawPhysicsDebug(RenderView& view)
#if WITH_CLOTH && COMPILE_WITH_DEBUG_DRAW
if (_cloth)
{
PROFILE_CPU();
const ModelInstanceActor::MeshReference mesh = GetMesh();
if (mesh.Actor == nullptr)
return;

View File

@@ -59,7 +59,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class
public:
/// <summary>
/// The default gravity force value (in cm^2/s).
/// The default gravity value (in cm/(s^2)).
/// </summary>
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Simulation\")")
Vector3 DefaultGravity = Vector3(0, -981.0f, 0);

View File

@@ -119,7 +119,7 @@ void Font::Invalidate()
_characters.Clear();
}
void Font::ProcessText(const StringView& text, Array<FontLineCache>& outputLines, const TextLayoutOptions& layout)
void Font::ProcessText(const StringView& text, Array<FontLineCache, InlinedAllocation<8>>& outputLines, const TextLayoutOptions& layout)
{
int32 textLength = text.Length();
if (textLength == 0)
@@ -311,7 +311,7 @@ Float2 Font::MeasureText(const StringView& text, const TextLayoutOptions& layout
return Float2::Zero;
// Process text
Array<FontLineCache> lines;
Array<FontLineCache, InlinedAllocation<8>> lines;
ProcessText(text, lines, layout);
// Calculate bounds
@@ -332,7 +332,7 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te
return 0;
// Process text
Array<FontLineCache> lines;
Array<FontLineCache, InlinedAllocation<8>> lines;
ProcessText(text, lines, layout);
ASSERT(lines.HasItems());
float scale = layout.Scale / FontManager::FontScale;
@@ -417,7 +417,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo
}
// Process text
Array<FontLineCache> lines;
Array<FontLineCache, InlinedAllocation<8>> lines;
ProcessText(text, lines, layout);
ASSERT(lines.HasItems());
float scale = layout.Scale / FontManager::FontScale;

View File

@@ -344,7 +344,7 @@ public:
/// <param name="text">The input text.</param>
/// <param name="layout">The layout properties.</param>
/// <param name="outputLines">The output lines list.</param>
void ProcessText(const StringView& text, Array<FontLineCache>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout);
void ProcessText(const StringView& text, Array<FontLineCache, InlinedAllocation<8>>& outputLines, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
/// Processes text to get cached lines for rendering.
@@ -352,9 +352,9 @@ public:
/// <param name="text">The input text.</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() Array<FontLineCache> ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout)
API_FUNCTION() Array<FontLineCache, InlinedAllocation<8>> ProcessText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout)
{
Array<FontLineCache> lines;
Array<FontLineCache, InlinedAllocation<8>> lines;
ProcessText(text, lines, layout);
return lines;
}
@@ -366,9 +366,9 @@ public:
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() Array<FontLineCache> ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
API_FUNCTION() Array<FontLineCache, InlinedAllocation<8>> ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
{
Array<FontLineCache> lines;
Array<FontLineCache, InlinedAllocation<8>> lines;
ProcessText(textRange.Substring(text), lines, layout);
return lines;
}
@@ -378,7 +378,7 @@ public:
/// </summary>
/// <param name="text">The input text.</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() FORCE_INLINE Array<FontLineCache> ProcessText(const StringView& text)
API_FUNCTION() FORCE_INLINE Array<FontLineCache, InlinedAllocation<8>> ProcessText(const StringView& text)
{
return ProcessText(text, TextLayoutOptions());
}
@@ -389,7 +389,7 @@ public:
/// <param name="text">The input text.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <returns>The output lines list.</returns>
API_FUNCTION() FORCE_INLINE Array<FontLineCache> ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange)
API_FUNCTION() FORCE_INLINE Array<FontLineCache, InlinedAllocation<8>> ProcessText(const StringView& text, API_PARAM(Ref) const TextRange& textRange)
{
return ProcessText(textRange.Substring(text), TextLayoutOptions());
}

View File

@@ -194,7 +194,7 @@ namespace
// Drawing
Array<Render2DDrawCall> DrawCalls;
Array<FontLineCache> Lines;
Array<FontLineCache, InlinedAllocation<8>> Lines;
Array<Float2> Lines2;
bool IsScissorsRectEmpty;
bool IsScissorsRectEnabled;

View File

@@ -72,7 +72,7 @@ void ForwardPass::Dispose()
_shader = nullptr;
}
void ForwardPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output)
void ForwardPass::Render(RenderContext& renderContext, GPUTexture*& input, GPUTexture*& output)
{
PROFILE_GPU_CPU("Forward");
auto context = GPUDevice::Instance->GetMainContext();
@@ -91,6 +91,16 @@ void ForwardPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTex
// Check if there is no objects to render or no resources ready
auto& forwardList = mainCache->DrawCallsLists[(int32)DrawCallsListType::Forward];
auto& distortionList = mainCache->DrawCallsLists[(int32)DrawCallsListType::Distortion];
if ((forwardList.IsEmpty() && distortionList.IsEmpty())
#if USE_EDITOR
|| renderContext.View.Mode == ViewMode::PhysicsColliders
#endif
)
{
// Skip rendering
Swap(input, output);
return;
}
if (distortionList.IsEmpty() || checkIfSkipPass())
{
// Copy frame

View File

@@ -31,7 +31,7 @@ public:
/// <param name="renderContext">The rendering context.</param>
/// <param name="input">Target with renderer frame ready for further processing.</param>
/// <param name="output">The output frame.</param>
void Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output);
void Render(RenderContext& renderContext, GPUTexture*& input, GPUTexture*& output);
private:

View File

@@ -276,11 +276,10 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input,
int32 h2 = h1 >> 1;
int32 h4 = h2 >> 1;
int32 h8 = h4 >> 1;
int32 bloomMipCount = CalculateBloomMipCount(w1, h1);
// Ensure to have valid data and if at least one effect should be applied
if (!(useBloom || useToneMapping || useCameraArtifacts) || checkIfSkipPass() || w8 == 0 || h8 == 0)
if (!(useBloom || useToneMapping || useCameraArtifacts) || checkIfSkipPass() || w8 <= 1 || h8 <= 1)
{
// Resources are missing. Do not perform rendering. Just copy raw frame
context->SetViewportAndScissors((float)output->Width(), (float)output->Height());

View File

@@ -237,6 +237,12 @@ void Renderer::Render(SceneRenderTask* task)
| ViewFlags::ContactShadows
| ViewFlags::DepthOfField);
}
// Force Debug Draw usage in some specific views that depend on it
if (renderContext.View.Mode == ViewMode::PhysicsColliders)
{
renderContext.View.Flags |= ViewFlags::DebugDraw;
}
#endif
// Perform the actual rendering

View File

@@ -27,6 +27,11 @@ namespace FlaxEngine.Json
}
}
internal interface ICustomValueEquals
{
bool ValueEquals(object other);
}
partial class JsonSerializer
{
internal class SerializerCache
@@ -262,7 +267,7 @@ namespace FlaxEngine.Json
return true;
if (objA == null || objB == null)
return false;
// Special case when saving reference to prefab object and the objects are different but the point to the same prefab object
// In that case, skip saving reference as it's defined in prefab (will be populated via IdsMapping during deserialization)
if (objA is SceneObject sceneA && objB is SceneObject sceneB && sceneA && sceneB && sceneA.HasPrefabLink && sceneB.HasPrefabLink)
@@ -311,6 +316,8 @@ namespace FlaxEngine.Json
return !bEnumerator.MoveNext();
}
if (objA is ICustomValueEquals customValueEquals && objA.GetType() == objB.GetType())
return customValueEquals.ValueEquals(objB);
return objA.Equals(objB);
#endif
}

View File

@@ -28,7 +28,7 @@ void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, c
else if (obj.IsString() && obj.GetStringLength() == 32)
{
auto value = JsonTools::GetGuid(obj);
if (mapping.TryGet(value, value))
if (value.IsValid() && mapping.TryGet(value, value))
{
// Unoptimized version:
//obj.SetString(value.ToString(Guid::FormatType::N).ToSTD().c_str(), 32, document.GetAllocator());
@@ -255,9 +255,8 @@ BoundingBox JsonTools::GetBoundingBox(const Value& value)
Guid JsonTools::GetGuid(const Value& value)
{
if (!value.IsString())
if (!value.IsString() || value.GetStringLength() != 32)
return Guid::Empty;
CHECK_RETURN(value.GetStringLength() == 32, Guid::Empty);
// Split
const char* a = value.GetString();
@@ -267,10 +266,12 @@ Guid JsonTools::GetGuid(const Value& value)
// Parse
Guid result;
StringUtils::ParseHex(a, 8, &result.A);
StringUtils::ParseHex(b, 8, &result.B);
StringUtils::ParseHex(c, 8, &result.C);
StringUtils::ParseHex(d, 8, &result.D);
bool failed = StringUtils::ParseHex(a, 8, &result.A);
failed |= StringUtils::ParseHex(b, 8, &result.B);
failed |= StringUtils::ParseHex(c, 8, &result.C);
failed |= StringUtils::ParseHex(d, 8, &result.D);
if (failed)
return Guid::Empty;
return result;
}

View File

@@ -214,7 +214,7 @@ public:
const auto member = node.FindMember(name);
if (member != node.MemberEnd() && member->value.IsInt())
{
result = member->value.GetInt();
result = (byte)member->value.GetInt();
}
}
@@ -232,7 +232,7 @@ public:
const auto member = node.FindMember(name);
if (member != node.MemberEnd() && member->value.IsInt())
{
result = member->value.GetInt();
result = (uint32)member->value.GetInt();
}
}
@@ -241,7 +241,7 @@ public:
const auto member = node.FindMember(name);
if (member != node.MemberEnd() && member->value.IsInt())
{
result = member->value.GetInt();
result = (int16)member->value.GetInt();
}
}
@@ -250,7 +250,7 @@ public:
const auto member = node.FindMember(name);
if (member != node.MemberEnd() && member->value.IsInt())
{
result = member->value.GetInt();
result = (uint16)member->value.GetInt();
}
}

View File

@@ -240,6 +240,7 @@ void Terrain::DrawChunk(const RenderContext& renderContext, const Int2& patchCoo
void Terrain::DrawPhysicsDebug(RenderView& view)
{
PROFILE_CPU();
for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++)
{
_patches[pathIndex]->DrawPhysicsDebug(view);

View File

@@ -104,6 +104,8 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
#endif
#if USE_EDITOR
_collisionTriangles.Resize(0);
SAFE_DELETE_GPU_RESOURCE(_collisionTrianglesBuffer);
_collisionTrianglesBufferDirty = true;
#endif
_collisionVertices.Resize(0);
}
@@ -120,6 +122,9 @@ TerrainPatch::~TerrainPatch()
#if TERRAIN_USE_PHYSICS_DEBUG
SAFE_DELETE_GPU_RESOURCE(_debugLines);
#endif
#if USE_EDITOR
SAFE_DELETE_GPU_RESOURCE(_collisionTrianglesBuffer);
#endif
}
RawDataAsset* TerrainPatch::GetHeightfield() const
@@ -2225,6 +2230,8 @@ void TerrainPatch::DestroyCollision()
#endif
#if USE_EDITOR
_collisionTriangles.Resize(0);
SAFE_DELETE(_collisionTrianglesBuffer);
_collisionTrianglesBufferDirty = true;
#endif
_collisionVertices.Resize(0);
}
@@ -2317,7 +2324,32 @@ void TerrainPatch::DrawPhysicsDebug(RenderView& view)
return;
if (view.Mode == ViewMode::PhysicsColliders)
{
DEBUG_DRAW_TRIANGLES(GetCollisionTriangles(), Color::DarkOliveGreen, 0, true);
const auto& triangles = GetCollisionTriangles();
typedef DebugDraw::Vertex Vertex;
if (!_collisionTrianglesBuffer)
_collisionTrianglesBuffer = GPUDevice::Instance->CreateBuffer(TEXT("Terrain.CollisionTriangles"));
const uint32 count = triangles.Count();
if (_collisionTrianglesBuffer->GetElementsCount() != count)
{
if (_collisionTrianglesBuffer->Init(GPUBufferDescription::Vertex(Vertex::GetLayout(), sizeof(Vertex), count)))
return;
_collisionTrianglesBufferDirty = true;
}
if (_collisionTrianglesBufferDirty)
{
const Color32 color(Color::DarkOliveGreen);
Array<Vertex> vertices;
vertices.Resize((int32)count);
const Vector3* src = triangles.Get();
Vertex* dst = vertices.Get();
for (uint32 i = 0; i < count; i++)
{
dst[i] = { (Float3)src[i], color };
}
_collisionTrianglesBuffer->SetData(vertices.Get(), _collisionTrianglesBuffer->GetSize());
_collisionTrianglesBufferDirty = false;
}
DebugDraw::DrawTriangles(_collisionTrianglesBuffer, Matrix::Identity, 0, true);
}
else
{
@@ -2351,6 +2383,7 @@ const Array<Vector3>& TerrainPatch::GetCollisionTriangles()
PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols);
_collisionTriangles.Resize((rows - 1) * (cols - 1) * 6);
_collisionTrianglesBufferDirty = true;
Vector3* data = _collisionTriangles.Get();
#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y)

View File

@@ -49,6 +49,8 @@ private:
#endif
#if USE_EDITOR
Array<Vector3> _collisionTriangles; // TODO: large-worlds
class GPUBuffer* _collisionTrianglesBuffer = nullptr;
bool _collisionTrianglesBufferDirty = true;
#endif
Array<Float3> _collisionVertices; // TODO: large-worlds

View File

@@ -44,9 +44,9 @@ void TestsRunnerService::Update()
LOG(Info, "Running Flax Tests...");
const int result = Catch::Session().run();
if (result == 0)
LOG(Info, "Result: {0}", result);
LOG(Info, "Flax Tests result: {0}", result);
else
LOG(Error, "Result: {0}", result);
LOG(Error, "Flax Tests result: {0}", result);
Log::Logger::WriteFloor();
Engine::RequestExit(result);
}

View File

@@ -3,6 +3,7 @@
#include "Engine/Content/Content.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/ScopeExit.h"
#include "Engine/Level/Actor.h"
#include "Engine/Level/Actors/EmptyActor.h"
#include "Engine/Level/Actors/DirectionalLight.h"
@@ -27,6 +28,7 @@ TEST_CASE("Prefabs")
// Create Prefab B with two children attached to the root
AssetReference<Prefab> prefabB = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabB);
SCOPE_EXIT{ Content::DeleteAsset(prefabB); };
Guid id;
Guid::Parse("665bb01c49a3370f14a023b5395de261", id);
prefabB->ChangeID(id);
@@ -55,6 +57,7 @@ TEST_CASE("Prefabs")
// Create Prefab A with nested Prefab B attached to the root
AssetReference<Prefab> prefabA = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabA);
SCOPE_EXIT{ Content::DeleteAsset(prefabA); };
Guid::Parse("02524a044184af56b6c664a0f98bd761", id);
prefabA->ChangeID(id);
auto prefabAInit = prefabA->Init(Prefab::TypeName,
@@ -123,8 +126,6 @@ TEST_CASE("Prefabs")
// Cleanup
instanceA->DeleteObject();
instanceB->DeleteObject();
Content::DeleteAsset(prefabA);
Content::DeleteAsset(prefabB);
}
SECTION("Test Adding Object in Nested Prefab")
{
@@ -133,6 +134,7 @@ TEST_CASE("Prefabs")
// Create Prefab B with just root object
AssetReference<Prefab> prefabB = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabB);
SCOPE_EXIT{ Content::DeleteAsset(prefabB); };
Guid id;
Guid::Parse("25dbe4b0416be0777a6ce59e8788b10f", id);
prefabB->ChangeID(id);
@@ -149,6 +151,7 @@ TEST_CASE("Prefabs")
// Create Prefab A with two nested Prefab B attached to the root
AssetReference<Prefab> prefabA = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabA);
SCOPE_EXIT{ Content::DeleteAsset(prefabA); };
Guid::Parse("4cb744714f746e31855f41815612d14b", id);
prefabA->ChangeID(id);
auto prefabAInit = prefabA->Init(Prefab::TypeName,
@@ -243,8 +246,6 @@ TEST_CASE("Prefabs")
// Cleanup
instanceA->DeleteObject();
instanceB->DeleteObject();
Content::DeleteAsset(prefabA);
Content::DeleteAsset(prefabB);
}
SECTION("Test Syncing Changes In Nested Prefab Instance")
{
@@ -253,6 +254,7 @@ TEST_CASE("Prefabs")
// Create TestActor prefab with just root object
AssetReference<Prefab> testActorPrefab = Content::CreateVirtualAsset<Prefab>();
REQUIRE(testActorPrefab);
SCOPE_EXIT{ Content::DeleteAsset(testActorPrefab); };
Guid id;
Guid::Parse("7691e981482f2a486e10cfae149e07d3", id);
testActorPrefab->ChangeID(id);
@@ -269,6 +271,7 @@ TEST_CASE("Prefabs")
// Create NestedActor prefab that inherits from TestActor prefab
AssetReference<Prefab> nestedActorPrefab = Content::CreateVirtualAsset<Prefab>();
REQUIRE(nestedActorPrefab);
SCOPE_EXIT{ Content::DeleteAsset(nestedActorPrefab); };
Guid::Parse("1d521df4465ad849e274748c6d14b703", id);
nestedActorPrefab->ChangeID(id);
auto nestedActorPrefabInit = nestedActorPrefab->Init(Prefab::TypeName,
@@ -328,8 +331,6 @@ TEST_CASE("Prefabs")
// Cleanup
nestedActor->DeleteObject();
testActor->DeleteObject();
Content::DeleteAsset(nestedActorPrefab);
Content::DeleteAsset(testActorPrefab);
}
SECTION("Test Loading Nested Prefab After Changing Root")
{
@@ -338,6 +339,7 @@ TEST_CASE("Prefabs")
// Create base prefab with 3 objects
AssetReference<Prefab> prefabBase = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabBase);
SCOPE_EXIT{ Content::DeleteAsset(prefabBase); };
Guid id;
Guid::Parse("2b3334524c696dcfa93cabacd2a4f404", id);
prefabBase->ChangeID(id);
@@ -366,6 +368,7 @@ TEST_CASE("Prefabs")
// Create nested prefab but with 'old' state where root object is different
AssetReference<Prefab> prefabNested = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabNested);
SCOPE_EXIT{ Content::DeleteAsset(prefabNested); };
Guid::Parse("a71447e947cbd2deea018a8377636ce6", id);
prefabNested->ChangeID(id);
auto prefabNestedInit = prefabNested->Init(Prefab::TypeName,
@@ -411,8 +414,6 @@ TEST_CASE("Prefabs")
// Cleanup
instanceNested->DeleteObject();
instanceBase->DeleteObject();
Content::DeleteAsset(prefabNested);
Content::DeleteAsset(prefabBase);
}
SECTION("Test Loading Nested Prefab After Changing and Deleting Root")
{
@@ -421,6 +422,7 @@ TEST_CASE("Prefabs")
// Create base prefab with 1 object
AssetReference<Prefab> prefabBase = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabBase);
SCOPE_EXIT{ Content::DeleteAsset(prefabBase); };
Guid id;
Guid::Parse("3b3334524c696dcfa93cabacd2a4f404", id);
prefabBase->ChangeID(id);
@@ -455,6 +457,7 @@ TEST_CASE("Prefabs")
// Create nested prefab but with 'old' state where root object is different
AssetReference<Prefab> prefabNested1 = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabNested1);
SCOPE_EXIT{ Content::DeleteAsset(prefabNested1); };
Guid::Parse("671447e947cbd2deea018a8377636ce6", id);
prefabNested1->ChangeID(id);
auto prefabNestedInit1 = prefabNested1->Init(Prefab::TypeName,
@@ -491,6 +494,7 @@ TEST_CASE("Prefabs")
// Create nested prefab but with 'old' state where root object is different and doesn't exist anymore
AssetReference<Prefab> prefabNested2 = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabNested2);
SCOPE_EXIT{ Content::DeleteAsset(prefabNested2); };
Guid::Parse("b71447e947cbd2deea018a8377636ce6", id);
prefabNested2->ChangeID(id);
auto prefabNestedInit2 = prefabNested2->Init(Prefab::TypeName,
@@ -555,9 +559,6 @@ TEST_CASE("Prefabs")
instanceNested2->DeleteObject();
instanceNested1->DeleteObject();
instanceBase->DeleteObject();
Content::DeleteAsset(prefabNested2);
Content::DeleteAsset(prefabNested1);
Content::DeleteAsset(prefabBase);
}
SECTION("Test Applying Prefab Change To Object References")
{
@@ -566,6 +567,7 @@ TEST_CASE("Prefabs")
// Create Prefab
AssetReference<Prefab> prefab = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefab);
SCOPE_EXIT{ Content::DeleteAsset(prefab); };
Guid id;
Guid::Parse("690e55514cd6fdc2a269429a2bf84133", id);
prefab->ChangeID(id);
@@ -612,7 +614,6 @@ TEST_CASE("Prefabs")
// Cleanup
instanceA->DeleteObject();
instanceB->DeleteObject();
Content::DeleteAsset(prefab);
}
SECTION("Test Applying Prefab With Missing Nested Prefab")
{
@@ -637,6 +638,7 @@ TEST_CASE("Prefabs")
// Create Prefab A with nested Prefab B attached to the root
AssetReference<Prefab> prefabA = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabA);
SCOPE_EXIT{ Content::DeleteAsset(prefabA); };
Guid::Parse("4cb744714f746e31855f41815612d14b", id);
prefabA->ChangeID(id);
auto prefabAInit = prefabA->Init(Prefab::TypeName,
@@ -685,7 +687,6 @@ TEST_CASE("Prefabs")
instanceA->DeleteObject();
instanceB->DeleteObject();
instanceC->DeleteObject();
Content::DeleteAsset(prefabA);
}
}

View File

@@ -40,7 +40,7 @@ void Task::Cancel()
bool Task::Wait(double timeoutMilliseconds) const
{
PROFILE_CPU();
double startTime = Platform::GetTimeSeconds() * 0.001;
const double startTime = Platform::GetTimeSeconds();
// TODO: no active waiting! use a semaphore!
@@ -54,7 +54,7 @@ bool Task::Wait(double timeoutMilliseconds) const
// Wait for child if has
if (_continueWith)
{
auto spendTime = Platform::GetTimeSeconds() * 0.001 - startTime;
const auto spendTime = (Platform::GetTimeSeconds() - startTime) * 1000.0;
return _continueWith->Wait(timeoutMilliseconds - spendTime);
}
@@ -66,7 +66,7 @@ bool Task::Wait(double timeoutMilliseconds) const
return true;
Platform::Sleep(1);
} while (timeoutMilliseconds <= 0.0 || Platform::GetTimeSeconds() * 0.001 - startTime < timeoutMilliseconds);
} while (timeoutMilliseconds <= 0.0 || (Platform::GetTimeSeconds() - startTime) * 1000.0 < timeoutMilliseconds);
// Timeout reached!
LOG(Warning, "\'{0}\' has timed out. Wait time: {1} ms", ToString(), timeoutMilliseconds);
@@ -208,8 +208,8 @@ void Task::OnCancel()
if (IsRunning())
{
// Wait for it a little bit
const double timeout = 2000.0;
LOG(Warning, "Cannot cancel \'{0}\' because it's still running, waiting for end with timeout: {1} ms", ToString(), timeout);
constexpr double timeout = 10000.0; // 10s
LOG(Warning, "Cannot cancel \'{0}\' because it's still running, waiting for end with timeout: {1}ms", ToString(), timeout);
Wait(timeout);
}

View File

@@ -51,6 +51,11 @@ namespace FlaxEngine.GUI
/// </summary>
protected float _cachedHeight = 16.0f;
/// <summary>
/// The items spacing.
/// </summary>
protected float _itemsSpacing = 2.0f;
/// <summary>
/// The items margin.
/// </summary>
@@ -168,9 +173,9 @@ namespace FlaxEngine.GUI
}
/// <summary>
/// Gets or sets the item slots margin (the space between items).
/// Gets or sets the item slots margin (the space around items).
/// </summary>
[EditorOrder(10), Tooltip("The item slots margin (the space between items).")]
[EditorOrder(10)]
public Margin ItemsMargin
{
get => _itemsMargin;
@@ -184,6 +189,23 @@ namespace FlaxEngine.GUI
}
}
/// <summary>
/// Gets or sets the item slots spacing (the margin between items).
/// </summary>
[EditorOrder(11)]
public float ItemsSpacing
{
get => _itemsSpacing;
set
{
if (!Mathf.NearEqual(_itemsSpacing, value))
{
_itemsSpacing = value;
PerformLayout();
}
}
}
/// <summary>
/// Gets or sets the panel close/open animation duration (in seconds).
/// </summary>
@@ -563,25 +585,27 @@ namespace FlaxEngine.GUI
var slotsLeft = clientArea.Left + slotsMargin.Left;
var slotsWidth = clientArea.Width - slotsMargin.Width;
float minHeight = HeaderHeight;
float y = clientArea.Top;
float height = clientArea.Top + dropOffset;
float y = clientArea.Top + slotsMargin.Top;
bool anyAdded = false;
for (int i = 0; i < _children.Count; i++)
{
Control c = _children[i];
if (c.IsScrollable && c.Visible)
{
var h = c.Height;
y += slotsMargin.Top;
c.Bounds = new Rectangle(slotsLeft, y, slotsWidth, h);
h += slotsMargin.Bottom;
h += _itemsSpacing;
y += h;
height += h + slotsMargin.Top;
anyAdded = true;
}
}
// Update panel height
if (anyAdded)
y -= _itemsSpacing;
if (anyAdded)
y += slotsMargin.Bottom;
float height = dropOffset + y;
_cachedHeight = height;
if (_animationProgress >= 1.0f && _isClosed)
y = minHeight;

View File

@@ -20,6 +20,7 @@ namespace FlaxEngine.GUI
private Color _scrollbarTrackColor;
private Color _scrollbarThumbColor;
private Color _scrollbarThumbSelectedColor;
private Rectangle _controlsBoundsBeforeLayout;
/// <summary>
/// The cached scroll area bounds. Used to scroll contents of the panel control. Cached during performing layout.
@@ -530,8 +531,25 @@ namespace FlaxEngine.GUI
{
// Arrange controls and get scroll bounds
ArrangeAndGetBounds();
UpdateScrollBars();
_controlsBoundsBeforeLayout = _controlsBounds;
}
// Update scroll bars
/// <inheritdoc />
protected override void PerformLayoutAfterChildren()
{
// If controls area changed during layout then update scroll bars again
ArrangeAndGetBounds();
if (_controlsBoundsBeforeLayout != _controlsBounds)
{
UpdateScrollBars();
}
base.PerformLayoutAfterChildren();
}
private void UpdateScrollBars()
{
var controlsBounds = _controlsBounds;
var scrollBounds = controlsBounds;
_scrollMargin.ExpandRectangle(ref scrollBounds);
@@ -689,7 +707,7 @@ namespace FlaxEngine.GUI
}
viewOffset.Y = Mathf.Clamp(viewOffset.Y, VScrollBar.Minimum, VScrollBar.Maximum);
VScrollBar.Value = viewOffset.Y;
VScrollBar.TargetValue = viewOffset.Y;
}
if (HScrollBar != null && HScrollBar.Enabled && width > MinSize)
@@ -704,7 +722,7 @@ namespace FlaxEngine.GUI
}
viewOffset.X = Mathf.Clamp(viewOffset.X, HScrollBar.Minimum, HScrollBar.Maximum);
HScrollBar.Value = viewOffset.X;
HScrollBar.TargetValue = viewOffset.X;
}
viewOffset *= -1;

View File

@@ -72,8 +72,11 @@ namespace FlaxEngine.GUI
get => _slotSpacing;
set
{
_slotSpacing = value;
PerformLayout();
if (!Float2.NearEqual(ref _slotSpacing, ref value))
{
_slotSpacing = value;
PerformLayout();
}
}
}
@@ -89,11 +92,11 @@ namespace FlaxEngine.GUI
/// Initializes a new instance of the <see cref="UniformGridPanel"/> class.
/// </summary>
/// <param name="slotPadding">The slot padding.</param>
public UniformGridPanel(float slotPadding = 0)
public UniformGridPanel(float slotPadding)
{
AutoFocus = false;
SlotPadding = new Margin(slotPadding);
SlotSpacing = new Float2(2);
_slotPadding = new Margin(slotPadding);
_slotSpacing = new Float2(2);
_slotsH = _slotsV = 5;
}
@@ -105,25 +108,32 @@ namespace FlaxEngine.GUI
int slotsV = _slotsV;
int slotsH = _slotsH;
Float2 slotSize;
Float2 size = Size;
bool applySpacing = true;
APPLY_SPACING:
if (_slotsV + _slotsH == 0)
{
slotSize = HasChildren ? Children[0].Size : new Float2(32);
slotsH = Mathf.CeilToInt(Width / slotSize.X);
slotsV = Mathf.CeilToInt(Height / slotSize.Y);
slotsH = Mathf.CeilToInt(size.X / slotSize.X);
slotsV = Mathf.CeilToInt(size.Y / slotSize.Y);
}
else if (slotsH == 0)
{
float size = Height / slotsV;
slotSize = new Float2(size);
slotSize = new Float2(size.Y / slotsV);
}
else if (slotsV == 0)
{
float size = Width / slotsH;
slotSize = new Float2(size);
slotSize = new Float2(size.X / slotsH);
}
else
{
slotSize = new Float2(Width / slotsH, Height / slotsV);
slotSize = new Float2(size.X / slotsH, size.Y / slotsV);
}
if (applySpacing && _slotSpacing != Float2.Zero)
{
applySpacing = false;
size -= _slotSpacing * new Float2(slotsH > 1 ? slotsH - 1 : 0, slotsV > 1 ? slotsV - 1 : 0);
goto APPLY_SPACING;
}
int i = 0;
@@ -135,45 +145,9 @@ namespace FlaxEngine.GUI
for (int x = 0; x < end; x++)
{
var slotBounds = new Rectangle(slotSize.X * x, slotSize.Y * y, slotSize.X, slotSize.Y);
var slotBounds = new Rectangle((slotSize + _slotSpacing) * new Float2(x, y), slotSize);
_slotPadding.ShrinkRectangle(ref slotBounds);
if (slotsV > 1)
{
if (y == 0)
{
slotBounds.Height -= _slotSpacing.Y * 0.5f;
}
else if (y == slotsV - 1)
{
slotBounds.Height -= _slotSpacing.Y * 0.5f;
slotBounds.Y += _slotSpacing.Y * 0.5f;
}
else
{
slotBounds.Height -= _slotSpacing.Y;
slotBounds.Y += _slotSpacing.Y * 0.5f;
}
}
if (slotsH > 1)
{
if (x == 0)
{
slotBounds.Width -= _slotSpacing.X * 0.5f;
}
else if (x == slotsH - 1)
{
slotBounds.Width -= _slotSpacing.X * 0.5f;
slotBounds.X += _slotSpacing.X * 0.5f;
}
else
{
slotBounds.Width -= _slotSpacing.X;
slotBounds.X += _slotSpacing.X * 0.5f;
}
}
var c = _children[i++];
c.Bounds = slotBounds;
}

View File

@@ -176,7 +176,7 @@ void TextRender::UpdateLayout()
int32 kerning;
// Perform layout
Array<FontLineCache> lines;
Array<FontLineCache, InlinedAllocation<8>> lines;
font->ProcessText(text, lines, _layoutOptions);
// Prepare buffers capacity

View File

@@ -372,8 +372,8 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value)
// Atan2
case 41:
{
Value v1 = tryGetValue(node->GetBox(0), Value::Zero);
Value v2 = tryGetValue(node->GetBox(1), Value::Zero);
Value v1 = tryGetValue(node->GetBox(0), 0, Value::Zero);
Value v2 = tryGetValue(node->GetBox(1), 1, Value::Zero);
value = writeFunction2(node, v1, v2, TEXT("atan2"));
break;
}