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

This commit is contained in:
Wojtek Figat
2023-02-13 10:05:51 +01:00
52 changed files with 1020 additions and 212 deletions

View File

@@ -1070,7 +1070,8 @@ bool findAsset(const Guid& id, const String& directory, Array<String>& tmpCache,
tmpCache.Clear();
if (FileSystem::DirectoryGetFiles(tmpCache, directory, TEXT("*"), DirectorySearchOption::AllDirectories))
{
LOG(Error, "Cannot query files in folder '{0}'.", directory);
if (FileSystem::DirectoryExists(directory))
LOG(Error, "Cannot query files in folder '{0}'.", directory);
return false;
}

View File

@@ -163,7 +163,12 @@ bool CreateAssetContext::AllocateChunk(int32 index)
void CreateAssetContext::AddMeta(JsonWriter& writer) const
{
writer.JKEY("ImportPath");
if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath))
if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath)
#if PLATFORM_WINDOWS
// Import path from other drive should be stored as absolute on Windows to prevent issues
&& InputPath.Length() > 2 && Globals::ProjectFolder.Length() > 2 && InputPath[0] == Globals::ProjectFolder[0]
#endif
)
{
const String relativePath = FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, InputPath);
writer.String(relativePath);

View File

@@ -15,6 +15,16 @@ namespace FlaxEditor.Content.Settings
internal const string XboxOnePlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxOnePlatformSettings";
internal const string XboxScarlettPlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxScarlettPlatformSettings";
internal const string SwitchPlatformSettingsTypename = "FlaxEditor.Content.Settings.SwitchPlatformSettings";
#if FLAX_EDITOR
internal static string[] OptionalPlatformSettings =
{
PS4PlatformSettingsTypename,
PS5PlatformSettingsTypename,
XboxOnePlatformSettingsTypename,
XboxScarlettPlatformSettingsTypename,
SwitchPlatformSettingsTypename,
};
#endif
/// <summary>
/// The default application icon.

View File

@@ -2,22 +2,18 @@
#include "ObjectsRemovalService.h"
#include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Engine/Time.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h"
#include "Log.h"
namespace ObjectsRemovalServiceImpl
{
bool IsReady = false;
CriticalSection PoolLocker;
CriticalSection NewItemsLocker;
DateTime LastUpdate;
float LastUpdateGameTime;
Dictionary<Object*, float> Pool(8192);
Dictionary<Object*, float> NewItemsPool(2048);
}
using namespace ObjectsRemovalServiceImpl;
@@ -39,41 +35,14 @@ ObjectsRemoval ObjectsRemovalInstance;
bool ObjectsRemovalService::IsInPool(Object* obj)
{
if (!IsReady)
return false;
{
ScopeLock lock(NewItemsLocker);
if (NewItemsPool.ContainsKey(obj))
return true;
}
{
ScopeLock lock(PoolLocker);
if (Pool.ContainsKey(obj))
return true;
}
return false;
}
bool ObjectsRemovalService::HasNewItemsForFlush()
{
NewItemsLocker.Lock();
const bool result = NewItemsPool.HasItems();
NewItemsLocker.Unlock();
PoolLocker.Lock();
const bool result = Pool.ContainsKey(obj);
PoolLocker.Unlock();
return result;
}
void ObjectsRemovalService::Dereference(Object* obj)
{
if (!IsReady)
return;
NewItemsLocker.Lock();
NewItemsPool.Remove(obj);
NewItemsLocker.Unlock();
PoolLocker.Lock();
Pool.Remove(obj);
PoolLocker.Unlock();
@@ -81,57 +50,37 @@ void ObjectsRemovalService::Dereference(Object* obj)
void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime)
{
ScopeLock lock(NewItemsLocker);
obj->Flags |= ObjectFlags::WasMarkedToDelete;
if (useGameTime)
obj->Flags |= ObjectFlags::UseGameTimeForDelete;
else
obj->Flags &= ~ObjectFlags::UseGameTimeForDelete;
NewItemsPool[obj] = timeToLive;
PoolLocker.Lock();
Pool[obj] = timeToLive;
PoolLocker.Unlock();
}
void ObjectsRemovalService::Flush(float dt, float gameDelta)
{
PROFILE_CPU();
// Add new items
PoolLocker.Lock();
int32 itemsLeft;
do
{
ScopeLock lock(NewItemsLocker);
for (auto i = NewItemsPool.Begin(); i.IsNotEnd(); ++i)
{
Pool[i->Key] = i->Value;
}
NewItemsPool.Clear();
}
// Update timeouts and delete objects that timed out
{
ScopeLock lock(PoolLocker);
// Update timeouts and delete objects that timed out
itemsLeft = Pool.Count();
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{
auto obj = i->Key;
Object* obj = i->Key;
const float ttl = i->Value - ((obj->Flags & ObjectFlags::UseGameTimeForDelete) != ObjectFlags::None ? gameDelta : dt);
if (ttl <= ZeroTolerance)
if (ttl <= 0.0f)
{
Pool.Remove(i);
#if BUILD_DEBUG || BUILD_DEVELOPMENT
if (NewItemsPool.ContainsKey(obj))
{
const auto asScriptingObj = dynamic_cast<ScriptingObject*>(obj);
if (asScriptingObj)
{
LOG(Warning, "Object {0} was marked to delete after delete timeout", asScriptingObj->GetID());
}
}
#endif
NewItemsPool.Remove(obj);
//ASSERT(!NewItemsPool.ContainsKey(obj));
obj->OnDeleteObject();
itemsLeft--;
}
else
{
@@ -139,38 +88,9 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
}
}
}
while (itemsLeft != Pool.Count()); // Continue removing if any new item was added during removing (eg. sub-object delete with 0 timeout)
// Perform removing in loop
// Note: objects during OnDeleteObject call can register new objects to remove with timeout=0, for example Actors do that to remove children and scripts
while (HasNewItemsForFlush())
{
// Add new items
{
ScopeLock lock(NewItemsLocker);
for (auto i = NewItemsPool.Begin(); i.IsNotEnd(); ++i)
{
Pool[i->Key] = i->Value;
}
NewItemsPool.Clear();
}
// Delete objects that timed out
{
ScopeLock lock(PoolLocker);
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value <= ZeroTolerance)
{
auto obj = i->Key;
Pool.Remove(i);
ASSERT(!NewItemsPool.ContainsKey(obj));
obj->OnDeleteObject();
}
}
}
}
PoolLocker.Unlock();
}
bool ObjectsRemoval::Init()
@@ -204,14 +124,12 @@ void ObjectsRemoval::Dispose()
ScopeLock lock(PoolLocker);
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
{
auto obj = i->Key;
Object* obj = i->Key;
Pool.Remove(i);
obj->OnDeleteObject();
}
Pool.Clear();
}
IsReady = false;
}
Object::~Object()

