Coverage Report

Created: 2024-09-08 06:47

/src/draco/src/draco/mesh/mesh.cc
Line
Count
Source (jump to first uncovered line)
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/mesh.h"
16
17
#include <array>
18
#include <memory>
19
#include <unordered_map>
20
#include <utility>
21
#include <vector>
22
23
namespace draco {
24
25
// Shortcut for typed conditionals.
26
template <bool B, class T, class F>
27
using conditional_t = typename std::conditional<B, T, F>::type;
28
29
548
Mesh::Mesh() {}
30
31
#ifdef DRACO_TRANSCODER_SUPPORTED
32
void Mesh::Copy(const Mesh &src) {
33
  PointCloud::Copy(src);
34
  name_ = src.name_;
35
  faces_ = src.faces_;
36
  attribute_data_ = src.attribute_data_;
37
  material_library_.Copy(src.material_library_);
38
39
  // Copy mesh feature ID sets.
40
  mesh_features_.clear();
41
  for (MeshFeaturesIndex i(0); i < src.NumMeshFeatures(); i++) {
42
    std::unique_ptr<MeshFeatures> mesh_features(new MeshFeatures());
43
    mesh_features->Copy(src.GetMeshFeatures(i));
44
    AddMeshFeatures(std::move(mesh_features));
45
  }
46
  mesh_features_material_mask_ = src.mesh_features_material_mask_;
47
48
  // Copy non-material textures.
49
  non_material_texture_library_.Copy(src.non_material_texture_library_);
50
51
  // Update pointers to non-material textures in mesh feature ID sets.
52
  if (non_material_texture_library_.NumTextures() != 0) {
53
    const auto texture_to_index_map =
54
        src.non_material_texture_library_.ComputeTextureToIndexMap();
55
    for (MeshFeaturesIndex j(0); j < NumMeshFeatures(); ++j) {
56
      Mesh::UpdateMeshFeaturesTexturePointer(texture_to_index_map,
57
                                             &non_material_texture_library_,
58
                                             &GetMeshFeatures(j));
59
    }
60
  }
61
62
  // Copy structural metadata.
63
  structural_metadata_.Copy(src.structural_metadata_);
64
  property_attributes_ = src.property_attributes_;
65
  property_attributes_material_mask_ = src.property_attributes_material_mask_;
66
}
67
68
namespace {
69
// A helper struct that augments a point index with an attribute value index.
70
// A unique combination of |point_index| and |attribute_value_index|
71
// corresponds to a unique point on the mesh. Used to identify unique points
72
// after a new attribute is added to the mesh.
73
struct AugmentedPointData {
74
  PointIndex point_index;
75
  AttributeValueIndex attribute_value_index;
76
  bool operator<(const AugmentedPointData &pd) const {
77
    if (point_index < pd.point_index) {
78
      return true;
79
    }
80
    if (point_index > pd.point_index) {
81
      return false;
82
    }
83
    return attribute_value_index < pd.attribute_value_index;
84
  }
85
};
86
}  // namespace
87
88
int32_t Mesh::AddAttributeWithConnectivity(
89
    std::unique_ptr<PointAttribute> att,
90
    const IndexTypeVector<CornerIndex, AttributeValueIndex> &corner_to_value) {
91
  // Map between augmented point and new point indices (one augmented point
92
  // corresponds to one PointIndex).
93
  std::map<AugmentedPointData, PointIndex> old_to_new_point_map;
94
95
  // Map between corners and the new point indices.
96
  IndexTypeVector<CornerIndex, PointIndex> corner_to_point(num_faces() * 3,
97
                                                           kInvalidPointIndex);
98
99
  // Flag whether a given existing point index has been used. Used to ensure
100
  // that mapping between existing and new point indices that are smaller
101
  // than num_points() is identity. In other words, we want to keep indices of
102
  // the existing points intact and add new points to end.
103
  IndexTypeVector<PointIndex, bool> is_point_used(num_points(), false);
104
105
  int new_num_points = num_points();
106
  for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) {
107
    AugmentedPointData apd;
108
    apd.point_index = CornerToPointId(ci);
109
    apd.attribute_value_index = corner_to_value[ci];
110
    const auto it = old_to_new_point_map.find(apd);
111
    if (it != old_to_new_point_map.end()) {
112
      // Augmented point is already mapped to a point index. Reuse it.
113
      corner_to_point[ci] = it->second;
114
    } else {
115
      // New combination of point index + attribute value index. Map it to a
116
      // unique point index.
117
      PointIndex new_point_index;
118
      if (!is_point_used[apd.point_index]) {
119
        // Reuse the existing (old) point index.
120
        new_point_index = apd.point_index;
121
        is_point_used[apd.point_index] = true;
122
      } else {
123
        // Add a new point index to the end.
124
        new_point_index = PointIndex(new_num_points++);
125
      }
126
      old_to_new_point_map[apd] = new_point_index;
127
      corner_to_point[ci] = new_point_index;
128
    }
129
  }
130
131
  // Update point to attribute value mapping for the new attribute.
132
  att->SetExplicitMapping(new_num_points);
133
  for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) {
134
    att->SetPointMapEntry(corner_to_point[ci], corner_to_value[ci]);
135
  }
