Geometry Nodes: deduplicate join geometry code

The Realize Instances and Join Geometry node can share most of their code.
Until now, both nodes had their own implementations though. This patch
removes the implementation in the Join Geometry node in favor of the more
general Realize Instances implementation.

This removes an optimization for avoiding spline copies. This can be brought
back later. The realize instances code has to be rewritten to support attribute
instances anyway.

Differential Revision: https://developer.blender.org/D13417
This commit is contained in:
Jacques Lucke 2021-12-01 17:52:52 +01:00
parent d54a08c8af
commit 506d672524
Notes: blender-bot 2023-02-14 07:39:44 +01:00
Referenced by issue #93782, Naming attribute in multiple geometry nodes modifier causes blender to crash
Referenced by issue #93782, Naming attribute in multiple geometry nodes modifier causes blender to crash
1 changed files with 21 additions and 320 deletions

View File

@ -35,111 +35,6 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>(N_("Geometry"));
}
static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent *> src_components)
{
int totverts = 0;
int totloops = 0;
int totedges = 0;
int totpolys = 0;
int64_t cd_dirty_vert = 0;
int64_t cd_dirty_poly = 0;
int64_t cd_dirty_edge = 0;
int64_t cd_dirty_loop = 0;
VectorSet<Material *> materials;
for (const MeshComponent *mesh_component : src_components) {
const Mesh *mesh = mesh_component->get_for_read();
totverts += mesh->totvert;
totloops += mesh->totloop;
totedges += mesh->totedge;
totpolys += mesh->totpoly;
cd_dirty_vert |= mesh->runtime.cd_dirty_vert;
cd_dirty_poly |= mesh->runtime.cd_dirty_poly;
cd_dirty_edge |= mesh->runtime.cd_dirty_edge;
cd_dirty_loop |= mesh->runtime.cd_dirty_loop;
for (const int slot_index : IndexRange(mesh->totcol)) {
Material *material = mesh->mat[slot_index];
materials.add(material);
}
}
const Mesh *first_input_mesh = src_components[0]->get_for_read();
Mesh *new_mesh = BKE_mesh_new_nomain(totverts, totedges, 0, totloops, totpolys);
BKE_mesh_copy_parameters_for_eval(new_mesh, first_input_mesh);
for (const int i : IndexRange(materials.size())) {
Material *material = materials[i];
BKE_id_material_eval_assign(&new_mesh->id, i + 1, material);
}
new_mesh->runtime.cd_dirty_vert = cd_dirty_vert;
new_mesh->runtime.cd_dirty_poly = cd_dirty_poly;
new_mesh->runtime.cd_dirty_edge = cd_dirty_edge;
new_mesh->runtime.cd_dirty_loop = cd_dirty_loop;
int vert_offset = 0;
int loop_offset = 0;
int edge_offset = 0;
int poly_offset = 0;
for (const MeshComponent *mesh_component : src_components) {
const Mesh *mesh = mesh_component->get_for_read();
if (mesh == nullptr) {
continue;
}
Array<int> material_index_map(mesh->totcol);
for (const int i : IndexRange(mesh->totcol)) {
Material *material = mesh->mat[i];
const int new_material_index = materials.index_of(material);
material_index_map[i] = new_material_index;
}
for (const int i : IndexRange(mesh->totvert)) {
const MVert &old_vert = mesh->mvert[i];
MVert &new_vert = new_mesh->mvert[vert_offset + i];
new_vert = old_vert;
}
for (const int i : IndexRange(mesh->totedge)) {
const MEdge &old_edge = mesh->medge[i];
MEdge &new_edge = new_mesh->medge[edge_offset + i];
new_edge = old_edge;
new_edge.v1 += vert_offset;
new_edge.v2 += vert_offset;
}
for (const int i : IndexRange(mesh->totloop)) {
const MLoop &old_loop = mesh->mloop[i];
MLoop &new_loop = new_mesh->mloop[loop_offset + i];
new_loop = old_loop;
new_loop.v += vert_offset;
new_loop.e += edge_offset;
}
for (const int i : IndexRange(mesh->totpoly)) {
const MPoly &old_poly = mesh->mpoly[i];
MPoly &new_poly = new_mesh->mpoly[poly_offset + i];
new_poly = old_poly;
new_poly.loopstart += loop_offset;
if (old_poly.mat_nr >= 0 && old_poly.mat_nr < mesh->totcol) {
new_poly.mat_nr = material_index_map[new_poly.mat_nr];
}
else {
/* The material index was invalid before. */
new_poly.mat_nr = 0;
}
}
vert_offset += mesh->totvert;
loop_offset += mesh->totloop;
edge_offset += mesh->totedge;
poly_offset += mesh->totpoly;
}
return new_mesh;
}
template<typename Component>
static Array<const GeometryComponent *> to_base_components(Span<const Component *> components)
{
@ -223,33 +118,6 @@ static void join_attributes(Span<const GeometryComponent *> src_components,
}
}
static void join_components(Span<const MeshComponent *> src_components, GeometrySet &result)
{
Mesh *new_mesh = join_mesh_topology_and_builtin_attributes(src_components);
MeshComponent &dst_component = result.get_component_for_write<MeshComponent>();
dst_component.replace(new_mesh);
/* Don't copy attributes that are stored directly in the mesh data structs. */
join_attributes(to_base_components(src_components),
dst_component,
{"position", "material_index", "normal", "shade_smooth", "crease"});
}
static void join_components(Span<const PointCloudComponent *> src_components, GeometrySet &result)
{
int totpoints = 0;
for (const PointCloudComponent *pointcloud_component : src_components) {
totpoints += pointcloud_component->attribute_domain_size(ATTR_DOMAIN_POINT);
}
PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>();
PointCloud *pointcloud = BKE_pointcloud_new_nomain(totpoints);
dst_component.replace(pointcloud);
join_attributes(to_base_components(src_components), dst_component);
}
static void join_components(Span<const InstancesComponent *> src_components, GeometrySet &result)
{
InstancesComponent &dst_component = result.get_component_for_write<InstancesComponent>();
@ -288,192 +156,6 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet
UNUSED_VARS(src_components, dst_component);
}
/**
* \note This takes advantage of the fact that creating attributes on joined curves never
* changes a point attribute into a spline attribute; it is always the other way around.
*/
static void ensure_control_point_attribute(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
Span<CurveComponent *> src_components,
CurveEval &result)
{
MutableSpan<SplinePtr> splines = result.splines();
const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type);
/* In order to fill point attributes with spline domain attribute values where necessary, keep
* track of the curve each spline came from while iterating over the splines in the result. */
int src_component_index = 0;
int spline_index_in_component = 0;
const CurveEval *current_curve = src_components[src_component_index]->get_for_read();
for (SplinePtr &spline : splines) {
std::optional<GSpan> attribute = spline->attributes.get_for_read(attribute_id);
if (attribute) {
if (attribute->type() != type) {
/* In this case, the attribute exists, but it has the wrong type. So create a buffer
* for the converted values, do the conversion, and then replace the attribute. */
void *converted_buffer = MEM_mallocN_aligned(
spline->size() * type.size(), type.alignment(), __func__);
const DataTypeConversions &conversions = blender::nodes::get_implicit_type_conversions();
conversions.try_convert(GVArray::ForSpan(*attribute), type).materialize(converted_buffer);
spline->attributes.remove(attribute_id);
spline->attributes.create_by_move(attribute_id, data_type, converted_buffer);
}
}
else {
spline->attributes.create(attribute_id, data_type);
if (current_curve->attributes.get_for_read(attribute_id)) {
/* In this case the attribute did not exist, but there is a spline domain attribute
* we can retrieve a value from, as a spline to point domain conversion. So fill the
* new attribute with the value for this spline. */
GVArray current_curve_attribute = current_curve->attributes.get_for_read(
attribute_id, data_type, nullptr);
BLI_assert(spline->attributes.get_for_read(attribute_id));
std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(attribute_id);
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
current_curve_attribute.get(spline_index_in_component, buffer);
type.fill_assign_n(buffer, new_attribute->data(), new_attribute->size());
}
}
/* Move to the next spline and maybe the next input component. */
spline_index_in_component++;
if (spline != splines.last() && spline_index_in_component >= current_curve->splines().size()) {
src_component_index++;
spline_index_in_component = 0;
current_curve = src_components[src_component_index]->get_for_read();
}
}
}
/**
* Curve point domain attributes must be in the same order on every spline. The order might have
* been different on separate instances, so ensure that all splines have the same order. Note that
* because #Map is used, the order is not necessarily consistent every time, but it is the same for
* every spline, and that's what matters.
*/
static void sort_curve_point_attributes(const Map<AttributeIDRef, AttributeMetaData> &info,
MutableSpan<SplinePtr> splines)
{
Vector<AttributeIDRef> new_order;
for (Map<AttributeIDRef, AttributeMetaData>::Item item : info.items()) {
if (item.value.domain == ATTR_DOMAIN_POINT) {
/* Only sort attributes stored on splines. */
new_order.append(item.key);
}
}
for (SplinePtr &spline : splines) {
spline->attributes.reorder(new_order);
}
}
/**
* Fill data for an attribute on the new curve based on all source curves.
*/
static void ensure_spline_attribute(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
Span<CurveComponent *> src_components,
CurveEval &result)
{
const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type);
result.attributes.create(attribute_id, data_type);
GMutableSpan result_attribute = *result.attributes.get_for_write(attribute_id);
int offset = 0;
for (const CurveComponent *component : src_components) {
const CurveEval &curve = *component->get_for_read();
const int size = curve.splines().size();
if (size == 0) {
continue;
}
GVArray read_attribute = curve.attributes.get_for_read(attribute_id, data_type, nullptr);
GVArray_GSpan src_span{read_attribute};
const void *src_buffer = src_span.data();
type.copy_assign_n(src_buffer, result_attribute[offset], size);
offset += size;
}
}
/**
* Special handling for copying spline attributes. This is necessary because we move the splines
* out of the source components instead of copying them, meaning we can no longer access point
* domain attributes on the source components.
*
* \warning Splines have been moved out of the source components at this point, so it
* is important to only read curve-level data (spline domain attributes) from them.
*/
static void join_curve_attributes(const Map<AttributeIDRef, AttributeMetaData> &info,
Span<CurveComponent *> src_components,
CurveEval &result)
{
for (const Map<AttributeIDRef, AttributeMetaData>::Item item : info.items()) {
const AttributeIDRef attribute_id = item.key;
const AttributeMetaData meta_data = item.value;
if (meta_data.domain == ATTR_DOMAIN_CURVE) {
ensure_spline_attribute(attribute_id, meta_data.data_type, src_components, result);
}
else {
ensure_control_point_attribute(attribute_id, meta_data.data_type, src_components, result);
}
}
sort_curve_point_attributes(info, result.splines());
}
static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, GeometrySet &result)
{
Vector<CurveComponent *> src_components;
for (GeometrySet &geometry_set : src_geometry_sets) {
if (geometry_set.has_curve()) {
/* Retrieving with write access seems counterintuitive, but it can allow avoiding a copy
* in the case where the input spline has no other users, because the splines can be
* moved from the source curve rather than copied from a read-only source. Retrieving
* the curve for write will make a copy only when it has a user elsewhere. */
CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>();
src_components.append(&component);
}
}
if (src_components.size() == 0) {
return;
}
if (src_components.size() == 1) {
result.add(*src_components[0]);
return;
}
/* Retrieve attribute info before moving the splines out of the input components. */
const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(
{(const GeometryComponent **)src_components.data(), src_components.size()},
{"position", "radius", "tilt", "handle_left", "handle_right", "cyclic", "resolution"});
CurveComponent &dst_component = result.get_component_for_write<CurveComponent>();
CurveEval *dst_curve = new CurveEval();
for (CurveComponent *component : src_components) {
CurveEval *src_curve = component->get_for_write();
for (SplinePtr &spline : src_curve->splines()) {
dst_curve->add_spline(std::move(spline));
}
}
dst_curve->attributes.reallocate(dst_curve->splines().size());
join_curve_attributes(info, src_components, *dst_curve);
dst_curve->assert_valid_point_attributes();
dst_component.replace(dst_curve);
}
template<typename Component>
static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet &result)
{
@ -492,7 +174,26 @@ static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet
result.add(*components[0]);
return;
}
join_components(components, result);
GeometrySet instances_geometry_set;
InstancesComponent &instances =
instances_geometry_set.get_component_for_write<InstancesComponent>();
if constexpr (std::is_same_v<Component, InstancesComponent> ||
std::is_same_v<Component, VolumeComponent>) {
join_components(components, result);
}
else {
for (const Component *component : components) {
GeometrySet tmp_geo;
tmp_geo.add(*component);
const int handle = instances.add_reference(InstanceReference{tmp_geo});
instances.add_instance(handle, float4x4::identity());
}
GeometrySet joined_components = bke::geometry_set_realize_instances(instances_geometry_set);
result.add(joined_components.get_component_for_write<Component>());
}
}
static void node_geo_exec(GeoNodeExecParams params)
@ -504,7 +205,7 @@ static void node_geo_exec(GeoNodeExecParams params)
join_component_type<PointCloudComponent>(geometry_sets, geometry_set_result);
join_component_type<InstancesComponent>(geometry_sets, geometry_set_result);
join_component_type<VolumeComponent>(geometry_sets, geometry_set_result);
join_curve_components(geometry_sets, geometry_set_result);
join_component_type<CurveComponent>(geometry_sets, geometry_set_result);
params.set_output("Geometry", std::move(geometry_set_result));
}