View File

@@ -17,12 +17,6 @@ public:
/// <returns>True if object has been registered in the pool for the removing, otherwise false.</returns>
static bool IsInPool(Object* obj);
/// <summary>
/// Determines whether any object has been registered to be removed from pool (requests are flushed on Flush call).
/// </summary>
/// <returns>True if any object has been registered to be removed, otherwise false.</returns>
static bool HasNewItemsForFlush();
/// <summary>
/// Removes the specified object from the dead pool (clears the reference to it).
/// </summary>

View File

@@ -10,7 +10,10 @@ namespace FlaxEngine
/// </summary>
public static class RandomUtil
{
private static readonly Random _random = new Random();
/// <summary>
/// Random numbers generator.
/// </summary>
public static readonly Random Random = new Random();
/// <summary>
/// Generates a pseudo-random number from normalized range [0;1].
@@ -19,7 +22,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Rand()
{
return _random.Next(0, int.MaxValue) / (float)int.MaxValue;
return Random.Next(0, int.MaxValue) / (float)int.MaxValue;
}
}
}

View File

@@ -57,9 +57,9 @@
#define GPU_USE_SHADERS_DEBUG_LAYER (BUILD_DEBUG)
// Maximum size of the texture that is supported by the engine (specific platforms can have lower limit)
#define GPU_MAX_TEXTURE_SIZE 8192
#define GPU_MAX_TEXTURE_MIP_LEVELS 14
#define GPU_MAX_TEXTURE_ARRAY_SIZE 512
#define GPU_MAX_TEXTURE_SIZE 16384
#define GPU_MAX_TEXTURE_MIP_LEVELS 15
#define GPU_MAX_TEXTURE_ARRAY_SIZE 1024
// Define default back buffer(s) format
#if GPU_USE_BGRA_BACK_BUFFER

View File

@@ -319,6 +319,15 @@ GPUDevice::~GPUDevice()
bool GPUDevice::Init()
{
// Clamp texture limits (eg. if driver reports higher value)
Limits.MaximumTexture1DSize = Math::Min(Limits.MaximumTexture1DSize, GPU_MAX_TEXTURE_SIZE);
Limits.MaximumTexture2DSize = Math::Min(Limits.MaximumTexture2DSize, GPU_MAX_TEXTURE_SIZE);
Limits.MaximumTexture3DSize = Math::Min(Limits.MaximumTexture3DSize, GPU_MAX_TEXTURE_SIZE);
Limits.MaximumTextureCubeSize = Math::Min(Limits.MaximumTextureCubeSize, GPU_MAX_TEXTURE_SIZE);
Limits.MaximumTexture1DArraySize = Math::Min(Limits.MaximumTexture1DArraySize, GPU_MAX_TEXTURE_ARRAY_SIZE);
Limits.MaximumTexture2DArraySize = Math::Min(Limits.MaximumTexture2DArraySize, GPU_MAX_TEXTURE_ARRAY_SIZE);
Limits.MaximumMipLevelsCount = Math::Min(Limits.MaximumMipLevelsCount, GPU_MAX_TEXTURE_MIP_LEVELS);
_res->TasksManager.SetExecutor(CreateTasksExecutor());
LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory));
return false;

View File

