From 75da6c6e3a0e989d71a8f909c4650e2f68dbee10 Mon Sep 17 00:00:00 2001 From: John Turner <7strbass@gmail.com> Date: Mon, 29 Jul 2024 13:11:50 -0400 Subject: [PATCH 1/5] --initial commit - calc volume and surface area of mesh --- src/esp/assets/ResourceManager.cpp | 62 +++++++++++++++++++++++++++++- src/esp/assets/ResourceManager.h | 7 ++++ src/esp/scene/SceneNode.h | 15 ++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/esp/assets/ResourceManager.cpp b/src/esp/assets/ResourceManager.cpp index 94fad3e729..fbd22aa673 100644 --- a/src/esp/assets/ResourceManager.cpp +++ b/src/esp/assets/ResourceManager.cpp @@ -1040,6 +1040,65 @@ 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) { + const int meshID = staticDrawableInfo[iEntry].meshID; + + Cr::Containers::Optional& meshData = + meshes_.at(meshID)->getMeshData(); + CORRADE_ASSERT( + meshData, + "::computeGeneralMeshAreaAndVolume: The mesh data specified at ID:" + << meshID << "is empty/undefined. Aborting", ); + + // Precalc all transformed verts - only use first position array for this + Cr::Containers::Array posArray = + meshData->positions3DAsArray(0); + Mn::MeshTools::transformPointsInPlace(absTransforms[iEntry], posArray); + + // Surface area of the mesh : .5 * ba.cross(bc) + double ttlSurfaceArea = 0.0; + + // Volume of the mesh : 1/6 * (OA.dot(ba.cross(bc))) + // Where O is a distant vertex + double ttlVolume = 0.0f; + + // locate the scene node which contains the current drawable + scene::SceneNode& node = staticDrawableInfo[iEntry].node; + // # of indices + int numIdxs = meshData->indexCount(); + + // const auto idxView = meshData->indices(); + const auto idxView = meshData->indicesAsArray(); + Mn::Vector3 distPt{1000, 1000, 1000}; + for (uint32_t rawIdx = 0; rawIdx < numIdxs; rawIdx += 3) { + const auto aVert = posArray[idxView[rawIdx + 1]]; + Mn::Vector3 a = posArray[idxView[rawIdx]] - aVert; + Mn::Vector3 b = posArray[idxView[rawIdx + 2]] - aVert; + Mn::Vector3 areaNorm = 0.5 * Mn::Math::cross(a, b); + double surfArea = areaNorm.length(); + ttlSurfaceArea += surfArea; + Mn::Vector3 c = distPt - aVert; + double signedVol = (Mn::Math::dot(c, areaNorm)) / 3.0; + ttlVolume += signedVol; + } + + // set the node's volume and surface area + node.setMeshVolume(ttlVolume); + node.setMeshSurfaceArea(ttlSurfaceArea); + + } // iEntry + +} // ResourceManager::computeGeneralMeshVolume + void ResourceManager::computeGeneralMeshAbsoluteAABBs( const std::vector& staticDrawableInfo) { std::vector absTransforms = @@ -1719,7 +1778,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) { 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/scene/SceneNode.h b/src/esp/scene/SceneNode.h index bb2ee5e003..25b5fffebb 100644 --- a/src/esp/scene/SceneNode.h +++ b/src/esp/scene/SceneNode.h @@ -292,6 +292,15 @@ class SceneNode : public MagnumObject, //! set frustum plane in last frame that culls this node void setFrustumPlaneIndex(int index) { frustumPlaneIndex = index; }; + //! 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 +340,12 @@ class SceneNode : public MagnumObject, //! The absolute translation of this node, updated in clean Magnum::Matrix4 absoluteTransformation_; + //! The volume of the drawable mesh held in this node + double volume_ = 0.0f; + + //! The surface area of the drawable mesh held in this node + double surfArea_ = 0.0f; + //! 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 From e8fd1fd3699a86999256a04a2a377d0192f87526 Mon Sep 17 00:00:00 2001 From: John Turner <7strbass@gmail.com> Date: Mon, 29 Jul 2024 13:24:23 -0400 Subject: [PATCH 2/5] --provide object-level access to node volume/surface area --- src/esp/physics/ArticulatedObject.h | 20 ++++++++++++++++++++ src/esp/physics/PhysicsManager.cpp | 2 +- src/esp/physics/PhysicsObjectBase.h | 6 ++++++ 3 files changed, 27 insertions(+), 1 deletion(-) 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 From 151c26c3bb0af15d714ed11ff0a0bb1da93c4c23 Mon Sep 17 00:00:00 2001 From: John Turner <7strbass@gmail.com> Date: Tue, 30 Jul 2024 12:51:09 -0400 Subject: [PATCH 3/5] --expand calculation to test for mesh topology - watertight and manifold. --- src/esp/assets/ResourceManager.cpp | 99 ++++++++++++++++++++++-------- src/esp/scene/SceneNode.h | 22 ++++++- 2 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/esp/assets/ResourceManager.cpp b/src/esp/assets/ResourceManager.cpp index fbd22aa673..ee83a4f70a 100644 --- a/src/esp/assets/ResourceManager.cpp +++ b/src/esp/assets/ResourceManager.cpp @@ -1064,37 +1064,86 @@ void ResourceManager::computeGeneralMeshAreaAndVolume( meshData->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 = meshData->indices(); + const auto idxAra = meshData->indicesAsArray(); + // # of indices + uint32_t numIdxs = meshData->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 altEdgeCount; + scene::DrawableMeshTopology meshTopology = + scene::DrawableMeshTopology::ClosedManifold; + for (uint32_t idx = 0; idx < numIdxs; idx += 3) { + 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) { + uint32_t next_i = (i + 1) % 3; + auto res = + edgeCount.emplace(std::make_pair(shift_vals[i] + vals[next_i], 0)); + if (!res.second) { + res.first->second += 1; + // Duplicate edge with same orientation + meshTopology = scene::DrawableMeshTopology::NonManifold; + } + // Alt edge placement - verify the alternate edge is present + altEdgeCount.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) { + altEdgeCount.erase(entry.first); + } + if (altEdgeCount.size() > 0) { + meshTopology = scene::DrawableMeshTopology::OpenManifold; + } + } + + // locate the scene node which contains the current drawable + scene::SceneNode& node = staticDrawableInfo[iEntry].node; // Surface area of the mesh : .5 * ba.cross(bc) double ttlSurfaceArea = 0.0; - // Volume of the mesh : 1/6 * (OA.dot(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; - - // locate the scene node which contains the current drawable - scene::SceneNode& node = staticDrawableInfo[iEntry].node; - // # of indices - int numIdxs = meshData->indexCount(); - - // const auto idxView = meshData->indices(); - const auto idxView = meshData->indicesAsArray(); - Mn::Vector3 distPt{1000, 1000, 1000}; - for (uint32_t rawIdx = 0; rawIdx < numIdxs; rawIdx += 3) { - const auto aVert = posArray[idxView[rawIdx + 1]]; - Mn::Vector3 a = posArray[idxView[rawIdx]] - aVert; - Mn::Vector3 b = posArray[idxView[rawIdx + 2]] - aVert; - Mn::Vector3 areaNorm = 0.5 * Mn::Math::cross(a, b); - double surfArea = areaNorm.length(); - ttlSurfaceArea += surfArea; - Mn::Vector3 c = distPt - aVert; - double signedVol = (Mn::Math::dot(c, areaNorm)) / 3.0; - ttlVolume += signedVol; + if (meshTopology == scene::DrawableMeshTopology::ClosedManifold) { + Mn::Vector3 origin{}; + for (uint32_t idx = 0; idx < numIdxs; idx += 3) { + const auto aVert = posArray[idxAra[idx + 1]]; + Mn::Vector3 aVec = posArray[idxAra[idx]] - aVert; + Mn::Vector3 bVec = posArray[idxAra[idx + 2]] - aVert; + Mn::Vector3 areaNormVec = 0.5 * Mn::Math::cross(aVec, bVec); + double surfArea = areaNormVec.length(); + ttlSurfaceArea += surfArea; + Mn::Vector3 c = origin - aVert; + double signedVol = (Mn::Math::dot(c, areaNormVec)) / 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 aVert = posArray[idxAra[idx + 1]]; + Mn::Vector3 aVec = posArray[idxAra[idx]] - aVert; + Mn::Vector3 bVec = posArray[idxAra[idx + 2]] - aVert; + Mn::Vector3 areaNormVec = 0.5 * Mn::Math::cross(aVec, bVec); + double surfArea = areaNormVec.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 @@ -3191,9 +3240,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/scene/SceneNode.h b/src/esp/scene/SceneNode.h index 25b5fffebb..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,11 @@ 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 @@ -340,11 +354,15 @@ 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.0f; + double volume_ = 0.0; //! The surface area of the drawable mesh held in this node - double surfArea_ = 0.0f; + 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: From bf22d2c87ad3f2ccc1e444db71581c5cc8b61c3b Mon Sep 17 00:00:00 2001 From: John Turner <7strbass@gmail.com> Date: Thu, 1 Aug 2024 11:45:22 -0400 Subject: [PATCH 4/5] --exit calc if not strictly triangle-based mesh; make temp dedup copy --- src/esp/assets/ResourceManager.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/esp/assets/ResourceManager.cpp b/src/esp/assets/ResourceManager.cpp index ee83a4f70a..37f969c89e 100644 --- a/src/esp/assets/ResourceManager.cpp +++ b/src/esp/assets/ResourceManager.cpp @@ -1050,26 +1050,40 @@ void ResourceManager::computeGeneralMeshAreaAndVolume( "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 = - meshData->positions3DAsArray(0); + 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 = meshData->indices(); - const auto idxAra = meshData->indicesAsArray(); + // const auto idxView = newMeshData->indices(); + const auto idxAra = newMeshData->indicesAsArray(); // # of indices - uint32_t numIdxs = meshData->indexCount(); + 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 @@ -1106,8 +1120,6 @@ void ResourceManager::computeGeneralMeshAreaAndVolume( } } - // locate the scene node which contains the current drawable - scene::SceneNode& node = staticDrawableInfo[iEntry].node; // Surface area of the mesh : .5 * ba.cross(bc) double ttlSurfaceArea = 0.0; // Volume of the mesh : 1/6 * (OA.dot(ba.cross(bc))) From c0642b6e5c037b7678a82783d74009e7cb899527 Mon Sep 17 00:00:00 2001 From: John Turner <7strbass@gmail.com> Date: Thu, 29 Aug 2024 09:23:12 -0400 Subject: [PATCH 5/5] --cleanup --- src/esp/assets/ResourceManager.cpp | 49 ++++++++++++++++++------------ 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/esp/assets/ResourceManager.cpp b/src/esp/assets/ResourceManager.cpp index 37f969c89e..e936be7434 100644 --- a/src/esp/assets/ResourceManager.cpp +++ b/src/esp/assets/ResourceManager.cpp @@ -1088,41 +1088,47 @@ void ResourceManager::computeGeneralMeshAreaAndVolume( // 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 altEdgeCount; + 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; } - // Alt edge placement - verify the alternate edge is present - altEdgeCount.emplace(std::make_pair(shift_vals[next_i] + vals[i], 0)); + // 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) { - altEdgeCount.erase(entry.first); + revEdgeCount.erase(entry.first); } - if (altEdgeCount.size() > 0) { + if (revEdgeCount.size() > 0) { meshTopology = scene::DrawableMeshTopology::OpenManifold; } } - // Surface area of the mesh : .5 * ba.cross(bc) + // Surface area of the mesh M_a : sum(Tri_abc) ( |.5 * ba.cross(bc)|) double ttlSurfaceArea = 0.0; - // Volume of the mesh : 1/6 * (OA.dot(ba.cross(bc))) + // 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) @@ -1130,24 +1136,29 @@ void ResourceManager::computeGeneralMeshAreaAndVolume( if (meshTopology == scene::DrawableMeshTopology::ClosedManifold) { Mn::Vector3 origin{}; for (uint32_t idx = 0; idx < numIdxs; idx += 3) { - const auto aVert = posArray[idxAra[idx + 1]]; - Mn::Vector3 aVec = posArray[idxAra[idx]] - aVert; - Mn::Vector3 bVec = posArray[idxAra[idx + 2]] - aVert; - Mn::Vector3 areaNormVec = 0.5 * Mn::Math::cross(aVec, bVec); - double surfArea = areaNormVec.length(); + 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 c = origin - aVert; - double signedVol = (Mn::Math::dot(c, areaNormVec)) / 3.0; + 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 aVert = posArray[idxAra[idx + 1]]; - Mn::Vector3 aVec = posArray[idxAra[idx]] - aVert; - Mn::Vector3 bVec = posArray[idxAra[idx + 2]] - aVert; - Mn::Vector3 areaNormVec = 0.5 * Mn::Math::cross(aVec, bVec); - double surfArea = areaNormVec.length(); + 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; } }