123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <Editor/PolygonPrismMeshUtils.h>
- #include <AzCore/std/containers/unordered_map.h>
- #include <AzCore/Math/Vector3.h>
- namespace PolygonPrismMeshUtils
- {
- AZ::Vector3 CalculateAngles(p2t::Triangle& triangle)
- {
- // Calculate the internal angles of the triangle from the edge lengths using the law of cosines
- const float e0 = static_cast<float>((*triangle.GetPoint(1) - *triangle.GetPoint(0)).Length());
- const float e1 = static_cast<float>((*triangle.GetPoint(2) - *triangle.GetPoint(1)).Length());
- const float e2 = static_cast<float>((*triangle.GetPoint(0) - *triangle.GetPoint(2)).Length());
- const float angle0 = acosf(AZ::GetClamp((e0 * e0 + e2 * e2 - e1 * e1) / (2.0f * e0 * e2), -1.0f, 1.0f));
- const float angle1 = acosf(AZ::GetClamp((e0 * e0 + e1 * e1 - e2 * e2) / (2.0f * e0 * e1), -1.0f, 1.0f));
- return AZ::Vector3(angle0, angle1, AZ::Constants::Pi - angle0 - angle1);
- }
- bool InternalEdgeCompare::operator()(const InternalEdge& left, const InternalEdge& right) const
- {
- return left.m_minAngle > right.m_minAngle;
- }
- void Mesh2D::CreateFromPoly2Tri(const std::vector<p2t::Triangle*>& triangles)
- {
- const int numTriangles = static_cast<int>(triangles.size());
- m_faces.resize(numTriangles);
- m_halfEdges.resize(3 * numTriangles);
- AZStd::unordered_map<p2t::Triangle*, int> triangleIndexMap;
- for (int faceIndex = 0; faceIndex < numTriangles; faceIndex++)
- {
- p2t::Triangle* triangle = triangles[faceIndex];
- triangleIndexMap.emplace(AZStd::make_pair(triangle, faceIndex));
- // The index of the first half-edge in this face
- const int firstHalfEdgeIndex = 3 * faceIndex;
- // Populate the face data
- m_faces[faceIndex].m_edge = &m_halfEdges[firstHalfEdgeIndex];
- m_faces[faceIndex].m_numEdges = 3;
- // Populate the half-edge data, apart from the twin pointers which will require a second pass
- const AZ::Vector3 angles = CalculateAngles(*triangle);
- for (int edgeIndex = 0; edgeIndex < 3; edgeIndex++)
- {
- const int nextIndex = (edgeIndex + 1) % 3;
- const int prevIndex = (edgeIndex + 2) % 3;
- HalfEdge* halfEdge = &m_halfEdges[firstHalfEdgeIndex + edgeIndex];
- halfEdge->m_face = &m_faces[faceIndex];
- const p2t::Point* point = triangle->GetPoint(edgeIndex);
- halfEdge->m_origin = AZ::Vector2(static_cast<float>(point->x), static_cast<float>(point->y));
- halfEdge->m_prev = &m_halfEdges[firstHalfEdgeIndex + prevIndex];
- halfEdge->m_next = &m_halfEdges[firstHalfEdgeIndex + nextIndex];
- halfEdge->m_prevAngle = angles.GetElement(edgeIndex);
- halfEdge->m_nextAngle = angles.GetElement(nextIndex);
- }
- }
- // Figure out twin half-edges, and populate the queue of internal edges to consider for removal
- for (int faceIndex = 0; faceIndex < numTriangles; faceIndex++)
- {
- p2t::Triangle* triangle = triangles[faceIndex];
- for (int edgeIndex = 0; edgeIndex < 3; edgeIndex++)
- {
- HalfEdge* halfEdge = &m_halfEdges[3 * faceIndex + edgeIndex];
- if (halfEdge->m_visited)
- {
- // We have already visited this half-edge when considering its twin, so there is nothing to do
- continue;
- }
- p2t::Triangle* twinFace = triangle->NeighborCCW(*triangle->GetPoint(edgeIndex));
- if (twinFace == nullptr)
- {
- // This half-edge doesn't have a twin, so it is an external edge and there is nothing to do
- continue;
- }
- auto triangleIndexPair = triangleIndexMap.find(twinFace);
- if (triangleIndexPair == triangleIndexMap.end())
- {
- // Poly2tri can have triangles outside of the polygon, so it is possible for them not to be
- // found in the face map, but in this case we should do nothing
- continue;
- }
- const int twinFaceIndex = triangleIndexPair->second;
- if (twinFaceIndex >= numTriangles)
- {
- AZ_Error("PolygonPrismMeshUtils", false, "Invalid triangle index.");
- continue;
- }
- const int nextIndex = (edgeIndex + 1) % 3;
- const int twinEdgeIndex = twinFace->Index(triangle->GetPoint(nextIndex));
- HalfEdge* twinHalfEdge = &m_halfEdges[3 * twinFaceIndex + twinEdgeIndex];
- halfEdge->m_twin = twinHalfEdge;
- halfEdge->m_visited = true;
- twinHalfEdge->m_twin = halfEdge;
- twinHalfEdge->m_visited = true;
- InternalEdge internalEdge;
- internalEdge.m_edges[0] = halfEdge;
- internalEdge.m_edges[1] = twinHalfEdge;
- internalEdge.m_minAngle = AZStd::GetMin(
- AZStd::GetMin(halfEdge->m_prevAngle, halfEdge->m_nextAngle),
- AZStd::GetMin(twinHalfEdge->m_prevAngle, twinHalfEdge->m_nextAngle));
- m_edgeQueue.push(internalEdge);
- }
- }
- }
- void Mesh2D::CreateFromSimpleConvexPolygon(const AZStd::vector<AZ::Vector2>& vertices)
- {
- const int numVertices = static_cast<int>(vertices.size());
- m_faces.resize(1);
- m_halfEdges.resize(numVertices);
- m_faces[0].m_edge = &m_halfEdges[0];
- m_faces[0].m_numEdges = numVertices;
- for (int edgeIndex = 0; edgeIndex < numVertices; edgeIndex++)
- {
- const int nextIndex = (edgeIndex + 1) % numVertices;
- const int prevIndex = (edgeIndex + numVertices - 1) % numVertices;
- m_halfEdges[edgeIndex].m_face = &m_faces[0];
- m_halfEdges[edgeIndex].m_origin = vertices[edgeIndex];
- m_halfEdges[edgeIndex].m_prev = &m_halfEdges[prevIndex];
- m_halfEdges[edgeIndex].m_next = &m_halfEdges[nextIndex];
- }
- }
- void Mesh2D::RemoveInternalEdge(const InternalEdge& internalEdge)
- {
- HalfEdge* halfEdge0 = internalEdge.m_edges[0];
- HalfEdge* halfEdge1 = internalEdge.m_edges[1];
- Face* faceToKeep = halfEdge0->m_face;
- Face* faceToRemove = halfEdge1->m_face;
- const float newAngle0 = halfEdge0->m_prevAngle + halfEdge1->m_nextAngle;
- const float newAngle1 = halfEdge0->m_nextAngle + halfEdge1->m_prevAngle;
- const int newNumEdges = halfEdge0->m_face->m_numEdges + halfEdge1->m_face->m_numEdges - 2;
- faceToKeep->m_numEdges = newNumEdges;
- faceToRemove->m_removed = true;
- // Make sure the face doesn't point to the half-edge that will be removed.
- if (faceToKeep->m_edge == internalEdge.m_edges[0])
- {
- faceToKeep->m_edge = internalEdge.m_edges[0]->m_next;
- }
- // Make all the half-edges in the face that will be removed point to the kept face instead.
- HalfEdge* currentEdge = internalEdge.m_edges[1];
- for (int edgeIndex = 0; edgeIndex < faceToRemove->m_numEdges; edgeIndex++)
- {
- currentEdge->m_face = faceToKeep;
- currentEdge = currentEdge->m_next;
- }
- // Update the previous and next half-edge pointers and angles for the 4 half-edges adjacent
- // to the edge that will be removed.
- halfEdge0->m_prev->m_dirty = true;
- halfEdge0->m_next->m_dirty = true;
- halfEdge1->m_prev->m_dirty = true;
- halfEdge1->m_next->m_dirty = true;
- halfEdge0->m_prev->m_next = halfEdge1->m_next;
- halfEdge0->m_next->m_prev = halfEdge1->m_prev;
- halfEdge1->m_prev->m_next = halfEdge0->m_next;
- halfEdge1->m_next->m_prev = halfEdge0->m_prev;
- halfEdge0->m_prev->m_nextAngle = newAngle0;
- halfEdge0->m_next->m_prevAngle = newAngle1;
- halfEdge1->m_prev->m_nextAngle = newAngle1;
- halfEdge1->m_next->m_prevAngle = newAngle0;
- }
- size_t Mesh2D::ConvexMerge()
- {
- size_t numFacesRemoved = 0;
- while (!m_edgeQueue.empty())
- {
- const InternalEdge& internalEdge = m_edgeQueue.top();
- if (internalEdge.m_edges[0] != nullptr && internalEdge.m_edges[1] != nullptr)
- {
- // If either of the half-edges are marked dirty due to previously removing adjacent edges,
- // update the edge and place back into the queue.
- if (internalEdge.m_edges[0]->m_dirty || internalEdge.m_edges[1]->m_dirty)
- {
- InternalEdge updatedInternalEdge(internalEdge);
- updatedInternalEdge.m_minAngle = AZStd::GetMin(
- AZStd::GetMin(internalEdge.m_edges[0]->m_prevAngle, internalEdge.m_edges[0]->m_nextAngle),
- AZStd::GetMin(internalEdge.m_edges[1]->m_prevAngle, internalEdge.m_edges[1]->m_nextAngle));
- internalEdge.m_edges[0]->m_dirty = false;
- internalEdge.m_edges[1]->m_dirty = false;
- m_edgeQueue.pop();
- m_edgeQueue.push(updatedInternalEdge);
- }
- else
- {
- // There are two conditions that need to be satisfied in order to allow removing this edge.
- // First, the merged face should remain convex, i.e. the two new internal angles which would be
- // created must both be less than 180 degrees.
- // Secondly, the merged polygon must meet the PhysX vertex limit for convex meshes.
- HalfEdge* halfEdge0 = internalEdge.m_edges[0];
- HalfEdge* halfEdge1 = internalEdge.m_edges[1];
- const float newAngle0 = halfEdge0->m_prevAngle + halfEdge1->m_nextAngle;
- const float newAngle1 = halfEdge0->m_nextAngle + halfEdge1->m_prevAngle;
- const int newNumEdges = halfEdge0->m_face->m_numEdges + halfEdge1->m_face->m_numEdges - 2;
- // Allow angles very slightly larger than 180 degrees to avoid unnecessary splitting due to
- // precision issues.
- const float epsilon = 1e-5f;
- const float maxAngle = AZ::Constants::Pi + epsilon;
- if (newAngle0 < maxAngle &&
- newAngle1 < maxAngle &&
- newNumEdges <= MaxPolygonPrismEdges)
- {
- RemoveInternalEdge(internalEdge);
- numFacesRemoved++;
- }
- m_edgeQueue.pop();
- }
- }
- else
- {
- AZ_Error("PhysX Shape Collider Component", false, "Invalid half-edge.");
- m_edgeQueue.pop();
- }
- }
- return numFacesRemoved;
- }
- const AZStd::vector<Face>& Mesh2D::GetFaces() const
- {
- return m_faces;
- }
- const AZStd::priority_queue<InternalEdge, AZStd::vector<InternalEdge>, InternalEdgeCompare>& Mesh2D::GetInternalEdges() const
- {
- return m_edgeQueue;
- }
- const AZStd::vector<AZ::Vector3>& Mesh2D::GetDebugDrawPoints(float height, const AZ::Vector3& nonUniformScale) const
- {
- if (m_debugDrawDirty)
- {
- m_debugDrawPoints.clear();
- for (const auto& face : m_faces)
- {
- if (!face.m_removed)
- {
- int numEdges = face.m_numEdges;
- PolygonPrismMeshUtils::HalfEdge* currentEdge = face.m_edge;
- for (int edgeIndex = 0; edgeIndex < numEdges; edgeIndex++)
- {
- PolygonPrismMeshUtils::HalfEdge* nextEdge = currentEdge->m_next;
- const auto v1 = nonUniformScale * AZ::Vector3(currentEdge->m_origin.GetX(), currentEdge->m_origin.GetY(), 0.0f);
- const auto v2 = nonUniformScale * AZ::Vector3(currentEdge->m_origin.GetX(), currentEdge->m_origin.GetY(), height);
- const auto v3 = nonUniformScale * AZ::Vector3(nextEdge->m_origin.GetX(), nextEdge->m_origin.GetY(), 0.0f);
- const auto v4 = nonUniformScale * AZ::Vector3(nextEdge->m_origin.GetX(), nextEdge->m_origin.GetY(), height);
- m_debugDrawPoints.insert(m_debugDrawPoints.end(), { v1, v2, v1, v3, v2, v4 });
- currentEdge = currentEdge->m_next;
- }
- }
- }
- m_debugDrawDirty = false;
- }
- return m_debugDrawPoints;
- }
- void Mesh2D::SetDebugDrawDirty()
- {
- m_debugDrawDirty = true;
- }
- void Mesh2D::Clear()
- {
- m_halfEdges.clear();
- m_faces.clear();
- m_edgeQueue = AZStd::priority_queue<InternalEdge, AZStd::vector<InternalEdge>, InternalEdgeCompare>();
- m_debugDrawDirty = true;
- }
- } // namespace PolygonPrismMeshUtils
|