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

This commit is contained in:
Wojtek Figat
2024-08-05 11:08:51 +02:00
62 changed files with 9529 additions and 2700 deletions

View File

@@ -83,13 +83,13 @@ public:
/// Checks if knowledge has a given goal (exact type match without base class check).
/// </summary>
/// <param name="type">The goal type.</param>
/// <returns>True if has a given goal, otherwise false.</returns>
/// <returns>True if knowledge has a given goal, otherwise false.</returns>
API_FUNCTION() bool HasGoal(ScriptingTypeHandle type) const;
/// <summary>
/// Checks if knowledge has a given goal (exact type match without base class check).
/// </summary>
/// <returns>True if has a given goal, otherwise false.</returns>
/// <returns>True if knowledge has a given goal, otherwise false.</returns>
template<typename T>
FORCE_INLINE bool HasGoal()
{

View File

@@ -140,7 +140,7 @@ void BehaviorTreeNode::Deserialize(DeserializeStream& stream, ISerializeModifier
{
SerializableScriptingObject::Deserialize(stream, modifier);
Name.Clear(); // Missing Name is assumes as unnamed node
Name.Clear(); // Missing Name is assumed as unnamed node
DESERIALIZE(Name);
}
@@ -197,7 +197,6 @@ BehaviorUpdateResult BehaviorTreeSequenceNode::Update(const BehaviorUpdateContex
return BehaviorUpdateResult::Failed;
auto result = Children[state->CurrentChildIndex]->InvokeUpdate(context);
switch (result)
{
case BehaviorUpdateResult::Success:
@@ -232,7 +231,6 @@ BehaviorUpdateResult BehaviorTreeSelectorNode::Update(const BehaviorUpdateContex
return BehaviorUpdateResult::Failed;
auto result = Children[state->CurrentChildIndex]->InvokeUpdate(context);
switch (result)
{
case BehaviorUpdateResult::Success:

View File

@@ -1522,12 +1522,16 @@ void VisualScript::unload(bool isReloading)
if (_scriptingTypeHandle)
{
VisualScriptingBinaryModule::Locker.Lock();
auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex];
ScriptingType& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex];
if (type.Script.DefaultInstance)
{
Delete(type.Script.DefaultInstance);
type.Script.DefaultInstance = nullptr;
}
char* typeName = (char*)Allocator::Allocate(sizeof(_typenameChars));
Platform::MemoryCopy(typeName, _typenameChars, sizeof(_typenameChars));
((StringAnsiView&)type.Fullname) = StringAnsiView(typeName, 32);
VisualScriptingModule._unloadedScriptTypeNames.Add(typeName);
VisualScriptingModule.TypeNameToTypeIndex.RemoveValue(_scriptingTypeHandle.TypeIndex);
VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr;
_scriptingTypeHandleCached = _scriptingTypeHandle;
@@ -1653,6 +1657,8 @@ VisualScriptingBinaryModule::VisualScriptingBinaryModule()
ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const ScriptingObjectSpawnParams& params)
{
// Create native object (base type can be C++ or C#)
if (params.Type.Module == nullptr)
return nullptr;
ScriptingType& visualScriptType = (ScriptingType&)params.Type.GetType();
ScriptingTypeHandle baseTypeHandle = visualScriptType.GetBaseType();
const ScriptingType* baseTypePtr = &baseTypeHandle.GetType();
@@ -1663,9 +1669,7 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri
}
ScriptingObject* object = baseTypePtr->Script.Spawn(params);
if (!object)
{
return nullptr;
}
// Beware! Hacking vtables incoming! Undefined behaviors exploits! Low-level programming!
visualScriptType.HackObjectVTable(object, baseTypeHandle, 1);
@@ -2060,6 +2064,11 @@ void VisualScriptingBinaryModule::Destroy(bool isReloading)
return;
BinaryModule::Destroy(isReloading);
// Free cached script typenames table
for (char* str : _unloadedScriptTypeNames)
Allocator::Free(str);
_unloadedScriptTypeNames.Clear();
}
ScriptingTypeHandle VisualScript::GetScriptingType()

View File

@@ -303,6 +303,7 @@ class FLAXENGINE_API VisualScriptingBinaryModule : public BinaryModule
friend VisualScript;
private:
StringAnsi _name;
Array<char*> _unloadedScriptTypeNames;
public:
/// <summary>

View File

@@ -12,6 +12,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))]
#endif
[Newtonsoft.Json.JsonConverter(typeof(Json.JsonAssetReferenceConverter))]
public struct JsonAssetReference<T> : IComparable, IComparable<JsonAssetReference<T>>, IEquatable<JsonAssetReference<T>>
{
/// <summary>

View File

@@ -579,7 +579,7 @@ public:
/// Insert the given item at specified index with keeping items order.
/// </summary>
/// <param name="index">The zero-based index at which item should be inserted.</param>
/// <param name="item">The item to insert.</param>
/// <param name="item">The item to be inserted by copying.</param>
void Insert(int32 index, const T& item)
{
ASSERT(index >= 0 && index <= _count);
@@ -592,6 +592,23 @@ public:
data[index] = item;
}
/// <summary>
/// Insert the given item at specified index with keeping items order.
/// </summary>
/// <param name="index">The zero-based index at which item should be inserted.</param>
/// <param name="item">The item to inserted by moving.</param>
void Insert(int32 index, T&& item)
{
ASSERT(index >= 0 && index <= _count);
EnsureCapacity(_count + 1);
T* data = _allocation.Get();
Memory::ConstructItems(data + _count, 1);
for (int32 i = _count - 1; i >= index; i--)
data[i + 1] = MoveTemp(data[i]);
_count++;
data[index] = MoveTemp(item);
}
/// <summary>
/// Insert the given item at specified index with keeping items order.
/// </summary>
@@ -772,9 +789,9 @@ public:
/// <summary>
/// Performs pop from stack operation (stack grows at the end of the collection).
/// </summary>
T Pop()
FORCE_INLINE T Pop()
{
T item(Last());
T item = MoveTemp(Last());
RemoveLast();
return item;
}
@@ -807,6 +824,15 @@ public:
Add(item);
}
/// <summary>
/// Performs enqueue to queue operation (queue head is in the beginning of queue).
/// </summary>
/// <param name="item">The item to append.</param>
void Enqueue(T&& item)
{
Add(MoveTemp(item));
}
/// <summary>
/// Performs dequeue from queue operation (queue head is in the beginning of queue).
/// </summary>
@@ -814,7 +840,7 @@ public:
T Dequeue()
{
ASSERT(HasItems());
T item(First());
T item = MoveTemp(_allocation.Get()[0]);
RemoveAtKeepOrder(0);
return item;
}

View File

