#include "../la_5.h"

extern LA MAIN;
extern struct _tnsMain *T;

int OPCHK_ThereIsActiveObject(laPropPack *This, laStringSplitor *ss){
    if(!This || !This->EndInstance){ return 0; }
    laCanvasExtra* ex=This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi;
    tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return 0;
    tnsMeshObject* mo=root->Active; if(!mo) return 0; return 1;
}
int OPINV_ToggleEdit(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 0;
    tnsMeshObject* mo=root->Active; if(!mo) return 0;
    if(mo->Base.Type!=TNS_OBJECT_MESH) return LA_CANCELED;
    if(mo->Mode==TNS_MESH_EDIT_MODE) tnsMeshLeaveEditMode(mo); else tnsMeshEnterEditMode(mo);
    laRecordInstanceDifferences(mo, "tns_mesh_object"); laPushDifferences("Toggle edit mode", TNS_HINT_GEOMETRY);
    laNotifyUsers("tns.world");
    return LA_FINISHED_PASS;
}

STRUCTURE(MSelectData){
    tnsOffscreen* FBO;
    tnsTexture* Color;
    void** Refs; int next,max;
};
MSelectData* la_InitSelectData(int w, int h, tnsCamera* camera){
    MSelectData* sd=memAcquireSimple(sizeof(MSelectData));
    if (!sd->FBO || sd->FBO->pColor[0]->Height != h || sd->FBO->pColor[0]->Width != w){
        if (sd->FBO) tnsDelete2DOffscreen(sd->FBO);
        sd->FBO = tnsCreate2DOffscreen(GL_RGB, w, h, 0, 1); sd->Color=sd->FBO->pColor[0];
    }
    tnsDrawToOffscreen(sd->FBO, 1, 0);
    return sd;
}
void la_AssignObjectSelectIDRecursive(tnsObject* root, MSelectData* sd){
    for(laListItemPointer*lip=root->ChildObjects.pFirst;lip;lip=lip->pNext){
        tnsObject* o=lip->p; if(!o) continue;
        arrEnsureLength(&sd->Refs, sd->next, &sd->max, sizeof(tnsObject*));
        sd->Refs[sd->next]=o; o->SelectID=sd->next; sd->next++;
        if(o->ChildObjects.pFirst){ la_AssignObjectSelectIDRecursive(o,sd); }
    }
}
void la_PopulateSelectDataObjects(MSelectData* sd, tnsObject* root, tnsCamera* camera){
    arrEnsureLength(&sd->Refs,0,&sd->max,sizeof(tnsObject*));
    sd->next++; // starting from 1;
    la_AssignObjectSelectIDRecursive(root, sd);
    if(sd->next==1) return; int w=sd->Color->Width, h=sd->Color->Height;
    tnsUnbindTexture(); tnsUniformUseTexture(T->immShader,0,0); tnsUseMultiplyColor(0);
    tnsEnableShaderv(T->SelectionShader);
    glDisableVertexAttribArray(T->SelectionShader->iColor); glVertexAttrib4f(T->SelectionShader->iColor,0,0,0,0);
    tnsViewportWithScissor(0,0,w,h);tnsResetViewMatrix();tnsResetModelMatrix();tnsResetProjectionMatrix();
    tnsApplyCameraView(w,h,camera);
    glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    tnsDrawObjectTree(root,0,0,0);
    glDisable(GL_DEPTH_TEST);
}
void la_PopulateSelectVerts(MSelectData* sd, tnsMeshObject* mo){
    arrEnsureLength(&sd->Refs,0,&sd->max,sizeof(tnsMVert*));
    if(mo->Base.Type!=TNS_OBJECT_MESH||!mo->mv.pFirst){ return; }
    for(tnsMVert* v=mo->mv.pFirst;v;v=v->Item.pNext){
        arrEnsureLength(&sd->Refs, v->i, &sd->max, sizeof(tnsObject*));
        sd->Refs[v->i]=v; sd->next=TNS_MAX2(v->i, sd->next);
    }
    sd->next++;
}
void la_PopulateSelectEdges(MSelectData* sd, tnsMeshObject* mo){
    arrEnsureLength(&sd->Refs,0,&sd->max,sizeof(tnsMEdge*));
    if(mo->Base.Type!=TNS_OBJECT_MESH||!mo->mv.pFirst){ return; }
    for(tnsMEdge* e=mo->me.pFirst;e;e=e->Item.pNext){
        arrEnsureLength(&sd->Refs, e->i, &sd->max, sizeof(tnsObject*));
        sd->Refs[e->i]=e; sd->next=TNS_MAX2(e->i, sd->next);
    }
    sd->next++;
}
void la_PopulateSelectDataPrimitives(MSelectData* sd, tnsMeshObject* mo, tnsCamera* camera, int WhatPrim){
    if(WhatPrim==LA_CANVAS_SELECT_MODE_VERTS){ la_PopulateSelectVerts(sd,mo); }
    elif(WhatPrim==LA_CANVAS_SELECT_MODE_EDGES){ la_PopulateSelectEdges(sd,mo); }
    int w=sd->Color->Width, h=sd->Color->Height;
    tnsUnbindTexture(); tnsUniformUseTexture(T->immShader,0,0); tnsUseMultiplyColor(0);
    tnsEnableShaderv(T->SelectionShader);
    tnsViewportWithScissor(0,0,w,h);tnsResetViewMatrix();tnsResetModelMatrix();tnsResetProjectionMatrix();
    tnsApplyCameraView(w,h,camera);
    glClearColor(0,0,0,0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    tnsPushMatrix(); tnsApplyObjectMatrix(mo);
    tnsDrawBatch(mo->Batch, (WhatPrim==LA_CANVAS_SELECT_MODE_VERTS)?"verts_select":"edges_select",0,0);
    tnsPopMatrix();
    glDisable(GL_DEPTH_TEST);
}
void la_PadSelectionBuffer(uint8_t* buf, int w, int h, int sx, int sy, int ex, int ey, int real_endx){
    if(!sx&&!sy&&!ex&&!ey) return;
    uint8_t* pad=calloc(1,sizeof(uint8_t)*4*w*h);
    for(int i=0;i<h;i++){
        if(i<sy || i>=h-ey){ memset(&pad[i*w*4],0,w*4); continue; }
        for(int j=0;j<w;j++){
            if(j<sx || j>=w-sx){ memset(&pad[(i*w+j)*4],0,4); continue; }
            memcpy(&pad[(i*w+j)*4], &buf[((i-sy)*(real_endx-sx)+j-sx)*4], 4);
        }
    }
    memcpy(buf,pad,sizeof(uint8_t)*4*w*h);
    free(pad);
}
uint8_t* la_ReadSelection(MSelectData* sd, u8bit* buf, int x, int y, int w, int h){
    glFlush(); glFinish(); 
    glGetError();
    glBindFramebuffer(GL_READ_FRAMEBUFFER, sd->FBO->FboHandle);
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glReadPixels(x,y,w,h, GL_RGBA, GL_UNSIGNED_BYTE, buf);
    return buf;
}
uint8_t* la_ReadSelectionRadius(MSelectData* sd, int uix, int uiy, int radius){
    int w=radius*2; int vh=sd->Color->Height,vw=sd->Color->Width;
    uint8_t* buf=calloc(1,sizeof(uint8_t)*4*w*w);
    int startx=uix-radius, starty=vh-uiy-radius, endx=uix+radius, endy=vh-uiy+radius;
    TNS_CLAMP(startx,0,vw);TNS_CLAMP(endx,0,vw);TNS_CLAMP(starty,0,vh);TNS_CLAMP(endy,0,vh);
    int _startx=TNS_MAX2(startx,0), _starty=TNS_MAX2(starty,0), _endx=TNS_MIN2(endx,vw), _endy=TNS_MIN2(endy,vh);
    la_ReadSelection(sd, buf, _startx, _starty, _endx-_startx, _endy-_starty);
    la_PadSelectionBuffer(buf, w, w, _startx-startx, _starty-starty, endx-_endx, endy-_endy,_endx);
    return buf;
}
uint8_t* la_ReadSelectionBox(MSelectData* sd, int uix, int uiy, int uix2, int uiy2){
    int vh=sd->Color->Height,vw=sd->Color->Width;
    if(uix2<uix) LA_SWAP(int,uix,uix2); if(uiy2<uiy) LA_SWAP(int,uiy,uiy2);
    TNS_CLAMP(uix,0,vw);TNS_CLAMP(uix2,0,vw);TNS_CLAMP(uiy,0,vh);TNS_CLAMP(uiy2,0,vh);
    int w=uix2-uix, h=uiy2-uiy;
    uint8_t* buf=calloc(1,sizeof(uint8_t)*4*w*h);
    int startx=uix, starty=vh-uiy2, endx=uix+w, endy=starty+h;
    int _startx=TNS_MAX2(startx,0), _starty=TNS_MAX2(starty,0), _endx=TNS_MIN2(endx,vw), _endy=TNS_MIN2(endy,vh);
    la_ReadSelection(sd, buf, _startx, _starty, _endx-_startx, _endy-_starty);
    la_PadSelectionBuffer(buf, w, h, _startx-startx, _starty-starty, endx-_endx, endy-_endy,_endx);
    return buf;
}
int la_SelectGetClosest(MSelectData* sd, int uix, int uiy, int radius){
    uint8_t* buf=la_ReadSelectionRadius(sd, uix, uiy, radius); if(!buf) return 0;
    int w=radius*2; int MinD=INT_MAX; int MinID=0, d;
    for(int i=0;i<w;i++){
        for(int j=0;j<w;j++){
            uint8_t* p=&buf[(i*w+j)*4]; int id=(p[0])|(p[1]<<8)|(p[2]<<16);
            if(id && (d=tnsDistIdv2(i,j, radius, radius))<MinD ){ MinD=d; MinID=id; }
            //printf("%d ",buf[(i*w+j)*4]);
        }
        //printf("\n");
    }
    free(buf);
    return MinID;
}
int* la_SelectGetBox(MSelectData* sd, int uix, int uiy, int uix2, int uiy2, int* r_length){
    uint8_t* buf=la_ReadSelectionBox(sd, uix, uiy, uix2, uiy2); if(!buf) return 0;
    int vh=sd->Color->Height,vw=sd->Color->Width;
    if(uix2<uix) LA_SWAP(int,uix,uix2); if(uiy2<uiy) LA_SWAP(int,uiy,uiy2);
    TNS_CLAMP(uix,0,vw);TNS_CLAMP(uix2,0,vw);TNS_CLAMP(uiy,0,vh);TNS_CLAMP(uiy2,0,vh);
    int w=uix2-uix, h=uiy2-uiy;
    int* ids=0; int next=0,max=0;
    arrEnsureLength(&ids, next, &max, sizeof(int));
    for(int i=0;i<h;i++){
        for(int j=0;j<w;j++){
            uint8_t* p=&buf[(i*w+j)*4]; int id=(p[0])|(p[1]<<8)|(p[2]<<16);
            if(id){ int found=0;
                for(int a=0;a<next;a++){ if(ids[a]==id){ found=1; break; } }
                if(!found){
                    arrEnsureLength(&ids, next, &max, sizeof(int)); ids[next]=id; next++; }
            }
        }
    }
    free(buf);
    *r_length=next;
    return ids;
}
void la_FreeSelectData(MSelectData* sd){
    tnsDelete2DOffscreen(sd->FBO);
    free(sd->Refs);
    memFree(sd);
}

int OPCHK_ViewportAndSceneExists(laPropPack *This, laStringSplitor *ss){
    if(!This || !This->EndInstance){ return 0; } laCanvasExtra* ex=This->EndInstance;
    laUiItem* ui=ex->ParentUi; tnsObject* root=ui?ui->PP.EndInstance:0;
    if(!ex->ViewingCamera || !root){ return 0; }
    return 1;
}
void la_DoObjectSelect(tnsObject* root, tnsObject* o, laCanvasExtra* e, int DeselectAll, int Select, int Toggle){
    if(DeselectAll){ tnsDeselectAllObjects(root); memAssignRef(root,&root->Active,0); }
    if(o){ tnsSelectObject(o, Select, o==root->Active?0:Toggle); memAssignRef(root,&root->Active,o); }
}
void la_DoMeshSelect(tnsMeshObject* mo, void* p, int WhatPrim, int DeselectAll, int Select, int Toggle){
    if(DeselectAll){ tnsMMeshDeselectAll(mo); }
    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);  }
}

