Merge branch 'master' into 1.11

This commit is contained in:
Wojtek Figat
2025-06-05 18:03:17 +02:00
189 changed files with 4627 additions and 1426 deletions

View File

@@ -15,6 +15,8 @@
#if USE_EDITOR
#include "Engine/Engine/Globals.h"
ThreadLocal<bool> ContentDeprecatedFlags;
void ContentDeprecated::Mark()
@@ -593,7 +595,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());

View File

@@ -326,7 +326,7 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
lod.Link(this, lodIndex);
lod.ScreenSize = 1.0f;
const int32 meshesCount = meshesCountPerLod[lodIndex];
if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES)
if (meshesCount < 0 || meshesCount > MODEL_MAX_MESHES)
return true;
lod.Meshes.Resize(meshesCount);
@@ -365,7 +365,7 @@ bool Model::LoadHeader(ReadStream& stream, byte& headerVersion)
// Meshes
uint16 meshesCount;
stream.ReadUint16(&meshesCount);
if (meshesCount == 0 || meshesCount > MODEL_MAX_MESHES)
if (meshesCount > MODEL_MAX_MESHES)
return true;
ASSERT(lodIndex == 0 || LODs[0].Meshes.Count() >= meshesCount);
lod.Meshes.Resize(meshesCount, false);

View File

@@ -669,6 +669,8 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
Array<GPUVertexLayout::Elements, FixedAllocation<MODEL_MAX_VB>> vbElements;
const bool useSeparatePositions = !isSkinned;
const bool useSeparateColors = !isSkinned;
PixelFormat positionsFormat = modelData.PositionFormat == ModelData::PositionFormats::Float32 ? PixelFormat::R32G32B32_Float : PixelFormat::R16G16B16A16_Float;
PixelFormat texCoordsFormat = modelData.TexCoordFormat == ModelData::TexCoordFormats::Float16 ? PixelFormat::R16G16_Float : PixelFormat::R8G8_UNorm;
PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt;
PixelFormat blendWeightsFormat = PixelFormat::R8G8B8A8_UNorm;
for (const Int4& indices : mesh.BlendIndices)
@@ -681,14 +683,13 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
}
{
byte vbIndex = 0;
// TODO: add option to quantize vertex positions (eg. 16-bit)
// TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords)
// Position
if (useSeparatePositions)
{
auto& vb0 = vbElements.AddOne();
vb0.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float });
vb0.Add({ VertexElement::Types::Position, vbIndex, 0, 0, positionsFormat });
vbIndex++;
}
@@ -696,13 +697,13 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
{
auto& vb = vbElements.AddOne();
if (!useSeparatePositions)
vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float });
vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, positionsFormat });
for (int32 channelIdx = 0; channelIdx < mesh.UVs.Count(); channelIdx++)
{
auto& channel = mesh.UVs.Get()[channelIdx];
if (channel.HasItems())
{
vb.Add({ (VertexElement::Types)((int32)VertexElement::Types::TexCoord0 + channelIdx), vbIndex, 0, 0, PixelFormat::R16G16_Float });
vb.Add({ (VertexElement::Types)((int32)VertexElement::Types::TexCoord0 + channelIdx), vbIndex, 0, 0, texCoordsFormat });
}
}
vb.Add({ VertexElement::Types::Normal, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
@@ -737,7 +738,7 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
// Write vertex buffers
for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++)
{
if (useSeparatePositions && vbIndex == 0)
if (useSeparatePositions && vbIndex == 0 && positionsFormat == PixelFormat::R32G32B32_Float)
{
// Fast path for vertex positions of static models using the first buffer
stream.WriteBytes(mesh.Positions.Get(), sizeof(Float3) * vertices);
@@ -755,7 +756,15 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
case VertexElement::Types::Position:
{
const Float3 position = mesh.Positions.Get()[vertex];
stream.Write(position);
if (positionsFormat == PixelFormat::R16G16B16A16_Float)
{
const Half4 positionEnc(Float4(position, 0.0f));
stream.Write(positionEnc);
}
else //if (positionsFormat == PixelFormat::R32G32B32_Float)
{
stream.Write(position);
}
break;
}
case VertexElement::Types::Color:
@@ -821,8 +830,16 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
{
const int32 channelIdx = (int32)element.Type - (int32)VertexElement::Types::TexCoord0;
const Float2 uv = mesh.UVs.Get()[channelIdx].Get()[vertex];
const Half2 uvEnc(uv);
stream.Write(uvEnc);
if (texCoordsFormat == PixelFormat::R8G8_UNorm)
{
stream.Write((uint8)Math::Clamp<int32>((int32)(uv.X * 255), 0, 255));
stream.Write((uint8)Math::Clamp<int32>((int32)(uv.Y * 255), 0, 255));
}
else //if (texCoordsFormat == PixelFormat::R16G16_Float)
{
const Half2 uvEnc(uv);
stream.Write(uvEnc);
}
break;
}
default:

View File

@@ -478,7 +478,7 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
lod._lodIndex = lodIndex;
lod.ScreenSize = 1.0f;
const int32 meshesCount = meshesCountPerLod[lodIndex];
if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES)
if (meshesCount < 0 || meshesCount > MODEL_MAX_MESHES)
return true;
lod.Meshes.Resize(meshesCount);

View File

@@ -9,6 +9,10 @@
#include "Engine/Level/Types.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#if USE_EDITOR
#include "Engine/Core/Collections/HashSet.h"
#include "Engine/Core/Collections/Dictionary.h"
#endif
#include <ThirdParty/rapidjson/document.h>
bool JsonStorageProxy::IsValidExtension(const StringView& extension)
@@ -56,27 +60,31 @@ bool JsonStorageProxy::GetAssetInfo(const StringView& path, Guid& resultId, Stri
#if USE_EDITOR
void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, const StringAnsi& srcId, const StringAnsi& dstId)
void FindObjectIds(const rapidjson_flax::Value& obj, const rapidjson_flax::Document& document, HashSet<Guid>& ids, const char* parentName = nullptr)
{
if (obj.IsObject())
{
for (rapidjson_flax::Value::MemberIterator i = obj.MemberBegin(); i != obj.MemberEnd(); ++i)
for (rapidjson_flax::Value::ConstMemberIterator i = obj.MemberBegin(); i != obj.MemberEnd(); ++i)
{
ChangeIds(i->value, document, srcId, dstId);
FindObjectIds(i->value, document, ids, i->name.GetString());
}
}
else if (obj.IsArray())
{
for (rapidjson::SizeType i = 0; i < obj.Size(); i++)
{
ChangeIds(obj[i], document, srcId, dstId);
FindObjectIds(obj[i], document, ids, parentName);
}
}
else if (obj.IsString())
else if (obj.IsString() && obj.GetStringLength() == 32)
{
if (StringUtils::Compare(srcId.Get(), obj.GetString()) == 0)
if (parentName && StringUtils::Compare(parentName, "ID") == 0)
{
obj.SetString(dstId.Get(), document.GetAllocator());
auto value = JsonTools::GetGuid(obj);
if (value.IsValid())
{
ids.Add(value);
}
}
}
}
@@ -91,9 +99,7 @@ bool JsonStorageProxy::ChangeId(const StringView& path, const Guid& newId)
// Load file
Array<byte> fileData;
if (File::ReadAllBytes(path, fileData))
{
return false;
}
// Parse data
rapidjson_flax::Document document;
@@ -107,33 +113,35 @@ bool JsonStorageProxy::ChangeId(const StringView& path, const Guid& newId)
return false;
}
// Try get asset metadata
// Get all IDs inside the file
HashSet<Guid> ids;
FindObjectIds(document, document, ids);
// Remap into a unique IDs
Dictionary<Guid, Guid> remap;
remap.EnsureCapacity(ids.Count());
for (const auto& id : ids)
remap.Add(id.Item, Guid::New());
// Remap asset ID using the provided value
auto idNode = document.FindMember("ID");
if (idNode == document.MemberEnd())
{
return true;
}
remap[JsonTools::GetGuid(idNode->value)] = newId;
// Change IDs
auto oldIdStr = idNode->value.GetString();
auto newIdStr = newId.ToString(Guid::FormatType::N).ToStringAnsi();
ChangeIds(document, document, oldIdStr, newIdStr);
// Change IDs of asset and objects inside asset
JsonTools::ChangeIds(document, remap);
// Save to file
rapidjson_flax::StringBuffer buffer;
PrettyJsonWriter writer(buffer);
document.Accept(writer.GetWriter());
if (File::WriteAllBytes(path, (byte*)buffer.GetString(), (int32)buffer.GetSize()))
{
return true;
}
return false;
#else
LOG(Warning, "Editing cooked content is invalid.");
return true;
#endif
}

View File

@@ -673,23 +673,23 @@ namespace FlaxEngine
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>
/// <param name="value">The <see cref="Vector4" /> to compare with this instance.</param>
/// <param name="other">The <see cref="Vector4" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref BoundingBox value)
public bool Equals(ref BoundingBox other)
{
return Minimum == value.Minimum && Maximum == value.Maximum;
return Minimum == other.Minimum && Maximum == other.Maximum;
}
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>
/// <param name="value">The <see cref="Vector4" /> to compare with this instance.</param>
/// <param name="other">The <see cref="Vector4" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(BoundingBox value)
public bool Equals(BoundingBox other)
{
return Equals(ref value);
return Equals(ref other);
}
/// <summary>

View File

@@ -487,23 +487,23 @@ namespace FlaxEngine
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>
/// <param name="value">The <see cref="Vector4" /> to compare with this instance.</param>
/// <param name="other">The <see cref="Vector4" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref BoundingSphere value)
public bool Equals(ref BoundingSphere other)
{
return (Center == value.Center) && (Radius == value.Radius);
return Center == other.Center && Radius == other.Radius;
}
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>
/// <param name="value">The <see cref="Vector4" /> to compare with this instance.</param>
/// <param name="other">The <see cref="Vector4" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(BoundingSphere value)
public bool Equals(BoundingSphere other)
{
return Equals(ref value);
return Equals(ref other);
}
/// <summary>

View File

@@ -197,12 +197,9 @@ namespace FlaxEngine
}
/// <inheritdoc />
public override bool Equals(object other)
public override bool Equals(object value)
{
if (!(other is Color))
return false;
var color = (Color)other;
return Equals(ref color);
return value is Color other && Equals(ref other);
}
/// <summary>
@@ -213,7 +210,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Color other)
{
return Mathf.NearEqual(other.R, R) && Mathf.NearEqual(other.G, G) && Mathf.NearEqual(other.B, B) && Mathf.NearEqual(other.A, A);
return R == other.R && G == other.G && B == other.B && A == other.A;
}
/// <inheritdoc />
@@ -661,23 +658,23 @@ namespace FlaxEngine
/// <summary>
/// Compares two colors.
/// </summary>
/// <param name="lhs">The left.</param>
/// <param name="rhs">The right.</param>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>True if colors are equal, otherwise false.</returns>
public static bool operator ==(Color lhs, Color rhs)
public static bool operator ==(Color left, Color right)
{
return lhs.Equals(ref rhs);
return left.Equals(ref right);
}
/// <summary>
/// Compares two colors.
/// </summary>
/// <param name="lhs">The left.</param>
/// <param name="rhs">The right.</param>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>True if colors are not equal, otherwise false.</returns>
public static bool operator !=(Color lhs, Color rhs)
public static bool operator !=(Color left, Color right)
{
return !lhs.Equals(ref rhs);
return !left.Equals(ref right);
}
/// <summary>