@@ -12,7 +12,7 @@ namespace FlaxEngine
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct FloatR10G10B10A2
{
private uint value;
private uint rawValue;
/// <summary>
/// Initializes a new instance of the <see cref = "T:FlaxEngine.FloatR10G10B10A2" /> structure.
@@ -23,7 +23,7 @@ namespace FlaxEngine
/// <param name="w">The floating point value that should be stored in A component (2 bit format).</param>
public FloatR10G10B10A2(float x, float y, float z, float w)
{
value = Pack(x, y, z, w);
rawValue = Pack(x, y, z, w);
}
/// <summary>
@@ -33,7 +33,7 @@ namespace FlaxEngine
/// <param name="w">The floating point value that should be stored in alpha component (2 bit format).</param>
public FloatR10G10B10A2(Float3 value, float w = 0)
{
this.value = Pack(value.X, value.Y, value.Z, w);
rawValue = Pack(value.X, value.Y, value.Z, w);
}
/// <summary>
@@ -42,37 +42,33 @@ namespace FlaxEngine
/// <param name = "value">The floating point value that should be stored in 10 bit format.</param>
public FloatR10G10B10A2(Float4 value)
{
this.value = Pack(value.X, value.Y, value.Z, value.W);
rawValue = Pack(value.X, value.Y, value.Z, value.W);
}
/// <summary>
/// Gets or sets the raw 32 bit value used to back this vector.
/// </summary>
public uint RawValue
{
get => value;
set => this.value = value;
}
public uint RawValue => rawValue;
/// <summary>
/// Gets the R component.
/// </summary>
public float R => (value & 0x3FF) / 1023.0f;
public float R => (rawValue & 0x3FF) / 1023.0f;
/// <summary>
/// Gets the G component.
/// </summary>
public float G => ((value >> 10) & 0x3FF) / 1023.0f;
public float G => ((rawValue >> 10) & 0x3FF) / 1023.0f;
/// <summary>
/// Gets the B component.
/// </summary>
public float B => ((value >> 20) & 0x3FF) / 1023.0f;
public float B => ((rawValue >> 20) & 0x3FF) / 1023.0f;
/// <summary>
/// Gets the A component.
/// </summary>
public float A => (value >> 30) / 3.0f;
public float A => (rawValue >> 30) / 3.0f;
/// <summary>
/// Performs an explicit conversion from <see cref = "T:FlaxEngine.Float4" /> to <see cref = "T:FlaxEngine.FloatR10G10B10A2" />.
@@ -102,7 +98,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name="left" /> has the same value as <paramref name="right" />; otherwise, <c>false</c>.</returns>
public static bool operator ==(FloatR10G10B10A2 left, FloatR10G10B10A2 right)
{
return left.value == right.value;
return left.rawValue == right.rawValue;
}
/// <summary>
@@ -113,7 +109,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name="left" /> has a different value than <paramref name="right" />; otherwise, <c>false</c>.</returns>
public static bool operator !=(FloatR10G10B10A2 left, FloatR10G10B10A2 right)
{
return left.value != right.value;
return left.rawValue != right.rawValue;
}
/// <summary>
@@ -131,7 +127,7 @@ namespace FlaxEngine
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
return value.GetHashCode();
return rawValue.GetHashCode();
}
/// <summary>
@@ -142,7 +138,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name = "value1" /> is the same instance as <paramref name = "value2" /> or if both are <c>null</c> references or if <c>value1.Equals(value2)</c> returns <c>true</c>; otherwise, <c>false</c>.</returns>
public static bool Equals(ref FloatR10G10B10A2 value1, ref FloatR10G10B10A2 value2)
{
return value1.value == value2.value;
return value1.rawValue == value2.rawValue;
}
/// <summary>
@@ -152,7 +148,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the current instance is equal to the specified object; <c>false</c> otherwise.</returns>
public bool Equals(FloatR10G10B10A2 other)
{
return other.value == value;
return other.rawValue == rawValue;
}
/// <summary>
@@ -162,7 +158,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the current instance is equal to the specified object; <c>false</c> otherwise.</returns>
public override bool Equals(object obj)
{
return obj is FloatR10G10B10A2 other && value == other.value;
return obj is FloatR10G10B10A2 other && rawValue == other.rawValue;
}
private static uint Pack(float x, float y, float z, float w)
@@ -191,11 +187,11 @@ namespace FlaxEngine
{
Float3 vectorOut;
uint tmp = value & 0x3FF;
uint tmp = rawValue & 0x3FF;
vectorOut.X = tmp / 1023.0f;
tmp = (value >> 10) & 0x3FF;
tmp = (rawValue >> 10) & 0x3FF;
vectorOut.Y = tmp / 1023.0f;
tmp = (value >> 20) & 0x3FF;
tmp = (rawValue >> 20) & 0x3FF;
vectorOut.Z = tmp / 1023.0f;
return vectorOut;
@@ -209,13 +205,13 @@ namespace FlaxEngine
{
Float4 vectorOut;
uint tmp = value & 0x3FF;
uint tmp = rawValue & 0x3FF;
vectorOut.X = tmp / 1023.0f;
tmp = (value >> 10) & 0x3FF;
tmp = (rawValue >> 10) & 0x3FF;
vectorOut.Y = tmp / 1023.0f;
tmp = (value >> 20) & 0x3FF;
tmp = (rawValue >> 20) & 0x3FF;
vectorOut.Z = tmp / 1023.0f;
vectorOut.W = (value >> 30) / 3.0f;
vectorOut.W = (rawValue >> 30) / 3.0f;
return vectorOut;
}

View File

@@ -21,7 +21,7 @@ namespace FlaxEngine
{
// Reference: [https://github.com/Microsoft/DirectXMath/blob/master/Inc/DirectXPackedVector.h]
private uint value;
private uint rawValue;
/// <summary>
/// Initializes a new instance of the <see cref = "T:FlaxEngine.FloatR11G11B10" /> structure.
@@ -31,7 +31,7 @@ namespace FlaxEngine
/// <param name="z">The floating point value that should be stored in B component (10 bits format).</param>
public FloatR11G11B10(float x, float y, float z)
{
value = Pack(x, y, z);
rawValue = Pack(x, y, z);
}
/// <summary>
@@ -40,17 +40,13 @@ namespace FlaxEngine
/// <param name="value">The floating point value that should be stored in compressed format.</param>
public FloatR11G11B10(Float3 value)
{
this.value = Pack(value.X, value.Y, value.Z);
rawValue = Pack(value.X, value.Y, value.Z);
}
/// <summary>
/// Gets or sets the raw 32 bit value used to back this vector.
/// </summary>
public uint RawValue
{
get => value;
set => this.value = value;
}
public uint RawValue => rawValue;
/// <summary>
/// Performs an explicit conversion from <see cref = "T:FlaxEngine.Float3" /> to <see cref = "T:FlaxEngine.FloatR11G11B10" />.
@@ -80,7 +76,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name="left" /> has the same value as <paramref name="right" />; otherwise, <c>false</c>.</returns>
public static bool operator ==(FloatR11G11B10 left, FloatR11G11B10 right)
{
return left.value == right.value;
return left.rawValue == right.rawValue;
}
/// <summary>
@@ -91,7 +87,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name="left" /> has a different value than <paramref name="right" />; otherwise, <c>false</c>.</returns>
public static bool operator !=(FloatR11G11B10 left, FloatR11G11B10 right)
{
return left.value != right.value;
return left.rawValue != right.rawValue;
}
/// <summary>
@@ -109,7 +105,7 @@ namespace FlaxEngine
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
return value.GetHashCode();
return rawValue.GetHashCode();
}
/// <summary>
@@ -120,7 +116,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name="value1" /> is the same instance as <paramref name="value2" /> or if both are <c>null</c> references or if <c>value1.Equals(value2)</c> returns <c>true</c>; otherwise, <c>false</c>.</returns>
public static bool Equals(ref FloatR11G11B10 value1, ref FloatR11G11B10 value2)
{
return value1.value == value2.value;
return value1.rawValue == value2.rawValue;
}
/// <summary>
@@ -130,7 +126,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the current instance is equal to the specified object; <c>false</c> otherwise.</returns>
public bool Equals(FloatR11G11B10 other)
{
return other.value == value;
return other.rawValue == rawValue;
}
/// <summary>
@@ -140,7 +136,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the current instance is equal to the specified object; <c>false</c> otherwise.</returns>
public override bool Equals(object obj)
{
return obj is FloatR11G11B10 other && value == other.value;
return obj is FloatR11G11B10 other && rawValue == other.rawValue;
}
private static unsafe uint Pack(float x, float y, float z)
@@ -288,7 +284,7 @@ namespace FlaxEngine
{
int zeroExponent = -112;
Packed packed = new Packed(value);
Packed packed = new Packed(rawValue);
uint* result = stackalloc uint[4];
uint exponent;

View File

@@ -38,7 +38,7 @@ namespace FlaxEngine
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct Half
{
private ushort value;
private ushort rawValue;
/// <summary>
/// Number of decimal digits of precision.
@@ -111,17 +111,13 @@ namespace FlaxEngine
/// <param name = "value">The floating point value that should be stored in 16 bit format.</param>
public Half(float value)
{
this.value = HalfUtils.Pack(value);
rawValue = HalfUtils.Pack(value);
}
/// <summary>
/// Gets or sets the raw 16 bit value used to back this half-float.
/// </summary>
public ushort RawValue
{
get => value;
set => this.value = value;
}
public ushort RawValue => rawValue;
/// <summary>
/// Converts an array of half precision values into full precision values.
@@ -166,7 +162,7 @@ namespace FlaxEngine
/// <returns>The converted value.</returns>
public static implicit operator float(Half value)
{
return HalfUtils.Unpack(value.value);
return HalfUtils.Unpack(value.rawValue);
}
/// <summary>
@@ -177,7 +173,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name = "left" /> has the same value as <paramref name = "right" />; otherwise, <c>false</c>.</returns>
public static bool operator ==(Half left, Half right)
{
return left.value == right.value;
return left.rawValue == right.rawValue;
}
/// <summary>
@@ -188,7 +184,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name = "left" /> has a different value than <paramref name = "right" />; otherwise, <c>false</c>.</returns>
public static bool operator !=(Half left, Half right)
{
return left.value != right.value;
return left.rawValue != right.rawValue;
}
/// <summary>
@@ -207,7 +203,7 @@ namespace FlaxEngine
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
ushort num = value;
ushort num = rawValue;
return (((num * 3) / 2) ^ num);
}
@@ -219,7 +215,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if <paramref name = "value1" /> is the same instance as <paramref name = "value2" /> or if both are <c>null</c> references or if <c>value1.Equals(value2)</c> returns <c>true</c>; otherwise, <c>false</c>.</returns>
public static bool Equals(ref Half value1, ref Half value2)
{
return value1.value == value2.value;
return value1.rawValue == value2.rawValue;
}
/// <summary>
@@ -229,7 +225,7 @@ namespace FlaxEngine
/// <returns><c>true</c> if the current instance is equal to the specified object; <c>false</c> otherwise.</returns>
public bool Equals(Half other)
{
return other.value == value;
return other.rawValue == rawValue;
}
/// <summary>
@@ -248,7 +244,7 @@ namespace FlaxEngine
return false;
}
Half half = (Half)obj;
return half.value == value;
return half.rawValue == rawValue;
}
static Half()