#define LA_SELECT_MODE_BOX 1
STRUCTURE(MSelectExtra){
    MSelectData* sd;
    tnsObject* root;
    tnsObject* mo;
    tnsCamera* cam;
    int Mode;
    int InSelect;
};

int OPINV_Select(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 0;
    tnsMeshObject* mo=root->Active;

    if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"), "toggle")){
        if(mo && mo->Base.Type==TNS_OBJECT_MESH && mo->Mode==TNS_MESH_EDIT_MODE){
            if(tnsMMeshAnySelected(mo)) tnsMMeshDeselectAll(mo); else tnsMMeshSelectAll(mo);
            tnsInvalidateMeshBatch(mo);
        }else{
            if(tnsAnyObjectsSelected(root)) tnsDeselectAllObjects(root); else tnsSelectAllObjects(root);
        }
        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world", "Toggle selection",mo->Mode==TNS_MESH_EDIT_MODE?TNS_HINT_GEOMETRY:TNS_HINT_TRANSFORM);
        return LA_FINISHED;
    }
    
    MSelectData* sd=la_InitSelectData(ex->OffScr->pColor[0]->Width, ex->OffScr->pColor[0]->Height, c);

    int DeselectAll=1;
    int Append=e->SpecialKeyBit&LA_KEY_SHIFT; if(Append) DeselectAll=0;
    
    if(mo && mo->Base.Type==TNS_OBJECT_MESH && mo->Mode==TNS_MESH_EDIT_MODE){
        la_PopulateSelectDataPrimitives(sd, mo, ex->ViewingCamera, ex->SelectMode);
        if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"), "box")){
            MSelectExtra* se=memAcquire(sizeof(MSelectExtra));
            ex->OnX=e->x; ex->OnX=e->y;
            a->CustomData=se; se->sd=sd; se->Mode=LA_SELECT_MODE_BOX; se->mo=mo; se->cam=c; ex->DrawCursor=1; se->root=root; return LA_RUNNING;
        }
        int id=la_SelectGetClosest(sd, e->x-ui->L, e->y-ui->U, LA_RH)-1;
        void* p; if(id>=0 && id<sd->next){ p=sd->Refs[id]; }
        la_DoMeshSelect(mo, p, ex->SelectMode, DeselectAll, 1, 1); tnsMMeshEnsureSelection(mo,ex->SelectMode);
        tnsInvalidateMeshBatch(mo);
        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Mesh selection",TNS_HINT_GEOMETRY);
    }else{
        la_PopulateSelectDataObjects(sd,root,ex->ViewingCamera);
        if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"), "box")){
            MSelectExtra* se=memAcquire(sizeof(MSelectExtra));
            ex->OnX=e->x; ex->OnX=e->y;
            a->CustomData=se; se->sd=sd; se->Mode=LA_SELECT_MODE_BOX; se->cam=c; ex->DrawCursor=1; se->root=root; return LA_RUNNING;
        }
        int id=la_SelectGetClosest(sd, e->x-ui->L, e->y-ui->U, LA_RH*2);
        if(id && id<sd->next){ la_DoObjectSelect(root, sd->Refs[id], ex, DeselectAll, 1, 1);  }
        else{ la_DoObjectSelect(root, 0, ex, DeselectAll, 1, 1); }
        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Object selection",TNS_HINT_TRANSFORM);
    }

    la_FreeSelectData(sd);
    
    return LA_FINISHED_PASS;
}
int OPMOD_Select(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;
    tnsMeshObject* mo=root->Active;
    MSelectExtra* se=a->CustomData;

    if(e->Type==LA_L_MOUSE_DOWN){ se->InSelect=1; ex->DrawCursor=2; ex->ClickedX=e->x; ex->ClickedY=e->y; laRedrawCurrentPanel(); }
    if(e->Type&LA_MOUSE_EVENT){ ex->OnX=e->x; ex->OnY=e->y; laRedrawCurrentPanel(); }
    if(e->Type==LA_R_MOUSE_DOWN || (e->Type == LA_KEY_DOWN && e->key==LA_KEY_ESCAPE)){
        ex->DrawCursor=0; la_FreeSelectData(se->sd); laNotifyUsers("tns.world"); return LA_FINISHED;
    }

    int DeselectAll=1;
    int Append=e->SpecialKeyBit&LA_KEY_SHIFT; if(Append) DeselectAll=0;
    int Remove=e->SpecialKeyBit&LA_KEY_ALT; if(Remove) DeselectAll=0;

    if(se->InSelect && e->Type==LA_L_MOUSE_UP){
        if(mo && mo->Base.Type==TNS_OBJECT_MESH && mo->Mode==TNS_MESH_EDIT_MODE){
            la_DoMeshSelect(mo, 0, ex->SelectMode, 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; if(id>=0 && id<se->sd->next){ p=se->sd->Refs[id]; }
                la_DoMeshSelect(mo, p, ex->SelectMode, 0, !Remove, 0);
            }
            tnsMMeshEnsureSelection(mo,ex->SelectMode);
            tnsInvalidateMeshBatch(mo);
        }else{
            la_DoObjectSelect(se->root, 0, ex, DeselectAll, 0, 0);
            int len; int* ids=la_SelectGetBox(se->sd, ex->ClickedX, ex->ClickedY, e->x-ui->L, e->y-ui->U, &len);
            for(int i=0;i<len;i++){
                int id=ids[i]; if(id && id<se->sd->next){ la_DoObjectSelect(se->root, se->sd->Refs[id], ex, 0, !Remove, 0);  }
            }
        }
        laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Box selection",mo->Mode==TNS_MESH_EDIT_MODE?TNS_HINT_GEOMETRY:TNS_HINT_TRANSFORM);
        ex->DrawCursor=0;
        la_FreeSelectData(se->sd);
        laRedrawCurrentPanel();
        return LA_FINISHED;
    }
    
    return LA_RUNNING;
}


#define LA_TRANSFORM_MODE_GRAB   1
#define LA_TRANSFORM_MODE_ROTATE 2
#define LA_TRANSFORM_MODE_SCALE  3

STRUCTURE(MTOrigObject){
    tnsObject* o;
    tnsMatrix44d Global;
    tnsMatrix44d Local;
    int Discard;
};
STRUCTURE(MTOrigMVert){
    tnsVector3d p;
    tnsVector3d origp;
    tnsMVert* mv;
};
STRUCTURE(MTransformData){
    tnsMatrix44d Delta;
    tnsMatrix44d ViewProjection;
    tnsVector4d Up,Right,Foward;
    tnsVector4d TCenter;
    int CenterX,CenterY; real Initial;
    tnsObject* mo; tnsMatrix44d obmatinv;
    tnsCamera* c; tnsObject* root;
    int w,h;
    void* Originals; int next,max;
    int mode;
    int LockAxis[3];
    int UseLocal;
    real DeltaVal, UserDeltaVal;
    laStringEdit* Entry; int UseUserDelta;
};