View File

@@ -1464,7 +1464,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Double2 left, Double2 right)
{
return Mathd.NearEqual(left.X, right.X) && Mathd.NearEqual(left.Y, right.Y);
return left.Equals(ref right);
}
/// <summary>
@@ -1476,7 +1476,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Double2 left, Double2 right)
{
return !Mathd.NearEqual(left.X, right.X) || !Mathd.NearEqual(left.Y, right.Y);
return !left.Equals(ref right);
}
/// <summary>
@@ -1582,7 +1582,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Double2 other)
{
return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y);
return X == other.X && Y == other.Y;
}
/// <summary>
@@ -1590,7 +1590,7 @@ namespace FlaxEngine
/// </summary>
public static bool Equals(ref Double2 a, ref Double2 b)
{
return Mathd.NearEqual(a.X, b.X) && Mathd.NearEqual(a.Y, b.Y);
return a.Equals(ref b);
}
/// <summary>
@@ -1601,7 +1601,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Double2 other)
{
return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y);
return Equals(ref other);
}
/// <summary>
@@ -1611,7 +1611,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Double2 other && Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y);
return value is Double2 other && Equals(ref other);
}
}
}

View File

@@ -1759,7 +1759,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Double3 left, Double3 right)
{
return Mathd.NearEqual(left.X, right.X) && Mathd.NearEqual(left.Y, right.Y) && Mathd.NearEqual(left.Z, right.Z);
return left.Equals(ref right);
}
/// <summary>
@@ -1771,7 +1771,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Double3 left, Double3 right)
{
return !Mathd.NearEqual(left.X, right.X) || !Mathd.NearEqual(left.Y, right.Y) || !Mathd.NearEqual(left.Z, right.Z);
return !left.Equals(ref right);
}
/// <summary>
@@ -1880,7 +1880,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Double3 other)
{
return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z);
return X == other.X && Y == other.Y && Z == other.Z;
}
/// <summary>
@@ -1891,7 +1891,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Double3 other)
{
return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z);
return Equals(ref other);
}
/// <summary>
@@ -1901,7 +1901,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Double3 other && Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z);
return value is Double3 other && Equals(ref other);
}
}
}

View File

@@ -1258,7 +1258,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Double4 left, Double4 right)
{
return Mathd.NearEqual(left.X, right.X) && Mathd.NearEqual(left.Y, right.Y) && Mathd.NearEqual(left.Z, right.Z) && Mathd.NearEqual(left.W, right.W);
return left.Equals(ref right);
}
/// <summary>
@@ -1379,7 +1379,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="Double4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(ref Double4 other)
{
return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z) && Mathd.NearEqual(other.W, W);
return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
}
/// <summary>
@@ -1390,7 +1390,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Double4 other)
{
return Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z) && Mathd.NearEqual(other.W, W);
return Equals(ref other);
}
/// <summary>
@@ -1400,7 +1400,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Double4 other && Mathd.NearEqual(other.X, X) && Mathd.NearEqual(other.Y, Y) && Mathd.NearEqual(other.Z, Z) && Mathd.NearEqual(other.W, W);
return value is Double4 other && Equals(ref other);
}
}
}

View File

@@ -1540,7 +1540,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Float2 left, Float2 right)
{
return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y);
return left.Equals(ref right);
}
/// <summary>
@@ -1552,7 +1552,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Float2 left, Float2 right)
{
return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y);
return !left.Equals(ref right);
}
/// <summary>
@@ -1658,7 +1658,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Float2 other)
{
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y);
return X == other.X && Y == other.Y;
}
/// <summary>
@@ -1666,7 +1666,7 @@ namespace FlaxEngine
/// </summary>
public static bool Equals(ref Float2 a, ref Float2 b)
{
return Mathf.NearEqual(a.X, b.X) && Mathf.NearEqual(a.Y, b.Y);
return a.Equals(ref b);
}
/// <summary>
@@ -1677,7 +1677,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Float2 other)
{
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y);
return Equals(ref other);
}
/// <summary>
@@ -1687,7 +1687,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Float2 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y);
return value is Float2 other && Equals(ref other);
}
}
}

View File

@@ -1791,7 +1791,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Float3 left, Float3 right)
{
return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z);
return left.Equals(ref right);
}
/// <summary>
@@ -1803,7 +1803,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Float3 left, Float3 right)
{
return !Mathf.NearEqual(left.X, right.X) || !Mathf.NearEqual(left.Y, right.Y) || !Mathf.NearEqual(left.Z, right.Z);
return !left.Equals(ref right);
}
/// <summary>
@@ -1912,7 +1912,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Float3 other)
{
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z);
return X == other.X && Y == other.Y && Z == other.Z;
}
/// <summary>
@@ -1923,7 +1923,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Float3 other)
{
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z);
return Equals(ref other);
}
/// <summary>
@@ -1933,7 +1933,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Float3 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z);
return value is Float3 other && Equals(ref other);
}
}
}

View File

@@ -1288,7 +1288,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Float4 left, Float4 right)
{
return Mathf.NearEqual(left.X, right.X) && Mathf.NearEqual(left.Y, right.Y) && Mathf.NearEqual(left.Z, right.Z) && Mathf.NearEqual(left.W, right.W);
return left.Equals(ref right);
}
/// <summary>
@@ -1419,7 +1419,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="Float4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(ref Float4 other)
{
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W);
return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
}
/// <summary>
@@ -1430,7 +1430,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Float4 other)
{
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W);
return Equals(ref other);
}
/// <summary>
@@ -1440,7 +1440,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Float4 other && Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W);
return value is Float4 other && Equals(ref other);
}
}
}

View File

@@ -163,7 +163,7 @@ bool Matrix::operator==(const Matrix& other) const
{
for (int32 i = 0; i < 16; i++)
{
if (Math::NotNearEqual(other.Raw[i], Raw[i]))
if (other.Raw[i] != Raw[i])
return false;
}
return true;

View File

@@ -3236,22 +3236,22 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="Matrix" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(ref Matrix other)
{
return Mathf.NearEqual(other.M11, M11) &&
Mathf.NearEqual(other.M12, M12) &&
Mathf.NearEqual(other.M13, M13) &&
Mathf.NearEqual(other.M14, M14) &&
Mathf.NearEqual(other.M21, M21) &&
Mathf.NearEqual(other.M22, M22) &&
Mathf.NearEqual(other.M23, M23) &&
Mathf.NearEqual(other.M24, M24) &&
Mathf.NearEqual(other.M31, M31) &&
Mathf.NearEqual(other.M32, M32) &&
Mathf.NearEqual(other.M33, M33) &&
Mathf.NearEqual(other.M34, M34) &&
Mathf.NearEqual(other.M41, M41) &&
Mathf.NearEqual(other.M42, M42) &&
Mathf.NearEqual(other.M43, M43) &&
Mathf.NearEqual(other.M44, M44);
return other.M11 == M11 &&
other.M12 == M12 &&
other.M13 == M13 &&
other.M14 == M14 &&
other.M21 == M21 &&
other.M22 == M22 &&
other.M23 == M23 &&
other.M24 == M24 &&
other.M31 == M31 &&
other.M32 == M32 &&
other.M33 == M33 &&
other.M34 == M34 &&
other.M41 == M41 &&
other.M42 == M42 &&
other.M43 == M43 &&
other.M44 == M44;
}
/// <summary>
@@ -3272,10 +3272,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
if (!(value is Matrix))
return false;
var v = (Matrix)value;
return Equals(ref v);
return value is Matrix other && Equals(ref other);
}
}
}

View File

@@ -483,7 +483,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="Matrix2x2"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(ref Matrix2x2 other)
{
return Mathf.NearEqual(other.M11, M11) && Mathf.NearEqual(other.M12, M12) && Mathf.NearEqual(other.M21, M21) && Mathf.NearEqual(other.M22, M22);
return M11 == other.M11 && M12 == other.M12 && M21 == other.M21 && M22 == other.M22;
}
/// <summary>
@@ -502,7 +502,7 @@ namespace FlaxEngine
/// </summary>
public static bool Equals(ref Matrix2x2 a, ref Matrix2x2 b)
{
return Mathf.NearEqual(a.M11, b.M11) && Mathf.NearEqual(a.M12, b.M12) && Mathf.NearEqual(a.M21, b.M21) && Mathf.NearEqual(a.M22, b.M22);
return a.Equals(ref b);
}
/// <summary>

View File

@@ -242,14 +242,13 @@ void Matrix3x3::Decompose(Float3& scale, Quaternion& rotation) const
bool Matrix3x3::operator==(const Matrix3x3& other) const
{
return
Math::NearEqual(M11, other.M11) &&
Math::NearEqual(M12, other.M12) &&
Math::NearEqual(M13, other.M13) &&
Math::NearEqual(M21, other.M21) &&
Math::NearEqual(M22, other.M22) &&
Math::NearEqual(M23, other.M23) &&
Math::NearEqual(M31, other.M31) &&
Math::NearEqual(M32, other.M32) &&
Math::NearEqual(M33, other.M33);
return M11 == other.M11 &&
M12 == other.M12 &&
M13 == other.M13 &&
M21 == other.M21 &&
M22 == other.M22 &&
M23 == other.M23 &&
M31 == other.M31 &&
M32 == other.M32 &&
M33 == other.M33;
}

View File

@@ -2125,15 +2125,15 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="Matrix3x3"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(ref Matrix3x3 other)
{
return (Mathf.NearEqual(other.M11, M11) &&
Mathf.NearEqual(other.M12, M12) &&
Mathf.NearEqual(other.M13, M13) &&
Mathf.NearEqual(other.M21, M21) &&
Mathf.NearEqual(other.M22, M22) &&
Mathf.NearEqual(other.M23, M23) &&
Mathf.NearEqual(other.M31, M31) &&
Mathf.NearEqual(other.M32, M32) &&
Mathf.NearEqual(other.M33, M33));
return M11 == other.M11 &&
M12 == other.M12 &&
M13 == other.M13 &&
M21 == other.M21 &&
M22 == other.M22 &&
M23 == other.M23 &&
M31 == other.M31 &&
M32 == other.M32 &&
M33 == other.M33;
}
/// <summary>
@@ -2152,17 +2152,7 @@ namespace FlaxEngine
/// </summary>
public static bool Equals(ref Matrix3x3 a, ref Matrix3x3 b)
{
return
Mathf.NearEqual(a.M11, b.M11) &&
Mathf.NearEqual(a.M12, b.M12) &&
Mathf.NearEqual(a.M13, b.M13) &&
Mathf.NearEqual(a.M21, b.M21) &&
Mathf.NearEqual(a.M22, b.M22) &&
Mathf.NearEqual(a.M23, b.M23) &&
Mathf.NearEqual(a.M31, b.M31) &&
Mathf.NearEqual(a.M32, b.M32) &&
Mathf.NearEqual(a.M33, b.M33)
;
return a.Equals(ref b);
}
/// <summary>

View File

