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

# Conflicts:
#	Content/Editor/MaterialTemplates/Deformable.shader
#	Flax.flaxproj
#	Source/Engine/Content/Content.h
#	Source/Engine/Serialization/JsonTools.cpp
This commit is contained in:
Wojtek Figat
2026-04-01 17:14:21 +02:00
115 changed files with 2933 additions and 1074 deletions

View File

@@ -84,6 +84,11 @@ bool BinaryAsset::Init(AssetInitData& initData)
{
asset->_dependantAssets.Add(this);
}
else
{
// Dependency is not yet loaded to keep track this link to act when it's loaded
Content::onAssetDepend(this, e.First);
}
}
#endif
@@ -336,9 +341,7 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
// Force-resolve storage (asset at that path could be not yet loaded into registry)
storage = ContentStorageManager::GetStorage(filePath);
}
// Check if can perform write operation to the asset container
if (storage && !storage->AllowDataModifications())
if (storage && storage->IsReadOnly())
{
LOG(Warning, "Cannot write to the asset storage container.");
return true;
@@ -635,7 +638,7 @@ void BinaryAsset::onRename(const StringView& newPath)
ScopeLock lock(Locker);
// We don't support packages now
ASSERT(!Storage->IsPackage() && Storage->AllowDataModifications() && Storage->GetEntriesCount() == 1);
ASSERT(!Storage->IsPackage() && !Storage->IsReadOnly() && Storage->GetEntriesCount() == 1);
// Rename storage
Storage->OnRename(newPath);

View File

@@ -22,6 +22,7 @@
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API BinaryAsset : public Asset
{
DECLARE_ASSET_HEADER(BinaryAsset);
friend Content;
protected:
AssetHeader _header;
FlaxStorageReference _storageRef; // Allow asset to have missing storage reference but only before asset is loaded or if it's virtual

View File

@@ -415,9 +415,7 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage)
// Check if need to resolve any collisions
if (duplicatedEntries.HasItems())
{
// Check if cannot resolve collision for that container (it must allow to write to)
// TODO: we could support packages as well but don't have to do it now, maybe in future
if (storage->AllowDataModifications() == false)
if (storage->IsReadOnly())
{
LOG(Error, "Cannot register \'{0}\'. Founded duplicated asset at \'{1}\' but storage container doesn't allow data modifications.", storagePath, info.Path);
return;

View File

@@ -98,6 +98,9 @@ namespace
DateTime LastWorkspaceDiscovery;
CriticalSection WorkspaceDiscoveryLocker;
#endif
#if USE_EDITOR
Dictionary<Guid, HashSet<BinaryAsset*>> PendingDependencies;
#endif
}
#if ENABLE_ASSETS_DISCOVERY
@@ -184,6 +187,9 @@ void ContentService::Update()
{
auto asset = LoadedAssetsToInvoke.Dequeue();
asset->onLoaded_MainThread();
#if USE_EDITOR
Content::onAddDependencies(asset);
#endif
}
LoadedAssetsToInvokeLocker.Unlock();
}
@@ -1091,10 +1097,17 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath);
// Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath, false))
auto storage = ContentStorageManager::GetStorage(dstPath, false);
if (storage && storage->IsLoaded())
{
storage->Reload();
}
else if (auto dependencies = PendingDependencies.TryGet(dstId))
{
// Destination storage is not loaded but there are other assets that depend on it so update them
for (const auto& e : *dependencies)
e.Item->OnDependencyModified(nullptr);
}
}
}
else
@@ -1311,6 +1324,9 @@ void Content::tryCallOnLoaded(Asset* asset)
{
LoadedAssetsToInvoke.RemoveAtKeepOrder(index);
asset->onLoaded_MainThread();
#if USE_EDITOR
onAddDependencies(asset);
#endif
}
}
@@ -1328,6 +1344,10 @@ void Content::onAssetUnload(Asset* asset)
Assets.Remove(asset->GetID());
UnloadQueue.Remove(asset);
LoadedAssetsToInvoke.Remove(asset);
#if USE_EDITOR
for (auto& e : PendingDependencies)
e.Value.Remove(asset);
#endif
}
void Content::onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId)
@@ -1335,8 +1355,42 @@ void Content::onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId
ScopeLock locker(AssetsLocker);
Assets.Remove(oldId);
Assets.Add(newId, asset);
#if USE_EDITOR
if (PendingDependencies.ContainsKey(oldId))
{
auto deps = MoveTemp(PendingDependencies[oldId]);
PendingDependencies.Remove(oldId);
PendingDependencies.Add(newId, MoveTemp(deps));
}
#endif
}
#if USE_EDITOR
void Content::onAssetDepend(BinaryAsset* asset, const Guid& otherId)
{
ScopeLock locker(AssetsLocker);
PendingDependencies[otherId].Add(asset);
}
void Content::onAddDependencies(Asset* asset)
{
auto it = PendingDependencies.Find(asset->GetID());
if (it.IsNotEnd())
{
auto& dependencies = it->Value;
auto binaryAsset = Asset::Cast<BinaryAsset>(asset);
if (binaryAsset)
{
for (const auto& e : dependencies)
binaryAsset->_dependantAssets.Add(e.Item);
}
PendingDependencies.Remove(it);
}
}
#endif
bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const ScriptingTypeHandle& assetType)
{
// Skip if no restrictions for the type

View File

@@ -12,6 +12,7 @@
class Engine;
class FlaxFile;
class BinaryAsset;
class IAssetFactory;
class AssetsCache;
@@ -395,6 +396,12 @@ private:
static void onAssetLoaded(Asset* asset);
static void onAssetUnload(Asset* asset);
static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId);
#if USE_EDITOR
friend BinaryAsset;
friend class ContentService;
static void onAssetDepend(BinaryAsset* asset, const Guid& otherId);
static void onAddDependencies(Asset* asset);
#endif
static void deleteFileSafety(const StringView& path, const Guid* id = nullptr);
// Internal bindings

View File

@@ -28,7 +28,7 @@ bool BinaryAssetFactoryBase::Init(BinaryAsset* asset)
#if USE_EDITOR
// Check if need to perform data conversion to the newer version (only in Editor)
const auto upgrader = GetUpgrader();
if (storage->AllowDataModifications() && upgrader && upgrader->ShouldUpgrade(initData.SerializedVersion))
if (!storage->IsReadOnly() && upgrader && upgrader->ShouldUpgrade(initData.SerializedVersion))
{
const auto startTime = DateTime::NowUTC();
const AssetInfo info(asset->GetID(), asset->GetTypeName(), storage->GetPath());

View File

@@ -80,7 +80,6 @@ private:
auto asset = Asset.Get();
if (asset)
{
asset->Locker.Lock();
Task* task = (Task*)Platform::AtomicRead(&asset->_loadingTask);
if (task)
{
@@ -99,7 +98,6 @@ private:
task = task->GetContinueWithTask();
} while (task);
}
asset->Locker.Unlock();
}
}
};

View File

@@ -23,7 +23,7 @@ public:
// [FlaxStorage]
String ToString() const override;
bool IsPackage() const override;
bool AllowDataModifications() const override;
bool IsReadOnly() const override;
bool HasAsset(const Guid& id) const override;
bool HasAsset(const AssetInfo& info) const override;
int32 GetEntriesCount() const override;

View File

@@ -24,7 +24,7 @@ public:
// [FlaxStorage]
String ToString() const override;
bool IsPackage() const override;
bool AllowDataModifications() const override;
bool IsReadOnly() const override;
bool HasAsset(const Guid& id) const override;
bool HasAsset(const AssetInfo& info) const override;
int32 GetEntriesCount() const override;

View File

