diff options
author | Hans Goudey <h.goudey@me.com> | 2021-06-14 23:13:43 +0300 |
---|---|---|
committer | Hans Goudey <h.goudey@me.com> | 2021-06-14 23:13:43 +0300 |
commit | 61fdc450342e28007faea2e1696afc2ff034d6b9 (patch) | |
tree | a5e104a0da07402e916af1be05cfb8f0fe1044a2 /source | |
parent | bf7f918a0ef7144f1015905fae22203528cc9100 (diff) |
Geometry Nodes: Join dynamic curve attributes in the join geometry node
This commit lets the join geometry node transfer dynamic attributes
to the result, the same way that point cloud and mesh attributes are
joined. The implementation is different though, because of an
optimization implemented for curves to avoid copying splines.
The result attribute is added with the highest priority domain (points
over splines), and the highest complexity data type. If one curve had
the attribute on the spline domain but not others, the point domain
values will be used.
Generally this is a bit lower level than I would have liked this code
to be, but should be efficient, and it's really not too complicated.
Differential Revision: https://developer.blender.org/D11491
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc | 139 |
1 files changed, 132 insertions, 7 deletions
diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 66199769586..8bcd7d69bc5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -23,8 +23,12 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "NOD_type_conversions.hh" + #include "node_geometry_util.hh" +using blender::fn::GVArray_For_GSpan; + static bNodeSocketTemplate geo_node_join_geometry_in[] = { {SOCK_GEOMETRY, N_("Geometry"), @@ -298,6 +302,127 @@ 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 StringRef name, + 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(name); + + 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(std::make_unique<GVArray_For_GSpan>(*attribute), type) + ->materialize(converted_buffer); + + spline->attributes.remove(name); + spline->attributes.create_by_move(name, data_type, converted_buffer); + } + } + else { + spline->attributes.create(name, data_type); + + if (current_curve->attributes.get_for_read(name)) { + /* 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. */ + GVArrayPtr current_curve_attribute = current_curve->attributes.get_for_read( + name, data_type, nullptr); + + BLI_assert(spline->attributes.get_for_read(name)); + std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(name); + + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + current_curve_attribute->get(spline_index_in_component, buffer); + type.fill_initialized(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(); + } + } +} + +/** + * Fill data for an attribute on the new curve based on all source curves. + */ +static void ensure_spline_attribute(const StringRef name, + 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(name, data_type); + GMutableSpan result_attribute = *result.attributes.get_for_write(name); + + 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; + } + GVArrayPtr read_attribute = curve.attributes.get_for_read(name, data_type, nullptr); + GVArray_GSpan src_span{*read_attribute}; + + const void *src_buffer = src_span.data(); + type.copy_to_initialized_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<std::string, AttributeMetaData> &info, + Span<CurveComponent *> src_components, + CurveEval &result) +{ + for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) { + const StringRef name = item.key; + const AttributeMetaData meta_data = item.value; + + if (meta_data.domain == ATTR_DOMAIN_CURVE) { + ensure_spline_attribute(name, meta_data.data_type, src_components, result); + } + else { + ensure_control_point_attribute(name, meta_data.data_type, src_components, result); + } + } +} + static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, GeometrySet &result) { Vector<CurveComponent *> src_components; @@ -320,6 +445,11 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge return; } + /* Retrieve attribute info before moving the splines out of the input components. */ + const Map<std::string, AttributeMetaData> info = get_final_attribute_info( + {(const GeometryComponent **)src_components.data(), src_components.size()}, + {"position", "radius", "tilt", "cyclic", "resolution"}); + CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); CurveEval *dst_curve = new CurveEval(); for (CurveComponent *component : src_components) { @@ -328,14 +458,9 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge dst_curve->add_spline(std::move(spline)); } } - - /* For now, remove all custom attributes, since they might have different types, - * or an attribute might not exist on all splines. */ dst_curve->attributes.reallocate(dst_curve->splines().size()); - CustomData_reset(&dst_curve->attributes.data); - for (SplinePtr &spline : dst_curve->splines()) { - CustomData_reset(&spline->attributes.data); - } + + join_curve_attributes(info, src_components, *dst_curve); dst_component.replace(dst_curve); } |