Add MeshDeformation utility for generic meshes vertices morphing (eg. via Blend Shapes or Cloth)

This commit is contained in:
Wojtek Figat
2023-07-03 09:49:23 +02:00
parent 60181a29a3
commit 99ee0b1bfe
26 changed files with 600 additions and 368 deletions

View File

@@ -255,6 +255,8 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=comperand/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=coord/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=cubemap/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deformer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=deformers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=defragmentation/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Delaunay/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Defocus/@EntryIndexedValue">True</s:Boolean>

View File

@@ -127,6 +127,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
Matrix m1, m2, world;
GeometryDrawStateData drawState;
draw.DrawState = &drawState;
draw.Deformation = nullptr;
draw.World = &world;
AssetReference<Texture> texture;
for (Actor* icon : icons)
@@ -205,6 +206,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
// Draw icon
GeometryDrawStateData drawState;
draw.DrawState = &drawState;
draw.Deformation = nullptr;
// Support custom icons through types, but not onces that were added through actors,
// since they cant register while in prefab view anyway

View File

@@ -852,6 +852,7 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
{
auto& lod = LODs[lodIndex];
lod._model = this;
lod._lodIndex = lodIndex;
lod.ScreenSize = 1.0f;
const int32 meshesCount = meshesCountPerLod[lodIndex];
if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES)
@@ -1112,6 +1113,7 @@ Asset::LoadResult SkinnedModel::load()
{
auto& lod = LODs[lodIndex];
lod._model = this;
lod._lodIndex = lodIndex;
// Screen Size
stream->ReadFloat(&lod.ScreenSize);

View File

@@ -1180,6 +1180,7 @@ void Foliage::Draw(RenderContext& renderContext)
draw.Buffer = &type.Entries;
draw.World = &world;
draw.DrawState = &instance.DrawState;
draw.Deformation = nullptr;
draw.Bounds = instance.Bounds;
draw.PerInstanceRandom = instance.Random;
draw.DrawModes = type.DrawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);

View File

@@ -4,10 +4,10 @@
#include "GPUContext.h"
#include "PixelFormatExtensions.h"
#include "GPUDevice.h"
#include "RenderTask.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Threading/Threading.h"
DynamicBuffer::DynamicBuffer(uint32 initialCapacity, uint32 stride, const String& name)
: _buffer(nullptr)
@@ -46,9 +46,11 @@ void DynamicBuffer::Flush()
}
// Upload data to the buffer
if (IsInMainThread() && GPUDevice::Instance->IsRendering())
if (GPUDevice::Instance->IsRendering())
{
RenderContext::GPULocker.Lock();
GPUDevice::Instance->GetMainContext()->UpdateBuffer(_buffer, Data.Get(), size);
RenderContext::GPULocker.Unlock();
}
else
{

View File

@@ -1,180 +0,0 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "BlendShape.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Profiler/ProfilerCPU.h"
BlendShapesInstance::MeshInstance::MeshInstance()
: IsUsed(false)
, IsDirty(false)
, DirtyMinVertexIndex(0)
, DirtyMaxVertexIndex(MAX_uint32 - 1)
, VertexBuffer(0, sizeof(VB0SkinnedElementType), TEXT("Skinned Mesh Blend Shape"))
{
}
BlendShapesInstance::MeshInstance::~MeshInstance()
{
}
BlendShapesInstance::~BlendShapesInstance()
{
Meshes.ClearDelete();
}
void BlendShapesInstance::Update(SkinnedModel* skinnedModel)
{
if (!WeightsDirty)
return;
WeightsDirty = false;
ASSERT(skinnedModel);
if (skinnedModel->IsVirtual())
{
// Blend shapes are not supported for virtual skinned models
return;
}
PROFILE_CPU_NAMED("Update Blend Shapes");
// Collect used meshes
for (auto& e : Meshes)
{
MeshInstance* instance = e.Value;
instance->IsUsed = false;
instance->IsDirty = false;
}
for (auto& e : Weights)
{
for (auto& lod : skinnedModel->LODs)
{
for (auto& mesh : lod.Meshes)
{
for (auto& blendShape : mesh.BlendShapes)
{
if (blendShape.Name != e.First)
continue;
// Setup mesh instance and mark it as used for blending
MeshInstance* instance;
if (!Meshes.TryGet(&mesh, instance))
{
instance = New<MeshInstance>();
Meshes.Add(&mesh, instance);
}
instance->IsUsed = true;
instance->IsDirty = true;
// One blend shape of that name per mesh
break;
}
}
}
}
// Update all used meshes
for (auto& e : Meshes)
{
MeshInstance& instance = *e.Value;
if (!instance.IsUsed)
continue;
const SkinnedMesh* mesh = e.Key;
// Get skinned mesh vertex buffer data (original, cached on CPU)
BytesContainer vertexBuffer;
int32 vertexCount;
if (mesh->DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffer, vertexCount))
{
// Don't use this mesh if failed to get it's vertices data
instance.IsUsed = false;
continue;
}
// Estimate the range of the vertices to modify by the currently active blend shapes
uint32 minVertexIndex = MAX_uint32, maxVertexIndex = 0;
bool useNormals = false;
Array<Pair<const BlendShape&, const float>, InlinedAllocation<32>> blendShapes;
for (const BlendShape& blendShape : mesh->BlendShapes)
{
for (auto& q : Weights)
{
if (q.First == blendShape.Name)
{
const float weight = q.Second * blendShape.Weight;
blendShapes.Add(Pair<const BlendShape&, const float>(blendShape, weight));
minVertexIndex = Math::Min(minVertexIndex, blendShape.MinVertexIndex);
maxVertexIndex = Math::Max(maxVertexIndex, blendShape.MaxVertexIndex);
useNormals |= blendShape.UseNormals;
break;
}
}
}
// Initialize the dynamic vertex buffer data (use the dirty range from the previous update to be cleared with initial data)
instance.VertexBuffer.Data.Resize(vertexBuffer.Length());
const uint32 dirtyVertexDataStart = instance.DirtyMinVertexIndex * sizeof(VB0SkinnedElementType);
const uint32 dirtyVertexDataLength = Math::Min<uint32>(instance.DirtyMaxVertexIndex - instance.DirtyMinVertexIndex + 1, vertexCount) * sizeof(VB0SkinnedElementType);
Platform::MemoryCopy(instance.VertexBuffer.Data.Get() + dirtyVertexDataStart, vertexBuffer.Get() + dirtyVertexDataStart, dirtyVertexDataLength);
// Blend all blend shapes
auto data = (VB0SkinnedElementType*)instance.VertexBuffer.Data.Get();
for (const auto& q : blendShapes)
{
// TODO: use SIMD
if (useNormals)
{
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
{
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < (uint32)vertexCount);
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta;
vertex.Normal = normal * 0.5f + 0.5f;
}
}
else
{
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
{
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < (uint32)vertexCount);
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
}
}
}
if (useNormals)
{
// Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range)
// TODO: use SIMD
for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++)
{
VB0SkinnedElementType& vertex = *(data + vertexIndex);
Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f;
normal.Normalize();
vertex.Normal = normal * 0.5f + 0.5f;
Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f;
tangent = tangent - ((tangent | normal) * normal);
tangent.Normalize();
const auto tangentSign = vertex.Tangent.W;
vertex.Tangent = tangent * 0.5f + 0.5f;
vertex.Tangent.W = tangentSign;
}
}
// Mark as dirty to be flushed before next rendering
instance.IsDirty = true;
instance.DirtyMinVertexIndex = minVertexIndex;
instance.DirtyMaxVertexIndex = maxVertexIndex;
}
}
void BlendShapesInstance::Clear()
{
Meshes.ClearDelete();
Weights.Clear();
WeightsDirty = false;
}