@@ -811,7 +811,7 @@ bool FlaxStorage::ChangeAssetID(Entry& e, const Guid& newId)
// TODO: validate entry
ASSERT(newId.IsValid());
ASSERT(AllowDataModifications());
ASSERT(!IsReadOnly());
LOG(Info, "Changing asset \'{0}\' id to \'{1}\' (storage: \'{2}\')", e.ID, newId, _path);
@@ -885,7 +885,7 @@ bool FlaxStorage::ChangeAssetID(Entry& e, const Guid& newId)
FlaxChunk* FlaxStorage::AllocateChunk()
{
if (AllowDataModifications())
if (!IsReadOnly())
{
PROFILE_MEM(ContentFiles);
auto chunk = New<FlaxChunk>();
@@ -1135,7 +1135,7 @@ bool FlaxStorage::Create(WriteStream* stream, Span<AssetInitData> assets, const
bool FlaxStorage::Save(const AssetInitData& data, bool silentMode)
{
// Check if can modify the storage
if (!AllowDataModifications())
if (IsReadOnly())
return true;
// Note: we support saving only single asset, to save more assets in single package use FlaxStorage::Create(..)
@@ -1546,7 +1546,7 @@ void FlaxStorage::Tick(double time)
void FlaxStorage::OnRename(const StringView& newPath)
{
ASSERT(AllowDataModifications());
ASSERT(!IsReadOnly());
_path = newPath;
}
@@ -1568,9 +1568,9 @@ bool FlaxFile::IsPackage() const
return false;
}
bool FlaxFile::AllowDataModifications() const
bool FlaxFile::IsReadOnly() const
{
return true;
return false;
}
bool FlaxFile::HasAsset(const Guid& id) const
@@ -1648,9 +1648,9 @@ bool FlaxPackage::IsPackage() const
return true;
}
bool FlaxPackage::AllowDataModifications() const
bool FlaxPackage::IsReadOnly() const
{
return false;
return true;
}
bool FlaxPackage::HasAsset(const Guid& id) const

View File

@@ -276,9 +276,9 @@ public:
virtual bool IsPackage() const = 0;
/// <summary>
/// Checks whenever storage container allows the data modifications.
/// Checks whenever storage container doesn't allow modifications.
/// </summary>
virtual bool AllowDataModifications() const = 0;
virtual bool IsReadOnly() const = 0;
/// <summary>
/// Determines whether the specified asset exists in this container.

View File

@@ -253,6 +253,16 @@ namespace FlaxEngine
return new Rectangle(Location + new Float2(x, y), Size);
}
/// <summary>
/// Make offseted rectangle
/// </summary>
/// <param name="offset">Offset (will be applied to X- and Y- axis).</param>
/// <returns>Offseted rectangle.</returns>
public Rectangle MakeOffsetted(float offset)
{
return new Rectangle(Location + offset, Size);
}
/// <summary>
/// Make offseted rectangle
/// </summary>

View File

@@ -253,7 +253,7 @@ public:
/// </summary>
FORCE_INLINE float GetTotalSeconds() const
{
return static_cast<float>(Ticks) / TicksPerSecond;
return (float)((double)Ticks / TicksPerSecond);
}
public:

View File

@@ -124,7 +124,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName, bool static
VariantType::VariantType(Types type, const ScriptingType& sType)
: VariantType(type)
{
SetTypeName(sType);
SetTypeName(sType.Fullname, sType.Module->CanReload);
}
VariantType::VariantType(Types type, const MClass* klass)
@@ -171,7 +171,7 @@ VariantType::VariantType(const StringAnsiView& typeName)
// Check case for array
if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive))
{
new(this) VariantType(Array, StringAnsiView(typeName.Get(), typeName.Length() - 2));
new(this) VariantType(Array, typeName);
return;
}

View File

@@ -68,10 +68,12 @@ API_STRUCT(InBuild) struct FLAXENGINE_API VariantType
MAX,
#if USE_LARGE_WORLDS
Real = Double,
Vector2 = Double2,
Vector3 = Double3,
Vector4 = Double4,
#else
Real = Float,
Vector2 = Float2,
Vector3 = Float3,
Vector4 = Float4,

View File

@@ -1197,11 +1197,17 @@ namespace FlaxEngine.Interop
}
[UnmanagedCallersOnly]
internal static byte GetMethodParameterIsOut(ManagedHandle methodHandle, int parameterNum)
internal static UInt64 GetMethodParameterIsOut(ManagedHandle methodHandle)
{
MethodHolder methodHolder = Unsafe.As<MethodHolder>(methodHandle.Target);
ParameterInfo parameterInfo = methodHolder.method.GetParameters()[parameterNum];
return (byte)(parameterInfo.IsOut ? 1 : 0);
var parameters = methodHolder.method.GetParameters();
UInt64 result = 0;
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].IsOut)
result |= 1ul << i;
}
return result;
}
[UnmanagedCallersOnly]

View File

