Merge branch 'FlaxEngine:master' into network-replication-fix-1

This commit is contained in:
Menotdan
2026-02-05 19:19:45 -05:00
committed by GitHub
114 changed files with 2147 additions and 726 deletions

View File

@@ -958,6 +958,21 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
}
break;
}
// Set Parameter
case 5:
{
// Set parameter value
int32 paramIndex;
const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex);
if (param)
{
context.Data->Parameters[paramIndex].Value = tryGetValue(node->GetBox(1), 1, Value::Null);
}
// Pass over the pose
value = tryGetValue(node->GetBox(2), Value::Null);
break;
}
default:
break;
}

View File

@@ -132,9 +132,7 @@ int32 AudioClip::GetFirstBufferIndex(float time, float& offset) const
if (_buffersStartTimes[i + 1] > time)
{
offset = time - _buffersStartTimes[i];
#if BUILD_DEBUG
ASSERT(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f);
#endif
ASSERT_LOW_LAYER(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f);
return i;
}
}

View File

@@ -18,6 +18,7 @@ class AudioSource;
API_CLASS(NoSpawn) class FLAXENGINE_API AudioClip : public BinaryAsset, public StreamableResource
{
DECLARE_BINARY_ASSET_HEADER(AudioClip, 2);
friend class AudioBackendOAL;
public:
/// <summary>

View File

@@ -37,15 +37,17 @@ void AudioListener::OnEnable()
{
_prevPos = GetPosition();
_velocity = Vector3::Zero;
ASSERT(!Audio::Listeners.Contains(this));
if (Audio::Listeners.Count() >= AUDIO_MAX_LISTENERS)
{
LOG(Error, "Unsupported amount of the audio listeners!");
if IF_CONSTEXPR (AUDIO_MAX_LISTENERS == 1)
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
else
LOG(Warning, "Too many Audio Listener active.");
}
else
{
ASSERT(!Audio::Listeners.Contains(this));
if (Audio::Listeners.Count() > 0)
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
Audio::Listeners.Add(this);
AudioBackend::Listener::Reset();
AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation());

View File

@@ -14,6 +14,9 @@
#include "Engine/Audio/AudioListener.h"
#include "Engine/Audio/AudioSource.h"
#include "Engine/Audio/AudioSettings.h"
#include "Engine/Content/Content.h"
#include "Engine/Level/Level.h"
#include "Engine/Video/VideoPlayer.h"
// Include OpenAL library
// Source: https://github.com/kcat/openal-soft
@@ -73,6 +76,7 @@ namespace ALC
ALCdevice* Device = nullptr;
ALCcontext* Context = nullptr;
AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None;
bool Inited = false;
CriticalSection Locker;
Dictionary<uint32, SourceData> SourcesData;
@@ -164,12 +168,9 @@ namespace ALC
float Time;
};
void RebuildContext(const Array<AudioSourceState>& states)
void RebuildContext()
{
LOG(Info, "Rebuilding audio contexts");
ClearContext();
if (Device == nullptr)
return;
@@ -182,10 +183,16 @@ namespace ALC
Context = alcCreateContext(Device, attrList);
alcMakeContextCurrent(Context);
}
void RebuildListeners()
{
for (AudioListener* listener : Audio::Listeners)
Listener::Rebuild(listener);
}
void RebuildSources(const Array<AudioSourceState>& states)
{
for (int32 i = 0; i < states.Count(); i++)
{
AudioSource* source = Audio::Sources[i];
@@ -205,6 +212,13 @@ namespace ALC
}
}
void RebuildContext(const Array<AudioSourceState>& states)
{
RebuildContext();
RebuildListeners();
RebuildSources(states);
}
void RebuildContext(bool isChangingDevice)
{
Array<AudioSourceState> states;
@@ -400,7 +414,7 @@ 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;
const float pan = ALC::SourcesData[sourceID].Pan;
ALC::Locker.Unlock();
if (spatial)
{
@@ -629,6 +643,7 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
void AudioBackendOAL::Base_OnActiveDeviceChanged()
{
PROFILE_CPU();
PROFILE_MEM(Audio);
// Cleanup
@@ -659,9 +674,53 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
LOG(Fatal, "Failed to open OpenAL device ({0}).", String(name));
return;
}
if (ALC::Inited)
LOG(Info, "Changed audio device to: {}", String(Audio::GetActiveDevice()->Name));
// Setup
ALC::RebuildContext(states);
// Rebuild context
ALC::RebuildContext();
if (ALC::Inited)
{
// Reload all audio clips to recreate their buffers
for (AudioClip* audioClip : Content::GetAssets<AudioClip>())
{
audioClip->WaitForLoaded();
ScopeLock lock(audioClip->Locker);
// Clear old buffer IDs
for (uint32& bufferID : audioClip->Buffers)
bufferID = 0;
if (audioClip->IsStreamable())
{
// Let the streaming recreate missing buffers
audioClip->RequestStreamingUpdate();
}
else
{
// Reload audio clip
auto assetLock = audioClip->Storage->Lock();
audioClip->LoadChunk(0);
audioClip->Buffers[0] = AudioBackend::Buffer::Create();
audioClip->WriteBuffer(0);
}
}
// Reload all videos to recreate their buffers
for (VideoPlayer* videoPlayer : Level::GetActors<VideoPlayer>(true))
{
VideoBackendPlayer& player = videoPlayer->_player;
// Clear audio state
for (uint32& bufferID : player.AudioBuffers)
bufferID = 0;
player.NextAudioBuffer = 0;
player.AudioSource = 0;
}
}
ALC::RebuildListeners();
ALC::RebuildSources(states);
}
void AudioBackendOAL::Base_SetDopplerFactor(float value)
@@ -782,6 +841,7 @@ bool AudioBackendOAL::Base_Init()
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel);
#endif
ALC::Inited = true;
// Log service info
LOG(Info, "{0} ({1})", String(alGetString(AL_RENDERER)), String(alGetString(AL_VERSION)));

View File

@@ -672,7 +672,7 @@ bool AudioBackendXAudio2::Base_Init()
HRESULT hr = XAudio2Create(&XAudio2::Instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
if (FAILED(hr))
{
LOG(Error, "Failed to initalize XAudio2. Error: 0x{0:x}", hr);
LOG(Error, "Failed to initialize XAudio2. Error: 0x{0:x}", hr);
return true;
}
XAudio2::Instance->RegisterForCallbacks(&XAudio2::Callback);
@@ -681,7 +681,7 @@ bool AudioBackendXAudio2::Base_Init()
hr = XAudio2::Instance->CreateMasteringVoice(&XAudio2::MasteringVoice);
if (FAILED(hr))
{
LOG(Error, "Failed to initalize XAudio2 mastering voice. Error: 0x{0:x}", hr);
LOG(Error, "Failed to initialize XAudio2 mastering voice. Error: 0x{0:x}", hr);
return true;
}
XAUDIO2_VOICE_DETAILS details;

View File

@@ -487,6 +487,8 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
if (loadingTask == nullptr)
{
if (IsLoaded())
return false;
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
return true;
}

View File

@@ -684,6 +684,19 @@ Array<Asset*> Content::GetAssets()
return assets;
}
Array<Asset*> Content::GetAssets(const MClass* type)
{
Array<Asset*> assets;
AssetsLocker.Lock();
for (auto& e : Assets)
{
if (e.Value->Is(type))
assets.Add(e.Value);
}
AssetsLocker.Unlock();
return assets;
}
const Dictionary<Guid, Asset*>& Content::GetAssetsRaw()
{
AssetsLocker.Lock();

View File

@@ -3,6 +3,9 @@
#pragma once
#include "Engine/Scripting/ScriptingType.h"
#ifndef _MSC_VER
#include "Engine/Core/Collections/Array.h"
#endif
#include "AssetInfo.h"
#include "Asset.h"
#include "Config.h"
@@ -122,7 +125,26 @@ public:
/// Gets the assets (loaded or during load).
/// </summary>
/// <returns>The collection of assets.</returns>
static Array<Asset*, HeapAllocation> GetAssets();
API_FUNCTION() static Array<Asset*, HeapAllocation> GetAssets();
/// <summary>
/// Gets the assets (loaded or during load).
/// </summary>
/// <param name="type">Type of the assets to search for. Includes any assets derived from the type.</param>
/// <returns>Found actors list.</returns>
API_FUNCTION() static Array<Asset*, HeapAllocation> GetAssets(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type);
/// <summary>
/// Gets the assets (loaded or during load).
/// </summary>
/// <typeparam name="T">Type of the object.</typeparam>
/// <returns>Found actors list.</returns>
template<typename T>
static Array<T*, HeapAllocation> GetAssets()
{
Array<Asset*, HeapAllocation> assets = GetAssets(T::GetStaticClass());
return *(Array<T*, HeapAllocation>*) & assets;
}
/// <summary>
/// Gets the raw dictionary of assets (loaded or during load).

View File

@@ -478,16 +478,23 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
}
// Check if restore local changes on asset reimport
constexpr bool RestoreModelOptionsOnReimport = true;
constexpr bool RestoreAnimEventsOnReimport = true;
const bool restoreModelOptions = RestoreModelOptionsOnReimport && (options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel);
const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
if ((restoreModelOptions || restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
{
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset && !asset->WaitForLoaded())
{
auto* model = ScriptingObject::Cast<ModelBase>(asset);
auto* animation = ScriptingObject::Cast<Animation>(asset);
if (restoreModelOptions && model)
{
// Copy general properties
data->MinScreenSize = model->MinScreenSize;
}
if (restoreMaterials && model)
{
// Copy material settings

View File

@@ -57,5 +57,5 @@
#define API_PARAM(...)
#define API_TYPEDEF(...)
#define API_INJECT_CODE(...)
#define API_AUTO_SERIALIZATION(...) public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
#define API_AUTO_SERIALIZATION(...) public: bool ShouldSerialize(const void* otherObj) const override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer;

View File

@@ -36,6 +36,13 @@ public:
/// </summary>
virtual ~ISerializable() = default;
/// <summary>
/// Compares with other instance to decide whether serialize this instance (eg. any field orp property is modified). Used to skip object serialization if not needed.
/// </summary>
/// <param name="otherObj">The instance of the object (always valid) to compare with to decide whether serialize this instance.</param>
/// <returns>True if any field or property is modified compared to the other object instance, otherwise false.</returns>
virtual bool ShouldSerialize(const void* otherObj) const { return true; }
/// <summary>
/// Serializes object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties.
/// </summary>

View File

@@ -11,7 +11,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.ColorConverter))]
#endif
partial struct Color
partial struct Color : Json.ICustomValueEquals
{
/// <summary>
/// The size of the <see cref="Color" /> type, in bytes.
@@ -196,6 +196,13 @@ namespace FlaxEngine
A = values[3];
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Color)other;
return Equals(ref o);
}
/// <inheritdoc />
public override bool Equals(object value)
{

View File

@@ -65,7 +65,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double2Converter))]
#endif
partial struct Double2 : IEquatable<Double2>, IFormattable
partial struct Double2 : IEquatable<Double2>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
@@ -1574,6 +1574,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Double2)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Double2" /> is equal to this instance.
/// </summary>

View File

@@ -66,7 +66,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double3Converter))]
#endif
partial struct Double3 : IEquatable<Double3>, IFormattable
partial struct Double3 : IEquatable<Double3>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
@@ -1872,6 +1872,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Double3)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Double3" /> is equal to this instance.
/// </summary>

View File

@@ -66,7 +66,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double4Converter))]
#endif
partial struct Double4 : IEquatable<Double4>, IFormattable
partial struct Double4 : IEquatable<Double4>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
@@ -1372,6 +1372,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Double4)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Double4" /> is equal to this instance.
/// </summary>

View File

@@ -60,7 +60,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float2Converter))]
#endif
partial struct Float2 : IEquatable<Float2>, IFormattable
partial struct Float2 : IEquatable<Float2>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
@@ -1650,6 +1650,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Float2)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Float2" /> is equal to this instance.
/// </summary>