@@ -21,7 +21,7 @@ namespace
void RenderTargetPool::Flush(bool force)
{
const uint64 framesOffset = 10;
const uint64 framesOffset = 3 * 60;
const uint64 maxReleaseFrame = Engine::FrameCount - framesOffset;
force |= Engine::ShouldExit();

View File

@@ -443,6 +443,19 @@ const String& Actor::GetLayerName() const
return Level::Layers[_layer];
}
void Actor::SetLayerName(const StringView& value)
{
for (int32 i = 0; i < 32; i++)
{
if (Level::Layers[i] == value)
{
SetLayer(i);
return;
}
}
LOG(Warning, "Unknown layer name '{0}'", value);
}
bool Actor::HasTag() const
{
return Tags.Count() != 0;
@@ -485,10 +498,21 @@ Script* Actor::GetScript(int32 index) const
Script* Actor::GetScript(const MClass* type) const
{
CHECK_RETURN(type, nullptr);
for (auto script : Scripts)
if (type->IsInterface())
{
if (script->GetClass()->IsSubClassOf(type))
return script;
for (auto script : Scripts)
{
if (script->GetClass()->HasInterface(type))
return script;
}
}
else
{
for (auto script : Scripts)
{
if (script->GetClass()->IsSubClassOf(type))
return script;
}
}
return nullptr;
}
@@ -496,9 +520,18 @@ Script* Actor::GetScript(const MClass* type) const
Array<Script*> Actor::GetScripts(const MClass* type) const
{
Array<Script*> result;
for (auto script : Scripts)
if (script->GetClass()->IsSubClassOf(type))
result.Add(script);
if (type->IsInterface())
{
for (auto script : Scripts)
if (script->GetClass()->HasInterface(type))
result.Add(script);
}
else
{
for (auto script : Scripts)
if (script->GetClass()->IsSubClassOf(type))
result.Add(script);
}
return result;
}

View File

@@ -222,7 +222,7 @@ namespace FlaxEngine
/// </summary>
/// <typeparam name="T">Type of the script to search for. Includes any scripts derived from the type.</typeparam>
/// <returns>The script or null if failed to find.</returns>
public T GetScript<T>() where T : Script
public T GetScript<T>() where T : class
{
return GetScript(typeof(T)) as T;
}
@@ -233,7 +233,7 @@ namespace FlaxEngine
/// <typeparam name="T">Type of the script to search for. Includes any scripts derived from the type.</typeparam>
/// <param name="script">The returned script, valid only if method returns true.</param>
/// <returns>True if found a script of that type or false if failed to find.</returns>
public bool TryGetScript<T>(out T script) where T : Script
public bool TryGetScript<T>(out T script) where T : class
{
script = GetScript(typeof(T)) as T;
return script != null;
@@ -244,7 +244,7 @@ namespace FlaxEngine
/// </summary>
/// <typeparam name="T">Type of the object.</typeparam>
/// <returns>Script instance if found, null otherwise.</returns>
public T FindScript<T>() where T : Script
public T FindScript<T>() where T : class
{
return FindScript(typeof(T)) as T;
}
@@ -290,7 +290,7 @@ namespace FlaxEngine
/// </summary>
/// <typeparam name="T">Type of the scripts to search for. Includes any scripts derived from the type.</typeparam>
/// <returns>All scripts matching the specified type.</returns>
public T[] GetScripts<T>() where T : Script
public T[] GetScripts<T>() where T : class
{
var count = ScriptsCount;
var length = 0;

View File

@@ -105,7 +105,13 @@ public:
/// <summary>
/// Gets the name of the layer.
/// </summary>
API_PROPERTY() const String& GetLayerName() const;
API_PROPERTY(Attributes="HideInEditor, NoSerialize, NoAnimate")
const String& GetLayerName() const;
/// <summary>
/// Sets the name of the layer.
/// </summary>
API_PROPERTY() void SetLayerName(const StringView& value);
/// <summary>
/// Determines whether this actor has any tag assigned.

View File

@@ -1443,20 +1443,15 @@ void Level::ReloadScriptsAsync()
Actor* Level::FindActor(const Guid& id)
{
return Scripting::FindObject<Actor>(id);
return Scripting::TryFindObject<Actor>(id);
}
Actor* Level::FindActor(const StringView& name)
{
Actor* result = nullptr;
ScopeLock lock(ScenesLock);
for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
{
result = Scenes[i]->FindActor(name);
}
return result;
}

View File

@@ -164,6 +164,7 @@ void ENetDriver::Disconnect(const NetworkConnection& connection)
bool ENetDriver::PopEvent(NetworkEvent* eventPtr)
{
ASSERT(_host);
ENetEvent event;
const int result = enet_host_service(_host, &event, 0);
if (result < 0)

View File

@@ -191,6 +191,7 @@ bool StartPeer()
if (!NetworkManager::Peer)
{
LOG(Error, "Failed to create Network Peer at {0}:{1}", networkConfig.Address, networkConfig.Port);
NetworkManager::State = NetworkConnectionState::Offline;
return true;
}
NetworkManager::Frame = 0;
@@ -243,7 +244,10 @@ bool NetworkManager::StartServer()
LOG(Info, "Starting network manager as server");
Mode = NetworkManagerMode::Server;
if (StartPeer())
{
Mode = NetworkManagerMode::Offline;
return true;
}
if (!Peer->Listen())
{
Stop();
@@ -265,7 +269,10 @@ bool NetworkManager::StartClient()
LOG(Info, "Starting network manager as client");
Mode = NetworkManagerMode::Client;
if (StartPeer())
{
Mode = NetworkManagerMode::Offline;
return true;
}
if (!Peer->Connect())
{
Stop();
@@ -286,9 +293,15 @@ bool NetworkManager::StartHost()
LOG(Info, "Starting network manager as host");
Mode = NetworkManagerMode::Host;
if (StartPeer())
{
Mode = NetworkManagerMode::Offline;
return true;
}
if (!Peer->Listen())
{
Mode = NetworkManagerMode::Offline;
return true;
}
LocalClientId = ServerClientId;
NextClientId = ServerClientId + 1;
LocalClient = New<NetworkClient>(LocalClientId, NetworkConnection{ 0 });

View File

@@ -140,6 +140,12 @@ public:
return State == NetworkConnectionState::Connected;
}
// Returns true if network is online or disconnected.
API_PROPERTY() FORCE_INLINE static bool IsOffline()
{
return State == NetworkConnectionState::Offline || State == NetworkConnectionState::Disconnected;
}
/// <summary>
/// Gets the network client for a given connection. Returns null if failed to find it.
/// </summary>

View File

@@ -663,7 +663,7 @@ bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle,
void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* parent)
{
if (!obj || NetworkManager::State == NetworkConnectionState::Offline)
if (!obj || NetworkManager::IsOffline())
return;
ScopeLock lock(ObjectsLock);
if (Objects.Contains(obj))
@@ -695,7 +695,7 @@ void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* parent)
void NetworkReplicator::RemoveObject(ScriptingObject* obj)
{
if (!obj || NetworkManager::State == NetworkConnectionState::Offline)
if (!obj || NetworkManager::IsOffline())
return;
ScopeLock lock(ObjectsLock);
const auto it = Objects.Find(obj->GetID());
@@ -715,7 +715,7 @@ void NetworkReplicator::SpawnObject(ScriptingObject* obj)
void NetworkReplicator::SpawnObject(ScriptingObject* obj, const DataContainer<uint32>& clientIds)
{
if (!obj || NetworkManager::State == NetworkConnectionState::Offline)
if (!obj || NetworkManager::IsOffline())
return;
ScopeLock lock(ObjectsLock);
const auto it = Objects.Find(obj->GetID());
@@ -730,7 +730,7 @@ void NetworkReplicator::SpawnObject(ScriptingObject* obj, const DataContainer<ui
void NetworkReplicator::DespawnObject(ScriptingObject* obj)
{
if (!obj || NetworkManager::State == NetworkConnectionState::Offline)
if (!obj || NetworkManager::IsOffline())
return;
ScopeLock lock(ObjectsLock);
const auto it = Objects.Find(obj->GetID());
@@ -887,7 +887,7 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream)
{
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj)
if (!info || !obj || NetworkManager::IsOffline())
return;
ObjectsLock.Lock();
auto& rpc = RpcQueue.AddOne();
@@ -1023,15 +1023,6 @@ void NetworkInternal::NetworkReplicatorUpdate()
NewClients.Clear();
}
// Collect clients for replication (from server)
BuildCachedTargets(NetworkManager::Clients);
if (!isClient && CachedTargets.Count() == 0)
{
// Early exit if server has nobody to send data to
Scripting::ObjectsLookupIdMapping.Set(nullptr);
return;
}
// Despawn
if (DespawnQueue.Count() != 0)
{
@@ -1474,6 +1465,10 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
if (!obj->IsRegistered())
obj->RegisterObject();
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
if (!parent && msgDataItem.ParentId.IsValid())
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
}
// Add object to the list
NetworkReplicatedObject item;

View File

@@ -407,6 +407,23 @@ namespace FlaxEngine.Networking
return new Quaternion(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
}
/// <summary>
/// Writes data of type <see cref="Ray"/> into the message.
/// </summary>
public void WriteRay(Ray value)
{
WriteVector3(value.Position);
WriteVector3(value.Direction);
}
/// <summary>
/// Reads and returns data of type <see cref="Ray"/> from the message.
/// </summary>
public Ray ReadRay()
{
return new Ray(ReadVector3(), ReadVector3());
}
/// <summary>
/// Writes data of type <see cref="Boolean"/> into the message.
/// </summary>

View File

@@ -39,6 +39,9 @@ void ParticleSystem::Init(ParticleEmitter* emitter, float duration, float fps)
track.AsEmitter.Index = 0;
track.AsEmitter.StartFrame = 0;
track.AsEmitter.DurationFrames = DurationFrames;
#if !BUILD_RELEASE
_debugName = StringUtils::GetFileNameWithoutExtension(emitter->GetPath());
#endif
}
}
@@ -155,6 +158,9 @@ ParticleEffect* ParticleSystem::Spawn(Actor* parent, const Transform& transform,
auto effect = New<ParticleEffect>();
effect->SetTransform(transform);
effect->ParticleSystem = this;
#if !BUILD_RELEASE
effect->SetName(_debugName); // Give usable name in development builds
#endif
Level::SpawnActor(effect, parent);
@@ -452,6 +458,9 @@ Asset::LoadResult ParticleSystem::load()
return LoadResult::InvalidData;
}
#if !BUILD_RELEASE
_debugName = StringUtils::GetFileNameWithoutExtension(GetPath());
#endif
return LoadResult::Ok;
}
@@ -463,6 +472,9 @@ void ParticleSystem::unload(bool isReloading)
Emitters.Resize(0);
EmittersParametersOverrides.SetCapacity(0);
Tracks.Resize(0);
#if !BUILD_RELEASE
_debugName.Clear();
#endif
}
AssetChunksFlag ParticleSystem::getChunksToPreload() const

View File

@@ -98,6 +98,11 @@ public:
typedef Pair<int32, Guid> EmitterParameterOverrideKey;
private:
#if !BUILD_RELEASE
String _debugName;
#endif
public:
/// <summary>
/// The asset data version number. Used to sync the data with the instances state. Incremented each time asset gets loaded.

View File

@@ -150,7 +150,6 @@ CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector
Vector3 displacement = speed;
displacement += GetPhysicsScene()->GetGravity() * deltaTime;
displacement *= deltaTime;
return Move(displacement);
}
@@ -162,7 +161,8 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis
const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds();
result = (CollisionFlags)PhysicsBackend::MoveController(_controller, _shape, displacement, _minMoveDistance, deltaTime);
_lastFlags = result;
SetPosition(PhysicsBackend::GetControllerPosition(_controller));
Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center;
SetPosition(position);
}
return result;
}
@@ -178,10 +178,11 @@ void CharacterController::DrawPhysicsDebug(RenderView& view)
const float minSize = 0.001f;
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
const Vector3 position = _transform.LocalToWorld(_center);
if (view.Mode == ViewMode::PhysicsColliders)
DEBUG_DRAW_TUBE(_transform.LocalToWorld(_center), Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true);
DEBUG_DRAW_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true);
else
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true);
DEBUG_DRAW_WIRE_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true);
}
void CharacterController::OnDebugDrawSelected()
@@ -190,7 +191,8 @@ void CharacterController::OnDebugDrawSelected()
const float minSize = 0.001f;
const float radius = Math::Max(Math::Abs(_radius) * scaling, minSize);
const float height = Math::Max(Math::Abs(_height) * scaling, minSize);
DEBUG_DRAW_WIRE_TUBE(_transform.LocalToWorld(_center), Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false);
const Vector3 position = _transform.LocalToWorld(_center);
DEBUG_DRAW_WIRE_TUBE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false);
// Base
Collider::OnDebugDrawSelected();
@@ -204,7 +206,8 @@ void CharacterController::CreateController()
ASSERT(_controller == nullptr && _shape == nullptr);
_cachedScale = GetScale();
const float scaling = _cachedScale.GetAbsolute().MaxValue();
_controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, _transform.Translation, _slopeLimit, (int32)_nonWalkableMode, Material.Get(), Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape);
const Vector3 position = _transform.LocalToWorld(_center);
_controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material.Get(), Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape);
// Setup
PhysicsBackend::SetControllerUpDirection(_shape, _upDirection);
@@ -280,6 +283,7 @@ void CharacterController::OnActiveTransformChanged()
_isUpdatingTransform = true;
Transform transform;
PhysicsBackend::GetRigidActorPose(PhysicsBackend::GetShapeActor(_shape), transform.Translation, transform.Orientation);
transform.Translation -= _center;
transform.Orientation = _transform.Orientation;
transform.Scale = _transform.Scale;
SetTransform(transform);
@@ -360,9 +364,10 @@ void CharacterController::OnTransformChanged()
Actor::OnTransformChanged();
// Update physics
const Vector3 position = _transform.LocalToWorld(_center);
if (!_isUpdatingTransform && _controller)
{
PhysicsBackend::SetControllerPosition(_controller, _transform.Translation);
PhysicsBackend::SetControllerPosition(_controller, position);
const Float3 scale = GetScale();
if (!Float3::NearEqual(_cachedScale, scale))
UpdateGeometry();
@@ -370,7 +375,7 @@ void CharacterController::OnTransformChanged()
}
else if (!_controller)
{
_box = BoundingBox(_transform.Translation);
_box = BoundingBox(position);
BoundingSphere::FromBox(_box, _sphere);
}
}