@@ -198,7 +198,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCal
sphere.Center -= context.ViewOrigin;
if (Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
context.RenderContext.View.CullingFrustum.Intersects(sphere) &&
RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, (float)sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
{
const auto modelFrame = instance.DrawState.PrevFrame + 1;

View File

@@ -428,6 +428,20 @@ namespace FlaxEngine
partial struct VertexElement : IEquatable<VertexElement>
{
/// <summary>
/// Creates the vertex element description.
/// </summary>
/// <param name="type">Element type.</param>
/// <param name="format">Data format.</param>
public VertexElement(Types type, PixelFormat format)
{
Type = type;
Slot = 0;
Offset = 0;
PerInstance = 0;
Format = format;
}
/// <summary>
/// Creates the vertex element description.
/// </summary>

View File

@@ -52,13 +52,10 @@ IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderC
}
IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderContext& renderContext, const ::DrawCall& drawCall, bool instanced)
: GPUContext(context)
, RenderContext(renderContext)
, DrawCall(&drawCall)
, Time(Time::Draw.UnscaledTime.GetTotalSeconds())
, ScaledTime(Time::Draw.Time.GetTotalSeconds())
, Instanced(instanced)
: BindParameters(context, renderContext)
{
DrawCall = &drawCall;
Instanced = instanced;
}
GPUConstantBuffer* IMaterial::BindParameters::PerViewConstants = nullptr;

View File

@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace FlaxEngine
@@ -17,13 +18,14 @@ namespace FlaxEngine
{
private Span<byte> _data;
private PixelFormat _format;
private int _stride;
private int _stride, _count;
private readonly PixelFormatSampler _sampler;
internal Stream(Span<byte> data, PixelFormat format, int stride)
internal Stream(Span<byte> data, PixelFormat format, int stride, int count)
{
_data = data;
_stride = stride;
_count = count;
if (PixelFormatSampler.Get(format, out _sampler))
{
_format = format;
@@ -52,7 +54,7 @@ namespace FlaxEngine
/// <summary>
/// Gets the count of the items in the stride.
/// </summary>
public int Count => _data.Length / _stride;
public int Count => _count;
/// <summary>
/// Returns true if stream is valid.
@@ -76,6 +78,10 @@ namespace FlaxEngine
/// <returns>Loaded value.</returns>
public int GetInt(int index)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
fixed (byte* data = _data)
return (int)_sampler.Read(data + index * _stride).X;
}
@@ -87,6 +93,10 @@ namespace FlaxEngine
/// <returns>Loaded value.</returns>
public float GetFloat(int index)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
fixed (byte* data = _data)
return _sampler.Read(data + index * _stride).X;
}
@@ -98,6 +108,10 @@ namespace FlaxEngine
/// <returns>Loaded value.</returns>
public Float2 GetFloat2(int index)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
fixed (byte* data = _data)
return new Float2(_sampler.Read(data + index * _stride));
}
@@ -109,6 +123,10 @@ namespace FlaxEngine
/// <returns>Loaded value.</returns>
public Float3 GetFloat3(int index)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
fixed (byte* data = _data)
return new Float3(_sampler.Read(data + index * _stride));
}
@@ -120,6 +138,10 @@ namespace FlaxEngine
/// <returns>Loaded value.</returns>
public Float4 GetFloat4(int index)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
fixed (byte* data = _data)
return _sampler.Read(data + index * _stride);
}
@@ -131,9 +153,13 @@ namespace FlaxEngine
/// <param name="value">Value to assign.</param>
public void SetInt(int index, int value)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
var v = new Float4(value);
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref v);
_sampler.Write(data + index * _stride, &v);
}
/// <summary>
@@ -143,9 +169,13 @@ namespace FlaxEngine
/// <param name="value">Value to assign.</param>
public void SetFloat(int index, float value)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
var v = new Float4(value);
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref v);
_sampler.Write(data + index * _stride, &v);
}
/// <summary>
@@ -155,9 +185,13 @@ namespace FlaxEngine
/// <param name="value">Value to assign.</param>
public void SetFloat2(int index, Float2 value)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
var v = new Float4(value, 0.0f, 0.0f);
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref v);
_sampler.Write(data + index * _stride, &v);
}
/// <summary>
@@ -167,9 +201,13 @@ namespace FlaxEngine
/// <param name="value">Value to assign.</param>
public void SetFloat3(int index, Float3 value)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
var v = new Float4(value, 0.0f);
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref v);
_sampler.Write(data + index * _stride, &v);
}
/// <summary>
@@ -179,8 +217,12 @@ namespace FlaxEngine
/// <param name="value">Value to assign.</param>
public void SetFloat4(int index, Float4 value)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref value);
_sampler.Write(data + index * _stride, &value);
}
/// <summary>
@@ -190,6 +232,10 @@ namespace FlaxEngine
/// <param name="data">Pointer to the source data.</param>
public void SetLinear(IntPtr data)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
new Span<byte>(data.ToPointer(), _data.Length).CopyTo(_data);
}
@@ -199,6 +245,10 @@ namespace FlaxEngine
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<Float2> src)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
if (IsLinear(PixelFormat.R32G32_Float))
{
src.CopyTo(MemoryMarshal.Cast<byte, Float2>(_data));
@@ -211,7 +261,7 @@ namespace FlaxEngine
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i], 0.0f, 0.0f);
_sampler.Write(data + i * _stride, ref v);
_sampler.Write(data + i * _stride, &v);
}
}
}
@@ -223,6 +273,10 @@ namespace FlaxEngine
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<Float3> src)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
if (IsLinear(PixelFormat.R32G32B32_Float))
{
src.CopyTo(MemoryMarshal.Cast<byte, Float3>(_data));
@@ -235,7 +289,7 @@ namespace FlaxEngine
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i], 0.0f);
_sampler.Write(data + i * _stride, ref v);
_sampler.Write(data + i * _stride, &v);
}
}
}
@@ -247,6 +301,10 @@ namespace FlaxEngine
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<Color> src)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
if (IsLinear(PixelFormat.R32G32B32A32_Float))
{
src.CopyTo(MemoryMarshal.Cast<byte, Color>(_data));
@@ -259,7 +317,7 @@ namespace FlaxEngine
for (int i = 0; i < count; i++)
{
var v = (Float4)src[i];
_sampler.Write(data + i * _stride, ref v);
_sampler.Write(data + i * _stride, &v);
}
}
}
@@ -271,6 +329,10 @@ namespace FlaxEngine
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<uint> src)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
if (IsLinear(PixelFormat.R32_UInt))
{
src.CopyTo(MemoryMarshal.Cast<byte, uint>(_data));
@@ -292,7 +354,7 @@ namespace FlaxEngine
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i]);
_sampler.Write(data + i * _stride, ref v);
_sampler.Write(data + i * _stride, &v);
}
}
}
@@ -304,6 +366,10 @@ namespace FlaxEngine
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<Float2> dst)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
if (IsLinear(PixelFormat.R32G32_Float))
{
_data.CopyTo(MemoryMarshal.Cast<Float2, byte>(dst));
@@ -325,6 +391,10 @@ namespace FlaxEngine
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<Float3> dst)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
if (IsLinear(PixelFormat.R32G32B32_Float))
{
_data.CopyTo(MemoryMarshal.Cast<Float3, byte>(dst));
@@ -346,6 +416,10 @@ namespace FlaxEngine
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<Color> dst)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
if (IsLinear(PixelFormat.R32G32B32A32_Float))
{
_data.CopyTo(MemoryMarshal.Cast<Color, byte>(dst));
@@ -367,6 +441,10 @@ namespace FlaxEngine
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<uint> dst)
{
#if !BUILD_RELEASE
if (_data == null)
throw new NullReferenceException("Missing stream data. Ensure to allocate mesh buffer or check for its existence.");
#endif
if (IsLinear(PixelFormat.R32_UInt))
{
_data.CopyTo(MemoryMarshal.Cast<uint, byte>(dst));
@@ -390,6 +468,17 @@ namespace FlaxEngine
}
}
}
/// <summary>
/// Checks if stream is valid.
/// </summary>
/// <param name="stream">The stream to check.</param>
/// <returns>True if stream is valid, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator bool(Stream stream)
{
return stream.IsValid;
}
}
private byte[][] _data = new byte[(int)MeshBufferType.MAX][];
@@ -510,19 +599,19 @@ namespace FlaxEngine
bool use16BitIndexBuffer = false;
IntPtr[] vbData = new IntPtr[3];
GPUVertexLayout[] vbLayout = new GPUVertexLayout[3];
if (_data[VB0] != null)
if (_data[VB0] != null && _data[VB0].Length != 0)
{
vbData[0] = dataPtr[VB0];
vbLayout[0] = _layouts[VB0];
vertices = (uint)_data[VB0].Length / _layouts[VB0].Stride;
}
if (_data[VB1] != null)
if (_data[VB1] != null && _data[VB1].Length != 0)
{
vbData[1] = dataPtr[VB1];
vbLayout[1] = _layouts[VB1];
vertices = (uint)_data[VB1].Length / _layouts[VB1].Stride;
}
if (_data[VB2] != null)
if (_data[VB2] != null && _data[VB2].Length != 0)
{
vbData[2] = dataPtr[VB2];
vbLayout[2] = _layouts[VB2];
@@ -576,15 +665,16 @@ namespace FlaxEngine
{
Span<byte> data = new Span<byte>();
PixelFormat format = PixelFormat.Unknown;
int stride = 0;
int stride = 0, count = 0;
var ib = _data[(int)MeshBufferType.Index];
if (ib != null)
{
data = ib;
format = _formats[(int)MeshBufferType.Index];
stride = PixelFormatExtensions.SizeInBytes(format);
count = data.Length / stride;
}
return new Stream(data, format, stride);
return new Stream(data, format, stride, count);
}
/// <summary>
@@ -596,7 +686,7 @@ namespace FlaxEngine
{
Span<byte> data = new Span<byte>();
PixelFormat format = PixelFormat.Unknown;
int stride = 0;
int stride = 0, count = 0;
for (int vbIndex = 0; vbIndex < 3 && format == PixelFormat.Unknown; vbIndex++)
{
int idx = vbIndex + 1;
@@ -611,11 +701,12 @@ namespace FlaxEngine
data = new Span<byte>(vb).Slice(e.Offset);
format = e.Format;
stride = (int)layout.Stride;
count = vb.Length / stride;
break;
}
}
}
return new Stream(data, format, stride);
return new Stream(data, format, stride, count);
}
/// <summary>
@@ -722,6 +813,16 @@ namespace FlaxEngine
set => SetStreamFloat3(VertexElement.Types.Normal, value, PackNormal);
}
/// <summary>
/// Gets or sets the vertex tangent vectors (unpacked, normalized). Null if <see cref="VertexElement.Types.Tangent"/> does not exist in vertex buffers of the mesh.
/// </summary>
/// <remarks>Uses <see cref="Tangent"/> stream to read or write data to the vertex buffer.</remarks>
public Float3[] Tangents
{
get => GetStreamFloat3(VertexElement.Types.Tangent, UnpackNormal);
set => SetStreamFloat3(VertexElement.Types.Tangent, value, PackNormal);
}
/// <summary>
/// Gets or sets the vertex UVs (texcoord channel 0). Null if <see cref="VertexElement.Types.TexCoord"/> does not exist in vertex buffers of the mesh.
/// </summary>
@@ -732,6 +833,70 @@ namespace FlaxEngine
set => SetStreamFloat2(VertexElement.Types.TexCoord, value);
}
/// <summary>
/// Recalculates normal vectors for all vertices.
/// </summary>
public void ComputeNormals()
{
var positions = Position();
var indices = Index();
if (!positions)
throw new Exception("Cannot compute tangents without positions.");
if (!indices)
throw new Exception("Cannot compute tangents without indices.");
if (!Normal())
throw new Exception("Cannot compute tangents without Normal vertex element.");
var vertexCount = positions.Count;
if (vertexCount == 0)
return;
var indexCount = indices.Count;
// Compute per-face normals but store them per-vertex
var normals = new Float3[vertexCount];
for (int i = 0; i < indexCount; i += 3)
{
var i1 = indices.GetInt(i + 0);
var i2 = indices.GetInt(i + 1);
var i3 = indices.GetInt(i + 2);
Float3 v1 = positions.GetFloat3(i1);
Float3 v2 = positions.GetFloat3(i2);
Float3 v3 = positions.GetFloat3(i3);
Float3 n = Float3.Cross((v2 - v1), (v3 - v1)).Normalized;
normals[i1] += n;
normals[i2] += n;
normals[i3] += n;
}
// Average normals
for (int i = 0; i < normals.Length; i++)
normals[i] = normals[i].Normalized;
// Write back to the buffer
Normals = normals;
}
/// <summary>
/// Recalculates tangent vectors for all vertices based on normals.
/// </summary>
public void ComputeTangents()
{
var normals = Normal();
var tangents = Tangent();
if (!normals)
throw new Exception("Cannot compute tangents without normals.");
if (!tangents)
throw new Exception("Cannot compute tangents without Tangent vertex element.");
var count = normals.Count;
for (int i = 0; i < count; i++)
{
Float3 normal = normals.GetFloat3(i);
UnpackNormal(ref normal);
RenderTools.CalculateTangentFrame(out var n, out var t, ref normal);
tangents.SetFloat4(i, t);
}
}
private uint[] GetStreamUInt(Stream stream)
{
uint[] result = null;

View File

@@ -25,10 +25,10 @@ public:
private:
Span<byte> _data;
PixelFormat _format;
int32 _stride;
int32 _stride, _count;
PixelFormatSampler _sampler;
Stream(Span<byte> data, PixelFormat format, int32 stride);
Stream(Span<byte> data, PixelFormat format, int32 stride, int32 count);
public:
Span<byte> GetData() const;
@@ -38,6 +38,11 @@ public:
bool IsValid() const;
bool IsLinear(PixelFormat expectedFormat) const;
FORCE_INLINE operator bool() const
{
return IsValid();
}
FORCE_INLINE int32 GetInt(int32 index) const
{
ASSERT_LOW_LAYER(index * _stride < _data.Length());

View File

@@ -38,10 +38,11 @@ namespace
#endif
}
MeshAccessor::Stream::Stream(Span<byte> data, PixelFormat format, int32 stride)
MeshAccessor::Stream::Stream(Span<byte> data, PixelFormat format, int32 stride, int32 count)
: _data(data)
, _format(PixelFormat::Unknown)
, _stride(stride)
, _count(count)
{
auto sampler = PixelFormatSampler::Get(format);
if (sampler)
@@ -72,7 +73,7 @@ int32 MeshAccessor::Stream::GetStride() const
int32 MeshAccessor::Stream::GetCount() const
{
return _data.Length() / _stride;
return _count;
}
bool MeshAccessor::Stream::IsValid() const
@@ -368,22 +369,23 @@ MeshAccessor::Stream MeshAccessor::Index()
{
Span<byte> data;
PixelFormat format = PixelFormat::Unknown;
int32 stride = 0;
int32 stride = 0, count = 0;
auto& ib = _data[(int32)MeshBufferType::Index];
if (ib.IsValid())
{
data = ib;
format = _formats[(int32)MeshBufferType::Index];
stride = PixelFormatExtensions::SizeInBytes(format);
count = data.Length() / stride;
}
return Stream(data, format, stride);
return Stream(data, format, stride, count);
}
MeshAccessor::Stream MeshAccessor::Attribute(VertexElement::Types attribute)
{
Span<byte> data;
PixelFormat format = PixelFormat::Unknown;
int32 stride = 0;
int32 stride = 0, count = 0;
for (int32 vbIndex = 0; vbIndex < 3 && format == PixelFormat::Unknown; vbIndex++)
{
static_assert((int32)MeshBufferType::Vertex0 == 1, "Update code.");
@@ -399,11 +401,12 @@ MeshAccessor::Stream MeshAccessor::Attribute(VertexElement::Types attribute)
data = vb.Slice(e.Offset);
format = e.Format;
stride = layout->GetStride();
count = vb.Length() / stride;
break;
}
}
}
return Stream(data, format, stride);
return Stream(data, format, stride, count);
}
MeshBase::~MeshBase()

