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

This commit is contained in:
Wojtek Figat
2025-03-07 23:41:02 +01:00
42 changed files with 1215 additions and 130 deletions

View File

@@ -1051,6 +1051,7 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
LOG(Error, "Cannot create virtual asset object.");
return nullptr;
}
asset->RegisterObject();
// Call initializer function
asset->InitAsVirtual();
@@ -1291,6 +1292,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
LOAD_FAILED();
}
#endif
result->RegisterObject();
// Register asset
AssetsLocker.Lock();

View File

@@ -348,6 +348,11 @@ struct DebugDrawContext
DebugDrawData DebugDrawDepthTest;
Float3 LastViewPos = Float3::Zero;
Matrix LastViewProj = Matrix::Identity;
inline int32 Count() const
{
return DebugDrawDefault.Count() + DebugDrawDepthTest.Count();
}
};
namespace
@@ -748,6 +753,13 @@ void DebugDraw::SetContext(void* context)
Context = context ? (DebugDrawContext*)context : &GlobalContext;
}
bool DebugDraw::CanClear(void* context)
{
if (!context)
context = &GlobalContext;
return ((DebugDrawContext*)context)->Count() != 0;
}
#endif
Vector3 DebugDraw::GetViewPos()

View File

@@ -65,6 +65,13 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
/// </summary>
/// <param name="context">The context or null.</param>
API_FUNCTION() static void SetContext(void* context);
/// <summary>
/// Checks if can clear all debug shapes displayed on screen. Can be used to disable this functionality when not needed for the user.
/// </summary>
/// <param name="context">The context.</param>
/// <returns>True fi context can be cleared (has any shapes), otherwise false.</returns>
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.

View File

@@ -19,7 +19,7 @@ public:
static void Log(LogType type, const StringView& message);
/// <summary>
/// A variant of Debug.Log that logs a warning message to the console.
/// A variant of Debug.Log that logs an info message to the console.
/// </summary>
/// <param name="message">The text message to display.</param>
FORCE_INLINE static void Log(const StringView& message)

View File