View File

@@ -3,15 +3,8 @@
#pragma once
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/Pair.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Graphics/DynamicBuffer.h"
class SkinnedMesh;
class SkinnedModel;
class GPUBuffer;
/// <summary>
/// The blend shape vertex data optimized for runtime meshes morphing.
@@ -76,59 +69,3 @@ public:
/// </summary>
Array<BlendShapeVertex> Vertices;
};
/// <summary>
/// The blend shapes runtime instance data. Handles blend shapes updating, blending and preparing for skinned mesh rendering.
/// </summary>
class BlendShapesInstance
{
public:
/// <summary>
/// The runtime data for blend shapes used for on a mesh.
/// </summary>
class MeshInstance
{
public:
bool IsUsed;
bool IsDirty;
uint32 DirtyMinVertexIndex;
uint32 DirtyMaxVertexIndex;
DynamicVertexBuffer VertexBuffer;
MeshInstance();
~MeshInstance();
};
public:
/// <summary>
/// The blend shapes weights (pair of blend shape name and the weight).
/// </summary>
Array<Pair<String, float>> Weights;
/// <summary>
/// Flag that marks if blend shapes weights has been modified.
/// </summary>
bool WeightsDirty = false;
/// <summary>
/// The blend shapes meshes data.
/// </summary>
Dictionary<SkinnedMesh*, MeshInstance*> Meshes;
public:
/// <summary>
/// Finalizes an instance of the <see cref="BlendShapesInstance"/> class.
/// </summary>
~BlendShapesInstance();
/// <summary>
/// Updates the instanced meshes. Performs the blend shapes blending (only if weights were modified).
/// </summary>
/// <param name="skinnedModel">The skinned model used for blend shapes.</param>
void Update(SkinnedModel* skinnedModel);
/// <summary>
/// Clears the runtime data.
/// </summary>
void Clear();
};

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Mesh.h"
#include "MeshDeformation.h"
#include "ModelInstanceEntry.h"
#include "Engine/Content/Assets/Material.h"
#include "Engine/Content/Assets/Model.h"
@@ -480,6 +481,11 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
drawCall.Geometry.VertexBuffersOffsets[0] = 0;
drawCall.Geometry.VertexBuffersOffsets[1] = 0;
drawCall.Geometry.VertexBuffersOffsets[2] = 0;
if (info.Deformation)
{
info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]);
info.Deformation->RunDeformers(this, MeshBufferType::Vertex1, drawCall.Geometry.VertexBuffers[1]);
}
if (info.VertexColors && info.VertexColors[_lodIndex])
{
// TODO: cache vertexOffset within the model LOD per-mesh
@@ -540,6 +546,11 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
drawCall.Geometry.VertexBuffersOffsets[0] = 0;
drawCall.Geometry.VertexBuffersOffsets[1] = 0;
drawCall.Geometry.VertexBuffersOffsets[2] = 0;
if (info.Deformation)
{
info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]);
info.Deformation->RunDeformers(this, MeshBufferType::Vertex1, drawCall.Geometry.VertexBuffers[1]);
}
if (info.VertexColors && info.VertexColors[_lodIndex])
{
// TODO: cache vertexOffset within the model LOD per-mesh

View File

@@ -159,7 +159,7 @@ public:
struct DrawInfo
{
/// <summary>
/// The instance buffer to use during model rendering
/// The instance buffer to use during model rendering.
/// </summary>
ModelInstanceEntries* Buffer;
@@ -169,10 +169,15 @@ public:
Matrix* World;
/// <summary>
/// The instance drawing state data container. Used for LOD transition handling and previous world transformation matrix updating.
/// The instance drawing state data container. Used for LOD transition handling and previous world transformation matrix updating.
/// </summary>
GeometryDrawStateData* DrawState;
/// <summary>
/// The instance deformation utility.
/// </summary>
MeshDeformation* Deformation;
union
{
struct
@@ -181,11 +186,6 @@ public:
/// The skinning.
/// </summary>
SkinnedMeshDrawData* Skinning;
/// <summary>
/// The blend shapes.
/// </summary>
BlendShapesInstance* BlendShapes;
};
struct

View File

@@ -0,0 +1,166 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "MeshDeformation.h"
#include "Engine/Graphics/Models/MeshBase.h"
#include "Engine/Profiler/ProfilerCPU.h"
struct Key
{
union
{
struct
{
uint8 Lod;
uint8 BufferType;
uint16 MeshIndex;
};
uint32 Value;
};
};
FORCE_INLINE static uint32 GetKey(int32 lodIndex, int32 meshIndex, MeshBufferType type)
{
Key key;
key.Value = 0;
key.Lod = (uint8)lodIndex;
key.BufferType = (uint8)type;
key.MeshIndex = (uint16)meshIndex;
return key.Value;
}
void MeshDeformation::GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const
{
const auto key = GetKey(lodIndex, meshIndex, MeshBufferType::Vertex0);
for (MeshDeformationData* deformation : _deformations)
{
if (deformation->Key == key)
{
bounds = deformation->Bounds;
break;
}
}
}
void MeshDeformation::Clear()
{
for (MeshDeformationData* e : _deformations)
Delete(e);
_deformations.Clear();
}
void MeshDeformation::Dirty()
{
for (MeshDeformationData* deformation : _deformations)
deformation->Dirty = true;
}
void MeshDeformation::Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type)
{
const auto key = GetKey(lodIndex, meshIndex, type);
for (MeshDeformationData* deformation : _deformations)
{
if (deformation->Key == key)
{
deformation->Dirty = true;
break;
}
}
}
void MeshDeformation::Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type, const BoundingBox& bounds)
{
const auto key = GetKey(lodIndex, meshIndex, type);
for (MeshDeformationData* deformation : _deformations)
{
if (deformation->Key == key)
{
deformation->Dirty = true;
deformation->Bounds = bounds;
break;
}
}
}
void MeshDeformation::AddDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function<void(const MeshBase* mesh, MeshDeformationData& deformation)>& deformer)
{
const auto key = GetKey(lodIndex, meshIndex, type);
_deformers[key].Bind(deformer);
Dirty(lodIndex, meshIndex, type);
}
void MeshDeformation::RemoveDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function<void(const MeshBase* mesh, MeshDeformationData& deformation)>& deformer)
{
const auto key = GetKey(lodIndex, meshIndex, type);
_deformers[key].Unbind(deformer);
Dirty(lodIndex, meshIndex, type);
}
void MeshDeformation::RunDeformers(const MeshBase* mesh, MeshBufferType type, GPUBuffer*& vertexBuffer)
{
const auto key = GetKey(mesh->GetLODIndex(), mesh->GetIndex(), type);
if (const auto* e = _deformers.TryGet(key))
{
PROFILE_CPU();
const int32 vertexStride = vertexBuffer->GetStride();
// Get mesh deformation container
MeshDeformationData* deformation = nullptr;
for (int32 i = 0; i < _deformations.Count(); i++)
{
if (_deformations[i]->Key == key)
{
deformation = _deformations[i];
break;
}
}
if (!e->IsBinded())
{
// Auto-recycle unused deformations
if (deformation)
{
_deformations.Remove(deformation);
Delete(deformation);
}
_deformers.Remove(key);
return;
}
if (!deformation)
{
deformation = New<MeshDeformationData>(key, vertexStride);
deformation->VertexBuffer.Data.Resize(vertexBuffer->GetSize());
deformation->Bounds = mesh->GetBox();
_deformations.Add(deformation);
}
if (deformation->Dirty)
{
// Get original mesh vertex buffer data (cached on CPU)
BytesContainer vertexData;
int32 vertexCount;
if (mesh->DownloadDataCPU(type, vertexData, vertexCount))
return;
ASSERT(vertexData.Length() / vertexCount == vertexStride);
// Init dirty range with valid data (use the dirty range from the previous update to be cleared with initial data)
deformation->VertexBuffer.Data.Resize(vertexData.Length());
const uint32 dirtyDataStart = Math::Min<uint32>(deformation->DirtyMinIndex, vertexCount - 1) * vertexStride;
const uint32 dirtyDataLength = Math::Min<uint32>(deformation->DirtyMaxIndex - deformation->DirtyMinIndex + 1, vertexCount) * vertexStride;
Platform::MemoryCopy(deformation->VertexBuffer.Data.Get() + dirtyDataStart, vertexData.Get() + dirtyDataStart, dirtyDataLength);
// Reset dirty state
deformation->DirtyMinIndex = MAX_uint32 - 1;
deformation->DirtyMaxIndex = 0;
deformation->Dirty = false;
// Run deformers
(*e)(mesh, *deformation);
// Upload modified vertex data to the GPU
deformation->VertexBuffer.Flush();
}
// Override vertex buffer for draw call
vertexBuffer = deformation->VertexBuffer.GetBuffer();
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Math/BoundingBox.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Graphics/Models/Types.h"
#include "Engine/Graphics/DynamicBuffer.h"
/// <summary>
/// The mesh deformation data container.
/// </summary>
struct MeshDeformationData
{
uint64 Key;
uint32 DirtyMinIndex = 0;
uint32 DirtyMaxIndex = MAX_uint32 - 1;
bool Dirty = true;
BoundingBox Bounds;
DynamicVertexBuffer VertexBuffer;
MeshDeformationData(uint64 key, uint32 stride)
: Key(key)
, VertexBuffer(0, stride, TEXT("MeshDeformation"))
{
}
NON_COPYABLE(MeshDeformationData);
~MeshDeformationData()
{
}
};
/// <summary>
/// The mesh deformation utility for editing or morphing models dynamically at runtime (eg. via Blend Shapes or Cloth).
/// </summary>
class FLAXENGINE_API MeshDeformation
{
private:
Dictionary<uint32, Delegate<const MeshBase*, MeshDeformationData&>> _deformers;
Array<MeshDeformationData*> _deformations;
public:
~MeshDeformation()
{
Clear();
}
void GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const;
void Clear();
void Dirty();
void Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type);
void Dirty(int32 lodIndex, int32 meshIndex, MeshBufferType type, const BoundingBox& bounds);
void AddDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function<void(const MeshBase* mesh, MeshDeformationData& deformation)>& deformer);
void RemoveDeformer(int32 lodIndex, int32 meshIndex, MeshBufferType type, const Function<void(const MeshBase* mesh, MeshDeformationData& deformation)>& deformer);
void RunDeformers(const MeshBase* mesh, MeshBufferType type, GPUBuffer*& vertexBuffer);
};

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "ModelLOD.h"
#include "MeshDeformation.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Graphics/GPUDevice.h"
@@ -108,9 +109,9 @@ BoundingBox ModelLOD::GetBox(const Matrix& world) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[j];
const auto& mesh = Meshes[meshIndex];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
@@ -122,14 +123,17 @@ BoundingBox ModelLOD::GetBox(const Matrix& world) const
return BoundingBox(min, max);
}
BoundingBox ModelLOD::GetBox(const Transform& transform) const
BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[j];
mesh.GetBox().GetCorners(corners);
const auto& mesh = Meshes[meshIndex];
BoundingBox box = mesh.GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
@@ -144,9 +148,9 @@ BoundingBox ModelLOD::GetBox() const
{
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
Meshes[j].GetBox().GetCorners(corners);
Meshes[meshIndex].GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
min = Vector3::Min(min, corners[i]);

View File

@@ -109,8 +109,9 @@ public:
/// Get model bounding box in transformed world.
/// </summary>
/// <param name="transform">The instance transformation.</param>
/// <param name="deformation">The meshes deformation container (optional).</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Transform& transform) const;
BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const;
/// <summary>
/// Gets the bounding box combined for all meshes in this model LOD.

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "SkinnedMesh.h"
#include "MeshDeformation.h"
#include "ModelInstanceEntry.h"
#include "Engine/Content/Assets/Material.h"
#include "Engine/Content/Assets/SkinnedModel.h"
@@ -172,26 +173,14 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info,
// Setup draw call
DrawCall drawCall;
drawCall.Geometry.IndexBuffer = _indexBuffer;
BlendShapesInstance::MeshInstance* blendShapeMeshInstance;
if (info.BlendShapes && info.BlendShapes->Meshes.TryGet(this, blendShapeMeshInstance) && blendShapeMeshInstance->IsUsed)
{
// Use modified vertex buffer from the blend shapes
if (blendShapeMeshInstance->IsDirty)
{
blendShapeMeshInstance->VertexBuffer.Flush();
blendShapeMeshInstance->IsDirty = false;
}
drawCall.Geometry.VertexBuffers[0] = blendShapeMeshInstance->VertexBuffer.GetBuffer();
}
else
{
drawCall.Geometry.VertexBuffers[0] = _vertexBuffer;
}
drawCall.Geometry.VertexBuffers[0] = _vertexBuffer;
drawCall.Geometry.VertexBuffers[1] = nullptr;
drawCall.Geometry.VertexBuffers[2] = nullptr;
drawCall.Geometry.VertexBuffersOffsets[0] = 0;
drawCall.Geometry.VertexBuffersOffsets[1] = 0;
drawCall.Geometry.VertexBuffersOffsets[2] = 0;
if (info.Deformation)
info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]);
drawCall.Draw.StartIndex = 0;
drawCall.Draw.IndicesCount = _triangles * 3;
drawCall.InstanceCount = 1;
@@ -232,26 +221,14 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI
// Setup draw call
DrawCall drawCall;
drawCall.Geometry.IndexBuffer = _indexBuffer;
BlendShapesInstance::MeshInstance* blendShapeMeshInstance;
if (info.BlendShapes && info.BlendShapes->Meshes.TryGet(this, blendShapeMeshInstance) && blendShapeMeshInstance->IsUsed)
{
// Use modified vertex buffer from the blend shapes
if (blendShapeMeshInstance->IsDirty)
{
blendShapeMeshInstance->VertexBuffer.Flush();
blendShapeMeshInstance->IsDirty = false;
}
drawCall.Geometry.VertexBuffers[0] = blendShapeMeshInstance->VertexBuffer.GetBuffer();
}
else
{
drawCall.Geometry.VertexBuffers[0] = _vertexBuffer;
}
drawCall.Geometry.VertexBuffers[0] = _vertexBuffer;
drawCall.Geometry.VertexBuffers[1] = nullptr;
drawCall.Geometry.VertexBuffers[2] = nullptr;
drawCall.Geometry.VertexBuffersOffsets[0] = 0;
drawCall.Geometry.VertexBuffersOffsets[1] = 0;
drawCall.Geometry.VertexBuffersOffsets[2] = 0;
if (info.Deformation)
info.Deformation->RunDeformers(this, MeshBufferType::Vertex0, drawCall.Geometry.VertexBuffers[0]);
drawCall.Draw.StartIndex = 0;
drawCall.Draw.IndicesCount = _triangles * 3;
drawCall.InstanceCount = 1;