View File

@@ -127,7 +127,7 @@ public:
/// <summary>
/// Gets the character up vector.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(240), DefaultValue(true), EditorDisplay(\"Character Controller\")")
API_PROPERTY(Attributes="EditorOrder(240), DefaultValue(typeof(Vector3), \"0,1,0\"), EditorDisplay(\"Character Controller\")")
Vector3 GetUpDirection() const;
/// <summary>

View File

@@ -1143,6 +1143,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
EyeAdaptationPass::Instance()->Render(renderContext, tempBuffer);
PostProcessingPass::Instance()->Render(renderContext, tempBuffer, output, colorGradingLUT);
RenderTargetPool::Release(colorGradingLUT);
RenderTargetPool::Release(tempBuffer);
context->ResetRenderTarget();
// Rebind resources

View File

@@ -681,6 +681,16 @@ void* BinaryModule::FindMethod(const ScriptingTypeHandle& typeHandle, const Scri
void BinaryModule::Destroy(bool isReloading)
{
// Destroy any default script instances
for (const auto& type : Types)
{
if (type.Type == ScriptingTypes::Script && type.Script.DefaultInstance)
{
Delete(type.Script.DefaultInstance);
type.Script.DefaultInstance = nullptr;
}
}
// Unregister
GetModules().RemoveKeepOrder(this);
}
@@ -1443,6 +1453,10 @@ void NativeBinaryModule::Destroy(bool isReloading)
{
ManagedBinaryModule::Destroy(isReloading);
// Skip native code unloading from core libs
if (this == GetBinaryModuleCorlib() || this == GetBinaryModuleFlaxEngine())
return;
// Release native library
const auto library = Library;
if (library)

View File

@@ -127,6 +127,15 @@ bool MClass::IsSubClassOf(const MonoClass* monoClass) const
}
#endif
bool MClass::HasInterface(const MClass* klass) const
{
#if USE_MONO
return klass && mono_class_is_assignable_from(klass->GetNative(), _monoClass) != 0;
#else
return false;
#endif
}
bool MClass::IsInstanceOfType(MObject* object) const
{
if (object == nullptr)

View File

@@ -154,6 +154,13 @@ public:
bool IsSubClassOf(const MonoClass* monoClass) const;
#endif
/// <summary>
/// Checks if this class implements the specified interface (including any base types).
/// </summary>
/// <param name="klass">The interface class.</param>
/// <returns>True if this class implements the specified interface.</returns>
bool HasInterface(const MClass* klass) const;
/// <summary>
/// Checks is the provided object instance of this class' type.
/// </summary>

View File

@@ -530,13 +530,6 @@ void Scripting::Release()
for (int32 i = modules.Count() - 1; i >= 0; i--)
{
auto module = modules[i];
if (module == GetBinaryModuleCorlib() || module == GetBinaryModuleFlaxEngine())
{
// Just C# assembly unload for in-build modules
((ManagedBinaryModule*)module)->Assembly->Unload();
continue;
}
module->Destroy(false);
}
_nonNativeModules.ClearDelete();

View File

@@ -819,7 +819,8 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti
ImportCurve(aAnim->mPositionKeys, aAnim->mNumPositionKeys, anim.Position);
ImportCurve(aAnim->mRotationKeys, aAnim->mNumRotationKeys, anim.Rotation);
ImportCurve(aAnim->mScalingKeys, aAnim->mNumScalingKeys, anim.Scale);
if (options.ImportScaleTracks)
ImportCurve(aAnim->mScalingKeys, aAnim->mNumScalingKeys, anim.Scale);
}
}
}