View File

@@ -22,7 +22,7 @@ namespace FlaxEngine
/// <summary>
/// Write data function.
/// </summary>
public delegate* unmanaged<void*, ref Float4, void> Write;
public delegate* unmanaged<void*, Float4*, void> Write;
/// <summary>
/// Tries to get a sampler tool for the specified format to read pixels.
@@ -38,7 +38,7 @@ namespace FlaxEngine
Format = format,
PixelSize = pixelSize,
Read = (delegate* unmanaged<void*, Float4>)read.ToPointer(),
Write = (delegate* unmanaged<void*, ref Float4, void>)write.ToPointer(),
Write = (delegate* unmanaged<void*, Float4*, void>)write.ToPointer(),
};
return pixelSize != 0;
}

View File

@@ -575,6 +575,13 @@ void RenderTools::ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascad
}
}
bool RenderTools::ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame, bool updateForce)
{
int32 updateFrequency, updatePhrase;
ComputeCascadeUpdateFrequency(cascadeIndex, cascadeCount, updateFrequency, updatePhrase, updateMaxCountPerFrame);
return (frameIndex % updateFrequency == updatePhrase) || updateForce;
}
float RenderTools::ComputeTemporalTime()
{
const float time = Time::Draw.UnscaledTime.GetTotalSeconds();

View File

@@ -133,12 +133,7 @@ public:
static void ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascadeCount, int32& updateFrequency, int32& updatePhrase, int32 updateMaxCountPerFrame = 1);
// Checks if cached data should be updated during the given frame.
FORCE_INLINE static bool ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame = 1, bool updateForce = false)
{
int32 updateFrequency, updatePhrase;
ComputeCascadeUpdateFrequency(cascadeIndex, cascadeCount, updateFrequency, updatePhrase, updateMaxCountPerFrame);
return (frameIndex % updateFrequency == updatePhrase) || updateForce;
}
static bool ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame = 1, bool updateForce = false);
// Calculates temporal offset in the dithering factor that gets cleaned out by TAA.
// Returns 0-1 value based on unscaled draw time for temporal effects to reduce artifacts from screen-space dithering when using Temporal Anti-Aliasing.
@@ -150,8 +145,9 @@ public:
DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent);
// Result normal/tangent are already packed into [0;1] range.
static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal);
static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent);
API_FUNCTION() static void CalculateTangentFrame(API_PARAM(Out) Float3& resultNormal, API_PARAM(Out) Float4& resultTangent, API_PARAM(Ref) const Float3& normal);
// Result normal/tangent are already packed into [0;1] range.
API_FUNCTION() static void CalculateTangentFrame(API_PARAM(Out) Float3& resultNormal, API_PARAM(Out) Float4& resultTangent, API_PARAM(Ref) const Float3& normal, API_PARAM(Ref) const Float3& tangent);
static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside);
static void ComputeBoxModelDrawMatrix(const RenderView& view, const OrientedBoundingBox& box, Matrix& resultWorld, bool& resultIsViewInside);

View File

