diff options
Diffstat (limited to 'Runtime/Filters/Mesh/MeshCombiner.cpp')
-rw-r--r-- | Runtime/Filters/Mesh/MeshCombiner.cpp | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/Runtime/Filters/Mesh/MeshCombiner.cpp b/Runtime/Filters/Mesh/MeshCombiner.cpp new file mode 100644 index 0000000..1bf93e5 --- /dev/null +++ b/Runtime/Filters/Mesh/MeshCombiner.cpp @@ -0,0 +1,502 @@ +#include "UnityPrefix.h" +#include "MeshCombiner.h" +#include "Runtime/Graphics/TriStripper.h" +#include "Runtime/Shaders/GraphicsCaps.h" +#include "Runtime/Profiler/Profiler.h" +#include <limits> + + +#define sqr(x) ((x)*(x)) + +PROFILER_INFORMATION(gCombineMeshesProfile, "CombineMeshes", kProfilerRender) +PROFILER_INFORMATION(gCombineVerticesProfile, "CombineVertices", kProfilerRender) +PROFILER_INFORMATION(gCombineIndicesProfile, "CombineIndices", kProfilerRender) + +static void CombineBoneSkinning (const CombineInstances &in, Mesh& outCombinedMesh); + + +size_t ExtractMeshIndices(Mesh::TemporaryIndexContainer& srcIndices, const CombineInstance& in, bool useVertexOffsets, size_t& inoutTotalVertexOffset, UInt16* dstIndices) +{ + srcIndices.clear(); + + if (in.subMeshIndex < 0 || in.subMeshIndex >= in.mesh->GetSubMeshCount()) + return 0; + + const int subMeshIndex = in.subMeshIndex; + const int vertexOffset = useVertexOffsets ? in.vertexOffset : inoutTotalVertexOffset; + inoutTotalVertexOffset += in.mesh->GetVertexCount(); + + in.mesh->GetTriangles( srcIndices, subMeshIndex ); + + size_t numIndices = srcIndices.size(); + if (Dot (Cross(in.transform.GetAxisX(), in.transform.GetAxisY()), in.transform.GetAxisZ()) >= 0) + { + for ( size_t k=0; k!=numIndices; ++k ) + dstIndices[k] = srcIndices[k] + vertexOffset; + } + else + { + // if trilist, then + // reverse Cull order by reversing indices + for ( size_t k=0; k!=numIndices; ++k ) + dstIndices[k] = srcIndices[numIndices-k-1] + vertexOffset; + } + + return numIndices; +} + +static bool IsMeshBatchable (const Mesh* mesh, int subMeshIndex) +{ + return mesh && mesh->HasVertexData() && subMeshIndex >= 0 && subMeshIndex < mesh->GetSubMeshCount(); +} + + +void CombineMeshIndicesForStaticBatching(const CombineInstances& in, Mesh& inoutMesh, bool mergeSubMeshes, bool useVertexOffsets) +{ + PROFILER_AUTO(gCombineIndicesProfile, &inoutMesh); + + size_t size = in.size(); + + UInt32 maxIndices = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const UInt32 numTris = in[i].mesh->GetSubMeshFast( in[i].subMeshIndex ).indexCount; + if (mergeSubMeshes) + maxIndices += numTris; + else + maxIndices = std::max( maxIndices, numTris ); + } + } + + UInt16* dstIndices = new UInt16[maxIndices+1]; + Mesh::TemporaryIndexContainer srcIndices; + srcIndices.reserve( maxIndices+1 ); + + size_t totalVertexOffset = 0; + if (mergeSubMeshes) + { + inoutMesh.SetSubMeshCount( 1 ); + size_t totalNumIndices = 0; + for ( size_t s=0; s!=size; ++s ) + { + if (in[s].mesh) + { + size_t numIndices = ExtractMeshIndices (srcIndices, in[s], useVertexOffsets, totalVertexOffset, dstIndices+totalNumIndices); + + totalNumIndices += numIndices; + Assert(totalNumIndices <= (maxIndices+1)); + } + } + int mask = Mesh::k16BitIndices; + inoutMesh.SetIndicesComplex (dstIndices, totalNumIndices, 0, kPrimitiveTriangles, mask); + } + else + { + inoutMesh.SetSubMeshCount( in.size() ); + for ( size_t s=0; s!=size; ++s ) + { + if (in[s].mesh) + { + size_t numIndices = ExtractMeshIndices (srcIndices, in[s], useVertexOffsets, totalVertexOffset, dstIndices); + Assert(numIndices <= (maxIndices+1)); + + int mask = Mesh::k16BitIndices; + inoutMesh.SetIndicesComplex (dstIndices, numIndices, s, kPrimitiveTriangles, mask); + } + } + } + + delete []dstIndices; +} + +void CombineMeshVerticesForStaticBatching ( const CombineInstances& in, const string& combinedMeshName, Mesh& outCombinedMesh, bool useTransforms ) +{ + PROFILER_AUTO(gCombineVerticesProfile, &outCombinedMesh); + + int vertexCount = 0; + size_t size = in.size(); + for( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + vertexCount += in[i].mesh->GetVertexCount(); + } + + bool hasNormals = false; + bool hasTangents = false; + bool hasUV0 = false; + bool hasUV1 = false; + bool hasColors = false; + bool hasSkin = false; + int bindposeCount = 0; + + for( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + const UInt32 channels = mesh->GetAvailableChannels(); + hasNormals |= (channels & (1<<kShaderChannelNormal)) != 0; + hasTangents |= (channels & (1<<kShaderChannelTangent)) != 0; + hasUV0 |= (channels & (1<<kShaderChannelTexCoord0)) != 0; + hasUV1 |= (channels & (1<<kShaderChannelTexCoord1)) != 0 || (in[i].lightmapTilingOffset != Vector4f(1, 1, 0, 0)); + hasColors |= (channels & (1<<kShaderChannelColor)) != 0; + hasSkin |= mesh->GetSkin().size() && mesh->GetBindpose().size(); + bindposeCount += mesh->GetBindpose().size(); + } + } + + UInt32 channels = 1<<kShaderChannelVertex; + if ( hasNormals ) channels |= 1<<kShaderChannelNormal; + if ( hasTangents ) channels |= 1<<kShaderChannelTangent; + if ( hasUV0 ) channels |= 1<<kShaderChannelTexCoord0; + if ( hasUV1 ) channels |= 1<<kShaderChannelTexCoord1; + if ( hasColors ) channels |= 1<<kShaderChannelColor; + + outCombinedMesh.Clear(true); + outCombinedMesh.ResizeVertices( vertexCount, channels ); + outCombinedMesh.SetName( combinedMeshName.c_str() ); + // Input meshes are already swizzled correctly, so we can copy colors directly + outCombinedMesh.SetVertexColorsSwizzled(gGraphicsCaps.needsToSwizzleVertexColors); + + if ( hasSkin ) + { + outCombinedMesh.GetSkin().resize_initialized(vertexCount); + outCombinedMesh.GetBindpose().resize_initialized(bindposeCount); + outCombinedMesh.GetBonePathHashes().resize_uninitialized(bindposeCount); + } + + // avoid doing twice (in worst case) + Matrix4x4f* normalMatrices; + bool* isNonUniformScaleTransform; + ALLOC_TEMP (normalMatrices, Matrix4x4f, size); + ALLOC_TEMP (isNonUniformScaleTransform, bool, size); + if ( hasNormals || hasTangents ) + { + for( size_t i=0; i!=size; ++i ) + { + float uniformScale; + TransformType type = ComputeTransformType(in[i].transform, uniformScale); + Matrix4x4f m; + isNonUniformScaleTransform[i] = IsNonUniformScaleTransform(type); + if (isNonUniformScaleTransform[i]) + { + Matrix4x4f::Invert_General3D( in[i].transform, normalMatrices[i] ); + normalMatrices[i].Transpose(); + } + else + { + normalMatrices[i] = Matrix3x3f(in[i].transform); + // Scale matrix to keep normals normalized + normalMatrices[i].Scale(Vector3f::one * (1.0f/uniformScale)); + } + } + } + + int offset = 0; + for( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Matrix4x4f& transform = in[i].transform; + const Mesh* mesh = in[i].mesh; + if (useTransforms) + TransformPoints3x4 (transform, + (Vector3f const*)mesh->GetChannelPointer (kShaderChannelVertex), + mesh->GetStride (kShaderChannelVertex), + (Vector3f*)outCombinedMesh.GetChannelPointer (kShaderChannelVertex, offset), + outCombinedMesh.GetStride (kShaderChannelVertex), + mesh->GetVertexCount()); + else + strided_copy (mesh->GetVertexBegin (), mesh->GetVertexEnd (), outCombinedMesh.GetVertexBegin () + offset); + offset += mesh->GetVertexCount(); + } + } + + if ( hasNormals ) + { + offset = 0; + for( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + int vertexCount = mesh->GetVertexCount (); + if (!mesh->IsAvailable (kShaderChannelNormal)) + std::fill(outCombinedMesh.GetNormalBegin () + offset, outCombinedMesh.GetNormalBegin () + offset + vertexCount, Vector3f(0.0f,1.0f,0.0f)); + else + { + const Matrix4x4f& transform = normalMatrices[i]; + + StrideIterator<Vector3f> outNormal = outCombinedMesh.GetNormalBegin () + offset; + if (useTransforms) + { + if (isNonUniformScaleTransform[i]) + { + for (StrideIterator<Vector3f> it = mesh->GetNormalBegin (), end = mesh->GetNormalEnd (); it != end; ++it, ++outNormal) + *outNormal = Normalize( transform.MultiplyVector3( *it) ); + } + else + { + for (StrideIterator<Vector3f> it = mesh->GetNormalBegin (), end = mesh->GetNormalEnd (); it != end; ++it, ++outNormal) + *outNormal = transform.MultiplyVector3( *it); + } + } + else + strided_copy (mesh->GetNormalBegin (), mesh->GetNormalEnd (), outCombinedMesh.GetNormalBegin () + offset); + } + offset += vertexCount; + } + } + } + + if ( hasTangents ) + { + offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + int vertexCount = mesh->GetVertexCount (); + if (!mesh->IsAvailable (kShaderChannelTangent)) + std::fill(outCombinedMesh.GetTangentBegin () + offset, outCombinedMesh.GetTangentBegin () + offset + vertexCount, Vector4f(1.0f,0.0f,0.0f,1.0f)); + else + { + const Matrix4x4f& transform = normalMatrices[i]; + + StrideIterator<Vector4f> outTanget = outCombinedMesh.GetTangentBegin () + offset; + if (useTransforms) + { + if (isNonUniformScaleTransform[i]) + { + for (StrideIterator<Vector4f> it = mesh->GetTangentBegin (), end = mesh->GetTangentEnd (); it != end; ++it, ++outTanget) + { + Vector3f t3 = Normalize(transform.MultiplyVector3(Vector3f(it->x, it->y, it->z))); + *outTanget = Vector4f(t3.x,t3.y,t3.z,it->w); + } + } + else + { + for (StrideIterator<Vector4f> it = mesh->GetTangentBegin (), end = mesh->GetTangentEnd (); it != end; ++it, ++outTanget) + { + Vector3f t3 = transform.MultiplyVector3(Vector3f(it->x, it->y, it->z)); + *outTanget = Vector4f(t3.x,t3.y,t3.z,it->w); + } + } + } + else + strided_copy (mesh->GetTangentBegin (), mesh->GetTangentEnd (), outCombinedMesh.GetTangentBegin () + offset); + } + offset += vertexCount; + } + } + } + + if ( hasUV0 ) + { + offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + int vertexCount = mesh->GetVertexCount (); + if (!mesh->IsAvailable (kShaderChannelTexCoord0)) + std::fill (outCombinedMesh.GetUvBegin (0) + offset, outCombinedMesh.GetUvBegin (0) + offset + vertexCount, Vector2f(0.0f,0.0f)); + else + strided_copy (mesh->GetUvBegin (0), mesh->GetUvEnd (0), outCombinedMesh.GetUvBegin (0) + offset); + offset += vertexCount; + } + } + } + + if ( hasUV1 ) + { + offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + const int uvIndex = (mesh->GetAvailableChannels() & (1<<kShaderChannelTexCoord1))!=0? 1 : 0; + StrideIterator<Vector2f> it = in[i].mesh->GetUvBegin( uvIndex ); + StrideIterator<Vector2f> end = in[i].mesh->GetUvEnd( uvIndex ); + + int vertexCount = mesh->GetVertexCount (); + if ( it == end) + std::fill (outCombinedMesh.GetUvBegin (1) + offset, outCombinedMesh.GetUvBegin (1) + offset + vertexCount, Vector2f(0.0f,0.0f)); + else + { + // we have to apply lightmap UV scale and offset factors + // callee is responsible to reset lightmapTilingOffset on the Renderer afterwards + const Vector4f uvScaleOffset = in[i].lightmapTilingOffset; + if ( uvScaleOffset != Vector4f(1, 1, 0, 0) ) + { + StrideIterator<Vector2f> outUV = outCombinedMesh.GetUvBegin (1) + offset; + for (; it != end; ++it, ++outUV) + { + outUV->x = it->x * uvScaleOffset.x + uvScaleOffset.z; + outUV->y = it->y * uvScaleOffset.y + uvScaleOffset.w; + } + } + else + strided_copy (it, end, outCombinedMesh.GetUvBegin (1) + offset); + } + offset += vertexCount; + } + } + } + + if ( hasColors ) + { + offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + { + const Mesh* mesh = in[i].mesh; + int vertexCount = mesh->GetVertexCount (); + if (!mesh->IsAvailable (kShaderChannelColor)) + std::fill (outCombinedMesh.GetColorBegin () + offset, outCombinedMesh.GetColorBegin () + offset + vertexCount, ColorRGBA32(255,255,255,255)); + else + { + DebugAssert(mesh->GetVertexColorsSwizzled() == outCombinedMesh.GetVertexColorsSwizzled()); + strided_copy (mesh->GetColorBegin (), mesh->GetColorEnd (), outCombinedMesh.GetColorBegin () + offset); + } + offset += vertexCount; + } + } + } + + if ( hasSkin ) + { + CombineBoneSkinning (in, outCombinedMesh); + } +} + +static void CalculateRootBonePathHash (const CombineInstances &in, Mesh& outCombinedMesh) +{ + // We always pick the root bone path hash of the first combine instance. + // This is because anything else gives unpredictable behaviour and makes it impossible for the user + // to setup the skinned mesh renderer T/R/S correctly. + outCombinedMesh.SetRootBonePathHash(in[0].mesh->GetRootBonePathHash()); + + // If we made it so that the skinnedmeshrenderer always used the default pose from the Avatar + // Then it would be possible to pick the root bone from the mesh with the most bones instead. +#if 0 + size_t size = in.size(); + + BindingHash rootBonePathHash = 0; + int boneCount = 0; + for (size_t i=0; i<size; ++i) + { + } + } + if (rootBonePathHash) + outCombinedMesh.SetRootBonePathHash(rootBonePathHash); +#endif +} + +static void CombineBoneSkinning (const CombineInstances &in, Mesh& outCombinedMesh) +{ + size_t size = in.size(); + + int boneOffset = 0; + int offset = 0; + for ( size_t i=0; i!=size; ++i ) + { + if (!IsMeshBatchable(in[i].mesh, in[i].subMeshIndex)) + continue; + + const Mesh* mesh = in[i].mesh; + Mesh::BoneInfluenceContainer& outSkin = outCombinedMesh.GetSkin(); + const Mesh::BoneInfluenceContainer& inSkin = mesh->GetSkin(); + int vertexCount = mesh->GetVertexCount (); + if (inSkin.empty()) + { + for(int i=0; i<vertexCount;i++) + { + outSkin[offset+i].weight[0] = 0; + outSkin[offset+i].weight[1] = 0; + outSkin[offset+i].weight[2] = 0; + outSkin[offset+i].weight[3] = 0; + outSkin[offset+i].boneIndex[0] = 0; + outSkin[offset+i].boneIndex[1] = 0; + outSkin[offset+i].boneIndex[2] = 0; + outSkin[offset+i].boneIndex[3] = 0; + } + } + else + { + for(int i=0; i<vertexCount;i++) + { + outSkin[offset+i].weight[0] = inSkin[i].weight[0]; + outSkin[offset+i].weight[1] = inSkin[i].weight[1]; + outSkin[offset+i].weight[2] = inSkin[i].weight[2]; + outSkin[offset+i].weight[3] = inSkin[i].weight[3]; + outSkin[offset+i].boneIndex[0] = inSkin[i].boneIndex[0]+boneOffset; + outSkin[offset+i].boneIndex[1] = inSkin[i].boneIndex[1]+boneOffset; + outSkin[offset+i].boneIndex[2] = inSkin[i].boneIndex[2]+boneOffset; + outSkin[offset+i].boneIndex[3] = inSkin[i].boneIndex[3]+boneOffset; + } + } + + offset += vertexCount; + + int poseCount = mesh->GetBindpose().size(); + int bindingHashCount = mesh->GetBonePathHashes().size(); + + memcpy(outCombinedMesh.GetBindpose().begin() + boneOffset, mesh->GetBindpose().begin(), poseCount*sizeof(Matrix4x4f)); + + // Old asset bundles might not have bindingHashCount in sync with bind poses. + if (poseCount == bindingHashCount) + memcpy(outCombinedMesh.GetBonePathHashes().begin () + boneOffset, mesh->GetBonePathHashes().begin(), poseCount*sizeof(BindingHash)); + else + memset(outCombinedMesh.GetBonePathHashes().begin () + boneOffset, 0, poseCount*sizeof(BindingHash)); + + boneOffset += poseCount; + } + + CalculateRootBonePathHash (in, outCombinedMesh); +} + + +void CombineMeshes (const CombineInstances &in, Mesh& out, bool mergeSubMeshes, bool useTransforms) +{ + if (!out.CanAccessFromScript()) + { + ErrorStringMsg("Cannot combine into mesh that does not allow access: %s", out.GetName()); + return; + } + for (size_t i = 0; i < in.size(); ++i) + { + Mesh* mesh = in[i].mesh; + if (!mesh) + { + WarningStringMsg("Combine mesh instance %" PRINTF_SIZET_FORMAT " is null.", i); + } + if (mesh && (in[i].subMeshIndex < 0 || in[i].subMeshIndex >= mesh->GetSubMeshCount())) + { + WarningStringMsg("Submesh index %d is invalid for mesh %s.", in[i].subMeshIndex, mesh->GetName()); + } + if (mesh && !mesh->CanAccessFromScript()) + { + ErrorStringMsg("Cannot combine mesh that does not allow access: %s", mesh->GetName()); + return; + } + if (mesh == &out) + { + ErrorStringMsg("Cannot combine into a mesh that is also in the CombineInstances input: %s", mesh->GetName()); + return; + } + } + + CombineMeshVerticesForStaticBatching (in, out.GetName(), out, useTransforms); + CombineMeshIndicesForStaticBatching (in, out, mergeSubMeshes, false); + + out.RecalculateBounds(); + out.UpdateVertexFormat(); +} + |