1f9b284de2
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> # Conflicts: # Code/Editor/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin_precompiled.h # Code/Editor/Plugins/EditorCommon/EditorCommon_precompiled.h # Code/Editor/Plugins/EditorCommon/stdafx.cpp # Code/Editor/Plugins/FFMPEGPlugin/FFMPEGPlugin_precompiled.h # Code/Editor/Plugins/PerforcePlugin/PerforcePlugin_precompiled.h # Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsTool_precompiled.h # Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFramework_precompiled.h # Code/Tools/AssetProcessor/native/precompiled.h # Code/Tools/Standalone/StandaloneTools_precompiled.h # Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer_precompiled.h # Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessing_precompiled.h # Gems/Atom/RHI/DX12/Code/Source/RHI/Atom_RHI_DX12_precompiled.h # Gems/Atom/RHI/Metal/Code/Include/Platform/Mac/Atom_RHI_Metal_precompiled_Platform.h # Gems/Atom/RHI/Metal/Code/Include/Platform/iOS/Atom_RHI_Metal_precompiled_Platform.h # Gems/Atom/RHI/Metal/Code/Source/Atom_RHI_Metal_precompiled.h # Gems/Atom/RHI/Metal/Code/atom_rhi_metal_common_files.cmake # Gems/Atom/RHI/Null/Code/Source/Atom_RHI_Null_precompiled.h # Gems/Atom/RHI/Null/Code/atom_rhi_null_common_files.cmake # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Android/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Linux/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Mac/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Windows/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Source/Atom_RHI_Vulkan_precompiled.h # Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp # Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_common_files.cmake # Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont_precompiled.h # Gems/Blast/Code/Source/StdAfx.cpp # Gems/Camera/Code/Source/Camera_precompiled.h # Gems/EMotionFX/Code/Source/EMotionFX_precompiled.h # Gems/FastNoise/Code/Source/FastNoise_precompiled.h # Gems/Gestures/Code/Source/Gestures_precompiled.h # Gems/GradientSignal/Code/Source/GradientSignal_precompiled.h # Gems/GraphCanvas/Code/precompiled.h # Gems/ImGui/Code/Source/ImGui_precompiled.h # Gems/InAppPurchases/Code/Source/InAppPurchases_precompiled.h # Gems/LmbrCentral/Code/Source/LmbrCentral_precompiled.h # Gems/LmbrCentral/Code/Tests/ShapeGeometryUtilTest.cpp # Gems/LyShine/Code/Editor/UiCanvasEditor_precompiled.h # Gems/LyShine/Code/Source/Animation/LyShine_precompiled.h # Gems/LyShine/Code/Source/LyShine_precompiled.h # Gems/LyShineExamples/Code/Source/LyShineExamples_precompiled.h # Gems/Maestro/Code/Source/Cinematics/Maestro_precompiled.h # Gems/Maestro/Code/Source/Maestro_precompiled.h # Gems/MessagePopup/Code/Source/MessagePopup_precompiled.h # Gems/Metastream/Code/Source/Metastream_precompiled.h # Gems/Microphone/Code/Source/Microphone_precompiled.h # Gems/Multiplayer/Code/Source/Multiplayer_precompiled.h # Gems/PhysX/Code/NumericalMethods/Source/NumericalMethods_precompiled.h # Gems/PhysX/Code/Source/PhysXUnsupported_precompiled.h # Gems/PhysX/Code/Source/PhysX_precompiled.h # Gems/PhysX/Code/physx_unsupported_files.cmake # Gems/PhysXDebug/Code/Source/PhysXDebugUnsupported_precompiled.h # Gems/PhysXDebug/Code/Source/PhysXDebug_precompiled.h # Gems/ScriptCanvas/Code/Editor/precompiled.h # Gems/ScriptCanvas/Code/Source/precompiled.h # Gems/ScriptCanvasDeveloper/Code/Source/precompiled.h # Gems/ScriptCanvasPhysics/Code/Source/ScriptCanvasPhysics_precompiled.h # Gems/ScriptEvents/Code/Source/precompiled.h # Gems/ScriptEvents/Code/Tests/Editor/EditorTests.cpp # Gems/ScriptedEntityTweener/Code/Source/ScriptedEntityTweener_precompiled.h # Gems/SliceFavorites/Code/Source/SliceFavorites_precompiled.h # Gems/StartingPointCamera/Code/Source/StartingPointCamera_precompiled.h # Gems/StartingPointInput/Code/Source/StartingPointInput_precompiled.h # Gems/StartingPointMovement/Code/Source/StartingPointMovement_precompiled.h # Gems/SurfaceData/Code/Source/SurfaceData_precompiled.h # Gems/TextureAtlas/Code/Source/TextureAtlas_precompiled.h # Gems/TickBusOrderViewer/Code/Source/TickBusOrderViewer_precompiled.h # Gems/Twitch/Code/Source/Twitch_precompiled.h # Gems/VirtualGamepad/Code/Source/VirtualGamepad_precompiled.h # Gems/WhiteBox/Code/Source/WhiteBoxUnsupported_precompiled.h # Gems/WhiteBox/Code/Source/WhiteBox_precompiled.h
310 lines
13 KiB
C++
310 lines
13 KiB
C++
/*
|
|
* 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
|