@@ -20,12 +20,13 @@
#include "Engine/Core/Math/Double4x4.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Serialization/ISerializeModifier.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
@@ -70,6 +71,44 @@ namespace
}
return result;
}
void LinkPrefab(SceneObject* object, Prefab* linkPrefab)
{
// Find prefab object that is used by this object in a given prefab
Guid currentPrefabId = object->GetPrefabID(), currentObjectId = object->GetPrefabObjectID();
RETRY:
auto prefab = Content::Load<Prefab>(currentPrefabId);
Guid nestedPrefabId, nestedObjectId;
if (prefab && prefab->GetNestedObject(currentObjectId, nestedPrefabId, nestedObjectId))
{
auto nestedPrefab = Content::Load<Prefab>(nestedPrefabId);
if (nestedPrefab)
{
auto nestedObject = (Actor*)nestedPrefab->GetDefaultInstance(nestedObjectId);
if (nestedObject && nestedPrefab == linkPrefab)
{
object->LinkPrefab(nestedPrefabId, nestedObjectId);
return;
}
}
// Try deeper
currentPrefabId = nestedPrefabId;
currentObjectId = nestedObjectId;
goto RETRY;
}
// Failed to resolve properly object from a given prefab
object->BreakPrefabLink();
}
void LinkPrefabRecursive(Actor* actor, Prefab* linkPrefab)
{
for (auto script : actor->Scripts)
LinkPrefab(script, linkPrefab);
for (auto child : actor->Children)
LinkPrefab(child, linkPrefab);
}
}
Actor::Actor(const SpawnParams& params)
@@ -1185,11 +1224,15 @@ void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
}
else if (!parent && parentId.IsValid())
{
// Skip warning if object was mapped to empty id (intentionally ignored)
Guid tmpId;
if (_prefabObjectID.IsValid())
LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID);
else if (!modifier->IdsMapping.TryGet(parentId, tmpId) || tmpId.IsValid()) // Skip warning if object was mapped to empty id (intentionally ignored)
LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
if (!modifier->IdsMapping.TryGet(parentId, tmpId) || tmpId.IsValid())
{
if (_prefabObjectID.IsValid())
LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID);
else
LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
}
}
}
}
@@ -1729,7 +1772,6 @@ void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer,
{
// Create JSON
CompactJsonWriter writer(buffer);
writer.SceneObject(obj);
// Write json to output
@@ -1737,28 +1779,24 @@ void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer,
output.WriteInt32((int32)buffer.GetSize());
output.WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize());
// Store order in parent. Makes life easier for editor to sync objects order on undo/redo actions.
output.WriteInt32(obj->GetOrderInParent());
// Reuse string buffer
buffer.Clear();
}
bool Actor::ToBytes(const Array<Actor*>& actors, MemoryWriteStream& output)
{
PROFILE_CPU();
if (actors.IsEmpty())
{
// Cannot serialize empty list
return true;
}
PROFILE_CPU();
PROFILE_MEM(Level);
// Collect object ids that exist in the serialized data to allow references mapping later
Array<Guid> ids(actors.Count());
for (int32 i = 0; i < actors.Count(); i++)
{
// By default we collect actors and scripts (they are ManagedObjects recognized by the id)
auto actor = actors[i];
if (!actor)
continue;
@@ -1766,7 +1804,8 @@ bool Actor::ToBytes(const Array<Actor*>& actors, MemoryWriteStream& output)
for (int32 j = 0; j < actor->Scripts.Count(); j++)
{
const auto script = actor->Scripts[j];
ids.Add(script->GetID());
if (script)
ids.Add(script->GetID());
}
}
@@ -1776,23 +1815,28 @@ bool Actor::ToBytes(const Array<Actor*>& actors, MemoryWriteStream& output)
// Serialized objects ids (for references mapping)
output.Write(ids);
// Objects data
// Objects data (JSON)
rapidjson_flax::StringBuffer buffer;
CompactJsonWriter writer(buffer);
writer.StartArray();
for (int32 i = 0; i < actors.Count(); i++)
{
Actor* actor = actors[i];
if (!actor)
continue;
WriteObjectToBytes(actor, buffer, output);
writer.SceneObject(actor);
for (int32 j = 0; j < actor->Scripts.Count(); j++)
{
Script* script = actor->Scripts[j];
WriteObjectToBytes(script, buffer, output);
if (!script)
continue;
writer.SceneObject(script);
}
}
writer.EndArray();
// TODO: compress json (LZ4) if it's big enough
output.WriteInt32((int32)buffer.GetSize());
output.WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize());
return false;
}
@@ -1811,8 +1855,8 @@ Array<byte> Actor::ToBytes(const Array<Actor*>& actors)
bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeModifier* modifier)
{
PROFILE_CPU();
PROFILE_MEM(Level);
output.Clear();
ASSERT(modifier);
if (data.Length() <= 0)
return true;
@@ -1828,50 +1872,71 @@ bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeM
}
// Serialized objects ids (for references mapping)
#if 0
Array<Guid> ids;
stream.Read(ids);
int32 objectsCount = ids.Count();
#else
int32 objectsCount;
stream.ReadInt32(&objectsCount);
stream.Move<Guid>(objectsCount);
#endif
if (objectsCount < 0)
return true;
// Load objects data (JSON)
int32 bufferSize;
stream.ReadInt32(&bufferSize);
const char* buffer = (const char*)stream.Move(bufferSize);
rapidjson_flax::Document document;
{
PROFILE_CPU_NAMED("Json.Parse");
document.Parse(buffer, bufferSize);
}
if (document.HasParseError())
{
Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
return true;
}
// Prepare
Array<int32> order;
order.Resize(objectsCount);
modifier->EngineBuild = engineBuild;
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
SceneObjectsFactory::Context context(modifier);
// Fix root linkage for prefab instances (eg. when user duplicates a sub-prefab actor but not a root one)
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, document, modifier);
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
for (auto& instance : context.Instances)
{
Guid prefabObjectId;
if (!JsonTools::GetGuidIfValid(prefabObjectId, document[instance.RootIndex], "PrefabObjectID"))
continue;
// Get the original object from prefab
SceneObject* prefabObject = instance.Prefab->GetDefaultInstance(prefabObjectId);
if (prefabObject && prefabObject->GetParent())
{
// Add empty mapping to parent object in prefab to prevent linking to it
auto prefabObjectParentId = prefabObject->GetParent()->GetPrefabObjectID();
instance.IdsMapping[prefabObjectParentId] = Guid::Empty;
modifier->IdsMapping[prefabObjectParentId] = Guid::Empty;
Guid nestedPrefabId, nestedPrefabObjectId;
if (instance.Prefab->GetNestedObject(prefabObjectParentId, nestedPrefabId, nestedPrefabObjectId))
{
instance.IdsMapping[nestedPrefabObjectId] = Guid::Empty;
modifier->IdsMapping[nestedPrefabObjectId] = Guid::Empty;
}
}
}
// Deserialize objects
Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping);
auto startPos = stream.GetPosition();
for (int32 i = 0; i < objectsCount; i++)
{
// Buffer
int32 bufferSize;
stream.ReadInt32(&bufferSize);
const char* buffer = (const char*)stream.GetPositionHandle();
stream.Move(bufferSize);
// Order in parent
int32 orderInParent;
stream.ReadInt32(&orderInParent);
order[i] = orderInParent;
// Load JSON
rapidjson_flax::Document document;
{
PROFILE_CPU_NAMED("Json.Parse");
document.Parse(buffer, bufferSize);
}
if (document.HasParseError())
{
Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
return true;
}
// Create object
auto obj = SceneObjectsFactory::Spawn(context, document);
auto& objData = document[i];
auto obj = SceneObjectsFactory::Spawn(context, objData);
sceneObjects->At(i) = obj;
if (obj == nullptr)
{
@@ -1879,57 +1944,21 @@ bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeM
continue;
}
obj->RegisterObject();
// Add to results
Actor* actor = dynamic_cast<Actor*>(obj);
if (actor)
{
output.Add(actor);
}
}
// TODO: optimize this to call json parsing only once per-object instead of twice (spawn + load)
stream.SetPosition(startPos);
for (int32 i = 0; i < objectsCount; i++)
{
// Buffer
int32 bufferSize;
stream.ReadInt32(&bufferSize);
const char* buffer = (const char*)stream.GetPositionHandle();
stream.Move(bufferSize);
// Order in parent
int32 orderInParent;
stream.ReadInt32(&orderInParent);
// Load JSON
rapidjson_flax::Document document;
{
PROFILE_CPU_NAMED("Json.Parse");
document.Parse(buffer, bufferSize);
}
if (document.HasParseError())
{
Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
return true;
}
// Deserialize object
auto& objData = document[i];
auto obj = sceneObjects->At(i);
if (obj)
SceneObjectsFactory::Deserialize(context, obj, document);
SceneObjectsFactory::Deserialize(context, obj, objData);
else
SceneObjectsFactory::HandleObjectDeserializationError(document);
SceneObjectsFactory::HandleObjectDeserializationError(objData);
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
// Update objects order
//for (int32 i = 0; i < objectsCount; i++)
{
//SceneObject* obj = sceneObjects->At(i);
// TODO: remove order from saved data?
//obj->SetOrderInParent(order[i]);
}
// Call events (only for parents because they will propagate events down the tree)
CollectionPoolCache<ActorsCache::ActorsListType>::ScopeCache parents = ActorsCache::ActorsListCache.Get();
parents->EnsureCapacity(output.Count());
@@ -1940,6 +1969,32 @@ bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeM
Actor* actor = parents->At(i);
if (actor->HasPrefabLink() && !actor->IsPrefabRoot())
{
// Find a prefab in which that object is a root to establish a new linkage
Guid currentPrefabId = actor->GetPrefabID(), currentObjectId = actor->GetPrefabObjectID();
RETRY:
auto prefab = Content::Load<Prefab>(currentPrefabId);
Guid nestedPrefabId, nestedObjectId;
if (prefab && prefab->GetNestedObject(currentObjectId, nestedPrefabId, nestedObjectId))
{
auto nestedPrefab = Content::Load<Prefab>(nestedPrefabId);
if (nestedPrefab)
{
auto nestedObject = (Actor*)nestedPrefab->GetDefaultInstance(nestedObjectId);
if (nestedObject && nestedObject->IsPrefabRoot())
{
// Change link to the nested prefab
actor->LinkPrefab(nestedPrefabId, nestedObjectId);
LinkPrefabRecursive(actor, nestedPrefab);
continue;
}
}
// Try deeper
currentPrefabId = nestedPrefabId;
currentObjectId = nestedObjectId;
goto RETRY;
}
actor->BreakPrefabLink();
}
}
@@ -1949,8 +2004,7 @@ bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeM
}
for (int32 i = 0; i < parents->Count(); i++)
{
Actor* actor = parents->At(i);
actor->OnTransformChanged();
parents->At(i)->OnTransformChanged();
}
// Initialize actor that are spawned to scene or create managed instanced for others

View File

