From 113d91aba8b3e4ddf4d8a1f791d3be1823fec80a Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 29 Aug 2025 16:38:51 -0400 Subject: [PATCH] Mesh: Restructure join operator for simplicity, speed, consistency Previously the joining code put the final data into temporary mesh data, which meant more complexity because it couldn't use the slightly higher level APIs more commonly used for transferring data. The process can be simplified by just merging into the active mesh directly (though some care must be taken when that is used by multiple selected objects). This process avoids iterating over attribute data twice, by processing values at the same time as copying them. Also some of the inner hot loops are parallelized. There is more opportunity for trivial multi- threading in the future. Mismatched attribute types and domains will now choose the higher complexity type and domain, which should help avoid information loss. There was a bug with the existing face set processing which would modify the source meshes. That is now fixed. --- source/blender/blenkernel/BKE_customdata.hh | 2 - .../blender/blenkernel/intern/customdata.cc | 19 - source/blender/editors/mesh/mesh_join.cc | 782 +++++++++--------- 3 files changed, 391 insertions(+), 412 deletions(-) diff --git a/source/blender/blenkernel/BKE_customdata.hh b/source/blender/blenkernel/BKE_customdata.hh index de51e6be5c9..17e19b3c71c 100644 --- a/source/blender/blenkernel/BKE_customdata.hh +++ b/source/blender/blenkernel/BKE_customdata.hh @@ -342,8 +342,6 @@ void CustomData_copy_data_layer(const CustomData *source, int src_index, int dst_index, int count); -void CustomData_copy_data_named( - const CustomData *source, CustomData *dest, int source_index, int dest_index, int count); void CustomData_copy_elements(eCustomDataType type, void *src_data_ofs, void *dst_data_ofs, diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 8d268de5ab6..ece381d5531 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -3313,25 +3313,6 @@ void CustomData_copy_data_layer(const CustomData *source, } } -void CustomData_copy_data_named(const CustomData *source, - CustomData *dest, - const int source_index, - const int dest_index, - const int count) -{ - /* copies a layer at a time */ - for (int src_i = 0; src_i < source->totlayer; src_i++) { - - int dest_i = CustomData_get_named_layer_index( - dest, eCustomDataType(source->layers[src_i].type), source->layers[src_i].name); - - /* if we found a matching layer, copy the data */ - if (dest_i != -1) { - CustomData_copy_data_layer(source, dest, src_i, dest_i, source_index, dest_index, count); - } - } -} - void CustomData_copy_data(const CustomData *source, CustomData *dest, const int source_index, diff --git a/source/blender/editors/mesh/mesh_join.cc b/source/blender/editors/mesh/mesh_join.cc index 71aa474a065..dcc998dba4a 100644 --- a/source/blender/editors/mesh/mesh_join.cc +++ b/source/blender/editors/mesh/mesh_join.cc @@ -13,7 +13,6 @@ #include "BLI_listbase.h" #include "BLI_math_matrix.h" #include "BLI_math_matrix.hh" -#include "BLI_math_vector.h" #include "BLI_vector.hh" #include "BLI_virtual_array.hh" @@ -43,6 +42,7 @@ #include "DEG_depsgraph_build.hh" #include "DEG_depsgraph_query.hh" +#include "ED_geometry.hh" #include "ED_mesh.hh" #include "ED_object.hh" #include "ED_view3d.hh" @@ -54,240 +54,330 @@ namespace blender::ed::mesh { -/* join selected meshes into the active mesh, context sensitive - * return 0 if no join is made (error) and 1 if the join is done */ - -static void join_mesh_single(Depsgraph *depsgraph, - Main *bmain, - Scene *scene, - Object *ob_dst, - Object *ob_src, - const float4x4 &world_to_active_object, - MutableSpan dst_positions, - MutableSpan dst_edges, - MutableSpan dst_corner_verts, - MutableSpan dst_corner_edges, - MutableSpan dst_face_offsets, - CustomData *vert_data, - CustomData *edge_data, - CustomData *face_data, - CustomData *corner_data, - int verts_num, - int edges_num, - int faces_num, - int corners_num, - Key *key, - Key *nkey, - Vector &materials, - const IndexRange vert_range, - const IndexRange edge_range, - const IndexRange face_range, - const IndexRange corner_range) +static VectorSet join_vertex_groups(const Span objects_to_join, + const OffsetIndices vert_ranges, + Mesh &dst_mesh) { - int a; + VectorSet vertex_group_names; + LISTBASE_FOREACH (const bDeformGroup *, dg, &dst_mesh.vertex_group_names) { + vertex_group_names.add_new(dg->name); + } - Mesh *mesh_src = static_cast(ob_src->data); - - if (mesh_src->verts_num) { - /* standard data */ - CustomData_merge_layout( - &mesh_src->vert_data, vert_data, CD_MASK_MESH.vmask, CD_SET_DEFAULT, verts_num); - CustomData_copy_data_named( - &mesh_src->vert_data, vert_data, 0, vert_range.start(), mesh_src->verts_num); - - /* vertex groups */ - MDeformVert *dvert = (MDeformVert *)CustomData_get_layer_for_write( - vert_data, CD_MDEFORMVERT, verts_num); - const MDeformVert *dvert_src = (const MDeformVert *)CustomData_get_layer(&mesh_src->vert_data, - CD_MDEFORMVERT); - - /* Remap to correct new vgroup indices, if needed. */ - if (dvert_src) { - BLI_assert(dvert != nullptr); - - /* Build src to merged mapping of vgroup indices. */ - int *vgroup_index_map; - int vgroup_index_map_len; - vgroup_index_map = BKE_object_defgroup_index_map_create( - ob_src, ob_dst, &vgroup_index_map_len); - BKE_object_defgroup_index_map_apply( - &dvert[vert_range.start()], mesh_src->verts_num, vgroup_index_map, vgroup_index_map_len); - if (vgroup_index_map != nullptr) { - MEM_freeN(vgroup_index_map); + bool any_vertex_group_data = false; + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Mesh &mesh = *static_cast(objects_to_join[i]->data); + any_vertex_group_data |= CustomData_has_layer(&mesh.vert_data, CD_MDEFORMVERT); + LISTBASE_FOREACH (const bDeformGroup *, dg, &mesh.vertex_group_names) { + if (vertex_group_names.add_as(dg->name)) { + BLI_addtail(&dst_mesh.vertex_group_names, BKE_defgroup_duplicate(dg)); } } + } - /* if this is the object we're merging into, no need to do anything */ - if (ob_src != ob_dst) { - float cmat[4][4]; + if (!any_vertex_group_data) { + return vertex_group_names; + } - /* Watch this: switch matrix multiplication order really goes wrong. */ - mul_m4_m4m4(cmat, world_to_active_object.ptr(), ob_src->object_to_world().ptr()); + MDeformVert *dvert = (MDeformVert *)CustomData_add_layer( + &dst_mesh.vert_data, CD_MDEFORMVERT, CD_CONSTRUCT, dst_mesh.verts_num); - math::transform_points(float4x4(cmat), dst_positions.slice(vert_range)); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Mesh &src_mesh = *static_cast(objects_to_join[i]->data); + const Span src_dverts = src_mesh.deform_verts().take_front(vert_ranges[i].size()); + if (src_dverts.is_empty()) { + continue; + } + Vector index_map; + LISTBASE_FOREACH (const bDeformGroup *, dg, &dst_mesh.vertex_group_names) { + index_map.append(vertex_group_names.index_of_as(dg->name)); + } + for (const int vert : src_dverts.index_range()) { + const MDeformVert &src = src_dverts[vert]; + MDeformVert &dst = dvert[vert_ranges[i][vert]]; + dst = src; + dst.dw = MEM_malloc_arrayN(src.totweight, __func__); + for (const int weight : IndexRange(src.totweight)) { + dst.dw[weight].def_nr = index_map[src.dw[weight].def_nr]; + dst.dw[weight].weight = src.dw[weight].weight; + } + } + } - /* For each shape-key in destination mesh: - * - if there's a matching one, copy it across - * (will need to transform vertices into new space...). - * - otherwise, just copy its own coordinates of mesh - * (no need to transform vertex coordinates into new space). - */ - if (key) { - /* if this mesh has any shape-keys, check first, otherwise just copy coordinates */ - LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { - MutableSpan key_data(static_cast(kb->data), kb->totelem); - if (const KeyBlock *src_kb = mesh_src->key ? - BKE_keyblock_find_name(mesh_src->key, kb->name) : - nullptr) - { - const Span src_kb_data(static_cast(src_kb->data), src_kb->totelem); - math::transform_points(src_kb_data, float4x4(cmat), key_data); - } - else { - key_data.slice(vert_range).copy_from(dst_positions.slice(vert_range)); - } + return vertex_group_names; +} + +static void join_positions_and_shape_keys(const Span objects_to_join, + const OffsetIndices vert_ranges, + const float4x4 &world_to_dst_mesh, + Mesh &dst_mesh) +{ + Vector key_blocks; + VectorSet key_names; + if (Key *key = dst_mesh.key) { + LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { + key_names.add_new(kb->name); + key_blocks.append(kb); + } + } + + const auto ensure_dst_key = [&]() { + if (!dst_mesh.key) { + dst_mesh.key = BKE_key_add(nullptr, nullptr); + dst_mesh.key->type = KEY_RELATIVE; + } + }; + + MutableSpan dst_positions = dst_mesh.vert_positions_for_write(); + + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Key *src_key = static_cast(objects_to_join[i]->data)->key; + if (!src_key) { + continue; + } + ensure_dst_key(); + LISTBASE_FOREACH (const KeyBlock *, src_kb, &src_key->block) { + if (key_names.add_as(src_kb->name)) { + KeyBlock *dst_kb = BKE_keyblock_add(dst_mesh.key, src_kb->name); + BKE_keyblock_copy_settings(dst_kb, src_kb); + dst_kb->data = MEM_malloc_arrayN(dst_mesh.verts_num, __func__); + dst_kb->totelem = dst_mesh.verts_num; + + /* Initialize the new shape key data with the base positions for the active object. */ + MutableSpan key_data(static_cast(dst_kb->data), dst_kb->totelem); + key_data.take_front(vert_ranges[0].size()) + .copy_from(dst_positions.take_front(vert_ranges[0].size())); + + /* Remap `KeyBlock::relative`. */ + if (const KeyBlock *src_kb_relative = static_cast( + BLI_findlink(&src_key->block, src_kb->relative))) + { + dst_kb->relative = key_names.index_of_as(src_kb_relative->name); } } } + } + + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = vert_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_positions = src_mesh.vert_positions().take_front(dst_range.size()); + const float4x4 transform = world_to_dst_mesh * src_object.object_to_world(); + math::transform_points(src_positions, transform, dst_positions.slice(dst_range)); + + if (Key *dst_key = dst_mesh.key) { + LISTBASE_FOREACH (KeyBlock *, kb, &dst_key->block) { + MutableSpan key_data(static_cast(kb->data), kb->totelem); + if (const KeyBlock *src_kb = src_mesh.key ? + BKE_keyblock_find_name(src_mesh.key, kb->name) : + nullptr) + { + const Span src_kb_data(static_cast(src_kb->data), dst_range.size()); + math::transform_points(src_kb_data, transform, key_data.slice(dst_range)); + } + else { + key_data.slice(dst_range).copy_from(dst_positions.slice(dst_range)); + } + } + } + } +} + +static void join_generic_attributes(const Span objects_to_join, + const VectorSet &all_vertex_group_names, + const OffsetIndices vert_ranges, + const OffsetIndices edge_ranges, + const OffsetIndices face_ranges, + const OffsetIndices corner_ranges, + Mesh &dst_mesh) +{ + Set skip_names{"position", + ".edge_verts", + ".corner_vert", + ".corner_edge", + "material_index", + ".sculpt_face_set"}; + + bke::GeometrySet::GatheredAttributes attr_info; + for (const int i : objects_to_join.index_range()) { + const Mesh &mesh = *static_cast(objects_to_join[i]->data); + mesh.attributes().foreach_attribute([&](const bke::AttributeIter &attr) { + if (skip_names.contains(attr.name) || all_vertex_group_names.contains(attr.name)) { + return; + } + attr_info.add(attr.name, {attr.domain, attr.data_type}); + }); + } + + bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write(); + + const Set attribute_names = dst_attributes.all_ids(); + for (const int attr_i : attr_info.names.index_range()) { + const StringRef name = attr_info.names[attr_i]; + const bke::AttrDomain domain = attr_info.kinds[attr_i].domain; + const bke::AttrType data_type = attr_info.kinds[attr_i].data_type; + if (const std::optional meta_data = dst_attributes.lookup_meta_data( + name)) + { + if (meta_data->domain != domain || meta_data->data_type != data_type) { + AttributeOwner owner = AttributeOwner::from_id(&dst_mesh.id); + geometry::convert_attribute( + owner, dst_attributes, name, meta_data->domain, meta_data->data_type, nullptr); + } + } else { - /* for each shape-key in destination mesh: - * - if it was an 'original', copy the appropriate data from nkey - * - otherwise, copy across plain coordinates (no need to transform coordinates) - */ - if (key) { - LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { - MutableSpan key_data(static_cast(kb->data), kb->totelem); - if (const KeyBlock *src_kb = nkey ? BKE_keyblock_find_name(nkey, kb->name) : nullptr) { - const Span src_kb_data(static_cast(src_kb->data), src_kb->totelem); - key_data.slice(vert_range).copy_from(src_kb_data); - } - else { - key_data.slice(vert_range).copy_from(dst_positions.slice(vert_range)); - } + dst_attributes.add(name, domain, data_type, bke::AttributeInitConstruct()); + } + } + + for (const int attr_i : attr_info.names.index_range()) { + const StringRef name = attr_info.names[attr_i]; + const bke::AttrDomain domain = attr_info.kinds[attr_i].domain; + const bke::AttrType data_type = attr_info.kinds[attr_i].data_type; + + bke::GSpanAttributeWriter dst = dst_attributes.lookup_for_write_span(name); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Mesh &src_mesh = *static_cast(objects_to_join[i]->data); + const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + const GVArray src = *src_attributes.lookup_or_default(name, domain, data_type); + + const IndexRange dst_range = [&]() { + switch (domain) { + case bke::AttrDomain::Point: + return vert_ranges[i]; + case bke::AttrDomain::Edge: + return edge_ranges[i]; + case bke::AttrDomain::Face: + return face_ranges[i]; + case bke::AttrDomain::Corner: + return corner_ranges[i]; + default: + BLI_assert_unreachable(); + return IndexRange(); } + }(); + + src.materialize(IndexRange(dst_range.size()), dst.span.slice(dst_range).data()); + } + dst.finish(); + } +} + +static VectorSet join_materials(const Span objects_to_join, + const OffsetIndices face_ranges, + Mesh &dst_mesh) +{ + VectorSet materials; + for (const int i : objects_to_join.index_range()) { + const Object &src_object = *objects_to_join[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + if (src_mesh.totcol == 0) { + materials.add(nullptr); + continue; + } + for (const int material_index : IndexRange(src_mesh.totcol)) { + Material *material = BKE_object_material_get(&const_cast(src_object), + material_index + 1); + if (materials.size() < MAXMAT) { + materials.add(material); } } } - if (mesh_src->edges_num) { - CustomData_merge_layout( - &mesh_src->edge_data, edge_data, CD_MASK_MESH.emask, CD_SET_DEFAULT, edges_num); - CustomData_copy_data_named( - &mesh_src->edge_data, edge_data, 0, edge_range.start(), mesh_src->edges_num); + bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write(); + if (materials.size() <= 1) { + dst_attributes.remove("material_index"); + return materials; + } - for (int2 &edge : dst_edges.slice(edge_range)) { - edge += vert_range.start(); + bke::SpanAttributeWriter dst_material_indices = dst_attributes.lookup_or_add_for_write_span( + "material_index", bke::AttrDomain::Face); + if (!dst_material_indices) { + return {}; + } + + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = face_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + + const VArray material_indices = *src_attributes.lookup("material_index", + bke::AttrDomain::Face); + if (material_indices.is_empty()) { + Material *first_material = src_mesh.totcol == 0 ? + nullptr : + BKE_object_material_get(&const_cast(src_object), 1); + dst_material_indices.span.slice(dst_range).fill(materials.index_of(first_material)); + continue; + } + + if (src_mesh.totcol == 0) { + /* These material indices are invalid, but copy them anyway to avoid destroying user data. */ + material_indices.materialize(dst_range.index_range(), + dst_material_indices.span.slice(dst_range)); + continue; + } + + Array index_map(src_mesh.totcol); + for (const int material_index : IndexRange(src_mesh.totcol)) { + Material *material = BKE_object_material_get(&const_cast(src_object), + material_index + 1); + const int dst_index = materials.index_of_try(material); + index_map[material_index] = dst_index == -1 ? 0 : dst_index; + } + + const int max = src_mesh.totcol - 1; + for (const int face : dst_range.index_range()) { + const int src = std::clamp(material_indices[face], 0, max); + dst_material_indices.span[dst_range[face]] = index_map[src]; } } - if (mesh_src->corners_num) { - if (ob_src != ob_dst) { - MultiresModifierData *mmd; + dst_material_indices.finish(); - multiresModifier_prepare_join(depsgraph, scene, ob_src, ob_dst); - - if ((mmd = get_multires_modifier(scene, ob_src, true))) { - object::iter_other(bmain, ob_src, true, object::multires_update_totlevels, &mmd->totlvl); - } - } - - CustomData_merge_layout( - &mesh_src->corner_data, corner_data, CD_MASK_MESH.lmask, CD_SET_DEFAULT, corners_num); - CustomData_copy_data_named( - &mesh_src->corner_data, corner_data, 0, corner_range.start(), mesh_src->corners_num); - - for (int &vert : dst_corner_verts.slice(corner_range)) { - vert += vert_range.start(); - } - for (int &edge : dst_corner_edges.slice(corner_range)) { - edge += edge_range.start(); - } - } - - /* Make remapping for material indices. Assume at least one slot, - * that will be null if there are no actual slots. */ - const int totcol = std::max(ob_src->totcol, 1); - Vector matmap(totcol); - if (mesh_src->faces_num) { - for (a = 1; a <= totcol; a++) { - Material *ma = (a <= ob_src->totcol) ? BKE_object_material_get(ob_src, a) : nullptr; - - /* Try to reuse existing slot. */ - int b = 0; - for (; b < materials.size(); b++) { - if (ma == materials[b]) { - matmap[a - 1] = b; - break; - } - } - - if (b == materials.size()) { - if (materials.size() == MAXMAT) { - /* Reached max limit of materials, use first slot. */ - matmap[a - 1] = 0; - } - else { - /* Add new slot. */ - matmap[a - 1] = materials.size(); - materials.append(ma); - if (ma) { - id_us_plus(&ma->id); - } - } - } - } - - CustomData_merge_layout( - &mesh_src->face_data, face_data, CD_MASK_MESH.pmask, CD_SET_DEFAULT, faces_num); - CustomData_copy_data_named( - &mesh_src->face_data, face_data, 0, face_range.start(), mesh_src->faces_num); - - /* Apply matmap. In case we don't have material indices yet, create them if more than one - * material is the result of joining. */ - int *material_indices = static_cast(CustomData_get_layer_named_for_write( - face_data, CD_PROP_INT32, "material_index", faces_num)); - if (!material_indices && materials.size() > 1) { - material_indices = (int *)CustomData_add_layer_named( - face_data, CD_PROP_INT32, CD_SET_DEFAULT, faces_num, "material_index"); - } - if (material_indices) { - for (a = 0; a < mesh_src->faces_num; a++) { - /* Clamp invalid slots, matching #BKE_object_material_get_p. */ - const int mat_index = std::clamp(material_indices[a + face_range.start()], 0, totcol - 1); - material_indices[a + face_range.start()] = matmap[mat_index]; - } - } - - const Span src_face_offsets = mesh_src->face_offsets(); - for (const int i : face_range.index_range()) { - dst_face_offsets[face_range[i]] = src_face_offsets[i] + corner_range.start(); - } - } + return materials; } /* Face Sets IDs are a sparse sequence, so this function offsets all the IDs by face_set_offset and * updates face_set_offset with the maximum ID value. This way, when used in multiple meshes, all * of them will have different IDs for their Face Sets. */ -static void mesh_join_offset_face_sets_ID(Mesh *mesh, int *face_set_offset) +static void join_face_sets(const Span objects_to_join, + const OffsetIndices face_ranges, + Mesh &dst_mesh) { - bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); - bke::SpanAttributeWriter face_sets = attributes.lookup_for_write_span( + bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write(); + bke::SpanAttributeWriter dst_face_sets = dst_attributes.lookup_for_write_span( ".sculpt_face_set"); - if (!face_sets) { + if (!dst_face_sets) { + return; + } + if (dst_face_sets.domain != bke::AttrDomain::Face) { return; } - int max_face_set = 0; - for (const int i : face_sets.span.index_range()) { - /* As face sets encode the visibility in the integer sign, the offset needs to be added or - * subtracted depending on the initial sign of the integer to get the new ID. */ - if (face_sets.span[i] <= *face_set_offset) { - face_sets.span[i] += *face_set_offset; + int max_face_set = 1; + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = face_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + const VArraySpan src_face_sets = *src_attributes.lookup(".sculpt_face_set", + bke::AttrDomain::Face); + if (src_face_sets.is_empty()) { + dst_face_sets.span.slice(dst_range).fill(max_face_set); } - max_face_set = max_ii(max_face_set, face_sets.span[i]); + else { + for (const int face : dst_range.index_range()) { + dst_face_sets.span[dst_range[face]] = src_face_sets[face] + max_face_set; + } + max_face_set = std::max( + max_face_set, + *std::max_element(src_face_sets.begin(), src_face_sets.begin() + dst_range.size())); + } + max_face_set++; } - *face_set_offset = max_face_set; - face_sets.finish(); + dst_face_sets.finish(); } wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) @@ -328,8 +418,6 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) objects_to_join.prepend(active_object); } - int haskey = 0; - Array vert_offset_data(objects_to_join.size() + 1); Array edge_offset_data(objects_to_join.size() + 1); Array face_offset_data(objects_to_join.size() + 1); @@ -340,9 +428,6 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) edge_offset_data[i] = mesh.edges_num; face_offset_data[i] = mesh.faces_num; corner_offset_data[i] = mesh.corners_num; - if (mesh.key) { - haskey++; - } } const OffsetIndices vert_ranges = offset_indices::accumulate_counts_to_offsets( @@ -384,201 +469,117 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Active object materials in new main array, is nicer start! */ - Vector materials; - for (const int a : IndexRange(active_object->totcol)) { - materials.append(BKE_object_material_get(active_object, a + 1)); - id_us_plus((ID *)materials[a]); - /* increase id->us : will be lowered later */ + CustomData_realloc(&dst_mesh->vert_data, dst_mesh->verts_num, vert_ranges.total_size()); + CustomData_realloc(&dst_mesh->edge_data, dst_mesh->edges_num, edge_ranges.total_size()); + CustomData_realloc(&dst_mesh->face_data, dst_mesh->faces_num, face_ranges.total_size()); + CustomData_realloc(&dst_mesh->corner_data, dst_mesh->corners_num, corner_ranges.total_size()); + if (face_ranges.total_size() != dst_mesh->faces_num) { + implicit_sharing::resize_trivial_array(&dst_mesh->face_offset_indices, + &dst_mesh->runtime->face_offsets_sharing_info, + dst_mesh->faces_num, + face_ranges.total_size() + 1); } - - /* - If destination mesh had shape-keys, move them somewhere safe, and set up placeholders - * with arrays that are large enough to hold shape-key data for all meshes. - * - If destination mesh didn't have shape-keys, but we encountered some in the meshes we're - * joining, set up a new key-block and assign to the mesh. - */ - Key *nkey = nullptr; - if (key) { - /* make a duplicate copy that will only be used here... (must remember to free it!) */ - nkey = (Key *)BKE_id_copy(bmain, &key->id); - - /* for all keys in old block, clear data-arrays */ + dst_mesh->verts_num = vert_ranges.total_size(); + dst_mesh->edges_num = edge_ranges.total_size(); + dst_mesh->faces_num = face_ranges.total_size(); + dst_mesh->corners_num = corner_ranges.total_size(); + if (Key *key = dst_mesh->key) { LISTBASE_FOREACH (KeyBlock *, kb, &key->block) { - if (kb->data) { - MEM_freeN(kb->data); - } - kb->data = MEM_callocN(sizeof(float[3]) * vert_ranges.total_size(), "join_shapekey"); - kb->totelem = vert_ranges.total_size(); - } - } - else if (haskey) { - /* add a new key-block and add to the mesh */ - key = dst_mesh->key = BKE_key_add(bmain, (ID *)dst_mesh); - key->type = KEY_RELATIVE; - } - - /* Update face_set_id_offset with the face set data in the active object first. This way the Face - * Sets IDs in the active object are not the ones that are modified. */ - int face_set_id_offset = 0; - mesh_join_offset_face_sets_ID(dst_mesh, &face_set_id_offset); - - /* Copy materials, vertex-groups, face sets & face-maps across objects. */ - for (const Object *ob_iter : objects_to_join) { - if (ob_iter == active_object) { - continue; - } - Mesh *mesh = static_cast(ob_iter->data); - - /* Join this object's vertex groups to the base one's */ - LISTBASE_FOREACH (bDeformGroup *, dg, &mesh->vertex_group_names) { - /* See if this group exists in the object (if it doesn't, add it to the end) */ - if (!BKE_object_defgroup_find_name(active_object, dg->name)) { - bDeformGroup *odg = MEM_mallocN(__func__); - memcpy(odg, dg, sizeof(bDeformGroup)); - BLI_addtail(&dst_mesh->vertex_group_names, odg); - } - } - if (!BLI_listbase_is_empty(&dst_mesh->vertex_group_names) && - mesh->vertex_group_active_index == 0) - { - mesh->vertex_group_active_index = 1; - } - - mesh_join_offset_face_sets_ID(mesh, &face_set_id_offset); - - if (mesh->verts_num) { - /* If this mesh has shape-keys, - * check if destination mesh already has matching entries too. */ - if (mesh->key && key) { - /* for remapping KeyBlock.relative */ - int *index_map = MEM_malloc_arrayN(mesh->key->totkey, __func__); - KeyBlock **kb_map = MEM_malloc_arrayN(mesh->key->totkey, __func__); - - int i; - LISTBASE_FOREACH_INDEX (KeyBlock *, kb, &mesh->key->block, i) { - BLI_assert(i < mesh->key->totkey); - - KeyBlock *kbn = BKE_keyblock_find_name(key, kb->name); - /* if key doesn't exist in destination mesh, add it */ - if (kbn) { - index_map[i] = BLI_findindex(&key->block, kbn); - } - else { - index_map[i] = key->totkey; - - kbn = BKE_keyblock_add(key, kb->name); - - BKE_keyblock_copy_settings(kbn, kb); - - /* adjust settings to fit (allocate a new data-array) */ - kbn->data = MEM_callocN(sizeof(float[3]) * vert_ranges.total_size(), - "joined_shapekey"); - kbn->totelem = vert_ranges.total_size(); - } - - kb_map[i] = kbn; - } - - /* remap relative index values */ - LISTBASE_FOREACH_INDEX (KeyBlock *, kb, &mesh->key->block, i) { - /* sanity check, should always be true */ - if (LIKELY(kb->relative < mesh->key->totkey)) { - kb_map[i]->relative = index_map[kb->relative]; - } - } - - MEM_freeN(index_map); - MEM_freeN(kb_map); - } + kb->data = MEM_reallocN(kb->data, sizeof(float3) * dst_mesh->verts_num); + kb->totelem = dst_mesh->verts_num; } } - /* setup new data for destination mesh */ - CustomData vert_data; - CustomData edge_data; - CustomData face_data; - CustomData corner_data; - CustomData_reset(&vert_data); - CustomData_reset(&edge_data); - CustomData_reset(&corner_data); - CustomData_reset(&face_data); - - MutableSpan vert_positions( - (float3 *)CustomData_add_layer_named( - &vert_data, CD_PROP_FLOAT3, CD_SET_DEFAULT, vert_ranges.total_size(), "position"), - vert_ranges.total_size()); - MutableSpan edge( - (int2 *)CustomData_add_layer_named( - &edge_data, CD_PROP_INT32_2D, CD_CONSTRUCT, edge_ranges.total_size(), ".edge_verts"), - edge_ranges.total_size()); - MutableSpan corner_verts( - (int *)CustomData_add_layer_named( - &corner_data, CD_PROP_INT32, CD_CONSTRUCT, corner_ranges.total_size(), ".corner_vert"), - corner_ranges.total_size()); - MutableSpan corner_edges( - (int *)CustomData_add_layer_named( - &corner_data, CD_PROP_INT32, CD_CONSTRUCT, corner_ranges.total_size(), ".corner_edge"), - corner_ranges.total_size()); - int *face_offsets = MEM_malloc_arrayN(face_ranges.total_size() + 1, __func__); - face_offsets[face_ranges.total_size()] = corner_ranges.total_size(); + BKE_mesh_runtime_clear_geometry(dst_mesh); /* Inverse transform for all selected meshes in this object, * See #object_join_exec for detailed comment on why the safe version is used. */ float4x4 world_to_active_object; invert_m4_m4_safe_ortho(world_to_active_object.ptr(), active_object->object_to_world().ptr()); - for (const int i : objects_to_join.index_range()) { - Object *ob_iter = objects_to_join[i]; - join_mesh_single(depsgraph, - bmain, - scene, - active_object, - ob_iter, - world_to_active_object, - vert_positions, - edge, - corner_verts, - corner_edges, - {face_offsets, face_ranges.total_size()}, - &vert_data, - &edge_data, - &face_data, - &corner_data, - vert_ranges.total_size(), - edge_ranges.total_size(), - face_ranges.total_size(), - corner_ranges.total_size(), - key, - nkey, - materials, - vert_ranges[i], - edge_ranges[i], - face_ranges[i], - corner_ranges[i]); + join_positions_and_shape_keys(objects_to_join, vert_ranges, world_to_active_object, *dst_mesh); - /* free base, now that data is merged */ - if (ob_iter != active_object) { - object::base_free_and_unlink(bmain, scene, ob_iter); + MutableSpan dst_edges = dst_mesh->edges_for_write(); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = edge_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_edges = src_mesh.edges(); + for (const int edge : dst_range.index_range()) { + dst_edges[dst_range[edge]] = src_edges[edge] + int(vert_ranges[i].start()); } } - BKE_mesh_clear_geometry(dst_mesh); - - if (face_offsets) { - dst_mesh->face_offset_indices = face_offsets; - dst_mesh->runtime->face_offsets_sharing_info = implicit_sharing::info_for_mem_free( - face_offsets); + MutableSpan dst_corner_verts = dst_mesh->corner_verts_for_write(); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = corner_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_corner_verts = src_mesh.corner_verts(); + for (const int corner : dst_range.index_range()) { + dst_corner_verts[dst_range[corner]] = src_corner_verts[corner] + int(vert_ranges[i].start()); + } } - dst_mesh->verts_num = vert_ranges.total_size(); - dst_mesh->edges_num = edge_ranges.total_size(); - dst_mesh->faces_num = face_ranges.total_size(); - dst_mesh->corners_num = corner_ranges.total_size(); + MutableSpan dst_corner_edges = dst_mesh->corner_edges_for_write(); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = corner_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_corner_edges = src_mesh.corner_edges(); + for (const int corner : dst_range.index_range()) { + dst_corner_edges[dst_range[corner]] = src_corner_edges[corner] + int(edge_ranges[i].start()); + } + } - dst_mesh->vert_data = vert_data; - dst_mesh->edge_data = edge_data; - dst_mesh->corner_data = corner_data; - dst_mesh->face_data = face_data; + MutableSpan dst_face_offsets = dst_mesh->face_offsets_for_write(); + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const IndexRange dst_range = face_ranges[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Span src_face_offsets = src_mesh.face_offsets(); + for (const int face : dst_range.index_range()) { + dst_face_offsets[dst_range[face]] = src_face_offsets[face] + corner_ranges[i].start(); + } + } + dst_face_offsets.last() = dst_mesh->corners_num; + + for (const int i : objects_to_join.index_range().drop_front(1)) { + const Object &src_object = *objects_to_join[i]; + const Mesh &src_mesh = *static_cast(src_object.data); + const Key *src_key = src_mesh.key; + if (!src_key) { + continue; + } + } + + for (const int i : objects_to_join.index_range().drop_front(1)) { + Object &src_object = *objects_to_join[i]; + multiresModifier_prepare_join(depsgraph, scene, &src_object, active_object); + if (MultiresModifierData *mmd = get_multires_modifier(scene, &src_object, true)) { + object::iter_other( + bmain, &src_object, true, object::multires_update_totlevels, &mmd->totlvl); + } + } + + join_face_sets(objects_to_join, face_ranges, *dst_mesh); + + VectorSet materials = join_materials(objects_to_join, face_ranges, *dst_mesh); + + VectorSet vertex_group_names = join_vertex_groups( + objects_to_join, vert_ranges, *dst_mesh); + + join_generic_attributes(objects_to_join, + vertex_group_names, + vert_ranges, + edge_ranges, + face_ranges, + corner_ranges, + *dst_mesh); + + for (Object *object : objects_to_join.as_span().drop_front(1)) { + object::base_free_and_unlink(bmain, scene, object); + } /* old material array */ for (const int a : IndexRange(active_object->totcol)) { @@ -602,8 +603,13 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) const int totcol = materials.size(); if (totcol) { - dst_mesh->mat = MEM_calloc_arrayN(totcol, __func__); - std::copy_n(materials.data(), totcol, dst_mesh->mat); + VectorData data = materials.extract_vector().release(); + dst_mesh->mat = data.data; + for (const int i : IndexRange(totcol)) { + if (Material *ma = dst_mesh->mat[i]) { + id_us_plus((ID *)ma); + } + } active_object->mat = MEM_calloc_arrayN(totcol, __func__); active_object->matbits = MEM_calloc_arrayN(totcol, __func__); } @@ -613,12 +619,6 @@ wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op) /* other mesh users */ BKE_objects_materials_sync_length_all(bmain, (ID *)dst_mesh); - /* Free temporary copy of destination shape-keys (if applicable). */ - if (nkey) { - /* We can assume nobody is using that ID currently. */ - BKE_id_free_ex(bmain, nkey, LIB_ID_FREE_NO_UI_USER, false); - } - /* ensure newly inserted keys are time sorted */ if (key && (key->type != KEY_RELATIVE)) { BKE_key_sort(key);