Lagrange
compute_normal.h
1/*
2 * Copyright 2020 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <numeric>
15#include <vector>
16
17#include <lagrange/Mesh.h>
18#include <lagrange/chain_corners_around_edges.h>
19#include <lagrange/chain_corners_around_vertices.h>
20#include <lagrange/common.h>
21#include <lagrange/compute_triangle_normal.h>
22#include <lagrange/corner_to_edge_mapping.h>
23#include <lagrange/legacy/inline.h>
24#include <lagrange/utils/DisjointSets.h>
25#include <lagrange/utils/geometry3d.h>
26
27namespace lagrange {
28LAGRANGE_LEGACY_INLINE
29namespace legacy {
30
41template <typename MeshType>
43 MeshType& mesh,
44 std::function<bool(typename MeshType::Index, typename MeshType::Index)> is_edge_smooth,
45 const std::vector<typename MeshType::Index>& cone_vertices = {})
46{
47 static_assert(MeshTrait<MeshType>::is_mesh(), "Input type is not Mesh");
48 using Scalar = typename MeshType::Scalar;
49 using Index = typename MeshType::Index;
50 using FacetArray = typename MeshType::FacetArray;
51 using AttributeArray = typename MeshType::AttributeArray;
52 using IndexArray = Eigen::Matrix<Index, Eigen::Dynamic, 1>;
53 using RowVector3r = Eigen::Matrix<Scalar, 1, 3>;
54
56 mesh.get_vertex_per_facet() == 3,
57 "Only triangle meshes are supported for this.");
58 if (!mesh.has_facet_attribute("normal")) {
59 compute_triangle_normal(mesh);
60 }
61
62 // Compute edge information
63 logger().trace("Corner to edge mapping");
64 IndexArray c2e;
65 corner_to_edge_mapping(mesh.get_facets(), c2e);
66 logger().trace("Chain corners around edges");
67 IndexArray e2c;
68 IndexArray next_corner_around_edge;
69 chain_corners_around_edges(mesh.get_facets(), c2e, e2c, next_corner_around_edge);
70 logger().trace("Chain corners around vertices");
71 IndexArray v2c;
72 IndexArray next_corner_around_vertex;
74 mesh.get_num_vertices(),
75 mesh.get_facets(),
76 v2c,
77 next_corner_around_vertex);
78
79 const auto& vertices = mesh.get_vertices();
80 const auto& facets = mesh.get_facets();
81 const auto& facet_normals = mesh.get_facet_attribute("normal");
82 const auto nvpf = mesh.get_vertex_per_facet();
83 const auto num_vertices = mesh.get_num_vertices();
84 const auto num_corners = mesh.get_num_facets() * nvpf;
85
86 std::vector<bool> is_cone_vertex(num_vertices, false);
87 for (auto vi : cone_vertices) {
88 is_cone_vertex[vi] = true;
89 }
90
91 // Check if two face vertices are collapsed
92 auto is_face_degenerate = [&](Index f) {
93 for (Index lv = 0; lv < nvpf; ++lv) {
94 if (facets(f, lv) == facets(f, (lv + 1) % nvpf)) {
95 return true;
96 }
97 }
98 return false;
99 };
100
101 // STEP 1: For each vertex v, iterate over each pair of incident facet (fi, fj) that share a
102 // common edge eij.
103 logger().trace("Loop to unify corner indices");
104 DisjointSets<Index> unified_indices(num_corners);
105 tbb::parallel_for(Index(0), num_vertices, [&](Index v) {
106 for (Index ci = v2c[v]; ci != invalid<Index>(); ci = next_corner_around_vertex[ci]) {
107 Index eij = c2e[ci];
108 Index fi = ci / nvpf;
109 Index lvi = ci % nvpf;
110 Index vi = facets(fi, lvi);
111 assert(vi == v);
112 if (is_cone_vertex[vi]) continue;
113 if (is_face_degenerate(fi)) continue;
114
115 for (Index cj = e2c[eij]; cj != invalid<Index>(); cj = next_corner_around_edge[cj]) {
116 Index fj = cj / nvpf;
117 Index lvj = cj % nvpf;
118 if (fi == fj) continue;
119 Index vj = facets(fj, lvj);
120 if (vi != vj) {
121 lvj = (lvj + 1) % nvpf;
122 vj = facets(fj, lvj);
123 assert(vi == vj);
124 }
125
126 if (is_edge_smooth(fi, fj)) {
127 unified_indices.merge(ci, fj * nvpf + lvj);
128 }
129 }
130 }
131 });
132
133 // STEP 2: Perform averaging and reindex attribute
134 logger().trace("Compute new indices");
135 std::vector<Index> repr(num_corners, invalid<Index>());
136 Index num_indices = 0;
137 for (Index n = 0; n < num_corners; ++n) {
138 Index r = unified_indices.find(n);
139 if (repr[r] == invalid<Index>()) {
140 repr[r] = num_indices++;
141 }
142 repr[n] = repr[r];
143 }
144 logger().trace("Compute offsets");
145 std::vector<Index> indices(num_corners);
146 std::vector<Index> offsets(num_indices + 1, 0);
147 for (Index c = 0; c < num_corners; ++c) {
148 offsets[repr[c] + 1]++;
149 }
150 for (Index r = 1; r <= num_indices; ++r) {
151 offsets[r] += offsets[r - 1];
152 }
153 {
154 // Bucket sort for corner indices
155 std::vector<Index> counter = offsets;
156 for (Index c = 0; c < num_corners; ++c) {
157 indices[counter[repr[c]]++] = c;
158 }
159 }
160
161 logger().trace("Project and average normals");
162 FacetArray normal_indices(mesh.get_num_facets(), 3);
163 AttributeArray normal_values(num_indices, mesh.get_dim());
164 normal_values.setZero();
165 tbb::parallel_for(Index(0), Index(offsets.size() - 1), [&](Index r) {
166 for (Index i = offsets[r]; i < offsets[r + 1]; ++i) {
167 Index c = indices[i];
168 Index f = c / nvpf;
169 Index lv = c % nvpf;
170 assert(repr[c] == r);
171
172 RowVector3r n = facet_normals.row(f);
173
174 // Compute weight as the angle between the (projected) edges
175 RowVector3r v0 = vertices.row(facets(f, lv));
176 RowVector3r v1 = vertices.row(facets(f, (lv + 1) % nvpf));
177 RowVector3r v2 = vertices.row(facets(f, (lv + 2) % nvpf));
178 RowVector3r e01 = v1 - v0;
179 RowVector3r e02 = v2 - v0;
180 Scalar w = angle_between(e01, e02);
181
182 normal_values.row(r) += n * w;
183 normal_indices(f, lv) = r;
184 }
185 });
186 logger().trace("Normalize vectors");
187 tbb::parallel_for(Index(0), Index(normal_values.rows()), [&](Index c) {
188 normal_values.row(c).template head<3>().stableNormalize();
189 });
190
191 mesh.add_indexed_attribute("normal");
192 mesh.set_indexed_attribute("normal", normal_values, normal_indices);
193}
194
195template <typename MeshType>
196void compute_normal(
197 MeshType& mesh,
198 const typename MeshType::Scalar feature_angle_threshold,
199 const std::vector<typename MeshType::Index>& cone_vertices = {})
200{
201 static_assert(MeshTrait<MeshType>::is_mesh(), "Input type is not Mesh");
202 using Index = typename MeshType::Index;
203 using Scalar = typename MeshType::Scalar;
204 using RowVector3r = Eigen::Matrix<Scalar, 1, 3>;
205
207 mesh.get_vertex_per_facet() == 3,
208 "Only triangle meshes are supported for this.");
209 if (!mesh.has_facet_attribute("normal")) {
210 compute_triangle_normal(mesh);
211 }
212 const auto& facet_normals = mesh.get_facet_attribute("normal");
213
214 // Assumes fi and fj are adjacent facets.
215 auto is_edge_smooth = [&](Index fi, Index fj) {
216 const RowVector3r ni = facet_normals.row(fi);
217 const RowVector3r nj = facet_normals.row(fj);
218 const auto theta = angle_between(ni, nj);
219 return theta < feature_angle_threshold;
220 };
221
222 legacy::compute_normal(mesh, is_edge_smooth, cone_vertices);
223}
224
225} // namespace legacy
226} // namespace lagrange
Definition: Mesh.h:48
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition: Logger.cpp:40
AttributeId compute_normal(SurfaceMesh< Scalar, Index > &mesh, function_ref< bool(Index)> is_edge_smooth, span< const Index > cone_vertices={}, NormalOptions options={})
Compute smooth normals based on specified sharp edges and cone vertices.
Definition: compute_normal.cpp:195
AttributeId compute_normal(SurfaceMesh< Scalar, Index > &mesh, Scalar feature_angle_threshold, span< const Index > cone_vertices={}, NormalOptions options={})
Compute smooth normal based on specified dihedral angle threshold and cone vertices.
Definition: compute_normal.cpp:219
#define la_runtime_assert(...)
Runtime assertion check.
Definition: assert.h:169
Main namespace for Lagrange.
Definition: AABBIGL.h:30
Scalar angle_between(const Eigen::Matrix< Scalar, _Rows, _Cols > &v1, const Eigen::Matrix< Scalar, _Rows, _Cols > &v2)
Returns the angle between two 3d vectors.
Definition: geometry3d.h:36
Eigen::Index corner_to_edge_mapping(const Eigen::MatrixBase< DerivedF > &F, Eigen::PlainObjectBase< DerivedC > &C2E)
Computes a mapping from mesh corners (k*f+i) to unique edge ids.
Definition: corner_to_edge_mapping.impl.h:36
void chain_corners_around_edges(const Eigen::MatrixBase< DerivedF > &facets, const Eigen::MatrixBase< DerivedC > &corner_to_edge, Eigen::PlainObjectBase< DerivedE > &edge_to_corner, Eigen::PlainObjectBase< DerivedN > &next_corner_around_edge)
Chains facet corners around edges of a mesh.
Definition: chain_corners_around_edges.h:39
void chain_corners_around_vertices(typename DerivedF::Scalar num_vertices, const Eigen::MatrixBase< DerivedF > &facets, Eigen::PlainObjectBase< DerivedE > &vertex_to_corner, Eigen::PlainObjectBase< DerivedN > &next_corner_around_vertex)
Chains facet corners around vertices of a mesh.
Definition: chain_corners_around_vertices.h:41