@@ -507,7 +507,7 @@ namespace
return;
Spline::Keyframe* prev = spline->Curve.GetKeyframes().Get();
Vector3 prevPos = transform.LocalToWorld(prev->Value.Translation);
float distance = Vector3::Distance(prevPos, DebugDraw::GetViewPos());
Real distance = Vector3::Distance(prevPos, DebugDraw::GetViewPos());
if (distance < METERS_TO_UNITS(800)) // 800m
{
// Bezier curve

View File

@@ -650,6 +650,11 @@ bool Prefab::ApplyAll(Actor* targetActor)
LOG(Warning, "Failed to create default prefab instance for the prefab asset.");
return true;
}
if (targetActor == _defaultInstance || targetActor->HasActorInHierarchy(_defaultInstance) || _defaultInstance->HasActorInHierarchy(targetActor))
{
LOG(Error, "Cannot apply changes to the prefab using default instance. Use manually spawned prefab instance instead.");
return true;
}
if (targetActor->GetPrefabObjectID() != GetRootObjectId())
{
LOG(Warning, "Applying prefab changes with modified root object. Root object id: {0}, new root: {1} (prefab object id: {2})", GetRootObjectId().ToString(), targetActor->ToString(), targetActor->GetPrefabObjectID());

View File

@@ -60,12 +60,14 @@ public:
/// <summary>
/// Requests the default prefab object instance. Deserializes the prefab objects from the asset. Skips if already done.
/// </summary>
/// <remarks>Default instances of the prefab are read-only and are used internally for objects serialization (prefab diff).</remarks>
/// <returns>The root of the prefab object loaded from the prefab. Contains the default values. It's not added to gameplay but deserialized with postLoad and init event fired.</returns>
API_FUNCTION() Actor* GetDefaultInstance();
/// <summary>
/// Requests the default prefab object instance. Deserializes the prefab objects from the asset. Skips if already done.
/// </summary>
/// <remarks>Default instances of the prefab are read-only and are used internally for objects serialization (prefab diff).</remarks>
/// <param name="objectId">The ID of the object to get from prefab default object. It can be one of the child-actors or any script that exists in the prefab. Methods returns root if id is empty.</param>
/// <returns>The object of the prefab loaded from the prefab. Contains the default values. It's not added to gameplay but deserialized with postLoad and init event fired.</returns>
API_FUNCTION() SceneObject* GetDefaultInstance(API_PARAM(Ref) const Guid& objectId);

View File

@@ -61,7 +61,7 @@ void SceneObject::BreakPrefabLink()
String SceneObject::GetNamePath(Char separatorChar) const
{
Array<StringView> names;
Array<StringView, InlinedAllocation<8>> names;
const Actor* a = dynamic_cast<const Actor*>(this);
if (!a)
a = GetParent();

View File

@@ -1863,7 +1863,7 @@ void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client)
{
auto& item = it->Item;
ScriptingObject* obj = item.Object.Get();
if (!obj || !item.Spawned || item.Role != NetworkObjectRole::OwnedAuthoritative)
if (!obj || item.Role != NetworkObjectRole::OwnedAuthoritative)
continue;
// Mark this client as missing cached data

View File

@@ -425,7 +425,7 @@ void GBufferPass::DrawSky(RenderContext& renderContext, GPUContext* context)
BoundingSphere frustumBounds;
renderContext.View.CullingFrustum.GetSphere(frustumBounds);
origin = frustumBounds.Center;
size = frustumBounds.Radius;
size = (float)frustumBounds.Radius;
}
Matrix::Scaling(size / ((float)box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum
Matrix::CreateWorld(origin, Float3::Up, Float3::Backward, m2); // Rotate sphere model

View File

@@ -747,7 +747,7 @@ void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawP
if (drawModes != DrawPass::None &&
(staticFlags & renderContext.View.StaticFlagsMask) == renderContext.View.StaticFlagsCompare &&
renderContext.View.CullingFrustum.Intersects(bounds) &&
RenderTools::ComputeBoundsScreenRadiusSquared(bounds.Center, bounds.Radius, renderContext.View) * (renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y) >= minObjectPixelSizeSq)
RenderTools::ComputeBoundsScreenRadiusSquared(bounds.Center, (float)bounds.Radius, renderContext.View) * (renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y) >= minObjectPixelSizeSq)
{
renderContext.List->ShadowDepthDrawCallsList.Indices.Add(index);
}

View File

@@ -17,7 +17,6 @@
#include "FlaxEngine.Gen.h"
#include "Scripting.h"
#include "Events.h"
#include "Internal/StdTypesContainer.h"
Dictionary<Pair<ScriptingTypeHandle, StringView>, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable;
Delegate<ScriptingObject*, Span<Variant>, ScriptingTypeHandle, StringView> ScriptingEvents::Event;
@@ -32,6 +31,18 @@ ManagedBinaryModule* GetBinaryModuleCorlib()
#endif
}
MMethod* MClass::FindMethod(const char* name, int32 numParams, bool checkBaseClasses) const
{
MMethod* method = GetMethod(name, numParams);
if (!method && checkBaseClasses)
{
MClass* base = GetBaseClass();
if (base)
method = base->FindMethod(name, numParams, true);
}
return method;
}
ScriptingTypeHandle::ScriptingTypeHandle(const ScriptingTypeInitializer& initializer)
: Module(initializer.Module)
, TypeIndex(initializer.TypeIndex)
@@ -835,61 +846,17 @@ namespace
}
return nullptr;
}
bool VariantTypeEquals(const VariantType& type, MType* mType, bool isOut = false)
{
MClass* mClass = MCore::Type::GetClass(mType);
MClass* variantClass = MUtils::GetClass(type);
if (variantClass != mClass)
{
// Hack for Vector2/3/4 which alias with Float2/3/4 or Double2/3/4 (depending on USE_LARGE_WORLDS)
const auto& stdTypes = *StdTypesContainer::Instance();
if (mClass == stdTypes.Vector2Class && (type.Type == VariantType::Float2 || type.Type == VariantType::Double2))
return true;
if (mClass == stdTypes.Vector3Class && (type.Type == VariantType::Float3 || type.Type == VariantType::Double3))
return true;
if (mClass == stdTypes.Vector4Class && (type.Type == VariantType::Float4 || type.Type == VariantType::Double4))
return true;
return false;
}
return true;
}
}
#endif
MMethod* ManagedBinaryModule::FindMethod(MClass* mclass, const ScriptingTypeMethodSignature& signature)
MMethod* ManagedBinaryModule::FindMethod(const MClass* mclass, const ScriptingTypeMethodSignature& signature)
{
#if USE_CSHARP
if (!mclass)
return nullptr;
const auto& methods = mclass->GetMethods();
for (MMethod* method : methods)
{
if (method->IsStatic() != signature.IsStatic)
continue;
if (method->GetName() != signature.Name)
continue;
if (method->GetParametersCount() != signature.Params.Count())
continue;
bool isValid = true;
for (int32 paramIdx = 0; paramIdx < signature.Params.Count(); paramIdx++)
{
auto& param = signature.Params[paramIdx];
MType* type = method->GetParameterType(paramIdx);
if (param.IsOut != method->GetParameterIsOut(paramIdx) ||
!VariantTypeEquals(param.Type, type, param.IsOut))
{
isValid = false;
break;
}
}
if (isValid && VariantTypeEquals(signature.ReturnType, method->GetReturnType()))
return method;
}
#endif
return mclass ? mclass->GetMethod(signature) : nullptr;
#else
return nullptr;
#endif
}
#if USE_CSHARP

View File

@@ -23,7 +23,7 @@ struct ScriptingTypeMethodSignature
StringAnsiView Name;
VariantType ReturnType;
bool IsStatic;
bool IsStatic = false;
Array<Param, InlinedAllocation<16>> Params;
};
@@ -322,7 +322,7 @@ public:
#endif
static ScriptingObject* ManagedObjectSpawn(const ScriptingObjectSpawnParams& params);
static MMethod* FindMethod(MClass* mclass, const ScriptingTypeMethodSignature& signature);
static MMethod* FindMethod(const MClass* mclass, const ScriptingTypeMethodSignature& signature);
#if USE_CSHARP
static ManagedBinaryModule* FindModule(const MClass* klass);
static ScriptingTypeHandle FindType(const MClass* klass);

View File