@@ -613,6 +613,209 @@ float Input::GetAxisRaw(const StringView& name)
return e ? e->ValueRaw : false;
}
void Input::SetInputMappingFromSettings(const JsonAssetReference<InputSettings>& settings)
{
auto actionMappings = settings.GetInstance()->ActionMappings;
ActionMappings.Resize(actionMappings.Count(), false);
for (int i = 0; i < actionMappings.Count(); i++)
{
ActionMappings[i] = actionMappings.At(i);
}
auto axisMappings = settings.GetInstance()->AxisMappings;
AxisMappings.Resize(axisMappings.Count(), false);
for (int i = 0; i < axisMappings.Count(); i++)
{
AxisMappings[i] = axisMappings.At(i);
}
Axes.Clear();
Actions.Clear();
}
void Input::SetInputMappingToDefaultSettings()
{
InputSettings* settings = InputSettings::Get();
if (settings)
{
ActionMappings = settings->ActionMappings;
AxisMappings = settings->AxisMappings;
Axes.Clear();
Actions.Clear();
}
}
ActionConfig Input::GetActionConfigByName(const StringView& name)
{
ActionConfig config = {};
for (const auto& a : ActionMappings)
{
if (a.Name == name)
{
config = a;
return config;
}
}
return config;
}
Array<ActionConfig> Input::GetAllActionConfigsByName(const StringView& name)
{
Array<ActionConfig> actionConfigs;
for (const auto& a : ActionMappings)
{
if (a.Name == name)
actionConfigs.Add(a);
}
return actionConfigs;
}
AxisConfig Input::GetAxisConfigByName(const StringView& name)
{
AxisConfig config = {};
for (const auto& a : AxisMappings)
{
if (a.Name == name)
{
config = a;
return config;
}
}
return config;
}
Array<AxisConfig> Input::GetAllAxisConfigsByName(const StringView& name)
{
Array<AxisConfig> actionConfigs;
for (const auto& a : AxisMappings)
{
if (a.Name == name)
actionConfigs.Add(a);
}
return actionConfigs;
}
void Input::SetAxisConfigByName(const StringView& name, AxisConfig& config, bool all)
{
for (int i = 0; i < AxisMappings.Count(); ++i)
{
auto& mapping = AxisMappings.At(i);
if (mapping.Name.Compare(name.ToString()) == 0)
{
if (config.Name.IsEmpty())
config.Name = name;
mapping = config;
if (!all)
break;
}
}
}
void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const KeyboardKeys positiveButton, const KeyboardKeys negativeButton, bool all)
{
for (int i = 0; i < AxisMappings.Count(); ++i)
{
auto& mapping = AxisMappings.At(i);
if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Axis == inputType)
{
mapping.PositiveButton = positiveButton;
mapping.NegativeButton = negativeButton;
if (!all)
break;
}
}
}
void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all)
{
for (int i = 0; i < AxisMappings.Count(); ++i)
{
auto& mapping = AxisMappings.At(i);
if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Gamepad == gamepadIndex && mapping.Axis == inputType)
{
mapping.GamepadPositiveButton = positiveButton;
mapping.GamepadNegativeButton = negativeButton;
if (!all)
break;
}
}
}
void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const float gravity, const float deadZone, const float sensitivity, const float scale, const bool snap, bool all)
{
for (int i = 0; i < AxisMappings.Count(); ++i)
{
auto& mapping = AxisMappings.At(i);
if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Axis == inputType)
{
mapping.Gravity = gravity;
mapping.DeadZone = deadZone;
mapping.Sensitivity = sensitivity;
mapping.Scale = scale;
mapping.Snap = snap;
if (!all)
break;
}
}
}
void Input::SetActionConfigByName(const StringView& name, const KeyboardKeys key, bool all)
{
for (int i = 0; i < ActionMappings.Count(); ++i)
{
auto& mapping = ActionMappings.At(i);
if (mapping.Name.Compare(name.ToString()) == 0)
{
mapping.Key = key;
if (!all)
break;
}
}
}
void Input::SetActionConfigByName(const StringView& name, const MouseButton mouseButton, bool all)
{
for (int i = 0; i < ActionMappings.Count(); ++i)
{
auto& mapping = ActionMappings.At(i);
if (mapping.Name.Compare(name.ToString()) == 0)
{
mapping.MouseButton = mouseButton;
if (!all)
break;
}
}
}
void Input::SetActionConfigByName(const StringView& name, const GamepadButton gamepadButton, InputGamepadIndex gamepadIndex, bool all)
{
for (int i = 0; i < ActionMappings.Count(); ++i)
{
auto& mapping = ActionMappings.At(i);
if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Gamepad == gamepadIndex)
{
mapping.GamepadButton = gamepadButton;
if (!all)
break;
}
}
}
void Input::SetActionConfigByName(const StringView& name, ActionConfig& config, bool all)
{
for (int i = 0; i < ActionMappings.Count(); ++i)
{
auto& mapping = ActionMappings.At(i);
if (mapping.Name.Compare(name.ToString()) == 0)
{
if (config.Name.IsEmpty())
config.Name = name;
mapping = config;
if (!all)
break;
}
}
}
void InputService::Update()
{
PROFILE_CPU();

View File

@@ -9,6 +9,7 @@
#include "KeyboardKeys.h"
#include "Enums.h"
#include "VirtualInput.h"
#include "Engine/Content/JsonAssetReference.h"
class Mouse;
class Keyboard;
@@ -332,4 +333,118 @@ public:
/// <returns>The current axis value (e.g for gamepads it's in the range -1..1). No smoothing applied.</returns>
/// <seealso cref="AxisMappings"/>
API_FUNCTION() static float GetAxisRaw(const StringView& name);
/// <summary>
/// Sets and overwrites the Action and Axis mappings with the values from a new InputSettings.
/// </summary>
/// <param name="settings">The input settings.</param>
API_FUNCTION() static void SetInputMappingFromSettings(const JsonAssetReference<class InputSettings>& settings);
/// <summary>
/// Sets and overwrites the Action and Axis mappings with the values from the InputSettings in GameSettings.
/// </summary>
API_FUNCTION() static void SetInputMappingToDefaultSettings();
/// <summary>
/// Gets the first action configuration by name.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <returns>The first Action configuration with the name. Empty configuration if not found.</returns>
API_FUNCTION() static ActionConfig GetActionConfigByName(const StringView& name);
/// <summary>
/// Gets all the action configurations by name.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <returns>The Action configurations with the name.</returns>
API_FUNCTION() static Array<ActionConfig> GetAllActionConfigsByName(const StringView& name);
/// <summary>
/// Sets the action configuration keyboard key by name.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <param name="key">The key to set.</param>
/// <param name="all">Whether to set only the first config found or all of them.</param>
API_FUNCTION() static void SetActionConfigByName(const StringView& name, const KeyboardKeys key, bool all = false);
/// <summary>
/// Sets the action configuration mouse button by name.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <param name="mouseButton">The mouse button to set.</param>
/// <param name="all">Whether to set only the first config found or all of them.</param>
API_FUNCTION() static void SetActionConfigByName(const StringView& name, const MouseButton mouseButton, bool all = false);
/// <summary>
/// Sets the action configuration gamepad button by name and index.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <param name="gamepadButton">The gamepad button to set.</param>
/// <param name="gamepadIndex">The gamepad index used to find the correct config.</param>
/// <param name="all">Whether to set only the first config found or all of them.</param>
API_FUNCTION() static void SetActionConfigByName(const StringView& name, const GamepadButton gamepadButton, InputGamepadIndex gamepadIndex, bool all = false);
/// <summary>
/// Sets the action configuration by name.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <param name="config">The action configuration to set. Leave the config name empty to use set name.</param>
/// <param name="all">Whether to set only the first config found or all of them.</param>
API_FUNCTION() static void SetActionConfigByName(const StringView& name, ActionConfig& config, bool all = false);
/// <summary>
/// Gets the first axis configurations by name.
/// </summary>
/// <param name="name">The name of the axis config.</param>
/// <returns>The first Axis configuration with the name. Empty configuration if not found.</returns>
API_FUNCTION() static AxisConfig GetAxisConfigByName(const StringView& name);
/// <summary>
/// Gets all the axis configurations by name.
/// </summary>
/// <param name="name">The name of the axis config.</param>
/// <returns>The axis configurations with the name.</returns>
API_FUNCTION() static Array<AxisConfig> GetAllAxisConfigsByName(const StringView& name);
/// <summary>
/// Sets the axis configuration keyboard key by name and type.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <param name="config">The configuration to set. Leave the config name empty to use set name.</param>
/// <param name="all">Whether to set only the first config found or all of them.</param>
API_FUNCTION() static void SetAxisConfigByName(const StringView& name, AxisConfig& config, bool all = false);
/// <summary>
/// Sets the axis configuration keyboard key buttons by name and type.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <param name="inputType">The type to sort by.</param>
/// <param name="positiveButton">The positive key button.</param>
/// <param name="negativeButton">The negative key button.</param>
/// <param name="all">Whether to set only the first config found or all of them.</param>
API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const KeyboardKeys positiveButton, const KeyboardKeys negativeButton, bool all = false);
/// <summary>
/// Sets the axis configuration gamepad buttons by name, type, and index.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <param name="inputType">The type to sort by.</param>
/// <param name="positiveButton">The positive gamepad button.</param>
/// <param name="negativeButton">The negative gamepad button.</param>
/// <param name="gamepadIndex">The gamepad index to sort by.</param>
/// <param name="all">Whether to set only the first config found or all of them.</param>
API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all = false);
/// <summary>
/// Sets the axis configuration accessories by name, and type.
/// </summary>
/// <param name="name">The name of the action config.</param>
/// <param name="inputType">The type to sort by.</param>
/// <param name="gravity">The gravity to set.</param>
/// <param name="deadZone">The dead zone to set.</param>
/// <param name="sensitivity">The sensitivity to set.</param>
/// <param name="scale">The scale to set.</param>
/// <param name="snap">The snap to set.</param>
/// <param name="all">Whether to set only the first config found or all of them.</param>
API_FUNCTION() static void SetAxisConfigByName(const StringView& name, InputAxisType inputType, const float gravity, const float deadZone, const float sensitivity, const float scale, const bool snap, bool all = false);
};

