Merge branch 'master' into blender2.8
authorCampbell Barton <ideasman42@gmail.com>
Fri, 13 Jul 2018 06:37:20 +0000 (08:37 +0200)
committerCampbell Barton <ideasman42@gmail.com>
Fri, 13 Jul 2018 06:40:17 +0000 (08:40 +0200)
1  2 
source/blender/blenkernel/intern/mesh.c
source/blender/blenkernel/intern/mesh_convert.c
source/blender/blenkernel/intern/mesh_evaluate.c
source/blender/blenkernel/intern/mesh_merge.c
source/blender/blenkernel/intern/mesh_remap.c
source/blender/blenkernel/intern/mesh_runtime.c
source/blender/blenkernel/intern/mesh_tangent.c
source/blender/blenkernel/intern/mesh_validate.c

index 5c9736c2620af141b58bacad16fb2dced4dafc3a,6d4814720741a5b7463ce539e7d5e1760a10409c..df20d5a2b5df7a29790d78e0be74e74c76391d5e
@@@ -372,48 -372,6 +374,49 @@@ void BKE_mesh_ensure_skin_customdata(Me
        }
  }
  
-                       CustomData_add_layer(&me->pdata,
-                                                 CD_FACEMAP,
-                                                 CD_DEFAULT,
-                                                 NULL,
-                                                 me->totpoly);
 +bool BKE_mesh_ensure_facemap_customdata(struct Mesh *me)
 +{
 +      BMesh *bm = me->edit_btmesh ? me->edit_btmesh->bm : NULL;
 +      bool changed = false;
 +      if (bm) {
 +              if (!CustomData_has_layer(&bm->pdata, CD_FACEMAP)) {
 +                      BM_data_layer_add(bm, &bm->pdata, CD_FACEMAP);
 +                      changed = true;
 +              }
 +      }
 +      else {
 +              if (!CustomData_has_layer(&me->pdata, CD_FACEMAP)) {
++                      CustomData_add_layer(
++                              &me->pdata,
++                              CD_FACEMAP,
++                              CD_DEFAULT,
++                              NULL,
++                              me->totpoly);
 +                      changed = true;
 +              }
 +      }
 +      return changed;
 +}
 +
 +bool BKE_mesh_clear_facemap_customdata(struct Mesh *me)
 +{
 +      BMesh *bm = me->edit_btmesh ? me->edit_btmesh->bm : NULL;
 +      bool changed = false;
 +      if (bm) {
 +              if (CustomData_has_layer(&bm->pdata, CD_FACEMAP)) {
 +                      BM_data_layer_free(bm, &bm->pdata, CD_FACEMAP);
 +                      changed = true;
 +              }
 +      }
 +      else {
 +              if (CustomData_has_layer(&me->pdata, CD_FACEMAP)) {
 +                      CustomData_free_layers(&me->pdata, CD_FACEMAP, me->totpoly);
 +                      changed = true;
 +              }
 +      }
 +      return changed;
 +}
 +
  /* this ensures grouped customdata (e.g. mtexpoly and mloopuv and mtface, or
   * mloopcol and mcol) have the same relative active/render/clone/mask indices.
   *
@@@ -1843,12 -1652,14 +1848,13 @@@ void BKE_mesh_split_faces(Mesh *mesh, b
  #endif
  }
  
 -
  /* **** Depsgraph evaluation **** */
  
- void BKE_mesh_eval_geometry(Depsgraph *depsgraph,
-                             Mesh *mesh)
+ void BKE_mesh_eval_geometry(
 -        EvaluationContext *UNUSED(eval_ctx),
++        Depsgraph *depsgraph,
+         Mesh *mesh)
  {
 -      DEG_debug_print_eval(__func__, mesh->id.name, mesh);
 +      DEG_debug_print_eval(depsgraph, __func__, mesh->id.name, mesh);
        if (mesh->bb == NULL || (mesh->bb->flag & BOUNDBOX_DIRTY)) {
                BKE_mesh_texspace_calc(mesh);
        }
index 3fc5d1ca51c4150d6909e374d94b1974f0003c77,c801c5dcb01646b4464b61ee720b548f081cd1fd..738f116310b2eaae08b005f34bb359e2871a4b39
@@@ -1074,339 -1026,3 +1076,343 @@@ Mesh *BKE_mesh_new_from_object
  
        return tmpmesh;
  }
-               BKE_id_copy_ex(NULL, &me->id, (ID **)&result,
-                              LIB_ID_CREATE_NO_MAIN |
-                              LIB_ID_CREATE_NO_USER_REFCOUNT |
-                              LIB_ID_CREATE_NO_DEG_TAG |
-                              LIB_ID_COPY_NO_PREVIEW,
-                              false);
 +
 +
 +static void add_shapekey_layers(Mesh *mesh_dest, Mesh *mesh_src)
 +{
 +      KeyBlock *kb;
 +      Key *key = mesh_src->key;
 +      int i;
 +
 +      if (!mesh_src->key)
 +              return;
 +
 +      /* ensure we can use mesh vertex count for derived mesh custom data */
 +      if (mesh_src->totvert != mesh_dest->totvert) {
 +              fprintf(stderr,
 +                      "%s: vertex size mismatch (mesh/dm) '%s' (%d != %d)\n",
 +                      __func__, mesh_src->id.name + 2, mesh_src->totvert, mesh_dest->totvert);
 +              return;
 +      }
 +
 +      for (i = 0, kb = key->block.first; kb; kb = kb->next, i++) {
 +              int ci;
 +              float *array;
 +
 +              if (mesh_src->totvert != kb->totelem) {
 +                      fprintf(stderr,
 +                              "%s: vertex size mismatch (Mesh '%s':%d != KeyBlock '%s':%d)\n",
 +                              __func__, mesh_src->id.name + 2, mesh_src->totvert, kb->name, kb->totelem);
 +                      array = MEM_calloc_arrayN((size_t)mesh_src->totvert, 3 * sizeof(float), __func__);
 +              }
 +              else {
 +                      array = MEM_malloc_arrayN((size_t)mesh_src->totvert, 3 * sizeof(float), __func__);
 +                      memcpy(array, kb->data, (size_t)mesh_src->totvert * 3 * sizeof(float));
 +              }
 +
 +              CustomData_add_layer_named(&mesh_dest->vdata, CD_SHAPEKEY, CD_ASSIGN, array, mesh_dest->totvert, kb->name);
 +              ci = CustomData_get_layer_index_n(&mesh_dest->vdata, CD_SHAPEKEY, i);
 +
 +              mesh_dest->vdata.layers[ci].uid = kb->uid;
 +      }
 +}
 +
 +
 +Mesh *BKE_mesh_create_derived_for_modifier(
 +        struct Depsgraph *depsgraph, Scene *scene, Object *ob,
 +        ModifierData *md, int build_shapekey_layers)
 +{
 +      Mesh *me = ob->data;
 +      const ModifierTypeInfo *mti = modifierType_getInfo(md->type);
 +      Mesh *result;
 +      KeyBlock *kb;
 +      ModifierEvalContext mectx = {depsgraph, ob, 0};
 +
 +      if (!(md->mode & eModifierMode_Realtime)) {
 +              return NULL;
 +      }
 +
 +      if (mti->isDisabled && mti->isDisabled(scene, md, 0)) {
 +              return NULL;
 +      }
 +
 +      if (build_shapekey_layers && me->key && (kb = BLI_findlink(&me->key->block, ob->shapenr - 1))) {
 +              BKE_keyblock_convert_to_mesh(kb, me);
 +      }
 +
 +      if (mti->type == eModifierTypeType_OnlyDeform) {
 +              int numVerts;
 +              float (*deformedVerts)[3] = BKE_mesh_vertexCos_get(me, &numVerts);
 +
 +              modifier_deformVerts(md, &mectx, NULL, deformedVerts, numVerts);
-               BKE_id_copy_ex(NULL, &me->id, (ID **)&mesh_temp,
-                              LIB_ID_CREATE_NO_MAIN |
-                              LIB_ID_CREATE_NO_USER_REFCOUNT |
-                              LIB_ID_CREATE_NO_DEG_TAG |
-                              LIB_ID_COPY_NO_PREVIEW,
-                              false);
++              BKE_id_copy_ex(
++                      NULL, &me->id, (ID **)&result,
++                      LIB_ID_CREATE_NO_MAIN |
++                      LIB_ID_CREATE_NO_USER_REFCOUNT |
++                      LIB_ID_CREATE_NO_DEG_TAG |
++                      LIB_ID_COPY_NO_PREVIEW,
++                      false);
 +              BKE_mesh_apply_vert_coords(result, deformedVerts);
 +
 +              if (build_shapekey_layers)
 +                      add_shapekey_layers(result, me);
 +
 +              MEM_freeN(deformedVerts);
 +      }
 +      else {
 +              Mesh *mesh_temp;
-               CustomData_add_layer(&tmp.vdata, CD_MVERT, CD_ASSIGN,
-                                    (alloctype == CD_ASSIGN) ? mesh_src->mvert : MEM_dupallocN(mesh_src->mvert),
-                                    totvert);
++              BKE_id_copy_ex(
++                      NULL, &me->id, (ID **)&mesh_temp,
++                      LIB_ID_CREATE_NO_MAIN |
++                      LIB_ID_CREATE_NO_USER_REFCOUNT |
++                      LIB_ID_CREATE_NO_DEG_TAG |
++                      LIB_ID_COPY_NO_PREVIEW,
++                      false);
 +
 +              if (build_shapekey_layers)
 +                      add_shapekey_layers(mesh_temp, me);
 +
 +              result = modifier_applyModifier(md, &mectx, mesh_temp);
 +              ASSERT_IS_VALID_MESH(result);
 +
 +              if (mesh_temp != result) {
 +                      BKE_id_free(NULL, mesh_temp);
 +              }
 +      }
 +
 +      return result;
 +}
 +
 +/* This is a Mesh-based copy of the same function in DerivedMesh.c */
 +static void shapekey_layers_to_keyblocks(Mesh *mesh_src, Mesh *mesh_dst, int actshape_uid)
 +{
 +      KeyBlock *kb;
 +      int i, j, tot;
 +
 +      if (!mesh_dst->key)
 +              return;
 +
 +      tot = CustomData_number_of_layers(&mesh_src->vdata, CD_SHAPEKEY);
 +      for (i = 0; i < tot; i++) {
 +              CustomDataLayer *layer = &mesh_src->vdata.layers[CustomData_get_layer_index_n(&mesh_src->vdata, CD_SHAPEKEY, i)];
 +              float (*cos)[3], (*kbcos)[3];
 +
 +              for (kb = mesh_dst->key->block.first; kb; kb = kb->next) {
 +                      if (kb->uid == layer->uid)
 +                              break;
 +              }
 +
 +              if (!kb) {
 +                      kb = BKE_keyblock_add(mesh_dst->key, layer->name);
 +                      kb->uid = layer->uid;
 +              }
 +
 +              if (kb->data)
 +                      MEM_freeN(kb->data);
 +
 +              cos = CustomData_get_layer_n(&mesh_src->vdata, CD_SHAPEKEY, i);
 +              kb->totelem = mesh_src->totvert;
 +
 +              kb->data = kbcos = MEM_malloc_arrayN(kb->totelem, 3 * sizeof(float), __func__);
 +              if (kb->uid == actshape_uid) {
 +                      MVert *mvert = mesh_src->mvert;
 +
 +                      for (j = 0; j < mesh_src->totvert; j++, kbcos++, mvert++) {
 +                              copy_v3_v3(*kbcos, mvert->co);
 +                      }
 +              }
 +              else {
 +                      for (j = 0; j < kb->totelem; j++, cos++, kbcos++) {
 +                              copy_v3_v3(*kbcos, *cos);
 +                      }
 +              }
 +      }
 +
 +      for (kb = mesh_dst->key->block.first; kb; kb = kb->next) {
 +              if (kb->totelem != mesh_src->totvert) {
 +                      if (kb->data)
 +                              MEM_freeN(kb->data);
 +
 +                      kb->totelem = mesh_src->totvert;
 +                      kb->data = MEM_calloc_arrayN(kb->totelem, 3 * sizeof(float), __func__);
 +                      fprintf(stderr, "%s: lost a shapekey layer: '%s'! (bmesh internal error)\n", __func__, kb->name);
 +              }
 +      }
 +}
 +
 +
 +/* This is a Mesh-based copy of DM_to_mesh() */
 +void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, Mesh *mesh_dst, Object *ob, CustomDataMask mask, bool take_ownership)
 +{
 +      /* mesh_src might depend on mesh_dst, so we need to do everything with a local copy */
 +      /* TODO(Sybren): the above claim came from DM_to_mesh(); check whether it is still true with Mesh */
 +      Mesh tmp = *mesh_dst;
 +      int totvert, totedge /*, totface */ /* UNUSED */, totloop, totpoly;
 +      int did_shapekeys = 0;
 +      eCDAllocType alloctype = CD_DUPLICATE;
 +
 +      if (take_ownership /* && dm->type == DM_TYPE_CDDM && dm->needsFree */) {
 +              bool has_any_referenced_layers =
 +                      CustomData_has_referenced(&mesh_src->vdata) ||
 +                      CustomData_has_referenced(&mesh_src->edata) ||
 +                      CustomData_has_referenced(&mesh_src->ldata) ||
 +                      CustomData_has_referenced(&mesh_src->fdata) ||
 +                      CustomData_has_referenced(&mesh_src->pdata);
 +              if (!has_any_referenced_layers) {
 +                      alloctype = CD_ASSIGN;
 +              }
 +      }
 +      CustomData_reset(&tmp.vdata);
 +      CustomData_reset(&tmp.edata);
 +      CustomData_reset(&tmp.fdata);
 +      CustomData_reset(&tmp.ldata);
 +      CustomData_reset(&tmp.pdata);
 +
 +      BKE_mesh_ensure_normals(mesh_src);
 +
 +      totvert = tmp.totvert = mesh_src->totvert;
 +      totedge = tmp.totedge = mesh_src->totedge;
 +      totloop = tmp.totloop = mesh_src->totloop;
 +      totpoly = tmp.totpoly = mesh_src->totpoly;
 +      tmp.totface = 0;
 +
 +      CustomData_copy(&mesh_src->vdata, &tmp.vdata, mask, alloctype, totvert);
 +      CustomData_copy(&mesh_src->edata, &tmp.edata, mask, alloctype, totedge);
 +      CustomData_copy(&mesh_src->ldata, &tmp.ldata, mask, alloctype, totloop);
 +      CustomData_copy(&mesh_src->pdata, &tmp.pdata, mask, alloctype, totpoly);
 +      tmp.cd_flag = mesh_src->cd_flag;
 +      tmp.runtime.deformed_only = mesh_src->runtime.deformed_only;
 +
 +      if (CustomData_has_layer(&mesh_src->vdata, CD_SHAPEKEY)) {
 +              KeyBlock *kb;
 +              int uid;
 +
 +              if (ob) {
 +                      kb = BLI_findlink(&mesh_dst->key->block, ob->shapenr - 1);
 +                      if (kb) {
 +                              uid = kb->uid;
 +                      }
 +                      else {
 +                              printf("%s: error - could not find active shapekey %d!\n",
 +                                     __func__, ob->shapenr - 1);
 +
 +                              uid = INT_MAX;
 +                      }
 +              }
 +              else {
 +                      /* if no object, set to INT_MAX so we don't mess up any shapekey layers */
 +                      uid = INT_MAX;
 +              }
 +
 +              shapekey_layers_to_keyblocks(mesh_src, mesh_dst, uid);
 +              did_shapekeys = 1;
 +      }
 +
 +      /* copy texture space */
 +      if (ob) {
 +              BKE_mesh_texspace_copy_from_object(&tmp, ob);
 +      }
 +
 +      /* not all DerivedMeshes store their verts/edges/faces in CustomData, so
 +       * we set them here in case they are missing */
 +      /* TODO(Sybren): we could probably replace CD_ASSIGN with alloctype and always directly pass mesh_src->mxxx,
 +       * instead of using a ternary operator. */
 +      if (!CustomData_has_layer(&tmp.vdata, CD_MVERT)) {
-               CustomData_add_layer(&tmp.edata, CD_MEDGE, CD_ASSIGN,
-                                    (alloctype == CD_ASSIGN) ? mesh_src->medge : MEM_dupallocN(mesh_src->medge),
-                                    totedge);
++              CustomData_add_layer(
++                      &tmp.vdata, CD_MVERT, CD_ASSIGN,
++                      (alloctype == CD_ASSIGN) ? mesh_src->mvert : MEM_dupallocN(mesh_src->mvert),
++                      totvert);
 +      }
 +      if (!CustomData_has_layer(&tmp.edata, CD_MEDGE)) {
++              CustomData_add_layer(
++                      &tmp.edata, CD_MEDGE, CD_ASSIGN,
++                      (alloctype == CD_ASSIGN) ? mesh_src->medge : MEM_dupallocN(mesh_src->medge),
++                      totedge);
 +      }
 +      if (!CustomData_has_layer(&tmp.pdata, CD_MPOLY)) {
 +              /* TODO(Sybren): assigment to tmp.mxxx is probably not necessary due to the
 +               * BKE_mesh_update_customdata_pointers() call below. */
 +              tmp.mloop = (alloctype == CD_ASSIGN) ? mesh_src->mloop : MEM_dupallocN(mesh_src->mloop);
 +              tmp.mpoly = (alloctype == CD_ASSIGN) ? mesh_src->mpoly : MEM_dupallocN(mesh_src->mpoly);
 +
 +              CustomData_add_layer(&tmp.ldata, CD_MLOOP, CD_ASSIGN, tmp.mloop, tmp.totloop);
 +              CustomData_add_layer(&tmp.pdata, CD_MPOLY, CD_ASSIGN, tmp.mpoly, tmp.totpoly);
 +      }
 +
 +      /* object had got displacement layer, should copy this layer to save sculpted data */
 +      /* NOTE: maybe some other layers should be copied? nazgul */
 +      if (CustomData_has_layer(&mesh_dst->ldata, CD_MDISPS)) {
 +              if (totloop == mesh_dst->totloop) {
 +                      MDisps *mdisps = CustomData_get_layer(&mesh_dst->ldata, CD_MDISPS);
 +                      CustomData_add_layer(&tmp.ldata, CD_MDISPS, alloctype, mdisps, totloop);
 +              }
 +      }
 +
 +      /* yes, must be before _and_ after tessellate */
 +      BKE_mesh_update_customdata_pointers(&tmp, false);
 +
 +      /* since 2.65 caller must do! */
 +      // BKE_mesh_tessface_calc(&tmp);
 +
 +      CustomData_free(&mesh_dst->vdata, mesh_dst->totvert);
 +      CustomData_free(&mesh_dst->edata, mesh_dst->totedge);
 +      CustomData_free(&mesh_dst->fdata, mesh_dst->totface);
 +      CustomData_free(&mesh_dst->ldata, mesh_dst->totloop);
 +      CustomData_free(&mesh_dst->pdata, mesh_dst->totpoly);
 +
 +      /* ok, this should now use new CD shapekey data,
 +       * which should be fed through the modifier
 +       * stack */
 +      if (tmp.totvert != mesh_dst->totvert && !did_shapekeys && mesh_dst->key) {
 +              printf("%s: YEEK! this should be recoded! Shape key loss!: ID '%s'\n", __func__, tmp.id.name);
 +              if (tmp.key && !(tmp.id.tag & LIB_TAG_NO_MAIN)) {
 +                      id_us_min(&tmp.key->id);
 +              }
 +              tmp.key = NULL;
 +      }
 +
 +      /* Clear selection history */
 +      MEM_SAFE_FREE(tmp.mselect);
 +      tmp.totselect = 0;
 +      BLI_assert(ELEM(tmp.bb, NULL, mesh_dst->bb));
 +      if (mesh_dst->bb) {
 +              MEM_freeN(mesh_dst->bb);
 +              tmp.bb = NULL;
 +      }
 +
 +      /* skip the listbase */
 +      MEMCPY_STRUCT_OFS(mesh_dst, &tmp, id.prev);
 +
 +      if (take_ownership) {
 +              if (alloctype == CD_ASSIGN) {
 +                      CustomData_free_typemask(&mesh_src->vdata, mesh_src->totvert, ~mask);
 +                      CustomData_free_typemask(&mesh_src->edata, mesh_src->totedge, ~mask);
 +                      CustomData_free_typemask(&mesh_src->ldata, mesh_src->totloop, ~mask);
 +                      CustomData_free_typemask(&mesh_src->pdata, mesh_src->totpoly, ~mask);
 +              }
 +              BKE_id_free(NULL, mesh_src);
 +      }
 +}
 +
 +/* This is a Mesh-based copy of DM_to_meshkey() */
 +void BKE_mesh_nomain_to_meshkey(Mesh *mesh_src, Mesh *mesh_dst, KeyBlock *kb)
 +{
 +      int a, totvert = mesh_src->totvert;
 +      float *fp;
 +      MVert *mvert;
 +
 +      if (totvert == 0 || mesh_dst->totvert == 0 || mesh_dst->totvert != totvert) {
 +              return;
 +      }
 +
 +      if (kb->data) MEM_freeN(kb->data);
 +      kb->data = MEM_malloc_arrayN(mesh_dst->key->elemsize, mesh_dst->totvert, "kb->data");
 +      kb->totelem = totvert;
 +
 +      fp = kb->data;
 +      mvert = mesh_src->mvert;
 +
 +      for (a = 0; a < kb->totelem; a++, fp += 3, mvert++) {
 +              copy_v3_v3(fp, mvert->co);
 +      }
 +}
