*/}}
Browse Source

Basic merging operator.

YimingWu 1 year ago
parent
commit
1c5ec9c9a2
5 changed files with 180 additions and 27 deletions
  1. 9 3
      la_tns.h
  2. 1 0
      la_tns_kernel.c
  3. 92 22
      la_tns_mesh.c
  4. 75 2
      resources/la_modelling.c
  5. 3 0
      resources/la_widgets_viewers.c

+ 9 - 3
la_tns.h

@@ -797,6 +797,9 @@ STRUCTURE(tnsMeshObject){
 
     int TriangleCount;
     int TriangulatedEdgeCount;
+
+    tnsMVert* LastSelectV, *FirstSelectV;
+    tnsMEdge* LastSelectE, *FirstSelectE;
     
     tnsBatch *Batch;
     tnsBatch *ExtraBatch;
@@ -1182,11 +1185,11 @@ tnsMVert* tnsMMeshNewVert(tnsMeshObject* mo);
 void tnsMMeshCalculateNormalFrom(tnsMFace* mf);
 int tnsMMeshCalculateNormal(tnsMeshObject* mo);
 void tnsMMeshEdgeAssignVerts(tnsMEdge* me,tnsMVert* mv1,tnsMVert* mv2);
-tnsMEdge* tnsMMeshVertShareEdge(tnsMVert* mv0, tnsMVert* mv1);
+tnsMEdge* tnsMMeshVertsShareEdge(tnsMVert* mv0, tnsMVert* mv1);
 int tnsMMeshEdgeHasVert(tnsMEdge* me, tnsMVert* MV);
 tnsMFace* tnsMMeshEdgeShareFace(tnsMEdge* me0, tnsMEdge* me1);
 tnsMVert* tnsMMeshEdgeShareVert(tnsMEdge* me0, tnsMEdge* me1);
-tnsMVert* tnsMMeshEdgeAnotherVert(tnsMEdge* me, tnsVert* v);
+tnsMVert* tnsMMeshEdgeAnotherVert(tnsMEdge* me, tnsMVert* v);
 tnsMVert* tnsMMeshEdgeStartingVert(tnsMEdge* me0, tnsMEdge* me1);
 int tnsMMeshSplitFace(tnsMeshObject* mo, tnsMFace* mf, tnsMEdge* me, tnsMFace** r_f1, tnsMFace** r_f2);
 tnsMFace* tnsMMeshVertsShareFace(tnsMVert* v1, tnsMVert* v2);
@@ -1194,7 +1197,7 @@ tnsMEdge* tnsMMeshMakeEdge(tnsMeshObject* mo, tnsMVert* v1, tnsMVert* v2);
 void tnsMMeshFaceAddEdge(tnsMFace* mf, tnsMEdge* me);
 int tnsMMeshFaceMatches(tnsMFace* mf, int ecount, ...);
 tnsMFace* tnsMMeshMakeFaceN(tnsMeshObject* mo, int count, laListHandle* vip, tnsMEdge** r_fallback_me);
-tnsMFace* tnsMMeshMakeFace4v(tnsMeshObject* mo, tnsVert* v1,tnsVert* v2,tnsVert* v3,tnsVert* v4);
+tnsMFace* tnsMMeshMakeFace4v(tnsMeshObject* mo, tnsMVert* v1,tnsMVert* v2,tnsMVert* v3,tnsMVert* v4);
 int tnsMMeshLoopIsInverted(laListItemPointer* l);
 int tnsMMeshEdgeInsertVert(tnsMeshObject* mo, tnsMEdge* me, tnsMVert* mv, tnsMVert* ref_e1v_optional, tnsMEdge** r_e1, tnsMEdge** r_e2);
 tnsMVert* tnsMMeshEdgeInsertVertAt(tnsMeshObject* mo, tnsMEdge* me, real at, tnsMVert* ref_e1v_optional, tnsMEdge** r_e1, tnsMEdge** r_e2);
@@ -1218,6 +1221,9 @@ void tnsMMeshEnsureSelection(tnsMeshObject* mo, int SelectMode);
 void tnsMMeshExpandBandList(tnsMeshObject* mo, tnsMEdge* me, laListHandle* lst);
 int tnsMMeshSelectRingBandFrom(tnsMeshObject* mo, tnsMEdge* me, int ring_band, int select, int toggle);
 
+int tnsMMeshVertsCanMerge(tnsMeshObject* mo, tnsMVert* into, tnsMVert* mv);
+int tnsMMeshMergeVerts(tnsMeshObject* mo, tnsMVert* into, tnsMVert* mv);
+
 void tnsInvalidateMeshBatch(tnsMeshObject* mo);
 void tnsRegenerateMeshBatch(tnsMeshObject* mo);
 void tnsEnsureMeshBatch(tnsMeshObject* mo);

+ 1 - 0
la_tns_kernel.c

@@ -1354,6 +1354,7 @@ void tnsDeleteBuiltinShaders(){
     tnsDeleteShaderProgram(T->TEST_MatcapShader); T->TEST_MatcapShader=0;
     tnsDeleteShaderProgram(T->TransparentGridShader); T->TransparentGridShader=0;
     tnsDeleteShaderProgram(T->SelectionShader); T->SelectionShader=0;
+    tnsDeleteShaderProgram(T->FloorShader); T->FloorShader=0;
     tnsDeleteShaderProgram(T->SceneShader); T->SceneShader=0;
     tnsDeleteShaderProgram(T->ShadowShader); T->ShadowShader=0;
     tnsDeleteShaderProgram(T->RayShader); T->RayShader=0;

+ 92 - 22
la_tns_mesh.c

@@ -199,25 +199,29 @@ float* tnsGetDrawingVertArray(tnsMeshObject* mo, int* r_tot_render_v, float** r_
         }
     }else{
         (*idcolors)=calloc(1,(totv+extraverts)*3*sizeof(float));
-        (*edgeelems)=calloc(1,(extraverts)*2*sizeof(int));
-        (*editcolors)=calloc(1,totv*4*sizeof(float)*extraverts);
+        if(extraverts){
+            (*edgeelems)=calloc(1,(extraverts)*2*sizeof(int));
+            (*editcolors)=calloc(1,totv*4*sizeof(float)*extraverts);
+        }
         for(tnsMVert*mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ int i=mv->i;
             tnsVectorSet3v(&p[i*3],mv->p); tnsVectorSet3v(&n[i*3],mv->n);
             int id=(i+1); real r=(real)((id & 0x000000FF)>>0)/255.0; real g=(real)((id & 0x0000FF00)>>8)/255.0; real b=(real)((id & 0x00FF0000)>>16)/255.0;
             (*idcolors)[i*3]=r; (*idcolors)[i*3+1]=g; (*idcolors)[i*3+2]=b;
             real* c=(mv->flags&TNS_MESH_FLAG_SELECTED)?laAccentColor(LA_BT_SVERTEX):laAccentColor(LA_BT_VERTEX);
-            (*editcolors)[i*4]=c[0]; (*editcolors)[i*4+1]=c[1]; (*editcolors)[i*4+2]=c[2]; (*editcolors)[i*4+3]=c[3];
+            if(extraverts){ (*editcolors)[i*4]=c[0]; (*editcolors)[i*4+1]=c[1]; (*editcolors)[i*4+2]=c[2]; (*editcolors)[i*4+3]=c[3];}
         }
-        for(tnsMEdge*me=mo->me.pFirst;me;me=me->Item.pNext){ int ei=me->i;
-            (*edgeelems)[ei*2]=mo->totmv+mo->totmf+ei*2; (*edgeelems)[ei*2+1]=mo->totmv+mo->totmf+ei*2+1;
-            float* eidcolor1=&(*idcolors)[(*edgeelems)[ei*2]*3], *eidcolor2=&(*idcolors)[(*edgeelems)[ei*2+1]*3];
-            int id=((ei+1)|TNS_MMESH_EDGE_BIT); real r=(real)((id & 0x000000FF)>>0)/255.0; real g=(real)((id & 0x0000FF00)>>8)/255.0; real b=(real)((id & 0x00FF0000)>>16)/255.0;
-            tnsVectorSet3(eidcolor1,r,g,b); tnsVectorSet3(eidcolor2,r,g,b);
-            int se1=(*edgeelems)[ei*2];   tnsVectorSet3v(&p[se1*3],me->vl->p);
-            int se2=(*edgeelems)[ei*2+1]; tnsVectorSet3v(&p[se2*3],me->vr->p);
-            float* eedcolor1=&(*editcolors)[(*edgeelems)[ei*2]*4], *eedcolor2=&(*editcolors)[(*edgeelems)[ei*2+1]*4];
-            real* c=(me->flags&TNS_MESH_FLAG_SELECTED)?laAccentColor(LA_BT_SEDGE):laAccentColor(LA_BT_EDGE);
-            tnsVectorSet4v(eedcolor1,c); tnsVectorSet4v(eedcolor2,c);
+        if(extraverts){ 
+            for(tnsMEdge*me=mo->me.pFirst;me;me=me->Item.pNext){ int ei=me->i;
+                (*edgeelems)[ei*2]=mo->totmv+mo->totmf+ei*2; (*edgeelems)[ei*2+1]=mo->totmv+mo->totmf+ei*2+1;
+                float* eidcolor1=&(*idcolors)[(*edgeelems)[ei*2]*3], *eidcolor2=&(*idcolors)[(*edgeelems)[ei*2+1]*3];
+                int id=((ei+1)|TNS_MMESH_EDGE_BIT); real r=(real)((id & 0x000000FF)>>0)/255.0; real g=(real)((id & 0x0000FF00)>>8)/255.0; real b=(real)((id & 0x00FF0000)>>16)/255.0;
+                tnsVectorSet3(eidcolor1,r,g,b); tnsVectorSet3(eidcolor2,r,g,b);
+                int se1=(*edgeelems)[ei*2];   tnsVectorSet3v(&p[se1*3],me->vl->p);
+                int se2=(*edgeelems)[ei*2+1]; tnsVectorSet3v(&p[se2*3],me->vr->p);
+                float* eedcolor1=&(*editcolors)[(*edgeelems)[ei*2]*4], *eedcolor2=&(*editcolors)[(*edgeelems)[ei*2+1]*4];
+                real* c=(me->flags&TNS_MESH_FLAG_SELECTED)?laAccentColor(LA_BT_SEDGE):laAccentColor(LA_BT_EDGE);
+                tnsVectorSet4v(eedcolor1,c); tnsVectorSet4v(eedcolor2,c);
+            }
         }
         int start=mo->totmv*3; int fi=0; for(tnsMFace*mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){
             tnsMVert* sv=tnsGetMFaceLastVert(mf); tnsVectorSet3v(&p[start+fi*3],sv->p); tnsVectorSet3v(&n[start+fi*3],mf->n); fi++;
@@ -248,8 +252,11 @@ void tnsRegenerateMeshBatch(tnsMeshObject* mo){
     if(!v){ if(elem){free(elem);} if(eelems){free(eelems);} return; }
 
     mo->Batch = tnsCreateBatch(totv, 3, v, 3, n, 4, editcolors);
-    tnsBatchCommand*c=tnsCreateCommand(mo->Batch, "body", tottri, 3, GL_TRIANGLES, elem, 0);
-    tnsCommandUseUniformColor(c,meshcolor);
+    tnsBatchCommand*c;
+    if(elem){
+        c=tnsCreateCommand(mo->Batch, "body", tottri, 3, GL_TRIANGLES, elem, 0);
+        tnsCommandUseUniformColor(c,meshcolor);
+    }
     free(elem); free(v); free(n);
 
     if(mo->Mode==TNS_MESH_EDIT_MODE){
@@ -260,10 +267,12 @@ void tnsRegenerateMeshBatch(tnsMeshObject* mo){
         c= tnsCreateCommand(mo->Batch, "verts", mo->totmv, 3, GL_POINTS, 0, 1);
         c= tnsCreateCommand(mo->Batch, "verts_select", mo->totmv, 3, GL_POINTS, 0, 1);
         tnsCommandOverrideColorArray(c, mo->Batch->NumVert, 3, idcolors);
-        c= tnsCreateCommand(mo->Batch, "edges_select", mo->totme, 2, GL_LINES, eelems, 1);
-        tnsCommandOverrideColorArray(c, mo->Batch->NumVert, 3, idcolors);
-        c= tnsCreateCommand(mo->Batch, "edges", mo->totme, 2, GL_LINES, eelems, 1);
-        tnsCommandOverrideColorArray(c, mo->Batch->NumVert, 4, editcolors);
+        if(eelems){
+            c= tnsCreateCommand(mo->Batch, "edges_select", mo->totme, 2, GL_LINES, eelems, 1);
+            tnsCommandOverrideColorArray(c, mo->Batch->NumVert, 3, idcolors);
+            c= tnsCreateCommand(mo->Batch, "edges", mo->totme, 2, GL_LINES, eelems, 1);
+            tnsCommandOverrideColorArray(c, mo->Batch->NumVert, 4, editcolors);
+        }
         //for(int i=0;i<mo->totme*2;i++){ printf("%d ",eelems[i]); } printf("\n");
     }
     if(idcolors) free(idcolors); if(editcolors) free(editcolors); if(eelems) free(eelems);
@@ -371,7 +380,7 @@ void tnsMMeshEdgeAssignVerts(tnsMEdge* me,tnsMVert* mv1,tnsMVert* mv2){
     me->vl=mv1; me->vr=mv2;
     lstAppendPointer(&me->vl->elink,me); lstAppendPointer(&me->vr->elink,me);
 }
-tnsMEdge* tnsMMeshVertShareEdge(tnsMVert* mv0, tnsMVert* mv1){
+tnsMEdge* tnsMMeshVertsShareEdge(tnsMVert* mv0, tnsMVert* mv1){
     for(laListItemPointer*lip=mv0->elink.pFirst;lip;lip=lip->pNext){ tnsMEdge* me=lip->p; if(tnsMMeshEdgeAnotherVert(me, mv0)==mv1) return me; } return 0;
 }
 int tnsMMeshEdgeHasVert(tnsMEdge* me, tnsMVert* mv){
@@ -387,7 +396,7 @@ tnsMVert* tnsMMeshEdgeShareVert(tnsMEdge* me0, tnsMEdge* me1){
     if(me0->vr==me1->vl || me0->vr==me1->vr) return me0->vr;
     return 0;
 }
-tnsMVert* tnsMMeshEdgeAnotherVert(tnsMEdge* me, tnsVert* v){
+tnsMVert* tnsMMeshEdgeAnotherVert(tnsMEdge* me, tnsMVert* v){
     if(me->vl==v) return me->vr; if(me->vr==v) return me->vl;
     return 0;
 }
@@ -482,7 +491,7 @@ tnsMFace* tnsMMeshMakeFaceN(tnsMeshObject* mo, int count, laListHandle* vip, tns
     while(lstPopPointer(&el));
     return mf;
 }
-tnsMFace* tnsMMeshMakeFace4v(tnsMeshObject* mo, tnsVert* v1,tnsVert* v2,tnsVert* v3,tnsVert* v4){
+tnsMFace* tnsMMeshMakeFace4v(tnsMeshObject* mo, tnsMVert* v1,tnsMVert* v2,tnsMVert* v3,tnsMVert* v4){
     tnsMEdge* e1=tnsMMeshMakeEdge(mo,v1,v2); tnsMEdge* e2=tnsMMeshMakeEdge(mo,v2,v3);
     tnsMEdge* e3=tnsMMeshMakeEdge(mo,v3,v4); tnsMEdge* e4=tnsMMeshMakeEdge(mo,v4,v1);
     if(tnsMMeshFaceMatches(e1->fl,4,e1,e2,e3,e4)) return e1->fl; if(tnsMMeshFaceMatches(e1->fr,4,e1,e2,e3,e4)) return e1->fr; //should not need more
@@ -525,17 +534,20 @@ void tnsMMeshRemoveFaceOnly(tnsMeshObject* mo, tnsMFace* mf){
     if(!mf) return; tnsMEdge* me;
     while(me=lstPopPointerLeave(&mf->l)){ if(me->fl==mf) me->fl=0; elif(me->fr==mf) me->fr=0; }
     lstRemoveItem(&mo->mf,mf); memLeave(mf); mo->totmf--;
+    tnsMMeshClearFirstLastSelection(mo);
 }
 void tnsMMeshRemoveEdgeFace(tnsMeshObject* mo, tnsMEdge* me){
     if(!me) return;
     tnsMMeshRemoveFaceOnly(mo, me->fl); tnsMMeshRemoveFaceOnly(mo, me->fr);
     lstRemovePointerLeave(&me->vl->elink, me); lstRemovePointerLeave(&me->vr->elink, me);
     lstRemoveItem(&mo->me,me); memLeave(me); mo->totme--;
+    tnsMMeshClearFirstLastSelection(mo);
 }
 void tnsMMeshRemoveVertEdgeFace(tnsMeshObject* mo, tnsMVert* mv){
     if(!mv) return; tnsMEdge* me;
     while(me=lstPopPointerLeave(&mv->elink)){ tnsMMeshRemoveEdgeFace(mo,me); }
     lstRemoveItem(&mo->mv,mv); memLeave(mv); mo->totmv--;
+    tnsMMeshClearFirstLastSelection(mo);
 }
 
 void tnsMMeshRefreshIndex(tnsMeshObject* mo){
@@ -618,6 +630,9 @@ void tnsMeshLeaveEditMode(tnsMeshObject* mo){
     tnsInvalidateMeshBatch(mo);
 }
 
+void tnsMMeshClearFirstLastSelection(tnsMeshObject* mo){
+    mo->FirstSelectE=mo->FirstSelectV=mo->LastSelectE=mo->LastSelectV=0;
+}
 int tnsMMeshAnySelected(tnsMeshObject* mo){
     for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ if(mv->flags&TNS_MESH_FLAG_SELECTED) return 1; }
     for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ if(me->flags&TNS_MESH_FLAG_SELECTED) return 1; }
@@ -632,6 +647,7 @@ void tnsMMeshDeselectAll(tnsMeshObject* mo){
     for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ mv->flags&=(~TNS_MESH_FLAG_SELECTED); }
     for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ me->flags&=(~TNS_MESH_FLAG_SELECTED); }
     for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ mf->flags&=(~TNS_MESH_FLAG_SELECTED); }
+    tnsMMeshClearFirstLastSelection(mo);
 }
 void tnsMMeshSelectAll(tnsMeshObject* mo){
     for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ mv->flags|=TNS_MESH_FLAG_SELECTED; }
@@ -642,11 +658,13 @@ void tnsMMeshSelectVert(tnsMeshObject* mo, tnsMVert* mv, int select, int toggle)
     if(!mo) return;
     if(toggle) tnsMMeshSelectVert(mo,mv,(mv->flags&TNS_MESH_FLAG_SELECTED?0:1),0);
     elif(select) mv->flags|=TNS_MESH_FLAG_SELECTED; else mv->flags&=(~TNS_MESH_FLAG_SELECTED);
+    if(!mo->FirstSelectV) mo->FirstSelectV=mv; mo->LastSelectV=mv;
 }
 void tnsMMeshSelectEdge(tnsMeshObject* mo, tnsMEdge* me, int select, int toggle){
     if(!mo) return;
     if(toggle) tnsMMeshSelectEdge(mo,me,(me->flags&TNS_MESH_FLAG_SELECTED?0:1),0);
     elif(select) me->flags|=TNS_MESH_FLAG_SELECTED; else me->flags&=(~TNS_MESH_FLAG_SELECTED);
+    if(!mo->FirstSelectE) mo->FirstSelectE=me; mo->LastSelectE=me;
 }
 void tnsMMeshEnsureSelectionFromVerts(tnsMeshObject* mo){
     for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ me->flags&=(~TNS_MESH_FLAG_SELECTED);
@@ -734,6 +752,58 @@ int tnsMMeshSelectRingBandFrom(tnsMeshObject* mo, tnsMEdge* me, int ring_band, i
     while(lstPopPointer(&lst)); return 1;
 }
 
+void tnsMMeshReduceFaceEdge(tnsMeshObject* mo, tnsMFace* mf, tnsMEdge* me){
+    lstRemovePointerLeave(&mf->l,me); mf->looplen--;
+}
+void tnsMMeshFaceReplaceEdgeWith(tnsMFace* mf, tnsMEdge* to_be_replaced, tnsMEdge* as){
+    if(!mf) return; for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
+        if(lip->p==to_be_replaced){ lip->p=as; return; }
+    }
+}
+void tnsMMeshReduceZippedFace(tnsMeshObject* mo, tnsMFace* mf){
+    if(mf->looplen>2) return; if(mf->looplen<2){ printf("mf->looplen < 2 ?\n");return; }
+    tnsMEdge* me1=((laListItemPointer*)mf->l.pFirst)->p,*me2=((laListItemPointer*)mf->l.pLast)->p;
+    tnsMVert* vl=me1->vl; tnsMVert*vr=me1->vr;
+    lstRemovePointerLeave(&vl->elink,me2); lstRemovePointerLeave(&vr->elink,me2);
+    tnsMMeshFaceReplaceEdgeWith(me1->fl,me2,me1); tnsMMeshFaceReplaceEdgeWith(me1->fr,me2,me1);
+    tnsMMeshFaceReplaceEdgeWith(me2->fl,me2,me1); tnsMMeshFaceReplaceEdgeWith(me2->fr,me2,me1);
+    if(me1->fl==mf){ me1->fl=((me2->fl==mf)?me2->fr:me2->fl); }elif(me1->fr==mf){ me1->fr=((me2->fl==mf)?me2->fr:me2->fl); }
+    lstRemoveItem(&mo->me,me2); mo->totme--; memLeave(me2);
+    lstRemoveItem(&mo->mf,mf); mo->totmf--; memLeave(mf);
+    tnsMMeshClearFirstLastSelection(mo);
+}
+int tnsMMeshVertsCanMerge(tnsMeshObject* mo, tnsMVert* into, tnsMVert* mv){
+    for(laListItemPointer* lip=into->elink.pFirst;lip;lip=lip->pNext){ tnsMEdge* me=lip->p;
+        for(laListItemPointer* lip2=mv->elink.pFirst;lip2;lip2=lip2->pNext){ tnsMEdge* me2=lip2->p; if(me==me2) continue;
+            tnsMVert* mvs=tnsMMeshEdgeShareVert(me,me2); if((!mvs)||mvs==mv||mvs==into) continue; int count=0;
+                if(me->fl) count++; if(me->fr) count++; if(me2->fl) count++; if(me2->fr) count++;
+                tnsMFace* sf=tnsMMeshEdgeShareFace(me,me2); if(sf){count--;}
+                if ((sf==me->fl && me->fr && (me->fr==me2->fl||me->fr==me2->fr))||
+                    (sf==me->fr && me->fl && (me->fl==me2->fl||me->fl==me2->fr))){ return 0; }
+                if((sf && count>3) || (!sf && count>2)){ return 0; }
+        }
+    }
+    return 1;
+}
+int tnsMMeshMergeVerts(tnsMeshObject* mo, tnsMVert* into, tnsMVert* mv){
+    if(!tnsMMeshVertsCanMerge(mo,into,mv)) return 0;
+    tnsMEdge* me=tnsMMeshMakeEdge(mo,into,mv);
+    if(me->fl){ tnsMMeshReduceFaceEdge(mo,me->fl,me); }
+    if(me->fr){ tnsMMeshReduceFaceEdge(mo,me->fr,me); }
+    tnsMEdge* me2; while(me2=lstPopPointerLeave(&mv->elink)){
+        if(me2==me) continue; lstAppendPointer(&into->elink,me2);
+        if(me2->vl==mv){ me2->vl=into; }elif(me2->vr==mv){ me2->vr=into; }
+    }
+    if(me->fl){ tnsMMeshReduceZippedFace(mo,me->fl); }
+    if(me->fr){ tnsMMeshReduceZippedFace(mo,me->fr); }
+    lstRemovePointerLeave(&into->elink,me);
+    lstRemoveItem(&mo->mv,mv); memLeave(mv); mo->totmv--;
+    lstRemoveItem(&mo->me,me); memLeave(me); mo->totme--;
+    tnsMMeshClearFirstLastSelection(mo);
+    tnsPrintMeshTopology(mo);
+    return 1;
+}
+
 tnsMeshObject *tnsCreateMeshEmpty(tnsObject *under, char *Name, real AtX, real AtY, real AtZ){
     tnsMeshObject *mo = memAcquireHyper(sizeof(tnsMeshObject));
     tnsInitObjectBase(&mo->Base, under, Name, TNS_OBJECT_MESH, AtX, AtY, AtZ, 0, 0, 0, 1.0f, TNS_ROTATION_XYZ_EULER, 1.0f);

+ 75 - 2
resources/la_modelling.c

@@ -1080,7 +1080,7 @@ tnsMFace* la_MakeFacesFrom1Vert(tnsMeshObject* mo, tnsMVert* mv){
 }
 tnsMFace* la_MakeFacesFrom2Verts(tnsMeshObject* mo, tnsMVert* mv1, tnsMVert* mv2){
     tnsMEdge* oe1=0,*oe2=0; tnsMVert* ov1,*ov2; tnsMFace* sf=0;
-    tnsMEdge* se=tnsMMeshVertShareEdge(mv1,mv2);  if(se->fl && se->fr) return 0; sf=se->fl?se->fl:se->fr;
+    tnsMEdge* se=tnsMMeshVertsShareEdge(mv1,mv2);  if(se->fl && se->fr) return 0; sf=se->fl?se->fl:se->fr;
     for(laListItemPointer* lip=mv1->elink.pFirst;lip;lip=lip->pNext){
         tnsMEdge* oe=lip->p; if(oe->flags&TNS_MESH_FLAG_SELECTED) continue; if(M_SHOULD_USE_OE(oe,sf)){ if(!oe1)oe1=oe;else return 0; /* more than 1 empty edge connected */ }
     }
@@ -1107,7 +1107,7 @@ void la_EnsureIslandVertsSequence(MIslandInfo* ii){
     lstRemovePointer(&ii->v, startv); lstAppendPointer(&l, startv);
     while(ii->v.pFirst){
         for(laListItemPointer*lip=ii->v.pFirst;lip;lip=lip->pNext){ nextlip=lip->pNext;
-            if(tnsMMeshVertShareEdge(startv, lip->p)){ startv=lip->p; lstRemoveItem(&ii->v,lip); lstAppendItem(&l, lip); break; }
+            if(tnsMMeshVertsShareEdge(startv, lip->p)){ startv=lip->p; lstRemoveItem(&ii->v,lip); lstAppendItem(&l, lip); break; }
         }
     }
     memcpy(&ii->v, &l, sizeof(laListHandle));
@@ -1472,6 +1472,77 @@ int OPMOD_Knife(laOperator *a, laEvent *e){
     return LA_RUNNING;
 }
 
+#define LA_MERGE_MODE_CENTER 0
+#define LA_MERGE_MODE_FIRST 1
+#define LA_MERGE_MODE_LAST 2
+int la_MergeGetCenter(tnsMeshObject* mo, int SelectMode, int MergeMode, real* center){
+    real pos[3]={0},tmp[3]={0}; int count=0;
+    tnsMVert* usev=0; tnsMEdge* usee=0;
+    if(MergeMode!=LA_MERGE_MODE_CENTER){
+        if(MergeMode==LA_MERGE_MODE_FIRST){ usev=mo->FirstSelectV; usee=mo->FirstSelectE; }
+        elif(MergeMode==LA_MERGE_MODE_LAST){ usev=mo->LastSelectV; usee=mo->LastSelectE; }
+        if(SelectMode==LA_CANVAS_SELECT_MODE_VERTS && usev){ tnsVectorCopy3d(usev->p,center); return 1; }
+        elif(SelectMode==LA_CANVAS_SELECT_MODE_EDGES && usee){
+            tnsVectorAccum3d(pos,usee->vl->p);tnsVectorAccum3d(pos,usee->vr->p);
+            tnsVectorMultiSelf3d(pos,0.5f); tnsVectorCopy3d(pos,center); return 1;
+        }
+    }
+    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ if(!(mv->flags&TNS_MESH_FLAG_SELECTED)) continue;
+        tnsVectorAccum3d(pos,mv->p); count++;
+    }
+    if(!count) return 0;
+    tnsVectorMultiSelf3d(pos,1.0f/count); tnsVectorCopy3d(pos,center);
+    return 1;
+}
+void la_MergeSelected(tnsMeshObject* mo, real* center, int* count_success, int* count_fail){
+    tnsMVert* fmv=0,*NextMv=0; int success=0,fail=0;
+    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=NextMv){ NextMv=mv->Item.pNext;
+        if(!(mv->flags&TNS_MESH_FLAG_SELECTED)) continue; if(!fmv){ fmv=mv; success++; continue; }
+        if(tnsMMeshMergeVerts(mo,fmv,mv)){ success++; }else{ fail++; }
+    }
+    if(count_success){ *count_success=success; } if(count_fail){ *count_fail=fail; }
+    if(!fmv) return; tnsVectorCopy3d(center,fmv->p);
+    tnsMMeshRefreshIndex(mo);
+}
+int OPINV_Merge(laOperator *a, laEvent *e){
+    if(!a->This || !a->This->EndInstance){ return 0; }
+    laCanvasExtra* ex=a->This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi;
+    tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return LA_CANCELED;
+    tnsMeshObject* mo=root->Active; int ran=0;
+    
+    if(!mo || mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){ return LA_CANCELED; }
+
+    char* arg=strGetArgumentString(a->ExtraInstructionsP, "towards");
+    if(arg){
+        int MergeMode=LA_MERGE_MODE_CENTER; real center[3];
+        if(strSame(arg,"FIRST")){ MergeMode=LA_MERGE_MODE_FIRST; }
+        elif(strSame(arg,"LAST")){ MergeMode=LA_MERGE_MODE_LAST; }
+        if(!(la_MergeGetCenter(mo,ex->SelectMode,MergeMode,center))){
+            laEnableMessagePanel(a, 0, "Can't perform merging:", "No vertices selected.", e->x, e->y, 0, e);
+            return LA_FINISHED;
+        }
+        int success,fail;
+        la_MergeSelected(mo,center,&success,&fail); printf("succeeded: %d failed:%d\n",success,fail);
+        if(fail){
+            char* msg[256]; sprintf(msg,"Succeeded: %d vertices, failed: %d vertices.",success,fail);
+            laEnableMessagePanel(a, 0, "Merging partially succeeded.", msg, e->x, e->y, 0, e);
+        }
+        if(success){
+            tnsMMeshEnsureSelection(mo,ex->SelectMode); tnsInvalidateMeshBatch(mo); laNotifyUsers("tns.world");
+            laRecordAndPush(0,"tns.world","Merge vertices",TNS_HINT_GEOMETRY);
+        }
+    }else{
+        laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING;
+    }
+    return LA_FINISHED;
+}
+void laui_Merge(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){
+    laColumn* c=laFirstColumn(uil);
+    laShowItemFull(uil,c,pp,"_this_M_merge",0,"towards=CENTER;text=Center",0,0);
+    laShowItemFull(uil,c,pp,"_this_M_merge",0,"towards=FIRST;text=First",0,0);
+    laShowItemFull(uil,c,pp,"_this_M_merge",0,"towards=LAST;text=Last",0,0);
+}
+
 
 void la_RegisterModellingOperators(){
     laPropContainer *pc; laProp *p;
@@ -1501,5 +1572,7 @@ void la_RegisterModellingOperators(){
     laCreateOperatorType("M_duplicate", "Duplicate", "Duplicate objects", 0, 0, 0, OPINV_Duplicate, OPMOD_Transformation, 0, 0);
     laCreateOperatorType("M_recalculate_normals", "Recalculate Normals", "Recalculate normals", 0, 0, 0, OPINV_RecalculateNormals, 0, 0, 0);
     laCreateOperatorType("M_knife", "Knife", "Cut through edges", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Knife, OPMOD_Knife, 0, LA_EXTRA_TO_PANEL);
+    at=laCreateOperatorType("M_merge", "Merge", "Merge vertices", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Merge, OPMOD_FinishOnData, 0, LA_EXTRA_TO_PANEL);
+    at->UiDefine=laui_Merge;
     
 }

+ 3 - 0
resources/la_widgets_viewers.c

@@ -496,6 +496,7 @@ void laDefault3DViewOverlay(laUiItem *ui){
         laShowItem(gu,gc,&ui->ExtraPP,"_this_M_make");
         laShowItem(gu,gc,&ui->ExtraPP,"_this_M_knife");
         laShowItemFull(gu,gc,&ui->ExtraPP,"_this_M_knife",0,"mode=loop_cut;text=Loop Cut",0,0);
+        laShowItem(gu,gc,&ui->ExtraPP,"_this_M_merge");
         laShowItem(gu,gc,&ui->ExtraPP,"_this_M_subdiv");
         laShowItem(gu,gc,&ui->ExtraPP,"_this_M_separate");
         laShowItem(gu,gc,&ui->ExtraPP,"_this_M_combine");
@@ -1064,6 +1065,7 @@ void la_RegisterUiTypesViewerWidgets(){
         laAddOperatorProperty(pc, "_this_M_duplicate", "Duplicate", "Duplicate objects", "M_duplicate", 0, 0);
         laAddOperatorProperty(pc, "_this_M_recalculate_normals", "Recalculate Normals", "Recalculate normals", "M_recalculate_normals", 0, 0);
         laAddOperatorProperty(pc, "_this_M_knife", "Knife", "Cut though edges", "M_knife", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_merge", "Merge", "Merge vertices", "M_merge", 0, 0);
     }
 
     km = &ct->KeyMapper;
@@ -1106,4 +1108,5 @@ void la_RegisterUiTypesViewerWidgets(){
     laAssignNewKey(km, 0, "M_recalculate_normals", LA_KM_SEL_UI_EXTRA, LA_KEY_CTRL, LA_KEY_DOWN, 'n', 0);
     laAssignNewKey(km, 0, "M_knife", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'k', 0);
     laAssignNewKey(km, 0, "M_knife", LA_KM_SEL_UI_EXTRA, LA_KEY_CTRL, LA_KEY_DOWN, 'r', "mode=loop_cut");
+    laAssignNewKey(km, 0, "M_merge", LA_KM_SEL_UI_EXTRA, LA_KEY_ALT, LA_KEY_DOWN, 'm', 0);
 }