This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc
Hans Goudey 96abaae9ac Cleanup: Remove legacy argument from mesh creation functions
The legacy `tessface_len` argument was only used for the explode
modifier. Remove it and copy the legacy face data manually there.
2023-02-27 11:24:22 -05:00

945 lines
39 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_attribute_math.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_dual_mesh_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Mesh").supported_type(GEO_COMPONENT_TYPE_MESH);
b.add_input<decl::Bool>("Keep Boundaries")
.default_value(false)
.description(
"Keep non-manifold boundaries of the input mesh in place by avoiding the dual "
"transformation there");
b.add_output<decl::Geometry>("Dual Mesh").propagate_all();
}
enum class EdgeType : int8_t {
Loose = 0, /* No polygons connected to it. */
Boundary = 1, /* An edge connected to exactly one polygon. */
Normal = 2, /* A normal edge (connected to two polygons). */
NonManifold = 3, /* An edge connected to more than two polygons. */
};
static EdgeType get_edge_type_with_added_neighbor(EdgeType old_type)
{
switch (old_type) {
case EdgeType::Loose:
return EdgeType::Boundary;
case EdgeType::Boundary:
return EdgeType::Normal;
case EdgeType::Normal:
case EdgeType::NonManifold:
return EdgeType::NonManifold;
}
BLI_assert_unreachable();
return EdgeType::Loose;
}
enum class VertexType : int8_t {
Loose = 0, /* Either no edges connected or only loose edges connected. */
Normal = 1, /* A normal vertex. */
Boundary = 2, /* A vertex on a boundary edge. */
NonManifold = 3, /* A vertex on a non-manifold edge. */
};
static VertexType get_vertex_type_with_added_neighbor(VertexType old_type)
{
switch (old_type) {
case VertexType::Loose:
return VertexType::Normal;
case VertexType::Normal:
return VertexType::Boundary;
case VertexType::Boundary:
case VertexType::NonManifold:
return VertexType::NonManifold;
}
BLI_assert_unreachable();
return VertexType::Loose;
}
/* Copy only where vertex_types is 'normal'. If keep boundaries is selected, also copy from
* boundary vertices. */
template<typename T>
static void copy_data_based_on_vertex_types(Span<T> data,
MutableSpan<T> r_data,
const Span<VertexType> vertex_types,
const bool keep_boundaries)
{
if (keep_boundaries) {
int out_i = 0;
for (const int i : data.index_range()) {
if (ELEM(vertex_types[i], VertexType::Normal, VertexType::Boundary)) {
r_data[out_i] = data[i];
out_i++;
}
}
}
else {
int out_i = 0;
for (const int i : data.index_range()) {
if (vertex_types[i] == VertexType::Normal) {
r_data[out_i] = data[i];
out_i++;
}
}
}
}
template<typename T>
static void copy_data_based_on_pairs(Span<T> data,
MutableSpan<T> r_data,
const Span<std::pair<int, int>> new_to_old_map)
{
for (const std::pair<int, int> &pair : new_to_old_map) {
r_data[pair.first] = data[pair.second];
}
}
/**
* Transfers the attributes from the original mesh to the new mesh using the following logic:
* - If the attribute was on the face domain it is now on the point domain, and this is true
* for all faces, so we can just copy these.
* - If the attribute was on the vertex domain there are three cases:
* - It was a 'bad' vertex so it is not in the dual mesh, and we can just ignore it
* - It was a normal vertex so it has a corresponding face in the dual mesh to which we can
* transfer.
* - It was a boundary vertex so it has a corresponding face, if keep_boundaries is true.
* Otherwise we can just ignore it.
* - If the attribute was on the edge domain we lookup for the new edges which edge it originated
* from using `new_to_old_edges_map`. We have to do it in this reverse order, because there can
* be more edges in the new mesh if keep boundaries is on.
* - We do the same thing for face corners as we do for edges.
*
* Some of the vertices (on the boundary) in the dual mesh don't come from faces, but from edges or
* vertices. For these the `boundary_vertex_to_relevant_face_map` is used, which maps them to the
* closest face.
*/
static void transfer_attributes(
const Span<VertexType> vertex_types,
const bool keep_boundaries,
const Span<int> new_to_old_edges_map,
const Span<int> new_to_old_face_corners_map,
const Span<std::pair<int, int>> boundary_vertex_to_relevant_face_map,
const AnonymousAttributePropagationInfo &propagation_info,
const AttributeAccessor src_attributes,
MutableAttributeAccessor dst_attributes)
{
/* Retrieve all attributes except for position which is handled manually.
* Remove anonymous attributes that don't need to be propagated. */
Set<AttributeIDRef> attribute_ids = src_attributes.all_ids();
attribute_ids.remove("position");
attribute_ids.remove_if([&](const AttributeIDRef &id) {
return id.is_anonymous() && !propagation_info.propagate(id.anonymous_id());
});
for (const AttributeIDRef &id : attribute_ids) {
GAttributeReader src_attribute = src_attributes.lookup(id);
eAttrDomain out_domain;
if (src_attribute.domain == ATTR_DOMAIN_FACE) {
out_domain = ATTR_DOMAIN_POINT;
}
else if (src_attribute.domain == ATTR_DOMAIN_POINT) {
out_domain = ATTR_DOMAIN_FACE;
}
else {
/* Edges and Face Corners. */
out_domain = src_attribute.domain;
}
const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(
src_attribute.varray.type());
GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
id, out_domain, data_type);
if (!dst_attribute) {
continue;
}
attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
using T = decltype(dummy);
VArraySpan<T> span{src_attribute.varray.typed<T>()};
MutableSpan<T> dst_span = dst_attribute.span.typed<T>();
switch (src_attribute.domain) {
case ATTR_DOMAIN_POINT:
copy_data_based_on_vertex_types(span, dst_span, vertex_types, keep_boundaries);
break;
case ATTR_DOMAIN_EDGE:
array_utils::gather(span, new_to_old_edges_map, dst_span);
break;
case ATTR_DOMAIN_FACE:
dst_span.take_front(span.size()).copy_from(span);
if (keep_boundaries) {
copy_data_based_on_pairs(span, dst_span, boundary_vertex_to_relevant_face_map);
}
break;
case ATTR_DOMAIN_CORNER:
array_utils::gather(span, new_to_old_face_corners_map, dst_span);
break;
default:
BLI_assert_unreachable();
}
});
dst_attribute.finish();
}
}
/**
* Calculates the boundaries of the mesh. Boundary polygons are not computed since we don't need
* them later on. We use the following definitions:
* - An edge is on a boundary if it is connected to only one polygon.
* - A vertex is on a boundary if it is on an edge on a boundary.
*/
static void calc_boundaries(const Mesh &mesh,
MutableSpan<VertexType> r_vertex_types,
MutableSpan<EdgeType> r_edge_types)
{
BLI_assert(r_vertex_types.size() == mesh.totvert);
BLI_assert(r_edge_types.size() == mesh.totedge);
const Span<MEdge> edges = mesh.edges();
const Span<MPoly> polys = mesh.polys();
const Span<MLoop> loops = mesh.loops();
r_vertex_types.fill(VertexType::Loose);
r_edge_types.fill(EdgeType::Loose);
/* Add up the number of polys connected to each edge. */
for (const int i : IndexRange(mesh.totpoly)) {
const MPoly &poly = polys[i];
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
for (const MLoop &loop : poly_loops) {
r_edge_types[loop.e] = get_edge_type_with_added_neighbor(r_edge_types[loop.e]);
}
}
/* Update vertices. */
for (const int i : IndexRange(mesh.totedge)) {
const EdgeType edge_type = r_edge_types[i];
if (edge_type == EdgeType::Loose) {
continue;
}
const MEdge &edge = edges[i];
if (edge_type == EdgeType::Boundary) {
r_vertex_types[edge.v1] = get_vertex_type_with_added_neighbor(r_vertex_types[edge.v1]);
r_vertex_types[edge.v2] = get_vertex_type_with_added_neighbor(r_vertex_types[edge.v2]);
}
else if (edge_type >= EdgeType::NonManifold) {
r_vertex_types[edge.v1] = VertexType::NonManifold;
r_vertex_types[edge.v2] = VertexType::NonManifold;
}
}
/* Normal verts are on a normal edge, and not on boundary edges or non-manifold edges. */
for (const int i : IndexRange(mesh.totedge)) {
const EdgeType edge_type = r_edge_types[i];
if (edge_type == EdgeType::Normal) {
const MEdge &edge = edges[i];
if (r_vertex_types[edge.v1] == VertexType::Loose) {
r_vertex_types[edge.v1] = VertexType::Normal;
}
if (r_vertex_types[edge.v2] == VertexType::Loose) {
r_vertex_types[edge.v2] = VertexType::Normal;
}
}
}
}
/**
* Sorts the polygons connected to the given vertex based on polygon adjacency. The ordering is
* so such that the normals point in the same way as the original mesh. If the vertex is a
* boundary vertex, the first and last polygon have a boundary edge connected to the vertex. The
* `r_shared_edges` array at index i is set to the index of the shared edge between the i-th and
* `(i+1)-th` sorted polygon. Similarly the `r_sorted_corners` array at index i is set to the
* corner in the i-th sorted polygon. If the polygons couldn't be sorted, `false` is returned.
*
* How the faces are sorted (see diagrams below):
* (For this explanation we'll assume all faces are oriented clockwise)
* (The vertex whose connected polygons we need to sort is "v0")
*
* \code{.unparsed}
* Normal case: Boundary Vertex case:
* v1 ----- v2 ----- v3 | | |
* | f3 | f0 | v2 ---- v4 --------- v3---
* | | | | / ,-' |
* v8 ----- v0 ----- v4 | f0 / f1 ,-' |
* | f2 | f1 | | / ,-' |
* | | | | / ,-' |
* v7 ----- v6 ----- v5 | / ,-' f2 |
* | /,-' |
* v0 ------------------ v1---
* \endcode
*
* - First we get the two corners of each face that have an edge which contains v0. A corner is
* simply a vertex followed by an edge. In this case for the face "f0" for example, we'd end up
* with the corners (v: v4, e: v4<->v0) and (v: v0, e: v0<->v2). Note that if the face was
* oriented counter-clockwise we'd end up with the corners (v: v0, e: v0<->v4) and (v: v2, e:
* v0<->v2) instead.
* - Then we need to choose one polygon as our first. If "v0" is not on a boundary we can just
* choose any polygon. If it is on a boundary some more care needs to be taken. Here we need to
* pick a polygon which lies on the boundary (in the diagram either f0 or f2). To choose between
* the two we need the next step.
* - In the normal case we use this polygon to set `shared_edge_i` which indicates the index of the
* shared edge between this polygon and the next one. There are two possible choices: v0<->v4 and
* v2<->v0. To choose we look at the corners. Since the edge v0<->v2 lies on the corner which has
* v0, we set `shared_edge_i` to the other edge (v0<->v4), such that the next face will be "f1"
* which is the next face in clockwise order.
* - In the boundary vertex case, we do something similar, but we are also forced to choose the
* edge which is not on the boundary. If this doesn't line up with orientation of the polygon, we
* know we'll need to choose the other boundary polygon as our first polygon. If the orientations
* don't line up there as well, it means that the mesh normals are not consistent, and we just
* have to force an orientation for ourselves. (Imagine if f0 is oriented counter-clockwise and
* f2 is oriented clockwise for example)
* - Next comes a loop where we look at the other faces and find the one which has the shared
* edge. Then we set the next shared edge to the other edge on the polygon connected to "v0", and
* continue. Because of the way we've chosen the first shared edge the order of the faces will
* have the same orientation as that of the first polygon.
* (In this case we'd have f0 -> f1 -> f2 -> f3 which also goes around clockwise).
* - Every time we determine a shared edge, we can also add a corner to `r_sorted_corners`. This
* will simply be the corner which doesn't contain the shared edge.
* - Finally if we are in the normal case we also need to add the last "shared edge" to close the
* loop.
*/
static bool sort_vertex_polys(const Span<MEdge> edges,
const Span<MPoly> polys,
const Span<MLoop> loops,
const int vertex_index,
const bool boundary_vertex,
const Span<EdgeType> edge_types,
MutableSpan<int> connected_polys,
MutableSpan<int> r_shared_edges,
MutableSpan<int> r_sorted_corners)
{
if (connected_polys.size() <= 2 && (!boundary_vertex || connected_polys.size() == 0)) {
return true;
}
/* For each polygon store the two corners whose edge contains the vertex. */
Array<std::pair<int, int>> poly_vertex_corners(connected_polys.size());
for (const int i : connected_polys.index_range()) {
const MPoly &poly = polys[connected_polys[i]];
bool first_edge_done = false;
for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) {
const MLoop &loop = loops[loop_index];
if (edges[loop.e].v1 == vertex_index || edges[loop.e].v2 == vertex_index) {
if (!first_edge_done) {
poly_vertex_corners[i].first = loop_index;
first_edge_done = true;
}
else {
poly_vertex_corners[i].second = loop_index;
break;
}
}
}
}
int shared_edge_i = -1;
/* Determine first polygon and orientation. For now the orientation of the whole loop depends
* on the one polygon we chose as first. It's probably not worth it to check every polygon in
* the loop to determine the 'average' orientation. */
if (boundary_vertex) {
/* Our first polygon needs to be one which has a boundary edge. */
for (const int i : connected_polys.index_range()) {
const MLoop &first_loop = loops[poly_vertex_corners[i].first];
const MLoop &second_loop = loops[poly_vertex_corners[i].second];
if (edge_types[first_loop.e] == EdgeType::Boundary && first_loop.v == vertex_index) {
shared_edge_i = second_loop.e;
r_sorted_corners[0] = poly_vertex_corners[i].first;
std::swap(connected_polys[i], connected_polys[0]);
std::swap(poly_vertex_corners[i], poly_vertex_corners[0]);
break;
}
if (edge_types[second_loop.e] == EdgeType::Boundary && second_loop.v == vertex_index) {
shared_edge_i = first_loop.e;
r_sorted_corners[0] = poly_vertex_corners[i].second;
std::swap(connected_polys[i], connected_polys[0]);
std::swap(poly_vertex_corners[i], poly_vertex_corners[0]);
break;
}
}
if (shared_edge_i == -1) {
/* The rotation is inconsistent between the two polygons on the boundary. Just choose one
* of the polygon's orientation. */
for (const int i : connected_polys.index_range()) {
const MLoop &first_loop = loops[poly_vertex_corners[i].first];
const MLoop &second_loop = loops[poly_vertex_corners[i].second];
if (edge_types[first_loop.e] == EdgeType::Boundary) {
shared_edge_i = second_loop.e;
r_sorted_corners[0] = poly_vertex_corners[i].first;
std::swap(connected_polys[i], connected_polys[0]);
std::swap(poly_vertex_corners[i], poly_vertex_corners[0]);
break;
}
if (edge_types[second_loop.e] == EdgeType::Boundary) {
shared_edge_i = first_loop.e;
r_sorted_corners[0] = poly_vertex_corners[i].second;
std::swap(connected_polys[i], connected_polys[0]);
std::swap(poly_vertex_corners[i], poly_vertex_corners[0]);
break;
}
}
}
}
else {
/* Any polygon can be the first. Just need to check the orientation. */
const MLoop &first_loop = loops[poly_vertex_corners[0].first];
const MLoop &second_loop = loops[poly_vertex_corners[0].second];
if (first_loop.v == vertex_index) {
shared_edge_i = second_loop.e;
r_sorted_corners[0] = poly_vertex_corners[0].first;
}
else {
r_sorted_corners[0] = poly_vertex_corners[0].second;
shared_edge_i = first_loop.e;
}
}
BLI_assert(shared_edge_i != -1);
for (const int i : IndexRange(connected_polys.size() - 1)) {
r_shared_edges[i] = shared_edge_i;
/* Look at the other polys to see if it has this shared edge. */
int j = i + 1;
for (; j < connected_polys.size(); ++j) {
const MLoop &first_loop = loops[poly_vertex_corners[j].first];
const MLoop &second_loop = loops[poly_vertex_corners[j].second];
if (first_loop.e == shared_edge_i) {
r_sorted_corners[i + 1] = poly_vertex_corners[j].first;
shared_edge_i = second_loop.e;
break;
}
if (second_loop.e == shared_edge_i) {
r_sorted_corners[i + 1] = poly_vertex_corners[j].second;
shared_edge_i = first_loop.e;
break;
}
}
if (j == connected_polys.size()) {
/* The vertex is not manifold because the polygons around the vertex don't form a loop, and
* hence can't be sorted. */
return false;
}
std::swap(connected_polys[i + 1], connected_polys[j]);
std::swap(poly_vertex_corners[i + 1], poly_vertex_corners[j]);
}
if (!boundary_vertex) {
/* Shared edge between first and last polygon. */
r_shared_edges.last() = shared_edge_i;
}
return true;
}
/**
* Get the edge on the poly that contains the given vertex and is a boundary edge.
*/
static void boundary_edge_on_poly(const MPoly &poly,
const Span<MEdge> edges,
const Span<MLoop> loops,
const int vertex_index,
const Span<EdgeType> edge_types,
int &r_edge)
{
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
for (const MLoop &loop : poly_loops) {
if (edge_types[loop.e] == EdgeType::Boundary) {
const MEdge &edge = edges[loop.e];
if (edge.v1 == vertex_index || edge.v2 == vertex_index) {
r_edge = loop.e;
return;
}
}
}
}
/**
* Get the two edges on the poly that contain the given vertex and are boundary edges. The
* orientation of the poly is taken into account.
*/
static void boundary_edges_on_poly(const MPoly &poly,
const Span<MEdge> edges,
const Span<MLoop> loops,
const int vertex_index,
const Span<EdgeType> edge_types,
int &r_edge1,
int &r_edge2)
{
bool edge1_done = false;
/* This is set to true if the order in which we encounter the two edges is inconsistent with the
* orientation of the polygon. */
bool needs_swap = false;
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
for (const MLoop &loop : poly_loops) {
if (edge_types[loop.e] == EdgeType::Boundary) {
const MEdge &edge = edges[loop.e];
if (edge.v1 == vertex_index || edge.v2 == vertex_index) {
if (edge1_done) {
if (needs_swap) {
r_edge2 = r_edge1;
r_edge1 = loop.e;
}
else {
r_edge2 = loop.e;
}
return;
}
r_edge1 = loop.e;
edge1_done = true;
if (loop.v == vertex_index) {
needs_swap = true;
}
}
}
}
}
static void add_edge(const Span<MEdge> src_edges,
const int old_edge_i,
const int v1,
const int v2,
Vector<int> &new_to_old_edges_map,
Vector<MEdge> &new_edges,
Vector<int> &loop_edges)
{
MEdge new_edge = src_edges[old_edge_i];
new_edge.v1 = v1;
new_edge.v2 = v2;
const int new_edge_i = new_edges.size();
new_to_old_edges_map.append(old_edge_i);
new_edges.append(new_edge);
loop_edges.append(new_edge_i);
}
/* Returns true if the vertex is connected only to the two polygons and is not on the boundary. */
static bool vertex_needs_dissolving(const int vertex,
const int first_poly_index,
const int second_poly_index,
const Span<VertexType> vertex_types,
const Span<Vector<int>> vert_to_poly_map)
{
/* Order is guaranteed to be the same because 2poly verts that are not on the boundary are
* ignored in `sort_vertex_polys`. */
return (vertex_types[vertex] != VertexType::Boundary && vert_to_poly_map[vertex].size() == 2 &&
vert_to_poly_map[vertex][0] == first_poly_index &&
vert_to_poly_map[vertex][1] == second_poly_index);
}
/**
* Finds 'normal' vertices which are connected to only two polygons and marks them to not be
* used in the data-structures derived from the mesh. For each pair of polygons which has such a
* vertex, an edge is created for the dual mesh between the centers of those two polygons. All
* edges in the input mesh which contain such a vertex are marked as 'done' to prevent duplicate
* edges being created. (See #94144)
*/
static void dissolve_redundant_verts(const Span<MEdge> edges,
const Span<MPoly> polys,
const Span<MLoop> loops,
const Span<Vector<int>> vert_to_poly_map,
MutableSpan<VertexType> vertex_types,
MutableSpan<int> old_to_new_edges_map,
Vector<MEdge> &new_edges,
Vector<int> &new_to_old_edges_map)
{
const int vertex_num = vertex_types.size();
for (const int vert_i : IndexRange(vertex_num)) {
if (vert_to_poly_map[vert_i].size() != 2 || vertex_types[vert_i] != VertexType::Normal) {
continue;
}
const int first_poly_index = vert_to_poly_map[vert_i][0];
const int second_poly_index = vert_to_poly_map[vert_i][1];
const int new_edge_index = new_edges.size();
bool edge_created = false;
const MPoly &poly = polys[first_poly_index];
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
for (const MLoop &loop : poly_loops) {
const MEdge &edge = edges[loop.e];
const int v1 = edge.v1;
const int v2 = edge.v2;
bool mark_edge = false;
if (vertex_needs_dissolving(
v1, first_poly_index, second_poly_index, vertex_types, vert_to_poly_map)) {
/* This vertex is now 'removed' and should be ignored elsewhere. */
vertex_types[v1] = VertexType::Loose;
mark_edge = true;
}
if (vertex_needs_dissolving(
v2, first_poly_index, second_poly_index, vertex_types, vert_to_poly_map)) {
/* This vertex is now 'removed' and should be ignored elsewhere. */
vertex_types[v2] = VertexType::Loose;
mark_edge = true;
}
if (mark_edge) {
if (!edge_created) {
MEdge new_edge = MEdge(edge);
/* The vertex indices in the dual mesh are the polygon indices of the input mesh. */
new_edge.v1 = first_poly_index;
new_edge.v2 = second_poly_index;
new_to_old_edges_map.append(loop.e);
new_edges.append(new_edge);
edge_created = true;
}
old_to_new_edges_map[loop.e] = new_edge_index;
}
}
}
}
/**
* Calculate the barycentric dual of a mesh. The dual is only "dual" in terms of connectivity,
* i.e. applying the function twice will give the same vertices, edges, and faces, but not the
* same positions. When the option "Keep Boundaries" is selected the connectivity is no
* longer dual.
*
* For the dual mesh of a manifold input mesh:
* - The vertices are at the centers of the faces of the input mesh.
* - The edges connect the two vertices created from the two faces next to the edge in the input
* mesh.
* - The faces are at the vertices of the input mesh.
*
* Some special cases are needed for boundaries and non-manifold geometry.
*/
static Mesh *calc_dual_mesh(const Mesh &src_mesh,
const bool keep_boundaries,
const AnonymousAttributePropagationInfo &propagation_info)
{
const Span<float3> src_positions = src_mesh.vert_positions();
const Span<MEdge> src_edges = src_mesh.edges();
const Span<MPoly> src_polys = src_mesh.polys();
const Span<MLoop> src_loops = src_mesh.loops();
Array<VertexType> vertex_types(src_mesh.totvert);
Array<EdgeType> edge_types(src_mesh.totedge);
calc_boundaries(src_mesh, vertex_types, edge_types);
/* Stores the indices of the polygons connected to the vertex. Because the polygons are looped
* over in order of their indices, the polygon's indices will be sorted in ascending order.
* (This can change once they are sorted using `sort_vertex_polys`). */
Array<Vector<int>> vert_to_poly_map = bke::mesh_topology::build_vert_to_poly_map(
src_polys, src_loops, src_positions.size());
Array<Array<int>> vertex_shared_edges(src_mesh.totvert);
Array<Array<int>> vertex_corners(src_mesh.totvert);
threading::parallel_for(vert_to_poly_map.index_range(), 512, [&](IndexRange range) {
for (const int i : range) {
if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold ||
(!keep_boundaries && vertex_types[i] == VertexType::Boundary)) {
/* Bad vertex that we can't work with. */
continue;
}
MutableSpan<int> loop_indices = vert_to_poly_map[i];
Array<int> sorted_corners(loop_indices.size());
bool vertex_ok = true;
if (vertex_types[i] == VertexType::Normal) {
Array<int> shared_edges(loop_indices.size());
vertex_ok = sort_vertex_polys(src_edges,
src_polys,
src_loops,
i,
false,
edge_types,
loop_indices,
shared_edges,
sorted_corners);
vertex_shared_edges[i] = std::move(shared_edges);
}
else {
Array<int> shared_edges(loop_indices.size() - 1);
vertex_ok = sort_vertex_polys(src_edges,
src_polys,
src_loops,
i,
true,
edge_types,
loop_indices,
shared_edges,
sorted_corners);
vertex_shared_edges[i] = std::move(shared_edges);
}
if (!vertex_ok) {
/* The sorting failed which means that the vertex is non-manifold and should be ignored
* further on. */
vertex_types[i] = VertexType::NonManifold;
continue;
}
vertex_corners[i] = std::move(sorted_corners);
}
});
Vector<float3> vertex_positions(src_mesh.totpoly);
for (const int i : IndexRange(src_mesh.totpoly)) {
const MPoly &poly = src_polys[i];
BKE_mesh_calc_poly_center(&poly,
&src_loops[poly.loopstart],
reinterpret_cast<const float(*)[3]>(src_positions.data()),
vertex_positions[i]);
}
Array<int> boundary_edge_midpoint_index;
if (keep_boundaries) {
/* Only initialize when we actually need it. */
boundary_edge_midpoint_index.reinitialize(src_mesh.totedge);
/* We need to add vertices at the centers of boundary edges. */
for (const int i : IndexRange(src_mesh.totedge)) {
if (edge_types[i] == EdgeType::Boundary) {
const MEdge &edge = src_edges[i];
const float3 mid = math::midpoint(src_positions[edge.v1], src_positions[edge.v2]);
boundary_edge_midpoint_index[i] = vertex_positions.size();
vertex_positions.append(mid);
}
}
}
Vector<int> loop_lengths;
Vector<int> loops;
Vector<int> loop_edges;
Vector<MEdge> new_edges;
/* These are used to transfer attributes. */
Vector<int> new_to_old_face_corners_map;
Vector<int> new_to_old_edges_map;
/* Stores the index of the vertex in the dual and the face it should get the attribute from. */
Vector<std::pair<int, int>> boundary_vertex_to_relevant_face_map;
/* Since each edge in the dual (except the ones created with keep boundaries) comes from
* exactly one edge in the original, we can use this array to keep track of whether it still
* needs to be created or not. If it's not -1 it gives the index in `new_edges` of the dual
* edge. The edges coming from preserving the boundaries only get added once anyway, so we
* don't need a hash-map for that. */
Array<int> old_to_new_edges_map(src_mesh.totedge);
old_to_new_edges_map.fill(-1);
/* This is necessary to prevent duplicate edges from being created, but will likely not do
* anything for most meshes. */
dissolve_redundant_verts(src_edges,
src_polys,
src_loops,
vert_to_poly_map,
vertex_types,
old_to_new_edges_map,
new_edges,
new_to_old_edges_map);
for (const int i : IndexRange(src_mesh.totvert)) {
if (vertex_types[i] == VertexType::Loose || vertex_types[i] >= VertexType::NonManifold ||
(!keep_boundaries && vertex_types[i] == VertexType::Boundary)) {
/* Bad vertex that we can't work with. */
continue;
}
Vector<int> loop_indices = vert_to_poly_map[i];
Span<int> shared_edges = vertex_shared_edges[i];
Span<int> sorted_corners = vertex_corners[i];
if (vertex_types[i] == VertexType::Normal) {
if (loop_indices.size() <= 2) {
/* We can't make a polygon from 2 vertices. */
continue;
}
/* Add edges in the loop. */
for (const int i : shared_edges.index_range()) {
const int old_edge_i = shared_edges[i];
if (old_to_new_edges_map[old_edge_i] == -1) {
/* This edge has not been created yet. */
MEdge new_edge = src_edges[old_edge_i];
new_edge.v1 = loop_indices[i];
new_edge.v2 = loop_indices[(i + 1) % loop_indices.size()];
new_to_old_edges_map.append(old_edge_i);
old_to_new_edges_map[old_edge_i] = new_edges.size();
new_edges.append(new_edge);
}
loop_edges.append(old_to_new_edges_map[old_edge_i]);
}
new_to_old_face_corners_map.extend(sorted_corners);
}
else {
/**
* The code handles boundary vertices like the vertex marked "V" in the diagram below.
* The first thing that happens is ordering the faces f1,f2 and f3 (stored in
* loop_indices), together with their shared edges e3 and e4 (which get stored in
* shared_edges). The ordering could end up being clockwise or counterclockwise, for this
* we'll assume that the ordering f1->f2->f3 is chosen. After that we add the edges in
* between the polygons, in this case the edges f1--f2, and f2--f3. Now we need to merge
* these with the boundary edges e1 and e2. To do this we create an edge from f3 to the
* midpoint of e2 (computed in a previous step), from this midpoint to V, from V to the
* midpoint of e1 and from the midpoint of e1 to f1.
*
* \code{.unparsed}
* | | | | | |
* v2 ---- v3 --------- v4--- v2 ---- v3 -------- v4---
* | f3 / ,-' | | / ,-'|
* | / f2 ,-' | | / ,-' |
* e2 | /e3 ,-' e4 | ====> M1-f3-/--f2-.,-' |
* | / ,-' | ====> | / ,-'\ |
* | / ,-' f1 | | / ,-' f1 |
* | /,-' | | /,-' | |
* V-------------------- v5--- V------------M2----- v5---
* \endcode
*/
/* Add the edges in between the polys. */
for (const int i : shared_edges.index_range()) {
const int old_edge_i = shared_edges[i];
if (old_to_new_edges_map[old_edge_i] == -1) {
/* This edge has not been created yet. */
MEdge new_edge = src_edges[old_edge_i];
new_edge.v1 = loop_indices[i];
new_edge.v2 = loop_indices[i + 1];
new_to_old_edges_map.append(old_edge_i);
old_to_new_edges_map[old_edge_i] = new_edges.size();
new_edges.append(new_edge);
}
loop_edges.append(old_to_new_edges_map[old_edge_i]);
}
new_to_old_face_corners_map.extend(sorted_corners);
/* Add the vertex and the midpoints of the two boundary edges to the loop. */
/* Get the boundary edges. */
int edge1;
int edge2;
if (loop_indices.size() >= 2) {
/* The first boundary edge is at the end of the chain of polygons. */
boundary_edge_on_poly(
src_polys[loop_indices.last()], src_edges, src_loops, i, edge_types, edge1);
boundary_edge_on_poly(
src_polys[loop_indices.first()], src_edges, src_loops, i, edge_types, edge2);
}
else {
/* If there is only one polygon both edges are in that polygon. */
boundary_edges_on_poly(
src_polys[loop_indices[0]], src_edges, src_loops, i, edge_types, edge1, edge2);
}
const int last_face_center = loop_indices.last();
loop_indices.append(boundary_edge_midpoint_index[edge1]);
new_to_old_face_corners_map.append(sorted_corners.last());
const int first_midpoint = loop_indices.last();
if (old_to_new_edges_map[edge1] == -1) {
add_edge(src_edges,
edge1,
last_face_center,
first_midpoint,
new_to_old_edges_map,
new_edges,
loop_edges);
old_to_new_edges_map[edge1] = new_edges.size() - 1;
boundary_vertex_to_relevant_face_map.append(std::pair(first_midpoint, last_face_center));
}
else {
loop_edges.append(old_to_new_edges_map[edge1]);
}
loop_indices.append(vertex_positions.size());
/* This is sort of arbitrary, but interpolating would be a lot harder to do. */
new_to_old_face_corners_map.append(sorted_corners.first());
boundary_vertex_to_relevant_face_map.append(
std::pair(loop_indices.last(), last_face_center));
vertex_positions.append(src_positions[i]);
const int boundary_vertex = loop_indices.last();
add_edge(src_edges,
edge1,
first_midpoint,
boundary_vertex,
new_to_old_edges_map,
new_edges,
loop_edges);
loop_indices.append(boundary_edge_midpoint_index[edge2]);
new_to_old_face_corners_map.append(sorted_corners.first());
const int second_midpoint = loop_indices.last();
add_edge(src_edges,
edge2,
boundary_vertex,
second_midpoint,
new_to_old_edges_map,
new_edges,
loop_edges);
if (old_to_new_edges_map[edge2] == -1) {
const int first_face_center = loop_indices.first();
add_edge(src_edges,
edge2,
second_midpoint,
first_face_center,
new_to_old_edges_map,
new_edges,
loop_edges);
old_to_new_edges_map[edge2] = new_edges.size() - 1;
boundary_vertex_to_relevant_face_map.append(std::pair(second_midpoint, first_face_center));
}
else {
loop_edges.append(old_to_new_edges_map[edge2]);
}
}
loop_lengths.append(loop_indices.size());
for (const int j : loop_indices) {
loops.append(j);
}
}
Mesh *mesh_out = BKE_mesh_new_nomain(
vertex_positions.size(), new_edges.size(), loops.size(), loop_lengths.size());
transfer_attributes(vertex_types,
keep_boundaries,
new_to_old_edges_map,
new_to_old_face_corners_map,
boundary_vertex_to_relevant_face_map,
propagation_info,
src_mesh.attributes(),
mesh_out->attributes_for_write());
mesh_out->vert_positions_for_write().copy_from(vertex_positions);
MutableSpan<MEdge> dst_edges = mesh_out->edges_for_write();
MutableSpan<MPoly> dst_polys = mesh_out->polys_for_write();
MutableSpan<MLoop> dst_loops = mesh_out->loops_for_write();
int loop_start = 0;
for (const int i : IndexRange(mesh_out->totpoly)) {
dst_polys[i].loopstart = loop_start;
dst_polys[i].totloop = loop_lengths[i];
loop_start += loop_lengths[i];
}
for (const int i : IndexRange(mesh_out->totloop)) {
dst_loops[i].v = loops[i];
dst_loops[i].e = loop_edges[i];
}
dst_edges.copy_from(new_edges);
return mesh_out;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh");
const bool keep_boundaries = params.extract_input<bool>("Keep Boundaries");
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
if (const Mesh *mesh = geometry_set.get_mesh_for_read()) {
Mesh *new_mesh = calc_dual_mesh(
*mesh, keep_boundaries, params.get_output_propagation_info("Dual Mesh"));
geometry_set.replace_mesh(new_mesh);
}
});
params.set_output("Dual Mesh", std::move(geometry_set));
}
} // namespace blender::nodes::node_geo_dual_mesh_cc
void register_node_type_geo_dual_mesh()
{
namespace file_ns = blender::nodes::node_geo_dual_mesh_cc;
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_DUAL_MESH, "Dual Mesh", NODE_CLASS_GEOMETRY);
ntype.declare = file_ns::node_declare;
ntype.geometry_node_execute = file_ns::node_geo_exec;
nodeRegisterType(&ntype);
}