Add MeshDeformation utility for generic meshes vertices morphing (eg. via Blend Shapes or Cloth)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
166
Source/Engine/Graphics/Models/MeshDeformation.cpp
Normal file
166
Source/Engine/Graphics/Models/MeshDeformation.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
58
Source/Engine/Graphics/Models/MeshDeformation.h
Normal file
58
Source/Engine/Graphics/Models/MeshDeformation.h
Normal 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);
|
||||
};
|
||||
@@ -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]);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user