View File

@@ -1,17 +1,12 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "SkinnedModelLOD.h"
#include "MeshDeformation.h"
#include "Engine/Core/Log.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Serialization/MemoryReadStream.h"
bool SkinnedModelLOD::HasAnyMeshInitialized() const
{
// Note: we initialize all meshes at once so the last one can be used to check it.
return Meshes.HasItems() && Meshes.Last().IsInitialized();
}
bool SkinnedModelLOD::Load(MemoryReadStream& stream)
{
// Load LOD for each mesh
@@ -147,6 +142,27 @@ BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
BoundingBox box = mesh.GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const
{
// Find minimum and maximum points of the mesh

View File

@@ -16,6 +16,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject
friend SkinnedModel;
private:
SkinnedModel* _model = nullptr;
int32 _lodIndex = 0;
public:
/// <summary>
@@ -28,10 +29,22 @@ public:
/// </summary>
API_FIELD(ReadOnly) Array<SkinnedMesh> Meshes;
/// <summary>
/// Gets the model LOD index.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 GetLODIndex() const
{
return _lodIndex;
}
/// <summary>
/// Determines whether any mesh has been initialized.
/// </summary>
bool HasAnyMeshInitialized() const;
bool HasAnyMeshInitialized() const
{
// Note: we initialize all meshes at once so the last one can be used to check it.
return Meshes.HasItems() && Meshes.Last().IsInitialized();
}
public:
/// <summary>
@@ -81,6 +94,14 @@ public:
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Matrix& world) const;
/// <summary>
/// Get model bounding box in transformed world.
/// </summary>
/// <param name="transform">The instance transformation.</param>
/// <param name="deformation">The meshes deformation container (optional).</param>
/// <returns>Bounding box</returns>
BoundingBox GetBox(const Transform& transform, const MeshDeformation* deformation = nullptr) const;
/// <summary>
/// Get model bounding box in transformed world for given instance buffer for only one mesh
/// </summary>

View File

@@ -11,12 +11,16 @@
class Model;
class SkinnedModel;
class ModelBase;
class Mesh;
class SkinnedMesh;
class MeshBase;
class ModelData;
class ModelInstanceEntries;
class Model;
class SkinnedModel;
class MeshDeformation;
class GPUContext;
struct RenderView;
/// <summary>

View File

@@ -12,6 +12,7 @@
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Serialization/Serialization.h"
@@ -34,6 +35,12 @@ AnimatedModel::AnimatedModel(const SpawnParams& params)
AnimationGraph.Loaded.Bind<AnimatedModel, &AnimatedModel::OnGraphLoaded>(this);
}
AnimatedModel::~AnimatedModel()
{
if (_deformation)
Delete(_deformation);
}
void AnimatedModel::ResetAnimation()
{
GraphInstance.ClearState();
@@ -56,11 +63,6 @@ void AnimatedModel::UpdateAnimation()
// Request an animation update
Animations::AddToUpdate(this);
}
else
{
// Allow to use blend shapes without animation graph assigned
_blendShapes.Update(SkinnedModel.Get());
}
}
void AnimatedModel::SetupSkinningData()
@@ -292,7 +294,7 @@ void AnimatedModel::SetParameterValue(const Guid& id, const Variant& value)
float AnimatedModel::GetBlendShapeWeight(const StringView& name)
{
for (auto& e : _blendShapes.Weights)
for (auto& e : _blendShapeWeights)
{
if (e.First == name)
return e.Second;
@@ -302,36 +304,130 @@ float AnimatedModel::GetBlendShapeWeight(const StringView& name)
void AnimatedModel::SetBlendShapeWeight(const StringView& name, float value)
{
const auto* model = SkinnedModel.Get();
CHECK(model);
model->WaitForLoaded();
value = Math::Clamp(value, -1.0f, 1.0f);
for (int32 i = 0; i < _blendShapes.Weights.Count(); i++)
const bool isZero = Math::IsZero(value);
if (!_deformation && !isZero)
_deformation = New<MeshDeformation>();
Function<void(const MeshBase*, MeshDeformationData&)> deformer;
deformer.Bind<AnimatedModel, &AnimatedModel::RunBlendShapeDeformer>(this);
for (int32 i = 0; i < _blendShapeWeights.Count(); i++)
{
auto& e = _blendShapes.Weights[i];
auto& e = _blendShapeWeights[i];
if (e.First == name)
{
if (Math::IsZero(value))
if (isZero)
{
_blendShapes.WeightsDirty = true;
_blendShapes.Weights.RemoveAt(i);
_blendShapeWeights.RemoveAt(i);
// Remove deformers for meshes using this blend shape
for (const auto& lod : model->LODs)
{
for (const auto& mesh : lod.Meshes)
{
for (const auto& blendShape : mesh.BlendShapes)
{
if (blendShape.Name == name)
{
for (int32 j = 0; j < _blendShapeMeshes.Count(); j++)
{
auto& blendShapeMesh = _blendShapeMeshes[j];
if (blendShapeMesh.LODIndex == mesh.GetLODIndex() && blendShapeMesh.MeshIndex == mesh.GetIndex())
{
blendShapeMesh.Usages--;
if (blendShapeMesh.Usages == 0)
{
_deformation->RemoveDeformer(blendShapeMesh.LODIndex, blendShapeMesh.MeshIndex, MeshBufferType::Vertex0, deformer);
_blendShapeMeshes.RemoveAt(j);
}
break;
}
}
break;
}
}
}
}
}
else if (Math::NotNearEqual(e.Second, value))
{
_blendShapes.WeightsDirty = true;
// Update blend shape weight
e.Second = value;
// Dirty deformers for meshes using this blend shape
for (const auto& lod : model->LODs)
{
for (const auto& mesh : lod.Meshes)
{
for (const auto& blendShape : mesh.BlendShapes)
{
if (blendShape.Name == name)
{
_deformation->Dirty(mesh.GetLODIndex(), mesh.GetIndex(), MeshBufferType::Vertex0);
break;
}
}
}
}
}
return;
}
}
if (!isZero)
{
auto& e = _blendShapes.Weights.AddOne();
// Add blend shape weight
auto& e = _blendShapeWeights.AddOne();
e.First = name;
e.Second = value;
_blendShapes.WeightsDirty = true;
// Add deformers for meshes using this blend shape
for (const auto& lod : model->LODs)
{
for (const auto& mesh : lod.Meshes)
{
for (const auto& blendShape : mesh.BlendShapes)
{
if (blendShape.Name == name)
{
int32 i = 0;
for (; i < _blendShapeMeshes.Count(); i++)
{
auto& blendShapeMesh = _blendShapeMeshes[i];
if (blendShapeMesh.LODIndex == mesh.GetLODIndex() && blendShapeMesh.MeshIndex == mesh.GetIndex())
{
blendShapeMesh.Usages++;
break;
}
}
if (i == _blendShapeMeshes.Count())
{
auto& blendShapeMesh = _blendShapeMeshes.AddOne();
blendShapeMesh.LODIndex = mesh.GetLODIndex();
blendShapeMesh.MeshIndex = mesh.GetIndex();
blendShapeMesh.Usages = 1;
_deformation->AddDeformer(blendShapeMesh.LODIndex, blendShapeMesh.MeshIndex, MeshBufferType::Vertex0, deformer);
}
break;
}
}
}
}
}
}
void AnimatedModel::ClearBlendShapeWeights()
{
_blendShapes.Clear();
if (_deformation)
{
Function<void(const MeshBase*, MeshDeformationData&)> deformer;
deformer.Bind<AnimatedModel, &AnimatedModel::RunBlendShapeDeformer>(this);
for (auto e : _blendShapeMeshes)
_deformation->RemoveDeformer(e.LODIndex, e.MeshIndex, MeshBufferType::Vertex0, deformer);
}
_blendShapeWeights.Clear();
_blendShapeMeshes.Clear();
}
void AnimatedModel::PlaySlotAnimation(const StringView& slotName, Animation* anim, float speed, float blendInTime, float blendOutTime, int32 loopCount)
@@ -469,6 +565,87 @@ void AnimatedModel::SyncParameters()
}
}
void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationData& deformation)
{
PROFILE_CPU_NAMED("BlendShapes");
auto* skinnedMesh = (const SkinnedMesh*)mesh;
// Estimate the range of the vertices to modify by the currently active blend shapes
uint32 minVertexIndex = MAX_uint32, maxVertexIndex = 0;
bool useNormals = false;
Array<Pair<const BlendShape&, const float>, InlinedAllocation<32>> blendShapes;
for (const BlendShape& blendShape : skinnedMesh->BlendShapes)
{
for (auto& q : _blendShapeWeights)
{
if (q.First == blendShape.Name)
{
const float weight = q.Second * blendShape.Weight;
blendShapes.Add(Pair<const BlendShape&, const float>(blendShape, weight));
minVertexIndex = Math::Min(minVertexIndex, blendShape.MinVertexIndex);
maxVertexIndex = Math::Max(maxVertexIndex, blendShape.MaxVertexIndex);
useNormals |= blendShape.UseNormals;
break;
}
}
}
// Blend all blend shapes
auto vertexCount = (uint32)mesh->GetVertexCount();
auto data = (VB0SkinnedElementType*)deformation.VertexBuffer.Data.Get();
for (const auto& q : blendShapes)
{
// TODO: use SIMD
if (useNormals)
{
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
{
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
Float3 normal = (vertex.Normal.ToFloat3() * 2.0f - 1.0f) + blendShapeVertex.NormalDelta;
vertex.Normal = normal * 0.5f + 0.5f;
}
}
else
{
for (int32 i = 0; i < q.First.Vertices.Count(); i++)
{
const BlendShapeVertex& blendShapeVertex = q.First.Vertices[i];
ASSERT_LOW_LAYER(blendShapeVertex.VertexIndex < vertexCount);
VB0SkinnedElementType& vertex = *(data + blendShapeVertex.VertexIndex);
vertex.Position = vertex.Position + blendShapeVertex.PositionDelta * q.Second;
}
}
}
if (useNormals)
{
// Normalize normal vectors and rebuild tangent frames (tangent frame is in range [-1;1] but packed to [0;1] range)
// TODO: use SIMD
for (uint32 vertexIndex = minVertexIndex; vertexIndex <= maxVertexIndex; vertexIndex++)
{
VB0SkinnedElementType& vertex = *(data + vertexIndex);
Float3 normal = vertex.Normal.ToFloat3() * 2.0f - 1.0f;
normal.Normalize();
vertex.Normal = normal * 0.5f + 0.5f;
Float3 tangent = vertex.Tangent.ToFloat3() * 2.0f - 1.0f;
tangent = tangent - ((tangent | normal) * normal);
tangent.Normalize();
const auto tangentSign = vertex.Tangent.W;
vertex.Tangent = tangent * 0.5f + 0.5f;
vertex.Tangent.W = tangentSign;
}
}
// Mark as dirty to be cleared before next rendering
deformation.DirtyMinIndex = Math::Min(minVertexIndex, deformation.DirtyMinIndex);
deformation.DirtyMaxIndex = Math::Max(maxVertexIndex, deformation.DirtyMaxIndex);
}
void AnimatedModel::BeginPlay(SceneBeginData* data)
{
if (SkinnedModel && SkinnedModel->IsLoaded())
@@ -513,31 +690,26 @@ void AnimatedModel::OnActiveInTreeChanged()
void AnimatedModel::UpdateBounds()
{
auto model = SkinnedModel.Get();
if (CustomBounds.GetSize().LengthSquared() > 0.01f)
{
BoundingBox::Transform(CustomBounds, _transform, _box);
}
else if (SkinnedModel && SkinnedModel->IsLoaded())
else if (model && model->IsLoaded())
{
BoundingBox box = model->LODs[0].GetBox(_transform, _deformation);
if (GraphInstance.NodesPose.Count() != 0)
{
// Per-bone bounds estimated from positions
auto& skeleton = SkinnedModel->Skeleton;
auto& skeleton = model->Skeleton;
const int32 bonesCount = skeleton.Bones.Count();
#define GET_NODE_POS(i) _transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones[i].NodeIndex].GetTranslation())
BoundingBox box(GET_NODE_POS(0));
for (int32 boneIndex = 1; boneIndex < bonesCount; boneIndex++)
box.Merge(GET_NODE_POS(boneIndex));
_box = box;
#undef GET_NODE_POS
}
else
{
_box = SkinnedModel->GetBox(_transform.GetWorld());
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
box.Merge(_transform.LocalToWorld(GraphInstance.NodesPose[skeleton.Bones.Get()[boneIndex].NodeIndex].GetTranslation()));
}
_box = box;
// Apply margin based on model dimensions
const Vector3 modelBoxSize = SkinnedModel->GetBox().GetSize();
const Vector3 modelBoxSize = model->GetBox().GetSize();
const Vector3 center = _box.GetCenter();
const Vector3 sizeHalf = Vector3::Max(_box.GetSize() + modelBoxSize * 0.2f, modelBoxSize) * 0.5f;
_box = BoundingBox(center - sizeHalf, center + sizeHalf);
@@ -595,7 +767,6 @@ void AnimatedModel::OnAnimationUpdated_Async()
}
UpdateBounds();
_blendShapes.Update(SkinnedModel.Get());
}
void AnimatedModel::OnAnimationUpdated_Sync()
@@ -616,23 +787,20 @@ void AnimatedModel::OnAnimationUpdated()
void AnimatedModel::OnSkinnedModelChanged()
{
Entries.Release();
if (SkinnedModel && !SkinnedModel->IsLoaded())
{
UpdateBounds();
GraphInstance.Invalidate();
}
if (_deformation)
_deformation->Clear();
GraphInstance.NodesSkeleton = SkinnedModel;
}
void AnimatedModel::OnSkinnedModelLoaded()
{
Entries.SetupIfInvalid(SkinnedModel);
GraphInstance.Invalidate();
if (_blendShapes.Weights.HasItems())
_blendShapes.WeightsDirty = true;
PreInitSkinningData();
}
@@ -699,7 +867,7 @@ void AnimatedModel::Draw(RenderContext& renderContext)
if (!SkinnedModel || !SkinnedModel->IsLoaded())
return;
if (renderContext.View.Pass == DrawPass::GlobalSDF)
return; // TODO: Animated Model rendering to Global SDF
return;
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
return; // No supported
Matrix world;
@@ -721,9 +889,9 @@ void AnimatedModel::Draw(RenderContext& renderContext)
SkinnedMesh::DrawInfo draw;
draw.Buffer = &Entries;
draw.Skinning = &_skinningData;
draw.BlendShapes = &_blendShapes;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
draw.DrawModes = DrawModes & renderContext.View.GetShadowsDrawPassMask(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
@@ -764,9 +932,9 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch)
SkinnedMesh::DrawInfo draw;
draw.Buffer = &Entries;
draw.Skinning = &_skinningData;
draw.BlendShapes = &_blendShapes;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
draw.DrawModes = DrawModes;
draw.Bounds = _sphere;
draw.Bounds.Center -= renderContext.View.Origin;
@@ -957,6 +1125,13 @@ bool AnimatedModel::GetMeshData(const MeshReference& mesh, MeshBufferType type,
return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count);
}
MeshDeformation* AnimatedModel::GetMeshDeformation() const
{
if (!_deformation)
_deformation = New<MeshDeformation>();
return _deformation;
}
void AnimatedModel::OnDeleteObject()
{
// Ensure this object is no longer referenced for anim update
@@ -965,14 +1140,6 @@ void AnimatedModel::OnDeleteObject()
ModelInstanceActor::OnDeleteObject();
}
void AnimatedModel::OnTransformChanged()
{
// Base
ModelInstanceActor::OnTransformChanged();
UpdateBounds();
}
void AnimatedModel::WaitForModelLoad()
{
if (SkinnedModel)

View File

@@ -17,7 +17,7 @@ class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
{
DECLARE_SCENE_OBJECT(AnimatedModel);
friend class AnimationsSystem;
public:
/// <summary>
/// Describes the animation graph updates frequency for the animated model.
/// </summary>
@@ -55,16 +55,27 @@ public:
};
private:
struct BlendShapeMesh
{
uint16 LODIndex;
uint16 MeshIndex;
uint32 Usages;
};
GeometryDrawStateData _drawState;
SkinnedMeshDrawData _skinningData;
AnimationUpdateMode _actualMode;
uint32 _counter;
Real _lastMinDstSqr;
uint64 _lastUpdateFrame;
BlendShapesInstance _blendShapes;
mutable MeshDeformation* _deformation = nullptr;
ScriptingObjectReference<AnimatedModel> _masterPose;
Array<Pair<String, float>> _blendShapeWeights;
Array<BlendShapeMesh> _blendShapeMeshes;
public:
~AnimatedModel();
/// <summary>
/// The skinned model asset used for rendering.
/// </summary>
@@ -291,7 +302,7 @@ public:
API_FUNCTION() void SetBlendShapeWeight(const StringView& name, float value);
/// <summary>
/// Clears the weights of the blend shapes (disabled any used blend shapes).
/// Clears the weights of the blend shapes (disables any used blend shapes).
/// </summary>
API_FUNCTION() void ClearBlendShapeWeights();
@@ -346,9 +357,9 @@ public:
private:
void ApplyRootMotion(const Transform& rootMotionDelta);
void SyncParameters();
void RunBlendShapeDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation);
void Update();
void UpdateBounds();
void UpdateSockets();
void OnAnimationUpdated_Async();
void OnAnimationUpdated_Sync();
@@ -375,6 +386,8 @@ public:
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override;
void UpdateBounds() override;
MeshDeformation* GetMeshDeformation() const override;
void OnDeleteObject() override;
protected:
@@ -384,6 +397,5 @@ protected:
void OnEnable() override;
void OnDisable() override;
void OnActiveInTreeChanged() override;
void OnTransformChanged() override;
void WaitForModelLoad() override;
};

