diff --git a/include/vclib/algorithms/update/normal.h b/include/vclib/algorithms/update/normal.h index fb1943d77..4da072aad 100644 --- a/include/vclib/algorithms/update/normal.h +++ b/include/vclib/algorithms/update/normal.h @@ -34,15 +34,15 @@ namespace vcl { namespace detail { -template -void normalizeNoThrow(auto& elem, LogType& log) +template +void normalizeNoThrow(auto& elem, LogType& log = nullLogger) { try { elem.normal().normalize(); } catch (const std::exception& e) { log.log( - LogType::WARNING, + log.WARNING, elementEnumString() + " " + std::to_string(elem.index()) + ": " + e.what()); } @@ -50,43 +50,95 @@ void normalizeNoThrow(auto& elem, LogType& log) } // namespace vcl::detail -template +/** + * @brief Sets to zero the normals of all the elements of the mesh, + * including the unreferenced ones. + * + * Requirements: + * - Mesh: + * - : + * - Normal + * + * @tparam ELEM_ID: The ID of an Element, that is a value in the ElementIDEnum. + * + * @param[in,out] mesh: The mesh on which clear the normals. + * @param[in,out] log: The logger used to log the performed operations. + */ +template +void clearPerElementNormals(MeshConcept auto& mesh, LogType& log = nullLogger) +{ + requirePerElementComponent(mesh); + + log.log(0, "Clearing per-" + elementEnumString() + " normals..."); + + parallelFor(mesh.template elements(), [](auto& e) { + e.normal().setZero(); + }); + + log.log( + 100, "Per-" + elementEnumString() + " normals cleared."); +} + +/** + * @brief Normalizes the length of the normals of all the elements. + * + * Requirements: + * - Mesh: + * - : + * - Normal + * + * @tparam ELEM_ID: The ID of an Element, that is a value in the ElementIDEnum. + * + * @param[in,out] mesh: the mesh on which normalize the normals. + * @param[in,out] log: The logger used to log the performed operations. + */ +template void normalizePerElementNormals( - MeshType& mesh, - LogType& log = nullLogger) + MeshConcept auto& mesh, + LogType& log = nullLogger) { - vcl::requirePerElementComponent(mesh); + requirePerElementComponent(mesh); - log.log(0, "Normalizing per-" + elementEnumString() + " normals."); + log.log( + 0, "Normalizing per-" + elementEnumString() + " normals..."); // define a lambda that, for each element, normalizes the normal auto normalize = [&](auto& elem) { detail::normalizeNoThrow(elem, log); }; - vcl::parallelFor(mesh.template elements(), normalize); + parallelFor(mesh.template elements(), normalize); log.log( 100, "Per-" + elementEnumString() + " normals normalized."); } -template -void clearPerElementNormals(MeshType& mesh) -{ - vcl::requirePerElementComponent(mesh); - - for (auto& elem : mesh.template elements()) { - elem.normal().setZero(); - } -} - -template +/** + * @brief Multiplies the normals of all the elements by the given 3x3 + * Matrix. + * + * If removeScalingFromMatrix is true (default), the scale component is + * removed from the matrix. + * + * Requirements: + * - Mesh: + * - : + * - Normal + * + * @param[in,out] mesh: the mesh on which multiply the element normals. + * @param[in] mat: the 3x3 matrix that is multiplied to the normals. + * @param[in] removeScalingFromMatrix: if true (default), the scale component is + * removed from the matrix. + * @param[in,out] log: The logger used to log the performed operations. + */ +template void multiplyPerElementNormalsByMatrix( - MeshType& mesh, - vcl::Matrix33 mat, - bool removeScalingFromMatrix = true) + MeshConcept auto& mesh, + Matrix33 mat, + bool removeScalingFromMatrix = true, + LogType& log = nullLogger) { - vcl::requirePerElementComponent(mesh); + requirePerElementComponent(mesh); if (removeScalingFromMatrix) { MScalar scaleX = std::sqrt( @@ -104,178 +156,253 @@ void multiplyPerElementNormalsByMatrix( mat(2, i) /= scaleZ; } } - for (auto& elem : mesh.template elements()) { - elem.normal() *= mat; - } + + log.log( + 0, + "Multiplying per-" + elementEnumString() + + " normals by matrix..."); + + parallelFor(mesh.template elements(), [&](auto& e) { + e.normal() *= mat; + }); + + log.log( + 100, "Per-" + elementEnumString() + " normals multiplied."); } -template +/** + * @brief Multiplies the normals of all the elements by the given TRS + * 4x4 Matrix. + * + * The normals are multiplied by the 3x3 rotation matrix of the given TRS + * matrix. If removeScalingFromMatrix is true (default), the scale component is + * removed from the matrix. + * + * Requirements: + * - Mesh: + * - : + * - Normal + * + * @param[in,out] mesh: the mesh on which multiply the element normals. + * @param[in] mat: the 4x4 TRS matrix that is multiplied to the normals. + * @param[in] removeScalingFromMatrix: if true (default), the scale component is + * removed from the matrix. + * @param[in,out] log: The logger used to log the performed operations. + */ +template void multiplyPerElementNormalsByMatrix( - MeshType& mesh, - const vcl::Matrix44& mat, - bool removeScalingFromMatrix = true) + MeshConcept auto& mesh, + const Matrix44& mat, + bool removeScalingFromMatrix = true, + LogType& log = nullLogger) { vcl::requirePerElementComponent(mesh); Matrix33 m33 = mat.block(0, 0, 3, 3); multiplyPerElementNormalsByMatrix( - mesh, m33, removeScalingFromMatrix); + mesh, m33, removeScalingFromMatrix, log); } /** - * @brief Sets to zero the normals of all the vertices of the mesh, including - * the unreferenced ones. - * - * Requirements: - * - Mesh: - * - Vertices: - * - Normal + * @brief Same as clearPerElementNormals, but for the vertex normals. * - * @param[in,out] m: The mesh on which clear the vertex normals. + * @see clearPerElementNormals */ -template -void clearPerVertexNormals(MeshType& m) +template +void clearPerVertexNormals(MeshConcept auto& mesh, LogType& log = nullLogger) { - clearPerElementNormals(m); + clearPerElementNormals(mesh, log); } /** - * @brief Sets to zero the normals of all the faces of the mesh. + * @brief Sets to zero all the normals of vertices that are referenced by at + * least one face, leaving unchanged all the normals of the unreferenced + * vertices that may be still useful. * * Requirements: * - Mesh: - * - Faces: + * - Vertices: * - Normal + * - Faces * - * @param[in,out] m: The mesh on which clear the face normals. + * @param[in,out] m: The mesh on which clear the referenced vertex normals. + * @param[in,out] log: The logger used to log the performed operations. */ -template -void clearPerFaceNormals(MeshType& m) +template +void clearPerReferencedVertexNormals( + FaceMeshConcept auto& mesh, + LogType& log = nullLogger) { - clearPerElementNormals(m); + vcl::requirePerVertexNormal(mesh); + + // TODO: make this function more general: + // for each container of the mesh, look if its element has vertex references + // and if it has, normalize the normals of the referenced vertices. + + log.log(0, "Clearing per-Vertex normals..."); + + for (auto& f : mesh.faces()) { + for (auto* v : f.vertices()) { + v->normal().setZero(); + } + } + + log.log(100, "Per-Vertex normals cleared."); } /** - * @brief Normalizes the length of the vertex normals. + * @brief Same as clearPerElementNormals, but for the face normals. * - * Requirements: - * - Mesh: - * - Vertices: - * - Normal + * @see clearPerElementNormals + */ +template +void clearPerFaceNormals(FaceMeshConcept auto& mesh, LogType& log = nullLogger) +{ + clearPerElementNormals(mesh, log); +} + +/** + * @brief Same as normalizePerElementNormals, but for the vertex normals. * - * @param[in,out] m: the mesh on which normalize the vertex normals. + * @see normalizePerElementNormals */ -template -void normalizePerVertexNormals(MeshType& m) +template +void normalizePerVertexNormals( + MeshConcept auto& mesh, + LogType& log = nullLogger) { - normalizePerElementNormals(m); + normalizePerElementNormals(mesh, log); } /** * @brief Normalizes the length of normals the referenced vertices. * - * @param m - * @param log + * Requirements: + * - Mesh: + * - Vertex: + * - Normal + * - Face + * + * @param[in,out] mesh: the mesh on which normalize the vertex normals. + * @param[in,out] log: The logger used to log the performed operations. */ -template -void normalizePerReferencedVertexNormals(MeshType& m, LogType& log = nullLogger) +template +void normalizePerReferencedVertexNormals( + MeshConcept auto& mesh, + LogType& log = nullLogger) { - for (auto& f : m.faces()) { + vcl::requirePerVertexNormal(mesh); + + // TODO: make this function more general: + // for each container of the mesh, look if its element has vertex references + // and if it has, normalize the normals of the referenced vertices. + log.log(0, "Normalizing per-Vertex normals..."); + + for (auto& f : mesh.faces()) { for (auto* v : f.vertices()) { detail::normalizeNoThrow(*v, log); } } + + log.log(100, "Per-Vertex normals normalized."); } /** - * @brief Normalizes the length of the face normals. + * @brief Same as normalizePerElementNormals, but for the face normals. * - * Requirements: - * - Mesh: - * - Faces: - * - Normal - * - * @param[in,out] m: the mesh on which normalize the face normals. + * @see normalizePerElementNormals */ -template -void normalizePerFaceNormals(MeshType& m) +template +void normalizePerFaceNormals( + FaceMeshConcept auto& mesh, + LogType& log = nullLogger) { - normalizePerElementNormals(m); + normalizePerElementNormals(mesh, log); } /** - * @brief Multiplies the Vertex Normals by the given TRS 4x4 Matrix. - * By default, the scale component is removed from the matrix. + * @brief Same as multiplyPerElementNormalsByMatrix, but for the vertex + * normals. * - * Requirements: - * - Mesh: - * - Vertices: - * - Normal + * Accepts both 3x3 and 4x4 matrices. * - * @param[in,out] mesh: the mesh on which multiply the vertex normals. - * @param[in] mat: the 4x4 TRS matrix that is multiplied to the normals. - * @param[in] removeScalingFromMatrix: if true (default), the scale component is - * removed from the matrix. + * @see multiplyPerElementNormalsByMatrix */ -template +template void multiplyPerVertexNormalsByMatrix( - MeshType& mesh, - const vcl::Matrix44& mat, - bool removeScalingFromMatrix = true) + MeshConcept auto& mesh, + const Matrix& mat, + bool removeScalingFromMatrix = true, + LogType& log = nullLogger) { multiplyPerElementNormalsByMatrix( - mesh, mat, removeScalingFromMatrix); + mesh, mat, removeScalingFromMatrix, log); } /** - * @brief Multiplies the Face Normals by the given TRS 4x4 Matrix. - * By default, the scale component is removed from the matrix. + * @brief Same as multiplyPerElementNormalsByMatrix, but for the face normals. * - * Requirements: - * - Mesh: - * - Faces - * - Normal + * Accepts both 3x3 and 4x4 matrices. * - * @param[in,out] mesh: the mesh on which multiply the face normals. - * @param[in] mat: the 4x4 TRS matrix that is multiplied to the normals. - * @param[in] removeScalingFromMatrix: if true (default), the scale component is - * removed from the matrix. + * @see multiplyPerElementNormalsByMatrix */ -template +template void multiplyPerFaceNormalsByMatrix( - MeshType& mesh, - const vcl::Matrix44& mat, - bool removeScalingFromMatrix = true) + FaceMeshConcept auto& mesh, + const Matrix& mat, + bool removeScalingFromMatrix = true, + LogType& log = nullLogger) { - multiplyPerElementNormalsByMatrix(mesh, mat, removeScalingFromMatrix); + multiplyPerElementNormalsByMatrix( + mesh, mat, removeScalingFromMatrix, log); } /** - * @brief updatePerFaceNormals - * @param m + * @brief Computes and sets the face normal. + * + * The function works both for Triangle and Polygonal faces. + * + * For polygonal faces, the normal is computed as the normalized sum of the + * cross products of each triplet of consecutive vertices of the face. + * + * @see vcl::Polygon::normal() + * + * @param[in,out] mesh: the mesh on which compute the face normals. * @param[in] normalize: if true (default), normals are normalized after * computation. + * @param[in,out] log: The logger used to log the performed operations. */ -template -void updatePerFaceNormals(MeshType& m, bool normalize = true) +template +void updatePerFaceNormals( + FaceMeshConcept auto& mesh, + bool normalize = true, + LogType& log = nullLogger) { - vcl::requirePerFaceNormal(m); + vcl::requirePerFaceNormal(mesh); + + using FaceType = std::remove_reference_t::FaceType; + using ScalarType = FaceType::NormalType::ScalarType; + + log.log(0, "Updating per-Face normals..."); + + parallelFor(mesh.faces(), [](auto& f){ + f.normal() = faceNormal(f).template cast(); + }); - using FaceType = MeshType::FaceType; - for (FaceType& f : m.faces()) { - f.normal() = - faceNormal(f) - .template cast(); + if (normalize) { + log.startNewTask(50, 100, "Normalizing per-Face normals..."); + normalizePerFaceNormals(mesh, log); + log.endTask("Normalizing per-Face normals..."); } - if (normalize) - normalizePerFaceNormals(m); + log.log(100, "Per-Face normals updated."); } /** - * @brief Sets to zero all the normals of vertices that are referenced by at - * least one face, leaving unchanged all the normals of the unreferenced - * vertices that may be still useful. + * @brief Computes the vertex normal as the classic area weighted average. + * + * This function does not need or exploit current face normals. Unreferenced + * vertex normals are left unchanged. * * Requirements: * - Mesh: @@ -283,61 +410,101 @@ void updatePerFaceNormals(MeshType& m, bool normalize = true) * - Normal * - Faces * - * @param[in,out] m: The mesh on which clear the referenced vertex normals. + * @param[in,out] mesh: the mesh on which compute the vertex normals. + * @param[in] normalize: if true (default), normals are normalized after + * computation. + * @param[in,out] log: The logger used to log the performed operations. */ -template -void clearPerReferencedVertexNormals(MeshType& m) +template +void updatePerVertexNormals( + FaceMeshConcept auto& mesh, + bool normalize = true, + LogType& log = nullLogger) { - vcl::requirePerVertexNormal(m); + using VertexType = std::remove_reference_t::VertexType; + using NScalar = VertexType::NormalType::ScalarType; + + log.log(0, "Updating per-Vertex normals..."); - using VertexType = MeshType::VertexType; - using FaceType = MeshType::FaceType; + log.startNewTask(0, 20, "Clearing per-Vertex normals..."); + clearPerReferencedVertexNormals(mesh, log); + log.endTask("Clearing per-Vertex normals..."); - for (FaceType& f : m.faces()) { - for (auto& n : f.vertices() | views::normals) { - n.setZero(); + log.log(20, "Updating per-Vertex normals..."); + + for (auto& f : mesh.faces()) { + for (auto* v : f.vertices()) { + v->normal() += faceNormal(f).template cast(); } } + + if (normalize) { + log.startNewTask(80, 100, "Normalizing per-Vertex normals..."); + normalizePerReferencedVertexNormals(mesh, log); + log.endTask("Normalizing per-Vertex normals..."); + } + + log.log(100, "Per-Vertex normals updated."); } /** - * @brief Computes the vertex normal as the classic area weighted average. + * @brief Computes the vertex normal as the sum of the adjacent faces normals. * - * This function does not need or exploit current face normals. Unreferenced - * vertex normals are left unchanged. + * Unreferenced vertex normals are left unchanged. * * Requirements: * - Mesh: * - Vertices: * - Normal * - Faces + * - Normal * - * @param[in,out] m: the mesh on which compute the vertex normals. + * @param[in,out] mesh: the mesh on which compute the vertex normals. * @param[in] normalize: if true (default), normals are normalized after * computation. + * @param[in,out] log: The logger used to log the performed operations. */ -void updatePerVertexNormals(FaceMeshConcept auto& m, bool normalize = true) +template +void updatePerVertexNormalsFromFaceNormals( + FaceMeshConcept auto& mesh, + bool normalize = true, + LogType& log = nullLogger) { - clearPerReferencedVertexNormals(m); + vcl::requirePerFaceNormal(mesh); - using MeshType = std::remove_reference_t; - using VertexType = MeshType::VertexType; - using NormalType = VertexType::NormalType; - using NScalar = NormalType::ScalarType; + using VertexType = std::remove_reference_t::VertexType; + using ScalarType = VertexType::NormalType::ScalarType; - for (auto& f : m.faces()) { - for (VertexType* v : f.vertices()) { - v->normal() += faceNormal(f).template cast(); + log.log(0, "Updating per-Vertex normals..."); + + log.startNewTask(0, 20, "Clearing per-Vertex normals..."); + clearPerReferencedVertexNormals(mesh, log); + log.endTask("Clearing per-Vertex normals..."); + + log.log(20, "Updating per-Vertex normals..."); + + for (auto& f : mesh.faces()) { + for (auto* v : f.vertices()) { + v->normal() += f.normal().template cast(); } } - if (normalize) - normalizePerReferencedVertexNormals(m); + + if (normalize) { + log.startNewTask(80, 100, "Normalizing per-Vertex normals..."); + normalizePerReferencedVertexNormals(mesh, log); + log.endTask("Normalizing per-Vertex normals..."); + } + + log.log(100, "Per-Vertex normals updated."); } /** - * @brief Computes the vertex normal as the sum of the adjacent faces normals. + * @brief Computes the face normals and then vertex normal as the angle weighted + * average. * - * Unreferenced vertex normals are left unchanged. + * The result is the same as calling updatePerFaceNormals() and then + * updatePerVertexNormals(), but it is more efficient because it exploits the + * (not yet normalized) face normals to compute the vertex normals. * * Requirements: * - Mesh: @@ -346,27 +513,34 @@ void updatePerVertexNormals(FaceMeshConcept auto& m, bool normalize = true) * - Faces * - Normal * - * @param[in,out] m: the mesh on which compute the vertex normals. + * @param[in,out] mesh: the mesh on which compute the normals. * @param[in] normalize: if true (default), normals are normalized after * computation. + * @param[in,out] log: The logger used to log the performed operations. */ -template -void updatePerVertexNormalsFromFaceNormals(MeshType& m, bool normalize = true) +template +void updatePerVertexAndFaceNormals( + FaceMeshConcept auto& mesh, + bool normalize = true, + LogType& log = nullLogger) { - vcl::requirePerFaceNormal(m); + log.log(0, "Updating per-Vertex and per-Face normals..."); - clearPerReferencedVertexNormals(m); + log.startNewTask(0, 40, "Updating per-Face normals..."); + updatePerFaceNormals(mesh, false, log); // normals are not normalized here + log.endTask(""); - using VertexType = MeshType::VertexType; - using FaceType = MeshType::FaceType; + log.startNewTask(40, 80, "Updating per-Vertex normals..."); + updatePerVertexNormalsFromFaceNormals(mesh, normalize, log); + log.endTask(""); - for (FaceType& f : m.faces()) { - for (VertexType* v : f.vertices()) { - v->normal() += f.normal(); - } + if (normalize) { + log.startNewTask(80, 100, "Normalizing per-Face normals..."); + normalizePerFaceNormals(mesh, log); + log.endTask(""); } - if (normalize) - normalizePerVertexNormals(m); + + log.log(100, "Per-Vertex normals updated."); } /** @@ -390,39 +564,51 @@ void updatePerVertexNormalsFromFaceNormals(MeshType& m, bool normalize = true) * - Normal * - Faces * - * @param[in,out] m: the mesh on which compute the angle weighted vertex + * @param[in,out] mesh: the mesh on which compute the angle weighted vertex * normals. * @param[in] normalize: if true (default), normals are normalized after * computation. + * @param[in,out] log: The logger used to log the performed operations. */ -template -void updatePerVertexNormalsAngleWeighted(MeshType& m, bool normalize = true) +template +void updatePerVertexNormalsAngleWeighted( + FaceMeshConcept auto& mesh, + bool normalize = true, + LogType& log = nullLogger) { - clearPerReferencedVertexNormals(m); + using VertexType = std::remove_reference_t::VertexType; + using NScalarType = VertexType::NormalType::ScalarType; + + log.log(0, "Updating per-Vertex normals..."); + + log.startNewTask(0, 5, "Clearing per-Vertex normals..."); + clearPerReferencedVertexNormals(mesh, log); + log.endTask("Clearing per-Vertex normals..."); - using VertexType = MeshType::VertexType; - using FaceType = MeshType::FaceType; - using NormalType = VertexType::NormalType; - using NScalarType = NormalType::ScalarType; + log.log(5, "Updating per-Vertex normals..."); - for (FaceType& f : m.faces()) { - NormalType n = faceNormal(f).template cast(); + for (auto& f : mesh.faces()) { + auto n = faceNormal(f).template cast(); for (uint i = 0; i < f.vertexNumber(); ++i) { - NormalType vec1 = - (f.vertexMod(i - 1)->coord() - f.vertexMod(i)->coord()) - .normalized() - .template cast(); - NormalType vec2 = - (f.vertexMod(i + 1)->coord() - f.vertexMod(i)->coord()) - .normalized() - .template cast(); + auto vec1 = (f.vertexMod(i - 1)->coord() - f.vertexMod(i)->coord()) + .normalized() + .template cast(); + auto vec2 = (f.vertexMod(i + 1)->coord() - f.vertexMod(i)->coord()) + .normalized() + .template cast(); f.vertex(i)->normal() += n * vec1.angle(vec2); } } - if (normalize) - normalizePerVertexNormals(m); + + if (normalize) { + log.startNewTask(95, 100, "Normalizing per-Vertex normals..."); + normalizePerReferencedVertexNormals(mesh, log); + log.endTask("Normalizing per-Vertex normals..."); + } + + log.log(100, "Per-Vertex normals updated."); } /** @@ -449,23 +635,31 @@ void updatePerVertexNormalsAngleWeighted(MeshType& m, bool normalize = true) * - Normal * - Faces * - * @param[in,out] m: the mesh on which compute the Max et al. weighted vertex + * @param[in,out] mesh: the mesh on which compute the Max et al. weighted vertex * normals. * @param[in] normalize: if true (default), normals are normalized after * computation. + * @param[in,out] log: The logger used to log the performed operations. */ -template -void updatePerVertexNormalsNelsonMaxWeighted(MeshType& m, bool normalize = true) +template +void updatePerVertexNormalsNelsonMaxWeighted( + FaceMeshConcept auto& mesh, + bool normalize = true, + LogType& log = nullLogger) { - clearPerReferencedVertexNormals(m); + using VertexType = std::remove_reference_t::VertexType; + using NScalarType = VertexType::NormalType::ScalarType; - using VertexType = MeshType::VertexType; - using FaceType = MeshType::FaceType; - using NormalType = VertexType::NormalType; - using NScalarType = NormalType::ScalarType; + log.log(0, "Updating per-Vertex normals..."); - for (FaceType& f : m.faces()) { - NormalType n = faceNormal(f).template cast(); + log.startNewTask(0, 5, "Clearing per-Vertex normals..."); + clearPerReferencedVertexNormals(mesh, log); + log.endTask("Clearing per-Vertex normals..."); + + log.log(5, "Updating per-Vertex normals..."); + + for (auto& f : mesh.faces()) { + auto n = faceNormal(f).template cast(); for (uint i = 0; i < f.vertexNumber(); ++i) { NScalarType e1 = @@ -478,8 +672,14 @@ void updatePerVertexNormalsNelsonMaxWeighted(MeshType& m, bool normalize = true) f.vertex(i)->normal() += n / (e1 * e2); } } - if (normalize) - normalizePerVertexNormals(m); + + if (normalize) { + log.startNewTask(95, 100, "Normalizing per-Vertex normals..."); + normalizePerReferencedVertexNormals(mesh, log); + log.endTask("Normalizing per-Vertex normals..."); + } + + log.log(100, "Per-Vertex normals updated."); } } // namespace vcl diff --git a/include/vclib/space/polygon.h b/include/vclib/space/polygon.h index 7c340edbe..5ffe51d15 100644 --- a/include/vclib/space/polygon.h +++ b/include/vclib/space/polygon.h @@ -100,6 +100,9 @@ class Polygon * the iterators begin and end, listed in counterclockwise order, * representing a polygon. * + * The normal is computed as the normalized sum of the cross products of + * each triplet of consecutive points. + * * @tparam Iterator: an iterator which points to a type that satiesfies the * PointConcept. * @param[in] begin: iterator pointing to the first point of the polygon.