You're breathtaking!
This commit is contained in:
180
Source/Engine/Graphics/Models/BlendShape.cpp
Normal file
180
Source/Engine/Graphics/Models/BlendShape.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) 2012-2020 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)
|
||||
, 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;
|
||||
const int32 vertexCount = mesh->GetVertexCount();
|
||||
|
||||
// Get skinned mesh vertex buffer data (original, cached on CPU)
|
||||
BytesContainer vertexBuffer;
|
||||
if (mesh->DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffer))
|
||||
{
|
||||
// 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, 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;
|
||||
Vector3 normal = (vertex.Normal.ToVector3() * 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);
|
||||
|
||||
Vector3 normal = vertex.Normal.ToVector3() * 2.0f - 1.0f;
|
||||
normal.Normalize();
|
||||
vertex.Normal = normal * 0.5f + 0.5f;
|
||||
|
||||
Vector3 tangent = vertex.Tangent.ToVector3() * 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;
|
||||
}
|
||||
139
Source/Engine/Graphics/Models/BlendShape.h
Normal file
139
Source/Engine/Graphics/Models/BlendShape.h
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#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.
|
||||
/// </summary>
|
||||
struct BlendShapeVertex
|
||||
{
|
||||
/// <summary>
|
||||
/// The position offset.
|
||||
/// </summary>
|
||||
Vector3 PositionDelta;
|
||||
|
||||
/// <summary>
|
||||
/// The normal vector offset (tangent Z).
|
||||
/// </summary>
|
||||
Vector3 NormalDelta;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the vertex in the mesh to blend.
|
||||
/// </summary>
|
||||
uint32 VertexIndex;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<BlendShapeVertex>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Data container for the blend shape.
|
||||
/// </summary>
|
||||
class BlendShape
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The name of the blend shape.
|
||||
/// </summary>
|
||||
String Name;
|
||||
|
||||
/// <summary>
|
||||
/// The weight of the blend shape.
|
||||
/// </summary>
|
||||
float Weight;
|
||||
|
||||
/// <summary>
|
||||
/// True if blend shape contains deltas for normal vectors of the mesh, otherwise calculations related to tangent space can be skipped.
|
||||
/// </summary>
|
||||
bool UseNormals;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum index of the vertex in all blend shape vertices. Used to optimize blend shapes updating.
|
||||
/// </summary>
|
||||
uint32 MinVertexIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum index of the vertex in all blend shape vertices. Used to optimize blend shapes updating.
|
||||
/// </summary>
|
||||
uint32 MaxVertexIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The list of shape vertices.
|
||||
/// </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();
|
||||
};
|
||||
73
Source/Engine/Graphics/Models/CollisionProxy.h
Normal file
73
Source/Engine/Graphics/Models/CollisionProxy.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Math/Ray.h"
|
||||
#include "Engine/Core/Math/Triangle.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
/// <summary>
|
||||
/// Helper container used for detailed triangle mesh intersections tests.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API CollisionProxy
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The triangles.
|
||||
/// </summary>
|
||||
Array<Triangle> Triangles;
|
||||
|
||||
public:
|
||||
|
||||
FORCE_INLINE bool HasData() const
|
||||
{
|
||||
return Triangles.HasItems();
|
||||
}
|
||||
|
||||
template<typename IndexType>
|
||||
void Init(uint32 vertices, uint32 triangles, Vector3* positions, IndexType* indices)
|
||||
{
|
||||
Triangles.Clear();
|
||||
Triangles.EnsureCapacity(triangles, false);
|
||||
|
||||
IndexType* it = indices;
|
||||
for (uint32 i = 0; i < triangles; i++)
|
||||
{
|
||||
auto i0 = *(it++);
|
||||
auto i1 = *(it++);
|
||||
auto i2 = *(it++);
|
||||
|
||||
if (i0 < vertices && i1 < vertices && i2 < vertices)
|
||||
{
|
||||
Triangles.Add({ positions[i0], positions[i1], positions[i2] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
Triangles.Clear();
|
||||
}
|
||||
|
||||
bool Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal) const
|
||||
{
|
||||
// TODO: use SIMD
|
||||
for (int32 i = 0; i < Triangles.Count(); i++)
|
||||
{
|
||||
Triangle triangle = Triangles[i];
|
||||
|
||||
Vector3::Transform(triangle.V0, world, triangle.V0);
|
||||
Vector3::Transform(triangle.V1, world, triangle.V1);
|
||||
Vector3::Transform(triangle.V2, world, triangle.V2);
|
||||
|
||||
if (triangle.Intersects(ray, distance, normal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
23
Source/Engine/Graphics/Models/Config.h
Normal file
23
Source/Engine/Graphics/Models/Config.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Config.h"
|
||||
|
||||
// The maximum allowed amount of material slots per model resource
|
||||
#define MAX_MATERIAL_SLOTS 4096
|
||||
|
||||
// Maximum amount of levels of detail for the model
|
||||
#define MODEL_MAX_LODS 6
|
||||
|
||||
// Maximum amount of meshes per model LOD
|
||||
#define MODEL_MAX_MESHES 4096
|
||||
|
||||
// Enable/disable precise mesh collision testing (with in-build vertex buffer caching, this will increase memory usage)
|
||||
#define USE_PRECISE_MESH_INTERSECTS (USE_EDITOR)
|
||||
|
||||
// Defines the maximum amount of bones affecting every vertex of the skinned mesh
|
||||
#define MAX_BONES_PER_VERTEX 4
|
||||
|
||||
// Defines the maximum allowed amount of skeleton bones to be used with skinned model
|
||||
#define MAX_BONES_PER_MODEL 256
|
||||
41
Source/Engine/Graphics/Models/MaterialSlot.h
Normal file
41
Source/Engine/Graphics/Models/MaterialSlot.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/Assets/MaterialBase.h"
|
||||
#include "Engine/Graphics/Enums.h"
|
||||
|
||||
/// <summary>
|
||||
/// The material slot descriptor that specifies how to render geometry using it.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API MaterialSlot : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialSlot, PersistentScriptingObject);
|
||||
|
||||
/// <summary>
|
||||
/// The material to use for rendering.
|
||||
/// </summary>
|
||||
API_FIELD() AssetReference<MaterialBase> Material;
|
||||
|
||||
/// <summary>
|
||||
/// The shadows casting mode by this visual element.
|
||||
/// </summary>
|
||||
API_FIELD() ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
|
||||
|
||||
/// <summary>
|
||||
/// The slot name.
|
||||
/// </summary>
|
||||
API_FIELD() String Name;
|
||||
|
||||
public:
|
||||
|
||||
MaterialSlot(const MaterialSlot& other)
|
||||
: MaterialSlot()
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
CRASH; // Not used
|
||||
#endif
|
||||
}
|
||||
};
|
||||
765
Source/Engine/Graphics/Models/Mesh.cpp
Normal file
765
Source/Engine/Graphics/Models/Mesh.cpp
Normal file
@@ -0,0 +1,765 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Mesh.h"
|
||||
#include "ModelInstanceEntry.h"
|
||||
#include "Engine/Content/Assets/Material.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
|
||||
|
||||
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices)
|
||||
{
|
||||
Unload();
|
||||
|
||||
// Setup GPU resources
|
||||
_model->LODs[_lodIndex]._verticesCount -= _vertices;
|
||||
const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices);
|
||||
if (!failed)
|
||||
{
|
||||
_model->LODs[_lodIndex]._verticesCount += _vertices;
|
||||
|
||||
// Calculate mesh bounds
|
||||
SetBounds(BoundingBox::FromPoints((Vector3*)vb0, vertexCount));
|
||||
|
||||
// Send event (actors using this model can update bounds, etc.)
|
||||
_model->onLoaded();
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
bool Mesh::UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices)
|
||||
{
|
||||
// Cache data
|
||||
uint32 indicesCount = triangleCount * 3;
|
||||
uint32 ibStride = use16BitIndices ? sizeof(uint16) : sizeof(uint32);
|
||||
|
||||
// Create index buffer
|
||||
GPUBuffer* indexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty);
|
||||
if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib)))
|
||||
{
|
||||
Delete(indexBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: update collision proxy
|
||||
|
||||
// Initialize
|
||||
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
|
||||
_indexBuffer = indexBuffer;
|
||||
_triangles = triangleCount;
|
||||
_use16BitIndexBuffer = use16BitIndices;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Mesh::Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs)
|
||||
{
|
||||
_model = model;
|
||||
_lodIndex = lodIndex;
|
||||
_index = index;
|
||||
_materialSlotIndex = materialSlotIndex;
|
||||
_use16BitIndexBuffer = false;
|
||||
_hasLightmapUVs = hasLightmapUVs;
|
||||
_box = box;
|
||||
_sphere = sphere;
|
||||
_vertices = 0;
|
||||
_triangles = 0;
|
||||
_vertexBuffers[0] = nullptr;
|
||||
_vertexBuffers[1] = nullptr;
|
||||
_vertexBuffers[2] = nullptr;
|
||||
_indexBuffer = nullptr;
|
||||
}
|
||||
|
||||
Mesh::~Mesh()
|
||||
{
|
||||
// Release buffers
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]);
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]);
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[2]);
|
||||
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
|
||||
}
|
||||
|
||||
void Mesh::SetMaterialSlotIndex(int32 value)
|
||||
{
|
||||
if (value < 0 || value >= _model->MaterialSlots.Count())
|
||||
{
|
||||
LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count());
|
||||
return;
|
||||
}
|
||||
|
||||
_materialSlotIndex = value;
|
||||
}
|
||||
|
||||
bool Mesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* vb2, void* ib, bool use16BitIndexBuffer)
|
||||
{
|
||||
// Cache data
|
||||
uint32 indicesCount = triangles * 3;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
|
||||
GPUBuffer* vertexBuffer0 = nullptr;
|
||||
GPUBuffer* vertexBuffer1 = nullptr;
|
||||
GPUBuffer* vertexBuffer2 = nullptr;
|
||||
GPUBuffer* indexBuffer = nullptr;
|
||||
|
||||
// Create vertex buffer 0
|
||||
#if GPU_ENABLE_RESOURCE_NAMING
|
||||
vertexBuffer0 = GPUDevice::Instance->CreateBuffer(GetModel()->ToString() + TEXT(".VB0"));
|
||||
#else
|
||||
vertexBuffer0 = GPUDevice::Instance->CreateBuffer(String::Empty);
|
||||
#endif
|
||||
if (vertexBuffer0->Init(GPUBufferDescription::Vertex(sizeof(VB0ElementType), vertices, vb0)))
|
||||
goto ERROR_LOAD_END;
|
||||
|
||||
// Create vertex buffer 1
|
||||
#if GPU_ENABLE_RESOURCE_NAMING
|
||||
vertexBuffer1 = GPUDevice::Instance->CreateBuffer(GetModel()->ToString() + TEXT(".VB1"));
|
||||
#else
|
||||
vertexBuffer1 = GPUDevice::Instance->CreateBuffer(String::Empty);
|
||||
#endif
|
||||
if (vertexBuffer1->Init(GPUBufferDescription::Vertex(sizeof(VB1ElementType), vertices, vb1)))
|
||||
goto ERROR_LOAD_END;
|
||||
|
||||
// Create vertex buffer 2
|
||||
if (vb2)
|
||||
{
|
||||
#if GPU_ENABLE_RESOURCE_NAMING
|
||||
vertexBuffer2 = GPUDevice::Instance->CreateBuffer(GetModel()->ToString() + TEXT(".VB2"));
|
||||
#else
|
||||
vertexBuffer2 = GPUDevice::Instance->CreateBuffer(String::Empty);
|
||||
#endif
|
||||
if (vertexBuffer2->Init(GPUBufferDescription::Vertex(sizeof(VB2ElementType), vertices, vb2)))
|
||||
goto ERROR_LOAD_END;
|
||||
}
|
||||
|
||||
// Create index buffer
|
||||
#if GPU_ENABLE_RESOURCE_NAMING
|
||||
indexBuffer = GPUDevice::Instance->CreateBuffer(GetModel()->ToString() + TEXT(".IB"));
|
||||
#else
|
||||
indexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty);
|
||||
#endif
|
||||
if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib)))
|
||||
goto ERROR_LOAD_END;
|
||||
|
||||
// Init collision proxy
|
||||
#if USE_PRECISE_MESH_INTERSECTS
|
||||
if (!_collisionProxy.HasData())
|
||||
{
|
||||
if (use16BitIndexBuffer)
|
||||
_collisionProxy.Init<uint16>(vertices, triangles, (Vector3*)vb0, (uint16*)ib);
|
||||
else
|
||||
_collisionProxy.Init<uint32>(vertices, triangles, (Vector3*)vb0, (uint32*)ib);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Initialize
|
||||
_vertexBuffers[0] = vertexBuffer0;
|
||||
_vertexBuffers[1] = vertexBuffer1;
|
||||
_vertexBuffers[2] = vertexBuffer2;
|
||||
_indexBuffer = indexBuffer;
|
||||
_triangles = triangles;
|
||||
_vertices = vertices;
|
||||
_use16BitIndexBuffer = use16BitIndexBuffer;
|
||||
|
||||
return false;
|
||||
|
||||
ERROR_LOAD_END:
|
||||
|
||||
SAFE_DELETE_GPU_RESOURCE(vertexBuffer0);
|
||||
SAFE_DELETE_GPU_RESOURCE(vertexBuffer1);
|
||||
SAFE_DELETE_GPU_RESOURCE(vertexBuffer2);
|
||||
SAFE_DELETE_GPU_RESOURCE(indexBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mesh::Unload()
|
||||
{
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]);
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]);
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[2]);
|
||||
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
|
||||
_triangles = 0;
|
||||
_vertices = 0;
|
||||
_use16BitIndexBuffer = false;
|
||||
}
|
||||
|
||||
bool Mesh::Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal) const
|
||||
{
|
||||
// Get bounding box of the mesh bounds transformed by the instance world matrix
|
||||
Vector3 corners[8];
|
||||
GetCorners(corners);
|
||||
Vector3 tmp;
|
||||
Vector3::Transform(corners[0], world, tmp);
|
||||
Vector3 min = tmp;
|
||||
Vector3 max = tmp;
|
||||
for (int32 i = 1; i < 8; i++)
|
||||
{
|
||||
Vector3::Transform(corners[i], world, tmp);
|
||||
min = Vector3::Min(min, tmp);
|
||||
max = Vector3::Max(max, tmp);
|
||||
}
|
||||
const BoundingBox transformedBox(min, max);
|
||||
|
||||
// Test ray on box
|
||||
#if USE_PRECISE_MESH_INTERSECTS
|
||||
if (transformedBox.Intersects(ray, distance))
|
||||
{
|
||||
// Use exact test on raw geometry
|
||||
return _collisionProxy.Intersects(ray, world, distance, normal);
|
||||
}
|
||||
|
||||
distance = 0;
|
||||
normal = Vector3::Up;
|
||||
return false;
|
||||
#else
|
||||
return transformedBox.Intersects(ray, distance, normal);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Mesh::GetDrawCallGeometry(DrawCall& drawCall)
|
||||
{
|
||||
drawCall.Geometry.IndexBuffer = _indexBuffer;
|
||||
drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0];
|
||||
drawCall.Geometry.VertexBuffers[1] = _vertexBuffers[1];
|
||||
drawCall.Geometry.VertexBuffers[2] = _vertexBuffers[2];
|
||||
drawCall.Geometry.VertexBuffersOffsets[0] = 0;
|
||||
drawCall.Geometry.VertexBuffersOffsets[1] = 0;
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = 0;
|
||||
drawCall.Geometry.StartIndex = 0;
|
||||
drawCall.Geometry.IndicesCount = _triangles * 3;
|
||||
}
|
||||
|
||||
void Mesh::Render(GPUContext* context) const
|
||||
{
|
||||
ASSERT(IsInitialized());
|
||||
|
||||
context->BindVB(ToSpan((GPUBuffer**)_vertexBuffers, 3));
|
||||
context->BindIB(_indexBuffer);
|
||||
context->DrawIndexedInstanced(_triangles * 3, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals) const
|
||||
{
|
||||
if (!material || !material->IsSurface())
|
||||
return;
|
||||
|
||||
// Submit draw call
|
||||
DrawCall drawCall;
|
||||
drawCall.Geometry.IndexBuffer = _indexBuffer;
|
||||
drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0];
|
||||
drawCall.Geometry.VertexBuffers[1] = _vertexBuffers[1];
|
||||
drawCall.Geometry.VertexBuffers[2] = _vertexBuffers[2];
|
||||
drawCall.Geometry.VertexBuffersOffsets[0] = 0;
|
||||
drawCall.Geometry.VertexBuffersOffsets[1] = 0;
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = 0;
|
||||
drawCall.Geometry.StartIndex = 0;
|
||||
drawCall.Geometry.IndicesCount = _triangles * 3;
|
||||
drawCall.InstanceCount = 1;
|
||||
drawCall.IndirectArgsBuffer = nullptr;
|
||||
drawCall.IndirectArgsOffset = 0;
|
||||
drawCall.Material = material;
|
||||
drawCall.World = world;
|
||||
drawCall.PrevWorld = world;
|
||||
drawCall.ObjectPosition = drawCall.World.GetTranslation();
|
||||
drawCall.GeometrySize = _box.GetSize();
|
||||
drawCall.Lightmap = nullptr;
|
||||
drawCall.LightmapUVsArea = Rectangle::Empty;
|
||||
drawCall.Skinning = nullptr;
|
||||
drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1);
|
||||
drawCall.PerInstanceRandom = 0.0f;
|
||||
drawCall.LODDitherFactor = 0.0f;
|
||||
renderContext.List->AddDrawCall(DrawPass::Default, flags, drawCall, receiveDecals);
|
||||
}
|
||||
|
||||
void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const
|
||||
{
|
||||
// Cache data
|
||||
const auto& entry = info.Buffer->At(_materialSlotIndex);
|
||||
if (!entry.Visible || !IsInitialized())
|
||||
return;
|
||||
const MaterialSlot& slot = _model->MaterialSlots[_materialSlotIndex];
|
||||
|
||||
// Check if skip rendering
|
||||
const auto shadowsMode = static_cast<ShadowsCastingMode>(entry.ShadowsMode & slot.ShadowsMode);
|
||||
const auto drawModes = static_cast<DrawPass>(info.DrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode));
|
||||
if (drawModes == DrawPass::None)
|
||||
return;
|
||||
|
||||
// Select material
|
||||
MaterialBase* material;
|
||||
if (entry.Material && entry.Material->IsLoaded())
|
||||
material = entry.Material;
|
||||
else if (slot.Material && slot.Material->IsLoaded())
|
||||
material = slot.Material;
|
||||
else
|
||||
material = GPUDevice::Instance->GetDefaultMaterial();
|
||||
if (!material || !material->IsSurface())
|
||||
return;
|
||||
|
||||
// Submit draw call
|
||||
DrawCall drawCall;
|
||||
drawCall.Geometry.IndexBuffer = _indexBuffer;
|
||||
drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0];
|
||||
drawCall.Geometry.VertexBuffers[1] = _vertexBuffers[1];
|
||||
drawCall.Geometry.VertexBuffers[2] = _vertexBuffers[2];
|
||||
drawCall.Geometry.VertexBuffersOffsets[0] = 0;
|
||||
drawCall.Geometry.VertexBuffersOffsets[1] = 0;
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = 0;
|
||||
if (info.VertexColors && info.VertexColors[_lodIndex])
|
||||
{
|
||||
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
|
||||
// TODO: cache vertexOffset within the model LOD per-mesh
|
||||
uint32 vertexOffset = 0;
|
||||
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
|
||||
vertexOffset += _model->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
|
||||
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType);
|
||||
}
|
||||
drawCall.Geometry.StartIndex = 0;
|
||||
drawCall.Geometry.IndicesCount = _triangles * 3;
|
||||
drawCall.InstanceCount = 1;
|
||||
drawCall.IndirectArgsBuffer = nullptr;
|
||||
drawCall.IndirectArgsOffset = 0;
|
||||
drawCall.Material = material;
|
||||
drawCall.World = *info.World;
|
||||
drawCall.PrevWorld = info.DrawState->PrevWorld;
|
||||
drawCall.ObjectPosition = drawCall.World.GetTranslation();
|
||||
drawCall.GeometrySize = _box.GetSize();
|
||||
drawCall.Lightmap = info.Flags & StaticFlags::Lightmap ? info.Lightmap : nullptr;
|
||||
drawCall.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty;
|
||||
drawCall.Skinning = nullptr;
|
||||
drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1);
|
||||
drawCall.PerInstanceRandom = info.PerInstanceRandom;
|
||||
drawCall.LODDitherFactor = lodDitherFactor;
|
||||
renderContext.List->AddDrawCall(drawModes, info.Flags, drawCall, entry.ReceiveDecals);
|
||||
}
|
||||
|
||||
bool Mesh::ExtractData(MeshBufferType type, BytesContainer& result) const
|
||||
{
|
||||
GPUBuffer* buffer = nullptr;
|
||||
switch (type)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
buffer = _indexBuffer;
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
buffer = _vertexBuffers[0];
|
||||
break;
|
||||
case MeshBufferType::Vertex1:
|
||||
buffer = _vertexBuffers[1];
|
||||
break;
|
||||
case MeshBufferType::Vertex2:
|
||||
buffer = _vertexBuffers[2];
|
||||
break;
|
||||
}
|
||||
return buffer && buffer->DownloadData(result);
|
||||
}
|
||||
|
||||
Task* Mesh::ExtractDataAsync(MeshBufferType type, BytesContainer& result) const
|
||||
{
|
||||
GPUBuffer* buffer = nullptr;
|
||||
switch (type)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
buffer = _indexBuffer;
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
buffer = _vertexBuffers[0];
|
||||
break;
|
||||
case MeshBufferType::Vertex1:
|
||||
buffer = _vertexBuffers[1];
|
||||
break;
|
||||
case MeshBufferType::Vertex2:
|
||||
buffer = _vertexBuffers[2];
|
||||
break;
|
||||
}
|
||||
return buffer ? buffer->DownloadDataAsync(result) : nullptr;
|
||||
}
|
||||
|
||||
ScriptingObject* Mesh::GetParentModel()
|
||||
{
|
||||
return _model;
|
||||
}
|
||||
|
||||
template<typename IndexType>
|
||||
bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
|
||||
{
|
||||
auto model = mesh->GetModel();
|
||||
ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj);
|
||||
|
||||
// Get buffers data
|
||||
ASSERT((uint32)mono_array_length(verticesObj) >= vertexCount);
|
||||
ASSERT((uint32)mono_array_length(trianglesObj) / 3 >= triangleCount);
|
||||
auto vb0 = (Vector3*)(void*)mono_array_addr_with_size(verticesObj, sizeof(Vector3), 0);
|
||||
auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0);
|
||||
Array<VB1ElementType> vb1;
|
||||
Array<VB2ElementType> vb2;
|
||||
vb1.Resize(vertexCount);
|
||||
if (normalsObj)
|
||||
{
|
||||
const auto normals = (Vector3*)(void*)mono_array_addr_with_size(normalsObj, sizeof(Vector3), 0);
|
||||
if (tangentsObj)
|
||||
{
|
||||
const auto tangents = (Vector3*)(void*)mono_array_addr_with_size(tangentsObj, sizeof(Vector3), 0);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
// Peek normal and tangent
|
||||
const Vector3 normal = normals[i];
|
||||
const Vector3 tangent = tangents[i];
|
||||
|
||||
// Calculate bitangent sign
|
||||
Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
|
||||
byte sign = static_cast<byte>(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||
|
||||
// Set tangent frame
|
||||
vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
||||
vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
// Peek normal
|
||||
const Vector3 normal = normals[i];
|
||||
|
||||
// Calculate tangent
|
||||
Vector3 c1 = Vector3::Cross(normal, Vector3::UnitZ);
|
||||
Vector3 c2 = Vector3::Cross(normal, Vector3::UnitY);
|
||||
Vector3 tangent;
|
||||
if (c1.LengthSquared() > c2.LengthSquared())
|
||||
tangent = c1;
|
||||
else
|
||||
tangent = c2;
|
||||
|
||||
// Calculate bitangent sign
|
||||
Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
|
||||
byte sign = static_cast<byte>(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||
|
||||
// Set tangent frame
|
||||
vb1[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
||||
vb1[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto n = Float1010102(Vector3::UnitZ);
|
||||
const auto t = Float1010102(Vector3::UnitX);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vb1[i].Normal = n;
|
||||
vb1[i].Tangent = t;
|
||||
}
|
||||
}
|
||||
if (uvObj)
|
||||
{
|
||||
const auto uvs = (Vector2*)(void*)mono_array_addr_with_size(uvObj, sizeof(Vector2), 0);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb1[i].TexCoord = Half2(uvs[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto v = Half2(0, 0);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb1[i].TexCoord = v;
|
||||
}
|
||||
{
|
||||
auto v = Half2(0, 0);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb1[i].LightmapUVs = v;
|
||||
}
|
||||
if (colorsObj)
|
||||
{
|
||||
vb2.Resize(vertexCount);
|
||||
const auto colors = (Color32*)(void*)mono_array_addr_with_size(colorsObj, sizeof(Color32), 0);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb2[i].Color = colors[i];
|
||||
}
|
||||
|
||||
return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vb0, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, ib);
|
||||
}
|
||||
|
||||
bool Mesh::UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
|
||||
{
|
||||
return ::UpdateMesh<int32>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||
}
|
||||
|
||||
bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj)
|
||||
{
|
||||
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
|
||||
}
|
||||
|
||||
template<typename IndexType>
|
||||
bool UpdateTriangles(Mesh* mesh, int32 triangleCount, MonoArray* trianglesObj)
|
||||
{
|
||||
auto model = mesh->GetModel();
|
||||
ASSERT(model && model->IsVirtual() && trianglesObj);
|
||||
|
||||
// Get buffer data
|
||||
ASSERT((int32)mono_array_length(trianglesObj) / 3 >= triangleCount);
|
||||
auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(IndexType), 0);
|
||||
|
||||
return mesh->UpdateTriangles(triangleCount, ib);
|
||||
}
|
||||
|
||||
bool Mesh::UpdateTrianglesInt(int32 triangleCount, MonoArray* trianglesObj)
|
||||
{
|
||||
return ::UpdateTriangles<int32>(this, triangleCount, trianglesObj);
|
||||
}
|
||||
|
||||
bool Mesh::UpdateTrianglesUShort(int32 triangleCount, MonoArray* trianglesObj)
|
||||
{
|
||||
return ::UpdateTriangles<uint16>(this, triangleCount, trianglesObj);
|
||||
}
|
||||
|
||||
enum class InternalBufferType
|
||||
{
|
||||
VB0 = 0,
|
||||
VB1 = 1,
|
||||
VB2 = 2,
|
||||
IB16 = 3,
|
||||
IB32 = 4,
|
||||
};
|
||||
|
||||
void ConvertMeshData(Mesh* mesh, InternalBufferType type, MonoArray* resultObj, void* srcData)
|
||||
{
|
||||
auto vertices = mesh->GetVertexCount();
|
||||
auto triangles = mesh->GetTriangleCount();
|
||||
auto indices = triangles * 3;
|
||||
auto use16BitIndexBuffer = mesh->Use16BitIndexBuffer();
|
||||
|
||||
void* managedArrayPtr = mono_array_addr_with_size(resultObj, 0, 0);
|
||||
switch (type)
|
||||
{
|
||||
case InternalBufferType::VB0:
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, srcData, sizeof(VB0ElementType) * vertices);
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::VB1:
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, srcData, sizeof(VB1ElementType) * vertices);
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::VB2:
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, srcData, sizeof(VB2ElementType) * vertices);
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::IB16:
|
||||
{
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, srcData, sizeof(uint16) * indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dst = (uint16*)managedArrayPtr;
|
||||
auto src = (uint32*)srcData;
|
||||
for (int32 i = 0; i < indices; i++)
|
||||
{
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::IB32:
|
||||
{
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
auto dst = (uint32*)managedArrayPtr;
|
||||
auto src = (uint16*)srcData;
|
||||
for (int32 i = 0; i < indices; i++)
|
||||
{
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, srcData, sizeof(uint32) * indices);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Mesh::DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI)
|
||||
{
|
||||
Mesh* mesh = this;
|
||||
InternalBufferType type = (InternalBufferType)typeI;
|
||||
auto model = mesh->GetModel();
|
||||
ASSERT(model && resultObj);
|
||||
|
||||
ScopeLock lock(model->Locker);
|
||||
|
||||
// Virtual assets always fetch from GPU memory
|
||||
forceGpu |= model->IsVirtual();
|
||||
|
||||
if (!mesh->IsInitialized() && forceGpu)
|
||||
{
|
||||
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
|
||||
return true;
|
||||
}
|
||||
if (type == InternalBufferType::IB16 || type == InternalBufferType::IB32)
|
||||
{
|
||||
if (mono_array_length(resultObj) != mesh->GetTriangleCount() * 3)
|
||||
{
|
||||
LOG(Error, "Invalid buffer size.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mono_array_length(resultObj) != mesh->GetVertexCount())
|
||||
{
|
||||
LOG(Error, "Invalid buffer size.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if load data from GPU
|
||||
if (forceGpu)
|
||||
{
|
||||
MeshBufferType bufferType;
|
||||
switch (type)
|
||||
{
|
||||
case InternalBufferType::VB0:
|
||||
bufferType = MeshBufferType::Vertex0;
|
||||
break;
|
||||
case InternalBufferType::VB1:
|
||||
bufferType = MeshBufferType::Vertex1;
|
||||
break;
|
||||
case InternalBufferType::VB2:
|
||||
bufferType = MeshBufferType::Vertex2;
|
||||
break;
|
||||
case InternalBufferType::IB16:
|
||||
case InternalBufferType::IB32:
|
||||
bufferType = MeshBufferType::Index;
|
||||
break;
|
||||
default: CRASH;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
|
||||
BytesContainer data;
|
||||
auto task = mesh->ExtractDataAsync(bufferType, data);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
|
||||
model->Locker.Unlock();
|
||||
|
||||
task->Start();
|
||||
if (task->Wait())
|
||||
{
|
||||
LOG(Error, "Task failed.");
|
||||
return true;
|
||||
}
|
||||
|
||||
ConvertMeshData(mesh, type, resultObj, data.Get());
|
||||
|
||||
model->Locker.Lock();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get data from drive/memory
|
||||
{
|
||||
// Fetch chunk with data
|
||||
const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(mesh->GetLODIndex());
|
||||
if (model->LoadChunk(chunkIndex))
|
||||
return true;
|
||||
const auto chunk = model->GetChunk(chunkIndex);
|
||||
if (!chunk)
|
||||
{
|
||||
LOG(Error, "Missing chunk.");
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
|
||||
// Seek to find mesh location
|
||||
for (int32 i = 0; i < mesh->GetIndex(); i++)
|
||||
{
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Error, "Invalid mesh data.");
|
||||
return true;
|
||||
}
|
||||
auto vb0 = stream.Read<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Read<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Read<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Read<byte>(indicesCount * ibStride);
|
||||
}
|
||||
|
||||
// Get mesh data
|
||||
void* data = nullptr;
|
||||
{
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Error, "Invalid mesh data.");
|
||||
return true;
|
||||
}
|
||||
auto vb0 = stream.Read<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Read<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Read<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Read<byte>(indicesCount * ibStride);
|
||||
|
||||
if (mesh->HasVertexColors() != hasColors || mesh->Use16BitIndexBuffer() != use16BitIndexBuffer)
|
||||
{
|
||||
LOG(Error, "Invalid mesh data loaded from chunk.");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case InternalBufferType::VB0:
|
||||
data = vb0;
|
||||
break;
|
||||
case InternalBufferType::VB1:
|
||||
data = vb1;
|
||||
break;
|
||||
case InternalBufferType::VB2:
|
||||
data = vb2;
|
||||
break;
|
||||
case InternalBufferType::IB16:
|
||||
case InternalBufferType::IB32:
|
||||
data = ib;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ConvertMeshData(mesh, type, resultObj, data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
465
Source/Engine/Graphics/Models/Mesh.h
Normal file
465
Source/Engine/Graphics/Models/Mesh.h
Normal file
@@ -0,0 +1,465 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/BoundingBox.h"
|
||||
#include "Engine/Core/Math/BoundingSphere.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "ModelInstanceEntry.h"
|
||||
#include "Config.h"
|
||||
#include "Types.h"
|
||||
#if USE_PRECISE_MESH_INTERSECTS
|
||||
#include "CollisionProxy.h"
|
||||
#endif
|
||||
|
||||
class GPUBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Represents part of the model that is made of vertices and can be rendered using custom material and transformation.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, PersistentScriptingObject);
|
||||
protected:
|
||||
|
||||
Model* _model;
|
||||
int32 _index;
|
||||
int32 _lodIndex;
|
||||
int32 _materialSlotIndex;
|
||||
bool _use16BitIndexBuffer;
|
||||
bool _hasLightmapUVs;
|
||||
BoundingBox _box;
|
||||
BoundingSphere _sphere;
|
||||
uint32 _vertices;
|
||||
uint32 _triangles;
|
||||
GPUBuffer* _vertexBuffers[3];
|
||||
GPUBuffer* _indexBuffer;
|
||||
#if USE_PRECISE_MESH_INTERSECTS
|
||||
CollisionProxy _collisionProxy;
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
Mesh(const Mesh& other)
|
||||
: Mesh()
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
CRASH; // Not used
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="Mesh"/> class.
|
||||
/// </summary>
|
||||
~Mesh();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model owning this mesh.
|
||||
/// </summary>
|
||||
FORCE_INLINE Model* GetModel() const
|
||||
{
|
||||
return _model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mesh parent LOD index.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 GetLODIndex() const
|
||||
{
|
||||
return _lodIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mesh index.
|
||||
/// </summary>
|
||||
FORCE_INLINE int32 GetIndex() const
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the material slot to use during this mesh rendering.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetMaterialSlotIndex() const
|
||||
{
|
||||
return _materialSlotIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the index of the material slot to use during this mesh rendering.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetMaterialSlotIndex(int32 value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triangle count.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const
|
||||
{
|
||||
return _triangles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertex count.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const
|
||||
{
|
||||
return _vertices;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index buffer.
|
||||
/// </summary>
|
||||
/// <returns>The buffer.</returns>
|
||||
FORCE_INLINE GPUBuffer* GetIndexBuffer() const
|
||||
{
|
||||
return _indexBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertex buffer.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>The buffer.</returns>
|
||||
FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const
|
||||
{
|
||||
return _vertexBuffers[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this mesh is initialized (has vertex and index buffers initialized).
|
||||
/// </summary>
|
||||
/// <returns>True if this instance is initialized, otherwise false.</returns>
|
||||
FORCE_INLINE bool IsInitialized() const
|
||||
{
|
||||
return _vertexBuffers[0] != nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit.
|
||||
/// </summary>
|
||||
/// <returns>True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer.</returns>
|
||||
API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const
|
||||
{
|
||||
return _use16BitIndexBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this mesh has a vertex colors buffer.
|
||||
/// </summary>
|
||||
/// <returns>True if this mesh has a vertex colors buffers.</returns>
|
||||
API_PROPERTY() FORCE_INLINE bool HasVertexColors() const
|
||||
{
|
||||
return _vertexBuffers[2] != nullptr && _vertexBuffers[2]->IsAllocated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this mesh contains valid lightmap texture coordinates data.
|
||||
/// </summary>
|
||||
/// <returns>True if this mesh has a vertex colors buffers.</returns>
|
||||
API_PROPERTY() FORCE_INLINE bool HasLightmapUVs() const
|
||||
{
|
||||
return _hasLightmapUVs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mesh bounds.
|
||||
/// </summary>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
void SetBounds(const BoundingBox& box)
|
||||
{
|
||||
_box = box;
|
||||
BoundingSphere::FromBox(box, _sphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the box.
|
||||
/// </summary>
|
||||
/// <returns>The bounding box.</returns>
|
||||
API_PROPERTY() FORCE_INLINE const BoundingBox& GetBox() const
|
||||
{
|
||||
return _box;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sphere.
|
||||
/// </summary>
|
||||
/// <returns>The bounding sphere.</returns>
|
||||
API_PROPERTY() FORCE_INLINE const BoundingSphere& GetSphere() const
|
||||
{
|
||||
return _sphere;
|
||||
}
|
||||
|
||||
#if USE_PRECISE_MESH_INTERSECTS
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collision proxy used by the mesh.
|
||||
/// </summary>
|
||||
/// <returns>The collisions proxy container object reference.</returns>
|
||||
FORCE_INLINE const CollisionProxy& GetCollisionProxy() const
|
||||
{
|
||||
return _collisionProxy;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb0">The first vertex buffer data.</param>
|
||||
/// <param name="vb1">The second vertex buffer data.</param>
|
||||
/// <param name="vb2">The third vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, int32* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb0">The first vertex buffer data.</param>
|
||||
/// <param name="vb1">The second vertex buffer data.</param>
|
||||
/// <param name="vb2">The third vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, uint16* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb0">The first vertex buffer data.</param>
|
||||
/// <param name="vb1">The second vertex buffer data.</param>
|
||||
/// <param name="vb2">The third vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0ElementType* vb0, VB1ElementType* vb1, VB2ElementType* vb2, void* ib, bool use16BitIndices);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, int32* ib)
|
||||
{
|
||||
return UpdateTriangles(triangleCount, ib, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateTriangles(uint32 triangleCount, uint16* ib)
|
||||
{
|
||||
return UpdateTriangles(triangleCount, ib, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes instance of the <see cref="Mesh"/> class.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="lodIndex">The LOD index.</param>
|
||||
/// <param name="index">The mesh index.</param>
|
||||
/// <param name="materialSlotIndex">The material slot index to use.</param>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
/// <param name="sphere">The bounding sphere.</param>
|
||||
/// <param name="hasLightmapUVs">The lightmap UVs flag.</param>
|
||||
void Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs);
|
||||
|
||||
/// <summary>
|
||||
/// Load mesh data and Initialize GPU buffers
|
||||
/// </summary>
|
||||
/// <param name="vertices">Amount of vertices in the vertex buffer</param>
|
||||
/// <param name="triangles">Amount of triangles in the index buffer</param>
|
||||
/// <param name="vb0">Vertex buffer 0 data</param>
|
||||
/// <param name="vb1">Vertex buffer 1 data</param>
|
||||
/// <param name="vb2">Vertex buffer 2 data (may be null if not used)</param>
|
||||
/// <param name="ib">Index buffer data</param>
|
||||
/// <param name="use16BitIndexBuffer">True if use 16 bit indices for the index buffer (true: uint16, false: uint32).</param>
|
||||
/// <returns>True if cannot load data, otherwise false.</returns>
|
||||
bool Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* vb2, void* ib, bool use16BitIndexBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there is an intersection between the mesh and a ray in given world
|
||||
/// </summary>
|
||||
/// <param name="ray">The ray to test</param>
|
||||
/// <param name="world">World to transform box</param>
|
||||
/// <param name="distance">When the method completes and returns true, contains the distance of the intersection (if any valid).</param>
|
||||
/// <param name="normal">When the method completes, contains the intersection surface normal vector (if any valid).</param>
|
||||
/// <returns>True whether the two objects intersected</returns>
|
||||
bool Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal) const;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the eight corners of the bounding box.
|
||||
/// </summary>
|
||||
/// <param name="corners">An array of points representing the eight corners of the bounding box.</param>
|
||||
FORCE_INLINE void GetCorners(Vector3 corners[8]) const
|
||||
{
|
||||
_box.GetCorners(corners);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the draw call geometry for this mesh. Sets the index and vertex buffers.
|
||||
/// </summary>
|
||||
/// <param name="drawCall">The draw call.</param>
|
||||
void GetDrawCallGeometry(DrawCall& drawCall);
|
||||
|
||||
/// <summary>
|
||||
/// Model instance drawing packed data.
|
||||
/// </summary>
|
||||
struct DrawInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The instance buffer to use during model rendering.
|
||||
/// </summary>
|
||||
ModelInstanceEntries* Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// The world transformation of the model.
|
||||
/// </summary>
|
||||
Matrix* World;
|
||||
|
||||
/// <summary>
|
||||
/// The instance drawing state data container. Used for LOD transition handling and previous world transformation matrix updating.
|
||||
/// </summary>
|
||||
GeometryDrawStateData* DrawState;
|
||||
|
||||
/// <summary>
|
||||
/// The lightmap.
|
||||
/// </summary>
|
||||
const Lightmap* Lightmap;
|
||||
|
||||
/// <summary>
|
||||
/// The lightmap UVs.
|
||||
/// </summary>
|
||||
const Rectangle* LightmapUVs;
|
||||
|
||||
/// <summary>
|
||||
/// The model instance vertex colors buffers (per-lod all meshes packed in a single allocation, array length equal to model lods count).
|
||||
/// </summary>
|
||||
GPUBuffer** VertexColors;
|
||||
|
||||
/// <summary>
|
||||
/// The object static flags.
|
||||
/// </summary>
|
||||
StaticFlags Flags;
|
||||
|
||||
/// <summary>
|
||||
/// The object draw modes.
|
||||
/// </summary>
|
||||
DrawPass DrawModes;
|
||||
|
||||
/// <summary>
|
||||
/// The bounds of the model (used to select a proper LOD during rendering).
|
||||
/// </summary>
|
||||
BoundingSphere Bounds;
|
||||
|
||||
/// <summary>
|
||||
/// The per-instance random value.
|
||||
/// </summary>
|
||||
float PerInstanceRandom;
|
||||
|
||||
/// <summary>
|
||||
/// The LOD bias value.
|
||||
/// </summary>
|
||||
char LODBias;
|
||||
|
||||
/// <summary>
|
||||
/// The forced LOD to use. Value -1 disables this feature.
|
||||
/// </summary>
|
||||
char ForcedLOD;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Draws the mesh. Binds vertex and index buffers and invokes the draw call.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context.</param>
|
||||
void Render(GPUContext* context) const;
|
||||
|
||||
/// <summary>
|
||||
/// Draws the mesh.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="material">The material to use for rendering.</param>
|
||||
/// <param name="world">The world transformation of the model.</param>
|
||||
/// <param name="flags">The object static flags.</param>
|
||||
/// <param name="receiveDecals">True if rendered geometry can receive decals, otherwise false.</param>
|
||||
API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true) const;
|
||||
|
||||
/// <summary>
|
||||
/// Draws the mesh.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="info">The packed drawing info data.</param>
|
||||
/// <param name="lodDitherFactor">The LOD transition dither factor.</param>
|
||||
void Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Extract mesh buffer data (cannot be called from the main thread!).
|
||||
/// </summary>
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <returns>True if failed, otherwise false</returns>
|
||||
bool ExtractData(MeshBufferType type, BytesContainer& result) const;
|
||||
|
||||
/// <summary>
|
||||
/// Extracts mesh buffer data in the async task.
|
||||
/// </summary>
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <returns>Created async task used to gather the buffer data.</returns>
|
||||
Task* ExtractDataAsync(MeshBufferType type, BytesContainer& result) const;
|
||||
|
||||
private:
|
||||
|
||||
// Internal bindings
|
||||
API_FUNCTION(NoProxy) ScriptingObject* GetParentModel();
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateTrianglesInt(int32 triangleCount, MonoArray* trianglesObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, MonoArray* trianglesObj);
|
||||
API_FUNCTION(NoProxy) bool DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI);
|
||||
};
|
||||
811
Source/Engine/Graphics/Models/ModelData.Tool.cpp
Normal file
811
Source/Engine/Graphics/Models/ModelData.Tool.cpp
Normal file
@@ -0,0 +1,811 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if COMPILE_WITH_MODEL_TOOL
|
||||
|
||||
#include "ModelData.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Utilities.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/BitArray.h"
|
||||
#include "Engine/Tools/ModelTool/ModelTool.h"
|
||||
#include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h"
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#if USE_ASSIMP
|
||||
#define USE_SPARIAL_SORT 1
|
||||
#define ASSIMP_BUILD_NO_EXPORT
|
||||
#include "Engine/Tools/ModelTool/SpatialSort.h"
|
||||
//#include <ThirdParty/assimp/SpatialSort.h>
|
||||
#else
|
||||
#define USE_SPARIAL_SORT 0
|
||||
#endif
|
||||
#include <stack>
|
||||
|
||||
// Import UVAtlas library
|
||||
// Source: https://github.com/Microsoft/UVAtlas
|
||||
#include <ThirdParty/UVAtlas/UVAtlas.h>
|
||||
|
||||
// Import DirectXMesh library
|
||||
// Source: https://github.com/Microsoft/DirectXMesh
|
||||
#include <ThirdParty/DirectXMesh/DirectXMesh.h>
|
||||
|
||||
HRESULT __cdecl UVAtlasCallback(float fPercentDone)
|
||||
{
|
||||
/*static ULONGLONG s_lastTick = 0;
|
||||
|
||||
ULONGLONG tick = GetTickCount64();
|
||||
|
||||
if ((tick - s_lastTick) > 1000)
|
||||
{
|
||||
wprintf(L"%.2f%% \r", fPercentDone * 100);
|
||||
s_lastTick = tick;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
if (_kbhit())
|
||||
{
|
||||
if (_getch() == 27)
|
||||
{
|
||||
wprintf(L"*** ABORT ***");
|
||||
return E_ABORT;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RemapArrayHelper(Array<T>& target, const std::vector<uint32_t>& remap)
|
||||
{
|
||||
if (target.HasItems())
|
||||
{
|
||||
Array<T> tmp;
|
||||
tmp.Swap(target);
|
||||
int32 size = (int32)remap.size();
|
||||
target.Resize(size, false);
|
||||
for (int32 i = 0; i < size; i++)
|
||||
target[i] = tmp.At(remap[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshData::GenerateLightmapUVs()
|
||||
{
|
||||
// Prepare
|
||||
HRESULT hr;
|
||||
int32 verticesCount = Positions.Count();
|
||||
int32 facesCount = Indices.Count() / 3;
|
||||
DirectX::XMFLOAT3* positions = (DirectX::XMFLOAT3*)Positions.Get();
|
||||
LOG(Info, "Generating lightmaps UVs ({0} vertices, {1} triangles)...", verticesCount, facesCount);
|
||||
|
||||
DateTime startTime = DateTime::Now();
|
||||
|
||||
// Generate adjacency data
|
||||
const float adjacencyEpsilon = 0.001f;
|
||||
Array<uint32> adjacency;
|
||||
adjacency.Resize(Indices.Count());
|
||||
hr = GenerateAdjacencyAndPointReps(Indices.Get(), facesCount, positions, verticesCount, adjacencyEpsilon, nullptr, adjacency.Get());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG(Warning, "Cannot generate lightmap uvs. Result: {0:x}:{1}", hr, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate UVs
|
||||
std::vector<DirectX::UVAtlasVertex> vb;
|
||||
std::vector<uint8_t> ib;
|
||||
float outStretch = 0.f;
|
||||
size_t outCharts = 0;
|
||||
std::vector<uint32_t> facePartitioning;
|
||||
std::vector<uint32_t> vertexRemapArray;
|
||||
int32 size = 1024;
|
||||
float gutter = 1.0f;
|
||||
hr = UVAtlasCreate(
|
||||
positions, verticesCount,
|
||||
Indices.Get(), DXGI_FORMAT_R32_UINT, facesCount,
|
||||
0, 0.1f, size, size, gutter,
|
||||
adjacency.Get(), nullptr, nullptr,
|
||||
UVAtlasCallback, DirectX::UVATLAS_DEFAULT_CALLBACK_FREQUENCY,
|
||||
DirectX::UVATLAS_GEODESIC_FAST, vb, ib,
|
||||
&facePartitioning,
|
||||
&vertexRemapArray,
|
||||
&outStretch, &outCharts);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG(Warning, "Cannot generate lightmap uvs. Result: {0:x}:{1}", hr, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
const DateTime endTime = DateTime::Now();
|
||||
|
||||
// Log info
|
||||
const int32 nTotalVerts = (int32)vb.size();
|
||||
const int32 msTime = Math::CeilToInt((float)(endTime - startTime).GetTotalMilliseconds());
|
||||
LOG(Info, "Lightmap UVs generated! Charts: {0}, stretching: {1}, {2} vertices. Time: {3}ms", outCharts, outStretch, nTotalVerts, msTime);
|
||||
|
||||
// Update mesh data (remap vertices due to vertex buffer and index buffer change)
|
||||
RemapArrayHelper(Positions, vertexRemapArray);
|
||||
RemapArrayHelper(UVs, vertexRemapArray);
|
||||
RemapArrayHelper(Normals, vertexRemapArray);
|
||||
RemapArrayHelper(Tangents, vertexRemapArray);
|
||||
RemapArrayHelper(Colors, vertexRemapArray);
|
||||
RemapArrayHelper(BlendIndices, vertexRemapArray);
|
||||
RemapArrayHelper(BlendWeights, vertexRemapArray);
|
||||
LightmapUVs.Resize(nTotalVerts, false);
|
||||
for (int32 i = 0; i < nTotalVerts; i++)
|
||||
LightmapUVs[i] = *(Vector2*)&vb[i].uv;
|
||||
uint32* ibP = (uint32*)ib.data();
|
||||
for (int32 i = 0; i < Indices.Count(); i++)
|
||||
Indices[i] = *ibP++;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 FindVertex(const MeshData& mesh, int32 vertexIndex, int32 startIndex, int32 searchRange, const Array<int32>& mapping
|
||||
#if USE_SPARIAL_SORT
|
||||
, const Assimp::SpatialSort& spatialSort
|
||||
, std::vector<unsigned int>& sparialSortCache
|
||||
#endif
|
||||
)
|
||||
{
|
||||
const float uvEpsSqr = (1.0f / 250.0f) * (1.0f / 250.0f);
|
||||
|
||||
#if USE_SPARIAL_SORT
|
||||
const Vector3 vPosition = mesh.Positions[vertexIndex];
|
||||
spatialSort.FindPositions(*(aiVector3D*)&vPosition, 1e-4f, sparialSortCache);
|
||||
if (sparialSortCache.empty())
|
||||
return INVALID_INDEX;
|
||||
|
||||
const Vector2 vUV = mesh.UVs.HasItems() ? mesh.UVs[vertexIndex] : Vector2::Zero;
|
||||
const Vector3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Vector3::Zero;
|
||||
const Vector3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Vector3::Zero;
|
||||
const Vector2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Vector2::Zero;
|
||||
const int32 end = startIndex + searchRange;
|
||||
|
||||
for (size_t i = 0; i < sparialSortCache.size(); i++)
|
||||
{
|
||||
const int32 v = sparialSortCache[i];
|
||||
if (v < startIndex || v >= end)
|
||||
continue;
|
||||
#else
|
||||
const Vector3 vPosition = mesh.Positions[vertexIndex];
|
||||
const Vector2 vUV = mesh.UVs.HasItems() ? mesh.UVs[vertexIndex] : Vector2::Zero;
|
||||
const Vector3 vNormal = mesh.Normals.HasItems() ? mesh.Normals[vertexIndex] : Vector3::Zero;
|
||||
const Vector3 vTangent = mesh.Tangents.HasItems() ? mesh.Tangents[vertexIndex] : Vector3::Zero;
|
||||
const Vector2 vLightmapUV = mesh.LightmapUVs.HasItems() ? mesh.LightmapUVs[vertexIndex] : Vector2::Zero;
|
||||
const int32 end = startIndex + searchRange;
|
||||
|
||||
for (int32 v = startIndex; v < end; v++)
|
||||
{
|
||||
if (!Vector3::NearEqual(vPosition, mesh.Positions[v]))
|
||||
continue;
|
||||
#endif
|
||||
if (mapping[v] == INVALID_INDEX)
|
||||
continue;
|
||||
if (mesh.UVs.HasItems() && (vUV - mesh.UVs[v]).LengthSquared() > uvEpsSqr)
|
||||
continue;
|
||||
if (mesh.Normals.HasItems() && Vector3::Dot(vNormal, mesh.Normals[v]) < 0.98f)
|
||||
continue;
|
||||
if (mesh.Tangents.HasItems() && Vector3::Dot(vTangent, mesh.Tangents[v]) < 0.98f)
|
||||
continue;
|
||||
if (mesh.LightmapUVs.HasItems() && (vLightmapUV - mesh.LightmapUVs[v]).LengthSquared() > uvEpsSqr)
|
||||
continue;
|
||||
// TODO: check more components?
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RemapBuffer(Array<T>& src, Array<T>& dst, const Array<int32>& mapping, int32 newSize)
|
||||
{
|
||||
if (src.IsEmpty())
|
||||
return;
|
||||
|
||||
dst.Resize(newSize, false);
|
||||
|
||||
for (int32 i = 0, j = 0; i < src.Count(); i++)
|
||||
{
|
||||
const auto idx = mapping[i];
|
||||
if (idx != INVALID_INDEX)
|
||||
{
|
||||
dst[j++] = src[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::BuildIndexBuffer()
|
||||
{
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
const int32 vertexCount = Positions.Count();
|
||||
MeshData newMesh;
|
||||
newMesh.Indices.EnsureCapacity(vertexCount);
|
||||
Array<int32> mapping;
|
||||
mapping.Resize(vertexCount);
|
||||
int32 newVertexCounter = 0;
|
||||
|
||||
#if USE_SPARIAL_SORT
|
||||
// Set up a SpatialSort to quickly find all vertices close to a given position
|
||||
Assimp::SpatialSort vertexFinder;
|
||||
vertexFinder.Fill((const aiVector3D*)Positions.Get(), vertexCount, sizeof(Vector3));
|
||||
std::vector<unsigned int> sparialSortCache;
|
||||
#endif
|
||||
|
||||
// Build index buffer
|
||||
for (int32 vertexIndex = 0; vertexIndex < vertexCount; vertexIndex++)
|
||||
{
|
||||
// Find duplicated vertex before the current one
|
||||
const int32 reuseVertexIndex = FindVertex(*this, vertexIndex, 0, vertexIndex, mapping
|
||||
#if USE_SPARIAL_SORT
|
||||
, vertexFinder, sparialSortCache
|
||||
#endif
|
||||
);
|
||||
if (reuseVertexIndex == INVALID_INDEX)
|
||||
{
|
||||
newMesh.Indices.Add(newVertexCounter);
|
||||
mapping[vertexIndex] = newVertexCounter;
|
||||
newVertexCounter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove vertex
|
||||
const int32 mapped = mapping[reuseVertexIndex];
|
||||
ASSERT(mapped != INVALID_INDEX && mapped < vertexIndex);
|
||||
newMesh.Indices.Add(mapped);
|
||||
mapping[vertexIndex] = INVALID_INDEX;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if no change
|
||||
if (vertexCount == newVertexCounter)
|
||||
return;
|
||||
|
||||
// Remove unused vertices
|
||||
newMesh.SwapBuffers(*this);
|
||||
#define REMAP_BUFFER(name) RemapBuffer(newMesh.name, name, mapping, newVertexCounter)
|
||||
REMAP_BUFFER(Positions);
|
||||
REMAP_BUFFER(UVs);
|
||||
REMAP_BUFFER(Normals);
|
||||
REMAP_BUFFER(Tangents);
|
||||
REMAP_BUFFER(LightmapUVs);
|
||||
REMAP_BUFFER(Colors);
|
||||
REMAP_BUFFER(BlendIndices);
|
||||
REMAP_BUFFER(BlendWeights);
|
||||
#undef REMAP_BUFFER
|
||||
BlendShapes.Resize(newMesh.BlendShapes.Count());
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < newMesh.BlendShapes.Count(); blendShapeIndex++)
|
||||
{
|
||||
auto& srcBlendShape = newMesh.BlendShapes[blendShapeIndex];
|
||||
auto& dstBlendShape = BlendShapes[blendShapeIndex];
|
||||
|
||||
dstBlendShape.Name = srcBlendShape.Name;
|
||||
dstBlendShape.Weight = srcBlendShape.Weight;
|
||||
dstBlendShape.Vertices.Resize(newVertexCounter);
|
||||
for (int32 i = 0, j = 0; i < srcBlendShape.Vertices.Count(); i++)
|
||||
{
|
||||
const auto idx = mapping[i];
|
||||
if (idx != INVALID_INDEX)
|
||||
{
|
||||
auto& v = srcBlendShape.Vertices[i];
|
||||
ASSERT_LOW_LAYER(v.VertexIndex < (uint32)vertexCount);
|
||||
ASSERT_LOW_LAYER(mapping[v.VertexIndex] != INVALID_INDEX);
|
||||
v.VertexIndex = mapping[v.VertexIndex];
|
||||
ASSERT_LOW_LAYER(v.VertexIndex < (uint32)newVertexCounter);
|
||||
dstBlendShape.Vertices[j++] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
LOG(Info, "Generated index buffer for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), Positions.Count(), Indices.Count());
|
||||
}
|
||||
|
||||
void MeshData::FindPositions(const Vector3& position, float epsilon, Array<int32>& result)
|
||||
{
|
||||
result.Clear();
|
||||
|
||||
for (int32 i = 0; i < Positions.Count(); i++)
|
||||
{
|
||||
if (Vector3::NearEqual(position, Positions[i], epsilon))
|
||||
result.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshData::GenerateNormals(float smoothingAngle)
|
||||
{
|
||||
if (Positions.IsEmpty() || Indices.IsEmpty())
|
||||
{
|
||||
LOG(Warning, "Missing vertex and index data");
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
const int32 vertexCount = Positions.Count();
|
||||
const int32 indexCount = Indices.Count();
|
||||
Normals.Resize(vertexCount, false);
|
||||
smoothingAngle = Math::Clamp(smoothingAngle, 0.0f, 175.0f);
|
||||
|
||||
// Compute per-face normals but store them per-vertex
|
||||
Vector3 min, max;
|
||||
min = max = Positions[0];
|
||||
for (int32 i = 0; i < indexCount; i += 3)
|
||||
{
|
||||
const Vector3 v1 = Positions[Indices[i + 0]];
|
||||
const Vector3 v2 = Positions[Indices[i + 1]];
|
||||
const Vector3 v3 = Positions[Indices[i + 2]];
|
||||
const Vector3 n = ((v2 - v1) ^ (v3 - v1));
|
||||
|
||||
Normals[Indices[i + 0]] = n;
|
||||
Normals[Indices[i + 1]] = n;
|
||||
Normals[Indices[i + 2]] = n;
|
||||
|
||||
Vector3::Min(min, v1, min);
|
||||
Vector3::Min(min, v2, min);
|
||||
Vector3::Min(min, v3, min);
|
||||
|
||||
Vector3::Max(max, v1, max);
|
||||
Vector3::Max(max, v2, max);
|
||||
Vector3::Max(max, v3, max);
|
||||
}
|
||||
|
||||
#if USE_SPARIAL_SORT
|
||||
// Set up a SpatialSort to quickly find all vertices close to a given position
|
||||
Assimp::SpatialSort vertexFinder;
|
||||
vertexFinder.Fill((const aiVector3D*)Positions.Get(), vertexCount, sizeof(Vector3));
|
||||
std::vector<uint32> verticesFound(16);
|
||||
#else
|
||||
Array<int32> verticesFound(16);
|
||||
#endif
|
||||
const float posEpsilon = (max - min).Length() * 1e-4f;
|
||||
|
||||
// Check if use the angle limit (then use the faster path)
|
||||
if (smoothingAngle >= 175.0f)
|
||||
{
|
||||
BitArray<> used;
|
||||
used.Resize(vertexCount);
|
||||
used.SetAll(false);
|
||||
|
||||
for (int32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
if (used[i])
|
||||
continue;
|
||||
|
||||
// Get all vertices that share this one
|
||||
#if USE_SPARIAL_SORT
|
||||
vertexFinder.FindPositions(*(aiVector3D*)&Positions[i], posEpsilon, verticesFound);
|
||||
const int32 verticesFoundCount = (int32)verticesFound.size();
|
||||
#else
|
||||
FindPositions(Positions[i], posEpsilon, verticesFound);
|
||||
const int32 verticesFoundCount = verticesFound.Count();
|
||||
#endif
|
||||
|
||||
// Get the smooth normal
|
||||
Vector3 n;
|
||||
for (int32 a = 0; a < verticesFoundCount; a++)
|
||||
n += Normals[verticesFound[a]];
|
||||
n.Normalize();
|
||||
|
||||
// Write the smoothed normal back to all affected normals
|
||||
for (int32 a = 0; a < verticesFoundCount; a++)
|
||||
{
|
||||
const auto vtx = verticesFound[a];
|
||||
Normals[vtx] = n;
|
||||
used.Set(vtx, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float limit = cosf(smoothingAngle * DegreesToRadians);
|
||||
|
||||
for (int32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
// Get all vertices that share this one
|
||||
#if USE_SPARIAL_SORT
|
||||
vertexFinder.FindPositions(*(aiVector3D*)&Positions[i], posEpsilon, verticesFound);
|
||||
const int32 verticesFoundCount = (int32)verticesFound.size();
|
||||
#else
|
||||
FindPositions(Positions[i], posEpsilon, verticesFound);
|
||||
const int32 verticesFoundCount = verticesFound.Count();
|
||||
#endif
|
||||
|
||||
// Get the smooth normal
|
||||
Vector3 vr = Normals[i];
|
||||
const float vrlen = vr.Length();
|
||||
Vector3 n;
|
||||
for (int32 a = 0; a < verticesFoundCount; a++)
|
||||
{
|
||||
Vector3 v = Normals[verticesFound[a]];
|
||||
|
||||
// Check whether the angle between the two normals is not too large
|
||||
// TODO: use length squared?
|
||||
if (v * vr >= limit * vrlen * v.Length())
|
||||
n += v;
|
||||
}
|
||||
Normals[i] = Vector3::Normalize(n);
|
||||
}
|
||||
}
|
||||
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshData::GenerateTangents(float smoothingAngle)
|
||||
{
|
||||
if (Positions.IsEmpty() || Indices.IsEmpty() || Normals.IsEmpty() || UVs.IsEmpty())
|
||||
{
|
||||
LOG(Warning, "Missing vertex and index data");
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
const int32 vertexCount = Positions.Count();
|
||||
const int32 indexCount = Indices.Count();
|
||||
Tangents.Resize(vertexCount, false);
|
||||
smoothingAngle = Math::Clamp(smoothingAngle, 0.0f, 45.0f);
|
||||
|
||||
// Note: this assumes that mesh is in a verbose format
|
||||
// where each triangle has its own set of vertices
|
||||
// and no vertices are shared between triangles (dummy index buffer).
|
||||
|
||||
const float angleEpsilon = 0.9999f;
|
||||
BitArray<> vertexDone;
|
||||
vertexDone.Resize(vertexCount);
|
||||
vertexDone.SetAll(false);
|
||||
|
||||
const Vector3* meshNorm = Normals.Get();
|
||||
const Vector2* meshTex = UVs.Get();
|
||||
Vector3* meshTang = Tangents.Get();
|
||||
|
||||
// Calculate the tangent for every face
|
||||
Vector3 min, max;
|
||||
min = max = Positions[0];
|
||||
for (int32 i = 0; i < indexCount; i += 3)
|
||||
{
|
||||
const int32 p0 = Indices[i + 0], p1 = Indices[i + 1], p2 = Indices[i + 2];
|
||||
|
||||
const Vector3 v1 = Positions[p0];
|
||||
const Vector3 v2 = Positions[p1];
|
||||
const Vector3 v3 = Positions[p2];
|
||||
|
||||
Vector3::Min(min, v1, min);
|
||||
Vector3::Min(min, v2, min);
|
||||
Vector3::Min(min, v3, min);
|
||||
|
||||
Vector3::Max(max, v1, max);
|
||||
Vector3::Max(max, v2, max);
|
||||
Vector3::Max(max, v3, max);
|
||||
|
||||
// Position differences p1->p2 and p1->p3
|
||||
Vector3 v = v2 - v1, w = v3 - v1;
|
||||
|
||||
// Texture offset p1->p2 and p1->p3
|
||||
float sx = meshTex[p1].X - meshTex[p0].X, sy = meshTex[p1].Y - meshTex[p0].Y;
|
||||
float tx = meshTex[p2].X - meshTex[p0].X, ty = meshTex[p2].Y - meshTex[p0].Y;
|
||||
const float dirCorrection = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f;
|
||||
|
||||
// When t1, t2, t3 in same position in UV space, just use default UV direction
|
||||
if (sx * ty == sy * tx)
|
||||
{
|
||||
sx = 0.0;
|
||||
sy = 1.0;
|
||||
tx = 1.0;
|
||||
ty = 0.0;
|
||||
}
|
||||
|
||||
// Tangent points in the direction where to positive X axis of the texture coord's would point in model space
|
||||
// Bitangent's points along the positive Y axis of the texture coord's, respectively
|
||||
Vector3 tangent, bitangent;
|
||||
tangent.X = (w.X * sy - v.X * ty) * dirCorrection;
|
||||
tangent.Y = (w.Y * sy - v.Y * ty) * dirCorrection;
|
||||
tangent.Z = (w.Z * sy - v.Z * ty) * dirCorrection;
|
||||
bitangent.X = (w.X * sx - v.X * tx) * dirCorrection;
|
||||
bitangent.Y = (w.Y * sx - v.Y * tx) * dirCorrection;
|
||||
bitangent.Z = (w.Z * sx - v.Z * tx) * dirCorrection;
|
||||
|
||||
// Store for every vertex of that face
|
||||
for (int32 b = 0; b < 3; b++)
|
||||
{
|
||||
const int32 p = Indices[i + b];
|
||||
|
||||
// Project tangent and bitangent into the plane formed by the vertex' normal
|
||||
Vector3 localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
|
||||
Vector3 localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
|
||||
localTangent.Normalize();
|
||||
localBitangent.Normalize();
|
||||
|
||||
// Reconstruct tangent according to normal and bitangent when it's infinite or NaN
|
||||
if (localTangent.IsNanOrInfinity())
|
||||
{
|
||||
localTangent = meshNorm[p] ^ localBitangent;
|
||||
localTangent.Normalize();
|
||||
}
|
||||
|
||||
// Write data into the mesh
|
||||
meshTang[p] = localTangent;
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_SPARIAL_SORT
|
||||
// Set up a SpatialSort to quickly find all vertices close to a given position
|
||||
Assimp::SpatialSort vertexFinder;
|
||||
vertexFinder.Fill((const aiVector3D*)Positions.Get(), vertexCount, sizeof(Vector3));
|
||||
std::vector<uint32> verticesFound(16);
|
||||
#else
|
||||
Array<int32> verticesFound(16);
|
||||
#endif
|
||||
|
||||
const float posEpsilon = (max - min).Length() * 1e-4f;
|
||||
const float limit = cosf(smoothingAngle * DegreesToRadians);
|
||||
Array<int32> closeVertices;
|
||||
|
||||
// In the second pass we now smooth out all tangents at the same local position if they are not too far off
|
||||
for (int32 a = 0; a < vertexCount; a++)
|
||||
{
|
||||
if (vertexDone[a])
|
||||
continue;
|
||||
|
||||
const Vector3 origPos = Positions[a];
|
||||
const Vector3 origNorm = Normals[a];
|
||||
const Vector3 origTang = Tangents[a];
|
||||
closeVertices.Clear();
|
||||
|
||||
// Find all vertices close to that position
|
||||
#if USE_SPARIAL_SORT
|
||||
vertexFinder.FindPositions(*(aiVector3D*)&origPos, posEpsilon, verticesFound);
|
||||
const int32 verticesFoundCount = (int32)verticesFound.size();
|
||||
#else
|
||||
FindPositions(origPos, posEpsilon, verticesFound);
|
||||
const int32 verticesFoundCount = verticesFound.Count();
|
||||
#endif
|
||||
|
||||
closeVertices.EnsureCapacity(verticesFoundCount + 5);
|
||||
closeVertices.Add(a);
|
||||
|
||||
// Look among them for other vertices sharing the same normal and a close-enough tangent
|
||||
for (int32 b = 0; b < verticesFoundCount; b++)
|
||||
{
|
||||
const int32 idx = verticesFound[b];
|
||||
|
||||
if (vertexDone[idx])
|
||||
continue;
|
||||
if (meshNorm[idx] * origNorm < angleEpsilon)
|
||||
continue;
|
||||
if (meshTang[idx] * origTang < limit)
|
||||
continue;
|
||||
|
||||
// It's similar enough -> add it to the smoothing group
|
||||
closeVertices.Add(idx);
|
||||
vertexDone.Set(idx, true);
|
||||
}
|
||||
|
||||
// Smooth the tangents of all vertices that were found to be close enough
|
||||
Vector3 smoothTangent(0, 0, 0);
|
||||
for (int32 b = 0; b < closeVertices.Count(); b++)
|
||||
{
|
||||
smoothTangent += meshTang[closeVertices[b]];
|
||||
}
|
||||
smoothTangent.Normalize();
|
||||
|
||||
// Write it back into all affected tangents
|
||||
for (int32 b = 0; b < closeVertices.Count(); b++)
|
||||
{
|
||||
meshTang[closeVertices[b]] = smoothTangent;
|
||||
}
|
||||
}
|
||||
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
LOG(Info, "Generated tangents for mesh in {0}s ({1} vertices, {2} indices)", Utilities::RoundTo2DecimalPlaces(endTime - startTime), vertexCount, indexCount);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MeshData::ImproveCacheLocality()
|
||||
{
|
||||
// The algorithm is roughly basing on this paper (except without overdraw reduction):
|
||||
// http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf
|
||||
|
||||
// Configured size of the cache for the vertex buffer used by the algorithm (size is in vertices count, it's not a stride)
|
||||
const int32 VertexCacheSize = 12;
|
||||
|
||||
if (Positions.IsEmpty() || Indices.IsEmpty() || Positions.Count() <= VertexCacheSize)
|
||||
return;
|
||||
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
const auto vertexCount = Positions.Count();
|
||||
const auto indexCount = Indices.Count();
|
||||
const uint32* const pcEnd = Indices.Get() + Indices.Count();
|
||||
|
||||
// First we need to build a vertex-triangle adjacency list
|
||||
VertexTriangleAdjacency adj(Indices.Get(), indexCount, vertexCount, true);
|
||||
|
||||
// Build a list to store per-vertex caching time stamps
|
||||
uint32* const piCachingStamps = NewArray<uint32>(vertexCount);
|
||||
Platform::MemoryClear(piCachingStamps, vertexCount * sizeof(uint32));
|
||||
|
||||
// Allocate an empty output index buffer. We store the output indices in one large array.
|
||||
// Since the number of triangles won't change the input faces can be reused. This is how
|
||||
// We save thousands of redundant mini allocations for aiFace::mIndices
|
||||
const uint32 iIdxCnt = indexCount;
|
||||
uint32* const piIBOutput = NewArray<uint32>(iIdxCnt);
|
||||
uint32* piCSIter = piIBOutput;
|
||||
|
||||
// Allocate the flag array to hold the information
|
||||
// Whether a face has already been emitted or not
|
||||
std::vector<bool> abEmitted(indexCount / 3, false);
|
||||
|
||||
// Dead-end vertex index stack
|
||||
std::stack<uint32, std::vector<uint32>> sDeadEndVStack;
|
||||
|
||||
// Create a copy of the piNumTriPtr buffer
|
||||
uint32* const piNumTriPtr = adj.LiveTriangles;
|
||||
const std::vector<uint32> piNumTriPtrNoModify(piNumTriPtr, piNumTriPtr + vertexCount);
|
||||
|
||||
// Get the largest number of referenced triangles and allocate the "candidate buffer"
|
||||
uint32 iMaxRefTris = 0;
|
||||
{
|
||||
const uint32* piCur = adj.LiveTriangles;
|
||||
const uint32* const piCurEnd = adj.LiveTriangles + vertexCount;
|
||||
for (; piCur != piCurEnd; piCur++)
|
||||
{
|
||||
iMaxRefTris = Math::Max(iMaxRefTris, *piCur);
|
||||
}
|
||||
}
|
||||
ASSERT(iMaxRefTris > 0);
|
||||
uint32* piCandidates = NewArray<uint32>(iMaxRefTris * 3);
|
||||
uint32 iCacheMisses = 0;
|
||||
|
||||
int ivdx = 0;
|
||||
int ics = 1;
|
||||
int iStampCnt = VertexCacheSize + 1;
|
||||
while (ivdx >= 0)
|
||||
{
|
||||
uint32 icnt = piNumTriPtrNoModify[ivdx];
|
||||
uint32* piList = adj.GetAdjacentTriangles(ivdx);
|
||||
uint32* piCurCandidate = piCandidates;
|
||||
|
||||
// Get all triangles in the neighborhood
|
||||
for (uint32 tri = 0; tri < icnt; tri++)
|
||||
{
|
||||
// If they have not yet been emitted, add them to the output IB
|
||||
const uint32 fidx = *piList++;
|
||||
if (!abEmitted[fidx])
|
||||
{
|
||||
// So iterate through all vertices of the current triangle
|
||||
uint32* pcFace = &Indices[fidx * 3];
|
||||
for (uint32 *p = pcFace, *p2 = pcFace + 3; p != p2; p++)
|
||||
{
|
||||
const uint32 dp = *p;
|
||||
|
||||
// The current vertex won't have any free triangles after this step
|
||||
if (ivdx != (int)dp)
|
||||
{
|
||||
// Append the vertex to the dead-end stack
|
||||
sDeadEndVStack.push(dp);
|
||||
|
||||
// Register as candidate for the next step
|
||||
*piCurCandidate++ = dp;
|
||||
|
||||
// Decrease the per-vertex triangle counts
|
||||
piNumTriPtr[dp]--;
|
||||
}
|
||||
|
||||
// Append the vertex to the output index buffer
|
||||
*piCSIter++ = dp;
|
||||
|
||||
// If the vertex is not yet in cache, set its cache count
|
||||
if (iStampCnt - piCachingStamps[dp] > VertexCacheSize)
|
||||
{
|
||||
piCachingStamps[dp] = iStampCnt++;
|
||||
iCacheMisses++;
|
||||
}
|
||||
}
|
||||
|
||||
// Flag triangle as emitted
|
||||
abEmitted[fidx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The vertex has now no living adjacent triangles anymore
|
||||
piNumTriPtr[ivdx] = 0;
|
||||
|
||||
// Get next fanning vertex
|
||||
ivdx = -1;
|
||||
int max_priority = -1;
|
||||
for (uint32* piCur = piCandidates; piCur != piCurCandidate; ++piCur)
|
||||
{
|
||||
const uint32 dp = *piCur;
|
||||
|
||||
// Must have live triangles
|
||||
if (piNumTriPtr[dp] > 0)
|
||||
{
|
||||
int priority = 0;
|
||||
|
||||
// Will the vertex be in cache, even after fanning occurs?
|
||||
uint32 tmp;
|
||||
if ((tmp = iStampCnt - piCachingStamps[dp]) + 2 * piNumTriPtr[dp] <= VertexCacheSize)
|
||||
{
|
||||
priority = tmp;
|
||||
}
|
||||
|
||||
// Keep best candidate
|
||||
if (priority > max_priority)
|
||||
{
|
||||
max_priority = priority;
|
||||
ivdx = dp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Did we reach a dead end?
|
||||
if (-1 == ivdx)
|
||||
{
|
||||
// Need to get a non-local vertex for which we have a good chance that it is still in the cache
|
||||
while (!sDeadEndVStack.empty())
|
||||
{
|
||||
uint32 iCachedIdx = sDeadEndVStack.top();
|
||||
sDeadEndVStack.pop();
|
||||
if (piNumTriPtr[iCachedIdx] > 0)
|
||||
{
|
||||
ivdx = iCachedIdx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (-1 == ivdx)
|
||||
{
|
||||
// Well, there isn't such a vertex. Simply get the next vertex in input order and hope it is not too bad
|
||||
while (ics < (int)vertexCount)
|
||||
{
|
||||
ics++;
|
||||
if (piNumTriPtr[ics] > 0)
|
||||
{
|
||||
ivdx = ics;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the output index buffer back to the input array
|
||||
piCSIter = piIBOutput;
|
||||
for (uint32* pcFace = Indices.Get(); pcFace != pcEnd; pcFace += 3)
|
||||
{
|
||||
pcFace[0] = *piCSIter++;
|
||||
pcFace[1] = *piCSIter++;
|
||||
pcFace[2] = *piCSIter++;
|
||||
}
|
||||
|
||||
// Delete temporary storage
|
||||
Allocator::Free(piCachingStamps);
|
||||
Allocator::Free(piIBOutput);
|
||||
Allocator::Free(piCandidates);
|
||||
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
LOG(Info, "Cache relevant optimzie for {0} vertices and {1} indices. Average output ACMR is {2}. Time: {3}s", vertexCount, indexCount, (float)iCacheMisses / indexCount / 3, Utilities::RoundTo2DecimalPlaces(endTime - startTime));
|
||||
}
|
||||
|
||||
float MeshData::CalculateTrianglesArea() const
|
||||
{
|
||||
float sum = 0;
|
||||
// TODO: use SIMD
|
||||
for (int32 i = 0; i + 2 < Indices.Count(); i += 3)
|
||||
{
|
||||
const Vector3 v1 = Positions[Indices[i + 0]];
|
||||
const Vector3 v2 = Positions[Indices[i + 1]];
|
||||
const Vector3 v3 = Positions[Indices[i + 2]];
|
||||
sum += Vector3::TriangleArea(v1, v2, v3);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
#endif
|
||||
839
Source/Engine/Graphics/Models/ModelData.cpp
Normal file
839
Source/Engine/Graphics/Models/ModelData.cpp
Normal file
@@ -0,0 +1,839 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ModelData.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Serialization/WriteStream.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
|
||||
|
||||
void MeshData::Clear()
|
||||
{
|
||||
MaterialSlotIndex = 0;
|
||||
NodeIndex = 0;
|
||||
Positions.Clear();
|
||||
Indices.Clear();
|
||||
UVs.Clear();
|
||||
Normals.Clear();
|
||||
Tangents.Clear();
|
||||
LightmapUVs.Clear();
|
||||
Colors.Clear();
|
||||
BlendIndices.Clear();
|
||||
BlendWeights.Clear();
|
||||
BlendShapes.Clear();
|
||||
}
|
||||
|
||||
void MeshData::EnsureCapacity(int32 vertices, int32 indices, bool preserveContents, bool withColors, bool withSkin)
|
||||
{
|
||||
Positions.EnsureCapacity(vertices, preserveContents);
|
||||
Indices.EnsureCapacity(indices, preserveContents);
|
||||
UVs.EnsureCapacity(vertices, preserveContents);
|
||||
Normals.EnsureCapacity(vertices, preserveContents);
|
||||
Tangents.EnsureCapacity(vertices, preserveContents);
|
||||
LightmapUVs.EnsureCapacity(vertices, preserveContents);
|
||||
Colors.EnsureCapacity(withColors ? vertices : 0, preserveContents);
|
||||
BlendIndices.EnsureCapacity(withSkin ? vertices : 0, preserveContents);
|
||||
BlendWeights.EnsureCapacity(withSkin ? vertices : 0, preserveContents);
|
||||
}
|
||||
|
||||
void MeshData::SwapBuffers(MeshData& other)
|
||||
{
|
||||
Positions.Swap(other.Positions);
|
||||
Indices.Swap(other.Indices);
|
||||
UVs.Swap(other.UVs);
|
||||
Normals.Swap(other.Normals);
|
||||
Tangents.Swap(other.Tangents);
|
||||
LightmapUVs.Swap(other.LightmapUVs);
|
||||
Colors.Swap(other.Colors);
|
||||
BlendIndices.Swap(other.BlendIndices);
|
||||
BlendWeights.Swap(other.BlendWeights);
|
||||
BlendShapes.Swap(other.BlendShapes);
|
||||
}
|
||||
|
||||
void MeshData::Release()
|
||||
{
|
||||
MaterialSlotIndex = 0;
|
||||
Positions.Resize(0);
|
||||
Indices.Resize(0);
|
||||
UVs.Resize(0);
|
||||
Normals.Resize(0);
|
||||
Tangents.Resize(0);
|
||||
LightmapUVs.Resize(0);
|
||||
Colors.Resize(0);
|
||||
BlendIndices.Resize(0);
|
||||
BlendWeights.Resize(0);
|
||||
BlendShapes.Resize(0);
|
||||
}
|
||||
|
||||
void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount)
|
||||
{
|
||||
Positions.Resize(verticesCount, false);
|
||||
UVs.Resize(verticesCount, false);
|
||||
Normals.Resize(verticesCount, false);
|
||||
Tangents.Resize(verticesCount, false);
|
||||
LightmapUVs.Resize(verticesCount, false);
|
||||
Colors.Resize(0);
|
||||
BlendIndices.Resize(0);
|
||||
BlendWeights.Resize(0);
|
||||
BlendShapes.Resize(0);
|
||||
|
||||
for (uint32 i = 0; i < verticesCount; i++)
|
||||
{
|
||||
Positions[i] = vertices->Position;
|
||||
UVs[i] = vertices->TexCoord.ToVector2();
|
||||
Normals[i] = vertices->Normal.ToVector3() * 2.0f - 1.0f;
|
||||
Tangents[i] = vertices->Tangent.ToVector3() * 2.0f - 1.0f;
|
||||
LightmapUVs[i] = vertices->LightmapUVs.ToVector2();
|
||||
Colors[i] = Color(vertices->Color);
|
||||
|
||||
vertices++;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::InitFromModelVertices(ModelVertex18* vertices, uint32 verticesCount)
|
||||
{
|
||||
Positions.Resize(verticesCount, false);
|
||||
UVs.Resize(verticesCount, false);
|
||||
Normals.Resize(verticesCount, false);
|
||||
Tangents.Resize(verticesCount, false);
|
||||
LightmapUVs.Resize(verticesCount, false);
|
||||
Colors.Resize(0);
|
||||
BlendIndices.Resize(0);
|
||||
BlendWeights.Resize(0);
|
||||
BlendShapes.Resize(0);
|
||||
|
||||
for (uint32 i = 0; i < verticesCount; i++)
|
||||
{
|
||||
Positions[i] = vertices->Position;
|
||||
UVs[i] = vertices->TexCoord.ToVector2();
|
||||
Normals[i] = vertices->Normal.ToVector3() * 2.0f - 1.0f;
|
||||
Tangents[i] = vertices->Tangent.ToVector3() * 2.0f - 1.0f;
|
||||
LightmapUVs[i] = vertices->LightmapUVs.ToVector2();
|
||||
|
||||
vertices++;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::InitFromModelVertices(ModelVertex15* vertices, uint32 verticesCount)
|
||||
{
|
||||
Positions.Resize(verticesCount, false);
|
||||
UVs.Resize(verticesCount, false);
|
||||
Normals.Resize(verticesCount, false);
|
||||
Tangents.Resize(verticesCount, false);
|
||||
LightmapUVs.Resize(0);
|
||||
Colors.Resize(0);
|
||||
BlendIndices.Resize(0);
|
||||
BlendWeights.Resize(0);
|
||||
BlendShapes.Resize(0);
|
||||
|
||||
for (uint32 i = 0; i < verticesCount; i++)
|
||||
{
|
||||
Positions[i] = vertices->Position;
|
||||
UVs[i] = vertices->TexCoord.ToVector2();
|
||||
Normals[i] = vertices->Normal.ToVector3() * 2.0f - 1.0f;
|
||||
Tangents[i] = vertices->Tangent.ToVector3() * 2.0f - 1.0f;
|
||||
|
||||
vertices++;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, uint32 verticesCount)
|
||||
{
|
||||
Positions.Resize(verticesCount, false);
|
||||
UVs.Resize(verticesCount, false);
|
||||
Normals.Resize(verticesCount, false);
|
||||
Tangents.Resize(verticesCount, false);
|
||||
LightmapUVs.Resize(verticesCount, false);
|
||||
Colors.Resize(0);
|
||||
BlendIndices.Resize(0);
|
||||
BlendWeights.Resize(0);
|
||||
BlendShapes.Resize(0);
|
||||
|
||||
for (uint32 i = 0; i < verticesCount; i++)
|
||||
{
|
||||
Positions[i] = vb0->Position;
|
||||
UVs[i] = vb1->TexCoord.ToVector2();
|
||||
Normals[i] = vb1->Normal.ToVector3() * 2.0f - 1.0f;
|
||||
Tangents[i] = vb1->Tangent.ToVector3() * 2.0f - 1.0f;
|
||||
LightmapUVs[i] = vb1->LightmapUVs.ToVector2();
|
||||
|
||||
vb0++;
|
||||
vb1++;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount)
|
||||
{
|
||||
Positions.Resize(verticesCount, false);
|
||||
UVs.Resize(verticesCount, false);
|
||||
Normals.Resize(verticesCount, false);
|
||||
Tangents.Resize(verticesCount, false);
|
||||
LightmapUVs.Resize(verticesCount, false);
|
||||
if (vb2)
|
||||
{
|
||||
Colors.Resize(verticesCount, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Colors.Resize(0);
|
||||
}
|
||||
BlendIndices.Resize(0);
|
||||
BlendWeights.Resize(0);
|
||||
BlendShapes.Resize(0);
|
||||
|
||||
for (uint32 i = 0; i < verticesCount; i++)
|
||||
{
|
||||
Positions[i] = vb0->Position;
|
||||
UVs[i] = vb1->TexCoord.ToVector2();
|
||||
Normals[i] = vb1->Normal.ToVector3() * 2.0f - 1.0f;
|
||||
Tangents[i] = vb1->Tangent.ToVector3() * 2.0f - 1.0f;
|
||||
LightmapUVs[i] = vb1->LightmapUVs.ToVector2();
|
||||
if (vb2)
|
||||
{
|
||||
Colors[i] = Color(vb2->Color);
|
||||
vb2++;
|
||||
}
|
||||
|
||||
vb0++;
|
||||
vb1++;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::InitFromModelVertices(VB0ElementType15* vb0, VB1ElementType15* vb1, uint32 verticesCount)
|
||||
{
|
||||
Positions.Resize(verticesCount, false);
|
||||
UVs.Resize(verticesCount, false);
|
||||
Normals.Resize(verticesCount, false);
|
||||
Tangents.Resize(verticesCount, false);
|
||||
LightmapUVs.Resize(0, false);
|
||||
Colors.Resize(0);
|
||||
BlendIndices.Resize(0);
|
||||
BlendWeights.Resize(0);
|
||||
BlendShapes.Resize(0);
|
||||
|
||||
for (uint32 i = 0; i < verticesCount; i++)
|
||||
{
|
||||
Positions[i] = vb0->Position;
|
||||
UVs[i] = vb1->TexCoord.ToVector2();
|
||||
Normals[i] = vb1->Normal.ToVector3() * 2.0f - 1.0f;
|
||||
Tangents[i] = vb1->Tangent.ToVector3() * 2.0f - 1.0f;
|
||||
|
||||
vb0++;
|
||||
vb1++;
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::SetIndexBuffer(void* data, uint32 indicesCount)
|
||||
{
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
|
||||
Indices.Resize(indicesCount, false);
|
||||
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
auto ib16 = static_cast<uint16*>(data);
|
||||
for (uint32 a = 0; a < indicesCount; a++)
|
||||
Indices[a] = *ib16++;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto ib32 = static_cast<uint32*>(data);
|
||||
for (uint32 a = 0; a < indicesCount; a++)
|
||||
Indices[a] = *ib32++;
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshData::Pack2Model(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
LOG(Error, "Invalid input.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cache size
|
||||
uint32 verticiecCount = Positions.Count();
|
||||
uint32 indicesCount = Indices.Count();
|
||||
uint32 trianglesCount = indicesCount / 3;
|
||||
bool use16Bit = indicesCount <= MAX_uint16;
|
||||
if (verticiecCount == 0 || trianglesCount == 0 || indicesCount % 3 != 0)
|
||||
{
|
||||
LOG(Error, "Empty mesh! Triangles: {0}, Verticies: {1}.", trianglesCount, verticiecCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate data structure
|
||||
bool hasUVs = UVs.HasItems();
|
||||
if (hasUVs && UVs.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("UVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasNormals = Normals.HasItems();
|
||||
if (hasNormals && Normals.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
|
||||
return true;
|
||||
}
|
||||
bool hasTangents = Tangents.HasItems();
|
||||
if (hasTangents && Tangents.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
|
||||
return true;
|
||||
}
|
||||
bool hasLightmapUVs = LightmapUVs.HasItems();
|
||||
if (hasLightmapUVs && LightmapUVs.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasVertexColors = Colors.HasItems();
|
||||
if (hasVertexColors && Colors.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Colors"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vertices
|
||||
stream->WriteUint32(verticiecCount);
|
||||
|
||||
// Triangles
|
||||
stream->WriteUint32(trianglesCount);
|
||||
|
||||
// Vertex Buffer 0
|
||||
stream->WriteBytes(Positions.Get(), sizeof(Vector3) * verticiecCount);
|
||||
|
||||
// Vertex Buffer 1
|
||||
VB1ElementType vb1;
|
||||
for (uint32 i = 0; i < verticiecCount; i++)
|
||||
{
|
||||
// Get vertex components
|
||||
Vector2 uv = hasUVs ? UVs[i] : Vector2::Zero;
|
||||
Vector3 normal = hasNormals ? Normals[i] : Vector3::UnitZ;
|
||||
Vector3 tangent = hasTangents ? Tangents[i] : Vector3::UnitX;
|
||||
Vector2 lightmapUV = hasLightmapUVs ? LightmapUVs[i] : Vector2::Zero;
|
||||
|
||||
// Calculate bitangent sign
|
||||
Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
|
||||
byte sign = static_cast<byte>(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||
|
||||
// Write vertex
|
||||
vb1.TexCoord = Half2(uv);
|
||||
vb1.Normal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
||||
vb1.LightmapUVs = Half2(lightmapUV);
|
||||
stream->Write(&vb1);
|
||||
|
||||
// Pack TBN matrix into a quaternion
|
||||
/*Quaternion quaternionTBN;
|
||||
bool invertedHandednessTBN;
|
||||
CalculateQuaternionFromTBN(tangent, bitangent, normal, &quaternionTBN, &invertedHandednessTBN);
|
||||
quaternionTBN.Normalize();
|
||||
uint32 packedQuaternionTBN = QuantizeNormalizedQuaternionWithHandedness(quaternionTBN, invertedHandednessTBN);
|
||||
|
||||
Vector4 unpackedQuaternionTBN = Vector4(quaternionTBN.X, quaternionTBN.Y, quaternionTBN.Z, ((invertedHandednessTBN ? 0.0f : 128.0f) + (127.0f * (quaternionTBN.W * 0.5f + 0.5f))) / 255.0f);
|
||||
|
||||
//lods.WriteUint32(packedQuaternionTBN);
|
||||
//lods.WriteVector4(unpackedQuaternionTBN);
|
||||
*/
|
||||
}
|
||||
|
||||
// Vertex Buffer 2
|
||||
stream->WriteBool(hasVertexColors);
|
||||
if (hasVertexColors)
|
||||
{
|
||||
VB2ElementType vb2;
|
||||
for (uint32 i = 0; i < verticiecCount; i++)
|
||||
{
|
||||
vb2.Color = Color32(Colors[i]);
|
||||
stream->Write(&vb2);
|
||||
}
|
||||
}
|
||||
|
||||
// Index Buffer
|
||||
if (use16Bit)
|
||||
{
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
stream->WriteUint16(Indices[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream->WriteBytes(Indices.Get(), sizeof(uint32) * indicesCount);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshData::Pack2SkinnedModel(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
LOG(Error, "Invalid input.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cache size
|
||||
uint32 verticiecCount = Positions.Count();
|
||||
uint32 indicesCount = Indices.Count();
|
||||
uint32 trianglesCount = indicesCount / 3;
|
||||
bool use16Bit = indicesCount <= MAX_uint16;
|
||||
if (verticiecCount == 0 || trianglesCount == 0 || indicesCount % 3 != 0)
|
||||
{
|
||||
LOG(Error, "Empty mesh! Triangles: {0}, Verticies: {1}.", trianglesCount, verticiecCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate data structure
|
||||
bool hasUVs = UVs.HasItems();
|
||||
if (hasUVs && UVs.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT( "UVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasNormals = Normals.HasItems();
|
||||
if (hasNormals && Normals.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
|
||||
return true;
|
||||
}
|
||||
bool hasTangents = Tangents.HasItems();
|
||||
if (hasTangents && Tangents.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
|
||||
return true;
|
||||
}
|
||||
if (BlendIndices.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices"));
|
||||
return true;
|
||||
}
|
||||
if (BlendWeights.Count() != verticiecCount)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendWeights"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vertices
|
||||
stream->WriteUint32(verticiecCount);
|
||||
|
||||
// Triangles
|
||||
stream->WriteUint32(trianglesCount);
|
||||
|
||||
// Blend Shapes
|
||||
stream->WriteUint16(BlendShapes.Count());
|
||||
for (const auto& blendShape : BlendShapes)
|
||||
{
|
||||
stream->WriteBool(blendShape.UseNormals);
|
||||
stream->WriteUint32(blendShape.MinVertexIndex);
|
||||
stream->WriteUint32(blendShape.MaxVertexIndex);
|
||||
stream->WriteUint32(blendShape.Vertices.Count());
|
||||
stream->WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
|
||||
// Vertex Buffer
|
||||
VB0SkinnedElementType vb;
|
||||
for (uint32 i = 0; i < verticiecCount; i++)
|
||||
{
|
||||
// Get vertex components
|
||||
Vector2 uv = hasUVs ? UVs[i] : Vector2::Zero;
|
||||
Vector3 normal = hasNormals ? Normals[i] : Vector3::UnitZ;
|
||||
Vector3 tangent = hasTangents ? Tangents[i] : Vector3::UnitX;
|
||||
Int4 blendIndices = BlendIndices[i];
|
||||
Vector4 blendWeights = BlendWeights[i];
|
||||
|
||||
// Calculate bitangent sign
|
||||
Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
|
||||
byte sign = static_cast<byte>(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||
|
||||
// Write vertex
|
||||
vb.Position = Positions[i];
|
||||
vb.TexCoord = Half2(uv);
|
||||
vb.Normal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
vb.Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
||||
vb.BlendIndices = Color32(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
|
||||
vb.BlendWeights = Half4(blendWeights);
|
||||
stream->Write(&vb);
|
||||
}
|
||||
|
||||
// Index Buffer
|
||||
if (use16Bit)
|
||||
{
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
stream->WriteUint16(Indices[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream->WriteBytes(Indices.Get(), sizeof(uint32) * indicesCount);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MeshData::TransformBuffer(const Matrix& matrix)
|
||||
{
|
||||
// Compute matrix inverse transpose
|
||||
Matrix inverseTransposeMatrix;
|
||||
Matrix::Invert(matrix, inverseTransposeMatrix);
|
||||
Matrix::Transpose(inverseTransposeMatrix, inverseTransposeMatrix);
|
||||
|
||||
// Transform positions
|
||||
for (int32 i = 0; i < Positions.Count(); i++)
|
||||
{
|
||||
Vector3::Transform(Positions[i], matrix, Positions[i]);
|
||||
}
|
||||
|
||||
// Transform normals and tangents
|
||||
for (int32 i = 0; i < Normals.Count(); i++)
|
||||
{
|
||||
Vector3::TransformNormal(Normals[i], inverseTransposeMatrix, Normals[i]);
|
||||
Normals[i].Normalize();
|
||||
}
|
||||
for (int32 i = 0; i < Tangents.Count(); i++)
|
||||
{
|
||||
Vector3::TransformNormal(Tangents[i], inverseTransposeMatrix, Tangents[i]);
|
||||
Tangents[i].Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::Merge(MeshData& other)
|
||||
{
|
||||
// Merge index buffer (and remap indices)
|
||||
const uint32 vertexIndexOffset = Positions.Count();
|
||||
const int32 indicesStart = Indices.Count();
|
||||
const int32 indicesEnd = indicesStart + other.Indices.Count();
|
||||
Indices.Add(other.Indices);
|
||||
for (int32 i = indicesStart; i < indicesEnd; i++)
|
||||
{
|
||||
Indices[i] += vertexIndexOffset;
|
||||
}
|
||||
|
||||
// Merge vertex buffer
|
||||
#define MERGE(item, defautValue) \
|
||||
if (item.HasItems() && other.item.HasItems()) \
|
||||
item.Add(other.item); \
|
||||
else if (item.HasItems() && !other.item.HasItems()) \
|
||||
for (int32 i = 0; i < other.Positions.Count(); i++) item.Add(defautValue); \
|
||||
else if (!item.HasItems() && other.item.HasItems()) \
|
||||
for (int32 i = 0; i < Positions.Count(); i++) item.Add(defautValue)
|
||||
MERGE(Positions, Vector3::Zero);
|
||||
MERGE(UVs, Vector2::Zero);
|
||||
MERGE(Normals, Vector3::Forward);
|
||||
MERGE(Tangents, Vector3::Right);
|
||||
MERGE(LightmapUVs, Vector2::Zero);
|
||||
MERGE(Colors, Color::Black);
|
||||
MERGE(BlendIndices, Int4::Zero);
|
||||
MERGE(BlendWeights, Vector4::Zero);
|
||||
#undef MERGE
|
||||
|
||||
// Merge blend shapes
|
||||
for (auto& otherBlendShape : other.BlendShapes)
|
||||
{
|
||||
BlendShape* blendShape = nullptr;
|
||||
for (int32 i = 0; i < BlendShapes.Count(); i++)
|
||||
{
|
||||
if (BlendShapes[i].Name == otherBlendShape.Name)
|
||||
{
|
||||
blendShape = &BlendShapes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!blendShape)
|
||||
{
|
||||
blendShape = &BlendShapes.AddOne();
|
||||
blendShape->Name = otherBlendShape.Name;
|
||||
blendShape->Weight = otherBlendShape.Weight;
|
||||
}
|
||||
const int32 startIndex = blendShape->Vertices.Count();
|
||||
blendShape->Vertices.Add(otherBlendShape.Vertices);
|
||||
for (int32 i = startIndex; i < blendShape->Vertices.Count(); i++)
|
||||
{
|
||||
blendShape->Vertices[i].VertexIndex += vertexIndexOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelData::CalculateLODsScreenSizes()
|
||||
{
|
||||
const float autoComputeLodPowerBase = 0.5f;
|
||||
const int32 lodCount = LODs.Count();
|
||||
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
if (lodIndex == 0)
|
||||
{
|
||||
lod.ScreenSize = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
lod.ScreenSize = Math::Pow(autoComputeLodPowerBase, (float)lodIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MinScreenSize = 0.01f;
|
||||
}
|
||||
|
||||
void ModelData::TransformBuffer(const Matrix& matrix)
|
||||
{
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
lod.Meshes[meshIndex]->TransformBuffer(matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelData::Pack2ModelHeader(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
Log::ArgumentNullException();
|
||||
return true;
|
||||
}
|
||||
const int32 lodCount = GetLODsCount();
|
||||
if (lodCount == 0 || lodCount > MODEL_MAX_LODS)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException();
|
||||
return true;
|
||||
}
|
||||
if (Materials.IsEmpty())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("MaterialSlots"), TEXT("Material slots collection cannot be empty."));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
stream->WriteInt32(Materials.Count());
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < Materials.Count(); materialSlotIndex++)
|
||||
{
|
||||
auto& slot = Materials[materialSlotIndex];
|
||||
|
||||
stream->Write(&slot.AssetID);
|
||||
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
|
||||
stream->WriteString(slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
stream->WriteByte(lodCount);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream->WriteFloat(lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
if (meshes == 0 || meshes > MODEL_MAX_MESHES)
|
||||
{
|
||||
LOG(Warning, "Too many meshes per LOD.");
|
||||
return true;
|
||||
}
|
||||
stream->WriteUint16(meshes);
|
||||
|
||||
// For each mesh
|
||||
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
|
||||
{
|
||||
auto& mesh = *lod.Meshes[meshIndex];
|
||||
|
||||
// Material Slot
|
||||
stream->WriteInt32(mesh.MaterialSlotIndex);
|
||||
|
||||
// Box
|
||||
BoundingBox box;
|
||||
mesh.CalculateBox(box);
|
||||
stream->Write(&box);
|
||||
|
||||
// Sphere
|
||||
BoundingSphere sphere;
|
||||
mesh.CalculateSphere(sphere);
|
||||
stream->Write(&sphere);
|
||||
|
||||
// TODO: calculate Sphere and Box at once - make it faster using SSE
|
||||
|
||||
// Has Lightmap UVs
|
||||
stream->WriteBool(mesh.LightmapUVs.HasItems());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
Log::ArgumentNullException();
|
||||
return true;
|
||||
}
|
||||
const int32 lodCount = GetLODsCount();
|
||||
if (lodCount == 0 || lodCount > MODEL_MAX_LODS)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException();
|
||||
return true;
|
||||
}
|
||||
if (Materials.IsEmpty())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("MaterialSlots"), TEXT("Material slots collection cannot be empty."));
|
||||
return true;
|
||||
}
|
||||
if (!HasSkeleton())
|
||||
{
|
||||
Log::InvalidOperationException(TEXT("Missing skeleton."));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
stream->WriteInt32(Materials.Count());
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < Materials.Count(); materialSlotIndex++)
|
||||
{
|
||||
auto& slot = Materials[materialSlotIndex];
|
||||
|
||||
stream->Write(&slot.AssetID);
|
||||
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
|
||||
stream->WriteString(slot.Name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
stream->WriteByte(lodCount);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
// Screen Size
|
||||
stream->WriteFloat(lod.ScreenSize);
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
if (meshes > MODEL_MAX_MESHES)
|
||||
{
|
||||
LOG(Warning, "Too many meshes per LOD.");
|
||||
return true;
|
||||
}
|
||||
stream->WriteUint16(meshes);
|
||||
|
||||
// For each mesh
|
||||
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
|
||||
{
|
||||
auto& mesh = *lod.Meshes[meshIndex];
|
||||
|
||||
// Material Slot
|
||||
stream->WriteInt32(mesh.MaterialSlotIndex);
|
||||
|
||||
// Box
|
||||
BoundingBox box;
|
||||
mesh.CalculateBox(box);
|
||||
stream->Write(&box);
|
||||
|
||||
// Sphere
|
||||
BoundingSphere sphere;
|
||||
mesh.CalculateSphere(sphere);
|
||||
stream->Write(&sphere);
|
||||
|
||||
// TODO: calculate Sphere and Box at once - make it faster using SSE
|
||||
|
||||
// Blend Shapes
|
||||
const int32 blendShapes = mesh.BlendShapes.Count();
|
||||
stream->WriteUint16(blendShapes);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++)
|
||||
{
|
||||
auto& blendShape = mesh.BlendShapes[blendShapeIndex];
|
||||
stream->WriteString(blendShape.Name, 13);
|
||||
stream->WriteFloat(blendShape.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
stream->WriteInt32(Skeleton.Nodes.Count());
|
||||
|
||||
// For each node
|
||||
for (int32 nodeIndex = 0; nodeIndex < Skeleton.Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
auto& node = Skeleton.Nodes[nodeIndex];
|
||||
|
||||
stream->Write(&node.ParentIndex);
|
||||
stream->Write(&node.LocalTransform);
|
||||
stream->WriteString(node.Name, 71);
|
||||
}
|
||||
|
||||
stream->WriteInt32(Skeleton.Bones.Count());
|
||||
|
||||
// For each bone
|
||||
for (int32 boneIndex = 0; boneIndex < Skeleton.Bones.Count(); boneIndex++)
|
||||
{
|
||||
auto& bone = Skeleton.Bones[boneIndex];
|
||||
|
||||
stream->Write(&bone.ParentIndex);
|
||||
stream->Write(&bone.NodeIndex);
|
||||
stream->Write(&bone.LocalTransform);
|
||||
stream->Write(&bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelData::Pack2AnimationHeader(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
{
|
||||
Log::ArgumentNullException();
|
||||
return true;
|
||||
}
|
||||
if (Animation.Duration <= ZeroTolerance || Animation.FramesPerSecond <= ZeroTolerance)
|
||||
{
|
||||
Log::InvalidOperationException(TEXT("Invalid animation duration."));
|
||||
return true;
|
||||
}
|
||||
if (Animation.Channels.IsEmpty())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty."));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Info
|
||||
stream->WriteInt32(100); // Header version (for fast version upgrades without serialization format change)
|
||||
stream->WriteDouble(Animation.Duration);
|
||||
stream->WriteDouble(Animation.FramesPerSecond);
|
||||
stream->WriteBool(Animation.EnableRootMotion);
|
||||
stream->WriteString(Animation.RootNodeName, 13);
|
||||
|
||||
// Animation channels
|
||||
stream->WriteInt32(Animation.Channels.Count());
|
||||
for (int32 i = 0; i < Animation.Channels.Count(); i++)
|
||||
{
|
||||
auto& anim = Animation.Channels[i];
|
||||
|
||||
stream->WriteString(anim.NodeName, 172);
|
||||
anim.Position.Serialize(*stream);
|
||||
anim.Rotation.Serialize(*stream);
|
||||
anim.Scale.Serialize(*stream);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
517
Source/Engine/Graphics/Models/ModelData.h
Normal file
517
Source/Engine/Graphics/Models/ModelData.h
Normal file
@@ -0,0 +1,517 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Common.h"
|
||||
#include "Engine/Core/Math/BoundingSphere.h"
|
||||
#include "Engine/Core/Math/BoundingBox.h"
|
||||
#include "Engine/Core/Math/VectorInt.h"
|
||||
#include "Engine/Serialization/Stream.h"
|
||||
#include "Engine/Graphics/Enums.h"
|
||||
#include "Types.h"
|
||||
#include "Config.h"
|
||||
#include "SkeletonData.h"
|
||||
#include "BlendShape.h"
|
||||
#include "Engine/Animations/AnimationData.h"
|
||||
|
||||
/// <summary>
|
||||
/// Data container for the common model meshes data. Supports holding all types of data related to the models pipeline.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API MeshData
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The slot index in the model materials to use during rendering.
|
||||
/// </summary>
|
||||
int32 MaterialSlotIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The model skeleton node index. Used during importing and by the animated models.
|
||||
/// </summary>
|
||||
int32 NodeIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the mesh.
|
||||
/// </summary>
|
||||
String Name;
|
||||
|
||||
/// <summary>
|
||||
/// Mesh positions buffer
|
||||
/// </summary>
|
||||
Array<Vector3> Positions;
|
||||
|
||||
/// <summary>
|
||||
/// Texture coordinates
|
||||
/// </summary>
|
||||
Array<Vector2> UVs;
|
||||
|
||||
/// <summary>
|
||||
/// Normals vector
|
||||
/// </summary>
|
||||
Array<Vector3> Normals;
|
||||
|
||||
/// <summary>
|
||||
/// Tangents vectors
|
||||
/// </summary>
|
||||
Array<Vector3> Tangents;
|
||||
|
||||
/// <summary>
|
||||
/// Mesh index buffer
|
||||
/// </summary>
|
||||
Array<uint32> Indices;
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap UVs
|
||||
/// </summary>
|
||||
Array<Vector2> LightmapUVs;
|
||||
|
||||
/// <summary>
|
||||
/// Vertex colors
|
||||
/// </summary>
|
||||
Array<Color> Colors;
|
||||
|
||||
/// <summary>
|
||||
/// Skinned mesh blend indices (max 4 per bone)
|
||||
/// </summary>
|
||||
Array<Int4> BlendIndices;
|
||||
|
||||
/// <summary>
|
||||
/// Skinned mesh index buffer (max 4 per bone)
|
||||
/// </summary>
|
||||
Array<Vector4> BlendWeights;
|
||||
|
||||
/// <summary>
|
||||
/// Blend shapes used by this mesh
|
||||
/// </summary>
|
||||
Array<BlendShape> BlendShapes;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance has any mesh data.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool HasData() const
|
||||
{
|
||||
return Indices.HasItems();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Clear arrays
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that buffers will have given space for data
|
||||
/// </summary>
|
||||
/// <param name="vertices">Amount of vertices.</param>
|
||||
/// <param name="indices">Amount of indices.</param>
|
||||
/// <param name="preserveContents">Failed if clear data otherwise will try to preserve the buffers contents.</param>
|
||||
/// <param name="withColors">True if use vertex colors buffer.</param>
|
||||
/// <param name="withSkin">True if use vertex blend indices and weights buffer.</param>
|
||||
void EnsureCapacity(int32 vertices, int32 indices, bool preserveContents = false, bool withColors = true, bool withSkin = true);
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the vertex and index buffers contents (without a data copy) with the other mesh.
|
||||
/// </summary>
|
||||
/// <param name="other">The other mesh to swap data with.</param>
|
||||
void SwapBuffers(MeshData& other);
|
||||
|
||||
/// <summary>
|
||||
/// Clean data
|
||||
/// </summary>
|
||||
void Release();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init from model vertices array
|
||||
/// </summary>
|
||||
/// <param name="vertices">Array of vertices</param>
|
||||
/// <param name="verticesCount">Amount of vertices</param>
|
||||
void InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount);
|
||||
|
||||
/// <summary>
|
||||
/// Init from model vertices array
|
||||
/// </summary>
|
||||
/// <param name="vertices">Array of vertices</param>
|
||||
/// <param name="verticesCount">Amount of vertices</param>
|
||||
void InitFromModelVertices(ModelVertex18* vertices, uint32 verticesCount);
|
||||
|
||||
/// <summary>
|
||||
/// Init from model vertices array
|
||||
/// </summary>
|
||||
/// <param name="vertices">Array of vertices</param>
|
||||
/// <param name="verticesCount">Amount of vertices</param>
|
||||
void InitFromModelVertices(ModelVertex15* vertices, uint32 verticesCount);
|
||||
|
||||
/// <summary>
|
||||
/// Init from model vertices array
|
||||
/// </summary>
|
||||
/// <param name="vb0">Array of data for vertex buffer 0</param>
|
||||
/// <param name="vb1">Array of data for vertex buffer 1</param>
|
||||
/// <param name="verticesCount">Amount of vertices</param>
|
||||
void InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, uint32 verticesCount);
|
||||
|
||||
/// <summary>
|
||||
/// Init from model vertices array
|
||||
/// </summary>
|
||||
/// <param name="vb0">Array of data for vertex buffer 0</param>
|
||||
/// <param name="vb1">Array of data for vertex buffer 1</param>
|
||||
/// <param name="vb2">Array of data for vertex buffer 2</param>
|
||||
/// <param name="verticesCount">Amount of vertices</param>
|
||||
void InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount);
|
||||
|
||||
/// <summary>
|
||||
/// Init from model vertices array
|
||||
/// </summary>
|
||||
/// <param name="vb0">Array of data for vertex buffer 0</param>
|
||||
/// <param name="vb1">Array of data for vertex buffer 1</param>
|
||||
/// <param name="verticesCount">Amount of vertices</param>
|
||||
void InitFromModelVertices(VB0ElementType15* vb0, VB1ElementType15* vb1, uint32 verticesCount);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the index buffer data.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="indicesCount">The indices count.</param>
|
||||
void SetIndexBuffer(void* data, uint32 indicesCount);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Pack mesh data to the stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2Model(WriteStream* stream) const;
|
||||
|
||||
/// <summary>
|
||||
/// Pack skinned mesh data to the stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2SkinnedModel(WriteStream* stream) const;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate bounding box for the mesh
|
||||
/// </summary>
|
||||
/// <param name="result">Output box</param>
|
||||
void CalculateBox(BoundingBox& result) const
|
||||
{
|
||||
if (Positions.HasItems())
|
||||
BoundingBox::FromPoints(Positions.Get(), Positions.Count(), result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate bounding sphere for the mesh
|
||||
/// </summary>
|
||||
/// <param name="result">Output sphere</param>
|
||||
void CalculateSphere(BoundingSphere& result) const
|
||||
{
|
||||
if (Positions.HasItems())
|
||||
BoundingSphere::FromPoints(Positions.Get(), Positions.Count(), result);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
#if COMPILE_WITH_MODEL_TOOL
|
||||
|
||||
/// <summary>
|
||||
/// Generate lightmap uvs for the mesh entry
|
||||
/// </summary>
|
||||
/// <returns>True if generating lightmap uvs failed, otherwise false</returns>
|
||||
bool GenerateLightmapUVs();
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the vertex buffers to remove the duplicated vertices and generate the optimized index buffer.
|
||||
/// </summary>
|
||||
void BuildIndexBuffer();
|
||||
|
||||
/// <summary>
|
||||
/// Generate lightmap uvs for the mesh entry
|
||||
/// </summary>
|
||||
/// <param name="position">The target position to check.</param>
|
||||
/// <param name="epsilon">The position comparision epsilon.</param>
|
||||
/// <param name="result">The output vertices indices array.</param>
|
||||
void FindPositions(const Vector3& position, float epsilon, Array<int32>& result);
|
||||
|
||||
/// <summary>
|
||||
/// Generates the normal vectors for the mesh geometry.
|
||||
/// </summary>
|
||||
/// <param name="smoothingAngle">Specifies the maximum angle (in degrees) that may be between two face normals at the same vertex position that their are smoothed together.</param>
|
||||
/// <returns>True if generating failed, otherwise false</returns>
|
||||
bool GenerateNormals(float smoothingAngle = 175.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Generates the tangent vectors for the mesh geometry. Requires normal vector and texture coords channel to be valid.
|
||||
/// </summary>
|
||||
/// <param name="smoothingAngle">Specifies the maximum angle (in degrees) that may be between two vertex tangents that their tangents and bi-tangents are smoothed.</param>
|
||||
/// <returns>True if generating failed, otherwise false.</returns>
|
||||
bool GenerateTangents(float smoothingAngle = 45.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Reorders all triangles for improved vertex cache locality. It tries to arrange all triangles to fans and to render triangles which share vertices directly one after the other.
|
||||
/// </summary>
|
||||
void ImproveCacheLocality();
|
||||
|
||||
/// <summary>
|
||||
/// Sums the area of all triangles in the mesh.
|
||||
/// </summary>
|
||||
/// <returns>The area sum of all mesh triangles.</returns>
|
||||
float CalculateTrianglesArea() const;
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Transform a vertex buffer positions, normals, tangents and bitangents using the given matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix to use for the transformation.</param>
|
||||
void TransformBuffer(const Matrix& matrix);
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the blend weights. Requires to have vertices with positions and blend weights setup.
|
||||
/// </summary>
|
||||
void NormalizeBlendWeights()
|
||||
{
|
||||
ASSERT(Positions.Count() == BlendWeights.Count());
|
||||
for (int32 i = 0; i < Positions.Count(); i++)
|
||||
{
|
||||
const float sum = BlendWeights[i].SumValues();
|
||||
const float invSum = sum > ZeroTolerance ? 1.0f / sum : 0.0f;
|
||||
BlendWeights[i] *= invSum;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges this mesh data with the specified other mesh.
|
||||
/// </summary>
|
||||
/// <param name="other">The other mesh to merge with.</param>
|
||||
void Merge(MeshData& other);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Model texture resource descriptor.
|
||||
/// </summary>
|
||||
struct FLAXENGINE_API TextureEntry
|
||||
{
|
||||
enum class TypeHint
|
||||
{
|
||||
ColorRGB,
|
||||
ColorRGBA,
|
||||
Normals,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The absolute path to the file.
|
||||
/// </summary>
|
||||
String FilePath;
|
||||
|
||||
/// <summary>
|
||||
/// The texture contents hint based on the usage/context.
|
||||
/// </summary>
|
||||
TypeHint Type;
|
||||
|
||||
/// <summary>
|
||||
/// The texture asset identifier.
|
||||
/// </summary>
|
||||
Guid AssetID;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Model material slot entry that describes model mesh appearance.
|
||||
/// </summary>
|
||||
struct FLAXENGINE_API MaterialSlotEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The slot name.
|
||||
/// </summary>
|
||||
String Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets shadows casting mode by this visual element
|
||||
/// </summary>
|
||||
ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
|
||||
|
||||
/// <summary>
|
||||
/// The material asset identifier (material or material instance).
|
||||
/// </summary>
|
||||
Guid AssetID;
|
||||
|
||||
struct
|
||||
{
|
||||
Color Color = Color::White;
|
||||
int32 TextureIndex = -1;
|
||||
bool HasAlphaMask = false;
|
||||
} Diffuse;
|
||||
|
||||
struct
|
||||
{
|
||||
Color Color = Color::Transparent;
|
||||
int32 TextureIndex = -1;
|
||||
} Emissive;
|
||||
|
||||
struct
|
||||
{
|
||||
float Value = 1.0f;
|
||||
int32 TextureIndex = -1;
|
||||
} Opacity;
|
||||
|
||||
struct
|
||||
{
|
||||
int32 TextureIndex = -1;
|
||||
} Normals;
|
||||
|
||||
bool TwoSided = false;
|
||||
|
||||
bool UsesProperties() const
|
||||
{
|
||||
return Diffuse.Color != Color::White ||
|
||||
Diffuse.TextureIndex != -1 ||
|
||||
Emissive.Color != Color::Transparent ||
|
||||
Emissive.TextureIndex != -1 ||
|
||||
!Math::IsOne(Opacity.Value) ||
|
||||
Opacity.TextureIndex != -1 ||
|
||||
Normals.TextureIndex != -1;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Data container for LOD metadata and sub meshes.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API ModelLodData
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.
|
||||
/// </summary>
|
||||
float ScreenSize = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The meshes array.
|
||||
/// </summary>
|
||||
Array<MeshData*> Meshes;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelLodData"/> class.
|
||||
/// </summary>
|
||||
ModelLodData()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ModelLodData"/> class.
|
||||
/// </summary>
|
||||
~ModelLodData()
|
||||
{
|
||||
Meshes.ClearDelete();
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Data container for model metadata and LODs.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API ModelData
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The minimum screen size to draw model (the bottom limit).
|
||||
/// </summary>
|
||||
float MinScreenSize = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The texture slots.
|
||||
/// </summary>
|
||||
Array<TextureEntry> Textures;
|
||||
|
||||
/// <summary>
|
||||
/// The material slots.
|
||||
/// </summary>
|
||||
Array<MaterialSlotEntry> Materials;
|
||||
|
||||
/// <summary>
|
||||
/// Array with all LODs. The first element is the top most LOD0 followed by the LOD1, LOD2, etc.
|
||||
/// </summary>
|
||||
Array<ModelLodData> LODs;
|
||||
|
||||
/// <summary>
|
||||
/// The skeleton bones hierarchy.
|
||||
/// </summary>
|
||||
SkeletonData Skeleton;
|
||||
|
||||
/// <summary>
|
||||
/// The node animations.
|
||||
/// </summary>
|
||||
AnimationData Animation;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelData"/> class.
|
||||
/// </summary>
|
||||
ModelData()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the valid level of details count.
|
||||
/// </summary>
|
||||
/// <returns>The LOD count.</returns>
|
||||
FORCE_INLINE int32 GetLODsCount() const
|
||||
{
|
||||
return LODs.Count();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance has valid skeleton structure.
|
||||
/// </summary>
|
||||
/// <returns>True if has skeleton, otherwise false.</returns>
|
||||
FORCE_INLINE bool HasSkeleton() const
|
||||
{
|
||||
return Skeleton.Bones.HasItems();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Automatically calculates the screen size for every model LOD for a proper transitions.
|
||||
/// </summary>
|
||||
void CalculateLODsScreenSizes();
|
||||
|
||||
/// <summary>
|
||||
/// Transform a vertex buffer positions, normals, tangents and bitangents using the given matrix. Applies to all the LODs and meshes.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix to use for the transformation.</param>
|
||||
void TransformBuffer(const Matrix& matrix);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Pack mesh data to the header stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2ModelHeader(WriteStream* stream) const;
|
||||
|
||||
/// <summary>
|
||||
/// Pack skinned mesh data to the header stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2SkinnedModelHeader(WriteStream* stream) const;
|
||||
|
||||
/// <summary>
|
||||
/// Pack animation data to the header stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2AnimationHeader(WriteStream* stream) const;
|
||||
};
|
||||
106
Source/Engine/Graphics/Models/ModelInstanceEntry.cpp
Normal file
106
Source/Engine/Graphics/Models/ModelInstanceEntry.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ModelInstanceEntry.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
|
||||
bool ModelInstanceEntries::HasContentLoaded() const
|
||||
{
|
||||
bool result = true;
|
||||
for (auto& e : *this)
|
||||
{
|
||||
const auto material = e.Material.Get();
|
||||
if (material && !material->IsLoaded())
|
||||
{
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ModelInstanceEntries::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
SERIALIZE_GET_OTHER_OBJ(ModelInstanceEntries);
|
||||
|
||||
stream.JKEY("Entries");
|
||||
stream.StartArray();
|
||||
if (other && other->Count() == Count())
|
||||
{
|
||||
for (int32 i = 0; i < Count(); i++)
|
||||
stream.Object(&At(i), &other->At(i));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& e : *this)
|
||||
stream.Object(&e, nullptr);
|
||||
}
|
||||
stream.EndArray();
|
||||
}
|
||||
|
||||
void ModelInstanceEntries::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
const DeserializeStream& entries = stream["Entries"];
|
||||
ASSERT(entries.IsArray());
|
||||
Resize(entries.Size());
|
||||
for (rapidjson::SizeType i = 0; i < entries.Size(); i++)
|
||||
{
|
||||
At(i).Deserialize((DeserializeStream&)entries[i], modifier);
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelInstanceEntry::operator==(const ModelInstanceEntry& other) const
|
||||
{
|
||||
return Material.Get() == other.Material.Get() && ShadowsMode == other.ShadowsMode && Visible == other.Visible && ReceiveDecals == other.ReceiveDecals;
|
||||
}
|
||||
|
||||
bool ModelInstanceEntries::IsValidFor(const Model* model) const
|
||||
{
|
||||
// Just check amount of material slots
|
||||
ASSERT(model && model->IsInitialized());
|
||||
return model->MaterialSlots.Count() == Count();
|
||||
}
|
||||
|
||||
bool ModelInstanceEntries::IsValidFor(const SkinnedModel* model) const
|
||||
{
|
||||
// Just check amount of material slots
|
||||
ASSERT(model && model->IsInitialized());
|
||||
return model->MaterialSlots.Count() == Count();
|
||||
}
|
||||
|
||||
void ModelInstanceEntries::Setup(const Model* model)
|
||||
{
|
||||
ASSERT(model && model->IsInitialized());
|
||||
const int32 slotsCount = model->MaterialSlots.Count();
|
||||
Setup(slotsCount);
|
||||
}
|
||||
|
||||
void ModelInstanceEntries::Setup(const SkinnedModel* model)
|
||||
{
|
||||
ASSERT(model && model->IsInitialized());
|
||||
const int32 slotsCount = model->MaterialSlots.Count();
|
||||
Setup(slotsCount);
|
||||
}
|
||||
|
||||
void ModelInstanceEntries::Setup(int32 slotsCount)
|
||||
{
|
||||
Clear();
|
||||
Resize(slotsCount);
|
||||
}
|
||||
|
||||
void ModelInstanceEntries::SetupIfInvalid(const Model* model)
|
||||
{
|
||||
if (!IsValidFor(model))
|
||||
{
|
||||
Setup(model);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelInstanceEntries::SetupIfInvalid(const SkinnedModel* model)
|
||||
{
|
||||
if (!IsValidFor(model))
|
||||
{
|
||||
Setup(model);
|
||||
}
|
||||
}
|
||||
120
Source/Engine/Graphics/Models/ModelInstanceEntry.h
Normal file
120
Source/Engine/Graphics/Models/ModelInstanceEntry.h
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/Assets/MaterialBase.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
#include "Types.h"
|
||||
|
||||
/// <summary>
|
||||
/// The model instance entry that describes how to draw it.
|
||||
/// </summary>
|
||||
API_STRUCT() struct FLAXENGINE_API ModelInstanceEntry : ISerializable
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(ModelInstanceEntry);
|
||||
|
||||
/// <summary>
|
||||
/// The mesh surface material used for the rendering. If not assigned the default value will be used from the model asset.
|
||||
/// </summary>
|
||||
API_FIELD() AssetReference<MaterialBase> Material;
|
||||
|
||||
/// <summary>
|
||||
/// The shadows casting mode.
|
||||
/// </summary>
|
||||
API_FIELD() ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whenever this mesh is visible.
|
||||
/// </summary>
|
||||
API_FIELD() bool Visible = true;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whenever this mesh can receive decals.
|
||||
/// </summary>
|
||||
API_FIELD() bool ReceiveDecals = true;
|
||||
|
||||
public:
|
||||
|
||||
bool operator==(const ModelInstanceEntry& other) const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Collection of model instance entries.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API ModelInstanceEntries : public Array<ModelInstanceEntry>, public ISerializable
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether buffer is valid for the given model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <returns>True if this buffer is valid for the specified model object.</returns>
|
||||
bool IsValidFor(const Model* model) const;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether buffer is valid ofr the given skinned model.
|
||||
/// </summary>
|
||||
/// <param name="model">The skinned model.</param>
|
||||
/// <returns>True if this buffer is valid for the specified skinned model object.</returns>
|
||||
bool IsValidFor(const SkinnedModel* model) const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Setup buffer for given model
|
||||
/// </summary>
|
||||
/// <param name="model">Model to setup for</param>
|
||||
void Setup(const Model* model);
|
||||
|
||||
/// <summary>
|
||||
/// Setup buffer for given skinned model
|
||||
/// </summary>
|
||||
/// <param name="model">Model to setup for</param>
|
||||
void Setup(const SkinnedModel* model);
|
||||
|
||||
/// <summary>
|
||||
/// Setup buffer for given amount of material slots
|
||||
/// </summary>
|
||||
/// <param name="slotsCount">Amount of material slots</param>
|
||||
void Setup(int32 slotsCount);
|
||||
|
||||
/// <summary>
|
||||
/// Setups the buffer if is invalid (has different amount of entries).
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
void SetupIfInvalid(const Model* model);
|
||||
|
||||
/// <summary>
|
||||
/// Setups the buffer if is invalid (has different amount of entries).
|
||||
/// </summary>
|
||||
/// <param name="model">The skinned model.</param>
|
||||
void SetupIfInvalid(const SkinnedModel* model);
|
||||
|
||||
/// <summary>
|
||||
/// Clones the other buffer data
|
||||
/// </summary>
|
||||
/// <param name="other">The other buffer to clone.</param>
|
||||
void Clone(const ModelInstanceEntries* other)
|
||||
{
|
||||
*this = *other;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the buffer data.
|
||||
/// </summary>
|
||||
void Release()
|
||||
{
|
||||
Resize(0);
|
||||
}
|
||||
|
||||
bool HasContentLoaded() const;
|
||||
|
||||
public:
|
||||
|
||||
// [ISerializable]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
};
|
||||
141
Source/Engine/Graphics/Models/ModelLOD.cpp
Normal file
141
Source/Engine/Graphics/Models/ModelLOD.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ModelLOD.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
|
||||
bool ModelLOD::Load(MemoryReadStream& stream)
|
||||
{
|
||||
// Load LOD for each mesh
|
||||
_verticesCount = 0;
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
_verticesCount += vertices;
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
auto vb0 = stream.Read<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Read<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Read<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Read<byte>(indicesCount * ibStride);
|
||||
|
||||
// Setup GPU resources
|
||||
if (Meshes[i].Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer))
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {0}. Vertices: {1}, triangles: {2}", i, vertices, triangles);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ModelLOD::Unload()
|
||||
{
|
||||
// Unload LOD for each mesh
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
Meshes[i].Unload();
|
||||
}
|
||||
}
|
||||
|
||||
void ModelLOD::Dispose()
|
||||
{
|
||||
_model = nullptr;
|
||||
ScreenSize = 0.0f;
|
||||
Meshes.Resize(0);
|
||||
}
|
||||
|
||||
bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal, Mesh** mesh)
|
||||
{
|
||||
// Check all meshes
|
||||
bool result = false;
|
||||
float closest = MAX_float;
|
||||
Vector3 closestNormal = Vector3::Up;
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
// Test intersection with mesh and check if is closer than previous
|
||||
float dst;
|
||||
Vector3 nrm;
|
||||
if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
|
||||
{
|
||||
result = true;
|
||||
*mesh = &Meshes[i];
|
||||
closest = dst;
|
||||
closestNormal = nrm;
|
||||
}
|
||||
}
|
||||
|
||||
distance = closest;
|
||||
normal = closestNormal;
|
||||
return result;
|
||||
}
|
||||
|
||||
BoundingBox ModelLOD::GetBox(const Matrix& world) const
|
||||
{
|
||||
// Find minimum and maximum points of all the meshes
|
||||
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
|
||||
Vector3 corners[8];
|
||||
for (int32 j = 0; j < Meshes.Count(); j++)
|
||||
{
|
||||
const auto& mesh = Meshes[j];
|
||||
mesh.GetCorners(corners);
|
||||
|
||||
for (int32 i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3::Transform(corners[i], world, tmp);
|
||||
min = Vector3::Min(min, tmp);
|
||||
max = Vector3::Max(max, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
return BoundingBox(min, max);
|
||||
}
|
||||
|
||||
BoundingBox ModelLOD::GetBox(const Matrix& world, int32 meshIndex) const
|
||||
{
|
||||
// Find minimum and maximum points of the mesh
|
||||
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
|
||||
Vector3 corners[8];
|
||||
const auto& mesh = Meshes[meshIndex];
|
||||
mesh.GetCorners(corners);
|
||||
for (int32 i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3::Transform(corners[i], world, tmp);
|
||||
min = Vector3::Min(min, tmp);
|
||||
max = Vector3::Max(max, tmp);
|
||||
}
|
||||
|
||||
return BoundingBox(min, max);
|
||||
}
|
||||
|
||||
BoundingBox ModelLOD::GetBox() const
|
||||
{
|
||||
// Find minimum and maximum points of the mesh in given world
|
||||
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
|
||||
Vector3 corners[8];
|
||||
for (int32 j = 0; j < Meshes.Count(); j++)
|
||||
{
|
||||
Meshes[j].GetCorners(corners);
|
||||
|
||||
for (int32 i = 0; i < 8; i++)
|
||||
{
|
||||
min = Vector3::Min(min, corners[i]);
|
||||
max = Vector3::Max(max, corners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return BoundingBox(min, max);
|
||||
}
|
||||
146
Source/Engine/Graphics/Models/ModelLOD.h
Normal file
146
Source/Engine/Graphics/Models/ModelLOD.h
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Mesh.h"
|
||||
|
||||
class MemoryReadStream;
|
||||
|
||||
/// <summary>
|
||||
/// Represents single Level Of Detail for the model. Contains a collection of the meshes.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, PersistentScriptingObject);
|
||||
friend Model;
|
||||
friend Mesh;
|
||||
private:
|
||||
|
||||
Model* _model = nullptr;
|
||||
uint32 _verticesCount;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.
|
||||
/// </summary>
|
||||
API_FIELD() float ScreenSize = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The meshes array.
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) Array<Mesh> Meshes;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether any mesh has been initialized.
|
||||
/// </summary>
|
||||
/// <returns>True if any mesh has been initialized, otherwise false.</returns>
|
||||
FORCE_INLINE 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertex count for this model LOD level.
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const
|
||||
{
|
||||
return _verticesCount;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the LOD from the data stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <returns>True if fails, otherwise false.</returns>
|
||||
bool Load(MemoryReadStream& stream);
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups the data.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there is an intersection between the Model and a Ray in given world using given instance
|
||||
/// </summary>
|
||||
/// <param name="ray">The ray to test</param>
|
||||
/// <param name="world">World to test</param>
|
||||
/// <param name="distance">When the method completes, contains the distance of the intersection (if any valid).</param>
|
||||
/// <param name="normal">When the method completes, contains the intersection surface normal vector (if any valid).</param>
|
||||
/// <param name="mesh">Mesh, or null</param>
|
||||
/// <returns>True whether the two objects intersected</returns>
|
||||
bool Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal, Mesh** mesh);
|
||||
|
||||
/// <summary>
|
||||
/// Get model bounding box in transformed world for given instance buffer
|
||||
/// </summary>
|
||||
/// <param name="world">World matrix</param>
|
||||
/// <returns>Bounding box</returns>
|
||||
BoundingBox GetBox(const Matrix& world) const;
|
||||
|
||||
/// <summary>
|
||||
/// Get model bounding box in transformed world for given instance buffer for only one mesh
|
||||
/// </summary>
|
||||
/// <param name="world">World matrix</param>
|
||||
/// <param name="meshIndex">esh index</param>
|
||||
/// <returns>Bounding box</returns>
|
||||
BoundingBox GetBox(const Matrix& world, int32 meshIndex) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding box combined for all meshes in this model LOD.
|
||||
/// </summary>
|
||||
API_PROPERTY() BoundingBox GetBox() const;
|
||||
|
||||
/// <summary>
|
||||
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context to draw with.</param>
|
||||
FORCE_INLINE void Render(GPUContext* context)
|
||||
{
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
Meshes[i].Render(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the meshes from the model LOD.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="material">The material to use for rendering.</param>
|
||||
/// <param name="world">The world transformation of the model.</param>
|
||||
/// <param name="flags">The object static flags.</param>
|
||||
/// <param name="receiveDecals">True if rendered geometry can receive decals, otherwise false.</param>
|
||||
API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true) const
|
||||
{
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
Meshes[i].Draw(renderContext, material, world, flags, receiveDecals);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws all the meshes from the model LOD.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="info">The packed drawing info data.</param>
|
||||
/// <param name="lodDitherFactor">The LOD transition dither factor.</param>
|
||||
FORCE_INLINE void Draw(const RenderContext& renderContext, const Mesh::DrawInfo& info, float lodDitherFactor) const
|
||||
{
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
Meshes[i].Draw(renderContext, info, lodDitherFactor);
|
||||
}
|
||||
}
|
||||
};
|
||||
146
Source/Engine/Graphics/Models/SkeletonData.h
Normal file
146
Source/Engine/Graphics/Models/SkeletonData.h
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
|
||||
/// <summary>
|
||||
/// Describes a single skeleton node data. Used by the runtime.
|
||||
/// </summary>
|
||||
API_STRUCT() struct SkeletonNode
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(SkeletonNode);
|
||||
|
||||
/// <summary>
|
||||
/// The parent node index. The root node uses value -1.
|
||||
/// </summary>
|
||||
API_FIELD() int32 ParentIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The local transformation of the node, relative to the parent node.
|
||||
/// </summary>
|
||||
API_FIELD() Transform LocalTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The name of this node.
|
||||
/// </summary>
|
||||
API_FIELD() String Name;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes a single skeleton bone data. Used by the runtime. Skeleton bones are subset of the skeleton nodes collection that are actually used by the skinned model meshes.
|
||||
/// </summary>
|
||||
API_STRUCT() struct SkeletonBone
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(SkeletonBone);
|
||||
|
||||
/// <summary>
|
||||
/// The parent bone index. The root bone uses value -1.
|
||||
/// </summary>
|
||||
API_FIELD() int32 ParentIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the skeleton node where bone is 'attached'. Used as a animation transformation source.
|
||||
/// </summary>
|
||||
API_FIELD() int32 NodeIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The local transformation of the bone, relative to the parent bone (in bind pose).
|
||||
/// </summary>
|
||||
API_FIELD() Transform LocalTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The matrix that transforms from mesh space to bone space in bind pose (inverse bind pose).
|
||||
/// </summary>
|
||||
API_FIELD() Matrix OffsetMatrix;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<SkeletonBone>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes hierarchical bones in a flattened array.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Bones are ordered so that parents always come first, allowing for hierarchical updates in a simple loop.
|
||||
/// </remarks>
|
||||
class SkeletonData
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The nodes in this hierarchy. The root node is always at the index 0.
|
||||
/// </summary>
|
||||
Array<SkeletonNode> Nodes;
|
||||
|
||||
/// <summary>
|
||||
/// The bones in this hierarchy.
|
||||
/// </summary>
|
||||
Array<SkeletonBone> Bones;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root node reference.
|
||||
/// </summary>
|
||||
/// <returns>The root node.</returns>
|
||||
FORCE_INLINE SkeletonNode& RootNode()
|
||||
{
|
||||
ASSERT(Nodes.HasItems());
|
||||
return Nodes[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root node reference.
|
||||
/// </summary>
|
||||
/// <returns>The root node.</returns>
|
||||
FORCE_INLINE const SkeletonNode& RootNode() const
|
||||
{
|
||||
ASSERT(Nodes.HasItems());
|
||||
return Nodes[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange.
|
||||
/// </summary>
|
||||
/// <param name="other">The other object.</param>
|
||||
void Swap(SkeletonData& other)
|
||||
{
|
||||
Nodes.Swap(other.Nodes);
|
||||
Bones.Swap(other.Bones);
|
||||
}
|
||||
|
||||
int32 FindNode(const StringView& name)
|
||||
{
|
||||
for (int32 i = 0; i < Nodes.Count(); i++)
|
||||
{
|
||||
if (Nodes[i].Name == name)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32 FindBone(int32 nodeIndex)
|
||||
{
|
||||
for (int32 i = 0; i < Bones.Count(); i++)
|
||||
{
|
||||
if (Bones[i].NodeIndex == nodeIndex)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases data.
|
||||
/// </summary>
|
||||
void Dispose()
|
||||
{
|
||||
Nodes.Resize(0);
|
||||
Bones.Resize(0);
|
||||
}
|
||||
};
|
||||
100
Source/Engine/Graphics/Models/SkeletonMapping.h
Normal file
100
Source/Engine/Graphics/Models/SkeletonMapping.h
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/ArrayExtensions.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
|
||||
/// <summary>
|
||||
/// Helper class used to map model nodes/bones from one skeleton into another. Useful for animation retargeting.
|
||||
/// </summary>
|
||||
template<typename T>
|
||||
class SkeletonMapping
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Array<T> Items;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The amount of the nodes (from the source skeleton).
|
||||
/// </summary>
|
||||
int32 Size;
|
||||
|
||||
/// <summary>
|
||||
/// The node mapping from source to target skeletons.
|
||||
/// </summary>
|
||||
Array<int32> SourceToTarget;
|
||||
|
||||
/// <summary>
|
||||
/// A round-trip through TargetToSource[SourceToTarget[i]] so that we know easily what nodes are remapped in source skeleton side.
|
||||
/// </summary>
|
||||
Array<int32> SourceToSource;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkeletonMapping"/> class.
|
||||
/// </summary>
|
||||
/// <param name="sourceSkeleton">The source model skeleton.</param>
|
||||
/// <param name="targetSkeleton">The target skeleton. May be null to disable nodes mapping.</param>
|
||||
SkeletonMapping(Items& sourceSkeleton, Items* targetSkeleton)
|
||||
{
|
||||
Size = sourceSkeleton.Count();
|
||||
SourceToTarget.Resize(Size); // model => skeleton mapping
|
||||
SourceToSource.Resize(Size); // model => model mapping
|
||||
|
||||
if (targetSkeleton == nullptr)
|
||||
{
|
||||
// No skeleton, we can compact everything
|
||||
for (int i = 0; i < Size; i++)
|
||||
{
|
||||
// Map everything to the root node
|
||||
SourceToTarget[i] = 0;
|
||||
SourceToSource[i] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Array<int32> targetToSource;
|
||||
targetToSource.Resize(Size); // skeleton => model mapping
|
||||
for (int32 i = 0; i < Size; i++)
|
||||
targetToSource[i] = -1;
|
||||
|
||||
// Build mapping from model to actual skeleton
|
||||
for (int32 modelIndex = 0; modelIndex < Size; modelIndex++)
|
||||
{
|
||||
auto node = sourceSkeleton[modelIndex];
|
||||
const auto parentModelIndex = node.ParentIndex;
|
||||
|
||||
// Find matching node in skeleton (or map to best parent)
|
||||
const std::function<bool(const T&)> f = [node](const T& x) -> bool
|
||||
{
|
||||
return x.Name == node.Name;
|
||||
};
|
||||
const auto skeletonIndex = ArrayExtensions::IndexOf(*targetSkeleton, f);
|
||||
if (skeletonIndex == -1)
|
||||
{
|
||||
// Nothing match, remap to parent node
|
||||
SourceToTarget[modelIndex] = parentModelIndex != -1 ? SourceToTarget[parentModelIndex] : 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Name match
|
||||
SourceToTarget[modelIndex] = skeletonIndex;
|
||||
targetToSource[skeletonIndex] = modelIndex;
|
||||
}
|
||||
for (int32 modelIndex = 0; modelIndex < Size; modelIndex++)
|
||||
{
|
||||
SourceToSource[modelIndex] = targetToSource[SourceToTarget[modelIndex]];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="SkeletonMapping"/> class.
|
||||
/// </summary>
|
||||
~SkeletonMapping()
|
||||
{
|
||||
}
|
||||
};
|
||||
168
Source/Engine/Graphics/Models/SkeletonUpdater.h
Normal file
168
Source/Engine/Graphics/Models/SkeletonUpdater.h
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SkeletonData.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
/// <summary>
|
||||
/// Performs hierarchical updates for skeleton nodes.
|
||||
/// </summary>
|
||||
template<typename T>
|
||||
class SkeletonUpdater
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Array<T> Items;
|
||||
|
||||
/// <summary>
|
||||
/// Represents skeleton node transformation data.
|
||||
/// </summary>
|
||||
struct Node
|
||||
{
|
||||
/// <summary>
|
||||
/// The parent node index.
|
||||
/// The parent bone index. The root node uses value -1.
|
||||
/// </summary>
|
||||
int32 ParentIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The local transform.
|
||||
/// </summary>
|
||||
Transform Transform;
|
||||
|
||||
/// <summary>
|
||||
/// The local transformation matrix (from parent local space to node local space).
|
||||
/// </summary>
|
||||
Matrix LocalMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// The absolute world transformation matrix (from world space to node local space).
|
||||
/// </summary>
|
||||
Matrix WorldMatrix;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The cached node transformations.
|
||||
/// </summary>
|
||||
Array<Node> NodeTransformations;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkeletonUpdater" /> class.
|
||||
/// </summary>
|
||||
/// <param name="skeleton">The skeleton.</param>
|
||||
SkeletonUpdater(const Items& skeleton)
|
||||
{
|
||||
Initialize(skeleton);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the updater using the specified skeleton.
|
||||
/// </summary>
|
||||
/// <param name="skeleton">The skeleton.</param>
|
||||
void Initialize(const Items& skeleton)
|
||||
{
|
||||
const int32 count = skeleton.Count();
|
||||
NodeTransformations.Resize(count, false);
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
auto& n1 = NodeTransformations[i];
|
||||
auto& n2 = skeleton[i];
|
||||
|
||||
n1.ParentIndex = n2.ParentIndex;
|
||||
n1.Transform = n2.LocalTransform;
|
||||
n1.WorldMatrix = Matrix::Identity;
|
||||
n1.Transform.GetWorld(n1.LocalMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For each node, updates the world matrices from local matrices.
|
||||
/// </summary>
|
||||
void UpdateMatrices()
|
||||
{
|
||||
for (int32 i = 0; i < NodeTransformations.Count(); i++)
|
||||
{
|
||||
UpdateNode(NodeTransformations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the transformation matrix to go from rootIndex to index.
|
||||
/// </summary>
|
||||
/// <param name="rootIndex">The root index.</param>
|
||||
/// <param name="index">The current index.</param>
|
||||
/// <returns>The matrix at this index.</returns>
|
||||
Matrix CombineMatricesFromNodeIndices(int32 rootIndex, int32 index)
|
||||
{
|
||||
if (index == -1)
|
||||
return Matrix::Identity;
|
||||
|
||||
auto result = NodeTransformations[index].LocalMatrix;
|
||||
if (index != rootIndex)
|
||||
{
|
||||
const auto topMatrix = CombineMatricesFromNodeIndices(rootIndex, NodeTransformations[index].ParentIndex);
|
||||
result = Matrix::Multiply(result, topMatrix);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the world matrix of the node.
|
||||
/// </summary>
|
||||
/// <param name="index">The node index.</param>
|
||||
/// <param name="matrix">The result matrix.</param>
|
||||
FORCE_INLINE void GetWorldMatrix(int32 index, Matrix* matrix)
|
||||
{
|
||||
*matrix = NodeTransformations[index].WorldMatrix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local matrix of the node.
|
||||
/// </summary>
|
||||
/// <param name="index">The node index.</param>
|
||||
/// <param name="matrix">The result matrix.</param>
|
||||
FORCE_INLINE void GetLocalMatrix(int32 index, Matrix* matrix)
|
||||
{
|
||||
*matrix = NodeTransformations[index].LocalMatrix;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default root node.
|
||||
/// </summary>
|
||||
/// <returns>The node.</returns>
|
||||
static Node GetDefaultNode()
|
||||
{
|
||||
Node node;
|
||||
node.ParentIndex = -1;
|
||||
node.Transform = Transform::Identity;
|
||||
node.LocalMatrix = Matrix::Identity;
|
||||
node.WorldMatrix = Matrix::Identity;
|
||||
return node;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void UpdateNode(Node& node)
|
||||
{
|
||||
// Compute local matrix
|
||||
node.Transform.GetWorld(node.LocalMatrix);
|
||||
|
||||
// Compute world matrix
|
||||
if (node.ParentIndex != -1)
|
||||
{
|
||||
Matrix::Multiply(node.LocalMatrix, NodeTransformations[node.ParentIndex].WorldMatrix, node.WorldMatrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
node.WorldMatrix = node.LocalMatrix;
|
||||
}
|
||||
}
|
||||
};
|
||||
560
Source/Engine/Graphics/Models/SkinnedMesh.cpp
Normal file
560
Source/Engine/Graphics/Models/SkinnedMesh.cpp
Normal file
@@ -0,0 +1,560 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SkinnedMesh.h"
|
||||
#include "ModelInstanceEntry.h"
|
||||
#include "Engine/Content/Assets/Material.h"
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
|
||||
|
||||
void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere)
|
||||
{
|
||||
_model = model;
|
||||
_index = index;
|
||||
_lodIndex = lodIndex;
|
||||
_materialSlotIndex = materialSlotIndex;
|
||||
_use16BitIndexBuffer = false;
|
||||
_box = box;
|
||||
_sphere = sphere;
|
||||
_vertices = 0;
|
||||
_triangles = 0;
|
||||
_vertexBuffer = nullptr;
|
||||
_indexBuffer = nullptr;
|
||||
_cachedIndexBuffer.Clear();
|
||||
_cachedVertexBuffer.Clear();
|
||||
BlendShapes.Clear();
|
||||
}
|
||||
|
||||
SkinnedMesh::~SkinnedMesh()
|
||||
{
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffer);
|
||||
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
|
||||
}
|
||||
|
||||
void SkinnedMesh::SetMaterialSlotIndex(int32 value)
|
||||
{
|
||||
if (value < 0 || value >= _model->MaterialSlots.Count())
|
||||
{
|
||||
LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count());
|
||||
return;
|
||||
}
|
||||
|
||||
_materialSlotIndex = value;
|
||||
}
|
||||
|
||||
bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* ib, bool use16BitIndexBuffer)
|
||||
{
|
||||
// Cache data
|
||||
uint32 indicesCount = triangles * 3;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
|
||||
GPUBuffer* vertexBuffer = nullptr;
|
||||
GPUBuffer* indexBuffer = nullptr;
|
||||
|
||||
// Create vertex buffer
|
||||
#if GPU_ENABLE_RESOURCE_NAMING
|
||||
vertexBuffer = GPUDevice::Instance->CreateBuffer(GetSkinnedModel()->ToString() + TEXT(".VB"));
|
||||
#else
|
||||
vertexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty);
|
||||
#endif
|
||||
if (vertexBuffer->Init(GPUBufferDescription::Vertex(sizeof(VB0SkinnedElementType), vertices, vb0)))
|
||||
goto ERROR_LOAD_END;
|
||||
|
||||
// Create index buffer
|
||||
#if GPU_ENABLE_RESOURCE_NAMING
|
||||
indexBuffer = GPUDevice::Instance->CreateBuffer(GetSkinnedModel()->ToString() + TEXT(".IB"));
|
||||
#else
|
||||
indexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty);
|
||||
#endif
|
||||
if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib)))
|
||||
goto ERROR_LOAD_END;
|
||||
|
||||
// Initialize
|
||||
_vertexBuffer = vertexBuffer;
|
||||
_indexBuffer = indexBuffer;
|
||||
_triangles = triangles;
|
||||
_vertices = vertices;
|
||||
_use16BitIndexBuffer = use16BitIndexBuffer;
|
||||
|
||||
return false;
|
||||
|
||||
ERROR_LOAD_END:
|
||||
|
||||
SAFE_DELETE_GPU_RESOURCE(vertexBuffer);
|
||||
SAFE_DELETE_GPU_RESOURCE(indexBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkinnedMesh::Unload()
|
||||
{
|
||||
SAFE_DELETE_GPU_RESOURCE(_vertexBuffer);
|
||||
SAFE_DELETE_GPU_RESOURCE(_indexBuffer);
|
||||
_cachedIndexBuffer.Clear();
|
||||
_cachedVertexBuffer.Clear();
|
||||
_triangles = 0;
|
||||
_vertices = 0;
|
||||
_use16BitIndexBuffer = false;
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices)
|
||||
{
|
||||
// Setup GPU resources
|
||||
const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices);
|
||||
if (!failed)
|
||||
{
|
||||
// Calculate mesh bounds
|
||||
SetBounds(BoundingBox::FromPoints((Vector3*)vb, vertexCount));
|
||||
|
||||
// Send event (actors using this model can update bounds, etc.)
|
||||
_model->onLoaded();
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
bool SkinnedMesh::Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal) const
|
||||
{
|
||||
// Transform points
|
||||
Vector3 min, max;
|
||||
Vector3::Transform(_box.Minimum, world, min);
|
||||
Vector3::Transform(_box.Maximum, world, max);
|
||||
|
||||
// Get transformed box
|
||||
BoundingBox transformedBox(min, max);
|
||||
|
||||
// Test ray on a box
|
||||
return transformedBox.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void SkinnedMesh::Render(GPUContext* context) const
|
||||
{
|
||||
ASSERT(IsInitialized());
|
||||
|
||||
context->BindVB(ToSpan(&_vertexBuffer, 1));
|
||||
context->BindIB(_indexBuffer);
|
||||
context->DrawIndexed(_triangles * 3);
|
||||
}
|
||||
|
||||
void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const
|
||||
{
|
||||
// Cache data
|
||||
const auto& entry = info.Buffer->At(_materialSlotIndex);
|
||||
if (!entry.Visible || !IsInitialized())
|
||||
return;
|
||||
const MaterialSlot& slot = _model->MaterialSlots[_materialSlotIndex];
|
||||
|
||||
// Check if skip rendering
|
||||
const auto shadowsMode = static_cast<ShadowsCastingMode>(entry.ShadowsMode & slot.ShadowsMode);
|
||||
const auto drawModes = static_cast<DrawPass>(info.DrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode));
|
||||
if (drawModes == DrawPass::None)
|
||||
return;
|
||||
|
||||
// Select material
|
||||
MaterialBase* material;
|
||||
if (entry.Material && entry.Material->IsLoaded())
|
||||
material = entry.Material;
|
||||
else if (slot.Material && slot.Material->IsLoaded())
|
||||
material = slot.Material;
|
||||
else
|
||||
material = GPUDevice::Instance->GetDefaultMaterial();
|
||||
if (!material || !material->IsSurface())
|
||||
return;
|
||||
|
||||
// Submit 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[1] = nullptr;
|
||||
drawCall.Geometry.VertexBuffers[2] = nullptr;
|
||||
drawCall.Geometry.VertexBuffersOffsets[0] = 0;
|
||||
drawCall.Geometry.VertexBuffersOffsets[1] = 0;
|
||||
drawCall.Geometry.VertexBuffersOffsets[2] = 0;
|
||||
drawCall.Geometry.StartIndex = 0;
|
||||
drawCall.Geometry.IndicesCount = _triangles * 3;
|
||||
drawCall.InstanceCount = 1;
|
||||
drawCall.IndirectArgsBuffer = nullptr;
|
||||
drawCall.IndirectArgsOffset = 0;
|
||||
drawCall.Material = material;
|
||||
drawCall.World = *info.World;
|
||||
drawCall.PrevWorld = info.DrawState->PrevWorld;
|
||||
drawCall.ObjectPosition = drawCall.World.GetTranslation();
|
||||
drawCall.GeometrySize = _box.GetSize();
|
||||
drawCall.Lightmap = nullptr;
|
||||
drawCall.LightmapUVsArea = Rectangle::Empty;
|
||||
drawCall.Skinning = info.Skinning;
|
||||
drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1);
|
||||
drawCall.PerInstanceRandom = info.PerInstanceRandom;
|
||||
drawCall.LODDitherFactor = lodDitherFactor;
|
||||
renderContext.List->AddDrawCall(drawModes, StaticFlags::None, drawCall, entry.ReceiveDecals);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const
|
||||
{
|
||||
GPUBuffer* buffer = nullptr;
|
||||
switch (type)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
buffer = _indexBuffer;
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
buffer = _vertexBuffer;
|
||||
break;
|
||||
}
|
||||
return buffer && buffer->DownloadData(result);
|
||||
}
|
||||
|
||||
Task* SkinnedMesh::DownloadDataAsyncGPU(MeshBufferType type, BytesContainer& result) const
|
||||
{
|
||||
GPUBuffer* buffer = nullptr;
|
||||
switch (type)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
buffer = _indexBuffer;
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
buffer = _vertexBuffer;
|
||||
break;
|
||||
}
|
||||
return buffer ? buffer->DownloadDataAsync(result) : nullptr;
|
||||
}
|
||||
|
||||
bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result) const
|
||||
{
|
||||
if (_cachedVertexBuffer.IsEmpty())
|
||||
{
|
||||
PROFILE_CPU();
|
||||
auto model = GetSkinnedModel();
|
||||
ScopeLock lock(model->Locker);
|
||||
|
||||
// Fetch chunk with data from drive/memory
|
||||
const auto chunkIndex = _lodIndex + 1;
|
||||
if (model->LoadChunk(chunkIndex))
|
||||
return true;
|
||||
const auto chunk = model->GetChunk(chunkIndex);
|
||||
if (!chunk)
|
||||
{
|
||||
LOG(Error, "Missing chunk.");
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
|
||||
// Seek to find mesh location
|
||||
for (int32 i = 0; i <= _index; i++)
|
||||
{
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.ReadUint16(&blendShapesCount);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
|
||||
{
|
||||
uint32 minVertexIndex, maxVertexIndex;
|
||||
bool useNormals = stream.ReadBool();
|
||||
stream.ReadUint32(&minVertexIndex);
|
||||
stream.ReadUint32(&maxVertexIndex);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
auto blendShapeVerticesData = stream.Read<byte>(blendShapeVertices * sizeof(BlendShapeVertex));
|
||||
}
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Error, "Invalid mesh data.");
|
||||
return true;
|
||||
}
|
||||
auto vb0 = stream.Read<VB0SkinnedElementType>(vertices);
|
||||
auto ib = stream.Read<byte>(indicesCount * ibStride);
|
||||
|
||||
if (i != _index)
|
||||
continue;
|
||||
|
||||
// Cache mesh data
|
||||
_cachedIndexBuffer.Set(ib, indicesCount * ibStride);
|
||||
_cachedVertexBuffer.Set((const byte*)vb0, vertices * sizeof(VB0SkinnedElementType));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MeshBufferType::Index:
|
||||
result.Link(_cachedIndexBuffer);
|
||||
break;
|
||||
case MeshBufferType::Vertex0:
|
||||
result.Link(_cachedVertexBuffer);
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ScriptingObject* SkinnedMesh::GetParentModel()
|
||||
{
|
||||
return _model;
|
||||
}
|
||||
|
||||
template<typename IndexType>
|
||||
bool UpdateMesh(SkinnedMesh* mesh, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj)
|
||||
{
|
||||
auto model = mesh->GetSkinnedModel();
|
||||
ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj && blendIndicesObj && blendWeightsObj);
|
||||
|
||||
// Get buffers data
|
||||
const auto vertexCount = (uint32)mono_array_length(verticesObj);
|
||||
const auto triangleCount = (uint32)mono_array_length(trianglesObj) / 3;
|
||||
auto vertices = (Vector3*)(void*)mono_array_addr_with_size(verticesObj, sizeof(Vector3), 0);
|
||||
auto ib = (IndexType*)(void*)mono_array_addr_with_size(trianglesObj, sizeof(int32), 0);
|
||||
auto blendIndices = (Int4*)(void*)mono_array_addr_with_size(blendIndicesObj, sizeof(Int4), 0);
|
||||
auto blendWeights = (Vector4*)(void*)mono_array_addr_with_size(blendWeightsObj, sizeof(Vector4), 0);
|
||||
Array<VB0SkinnedElementType> vb;
|
||||
vb.Resize(vertexCount);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vb[i].Position = vertices[i];
|
||||
}
|
||||
if (normalsObj)
|
||||
{
|
||||
const auto normals = (Vector3*)(void*)mono_array_addr_with_size(normalsObj, sizeof(Vector3), 0);
|
||||
if (tangentsObj)
|
||||
{
|
||||
const auto tangents = (Vector3*)(void*)mono_array_addr_with_size(tangentsObj, sizeof(Vector3), 0);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
// Peek normal and tangent
|
||||
const Vector3 normal = normals[i];
|
||||
const Vector3 tangent = tangents[i];
|
||||
|
||||
// Calculate bitangent sign
|
||||
Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
|
||||
byte sign = static_cast<byte>(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||
|
||||
// Set tangent frame
|
||||
vb[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
||||
vb[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
// Peek normal
|
||||
const Vector3 normal = normals[i];
|
||||
|
||||
// Calculate tangent
|
||||
Vector3 c1 = Vector3::Cross(normal, Vector3::UnitZ);
|
||||
Vector3 c2 = Vector3::Cross(normal, Vector3::UnitY);
|
||||
Vector3 tangent;
|
||||
if (c1.LengthSquared() > c2.LengthSquared())
|
||||
tangent = c1;
|
||||
else
|
||||
tangent = c2;
|
||||
|
||||
// Calculate bitangent sign
|
||||
Vector3 bitangent = Vector3::Normalize(Vector3::Cross(normal, tangent));
|
||||
byte sign = static_cast<byte>(Vector3::Dot(Vector3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
||||
|
||||
// Set tangent frame
|
||||
vb[i].Tangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
||||
vb[i].Normal = Float1010102(normal * 0.5f + 0.5f, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto n = Float1010102(Vector3::UnitZ);
|
||||
const auto t = Float1010102(Vector3::UnitX);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
vb[i].Normal = n;
|
||||
vb[i].Tangent = t;
|
||||
}
|
||||
}
|
||||
if (uvObj)
|
||||
{
|
||||
const auto uvs = (Vector2*)(void*)mono_array_addr_with_size(uvObj, sizeof(Vector2), 0);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb[i].TexCoord = Half2(uvs[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto v = Half2(0, 0);
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
vb[i].TexCoord = v;
|
||||
}
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
auto v = blendIndices[i];
|
||||
vb[i].BlendIndices = Color32(v.X, v.Y, v.Z, v.W);
|
||||
}
|
||||
for (uint32 i = 0; i < vertexCount; i++)
|
||||
{
|
||||
auto v = blendWeights[i];
|
||||
vb[i].BlendWeights = Half4(v);
|
||||
}
|
||||
|
||||
return mesh->UpdateMesh(vertexCount, triangleCount, vb.Get(), ib);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMeshInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj)
|
||||
{
|
||||
return ::UpdateMesh<int32>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
|
||||
}
|
||||
|
||||
bool SkinnedMesh::UpdateMeshUShort(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj)
|
||||
{
|
||||
return ::UpdateMesh<uint16>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
|
||||
}
|
||||
|
||||
enum class InternalBufferType
|
||||
{
|
||||
VB0 = 0,
|
||||
IB16 = 3,
|
||||
IB32 = 4,
|
||||
};
|
||||
|
||||
void ConvertMeshData(SkinnedMesh* mesh, InternalBufferType type, MonoArray* resultObj, void* srcData)
|
||||
{
|
||||
auto vertices = mesh->GetVertexCount();
|
||||
auto triangles = mesh->GetTriangleCount();
|
||||
auto indices = triangles * 3;
|
||||
auto use16BitIndexBuffer = mesh->Use16BitIndexBuffer();
|
||||
|
||||
void* managedArrayPtr = mono_array_addr_with_size(resultObj, 0, 0);
|
||||
switch (type)
|
||||
{
|
||||
case InternalBufferType::VB0:
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, srcData, sizeof(VB0SkinnedElementType) * vertices);
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::IB16:
|
||||
{
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, srcData, sizeof(uint16) * indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dst = (uint16*)managedArrayPtr;
|
||||
auto src = (uint32*)srcData;
|
||||
for (int32 i = 0; i < indices; i++)
|
||||
{
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case InternalBufferType::IB32:
|
||||
{
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
auto dst = (uint32*)managedArrayPtr;
|
||||
auto src = (uint16*)srcData;
|
||||
for (int32 i = 0; i < indices; i++)
|
||||
{
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform::MemoryCopy(managedArrayPtr, srcData, sizeof(uint32) * indices);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SkinnedMesh::DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI)
|
||||
{
|
||||
SkinnedMesh* mesh = this;
|
||||
InternalBufferType type = (InternalBufferType)typeI;
|
||||
auto model = mesh->GetSkinnedModel();
|
||||
ASSERT(model && resultObj);
|
||||
|
||||
// Virtual assets always fetch from GPU memory
|
||||
forceGpu |= model->IsVirtual();
|
||||
|
||||
if (!mesh->IsInitialized() && forceGpu)
|
||||
{
|
||||
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
|
||||
return true;
|
||||
}
|
||||
if (type == InternalBufferType::IB16 || type == InternalBufferType::IB32)
|
||||
{
|
||||
if (mono_array_length(resultObj) != mesh->GetTriangleCount() * 3)
|
||||
{
|
||||
LOG(Error, "Invalid buffer size.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mono_array_length(resultObj) != mesh->GetVertexCount())
|
||||
{
|
||||
LOG(Error, "Invalid buffer size.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
MeshBufferType bufferType;
|
||||
switch (type)
|
||||
{
|
||||
case InternalBufferType::VB0:
|
||||
bufferType = MeshBufferType::Vertex0;
|
||||
break;
|
||||
case InternalBufferType::IB16:
|
||||
case InternalBufferType::IB32:
|
||||
bufferType = MeshBufferType::Index;
|
||||
break;
|
||||
default: CRASH;
|
||||
return true;
|
||||
}
|
||||
BytesContainer data;
|
||||
if (forceGpu)
|
||||
{
|
||||
// Get data from GPU
|
||||
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
|
||||
auto task = mesh->DownloadDataAsyncGPU(bufferType, data);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
if (task->Wait())
|
||||
{
|
||||
LOG(Error, "Task failed.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get data from CPU
|
||||
if (DownloadDataCPU(bufferType, data))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert into managed memory
|
||||
ConvertMeshData(mesh, type, resultObj, data.Get());
|
||||
return false;
|
||||
}
|
||||
353
Source/Engine/Graphics/Models/SkinnedMesh.h
Normal file
353
Source/Engine/Graphics/Models/SkinnedMesh.h
Normal file
@@ -0,0 +1,353 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/BoundingBox.h"
|
||||
#include "Engine/Core/Math/BoundingSphere.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "ModelInstanceEntry.h"
|
||||
#include "Types.h"
|
||||
#include "BlendShape.h"
|
||||
|
||||
class GPUBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Represents part of the skinned model that is made of vertices and can be rendered using custom material, transformation and skeleton bones hierarchy.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedMesh : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedMesh, PersistentScriptingObject);
|
||||
protected:
|
||||
|
||||
SkinnedModel* _model;
|
||||
int32 _index;
|
||||
int32 _lodIndex;
|
||||
int32 _materialSlotIndex;
|
||||
bool _use16BitIndexBuffer;
|
||||
BoundingBox _box;
|
||||
BoundingSphere _sphere;
|
||||
uint32 _vertices;
|
||||
uint32 _triangles;
|
||||
GPUBuffer* _vertexBuffer;
|
||||
GPUBuffer* _indexBuffer;
|
||||
mutable Array<byte> _cachedIndexBuffer;
|
||||
mutable Array<byte> _cachedVertexBuffer;
|
||||
|
||||
public:
|
||||
|
||||
SkinnedMesh(const SkinnedMesh& other)
|
||||
: SkinnedMesh()
|
||||
{
|
||||
#if !!BUILD_RELEASE
|
||||
CRASH; // Not used
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="SkinnedMesh"/> class.
|
||||
/// </summary>
|
||||
~SkinnedMesh();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the skinned model owning this mesh.
|
||||
/// </summary>
|
||||
/// <returns>The skinned model</returns>
|
||||
FORCE_INLINE SkinnedModel* GetSkinnedModel() const
|
||||
{
|
||||
return _model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mesh index.
|
||||
/// </summary>
|
||||
/// <returns>The index</returns>
|
||||
FORCE_INLINE int32 GetIndex() const
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the material slot index.
|
||||
/// </summary>
|
||||
/// <returns>The material slot index</returns>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetMaterialSlotIndex() const
|
||||
{
|
||||
return _materialSlotIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the index of the material slot index.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
API_PROPERTY() void SetMaterialSlotIndex(int32 value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triangle count.
|
||||
/// </summary>
|
||||
/// <returns>The triangles</returns>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const
|
||||
{
|
||||
return _triangles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertex count.
|
||||
/// </summary>
|
||||
/// <returns>The vertices</returns>
|
||||
API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const
|
||||
{
|
||||
return _vertices;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this mesh is initialized (has vertex and index buffers initialized).
|
||||
/// </summary>
|
||||
/// <returns>True if this instance is initialized, otherwise false.</returns>
|
||||
FORCE_INLINE bool IsInitialized() const
|
||||
{
|
||||
return _vertexBuffer != nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this mesh is using 16 bit index buffer, otherwise it's 32 bit.
|
||||
/// </summary>
|
||||
/// <returns>True if this mesh is using 16 bit index buffer, otherwise 32 bit index buffer.</returns>
|
||||
API_PROPERTY() FORCE_INLINE bool Use16BitIndexBuffer() const
|
||||
{
|
||||
return _use16BitIndexBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend shapes used by this mesh.
|
||||
/// </summary>
|
||||
Array<BlendShape> BlendShapes;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkinnedMesh"/> class.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="lodIndex">The model LOD index.</param>
|
||||
/// <param name="index">The mesh index.</param>
|
||||
/// <param name="materialSlotIndex">The material slot index to use.</param>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
/// <param name="sphere">The bounding sphere.</param>
|
||||
void Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere);
|
||||
|
||||
/// <summary>
|
||||
/// Load mesh data and Initialize GPU buffers
|
||||
/// </summary>
|
||||
/// <param name="vertices">Amount of vertices in the vertex buffer</param>
|
||||
/// <param name="triangles">Amount of triangles in the index buffer</param>
|
||||
/// <param name="vb0">Vertex buffer data</param>
|
||||
/// <param name="ib">Index buffer data</param>
|
||||
/// <param name="use16BitIndexBuffer">True if use 16 bit indices for the index buffer (true: uint16, false: uint32).</param>
|
||||
/// <returns>True if cannot load data, otherwise false.</returns>
|
||||
bool Load(uint32 vertices, uint32 triangles, void* vb0, void* ib, bool use16BitIndexBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb">The vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, int32* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb">The vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, uint16* ib)
|
||||
{
|
||||
return UpdateMesh(vertexCount, triangleCount, vb, ib, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
|
||||
/// </summary>
|
||||
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
|
||||
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
|
||||
/// <param name="vb">The vertex buffer data.</param>
|
||||
/// <param name="ib">The index buffer.</param>
|
||||
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, VB0SkinnedElementType* vb, void* ib, bool use16BitIndices);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mesh bounds.
|
||||
/// </summary>
|
||||
/// <param name="box">The bounding box.</param>
|
||||
void SetBounds(const BoundingBox& box)
|
||||
{
|
||||
_box = box;
|
||||
BoundingSphere::FromBox(box, _sphere);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the box.
|
||||
/// </summary>
|
||||
/// <returns>The bounding box.</returns>
|
||||
API_PROPERTY() FORCE_INLINE BoundingBox GetBox() const
|
||||
{
|
||||
return _box;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sphere.
|
||||
/// </summary>
|
||||
/// <returns>The bounding sphere.</returns>
|
||||
API_PROPERTY() FORCE_INLINE BoundingSphere GetSphere() const
|
||||
{
|
||||
return _sphere;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there is an intersection between the mesh and a ray in given world
|
||||
/// </summary>
|
||||
/// <param name="ray">The ray to test</param>
|
||||
/// <param name="world">World to transform box</param>
|
||||
/// <param name="distance">When the method completes and returns true, contains the distance of the intersection (if any valid).</param>
|
||||
/// <param name="normal">When the method completes, contains the intersection surface normal vector (if any valid).</param>
|
||||
/// <returns>True whether the two objects intersected</returns>
|
||||
bool Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal) const;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the eight corners of the bounding box.
|
||||
/// </summary>
|
||||
/// <param name="corners">An array of points representing the eight corners of the bounding box.</param>
|
||||
FORCE_INLINE void GetCorners(Vector3 corners[8]) const
|
||||
{
|
||||
_box.GetCorners(corners);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Model instance drawing packed data.
|
||||
/// </summary>
|
||||
struct DrawInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The instance buffer to use during model rendering
|
||||
/// </summary>
|
||||
ModelInstanceEntries* Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// The skinning.
|
||||
/// </summary>
|
||||
SkinnedMeshDrawData* Skinning;
|
||||
|
||||
/// <summary>
|
||||
/// The blend shapes.
|
||||
/// </summary>
|
||||
BlendShapesInstance* BlendShapes;
|
||||
|
||||
/// <summary>
|
||||
/// The world transformation of the model.
|
||||
/// </summary>
|
||||
Matrix* World;
|
||||
|
||||
/// <summary>
|
||||
/// The instance drawing state data container. Used for LOD transition handling and previous world transformation matrix updating.
|
||||
/// </summary>
|
||||
GeometryDrawStateData* DrawState;
|
||||
|
||||
/// <summary>
|
||||
/// The object draw modes.
|
||||
/// </summary>
|
||||
DrawPass DrawModes;
|
||||
|
||||
/// <summary>
|
||||
/// The bounds of the model (used to select a proper LOD during rendering).
|
||||
/// </summary>
|
||||
BoundingSphere Bounds;
|
||||
|
||||
/// <summary>
|
||||
/// The per-instance random value.
|
||||
/// </summary>
|
||||
float PerInstanceRandom;
|
||||
|
||||
/// <summary>
|
||||
/// The LOD bias value.
|
||||
/// </summary>
|
||||
char LODBias;
|
||||
|
||||
/// <summary>
|
||||
/// The forced LOD to use. Value -1 disables this feature.
|
||||
/// </summary>
|
||||
char ForcedLOD;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Draws the mesh. Binds vertex and index buffers and invokes the draw call.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context.</param>
|
||||
void Render(GPUContext* context) const;
|
||||
|
||||
/// <summary>
|
||||
/// Draws the mesh.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="info">The packed drawing info data.</param>
|
||||
/// <param name="lodDitherFactor">The LOD transition dither factor.</param>
|
||||
void Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Extract mesh buffer data from the GPU (cannot be called from the main thread!).
|
||||
/// </summary>
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <returns>True if failed, otherwise false</returns>
|
||||
bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const;
|
||||
|
||||
/// <summary>
|
||||
/// Extracts mesh buffer data from the GPU in the async task.
|
||||
/// </summary>
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <returns>Created async task used to gather the buffer data.</returns>
|
||||
Task* DownloadDataAsyncGPU(MeshBufferType type, BytesContainer& result) const;
|
||||
|
||||
/// <summary>
|
||||
/// Extract mesh buffer data from the CPU.
|
||||
/// </summary>
|
||||
/// <param name="type">Buffer type</param>
|
||||
/// <param name="result">The result data</param>
|
||||
/// <returns>True if failed, otherwise false</returns>
|
||||
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const;
|
||||
|
||||
private:
|
||||
|
||||
// Internal bindings
|
||||
API_FUNCTION(NoProxy) ScriptingObject* GetParentModel();
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshInt(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj);
|
||||
API_FUNCTION(NoProxy) bool UpdateMeshUShort(MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* blendIndicesObj, MonoArray* blendWeightsObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj);
|
||||
API_FUNCTION(NoProxy) bool DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI);
|
||||
};
|
||||
135
Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp
Normal file
135
Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SkinnedMeshDrawData.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Animations/Config.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
|
||||
struct SkinMatrix3x4
|
||||
{
|
||||
float M[3][4];
|
||||
|
||||
FORCE_INLINE void SetMatrix(const Matrix& mat)
|
||||
{
|
||||
const float* src = mat.Raw;
|
||||
float* dest = &(M[0][0]);
|
||||
|
||||
dest[0] = src[0]; // [0][0]
|
||||
dest[1] = src[1]; // [0][1]
|
||||
dest[2] = src[2]; // [0][2]
|
||||
dest[3] = src[3]; // [0][3]
|
||||
|
||||
dest[4] = src[4]; // [1][0]
|
||||
dest[5] = src[5]; // [1][1]
|
||||
dest[6] = src[6]; // [1][2]
|
||||
dest[7] = src[7]; // [1][3]
|
||||
|
||||
dest[8] = src[8]; // [2][0]
|
||||
dest[9] = src[9]; // [2][1]
|
||||
dest[10] = src[10]; // [2][2]
|
||||
dest[11] = src[11]; // [2][3]
|
||||
}
|
||||
|
||||
FORCE_INLINE void SetMatrixTranspose(const Matrix& mat)
|
||||
{
|
||||
const float* src = mat.Raw;
|
||||
float* dest = &(M[0][0]);
|
||||
|
||||
dest[0] = src[0]; // [0][0]
|
||||
dest[1] = src[4]; // [1][0]
|
||||
dest[2] = src[8]; // [2][0]
|
||||
dest[3] = src[12]; // [3][0]
|
||||
|
||||
dest[4] = src[1]; // [0][1]
|
||||
dest[5] = src[5]; // [1][1]
|
||||
dest[6] = src[9]; // [2][1]
|
||||
dest[7] = src[13]; // [3][1]
|
||||
|
||||
dest[8] = src[2]; // [0][2]
|
||||
dest[9] = src[6]; // [1][2]
|
||||
dest[10] = src[10]; // [2][2]
|
||||
dest[11] = src[14]; // [3][2]
|
||||
}
|
||||
};
|
||||
|
||||
SkinnedMeshDrawData::~SkinnedMeshDrawData()
|
||||
{
|
||||
SAFE_DELETE_GPU_RESOURCE(BoneMatrices);
|
||||
SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices);
|
||||
}
|
||||
|
||||
void SkinnedMeshDrawData::Setup(int32 bonesCount)
|
||||
{
|
||||
if (BoneMatrices == nullptr)
|
||||
{
|
||||
BoneMatrices = GPUDevice::Instance->CreateBuffer(TEXT("BoneMatrices"));
|
||||
}
|
||||
|
||||
const int32 elementsCount = bonesCount * 3; // 3 * float4 per bone
|
||||
if (BoneMatrices->Init(GPUBufferDescription::Typed(elementsCount, PixelFormat::R32G32B32A32_Float, false, GPUResourceUsage::Dynamic)))
|
||||
{
|
||||
LOG(Error, "Failed to initialize the skinned mesh bones buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
BonesCount = bonesCount;
|
||||
_hasValidData = false;
|
||||
_isDirty = false;
|
||||
Data.Resize(BoneMatrices->GetSize());
|
||||
SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices);
|
||||
}
|
||||
|
||||
void SkinnedMeshDrawData::SetData(const Matrix* bones, bool dropHistory)
|
||||
{
|
||||
if (!bones)
|
||||
return;
|
||||
ASSERT(BonesCount > 0);
|
||||
|
||||
ANIM_GRAPH_PROFILE_EVENT("SetSkinnedMeshData");
|
||||
|
||||
// Setup previous frame bone matrices if needed
|
||||
if (_hasValidData && !dropHistory)
|
||||
{
|
||||
ASSERT(BoneMatrices);
|
||||
if (PrevBoneMatrices == nullptr)
|
||||
{
|
||||
PrevBoneMatrices = GPUDevice::Instance->CreateBuffer(TEXT("BoneMatrices"));
|
||||
if (PrevBoneMatrices->Init(BoneMatrices->GetDescription()))
|
||||
{
|
||||
LOG(Fatal, "Failed to initialize the skinned mesh bones buffer");
|
||||
}
|
||||
}
|
||||
|
||||
Swap(PrevBoneMatrices, BoneMatrices);
|
||||
}
|
||||
else
|
||||
{
|
||||
SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices);
|
||||
}
|
||||
|
||||
// Copy bones to the buffer
|
||||
const int32 count = BonesCount;
|
||||
const int32 PreFetchStride = 2;
|
||||
const Matrix* input = bones;
|
||||
const auto output = (SkinMatrix3x4*)Data.Get();
|
||||
ASSERT(Data.Count() == count * sizeof(SkinMatrix3x4));
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
SkinMatrix3x4* bone = output + i;
|
||||
Platform::Prefetch(bone + PreFetchStride);
|
||||
Platform::Prefetch((byte*)(bone + PreFetchStride) + PLATFORM_CACHE_LINE_SIZE);
|
||||
bone->SetMatrixTranspose(input[i]);
|
||||
}
|
||||
|
||||
_isDirty = true;
|
||||
_hasValidData = true;
|
||||
}
|
||||
|
||||
void SkinnedMeshDrawData::Flush(GPUContext* context)
|
||||
{
|
||||
if (_isDirty)
|
||||
{
|
||||
_isDirty = false;
|
||||
context->UpdateBuffer(BoneMatrices, Data.Get(), Data.Count());
|
||||
}
|
||||
}
|
||||
83
Source/Engine/Graphics/Models/SkinnedMeshDrawData.h
Normal file
83
Source/Engine/Graphics/Models/SkinnedMeshDrawData.h
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Common.h"
|
||||
#include "Engine/Graphics/GPUBuffer.h"
|
||||
|
||||
/// <summary>
|
||||
/// Data storage for the skinned meshes rendering
|
||||
/// </summary>
|
||||
class FLAXENGINE_API SkinnedMeshDrawData
|
||||
{
|
||||
private:
|
||||
|
||||
bool _hasValidData = false;
|
||||
bool _isDirty = false;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The bones count.
|
||||
/// </summary>
|
||||
int32 BonesCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The bone matrices buffer. Contains prepared skeletal bones transformations (stored as 4x3, 3 Vector4 behind each other).
|
||||
/// </summary>
|
||||
GPUBuffer* BoneMatrices = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// The bone matrices buffer used during the previous update. Used by per-bone motion blur.
|
||||
/// </summary>
|
||||
GPUBuffer* PrevBoneMatrices = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// The CPU data buffer with the bones transformations (ready to be flushed with the GPU).
|
||||
/// </summary>
|
||||
Array<byte> Data;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkinnedMeshDrawData"/> class.
|
||||
/// </summary>
|
||||
SkinnedMeshDrawData()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="SkinnedMeshDrawData"/> class.
|
||||
/// </summary>
|
||||
~SkinnedMeshDrawData();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance is ready for rendering.
|
||||
/// </summary>
|
||||
/// <returns>True if has valid data and can be rendered, otherwise false.</returns>
|
||||
FORCE_INLINE bool IsReady() const
|
||||
{
|
||||
return BoneMatrices != nullptr && BoneMatrices->IsAllocated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setups the data container for the specified bones amount.
|
||||
/// </summary>
|
||||
/// <param name="bonesCount">The bones count.</param>
|
||||
void Setup(int32 bonesCount);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bone matrices data for the GPU buffer. Ensure to call Flush before rendering.
|
||||
/// </summary>
|
||||
/// <param name="bones">The bones data.</param>
|
||||
/// <param name="dropHistory">True if drop previous update bones used for motion blur, otherwise will keep them and do the update.</param>
|
||||
void SetData(const Matrix* bones, bool dropHistory);
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the bones data buffer with the GPU by sending the data fro the CPU.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context.</param>
|
||||
void Flush(class GPUContext* context);
|
||||
};
|
||||
152
Source/Engine/Graphics/Models/SkinnedModelLOD.cpp
Normal file
152
Source/Engine/Graphics/Models/SkinnedModelLOD.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SkinnedModelLOD.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
|
||||
bool SkinnedModelLOD::Load(MemoryReadStream& stream)
|
||||
{
|
||||
// Load LOD for each mesh
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
auto& mesh = Meshes[i];
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.ReadUint16(&blendShapesCount);
|
||||
if (blendShapesCount != mesh.BlendShapes.Count())
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {0}. Incorect blend shapes amount: {1} (expected: {2})", i, blendShapesCount, mesh.BlendShapes.Count());
|
||||
return true;
|
||||
}
|
||||
for (auto& blendShape : mesh.BlendShapes)
|
||||
{
|
||||
blendShape.UseNormals = stream.ReadBool();
|
||||
stream.ReadUint32(&blendShape.MinVertexIndex);
|
||||
stream.ReadUint32(&blendShape.MaxVertexIndex);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
blendShape.Vertices.Resize(blendShapeVertices);
|
||||
stream.ReadBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
const uint32 indicesCount = triangles * 3;
|
||||
const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
const auto vb0 = stream.Read<VB0SkinnedElementType>(vertices);
|
||||
const auto ib = stream.Read<byte>(indicesCount * ibStride);
|
||||
|
||||
// Setup GPU resources
|
||||
if (mesh.Load(vertices, triangles, vb0, ib, use16BitIndexBuffer))
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {0}. Vertices: {1}, triangles: {2}", i, vertices, triangles);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkinnedModelLOD::Unload()
|
||||
{
|
||||
// Unload LOD for each mesh
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
Meshes[i].Unload();
|
||||
}
|
||||
}
|
||||
|
||||
void SkinnedModelLOD::Dispose()
|
||||
{
|
||||
_model = nullptr;
|
||||
ScreenSize = 0.0f;
|
||||
Meshes.Resize(0);
|
||||
}
|
||||
|
||||
bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal, SkinnedMesh** mesh)
|
||||
{
|
||||
// Check all meshes
|
||||
bool result = false;
|
||||
float closest = MAX_float;
|
||||
Vector3 closestNormal = Vector3::Up;
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
// Test intersection with mesh and check if is closer than previous
|
||||
float dst;
|
||||
Vector3 nrm;
|
||||
if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
|
||||
{
|
||||
result = true;
|
||||
*mesh = &Meshes[i];
|
||||
closest = dst;
|
||||
closestNormal = nrm;
|
||||
}
|
||||
}
|
||||
|
||||
distance = closest;
|
||||
normal = closestNormal;
|
||||
return result;
|
||||
}
|
||||
|
||||
BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const
|
||||
{
|
||||
// Find minimum and maximum points of all the meshes
|
||||
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
|
||||
Vector3 corners[8];
|
||||
for (int32 j = 0; j < Meshes.Count(); j++)
|
||||
{
|
||||
const auto& mesh = Meshes[j];
|
||||
mesh.GetCorners(corners);
|
||||
|
||||
for (int32 i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3::Transform(corners[i], world, 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
|
||||
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
|
||||
Vector3 corners[8];
|
||||
const auto& mesh = Meshes[meshIndex];
|
||||
mesh.GetCorners(corners);
|
||||
for (int32 i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3::Transform(corners[i], world, tmp);
|
||||
min = Vector3::Min(min, tmp);
|
||||
max = Vector3::Max(max, tmp);
|
||||
}
|
||||
|
||||
return BoundingBox(min, max);
|
||||
}
|
||||
|
||||
BoundingBox SkinnedModelLOD::GetBox() const
|
||||
{
|
||||
// Find minimum and maximum points of the mesh in given world
|
||||
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
|
||||
Vector3 corners[8];
|
||||
for (int32 j = 0; j < Meshes.Count(); j++)
|
||||
{
|
||||
Meshes[j].GetCorners(corners);
|
||||
|
||||
for (int32 i = 0; i < 8; i++)
|
||||
{
|
||||
min = Vector3::Min(min, corners[i]);
|
||||
max = Vector3::Max(max, corners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return BoundingBox(min, max);
|
||||
}
|
||||
120
Source/Engine/Graphics/Models/SkinnedModelLOD.h
Normal file
120
Source/Engine/Graphics/Models/SkinnedModelLOD.h
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "SkinnedMesh.h"
|
||||
|
||||
class MemoryReadStream;
|
||||
|
||||
/// <summary>
|
||||
/// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes.
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, PersistentScriptingObject);
|
||||
friend SkinnedModel;
|
||||
private:
|
||||
|
||||
SkinnedModel* _model = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.
|
||||
/// </summary>
|
||||
API_FIELD() float ScreenSize = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The meshes array.
|
||||
/// </summary>
|
||||
API_FIELD(ReadOnly) Array<SkinnedMesh> Meshes;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether any mesh has been initialized.
|
||||
/// </summary>
|
||||
/// <returns>True if any mesh has been initialized, otherwise false.</returns>
|
||||
FORCE_INLINE 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>
|
||||
/// Initializes the LOD from the data stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <returns>True if fails, otherwise false.</returns>
|
||||
bool Load(MemoryReadStream& stream);
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load.
|
||||
/// </summary>
|
||||
void Unload();
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups the data.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there is an intersection between the Model and a Ray in given world using given instance
|
||||
/// </summary>
|
||||
/// <param name="ray">The ray to test</param>
|
||||
/// <param name="world">World to test</param>
|
||||
/// <param name="distance">When the method completes, contains the distance of the intersection (if any valid).</param>
|
||||
/// <param name="normal">When the method completes, contains the intersection surface normal vector (if any valid).</param>
|
||||
/// <param name="mesh">Mesh, or null</param>
|
||||
/// <returns>True whether the two objects intersected</returns>
|
||||
bool Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal, SkinnedMesh** mesh);
|
||||
|
||||
/// <summary>
|
||||
/// Get model bounding box in transformed world for given instance buffer
|
||||
/// </summary>
|
||||
/// <param name="world">World matrix</param>
|
||||
/// <returns>Bounding box</returns>
|
||||
BoundingBox GetBox(const Matrix& world) const;
|
||||
|
||||
/// <summary>
|
||||
/// Get model bounding box in transformed world for given instance buffer for only one mesh
|
||||
/// </summary>
|
||||
/// <param name="world">World matrix</param>
|
||||
/// <param name="meshIndex">esh index</param>
|
||||
/// <returns>Bounding box</returns>
|
||||
BoundingBox GetBox(const Matrix& world, int32 meshIndex) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding box combined for all meshes in this model LOD.
|
||||
/// </summary>
|
||||
API_PROPERTY() BoundingBox GetBox() const;
|
||||
|
||||
/// <summary>
|
||||
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context to draw with.</param>
|
||||
FORCE_INLINE void Render(GPUContext* context)
|
||||
{
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
Meshes[i].Render(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws all the meshes from the model LOD.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="info">The packed drawing info data.</param>
|
||||
/// <param name="lodDitherFactor">The LOD transition dither factor.</param>
|
||||
FORCE_INLINE void Draw(const RenderContext& renderContext, const SkinnedMesh::DrawInfo& info, float lodDitherFactor) const
|
||||
{
|
||||
for (int32 i = 0; i < Meshes.Count(); i++)
|
||||
{
|
||||
Meshes[i].Draw(renderContext, info, lodDitherFactor);
|
||||
}
|
||||
}
|
||||
};
|
||||
179
Source/Engine/Graphics/Models/Types.h
Normal file
179
Source/Engine/Graphics/Models/Types.h
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Enums.h"
|
||||
#include "Engine/Core/Math/Packed.h"
|
||||
#include "Engine/Core/Math/Vector2.h"
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Math/Vector4.h"
|
||||
#include "Engine/Core/Math/Color.h"
|
||||
#include "Engine/Core/Math/Color32.h"
|
||||
#include "Engine/Core/Math/VectorInt.h"
|
||||
|
||||
class Model;
|
||||
class SkinnedModel;
|
||||
class Mesh;
|
||||
class SkinnedMesh;
|
||||
class ModelData;
|
||||
class ModelInstanceEntries;
|
||||
class Model;
|
||||
class SkinnedModel;
|
||||
struct RenderView;
|
||||
|
||||
/// <summary>
|
||||
/// Importing model lightmap UVs source
|
||||
/// </summary>
|
||||
DECLARE_ENUM_6(ModelLightmapUVsSource, Disable, Generate, Channel0, Channel1, Channel2, Channel3);
|
||||
|
||||
/// <summary>
|
||||
/// The mesh buffer types.
|
||||
/// </summary>
|
||||
enum class MeshBufferType
|
||||
{
|
||||
/// <summary>
|
||||
/// The index buffer.
|
||||
/// </summary>
|
||||
Index = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The vertex buffer (first).
|
||||
/// </summary>
|
||||
Vertex0 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The vertex buffer (second).
|
||||
/// </summary>
|
||||
Vertex1 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The vertex buffer (third).
|
||||
/// </summary>
|
||||
Vertex2 = 3,
|
||||
};
|
||||
|
||||
// Vertex structure for all models (versioned)
|
||||
PACK_STRUCT(struct ModelVertex15
|
||||
{
|
||||
Vector3 Position;
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct ModelVertex18
|
||||
{
|
||||
Vector3 Position;
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
Half2 LightmapUVs;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct ModelVertex19
|
||||
{
|
||||
Vector3 Position;
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
Half2 LightmapUVs;
|
||||
Color32 Color;
|
||||
});
|
||||
|
||||
typedef ModelVertex19 ModelVertex;
|
||||
|
||||
//
|
||||
struct RawModelVertex
|
||||
{
|
||||
Vector3 Position;
|
||||
Vector2 TexCoord;
|
||||
Vector3 Normal;
|
||||
Vector3 Tangent;
|
||||
Vector3 Bitangent;
|
||||
Vector2 LightmapUVs;
|
||||
Color Color;
|
||||
};
|
||||
|
||||
// For vertex data we use three buffers: one with positions, one with other attributes, and one with colors
|
||||
PACK_STRUCT(struct VB0ElementType15
|
||||
{
|
||||
Vector3 Position;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct VB1ElementType15
|
||||
{
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct VB0ElementType18
|
||||
{
|
||||
Vector3 Position;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct VB1ElementType18
|
||||
{
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
Half2 LightmapUVs;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct VB2ElementType18
|
||||
{
|
||||
Color32 Color;
|
||||
});
|
||||
|
||||
typedef VB0ElementType18 VB0ElementType;
|
||||
typedef VB1ElementType18 VB1ElementType;
|
||||
typedef VB2ElementType18 VB2ElementType;
|
||||
//
|
||||
|
||||
// Vertex structure for all skinned models (versioned)
|
||||
PACK_STRUCT(struct SkinnedModelVertex1
|
||||
{
|
||||
Vector3 Position;
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
Color32 BlendIndices;
|
||||
Color32 BlendWeights;
|
||||
});
|
||||
|
||||
typedef SkinnedModelVertex1 SkinnedModelVertex;
|
||||
|
||||
//
|
||||
struct RawSkinnedModelVertex
|
||||
{
|
||||
Vector3 Position;
|
||||
Vector2 TexCoord;
|
||||
Vector3 Normal;
|
||||
Vector3 Tangent;
|
||||
Vector3 Bitangent;
|
||||
Int4 BlendIndices;
|
||||
Vector4 BlendWeights;
|
||||
};
|
||||
|
||||
PACK_STRUCT(struct VB0SkinnedElementType1
|
||||
{
|
||||
Vector3 Position;
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
Color32 BlendIndices;
|
||||
Color32 BlendWeights;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct VB0SkinnedElementType2
|
||||
{
|
||||
Vector3 Position;
|
||||
Half2 TexCoord;
|
||||
Float1010102 Normal;
|
||||
Float1010102 Tangent;
|
||||
Color32 BlendIndices;
|
||||
Half4 BlendWeights;
|
||||
});
|
||||
|
||||
typedef VB0SkinnedElementType2 VB0SkinnedElementType;
|
||||
//
|
||||
Reference in New Issue
Block a user