View File

@@ -960,10 +960,12 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
objects[i] = obj;
if (obj)
{
obj->RegisterObject();
if (!obj->IsRegistered())
obj->RegisterObject();
#if USE_EDITOR
// Auto-create C# objects for all actors in Editor during scene load when running in async (so main thread already has all of them)
obj->CreateManaged();
if (!obj->GetManagedInstance())
obj->CreateManaged();
#endif
}
else

View File

@@ -644,6 +644,14 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
int32 indicesCount;
if (mesh.Actor->GetMeshData(mesh, MeshBufferType::Index, indicesData, indicesCount))
return;
if (_paint.Count() != verticesCount)
{
// Fix incorrect paint data
int32 countBefore = _paint.Count();
_paint.Resize(verticesCount);
for (int32 i = countBefore; i < verticesCount; i++)
_paint.Get()[i] = 0.0f;
}
const int32 verticesStride = verticesData.Length() / verticesCount;
const bool indices16bit = indicesData.Length() / indicesCount == sizeof(uint16);
const int32 trianglesCount = indicesCount / 3;
@@ -697,12 +705,12 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
float massSum = 0;
for (int32 i = 0; i < verticesCount; i++)
{
float& mass = invMasses[i];
float& mass = invMasses.Get()[i];
#if USE_CLOTH_SANITY_CHECKS
// Sanity check
ASSERT(!isnan(mass) && !isinf(mass) && mass >= 0.0f);
#endif
const float maxDistance = _paint[i];
const float maxDistance = _paint.Get()[i];
if (maxDistance < 0.01f)
{
// Fixed
@@ -722,7 +730,7 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
const float massScale = (float)(verticesCount - fixedCount) / massSum;
for (int32 i = 0; i < verticesCount; i++)
{
float& mass = invMasses[i];
float& mass = invMasses.Get()[i];
if (mass > 0.0f)
{
mass *= massScale;
@@ -786,6 +794,8 @@ bool Cloth::OnPreUpdate()
auto blendWeightsStream = accessor.BlendWeights();
if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid())
return false;
if (verticesCount != _paint.Count())
return false;
PROFILE_CPU_NAMED("Skinned Pose");
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);

View File

@@ -272,7 +272,7 @@ bool PlatformBase::Is64BitApp()
int32 PlatformBase::GetCacheLineSize()
{
return Platform::GetCPUInfo().CacheLineSize;
return (int32)Platform::GetCPUInfo().CacheLineSize;
}
void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType error)

View File

@@ -394,7 +394,7 @@ public:
/// [Deprecated in v1.10]
/// </summary>
/// <returns>The cache line size.</returns>
API_PROPERTY() DEPRECATED("Use CPUInfo.CacheLineSize instead") static int32 GetCacheLineSize();
API_PROPERTY() DEPRECATED("Use CacheLineSize field from CPUInfo.") static int32 GetCacheLineSize();
/// <summary>
/// Gets the current memory stats.

View File

@@ -796,6 +796,17 @@ ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSp
// Mark as managed type
object->Flags |= ObjectFlags::IsManagedType;
// Initialize managed instance
if (params.Managed)
{
object->SetManagedInstance((MObject*)params.Managed);
}
else
{
// Invoke managed ctor (to match C++ logic)
object->CreateManaged();
}
return object;
}