View File

@@ -332,6 +332,7 @@ void Camera::Draw(RenderContext& renderContext)
draw.Buffer = &_previewModelBuffer;
draw.World = &world;
draw.DrawState = &drawState;
draw.Deformation = nullptr;
draw.Lightmap = nullptr;
draw.LightmapUVs = nullptr;
draw.Flags = StaticFlags::Transform;

View File

@@ -59,6 +59,14 @@ void ModelInstanceActor::OnLayerChanged()
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);
}
void ModelInstanceActor::OnTransformChanged()
{
// Base
Actor::OnTransformChanged();
UpdateBounds();
}
void ModelInstanceActor::OnEnable()
{
GetSceneRendering()->AddActor(this, _sceneRenderingKey);

View File

@@ -97,7 +97,7 @@ public:
{
return false;
}
/// <summary>
/// Extracts mesh buffer data from CPU. Might be cached internally (eg. by Model/SkinnedModel).
/// </summary>
@@ -105,18 +105,33 @@ public:
/// <param name="type">Buffer type</param>
/// <param name="result">The result data</param>
/// <param name="count">The amount of items inside the result buffer.</param>
/// <returns>True if failed, otherwise false</returns>
/// <returns>True if failed, otherwise false.</returns>
virtual bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const
{
return true;
}
/// <summary>
/// Gets the mesh deformation utility for this model instance (optional).
/// </summary>
/// <returns>Model deformation utility or null if not supported.</returns>
virtual MeshDeformation* GetMeshDeformation() const
{
return nullptr;
}
/// <summary>
/// Updates the model bounds (eg. when mesh has applied significant deformation).
/// </summary>
virtual void UpdateBounds() = 0;
protected:
virtual void WaitForModelLoad();
public:
// [Actor]
void OnLayerChanged() override;
void OnTransformChanged() override;
protected:
// [Actor]

View File

@@ -341,6 +341,11 @@ void SplineModel::OnParentChanged()
OnSplineUpdated();
}
void SplineModel::UpdateBounds()
{
OnSplineUpdated();
}
bool SplineModel::HasContentLoaded() const
{
return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded();
@@ -489,14 +494,6 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DrawModes |= DrawPass::GlobalSurfaceAtlas;
}
void SplineModel::OnTransformChanged()
{
// Base
ModelInstanceActor::OnTransformChanged();
OnSplineUpdated();
}
void SplineModel::OnActiveInTreeChanged()
{
// Base

View File

@@ -115,9 +115,9 @@ public:
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
void OnParentChanged() override;
void UpdateBounds() override;
protected:
// [ModelInstanceActor]
void OnTransformChanged() override;
void OnActiveInTreeChanged() override;
};