View File

@@ -1051,9 +1051,9 @@ bool ImportAnimation(int32 index, ImportedModelData& data, OpenFbxImporterData&
const ofbx::AnimationCurveNode* translationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Translation");
const ofbx::AnimationCurveNode* rotationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Rotation");
const ofbx::AnimationCurveNode* scalingNode = nullptr; //layer->getCurveNode(*aNode.FbxObj, "Lcl Scaling");
const ofbx::AnimationCurveNode* scalingNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Scaling");
if (translationNode || rotationNode || scalingNode)
if (translationNode || rotationNode || (scalingNode && importerData.Options.ImportScaleTracks))
animatedNodes.Add(nodeIndex);
}
if (animatedNodes.IsEmpty())
@@ -1069,13 +1069,14 @@ bool ImportAnimation(int32 index, ImportedModelData& data, OpenFbxImporterData&
const ofbx::AnimationCurveNode* translationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Translation");
const ofbx::AnimationCurveNode* rotationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Rotation");
//const ofbx::AnimationCurveNode* scalingNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Scaling");
const ofbx::AnimationCurveNode* scalingNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Scaling");
anim.NodeName = aNode.Name;
ImportCurve(translationNode, anim.Position, info, ExtractKeyframePosition);
ImportCurve(rotationNode, anim.Rotation, info, ExtractKeyframeRotation);
//ImportCurve(scalingNode, anim.Scale, info, ExtractKeyframeScale);
if (importerData.Options.ImportScaleTracks)
ImportCurve(scalingNode, anim.Scale, info, ExtractKeyframeScale);
}
if (importerData.ConvertRH)