@@ -397,7 +397,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref OrientedBoundingBox value)
{
return (Extents == value.Extents) && (Transformation == value.Transformation);
return Extents == value.Extents && Transformation == value.Transformation;
}
/// <summary>

View File

@@ -582,23 +582,23 @@ namespace FlaxEngine
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>
/// <param name="value">The <see cref="Vector4" /> to compare with this instance.</param>
/// <param name="other">The <see cref="Vector4" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Plane value)
public bool Equals(ref Plane other)
{
return Normal == value.Normal && D == value.D;
return Normal == other.Normal && D == other.D;
}
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>
/// <param name="value">The <see cref="Vector4" /> to compare with this instance.</param>
/// <param name="other">The <see cref="Vector4" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Plane value)
public bool Equals(Plane other)
{
return Equals(ref value);
return Equals(ref other);
}
/// <summary>
@@ -608,10 +608,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
if (!(value is Plane))
return false;
var strongValue = (Plane)value;
return Equals(ref strongValue);
return value is Plane other && Equals(ref other);
}
}
}

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>
@@ -1602,7 +1602,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Quaternion left, Quaternion right)
{
return Dot(ref left, ref right) > Tolerance;
return left.Equals(ref right);
}
/// <summary>
@@ -1614,7 +1614,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Quaternion left, Quaternion right)
{
return Dot(ref left, ref right) <= Tolerance;
return !left.Equals(ref right);
}
/// <summary>
@@ -1714,8 +1714,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Quaternion other)
{
//return Dot(ref this, ref other) > Tolerance;
return Mathf.NearEqual(other.X, X) && Mathf.NearEqual(other.Y, Y) && Mathf.NearEqual(other.Z, Z) && Mathf.NearEqual(other.W, W);
return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
}
/// <summary>
@@ -1736,10 +1735,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
if (!(value is Quaternion))
return false;
var strongValue = (Quaternion)value;
return Equals(ref strongValue);
return value is Quaternion other && Equals(ref other);
}
}
}

View File

@@ -348,7 +348,7 @@ public:
/// <returns><c>true</c> if the specified <see cref="Quaternion" /> is equal to this instance; otherwise, <c>false</c>.</returns>
FORCE_INLINE bool operator==(const Quaternion& other) const
{
return Dot(*this, other) > Tolerance;
return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
}
/// <summary>
@@ -358,7 +358,7 @@ public:
/// <returns><c>true</c> if the specified <see cref="Quaternion" /> isn't equal to this instance; otherwise, <c>false</c>.</returns>
FORCE_INLINE bool operator!=(const Quaternion& other) const
{
return Dot(*this, other) < Tolerance;
return X != other.X || Y != other.Y || Z != other.Z || W != other.W;
}
public:

View File

@@ -428,23 +428,23 @@ namespace FlaxEngine
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>
/// <param name="value">The <see cref="Vector4" /> to compare with this instance.</param>
/// <param name="other">The <see cref="Vector4" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Ray value)
public bool Equals(ref Ray other)
{
return (Position == value.Position) && (Direction == value.Direction);
return Position == other.Position && Direction == other.Direction;
}
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>
/// <param name="value">The <see cref="Vector4" /> to compare with this instance.</param>
/// <param name="other">The <see cref="Vector4" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Ray value)
public bool Equals(Ray other)
{
return Equals(ref value);
return Equals(ref other);
}
/// <summary>

View File

@@ -499,21 +499,19 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Rectangle other)
{
return Location.Equals(ref other.Location) && Size.Equals(ref other.Size);
return Location == other.Location && Size == other.Size;
}
/// <inheritdoc />
public bool Equals(Rectangle other)
{
return Location.Equals(ref other.Location) && Size.Equals(ref other.Size);
return Equals(ref other);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is Rectangle && Equals((Rectangle)obj);
return obj is Rectangle other && Equals(ref other);
}
/// <inheritdoc />

View File

@@ -1654,7 +1654,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector2 left, Vector2 right)
{
return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y);
return left.Equals(ref right);
}
/// <summary>
@@ -1666,7 +1666,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Vector2 left, Vector2 right)
{
return !Mathr.NearEqual(left.X, right.X) || !Mathr.NearEqual(left.Y, right.Y);
return !left.Equals(ref right);
}
/// <summary>
@@ -1782,7 +1782,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Vector2 other)
{
return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y);
return X == other.X && Y == other.Y;
}
/// <summary>
@@ -1790,7 +1790,7 @@ namespace FlaxEngine
/// </summary>
public static bool Equals(ref Vector2 a, ref Vector2 b)
{
return Mathr.NearEqual(a.X, b.X) && Mathr.NearEqual(a.Y, b.Y);
return a.Equals(ref b);
}
/// <summary>
@@ -1801,7 +1801,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector2 other)
{
return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y);
return Equals(ref other);
}
/// <summary>
@@ -1811,7 +1811,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Vector2 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y);
return value is Vector2 other && Equals(ref other);
}
}
}

View File

@@ -2010,7 +2010,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector3 left, Vector3 right)
{
return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y) && Mathr.NearEqual(left.Z, right.Z);
return left.Equals(ref right);
}
/// <summary>
@@ -2022,7 +2022,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Vector3 left, Vector3 right)
{
return !Mathr.NearEqual(left.X, right.X) || !Mathr.NearEqual(left.Y, right.Y) || !Mathr.NearEqual(left.Z, right.Z);
return !left.Equals(ref right);
}
/// <summary>
@@ -2141,7 +2141,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref Vector3 other)
{
return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z);
return X == other.X && Y == other.Y && Z == other.Z;
}
/// <summary>
@@ -2152,7 +2152,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector3 other)
{
return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z);
return Equals(ref other);
}
/// <summary>
@@ -2162,7 +2162,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Vector3 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z);
return value is Vector3 other && Equals(ref other);
}
}
}

View File

@@ -1362,7 +1362,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Vector4 left, Vector4 right)
{
return Mathr.NearEqual(left.X, right.X) && Mathr.NearEqual(left.Y, right.Y) && Mathr.NearEqual(left.Z, right.Z) && Mathr.NearEqual(left.W, right.W);
return left.Equals(ref right);
}
/// <summary>
@@ -1493,7 +1493,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="Vector4" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(ref Vector4 other)
{
return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W);
return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
}
/// <summary>
@@ -1504,7 +1504,7 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Vector4 other)
{
return Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W);
return Equals(ref other);
}
/// <summary>
@@ -1514,7 +1514,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object value)
{
return value is Vector4 other && Mathr.NearEqual(other.X, X) && Mathr.NearEqual(other.Y, Y) && Mathr.NearEqual(other.Z, Z) && Mathr.NearEqual(other.W, W);
return value is Vector4 other && Equals(ref other);
}
}
}

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

@@ -1162,9 +1162,9 @@ bool Variant::operator==(const Variant& other) const
case VariantType::Enum:
return AsEnum == other.AsEnum;
case VariantType::Float:
return Math::NearEqual(AsFloat, other.AsFloat);
return AsFloat == other.AsFloat;
case VariantType::Double:
return Math::Abs(AsDouble - other.AsDouble) < ZeroTolerance;
return AsDouble == other.AsDouble;
case VariantType::Pointer:
return AsPointer == other.AsPointer;
case VariantType::String:

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);
@@ -812,6 +822,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);
}
}
@@ -871,8 +882,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());
@@ -920,8 +931,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());
}
@@ -1166,6 +1178,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);
}
@@ -1522,6 +1535,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

@@ -459,6 +459,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

@@ -52,7 +52,7 @@ API_STRUCT(NoPod) struct FLAXENGINE_API FoliageInstance
public:
bool operator==(const FoliageInstance& v) const
{
return Type == v.Type && Math::NearEqual(Random, v.Random) && Transform == v.Transform;
return Type == v.Type && Random == v.Random && Transform == v.Transform;
}
/// <summary>

View File

@@ -428,6 +428,21 @@ public:
/// </summary>
Array<AnimationData> Animations;
public:
// See ModelTool::PositionFormat
enum class PositionFormats
{
Float32,
Float16,
} PositionFormat = PositionFormats::Float32;
// See ModelTool::TexCoordFormats
enum class TexCoordFormats
{
Float16,
UNorm8,
} TexCoordFormat = TexCoordFormats::Float16;
public:
/// <summary>
/// Automatically calculates the screen size for every model LOD for a proper transitions.

View File

