Files
test/source/blender/nodes/geometry/nodes/node_geo_boolean.cc
Jacques Lucke 2ffd08e952 Geometry Nodes: deterministic anonymous attribute lifetimes
Previously, the lifetimes of anonymous attributes were determined by
reference counts which were non-deterministic when multiple threads
are used. Now the lifetimes of anonymous attributes are handled
more explicitly and deterministically. This is a prerequisite for any kind
of caching, because caching the output of nodes that do things
non-deterministically and have "invisible inputs" (reference counts)
doesn't really work.

For more details for how deterministic lifetimes are achieved, see D16858.

No functional changes are expected. Small performance changes are expected
as well (within few percent, anything larger regressions should be reported as
bugs).

Differential Revision: https://developer.blender.org/D16858
2023-01-05 14:05:30 +01:00

194 lines
6.6 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_mesh_types.h"
#include "BKE_geometry_set_instances.hh"
#include "BKE_mesh_boolean_convert.hh"
#include "UI_interface.h"
#include "UI_resources.h"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_boolean_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Mesh 1"))
.only_realized_data()
.supported_type(GEO_COMPONENT_TYPE_MESH);
b.add_input<decl::Geometry>(N_("Mesh 2")).multi_input().supported_type(GEO_COMPONENT_TYPE_MESH);
b.add_input<decl::Bool>(N_("Self Intersection"));
b.add_input<decl::Bool>(N_("Hole Tolerant"));
b.add_output<decl::Geometry>(N_("Mesh")).propagate_all();
b.add_output<decl::Bool>(N_("Intersecting Edges")).field_on_all();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "operation", 0, "", ICON_NONE);
}
struct AttributeOutputs {
AutoAnonymousAttributeID intersecting_edges_id;
};
static void node_update(bNodeTree *ntree, bNode *node)
{
GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)node->custom1;
bNodeSocket *geometry_1_socket = static_cast<bNodeSocket *>(node->inputs.first);
bNodeSocket *geometry_2_socket = geometry_1_socket->next;
switch (operation) {
case GEO_NODE_BOOLEAN_INTERSECT:
case GEO_NODE_BOOLEAN_UNION:
nodeSetSocketAvailability(ntree, geometry_1_socket, false);
nodeSetSocketAvailability(ntree, geometry_2_socket, true);
node_sock_label(geometry_2_socket, N_("Mesh"));
break;
case GEO_NODE_BOOLEAN_DIFFERENCE:
nodeSetSocketAvailability(ntree, geometry_1_socket, true);
nodeSetSocketAvailability(ntree, geometry_2_socket, true);
node_sock_label(geometry_2_socket, N_("Mesh 2"));
break;
}
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = GEO_NODE_BOOLEAN_DIFFERENCE;
}
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_GMP
GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1;
const bool use_self = params.get_input<bool>("Self Intersection");
const bool hole_tolerant = params.get_input<bool>("Hole Tolerant");
Vector<const Mesh *> meshes;
Vector<const float4x4 *> transforms;
VectorSet<Material *> materials;
Vector<Array<short>> material_remaps;
GeometrySet set_a;
if (operation == GEO_NODE_BOOLEAN_DIFFERENCE) {
set_a = params.extract_input<GeometrySet>("Mesh 1");
/* Note that it technically wouldn't be necessary to realize the instances for the first
* geometry input, but the boolean code expects the first shape for the difference operation
* to be a single mesh. */
const Mesh *mesh_in_a = set_a.get_mesh_for_read();
if (mesh_in_a != nullptr) {
meshes.append(mesh_in_a);
transforms.append(nullptr);
if (mesh_in_a->totcol == 0) {
/* Necessary for faces using the default material when there are no material slots. */
materials.add(nullptr);
}
else {
materials.add_multiple({mesh_in_a->mat, mesh_in_a->totcol});
}
material_remaps.append({});
}
}
/* The instance transform matrices are owned by the instance group, so we have to
* keep all of them around for use during the boolean operation. */
Vector<bke::GeometryInstanceGroup> set_groups;
Vector<GeometrySet> geometry_sets = params.extract_input<Vector<GeometrySet>>("Mesh 2");
for (const GeometrySet &geometry_set : geometry_sets) {
bke::geometry_set_gather_instances(geometry_set, set_groups);
}
for (const bke::GeometryInstanceGroup &set_group : set_groups) {
const Mesh *mesh = set_group.geometry_set.get_mesh_for_read();
if (mesh != nullptr) {
Array<short> map(mesh->totcol);
for (const int i : IndexRange(mesh->totcol)) {
Material *material = mesh->mat[i];
map[i] = material ? materials.index_of_or_add(material) : -1;
}
material_remaps.append(std::move(map));
}
}
for (const bke::GeometryInstanceGroup &set_group : set_groups) {
const Mesh *mesh_in = set_group.geometry_set.get_mesh_for_read();
if (mesh_in != nullptr) {
meshes.append_n_times(mesh_in, set_group.transforms.size());
for (const int i : set_group.transforms.index_range()) {
transforms.append(set_group.transforms.begin() + i);
}
}
}
AttributeOutputs attribute_outputs;
attribute_outputs.intersecting_edges_id = params.get_output_anonymous_attribute_id_if_needed(
"Intersecting Edges");
Vector<int> intersecting_edges;
Mesh *result = blender::meshintersect::direct_mesh_boolean(
meshes,
transforms,
float4x4::identity(),
material_remaps,
use_self,
hole_tolerant,
operation,
attribute_outputs.intersecting_edges_id ? &intersecting_edges : nullptr);
if (!result) {
params.set_default_remaining_outputs();
return;
}
MEM_SAFE_FREE(result->mat);
result->mat = static_cast<Material **>(
MEM_malloc_arrayN(materials.size(), sizeof(Material *), __func__));
result->totcol = materials.size();
MutableSpan(result->mat, result->totcol).copy_from(materials);
/* Store intersecting edges in attribute. */
if (attribute_outputs.intersecting_edges_id) {
MutableAttributeAccessor attributes = result->attributes_for_write();
SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_only_span<bool>(
attribute_outputs.intersecting_edges_id.get(), ATTR_DOMAIN_EDGE);
selection.span.fill(false);
for (const int i : intersecting_edges) {
selection.span[i] = true;
}
selection.finish();
params.set_output(
"Intersecting Edges",
AnonymousAttributeFieldInput::Create<bool>(
std::move(attribute_outputs.intersecting_edges_id), params.attribute_producer_name()));
}
params.set_output("Mesh", GeometrySet::create_with_mesh(result));
#else
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without GMP"));
params.set_default_remaining_outputs();
#endif
}
} // namespace blender::nodes::node_geo_boolean_cc
void register_node_type_geo_boolean()
{
namespace file_ns = blender::nodes::node_geo_boolean_cc;
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_MESH_BOOLEAN, "Mesh Boolean", NODE_CLASS_GEOMETRY);
ntype.declare = file_ns::node_declare;
ntype.draw_buttons = file_ns::node_layout;
ntype.updatefunc = file_ns::node_update;
ntype.initfunc = file_ns::node_init;
ntype.geometry_node_execute = file_ns::node_geo_exec;
nodeRegisterType(&ntype);
}