MTransformData* la_InitTransformData(int w, int h, tnsCamera* c){
    MTransformData* td=memAcquireSimple(sizeof(MTransformData));
    tnsVector4d pu={0,1,0,0}, pr={1,0,0,0}, pf={0,0,1};
    tnsGetCameraMovingDeltas(c,w,h,1,0,pr); pr[2]=0; pr[3]=0;
    tnsGetCameraMovingDeltas(c,w,h,0,-1,pu); pu[2]=0; pu[3]=0;
    tnsMatrix44d inv; tnsInverse44d(inv,c->Base.GlobalTransform);
    tnsApplyRotation43d(td->Up,inv,pu);
    tnsApplyRotation43d(td->Right,inv,pr);
    tnsApplyRotation43d(td->Foward,inv,pf);

    tnsGetCameraViewProjection(td->ViewProjection, w,h,c);
    td->c=c; td->w=w; td->h=h;
    strBeginEdit(&td->Entry, "");
    return td;
}
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){
    tnsVector4d vp; tnsApplyTransform44d(vp,td->ViewProjection,td->TCenter);
    if(td->c->CameraType==TNS_PRESPECTIVE_CAMERA){ tnsVectorMultiSelf3d(vp, 1/vp[3]); }
    td->CenterX = (vp[0]/2+0.5f)*td->w; td->CenterY=(-vp[1]/2+0.5f)*td->h;
    if(td->CenterX<0||td->CenterY<0) { td->CenterX=td->w/2; td->CenterY=td->h/2; }
}
int la_AddTransformObjectsRecursive(MTransformData* td, tnsObject*root){
    int any=0; for(laListItemPointer* lip=root->ChildObjects.pFirst;lip;lip=lip->pNext){
        tnsObject* o=lip->p; if(o->ChildObjects.pFirst) any+=la_AddTransformObjectsRecursive(td,o);
        if(!(o->Flags&TNS_OBJECT_FLAGS_SELECTED)) continue;
        arrEnsureLength(&td->Originals, td->next, &td->max, sizeof(MTOrigObject));
        MTOrigObject* to=arrElement(td->Originals, td->next, sizeof(MTOrigObject)); td->next++; to->o=o;
        memcpy(to->Global, o->GlobalTransform, sizeof(tnsMatrix44d));
        memcpy(to->Local, o->SelfTransform, sizeof(tnsMatrix44d));
        tnsVectorAccum3d(td->TCenter,o->GLocation); any++;
        int discard=0; tnsObject* po=o; while(po=po->ParentObject){ if(po->Flags&TNS_OBJECT_FLAGS_SELECTED){ discard=1; break;} } if(discard) to->Discard=1;
    }
    return any;
}
int la_PopulateTransformObjects(MTransformData* td, tnsObject* root){
    arrEnsureLength(&td->Originals, 0, &td->max, sizeof(MTOrigObject));
    int any=la_AddTransformObjectsRecursive(td,root);
    tnsVectorMultiSelf3d(td->TCenter, 1.0f/any);
    la_GetTransformCenter2D(td);
    return any;
}
int la_PopulateTransformVerticies(MTransformData* td, tnsMeshObject* mo){
    int any=0; td->mo=mo;
    arrEnsureLength(&td->Originals, 0, &td->max, sizeof(MTOrigMVert));
    tnsInverse44d(td->obmatinv, mo->Base.GlobalTransform);
    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){
        if(!(mv->flags&TNS_MESH_FLAG_SELECTED)) continue;
        printf("v %d ",mv->i);
        arrEnsureLength(&td->Originals, td->next, &td->max, sizeof(MTOrigMVert));
        MTOrigMVert* to=arrElement(td->Originals, td->next, sizeof(MTOrigMVert)); td->next++; to->mv=mv;
        tnsApplyTransform43d(to->p, mo->Base.GlobalTransform, mv->p);
        memcpy(to->origp, mv->p, sizeof(tnsVector3d)); any++;
        tnsVectorAccum3d(td->TCenter,to->p);
    }
    printf(" [totmv %d]\n",mo->totmv);
    tnsVectorMultiSelf3d(td->TCenter, 1.0f/any);
    la_GetTransformCenter2D(td);
    return any;
}
void la_ApplyTranslation(MTransformData* td, int x, int y){
    tnsMatrix44d trans; tnsVector3d deltay,delta; tnsVector3d gp;
    tnsVectorMulti3d(delta, td->Right, x); tnsVectorMulti3d(deltay, td->Up, y); tnsVectorAccum3d(delta, deltay);
    tnsVector3d use_delta={LA_COLOR3(delta)}; real len;
    if(td->LockAxis[0]||td->LockAxis[1]||td->LockAxis[2]){ len=tnsLength3d(delta); }
    if(td->LockAxis[0]>0){ use_delta[1]=use_delta[2]=0; real l=fabs(use_delta[0]); use_delta[0]=l?use_delta[0]*len/l:1e-7; }
    if(td->LockAxis[1]>0){ use_delta[0]=use_delta[2]=0; real l=fabs(use_delta[1]); use_delta[1]=l?use_delta[1]*len/l:1e-7; }
    if(td->LockAxis[2]>0){ use_delta[0]=use_delta[1]=0; real l=fabs(use_delta[2]); use_delta[2]=l?use_delta[2]*len/l:1e-7; }
    if(td->LockAxis[0]<0){ use_delta[0]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l*len/l:0); }
    if(td->LockAxis[1]<0){ use_delta[1]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l*len/l:0); }
    if(td->LockAxis[2]<0){ use_delta[2]=0; real l=tnsLength3d(use_delta); tnsVectorMultiSelf3d(use_delta, l?len/l*len/l:0); }
    td->DeltaVal=tnsLength3d(use_delta);
    if(td->UseUserDelta){
        tnsVectorMultiSelf3d(use_delta,1/tnsLength3d(use_delta)*td->UserDeltaVal);
        tnsVector3d lock={ td->LockAxis[0], td->LockAxis[1], td->LockAxis[2] };
        real dir=tnsDot3d(use_delta, lock, 0); tnsVectorMultiSelf3d(use_delta,(td->UserDeltaVal*dir<=0)?-1:1);
        td->DeltaVal=td->UserDeltaVal; 
    }
    if(!td->mo){
        for(int i=0;i<td->next;i++){
            MTOrigObject* to=arrElement(td->Originals, i, sizeof(MTOrigObject)); memcpy(to->o->GlobalTransform, to->Global,sizeof(tnsMatrix44d));
            if(to->Discard){ tnsSelfMatrixChanged(to->o,1); continue; }
            tnsGlobalMatrixChanged(to->o, 0);
            if(td->UseLocal) tnsMoveObjectLocal(to->o, LA_COLOR3(use_delta)); else tnsMoveObjectGlobal(to->o, LA_COLOR3(use_delta));
        }
    }else{
        tnsMakeTranslationMatrix44d(trans, LA_COLOR3(use_delta));
        for(int i=0;i<td->next;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert));
            tnsApplyTransform43d(gp, trans, to->p); if(!td->UseLocal) tnsApplyTransform43d(to->mv->p, td->obmatinv, gp); else tnsVectorCopy3d(gp, to->mv->p);
        }
        tnsInvalidateMeshBatch(td->mo);
    }
}
void la_ApplyScale(MTransformData* td, int uix, int uiy){
    tnsMatrix44d trans; real d=tnsDistIdv2(uix,uiy,td->CenterX,td->CenterY); if(!td->Initial){ td->Initial=100; }
    real s=d/td->Initial; tnsVector3d gp;
    td->DeltaVal=s; if(td->UseUserDelta) td->DeltaVal=s=td->UserDeltaVal;
    if(!td->mo){
        for(int i=0;i<td->next;i++){
            MTOrigObject* to=arrElement(td->Originals, i, sizeof(MTOrigObject)); memcpy(to->o->GlobalTransform, to->Global,sizeof(tnsMatrix44d));
            if(to->Discard){ tnsSelfMatrixChanged(to->o,1); continue; }
            tnsGlobalMatrixChanged(to->o, 0); tnsScaleObject(to->o, s, LA_COLOR3(td->TCenter));
        }
    }else{
        tnsMakeScaleMatrix44d(trans,s,s,s);
        tnsMatrix44d t1,t2,t3; tnsMakeTranslationMatrix44d(t1,LA_COLOR3(td->TCenter)); tnsInverse44d(t2,t1);
        tnsMatrix44d final; tnsMultiply44d(t3,t1,trans); tnsMultiply44d(final,t3,t2);
        for(int i=0;i<td->next;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert));
            tnsApplyTransform43d(gp, final, to->p); tnsApplyTransform43d(to->mv->p, td->obmatinv, gp);
        }
        tnsInvalidateMeshBatch(td->mo);
    }
}
void la_ApplyRotation(MTransformData* td, int uix, int uiy){
    tnsMatrix44d trans; real a=atan2(uiy-td->CenterY,uix-td->CenterX);
    real angle=a-td->Initial; tnsVector3d gp; tnsVector3d LimFoward={0}; real* use_forward=td->Foward;
    if(td->LockAxis[0]||td->LockAxis[1]||td->LockAxis[2]){ use_forward=LimFoward; }
    if(td->LockAxis[0]){ LimFoward[0]=1; }
    if(td->LockAxis[1]){ LimFoward[1]=1; }
    if(td->LockAxis[2]){ LimFoward[2]=1; }
     if(td->UseUserDelta) angle=rad(td->UserDeltaVal); td->DeltaVal=deg(angle);
    if(!td->mo){
        for(int i=0;i<td->next;i++){
            MTOrigObject* to=arrElement(td->Originals, i, sizeof(MTOrigObject)); memcpy(to->o->GlobalTransform, to->Global,sizeof(tnsMatrix44d));
            if(to->Discard){ tnsSelfMatrixChanged(to->o,1); continue; }
            tnsGlobalMatrixChanged(to->o, 0); tnsRotateObjectGlobal(to->o,LA_COLOR3(use_forward),angle,LA_COLOR3(td->TCenter));
        }
    }else{
        tnsMakeRotationMatrix44d(trans, angle, LA_COLOR3(use_forward));
        tnsMatrix44d t1,t2,t3; tnsMakeTranslationMatrix44d(t1,LA_COLOR3(td->TCenter)); tnsInverse44d(t2,t1);
        tnsMatrix44d final; tnsMultiply44d(t3,t1,trans); tnsMultiply44d(final,t3,t2);
        for(int i=0;i<td->next;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert));
            tnsApplyTransform43d(gp, final, to->p); tnsApplyTransform43d(to->mv->p, td->obmatinv, gp);
        }
        tnsInvalidateMeshBatch(td->mo);
    }
}
void la_CancelTransformObjects(MTransformData* td){
    if(!td->mo){
        for(int i=0;i<td->next;i++){ MTOrigObject* to=arrElement(td->Originals, i, sizeof(MTOrigObject));
            if(to->Discard){ tnsSelfMatrixChanged(to->o,1); continue; }
            memcpy(to->o->GlobalTransform, to->Global,sizeof(tnsMatrix44d));
            tnsGlobalMatrixChanged(to->o, 1);
        }
    }else{
        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);
    }
}
void la_RecordTransformDifferences(MTransformData* td){
    if(!td->mo){
        for(int i=0;i<td->next;i++){ MTOrigObject* to=arrElement(td->Originals, i, sizeof(MTOrigObject));
            laRecordInstanceDifferences(to->o, "tns_object");
        } laPushDifferences(td->mode==LA_TRANSFORM_MODE_GRAB?"Moved objects":td->mode==LA_TRANSFORM_MODE_ROTATE?"Rotated objects":"Scaled objects", TNS_HINT_TRANSFORM);
    }else{
        laRecordInstanceDifferences(td->mo, "tns_mesh_object");
        laPushDifferences(td->mode==LA_TRANSFORM_MODE_GRAB?"Moved primitives":td->mode==LA_TRANSFORM_MODE_ROTATE?"Rotated primitives":"Scaled primitives", TNS_HINT_GEOMETRY);
        tnsInvalidateMeshBatch(td->mo);
    }
}
void la_FreeTransformData(MTransformData* td){
    free(td->Originals);
    strEndEdit(&td->Entry, 1);
    memFree(td);
}
void la_MakeTransformOperatorHint(laOperator* a, MTransformData* td){
    strSafeDestroy(&a->RuntimeHint);
    strSafePrint(&a->RuntimeHint, "%s  ",
        td->mode==LA_TRANSFORM_MODE_GRAB?"Grab":td->mode==LA_TRANSFORM_MODE_ROTATE?"Rotate":td->mode==LA_TRANSFORM_MODE_SCALE?"Scale":"");
    char* entry=strGetEditString(td->Entry,0);
    strSafePrint(&a->RuntimeHint, "Delta: %.3lf [🔢 %s]  🆇🆈🆉 Lock axis: %s  🈳 %s  ", td->DeltaVal, (entry&&entry[0])?entry:"Type...",
        td->LockAxis[0]?"X":td->LockAxis[1]?"Y":td->LockAxis[2]?"Z":"None",
        td->UseLocal?"Global/[Local]":"[Global]/Local");
    free(entry);
    if(td->mode==LA_TRANSFORM_MODE_GRAB){ strSafePrint(&a->RuntimeHint, "🡅🆇🆈🆉 Reverse: %s  ",
        (td->LockAxis[0]<0||td->LockAxis[1]<0||td->LockAxis[2]<0)?"Yes":"No"); }
    if(td->mode!=LA_TRANSFORM_MODE_GRAB){ strSafePrint(&a->RuntimeHint,"🅶 Grab  "); }
    if(td->mode!=LA_TRANSFORM_MODE_SCALE){ strSafePrint(&a->RuntimeHint,"🆂 Scale  "); }
    if(td->mode!=LA_TRANSFORM_MODE_ROTATE){ strSafePrint(&a->RuntimeHint,"🆁 Rotate  "); }
}
int la_InitTransform(laOperator* a, laEvent* e, int mode){
    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 0;
    tnsMeshObject* mo=root->Active;

    MTransformData* td=la_InitTransformData(ex->OffScr->pColor[0]->Width, ex->OffScr->pColor[0]->Height, c);
    a->CustomData = td;
    td->mode=mode;
    td->root=root;

    int ret=0;
    if(mo && mo->Base.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; }
    }else{
        if(la_PopulateTransformObjects(td,root)){ ex->ClickedX=e->x; ex->ClickedY=e->y; ret=1; }
    }
    
    if(ret){
        if(mode==LA_TRANSFORM_MODE_SCALE){ la_GetTransformInitialScale(td,ui,e->x,e->y); ex->DrawCursor=LA_CANVAS_CURSOR_ARROW; }
        elif(mode==LA_TRANSFORM_MODE_ROTATE){ la_GetTransformInitialRotation(td,ui,e->x,e->y); ex->DrawCursor=LA_CANVAS_CURSOR_ARROW; }
        ex->TargetX=td->CenterX+ui->L; ex->TargetY=td->CenterY+ui->U; ex->OnX=e->x; ex->OnY=e->y;
        la_MakeTransformOperatorHint(a, td);
        laNotifyUsers("tns.world"); return 1;
    }

    return 0;
}
int OPINV_Grab(laOperator *a, laEvent *e){
    if(la_InitTransform(a, e, LA_TRANSFORM_MODE_GRAB)) return LA_RUNNING; return LA_FINISHED_PASS;
}
int OPINV_Scale(laOperator *a, laEvent *e){
    if(la_InitTransform(a, e, LA_TRANSFORM_MODE_SCALE)) return LA_RUNNING; return LA_FINISHED_PASS;
}
int OPINV_Rotate(laOperator *a, laEvent *e){
    if(la_InitTransform(a, e, LA_TRANSFORM_MODE_ROTATE)) return LA_RUNNING; return LA_FINISHED_PASS;
}
int OPMOD_Transformation(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 0;
    tnsMeshObject* mo=root->Active;
    MTransformData* td=a->CustomData;

    if (e->Input=='x'||e->Input=='y'||e->Input=='z'||e->Input=='g'||e->Input=='s'||e->Input=='r'||
        e->Input=='X'||e->Input=='Y'||e->Input=='Z'||e->Input==' '){ /*pass*/ }
    else{ la_ProcessTextEdit(e, td->Entry, 0); }

    char* entered;
    if(entered=strGetEditString(td->Entry,0)){ int status;
        if((status=sscanf(entered,"%lf",&td->UserDeltaVal)) && status!=EOF) td->UseUserDelta=1; else td->UseUserDelta=0; 
        td->UserDeltaVal=fabs(td->UserDeltaVal); for(char*pc=entered;*pc;pc++){ if(*pc=='-') td->UserDeltaVal=-td->UserDeltaVal; } free(entered); 
    }

    if(e->Type==LA_KEY_DOWN){
        int Other=1; if(e->SpecialKeyBit&LA_KEY_SHIFT){ Other=-1; }
        if(e->key=='x'){ td->LockAxis[0]=Other*((Other<0&&td->LockAxis[0])?td->LockAxis[0]:!td->LockAxis[0]); td->LockAxis[1]=td->LockAxis[2]=0; }
        if(e->key=='y'){ td->LockAxis[1]=Other*((Other<0&&td->LockAxis[1])?td->LockAxis[1]:!td->LockAxis[1]); td->LockAxis[0]=td->LockAxis[2]=0; }
        if(e->key=='z'){ td->LockAxis[2]=Other*((Other<0&&td->LockAxis[2])?td->LockAxis[2]:!td->LockAxis[2]); td->LockAxis[0]=td->LockAxis[1]=0; }
        if(e->key=='g' && td->mode!=LA_TRANSFORM_MODE_GRAB){ td->mode=LA_TRANSFORM_MODE_GRAB; ex->DrawCursor=0; }
        if(e->key=='s' && td->mode!=LA_TRANSFORM_MODE_SCALE){ td->mode=LA_TRANSFORM_MODE_SCALE; la_GetTransformInitialScale(td,ui,e->x,e->y);ex->DrawCursor=LA_CANVAS_CURSOR_ARROW; }
        if(e->key=='r' && td->mode!=LA_TRANSFORM_MODE_ROTATE){ td->mode=LA_TRANSFORM_MODE_ROTATE; la_GetTransformInitialRotation(td,ui,e->x,e->y);ex->DrawCursor=LA_CANVAS_CURSOR_ARROW; }
        if(e->key==' '){ td->UseLocal=!td->UseLocal; }
    }

    if(e->Type==LA_MOUSEMOVE || e->Type==LA_KEY_DOWN){
        switch(td->mode){
            case LA_TRANSFORM_MODE_GRAB: la_ApplyTranslation(td,e->x-ex->ClickedX, e->y-ex->ClickedY); break;
            case LA_TRANSFORM_MODE_SCALE: la_ApplyScale(td,e->x-ui->L, e->y-ui->U); break;
            case LA_TRANSFORM_MODE_ROTATE: la_ApplyRotation(td,e->x-ui->L, e->y-ui->U); break;
            default: break;
        }
        ex->OnX=e->x; ex->OnY=e->y;
        laNotifyUsers("tns.world");
    }

    if(e->Type==LA_L_MOUSE_DOWN || (e->Type==LA_KEY_DOWN && e->key==LA_KEY_ENTER)){ ex->DrawCursor=0;
        la_RecordTransformDifferences(td);
        laNotifyUsers("tns.world"); la_FreeTransformData(td); return LA_FINISHED;
    }
    if(e->Type==LA_R_MOUSE_DOWN || e->Type==LA_ESCAPE_DOWN){ ex->DrawCursor=0;
        la_CancelTransformObjects(td); laNotifyUsers("tns.world"); la_FreeTransformData(td); return LA_FINISHED;
    }

    la_MakeTransformOperatorHint(a, td);

    return LA_RUNNING;
}