136
137
  // Update point to attribute value mapping on the remaining attributes if
138
  // needed.
139
  if (new_num_points > num_points()) {
140
    set_num_points(new_num_points);
141
142
    // Setup attributes for the new number of points.
143
    for (int ai = 0; ai < num_attributes(); ++ai) {
144
      const bool mapping_was_identity = attribute(ai)->is_mapping_identity();
145
      attribute(ai)->SetExplicitMapping(new_num_points);
146
      if (mapping_was_identity) {
147
        // Convert all old points from identity to explicit mapping.
148
        for (AttributeValueIndex avi(0); avi < attribute(ai)->size(); ++avi) {
149
          attribute(ai)->SetPointMapEntry(PointIndex(avi.value()), avi);
150
        }
151
      }
152
    }
153
154
    for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) {
155
      const PointIndex old_point_index = CornerToPointId(ci);
156
      const PointIndex new_point_index = corner_to_point[ci];
157
      if (old_point_index == new_point_index) {
158
        continue;
159
      }
160
      // Update point to value mapping for all existing attributes.
161
      for (int ai = 0; ai < num_attributes(); ++ai) {
162
        attribute(ai)->SetPointMapEntry(
163
            new_point_index, attribute(ai)->mapped_index(old_point_index));
164
      }
165
      // Update mapping between the corner and the new point index.
166
      faces_[FaceIndex(ci.value() / 3)][ci.value() % 3] = new_point_index;
167
    }
168
  }
169
170
  // If any of the old points have not been used, initialize dummy mapping for
171
  // the new attribute.
172
  for (PointIndex pi(0); pi < is_point_used.size(); ++pi) {
173
    if (!is_point_used[pi]) {
174
      att->SetPointMapEntry(pi, AttributeValueIndex(0));
175
    }
176
  }
177
178
  return PointCloud::AddAttribute(std::move(att));
179
}
180
181
int32_t Mesh::AddPerVertexAttribute(std::unique_ptr<PointAttribute> att) {
182
  const PointAttribute *const pos_att =
183
      GetNamedAttribute(GeometryAttribute::POSITION);
184
  if (pos_att == nullptr) {
185
    return -1;
186
  }
187
  if (att->size() != pos_att->size()) {
188
    return -1;  // Number of values must be same as in the position attribute.
189
  }
190
191
  if (pos_att->is_mapping_identity()) {
192
    att->SetIdentityMapping();
193
  } else {
194
    // Copy point to attribute value mapping from the position attribute to
195
    // |att|.
196
    att->SetExplicitMapping(num_points());
197
    for (PointIndex pi(0); pi < num_points(); ++pi) {
198
      att->SetPointMapEntry(pi, pos_att->mapped_index(pi));
199
    }
200
  }
201
202
  return PointCloud::AddAttribute(std::move(att));
203
}
204
205
void Mesh::RemoveIsolatedPoints() {
206
  // For each point, check if it is mapped to a face.
207
  IndexTypeVector<PointIndex, bool> is_point_used(num_points(), false);
208
  int num_used_points = 0;
209
  for (FaceIndex fi(0); fi < num_faces(); ++fi) {
210
    const auto &f = face(fi);
211
    for (int c = 0; c < 3; ++c) {
212
      if (!is_point_used[f[c]]) {
213
        num_used_points++;
214
        is_point_used[f[c]] = true;
215
      }
216
    }
217
  }
218
  if (num_used_points == num_points()) {
219
    return;  // All points are used.
220
  }
221
222
  // Create mapping between the old and new point indices.
223
  IndexTypeVector<PointIndex, PointIndex> old_to_new_point_map(
224
      num_points(), kInvalidPointIndex);
225
  PointIndex new_point_index(0);
226
  for (PointIndex pi(0); pi < num_points(); ++pi) {
227
    if (is_point_used[pi]) {
228
      old_to_new_point_map[pi] = new_point_index++;
229
    }
230
  }
231
232
  // Update point to attribute value index map for all attributes.
233
  for (int ai = 0; ai < num_attributes(); ++ai) {
234
    PointAttribute *att = attribute(ai);
235
    if (att->is_mapping_identity()) {
236
      // When the attribute uses identity mapping we need to reorder to the
237
      // attribute values to match the new point indices.
238
      for (PointIndex pi(0); pi < num_points(); ++pi) {
239
        const PointIndex new_pi = old_to_new_point_map[pi];
240
        if (new_pi == pi || new_pi == kInvalidPointIndex) {
241
          continue;
242
        }
243
        att->SetAttributeValue(
244
            AttributeValueIndex(new_pi.value()),
245
            att->GetAddress(AttributeValueIndex(pi.value())));
246
      }
247
      att->Resize(num_used_points);
248
    } else {
249
      // For explicitly mapped attributes, we first update the point to
250
      // attribute value mapping and then we remove all unused values from the
251
      // attribute.
252
      for (PointIndex pi(0); pi < num_points(); ++pi) {
253
        const PointIndex new_pi = old_to_new_point_map[pi];
254
        if (new_pi == pi || new_pi == kInvalidPointIndex) {
255
          continue;
256
        }
257
        att->SetPointMapEntry(new_pi, att->mapped_index(pi));
258
      }
259
      att->SetExplicitMapping(num_used_points);
260
261
      att->RemoveUnusedValues();
262
    }
263
  }
264
265
  // Update the mapping between faces and point indices.
266
  for (FaceIndex fi(0); fi < num_faces(); ++fi) {
267
    auto &f = faces_[fi];
268
    for (int c = 0; c < 3; ++c) {
269
      f[c] = old_to_new_point_map[f[c]];
270
    }
271
  }
272
273
  set_num_points(num_used_points);
274
}
275
276
void Mesh::RemoveUnusedMaterials() { RemoveUnusedMaterials(true); }
277
278
void Mesh::RemoveUnusedMaterials(bool remove_unused_material_indices) {
279
  const int mat_att_index = GetNamedAttributeId(GeometryAttribute::MATERIAL);
280
  if (mat_att_index == -1) {
281
    // Remove all materials except for the first one.
282
    while (GetMaterialLibrary().NumMaterials() > 1) {
283
      GetMaterialLibrary().RemoveMaterial(1);
284
    }
285
    GetMaterialLibrary().RemoveUnusedTextures();
286
    return;
287
  }
288
  auto mat_att = attribute(mat_att_index);
289
290
  // Deduplicate attribute values in the material attribute to ensure that one
291
  // attribute value index corresponds to one unique material index.
292
  // Note that this does not remove unused material indices.
293
  mat_att->DeduplicateValues(*mat_att);
294
295
  // Gather all material indices that are referenced by faces of the mesh.
296
  const int num_materials = GetMaterialLibrary().NumMaterials();
297
  std::vector<bool> is_material_used(num_materials, false);
298
  int num_used_materials = 0;
299
300
  // Helper function that updates |is_material_used| for the processed mesh.
301
  auto update_used_materials = [&is_material_used, &num_used_materials, mat_att,
302
                                num_materials](PointIndex pi) {
303
    uint32_t mat_index = 0;
304
    mat_att->GetMappedValue(pi, &mat_index);
305
    if (mat_index < num_materials) {
306
      if (!is_material_used[mat_index]) {
307
        is_material_used[mat_index] = true;
308
        num_used_materials++;
309
      }
310
    }
311
  };
312
313
  if (num_faces() > 0) {
314
    for (FaceIndex fi(0); fi < num_faces(); ++fi) {
315
      update_used_materials(faces_[fi][0]);
316
    }
317
  } else {
318
    // Handle the mesh as a point cloud and check materials used by points.
319
    for (PointIndex pi(0); pi < num_points(); ++pi) {
320
      update_used_materials(pi);
321
    }
322
  }
323
324
  // Check if any of the (unused) materials is used by mesh features. If so,
325
  // user should remove unused mesh features first.
326
  for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) {
327
    for (int mask_index = 0; mask_index < NumMeshFeaturesMaterialMasks(mfi);
328
         ++mask_index) {
329
      const int mat_index = GetMeshFeaturesMaterialMask(mfi, mask_index);
330
      if (mat_index < num_materials && !is_material_used[mat_index]) {
331
        is_material_used[mat_index] = true;
332
        num_used_materials++;
333
      }
334
    }
335
  }
336
337
  // Check if any of the (unused) materials is used by property attributes
338
  // indices. If so, user should remove unused property attributes indices
339
  // first.
340
  for (int i = 0; i < NumPropertyAttributesIndices(); ++i) {
341
    for (int mask_index = 0;
342
         mask_index < NumPropertyAttributesIndexMaterialMasks(i);
343
         ++mask_index) {
344
      const int mat_index =
345
          GetPropertyAttributesIndexMaterialMask(i, mask_index);
346
      if (mat_index < num_materials && !is_material_used[mat_index]) {
347
        is_material_used[mat_index] = true;
348
        num_used_materials++;
349
      }
350
    }
351
  }
352
353
  if (num_used_materials == num_materials) {
354
    return;  // All materials are used, don't do anything.
355
  }
356
357
  // Remove unused materials from the material library or replace them with
358
  // default materials if we do not remove unused material indices.
359
  for (int mi = num_materials - 1; mi >= 0; --mi) {
360
    if (!is_material_used[mi] && mi < GetMaterialLibrary().NumMaterials()) {
361
      if (remove_unused_material_indices) {
362
        GetMaterialLibrary().RemoveMaterial(mi);
363
      } else {
364
        GetMaterialLibrary().MutableMaterial(mi)->Clear();
365
      }
366
    }
367
  }
368
  GetMaterialLibrary().RemoveUnusedTextures();
369
370
  if (!remove_unused_material_indices) {
371
    // All the code below handles updating of material indices. Since we do not
372
    // want to update them, we can return early.
373
    return;
374
  }
375
376
  // Compute map between old and new material indices.
377
  std::vector<int> old_to_new_material_index_map(num_materials, -1);
378
  for (int mi = 0, new_material_index = 0; mi < num_materials; ++mi) {
379
    if (is_material_used[mi]) {
380
      old_to_new_material_index_map[mi] = new_material_index;
381
      ++new_material_index;
382
    }
383
  }
384
  IndexTypeVector<AttributeValueIndex, int>
385
      old_to_new_material_attribute_value_index_map(mat_att->size(), -1);
386
  for (AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) {
387
    uint32_t mat_index = 0;
388
    mat_att->GetValue(avi, &mat_index);
389
    if (mat_index < num_materials && is_material_used[mat_index]) {
390
      old_to_new_material_attribute_value_index_map[avi] =
391
          old_to_new_material_index_map[mat_index];
392
    }
393
  }
394
395
  // Update attribute values with the new number of materials.
396
  mat_att->Reset(num_used_materials);
397
398
  // Set identity mapping between AttributeValueIndex and material indices.
399
  for (AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) {
400
    const uint32_t mat_index = avi.value();
401
    mat_att->SetAttributeValue(avi, &mat_index);
402
  }
403
404
  // Update mapping between points and attribute values.
405
  for (PointIndex pi(0); pi < num_points(); ++pi) {
406
    const AttributeValueIndex old_avi = mat_att->mapped_index(pi);
407
    mat_att->SetPointMapEntry(
408
        pi, AttributeValueIndex(
409
                old_to_new_material_attribute_value_index_map[old_avi]));
410
  }
411
412
  // Update material indices on mesh features.
413
  for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) {
414
    for (int mask_index = 0; mask_index < NumMeshFeaturesMaterialMasks(mfi);
415
         ++mask_index) {
416
      const int old_mat_index = GetMeshFeaturesMaterialMask(mfi, mask_index);
417
      if (old_mat_index < num_materials && is_material_used[old_mat_index]) {
418
        mesh_features_material_mask_[mfi][mask_index] =
419
            old_to_new_material_index_map[old_mat_index];
420
      }
421
    }
422
  }
423
424
  // Update material indices on property attributes incices.
425
  for (int i = 0; i < NumPropertyAttributesIndices(); ++i) {
426
    for (int mask_index = 0;
427
         mask_index < NumPropertyAttributesIndexMaterialMasks(i);
428
         ++mask_index) {
429
      const int old_mat_index =
430
          GetPropertyAttributesIndexMaterialMask(i, mask_index);
431
      if (old_mat_index < num_materials && is_material_used[old_mat_index]) {
432
        property_attributes_material_mask_[i][mask_index] =
433
            old_to_new_material_index_map[old_mat_index];
434
      }
435
    }
436
  }
437
}
438
439
bool Mesh::IsAttributeUsedByMeshFeatures(int att_id) const {
440
  for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) {
441
    const auto &mf = GetMeshFeatures(mfi);
442
    if (mf.GetAttributeIndex() == att_id) {
443
      return true;
444
    }
445
  }
446
  return false;
447
}
448
449
void Mesh::UpdateMeshFeaturesTexturePointer(
450
    const std::unordered_map<const Texture *, int> &texture_to_index_map,
451
    TextureLibrary *texture_library, MeshFeatures *mesh_features) {
452
  TextureMap &texture_map = mesh_features->GetTextureMap();
453
  if (texture_map.texture() == nullptr) {
454
    return;
455
  }
456
  const auto it = texture_to_index_map.find(texture_map.texture());
457
  DRACO_DCHECK(it != texture_to_index_map.end());
458
  const int texture_index = it->second;
459
  DRACO_DCHECK(texture_index < texture_library->NumTextures());
460
  texture_map.SetTexture(texture_library->GetTexture(texture_index));
461
}
462
463
void Mesh::CopyMeshFeaturesForMaterial(const Mesh &source_mesh,
464
                                       Mesh *target_mesh, int material_index) {
465
  for (MeshFeaturesIndex mfi(0); mfi < source_mesh.NumMeshFeatures(); ++mfi) {
466
    // Mesh features is used if it doesn't have any material mask or if one
467
    // of the material masks matches |material_index|.
468
    bool is_used = source_mesh.NumMeshFeaturesMaterialMasks(mfi) == 0;
469
    for (int mask_index = 0;
470
         !is_used && mask_index < source_mesh.NumMeshFeaturesMaterialMasks(mfi);
471
         ++mask_index) {
472
      if (source_mesh.GetMeshFeaturesMaterialMask(mfi, mask_index) ==
473
          material_index) {
474
        is_used = true;
475
      }
476
    }
477
    if (is_used) {
478
      // Copy over the mesh features to the target mesh. Note that texture
479
      // pointers are not updated at this step.
480
      std::unique_ptr<MeshFeatures> new_mf(new MeshFeatures());
481
      new_mf->Copy(source_mesh.GetMeshFeatures(mfi));
482
      target_mesh->AddMeshFeatures(std::move(new_mf));
483
    }
484
  }
485
}
486
487
void Mesh::CopyPropertyAttributesIndicesForMaterial(const Mesh &source_mesh,
488
                                                    Mesh *target_mesh,
489
                                                    int material_index) {
490
  for (int i = 0; i < source_mesh.NumPropertyAttributesIndices(); ++i) {
491
    // Property attributes index is used if it doesn't have any material mask or
492
    // if one of the material masks matches |material_index|.
493
    bool is_used = source_mesh.NumPropertyAttributesIndexMaterialMasks(i) == 0;
494
    for (int mask_index = 0;
495
         !is_used &&
496
         mask_index < source_mesh.NumPropertyAttributesIndexMaterialMasks(i);
497
         ++mask_index) {
498
      if (source_mesh.GetPropertyAttributesIndexMaterialMask(i, mask_index) ==
499
          material_index) {
500
        is_used = true;
501
      }
502
    }
503
    if (is_used) {
504
      // Copy over the property attributes index to the target mesh.
505
      target_mesh->AddPropertyAttributesIndex(
506
          source_mesh.GetPropertyAttributesIndex(i));
507
    }
508
  }
509
}
510
511
void Mesh::UpdateMeshFeaturesAfterDeletedAttribute(int att_id) {
512
  for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) {
513
    auto &mf = GetMeshFeatures(mfi);
514
    if (mf.GetAttributeIndex() == att_id) {
515
      // Mesh features is no longer associated with a vertex attribute.
516
      mf.SetAttributeIndex(-1);
517
    } else if (mf.GetAttributeIndex() > att_id) {
518
      // Attribute index decremented by one.
519
      mf.SetAttributeIndex(mf.GetAttributeIndex() - 1);
520
    }
521
  }
522
}
523
524
int32_t Mesh::AddPerFaceAttribute(std::unique_ptr<PointAttribute> att) {
525
  IndexTypeVector<CornerIndex, AttributeValueIndex> corner_map(num_faces() * 3);
526
  for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) {
527
    corner_map[ci] = AttributeValueIndex(ci.value() / 3);
528
  }
529
  return AddAttributeWithConnectivity(std::move(att), corner_map);
530
}
531
#endif  // DRACO_TRANSCODER_SUPPORTED
532
533
#ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED
534
void Mesh::ApplyPointIdDeduplication(
535
    const IndexTypeVector<PointIndex, PointIndex> &id_map,
536
0
    const std::vector<PointIndex> &unique_point_ids) {
537
0
  PointCloud::ApplyPointIdDeduplication(id_map, unique_point_ids);
538
0
  for (FaceIndex f(0); f < num_faces(); ++f) {
539
0
    for (int32_t c = 0; c < 3; ++c) {
540
0
      faces_[f][c] = id_map[faces_[f][c]];
541
0
    }
542
0
  }
543
0
}
544
#endif
545
546
}  // namespace draco