Merge remote-tracking branch 'origin/master' into 1.7
# Conflicts: # Flax.flaxproj
This commit is contained in:
@@ -348,7 +348,7 @@ Asset::LoadResult AudioClip::load()
|
||||
|
||||
#if !BUILD_RELEASE
|
||||
// Validate buffer start times
|
||||
if (Math::NotNearEqual(_buffersStartTimes[_totalChunks], GetLength()))
|
||||
if (!Math::NearEqual(_buffersStartTimes[_totalChunks], GetLength(), 1.0f / 60.0f))
|
||||
{
|
||||
LOG(Warning, "Invalid audio buffers data size. Expected length: {0}s", GetLength());
|
||||
for (int32 i = 0; i < _totalChunks + 1; i++)
|
||||
@@ -454,14 +454,12 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
|
||||
}
|
||||
break;
|
||||
case AudioFormat::Raw:
|
||||
{
|
||||
data = Span<byte>(chunk->Get(), chunk->Size());
|
||||
}
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
info.NumSamples = data.Length() / bytesPerSample;
|
||||
info.NumSamples = Math::AlignDown(data.Length() / bytesPerSample, info.NumChannels * bytesPerSample);
|
||||
|
||||
// Convert to Mono if used as 3D source and backend doesn't support it
|
||||
if (Is3D() && info.NumChannels > 1 && EnumHasNoneFlags(AudioBackend::Features(), AudioBackend::FeatureFlags::SpatialMultiChannel))
|
||||
|
||||
@@ -183,7 +183,7 @@ void AudioSource::Stop()
|
||||
|
||||
float AudioSource::GetTime() const
|
||||
{
|
||||
if (_state == States::Stopped || SourceIDs.IsEmpty())
|
||||
if (_state == States::Stopped || SourceIDs.IsEmpty() || !Clip->IsLoaded())
|
||||
return 0.0f;
|
||||
|
||||
float time = AudioBackend::Source::GetCurrentBufferTime(this);
|
||||
@@ -265,6 +265,7 @@ void AudioSource::Cleanup()
|
||||
void AudioSource::OnClipChanged()
|
||||
{
|
||||
Stop();
|
||||
_clipChanged = true;
|
||||
}
|
||||
|
||||
void AudioSource::OnClipLoaded()
|
||||
@@ -318,6 +319,12 @@ void AudioSource::SetNonStreamingBuffer()
|
||||
|
||||
void AudioSource::PlayInternal()
|
||||
{
|
||||
if (_clipChanged && SourceIDs.HasItems())
|
||||
{
|
||||
// If clip was changed between source setup (OnEnable) and actual playback start then ensure to flush any runtime properties with the audio backend
|
||||
_clipChanged = false;
|
||||
AudioBackend::Source::SpatialSetupChanged(this);
|
||||
}
|
||||
AudioBackend::Source::Play(this);
|
||||
|
||||
_isActuallyPlayingSth = true;
|
||||
@@ -482,6 +489,7 @@ void AudioSource::OnEnable()
|
||||
{
|
||||
_prevPos = GetPosition();
|
||||
_velocity = Vector3::Zero;
|
||||
_clipChanged = false;
|
||||
|
||||
Audio::OnAddSource(this);
|
||||
GetScene()->Ticking.Update.AddTick<AudioSource, &AudioSource::Update>(this);
|
||||
|
||||
@@ -53,6 +53,7 @@ private:
|
||||
bool _loop;
|
||||
bool _playOnStart;
|
||||
bool _allowSpatialization;
|
||||
bool _clipChanged = false;
|
||||
|
||||
bool _isActuallyPlayingSth = false;
|
||||
bool _needToUpdateStreamingBuffers = false;
|
||||
|
||||
@@ -228,11 +228,9 @@ namespace ALC
|
||||
ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
|
||||
{
|
||||
// TODO: cache enum values in Init()??
|
||||
|
||||
switch (bitDepth)
|
||||
{
|
||||
case 8:
|
||||
{
|
||||
switch (numChannels)
|
||||
{
|
||||
case 1:
|
||||
@@ -247,13 +245,8 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
|
||||
return alGetEnumValue("AL_FORMAT_61CHN8");
|
||||
case 8:
|
||||
return alGetEnumValue("AL_FORMAT_71CHN8");
|
||||
default:
|
||||
CRASH;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
case 16:
|
||||
{
|
||||
switch (numChannels)
|
||||
{
|
||||
case 1:
|
||||
@@ -268,19 +261,22 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
|
||||
return alGetEnumValue("AL_FORMAT_61CHN16");
|
||||
case 8:
|
||||
return alGetEnumValue("AL_FORMAT_71CHN16");
|
||||
default:
|
||||
CRASH;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
case 32:
|
||||
{
|
||||
switch (numChannels)
|
||||
{
|
||||
case 1:
|
||||
#ifdef AL_FORMAT_MONO_FLOAT32
|
||||
return AL_FORMAT_MONO_FLOAT32;
|
||||
#else
|
||||
return alGetEnumValue("AL_FORMAT_MONO_FLOAT32");
|
||||
#endif
|
||||
case 2:
|
||||
#ifdef AL_FORMAT_STEREO_FLOAT32
|
||||
return AL_FORMAT_STEREO_FLOAT32;
|
||||
#else
|
||||
return alGetEnumValue("AL_FORMAT_STEREO_FLOAT32");
|
||||
#endif
|
||||
case 4:
|
||||
return alGetEnumValue("AL_FORMAT_QUAD32");
|
||||
case 6:
|
||||
@@ -289,15 +285,9 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
|
||||
return alGetEnumValue("AL_FORMAT_61CHN32");
|
||||
case 8:
|
||||
return alGetEnumValue("AL_FORMAT_71CHN32");
|
||||
default:
|
||||
CRASH;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
default:
|
||||
CRASH;
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
|
||||
@@ -607,7 +597,8 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// TODO: maybe use temporary buffers per thread to reduce dynamic allocations when uploading data to OpenAL?
|
||||
// Pick the format for the audio data (it might not be supported natively)
|
||||
ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
|
||||
|
||||
// Mono or stereo
|
||||
if (info.NumChannels <= 2)
|
||||
@@ -618,28 +609,23 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
|
||||
{
|
||||
const uint32 bufferSize = info.NumSamples * sizeof(float);
|
||||
float* sampleBufferFloat = (float*)Allocator::Allocate(bufferSize);
|
||||
|
||||
AudioTool::ConvertToFloat(samples, info.BitDepth, sampleBufferFloat, info.NumSamples);
|
||||
|
||||
const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
|
||||
format = GetOpenALBufferFormat(info.NumChannels, 32);
|
||||
alBufferData(bufferId, format, sampleBufferFloat, bufferSize, info.SampleRate);
|
||||
ALC_CHECK_ERROR(alBufferData);
|
||||
|
||||
Allocator::Free(sampleBufferFloat);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "OpenAL doesn't support bit depth larger than 16. Your audio data will be truncated.");
|
||||
|
||||
const uint32 bufferSize = info.NumSamples * 2;
|
||||
byte* sampleBuffer16 = (byte*)Allocator::Allocate(bufferSize);
|
||||
|
||||
AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer16, 16, info.NumSamples);
|
||||
|
||||
const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16);
|
||||
format = GetOpenALBufferFormat(info.NumChannels, 16);
|
||||
alBufferData(bufferId, format, sampleBuffer16, bufferSize, info.SampleRate);
|
||||
ALC_CHECK_ERROR(alBufferData);
|
||||
|
||||
Allocator::Free(sampleBuffer16);
|
||||
}
|
||||
}
|
||||
@@ -648,19 +634,15 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
|
||||
// OpenAL expects unsigned 8-bit data, but engine stores it as signed, so convert
|
||||
const uint32 bufferSize = info.NumSamples * (info.BitDepth / 8);
|
||||
byte* sampleBuffer = (byte*)Allocator::Allocate(bufferSize);
|
||||
|
||||
for (uint32 i = 0; i < info.NumSamples; i++)
|
||||
sampleBuffer[i] = ((int8*)samples)[i] + 128;
|
||||
|
||||
const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16);
|
||||
alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate);
|
||||
ALC_CHECK_ERROR(alBufferData);
|
||||
|
||||
Allocator::Free(sampleBuffer);
|
||||
}
|
||||
else
|
||||
else if (format)
|
||||
{
|
||||
const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
|
||||
alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
|
||||
ALC_CHECK_ERROR(alBufferData);
|
||||
}
|
||||
@@ -675,10 +657,9 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
|
||||
{
|
||||
const uint32 bufferSize = info.NumChannels * sizeof(int32);
|
||||
byte* sampleBuffer32 = (byte*)Allocator::Allocate(bufferSize);
|
||||
|
||||
AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer32, 32, info.NumSamples);
|
||||
|
||||
const ALenum format = GetOpenALBufferFormat(info.NumChannels, 32);
|
||||
format = GetOpenALBufferFormat(info.NumChannels, 32);
|
||||
alBufferData(bufferId, format, sampleBuffer32, bufferSize, info.SampleRate);
|
||||
ALC_CHECK_ERROR(alBufferData);
|
||||
|
||||
@@ -693,19 +674,23 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
|
||||
for (uint32 i = 0; i < info.NumSamples; i++)
|
||||
sampleBuffer[i] = ((int8*)samples)[i] + 128;
|
||||
|
||||
const ALenum format = GetOpenALBufferFormat(info.NumChannels, 16);
|
||||
format = GetOpenALBufferFormat(info.NumChannels, 16);
|
||||
alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate);
|
||||
ALC_CHECK_ERROR(alBufferData);
|
||||
|
||||
Allocator::Free(sampleBuffer);
|
||||
}
|
||||
else
|
||||
else if (format)
|
||||
{
|
||||
const ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
|
||||
alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
|
||||
ALC_CHECK_ERROR(alBufferData);
|
||||
}
|
||||
}
|
||||
|
||||
if (!format)
|
||||
{
|
||||
LOG(Error, "Not suppported audio data format for OpenAL device: BitDepth={}, NumChannels={}", info.BitDepth, info.NumChannels);
|
||||
}
|
||||
}
|
||||
|
||||
const Char* AudioBackendOAL::Base_Name()
|
||||
|
||||
@@ -15,15 +15,8 @@
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
AssetsCache::AssetsCache()
|
||||
: _isDirty(false)
|
||||
, _registry(4096)
|
||||
{
|
||||
}
|
||||
|
||||
void AssetsCache::Init()
|
||||
{
|
||||
// Cache data
|
||||
Entry e;
|
||||
int32 count;
|
||||
const DateTime loadStartTime = DateTime::Now();
|
||||
@@ -32,13 +25,11 @@ void AssetsCache::Init()
|
||||
#else
|
||||
_path = Globals::ProjectContentFolder / TEXT("AssetsCache.dat");
|
||||
#endif
|
||||
|
||||
LOG(Info, "Loading Asset Cache {0}...", _path);
|
||||
|
||||
// Check if assets registry exists
|
||||
if (!FileSystem::FileExists(_path))
|
||||
{
|
||||
// Back
|
||||
_isDirty = true;
|
||||
LOG(Warning, "Cannot find assets cache file");
|
||||
return;
|
||||
@@ -49,7 +40,6 @@ void AssetsCache::Init()
|
||||
DeleteMe<FileReadStream> deleteStream(stream);
|
||||
|
||||
// Load version
|
||||
// Note: every Engine build is using different assets cache
|
||||
int32 version;
|
||||
stream->ReadInt32(&version);
|
||||
if (version != FLAXENGINE_VERSION_BUILD)
|
||||
@@ -58,24 +48,28 @@ void AssetsCache::Init()
|
||||
return;
|
||||
}
|
||||
|
||||
// Load Engine workspace path
|
||||
String workspacePath;
|
||||
stream->ReadString(&workspacePath, -410);
|
||||
// Load paths
|
||||
String enginePath, projectPath;
|
||||
stream->ReadString(&enginePath, -410);
|
||||
stream->ReadString(&projectPath, -410);
|
||||
|
||||
// Flags
|
||||
AssetsCacheFlags flags;
|
||||
stream->ReadInt32((int32*)&flags);
|
||||
|
||||
// Check if other engine instance used this cache (cache depends on engine build and install location)
|
||||
// Skip it for relative paths mode
|
||||
if (!(flags & AssetsCacheFlags::RelativePaths) && workspacePath != Globals::StartupFolder)
|
||||
// Check if other workspace instance used this cache
|
||||
if (EnumHasNoneFlags(flags, AssetsCacheFlags::RelativePaths) && enginePath != Globals::StartupFolder)
|
||||
{
|
||||
LOG(Warning, "Assets cache generated by the different engine installation in \'{0}\'", workspacePath);
|
||||
LOG(Warning, "Assets cache generated by the different {1} installation in \'{0}\'", enginePath, TEXT("engine"));
|
||||
return;
|
||||
}
|
||||
if (EnumHasNoneFlags(flags, AssetsCacheFlags::RelativePaths) && projectPath != Globals::ProjectFolder)
|
||||
{
|
||||
LOG(Warning, "Assets cache generated by the different {1} installation in \'{0}\'", projectPath, TEXT("project"));
|
||||
return;
|
||||
}
|
||||
|
||||
ScopeLock lock(_locker);
|
||||
|
||||
_isDirty = false;
|
||||
|
||||
// Load elements count
|
||||
@@ -103,15 +97,11 @@ void AssetsCache::Init()
|
||||
e.Info.Path = Globals::StartupFolder / e.Info.Path;
|
||||
}
|
||||
|
||||
// Validate entry
|
||||
if (!IsEntryValid(e))
|
||||
{
|
||||
// Reject
|
||||
// Use only valid entries
|
||||
if (IsEntryValid(e))
|
||||
_registry.Add(e.Info.ID, e);
|
||||
else
|
||||
rejectedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
_registry.Add(e.Info.ID, e);
|
||||
}
|
||||
|
||||
// Paths mapping
|
||||
@@ -148,7 +138,6 @@ void AssetsCache::Init()
|
||||
}
|
||||
}
|
||||
|
||||
// End
|
||||
const int32 loadTimeInMs = static_cast<int32>((DateTime::Now() - loadStartTime).GetTotalMilliseconds());
|
||||
LOG(Info, "Asset Cache loaded {0} entries in {1} ms ({2} rejected)", _registry.Count(), loadTimeInMs, rejectedCount);
|
||||
}
|
||||
@@ -188,8 +177,9 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
|
||||
// Version
|
||||
stream->WriteInt32(FLAXENGINE_VERSION_BUILD);
|
||||
|
||||
// Engine workspace path
|
||||
// Paths
|
||||
stream->WriteString(Globals::StartupFolder, -410);
|
||||
stream->WriteString(Globals::ProjectFolder, -410);
|
||||
|
||||
// Flags
|
||||
stream->WriteInt32((int32)flags);
|
||||
@@ -202,7 +192,6 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
|
||||
for (auto i = entries.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
auto& e = i->Value;
|
||||
|
||||
stream->Write(e.Info.ID);
|
||||
stream->WriteString(e.Info.TypeName, index - 13);
|
||||
stream->WriteString(e.Info.Path, index);
|
||||
@@ -211,7 +200,6 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
|
||||
#else
|
||||
stream->WriteInt64(0);
|
||||
#endif
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
@@ -222,7 +210,6 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
|
||||
{
|
||||
stream->Write(i->Value);
|
||||
stream->WriteString(i->Key, index + 73);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public:
|
||||
typedef Dictionary<String, Guid> PathsMapping;
|
||||
|
||||
private:
|
||||
bool _isDirty;
|
||||
bool _isDirty = false;
|
||||
CriticalSection _locker;
|
||||
Registry _registry;
|
||||
PathsMapping _pathsMapping;
|
||||
@@ -82,15 +82,8 @@ private:
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetsCache"/> class.
|
||||
/// Gets amount of registered assets.
|
||||
/// </summary>
|
||||
AssetsCache();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets amount of registered assets
|
||||
/// </summary>
|
||||
/// <returns>Registry size</returns>
|
||||
int32 Size() const
|
||||
{
|
||||
_locker.Lock();
|
||||
|
||||
@@ -125,16 +125,12 @@ void ContentService::LateUpdate()
|
||||
if (timeNow - LastUnloadCheckTime < Content::AssetsUpdateInterval)
|
||||
return;
|
||||
LastUnloadCheckTime = timeNow;
|
||||
|
||||
Asset* asset;
|
||||
ScopeLock lock(AssetsLocker);
|
||||
|
||||
// TODO: maybe it would be better to link for asset remove ref event and cache only assets with no references - test it with millions of assets?
|
||||
AssetsLocker.Lock();
|
||||
|
||||
// Verify all assets
|
||||
for (auto i = Assets.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
asset = i->Value;
|
||||
Asset* asset = i->Value;
|
||||
|
||||
// Check if has no references and is not during unloading
|
||||
if (asset->GetReferencesCount() <= 0 && !UnloadQueue.ContainsKey(asset))
|
||||
@@ -158,7 +154,7 @@ void ContentService::LateUpdate()
|
||||
// Unload marked assets
|
||||
for (int32 i = 0; i < ToUnload.Count(); i++)
|
||||
{
|
||||
asset = ToUnload[i];
|
||||
Asset* asset = ToUnload[i];
|
||||
|
||||
// Check if has no references
|
||||
if (asset->GetReferencesCount() <= 0)
|
||||
@@ -170,6 +166,8 @@ void ContentService::LateUpdate()
|
||||
UnloadQueue.Remove(asset);
|
||||
}
|
||||
|
||||
AssetsLocker.Unlock();
|
||||
|
||||
// Update cache (for longer sessions it will help to reduce cache misses)
|
||||
Cache.Save();
|
||||
}
|
||||
@@ -212,7 +210,6 @@ bool FindAssets(const ProjectInfo* project, HashSet<const ProjectInfo*>& project
|
||||
{
|
||||
if (projects.Contains(project))
|
||||
return false;
|
||||
|
||||
projects.Add(project);
|
||||
bool found = findAsset(id, project->ProjectFolderPath / TEXT("Content"), tmpCache, info);
|
||||
for (const auto& reference : project->References)
|
||||
@@ -220,7 +217,6 @@ bool FindAssets(const ProjectInfo* project, HashSet<const ProjectInfo*>& project
|
||||
if (reference.Project)
|
||||
found |= FindAssets(reference.Project, projects, id, tmpCache, info);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
@@ -232,7 +228,6 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
|
||||
return false;
|
||||
|
||||
#if ENABLE_ASSETS_DISCOVERY
|
||||
|
||||
// Find asset in registry
|
||||
if (Cache.FindAsset(id, info))
|
||||
return true;
|
||||
@@ -270,19 +265,15 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
|
||||
|
||||
//LOG(Warning, "Cannot find {0}.", id);
|
||||
return false;
|
||||
|
||||
#else
|
||||
|
||||
// Find asset in registry
|
||||
return Cache.FindAsset(id, info);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
|
||||
{
|
||||
#if ENABLE_ASSETS_DISCOVERY
|
||||
|
||||
// Find asset in registry
|
||||
if (Cache.FindAsset(path, info))
|
||||
return true;
|
||||
@@ -326,12 +317,9 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
#else
|
||||
|
||||
// Find asset in registry
|
||||
return Cache.FindAsset(path, info);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -18,32 +18,6 @@
|
||||
#include "Engine/Tools/AudioTool/OggVorbisDecoder.h"
|
||||
#include "Engine/Tools/AudioTool/OggVorbisEncoder.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
#include "Engine/Scripting/Enums.h"
|
||||
|
||||
String ImportAudio::Options::ToString() const
|
||||
{
|
||||
return String::Format(TEXT("Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"), ScriptingEnum::ToString(Format), DisableStreaming, Is3D, Quality, BitDepth);
|
||||
}
|
||||
|
||||
void ImportAudio::Options::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
SERIALIZE_GET_OTHER_OBJ(ImportAudio::Options);
|
||||
|
||||
SERIALIZE(Format);
|
||||
SERIALIZE(DisableStreaming);
|
||||
SERIALIZE(Is3D);
|
||||
SERIALIZE(Quality);
|
||||
SERIALIZE(BitDepth);
|
||||
}
|
||||
|
||||
void ImportAudio::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
DESERIALIZE(Format);
|
||||
DESERIALIZE(DisableStreaming);
|
||||
DESERIALIZE(Is3D);
|
||||
DESERIALIZE(Quality);
|
||||
DESERIALIZE(BitDepth);
|
||||
}
|
||||
|
||||
bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options)
|
||||
{
|
||||
@@ -90,6 +64,10 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
|
||||
}
|
||||
}
|
||||
|
||||
// Vorbis uses fixed 16-bit depth
|
||||
if (options.Format == AudioFormat::Vorbis)
|
||||
options.BitDepth = AudioTool::BitDepth::_16;
|
||||
|
||||
LOG_STR(Info, options.ToString());
|
||||
|
||||
// Open the file
|
||||
@@ -112,16 +90,14 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
|
||||
sampleBuffer.Link(audioData.Get());
|
||||
|
||||
// Convert bit depth if need to
|
||||
if (options.BitDepth != static_cast<int32>(info.BitDepth))
|
||||
uint32 outputBitDepth = (uint32)options.BitDepth;
|
||||
if (outputBitDepth != info.BitDepth)
|
||||
{
|
||||
const uint32 outBufferSize = info.NumSamples * (options.BitDepth / 8);
|
||||
const uint32 outBufferSize = info.NumSamples * (outputBitDepth / 8);
|
||||
sampleBuffer.Allocate(outBufferSize);
|
||||
|
||||
AudioTool::ConvertBitDepth(audioData.Get(), info.BitDepth, sampleBuffer.Get(), options.BitDepth, info.NumSamples);
|
||||
|
||||
info.BitDepth = options.BitDepth;
|
||||
AudioTool::ConvertBitDepth(audioData.Get(), info.BitDepth, sampleBuffer.Get(), outputBitDepth, info.NumSamples);
|
||||
info.BitDepth = outputBitDepth;
|
||||
bytesPerSample = info.BitDepth / 8;
|
||||
|
||||
bufferSize = outBufferSize;
|
||||
}
|
||||
|
||||
@@ -149,7 +125,7 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
|
||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(dataPtr, dataSize);
|
||||
|
||||
#define WRITE_DATA(chunkIndex, dataPtr, dataSize) \
|
||||
samplesPerChunk[chunkIndex] = (dataSize) / (options.BitDepth / 8); \
|
||||
samplesPerChunk[chunkIndex] = (dataSize) / (outputBitDepth / 8); \
|
||||
switch (options.Format) \
|
||||
{ \
|
||||
case AudioFormat::Raw: \
|
||||
@@ -183,8 +159,9 @@ CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder&
|
||||
else
|
||||
{
|
||||
// Split audio data into a several chunks (uniform data spread)
|
||||
const int32 MinChunkSize = 1 * 1024 * 1024; // 1 MB
|
||||
const int32 chunkSize = Math::Max<int32>(MinChunkSize, (int32)Math::AlignUp<uint32>(bufferSize / ASSET_FILE_DATA_CHUNKS, 256));
|
||||
const int32 minChunkSize = 1 * 1024 * 1024; // 1 MB
|
||||
const int32 dataAlignment = info.NumChannels * bytesPerSample; // Ensure to never split samples in-between (eg. 24-bit that uses 3 bytes)
|
||||
const int32 chunkSize = Math::Max<int32>(minChunkSize, (int32)Math::AlignUp<uint32>(bufferSize / ASSET_FILE_DATA_CHUNKS, dataAlignment));
|
||||
const int32 chunksCount = Math::CeilToInt((float)bufferSize / chunkSize);
|
||||
ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS);
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "Engine/Tools/AudioTool/AudioDecoder.h"
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#include "Engine/Audio/Config.h"
|
||||
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
|
||||
#include "Engine/Tools/AudioTool/AudioTool.h"
|
||||
#include "Engine/Tools/AudioTool/AudioDecoder.h"
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable caching audio import options
|
||||
/// </summary>
|
||||
@@ -20,23 +20,7 @@
|
||||
class ImportAudio
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Importing audio options
|
||||
/// </summary>
|
||||
struct Options : public ISerializable
|
||||
{
|
||||
AudioFormat Format = AudioFormat::Vorbis;
|
||||
bool DisableStreaming = false;
|
||||
bool Is3D = false;
|
||||
int32 BitDepth = 16;
|
||||
float Quality = 0.4f;
|
||||
|
||||
String ToString() const;
|
||||
|
||||
// [ISerializable]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
};
|
||||
typedef AudioTool::Options Options;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -136,13 +136,11 @@ public:
|
||||
public:
|
||||
bool IsEnd() const
|
||||
{
|
||||
ASSERT(_collection);
|
||||
return Index() == _collection->Count();
|
||||
}
|
||||
|
||||
bool IsNotEnd() const
|
||||
{
|
||||
ASSERT(_collection);
|
||||
return Index() != _collection->Count();
|
||||
}
|
||||
|
||||
@@ -171,8 +169,6 @@ public:
|
||||
public:
|
||||
Iterator& operator++()
|
||||
{
|
||||
ASSERT(_collection);
|
||||
|
||||
// Check if it is not at end
|
||||
const int32 end = _collection->Count();
|
||||
if (Index() != end)
|
||||
@@ -188,38 +184,18 @@ public:
|
||||
_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int)
|
||||
{
|
||||
ASSERT(_collection);
|
||||
Iterator temp = *this;
|
||||
|
||||
// Check if it is not at end
|
||||
const int32 end = _collection->Count();
|
||||
if (Index() != end)
|
||||
{
|
||||
// Move forward within chunk
|
||||
_index++;
|
||||
|
||||
// Check if need to change chunk
|
||||
if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1)
|
||||
{
|
||||
// Move to next chunk
|
||||
_chunkIndex++;
|
||||
_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
++temp;
|
||||
return temp;
|
||||
}
|
||||
|
||||
Iterator& operator--()
|
||||
{
|
||||
ASSERT(_collection);
|
||||
|
||||
// Check if it's not at beginning
|
||||
if (_index != 0 || _chunkIndex != 0)
|
||||
{
|
||||
@@ -236,32 +212,13 @@ public:
|
||||
_index--;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator--(int)
|
||||
{
|
||||
ASSERT(_collection);
|
||||
Iterator temp = *this;
|
||||
|
||||
// Check if it's not at beginning
|
||||
if (_index != 0 || _chunkIndex != 0)
|
||||
{
|
||||
// Check if need to change chunk
|
||||
if (_index == 0)
|
||||
{
|
||||
// Move to previous chunk
|
||||
_chunkIndex--;
|
||||
_index = ChunkSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move backward within chunk
|
||||
_index--;
|
||||
}
|
||||
}
|
||||
|
||||
--temp;
|
||||
return temp;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -309,7 +309,7 @@ public:
|
||||
|
||||
FORCE_INLINE bool operator==(const Iterator& v) const
|
||||
{
|
||||
return _index == v._index && &_collection == &v._collection;
|
||||
return _index == v._index && _collection == v._collection;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const Iterator& v) const
|
||||
|
||||
@@ -73,28 +73,22 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
|
||||
|
||||
PoolLocker.Lock();
|
||||
|
||||
int32 itemsLeft;
|
||||
do
|
||||
// Update timeouts and delete objects that timed out
|
||||
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
// Update timeouts and delete objects that timed out
|
||||
itemsLeft = Pool.Count();
|
||||
for (auto i = Pool.Begin(); i.IsNotEnd(); ++i)
|
||||
auto& bucket = *i;
|
||||
Object* obj = bucket.Key;
|
||||
const float ttl = bucket.Value - ((obj->Flags & ObjectFlags::UseGameTimeForDelete) != ObjectFlags::None ? gameDelta : dt);
|
||||
if (ttl <= 0.0f)
|
||||
{
|
||||
Object* obj = i->Key;
|
||||
const float ttl = i->Value - ((obj->Flags & ObjectFlags::UseGameTimeForDelete) != ObjectFlags::None ? gameDelta : dt);
|
||||
if (ttl <= 0.0f)
|
||||
{
|
||||
Pool.Remove(i);
|
||||
obj->OnDeleteObject();
|
||||
itemsLeft--;
|
||||
}
|
||||
else
|
||||
{
|
||||
i->Value = ttl;
|
||||
}
|
||||
Pool.Remove(i);
|
||||
obj->OnDeleteObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
bucket.Value = ttl;
|
||||
}
|
||||
}
|
||||
while (itemsLeft != Pool.Count()); // Continue removing if any new item was added during removing (eg. sub-object delete with 0 timeout)
|
||||
|
||||
PoolLocker.Unlock();
|
||||
}
|
||||
|
||||
@@ -171,6 +171,8 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3
|
||||
const Float3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Float3::Zero;
|
||||
const Float3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Float3::Zero;
|
||||
const Float2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Float2::Zero;
|
||||
const Color vColor = mesh.Colors.HasItems() ? mesh.Colors[vertexIndex] : Color::Black; // Assuming Color::Black as a default color
|
||||
|
||||
const int32 end = startIndex + searchRange;
|
||||
|
||||
for (size_t i = 0; i < sparialSortCache.size(); i++)
|
||||
@@ -184,6 +186,8 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3
|
||||
const Float3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Float3::Zero;
|
||||
const Float3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Float3::Zero;
|
||||
const Float2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Float2::Zero;
|
||||
const Color vColor = mesh.Colors.HasItems() ? mesh.Colors[vertexIndex] : Color::Black; // Assuming Color::Black as a default color
|
||||
|
||||
const int32 end = startIndex + searchRange;
|
||||
|
||||
for (int32 v = startIndex; v < end; v++)
|
||||
@@ -201,6 +205,8 @@ int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int3
|
||||
continue;
|
||||
if (mesh.LightmapUVs.HasItems() && (vLightmapUV - mesh.LightmapUVs[v]).LengthSquared() > uvEpsSqr)
|
||||
continue;
|
||||
if (mesh.Colors.HasItems() && vColor != mesh.Colors[v])
|
||||
continue;
|
||||
// TODO: check more components?
|
||||
|
||||
return v;
|
||||
|
||||
@@ -39,5 +39,19 @@ namespace FlaxEngine
|
||||
View = view;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rendering mask for layers. Used to exclude objects from rendering (via <see cref="View"/> property).
|
||||
/// </summary>
|
||||
public LayersMask ViewLayersMask
|
||||
{
|
||||
get => View.RenderLayersMask;
|
||||
set
|
||||
{
|
||||
var view = View;
|
||||
view.RenderLayersMask = value;
|
||||
View = view;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ void RenderView::SetProjector(float nearPlane, float farPlane, const Float3& pos
|
||||
CullingFrustum = Frustum;
|
||||
}
|
||||
|
||||
void RenderView::CopyFrom(Camera* camera, Viewport* viewport)
|
||||
void RenderView::CopyFrom(const Camera* camera, const Viewport* viewport)
|
||||
{
|
||||
const Vector3 cameraPos = camera->GetPosition();
|
||||
LargeWorlds::UpdateOrigin(Origin, cameraPos);
|
||||
@@ -192,6 +192,8 @@ void RenderView::CopyFrom(Camera* camera, Viewport* viewport)
|
||||
Frustum.GetInvMatrix(IVP);
|
||||
CullingFrustum = Frustum;
|
||||
RenderLayersMask = camera->RenderLayersMask;
|
||||
Flags = camera->RenderFlags;
|
||||
Mode = camera->RenderMode;
|
||||
}
|
||||
|
||||
void RenderView::GetWorldMatrix(const Transform& transform, Matrix& world) const
|
||||
|
||||
@@ -102,6 +102,8 @@ namespace FlaxEngine
|
||||
NonJitteredProjection = Projection;
|
||||
TemporalAAJitter = Float4.Zero;
|
||||
RenderLayersMask = camera.RenderLayersMask;
|
||||
Flags = camera.RenderFlags;
|
||||
Mode = camera.RenderMode;
|
||||
|
||||
UpdateCachedData();
|
||||
}
|
||||
|
||||
@@ -295,10 +295,12 @@ public:
|
||||
/// <param name="angle">Camera's FOV angle (in degrees)</param>
|
||||
void SetProjector(float nearPlane, float farPlane, const Float3& position, const Float3& direction, const Float3& up, float angle);
|
||||
|
||||
// Copy view data from camera
|
||||
// @param camera Camera to copy its data
|
||||
// @param camera The custom viewport to use for view/projection matrices override.
|
||||
void CopyFrom(Camera* camera, Viewport* viewport = nullptr);
|
||||
/// <summary>
|
||||
/// Copies view data from camera to the view.
|
||||
/// </summary>
|
||||
/// <param name="camera">The camera to copy its data.</param>
|
||||
/// <param name="viewport">The custom viewport to use for view/projection matrices override.</param>
|
||||
void CopyFrom(const Camera* camera, const Viewport* viewport = nullptr);
|
||||
|
||||
public:
|
||||
FORCE_INLINE DrawPass GetShadowsDrawPassMask(ShadowsCastingMode shadowsMode) const
|
||||
|
||||
@@ -649,22 +649,6 @@ void GPUContextVulkan::UpdateDescriptorSets(ComputePipelineStateVulkan* pipeline
|
||||
}
|
||||
}
|
||||
|
||||
void GPUContextVulkan::BindPipeline()
|
||||
{
|
||||
if (_psDirtyFlag && _currentState && (_rtDepth || _rtCount))
|
||||
{
|
||||
// Clear flag
|
||||
_psDirtyFlag = false;
|
||||
|
||||
// Change state
|
||||
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
|
||||
const auto pipeline = _currentState->GetState(_renderPass);
|
||||
vkCmdBindPipeline(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
|
||||
RENDER_STAT_PS_STATE_CHANGE();
|
||||
}
|
||||
}
|
||||
|
||||
void GPUContextVulkan::OnDrawCall()
|
||||
{
|
||||
GPUPipelineStateVulkan* pipelineState = _currentState;
|
||||
@@ -704,7 +688,15 @@ void GPUContextVulkan::OnDrawCall()
|
||||
BeginRenderPass();
|
||||
}
|
||||
|
||||
BindPipeline();
|
||||
// Bind pipeline
|
||||
if (_psDirtyFlag && _currentState && (_rtDepth || _rtCount))
|
||||
{
|
||||
_psDirtyFlag = false;
|
||||
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
|
||||
const auto pipeline = _currentState->GetState(_renderPass);
|
||||
vkCmdBindPipeline(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
RENDER_STAT_PS_STATE_CHANGE();
|
||||
}
|
||||
|
||||
//UpdateDynamicStates();
|
||||
|
||||
@@ -1115,9 +1107,7 @@ void GPUContextVulkan::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCo
|
||||
EndRenderPass();
|
||||
|
||||
auto pipelineState = shaderVulkan->GetOrCreateState();
|
||||
|
||||
UpdateDescriptorSets(pipelineState);
|
||||
|
||||
FlushBarriers();
|
||||
|
||||
// Bind pipeline
|
||||
@@ -1152,10 +1142,8 @@ void GPUContextVulkan::DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* b
|
||||
EndRenderPass();
|
||||
|
||||
auto pipelineState = shaderVulkan->GetOrCreateState();
|
||||
|
||||
UpdateDescriptorSets(pipelineState);
|
||||
AddBufferBarrier(bufferForArgsVulkan, VK_ACCESS_INDIRECT_COMMAND_READ_BIT);
|
||||
|
||||
FlushBarriers();
|
||||
|
||||
// Bind pipeline
|
||||
|
||||
@@ -161,7 +161,6 @@ private:
|
||||
void UpdateDescriptorSets(const struct SpirvShaderDescriptorInfo& descriptorInfo, class DescriptorSetWriterVulkan& dsWriter, bool& needsWrite);
|
||||
void UpdateDescriptorSets(GPUPipelineStateVulkan* pipelineState);
|
||||
void UpdateDescriptorSets(ComputePipelineStateVulkan* pipelineState);
|
||||
void BindPipeline();
|
||||
void OnDrawCall();
|
||||
|
||||
public:
|
||||
|
||||
@@ -886,6 +886,14 @@ void Actor::EndPlay()
|
||||
OnDisable();
|
||||
}
|
||||
|
||||
for (auto* script : Scripts)
|
||||
{
|
||||
CHECK_EXECUTE_IN_EDITOR
|
||||
{
|
||||
script->OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
OnEndPlay();
|
||||
|
||||
// Clear flag
|
||||
@@ -899,15 +907,6 @@ void Actor::EndPlay()
|
||||
e->EndPlay();
|
||||
}
|
||||
|
||||
// Fire event for scripting
|
||||
for (auto* script : Scripts)
|
||||
{
|
||||
CHECK_EXECUTE_IN_EDITOR
|
||||
{
|
||||
script->OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Inform attached scripts
|
||||
for (int32 i = 0; i < Scripts.Count(); i++)
|
||||
{
|
||||
|
||||
@@ -416,6 +416,8 @@ void Camera::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
SERIALIZE_MEMBER(Far, _far);
|
||||
SERIALIZE_MEMBER(OrthoScale, _orthoScale);
|
||||
SERIALIZE(RenderLayersMask);
|
||||
SERIALIZE(RenderFlags);
|
||||
SERIALIZE(RenderMode);
|
||||
}
|
||||
|
||||
void Camera::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
@@ -430,6 +432,8 @@ void Camera::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier
|
||||
DESERIALIZE_MEMBER(Far, _far);
|
||||
DESERIALIZE_MEMBER(OrthoScale, _orthoScale);
|
||||
DESERIALIZE(RenderLayersMask);
|
||||
DESERIALIZE(RenderFlags);
|
||||
DESERIALIZE(RenderMode);
|
||||
}
|
||||
|
||||
void Camera::OnEnable()
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Engine/Core/Math/Viewport.h"
|
||||
#include "Engine/Core/Math/Ray.h"
|
||||
#include "Engine/Core/Types/LayersMask.h"
|
||||
#include "Engine/Graphics/Enums.h"
|
||||
#include "Engine/Scripting/ScriptingObjectReference.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
@@ -134,6 +135,18 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Camera\")")
|
||||
LayersMask RenderLayersMask;
|
||||
|
||||
/// <summary>
|
||||
/// Frame rendering flags used to switch between graphics features for this camera.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes = "EditorOrder(110), EditorDisplay(\"Camera\")")
|
||||
ViewFlags RenderFlags = ViewFlags::DefaultGame;
|
||||
|
||||
/// <summary>
|
||||
/// Describes frame rendering modes for this camera.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes = "EditorOrder(120), EditorDisplay(\"Camera\")")
|
||||
ViewMode RenderMode = ViewMode::Default;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Projects the point from 3D world-space to game window coordinates (in screen pixels for default viewport calculated from <see cref="Viewport"/>).
|
||||
|
||||
@@ -30,18 +30,18 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(3), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
|
||||
float SourceLength = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the radial falloff of light when UseInverseSquaredFalloff is disabled.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(13), DefaultValue(8.0f), EditorDisplay(\"Light\"), Limit(2, 16, 0.01f), VisibleIf(nameof(UseInverseSquaredFalloff), true)")
|
||||
float FallOffExponent = 8.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use physically based inverse squared distance falloff, where Radius is only clamping the light's contribution.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(14), DefaultValue(false), EditorDisplay(\"Light\")")
|
||||
API_FIELD(Attributes = "EditorOrder(13), DefaultValue(false), EditorDisplay(\"Light\")")
|
||||
bool UseInverseSquaredFalloff = false;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the radial falloff of light when UseInverseSquaredFalloff is disabled.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(14), DefaultValue(8.0f), EditorDisplay(\"Light\"), Limit(2, 16, 0.01f), VisibleIf(nameof(UseInverseSquaredFalloff), true)")
|
||||
float FallOffExponent = 8.0f;
|
||||
|
||||
/// <summary>
|
||||
/// IES texture (light profiles from real world measured data)
|
||||
/// </summary>
|
||||
|
||||
@@ -29,18 +29,18 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
|
||||
float SourceRadius = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the radial falloff of light when UseInverseSquaredFalloff is disabled.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(13), DefaultValue(8.0f), EditorDisplay(\"Light\"), Limit(2, 16, 0.01f), VisibleIf(nameof(UseInverseSquaredFalloff), true)")
|
||||
float FallOffExponent = 8.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use physically based inverse squared distance falloff, where Radius is only clamping the light's contribution.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(14), DefaultValue(false), EditorDisplay(\"Light\")")
|
||||
API_FIELD(Attributes = "EditorOrder(13), DefaultValue(false), EditorDisplay(\"Light\")")
|
||||
bool UseInverseSquaredFalloff = false;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the radial falloff of light when UseInverseSquaredFalloff is disabled.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(14), DefaultValue(8.0f), EditorDisplay(\"Light\"), Limit(2, 16, 0.01f), VisibleIf(nameof(UseInverseSquaredFalloff), true)")
|
||||
float FallOffExponent = 8.0f;
|
||||
|
||||
/// <summary>
|
||||
/// IES texture (light profiles from real world measured data)
|
||||
/// </summary>
|
||||
|
||||
@@ -332,6 +332,8 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
|
||||
// Serialize to json data
|
||||
ASSERT(!IsCreatingPrefab);
|
||||
IsCreatingPrefab = true;
|
||||
const Guid targetPrefabId = targetActor->GetPrefabID();
|
||||
const bool hasTargetPrefabId = targetPrefabId.IsValid();
|
||||
rapidjson_flax::StringBuffer actorsDataBuffer;
|
||||
{
|
||||
CompactJsonWriter writerObj(actorsDataBuffer);
|
||||
@@ -340,7 +342,27 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
|
||||
for (int32 i = 0; i < sceneObjects->Count(); i++)
|
||||
{
|
||||
SceneObject* obj = sceneObjects->At(i);
|
||||
|
||||
// Detect when creating prefab from object that is already part of prefab then serialize it as unlinked
|
||||
const Guid prefabId = obj->GetPrefabID();
|
||||
const Guid prefabObjectId = obj->GetPrefabObjectID();
|
||||
bool isObjectFromPrefab = targetPrefabId == prefabId && prefabId.IsValid(); // Allow to use other nested prefabs properly (ignore only root object's prefab link)
|
||||
if (isObjectFromPrefab)
|
||||
{
|
||||
//obj->BreakPrefabLink();
|
||||
obj->_prefabID = Guid::Empty;
|
||||
obj->_prefabObjectID = Guid::Empty;
|
||||
}
|
||||
|
||||
writer.SceneObject(obj);
|
||||
|
||||
// Restore broken link
|
||||
if (hasTargetPrefabId)
|
||||
{
|
||||
//obj->LinkPrefab(prefabId, prefabObjectId);
|
||||
obj->_prefabID = prefabId;
|
||||
obj->_prefabObjectID = prefabObjectId;
|
||||
}
|
||||
}
|
||||
writer.EndArray();
|
||||
}
|
||||
@@ -406,7 +428,6 @@ bool PrefabManager::CreatePrefab(Actor* targetActor, const StringView& outputPat
|
||||
{
|
||||
SceneObject* obj = sceneObjects->At(i);
|
||||
Guid prefabObjectId;
|
||||
|
||||
if (objectInstanceIdToPrefabObjectId.TryGet(obj->GetSceneObjectId(), prefabObjectId))
|
||||
{
|
||||
obj->LinkPrefab(assetInfo.ID, prefabObjectId);
|
||||
|
||||
@@ -945,7 +945,7 @@ const char* ButtonCodeToKeyName(KeyboardKeys code)
|
||||
// Row #6
|
||||
case KeyboardKeys::Control: return "LCTL"; // Left Control
|
||||
case KeyboardKeys::LeftWindows: return "LWIN";
|
||||
case KeyboardKeys::LeftMenu: return "LALT";
|
||||
case KeyboardKeys::Alt: return "LALT";
|
||||
case KeyboardKeys::Spacebar: return "SPCE";
|
||||
case KeyboardKeys::RightMenu: return "RALT";
|
||||
case KeyboardKeys::RightWindows: return "RWIN";
|
||||
@@ -2700,6 +2700,8 @@ Float2 LinuxPlatform::GetDesktopSize()
|
||||
if (screenIdx >= count)
|
||||
return Float2::Zero;
|
||||
|
||||
// this function is used as a fallback to place a window at the center of
|
||||
// a screen so we report only one screen instead of the real desktop
|
||||
Float2 size((float)xsi[screenIdx].width, (float)xsi[screenIdx].height);
|
||||
X11::XFree(xsi);
|
||||
return size;
|
||||
@@ -2707,14 +2709,72 @@ Float2 LinuxPlatform::GetDesktopSize()
|
||||
|
||||
Rectangle LinuxPlatform::GetMonitorBounds(const Float2& screenPos)
|
||||
{
|
||||
// TODO: do it in a proper way
|
||||
return Rectangle(Float2::Zero, GetDesktopSize());
|
||||
if (!xDisplay)
|
||||
return Rectangle::Empty;
|
||||
|
||||
int event, err;
|
||||
const bool ok = X11::XineramaQueryExtension(xDisplay, &event, &err);
|
||||
if (!ok)
|
||||
return Rectangle::Empty;
|
||||
|
||||
int count;
|
||||
int screenIdx = 0;
|
||||
X11::XineramaScreenInfo* xsi = X11::XineramaQueryScreens(xDisplay, &count);
|
||||
if (screenIdx >= count)
|
||||
return Rectangle::Empty;
|
||||
// find the screen for this screenPos
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (screenPos.X >= xsi[i].x_org && screenPos.X < xsi[i].x_org+xsi[i].width
|
||||
&& screenPos.Y >= xsi[i].y_org && screenPos.Y < xsi[i].y_org+xsi[i].height)
|
||||
{
|
||||
screenIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Float2 org((float)xsi[screenIdx].x_org, (float)xsi[screenIdx].y_org);
|
||||
Float2 size((float)xsi[screenIdx].width, (float)xsi[screenIdx].height);
|
||||
X11::XFree(xsi);
|
||||
return Rectangle(org, size);
|
||||
}
|
||||
|
||||
Rectangle LinuxPlatform::GetVirtualDesktopBounds()
|
||||
{
|
||||
// TODO: do it in a proper way
|
||||
return Rectangle(Float2::Zero, GetDesktopSize());
|
||||
if (!xDisplay)
|
||||
return Rectangle::Empty;
|
||||
|
||||
int event, err;
|
||||
const bool ok = X11::XineramaQueryExtension(xDisplay, &event, &err);
|
||||
if (!ok)
|
||||
return Rectangle::Empty;
|
||||
|
||||
int count;
|
||||
X11::XineramaScreenInfo* xsi = X11::XineramaQueryScreens(xDisplay, &count);
|
||||
if (count <= 0)
|
||||
return Rectangle::Empty;
|
||||
// get all screen dimensions and assume the monitors form a rectangle
|
||||
// as you can arrange monitors to your liking this is not necessarily the case
|
||||
int minX = INT32_MAX, minY = INT32_MAX;
|
||||
int maxX = 0, maxY = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int maxScreenX = xsi[i].x_org + xsi[i].width;
|
||||
int maxScreenY = xsi[i].y_org + xsi[i].height;
|
||||
if (maxScreenX > maxX)
|
||||
maxX = maxScreenX;
|
||||
if (maxScreenY > maxY)
|
||||
maxY = maxScreenY;
|
||||
if (minX > xsi[i].x_org)
|
||||
minX = xsi[i].x_org;
|
||||
if (minY > xsi[i].y_org)
|
||||
minY = xsi[i].y_org;
|
||||
}
|
||||
|
||||
Float2 org(static_cast<float>(minX), static_cast<float>(minY));
|
||||
Float2 size(static_cast<float>(maxX - minX), static_cast<float>(maxY - minY));
|
||||
X11::XFree(xsi);
|
||||
return Rectangle(org, size);
|
||||
}
|
||||
|
||||
String LinuxPlatform::GetMainDirectory()
|
||||
|
||||
@@ -41,8 +41,10 @@ extern Dictionary<StringAnsi, X11::KeyCode> KeyNameMap;
|
||||
extern Array<KeyboardKeys> KeyCodeMap;
|
||||
extern X11::Cursor Cursors[(int32)CursorType::MAX];
|
||||
|
||||
static const uint32 MouseDoubleClickTime = 500;
|
||||
static constexpr uint32 MouseDoubleClickTime = 500;
|
||||
static constexpr uint32 MaxDoubleClickDistanceSquared = 10;
|
||||
static X11::Time MouseLastButtonPressTime = 0;
|
||||
static Float2 OldMouseClickPosition;
|
||||
|
||||
LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
|
||||
: WindowBase(settings)
|
||||
@@ -153,7 +155,11 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
|
||||
hints.max_height = (int)settings.MaximumSize.Y;
|
||||
hints.flags |= USSize;
|
||||
}
|
||||
X11::XSetNormalHints(display, window, &hints);
|
||||
// honor the WM placement except for manual (overriding) placements
|
||||
if (settings.StartPosition == WindowStartPosition::Manual)
|
||||
{
|
||||
X11::XSetNormalHints(display, window, &hints);
|
||||
}
|
||||
|
||||
// Ensures the child window is always on top of the parent window
|
||||
if (settings.Parent)
|
||||
@@ -595,15 +601,19 @@ void LinuxWindow::OnButtonPress(void* event)
|
||||
// Handle double-click
|
||||
if (buttonEvent->button == Button1)
|
||||
{
|
||||
if (buttonEvent->time < (MouseLastButtonPressTime + MouseDoubleClickTime))
|
||||
if (
|
||||
buttonEvent->time < (MouseLastButtonPressTime + MouseDoubleClickTime) &&
|
||||
Float2::DistanceSquared(mousePos, OldMouseClickPosition) < MaxDoubleClickDistanceSquared)
|
||||
{
|
||||
Input::Mouse->OnMouseDoubleClick(ClientToScreen(mousePos), mouseButton, this);
|
||||
MouseLastButtonPressTime = 0;
|
||||
OldMouseClickPosition = mousePos;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
MouseLastButtonPressTime = buttonEvent->time;
|
||||
OldMouseClickPosition = mousePos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ void JsonTools::ChangeIds(Document& doc, const Dictionary<Guid, Guid>& mapping)
|
||||
::ChangeIds(doc, doc, mapping);
|
||||
}
|
||||
|
||||
|
||||
Float2 JsonTools::GetFloat2(const Value& value)
|
||||
{
|
||||
Float2 result;
|
||||
|
||||
@@ -301,7 +301,7 @@ void ShadersCompilation::RegisterForShaderReloads(Asset* asset, const String& in
|
||||
{
|
||||
// Create a directory watcher to track the included file changes
|
||||
const String directory = StringUtils::GetDirectoryName(includedPath);
|
||||
if (!ShaderIncludesWatcher.ContainsKey(directory))
|
||||
if (FileSystem::DirectoryExists(directory) && !ShaderIncludesWatcher.ContainsKey(directory))
|
||||
{
|
||||
auto watcher = New<FileSystemWatcher>(directory, false);
|
||||
watcher->OnEvent.Bind<OnShaderIncludesWatcherEvent>();
|
||||
|
||||
@@ -1126,12 +1126,9 @@ bool TerrainPatch::InitializeHeightMap()
|
||||
float* TerrainPatch::GetHeightmapData()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.GetHeightmapData");
|
||||
|
||||
if (_cachedHeightMap.HasItems())
|
||||
return _cachedHeightMap.Get();
|
||||
|
||||
CacheHeightData();
|
||||
|
||||
return _cachedHeightMap.Get();
|
||||
}
|
||||
|
||||
@@ -1144,12 +1141,9 @@ void TerrainPatch::ClearHeightmapCache()
|
||||
byte* TerrainPatch::GetHolesMaskData()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Terrain.GetHolesMaskData");
|
||||
|
||||
if (_cachedHolesMask.HasItems())
|
||||
return _cachedHolesMask.Get();
|
||||
|
||||
CacheHeightData();
|
||||
|
||||
return _cachedHolesMask.Get();
|
||||
}
|
||||
|
||||
@@ -1161,15 +1155,11 @@ void TerrainPatch::ClearHolesMaskCache()
|
||||
|
||||
Color32* TerrainPatch::GetSplatMapData(int32 index)
|
||||
{
|
||||
ASSERT(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT);
|
||||
|
||||
CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, nullptr);
|
||||
PROFILE_CPU_NAMED("Terrain.GetSplatMapData");
|
||||
|
||||
if (_cachedSplatMap[index].HasItems())
|
||||
return _cachedSplatMap[index].Get();
|
||||
|
||||
CacheSplatData();
|
||||
|
||||
return _cachedSplatMap[index].Get();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,49 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if COMPILE_WITH_AUDIO_TOOL
|
||||
|
||||
#include "AudioTool.h"
|
||||
#include "Engine/Core/Core.h"
|
||||
#include "Engine/Core/Memory/Allocation.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Scripting/Enums.h"
|
||||
#endif
|
||||
|
||||
#define CONVERT_TO_MONO_AVG 1
|
||||
#if !CONVERT_TO_MONO_AVG
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#endif
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
String AudioTool::Options::ToString() const
|
||||
{
|
||||
return String::Format(TEXT("Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"), ScriptingEnum::ToString(Format), DisableStreaming, Is3D, Quality, (int32)BitDepth);
|
||||
}
|
||||
|
||||
void AudioTool::Options::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
SERIALIZE_GET_OTHER_OBJ(AudioTool::Options);
|
||||
|
||||
SERIALIZE(Format);
|
||||
SERIALIZE(DisableStreaming);
|
||||
SERIALIZE(Is3D);
|
||||
SERIALIZE(Quality);
|
||||
SERIALIZE(BitDepth);
|
||||
}
|
||||
|
||||
void AudioTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
DESERIALIZE(Format);
|
||||
DESERIALIZE(DisableStreaming);
|
||||
DESERIALIZE(Is3D);
|
||||
DESERIALIZE(Quality);
|
||||
DESERIALIZE(BitDepth);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void ConvertToMono8(const int8* input, uint8* output, uint32 numSamples, uint32 numChannels)
|
||||
{
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
@@ -231,8 +266,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int8 sample = *(int8*)input;
|
||||
output[i] = sample / 127.0f;
|
||||
|
||||
output[i] = sample * (1.0f / 127.0f);
|
||||
input++;
|
||||
}
|
||||
}
|
||||
@@ -241,8 +275,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int16 sample = *(int16*)input;
|
||||
output[i] = sample / 32767.0f;
|
||||
|
||||
output[i] = sample * (1.0f / 32767.0f);
|
||||
input += 2;
|
||||
}
|
||||
}
|
||||
@@ -251,8 +284,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int32 sample = Convert24To32Bits(input);
|
||||
output[i] = sample / 2147483647.0f;
|
||||
|
||||
output[i] = sample * (1.0f / 2147483647.0f);
|
||||
input += 3;
|
||||
}
|
||||
}
|
||||
@@ -261,8 +293,7 @@ void AudioTool::ConvertToFloat(const byte* input, uint32 inBitDepth, float* outp
|
||||
for (uint32 i = 0; i < numSamples; i++)
|
||||
{
|
||||
const int32 sample = *(int32*)input;
|
||||
output[i] = sample / 2147483647.0f;
|
||||
|
||||
output[i] = sample * (1.0f / 2147483647.0f);
|
||||
input += 4;
|
||||
}
|
||||
}
|
||||
@@ -278,7 +309,8 @@ void AudioTool::ConvertFromFloat(const float* input, int32* output, uint32 numSa
|
||||
{
|
||||
const float sample = *(float*)input;
|
||||
output[i] = static_cast<int32>(sample * 2147483647.0f);
|
||||
|
||||
input++;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,16 +2,86 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if COMPILE_WITH_AUDIO_TOOL
|
||||
|
||||
#include "Engine/Core/Config.h"
|
||||
#include "Engine/Core/Types/BaseTypes.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Core/ISerializable.h"
|
||||
#endif
|
||||
#include "Engine/Audio/Types.h"
|
||||
|
||||
/// <summary>
|
||||
/// Audio data importing and processing utilities.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API AudioTool
|
||||
API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API AudioTool
|
||||
{
|
||||
public:
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(AudioTool);
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Declares the imported audio clip bit depth.
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="HideInEditor") enum class BitDepth : int32
|
||||
{
|
||||
// 8-bits per sample.
|
||||
_8 = 8,
|
||||
// 16-bits per sample.
|
||||
_16 = 16,
|
||||
// 24-bits per sample.
|
||||
_24 = 24,
|
||||
// 32-bits per sample.
|
||||
_32 = 32,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Audio import options.
|
||||
/// </summary>
|
||||
API_STRUCT(Attributes="HideInEditor") struct FLAXENGINE_API Options : public ISerializable
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(Options);
|
||||
|
||||
/// <summary>
|
||||
/// The audio data format to import the audio clip as.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(10)")
|
||||
AudioFormat Format = AudioFormat::Vorbis;
|
||||
|
||||
/// <summary>
|
||||
/// The audio data compression quality. Used only if target format is using compression. Value 0 means the smallest size, value 1 means the best quality.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Compression Quality\"), Limit(0, 1, 0.01f)")
|
||||
float Quality = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Disables dynamic audio streaming. The whole clip will be loaded into the memory. Useful for small clips (eg. gunfire sounds).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(30)")
|
||||
bool DisableStreaming = false;
|
||||
|
||||
/// <summary>
|
||||
/// Checks should the clip be played as spatial (3D) audio or as normal audio. 3D audio is stored in Mono format.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(40), EditorDisplay(null, \"Is 3D\")")
|
||||
bool Is3D = false;
|
||||
|
||||
/// <summary>
|
||||
/// The size of a single sample in bits. The clip will be converted to this bit depth on import.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(50), VisibleIf(nameof(ShowBtiDepth))")
|
||||
BitDepth BitDepth = BitDepth::_16;
|
||||
|
||||
String ToString() const;
|
||||
|
||||
// [ISerializable]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
};
|
||||
#endif
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Converts a set of audio samples using multiple channels into a set of mono samples.
|
||||
/// </summary>
|
||||
@@ -59,3 +129,5 @@ public:
|
||||
return (input[2] << 24) | (input[1] << 16) | (input[0] << 8);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -314,6 +314,28 @@ namespace FlaxEngine.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDoubleClick(location, button))
|
||||
return true;
|
||||
|
||||
if (button == MouseButton.Left && _isPressed)
|
||||
{
|
||||
OnPressEnd();
|
||||
OnClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (button == MouseButton.Left && !_isPressed)
|
||||
{
|
||||
OnPressBegin();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnTouchDown(Float2 location, int pointerId)
|
||||
{
|
||||
|
||||
@@ -277,6 +277,27 @@ namespace FlaxEngine.GUI
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && !_isPressed)
|
||||
{
|
||||
OnPressBegin();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (button == MouseButton.Left && _isPressed)
|
||||
{
|
||||
OnPressEnd();
|
||||
if (_box.Contains(ref location))
|
||||
{
|
||||
OnClick();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return base.OnMouseDoubleClick(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
|
||||
@@ -666,6 +666,30 @@ namespace FlaxEngine.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDoubleClick(location, button))
|
||||
return true;
|
||||
|
||||
if (_touchDown && button == MouseButton.Left)
|
||||
{
|
||||
_touchDown = false;
|
||||
ShowPopup();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
_touchDown = true;
|
||||
if (!IsPopupOpened)
|
||||
Focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnTouchDown(Float2 location, int pointerId)
|
||||
{
|
||||
|
||||
@@ -769,13 +769,13 @@ namespace FlaxEngine.GUI
|
||||
{
|
||||
SetSelection(SelectionLeft);
|
||||
}
|
||||
else if (SelectionLeft > 0)
|
||||
else if (SelectionLeft >= 0)
|
||||
{
|
||||
int position;
|
||||
if (ctrl)
|
||||
position = FindPrevWordBegin();
|
||||
else
|
||||
position = _selectionEnd - 1;
|
||||
position = Mathf.Max(_selectionEnd - 1, 0);
|
||||
|
||||
if (shift)
|
||||
{
|
||||
|
||||
@@ -501,6 +501,29 @@ namespace FlaxEngine.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDoubleClick(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDoubleClick(location, button))
|
||||
return true;
|
||||
|
||||
_mouseOverHeader = HeaderRectangle.Contains(location);
|
||||
if (button == MouseButton.Left && _mouseOverHeader)
|
||||
{
|
||||
_mouseButtonLeftDown = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (button == MouseButton.Left && _mouseButtonLeftDown)
|
||||
{
|
||||
_mouseButtonLeftDown = false;
|
||||
if (_mouseOverHeader)
|
||||
Toggle();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseLeave()
|
||||
{
|
||||
|
||||
@@ -23,9 +23,9 @@ namespace FlaxEngine.GUI
|
||||
|
||||
// Scrolling
|
||||
|
||||
private float _clickChange = 20, _scrollChange = 100;
|
||||
private float _clickChange = 20, _scrollChange = 50;
|
||||
private float _minimum, _maximum = 100;
|
||||
private float _value, _targetValue;
|
||||
private float _startValue, _value, _targetValue;
|
||||
private readonly Orientation _orientation;
|
||||
private RootControl.UpdateDelegate _update;
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace FlaxEngine.GUI
|
||||
// Smoothing
|
||||
|
||||
private float _thumbOpacity = DefaultMinimumOpacity;
|
||||
private float _scrollAnimationProgress = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the orientation.
|
||||
@@ -59,14 +60,19 @@ namespace FlaxEngine.GUI
|
||||
public float TrackThickness { get; set; } = 2.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value smoothing scale (0 to not use it).
|
||||
/// The maximum time it takes to animate from current to target scroll position
|
||||
/// </summary>
|
||||
public float SmoothingScale { get; set; } = 0.6f;
|
||||
public float ScrollAnimationDuration { get; set; } = 0.18f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether use scroll value smoothing.
|
||||
/// </summary>
|
||||
public bool UseSmoothing => !Mathf.IsZero(SmoothingScale);
|
||||
public bool UseSmoothing => EnableSmoothing && !Mathf.IsZero(ScrollAnimationDuration);
|
||||
|
||||
/// <summary>
|
||||
/// Enables scroll smoothing
|
||||
/// </summary>
|
||||
public bool EnableSmoothing { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum value.
|
||||
@@ -112,11 +118,15 @@ namespace FlaxEngine.GUI
|
||||
if (!Mathf.NearEqual(value, _targetValue))
|
||||
{
|
||||
_targetValue = value;
|
||||
_startValue = _value;
|
||||
_scrollAnimationProgress = 0f;
|
||||
|
||||
// Check if skip smoothing
|
||||
if (!UseSmoothing)
|
||||
{
|
||||
_value = value;
|
||||
_startValue = value;
|
||||
_scrollAnimationProgress = 1f;
|
||||
OnValueChanged();
|
||||
}
|
||||
else
|
||||
@@ -208,7 +218,8 @@ namespace FlaxEngine.GUI
|
||||
{
|
||||
if (!Mathf.NearEqual(_value, _targetValue))
|
||||
{
|
||||
_value = _targetValue;
|
||||
_value = _targetValue = _startValue;
|
||||
_scrollAnimationProgress = 0f;
|
||||
SetUpdate(ref _update, null);
|
||||
OnValueChanged();
|
||||
}
|
||||
@@ -274,7 +285,8 @@ namespace FlaxEngine.GUI
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
_value = _targetValue = 0;
|
||||
_value = _targetValue = _startValue = 0;
|
||||
_scrollAnimationProgress = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -296,22 +308,39 @@ namespace FlaxEngine.GUI
|
||||
_thumbOpacity = isDeltaSlow ? targetOpacity : Mathf.Lerp(_thumbOpacity, targetOpacity, deltaTime * 10.0f);
|
||||
bool needUpdate = Mathf.Abs(_thumbOpacity - targetOpacity) > 0.001f;
|
||||
|
||||
// Ensure scroll bar is visible
|
||||
if (Visible)
|
||||
// Ensure scroll bar is visible and smoothing is required
|
||||
if (Visible && Mathf.Abs(_targetValue - _value) > 0.01f)
|
||||
{
|
||||
// Value smoothing
|
||||
if (Mathf.Abs(_targetValue - _value) > 0.01f)
|
||||
// Interpolate or not if running slow
|
||||
float value;
|
||||
if (!isDeltaSlow && UseSmoothing)
|
||||
{
|
||||
// Interpolate or not if running slow
|
||||
float value;
|
||||
if (!isDeltaSlow && UseSmoothing)
|
||||
value = Mathf.Lerp(_value, _targetValue, deltaTime * 20.0f * SmoothingScale);
|
||||
else
|
||||
value = _targetValue;
|
||||
_value = value;
|
||||
OnValueChanged();
|
||||
needUpdate = true;
|
||||
// percentage of scroll from 0 to _scrollChange, ex. 0.5 at _scrollChange / 2
|
||||
var minScrollChangeRatio = Mathf.Clamp(Mathf.Abs(_targetValue - _startValue) / _scrollChange, 0, 1);
|
||||
|
||||
// shorten the duration if we scrolled less than _scrollChange
|
||||
var actualDuration = ScrollAnimationDuration * minScrollChangeRatio;
|
||||
var step = deltaTime / actualDuration;
|
||||
|
||||
var progress = _scrollAnimationProgress;
|
||||
progress = Mathf.Clamp(progress + step, 0, 1);
|
||||
|
||||
// https://easings.net/#easeOutSine
|
||||
var easedProgress = Mathf.Sin((progress * Mathf.Pi) / 2);
|
||||
value = Mathf.Lerp(_startValue, _targetValue, easedProgress);
|
||||
|
||||
_scrollAnimationProgress = progress;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = _targetValue;
|
||||
_startValue = _targetValue;
|
||||
_scrollAnimationProgress = 0f;
|
||||
}
|
||||
|
||||
_value = value;
|
||||
OnValueChanged();
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
// End updating if all animations are done
|
||||
@@ -371,7 +400,7 @@ namespace FlaxEngine.GUI
|
||||
float mousePosition = _orientation == Orientation.Vertical ? slidePosition.Y : slidePosition.X;
|
||||
|
||||
float percentage = (mousePosition - _mouseOffset - _thumbSize / 2) / (TrackSize - _thumbSize);
|
||||
Value = _minimum + percentage * (_maximum - _minimum);
|
||||
TargetValue = _minimum + percentage * (_maximum - _minimum);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +410,7 @@ namespace FlaxEngine.GUI
|
||||
if (ThumbEnabled)
|
||||
{
|
||||
// Scroll
|
||||
Value = _value - delta * _scrollChange;
|
||||
Value = _targetValue - delta * _scrollChange;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user