View File

@@ -60,7 +60,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float3Converter))]
#endif
partial struct Float3 : IEquatable<Float3>, IFormattable
partial struct Float3 : IEquatable<Float3>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
@@ -1904,6 +1904,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Float3)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Float3" /> is equal to this instance.
/// </summary>

View File

@@ -60,7 +60,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float4Converter))]
#endif
partial struct Float4 : IEquatable<Float4>, IFormattable
partial struct Float4 : IEquatable<Float4>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
@@ -1412,6 +1412,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Float4)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Float4" /> is equal to this instance.
/// </summary>

View File

@@ -5,6 +5,7 @@
#include "Math.h"
#include "Vector2.h"
#include "Vector3.h"
#include "Vector4.h"
/// <summary>
/// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa
@@ -248,6 +249,19 @@ public:
explicit Half4(const Color& c);
explicit Half4(const Rectangle& rect);
operator Float2() const
{
return ToFloat2();
}
operator Float3() const
{
return ToFloat3();
}
operator Float4() const
{
return ToFloat4();
}
public:
Float2 ToFloat2() const;
Float3 ToFloat3() const;

View File

@@ -14,7 +14,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int2Converter))]
#endif
partial struct Int2 : IEquatable<Int2>, IFormattable
partial struct Int2 : IEquatable<Int2>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0} Y:{1}";
@@ -940,6 +940,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Int2)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Int2" /> is equal to this instance.
/// </summary>

View File

@@ -14,7 +14,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int3Converter))]
#endif
partial struct Int3 : IEquatable<Int3>, IFormattable
partial struct Int3 : IEquatable<Int3>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0} Y:{1} Z:{2}";
@@ -1023,6 +1023,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Int3)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Int3" /> is equal to this instance.
/// </summary>

View File

@@ -14,7 +14,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int4Converter))]
#endif
partial struct Int4 : IEquatable<Int4>, IFormattable
partial struct Int4 : IEquatable<Int4>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}";
@@ -881,6 +881,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Int4)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Int4" /> is equal to this instance.
/// </summary>

View File

@@ -41,16 +41,6 @@ FloatR10G10B10A2::FloatR10G10B10A2(const float* values)
{
}
FloatR10G10B10A2::operator Float3() const
{
return ToFloat3();
}
FloatR10G10B10A2::operator Float4() const
{
return ToFloat4();
}
Float3 FloatR10G10B10A2::ToFloat3() const
{
Float3 vectorOut;

View File

@@ -40,9 +40,14 @@ struct FLAXENGINE_API FloatR10G10B10A2
{
return Value;
}
operator Float3() const;
operator Float4() const;
operator Float3() const
{
return ToFloat3();
}
operator Float4() const
{
return ToFloat4();
}
FloatR10G10B10A2& operator=(const FloatR10G10B10A2& other)
{

View File

@@ -60,7 +60,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.QuaternionConverter))]
#endif
partial struct Quaternion : IEquatable<Quaternion>, IFormattable
partial struct Quaternion : IEquatable<Quaternion>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
@@ -1681,6 +1681,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Quaternion)other;
return Equals(ref o);
}
/// <summary>
/// Tests whether one quaternion is near another quaternion.
/// </summary>

View File

@@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
namespace FlaxEngine
{
partial struct Rectangle : IEquatable<Rectangle>
partial struct Rectangle : IEquatable<Rectangle>, Json.ICustomValueEquals
{
/// <summary>
/// A <see cref="Rectangle"/> which represents an empty space.
@@ -523,6 +523,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Rectangle)other;
return Equals(ref o);
}
/// <inheritdoc />
public override string ToString()
{

View File

@@ -16,7 +16,7 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
[Serializable]
partial struct Transform : IEquatable<Transform>, IFormattable
partial struct Transform : IEquatable<Transform>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "Translation:{0} Orientation:{1} Scale:{2}";
@@ -673,6 +673,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Transform)other;
return Equals(ref o);
}
/// <summary>
/// Tests whether one transform is near another transform.
/// </summary>

View File

@@ -73,7 +73,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector2Converter))]
#endif
public unsafe partial struct Vector2 : IEquatable<Vector2>, IFormattable
public unsafe partial struct Vector2 : IEquatable<Vector2>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
@@ -954,6 +954,33 @@ namespace FlaxEngine
return result;
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
/// <param name="result">>When the method completes, contains the linear interpolation of the two vectors.</param>
public static void Slerp(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result)
{
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
var theta = Mathr.Acos(dot) * amount;
Vector2 relativeVector = (end - start * dot).Normalized;
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
public static Vector2 Slerp(Vector2 start, Vector2 end, float amount)
{
Slerp(ref start, ref end, amount, out Vector2 result);
return result;
}
/// <summary>
/// Performs a gradual change of a vector towards a specified target over time
/// </summary>
@@ -1774,6 +1801,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Vector2)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Vector2" /> is equal to this instance.
/// </summary>

View File

@@ -558,6 +558,24 @@ public:
return result;
}
// Performs a spherical linear interpolation between two vectors.
static void Slerp(const Vector2Base& start, const Vector2Base& end, T amount, Vector2Base& result)
{
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
T theta = Math::Acos(dot) * amount;
Vector2Base relativeVector = end - start * dot;
relativeVector.Normalize();
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
}
// Performs a spherical linear interpolation between two vectors.
static Vector2Base Slerp(const Vector2Base& start, const Vector2Base& end, T amount)
{
Vector2Base result;
Slerp(start, end, amount, result);
return result;
}
public:
/// <summary>
/// Calculates the area of the triangle.

View File

@@ -73,7 +73,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector3Converter))]
#endif
public unsafe partial struct Vector3 : IEquatable<Vector3>, IFormattable
public unsafe partial struct Vector3 : IEquatable<Vector3>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
@@ -1043,6 +1043,33 @@ namespace FlaxEngine
return result;
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
/// <param name="result">When the method completes, contains the linear interpolation of the two vectors.</param>
public static void Slerp(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result)
{
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
var theta = Mathr.Acos(dot) * amount;
Vector3 relativeVector = (end - start * dot).Normalized;
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
public static Vector3 Slerp(Vector3 start, Vector3 end, float amount)
{
Slerp(ref start, ref end, amount, out var result);
return result;
}
/// <summary>
/// Performs a gradual change of a vector towards a specified target over time
/// </summary>
@@ -2133,6 +2160,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Vector3)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Vector3" /> is equal to this instance.
/// </summary>

View File

@@ -686,6 +686,24 @@ public:
return result;
}
// Performs a spherical linear interpolation between two vectors.
static void Slerp(const Vector3Base& start, const Vector3Base& end, T amount, Vector3Base& result)
{
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
T theta = Math::Acos(dot) * amount;
Vector3Base relativeVector = end - start * dot;
relativeVector.Normalize();
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
}
// Performs a spherical linear interpolation between two vectors.
static Vector3Base Slerp(const Vector3Base& start, const Vector3Base& end, T amount)
{
Vector3Base result;
Slerp(start, end, amount, result);
return result;
}
// Performs a cubic interpolation between two vectors.
static void SmoothStep(const Vector3Base& start, const Vector3Base& end, T amount, Vector3Base& result)
{

View File

@@ -72,7 +72,7 @@ namespace FlaxEngine
#if FLAX_EDITOR
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector4Converter))]
#endif
public partial struct Vector4 : IEquatable<Vector4>, IFormattable
public partial struct Vector4 : IEquatable<Vector4>, IFormattable, Json.ICustomValueEquals
{
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
@@ -260,6 +260,19 @@ namespace FlaxEngine
/// </summary>
public bool IsNormalized => Mathr.Abs((X * X + Y * Y + Z * Z + W * W) - 1.0f) < 1e-4f;
/// <summary>
/// Gets the normalized vector. Returned vector has length equal 1.
/// </summary>
public Vector4 Normalized
{
get
{
Vector4 vector4 = this;
vector4.Normalize();
return vector4;
}
}
/// <summary>
/// Gets a value indicting whether this vector is zero
/// </summary>
@@ -878,6 +891,33 @@ namespace FlaxEngine
return result;
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
/// <param name="result">When the method completes, contains the linear interpolation of the two vectors.</param>
public static void Slerp(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result)
{
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
var theta = Mathr.Acos(dot) * amount;
Vector4 relativeVector = (end - start * dot).Normalized;
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
}
/// <summary>
/// Performs a spherical linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
public static Vector4 Slerp(Vector4 start, Vector4 end, Real amount)
{
Slerp(ref start, ref end, amount, out var result);
return result;
}
/// <summary>
/// Performs a cubic interpolation between two vectors.
/// </summary>
@@ -1486,6 +1526,13 @@ namespace FlaxEngine
}
}
/// <inheritdoc />
public bool ValueEquals(object other)
{
var o = (Vector4)other;
return Equals(ref o);
}
/// <summary>
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
/// </summary>

View File

@@ -129,6 +129,12 @@ public:
FLAXENGINE_API String ToString() const;
public:
// Gets a value indicting whether this instance is normalized.
bool IsNormalized() const
{
return Math::Abs((X * X + Y * Y + Z * Z + W * W) - 1.0f) < 1e-4f;
}
// Gets a value indicting whether this vector is zero.
bool IsZero() const
{
@@ -219,6 +225,45 @@ public:
return Vector4Base(-X, -Y, -Z, -W);
}
/// <summary>
/// Calculates a normalized vector that has length equal to 1.
/// </summary>
Vector4Base GetNormalized() const
{
Vector4Base result(X, Y, Z, W);
result.Normalize();
return result;
}
public:
/// <summary>
/// Performs vector normalization (scales vector up to unit length).
/// </summary>
void Normalize()
{
const T length = Math::Sqrt(X * X + Y * Y + Z * Z + W * W);
if (length >= ZeroTolerance)
{
const T inv = (T)1.0f / length;
X *= inv;
Y *= inv;
Z *= inv;
W *= inv;
}
}
/// <summary>
/// Performs fast vector normalization (scales vector up to unit length).
/// </summary>
void NormalizeFast()
{
const T inv = 1.0f / Math::Sqrt(X * X + Y * Y + Z * Z + W * W);
X *= inv;
Y *= inv;
Z *= inv;
W *= inv;
}
public:
Vector4Base operator+(const Vector4Base& b) const
{
@@ -469,6 +514,41 @@ public:
result = Vector4Base(Math::Clamp(v.X, min.X, max.X), Math::Clamp(v.Y, min.Y, max.Y), Math::Clamp(v.Z, min.Z, max.Z), Math::Clamp(v.W, min.W, max.W));
}
// Performs vector normalization (scales vector up to unit length).
static Vector4Base Normalize(const Vector4Base& v)
{
Vector4Base r = v;
const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y + r.Z * r.Z + r.W * r.W);
if (length >= ZeroTolerance)
{
const T inv = (T)1.0f / length;
r.X *= inv;
r.Y *= inv;
r.Z *= inv;
r.W *= inv;
}
return r;
}
// Performs vector normalization (scales vector up to unit length). This is a faster version that does not perform check for length equal 0 (it assumes that input vector is not empty).
static Vector4Base NormalizeFast(const Vector4Base& v)
{
const T inv = 1.0f / v.Length();
return Vector4Base(v.X * inv, v.Y * inv, v.Z * inv, v.W * inv);
}
// Performs vector normalization (scales vector up to unit length).
static FORCE_INLINE void Normalize(const Vector4Base& input, Vector4Base& result)
{
result = Normalize(input);
}
// Calculates the dot product of two vectors.
FORCE_INLINE static T Dot(const Vector4Base& a, const Vector4Base& b)
{
return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;
}
// Performs a linear interpolation between two vectors.
static void Lerp(const Vector4Base& start, const Vector4Base& end, T amount, Vector4Base& result)
{
@@ -486,6 +566,24 @@ public:
return result;
}
// Performs a spherical linear interpolation between two vectors.
static void Slerp(const Vector4Base& start, const Vector4Base& end, T amount, Vector4Base& result)
{
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
T theta = Math::Acos(dot) * amount;
Vector4Base relativeVector = end - start * dot;
relativeVector.Normalize();
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
}
// Performs a spherical linear interpolation between two vectors.
static Vector4Base Slerp(const Vector4Base& start, const Vector4Base& end, T amount)
{
Vector4Base result;
Slerp(start, end, amount, result);
return result;
}
FLAXENGINE_API static Vector4Base Transform(const Vector4Base& v, const Matrix& m);
};

