diff --git a/src/esp/assets/ResourceManager.cpp b/src/esp/assets/ResourceManager.cpp index 94fad3e729..e936be7434 100644 --- a/src/esp/assets/ResourceManager.cpp +++ b/src/esp/assets/ResourceManager.cpp @@ -1040,6 +1040,137 @@ Mn::Range3D ResourceManager::computeMeshBB(BaseMesh* meshDataGL) { return Mn::Math::minmax(meshData.positions); } +void ResourceManager::computeGeneralMeshAreaAndVolume( + const std::vector& staticDrawableInfo) { + std::vector absTransforms = + computeAbsoluteTransformations(staticDrawableInfo); + + CORRADE_ASSERT(absTransforms.size() == staticDrawableInfo.size(), + "::computeGeneralMeshAreaAndVolume: number of " + "transforms does not match number of drawables.", ); + + for (uint32_t iEntry = 0; iEntry < staticDrawableInfo.size(); ++iEntry) { + // Current drawable's meshID + const int meshID = staticDrawableInfo[iEntry].meshID; + // Current drawable's scene node + scene::SceneNode& node = staticDrawableInfo[iEntry].node; + + Cr::Containers::Optional& meshData = + meshes_.at(meshID)->getMeshData(); + if (meshData->primitive() != Mn::MeshPrimitive::Triangles) { + // These calculations rely on this mesh being purely triangle-based + // Make sure mesh's topology is set to unknown so area/volume values are + // not trusted + node.setMeshTopology(scene::DrawableMeshTopology::Unknown); + continue; + } + CORRADE_ASSERT( + meshData, + "::computeGeneralMeshAreaAndVolume: The mesh data specified at ID:" + << meshID << "is empty/undefined. Aborting", ); + // Make temp copy that removes dupes for volume calc + Cr::Containers::Optional newMeshData = + Mn::MeshTools::removeDuplicates(Mn::MeshTools::filterOnlyAttributes( + *meshData, {Mn::Trade::MeshAttribute::Position})); + + // Precalc all transformed verts - only use first position array for this + Cr::Containers::Array posArray = + newMeshData->positions3DAsArray(0); + Mn::MeshTools::transformPointsInPlace(absTransforms[iEntry], posArray); + + // Getting the view properly relies on having the appropriate type of the + // loaded data + // const auto idxView = newMeshData->indices(); + const auto idxAra = newMeshData->indicesAsArray(); + // # of indices + uint32_t numIdxs = newMeshData->indexCount(); + // Assuming no duplicate vertices with different idxs + // Determine that all edges have exactly 2 sides -> + // idxAra describes exactly 2 pairs of the same idxs, a->b and b->a + std::unordered_map edgeCount; + std::unordered_map revEdgeCount; + scene::DrawableMeshTopology meshTopology = + scene::DrawableMeshTopology::ClosedManifold; + for (uint32_t idx = 0; idx < numIdxs; idx += 3) { + // First edge index, encoded in 64bit + uint64_t vals[] = {uint64_t(idxAra[idx]), uint64_t(idxAra[idx + 1]), + uint64_t(idxAra[idx + 2])}; + uint64_t shift_vals[] = {vals[0] << 32, vals[1] << 32, vals[2] << 32}; + // for each edge in poly + for (uint32_t i = 0; i < 3; ++i) { + // Second edge index + uint32_t next_i = (i + 1) % 3; + // Encode directed edge vert idxs in single unsigned long + auto res = + edgeCount.emplace(std::make_pair(shift_vals[i] + vals[next_i], 0)); + // Check if duplicate already exists - if so then non-manifold + if (!res.second) { + // Keep count of dupes + res.first->second += 1; + // Duplicate edge with same orientation + meshTopology = scene::DrawableMeshTopology::NonManifold; + } + // Reverse edge placement - verify the reverse edge is present + revEdgeCount.emplace(std::make_pair(shift_vals[next_i] + vals[i], 0)); + } + } + // If still closed manifold then check that every edge has an alt edge + // present + if (meshTopology == scene::DrawableMeshTopology::ClosedManifold) { + for (const auto entry : edgeCount) { + revEdgeCount.erase(entry.first); + } + if (revEdgeCount.size() > 0) { + meshTopology = scene::DrawableMeshTopology::OpenManifold; + } + } + + // Surface area of the mesh M_a : sum(Tri_abc) ( |.5 * ba.cross(bc)|) + double ttlSurfaceArea = 0.0; + // Volume of the mesh M_v = sum(Tri_abc)( 1/3 (area_abc) h_O + // = sum(Tri_abc)(1/3 * (bO.dot(.5 * (ba.cross(bc))))) + // Where O is a distant vertex + // Only applicable on closed manifold meshes (i.e. all edges have exactly + // 2 faces) + double ttlVolume = 0.0f; + if (meshTopology == scene::DrawableMeshTopology::ClosedManifold) { + Mn::Vector3 origin{}; + for (uint32_t idx = 0; idx < numIdxs; idx += 3) { + const auto bVert = posArray[idxAra[idx + 1]]; + Mn::Vector3 baVec = posArray[idxAra[idx]] - bVert; + Mn::Vector3 bcVec = posArray[idxAra[idx + 2]] - bVert; + // Magnitude is 2x tri_abc area, direction is orthogonal to tri_abc + // ("height" dir) + Mn::Vector3 areaOrthoVec = 0.5 * Mn::Math::cross(baVec, bcVec); + double surfArea = areaOrthoVec.length(); + ttlSurfaceArea += surfArea; + Mn::Vector3 bO = origin - bVert; + // Project along "height" direction + double signedVol = (Mn::Math::dot(bO, areaOrthoVec)) / 3.0; + ttlVolume += signedVol; + } + } else { + // Open or non-manifold meshes won't have an accurate volume calc + for (uint32_t idx = 0; idx < numIdxs; idx += 3) { + const auto bVert = posArray[idxAra[idx + 1]]; + Mn::Vector3 baVec = posArray[idxAra[idx]] - bVert; + Mn::Vector3 bcVec = posArray[idxAra[idx + 2]] - bVert; + // Magnitude is 2x tri_abc area, direction is orthogonal to tri_abc + // ("height" dir) + Mn::Vector3 areaOrthoVec = 0.5 * Mn::Math::cross(baVec, bcVec); + double surfArea = areaOrthoVec.length(); + ttlSurfaceArea += surfArea; + } + } + // set the node's volume and surface area + node.setMeshVolume(ttlVolume); + node.setMeshSurfaceArea(ttlSurfaceArea); + // Set whether the mesh is manifold and/or closed/watertight + node.setMeshTopology(meshTopology); + } // iEntry + +} // ResourceManager::computeGeneralMeshVolume + void ResourceManager::computeGeneralMeshAbsoluteAABBs( const std::vector& staticDrawableInfo) { std::vector absTransforms = @@ -1719,7 +1850,8 @@ scene::SceneNode* ResourceManager::createRenderAssetInstanceGeneralPrimitive( // now compute aabbs by constructed staticDrawableInfo computeGeneralMeshAbsoluteAABBs(staticDrawableInfo); } - + // Might be expensive + computeGeneralMeshAreaAndVolume(staticDrawableInfo); // set the node type for all cached visual nodes if (nodeType != scene::SceneNodeType::Empty) { for (auto* node : visNodeCache) { @@ -3131,9 +3263,9 @@ void ResourceManager::addComponent( drawableConfig); // instance skinning data // compute the bounding box for the mesh we are adding - if (computeAbsoluteAABBs) { - staticDrawableInfo.emplace_back(StaticDrawableInfo{node, meshID}); - } + // if (computeAbsoluteAABBs) { + staticDrawableInfo.emplace_back(StaticDrawableInfo{node, meshID}); + //} BaseMesh* meshBB = meshes_.at(meshID).get(); node.setMeshBB(computeMeshBB(meshBB)); } diff --git a/src/esp/assets/ResourceManager.h b/src/esp/assets/ResourceManager.h index 4ab4b339f8..276d048dbf 100644 --- a/src/esp/assets/ResourceManager.h +++ b/src/esp/assets/ResourceManager.h @@ -1161,6 +1161,13 @@ class ResourceManager { */ Mn::Range3D computeMeshBB(BaseMesh* meshDataGL); + /** + * @brief Compute the surface area and volume of the drawables in the general + * mesh (assumes each is a closed mesh). + */ + void computeGeneralMeshAreaAndVolume( + const std::vector& staticDrawableInfo); + /** * @brief Compute the absolute AABBs for drawables in general mesh (e.g., * MP3D) world space diff --git a/src/esp/physics/ArticulatedObject.h b/src/esp/physics/ArticulatedObject.h index 7312a8ffcb..4b8dbb7fcd 100644 --- a/src/esp/physics/ArticulatedObject.h +++ b/src/esp/physics/ArticulatedObject.h @@ -429,6 +429,26 @@ class ArticulatedObject : public esp::physics::PhysicsObjectBase { return res; } // getMarkerPointsGlobal + /** @brief Return this object's mesh volume. */ + double getVolume() const override { + double ttlVolume = baseLink_->getVolume(); + // For all other nodes + for (const auto& link : links_) { + ttlVolume += link.second->getVolume(); + } + return ttlVolume; + } + + /** @brief Return this object's mesh surface area. */ + double getSurfaceArea() const override { + double ttlSurfArea = baseLink_->getSurfaceArea(); + // For all other nodes + for (const auto& link : links_) { + ttlSurfArea += link.second->getSurfaceArea(); + } + return ttlSurfArea; + } + /** * @brief Set forces/torques for all joints indexed by degrees of freedom. * diff --git a/src/esp/physics/PhysicsManager.cpp b/src/esp/physics/PhysicsManager.cpp index 12ca8cf7e6..b4a348eb5f 100644 --- a/src/esp/physics/PhysicsManager.cpp +++ b/src/esp/physics/PhysicsManager.cpp @@ -388,7 +388,7 @@ int PhysicsManager::addObjectInternal( obj->setManagedObjectPtr(objWrapper); return newObjectID; -} // PhysicsManager::addObject +} // PhysicsManager::addObjectInternal ///////////////////////////////// // Articulated Object Creation diff --git a/src/esp/physics/PhysicsObjectBase.h b/src/esp/physics/PhysicsObjectBase.h index a07f686618..02bb806583 100644 --- a/src/esp/physics/PhysicsObjectBase.h +++ b/src/esp/physics/PhysicsObjectBase.h @@ -615,6 +615,12 @@ class PhysicsObjectBase : public Magnum::SceneGraph::AbstractFeature3D { _managedObject = std::move(managedObjPtr); } + /** @brief Return this object's mesh volume. */ + virtual double getVolume() const { return node().getMeshVolume(); } + + /** @brief Return this object's mesh surface area. */ + virtual double getSurfaceArea() const { return node().getMeshSurfaceArea(); } + protected: /** * @brief Accessed Internally. Get the Managed Object that references this diff --git a/src/esp/scene/SceneNode.h b/src/esp/scene/SceneNode.h index bb2ee5e003..02e9f2157f 100644 --- a/src/esp/scene/SceneNode.h +++ b/src/esp/scene/SceneNode.h @@ -41,6 +41,15 @@ enum class SceneNodeType { EndSceneNodeType, }; +// Topological nature of the drawable mesh held by this scene node, if exists +enum class DrawableMeshTopology { + Unknown = ID_UNDEFINED, + ClosedManifold = 0, + NonManifold, + OpenManifold + +}; + /** * @brief This enum holds the idx values for the vector of various types * of IDs that can be rendered via semantic sensors. @@ -292,6 +301,20 @@ class SceneNode : public MagnumObject, //! set frustum plane in last frame that culls this node void setFrustumPlaneIndex(int index) { frustumPlaneIndex = index; }; + DrawableMeshTopology getMeshTopology() const { return isClosedManifold_; } + + void setMeshTopology(DrawableMeshTopology _isClosedManifold) { + isClosedManifold_ = _isClosedManifold; + } + //! Set this node's drawable's volume + void setMeshVolume(double _volume) { volume_ = _volume; } + //! Get this node's drawable's volume + double getMeshVolume() const { return volume_; } + //! Set this node's drawable's surface area + void setMeshSurfaceArea(double _surfArea) { surfArea_ = _surfArea; } + //! Get this node's drawable's surface area + double getMeshSurfaceArea() const { return surfArea_; } + protected: // DO not make the following constructor public! // it can ONLY be called from SceneGraph class to initialize the scene graph @@ -331,6 +354,16 @@ class SceneNode : public MagnumObject, //! The absolute translation of this node, updated in clean Magnum::Matrix4 absoluteTransformation_; + //! Whether the drawable mesh is closed and manifold (ever edge is adjacent to + //! exactly 2 faces) + DrawableMeshTopology isClosedManifold_ = DrawableMeshTopology::Unknown; + + //! The volume of the drawable mesh held in this node + double volume_ = 0.0; + + //! The surface area of the drawable mesh held in this node + double surfArea_ = 0.0; + //! the global bounding box for *static* meshes stored at this node // NOTE: this is different from the local bounding box meshBB_ defined above: // -) it only applies to *static* meshes, NOT dynamic meshes in the scene (so