@@ -188,11 +188,11 @@ public:
MClass* GetBaseClass() const;
/// <summary>
/// Checks if this class is a sub class of the specified class (including any derived types).
/// Checks if this class is a subclass of the specified class (including any derived types).
/// </summary>
/// <param name="klass">The class.</param>
/// <param name="checkInterfaces">True if check interfaces, otherwise just base class.</param>
/// <returns>True if this class is a sub class of the specified class.</returns>
/// <returns>True if this class is a subclass of the specified class.</returns>
bool IsSubClassOf(const MClass* klass, bool checkInterfaces = false) const;
/// <summary>
@@ -206,7 +206,7 @@ public:
/// Checks is the provided object instance of this class' type.
/// </summary>
/// <param name="object">The object to check.</param>
/// <returns>True if object is an instance the this class.</returns>
/// <returns>True if object is an instance this class.</returns>
bool IsInstanceOfType(MObject* object) const;
/// <summary>
@@ -227,17 +227,7 @@ public:
/// <param name="numParams">The method parameters count.</param>
/// <param name="checkBaseClasses">True if check base classes when searching for the given method.</param>
/// <returns>The method or null if failed to find it.</returns>
MMethod* FindMethod(const char* name, int32 numParams, bool checkBaseClasses = true) const
{
MMethod* method = GetMethod(name, numParams);
if (!method && checkBaseClasses)
{
MClass* base = GetBaseClass();
if (base)
method = base->FindMethod(name, numParams, true);
}
return method;
}
MMethod* FindMethod(const char* name, int32 numParams, bool checkBaseClasses = true) const;
/// <summary>
/// Returns an object referencing a method with the specified name and number of parameters.
@@ -248,6 +238,13 @@ public:
/// <returns>The method or null if failed to get it.</returns>
MMethod* GetMethod(const char* name, int32 numParams = 0) const;
/// <summary>
/// Returns an object referencing a method with the specified signature.
/// </summary>
/// <param name="signature">The method signature.</param>
/// <returns>The method or null if failed to get it.</returns>
MMethod* GetMethod(const struct ScriptingTypeMethodSignature& signature) const;
/// <summary>
/// Returns all methods belonging to this class.
/// </summary>
@@ -271,7 +268,7 @@ public:
const Array<MField*, ArenaAllocation>& GetFields() const;
/// <summary>
/// Returns an object referencing a event with the specified name.
/// Returns an object referencing an event with the specified name.
/// </summary>
/// <param name="name">The event name.</param>
/// <returns>The event object.</returns>

View File

@@ -29,6 +29,7 @@ protected:
int32 _paramsCount;
mutable void* _returnType;
mutable Array<void*, InlinedAllocation<8>> _parameterTypes;
mutable uint64 _parameterOuts = 0;
void CacheSignature() const;
#else
StringAnsiView _name;

View File

@@ -845,7 +845,6 @@ MClass* MUtils::GetClass(const VariantType& value)
auto mclass = Scripting::FindClass(StringAnsiView(value.TypeName));
if (mclass)
return mclass;
const auto& stdTypes = *StdTypesContainer::Instance();
switch (value.Type)
{
case VariantType::Void:
@@ -891,25 +890,25 @@ MClass* MUtils::GetClass(const VariantType& value)
case VariantType::Double4:
return Double4::TypeInitializer.GetClass();
case VariantType::Color:
return stdTypes.ColorClass;
return Color::TypeInitializer.GetClass();
case VariantType::Guid:
return stdTypes.GuidClass;
return GetBinaryModuleCorlib()->Assembly->GetClass("System.Guid");
case VariantType::Typename:
return stdTypes.TypeClass;
return GetBinaryModuleCorlib()->Assembly->GetClass("System.Type");
case VariantType::BoundingBox:
return stdTypes.BoundingBoxClass;
return BoundingBox::TypeInitializer.GetClass();
case VariantType::BoundingSphere:
return stdTypes.BoundingSphereClass;
return BoundingSphere::TypeInitializer.GetClass();
case VariantType::Quaternion:
return stdTypes.QuaternionClass;
return Quaternion::TypeInitializer.GetClass();
case VariantType::Transform:
return stdTypes.TransformClass;
return Transform::TypeInitializer.GetClass();
case VariantType::Rectangle:
return stdTypes.RectangleClass;
return Rectangle::TypeInitializer.GetClass();
case VariantType::Ray:
return stdTypes.RayClass;
return Ray::TypeInitializer.GetClass();
case VariantType::Matrix:
return stdTypes.MatrixClass;
return Matrix::TypeInitializer.GetClass();
case VariantType::Array:
if (value.TypeName)
{
@@ -1202,8 +1201,7 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
if (value.Type.Type != VariantType::Array)
return nullptr;
MObject* object = BoxVariant(value);
auto typeStr = MCore::Type::ToString(type);
if (object && !MCore::Object::GetClass(object)->IsSubClassOf(MCore::Type::GetClass(type)))
if (object && MCore::Type::GetClass(type) != MCore::Array::GetArrayClass((MArray*)object))
object = nullptr;
return object;
}
@@ -1238,6 +1236,29 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
return nullptr;
}
bool MUtils::VariantTypeEquals(const VariantType& type, MType* mType, bool isOut)
{
MClass* mClass = MCore::Type::GetClass(mType);
MClass* variantClass = MUtils::GetClass(type);
if (variantClass != mClass)
{
// Hack for Vector2/3/4 which alias with Float2/3/4 or Double2/3/4 (depending on USE_LARGE_WORLDS)
if (mClass->GetFullName() == StringAnsiView("FlaxEngine.Vector2", 18) && (type.Type == VariantType::Float2 || type.Type == VariantType::Double2))
return true;
if (mClass->GetFullName() == StringAnsiView("FlaxEngine.Vector3", 18) && (type.Type == VariantType::Float3 || type.Type == VariantType::Double3))
return true;
if (mClass->GetFullName() == StringAnsiView("FlaxEngine.Vector4", 18) && (type.Type == VariantType::Float4 || type.Type == VariantType::Double4))
return true;
// Arrays
if (type == VariantType::Array && type.GetElementType() == VariantType::Object)
return MCore::Type::GetType(mType) == MTypes::Array;
return false;
}
return true;
}
MObject* MUtils::ToManaged(const Version& value)
{
#if USE_NETCORE

View File

@@ -621,6 +621,7 @@ namespace MUtils
#endif
extern void* VariantToManagedArgPtr(Variant& value, MType* type, bool& failed);
extern bool VariantTypeEquals(const VariantType& type, MType* mType, bool isOut = false);
extern MObject* ToManaged(const Version& value);
extern Version ToNative(MObject* value);

View File

@@ -1062,10 +1062,39 @@ MClass* MClass::GetElementClass() const
MMethod* MClass::GetMethod(const char* name, int32 numParams) const
{
GetMethods();
for (int32 i = 0; i < _methods.Count(); i++)
for (MMethod* method : _methods)
{
if (_methods[i]->GetParametersCount() == numParams && _methods[i]->GetName() == name)
return _methods[i];
if (method->GetParametersCount() == numParams && method->GetName() == name)
return method;
}
return nullptr;
}
MMethod* MClass::GetMethod(const ScriptingTypeMethodSignature& signature) const
{
GetMethods();
for (MMethod* method : _methods)
{
if (method->IsStatic() != signature.IsStatic)
continue;
if (method->GetName() != signature.Name)
continue;
if (method->GetParametersCount() != signature.Params.Count())
continue;
bool isValid = true;
for (int32 paramIdx = 0; paramIdx < signature.Params.Count(); paramIdx++)
{
auto& param = signature.Params[paramIdx];
MType* type = method->GetParameterType(paramIdx);
if (param.IsOut != method->GetParameterIsOut(paramIdx) ||
!MUtils::VariantTypeEquals(param.Type, type, param.IsOut))
{
isValid = false;
break;
}
}
if (isValid && (signature.ReturnType.Type == VariantType::Null || MUtils::VariantTypeEquals(signature.ReturnType, method->GetReturnType())))
return method;
}
return nullptr;
}
@@ -1485,13 +1514,16 @@ void MMethod::CacheSignature() const
static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType"));
static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes"));
static void* GetMethodParameterIsOutPtr = GetStaticMethodPointer(TEXT("GetMethodParameterIsOut"));
_returnType = CallStaticMethod<void*, void*>(GetMethodReturnTypePtr, _handle);
_parameterOuts = 0;
if (_paramsCount != 0)
{
void** parameterTypeHandles;
CallStaticMethod<void, void*, void***>(GetMethodParameterTypesPtr, _handle, &parameterTypeHandles);
_parameterTypes.Set(parameterTypeHandles, _paramsCount);
MCore::GC::FreeMemory(parameterTypeHandles);
_parameterOuts = CallStaticMethod<uint64, void*>(GetMethodParameterIsOutPtr, _handle);
}
_hasCachedSignature = true;
@@ -1558,9 +1590,7 @@ bool MMethod::GetParameterIsOut(int32 paramIdx) const
if (!_hasCachedSignature)
CacheSignature();
ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount);
// TODO: cache GetParameterIsOut maybe?
static void* GetMethodParameterIsOutPtr = GetStaticMethodPointer(TEXT("GetMethodParameterIsOut"));
return CallStaticMethod<bool, void*, int>(GetMethodParameterIsOutPtr, _handle, paramIdx);
return _parameterOuts & (1ull << paramIdx);
}
bool MMethod::HasAttribute(const MClass* klass) const

View File