View File

@@ -8,6 +8,7 @@
#include "Engine/Threading/Threading.h"
#include "Engine/Threading/Task.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
@@ -219,6 +220,7 @@ namespace
if (module == GetBinaryModuleCorlib())
return;
PROFILE_CPU();
PROFILE_MEM(EngineDebug);
#if USE_CSHARP
if (auto* managedModule = dynamic_cast<ManagedBinaryModule*>(module))
@@ -381,6 +383,7 @@ DebugCommandsService DebugCommandsServiceInstance;
void DebugCommands::Execute(StringView command)
{
PROFILE_MEM(EngineDebug);
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String commandCopy = command;
command = commandCopy;
@@ -423,6 +426,7 @@ void DebugCommands::Search(StringView searchText, Array<StringView>& matches, bo
{
if (searchText.IsEmpty())
return;
PROFILE_MEM(EngineDebug);
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String searchTextCopy = searchText;
searchText = searchTextCopy;

View File

@@ -480,6 +480,7 @@ DebugDrawCall WriteLists(int32& vertexCounter, const Array<T>& listA, const Arra
FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
Array<DebugTriangle>* list;
if (depthTest)
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
@@ -492,6 +493,7 @@ FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool de
FORCE_INLINE DebugTriangle* AppendWireTriangles(int32 count, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
Array<DebugTriangle>* list;
if (depthTest)
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultWireTriangles : &Context->DebugDrawDepthTest.OneFrameWireTriangles;
@@ -539,7 +541,7 @@ DebugDrawService DebugDrawServiceInstance;
bool DebugDrawService::Init()
{
PROFILE_MEM(Graphics);
PROFILE_MEM(EngineDebug);
Context = &GlobalContext;
// Init wireframe sphere cache
@@ -658,7 +660,7 @@ void DebugDrawService::Update()
}
PROFILE_CPU();
PROFILE_MEM(Graphics);
PROFILE_MEM(EngineDebug);
// Update lists
float deltaTime = Time::Update.DeltaTime.GetTotalSeconds();
@@ -1114,6 +1116,7 @@ void DebugDraw::DrawRay(const Ray& ray, const Color& color, float length, float
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
@@ -1132,6 +1135,7 @@ void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color&
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& startColor, const Color& endColor, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
@@ -1161,6 +1165,7 @@ void DebugDraw::DrawLines(const Span<Float3>& lines, const Matrix& transform, co
}
// Draw lines
PROFILE_MEM(EngineDebug);
const Float3* p = lines.Get();
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
const Matrix transformF = transform * Matrix::Translation(-Context->Origin);
@@ -1200,6 +1205,7 @@ void DebugDraw::DrawLines(GPUBuffer* lines, const Matrix& transform, float durat
}
// Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
auto& geometry = debugDrawData.GeometryBuffers.AddOne();
geometry.Buffer = lines;
@@ -1224,6 +1230,7 @@ void DebugDraw::DrawLines(const Span<Double3>& lines, const Matrix& transform, c
}
// Draw lines
PROFILE_MEM(EngineDebug);
const Double3* p = lines.Get();
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
const Matrix transformF = transform * Matrix::Translation(-Context->Origin);
@@ -1270,6 +1277,7 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3&
const float segmentCountInv = 1.0f / (float)segmentCount;
// Draw segmented curve from lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1310,6 +1318,7 @@ void DebugDraw::DrawWireBox(const BoundingBox& box, const Color& color, float du
c -= Context->Origin;
// Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1344,6 +1353,7 @@ void DebugDraw::DrawWireFrustum(const BoundingFrustum& frustum, const Color& col
c -= Context->Origin;
// Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1378,6 +1388,7 @@ void DebugDraw::DrawWireBox(const OrientedBoundingBox& box, const Color& color,
c -= Context->Origin;
// Draw lines
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1419,6 +1430,7 @@ void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color,
auto& cache = SphereCache[index];
// Draw lines of the unit sphere after linear transform
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
if (duration > 0)
{
@@ -1454,6 +1466,7 @@ void DebugDraw::DrawSphere(const BoundingSphere& sphere, const Color& color, flo
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + SphereTriangleCache.Count());
const Float3 centerF = sphere.Center - Context->Origin;
@@ -1485,6 +1498,7 @@ void DebugDraw::DrawCircle(const Vector3& position, const Float3& normal, float
Matrix::Multiply(scale, world, matrix);
// Draw lines of the unit circle after linear transform
PROFILE_MEM(EngineDebug);
Float3 prev = Float3::Transform(CircleCache[0], matrix);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
for (int32 i = 1; i < DEBUG_DRAW_CIRCLE_VERTICES;)
@@ -1515,6 +1529,7 @@ void DebugDraw::DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vec
void DebugDraw::DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration, bool depthTest)
{
PROFILE_MEM(EngineDebug);
DebugTriangle t;
t.Color = Color32(color);
t.TimeLeft = duration;
@@ -1570,6 +1585,7 @@ void DebugDraw::DrawTriangles(GPUBuffer* triangles, const Matrix& transform, flo
DebugLog::ThrowException("Cannot draw debug lines with incorrect amount of items in array");
return;
}
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
auto& geometry = debugDrawData.GeometryBuffers.AddOne();
geometry.Buffer = triangles;
@@ -1859,6 +1875,7 @@ void DebugDraw::DrawWireCapsule(const Vector3& position, const Quaternion& orien
Matrix::Multiply(rotation, translation, world);
// Write vertices
PROFILE_MEM(EngineDebug);
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
Color32 color32(color);
if (duration > 0)
@@ -1953,6 +1970,7 @@ namespace
void DrawCylinder(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration)
{
// Setup cache
PROFILE_MEM(EngineDebug);
Float3 CylinderCache[DEBUG_DRAW_CYLINDER_VERTICES];
const float angleBetweenFacets = TWO_PI / DEBUG_DRAW_CYLINDER_RESOLUTION;
const float verticalOffset = height * 0.5f;
@@ -2024,6 +2042,7 @@ namespace
void DrawCone(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration)
{
PROFILE_MEM(EngineDebug);
const float tolerance = 0.001f;
const float angle1 = Math::Clamp(angleXY, tolerance, PI - tolerance);
const float angle2 = Math::Clamp(angleXZ, tolerance, PI - tolerance);
@@ -2113,6 +2132,7 @@ void DebugDraw::DrawArc(const Vector3& position, const Quaternion& orientation,
{
if (angle <= 0)
return;
PROFILE_MEM(EngineDebug);
if (angle > TWO_PI)
angle = TWO_PI;
Array<DebugTriangle>* list;
@@ -2145,6 +2165,7 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati
{
if (angle <= 0)
return;
PROFILE_MEM(EngineDebug);
if (angle > TWO_PI)
angle = TWO_PI;
const int32 resolution = Math::CeilToInt((float)DEBUG_DRAW_CONE_RESOLUTION / TWO_PI * angle);
@@ -2211,6 +2232,7 @@ void DebugDraw::DrawBox(const BoundingBox& box, const Color& color, float durati
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + 36);
for (int i0 = 0; i0 < 36;)
{
@@ -2239,6 +2261,7 @@ void DebugDraw::DrawBox(const OrientedBoundingBox& box, const Color& color, floa
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
else
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
PROFILE_MEM(EngineDebug);
list->EnsureCapacity(list->Count() + 36);
for (int i0 = 0; i0 < 36;)
{
@@ -2254,6 +2277,7 @@ void DebugDraw::DrawText(const StringView& text, const Float2& position, const C
{
if (text.Length() == 0 || size < 4)
return;
PROFILE_MEM(EngineDebug);
Array<DebugText2D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText2D : &Context->DebugDrawDefault.OneFrameText2D;
auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1);
@@ -2269,6 +2293,7 @@ void DebugDraw::DrawText(const StringView& text, const Vector3& position, const
{
if (text.Length() == 0 || size < 4)
return;
PROFILE_MEM(EngineDebug);
Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1);
@@ -2286,6 +2311,7 @@ void DebugDraw::DrawText(const StringView& text, const Transform& transform, con
{
if (text.Length() == 0 || size < 4)
return;
PROFILE_MEM(EngineDebug);
Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
auto& t = list->AddOne();
t.Text.Resize(text.Length() + 1);

View File

@@ -44,20 +44,39 @@ void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_S
ASSERT(instance.Bounds.Radius > ZeroTolerance);
ASSERT(cluster->Bounds.Intersects(instance.Bounds));
// Find target cluster
while (cluster->Children[0])
// Minor clusters don't use bounds intersection but try to find the first free cluster instead
if (cluster->IsMinor)
{
// Insert into the first non-full child cluster or subdivide 1st child
#define CHECK_CHILD(idx) \
if (cluster->Children[idx]->Instances.Count() < FOLIAGE_CLUSTER_CAPACITY) \
{ \
cluster->Children[idx]->Instances.Add(&instance); \
return; \
}
CHECK_CHILD(3);
CHECK_CHILD(2);
CHECK_CHILD(1);
cluster = cluster->Children[0];
#undef CHECK_CHILD
}
else
{
// Find target cluster
while (cluster->Children[0])
{
#define CHECK_CHILD(idx) \
if (cluster->Children[idx]->Bounds.Intersects(instance.Bounds)) \
{ \
cluster = cluster->Children[idx]; \
continue; \
}
CHECK_CHILD(0);
CHECK_CHILD(1);
CHECK_CHILD(2);
CHECK_CHILD(3);
CHECK_CHILD(0);
CHECK_CHILD(1);
CHECK_CHILD(2);
CHECK_CHILD(3);
#undef CHECK_CHILD
}
}
// Check if it's not full
@@ -79,11 +98,20 @@ void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_S
// Setup children
const Vector3 min = cluster->Bounds.Minimum;
const Vector3 max = cluster->Bounds.Maximum;
const Vector3 size = cluster->Bounds.GetSize();
const Vector3 size = max - min;
cluster->Children[0]->Init(BoundingBox(min, min + size * Vector3(0.5f, 1.0f, 0.5f)));
cluster->Children[1]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.5f), max));
cluster->Children[2]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.0f), min + size * Vector3(1.0f, 1.0f, 0.5f)));
cluster->Children[3]->Init(BoundingBox(min + size * Vector3(0.0f, 0.0f, 0.5f), min + size * Vector3(0.5f, 1.0f, 1.0f)));
if (cluster->IsMinor || size.MinValue() < 1.0f)
{
// Mark children as minor to avoid infinite subdivision
cluster->IsMinor = true;
cluster->Children[0]->IsMinor = true;
cluster->Children[1]->IsMinor = true;
cluster->Children[2]->IsMinor = true;
cluster->Children[3]->IsMinor = true;
}
// Move instances to a proper cells
for (int32 i = 0; i < cluster->Instances.Count(); i++)

View File

@@ -9,6 +9,7 @@ void FoliageCluster::Init(const BoundingBox& bounds)
Bounds = bounds;
TotalBounds = bounds;
MaxCullDistance = 0.0f;
IsMinor = false;
Children[0] = nullptr;
Children[1] = nullptr;

View File

@@ -33,6 +33,11 @@ public:
/// </summary>
float MaxCullDistance;
/// <summary>
/// Flag used by clusters that are not typical quad-tree nodes but have no volume (eg. lots of instances placed on top of each other).
/// </summary>
int32 IsMinor : 1;
/// <summary>
/// The child clusters. If any element is valid then all are created.
/// </summary>

View File

@@ -1094,6 +1094,11 @@ API_ENUM(Attributes="Flags") enum class ViewFlags : uint64
/// Default flags for materials/models previews generating.
/// </summary>
DefaultAssetPreview = Reflections | Decals | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows | Sky | Particles,
/// <summary>
/// All flags enabled.
/// </summary>
All = None | DebugDraw | EditorSprites | Reflections | SSR | AO | GI | DirectionalLights | PointLights | SpotLights | SkyLights | Shadows | SpecularLight | AntiAliasing | CustomPostProcess | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | Decals | DepthOfField | PhysicsDebug | Fog | MotionBlur | ContactShadows | GlobalSDF | Sky | LightsDebug | Particles,
};
DECLARE_ENUM_OPERATORS(ViewFlags);

View File

@@ -6,7 +6,9 @@
#include "Engine/Core/Math/Transform.h"
#include "Engine/Core/Math/Ray.h"
#include "Engine/Core/Math/CollisionsHelper.h"
#include "Engine/Core/Math/Half.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Graphics/PixelFormat.h"
/// <summary>
/// Helper container used for detailed triangle mesh intersections tests.
@@ -31,23 +33,38 @@ public:
}
template<typename IndexType>
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3))
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3), PixelFormat positionsFormat = PixelFormat::R32G32B32_Float)
{
Triangles.Clear();
Triangles.EnsureCapacity(triangles, false);
const IndexType* it = indices;
for (uint32 i = 0; i < triangles; i++)
#define LOOP_BEGIN() \
for (uint32 i = 0; i < triangles; i++) \
{ \
const IndexType i0 = *(it++); \
const IndexType i1 = *(it++); \
const IndexType i2 = *(it++); \
if (i0 < vertices && i1 < vertices && i2 < vertices) \
{
const IndexType i0 = *(it++);
const IndexType i1 = *(it++);
const IndexType i2 = *(it++);
if (i0 < vertices && i1 < vertices && i2 < vertices)
{
#define LOOP_END() } }
if (positionsFormat == PixelFormat::R32G32B32_Float)
{
LOOP_BEGIN()
#define GET_POS(idx) *(const Float3*)((const byte*)positions + positionsStride * idx)
Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
#undef GET_POS
}
LOOP_END()
}
else if (positionsFormat == PixelFormat::R16G16B16A16_Float)
{
LOOP_BEGIN()
#define GET_POS(idx) ((const Half4*)((const byte*)positions + positionsStride * idx))->ToFloat3()
Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
#undef GET_POS
LOOP_END()
}
#undef LOOP_BEGIN
#undef LOOP_END
}
void Clear()

View File

@@ -265,6 +265,39 @@ namespace FlaxEngine
}
}
/// <summary>
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
/// </summary>
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<uint> src)
{
if (IsLinear(PixelFormat.R32_UInt))
{
src.CopyTo(MemoryMarshal.Cast<byte, uint>(_data));
}
else if (IsLinear(PixelFormat.R16_UInt))
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
((ushort*)data)[i] = (ushort)src[i];
}
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i]);
_sampler.Write(data + i * _stride, ref v);
}
}
}
}
/// <summary>
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
/// </summary>
@@ -281,9 +314,7 @@ namespace FlaxEngine
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
dst[i] = new Float2(_sampler.Read(data + i * _stride));
}
}
}
}
@@ -304,9 +335,7 @@ namespace FlaxEngine
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
dst[i] = new Float3(_sampler.Read(data + i * _stride));
}
}
}
}
@@ -327,9 +356,37 @@ namespace FlaxEngine
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
dst[i] = (Color)_sampler.Read(data + i * _stride);
}
}
}
}
/// <summary>
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
/// </summary>
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<uint> dst)
{
if (IsLinear(PixelFormat.R32_UInt))
{
_data.CopyTo(MemoryMarshal.Cast<uint, byte>(dst));
}
else if (IsLinear(PixelFormat.R16_UInt))
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
dst[i] = ((ushort*)data)[i];
}
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
dst[i] = (uint)_sampler.Read(data + i * _stride).X;
}
}
}
@@ -619,6 +676,16 @@ namespace FlaxEngine
return Attribute((VertexElement.Types)((byte)VertexElement.Types.TexCoord0 + channel));
}
/// <summary>
/// Gets or sets the index buffer with triangle indices.
/// </summary>
/// <remarks>Uses <see cref="Index"/> stream to read or write data to the index buffer.</remarks>
public uint[] Triangles
{
get => GetStreamUInt(Index());
set => SetStreamUInt(Index(), value);
}
/// <summary>
/// Gets or sets the vertex positions. Null if <see cref="VertexElement.Types.Position"/> does not exist in vertex buffers of the mesh.
/// </summary>
@@ -659,6 +726,25 @@ namespace FlaxEngine
set => SetStreamFloat2(VertexElement.Types.TexCoord, value);
}
private uint[] GetStreamUInt(Stream stream)
{
uint[] result = null;
if (stream.IsValid)
{
result = new uint[stream.Count];
stream.CopyTo(result);
}
return result;
}
private void SetStreamUInt(Stream stream, uint[] value)
{
if (stream.IsValid)
{
stream.Set(value);
}
}
private delegate void TransformDelegate3(ref Float3 value);
private Float3[] GetStreamFloat3(VertexElement.Types attribute, TransformDelegate3 transform = null)

