Files
test2/source/blender/geometry/intern/reorder.cc
Laurynas Duburas 46bc894570 Fix: Curves: Custom knots in Curves and GP operators
Current strategy to deal with operators not supporting custom NURBS
knots is to fall back to calculated knots for curves of the custom mode
but with no `CurvesGeometry::custom_knots` allocated. Such curves are
the result of operators that copy only `Point` and `Curve` domains. This
way the problem is only postponed. It is not possible to add new custom
knot curves to such `CurvesGeometry` as custom knot offsets are
calculated all together and there is no way to distinguish between old
curves with lost knots and new ones. This is more a future problem.

The actual problem in `main` can be shown with an attached blend file
(see PR) by applying `Subdivide` to some points and then adding new
`Bezier` curve to the same object. This particular problem could be
addressed somewhere in `realize_instances.cc` but the actual problem
would persist.

This PR handles custom knots in all places where `BKE_defgroup_copy_list`
is iused, and where `bke::curves::copy_only_curve_domain` is called.
Here the assumption is made that only these places can copy custom knots
modes without copying custom knots. Depending on operator logic knots are
handled most often in one of two ways:
 - `bke::curves::nurbs::copy_custom_knots`:
   copies custom knots for all curves excluding `selection`. Knot modes
   for excluded curves are altered from the custom mode to calculated.
   This way only curves modified by the operator will loose custom knots.
 - `bke::curves::nurbs::update_custom_knot_modes;`
   alters all curves to calculated mode.

In some places (e.g. `reorder.cc`) it is possible to deal with knots
without side effects.

PR also adds `BLI_assert` in `load_curve_knots` function to check if
`CurvesGeometry::custom_knots` exists for custom mode curves. Thus
versioning code is needed addressing the issue in files in case such
already exists.
Pull Request: https://projects.blender.org/blender/blender/pulls/139554
2025-06-04 20:43:15 +02:00

