/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #ifndef CRYINCLUDE_CRYCOMMON_IINDEXEDMESH_H #define CRYINCLUDE_CRYCOMMON_IINDEXEDMESH_H #pragma once #include "CryHeaders.h" #include "Cry_Color.h" #include "StlUtils.h" #include "CryEndian.h" #include #include #include <../RenderDll/Common/Shaders/Vertex.h> #include // Description: // 2D Texture coordinates used by CMesh. struct SMeshTexCoord { SMeshTexCoord() {} private: float s, t; public: explicit SMeshTexCoord(float x, float y) { s = x; t = y; } explicit SMeshTexCoord(const Vec2f16& other) { const Vec2 uv = other.ToVec2(); s = uv.x; t = uv.y; } explicit SMeshTexCoord(const Vec2& other) { s = other.x; t = other.y; } explicit SMeshTexCoord(const Vec4& other) { s = other.x; t = other.y; } void ExportTo(Vec2f16& other) const { other = Vec2f16(s, t); } void ExportTo(float& others, float& othert) const { others = s; othert = t; } bool operator ==(const SMeshTexCoord& other) const { return (s == other.s) && (t == other.t); } bool operator !=(const SMeshTexCoord& other) const { return !(*this == other); } bool operator <(const SMeshTexCoord& other) const { return (s != other.s) ? (s < other.s) : (t < other.t); } bool IsEquivalent(const Vec2& other, float epsilon = 0.003f) const { return (fabs_tpl(s - other.x) <= epsilon) && (fabs_tpl(t - other.y) <= epsilon); } bool IsEquivalent(const SMeshTexCoord& other, float epsilon = 0.00005f) const { return (fabs_tpl(s - other.s) <= epsilon) && (fabs_tpl(t - other.t) <= epsilon); } ILINE Vec2 GetUV() const { return Vec2(s, t); } void GetUV(Vec2& otheruv) const { otheruv = GetUV(); } void GetUV(Vec4& otheruv) const { otheruv = Vec4(s, t, 0.0f, 1.0f); } void Lerp(const SMeshTexCoord& other, float pos) { Vec2 texA; Vec2 texB; this->GetUV(); other.GetUV(); texA.SetLerp(texA, texB, pos); *this = SMeshTexCoord(texA); } AUTO_STRUCT_INFO }; // Description: // RGBA Color description structure used by CMesh. struct SMeshColor { SMeshColor() {} private: uint8 r, g, b, a; public: explicit SMeshColor(uint8 otherr, uint8 otherg, uint8 otherb, uint8 othera) { r = otherr; g = otherg; b = otherb; a = othera; } explicit SMeshColor(const Vec4& otherc) { r = aznumeric_caster(FtoI(otherc.x)); g = aznumeric_caster(FtoI(otherc.y)); b = aznumeric_caster(FtoI(otherc.z)); a = aznumeric_caster(FtoI(otherc.w)); } void TransferRGBTo(SMeshColor& other) const { other.r = r; other.g = g; other.b = b; } void TransferATo(SMeshColor& other) const { other.a = a; } void MaskA(uint8 maska) { a &= maska; } bool operator ==(const SMeshColor& other) const { return (r == other.r) && (g == other.g) && (b == other.b) && (a == other.a); } bool operator !=(const SMeshColor& other) const { return !(*this == other); } bool operator <(const SMeshColor& other) const { return (r != other.r) ? (r < other.r) : (g != other.g) ? (g < other.g) : (b != other.b) ? (b < other.b) : (a < other.a); } ILINE ColorB GetRGBA() const { return ColorB(r, g, b, a); } void GetRGBA(ColorB& otherc) const { otherc = GetRGBA(); } void GetRGBA(Vec4& otherc) const { otherc = Vec4(r, g, b, a); } void Lerp(const SMeshColor& other, float pos) { Vec4 clrA; Vec4 clrB; this->GetRGBA(clrA); other.GetRGBA(clrB); clrA.SetLerp(clrA, clrB, pos); *this = SMeshColor(clrA); } AUTO_STRUCT_INFO }; // Description: // Defines a single triangle face in the CMesh topology. struct SMeshFace { int v[3]; // indices to vertex, normals and optionally tangent basis arrays unsigned char nSubset; // index to mesh subsets array. AUTO_STRUCT_INFO }; // Description: // 3D Normal Vector used by CMesh. struct SMeshNormal { SMeshNormal() {} private: Vec3 Normal; public: explicit SMeshNormal(const Vec3& othern) { Normal = othern; } bool operator ==(const SMeshNormal& othern) const { return (Normal.x == othern.Normal.x) && (Normal.y == othern.Normal.y) && (Normal.z == othern.Normal.z); } bool operator !=(const SMeshNormal& othern) const { return !(*this == othern); } bool operator <(const SMeshNormal& othern) const { return (Normal.x != othern.Normal.x) ? (Normal.x < othern.Normal.x) : (Normal.y != othern.Normal.y) ? (Normal.y < othern.Normal.y) : (Normal.z < othern.Normal.z); } bool IsEquivalent(const Vec3& othern, float epsilon = 0.00005f) const { return Normal.IsEquivalent(othern, epsilon); } bool IsEquivalent(const SMeshNormal& othern, float epsilon = 0.00005f) const { return IsEquivalent(othern.Normal, epsilon); } ILINE Vec3 GetN() const { return Normal; } void GetN(Vec3& othern) const { othern = GetN(); } void RotateBy(const Matrix33& rot) { Normal = rot * Normal; } void RotateSafelyBy(const Matrix33& rot) { Normal = rot * Normal; // normalize in case "rot" wasn't length-preserving Normal.Normalize(); } void RotateBy(const Matrix34& trn) { Normal = trn.TransformVector(Normal); } void RotateSafelyBy(const Matrix34& trn) { Normal = trn.TransformVector(Normal); // normalize in case "trn" wasn't length-preserving Normal.Normalize(); } void Slerp(const SMeshNormal& other, float pos) { Vec3 nrmA = this->GetN(); Vec3 nrmB = other.GetN(); nrmA.Normalize(); nrmB.Normalize(); nrmA.SetSlerp(nrmA, nrmB, pos); *this = SMeshNormal(nrmA); } AUTO_STRUCT_INFO }; // Description: // Mesh tangents (tangent space normals). struct SMeshTangents { SMeshTangents() {} private: Vec4sf Tangent; Vec4sf Bitangent; public: explicit SMeshTangents(const Vec4sf& othert, const Vec4sf& otherb) { Tangent = othert; Bitangent = otherb; } explicit SMeshTangents(const SPipTangents& other) { Tangent = other.Tangent; Bitangent = other.Bitangent; } explicit SMeshTangents(const Vec4& othert, const Vec4& otherb) { Tangent = PackingSNorm::tPackF2Bv(othert); Bitangent = PackingSNorm::tPackF2Bv(otherb); } explicit SMeshTangents(const Vec3& othert, const Vec3& otherb, const Vec3& othern) { // TODO: can be optimized to use only integer arithmetic int16 othersign = 1; if (othert.Cross(otherb).Dot(othern) < 0) { othersign = -1; } Tangent = Vec4sf(PackingSNorm::tPackF2B(othert.x), PackingSNorm::tPackF2B(othert.y), PackingSNorm::tPackF2B(othert.z), PackingSNorm::tPackS2B(othersign)); Bitangent = Vec4sf(PackingSNorm::tPackF2B(otherb.x), PackingSNorm::tPackF2B(otherb.y), PackingSNorm::tPackF2B(otherb.z), PackingSNorm::tPackS2B(othersign)); } explicit SMeshTangents(const Vec3& othert, const Vec3& otherb, const int16& othersign) { Tangent = Vec4sf(PackingSNorm::tPackF2B(othert.x), PackingSNorm::tPackF2B(othert.y), PackingSNorm::tPackF2B(othert.z), PackingSNorm::tPackS2B(othersign)); Bitangent = Vec4sf(PackingSNorm::tPackF2B(otherb.x), PackingSNorm::tPackF2B(otherb.y), PackingSNorm::tPackF2B(otherb.z), PackingSNorm::tPackS2B(othersign)); } void ExportTo(Vec4sf& othert, Vec4sf& otherb) const { othert = Tangent; otherb = Bitangent; } void ExportTo(SPipTangents& other) const { other.Tangent = Tangent; other.Bitangent = Bitangent; } bool operator ==(const SMeshTangents& other) const { return Tangent[0] == other.Tangent[0] || Tangent[1] == other.Tangent[1] || Tangent[2] == other.Tangent[2] || Tangent[3] == other.Tangent[3] || Bitangent[0] == other.Bitangent[0] || Bitangent[1] == other.Bitangent[1] || Bitangent[2] == other.Bitangent[2] || Bitangent[3] == other.Bitangent[3]; } bool operator !=(const SMeshTangents& other) const { return !(*this == other); } bool IsEquivalent(const Vec3& othert, const Vec3& otherb, const int16& othersign, float epsilon = 0.01f) const { // TODO: can be optimized to use only integer arithmetic Vec4 tng, btg; GetTB(tng, btg); Vec3 tng3(tng.x, tng.y, tng.z); Vec3 btg3(btg.x, btg.y, btg.z); return (tng.w == othersign) && (btg.w == othersign) && (tng3.Dot(othert) >= (1.0f - epsilon)) && (btg3.Dot(otherb) >= (1.0f - epsilon)); } void GetTB(Vec4sf& othert, Vec4sf& otherb) const { othert = Tangent; otherb = Bitangent; } void GetTB(Vec4& othert, Vec4& otherb) const { othert = PackingSNorm::tPackB2F(Tangent); otherb = PackingSNorm::tPackB2F(Bitangent); } void GetTB(Vec3& othert, Vec3& otherb) const { const Vec4 t = PackingSNorm::tPackB2F(Tangent); const Vec4 b = PackingSNorm::tPackB2F(Bitangent); othert = Vec3(t.x, t.y, t.z); otherb = Vec3(b.x, b.y, b.z); } ILINE Vec3 GetN() const { Vec4 tng, btg; GetTB(tng, btg); Vec3 tng3(tng.x, tng.y, tng.z); Vec3 btg3(btg.x, btg.y, btg.z); // assumes w 1 or -1 return tng3.Cross(btg3) * tng.w; } void GetN(Vec3& othern) const { othern = GetN(); } void GetTBN(Vec3& othert, Vec3& otherb, Vec3& othern) const { Vec4 tng, btg; GetTB(tng, btg); Vec3 tng3(tng.x, tng.y, tng.z); Vec3 btg3(btg.x, btg.y, btg.z); // assumes w 1 or -1 othert = tng3; otherb = btg3; othern = tng3.Cross(btg3) * tng.w; } ILINE int16 GetR() const { return PackingSNorm::tPackB2S(Tangent.w); } void GetR(int16& sign) const { sign = GetR(); } void RotateBy(const Matrix33& rot) { Vec4 tng, btg; GetTB(tng, btg); Vec3 tng3(tng.x, tng.y, tng.z); Vec3 btg3(btg.x, btg.y, btg.z); tng3 = rot * tng3; btg3 = rot * btg3; *this = SMeshTangents(tng3, btg3, PackingSNorm::tPackB2S(Tangent.w)); } void RotateSafelyBy(const Matrix33& rot) { Vec4 tng, btg; GetTB(tng, btg); Vec3 tng3(tng.x, tng.y, tng.z); Vec3 btg3(btg.x, btg.y, btg.z); tng3 = rot * tng3; btg3 = rot * btg3; // normalize in case "rot" wasn't length-preserving tng3.Normalize(); btg3.Normalize(); *this = SMeshTangents(tng3, btg3, PackingSNorm::tPackB2S(Tangent.w)); } void RotateBy(const Matrix34& trn) { Vec4 tng, btg; GetTB(tng, btg); Vec3 tng3(tng.x, tng.y, tng.z); Vec3 btg3(btg.x, btg.y, btg.z); tng3 = trn.TransformVector(tng3); btg3 = trn.TransformVector(btg3); *this = SMeshTangents(tng3, btg3, PackingSNorm::tPackB2S(Tangent.w)); } void RotateSafelyBy(const Matrix34& trn) { Vec4 tng, btg; GetTB(tng, btg); Vec3 tng3(tng.x, tng.y, tng.z); Vec3 btg3(btg.x, btg.y, btg.z); tng3 = trn.TransformVector(tng3); btg3 = trn.TransformVector(btg3); // normalize in case "rot" wasn't length-preserving tng3.Normalize(); btg3.Normalize(); *this = SMeshTangents(tng3, btg3, PackingSNorm::tPackB2S(Tangent.w)); } void SlerpTowards(const SMeshTangents& other, const SMeshNormal& normal, float pos) { Vec3 tngA, btgA; Vec3 tngB, btgB; this->GetTB(tngA, btgA); other.GetTB(tngB, btgB); // Q: necessary? tngA.Normalize(); tngB.Normalize(); btgA.Normalize(); btgB.Normalize(); tngA.SetSlerp(tngA, tngB, pos); btgA.SetSlerp(btgA, btgB, pos); *this = SMeshTangents(tngA, btgA, normal.GetN()); } AUTO_STRUCT_INFO }; struct SMeshQTangents { SMeshQTangents() {} private: Vec4sf TangentBitangent; public: explicit SMeshQTangents(const SPipQTangents& other) { TangentBitangent = other.QTangent; } explicit SMeshQTangents(const Quat& other) { TangentBitangent.x = PackingSNorm::tPackF2B(other.v.x); TangentBitangent.y = PackingSNorm::tPackF2B(other.v.y); TangentBitangent.z = PackingSNorm::tPackF2B(other.v.z); TangentBitangent.w = PackingSNorm::tPackF2B(other.w); } void ExportTo(SPipQTangents& other) { other.QTangent = TangentBitangent; } ILINE Quat GetQ() const { Quat q; q.v.x = PackingSNorm::tPackB2F(TangentBitangent.x); q.v.y = PackingSNorm::tPackB2F(TangentBitangent.y); q.v.z = PackingSNorm::tPackB2F(TangentBitangent.z); q.w = PackingSNorm::tPackB2F(TangentBitangent.w); return q; } AUTO_STRUCT_INFO }; // Description: // for skinning every vertex has 4 bones and 4 weights. struct SMeshBoneMapping_uint16 { typedef uint16 BoneId; typedef uint8 Weight; BoneId boneIds[4]; Weight weights[4]; AUTO_STRUCT_INFO }; struct SMeshBoneMapping_uint8 { typedef uint8 BoneId; typedef uint8 Weight; BoneId boneIds[4]; Weight weights[4]; AUTO_STRUCT_INFO }; //START: Add LOD support for touch bending vegetation struct SMeshBoneMappingInfo_uint8 { SMeshBoneMappingInfo_uint8(int vertexCount) { //Will be deleted by ~SFoliageInfoCGF() pBoneMapping = new SMeshBoneMapping_uint8[vertexCount]; nVertexCount = vertexCount; } int nVertexCount; struct SMeshBoneMapping_uint8* pBoneMapping; AUTO_STRUCT_INFO }; //END: Add LOD support for touch bending vegetation // Subset of mesh is a continuous range of vertices and indices that share same material. struct SMeshSubset { Vec3 vCenter; float fRadius; float fTexelDensity; int nFirstIndexId; int nNumIndices; int nFirstVertId; int nNumVerts; int nMatID; // Material Sub-object id. int nMatFlags; // Special Material flags. int nPhysicalizeType; // Type of physicalization for this subset. AZ::Vertex::Format vertexFormat; SMeshSubset() : vCenter(0, 0, 0) , fRadius(0) , fTexelDensity(0) , nFirstIndexId(0) , nNumIndices(0) , nFirstVertId(0) , nNumVerts(0) , nMatID(0) , nMatFlags(0) , nPhysicalizeType(PHYS_GEOM_TYPE_DEFAULT) , vertexFormat(eVF_P3S_C4B_T2S) { } void GetMemoryUsage(class ICrySizer* pSizer) const { } // fix numVerts void FixRanges(vtx_idx* pIndices) { int startVertexToMerge = nFirstVertId; int startIndexToMerge = nFirstIndexId; int numIndiciesToMerge = nNumIndices; // find good min and max AGAIN int maxVertexInUse = 0; for (int n = 0; n < numIndiciesToMerge; n++) { int i = (int)pIndices[n + startIndexToMerge]; startVertexToMerge = (i < startVertexToMerge ? i : startVertexToMerge);// min maxVertexInUse = (i > maxVertexInUse ? i : maxVertexInUse);// max } nNumVerts = maxVertexInUse - startVertexToMerge + 1; } }; class CMeshHelpers { public: template static bool ComputeTexMappingAreas( size_t indexCount, const TIndex* pIndices, size_t vertexCount, const TPosition* pPositions, size_t stridePositions, const TTexCoordinates* pTexCoords, size_t strideTexCoords, float& computedPosArea, float& computedTexArea, const char*& errorText) { static const float minPosArea = 10e-6f; static const float minTexArea = 10e-8f; computedPosArea = 0; computedTexArea = 0; errorText = "?"; if (indexCount <= 0) { errorText = "index count is 0"; return false; } if (vertexCount <= 0) { errorText = "vertex count is 0"; return false; } if ((pIndices == NULL) || (pPositions == NULL)) { errorText = "indices and/or positions are NULL"; return false; } if (pTexCoords == NULL) { errorText = "texture coordinates are NULL"; return false; } if (indexCount % 3 != 0) { assert(0); errorText = "bad number of indices"; return false; } // Compute average geometry area of face int count = 0; float posAreaSum = 0; float texAreaSum = 0; for (size_t i = 0; i < indexCount; i += 3) { const size_t index0 = pIndices[i]; const size_t index1 = pIndices[i + 1]; const size_t index2 = pIndices[i + 2]; if ((index0 >= vertexCount) || (index1 >= vertexCount) || (index2 >= vertexCount)) { errorText = "bad vertex index detected"; return false; } const Vec3 pos0 = ToVec3(*(const TPosition*) (((const char*)pPositions) + index0 * stridePositions)); const Vec3 pos1 = ToVec3(*(const TPosition*) (((const char*)pPositions) + index1 * stridePositions)); const Vec3 pos2 = ToVec3(*(const TPosition*) (((const char*)pPositions) + index2 * stridePositions)); const Vec2 tex0 = ToVec2(*(const TTexCoordinates*) (((const char*)pTexCoords) + index0 * strideTexCoords)); const Vec2 tex1 = ToVec2(*(const TTexCoordinates*) (((const char*)pTexCoords) + index1 * strideTexCoords)); const Vec2 tex2 = ToVec2(*(const TTexCoordinates*) (((const char*)pTexCoords) + index2 * strideTexCoords)); const float posArea = ((pos1 - pos0).Cross(pos2 - pos0)).GetLength() * 0.5f; const float texArea = fabsf((tex1 - tex0).Cross(tex2 - tex0)) * 0.5f; if ((posArea >= minPosArea) && (texArea >= minTexArea)) { posAreaSum += posArea; texAreaSum += texArea; ++count; } } if (count == 0 || (posAreaSum < minPosArea) || (texAreaSum < minTexArea)) { errorText = "faces are too small or have stretched mapping"; return false; } computedPosArea = posAreaSum; computedTexArea = texAreaSum; return true; } template static bool CollectFaceAreas(size_t indexCount, const vtx_idx* pIndices, size_t vertexCount, const TPosition* pPositions, size_t stridePositions, std::vector& areas) { static const float minFaceArea = 10e-6f; if (indexCount % 3 != 0) { return false; } areas.reserve(areas.size() + indexCount / 3); for (size_t i = 0; i < indexCount; i += 3) { const uint index0 = pIndices[i]; const uint index1 = pIndices[i + 1]; const uint index2 = pIndices[i + 2]; if ((index0 >= vertexCount) || (index1 >= vertexCount) || (index2 >= vertexCount)) { return false; } const Vec3 pos0 = ToVec3(*(const TPosition*) (((const char*)pPositions) + index0 * stridePositions)); const Vec3 pos1 = ToVec3(*(const TPosition*) (((const char*)pPositions) + index1 * stridePositions)); const Vec3 pos2 = ToVec3(*(const TPosition*) (((const char*)pPositions) + index2 * stridePositions)); const float faceArea = ((pos1 - pos0).Cross(pos2 - pos0)).GetLength() * 0.5f; if (faceArea >= minFaceArea) { areas.push_back(faceArea); } } return true; } private: template inline static Vec3 ToVec3(const T& v) { return v.ToVec3(); } template inline static Vec2 ToVec2(const T& v) { Vec2 uv; v.GetUV(uv); return uv; } }; template <> inline Vec3 CMeshHelpers::ToVec3(const Vec3& v) { return v; } template <> inline Vec2 CMeshHelpers::ToVec2(const Vec2& v) { return v; } template <> inline Vec2 CMeshHelpers::ToVec2(const Vec2f16& v) { return v.ToVec2(); } template <> inline Vec2 CMeshHelpers::ToVec2(const SMeshTexCoord& v) { return v.GetUV(); } ////////////////////////////////////////////////////////////////////////// // Description: // General purpose mesh class. ////////////////////////////////////////////////////////////////////////// class CMesh { public: // e.g. no more than 8 positions, 8 colors, 8 uv sets, etc. const static uint maxStreamsPerType = 8; enum EStream { POSITIONS = 0, POSITIONSF16, NORMALS, FACES, TOPOLOGY_IDS, TEXCOORDS, COLORS, INDICES, TANGENTS, BONEMAPPING, VERT_MATS, QTANGENTS, P3S_C4B_T2S, EXTRABONEMAPPING, // Extra stream. Does not have a stream ID in the CGF. Its data is saved at the end of the BONEMAPPING stream. LAST_STREAM, }; SMeshFace* m_pFaces; // faces are used in mesh processing/compilation int32* m_pTopologyIds; // geometry data vtx_idx* m_pIndices; // indices are used for the final render-mesh Vec3* m_pPositions; Vec3f16* m_pPositionsF16; SMeshNormal* m_pNorms; SMeshTangents* m_pTangents; SMeshQTangents* m_pQTangents; SMeshTexCoord* m_pTexCoord; SMeshColor* m_pColor0; SMeshColor* m_pColor1; int* m_pVertMats; SVF_P3S_C4B_T2S* m_pP3S_C4B_T2S; SMeshBoneMapping_uint16* m_pBoneMapping; //bone-mapping for the final render-mesh SMeshBoneMapping_uint16* m_pExtraBoneMapping; //bone indices and weights for bones 5 to 8. int m_nCoorCount; //number of texture coordinates in m_pTexCoord array int m_streamSize[LAST_STREAM][maxStreamsPerType]; // Bounding box. AABB m_bbox; // Array of mesh subsets. DynArray m_subsets; // Mask that indicate if this stream is using not allocated in Mesh pointer; // ex. if (m_sharedStreamMasks[0] & (1< the 1st normals stream is shared // if (m_sharedStreamMasks[1] & (1< the 2nd uv set stream is shared uint32 m_sharedStreamMasks[maxStreamsPerType]; // Texture space area divided by geometry area. Zero if cannot compute. float m_texMappingDensity; // Geometric mean value calculated from the areas of this mesh faces. float m_geometricMeanFaceArea; ////////////////////////////////////////////////////////////////////////// void GetMemoryUsage(class ICrySizer* pSizer) const { pSizer->AddObject(this, sizeof(*this)); pSizer->AddObject(m_subsets); for (int streamType = 0; streamType < LAST_STREAM; streamType++) { for (int streamIndex = 0; streamIndex < GetNumberOfStreamsByType(streamType); ++streamIndex) { void* pStream; int nElementSize = 0; GetStreamInfo(streamType, streamIndex, pStream, nElementSize); pSizer->AddObject(pStream, m_streamSize[streamType][streamIndex] * nElementSize); } } } ////////////////////////////////////////////////////////////////////////// CMesh() { m_pFaces = NULL; m_pTopologyIds = NULL; m_pIndices = NULL; m_pPositions = NULL; m_pPositionsF16 = NULL; m_pNorms = NULL; m_pTangents = NULL; m_pQTangents = NULL; m_pTexCoord = NULL; m_pColor0 = NULL; m_pColor1 = NULL; m_pVertMats = NULL; m_pP3S_C4B_T2S = NULL; m_pBoneMapping = NULL; m_pExtraBoneMapping = NULL; m_nCoorCount = 0; memset(m_texCoords, 0, sizeof(m_texCoords)); memset(m_streamSize, 0, sizeof(m_streamSize)); m_bbox.Reset(); memset(m_sharedStreamMasks, 0, sizeof(m_sharedStreamMasks)); m_texMappingDensity = 0.0f; m_geometricMeanFaceArea = 0.0f; } virtual ~CMesh() { FreeStreams(); } void FreeStreams() { for (int streamType = 0; streamType < LAST_STREAM; ++streamType) { for (int streamIndex = 0; streamIndex < GetNumberOfStreamsByType(streamType); ++streamIndex) { ReallocStream(streamType, streamIndex, 0); } } } int GetFaceCount() const { return m_streamSize[FACES][0]; } int GetVertexCount() const { return max(max(m_streamSize[POSITIONS][0], m_streamSize[POSITIONSF16][0]), m_streamSize[P3S_C4B_T2S][0]); } int GetTexCoordCount() const { return m_nCoorCount; } int GetTangentCount() const { return m_streamSize[TANGENTS][0]; } int GetSubSetCount() const { return m_subsets.size(); } int GetIndexCount() const { return m_streamSize[INDICES][0]; } void SetFaceCount(int nNewCount) { ReallocStream(FACES, 0, nNewCount); } void SetVertexCount(int nNewCount) { if (GetVertexCount() != nNewCount || GetVertexCount() == 0) { ReallocStream(POSITIONS, 0, nNewCount); ReallocStream(POSITIONSF16, 0, 0); ReallocStream(NORMALS, 0, nNewCount); if (m_pColor0) { ReallocStream(COLORS, 0, nNewCount); } if (m_pColor1) { ReallocStream(COLORS, 1, nNewCount); } if (m_pVertMats) { ReallocStream(VERT_MATS, 0, nNewCount); } } } void SetTexCoordsCount(int nNewCount) { if (m_nCoorCount != nNewCount || m_nCoorCount == 0) { ReallocStream(TEXCOORDS, 0, nNewCount); m_nCoorCount = nNewCount; } } void SetTexCoordsAndTangentsCount(int nNewCount) { if (m_nCoorCount != nNewCount || m_nCoorCount == 0) { ReallocStream(TEXCOORDS, 0, nNewCount); ReallocStream(TANGENTS, 0, nNewCount); m_nCoorCount = nNewCount; } } void SetIndexCount(int nNewCount) { ReallocStream(INDICES, 0, nNewCount); } // Once m_pTexCoords, m_pColors, etc. are wrapped in vectors, return the size of the vector // or if we go with fixed size arrays, with a bunch of nullptrs, maybe just return the fixed size or the number of non-null ptrs int GetNumberOfStreamsByType(int streamType) const { if (streamType == COLORS || streamType == TEXCOORDS) { return 2; } return 1; } bool Has32BitPositions(void) const { return m_pPositions != nullptr; } bool Has16BitPositions(void) const { return m_pPositionsF16 != nullptr; } bool IsUVSetEmptyForSubmesh(uint submeshIndex, uint uvSet) { // Get a pointer to the uv set SMeshTexCoord* texCoords = nullptr; if (uvSet == 0) { texCoords = m_pTexCoord; } else { texCoords = m_texCoords[uvSet]; } if (texCoords) { // Iterate through the vertices for the submesh Vec2 emptyTexCoord = Vec2(0.0f, 0.0f); for (int i = m_subsets[submeshIndex].nFirstVertId; i < m_subsets[submeshIndex].nFirstVertId + m_subsets[submeshIndex].nNumVerts; ++i) { // If any of the texture coordinates for the given uv set are non-zero, return false. if (texCoords[i].GetUV() != emptyTexCoord) { return false; } } } // If no valid texture coordinates are found for submesh for the given uv set, return true. return true; } void SetSubmeshVertexFormats(void) { EVertexFormat desiredFormat = eVF_Unknown; for (int submeshIndex = 0; submeshIndex < m_subsets.size(); ++submeshIndex) { // Choose float or short based on the precision of the positions if (this->Has32BitPositions()) { // Choose one or two uv sets if (IsUVSetEmptyForSubmesh(submeshIndex, 1)) { desiredFormat = eVF_P3F_C4B_T2F; } else { desiredFormat = eVF_P3F_C4B_T2F_T2F; } } else if (this->Has16BitPositions()) { // Choose one or two uv sets if (IsUVSetEmptyForSubmesh(submeshIndex, 1)) { desiredFormat = eVF_P3S_C4B_T2S; } else { desiredFormat = eVF_P3S_C4B_T2S_T2S; } } else { AZ_Assert(false, "Submesh does not contain positions"); } // Set the vertex format for the submesh m_subsets[submeshIndex].vertexFormat = AZ::Vertex::Format(desiredFormat); } } AZ::Vertex::Format GetVertexFormatForSubmesh(int submeshIndex) const { assert(submeshIndex < m_subsets.size()); return m_subsets[submeshIndex].vertexFormat; } AZ::Vertex::Format GetMeshGroupVertexFormat() const { AZ::Vertex::Format meshGroupFormat; for (int submeshIndex = 0; submeshIndex < m_subsets.size(); ++submeshIndex) { if (m_subsets[submeshIndex].vertexFormat > meshGroupFormat) { meshGroupFormat = m_subsets[submeshIndex].vertexFormat; } } return meshGroupFormat; } // Set specific stream type as shared. If there are multiple streams for a given type (such as multiple uv sets), then all streams of that type will be marked as shared void SetSharedStream(int streamType, int streamIndex, void* pStream, int nElementCount) { AZ_Assert(streamType >= 0 && streamType < LAST_STREAM && streamIndex < maxStreamsPerType, "Stream type %d outside of allowable range (%d to %d) of CMesh::EStream, or stream index %d exceeds the maximum number of vertex streams (%d) per type.", streamType, 0, CMesh::LAST_STREAM, streamIndex, maxStreamsPerType); if ((m_sharedStreamMasks[streamIndex] & (1 << streamType)) == 0) { ReallocStream(streamType, streamIndex, 0); m_sharedStreamMasks[streamIndex] |= (1 << streamType); } SetStreamData(streamType, streamIndex, pStream, nElementCount); } template T* GetStreamPtrAndElementCount(int streamType, int streamIndex, int* pElementCount = 0) const { void* pStream = 0; int nElementSize = 0; GetStreamInfo(streamType, streamIndex, pStream, nElementSize); if (nElementSize != sizeof(T)) { AZ_Assert(false, "The element size %d returned by GetStreamInfo does not match the size %d of type T", nElementSize, sizeof(T)); pStream = 0; } const int nElementCount = (pStream ? this->m_streamSize[streamType][streamIndex] : 0); if (pElementCount) { *pElementCount = nElementCount; } return (T*)pStream; } template T* GetStreamPtr(int streamType, int streamIndex = 0) const { void* pStream = 0; int nElementSize = 0; GetStreamInfo(streamType, streamIndex, pStream, nElementSize); if (nElementSize != sizeof(T)) { AZ_Assert(false, "The element size %d returned by GetStreamInfo does not match the size %d of type T", nElementSize, sizeof(T)); pStream = 0; } return (T*)pStream; } void GetStreamInfo(int streamType, int streamIndex, void*& pStream, int& nElementSize) const { pStream = 0; nElementSize = 0; AZ_Assert(streamType >= 0 && streamType < LAST_STREAM && streamIndex < maxStreamsPerType, "Stream type %d outside of allowable range (%d to %d) of CMesh::EStream, or stream index %d exceeds the maximum number of vertex streams (%d) per type.", streamType, 0, CMesh::LAST_STREAM, streamIndex, maxStreamsPerType); switch (streamType) { case POSITIONS: pStream = m_pPositions; nElementSize = sizeof(Vec3); break; case POSITIONSF16: pStream = m_pPositionsF16; nElementSize = sizeof(Vec3f16); break; case NORMALS: pStream = m_pNorms; nElementSize = sizeof(Vec3); break; case VERT_MATS: pStream = m_pVertMats; nElementSize = sizeof(int); break; case FACES: pStream = m_pFaces; nElementSize = sizeof(SMeshFace); break; case TOPOLOGY_IDS: pStream = m_pTopologyIds; nElementSize = sizeof(int32); break; case TEXCOORDS: if (streamIndex == 0) { pStream = m_pTexCoord; } else { pStream = m_texCoords[streamIndex]; } nElementSize = sizeof(SMeshTexCoord); break; case COLORS: if (streamIndex == 0) { pStream = m_pColor0; } else { pStream = m_pColor1; } nElementSize = sizeof(SMeshColor); break; case INDICES: pStream = m_pIndices; nElementSize = sizeof(vtx_idx); break; case TANGENTS: pStream = m_pTangents; nElementSize = sizeof(SMeshTangents); break; case QTANGENTS: pStream = m_pQTangents; nElementSize = sizeof(SMeshQTangents); break; case BONEMAPPING: pStream = m_pBoneMapping; nElementSize = sizeof(SMeshBoneMapping_uint16); break; case EXTRABONEMAPPING: pStream = m_pExtraBoneMapping; nElementSize = sizeof(SMeshBoneMapping_uint16); break; case P3S_C4B_T2S: pStream = m_pP3S_C4B_T2S; nElementSize = sizeof(SVF_P3S_C4B_T2S); break; default: AZ_Assert(false, "Unknown stream"); break; } } virtual void ReallocStream(int streamType, int streamIndex, int nNewCount) { if (streamType < 0 || streamType >= LAST_STREAM || streamIndex >= maxStreamsPerType) { AZ_Assert(false, "Stream type %d outside of allowable range (%d to %d) of CMesh::EStream, or stream index %d exceeds the maximum number of vertex streams (%d) per type.", streamType, 0, CMesh::LAST_STREAM, streamIndex, maxStreamsPerType); return; } if (m_sharedStreamMasks[streamIndex] & (1 << streamType)) { m_sharedStreamMasks[streamIndex] &= ~(1 << streamType); if (nNewCount <= 0) { SetStreamData(streamType, 0, nullptr, 0); } else { const int nOldCount = m_streamSize[streamType][streamIndex]; void* pOldElements = 0; int nElementSize = 0; GetStreamInfo(streamType, streamIndex, pOldElements, nElementSize); void* const pNewElements = realloc(0, nNewCount * nElementSize); if (!pNewElements) { AZ_Assert(false, "Allocation failed"); SetStreamData(streamType, streamIndex, nullptr, 0); return; } if (nOldCount > 0) { memcpy(pNewElements, pOldElements, min(nOldCount, nNewCount) * nElementSize); } if (nNewCount > nOldCount) { memset((char*)pNewElements + nOldCount * nElementSize, 0, (nNewCount - nOldCount) * nElementSize); } SetStreamData(streamType, streamIndex, pNewElements, nNewCount); } } else { const int nOldCount = m_streamSize[streamType][streamIndex]; if (nOldCount == nNewCount) { // stream already has required size return; } void* pOldElements = 0; int nElementSize = 0; GetStreamInfo(streamType, streamIndex, pOldElements, nElementSize); if (nNewCount <= 0) { free(pOldElements); SetStreamData(streamType, streamIndex, nullptr, 0); } else { void* const pNewElements = realloc(pOldElements, nNewCount * nElementSize); if (!pNewElements) { AZ_Assert(false, "Allocation failed"); free(pOldElements); SetStreamData(streamType, streamIndex, nullptr, 0); return; } if (nNewCount > nOldCount) { memset((char*)pNewElements + nOldCount * nElementSize, 0, (nNewCount - nOldCount) * nElementSize); } SetStreamData(streamType, streamIndex, pNewElements, nNewCount); } } } // Copy mesh from source mesh. void Copy(const CMesh& mesh) { for (int streamType = 0; streamType < LAST_STREAM; streamType++) { for (int streamIndex = 0; streamIndex < GetNumberOfStreamsByType(streamType); ++streamIndex) { ReallocStream(streamType, streamIndex, mesh.m_streamSize[streamType][streamIndex]); if (mesh.m_streamSize[streamType][streamIndex] > 0) { void* pSrcStream = 0; void* pTrgStream = 0; int nElementSize = 0; mesh.GetStreamInfo(streamType, streamIndex, pSrcStream, nElementSize); GetStreamInfo(streamType, streamIndex, pTrgStream, nElementSize); if (pSrcStream && pTrgStream) { memcpy(pTrgStream, pSrcStream, m_streamSize[streamType][streamIndex] * nElementSize); } } } } m_bbox = mesh.m_bbox; m_subsets = mesh.m_subsets; m_texMappingDensity = mesh.m_texMappingDensity; m_geometricMeanFaceArea = mesh.m_geometricMeanFaceArea; } bool CompareStreams(const CMesh& mesh) const { for (int streamType = 0; streamType < LAST_STREAM; streamType++) { for (int streamIndex = 0; streamIndex < GetNumberOfStreamsByType(streamType); ++streamIndex) { if (m_streamSize[streamType][streamIndex] != mesh.m_streamSize[streamType][streamIndex]) { return false; } if (m_streamSize[streamType][streamIndex]) { void* pStream1 = 0; void* pStream2 = 0; int nElementSize1 = 0; int nElementSize2 = 0; GetStreamInfo(streamType, streamIndex, pStream1, nElementSize1); mesh.GetStreamInfo(streamType, streamIndex, pStream2, nElementSize2); assert(nElementSize1 == nElementSize2); if ((pStream1 && !pStream2) || (!pStream1 && pStream2)) { return false; } if (pStream1 && pStream2) { if (memcmp(pStream1, pStream2, m_streamSize[streamType][streamIndex] * nElementSize1) != 0) { return false; } } } } } return true; } // Add streams from source mesh to the end of existing streams. const char* Append(const CMesh& mesh) { return Append(mesh, 0, -1, 0, -1); } // Add streams from source mesh to the end of existing streams. const char* Append(const CMesh& mesh, int fromVertex, int vertexCount, int fromFace, int faceCount) { if (GetIndexCount() > 0 || mesh.GetIndexCount() > 0) { assert(0); return "Cmesh::Append() cannot handle meshes with indices, it can handle faces only"; } // Non-ranged requests should start from 0th element and element count should be <0. if ((vertexCount < 0 && fromVertex != 0) || (faceCount < 0 && fromFace != 0)) { assert(0); return "Cmesh::Append(): Bad CMesh parameters"; } if (vertexCount < 0) { vertexCount = mesh.GetVertexCount(); } if (faceCount < 0) { faceCount = mesh.GetFaceCount(); } const int oldVertexCount = GetVertexCount(); const int oldFaceCount = GetFaceCount(); const int nOldCoorCount = GetTexCoordCount(); if (GetTexCoordCount() != 0 && GetTexCoordCount() != oldVertexCount) { assert(0); return "Cmesh::Append(): Mismatch in target CMesh vert/tcoord counts"; } if (mesh.GetTexCoordCount() != 0 && mesh.GetTexCoordCount() != mesh.GetVertexCount()) { assert(0); return "Cmesh::Append(): Mismatch in source CMesh vert/tcoord counts"; } for (int streamType = 0; streamType < LAST_STREAM; ++streamType) { for (int streamIndex = 0; streamIndex < GetNumberOfStreamsByType(streamType); ++streamIndex) { const int oldCount = (streamType == FACES) ? oldFaceCount : oldVertexCount; const int from = (streamType == FACES) ? fromFace : fromVertex; const int count = (streamType == FACES) ? faceCount : vertexCount; const int oldStreamSize = m_streamSize[streamType][streamIndex]; const int streamSize = mesh.m_streamSize[streamType][streamIndex]; if (oldStreamSize <= 0 && (count <= 0 || streamSize <= 0)) { continue; } ReallocStream(streamType, streamIndex, oldCount + count); if (count > 0) { void* pSrcStream = 0; void* pTrgStream = 0; int srcElementSize = 0; int trgElementSize = 0; mesh.GetStreamInfo(streamType, streamIndex, pSrcStream, srcElementSize); GetStreamInfo(streamType, streamIndex, pTrgStream, trgElementSize); assert(srcElementSize == trgElementSize); if (pSrcStream && pTrgStream) { memcpy( (char*)pTrgStream + oldCount * trgElementSize, (char*)pSrcStream + from * srcElementSize, count * srcElementSize); } } } } { const int nOffset = oldVertexCount - fromVertex; const int newFaceCount = GetFaceCount(); for (int i = oldFaceCount; i < newFaceCount; ++i) { m_pFaces[i].v[0] += nOffset; m_pFaces[i].v[1] += nOffset; m_pFaces[i].v[2] += nOffset; } } m_bbox.Add(mesh.m_bbox.min); m_bbox.Add(mesh.m_bbox.max); return 0; } void RemoveRangeFromStream(int streamType, int streamIndex, int nFirst, int nCount) { if (streamType < 0 || streamType >= LAST_STREAM || streamIndex >= maxStreamsPerType) { AZ_Assert(false, "Stream type %d outside of allowable range (%d to %d) of CMesh::EStream, or stream index %d exceeds the maximum number of vertex streams (%d) per type.", streamType, 0, CMesh::LAST_STREAM, streamIndex, maxStreamsPerType); return; } if (m_sharedStreamMasks[streamIndex] & (1 << streamType)) { // Make shared stream non-shared ReallocStream(streamType, streamIndex, m_streamSize[streamType][streamIndex]); } const int nTotalCount = m_streamSize[streamType][streamIndex]; int nElementSize; void* pStream = 0; GetStreamInfo(streamType, streamIndex, pStream, nElementSize); if (nFirst >= nTotalCount || nTotalCount <= 0 || pStream == 0) { return; } if (nFirst + nCount > nTotalCount) { nCount = nTotalCount - nFirst; } if (nCount <= 0) { return; } const int nTailCount = nTotalCount - (nFirst + nCount); if (nTailCount > 0) { char* const pRangeStart = (char*)pStream + nFirst * nElementSize; char* const pRangeEnd = (char*)pStream + (nFirst + nCount) * nElementSize; memmove(pRangeStart, pRangeEnd, nTailCount * nElementSize); } ReallocStream(streamType, streamIndex, nTotalCount - nCount); } bool Validate(const char** const ppErrorDescription) const { const int vertexCount = GetVertexCount(); const int faceCount = GetFaceCount(); const int indexCount = GetIndexCount(); if ((faceCount <= 0) && (indexCount <= 0)) { if (vertexCount > 0) { if (ppErrorDescription) { ppErrorDescription[0] = "no any indices, but vertices exist"; } return false; } } if ((uint)vertexCount > (sizeof(vtx_idx) == 2 ? 0xffff : 0x7fffffff)) { if (ppErrorDescription) { ppErrorDescription[0] = (sizeof(vtx_idx) == 2) ? "vertex count is greater or equal than 64K" : "vertex count is greater or equal than 2G"; } return false; } if (faceCount > 0) { if (vertexCount <= 0) { if (ppErrorDescription) { ppErrorDescription[0] = "no any vertices, but faces exist"; } return false; } } if (indexCount > 0) { if (vertexCount <= 0) { if (ppErrorDescription) { ppErrorDescription[0] = "no any vertices, but indices exist"; } return false; } } for (int i = 0; i < faceCount; ++i) { const SMeshFace& face = m_pFaces[i]; for (int j = 0; j < 3; ++j) { const int v = face.v[j]; if ((v < 0) || (v >= vertexCount)) { if (ppErrorDescription) { ppErrorDescription[0] = "a face refers vertex outside of vertex array"; } return false; } } } for (int i = 0; i < indexCount; i++) { if ((uint)m_pIndices[i] >= (uint)vertexCount) { if (ppErrorDescription) { ppErrorDescription[0] = "an index refers vertex outside of vertex array"; } return false; } } if (GetTexCoordCount() != 0 && GetTexCoordCount() != vertexCount) { if (ppErrorDescription) { ppErrorDescription[0] = "number of texture coordinates is different from number of vertices"; } return false; } if (!_finite(m_bbox.min.x) || !_finite(m_bbox.min.y) || !_finite(m_bbox.min.z) || !_finite(m_bbox.max.x) || !_finite(m_bbox.max.y) || !_finite(m_bbox.max.z)) { if (ppErrorDescription) { ppErrorDescription[0] = "bounding box contains damaged data"; } return false; } if (m_bbox.IsReset()) { if (ppErrorDescription) { ppErrorDescription[0] = "bounding box is not set"; } return false; } if (m_bbox.max.x < m_bbox.min.x || m_bbox.max.y < m_bbox.min.y || m_bbox.max.z < m_bbox.min.z) { if (ppErrorDescription) { ppErrorDescription[0] = "bounding box min is greater than max"; } return false; } if (m_bbox.min.GetDistance(m_bbox.max) < 0.001f) { if (ppErrorDescription) { ppErrorDescription[0] = "bounding box is less than 1 mm in size"; } return false; } for (int s = 0, subsetCount = m_subsets.size(); s < subsetCount; ++s) { const SMeshSubset& subset = m_subsets[s]; if (subset.nNumIndices <= 0) { if (subset.nNumVerts > 0) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset without indices contains vertices"; } return false; } continue; } else if (subset.nNumVerts <= 0) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset has indices but vertices are missing"; } return false; } if (subset.nFirstIndexId < 0) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset has negative start position in index array"; } return false; } if (subset.nFirstIndexId + subset.nNumIndices > indexCount) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset refers indices outside of index array"; } return false; } if (subset.nFirstVertId < 0) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset has negative start position in vertex array"; } return false; } if (subset.nFirstVertId + subset.nNumVerts > vertexCount) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset refers vertices outside of vertex array"; } return false; } for (int ii = subset.nFirstIndexId; ii < subset.nFirstIndexId + subset.nNumIndices; ++ii) { const uint index = m_pIndices[ii]; if (index < (uint)subset.nFirstVertId) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset refers a vertex lying before subset vertices"; } return false; } if (index >= (uint)(subset.nFirstVertId + subset.nNumVerts)) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset refers a vertex lying after subset vertices"; } return false; } Vec3 p(ZERO); const Vec3* pp = &p; if (m_pPositions) { pp = &m_pPositions[index]; } else if (m_pPositionsF16) { p = m_pPositionsF16[index].ToVec3(); } else if (m_pP3S_C4B_T2S) { p = m_pP3S_C4B_T2S[index].xyz.ToVec3(); } if (!_finite(pp->x)) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset contains a vertex with damaged x component"; } return false; } if (!_finite(pp->y)) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset contains a vertex with damaged y component"; } return false; } if (!_finite(pp->z)) { if (ppErrorDescription) { ppErrorDescription[0] = "a mesh subset contains a vertex with damaged z component"; } return false; } } } return true; } bool ComputeSubsetTexMappingAreas( size_t subsetIndex, float& computedPosArea, float& computedTexArea, const char*& errorText) { computedPosArea = 0.0f; computedTexArea = 0.0f; errorText = ""; if (subsetIndex >= (size_t)m_subsets.size()) { errorText = "subset index is bad"; return false; } if (GetIndexCount() <= 0) { errorText = "missing indices"; return false; } if ((GetVertexCount() <= 0) || ((m_pPositions == NULL) && (m_pPositionsF16 == NULL))) { errorText = "missing vertices"; return false; } if ((m_pTexCoord == NULL) || (GetTexCoordCount() <= 0)) { errorText = "missing texture coordinates"; return false; } const SMeshSubset& subset = m_subsets[subsetIndex]; if ((subset.nNumIndices <= 0) || (subset.nFirstIndexId < 0)) { errorText = "missing or bad indices in subset"; return false; } bool ok; if (m_pPositions) { ok = CMeshHelpers::ComputeTexMappingAreas( subset.nNumIndices, &m_pIndices[subset.nFirstIndexId], GetVertexCount(), &m_pPositions[0], sizeof(m_pPositions[0]), &m_pTexCoord[0], sizeof(m_pTexCoord[0]), computedPosArea, computedTexArea, errorText); } else { ok = CMeshHelpers::ComputeTexMappingAreas( subset.nNumIndices, &m_pIndices[subset.nFirstIndexId], GetVertexCount(), &m_pPositionsF16[0], sizeof(m_pPositionsF16[0]), &m_pTexCoord[0], sizeof(m_pTexCoord[0]), computedPosArea, computedTexArea, errorText); } return ok; } // note: this function doesn't work for "old" uncompressed meshes (with faces instead of indices) bool RecomputeTexMappingDensity() { m_texMappingDensity = 0; if (GetFaceCount() > 0) { // uncompressed mesh - not supported return false; } if ((GetIndexCount() <= 0) || (GetVertexCount() <= 0) || ((m_pPositions == NULL) && (m_pPositionsF16 == NULL))) { return false; } if ((m_pTexCoord == NULL) || (GetTexCoordCount() <= 0)) { return false; } float totalPosArea = 0; float totalTexArea = 0; for (size_t i = 0, count = m_subsets.size(); i < count; ++i) { const SMeshSubset& subset = m_subsets[i]; float posArea; float texArea; const char* errorText = ""; const bool ok = ComputeSubsetTexMappingAreas(i, posArea, texArea, errorText); if (ok) { totalPosArea += posArea; totalTexArea += texArea; } } if (totalPosArea <= 0) { return false; } m_texMappingDensity = totalTexArea / totalPosArea; return true; } bool RecomputeGeometricMeanFaceArea() { m_geometricMeanFaceArea = 0.0f; if (GetFaceCount() > 0) { // uncompressed mesh - not supported return false; } if ((GetIndexCount() <= 0) || (GetVertexCount() <= 0) || ((m_pPositions == NULL) && (m_pPositionsF16 == NULL))) { return false; } std::vector areas; const size_t subsetCount = m_subsets.size(); for (size_t i = 0; i < subsetCount; ++i) { CollectSubsetFaceAreas(m_subsets[i], areas); } const size_t areasCount = areas.size(); if (areasCount == 0) { return false; } float fGeometricTotal = 0.0f; for (size_t i = 0; i < areasCount; ++i) { fGeometricTotal += logf(areas[i]); } m_geometricMeanFaceArea = expf(fGeometricTotal / areasCount); assert(m_geometricMeanFaceArea > 0.0f); return true; } bool CollectSubsetFaceAreas(const SMeshSubset& subset, std::vector& areas) { if ((subset.nNumIndices <= 0) || (subset.nFirstIndexId < 0)) { return false; } bool ok = false; if (m_pPositions != NULL) { ok = CMeshHelpers::CollectFaceAreas(subset.nNumIndices, &m_pIndices[subset.nFirstIndexId], GetVertexCount(), &m_pPositions[0], sizeof(m_pPositions[0]), areas); } else if (m_pPositionsF16 != NULL) { ok = CMeshHelpers::CollectFaceAreas(subset.nNumIndices, &m_pIndices[subset.nFirstIndexId], GetVertexCount(), &m_pPositionsF16[0], sizeof(m_pPositionsF16[0]), areas); } return ok; } ////////////////////////////////////////////////////////////////////////// // Estimates the size of the render mesh. uint32 EstimateRenderMeshMemoryUsage() const { const size_t cSizeStream[VSF_NUM] = { 0U, sizeof(SPipTangents), // VSF_TANGENTS sizeof(SPipQTangents), // VSF_QTANGENTS sizeof(SVF_W4B_I4S), // VSF_HWSKIN_INFO sizeof(SVF_P3F), // VSF_VERTEX_VELOCITY #if ENABLE_NORMALSTREAM_SUPPORT sizeof(SPipNormal), // VSF_NORMALS #endif }; uint32 nMeshSize = 0; uint32 activeStreams = (GetVertexCount()) ? 1U << VSF_GENERAL : 0U; activeStreams |= (m_pQTangents) ? (1U << VSF_QTANGENTS) : (m_pTangents) ? (1U << VSF_TANGENTS) : 0U; if (m_pBoneMapping) { activeStreams |= 1U << VSF_HWSKIN_INFO; } for (uint32 i = 0; i < VSF_NUM; i++) { if (activeStreams & (1U << i)) { nMeshSize += ((i == VSF_GENERAL) ? sizeof(SVF_P3S_C4B_T2S) : cSizeStream[i]) * GetVertexCount(); nMeshSize += TARGET_DEFAULT_ALIGN - (nMeshSize & (TARGET_DEFAULT_ALIGN - 1)); } } if (GetIndexCount()) { nMeshSize += GetIndexCount() * sizeof(vtx_idx); nMeshSize += TARGET_DEFAULT_ALIGN - (nMeshSize & (TARGET_DEFAULT_ALIGN - 1)); } return nMeshSize; } ////////////////////////////////////////////////////////////////////////// // This function used when we do not have an actual mesh, but only vertex/index count of it. static uint32 ApproximateRenderMeshMemoryUsage(int nVertexCount, int nIndexCount) { uint32 nMeshSize = 0; nMeshSize += nVertexCount * sizeof(SVF_P3S_C4B_T2S); nMeshSize += nVertexCount * sizeof(SPipTangents); nMeshSize += nIndexCount * sizeof(vtx_idx); return nMeshSize; } private: // Set stream size. void SetStreamData(int streamType, int streamIndex, void* pStream, int nNewCount) { if (streamType < 0 || streamType >= LAST_STREAM || streamIndex >= maxStreamsPerType) { AZ_Assert(false, "Stream type %d outside of allowable range (%d to %d) of CMesh::EStream, or stream index %d exceeds the maximum number of vertex streams (%d) per type.", streamType, 0, CMesh::LAST_STREAM, streamIndex, maxStreamsPerType); return; } m_streamSize[streamType][streamIndex] = nNewCount; switch (streamType) { case POSITIONS: m_pPositions = (Vec3*)pStream; break; case POSITIONSF16: m_pPositionsF16 = (Vec3f16*)pStream; break; case NORMALS: m_pNorms = (SMeshNormal*)pStream; break; case VERT_MATS: m_pVertMats = (int*)pStream; break; case FACES: m_pFaces = (SMeshFace*)pStream; break; case TOPOLOGY_IDS: m_pTopologyIds = (int32*)pStream; break; case TEXCOORDS: if (streamIndex == 0) { m_pTexCoord = (SMeshTexCoord*)pStream; } else { m_texCoords[streamIndex] = (SMeshTexCoord*)pStream; } m_nCoorCount = nNewCount; break; case COLORS: if (streamIndex == 0) { m_pColor0 = (SMeshColor*)pStream; } else { m_pColor1 = (SMeshColor*)pStream; } break; case INDICES: m_pIndices = (vtx_idx*)pStream; break; case TANGENTS: m_pTangents = (SMeshTangents*)pStream; break; case QTANGENTS: m_pQTangents = (SMeshQTangents*)pStream; break; case BONEMAPPING: m_pBoneMapping = (SMeshBoneMapping_uint16*)pStream; break; case EXTRABONEMAPPING: m_pExtraBoneMapping = (SMeshBoneMapping_uint16*)pStream; break; case P3S_C4B_T2S: m_pP3S_C4B_T2S = (SVF_P3S_C4B_T2S*)pStream; m_nCoorCount = nNewCount; break; default: AZ_Assert(false, "Unknown stream"); break; } } SMeshTexCoord* m_texCoords[maxStreamsPerType]; }; // Description: // Editable mesh interface. // IndexedMesh can be created directly or loaded from CGF file, before rendering it is converted into IRenderMesh. // IStatObj is used to host IIndexedMesh, and corresponding IRenderMesh. struct IIndexedMesh { /*! Structure used for read-only access to mesh data. Used by GetMesh() function */ struct SMeshDescription { const SMeshFace* m_pFaces; // pointer to array of faces const Vec3* m_pVerts; // pointer to array of vertices in f32 format const Vec3f16* m_pVertsF16; // pointer to array of vertices in f16 format const SMeshNormal* m_pNorms; // pointer to array of normals const SMeshColor* m_pColor; // pointer to array of vertex colors const SMeshTexCoord* m_pTexCoord; // pointer to array of texture coordinates const vtx_idx* m_pIndices; // pointer to array of indices int m_nFaceCount; // number of elements m_pFaces array int m_nVertCount; // number of elements in m_pVerts, m_pNorms and m_pColor arrays int m_nCoorCount; // number of elements in m_pTexCoord array int m_nIndexCount; // number of elements in m_pIndices array }; // virtual ~IIndexedMesh() {} // Release indexed mesh. virtual void Release() = 0; //! Gives read-only access to mesh data virtual void GetMeshDescription(SMeshDescription& meshDesc) const = 0; virtual CMesh* GetMesh() = 0; virtual void SetMesh(CMesh& mesh) = 0; /*! Frees vertex and face streams. Calling this function invalidates SMeshDescription pointers */ virtual void FreeStreams() = 0; //! Return number of allocated faces virtual int GetFaceCount() const = 0; /*! Reallocates faces. Calling this function invalidates SMeshDescription pointers */ virtual void SetFaceCount(int nNewCount) = 0; //! Return number of allocated vertices, normals and colors virtual int GetVertexCount() const = 0; /*! Reallocates vertices, normals and colors. Calling this function invalidates SMeshDescription pointers */ virtual void SetVertexCount(int nNewCount) = 0; /*! Reallocates colors. Calling this function invalidates SMeshDescription pointers */ virtual void SetColorCount(int nNewCount) = 0; //! Return number of allocated texture coordinates virtual int GetTexCoordCount() const = 0; /*! Reallocates texture coordinates. Calling this function invalidates SMeshDescription pointers */ virtual void SetTexCoordCount(int nNewCount, int numStreams = 1) = 0; //! Return number of allocated tangents. virtual int GetTangentCount() const = 0; /*! Reallocates tangents. Calling this function invalidates SMeshDescription pointers */ virtual void SetTangentCount(int nNewCount) = 0; // Get number of indices in the mesh. virtual int GetIndexCount() const = 0; // Set number of indices in the mesh. virtual void SetIndexCount(int nNewCount) = 0; // Allocates m_pBoneMapping in CMesh virtual void AllocateBoneMapping() = 0; ////////////////////////////////////////////////////////////////////////// // Subset access. ////////////////////////////////////////////////////////////////////////// virtual int GetSubSetCount() const = 0; virtual void SetSubSetCount(int nSubsets) = 0; virtual const SMeshSubset& GetSubSet(int nIndex) const = 0; virtual void SetSubsetBounds(int nIndex, const Vec3& vCenter, float fRadius) = 0; virtual void SetSubsetIndexVertexRanges(int nIndex, int nFirstIndexId, int nNumIndices, int nFirstVertId, int nNumVerts) = 0; virtual void SetSubsetMaterialId(int nIndex, int nMatID) = 0; virtual void SetSubsetMaterialProperties(int nIndex, int nMatFlags, int nPhysicalizeType, const AZ::Vertex::Format& vertexFormat) = 0; ////////////////////////////////////////////////////////////////////////// // Mesh bounding box. ////////////////////////////////////////////////////////////////////////// virtual void SetBBox(const AABB& box) = 0; virtual AABB GetBBox() const = 0; virtual void CalcBBox() = 0; virtual void RestoreFacesFromIndices() = 0; // #if defined(WIN32) || defined(WIN64) // Optimizes mesh virtual void Optimize(const char* szComment = NULL) = 0; #endif virtual void Build() {} }; #endif // CRYINCLUDE_CRYCOMMON_IINDEXEDMESH_H