/* SPDX-FileCopyrightText: 2004 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup edmesh
 *
 * `meshtools.cc`: no editmode (violated already :), mirror & join),
 * tools operating on meshes
 */

#include <algorithm>

#include "MEM_guardedalloc.h"

#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_vector.hh"
#include "BLI_virtual_array.hh"

#include "DNA_key_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"

#include "BKE_attribute.hh"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "BKE_editmesh.hh"
#include "BKE_key.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_iterators.hh"
#include "BKE_mesh_runtime.hh"
#include "BKE_multires.hh"
#include "BKE_object.hh"
#include "BKE_object_deform.h"
#include "BKE_report.hh"

#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"

#include "DRW_select_buffer.hh"

#include "ED_mesh.hh"
#include "ED_object.hh"
#include "ED_view3d.hh"

#include "WM_api.hh"
#include "WM_types.hh"

using blender::float3;
using blender::int2;
using blender::MutableSpan;
using blender::Span;

/* * ********************** no editmode!!! *********** */

/*********************** JOIN ***************************/

/* 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 float imat[4][4],
                             float3 **vert_positions_pp,
                             blender::int2 **medge_pp,
                             int **corner_verts_pp,
                             int **corner_edges_pp,
                             int *all_face_offsets,
                             CustomData *vert_data,
                             CustomData *edge_data,
                             CustomData *ldata,
                             CustomData *face_data,
                             int totvert,
                             int totedge,
                             int totloop,
                             int faces_num,
                             Key *key,
                             Key *nkey,
                             blender::Vector<Material *> &matar,
                             int *vertofs,
                             int *edgeofs,
                             int *loopofs,
                             int *polyofs)
{
  int a;

  Mesh *mesh = static_cast<Mesh *>(ob_src->data);
  float3 *vert_positions = *vert_positions_pp;
  blender::int2 *edge = *medge_pp;
  int *corner_verts = *corner_verts_pp;
  int *corner_edges = *corner_edges_pp;

  if (mesh->verts_num) {
    /* standard data */
    CustomData_merge_layout(
        &mesh->vert_data, vert_data, CD_MASK_MESH.vmask, CD_SET_DEFAULT, totvert);
    CustomData_copy_data_named(&mesh->vert_data, vert_data, 0, *vertofs, mesh->verts_num);

    /* vertex groups */
    MDeformVert *dvert = (MDeformVert *)CustomData_get_for_write(
        vert_data, *vertofs, CD_MDEFORMVERT, totvert);
    const MDeformVert *dvert_src = (const MDeformVert *)CustomData_get_layer(&mesh->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, mesh->verts_num, vgroup_index_map, vgroup_index_map_len);
      if (vgroup_index_map != nullptr) {
        MEM_freeN(vgroup_index_map);
      }
    }

    /* if this is the object we're merging into, no need to do anything */
    if (ob_src != ob_dst) {
      float cmat[4][4];

      /* Watch this: switch matrix multiplication order really goes wrong. */
      mul_m4_m4m4(cmat, imat, ob_src->object_to_world().ptr());

      /* transform vertex coordinates into new space */
      for (a = 0; a < mesh->verts_num; a++) {
        mul_m4_v3(cmat, vert_positions[a]);
      }

      /* 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) {
          /* get pointer to where to write data for this mesh in shape-key's data array */
          float(*cos)[3] = ((float(*)[3])kb->data) + *vertofs;

          /* Check if this mesh has such a shape-key. */
          KeyBlock *okb = mesh->key ? BKE_keyblock_find_name(mesh->key, kb->name) : nullptr;
          if (okb) {
            /* copy this mesh's shape-key to the destination shape-key
             * (need to transform first) */
            float(*ocos)[3] = static_cast<float(*)[3]>(okb->data);
            for (a = 0; a < mesh->verts_num; a++, cos++, ocos++) {
              copy_v3_v3(*cos, *ocos);
              mul_m4_v3(cmat, *cos);
            }
          }
          else {
            /* Copy this mesh's vertex coordinates to the destination shape-key. */
            for (a = 0; a < mesh->verts_num; a++, cos++) {
              copy_v3_v3(*cos, vert_positions[a]);
            }
          }
        }
      }
    }
    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) {
          /* get pointer to where to write data for this mesh in shape-key's data array */
          float(*cos)[3] = ((float(*)[3])kb->data) + *vertofs;

          /* Check if this was one of the original shape-keys. */
          KeyBlock *okb = nkey ? BKE_keyblock_find_name(nkey, kb->name) : nullptr;
          if (okb) {
            /* copy this mesh's shape-key to the destination shape-key */
            float(*ocos)[3] = static_cast<float(*)[3]>(okb->data);
            for (a = 0; a < mesh->verts_num; a++, cos++, ocos++) {
              copy_v3_v3(*cos, *ocos);
            }
          }
          else {
            /* Copy base-coordinates to the destination shape-key. */
            for (a = 0; a < mesh->verts_num; a++, cos++) {
              copy_v3_v3(*cos, vert_positions[a]);
            }
          }
        }
      }
    }
  }

  if (mesh->edges_num) {
    CustomData_merge_layout(
        &mesh->edge_data, edge_data, CD_MASK_MESH.emask, CD_SET_DEFAULT, totedge);
    CustomData_copy_data_named(&mesh->edge_data, edge_data, 0, *edgeofs, mesh->edges_num);

    for (a = 0; a < mesh->edges_num; a++, edge++) {
      (*edge) += *vertofs;
    }
  }

  if (mesh->corners_num) {
    if (ob_src != ob_dst) {
      MultiresModifierData *mmd;

      multiresModifier_prepare_join(depsgraph, scene, ob_src, ob_dst);

      if ((mmd = get_multires_modifier(scene, ob_src, true))) {
        blender::ed::object::iter_other(
            bmain, ob_src, true, blender::ed::object::multires_update_totlevels, &mmd->totlvl);
      }
    }

    CustomData_merge_layout(
        &mesh->corner_data, ldata, CD_MASK_MESH.lmask, CD_SET_DEFAULT, totloop);
    CustomData_copy_data_named(&mesh->corner_data, ldata, 0, *loopofs, mesh->corners_num);

    for (a = 0; a < mesh->corners_num; a++) {
      corner_verts[a] += *vertofs;
      corner_edges[a] += *edgeofs;
    }
  }

  /* 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);
  blender::Vector<int> matmap(totcol);
  if (mesh->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 < matar.size(); b++) {
        if (ma == matar[b]) {
          matmap[a - 1] = b;
          break;
        }
      }

      if (b == matar.size()) {
        if (matar.size() == MAXMAT) {
          /* Reached max limit of materials, use first slot. */
          matmap[a - 1] = 0;
        }
        else {
          /* Add new slot. */
          matmap[a - 1] = matar.size();
          matar.append(ma);
          if (ma) {
            id_us_plus(&ma->id);
          }
        }
      }
    }

    CustomData_merge_layout(
        &mesh->face_data, face_data, CD_MASK_MESH.pmask, CD_SET_DEFAULT, faces_num);
    CustomData_copy_data_named(&mesh->face_data, face_data, 0, *polyofs, mesh->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<int *>(CustomData_get_layer_named_for_write(
        face_data, CD_PROP_INT32, "material_index", faces_num));
    if (!material_indices && matar.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->faces_num; a++) {
        /* Clamp invalid slots, matching #BKE_object_material_get_p. */
        const int mat_index = std::clamp(material_indices[a + *polyofs], 0, totcol - 1);
        material_indices[a + *polyofs] = matmap[mat_index];
      }
    }

    const Span<int> src_face_offsets = mesh->face_offsets();
    int *face_offsets = all_face_offsets + *polyofs;
    for (const int i : blender::IndexRange(mesh->faces_num)) {
      face_offsets[i] = src_face_offsets[i] + *loopofs;
    }
  }

  /* these are used for relinking (cannot be set earlier, or else reattaching goes wrong) */
  *vertofs += mesh->verts_num;
  *vert_positions_pp += mesh->verts_num;
  *edgeofs += mesh->edges_num;
  *medge_pp += mesh->edges_num;
  *loopofs += mesh->corners_num;
  *corner_verts_pp += mesh->corners_num;
  *corner_edges_pp += mesh->corners_num;
  *polyofs += mesh->faces_num;
}