View File

@@ -7,6 +7,7 @@
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Scene/Scene.h"
@@ -36,6 +37,8 @@ StaticModel::~StaticModel()
{
for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++)
SAFE_DELETE_GPU_RESOURCE(_vertexColorsBuffer[lodIndex]);
if (_deformation)
Delete(_deformation);
}
float StaticModel::GetScaleInLightmap() const
@@ -230,6 +233,8 @@ void StaticModel::OnModelChanged()
Entries.Release();
if (Model && !Model->IsLoaded())
UpdateBounds();
if (_deformation)
_deformation->Clear();
else if (!Model && _sceneRenderingKey != -1)
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
}
@@ -265,11 +270,12 @@ void StaticModel::OnModelResidencyChanged()
void StaticModel::UpdateBounds()
{
if (Model && Model->IsLoaded())
auto model = Model.Get();
if (model && model->IsLoaded())
{
Transform transform = _transform;
transform.Scale *= _boundsScale;
_box = Model->GetBox(transform);
_box = model->LODs[0].GetBox(transform, _deformation);
}
else
{
@@ -339,6 +345,7 @@ void StaticModel::Draw(RenderContext& renderContext)
draw.Buffer = &Entries;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex);
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
@@ -372,6 +379,7 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch)
draw.Buffer = &Entries;
draw.World = &world;
draw.DrawState = &_drawState;
draw.Deformation = _deformation;
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(Lightmap.TextureIndex);
draw.LightmapUVs = &Lightmap.UVsArea;
draw.Flags = _staticFlags;
@@ -600,12 +608,11 @@ bool StaticModel::GetMeshData(const MeshReference& mesh, MeshBufferType type, By
return lod.Meshes[Math::Min(mesh.MeshIndex, lod.Meshes.Count() - 1)].DownloadDataCPU(type, result, count);
}
void StaticModel::OnTransformChanged()
MeshDeformation* StaticModel::GetMeshDeformation() const
{
// Base
ModelInstanceActor::OnTransformChanged();
UpdateBounds();
if (!_deformation)
_deformation = New<MeshDeformation>();
return _deformation;
}
void StaticModel::OnEnable()

View File

@@ -25,6 +25,7 @@ private:
Array<Color32> _vertexColorsData[MODEL_MAX_LODS];
GPUBuffer* _vertexColorsBuffer[MODEL_MAX_LODS];
Model* _residencyChangedModel = nullptr;
mutable MeshDeformation* _deformation = nullptr;
public:
/// <summary>
@@ -155,7 +156,6 @@ private:
void OnModelChanged();
void OnModelLoaded();
void OnModelResidencyChanged();
void UpdateBounds();
void FlushVertexColors();
public:
@@ -169,10 +169,11 @@ public:
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
bool GetMeshData(const MeshReference& mesh, MeshBufferType type, BytesContainer& result, int32& count) const override;
MeshDeformation* GetMeshDeformation() const override;
void UpdateBounds() override;
protected:
// [ModelInstanceActor]
void OnTransformChanged() override;
void OnEnable() override;
void OnDisable() override;
void WaitForModelLoad() override;