View File

@@ -289,6 +289,7 @@ void Quaternion::Billboard(const Float3& objectPosition, const Float3& cameraPos
Quaternion Quaternion::FromDirection(const Float3& direction)
{
ASSERT(direction.IsNormalized());
Quaternion orientation;
if (Float3::Dot(direction, Float3::Up) >= 0.999f)
{

View File

@@ -46,6 +46,7 @@ public:
: Position(position)
, Direction(direction)
{
ASSERT(Direction.IsNormalized());
}
public:

View File

@@ -946,7 +946,8 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo
void DebugDraw::DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, float size, float duration, bool depthTest)
{
const auto rot = Quaternion::FromDirection(direction.GetNormalized());
ASSERT(direction.IsNormalized());
const auto rot = Quaternion::FromDirection(direction);
const Vector3 up = (rot * Vector3::Up);
const Vector3 forward = (rot * Vector3::Forward);
const Vector3 right = (rot * Vector3::Right);
@@ -971,16 +972,17 @@ void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const C
void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float length, float duration, bool depthTest)
{
ASSERT(direction.IsNormalized());
if (isnan(length) || isinf(length))
return;
DrawLine(origin, origin + (direction.GetNormalized() * length), color, duration, depthTest);
DrawLine(origin, origin + (direction * length), color, duration, depthTest);
}
void DebugDraw::DrawRay(const Ray& ray, const Color& color, float length, float duration, bool depthTest)
{
if (isnan(length) || isinf(length))
return;
DrawLine(ray.Position, ray.Position + (ray.Direction.GetNormalized() * length), color, duration, depthTest);
DrawLine(ray.Position, ray.Position + (ray.Direction * length), color, duration, depthTest);
}
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest)

View File

@@ -850,11 +850,7 @@ namespace FlaxEngine.Interop
{
object fieldOwner = fieldOwnerHandle.Target;
FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
object value = null;
if (field.field.FieldType.IsValueType)
value = Marshal.PtrToStructure(valuePtr, field.field.FieldType);
else if (valuePtr != IntPtr.Zero)
value = ManagedHandle.FromIntPtr(valuePtr).Target;
object value = MarshalToManaged(valuePtr, field.field.FieldType);
field.field.SetValue(fieldOwner, value);
}

View File

@@ -15,12 +15,12 @@
#define USE_MIKKTSPACE 1
#include "ThirdParty/MikkTSpace/mikktspace.h"
#if USE_ASSIMP
#define USE_SPARIAL_SORT 1
#define USE_SPATIAL_SORT 1
#define ASSIMP_BUILD_NO_EXPORT
#include "Engine/Tools/ModelTool/SpatialSort.h"
//#include <ThirdParty/assimp/SpatialSort.h>
#else
#define USE_SPARIAL_SORT 0
#define USE_SPATIAL_SORT 0
#endif
#include <stack>
@@ -155,18 +155,18 @@ bool MeshData::GenerateLightmapUVs()
}
int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int32 searchRange, const Array<int32>& mapping
#if USE_SPARIAL_SORT
#if USE_SPATIAL_SORT
, const Assimp::SpatialSort& spatialSort
, std::vector<unsigned int>& sparialSortCache
, std::vector<unsigned int>& spatialSortCache
#endif
)
{
const float uvEpsSqr = (1.0f / 250.0f) * (1.0f / 250.0f);
#if USE_SPARIAL_SORT
#if USE_SPATIAL_SORT
const Float3 vPosition = mesh.Positions[vertexIndex];
spatialSort.FindPositions(*(aiVector3D*)&vPosition, 1e-4f, sparialSortCache);
if (sparialSortCache.empty())
spatialSort.FindPositions(*(aiVector3D*)&vPosition, 1e-5f, spatialSortCache);
if (spatialSortCache.empty())
return INVALID_INDEX;
const Float2 vUV = mesh.UVs.HasItems() ? mesh.UVs[vertexIndex] : Float2::Zero;
@@ -177,9 +177,9 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3
const int32 end = startIndex + searchRange;
for (size_t i = 0; i < sparialSortCache.size(); i++)
for (size_t i = 0; i < spatialSortCache.size(); i++)
{
const int32 v = sparialSortCache[i];
const int32 v = spatialSortCache[i];
if (v < startIndex || v >= end)
continue;
#else
@@ -247,11 +247,11 @@ void MeshData::BuildIndexBuffer()
mapping.Resize(vertexCount);
int32 newVertexCounter = 0;
#if USE_SPARIAL_SORT
#if USE_SPATIAL_SORT
// Set up a SpatialSort to quickly find all vertices close to a given position
Assimp::SpatialSort vertexFinder;
vertexFinder.Fill((const aiVector3D*)Positions.Get(), vertexCount, sizeof(Float3));
std::vector<unsigned int> sparialSortCache;
std::vector<unsigned int> spatialSortCache;
#endif
// Build index buffer
@@ -259,8 +259,8 @@ void MeshData::BuildIndexBuffer()
{
// Find duplicated vertex before the current one
const int32 reuseVertexIndex = FindVertex(*this, vertexIndex, 0, vertexIndex, mapping
#if USE_SPARIAL_SORT
, vertexFinder, sparialSortCache
#if USE_SPATIAL_SORT
, vertexFinder, spatialSortCache
#endif
);
if (reuseVertexIndex == INVALID_INDEX)
@@ -304,18 +304,15 @@ void MeshData::BuildIndexBuffer()
dstBlendShape.Name = srcBlendShape.Name;
dstBlendShape.Weight = srcBlendShape.Weight;
dstBlendShape.Vertices.Resize(newVertexCounter);
for (int32 i = 0, j = 0; i < srcBlendShape.Vertices.Count(); i++)
dstBlendShape.Vertices.EnsureCapacity(srcBlendShape.Vertices.Count());
for (int32 i = 0; i < srcBlendShape.Vertices.Count(); i++)
{
const auto idx = mapping[i];
if (idx != INVALID_INDEX)
auto& v = srcBlendShape.Vertices[i];
int32 newVertexIndex = v.VertexIndex < (uint32)vertexCount ? mapping[v.VertexIndex] : INVALID_INDEX;
if (newVertexIndex != INVALID_INDEX)
{
auto& v = srcBlendShape.Vertices[i];
ASSERT_LOW_LAYER(v.VertexIndex < (uint32)vertexCount);
ASSERT_LOW_LAYER(mapping[v.VertexIndex] != INVALID_INDEX);
v.VertexIndex = mapping[v.VertexIndex];
ASSERT_LOW_LAYER(v.VertexIndex < (uint32)newVertexCounter);
dstBlendShape.Vertices[j++] = v;
v.VertexIndex = newVertexIndex;
dstBlendShape.Vertices.Add(v);
}
}
}
@@ -376,7 +373,7 @@ bool MeshData::GenerateNormals(float smoothingAngle)
Float3::Max(max, v3, max);
}
#if USE_SPARIAL_SORT
#if USE_SPATIAL_SORT
// Set up a SpatialSort to quickly find all vertices close to a given position
Assimp::SpatialSort vertexFinder;
vertexFinder.Fill((const aiVector3D*)Positions.Get(), vertexCount, sizeof(Float3));
@@ -399,7 +396,7 @@ bool MeshData::GenerateNormals(float smoothingAngle)
continue;
// Get all vertices that share this one
#if USE_SPARIAL_SORT
#if USE_SPATIAL_SORT
vertexFinder.FindPositions(*(aiVector3D*)&Positions[i], posEpsilon, verticesFound);
const int32 verticesFoundCount = (int32)verticesFound.size();
#else
@@ -429,7 +426,7 @@ bool MeshData::GenerateNormals(float smoothingAngle)
for (int32 i = 0; i < vertexCount; i++)
{
// Get all vertices that share this one
#if USE_SPARIAL_SORT
#if USE_SPATIAL_SORT
vertexFinder.FindPositions(*(aiVector3D*)&Positions[i], posEpsilon, verticesFound);
const int32 verticesFoundCount = (int32)verticesFound.size();
#else
@@ -623,7 +620,7 @@ bool MeshData::GenerateTangents(float smoothingAngle)
}
}
#if USE_SPARIAL_SORT
#if USE_SPATIAL_SORT
// Set up a SpatialSort to quickly find all vertices close to a given position
Assimp::SpatialSort vertexFinder;
vertexFinder.Fill((const aiVector3D*)Positions.Get(), vertexCount, sizeof(Float3));
@@ -648,7 +645,7 @@ bool MeshData::GenerateTangents(float smoothingAngle)
closeVertices.Clear();
// Find all vertices close to that position
#if USE_SPARIAL_SORT
#if USE_SPATIAL_SORT
vertexFinder.FindPositions(*(aiVector3D*)&origPos, posEpsilon, verticesFound);
const int32 verticesFoundCount = (int32)verticesFound.size();
#else