/* 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)
{
  using namespace blender;
  bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
  bke::SpanAttributeWriter<int> face_sets = attributes.lookup_for_write_span<int>(
      ".sculpt_face_set");
  if (!face_sets) {
    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;
    }
    max_face_set = max_ii(max_face_set, face_sets.span[i]);
  }
  *face_set_offset = max_face_set;
  face_sets.finish();
}

wmOperatorStatus ED_mesh_join_objects_exec(bContext *C, wmOperator *op)
{
  Main *bmain = CTX_data_main(C);
  Scene *scene = CTX_data_scene(C);
  Object *ob = CTX_data_active_object(C);
  Material *ma;
  Mesh *mesh;
  blender::int2 *edge = nullptr;
  Key *key, *nkey = nullptr;
  float imat[4][4];
  int a, totedge = 0, totvert = 0;
  int totloop = 0, faces_num = 0, vertofs;
  int i, haskey = 0, edgeofs, loopofs, polyofs;
  bool ok = false, join_parent = false;
  CustomData vert_data, edge_data, ldata, face_data;

  if (ob->mode & OB_MODE_EDIT) {
    BKE_report(op->reports, RPT_WARNING, "Cannot join while in edit mode");
    return OPERATOR_CANCELLED;
  }

  /* ob is the object we are adding geometry to */
  if (!ob || ob->type != OB_MESH) {
    BKE_report(op->reports, RPT_WARNING, "Active object is not a mesh");
    return OPERATOR_CANCELLED;
  }

  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);

  /* count & check */
  CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
    if (ob_iter->type == OB_MESH) {
      mesh = static_cast<Mesh *>(ob_iter->data);

      totvert += mesh->verts_num;
      totedge += mesh->edges_num;
      totloop += mesh->corners_num;
      faces_num += mesh->faces_num;

      if (ob_iter == ob) {
        ok = true;
      }

      if ((ob->parent != nullptr) && (ob_iter == ob->parent)) {
        join_parent = true;
      }

      /* Check for shape-keys. */
      if (mesh->key) {
        haskey++;
      }
    }
  }
  CTX_DATA_END;

  /* Apply parent transform if the active object's parent was joined to it.
   * NOTE: This doesn't apply recursive parenting. */
  if (join_parent) {
    ob->parent = nullptr;
    BKE_object_apply_mat4_ex(ob, ob->object_to_world().ptr(), ob->parent, ob->parentinv, false);
  }

  /* that way the active object is always selected */
  if (ok == false) {
    BKE_report(op->reports, RPT_WARNING, "Active object is not a selected mesh");
    return OPERATOR_CANCELLED;
  }

  /* Only join meshes if there are verts to join,
   * there aren't too many, and we only had one mesh selected. */
  mesh = (Mesh *)ob->data;
  key = mesh->key;

  if (ELEM(totvert, 0, mesh->verts_num)) {
    BKE_report(op->reports, RPT_WARNING, "No mesh data to join");
    return OPERATOR_CANCELLED;
  }

  if (totvert > MESH_MAX_VERTS) {
    BKE_reportf(op->reports,
                RPT_WARNING,
                "Joining results in %d vertices, limit is %ld",
                totvert,
                MESH_MAX_VERTS);
    return OPERATOR_CANCELLED;
  }

  /* Active object materials in new main array, is nicer start! */
  blender::Vector<Material *> matar;
  for (a = 0; a < ob->totcol; a++) {
    matar.append(BKE_object_material_get(ob, a + 1));
    id_us_plus((ID *)matar[a]);
    /* increase id->us : will be lowered later */
  }

  /* - 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.
   */
  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 */
    LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
      if (kb->data) {
        MEM_freeN(kb->data);
      }
      kb->data = MEM_callocN(sizeof(float[3]) * totvert, "join_shapekey");
      kb->totelem = totvert;
    }
  }
  else if (haskey) {
    /* add a new key-block and add to the mesh */
    key = mesh->key = BKE_key_add(bmain, (ID *)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. */
  Mesh *mesh_active = BKE_mesh_from_object(ob);
  int face_set_id_offset = 0;
  mesh_join_offset_face_sets_ID(mesh_active, &face_set_id_offset);

  /* Copy materials, vertex-groups, face sets & face-maps across objects. */
  CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
    /* only act if a mesh, and not the one we're joining to */
    if ((ob != ob_iter) && (ob_iter->type == OB_MESH)) {
      mesh = static_cast<Mesh *>(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(ob, dg->name)) {
          bDeformGroup *odg = MEM_mallocN<bDeformGroup>(__func__);
          memcpy(odg, dg, sizeof(bDeformGroup));
          BLI_addtail(&mesh_active->vertex_group_names, odg);
        }
      }
      if (!BLI_listbase_is_empty(&mesh_active->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<int>(mesh->key->totkey, __func__);
          KeyBlock **kb_map = MEM_malloc_arrayN<KeyBlock *>(mesh->key->totkey, __func__);

          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]) * totvert, "joined_shapekey");
              kbn->totelem = totvert;
            }

            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);
        }
      }
    }
  }
  CTX_DATA_END;

  /* setup new data for destination mesh */
  CustomData_reset(&vert_data);
  CustomData_reset(&edge_data);
  CustomData_reset(&ldata);
  CustomData_reset(&face_data);

  float3 *vert_positions = (float3 *)CustomData_add_layer_named(
      &vert_data, CD_PROP_FLOAT3, CD_SET_DEFAULT, totvert, "position");
  edge = (int2 *)CustomData_add_layer_named(
      &edge_data, CD_PROP_INT32_2D, CD_CONSTRUCT, totedge, ".edge_verts");
  int *corner_verts = (int *)CustomData_add_layer_named(
      &ldata, CD_PROP_INT32, CD_CONSTRUCT, totloop, ".corner_vert");
  int *corner_edges = (int *)CustomData_add_layer_named(
      &ldata, CD_PROP_INT32, CD_CONSTRUCT, totloop, ".corner_edge");
  int *face_offsets = MEM_malloc_arrayN<int>(faces_num + 1, __func__);
  face_offsets[faces_num] = totloop;

  vertofs = 0;
  edgeofs = 0;
  loopofs = 0;
  polyofs = 0;

  /* Inverse transform for all selected meshes in this object,
   * See #object_join_exec for detailed comment on why the safe version is used. */
  invert_m4_m4_safe_ortho(imat, ob->object_to_world().ptr());

  /* Add back active mesh first.
   * This allows to keep things similar as they were, as much as possible
   * (i.e. data from active mesh will remain first ones in new result of the merge,
   * in same order for CD layers, etc). See also #50084.
   */
  join_mesh_single(depsgraph,
                   bmain,
                   scene,
                   ob,
                   ob,
                   imat,
                   &vert_positions,
                   &edge,
                   &corner_verts,
                   &corner_edges,
                   face_offsets,
                   &vert_data,
                   &edge_data,
                   &ldata,
                   &face_data,
                   totvert,
                   totedge,
                   totloop,
                   faces_num,
                   key,
                   nkey,
                   matar,
                   &vertofs,
                   &edgeofs,
                   &loopofs,
                   &polyofs);

  CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
    if (ob_iter == ob) {
      continue;
    }
    /* only join if this is a mesh */
    if (ob_iter->type == OB_MESH) {
      join_mesh_single(depsgraph,
                       bmain,
                       scene,
                       ob,
                       ob_iter,
                       imat,
                       &vert_positions,
                       &edge,
                       &corner_verts,
                       &corner_edges,
                       face_offsets,
                       &vert_data,
                       &edge_data,
                       &ldata,
                       &face_data,
                       totvert,
                       totedge,
                       totloop,
                       faces_num,
                       key,
                       nkey,
                       matar,
                       &vertofs,
                       &edgeofs,
                       &loopofs,
                       &polyofs);

      /* free base, now that data is merged */
      if (ob_iter != ob) {
        blender::ed::object::base_free_and_unlink(bmain, scene, ob_iter);
      }
    }
  }
  CTX_DATA_END;

  /* return to mesh we're merging to */
  mesh = static_cast<Mesh *>(ob->data);

  BKE_mesh_clear_geometry(mesh);

  if (faces_num) {
    mesh->face_offset_indices = face_offsets;
    mesh->runtime->face_offsets_sharing_info = blender::implicit_sharing::info_for_mem_free(
        face_offsets);
  }

  mesh->verts_num = totvert;
  mesh->edges_num = totedge;
  mesh->corners_num = totloop;
  mesh->faces_num = faces_num;

  mesh->vert_data = vert_data;
  mesh->edge_data = edge_data;
  mesh->corner_data = ldata;
  mesh->face_data = face_data;

  /* old material array */
  for (a = 1; a <= ob->totcol; a++) {
    ma = ob->mat[a - 1];
    if (ma) {
      id_us_min(&ma->id);
    }
  }
  for (a = 1; a <= mesh->totcol; a++) {
    ma = mesh->mat[a - 1];
    if (ma) {
      id_us_min(&ma->id);
    }
  }
  MEM_SAFE_FREE(ob->mat);
  MEM_SAFE_FREE(ob->matbits);
  MEM_SAFE_FREE(mesh->mat);

  /* If the object had no slots, don't add an empty one. */
  if (ob->totcol == 0 && matar.size() == 1 && matar[0] == nullptr) {
    matar.clear();
  }

  const int totcol = matar.size();
  if (totcol) {
    mesh->mat = MEM_calloc_arrayN<Material *>(totcol, __func__);
    std::copy_n(matar.data(), totcol, mesh->mat);
    ob->mat = MEM_calloc_arrayN<Material *>(totcol, __func__);
    ob->matbits = MEM_calloc_arrayN<char>(totcol, __func__);
  }

  ob->totcol = mesh->totcol = totcol;

  /* other mesh users */
  BKE_objects_materials_sync_length_all(bmain, (ID *)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);
  }

  /* Due to dependency cycle some other object might access old derived data. */
  BKE_object_free_derived_caches(ob);

  DEG_relations_tag_update(bmain); /* removed objects, need to rebuild dag */

  DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);

  DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
  WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
  WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);

  return OPERATOR_FINISHED;
}

