|  | @@ -60,6 +60,8 @@ tnsFace* tnsFillFace(tnsMeshObject* mo, int vertcount, ...){
 | 
	
		
			
				|  |  |      mo->totf++; return &mo->f[mo->totf-1];
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void tnsFillFaceLoop(tnsFace* f, int i, int v){ f->loop[i]=v; }
 | 
	
		
			
				|  |  | +void tnsFillFaceNormal(tnsFace* f, real* normal){ tnsVectorSet3v(f->n, normal); }
 | 
	
		
			
				|  |  | +void tnsFillVertNormal(tnsVert* v, real* normal){ tnsVectorSet3v(v->n, normal); }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  int tnsMergeMeshObjects(tnsMeshObject* into, tnsMeshObject* mo){
 | 
	
		
			
				|  |  |      if(into->Base.Type!=TNS_OBJECT_MESH||mo->Base.Type!=TNS_OBJECT_MESH) return 0;
 | 
	
	
		
			
				|  | @@ -81,7 +83,7 @@ int tnsMergeMeshObjects(tnsMeshObject* into, tnsMeshObject* mo){
 | 
	
		
			
				|  |  |  tnsMeshObject* tnsDuplicateMeshObjects(tnsMeshObject* from){
 | 
	
		
			
				|  |  |      if(from->Base.Type!=TNS_OBJECT_MESH) return 0;
 | 
	
		
			
				|  |  |      tnsMeshObject* to = memAcquireHyper(sizeof(tnsMeshObject));
 | 
	
		
			
				|  |  | -    tnsInitObjectBase(to, from->Base.ParentObject?from->Base.ParentObject:from->Base.InRoot, from->Base.Name, TNS_OBJECT_MESH,0,0,0,0,0,0,0,0,1);
 | 
	
		
			
				|  |  | +    tnsInitObjectBase(to, from->Base.ParentObject?from->Base.ParentObject:from->Base.InRoot, from->Base.Name->Ptr, TNS_OBJECT_MESH,0,0,0,0,0,0,0,0,1);
 | 
	
		
			
				|  |  |      tnsCopyObjectTransformationsLocal(to,from);
 | 
	
		
			
				|  |  |      if(!from->totv){ return to; }
 | 
	
		
			
				|  |  |      to->totv=from->totv; to->tote=from->tote; to->totf=from->totf;
 | 
	
	
		
			
				|  | @@ -98,7 +100,7 @@ void tnsInitMeshPlane(tnsMeshObject* mo, real size){
 | 
	
		
			
				|  |  |      tnsInitMesh(mo, 4,0,1);
 | 
	
		
			
				|  |  |      tnsFillVert(mo, size, size,0); tnsFillVert(mo,-size, size,0);
 | 
	
		
			
				|  |  |      tnsFillVert(mo,-size,-size,0); tnsFillVert(mo, size,-size,0);
 | 
	
		
			
				|  |  | -    tnsFillFace(mo, 4, 0,1,2,3);
 | 
	
		
			
				|  |  | +    tnsFace* f=tnsFillFace(mo, 4, 0,1,2,3); tnsVector3d n={0,0,1}; tnsFillFaceNormal(f,n);
 | 
	
		
			
				|  |  |      mo->v[0].flags|=TNS_MESH_FLAG_SELECTED;
 | 
	
		
			
				|  |  |      mo->v[1].flags|=TNS_MESH_FLAG_SELECTED;
 | 
	
		
			
				|  |  |      mo->v[2].flags|=TNS_MESH_FLAG_SELECTED;
 | 
	
	
		
			
				|  | @@ -114,68 +116,79 @@ void tnsAddMMeshPlane(tnsMeshObject* mo, real size){
 | 
	
		
			
				|  |  |      tnsMMeshMakeFace4v(mo,mv1,mv2,mv3,mv4);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -void tnsTrangulateFaceSimple(tnsMeshObject* mo, tnsFace* f, int* ebuf){
 | 
	
		
			
				|  |  | +void tnsTrangulateFaceSimple(tnsMeshObject* mo, int fi, tnsFace* f, int* ebuf){
 | 
	
		
			
				|  |  |      for(int i=0;i<f->looplen-2;i++){
 | 
	
		
			
				|  |  | -        ebuf[i*3]=f->loop[0];
 | 
	
		
			
				|  |  | +        ebuf[i*3]=f->loop[i];
 | 
	
		
			
				|  |  |          ebuf[i*3+1]=f->loop[i+1];
 | 
	
		
			
				|  |  | -        ebuf[i*3+2]=f->loop[i+2];
 | 
	
		
			
				|  |  | +        ebuf[i*3+2]=fi+mo->totv; //f->loop[i+2];
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | -void tnsTrangulateFaceSimpleM(tnsMeshObject* mo, tnsMFace* mf, int* ebuf){
 | 
	
		
			
				|  |  | +void tnsTrangulateFaceSimpleM(tnsMeshObject* mo, int fi, tnsMFace* mf, int* ebuf){
 | 
	
		
			
				|  |  |      tnsMVert* mv=0,*mvs; int i=0;
 | 
	
		
			
				|  |  |      for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
 | 
	
		
			
				|  |  |          laListItemPointer* next=lip->pNext; if(!next) next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
 | 
	
		
			
				|  |  |          if(next==mf->l.pLast){ break; }
 | 
	
		
			
				|  |  |          mvs=tnsMMeshEdgeStartingVert(me0,me1);
 | 
	
		
			
				|  |  |          if(!mv) mv=mvs;
 | 
	
		
			
				|  |  | -        ebuf[i*3]=mv->i; tnsMVert*mm=tnsMMeshEdgeAnotherVert(me0,mvs);
 | 
	
		
			
				|  |  | +        ebuf[i*3]=mvs->i; tnsMVert*mm=tnsMMeshEdgeAnotherVert(me0,mvs);
 | 
	
		
			
				|  |  |          ebuf[i*3+1]=mm->i;
 | 
	
		
			
				|  |  | -        ebuf[i*3+2]=tnsMMeshEdgeAnotherVert(me1,mm)->i; i++;
 | 
	
		
			
				|  |  | +        ebuf[i*3+2]=fi+mo->totmv; /*tnsMMeshEdgeAnotherVert(me1,mm)->i;*/ i++;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +tnsMVert* tnsGetMFaceLastVert(tnsMFace* mf){
 | 
	
		
			
				|  |  | +    laListItemPointer* lip=mf->l.pLast; laListItemPointer* next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
 | 
	
		
			
				|  |  | +    return tnsMMeshEdgeStartingVert(me0,me1);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  int* tnsGetTriangulatedBatch(tnsMeshObject* mo, int* totelem){
 | 
	
		
			
				|  |  |      int tottri=0;
 | 
	
		
			
				|  |  |      if(mo->Mode==TNS_MESH_OBJECT_MODE){ for(int i=0;i<mo->totf;i++){ tottri+=(mo->f[i].looplen-2); } }
 | 
	
		
			
				|  |  |      else{ for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tottri+=mf->looplen-2; } }
 | 
	
		
			
				|  |  |      if(!tottri) return 0;
 | 
	
		
			
				|  |  |      int* ebuf=calloc(1,sizeof(int)*tottri*3); int* pebuf=ebuf;
 | 
	
		
			
				|  |  | -    if(mo->Mode==TNS_MESH_OBJECT_MODE){ for(int i=0;i<mo->totf;i++){ tnsTrangulateFaceSimple(mo, &mo->f[i], pebuf); pebuf+=(mo->f[i].looplen-2)*3; } }
 | 
	
		
			
				|  |  | -    else{ for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tnsTrangulateFaceSimpleM(mo, mf, pebuf); pebuf+=(mf->looplen-2)*3; } }
 | 
	
		
			
				|  |  | +    if(mo->Mode==TNS_MESH_OBJECT_MODE){ for(int i=0;i<mo->totf;i++){ tnsTrangulateFaceSimple(mo, i, &mo->f[i], pebuf); pebuf+=(mo->f[i].looplen-2)*3; } }
 | 
	
		
			
				|  |  | +    else{ int i=0; for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tnsTrangulateFaceSimpleM(mo, i, mf, pebuf); pebuf+=(mf->looplen-2)*3; i++; } }
 | 
	
		
			
				|  |  |      *totelem = tottri;
 | 
	
		
			
				|  |  |      return ebuf;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | -float* tnsGetDrawingVertArray(tnsMeshObject* mo, int* r_totv, int DoIdColors, float** idcolors, int DoEditModeColors, float** editcolors, int** edgeelems, int* r_tote){
 | 
	
		
			
				|  |  | +float* tnsGetDrawingVertArray(tnsMeshObject* mo, int* r_tot_render_v, float** r_normals, float** idcolors, float** editcolors, int** edgeelems, int* r_tote){
 | 
	
		
			
				|  |  |      if(!mo->totv&&!mo->totmv) return 0;
 | 
	
		
			
				|  |  | -    int totv=mo->Mode==TNS_MESH_EDIT_MODE?mo->totmv:mo->totv; 
 | 
	
		
			
				|  |  | -    int tote=mo->Mode==TNS_MESH_EDIT_MODE?mo->totme:mo->tote; *r_tote=tote;
 | 
	
		
			
				|  |  | -    int extraverts=DoIdColors?mo->totme*2:0;
 | 
	
		
			
				|  |  | -    float* p=calloc(1,(totv+extraverts)*3*sizeof(float)); *r_totv=(totv+extraverts);
 | 
	
		
			
				|  |  | -    if(DoIdColors){ (*idcolors)=calloc(1,(totv+extraverts)*3*sizeof(float)); (*edgeelems)=calloc(1,(extraverts)*2*sizeof(int));}
 | 
	
		
			
				|  |  | -    if(DoEditModeColors){ (*editcolors)=calloc(1,totv*4*sizeof(float)*extraverts); }
 | 
	
		
			
				|  |  | -    if(mo->Mode==TNS_MESH_OBJECT_MODE){ for(int i=0;i<totv;i++){ p[i*3]=mo->v[i].p[0]; p[i*3+1]=mo->v[i].p[1]; p[i*3+2]=mo->v[i].p[2]; } }
 | 
	
		
			
				|  |  | -    else{ for(tnsMVert*mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ int i=mv->i;
 | 
	
		
			
				|  |  | -            p[i*3]=mv->p[0]; p[i*3+1]=mv->p[1]; p[i*3+2]=mv->p[2];
 | 
	
		
			
				|  |  | -            if(DoIdColors){
 | 
	
		
			
				|  |  | -                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;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            if(DoEditModeColors){
 | 
	
		
			
				|  |  | -                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];
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +    int objmode=mo->Mode==TNS_MESH_OBJECT_MODE;
 | 
	
		
			
				|  |  | +    int totv=objmode?mo->totv:mo->totmv;
 | 
	
		
			
				|  |  | +    int tote=objmode?mo->tote:mo->totme; *r_tote=tote;
 | 
	
		
			
				|  |  | +    int totf=objmode?mo->totf:mo->totmf;
 | 
	
		
			
				|  |  | +    int extraverts=objmode?mo->totf:(mo->totme*2+mo->totmf);
 | 
	
		
			
				|  |  | +    float* p=calloc(1,(totv+extraverts)*3*sizeof(float)); *r_tot_render_v=(totv+extraverts);
 | 
	
		
			
				|  |  | +    float* n=calloc(1,(totv+extraverts)*3*sizeof(float)); *r_normals = n;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if(objmode){
 | 
	
		
			
				|  |  | +        for(int i=0;i<totv;i++){ tnsVectorSet3v(&p[i*3],mo->v[i].p); tnsVectorSet3v(&n[i*3],mo->v[i].n); }
 | 
	
		
			
				|  |  | +        int start=mo->totv*3; for(int i=0;i<totf;i++){
 | 
	
		
			
				|  |  | +            tnsVectorSet3v(&p[start+i*3],mo->v[mo->f[i].loop[mo->f[i].looplen-1]].p); tnsVectorSet3v(&n[start+i*3],mo->f[i].n);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }else{
 | 
	
		
			
				|  |  | +        (*idcolors)=calloc(1,(totv+extraverts)*3*sizeof(float));
 | 
	
		
			
				|  |  | +        (*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];
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          for(tnsMEdge*me=mo->me.pFirst;me;me=me->Item.pNext){ int ei=me->i;
 | 
	
		
			
				|  |  |              (*edgeelems)[ei*2]=mo->totmv+ei*2; (*edgeelems)[ei*2+1]=mo->totmv+ei*2+1;
 | 
	
		
			
				|  |  |              float* eidcolor1=&(*idcolors)[(*edgeelems)[ei*2]*3], *eidcolor2=&(*idcolors)[(*edgeelems)[ei*2+1]*3];
 | 
	
		
			
				|  |  |              int id=ei+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;
 | 
	
		
			
				|  |  | -            eidcolor1[0]=r;eidcolor1[1]=g;eidcolor1[2]=b;
 | 
	
		
			
				|  |  | -            eidcolor2[0]=r;eidcolor2[1]=g;eidcolor2[2]=b;
 | 
	
		
			
				|  |  | -            int se1=(*edgeelems)[ei*2];   p[se1*3]=me->vl->p[0]; p[se1*3+1]=me->vl->p[1]; p[se1*3+2]=me->vl->p[2];
 | 
	
		
			
				|  |  | -            int se2=(*edgeelems)[ei*2+1]; p[se2*3]=me->vr->p[0]; p[se2*3+1]=me->vr->p[1]; p[se2*3+2]=me->vr->p[2];
 | 
	
		
			
				|  |  | +            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);
 | 
	
		
			
				|  |  | -            eedcolor1[0]=c[0]; eedcolor1[1]=c[1]; eedcolor1[2]=c[2]; eedcolor1[3]=c[3];
 | 
	
		
			
				|  |  | -            eedcolor2[0]=c[0]; eedcolor2[1]=c[1]; eedcolor2[2]=c[2]; eedcolor2[3]=c[3];
 | 
	
		
			
				|  |  | +            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++;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      return p;
 | 
	
	
		
			
				|  | @@ -194,18 +207,18 @@ void tnsInvalidateMeshBatch(tnsMeshObject* mo){
 | 
	
		
			
				|  |  |  void tnsRegenerateMeshBatch(tnsMeshObject* mo){
 | 
	
		
			
				|  |  |      if(!mo) return;
 | 
	
		
			
				|  |  |      if(mo->Batch) tnsDeleteBatch(mo->Batch); mo->Batch=0;
 | 
	
		
			
				|  |  | -    real meshcolor[4]={0.8,0.8,0.8,0.6};
 | 
	
		
			
				|  |  | +    real meshcolor[4]={0.8,0.8,0.8,1};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      int tottri; int* elem = tnsGetTriangulatedBatch(mo, &tottri);
 | 
	
		
			
				|  |  |      float* idcolors=0,*editcolors=0; int docolors=mo->Mode==TNS_MESH_EDIT_MODE;
 | 
	
		
			
				|  |  | -    int totv,tote; int* eelems=0;
 | 
	
		
			
				|  |  | -    float* v = tnsGetDrawingVertArray(mo,&totv,docolors,&idcolors,docolors,&editcolors,&eelems,&tote);
 | 
	
		
			
				|  |  | +    int totv,tote; int* eelems=0; float* n=0;
 | 
	
		
			
				|  |  | +    float* v = tnsGetDrawingVertArray(mo,&totv,&n,&idcolors,&editcolors,&eelems,&tote);
 | 
	
		
			
				|  |  |      if(!v){ if(elem){free(elem);} if(eelems){free(eelems);} return; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    mo->Batch = tnsCreateBatch(totv, 3, v, 0, 0, 4, editcolors);
 | 
	
		
			
				|  |  | +    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);
 | 
	
		
			
				|  |  | -    free(elem); free(v);
 | 
	
		
			
				|  |  | +    free(elem); free(v); free(n);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if(mo->Mode==TNS_MESH_EDIT_MODE){
 | 
	
		
			
				|  |  |          elem=tnsGetEdgeBatch(mo); if(elem) {
 | 
	
	
		
			
				|  | @@ -243,7 +256,9 @@ void tnsDrawMeshObject(tnsMeshObject* mo, int DrawAsObjectSelection, int MeshSel
 | 
	
		
			
				|  |  |              color[2]=(real)((i & 0x00FF0000)>>16)/255.0;
 | 
	
		
			
				|  |  |              tnsDrawBatch(mo->Batch,"body",color,0);
 | 
	
		
			
				|  |  |          }else{
 | 
	
		
			
				|  |  | +            tnsUseNormal(1);
 | 
	
		
			
				|  |  |              tnsDrawBatch(mo->Batch,"body",0,0);
 | 
	
		
			
				|  |  | +            tnsUseNormal(0);
 | 
	
		
			
				|  |  |              if(mo->Mode==TNS_MESH_EDIT_MODE){
 | 
	
		
			
				|  |  |                  if(MeshSelectionMode==LA_CANVAS_SELECT_MODE_VERTS){
 | 
	
		
			
				|  |  |                      tnsDrawBatch(mo->Batch,"verts",0,0); tnsDrawBatch(mo->Batch,"lines",0,0);
 | 
	
	
		
			
				|  | @@ -258,6 +273,48 @@ void tnsDrawMeshObject(tnsMeshObject* mo, int DrawAsObjectSelection, int MeshSel
 | 
	
		
			
				|  |  |  tnsMFace* tnsMMeshNewFace(tnsMeshObject* mo){ tnsMFace* mf=memAcquireSimple(sizeof(tnsMFace)); mf->i=mo->totmf; mo->totmf++; lstAppendItem(&mo->mf,mf); return mf; }
 | 
	
		
			
				|  |  |  tnsMEdge* tnsMMeshNewEdge(tnsMeshObject* mo){ tnsMEdge* me=memAcquireSimple(sizeof(tnsMEdge)); me->i=mo->totme; mo->totme++; lstAppendItem(&mo->me,me); return me; }
 | 
	
		
			
				|  |  |  tnsMVert* tnsMMeshNewVert(tnsMeshObject* mo){ tnsMVert* mv=memAcquireSimple(sizeof(tnsMVert)); mv->i=mo->totmv; mo->totmv++; lstAppendItem(&mo->mv,mv); return mv; }
 | 
	
		
			
				|  |  | +void tnsMMeshCalculateNormalFrom(tnsMFace* mf){
 | 
	
		
			
				|  |  | +    tnsVector3d accum={0}; tnsVector3d d0,d1; tnsVector3d naccum={0},nn={0};if(mf->flags&TNS_MESH_FLAG_PICKED) return;
 | 
	
		
			
				|  |  | +    for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
 | 
	
		
			
				|  |  | +        laListItemPointer* next=lip->pNext; if(!next) next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
 | 
	
		
			
				|  |  | +        tnsMVert* v0=tnsMMeshEdgeStartingVert(me0,me1); tnsMVert* v1=tnsMMeshEdgeShareVert(me0,me1); tnsMVert* v2=tnsMMeshEdgeAnotherVert(me1, v1);
 | 
	
		
			
				|  |  | +        tnsVectorAccum3d(accum, v0->p); tnsVectorAccum3d(accum, v1->p); tnsVectorAccum3d(accum, v2->p);
 | 
	
		
			
				|  |  | +        tnsVectorMinus3d(d0,v0->p,v1->p); tnsVectorMinus3d(d1,v2->p,v1->p); real len=tnsVectorCross3d(nn,d0,d1); tnsVectorMultiSelf3d(nn, len);
 | 
	
		
			
				|  |  | +        tnsVectorAccum3d(naccum, nn); 
 | 
	
		
			
				|  |  | +        //if(v0==me0->vr){ me0->flags|=TNS_MESH_FLAG_LOOP_REVERSE; } // TODO: consistency not implemented.
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    tnsNormalize3d(mf->n, naccum); tnsVectorMulti3d(mf->c, accum, 1.0/(mf->looplen+2));
 | 
	
		
			
				|  |  | +    mf->flags|=TNS_MESH_FLAG_PICKED;
 | 
	
		
			
				|  |  | +    if(mf->flags&TNS_MESH_FLAG_SELECTED){
 | 
	
		
			
				|  |  | +        for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
 | 
	
		
			
				|  |  | +            tnsMEdge* me=lip->p; tnsMFace* of=((mf==me->fl)?me->fr:me->fl);
 | 
	
		
			
				|  |  | +            if(of) tnsMMeshCalculateNormalFrom(of);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +void tnsMMeshCalculateFaceVertNormal(tnsMFace* mf){
 | 
	
		
			
				|  |  | +    tnsVector3d accum={0};
 | 
	
		
			
				|  |  | +    for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
 | 
	
		
			
				|  |  | +        laListItemPointer* next=lip->pNext; if(!next) next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
 | 
	
		
			
				|  |  | +        tnsMVert* mv=tnsMMeshEdgeStartingVert(me0,me1); int fcount=0;
 | 
	
		
			
				|  |  | +        for(laListItemPointer* el=mv->elink.pFirst;el;el=el->pNext){ tnsMEdge* ve=lip->p;
 | 
	
		
			
				|  |  | +            if(ve->fl){ tnsVectorAccum3d(accum, ve->fl->n); fcount++; }
 | 
	
		
			
				|  |  | +            if(ve->fr){ tnsVectorAccum3d(accum, ve->fr->n); fcount++; }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        if(!fcount) accum[2]=1; else tnsNormalizeSelf3d(accum); 
 | 
	
		
			
				|  |  | +        tnsVectorSet3v(mv->n, accum);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +int tnsMMeshCalculateNormal(tnsMeshObject* mo){
 | 
	
		
			
				|  |  | +    tnsMMeshClearExtraFlags(mo); int ran=0;
 | 
	
		
			
				|  |  | +    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ if((!(mf->flags&TNS_MESH_FLAG_SELECTED))||(mf->flags&TNS_MESH_FLAG_PICKED)) continue;
 | 
	
		
			
				|  |  | +        tnsMMeshCalculateNormalFrom(mf); ran=1;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ if(!(mf->flags&TNS_MESH_FLAG_PICKED)) continue;
 | 
	
		
			
				|  |  | +        tnsMMeshCalculateFaceVertNormal(mf); ran=1;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return ran;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  void tnsMMeshEdgeAssignVerts(tnsMEdge* me,tnsMVert* mv1,tnsMVert* mv2){
 | 
	
		
			
				|  |  |      if(me->vl||me->vr){ return; }  //if((me->vl==mv1&&me->vr=mv2) || (me->vl==mv2&&me->vr=mv1))
 | 
	
		
			
				|  |  |      me->vl=mv1; me->vr=mv2;
 | 
	
	
		
			
				|  | @@ -452,13 +509,13 @@ void tnsMMeshFromMesh(tnsMeshObject* mo){
 | 
	
		
			
				|  |  |          for(int j=0;j<ehv->next;j++){ tnsMEdge*me=tnsMMeshNewEdge(mo); ehv->e[j].me=me; }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      for(int i=0;i<mo->totv;i++){ tnsVert*v=&mo->v[i]; tnsMVert*mv=tnsMMeshNewVert(mo); eh->vl[i].mv=mv;
 | 
	
		
			
				|  |  | -        mv->p[0]=mo->v[i].p[0]; mv->p[1]=mo->v[i].p[1]; mv->p[2]=mo->v[i].p[2]; mv->flags=mo->v[i].flags; }
 | 
	
		
			
				|  |  | +        tnsVectorSet3v(mv->p,mo->v[i].p); mv->flags=mo->v[i].flags; tnsVectorSet3v(mv->n,v->n); }
 | 
	
		
			
				|  |  |      for(int i=0;i<mo->totf;i++){
 | 
	
		
			
				|  |  |          tnsFace* f=&mo->f[i]; tnsMFace* mf=tnsMMeshNewFace(mo); mf->flags=f->flags;
 | 
	
		
			
				|  |  |          for(int j=0;j<f->looplen;j++){ int v2=j+1; if(j==f->looplen-1) v2=0;
 | 
	
		
			
				|  |  |              tnsEdgeHashEdge* ehe=tnsEdgeHashGetEdge(eh,f->loop[j],f->loop[v2]);
 | 
	
		
			
				|  |  |              tnsMEdge* me=ehe->me; tnsMMeshEdgeAssignVerts(me,eh->vl[f->loop[j]].mv,eh->vl[f->loop[v2]].mv);
 | 
	
		
			
				|  |  | -            tnsMMeshFaceAddEdge(mf,me);
 | 
	
		
			
				|  |  | +            tnsMMeshFaceAddEdge(mf,me); tnsVectorSet3v(mf->n,f->n);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      tnsMMeshEnsureSelectionFromVerts(mo);
 | 
	
	
		
			
				|  | @@ -469,12 +526,13 @@ void tnsMeshFromMMesh(tnsMeshObject* mo){
 | 
	
		
			
				|  |  |      tnsInitMesh(mo, mo->totmv, 0, mo->totmf); int i=0;
 | 
	
		
			
				|  |  |      /* Vertex index should already correct. */
 | 
	
		
			
				|  |  |      //for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ mv->i=i; i++; }
 | 
	
		
			
				|  |  | -    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ tnsVert* v=tnsFillVert(mo, mv->p[0], mv->p[1], mv->p[2]); v->flags=mv->flags; }
 | 
	
		
			
				|  |  | -    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tnsFace* f=tnsFillFace(mo, mf->looplen, -1);  f->flags=mf->flags;
 | 
	
		
			
				|  |  | +    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ tnsVert* v=tnsFillVert(mo, LA_COLOR3(mv->p)); tnsFillVertNormal(v,mv->n); v->flags=mv->flags; }
 | 
	
		
			
				|  |  | +    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ tnsFace* f=tnsFillFace(mo, mf->looplen, -1); f->flags=mf->flags;
 | 
	
		
			
				|  |  |          int j=0; for(laListItemPointer* lip=mf->l.pFirst;lip;lip=lip->pNext){
 | 
	
		
			
				|  |  |              laListItemPointer* next=lip->pNext; if(!next) next=mf->l.pFirst; tnsMEdge* me0=lip->p, *me1=next->p;
 | 
	
		
			
				|  |  |              tnsFillFaceLoop(f, j, tnsMMeshEdgeStartingVert(me0,me1)->i); j++;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +        tnsFillFaceNormal(f,mf->n);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      mo->totv=mo->totmv; mo->totf=mo->totmf;
 | 
	
		
			
				|  |  |      mo->maxv=mo->totv; mo->maxe=mo->tote; mo->maxf=mo->totf;
 | 
	
	
		
			
				|  | @@ -502,9 +560,9 @@ int tnsMMeshAnySelected(tnsMeshObject* mo){
 | 
	
		
			
				|  |  |      for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ if(me->flags&TNS_MESH_FLAG_SELECTED) return 1; }
 | 
	
		
			
				|  |  |      for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ if(mf->flags&TNS_MESH_FLAG_SELECTED) return 1; } return 0;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | -void tnsMMeshClearPickedFlags(tnsMeshObject* mo){
 | 
	
		
			
				|  |  | +void tnsMMeshClearExtraFlags(tnsMeshObject* mo){
 | 
	
		
			
				|  |  |      for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ mv->flags&=(~TNS_MESH_FLAG_PICKED); }
 | 
	
		
			
				|  |  | -    for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ me->flags&=(~TNS_MESH_FLAG_PICKED); }
 | 
	
		
			
				|  |  | +    for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ me->flags&=(~TNS_MESH_FLAG_PICKED); /*me->flags&=(~TNS_MESH_FLAG_LOOP_REVERSE);*/ }
 | 
	
		
			
				|  |  |      for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){ mf->flags&=(~TNS_MESH_FLAG_PICKED); }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  void tnsMMeshDeselectAll(tnsMeshObject* mo){
 |