index 38e0d79d78c5a1b1da3209958115bc41ad8d072e,279ab7775292934854bcc5bc64c63313a159f752..ac935bb7f81717c50d785bffe852f0b4b5ffba41
@@@ -88,20 -88,6 +88,20 @@@ static void mesh_calc_normals_vert_fall
        }
  }
  
-                   mesh->mvert, mesh->totvert,
-                   mesh->mloop, mesh->mpoly, mesh->totloop, mesh->totpoly, NULL,
-                   mesh->mface, mesh->totface, NULL, NULL,
-                   only_face_normals);
 +/* TODO(Sybren): we can probably rename this to BKE_mesh_calc_normals_mapping(),
 + * and remove the function of the same name below, as that one doesn't seem to be
 + * called anywhere. */
 +void BKE_mesh_calc_normals_mapping_simple(struct Mesh *mesh)
 +{
 +      const bool only_face_normals = CustomData_is_referenced_layer(&mesh->vdata, CD_MVERT);
 +
 +      BKE_mesh_calc_normals_mapping_ex(
++              mesh->mvert, mesh->totvert,
++              mesh->mloop, mesh->mpoly, mesh->totloop, mesh->totpoly, NULL,
++              mesh->mface, mesh->totface, NULL, NULL,
++              only_face_normals);
 +}
 +
  /* Calculate vertex and face normals, face normals are returned in *r_faceNors if non-NULL
   * and vertex normals are stored in actual mverts.
   */
@@@ -2494,8 -2631,9 +2502,9 @@@ void BKE_mesh_loops_to_mface_corners
   *
   * \note when mface is not NULL, mface[face_index].v4 is used to test quads, else, loopindices[face_index][3] is used.
   */