/* -------------------------------------------------------------------- */
/** \name Join as Shapes
 *
 * Add vertex positions of selected meshes as shape keys to the active mesh.
 * \{ */

wmOperatorStatus ED_mesh_shapes_join_objects_exec(bContext *C,
                                                  const bool ensure_keys_exist,
                                                  ReportList *reports)
{
  using namespace blender;
  Main *bmain = CTX_data_main(C);
  Object &active_object = *CTX_data_active_object(C);
  Depsgraph &depsgraph = *CTX_data_ensure_evaluated_depsgraph(C);
  Mesh &active_mesh = *static_cast<Mesh *>(active_object.data);

  struct ObjectInfo {
    StringRefNull name;
    const Mesh &mesh;
  };

  auto topology_count_matches = [](const Mesh &a, const Mesh &b) {
    return a.verts_num == b.verts_num;
  };

  bool found_object = false;
  bool found_non_equal_count = false;
  Vector<ObjectInfo> compatible_objects;
  CTX_DATA_BEGIN (C, Object *, ob_iter, selected_editable_objects) {
    if (ob_iter == &active_object) {
      continue;
    }
    if (ob_iter->type != OB_MESH) {
      continue;
    }
    const Object *object_eval = DEG_get_evaluated(&depsgraph, ob_iter);
    if (!object_eval) {
      continue;
    }
    found_object = true;
    if (const Mesh *mesh = BKE_object_get_evaluated_mesh(object_eval)) {
      if (topology_count_matches(*mesh, active_mesh)) {
        compatible_objects.append({BKE_id_name(ob_iter->id), *mesh});
        continue;
      }
    }
    /* Fall back to the original mesh. */
    const Mesh &mesh_orig = *static_cast<const Mesh *>(ob_iter->data);
    if (topology_count_matches(mesh_orig, active_mesh)) {
      compatible_objects.append({BKE_id_name(ob_iter->id), mesh_orig});
      continue;
    }
    found_non_equal_count = true;
  }
  CTX_DATA_END;

  if (!found_object) {
    BKE_report(reports, RPT_WARNING, "No source mesh objects selected");
    return OPERATOR_CANCELLED;
  }

  if (found_non_equal_count) {
    BKE_report(reports, RPT_WARNING, "Selected meshes must have equal numbers of vertices");
    return OPERATOR_CANCELLED;
  }

  if (compatible_objects.is_empty()) {
    BKE_report(
        reports, RPT_WARNING, "No additional selected meshes with equal vertex count to join");
    return OPERATOR_CANCELLED;
  }

  if (!active_mesh.key) {
    /* Initialize basis shape key with existing mesh. */
    active_mesh.key = BKE_key_add(bmain, &active_mesh.id);
    active_mesh.key->type = KEY_RELATIVE;
    BKE_keyblock_convert_from_mesh(
        &active_mesh, active_mesh.key, BKE_keyblock_add(active_mesh.key, nullptr));
  }

  int keys_changed = 0;
  bool any_keys_added = false;
  for (const ObjectInfo &info : compatible_objects) {
    if (ensure_keys_exist) {
      KeyBlock *kb = BKE_keyblock_add(active_mesh.key, info.name.c_str());
      BKE_keyblock_convert_from_mesh(&info.mesh, active_mesh.key, kb);
      any_keys_added = true;
    }
    else if (KeyBlock *kb = BKE_keyblock_find_name(active_mesh.key, info.name.c_str())) {
      keys_changed++;
      BKE_keyblock_update_from_mesh(&info.mesh, kb);
    }
  }

  if (!ensure_keys_exist) {
    if (keys_changed == 0) {
      BKE_report(reports, RPT_ERROR, "No name matches between selected objects and shape keys");
      return OPERATOR_CANCELLED;
    }
    BKE_reportf(reports, RPT_INFO, "Updated %d shape key(s)", keys_changed);
  }

  DEG_id_tag_update(&active_mesh.id, ID_RECALC_GEOMETRY);
  WM_main_add_notifier(NC_GEOM | ND_DATA, &active_mesh.id);

  if (any_keys_added && bmain) {
    /* Adding a new shape key should trigger a rebuild of relationships. */
    DEG_relations_tag_update(bmain);
  }

  return OPERATOR_FINISHED;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Mesh Topology Mirror API
 * \{ */

static MirrTopoStore_t mesh_topo_store = {nullptr, -1, -1, false};

BLI_INLINE void mesh_mirror_topo_table_get_meshes(Object *ob,
                                                  Mesh *mesh_eval,
                                                  Mesh **r_mesh_mirror,
                                                  BMEditMesh **r_em_mirror)
{
  Mesh *mesh_mirror = nullptr;
  BMEditMesh *em_mirror = nullptr;

  Mesh *mesh = static_cast<Mesh *>(ob->data);
  if (mesh_eval != nullptr) {
    mesh_mirror = mesh_eval;
  }
  else if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
    em_mirror = em;
  }
  else {
    mesh_mirror = mesh;
  }

  *r_mesh_mirror = mesh_mirror;
  *r_em_mirror = em_mirror;
}

void ED_mesh_mirror_topo_table_begin(Object *ob, Mesh *mesh_eval)
{
  Mesh *mesh_mirror;
  BMEditMesh *em_mirror;
  mesh_mirror_topo_table_get_meshes(ob, mesh_eval, &mesh_mirror, &em_mirror);

  ED_mesh_mirrtopo_init(em_mirror, mesh_mirror, &mesh_topo_store, false);
}

void ED_mesh_mirror_topo_table_end(Object * /*ob*/)
{
  /* TODO: store this in object/object-data (keep unused argument for now). */
  ED_mesh_mirrtopo_free(&mesh_topo_store);
}

/* Returns true on success. */
static bool ed_mesh_mirror_topo_table_update(Object *ob, Mesh *mesh_eval)
{
  Mesh *mesh_mirror;
  BMEditMesh *em_mirror;
  mesh_mirror_topo_table_get_meshes(ob, mesh_eval, &mesh_mirror, &em_mirror);

  if (ED_mesh_mirrtopo_recalc_check(em_mirror, mesh_mirror, &mesh_topo_store)) {
    ED_mesh_mirror_topo_table_begin(ob, mesh_eval);
  }
  return true;
}

/** \} */

static int mesh_get_x_mirror_vert_spatial(Object *ob, Mesh *mesh_eval, int index)
{
  Mesh *mesh = static_cast<Mesh *>(ob->data);
  const Span<float3> positions = mesh_eval ? mesh_eval->vert_positions() : mesh->vert_positions();

  float vec[3];

  vec[0] = -positions[index][0];
  vec[1] = positions[index][1];
  vec[2] = positions[index][2];

  return ED_mesh_mirror_spatial_table_lookup(ob, nullptr, mesh_eval, vec);
}

static int mesh_get_x_mirror_vert_topo(Object *ob, Mesh *mesh, int index)
{
  if (!ed_mesh_mirror_topo_table_update(ob, mesh)) {
    return -1;
  }

  return mesh_topo_store.index_lookup[index];
}

int mesh_get_x_mirror_vert(Object *ob, Mesh *mesh_eval, int index, const bool use_topology)
{
  if (use_topology) {
    return mesh_get_x_mirror_vert_topo(ob, mesh_eval, index);
  }
  return mesh_get_x_mirror_vert_spatial(ob, mesh_eval, index);
}

static BMVert *editbmesh_get_x_mirror_vert_spatial(Object *ob, BMEditMesh *em, const float co[3])
{
  float vec[3];
  int i;

  /* ignore nan verts */
  if ((isfinite(co[0]) == false) || (isfinite(co[1]) == false) || (isfinite(co[2]) == false)) {
    return nullptr;
  }

  vec[0] = -co[0];
  vec[1] = co[1];
  vec[2] = co[2];

  i = ED_mesh_mirror_spatial_table_lookup(ob, em, nullptr, vec);
  if (i != -1) {
    return BM_vert_at_index(em->bm, i);
  }
  return nullptr;
}

static BMVert *editbmesh_get_x_mirror_vert_topo(Object *ob, BMEditMesh *em, BMVert *eve, int index)
{
  intptr_t poinval;
  if (!ed_mesh_mirror_topo_table_update(ob, nullptr)) {
    return nullptr;
  }

  if (index == -1) {
    BMIter iter;
    BMVert *v;

    index = 0;
    BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) {
      if (v == eve) {
        break;
      }
      index++;
    }

    if (index == em->bm->totvert) {
      return nullptr;
    }
  }

  poinval = mesh_topo_store.index_lookup[index];

  if (poinval != -1) {
    return (BMVert *)(poinval);
  }
  return nullptr;
}