View File

@@ -53,6 +53,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(SamplingRate);
SERIALIZE(SkipEmptyCurves);
SERIALIZE(OptimizeKeyframes);
SERIALIZE(ImportScaleTracks);
SERIALIZE(EnableRootMotion);
SERIALIZE(RootNodeName);
SERIALIZE(GenerateLODs);
@@ -93,6 +94,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(SamplingRate);
DESERIALIZE(SkipEmptyCurves);
DESERIALIZE(OptimizeKeyframes);
DESERIALIZE(ImportScaleTracks);
DESERIALIZE(EnableRootMotion);
DESERIALIZE(RootNodeName);
DESERIALIZE(GenerateLODs);

View File

@@ -235,6 +235,7 @@ public:
float SamplingRate = 0.0f;
bool SkipEmptyCurves = true;
bool OptimizeKeyframes = true;
bool ImportScaleTracks = false;
bool EnableRootMotion = false;
String RootNodeName;

View File

@@ -0,0 +1,541 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.ComponentModel;
namespace FlaxEngine.GUI
{
/// <summary>
/// UI canvas scaling component for user interface that targets multiple different game resolutions (eg. mobile screens).
/// </summary>
public class CanvasScaler : ContainerControl
{
/// <summary>
/// Canvas scaling modes.
/// </summary>
public enum ScalingMode
{
/// <summary>
/// Applies constant scale to the whole UI in pixels.
/// </summary>
ConstantPixelSize,
/// <summary>
/// Applies constant scale to the whole UI in physical units (depends on the screen DPI). Ensures the UI will have specific real-world size no matter the screen resolution.
/// </summary>
ConstantPhysicalSize,
/// <summary>
/// Applies min/max scaling to the UI depending on the screen resolution. Ensures the UI size won't go below min or above max resolution to maintain it's readability.
/// </summary>
ScaleWithResolution,
/// <summary>
/// Applies scaling curve to the UI depending on the screen DPI.
/// </summary>
ScaleWithDpi,
}
/// <summary>
/// Physical unit types for canvas scaling.
/// </summary>
public enum PhysicalUnitMode
{
/// <summary>
/// Centimeters (0.01 meter).
/// </summary>
Centimeters,
/// <summary>
/// Millimeters (0.1 centimeter, 0.001 meter).
/// </summary>
Millimeters,
/// <summary>
/// Inches (2.54 centimeters).
/// </summary>
Inches,
/// <summary>
/// Points (1/72 inch, 1/112 of pica).
/// </summary>
Points,
/// <summary>
/// Pica (1/6 inch).
/// </summary>
Picas,
}
/// <summary>
/// Resolution scaling modes.
/// </summary>
public enum ResolutionScalingMode
{
/// <summary>
/// Uses the shortest side of the screen to scale the canvas for min/max rule.
/// </summary>
ShortestSide,
/// <summary>
/// Uses the longest side of the screen to scale the canvas for min/max rule.
/// </summary>
LongestSide,
/// <summary>
/// Uses the horizontal (X, width) side of the screen to scale the canvas for min/max rule.
/// </summary>
Horizontal,
/// <summary>
/// Uses the vertical (Y, height) side of the screen to scale the canvas for min/max rule.
/// </summary>
Vertical,
}
private ScalingMode _scalingMode = ScalingMode.ConstantPixelSize;
private PhysicalUnitMode _physicalUnit = PhysicalUnitMode.Points;
private ResolutionScalingMode _resolutionMode = ResolutionScalingMode.ShortestSide;
private float _scale = 1.0f;
private float _scaleFactor = 1.0f;
private float _physicalUnitSize = 1.0f;
private Float2 _resolutionMin = new Float2(1, 1);
private Float2 _resolutionMax = new Float2(10000, 10000);
/// <summary>
/// Gets the current UI scale. Computed based on the setup when performing layout.
/// </summary>
public float CurrentScale => _scale;
/// <summary>
/// The UI Canvas scaling mode.
/// </summary>
[EditorOrder(0), EditorDisplay("Canvas Scaler"), ExpandGroups, DefaultValue(ScalingMode.ConstantPixelSize)]
public ScalingMode Scaling
{
get => _scalingMode;
set
{
if (_scalingMode == value)
return;
_scalingMode = value;
PerformLayout();
}
}
/// <summary>
/// The UI Canvas scale. Applied in all scaling modes for custom UI sizing.
/// </summary>
[EditorOrder(10), EditorDisplay("Canvas Scaler"), DefaultValue(1.0f), Limit(0.001f, 1000.0f, 0.01f)]
public float ScaleFactor
{
get => _scaleFactor;
set
{
if (Mathf.NearEqual(_scaleFactor, value))
return;
_scaleFactor = value;
PerformLayout();
}
}
/// <summary>
/// The UI Canvas physical unit to use for scaling via PhysicalUnitSize. Used only in ConstantPhysicalSize mode.
/// </summary>
#if FLAX_EDITOR
[EditorOrder(100), EditorDisplay("Canvas Scaler"), DefaultValue(PhysicalUnitMode.Points), VisibleIf(nameof(IsConstantPhysicalSize))]
#endif
public PhysicalUnitMode PhysicalUnit
{
get => _physicalUnit;
set
{
if (_physicalUnit == value)
return;
_physicalUnit = value;
#if FLAX_EDITOR
if (FlaxEditor.CustomEditors.CustomEditor.IsSettingValue)
{
// Set auto-default physical unit value for easier tweaking in Editor
_physicalUnitSize = GetUnitDpi(_physicalUnit) / Platform.Dpi;
}
#endif
PerformLayout();
}
}
/// <summary>
/// The UI Canvas physical unit value. Used only in ConstantPhysicalSize mode.
/// </summary>
#if FLAX_EDITOR
[EditorOrder(110), EditorDisplay("Canvas Scaler"), DefaultValue(1.0f), Limit(0.000001f, 1000000.0f, 0.0f), VisibleIf(nameof(IsConstantPhysicalSize))]
#endif
public float PhysicalUnitSize
{
get => _physicalUnitSize;
set
{
if (Mathf.NearEqual(_physicalUnitSize, value))
return;
_physicalUnitSize = value;
PerformLayout();
}
}
/// <summary>
/// The UI Canvas resolution scaling mode. Controls min/max resolutions usage in relation to the current screen resolution to compute the UI scale. Used only in ScaleWithResolution mode.
/// </summary>
#if FLAX_EDITOR
[EditorOrder(120), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithResolution))]
#endif
public ResolutionScalingMode ResolutionMode
{
get => _resolutionMode;
set
{
if (_resolutionMode == value)
return;
_resolutionMode = value;
PerformLayout();
}
}
/// <summary>
/// The UI Canvas minimum resolution. If the screen has lower size, then the interface will be scaled accordingly. Used only in ScaleWithResolution mode.
/// </summary>
#if FLAX_EDITOR
[EditorOrder(120), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithResolution))]
#endif
public Float2 ResolutionMin
{
get => _resolutionMin;
set
{
value = Float2.Max(value, Float2.One);
if (Float2.NearEqual(ref _resolutionMin, ref value))
return;
_resolutionMin = value;
PerformLayout();
}
}
/// <summary>
/// The UI Canvas maximum resolution. If the screen has higher size, then the interface will be scaled accordingly. Used only in ScaleWithResolution mode.
/// </summary>
#if FLAX_EDITOR
[EditorOrder(130), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithResolution))]
#endif
public Float2 ResolutionMax
{
get => _resolutionMax;
set
{
value = Float2.Max(value, Float2.One);
if (Float2.NearEqual(ref _resolutionMax, ref value))
return;
_resolutionMax = value;
PerformLayout();
}
}
/// <summary>
/// The UI Canvas scaling curve based on screen resolution - shortest/longest/vertical/horizontal (key is resolution, value is scale factor). Clear keyframes to skip using it and follow min/max rules only. Used only in ScaleWithResolution mode.
/// </summary>
#if FLAX_EDITOR
[EditorOrder(140), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithResolution))]
#endif
public LinearCurve<float> ResolutionCurve = new LinearCurve<float>(new[]
{
new LinearCurve<float>.Keyframe(480, 0.444f), // 480p
new LinearCurve<float>.Keyframe(720, 0.666f), // 720p
new LinearCurve<float>.Keyframe(1080, 1.0f), // 1080p
new LinearCurve<float>.Keyframe(8640, 8.0f), // 8640p
});
/// <summary>
/// The UI Canvas scaling curve based on screen DPI (key is DPI, value is scale factor). Used only in ScaleWithDpi mode.
/// </summary>
#if FLAX_EDITOR
[EditorOrder(150), EditorDisplay("Canvas Scaler"), VisibleIf(nameof(IsScaleWithDpi))]
#endif
public LinearCurve<float> DpiCurve = new LinearCurve<float>(new[]
{
new LinearCurve<float>.Keyframe(1.0f, 1.0f),
new LinearCurve<float>.Keyframe(96.0f, 1.0f),
new LinearCurve<float>.Keyframe(200.0f, 2.0f),
new LinearCurve<float>.Keyframe(400.0f, 4.0f),
});
#if FLAX_EDITOR
private bool IsConstantPhysicalSize => _scalingMode == ScalingMode.ConstantPhysicalSize;
private bool IsScaleWithResolution => _scalingMode == ScalingMode.ScaleWithResolution;
private bool IsScaleWithDpi => _scalingMode == ScalingMode.ScaleWithDpi;
#endif
/// <summary>
/// Initializes a new instance of the <see cref="CanvasScaler"/> class.
/// </summary>
public CanvasScaler()
{
// Fill the canvas by default
Offsets = Margin.Zero;
AnchorPreset = AnchorPresets.StretchAll;
AutoFocus = false;
}
/// <summary>
/// Updates the scaler for the current setup.
/// </summary>
public void UpdateScale()
{
float scale = 1.0f;
if (Parent != null)
{
UICanvas canvas = (Root as CanvasRootControl)?.Canvas;
float dpi = Platform.Dpi;
switch (canvas?.RenderMode ?? CanvasRenderMode.ScreenSpace)
{
case CanvasRenderMode.WorldSpace:
case CanvasRenderMode.WorldSpaceFaceCamera:
scale = 1.0f;
break;
default:
switch (_scalingMode)
{
case ScalingMode.ConstantPixelSize:
scale = 1.0f;
break;
case ScalingMode.ConstantPhysicalSize:
{
float targetDpi = GetUnitDpi(_physicalUnit);
scale = dpi / targetDpi * _physicalUnitSize;
break;
}
case ScalingMode.ScaleWithResolution:
{
Float2 resolution = Float2.Max(Size, Float2.One);
int axis = 0;
switch (_resolutionMode)
{
case ResolutionScalingMode.ShortestSide:
axis = resolution.X > resolution.Y ? 1 : 0;
break;
case ResolutionScalingMode.LongestSide:
axis = resolution.X > resolution.Y ? 0 : 1;
break;
case ResolutionScalingMode.Horizontal:
axis = 0;
break;
case ResolutionScalingMode.Vertical:
axis = 1;
break;
}
float min = _resolutionMin[axis], max = _resolutionMax[axis], value = resolution[axis];
if (value < min)
scale = min / value;
else if (value > max)
scale = max / value;
if (ResolutionCurve != null && ResolutionCurve.Keyframes?.Length != 0)
{
ResolutionCurve.Evaluate(out var curveScale, value, false);
scale *= curveScale;
}
break;
}
case ScalingMode.ScaleWithDpi:
DpiCurve?.Evaluate(out scale, dpi, false);
break;
}
break;
}
}
_scale = Mathf.Max(scale * _scaleFactor, 0.01f);
}
private float GetUnitDpi(PhysicalUnitMode unit)
{
float dpi = 1.0f;
switch (unit)
{
case PhysicalUnitMode.Centimeters:
dpi = 2.54f;
break;
case PhysicalUnitMode.Millimeters:
dpi = 25.4f;
break;
case PhysicalUnitMode.Inches:
dpi = 1;
break;
case PhysicalUnitMode.Points:
dpi = 72;
break;
case PhysicalUnitMode.Picas:
dpi = 6;
break;
}
return dpi;
}
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{
// Update current scaling before performing layout
UpdateScale();
base.PerformLayoutBeforeChildren();
}
#region UI Scale
#if FLAX_EDITOR
/// <inheritdoc />
public override Rectangle EditorBounds => new Rectangle(Float2.Zero, Size / _scale);
#endif
/// <inheritdoc />
public override void Draw()
{
DrawSelf();
// Draw children with scale
var scaling = new Float3(_scale, _scale, 1);
Matrix3x3.Scaling(ref scaling, out Matrix3x3 scale);
Render2D.PushTransform(scale);
if (ClipChildren)
{
GetDesireClientArea(out var clientArea);
Render2D.PushClip(ref clientArea);
DrawChildren();
Render2D.PopClip();
}
else
{
DrawChildren();
}
Render2D.PopTransform();
}
/// <inheritdoc />
public override void GetDesireClientArea(out Rectangle rect)
{
// Scale the area for the client controls
rect = new Rectangle(Float2.Zero, Size / _scale);
}
/// <inheritdoc />
public override bool IntersectsContent(ref Float2 locationParent, out Float2 location)
{
// Skip local PointFromParent but use base code
location = base.PointFromParent(ref locationParent);
return ContainsPoint(ref location);
}
/// <inheritdoc />
public override Float2 PointToParent(ref Float2 location)
{
var result = base.PointToParent(ref location);
result *= _scaleFactor;
return result;
}
/// <inheritdoc />
public override Float2 PointFromParent(ref Float2 location)
{
var result = base.PointFromParent(ref location);
result /= _scaleFactor;
return result;
}
/// <inheritdoc />
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
location /= _scale;
return base.OnDragEnter(ref location, data);
}
/// <inheritdoc />
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
{
location /= _scale;
return base.OnDragMove(ref location, data);
}
/// <inheritdoc />
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
{
location /= _scale;
return base.OnDragDrop(ref location, data);
}
/// <inheritdoc />
public override void OnMouseEnter(Float2 location)
{
location /= _scale;
base.OnMouseEnter(location);
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
location /= _scale;
base.OnMouseMove(location);
}
/// <inheritdoc />
public override bool OnMouseDown(Float2 location, MouseButton button)
{
location /= _scale;
return base.OnMouseDown(location, button);
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
location /= _scale;
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
{
location /= _scale;
return base.OnMouseDoubleClick(location, button);
}
/// <inheritdoc />
public override bool OnMouseWheel(Float2 location, float delta)
{
location /= _scale;
return base.OnMouseWheel(location, delta);
}
/// <inheritdoc />
public override void OnTouchEnter(Float2 location, int pointerId)
{
location /= _scale;
base.OnTouchEnter(location, pointerId);
}
/// <inheritdoc />
public override void OnTouchMove(Float2 location, int pointerId)
{
location /= _scale;
base.OnTouchMove(location, pointerId);
}
/// <inheritdoc />
public override bool OnTouchDown(Float2 location, int pointerId)
{
location /= _scale;
return base.OnTouchDown(location, pointerId);
}
/// <inheritdoc />
public override bool OnTouchUp(Float2 location, int pointerId)
{
location /= _scale;
return base.OnTouchUp(location, pointerId);
}
#endregion
}
}

