You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/WhiteBox/Code/Source/Core/WhiteBoxToolApi.cpp

3546 lines
150 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 "Util/WhiteBoxMathUtil.h"
#include "Util/WhiteBoxTextureUtil.h"
#include <AzCore/Console/Console.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Math/Matrix3x4.h>
#include <AzCore/Math/MathStringConversions.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/hash.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/std/sort.h>
#include <AzCore/std/string/conversions.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <WhiteBox/WhiteBoxToolApi.h>
namespace OpenMesh
{
// Overload methods need to be declared before including OpenMesh so their definitions are found
inline AZ::Vector3 normalize(const AZ::Vector3& v)
{
AZ::Vector3 vret = v;
vret.Normalize();
return vret;
}
inline float dot(const AZ::Vector3& v1, const AZ::Vector3& v2)
{
return v1.Dot(v2);
}
inline float norm(const AZ::Vector3& v)
{
return v.GetLength();
}
inline AZ::Vector3 cross(const AZ::Vector3& v1, const AZ::Vector3& v2)
{
return v1.Cross(v2);
}
inline AZ::Vector3 vectorize(AZ::Vector3& v, float s)
{
v = AZ::Vector3(s);
return v;
}
inline void newell_norm(AZ::Vector3& n, const AZ::Vector3& a, const AZ::Vector3& b)
{
n.SetX(n.GetX() + (a.GetY() * b.GetZ()));
n.SetY(n.GetY() + (a.GetZ() * b.GetX()));
n.SetZ(n.GetZ() + (a.GetX() * b.GetY()));
}
}
// OpenMesh includes
AZ_PUSH_DISABLE_WARNING(4702, "-Wunknown-warning-option") // OpenMesh\Core\Utils\Property.hh has unreachable code
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Core/IO/SR_binary.hh>
#include <OpenMesh/Core/IO/importer/ImporterT.hh>
#include <OpenMesh/Core/Mesh/Traits.hh>
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <OpenMesh/Core/Utils/GenProg.hh>
#include <OpenMesh/Core/Utils/vector_traits.hh>
AZ_POP_DISABLE_WARNING
AZ_DECLARE_BUDGET(AzToolsFramework);
namespace OpenMesh
{
template<>
struct vector_traits<AZ::Vector3>
{
//! Type of the vector class
using vector_type = AZ::Vector3;
//! Type of the scalar value
using value_type = float;
//! size/dimension of the vector
static const size_t size_ = 3;
//! size/dimension of the vector
static size_t size()
{
return size_;
}
};
template<>
struct vector_traits<AZ::Vector2>
{
//! Type of the vector class
using vector_type = AZ::Vector2;
//! Type of the scalar value
using value_type = float;
//! size/dimension of the vector
static const size_t size_ = 2;
//! size/dimension of the vector
static size_t size()
{
return size_;
}
};
template<>
inline void vector_cast(const AZ::Vector3& src, OpenMesh::Vec3f& dst, GenProg::Int2Type<3> /*unused*/)
{
dst[0] = static_cast<vector_traits<Vec3f>::value_type>(src.GetX());
dst[1] = static_cast<vector_traits<Vec3f>::value_type>(src.GetY());
dst[2] = static_cast<vector_traits<Vec3f>::value_type>(src.GetZ());
}
template<>
inline void vector_cast(const AZ::Vector2& src, OpenMesh::Vec2f& dst, GenProg::Int2Type<2> /*unused*/)
{
dst[0] = static_cast<vector_traits<Vec2f>::value_type>(src.GetX());
dst[1] = static_cast<vector_traits<Vec2f>::value_type>(src.GetY());
}
template<>
inline void vector_cast(const OpenMesh::Vec3f& src, AZ::Vector3& dst, GenProg::Int2Type<3> /*unused*/)
{
dst.SetX(static_cast<vector_traits<Vec3f>::value_type>(src[0]));
dst.SetY(static_cast<vector_traits<Vec3f>::value_type>(src[1]));
dst.SetZ(static_cast<vector_traits<Vec3f>::value_type>(src[2]));
}
template<>
inline void vector_cast(const OpenMesh::Vec2f& src, AZ::Vector2& dst, GenProg::Int2Type<2> /*unused*/)
{
dst.SetX(static_cast<vector_traits<Vec2f>::value_type>(src[0]));
dst.SetY(static_cast<vector_traits<Vec2f>::value_type>(src[1]));
}
template<>
inline void vector_cast(const AZ::Vector3& src, OpenMesh::Vec3d& dst, GenProg::Int2Type<3> /*unused*/)
{
dst[0] = static_cast<vector_traits<Vec3d>::value_type>(src.GetX());
dst[1] = static_cast<vector_traits<Vec3d>::value_type>(src.GetY());
dst[2] = static_cast<vector_traits<Vec3d>::value_type>(src.GetZ());
}
template<>
inline void vector_cast(const AZ::Vector2& src, OpenMesh::Vec2d& dst, GenProg::Int2Type<2> /*unused*/)
{
dst[0] = static_cast<vector_traits<Vec2d>::value_type>(src.GetX());
dst[1] = static_cast<vector_traits<Vec2d>::value_type>(src.GetY());
}
template<>
inline void vector_cast(const OpenMesh::Vec3d& src, AZ::Vector3& dst, GenProg::Int2Type<3> /*unused*/)
{
dst.SetX(static_cast<float>(src[0]));
dst.SetY(static_cast<float>(src[1]));
dst.SetZ(static_cast<float>(src[2]));
}
template<>
inline void vector_cast(const OpenMesh::Vec2d& src, AZ::Vector2& dst, GenProg::Int2Type<2> /*unused*/)
{
dst.SetX(static_cast<float>(src[0]));
dst.SetY(static_cast<float>(src[1]));
}
} // namespace OpenMesh
#ifdef AZ_ENABLE_TRACING
#define WHITEBOX_LOG(str, ...) \
do \
{ \
if (cl_whiteBoxLogMessages) \
{ \
AZ_Printf(str, __VA_ARGS__) \
} \
} while (0);
#else
#define WHITEBOX_LOG(str, ...)
#endif
namespace WhiteBox
{
// cvar for logging debug messages
AZ_CVAR(bool, cl_whiteBoxLogMessages, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Log debug messages.");
struct WhiteBoxTraits : public OpenMesh::DefaultTraits
{
using Point = AZ::Vector3;
using Normal = AZ::Vector3;
using TexCoord2D = AZ::Vector2;
using TexCoord3D = AZ::Vector3;
};
using Mesh = OpenMesh::TriMesh_ArrayKernelT<WhiteBoxTraits>;
} // namespace WhiteBox
namespace AZStd
{
template<>
struct hash<WhiteBox::Mesh::FaceHandle>
{
public:
size_t operator()(const WhiteBox::Mesh::FaceHandle& faceHandle) const
{
size_t h{0};
AZStd::hash_combine(h, faceHandle.idx());
return h;
}
};
} // namespace AZStd
namespace WhiteBox
{
// alias for vector of OpenMesh FaceHandles
using FaceHandlesInternal = AZStd::vector<Mesh::FaceHandle>;
// a property to map from a FaceHandle to the Polygon it corresponds to
// note: PolygonHandle will include the FaceHandle used to do the lookup
using FaceHandlePolygonMapping = AZStd::unordered_map<Mesh::FaceHandle, FaceHandlesInternal>;
using PolygonPropertyHandle = OpenMesh::MPropHandleT<FaceHandlePolygonMapping>;
// unique string to lookup the polygon custom property via get_property_handle
static const char* const PolygonProps = "polygon-props";
// a property to track the hidden state of a vertex
using VertexBoolPropertyHandle = OpenMesh::VPropHandleT<bool>;
// unique string to lookup the vertex custom property via get_property_handle
static const char* const VertexHiddenProp = "vertex-hidden-props";
} // namespace WhiteBox
namespace OpenMesh::IO
{
template<>
struct binary<WhiteBox::FaceHandlesInternal>
{
using value_type = WhiteBox::FaceHandlesInternal;
static const bool is_streamable = true;
// return generic binary size of self, if known
static size_t size_of()
{
return UnknownSize;
}
// return binary size of the value
static size_t size_of(const value_type& _v)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (_v.empty())
{
return sizeof(uint32_t);
}
value_type::const_iterator it = _v.begin();
const auto count = static_cast<uint32_t>(_v.size());
size_t bytes = IO::size_of(count);
for (; it != _v.end(); ++it)
{
bytes += IO::size_of(it->idx());
}
return bytes;
}
static size_t store(std::ostream& _os, const value_type& _v, bool _swap = false)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
size_t bytes = 0;
const auto count = static_cast<uint32_t>(_v.size());
value_type::const_iterator it = _v.begin();
bytes += IO::store(_os, count, _swap);
for (; it != _v.end() && _os.good(); ++it)
{
bytes += IO::store(_os, (*it).idx(), _swap);
}
return _os.good() ? bytes : 0;
}
static size_t restore(std::istream& _is, value_type& _v, bool _swap = false)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
size_t bytes = 0;
uint32_t count = 0;
_v.clear();
bytes += IO::restore(_is, count, _swap);
_v.reserve(count);
for (size_t i = 0; i < count && _is.good(); ++i)
{
int elem; // value_type::value_type -> Mesh::FaceHandle (underlying type int)
bytes += IO::restore(_is, elem, _swap);
_v.push_back(value_type::value_type(elem));
}
return _is.good() ? bytes : 0;
}
};
template<>
struct binary<WhiteBox::FaceHandlePolygonMapping>
{
using value_type = WhiteBox::FaceHandlePolygonMapping;
static const bool is_streamable = true;
// return generic binary size of self, if known
static size_t size_of()
{
return UnknownSize;
}
// return binary size of the value
static size_t size_of(const value_type& _v)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (_v.empty())
{
return sizeof(uint32_t);
}
value_type::const_iterator it = _v.begin();
const auto count = static_cast<uint32_t>(_v.size());
size_t bytes = IO::size_of(count);
for (; it != _v.end(); ++it)
{
bytes += IO::size_of(it->first.idx());
bytes += IO::size_of(it->second);
}
return bytes;
}
static size_t store(std::ostream& _os, const value_type& _v, bool _swap = false)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
size_t bytes = 0;
const auto count = static_cast<uint32_t>(_v.size());
value_type::const_iterator it = _v.begin();
bytes += IO::store(_os, count, _swap);
for (; it != _v.end() && _os.good(); ++it)
{
bytes += IO::store(_os, it->first.idx(), _swap);
bytes += IO::store(_os, it->second, _swap);
}
return _os.good() ? bytes : 0;
}
static size_t restore(std::istream& _is, value_type& _v, bool _swap = false)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
size_t bytes = 0;
uint32_t count = 0;
_v.clear();
bytes += IO::restore(_is, count, _swap);
value_type::mapped_type val;
for (size_t i = 0; i < count && _is.good(); ++i)
{
int key; // value_type::key_type -> Mesh::FaceHandle (underlying type int)
bytes += IO::restore(_is, key, _swap);
bytes += IO::restore(_is, val, _swap);
_v[value_type::key_type(key)] = val;
}
return _is.good() ? bytes : 0;
}
};
} // namespace OpenMesh::IO
namespace WhiteBox
{
static const float NormalTolerance = 0.99f;
static const float AdjacentPolygonNormalTolerance = 0.0001f;
// A wrapper for the OpenMesh source data.
struct WhiteBoxMesh
{
AZ_CLASS_ALLOCATOR(WhiteBoxMesh, AZ::SystemAllocator, 0);
WhiteBoxMesh() = default;
WhiteBoxMesh(WhiteBoxMesh&&) = default;
WhiteBoxMesh& operator=(WhiteBoxMesh&&) = default;
Mesh mesh; //!< The OpenMesh triangle mesh kernel (with customized AZ traits).
};
// 0,0 is tl - 1,1 is br
// 3 is 0,0
// 2 is 1,0
// 1 is 1,1
// 0 is 0,1
const Mesh::TexCoord2D g_quadUVs[] = {
Mesh::TexCoord2D(0.0f, 1.0f),
Mesh::TexCoord2D(1.0f, 1.0f),
Mesh::TexCoord2D(1.0f, 0.0f),
Mesh::TexCoord2D(0.0f, 0.0f),
};
// conversion functions between OpenMesh and AZ types
// convert WhiteBox face handle to OpenMesh face handle
static Mesh::FaceHandle om_fh(const Api::FaceHandle fh)
{
return Mesh::FaceHandle{fh.Index()};
}
// convert WhiteBox vertex handle to OpenMesh vertex handle
static Mesh::VertexHandle om_vh(const Api::VertexHandle vh)
{
return Mesh::VertexHandle{vh.Index()};
}
// convert WhiteBox edge handle to OpenMesh edge handle
static Mesh::EdgeHandle om_eh(const Api::EdgeHandle eh)
{
return Mesh::EdgeHandle{eh.Index()};
}
// convert WhiteBox halfedge handle to OpenMesh halfedge handle
static Mesh::HalfedgeHandle om_heh(const Api::HalfedgeHandle heh)
{
return Mesh::HalfedgeHandle{heh.Index()};
}
// convert OpenMesh face handle to WhiteBox face handle
static Api::FaceHandle wb_fh(const Mesh::FaceHandle fh)
{
return Api::FaceHandle{fh.idx()};
}
// convert OpenMesh vertex handle to WhiteBox vertex handle
static Api::VertexHandle wb_vh(const Mesh::VertexHandle vh)
{
return Api::VertexHandle{vh.idx()};
}
// convert OpenMesh halfedge handle to WhiteBox halfedge handle
static Api::HalfedgeHandle wb_heh(const Mesh::HalfedgeHandle heh)
{
return Api::HalfedgeHandle{heh.idx()};
}
// convert OpenMesh edge handle to WhiteBox edge handle
static Api::EdgeHandle wb_eh(const Mesh::EdgeHandle eh)
{
return Api::EdgeHandle{eh.idx()};
}
// map from internal handles to external handles
Api::PolygonHandle PolygonHandleFromInternal(const FaceHandlesInternal& faceHandlesInternal)
{
Api::PolygonHandle polygonHandle;
polygonHandle.m_faceHandles.reserve(faceHandlesInternal.size());
AZStd::transform(
faceHandlesInternal.begin(), faceHandlesInternal.end(), AZStd::back_inserter(polygonHandle.m_faceHandles),
&wb_fh);
return polygonHandle;
}
FaceHandlesInternal InternalFaceHandlesFromPolygon(const Api::PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
FaceHandlesInternal faceHandlesInternal;
faceHandlesInternal.reserve(polygonHandle.m_faceHandles.size());
AZStd::transform(
polygonHandle.m_faceHandles.begin(), polygonHandle.m_faceHandles.end(),
AZStd::back_inserter(faceHandlesInternal), &om_fh);
return faceHandlesInternal;
}
namespace Api
{
AZStd::mutex g_omSerializationLock; // serialization lock required when using Open Mesh IOManager
namespace Internal
{
// when performing an append (extrusion or impression) new vertices will
// be added to the mesh - this struct maps from the existing vertex and the
// newly added one at the same location.
// note: it is possible that as part of an impression, m_existing and m_added
// both refer to the same vertex handle as the same vertex will be reused
struct VertexHandlePair
{
VertexHandle m_existing;
VertexHandle m_added;
VertexHandlePair() = default;
VertexHandlePair(const VertexHandle existing, const VertexHandle added)
: m_existing(existing)
, m_added(added)
{
}
};
// a collection of VertexHandlePairs
// generated as part of an append (extrusion or impression)
struct AppendedVerts
{
AZStd::vector<VertexHandlePair> m_vertexHandlePairs;
};
// intermediate data to use when appending an edge (performing an 'edge extrusion')
struct EdgeAppendVertexHandles
{
PolygonHandle m_existingPolygonHandle; // the polygon to be replaced by the new edge extrusion
// the vertices to use when 'appending' new geometry to the mesh while performing an edge extrusion.
VertexHandle m_toVertexHandle;
VertexHandle m_fromVertexHandle;
VertexHandle m_addedFromVertexHandle;
VertexHandle m_addedToVertexHandle;
VertexHandle m_afterToVertexHandle;
VertexHandle m_beforeFromVertexHandle;
};
// intermediate data to use when appending an edge (performing an 'edge extrusion')
struct EdgeAppendPolygonHandles
{
PolygonHandle m_nearPolygonHandle;
PolygonHandle m_farPolygonHandle;
PolygonHandle m_topPolygonHandle;
PolygonHandle m_bottomPolygonHandle;
};
} // namespace Internal
// forward declarations
AZStd::vector<FaceVertHandles> BuildNewVertexFaceHandles(
WhiteBoxMesh& whiteBox, const Internal::AppendedVerts& appendedVerts, const FaceHandles& existingFaces);
void RemoveFaces(WhiteBoxMesh& whiteBox, const FaceHandles& faceHandles);
void CalculatePlanarUVs(WhiteBoxMesh& whiteBox, const FaceHandles& faceHandles);
// restores WhiteBoxMesh properties, use when properties have not been initialized or have been cleared
static void InitializeWhiteBoxMesh(WhiteBoxMesh& whiteBox)
{
// add default properties for all white box meshes
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.add_property(polygonPropsHandle, PolygonProps);
whiteBox.mesh.mproperty(polygonPropsHandle).set_persistent(true);
VertexBoolPropertyHandle vertexPropsHiddenHandle;
whiteBox.mesh.add_property(vertexPropsHiddenHandle, VertexHiddenProp);
whiteBox.mesh.property(vertexPropsHiddenHandle).set_persistent(true);
// request default properties required for all white box meshes
whiteBox.mesh.request_face_normals();
whiteBox.mesh.request_halfedge_texcoords2D();
}
WhiteBoxMeshPtr CreateWhiteBoxMesh()
{
auto whiteBox = WhiteBoxMeshPtr(aznew WhiteBoxMesh());
InitializeWhiteBoxMesh(*whiteBox);
return whiteBox;
}
void WhiteBoxMeshDeleter::DestroyWhiteBoxMesh(WhiteBoxMesh* whiteBox)
{
delete whiteBox;
}
VertexHandles MeshVertexHandles(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
VertexHandles vertexHandles;
vertexHandles.reserve(whiteBox.mesh.n_vertices());
for (const auto& vertexHandle : whiteBox.mesh.vertices())
{
vertexHandles.push_back(wb_vh(vertexHandle));
}
return vertexHandles;
}
FaceHandles MeshFaceHandles(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
FaceHandles faceHandles;
faceHandles.reserve(whiteBox.mesh.n_faces());
for (const auto& faceHandle : whiteBox.mesh.faces())
{
faceHandles.push_back(wb_fh(faceHandle));
}
return faceHandles;
}
PolygonHandles MeshPolygonHandles(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
const auto& polygonProps = whiteBox.mesh.property(polygonPropsHandle);
AZStd::vector<PolygonHandle> polygonHandles;
for (const auto& polygonProp : polygonProps)
{
// don't add duplicate polygons
PolygonHandle polygonHandle = PolygonHandleFromInternal(polygonProp.second);
if (AZStd::find(polygonHandles.begin(), polygonHandles.end(), polygonHandle) == polygonHandles.end())
{
polygonHandles.push_back(AZStd::move(polygonHandle));
}
}
return polygonHandles;
}
EdgeHandlesCollection PolygonBorderEdgeHandles(const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const HalfedgeHandlesCollection halfedgeHandlesCollection =
PolygonBorderHalfedgeHandles(whiteBox, polygonHandle);
EdgeHandlesCollection orderedEdgeHandlesCollection;
orderedEdgeHandlesCollection.reserve(halfedgeHandlesCollection.size());
for (const auto& halfedgeHandles : halfedgeHandlesCollection)
{
EdgeHandles orderedEdgeHandles;
orderedEdgeHandles.reserve(halfedgeHandles.size());
for (const auto& halfedgeHandle : halfedgeHandles)
{
orderedEdgeHandles.push_back(HalfedgeEdgeHandle(whiteBox, halfedgeHandle));
}
orderedEdgeHandlesCollection.push_back(orderedEdgeHandles);
}
return orderedEdgeHandlesCollection;
}
EdgeHandles PolygonBorderEdgeHandlesFlattened(const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const EdgeHandlesCollection borderEdgeHandlesCollection = PolygonBorderEdgeHandles(whiteBox, polygonHandle);
EdgeHandles polygonBorderEdgeHandles;
for (const auto& borderEdgeHandles : borderEdgeHandlesCollection)
{
polygonBorderEdgeHandles.insert(
polygonBorderEdgeHandles.end(), borderEdgeHandles.cbegin(), borderEdgeHandles.cend());
}
return polygonBorderEdgeHandles;
}
EdgeHandles MeshPolygonEdgeHandles(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
auto polygonHandles = MeshPolygonHandles(whiteBox);
EdgeHandles allEdgeHandles;
for (const auto& polygonHandle : polygonHandles)
{
auto polygonEdgeHandles = PolygonBorderEdgeHandlesFlattened(whiteBox, polygonHandle);
allEdgeHandles.insert(allEdgeHandles.end(), polygonEdgeHandles.begin(), polygonEdgeHandles.end());
}
// remove duplicates
AZStd::sort(allEdgeHandles.begin(), allEdgeHandles.end());
allEdgeHandles.erase(AZStd::unique(allEdgeHandles.begin(), allEdgeHandles.end()), allEdgeHandles.end());
return allEdgeHandles;
}
EdgeHandles MeshEdgeHandles(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
EdgeHandles edgeHandles;
edgeHandles.reserve(whiteBox.mesh.n_edges());
for (const auto& edgeHandle : whiteBox.mesh.edges())
{
edgeHandles.push_back(wb_eh(edgeHandle));
}
return edgeHandles;
}
EdgeTypes MeshUserEdgeHandles(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
EdgeHandles userEdgeHandles = MeshPolygonEdgeHandles(whiteBox);
AZStd::sort(userEdgeHandles.begin(), userEdgeHandles.end());
EdgeHandles allEdgeHandles = MeshEdgeHandles(whiteBox);
AZStd::sort(allEdgeHandles.begin(), allEdgeHandles.end());
EdgeHandles meshEdgeHandles;
meshEdgeHandles.reserve(allEdgeHandles.size()); // over reserve vector
AZStd::set_difference(
allEdgeHandles.begin(), allEdgeHandles.end(), userEdgeHandles.begin(), userEdgeHandles.end(),
AZStd::back_inserter(meshEdgeHandles));
return {AZStd::move(userEdgeHandles), AZStd::move(meshEdgeHandles)};
}
AZStd::vector<AZ::Vector3> MeshVertexPositions(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return VertexPositions(whiteBox, MeshVertexHandles(whiteBox));
}
HalfedgeHandles FaceHalfedgeHandles(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
HalfedgeHandles halfedgeHandles;
halfedgeHandles.reserve(3);
for (Mesh::ConstFaceHalfedgeCCWIter faceHalfedgeIt = whiteBox.mesh.cfh_ccwiter(om_fh(faceHandle));
faceHalfedgeIt.is_valid(); ++faceHalfedgeIt)
{
halfedgeHandles.push_back(wb_heh(*faceHalfedgeIt));
}
return halfedgeHandles;
}
EdgeHandles FaceEdgeHandles(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
if (!faceHandle.IsValid())
{
return {};
}
EdgeHandles edgeHandles;
edgeHandles.reserve(3);
for (const auto& halfedgeHandle : FaceHalfedgeHandles(whiteBox, faceHandle))
{
edgeHandles.push_back(HalfedgeEdgeHandle(whiteBox, halfedgeHandle));
}
return edgeHandles;
}
VertexHandles FaceVertexHandles(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
if (!faceHandle.IsValid())
{
return {};
}
VertexHandles vertexHandles;
vertexHandles.reserve(3);
for (const auto& halfedgeHandle : FaceHalfedgeHandles(whiteBox, faceHandle))
{
vertexHandles.emplace_back(HalfedgeVertexHandleAtTip(whiteBox, halfedgeHandle));
}
return vertexHandles;
}
AZStd::vector<AZ::Vector3> FaceVertexPositions(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
return VertexPositions(whiteBox, FaceVertexHandles(whiteBox, faceHandle));
}
AZStd::vector<AZ::Vector3> FacesPositions(const WhiteBoxMesh& whiteBox, const FaceHandles& faceHandles)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
AZStd::vector<AZ::Vector3> triangles;
triangles.reserve(faceHandles.size() * 3);
for (const auto& faceHandle : faceHandles)
{
const auto corners = FaceVertexPositions(whiteBox, faceHandle);
triangles.insert(triangles.end(), corners.begin(), corners.end());
}
return triangles;
}
FaceHandle HalfedgeFaceHandle(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
if (halfedgeHandle.IsValid())
{
return wb_fh(whiteBox.mesh.face_handle(om_heh(halfedgeHandle)));
}
return {};
}
HalfedgeHandle HalfedgeOppositeHalfedgeHandle(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
if (halfedgeHandle.IsValid())
{
return wb_heh(whiteBox.mesh.opposite_halfedge_handle(om_heh(halfedgeHandle)));
}
return {};
}
FaceHandle HalfedgeOppositeFaceHandle(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
if (halfedgeHandle.IsValid())
{
return wb_fh(whiteBox.mesh.opposite_face_handle(om_heh(halfedgeHandle)));
}
return {};
}
HalfedgeHandles VertexOutgoingHalfedgeHandles(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
HalfedgeHandles outgoingHalfEdgeHandles;
for (auto oheh = whiteBox.mesh.cvoh_ccwbegin(om_vh(vertexHandle));
oheh != whiteBox.mesh.cvoh_ccwend(om_vh(vertexHandle)); ++oheh)
{
outgoingHalfEdgeHandles.push_back(wb_heh(*oheh));
}
return outgoingHalfEdgeHandles;
}
HalfedgeHandles VertexIncomingHalfedgeHandles(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
HalfedgeHandles incomingHalfEdgeHandles;
for (auto iheh = whiteBox.mesh.cvih_ccwbegin(om_vh(vertexHandle));
iheh != whiteBox.mesh.cvih_ccwend(om_vh(vertexHandle)); ++iheh)
{
incomingHalfEdgeHandles.push_back(wb_heh(*iheh));
}
return incomingHalfEdgeHandles;
}
HalfedgeHandles VertexHalfedgeHandles(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
HalfedgeHandles outgoingHandles = VertexOutgoingHalfedgeHandles(whiteBox, vertexHandle);
HalfedgeHandles incomingHandles = VertexIncomingHalfedgeHandles(whiteBox, vertexHandle);
auto allHandles = AZStd::move(outgoingHandles);
allHandles.insert(
allHandles.end(), AZStd::make_move_iterator(incomingHandles.begin()),
AZStd::make_move_iterator(incomingHandles.end()));
return allHandles;
}
EdgeHandles VertexEdgeHandles(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto omVertexHandle = om_vh(vertexHandle);
return AZStd::accumulate(
whiteBox.mesh.cve_ccwbegin(omVertexHandle), whiteBox.mesh.cve_ccwend(omVertexHandle), EdgeHandles{},
[](EdgeHandles edgeHandles, const auto edgeHandle)
{
edgeHandles.push_back(wb_eh(edgeHandle));
return edgeHandles;
});
}
static bool BuildFaceHandles(
const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle, FaceHandles& faceHandles,
const AZ::Vector3& normal)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto* const found_fh = AZStd::find(faceHandles.cbegin(), faceHandles.cend(), faceHandle);
if (found_fh == faceHandles.cend())
{
const AZ::Vector3 nextNormal = FaceNormal(whiteBox, faceHandle).GetNormalized();
if (OpenMesh::dot(nextNormal, normal) > NormalTolerance)
{
faceHandles.push_back(faceHandle);
return true;
}
}
return false;
}
static FaceHandle OppositeFaceHandle(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const HalfedgeHandle oppositeHalfedgeHandle = HalfedgeOppositeHalfedgeHandle(whiteBox, halfedgeHandle);
if (HalfedgeIsBoundary(whiteBox, oppositeHalfedgeHandle))
{
return {};
}
// note: oppositeFaceHandle will be invalid if oppositeHalfedgeHandle is a boundary
const FaceHandle oppositeFaceHandle = HalfedgeFaceHandle(whiteBox, oppositeHalfedgeHandle);
return oppositeFaceHandle;
}
static void SideFaceHandlesInternal(
const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle, FaceHandles& faceHandles,
const AZ::Vector3& normal)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (BuildFaceHandles(whiteBox, faceHandle, faceHandles, normal))
{
// all halfedges for a given face
const auto halfedges = FaceHalfedgeHandles(whiteBox, faceHandle);
for (const HalfedgeHandle& halfedgeHandle : halfedges)
{
const FaceHandle oppositeFaceHandle = OppositeFaceHandle(whiteBox, halfedgeHandle);
if (oppositeFaceHandle.IsValid())
{
SideFaceHandlesInternal(whiteBox, oppositeFaceHandle, faceHandles, normal);
}
}
}
}
FaceHandles SideFaceHandles(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
FaceHandles faceHandles;
SideFaceHandlesInternal(
whiteBox, faceHandle, faceHandles, FaceNormal(whiteBox, faceHandle).GetNormalized());
return faceHandles;
}
static HalfedgeHandlesCollection BorderHalfedgeHandles(
const WhiteBoxMesh& whiteBox, const FaceHandles& faceHandles)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// build all possible halfedge handles
HalfedgeHandles halfedgeHandles;
for (const auto& faceHandle : faceHandles)
{
// find all vertices for a given face
const auto vertexHandles = FaceVertexHandles(whiteBox, faceHandle);
for (const auto& vertexHandle : vertexHandles)
{
// find all outgoing halfedges from vertex
const auto outgoingHalfedgeHandles = VertexOutgoingHalfedgeHandles(whiteBox, vertexHandle);
for (const auto& halfedgeHandle : outgoingHalfedgeHandles)
{
// find what face corresponds to this halfedge
const FaceHandle halfedgeFaceHandle = HalfedgeFaceHandle(whiteBox, halfedgeHandle);
// if the halfedge corresponds to a face on this side
if (AZStd::find(faceHandles.cbegin(), faceHandles.cend(), halfedgeFaceHandle) !=
faceHandles.end())
{
// check the opposite face handle
const FaceHandle oppositeFaceHandle = HalfedgeOppositeFaceHandle(whiteBox, halfedgeHandle);
// if the opposite face handle isn't on this side, we know it is a 'boundary' halfedge
if (AZStd::find(faceHandles.cbegin(), faceHandles.cend(), oppositeFaceHandle) ==
faceHandles.end())
{
// check we haven't already stored this halfedge
if (AZStd::find(halfedgeHandles.cbegin(), halfedgeHandles.cend(), halfedgeHandle) ==
halfedgeHandles.cend())
{
// add to border halfedges
halfedgeHandles.push_back(halfedgeHandle);
}
}
}
}
}
}
// handle potentially pathological case where all edges have
// been hidden and no halfedge loop can be found
if (halfedgeHandles.empty())
{
return {};
}
HalfedgeHandlesCollection orderHalfedgeHandlesCollection;
// can sort based on tip/tail
HalfedgeHandles orderedHalfedgeHandles;
orderedHalfedgeHandles.push_back(halfedgeHandles.back());
halfedgeHandles.pop_back();
// empty our list of unordered border side halfedge handles
while (!halfedgeHandles.empty())
{
// use next vertex to get halfedges in order
const VertexHandle nextVertex = HalfedgeVertexHandleAtTip(whiteBox, orderedHalfedgeHandles.back());
// find next ordered halfedge
const auto* const nextHalfedge = AZStd::find_if(
halfedgeHandles.cbegin(), halfedgeHandles.cend(),
[nextVertex, &whiteBox](auto halfedgeHandle)
{
return nextVertex == HalfedgeVertexHandleAtTail(whiteBox, halfedgeHandle);
});
// if we found it
if (nextHalfedge != halfedgeHandles.end())
{
// add it to the ordered list and remove it from the unordered list
orderedHalfedgeHandles.push_back(*nextHalfedge);
halfedgeHandles[nextHalfedge - halfedgeHandles.begin()] = halfedgeHandles.back();
halfedgeHandles.pop_back();
}
else
{
// cycle detected, start a new list
orderHalfedgeHandlesCollection.push_back(orderedHalfedgeHandles);
orderedHalfedgeHandles.clear();
orderedHalfedgeHandles.push_back(halfedgeHandles.back());
halfedgeHandles.pop_back();
}
}
if (halfedgeHandles.empty())
{
AZ_Assert(!orderedHalfedgeHandles.empty(), "No ordered halfedges generated");
orderHalfedgeHandlesCollection.push_back(orderedHalfedgeHandles);
}
// finally return the ordered list
return orderHalfedgeHandlesCollection;
}
HalfedgeHandlesCollection SideBorderHalfedgeHandles(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// find all face handles for a side
return BorderHalfedgeHandles(whiteBox, SideFaceHandles(whiteBox, faceHandle));
}
static VertexHandlesCollection BorderVertexHandles(
const WhiteBoxMesh& whiteBox, const HalfedgeHandlesCollection& halfedgeHandlesCollection)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
VertexHandlesCollection orderedVertexHandlesCollection;
orderedVertexHandlesCollection.reserve(halfedgeHandlesCollection.size());
for (const auto& halfedgeHandles : halfedgeHandlesCollection)
{
VertexHandles orderedVertexHandles;
orderedVertexHandles.reserve(halfedgeHandles.size());
for (const auto& halfedgeHandle : halfedgeHandles)
{
orderedVertexHandles.push_back(HalfedgeVertexHandleAtTip(whiteBox, halfedgeHandle));
}
orderedVertexHandlesCollection.push_back(orderedVertexHandles);
}
return orderedVertexHandlesCollection;
}
VertexHandlesCollection SideBorderVertexHandles(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return BorderVertexHandles(whiteBox, SideBorderHalfedgeHandles(whiteBox, faceHandle));
}
static VertexHandles FacesVertexHandles(const WhiteBoxMesh& whiteBox, const FaceHandles& faceHandles)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
VertexHandles vertexHandles;
for (const FaceHandle& faceHandle : faceHandles)
{
const auto faceVertexHandles = FaceVertexHandles(whiteBox, faceHandle);
for (const VertexHandle& faceVertexHandle : faceVertexHandles)
{
const auto* const vertexIt =
AZStd::find(vertexHandles.cbegin(), vertexHandles.cend(), faceVertexHandle);
// ensure we do not add duplicate vertices
if (vertexIt == vertexHandles.end())
{
vertexHandles.push_back(faceVertexHandle);
}
}
}
return vertexHandles;
}
VertexHandles SideVertexHandles(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return FacesVertexHandles(whiteBox, SideFaceHandles(whiteBox, faceHandle));
}
VertexHandle HalfedgeVertexHandleAtTip(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
return wb_vh(whiteBox.mesh.to_vertex_handle(om_heh(halfedgeHandle)));
}
VertexHandle HalfedgeVertexHandleAtTail(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
return wb_vh(whiteBox.mesh.from_vertex_handle(om_heh(halfedgeHandle)));
}
AZ::Vector3 HalfedgeVertexPositionAtTip(const WhiteBoxMesh& whiteBox, HalfedgeHandle halfedgeHandle)
{
return VertexPosition(whiteBox, HalfedgeVertexHandleAtTip(whiteBox, halfedgeHandle));
}
AZ::Vector3 HalfedgeVertexPositionAtTail(const WhiteBoxMesh& whiteBox, HalfedgeHandle halfedgeHandle)
{
return VertexPosition(whiteBox, HalfedgeVertexHandleAtTail(whiteBox, halfedgeHandle));
}
EdgeHandle HalfedgeEdgeHandle(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
return wb_eh(whiteBox.mesh.edge_handle(om_heh(halfedgeHandle)));
}
bool HalfedgeIsBoundary(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
return whiteBox.mesh.is_boundary(om_heh(halfedgeHandle));
}
HalfedgeHandle HalfedgeHandleNext(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
return wb_heh(whiteBox.mesh.next_halfedge_handle(om_heh(halfedgeHandle)));
}
HalfedgeHandle HalfedgeHandlePrevious(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
return wb_heh(whiteBox.mesh.prev_halfedge_handle(om_heh(halfedgeHandle)));
}
static AZStd::array<AZ::Vector3, 2> EdgeVertexPositions(
const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const VertexHandle vertexHandle)
{
if (const auto vertexHandles = EdgeVertexHandles(whiteBox, edgeHandle); vertexHandle.IsValid())
{
const auto otherVertexHandle = vertexHandles[0] == vertexHandle ? vertexHandles[1] : vertexHandles[0];
return {VertexPosition(whiteBox, vertexHandle), VertexPosition(whiteBox, otherVertexHandle)};
}
else
{
return {VertexPosition(whiteBox, vertexHandles[0]), VertexPosition(whiteBox, vertexHandles[1])};
}
}
AZStd::array<AZ::Vector3, 2> EdgeVertexPositions(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
return EdgeVertexPositions(whiteBox, edgeHandle, VertexHandle{});
}
AZStd::array<VertexHandle, 2> EdgeVertexHandles(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
// note: first halfedge handle should always exist
if (const auto halfedgeHandle = EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::First);
halfedgeHandle.IsValid())
{
return {
HalfedgeVertexHandleAtTail(whiteBox, halfedgeHandle),
HalfedgeVertexHandleAtTip(whiteBox, halfedgeHandle)};
}
AZ_Assert(false, "Could not find Vertex Handles for Edge Handle %d", edgeHandle.Index());
return {VertexHandle{}, VertexHandle{}};
}
// provide the ability to pass a vertex handle to explicitly determine the direction of the axis
static AZ::Vector3 EdgeVectorWithStartingVertexHandle(
const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const VertexHandle vertexHandle)
{
const auto edgeVertexPositions = EdgeVertexPositions(whiteBox, edgeHandle, vertexHandle);
return (edgeVertexPositions[1] - edgeVertexPositions[0]);
}
AZ::Vector3 EdgeVector(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
return EdgeVectorWithStartingVertexHandle(whiteBox, edgeHandle, VertexHandle{});
}
// provide the ability to pass a vertex handle to explicitly determine the direction of the axis
static AZ::Vector3 EdgeAxisWithStartingVertexHandle(
const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const VertexHandle vertexHandle)
{
if (const AZ::Vector3 edgeVector = EdgeVectorWithStartingVertexHandle(whiteBox, edgeHandle, vertexHandle);
edgeVector.GetLength() > 0.0f)
{
return edgeVector / edgeVector.GetLength();
}
return AZ::Vector3::CreateZero();
}
AZ::Vector3 EdgeAxis(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
return EdgeAxisWithStartingVertexHandle(whiteBox, edgeHandle, VertexHandle{});
}
bool EdgeIsBoundary(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
return whiteBox.mesh.is_boundary(om_eh(edgeHandle));
}
// note: halfedge handle must be from the edge handle passed in
static bool EdgeIsUser(
const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle, const EdgeHandle edgeHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto polygonEdgeHandles = PolygonBorderEdgeHandlesFlattened(
whiteBox, FacePolygonHandle(whiteBox, HalfedgeFaceHandle(whiteBox, halfedgeHandle)));
return AZStd::find(polygonEdgeHandles.cbegin(), polygonEdgeHandles.cend(), edgeHandle) !=
polygonEdgeHandles.cend();
}
// note: overload of EdgeIsUser that does not require halfedgeHande to be
// passed in but does slightly more work
static bool EdgeIsUser(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
const auto halfedgeHandle = EdgeHalfedgeHandles(whiteBox, edgeHandle);
return AZStd::any_of(
AZStd::cbegin(halfedgeHandle), AZStd::cend(halfedgeHandle),
[&whiteBox, edgeHandle](const HalfedgeHandle halfedgeHandle)
{
return EdgeIsUser(whiteBox, halfedgeHandle, edgeHandle);
});
}
EdgeHandles EdgeGrouping(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// a non-user ('mesh') edge is never part of a grouping so if one is passed
// in ensure we return an empty group
if (!EdgeIsUser(whiteBox, edgeHandle))
{
return EdgeHandles{};
}
// the edge group to begin building
EdgeHandles edgeGrouping = {edgeHandle};
// get vertex handles from the hovered/selected edge
const auto initialVertexHandles = Api::EdgeVertexHandles(whiteBox, edgeHandle);
auto vertexHandles = VertexHandles{initialVertexHandles.cbegin(), initialVertexHandles.cend()};
// track all vertices we've already seen
auto visitedVertexHandles = VertexHandles{};
while (!vertexHandles.empty())
{
const auto vertexHandle = vertexHandles.back();
vertexHandles.pop_back();
// if the vertex is not hidden this is where the search ends
if (!VertexIsHidden(whiteBox, vertexHandle))
{
continue;
}
visitedVertexHandles.push_back(vertexHandle);
// for all connected vertex handles to this edge
for (const auto& vertexEdgeHandle : VertexEdgeHandles(whiteBox, vertexHandle))
{
// check all halfedges in the edge
for (const auto& halfedgeHandle : EdgeHalfedgeHandles(whiteBox, vertexEdgeHandle))
{
// only track the edge if it's a 'user' edge (selectable - not a 'mesh' edge)
if (!EdgeIsUser(whiteBox, halfedgeHandle, vertexEdgeHandle))
{
continue;
}
// check if have we already added the edge to the grouping
if (AZStd::find(edgeGrouping.cbegin(), edgeGrouping.cend(), vertexEdgeHandle) !=
edgeGrouping.cend())
{
continue;
}
// store the edge to the grouping
edgeGrouping.push_back(vertexEdgeHandle);
for (const auto& nextVertexHandle : Api::EdgeVertexHandles(whiteBox, vertexEdgeHandle))
{
// if we haven't seen this vertex yet, add it to
// the vertex handles to explore
if (AZStd::find(
visitedVertexHandles.cbegin(), visitedVertexHandles.cend(), nextVertexHandle) ==
visitedVertexHandles.cend())
{
vertexHandles.push_back(nextVertexHandle);
}
}
}
}
}
return edgeGrouping;
}
bool EdgeIsHidden(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const EdgeHandles userEdgeHandles = MeshPolygonEdgeHandles(whiteBox);
return AZStd::find(userEdgeHandles.cbegin(), userEdgeHandles.cend(), edgeHandle) == userEdgeHandles.cend();
}
AZStd::vector<FaceHandle> EdgeFaceHandles(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto openMeshEdgeHandle = om_eh(edgeHandle);
const auto firstHalfedgeHandle = whiteBox.mesh.halfedge_handle(openMeshEdgeHandle, 0);
const auto secondHalfedgeHandle = whiteBox.mesh.halfedge_handle(openMeshEdgeHandle, 1);
AZ_Assert(
firstHalfedgeHandle.is_valid() || secondHalfedgeHandle.is_valid(),
"There should be at least one valid half edge handle for any given edge");
AZStd::vector<FaceHandle> validFaceHandles;
// only one face handle is valid at mesh boundaries
if (const auto firstFaceHandle = whiteBox.mesh.face_handle(firstHalfedgeHandle); firstFaceHandle.is_valid())
{
validFaceHandles.push_back(wb_fh(firstFaceHandle));
}
if (const auto secondFaceHandle = whiteBox.mesh.face_handle(secondHalfedgeHandle);
secondFaceHandle.is_valid())
{
validFaceHandles.push_back(wb_fh(secondFaceHandle));
}
return validFaceHandles;
}
static size_t EdgeHalfedgeMapping(const EdgeHalfedge edgeHalfedge)
{
switch (edgeHalfedge)
{
case EdgeHalfedge::First:
return 0;
case EdgeHalfedge::Second:
return 1;
default:
AZ_Assert(false, "Invalid EdgeHalfedge type passed");
return 2;
}
}
HalfedgeHandle EdgeHalfedgeHandle(
const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const EdgeHalfedge edgeHalfedge)
{
return wb_heh(whiteBox.mesh.halfedge_handle(om_eh(edgeHandle), static_cast<unsigned int>(EdgeHalfedgeMapping(edgeHalfedge))));
}
HalfedgeHandles EdgeHalfedgeHandles(const WhiteBoxMesh& whiteBox, EdgeHandle edgeHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const AZStd::array<HalfedgeHandle, 2> halfedgeHandles = {
EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::First),
EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::Second)};
return AZStd::accumulate(
halfedgeHandles.cbegin(), halfedgeHandles.cend(), HalfedgeHandles{},
[&whiteBox](HalfedgeHandles halfedgeHandles, const HalfedgeHandle halfedgeHandle)
{
if (!HalfedgeIsBoundary(whiteBox, halfedgeHandle))
{
halfedgeHandles.push_back(halfedgeHandle);
}
return halfedgeHandles;
});
}
void TranslateEdge(WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const AZ::Vector3& displacement)
{
WHITEBOX_LOG(
"White Box", "TranslateEdge eh(%s) %s", ToString(edgeHandle).c_str(), AZStd::to_string(displacement).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto vertexHandles = EdgeVertexHandles(whiteBox, edgeHandle);
for (const auto& vertexHandle : vertexHandles)
{
auto position = VertexPosition(whiteBox, vertexHandle);
position += displacement;
SetVertexPosition(whiteBox, vertexHandle, position);
}
CalculateNormals(whiteBox);
CalculatePlanarUVs(whiteBox);
}
// Given a displacement in local space applied to an edge, find the halfedge handle that the
// edge is most likely moving towards. We're attempting to infer the user's intention which is
// never perfect so there's a chance we may not return the edge the user expects. On the
// whole the heuristic used (delta distance moved towards a connected face midpoint) is pretty stable.
static HalfedgeHandle FindBestFitHalfedge(
WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const AZ::Vector3& displacement)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// get both halfedge handles for the edge (0 and 1 just correspond to each halfedge)
const HalfedgeHandle firstHalfedgeHandle = EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::First);
const HalfedgeHandle secondHalfedgeHandle = EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::Second);
// get all vertices for each face (triangle) that each halfedge corresponds to
const auto firstFaceVertexHandles =
FaceVertexHandles(whiteBox, HalfedgeFaceHandle(whiteBox, firstHalfedgeHandle));
const auto secondFaceVertexHandles =
FaceVertexHandles(whiteBox, HalfedgeFaceHandle(whiteBox, secondHalfedgeHandle));
// calculate the midpoint of each face
const auto firstFaceMidpoint = VerticesMidpoint(whiteBox, firstFaceVertexHandles);
const auto secondFaceMidpoint = VerticesMidpoint(whiteBox, secondFaceVertexHandles);
// calculate the midpoint of the edge we wish to append to and where it will be after the displacement
const auto edgeMidpoint = EdgeMidpoint(whiteBox, edgeHandle);
const auto nextEdgePosition = edgeMidpoint + displacement;
// calculate how far the center of each face is from the edge midpoint
const auto distanceFromFirstFace = (firstFaceMidpoint - edgeMidpoint).GetLength();
const auto distanceFromSecondFace = (secondFaceMidpoint - edgeMidpoint).GetLength();
// then calculate how far the center of each face is from the edge midpoint plus the displacement
const auto nextDistanceFromFirstFace = (nextEdgePosition - firstFaceMidpoint).GetLength();
const auto nextDistanceFromSecondFace = (nextEdgePosition - secondFaceMidpoint).GetLength();
// next see what the delta is from next and current positions
// this is to determine did the displacement move us towards the first or second face
// i.e. infer which way the user dragged
const auto nextDeltaFromFirstFace = nextDistanceFromFirstFace - distanceFromFirstFace;
const auto nextDeltaFromSecondFace = nextDistanceFromSecondFace - distanceFromSecondFace;
// pick the best halfedge we inferred
const EdgeHalfedge halfedge =
nextDeltaFromFirstFace < nextDeltaFromSecondFace ? EdgeHalfedge::First : EdgeHalfedge::Second;
return EdgeHalfedgeHandle(whiteBox, edgeHandle, halfedge);
}
// Determine the vertices required to append the new edge geometry being created
static Internal::EdgeAppendVertexHandles CalculateEdgeAppendVertexHandles(
WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const AZ::Vector3& displacement)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// based on the displacement find which halfedge is a better fit (which direction did we move in)
const HalfedgeHandle halfedgeHandle = FindBestFitHalfedge(whiteBox, edgeHandle, displacement);
const FaceHandle faceHandle = HalfedgeFaceHandle(whiteBox, halfedgeHandle);
// find the polygon this face handle corresponds to
const PolygonHandle polygonHandle = FacePolygonHandle(whiteBox, faceHandle);
// find all border vertex handles for this polygon
const VertexHandlesCollection polygonBorderVertexHandlesCollection =
PolygonBorderVertexHandles(whiteBox, polygonHandle);
// following the direction of the halfedge, what vertex is it pointing to
const VertexHandle toVertexHandle = HalfedgeVertexHandleAtTip(whiteBox, halfedgeHandle);
// following the direction of the halfedge, what vertex is coming from
const VertexHandle fromVertexHandle = HalfedgeVertexHandleAtTail(whiteBox, halfedgeHandle);
// find which vertex loop the vertex is in based on the halfedge we've selected
const VertexHandles borderVertexHandles = AZStd::accumulate(
AZStd::begin(polygonBorderVertexHandlesCollection), AZStd::end(polygonBorderVertexHandlesCollection),
VertexHandles{},
[toVertexHandle](VertexHandles borderVertexHandles, const VertexHandles& vertexHandles)
{
// check if the vertex is in this loop of the collection
// (there may be 1 - * loops in the collection)
if (AZStd::find(AZStd::begin(vertexHandles), AZStd::end(vertexHandles), toVertexHandle) !=
AZStd::end(vertexHandles))
{
// if so add the vertex handles for this loop to be returned
borderVertexHandles.insert(
borderVertexHandles.end(), vertexHandles.begin(), vertexHandles.end());
}
return borderVertexHandles;
});
// find the index of the vertex handle in the polygon handle collection
const auto toVertexHandlePolygonIndex =
AZStd::find(borderVertexHandles.begin(), borderVertexHandles.end(), toVertexHandle) -
borderVertexHandles.begin();
const auto fromVertexHandlePolygonIndex =
AZStd::find(borderVertexHandles.begin(), borderVertexHandles.end(), fromVertexHandle) -
borderVertexHandles.begin();
// we then want to find the vertex after the 'to' vertex, and the vertex before the 'from' vertex
const VertexHandle afterToVertexHandle = borderVertexHandles
[((toVertexHandlePolygonIndex + borderVertexHandles.size()) + 1) % borderVertexHandles.size()];
const VertexHandle beforeFromVertexHandle = borderVertexHandles
[((fromVertexHandlePolygonIndex + borderVertexHandles.size()) - 1) % borderVertexHandles.size()];
// find the position of the 'to' and 'from' vertex handle
const Mesh::Point toVertexPosition = VertexPosition(whiteBox, toVertexHandle);
const Mesh::Point fromVertexPosition = VertexPosition(whiteBox, fromVertexHandle);
// find the next position by moving the previous positions by the displacement
const Mesh::Point nextToVertexPosition = toVertexPosition + displacement;
const Mesh::Point nextFromVertexPosition = fromVertexPosition + displacement;
// add two new vertices in the new positions
const VertexHandle addedToVertexHandle = AddVertex(whiteBox, nextToVertexPosition);
const VertexHandle addedFromVertexHandle = AddVertex(whiteBox, nextFromVertexPosition);
// populate data for the next stage
Internal::EdgeAppendVertexHandles edgeAppendHandles;
edgeAppendHandles.m_existingPolygonHandle = polygonHandle;
edgeAppendHandles.m_toVertexHandle = toVertexHandle;
edgeAppendHandles.m_fromVertexHandle = fromVertexHandle;
edgeAppendHandles.m_addedFromVertexHandle = addedFromVertexHandle;
edgeAppendHandles.m_addedToVertexHandle = addedToVertexHandle;
edgeAppendHandles.m_afterToVertexHandle = afterToVertexHandle;
edgeAppendHandles.m_beforeFromVertexHandle = beforeFromVertexHandle;
return edgeAppendHandles;
}
// After determining the vertex handles required, build the polygons for the new appended edge
static Internal::EdgeAppendPolygonHandles AddNewPolygonsForEdgeAppend(
WhiteBoxMesh& whiteBox, const Internal::EdgeAppendVertexHandles& edgeAppendVertexHandles)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
Internal::EdgeAppendPolygonHandles edgeAppendPolygonHandles;
// build two faces to make up the polygon on the 'near' side of the edge
AZStd::vector<FaceVertHandles> nearFaceHandles = {
FaceVertHandles{
edgeAppendVertexHandles.m_fromVertexHandle, edgeAppendVertexHandles.m_toVertexHandle,
edgeAppendVertexHandles.m_addedToVertexHandle},
FaceVertHandles{
edgeAppendVertexHandles.m_fromVertexHandle, edgeAppendVertexHandles.m_addedToVertexHandle,
edgeAppendVertexHandles.m_addedFromVertexHandle}};
edgeAppendPolygonHandles.m_nearPolygonHandle = AddPolygon(whiteBox, nearFaceHandles);
AZStd::vector<FaceVertHandles> farFaceHandles;
// note: need to check the number of faces for the polygon we'll be replacing with the edge append
if (edgeAppendVertexHandles.m_existingPolygonHandle.m_faceHandles.size() > 1)
{
// build two faces to make up the polygon on the 'far' side of the edge
farFaceHandles = {
FaceVertHandles{
edgeAppendVertexHandles.m_addedFromVertexHandle, edgeAppendVertexHandles.m_addedToVertexHandle,
edgeAppendVertexHandles.m_afterToVertexHandle},
FaceVertHandles{
edgeAppendVertexHandles.m_addedFromVertexHandle, edgeAppendVertexHandles.m_afterToVertexHandle,
edgeAppendVertexHandles.m_beforeFromVertexHandle}};
}
else
{
// build one face to make up the polygon on the 'far' side of the edge
// if we're extruding an edge on a triangle not a quad
farFaceHandles = {FaceVertHandles{
edgeAppendVertexHandles.m_addedFromVertexHandle, edgeAppendVertexHandles.m_addedToVertexHandle,
edgeAppendVertexHandles.m_afterToVertexHandle}};
}
edgeAppendPolygonHandles.m_farPolygonHandle = AddPolygon(whiteBox, farFaceHandles);
// add the top triangle for the edge extrusion
const AZStd::vector<FaceVertHandles> topFaceHandles = {FaceVertHandles{
edgeAppendVertexHandles.m_fromVertexHandle, edgeAppendVertexHandles.m_addedFromVertexHandle,
edgeAppendVertexHandles.m_beforeFromVertexHandle}};
edgeAppendPolygonHandles.m_topPolygonHandle = AddPolygon(whiteBox, topFaceHandles);
// add the bottom triangle for the edge extrusion
const AZStd::vector<FaceVertHandles> bottomFaceHandles = {FaceVertHandles{
edgeAppendVertexHandles.m_toVertexHandle, edgeAppendVertexHandles.m_afterToVertexHandle,
edgeAppendVertexHandles.m_addedToVertexHandle}};
edgeAppendPolygonHandles.m_bottomPolygonHandle = AddPolygon(whiteBox, bottomFaceHandles);
return edgeAppendPolygonHandles;
}
// given two polygon handles, return the (first) edge that is shared between the two polygons
// note: this may not always give expected results for polygons with greater than two faces
static EdgeHandle FindSelectedEdgeHandle(
const WhiteBoxMesh& whiteBox, const PolygonHandle& nearPolygonHandle, const PolygonHandle& farPolygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// actually find the new edge we created
const EdgeHandles nearEdgeHandles = PolygonBorderEdgeHandlesFlattened(whiteBox, nearPolygonHandle);
const EdgeHandles farEdgeHandles = PolygonBorderEdgeHandlesFlattened(whiteBox, farPolygonHandle);
// add all edges and find the one duplicate (this will be the new edge we want to return to the caller)
EdgeHandles allEdgeHandles;
allEdgeHandles.reserve(nearEdgeHandles.size() + farEdgeHandles.size());
allEdgeHandles.insert(allEdgeHandles.end(), nearEdgeHandles.begin(), nearEdgeHandles.end());
allEdgeHandles.insert(allEdgeHandles.end(), farEdgeHandles.begin(), farEdgeHandles.end());
AZStd::sort(allEdgeHandles.begin(), allEdgeHandles.end());
auto* edgeIt = AZStd::adjacent_find(allEdgeHandles.begin(), allEdgeHandles.end());
if (edgeIt != allEdgeHandles.end())
{
return *edgeIt;
}
return EdgeHandle{};
}
static bool EdgeExtrusionAllowed(const PolygonHandle& polygonHandle)
{
// currently only allow edge extrusion for quad polygons
return polygonHandle.m_faceHandles.size() <= 2;
}
EdgeHandle TranslateEdgeAppend(
WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const AZ::Vector3& displacement)
{
WHITEBOX_LOG(
"White Box", "TranslateEdgeAppend eh(%s) %s", ToString(edgeHandle).c_str(), AZStd::to_string(displacement).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
// the new and existing handles required for an edge append
const Internal::EdgeAppendVertexHandles edgeAppendVertexHandles =
CalculateEdgeAppendVertexHandles(whiteBox, edgeHandle, displacement);
// if edge extrusion is not allowed simply return the previous edge handle
if (!EdgeExtrusionAllowed(edgeAppendVertexHandles.m_existingPolygonHandle))
{
return edgeHandle;
}
// remove the current polygon (two new polygons will later be inserted in its place)
RemoveFaces(whiteBox, edgeAppendVertexHandles.m_existingPolygonHandle.m_faceHandles);
const Internal::EdgeAppendPolygonHandles edgeAppendPolygonHandles =
AddNewPolygonsForEdgeAppend(whiteBox, edgeAppendVertexHandles);
// update internal state
CalculateNormals(whiteBox);
CalculatePlanarUVs(whiteBox);
return FindSelectedEdgeHandle(
whiteBox, edgeAppendPolygonHandles.m_nearPolygonHandle, edgeAppendPolygonHandles.m_farPolygonHandle);
}
AZ::Vector3 PolygonNormal(const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return AZStd::accumulate(
polygonHandle.m_faceHandles.cbegin(), polygonHandle.m_faceHandles.cend(),
AZ::Vector3::CreateZero(),
[&whiteBox](const AZ::Vector3& normal, const FaceHandle faceHandle)
{
return normal + FaceNormal(whiteBox, faceHandle);
})
.GetNormalizedSafe();
}
PolygonHandle FacePolygonHandle(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
const auto& polygonProps = whiteBox.mesh.property(polygonPropsHandle);
const auto polygonIt = polygonProps.find(om_fh(faceHandle));
if (polygonIt != polygonProps.end())
{
return PolygonHandleFromInternal(polygonIt->second);
}
return {};
}
VertexHandles PolygonVertexHandles(const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return FacesVertexHandles(whiteBox, polygonHandle.m_faceHandles);
}
VertexHandlesCollection PolygonBorderVertexHandles(
const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return BorderVertexHandles(whiteBox, PolygonBorderHalfedgeHandles(whiteBox, polygonHandle));
}
VertexHandles PolygonBorderVertexHandlesFlattened(
const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const VertexHandlesCollection borderVertexHandlesCollection =
BorderVertexHandles(whiteBox, PolygonBorderHalfedgeHandles(whiteBox, polygonHandle));
VertexHandles polygonBorderVertexHandles;
for (const auto& borderVertexHandles : borderVertexHandlesCollection)
{
polygonBorderVertexHandles.insert(
polygonBorderVertexHandles.end(), borderVertexHandles.cbegin(), borderVertexHandles.cend());
}
return polygonBorderVertexHandles;
}
HalfedgeHandles PolygonBorderHalfedgeHandlesFlattened(
const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const HalfedgeHandlesCollection borderHalfedgeHandlesCollection =
PolygonBorderHalfedgeHandles(whiteBox, polygonHandle);
HalfedgeHandles polygonBorderHalfedgeHandles;
for (const auto& borderHalfedgeHandles : borderHalfedgeHandlesCollection)
{
polygonBorderHalfedgeHandles.insert(
polygonBorderHalfedgeHandles.end(), borderHalfedgeHandles.cbegin(), borderHalfedgeHandles.cend());
}
return polygonBorderHalfedgeHandles;
}
HalfedgeHandles PolygonHalfedgeHandles(const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return AZStd::accumulate(
polygonHandle.m_faceHandles.cbegin(), polygonHandle.m_faceHandles.cend(), HalfedgeHandles{},
[&whiteBox](HalfedgeHandles halfedges, const FaceHandle faceHandle)
{
const HalfedgeHandles nextHalfedgeHandles = FaceHalfedgeHandles(whiteBox, faceHandle);
halfedges.insert(halfedges.end(), nextHalfedgeHandles.cbegin(), nextHalfedgeHandles.cend());
return halfedges;
});
}
HalfedgeHandlesCollection PolygonBorderHalfedgeHandles(
const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
return BorderHalfedgeHandles(whiteBox, polygonHandle.m_faceHandles);
}
AZStd::vector<AZ::Vector3> PolygonVertexPositions(
const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return VertexPositions(whiteBox, PolygonVertexHandles(whiteBox, polygonHandle));
}
VertexPositionsCollection PolygonBorderVertexPositions(
const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto polygonBorderVertexHandlesCollection = PolygonBorderVertexHandles(whiteBox, polygonHandle);
VertexPositionsCollection polygonBorderVertexPositionsCollection;
polygonBorderVertexPositionsCollection.reserve(polygonBorderVertexHandlesCollection.size());
for (const auto& polygonBorderVertexHandles : polygonBorderVertexHandlesCollection)
{
polygonBorderVertexPositionsCollection.push_back(VertexPositions(whiteBox, polygonBorderVertexHandles));
}
return polygonBorderVertexPositionsCollection;
}
AZStd::vector<AZ::Vector3> PolygonFacesPositions(
const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return FacesPositions(whiteBox, polygonHandle.m_faceHandles);
}
AZ::Vector3 VertexPosition(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
return whiteBox.mesh.point(om_vh(vertexHandle));
}
AZStd::vector<AZ::Vector3> VertexPositions(const WhiteBoxMesh& whiteBox, const VertexHandles& vertexHandles)
{
AZStd::vector<AZ::Vector3> positions;
positions.reserve(vertexHandles.size());
AZStd::transform(
vertexHandles.cbegin(), vertexHandles.cend(), AZStd::back_inserter(positions),
[&whiteBox](const auto vertexHandle)
{
return VertexPosition(whiteBox, vertexHandle);
});
return positions;
}
EdgeHandles VertexUserEdgeHandles(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
auto vertexEdgeHandles = VertexEdgeHandles(whiteBox, vertexHandle);
vertexEdgeHandles.erase(
AZStd::remove_if(
AZStd::begin(vertexEdgeHandles), AZStd::end(vertexEdgeHandles),
[&whiteBox](const EdgeHandle edgeHandle)
{
return !EdgeIsUser(whiteBox, edgeHandle);
}),
AZStd::end(vertexEdgeHandles));
return vertexEdgeHandles;
}
template<typename EdgeFn>
static AZStd::vector<AZ::Vector3> VertexUserEdges(
const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle, EdgeFn&& edgeFn)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto vertexEdgeHandles = VertexUserEdgeHandles(whiteBox, vertexHandle);
AZStd::vector<AZ::Vector3> edgeVectors;
edgeVectors.reserve(vertexEdgeHandles.size());
AZStd::transform(
AZStd::cbegin(vertexEdgeHandles), AZStd::cend(vertexEdgeHandles), AZStd::back_inserter(edgeVectors),
[&whiteBox, edgeFn, vertexHandle](const EdgeHandle edgeHandle)
{
return edgeFn(whiteBox, edgeHandle, vertexHandle);
});
// filter out any invalid edges
edgeVectors.erase(
AZStd::remove_if(
AZStd::begin(edgeVectors), AZStd::end(edgeVectors),
[](const AZ::Vector3& edge)
{
return AZ::IsCloseMag<float>(edge.GetLengthSq(), 0.0f);
}),
AZStd::end(edgeVectors));
return edgeVectors;
}
AZStd::vector<AZ::Vector3> VertexUserEdgeVectors(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
return VertexUserEdges(whiteBox, vertexHandle, &EdgeVectorWithStartingVertexHandle);
}
AZStd::vector<AZ::Vector3> VertexUserEdgeAxes(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
return VertexUserEdges(whiteBox, vertexHandle, &EdgeAxisWithStartingVertexHandle);
}
bool VertexIsHidden(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
VertexBoolPropertyHandle vertexPropsHiddenHandle;
whiteBox.mesh.get_property_handle(vertexPropsHiddenHandle, VertexHiddenProp);
return whiteBox.mesh.property(vertexPropsHiddenHandle, om_vh(vertexHandle));
}
bool VertexIsIsolated(const WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
const auto connectedEdgeHandles = VertexEdgeHandles(whiteBox, vertexHandle);
return AZStd::all_of(
AZStd::cbegin(connectedEdgeHandles), AZStd::cend(connectedEdgeHandles),
[&whiteBox](const EdgeHandle edgeHandle)
{
return !EdgeIsUser(whiteBox, edgeHandle);
});
}
AZ::Vector3 FaceNormal(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return whiteBox.mesh.normal(om_fh(faceHandle));
}
AZ::Vector2 HalfedgeUV(const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
return whiteBox.mesh.texcoord2D(om_heh(halfedgeHandle));
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// note: MeshXXXCount functions are only valid if garbage_collection is called after
// each face/vertex removal. If garbage_collection is deferred, the faces()/vertices()/halfedges()
// range must be used to count iterations via skipping iterator to ignore deleted faces
size_t MeshFaceCount(const WhiteBoxMesh& whiteBox)
{
return whiteBox.mesh.n_faces();
}
size_t MeshHalfedgeCount(const WhiteBoxMesh& whiteBox)
{
return whiteBox.mesh.n_halfedges();
}
size_t MeshVertexCount(const WhiteBoxMesh& whiteBox)
{
return whiteBox.mesh.n_vertices();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
Faces MeshFaces(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
Faces faces;
faces.reserve(MeshFaceCount(whiteBox));
for (const auto& faceHandle : MeshFaceHandles(whiteBox))
{
const auto halfEdgeHandles = FaceHalfedgeHandles(whiteBox, faceHandle);
Face face;
AZStd::transform(
halfEdgeHandles.begin(), halfEdgeHandles.end(), face.begin(),
[&whiteBox](const auto halfedgeHandle)
{
// calculate the position of each vertex at the tip of each vertex handle
return VertexPosition(whiteBox, HalfedgeVertexHandleAtTip(whiteBox, halfedgeHandle));
});
faces.push_back(face);
}
return faces;
}
void CalculatePlanarUVs(WhiteBoxMesh& whiteBox, const FaceHandles& faceHandles)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
auto& mesh = whiteBox.mesh;
for (const auto& faceHandle : faceHandles)
{
for (Mesh::ConstFaceHalfedgeCCWIter faceHalfedgeIt = mesh.fh_ccwiter(om_fh(faceHandle));
faceHalfedgeIt.is_valid(); ++faceHalfedgeIt)
{
const Mesh::HalfedgeHandle heh = *faceHalfedgeIt;
const Mesh::VertexHandle vh = mesh.to_vertex_handle(heh);
const AZ::Vector3 position = mesh.point(vh);
const AZ::Vector3 normal = FaceNormal(whiteBox, faceHandle);
const Mesh::TexCoord2D uv = CreatePlanarUVFromVertex(normal, position);
mesh.set_texcoord2D(heh, uv);
}
}
}
void CalculatePlanarUVs(WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
CalculatePlanarUVs(whiteBox, MeshFaceHandles(whiteBox));
}
static PolygonHandle MergeFaces(
const WhiteBoxMesh& whiteBox, const HalfedgeHandle halfedgeHandle,
const HalfedgeHandle oppositeHalfedgeHandle, const HalfedgeHandles& borderHalfedgeHandles,
const EdgeHandles& buildingEdgeHandles)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// the polygon handle to build
PolygonHandle polygonHandle;
// begin populating halfedges to visit to build a polygon
HalfedgeHandles halfedgesToVisit{halfedgeHandle};
// store already visited halfedges
HalfedgeHandles visitedHalfedges;
while (!halfedgesToVisit.empty())
{
const HalfedgeHandle halfedgeToVisit = halfedgesToVisit.back();
halfedgesToVisit.pop_back();
visitedHalfedges.push_back(halfedgeToVisit);
const FaceHandle faceHandleToVisit = HalfedgeFaceHandle(whiteBox, halfedgeToVisit);
const HalfedgeHandles faceHalfedges = FaceHalfedgeHandles(whiteBox, faceHandleToVisit);
// check we have not already visited this face handle
if (AZStd::find(
polygonHandle.m_faceHandles.cbegin(), polygonHandle.m_faceHandles.cend(), faceHandleToVisit) !=
polygonHandle.m_faceHandles.cend())
{
continue;
}
// store the face handle in this polygon
polygonHandle.m_faceHandles.push_back(faceHandleToVisit);
// for all halfedges
for (const auto& faceHalfedgeHandle : faceHalfedges)
{
const EdgeHandle edgeHandle = HalfedgeEdgeHandle(whiteBox, faceHalfedgeHandle);
// if we haven't seen this halfedge before and we want to track it,
// store it in visited halfedges
if (faceHalfedgeHandle != oppositeHalfedgeHandle
// ignore border halfedges (not inside the polygon)
&& AZStd::find(borderHalfedgeHandles.cbegin(), borderHalfedgeHandles.cend(), faceHalfedgeHandle) ==
borderHalfedgeHandles.cend()
// ensure we do not visit the same halfedge again
&& AZStd::find(visitedHalfedges.cbegin(), visitedHalfedges.cend(), faceHalfedgeHandle) ==
visitedHalfedges.cend()
// ignore the halfedge if we've already tracked it in our 'building' list
&& AZStd::find(buildingEdgeHandles.cbegin(), buildingEdgeHandles.cend(), edgeHandle) ==
buildingEdgeHandles.cend())
{
halfedgesToVisit.push_back(HalfedgeOppositeHalfedgeHandle(whiteBox, faceHalfedgeHandle));
}
}
}
// return the polygon we've built by traversing all connected face handles
// (by following the connected halfedges)
return polygonHandle;
}
static void PopulatePolygonProps(FaceHandlePolygonMapping& polygonProps, const FaceHandles& faceHandles)
{
for (const auto& faceHandle : faceHandles)
{
auto polygonIt = polygonProps.find(om_fh(faceHandle));
for (const auto& innerFaceHandle : faceHandles)
{
polygonIt->second.push_back(om_fh(innerFaceHandle));
}
}
}
static void ClearPolygonProps(FaceHandlePolygonMapping& polygonProps, const FaceHandles& faceHandles)
{
for (const auto& faceHandle : faceHandles)
{
if (auto polygonIt = polygonProps.find(om_fh(faceHandle)); polygonIt != polygonProps.end())
{
polygonIt->second.clear();
}
}
}
// restore all vertices along the restored edges (after creating a new polygon)
static void RestoreVertexHandlesForEdges(WhiteBoxMesh& whiteBox, const EdgeHandles& restoredEdgeHandles)
{
for (const auto& edgeHandle : restoredEdgeHandles)
{
for (const auto& vertexHandle : EdgeVertexHandles(whiteBox, edgeHandle))
{
RestoreVertex(whiteBox, vertexHandle);
}
}
}
AZStd::optional<AZStd::array<PolygonHandle, 2>> RestoreEdge(
WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, EdgeHandles& restoringEdgeHandles)
{
WHITEBOX_LOG("White Box", "RestoreEdge eh(%s)", ToString(edgeHandle).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
// check we're not selecting an existing user edge
if (!EdgeIsHidden(whiteBox, edgeHandle))
{
// do nothing
return {};
}
// attempt to make a new polygon if possible
const HalfedgeHandle firstHalfedgeHandle = EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::First);
const HalfedgeHandle secondHalfedgeHandle = EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::Second);
// the existing polygon, containing the edge we've selected
const PolygonHandle polygonHandle =
FacePolygonHandle(whiteBox, HalfedgeFaceHandle(whiteBox, firstHalfedgeHandle));
// all border halfedges (not necessarily contiguous)
const HalfedgeHandles polygonBorderHalfedgeHandles =
PolygonBorderHalfedgeHandlesFlattened(whiteBox, polygonHandle);
const auto firstPolygon = MergeFaces(
whiteBox, firstHalfedgeHandle, secondHalfedgeHandle, polygonBorderHalfedgeHandles,
restoringEdgeHandles);
const auto secondPolygon = MergeFaces(
whiteBox, secondHalfedgeHandle, firstHalfedgeHandle, polygonBorderHalfedgeHandles,
restoringEdgeHandles);
// check if the first and second polygons are identical,
// this can happen if the vertex list forms a loop
const bool identical = [first = firstPolygon, second = secondPolygon]() mutable
{
AZStd::sort(first.m_faceHandles.begin(), first.m_faceHandles.end());
AZStd::sort(second.m_faceHandles.begin(), second.m_faceHandles.end());
return first == second;
}();
// if the number of face handles in at least one of the new polygons is the
// same as the existing polygon, we know a new polygon has not been formed
// (the restored edge has not connected to another edge and created a new polygon)
if (firstPolygon.m_faceHandles.size() == polygonHandle.m_faceHandles.size() || identical)
{
restoringEdgeHandles.push_back(edgeHandle);
return {};
}
// get polygon property handle from mesh
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
auto& polygonProps = whiteBox.mesh.property(polygonPropsHandle);
// update all face handles to refer to the new face handles in the group
// clear existing face handles
ClearPolygonProps(polygonProps, polygonHandle.m_faceHandles);
// populate face handles for first polygon
PopulatePolygonProps(polygonProps, firstPolygon.m_faceHandles);
// populate face handles for second polygon
PopulatePolygonProps(polygonProps, secondPolygon.m_faceHandles);
// get all edges
const auto firstPolygonEdges = Api::PolygonBorderEdgeHandlesFlattened(whiteBox, firstPolygon);
const auto secondPolygonEdges = Api::PolygonBorderEdgeHandlesFlattened(whiteBox, secondPolygon);
Api::EdgeHandles allPolygonEdges;
allPolygonEdges.reserve(firstPolygonEdges.size() + secondPolygonEdges.size());
allPolygonEdges.insert(allPolygonEdges.end(), firstPolygonEdges.begin(), firstPolygonEdges.end());
allPolygonEdges.insert(allPolygonEdges.end(), secondPolygonEdges.begin(), secondPolygonEdges.end());
AZStd::sort(allPolygonEdges.begin(), allPolygonEdges.end());
allPolygonEdges.erase(AZStd::unique(allPolygonEdges.begin(), allPolygonEdges.end()), allPolygonEdges.end());
RestoreVertexHandlesForEdges(whiteBox, restoringEdgeHandles);
// remove all edges that make up the new polygons from the ones currently being restored
restoringEdgeHandles.erase(
AZStd::remove_if(
restoringEdgeHandles.begin(), restoringEdgeHandles.end(),
[&allPolygonEdges](const Api::EdgeHandle edgeHandle)
{
return AZStd::find(allPolygonEdges.begin(), allPolygonEdges.end(), edgeHandle) !=
allPolygonEdges.end();
}),
restoringEdgeHandles.end());
return AZStd::make_optional<AZStd::array<PolygonHandle, 2>>(
AZStd::array<PolygonHandle, 2>{firstPolygon, secondPolygon});
}
void RestoreVertex(WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
WHITEBOX_LOG("White Box", "RestoreVertex vh(%s)", ToString(vertexHandle).c_str());
VertexBoolPropertyHandle vertexPropsHiddenHandle;
whiteBox.mesh.get_property_handle(vertexPropsHiddenHandle, VertexHiddenProp);
whiteBox.mesh.property(vertexPropsHiddenHandle, om_vh(vertexHandle)) = false;
}
bool TryRestoreVertex(WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
WHITEBOX_LOG("White Box", "TryRestoreVertex vh(%s)", ToString(vertexHandle).c_str());
// if none of the connected edge handles are user edges then the vertex should not be restored
if (!VertexIsIsolated(whiteBox, vertexHandle))
{
RestoreVertex(whiteBox, vertexHandle);
return true;
}
return false;
}
PolygonHandle HideEdge(WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
WHITEBOX_LOG("White Box", "HideEdge eh(%s)", ToString(edgeHandle).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (MeshHalfedgeCount(whiteBox) == 0)
{
return {};
}
// get halfedge handles
const HalfedgeHandle firstHalfedgeHandle = EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::First);
const HalfedgeHandle secondHalfedgeHandle = EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::Second);
// get face handles from each halfedge
const FaceHandle firstFaceHandle = HalfedgeFaceHandle(whiteBox, firstHalfedgeHandle);
const FaceHandle secondFaceHandle = HalfedgeFaceHandle(whiteBox, secondHalfedgeHandle);
// get polygon handle from each face handle
const PolygonHandle firstPolygonHandle = FacePolygonHandle(whiteBox, firstFaceHandle);
const PolygonHandle secondPolygonHandle = FacePolygonHandle(whiteBox, secondFaceHandle);
// get all vertex handles associated with the first polygon
const VertexHandles firstPolygonVertexHandles = PolygonVertexHandles(whiteBox, firstPolygonHandle);
// union of all face handles
FaceHandles combinedFaceHandles;
combinedFaceHandles.reserve(
firstPolygonHandle.m_faceHandles.size() + secondPolygonHandle.m_faceHandles.size());
combinedFaceHandles.insert(
combinedFaceHandles.end(), firstPolygonHandle.m_faceHandles.begin(),
firstPolygonHandle.m_faceHandles.end());
combinedFaceHandles.insert(
combinedFaceHandles.end(), secondPolygonHandle.m_faceHandles.begin(),
secondPolygonHandle.m_faceHandles.end());
// get polygon property handle from mesh
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
auto& polygonProps = whiteBox.mesh.property(polygonPropsHandle);
// update all face handles to refer to the new face handles in the group
for (const auto& faceHandle : combinedFaceHandles)
{
auto polygonIt = polygonProps.find(om_fh(faceHandle));
polygonIt->second.clear();
for (const auto& innerFaceHandle : combinedFaceHandles)
{
polygonIt->second.push_back(om_fh(innerFaceHandle));
}
}
// hide any vertices that are not connected to a 'user' edge
for (const auto& vertexHandle : firstPolygonVertexHandles)
{
if (VertexIsIsolated(whiteBox, vertexHandle))
{
HideVertex(whiteBox, vertexHandle);
}
}
return PolygonHandle{combinedFaceHandles};
}
VertexHandle SplitFace(WhiteBoxMesh& whiteBox, const FaceHandle faceHandle, const AZ::Vector3& position)
{
WHITEBOX_LOG("White Box", "SplitFace fh(%s)", ToString(faceHandle).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto omFaceHandle = om_fh(faceHandle);
const auto omVertexHandle = whiteBox.mesh.split_copy(omFaceHandle, position);
const VertexHandle splitVertexHandle = wb_vh(omVertexHandle);
// as all new edges will be by default hidden, ensure
// the newly added vertex is also hidden
HideVertex(whiteBox, splitVertexHandle);
// build collection of current face handles for newly inserted vertex
auto omFaceHandles = AZStd::accumulate(
whiteBox.mesh.vf_ccwbegin(omVertexHandle), whiteBox.mesh.vf_ccwend(omVertexHandle),
AZStd::vector<Mesh::FaceHandle>{},
[](AZStd::vector<Mesh::FaceHandle> faceHandles, const Mesh::FaceHandle faceHandle)
{
faceHandles.push_back(faceHandle);
return faceHandles;
});
// get polygon property handle from mesh
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
auto& polygonProps = whiteBox.mesh.property(polygonPropsHandle);
// get all faces associated with the split face handle and added the newly split faces
// ensuring we do not have any duplicates
const auto polygonIt = polygonProps.find(om_fh(faceHandle));
omFaceHandles.insert(omFaceHandles.end(), polygonIt->second.begin(), polygonIt->second.end());
AZStd::sort(omFaceHandles.begin(), omFaceHandles.end());
omFaceHandles.erase(AZStd::unique(omFaceHandles.begin(), omFaceHandles.end()), omFaceHandles.end());
// update all face handles to point to the new polygon grouping
for (const auto& omFaceHandle2 : omFaceHandles)
{
polygonProps[omFaceHandle2] = omFaceHandles;
}
return splitVertexHandle;
}
VertexHandle SplitEdge(WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const AZ::Vector3& position)
{
WHITEBOX_LOG("White Box", "SplitEdge eh(%s)", ToString(edgeHandle).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
const HalfedgeHandle halfedgeHandle = EdgeHalfedgeHandle(whiteBox, edgeHandle, EdgeHalfedge::First);
const VertexHandle tailVertexHandle = HalfedgeVertexHandleAtTail(whiteBox, halfedgeHandle);
const VertexHandle tipVertexHandle = HalfedgeVertexHandleAtTip(whiteBox, halfedgeHandle);
const VertexHandle existingConnectedVerts[] = {tailVertexHandle, tipVertexHandle};
// determine if the edge is a user edge or not before the split
const bool userEdge = EdgeIsUser(whiteBox, halfedgeHandle, edgeHandle);
const auto omEdgeHandle = om_eh(edgeHandle);
const auto omVertexHandle = whiteBox.mesh.add_vertex(position);
whiteBox.mesh.split_copy(omEdgeHandle, omVertexHandle);
const VertexHandle splitVertexHandle = wb_vh(omVertexHandle);
// if the edge that was split was not a 'user' edge we should ensure the
// newly added vertex is also hidden
if (!userEdge)
{
HideVertex(whiteBox, splitVertexHandle);
}
// get all outing edge handles from the new inserted vertex
const EdgeHandles splitEdgeHandles = VertexEdgeHandles(whiteBox, splitVertexHandle);
AZStd::for_each(
splitEdgeHandles.cbegin(), splitEdgeHandles.cend(),
[&whiteBox, &existingConnectedVerts](const EdgeHandle edgeHandle)
{
const auto vertexHandles = EdgeVertexHandles(whiteBox, edgeHandle);
const bool alreadyConnectedVertex = AZStd::any_of(
AZStd::cbegin(existingConnectedVerts), AZStd::cend(existingConnectedVerts),
[&vertexHandles](const VertexHandle vertexHandle)
{
return AZStd::find(
AZStd::cbegin(vertexHandles), AZStd::cend(vertexHandles), vertexHandle) !=
AZStd::cend(vertexHandles);
});
// find if the edge was added or is part of the existing edge which was split
if (!alreadyConnectedVertex)
{
const FaceHandles faceHandles = EdgeFaceHandles(whiteBox, edgeHandle);
const PolygonHandle polygonHandle = FacePolygonHandle(whiteBox, faceHandles[0]);
// if the edge was not already connected to on of the existing verts,
// find the associated polygon handle update them with the newly split faces
const PolygonHandle existingPolygonHandle = polygonHandle.m_faceHandles.empty()
? FacePolygonHandle(whiteBox, faceHandles[1])
: polygonHandle;
const FaceHandle newFaceHandle =
polygonHandle.m_faceHandles.empty() ? faceHandles[0] : faceHandles[1];
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
auto& polygonProps = whiteBox.mesh.property(polygonPropsHandle);
auto omExistingPolygonHandle = InternalFaceHandlesFromPolygon(existingPolygonHandle);
omExistingPolygonHandle.push_back(om_fh(newFaceHandle));
// update all face handles to point to the new polygon grouping
for (const Mesh::FaceHandle& faceHandle : omExistingPolygonHandle)
{
polygonProps[faceHandle] = omExistingPolygonHandle;
}
}
});
return splitVertexHandle;
}
bool FlipEdge(WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
WHITEBOX_LOG("White Box", "FlipEdge eh(%s)", ToString(edgeHandle).c_str());
const auto omEdgeHandle = om_eh(edgeHandle);
// check if edge can be flipped
const bool canFlip = whiteBox.mesh.is_flip_ok(omEdgeHandle) && EdgeIsHidden(whiteBox, edgeHandle);
if (canFlip)
{
whiteBox.mesh.flip(omEdgeHandle);
}
return canFlip;
}
void HideVertex(WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle)
{
WHITEBOX_LOG("White Box", "HideVertex vh(%s)", ToString(vertexHandle).c_str());
VertexBoolPropertyHandle vertexPropsHiddenHandle;
whiteBox.mesh.get_property_handle(vertexPropsHiddenHandle, VertexHiddenProp);
whiteBox.mesh.property(vertexPropsHiddenHandle, om_vh(vertexHandle)) = true;
}
void Clear(WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
whiteBox.mesh.remove_property(polygonPropsHandle);
VertexBoolPropertyHandle vertexPropsHiddenHandle;
whiteBox.mesh.get_property_handle(vertexPropsHiddenHandle, VertexHiddenProp);
whiteBox.mesh.remove_property(vertexPropsHiddenHandle);
whiteBox.mesh.clear();
InitializeWhiteBoxMesh(whiteBox);
}
PolygonHandle AddTriPolygon(
WhiteBoxMesh& whiteBox, const VertexHandle vh0, const VertexHandle vh1, const VertexHandle vh2)
{
WHITEBOX_LOG(
"White Box", "AddTriPolygon vh(%s), vh(%s), vh(%s)", ToString(vh0).c_str(), ToString(vh1).c_str(),
ToString(vh2).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
return AddPolygon(whiteBox, AZStd::vector<FaceVertHandles>{{vh0, vh1, vh2}});
}
PolygonHandle AddQuadPolygon(
WhiteBoxMesh& whiteBox, const VertexHandle vh0, const VertexHandle vh1, const VertexHandle vh2,
const VertexHandle vh3)
{
WHITEBOX_LOG(
"White Box", "AddQuadPolygon vh(%s), vh(%s), vh(%s), vh(%s)", ToString(vh0).c_str(),
ToString(vh1).c_str(), ToString(vh2).c_str(), ToString(vh3).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
return AddPolygon(whiteBox, AZStd::vector<FaceVertHandles>{{vh0, vh1, vh2}, {vh0, vh2, vh3}});
}
PolygonHandle AddPolygon(WhiteBoxMesh& whiteBox, const FaceVertHandlesList& faceVertHandles)
{
WHITEBOX_LOG("White Box", "AddPolygon [%s]", ToString(faceVertHandles).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
auto polygon = FaceHandlesInternal{};
polygon.reserve(faceVertHandles.size());
for (const auto& face : faceVertHandles)
{
polygon.push_back(whiteBox.mesh.add_face(
om_vh(face.m_vertexHandles[0]), om_vh(face.m_vertexHandles[1]), om_vh(face.m_vertexHandles[2])));
}
PolygonHandle polygonHandle = PolygonHandleFromInternal(polygon);
auto& polygonProps = whiteBox.mesh.property(polygonPropsHandle);
// multiple face handles map to a polygon handle
for (const auto& faceHandle : polygon)
{
polygonProps[faceHandle] = polygon;
}
return polygonHandle;
}
PolygonHandles InitializeAsUnitCube(WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// generate vertices
VertexHandle vertexHandles[8];
// top verts
vertexHandles[0] = AddVertex(whiteBox, AZ::Vector3(-0.5f, -0.5f, 0.5f));
vertexHandles[1] = AddVertex(whiteBox, AZ::Vector3(0.5f, -0.5f, 0.5f));
vertexHandles[2] = AddVertex(whiteBox, AZ::Vector3(0.5f, 0.5f, 0.5f));
vertexHandles[3] = AddVertex(whiteBox, AZ::Vector3(-0.5f, 0.5f, 0.5f));
// bottom verts
vertexHandles[4] = AddVertex(whiteBox, AZ::Vector3(-0.5f, -0.5f, -0.5f));
vertexHandles[5] = AddVertex(whiteBox, AZ::Vector3(0.5f, -0.5f, -0.5f));
vertexHandles[6] = AddVertex(whiteBox, AZ::Vector3(0.5f, 0.5f, -0.5f));
vertexHandles[7] = AddVertex(whiteBox, AZ::Vector3(-0.5f, 0.5f, -0.5f));
// generate faces
PolygonHandles polygonHandles{
// top
AddQuadPolygon(whiteBox, vertexHandles[0], vertexHandles[1], vertexHandles[2], vertexHandles[3]),
// bottom
AddQuadPolygon(whiteBox, vertexHandles[7], vertexHandles[6], vertexHandles[5], vertexHandles[4]),
// front
AddQuadPolygon(whiteBox, vertexHandles[4], vertexHandles[5], vertexHandles[1], vertexHandles[0]),
// right
AddQuadPolygon(whiteBox, vertexHandles[5], vertexHandles[6], vertexHandles[2], vertexHandles[1]),
// back
AddQuadPolygon(whiteBox, vertexHandles[6], vertexHandles[7], vertexHandles[3], vertexHandles[2]),
// left
AddQuadPolygon(whiteBox, vertexHandles[7], vertexHandles[4], vertexHandles[0], vertexHandles[3])};
CalculateNormals(whiteBox);
CalculatePlanarUVs(whiteBox);
return polygonHandles;
}
PolygonHandle InitializeAsUnitQuad(WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// generate vertices
VertexHandle vertexHandles[4];
// front face
vertexHandles[0] = AddVertex(whiteBox, AZ::Vector3(-0.5f, 0.0f, -0.5f)); // bottom left
vertexHandles[1] = AddVertex(whiteBox, AZ::Vector3(0.5f, 0.0f, -0.5f)); // bottom right
vertexHandles[2] = AddVertex(whiteBox, AZ::Vector3(0.5f, 0.0f, 0.5f)); // top right
vertexHandles[3] = AddVertex(whiteBox, AZ::Vector3(-0.5f, 0.0f, 0.5f)); // top left
// generate faces - front
auto polygonHandle =
AddQuadPolygon(whiteBox, vertexHandles[0], vertexHandles[1], vertexHandles[2], vertexHandles[3]);
CalculateNormals(whiteBox);
CalculatePlanarUVs(whiteBox);
return polygonHandle;
}
PolygonHandle InitializeAsUnitTriangle(WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// generate vertices
VertexHandle vertexHandles[3];
const auto pointOnCircle = [](const float angle)
{
return AZ::Vector3{cosf(angle), sinf(angle), 0.0f};
};
// front face
vertexHandles[0] = AddVertex(whiteBox, pointOnCircle(AZ::DegToRad(90.0f))); // top
vertexHandles[1] = AddVertex(whiteBox, pointOnCircle(AZ::DegToRad(-150.0f))); // bottom left
vertexHandles[2] = AddVertex(whiteBox, pointOnCircle(AZ::DegToRad(-30.0f))); // bottom right
// generate faces - front
auto polygonHandle = AddTriPolygon(whiteBox, vertexHandles[0], vertexHandles[1], vertexHandles[2]);
CalculateNormals(whiteBox);
CalculatePlanarUVs(whiteBox);
return polygonHandle;
}
void SetVertexPosition(WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle, const AZ::Vector3& position)
{
WHITEBOX_LOG(
"White Box", "SetVertexPosition vh(%s) %s", ToString(vertexHandle).c_str(), AZStd::to_string(position).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
whiteBox.mesh.set_point(om_vh(vertexHandle), position);
}
void SetVertexPositionAndUpdateUVs(
WhiteBoxMesh& whiteBox, const VertexHandle vertexHandle, const AZ::Vector3& position)
{
WHITEBOX_LOG(
"White Box", "SetVertexPositionAndUpdateUVs vh(%s) %s", ToString(vertexHandle).c_str(), AZStd::to_string(position).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
SetVertexPosition(whiteBox, vertexHandle, position);
CalculatePlanarUVs(whiteBox);
}
VertexHandle AddVertex(WhiteBoxMesh& whiteBox, const AZ::Vector3& vertex)
{
WHITEBOX_LOG("White Box", "AddVertex %s", AZStd::to_string(vertex).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
return wb_vh(whiteBox.mesh.add_vertex(vertex));
}
FaceHandle AddFace(WhiteBoxMesh& whiteBox, const VertexHandle v0, const VertexHandle v1, const VertexHandle v2)
{
WHITEBOX_LOG(
"White Box", "AddFace vh(%s), vh(%s), vh(%s)", ToString(v0).c_str(), ToString(v1).c_str(),
ToString(v2).c_str());
AZ_PROFILE_FUNCTION(AzToolsFramework);
return wb_fh(whiteBox.mesh.add_face(om_vh(v0), om_vh(v1), om_vh(v2)));
}
void CalculateNormals(WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
whiteBox.mesh.update_normals();
}
void ZeroUVs(WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
for (const Mesh::FaceHandle& faceHandle : whiteBox.mesh.faces())
{
for (Mesh::FaceHalfedgeCCWIter faceHalfedgeIt = whiteBox.mesh.fh_ccwiter(faceHandle);
faceHalfedgeIt.is_valid(); ++faceHalfedgeIt)
{
whiteBox.mesh.set_texcoord2D(*faceHalfedgeIt, AZ::Vector2::CreateZero());
}
}
}
AZ::Vector3 EdgeMidpoint(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle)
{
const auto vertexPositions = EdgeVertexPositions(whiteBox, edgeHandle);
return (vertexPositions[0] + vertexPositions[1]) * 0.5f;
}
AZ::Vector3 FaceMidpoint(const WhiteBoxMesh& whiteBox, const FaceHandle faceHandle)
{
const auto vertexPositions = FaceVertexPositions(whiteBox, faceHandle);
return AZStd::accumulate(
AZStd::begin(vertexPositions), AZStd::end(vertexPositions), AZ::Vector3::CreateZero(),
[](const AZ::Vector3& acc, const AZ::Vector3& position)
{
return acc + position;
}) /
3;
}
AZ::Vector3 PolygonMidpoint(const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle)
{
// first attempt using border vertex handles (this is usually what we want, but it might
// fail if all edges of a polygon have been hidden)
if (const auto polygonBorderVertexHandles = PolygonBorderVertexHandlesFlattened(whiteBox, polygonHandle);
!polygonBorderVertexHandles.empty())
{
return VerticesMidpoint(whiteBox, polygonBorderVertexHandles);
}
// if that fails, fall back to all vertex handles in the polygon to calculate the midpoint
return VerticesMidpoint(whiteBox, PolygonVertexHandles(whiteBox, polygonHandle));
}
AZ::Vector3 VerticesMidpoint(const WhiteBoxMesh& whiteBox, const VertexHandles& vertexHandles)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
AzToolsFramework::MidpointCalculator midpointCalculator;
for (const auto& vertexHandle : vertexHandles)
{
midpointCalculator.AddPosition(VertexPosition(whiteBox, vertexHandle));
}
return midpointCalculator.CalculateMidpoint();
}
static HalfedgeHandle FindHalfedgeInAdjacentPolygon(
const WhiteBoxMesh& whiteBox, const Internal::VertexHandlePair vertexHandlePair,
const PolygonHandle& selectedPolygonHandle, const PolygonHandle& adjacentPolygonHandle)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto selectedPolygonEdgeHandles = PolygonBorderEdgeHandlesFlattened(whiteBox, selectedPolygonHandle);
const auto adjacentPolygonEdgeHandles = PolygonBorderEdgeHandlesFlattened(whiteBox, adjacentPolygonHandle);
// iterate over all halfedges in the adjacent polygon
for (const auto& edgeHandle : adjacentPolygonEdgeHandles)
{
const auto* const foundEdgeHandleInSelectedPolygon =
AZStd::find(selectedPolygonEdgeHandles.cbegin(), selectedPolygonEdgeHandles.cend(), edgeHandle);
// did not find edge handle in selected polygon
if (foundEdgeHandleInSelectedPolygon == selectedPolygonEdgeHandles.cend())
{
// find outgoing edge handles
for (const auto& halfedgeHandle :
VertexOutgoingHalfedgeHandles(whiteBox, vertexHandlePair.m_existing))
{
// attempt to find one of the outgoing halfedge handles in the adjacent polygon
if (HalfedgeEdgeHandle(whiteBox, halfedgeHandle) == edgeHandle)
{
return halfedgeHandle;
}
}
}
}
return HalfedgeHandle{};
}
// add 'linking/connecting' faces for when an 'impression' happens
// note: temporary measure before triangulation support is added to the white box tool
static void AddLinkingFace(
const WhiteBoxMesh& whiteBox, const Internal::VertexHandlePair vertexHandlePair,
const PolygonHandle& selectedPolygonHandle, const PolygonHandle& adjacentPolygonHandle,
FaceVertHandlesCollection& vertsForLinkingAdjacentPolygons)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// if we found a valid halfedge
if (const HalfedgeHandle foundHalfedgeHandle = FindHalfedgeInAdjacentPolygon(
whiteBox, vertexHandlePair, selectedPolygonHandle, adjacentPolygonHandle);
foundHalfedgeHandle.IsValid())
{
// find the 'to' vertex
const auto toVertexHandle = HalfedgeVertexHandleAtTip(whiteBox, foundHalfedgeHandle);
// find if the face handle of the halfedge is 'in' the adjacent polygon
const auto* const faceHandleInAdjacentPolygon = AZStd::find(
adjacentPolygonHandle.m_faceHandles.cbegin(), adjacentPolygonHandle.m_faceHandles.cend(),
HalfedgeFaceHandle(whiteBox, foundHalfedgeHandle));
// adjust winding order if the outgoing halfedge is in the adjacent polygon or not
FaceVertHandles linkingPolygonVertexHandles =
faceHandleInAdjacentPolygon != adjacentPolygonHandle.m_faceHandles.cend()
? FaceVertHandles{vertexHandlePair.m_existing, toVertexHandle, vertexHandlePair.m_added}
: FaceVertHandles{vertexHandlePair.m_existing, vertexHandlePair.m_added, toVertexHandle};
// store verts for new polygon
vertsForLinkingAdjacentPolygons.push_back(
AZStd::vector<FaceVertHandles>{AZStd::move(linkingPolygonVertexHandles)});
}
}
// return true if existing verts were reused and linking faces were added
// return false if a new adjacent polygon must be created (new verts have
// been added and will be used)
static bool TryAddLinkingFaces(
WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const Internal::AppendedVerts& appendedVerts,
const PolygonHandle& selectedPolygonHandle, const Internal::VertexHandlePair& currentVertexHandlePair,
const Internal::VertexHandlePair& nextVertexHandlePair,
AZStd::vector<PolygonHandle>& polygonHandlesToRemove,
FaceVertHandlesCollection& vertsForExistingAdjacentPolygons,
FaceVertHandlesCollection& vertsForLinkingAdjacentPolygons)
{
// find all faces connected to this edge
for (const auto& faceHandle : EdgeFaceHandles(whiteBox, edgeHandle))
{
// find a face that is _not_ part of the polygon being appended/selected
if (AZStd::find(
selectedPolygonHandle.m_faceHandles.cbegin(), selectedPolygonHandle.m_faceHandles.cend(),
faceHandle) == selectedPolygonHandle.m_faceHandles.cend())
{
// the polygon handle of the face connected to one of the top edges
const auto adjacentPolygonHandle = FacePolygonHandle(whiteBox, faceHandle);
const auto selectedPolygonNormal = PolygonNormal(whiteBox, selectedPolygonHandle);
// the normal of the adjacent (connected) polygon
const auto adjacentPolygonNormal = PolygonNormal(whiteBox, adjacentPolygonHandle);
const float angleCosine = adjacentPolygonNormal.Dot(selectedPolygonNormal);
// check if the adjacent polygon is completely orthogonal to the
// selected polygon - if so reuse the existing verts and do not
// create a new adjacent polygon as part of the operation
if (AZ::IsClose(angleCosine, 0.0f, AdjacentPolygonNormalTolerance))
{
// if the current or next vertex on the border have had a new vertex added
if (currentVertexHandlePair.m_added != currentVertexHandlePair.m_existing ||
nextVertexHandlePair.m_added != nextVertexHandlePair.m_existing)
{
// remove the existing adjacent polygon (a new one will be added
// that is connected to the newly added verts)
polygonHandlesToRemove.push_back(adjacentPolygonHandle);
// calculate new verts for faces to be created
const auto adjacentPolygonVerts =
BuildNewVertexFaceHandles(whiteBox, appendedVerts, adjacentPolygonHandle.m_faceHandles);
// store the face verts to be added later after existing faces have been removed
vertsForExistingAdjacentPolygons.push_back(adjacentPolygonVerts);
}
// first linking face
if (currentVertexHandlePair.m_added != currentVertexHandlePair.m_existing)
{
AddLinkingFace(
whiteBox, currentVertexHandlePair, selectedPolygonHandle, adjacentPolygonHandle,
vertsForLinkingAdjacentPolygons);
}
// second linking face
if (nextVertexHandlePair.m_added != nextVertexHandlePair.m_existing)
{
AddLinkingFace(
whiteBox, nextVertexHandlePair, selectedPolygonHandle, adjacentPolygonHandle,
vertsForLinkingAdjacentPolygons);
}
return true;
}
}
}
return false;
}
// build the adjacent walls of an extrusion
// note: borderVertexHandles must be ordered correctly (CCW)
static void AddAdjacentFaces(
WhiteBoxMesh& whiteBox, const Internal::AppendedVerts& appendedVerts, const bool appendAll,
const PolygonHandle& selectedPolygonHandle, const VertexHandles& borderVertexHandles,
const EdgeHandles& borderEdgeHandles, AZStd::vector<PolygonHandle>& polygonHandlesToRemove,
FaceVertHandlesCollection& vertsForNewAdjacentPolygons,
FaceVertHandlesCollection& vertsForExistingAdjacentPolygons,
FaceVertHandlesCollection& vertsForLinkingAdjacentPolygons)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// adjacent faces
for (size_t index = 0; index < borderVertexHandles.size(); ++index)
{
const size_t nextIndexWrapped = (index + 1) % borderVertexHandles.size();
const VertexHandle existingBorderVertexHandle = borderVertexHandles[index];
const VertexHandle nextExistingBorderVertexHandle = borderVertexHandles[nextIndexWrapped];
const auto* const currentVertexHandlePairIt = AZStd::find_if(
appendedVerts.m_vertexHandlePairs.cbegin(), appendedVerts.m_vertexHandlePairs.cend(),
[existingBorderVertexHandle](const Internal::VertexHandlePair vertexHandlePair)
{
return vertexHandlePair.m_existing == existingBorderVertexHandle;
});
const auto* const nextVertexHandlePairIt = AZStd::find_if(
appendedVerts.m_vertexHandlePairs.cbegin(), appendedVerts.m_vertexHandlePairs.cend(),
[nextExistingBorderVertexHandle](const Internal::VertexHandlePair vertexHandlePair)
{
return vertexHandlePair.m_existing == nextExistingBorderVertexHandle;
});
// find the edge on the border of the polygon we're appending
const auto* const edgeHandleIt = AZStd::find_if(
borderEdgeHandles.cbegin(), borderEdgeHandles.cend(),
[&whiteBox, existingBorderVertexHandle, nextExistingBorderVertexHandle](const EdgeHandle edgeHandle)
{
const auto edgeVertexHandles = EdgeVertexHandles(whiteBox, edgeHandle);
return (existingBorderVertexHandle == edgeVertexHandles[0] &&
nextExistingBorderVertexHandle == edgeVertexHandles[1]) ||
(existingBorderVertexHandle == edgeVertexHandles[1] &&
nextExistingBorderVertexHandle == edgeVertexHandles[0]);
});
// does a new side need to be created (new verts added) or should we reuse existing verts
const bool createNewAdjacentPolygon = appendAll
// short circuit if appendAll is true (no linking faces are required)
|| !TryAddLinkingFaces(whiteBox, *edgeHandleIt, appendedVerts, selectedPolygonHandle,
*currentVertexHandlePairIt, *nextVertexHandlePairIt, polygonHandlesToRemove,
vertsForExistingAdjacentPolygons, vertsForLinkingAdjacentPolygons);
// a new full side must be created (we're not reusing existing verts for the new polygon)
if (createNewAdjacentPolygon)
{
vertsForNewAdjacentPolygons.push_back(AZStd::vector<FaceVertHandles>{
{existingBorderVertexHandle, nextExistingBorderVertexHandle, nextVertexHandlePairIt->m_added},
{existingBorderVertexHandle, nextVertexHandlePairIt->m_added,
currentVertexHandlePairIt->m_added}});
}
}
}
// note: it is important to collect all face handles to remove and call RemoveFaces
// once for a given operation (for example to do not call RemoveFaces in a loop,
// instead build the collection of face handles in a loop and then call RemoveFaces)
// this is to ensure the face handles remain stable as they may be invalidated/changed
// during garbage_collect
void RemoveFaces(WhiteBoxMesh& whiteBox, const FaceHandles& faceHandles)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
whiteBox.mesh.request_face_status();
whiteBox.mesh.request_edge_status();
whiteBox.mesh.request_vertex_status();
PolygonPropertyHandle polygonPropsHandle;
whiteBox.mesh.get_property_handle(polygonPropsHandle, PolygonProps);
auto& polygonProps = whiteBox.mesh.property(polygonPropsHandle);
// erase face handles from the polygon map and
// delete the faces from OpenMesh
for (const auto& faceHandle : faceHandles)
{
polygonProps.erase(om_fh(faceHandle));
whiteBox.mesh.delete_face(om_fh(faceHandle), false);
}
using VertexHandlePtrs = AZStd::vector<Mesh::VertexHandle*>;
using FaceHandlePtrs = AZStd::vector<Mesh::FaceHandle*>;
using HalfedgeHandlePtrs = AZStd::vector<Mesh::HalfedgeHandle*>;
// store pointers to all face handles stored within the map (the values - e.g. it.second)
FaceHandlePtrs faceHandlePtrs;
for (auto& polygonProp : polygonProps)
{
faceHandlePtrs = AZStd::accumulate(
polygonProp.second.begin(), polygonProp.second.end(), faceHandlePtrs,
[](FaceHandlePtrs ptrs, Mesh::FaceHandle& faceHandle)
{
ptrs.push_back(&faceHandle);
return ptrs;
});
}
// make a copy of the face handles to compare against after the garbage_collect
FaceHandlesInternal faceHandlesCopy;
faceHandlesCopy.reserve(faceHandlePtrs.size());
AZStd::transform(
faceHandlePtrs.begin(), faceHandlePtrs.end(), AZStd::back_inserter(faceHandlesCopy),
[](const Mesh::FaceHandle* faceHandle)
{
return *faceHandle;
});
// actually delete faces from mesh
auto vhs = VertexHandlePtrs{};
auto hehs = HalfedgeHandlePtrs{};
whiteBox.mesh.garbage_collection(vhs, hehs, faceHandlePtrs);
using ModifiedFaceHandle = AZStd::pair<Mesh::FaceHandle, Mesh::FaceHandle>;
using ModifiedFaceHandles = AZStd::vector<ModifiedFaceHandle>;
const ModifiedFaceHandles modifiedFaceHandles = AZStd::inner_product(
faceHandlesCopy.begin(), faceHandlesCopy.end(), faceHandlePtrs.begin(), ModifiedFaceHandles{},
//reduce
[](ModifiedFaceHandles modifiedFaceHandles, const ModifiedFaceHandle& fh)
{
if (fh.first.is_valid())
{
modifiedFaceHandles.push_back(fh);
}
return modifiedFaceHandles;
},
//transform
[](const Mesh::FaceHandle lhs, const Mesh::FaceHandle* rhs)
{
// if any of the faceHandlePtrs differ, we know the handles
// have been invalidated during the garbage collect
if (lhs != *rhs)
{
return ModifiedFaceHandle{lhs, *rhs};
}
return ModifiedFaceHandle{};
});
// iterate over all modified face handles
for (const ModifiedFaceHandle& modifiedFaceHandle : modifiedFaceHandles)
{
// find the value in the map using the old key
// e.g. faceHandle 10 -> polygon was 10, 11 -> now 4, 5
auto found = polygonProps.find(modifiedFaceHandle.first);
if (found != polygonProps.end())
{
// copy the updated faceHandle value (e.g was 10, now 4)
auto faceHandle = modifiedFaceHandle.second;
// pull the values out of the map (e.g. 4, 5) - make a copy
auto val = found->second;
// erase the old key/value -> key 10, value 4, 5
polygonProps.erase(found);
// reinsert the values back into the map with the right key (key 4, value 4, 5)
polygonProps.insert({faceHandle, val});
}
}
}
AZStd::vector<FaceVertHandles> BuildNewVertexFaceHandles(
WhiteBoxMesh& whiteBox, const Internal::AppendedVerts& appendedVerts, const FaceHandles& existingFaces)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
AZStd::vector<FaceVertHandles> faces;
faces.reserve(existingFaces.size());
// for each face
for (const FaceHandle& faceHandle : existingFaces)
{
VertexHandles vertexHandlesForFace;
vertexHandlesForFace.reserve(3);
const auto vertexHandles = FaceVertexHandles(whiteBox, faceHandle);
// for each vertex handle
for (const VertexHandle& vertexHandle : vertexHandles)
{
// find vertex handle in vertices list
const auto* const vertexHandlePairIt = AZStd::find_if(
appendedVerts.m_vertexHandlePairs.cbegin(), appendedVerts.m_vertexHandlePairs.cend(),
[vertexHandle](const Internal::VertexHandlePair vertexHandlePair)
{
return vertexHandle == vertexHandlePair.m_existing;
});
// record corresponding (added) vertex
if (vertexHandlePairIt != appendedVerts.m_vertexHandlePairs.cend())
{
// store vertex
vertexHandlesForFace.push_back(vertexHandlePairIt->m_added);
}
// or existing vertex if one was not added
else
{
vertexHandlesForFace.push_back(vertexHandle);
}
}
// add face using vertex stored vertex
FaceVertHandles face;
face.m_vertexHandles[0] = vertexHandlesForFace[0];
face.m_vertexHandles[1] = vertexHandlesForFace[1];
face.m_vertexHandles[2] = vertexHandlesForFace[2];
faces.push_back(face);
}
return faces;
}
// determine whether new or existing verts be returned based on the type of
// append (extrude -> out, impression -> in)
template<typename AppendVertFn>
static AZStd::tuple<Internal::AppendedVerts, bool> AddVertsForAppend(
WhiteBoxMesh& whiteBox, const VertexHandles& existingVertexHandles, const PolygonHandle& polygonHandle,
AppendVertFn&& appendFn)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const AZ::Vector3 polygonNormal = PolygonNormal(whiteBox, polygonHandle);
const auto polygonHalfedgeHandles = PolygonHalfedgeHandles(whiteBox, polygonHandle);
const AZ::Vector3 extrudeDirection = appendFn(AZ::Vector3::CreateZero());
const float angleCosine = extrudeDirection.Dot(polygonNormal);
// detect if the user is extruding the polygon (pulling out) - if so we
// always want to append new vertices for every existing vertex
const bool appendAll = angleCosine >= 0.0f;
Internal::AppendedVerts appendedVerts;
appendedVerts.m_vertexHandlePairs.reserve(existingVertexHandles.size());
for (const VertexHandle& existingVertexHandle : existingVertexHandles)
{
bool vertexHandleAdded = false;
// visit all connected halfedge handles
for (const auto& halfedgeHandle : VertexHalfedgeHandles(whiteBox, existingVertexHandle))
{
const auto edgeHandle = HalfedgeEdgeHandle(whiteBox, halfedgeHandle);
const bool boundaryEdge = EdgeIsBoundary(whiteBox, edgeHandle);
// is the edge not contained in selected polygon (we want to only check adjacent polygons)
// or is the edge on a boundary (no adjacent face)
if (boundaryEdge ||
AZStd::find(polygonHalfedgeHandles.cbegin(), polygonHalfedgeHandles.cend(), halfedgeHandle) ==
polygonHalfedgeHandles.cend())
{
const auto nextHalfedgeHandle = HalfedgeHandleNext(whiteBox, halfedgeHandle);
const auto nextEdgeHandle = HalfedgeEdgeHandle(whiteBox, nextHalfedgeHandle);
const AZ::Vector3 edgeAxis = EdgeAxis(whiteBox, edgeHandle);
const AZ::Vector3 nextEdgeAxis = EdgeAxis(whiteBox, nextEdgeHandle);
// calculate face normal from two edges
const AZ::Vector3 faceNormal = edgeAxis.Cross(nextEdgeAxis).GetNormalizedSafe();
const bool adjacentFaceAndPolygonNormalOrthogonal = AZ::IsClose(
AZ::GetAbs(faceNormal.Dot(polygonNormal)), 0.0f, AdjacentPolygonNormalTolerance);
// if the polygon normal and edge direction are not parallel, we should
// add a new vertex for the polygon to be later created
if (appendAll || boundaryEdge || !adjacentFaceAndPolygonNormalOrthogonal)
{
vertexHandleAdded = true;
const AZ::Vector3 localPoint = VertexPosition(whiteBox, existingVertexHandle);
const AZ::Vector3 extrudedPoint = appendFn(localPoint);
const VertexHandle addedVertex = AddVertex(whiteBox, extrudedPoint);
// vertex pairs differ
appendedVerts.m_vertexHandlePairs.emplace_back(existingVertexHandle, addedVertex);
break;
}
}
}
if (!vertexHandleAdded)
{
const AZ::Vector3 localPoint = VertexPosition(whiteBox, existingVertexHandle);
const AZ::Vector3 extrudedPoint = appendFn(localPoint);
SetVertexPosition(whiteBox, existingVertexHandle, extrudedPoint);
// vertex pairs match
appendedVerts.m_vertexHandlePairs.emplace_back(existingVertexHandle, existingVertexHandle);
}
}
return {appendedVerts, appendAll};
}
//! @AppendVertexFn The way vertices should be translated as an append happens
//! note: This is a customization point for scale and translation types of append
template<typename AppendVertexFn>
static AppendedPolygonHandles Extrude(
WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle, AppendVertexFn&& appendFn)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
// find border vertex handles for polygon
const auto polygonBorderVertexHandlesCollection = PolygonBorderVertexHandles(whiteBox, polygonHandle);
// handle potentially pathological case where all edges have
// been hidden and no border vertex loop can be found
if (polygonBorderVertexHandlesCollection.empty())
{
AppendedPolygonHandles appendedPolygonHandles;
appendedPolygonHandles.m_appendedPolygonHandle = polygonHandle;
return appendedPolygonHandles;
}
// find all vertex handles for polygon
const auto polygonVertexHandles = PolygonVertexHandles(whiteBox, polygonHandle);
const auto borderEdgeHandlesCollection = PolygonBorderEdgeHandles(whiteBox, polygonHandle);
// the vertices to use for the new append (vertex handle pairs may both be existing, or new and existing)
const auto [appendedVerts, appendAll] =
AddVertsForAppend(whiteBox, polygonVertexHandles, polygonHandle, appendFn);
// the face vertex combinations to use for the new polygon begin appended
const AZStd::vector<FaceVertHandles> topFacesToAdd =
BuildNewVertexFaceHandles(whiteBox, appendedVerts, polygonHandle.m_faceHandles);
// polygons that will be removed as part of this operation
AZStd::vector<PolygonHandle> polygonHandlesToRemove;
// all new faces to be added
FaceVertHandlesCollection vertsForNewAdjacentPolygons;
FaceVertHandlesCollection vertsForExistingAdjacentPolygons;
FaceVertHandlesCollection vertsForLinkingAdjacentPolygons;
for (size_t index = 0; index < polygonBorderVertexHandlesCollection.size(); ++index)
{
// note: quads/walls of extrusion
AddAdjacentFaces(
whiteBox, appendedVerts, appendAll, polygonHandle, polygonBorderVertexHandlesCollection[index],
borderEdgeHandlesCollection[index], polygonHandlesToRemove, vertsForNewAdjacentPolygons,
vertsForExistingAdjacentPolygons, vertsForLinkingAdjacentPolygons);
}
// <missing> - add bottom faces if mesh was 2d previously (reverse winding order)
FaceHandles allFacesToRemove = polygonHandle.m_faceHandles;
for (const auto& polygonHandleToRemove : polygonHandlesToRemove)
{
allFacesToRemove.insert(
allFacesToRemove.end(), polygonHandleToRemove.m_faceHandles.cbegin(), polygonHandleToRemove.m_faceHandles.cend());
}
// remove all faces that were already there
// note: it is very important to not use the existing polygon handle after remove
// has been called as this will invalidate all existing face handles
RemoveFaces(whiteBox, allFacesToRemove);
PolygonHandles polygonHandlesToRestore;
// re-add existing adjacent polygons
for (const auto& verts : vertsForExistingAdjacentPolygons)
{
polygonHandlesToRestore.push_back(AddPolygon(whiteBox, verts));
}
AZ_Assert(
polygonHandlesToRestore.size() == polygonHandlesToRemove.size(),
"PolygonHandles to restore and PolygonHandles to remove have different sizes");
AppendedPolygonHandles appendedPolygonHandles;
appendedPolygonHandles.m_restoredPolygonHandles.reserve(polygonHandlesToRestore.size());
for (size_t index = 0; index < polygonHandlesToRestore.size(); ++index)
{
RestoredPolygonHandlePair restoredPair;
restoredPair.m_before = polygonHandlesToRemove[index];
restoredPair.m_after = polygonHandlesToRestore[index];
appendedPolygonHandles.m_restoredPolygonHandles.push_back(AZStd::move(restoredPair));
}
// add linking polygons
for (const auto& verts : vertsForLinkingAdjacentPolygons)
{
AddPolygon(whiteBox, verts);
}
// add top polygon
PolygonHandle newPolygonHandle = AddPolygon(whiteBox, topFacesToAdd);
// add new adjacent polygons
for (const auto& verts : vertsForNewAdjacentPolygons)
{
AddPolygon(whiteBox, verts);
}
whiteBox.mesh.update_normals();
appendedPolygonHandles.m_appendedPolygonHandle = newPolygonHandle;
return appendedPolygonHandles;
}
using AppendFn = AZStd::function<AZ::Vector3(const AZ::Vector3&)>;
static AppendFn TranslatePoint(const AZ::Vector3& direction, const float distance)
{
return [direction, distance](const AZ::Vector3& point)
{
return point + (direction * distance);
};
}
PolygonHandle TranslatePolygonAppend(
WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle, const float distance)
{
WHITEBOX_LOG("White Box", "TranslatePolygonAppend ph(%s) %f", ToString(polygonHandle).c_str(), distance)
AZ_PROFILE_FUNCTION(AzToolsFramework);
return TranslatePolygonAppendAdvanced(whiteBox, polygonHandle, distance).m_appendedPolygonHandle;
}
AppendedPolygonHandles TranslatePolygonAppendAdvanced(
WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle, const float distance)
{
WHITEBOX_LOG(
"White Box", "TranslatePolygonAppendAdvanced ph(%s) %f", ToString(polygonHandle).c_str(), distance)
AZ_PROFILE_FUNCTION(AzToolsFramework);
// check mesh has faces
if (whiteBox.mesh.n_faces() == 0)
{
return {};
}
auto appendedPolygonHandles =
Extrude(whiteBox, polygonHandle, TranslatePoint(PolygonNormal(whiteBox, polygonHandle), distance));
return appendedPolygonHandles;
}
void TranslatePolygon(WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle, const float distance)
{
WHITEBOX_LOG("White Box", "TranslatePolygon ph(%s) %d", ToString(polygonHandle).c_str(), distance)
AZ_PROFILE_FUNCTION(AzToolsFramework);
const auto vertexHandles = PolygonVertexHandles(whiteBox, polygonHandle);
const auto vertexPositions = VertexPositions(whiteBox, vertexHandles);
const auto normal = PolygonNormal(whiteBox, polygonHandle);
for (size_t index = 0; index < vertexHandles.size(); ++index)
{
SetVertexPosition(whiteBox, vertexHandles[index], vertexPositions[index] + normal * distance);
}
CalculatePlanarUVs(whiteBox);
}
PolygonHandle ScalePolygonAppendRelative(
WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle, const float scale)
{
WHITEBOX_LOG("White Box", "ScalePolygonAppendRelative ph(%s) %f", ToString(polygonHandle).c_str(), scale);
AZ_PROFILE_FUNCTION(AzToolsFramework);
// check mesh has faces
if (whiteBox.mesh.n_faces() == 0)
{
return {};
}
const AZ::Transform polygonSpace =
PolygonSpace(whiteBox, polygonHandle, PolygonMidpoint(whiteBox, polygonHandle));
const auto scalePolygonFn = [polygonSpace, scale](const AZ::Vector3& localPosition)
{
return ScalePosition(1.0f + scale, localPosition, polygonSpace);
};
auto appendedPolygonHandles = Extrude(whiteBox, polygonHandle, scalePolygonFn);
return appendedPolygonHandles.m_appendedPolygonHandle;
}
static AZ::Transform BuildSpace(const AZ::Vector3& normal, const AZ::Vector3& pivot)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
AZ::Vector3 axis1;
AZ::Vector3 axis2;
CalculateOrthonormalBasis(normal, axis1, axis2);
AZ::Matrix3x4 matrix = AZ::Matrix3x4::CreateFromColumns(axis1, axis2, normal, pivot);
return AZ::Transform::CreateFromMatrix3x4(matrix);
}
AZ::Transform PolygonSpace(
const WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle, const AZ::Vector3& pivot)
{
return BuildSpace(PolygonNormal(whiteBox, polygonHandle), pivot);
}
AZ::Transform EdgeSpace(const WhiteBoxMesh& whiteBox, const EdgeHandle edgeHandle, const AZ::Vector3& pivot)
{
const auto vertexPositions = EdgeVertexPositions(whiteBox, edgeHandle);
return BuildSpace((vertexPositions[1] - vertexPositions[0]).GetNormalizedSafe(), pivot);
}
void ScalePolygonRelative(
WhiteBoxMesh& whiteBox, const PolygonHandle& polygonHandle, const AZ::Vector3& pivot,
const float scaleDelta)
{
WHITEBOX_LOG(
"White Box", "ScalePolygonRelative ph(%s) pivot %s scale: %f", ToString(polygonHandle).c_str(),
AZStd::to_string(pivot).c_str(), scaleDelta);
AZ_PROFILE_FUNCTION(AzToolsFramework);
const AZ::Transform polygonSpace = PolygonSpace(whiteBox, polygonHandle, pivot);
for (const auto& vertexHandle : PolygonVertexHandles(whiteBox, polygonHandle))
{
SetVertexPosition(
whiteBox, vertexHandle,
ScalePosition(1.0f + scaleDelta, VertexPosition(whiteBox, vertexHandle), polygonSpace));
}
CalculateNormals(whiteBox);
CalculatePlanarUVs(whiteBox);
}
bool WriteMesh(const WhiteBoxMesh& whiteBox, WhiteBoxMeshStream& output)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
AZStd::lock_guard lg(g_omSerializationLock);
std::stringstream whiteBoxStream;
if (OpenMesh::IO::write_mesh(
whiteBox.mesh, whiteBoxStream, ".om",
OpenMesh::IO::Options::Binary | OpenMesh::IO::Options::FaceTexCoord |
OpenMesh::IO::Options::FaceNormal))
{
const std::string outputStr = whiteBoxStream.str();
output.clear();
output.reserve(outputStr.size());
AZStd::copy(outputStr.cbegin(), outputStr.cend(), AZStd::back_inserter(output));
return true;
}
// handle error
return false;
}
ReadResult ReadMesh(WhiteBoxMesh& whiteBox, const WhiteBoxMeshStream& input)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (input.empty())
{
return ReadResult::Empty;
}
std::string inputStr;
inputStr.reserve(input.size());
AZStd::copy(input.cbegin(), input.cend(), AZStd::back_inserter(inputStr));
std::stringstream whiteBoxStream;
whiteBoxStream.str(inputStr);
whiteBoxStream >> std::noskipws;
return ReadMesh(whiteBox, whiteBoxStream);
}
ReadResult ReadMesh(WhiteBoxMesh& whiteBox, std::istream& input)
{
const auto skipws = input.flags() & std::ios_base::skipws;
AZ_Assert(skipws == 0, "Input stream must not skip white space characters");
if (skipws != 0)
{
return ReadResult::Error;
}
AZStd::lock_guard lg(g_omSerializationLock);
OpenMesh::IO::Options options{OpenMesh::IO::Options::FaceTexCoord | OpenMesh::IO::Options::FaceNormal};
return OpenMesh::IO::read_mesh(whiteBox.mesh, input, ".om", options) ? ReadResult::Full : ReadResult::Error;
}
WhiteBoxMeshPtr CloneMesh(const WhiteBoxMesh& whiteBox)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
WhiteBoxMeshStream clonedData;
if (!WriteMesh(whiteBox, clonedData))
{
return nullptr;
}
WhiteBoxMeshPtr newMesh = CreateWhiteBoxMesh();
if (ReadMesh(*newMesh, clonedData) != ReadResult::Full)
{
return nullptr;
}
return newMesh;
}
bool SaveToObj(const WhiteBoxMesh& whiteBox, const AZStd::string& filePath)
{
OpenMesh::IO::Options options{OpenMesh::IO::Options::FaceTexCoord};
OpenMesh::IO::ExporterT<WhiteBox::Mesh> exporter{whiteBox.mesh};
return OpenMesh::IO::OBJWriter().write(filePath.c_str(), exporter, options);
}
bool SaveToWbm(const WhiteBoxMesh& whiteBox, AZ::IO::GenericStream& stream)
{
WhiteBoxMeshStream buffer;
const bool success = WhiteBox::Api::WriteMesh(whiteBox, buffer);
const auto bytesWritten = stream.Write(buffer.size(), buffer.data());
return success && bytesWritten == buffer.size();
}
bool SaveToWbm(const WhiteBoxMesh& whiteBox, const AZStd::string& filePath)
{
AZ::IO::FileIOStream fileStream(filePath.c_str(), AZ::IO::OpenMode::ModeWrite);
if (!fileStream.IsOpen())
{
return false;
}
return SaveToWbm(whiteBox, fileStream);
}
static AZStd::string TrimLastChar(const AZStd::string& str)
{
if (str.empty())
{
return str;
}
return str.substr(0, str.length() - 1);
}
AZStd::string ToString(const PolygonHandle& polygonHandle)
{
AZStd::string str = "";
for (auto faceHandle : polygonHandle.m_faceHandles)
{
str.append(ToString(faceHandle)).append(",");
}
return TrimLastChar(str);
}
AZStd::string ToString(const FaceVertHandles& faceVertHandles)
{
AZStd::string str = "";
for (auto vertexHandle : faceVertHandles.m_vertexHandles)
{
str.append(ToString(vertexHandle)).append(",");
}
return TrimLastChar(str);
}
AZStd::string ToString(const FaceVertHandlesList& faceVertHandlesList)
{
AZStd::string str = "";
for (auto faceVertHandles : faceVertHandlesList)
{
str.append("fvh(").append(ToString(faceVertHandles)).append("),");
}
return TrimLastChar(str);
}
AZStd::string ToString(const VertexHandle vertexHandle)
{
return AZStd::to_string(vertexHandle.Index());
}
AZStd::string ToString(const FaceHandle faceHandle)
{
return AZStd::to_string(faceHandle.Index());
}
AZStd::string ToString(const EdgeHandle edgeHandle)
{
return AZStd::to_string(edgeHandle.Index());
}
AZStd::string ToString(const HalfedgeHandle halfedgeHandle)
{
return AZStd::to_string(halfedgeHandle.Index());
}
} // namespace Api
} // namespace WhiteBox