@@ -1230,7 +1230,7 @@ void InputService::Update()
{
for (auto i = Axes.Begin(); i.IsNotEnd(); ++i)
{
if (Math::NotNearEqual(i->Value.Value, i->Value.PrevValue))
if (i->Value.Value != i->Value.PrevValue)
{
Input::AxisValueChanged(i->Key);
}

View File

@@ -36,8 +36,6 @@
#define CHECK_EXECUTE_IN_EDITOR
#endif
#define ACTOR_ORIENTATION_EPSILON 0.000000001f
// Start loop over actor children/scripts from the beginning to account for any newly added or removed actors.
#define ACTOR_LOOP_START_MODIFIED_HIERARCHY() _isHierarchyDirty = false
#define ACTOR_LOOP_CHECK_MODIFIED_HIERARCHY() if (_isHierarchyDirty) { _isHierarchyDirty = false; i = -1; }
@@ -660,7 +658,7 @@ void Actor::SetStaticFlags(StaticFlags value)
void Actor::SetTransform(const Transform& value)
{
CHECK(!value.IsNanOrInfinity());
if (!(Vector3::NearEqual(_transform.Translation, value.Translation) && Quaternion::NearEqual(_transform.Orientation, value.Orientation, ACTOR_ORIENTATION_EPSILON) && Float3::NearEqual(_transform.Scale, value.Scale)))
if (_transform.Translation != value.Translation || _transform.Orientation != value.Orientation || _transform.Scale != value.Scale)
{
if (_parent)
_parent->_transform.WorldToLocal(value, _localTransform);
@@ -673,7 +671,7 @@ void Actor::SetTransform(const Transform& value)
void Actor::SetPosition(const Vector3& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Vector3::NearEqual(_transform.Translation, value))
if (_transform.Translation != value)
{
if (_parent)
_localTransform.Translation = _parent->_transform.WorldToLocal(value);
@@ -686,7 +684,7 @@ void Actor::SetPosition(const Vector3& value)
void Actor::SetOrientation(const Quaternion& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Quaternion::NearEqual(_transform.Orientation, value, ACTOR_ORIENTATION_EPSILON))
if (_transform.Orientation != value)
{
if (_parent)
_parent->_transform.WorldToLocal(value, _localTransform.Orientation);
@@ -699,7 +697,7 @@ void Actor::SetOrientation(const Quaternion& value)
void Actor::SetScale(const Float3& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Float3::NearEqual(_transform.Scale, value))
if (_transform.Scale != value)
{
if (_parent)
Float3::Divide(value, _parent->_transform.Scale, _localTransform.Scale);
@@ -748,7 +746,7 @@ void Actor::ResetLocalTransform()
void Actor::SetLocalTransform(const Transform& value)
{
CHECK(!value.IsNanOrInfinity());
if (!(Vector3::NearEqual(_localTransform.Translation, value.Translation) && Quaternion::NearEqual(_localTransform.Orientation, value.Orientation, ACTOR_ORIENTATION_EPSILON) && Float3::NearEqual(_localTransform.Scale, value.Scale)))
if (_localTransform.Translation != value.Translation || _localTransform.Orientation != value.Orientation || _localTransform.Scale != value.Scale)
{
_localTransform = value;
OnTransformChanged();
@@ -758,7 +756,7 @@ void Actor::SetLocalTransform(const Transform& value)
void Actor::SetLocalPosition(const Vector3& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Vector3::NearEqual(_localTransform.Translation, value))
if (_localTransform.Translation != value)
{
_localTransform.Translation = value;
OnTransformChanged();
@@ -770,7 +768,7 @@ void Actor::SetLocalOrientation(const Quaternion& value)
CHECK(!value.IsNanOrInfinity());
Quaternion v = value;
v.Normalize();
if (!Quaternion::NearEqual(_localTransform.Orientation, v, ACTOR_ORIENTATION_EPSILON))
if (_localTransform.Orientation != value)
{
_localTransform.Orientation = v;
OnTransformChanged();
@@ -780,7 +778,7 @@ void Actor::SetLocalOrientation(const Quaternion& value)
void Actor::SetLocalScale(const Float3& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Float3::NearEqual(_localTransform.Scale, value))
if (_localTransform.Scale != value)
{
_localTransform.Scale = value;
OnTransformChanged();

View File

@@ -65,7 +65,7 @@ void BoxBrush::SetMode(BrushMode value)
void BoxBrush::SetCenter(const Vector3& value)
{
if (Vector3::NearEqual(value, _center))
if (value == _center)
return;
_center = value;
@@ -77,7 +77,7 @@ void BoxBrush::SetCenter(const Vector3& value)
void BoxBrush::SetSize(const Vector3& value)
{
if (Vector3::NearEqual(value, _size))
if (value == _size)
return;
_size = value;

View File

@@ -12,7 +12,7 @@ BoxVolume::BoxVolume(const SpawnParams& params)
void BoxVolume::SetSize(const Vector3& value)
{
if (!Vector3::NearEqual(value, _size))
if (value != _size)
{
const auto prevBounds = _box;
_size = value;

View File

@@ -70,7 +70,7 @@ float Camera::GetFieldOfView() const
void Camera::SetFieldOfView(float value)
{
value = Math::Clamp(value, 1.0f, 179.9f);
if (Math::NotNearEqual(_fov, value))
if (_fov != value)
{
_fov = value;
UpdateCache();
@@ -85,7 +85,7 @@ float Camera::GetCustomAspectRatio() const
void Camera::SetCustomAspectRatio(float value)
{
value = Math::Clamp(value, 0.0f, 100.0f);
if (Math::NotNearEqual(_customAspectRatio, value))
if (_customAspectRatio != value)
{
_customAspectRatio = value;
UpdateCache();
@@ -100,7 +100,7 @@ float Camera::GetNearPlane() const
void Camera::SetNearPlane(float value)
{
value = Math::Clamp(value, 0.001f, _far - 1.0f);
if (Math::NotNearEqual(_near, value))
if (_near != value)
{
_near = value;
UpdateCache();
@@ -115,7 +115,7 @@ float Camera::GetFarPlane() const
void Camera::SetFarPlane(float value)
{
value = Math::Max(value, _near + 1.0f);
if (Math::NotNearEqual(_far, value))
if (_far != value)
{
_far = value;
UpdateCache();
@@ -130,7 +130,7 @@ float Camera::GetOrthographicSize() const
void Camera::SetOrthographicSize(float value)
{
value = Math::Clamp(value, 0.0f, 1000000.0f);
if (Math::NotNearEqual(_orthoSize, value))
if (_orthoSize != value)
{
_orthoSize = value;
UpdateCache();
@@ -145,7 +145,7 @@ float Camera::GetOrthographicScale() const
void Camera::SetOrthographicScale(float value)
{
value = Math::Clamp(value, 0.0001f, 1000000.0f);
if (Math::NotNearEqual(_orthoScale, value))
if (_orthoScale != value)
{
_orthoScale = value;
UpdateCache();

View File

@@ -41,7 +41,7 @@ float EnvironmentProbe::GetRadius() const
void EnvironmentProbe::SetRadius(float value)
{
value = Math::Max(0.0f, value);
if (Math::NearEqual(value, _radius))
if (value == _radius)
return;
_radius = value;

View File

@@ -49,7 +49,7 @@ float PointLight::GetScaledRadius() const
void PointLight::SetRadius(float value)
{
value = Math::Max(0.0f, value);
if (Math::NearEqual(value, _radius))
if (value == _radius)
return;
_radius = value;

View File

@@ -26,7 +26,7 @@ SkyLight::SkyLight(const SpawnParams& params)
void SkyLight::SetRadius(float value)
{
value = Math::Max(0.0f, value);
if (Math::NearEqual(value, _radius))
if (value == _radius)
return;
_radius = value;

View File

@@ -59,7 +59,7 @@ float SplineModel::GetQuality() const
void SplineModel::SetQuality(float value)
{
value = Math::Clamp(value, 0.0f, 100.0f);
if (Math::NearEqual(value, _quality))
if (value == _quality)
return;
_quality = value;
OnSplineUpdated();
@@ -72,7 +72,7 @@ float SplineModel::GetBoundsScale() const
void SplineModel::SetBoundsScale(float value)
{
if (Math::NearEqual(_boundsScale, value))
if (_boundsScale == value)
return;
_boundsScale = value;
OnSplineUpdated();

View File

@@ -57,7 +57,7 @@ float SpotLight::GetScaledRadius() const
void SpotLight::SetRadius(float value)
{
value = Math::Max(0.0f, value);
if (Math::NearEqual(value, _radius))
if (value == _radius)
return;
_radius = value;
@@ -70,7 +70,7 @@ void SpotLight::SetOuterConeAngle(float value)
value = Math::Clamp(value, 0.0f, 89.0f);
// Check if value will change
if (!Math::NearEqual(value, _outerConeAngle))
if (value != _outerConeAngle)
{
// Change values
_innerConeAngle = Math::Min(_innerConeAngle, value - ZeroTolerance);
@@ -86,7 +86,7 @@ void SpotLight::SetInnerConeAngle(float value)
value = Math::Clamp(value, 0.0f, 89.0f);
// Check if value will change
if (!Math::NearEqual(value, _innerConeAngle))
if (value != _innerConeAngle)
{
// Change values
_innerConeAngle = value;

View File

@@ -60,7 +60,7 @@ float StaticModel::GetBoundsScale() const
void StaticModel::SetBoundsScale(float value)
{
if (Math::NearEqual(_boundsScale, value))
if (_boundsScale == value)
return;
_boundsScale = value;

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

@@ -293,7 +293,7 @@ void NavMeshRuntime::SetTileSize(float tileSize)
ScopeLock lock(Locker);
// Skip if the same or invalid
if (Math::NearEqual(_tileSize, tileSize) || tileSize < 1)
if (_tileSize == tileSize || tileSize < 1)
return;
// Dispose the existing mesh (its invalid)

View File

@@ -109,7 +109,7 @@ Color NavMeshRuntime::NavAreasColors[64];
bool NavAgentProperties::operator==(const NavAgentProperties& other) const
{
return Math::NearEqual(Radius, other.Radius) && Math::NearEqual(Height, other.Height) && Math::NearEqual(StepHeight, other.StepHeight) && Math::NearEqual(MaxSlopeAngle, other.MaxSlopeAngle) && Math::NearEqual(MaxSpeed, other.MaxSpeed) && Math::NearEqual(CrowdSeparationWeight, other.CrowdSeparationWeight);
return Radius == other.Radius && Height == other.Height && StepHeight == other.StepHeight && MaxSlopeAngle == other.MaxSlopeAngle && MaxSpeed == other.MaxSpeed && CrowdSeparationWeight == other.CrowdSeparationWeight;
}
bool NavAgentMask::IsAgentSupported(int32 agentIndex) const
@@ -150,12 +150,12 @@ bool NavAgentMask::operator==(const NavAgentMask& other) const
bool NavAreaProperties::operator==(const NavAreaProperties& other) const
{
return Name == other.Name && Id == other.Id && Math::NearEqual(Cost, other.Cost);
return Name == other.Name && Id == other.Id && Cost == other.Cost;
}
bool NavMeshProperties::operator==(const NavMeshProperties& other) const
{
return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent && Vector3::NearEqual(DefaultQueryExtent, other.DefaultQueryExtent);
return Name == other.Name && Rotation == other.Rotation && Agent == other.Agent && DefaultQueryExtent == other.DefaultQueryExtent;
}
class NavigationService : public EngineService

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

@@ -340,6 +340,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

@@ -44,7 +44,7 @@ void RigidBody::SetIsKinematic(const bool value)
void RigidBody::SetLinearDamping(float value)
{
if (Math::NearEqual(value, _linearDamping))
if (value == _linearDamping)
return;
_linearDamping = value;
if (_actor)
@@ -53,7 +53,7 @@ void RigidBody::SetLinearDamping(float value)
void RigidBody::SetAngularDamping(float value)
{
if (Math::NearEqual(value, _angularDamping))
if (value == _angularDamping)
return;
_angularDamping = value;
if (_actor)
@@ -108,7 +108,7 @@ void RigidBody::SetUpdateMassWhenScaleChanges(bool value)
void RigidBody::SetMaxAngularVelocity(float value)
{
if (Math::NearEqual(value, _maxAngularVelocity))
if (value == _maxAngularVelocity)
return;
_maxAngularVelocity = value;
if (_actor)
@@ -135,7 +135,7 @@ float RigidBody::GetMass() const
void RigidBody::SetMass(float value)
{
if (Math::NearEqual(value, _mass))
if (value == _mass)
return;
_mass = value;
_overrideMass = true;
@@ -149,7 +149,7 @@ float RigidBody::GetMassScale() const
void RigidBody::SetMassScale(float value)
{
if (Math::NearEqual(value, _massScale))
if (value == _massScale)
return;
_massScale = value;
UpdateMass();
@@ -157,7 +157,7 @@ void RigidBody::SetMassScale(float value)
void RigidBody::SetCenterOfMassOffset(const Float3& value)
{
if (Float3::NearEqual(value, _centerOfMassOffset))
if (value == _centerOfMassOffset)
return;
_centerOfMassOffset = value;
if (_actor)
@@ -380,7 +380,7 @@ void RigidBody::UpdateBounds()
void RigidBody::UpdateScale()
{
const Float3 scale = GetScale();
if (Float3::NearEqual(_cachedScale, scale))
if (_cachedScale == scale)
return;
_cachedScale = scale;

View File

@@ -12,7 +12,7 @@ BoxCollider::BoxCollider(const SpawnParams& params)
void BoxCollider::SetSize(const Float3& value)
{
if (Float3::NearEqual(value, _size))
if (value == _size)
return;
_size = value;
@@ -162,7 +162,7 @@ void BoxCollider::UpdateBounds()
void BoxCollider::GetGeometry(CollisionShape& collision)
{
Float3 size = _size * _cachedScale;
Float3 size = _size * _transform.Scale;
const float minSize = 0.001f;
size = Float3::Max(size.GetAbsolute() * 0.5f, Float3(minSize));
collision.SetBox(size.Raw);

View File

@@ -11,7 +11,7 @@ CapsuleCollider::CapsuleCollider(const SpawnParams& params)
void CapsuleCollider::SetRadius(const float value)
{
if (Math::NearEqual(value, _radius))
if (value == _radius)
return;
_radius = value;
@@ -22,7 +22,7 @@ void CapsuleCollider::SetRadius(const float value)
void CapsuleCollider::SetHeight(const float value)
{
if (Math::NearEqual(value, _height))
if (value == _height)
return;
_height = value;
@@ -43,10 +43,9 @@ void CapsuleCollider::DrawPhysicsDebug(RenderView& view)
return;
Quaternion rotation;
Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation);
const float scaling = _cachedScale.GetAbsolute().MaxValue();
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 float radius = Math::Max(Math::Abs(_radius) * _cachedScale, minSize);
const float height = Math::Max(Math::Abs(_height) * _cachedScale, minSize);
if (view.Mode == ViewMode::PhysicsColliders && !GetIsTrigger())
DEBUG_DRAW_CAPSULE(_transform.LocalToWorld(_center), rotation, radius, height, _staticActor ? Color::CornflowerBlue : Color::Orchid, 0, true);
else
@@ -57,10 +56,9 @@ void CapsuleCollider::OnDebugDrawSelected()
{
Quaternion rotation;
Quaternion::Multiply(_transform.Orientation, Quaternion::Euler(0, 90, 0), rotation);
const float scaling = _cachedScale.GetAbsolute().MaxValue();
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 float radius = Math::Max(Math::Abs(_radius) * _cachedScale, minSize);
const float height = Math::Max(Math::Abs(_height) * _cachedScale, minSize);
const Vector3 position = _transform.LocalToWorld(_center);
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, radius, height, Color::GreenYellow, 0, false);
@@ -92,9 +90,8 @@ void CapsuleCollider::UpdateBounds()
void CapsuleCollider::GetGeometry(CollisionShape& collision)
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
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 float radius = Math::Max(Math::Abs(_radius) * _cachedScale, minSize);
const float height = Math::Max(Math::Abs(_height) * _cachedScale, minSize);
collision.SetCapsule(radius, height * 0.5f);
}

View File

@@ -21,6 +21,7 @@ CharacterController::CharacterController(const SpawnParams& params)
, _upDirection(Vector3::Up)
, _gravityDisplacement(Vector3::Zero)
, _nonWalkableMode(NonWalkableModes::PreventClimbing)
, _originMode(OriginModes::CapsuleCenter)
, _lastFlags(CollisionFlags::None)
{
_contactOffset = 10.0f;
@@ -33,11 +34,9 @@ float CharacterController::GetRadius() const
void CharacterController::SetRadius(const float value)
{
if (Math::NearEqual(value, _radius))
if (value == _radius)
return;
_radius = value;
UpdateSize();
UpdateBounds();
}
@@ -49,11 +48,9 @@ float CharacterController::GetHeight() const
void CharacterController::SetHeight(const float value)
{
if (Math::NearEqual(value, _height))
if (value == _height)
return;
_height = value;
UpdateSize();
UpdateBounds();
}
@@ -66,7 +63,7 @@ float CharacterController::GetSlopeLimit() const
void CharacterController::SetSlopeLimit(float value)
{
value = Math::Clamp(value, 0.0f, 89.0f);
if (Math::NearEqual(value, _slopeLimit))
if (value == _slopeLimit)
return;
_slopeLimit = value;
if (_controller)
@@ -87,6 +84,23 @@ void CharacterController::SetNonWalkableMode(NonWalkableModes value)
PhysicsBackend::SetControllerNonWalkableMode(_controller, (int32)value);
}
CharacterController::OriginModes CharacterController::GetOriginMode() const
{
return _originMode;
}
void CharacterController::SetOriginMode(OriginModes value)
{
if (_originMode == value)
return;
_originMode = value;
if (_controller)
{
DeleteController();
CreateController();
}
}
float CharacterController::GetStepOffset() const
{
return _stepOffset;
@@ -94,17 +108,13 @@ float CharacterController::GetStepOffset() const
void CharacterController::SetStepOffset(float value)
{
if (Math::NearEqual(value, _stepOffset))
if (value == _stepOffset)
return;
_stepOffset = value;
if (_controller)
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float contactOffset = Math::Max(_contactOffset, ZeroTolerance);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
const float radius = Math::Max(Math::Abs(_radius) * scaling - contactOffset, CC_MIN_SIZE);
float height, radius;
GetControllerSize(height, radius);
PhysicsBackend::SetControllerStepOffset(_controller, Math::Min(value, height + radius * 2.0f - CC_MIN_SIZE));
}
}
@@ -169,17 +179,69 @@ CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector
CharacterController::CollisionFlags CharacterController::Move(const Vector3& displacement)
{
CollisionFlags result = CollisionFlags::None;
if (_controller)
if (_controller && !_isUpdatingTransform)
{
// Perform move
const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds();
result = (CollisionFlags)PhysicsBackend::MoveController(_controller, _shape, displacement, _minMoveDistance, deltaTime);
_lastFlags = result;
Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center;
// Update position
Vector3 position;
if (_originMode == OriginModes::Base)
position = PhysicsBackend::GetControllerBasePosition(_controller);
else
position = PhysicsBackend::GetControllerPosition(_controller);
position -= _center;
_isUpdatingTransform = true;
SetPosition(position);
_isUpdatingTransform = false;
}
return result;
}
void CharacterController::Resize(float height, float radius)
{
const float heightDiff = height - _height;
const float radiusDiff = radius - _radius;
if (Math::IsZero(heightDiff) && Math::IsZero(radiusDiff))
return;
_height = height;
_radius = radius;
if (_controller)
{
float centerDiff = heightDiff * 0.5f + radiusDiff;
// Change physics size
GetControllerSize(height, radius);
PhysicsBackend::SetControllerSize(_controller, radius, height);
Vector3 positionDelta = _upDirection * centerDiff;
// Change physics position to maintain feet placement (base)
Vector3 position;
switch (_originMode)
{
case OriginModes::CapsuleCenter:
position = PhysicsBackend::GetControllerPosition(_controller);
position += positionDelta;
_center += positionDelta;
PhysicsBackend::SetControllerPosition(_controller, position);
break;
case OriginModes::Base:
position = PhysicsBackend::GetControllerBasePosition(_controller);
position += positionDelta;
PhysicsBackend::SetControllerBasePosition(_controller, position);
break;
}
// Change actor position
_isUpdatingTransform = true;
SetPosition(position - _center);
_isUpdatingTransform = false;
}
UpdateBounds();
}
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
@@ -187,23 +249,47 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis
void CharacterController::DrawPhysicsDebug(RenderView& view)
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
const Vector3 position = _transform.LocalToWorld(_center);
Quaternion rotation = Quaternion::Euler(90, 0, 0);
const Vector3 position = GetControllerPosition();
if (view.Mode == ViewMode::PhysicsColliders)
DEBUG_DRAW_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::LightYellow, 0, true);
DEBUG_DRAW_CAPSULE(position, rotation, _radius, _height, Color::LightYellow, 0, true);
else
DEBUG_DRAW_WIRE_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow * 0.8f, 0, true);
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow * 0.8f, 0, true);
}
void CharacterController::OnDebugDrawSelected()
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
const Vector3 position = _transform.LocalToWorld(_center);
DEBUG_DRAW_WIRE_CAPSULE(position, Quaternion::Euler(90, 0, 0), radius, height, Color::GreenYellow, 0, false);
Quaternion rotation = Quaternion::Euler(90, 0, 0);
const Vector3 position = GetControllerPosition();
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius, _height, Color::GreenYellow, 0, false);
if (_contactOffset > 0)
DEBUG_DRAW_WIRE_CAPSULE(position, rotation, _radius - _contactOffset, _height, Color::Blue.AlphaMultiplied(0.4f), 0, false);
#if 1
// More technical visuals debugging
if (_controller)
{
float height, radius;
GetControllerSize(height, radius);
Vector3 base = PhysicsBackend::GetControllerBasePosition(_controller);
Vector3 pos = PhysicsBackend::GetControllerPosition(_controller);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(base, 5.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos, 4.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos + Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos - Vector3(0, height * 0.5f, 0), 2.0f), Color::Red, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos + Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(pos - Vector3(0, height * 0.5f, 0), radius), Color::Red.AlphaMultiplied(0.5f), 0, false);
DEBUG_DRAW_WIRE_CYLINDER(pos, Quaternion::Identity, radius, height, Color::Red.AlphaMultiplied(0.2f), 0, false);
}
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(position, 3.0f), Color::GreenYellow, 0, false);
#else
if (_controller)
{
// Physics backend capsule shape
float height, radius;
GetControllerSize(height, radius);
DEBUG_DRAW_WIRE_CAPSULE(PhysicsBackend::GetControllerPosition(_controller), rotation, radius, height, Color::Blue.AlphaMultiplied(0.2f), 0, false);
}
#endif
// Base
Collider::OnDebugDrawSelected();
@@ -215,10 +301,14 @@ void CharacterController::CreateController()
{
// Create controller
ASSERT(_controller == nullptr && _shape == nullptr);
_cachedScale = GetScale();
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const Vector3 position = _transform.LocalToWorld(_center);
_controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape);
_cachedScale = GetScale().GetAbsolute().MaxValue();
float height, radius;
GetControllerSize(height, radius);
Vector3 position = _center;
if (_originMode == OriginModes::Base)
position += _upDirection * (_height * 0.5f + _radius);
position = _transform.LocalToWorld(position);
_controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, radius, height, _stepOffset, _shape);
// Setup
PhysicsBackend::SetControllerUpDirection(_controller, _upDirection);
@@ -241,13 +331,35 @@ void CharacterController::UpdateSize() const
{
if (_controller)
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float radius = Math::Max(Math::Abs(_radius) * scaling - Math::Max(_contactOffset, ZeroTolerance), CC_MIN_SIZE);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
float height, radius;
GetControllerSize(height, radius);
PhysicsBackend::SetControllerSize(_controller, radius, height);
}
}
Vector3 CharacterController::GetControllerPosition() const
{
Vector3 position = _center;
if (_originMode == OriginModes::Base)
position += _upDirection * (_height * 0.5f + _radius);
position = _transform.LocalToWorld(position);
return position;
}
void CharacterController::GetControllerSize(float& height, float& radius) const
{
// Use absolute values including scale
height = Math::Abs(_height) * _cachedScale;
radius = Math::Abs(_radius) * _cachedScale;
// Exclude contact offset around the capsule (otherwise character floats in the air)
radius = radius - Math::Max(_contactOffset, 0.0f);
// Prevent too small controllers
height = Math::Max(height, CC_MIN_SIZE);
radius = Math::Max(radius, CC_MIN_SIZE);
}
void CharacterController::CreateShape()
{
// Not used
@@ -255,10 +367,10 @@ void CharacterController::CreateShape()
void CharacterController::UpdateBounds()
{
const float scaling = GetScale().GetAbsolute().MaxValue();
const float radius = Math::Max(Math::Abs(_radius) * scaling, CC_MIN_SIZE);
const float height = Math::Max(Math::Abs(_height) * scaling, CC_MIN_SIZE);
const Vector3 position = _transform.LocalToWorld(_center);
_cachedScale = GetScale().GetAbsolute().MaxValue();
float height, radius;
GetControllerSize(height, radius);
const Vector3 position = GetControllerPosition();
const Vector3 extent(radius, height * 0.5f + radius, radius);
_box = BoundingBox(position - extent, position + extent);
BoundingSphere::FromBox(_box, _sphere);
@@ -292,6 +404,21 @@ RigidBody* CharacterController::GetAttachedRigidBody() const
return nullptr;
}
void CharacterController::SetCenter(const Vector3& value)
{
if (value == _center)
return;
Vector3 delta = value - _center;
_center = value;
if (_controller)
{
// Change physics position while maintaining actor placement
Vector3 position = PhysicsBackend::GetControllerPosition(_controller);
position += _upDirection * delta;
PhysicsBackend::SetControllerPosition(_controller, position);
}
}
void CharacterController::OnActiveTransformChanged()
{
if (!_shape)
@@ -300,7 +427,12 @@ void CharacterController::OnActiveTransformChanged()
// Change actor transform (but with locking)
ASSERT(!_isUpdatingTransform);
_isUpdatingTransform = true;
const Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center;
Vector3 position;
if (_originMode == OriginModes::Base)
position = PhysicsBackend::GetControllerBasePosition(_controller);
else
position = PhysicsBackend::GetControllerPosition(_controller);
position -= _center;
SetPosition(position);
_isUpdatingTransform = false;
@@ -319,7 +451,7 @@ void CharacterController::UpdateGeometry()
return;
// Setup shape geometry
_cachedScale = GetScale();
_cachedScale = GetScale().GetAbsolute().MaxValue();
UpdateSize();
}
@@ -382,9 +514,12 @@ void CharacterController::OnTransformChanged()
const Vector3 position = _transform.LocalToWorld(_center);
if (!_isUpdatingTransform && _controller)
{
PhysicsBackend::SetControllerPosition(_controller, position);
const Float3 scale = GetScale();
if (!Float3::NearEqual(_cachedScale, scale))
if (_originMode == OriginModes::Base)
PhysicsBackend::SetControllerBasePosition(_controller, position);
else
PhysicsBackend::SetControllerPosition(_controller, position);
const float scale = GetScale().GetAbsolute().MaxValue();
if (_cachedScale != scale)
UpdateGeometry();
UpdateBounds();
}

View File

@@ -41,6 +41,22 @@ public:
Below = 1 << 2,
};
/// <summary>
/// Specifies how a character controller capsule placement.
/// </summary>
API_ENUM() enum class OriginModes
{
/// <summary>
/// Character origin starts at capsule center (including Center offset properly).
/// </summary>
CapsuleCenter,
/// <summary>
/// Character origin starts at capsule base position aka character feet placement.
/// </summary>
Base,
};
/// <summary>
/// Specifies how a character controller interacts with non-walkable parts.
/// </summary>
@@ -69,6 +85,7 @@ private:
Vector3 _upDirection;
Vector3 _gravityDisplacement;
NonWalkableModes _nonWalkableMode;
OriginModes _originMode;
CollisionFlags _lastFlags;
public:
@@ -84,13 +101,13 @@ public:
API_PROPERTY() void SetRadius(float value);
/// <summary>
/// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale.
/// Gets the height of the capsule as a distance between the two sphere centers at the end of the capsule. The capsule height is measured in the object's local space and will be scaled by the actor's world scale.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)")
float GetHeight() const;
/// <summary>
/// Sets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale.
/// Sets the height of the capsule as a distance between the two sphere centers at the end of the capsule. The capsule height is measured in the object's local space and will be scaled by the actor's world scale.
/// </summary>
API_PROPERTY() void SetHeight(float value);
@@ -116,6 +133,17 @@ public:
/// </summary>
API_PROPERTY() void SetNonWalkableMode(NonWalkableModes value);
/// <summary>
/// Gets the position origin placement mode.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(216), DefaultValue(OriginModes.CapsuleCenter), EditorDisplay(\"Character Controller\")")
OriginModes GetOriginMode() const;
/// <summary>
/// Sets the position origin placement mode.
/// </summary>
API_PROPERTY() void SetOriginMode(OriginModes value);
/// <summary>
/// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controllers height or it will generate an error.
/// </summary>
@@ -194,6 +222,13 @@ public:
/// <returns>The collision flags. It can be used to trigger various character animations.</returns>
API_FUNCTION() CollisionFlags Move(const Vector3& displacement);
/// <summary>
/// Updates the character height and center position to ensure its feet position stays the same. This can be used to implement a 'crouch' functionality for example. Maintains the same actor position to stay in the middle of capsule by adjusting center of collider accordingly to height difference.
/// </summary>
/// <param name="height">The height of the capsule, measured in the object's local space.</param>
/// <param name="radius">The radius of the capsule, measured in the object's local space.</param>
API_FUNCTION() void Resize(float height, float radius);
protected:
/// <summary>
/// Creates the physics actor.
@@ -210,6 +245,10 @@ protected:
/// </summary>
void UpdateSize() const;
private:
Vector3 GetControllerPosition() const;
void GetControllerSize(float& height, float& radius) const;
public:
// [Collider]
#if USE_EDITOR
@@ -220,6 +259,7 @@ public:
void AddMovement(const Vector3& translation, const Quaternion& rotation) override;
bool CanAttach(RigidBody* rigidBody) const override;
RigidBody* GetAttachedRigidBody() const override;
void SetCenter(const Vector3& value) override;
// [IPhysicsActor]
void OnActiveTransformChanged() override;

View File

@@ -49,7 +49,7 @@ void Collider::SetIsTrigger(bool value)
void Collider::SetCenter(const Vector3& value)
{
if (Vector3::NearEqual(value, _center))
if (value == _center)
return;
_center = value;
if (_staticActor)
@@ -62,7 +62,7 @@ void Collider::SetCenter(const Vector3& value)
void Collider::SetContactOffset(float value)
{
value = Math::Clamp(value, 0.0f, 100.0f);
if (Math::NearEqual(value, _contactOffset))
if (value == _contactOffset)
return;
_contactOffset = value;
if (_shape)
@@ -205,7 +205,7 @@ void Collider::CreateShape()
ASSERT(_shape == nullptr);
// Setup shape geometry
_cachedScale = GetScale();
_cachedScale = GetScale().GetAbsolute().MaxValue();
CollisionShape shape;
GetGeometry(shape);
@@ -222,7 +222,7 @@ void Collider::UpdateGeometry()
return;
// Setup shape geometry
_cachedScale = GetScale();
_cachedScale = GetScale().GetAbsolute().MaxValue();
CollisionShape shape;
GetGeometry(shape);
@@ -427,8 +427,8 @@ void Collider::OnTransformChanged()
}
}
const Float3 scale = GetScale();
if (!Float3::NearEqual(_cachedScale, scale))
const float scale = GetScale().GetAbsolute().MaxValue();
if (_cachedScale != scale)
UpdateGeometry();
UpdateBounds();
}

View File

@@ -24,7 +24,7 @@ protected:
bool _isTrigger;
void* _shape;
void* _staticActor;
Float3 _cachedScale;
float _cachedScale;
float _contactOffset;
Vector3 _cachedLocalPosePos;
Quaternion _cachedLocalPoseRot;
@@ -61,7 +61,7 @@ public:
/// <summary>
/// Sets the center of the collider, measured in the object's local space.
/// </summary>
API_PROPERTY() void SetCenter(const Vector3& value);
API_PROPERTY() virtual void SetCenter(const Vector3& value);
/// <summary>
/// Gets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.

View File

@@ -131,7 +131,7 @@ void MeshCollider::UpdateBounds()
void MeshCollider::GetGeometry(CollisionShape& collision)
{
// Prepare scale
Float3 scale = _cachedScale;
Float3 scale = _transform.Scale;
const float minSize = 0.001f;
Float3 scaleAbs = scale.GetAbsolute();
if (scaleAbs.X < minSize)

View File

@@ -10,7 +10,7 @@ SphereCollider::SphereCollider(const SpawnParams& params)
void SphereCollider::SetRadius(const float value)
{
if (Math::NearEqual(value, _radius))
if (value == _radius)
return;
_radius = value;
@@ -67,8 +67,7 @@ void SphereCollider::UpdateBounds()
void SphereCollider::GetGeometry(CollisionShape& collision)
{
const float scaling = _cachedScale.GetAbsolute().MaxValue();
const float radius = Math::Abs(_radius) * scaling;
const float radius = Math::Abs(_radius) * _cachedScale;
const float minSize = 0.001f;
collision.SetSphere(Math::Max(radius, minSize));
}

View File

@@ -259,8 +259,7 @@ void SplineCollider::GetGeometry(CollisionShape& collision)
}
// Prepare scale
Float3 scale = _cachedScale;
scale = Float3::Max(scale.GetAbsolute(), minSize);
Float3 scale = Float3::Max(_transform.Scale.GetAbsolute(), minSize);
// TODO: add support for cooking collision for static splines in editor and reusing it in game

View File

@@ -25,7 +25,7 @@ void DistanceJoint::SetFlags(DistanceJointFlag value)
void DistanceJoint::SetMinDistance(float value)
{
value = Math::Clamp(value, 0.0f, _maxDistance);
if (Math::NearEqual(value, _minDistance))
if (value == _minDistance)
return;
_minDistance = value;
if (_joint)
@@ -35,7 +35,7 @@ void DistanceJoint::SetMinDistance(float value)
void DistanceJoint::SetMaxDistance(float value)
{
value = Math::Max(_minDistance, value);
if (Math::NearEqual(value, _maxDistance))
if (value == _maxDistance)
return;
_maxDistance = value;
if (_joint)
@@ -45,7 +45,7 @@ void DistanceJoint::SetMaxDistance(float value)
void DistanceJoint::SetTolerance(float value)
{
value = Math::Max(0.1f, value);
if (Math::NearEqual(value, _tolerance))
if (value == _tolerance)
return;
_tolerance = value;
if (_joint)

View File

@@ -59,7 +59,7 @@ API_STRUCT() struct HingeJointDrive
public:
bool operator==(const HingeJointDrive& other) const
{
return Math::NearEqual(Velocity, other.Velocity) && Math::NearEqual(ForceLimit, other.ForceLimit) && Math::NearEqual(GearRatio, other.GearRatio) && FreeSpin == other.FreeSpin;
return Velocity == other.Velocity && ForceLimit == other.ForceLimit && GearRatio == other.GearRatio && FreeSpin == other.FreeSpin;
}
};

View File

@@ -24,7 +24,7 @@ Joint::Joint(const SpawnParams& params)
void Joint::SetBreakForce(float value)
{
if (Math::NearEqual(value, _breakForce))
if (value == _breakForce)
return;
_breakForce = value;
if (_joint)
@@ -33,7 +33,7 @@ void Joint::SetBreakForce(float value)
void Joint::SetBreakTorque(float value)
{
if (Math::NearEqual(value, _breakTorque))
if (value == _breakTorque)
return;
_breakTorque = value;
if (_joint)
@@ -61,7 +61,7 @@ void Joint::SetEnableAutoAnchor(bool value)
void Joint::SetTargetAnchor(const Vector3& value)
{
if (Vector3::NearEqual(value, _targetAnchor))
if (value == _targetAnchor)
return;
_targetAnchor = value;
if (_joint && !_enableAutoAnchor)

View File

@@ -3160,10 +3160,9 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic
desc.material = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial();
else
desc.material = DefaultMaterial;
const float minSize = 0.001f;
desc.height = Math::Max(height, minSize);
desc.radius = Math::Max(radius - Math::Max(contactOffset, 0.0f), minSize);
desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize);
desc.height = height;
desc.radius = radius;
desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - 0.001f);
auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc);
PxRigidActor* actorPhysX = controllerPhysX->getActor();
ASSERT(actorPhysX && actorPhysX->getNbShapes() == 1);
@@ -3188,7 +3187,7 @@ void PhysicsBackend::SetControllerSize(void* controller, float radius, float hei
{
auto controllerPhysX = (PxCapsuleController*)controller;
controllerPhysX->setRadius(radius);
controllerPhysX->resize(height);
controllerPhysX->setHeight(height);
}
void PhysicsBackend::SetControllerSlopeLimit(void* controller, float value)
@@ -3209,6 +3208,20 @@ void PhysicsBackend::SetControllerStepOffset(void* controller, float value)
controllerPhysX->setStepOffset(value);
}
Vector3 PhysicsBackend::GetControllerBasePosition(void* controller)
{
auto controllerPhysX = (PxCapsuleController*)controller;
const Vector3 origin = SceneOrigins[controllerPhysX->getScene()];
return P2C(controllerPhysX->getFootPosition()) + origin;
}
void PhysicsBackend::SetControllerBasePosition(void* controller, const Vector3& value)
{
auto controllerPhysX = (PxCapsuleController*)controller;
const Vector3 sceneOrigin = SceneOrigins[controllerPhysX->getScene()];
controllerPhysX->setFootPosition(PxExtendedVec3(value.X - sceneOrigin.X, value.Y - sceneOrigin.Y, value.Z - sceneOrigin.Z));
}
Vector3 PhysicsBackend::GetControllerUpDirection(void* controller)
{
auto controllerPhysX = (PxCapsuleController*)controller;

View File

@@ -248,6 +248,8 @@ public:
static void SetControllerSlopeLimit(void* controller, float value);
static void SetControllerNonWalkableMode(void* controller, int32 value);
static void SetControllerStepOffset(void* controller, float value);
static Vector3 GetControllerBasePosition(void* controller);
static void SetControllerBasePosition(void* controller, const Vector3& value);
static Vector3 GetControllerUpDirection(void* controller);
static void SetControllerUpDirection(void* controller, const Vector3& value);
static Vector3 GetControllerPosition(void* controller);

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

@@ -42,17 +42,17 @@ void* WindowsPlatform::Instance = nullptr;
extern "C" {
static HANDLE dbgHelpLock;
void DbgHelpInit()
void FlaxDbgHelpInit()
{
dbgHelpLock = CreateMutexW(nullptr, FALSE, nullptr);
}
void DbgHelpLock()
void FlaxDbgHelpLock()
{
WaitForSingleObject(dbgHelpLock, INFINITE);
}
void DbgHelpUnlock()
void FlaxDbgHelpUnlock()
{
ReleaseMutex(dbgHelpLock);
}
@@ -544,7 +544,7 @@ void WindowsPlatform::PreInit(void* hInstance)
#if CRASH_LOG_ENABLE
TCHAR buffer[MAX_PATH] = { 0 };
DbgHelpLock();
FlaxDbgHelpLock();
if (::GetModuleFileNameW(::GetModuleHandleW(nullptr), buffer, MAX_PATH))
SymbolsPath.Add(StringUtils::GetDirectoryName(buffer));
if (::GetEnvironmentVariableW(TEXT("_NT_SYMBOL_PATH"), buffer, MAX_PATH))
@@ -553,7 +553,7 @@ void WindowsPlatform::PreInit(void* hInstance)
options |= SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS;
SymSetOptions(options);
OnSymbolsPathModified();
DbgHelpUnlock();
FlaxDbgHelpUnlock();
#endif
GetWindowsVersion(WindowsName, VersionMajor, VersionMinor, VersionBuild);
@@ -767,7 +767,7 @@ void WindowsPlatform::BeforeExit()
void WindowsPlatform::Exit()
{
#if CRASH_LOG_ENABLE
DbgHelpLock();
FlaxDbgHelpLock();
#if !TRACY_ENABLE
if (SymInitialized)
{
@@ -776,7 +776,7 @@ void WindowsPlatform::Exit()
}
#endif
SymbolsPath.Resize(0);
DbgHelpUnlock();
FlaxDbgHelpUnlock();
#endif
// Unregister app class
@@ -1278,14 +1278,14 @@ void* WindowsPlatform::LoadLibrary(const Char* filename)
#if CRASH_LOG_ENABLE
// Refresh modules info during next stack trace collecting to have valid debug symbols information
DbgHelpLock();
FlaxDbgHelpLock();
if (folder.HasChars() && !SymbolsPath.Contains(folder))
{
SymbolsPath.Add(folder);
SymbolsPath.Last().Replace('/', '\\');
OnSymbolsPathModified();
}
DbgHelpUnlock();
FlaxDbgHelpUnlock();
#endif
return handle;
@@ -1296,7 +1296,7 @@ void* WindowsPlatform::LoadLibrary(const Char* filename)
Array<PlatformBase::StackFrame> WindowsPlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context)
{
Array<StackFrame> result;
DbgHelpLock();
FlaxDbgHelpLock();
// Initialize
HANDLE process = GetCurrentProcess();
@@ -1428,7 +1428,7 @@ Array<PlatformBase::StackFrame> WindowsPlatform::GetStackFrames(int32 skipCount,
}
}
DbgHelpUnlock();
FlaxDbgHelpUnlock();
return result;
}

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

@@ -240,6 +240,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);
@@ -260,7 +261,7 @@ void Terrain::SetScaleInLightmap(float value)
void Terrain::SetBoundsExtent(const Vector3& value)
{
if (Vector3::NearEqual(_boundsExtent, value))
if (_boundsExtent == value)
return;
_boundsExtent = value;
@@ -891,7 +892,7 @@ void Terrain::OnTransformChanged()
auto patch = _patches[i];
patch->UpdateTransform();
}
if (!Float3::NearEqual(_cachedScale, _transform.Scale))
if (_cachedScale != _transform.Scale)
{
_cachedScale = _transform.Scale;
for (int32 i = 0; i < _patches.Count(); i++)

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

@@ -11,13 +11,13 @@ namespace FlaxEngine.Tests
[Test]
public void TestConversion()
{
Assert.AreEqual(Float4.Zero, new FloatR10G10B10A2(Float4.Zero).ToFloat4());
Assert.AreEqual(Float4.One, new FloatR10G10B10A2(Float4.One).ToFloat4());
Assert.AreEqual(new Float4(0.5004888f, 0.5004888f, 0.5004888f, 0.666667f), new FloatR10G10B10A2(new Float4(0.5f)).ToFloat4());
Assert.AreEqual(new Float4(1, 0, 0, 0), new FloatR10G10B10A2(new Float4(1, 0, 0, 0)).ToFloat4());
Assert.AreEqual(new Float4(0, 1, 0, 0), new FloatR10G10B10A2(new Float4(0, 1, 0, 0)).ToFloat4());
Assert.AreEqual(new Float4(0, 0, 1, 0), new FloatR10G10B10A2(new Float4(0, 0, 1, 0)).ToFloat4());
Assert.AreEqual(new Float4(0, 0, 0, 1), new FloatR10G10B10A2(new Float4(0, 0, 0, 1)).ToFloat4());
Assert.IsTrue(Float4.NearEqual(Float4.Zero, new FloatR10G10B10A2(Float4.Zero).ToFloat4()));
Assert.IsTrue(Float4.NearEqual(Float4.One, new FloatR10G10B10A2(Float4.One).ToFloat4()));
Assert.IsTrue(Float4.NearEqual(new Float4(0.5004888f, 0.5004888f, 0.5004888f, 0.666667f), new FloatR10G10B10A2(new Float4(0.5f)).ToFloat4()));
Assert.IsTrue(Float4.NearEqual(new Float4(1, 0, 0, 0), new FloatR10G10B10A2(new Float4(1, 0, 0, 0)).ToFloat4()));
Assert.IsTrue(Float4.NearEqual(new Float4(0, 1, 0, 0), new FloatR10G10B10A2(new Float4(0, 1, 0, 0)).ToFloat4()));
Assert.IsTrue(Float4.NearEqual(new Float4(0, 0, 1, 0), new FloatR10G10B10A2(new Float4(0, 0, 1, 0)).ToFloat4()));
Assert.IsTrue(Float4.NearEqual(new Float4(0, 0, 0, 1), new FloatR10G10B10A2(new Float4(0, 0, 0, 1)).ToFloat4()));
}
}
}

View File

@@ -11,13 +11,13 @@ namespace FlaxEngine.Tests
[Test]
public void TestConversion()
{
Assert.AreEqual(Float3.Zero, new FloatR11G11B10(Float3.Zero).ToFloat3());
Assert.AreEqual(Float3.One, new FloatR11G11B10(Float3.One).ToFloat3());
Assert.AreEqual(new Float3(0.5f, 0.5f, 0.5f), new FloatR11G11B10(new Float3(0.5f)).ToFloat3());
Assert.AreEqual(new Float3(1, 0, 0), new FloatR11G11B10(new Float3(1, 0, 0)).ToFloat3());
Assert.AreEqual(new Float3(0, 1, 0), new FloatR11G11B10(new Float3(0, 1, 0)).ToFloat3());
Assert.AreEqual(new Float3(0, 0, 1), new FloatR11G11B10(new Float3(0, 0, 1)).ToFloat3());
Assert.AreEqual(new Float3(10, 11, 12), new FloatR11G11B10(new Float3(10, 11, 12)).ToFloat3());
Assert.IsTrue(Float3.NearEqual(Float3.Zero, new FloatR11G11B10(Float3.Zero).ToFloat3()));
Assert.IsTrue(Float3.NearEqual(Float3.One, new FloatR11G11B10(Float3.One).ToFloat3()));
Assert.IsTrue(Float3.NearEqual(new Float3(0.5f, 0.5f, 0.5f), new FloatR11G11B10(new Float3(0.5f)).ToFloat3()));
Assert.IsTrue(Float3.NearEqual(new Float3(1, 0, 0), new FloatR11G11B10(new Float3(1, 0, 0)).ToFloat3()));
Assert.IsTrue(Float3.NearEqual(new Float3(0, 1, 0), new FloatR11G11B10(new Float3(0, 1, 0)).ToFloat3()));
Assert.IsTrue(Float3.NearEqual(new Float3(0, 0, 1), new FloatR11G11B10(new Float3(0, 0, 1)).ToFloat3()));
Assert.IsTrue(Float3.NearEqual(new Float3(10, 11, 12), new FloatR11G11B10(new Float3(10, 11, 12)).ToFloat3()));
}
}
}

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

@@ -17,10 +17,10 @@ namespace FlaxEngine.Tests
[Test]
public void TestEuler()
{
Assert.AreEqual(Quaternion.Euler(90, 0, 0), new Quaternion(0.7071068f, 0, 0, 0.7071068f));
Assert.AreEqual(Quaternion.Euler(25, 0, 10), new Quaternion(0.215616f, -0.018864f, 0.0850898f, 0.9725809f));
Assert.AreEqual(new Float3(25, 0, 10), Quaternion.Euler(25, 0, 10).EulerAngles);
Assert.AreEqual(new Float3(25, -5, 10), Quaternion.Euler(25, -5, 10).EulerAngles);
Assert.IsTrue(Quaternion.NearEqual(Quaternion.Euler(90, 0, 0), new Quaternion(0.7071068f, 0, 0, 0.7071068f)));
Assert.IsTrue(Quaternion.NearEqual(Quaternion.Euler(25, 0, 10), new Quaternion(0.215616f, -0.018864f, 0.0850898f, 0.9725809f)));
Assert.IsTrue(Float3.NearEqual(new Float3(25, 0, 10), Quaternion.Euler(25, 0, 10).EulerAngles, 0.00001f));
Assert.IsTrue(Float3.NearEqual(new Float3(25, -5, 10), Quaternion.Euler(25, -5, 10).EulerAngles, 0.00001f));
}
/// <summary>
@@ -33,7 +33,7 @@ namespace FlaxEngine.Tests
var delta = Quaternion.Euler(0, 10, 0);
for (int i = 0; i < 9; i++)
q *= delta;
Assert.AreEqual(Quaternion.Euler(0, 90, 0), q);
Assert.IsTrue(Quaternion.NearEqual(Quaternion.Euler(0, 90, 0), q));
}
}
}

View File

@@ -68,9 +68,9 @@ namespace FlaxEngine.Tests
t1.LocalToWorld(new Vector3[1] { t2.Translation }, a4T);
Vector3 a4 = a4T[0];
Assert.AreEqual(a1.Translation, a2);
Assert.AreEqual(a2, a3);
Assert.AreEqual(a2, a4);
Assert.IsTrue(Vector3.NearEqual(a1.Translation, a2));
Assert.IsTrue(Vector3.NearEqual(a2, a3));
Assert.IsTrue(Vector3.NearEqual(a2, a4));
}
/// <summary>
@@ -100,9 +100,9 @@ namespace FlaxEngine.Tests
t1.WorldToLocal(new Vector3[1] { t2.Translation }, a4T);
Float3 a4 = a4T[0];
Assert.AreEqual((Float3)a1.Translation, a2);
Assert.AreEqual(a2, a3);
Assert.AreEqual(a2, a4);
Assert.IsTrue(Float3.NearEqual((Float3)a1.Translation, a2));
Assert.IsTrue(Float3.NearEqual(a2, a3, 0.0001f));
Assert.IsTrue(Float3.NearEqual(a2, a4));
}
/// <summary>
@@ -113,28 +113,28 @@ namespace FlaxEngine.Tests
{
Transform trans = new Transform(new Vector3(1, 2, 3));
Assert.AreEqual(new Float3(1, 2, 3), (Float3)trans.LocalToWorld(new Float3(0, 0, 0)));
Assert.AreEqual(new Float3(4, 4, 4), (Float3)trans.LocalToWorld(new Float3(3, 2, 1)));
Assert.AreEqual(new Float3(-1, -2, -3), (Float3)trans.WorldToLocal(new Float3(0, 0, 0)));
Assert.AreEqual(new Float3(0, 0, 0), (Float3)trans.WorldToLocal(new Float3(1, 2, 3)));
Assert.IsTrue(Float3.NearEqual(new Float3(1, 2, 3), (Float3)trans.LocalToWorld(new Float3(0, 0, 0))));
Assert.IsTrue(Float3.NearEqual(new Float3(4, 4, 4), (Float3)trans.LocalToWorld(new Float3(3, 2, 1))));
Assert.IsTrue(Float3.NearEqual(new Float3(-1, -2, -3), (Float3)trans.WorldToLocal(new Float3(0, 0, 0))));
Assert.IsTrue(Float3.NearEqual(new Float3(0, 0, 0), (Float3)trans.WorldToLocal(new Float3(1, 2, 3))));
trans = new Transform(Vector3.Zero, Quaternion.Euler(0, 90, 0));
Assert.AreEqual(new Float3(0, 2, -1), (Float3)trans.LocalToWorld(new Float3(1, 2, 0)));
Assert.IsTrue(Float3.NearEqual(new Float3(0, 2, -1), (Float3)trans.LocalToWorld(new Float3(1, 2, 0))));
trans.Translation = new Vector3(1, 0, 0);
trans.Orientation = Quaternion.RotationX((float)Math.PI * 0.5f);
trans.Scale = new Vector3(2, 2, 2);
Assert.AreEqual(new Float3(1, 0, 2), (Float3)trans.LocalToWorld(new Float3(0, 1, 0)));
Assert.IsTrue(Float3.NearEqual(new Float3(1, 0, 2), (Float3)trans.LocalToWorld(new Float3(0, 1, 0))));
Transform t1 = trans.LocalToWorld(Transform.Identity);
Assert.AreEqual(new Float3(1.0f, 0, 0), (Float3)t1.Translation);
Assert.AreEqual(Quaternion.RotationX((float)Math.PI * 0.5f), t1.Orientation);
Assert.AreEqual(new Float3(2.0f, 2.0f, 2.0f), t1.Scale);
Assert.IsTrue(Float3.NearEqual(new Float3(1.0f, 0, 0), (Float3)t1.Translation));
Assert.IsTrue(Quaternion.NearEqual(Quaternion.RotationX((float)Math.PI * 0.5f), t1.Orientation));
Assert.IsTrue(Float3.NearEqual(new Float3(2.0f, 2.0f, 2.0f), t1.Scale));
Transform t2 = trans.WorldToLocal(Transform.Identity);
Assert.AreEqual(new Float3(-0.5f, 0, 0), (Float3)t2.Translation);
Assert.AreEqual(Quaternion.RotationX((float)Math.PI * -0.5f), t2.Orientation);
Assert.AreEqual(new Float3(0.5f, 0.5f, 0.5f), t2.Scale);
Assert.IsTrue(Float3.NearEqual(new Float3(-0.5f, 0, 0), (Float3)t2.Translation));
Assert.IsTrue(Quaternion.NearEqual(Quaternion.RotationX((float)Math.PI * -0.5f), t2.Orientation));
Assert.IsTrue(Float3.NearEqual(new Float3(0.5f, 0.5f, 0.5f), t2.Scale));
var rand = new Random(10);
for (int i = 0; i < 10; i++)

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);

View File

@@ -676,6 +676,9 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(CalculateBoneOffsetMatrices);
SERIALIZE(LightmapUVsSource);
SERIALIZE(CollisionMeshesPrefix);
SERIALIZE(CollisionType);
SERIALIZE(PositionFormat);
SERIALIZE(TexCoordFormat);
SERIALIZE(Scale);
SERIALIZE(Rotation);
SERIALIZE(Translation);
@@ -727,6 +730,9 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(CalculateBoneOffsetMatrices);
DESERIALIZE(LightmapUVsSource);
DESERIALIZE(CollisionMeshesPrefix);
DESERIALIZE(CollisionType);
DESERIALIZE(PositionFormat);
DESERIALIZE(TexCoordFormat);
DESERIALIZE(Scale);
DESERIALIZE(Rotation);
DESERIALIZE(Translation);
@@ -1137,6 +1143,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
if (ImportData(path, data, options, errorMsg))
return true;
// Copy over data format options
data.PositionFormat = (ModelData::PositionFormats)options.PositionFormat;
data.TexCoordFormat = (ModelData::TexCoordFormats)options.TexCoordFormat;
// Validate result data
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry))
{
@@ -1921,9 +1931,43 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
auto mesh = lod.Meshes[i];
if (mesh->Name.StartsWith(options.CollisionMeshesPrefix, StringSearchCase::IgnoreCase))
{
// Remove material slot used by this mesh (if no other mesh else uses it)
int32 materialSlotUsageCount = 0;
for (const auto& e : data.LODs)
{
for (const MeshData* q : e.Meshes)
{
if (q->MaterialSlotIndex == mesh->MaterialSlotIndex)
materialSlotUsageCount++;
}
}
if (materialSlotUsageCount == 1)
{
data.Materials.RemoveAt(mesh->MaterialSlotIndex);
// Fixup linkage of other meshes to materials
for (auto& e : data.LODs)
{
for (MeshData* q : e.Meshes)
{
if (q->MaterialSlotIndex > mesh->MaterialSlotIndex)
{
q->MaterialSlotIndex--;
}
}
}
}
// Remove data linkage
mesh->NodeIndex = 0;
mesh->MaterialSlotIndex = 0;
// Add mesh to collision
if (collisionModel.LODs.Count() == 0)
collisionModel.LODs.AddOne();
collisionModel.LODs[0].Meshes.Add(mesh);
// Remove mesh from model
lod.Meshes.RemoveAtKeepOrder(i);
if (lod.Meshes.IsEmpty())
break;

View File

@@ -142,6 +142,28 @@ public:
ExtractCenterOfMass = 2,
};
/// <summary>
/// Declares the imported vertex positions data formats.
/// </summary>
API_ENUM(Attributes="HideInEditor") enum class PositionFormat
{
// XYZ channels with 32-bit precision (12 bytes per vertex).
Float32,
// XYZ(W) channels with 12-bit precision (8 bytes per vertex).
Float16,
};
/// <summary>
/// Declares the imported vertex texture coordinates data formats.
/// </summary>
API_ENUM(Attributes="HideInEditor") enum class TexCoordFormats
{
// XY channels with 16-bit precision (4 bytes per vertex).
Float16,
// XY channels with 8-bit precision (2 bytes per vertex). Valid only for normalized UVs within range [0; 1], scaled or negative UVs won't work.
UNorm8,
};
/// <summary>
/// Model import options.
/// </summary>
@@ -201,6 +223,14 @@ public:
API_FIELD(Attributes="EditorOrder(105), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
CollisionDataType CollisionType = CollisionDataType::ConvexMesh;
public:
// The imported vertex positions data format to use by meshes. Changing this affects memory usage of the mesh data, performance and overall quality.
API_FIELD(Attributes = "EditorOrder(200), EditorDisplay(\"Data Format\"), VisibleIf(nameof(ShowGeometry))")
PositionFormat PositionFormat = PositionFormat::Float32;
// The imported vertex texture coordinates data format to use by meshes. Changing this affects memory usage of the mesh data, performance and overall quality.
API_FIELD(Attributes = "EditorOrder(205), EditorDisplay(\"Data Format\"), VisibleIf(nameof(ShowGeometry))")
TexCoordFormats TexCoordFormat = TexCoordFormats::Float16;
public: // Transform
// Custom uniform import scale.

View File

@@ -298,9 +298,13 @@ namespace FlaxEngine.GUI
color *= 0.85f;
Render2D.DrawText(font, text, color, ref _layout, TextMaterial);
}
else if (!string.IsNullOrEmpty(_watermarkText))
else
{
Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial);
text = _watermarkText;
if (text?.Length > 0)
{
Render2D.DrawText(font, _watermarkText, WatermarkTextColor, ref _layout, TextMaterial);
}
}
// Caret

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);

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

@@ -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;
}