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

# Conflicts:
#	Source/Editor/SceneGraph/Actors/StaticModelNode.cs
#	Source/Engine/Graphics/Models/Mesh.cs
#	Source/Engine/Graphics/Models/ModelData.h
This commit is contained in:
2025-03-13 11:23:01 +01:00
34 changed files with 834 additions and 99 deletions
+34 -27
View File
@@ -65,6 +65,7 @@ namespace ALC
struct SourceData
{
AudioDataInfo Format;
float Pan;
bool Spatial;
};
@@ -106,6 +107,26 @@ namespace ALC
namespace Source
{
void SetupSpatial(uint32 sourceID, float pan, bool spatial)
{
alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial); // Non-spatial sounds use AL_POSITION for panning
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, spatial || Math::Abs(pan) > ZeroTolerance ? AL_TRUE : AL_FALSE); // Fix multi-channel sources played as spatial or non-spatial sources played with panning
#endif
if (spatial)
{
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
}
else
{
alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan));
}
}
void Rebuild(uint32& sourceID, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
ASSERT_LOW_LAYER(sourceID == 0);
@@ -116,11 +137,8 @@ namespace ALC
alSourcef(sourceID, AL_PITCH, pitch);
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcei(sourceID, AL_LOOPING, loop);
alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial); // Non-spatial sounds use AL_POSITION for panning
alSourcei(sourceID, AL_BUFFER, 0);
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE); // Always spatialize, fixes multi-channel played as spatial
#endif
SetupSpatial(sourceID, pan, spatial);
if (spatial)
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
@@ -128,18 +146,12 @@ namespace ALC
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(Vector3::Zero));
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan));
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
}
@@ -160,12 +172,11 @@ namespace ALC
if (Device == nullptr)
return;
ALCint attrsHrtf[] = { ALC_HRTF_SOFT, ALC_TRUE };
const ALCint* attrList = nullptr;
ALCint attrList[] = { ALC_HRTF_SOFT, ALC_FALSE };
if (Audio::GetEnableHRTF())
{
LOG(Info, "Enabling OpenAL HRTF");
attrList = attrsHrtf;
attrList[1] = ALC_TRUE;
}
Context = alcCreateContext(Device, attrList);
@@ -318,6 +329,7 @@ uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& p
auto& data = ALC::SourcesData[sourceID];
data.Format = format;
data.Spatial = spatial;
data.Pan = pan;
ALC::Locker.Unlock();
return sourceID;
@@ -370,20 +382,11 @@ void AudioBackendOAL::Source_PitchChanged(uint32 sourceID, float pitch)
void AudioBackendOAL::Source_PanChanged(uint32 sourceID, float pan)
{
ALC::Locker.Lock();
const bool spatial = ALC::SourcesData[sourceID].Spatial;
auto& e = ALC::SourcesData[sourceID];
e.Pan = pan;
const bool spatial = e.Spatial;
ALC::Locker.Unlock();
if (spatial)
{
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
}
else
{
alSource3f(sourceID, AL_POSITION, pan, 0, -sqrtf(1.0f - pan * pan));
}
ALC::Source::SetupSpatial(sourceID, pan, spatial);
}
void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
@@ -393,6 +396,9 @@ void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{
ALC::Locker.Lock();
const bool pan = ALC::SourcesData[sourceID].Spatial;
ALC::Locker.Unlock();
if (spatial)
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
@@ -405,6 +411,7 @@ void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial,
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
}
ALC::Source::SetupSpatial(sourceID, pan, spatial);
}
void AudioBackendOAL::Source_Play(uint32 sourceID)
@@ -602,7 +609,7 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferID, byte* samples, const AudioDa
if (!format)
{
LOG(Error, "Not suppported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels);
LOG(Error, "Not supported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels);
}
}
@@ -159,19 +159,19 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
else
{
// Split audio data into a several chunks (uniform data spread)
const int32 minChunkSize = 1 * 1024 * 1024; // 1 MB
const int32 dataAlignment = info.NumChannels * bytesPerSample; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes)
const int32 chunkSize = Math::Max<int32>(minChunkSize, (int32)Math::AlignUp<uint32>(bufferSize / ASSET_FILE_DATA_CHUNKS, dataAlignment));
const int32 chunksCount = Math::CeilToInt((float)bufferSize / chunkSize);
const uint32 minChunkSize = 1 * 1024 * 1024; // 1 MB
const uint32 dataAlignment = info.NumChannels * bytesPerSample * ASSET_FILE_DATA_CHUNKS; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes)
const uint32 chunkSize = Math::AlignUp(Math::Max(minChunkSize, bufferSize / ASSET_FILE_DATA_CHUNKS), dataAlignment);
const int32 chunksCount = Math::CeilToInt((float)bufferSize / (float)chunkSize);
ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS);
byte* ptr = sampleBuffer.Get();
int32 size = bufferSize;
uint32 size = bufferSize;
for (int32 chunkIndex = 0; chunkIndex < chunksCount; chunkIndex++)
{
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
const int32 t = Math::Min(size, chunkSize);
const uint32 t = Math::Min(size, chunkSize);
WRITE_DATA(chunkIndex, ptr, t);
+20
View File
@@ -520,4 +520,24 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS
return result;
}
#if USE_EDITOR
Array<Vector3> Mesh::GetCollisionProxyPoints() const
{
PROFILE_CPU();
Array<Vector3> result;
#if USE_PRECISE_MESH_INTERSECTS
for (int32 i = 0; i < _collisionProxy.Triangles.Count(); i++)
{
auto triangle = _collisionProxy.Triangles.Get()[i];
result.Add(triangle.V0);
result.Add(triangle.V1);
result.Add(triangle.V2);
}
#endif
return result;
}
#endif
#endif
+11
View File
@@ -536,5 +536,16 @@ namespace FlaxEngine
return result;
}
#if FLAX_EDITOR
/// <summary>
/// Gets the collision proxy points for the mesh.
/// </summary>
/// <returns>The triangle points in the collision proxy.</returns>
internal Vector3[] GetCollisionProxyPoints()
{
return Internal_GetCollisionProxyPoints(__unmanagedPtr, out _);
}
#endif
}
}
+3
View File
@@ -171,5 +171,8 @@ private:
API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
#if USE_EDITOR
API_FUNCTION(NoProxy) Array<Vector3> GetCollisionProxyPoints() const;
#endif
#endif
};
+1 -1
View File
@@ -91,7 +91,7 @@ public:
int32 LightmapUVsIndex = -1;
/// <summary>
/// Global translation for this mesh to be at its local origin.
/// Local translation for this mesh to be at it's local origin.
/// </summary>
Vector3 OriginTranslation = Vector3::Zero;
@@ -9,6 +9,11 @@ ModelInstanceActor::ModelInstanceActor(const SpawnParams& params)
{
}
String ModelInstanceActor::MeshReference::ToString() const
{
return String::Format(TEXT("Actor={},LOD={},Mesh={}"), Actor ? Actor->GetNamePath() : String::Empty, LODIndex, MeshIndex);
}
void ModelInstanceActor::SetEntries(const Array<ModelInstanceEntry>& value)
{
WaitForModelLoad();
@@ -27,6 +27,8 @@ API_CLASS(Abstract) class FLAXENGINE_API ModelInstanceActor : public Actor
API_FIELD() int32 LODIndex = 0;
// Index of the mesh (within the LOD).
API_FIELD() int32 MeshIndex = 0;
String ToString() const;
};
protected:
+4
View File
@@ -647,6 +647,7 @@ void Cloth::CalculateInvMasses(Array<float>& invMasses)
if (_paint.Count() != verticesCount)
{
// Fix incorrect paint data
LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), mesh.ToString(), verticesCount);
int32 countBefore = _paint.Count();
_paint.Resize(verticesCount);
for (int32 i = countBefore; i < verticesCount; i++)
@@ -795,7 +796,10 @@ bool Cloth::OnPreUpdate()
if (!positionStream.IsValid() || !blendIndicesStream.IsValid() || !blendWeightsStream.IsValid())
return false;
if (verticesCount != _paint.Count())
{
LOG(Warning, "Incorrect cloth '{}' paint size {} for mesh '{}' that has {} vertices", GetNamePath(), _paint.Count(), mesh.ToString(), verticesCount);
return false;
}
PROFILE_CPU_NAMED("Skinned Pose");
PhysicsBackend::LockClothParticles(_cloth);
const Span<const Float4> particles = PhysicsBackend::GetClothParticles(_cloth);
+4 -4
View File
@@ -399,7 +399,7 @@ public:
/// </summary>
/// <param name="text">The input text to test.</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
/// <returns>The minimum size for that text and font to render properly.</returns>
API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextLayoutOptions& layout);
/// <summary>
@@ -408,7 +408,7 @@ public:
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <param name="layout">The layout properties.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
/// <returns>The minimum size for that text and font to render properly.</returns>
API_FUNCTION() Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange, API_PARAM(Ref) const TextLayoutOptions& layout)
{
return MeasureText(textRange.Substring(text), layout);
@@ -418,7 +418,7 @@ public:
/// Measures minimum size of the rectangle that will be needed to draw given text
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
/// <returns>The minimum size for that text and font to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text)
{
return MeasureText(text, TextLayoutOptions());
@@ -429,7 +429,7 @@ public:
/// </summary>.
/// <param name="text">The input text to test.</param>
/// <param name="textRange">The input text range (substring range of the input text parameter).</param>
/// <returns>The minimum size for that text and fot to render properly.</returns>
/// <returns>The minimum size for that text and font to render properly.</returns>
API_FUNCTION() FORCE_INLINE Float2 MeasureText(const StringView& text, API_PARAM(Ref) const TextRange& textRange)
{
return MeasureText(textRange.Substring(text), TextLayoutOptions());
+5
View File
@@ -732,7 +732,12 @@ Array<ScriptingObject*, HeapAllocation> Scripting::GetObjects()
{
Array<ScriptingObject*> objects;
_objectsLocker.Lock();
#if USE_OBJECTS_DISPOSE_CRASHES_DEBUGGING
for (const auto& e : _objectsDictionary)
objects.Add(e.Value.Ptr);
#else
_objectsDictionary.GetValues(objects);
#endif
_objectsLocker.Unlock();
return objects;
}
+1 -1
View File
@@ -185,7 +185,7 @@ Ray JsonTools::GetRay(const Value& value)
{
return Ray(
GetVector3(value, "Position", Vector3::Zero),
GetVector3(value, "Direction", Vector3::One)
GetVector3(value, "Direction", Vector3::Forward)
);
}
+4 -2
View File
@@ -4,6 +4,7 @@
#include "AudioTool.h"
#include "Engine/Core/Core.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Memory/Allocation.h"
#if USE_EDITOR
#include "Engine/Serialization/Serialization.h"
@@ -307,8 +308,9 @@ void AudioTool::ConvertFromFloat(const float* input, int32* output, uint32 numSa
{
for (uint32 i = 0; i < numSamples; i++)
{
const float sample = *(float*)input;
output[i] = static_cast<int32>(sample * 2147483647.0f);
float sample = *(float*)input;
sample = Math::Clamp(sample, -1.0f + ZeroTolerance, +1.0f - ZeroTolerance);
output[i] = static_cast<int32>(sample * 2147483648.0f);
input++;
}
}
@@ -597,24 +597,10 @@ bool ImportMesh(int32 index, ModelData& result, AssimpImporterData& data, String
// Link mesh
meshData->NodeIndex = nodeIndex;
AssimpNode* curNode = &data.Nodes[meshData->NodeIndex];
Vector3 translation = Vector3::Zero;
Vector3 scale = Vector3::One;
Quaternion rotation = Quaternion::Identity;
while (true)
{
translation += curNode->LocalTransform.Translation;
scale *= curNode->LocalTransform.Scale;
rotation *= curNode->LocalTransform.Orientation;
if (curNode->ParentIndex == -1)
break;
curNode = &data.Nodes[curNode->ParentIndex];
}
meshData->OriginTranslation = translation;
meshData->OriginOrientation = rotation;
meshData->Scaling = scale;
meshData->OriginTranslation = curNode->LocalTransform.Translation;
meshData->OriginOrientation = curNode->LocalTransform.Orientation;
meshData->Scaling = curNode->LocalTransform.Scale;
if (result.LODs.Count() <= lodIndex)
result.LODs.Resize(lodIndex + 1);
@@ -992,7 +992,8 @@ bool ProcessMesh(ModelData& result, OpenFbxImporterData& data, const ofbx::Mesh*
}*/
// Get local transform for origin shifting translation
auto translation = ToMatrix(aMesh->getGlobalTransform()).GetTranslation();
auto translation = Float3((float)aMesh->getLocalTranslation().x, (float)aMesh->getLocalTranslation().y, (float)aMesh->getLocalTranslation().z);
auto scale = data.GlobalSettings.UnitScaleFactor;
if (data.GlobalSettings.CoordAxis == ofbx::CoordSystem_RightHanded)
mesh.OriginTranslation = scale * Vector3(translation.X, translation.Y, -translation.Z);
+39 -15
View File
@@ -1549,23 +1549,25 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
// Prepare import transformation
Transform importTransform(options.Translation, options.Rotation, Float3(options.Scale));
if (options.UseLocalOrigin && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
importTransform.Translation -= importTransform.Orientation * data.LODs[0].Meshes[0]->OriginTranslation * importTransform.Scale;
}
if (options.CenterGeometry && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
// Calculate the bounding box (use LOD0 as a reference)
BoundingBox box = data.LODs[0].GetBox();
auto center = data.LODs[0].Meshes[0]->OriginOrientation * importTransform.Orientation * box.GetCenter() * importTransform.Scale * data.LODs[0].Meshes[0]->Scaling;
importTransform.Translation -= center;
}
// Apply the import transformation
if (!importTransform.IsIdentity() && data.Nodes.HasItems())
if ((!importTransform.IsIdentity() || options.UseLocalOrigin || options.CenterGeometry) && data.Nodes.HasItems())
{
if (options.Type == ModelType::SkinnedModel)
{
// Setup other transform options
if (options.UseLocalOrigin && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
importTransform.Translation -= importTransform.Orientation * data.LODs[0].Meshes[0]->OriginTranslation * importTransform.Scale;
}
if (options.CenterGeometry && data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
// Calculate the bounding box (use LOD0 as a reference)
BoundingBox box = data.LODs[0].GetBox();
auto center = data.LODs[0].Meshes[0]->OriginOrientation * importTransform.Orientation * box.GetCenter() * importTransform.Scale * data.LODs[0].Meshes[0]->Scaling;
importTransform.Translation -= center;
}
// Transform the root node using the import transformation
auto& root = data.Skeleton.RootNode();
Transform meshTransform = root.LocalTransform.WorldToLocal(importTransform).LocalToWorld(root.LocalTransform);
@@ -1596,9 +1598,31 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
}
else
{
// Transform the root node using the import transformation
auto& root = data.Nodes[0];
root.LocalTransform = importTransform.LocalToWorld(root.LocalTransform);
// Transform the nodes using the import transformation
if (data.LODs.HasItems() && data.LODs[0].Meshes.HasItems())
{
for (int i = 0; i < data.LODs[0].Meshes.Count(); ++i)
{
auto* meshData = data.LODs[0].Meshes[i];
Transform transform = importTransform;
if (options.UseLocalOrigin)
{
transform.Translation -= transform.Orientation * meshData->OriginTranslation * transform.Scale;
}
if (options.CenterGeometry)
{
// Calculate the bounding box (use LOD0 as a reference)
BoundingBox box = data.LODs[0].GetBox();
auto center = meshData->OriginOrientation * transform.Orientation * box.GetCenter() * transform.Scale * meshData->Scaling;
transform.Translation -= center;
}
int32 nodeIndex = meshData->NodeIndex;
auto& node = data.Nodes[nodeIndex];
node.LocalTransform = transform.LocalToWorld(node.LocalTransform);
}
}
}
}