BMVert *editbmesh_get_x_mirror_vert(
    Object *ob, BMEditMesh *em, BMVert *eve, const float co[3], int index, const bool use_topology)
{
  if (use_topology) {
    return editbmesh_get_x_mirror_vert_topo(ob, em, eve, index);
  }
  return editbmesh_get_x_mirror_vert_spatial(ob, em, co);
}

int ED_mesh_mirror_get_vert(Object *ob, int index)
{
  Mesh *mesh = static_cast<Mesh *>(ob->data);
  bool use_topology = (mesh->editflag & ME_EDIT_MIRROR_TOPO) != 0;
  int index_mirr;

  if (BMEditMesh *em = mesh->runtime->edit_mesh.get()) {
    BMVert *eve, *eve_mirr;
    eve = BM_vert_at_index(em->bm, index);
    eve_mirr = editbmesh_get_x_mirror_vert(ob, em, eve, eve->co, index, use_topology);
    index_mirr = eve_mirr ? BM_elem_index_get(eve_mirr) : -1;
  }
  else {
    index_mirr = mesh_get_x_mirror_vert(ob, nullptr, index, use_topology);
  }

  return index_mirr;
}

#if 0

static float *editmesh_get_mirror_uv(
    BMEditMesh *em, int axis, float *uv, float *mirrCent, float *face_cent)
{
  float vec[2];
  float cent_vec[2];
  float cent[2];

  /* ignore nan verts */
  if (isnan(uv[0]) || !isfinite(uv[0]) || isnan(uv[1]) || !isfinite(uv[1])) {
    return nullptr;
  }

  if (axis) {
    vec[0] = uv[0];
    vec[1] = -((uv[1]) - mirrCent[1]) + mirrCent[1];

    cent_vec[0] = face_cent[0];
    cent_vec[1] = -((face_cent[1]) - mirrCent[1]) + mirrCent[1];
  }
  else {
    vec[0] = -((uv[0]) - mirrCent[0]) + mirrCent[0];
    vec[1] = uv[1];

    cent_vec[0] = -((face_cent[0]) - mirrCent[0]) + mirrCent[0];
    cent_vec[1] = face_cent[1];
  }

  /* TODO: Optimize. */
  {
    BMIter iter;
    BMFace *efa;

    BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
      BM_face_uv_calc_center_median(efa, cd_loop_uv_offset, cent);

      if ((fabsf(cent[0] - cent_vec[0]) < 0.001f) && (fabsf(cent[1] - cent_vec[1]) < 0.001f)) {
        BMIter liter;
        BMLoop *l;

        BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
          float *luv2 = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
          if ((fabsf(luv[0] - vec[0]) < 0.001f) && (fabsf(luv[1] - vec[1]) < 0.001f)) {
            return luv;
          }
        }
      }
    }
  }

  return nullptr;
}

