Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--Provide mesh surface area, volume and topology tools #2437

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 136 additions & 4 deletions src/esp/assets/ResourceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,137 @@ Mn::Range3D ResourceManager::computeMeshBB(BaseMesh* meshDataGL) {
return Mn::Math::minmax(meshData.positions);
}

void ResourceManager::computeGeneralMeshAreaAndVolume(
const std::vector<StaticDrawableInfo>& staticDrawableInfo) {
std::vector<Mn::Matrix4> 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<Mn::Trade::MeshData>& 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<Mn::Trade::MeshData> 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<Mn::Vector3> 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<std::uint32_t>();
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<uint64_t, int> edgeCount;
std::unordered_map<uint64_t, int> 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>& staticDrawableInfo) {
std::vector<Mn::Matrix4> absTransforms =
Expand Down Expand Up @@ -1717,7 +1848,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) {
Expand Down Expand Up @@ -3129,9 +3261,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));
}
Expand Down
7 changes: 7 additions & 0 deletions src/esp/assets/ResourceManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>& staticDrawableInfo);

/**
* @brief Compute the absolute AABBs for drawables in general mesh (e.g.,
* MP3D) world space
Expand Down
20 changes: 20 additions & 0 deletions src/esp/physics/ArticulatedObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion src/esp/physics/PhysicsManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ int PhysicsManager::addObjectInternal(
obj->setManagedObjectPtr(objWrapper);

return newObjectID;
} // PhysicsManager::addObject
} // PhysicsManager::addObjectInternal

/////////////////////////////////
// Articulated Object Creation
Expand Down
6 changes: 6 additions & 0 deletions src/esp/physics/PhysicsObjectBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions src/esp/scene/SceneNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading