diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index bc965d74c91f46268aface68d889c048d131c769..27569c327bce349801f8ce1018e38acbe544850f 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,5 +1,5 @@
 4.10.2 (Work-in-progress): fixed regression introduced in 4.9 for recombined
-meshes with boundary layers; small bug fixes.
+meshes with boundary layers; generalized Crack plugin; small bug fixes.
 
 4.10.1 (May 1, 2022): small bug fixes.
 
diff --git a/examples/api/crack.py b/examples/api/crack.py
index 7a3cab9f4576ee689d183db2e70800a83fdd45ae..f5f72e43f172fb2da22848e4f915fbba368b1baa 100644
--- a/examples/api/crack.py
+++ b/examples/api/crack.py
@@ -33,6 +33,11 @@ gmsh.model.mesh.generate(2)
 gmsh.plugin.setNumber("Crack", "PhysicalGroup", 101)
 gmsh.plugin.run("Crack")
 
+# save all the elements in the mesh (even those that do not belong to any
+# physical group):
+gmsh.option.setNumber("Mesh.SaveAll", 1)
+gmsh.write("crack.msh")
+
 if '-nopopup' not in sys.argv:
     gmsh.fltk.run()
 
diff --git a/src/plugin/Crack.cpp b/src/plugin/Crack.cpp
index 5b28d36defec2d61f1cd27e4b2f2e15e91c9ab5d..f78eb233b05b4ce004cb3b6e8549eb5cad917b65 100644
--- a/src/plugin/Crack.cpp
+++ b/src/plugin/Crack.cpp
@@ -21,7 +21,8 @@ StringXNumber CrackOptions_Number[] = {
   {GMSH_FULLRC, "NormalX", nullptr, 0.},
   {GMSH_FULLRC, "NormalY", nullptr, 0.},
   {GMSH_FULLRC, "NormalZ", nullptr, 1.},
-  {GMSH_FULLRC, "NewPhysicalGroup", nullptr, 0}
+  {GMSH_FULLRC, "NewPhysicalGroup", nullptr, 0},
+  {GMSH_FULLRC, "Debug", nullptr, 0}
 };
 
 extern "C" {
@@ -86,6 +87,7 @@ PView *GMSH_CrackPlugin::execute(PView *view)
   SVector3 normal1d(CrackOptions_Number[3].def, CrackOptions_Number[4].def,
                     CrackOptions_Number[5].def);
   int newPhysical = (int)CrackOptions_Number[6].def;
+  int debug = (int)CrackOptions_Number[7].def;
 
   if(dim != 1 && dim != 2) {
     Msg::Error("Crack dimension should be 1 or 2");
@@ -122,13 +124,19 @@ PView *GMSH_CrackPlugin::execute(PView *view)
     for(std::size_t j = 0; j < entities[i]->getNumMeshElements(); j++)
       crackElements.push_back(entities[i]->getMeshElement(j));
 
-  // get internal crack nodes and boundary nodes
-  std::set<MVertex *> crackVertices, bndVertices;
+  // get internal crack nodes (and list of connected crack elements), as well as
+  // and boundary nodes
+  std::map<MVertex *, std::vector<MElement *> > crackVertices;
+  std::set<MVertex *> bndVertices;
   if(dim == 1) {
     for(std::size_t i = 0; i < crackElements.size(); i++) {
       for(std::size_t j = 0; j < crackElements[i]->getNumVertices(); j++) {
         MVertex *v = crackElements[i]->getVertex(j);
-        crackVertices.insert(v);
+        auto it = crackVertices.find(v);
+        if(it == crackVertices.end())
+          crackVertices[v] = {crackElements[i]};
+        else
+          it->second.push_back(crackElements[i]);
       }
       for(std::size_t j = 0; j < crackElements[i]->getNumPrimaryVertices();
           j++) {
@@ -145,7 +153,11 @@ PView *GMSH_CrackPlugin::execute(PView *view)
     for(std::size_t i = 0; i < crackElements.size(); i++) {
       for(std::size_t j = 0; j < crackElements[i]->getNumVertices(); j++) {
         MVertex *v = crackElements[i]->getVertex(j);
-        crackVertices.insert(v);
+        auto it = crackVertices.find(v);
+        if(it == crackVertices.end())
+          crackVertices[v] = {crackElements[i]};
+        else
+          it->second.push_back(crackElements[i]);
       }
       for(int j = 0; j < crackElements[i]->getNumEdges(); j++) {
         EdgeData ed(crackElements[i]->getEdge(j));
@@ -208,40 +220,46 @@ PView *GMSH_CrackPlugin::execute(PView *view)
     for(std::size_t i = 0; i < allentities[ent]->getNumMeshElements(); i++) {
       MElement *e = allentities[ent]->getMeshElement(i);
       for(std::size_t j = 0; j < e->getNumVertices(); j++) {
-        if(crackVertices.find(e->getVertex(j)) != crackVertices.end()) {
-          // element touches the crack: find the closest crack element
-          SPoint3 b = e->barycenter();
-          double d = 1e200;
-          MElement *ce = nullptr;
-          for(std::size_t k = 0; k < crackElements.size(); k++) {
-            double d2 = b.distance(crackElements[k]->barycenter());
-            if(d2 < d) {
-              d = d2;
-              ce = crackElements[k];
-            }
-          }
-          SVector3 dv = SVector3(e->barycenter(), ce->barycenter());
+        auto it = crackVertices.find(e->getVertex(j));
+        if(it == crackVertices.end()) continue;
+        // the element touches the crack by at least one node: determine if the
+        // vector joining its barycenter to the barycenter of one of the
+        // connected crack elements is not in the same direction as the normal
+        // to the crack element; if the condition is fulfilled for one of the
+        // connected crack elements, we consider the element lies on the
+        // "positive side" of the crack
+        SPoint3 b = e->barycenter();
+        bool found = false;
+        for(auto ce : it->second) {
+          SVector3 dv = SVector3(b, ce->barycenter());
           SVector3 n;
           if(dim == 1)
             n = crossprod(normal1d, ce->getEdge(0).tangent());
           else
             n = ce->getFace(0).normal();
-          if(dot(n, dv) < 0) { oneside.insert(e); }
+          if(dot(n, dv) < 0) {
+            oneside.insert(e);
+            found = true;
+            break;
+          }
         }
+        if(found) break; // we're done for the element
       }
     }
   }
 
-  /*
-  FILE *fp = fopen("debug.pos", "w");
-  if(fp){
-    fprintf(fp, "View \"Ele < 0\" {\n");
-    for(auto it = oneside.begin(); it != oneside.end(); it++)
-      (*it)->writePOS(fp, false, true, false, false, false, false);
-    fprintf(fp, "};\n");
-    fclose(fp);
+  if(debug) {
+    Msg::Info("Writing 'debug.pos' file with elements detected on one side "
+              "of the crack");
+    FILE *fp = fopen("debug.pos", "w");
+    if(fp){
+      fprintf(fp, "View \"Elements on one side\" {\n");
+      for(auto it = oneside.begin(); it != oneside.end(); it++)
+        (*it)->writePOS(fp, false, true, false, false, false, false);
+      fprintf(fp, "};\n");
+      fclose(fp);
+    }
   }
-  */
 
   // create new crack entity
 
@@ -278,7 +296,7 @@ PView *GMSH_CrackPlugin::execute(PView *view)
   // duplicate internal crack nodes
   std::map<MVertex *, MVertex *> vxv;
   for(auto it = crackVertices.begin(); it != crackVertices.end(); it++) {
-    MVertex *v = *it;
+    MVertex *v = it->first;
     MVertex *newv = new MVertex(v->x(), v->y(), v->z(), crackEntity);
     crackEntity->mesh_vertices.push_back(newv);
     vxv[v] = newv;