#endif

static uint mirror_facehash(const void *ptr)
{
  const MFace *mf = static_cast<const MFace *>(ptr);
  uint v0, v1;

  if (mf->v4) {
    v0 = std::min({mf->v1, mf->v2, mf->v3, mf->v4});
    v1 = std::max({mf->v1, mf->v2, mf->v3, mf->v4});
  }
  else {
    v0 = std::min({mf->v1, mf->v2, mf->v3});
    v1 = std::min({mf->v1, mf->v2, mf->v3});
  }

  return ((v0 * 39) ^ (v1 * 31));
}

static int mirror_facerotation(const MFace *a, const MFace *b)
{
  if (b->v4) {
    if (a->v1 == b->v1 && a->v2 == b->v2 && a->v3 == b->v3 && a->v4 == b->v4) {
      return 0;
    }
    if (a->v4 == b->v1 && a->v1 == b->v2 && a->v2 == b->v3 && a->v3 == b->v4) {
      return 1;
    }
    if (a->v3 == b->v1 && a->v4 == b->v2 && a->v1 == b->v3 && a->v2 == b->v4) {
      return 2;
    }
    if (a->v2 == b->v1 && a->v3 == b->v2 && a->v4 == b->v3 && a->v1 == b->v4) {
      return 3;
    }
  }
  else {
    if (a->v1 == b->v1 && a->v2 == b->v2 && a->v3 == b->v3) {
      return 0;
    }
    if (a->v3 == b->v1 && a->v1 == b->v2 && a->v2 == b->v3) {
      return 1;
    }
    if (a->v2 == b->v1 && a->v3 == b->v2 && a->v1 == b->v3) {
      return 2;
    }
  }

  return -1;
}

static bool mirror_facecmp(const void *a, const void *b)
{
  return (mirror_facerotation((MFace *)a, (MFace *)b) == -1);
}

int *mesh_get_x_mirror_faces(Object *ob, BMEditMesh *em, Mesh *mesh_eval)
{
  Mesh *mesh = static_cast<Mesh *>(ob->data);
  MFace mirrormf;
  const MFace *mf, *hashmf;
  GHash *fhash;
  int *mirrorverts, *mirrorfaces;

  BLI_assert(em == nullptr); /* Does not work otherwise, currently... */

  const bool use_topology = (mesh->editflag & ME_EDIT_MIRROR_TOPO) != 0;
  const int totvert = mesh_eval ? mesh_eval->verts_num : mesh->verts_num;
  const int totface = mesh_eval ? mesh_eval->totface_legacy : mesh->totface_legacy;
  int a;

  mirrorverts = MEM_calloc_arrayN<int>(totvert, "MirrorVerts");
  mirrorfaces = MEM_calloc_arrayN<int>(2 * totface, "MirrorFaces");

  const Span<float3> vert_positions = mesh_eval ? mesh_eval->vert_positions() :
                                                  mesh->vert_positions();
  const MFace *mface = (const MFace *)CustomData_get_layer(
      &(mesh_eval ? mesh_eval : mesh)->fdata_legacy, CD_MFACE);

  ED_mesh_mirror_spatial_table_begin(ob, em, mesh_eval);

  for (const int i : vert_positions.index_range()) {
    mirrorverts[i] = mesh_get_x_mirror_vert(ob, mesh_eval, i, use_topology);
  }

  ED_mesh_mirror_spatial_table_end(ob);

  fhash = BLI_ghash_new_ex(
      mirror_facehash, mirror_facecmp, "mirror_facehash gh", mesh->totface_legacy);
  for (a = 0, mf = mface; a < totface; a++, mf++) {
    BLI_ghash_insert(fhash, (void *)mf, (void *)mf);
  }

  for (a = 0, mf = mface; a < totface; a++, mf++) {
    mirrormf.v1 = mirrorverts[mf->v3];
    mirrormf.v2 = mirrorverts[mf->v2];
    mirrormf.v3 = mirrorverts[mf->v1];
    mirrormf.v4 = (mf->v4) ? mirrorverts[mf->v4] : 0;

    /* make sure v4 is not 0 if a quad */
    if (mf->v4 && mirrormf.v4 == 0) {
      std::swap(mirrormf.v1, mirrormf.v3);
      std::swap(mirrormf.v2, mirrormf.v4);
    }

    hashmf = static_cast<const MFace *>(BLI_ghash_lookup(fhash, &mirrormf));
    if (hashmf) {
      mirrorfaces[a * 2] = hashmf - mface;
      mirrorfaces[a * 2 + 1] = mirror_facerotation(&mirrormf, hashmf);
    }
    else {
      mirrorfaces[a * 2] = -1;
    }
  }

  BLI_ghash_free(fhash, nullptr, nullptr);
  MEM_freeN(mirrorverts);

  return mirrorfaces;
}

/* Selection (vertex and face). */

bool ED_mesh_pick_face(bContext *C, Object *ob, const int mval[2], uint dist_px, uint *r_index)
{
  Mesh *mesh = static_cast<Mesh *>(ob->data);

  BLI_assert(mesh && GS(mesh->id.name) == ID_ME);

  if (!mesh || mesh->faces_num == 0) {
    return false;
  }

  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
  ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
  ED_view3d_select_id_validate(&vc);

  if (dist_px) {
    /* Sample rect to increase chances of selecting, so that when clicking
     * on an edge in the back-buffer, we can still select a face. */
    *r_index = DRW_select_buffer_find_nearest_to_point(
        vc.depsgraph, vc.region, vc.v3d, mval, 1, mesh->faces_num + 1, &dist_px);
  }
  else {
    /* sample only on the exact position */
    *r_index = DRW_select_buffer_sample_point(vc.depsgraph, vc.region, vc.v3d, mval);
  }

  if ((*r_index) == 0 || (*r_index) > uint(mesh->faces_num)) {
    return false;
  }

  (*r_index)--;

  return true;
}

static void ed_mesh_pick_face_vert__mpoly_find(
    /* context */
    ARegion *region,
    const float mval[2],
    /* mesh data (evaluated) */
    const blender::IndexRange face,
    const Span<float3> vert_positions,
    const int *corner_verts,
    /* return values */
    float *r_len_best,
    int *r_v_idx_best)
{
  for (int j = face.size(); j--;) {
    float sco[2];
    const int v_idx = corner_verts[face[j]];
    if (ED_view3d_project_float_object(region, vert_positions[v_idx], sco, V3D_PROJ_TEST_NOP) ==
        V3D_PROJ_RET_OK)
    {
      const float len_test = len_manhattan_v2v2(mval, sco);
      if (len_test < *r_len_best) {
        *r_len_best = len_test;
        *r_v_idx_best = v_idx;
      }
    }
  }
}
bool ED_mesh_pick_face_vert(
    bContext *C, Object *ob, const int mval[2], uint dist_px, uint *r_index)
{
  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
  uint face_index;
  Mesh *mesh = static_cast<Mesh *>(ob->data);

  BLI_assert(mesh && GS(mesh->id.name) == ID_ME);

  if (ED_mesh_pick_face(C, ob, mval, dist_px, &face_index)) {
    const Object *ob_eval = DEG_get_evaluated(depsgraph, ob);
    const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
    if (!mesh_eval) {
      return false;
    }
    ARegion *region = CTX_wm_region(C);

    int v_idx_best = ORIGINDEX_NONE;

    /* find the vert closest to 'mval' */
    const float mval_f[2] = {float(mval[0]), float(mval[1])};
    float len_best = FLT_MAX;

    const Span<float3> vert_positions = mesh_eval->vert_positions();
    const blender::OffsetIndices faces = mesh_eval->faces();
    const Span<int> corner_verts = mesh_eval->corner_verts();

    const int *index_mp_to_orig = (const int *)CustomData_get_layer(&mesh_eval->face_data,
                                                                    CD_ORIGINDEX);

    /* tag all verts using this face */
    if (index_mp_to_orig) {
      for (const int i : faces.index_range()) {
        if (index_mp_to_orig[i] == face_index) {
          ed_mesh_pick_face_vert__mpoly_find(region,
                                             mval_f,
                                             faces[i],
                                             vert_positions,
                                             corner_verts.data(),
                                             &len_best,
                                             &v_idx_best);
        }
      }
    }
    else {
      if (face_index < faces.size()) {
        ed_mesh_pick_face_vert__mpoly_find(region,
                                           mval_f,
                                           faces[face_index],
                                           vert_positions,
                                           corner_verts.data(),
                                           &len_best,
                                           &v_idx_best);
      }
    }

    /* Map the `dm` to `mesh`, setting the `r_index` if possible. */
    if (v_idx_best != ORIGINDEX_NONE) {
      const int *index_mv_to_orig = (const int *)CustomData_get_layer(&mesh_eval->vert_data,
                                                                      CD_ORIGINDEX);
      if (index_mv_to_orig) {
        v_idx_best = index_mv_to_orig[v_idx_best];
      }
    }

    if ((v_idx_best != ORIGINDEX_NONE) && (v_idx_best < mesh->verts_num)) {
      *r_index = v_idx_best;
      return true;
    }
  }

  return false;
}

bool ED_mesh_pick_edge(bContext *C, Object *ob, const int mval[2], uint dist_px, uint *r_index)
{
  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
  Mesh *mesh = static_cast<Mesh *>(ob->data);

  BLI_assert(mesh && GS(mesh->id.name) == ID_ME);

  if (!mesh || mesh->edges_num == 0) {
    return false;
  }

  ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
  ED_view3d_select_id_validate(&vc);
  Base *base = BKE_view_layer_base_find(vc.view_layer, vc.obact);
  DRW_select_buffer_context_create(vc.depsgraph, {base}, SCE_SELECT_EDGE);

  uint edge_idx_best = ORIGINDEX_NONE;

  if (dist_px) {
    /* Sample rect to increase chances of selecting, so that when clicking
     * on an edge in the back-buffer, we can still select a face. */
    edge_idx_best = DRW_select_buffer_find_nearest_to_point(
        vc.depsgraph, vc.region, vc.v3d, mval, 1, mesh->edges_num + 1, &dist_px);
  }
  else {
    /* sample only on the exact position */
    edge_idx_best = DRW_select_buffer_sample_point(vc.depsgraph, vc.region, vc.v3d, mval);
  }

  if (edge_idx_best == 0 || edge_idx_best > uint(mesh->edges_num)) {
    return false;
  }

  edge_idx_best--;

  if (edge_idx_best != ORIGINDEX_NONE) {
    *r_index = edge_idx_best;
    return true;
  }

  return false;
}

/**
 * Vertex selection in object mode,
 * currently only weight paint uses this.
 *
 * \return boolean true == Found
 */
struct VertPickData {
  blender::VArraySpan<bool> hide_vert;
  const float *mval_f; /* [2] */
  ARegion *region;

  /* runtime */
  float len_best;
  int v_idx_best;
};

static void ed_mesh_pick_vert__mapFunc(void *user_data,
                                       int index,
                                       const float co[3],
                                       const float /*no*/[3])
{
  VertPickData *data = static_cast<VertPickData *>(user_data);
  if (!data->hide_vert.is_empty() && data->hide_vert[index]) {
    return;
  }
  float sco[2];
  if (ED_view3d_project_float_object(data->region, co, sco, V3D_PROJ_TEST_CLIP_DEFAULT) ==
      V3D_PROJ_RET_OK)
  {
    const float len = len_manhattan_v2v2(data->mval_f, sco);
    if (len < data->len_best) {
      data->len_best = len;
      data->v_idx_best = index;
    }
  }
}
bool ED_mesh_pick_vert(
    bContext *C, Object *ob, const int mval[2], uint dist_px, bool use_zbuf, uint *r_index)
{
  using namespace blender;
  Mesh *mesh = static_cast<Mesh *>(ob->data);

  BLI_assert(mesh && GS(mesh->id.name) == ID_ME);

  if (!mesh || mesh->verts_num == 0) {
    return false;
  }

  Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
  ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
  ED_view3d_select_id_validate(&vc);

  if (use_zbuf) {
    if (dist_px > 0) {
      /* Sample rectangle to increase chances of selecting, so that when clicking
       * on an face in the back-buffer, we can still select a vert. */
      *r_index = DRW_select_buffer_find_nearest_to_point(
          vc.depsgraph, vc.region, vc.v3d, mval, 1, mesh->verts_num + 1, &dist_px);
    }
    else {
      /* sample only on the exact position */
      *r_index = DRW_select_buffer_sample_point(vc.depsgraph, vc.region, vc.v3d, mval);
    }

    if ((*r_index) == 0 || (*r_index) > uint(mesh->verts_num)) {
      return false;
    }

    (*r_index)--;
  }
  else {
    const Object *ob_eval = DEG_get_evaluated(vc.depsgraph, ob);
    const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
    ARegion *region = vc.region;
    RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);

    /* find the vert closest to 'mval' */
    const float mval_f[2] = {float(mval[0]), float(mval[1])};

    VertPickData data{};

    ED_view3d_init_mats_rv3d(ob, rv3d);

    if (mesh_eval == nullptr) {
      return false;
    }

    const bke::AttributeAccessor attributes = mesh->attributes();

    /* setup data */
    data.region = region;
    data.mval_f = mval_f;
    data.len_best = FLT_MAX;
    data.v_idx_best = -1;
    data.hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);

    BKE_mesh_foreach_mapped_vert(mesh_eval, ed_mesh_pick_vert__mapFunc, &data, MESH_FOREACH_NOP);

    if (data.v_idx_best == -1) {
      return false;
    }

    *r_index = data.v_idx_best;
  }

  return true;
}

MDeformVert *ED_mesh_active_dvert_get_em(Object *ob, BMVert **r_eve)
{
  if (ob->mode & OB_MODE_EDIT && ob->type == OB_MESH) {
    Mesh *mesh = static_cast<Mesh *>(ob->data);
    if (!BLI_listbase_is_empty(&mesh->vertex_group_names)) {
      BMesh *bm = mesh->runtime->edit_mesh->bm;
      const int cd_dvert_offset = CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT);

      if (cd_dvert_offset != -1) {
        BMVert *eve = BM_mesh_active_vert_get(bm);

        if (eve) {
          if (r_eve) {
            *r_eve = eve;
          }
          return static_cast<MDeformVert *>(BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset));
        }
      }
    }
  }

  if (r_eve) {
    *r_eve = nullptr;
  }
  return nullptr;
}

MDeformVert *ED_mesh_active_dvert_get_ob(Object *ob, int *r_index)
{
  Mesh *mesh = static_cast<Mesh *>(ob->data);
  int index = BKE_mesh_mselect_active_get(mesh, ME_VSEL);
  if (r_index) {
    *r_index = index;
  }
  if (index == -1 || mesh->deform_verts().is_empty()) {
    return nullptr;
  }
  MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
  return &dverts[index];
}

MDeformVert *ED_mesh_active_dvert_get_only(Object *ob)
{
  if (ob->type == OB_MESH) {
    if (ob->mode & OB_MODE_EDIT) {
      return ED_mesh_active_dvert_get_em(ob, nullptr);
    }
    return ED_mesh_active_dvert_get_ob(ob, nullptr);
  }
  return nullptr;
}

void EDBM_mesh_stats_multi(const Span<Object *> objects, int totelem[3], int totelem_sel[3])
{
  if (totelem) {
    totelem[0] = 0;
    totelem[1] = 0;
    totelem[2] = 0;
  }
  if (totelem_sel) {
    totelem_sel[0] = 0;
    totelem_sel[1] = 0;
    totelem_sel[2] = 0;
  }

  for (Object *obedit : objects) {
    BMEditMesh *em = BKE_editmesh_from_object(obedit);
    BMesh *bm = em->bm;
    if (totelem) {
      totelem[0] += bm->totvert;
      totelem[1] += bm->totedge;
      totelem[2] += bm->totface;
    }
    if (totelem_sel) {
      totelem_sel[0] += bm->totvertsel;
      totelem_sel[1] += bm->totedgesel;
      totelem_sel[2] += bm->totfacesel;
    }
  }
}

void EDBM_mesh_elem_index_ensure_multi(const Span<Object *> objects, const char htype)
{
  int elem_offset[4] = {0, 0, 0, 0};
  for (Object *obedit : objects) {
    BMEditMesh *em = BKE_editmesh_from_object(obedit);
    BMesh *bm = em->bm;
    BM_mesh_elem_index_ensure_ex(bm, htype, elem_offset);
  }
}