@@ -1357,6 +1357,11 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const
return method;
}
MMethod* MClass::GetMethod(const ScriptingTypeMethodSignature& signature) const
{
return GetMethod(signature.Name.Get(), signature.Params.Count());
}
const Array<MMethod*>& MClass::GetMethods() const
{
if (_hasCachedMethods)

View File

@@ -363,6 +363,11 @@ MMethod* MClass::GetMethod(const char* name, int32 numParams) const
return nullptr;
}
MMethod* MClass::GetMethod(const ScriptingTypeMethodSignature& signature) const
{
return nullptr;
}
const Array<MMethod*, ArenaAllocation>& MClass::GetMethods() const
{
_hasCachedMethods = true;

View File

@@ -72,6 +72,13 @@ void ChangeIds(rapidjson_flax::Value& obj, rapidjson_flax::Document& document, c
}
}
void JsonTools::MergeObjects(Value& target, Value& source, Value::AllocatorType& allocator)
{
ASSERT(target.IsObject() && source.IsObject());
for (auto itr = source.MemberBegin(); itr != source.MemberEnd(); ++itr)
target.AddMember(itr->name, itr->value, allocator);
}
void JsonTools::ChangeIds(Document& doc, const Dictionary<Guid, Guid>& mapping)
{
if (mapping.IsEmpty())
@@ -235,6 +242,14 @@ Plane JsonTools::GetPlane(const Value& value)
return result;
}
Rectangle JsonTools::GetRectangle(const Value& value)
{
return Rectangle(
GetVector2(value, "Location", Vector2::Zero),
GetVector2(value, "Size", Vector2::Zero)
);
}
BoundingSphere JsonTools::GetBoundingSphere(const Value& value)
{
BoundingSphere result;
@@ -283,3 +298,14 @@ DateTime JsonTools::GetDateTime(const Value& value)
{
return DateTime(value.GetInt64());
}
bool JsonTools::GetGuidIfValid(Guid& result, const Value& node, const char* name)
{
auto member = node.FindMember(name);
if (member != node.MemberEnd())
{
result = GetGuid(member->value);
return result.IsValid();
}
return false;
}

View File

@@ -35,13 +35,7 @@ public:
MergeObjects(target, source, target.GetAllocator());
}
static void MergeObjects(Value& target, Value& source, Value::AllocatorType& allocator)
{
ASSERT(target.IsObject() && source.IsObject());
for (auto itr = source.MemberBegin(); itr != source.MemberEnd(); ++itr)
target.AddMember(itr->name, itr->value, allocator);
}
static void MergeObjects(Value& target, Value& source, Value::AllocatorType& allocator);
static void ChangeIds(Document& doc, const Dictionary<Guid, Guid, HeapAllocation>& mapping);
public:
@@ -75,11 +69,9 @@ public:
static Float2 GetFloat2(const Value& value);
static Float3 GetFloat3(const Value& value);
static Float4 GetFloat4(const Value& value);
static Double2 GetDouble2(const Value& value);
static Double3 GetDouble3(const Value& value);
static Double4 GetDouble4(const Value& value);
static Color GetColor(const Value& value);
static Quaternion GetQuaternion(const Value& value);
static Ray GetRay(const Value& value);
@@ -87,15 +79,7 @@ public:
static Transform GetTransform(const Value& value);
static void GetTransform(Transform& result, const Value& value);
static Plane GetPlane(const Value& value);
static Rectangle GetRectangle(const Value& value)
{
return Rectangle(
GetVector2(value, "Location", Vector2::Zero),
GetVector2(value, "Size", Vector2::Zero)
);
}
static Rectangle GetRectangle(const Value& value);
static BoundingSphere GetBoundingSphere(const Value& value);
static BoundingBox GetBoundingBox(const Value& value);
static Guid GetGuid(const Value& value);
@@ -176,16 +160,7 @@ public:
return member != node.MemberEnd() ? GetGuid(member->value) : Guid::Empty;
}
FORCE_INLINE static bool GetGuidIfValid(Guid& result, const Value& node, const char* name)
{
auto member = node.FindMember(name);
if (member != node.MemberEnd())
{
result = GetGuid(member->value);
return result.IsValid();
}
return false;
}
static bool GetGuidIfValid(Guid& result, const Value& node, const char* name);
public:
FORCE_INLINE static void GetBool(bool& result, const Value& node, const char* name)

View File

@@ -439,7 +439,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
// Calculate normals for quad two vertices
Float3 n0 = Float3::Normalize((v00 - v01) ^ (v01 - v10));
Float3 n1 = Float3::Normalize((v11 - v10) ^ (v10 - v01));
Float3 n2 = n0 + n1;
Float3 n2 = Float3::Normalize(n0 + n1);
// Apply normal to each vertex using it
normalsPerVertex[i00] += n1;
@@ -2572,9 +2572,9 @@ void TerrainPatch::ExtractCollisionGeometry(Array<Float3>& vertexBuffer, Array<i
const int32 vertexCount = rows * cols;
_collisionVertices.Resize(vertexCount);
Float3* vb = _collisionVertices.Get();
for (int32 row = 0; row < rows; row++)
for (int32 col = 0; col < cols; col++)
{
for (int32 col = 0; col < cols; col++)
for (int32 row = 0; row < rows; row++)
{
Float3 v((float)row, PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row, col) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)col);
Float3::Transform(v, world, v);
@@ -2591,9 +2591,9 @@ void TerrainPatch::ExtractCollisionGeometry(Array<Float3>& vertexBuffer, Array<i
const int32 indexCount = (rows - 1) * (cols - 1) * 6;
indexBuffer.Resize(indexCount);
int32* ib = indexBuffer.Get();
for (int32 row = 0; row < rows - 1; row++)
for (int32 col = 0; col < cols - 1; col++)
{
for (int32 col = 0; col < cols - 1; col++)
for (int32 row = 0; row < rows - 1; row++)
{
#define GET_INDEX(x, y) *ib++ = (col + (y)) + (row + (x)) * cols

View File

@@ -456,8 +456,8 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo
_writer.Write(TEXT("#define MATERIAL_REFLECTIONS_SSR_COLOR ({0})\n"), sceneColorTexture.ShaderName);
}
WRITE_FEATURES(Defines);
inputs[In_Defines] = _writer.ToString();
WriteCustomGlobalCode(customGlobalCodeNodes, In_Defines);
inputs[In_Defines] = _writer.ToString();
_writer.Clear();
}

View File

@@ -438,7 +438,7 @@ bool ModelTool::GenerateModelSDF(Model* inputModel, const ModelData* modelData,
minDistance *= -1; // Voxel is inside the geometry so turn it into negative distance to the surface
const int32 xAddress = x + yAddress;
formatWrite(voxels.Get() + xAddress * formatStride, minDistance * encodeMAD.X + encodeMAD.Y);
formatWrite(voxels.Get() + xAddress * formatStride, (float)minDistance * encodeMAD.X + encodeMAD.Y);
}
}
};

View File

@@ -42,6 +42,7 @@ namespace FlaxEngine.GUI
'\\',
'>',
'<',
'=',
};
/// <summary>

View File

@@ -61,6 +61,9 @@ namespace FlaxEngine
/// <inheritdoc />
public override bool CanRender()
{
if (!Canvas)
return false;
// Sync with canvas options
Location = Canvas.RenderLocation;
Order = Canvas.Order;

View File

@@ -493,35 +493,26 @@ void ShaderGenerator::ProcessGroupPacking(Box* box, Node* node, Value& value)
// Unpack
case 30:
{
Box* b = node->GetBox(0);
Value v = tryGetValue(b, Float2::Zero).AsFloat2();
int32 subIndex = box->ID - 1;
ASSERT(subIndex >= 0 && subIndex < 2);
value = Value(ValueType::Float, v.Value + _subs[subIndex]);
value = tryGetValue(node->GetBox(0), Float2::Zero).AsFloat2();
const int32 subIndex = box->ID - 1;
if (subIndex >= 0 && subIndex < 2)
value = Value(ValueType::Float, value.Value + _subs[subIndex]);
break;
}
case 31:
{
Box* b = node->GetBox(0);
Value v = tryGetValue(b, Float3::Zero).AsFloat3();
int32 subIndex = box->ID - 1;
ASSERT(subIndex >= 0 && subIndex < 3);
value = Value(ValueType::Float, v.Value + _subs[subIndex]);
value = tryGetValue(node->GetBox(0), Float3::Zero).AsFloat3();
const int32 subIndex = box->ID - 1;
if (subIndex >= 0 && subIndex < 3)
value = Value(ValueType::Float, value.Value + _subs[subIndex]);
break;
}
case 32:
{
Box* b = node->GetBox(0);
Value v = tryGetValue(b, Float4::Zero).AsFloat4();
int32 subIndex = box->ID - 1;
ASSERT(subIndex >= 0 && subIndex < 4);
value = Value(ValueType::Float, v.Value + _subs[subIndex]);
value = tryGetValue(node->GetBox(0), Float4::Zero).AsFloat4();
const int32 subIndex = box->ID - 1;
if (subIndex >= 0 && subIndex < 4)
value = Value(ValueType::Float, value.Value + _subs[subIndex]);
break;
}
case 33: