Welcome to mirror list, hosted at ThFree Co, Russian Federation.

mesh_calc_edges.cc « intern « blenkernel « blender « source - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 4a4c2ebcbb0de5f859c3138711b4ad7eb181bd60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/* SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup bke
 */

#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"

#include "BLI_map.hh"
#include "BLI_task.hh"
#include "BLI_threads.h"
#include "BLI_timeit.hh"

#include "BKE_attribute.hh"
#include "BKE_customdata.h"
#include "BKE_mesh.h"

namespace blender::bke::calc_edges {

/** This is used to uniquely identify edges in a hash map. */
struct OrderedEdge {
  int v_low, v_high;

  OrderedEdge(const int v1, const int v2)
  {
    if (v1 < v2) {
      v_low = v1;
      v_high = v2;
    }
    else {
      v_low = v2;
      v_high = v1;
    }
  }

  OrderedEdge(const uint v1, const uint v2) : OrderedEdge(int(v1), int(v2))
  {
  }

  uint64_t hash() const
  {
    return (this->v_low << 8) ^ this->v_high;
  }

  /** Return a hash value that is likely to be different in the low bits from the normal `hash()`
   * function. This is necessary to avoid collisions in #BKE_mesh_calc_edges. */
  uint64_t hash2() const
  {
    return this->v_low;
  }

  friend bool operator==(const OrderedEdge &e1, const OrderedEdge &e2)
  {
    BLI_assert(e1.v_low < e1.v_high);
    BLI_assert(e2.v_low < e2.v_high);
    return e1.v_low == e2.v_low && e1.v_high == e2.v_high;
  }
};

/* The map first contains an edge pointer and later an index. */
union OrigEdgeOrIndex {
  const MEdge *original_edge;
  int index;
};
using EdgeMap = Map<OrderedEdge, OrigEdgeOrIndex>;

static void reserve_hash_maps(const Mesh *mesh,
                              const bool keep_existing_edges,
                              MutableSpan<EdgeMap> edge_maps)
{
  const int totedge_guess = std::max(keep_existing_edges ? mesh->totedge : 0, mesh->totpoly * 2);
  threading::parallel_for_each(
      edge_maps, [&](EdgeMap &edge_map) { edge_map.reserve(totedge_guess / edge_maps.size()); });
}

static void add_existing_edges_to_hash_maps(Mesh *mesh,
                                            MutableSpan<EdgeMap> edge_maps,
                                            uint32_t parallel_mask)
{
  /* Assume existing edges are valid. */
  const Span<MEdge> edges = mesh->edges();
  threading::parallel_for_each(edge_maps, [&](EdgeMap &edge_map) {
    const int task_index = &edge_map - edge_maps.data();
    for (const MEdge &edge : edges) {
      OrderedEdge ordered_edge{edge.v1, edge.v2};
      /* Only add the edge when it belongs into this map. */
      if (task_index == (parallel_mask & ordered_edge.hash2())) {
        edge_map.add_new(ordered_edge, {&edge});
      }
    }
  });
}

static void add_polygon_edges_to_hash_maps(Mesh *mesh,
                                           MutableSpan<EdgeMap> edge_maps,
                                           uint32_t parallel_mask)
{
  const Span<MPoly> polys = mesh->polys();
  const Span<MLoop> loops = mesh->loops();
  threading::parallel_for_each(edge_maps, [&](EdgeMap &edge_map) {
    const int task_index = &edge_map - edge_maps.data();
    for (const MPoly &poly : polys) {
      Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
      const MLoop *prev_loop = &poly_loops.last();
      for (const MLoop &next_loop : poly_loops) {
        /* Can only be the same when the mesh data is invalid. */
        if (prev_loop->v != next_loop.v) {
          OrderedEdge ordered_edge{prev_loop->v, next_loop.v};
          /* Only add the edge when it belongs into this map. */
          if (task_index == (parallel_mask & ordered_edge.hash2())) {
            edge_map.lookup_or_add(ordered_edge, {nullptr});
          }
        }
        prev_loop = &next_loop;
      }
    }
  });
}

static void serialize_and_initialize_deduplicated_edges(MutableSpan<EdgeMap> edge_maps,
                                                        MutableSpan<MEdge> new_edges)
{
  /* All edges are distributed in the hash tables now. They have to be serialized into a single
   * array below. To be able to parallelize this, we have to compute edge index offsets for each
   * map. */
  Array<int> edge_index_offsets(edge_maps.size());
  edge_index_offsets[0] = 0;
  for (const int i : IndexRange(edge_maps.size() - 1)) {
    edge_index_offsets[i + 1] = edge_index_offsets[i] + edge_maps[i].size();
  }

  threading::parallel_for_each(edge_maps, [&](EdgeMap &edge_map) {
    const int task_index = &edge_map - edge_maps.data();

    int new_edge_index = edge_index_offsets[task_index];
    for (EdgeMap::MutableItem item : edge_map.items()) {
      MEdge &new_edge = new_edges[new_edge_index];
      const MEdge *orig_edge = item.value.original_edge;
      if (orig_edge != nullptr) {
        /* Copy values from original edge. */
        new_edge = *orig_edge;
      }
      else {
        /* Initialize new edge. */
        new_edge.v1 = item.key.v_low;
        new_edge.v2 = item.key.v_high;
        new_edge.flag = ME_EDGEDRAW;
      }
      item.value.index = new_edge_index;
      new_edge_index++;
    }
  });
}

static void update_edge_indices_in_poly_loops(Mesh *mesh,
                                              Span<EdgeMap> edge_maps,
                                              uint32_t parallel_mask)
{
  const Span<MPoly> polys = mesh->polys();
  MutableSpan<MLoop> loops = mesh->loops_for_write();
  threading::parallel_for(IndexRange(mesh->totpoly), 100, [&](IndexRange range) {
    for (const int poly_index : range) {
      const MPoly &poly = polys[poly_index];
      MutableSpan<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);

      MLoop *prev_loop = &poly_loops.last();
      for (MLoop &next_loop : poly_loops) {
        int edge_index;
        if (prev_loop->v != next_loop.v) {
          OrderedEdge ordered_edge{prev_loop->v, next_loop.v};
          /* Double lookup: First find the map that contains the edge, then lookup the edge. */
          const EdgeMap &edge_map = edge_maps[parallel_mask & ordered_edge.hash2()];
          edge_index = edge_map.lookup(ordered_edge).index;
        }
        else {
          /* This is an invalid edge; normally this does not happen in Blender,
           * but it can be part of an imported mesh with invalid geometry. See
           * T76514. */
          edge_index = 0;
        }
        prev_loop->e = edge_index;
        prev_loop = &next_loop;
      }
    }
  });
}

static int get_parallel_maps_count(const Mesh *mesh)
{
  /* Don't use parallelization when the mesh is small. */
  if (mesh->totpoly < 1000) {
    return 1;
  }
  /* Use at most 8 separate hash tables. Using more threads has diminishing returns. These threads
   * can better do something more useful instead. */
  const int system_thread_count = BLI_system_thread_count();
  return power_of_2_min_i(std::min(8, system_thread_count));
}

static void clear_hash_tables(MutableSpan<EdgeMap> edge_maps)
{
  threading::parallel_for_each(edge_maps, [](EdgeMap &edge_map) { edge_map.clear(); });
}

}  // namespace blender::bke::calc_edges

void BKE_mesh_calc_edges(Mesh *mesh, bool keep_existing_edges, const bool select_new_edges)
{
  using namespace blender;
  using namespace blender::bke;
  using namespace blender::bke::calc_edges;

  /* Parallelization is achieved by having multiple hash tables for different subsets of edges.
   * Each edge is assigned to one of the hash maps based on the lower bits of a hash value. */
  const int parallel_maps = get_parallel_maps_count(mesh);
  BLI_assert(is_power_of_2_i(parallel_maps));
  const uint32_t parallel_mask = uint32_t(parallel_maps) - 1;
  Array<EdgeMap> edge_maps(parallel_maps);
  reserve_hash_maps(mesh, keep_existing_edges, edge_maps);

  /* Add all edges. */
  if (keep_existing_edges) {
    calc_edges::add_existing_edges_to_hash_maps(mesh, edge_maps, parallel_mask);
  }
  calc_edges::add_polygon_edges_to_hash_maps(mesh, edge_maps, parallel_mask);

  /* Compute total number of edges. */
  int new_totedge = 0;
  for (EdgeMap &edge_map : edge_maps) {
    new_totedge += edge_map.size();
  }

  /* Create new edges. */
  MutableSpan<MEdge> new_edges{
      static_cast<MEdge *>(MEM_calloc_arrayN(new_totedge, sizeof(MEdge), __func__)), new_totedge};
  calc_edges::serialize_and_initialize_deduplicated_edges(edge_maps, new_edges);
  calc_edges::update_edge_indices_in_poly_loops(mesh, edge_maps, parallel_mask);

  /* Free old CustomData and assign new one. */
  CustomData_free(&mesh->edata, mesh->totedge);
  CustomData_reset(&mesh->edata);
  CustomData_add_layer(&mesh->edata, CD_MEDGE, CD_ASSIGN, new_edges.data(), new_totedge);
  mesh->totedge = new_totedge;

  if (select_new_edges) {
    MutableAttributeAccessor attributes = mesh->attributes_for_write();
    SpanAttributeWriter<bool> select_edge = attributes.lookup_or_add_for_write_span<bool>(
        ".select_edge", ATTR_DOMAIN_EDGE);
    if (select_edge) {
      int new_edge_index = 0;
      for (const EdgeMap &edge_map : edge_maps) {
        for (EdgeMap::Item item : edge_map.items()) {
          if (item.value.original_edge == nullptr) {
            select_edge.span[new_edge_index] = true;
          }
          new_edge_index++;
        }
      }
      select_edge.finish();
    }
  }

  /* Explicitly clear edge maps, because that way it can be parallelized. */
  clear_hash_tables(edge_maps);
}