int la_ParentableRecursive(tnsObject* root, tnsObject* parent){
    for(laListItemPointer* lip=root->ChildObjects.pFirst;lip;lip=lip->pNext){
        tnsObject* o=lip->p; if((!o) || (!(o->Flags&TNS_OBJECT_FLAGS_SELECTED)) || (o==parent)) continue;
        if(!tnsCheckParentable(o,parent)) return 0;
        if(!la_ParentableRecursive(o,parent)) return 0;
    }return 1;
}
void la_MakeParentExecuteRecursive(tnsObject* root, tnsObject* parent, int Unparent, int KeepTransform){
    for(laListItemPointer* lip=root->ChildObjects.pFirst;lip;lip=lip->pNext){
        tnsObject* o=lip->p; if((!o) || (!(o->Flags&TNS_OBJECT_FLAGS_SELECTED))) continue;
        if(Unparent) tnsUnparentObject(o, KeepTransform);
        else tnsParentObject(o, parent, KeepTransform);
        la_MakeParentExecuteRecursive(o,parent,Unparent,KeepTransform);
    }
    laNotifyUsers("tns.world");
}
int OPINV_MakeParent(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 0;
    tnsObject* mo=root->Active;

    int Unparent=0,KeepTransform=1;
    char* action=strGetArgumentString(a->ExtraInstructionsP,"action");
    char* keep=strGetArgumentString(a->ExtraInstructionsP,"keep_transform");
    if(strSame(action,"unparent")){  Unparent=1; }
    if(strSame(keep,"false")){ KeepTransform=0; }

    if(!Unparent){ if((!mo) || (!(mo->Flags&TNS_OBJECT_FLAGS_SELECTED))) return LA_FINISHED; 
        if(!la_ParentableRecursive(root,mo)) laEnableMessagePanel(0,0,"It didn't work","There are loops in parenting",e->x,e->y,0,e); return LA_FINISHED;
    }

    if(keep){
        la_MakeParentExecuteRecursive(root,mo,Unparent,KeepTransform);
        laRecordInstanceDifferences(&T->World, "tns_world"); laPushDifferences(Unparent?"Unparent":"Parent", TNS_HINT_TRANSFORM);
        return LA_FINISHED;
    }
    laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e);
    return LA_RUNNING;
}
void laui_MakeParent(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){
    laColumn* c=laFirstColumn(uil);
    laShowItemFull(uil,c,pp,"_this_M_make_parent",0,"action=parent;keep_transform=true;text=Keep transform",0,0);
    laShowItemFull(uil,c,pp,"_this_M_make_parent",0,"action=parent;keep_transform=false;text=Directly",0,0);
}
void laui_Unparent(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){
    laColumn* c=laFirstColumn(uil);
    laShowItemFull(uil,c,pp,"_this_M_unparent",0,"action=unparent;keep_transform=true;text=Keep transform",0,0);
    laShowItemFull(uil,c,pp,"_this_M_unparent",0,"action=unparent;keep_transform=false;text=Directly",0,0);
}

int la_ClearTransformationRecursive(tnsObject* root, int global,int location,int rotation,int scale){
    int any=0; for(laListItemPointer* lip=root->ChildObjects.pFirst;lip;lip=lip->pNext){
        tnsObject* o=lip->p; if(!(o->Flags&TNS_OBJECT_FLAGS_SELECTED)) continue;
        if(location){
            if(global){ o->GLocation[0]=o->GLocation[1]=o->GLocation[2]=0;}
            else{ o->Location[0]=o->Location[1]=o->Location[2]=0; }
        }
        if(rotation){
            if(global){ o->GRotation[0]=o->GRotation[1]=o->GRotation[2]=0;}
            else{ o->Rotation[0]=o->Rotation[1]=o->Rotation[2]=0; }
        }
        if(scale){ if(global){ o->GScale=1;} else{ o->Scale=1; } }
        if(global) tnsGlobalTransformValueChanged(o); else tnsSelfTransformValueChanged(o);
        laRecordInstanceDifferences(o, "tns_object"); any++;
        any+=la_ClearTransformationRecursive(o,global,location,rotation,scale);
    } return any;
}
int OPINV_ClearTransformation(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 0;

    int global=0,location=0,rotation=0,scale=0;
    if(strSame(strGetArgumentString(a->ExtraInstructionsP,"global"),"true")){ global=1; }
    if(strSame(strGetArgumentString(a->ExtraInstructionsP,"location"),"true")){ location=1; }
    if(strSame(strGetArgumentString(a->ExtraInstructionsP,"rotation"),"true")){ rotation=1; }
    if(strSame(strGetArgumentString(a->ExtraInstructionsP,"scale"),"true")){ scale=1; }

    int any=0;
    if(location||rotation||scale){
        any=la_ClearTransformationRecursive(root,global,location,rotation,scale);
        if(any){ laPushDifferences("Clear Transformations", TNS_HINT_TRANSFORM); laNotifyUsers("tns.world"); }
    }

    return LA_FINISHED;
}


STRUCTURE(MEDupVert){
    int oi; tnsMVert* nmv; tnsMVert* omv; int IsBorder;
};
STRUCTURE(MEDupEdge){
    int oi; tnsMEdge* nme; tnsMEdge* ome; int IsBorder; 
};
STRUCTURE(MEDupFace){
    int oi; tnsMFace* nmf; tnsMFace* omf;
};
STRUCTURE(MExtrudeExtra){
    MEDupVert* dv; int nextv,maxv;
    MEDupEdge* de; int nexte,maxe;
    MEDupFace* df; int nextf,maxf;
    tnsMeshObject* mo;
    int RemoveOriginalFaces;
};

int la_IsSelectionBorderVertex(tnsMVert* mv){
    if(!mv->elink.pFirst)
        return 0;
    int NearSelected=0, NearUnselected=0;
    for(laListItemPointer* lip=mv->elink.pFirst;lip;lip=lip->pNext){
        tnsMEdge* me=lip->p; if(!(me->fl&&me->fr)) return 1;
        tnsMVert* av=tnsMMeshEdgeAnotherVert(me,mv);
        if(me->fl->flags&TNS_MESH_FLAG_SELECTED){ NearSelected=1; } else { return 1; }
        if(me->fr->flags&TNS_MESH_FLAG_SELECTED){ NearSelected=1; } else { return 1; }
        if(NearUnselected && NearSelected) return 1;
    }
    return 0;
}
int la_IsSelectionBorderEdge(tnsMEdge* me){
    if(me->fl&&me->fr){ 
        if ((me->fl->flags&TNS_MESH_FLAG_SELECTED) && (!(me->fr->flags&TNS_MESH_FLAG_SELECTED))||
            (me->fr->flags&TNS_MESH_FLAG_SELECTED) && (!(me->fl->flags&TNS_MESH_FLAG_SELECTED))) return 1;
        else return (me->fr->flags==me->fl->flags&&me->fl->flags==0);
    } return 1;
}
MExtrudeExtra* la_InitExtrude(tnsMeshObject* mo){
    MExtrudeExtra* ee=memAcquireSimple(sizeof(MExtrudeExtra));
    arrEnsureLength(&ee->dv, ee->nextv, &ee->maxv, sizeof(MEDupVert));
    arrEnsureLength(&ee->de, ee->nexte, &ee->maxe, sizeof(MEDupEdge));
    arrEnsureLength(&ee->df, ee->nextf, &ee->maxf, sizeof(MEDupFace));
    ee->mo=mo;
    return ee;
}
void la_ExtrudeMakeDuplication(MExtrudeExtra* ee){
    tnsMeshObject* mo=ee->mo;
    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ if(!(mv->flags&TNS_MESH_FLAG_SELECTED)) continue;
        arrEnsureLength(&ee->dv, ee->nextv, &ee->maxv, sizeof(MEDupVert));
        MEDupVert* dv=&ee->dv[ee->nextv];
        tnsMVert* nmv=tnsMMeshNewVert(mo); tnsVectorCopy3d(mv->p, &nmv->p); dv->oi=mv->i; mv->i=ee->nextv; dv->nmv=nmv; dv->omv=mv;
        dv->IsBorder=la_IsSelectionBorderVertex(mv);
        ee->nextv++;
    }
    for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ if(!(me->flags&TNS_MESH_FLAG_SELECTED)) continue;
        arrEnsureLength(&ee->de, ee->nexte, &ee->maxe, sizeof(MEDupEdge));
        MEDupEdge* de=&ee->de[ee->nexte];
        tnsMEdge* nme=tnsMMeshNewEdge(mo); de->oi=me->i; me->i=ee->nexte; de->nme=nme; de->ome=me; de->IsBorder=la_IsSelectionBorderEdge(me);
        tnsMMeshEdgeAssignVerts(nme, ee->dv[me->vl->i].nmv, ee->dv[me->vr->i].nmv);
        if(de->IsBorder&&me->fl&&me->fr){ ee->RemoveOriginalFaces=1; }
        ee->nexte++;
    }
    for(tnsMFace* mf=mo->mf.pFirst;mf;mf=mf->Item.pNext){
        if(!(mf->flags&TNS_MESH_FLAG_SELECTED)) continue;
        arrEnsureLength(&ee->df, ee->nextf, &ee->maxf, sizeof(MEDupFace));
        MEDupFace* df=&ee->df[ee->nextf];
        tnsMFace* nmf=tnsMMeshNewFace(mo); df->oi=mf->i; mf->i=ee->nextf; df->nmf=nmf; df->omf=mf;
        for(laListItemPointer*lip=mf->l.pFirst;lip;lip=lip->pNext){
            tnsMEdge* ome=lip->p; tnsMMeshFaceAddEdge(nmf,ee->de[ome->i].nme);
        }
        ee->nextf++;
    }
    for(int i=0;i<ee->nextv;i++){ ee->dv[i].omv->i=ee->dv[i].oi; ee->dv[i].omv->flags&=(~TNS_MESH_FLAG_SELECTED); ee->dv[i].nmv->flags|=TNS_MESH_FLAG_SELECTED; }
    for(int i=0;i<ee->nexte;i++){ ee->de[i].ome->i=ee->de[i].oi; ee->de[i].ome->flags&=(~TNS_MESH_FLAG_SELECTED); ee->de[i].nme->flags|=TNS_MESH_FLAG_SELECTED; }
    for(int i=0;i<ee->nextf;i++){
        ee->df[i].omf->i=ee->df[i].oi;
        ee->df[i].omf->flags&=(~TNS_MESH_FLAG_SELECTED);
        ee->df[i].nmf->flags|=TNS_MESH_FLAG_SELECTED; }
}
void la_RemoveOriginalFaces(MExtrudeExtra* ee){
    tnsMeshObject* mo=ee->mo;
    if(ee->RemoveOriginalFaces){
        for(int i=0;i<ee->nextf;i++){ tnsMMeshRemoveFaceOnly(mo, ee->df[i].omf); }
        for(int i=0;i<ee->nexte;i++){ if(ee->de[i].IsBorder) continue; tnsMMeshRemoveEdgeFace(mo, ee->de[i].ome); }
        for(int i=0;i<ee->nextv;i++){ if(ee->dv[i].IsBorder) continue; tnsMMeshRemoveVertEdgeFace(mo, ee->dv[i].omv); }
    }
}
void la_ReconnectFaces(MExtrudeExtra* ee){
    tnsMeshObject* mo=ee->mo;
    for(int i=0;i<ee->nexte;i++){ 
        if(!ee->de[i].IsBorder) continue; MEDupEdge*de=&ee->de[i];
        tnsMMeshMakeFace4v(mo, de->ome->vl, de->ome->vr, de->nme->vr, de->nme->vl);
    }
}
void la_FinishExtrude(MExtrudeExtra* ee, int PushDifferences){
    tnsMMeshRefreshIndex(ee->mo);
    tnsInvalidateMeshBatch(ee->mo);
    if(PushDifferences){
        laRecordInstanceDifferences(ee->mo, "tns_mesh_object"); laPushDifferences("Extruded", TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
    }
    free(ee->dv); free(ee->de); free(ee->df); memFree(ee);
}
int OPINV_Extrude(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 0;
    tnsMeshObject* mo=root->Active; if(mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE) return 0;

    if(!tnsMMeshAnySelected(mo)) return LA_FINISHED;

    MExtrudeExtra* ee=la_InitExtrude(mo);

    la_ExtrudeMakeDuplication(ee);
    
    if(strSame(strGetArgumentString(a->ExtraInstructionsP,"duplicate_only"), "true")){
        la_FinishExtrude(ee, 1);
        if(la_InitTransform(a, e, LA_TRANSFORM_MODE_GRAB)) return LA_RUNNING; return LA_FINISHED;
    }

    la_RemoveOriginalFaces(ee);
    la_ReconnectFaces(ee);

    la_FinishExtrude(ee, 1);
    if(la_InitTransform(a, e, LA_TRANSFORM_MODE_GRAB)) return LA_RUNNING; return LA_FINISHED;

    return LA_FINISHED;
}

int la_EdgeShouldDeleteVert(tnsMVert* mv){
    if(!mv->elink.pFirst) return 0;
    int NearSelected=0, NearUnselected=0;
    for(laListItemPointer* lip=mv->elink.pFirst;lip;lip=lip->pNext){
        tnsMEdge* me=lip->p;
        tnsMVert* av=tnsMMeshEdgeAnotherVert(me,mv);
        if(av->flags&TNS_MESH_FLAG_SELECTED){ NearSelected=1; } else { NearUnselected=1; }
        if(NearUnselected && NearSelected) return 0;
    }
    if(NearSelected&&(!NearUnselected)) return 1; return 0;
}
int la_FaceShouldDeleteVert(tnsMVert* mv){
    if(!mv->elink.pFirst) return 0;
    int NearSelected=0, NearUnselected=0, IsBorder=0;
    for(laListItemPointer* lip=mv->elink.pFirst;lip;lip=lip->pNext){
        tnsMEdge* me=lip->p;
        if(!(me->fl&&me->fr)){ IsBorder=1; }
        if(me->fl){ if(me->fl->flags&TNS_MESH_FLAG_SELECTED){ NearSelected=1; } else { NearUnselected=1; } }
        if(me->fr){ if(me->fr->flags&TNS_MESH_FLAG_SELECTED){ NearSelected=1; } else { NearUnselected=1; } }
        if(NearUnselected && NearSelected) return 0;
    }
    if((!NearUnselected) && IsBorder) return 1;
    return 1;
}
int la_FaceShouldDeleteEdge(tnsMEdge* me){
    if(me->fl&&me->fr){ 
        if ((me->fl->flags&TNS_MESH_FLAG_SELECTED) && (!(me->fr->flags&TNS_MESH_FLAG_SELECTED))||
            (me->fr->flags&TNS_MESH_FLAG_SELECTED) && (!(me->fl->flags&TNS_MESH_FLAG_SELECTED))) return 0;
        else return (me->fr->flags==me->fl->flags&&me->fl->flags==1);
    }else{
        if(me->fl&&me->fl->flags&TNS_MESH_FLAG_SELECTED&&!me->fr) return 1;
        if(me->fr&&me->fr->flags&TNS_MESH_FLAG_SELECTED&&!me->fl) return 1;
    } return 0;
}
void la_DeleteVertices(tnsMeshObject* mo){
    tnsMVert* nextmv; for(tnsMVert*mv=mo->mv.pFirst;mv;mv=nextmv){ nextmv=mv->Item.pNext; if(!(mv->flags&TNS_MESH_FLAG_SELECTED)) continue;
        tnsMMeshRemoveVertEdgeFace(mo, mv);
    }
}
void la_DeleteEdges(tnsMeshObject* mo){
    laListHandle lv={0};
    for(tnsMVert*mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ if(!(mv->flags&TNS_MESH_FLAG_SELECTED)) continue;
        if(la_EdgeShouldDeleteVert(mv)) lstAppendPointer(&lv,mv);
    }
    tnsMEdge* nextme; for(tnsMEdge*me=mo->me.pFirst;me;me=nextme){ nextme=me->Item.pNext; if(!(me->flags&TNS_MESH_FLAG_SELECTED)) continue;
        tnsMMeshRemoveEdgeFace(mo, me);
    }
    tnsMVert* mv; while(mv=lstPopPointer(&lv)){ tnsMMeshRemoveVertEdgeFace(mo,mv); }
}
void la_DeleteFaces(tnsMeshObject* mo, int OnlyFaces){
    laListHandle lv={0}, le={0};
    if(OnlyFaces){
        tnsMFace* nextmf; for(tnsMFace*mf=mo->mf.pFirst;mf;mf=nextmf){ nextmf=mf->Item.pNext; if(!(mf->flags&TNS_MESH_FLAG_SELECTED)) continue;
            tnsMMeshRemoveFaceOnly(mo, mf);
        }
    }else{
        for(tnsMVert*mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ if(!(mv->flags&TNS_MESH_FLAG_SELECTED)) continue;
            if(la_FaceShouldDeleteVert(mv)) lstAppendPointer(&lv,mv);
        }
        for(tnsMEdge*me=mo->me.pFirst;me;me=me->Item.pNext){ if(!(me->flags&TNS_MESH_FLAG_SELECTED)) continue;
            if(la_FaceShouldDeleteEdge(me)) lstAppendPointer(&le,me);
        }
        tnsMFace* nextmf; for(tnsMFace*mf=mo->mf.pFirst;mf;mf=nextmf){ nextmf=mf->Item.pNext; if(!(mf->flags&TNS_MESH_FLAG_SELECTED)) continue;
            tnsMMeshRemoveFaceOnly(mo, mf);
        }
        tnsMEdge* me; while(me=lstPopPointer(&le)){ tnsMMeshRemoveEdgeFace(mo,me); }
        tnsMVert* mv; while(mv=lstPopPointer(&lv)){ tnsMMeshRemoveVertEdgeFace(mo,mv); }
    }
}
int la_DeleteSelectedObjectsRecursive(tnsObject* root){
    int any=0; for(laListItemPointer* lip=root->ChildObjects.pFirst;lip;lip=lip->pNext){ if(!lip->p) continue;
        tnsObject* o=lip->p; la_DeleteSelectedObjectsRecursive(lip->p);
        if(o->Flags&TNS_OBJECT_FLAGS_SELECTED){ tnsDestroyObject(o); any++; }
    }
    return any;
}
int OPINV_Delete(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 0;
    tnsMeshObject* mo=root->Active;
    
    if(mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){
        if(la_DeleteSelectedObjectsRecursive(root)){
            laRecordInstanceDifferences(&T->World, "tns_world"); laPushDifferences("Deleted objects", TNS_HINT_TRANSFORM); laNotifyUsers("tns.world");
        }
    }else{
        if(!tnsMMeshAnySelected(mo)) return LA_FINISHED;
        if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"),"vertices")){
            la_DeleteVertices(mo);
        }elif(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"),"edges")){
            la_DeleteEdges(mo);
        }elif(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"),"faces")){
            la_DeleteFaces(mo,0);
        }elif(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"),"only_faces")){
            la_DeleteFaces(mo,1);
        }else{
            laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e);
            return LA_RUNNING;
        }
        tnsMMeshDeselectAll(mo);
        tnsMMeshRefreshIndex(mo);
        tnsInvalidateMeshBatch(mo);
        laRecordInstanceDifferences(mo, "tns_mesh_object"); laPushDifferences("Deleted primitives", TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
    }

    return LA_FINISHED;
}
void laui_Delete(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){
    laColumn* c=laFirstColumn(uil);
    laShowItemFull(uil,c,pp,"_this_M_delete",0,"mode=vertices;text=Vertices",0,0);
    laShowItemFull(uil,c,pp,"_this_M_delete",0,"mode=edges;text=Edges",0,0);
    laShowItemFull(uil,c,pp,"_this_M_delete",0,"mode=faces;text=Faces",0,0);
    laShowItemFull(uil,c,pp,"_this_M_delete",0,"mode=only_faces;text=Only Faces",0,0);
}