420 lines
18 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_attribute_filters.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "BKE_geometry_set.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_multi_value_map.hh"
#include "DNA_curves_types.h"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "GEO_reorder.hh"
namespace blender::geometry {
const MultiValueMap<bke::GeometryComponent::Type, bke::AttrDomain> &
components_supported_reordering()
{
using namespace bke;
const static MultiValueMap<GeometryComponent::Type, AttrDomain> supported_types_and_domains =
[]() {
MultiValueMap<GeometryComponent::Type, AttrDomain> supported_types_and_domains;
supported_types_and_domains.add_multiple(
GeometryComponent::Type::Mesh,
{AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face});
supported_types_and_domains.add(GeometryComponent::Type::Curve, AttrDomain::Curve);
supported_types_and_domains.add(GeometryComponent::Type::PointCloud, AttrDomain::Point);
supported_types_and_domains.add(GeometryComponent::Type::Instance, AttrDomain::Instance);
return supported_types_and_domains;
}();
return supported_types_and_domains;
}
static void reorder_attributes_group_to_group(const bke::AttributeAccessor src_attributes,
const bke::AttrDomain domain,
const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::MutableAttributeAccessor dst_attributes)
{
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain != domain) {
return;
}
if (iter.data_type == CD_PROP_STRING) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GVArray src = *iter.get(domain);
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, domain, iter.data_type);
if (!dst) {
return;
}
threading::parallel_for(old_by_new_map.index_range(), 1024, [&](const IndexRange range) {
for (const int new_i : range) {
const int old_i = old_by_new_map[new_i];
array_utils::copy(src.slice(src_offsets[old_i]), dst.span.slice(dst_offsets[new_i]));
}
});
dst.finish();
});
}
static Array<int> invert_permutation(const Span<int> permutation)
{
Array<int> data(permutation.size());
threading::parallel_for(permutation.index_range(), 2048, [&](const IndexRange range) {
for (const int64_t i : range) {
data[permutation[i]] = i;
}
});
return data;
}
static void copy_and_reorder_mesh_verts(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::gather_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
old_by_new_map,
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
bke::attribute_filter_with_skip_ref(attribute_filter, {".edge_verts"}),
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
dst_attributes);
implicit_sharing::free_shared_data(&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
implicit_sharing::copy_shared_pointer(src_mesh.face_offset_indices,
src_mesh.runtime->face_offsets_sharing_info,
&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
bke::attribute_filter_with_skip_ref(attribute_filter, {".corner_vert"}),
dst_attributes);
const Array<int> new_by_old_map = invert_permutation(old_by_new_map);
dst_attributes.add<int2>(".edge_verts", bke::AttrDomain::Edge, bke::AttributeInitConstruct());
array_utils::gather(new_by_old_map.as_span(),
src_mesh.edges().cast<int>(),
dst_mesh.edges_for_write().cast<int>());
dst_attributes.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
array_utils::gather(
new_by_old_map.as_span(), src_mesh.corner_verts(), dst_mesh.corner_verts_for_write());
}
static void copy_and_reorder_mesh_edges(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::copy_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
attribute_filter,
old_by_new_map,
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
dst_attributes);
implicit_sharing::free_shared_data(&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
implicit_sharing::copy_shared_pointer(src_mesh.face_offset_indices,
src_mesh.runtime->face_offsets_sharing_info,
&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
bke::attribute_filter_with_skip_ref(attribute_filter, {".corner_edge"}),
dst_attributes);
const Array<int> new_by_old_map = invert_permutation(old_by_new_map);
dst_attributes.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
array_utils::gather(
new_by_old_map.as_span(), src_mesh.corner_edges(), dst_mesh.corner_edges_for_write());
}
static void copy_and_reorder_mesh_faces(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::copy_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
attribute_filter,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
old_by_new_map,
dst_attributes);
const Span<int> old_offsets = src_mesh.face_offsets();
MutableSpan<int> new_offsets = dst_mesh.face_offsets_for_write();
offset_indices::gather_group_sizes(old_offsets, old_by_new_map, new_offsets);
offset_indices::accumulate_counts_to_offsets(new_offsets);
reorder_attributes_group_to_group(src_attributes,
bke::AttrDomain::Corner,
old_offsets,
new_offsets.as_span(),
old_by_new_map,
attribute_filter,
dst_attributes);
}
static void copy_and_reorder_mesh(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttrDomain domain,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
switch (domain) {
case bke::AttrDomain::Point:
copy_and_reorder_mesh_verts(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
case bke::AttrDomain::Edge:
copy_and_reorder_mesh_edges(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
case bke::AttrDomain::Face:
copy_and_reorder_mesh_faces(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
default:
break;
}
dst_mesh.tag_positions_changed();
dst_mesh.tag_topology_changed();
}
static void copy_and_reorder_points(const PointCloud &src_pointcloud,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
PointCloud &dst_pointcloud)
{
bke::gather_attributes(src_pointcloud.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
old_by_new_map,
dst_pointcloud.attributes_for_write());
dst_pointcloud.tag_positions_changed();
dst_pointcloud.tag_radii_changed();
}
static void copy_and_reorder_curves(const bke::CurvesGeometry &src_curves,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::CurvesGeometry &dst_curves)
{
bke::gather_attributes(src_curves.attributes(),
bke::AttrDomain::Curve,
bke::AttrDomain::Curve,
attribute_filter,
old_by_new_map,
dst_curves.attributes_for_write());
const Span<int> old_offsets = src_curves.offsets();
MutableSpan<int> new_offsets = dst_curves.offsets_for_write();
offset_indices::gather_group_sizes(old_offsets, old_by_new_map, new_offsets);
offset_indices::accumulate_counts_to_offsets(new_offsets);
reorder_attributes_group_to_group(src_curves.attributes(),
bke::AttrDomain::Point,
old_offsets,
new_offsets.as_span(),
old_by_new_map,
attribute_filter,
dst_curves.attributes_for_write());
dst_curves.tag_topology_changed();
if (src_curves.nurbs_has_custom_knots()) {
dst_curves.nurbs_custom_knots_update_size();
IndexMaskMemory memory;
bke::curves::nurbs::gather_custom_knots(
src_curves, IndexMask::from_indices(old_by_new_map, memory), 0, dst_curves);
}
}
static void copy_and_reorder_instaces(const bke::Instances &src_instances,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::Instances &dst_instances)
{
dst_instances.resize(src_instances.instances_num());
bke::gather_attributes(src_instances.attributes(),
bke::AttrDomain::Instance,
bke::AttrDomain::Instance,
attribute_filter,
old_by_new_map,
dst_instances.attributes_for_write());
for (const bke::InstanceReference &reference : src_instances.references()) {
dst_instances.add_reference(reference);
}
BLI_assert(src_instances.references() == dst_instances.references());
const Span<float4x4> old_transforms = src_instances.transforms();
MutableSpan<float4x4> new_transforms = dst_instances.transforms_for_write();
array_utils::gather(old_transforms, old_by_new_map, new_transforms);
}
Mesh *reorder_mesh(const Mesh &src_mesh,
Span<int> old_by_new_map,
bke::AttrDomain domain,
const bke::AttributeFilter &attribute_filter)
{
Mesh *dst_mesh = bke::mesh_new_no_attributes(
src_mesh.verts_num, src_mesh.edges_num, src_mesh.faces_num, src_mesh.corners_num);
BKE_mesh_copy_parameters_for_eval(dst_mesh, &src_mesh);
copy_and_reorder_mesh(src_mesh, old_by_new_map, domain, attribute_filter, *dst_mesh);
return dst_mesh;
}
PointCloud *reorder_points(const PointCloud &src_pointcloud,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
PointCloud *dst_pointcloud = bke::pointcloud_new_no_attributes(src_pointcloud.totpoint);
copy_and_reorder_points(src_pointcloud, old_by_new_map, attribute_filter, *dst_pointcloud);
return dst_pointcloud;
}
bke::CurvesGeometry reorder_curves_geometry(const bke::CurvesGeometry &src_curves,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
bke::CurvesGeometry dst_curves = bke::curves_new_no_attributes(src_curves.points_num(),
src_curves.curves_num());
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
copy_and_reorder_curves(src_curves, old_by_new_map, attribute_filter, dst_curves);
return dst_curves;
}
Curves *reorder_curves(const Curves &src_curves,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
const bke::CurvesGeometry src_curve_geometry = src_curves.geometry.wrap();
Curves *dst_curves = bke::curves_new_nomain(0, 0);
dst_curves->geometry.wrap() = reorder_curves_geometry(
src_curve_geometry, old_by_new_map, attribute_filter);
return dst_curves;
}
bke::Instances *reorder_instaces(const bke::Instances &src_instances,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
bke::Instances *dst_instances = new bke::Instances(src_instances);
copy_and_reorder_instaces(src_instances, old_by_new_map, attribute_filter, *dst_instances);
return dst_instances;
}
bke::GeometryComponentPtr reordered_component(const bke::GeometryComponent &src_component,
const Span<int> old_by_new_map,
const bke::AttrDomain domain,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(!src_component.is_empty());
if (const bke::MeshComponent *src_mesh_component = dynamic_cast<const bke::MeshComponent *>(
&src_component))
{
Mesh *result_mesh = reorder_mesh(
*src_mesh_component->get(), old_by_new_map, domain, attribute_filter);
return bke::GeometryComponentPtr(new bke::MeshComponent(result_mesh));
}
if (const bke::PointCloudComponent *src_points_component =
dynamic_cast<const bke::PointCloudComponent *>(&src_component))
{
PointCloud *result_pointcloud = reorder_points(
*src_points_component->get(), old_by_new_map, attribute_filter);
return bke::GeometryComponentPtr(new bke::PointCloudComponent(result_pointcloud));
}
if (const bke::CurveComponent *src_curves_component = dynamic_cast<const bke::CurveComponent *>(
&src_component))
{
Curves *result_curves = reorder_curves(
*src_curves_component->get(), old_by_new_map, attribute_filter);
return bke::GeometryComponentPtr(new bke::CurveComponent(result_curves));
}
if (const bke::InstancesComponent *src_instances_component =
dynamic_cast<const bke::InstancesComponent *>(&src_component))
{
bke::Instances *result_instances = reorder_instaces(
*src_instances_component->get(), old_by_new_map, attribute_filter);
return bke::GeometryComponentPtr(new bke::InstancesComponent(result_instances));
}
BLI_assert_unreachable();
return {};
}
} // namespace blender::geometry