- void BKE_mesh_loops_to_tessdata(CustomData *fdata, CustomData *ldata, MFace *mface,
-                                 int *polyindices, unsigned int (*loopindices)[4], const int num_faces)
+ void BKE_mesh_loops_to_tessdata(
 -        CustomData *fdata, CustomData *ldata, CustomData *pdata, MFace *mface,
++        CustomData *fdata, CustomData *ldata, MFace *mface,
+         int *polyindices, unsigned int (*loopindices)[4], const int num_faces)
  {
        /* Note: performances are sub-optimal when we get a NULL mface, we could be ~25% quicker with dedicated code...
         *       Issue is, unless having two different functions with nearly the same code, there's not much ways to solve
@@@ -3147,12 -3294,13 +3158,13 @@@ void BKE_mesh_convert_mfaces_to_mpolys(
   */
  void BKE_mesh_do_versions_convert_mfaces_to_mpolys(Mesh *mesh)
  {
-       BKE_mesh_convert_mfaces_to_mpolys_ex(&mesh->id, &mesh->fdata, &mesh->ldata, &mesh->pdata,
-                                            mesh->totedge, mesh->totface, mesh->totloop, mesh->totpoly,
-                                            mesh->medge, mesh->mface,
-                                            &mesh->totloop, &mesh->totpoly, &mesh->mloop, &mesh->mpoly);
+       BKE_mesh_convert_mfaces_to_mpolys_ex(
+               &mesh->id, &mesh->fdata, &mesh->ldata, &mesh->pdata,
+               mesh->totedge, mesh->totface, mesh->totloop, mesh->totpoly,
+               mesh->medge, mesh->mface,
+               &mesh->totloop, &mesh->totpoly, &mesh->mloop, &mesh->mpoly);
  
 -      CustomData_bmesh_do_versions_update_active_layers(&mesh->fdata, &mesh->pdata, &mesh->ldata);
 +      CustomData_bmesh_do_versions_update_active_layers(&mesh->fdata, &mesh->ldata);
  
        BKE_mesh_update_customdata_pointers(mesh, true);
  }
index 27ba7e76fa366811dae4a2b9b052453735a11f4f,0000000000000000000000000000000000000000..d49ca507e024efd4acc2f3cec1ea7d0e958a7309
mode 100644,000000..100644
--- /dev/null
@@@ -1,684 -1,0 +1,685 @@@
-               BKE_mesh_vert_poly_map_create(&poly_map, &poly_map_mem,
-                                             mesh->mpoly, mesh->mloop,
-                                             totvert, totpoly, totloop);
 +/*
 + * ***** BEGIN GPL LICENSE BLOCK *****
 + *
 + * This program is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU General Public License
 + * as published by the Free Software Foundation; either version 2
 + * of the License, or (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 + *
 + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 + * All rights reserved.
 + *
 + * Contributor(s): Blender Foundation
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file blender/blenkernel/intern/mesh_merge.c
 + *  \ingroup bke
 + */
 +#include <string.h> // for memcpy
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "DNA_mesh_types.h"
 +#include "DNA_meshdata_types.h"
 +
 +#include "BLI_utildefines.h"
 +#include "BLI_utildefines_stack.h"
 +#include "BLI_edgehash.h"
 +#include "BLI_ghash.h"
 +
 +#include "BKE_customdata.h"
 +#include "BKE_library.h"
 +#include "BKE_mesh.h"
 +#include "BKE_mesh_mapping.h"
 +
 +
 +/**
 + * Poly compare with vtargetmap
 + * Function used by #BKE_mesh_merge_verts.
 + * The function compares poly_source after applying vtargetmap, with poly_target.
 + * The two polys are identical if they share the same vertices in the same order, or in reverse order,
 + * but starting position loopstart may be different.
 + * The function is called with direct_reverse=1 for same order (i.e. same normal),
 + * and may be called again with direct_reverse=-1 for reverse order.
 + * \return 1 if polys are identical,  0 if polys are different.
 + */
 +static int cddm_poly_compare(
 +        MLoop *mloop_array,
 +        MPoly *mpoly_source, MPoly *mpoly_target,
 +        const int *vtargetmap, const int direct_reverse)
 +{
 +      int vert_source, first_vert_source, vert_target;
 +      int i_loop_source;
 +      int i_loop_target, i_loop_target_start, i_loop_target_offset, i_loop_target_adjusted;
 +      bool compare_completed = false;
 +      bool same_loops = false;
 +
 +      MLoop *mloop_source, *mloop_target;
 +
 +      BLI_assert(direct_reverse == 1 || direct_reverse == -1);
 +
 +      i_loop_source = 0;
 +      mloop_source = mloop_array + mpoly_source->loopstart;
 +      vert_source = mloop_source->v;
 +
 +      if (vtargetmap[vert_source] != -1) {
 +              vert_source = vtargetmap[vert_source];
 +      }
 +      else {
 +              /* All source loop vertices should be mapped */
 +              BLI_assert(false);
 +      }
 +
 +      /* Find same vertex within mpoly_target's loops */
 +      mloop_target = mloop_array + mpoly_target->loopstart;
 +      for (i_loop_target = 0; i_loop_target < mpoly_target->totloop; i_loop_target++, mloop_target++) {
 +              if (mloop_target->v == vert_source) {
 +                      break;
 +              }
 +      }
 +
 +      /* If same vertex not found, then polys cannot be equal */
 +      if (i_loop_target >= mpoly_target->totloop) {
 +              return false;
 +      }
 +
 +      /* Now mloop_source and m_loop_target have one identical vertex */
 +      /* mloop_source is at position 0, while m_loop_target has advanced to find identical vertex */
 +      /* Go around the loop and check that all vertices match in same order */
 +      /* Skipping source loops when consecutive source vertices are mapped to same target vertex */
 +
 +      i_loop_target_start = i_loop_target;
 +      i_loop_target_offset = 0;
 +      first_vert_source = vert_source;
 +
 +      compare_completed = false;
 +      same_loops = false;
 +
 +      while (!compare_completed) {
 +
 +              vert_target = mloop_target->v;
 +
 +              /* First advance i_loop_source, until it points to different vertex, after mapping applied */
 +              do {
 +                      i_loop_source++;
 +
 +                      if (i_loop_source == mpoly_source->totloop) {
 +                              /* End of loops for source, must match end of loop for target.  */
 +                              if (i_loop_target_offset == mpoly_target->totloop - 1) {
 +                                      compare_completed = true;
 +                                      same_loops = true;
 +                                      break;  /* Polys are identical */
 +                              }
 +                              else {
 +                                      compare_completed = true;
 +                                      same_loops = false;
 +                                      break;  /* Polys are different */
 +                              }
 +                      }
 +
 +                      mloop_source++;
 +                      vert_source = mloop_source->v;
 +
 +                      if (vtargetmap[vert_source] != -1) {
 +                              vert_source = vtargetmap[vert_source];
 +                      }
 +                      else {
 +                              /* All source loop vertices should be mapped */
 +                              BLI_assert(false);
 +                      }
 +
 +              } while (vert_source == vert_target);
 +
 +              if (compare_completed) {
 +                      break;
 +              }
 +
 +              /* Now advance i_loop_target as well */
 +              i_loop_target_offset++;
 +
 +              if (i_loop_target_offset == mpoly_target->totloop) {
 +                      /* End of loops for target only, that means no match */
 +                      /* except if all remaining source vertices are mapped to first target */
 +                      for (; i_loop_source < mpoly_source->totloop; i_loop_source++, mloop_source++) {
 +                              vert_source = vtargetmap[mloop_source->v];
 +                              if (vert_source != first_vert_source) {
 +                                      compare_completed = true;
 +                                      same_loops = false;
 +                                      break;
 +                              }
 +                      }
 +                      if (!compare_completed) {
 +                              same_loops = true;
 +                      }
 +                      break;
 +              }
 +
 +              /* Adjust i_loop_target for cycling around and for direct/reverse order defined by delta = +1 or -1 */
 +              i_loop_target_adjusted = (i_loop_target_start + direct_reverse * i_loop_target_offset) % mpoly_target->totloop;
 +              if (i_loop_target_adjusted < 0) {
 +                      i_loop_target_adjusted += mpoly_target->totloop;
 +              }
 +              mloop_target = mloop_array + mpoly_target->loopstart + i_loop_target_adjusted;
 +              vert_target = mloop_target->v;
 +
 +              if (vert_target != vert_source) {
 +                      same_loops = false;  /* Polys are different */
 +                      break;
 +              }
 +      }
 +      return same_loops;
 +}
 +
 +
 +/* Utility stuff for using GHash with polys, used by vertex merging. */
 +
 +typedef struct PolyKey {
 +      int poly_index;   /* index of the MPoly within the derived mesh */
 +      int totloops;     /* number of loops in the poly */
 +      unsigned int hash_sum;  /* Sum of all vertices indices */
 +      unsigned int hash_xor;  /* Xor of all vertices indices */
 +} PolyKey;
 +
 +
 +static unsigned int poly_gset_hash_fn(const void *key)
 +{
 +      const PolyKey *pk = key;
 +      return pk->hash_sum;
 +}
 +
 +static bool poly_gset_compare_fn(const void *k1, const void *k2)
 +{
 +      const PolyKey *pk1 = k1;
 +      const PolyKey *pk2 = k2;
 +      if ((pk1->hash_sum == pk2->hash_sum) &&
 +          (pk1->hash_xor == pk2->hash_xor) &&
 +          (pk1->totloops == pk2->totloops))
 +      {
 +              /* Equality - note that this does not mean equality of polys */
 +              return false;
 +      }
 +      else {
 +              return true;
 +      }
 +}
 +
 +/**
 + * Merge Verts
 + *
 + * This frees the given mesh and returns a new mesh.
 + *
 + * \param vtargetmap  The table that maps vertices to target vertices.  a value of -1
 + * indicates a vertex is a target, and is to be kept.
 + * This array is aligned with 'mesh->totvert'
 + * \warning \a vtargetmap must **not** contain any chained mapping (v1 -> v2 -> v3 etc.), this is not supported
 + * and will likely generate corrupted geometry.
 + *
 + * \param tot_vtargetmap  The number of non '-1' values in vtargetmap. (not the size)
 + *
 + * \param merge_mode enum with two modes.
 + * - #MESH_MERGE_VERTS_DUMP_IF_MAPPED
 + * When called by the Mirror Modifier,
 + * In this mode it skips any faces that have all vertices merged (to avoid creating pairs
 + * of faces sharing the same set of vertices)
 + * - #MESH_MERGE_VERTS_DUMP_IF_EQUAL
 + * When called by the Array Modifier,
 + * In this mode, faces where all vertices are merged are double-checked,
 + * to see whether all target vertices actually make up a poly already.
 + * Indeed it could be that all of a poly's vertices are merged,
 + * but merged to vertices that do not make up a single poly,
 + * in which case the original poly should not be dumped.
 + * Actually this later behavior could apply to the Mirror Modifier as well, but the additional checks are
 + * costly and not necessary in the case of mirror, because each vertex is only merged to its own mirror.
 + *
 + * \note #BKE_mesh_recalc_tessellation has to run on the returned DM if you want to access tessfaces.
 + */
 +Mesh *BKE_mesh_merge_verts(Mesh *mesh, const int *vtargetmap, const int tot_vtargetmap, const int merge_mode)
 +{
 +      /* This was commented out back in 2013, see commit f45d8827bafe6b9eaf9de42f4054e9d84a21955d. */
 +// #define USE_LOOPS
 +
 +      Mesh *result = NULL;
 +
 +      const int totvert = mesh->totvert;
 +      const int totedge = mesh->totedge;
 +      const int totloop = mesh->totloop;
 +      const int totpoly = mesh->totpoly;
 +
 +      const int totvert_final = totvert - tot_vtargetmap;
 +
 +      MVert *mv, *mvert = MEM_malloc_arrayN(totvert_final, sizeof(*mvert), __func__);
 +      int *oldv         = MEM_malloc_arrayN(totvert_final, sizeof(*oldv), __func__);
 +      int *newv         = MEM_malloc_arrayN(totvert, sizeof(*newv), __func__);
 +      STACK_DECLARE(mvert);
 +      STACK_DECLARE(oldv);
 +
 +      /* Note: create (totedge + totloop) elements because partially invalid polys due to merge may require
 +       * generating new edges, and while in 99% cases we'll still end with less final edges than totedge,
 +       * cases can be forged that would end requiring more... */
 +      MEdge *med, *medge = MEM_malloc_arrayN((totedge + totloop), sizeof(*medge), __func__);
 +      int *olde          = MEM_malloc_arrayN((totedge + totloop), sizeof(*olde), __func__);
 +      int *newe          = MEM_malloc_arrayN((totedge + totloop), sizeof(*newe), __func__);
 +      STACK_DECLARE(medge);
 +      STACK_DECLARE(olde);
 +
 +      MLoop *ml, *mloop = MEM_malloc_arrayN(totloop, sizeof(*mloop), __func__);
 +      int *oldl         = MEM_malloc_arrayN(totloop, sizeof(*oldl), __func__);
 +#ifdef USE_LOOPS
 +      int *newl          = MEM_malloc_arrayN(totloop, sizeof(*newl), __func__);
 +#endif
 +      STACK_DECLARE(mloop);
 +      STACK_DECLARE(oldl);
 +
 +      MPoly *mp, *mpoly = MEM_malloc_arrayN(totpoly, sizeof(*medge), __func__);
 +      int *oldp         = MEM_malloc_arrayN(totpoly, sizeof(*oldp), __func__);
 +      STACK_DECLARE(mpoly);
 +      STACK_DECLARE(oldp);
 +
 +      EdgeHash *ehash = BLI_edgehash_new_ex(__func__, totedge);
 +
 +      int i, j, c;
 +
 +      PolyKey *poly_keys;
 +      GSet *poly_gset = NULL;
 +      MeshElemMap *poly_map = NULL;
 +      int *poly_map_mem = NULL;
 +
 +      STACK_INIT(oldv, totvert_final);
 +      STACK_INIT(olde, totedge);
 +      STACK_INIT(oldl, totloop);
 +      STACK_INIT(oldp, totpoly);
 +
 +      STACK_INIT(mvert, totvert_final);
 +      STACK_INIT(medge, totedge);
 +      STACK_INIT(mloop, totloop);
 +      STACK_INIT(mpoly, totpoly);
 +
 +      /* fill newv with destination vertex indices */
 +      mv = mesh->mvert;
 +      c = 0;
 +      for (i = 0; i < totvert; i++, mv++) {
 +              if (vtargetmap[i] == -1) {
 +                      STACK_PUSH(oldv, i);
 +                      STACK_PUSH(mvert, *mv);
 +                      newv[i] = c++;
 +              }
 +              else {
 +                      /* dummy value */
 +                      newv[i] = 0;
 +              }
 +      }
 +
 +      /* now link target vertices to destination indices */
 +      for (i = 0; i < totvert; i++) {
 +              if (vtargetmap[i] != -1) {
 +                      newv[i] = newv[vtargetmap[i]];
 +              }
 +      }
 +
 +      /* Don't remap vertices in cddm->mloop, because we need to know the original
 +       * indices in order to skip faces with all vertices merged.
 +       * The "update loop indices..." section further down remaps vertices in mloop.
 +       */
 +
 +      /* now go through and fix edges and faces */
 +      med = mesh->medge;
 +      c = 0;
 +      for (i = 0; i < totedge; i++, med++) {
 +              const unsigned int v1 = (vtargetmap[med->v1] != -1) ? vtargetmap[med->v1] : med->v1;
 +              const unsigned int v2 = (vtargetmap[med->v2] != -1) ? vtargetmap[med->v2] : med->v2;
 +              if (LIKELY(v1 != v2)) {
 +                      void **val_p;
 +
 +                      if (BLI_edgehash_ensure_p(ehash, v1, v2, &val_p)) {
 +                              newe[i] = GET_INT_FROM_POINTER(*val_p);
 +                      }
 +                      else {
 +                              STACK_PUSH(olde, i);
 +                              STACK_PUSH(medge, *med);
 +                              newe[i] = c;
 +                              *val_p = SET_INT_IN_POINTER(c);
 +                              c++;
 +                      }
 +              }
 +              else {
 +                      newe[i] = -1;
 +              }
 +      }
 +
 +      if (merge_mode == MESH_MERGE_VERTS_DUMP_IF_EQUAL) {
 +              /* In this mode, we need to determine,  whenever a poly' vertices are all mapped */
 +              /* if the targets already make up a poly, in which case the new poly is dropped */
 +              /* This poly equality check is rather complex.   We use a BLI_ghash to speed it up with a first level check */
 +              PolyKey *mpgh;
 +              poly_keys = MEM_malloc_arrayN(totpoly, sizeof(PolyKey), __func__);
 +              poly_gset = BLI_gset_new_ex(poly_gset_hash_fn, poly_gset_compare_fn, __func__, totpoly);
 +              /* Duplicates allowed because our compare function is not pure equality */
 +              BLI_gset_flag_set(poly_gset, GHASH_FLAG_ALLOW_DUPES);
 +
 +              mp = mesh->mpoly;
 +              mpgh = poly_keys;
 +              for (i = 0; i < totpoly; i++, mp++, mpgh++) {
 +                      mpgh->poly_index = i;
 +                      mpgh->totloops = mp->totloop;
 +                      ml = mesh->mloop + mp->loopstart;
 +                      mpgh->hash_sum = mpgh->hash_xor = 0;
 +                      for (j = 0; j < mp->totloop; j++, ml++) {
 +                              mpgh->hash_sum += ml->v;
 +                              mpgh->hash_xor ^= ml->v;
 +                      }
 +                      BLI_gset_insert(poly_gset, mpgh);
 +              }
 +
 +              /* Can we optimise by reusing an old pmap ?  How do we know an old pmap is stale ?  */
 +              /* When called by MOD_array.c, the cddm has just been created, so it has no valid pmap.   */
++              BKE_mesh_vert_poly_map_create(
++                      &poly_map, &poly_map_mem,
++                      mesh->mpoly, mesh->mloop,
++                      totvert, totpoly, totloop);
 +      }  /* done preparing for fast poly compare */
 +
 +
 +      mp = mesh->mpoly;
 +      mv = mesh->mvert;
 +      for (i = 0; i < totpoly; i++, mp++) {
 +              MPoly *mp_new;
 +
 +              ml = mesh->mloop + mp->loopstart;
 +
 +              /* check faces with all vertices merged */
 +              bool all_vertices_merged = true;
 +
 +              for (j = 0; j < mp->totloop; j++, ml++) {
 +                      if (vtargetmap[ml->v] == -1) {
 +                              all_vertices_merged = false;
 +                              /* This will be used to check for poly using several time the same vert. */
 +                              mv[ml->v].flag &= ~ME_VERT_TMP_TAG;
 +                      }
 +                      else {
 +                              /* This will be used to check for poly using several time the same vert. */
 +                              mv[vtargetmap[ml->v]].flag &= ~ME_VERT_TMP_TAG;
 +                      }
 +              }
 +
 +              if (UNLIKELY(all_vertices_merged)) {
 +                      if (merge_mode == MESH_MERGE_VERTS_DUMP_IF_MAPPED) {
 +                              /* In this mode, all vertices merged is enough to dump face */
 +                              continue;
 +                      }
 +                      else if (merge_mode == MESH_MERGE_VERTS_DUMP_IF_EQUAL) {
 +                              /* Additional condition for face dump:  target vertices must make up an identical face */
 +                              /* The test has 2 steps:  (1) first step is fast ghash lookup, but not failproof       */
 +                              /*                        (2) second step is thorough but more costly poly compare     */
 +                              int i_poly, v_target;
 +                              bool found = false;
 +                              PolyKey pkey;
 +
 +                              /* Use poly_gset for fast (although not 100% certain) identification of same poly */
 +                              /* First, make up a poly_summary structure */
 +                              ml = mesh->mloop + mp->loopstart;
 +                              pkey.hash_sum = pkey.hash_xor = 0;
 +                              pkey.totloops = 0;
 +                              for (j = 0; j < mp->totloop; j++, ml++) {
 +                                      v_target = vtargetmap[ml->v];   /* Cannot be -1, they are all mapped */
 +                                      pkey.hash_sum += v_target;
 +                                      pkey.hash_xor ^= v_target;
 +                                      pkey.totloops++;
 +                              }
 +                              if (BLI_gset_haskey(poly_gset, &pkey)) {
 +
 +                                      /* There might be a poly that matches this one.
 +                                       * We could just leave it there and say there is, and do a "continue".
 +                                       * ... but we are checking whether there is an exact poly match.
 +                                       * It's not so costly in terms of CPU since it's very rare, just a lot of complex code.
 +                                       */
 +
 +                                      /* Consider current loop again */
 +                                      ml = mesh->mloop + mp->loopstart;
 +                                      /* Consider the target of the loop's first vert */
 +                                      v_target = vtargetmap[ml->v];
 +                                      /* Now see if v_target belongs to a poly that shares all vertices with source poly,
 +                                       * in same order, or reverse order */
 +
 +                                      for (i_poly = 0; i_poly < poly_map[v_target].count; i_poly++) {
 +                                              MPoly *target_poly = mesh->mpoly + *(poly_map[v_target].indices + i_poly);
 +
 +                                              if (cddm_poly_compare(mesh->mloop, mp, target_poly, vtargetmap, +1) ||
 +                                                  cddm_poly_compare(mesh->mloop, mp, target_poly, vtargetmap, -1))
 +                                              {
 +                                                      found = true;
 +                                                      break;
 +                                              }
 +                                      }
 +                                      if (found) {
 +                                              /* Current poly's vertices are mapped to a poly that is strictly identical */
 +                                              /* Current poly is dumped */
 +                                              continue;
 +                                      }
 +                              }
 +                      }
 +              }
 +
 +
 +              /* Here either the poly's vertices were not all merged
 +               * or they were all merged, but targets do not make up an identical poly,
 +               * the poly is retained.
 +               */
 +              ml = mesh->mloop + mp->loopstart;
 +
 +              c = 0;
 +              MLoop *last_valid_ml = NULL;
 +              MLoop *first_valid_ml = NULL;
 +              bool need_edge_from_last_valid_ml = false;
 +              bool need_edge_to_first_valid_ml = false;
 +              int created_edges = 0;
 +              for (j = 0; j < mp->totloop; j++, ml++) {
 +                      const uint mlv = (vtargetmap[ml->v] != -1) ? vtargetmap[ml->v] : ml->v;
 +#ifndef NDEBUG
 +                      {
 +                              MLoop *next_ml = mesh->mloop + mp->loopstart + ((j + 1) % mp->totloop);
 +                              uint next_mlv = (vtargetmap[next_ml->v] != -1) ? vtargetmap[next_ml->v] : next_ml->v;
 +                              med = mesh->medge + ml->e;
 +                              uint v1 = (vtargetmap[med->v1] != -1) ? vtargetmap[med->v1] : med->v1;
 +                              uint v2 = (vtargetmap[med->v2] != -1) ? vtargetmap[med->v2] : med->v2;
 +                              BLI_assert((mlv == v1 && next_mlv == v2) || (mlv == v2 && next_mlv == v1));
 +                      }
 +#endif
 +                      /* A loop is only valid if its matching edge is, and it's not reusing a vertex already used by this poly. */
 +                      if (LIKELY((newe[ml->e] != -1) && ((mv[mlv].flag & ME_VERT_TMP_TAG) == 0))) {
 +                              mv[mlv].flag |= ME_VERT_TMP_TAG;
 +
 +                              if (UNLIKELY(last_valid_ml != NULL && need_edge_from_last_valid_ml)) {
 +                                      /* We need to create a new edge between last valid loop and this one! */
 +                                      void **val_p;
 +
 +                                      uint v1 = (vtargetmap[last_valid_ml->v] != -1) ? vtargetmap[last_valid_ml->v] : last_valid_ml->v;
 +                                      uint v2 = mlv;
 +                                      BLI_assert(v1 != v2);
 +                                      if (BLI_edgehash_ensure_p(ehash, v1, v2, &val_p)) {
 +                                              last_valid_ml->e = GET_INT_FROM_POINTER(*val_p);
 +                                      }
 +                                      else {
 +                                              const int new_eidx = STACK_SIZE(medge);
 +                                              STACK_PUSH(olde, olde[last_valid_ml->e]);
 +                                              STACK_PUSH(medge, mesh->medge[last_valid_ml->e]);
 +                                              medge[new_eidx].v1 = last_valid_ml->v;
 +                                              medge[new_eidx].v2 = ml->v;
 +                                              /* DO NOT change newe mapping, could break actual values due to some deleted original edges. */
 +                                              *val_p = SET_INT_IN_POINTER(new_eidx);
 +                                              created_edges++;
 +
 +                                              last_valid_ml->e = new_eidx;
 +                                      }
 +                                      need_edge_from_last_valid_ml = false;
 +                              }
 +
 +#ifdef USE_LOOPS
 +                              newl[j + mp->loopstart] = STACK_SIZE(mloop);
 +#endif
 +                              STACK_PUSH(oldl, j + mp->loopstart);
 +                              last_valid_ml = STACK_PUSH_RET_PTR(mloop);
 +                              *last_valid_ml = *ml;
 +                              if (first_valid_ml == NULL) {
 +                                      first_valid_ml = last_valid_ml;
 +                              }
 +                              c++;
 +
 +                              /* We absolutely HAVE to handle edge index remapping here, otherwise potential newly created edges
 +                               * in that part of code make remapping later totally unreliable. */
 +                              BLI_assert(newe[ml->e] != -1);
 +                              last_valid_ml->e = newe[ml->e];
 +                      }
 +                      else {
 +                              if (last_valid_ml != NULL) {
 +                                      need_edge_from_last_valid_ml = true;
 +                              }
 +                              else {
 +                                      need_edge_to_first_valid_ml = true;
 +                              }
 +                      }
 +              }
 +              if (UNLIKELY(last_valid_ml != NULL && !ELEM(first_valid_ml, NULL, last_valid_ml) &&
 +                           (need_edge_to_first_valid_ml || need_edge_from_last_valid_ml)))
 +              {
 +                      /* We need to create a new edge between last valid loop and first valid one! */
 +                      void **val_p;
 +
 +                      uint v1 = (vtargetmap[last_valid_ml->v] != -1) ? vtargetmap[last_valid_ml->v] : last_valid_ml->v;
 +                      uint v2 = (vtargetmap[first_valid_ml->v] != -1) ? vtargetmap[first_valid_ml->v] : first_valid_ml->v;
 +                      BLI_assert(v1 != v2);
 +                      if (BLI_edgehash_ensure_p(ehash, v1, v2, &val_p)) {
 +                              last_valid_ml->e = GET_INT_FROM_POINTER(*val_p);
 +                      }
 +                      else {
 +                              const int new_eidx = STACK_SIZE(medge);
 +                              STACK_PUSH(olde, olde[last_valid_ml->e]);
 +                              STACK_PUSH(medge, mesh->medge[last_valid_ml->e]);
 +                              medge[new_eidx].v1 = last_valid_ml->v;
 +                              medge[new_eidx].v2 = first_valid_ml->v;
 +                              /* DO NOT change newe mapping, could break actual values due to some deleted original edges. */
 +                              *val_p = SET_INT_IN_POINTER(new_eidx);
 +                              created_edges++;
 +
 +                              last_valid_ml->e = new_eidx;
 +                      }
 +                      need_edge_to_first_valid_ml = need_edge_from_last_valid_ml = false;
 +              }
 +
 +              if (UNLIKELY(c == 0)) {
 +                      BLI_assert(created_edges == 0);
 +                      continue;
 +              }
 +              else if (UNLIKELY(c < 3)) {
 +                      STACK_DISCARD(oldl, c);
 +                      STACK_DISCARD(mloop, c);
 +                      if (created_edges > 0) {
 +                              for (j = STACK_SIZE(medge) - created_edges; j < STACK_SIZE(medge); j++) {
 +                                      BLI_edgehash_remove(ehash, medge[j].v1, medge[j].v2, NULL);
 +                              }
 +                              STACK_DISCARD(olde, created_edges);
 +                              STACK_DISCARD(medge, created_edges);
 +                      }
 +                      continue;
 +              }
 +
 +              mp_new = STACK_PUSH_RET_PTR(mpoly);
 +              *mp_new = *mp;
 +              mp_new->totloop = c;
 +              BLI_assert(mp_new->totloop >= 3);
 +              mp_new->loopstart = STACK_SIZE(mloop) - c;
 +
 +              STACK_PUSH(oldp, i);
 +      }  /* end of the loop that tests polys   */
 +
 +
 +      if (poly_gset) {
 +              // printf("hash quality %.6f\n", BLI_gset_calc_quality(poly_gset));
 +
 +              BLI_gset_free(poly_gset, NULL);
 +              MEM_freeN(poly_keys);
 +      }
 +
 +      /*create new cddm*/
 +      result = BKE_mesh_new_nomain_from_template(
 +              mesh, STACK_SIZE(mvert), STACK_SIZE(medge), 0, STACK_SIZE(mloop), STACK_SIZE(mpoly));
 +
 +      /*update edge indices and copy customdata*/
 +      med = medge;
 +      for (i = 0; i < result->totedge; i++, med++) {
 +              BLI_assert(newv[med->v1] != -1);
 +              med->v1 = newv[med->v1];
 +              BLI_assert(newv[med->v2] != -1);
 +              med->v2 = newv[med->v2];
 +
 +              /* Can happen in case vtargetmap contains some double chains, we do not support that. */
 +              BLI_assert(med->v1 != med->v2);
 +
 +              CustomData_copy_data(&mesh->edata, &result->edata, olde[i], i, 1);
 +      }
 +
 +      /*update loop indices and copy customdata*/
 +      ml = mloop;
 +      for (i = 0; i < result->totloop; i++, ml++) {
 +              /* Edge remapping has already be done in main loop handling part above. */
 +              BLI_assert(newv[ml->v] != -1);
 +              ml->v = newv[ml->v];
 +
 +              CustomData_copy_data(&mesh->ldata, &result->ldata, oldl[i], i, 1);
 +      }
 +
 +      /*copy vertex customdata*/
 +      mv = mvert;
 +      for (i = 0; i < result->totvert; i++, mv++) {
 +              CustomData_copy_data(&mesh->vdata, &result->vdata, oldv[i], i, 1);
 +      }
 +
 +      /*copy poly customdata*/
 +      mp = mpoly;
 +      for (i = 0; i < result->totpoly; i++, mp++) {
 +              CustomData_copy_data(&mesh->pdata, &result->pdata, oldp[i], i, 1);
 +      }
 +
 +      /*copy over data.  CustomData_add_layer can do this, need to look it up.*/
 +      memcpy(result->mvert, mvert, sizeof(MVert) * STACK_SIZE(mvert));
 +      memcpy(result->medge, medge, sizeof(MEdge) * STACK_SIZE(medge));
 +      memcpy(result->mloop, mloop, sizeof(MLoop) * STACK_SIZE(mloop));
 +      memcpy(result->mpoly, mpoly, sizeof(MPoly) * STACK_SIZE(mpoly));
 +
 +      MEM_freeN(mvert);
 +      MEM_freeN(medge);
 +      MEM_freeN(mloop);
 +      MEM_freeN(mpoly);
 +
 +      MEM_freeN(newv);
 +      MEM_freeN(newe);
 +#ifdef USE_LOOPS
 +      MEM_freeN(newl);
 +#endif
 +
 +      MEM_freeN(oldv);
 +      MEM_freeN(olde);
 +      MEM_freeN(oldl);
 +      MEM_freeN(oldp);
 +
 +      BLI_edgehash_free(ehash, NULL);
 +
 +      if (poly_map != NULL)
 +              MEM_freeN(poly_map);
 +      if (poly_map_mem != NULL)
 +              MEM_freeN(poly_map_mem);
 +
 +      BKE_id_free(NULL, mesh);
 +
 +      return result;
 +}
index c882cc0a7faedf03248516986638fc1b0e80d746,77aefc04f5ff17b82687314debd69c3713601cec..bf98b8040e3c9b520c747aa0067585f1c6367e02
@@@ -666,10 -669,13 +666,11 @@@ void BKE_mesh_remap_calc_edges_from_mes
                                v_dst_to_src_map[i].hit_dist = -1.0f;
                        }
  
-                       BKE_mesh_vert_edge_map_create(&vert_to_edge_src_map, &vert_to_edge_src_map_mem,
-                                                     edges_src, num_verts_src, num_edges_src);
+                       BKE_mesh_vert_edge_map_create(
+                               &vert_to_edge_src_map, &vert_to_edge_src_map_mem,
+                               edges_src, num_verts_src, num_edges_src);
  
 -                      dm_src->getVertCos(dm_src, vcos_src);
 -
 -                      bvhtree_from_mesh_get(&treedata, dm_src, BVHTREE_FROM_VERTS, 2);
 +                      BKE_bvhtree_from_mesh_get(&treedata, me_src, BVHTREE_FROM_VERTS, 2);
                        nearest.index = -1;
  
                        for (i = 0; i < numedges_dst; i++) {
@@@ -1209,9 -1222,10 +1212,10 @@@ void BKE_mesh_remap_calc_loops_from_mes
                                        poly_nors_dst = CustomData_add_layer(pdata_dst, CD_NORMAL, CD_CALLOC, NULL, numpolys_dst);
                                        CustomData_set_layer_flag(pdata_dst, CD_NORMAL, CD_FLAG_TEMPORARY);
                                }
 -                              if (dirty_nors_dst) {
 +                              if (dirty_nors_dst || do_poly_nors_dst) {
-                                       BKE_mesh_calc_normals_poly(verts_dst, NULL, numverts_dst, loops_dst, polys_dst,
-                                                                  numloops_dst, numpolys_dst, poly_nors_dst, true);
+                                       BKE_mesh_calc_normals_poly(
+                                               verts_dst, NULL, numverts_dst, loops_dst, polys_dst,
+                                               numloops_dst, numpolys_dst, poly_nors_dst, true);
                                }
                        }
                        if (need_lnors_dst) {
  
                                /* Cache poly nors into a temp CDLayer. */
                                loop_nors_dst = CustomData_get_layer(ldata_dst, CD_NORMAL);
 -                              if (dirty_nors_dst || !loop_nors_dst) {
 -                                      if (!loop_nors_dst) {
 -                                              loop_nors_dst = CustomData_add_layer(ldata_dst, CD_NORMAL, CD_CALLOC, NULL, numloops_dst);
 -                                              CustomData_set_layer_flag(ldata_dst, CD_NORMAL, CD_FLAG_TEMPORARY);
 -                                      }
 +                              const bool do_loop_nors_dst = (loop_nors_dst == NULL);
 +                              if (!loop_nors_dst) {
 +                                      loop_nors_dst = CustomData_add_layer(ldata_dst, CD_NORMAL, CD_CALLOC, NULL, numloops_dst);
 +                                      CustomData_set_layer_flag(ldata_dst, CD_NORMAL, CD_FLAG_TEMPORARY);
 +                              }
 +                              if (dirty_nors_dst || do_loop_nors_dst) {
-                                       BKE_mesh_normals_loop_split(verts_dst, numverts_dst, edges_dst, numedges_dst,
-                                                                   loops_dst, loop_nors_dst, numloops_dst,
-                                                                   polys_dst, (const float (*)[3])poly_nors_dst, numpolys_dst,
-                                                                   use_split_nors_dst, split_angle_dst, NULL, custom_nors_dst, NULL);
+                                       BKE_mesh_normals_loop_split(
+                                               verts_dst, numverts_dst, edges_dst, numedges_dst,
+                                               loops_dst, loop_nors_dst, numloops_dst,
+                                               polys_dst, (const float (*)[3])poly_nors_dst, numpolys_dst,
+                                               use_split_nors_dst, split_angle_dst, NULL, custom_nors_dst, NULL);
                                }
                        }
                        if (need_pnors_src || need_lnors_src) {
                                                        }
                                                }
                                        }
-                                       bvhtree_from_mesh_verts_ex(&treedata[tindex], verts_src, num_verts_src, false,
-                                                                  verts_active, num_verts_active, 0.0, 2, 6);
 -                                      /* verts 'ownership' is transfered to treedata here, which will handle its freeing. */
+                                       bvhtree_from_mesh_verts_ex(
 -                                              &treedata[tindex], verts_src, num_verts_src, verts_allocated_src,
++                                              &treedata[tindex], verts_src, num_verts_src, false,
+                                               verts_active, num_verts_active, 0.0, 2, 6);
 -                                      if (verts_allocated_src) {
 -                                              verts_allocated_src = false;  /* Only 'give' our verts once, to first tree! */
 -                                      }
                                }
  
                                MEM_freeN(verts_active);
index 07c354559b85bc85fc73e19479c2b3882cc41f1e,0000000000000000000000000000000000000000..a3fc422ade7e0d41f06e9ea5643c169169b03788
mode 100644,000000..100644
--- /dev/null
@@@ -1,372 -1,0 +1,373 @@@
-                       BLI_dynstr_appendf(dynstr,
-                                          "        dict(name='%s', struct='%s', type=%d, ptr='%p', elem=%d, length=%d),\n",
-                                          name, structname, type, (const void *)pt, size, pt_size);
 +/*
 + * ***** BEGIN GPL LICENSE BLOCK *****
 + *
 + * This program is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU General Public License
 + * as published by the Free Software Foundation; either version 2
 + * of the License, or (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 + *
 + * The Original Code is Copyright (C) 2005 Blender Foundation.
 + * All rights reserved.
 + *
 + * The Original Code is: all of this file.
 + *
 + * Contributor(s): Blender Foundation.
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file blender/blenkernel/intern/mesh_runtime.c
 + *  \ingroup bke
 + */
 +
 +#include "atomic_ops.h"
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "DNA_mesh_types.h"
 +#include "DNA_meshdata_types.h"
 +#include "DNA_object_types.h"
 +
 +#include "BLI_math_geom.h"
 +#include "BLI_threads.h"
 +
 +#include "BKE_bvhutils.h"
 +#include "BKE_mesh.h"
 +#include "BKE_mesh_runtime.h"
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Mesh Runtime Struct Utils
 + * \{ */
 +
 +static ThreadRWMutex loops_cache_lock = PTHREAD_RWLOCK_INITIALIZER;
 +
 +/**
 + * Default values defined at read time.
 + */
 +void BKE_mesh_runtime_reset(Mesh *mesh)
 +{
 +      memset(&mesh->runtime, 0, sizeof(mesh->runtime));
 +}
 +
 +void BKE_mesh_runtime_clear_cache(Mesh *mesh)
 +{
 +      BKE_mesh_runtime_clear_geometry(mesh);
 +      BKE_mesh_batch_cache_free(mesh);
 +      BKE_mesh_runtime_clear_edit_data(mesh);
 +}
 +
 +/* This is a ported copy of DM_ensure_looptri_data(dm) */
 +/**
 + * Ensure the array is large enough
 + *
 + * /note This function must always be thread-protected by caller. It should only be used by internal code.
 + */
 +static void mesh_ensure_looptri_data(Mesh *mesh)
 +{
 +      const unsigned int totpoly = mesh->totpoly;
 +      const int looptris_len = poly_to_tri_count(totpoly, mesh->totloop);
 +
 +      BLI_assert(mesh->runtime.looptris.array_wip == NULL);
 +
 +      SWAP(MLoopTri *, mesh->runtime.looptris.array, mesh->runtime.looptris.array_wip);
 +
 +      if ((looptris_len > mesh->runtime.looptris.len_alloc) ||
 +          (looptris_len < mesh->runtime.looptris.len_alloc * 2) ||
 +          (totpoly == 0))
 +      {
 +              MEM_SAFE_FREE(mesh->runtime.looptris.array_wip);
 +              mesh->runtime.looptris.len_alloc = 0;
 +              mesh->runtime.looptris.len = 0;
 +      }
 +
 +      if (totpoly) {
 +              if (mesh->runtime.looptris.array_wip == NULL) {
 +                      mesh->runtime.looptris.array_wip = MEM_malloc_arrayN(looptris_len, sizeof(*mesh->runtime.looptris.array_wip), __func__);
 +                      mesh->runtime.looptris.len_alloc = looptris_len;
 +              }
 +
 +              mesh->runtime.looptris.len = looptris_len;
 +      }
 +}
 +
 +/* This is a ported copy of CDDM_recalc_looptri(dm). */
 +void BKE_mesh_runtime_looptri_recalc(Mesh *mesh)
 +{
 +      mesh_ensure_looptri_data(mesh);
 +      BLI_assert(mesh->totpoly == 0 || mesh->runtime.looptris.array_wip != NULL);
 +
 +      BKE_mesh_recalc_looptri(
 +              mesh->mloop, mesh->mpoly,
 +              mesh->mvert,
 +              mesh->totloop, mesh->totpoly,
 +              mesh->runtime.looptris.array_wip);
 +
 +      BLI_assert(mesh->runtime.looptris.array == NULL);
 +      atomic_cas_ptr((void **)&mesh->runtime.looptris.array, mesh->runtime.looptris.array, mesh->runtime.looptris.array_wip);
 +      mesh->runtime.looptris.array_wip = NULL;
 +}
 +
 +/* This is a ported copy of dm_getNumLoopTri(dm). */
 +int BKE_mesh_runtime_looptri_len(const Mesh *mesh)
 +{
 +      const int looptri_len = poly_to_tri_count(mesh->totpoly, mesh->totloop);
 +      BLI_assert(ELEM(mesh->runtime.looptris.len, 0, looptri_len));
 +      return looptri_len;
 +}
 +
 +/* This is a ported copy of dm_getLoopTriArray(dm). */
 +const MLoopTri *BKE_mesh_runtime_looptri_ensure(Mesh *mesh)
 +{
 +      MLoopTri *looptri;
 +
 +      BLI_rw_mutex_lock(&loops_cache_lock, THREAD_LOCK_READ);
 +      looptri = mesh->runtime.looptris.array;
 +      BLI_rw_mutex_unlock(&loops_cache_lock);
 +
 +      if (looptri != NULL) {
 +              BLI_assert(BKE_mesh_runtime_looptri_len(mesh) == mesh->runtime.looptris.len);
 +      }
 +      else {
 +              BLI_rw_mutex_lock(&loops_cache_lock, THREAD_LOCK_WRITE);
 +              /* We need to ensure array is still NULL inside mutex-protected code, some other thread might have already
 +               * recomputed those looptris. */
 +              if (mesh->runtime.looptris.array == NULL) {
 +                      BKE_mesh_runtime_looptri_recalc(mesh);
 +              }
 +              looptri = mesh->runtime.looptris.array;
 +              BLI_rw_mutex_unlock(&loops_cache_lock);
 +      }
 +      return looptri;
 +}
 +
 +/* This is a copy of DM_verttri_from_looptri(). */
 +void BKE_mesh_runtime_verttri_from_looptri(
 +        MVertTri *r_verttri, const MLoop *mloop,
 +        const MLoopTri *looptri, int looptri_num)
 +{
 +      int i;
 +      for (i = 0; i < looptri_num; i++) {
 +              r_verttri[i].tri[0] = mloop[looptri[i].tri[0]].v;
 +              r_verttri[i].tri[1] = mloop[looptri[i].tri[1]].v;
 +              r_verttri[i].tri[2] = mloop[looptri[i].tri[2]].v;
 +      }
 +}
 +
 +
 +bool BKE_mesh_runtime_ensure_edit_data(struct Mesh *mesh)
 +{
 +      if (mesh->runtime.edit_data != NULL) {
 +              return false;
 +      }
 +
 +      mesh->runtime.edit_data = MEM_callocN(sizeof(EditMeshData), "EditMeshData");
 +      return true;
 +}
 +
 +bool BKE_mesh_runtime_clear_edit_data(Mesh *mesh)
 +{
 +      if (mesh->runtime.edit_data == NULL) {
 +              return false;
 +      }
 +
 +      if (mesh->runtime.edit_data->polyCos != NULL)
 +              MEM_freeN((void *)mesh->runtime.edit_data->polyCos);
 +      if (mesh->runtime.edit_data->polyNos != NULL)
 +              MEM_freeN((void *)mesh->runtime.edit_data->polyNos);
 +      if (mesh->runtime.edit_data->vertexCos != NULL)
 +              MEM_freeN((void *)mesh->runtime.edit_data->vertexCos);
 +      if (mesh->runtime.edit_data->vertexNos != NULL)
 +              MEM_freeN((void *)mesh->runtime.edit_data->vertexNos);
 +
 +      MEM_SAFE_FREE(mesh->runtime.edit_data);
 +      return true;
 +}
 +
 +void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
 +{
 +      bvhcache_free(&mesh->runtime.bvh_cache);
 +      MEM_SAFE_FREE(mesh->runtime.looptris.array);
 +}
 +
 +/** \} */
 +
 +/* -------------------------------------------------------------------- */
 +/** \name Mesh Batch Cache Callbacks
 + * \{ */
 +
 +/* Draw Engine */
 +void (*BKE_mesh_batch_cache_dirty_cb)(Mesh *me, int mode) = NULL;
 +void (*BKE_mesh_batch_cache_free_cb)(Mesh *me) = NULL;
 +
 +void BKE_mesh_batch_cache_dirty(Mesh *me, int mode)
 +{
 +      if (me->runtime.batch_cache) {
 +              BKE_mesh_batch_cache_dirty_cb(me, mode);
 +      }
 +}
 +void BKE_mesh_batch_cache_free(Mesh *me)
 +{
 +      if (me->runtime.batch_cache) {
 +              BKE_mesh_batch_cache_free_cb(me);
 +      }
 +}
 +
 +/** \} */
 +
 +/** \name Mesh runtime debug helpers.
 + * \{ */
 +/* evaluated mesh info printing function,
 + * to help track down differences output */
 +
 +#ifndef NDEBUG
 +#include "BLI_dynstr.h"
 +
 +static void mesh_runtime_debug_info_layers(
 +        DynStr *dynstr, CustomData *cd)
 +{
 +      int type;
 +
 +      for (type = 0; type < CD_NUMTYPES; type++) {
 +              if (CustomData_has_layer(cd, type)) {
 +                      /* note: doesnt account for multiple layers */
 +                      const char *name = CustomData_layertype_name(type);
 +                      const int size = CustomData_sizeof(type);
 +                      const void *pt = CustomData_get_layer(cd, type);
 +                      const int pt_size = pt ? (int)(MEM_allocN_len(pt) / size) : 0;
 +                      const char *structname;
 +                      int structnum;
 +                      CustomData_file_write_info(type, &structname, &structnum);
-                       &me_eval->vdata, &me_eval->edata, &me_eval->ldata, &me_eval->pdata,
-                       false,  /* setting mask here isn't useful, gives false positives */
-                       do_verbose, do_fixes,
-                       &changed);
++                      BLI_dynstr_appendf(
++                              dynstr,
++                              "        dict(name='%s', struct='%s', type=%d, ptr='%p', elem=%d, length=%d),\n",
++                              name, structname, type, (const void *)pt, size, pt_size);
 +              }
 +      }
 +}
 +
 +char *BKE_mesh_runtime_debug_info(Mesh *me_eval)
 +{
 +      DynStr *dynstr = BLI_dynstr_new();
 +      char *ret;
 +
 +      BLI_dynstr_appendf(dynstr, "{\n");
 +      BLI_dynstr_appendf(dynstr, "    'ptr': '%p',\n", (void *)me_eval);
 +#if 0
 +      const char *tstr;
 +      switch (me_eval->type) {
 +              case DM_TYPE_CDDM:     tstr = "DM_TYPE_CDDM";     break;
 +              case DM_TYPE_EDITBMESH: tstr = "DM_TYPE_EDITMESH";  break;
 +              case DM_TYPE_CCGDM:    tstr = "DM_TYPE_CCGDM";     break;
 +              default:               tstr = "UNKNOWN";           break;
 +      }
 +      BLI_dynstr_appendf(dynstr, "    'type': '%s',\n", tstr);
 +#endif
 +      BLI_dynstr_appendf(dynstr, "    'totvert': %d,\n", me_eval->totvert);
 +      BLI_dynstr_appendf(dynstr, "    'totedge': %d,\n", me_eval->totedge);
 +      BLI_dynstr_appendf(dynstr, "    'totface': %d,\n", me_eval->totface);
 +      BLI_dynstr_appendf(dynstr, "    'totpoly': %d,\n", me_eval->totpoly);
 +      BLI_dynstr_appendf(dynstr, "    'deformed_only': %d,\n", me_eval->runtime.deformed_only);
 +
 +      BLI_dynstr_appendf(dynstr, "    'vertexLayers': (\n");
 +      mesh_runtime_debug_info_layers(dynstr, &me_eval->vdata);
 +      BLI_dynstr_appendf(dynstr, "    ),\n");
 +
 +      BLI_dynstr_appendf(dynstr, "    'edgeLayers': (\n");
 +      mesh_runtime_debug_info_layers(dynstr, &me_eval->edata);
 +      BLI_dynstr_appendf(dynstr, "    ),\n");
 +
 +      BLI_dynstr_appendf(dynstr, "    'loopLayers': (\n");
 +      mesh_runtime_debug_info_layers(dynstr, &me_eval->ldata);
 +      BLI_dynstr_appendf(dynstr, "    ),\n");
 +
 +      BLI_dynstr_appendf(dynstr, "    'polyLayers': (\n");
 +      mesh_runtime_debug_info_layers(dynstr, &me_eval->pdata);
 +      BLI_dynstr_appendf(dynstr, "    ),\n");
 +
 +      BLI_dynstr_appendf(dynstr, "    'tessFaceLayers': (\n");
 +      mesh_runtime_debug_info_layers(dynstr, &me_eval->fdata);
 +      BLI_dynstr_appendf(dynstr, "    ),\n");
 +
 +      BLI_dynstr_appendf(dynstr, "}\n");
 +
 +      ret = BLI_dynstr_get_cstring(dynstr);
 +      BLI_dynstr_free(dynstr);
 +      return ret;
 +}
 +
 +void BKE_mesh_runtime_debug_print(Mesh *me_eval)
 +{
 +      char *str = BKE_mesh_runtime_debug_info(me_eval);
 +      puts(str);
 +      fflush(stdout);
 +      MEM_freeN(str);
 +}
 +
 +/* XXX Should go in customdata file? */
 +void BKE_mesh_runtime_debug_print_cdlayers(CustomData *data)
 +{
 +      int i;
 +      const CustomDataLayer *layer;
 +
 +      printf("{\n");
 +
 +      for (i = 0, layer = data->layers; i < data->totlayer; i++, layer++) {
 +
 +              const char *name = CustomData_layertype_name(layer->type);
 +              const int size = CustomData_sizeof(layer->type);
 +              const char *structname;
 +              int structnum;
 +              CustomData_file_write_info(layer->type, &structname, &structnum);
 +              printf("        dict(name='%s', struct='%s', type=%d, ptr='%p', elem=%d, length=%d),\n",
 +                     name, structname, layer->type, (const void *)layer->data, size, (int)(MEM_allocN_len(layer->data) / size));
 +      }
 +
 +      printf("}\n");
 +}
 +
 +bool BKE_mesh_runtime_is_valid(Mesh *me_eval)
 +{
 +      const bool do_verbose = true;
 +      const bool do_fixes = false;
 +
 +      bool is_valid = true;
 +      bool changed = true;
 +
 +      if (do_verbose) {
 +              printf("MESH: %s\n", me_eval->id.name + 2);
 +      }
 +
 +      is_valid &= BKE_mesh_validate_all_customdata(
-                       me_eval,
-                       me_eval->mvert, me_eval->totvert,
-                       me_eval->medge, me_eval->totedge,
-                       me_eval->mface, me_eval->totface,
-                       me_eval->mloop, me_eval->totloop,
-                       me_eval->mpoly, me_eval->totpoly,
-                       me_eval->dvert,
-                       do_verbose, do_fixes,
-                       &changed);
++              &me_eval->vdata, &me_eval->edata, &me_eval->ldata, &me_eval->pdata,
++              false,  /* setting mask here isn't useful, gives false positives */
++              do_verbose, do_fixes,
++              &changed);
 +
 +      is_valid &= BKE_mesh_validate_arrays(
++              me_eval,
++              me_eval->mvert, me_eval->totvert,
++              me_eval->medge, me_eval->totedge,
++              me_eval->mface, me_eval->totface,
++              me_eval->mloop, me_eval->totloop,
++              me_eval->mpoly, me_eval->totpoly,
++              me_eval->dvert,
++              do_verbose, do_fixes,
++              &changed);
 +
 +      BLI_assert(changed == false);
 +
 +      return is_valid;
 +}
 +
 +#endif  /* NDEBUG */
 +
 +/** \} */
index 05a5a5e870384bba42c448f2820b195b73a62c07,0000000000000000000000000000000000000000..9d6d0b909595a56d1174d4a842bb40fe9dbf0cd4
mode 100644,000000..100644
--- /dev/null
@@@ -1,691 -1,0 +1,694 @@@
- static void get_texture_coordinate(const SMikkTSpaceContext *pContext, float r_uv[2], const int face_idx,
-                                    const int vert_idx)
 +/*
 + * ***** BEGIN GPL LICENSE BLOCK *****
 + *
 + * This program is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU General Public License
 + * as published by the Free Software Foundation; either version 2
 + * of the License, or (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software Foundation,
 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 + *
 + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 + * All rights reserved.
 + *
 + * Contributor(s): Blender Foundation
 + *
 + * ***** END GPL LICENSE BLOCK *****
 + */
 +
 +/** \file blender/blenkernel/intern/mesh_tangent.c
 + *  \ingroup bke
 + *
 + * Functions to evaluate mesh tangents.
 + */
 +
 +#include <limits.h>
 +
 +#include "MEM_guardedalloc.h"
 +
 +#include "DNA_mesh_types.h"
 +#include "DNA_meshdata_types.h"
 +
 +#include "BLI_utildefines.h"
 +#include "BLI_math.h"
 +#include "BLI_stack.h"
 +#include "BLI_task.h"
 +
 +#include "BKE_customdata.h"
 +#include "BKE_global.h"
 +#include "BKE_mesh.h"
 +#include "BKE_mesh_tangent.h"
 +#include "BKE_report.h"
 +
 +#include "BLI_strict_flags.h"
 +
 +#include "atomic_ops.h"
 +#include "mikktspace.h"
 +
 +
 +/* -------------------------------------------------------------------- */
 +
 +/** \name Mesh Tangent Calculations (Single Layer)
 + * \{ */
 +
 +/* Tangent space utils. */
 +
 +/* User data. */
 +typedef struct {
 +      const MPoly *mpolys;   /* faces */
 +      const MLoop *mloops;   /* faces's vertices */
 +      const MVert *mverts;   /* vertices */
 +      const MLoopUV *luvs;   /* texture coordinates */
 +      float (*lnors)[3];     /* loops' normals */
 +      float (*tangents)[4];  /* output tangents */
 +      int num_polys;         /* number of polygons */
 +} BKEMeshToTangent;
 +
 +/* Mikktspace's API */
 +static int get_num_faces(const SMikkTSpaceContext *pContext)
 +{
 +      BKEMeshToTangent *p_mesh = (BKEMeshToTangent *)pContext->m_pUserData;
 +      return p_mesh->num_polys;
 +}
 +
 +static int get_num_verts_of_face(const SMikkTSpaceContext *pContext, const int face_idx)
 +{
 +      BKEMeshToTangent *p_mesh = (BKEMeshToTangent *)pContext->m_pUserData;
 +      return p_mesh->mpolys[face_idx].totloop;
 +}
 +
 +static void get_position(const SMikkTSpaceContext *pContext, float r_co[3], const int face_idx, const int vert_idx)
 +{
 +      BKEMeshToTangent *p_mesh = (BKEMeshToTangent *)pContext->m_pUserData;
 +      const int loop_idx = p_mesh->mpolys[face_idx].loopstart + vert_idx;
 +      copy_v3_v3(r_co, p_mesh->mverts[p_mesh->mloops[loop_idx].v].co);
 +}
 +
- static void set_tspace(const SMikkTSpaceContext *pContext, const float fv_tangent[3], const float face_sign,
-                        const int face_idx, const int vert_idx)
++static void get_texture_coordinate(
++        const SMikkTSpaceContext *pContext, float r_uv[2], const int face_idx,
++        const int vert_idx)
 +{
 +      BKEMeshToTangent *p_mesh = (BKEMeshToTangent *)pContext->m_pUserData;
 +      copy_v2_v2(r_uv, p_mesh->luvs[p_mesh->mpolys[face_idx].loopstart + vert_idx].uv);
 +}
 +
 +static void get_normal(const SMikkTSpaceContext *pContext, float r_no[3], const int face_idx, const int vert_idx)
 +{
 +      BKEMeshToTangent *p_mesh = (BKEMeshToTangent *)pContext->m_pUserData;
 +      copy_v3_v3(r_no, p_mesh->lnors[p_mesh->mpolys[face_idx].loopstart + vert_idx]);
 +}
 +
-       BKE_mesh_calc_loop_tangent_single_ex(mesh->mvert, mesh->totvert, mesh->mloop, r_looptangents,
-                                 loopnors, loopuvs, mesh->totloop, mesh->mpoly, mesh->totpoly, reports);
++static void set_tspace(
++        const SMikkTSpaceContext *pContext, const float fv_tangent[3], const float face_sign,
++        const int face_idx, const int vert_idx)
 +{
 +      BKEMeshToTangent *p_mesh = (BKEMeshToTangent *)pContext->m_pUserData;
 +      float *p_res = p_mesh->tangents[p_mesh->mpolys[face_idx].loopstart + vert_idx];
 +      copy_v3_v3(p_res, fv_tangent);
 +      p_res[3] = face_sign;
 +}
 +
 +/**
 + * Compute simplified tangent space normals, i.e. tangent vector + sign of bi-tangent one, which combined with
 + * split normals can be used to recreate the full tangent space.
 + * Note: * The mesh should be made of only tris and quads!
 + */
 +void BKE_mesh_calc_loop_tangent_single_ex(
 +        const MVert *mverts, const int UNUSED(numVerts), const MLoop *mloops,
 +        float (*r_looptangent)[4], float (*loopnors)[3], const MLoopUV *loopuvs,
 +        const int UNUSED(numLoops), const MPoly *mpolys, const int numPolys,
 +        ReportList *reports)
 +{
 +      BKEMeshToTangent mesh_to_tangent = {NULL};
 +      SMikkTSpaceContext s_context = {NULL};
 +      SMikkTSpaceInterface s_interface = {NULL};
 +
 +      const MPoly *mp;
 +      int mp_index;
 +
 +      /* First check we do have a tris/quads only mesh. */
 +      for (mp = mpolys, mp_index = 0; mp_index < numPolys; mp++, mp_index++) {
 +              if (mp->totloop > 4) {
 +                      BKE_report(reports, RPT_ERROR, "Tangent space can only be computed for tris/quads, aborting");
 +                      return;
 +              }
 +      }
 +
 +      /* Compute Mikktspace's tangent normals. */
 +      mesh_to_tangent.mpolys = mpolys;
 +      mesh_to_tangent.mloops = mloops;
 +      mesh_to_tangent.mverts = mverts;
 +      mesh_to_tangent.luvs = loopuvs;
 +      mesh_to_tangent.lnors = loopnors;
 +      mesh_to_tangent.tangents = r_looptangent;
 +      mesh_to_tangent.num_polys = numPolys;
 +
 +      s_context.m_pUserData = &mesh_to_tangent;
 +      s_context.m_pInterface = &s_interface;
 +      s_interface.m_getNumFaces = get_num_faces;
 +      s_interface.m_getNumVerticesOfFace = get_num_verts_of_face;
 +      s_interface.m_getPosition = get_position;
 +      s_interface.m_getTexCoord = get_texture_coordinate;
 +      s_interface.m_getNormal = get_normal;
 +      s_interface.m_setTSpaceBasic = set_tspace;
 +
 +      /* 0 if failed */
 +      if (genTangSpaceDefault(&s_context) == false) {
 +              BKE_report(reports, RPT_ERROR, "Mikktspace failed to generate tangents for this mesh!");
 +      }
 +}
 +
 +/**
 + * Wrapper around BKE_mesh_calc_loop_tangent_single_ex, which takes care of most boiling code.
 + * \note
 + * - There must be a valid loop's CD_NORMALS available.
 + * - The mesh should be made of only tris and quads!
 + */
 +void BKE_mesh_calc_loop_tangent_single(Mesh *mesh, const char *uvmap, float (*r_looptangents)[4], ReportList *reports)
 +{
 +      MLoopUV *loopuvs;
 +      float (*loopnors)[3];
 +
 +      /* Check we have valid texture coordinates first! */
 +      if (uvmap) {
 +              loopuvs = CustomData_get_layer_named(&mesh->ldata, CD_MLOOPUV, uvmap);
 +      }
 +      else {
 +              loopuvs = CustomData_get_layer(&mesh->ldata, CD_MLOOPUV);
 +      }
 +      if (!loopuvs) {
 +              BKE_reportf(reports, RPT_ERROR, "Tangent space computation needs an UVMap, \"%s\" not found, aborting", uvmap);
 +              return;
 +      }
 +
 +      loopnors = CustomData_get_layer(&mesh->ldata, CD_NORMAL);
 +      if (!loopnors) {
 +              BKE_report(reports, RPT_ERROR, "Tangent space computation needs loop normals, none found, aborting");
 +              return;
 +      }
 +
++      BKE_mesh_calc_loop_tangent_single_ex(
++              mesh->mvert, mesh->totvert, mesh->mloop, r_looptangents,
++              loopnors, loopuvs, mesh->totloop, mesh->mpoly, mesh->totpoly, reports);
 +}
 +
 +/** \} */
 +
 +
 +/* -------------------------------------------------------------------- */
 +
 +/** \name Mesh Tangent Calculations (All Layers)
 + * \{ */
 +
 +
 +/* Necessary complexity to handle looptri's as quads for correct tangents */
 +#define USE_LOOPTRI_DETECT_QUADS
 +
 +typedef struct {
 +      const float (*precomputedFaceNormals)[3];
 +      const float (*precomputedLoopNormals)[3];
 +      const MLoopTri *looptri;
 +      MLoopUV *mloopuv;   /* texture coordinates */
 +      const MPoly *mpoly;       /* indices */
 +      const MLoop *mloop;       /* indices */
 +      const MVert *mvert;       /* vertices & normals */
 +      const float (*orco)[3];
 +      float (*tangent)[4];    /* destination */
 +      int numTessFaces;
 +
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +      /* map from 'fake' face index to looptri,
 +       * quads will point to the first looptri of the quad */
 +      const int    *face_as_quad_map;
 +      int       num_face_as_quad_map;
 +#endif
 +
 +} SGLSLMeshToTangent;
 +
 +/* interface */
 +static int dm_ts_GetNumFaces(const SMikkTSpaceContext *pContext)
 +{
 +      SGLSLMeshToTangent *pMesh = pContext->m_pUserData;
 +
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +      return pMesh->num_face_as_quad_map;
 +#else
 +      return pMesh->numTessFaces;
 +#endif
 +}
 +
 +static int dm_ts_GetNumVertsOfFace(const SMikkTSpaceContext *pContext, const int face_num)
 +{
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +      SGLSLMeshToTangent *pMesh = pContext->m_pUserData;
 +      if (pMesh->face_as_quad_map) {
 +              const MLoopTri *lt = &pMesh->looptri[pMesh->face_as_quad_map[face_num]];
 +              const MPoly *mp = &pMesh->mpoly[lt->poly];
 +              if (mp->totloop == 4) {
 +                      return 4;
 +              }
 +      }
 +      return 3;
 +#else
 +      UNUSED_VARS(pContext, face_num);
 +      return 3;
 +#endif
 +}
 +
 +static void dm_ts_GetPosition(
 +        const SMikkTSpaceContext *pContext, float r_co[3],
 +        const int face_num, const int vert_index)
 +{
 +      //assert(vert_index >= 0 && vert_index < 4);
 +      SGLSLMeshToTangent *pMesh = pContext->m_pUserData;
 +      const MLoopTri *lt;
 +      uint loop_index;
 +      const float *co;
 +
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +      if (pMesh->face_as_quad_map) {
 +              lt = &pMesh->looptri[pMesh->face_as_quad_map[face_num]];
 +              const MPoly *mp = &pMesh->mpoly[lt->poly];
 +              if (mp->totloop == 4) {
 +                      loop_index = (uint)(mp->loopstart + vert_index);
 +                      goto finally;
 +              }
 +              /* fall through to regular triangle */
 +      }
 +      else {
 +              lt = &pMesh->looptri[face_num];
 +      }
 +#else
 +      lt = &pMesh->looptri[face_num];
 +#endif
 +      loop_index = lt->tri[vert_index];
 +
 +finally:
 +      co = pMesh->mvert[pMesh->mloop[loop_index].v].co;
 +      copy_v3_v3(r_co, co);
 +}
 +
 +static void dm_ts_GetTextureCoordinate(
 +        const SMikkTSpaceContext *pContext, float r_uv[2],
 +        const int face_num, const int vert_index)
 +{
 +      //assert(vert_index >= 0 && vert_index < 4);
 +      SGLSLMeshToTangent *pMesh = pContext->m_pUserData;
 +      const MLoopTri *lt;
 +      uint loop_index;
 +
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +      if (pMesh->face_as_quad_map) {
 +              lt = &pMesh->looptri[pMesh->face_as_quad_map[face_num]];
 +              const MPoly *mp = &pMesh->mpoly[lt->poly];
 +              if (mp->totloop == 4) {
 +                      loop_index = (uint)(mp->loopstart + vert_index);
 +                      goto finally;
 +              }
 +              /* fall through to regular triangle */
 +      }
 +      else {
 +              lt = &pMesh->looptri[face_num];
 +      }
 +#else
 +      lt = &pMesh->looptri[face_num];
 +#endif
 +      loop_index = lt->tri[vert_index];
 +
 +finally:
 +      if (pMesh->mloopuv != NULL) {
 +              const float *uv = pMesh->mloopuv[loop_index].uv;
 +              copy_v2_v2(r_uv, uv);
 +      }
 +      else {
 +              const float *orco = pMesh->orco[pMesh->mloop[loop_index].v];
 +              map_to_sphere(&r_uv[0], &r_uv[1], orco[0], orco[1], orco[2]);
 +      }
 +}
 +
 +static void dm_ts_GetNormal(
 +        const SMikkTSpaceContext *pContext, float r_no[3],
 +        const int face_num, const int vert_index)
 +{
 +      //assert(vert_index >= 0 && vert_index < 4);
 +      SGLSLMeshToTangent *pMesh = (SGLSLMeshToTangent *) pContext->m_pUserData;
 +      const MLoopTri *lt;
 +      uint loop_index;
 +
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +      if (pMesh->face_as_quad_map) {
 +              lt = &pMesh->looptri[pMesh->face_as_quad_map[face_num]];
 +              const MPoly *mp = &pMesh->mpoly[lt->poly];
 +              if (mp->totloop == 4) {
 +                      loop_index = (uint)(mp->loopstart + vert_index);
 +                      goto finally;
 +              }
 +              /* fall through to regular triangle */
 +      }
 +      else {
 +              lt = &pMesh->looptri[face_num];
 +      }
 +#else
 +      lt = &pMesh->looptri[face_num];
 +#endif
 +      loop_index = lt->tri[vert_index];
 +
 +finally:
 +      if (pMesh->precomputedLoopNormals) {
 +              copy_v3_v3(r_no, pMesh->precomputedLoopNormals[loop_index]);
 +      }
 +      else if ((pMesh->mpoly[lt->poly].flag & ME_SMOOTH) == 0) {  /* flat */
 +              if (pMesh->precomputedFaceNormals) {
 +                      copy_v3_v3(r_no, pMesh->precomputedFaceNormals[lt->poly]);
 +              }
 +              else {
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +                      const MPoly *mp = &pMesh->mpoly[lt->poly];
 +                      if (mp->totloop == 4) {
 +                              normal_quad_v3(
 +                                      r_no,
 +                                      pMesh->mvert[pMesh->mloop[mp->loopstart + 0].v].co,
 +                                      pMesh->mvert[pMesh->mloop[mp->loopstart + 1].v].co,
 +                                      pMesh->mvert[pMesh->mloop[mp->loopstart + 2].v].co,
 +                                      pMesh->mvert[pMesh->mloop[mp->loopstart + 3].v].co);
 +                      }
 +                      else
 +#endif
 +                      {
 +                              normal_tri_v3(
 +                                      r_no,
 +                                      pMesh->mvert[pMesh->mloop[lt->tri[0]].v].co,
 +                                      pMesh->mvert[pMesh->mloop[lt->tri[1]].v].co,
 +                                      pMesh->mvert[pMesh->mloop[lt->tri[2]].v].co);
 +                      }
 +              }
 +      }
 +      else {
 +              const short *no = pMesh->mvert[pMesh->mloop[loop_index].v].no;
 +              normal_short_to_float_v3(r_no, no);
 +      }
 +}
 +
 +static void dm_ts_SetTSpace(
 +        const SMikkTSpaceContext *pContext, const float fvTangent[3], const float fSign,
 +        const int face_num, const int vert_index)
 +{
 +      //assert(vert_index >= 0 && vert_index < 4);
 +      SGLSLMeshToTangent *pMesh = (SGLSLMeshToTangent *) pContext->m_pUserData;
 +      const MLoopTri *lt;
 +      uint loop_index;
 +
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +      if (pMesh->face_as_quad_map) {
 +              lt = &pMesh->looptri[pMesh->face_as_quad_map[face_num]];
 +              const MPoly *mp = &pMesh->mpoly[lt->poly];
 +              if (mp->totloop == 4) {
 +                      loop_index = (uint)(mp->loopstart + vert_index);
 +                      goto finally;
 +              }
 +              /* fall through to regular triangle */
 +      }
 +      else {
 +              lt = &pMesh->looptri[face_num];
 +      }
 +#else
 +      lt = &pMesh->looptri[face_num];
 +#endif
 +      loop_index = lt->tri[vert_index];
 +
 +      float *pRes;
 +
 +finally:
 +      pRes = pMesh->tangent[loop_index];
 +      copy_v3_v3(pRes, fvTangent);
 +      pRes[3] = fSign;
 +}
 +
 +static void DM_calc_loop_tangents_thread(TaskPool * __restrict UNUSED(pool), void *taskdata, int UNUSED(threadid))
 +{
 +      struct SGLSLMeshToTangent *mesh2tangent = taskdata;
 +      /* new computation method */
 +      {
 +              SMikkTSpaceContext sContext = {NULL};
 +              SMikkTSpaceInterface sInterface = {NULL};
 +
 +              sContext.m_pUserData = mesh2tangent;
 +              sContext.m_pInterface = &sInterface;
 +              sInterface.m_getNumFaces = dm_ts_GetNumFaces;
 +              sInterface.m_getNumVerticesOfFace = dm_ts_GetNumVertsOfFace;
 +              sInterface.m_getPosition = dm_ts_GetPosition;
 +              sInterface.m_getTexCoord = dm_ts_GetTextureCoordinate;
 +              sInterface.m_getNormal = dm_ts_GetNormal;
 +              sInterface.m_setTSpaceBasic = dm_ts_SetTSpace;
 +
 +              /* 0 if failed */
 +              genTangSpaceDefault(&sContext);
 +      }
 +}
 +
 +void BKE_mesh_add_loop_tangent_named_layer_for_uv(
 +        CustomData *uv_data, CustomData *tan_data, int numLoopData,
 +        const char *layer_name)
 +{
 +      if (CustomData_get_named_layer_index(tan_data, CD_TANGENT, layer_name) == -1 &&
 +          CustomData_get_named_layer_index(uv_data, CD_MLOOPUV, layer_name) != -1)
 +      {
 +              CustomData_add_layer_named(
 +                      tan_data, CD_TANGENT, CD_CALLOC, NULL,
 +                      numLoopData, layer_name);
 +      }
 +}
 +
 +/**
 + * Here we get some useful information such as active uv layer name and search if it is already in tangent_names.
 + * Also, we calculate tangent_mask that works as a descriptor of tangents state.
 + * If tangent_mask has changed, then recalculate tangents.
 + */
 +void BKE_mesh_calc_loop_tangent_step_0(
 +        const CustomData *loopData, bool calc_active_tangent,
 +        const char (*tangent_names)[MAX_NAME], int tangent_names_count,
 +        bool *rcalc_act, bool *rcalc_ren, int *ract_uv_n, int *rren_uv_n,
 +        char *ract_uv_name, char *rren_uv_name, short *rtangent_mask)
 +{
 +      /* Active uv in viewport */
 +      int layer_index = CustomData_get_layer_index(loopData, CD_MLOOPUV);
 +      *ract_uv_n = CustomData_get_active_layer(loopData, CD_MLOOPUV);
 +      ract_uv_name[0] = 0;
 +      if (*ract_uv_n != -1) {
 +              strcpy(ract_uv_name, loopData->layers[*ract_uv_n + layer_index].name);
 +      }
 +
 +      /* Active tangent in render */
 +      *rren_uv_n = CustomData_get_render_layer(loopData, CD_MLOOPUV);
 +      rren_uv_name[0] = 0;
 +      if (*rren_uv_n != -1) {
 +              strcpy(rren_uv_name, loopData->layers[*rren_uv_n + layer_index].name);
 +      }
 +
 +      /* If active tangent not in tangent_names we take it into account */
 +      *rcalc_act = false;
 +      *rcalc_ren = false;
 +      for (int i = 0; i < tangent_names_count; i++) {
 +              if (tangent_names[i][0] == 0) {
 +                      calc_active_tangent = true;
 +              }
 +      }
 +      if (calc_active_tangent) {
 +              *rcalc_act = true;
 +              *rcalc_ren = true;
 +              for (int i = 0; i < tangent_names_count; i++) {
 +                      if (STREQ(ract_uv_name, tangent_names[i]))
 +                              *rcalc_act = false;
 +                      if (STREQ(rren_uv_name, tangent_names[i]))
 +                              *rcalc_ren = false;
 +              }
 +      }
 +      *rtangent_mask = 0;
 +
 +      const int uv_layer_num = CustomData_number_of_layers(loopData, CD_MLOOPUV);
 +      for (int n = 0; n < uv_layer_num; n++) {
 +              const char *name = CustomData_get_layer_name(loopData, CD_MLOOPUV, n);
 +              bool add = false;
 +              for (int i = 0; i < tangent_names_count; i++) {
 +                      if (tangent_names[i][0] && STREQ(tangent_names[i], name)) {
 +                              add = true;
 +                              break;
 +                      }
 +              }
 +              if (!add && ((*rcalc_act && ract_uv_name[0] && STREQ(ract_uv_name, name)) ||
 +                           (*rcalc_ren && rren_uv_name[0] && STREQ(rren_uv_name, name))))
 +              {
 +                      add = true;
 +              }
 +              if (add)
 +                      *rtangent_mask |= (short)(1 << n);
 +      }
 +
 +      if (uv_layer_num == 0)
 +              *rtangent_mask |= DM_TANGENT_MASK_ORCO;
 +}
 +
 +/**
 + * See: #BKE_editmesh_loop_tangent_calc (matching logic).
 + */
 +void BKE_mesh_calc_loop_tangent_ex(
 +        const MVert *mvert,
 +        const MPoly *mpoly, const uint mpoly_len,
 +        const MLoop *mloop,
 +        const MLoopTri *looptri,
 +        const uint looptri_len,
 +
 +        CustomData *loopdata,
 +        bool calc_active_tangent,
 +        const char (*tangent_names)[MAX_NAME], int tangent_names_len,
 +        const float (*poly_normals)[3],
 +        const float (*loop_normals)[3],
 +        const float (*vert_orco)[3],
 +        /* result */
 +        CustomData *loopdata_out,
 +        const uint  loopdata_out_len,
 +        short *tangent_mask_curr_p)
 +{
 +      int act_uv_n = -1;
 +      int ren_uv_n = -1;
 +      bool calc_act = false;
 +      bool calc_ren = false;
 +      char act_uv_name[MAX_NAME];
 +      char ren_uv_name[MAX_NAME];
 +      short tangent_mask = 0;
 +      short tangent_mask_curr = *tangent_mask_curr_p;
 +
 +      BKE_mesh_calc_loop_tangent_step_0(
 +              loopdata, calc_active_tangent, tangent_names, tangent_names_len,
 +              &calc_act, &calc_ren, &act_uv_n, &ren_uv_n, act_uv_name, ren_uv_name, &tangent_mask);
 +      if ((tangent_mask_curr | tangent_mask) != tangent_mask_curr) {
 +              /* Check we have all the needed layers */
 +              /* Allocate needed tangent layers */
 +              for (int i = 0; i < tangent_names_len; i++)
 +                      if (tangent_names[i][0])
 +                              BKE_mesh_add_loop_tangent_named_layer_for_uv(loopdata, loopdata_out, (int)loopdata_out_len, tangent_names[i]);
 +              if ((tangent_mask & DM_TANGENT_MASK_ORCO) && CustomData_get_named_layer_index(loopdata, CD_TANGENT, "") == -1)
 +                          CustomData_add_layer_named(loopdata_out, CD_TANGENT, CD_CALLOC, NULL, (int)loopdata_out_len, "");
 +              if (calc_act && act_uv_name[0])
 +                      BKE_mesh_add_loop_tangent_named_layer_for_uv(loopdata, loopdata_out, (int)loopdata_out_len, act_uv_name);
 +              if (calc_ren && ren_uv_name[0])
 +                      BKE_mesh_add_loop_tangent_named_layer_for_uv(loopdata, loopdata_out, (int)loopdata_out_len, ren_uv_name);
 +
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +              int num_face_as_quad_map;
 +              int *face_as_quad_map = NULL;
 +
 +              /* map faces to quads */
 +              if (looptri_len != mpoly_len) {
 +                      /* over alloc, since we dont know how many ngon or quads we have */
 +
 +                      /* map fake face index to looptri */
 +                      face_as_quad_map = MEM_mallocN(sizeof(int) * looptri_len, __func__);
 +                      int k, j;
 +                      for (k = 0, j = 0; j < (int)looptri_len; k++, j++) {
 +                              face_as_quad_map[k] = j;
 +                              /* step over all quads */
 +                              if (mpoly[looptri[j].poly].totloop == 4) {
 +                                      j++;  /* skips the nest looptri */
 +                              }
 +                      }
 +                      num_face_as_quad_map = k;
 +              }
 +              else {
 +                      num_face_as_quad_map = (int)looptri_len;
 +              }
 +#endif
 +
 +              /* Calculation */
 +              if (looptri_len != 0) {
 +                      TaskScheduler *scheduler = BLI_task_scheduler_get();
 +                      TaskPool *task_pool;
 +                      task_pool = BLI_task_pool_create(scheduler, NULL);
 +
 +                      tangent_mask_curr = 0;
 +                      /* Calculate tangent layers */
 +                      SGLSLMeshToTangent data_array[MAX_MTFACE];
 +                      const int tangent_layer_num = CustomData_number_of_layers(loopdata_out, CD_TANGENT);
 +                      for (int n = 0; n < tangent_layer_num; n++) {
 +                              int index = CustomData_get_layer_index_n(loopdata_out, CD_TANGENT, n);
 +                              BLI_assert(n < MAX_MTFACE);
 +                              SGLSLMeshToTangent *mesh2tangent = &data_array[n];
 +                              mesh2tangent->numTessFaces = (int)looptri_len;
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +                              mesh2tangent->face_as_quad_map = face_as_quad_map;
 +                              mesh2tangent->num_face_as_quad_map = num_face_as_quad_map;
 +#endif
 +                              mesh2tangent->mvert = mvert;
 +                              mesh2tangent->mpoly = mpoly;
 +                              mesh2tangent->mloop = mloop;
 +                              mesh2tangent->looptri = looptri;
 +                              /* Note, we assume we do have tessellated loop normals at this point (in case it is object-enabled),
 +                               * have to check this is valid...
 +                               */
 +                              mesh2tangent->precomputedLoopNormals = loop_normals;
 +                              mesh2tangent->precomputedFaceNormals = poly_normals;
 +
 +                              mesh2tangent->orco = NULL;
 +                              mesh2tangent->mloopuv = CustomData_get_layer_named(loopdata, CD_MLOOPUV, loopdata_out->layers[index].name);
 +
 +                              /* Fill the resulting tangent_mask */
 +                              if (!mesh2tangent->mloopuv) {
 +                                  mesh2tangent->orco = vert_orco;
 +                                  if (!mesh2tangent->orco)
 +                                          continue;
 +
 +                                  tangent_mask_curr |= DM_TANGENT_MASK_ORCO;
 +                              }
 +                              else {
 +                                  int uv_ind = CustomData_get_named_layer_index(loopdata, CD_MLOOPUV, loopdata_out->layers[index].name);
 +                                  int uv_start = CustomData_get_layer_index(loopdata, CD_MLOOPUV);
 +                                  BLI_assert(uv_ind != -1 && uv_start != -1);
 +                                  BLI_assert(uv_ind - uv_start < MAX_MTFACE);
 +                                  tangent_mask_curr |= (short)(1 << (uv_ind - uv_start));
 +                              }
 +
 +                              mesh2tangent->tangent = loopdata_out->layers[index].data;
 +                              BLI_task_pool_push(task_pool, DM_calc_loop_tangents_thread, mesh2tangent, false, TASK_PRIORITY_LOW);
 +                      }
 +
 +                      BLI_assert(tangent_mask_curr == tangent_mask);
 +                      BLI_task_pool_work_and_wait(task_pool);
 +                      BLI_task_pool_free(task_pool);
 +              }
 +              else {
 +                      tangent_mask_curr = tangent_mask;
 +              }
 +#ifdef USE_LOOPTRI_DETECT_QUADS
 +              if (face_as_quad_map) {
 +                      MEM_freeN(face_as_quad_map);
 +              }
 +#undef USE_LOOPTRI_DETECT_QUADS
 +
 +#endif
 +
 +              *tangent_mask_curr_p = tangent_mask_curr;
 +
 +              /* Update active layer index */
 +              int act_uv_index = CustomData_get_layer_index_n(loopdata, CD_MLOOPUV, act_uv_n);
 +              if (act_uv_index != -1) {
 +                  int tan_index = CustomData_get_named_layer_index(loopdata, CD_TANGENT, loopdata->layers[act_uv_index].name);
 +                  CustomData_set_layer_active_index(loopdata, CD_TANGENT, tan_index);
 +              }/* else tangent has been built from orco */
 +
 +              /* Update render layer index */
 +              int ren_uv_index = CustomData_get_layer_index_n(loopdata, CD_MLOOPUV, ren_uv_n);
 +              if (ren_uv_index != -1) {
 +                  int tan_index = CustomData_get_named_layer_index(loopdata, CD_TANGENT, loopdata->layers[ren_uv_index].name);
 +                  CustomData_set_layer_render_index(loopdata, CD_TANGENT, tan_index);
 +              }/* else tangent has been built from orco */
 +      }
 +}
 +
 +/** \} */