View File

@@ -441,6 +441,9 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
GPUBuffer* vertexBuffer1 = nullptr;
GPUBuffer* vertexBuffer2 = nullptr;
GPUBuffer* indexBuffer = nullptr;
#if MODEL_USE_PRECISE_MESH_INTERSECTS
VertexElement positionsElement;
#endif
// Create GPU buffers
#if GPU_ENABLE_RESOURCE_NAMING
@@ -470,10 +473,11 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
// Init collision proxy
#if MODEL_USE_PRECISE_MESH_INTERSECTS
positionsElement = vbLayout[0]->FindElement(VertexElement::Types::Position);
if (use16BitIndexBuffer)
_collisionProxy.Init<uint16>(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData);
_collisionProxy.Init<uint16>(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData, vertexBuffer0->GetStride(), positionsElement.Format);
else
_collisionProxy.Init<uint32>(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData);
_collisionProxy.Init<uint32>(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData, vertexBuffer0->GetStride(), positionsElement.Format);
#endif
// Free old buffers

View File

@@ -59,7 +59,7 @@ namespace
elements.Get()[j].Slot = (byte)slot;
}
}
GPUVertexLayout* result = anyValid ? GPUVertexLayout::Get(elements) : nullptr;
GPUVertexLayout* result = anyValid ? GPUVertexLayout::Get(elements, true) : nullptr;
VertexBufferCache.Add(key, result);
return result;
}
@@ -97,6 +97,7 @@ GPUVertexLayout::GPUVertexLayout()
void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets)
{
uint32 offsets[GPU_MAX_VB_BINDED + 1] = {};
uint32 maxOffset[GPU_MAX_VB_BINDED + 1] = {};
_elements = elements;
for (int32 i = 0; i < _elements.Count(); i++)
{
@@ -108,9 +109,10 @@ void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets
else
e.Offset = (byte)offset;
offset += PixelFormatExtensions::SizeInBytes(e.Format);
maxOffset[e.Slot] = Math::Max(maxOffset[e.Slot], offset);
}
_stride = 0;
for (uint32 offset : offsets)
for (uint32 offset : maxOffset)
_stride += offset;
}
@@ -139,7 +141,7 @@ VertexElement GPUVertexLayout::FindElement(VertexElement::Types type) const
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets)
{
// Hash input layout
uint32 hash = 0;
uint32 hash = explicitOffsets ? 131 : 0;
for (const VertexElement& element : elements)
{
CombineHash(hash, GetHash(element));

View File

@@ -4,6 +4,7 @@
#include "Engine/Graphics/Shaders/GPUShaderProgram.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "../IncludeDirectXHeaders.h"
#if GRAPHICS_API_DIRECTX11

View File

@@ -3,16 +3,9 @@
#if GRAPHICS_API_DIRECTX12
#include "Engine/Graphics/Config.h"
#include "Engine/Platform/Platform.h"
#include "../IncludeDirectXHeaders.h"
#if USE_PIX && GPU_ALLOW_PROFILE_EVENTS
// Include these header files before pix3
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define NOGDI
#define NODRAWTEXT
//#define NOCTLMGR
#define NOFLATSBAPIS
#include <Windows.h>
#include <d3d12.h>
#include <ThirdParty/WinPixEventRuntime/pix3.h>
#endif
#include "GPUContextDX12.h"

View File

@@ -473,6 +473,19 @@ public:
/// <returns>Found actors list.</returns>
API_FUNCTION() static Array<Actor*> GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false);
/// <summary>
/// Finds all the actors of the given type in all the loaded scenes.
/// </summary>
/// <typeparam name="T">Type of the object.</typeparam>
/// <param name="activeOnly">Finds only active actors.</param>
/// <returns>Found actors list.</returns>
template<typename T>
static Array<T*> GetActors(bool activeOnly = false)
{
Array<Actor*> actors = GetActors(T::GetStaticClass(), activeOnly);
return *(Array<T*>*)&actors;
}
/// <summary>
/// Finds all the scripts of the given type in an actor or all the loaded scenes.
/// </summary>

View File

@@ -13,7 +13,7 @@ namespace FlaxEngine
public bool ValueEquals(object other)
{
var o = (MeshReference)other;
return JsonSerializer.ValueEquals(Actor, o.Actor) &&
return JsonSerializer.SceneObjectEquals(Actor, o.Actor) &&
LODIndex == o.LODIndex &&
MeshIndex == o.MeshIndex;
}

View File

@@ -227,9 +227,9 @@ public:
void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor)
{
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
if (PrefabManager::PrefabsReferences.ContainsKey(prefabId))
if (auto instancesPtr = PrefabManager::PrefabsReferences.TryGet(prefabId))
{
auto& instances = PrefabManager::PrefabsReferences[prefabId];
auto& instances = *instancesPtr;
int32 usedCount = 0;
for (int32 instanceIndex = 0; instanceIndex < instances.Count(); instanceIndex++)
{

View File

@@ -25,6 +25,7 @@ NavMesh::NavMesh(const SpawnParams& params)
void NavMesh::SaveNavMesh()
{
#if COMPILE_WITH_ASSETS_IMPORTER
PROFILE_MEM(NavigationMesh);
// Skip if scene is missing
const auto scene = GetScene();
@@ -111,7 +112,7 @@ void NavMesh::OnAssetLoaded(Asset* asset, void* caller)
if (Data.Tiles.HasItems())
return;
ScopeLock lock(DataAsset->Locker);
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
// Remove added tiles
if (_navMeshActive)

View File

@@ -6,7 +6,7 @@
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
#include "NavMeshBuilder.h"
#include "Navigation.h"
#endif
NavMeshBoundsVolume::NavMeshBoundsVolume(const SpawnParams& params)
@@ -55,9 +55,30 @@ void NavMeshBoundsVolume::OnBoundsChanged(const BoundingBox& prevBounds)
// Auto-rebuild modified navmesh area
if (IsDuringPlay() && IsActiveInHierarchy() && !Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh())
{
BoundingBox dirtyBounds;
BoundingBox::Merge(prevBounds, _box, dirtyBounds);
NavMeshBuilder::Build(GetScene(), dirtyBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
if (_box.Intersects(prevBounds))
{
// Bounds were moved a bit so merge into a single request (for performance reasons)
BoundingBox dirtyBounds;
BoundingBox::Merge(prevBounds, _box, dirtyBounds);
Navigation::BuildNavMesh(dirtyBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
}
else
{
// Dirty each bounds in separate
Navigation::BuildNavMesh(prevBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
Navigation::BuildNavMesh(_box, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
}
}
}
void NavMeshBoundsVolume::OnActiveInTreeChanged()
{
BoxVolume::OnActiveInTreeChanged();
// Auto-rebuild
if (IsDuringPlay() && !Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh())
{
Navigation::BuildNavMesh(_box, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
}
}

View File

@@ -30,6 +30,7 @@ protected:
void OnDisable() override;
#if USE_EDITOR
void OnBoundsChanged(const BoundingBox& prevBounds) override;
void OnActiveInTreeChanged() override;
Color GetWiresColor() override;
#endif
};

View File

@@ -3,6 +3,7 @@
#if COMPILE_WITH_NAV_MESH_BUILDER
#include "NavMeshBuilder.h"
#include "Navigation.h"
#include "NavMesh.h"
#include "NavigationSettings.h"
#include "NavMeshBoundsVolume.h"
@@ -706,6 +707,7 @@ struct BuildRequest
ScriptingObjectReference<Scene> Scene;
DateTime Time;
BoundingBox DirtyBounds;
bool SpecificScene;
};
CriticalSection NavBuildQueueLocker;
@@ -713,6 +715,7 @@ Array<BuildRequest> NavBuildQueue;
CriticalSection NavBuildTasksLocker;
int32 NavBuildTasksMaxCount = 0;
bool NavBuildCheckMissingNavMeshes = false;
Array<class NavMeshTileBuildTask*> NavBuildTasks;
class NavMeshTileBuildTask : public ThreadPoolTask
@@ -733,7 +736,7 @@ public:
bool Run() override
{
PROFILE_CPU_NAMED("BuildNavMeshTile");
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationBuilding);
const auto navMesh = NavMesh.Get();
if (!navMesh)
return false;
@@ -776,13 +779,13 @@ void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime)
NavBuildTasksLocker.Unlock();
}
void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime, int32 x, int32 y)
void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime, int32 x, int32 y, NavMesh* navMesh)
{
NavBuildTasksLocker.Lock();
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
{
auto task = NavBuildTasks[i];
if (task->Runtime == runtime && task->X == x && task->Y == y)
if (task->Runtime == runtime && task->X == x && task->Y == y && task->NavMesh == navMesh)
{
NavBuildTasksLocker.Unlock();
@@ -838,7 +841,7 @@ void NavMeshBuilder::Init()
Level::SceneUnloading.Bind<OnSceneUnloading>();
}
bool NavMeshBuilder::IsBuildingNavMesh()
bool Navigation::IsBuildingNavMesh()
{
NavBuildTasksLocker.Lock();
const bool hasAnyTask = NavBuildTasks.HasItems();
@@ -847,7 +850,7 @@ bool NavMeshBuilder::IsBuildingNavMesh()
return hasAnyTask;
}
float NavMeshBuilder::GetNavMeshBuildingProgress()
float Navigation::GetNavMeshBuildingProgress()
{
NavBuildTasksLocker.Lock();
float result = 1.0f;
@@ -907,15 +910,13 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
// Align dirty bounds to tile size
BoundingBox dirtyBoundsNavMesh;
BoundingBox::Transform(dirtyBounds, worldToNavMesh, dirtyBoundsNavMesh);
BoundingBox dirtyBoundsAligned;
dirtyBoundsAligned.Minimum = Float3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize;
dirtyBoundsAligned.Maximum = Float3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize;
dirtyBoundsNavMesh.Minimum = Float3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize;
dirtyBoundsNavMesh.Maximum = Float3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize;
// Calculate tiles range for the given navigation dirty bounds (aligned to tiles size)
const Int3 tilesMin(dirtyBoundsAligned.Minimum / tileSize);
const Int3 tilesMax(dirtyBoundsAligned.Maximum / tileSize);
const int32 tilesX = tilesMax.X - tilesMin.X;
const int32 tilesY = tilesMax.Z - tilesMin.Z;
const Int3 tilesMin(dirtyBoundsNavMesh.Minimum / tileSize);
const Int3 tilesMax(dirtyBoundsNavMesh.Maximum / tileSize);
const int32 tilesXZ = (tilesMax.X - tilesMin.X) * (tilesMax.Z - tilesMin.Z);
{
PROFILE_CPU_NAMED("Prepare");
@@ -932,18 +933,18 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
// Remove all tiles from navmesh runtime
runtime->RemoveTiles(navMesh);
runtime->SetTileSize(tileSize);
runtime->EnsureCapacity(tilesX * tilesY);
runtime->EnsureCapacity(tilesXZ);
// Remove all tiles from navmesh data
navMesh->Data.TileSize = tileSize;
navMesh->Data.Tiles.Clear();
navMesh->Data.Tiles.EnsureCapacity(tilesX * tilesX);
navMesh->Data.Tiles.EnsureCapacity(tilesXZ);
navMesh->IsDataDirty = true;
}
else
{
// Ensure to have enough memory for tiles
runtime->EnsureCapacity(tilesX * tilesY);
runtime->EnsureCapacity(tilesXZ);
}
runtime->Locker.Unlock();
@@ -959,11 +960,10 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
// Cache navmesh volumes
Array<BoundingBox, InlinedAllocation<8>> volumes;
for (int32 i = 0; i < scene->Navigation.Volumes.Count(); i++)
for (const NavMeshBoundsVolume* volume : scene->Navigation.Volumes)
{
const auto volume = scene->Navigation.Volumes.Get()[i];
if (!volume->AgentsMask.IsNavMeshSupported(navMesh->Properties) ||
!volume->GetBox().Intersects(dirtyBoundsAligned))
!volume->GetBox().Intersects(dirtyBoundsNavMesh))
continue;
auto& bounds = volumes.AddOne();
BoundingBox::Transform(volume->GetBox(), worldToNavMesh, bounds);
@@ -1026,7 +1026,7 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
for (const auto& tile : unusedTiles)
{
// Wait for any async tasks that are producing this tile
CancelNavMeshTileBuildTasks(runtime, tile.X, tile.Y);
CancelNavMeshTileBuildTasks(runtime, tile.X, tile.Y, navMesh);
}
runtime->Locker.Lock();
for (const auto& tile : unusedTiles)
@@ -1095,6 +1095,7 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild
else if (settings->AutoAddMissingNavMeshes)
{
// Spawn missing navmesh
PROFILE_MEM(Navigation);
navMesh = New<NavMesh>();
navMesh->SetStaticFlags(StaticFlags::FullyStatic);
navMesh->SetName(TEXT("NavMesh.") + navMeshProperties.Name);
@@ -1108,39 +1109,6 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild
{
BuildDirtyBounds(scene, navMesh, dirtyBounds, rebuild);
}
// Remove unused navmeshes
if (settings->AutoRemoveMissingNavMeshes)
{
for (NavMesh* navMesh : scene->Navigation.Meshes)
{
// Skip used navmeshes
if (navMesh->Data.Tiles.HasItems())
continue;
// Skip navmeshes during async building
int32 usageCount = 0;
NavBuildTasksLocker.Lock();
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
{
if (NavBuildTasks.Get()[i]->NavMesh == navMesh)
usageCount++;
}
NavBuildTasksLocker.Unlock();
if (usageCount != 0)
continue;
navMesh->DeleteObject();
}
}
}
void BuildWholeScene(Scene* scene)
{
// Compute total navigation area bounds
const BoundingBox worldBounds = scene->Navigation.GetNavigationBounds();
BuildDirtyBounds(scene, worldBounds, true);
}
void ClearNavigation(Scene* scene)
@@ -1154,22 +1122,58 @@ void ClearNavigation(Scene* scene)
}
}
void BuildNavigation(BuildRequest& request)
{
// If scene is not specified then build all loaded scenes
if (!request.Scene)
{
for (Scene* scene : Level::Scenes)
{
request.Scene = scene;
BuildNavigation(request);
}
return;
}
// Early out if scene is not using navigation
if (request.Scene->Navigation.Volumes.IsEmpty())
{
ClearNavigation(request.Scene);
return;
}
// Check if similar request is already in a queue
for (auto& e : NavBuildQueue)
{
if (e.Scene == request.Scene && (e.DirtyBounds == request.DirtyBounds || request.DirtyBounds == BoundingBox::Empty))
{
e = request;
return;
}
}
// Enqueue request
NavBuildQueue.Add(request);
}
void NavMeshBuilder::Update()
{
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationBuilding);
ScopeLock lock(NavBuildQueueLocker);
// Process nav mesh building requests and kick the tasks
const auto now = DateTime::NowUTC();
bool didRebuild = false;
for (int32 i = 0; NavBuildQueue.HasItems() && i < NavBuildQueue.Count(); i++)
{
auto req = NavBuildQueue.Get()[i];
if (now - req.Time >= 0)
{
NavBuildQueue.RemoveAt(i--);
const auto scene = req.Scene.Get();
Scene* scene = req.Scene.Get();
if (!scene)
continue;
bool rebuild = req.DirtyBounds == BoundingBox::Empty;
// Early out if scene has no bounds volumes to define nav mesh area
if (scene->Navigation.Volumes.IsEmpty())
@@ -1179,80 +1183,69 @@ void NavMeshBuilder::Update()
}
// Check if build a custom dirty bounds or whole scene
if (req.DirtyBounds == BoundingBox::Empty)
{
BuildWholeScene(scene);
}
if (rebuild)
req.DirtyBounds = scene->Navigation.GetNavigationBounds(); // Compute total navigation area bounds
if (didRebuild)
rebuild = false; // When rebuilding navmesh for multiple scenes, rebuild only the first one (other scenes will use additive update)
else
didRebuild = true;
BuildDirtyBounds(scene, req.DirtyBounds, rebuild);
NavBuildCheckMissingNavMeshes = true;
}
}
// Remove unused navmeshes (when all active tasks are done)
// TODO: ignore AutoRemoveMissingNavMeshes in game and make it editor-only?
if (NavBuildCheckMissingNavMeshes && NavBuildTasksMaxCount == 0 && NavigationSettings::Get()->AutoRemoveMissingNavMeshes)
{
NavBuildCheckMissingNavMeshes = false;
NavBuildTasksLocker.Lock();
int32 taskCount = NavBuildTasks.Count();
NavBuildTasksLocker.Unlock();
if (taskCount == 0)
{
for (Scene* scene : Level::Scenes)
{
BuildDirtyBounds(scene, req.DirtyBounds, false);
for (NavMesh* navMesh : scene->Navigation.Meshes)
{
if (!navMesh->Data.Tiles.HasItems())
{
navMesh->DeleteObject();
}
}
}
}
}
}
void NavMeshBuilder::Build(Scene* scene, float timeoutMs)
void Navigation::BuildNavMesh(Scene* scene, float timeoutMs)
{
if (!scene)
{
LOG(Warning, "Could not generate navmesh without scene.");
return;
}
// Early out if scene is not using navigation
if (scene->Navigation.Volumes.IsEmpty())
{
ClearNavigation(scene);
return;
}
PROFILE_CPU_NAMED("NavMeshBuilder");
PROFILE_MEM(Navigation);
PROFILE_CPU();
PROFILE_MEM(NavigationBuilding);
ScopeLock lock(NavBuildQueueLocker);
BuildRequest req;
req.Scene = scene;
req.Time = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
req.DirtyBounds = BoundingBox::Empty;
for (int32 i = 0; i < NavBuildQueue.Count(); i++)
{
auto& e = NavBuildQueue.Get()[i];
if (e.Scene == scene && e.DirtyBounds == req.DirtyBounds)
{
e = req;
return;
}
}
NavBuildQueue.Add(req);
req.SpecificScene = scene != nullptr;
BuildNavigation(req);
}
void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs)
void Navigation::BuildNavMesh(const BoundingBox& dirtyBounds, Scene* scene, float timeoutMs)
{
if (!scene)
{
LOG(Warning, "Could not generate navmesh without scene.");
return;
}
// Early out if scene is not using navigation
if (scene->Navigation.Volumes.IsEmpty())
{
ClearNavigation(scene);
return;
}
PROFILE_CPU_NAMED("NavMeshBuilder");
PROFILE_MEM(Navigation);
if (dirtyBounds.GetVolume() <= ZeroTolerance)
return; // Skip updating empty bounds
PROFILE_CPU();
PROFILE_MEM(NavigationBuilding);
ScopeLock lock(NavBuildQueueLocker);
BuildRequest req;
req.Scene = scene;
req.Time = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
req.DirtyBounds = dirtyBounds;
NavBuildQueue.Add(req);
req.SpecificScene = scene != nullptr;
BuildNavigation(req);
}
#endif

