You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View 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;
}

View 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();
};

View 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;
}
};

View 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

View 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
}
};

View 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;
}

View 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);
};

View 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

View 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;
}

View 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;
};

View 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);
}
}

View 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;
};

View 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);
}

View 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);
}
}
};

View 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);
}
};

View 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()
{
}
};

View 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;
}
}
};

View 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;
}

View 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);
};

View 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());
}
}

View 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);
};

View 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);
}

View 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);
}
}
};

View 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;
//