View File

@@ -543,6 +543,15 @@ void Actor::SetLayerRecursive(int32 layerIndex)
OnLayerChanged();
}
void Actor::SetName(String&& value)
{
if (_name == value)
return;
_name = MoveTemp(value);
if (GetScene())
Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, this, nullptr);
}
void Actor::SetName(const StringView& value)
{
if (_name == value)

View File

@@ -185,7 +185,13 @@ public:
/// Sets the actor name.
/// </summary>
/// <param name="value">The value to set.</param>
API_PROPERTY() void SetName(const StringView& value);
API_PROPERTY() void SetName(String&& value);
/// <summary>
/// Sets the actor name.
/// </summary>
/// <param name="value">The value to set.</param>
void SetName(const StringView& value);
public:
/// <summary>

View File

@@ -930,6 +930,9 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
// Fire event
CallSceneEvent(SceneEventType::OnSceneLoading, scene, sceneId);
// Get any injected children of the scene.
Array<Actor*> injectedSceneChildren = scene->Children;
// Loaded scene objects list
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
const int32 dataCount = (int32)data.Size();
@@ -1031,6 +1034,20 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
// /\ all above this has to be done on multiple threads at once
// \/ all below this has to be done on an any thread
// Add injected children of scene (via OnSceneLoading) into sceneObjects to be initialized
for (auto child : injectedSceneChildren)
{
Array<SceneObject*> injectedSceneObjects;
injectedSceneObjects.Add(child);
SceneQuery::GetAllSceneObjects(child, injectedSceneObjects);
for (auto o : injectedSceneObjects)
{
if (!o->IsRegistered())
o->RegisterObject();
sceneObjects->Add(o);
}
}
// Synchronize prefab instances (prefab may have objects removed or reordered so deserialized instances need to synchronize with it)
// TODO: resave and force sync scenes during game cooking so this step could be skipped in game
SceneObjectsFactory::SynchronizePrefabInstances(context, prefabSyncData);
@@ -1047,7 +1064,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
PROFILE_CPU_NAMED("Initialize");
SceneObject** objects = sceneObjects->Get();
for (int32 i = 0; i < dataCount; i++)
for (int32 i = 0; i < sceneObjects->Count(); i++)
{
SceneObject* obj = objects[i];
if (obj)

View File

@@ -180,7 +180,7 @@ void Scene::CreateCsgCollider()
// Create collider
auto result = New<MeshCollider>();
result->SetStaticFlags(StaticFlags::FullyStatic);
result->SetName(CSG_COLLIDER_NAME);
result->SetName(String(CSG_COLLIDER_NAME));
result->CollisionData = CSGData.CollisionData;
result->HideFlags |= HideFlags::DontSelect;
@@ -203,7 +203,7 @@ void Scene::CreateCsgModel()
// Create model
auto result = New<StaticModel>();
result->SetStaticFlags(StaticFlags::FullyStatic);
result->SetName(CSG_MODEL_NAME);
result->SetName(String(CSG_MODEL_NAME));
result->Model = CSGData.Model;
result->HideFlags |= HideFlags::DontSelect;

View File

@@ -27,6 +27,11 @@ Cloth::Cloth(const SpawnParams& params)
_drawCategory = SceneRendering::SceneDrawAsync;
}
void* Cloth::GetPhysicsCloth() const
{
return _cloth;
}
ModelInstanceActor::MeshReference Cloth::GetMesh() const
{
auto value = _mesh;

View File

@@ -13,7 +13,6 @@
/// </summary>
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Cloth\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API Cloth : public Actor
{
friend class PhysicsBackend;
DECLARE_SCENE_OBJECT(Cloth);
/// <summary>
@@ -231,6 +230,11 @@ private:
Array<float> _paint;
public:
/// <summary>
/// Gets the native physics backend object.
/// </summary>
void* GetPhysicsCloth() const;
/// <summary>
/// Gets the mesh to use for the cloth simulation (single mesh from specific LOD). Always from the parent static or animated model actor.
/// </summary>

View File

@@ -13,6 +13,7 @@
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Wheeled Vehicle\"), ActorToolbox(\"Physics\")") class FLAXENGINE_API WheeledVehicle : public RigidBody
{
friend class PhysicsBackend;
friend struct ScenePhysX;
DECLARE_SCENE_OBJECT(WheeledVehicle);
/// <summary>

View File

@@ -71,6 +71,7 @@ void Collider::SetContactOffset(float value)
bool Collider::RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance) const
{
ASSERT(direction.IsNormalized());
resultHitDistance = MAX_float;
if (_shape == nullptr)
return false;
@@ -79,6 +80,7 @@ bool Collider::RayCast(const Vector3& origin, const Vector3& direction, float& r
bool Collider::RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance) const
{
ASSERT(direction.IsNormalized());
if (_shape == nullptr)
return false;
return PhysicsBackend::RayCastShape(_shape, _transform.Translation, _transform.Orientation, origin, direction, hitInfo, maxDistance);

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ public:
static PxCooking* GetCooking();
#endif
static PxMaterial* GetDefaultMaterial();
static void SimulationStepDone(PxScene* scene, float dt);
};
#endif

View File

@@ -3,7 +3,7 @@
#if COMPILE_WITH_PHYSX
#include "PhysicsStepperPhysX.h"
#include "Engine/Physics/Physics.h"
#include "PhysicsBackendPhysX.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include <ThirdParty/PhysX/foundation/PxMath.h>
#include <ThirdParty/PhysX/PxSceneLock.h>
@@ -66,7 +66,7 @@ void MultiThreadStepper::substepDone(StepperTask* ownerTask)
}
// -> OnSubstep
//Physics::OnSubstep();
PhysicsBackendPhysX::SimulationStepDone(mScene, mSubStepSize);
if (mCurrentSubStep >= mNbSubSteps)
{

View File

@@ -235,76 +235,91 @@ bool Physics::LineCastAll(const Vector3& start, const Vector3& end, Array<RayCas
bool Physics::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->RayCast(origin, direction, maxDistance, layerMask, hitTriggers);
}
bool Physics::RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->RayCast(origin, direction, hitInfo, maxDistance, layerMask, hitTriggers);
}
bool Physics::RayCastAll(const Vector3& origin, const Vector3& direction, Array<RayCastHit>& results, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->RayCastAll(origin, direction, results, maxDistance, layerMask, hitTriggers);
}
bool Physics::BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->BoxCast(center, halfExtents, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->BoxCast(center, halfExtents, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->BoxCastAll(center, halfExtents, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::SphereCast(const Vector3& center, const float radius, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->SphereCast(center, radius, direction, maxDistance, layerMask, hitTriggers);
}
bool Physics::SphereCast(const Vector3& center, const float radius, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->SphereCast(center, radius, direction, hitInfo, maxDistance, layerMask, hitTriggers);
}
bool Physics::SphereCastAll(const Vector3& center, const float radius, const Vector3& direction, Array<RayCastHit>& results, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->SphereCastAll(center, radius, direction, results, maxDistance, layerMask, hitTriggers);
}
bool Physics::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->CapsuleCast(center, radius, height, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->CapsuleCast(center, radius, height, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::CapsuleCastAll(const Vector3& center, const float radius, const float height, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->CapsuleCastAll(center, radius, height, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->ConvexCast(center, convexMesh, scale, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->ConvexCast(center, convexMesh, scale, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return DefaultScene->ConvexCastAll(center, convexMesh, scale, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}
@@ -505,76 +520,91 @@ bool PhysicsScene::LineCastAll(const Vector3& start, const Vector3& end, Array<R
bool PhysicsScene::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::RayCast(_scene, origin, direction, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::RayCast(_scene, origin, direction, hitInfo, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::RayCastAll(const Vector3& origin, const Vector3& direction, Array<RayCastHit>& results, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::RayCastAll(_scene, origin, direction, results, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::BoxCast(_scene, center, halfExtents, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::BoxCast(_scene, center, halfExtents, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::BoxCastAll(_scene, center, halfExtents, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::SphereCast(const Vector3& center, const float radius, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::SphereCast(_scene, center, radius, direction, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::SphereCast(const Vector3& center, const float radius, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::SphereCast(_scene, center, radius, direction, hitInfo, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::SphereCastAll(const Vector3& center, const float radius, const Vector3& direction, Array<RayCastHit>& results, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::SphereCastAll(_scene, center, radius, direction, results, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::CapsuleCast(_scene, center, radius, height, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::CapsuleCast(_scene, center, radius, height, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::CapsuleCastAll(const Vector3& center, const float radius, const float height, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::CapsuleCastAll(_scene, center, radius, height, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::ConvexCast(_scene, center, convexMesh, scale, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::ConvexCast(_scene, center, convexMesh, scale, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool PhysicsScene::ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
ASSERT(direction.IsNormalized());
return PhysicsBackend::ConvexCastAll(_scene, center, convexMesh, scale, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}

View File

@@ -120,7 +120,7 @@ public:
/// Performs a line between two points in the scene, returns all hitpoints infos.
/// </summary>
/// <param name="start">The origin of the ray.</param>
/// <param name="end">The normalized direction of the ray.</param>
/// <param name="end">The end position of the line.</param>
/// <param name="results">The result hits. Valid only when method returns true.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>

View File

@@ -118,7 +118,7 @@ public:
/// <summary>
/// Tries to find a method in a given scripting type by the method name and parameters count.
/// </summary>
/// <remarks>If the the type contains more than one method of the given name and parameters count the returned value can be non-deterministic (one of the matching methods).</remarks>
/// <remarks>If the type contains more than one method of the given name and parameters count the returned value can be non-deterministic (one of the matching methods).</remarks>
/// <param name="typeHandle">The type to find method inside it.</param>
/// <param name="name">The method name.</param>
/// <param name="numParams">The method parameters count.</param>
@@ -182,7 +182,7 @@ public:
/// Gets the value of a given scripting field.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="instance">The object instance to get it's member field. Unused for static fields.</param>
/// <param name="instance">The object instance to get its member field. Unused for static fields.</param>
/// <param name="result">The output field value.</param>
/// <returns>True if failed, otherwise false.</returns>
virtual bool GetFieldValue(void* field, const Variant& instance, Variant& result)
@@ -194,7 +194,7 @@ public:
/// Sets the value of a given scripting field.
/// </summary>
/// <param name="field">The field.</param>
/// <param name="instance">The object instance to set it's member field. Unused for static fields.</param>
/// <param name="instance">The object instance to set its member field. Unused for static fields.</param>
/// <param name="value">The field value to assign.</param>
/// <returns>True if failed, otherwise false.</returns>
virtual bool SetFieldValue(void* field, const Variant& instance, Variant& value)
@@ -242,7 +242,7 @@ public:
/// <summary>
/// Unloads the module (native library and C# assembly and any other scripting data). Unregisters the module.
/// </summary>
/// <param name="isReloading">If true module is during reloading and should force release the runtime data. Used for C# assembly to cleanup it's runtime data in Mono (or other scripting runtime).</param>
/// <param name="isReloading">If true module is during reloading and should force release the runtime data. Used for C# assembly to clean up it's runtime data in Mono (or other scripting runtime).</param>
virtual void Destroy(bool isReloading);
};

View File

@@ -429,6 +429,60 @@ namespace FlaxEngine.Json
}
}
internal sealed class JsonAssetReferenceConverter : JsonConverter
{
/// <inheritdoc />
public override unsafe void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
var asset = (JsonAsset)value.GetType().GetField("Asset").GetValue(value);
var id = asset?.ID ?? Guid.Empty;
writer.WriteValue(JsonSerializer.GetStringID(&id));
}
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
var result = Activator.CreateInstance(objectType);
if (reader.TokenType == JsonToken.String)
{
JsonSerializer.ParseID((string)reader.Value, out var id);
var asset = Content.LoadAsync<JsonAsset>(id);
objectType.GetField("Asset").SetValue(result, asset);
}
else if (reader.TokenType == JsonToken.StartObject)
{
// [Deprecated on 26.07.2024, expires on 26.07.2026]
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
{
var propertyName = (string)reader.Value;
reader.Read();
if (propertyName == "Asset" && reader.TokenType == JsonToken.String)
{
JsonSerializer.ParseID((string)reader.Value, out var id);
var asset = Content.LoadAsync<JsonAsset>(id);
objectType.GetField("Asset").SetValue(result, asset);
}
break;
}
}
}
}
return result;
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType.Name.StartsWith("JsonAssetReference");
}
}
/*
/// <summary>
/// Serialize Guid values using `N` format

View File

@@ -1963,6 +1963,7 @@ bool TerrainPatch::UpdateCollision()
bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance) const
{
ASSERT(direction.IsNormalized());
if (_physicsShape == nullptr)
return false;
Vector3 shapePos;
@@ -1973,6 +1974,7 @@ bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, floa
bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, Vector3& resultHitNormal, float maxDistance) const
{
ASSERT(direction.IsNormalized());
if (_physicsShape == nullptr)
return false;
Vector3 shapePos;
@@ -1990,6 +1992,7 @@ bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, floa
bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance) const
{
ASSERT(direction.IsNormalized());
if (_physicsShape == nullptr)
return false;
Vector3 shapePos;
@@ -2027,6 +2030,7 @@ bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, floa
bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance) const
{
ASSERT(direction.IsNormalized());
if (_physicsShape == nullptr)
return false;
Vector3 shapePos;

View File

@@ -189,7 +189,7 @@ TEST_CASE("Prefabs")
Guid newChildId;
Guid::Parse(TEXT("123456a04cc60d56a2f024bfeef57723"), newChildId);
auto newChild = EmptyActor::Spawn(ScriptingObject::SpawnParams(newChildId, EmptyActor::TypeInitializer));
newChild->SetName(TEXT("Prefab B.Child"));
newChild->SetName(String(TEXT("Prefab B.Child")));
newChild->SetParent(instanceB);
// Apply nested prefab changes
@@ -213,7 +213,7 @@ TEST_CASE("Prefabs")
// Add another child
Guid::Parse(TEXT("678906a04cc60d56a2f024bfeef57723"), newChildId);
newChild = EmptyActor::Spawn(ScriptingObject::SpawnParams(newChildId, EmptyActor::TypeInitializer));
newChild->SetName(TEXT("Prefab B.Child 2"));
newChild->SetName(String(TEXT("Prefab B.Child 2")));
newChild->SetParent(instanceB);
// Apply nested prefab changes

View File

@@ -735,6 +735,25 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
_writer.Write(*triplanarTexture);
value = result;
}
// Get Lightmap UV
case 18:
{
auto output = writeLocal(Value::InitForZero(ValueType::Float2), node);
auto lightmapUV = String::Format(TEXT(
"{{\n"
"#if USE_LIGHTMAP\n"
"\t {0} = input.LightmapUV;\n"
"#else\n"
"\t {0} = float2(0,0);\n"
"#endif\n"
"}}\n"
), output.Value);
_writer.Write(*lightmapUV);
value = output;
break;
}
default:
break;
}

View File

@@ -6,6 +6,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Mathd.h"
#include "Engine/Core/Math/Matrix.h"
#include "Engine/Core/Math/Plane.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
@@ -13,6 +14,11 @@
#include "Engine/Platform/File.h"
#define OPEN_FBX_CONVERT_SPACE 1
#if BUILD_DEBUG
#define OPEN_FBX_GET_CACHE_LIST(arrayName, varName, size) data.arrayName.Resize(size, false); auto& varName = data.arrayName
#else
#define OPEN_FBX_GET_CACHE_LIST(arrayName, varName, size) data.arrayName.Resize(size, false); auto* varName = data.arrayName.Get()
#endif
// Import OpenFBX library
// Source: https://github.com/nem0/OpenFBX
@@ -49,7 +55,7 @@ Quaternion ToQuaternion(const ofbx::Quat& v)
return Quaternion((float)v.x, (float)v.y, (float)v.z, (float)v.w);
}
Matrix ToMatrix(const ofbx::Matrix& mat)
Matrix ToMatrix(const ofbx::DMatrix& mat)
{
Matrix result;
for (int32 i = 0; i < 16; i++)
@@ -103,6 +109,13 @@ struct OpenFbxImporterData
Array<const ofbx::Material*> Materials;
Array<MaterialSlotEntry> ImportedMaterials;
Array<int> TriangulatedIndicesCache;
Array<Int4> BlendIndicesCache;
Array<Float4> BlendWeightsCache;
Array<Float2> TriangulatePointsCache;
Array<int> TriangulateIndicesCache;
Array<int> TriangulateEarIndicesCache;
OpenFbxImporterData(const String& path, const ModelTool::Options& options, ofbx::IScene* scene)
: Scene(scene)
, ScenePtr(scene)
@@ -416,7 +429,7 @@ void ProcessNodes(OpenFbxImporterData& data, const ofbx::Object* aNode, int32 pa
Matrix GetOffsetMatrix(OpenFbxImporterData& data, const ofbx::Mesh* mesh, const ofbx::Object* node)
{
#if 1
auto* skin = mesh ? mesh->getGeometry()->getSkin() : nullptr;
auto* skin = mesh ? mesh->getSkin() : nullptr;
if (skin)
{
for (int i = 0, c = skin->getClusterCount(); i < c; i++)
@@ -445,7 +458,7 @@ Matrix GetOffsetMatrix(OpenFbxImporterData& data, const ofbx::Mesh* mesh, const
bool IsMeshInvalid(const ofbx::Mesh* aMesh)
{
return aMesh->getGeometry()->getVertexCount() == 0;
return aMesh->getGeometryData().getPositions().count == 0;
}
bool ImportBones(OpenFbxImporterData& data, String& errorMsg)
@@ -455,8 +468,7 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg)
for (int i = 0; i < meshCount; i++)
{
const auto aMesh = data.Scene->getMesh(i);
const auto aGeometry = aMesh->getGeometry();
const ofbx::Skin* skin = aGeometry->getSkin();
const ofbx::Skin* skin = aMesh->getSkin();
if (skin == nullptr || IsMeshInvalid(aMesh))
continue;
@@ -524,56 +536,198 @@ bool ImportBones(OpenFbxImporterData& data, String& errorMsg)
return false;
}
bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& mesh, String& errorMsg, int32 triangleStart, int32 triangleEnd)
int Triangulate(OpenFbxImporterData& data, const ofbx::GeometryData& geom, const ofbx::GeometryPartition::Polygon& polygon, int* triangulatedIndices)
{
if (polygon.vertex_count < 3)
return 0;
else if (polygon.vertex_count == 3)
{
triangulatedIndices[0] = polygon.from_vertex;
triangulatedIndices[1] = polygon.from_vertex + 1;
triangulatedIndices[2] = polygon.from_vertex + 2;
return 3;
}
else if (polygon.vertex_count == 4)
{
triangulatedIndices[0] = polygon.from_vertex + 0;
triangulatedIndices[1] = polygon.from_vertex + 1;
triangulatedIndices[2] = polygon.from_vertex + 2;
triangulatedIndices[3] = polygon.from_vertex + 0;
triangulatedIndices[4] = polygon.from_vertex + 2;
triangulatedIndices[5] = polygon.from_vertex + 3;
return 6;
}
const ofbx::Vec3Attributes& positions = geom.getPositions();
Float3 normal = ToFloat3(geom.getNormals().get(polygon.from_vertex));
// Check if the polygon is convex
int lastSign = 0;
bool isConvex = true;
for (int i = 0; i < polygon.vertex_count; i++)
{
Float3 v1 = ToFloat3(positions.get(polygon.from_vertex + i));
Float3 v2 = ToFloat3(positions.get(polygon.from_vertex + (i + 1) % polygon.vertex_count));
Float3 v3 = ToFloat3(positions.get(polygon.from_vertex + (i + 2) % polygon.vertex_count));
// The winding order of all triangles must be same for polygon to be considered convex
int sign;
Float3 c = Float3::Cross(v1 - v2, v3 - v2);
if (c.LengthSquared() == 0.0f)
continue;
else if (Math::NotSameSign(c.X, normal.X) || Math::NotSameSign(c.Y, normal.Y) || Math::NotSameSign(c.Z, normal.Z))
sign = 1;
else
sign = -1;
if ((sign < 0 && lastSign > 0) || (sign > 0 && lastSign < 0))
{
isConvex = false;
break;
}
lastSign += sign;
}
// Fast-path for convex case
if (isConvex)
{
for (int i = 0; i < polygon.vertex_count - 2; i++)
{
triangulatedIndices[i * 3 + 0] = polygon.from_vertex;
triangulatedIndices[i * 3 + 1] = polygon.from_vertex + (i + 1) % polygon.vertex_count;
triangulatedIndices[i * 3 + 2] = polygon.from_vertex + (i + 2) % polygon.vertex_count;
}
return 3 * (polygon.vertex_count - 2);
}
// Setup arrays for temporary data (TODO: maybe double-linked list is more optimal?)
auto& points = data.TriangulatePointsCache;
auto& indices = data.TriangulateIndicesCache;
auto& earIndices = data.TriangulateEarIndicesCache;
points.Clear();
indices.Clear();
earIndices.Clear();
points.EnsureCapacity(polygon.vertex_count, false);
indices.EnsureCapacity(polygon.vertex_count, false);
earIndices.EnsureCapacity(3 * (polygon.vertex_count - 2), false);
// Project points to a plane, choose two arbitrary axises
const Float3 u = Float3::Cross(normal, Math::Abs(normal.X) > Math::Abs(normal.Y) ? Float3::Up : Float3::Right).GetNormalized();
const Float3 v = Float3::Cross(normal, u).GetNormalized();
for (int i = 0; i < polygon.vertex_count; i++)
{
const Float3 point = ToFloat3(positions.get(polygon.from_vertex + i));
const Float3 projectedPoint = Float3::ProjectOnPlane(point, normal);
const Float2 pointOnPlane = Float2(
projectedPoint.X * u.X + projectedPoint.Y * u.Y + projectedPoint.Z * u.Z,
projectedPoint.X * v.X + projectedPoint.Y * v.Y + projectedPoint.Z * v.Z);
points.Add(pointOnPlane);
indices.Add(i);
}
// Triangulate non-convex polygons using simple ear-clipping algorithm (https://nils-olovsson.se/articles/ear_clipping_triangulation/)
const int maxIterations = indices.Count() * 10; // Safe guard to prevent infinite loop
int index = 0;
while (indices.Count() > 3 && index < maxIterations)
{
const int i1 = index % indices.Count();
const int i2 = (index + 1) % indices.Count();
const int i3 = (index + 2) % indices.Count();
const Float2 p1 = points[indices[i1]];
const Float2 p2 = points[indices[i2]];
const Float2 p3 = points[indices[i3]];
// TODO: Skip triangles with very sharp angles?
// Skip reflex vertices
if (Float2::Cross(p2 - p1, p3 - p1) < 0.0f)
{
index++;
continue;
}
// The triangle is considered to be an "ear" when no other points reside inside the triangle
bool isEar = true;
for (int j = 0; j < indices.Count(); j++)
{
if (j == i1 || j == i2 || j == i3)
continue;
const Float2 candidate = points[indices[j]];
if (CollisionsHelper::IsPointInTriangle(candidate, p1, p2, p3))
{
isEar = false;
break;
}
}
if (!isEar)
{
index++;
continue;
}
// Add an ear and remove the tip point from evaluation
earIndices.Add(indices[i1]);
earIndices.Add(indices[i2]);
earIndices.Add(indices[i3]);
indices.RemoveAtKeepOrder(i2);
}
for (int i = 0; i < earIndices.Count(); i++)
triangulatedIndices[i] = polygon.from_vertex + (earIndices[i] % polygon.vertex_count);
triangulatedIndices[earIndices.Count() + 0] = polygon.from_vertex + (indices[0] % polygon.vertex_count);
triangulatedIndices[earIndices.Count() + 1] = polygon.from_vertex + (indices[1] % polygon.vertex_count);
triangulatedIndices[earIndices.Count() + 2] = polygon.from_vertex + (indices[2] % polygon.vertex_count);
return 3 * (polygon.vertex_count - 2);
}
bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* aMesh, MeshData& mesh, String& errorMsg, int partitionIndex)
{
PROFILE_CPU();
mesh.Name = aMesh->name;
ZoneText(*mesh.Name, mesh.Name.Length());
const int32 firstVertexOffset = triangleStart * 3;
const int32 lastVertexOffset = triangleEnd * 3;
const ofbx::Geometry* aGeometry = aMesh->getGeometry();
const int vertexCount = lastVertexOffset - firstVertexOffset + 3;
ASSERT(firstVertexOffset + vertexCount <= aGeometry->getVertexCount());
const ofbx::Vec3* vertices = aGeometry->getVertices();
const ofbx::Vec3* normals = aGeometry->getNormals();
const ofbx::Vec3* tangents = aGeometry->getTangents();
const ofbx::Vec4* colors = aGeometry->getColors();
const ofbx::Vec2* uvs = aGeometry->getUVs();
const ofbx::Skin* skin = aGeometry->getSkin();
const ofbx::BlendShape* blendShape = aGeometry->getBlendShape();
const ofbx::GeometryData& geometryData = aMesh->getGeometryData();
const ofbx::GeometryPartition& partition = geometryData.getPartition(partitionIndex);
const int vertexCount = partition.triangles_count * 3;
const ofbx::Vec3Attributes& positions = geometryData.getPositions();
const ofbx::Vec2Attributes& uvs = geometryData.getUVs();
const ofbx::Vec3Attributes& normals = geometryData.getNormals();
const ofbx::Vec3Attributes& tangents = geometryData.getTangents();
const ofbx::Vec4Attributes& colors = geometryData.getColors();
const ofbx::Skin* skin = aMesh->getSkin();
const ofbx::BlendShape* blendShape = aMesh->getBlendShape();
OPEN_FBX_GET_CACHE_LIST(TriangulatedIndicesCache, triangulatedIndices, vertexCount);
// Properties
const ofbx::Material* aMaterial = nullptr;
if (aMesh->getMaterialCount() > 0)
{
if (aGeometry->getMaterials())
aMaterial = aMesh->getMaterial(aGeometry->getMaterials()[triangleStart]);
else
aMaterial = aMesh->getMaterial(0);
}
aMaterial = aMesh->getMaterial(partitionIndex);
mesh.MaterialSlotIndex = data.AddMaterial(result, aMaterial);
// Vertex positions
mesh.Positions.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
mesh.Positions.Get()[i] = ToFloat3(vertices[i + firstVertexOffset]);
{
int numIndicesTotal = 0;
for (int i = 0; i < partition.polygon_count; i++)
{
int numIndices = Triangulate(data, geometryData, partition.polygons[i], &triangulatedIndices[numIndicesTotal]);
for (int j = numIndicesTotal; j < numIndicesTotal + numIndices; j++)
mesh.Positions.Get()[j] = ToFloat3(positions.get(triangulatedIndices[j]));
numIndicesTotal += numIndices;
}
}
// Indices (dummy index buffer)
if (vertexCount % 3 != 0)
{
errorMsg = TEXT("Invalid vertex count. It must be multiple of 3.");
return true;
}
mesh.Indices.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
mesh.Indices.Get()[i] = i;
// Texture coordinates
if (uvs)
if (uvs.values)
{
mesh.UVs.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
mesh.UVs.Get()[i] = ToFloat2(uvs[i + firstVertexOffset]);
mesh.UVs.Get()[i] = ToFloat2(uvs.get(triangulatedIndices[i]));
if (data.ConvertRH)
{
for (int32 v = 0; v < vertexCount; v++)
@@ -582,7 +736,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
}
// Normals
if (data.Options.CalculateNormals || !normals)
if (data.Options.CalculateNormals || !normals.values)
{
if (mesh.GenerateNormals(data.Options.SmoothingNormalsAngle))
{
@@ -590,11 +744,11 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
return true;
}
}
else if (normals)
else if (normals.values)
{
mesh.Normals.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
mesh.Normals.Get()[i] = ToFloat3(normals[i + firstVertexOffset]);
mesh.Normals.Get()[i] = ToFloat3(normals.get(triangulatedIndices[i]));
if (data.ConvertRH)
{
// Mirror normals along the Z axis
@@ -604,15 +758,15 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
}
// Tangents
if ((data.Options.CalculateTangents || !tangents) && mesh.UVs.HasItems())
if ((data.Options.CalculateTangents || !tangents.values) && mesh.UVs.HasItems())
{
// Generated after full mesh data conversion
}
else if (tangents)
else if (tangents.values)
{
mesh.Tangents.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
mesh.Tangents.Get()[i] = ToFloat3(tangents[i + firstVertexOffset]);
mesh.Tangents.Get()[i] = ToFloat3(tangents.get(triangulatedIndices[i]));
if (data.ConvertRH)
{
// Mirror tangents along the Z axis
@@ -658,12 +812,12 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
}
// Check if has that channel texcoords
const auto lightmapUVs = aGeometry->getUVs(inputChannelIndex);
if (lightmapUVs)
const auto lightmapUVs = geometryData.getUVs(inputChannelIndex);
if (lightmapUVs.values)
{
mesh.LightmapUVs.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
mesh.LightmapUVs.Get()[i] = ToFloat2(lightmapUVs[i + firstVertexOffset]);
mesh.LightmapUVs.Get()[i] = ToFloat2(lightmapUVs.get(triangulatedIndices[i]));
if (data.ConvertRH)
{
for (int32 v = 0; v < vertexCount; v++)
@@ -677,20 +831,20 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
}
// Vertex Colors
if (data.Options.ImportVertexColors && colors)
if (data.Options.ImportVertexColors && colors.values)
{
mesh.Colors.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
mesh.Colors.Get()[i] = ToColor(colors[i + firstVertexOffset]);
mesh.Colors.Get()[i] = ToColor(colors.get(triangulatedIndices[i]));
}
// Blend Indices and Blend Weights
if (skin && skin->getClusterCount() > 0 && EnumHasAnyFlags(data.Options.ImportTypes, ImportDataTypes::Skeleton))
{
mesh.BlendIndices.Resize(vertexCount);
mesh.BlendWeights.Resize(vertexCount);
mesh.BlendIndices.SetAll(Int4::Zero);
mesh.BlendWeights.SetAll(Float4::Zero);
OPEN_FBX_GET_CACHE_LIST(BlendIndicesCache, blendIndices, positions.values_count);
OPEN_FBX_GET_CACHE_LIST(BlendWeightsCache, blendWeights, positions.values_count);
data.BlendIndicesCache.SetAll(Int4::Zero);
data.BlendWeightsCache.SetAll(Float4::Zero);
for (int clusterIndex = 0, clusterCount = skin->getClusterCount(); clusterIndex < clusterCount; clusterIndex++)
{
@@ -718,12 +872,12 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
const double* clusterWeights = cluster->getWeights();
for (int j = 0; j < cluster->getIndicesCount(); j++)
{
int vtxIndex = clusterIndices[j] - firstVertexOffset;
int vtxIndex = clusterIndices[j];
float vtxWeight = (float)clusterWeights[j];
if (vtxWeight <= 0 || vtxIndex < 0 || vtxIndex >= vertexCount)
if (vtxWeight <= 0 || vtxIndex < 0 || vtxIndex >= positions.values_count)
continue;
Int4& indices = mesh.BlendIndices.Get()[vtxIndex];
Float4& weights = mesh.BlendWeights.Get()[vtxIndex];
Int4& indices = blendIndices[vtxIndex];
Float4& weights = blendWeights[vtxIndex];
for (int32 k = 0; k < 4; k++)
{
@@ -745,6 +899,16 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
}
}
// Remap blend values to triangulated data
mesh.BlendIndices.Resize(vertexCount, false);
mesh.BlendWeights.Resize(vertexCount, false);
for (int i = 0; i < vertexCount; i++)
{
const int idx = positions.indices[triangulatedIndices[i]];
mesh.BlendIndices.Get()[i] = blendIndices[idx];
mesh.BlendWeights.Get()[i] = blendWeights[idx];
}
mesh.NormalizeBlendWeights();
}
@@ -756,44 +920,43 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
{
const ofbx::BlendShapeChannel* channel = blendShape->getBlendShapeChannel(channelIndex);
// Use last shape
// Use the last shape
const int targetShapeCount = channel->getShapeCount();
if (targetShapeCount == 0)
continue;
const ofbx::Shape* shape = channel->getShape(targetShapeCount - 1);
if (shape->getVertexCount() != aGeometry->getVertexCount())
const ofbx::Vec3* shapeVertices = shape->getVertices();
const ofbx::Vec3* shapeNormals = shape->getNormals();
const int* shapeIndices = shape->getIndices();
const int shapeVertexCount = shape->getVertexCount();
const int shapeIndexCount = shape->getIndexCount();
if (shapeVertexCount != shapeIndexCount)
{
LOG(Error, "Blend shape '{0}' in mesh '{1}' has different amount of vertices ({2}) than mesh ({3})", String(shape->name), mesh.Name, shape->getVertexCount(), aGeometry->getVertexCount());
LOG(Error, "Blend shape '{0}' in mesh '{1}' has different amount of vertices ({2}) and indices ({3})", String(shape->name), mesh.Name, shapeVertexCount, shapeIndexCount);
continue;
}
BlendShape& blendShapeData = mesh.BlendShapes.AddOne();
blendShapeData.Name = shape->name;
blendShapeData.Weight = channel->getShapeCount() > 1 ? (float)(channel->getDeformPercent() / 100.0) : 1.0f;
blendShapeData.Vertices.EnsureCapacity(shapeIndexCount);
blendShapeData.Vertices.Resize(vertexCount);
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
blendShapeData.Vertices.Get()[i].VertexIndex = i;
auto shapeVertices = shape->getVertices();
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
for (int32 i = 0; i < shapeIndexCount; i++)
{
auto delta = ToFloat3(shapeVertices[i + firstVertexOffset]) - mesh.Positions.Get()[i];
blendShapeData.Vertices.Get()[i].PositionDelta = delta;
}
auto shapeNormals = shape->getNormals();
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
{
auto delta = ToFloat3(shapeNormals[i + firstVertexOffset]);
if (data.ConvertRH)
int shapeIndex = shapeIndices[i];
BlendShapeVertex v;
v.PositionDelta = ToFloat3(shapeVertices[i]);
v.NormalDelta = shapeNormals ? ToFloat3(shapeNormals[i]) : Float3::Zero;
for (int32 vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
{
// Mirror normals along the Z axis
delta.Z *= -1.0f;
int sourceIndex = positions.indices[triangulatedIndices[vertexIndex]];
if (sourceIndex == shapeIndex)
{
// Add blend shape vertex
v.VertexIndex = vertexIndex;
blendShapeData.Vertices.Add(v);
}
}
delta = delta - mesh.Normals.Get()[i];
blendShapeData.Vertices.Get()[i].NormalDelta = delta;
}
}
}
@@ -806,7 +969,10 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
for (auto& blendShapeData : mesh.BlendShapes)
{
for (auto& v : blendShapeData.Vertices)
{
v.PositionDelta.Z *= -1.0f;
v.NormalDelta.Z *= -1.0f;
}
}
}
@@ -820,7 +986,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
Swap(mesh.Indices.Get()[i], mesh.Indices.Get()[i + 2]);
}
if ((data.Options.CalculateTangents || !tangents) && mesh.UVs.HasItems())
if ((data.Options.CalculateTangents || !tangents.values) && mesh.UVs.HasItems())
{
if (mesh.GenerateTangents(data.Options.SmoothingTangentsAngle))
{
@@ -858,7 +1024,7 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
return false;
}
bool ImportMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* aMesh, String& errorMsg, int32 triangleStart, int32 triangleEnd)
bool ImportMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh* aMesh, String& errorMsg, int partitionIndex)
{
PROFILE_CPU();
@@ -899,7 +1065,7 @@ bool ImportMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
// Import mesh data
MeshData* meshData = New<MeshData>();
if (ProcessMesh(result, data, aMesh, *meshData, errorMsg, triangleStart, triangleEnd))
if (ProcessMesh(result, data, aMesh, *meshData, errorMsg, partitionIndex))
return true;
// Link mesh
@@ -916,36 +1082,17 @@ bool ImportMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
bool ImportMesh(int32 index, ModelData& result, OpenFbxImporterData& data, String& errorMsg)
{
const auto aMesh = data.Scene->getMesh(index);
const auto aGeometry = aMesh->getGeometry();
const auto trianglesCount = aGeometry->getVertexCount() / 3;
if (IsMeshInvalid(aMesh))
return false;
if (aMesh->getMaterialCount() < 2 || !aGeometry->getMaterials())
const auto& geomData = aMesh->getGeometryData();
for (int i = 0; i < geomData.getPartitionCount(); i++)
{
// Fast path if mesh is using single material for all triangles
if (ImportMesh(result, data, aMesh, errorMsg, 0, trianglesCount - 1))
return true;
}
else
{
// Create mesh for each sequence of triangles that share the same material
const auto materials = aGeometry->getMaterials();
int32 rangeStart = 0;
int32 rangeStartVal = materials[rangeStart];
for (int32 triangleIndex = 1; triangleIndex < trianglesCount; triangleIndex++)
{
if (rangeStartVal != materials[triangleIndex])
{
if (ImportMesh(result, data, aMesh, errorMsg, rangeStart, triangleIndex - 1))
return true;
const auto& partition = geomData.getPartition(i);
if (partition.polygon_count == 0)
continue;
// Start a new range
rangeStart = triangleIndex;
rangeStartVal = materials[triangleIndex];
}
}
if (ImportMesh(result, data, aMesh, errorMsg, rangeStart, trianglesCount - 1))
if (ImportMesh(result, data, aMesh, errorMsg, i))
return true;
}
return false;
@@ -962,35 +1109,35 @@ struct AnimInfo
struct Frame
{
ofbx::Vec3 Translation;
ofbx::Vec3 Rotation;
ofbx::Vec3 Scaling;
ofbx::DVec3 Translation;
ofbx::DVec3 Rotation;
ofbx::DVec3 Scaling;
};
void ExtractKeyframePosition(const ofbx::Object* bone, ofbx::Vec3& trans, const Frame& localFrame, Float3& keyframe)
void ExtractKeyframePosition(const ofbx::Object* bone, ofbx::DVec3& trans, const Frame& localFrame, Float3& keyframe)
{
const Matrix frameTrans = ToMatrix(bone->evalLocal(trans, localFrame.Rotation, localFrame.Scaling));
keyframe = frameTrans.GetTranslation();
}
void ExtractKeyframeRotation(const ofbx::Object* bone, ofbx::Vec3& trans, const Frame& localFrame, Quaternion& keyframe)
void ExtractKeyframeRotation(const ofbx::Object* bone, ofbx::DVec3& trans, const Frame& localFrame, Quaternion& keyframe)
{
const Matrix frameTrans = ToMatrix(bone->evalLocal(localFrame.Translation, trans, { 1.0, 1.0, 1.0 }));
Quaternion::RotationMatrix(frameTrans, keyframe);
}
void ExtractKeyframeScale(const ofbx::Object* bone, ofbx::Vec3& trans, const Frame& localFrame, Float3& keyframe)
void ExtractKeyframeScale(const ofbx::Object* bone, ofbx::DVec3& trans, const Frame& localFrame, Float3& keyframe)
{
// Fix empty scale case
if (Math::IsZero(trans.x) && Math::IsZero(trans.y) && Math::IsZero(trans.z))
trans = { 1.0, 1.0, 1.0 };
const Matrix frameTrans = ToMatrix(bone->evalLocal(localFrame.Translation, localFrame.Rotation, trans));
const Matrix frameTrans = ToMatrix(bone->evalLocal(localFrame.Translation, { 0.0, 0.0, 0.0 }, trans));
keyframe = frameTrans.GetScaleVector();
}
template<typename T>
void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve<T>& curve, AnimInfo& info, void (*ExtractKeyframe)(const ofbx::Object*, ofbx::Vec3&, const Frame&, T&))
void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve<T>& curve, AnimInfo& info, void (*ExtractKeyframe)(const ofbx::Object*, ofbx::DVec3&, const Frame&, T&))
{
if (curveNode == nullptr)
return;
@@ -1008,7 +1155,7 @@ void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve<T>& curv
key.Time = (float)i;
ofbx::Vec3 trans = curveNode->getNodeLocalTransform(t);
ofbx::DVec3 trans = curveNode->getNodeLocalTransform(t);
ExtractKeyframe(bone, trans, localFrame, key.Value);
}
}
@@ -1125,21 +1272,26 @@ bool ModelTool::ImportDataOpenFBX(const String& path, ModelData& data, Options&
errorMsg = TEXT("Cannot load file.");
return true;
}
ofbx::u64 loadFlags = 0;
ofbx::LoadFlags loadFlags = ofbx::LoadFlags::NONE;
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry))
{
loadFlags |= (ofbx::u64)ofbx::LoadFlags::TRIANGULATE;
if (!options.ImportBlendShapes)
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
loadFlags |= ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
}
else
{
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_GEOMETRY | (ofbx::u64)ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
loadFlags |= ofbx::LoadFlags::IGNORE_GEOMETRY | ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
}
if (EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Materials))
loadFlags |= ofbx::LoadFlags::IGNORE_MATERIALS;
if (EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Textures))
loadFlags |= ofbx::LoadFlags::IGNORE_TEXTURES;
if (EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Animations))
loadFlags |= ofbx::LoadFlags::IGNORE_ANIMATIONS;
ofbx::IScene* scene;
{
PROFILE_CPU_NAMED("ofbx::load");
scene = ofbx::load(fileData.Get(), fileData.Count(), loadFlags);
scene = ofbx::load(fileData.Get(), fileData.Count(), (ofbx::u16)loadFlags);
}
if (!scene)
{

View File

@@ -1349,31 +1349,31 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
mesh->BlendIndices.SetAll(indices);
mesh->BlendWeights.SetAll(weights);
}
#if BUILD_DEBUG
else
{
auto& indices = mesh->BlendIndices;
for (int32 j = 0; j < indices.Count(); j++)
{
const int32 min = indices[j].MinValue();
const int32 max = indices[j].MaxValue();
const Int4 ij = indices.Get()[j];
const int32 min = ij.MinValue();
const int32 max = ij.MaxValue();
if (min < 0 || max >= data.Skeleton.Bones.Count())
{
LOG(Warning, "Imported mesh \'{0}\' has invalid blend indices. It may result in invalid rendering.", mesh->Name);
break;
}
}
auto& weights = mesh->BlendWeights;
for (int32 j = 0; j < weights.Count(); j++)
{
const float sum = weights[j].SumValues();
const float sum = weights.Get()[j].SumValues();
if (Math::Abs(sum - 1.0f) > ZeroTolerance)
{
LOG(Warning, "Imported mesh \'{0}\' has invalid blend weights. It may result in invalid rendering.", mesh->Name);
break;
}
}
}
#endif
}
}
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations))