*/}}
Browse Source

Bezier shape selection and editing basics

YimingWu 8 tháng trước cách đây
mục cha
commit
648703f210
6 tập tin đã thay đổi với 263 bổ sung54 xóa
  1. 1 0
      la_kernel.c
  2. 12 3
      la_tns.h
  3. 149 24
      la_tns_shape.c
  4. 82 24
      resources/la_modelling.c
  5. 3 2
      resources/la_properties.c
  6. 16 1
      resources/la_widgets_viewers.c

+ 1 - 0
la_kernel.c

@@ -1096,6 +1096,7 @@ int laGetReadyWith(laInitArguments* ia){
     la_RegisterMainOperators();
     la_RegisterMainUiTypes();
     la_RegisterModellingOperators();
+    la_RegisterShapeOperators();
     la_RegisterInternalProps();
     la_RegisterAnimationResources();
     la_RegisterWindowKeys();

+ 12 - 3
la_tns.h

@@ -729,6 +729,14 @@ STRUCTURE(tnsMaterialSlot){
     short Index;
 };
 
+#define TNS_SPOINT_ALIGNED (1<<3)
+#define TNS_SPOINT_FREE    (1<<4)
+#define TNS_SPOINT_SELECTED_L (1<<5)
+#define TNS_SPOINT_SELECTED_R (1<<6)
+#define TNS_SPOINT_BEZIER (TNS_SPOINT_ALIGNED|TNS_SPOINT_FREE)
+#define TNS_SPOINT_SELECTED (TNS_MESH_FLAG_SELECTED|TNS_SPOINT_SELECTED_R|TNS_SPOINT_SELECTED_L)
+#define TNS_SHAPE_CLOSED   (1<<7)
+
 STRUCTURE(tnsSPoint){
     laListItem Item;
     tnsVector2d p,dl,dr;
@@ -991,7 +999,7 @@ real tnsDist3dv(tnsVector3d l, tnsVector3d r);
 real tnsDist2dv(tnsVector2d l, tnsVector2d r);
 
 real tnsLength3d(tnsVector3d l);
-real tnsLength2d(tnsVector3d l);
+real tnsLength2d(tnsVector2d l);
 void tnsNormalize3d(tnsVector3d result, tnsVector3d l);
 void tnsNormalizeSelf2d(tnsVector3d result);
 void tnsNormalizeSelf3d(tnsVector3d result);
@@ -1183,12 +1191,12 @@ void tnsEnsureMeshBatch(tnsMeshObject* mo);
 void tnsEvaluateMeshObject(tnsMeshObject* mo, tnsEvaluateData* ed);
 void tnsEvaluateShapeObject(tnsShapeObject* so, tnsEvaluateData* ed);
 
-
+void tnsShapeSetClosed(tnsShape* s, int closed, int toggle);
 int tnsShapePointAnySelected(tnsShape* s);
 int tnsShapeAnySelected(tnsShapeObject* so);
 void tnsShapeDeselectAll(tnsShapeObject* so);
 void tnsShapeSelectAll(tnsShapeObject* so);
-void tnsShapeSelectPoint(tnsShapeObject* so, tnsSPoint* sp, int select, int toggle);
+void tnsShapeSelectPoint(tnsShapeObject* so, tnsSPoint* sp, int select, int toggle, int lr);
 void tnsShapeEnterEditMode(tnsShapeObject* mo);
 void tnsShapeLeaveEditMode(tnsShapeObject* mo);
 void tnsShapeSelectRingFrom(tnsShapeObject* so, tnsSPoint* sp, int select, int toggle);
@@ -1199,6 +1207,7 @@ void tnsDrawShapePointsSelectionID(tnsEvaluatedInstance* ei, la3DObjectDrawExtra
 void tnsDrawShapeObject(tnsEvaluatedInstance* ei, la3DObjectDrawExtra* de);
 
 void la_RegisterModellingOperators();
+void la_RegisterShapeOperators();
 
 void tnsGetCameraProjection(tnsMatrix44d* mat, int w, int h, tnsCamera* Camera);
 void tnsGetCameraViewProjection(tnsMatrix44d* mat, int w, int h, tnsCamera* Camera);

+ 149 - 24
la_tns_shape.c

@@ -31,6 +31,10 @@ tnsSPoint* tnsNewSPoint(tnsShape* s,real x, real y,real ldx,real ldy, real rdx,r
     tnsVectorSet2(sp->p,x,y); tnsVectorSet2(sp->dl,ldx,ldy); tnsVectorSet2(sp->dr,rdx,rdy);
     lstAppendItem(&s->Points,sp); return sp;
 }
+void tnsShapeSetClosed(tnsShape* s, int closed, int toggle){
+    if(toggle) tnsShapeSetClosed(s,!(s->flags&TNS_SHAPE_CLOSED),0);
+    else{ if(closed) s->flags|=TNS_SHAPE_CLOSED; else s->flags&=(~TNS_SHAPE_CLOSED); }
+}
 
 void tnsShapeRefreshIndex(tnsShapeObject* so){
     u32bit i; for(tnsShape* s=so->Shapes.pFirst;s;s=s->Item.pNext){
@@ -39,25 +43,26 @@ void tnsShapeRefreshIndex(tnsShapeObject* so){
 }
 
 int tnsShapePointAnySelected(tnsShape* s){
-    for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ if(sp->flags&TNS_MESH_FLAG_SELECTED) return 1; } return 0;
+    for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ if(sp->flags&TNS_SPOINT_SELECTED) return 1; } return 0;
 }
 int tnsShapeAnySelected(tnsShapeObject* so){
     for(tnsShape* s=so->Shapes.pFirst;s;s=s->Item.pNext){ if(tnsShapePointAnySelected(s)) return 1; } return 0;
 }
 void tnsShapeDeselectAll(tnsShapeObject* so){
     for(tnsShape* s=so->Shapes.pFirst;s;s=s->Item.pNext){
-        for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ sp->flags&=(~TNS_MESH_FLAG_SELECTED); }
+        for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ sp->flags&=(~TNS_SPOINT_SELECTED); }
     }
 }
 void tnsShapeSelectAll(tnsShapeObject* so){
     for(tnsShape* s=so->Shapes.pFirst;s;s=s->Item.pNext){
-        for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ sp->flags|=TNS_MESH_FLAG_SELECTED; }
+        for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ sp->flags|=TNS_SPOINT_SELECTED; }
     }
 }
-void tnsShapeSelectPoint(tnsShapeObject* so, tnsSPoint* sp, int select, int toggle){
-    if(!so) return;
-    if(toggle) tnsShapeSelectPoint(so,sp,(sp->flags&TNS_MESH_FLAG_SELECTED?0:1),0);
-    elif(select) sp->flags|=TNS_MESH_FLAG_SELECTED; else sp->flags&=(~TNS_MESH_FLAG_SELECTED);
+void tnsShapeSelectPoint(tnsShapeObject* so, tnsSPoint* sp, int select, int toggle, int lr){
+    if(!so) return; int flag=(lr==1)?TNS_SPOINT_SELECTED_L:(lr==2)?TNS_SPOINT_SELECTED_R:(TNS_SPOINT_SELECTED);
+    if(!(sp->flags&TNS_SPOINT_BEZIER)){ flag=TNS_SPOINT_SELECTED; }
+    if(toggle) tnsShapeSelectPoint(so,sp,((sp->flags&flag)==flag?0:1),0,lr);
+    elif(select) sp->flags|=flag; else sp->flags&=(~flag);
     //if(!so->FirstSelectV) so->FirstSelectV=sp; so->LastSelectV=sp;
 }
 void tnsShapeSelectRingFrom(tnsShapeObject* so, tnsSPoint* sp, int select, int toggle){
@@ -66,13 +71,13 @@ void tnsShapeSelectRingFrom(tnsShapeObject* so, tnsSPoint* sp, int select, int t
     while(spl.pLast && ((laListItem*)spl.pLast)->pNext){ spl.pLast=((laListItem*)spl.pLast)->pNext; }
     if(toggle){
         for(tnsSPoint* isp=spl.pFirst;isp;isp=isp->Item.pNext){
-            if(isp!=sp && (!(isp->flags&TNS_MESH_FLAG_SELECTED))) has_idle=1;
+            if(isp!=sp && (!(isp->flags&TNS_SPOINT_SELECTED))) has_idle=1;
             if(has_idle){ break; }
         }
         if(has_idle){ select=1; }else{ select=0; }
     }
     for(tnsSPoint* isp=spl.pFirst;isp;isp=isp->Item.pNext){
-        if(select){ isp->flags|=TNS_MESH_FLAG_SELECTED; }else{ isp->flags&=(~TNS_MESH_FLAG_SELECTED); }
+        if(select){ isp->flags|=TNS_SPOINT_SELECTED; }else{ isp->flags&=(~TNS_SPOINT_SELECTED); }
     }
 }
 
@@ -115,37 +120,72 @@ void tnsInitShapeSquare(tnsShapeObject* so, real size){
     tnsNewSPoint(s,-size,size,0,0,0,0)->flags|=TNS_MESH_FLAG_SELECTED;
     tnsNewSPoint(s,size,size,0,0,0,0)->flags|=TNS_MESH_FLAG_SELECTED;
     tnsNewSPoint(s,size,-size,0,0,0,0)->flags|=TNS_MESH_FLAG_SELECTED;
-    tnsShapeRefreshIndex(so);
+    tnsShapeSetClosed(s,1,0); tnsShapeRefreshIndex(so);
 }
 
 void tns_DrawShape(tnsShape* s, real* override_color, int DrawEdit, real PointScale){
-    if(!s->Points.pFirst) return; int HasSelection=0;
+    if(!s->Points.pFirst) return; int HasSelection=0; int closed=(s->flags&TNS_SHAPE_CLOSED);
     NVGcontext* vg=MAIN.CurrentWindow->nvg;
     if(DrawEdit==2){
         nvgShapeAntiAlias(vg,0);
-        for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ nvgBeginPath(vg); nvgCircle(vg,sp->p[0],sp->p[1],PointScale*1);
-            real color[4]={0,0,0,1}; TNS_ID_TO_COLOR(color,sp->i+1);
-            nvgFillColor(vg,nvgRGBAf(LA_COLOR4(color))); nvgFill(vg);
+        for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ real color[4]={0,0,0,1};
+            nvgBeginPath(vg); nvgCircle(vg,sp->p[0],sp->p[1],PointScale*1);
+            TNS_ID_TO_COLOR(color,sp->i*3+1); nvgFillColor(vg,nvgRGBAf(LA_COLOR4(color))); nvgFill(vg);
+            nvgBeginPath(vg); nvgCircle(vg,sp->p[0]+sp->dl[0],sp->p[1]+sp->dl[1],PointScale*1);
+            TNS_ID_TO_COLOR(color,sp->i*3+2); nvgFillColor(vg,nvgRGBAf(LA_COLOR4(color))); nvgFill(vg);
+            nvgBeginPath(vg); nvgCircle(vg,sp->p[0]+sp->dr[0],sp->p[1]+sp->dr[1],PointScale*1);
+            TNS_ID_TO_COLOR(color,sp->i*3+3); nvgFillColor(vg,nvgRGBAf(LA_COLOR4(color))); nvgFill(vg);
         }
         nvgShapeAntiAlias(vg,1);
         return;
     }
     nvgBeginPath(vg);
-    tnsSPoint* sp1=s->Points.pFirst; nvgMoveTo(vg,sp1->p[0],sp1->p[1]); if(sp1->flags&TNS_MESH_FLAG_SELECTED){HasSelection=1;}
+    tnsSPoint* sp1=s->Points.pFirst; nvgMoveTo(vg,sp1->p[0],sp1->p[1]); if(sp1->flags&TNS_SPOINT_SELECTED){HasSelection=1;}
     for(tnsSPoint* sp=sp1->Item.pNext;sp;sp=sp->Item.pNext){
-        nvgLineTo(vg,sp->p[0],sp->p[1]); if(sp->flags&TNS_MESH_FLAG_SELECTED){HasSelection=1;}
+        if(sp->flags&TNS_SPOINT_SELECTED){HasSelection=1;}
+        if(sp->Item.pPrev){ tnsSPoint* np=sp->Item.pPrev;
+            real c1[2],c2[2]; tnsVectorSet2v(c1,np->p); tnsVectorSet2v(c2,sp->p);
+            if(np->flags&TNS_SPOINT_BEZIER){ tnsVectorAccum2d(c1,np->dr); }
+            if(sp->flags&TNS_SPOINT_BEZIER){ tnsVectorAccum2d(c2,sp->dl); }
+            nvgBezierTo(vg,c1[0],c1[1],c2[0],c2[1],sp->p[0],sp->p[1]);
+        }else{
+            nvgLineTo(vg,sp->p[0],sp->p[1]);
+        }
+    }
+    if(closed && (sp1!=s->Points.pLast)){
+        tnsSPoint* np=s->Points.pLast; tnsSPoint* sp=sp1;
+        real c1[2],c2[2]; tnsVectorSet2v(c1,np->p); tnsVectorSet2v(c2,sp->p);
+        if(np->flags&TNS_SPOINT_BEZIER){ tnsVectorAccum2d(c1,np->dr); }
+        if(sp->flags&TNS_SPOINT_BEZIER){ tnsVectorAccum2d(c2,sp->dl); }
+        nvgBezierTo(vg,c1[0],c1[1],c2[0],c2[1],sp->p[0],sp->p[1]);
+        nvgClosePath(vg);
+        nvgFillColor(vg, override_color?nvgRGBAf(LA_COLOR4(override_color)):nvgRGBAf(0.8,0.8,0.8,1));
+        nvgFill(vg);
     }
-    nvgClosePath(vg);
-	nvgFillColor(vg, override_color?nvgRGBAf(LA_COLOR4(override_color)):nvgRGBAf(0.8,0.8,0.8,1));
-	nvgFill(vg);
-    if(DrawEdit){
+    if(DrawEdit || (!closed)){
         tnsVector4d color; tnsVectorCopy4d(laAccentColor(LA_BT_VERTEX),color);
         real* ActiveColor=laAccentColor(LA_BT_SVERTEX);
         if(!HasSelection) color[3]*=0.4;
-        nvgStrokeColor(vg,nvgRGBAf(LA_COLOR4(color)));
-        nvgStrokeWidth(vg,PointScale*4); nvgStroke(vg);
-        for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ nvgBeginPath(vg); nvgCircle(vg,sp->p[0],sp->p[1],PointScale*5);
-            nvgFillColor(vg,sp->flags&TNS_MESH_FLAG_SELECTED?nvgRGBAf(LA_COLOR4(ActiveColor)):nvgRGBAf(LA_COLOR4(color)));
+        nvgStrokeColor(vg,override_color?nvgRGBAf(LA_COLOR4(override_color)):nvgRGBAf(LA_COLOR4(color)));
+        nvgStrokeWidth(vg,PointScale*4);
+        if(!DrawEdit){nvgShapeAntiAlias(vg,0);nvgStroke(vg);nvgShapeAntiAlias(vg,1);return;}
+        nvgStroke(vg);
+        for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ int mainsel=sp->flags&TNS_MESH_FLAG_SELECTED;
+            if(sp->flags&(TNS_SPOINT_FREE|TNS_SPOINT_ALIGNED)){
+                nvgStrokeWidth(vg,PointScale*2);
+                nvgStrokeColor(vg,(mainsel||(sp->flags&TNS_SPOINT_SELECTED_L))?nvgRGBAf(LA_COLOR4(ActiveColor)):nvgRGBAf(LA_COLOR4(color)));
+                nvgBeginPath(vg); nvgMoveTo(vg,sp->p[0]+sp->dl[0],sp->p[1]+sp->dl[1]); nvgLineTo(vg,sp->p[0],sp->p[1]);
+                nvgStroke(vg);
+                nvgBeginPath(vg); nvgCircle(vg,sp->p[0]+sp->dl[0],sp->p[1]+sp->dl[1],PointScale*5);
+                nvgStroke(vg);
+                nvgStrokeColor(vg,(mainsel||(sp->flags&TNS_SPOINT_SELECTED_R))?nvgRGBAf(LA_COLOR4(ActiveColor)):nvgRGBAf(LA_COLOR4(color)));
+                nvgBeginPath(vg); nvgMoveTo(vg,sp->p[0]+sp->dr[0],sp->p[1]+sp->dr[1]); nvgLineTo(vg,sp->p[0],sp->p[1]);
+                nvgStroke(vg);
+                nvgBeginPath(vg); nvgCircle(vg,sp->p[0]+sp->dr[0],sp->p[1]+sp->dr[1],PointScale*5);
+                nvgStroke(vg);
+            }
+            nvgBeginPath(vg); nvgCircle(vg,sp->p[0],sp->p[1],PointScale*5);
+            nvgFillColor(vg,mainsel?nvgRGBAf(LA_COLOR4(ActiveColor)):nvgRGBAf(LA_COLOR4(color)));
             nvgFill(vg);
         }
     }
@@ -210,3 +250,88 @@ tnsShapeObject *tnsCreateShapeSquare(tnsObject *under, char *Name, real AtX, rea
     return so;
 }
 
+int OPCHK_IsAnyPointSelected(laPropPack *This, laStringSplitor *ss){
+    if(This && This->EndInstance){
+        laCanvasExtra* ex=This->EndInstance; laUiItem* ui=ex->ParentUi;
+        tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return 0;
+        if(root->Active->Type!=TNS_OBJECT_SHAPE) return 0;
+        tnsShapeObject* so=root->Active; return tnsShapeAnySelected(so);
+    }
+    return 0;
+}
+int OPINV_SetPointHandle(laOperator *a, laEvent *e){
+    laCanvasExtra* ex=a->This->EndInstance; laUiItem* ui=ex->ParentUi;
+    tnsObject*root=ui?ui->PP.EndInstance:0;
+    if(!root || !root->Active || root->Active->Type!=TNS_OBJECT_SHAPE) return LA_FINISHED;
+    tnsShapeObject* so=root->Active; int ran=0;
+    char* mode=strGetArgumentString(a->ExtraInstructionsP,"mode");
+    if(!mode){
+        laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING;
+    } 
+    int flags=0,HandleFlags=TNS_SPOINT_ALIGNED|TNS_SPOINT_FREE;
+    if(strSame(mode,"ALIGNED")){ flags=TNS_SPOINT_ALIGNED; }
+    elif(strSame(mode,"FREE")){ flags=TNS_SPOINT_FREE; }
+    tnsVector2d pl={0,0},pr={0,0};
+    for(tnsShape*s=so->Shapes.pFirst;s;s=s->Item.pNext){
+        for(tnsSPoint*sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ if(!(sp->flags&TNS_MESH_FLAG_SELECTED)) continue;
+            int oldf=sp->flags&HandleFlags, hasl=0,hasr=0;
+            sp->flags&=(~HandleFlags); sp->flags|=flags; ran=1; if(!flags){ continue; }
+            if(sp->Item.pPrev){ tnsVectorSet2v(pl,((tnsSPoint*)sp->Item.pPrev)->p); hasl=1;}
+            if(sp->Item.pNext){ tnsVectorSet2v(pr,((tnsSPoint*)sp->Item.pNext)->p); hasr=1;}
+            if(!hasl){
+                if(!hasr){ tnsVectorSet2(pl,sp->p[0]-0.5,sp->p[1]); tnsVectorSet2(pr,sp->p[0]+0.5,sp->p[1]);  }
+                else{ tnsVectorSet2(pl,-pr[0]+2*sp->p[0],-pr[1]+2*sp->p[1]); }
+            }else{
+                if(!hasr){ tnsVectorSet2(pr,-pl[0]+2*sp->p[0],-pl[1]+2*sp->p[1]); }
+            }
+            if(flags==TNS_SPOINT_ALIGNED && flags!=oldf){
+                tnsVector2d diff; tnsVectorMinus2d(diff,pr,pl); tnsVectorMultiSelf2d(diff,0.25);
+                tnsVectorSet2v(sp->dr,diff); tnsVectorMultiSelf2d(diff,-1); tnsVectorSet2v(sp->dl,diff);
+            }elif(flags==TNS_SPOINT_FREE && oldf!=TNS_SPOINT_ALIGNED){
+                tnsVectorMinus2d(sp->dl,pl,sp->p); tnsVectorMultiSelf2d(sp->dl,0.3);
+                tnsVectorMinus2d(sp->dr,pr,sp->p); tnsVectorMultiSelf2d(sp->dr,0.3);
+            }
+        }
+    }
+    if(ran){
+        laRecordInstanceDifferences(so,"tns_shape_object"); laPushDifferences("Set point handle",TNS_HINT_GEOMETRY);
+        laNotifyUsers("tns.world");
+    }
+    return LA_FINISHED;
+}
+void laui_SetPointHandle(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){
+    laColumn* c=laFirstColumn(uil);
+    laShowItemFull(uil,c,pp,"_this_M_set_point_handle",0,"mode=NONE;text=Normal",0,0);
+    laShowItemFull(uil,c,pp,"_this_M_set_point_handle",0,"mode=ALIGNED;text=Aligned",0,0);
+    laShowItemFull(uil,c,pp,"_this_M_set_point_handle",0,"mode=FREE;text=Free",0,0);
+}
+
+int OPINV_SetShapeClosed(laOperator *a, laEvent *e){
+    laCanvasExtra* ex=a->This->EndInstance; laUiItem* ui=ex->ParentUi;
+    tnsObject*root=ui?ui->PP.EndInstance:0;
+    if(!root || !root->Active || root->Active->Type!=TNS_OBJECT_SHAPE) return LA_FINISHED;
+    tnsShapeObject* so=root->Active; int ran=0;
+
+    char* mode=strGetArgumentString(a->ExtraInstructionsP,"mode");
+    int set=2; if(strSame(mode,"CLOSE")){ set=1; }elif(strSame(mode,"OPEN")){ set=0; }
+
+    for(tnsShape*s=so->Shapes.pFirst;s;s=s->Item.pNext){
+        for(tnsSPoint*sp=s->Points.pFirst;sp;sp=sp->Item.pNext){ if(!(sp->flags&TNS_SPOINT_SELECTED)) continue;
+            tnsShapeSetClosed(s,set,set==2); ran=1; break;
+        }
+    }
+    if(ran){
+        laRecordInstanceDifferences(so,"tns_shape_object"); laPushDifferences("Open/Close shape",TNS_HINT_GEOMETRY);
+        laNotifyUsers("tns.world");
+    }
+    return LA_FINISHED;
+}
+void la_RegisterShapeOperators(){
+    laPropContainer *pc; laProp *p;
+    laOperatorType *at; laEnumProp *ep;
+
+    at=laCreateOperatorType("M_set_point_handle", "Set Point Handle", "Set handle type of selected points",OPCHK_IsAnyPointSelected,0,0,OPINV_SetPointHandle,OPMOD_FinishOnData,0,0);
+    at->UiDefine=laui_SetPointHandle;
+    at=laCreateOperatorType("M_set_shape_closed", "Set Shape Closed", "Set shape closed or open",OPCHK_IsAnyPointSelected,0,0,OPINV_SetShapeClosed,0,0,0);
+    
+}

+ 82 - 24
resources/la_modelling.c

@@ -162,8 +162,9 @@ void la_PopulateSelectPoints(MSelectData* sd, tnsShapeObject* so){
     if(so->Base.Type!=TNS_OBJECT_SHAPE||!so->Shapes.pFirst){ return; }
     for(tnsShape*s=so->Shapes.pFirst;s;s=s->Item.pNext){
         for(tnsSPoint*sp=s->Points.pFirst;sp;sp=sp->Item.pNext){
-            arrEnsureLength(&sd->RefsV, sd->nextV, &sd->maxV, sizeof(tnsSPoint*));
-            sd->RefsV[sd->nextV]=sp; sd->nextV++;
+            arrEnsureLength(&sd->RefsV, sd->nextV+3, &sd->maxV, sizeof(tnsSPoint*));
+            sd->RefsV[sd->nextV]=sp; sd->RefsV[sd->nextV+1]=sp; sd->RefsV[sd->nextV+2]=sp;
+            sd->nextV+=3;
         }
     }
     sd->nextV++;
@@ -326,8 +327,8 @@ void la_DoMeshSelect(tnsMeshObject* mo, void* p, int WhatPrim, int DeselectAll,
     if(p){ if(WhatPrim==LA_CANVAS_SELECT_MODE_VERTS) tnsMMeshSelectVert(mo,p,Select,Toggle);
         elif(WhatPrim==LA_CANVAS_SELECT_MODE_EDGES) tnsMMeshSelectEdge(mo,p,Select,Toggle);  }
 }
-void la_DoShapeSelect(tnsShapeObject* so, void* p, int DeselectAll, int Select, int Toggle){
-    if(DeselectAll){ tnsShapeDeselectAll(so); } if(p) tnsShapeSelectPoint(so,p,Select,Toggle);
+void la_DoShapeSelect(tnsShapeObject* so, void* p, int lr, int DeselectAll, int Select, int Toggle){
+    if(DeselectAll){ tnsShapeDeselectAll(so); } if(p) tnsShapeSelectPoint(so,p,Select,Toggle,lr);
 }
 
 #define LA_SELECT_MODE_BOX 1
@@ -397,7 +398,7 @@ int OPINV_Select(laOperator *a, laEvent *e){
         }
         int elemtype,id=la_SelectGetClosest(sd, e->x-ui->L, e->y-ui->U, LA_RH,&elemtype)-1;
         void* p=la_SelectGetRef(sd,id,elemtype);
-        la_DoShapeSelect(mo, p, DeselectAll, 1, 1);
+        la_DoShapeSelect(mo, p, id%3, DeselectAll, 1, 1);
         if(ring_band && p){ tnsShapeSelectRingFrom(mo,p,1,Append); }
         laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Shape selection",TNS_HINT_GEOMETRY);
     }else{
@@ -446,11 +447,11 @@ int OPMOD_Select(laOperator *a, laEvent *e){
             tnsMMeshEnsureSelection(mo,ex->SelectMode);
             tnsInvalidateMeshBatch(mo); 
         }elif(o && o->Type==TNS_OBJECT_SHAPE && so->Mode==TNS_MESH_EDIT_MODE){ is_geo=1;
-            la_DoShapeSelect(so, 0, DeselectAll, 0, 0);
+            la_DoShapeSelect(so, 0, 0, DeselectAll, 0, 0);
             int len; int* ids=la_SelectGetBox(se->sd, ex->ClickedX-ui->L, ex->ClickedY-ui->U, e->x-ui->L, e->y-ui->U, &len);
             for(int i=0;i<len;i++){
                 int id=ids[i]-1; void* p=la_SelectGetRef(se->sd,id,ex->SelectMode==LA_CANVAS_SELECT_MODE_EDGES?TNS_MMESH_EDGE_BIT:0);
-                la_DoShapeSelect(so, p, 0, !Remove, 0);
+                la_DoShapeSelect(so, p, id%3, 0, !Remove, 0);
             }
         }else{
             la_DoObjectSelect(se->root, 0, ex, DeselectAll, 0, 0);
@@ -491,8 +492,8 @@ STRUCTURE(MTOrigMVert){
     tnsMVert* mv;
 };
 STRUCTURE(MTOrigSPoint){
-    tnsVector2d p;
-    tnsVector2d origp;
+    tnsVector2d p,pl,pr;
+    tnsVector2d origp,origdl,origdr;
     tnsSPoint* sp;
 };
 STRUCTURE(MTransformData){
@@ -533,6 +534,9 @@ MTransformData* la_InitTransformData(int w, int h, tnsCamera* c, int CanvasDelta
 void la_GetTransformInitialScale(MTransformData* td, laUiItem* ui, int x, int y){ td->Initial=tnsDistIdv2(x-ui->L,y-ui->U,td->CenterX,td->CenterY); }
 void la_GetTransformInitialRotation(MTransformData* td, laUiItem* ui, int x, int y){ td->Initial=atan2(y-ui->U-td->CenterY,x-ui->L-td->CenterX); }
 void la_GetTransformCenter2D(MTransformData* td){
+    if(td->Is2D||td->so){
+        { td->CenterX=td->w/2; td->CenterY=td->h/2; } return;
+    }
     tnsVector4d vp; tnsApplyTransform44d(vp,td->ViewProjection,td->TCenter);
     if(td->c->CameraType==TNS_CAMERA_PERSP){ tnsVectorMultiSelf3d(vp, 1/vp[3]); }
     td->CenterX = (vp[0]/2+0.5f)*td->w; td->CenterY=(-vp[1]/2+0.5f)*td->h;
@@ -559,7 +563,7 @@ int la_PopulateTransformObjects(MTransformData* td, tnsObject* root){
     return any;
 }
 int la_PopulateTransformVerticies(MTransformData* td, tnsMeshObject* mo){
-    int any=0; td->mo=mo;
+    int any=0; td->mo=mo; td->next=0;
     arrEnsureLength(&td->Originals, 0, &td->max, sizeof(MTOrigMVert));
     tnsInverse44d(td->obmatinv, mo->Base.GlobalTransform);
     tnsInverse44d(td->deltainv, mo->Base.DeltaTransform);
@@ -586,12 +590,22 @@ int la_PopulateTransformPoints(MTransformData* td, tnsShapeObject* so){
     tnsInverse44d(td->deltainv, so->Base.DeltaTransform);
     for(tnsShape* s=so->Shapes.pFirst;s;s=s->Item.pNext){
         for(tnsSPoint* sp=s->Points.pFirst;sp;sp=sp->Item.pNext){
-            if(!(sp->flags&TNS_MESH_FLAG_SELECTED)) continue;
+            if(!(sp->flags&TNS_SPOINT_SELECTED)) continue;
+            int addl=0,addr=0,addmain=0;
+            if(sp->flags&TNS_MESH_FLAG_SELECTED) addmain=1;
+            elif(sp->flags&TNS_SPOINT_BEZIER){
+                if(sp->flags&TNS_SPOINT_SELECTED_L) addl=1; if(sp->flags&TNS_SPOINT_SELECTED_R) addr=1;
+                if(sp->flags&TNS_SPOINT_ALIGNED){ if(addl&&addr){ addmain=1; } }
+            }
             arrEnsureLength(&td->Originals, td->next, &td->max, sizeof(MTOrigSPoint));
             MTOrigSPoint* to=arrElement(td->Originals, td->next, sizeof(MTOrigSPoint)); td->next++; to->sp=sp;
-            tnsVector3d tp; tnsVector3d fp={sp->p[0],sp->p[1],0};
-            tnsApplyTransform43d(tp, so->Base.GlobalTransform, fp);
-            tnsVectorSet2v(to->p,tp); tnsVectorSet2v(to->origp,sp->p); any++;
+            tnsVector3d tp; tnsVector3d fp={sp->p[0],sp->p[1],0},
+                fpl={sp->p[0]+sp->dl[0],sp->p[1]+sp->dl[1],0},fpr={sp->p[0]+sp->dr[0],sp->p[1]+sp->dr[1],0};
+            tnsApplyTransform43d(tp, so->Base.GlobalTransform, fp); tnsVectorSet2v(to->p,tp);
+            tnsApplyTransform43d(tp, so->Base.GlobalTransform, fpl); tnsVectorSet2v(to->pl,tp);
+            tnsApplyTransform43d(tp, so->Base.GlobalTransform, fpr); tnsVectorSet2v(to->pr,tp);
+            tnsVectorSet2v(to->origp,sp->p); tnsVectorSet2v(to->origdl,sp->dl); tnsVectorSet2v(to->origdr,sp->dr);
+            any++;
             tnsVectorAccum2d(td->TCenter,to->p);
             tnsVectorAccum2d(td->TLCenter,sp->p);
         }
@@ -604,7 +618,7 @@ int la_PopulateTransformPoints(MTransformData* td, tnsShapeObject* so){
 void la_ApplyTranslation(MTransformData* td, int x, int y){
     tnsMatrix44d trans; tnsVector3d deltay,delta; tnsVector3d gp; tnsVector3d use_delta;
     if(td->Is2D || td->so){
-        use_delta[0]=x*(td->so?(1.0f/LA_RH):td->zoomx); use_delta[1]=-y*(td->so?(1.0f/LA_RH):td->zoomy); use_delta[2]=0;
+        use_delta[0]=x*(td->Is2D?td->zoomx:(1.0f/LA_RH)); use_delta[1]=-y*(td->Is2D?td->zoomy:(1.0f/LA_RH)); use_delta[2]=0;
         if(td->LockAxis[0]){ use_delta[1]=0; } if(td->LockAxis[1]){ use_delta[0]=0; }
     }else{
         tnsVectorMulti3d(delta, td->Right, x); tnsVectorMulti3d(deltay, td->Up, y); tnsVectorAccum3d(delta, deltay);
@@ -637,8 +651,21 @@ void la_ApplyTranslation(MTransformData* td, int x, int y){
         if(!td->UseLocal) tnsMultiply44d(final,td->obmatinv,trans);
         else tnsMultiply44d(final,trans,td->obmatinv);
         for(int i=0;i<td->next;i++){ MTOrigSPoint* to=arrElement(td->Originals, i, sizeof(MTOrigSPoint));
-            tnsVector3d fp={to->p[0],to->p[1],0}, tp;
-            tnsApplyTransform43d(tp, final, fp); tnsVectorSet3v(to->sp->p,tp);
+            tnsVector3d fp={to->p[0],to->p[1],0}, tp, tpl, tpr, fpl={to->pl[0],to->pl[1],0},fpr={to->pr[0],to->pr[1],0};
+            if((to->sp->flags&TNS_SPOINT_BEZIER) && (!(to->sp->flags&TNS_MESH_FLAG_SELECTED))){
+                if(to->sp->flags&TNS_SPOINT_SELECTED_L){ tnsApplyTransform43d(tpl, final, fpl); tnsVectorMinus2d(to->sp->dl,tpl,to->sp->p);
+                    if(to->sp->flags&TNS_SPOINT_ALIGNED){ real len=tnsLength2d(to->origdr);
+                        tnsVectorMulti2d(to->sp->dr,to->sp->dl,-1*len/tnsLength2d(to->sp->dl));
+                    }
+                }
+                if(to->sp->flags&TNS_SPOINT_SELECTED_R){ tnsApplyTransform43d(tpr, final, fpr); tnsVectorMinus2d(to->sp->dr,tpr,to->sp->p);
+                    if(to->sp->flags&TNS_SPOINT_ALIGNED){ real len=tnsLength2d(to->origdl);
+                        tnsVectorMulti2d(to->sp->dl,to->sp->dr,-1*len/tnsLength2d(to->sp->dr));
+                    }
+                }
+            }else{
+                tnsApplyTransform43d(tp, final, fp); tnsVectorSet2v(to->sp->p,tp);
+            }
         }
     }else{
         for(int i=0;i<td->next;i++){
@@ -681,8 +708,23 @@ void la_ApplyScale(MTransformData* td, int uix, int uiy){
         tnsInvalidateMeshBatch(td->mo); tnsMMeshCalculateNormal(td->mo);
     }elif(td->so){
         for(int i=0;i<td->next;i++){ MTOrigSPoint* to=arrElement(td->Originals, i, sizeof(MTOrigSPoint));
-            tnsVector3d fp={to->p[0],to->p[1],0}, tp;
-            tnsApplyTransform43d(tp, final, fp); tnsVectorSet3v(to->sp->p,tp);
+            tnsVector3d fp={to->p[0],to->p[1],0}, tp, tpl, tpr, fpl={to->pl[0],to->pl[1],0},fpr={to->pr[0],to->pr[1],0};
+            int domain=0;
+            if(to->sp->flags&TNS_MESH_FLAG_SELECTED){
+                tnsApplyTransform43d(tp, final, fp); tnsVectorSet2v(to->sp->p,tp); domain=1;
+            }
+            if((to->sp->flags&TNS_SPOINT_BEZIER)){
+                if((to->sp->flags&TNS_SPOINT_SELECTED_L) || domain){ tnsApplyTransform43d(tpl, final, fpl); tnsVectorMinus2d(to->sp->dl,tpl,to->sp->p);
+                    if((to->sp->flags&TNS_SPOINT_ALIGNED) && (!domain)){ real len=tnsLength2d(to->origdr);
+                        tnsVectorMulti2d(to->sp->dr,to->sp->dl,-1*len/tnsLength2d(to->sp->dl));
+                    }
+                }
+                if((to->sp->flags&TNS_SPOINT_SELECTED_R) || domain){ tnsApplyTransform43d(tpr, final, fpr); tnsVectorMinus2d(to->sp->dr,tpr,to->sp->p);
+                    if((to->sp->flags&TNS_SPOINT_ALIGNED) && (!domain)){ real len=tnsLength2d(to->origdl);
+                        tnsVectorMulti2d(to->sp->dl,to->sp->dr,-1*len/tnsLength2d(to->sp->dr));
+                    }
+                }
+            }
         }
     }else{
         for(int i=0;i<td->next;i++){
@@ -718,8 +760,23 @@ void la_ApplyRotation(MTransformData* td, int uix, int uiy){
         tnsInvalidateMeshBatch(td->mo); tnsMMeshCalculateNormal(td->mo);
     }elif(td->so){
         for(int i=0;i<td->next;i++){ MTOrigSPoint* to=arrElement(td->Originals, i, sizeof(MTOrigSPoint));
-            tnsVector3d fp={to->p[0],to->p[1],0}, tp;
-            tnsApplyTransform43d(tp, final, fp); tnsVectorSet3v(to->sp->p,tp);
+            tnsVector3d fp={to->p[0],to->p[1],0}, tp, tpl, tpr, fpl={to->pl[0],to->pl[1],0},fpr={to->pr[0],to->pr[1],0};
+            int domain=0;
+            if(to->sp->flags&TNS_MESH_FLAG_SELECTED){
+                tnsApplyTransform43d(tp, final, fp); tnsVectorSet2v(to->sp->p,tp); domain=1;
+            }
+            if((to->sp->flags&TNS_SPOINT_BEZIER)){
+                if((to->sp->flags&TNS_SPOINT_SELECTED_L) || domain){ tnsApplyTransform43d(tpl, final, fpl); tnsVectorMinus2d(to->sp->dl,tpl,to->sp->p);
+                    if((to->sp->flags&TNS_SPOINT_ALIGNED) && (!domain)){ real len=tnsLength2d(to->origdr);
+                        tnsVectorMulti2d(to->sp->dr,to->sp->dl,-1*len/tnsLength2d(to->sp->dl));
+                    }
+                }
+                if((to->sp->flags&TNS_SPOINT_SELECTED_R) || domain){ tnsApplyTransform43d(tpr, final, fpr); tnsVectorMinus2d(to->sp->dr,tpr,to->sp->p);
+                    if((to->sp->flags&TNS_SPOINT_ALIGNED) && (!domain)){ real len=tnsLength2d(to->origdl);
+                        tnsVectorMulti2d(to->sp->dl,to->sp->dr,-1*len/tnsLength2d(to->sp->dr));
+                    }
+                }
+            }
         }
     }else{
         for(int i=0;i<td->next;i++){
@@ -741,7 +798,9 @@ void la_CancelTransformObjects(MTransformData* td){
         for(int i=0;i<td->next;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert)); tnsVectorCopy3d(to->origp,to->mv->p); }
         tnsInvalidateMeshBatch(td->mo);
     }elif(td->so){
-        for(int i=0;i<td->next;i++){ MTOrigSPoint* to=arrElement(td->Originals, i, sizeof(MTOrigSPoint)); tnsVectorCopy2d(to->origp,to->sp->p); }
+        for(int i=0;i<td->next;i++){ MTOrigSPoint* to=arrElement(td->Originals, i, sizeof(MTOrigSPoint));
+            tnsVectorCopy2d(to->origp,to->sp->p); tnsVectorCopy2d(to->origdl,to->sp->dl);  tnsVectorCopy2d(to->origdr,to->sp->dr); 
+        }
     }else{
         for(int i=0;i<td->next;i++){ MTOrigObject* to=arrElement(td->Originals, i, sizeof(MTOrigObject));
             if(to->Discard){ tnsSelfMatrixChanged(to->o,1); continue; }
@@ -749,7 +808,6 @@ void la_CancelTransformObjects(MTransformData* td){
             if(td->CanvasDeltaMode) tnsGlobalMatrixChangedForDelta(to->o, 1);
             else tnsGlobalMatrixChanged(to->o, 1);
         }
-    
     }
 }
 void la_RecordTransformDifferences(MTransformData* td){
@@ -816,7 +874,7 @@ int la_InitTransform(laOperator* a, laEvent* e, int mode, int restore_type, int
     int ret=0;
     if(o && o->Type==TNS_OBJECT_MESH && mo->Mode==TNS_MESH_EDIT_MODE){
         if(la_PopulateTransformVerticies(td, mo)){ ex->ClickedX=e->x; ex->ClickedY=e->y; ret=1; }
-    }if(o && o->Type==TNS_OBJECT_SHAPE && so->Mode==TNS_MESH_EDIT_MODE){
+    }elif(o && o->Type==TNS_OBJECT_SHAPE && so->Mode==TNS_MESH_EDIT_MODE){
         if(la_PopulateTransformPoints(td, so)){ ex->ClickedX=e->x; ex->ClickedY=e->y; ret=1; }
     }else{
         if(la_PopulateTransformObjects(td,root)){ ex->ClickedX=e->x; ex->ClickedY=e->y; ret=1; }

+ 3 - 2
resources/la_properties.c

@@ -850,7 +850,7 @@ void* tnsget_MeshObjectFaceRaw(tnsMeshObject* o, int* r_size, int* r_is_copy){
         arr[i]=o->f[f].looplen; i++;
         arr[i]=o->f[f].flags; i++;
         arr[i]=o->f[f].mat; i++;
-        float* nm=(void*)&arr[i]; tnsVectorSet3v(nm,o->f[f].n); i+=3;
+        float* nm=(void*)&arr[i]; tnsVectorSet2v(nm,o->f[f].n); i+=3;
         for(int l=0;l<o->f[f].looplen;l++){
             arr[i]=o->f[f].loop[l]; i++;
         }
@@ -874,7 +874,7 @@ void tnsset_MeshObjectFaceRaw(tnsMeshObject* o, int* data, int s){
         f->looplen=data[i]; i++;
         f->flags=data[i]; i++;
         f->mat=data[i]; i++;
-        float* nm=(void*)&data[i]; tnsVectorSet3v(f->n,nm); i+=3;
+        float* nm=(void*)&data[i]; tnsVectorSet2v(f->n,nm); i+=3;
         f->loop=calloc(1,sizeof(int)*f->looplen);
         for(int li=0;li<f->looplen;li++){
             f->loop[li]=data[i]; i++;
@@ -1142,6 +1142,7 @@ void la_RegisterTNSProps(){
             laAddEnumItemAs(ep, "CAMERA", "Camera", "Camera object, to render a scene", TNS_OBJECT_CAMERA, U'📷');
             laAddEnumItemAs(ep, "LIGHT", "Lamp", "Lamp object, to illuminate the scene", TNS_OBJECT_LIGHT, 0);
             laAddEnumItemAs(ep, "MESH", "Mesh", "Mesh object, made of verts/edges/faces", TNS_OBJECT_MESH, 0);
+            laAddEnumItemAs(ep, "SHAPE", "Shape", "Shape object, 2d boundary", TNS_OBJECT_SHAPE, 0);
         }
         laAddFloatProperty(p, "location", "Location", "XYZ Location In Local Coordinates", 0,"X,Y,Z", 0,0,0,0.1, 0,0,offsetof(tnsObject, Location), 0,0,3, 0,tnsset_ObjectLocation, 0,0,0,0,0,0);
         laAddFloatProperty(p, "rotation", "Rotation", "Rotation In Local Coordinates", 0,"X,Y,Z", 0,0,0,0,0,0,offsetof(tnsObject, Rotation), 0,0,3, 0,tnsset_ObjectRotation, 0,0,0,0,0,0);

+ 16 - 1
resources/la_widgets_viewers.c

@@ -555,8 +555,17 @@ void laDefault3DViewOverlay(laUiItem *ui){
             laShowItem(uil,c,&ui->ExtraPP,"select_mode")->Flags|=LA_UI_FLAGS_EXPAND;
             laShowItem(uil,c,&ui->ExtraPP,"select_through");
         }laEndCondition(uil,b2);
-    }laEndCondition(uil,b1);
 
+        b2=laOnConditionThat(uil,c,laEqual(laPropExpression(&activeui->PP,"type"),laIntExpression(TNS_OBJECT_SHAPE)));{
+            muil=laMakeMenuPage(uil,c,"Shape"); mc=laFirstColumn(muil);{
+                laShowItem(muil,mc,&ui->ExtraPP,"_this_M_set_point_handle");
+                laUiItem* b3=laBeginRow(muil,mc,0,0);
+                laShowItemFull(muil,mc,&ui->ExtraPP,"_this_M_set_shape_closed",0,"text=Open;mode=OPEN",0,0);
+                laShowItemFull(muil,mc,&ui->ExtraPP,"_this_M_set_shape_closed",0,"text=Close Shape;mode=CLOSE",0,0);
+                laEndRow(muil,b3);
+            }
+        }laEndCondition(uil,b2);
+    }laEndCondition(uil,b1);
 
     laShowSeparator(uil,c)->Expand=1;
 
@@ -1141,6 +1150,9 @@ void la_RegisterUiTypesViewerWidgets(){
         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);
         laAddOperatorProperty(pc, "_this_M_clear_transformations", "Clear Transformations", "Clear object transformations", "M_clear_transformations", 0, 0);
+
+        laAddOperatorProperty(pc, "_this_M_set_point_handle", "Set Point Handle", "Set handle type of selected points", "M_set_point_handle", 0, 0);
+        laAddOperatorProperty(pc, "_this_M_set_shape_closed", "Set Shape Closed", "Open or close the shape", "M_set_shape_closed", 0, 0);
     }
 
     km = &ct->KeyMapper;
@@ -1185,4 +1197,7 @@ void la_RegisterUiTypesViewerWidgets(){
     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);
+
+    laAssignNewKey(km, 0, "M_set_point_handle", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 'q', 0);
+    laAssignNewKey(km, 0, "M_set_shape_closed", LA_KM_SEL_UI_EXTRA, 0, LA_KEY_DOWN, 't', 0);
 }