STRUCTURE(MIslandInfo){
    laListItem Item;
    laListHandle v,e,f;int numv,nume,numf;
    int Paired,HasBranches;
};
STRUCTURE(MMakeData){
    laListHandle Islands; int NumIslands;
};

#define M_SHOULD_INCL_PRIM(m)\
    ((!(m->flags&TNS_MESH_FLAG_PICKED)) && (m->flags&TNS_MESH_FLAG_SELECTED))
#define M_SHOULD_USE_OE(oe,sf)\
    (((!oe->fl)&&((!sf)||(oe->fr!=sf)))||((!oe->fr)&&((!sf)||(oe->fl!=sf))))

MIslandInfo* la_NewMIsland(MMakeData* md){ MIslandInfo* ii=memAcquireSimple(sizeof(MIslandInfo)); lstAppendItem(&md->Islands, ii); md->NumIslands++; return ii; }
void la_FillIslandFromVert(MIslandInfo* ii, tnsMVert* mv, int SelectMode){
    mv->flags|=TNS_MESH_FLAG_PICKED; lstAppendPointer(&ii->v,mv); ii->numv++; int connections=0;
    for(laListItemPointer* lip=mv->elink.pFirst;lip;lip=lip->pNext){ tnsMEdge* oe=lip->p; tnsMVert* ov=tnsMMeshEdgeAnotherVert(oe,mv);
        if(ov->flags&TNS_MESH_FLAG_SELECTED) connections++; else continue;
        if(SelectMode==LA_CANVAS_SELECT_MODE_EDGES){ if(!(oe->flags&TNS_MESH_FLAG_SELECTED)) continue; }
        if(M_SHOULD_INCL_PRIM(ov)){ la_FillIslandFromVert(ii,ov,SelectMode); } else { continue; }
        if(M_SHOULD_INCL_PRIM(oe)){ lstAppendPointer(&ii->e,oe); ii->nume++; oe->flags|=TNS_MESH_FLAG_PICKED; }
        if(oe->fl&&M_SHOULD_INCL_PRIM(oe->fl)){ lstAppendPointer(&ii->f,oe->fl); ii->numf++; oe->fl->flags|=TNS_MESH_FLAG_PICKED; }
        if(oe->fr&&M_SHOULD_INCL_PRIM(oe->fr)){ lstAppendPointer(&ii->f,oe->fr); ii->numf++; oe->fr->flags|=TNS_MESH_FLAG_PICKED; }
    }
    if(connections>2) ii->HasBranches=1;
}
void la_GetSelectionIslands(tnsMeshObject* mo, MMakeData* md, int SelectMode){
    tnsMMeshClearPickedFlags(mo);
    for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){
        if(M_SHOULD_INCL_PRIM(mv)){ MIslandInfo* ii=la_NewMIsland(md); la_FillIslandFromVert(ii,mv,SelectMode); }
    }
}
void la_ClearIslands(MMakeData* md){ MIslandInfo* ii; while(ii=lstPopItem(&md->Islands)){ while(lstPopPointer(&ii->v)); memFree(ii); } }
tnsMFace* la_MakeFacesFrom1Vert(tnsMeshObject* mo, tnsMVert* mv){
    tnsMEdge* oe1=0,*oe2=0; tnsMVert* ov1,*ov2;
    for(laListItemPointer* lip=mv->elink.pFirst;lip;lip=lip->pNext){
        tnsMEdge* oe=lip->p; if(oe->flags&TNS_MESH_FLAG_SELECTED) continue; if((!oe->fl)||(!oe->fr)){ if(!oe1)oe1=oe;elif(!oe2)oe2=oe;else return 0; /* more than 2 empty edges connected */ }
    } if(!oe1||!oe2) return 0;
    ov1=tnsMMeshEdgeAnotherVert(oe1,mv); ov2=tnsMMeshEdgeAnotherVert(oe2,mv);
    laListHandle vl={0}; lstAppendPointer(&vl,ov1); lstAppendPointer(&vl,mv); lstAppendPointer(&vl,ov2);
    tnsMFace* f=tnsMMeshMakeFaceN(mo, 3, &vl, 0); while(lstPopPointer(&vl)); return f;
}
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;
    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 */ }
    }
    for(laListItemPointer* lip=mv2->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(!oe2)oe2=oe;else return 0; /* more than 1 empty edge connected */ }
    }
    if(!oe1||!oe2) return 0;
    ov1=tnsMMeshEdgeAnotherVert(oe1,mv1); ov2=tnsMMeshEdgeAnotherVert(oe2,mv2);
    ov1->flags|=TNS_MESH_FLAG_SELECTED;ov2->flags|=TNS_MESH_FLAG_SELECTED; mv1->flags&=(~TNS_MESH_FLAG_SELECTED);mv2->flags&=(~TNS_MESH_FLAG_SELECTED);
    laListHandle vl={0}; lstAppendPointer(&vl,ov1); lstAppendPointer(&vl,mv1); lstAppendPointer(&vl,mv2); lstAppendPointer(&vl,ov2);
    tnsMFace* f=tnsMMeshMakeFaceN(mo, 4, &vl, 0); while(lstPopPointer(&vl)); return f;
}
int la_IsEndingVert(tnsMVert* mv){
    int sel=0; for(laListItemPointer*lip=mv->elink.pFirst;lip;lip=lip->pNext){ tnsMEdge* me=lip->p;
        if(tnsMMeshEdgeAnotherVert(me,mv)->flags&TNS_MESH_FLAG_SELECTED){ sel++; if(sel>1) return 0; }
    }
    if(sel==1) return 1; return 0;
}
void la_EnsureIslandVertsSequence(MIslandInfo* ii){
    laListHandle l={0}; tnsMVert* startv=((laListItemPointer*)ii->v.pFirst)->p; laListItemPointer* nextlip;
    for(laListItemPointer*lip=ii->v.pFirst;lip;lip=lip->pNext){ if(la_IsEndingVert(lip->p)){ startv=lip->p; break; } } // otherwise a loop, doesn't matter.
    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; }
        }
    }
    memcpy(&ii->v, &l, sizeof(laListHandle));
}
MIslandInfo* la_GetNeighborIsland(tnsMVert* from, MMakeData* md){
    int found=0; real dist=1e10; MIslandInfo* rii=0; for(MIslandInfo* ii=md->Islands.pFirst;ii;ii=ii->Item.pNext){ if(ii->Paired) continue;
        found=1; tnsMVert* mv1=((laListItemPointer*)ii->v.pFirst)->p,*mv2=((laListItemPointer*)ii->v.pLast)->p;
        real d1=tnsDist3dv(mv1->p, from->p),d2=tnsDist3dv(mv2->p, from->p);
        if(d2<d1 && d2<dist){ lstReverse(&ii->v); } if(d1<dist || d2<dist){ rii=ii; }
    }
    if(rii) rii->Paired=1;
    return rii;
}
int la_MakeFacesFromIslands(tnsMeshObject* mo, MMakeData* md){
    int success=0;
    if(!md->Islands.pFirst) return 0;
    if(md->Islands.pFirst==md->Islands.pLast){
        MIslandInfo* ii=md->Islands.pFirst; if(ii->HasBranches) return 0;
        if(ii->numv==1){ laListItemPointer*lip=ii->v.pFirst; if(la_MakeFacesFrom1Vert(mo,lip->p)) success++; }
        elif(ii->numv==2){ laListItemPointer*lip=ii->v.pFirst,*lip2=ii->v.pLast; if(la_MakeFacesFrom2Verts(mo,lip->p, lip2->p)) success++; }
        else{ la_EnsureIslandVertsSequence(ii); if(tnsMMeshMakeFaceN(mo, ii->numv, &ii->v, 0)) success++; }
    }else{
        for(MIslandInfo* ii=md->Islands.pFirst;ii;ii=ii->Item.pNext){ if(ii->HasBranches) return 0; la_EnsureIslandVertsSequence(ii); }
        laListHandle final={0}; int vcount=0;
        MIslandInfo* ii=md->Islands.pFirst; ii->Paired=1; for(laListItemPointer* lip=ii->v.pFirst;lip;lip=lip->pNext){ lstAppendPointer(&final, lip->p); vcount++; }
        while((ii=la_GetNeighborIsland(((laListItemPointer*)ii->v.pLast)->p, md))){
            for(laListItemPointer* lip=ii->v.pFirst;lip;lip=lip->pNext){ lstAppendPointer(&final, lip->p); vcount++; }
        }
        tnsMEdge* fallback_e=0;
        if(tnsMMeshMakeFaceN(mo,vcount,&final, &fallback_e) || fallback_e) success++;
        while(lstPopPointer(&final));
    }
    return success;
}
int OPINV_Make(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 0;
    tnsMeshObject* mo=root->Active;
    
    if(mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){ return LA_CANCELED; }

    MMakeData md={0};
    la_GetSelectionIslands(mo,&md,ex->SelectMode);
    int success=la_MakeFacesFromIslands(mo,&md);
    la_ClearIslands(&md);

    tnsMMeshRefreshIndex(mo);
    tnsMMeshEnsureSelection(mo,ex->SelectMode);
    tnsInvalidateMeshBatch(mo);
    if(laRecordInstanceDifferences(mo, "tns_mesh_object")) laPushDifferences("Make primitives", TNS_HINT_GEOMETRY);
    laNotifyUsers("tns.world");
    
    return LA_FINISHED;
}