View File

@@ -412,7 +412,8 @@ void ScriptingObject::DestroyManaged()
void ScriptingObject::RegisterObject()
{
ASSERT(!IsRegistered());
if (IsRegistered())
return;
Flags |= ObjectFlags::IsRegistered;
Scripting::RegisterObject(this);
}
@@ -532,10 +533,6 @@ bool ManagedScriptingObject::CreateManaged()
}
#endif
// Ensure to be registered
if (!IsRegistered())
RegisterObject();
return false;
}
@@ -598,8 +595,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_Create1(MTypeObject* type)
}
// Create managed object
obj->CreateManaged();
MObject* managedInstance = obj->GetManagedInstance();
MObject* managedInstance = obj->GetOrCreateManagedInstance();
if (managedInstance == nullptr)
{
LOG(Error, "Cannot create managed instance for type \'{0}\'.", String(typeClass->GetFullName()));
@@ -636,8 +632,7 @@ DEFINE_INTERNAL_CALL(MObject*) ObjectInternal_Create2(MString* typeNameObj)
}
// Create managed object
obj->CreateManaged();
MObject* managedInstance = obj->GetManagedInstance();
MObject* managedInstance = obj->GetOrCreateManagedInstance();
if (managedInstance == nullptr)
{
LOG(Error, "Cannot create managed instance for type \'{0}\'.", String(typeName));
@@ -667,7 +662,8 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage
const ScriptingType& scriptingType = module->Types[typeIndex];
// Create unmanaged object
const ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex));
ScriptingObjectSpawnParams params(Guid::New(), ScriptingTypeHandle(module, typeIndex));
params.Managed = managedInstance; // Link created managed instance to the unmanaged object
ScriptingObject* obj = scriptingType.Script.Spawn(params);
if (obj == nullptr)
{
@@ -675,9 +671,6 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage
return;
}
// Link created managed instance to the unmanaged object
obj->SetManagedInstance(managedInstance);
// Set default name for actors
if (auto* actor = dynamic_cast<Actor*>(obj))
{
@@ -689,8 +682,7 @@ DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceCreated(MObject* manage
MCore::ScriptingObject::SetInternalValues(klass, managedInstance, obj, &id);
// Register object
if (!obj->IsRegistered())
obj->RegisterObject();
obj->RegisterObject();
}
DEFINE_INTERNAL_CALL(void) ObjectInternal_ManagedInstanceDeleted(ScriptingObject* obj)

View File

@@ -355,9 +355,15 @@ struct ScriptingObjectSpawnParams
/// </summary>
const ScriptingTypeHandle Type;
/// <summary>
/// Optional C# object instance to use for unmanaged object.
/// </summary>
void* Managed;
FORCE_INLINE ScriptingObjectSpawnParams(const Guid& id, const ScriptingTypeHandle& typeHandle)
: ID(id)
, Type(typeHandle)
, Managed(nullptr)
{
}
};