View File

@@ -15,11 +15,7 @@ class FLAXENGINE_API NavMeshBuilder
{
public:
static void Init();
static bool IsBuildingNavMesh();
static float GetNavMeshBuildingProgress();
static void Update();
static void Build(Scene* scene, float timeoutMs);
static void Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs);
};
#endif

View File

@@ -5,6 +5,9 @@
#include "NavMesh.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Random.h"
#if COMPILE_WITH_DEBUG_DRAW
#include "Engine/Level/Scene/Scene.h"
#endif
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Threading/Threading.h"
@@ -326,7 +329,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount)
if (newTilesCount <= capacity)
return;
PROFILE_CPU_NAMED("NavMeshRuntime.EnsureCapacity");
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
// Navmesh tiles capacity growing rule
int32 newCapacity = capacity ? capacity : 32;
@@ -387,7 +390,7 @@ void NavMeshRuntime::AddTiles(NavMesh* navMesh)
return;
auto& data = navMesh->Data;
PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles");
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
ScopeLock lock(Locker);
// Validate data (must match navmesh) or init navmesh to match the tiles options
@@ -419,7 +422,7 @@ void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData)
ASSERT(navMesh);
auto& data = navMesh->Data;
PROFILE_CPU_NAMED("NavMeshRuntime.AddTile");
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
ScopeLock lock(Locker);
// Validate data (must match navmesh) or init navmesh to match the tiles options
@@ -603,7 +606,21 @@ void NavMeshRuntime::DebugDraw()
if (!tile->header)
continue;
//DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue);
#if 0
// Debug draw tile bounds and owner scene name
BoundingBox tileBounds = *(BoundingBox*)&tile->header->bmin[0];
DebugDraw::DrawWireBox(tileBounds, Color::CadetBlue);
// TODO: build map from tile coords to tile data to avoid this loop
for (const auto& e : _tiles)
{
if (e.X == tile->header->x && e.Y == tile->header->y && e.Layer == tile->header->layer)
{
if (e.NavMesh && e.NavMesh->GetScene())
DebugDraw::DrawText(e.NavMesh->GetScene()->GetName(), tileBounds.Minimum + tileBounds.GetSize() * Float3(0.5f, 0.8f, 0.5f), Color::CadetBlue);
break;
}
}
#endif
for (int i = 0; i < tile->header->polyCount; i++)
{

View File

@@ -111,7 +111,7 @@ public:
/// <param name="startPosition">The start position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <param name="maxDistance">The maximum distance to search for wall (search radius).</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance = MAX_float) const;
/// <summary>
@@ -187,7 +187,7 @@ public:
/// <param name="startPosition">The start position.</param>
/// <param name="endPosition">The end position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) NavMeshHit& hitInfo) const;
public:

View File

@@ -2,7 +2,7 @@
#include "NavModifierVolume.h"
#include "NavigationSettings.h"
#include "NavMeshBuilder.h"
#include "Navigation.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Serialization/Serialization.h"
#if USE_EDITOR
@@ -83,7 +83,7 @@ void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds)
#else
const float timeoutMs = 0.0f;
#endif
NavMeshBuilder::Build(GetScene(), dirtyBounds, timeoutMs);
Navigation::BuildNavMesh(dirtyBounds, GetScene(), timeoutMs);
}
#endif
}

