Coverage Report

Created: 2026-08-15 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/draco/src/draco/mesh/corner_table.cc
Line
Count
Source
1
// Copyright 2016 The Draco Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
//
15
#include "draco/mesh/corner_table.h"
16
17
#include <limits>
18
#include <memory>
19
20
#include "draco/attributes/geometry_indices.h"
21
#include "draco/mesh/corner_table_iterators.h"
22
23
namespace draco {
24
25
CornerTable::CornerTable()
26
27.8k
    : num_original_vertices_(0),
27
27.8k
      num_degenerated_faces_(0),
28
27.8k
      num_isolated_vertices_(0),
29
27.8k
      valence_cache_(*this) {}
30
31
std::unique_ptr<CornerTable> CornerTable::Create(
32
0
    const IndexTypeVector<FaceIndex, FaceType> &faces) {
33
0
  std::unique_ptr<CornerTable> ct(new CornerTable());
34
0
  if (!ct->Init(faces)) {
35
0
    return nullptr;
36
0
  }
37
0
  return ct;
38
0
}
39
40
0
bool CornerTable::Init(const IndexTypeVector<FaceIndex, FaceType> &faces) {
41
0
  valence_cache_.ClearValenceCache();
42
0
  valence_cache_.ClearValenceCacheInaccurate();
43
0
  corner_to_vertex_map_.resize(faces.size() * 3);
44
0
  for (FaceIndex fi(0); fi < static_cast<uint32_t>(faces.size()); ++fi) {
45
0
    for (int i = 0; i < 3; ++i) {
46
0
      corner_to_vertex_map_[FirstCorner(fi) + i] = faces[fi][i];
47
0
    }
48
0
  }
49
0
  int num_vertices = -1;
50
0
  if (!ComputeOppositeCorners(&num_vertices)) {
51
0
    return false;
52
0
  }
53
0
  if (!BreakNonManifoldEdges()) {
54
0
    return false;
55
0
  }
56
0
  if (!ComputeVertexCorners(num_vertices)) {
57
0
    return false;
58
0
  }
59
0
  return true;
60
0
}
61
62
0
bool CornerTable::Reset(int num_faces) {
63
0
  return Reset(num_faces, num_faces * 3);
64
0
}
65
66
27.8k
bool CornerTable::Reset(int num_faces, int num_vertices) {
67
27.8k
  if (num_faces < 0 || num_vertices < 0) {
68
5
    return false;
69
5
  }
70
27.8k
  const unsigned int num_faces_unsigned = num_faces;
71
27.8k
  if (num_faces_unsigned >
72
27.8k
      std::numeric_limits<CornerIndex::ValueType>::max() / 3) {
73
0
    return false;
74
0
  }
75
27.8k
  corner_to_vertex_map_.assign(num_faces_unsigned * 3, kInvalidVertexIndex);
76
27.8k
  opposite_corners_.assign(num_faces_unsigned * 3, kInvalidCornerIndex);
77
27.8k
  vertex_corners_.reserve(num_vertices);
78
27.8k
  valence_cache_.ClearValenceCache();
79
27.8k
  valence_cache_.ClearValenceCacheInaccurate();
80
27.8k
  return true;
81
27.8k
}
82
83
0
bool CornerTable::ComputeOppositeCorners(int *num_vertices) {
84
0
  DRACO_DCHECK(GetValenceCache().IsCacheEmpty());
85
0
  if (num_vertices == nullptr) {
86
0
    return false;
87
0
  }
88
0
  opposite_corners_.resize(num_corners(), kInvalidCornerIndex);
89
90
  // Out implementation for finding opposite corners is based on keeping track
91
  // of outgoing half-edges for each vertex of the mesh. Half-edges (defined by
92
  // their opposite corners) are processed one by one and whenever a new
93
  // half-edge (corner) is processed, we check whether the sink vertex of
94
  // this half-edge contains its sibling half-edge. If yes, we connect them and
95
  // remove the sibling half-edge from the sink vertex, otherwise we add the new
96
  // half-edge to its source vertex.
97
98
  // First compute the number of outgoing half-edges (corners) attached to each
99
  // vertex.
100
0
  std::vector<int> num_corners_on_vertices;
101
0
  num_corners_on_vertices.reserve(num_corners());
102
0
  for (CornerIndex c(0); c < num_corners(); ++c) {
103
0
    const VertexIndex v1 = Vertex(c);
104
0
    if (v1.value() >= static_cast<int>(num_corners_on_vertices.size())) {
105
0
      num_corners_on_vertices.resize(v1.value() + 1, 0);
106
0
    }
107
    // For each corner there is always exactly one outgoing half-edge attached
108
    // to its vertex.
109
0
    num_corners_on_vertices[v1.value()]++;
110
0
  }
111
112
  // Create a storage for half-edges on each vertex. We store all half-edges in
113
  // one array, where each entry is identified by the half-edge's sink vertex id
114
  // and the associated half-edge corner id (corner opposite to the half-edge).
115
  // Each vertex will be assigned storage for up to
116
  // |num_corners_on_vertices[vert_id]| half-edges. Unused half-edges are marked
117
  // with |sink_vert| == kInvalidVertexIndex.
118
0
  struct VertexEdgePair {
119
0
    VertexEdgePair()
120
0
        : sink_vert(kInvalidVertexIndex), edge_corner(kInvalidCornerIndex) {}
121
0
    VertexIndex sink_vert;
122
0
    CornerIndex edge_corner;
123
0
  };
124
0
  std::vector<VertexEdgePair> vertex_edges(num_corners(), VertexEdgePair());
125
126
  // For each vertex compute the offset (location where the first half-edge
127
  // entry of a given vertex is going to be stored). This way each vertex is
128
  // guaranteed to have a non-overlapping storage with respect to the other
129
  // vertices.
130
0
  std::vector<int> vertex_offset(num_corners_on_vertices.size());
131
0
  int offset = 0;
132
0
  for (size_t i = 0; i < num_corners_on_vertices.size(); ++i) {
133
0
    vertex_offset[i] = offset;
134
0
    offset += num_corners_on_vertices[i];
135
0
  }
136
137
  // Now go over the all half-edges (using their opposite corners) and either
138
  // insert them to the |vertex_edge| array or connect them with existing
139
  // half-edges.
140
0
  for (CornerIndex c(0); c < num_corners(); ++c) {
141
0
    const VertexIndex tip_v = Vertex(c);
142
0
    const VertexIndex source_v = Vertex(Next(c));
143
0
    const VertexIndex sink_v = Vertex(Previous(c));
144
145
0
    const FaceIndex face_index = Face(c);
146
0
    if (c == FirstCorner(face_index)) {
147
      // Check whether the face is degenerated, if so ignore it.
148
0
      const VertexIndex v0 = Vertex(c);
149
0
      if (v0 == source_v || v0 == sink_v || source_v == sink_v) {
150
0
        ++num_degenerated_faces_;
151
0
        c += 2;  // Ignore the next two corners of the same face.
152
0
        continue;
153
0
      }
154
0
    }
155
156
0
    CornerIndex opposite_c(kInvalidCornerIndex);
157
    // The maximum number of half-edges attached to the sink vertex.
158
0
    const int num_corners_on_vert = num_corners_on_vertices[sink_v.value()];
159
    // Where to look for the first half-edge on the sink vertex.
160
0
    offset = vertex_offset[sink_v.value()];
161
0
    for (int i = 0; i < num_corners_on_vert; ++i, ++offset) {
162
0
      const VertexIndex other_v = vertex_edges[offset].sink_vert;
163
0
      if (other_v == kInvalidVertexIndex) {
164
0
        break;  // No matching half-edge found on the sink vertex.
165
0
      }
166
0
      if (other_v == source_v) {
167
0
        if (tip_v == Vertex(vertex_edges[offset].edge_corner)) {
168
0
          continue;  // Don't connect mirrored faces.
169
0
        }
170
        // A matching half-edge was found on the sink vertex. Mark the
171
        // half-edge's opposite corner.
172
0
        opposite_c = vertex_edges[offset].edge_corner;
173
        // Remove the half-edge from the sink vertex. We remap all subsequent
174
        // half-edges one slot down.
175
        // TODO(ostava): This can be optimized a little bit, by remapping only
176
        // the half-edge on the last valid slot into the deleted half-edge's
177
        // slot.
178
0
        for (int j = i + 1; j < num_corners_on_vert; ++j, ++offset) {
179
0
          vertex_edges[offset] = vertex_edges[offset + 1];
180
0
          if (vertex_edges[offset].sink_vert == kInvalidVertexIndex) {
181
0
            break;  // Unused half-edge reached.
182
0
          }
183
0
        }
184
        // Mark the last entry as unused.
185
0
        vertex_edges[offset].sink_vert = kInvalidVertexIndex;
186
0
        break;
187
0
      }
188
0
    }
189
0
    if (opposite_c == kInvalidCornerIndex) {
190
      // No opposite corner found. Insert the new edge
191
0
      const int num_corners_on_source_vert =
192
0
          num_corners_on_vertices[source_v.value()];
193
0
      offset = vertex_offset[source_v.value()];
194
0
      for (int i = 0; i < num_corners_on_source_vert; ++i, ++offset) {
195
        // Find the first unused half-edge slot on the source vertex.
196
0
        if (vertex_edges[offset].sink_vert == kInvalidVertexIndex) {
197
0
          vertex_edges[offset].sink_vert = sink_v;
198
0
          vertex_edges[offset].edge_corner = c;
199
0
          break;
200
0
        }
201
0
      }
202
0
    } else {
203
      // Opposite corner found.
204
0
      opposite_corners_[c] = opposite_c;
205
0
      opposite_corners_[opposite_c] = c;
206
0
    }
207
0
  }
208
0
  *num_vertices = static_cast<int>(num_corners_on_vertices.size());
209
0
  return true;
210
0
}
211
212
0
bool CornerTable::BreakNonManifoldEdges() {
213
  // This function detects and breaks non-manifold edges that are caused by
214
  // folds in 1-ring neighborhood around a vertex. Non-manifold edges can occur
215
  // when the 1-ring surface around a vertex self-intersects in a common edge.
216
  // For example imagine a surface around a pivot vertex 0, where the 1-ring
217
  // is defined by vertices |1, 2, 3, 1, 4|. The surface passes edge <0, 1>
218
  // twice which would result in a non-manifold edge that needs to be broken.
219
  // For now all faces connected to these non-manifold edges are disconnected
220
  // resulting in open boundaries on the mesh. New vertices will be created
221
  // automatically for each new disjoint patch in the ComputeVertexCorners()
222
  // method.
223
  // Note that all other non-manifold edges are implicitly handled by the
224
  // function ComputeVertexCorners() that automatically creates new vertices
225
  // on disjoint 1-ring surface patches.
226
227
0
  std::vector<bool> visited_corners(num_corners(), false);
228
0
  std::vector<std::pair<VertexIndex, CornerIndex>> sink_vertices;
229
0
  bool mesh_connectivity_updated = false;
230
0
  do {
231
0
    mesh_connectivity_updated = false;
232
0
    for (CornerIndex c(0); c < num_corners(); ++c) {
233
0
      if (visited_corners[c.value()]) {
234
0
        continue;
235
0
      }
236
0
      sink_vertices.clear();
237
238
      // First swing all the way to find the left-most corner connected to the
239
      // corner's vertex.
240
0
      CornerIndex first_c = c;
241
0
      CornerIndex current_c = c;
242
0
      CornerIndex next_c;
243
0
      while (next_c = SwingLeft(current_c),
244
0
             next_c != first_c && next_c != kInvalidCornerIndex &&
245
0
                 !visited_corners[next_c.value()]) {
246
0
        current_c = next_c;
247
0
      }
248
249
0
      first_c = current_c;
250
251
      // Swing right from the first corner and check if all visited edges
252
      // are unique.
253
0
      do {
254
0
        visited_corners[current_c.value()] = true;
255
        // Each new edge is defined by the pivot vertex (that is the same for
256
        // all faces) and by the sink vertex (that is the |next| vertex from the
257
        // currently processed pivot corner. I.e., each edge is uniquely defined
258
        // by the sink vertex index.
259
0
        const CornerIndex sink_c = Next(current_c);
260
0
        const VertexIndex sink_v = corner_to_vertex_map_[sink_c];
261
262
        // Corner that defines the edge on the face.
263
0
        const CornerIndex edge_corner = Previous(current_c);
264
0
        bool vertex_connectivity_updated = false;
265
        // Go over all processed edges (sink vertices). If the current sink
266
        // vertex has been already encountered before it may indicate a
267
        // non-manifold edge that needs to be broken.
268
0
        for (auto &&attached_sink_vertex : sink_vertices) {
269
0
          if (attached_sink_vertex.first == sink_v) {
270
            // Sink vertex has been already processed.
271
0
            const CornerIndex other_edge_corner = attached_sink_vertex.second;
272
0
            const CornerIndex opp_edge_corner = Opposite(edge_corner);
273
274
0
            if (opp_edge_corner == other_edge_corner) {
275
              // We are closing the loop so no need to change the connectivity.
276
0
              continue;
277
0
            }
278
279
            // Break the connectivity on the non-manifold edge.
280
            // TODO(ostava): It may be possible to reconnect the faces in a way
281
            // that the final surface would be manifold.
282
0
            const CornerIndex opp_other_edge_corner =
283
0
                Opposite(other_edge_corner);
284
0
            if (opp_edge_corner != kInvalidCornerIndex) {
285
0
              SetOppositeCorner(opp_edge_corner, kInvalidCornerIndex);
286
0
            }
287
0
            if (opp_other_edge_corner != kInvalidCornerIndex) {
288
0
              SetOppositeCorner(opp_other_edge_corner, kInvalidCornerIndex);
289
0
            }
290
291
0
            SetOppositeCorner(edge_corner, kInvalidCornerIndex);
292
0
            SetOppositeCorner(other_edge_corner, kInvalidCornerIndex);
293
294
0
            vertex_connectivity_updated = true;
295
0
            break;
296
0
          }
297
0
        }
298
0
        if (vertex_connectivity_updated) {
299
          // Because of the updated connectivity, not all corners connected to
300
          // this vertex have been processed and we need to go over them again.
301
          // TODO(ostava): This can be optimized as we don't really need to
302
          // iterate over all corners.
303
0
          mesh_connectivity_updated = true;
304
0
          break;
305
0
        }
306
        // Insert new sink vertex information <sink vertex index, edge corner>.
307
0
        std::pair<VertexIndex, CornerIndex> new_sink_vert;
308
0
        new_sink_vert.first = corner_to_vertex_map_[Previous(current_c)];
309
0
        new_sink_vert.second = sink_c;
310
0
        sink_vertices.push_back(new_sink_vert);
311
312
0
        current_c = SwingRight(current_c);
313
0
      } while (current_c != first_c && current_c != kInvalidCornerIndex);
314
0
    }
315
0
  } while (mesh_connectivity_updated);
316
0
  return true;
317
0
}
318
319
0
bool CornerTable::ComputeVertexCorners(int num_vertices) {
320
0
  DRACO_DCHECK(GetValenceCache().IsCacheEmpty());
321
0
  num_original_vertices_ = num_vertices;
322
0
  vertex_corners_.resize(num_vertices, kInvalidCornerIndex);
323
  // Arrays for marking visited vertices and corners that allow us to detect
324
  // non-manifold vertices.
325
0
  std::vector<bool> visited_vertices(num_vertices, false);
326
0
  std::vector<bool> visited_corners(num_corners(), false);
327
328
0
  for (FaceIndex f(0); f < num_faces(); ++f) {
329
0
    const CornerIndex first_face_corner = FirstCorner(f);
330
    // Check whether the face is degenerated. If so ignore it.
331
0
    if (IsDegenerated(f)) {
332
0
      continue;
333
0
    }
334
335
0
    for (int k = 0; k < 3; ++k) {
336
0
      const CornerIndex c = first_face_corner + k;
337
0
      if (visited_corners[c.value()]) {
338
0
        continue;
339
0
      }
340
0
      VertexIndex v = corner_to_vertex_map_[c];
341
      // Note that one vertex maps to many corners, but we just keep track
342
      // of the vertex which has a boundary on the left if the vertex lies on
343
      // the boundary. This means that all the related corners can be accessed
344
      // by iterating over the SwingRight() operator.
345
      // In case of a vertex inside the mesh, the choice is arbitrary.
346
0
      bool is_non_manifold_vertex = false;
347
0
      if (visited_vertices[v.value()]) {
348
        // A visited vertex of an unvisited corner found. Must be a non-manifold
349
        // vertex.
350
        // Create a new vertex for it.
351
0
        vertex_corners_.push_back(kInvalidCornerIndex);
352
0
        non_manifold_vertex_parents_.push_back(v);
353
0
        visited_vertices.push_back(false);
354
0
        v = VertexIndex(num_vertices++);
355
0
        is_non_manifold_vertex = true;
356
0
      }
357
      // Mark the vertex as visited.
358
0
      visited_vertices[v.value()] = true;
359
360
      // First swing all the way to the left and mark all corners on the way.
361
0
      CornerIndex act_c(c);
362
0
      while (act_c != kInvalidCornerIndex) {
363
0
        visited_corners[act_c.value()] = true;
364
        // Vertex will eventually point to the left most corner.
365
0
        vertex_corners_[v] = act_c;
366
0
        if (is_non_manifold_vertex) {
367
          // Update vertex index in the corresponding face.
368
0
          corner_to_vertex_map_[act_c] = v;
369
0
        }
370
0
        act_c = SwingLeft(act_c);
371
0
        if (act_c == c) {
372
0
          break;  // Full circle reached.
373
0
        }
374
0
      }
375
0
      if (act_c == kInvalidCornerIndex) {
376
        // If we have reached an open boundary we need to swing right from the
377
        // initial corner to mark all corners in the opposite direction.
378
0
        act_c = SwingRight(c);
379
0
        while (act_c != kInvalidCornerIndex) {
380
0
          visited_corners[act_c.value()] = true;
381
0
          if (is_non_manifold_vertex) {
382
            // Update vertex index in the corresponding face.
383
0
            corner_to_vertex_map_[act_c] = v;
384
0
          }
385
0
          act_c = SwingRight(act_c);
386
0
        }
387
0
      }
388
0
    }
389
0
  }
390
391
  // Count the number of isolated (unprocessed) vertices.
392
0
  num_isolated_vertices_ = 0;
393
0
  for (bool visited : visited_vertices) {
394
0
    if (!visited) {
395
0
      ++num_isolated_vertices_;
396
0
    }
397
0
  }
398
0
  return true;
399
0
}
400
401
0
bool CornerTable::IsDegenerated(FaceIndex face) const {
402
0
  if (face == kInvalidFaceIndex) {
403
0
    return true;
404
0
  }
405
0
  const CornerIndex first_face_corner = FirstCorner(face);
406
0
  const VertexIndex v0 = Vertex(first_face_corner);
407
0
  const VertexIndex v1 = Vertex(Next(first_face_corner));
408
0
  const VertexIndex v2 = Vertex(Previous(first_face_corner));
409
0
  if (v0 == v1 || v0 == v2 || v1 == v2) {
410
0
    return true;
411
0
  }
412
0
  return false;
413
0
}
414
415
0
int CornerTable::Valence(VertexIndex v) const {
416
0
  if (v == kInvalidVertexIndex) {
417
0
    return -1;
418
0
  }
419
0
  return ConfidentValence(v);
420
0
}
421
422
0
int CornerTable::ConfidentValence(VertexIndex v) const {
423
0
  DRACO_DCHECK_GE(v.value(), 0);
424
0
  DRACO_DCHECK_LT(v.value(), num_vertices());
425
0
  VertexRingIterator<CornerTable> vi(this, v);
426
0
  int valence = 0;
427
0
  for (; !vi.End(); vi.Next()) {
428
0
    ++valence;
429
0
  }
430
0
  return valence;
431
0
}
432
433
0
void CornerTable::UpdateFaceToVertexMap(const VertexIndex vertex) {
434
0
  DRACO_DCHECK(GetValenceCache().IsCacheEmpty());
435
0
  VertexCornersIterator<CornerTable> it(this, vertex);
436
0
  for (; !it.End(); ++it) {
437
0
    const CornerIndex corner = *it;
438
0
    corner_to_vertex_map_[corner] = vertex;
439
0
  }
440
0
}
441
442
}  // namespace draco