View File

@@ -442,7 +442,7 @@ namespace FlaxEngine.Json
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
var result = Activator.CreateInstance(objectType);
var result = existingValue ?? Activator.CreateInstance(objectType);
if (reader.TokenType == JsonToken.String)
{
JsonSerializer.ParseID((string)reader.Value, out var id);
@@ -483,6 +483,44 @@ namespace FlaxEngine.Json
}
}
internal class ControlReferenceConverter : JsonConverter
{
/// <inheritdoc />
public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
var id = (value as IControlReference)?.UIControl?.ID ?? Guid.Empty;
writer.WriteValue(JsonSerializer.GetStringID(&id));
}
/// <inheritdoc />
public override void WriteJsonDiff(JsonWriter writer, object value, object other, Newtonsoft.Json.JsonSerializer serializer)
{
if (value is IControlReference valueRef &&
other is IControlReference otherRef &&
JsonSerializer.SceneObjectEquals(valueRef.UIControl, otherRef.UIControl))
return;
base.WriteJsonDiff(writer, value, other, serializer);
}
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
var result = existingValue ?? Activator.CreateInstance(objectType);
if (reader.TokenType == JsonToken.String && result is IControlReference controlReference)
{
JsonSerializer.ParseID((string)reader.Value, out var id);
controlReference.Load(Object.Find<UIControl>(ref id));
}
return result;
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType.Name.StartsWith("ControlReference", StringComparison.Ordinal);
}
}
/*
/// <summary>
/// Serialize Guid values using `N` format

View File

@@ -194,6 +194,7 @@ namespace FlaxEngine.Json
settings.Converters.Add(new SoftObjectReferenceConverter());
settings.Converters.Add(new SoftTypeReferenceConverter());
settings.Converters.Add(new BehaviorKnowledgeSelectorAnyConverter());
settings.Converters.Add(new ControlReferenceConverter());
settings.Converters.Add(new MarginConverter());
settings.Converters.Add(new VersionConverter());
settings.Converters.Add(new LocalizedStringConverter());
@@ -227,6 +228,23 @@ namespace FlaxEngine.Json
CacheManagedOnly.Dispose();
}
/// <summary>
/// The default implementation of the values comparision function used by the serialization system.
/// </summary>
/// <param name="objA">The object a.</param>
/// <param name="objB">The object b.</param>
/// <returns>True if both objects are equal, otherwise false.</returns>
public static bool SceneObjectEquals(SceneObject objA, SceneObject objB)
{
if (objA == objB)
return true;
if (objA == null || objB == null)
return false;
if (objA.HasPrefabLink && objB.HasPrefabLink)
return objA.PrefabObjectID == objB.PrefabObjectID;
return false;
}
/// <summary>
/// The default implementation of the values comparision function used by the serialization system.
/// </summary>

View File

@@ -74,13 +74,13 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
_physicsHeightField = nullptr;
_x = x;
_z = z;
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
_offset = Float3(_x * size, 0.0f, _z * size);
_yOffset = 0.0f;
_yHeight = 1.0f;
for (int32 i = 0; i < Terrain::ChunksCount; i++)
{
Chunks[i].Init(this, i % Terrain::Terrain::ChunksCountEdge, i / Terrain::Terrain::ChunksCountEdge);
Chunks[i].Init(this, i % Terrain::ChunksCountEdge, i / Terrain::ChunksCountEdge);
}
Heightmap = nullptr;
for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++)
@@ -99,7 +99,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
}
#endif
#if TERRAIN_USE_PHYSICS_DEBUG
SAFE_DELETE(_debugLines);
SAFE_DELETE_GPU_RESOURCE(_debugLines);
_debugLinesDirty = true;
#endif
#if USE_EDITOR
@@ -117,6 +117,9 @@ TerrainPatch::~TerrainPatch()
SAFE_DELETE(_dataSplatmap[i]);
}
#endif
#if TERRAIN_USE_PHYSICS_DEBUG
SAFE_DELETE_GPU_RESOURCE(_debugLines);
#endif
}
RawDataAsset* TerrainPatch::GetHeightfield() const
@@ -2324,9 +2327,9 @@ void TerrainPatch::DrawPhysicsDebug(RenderView& view)
{
if (!_debugLines || _debugLinesDirty)
CacheDebugLines();
const Transform terrainTransform = _terrain->_transform;
const Transform localTransform(Vector3(0, _yOffset, 0), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
const Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld();
DebugDraw::DrawLines(_debugLines, world);
}
}
@@ -2352,10 +2355,9 @@ const Array<Vector3>& TerrainPatch::GetCollisionTriangles()
#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)
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
const Transform terrainTransform = _terrain->_transform;
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld();
for (int32 row = 0; row < rows - 1; row++)
{
@@ -2401,7 +2403,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array<Vec
// Prepare
const auto& triangles = GetCollisionTriangles();
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
Transform transform;
transform.Translation = _offset + Vector3(0, _yOffset, 0);
transform.Orientation = Quaternion::Identity;
@@ -2507,10 +2509,9 @@ void TerrainPatch::ExtractCollisionGeometry(Array<Float3>& vertexBuffer, Array<i
ScopeLock sceneLock(Level::ScenesLock);
if (_collisionVertices.IsEmpty())
{
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge;
const Transform terrainTransform = _terrain->_transform;
const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge;
const Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Float3(_collisionScaleXZ, _yHeight, _collisionScaleXZ));
const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld();
const Matrix world = localTransform.GetWorld() * _terrain->_transform.GetWorld();
const int32 vertexCount = rows * cols;
_collisionVertices.Resize(vertexCount);
@@ -2569,7 +2570,7 @@ void TerrainPatch::Serialize(SerializeStream& stream, const void* otherObj)
stream.JKEY("Chunks");
stream.StartArray();
for (int32 i = 0; i < Terrain::Terrain::ChunksCount; i++)
for (int32 i = 0; i < Terrain::ChunksCount; i++)
{
stream.StartObject();
Chunks[i].Serialize(stream, other ? &other->Chunks[i] : nullptr);

View File

@@ -0,0 +1,199 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using FlaxEngine.GUI;
namespace FlaxEngine
{
/// <summary>
/// Interface for control references access.
/// </summary>
public interface IControlReference
{
/// <summary>
/// Gets or sets the reference to <see cref="FlaxEngine.UIControl"/> actor.
/// </summary>
public UIControl UIControl { get; set; }
/// <summary>
/// Gets the type of the control the interface uses.
/// </summary>
public Type ControlType { get; }
/// <summary>
/// Sets control ref by force - used during loading when <see cref="UIControl.Control"/> is not loaded yet.
/// </summary>
/// <param name="control">The reference.</param>
internal void Load(UIControl control);
}
/// <summary>
/// UI Control reference utility. References UI Control actor with a typed control type.
/// </summary>
/// <typeparam name="T">Type of the UI control object.</typeparam>
#if FLAX_EDITOR
[TypeConverter(typeof(TypeConverters.ControlReferenceConverter))]
#endif
public struct ControlReference<T> : IControlReference, IComparable, IComparable<ControlReference<T>>, IEquatable<ControlReference<T>> where T : Control
{
private UIControl _uiControl;
/// <summary>
/// Gets the typed UI control object owned by the referenced <see cref="FlaxEngine.UIControl"/> actor.
/// </summary>
[HideInEditor]
public T Control
{
get
{
var control = _uiControl?.Control;
if (control == null)
return null;
if (control is T t)
return t;
Debug.Write(LogType.Warning, $"Trying to get Control from ControlReference but UIControl.Control is not correct type. It should be {typeof(T)} but is {control.GetType()}.");
return null;
}
}
/// <inheritdoc />
public UIControl UIControl
{
get => _uiControl;
set
{
var control = value?.Control;
if (value == null)
{
_uiControl = null;
}
else if (control is T)
{
_uiControl = value;
}
else
{
Debug.Write(LogType.Warning, $"Trying to set UIControl but UIControl.Control is not the correct type. It should be {typeof(T)} but is {control.GetType()}.");
}
}
}
/// <inheritdoc />
public Type ControlType => typeof(T);
/// <inheritdoc />
public void Load(UIControl value)
{
_uiControl = value;
}
/// <inheritdoc />
public override string ToString()
{
return _uiControl?.ToString() ?? "null";
}
/// <inheritdoc />
public override int GetHashCode()
{
return _uiControl?.GetHashCode() ?? 0;
}
/// <inheritdoc />
public int CompareTo(object obj)
{
if (obj is IControlReference other)
return Json.JsonSerializer.SceneObjectEquals(_uiControl, other.UIControl) ? 0 : 1;
return 1;
}
/// <inheritdoc />
public int CompareTo(ControlReference<T> other)
{
return Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl) ? 0 : 1;
}
/// <inheritdoc />
public bool Equals(ControlReference<T> other)
{
return Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is ControlReference<T> other && Json.JsonSerializer.SceneObjectEquals(_uiControl, other._uiControl);
}
/// <summary>
/// The implicit operator for the Control.
/// </summary>
/// <param name="reference">The control reference.</param>
/// <returns>The control object.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T(ControlReference<T> reference) => reference.Control;
/// <summary>
/// The implicit operator for the UIControl.
/// </summary>
/// <param name="reference">The control reference.</param>
/// <returns>The control actor.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator UIControl(ControlReference<T> reference) => reference.UIControl;
/// <summary>
/// Checks if the object exists (reference is not null and the unmanaged object pointer is valid).
/// </summary>
/// <param name="obj">The object to check.</param>
/// <returns>True if object is valid, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator bool(ControlReference<T> obj) => obj._uiControl;
/// <summary>
/// Checks whether the two objects are equal.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(ControlReference<T> left, ControlReference<T> right) => Json.JsonSerializer.SceneObjectEquals(left._uiControl, right._uiControl);
/// <summary>
/// Checks whether the two objects are not equal.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(ControlReference<T> left, ControlReference<T> right) => !Json.JsonSerializer.SceneObjectEquals(left._uiControl, right._uiControl);
}
}
#if FLAX_EDITOR
namespace FlaxEngine.TypeConverters
{
internal class ControlReferenceConverter : TypeConverter
{
/// <inheritdoc />
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is string valueStr)
{
var result = Activator.CreateInstance(destinationType);
if (result is IControlReference control)
{
Json.JsonSerializer.ParseID(valueStr, out var id);
control.Load(Object.Find<UIControl>(ref id));
}
return result;
}
return base.ConvertTo(context, culture, value, destinationType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType.Name.StartsWith("ControlReference", StringComparison.Ordinal))
return true;
return base.CanConvertTo(context, destinationType);
}
}
}
#endif

View File

@@ -35,7 +35,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// The cells heights in container height percentage (from top to bottom). Use negative values to set fixed widths for the cells.
/// </summary>
[EditorOrder(10), Tooltip("The cells heights in container height percentage (from top to bottom). Use negative values to set fixed height for the cells.")]
[EditorOrder(10), Limit(float.MinValue, float.MaxValue, 0.001f), Tooltip("The cells heights in container height percentage (from top to bottom). Use negative values to set fixed height for the cells.")]
public float[] RowFill
{
get => _cellsV;
@@ -49,7 +49,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// The cells heights in container width percentage (from left to right). Use negative values to set fixed heights for the cells.
/// </summary>
[EditorOrder(20), Tooltip("The cells heights in container width percentage (from left to right). Use negative values to set fixed width for the cells.")]
[EditorOrder(20), Limit(float.MinValue, float.MaxValue, 0.001f), Tooltip("The cells heights in container width percentage (from left to right). Use negative values to set fixed width for the cells.")]
public float[] ColumnFill
{
get => _cellsH;