View File

@@ -180,7 +180,7 @@ NavigationService NavigationServiceInstance;
void* dtAllocDefault(size_t size, dtAllocHint)
{
PROFILE_MEM(Navigation);
PROFILE_MEM(NavigationMesh);
return Allocator::Allocate(size);
}
@@ -382,30 +382,6 @@ bool Navigation::RayCast(const Vector3& startPosition, const Vector3& endPositio
return NavMeshes.First()->RayCast(startPosition, endPosition, hitInfo);
}
#if COMPILE_WITH_NAV_MESH_BUILDER
bool Navigation::IsBuildingNavMesh()
{
return NavMeshBuilder::IsBuildingNavMesh();
}
float Navigation::GetNavMeshBuildingProgress()
{
return NavMeshBuilder::GetNavMeshBuildingProgress();
}
void Navigation::BuildNavMesh(Scene* scene, float timeoutMs)
{
NavMeshBuilder::Build(scene, timeoutMs);
}
void Navigation::BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs)
{
NavMeshBuilder::Build(scene, dirtyBounds, timeoutMs);
}
#endif
#if COMPILE_WITH_DEBUG_DRAW
void Navigation::DrawNavMesh()

View File

@@ -19,7 +19,7 @@ public:
/// <param name="startPosition">The start position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <param name="maxDistance">The maximum distance to search for wall (search radius).</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool FindDistanceToWall(const Vector3& startPosition, API_PARAM(Out) NavMeshHit& hitInfo, float maxDistance = MAX_float);
/// <summary>
@@ -81,12 +81,10 @@ public:
/// <param name="startPosition">The start position.</param>
/// <param name="endPosition">The end position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool RayCast(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) NavMeshHit& hitInfo);
public:
#if COMPILE_WITH_NAV_MESH_BUILDER
/// <summary>
/// Returns true if navigation system is during navmesh building (any request is valid or async task active).
/// </summary>
@@ -100,32 +98,49 @@ public:
/// <summary>
/// Builds the Nav Mesh for the given scene (discards all its tiles).
/// </summary>
/// <remarks>
/// Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.
/// </remarks>
/// <param name="scene">The scene.</param>
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
/// <param name="scene">The scene. Pass null to build navmesh for all loaded scenes.</param>
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
API_FUNCTION() static void BuildNavMesh(Scene* scene, float timeoutMs = 50);
API_FUNCTION() static void BuildNavMesh(Scene* scene = nullptr, float timeoutMs = 50);
/// <summary>
/// Builds the Nav Mesh for the given scene (builds only the tiles overlapping the given bounding box).
/// </summary>
/// <remarks>
/// Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.
/// </remarks>
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
/// <param name="scene">The scene. Pass null to build navmesh for all loaded scenes that intersect with a given bounds.</param>
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
API_FUNCTION() static void BuildNavMesh(const BoundingBox& dirtyBounds, Scene* scene = nullptr, float timeoutMs = 50);
/// <summary>
/// Builds the Nav Mesh for all the loaded scenes (builds only the tiles overlapping the given bounding box).
/// </summary>
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
API_FUNCTION() static void BuildNavMesh(const BoundingBox& dirtyBounds, float timeoutMs = 50)
{
BuildNavMesh(dirtyBounds, nullptr, timeoutMs);
}
/// <summary>
/// Builds the Nav Mesh for the given scene (builds only the tiles overlapping the given bounding box).
/// [Deprecated in v1.12]
/// </summary>
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
/// <param name="scene">The scene.</param>
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
API_FUNCTION() static void BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs = 50);
API_FUNCTION() DEPRECATED("Use BuildNavMesh with reordered arguments instead") static void BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs = 50)
{
BuildNavMesh(dirtyBounds, scene, timeoutMs);
}
#endif
#if COMPILE_WITH_DEBUG_DRAW
/// <summary>
/// Draws the navigation for all the scenes (uses DebugDraw interface).
/// </summary>
static void DrawNavMesh();
#endif
};

View File