int OPINV_Subdiv(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 0;
    tnsMeshObject* mo=root->Active;
    
    if(mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){ return LA_CANCELED; }

    laListHandle pending={0}; for(tnsMEdge* me=mo->me.pFirst;me;me=me->Item.pNext){ if(me->flags&TNS_MESH_FLAG_SELECTED) lstAppendPointer(&pending, me); }
    if(!pending.pFirst) return LA_FINISHED;
    tnsMEdge* me; while(me=lstPopPointer(&pending)){ tnsMVert* mv=tnsMMeshEdgeInsertVertAt(mo,me,0.5,0,0,0); mv->flags|=TNS_MESH_FLAG_SELECTED; }

    tnsMMeshRefreshIndex(mo);
    tnsMMeshEnsureSelection(mo,ex->SelectMode);
    tnsInvalidateMeshBatch(mo);
    if(laRecordInstanceDifferences(mo, "tns_mesh_object")) laPushDifferences("Subdivide edges", TNS_HINT_GEOMETRY);
    laNotifyUsers("tns.world");
    
    return LA_FINISHED;
}

int OPINV_Add(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 0;
    tnsMeshObject* mo=root->Active; int ran=0; tnsObject* no=0;
    
    if(mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){
        if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"),"PLANE")){ tnsDeselectAllObjects(root); 
            no=tnsCreateMeshPlane(root, "Plane",0,0,0,10); no->Flags|=TNS_OBJECT_FLAGS_SELECTED; memAssignRef(root,&root->Active,no); ran=1; }
        else{ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; }
        if(ran){ laRecordAndPush(0,"tns.world","Add object",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world"); }
    }else{
        if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"),"PLANE")){
            tnsMMeshDeselectAll(mo); tnsAddMMeshPlane(mo, 10); tnsMMeshEnsureSelection(mo,ex->SelectMode); ran=1;
        }else{ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; }
        if(ran){
            tnsMMeshRefreshIndex(mo); tnsInvalidateMeshBatch(mo); 
            laRecordInstanceDifferences(mo, "tns_mesh_object"); laPushDifferences("Add primitives", TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
        }
    }
    return LA_FINISHED;
}
void laui_Add(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){
    laColumn* c=laFirstColumn(uil);
    laShowItemFull(uil,c,pp,"_this_M_add",0,"mode=PLANE;text=Plane",0,0);
}

int OPINV_Separate(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->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){
        return LA_CANCELED;
    }
    if(!tnsMMeshAnySelected(mo)) return LA_CANCELED;

    MExtrudeExtra* ee=la_InitExtrude(mo);
    la_ExtrudeMakeDuplication(ee);
    ee->RemoveOriginalFaces=1;la_RemoveOriginalFaces(ee);

    tnsMeshObject* no=tnsCreateMeshEmpty(mo->Base.ParentObject?mo->Base.ParentObject:mo->Base.InRoot, mo->Base.Name->Ptr, 0,0,0);
    tnsCopyObjectTransformationsLocal(no,mo);
    no->Mode=TNS_MESH_EDIT_MODE;
    tnsMVert* nmv; for(tnsMVert* mv=mo->mv.pFirst;mv;mv=nmv){ nmv=mv->Item.pNext; if(!(mv->flags&TNS_MESH_FLAG_SELECTED))continue; 
        lstRemoveItem(&mo->mv, mv); lstAppendItem(&no->mv, mv); no->totmv++; mo->totmv--; }
    tnsMEdge* nme; for(tnsMEdge* me=mo->me.pFirst;me;me=nme){ nme=me->Item.pNext; if(!(me->flags&TNS_MESH_FLAG_SELECTED))continue; 
        lstRemoveItem(&mo->me, me); lstAppendItem(&no->me, me); no->totme++; mo->totme--; }
    tnsMFace* nmf; for(tnsMFace* mf=mo->mf.pFirst;mf;mf=nmf){ nmf=mf->Item.pNext; if(!(mf->flags&TNS_MESH_FLAG_SELECTED))continue; 
        lstRemoveItem(&mo->mf, mf); lstAppendItem(&no->mf, mf); no->totmf++; mo->totmf--; }
    tnsMMeshRefreshIndex(no); tnsMeshLeaveEditMode(no);

    la_FinishExtrude(ee, 0);

    tnsMMeshRefreshIndex(mo); tnsInvalidateMeshBatch(mo);
    laRecordAndPush(0,"tns.world","Separate mesh parts",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
    
    return LA_FINISHED;
}

void la_PopulateSelectedMeshObjects(tnsObject* root, laListHandle* l){
    if(root->Type==TNS_OBJECT_MESH && root->Flags&TNS_OBJECT_FLAGS_SELECTED){ lstAppendPointer(l,root); }
    for(laListItemPointer* lip=root->ChildObjects.pFirst;lip;lip=lip->pNext){
        la_PopulateSelectedMeshObjects(lip->p, l);
    }
}
int OPINV_Combine(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; }

    laListHandle pending={0}; la_PopulateSelectedMeshObjects(root,&pending);
    tnsMeshObject* o; while(o=lstPopPointer(&pending)){ if(o==mo || o->Mode==TNS_MESH_EDIT_MODE) continue;
        if(tnsMergeMeshObjects(mo, o)) ran++;
    }

    if(ran){
        tnsMMeshRefreshIndex(mo); tnsInvalidateMeshBatch(mo);
        laRecordAndPush(0,"tns.world","Merge mesh objects",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
    }
    
    return LA_FINISHED;
}

int OPINV_Duplicate(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; }

    laListHandle pending={0}; la_PopulateSelectedMeshObjects(root,&pending);
    tnsMeshObject* o; tnsMeshObject* no;while(o=lstPopPointer(&pending)){ if(o->Mode==TNS_MESH_EDIT_MODE) continue;
        if(no=tnsDuplicateMeshObjects(o)){ no->Base.Flags|=TNS_OBJECT_FLAGS_SELECTED; o->Base.Flags&=(~TNS_OBJECT_FLAGS_SELECTED); 
            if(mo==o){ memAssignRef(root,&root->Active,no); } ran++; 
        }
    }
    
    if(ran){
        laRecordAndPush(0,"tns.world","Merge mesh objects",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world");
        if(la_InitTransform(a,e,LA_TRANSFORM_MODE_GRAB)) return LA_RUNNING; return LA_FINISHED;
    }
    
    return LA_FINISHED;
}


void la_RegisterModellingOperators(){
    laPropContainer *pc; laProp *p;
    laOperatorType *at;
    laEnumProp *ep;

    laCreateOperatorType("M_toggle_edit_mode", "Toggle Edit Mode", "Toggle edit mode of the active object", OPCHK_ThereIsActiveObject, 0, 0, OPINV_ToggleEdit, 0, 0, 0);
    laCreateOperatorType("M_select", "Select", "Select things in the viewport", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Select, OPMOD_Select, 0, LA_EXTRA_TO_PANEL);
    laCreateOperatorType("M_grab", "Grab", "Grab things and move around", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Grab, OPMOD_Transformation, 0, LA_EXTRA_TO_PANEL);
    laCreateOperatorType("M_scale", "Scale", "Scale selected things", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Scale, OPMOD_Transformation, 0, LA_EXTRA_TO_PANEL);
    laCreateOperatorType("M_rotate", "Rotate", "Rotation selected things", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Rotate, OPMOD_Transformation, 0, LA_EXTRA_TO_PANEL);
    at=laCreateOperatorType("M_make_parent", "Make Parent", "Parent objects to active objects or unparent selected ones", 0, 0, 0, OPINV_MakeParent, OPMOD_FinishOnData, 0, 0);
    at->UiDefine = laui_MakeParent;
    at=laCreateOperatorType("M_unparent", "Unparent", "Unparent selected objects", 0, 0, 0, OPINV_MakeParent, OPMOD_FinishOnData, 0, 0);
    at->UiDefine = laui_Unparent;
    laCreateOperatorType("M_clear_transformations", "Clear Transformations", "Clear transformations in objects", 0, 0, 0, OPINV_ClearTransformation, 0, 0, 0);
    laCreateOperatorType("M_extrude", "Extrude", "Extrude parts of the mesh", 0, 0, 0, OPINV_Extrude, OPMOD_Transformation, 0, 0);
    at=laCreateOperatorType("M_delete", "Delete", "Delete parts of the mesh", 0, 0, 0, OPINV_Delete, OPMOD_FinishOnData, 0, 0);
    at->UiDefine=laui_Delete;
    laCreateOperatorType("M_make", "Make", "Make mesh primitive from selected ones", 0, 0, 0, OPINV_Make, 0, 0, 0);
    laCreateOperatorType("M_subdiv", "Subdiv", "Subdivide edges", 0, 0, 0, OPINV_Subdiv, 0, 0, 0);
    at=laCreateOperatorType("M_add", "Add", "Add mesh or primitives", 0, 0, 0, OPINV_Add, OPMOD_FinishOnData, 0, 0);
    at->UiDefine=laui_Add;
    laCreateOperatorType("M_separate", "Separate", "Separate mesh parts", 0, 0, 0, OPINV_Separate, 0, 0, 0);
    laCreateOperatorType("M_combine", "Combine", "Combine mesh objects", 0, 0, 0, OPINV_Combine, 0, 0, 0);
    laCreateOperatorType("M_duplicate", "Duplicate", "Duplicate objects", 0, 0, 0, OPINV_Duplicate, OPMOD_Transformation, 0, 0);
}