View File

@@ -1259,6 +1259,13 @@ namespace FlaxEngine.GUI
return PointFromParent(ref location);
}
#if FLAX_EDITOR
/// <summary>
/// Bounds rectangle for editor UI.
/// </summary>
public virtual Rectangle EditorBounds => new Rectangle(Float2.Zero, _bounds.Size);
#endif
#endregion
#region Control Action

View File

@@ -11,9 +11,15 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the blur strength. Defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.
/// </summary>
[EditorOrder(0), Limit(0, 100, 0.0f), Tooltip("Blur strength defines how blurry the background is. Larger numbers increase blur, resulting in a larger runtime cost on the GPU.")]
[EditorOrder(0), Limit(0, 100, 0.0f)]
public float BlurStrength { get; set; }
/// <summary>
/// If checked, the blur strength will be scaled with the control size, which makes it resolution-independent.
/// </summary>
[EditorOrder(10)]
public bool BlurScaleWithSize { get; set; } = false;
/// <summary>
/// Initializes a new instance of the <see cref="BlurPanel"/> class.
/// </summary>
@@ -27,10 +33,13 @@ namespace FlaxEngine.GUI
{
base.Draw();
float strength = BlurStrength;
var size = Size;
var strength = BlurStrength;
if (BlurScaleWithSize)
strength *= size.MinValue / 1000.0f;
if (strength > Mathf.Epsilon)
{
Render2D.DrawBlur(new Rectangle(Float2.Zero, Size), strength);
Render2D.DrawBlur(new Rectangle(Float2.Zero, size), strength);
}
}
}