@@ -11,6 +11,10 @@
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/Engine.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
#endif
ParticleEffect::ParticleEffect(const SpawnParams& params)
: Actor(params)
@@ -465,7 +469,12 @@ void ParticleEffect::Update()
if (UpdateMode == SimulationUpdateMode::FixedTimestep)
{
// Check if last simulation update was past enough to kick a new on
const float time = Time::Update.Time.GetTotalSeconds();
bool useTimeScale = UseTimeScale;
#if USE_EDITOR
if (!Editor::IsPlayMode && IsDuringPlay())
useTimeScale = false;
#endif
const float time = (useTimeScale ? Time::Update.Time : Time::Update.UnscaledTime).GetTotalSeconds();
if (time - Instance.LastUpdateTime < FixedTimestep)
return;
}
@@ -475,9 +484,6 @@ void ParticleEffect::Update()
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
void ParticleEffect::UpdateExecuteInEditor()
{
// Auto-play in Editor

View File

@@ -2784,6 +2784,69 @@ float PhysicsBackend::ComputeShapeSqrDistanceToPoint(void* shape, const Vector3&
{
auto shapePhysX = (PxShape*)shape;
const PxTransform trans(C2P(position), C2P(orientation));
// Special case for heightfield collider (not implemented in PhysX)
if (shapePhysX->getGeometryType() == PxGeometryType::eHEIGHTFIELD)
{
// Do a bunch of raycasts in all directions to find the closest point on the heightfield
PxVec3 origin = C2P(point);
Array<PxVec3> unitDirections;
constexpr int32 resolution = 32;
unitDirections.EnsureCapacity((resolution + 1) * (resolution + 1));
for (int32 i = 0; i <= resolution; i++)
{
float phi = PI * (float)i / resolution;
float sinPhi = Math::Sin(phi);
float cosPhi = Math::Cos(phi);
for (int32 j = 0; j <= resolution; j++)
{
float theta = 2.0f * PI * (float)j / resolution;
float cosTheta = Math::Cos(theta);
float sinTheta = Math::Sin(theta);
PxVec3 v;
v.x = cosTheta * sinPhi;
v.y = cosPhi;
v.z = sinTheta * sinPhi;
// All generated vectors are unit vectors (length 1)
unitDirections.Add(v);
}
}
PxReal maxDistance = PX_MAX_REAL; // Search indefinitely
PxQueryFilterData filterData;
filterData.data.word0 = (PxU32)shapePhysX->getSimulationFilterData().word0;
PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eMESH_BOTH_SIDES; // Both sides added for if it is underneath the height field
PxRaycastBuffer buffer;
auto scene = shapePhysX->getActor()->getScene();
PxReal closestDistance = maxDistance;
PxVec3 tempClosestPoint;
for (PxVec3& unitDir : unitDirections)
{
bool hitResult = scene->raycast(origin, unitDir, maxDistance, buffer, hitFlags, filterData);
if (hitResult)
{
auto& hit = buffer.getAnyHit(0);
if (hit.distance < closestDistance && hit.distance > 0.0f)
{
tempClosestPoint = hit.position;
closestDistance = hit.distance;
}
}
}
if (closestDistance < maxDistance)
{
*closestPoint = P2C(tempClosestPoint);
return closestDistance * closestDistance; // Result is squared distance
}
return -1.0f;
}
// Default point distance for other collider queries
#if USE_LARGE_WORLDS
PxVec3 closestPointPx;
float result = PxGeometryQuery::pointDistance(C2P(point), shapePhysX->getGeometry(), trans, &closestPointPx);

View File

@@ -102,7 +102,7 @@ public:
/// <param name="end">The end position of the line.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool LineCast(const Vector3& start, const Vector3& end, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -113,18 +113,18 @@ public:
/// <param name="hitInfo">The result hit information. 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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool LineCast(const Vector3& start, const Vector3& end, API_PARAM(Out) RayCastHit& hitInfo, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
// <summary>
/// Performs a line between two points in the scene, returns all hitpoints infos.
/// Performs a line between two points in the scene, returns all hit points info.
/// </summary>
/// <param name="start">The origin 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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool LineCastAll(const Vector3& start, const Vector3& end, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -135,7 +135,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool RayCast(const Vector3& origin, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -147,7 +147,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -159,7 +159,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool RayCastAll(const Vector3& origin, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -172,7 +172,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if box hits an matching object, otherwise false.</returns>
/// <returns>True if box hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -186,7 +186,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if box hits an matching object, otherwise false.</returns>
/// <returns>True if box hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -200,7 +200,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if box hits an matching object, otherwise false.</returns>
/// <returns>True if box hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -212,7 +212,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool SphereCast(const Vector3& center, float radius, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -225,7 +225,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool SphereCast(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -238,7 +238,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool SphereCastAll(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -252,7 +252,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -267,7 +267,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -282,7 +282,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool CapsuleCastAll(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -296,7 +296,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -311,7 +311,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -326,7 +326,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
API_FUNCTION() static bool ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -375,7 +375,7 @@ public:
API_FUNCTION() static bool CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given box.
/// Finds all colliders touching or inside the given box.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
@@ -387,7 +387,7 @@ public:
API_FUNCTION() static bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given sphere.
/// Finds all colliders touching or inside the given sphere.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
@@ -398,7 +398,7 @@ public:
API_FUNCTION() static bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given capsule.
/// Finds all colliders touching or inside the given capsule.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
@@ -411,7 +411,7 @@ public:
API_FUNCTION() static bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given convex mesh.
/// Finds all colliders touching or inside the given convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>
@@ -424,7 +424,7 @@ public:
API_FUNCTION() static bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given box.
/// Finds all colliders touching or inside the given box.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
@@ -436,7 +436,7 @@ public:
API_FUNCTION() static bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given sphere.
/// Finds all colliders touching or inside the given sphere.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
@@ -447,7 +447,7 @@ public:
API_FUNCTION() static bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given capsule.
/// Finds all colliders touching or inside the given capsule.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
@@ -460,7 +460,7 @@ public:
API_FUNCTION() static bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given convex mesh.
/// Finds all colliders touching or inside the given convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>

View File

@@ -140,7 +140,7 @@ public:
/// <param name="end">The end position of the line.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool LineCast(const Vector3& start, const Vector3& end, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -151,18 +151,18 @@ public:
/// <param name="hitInfo">The result hit information. 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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool LineCast(const Vector3& start, const Vector3& end, API_PARAM(Out) RayCastHit& hitInfo, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
// <summary>
/// Performs a line between two points in the scene, returns all hitpoints infos.
/// Performs a line between two points in the scene, returns all hit points info.
/// </summary>
/// <param name="start">The origin of the ray.</param>
/// <param name="end">The normalized direction of the ray.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool LineCastAll(const Vector3& start, const Vector3& end, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -173,7 +173,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -185,7 +185,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -197,7 +197,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
/// <returns>True if ray hits a matching object, otherwise false.</returns>
API_FUNCTION() bool RayCastAll(const Vector3& origin, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -210,7 +210,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if box hits an matching object, otherwise false.</returns>
/// <returns>True if box hits a matching object, otherwise false.</returns>
API_FUNCTION() bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -224,7 +224,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if box hits an matching object, otherwise false.</returns>
/// <returns>True if box hits a matching object, otherwise false.</returns>
API_FUNCTION() bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -238,7 +238,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if box hits an matching object, otherwise false.</returns>
/// <returns>True if box hits a matching object, otherwise false.</returns>
API_FUNCTION() bool BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -250,7 +250,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
API_FUNCTION() bool SphereCast(const Vector3& center, float radius, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -263,7 +263,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
API_FUNCTION() bool SphereCast(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -276,7 +276,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
API_FUNCTION() bool SphereCastAll(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -290,7 +290,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
API_FUNCTION() bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -305,7 +305,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
API_FUNCTION() bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -320,7 +320,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
API_FUNCTION() bool CapsuleCastAll(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -334,7 +334,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
API_FUNCTION() bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -349,7 +349,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
API_FUNCTION() bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -364,7 +364,7 @@ public:
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</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>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
API_FUNCTION() bool ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
@@ -413,7 +413,7 @@ public:
API_FUNCTION() bool CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given box.
/// Finds all colliders touching or inside the given box.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
@@ -425,7 +425,7 @@ public:
API_FUNCTION() bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given sphere.
/// Finds all colliders touching or inside the given sphere.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
@@ -436,7 +436,7 @@ public:
API_FUNCTION() bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given capsule.
/// Finds all colliders touching or inside the given capsule.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
@@ -449,7 +449,7 @@ public:
API_FUNCTION() bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given convex mesh.
/// Finds all colliders touching or inside the given convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>
@@ -462,7 +462,7 @@ public:
API_FUNCTION() bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given box.
/// Finds all colliders touching or inside the given box.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
@@ -474,7 +474,7 @@ public:
API_FUNCTION() bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given sphere.
/// Finds all colliders touching or inside the given sphere.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
@@ -485,7 +485,7 @@ public:
API_FUNCTION() bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given capsule.
/// Finds all colliders touching or inside the given capsule.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
@@ -498,7 +498,7 @@ public:
API_FUNCTION() bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given convex mesh.
/// Finds all colliders touching or inside the given convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>

View File

@@ -243,6 +243,7 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage)
#define INIT_PARENT(parent, child) GroupParents[(int32)ProfilerMemory::Groups::child] = (uint8)ProfilerMemory::Groups::parent
INIT_PARENT(Engine, EngineThreading);
INIT_PARENT(Engine, EngineDelegate);
INIT_PARENT(Engine, EngineDebug);
INIT_PARENT(Malloc, MallocArena);
INIT_PARENT(Graphics, GraphicsTextures);
INIT_PARENT(Graphics, GraphicsRenderTargets);
@@ -260,6 +261,8 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage)
INIT_PARENT(Content, ContentFiles);
INIT_PARENT(Level, LevelFoliage);
INIT_PARENT(Level, LevelTerrain);
INIT_PARENT(Navigation, NavigationMesh);
INIT_PARENT(Navigation, NavigationBuilding);
INIT_PARENT(Scripting, ScriptingVisual);
INIT_PARENT(Scripting, ScriptingCSharp);
INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCCommitted);

View File

@@ -44,6 +44,8 @@ public:
EngineThreading,
// Memory used by Delegate (engine events system to store all references).
EngineDelegate,
// Memory used by debug tools (eg. DebugDraw, DebugCommands or DebugLog).
EngineDebug,
// Total graphics memory usage.
Graphics,
@@ -105,6 +107,10 @@ public:
// Total navigation system memory.
Navigation,
// Navigation mesh memory.
NavigationMesh,
// Navigation mesh builder memory.
NavigationBuilding,
// Total networking system memory.
Networking,

View File

@@ -270,8 +270,8 @@ namespace FlaxEngine.Json
// 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)
return sceneA.PrefabObjectID == sceneB.PrefabObjectID;
if (objA is SceneObject sceneObjA && objB is SceneObject sceneObjB && sceneObjA && sceneObjB && sceneObjA.HasPrefabLink && sceneObjB.HasPrefabLink)
return sceneObjA.PrefabObjectID == sceneObjB.PrefabObjectID;
// Comparing an Int32 and Int64 both of the same value returns false, make types the same then compare
if (objA.GetType() != objB.GetType())
@@ -286,7 +286,6 @@ namespace FlaxEngine.Json
type == typeof(Int32) ||
type == typeof(UInt32) ||
type == typeof(Int64) ||
type == typeof(SByte) ||
type == typeof(UInt64);
}
if (IsInteger(objA) && IsInteger(objB))
@@ -301,6 +300,12 @@ namespace FlaxEngine.Json
{
if (aList.Count != bList.Count)
return false;
for (int i = 0; i < aList.Count; i++)
{
if (!ValueEquals(aList[i], bList[i]))
return false;
}
return true;
}
if (objA is IEnumerable aEnumerable && objB is IEnumerable bEnumerable)
{
@@ -316,8 +321,30 @@ namespace FlaxEngine.Json
return !bEnumerator.MoveNext();
}
if (objA is ICustomValueEquals customValueEquals && objA.GetType() == objB.GetType())
// Custom comparer
if (objA is ICustomValueEquals customValueEquals)
return customValueEquals.ValueEquals(objB);
// If type contains SceneObject references then it needs to use custom comparision that handles prefab links (see SceneObjectEquals)
var typeA = objA.GetType();
if (typeA.IsValueType && !typeA.IsEnum && !typeA.IsPrimitive)
{
var contract = Settings.ContractResolver.ResolveContract(typeA);
if (contract is JsonObjectContract objContract)
{
foreach (var property in objContract.Properties)
{
var valueProvider = property.ValueProvider;
var propA = valueProvider.GetValue(objA);
var propB = valueProvider.GetValue(objB);
if (!ValueEquals(propA, propB))
return false;
}
return true;
}
}
// Generic fallback
return objA.Equals(objB);
#endif
}

View File

@@ -415,7 +415,7 @@ namespace Serialization
inline bool ShouldSerialize(const ISerializable& v, const void* otherObj)
{
return true;
return !otherObj || v.ShouldSerialize(otherObj);
}
inline void Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)
{
@@ -431,7 +431,7 @@ namespace Serialization
template<typename T>
inline typename TEnableIf<TIsBaseOf<ISerializable, T>::Value, bool>::Type ShouldSerialize(const ISerializable& v, const void* otherObj)
{
return true;
return !otherObj || v.ShouldSerialize(otherObj);
}
template<typename T>
inline typename TEnableIf<TIsBaseOf<ISerializable, T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)

View File

@@ -148,9 +148,8 @@ Task* Task::StartNew(Function<bool()>::Signature& action, Object* target)
void Task::Execute()
{
if (IsCanceled())
if (!IsQueued())
return;
ASSERT(IsQueued());
SetState(TaskState::Running);
// Perform an operation

View File

@@ -534,7 +534,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
}
// Sample Texture
case 9:
// Procedural Texture Sample
// Procedural Sample Texture
case 17:
{
// Get input boxes
@@ -585,7 +585,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
const auto offset = useOffset ? eatBox(offsetBox->GetParent<Node>(), offsetBox->FirstConnection()) : Value::Zero;
const Char* samplerName;
const int32 samplerIndex = node->Values[0].AsInt;
if (samplerIndex == TextureGroup)
if (samplerIndex == TextureGroup && node->Values.Count() > 2)
{
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[2].AsInt);
samplerName = *textureGroupSampler.ShaderName;
@@ -739,7 +739,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
const int32 samplerIndex = node->Values.Count() >= 4 ? node->Values[3].AsInt : LinearWrap;
if (samplerIndex == TextureGroup)
{
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[3].AsInt);
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[5].AsInt);
samplerName = *textureGroupSampler.ShaderName;
}
else if (samplerIndex >= 0 && samplerIndex < ARRAY_COUNT(SamplerNames))
@@ -828,7 +828,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
const int32 samplerIndex = node->Values[3].AsInt;
if (samplerIndex == TextureGroup)
{
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[3].AsInt);
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[5].AsInt);
samplerName = *textureGroupSampler.ShaderName;
}
else if (samplerIndex >= 0 && samplerIndex < ARRAY_COUNT(SamplerNames))

View File

