/* * 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. #include "StdAfx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "MergedMeshRenderNode.h" #include "MergedMeshGeometry.h" #include "DeformableNode.h" #define FSL_CREATE_MODE FSL_VIDEO_CREATE #include #if !ENABLE_CRY_PHYSICS #include #endif namespace MergedMesh { static constexpr int s_defaultGroupId = 0; } namespace MergedMeshDebug { // flags for e_MergedMeshesDebug enum class DisplayFlags { StressInfo = (1 << 1), // sector stress information PrintState = (1 << 2), // debug info (position, state, size, visibility) InstanceLines = (1 << 3), // draw instances as lines InstanceAABBs = (1 << 4), // draw instances AABBs SpinesFilter = (1 << 5), // draw spines filter CalculatedWind = (1 << 6), // Show the calculated wind InfluencingColliders = (1 << 8), // Draw colliders of objects influencing the merged meshes SpinesAsLines = (1 << 9),// draw spines as lines SimulatedSpines = (1 << 10), // draw simulated spine SpinesLOD = (1 << 11), // draw spines with LOD info (red/blue) }; bool Show(int value, DisplayFlags flag) { return (value & static_cast(flag)) == static_cast(flag); } } namespace { static IGeneralMemoryHeap* s_MergedMeshPool = NULL; static CryCriticalSection s_MergedMeshPoolLock; static inline SMeshBoneMapping_uint8 _SortBoneMapping(const SMeshBoneMapping_uint8& a) { SMeshBoneMapping_uint8 r; memset(&r, 0, sizeof(r)); for (size_t i = 0, j = 0; i < 4; ++i) { if (a.weights[i]) { r.weights[j] = a.weights[i]; r.boneIds[j] = a.boneIds[i]; ++j; } } return r; } static inline bool operator== (const SMeshBoneMapping_uint8& a, const SMeshBoneMapping_uint8& b) { const SMeshBoneMapping_uint8 _a = _SortBoneMapping(a); const SMeshBoneMapping_uint8 _b = _SortBoneMapping(b); return memcmp(&_a, &_b, sizeof(_a)) == 0; } static inline bool operator!= (const SMeshBoneMapping_uint8& a, const SMeshBoneMapping_uint8& b) { return !(a == b); } // A hash function with multiplier 65599 (from Red Dragon book) static inline size_t hasher(const char* s, size_t len) { size_t hash = 0; for (size_t i = 0; i < len; ++i) { hash = 65599 * hash + s[i]; } return hash ^ (hash >> 16); } # if MMRM_RENDER_DEBUG && MMRM_VISUALIZE_WINDFIELD void VisualizeWindField(const Vec3* samples, const AABB& bbox, bool wind_dir, bool trace) { IRenderAuxGeom* pAuxGeomRender = gEnv->pRenderer->GetIRenderAuxGeom(); pAuxGeomRender->DrawAABB(bbox, false, Col_Green, eBBD_Faceted); const Vec3& size = bbox.GetSize(); if (wind_dir) { for (size_t x = 0; x < MMRM_WIND_DIM; ++x) { for (size_t y = 0; y < MMRM_WIND_DIM; ++y) { for (size_t z = 0; z < MMRM_WIND_DIM; ++z) { Vec3 pos, w; pos.x = bbox.min.x + ((float)x / (float)((MMRM_WIND_DIM))) * size.x; pos.y = bbox.min.y + ((float)y / (float)((MMRM_WIND_DIM))) * size.y; pos.z = bbox.min.z + ((float)z / (float)((MMRM_WIND_DIM))) * size.z; pAuxGeomRender->DrawLine(pos, Col_Green, pos + samples[x + y * MMRM_WIND_DIM + z * sqr(MMRM_WIND_DIM)].GetNormalized(), Col_Pink, 1.f); } } } } if (trace) { std::vector< std::pair > points; const size_t count = MMRM_WIND_DIM * 4; for (size_t x = 0; x < count; ++x) { for (size_t y = 0; y < count; ++y) { for (size_t z = 0; z < count; ++z) { Vec3 pos; pos.x = ((float)x / (float)(count)); pos.y = ((float)y / (float)(count)); pos.z = ((float)z / (float)(count)); points.push_back(std::make_pair(pos, Vec3(0, 0, 0))); } } } for (size_t i = 0; i < 16; ++i) { ColorB col0; col0.lerpFloat(Col_Blue, Col_Red, i / 16.f); ColorB col1; col1.lerpFloat(Col_Blue, Col_Red, (i + 1) / 16.f); for (size_t j = 0; j < count * count * count; ++j) { const Vec3& pos = points[j].first; const Vec3& vel = points[j].second; Vec3 realpos; realpos.x = bbox.min.x + pos.x * size.x; realpos.y = bbox.min.y + pos.y * size.y; realpos.z = bbox.min.z + pos.z * size.z; const Vec3& nvel = vel + ::SampleWind(pos, samples) * (1.f / 8.f); Vec3 npos = realpos + nvel * 0.3333f; pAuxGeomRender->DrawLine(realpos, col0, npos, col1, 1.f); npos.x = (realpos.x - bbox.min.x) / size.x; npos.y = (realpos.y - bbox.min.y) / size.y; npos.z = (realpos.z - bbox.min.z) / size.z; points[j].first = npos; points[j].second = nvel; } } } } # endif // Three points are a counter-clockwise turn if turn() > 0, clockwise if // turn() < 0, and collinear if turn() == 0. static inline float turn(const Vec2& p1, const Vec2& p2, const Vec2& p3) { return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x); } // Weirdo functions for DynArray // NOTE: Can't make these static, gcc wants an external for a template parameter in graham_reduce inline void graham_increment(Vec2*& iter) { ++iter; } inline void graham_decrement(Vec2*& iter) { --iter; } // Create and maintain a convex hull while iterating over a sorted list of points template static size_t graham_reduce(Vec2* begin, Vec2* end, DynArray& hull) { while (begin != end) { while (hull.size() > 1 && turn(*(hull.rbegin() - 1), *(hull.rbegin()), *begin) > 0) { hull.pop_back(); } if (!hull.size() || *hull.rbegin() != *begin) { hull.push_back(*begin); } Advance(begin); } return hull.size(); } static inline bool vec_compare_lex(const Vec2& a, const Vec2& b) { if (a.x < b.x) { return true; } if (a.x > b.x) { return false; } return a.y < b.y; } // Basic Graham scan implementation. Note this variant does not sort the points by // polar angle but rather performs the ccw-reduction pass on the sorted point list // twice, once in regular and once in reversed order producing the upper and lower // convex hull which get merged as a last step. // // The original algorithm (as can be seen on wikipedia) calculates the hull // in-place in the input list, but has to deal with searching for the lowest // y-coordinate and sorting the input list by polar angles of each point in respect to the // x-axis, so (apart from the additional allocations) the running time should be roughly // identical. static size_t convexhull_graham_scan(DynArray& points) { // If we have less than 4 points, we already have our hull implicitly if (points.size() < 4) { return points.size(); } // Sort the point list by x-coordinate std::sort(points.begin(), points.end(), vec_compare_lex); Vec2* begin = points.begin(); Vec2* end = std::unique(begin, points.end()); // Calculate the lower and upper half of the hull DynArray lower, upper; graham_reduce(begin, end, lower); graham_reduce(end - 1, begin - 1, upper); // Merge the two hulls and swap it with input array // Note: it's safe to skip the first and the last points as they will // in lower already // Note: The insert method of our moronic dynarray DOES NOT COMPILE! for (size_t i = 1, upperEnd = upper.size() - 1; i < upperEnd; lower.push_back(upper[i++])) { ; } std::swap(points, lower); return points.size(); }; static size_t convexhull_giftwrap(DynArray& points) { size_t i = 0, h = 0, n = points.size(); for (size_t j = 0; j < n; ++j) { if (points[j].y < points[i].y) { i = j; } } do { std::swap(points[i], points[h]); for (size_t j = 0; j < n; ++j) { if (turn(points[h], points[i], points[j]) < 0) { i = j; } } h++; } while (i > 0); points.erase(points.begin() + h, points.end()); return points.size(); } /* int edgeTable[256]. It corresponds to the 2^8 possible combinations of of the eight (n) vertices either existing inside or outside (2^n) of the surface. A vertex is inside of a surface if the value at that vertex is less than that of the surface you are scanning for. The table index is constructed bitwise with bit 0 corresponding to vertex 0, bit 1 to vert 1.. bit 7 to vert 7. The value in the table tells you which edges of the table are intersected by the surface. Once again bit 0 corresponds to edge 0 and so on, up to edge 12. Constructing the table simply consisted of having a program run thru the 256 cases and setting the edge bit if the vertices at either end of the edge had different values (one is inside while the other is out). The purpose of the table is to speed up the scanning process. Only the edges whose bit's are set contain vertices of the surface. Vertex 0 is on the bottom face, back edge, left side. The progression of vertices is clockwise around the bottom face and then clockwise around the top face of the cube. Edge 0 goes from vertex 0 to vertex 1, Edge 2 is from 2->3 and so on around clockwise to vertex 0 again. Then Edge 4 to 7 make up the top face, 4->5, 5->6, 6->7 and 7->4. Edge 8 thru 11 are the vertical edges from vert 0->4, 1->5, 2->6, and 3->7. 4--------5 *---4----* /| /| /| /| / | / | 7 | 5 | / | / | / 8 / 9 7--------6 | *----6---* | | | | | | | | | | 0----|---1 | *---0|---* | / | / 11 / 10 / | / | / | 3 | 1 |/ |/ |/ |/ 3--------2 *---2----* */ static uint16 s_marching_cubes_edge_table[256] = { 0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x33, 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 0x3a0, 0x2a9, 0x1a3, 0xaa, 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 0x460, 0x569, 0x663, 0x76a, 0x66, 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff, 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55, 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc, 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66, 0x76a, 0x663, 0x569, 0x460, 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa, 0x1a3, 0x2a9, 0x3a0, 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33, 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99, 0x190, 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 }; /* int8 s_triTable[256][16] also corresponds to the 256 possible combinations of vertices. The [16] dimension of the table is again the list of edges of the cube which are intersected by the surface. This time however, the edges are enumerated in the order of the vertices making up the triangle mesh of the surface. Each edge contains one vertex that is on the surface. Each triple of edges listed in the table contains the vertices of one triangle on the mesh. The are 16 entries because it has been shown that there are at most 5 triangles in a cube and each "edge triple" list is terminated with the value -1. For example triTable[3] contains {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} This corresponds to the case of a cube whose vertex 0 and 1 are inside of the surface and the rest of the verts are outside (00000001 bitwise OR'ed with 00000010 makes 00000011 == 3). Therefore, this cube is intersected by the surface roughly in the form of a plane which cuts edges 8,9,1 and 3. This quadrilateral can be constructed from two triangles: one which is made of the intersection vertices found on edges 1,8, and 3; the other is formed from the vertices on edges 9,8, and 1. Remember, each intersected edge contains only one surface vertex. The vertex triples are listed in counter clockwise order for proper facing. */ static int8 s_marching_cubes_tri_table[256][16] = { {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} }; static inline Vec3 interpolate(float val0, float val1, const Vec3& p0, const Vec3& p1) { if (std::abs(val0) < FLT_EPSILON) { return p0; } if (std::abs(val1) < FLT_EPSILON) { return p1; } if (std::abs(val0 - val1) < FLT_EPSILON) { return p1; } float alpha = (0 - val0) / (val1 - val0); Vec3 p; p.x = p0.x + alpha * (p1.x - p0.x); p.y = p0.y + alpha * (p1.y - p0.y); p.z = p0.z + alpha * (p1.z - p0.z); return p; } static inline void DisplayDensity(IMergedMeshesManager* manager, AABB _bbox, uint8 dim) { const AABB& bbox = _bbox; const Vec3 extents = (bbox.max - bbox.min); IRenderAuxGeom* pAuxGeom = gEnv->pRenderer->GetIRenderAuxGeom(); SAuxGeomRenderFlags old_flags = pAuxGeom->GetRenderFlags(); SAuxGeomRenderFlags new_flags = old_flags; new_flags.SetCullMode(e_CullModeNone); new_flags.SetAlphaBlendMode(e_AlphaBlended); pAuxGeom->SetRenderFlags(new_flags); Vec3 vertlist[12], v[8], e[2]; float d[8][MMRM_MAX_SURFACE_TYPES]; ISurfaceType* surfaceTypes[8][MMRM_MAX_SURFACE_TYPES]; const float rcp = 1.f / (float)(dim - 1); size_t c[8]; ColorF col0, col1, col2; col0.r = 0.8f; col0.g = 0.1f; col0.b = 0.2f; col0.a = 0.85f; col1.r = 0.1f; col1.g = 0.1f; col1.b = 0.6f; col1.a = 0.80f; col1.r = 0.1f; col1.g = 0.8f; col1.b = 0.1f; col1.a = 0.90f; // Extract a surface from each grid cell if the cell straddles // the boundary of the cell for (uint8 i = 0; i < dim; ++i) { for (uint8 j = 0; j < dim; ++j) { for (uint8 k = 0; k < dim; ++k) { const float min_x = bbox.min.x + ((i + 0) * rcp) * extents.x; const float min_y = bbox.min.y + ((j + 0) * rcp) * extents.y; const float min_z = bbox.min.z + ((k + 0) * rcp) * extents.z; const float max_x = bbox.min.x + ((i + 1) * rcp) * extents.x; const float max_y = bbox.min.y + ((j + 1) * rcp) * extents.y; const float max_z = bbox.min.z + ((k + 1) * rcp) * extents.z; memset(surfaceTypes, 0, sizeof(surfaceTypes)); memset(d, 0, sizeof(d)); v[0] = Vec3(min_x, min_y, min_z); v[1] = Vec3(max_x, min_y, min_z); v[2] = Vec3(min_x, max_y, min_z); v[3] = Vec3(max_x, max_y, min_z); v[4] = Vec3(min_x, min_y, max_z); v[5] = Vec3(max_x, min_y, max_z); v[6] = Vec3(min_x, max_y, max_z); v[7] = Vec3(max_x, max_y, max_z); c[0] = manager->QueryDensity(v[0], surfaceTypes[0], d[0]); c[1] = manager->QueryDensity(v[1], surfaceTypes[1], d[1]); c[2] = manager->QueryDensity(v[2], surfaceTypes[2], d[2]); c[3] = manager->QueryDensity(v[3], surfaceTypes[3], d[3]); c[4] = manager->QueryDensity(v[4], surfaceTypes[0], d[4]); c[5] = manager->QueryDensity(v[5], surfaceTypes[1], d[5]); c[6] = manager->QueryDensity(v[6], surfaceTypes[2], d[6]); c[7] = manager->QueryDensity(v[7], surfaceTypes[3], d[7]); for (size_t s = 0; s < MMRM_MAX_SURFACE_TYPES; ++s) { int32 inside = 0; inside |= (1 << 0) & - (d[0][s] > 0.f); inside |= (1 << 1) & - (d[1][s] > 0.f); inside |= (1 << 2) & - (d[2][s] > 0.f); inside |= (1 << 3) & - (d[3][s] > 0.f); inside |= (1 << 4) & - (d[4][s] > 0.f); inside |= (1 << 5) & - (d[5][s] > 0.f); inside |= (1 << 6) & - (d[6][s] > 0.f); inside |= (1 << 7) & - (d[7][s] > 0.f); if (inside == 0x0 || inside == 0xff) { continue; } int32 cubeindex = 0; cubeindex |= 1 & - iszero(inside & (1 << (0 + 0 * 2 + 0 * 4))); cubeindex |= 2 & - iszero(inside & (1 << (1 + 0 * 2 + 0 * 4))); cubeindex |= 4 & - iszero(inside & (1 << (1 + 1 * 2 + 0 * 4))); cubeindex |= 8 & - iszero(inside & (1 << (0 + 1 * 2 + 0 * 4))); cubeindex |= 16 & - iszero(inside & (1 << (0 + 0 * 2 + 1 * 4))); cubeindex |= 32 & - iszero(inside & (1 << (1 + 0 * 2 + 1 * 4))); cubeindex |= 64 & - iszero(inside & (1 << (1 + 1 * 2 + 1 * 4))); cubeindex |= 128 & - iszero(inside & (1 << (0 + 1 * 2 + 1 * 4))); e[0].x = min_x; e[0].y = min_y; e[0].z = min_z; e[1].x = max_x; e[1].y = max_y; e[1].z = max_z; // Define vertices on the edges intersecting the surface const uint16 edge_code = s_marching_cubes_edge_table[cubeindex]; if (edge_code & 1) { vertlist[ 0] = interpolate(d[(0 + 0 * 2 + 0 * 4)][s], d[(1 + 0 * 2 + 0 * 4)][s], Vec3(e[0].x, e[0].y, e[0].z), Vec3(e[1].x, e[0].y, e[0].z)); } if (edge_code & 2) { vertlist[ 1] = interpolate(d[(1 + 0 * 2 + 0 * 4)][s], d[(1 + 1 * 2 + 0 * 4)][s], Vec3(e[1].x, e[0].y, e[0].z), Vec3(e[1].x, e[1].y, e[0].z)); } if (edge_code & 4) { vertlist[ 2] = interpolate(d[(1 + 1 * 2 + 0 * 4)][s], d[(0 + 1 * 2 + 0 * 4)][s], Vec3(e[1].x, e[1].y, e[0].z), Vec3(e[0].x, e[1].y, e[0].z)); } if (edge_code & 8) { vertlist[ 3] = interpolate(d[(0 + 1 * 2 + 0 * 4)][s], d[(0 + 0 * 2 + 0 * 4)][s], Vec3(e[0].x, e[1].y, e[0].z), Vec3(e[0].x, e[0].y, e[0].z)); } if (edge_code & 16) { vertlist[ 4] = interpolate(d[(0 + 0 * 2 + 1 * 4)][s], d[(1 + 0 * 2 + 1 * 4)][s], Vec3(e[0].x, e[0].y, e[1].z), Vec3(e[1].x, e[0].y, e[1].z)); } if (edge_code & 32) { vertlist[ 5] = interpolate(d[(1 + 0 * 2 + 1 * 4)][s], d[(1 + 1 * 2 + 1 * 4)][s], Vec3(e[1].x, e[0].y, e[1].z), Vec3(e[1].x, e[1].y, e[1].z)); } if (edge_code & 64) { vertlist[ 6] = interpolate(d[(1 + 1 * 2 + 1 * 4)][s], d[(0 + 1 * 2 + 1 * 4)][s], Vec3(e[1].x, e[1].y, e[1].z), Vec3(e[0].x, e[1].y, e[1].z)); } if (edge_code & 128) { vertlist[ 7] = interpolate(d[(0 + 1 * 2 + 1 * 4)][s], d[(0 + 0 * 2 + 1 * 4)][s], Vec3(e[0].x, e[1].y, e[1].z), Vec3(e[0].x, e[0].y, e[1].z)); } if (edge_code & 256) { vertlist[ 8] = interpolate(d[(0 + 0 * 2 + 0 * 4)][s], d[(0 + 0 * 2 + 1 * 4)][s], Vec3(e[0].x, e[0].y, e[0].z), Vec3(e[0].x, e[0].y, e[1].z)); } if (edge_code & 512) { vertlist[ 9] = interpolate(d[(1 + 0 * 2 + 0 * 4)][s], d[(1 + 0 * 2 + 1 * 4)][s], Vec3(e[1].x, e[0].y, e[0].z), Vec3(e[1].x, e[0].y, e[1].z)); } if (edge_code & 1024) { vertlist[10] = interpolate(d[(1 + 1 * 2 + 0 * 4)][s], d[(1 + 1 * 2 + 1 * 4)][s], Vec3(e[1].x, e[1].y, e[0].z), Vec3(e[1].x, e[1].y, e[1].z)); } if (edge_code & 2048) { vertlist[11] = interpolate(d[(0 + 1 * 2 + 0 * 4)][s], d[(0 + 1 * 2 + 1 * 4)][s], Vec3(e[0].x, e[1].y, e[0].z), Vec3(e[0].x, e[1].y, e[1].z)); } // Extract triangles for (size_t l = 0; s_marching_cubes_tri_table[cubeindex][l] != -1; l += 3) { pAuxGeom->DrawTriangle( vertlist[s_marching_cubes_tri_table[cubeindex][l + 0]], col0, vertlist[s_marching_cubes_tri_table[cubeindex][l + 1]], col1, vertlist[s_marching_cubes_tri_table[cubeindex][l + 2]], col2); } } } } } pAuxGeom->SetRenderFlags(old_flags); } static inline void DisplayDensitySpheres(IMergedMeshesManager* manager, AABB _bbox, int dim) { const AABB& bbox = _bbox; const Vec3 extents = (bbox.max - bbox.min); IRenderAuxGeom* pAuxGeom = gEnv->pRenderer->GetIRenderAuxGeom(); SAuxGeomRenderFlags old_flags = pAuxGeom->GetRenderFlags(); SAuxGeomRenderFlags new_flags = old_flags; new_flags.SetCullMode(e_CullModeNone); new_flags.SetAlphaBlendMode(e_AlphaBlended); pAuxGeom->SetRenderFlags(new_flags); Vec3 vertlist[12], v[8], e[2]; float d[8][MMRM_MAX_SURFACE_TYPES]; ISurfaceType* surfaceTypes[8][MMRM_MAX_SURFACE_TYPES]; const float rcp = 1.f / (float)(dim - 1); size_t c[8]; ColorF col0, col1, col2; col0.r = 0.8f; col0.g = 0.1f; col0.b = 0.2f; col0.a = 0.85f; col1.r = 0.1f; col1.g = 0.1f; col1.b = 0.6f; col1.a = 0.80f; col1.r = 0.1f; col1.g = 0.8f; col1.b = 0.1f; col1.a = 0.90f; // Extract a surface from each grid cell if the cell straddles // the boundary of the cell for (int i = 0; i < dim; ++i) { for (int j = 0; j < dim; ++j) { for (int k = 0; k < dim; ++k) { const float min_x = bbox.min.x + ((i + 0) * rcp) * extents.x + extents.x * rcp * 0.5f; const float min_y = bbox.min.y + ((j + 0) * rcp) * extents.y + extents.y * rcp * 0.5f; const float min_z = bbox.min.z + ((k + 0) * rcp) * extents.z + extents.z * rcp * 0.5f; c[0] = manager->QueryDensity(Vec3(min_x, min_y, min_z), surfaceTypes[0], d[0]); for (size_t s = 0; s < MMRM_MAX_SURFACE_TYPES; ++s) { if (s < c[0] && d[0][s] > 0.f) { pAuxGeom->DrawSphere(Vec3(min_x, min_y, min_z), extents.x * rcp * 0.5f, Col_Red, true); } else { //pAuxGeom->DrawSphere(Vec3(min_x, min_y, min_z), extents.x*rcp*0.05f, Col_Green, false); } } } } } pAuxGeom->SetRenderFlags(old_flags); } } #include "TypeInfo_impl.h" STRUCT_INFO_BEGIN(SMergedMeshInstanceCompressed) STRUCT_VAR_INFO(pos_x, TYPE_INFO(uint16)) STRUCT_VAR_INFO(pos_y, TYPE_INFO(uint16)) STRUCT_VAR_INFO(pos_z, TYPE_INFO(uint16)) STRUCT_VAR_INFO(scale, TYPE_INFO(uint8)) STRUCT_VAR_INFO(pad8, TYPE_INFO(uint8)) STRUCT_VAR_INFO(rot, TYPE_ARRAY(4, TYPE_INFO(int8))) STRUCT_INFO_END(SMergedMeshInstanceCompressed) STRUCT_INFO_BEGIN(SMergedMeshSectorChunk) STRUCT_VAR_INFO(ver, TYPE_INFO(uint32)) STRUCT_VAR_INFO(i, TYPE_INFO(uint32)) STRUCT_VAR_INFO(j, TYPE_INFO(uint32)) STRUCT_VAR_INFO(k, TYPE_INFO(uint32)) STRUCT_VAR_INFO(m_StatInstGroupID, TYPE_INFO(uint32)) STRUCT_VAR_INFO(m_nSamples, TYPE_INFO(uint32)) STRUCT_INFO_END(SMergedMeshSectorChunk) static const int s_lodNotSet = -1; // ToDo: pack into struct to ensure better locality in data segment struct SMergedMeshGlobals { CCamera camera _ALIGN(16); uint32 frameId; float dt; float dtscale; float abstime; }; static SMergedMeshGlobals s_mmrm_globals _ALIGN(128); template static inline void resize_list(T*& list, size_t nsize, size_t align) { list = (T*) CryModuleReallocAlign(list, (nsize * sizeof(T) + (align - 1)) & ~(align - 1), align); } template static inline void pool_resize_list(T*& list, size_t nsize, size_t align) { AUTO_LOCK(s_MergedMeshPoolLock); if (nsize == 0) { if (s_MergedMeshPool && !(gEnv->IsDynamicMergedMeshGenerationEnabled()) && s_MergedMeshPool->UsableSize(list)) { s_MergedMeshPool->Free(list); list = NULL; return; } CryModuleMemalignFree(list); list = NULL; return; } if (s_MergedMeshPool && !(gEnv->IsDynamicMergedMeshGenerationEnabled())) { size_t osize = 0; if (!list || (osize = s_MergedMeshPool->UsableSize(list)) != 0u) { T* nList = (T*) s_MergedMeshPool->ReallocAlign(list, (nsize * sizeof(T) + (align - 1)) & ~(align - 1), align, "mergedmesh"); if (nList == NULL) { memcpy(nList = (T*)CryModuleMemalign((nsize * sizeof(T) + (align - 1)) & ~(align - 1), align), list, osize); if (osize && list) { s_MergedMeshPool->Free(list); } else if (list) { CryModuleMemalignFree(list); } } if ((list = nList) != NULL) { return; } } } resize_list(list, nsize, align); return; } static inline void pool_free(void* ptr) { AUTO_LOCK(s_MergedMeshPoolLock); if (s_MergedMeshPool && !(gEnv->IsDynamicMergedMeshGenerationEnabled()) && s_MergedMeshPool->UsableSize(ptr)) { s_MergedMeshPool->Free(ptr); return; } CryModuleMemalignFree(ptr); } static void BuildSphereSet( AABB& bbox , primitives::sphere* pColliders , int& nColliders , const Vec3& pos , const quaternionf& q , const AABB& visibleAABB) { Vec3 centre = ((bbox.max + bbox.min) * 0.5f); Vec3 size = (bbox.max - bbox.min) * 0.5f; int max_axis = size[0] > size[1] ? 0 : 1; max_axis = size[max_axis] > size[2] ? max_axis : 2; int min_axis = size[inc_mod3[max_axis]] < size[dec_mod3[max_axis]] ? inc_mod3[max_axis] : dec_mod3[max_axis]; if (size[min_axis] / (max(size[max_axis], FLT_EPSILON)) > 0.75f) { if (nColliders < MMRM_MAX_COLLIDERS) { float radius = size[min_axis]; centre = pos + q * centre; if (Overlap::Sphere_AABB(Sphere(centre, radius), visibleAABB)) { pColliders[nColliders].center = centre; pColliders[nColliders++].r = radius; } } } else { int plane_axis[2] = { max_axis, ((inc_mod3[max_axis] != min_axis) ? inc_mod3[max_axis] : dec_mod3[max_axis]) }; Vec3 base; base[min_axis] = centre[min_axis]; base[plane_axis[0]] = centre[plane_axis[0]] - size[plane_axis[0]] + size[plane_axis[0]] * 0.5f; base[plane_axis[1]] = centre[plane_axis[1]] - size[plane_axis[1]] + size[plane_axis[1]] * 0.5f; ; float radius = size[min_axis] * 1.5f; Vec3 sphcent = pos + q * centre; if (Overlap::Sphere_AABB(Sphere(sphcent, radius), visibleAABB) && nColliders < MMRM_MAX_COLLIDERS) { pColliders[nColliders].center = sphcent; pColliders[nColliders++].r = radius; } for (float i = 0; i < 2.f; i += 1.f) { for (float j = 0; j < 2.f && nColliders < MMRM_MAX_COLLIDERS; j += 1.f) { sphcent[min_axis] = base[min_axis]; sphcent[plane_axis[0]] = base[plane_axis[0]] + ((float)i * 0.5f) * size[plane_axis[0]] * 2.f; sphcent[plane_axis[1]] = base[plane_axis[1]] + ((float)j * 0.5f) * size[plane_axis[1]] * 2.f; sphcent = pos + q * sphcent; if (Overlap::Sphere_AABB(Sphere(sphcent, radius), visibleAABB)) { pColliders[nColliders].center = sphcent; pColliders[nColliders++].r = radius; } } } } } static inline void ExtractSphereSet( IPhysicalEntity* pent , primitives::sphere* pColliders , int& nColliders , const AABB& visibleAABB , bool partsOnly = false) { primitives::cylinder cylinder; pe_status_pos statusPos; pe_status_nparts statusNParts; int nParts = 0; Vec3 delta, pos; quaternionf q; if (pent->GetStatus(&statusPos) == 0) { return; } pos = statusPos.pos; q = statusPos.q; float scale = statusPos.scale; switch (pent->GetType()) { case PE_LIVING: if ((delta = (statusPos.BBox[1] - statusPos.BBox[0])).len2() < sqr(3.5f)) { nParts = pent->GetStatus(&statusNParts); for (statusPos.ipart = 0; statusPos.ipart < nParts && nColliders < MMRM_MAX_COLLIDERS; ++statusPos.ipart) { if (!pent->GetStatus(&statusPos)) { continue; } if (!statusPos.pGeom) { continue; } switch (statusPos.pGeom->GetType()) { if (false) { case GEOM_CAPSULE: statusPos.pGeom->GetPrimitive(0, &cylinder); } if (false) { case GEOM_CYLINDER: statusPos.pGeom->GetPrimitive(0, &cylinder); } for (int i = 0; i < 2 && nColliders < MMRM_MAX_COLLIDERS; ++i) { pColliders[nColliders].center = statusPos.pos + statusPos.q * (cylinder.center + ((float)(i - 1) * cylinder.axis * cylinder.hh * 2.75f)); pColliders[nColliders++].r = cylinder.r * statusPos.scale * scale * 1.75f; } break; case GEOM_SPHERE: statusPos.pGeom->GetPrimitive(0, &pColliders[nColliders++]); break; default: break; } } } else { #if ENABLE_CRY_PHYSICS if (pent->GetiForeignData() == PHYS_FOREIGN_ID_ENTITY) { IEntity* pEntity = (IEntity*)pent->GetForeignData(PHYS_FOREIGN_ID_ENTITY); IF (!pEntity, 0) { return; } ICharacterInstance* pCharInstance = pEntity->GetCharacter(0); IF (!pCharInstance, 0) { return; } ISkeletonPose* pSkeletonPose = pCharInstance->GetISkeletonPose(); IF (!pSkeletonPose, 0) { return; } IPhysicalEntity* pEnt = pSkeletonPose->GetCharacterPhysics(); IF (!pEnt || pEnt->GetType() != PE_ARTICULATED, 0) { return; } ExtractSphereSet(pEnt, pColliders, nColliders, visibleAABB, true); } #else return; #endif // ENABLE_CRY_PHYSICS } break; case PE_ARTICULATED: partsOnly = true; default: // fallthrough Z if ((delta = (statusPos.BBox[1] - statusPos.BBox[0])).len2() > sqr(3.5f)) { primitives::box box; bool added = false; AABB bbox = AABB(AABB::RESET); nParts = pent->GetStatus(&statusNParts); for (statusPos.ipart = 0; statusPos.ipart < nParts; ++statusPos.ipart) { statusPos.flags = status_local; if (!pent->GetStatus(&statusPos)) { continue; } if (!statusPos.pGeom || (statusPos.flagsAND & geom_squashy)) { continue; } statusPos.pGeom->GetBBox(&box); Vec3 centre = box.center * statusPos.scale; Vec3 size = box.size * statusPos.scale; centre = statusPos.pos + statusPos.q * centre; Matrix33 orientationTM = Matrix33(statusPos.q) * box.Basis.GetTransposed(); if (partsOnly) { AABB lbbox = AABB(AABB::RESET); lbbox.Add(centre + orientationTM * Vec3(size.x, size.y, size.z)); lbbox.Add(centre + orientationTM * Vec3(size.x, size.y, -size.z)); lbbox.Add(centre + orientationTM * Vec3(size.x, -size.y, size.z)); lbbox.Add(centre + orientationTM * Vec3(size.x, -size.y, -size.z)); lbbox.Add(centre + orientationTM * Vec3(-size.x, size.y, size.z)); lbbox.Add(centre + orientationTM * Vec3(-size.x, size.y, -size.z)); lbbox.Add(centre + orientationTM * Vec3(-size.x, -size.y, size.z)); lbbox.Add(centre + orientationTM * Vec3(-size.x, -size.y, -size.z)); BuildSphereSet(lbbox, pColliders, nColliders, pos, q, visibleAABB); continue; } else { bbox.Add(centre + orientationTM * Vec3(size.x, size.y, size.z)); bbox.Add(centre + orientationTM * Vec3(size.x, size.y, -size.z)); bbox.Add(centre + orientationTM * Vec3(size.x, -size.y, size.z)); bbox.Add(centre + orientationTM * Vec3(size.x, -size.y, -size.z)); bbox.Add(centre + orientationTM * Vec3(-size.x, size.y, size.z)); bbox.Add(centre + orientationTM * Vec3(-size.x, size.y, -size.z)); bbox.Add(centre + orientationTM * Vec3(-size.x, -size.y, size.z)); bbox.Add(centre + orientationTM * Vec3(-size.x, -size.y, -size.z)); added = true; } } if (added) { BuildSphereSet(bbox, pColliders, nColliders, pos, q, visibleAABB); } } else { pColliders[nColliders].center = pos + ((statusPos.BBox[0] + statusPos.BBox[1]) * 0.5f); pColliders[nColliders++].r = (statusPos.BBox[0] - statusPos.BBox[1]).len() * 0.25f * scale; } break; } } static inline bool SortCollider(const primitives::sphere& a, const primitives::sphere& b) { return a.r > b.r; } static inline void QueryColliders(primitives::sphere*& pColliders, int& nColliders, const AABB& visibleAABB) { if (!pColliders) { resize_list(pColliders, MMRM_MAX_COLLIDERS, 16); } nColliders = 0; IPhysicalEntity* pents[MMRM_MAX_COLLIDERS]; IPhysicalEntity** pentList = &pents[0]; #if ENABLE_CRY_PHYSICS int nents = gEnv->pPhysicalWorld->GetEntitiesInBox( visibleAABB.min, visibleAABB.max, pentList, ent_sleeping_rigid | ent_rigid | ent_living | ent_allocate_list, MMRM_MAX_COLLIDERS); #else // Retreive colliders CRY_PHYSICS_REPLACEMENT_ASSERT(); int nents = 0; #endif // ENABLE_CRY_PHYSICS nColliders = 0; for (int j = 0; j < min(nents, MMRM_MAX_COLLIDERS) && nColliders < MMRM_MAX_COLLIDERS; ++j) { if (!pentList[j]) { continue; } ExtractSphereSet(pentList[j], pColliders, nColliders, visibleAABB); } std::sort(pColliders, pColliders + nColliders, SortCollider); #if ENABLE_CRY_PHYSICS if (pents != pentList) { gEnv->pPhysicalWorld->GetPhysUtils()->DeletePointer(pentList); } #endif // ENABLE_CRY_PHYSICS } inline void QueryProjectiles(SMMRMProjectile*& pColliders, int& nColliders, const AABB& visibleAABB) { if (!pColliders) { resize_list(pColliders, MMRM_MAX_PROJECTILES, 16); } nColliders = 0; ReadLock lock(Cry3DEngineBase::m_pMergedMeshesManager->m_ProjectileLock); CMergedMeshesManager* pMM = Cry3DEngineBase::m_pMergedMeshesManager; for (size_t i = 0, end = pMM->m_Projectiles.size(); nColliders < MMRM_MAX_PROJECTILES && i < end; ++i) { const SProjectile& projectile = pMM->m_Projectiles[i]; if (Overlap::Lineseg_AABB(Lineseg(projectile.initial_pos, projectile.current_pos), visibleAABB) == false) { continue; } pColliders[nColliders].pos[0] = projectile.initial_pos; pColliders[nColliders].pos[1] = projectile.current_pos; pColliders[nColliders].dir = projectile.direction; pColliders[nColliders++].r = projectile.size; } } static inline void SampleWind(Vec3*& wind, const AABB& bbox) { if (!wind) { resize_list(wind, cube(MMRM_WIND_DIM), 16); } for (size_t x = 0; x < MMRM_WIND_DIM; ++x) { for (size_t y = 0; y < MMRM_WIND_DIM; ++y) { for (size_t z = 0; z < MMRM_WIND_DIM; ++z) { wind[x + y * MMRM_WIND_DIM + z * MMRM_WIND_DIM * MMRM_WIND_DIM].x = bbox.min.x + (x / (MMRM_WIND_DIM - 1.f)) * ((bbox.max.x - bbox.min.x)); wind[x + y * MMRM_WIND_DIM + z * MMRM_WIND_DIM * MMRM_WIND_DIM].y = bbox.min.y + (y / (MMRM_WIND_DIM - 1.f)) * ((bbox.max.y - bbox.min.y)); wind[x + y * MMRM_WIND_DIM + z * MMRM_WIND_DIM * MMRM_WIND_DIM].z = bbox.min.z + (z / (MMRM_WIND_DIM - 1.f)) * ((bbox.max.z - bbox.min.z)); } } } # if MMRM_VISUALIZE_WINDSAMPLES Vec3* copy = new Vec3[cube(MMRM_WIND_DIM)]; memcpy(copy, wind, sizeof(Vec3) * cube(MMRM_WIND_DIM)); # endif if (!Cry3DEngineBase::Get3DEngine()->SampleWind(wind, (MMRM_WIND_DIM)*(MMRM_WIND_DIM)*(MMRM_WIND_DIM), bbox, false)) { for (size_t x = 0; x < MMRM_WIND_DIM; ++x) { for (size_t y = 0; y < MMRM_WIND_DIM; ++y) { for (size_t z = 0; z < MMRM_WIND_DIM; ++z) { wind[x + y * MMRM_WIND_DIM + z * MMRM_WIND_DIM * MMRM_WIND_DIM] = Vec3(0, 0, 0); } } } } # if MMRM_VISUALIZE_WINDSAMPLES for (size_t x = 0; x < MMRM_WIND_DIM; ++x) { for (size_t y = 0; y < MMRM_WIND_DIM; ++y) { for (size_t z = 0; z < MMRM_WIND_DIM; ++z) { Vec3 start = copy[x + y * MMRM_WIND_DIM + z * sqr(MMRM_WIND_DIM)]; Vec3 end = start + wind[x + y * MMRM_WIND_DIM + z * MMRM_WIND_DIM * MMRM_WIND_DIM]; gEnv->pRenderer->GetIRenderAuxGeom()->DrawLine(start, Col_Black, end, Col_White); } } } delete[] copy; gEnv->pRenderer->GetIRenderAuxGeom()->DrawAABB(bbox, false, Col_Red, eBBD_Faceted); # endif } struct RenderMeshMeta { _smart_ptr material; uint32 vertices; uint32 indices; }; //////////////////////////////////////////////////////////////////////////////// // Local geometry manager that contains the preprocessed geometry for the job class CGeometryManager : public Cry3DEngineBase { // Key = StatObj + Material + GroupId using GeometryMapKey = std::tuple; typedef std::map GeometryMapT; // The geometry map of the geometry manager GeometryMapT m_geomMap; // The size in bytes of all preproccessed meshes size_t m_PreprocessedSize; // Bakes the lod into a special structure for efficient runtime merging bool PrepareLOD(SMMRMGeometry* geometry, IStatObj* host, size_t nLod, const Matrix34& transformMatrix); // If statobj has "mergedmesh_autodeform" property set, extract deform info // to perform quick&dirty deform sim bool ExtractDeformLOD(SMMRMGeometry* geometry, IStatObj* host, size_t nLod); public: CGeometryManager(); ~CGeometryManager(); void Initialize(); void Shutdown(); SMMRMGeometry* GetGeometry(uint32); SMMRMGeometry* GetGeometry(IStatObj*, IMaterial*, uint32); void ReleaseGeometry(SMMRMGeometry*); // Returns the main memory size of the geometry stored in the geometry manager size_t Size() const { return m_PreprocessedSize; } // Important Note: This method should not be called from any client code, // it is called implicitly from GetGeometry. Sadly, it has to be public // because DECLARE_CLASS_JOB macros only work on public methods. void PrepareGeometry(SMMRMGeometry*); void GetUsedMeshes(DynArray& names); bool SyncPreparationStep(); }; static size_t s_jobQueueIndex[2] = {0, 0}; CGeometryManager::CGeometryManager() : m_PreprocessedSize() {} CGeometryManager::~CGeometryManager() {} void CGeometryManager::Shutdown() { m_geomMap.clear(); m_PreprocessedSize = 0; } void CGeometryManager::Initialize() { } bool CGeometryManager::ExtractDeformLOD(SMMRMGeometry* geometry, IStatObj* host, size_t nLod) { strided_pointer vtx; strided_pointer colour; vtx_idx* indices; size_t constraints_alloc = 0, nconstraints = 0, nvertices = 0, nvertices_alloc = 0; vtx_idx* mapping = 0; std::set edges, tris; SMMRMDeform* deform = new SMMRMDeform(); Vec3* vertices = NULL; float* mass = NULL; SMMRMDeformConstraint* constraints = NULL; const int endian = 0; resize_list(constraints, constraints_alloc = geometry->numVtx, 16); resize_list(vertices, nvertices_alloc = geometry->numVtx, 16); resize_list(mass, nvertices_alloc, 16); resize_list(mapping, geometry->numVtx, 16); // Map incoming mesh vertices to collapsed simulation vertices for (size_t i = 0; i < geometry->numChunks[nLod]; ++i) { SMMRMChunk& chunk = geometry->pChunks[nLod][i]; vtx = strided_pointer(&chunk.general[0].xyz, sizeof(chunk.general[0])); colour = strided_pointer(reinterpret_cast(&chunk.general[0].color), sizeof(chunk.general[0])); indices = chunk.indices; for (size_t k = 0; k < chunk.nindices; k++) { const Vec3& pos = vtx[indices[k]]; const uint8 c[2] = { colour[indices[k]].g, colour[indices[k]].b }; const float m = (float)c[endian] / 255.f; size_t v_idx = (size_t)-1; for (size_t n = 0; n < nvertices; ++n) { if (fabs(pos.x - vertices[n].x) < FLT_EPSILON && fabs(pos.y - vertices[n].y) < FLT_EPSILON && fabs(pos.z - vertices[n].z) < FLT_EPSILON && fabsf(mass[n] - m) < FLT_EPSILON) { v_idx = n; break; } } if (v_idx == (size_t)-1) { v_idx = nvertices; vertices[nvertices] = pos; mass[nvertices++] = m; } mapping[indices[k]] = v_idx; } } bool collapse_ring = true; // Construct constraint based topology for (size_t i = 0; i < geometry->numChunks[nLod]; ++i) { SMMRMChunk& chunk = geometry->pChunks[nLod][i]; indices = chunk.indices; // Extract bending constraints for (size_t j = 0; j < chunk.nvertices; ++j) { if (mass[mapping[j]] == 0.f) { continue; } size_t buddy[2] = { (size_t)-1, (size_t)-1 }; // The neighboring vertices on the same triangle as the current vertex. std::vector ring; // The ring of vertices that surround the current vertex for all triangles that this vertex is a part of. for (size_t k = 0; k < chunk.nindices; k += 3) { size_t centre = (size_t)-1; for (size_t l = 0; centre == (size_t)-1 && l < 3; ++l) { if (mapping[indices[k + l]] == mapping[j]) { centre = l; } } if (centre == (size_t)-1) { continue; } buddy[0] = mapping[indices[k + dec_mod3[centre]]]; buddy[1] = mapping[indices[k + inc_mod3[centre]]]; for (size_t l = 0; l < 2; ++l) { std::vector::iterator where = std::lower_bound(ring.begin(), ring.end(), buddy[l]); if (where != ring.end() && *where == buddy[l]) { continue; } ring.insert(where, buddy[l]); } } // Set up bending constraints buddy[0] = (size_t)-1; buddy[1] = (size_t)-1; if (collapse_ring) // Only create contraints between opposite sides of the ring. { // Find the two most opposite points for (size_t k = 0; k < ring.size(); ++k) { Vec3 d[2]; d[0] = (vertices[ring[k]] - vertices[mapping[j]]).GetNormalized(); float cbest = 0.f; for (size_t l = 0; l < ring.size(); ++l) { if (l == k) { continue; } d[1] = (vertices[ring[l]] - vertices[mapping[j]]).GetNormalized(); float angle = d[1] | d[0]; if (angle < cbest) { cbest = angle; buddy[0] = ring[k]; buddy[1] = ring[l]; } } } // Create the constraint if (buddy[0] != (size_t)-1 && buddy[1] != (size_t)-1) { // check for duplicate constraint. for (size_t o = 0; o < nconstraints; ++o) { if (constraints[o].type == SMMRMDeformConstraint::DC_BENDING && constraints[o].bending[0] == buddy[0] && constraints[o].bending[1] == mapping[j] && constraints[o].bending[2] == buddy[1]) { continue; } } if (mass[buddy[0]] + mass[mapping[j]] + mass[buddy[1]] == 0.f) { continue; } if (nconstraints + 1 > constraints_alloc) { resize_list(constraints, constraints_alloc += 0xff, 16); } Vec3 tri[3] = { vertices[buddy[0]], vertices[mapping[j]], vertices[buddy[1]] }; constraints[nconstraints].bending[0] = buddy[0]; constraints[nconstraints].bending[1] = mapping[j]; constraints[nconstraints].bending[2] = buddy[1]; constraints[nconstraints].displacement = (tri[1] - (1.f / 3.f) * (tri[0] + tri[1] + tri[2])).len(); constraints[nconstraints].k = (1.f - mass[mapping[j]]); constraints[nconstraints++].type = SMMRMDeformConstraint::DC_BENDING; } } else // Create contraints on all triangles around the ring. { for (size_t k = 0; k < ring.size(); ++k) { for (size_t l = 0; l < ring.size(); ++l) { if (l == k) { continue; } // check for duplicate constraint. for (size_t o = 0; o < nconstraints; ++o) { if (constraints[o].type == SMMRMDeformConstraint::DC_BENDING && constraints[o].bending[0] == ring[k] && constraints[o].bending[1] == mapping[j] && constraints[o].bending[2] == ring[l]) { continue; } } if (mass[ring[k]] + mass[mapping[j]] + mass[ring[1]] == 0.f) { continue; } if (nconstraints + 1 > constraints_alloc) { resize_list(constraints, constraints_alloc += 0xff, 16); } Vec3 tri[3] = { vertices[ring[k]], vertices[mapping[j]], vertices[ring[l]] }; constraints[nconstraints].bending[0] = (vtx_idx)ring[k]; constraints[nconstraints].bending[1] = (vtx_idx)mapping[j]; constraints[nconstraints].bending[2] = (vtx_idx)ring[l]; constraints[nconstraints].displacement = (tri[1] - (1.f / 3.f) * (tri[0] + tri[1] + tri[2])).len(); constraints[nconstraints].k = (1.f - mass[mapping[j]]); constraints[nconstraints++].type = SMMRMDeformConstraint::DC_BENDING; } } } } // Extract edge length constraints for (size_t j = 0; j < chunk.nindices; j += 3) { for (size_t k = 0; k < 3; ++k) { uint32 index[2] = {mapping[indices[j + k]], mapping[indices[j + inc_mod3[k]]] }; uint32 hash = min(index[0], index[1]) + max(index[0], index[1]) * nvertices; std::set::iterator where = edges.lower_bound(hash); if (where != edges.end() && *where == hash) { continue; } if (mass[index[0]] + mass[index[1]] == 0.f) { continue; } edges.insert(where, hash); if (nconstraints + 1 > constraints_alloc) { resize_list(constraints, constraints_alloc += 0xff, 16); } constraints[nconstraints].edge[0] = index[0]; constraints[nconstraints].edge[1] = index[1]; constraints[nconstraints].edge_distance = (vertices[index[0]] - vertices[index[1]]).len(); constraints[nconstraints].k = 1.f; constraints[nconstraints++].type = SMMRMDeformConstraint::DC_EDGE; } } } // Truncate the lists to final size resize_list(constraints, nconstraints, 16); resize_list(vertices, nvertices, 16); resize_list(mass, nvertices, 16); deform->invmass = mass; deform->initial = vertices; deform->mapping = mapping; deform->constraints = constraints; deform->nconstraints = nconstraints; deform->nvertices = nvertices; geometry->deform = deform; // Recalculate approximate bounding volume geometry->aabb.Reset(); for (size_t i = 0; i < nvertices; ++i) { if (mass[i] > 0.f) { float distance = FLT_MAX, d; Vec3 centre; for (size_t j = 0; j < nvertices; ++j) { if (mass[j] == 0.f && (d = (vertices[i] - vertices[j]).len()) < distance) { distance = d; centre = vertices[j]; } } geometry->aabb.Add(centre + Vec3(distance, distance, distance)); geometry->aabb.Add(centre - Vec3(distance, distance, distance)); } } return true; } bool CGeometryManager::PrepareLOD(SMMRMGeometry* geometry, IStatObj* host, size_t nLod, const Matrix34& transformMatrix) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::ThreeDEngine); IRenderMesh* renderMesh = NULL; strided_pointer uv; strided_pointer tangs; strided_pointer vtx; strided_pointer norm; strided_pointer colour; SPipQTangents* qtangents = NULL; SMeshBoneMapping_uint8* weights; bool weightsAllocated = false; vtx_idx* indices; CStatObj* statObj = (CStatObj*)host->GetLodObject(nLod, false); if (!statObj) { return true; } bool success = false; // Append sub objects to the geometry if (statObj->SubObjectCount() > 0) { for (int i = 0; i < statObj->SubObjectCount(); ++i) { IStatObj::SSubObject& subObject = statObj->SubObject(i); CStatObj* subObjectStatObj = static_cast(subObject.pStatObj); if (!subObject.bHidden && subObjectStatObj) { Matrix34 childMatrix = transformMatrix * subObject.localTM; success |= PrepareLOD(geometry, static_cast(subObjectStatObj), nLod, childMatrix); } } } IF ((!(renderMesh = statObj->GetRenderMesh())) || (!(gEnv->IsDynamicMergedMeshGenerationEnabled()) && (Cry3DEngineBase::m_p3DEngine->m_bInShutDown || Cry3DEngineBase::m_p3DEngine->m_bInUnload)), 0) { // Only set to NO_RENDERMESH if no sub-objects have already set this. if (geometry->state == SMMRMGeometry::CREATED) { geometry->state = SMMRMGeometry::NO_RENDERMESH; } return success; } renderMesh->LockForThreadAccess(); int nvertices = renderMesh->GetVerticesCount(); vtx.data = (Vec3*)renderMesh->GetPosPtr(vtx.iStride, FSL_READ); colour.data = (uint32*)renderMesh->GetColorPtr(colour.iStride, FSL_READ); uv.data = (Vec2*)renderMesh->GetUVPtr(uv.iStride, FSL_READ); tangs.data = (SPipTangents*)renderMesh->GetTangentPtr(tangs.iStride, FSL_READ); norm.data = (Vec3f16*)renderMesh->GetNormPtr(norm.iStride, FSL_READ); indices = renderMesh->GetIndexPtr(FSL_READ); weights = statObj->m_pBoneMapping; Matrix33 rotationMatrix; transformMatrix.GetRotation33(rotationMatrix); bool rotationMatrixIsIdentity = rotationMatrix.IsIdentity(); if (Cry3DEngineBase::GetCVars()->e_MergedMeshesTesselationSupport == 0) { norm.data = NULL; norm.iStride = 0; } else { norm.data = (Vec3f16*)renderMesh->GetNormPtr(norm.iStride, FSL_READ); } if (tangs) { resize_list(qtangents, renderMesh->GetVerticesCount(), 16); if (rotationMatrixIsIdentity) { MeshTangentsFrameToQTangents( tangs.data, tangs.iStride, nvertices, qtangents, sizeof(qtangents[0])); } else { strided_pointer transformedTangents; resize_list(transformedTangents.data, renderMesh->GetVerticesCount(), tangs.iStride); for (int i = 0; i < renderMesh->GetVerticesCount(); ++i) { transformedTangents[i] = tangs[i]; transformedTangents[i].TransformBy(rotationMatrix); } MeshTangentsFrameToQTangents( transformedTangents.data, transformedTangents.iStride, nvertices, qtangents, sizeof(qtangents[0])); CryModuleMemalignFree(transformedTangents.data); } } // Patch envelope based weighting into geometry if RC skipped weighting step for lower lods. if (weights == NULL && host->GetBoneMapping()) { int vertCount = renderMesh->GetVerticesCount(); weights = new SMeshBoneMapping_uint8[vertCount]; weightsAllocated = true; for (int i = 0; i < vertCount; ++i) { float dist = FLT_MAX; size_t idx = (size_t)-1; for (int j = 0, boneIdx = 1; j < host->GetSpineCount(); boneIdx += host->GetSpines()[j++].nVtx - 1) { for (int k = 0; k < host->GetSpines()[j].nVtx - 1; ++k) { float t = 0.f, dp = Distance::Point_Lineseg( vtx[i], Lineseg(host->GetSpines()[j].pVtx[k], host->GetSpines()[j].pVtx[k + 1]), t); if (dp < dist) { dist = dp; idx = boneIdx + k; } } } weights[i].boneIds[0] = idx; weights[i].weights[0] = 255; for (int j = 1; j < 4; ++j) { weights[i].boneIds[j] = 0; weights[i].weights[j] = 0; } } } TRenderChunkArray& renderChunks = renderMesh->GetChunks(); for (int i = 0; i < renderChunks.size(); ++i) { const CRenderChunk renderChunk = renderChunks[i]; SMMRMChunk* chunk = NULL; if (renderChunks[i].m_nMatFlags & (MTL_FLAG_NODRAW | MTL_FLAG_COLLISION_PROXY | MTL_FLAG_RAYCAST_PROXY)) { continue; } for (size_t j = 0; j < geometry->numChunks[nLod]; ++j) { if (geometry->pChunks[nLod][j].matId == renderChunk.m_nMatID) { chunk = &geometry->pChunks[nLod][j]; break; } } if (!chunk) { resize_list(geometry->pChunks[nLod], (geometry->numChunks[nLod] + 1) * sizeof(SMMRMChunk), 16); chunk = new (&geometry->pChunks[nLod][geometry->numChunks[nLod]++])SMMRMChunk(renderChunk.m_nMatID); } chunk->nvertices_alloc += renderChunk.nNumVerts; chunk->nindices_alloc += renderChunk.nNumIndices; // resize data containers to new size. resize_list(chunk->general, chunk->nvertices_alloc, 16); resize_list(chunk->indices, chunk->nindices_alloc, 16); if (qtangents) { resize_list(chunk->qtangents, chunk->nvertices_alloc, 16); } if (norm) { resize_list(chunk->normals, chunk->nvertices_alloc, 16); } if (weights) { resize_list(chunk->weights, chunk->nvertices_alloc, 16); } } for (int i = 0; i < renderChunks.size(); ++i) { const CRenderChunk renderChunk = renderChunks[i]; SMMRMChunk* chunk = NULL; if (renderChunks[i].m_nMatFlags & (MTL_FLAG_NODRAW | MTL_FLAG_COLLISION_PROXY | MTL_FLAG_RAYCAST_PROXY)) { continue; } for (size_t j = 0; j < geometry->numChunks[nLod]; ++j) { if (geometry->pChunks[nLod][j].matId == renderChunk.m_nMatID) { chunk = &geometry->pChunks[nLod][j]; break; } } assert (chunk); PREFAST_ASSUME (chunk); for (size_t j = renderChunk.nFirstIndexId; j < renderChunk.nFirstIndexId + renderChunk.nNumIndices; ++j) { vtx_idx index = indices[j]; vtx_idx chkidx = (vtx_idx) - 1; SMMRMBoneMapping weightsForIndex; if (weights) { weightsForIndex = weights[index]; } Vec3 transformedPosition = transformMatrix.TransformPoint(vtx[index]); Vec3 transformedNormal; if (norm) { if (rotationMatrixIsIdentity) { transformedNormal = norm[index].ToVec3(); } else { transformedNormal = rotationMatrix.TransformVector(norm[index].ToVec3()); } } for (size_t k = 0; k < chunk->nvertices; ++k) { IF (transformedPosition != chunk->general[k].xyz, 1) { continue; } IF (colour && colour[index] != chunk->general[k].color.dcolor, 1) { continue; } IF (uv && uv[index] != chunk->general[k].st, 1) { continue; } IF (qtangents && qtangents[index] != chunk->qtangents[k], 1) { continue; } IF (norm && transformedNormal != chunk->normals[k], 1) { continue; } IF (weights && chunk->weights[k] != weightsForIndex, 1) { continue; } chkidx = (vtx_idx)k; break; } if (chkidx == (vtx_idx) - 1) { assert(chunk->nvertices < chunk->nvertices_alloc); size_t usedSpines = 0; chunk->general[chunk->nvertices].xyz = transformedPosition; if (colour) { chunk->general[chunk->nvertices].color.dcolor = colour[index]; } if (uv) { chunk->general[chunk->nvertices].st = uv[index]; } if (qtangents) { chunk->qtangents[chunk->nvertices] = qtangents[index]; } if (weights) { chunk->weights[chunk->nvertices] = weightsForIndex; } if (norm) { chunk->normals[chunk->nvertices] = transformedNormal; } for (size_t k = 0; weights && k < 4; ++k) { if (weights[index].weights[k]) { ++usedSpines; } } geometry->maxSpinesPerVtx = max(geometry->maxSpinesPerVtx, usedSpines); chkidx = (vtx_idx)(chunk->nvertices++); } assert(chunk->nindices < chunk->nindices_alloc); chunk->indices[chunk->nindices++] = chkidx; } } for (size_t i = 0; i < geometry->numChunks[nLod]; ++i) { SMMRMChunk* chunk = &geometry->pChunks[nLod][i]; resize_list(chunk->general, chunk->nvertices, 16); resize_list(chunk->indices, chunk->nindices, 16); if (qtangents) { resize_list(chunk->qtangents, chunk->nvertices, 16); } if (weights) { resize_list(chunk->weights, chunk->nvertices, 16); } if (norm) { resize_list(chunk->normals, chunk->nvertices, 16); } if (nLod == 0) { geometry->numVtx += (chunk->nvertices_alloc = chunk->nvertices); geometry->numIdx += (chunk->nindices_alloc = chunk->nindices); } resize_list(chunk->skin_vertices, chunk->nvertices, SMMRMSkinVertex_ALIGN); for (size_t j = 0; j < chunk->nvertices; ++j) { memset(&chunk->skin_vertices[j], 0, sizeof(chunk->skin_vertices[j])); chunk->skin_vertices[j].pos = chunk->general[j].xyz; chunk->skin_vertices[j].uv = chunk->general[j].st; chunk->skin_vertices[j].colour = chunk->general[j].color; if (chunk->normals) { chunk->skin_vertices[j].normal = chunk->normals[j]; } if (chunk->weights) { chunk->skin_vertices[j].SetWeights(chunk->weights[j].weights); chunk->skin_vertices[j].SetBoneIds(chunk->weights[j].boneIds); } if (chunk->qtangents) { chunk->skin_vertices[j].qt = chunk->qtangents[j]; } } } if (qtangents) { CryModuleMemalignFree(qtangents); } renderMesh->UnlockStream(VSF_GENERAL); renderMesh->UnlockStream(VSF_TANGENTS); renderMesh->UnlockStream(VSF_QTANGENTS); renderMesh->UnlockIndexStream(); renderMesh->UnLockForThreadAccess(); if (weightsAllocated) { delete[] weights; } return true; } void CGeometryManager::PrepareGeometry(SMMRMGeometry* geometry) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::ThreeDEngine); if (!geometry) { return; } IStatObj* statObj = geometry->srcObj; if (!statObj) { geometry->state = SMMRMGeometry::NO_STATINSTGROUP; return; } bool success = true; float maxSpineLen = 0, len = 0; int i = 0, j = 0; size_t resultingSize = 0; Matrix34 identityMatrix; identityMatrix.SetIdentity(); for (i = 0; success && i < (int)MAX_STATOBJ_LODS_NUM; ++i) { success &= PrepareLOD(geometry, statObj, i, identityMatrix); } if (!!CryStringUtils::stristr(statObj->GetProperties(), "mergedmesh_deform")) { success &= ExtractDeformLOD(geometry, statObj, 0); } else if (statObj->GetSpineCount() && statObj->GetSpines()) { resize_list(geometry->pSpineInfo, geometry->numSpines = statObj->GetSpineCount(), 16); for (i = 0; i < statObj->GetSpineCount(); ++i) { const SSpine& spine = statObj->GetSpines()[i]; size_t baseVtx = geometry->numSpineVtx; resize_list(geometry->pSpineVtx, geometry->numSpineVtx += (geometry->pSpineInfo[i].nSpineVtx = spine.nVtx), 16); for (j = 0; j < spine.nVtx; ++j) { geometry->pSpineVtx[baseVtx + j].pt = spine.pVtx[j]; } for (j = 0, maxSpineLen = 0; j < spine.nVtx; ++j) { int j1 = baseVtx + j; int j2 = baseVtx + j - (iszero(j - (spine.nVtx - 1)) - 1); maxSpineLen += (len = (geometry->pSpineVtx[j2].pt - geometry->pSpineVtx[j1].pt).len()); geometry->pSpineVtx[j1].h = 0.f; if (j == 0 && spine.nVtx > 2) // special handling for the first segment { Vec3 tri[3]; tri[0] = geometry->pSpineVtx[baseVtx + j + 0].pt - Vec3(0, 0, 1); tri[1] = geometry->pSpineVtx[baseVtx + j + 0].pt; tri[2] = geometry->pSpineVtx[baseVtx + j + 1].pt; geometry->pSpineVtx[baseVtx + j].h = (tri[1] - (1.f / 3.f) * (tri[0] + tri[1] + tri[2])).len(); } if (j > 0 && j < spine.nVtx - 1) { Vec3 tri[3]; tri[0] = geometry->pSpineVtx[baseVtx + j - 1].pt; tri[1] = geometry->pSpineVtx[baseVtx + j ].pt; tri[2] = geometry->pSpineVtx[baseVtx + j + 1].pt; geometry->pSpineVtx[j1].h = (tri[1] - (1.f / 3.f) * (tri[0] + tri[1] + tri[2])).len(); } geometry->pSpineVtx[j1].len = len; } geometry->pSpineInfo[i].fSpineLen = maxSpineLen; } } if (success) { geometry->state = SMMRMGeometry::PREPARED; resultingSize = geometry->Size() + sizeof(SMMRMGeometry); m_PreprocessedSize += resultingSize; if (GetCVars()->e_DebugGeomPrep > 0) { string szGeomName = statObj->GetFileName() + "_" + statObj->GetGeoName(); CryLogAlways("PVRN geometry '%s' prepared, size %" PRISIZE_T " bytes" , (szGeomName.c_str() ? szGeomName.c_str() : "unknown") , resultingSize); for (i = 0; i < (int)MAX_STATOBJ_LODS_NUM; ++i) { size_t vcnt = 0u, icnt = 0u; if (geometry->pChunks[i]) { for (j = 0; (size_t)j < geometry->numChunks[i]; ++j) { vcnt += geometry->pChunks[i][j].nvertices; icnt += geometry->pChunks[i][j].nindices; } CryLogAlways("\tLOD %d %8" PRISIZE_T " vertices %8" PRISIZE_T " indices" , i , vcnt , icnt); } } } } } SMMRMGeometry* CGeometryManager::GetGeometry(uint32 groupId) { StatInstGroup& instGroup = GetObjManager()->GetListStaticTypes()[DEFAULT_SID][groupId]; IStatObj* statObj = instGroup.GetStatObj(); if (!statObj) { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "CGeometryManager::GetGeometry group has no statobj"); statObj = m_pObjManager->GetDefaultCGF(); } IMaterial* material = instGroup.pMaterial; if (!material) { material = statObj->GetMaterial(); } return GetGeometry(statObj, material, groupId); } SMMRMGeometry* CGeometryManager::GetGeometry(IStatObj* statObj, IMaterial* material, uint32 groupId) { if (!statObj) { return NULL; } GeometryMapKey key(statObj, material, groupId); auto it = m_geomMap.find(key); if (it == m_geomMap.end()) { auto result = m_geomMap.emplace( std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(statObj, material)); SMMRMGeometry& geometry = result.first->second; geometry.aabb = statObj->GetAABB(); if (!(gEnv->IsDynamicMergedMeshGenerationEnabled())) { geometry.geomPrepareJobExecutor.StartJob([this, &geometry]() { PrepareGeometry(&geometry); }); // Legacy JobManager priority: eLowPriority, used SJobState::SetBlocking } else { PrepareGeometry(&geometry); } ++geometry.refCount; return &geometry; } SMMRMGeometry& geometry = it->second; ++geometry.refCount; return &geometry; } void CGeometryManager::ReleaseGeometry(SMMRMGeometry* geom) { GeometryMapT::iterator it = m_geomMap.begin(); while (it != m_geomMap.end()) { if (&it->second == geom) { break; } ++it; } if (it == m_geomMap.end()) { CryLogAlways("CGeometryManager::ReleaseGeometry: geometry to release not found!"); } else if (--it->second.refCount == 0) { m_PreprocessedSize -= it->second.Size(); m_PreprocessedSize -= sizeof(SMMRMGeometry); m_geomMap.erase(it); } } void CGeometryManager::GetUsedMeshes(DynArray& names) { for (GeometryMapT::const_iterator it = m_geomMap.begin(), end = m_geomMap.end(); it != end; ++it) { const SMMRMGeometry& geom = it->second; const_cast(geom.geomPrepareJobExecutor).WaitForCompletion(); IStatObj* pObj = geom.srcObj; if (pObj) { names.push_back(pObj->GetFileName()); } } } bool CGeometryManager::SyncPreparationStep() { bool success = true; for (GeometryMapT::const_iterator it = m_geomMap.begin(), end = m_geomMap.end(); it != end; ++it) { const SMMRMGeometry& geom = it->second; const_cast(geom.geomPrepareJobExecutor).WaitForCompletion(); success &= geom.state == SMMRMGeometry::PREPARED; } return success; } StaticInstance s_GeomManager; // This a definition of the callback function that is called when variable change. static void UpdateRatios(ICVar* pVar) { CMergedMeshesManager* pManager = ((C3DEngine*)(gEnv->p3DEngine))->m_pMergedMeshesManager; if (!pVar || !pManager) { return; } if (!strcmp(pVar->GetName(), "e_MergedMeshesViewDistRatio")) { pManager->UpdateViewDistRatio(pVar->GetFVal()); } if (!strcmp(pVar->GetName(), "e_MergedMeshesLodRatio")) { pManager->UpdateLodRatio(pVar->GetFVal()); } } SMMRMGroupHeader::~SMMRMGroupHeader() { s_GeomManager->ReleaseGeometry(procGeom); if (instances) { pool_free(instances); } if (spines) { pool_free(spines); } if (deform_vertices) { pool_free(deform_vertices); } if (visibleChunks) { pool_free(visibleChunks); } if (proxies) { pool_free(proxies); } } CMergedMeshRenderNode::CMergedMeshRenderNode() : m_nActive(0) , m_nVisible(0) , m_needsStaticMeshUpdate(0) , m_needsDynamicMeshUpdate(0) , m_needsPostRenderStatic(0) , m_needsPostRenderDynamic(0) , m_ownsGroups(1) , m_hasDynamicInstances(0) { memset(m_surface_types, 0x0, sizeof(m_surface_types)); SetViewDistUnlimited(); m_fWSMaxViewDist = FLT_MAX; SetRndFlags(ERF_CASTSHADOWMAPS | ERF_HAS_CASTSHADOWMAPS, true); if (GetCVars()->e_MergedMeshesOutdoorOnly) { SetRndFlags(GetRndFlags() | ERF_OUTDOORONLY, true); } } CMergedMeshRenderNode::~CMergedMeshRenderNode() { Clear(); GetISystem()->GetI3DEngine()->FreeRenderNodeState(this); if (m_pMergedMeshesManager) { m_pMergedMeshesManager->RemoveMergedMesh(this); } } void CMergedMeshRenderNode::Clear() { for (size_t i = 0; i < m_nGroups; ++i) { if (m_groups[i].procGeom) { m_groups[i].procGeom->geomPrepareJobExecutor.WaitForCompletion(); } } DeleteRenderMesh(RUT_STATIC); DeleteRenderMesh(RUT_DYNAMIC); m_renderMeshes[RUT_STATIC].clear(); m_renderMeshes[RUT_DYNAMIC].clear(); if (m_groups && m_ownsGroups) { for (size_t i = 0; i < m_nGroups; ++i) { if ((gEnv->IsDynamicMergedMeshGenerationEnabled()) && m_groups[i].proxies) { for (size_t j = 0; j < m_groups[i].numSamples; ++j) { assert(m_groups[i].proxies && m_groups[i].proxies[j]); PREFAST_ASSUME(m_groups[i].proxies && m_groups[i].proxies[j]); m_groups[i].proxies[j]->m_host = NULL; delete m_groups[i].proxies[j]; } } m_groups[i].~SMMRMGroupHeader(); } CryModuleMemalignFree(m_groups); } m_groups = NULL; m_nGroups = 0u; if (m_wind) { CryModuleMemalignFree(m_wind); m_wind = NULL; } if (m_density) { CryModuleMemalignFree(m_density); m_density = NULL; } if (m_Colliders) { CryModuleMemalignFree(m_Colliders); m_Colliders = NULL; } if (m_Projectiles) { CryModuleMemalignFree(m_Projectiles); m_Projectiles = NULL; } } bool CMergedMeshRenderNode::PrepareRenderMesh(RENDERMESH_UPDATE_TYPE type, int nLod) { // Make sure that all preparation jobs have completed until then. std::vector groups(m_nGroups); std::vector& meshes = m_renderMeshes[type]; std::iota(groups.begin(), groups.end(), 0); Vec3 extents = (m_visibleAABB.max - m_visibleAABB.min) * 0.5f; Vec3 origin = m_pos - extents; bool allDone = true; Quat q; int updateTypeValue = static_cast(type); m_SpinesInitialized = false; // NOTE: This will get set to true inside of InitializeSpineAndDeformationData if any data is found to exist. m_SpinesExist = false; for (size_t i = 0, end = groups.size(); i < end; ++i) { SMMRMGroupHeader* header = NULL; size_t index = groups[i]; if (m_groups[index].procGeom->geomPrepareJobExecutor.IsRunning() == true) { allDone = false; continue; } else { switch (m_groups[index].procGeom->state) { case SMMRMGeometry::PREPARED: header = &m_groups[index]; break; case SMMRMGeometry::NO_RENDERMESH: assert (0); CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "merged mesh '%s' had no rendermesh during merging", m_groups[index].procGeom->srcObj->GetFileName().c_str()); break; case SMMRMGeometry::CREATED: assert (0); CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "merged mesh '%s' has an invalid state during merging", m_groups[index].procGeom->srcObj->GetFileName().c_str()); break; case SMMRMGeometry::NO_STATINSTGROUP: assert (0); CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "merged mesh had no valid statinst group"); break; default: break; } } if (header && header->numSamples) { const SMMRMGeometry* procGeom = header->procGeom; // Skip this group if not the correct type header->is_dynamic = procGeom->numSpines > 0u; if (static_cast(header->is_dynamic) != updateTypeValue) { continue; } // Find the correct material for this group _smart_ptr material = procGeom->srcMaterial; // See if there's a mesh that already uses this material. If not, create a new mesh for this material. SMMRM* mesh = NULL; for (std::size_t j = 0; j < meshes.size(); ++j) { if (meshes[j].mat == material) { mesh = &meshes[j]; break; } } if (!mesh) { meshes.push_back(SMMRM()); mesh = &meshes.back(); mesh->mat = material; mesh->chunks = procGeom->numChunks[0]; } pool_resize_list(header->visibleChunks, header->numVisbleChunks = procGeom->numChunks[0], 16); for (size_t j = 0; j < procGeom->numChunks[0]; ++j) { const SMMRMChunk& chunk = procGeom->pChunks[0][j]; mesh->hasTangents |= chunk.qtangents != nullptr; mesh->hasNormals |= chunk.normals != nullptr; } } // If this node doesn't support streaming (in-Editor, or dynamic veg), we create spines and/or deformation data on demand here if (!SupportsStreaming() && header && header->instances) { if (header->procGeom->numSpines) { pool_resize_list(header->spines, header->numSamples * header->procGeom->numSpineVtx, 128); } if (header->procGeom->deform) { pool_resize_list(header->deform_vertices, header->numSamples * header->procGeom->deform->nvertices, 16); } InitializeSpineAndDeformationData(header); } } m_SpinesInitialized = true; return allDone; } bool CMergedMeshRenderNode::Setup(const AABB& aabb, const AABB& visAbb, const PodArray* samples) { m_internalAABB = aabb; m_visibleAABB = visAbb; m_pos = (aabb.max + aabb.min) * 0.5f; m_initPos = m_pos; // Clear out the camera's position from the last time we updated the merged mesh. // (Note: It doesn't really matter what we clear it to, because even if the camera is at the origin, // we'll still refresh the first time through) m_cameraPosAtLastUpdate = Vec3(0.0f); for (size_t i = 0; samples && i < samples->size(); ++i) { AddInstance(samples->GetAt(i)); } m_State = DIRTY; return true; } bool CMergedMeshRenderNode::AddGroup(uint32 statInstGroupId, uint32 numSamples) { if (Cry3DEngineBase::GetCVars()->e_MergedMeshes == 0) { return false; } StatInstGroup& instGroup = GetStatObjGroup(statInstGroupId); for (uint32 ic = 0; ic < numSamples; ic += min(numSamples - ic, MMRM_MAX_SAMPLES_PER_BATCH)) { SMMRMGroupHeader* header = NULL; resize_list(m_groups, m_nGroups + 1, 128); header = new (&m_groups[m_nGroups++])SMMRMGroupHeader; header->procGeom = s_GeomManager->GetGeometry(statInstGroupId); header->instGroupId = statInstGroupId; header->numSamples = min(numSamples - ic, MMRM_MAX_SAMPLES_PER_BATCH); header->splitGroup = ic > 0; float overrideDistanceRatio = 100.0f; if (ICVar* pVar = gEnv->pConsole->GetCVar("e_MergedMeshesViewDistRatio")) { overrideDistanceRatio = pVar->GetFVal(); } header->maxViewDistance = instGroup.fVegRadius * instGroup.fMaxViewDistRatio * overrideDistanceRatio; header->lodRationNorm = instGroup.fLodDistRatio; if (!(header->specMismatch = !CheckMinSpec(instGroup.minConfigSpec))) { m_Instances += min(numSamples - ic, MMRM_MAX_SAMPLES_PER_BATCH); } header->physConfig.Update(&instGroup, 1.f); } return true; } IRenderNode* CMergedMeshRenderNode::AddInstance(const SProcVegSample& sample, bool bRegister) { assert (gEnv->IsDynamicMergedMeshGenerationEnabled()); SMMRMGeometry* procGeom = s_GeomManager->GetGeometry(sample.InstGroupId); IF(!procGeom, 0) { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "CMergedMeshRenderNode::AddInstance no valid procGeom"); return NULL; } m_cullJobExecutor.WaitForCompletion(); size_t headerIndex = (size_t)-1; SMMRMGroupHeader* header = NULL; for (size_t i = 0; i < m_nGroups; ++i) { if (m_groups[i].procGeom == procGeom && (procGeom->numVtx == 0 || m_groups[i].numSamples < min((size_t)MMRM_MAX_SAMPLES_PER_BATCH, 0xffffu / procGeom->numVtx))) { headerIndex = i; header = &m_groups[i]; break; } } m_updateJobExecutor.WaitForCompletion(); if (headerIndex == (size_t)-1) { resize_list(m_groups, m_nGroups + 1, 128); headerIndex = m_nGroups++; header = new (&m_groups[headerIndex])SMMRMGroupHeader; header->procGeom = procGeom; header->instGroupId = sample.InstGroupId; } else { s_GeomManager->ReleaseGeometry(procGeom); } if (header->numSamples + 1 >= header->numSamplesAlloc) { pool_resize_list(header->instances, header->numSamplesAlloc += 0xff, 128); if (gEnv->IsDynamicMergedMeshGenerationEnabled()) { pool_resize_list(header->proxies, header->numSamplesAlloc, 16); } } const float fExtentsRec = 1.0f / c_MergedMeshesExtent; ConvertInstanceRelative(header->instances[header->numSamples], sample.pos, m_internalAABB.min, fExtentsRec); header->instances[header->numSamples].scale = sample.scale; CompressQuat(sample.q, header->instances[header->numSamples]); header->instances[header->numSamples++].lastLod = -2; float overrideDistanceRatio = 100.0f; if (ICVar* pVar = gEnv->pConsole->GetCVar("e_MergedMeshesViewDistRatio")) { overrideDistanceRatio = pVar->GetFVal(); } // in case the values changed, reapply them here as otherwise the header setup will never get updated StatInstGroup& instGroup = GetStatObjGroup(sample.InstGroupId); header->maxViewDistance = instGroup.fVegRadius * instGroup.fMaxViewDistRatio * overrideDistanceRatio; header->lodRationNorm = instGroup.fLodDistRatio; if (header->spines) { pool_free(header->spines); header->spines = NULL; } if (header->deform_vertices) { pool_free(header->deform_vertices); header->deform_vertices = NULL; } m_visibleAABB.Add(sample.pos + header->procGeom->aabb.max); m_visibleAABB.Add(sample.pos + header->procGeom->aabb.min); if (bRegister) { Get3DEngine()->UnRegisterEntityAsJob(this); Get3DEngine()->RegisterEntity(this); } assert (m_internalAABB.IsContainPoint(sample.pos)); ++m_Instances; m_State = DIRTY; if (gEnv->IsDynamicMergedMeshGenerationEnabled()) { header->proxies[header->numSamples - 1] = new CMergedMeshInstanceProxy(this, headerIndex, header->numSamples - 1); return header->proxies[header->numSamples - 1]; } return NULL; } IRenderNode* CMergedMeshesManager::AddDynamicInstance(const IMergedMeshesManager::SInstanceSample& sample, IRenderNode** ppNode, bool bRegister) { SProcVegSample s = { sample.pos, sample.q, sample.instGroupId, sample.scale }; CMergedMeshRenderNode* meshNode = nullptr; const bool dynamicInstance = true; IRenderNode* node = AddInstance(s, &meshNode, bRegister, dynamicInstance); meshNode->m_hasDynamicInstances = 1; if (ppNode) { *ppNode = meshNode; } return node; } size_t CMergedMeshRenderNode::RemoveInstance(size_t headerIndex, size_t instanceIndex) { assert (headerIndex < m_nGroups); SMMRMGroupHeader* header = &m_groups[headerIndex]; m_cullJobExecutor.WaitForCompletion(); m_updateJobExecutor.WaitForCompletion(); assert (instanceIndex < header->numSamples); // If we've exceeded memory pool limits, we might not have *actual* instances for the vegetation. if (header->instances) { std::swap(header->instances[instanceIndex], header->instances[header->numSamples - 1]); } if (gEnv->IsDynamicMergedMeshGenerationEnabled() && instanceIndex != header->numSamples - 1) { std::swap(header->proxies[instanceIndex], header->proxies[header->numSamples - 1]); header->proxies[instanceIndex]->m_sampleIndex = instanceIndex; } --header->numSamples; if ((header->numSamplesAlloc > 0) && (header->numSamplesAlloc - header->numSamples > 0xff)) { pool_resize_list(header->instances, header->numSamplesAlloc -= 0xff, 128); if (gEnv->IsDynamicMergedMeshGenerationEnabled()) { pool_resize_list(header->proxies, header->numSamplesAlloc, 16); } } if (header->spines) { CryModuleMemalignFree(header->spines); header->spines = NULL; } if (header->deform_vertices) { CryModuleMemalignFree(header->deform_vertices); header->deform_vertices = NULL; } m_State = DIRTY; return --m_Instances; } void CMergedMeshRenderNode::QueryColliders() { ::QueryColliders(m_Colliders, m_nColliders, m_visibleAABB); } void CMergedMeshRenderNode::QueryProjectiles() { ::QueryProjectiles(m_Projectiles, m_nProjectiles, m_visibleAABB); } void CMergedMeshRenderNode::SampleWind() { ::SampleWind(m_wind, m_internalAABB); } bool CMergedMeshRenderNode::DeleteRenderMesh(RENDERMESH_UPDATE_TYPE type, bool block, bool zap) { { AZ_PROFILE_SCOPE_STALL(AZ::Debug::ProfileCategory::ThreeDEngine, "CMergedMeshRenderNode::DeleteRenderMesh:SyncAllJobs"); while (!SyncAllJobs()) { if (!block) { return false; } } } // Clear the rendermesh update structures std::vector& renderMeshes = m_renderMeshes[type]; for (size_t j = 0; j < renderMeshes.size(); ++j) { if (zap) { stl::free_container(renderMeshes[j].updates); } else { for (size_t i = 0; i < renderMeshes[j].updates.size(); ++i) { renderMeshes[j].updates[i].chunks.clear(); } } if (zap) { stl::free_container(renderMeshes[j].rms); } else { renderMeshes[j].rms.clear(); } renderMeshes[j].vertices = 0; renderMeshes[j].indices = 0; } m_SizeInVRam = 0u; m_nLod[type] = s_lodNotSet; return true; } void CMergedMeshRenderNode::CreateRenderMesh(RENDERMESH_UPDATE_TYPE type, const SRenderingPassInfo& passInfo) { AZ_TRACE_METHOD(); const int use_spines = GetCVars()->e_MergedMeshesUseSpines; std::vector& renderMeshes = m_renderMeshes[type]; const size_t num_rms = renderMeshes.size(); m_SizeInVRam = 0; uint64 maxVertices = Cry3DEngineBase::GetCVars()->e_MergedMeshesMaxVerticesPerSector; uint64 totalVerticesRendered = 0; // Prime memory for (uintptr_t i = (uintptr_t)&m_groups[0]; i < (uintptr_t)&m_groups[m_nGroups]; i += 128u) { CryPrefetch((void*)i); } PREFAST_SUPPRESS_WARNING(6255) size_t * num_updates = (size_t*)alloca(sizeof(size_t) * num_rms); PREFAST_SUPPRESS_WARNING(6255) size_t * groups_mesh = (size_t*)alloca(sizeof(size_t) * m_nGroups); PREFAST_SUPPRESS_WARNING(6255) size_t * num_vi = (size_t*)alloca(sizeof(size_t) * m_nGroups); memset(num_updates, 0x0, sizeof(size_t) * num_rms); memset(groups_mesh, 0x0, sizeof(size_t) * m_nGroups); memset(num_vi, 0x0, sizeof(size_t) * m_nGroups); // For all registered groups, build the update contexts // and grab required vertex and index counts for (size_t i = 0, j = 0; i < m_nGroups; ++i) { SMMRMGroupHeader* group = &m_groups[i]; IF (group->procGeom == NULL || group->numSamples == 0 || group->specMismatch || ((int)type != (int)group->is_dynamic), 0) { continue; } size_t ii = 0, iv = 0; for (j = 0; j < group->numVisbleChunks; ++j) { ii += group->visibleChunks[j].indices; iv += group->visibleChunks[j].vertices; } if ((num_vi[i] = ii + iv) == 0) { continue; } mmrm_assert(ii > 0 && iv > 0); if (gEnv->IsDynamicMergedMeshGenerationEnabled()) { StatInstGroup& instGroup = GetStatObjGroup(group->instGroupId); group->physConfig.Update(&instGroup, 1.f); } _smart_ptr material = group->procGeom->srcMaterial; SMMRM* mesh = NULL; for (j = 0; j < num_rms; ++j) { if (renderMeshes[j].mat == material) { mesh = & renderMeshes[j]; break; } } if (mesh) { ++num_updates[j]; groups_mesh[i] = j; } else { AZ_Assert(false, "We didn't find a proper mesh for the given group material, most likely the node wasn't properly reset after a material change."); } } // Pre-allocate the number of concurrent updates each mesh will receive for (size_t j = 0; j < num_rms; ++j) { renderMeshes[j].updates.resize(num_updates[j]); num_updates[j] = 0; } // For all registered groups, build the update contexts // and grab required vertex and index counts for (size_t i = 0, j = 0; i < m_nGroups; ++i) { SMMRMGroupHeader* group = &m_groups[i]; IF(group->numSamples == 0 || group->specMismatch || ((int)type != (int)group->is_dynamic) || num_vi[i] == 0, 0) { continue; } size_t groupMeshIdx = groups_mesh[i]; AZ_Assert(groupMeshIdx < num_rms, "Group mesh index ended up in an invalid state."); SMMRM* mesh = &renderMeshes[groupMeshIdx]; AZ_Assert(num_updates[groupMeshIdx] < mesh->updates.size(), "num_updates ended up in an invalid state."); if ((groupMeshIdx < num_rms) && (num_updates[groupMeshIdx] < mesh->updates.size())) { SMMRMUpdateContext& update = mesh->updates[num_updates[groupMeshIdx]]; num_updates[groupMeshIdx]++; update.group = group; update.chunks.resize(group->numVisbleChunks); for (size_t k = 0; k < group->numVisbleChunks; ++k) { mesh->indices += (update.chunks[k].icnt = group->visibleChunks[k].indices); mesh->vertices += (update.chunks[k].vcnt = group->visibleChunks[k].vertices); update.chunks[k].matId = group->visibleChunks[k].matId; } } } #if MMRM_RENDER_DEBUG && MMRM_VISUALIZE_WINDFIELD if (GetCVars()->e_MergedMeshesDebug) { VisualizeWindField(m_wind, m_internalAABB, true, true); } #endif // Loop over the rendermeshes and dispatch the update passes for (size_t i = 0, nrm = renderMeshes.size(); i < nrm; ++i) { SMMRM* mesh = &renderMeshes[i]; if (mesh->updates.empty() || mesh->vertices == 0 || mesh->indices == 0) { continue; } if ((totalVerticesRendered + mesh->vertices) > maxVertices) { AZ_Error("MergedMesh", false, "Merged mesh sector exceeded e_MergedMeshesMaxVerticesPerSector. Portions of the sector won't be rendered."); continue; } totalVerticesRendered += mesh->vertices; size_t k = 0, vertices = 0, indices = 0; do { // Assign contiguous renderchunks per group subchunk, split into multiple rendermeshes // if the combined size of the rendermeshes exceed the allowed vertex count for 16bit // indices. static const size_t maxVertices = 0xffff; std::vector rmchunks; rmchunks.reserve(mesh->updates.size()); CRenderChunk chunk; chunk.m_vertexFormat = eVF_P3S_C4B_T2S; size_t iv = 0, ii = 0, beg = k; for (; k < mesh->updates.size(); ++k) { size_t accum = 0; SMMRMUpdateContext* update = &mesh->updates[k]; for (size_t j = 0, nc = update->chunks.size(); j < nc; ++j) { accum += update->chunks[j].vcnt; } AZ_Error("MergedMesh", accum <= maxVertices, "Too many vertices in a single mesh chunk."); if (accum > maxVertices) { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "CMergedMeshRenderNode::CreateRenderMesh submesh exceeds max vertices. Perform Export To Game to resolve issue."); goto exitWhileLoop; } if (iv + accum > maxVertices) { break; // out of space. } for (size_t j = 0, nc = update->chunks.size(); j < nc; ++j) { if (update->chunks[j].matId != chunk.m_nMatID) { if (chunk.nNumIndices > 0 && chunk.nNumVerts > 0) { rmchunks.push_back(chunk); } chunk.m_nMatID = update->chunks[j].matId; chunk.nFirstIndexId = ii; chunk.nFirstVertId = iv; chunk.nNumIndices = 0; chunk.nNumVerts = 0; } update->chunks[j].ioff = ii; update->chunks[j].voff = iv; chunk.nNumIndices += update->chunks[j].icnt; ii += update->chunks[j].icnt; chunk.nNumVerts += update->chunks[j].vcnt; iv += update->chunks[j].vcnt; } } if (chunk.nNumIndices && chunk.nNumVerts) { rmchunks.push_back(chunk); } mmrm_assert(iv <= maxVertices); // Create a new rendermesh and dispatch the asynchronous updates _smart_ptr rm = GetRenderer()->CreateRenderMeshInitialized( nullptr, iv, chunk.m_vertexFormat, nullptr, ii, prtTriangleList, "MergedMesh", "MergedMesh", eRMT_Dynamic); rm->LockForThreadAccess(); m_SizeInVRam += (sizeof(chunk.m_vertexFormat) + sizeof(SPipTangents))* iv; m_SizeInVRam += sizeof(vtx_idx)* ii; strided_pointer tgtBuf; strided_pointer vtxBuf; strided_pointer nrmBuf; vtx_idx* idxBuf = nullptr; vtxBuf.data = (Vec3f16*)rm->GetPosPtrNoCache(vtxBuf.iStride, FSL_CREATE_MODE); if (mesh->hasNormals) { nrmBuf.data = (Vec3f16*)rm->GetNormPtr(nrmBuf.iStride, FSL_CREATE_MODE); } if (mesh->hasTangents) { tgtBuf.data = (SPipTangents*)rm->GetTangentPtr(tgtBuf.iStride, FSL_CREATE_MODE); } idxBuf = rm->GetIndexPtr(FSL_CREATE_MODE); if (!rm->CanRender() || !vtxBuf || !idxBuf || (mesh->hasNormals && !nrmBuf) || (mesh->hasTangents && (!tgtBuf))) { rm->UnlockStream(VSF_GENERAL); rm->UnlockStream(VSF_TANGENTS); rm->UnlockIndexStream(); rm->UnLockForThreadAccess(); continue; } for (size_t j = beg; j < k; ++j) { SMMRMUpdateContext* update = & mesh->updates[j]; update->general = (SVF_P3S_C4B_T2S*)vtxBuf.data; update->tangents = tgtBuf.data; update->normals = nrmBuf.data; update->idxBuf = idxBuf; update->updateFlag = rm->SetAsyncUpdateState(); update->colliders = m_Colliders; update->ncolliders = m_nColliders; update->projectiles = m_Projectiles; update->nprojectiles = m_nProjectiles; update->max_iter = 1; update->dt = s_mmrm_globals.dt; update->abstime = s_mmrm_globals.abstime; update->zRotation = m_zRotation; update->rotationOrigin = m_pos; update->_min = m_internalAABB.min; update->_max = m_internalAABB.max; update->wind = m_wind; update->use_spines = use_spines && m_SpinesInitialized && m_SpinesExist; update->frame_count = passInfo.GetMainFrameID(); #if MMRM_USE_BOUNDS_CHECK for (size_t u = 0; u < update->chunks.size(); ++u) { if (((SVF_P3S_C4B_T2S*)vtxBuf.data) + update->chunks[u].voff > (((SVF_P3S_C4B_T2S*)vtxBuf.data) + iv)) { __debugbreak(); } if (idxBuf + update->chunks[u].ioff > idxBuf + ii) { __debugbreak(); } } update->general_end = ((SVF_P3S_C4B_T2S*)vtxBuf.data) + iv; update->tangents_end = ((SPipTangents*)tgtBuf.data) + iv; update->idx_end = idxBuf + ii; #endif if (update->group->deform_vertices) { m_updateJobExecutor.StartJob([update]() { update->MergeInstanceMeshesDeform(&s_mmrm_globals.camera, 0u); }); } else { m_updateJobExecutor.StartJob([update]() { update->MergeInstanceMeshesSpines(&s_mmrm_globals.camera, 0u); }); } } rm->SetRenderChunks(&rmchunks[0], rmchunks.size(), false); rm->UnLockForThreadAccess(); mesh->rms.push_back(rm); vertices += iv; indices += ii; } while (vertices < mesh->vertices && indices < mesh->indices); exitWhileLoop:; // "Too many vertices in a single mesh chunk." } m_LastUpdateFrame = passInfo.GetMainFrameID(); } void CMergedMeshRenderNode::Render(const struct SRendParams& EntDrawParams, const SRenderingPassInfo& passInfo) { uint32 frameId; #if AZ_RENDER_TO_TEXTURE_GEM_ENABLED // RTT does not support merged meshes yet. if (passInfo.IsRenderSceneToTexturePass()) { return; } #endif if (!passInfo.RenderVegetation() || GetCVars()->e_MergedMeshes == 0) { return; } size_t i = 0; frameId = passInfo.GetMainFrameID(); // ToDo : make nicer (All the merged mesh render nodes render through this function, but this is updating globals that only need to be updated once per frame) const CCamera& currentCam = passInfo.GetCamera(); if (frameId != s_mmrm_globals.frameId) { s_mmrm_globals.camera = currentCam; s_mmrm_globals.dt = gEnv->pTimer->GetFrameTime(); s_mmrm_globals.dtscale = gEnv->pTimer->GetTimeScale(); s_mmrm_globals.abstime = gEnv->pTimer->GetCurrTime(); s_mmrm_globals.frameId = frameId; } const Vec3& camPos = currentCam.GetPosition(); // internalAABB = the bounding box of the vegetation sector, regardless of how full it is. // visibleAABB = the bounding box inside the vegetation sector of where the vegetation instances actually exist. // // When making our various rendering decisions below, we're *conceptually* doing distance checks vs multiples // of sector sizes. // What we're *actually* doing is checking multiples of *squared* lengths of half of the bounding box's diagonal. // Keep in mind that the diagonal is in 3D space, so if you do the trigonometry, a sector size of X meters has a // "half diagonal" length of sqrt((X/2) * (X/2) + (X/2) * (X/2) + (X/2) * (X/2)), or (X/2) * sqrt(3). The squared // length then is X^2 * (3/4). // Ideally, our tuning variables would just be described as absolute distances, not multiples of 3/4 the squared // sector size, but changing it now would break any existing tuned data. const float sqSectorDiagonal = m_internalAABB.GetRadiusSqr(); const float sqSectorDiagonalInstanceDistMultiple = sqSectorDiagonal * GetCVars()->e_MergedMeshesInstanceDist; const float sqSectorDiagonalInstanceShadowsMultiple = sqSectorDiagonal * GetCVars()->e_MergedMeshesInstanceDistShadows; const float sqSectorDiagonalLodMultiple = sqSectorDiagonal * GetCVars()->e_MergedMeshesLodRatio; // Note that this one takes the distance to the *visible* bounding box, not the sector box. For sectors that aren't completely // full, this gives us a slightly more accurate distance to the closest instance. If we used the sector box, partially full // sectors would seem like they're transitioning LODs sooner or later than you would visually expect. const float sqDistanceToBox = Distance::Point_AABBSq(camPos, m_visibleAABB); const uint32 lodFrequency[] = { (const uint32)GetCVars()->e_MergedMeshesUpdateRateLOD0, (const uint32)GetCVars()->e_MergedMeshesUpdateRateLOD1, (const uint32)GetCVars()->e_MergedMeshesUpdateRateLOD2, (const uint32)GetCVars()->e_MergedMeshesUpdateRateLOD3, (const uint32)GetCVars()->e_MergedMeshesUpdateRateLOD4, (const uint32)GetCVars()->e_MergedMeshesUpdateRateLOD5 }; // Handle the shadow pass if (!passInfo.IsGeneralPass()) { // If our distance to the visible bounding box < our "configured value", draw shadows. // More specifically, the "configured value" is effectively "sqrt(e_mergedMeshesInstanceDistShadows) * (sector size / 2) * sqrt(3)" if (sqDistanceToBox < sqSectorDiagonalInstanceShadowsMultiple) { float len = (passInfo.GetCamera().GetPosition() - m_pos).len(); if (GroupsCastShadows(RUT_STATIC)) { RenderRenderMesh(RUT_STATIC, len, passInfo, SRendItemSorter(EntDrawParams.rendItemSorter)); } if (GroupsCastShadows(RUT_DYNAMIC)) { RenderRenderMesh(RUT_DYNAMIC, len, passInfo, SRendItemSorter(EntDrawParams.rendItemSorter)); } } return; } m_rendParams = EntDrawParams; bool switched = false; // If our distance to the visible bounding box < our "configured value", and we have spine data, switch to dynamic // instances. Dynamic instances means that we do per-instance LOD selection, instead of per-sector, because we're rendering them // as separate instances. // In this case, the "configured value" is effectively "sqrt(e_MergedMeshesInstanceDist) * (sector size / 2) * sqrt(3)" // NOTE: e_MergedMeshesInstanceDist affects both when to switch to dynamic, and when to refresh the mesh based on distance moved. if ((sqDistanceToBox < sqSectorDiagonalInstanceDistMultiple) && m_SpinesInitialized && m_SpinesExist) { switched = m_RenderMode != DYNAMIC; m_RenderMode = DYNAMIC; // If we've switched render types, clear out our previous LOD values to ensure the meshes refresh. if (switched) { m_nLod[RUT_STATIC] = m_nLod[RUT_DYNAMIC] = s_lodNotSet; } } else { switched = m_RenderMode != INSTANCED; m_RenderMode = INSTANCED; // If we've switched render types, clear out our previous LOD values to ensure the meshes refresh. if (switched) { m_nLod[RUT_STATIC] = m_nLod[RUT_DYNAMIC] = s_lodNotSet; } if ((m_nLod[RUT_STATIC] != s_lodNotSet) || (m_nLod[RUT_DYNAMIC] != s_lodNotSet)) { const float lastDistanceToBox = Distance::Point_AABBSq(m_cameraPosAtLastUpdate, m_visibleAABB); // Once the camera has moved "far enough" from our last mesh update, clear out our previous LOD values to // ensure the meshes refresh. This is necessary because even if we keep the same LOD, we still might cull // different subsets of instances within the sector as the camera moves. // "far enough" is effectively "sqrt(e_MergedMeshesInstanceDist) * (sector size / 2) * sqrt(3) * (1/2)" // NOTE: e_MergedMeshesInstanceDist affects both when to switch to dynamic, and when to refresh the mesh based on distance moved. if (abs(lastDistanceToBox - sqDistanceToBox) > (sqSectorDiagonalInstanceDistMultiple * 0.25f)) { m_nLod[RUT_STATIC] = m_nLod[RUT_DYNAMIC] = s_lodNotSet; } } m_nColliders = 0; } // The LOD is selected based on distance as a multiple of the "configured value". In this case, because we're using a ratio // instead of a straight comparison check, we're actually working in squared distance space. In practical terms, here's what // it means for a sector size of 16 meters and e_MergedMeshesLodRatio = 1: // sqSectorDiagonalLodMultiple = X^2 * (3/4) * 1 = 16 * 16 * (3/4) * 1 = 192 // (The following will show actual distance, not squared distance, which is why we're taking square roots) // LOD 0: sqrt(0*192) to sqrt(1*192) m = 0 - 13.8 m // LOD 1: sqrt(1*192) to sqrt(2*192) m = 13.8 - 19.6 m // LOD 2: sqrt(2*192) to sqrt(3*192) m = 19.6 - 24.0 m // LOD 3: sqrt(3*192) to sqrt(4*192) m = 24.0 - 27.7 m // LOD 4: sqrt(4*192) to sqrt(5*192) m = 27.7 - 31.0 m // LOD 5: sqrt(5*192) to sqrt(6*192) m = 31.0 - 33.9 m // As you can see, the linear distance range for each LOD band is a different size. // The LOD ratio would be multiplied inside the sqrt term as well, so an LOD ratio of 4 would double the values above. // NOTE: Be aware that e_MergedMeshesLodRatio appears in the denominator in the sector LOD calculation, // but the merged mesh group's lodRatioSq is in the numerator for the LOD calculation in MergedMeshRenderNode::CullInstanceList. const int nLod = (int)min(max(sqDistanceToBox / (max(sqSectorDiagonalLodMultiple, 0.001f)), 0.f), ((float)MAX_STATOBJ_LODS_NUM - 1.f)); m_LastDrawFrame = frameId; bool requiresPostRender = false; switch (m_State) { case INITIALIZED: break; case DIRTY: DeleteRenderMesh(RUT_DYNAMIC); m_renderMeshes[RUT_DYNAMIC].clear(); DeleteRenderMesh(RUT_STATIC); m_renderMeshes[RUT_STATIC].clear(); m_nLod[RUT_STATIC] = m_nLod[RUT_DYNAMIC] = s_lodNotSet; m_State = PREPARING; case PREPARING: // We can't do anything if the prepping stage is still running if (PrepareRenderMesh(RUT_STATIC) == false || PrepareRenderMesh(RUT_DYNAMIC) == false) { break; } m_State = PREPARED; break; case STREAMED_IN: for (signed pass = 0; pass < 2; ++pass) { switch (m_RenderMode) { case DYNAMIC: if (pass == RUT_STATIC) { goto static_fallthrough; } if (!(s_mmrm_globals.dt > 0.f)) { RenderRenderMesh( RUT_DYNAMIC , (passInfo.GetCamera().GetPosition() - m_pos).len() , passInfo , SRendItemSorter(EntDrawParams.rendItemSorter)); } else { bool dispatched = false; DeleteRenderMesh(RUT_DYNAMIC); for (i = 0; i < m_nGroups; ++i) { if (m_groups[i].numSamples == 0u || m_groups[i].specMismatch || !m_groups[i].is_dynamic) { continue; } int flags = (int)MMRM_CULL_FRUSTUM | MMRM_CULL_DISTANCE; mmrm_printf("starting culljob on %#x header (%#x instance %#x numsamples)\n" , (unsigned int)&m_groups[i] , (unsigned int)m_groups[i].instances , (unsigned int)m_groups[i].numSamples); m_cullJobExecutor.StartJob( [this, i, flags]() { m_groups[i].CullInstances(&s_mmrm_globals.camera, &m_internalAABB.min, &m_pos, m_zRotation, flags); } ); dispatched = true; } if (dispatched) { m_nLod[RUT_DYNAMIC] = nLod; m_cameraPosAtLastUpdate = passInfo.GetCamera().GetPosition(); m_needsDynamicMeshUpdate = true; m_needsPostRenderDynamic = true; requiresPostRender = true; } } break; static_fallthrough: case INSTANCED: if (nLod != m_nLod[pass] || m_needsStaticMeshUpdate || ((pass == RUT_DYNAMIC && (lodFrequency[nLod] != 0 && frameId - m_LastUpdateFrame > lodFrequency[nLod])) && s_mmrm_globals.dt > 0.f)) { bool dispatched = false; DeleteRenderMesh((RENDERMESH_UPDATE_TYPE)pass); for (i = 0; i < m_nGroups; ++i) { if (m_groups[i].numSamples == 0u || m_groups[i].specMismatch || (pass != (signed)m_groups[i].is_dynamic)) { continue; } int flags = (int)(nLod << MMRM_LOD_SHIFT) | MMRM_CULL_LOD | MMRM_CULL_DISTANCE; m_cullJobExecutor.StartJob( [this, i, flags]() { m_groups[i].CullInstances(&s_mmrm_globals.camera, &m_internalAABB.min, &m_pos, m_zRotation, flags); // job.SetPriorityLevel(JobManager::eHighPriority); } ); dispatched = true; } if (dispatched) { m_nLod[pass] = nLod; m_cameraPosAtLastUpdate = passInfo.GetCamera().GetPosition(); switch (pass) { case RUT_STATIC: m_needsStaticMeshUpdate = true; m_needsPostRenderStatic = true; break; case RUT_DYNAMIC: m_needsDynamicMeshUpdate = true; m_needsPostRenderDynamic = true; break; } requiresPostRender = true; } break; } RenderRenderMesh( (RENDERMESH_UPDATE_TYPE)pass , (passInfo.GetCamera().GetPosition() - m_pos).len() , passInfo , SRendItemSorter(EntDrawParams.rendItemSorter)); break; } } break; case STREAMING: case PREPARED: break; case RENDERNODE_STATE_ERROR: CryLogAlways("MergedMeshRendernode is in an Error state\n"); default: //__debugbreak(); break; } if (requiresPostRender) { Cry3DEngineBase::m_pMergedMeshesManager->RegisterForPostRender(this); } } bool CMergedMeshRenderNode::GroupsCastShadows(RENDERMESH_UPDATE_TYPE type) { for (size_t i = 0; i < m_nGroups; ++i) { if (m_groups[i].numSamples == 0u || m_groups[i].specMismatch || (type != (signed)m_groups[i].is_dynamic)) { continue; } const StatInstGroup& srcGroup = GetStatObjGroup(m_groups[i].instGroupId); if (srcGroup.nCastShadowMinSpec <= GetCVars()->e_ObjShadowCastSpec) { return true; } } return false; } void CMergedMeshRenderNode::RenderRenderMesh( RENDERMESH_UPDATE_TYPE type , const float distance , const SRenderingPassInfo& passInfo , const SRendItemSorter& rendItemSorter) { std::vector& renderMeshes = m_renderMeshes[type]; SSectorTextureSet* pTerrainTexInfo = NULL; byte ucSunDotTerrain = 255; const float fRenderQuality = min(1.f, max(1.f - (distance* passInfo.GetZoomFactor()) / (GetCVars()->e_MergedMeshesActiveDist* 0.3333f), 0.f)); bool bUseTerrainColor = UsesTerrainColor(); if (GetCVars()->e_VegetationUseTerrainColor && bUseTerrainColor) { const Vec3 bboxCenter = GetBBox().GetCenter(); AZ::Vector3 terrainNormal = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainNormal(); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainNormal , &AzFramework::Terrain::TerrainDataRequests::GetNormalFromFloats , bboxCenter.x, bboxCenter.y , AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, nullptr); Vec3 vTerrainNormal = AZVec3ToLYVec3(terrainNormal); ucSunDotTerrain = (uint8)(CLAMP((vTerrainNormal.Dot(Get3DEngine()->GetSunDirNormalized()))* 255.0f, 0, 255)); GetObjManager()->FillTerrainTexInfo(m_pOcNode, distance, pTerrainTexInfo, GetBBox()); } for (size_t i = 0; i < renderMeshes.size(); ++i) { SMMRM& mesh = renderMeshes[i]; for (size_t j = 0; j < mesh.rms.size(); ++j) { IRenderMesh* rm = mesh.rms[j]; IF (!rm, 0) { continue; } CRenderObject* ro = GetRenderer()->EF_GetObject_Temp(passInfo.ThreadID()); IF (!ro, 0) { continue; // EF_GetObject_Temp should always return a valid pointer (it creates a new object if it can't find an existing one) so this shouldn't be necessary. } ro->m_II.m_AmbColor = m_rendParams.AmbientColor; ro->m_II.m_Matrix.SetTranslationMat(m_pos); ro->m_fAlpha = m_rendParams.fAlpha; ro->m_ObjFlags = FOB_PARTICLE_SHADOWS | FOB_DYNAMIC_OBJECT; if (pTerrainTexInfo) { ro->m_II.m_AmbColor.a = clamp_tpl((1.0f / 255.f* ucSunDotTerrain), 0.f, 1.f); ro->m_ObjFlags |= FOB_BLEND_WITH_TERRAIN_COLOR; ro->m_nTextureID = pTerrainTexInfo->nTex0; if (SRenderObjData* pOD = GetRenderer()->EF_GetObjData(ro, true, passInfo.ThreadID())) { pOD->m_fTempVars[3] = pTerrainTexInfo->fTexOffsetX; pOD->m_fTempVars[4] = pTerrainTexInfo->fTexOffsetY; pOD->m_fTempVars[5] = pTerrainTexInfo->fTexScale; pOD->m_fTempVars[8] = m_fWSMaxViewDist* 0.3333f; // 0.333f => replicating magic ratio from fRenderQuality } } ro->m_fSort = m_rendParams.fCustomSortOffset; ro->m_nRenderQuality = (uint16)(fRenderQuality* 65535.0f); ro->m_fDistance = distance; ro->m_pCurrMaterial = mesh.mat; if (Get3DEngine()->IsTessellationAllowed(ro, passInfo)) { ro->m_ObjFlags |= FOB_ALLOW_TESSELLATION; } rm->Render(ro, passInfo, rendItemSorter); } } } bool CMergedMeshRenderNode::PostRender(const SRenderingPassInfo& passInfo) { FUNCTION_PROFILER_3DENGINE; const uint32 frameId = passInfo.GetMainFrameID(); // Sadly because of the interleaved renderchunks structure, we can // only start building a mesh when all culling tasks have completed. IF(m_cullJobExecutor.IsRunning(), 1) { return true; } const float distance = (s_mmrm_globals.camera.GetPosition() - m_pos).len(); if (m_needsStaticMeshUpdate) { FRAME_PROFILER_FAST("MMRM PR CR static", gEnv->pSystem, PROFILE_3DENGINE, true); CreateRenderMesh(RUT_STATIC, passInfo); m_needsStaticMeshUpdate = false; } if (m_needsDynamicMeshUpdate) { FRAME_PROFILER_FAST("MMRM PR CR dynamic", gEnv->pSystem, PROFILE_3DENGINE, true); CreateRenderMesh(RUT_DYNAMIC, passInfo); m_needsDynamicMeshUpdate = false; } SRendItemSorter rendItemSorter = SRendItemSorter::CreateRendItemSorter(passInfo); if (m_needsPostRenderStatic) { FRAME_PROFILER_FAST("MMRM PR RR static", gEnv->pSystem, PROFILE_3DENGINE, true); RenderRenderMesh(RUT_STATIC, distance, passInfo, rendItemSorter); m_needsPostRenderStatic = false; } if (m_needsPostRenderDynamic) { FRAME_PROFILER_FAST("MMRM PR RR dynamic", gEnv->pSystem, PROFILE_3DENGINE, true); RenderRenderMesh(RUT_DYNAMIC, distance, passInfo, rendItemSorter); m_needsPostRenderDynamic = false; } return false; } bool CMergedMeshRenderNode::Compile(byte* pData, int& nSize, string* pName, std::vector* pVegGroupTable) { // Construct the unique id for the sector const float fExtentsRec = 1.0f / c_MergedMeshesExtent; if (pData) { char token[256]; int _i = (int)floorf(m_pos.x * fExtentsRec); int _j = (int)floorf(m_pos.y * fExtentsRec); int _k = (int)floorf(m_pos.z * fExtentsRec); sprintf_s(token, "sector_%d_%d_%d", _i, _j, _k); *pName = token; Vec3 extents = (m_visibleAABB.max - m_visibleAABB.min)* 0.5f; Vec3 origin = m_pos - extents; uint8* pPtr = pData; // Compile the headers and instances for this merged mesh instance for (size_t i = 0; i < NumGroups(); ++i) { const SMMRMGroupHeader* header = Group(i); StatInstGroup& instGroup = GetStatObjGroup(header->instGroupId); int nItemId = CObjManager::GetItemId(pVegGroupTable, &instGroup); if (nItemId < 0) { assert(!"StatObj not found in StatInstGroupTable on exporting"); } SMergedMeshSectorChunk sectorChunk; sectorChunk.m_StatInstGroupID = nItemId; sectorChunk.m_nSamples = header->numSamples; sectorChunk.i = (uint32)floorf(m_pos.x * fExtentsRec); sectorChunk.j = (uint32)floorf(m_pos.y * fExtentsRec); sectorChunk.k = (uint32)floorf(m_pos.z * fExtentsRec); sectorChunk.ver = c_MergedMeshChunkVersion; AddToPtr(pPtr, sectorChunk, GetPlatformEndian()); for (size_t j = 0; j < header->numSamples; ++j) { SMergedMeshInstanceCompressed sampleChunk; sampleChunk.pos_x = header->instances[j].pos_x; sampleChunk.pos_y = header->instances[j].pos_y; sampleChunk.pos_z = header->instances[j].pos_z; sampleChunk.rot[0] = header->instances[j].qx; sampleChunk.rot[1] = header->instances[j].qy; sampleChunk.rot[2] = header->instances[j].qz; sampleChunk.rot[3] = header->instances[j].qw; sampleChunk.scale = header->instances[j].scale; AddToPtr(pPtr, sampleChunk, GetPlatformEndian()); } } } else { for (size_t i = 0; i < NumGroups(); ++i) { nSize += sizeof(SMergedMeshSectorChunk); nSize += Group(i)->numSamples* sizeof(SMergedMeshInstanceCompressed); } } return true; } void CMergedMeshRenderNode::FillSamples(DynArray& samples) { // Compile the headers and instances for this merged mesh instance const float fExtents = c_MergedMeshesExtent; if (m_State != STREAMED_IN) { return; } for (size_t i = 0; i < NumGroups(); ++i) { const SMMRMGroupHeader* header = Group(i); const AABB& aabb = header->procGeom->aabb; const Vec3& centre = aabb.GetCenter(); const Vec3& size = aabb.GetSize(); samples.reserve(samples.size() + header->numSamples* 4); for (size_t j = 0; j < header->numSamples; ++j) { Vec3 pos = ConvertInstanceAbsolute(header->instances[j], m_internalAABB.min, m_pos, m_zRotation, fExtents); samples.push_back(Vec2(pos.x - size.x, pos.y - size.y)); samples.push_back(Vec2(pos.x - size.x, pos.y + size.y)); samples.push_back(Vec2(pos.x + size.x, pos.y + size.y)); samples.push_back(Vec2(pos.x + size.x, pos.y - size.y)); } } } void CMergedMeshRenderNode::ActivateSpines() { if (m_spineInitializationJobExecutor.IsRunning() || m_updateJobExecutor.IsRunning()) { return; } if (m_State != STREAMED_IN || m_SpinesInitialized) { return; } for (size_t i = 0; i < NumGroups(); ++i) { CryPrefetch(m_groups[i].procGeom); } for (size_t i = 0; i < NumGroups(); ++i) { SMMRMGroupHeader* header = &m_groups[i]; if (header->specMismatch) { continue; } if (header->procGeom->numSpines) { pool_resize_list(header->spines, header->numSamples* header->procGeom->numSpineVtx, 16); } if (header->procGeom->deform) { pool_resize_list(header->deform_vertices, header->numSamples* header->procGeom->deform->nvertices, 16); } } m_spineInitializationJobExecutor.StartJob([this]() { this->InitializeSpines(); }); } void CMergedMeshRenderNode::RemoveSpines() { if (m_spineInitializationJobExecutor.IsRunning() || m_updateJobExecutor.IsRunning()) { return; } if (m_State != STREAMED_IN || !m_SpinesInitialized) { return; } for (size_t i = 0; i < NumGroups(); ++i) { SMMRMGroupHeader* header = & m_groups[i]; if (header->spines) { pool_free(header->spines); } if (header->deform_vertices) { pool_free(header->deform_vertices); } header->spines = NULL; header->deform_vertices = NULL; } m_SpinesInitialized = false; m_SpinesExist = false; } void CMergedMeshRenderNode::StreamAsyncOnComplete (IReadStream* pStream, unsigned nError) { if (m_State != STREAMING) { CryLogAlways("StreamAsyncOnComplete state check failed (%d) (THIS SHOULD NEVER HAPPEN)!\n", m_State); return; } if (!pStream) { CryLogAlways("StreamAsyncOnComplete stream is NULL\n"); return; } if (nError != 0u && (nError != ERROR_CANT_OPEN_FILE || !gEnv->IsDynamicMergedMeshGenerationEnabled())) { CryLogAlways("StreamAsyncOnComplete error check failed (%d)\n", nError); return; } const float fExtents = c_MergedMeshesExtent; const float fExtentsRec = 1.0f / c_MergedMeshesExtent; size_t stepcount = 0u; unsigned int nSize = pStream->GetBytesRead(); const uint8* pBuffer = reinterpret_cast(pStream->GetBuffer()); if (nSize == 0u || pBuffer == NULL) { if (!gEnv->IsDynamicMergedMeshGenerationEnabled()) { CryLogAlways("%s: zero sized block or null buffer?", __FUNCTION__); } return; } for (size_t i = 0; i < NumGroups(); ++i) { SMMRMGroupHeader* header = & m_groups[i]; if (header->splitGroup == false && stepcount) { pBuffer += sizeof(SMergedMeshInstanceCompressed) * stepcount; stepcount = 0; } if (header->splitGroup == false) { const SMergedMeshSectorChunk& sectorChunk = *StepData(pBuffer, eLittleEndian); if (!(sectorChunk.ver == c_MergedMeshChunkVersion)) { CryLogAlways("ERROR: outdated merged mesh chunk version found, please re-export level!"); m_State = RENDERNODE_STATE_ERROR; return; // Data may not be what is expected, so don't load to avoid potential crash. } # if MMRM_DEBUG { if (sectorChunk.m_StatInstGroupID != header->instGroupId) { __debugbreak(); } if (sectorChunk.i != (uint32)floorf(m_initPos.x * fExtentsRec)) { __debugbreak(); } if (sectorChunk.j != (uint32)floorf(m_initPos.y * fExtentsRec)) { __debugbreak(); } if (sectorChunk.k != (uint32)floorf(m_initPos.z * fExtentsRec)) { __debugbreak(); } } # endif } if (!header->specMismatch) { pool_resize_list(header->instances, header->numSamplesAlloc = header->numSamples, 16); } stepcount += header->numSamples; } const uint8* buffer = reinterpret_cast(pStream->GetBuffer()); // The ReadStream buffer isn't guaranteed to exist after this callback completes, so we // synchronously convert the data into instance data. InitializeSamplesFromBuffer(buffer); // We can defer any notifications / calculations based on the instance data to a job, since // those no longer rely on the read buffer AZ::Job* job = AZ::CreateJobFunction([this](){ this->FinishInitializingSamples(); }, true); job->Start(); // legacy: job.SetPriorityLevel(JobManager::eLowPriority); } void CMergedMeshRenderNode::PrintState(float& yPos) { int i, j, k; const float fExtentsRec = 1.0f / c_MergedMeshesExtent; i = (int)floorf(m_pos.x * fExtentsRec); j = (int)floorf(m_pos.y * fExtentsRec); k = (int)floorf(m_pos.z * fExtentsRec); Cry3DEngineBase::Get3DEngine()->DrawTextLeftAligned(10.f, yPos += 13.f, 1.f, Col_Red , "mmrm__%02d_%02d_%02d \t %5s \t %9.3f \t %11s \t %8.3f kb \t %8d \t\t\t %s" , (unsigned int)i, (unsigned int)j, (unsigned int)k , (Visible() ? "true" : "false") , DistanceSq() , ToString(m_State) , MetaSize() / 1024.f , m_LastDrawFrame , ToString(m_RenderMode)); } void CMergedMeshRenderNode::StreamOnComplete (IReadStream* pStream, unsigned nError) { if (!pStream) { CryLogAlways("StreamOnComplete stream is NULL\n"); return; } if (nError != 0u && (nError != ERROR_CANT_OPEN_FILE || !gEnv->IsDynamicMergedMeshGenerationEnabled())) { CryLogAlways("StreamOnComplete error check failed (%d)\n", nError); return; } if (m_pReadStream) { m_pReadStream->FreeTemporaryMemory(); m_pReadStream = NULL; } } bool CMergedMeshRenderNode::StreamIn() { int x = 0, y = 0; int i, j, k; char szFileName[_MAX_PATH]; const float fExtentsRec = 1.0f / c_MergedMeshesExtent; switch (m_State) { case PREPARED: if (!SupportsStreaming()) { // If we don't support streaming, just jump states to say we've finished streaming. // We should already have the correct instance data in place. m_State = STREAMED_IN; } else { i = (int)floorf(m_initPos.x * fExtentsRec); j = (int)floorf(m_initPos.y * fExtentsRec); k = (int)floorf(m_initPos.z * fExtentsRec); m_State = STREAMING; sprintf_s(szFileName, "%s%s\\sector_%d_%d_%d.dat", Get3DEngine()->GetLevelFolder(), COMPILED_MERGED_MESHES_BASE_NAME, i, j, k); m_pReadStream = GetSystem()->GetStreamEngine()->StartRead(eStreamTaskTypeMergedMesh, szFileName, this); } return true; default: break; } return false; } bool CMergedMeshRenderNode::StreamOut() { // If this node doesn't support streaming, don't do anything on StreamOut requests. if (!SupportsStreaming()) { return true; } switch (m_State) { case STREAMED_IN: if (!SyncAllJobs()) { return false; } DeleteRenderMesh(RUT_STATIC, true, true); DeleteRenderMesh(RUT_DYNAMIC, true, true); m_State = PREPARED; for (size_t i = 0; i < NumGroups(); ++i) { SMMRMGroupHeader* header = &m_groups[i]; if (header->instances) { pool_free(header->instances); } if (header->spines) { pool_free(header->spines); } if (header->deform_vertices) { pool_free(header->deform_vertices); } header->numSamplesAlloc = 0; header->spines = NULL; header->deform_vertices = NULL; header->instances = NULL; } if (m_wind) { CryModuleMemalignFree(m_wind); m_wind = NULL; } if (m_density) { CryModuleMemalignFree(m_density); m_density = NULL; } if (m_Colliders) { CryModuleMemalignFree(m_Colliders); m_Colliders = NULL; } m_SpinesInitialized = false; m_SpinesExist = false; return true; default: break; } return false; } void CMergedMeshRenderNode::DebugRender() { #if MMRM_RENDER_DEBUG const float fExtents = c_MergedMeshesExtent; const float fExtentsRec = 1.0f / c_MergedMeshesExtent; Vec3 extents = (m_visibleAABB.max - m_visibleAABB.min)* 0.5f; Vec3 origin = m_pos - extents; Quat q; IRenderAuxGeom* pAuxGeomRender = gEnv->pRenderer->GetIRenderAuxGeom(); const int mergedMeshesDebug = GetCVars()->e_MergedMeshesDebug; if (mergedMeshesDebug > 0) { if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::StressInfo)) { Matrix34 mat; mat.SetIdentity(); ColorF col; switch (m_State) { case STREAMED_IN: pAuxGeomRender->DrawAABB(m_internalAABB, mat, false, Col_Yellow, eBBD_Extremes_Color_Encoded); switch (m_RenderMode) { case DYNAMIC: pAuxGeomRender->DrawAABB(m_visibleAABB, mat, false, Col_Red, eBBD_Extremes_Color_Encoded); break; case INSTANCED: // For color-coding, we're choosing the largest LOD value between the two passes // because if a pass isn't being used, it should be getting set back to -1. int maxLod = AZ::GetMax(m_nLod[RUT_STATIC], m_nLod[RUT_DYNAMIC]); col.lerpFloat(Col_Red, Col_Green, static_cast(maxLod) / (float)MAX_STATOBJ_LODS_NUM); pAuxGeomRender->DrawAABB(m_visibleAABB, mat, false, Col_Green, eBBD_Extremes_Color_Encoded); break; } break; default: pAuxGeomRender->DrawAABB(m_internalAABB, mat, false, Col_Magenta, eBBD_Extremes_Color_Encoded); break; } } uint32 nInds = 0, nVerts = 0; for (size_t t = 0; t < 2; ++t) { for (size_t i = 0; i < m_renderMeshes[t].size(); ++i) { nInds += m_renderMeshes[t][i].indices; nVerts += m_renderMeshes[t][i].vertices; } } gEnv->pRenderer->DrawLabel(m_pos, 1.f, "(vb %04d ib %04d) (size %3.1f kb) (nLod %d / %d)" , nVerts , nInds , (m_SizeInVRam) / 1024.f , m_nLod[RUT_STATIC] , m_nLod[RUT_DYNAMIC] ); } if (m_State != STREAMED_IN) { return; } if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::InstanceLines)) { std::vector lines; std::vector indices; Matrix34 wmat; wmat.SetIdentity(); for (size_t i = 0; i < m_nGroups; ++i) { const SMMRMGroupHeader& header = m_groups[i]; for (size_t l = 0; l < header.numSamples; ++l) { Vec3 v; ConvertInstanceAbsolute(v, header.instances[l], m_internalAABB.min, m_pos, m_zRotation, fExtents); float scale = (1.f / VEGETATION_CONV_FACTOR)* header.instances[l].scale; indices.push_back(lines.size()); lines.push_back(v); indices.push_back(lines.size()); lines.push_back(v + Vec3(0, 0, scale)); } } if (!lines.empty() && !indices.empty()) { pAuxGeomRender->DrawLines( & lines[0], lines.size(), & indices[0], indices.size(), Col_Green, 1.f); } } if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::InstanceAABBs)) { for (size_t i = 0; i < m_nGroups; ++i) { const SMMRMGroupHeader& header = m_groups[i]; const AABB aabb = header.procGeom->srcObj->GetAABB(); for (size_t l = 0; l < header.numSamples; ++l) { Vec3 pos; ConvertInstanceAbsolute(pos, header.instances[l], m_internalAABB.min, m_pos, m_zRotation, fExtents); float scale = (1.f / VEGETATION_CONV_FACTOR)* header.instances[l].scale; ColorF color(Col_Red); if (header.instances[l].lastLod < 7) { static ColorF lodColors[] = { Col_LightBlue, Col_Blue, Col_LimeGreen, Col_Green, Col_Orange, Col_OrangeRed }; color = lodColors[header.instances[l].lastLod]; } pAuxGeomRender->DrawAABB(AABB(pos + (aabb.min * scale), pos + (aabb.max * scale)), false, color, eBBD_Extremes_Color_Encoded); } } } if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::SpinesFilter)) { std::vector lines0, lines1; std::vector indices0, indices1; for (size_t i = 0; i < m_nGroups; ++i) { const SMMRMGroupHeader& header = m_groups[i]; if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::SpinesAsLines)) { for (size_t l = 0; header.spines && l < header.numSamples; ++l) { const SMMRMInstance& sample = header.instances[l]; const float fScale = (1.f / VEGETATION_CONV_FACTOR)* sample.scale; DecompressQuat(q, sample); Matrix34 wmat = CreateRotationQ(q, ConvertInstanceAbsolute(sample, m_internalAABB.min, m_pos, m_zRotation, fExtents))* Matrix34::CreateScale(Vec3(fScale, fScale, fScale)); for (size_t j = 0, base = 0; j < header.procGeom->numSpines; ++j) { for (size_t k = 0; k < header.procGeom->pSpineInfo[j].nSpineVtx - 1; ++k) { indices0.push_back(lines0.size()); lines0.push_back(wmat* header.procGeom->pSpineVtx[base + k].pt); indices0.push_back(lines0.size()); lines0.push_back(wmat* header.procGeom->pSpineVtx[base + k + 1].pt); } base += header.procGeom->pSpineInfo[j].nSpineVtx; } } } if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::SimulatedSpines)) { for (size_t l = 0, sbase = 0; header.spines && l < header.numSamples; ++l) { const SMMRMInstance& sample = header.instances[l]; const float fScale = (1.f / VEGETATION_CONV_FACTOR)* sample.scale; DecompressQuat(q, sample); Matrix34 wmat = CreateRotationQ(q, ConvertInstanceAbsolute(sample, m_internalAABB.min, m_pos, m_zRotation, fExtents))* Matrix34::CreateScale(Vec3(fScale, fScale, fScale)); for (size_t j = 0, base = 0; j < header.procGeom->numSpines; ++j) { for (size_t k = 0; k < header.procGeom->pSpineInfo[j].nSpineVtx - 1; ++k) { # if MMRM_SIMULATION_USES_FP32 indices1.push_back(lines1.size()); lines1.push_back(header.spines[sbase + base + k].pt); indices1.push_back(lines1.size()); lines1.push_back(header.spines[sbase + base + k + 1].pt); indices1.push_back(lines1.size()); lines1.push_back(header.spines[sbase + base + k + 1].pt); indices1.push_back(lines1.size()); lines1.push_back(header.spines[sbase + base + k + 1].pt + header.spines[sbase + base + k + 1].vel); # else indices1.push_back(lines1.size()); lines1.push_back(wmat* header.spines[sbase + base + k].pt.ToVec3()); indices1.push_back(lines1.size()); lines1.push_back(wmat* header.spines[sbase + base + k + 1].pt.ToVec3()); indices1.push_back(lines1.size()); lines1.push_back(wmat* header.spines[sbase + base + k + 1].pt.ToVec3()); indices1.push_back(lines1.size()); lines1.push_back(wmat* header.spines[sbase + base + k + 1].pt.ToVec3() + header.spines[sbase + base + k + 1].vel.ToVec3()); # endif } base += header.procGeom->pSpineInfo[j].nSpineVtx; } sbase += header.procGeom->numSpineVtx; } } if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::SpinesLOD)) { for (size_t l = 0, sbase = 0; header.spines && l < header.numSamples; ++l) { const SMMRMInstance& sample = header.instances[l]; ColorB col; if (sample.lastLod == -1) { col = Col_Blue; } else { col.lerpFloat(Col_Red, Col_Green, (float)sample.lastLod / (float)MAX_STATOBJ_LODS_NUM); } for (size_t j = 0, base = 0; j < header.procGeom->numSpines; ++j) { for (size_t k = 0; k < header.procGeom->pSpineInfo[j].nSpineVtx - 1; ++k) { # if MMRM_SIMULATION_USES_FP32 pAuxGeomRender->DrawLine( header.spines[sbase + base + k].pt, col, header.spines[sbase + base + k + 1].pt, col, 1.f); # else pAuxGeomRender->DrawLine( header.spines[sbase + base + k].pt.ToVec3(), col, header.spines[sbase + base + k + 1].pt.ToVec3(), col, 1.f); # endif } base += header.procGeom->pSpineInfo[j].nSpineVtx; } sbase += header.procGeom->numSpineVtx; } } } if (!lines0.empty() && !indices0.empty()) { pAuxGeomRender->DrawLines( & lines0[0], lines0.size(), & indices0[0], indices0.size(), Col_Green, 1.f); } if (!lines1.empty() && !indices1.empty()) { pAuxGeomRender->DrawLines( & lines1[0], lines1.size(), & indices1[0], indices1.size(), Col_Red, 1.f); } } if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::CalculatedWind) && m_wind) { for (size_t x = 0; x < MMRM_WIND_DIM; ++x) { for (size_t y = 0; y < MMRM_WIND_DIM; ++y) { for (size_t z = 0; z < MMRM_WIND_DIM; ++z) { Vec3 pos, w = m_wind[x + y* MMRM_WIND_DIM + z* MMRM_WIND_DIM* MMRM_WIND_DIM].GetNormalized(), size = m_internalAABB.GetSize(); pos.x = m_internalAABB.min.x + (x / (MMRM_WIND_DIM - 1.f))* size.x; pos.y = m_internalAABB.min.y + (y / (MMRM_WIND_DIM - 1.f))* size.y; pos.z = m_internalAABB.min.z + (z / (MMRM_WIND_DIM - 1.f))* size.z; pAuxGeomRender->DrawLine(pos, Col_Blue, pos + w, Col_Blue, 4.f); pAuxGeomRender->DrawCone(pos + w, w, 0.1f, 0.25f, Col_LightBlue); } } } } # if MMRM_VISUALIZE_WINDFIELD VisualizeWindField(m_wind, m_internalAABB, true, true); # endif if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::InfluencingColliders) && m_nColliders) { SAuxGeomRenderFlags oldFlags = pAuxGeomRender->GetRenderFlags(), newFlags = oldFlags; newFlags.SetFillMode(e_FillModeWireframe); pAuxGeomRender->SetRenderFlags(newFlags); for (int i = 0; i < m_nColliders; ++i) { primitives::sphere& sphere = m_Colliders[i]; pAuxGeomRender->DrawSphere(sphere.center, sphere.r, Col_Pink, true); } pAuxGeomRender->SetRenderFlags(oldFlags); } #endif } const SMMRMGroupHeader* CMergedMeshRenderNode::Group(size_t index) const { return & m_groups[index]; } uint32 CMergedMeshRenderNode::NumGroups() const { return m_nGroups; } uint32 CMergedMeshRenderNode::MetaSize() const { uint32 size = 0u; for (size_t i = 0; i < m_nGroups; ++i) { size += sizeof(SMMRMGroupHeader); if (m_groups[i].specMismatch) { continue; } size += sizeof(SMMRMInstance)* m_groups[i].numSamples; if (m_groups[i].procGeom->numSpines) { size += sizeof(SMMRMSpineVtx)* m_groups[i].numSamples* m_groups[i].procGeom->numSpineVtx; } size += sizeof(SMMRMVisibleChunk)* m_groups[i].numVisbleChunks; } return size; } uint32 CMergedMeshRenderNode::VisibleInstances(uint32 frameId) const { if (m_LastDrawFrame != frameId) { return 0u; } uint32 count = 0u; for (size_t i = 0; i < m_nGroups; ++i) { count += m_groups[i].numSamplesVisible; } return count; } uint32 CMergedMeshRenderNode::SpineCount() const { const size_t unpreparedSpineCount = std::numeric_limits::max(); IF (gEnv->IsDynamicMergedMeshGenerationEnabled() || m_SpineCount == unpreparedSpineCount, 0) { uint32 count = 0u; bool all_prepared = true; for (size_t i = 0; i < m_nGroups; ++i) { all_prepared &= (m_groups[i].procGeom->state == SMMRMGeometry::PREPARED); if (!m_groups[i].specMismatch && m_groups[i].procGeom->numSpines) { count += m_groups[i].procGeom->numSpineVtx* m_groups[i].numSamples; } } IF (all_prepared, 1) { m_SpineCount = count; } return (count); } return m_SpineCount; } uint32 CMergedMeshRenderNode::DeformCount() const { const size_t unpreparedDeformCount = std::numeric_limits::max(); IF (gEnv->IsDynamicMergedMeshGenerationEnabled() || m_DeformCount == unpreparedDeformCount, 0) { uint32 count = 0u; bool all_prepared = true; for (size_t i = 0; i < m_nGroups; ++i) { all_prepared &= (m_groups[i].procGeom->state == SMMRMGeometry::PREPARED); if (!m_groups[i].specMismatch && m_groups[i].procGeom->deform) { count += m_groups[i].procGeom->numVtx; } } IF (all_prepared, 1) { m_DeformCount = count; } return count; } return m_DeformCount; } void CMergedMeshRenderNode::OverrideViewDistRatio(float value) { for (size_t i = 0; i < m_nGroups; ++i) { const StatInstGroup& instGroup = GetStatObjGroup(m_groups[i].instGroupId); m_groups[i].maxViewDistance = instGroup.fVegRadius * instGroup.fMaxViewDistRatio * value; } } void CMergedMeshRenderNode::OverrideLodRatio(float value) { for (size_t i = 0; i < m_nGroups; ++i) { const StatInstGroup& instGroup = GetStatObjGroup(m_groups[i].instGroupId); m_groups[i].lodRationNorm = instGroup.fLodDistRatio; } } bool CMergedMeshRenderNode::UpdateStreamableComponents(float fImportance, float fEntDistance, bool bFullUpdate) { IObjManager* pObjManager = GetObjManager(); if (pObjManager) { for (size_t i = 0; i < m_nGroups; ++i) { SMMRMGeometry* procGeom = m_groups[i].procGeom; if (procGeom && procGeom->srcMaterial && procGeom->srcObj) { pObjManager->PrecacheStatObjMaterial(procGeom->srcMaterial, fEntDistance, procGeom->srcObj, bFullUpdate, false); } } } return true; } bool CMergedMeshRenderNode::SyncAllJobs() { IF(m_cullJobExecutor.IsRunning(), 0) { return false; } IF(m_updateJobExecutor.IsRunning(), 0) { return false; } IF(m_spineInitializationJobExecutor.IsRunning(), 0) { return false; } return true; } Vec3 CMergedMeshRenderNode::GetSamplePos(size_t group, size_t sample) const { const SMMRMGroupHeader* header; if (group >= m_nGroups) { goto error; } header = & m_groups[group]; if (sample >= header->numSamples) { goto error; } return ConvertInstanceAbsolute(header->instances[sample], m_internalAABB.min, m_pos, m_zRotation, c_MergedMeshesExtent); error: __debugbreak(); return Vec3(-1, -1, -1); } AABB CMergedMeshRenderNode::GetSampleAABB(size_t group, size_t sample) const { AABB bb; float fScale; Quat q; const SMMRMGroupHeader* header; if (group >= m_nGroups) { goto error; } header = & m_groups[group]; if (sample >= header->numSamples) { goto error; } fScale = (1.f / VEGETATION_CONV_FACTOR)* header->instances[sample].scale; DecompressQuat(q, header->instances[sample]); bb = AABB::CreateTransformedAABB(CreateRotationQ(q, ConvertInstanceAbsolute(header->instances[sample], m_internalAABB.min, m_pos, m_zRotation, c_MergedMeshesExtent))* Matrix34::CreateScale(Vec3(fScale, fScale, fScale)), header->procGeom->aabb); return bb; error: __debugbreak(); return AABB(); } bool CMergedMeshRenderNode::Visible() const { return (s_mmrm_globals.frameId == m_LastDrawFrame); } void CMergedMeshRenderNode::Reset() { for (size_t i = 0; i < m_nGroups; ++i) { SMMRMGroupHeader* header = &m_groups[i]; if (header->procGeom) { SMMRMGeometry* procGeomOld = header->procGeom; header->procGeom->geomPrepareJobExecutor.WaitForCompletion(); StatInstGroup& instGroup = GetObjManager()->GetListStaticTypes()[DEFAULT_SID][header->instGroupId]; IStatObj* statObj = instGroup.GetStatObj(); if (statObj) { header->procGeom = s_GeomManager->GetGeometry(header->instGroupId); header->procGeom->geomPrepareJobExecutor.WaitForCompletion(); s_GeomManager->ReleaseGeometry(procGeomOld); } } } m_cullJobExecutor.WaitForCompletion(); m_updateJobExecutor.WaitForCompletion(); DeleteRenderMesh(RUT_STATIC); DeleteRenderMesh(RUT_DYNAMIC); m_renderMeshes[RUT_STATIC].clear(); m_renderMeshes[RUT_DYNAMIC].clear(); m_State = DIRTY; } CMergedMeshInstanceProxy::CMergedMeshInstanceProxy() : IRenderNode() , m_host(nullptr) , m_headerIndex(0) , m_sampleIndex(0) { } CMergedMeshInstanceProxy::CMergedMeshInstanceProxy(CMergedMeshRenderNode* host, size_t header, size_t index) : IRenderNode() , m_host(host) , m_headerIndex(header) , m_sampleIndex(index) { } CMergedMeshInstanceProxy::~CMergedMeshInstanceProxy() { if (m_host && m_host->RemoveInstance(m_headerIndex, m_sampleIndex) == 0) { m_host->ReleaseNode(); } } CMergedMeshesManager::CMergedMeshesManager() : m_ProjectileLock() , m_CurrentSizeInVramDynamic() , m_CurrentSizeInVramInstanced() , m_CurrentSizeInMainMem() , m_GeomSizeInMainMem() , m_InstanceCount() , m_VisibleInstances() , m_InstanceSize() , m_SpineSize() , m_nActiveNodes() , m_PoolOverFlow() , m_MeshListPresent() { memset(m_CachedBBs, 0, sizeof(m_CachedBBs)); memset(m_CachedLRU, 0, sizeof(m_CachedLRU)); memset(m_CachedNodes, 0, sizeof(m_CachedNodes)); gEnv->SetDynamicMergedMeshGenerationEnabled(gEnv->IsEditor()); } CMergedMeshesManager::~CMergedMeshesManager() { } void CMergedMeshesManager::Init() { s_GeomManager->Initialize(); if (ICVar* pVar = gEnv->pConsole->GetCVar("e_MergedMeshesLodRatio")) { pVar->SetOnChangeCallback(UpdateRatios); } if (ICVar* pVar = gEnv->pConsole->GetCVar("e_MergedMeshesViewDistRatio")) { pVar->SetOnChangeCallback(UpdateRatios); } if (!s_MergedMeshPool) { AUTO_LOCK(s_MergedMeshPoolLock); size_t memsize = (Cry3DEngineBase::GetCVars()->e_MergedMeshesPool + 4096)* 1024; // include 2mb buffer size for overhead and large peaks if (memsize && (s_MergedMeshPool = gEnv->pSystem->GetIMemoryManager()->CreateGeneralExpandingMemoryHeap( memsize, 0, "MERGEDMESH_POOL")) == NULL) { CryFatalError("could not allocate mergedmeshpoolheap"); } } // Preallocate to prevent reallocations m_ActiveNodes.reserve(8192); m_VisibleNodes.reserve(8192); #if ENABLE_CRY_PHYSICS if (gEnv->pPhysicalWorld) { gEnv->pPhysicalWorld->AddEventClient(EventPhysPostStep::id, CMergedMeshesManager::OnPhysPostStep, 0, 1.0f); } #else // Projectile postphys step CRY_PHYSICS_REPLACEMENT_ASSERT(); #endif // ENABLE_CRY_PHYSICS } void CMergedMeshesManager::Shutdown() { if (ICVar* pVar = gEnv->pConsole->GetCVar("e_MergedMeshesLodRatio")) { pVar->SetOnChangeCallback(NULL); } if (ICVar* pVar = gEnv->pConsole->GetCVar("e_MergedMeshesViewDistRatio")) { pVar->SetOnChangeCallback(NULL); } for (size_t i = 0; i < HashDimXY; ++i) { for (size_t j = 0; j < HashDimXY; ++j) { for (size_t k = 0; k < HashDimZ; ++k) { NodeListT& list = m_Nodes[i][j][k]; if (!list.empty()) { CryFatalError("Node list not empty. (THIS SHOULD NEVER HAPPEN)"); } stl::free_container(list); } } } s_GeomManager->Shutdown(); stl::free_container(m_ActiveNodes); stl::free_container(m_VisibleNodes); stl::free_container(m_Projectiles); stl::free_container(m_PostRenderNodes); if (s_MergedMeshPool) { AUTO_LOCK(s_MergedMeshPoolLock); s_MergedMeshPool->Cleanup(); } m_MeshListPresent = false; #if ENABLE_CRY_PHYSICS if (gEnv->pPhysicalWorld) { gEnv->pPhysicalWorld->RemoveEventClient(EventPhysPostStep::id, CMergedMeshesManager::OnPhysPostStep, 0); } #endif } void CMergedMeshesManager::UpdateViewDistRatio(float value) { for (size_t i = 0; i < HashDimXY; ++i) { for (size_t j = 0; j < HashDimXY; ++j) { for (size_t k = 0; k < HashDimZ; ++k) { NodeListT& list = m_Nodes[i][j][k]; for (NodeListT::iterator it = list.begin(); it != list.end(); ++it) { (* it)->OverrideViewDistRatio(value); } } } } } void CMergedMeshesManager::UpdateLodRatio(float value) { for (size_t i = 0; i < HashDimXY; ++i) { for (size_t j = 0; j < HashDimXY; ++j) { for (size_t k = 0; k < HashDimZ; ++k) { NodeListT& list = m_Nodes[i][j][k]; for (NodeListT::iterator it = list.begin(); it != list.end(); ++it) { (* it)->OverrideLodRatio(value); } } } } } bool CMergedMeshesManager::CompileSectors(DynArray& sectors, std::vector* pVegGroupTable) { int nSize = 0; for (size_t i = 0; i < HashDimXY; ++i) { for (size_t j = 0; j < HashDimXY; ++j) { for (size_t k = 0; k < HashDimZ; ++k) { NodeListT& list = m_Nodes[i][j][k]; for (NodeListT::iterator it = list.begin(); it != list.end(); ++it) { nSize = 0; SInstanceSector* pSector = sectors.grow(1); (* it)->Compile(NULL, nSize, NULL, NULL); pSector->data.grow(nSize); if ((* it)->Compile(& pSector->data[0], nSize, & pSector->id, pVegGroupTable) == false) { return false; } } } } } return true; } bool CMergedMeshesManager::CompileAreas(DynArray& clusters, int flags) { const float fExtentsRec = 1.0f / c_MergedMeshesExtent; using ClusterMappingT = AZStd::unordered_map; ClusterMappingT mapping; NodeArrayT list, stack; CryLogAlways("MMRM: clustering ..."); // Extract a list of all merged mesh nodes for (size_t i = 0; i < HashDimXY; ++i) { for (size_t j = 0; j < HashDimXY; ++j) { for (size_t k = 0; k < HashDimZ; ++k) { NodeListT& _list = m_Nodes[i][j][k]; for (NodeListT::iterator it = _list.begin(); it != _list.end(); ++it) { list.push_back(* it); } } } } // No nodes means no areas - we are done if (list.size() == 0) { return true; } CryLogAlways("MMRM: processing %d nodes ...", (int)list.size()); // Pick a node from the list and create a cluster. Perform a breadth-first-search on every // node and collect all neighbours reachable from the starting node to the cluster. Mark // already visited nodes in a hash table to skip them efficiently. for (size_t c = list.size(); c >= 1; --c) { ClusterMappingT::iterator found = mapping.find(list[c - 1]); if (found != mapping.end()) { continue; } size_t cluster_index = clusters.size(); clusters.push_back(SMeshAreaCluster()); clusters[cluster_index].extents.Reset(); stack.push_back(list[c - 1]); while (stack.size()) { CMergedMeshRenderNode* node = stack.back(); stack.pop_back(); found = mapping.find(node); if (found != mapping.end()) { continue; } mapping.insert(AZStd::make_pair(node, cluster_index)); const AABB& box = node->GetInternalBBox(); const Vec3& centre = box.GetCenter(); int _i = (int)floorf(centre.x * fExtentsRec); int _j = (int)floorf(centre.y * fExtentsRec); int _k = (int)floorf(centre.z * fExtentsRec); // Note, as this is a wrapping grid, we need to weed non-neighbours for (int i = _i - 1; i <= _i + 1; ++i) { for (int j = _j - 1; j <= _j + 1; ++j) { for (int k = _k - 1; k <= _k + 1; ++k) { NodeListT& _list = m_Nodes[i& (HashDimXY - 1)][j& (HashDimXY - 1)][k& (HashDimZ - 1)]; for (NodeListT::iterator it = _list.begin(); it != _list.end(); ++it) { const AABB& nbox = (* it)->GetInternalBBox(); const Vec3& ncentre = nbox.GetCenter(); int ni = (int)floorf(ncentre.x * fExtentsRec); int nj = (int)floorf(ncentre.y * fExtentsRec); int nk = (int)floorf(ncentre.z * fExtentsRec); if (abs(ni - _i) <= 1 && abs(nj - _j) <= 1 && abs(nk - _k) <= 1) { stack.push_back(*(it)); } } } } } } } // Let the clusters process all the nodes that were found by them CryLogAlways("MMRM: creating %d clusters ...", (int)clusters.size()); for (ClusterMappingT::iterator it = mapping.begin(), end = mapping.end(); it != end; ++it) { CMergedMeshRenderNode* node = it->first; SMeshAreaCluster& cluster = clusters[it->second]; const AABB& nodeBB = node->GetInternalBBox(); cluster.extents.Add(nodeBB); if (flags& CLUSTER_BOUNDARY_FROM_SAMPLES) { node->FillSamples(cluster.boundary_points); } else { cluster.boundary_points.push_back(Vec2(nodeBB.min.x, nodeBB.min.y)); cluster.boundary_points.push_back(Vec2(nodeBB.max.x, nodeBB.min.y)); cluster.boundary_points.push_back(Vec2(nodeBB.max.x, nodeBB.max.y)); cluster.boundary_points.push_back(Vec2(nodeBB.min.x, nodeBB.max.y)); } } // Now calculate the boundary for each cluster CryLogAlways("MMRM: calculating boundaries ..."); for (size_t i = 0, end = clusters.size(); i < end; ++i) { if (flags& CLUSTER_CONVEXHULL_GRAHAMSCAN) { size_t boundaries = convexhull_graham_scan(clusters[i].boundary_points); CryLogAlways("MMRM: cluster %d has %d points on it's convex hull", (int)i, (int)boundaries); continue; } if (flags& CLUSTER_CONVEXHULL_GIFTWRAP) { size_t boundaries = convexhull_giftwrap(clusters[i].boundary_points); CryLogAlways("MMRM: cluster %d has %d points on it's convex hull", (int)i, (int)boundaries); continue; } CryFatalError( "MMRM: no clustering algorithm selected, please provide one of the following:\n" "\tCLUSTER_CONVEXHULL_GRAHAMSCAN\n" "\tCLUSTER_CONVEXHULL_GIFTWRAP\n"); } #if MMRM_CLUSTER_VISUALIZATION if (Cry3DEngineBase::GetCVars()->e_MergedMeshesClusterVisualization > 0) { m_clusters = clusters; } else { m_clusters.clear(); } #endif // MMRM_CLUSTER_VISUALIZATION CryLogAlways("MMRM: done ..."); return true; } CMergedMeshRenderNode* CMergedMeshesManager::FindNode(const Vec3& pos) { for (size_t i = 0; i < 4; ++i) { if (m_CachedBBs[i].IsContainPoint(pos)) { return m_CachedNodes[i]; } else { ++m_CachedLRU[i]; } } const float fExtentsRec = 1.0f / c_MergedMeshesExtent; int i = (int)floorf(pos.x * fExtentsRec); int j = (int)floorf(pos.y * fExtentsRec); int k = (int)floorf(pos.z * fExtentsRec); NodeListT& _list = m_Nodes[i& (HashDimXY - 1)][j& (HashDimXY - 1) ][k& (HashDimZ - 1)]; for (NodeListT::iterator it = _list.begin(); it != _list.end(); ++it) { CMergedMeshRenderNode* node = * it; if (node->m_internalAABB.IsContainPoint(pos)) { int oldest = -INT_MAX, oldidx = 0; for (size_t idx = 0; idx < 4; ++idx) { if (m_CachedLRU[idx] > oldest) { oldidx = idx; oldest = m_CachedLRU[idx]; } } m_CachedNodes[oldidx] = node; m_CachedBBs[oldidx] = node->m_internalAABB; m_CachedLRU[oldidx] = 0; return node; } } return NULL; } void CMergedMeshesManager::CalculateDensity() { if (!gEnv->IsDynamicMergedMeshGenerationEnabled()) { return; } // Extract a list of all merged mesh nodes for (size_t i = 0; i < HashDimXY; ++i) { for (size_t j = 0; j < HashDimXY; ++j) { for (size_t k = 0; k < HashDimZ; ++k) { NodeListT& _list = m_Nodes[i][j][k]; for (NodeListT::iterator it = _list.begin(); it != _list.end(); ++it) { (* it)->CalculateDensity(); } } } } } size_t CMergedMeshesManager::QueryDensity(const Vec3& pos, ISurfaceType*(& surfaceTypes)[MMRM_MAX_SURFACE_TYPES], float (& density)[MMRM_MAX_SURFACE_TYPES]) { FUNCTION_PROFILER_3DENGINE; const float fExtentsRec = 1.0f / c_MergedMeshesExtent; int i = (int)(pos.x * fExtentsRec); int j = (int)(pos.y * fExtentsRec); int k = (int)(pos.z * fExtentsRec); NodeListT& _list = m_Nodes[i& (HashDimXY - 1)][j& (HashDimXY - 1) ][k& (HashDimZ - 1)]; for (NodeListT::iterator it = _list.begin(); it != _list.end(); ++it) { CMergedMeshRenderNode* node = * it; if (node->m_internalAABB.IsContainPoint(pos)) { return node->QueryDensity(pos, surfaceTypes, density); } } return 0; } bool CMergedMeshesManager::GetUsedMeshes(DynArray& meshNames) { s_GeomManager->GetUsedMeshes(meshNames); return true; } void CMergedMeshesManager::PreloadMeshes() { AZ::IO::HandleType fileHandle = gEnv->pCryPak->FOpen(Get3DEngine()->GetLevelFilePath(COMPILED_MERGED_MESHES_BASE_NAME COMPILED_MERGED_MESHES_LIST), "r"); if (fileHandle != AZ::IO::InvalidHandle) { gEnv->pCryPak->FSeek(fileHandle, 0, SEEK_END); size_t length = gEnv->pCryPak->FTell(fileHandle); gEnv->pCryPak->FSeek(fileHandle, 0, SEEK_SET); char* contents = new char[length + 1]; contents[length] = '\0'; gEnv->pCryPak->FReadRawAll(contents, length, fileHandle); char* end = contents + length; char* iter = contents, * current = contents; while (iter < end) { for (; * iter != '\n' && iter < end; ++iter) { ; } if (* iter == '\n' && iter != current) { * iter = '\0'; CryLog("MMRM loading cgf '%s'", current); Get3DEngine()->LoadStatObjUnsafeManualRef(current, NULL, NULL, false); } current = ++iter; } delete[] contents; gEnv->pCryPak->FClose(fileHandle); m_MeshListPresent = true; } } bool CMergedMeshesManager::SyncPreparationStep() { if (m_MeshListPresent) { return s_GeomManager->SyncPreparationStep(); } return true; } IRenderNode* CMergedMeshesManager::AddInstance(const SProcVegSample& sample, CMergedMeshRenderNode** ppNode, bool bRegister, bool dynamicInstance) { float fExtents = c_MergedMeshesExtent; const float fExtentsRec = 1.0f / c_MergedMeshesExtent; const Vec3& vPos = sample.pos; int i = (int)floorf(vPos.x * fExtentsRec); int j = (int)floorf(vPos.y * fExtentsRec); int k = (int)floorf(vPos.z * fExtentsRec); NodeListT& list = m_Nodes[i& (HashDimXY - 1)][j& (HashDimXY - 1)][k& (HashDimZ - 1)]; CMergedMeshRenderNode* node = NULL; for (NodeListT::iterator it = list.begin(); it != list.end(); ++it) { if ((* it)->GetInternalBBox().IsContainPoint(vPos)) { // Make sure we keep dynamic instances and static instances separate within a merged mesh render // node so that way we can correctly delete static render nodes when necessary. if ((*it)->HasDynamicInstances() == dynamicInstance) { node = *it; break; } } } if (!node) { Vec3 pos = Vec3(i * fExtents, j * fExtents, k * fExtents); Vec3 extents = Vec3(fExtents, fExtents, fExtents); list.push_back(node = new CMergedMeshRenderNode()); AABB box; box.max = pos + extents; box.min = pos; node->Setup(box, AABB(AABB::RESET), NULL); node->SetActive(1); m_ActiveNodes.push_back(node); } if (ppNode) { *ppNode = node; } return node->AddInstance(sample, bRegister); } CMergedMeshRenderNode* CMergedMeshesManager::GetNode(const Vec3& vPos) { const float fExtents = c_MergedMeshesExtent; const float fExtentsRec = 1.f / c_MergedMeshesExtent; const int i = (int)floorf(vPos.x * fExtentsRec); const int j = (int)floorf(vPos.y * fExtentsRec); const int k = (int)floorf(vPos.z * fExtentsRec); CMergedMeshRenderNode* node = NULL; NodeListT& list = m_Nodes[i & (HashDimXY - 1)][j & (HashDimXY - 1)][k & (HashDimZ - 1)]; for (NodeListT::iterator it = list.begin(); it != list.end(); ++it) { if ((*it)->GetInternalBBox().IsContainPoint(vPos)) { node = *it; break; } } if (!node) { list.push_back(node = new CMergedMeshRenderNode()); const Vec3 pos = Vec3(i * fExtents, j * fExtents, k * fExtents); const Vec3 extents = Vec3(fExtents, fExtents, fExtents); AABB box; box.max = pos + extents; box.min = pos; node->Setup(box, AABB(AABB::RESET), NULL); node->SetActive(1); m_ActiveNodes.push_back(node); } return node; } void CMergedMeshesManager::RemoveMergedMesh(CMergedMeshRenderNode* node) { const float fExtentsRec = 1.0f / c_MergedMeshesExtent; const Vec3& vPos = node->m_pos; const int i = (int)floorf(vPos.x * fExtentsRec); const int j = (int)floorf(vPos.y * fExtentsRec); const int k = (int)floorf(vPos.z * fExtentsRec); NodeListT& list = m_Nodes[i & (HashDimXY - 1)][j & (HashDimXY - 1)][k & (HashDimZ - 1)]; assert (std::find(list.begin(), list.end(), node) != list.end()); list.erase(std::remove(list.begin(), list.end(), node), list.end()); // Make sure it's deleted from the active list in the editor m_ActiveNodes.erase(std::remove(m_ActiveNodes.begin(), m_ActiveNodes.end(), node), m_ActiveNodes.end()); m_VisibleNodes.erase(std::remove(m_VisibleNodes.begin(), m_VisibleNodes.end(), node), m_VisibleNodes.end()); } void CMergedMeshesManager::SortActiveInstances(const SRenderingPassInfo& passInfo) { #if AZ_RENDER_TO_TEXTURE_GEM_ENABLED if (passInfo.IsRenderSceneToTexturePass()) { return; } #endif m_updateCompletionMergedMeshesManager.StartJob([this, passInfo]() { this->SortActiveInstances_Async(passInfo); }); // legacy: job.SetPriorityLevel(JobManager::eHighPriority); } void CMergedMeshesManager::Update(const SRenderingPassInfo& passInfo) { FUNCTION_PROFILER(GetISystem(), PROFILE_3DENGINE); AZ_TRACE_METHOD(); CRYPROFILE_SCOPE_PROFILE_MARKER("MMRMGR: update") #if AZ_RENDER_TO_TEXTURE_GEM_ENABLED if (passInfo.IsRenderSceneToTexturePass()) { return; } #endif if (GetCVars()->e_MergedMeshes == 0 || !passInfo.IsGeneralPass()) { return; } uint32 mainMemLimit = GetCVars()->e_MergedMeshesPool* 1024; const uint32 activeSpineLimit = (mainMemLimit* GetCVars()->e_MergedMeshesPoolSpines) / 100; mainMemLimit -= activeSpineLimit; const uint32 streamInLimit = min(mainMemLimit >> 2, 256u << 10); uint32 sumVramSizeDynamic = 0u , sumVramSizeInstanced = 0u , visibleInstances = 0u , nActiveInstances = 0u , streamedInSize = 0u , metaSize = 0u , streamRequests = 0u , maxStreamRequests = 16u; const int mergedMeshesDebug = GetCVars()->e_MergedMeshesDebug; const float dt = gEnv->pTimer->GetFrameTime(); const float sqDiagonalInternal = sqr(c_MergedMeshesExtent)* 1.25f; uint32 frameId = passInfo.GetMainFrameID(); # if !defined(_RELEASE) m_InstanceSize = 0; m_SpineSize = 0; # endif // Update registered particles { CRYPROFILE_SCOPE_PROFILE_MARKER("MMRMGR: update projectiles"); WriteLock _lock(m_ProjectileLock); size_t pi = 0, pe = m_Projectiles.size(); while (pi < pe) { SProjectile& p = m_Projectiles[pi]; if ((p.lifetime -= dt) <= 0.f) { std::swap(m_Projectiles[pi], m_Projectiles[pe - 1]); --pe; continue; } ++pi; } m_Projectiles.erase(m_Projectiles.begin() + pe, m_Projectiles.end()); } { CRYPROFILE_SCOPE_PROFILE_MARKER("MMRMGR: state jobsync"); // Maintain a sorted list of active instances - we have to wait here for the job to have completed m_updateCompletionMergedMeshesManager.WaitForCompletion(); } // Stream in instances up until main memory pool limit. For non-streamable nodes, just instantiate regardless of memory. { CRYPROFILE_SCOPE_PROFILE_MARKER("MMRMGR: streaming"); float yPos = 8.f; bool hadOverflow = m_PoolOverFlow; m_PoolOverFlow = false; size_t nActiveNodes = m_ActiveNodes.size(); size_t nVisibleNodes = m_VisibleNodes.size(); if(MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::PrintState)) { Cry3DEngineBase::Get3DEngine()->DrawTextLeftAligned(10.f, yPos += 13.f, 1.f, Col_White , "Sector(xyz)\tvisible\t\tDistanceSq\tState\t\t\tSize(kb)\t\t\tLast frame drawn\tRender Mode"); } for (size_t i = 0, activeSize = 0u; i < nActiveNodes; ++i) { CMergedMeshRenderNode* node = m_ActiveNodes[i]; metaSize += node->MetaSize(); #if MMRM_RENDER_DEBUG if (mergedMeshesDebug) { node->DebugRender(); } #endif #if !defined(_RELEASE) if (MergedMeshDebug::Show(mergedMeshesDebug, MergedMeshDebug::DisplayFlags::PrintState)) { node->PrintState(yPos); } #endif IF (node->m_LastDrawFrame != frameId, 1) { node->m_SizeInVRam = 0u; node->DeleteRenderMesh(CMergedMeshRenderNode::RUT_DYNAMIC); } IF ((frameId - node->m_LastDrawFrame) > 0x7ffff, 1) // arbitrary high number { node->m_SizeInVRam = 0u; node->DeleteRenderMesh(CMergedMeshRenderNode::RUT_STATIC); } IF (i + 1 < nActiveNodes, 1) { CryPrefetch(m_ActiveNodes[i + 1]); } if (node->m_State == CMergedMeshRenderNode::INITIALIZED || node->m_State == CMergedMeshRenderNode::DIRTY || node->m_State == CMergedMeshRenderNode::PREPARING) { continue; } #if !defined(_RELEASE) nActiveInstances += node->m_Instances; if (mergedMeshesDebug) { for (size_t j = 0; j < node->m_nGroups; ++j) { visibleInstances += node->m_groups[j].numSamplesVisible; } for (size_t j = 0; j < node->m_renderMeshes[0].size(); ++j) { for (size_t k = 0; k < node->m_renderMeshes[0][j].rms.size(); ++k) { sumVramSizeInstanced += node->m_renderMeshes[0][j].rms[k]->GetMemoryUsage(NULL, IRenderMesh::MEM_USAGE_ONLY_VIDEO); } } for (size_t j = 0; j < node->m_renderMeshes[1].size(); ++j) { for (size_t k = 0; k < node->m_renderMeshes[1][j].rms.size(); ++k) { sumVramSizeDynamic += node->m_renderMeshes[1][j].rms[k]->GetMemoryUsage(NULL, IRenderMesh::MEM_USAGE_ONLY_VIDEO); } } } #endif uint32 currentInstSize = node->m_Instances* sizeof(SMMRMInstance); if (currentInstSize == 0) // Safe to skip over empty nodes { continue; } uint32 currentSpineSize = node->SpineCount()* sizeof(SMMRMSpineVtxBase); uint32 currentDeformSize = node->DeformCount()* sizeof(SMMRMDeformVertex); if (node->StreamedIn()) { // If our node is streamed in and we're past the memory limit, try to stream it out. if (node->SupportsStreaming() && ((activeSize + currentInstSize) >= mainMemLimit)) { node->StreamOut(); } } else { // By default, try to stream the node in if it isn't streamed in yet. bool shouldStreamIn = true; // If it's a node that supports streaming, only stream it in if it falls within memory limits. if (node->SupportsStreaming()) { if (((activeSize + currentInstSize) >= mainMemLimit) || (streamedInSize >= streamInLimit) || (streamRequests >= maxStreamRequests)) { shouldStreamIn = false; } } if (shouldStreamIn) { if (node->StreamIn()) { streamedInSize += currentInstSize; ++streamRequests; } activeSize += currentInstSize; #if !defined(_RELEASE) m_InstanceSize += currentInstSize; m_SpineSize += currentSpineSize; #endif } } } for (size_t i = 0, activeSize = 0u; i < nVisibleNodes; ++i) { CMergedMeshRenderNode* node = m_VisibleNodes[i]; IF (i + 1 < nVisibleNodes, 1) { CryPrefetch(m_VisibleNodes[i + 1]); } if ((hadOverflow && node->m_LastDrawFrame != frameId) || node->m_State == CMergedMeshRenderNode::INITIALIZED || node->m_State == CMergedMeshRenderNode::DIRTY || node->m_State == CMergedMeshRenderNode::PREPARING) { if (node->SupportsStreaming()) { node->RemoveSpines(); } node->SetInVisibleSet(0); m_VisibleNodes.erase(m_VisibleNodes.begin() + i); --nVisibleNodes; continue; } uint32 currentSpineSize = node->SpineCount()* sizeof(SMMRMSpineVtxBase); uint32 currentDeformSize = node->DeformCount()* sizeof(SMMRMDeformVertex); if (activeSize + currentSpineSize + currentDeformSize < activeSpineLimit) { if (node->SupportsStreaming()) { node->ActivateSpines(); } activeSize += currentSpineSize + currentDeformSize; } else { if (node->SupportsStreaming()) { node->RemoveSpines(); } m_PoolOverFlow = true; } } } #if MMRM_CLUSTER_VISUALIZATION if (Cry3DEngineBase::GetCVars()->e_MergedMeshesClusterVisualization > 0) { for (int i = 0; i < m_clusters.size(); ++i) { IRenderAuxGeom* pAuxGeom = gEnv->pRenderer->GetIRenderAuxGeom(); SAuxGeomRenderFlags old_flags = pAuxGeom->GetRenderFlags(); SAuxGeomRenderFlags new_flags = old_flags; new_flags.SetCullMode(e_CullModeNone); new_flags.SetAlphaBlendMode(e_AlphaBlended); pAuxGeom->SetRenderFlags(new_flags); ColorF col0, col1; col0.r = 0.8f; col0.g = 0.1f; col0.b = 0.2f; col0.a = 0.55f; col1.r = 0.1f; col1.g = 0.9f; col1.b = 0.1f; col1.a = 0.55f; SMeshAreaCluster& cluster = m_clusters[i]; DynArray& points = cluster.boundary_points; size_t npoints = points.size(); for (size_t j = 1; j < npoints; ++j) { Vec3 v[4]; v[0] = Vec3(points[j - 1].x, points[j - 1].y, cluster.extents.min.z); v[1] = Vec3(points[j].x, points[j].y, cluster.extents.min.z); v[2] = Vec3(points[j].x, points[j].y, cluster.extents.max.z); v[3] = Vec3(points[j - 1].x, points[j - 1].y, cluster.extents.max.z); pAuxGeom->DrawTriangle(v[0], col0, v[1], col1, v[2], col0); pAuxGeom->DrawTriangle(v[2], col0, v[3], col1, v[0], col0); } Vec3 v[4]; v[0] = Vec3(points[npoints - 1].x, points[npoints - 1].y, cluster.extents.min.z); v[1] = Vec3(points[0].x, points[0].y, cluster.extents.min.z); v[2] = Vec3(points[0].x, points[0].y, cluster.extents.max.z); v[3] = Vec3(points[npoints - 1].x, points[npoints - 1].y, cluster.extents.max.z); pAuxGeom->DrawTriangle(v[0], col0, v[1], col1, v[2], col0); pAuxGeom->DrawTriangle(v[2], col0, v[3], col1, v[0], col0); pAuxGeom->SetRenderFlags(old_flags); } if (Cry3DEngineBase::GetCVars()->e_MergedMeshesClusterVisualization > 1 || CryGetAsyncKeyState(0x59)) { Vec3 cpos = gEnv->pRenderer->GetCamera().GetPosition(); float const fDim = static_cast(Cry3DEngineBase::GetCVars()->e_MergedMeshesClusterVisualizationDimension) * 0.5f; uint8 const nDim = static_cast(Cry3DEngineBase::GetCVars()->e_MergedMeshesClusterVisualizationDimension); DisplayDensity(this, AABB(cpos - Vec3(fDim, fDim, fDim), cpos + Vec3(fDim, fDim, fDim)), nDim); } } #endif // MMRM_CLUSTER_VISUALIZATION m_CurrentSizeInVramDynamic = sumVramSizeDynamic; m_CurrentSizeInVramInstanced = sumVramSizeInstanced; m_CurrentSizeInMainMem = metaSize; m_GeomSizeInMainMem = s_GeomManager->Size(); m_VisibleInstances = visibleInstances; m_nActiveNodes = (uint32)m_ActiveNodes.size(); m_InstanceCount = nActiveInstances; } void CMergedMeshesManager::AddProjectile(const SProjectile& projectile) { WriteLock _lock(m_ProjectileLock); // Skip already added entities and only add till end if (m_Projectiles.size() >= MaxProjectiles) { return; } m_Projectiles.push_back(projectile); } int CMergedMeshesManager::OnPhysPostStep(const EventPhys* event) { CMergedMeshesManager* _this = Cry3DEngineBase::m_pMergedMeshesManager; const EventPhysPostStep* poststep = static_cast(event); IPhysicalEntity* entity = poststep->pEntity; SProjectile projectile; pe_status_pos pos; pe_params_particle particles; if (entity->GetType() != PE_PARTICLE) { goto skip; } if (entity->GetStatus(& pos) == 0 || entity->GetParams(& particles) == 0) { goto skip; } if (particles.mass > 1.f || particles.size > 0.01f) { goto skip; } projectile.entity = entity; projectile.current_pos = pos.pos; projectile.initial_pos = pos.pos - particles.heading* particles.velocity* poststep->dt; projectile.direction = (projectile.current_pos - projectile.initial_pos).GetNormalized(); projectile.size = (pos.BBox[0] - pos.BBox[1]).len()* Cry3DEngineBase::GetCVars()->e_MergedMeshesBulletScale; //roughly a bullet I'ld say projectile.size = max(min(projectile.size, 8.5f), 1.f); projectile.lifetime = Cry3DEngineBase::GetCVars()->e_MergedMeshesBulletLifetime; _this->AddProjectile(projectile); skip: return 1; } void CMergedMeshesManager::RegisterForPostRender(CMergedMeshRenderNode* node) { assert(node); m_PostRenderNodes.push_back(node); } void CMergedMeshesManager::PostRenderMeshes(const SRenderingPassInfo& passInfo) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_3DENGINE); #if AZ_RENDER_TO_TEXTURE_GEM_ENABLED if (passInfo.IsRenderSceneToTexturePass()) { return; } #endif size_t num_nodes = m_PostRenderNodes.size(); for (size_t i = 0; i < num_nodes; ++i) { m_PostRenderNodes[i]->SampleWind(); } for (size_t i = 0; i < num_nodes; ++i) { m_PostRenderNodes[i]->QueryColliders(); } for (size_t i = 0; i < num_nodes; ++i) { m_PostRenderNodes[i]->QueryProjectiles(); } // Perform the post render do { bool done = true; for (size_t i = 0; i < m_PostRenderNodes.size(); ++i) { IF (m_PostRenderNodes[i] == NULL, 1) { continue; } if (m_PostRenderNodes[i]->PostRender(passInfo) == false) { m_PostRenderNodes[i] = NULL; continue; } done = false; } if (done) { m_PostRenderNodes.clear(); break; } } while (true); } void CMergedMeshesManager::ResetActiveNodes() { for (size_t i = 0; i < HashDimXY; ++i) { for (size_t j = 0; j < HashDimXY; ++j) { for (size_t k = 0; k < HashDimZ; ++k) { NodeListT& list = m_Nodes[i][j][k]; for (NodeListT::iterator it = list.begin(); it != list.end(); ++it) { (* it)->Reset(); } } } } } struct SDeformableData { SMMRMGroupHeader m_mmrmHeader; SMMRMUpdateContext m_mmrmContext; Matrix34 m_localTM; bool m_needsBaking; enum { CREATED = 0, PREPARED, READY } m_State; SDeformableData() : m_mmrmHeader() , m_mmrmContext() , m_localTM(Matrix34::CreateIdentity()) , m_State(CREATED) , m_needsBaking(true) {} ~SDeformableData() { } }; CDeformableNode::CDeformableNode() : m_pData() , m_nData() , m_wind() , m_nFrameId() , m_Colliders() , m_nColliders() , m_Projectiles() , m_nProjectiles() , m_pHeap() , m_numIndices() , m_numVertices() , m_renderMesh() , m_pStatObj() , m_all_prepared() { m_bbox = AABB(Vec3(0, 0, 0)); } CDeformableNode::~CDeformableNode() { ClearSimulationData(); ClearInstanceData(); } void CDeformableNode::ClearInstanceData() { m_cullCompletionDeformableNode.WaitForCompletion(); m_updateCompletionDeformableNode.WaitForCompletion(); for (size_t i = 0; i < m_nData; ++i) { m_pData[i]->m_mmrmHeader.procGeom->geomPrepareJobExecutor.WaitForCompletion(); if (m_pHeap && m_pHeap->UsableSize(m_pData[i]->m_mmrmHeader.deform_vertices)) { m_pHeap->Free(m_pData[i]->m_mmrmHeader.deform_vertices); m_pData[i]->m_mmrmHeader.deform_vertices = NULL; } else if (m_pData[i]->m_mmrmHeader.deform_vertices) { CryModuleMemalignFree(m_pData[i]->m_mmrmHeader.deform_vertices); m_pData[i]->m_mmrmHeader.deform_vertices = NULL; } delete m_pData[i]; } if (m_pData) { CryModuleMemalignFree(m_pData); m_pData = NULL; } if (m_Colliders) { CryModuleMemalignFree(m_Colliders); m_Colliders = NULL; } if (m_Projectiles) { CryModuleMemalignFree(m_Projectiles); m_Projectiles = NULL; } if (m_wind) { CryModuleMemalignFree(m_wind); m_wind = NULL; } m_bbox.Reset(); stl::free_container(m_renderChunks); m_numIndices = m_numVertices = 0; m_renderMesh = NULL; m_nData = 0u; m_nFrameId = 0u; m_nColliders = 0u; m_pHeap = NULL; m_all_prepared = false; } void CDeformableNode::ClearSimulationData() { for (size_t i = 0; i < m_nData; ++i) { m_pData[i]->m_mmrmHeader.procGeom->geomPrepareJobExecutor.WaitForCompletion(); if (m_pHeap && m_pHeap->UsableSize(m_pData[i]->m_mmrmHeader.deform_vertices)) { m_pHeap->Free(m_pData[i]->m_mmrmHeader.deform_vertices); m_pData[i]->m_mmrmHeader.deform_vertices = NULL; } else if (m_pData[i]->m_mmrmHeader.deform_vertices) { CryModuleMemalignFree(m_pData[i]->m_mmrmHeader.deform_vertices); m_pData[i]->m_mmrmHeader.deform_vertices = NULL; } m_pData[i]->m_State = SDeformableData::PREPARED; } m_bbox.Reset(); stl::free_container(m_renderChunks); m_numIndices = m_numVertices = 0; m_renderMesh = NULL; m_pHeap = NULL; m_all_prepared = false; } void CDeformableNode::CreateInstanceData(SDeformableData* pData, IStatObj* pStatObj) { SMMRMGroupHeader* header = & pData->m_mmrmHeader; header->procGeom = s_GeomManager->GetGeometry(pStatObj, pStatObj->GetMaterial(), MergedMesh::s_defaultGroupId); header->physConfig.Update(header->procGeom); header->instGroupId = MergedMesh::s_defaultGroupId; header->maxViewDistance = 0; header->lodRationNorm = 0; SMMRMUpdateContext* update = & pData->m_mmrmContext; update->group = header; pool_resize_list(header->instances, header->numSamplesAlloc = header->numSamples = 1, 128); header->instances[0].pos_x = 0u; header->instances[0].pos_y = 0u; header->instances[0].pos_z = 0u; CompressQuat(Quat::CreateIdentity(), header->instances[0]); header->instances[0].scale = (uint8)SATURATEB(1.f* VEGETATION_CONV_FACTOR); header->instances[0].lastLod = 0; pData->m_State = SDeformableData::PREPARED; } void CDeformableNode::SetStatObj(IStatObj* pStatObj) { ClearInstanceData(); if (pStatObj == NULL ) { return; } // Create deformable subobject is present //Can't use the IsDeformable() interface as it checks children as well. //A new interface should probably be added to the statobj at some point //but since the statobj needs to be pretty heavily refactored we can //just use the cheap 'hack' of casting to the concrete class //and checking the booleans directly. if (static_cast(pStatObj)->m_isDeformable) { resize_list(m_pData, m_nData = 1, 16); CreateInstanceData(m_pData[0] = new SDeformableData, pStatObj); } else { for (int i = 0; i < pStatObj->GetSubObjectCount(); ++i) { IStatObj::SSubObject* subObject = pStatObj->GetSubObject(i); if (!subObject) { continue; } if (IStatObj* pChild = static_cast(pStatObj->GetSubObject(i)->pStatObj)) { if (!static_cast(pChild)->m_isDeformable) { continue; } resize_list(m_pData, m_nData + 1, 16); m_pData[m_nData] = new SDeformableData; if (!subObject->bIdentityMatrix) { m_pData[m_nData]->m_localTM = subObject->tm; } CreateInstanceData(m_pData[m_nData++], pChild); } } } m_pStatObj = pStatObj; } void CDeformableNode::CreateDeformableSubObject(bool create, const Matrix34& worldTM, IGeneralMemoryHeap* pHeap) { ClearSimulationData(); if (create == false || Cry3DEngineBase::GetCVars()->e_MergedMeshes == 0) { return; } m_pHeap = pHeap; BakeDeform(worldTM); } void CDeformableNode::UpdateInternalDeform( SDeformableData* pData, CRenderObject* pRenderObject, const AABB& bbox , const SRenderingPassInfo& passInfo , _smart_ptr& rm , strided_pointer vtxBuf , strided_pointer tgtBuf , strided_pointer velocities , vtx_idx* idxBuf , size_t& iv , size_t& ii) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_3DENGINE); SMMRMGroupHeader* group = & pData->m_mmrmHeader; if (group == NULL) { return; } SMMRMGeometry* geom = group->procGeom; if (!geom) { return; } SMMRMUpdateContext* update = & pData->m_mmrmContext; SMMRMGroupHeader* header = & pData->m_mmrmHeader; if (pData->m_State == SDeformableData::READY) { FUNCTION_PROFILER_3DENGINE; Matrix34 worldTM = pRenderObject->GetMatrix(); // Create a new render mesh and dispatch the asynchronous updates size_t indices = group->procGeom->numIdx, vertices = group->procGeom->numVtx; update->general = (SVF_P3S_C4B_T2S*)vtxBuf.data; update->tangents = (SPipTangents*)tgtBuf.data; update->velocities = (Vec3*)velocities.data; update->idxBuf = idxBuf; update->updateFlag = rm->SetAsyncUpdateState(); update->colliders = m_Colliders; update->ncolliders = m_nColliders; update->projectiles = m_Projectiles; update->nprojectiles = m_nProjectiles; update->max_iter = 2; update->dt = gEnv->pTimer->GetFrameTime(); update->abstime = gEnv->pTimer->GetCurrTime(); update->_min = bbox.min; update->_max = bbox.max; update->wind = m_wind; update->use_spines = 0; update->chunks.resize(group->numVisbleChunks = group->procGeom->numChunks[0]); update->frame_count = passInfo.GetMainFrameID(); for (size_t j = 0; j < group->procGeom->numChunks[0]; ++j) { update->chunks[j].ioff = ii; update->chunks[j].voff = iv; update->chunks[j].matId = group->procGeom->pChunks[0][j].matId; ii += (update->chunks[j].icnt = group->procGeom->pChunks[0][j].nindices); iv += (update->chunks[j].vcnt = group->procGeom->pChunks[0][j].nvertices); } # if MMRM_USE_BOUNDS_CHECK for (size_t u = 0; u < update->chunks.size(); ++u) { if (((SVF_P3S_C4B_T2S*)vtxBuf.data) + update->chunks[u].voff >= (((SVF_P3S_C4B_T2S*)vtxBuf.data) + vertices)) { __debugbreak(); } if (((SVF_P3S_C4B_T2S*)vtxBuf.data) + update->chunks[u].voff + (update->chunks[u].vcnt - 1) >= (((SVF_P3S_C4B_T2S*)vtxBuf.data) + vertices)) { __debugbreak(); } } update->general_end = ((SVF_P3S_C4B_T2S*)vtxBuf.data) + vertices; update->tangents_end = ((SPipTangents*)tgtBuf.data) + vertices; update->idx_end = idxBuf + indices; # endif if (group->deform_vertices) { if (pData->m_needsBaking) { BakeInternal(pData, worldTM); } m_updateCompletionDeformableNode.StartJob([update](){ update->MergeInstanceMeshesDeform(&s_mmrm_globals.camera, 0u); }); //job.SetPriorityLevel(JobManager::eLowPriority); } } } void CDeformableNode::Render(const struct SRendParams& rParams, const SRenderingPassInfo& passInfo, const AABB& aabb) { //exiting the deformable rendering because the static rendering will render the object instead. if (rParams.bForceDrawStatic) { return; } CRenderObject* pObj = gEnv->pRenderer->EF_GetObject_Temp(passInfo.ThreadID()); CStatObj* statObj = static_cast(m_pStatObj); if (statObj) { statObj->FillRenderObject(rParams, rParams.pRenderNode, statObj->m_pMaterial, NULL, pObj, passInfo); RenderInternalDeform(gEnv->pRenderer->EF_DuplicateRO(pObj, passInfo), rParams.lodValue.LodA(), aabb, passInfo, SRendItemSorter(rParams.rendItemSorter)); } } void CDeformableNode::RenderInternalDeform( CRenderObject* pRenderObject, int nLod, const AABB& bbox , const SRenderingPassInfo& passInfo, const SRendItemSorter& rendItemSorter) { FUNCTION_PROFILER(gEnv->pSystem, PROFILE_3DENGINE); if (nLod > 0 || m_nData == 0 || Cry3DEngineBase::GetCVars()->e_MergedMeshes == 0) { return; } #if AZ_RENDER_TO_TEXTURE_GEM_ENABLED // RTT does not support merged meshes yet. if (passInfo.IsRenderSceneToTexturePass()) { return; } #endif float radius = max(bbox.max.x - bbox.min.x, max(bbox.max.y - bbox.min.y, bbox.max.z - bbox.min.z))* 0.5f; Vec3 centre = (bbox.max + bbox.min)* 0.5f; AABB radbox(centre, radius); if (passInfo.IsGeneralPass() && ((passInfo.GetCamera().GetPosition() - bbox.GetCenter()).len2() < sqr(((IRenderNode*)pRenderObject->m_pRenderNode)->GetMaxViewDist())* Cry3DEngineBase::GetCVars()->e_MergedMeshesDeformViewDistMod)) { { FRAME_PROFILER("CDeformableNode::RenderInternalDeform JobSync", gEnv->pSystem, PROFILE_3DENGINE); m_cullCompletionDeformableNode.WaitForCompletion(); m_updateCompletionDeformableNode.WaitForCompletion(); } m_nFrameId = passInfo.GetMainFrameID(); if (m_bbox.IsReset() == false) { Matrix34 worldTM = pRenderObject->GetMatrix(); AABB cbbox = AABB::CreateTransformedAABB(worldTM, m_bbox); QueryColliders(m_Colliders, m_nColliders, cbbox); QueryProjectiles(m_Projectiles, m_nProjectiles, cbbox); } SampleWind(m_wind, bbox); # if MMRM_RENDER_DEBUG && MMRM_VISUALIZE_WINDFIELD VisualizeWindField(m_wind, radbox, true, true); # endif bool update_chunks = m_all_prepared && !m_renderChunks.size(), all_prepared = true; for (size_t i = 0; i < m_nData; CryPrefetch(m_pData[i++])) { ; } const AZ::Vertex::Format vertexFormat = eVF_P3S_C4B_T2S; for (size_t i = 0; i < m_nData; ++i) { SDeformableData* pData = m_pData[i]; SMMRMGroupHeader* group = & pData->m_mmrmHeader; SMMRMGeometry* geom = group->procGeom; if (!geom) { return; } switch (pData->m_State) { case SDeformableData::PREPARED: if (geom->state != SMMRMGeometry::PREPARED) { all_prepared = false; break; // return or else problems with the preparing below } if (m_pHeap) { group->deform_vertices = (SMMRMDeformVertex*)m_pHeap->Memalign(16, sizeof(SMMRMDeformVertex)* group->numSamples* group->procGeom->deform->nvertices, "deformable vertices"); } if (!group->deform_vertices) { pool_resize_list(group->deform_vertices, group->numSamples* group->procGeom->deform->nvertices, 16); } memset(group->deform_vertices, 0x0, sizeof(SMMRMDeformVertex)* group->numSamples* group->procGeom->deform->nvertices); for (size_t k = 0; group->procGeom->deform && k < group->procGeom->deform->nvertices; ++k) { group->deform_vertices[k].pos[0] = group->deform_vertices[k].pos[1] = group->procGeom->deform->initial[k]; group->deform_vertices[k].vel = Vec3(0, 0, 0); } m_bbox.Add(group->procGeom->aabb.min); m_bbox.Add(group->procGeom->aabb.max); pData->m_State = SDeformableData::READY; case SDeformableData::READY: // fallthru from PREPARED IF (update_chunks, 0) { for (size_t j = 0; j < geom->numChunks[0]; ++j) { CRenderChunk chunk; chunk.m_nMatID = geom->pChunks[0][j].matId; chunk.nFirstIndexId = m_numIndices; chunk.nFirstVertId = m_numVertices; chunk.nNumIndices = geom->pChunks[0][j].nindices; chunk.nNumVerts = geom->pChunks[0][j].nvertices; chunk.m_vertexFormat = vertexFormat; m_renderChunks.push_back(chunk); m_numVertices += geom->pChunks[0][j].nvertices; m_numIndices += geom->pChunks[0][j].nindices; } } break; default: all_prepared = false; break; } } if (!(m_all_prepared = all_prepared) || m_numVertices + m_numIndices == 0) { return; } m_renderMesh = gEnv->pRenderer->CreateRenderMeshInitialized( NULL, m_numVertices, vertexFormat, NULL, m_numIndices, prtTriangleList, "MergedMesh", "MergedMesh", eRMT_Dynamic); m_renderMesh->LockForThreadAccess(); strided_pointer tgtBuf; strided_pointer vtxBuf; strided_pointer velocities; vtx_idx* idxBuf = NULL; vtxBuf.data = (SVF_P3S_C4B_T2S*)m_renderMesh->GetPosPtrNoCache(vtxBuf.iStride, FSL_CREATE_MODE); tgtBuf.data = (SPipTangents*)m_renderMesh->GetTangentPtr(tgtBuf.iStride, FSL_CREATE_MODE); velocities.data = (Vec3*)m_renderMesh->GetVelocityPtr(velocities.iStride, FSL_CREATE_MODE); idxBuf = m_renderMesh->GetIndexPtr(FSL_CREATE_MODE); if (!vtxBuf || !idxBuf || !tgtBuf || !velocities) { m_renderMesh->UnlockStream(VSF_GENERAL); m_renderMesh->UnlockStream(VSF_TANGENTS); m_renderMesh->UnlockStream(VSF_VERTEX_VELOCITY); m_renderMesh->UnlockIndexStream(); m_renderMesh->UnLockForThreadAccess(); return; } for (size_t i = 0, iv = 0, ii = 0; i < m_nData; ++i) { UpdateInternalDeform(m_pData[i], pRenderObject, radbox, passInfo, m_renderMesh, vtxBuf, tgtBuf, velocities, idxBuf, iv, ii); } m_renderMesh->SetRenderChunks(& m_renderChunks[0], m_renderChunks.size(), false); m_renderMesh->UnLockForThreadAccess(); } if (!m_renderMesh) { return; } CRenderObject* ro = gEnv->pRenderer->EF_DuplicateRO(pRenderObject, passInfo); if (!ro) { return; } ro->m_ObjFlags |= FOB_DYNAMIC_OBJECT | FOB_VERTEX_VELOCITY | FOB_MOTION_BLUR; ro->m_II.m_Matrix.SetTranslationMat(centre); m_renderMesh->Render(ro, passInfo, rendItemSorter); } void CDeformableNode::BakeDeform(const Matrix34& worldTM) { for (size_t i = 0; i < m_nData; ++i) { SDeformableData* pData = m_pData[i]; pData->m_needsBaking = true; if (pData->m_State != SDeformableData::READY) { continue; } BakeInternal(pData, worldTM); } } void CDeformableNode::BakeInternal(SDeformableData* pData, const Matrix34& tm) { SMMRMGroupHeader* group = & pData->m_mmrmHeader; if (group == NULL) { return; } SMMRMGeometry* geom = group->procGeom; if (!geom) { return; } Matrix34 worldTM; worldTM = tm* pData->m_localTM; SMMRMUpdateContext* update = & pData->m_mmrmContext; SMMRMDeformVertex* deform_vertices = update->group->deform_vertices; for (size_t i = 0; i < geom->deform->nvertices; ++i) { const Vec3 npos = (worldTM* geom->deform->initial[i]); deform_vertices[i].pos[0] = deform_vertices[i].pos[1] = npos; deform_vertices[i].vel = Vec3(0, 0, 0); } pData->m_needsBaking = false; } ////////////////////////////////////////////////////////////////////// // Segmented World void CMergedMeshRenderNode::OffsetPosition(const Vec3& delta) { m_internalAABB.Move(delta); m_visibleAABB.Move(delta); m_pos += delta; } void CMergedMeshesManager::PrepareSegmentData(const AABB& aabb) { m_SegNodes.clear(); for (size_t i = 0; i < HashDimXY; ++i) { for (size_t j = 0; j < HashDimXY; ++j) { for (size_t k = 0; k < HashDimZ; ++k) { NodeListT& list = m_Nodes[i][j][k]; for (NodeListT::iterator it = list.begin(); it != list.end(); ++it) { if (aabb.ContainsBox2D((* it)->m_internalAABB)) { m_SegNodes.push_back((* it)); } } } } } } int CMergedMeshesManager::GetCompiledDataSize(uint32 index) { return 0; } bool CMergedMeshesManager::GetCompiledData(uint32 index, byte* pData, int nSize, string* pName, std::vector** ppStatInstGroupTable) { return false; } bool CMergedMeshRenderNode::UsesTerrainColor() const { for (size_t i = 0; i < m_nGroups; ++i) { const StatInstGroup& instGroup = GetStatObjGroup(m_groups[i].instGroupId); if (instGroup.bUseTerrainColor) { return true; } } return false; } #undef FSL_CREATE_MODE