@@ -567,6 +567,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(CalculateBoneOffsetMatrices);
SERIALIZE(LightmapUVsSource);
SERIALIZE(CollisionMeshesPrefix);
SERIALIZE(CollisionMeshesPostfix);
SERIALIZE(CollisionType);
SERIALIZE(PositionFormat);
SERIALIZE(TexCoordFormat);
@@ -621,6 +622,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(CalculateBoneOffsetMatrices);
DESERIALIZE(LightmapUVsSource);
DESERIALIZE(CollisionMeshesPrefix);
DESERIALIZE(CollisionMeshesPostfix);
DESERIALIZE(CollisionType);
DESERIALIZE(PositionFormat);
DESERIALIZE(TexCoordFormat);
@@ -1830,7 +1832,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
}
// Collision mesh output
if (options.CollisionMeshesPrefix.HasChars())
if (options.CollisionMeshesPrefix.HasChars() || options.CollisionMeshesPostfix.HasChars())
{
// Extract collision meshes from the model
ModelData collisionModel;
@@ -1839,7 +1841,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
for (int32 i = lod.Meshes.Count() - 1; i >= 0; i--)
{
auto mesh = lod.Meshes[i];
if (mesh->Name.StartsWith(options.CollisionMeshesPrefix, StringSearchCase::IgnoreCase))
if ((options.CollisionMeshesPrefix.HasChars() && mesh->Name.StartsWith(options.CollisionMeshesPrefix, StringSearchCase::IgnoreCase)) ||
(options.CollisionMeshesPostfix.HasChars() && mesh->Name.EndsWith(options.CollisionMeshesPostfix, StringSearchCase::IgnoreCase)))
{
// Remove material slot used by this mesh (if no other mesh else uses it)
int32 materialSlotUsageCount = 0;

View File

@@ -221,6 +221,9 @@ public:
// If specified, all meshes that name starts with this prefix in the name will be imported as a separate collision data asset (excluded used for rendering).
API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
String CollisionMeshesPrefix = TEXT("");
// If specified, all meshes that name ends with this postfix in the name will be imported as a separate collision data asset (excluded used for rendering).
API_FIELD(Attributes="EditorOrder(101), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
String CollisionMeshesPostfix = TEXT("");
// The type of collision that should be generated if the mesh has a collision prefix specified.
API_FIELD(Attributes="EditorOrder(105), EditorDisplay(\"Geometry\"), VisibleIf(nameof(ShowGeometry))")
CollisionDataType CollisionType = CollisionDataType::ConvexMesh;

View File

@@ -449,8 +449,7 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override bool RayCast(ref Float2 location, out Control hit)
{
var p = location / _scale;
if (RayCastChildren(ref p, out hit))
if (RayCastChildren(ref location, out hit))
return true;
return base.RayCast(ref location, out hit);
}

View File

@@ -143,6 +143,40 @@ namespace FlaxEngine.GUI
context.Caret.X = 0;
OnLineAdded(ref context, _text.Length - 1);
}
// Organize lines vertically
if (_textBlocks.Count != 0)
{
var lastBlock = _textBlocks[_textBlocks.Count - 1];
// Get style (global or leftover from style stack or the last lime)
var verticalAlignments = _textStyle.Alignment;
if (context.StyleStack.Count > 1)
verticalAlignments = context.StyleStack.Peek().Alignment;
else if ((lastBlock.Style.Alignment & TextBlockStyle.Alignments.VerticalMask) != TextBlockStyle.Alignments.Baseline)
verticalAlignments = lastBlock.Style.Alignment;
var totalSize = lastBlock.Bounds.BottomRight;
var sizeOffset = Size - totalSize;
var textBlocks = CollectionsMarshal.AsSpan(_textBlocks);
if ((verticalAlignments & TextBlockStyle.Alignments.Middle) == TextBlockStyle.Alignments.Middle)
{
sizeOffset.Y *= 0.5f;
for (int i = 0; i < _textBlocks.Count; i++)
{
ref TextBlock textBlock = ref textBlocks[i];
textBlock.Bounds.Location.Y += sizeOffset.Y;
}
}
else if ((verticalAlignments & TextBlockStyle.Alignments.Bottom) == TextBlockStyle.Alignments.Bottom)
{
for (int i = 0; i < _textBlocks.Count; i++)
{
ref TextBlock textBlock = ref textBlocks[i];
textBlock.Bounds.Location.Y += sizeOffset.Y;
}
}
}
}
/// <summary>
@@ -239,14 +273,15 @@ namespace FlaxEngine.GUI
}
// Organize text blocks within line
var horizontalAlignments = TextBlockStyle.Alignments.Baseline;
var verticalAlignments = TextBlockStyle.Alignments.Baseline;
var lineAlignments = TextBlockStyle.Alignments.Baseline;
for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++)
{
ref TextBlock textBlock = ref textBlocks[i];
var vOffset = lineSize.Y - textBlock.Bounds.Height;
horizontalAlignments |= textBlock.Style.Alignment & TextBlockStyle.Alignments.HorizontalMask;
verticalAlignments |= textBlock.Style.Alignment & TextBlockStyle.Alignments.VerticalMask;
if (i == context.LineStartTextBlockIndex)
lineAlignments = textBlock.Style.Alignment;
else
lineAlignments &= textBlock.Style.Alignment;
switch (textBlock.Style.Alignment & TextBlockStyle.Alignments.VerticalMask)
{
case TextBlockStyle.Alignments.Baseline:
@@ -275,9 +310,9 @@ namespace FlaxEngine.GUI
}
}
// Organize blocks within whole container
// Organize whole line horizontally
var sizeOffset = Size - lineSize;
if ((horizontalAlignments & TextBlockStyle.Alignments.Center) == TextBlockStyle.Alignments.Center)
if ((lineAlignments & TextBlockStyle.Alignments.Center) == TextBlockStyle.Alignments.Center)
{
sizeOffset.X *= 0.5f;
for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++)
@@ -286,7 +321,7 @@ namespace FlaxEngine.GUI
textBlock.Bounds.Location.X += sizeOffset.X;
}
}
else if ((horizontalAlignments & TextBlockStyle.Alignments.Right) == TextBlockStyle.Alignments.Right)
else if ((lineAlignments & TextBlockStyle.Alignments.Right) == TextBlockStyle.Alignments.Right)
{
for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++)
{
@@ -294,23 +329,6 @@ namespace FlaxEngine.GUI
textBlock.Bounds.Location.X += sizeOffset.X;
}
}
if ((verticalAlignments & TextBlockStyle.Alignments.Middle) == TextBlockStyle.Alignments.Middle)
{
sizeOffset.Y *= 0.5f;
for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++)
{
ref TextBlock textBlock = ref textBlocks[i];
textBlock.Bounds.Location.Y += sizeOffset.Y;
}
}
else if ((verticalAlignments & TextBlockStyle.Alignments.Bottom) == TextBlockStyle.Alignments.Bottom)
{
for (int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++)
{
ref TextBlock textBlock = ref textBlocks[i];
textBlock.Bounds.Location.Y += sizeOffset.Y;
}
}
// Move to the next line
context.LineStartCharacterIndex = lineEnd + 1;

View File

@@ -175,7 +175,7 @@ namespace FlaxEngine.GUI
// Setup size
var font = imageBlock.Style.Font.GetFont();
if (font)
imageBlock.Bounds.Size = new Float2(font.Height);
imageBlock.Bounds.Size = new Float2(font.Ascender);
var imageSize = image.Size;
imageBlock.Bounds.Size.X *= imageSize.X / imageSize.Y; // Keep original aspect ratio
bool hasWidth = TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width);
@@ -215,16 +215,16 @@ namespace FlaxEngine.GUI
switch (valign)
{
case "top":
style.Alignment = TextBlockStyle.Alignments.Top;
style.Alignment |= TextBlockStyle.Alignments.Top;
break;
case "bottom":
style.Alignment = TextBlockStyle.Alignments.Bottom;
style.Alignment |= TextBlockStyle.Alignments.Bottom;
break;
case "middle":
style.Alignment = TextBlockStyle.Alignments.Middle;
style.Alignment |= TextBlockStyle.Alignments.Middle;
break;
case "baseline":
style.Alignment = TextBlockStyle.Alignments.Baseline;
style.Alignment |= TextBlockStyle.Alignments.Baseline;
break;
}
}
@@ -243,17 +243,17 @@ namespace FlaxEngine.GUI
var style = context.StyleStack.Peek();
if (tag.Attributes.TryGetValue(string.Empty, out var valign))
{
style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask;
style.Alignment &= ~TextBlockStyle.Alignments.HorizontalMask;
switch (valign)
{
case "left":
style.Alignment = TextBlockStyle.Alignments.Left;
style.Alignment |= TextBlockStyle.Alignments.Left;
break;
case "right":
style.Alignment = TextBlockStyle.Alignments.Right;
style.Alignment |= TextBlockStyle.Alignments.Right;
break;
case "center":
style.Alignment = TextBlockStyle.Alignments.Center;
style.Alignment |= TextBlockStyle.Alignments.Center;
break;
}
}
@@ -270,7 +270,8 @@ namespace FlaxEngine.GUI
else
{
var style = context.StyleStack.Peek();
style.Alignment = TextBlockStyle.Alignments.Center;
style.Alignment &= ~TextBlockStyle.Alignments.HorizontalMask;
style.Alignment |= TextBlockStyle.Alignments.Center;
context.StyleStack.Push(style);
}
}

View File

@@ -901,6 +901,15 @@ namespace FlaxEngine.GUI
internal bool RayCastChildren(ref Float2 location, out Control hit)
{
if (_clipChildren)
{
GetDesireClientArea(out var clientArea);
if (!clientArea.Contains(ref location))
{
hit = null;
return false;
}
}
for (int i = _children.Count - 1; i >= 0 && _children.Count > 0; i--)
{
var child = _children[i];

View File

@@ -47,6 +47,8 @@ struct VideoPlayerMF
namespace MF
{
Array<VideoBackendPlayer*> Players;
TimeSpan UpdateDeltaTime;
double UpdateTime;
bool Configure(VideoBackendPlayer& player, VideoPlayerMF& playerMF, DWORD streamIndex)
{
@@ -395,13 +397,6 @@ namespace MF
if (!playerMF.Playing && !playerMF.Seek)
return;
bool useTimeScale = true;
#if USE_EDITOR
if (!Editor::IsPlayMode)
useTimeScale = false;
#endif
TimeSpan dt = useTimeScale ? Time::Update.DeltaTime : Time::Update.UnscaledDeltaTime;
// Update playback time
if (playerMF.FirstFrame)
{
@@ -410,9 +405,9 @@ namespace MF
}
else if (playerMF.Playing)
{
playerMF.Time += dt;
playerMF.Time += UpdateDeltaTime;
}
if (playerMF.Time > player.Duration)
if (playerMF.Time > player.Duration && player.Duration.Ticks != 0)
{
if (playerMF.Loop)
{
@@ -452,7 +447,7 @@ namespace MF
}
// Update streams
if (ReadStream(player, playerMF, MF_SOURCE_READER_FIRST_VIDEO_STREAM, dt))
if (ReadStream(player, playerMF, MF_SOURCE_READER_FIRST_VIDEO_STREAM, UpdateDeltaTime))
{
// Failed to pick a valid sample so try again with seeking
playerMF.Seek = 1;
@@ -464,7 +459,7 @@ namespace MF
}
}
if (player.AudioInfo.BitDepth != 0)
ReadStream(player, playerMF, MF_SOURCE_READER_FIRST_AUDIO_STREAM, dt);
ReadStream(player, playerMF, MF_SOURCE_READER_FIRST_AUDIO_STREAM, UpdateDeltaTime);
player.Tick();
}
@@ -610,12 +605,17 @@ bool VideoBackendMF::Base_Init()
VIDEO_API_MF_ERROR(MFStartup, hr);
return true;
}
MF::UpdateTime = Platform::GetTimeSeconds();
return false;
}
void VideoBackendMF::Base_Update(TaskGraph* graph)
{
double time = Platform::GetTimeSeconds();
MF::UpdateDeltaTime = TimeSpan::FromSeconds(time - MF::UpdateTime);
MF::UpdateTime = time;
// Schedule work to update all videos in async
Function<void(int32)> job;
job.Bind(MF::UpdatePlayer);

View File

@@ -133,7 +133,9 @@ void VideoPlayer::SetTime(float time)
if (_state == States::Stopped || _player.Backend == nullptr)
return;
TimeSpan timeSpan = TimeSpan::FromSeconds(time);
timeSpan.Ticks = Math::Clamp<int64>(timeSpan.Ticks, 0, _player.Duration.Ticks);
timeSpan.Ticks = Math::Max<int64>(timeSpan.Ticks, 0);
if (_player.Duration.Ticks > 0)
timeSpan.Ticks = Math::Min<int64>(timeSpan.Ticks, _player.Duration.Ticks);
_player.Backend->Player_Seek(_player, timeSpan);
}

View File

@@ -15,6 +15,7 @@ class FLAXENGINE_API VideoPlayer : public Actor
{
DECLARE_SCENE_OBJECT(VideoPlayer);
API_AUTO_SERIALIZATION();
friend class AudioBackendOAL;
public:
/// <summary>