/* * LaGUI: A graphical application framework. * Copyright (C) 2022-2023 Wu Yiming * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "../la_5.h" extern LA MAIN; extern struct _tnsMain *T; void la_ReadGLocation(tnsOffscreen* off, int x, int y,float* xyz0){ glBindFramebuffer(GL_READ_FRAMEBUFFER, off->FboHandle); glReadBuffer(GL_COLOR_ATTACHMENT2); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glReadPixels(x,y,1,1, GL_RGBA, GL_FLOAT, xyz0); } int OPINV_SetCursor(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 || root->Type!=TNS_OBJECT_ROOT) return 0; float pos[4]; la_ReadGLocation(ex->OffScr, e->x-ui->L,ui->B-e->y,pos); if(pos[0]>-1e20){ tnsVectorSet3v(root->GLocation,pos); laNotifyUsers("tns.world"); return LA_FINISHED_PASS; }else { if(!ex->ViewingCamera){ return LA_FINISHED_PASS; } real vv[4], gp[4]; tnsMatrix44d proj; tnsMatrix44d inv; real focus=ex->ViewingCamera->FocusDistance, _near=ex->ViewingCamera->ZMin, _far=ex->ViewingCamera->ZMax; tnsGetCameraViewProjection(proj,ui->R-ui->L,ui->B-ui->U,ex->ViewingCamera); tnsInverse44d(inv,proj); vv[0]=(real)(e->x-ui->L)/(real)(ui->R-ui->L)*2-1; vv[0]*=focus; vv[1]=(real)(ui->B-e->y)/(real)(ui->B-ui->U)*2-1; vv[1]*=focus; vv[2]=tnsGetRatiod(1/_near,1/_far,1/focus)*focus; vv[3]=focus; tnsApplyTransform44dTrue(gp, inv, vv); tnsVectorMultiSelf3d(gp,1/gp[3]); real vv0[3]={0,0,-1}, vv1[3], ray[3], p0[3]; real t; tnsMatrix44d inv2; tnsSelfTransformValueChanged(ex->ViewingCamera);tnsInverse44d(inv2,ex->ViewingCamera->Base.GlobalTransform); tnsApplyRotation43d(vv1,inv2,vv0); tnsVectorMulti3d(vv0,vv1,focus); tnsVectorAccum3d(vv0,ex->ViewingCamera->Base.GLocation); tnsVectorMinus3d(ray, gp, ex->ViewingCamera->Base.GLocation); tnsNormalizeSelf3d(ray); if(tnsIntersectPlaneRay(vv1,vv0,ex->ViewingCamera->Base.GLocation,ray,&t)){ tnsVectorMultiSelf3d(ray,t); tnsVectorPlus3d(gp,ray,ex->ViewingCamera->Base.GLocation); } tnsVectorSet3v(root->GLocation,gp); laNotifyUsers("tns.world"); return LA_FINISHED_PASS; } return LA_FINISHED_PASS; } 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** RefsV; int nextV,maxV; void** RefsE; int nextE,maxE; }; 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, 0); 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->RefsV, sd->nextV, &sd->maxV, sizeof(tnsObject*)); sd->RefsV[sd->nextV]=o; o->SelectID=sd->nextV; sd->nextV++; if(o->ChildObjects.pFirst){ la_AssignObjectSelectIDRecursive(o,sd); } } } void la_PopulateSelectDataObjects(MSelectData* sd, tnsObject* root, tnsCamera* camera){ arrEnsureLength(&sd->RefsV,0,&sd->maxV,sizeof(tnsObject*)); sd->nextV++; // starting from 1; la_AssignObjectSelectIDRecursive(root, sd); if(sd->nextV==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); tnsInvalidateEvaluation(root); tnsSetObjectTreeEvaluationArgs(root,root->Active,1,1); tnsEvaluateObjectTree(root,0,0); tnsDrawObjectTree(root,TNS_EVAL_LAYER_SELECTION,0,0); glDisable(GL_DEPTH_TEST); tnsEnableShaderv(T->immShader); } void la_PopulateSelectVerts(MSelectData* sd, tnsMeshObject* mo){ arrEnsureLength(&sd->RefsV,0,&sd->maxV,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->RefsV, v->i, &sd->maxV, sizeof(tnsObject*)); sd->RefsV[v->i]=v; sd->nextV=TNS_MAX2(v->i, sd->nextV); } sd->nextV++; } void la_PopulateSelectEdges(MSelectData* sd, tnsMeshObject* mo){ arrEnsureLength(&sd->RefsE,0,&sd->maxE,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->RefsE, e->i, &sd->maxE, sizeof(tnsObject*)); sd->RefsE[e->i]=e; sd->nextE=TNS_MAX2(e->i, sd->nextE); } sd->nextE++; } void la_PopulateSelectDataPrimitives(MSelectData* sd, tnsMeshObject* mo, tnsCamera* camera, int WhatPrim, int SelectThrough){ int DoVerts=(WhatPrim==LA_CANVAS_SELECT_MODE_VERTS),DoEdges=(WhatPrim==LA_CANVAS_SELECT_MODE_EDGES); int Knife=(WhatPrim==LA_CANVAS_SELECT_MODE_KNIFE); if(DoVerts || Knife){ la_PopulateSelectVerts(sd,mo); } if(DoEdges || Knife){ 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); tnsPushMatrix(); tnsApplyModelMatrix(mo->Base.GlobalTransform);glEnable(GL_DEPTH_TEST); if(!SelectThrough){ glDepthMask(GL_TRUE); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); tnsUniformUseOffset(T->SelectionShader,0); tnsDrawBatch(mo->Batch,"body",0,0); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } glDepthMask(GL_FALSE); tnsUniformUseOffset(T->SelectionShader,1); if(DoEdges || Knife){ tnsDrawBatch(mo->Batch, "edges_select",0,0); } if(DoVerts || Knife){ tnsDrawBatch(mo->Batch, "verts_select",0,0); } tnsUniformUseOffset(T->SelectionShader,0); tnsPopMatrix(); glDisable(GL_DEPTH_TEST); tnsEnableShaderv(T->immShader); } 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-ey){ memset(&pad[i*w*4],0,w*4); continue; } for(int j=0;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(uix2Color->Height,vw=sd->Color->Width; if(uix2FBO); free(sd->RefsV); free(sd->RefsE); memFree(sd); } void* la_SelectGetRef(MSelectData* sd, int id, int elemtype){ if(!elemtype){ if(id>=0 && idnextV){ return sd->RefsV[id]; } } elif(elemtype==TNS_MMESH_EDGE_BIT){ if(id>=0 && idnextE){ return sd->RefsE[id]; } } return 0; } 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; laListHandle KnifeElements; void* PendingElem; int PendingElemType, BatchDirty; int IsLoop; }; 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; int is_geo=0, SelectMode=ex->SelectMode, ring_band=0; 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); is_geo=1; }else{ if(tnsAnyObjectsSelected(root)) tnsDeselectAllObjects(root); else tnsSelectAllObjects(root); } laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world", "Toggle selection",is_geo?TNS_HINT_GEOMETRY:TNS_HINT_TRANSFORM); return LA_FINISHED; } int spk=e->SpecialKeyBit & (LA_KEY_CTRL|LA_KEY_ALT); if(spk==LA_KEY_ALT){ ring_band=1; SelectMode=LA_CANVAS_SELECT_MODE_EDGES; } elif(spk==(LA_KEY_CTRL|LA_KEY_ALT)){ ring_band=2; SelectMode=LA_CANVAS_SELECT_MODE_EDGES; } 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, SelectMode, ex->SelectThrough); 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 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_DoMeshSelect(mo, p, SelectMode, DeselectAll, 1, 1); tnsMMeshEnsureSelection(mo,ex->SelectMode); if(ring_band && p){ tnsMMeshSelectRingBandFrom(mo,p,ring_band,1,Append); 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 elemtype,id=la_SelectGetClosest(sd, e->x-ui->L, e->y-ui->U, LA_RH*2,&elemtype); void* p=la_SelectGetRef(sd,id,elemtype); if(p){ la_DoObjectSelect(root, p, 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 0; 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); memFree(se); 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; int is_geo=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;isd,id,ex->SelectMode==LA_CANVAS_SELECT_MODE_EDGES?TNS_MMESH_EDGE_BIT:0); la_DoMeshSelect(mo, p, ex->SelectMode, 0, !Remove, 0); } tnsMMeshEnsureSelection(mo,ex->SelectMode); tnsInvalidateMeshBatch(mo); is_geo=1; }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;isd,id,0); if(p){ la_DoObjectSelect(se->root, p, ex, 0, !Remove, 0); } } } laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Box selection",is_geo?TNS_HINT_GEOMETRY:TNS_HINT_TRANSFORM); ex->DrawCursor=0; la_FreeSelectData(se->sd); memFree(se); 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 #define LA_TRANSFORM_RESTORE_LOC 1 #define LA_TRANSFORM_RESTORE_ROT 2 #define LA_TRANSFORM_RESTORE_SCA 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,TLCenter; int CenterX,CenterY; real Initial; tnsObject* mo; tnsMatrix44d obmatinv,deltainv; 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; int CanvasDeltaMode; }; MTransformData* la_InitTransformData(int w, int h, tnsCamera* c, int CanvasDeltaMode){ 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; td->CanvasDeltaMode=CanvasDeltaMode; 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_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; 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); tnsInverse44d(td->deltainv, mo->Base.DeltaTransform); 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); tnsVectorAccum3d(td->TLCenter,mv->p); } //printf(" [totmv %d]\n",mo->totmv); tnsVectorMultiSelf3d(td->TCenter, 1.0f/any); tnsVectorMultiSelf3d(td->TLCenter, 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;inext;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; } if(td->CanvasDeltaMode) tnsGlobalMatrixChangedForDelta(to->o, 0); else tnsGlobalMatrixChanged(to->o, 0); if(td->UseLocal){ if(td->CanvasDeltaMode) tnsMoveObjectDelta(to->o, LA_COLOR3(use_delta)); else tnsMoveObjectLocal(to->o, LA_COLOR3(use_delta)); }else{ if(td->CanvasDeltaMode) tnsMoveObjectGlobalForDelta(to->o, LA_COLOR3(use_delta)); else tnsMoveObjectGlobal(to->o, LA_COLOR3(use_delta)); } } }else{ tnsMakeTranslationMatrix44d(trans, LA_COLOR3(use_delta)); tnsMatrix44d final; if(!td->UseLocal) tnsMultiply44d(final,td->obmatinv,trans); else tnsMultiply44d(final,trans,td->obmatinv); for(int i=0;inext;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert)); tnsApplyTransform43d(to->mv->p, final, to->p); } tnsInvalidateMeshBatch(td->mo); tnsMMeshCalculateNormal(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;inext;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; } if(td->CanvasDeltaMode) tnsGlobalMatrixChangedForDelta(to->o, 0); else tnsGlobalMatrixChanged(to->o, 0); if(td->CanvasDeltaMode) tnsScaleObjectDelta(to->o,s,LA_COLOR3(td->TCenter)); else tnsScaleObject(to->o, s, LA_COLOR3(td->TCenter)); } }else{ tnsVector3d use_delta={s,s,s}; if(td->LockAxis[0]>0){ use_delta[1]=use_delta[2]=1;} if(td->LockAxis[1]>0){ use_delta[0]=use_delta[2]=1;} if(td->LockAxis[2]>0){ use_delta[0]=use_delta[1]=1;} if(td->LockAxis[0]<0){ use_delta[0]=1; } if(td->LockAxis[1]<0){ use_delta[1]=1; } if(td->LockAxis[2]<0){ use_delta[2]=1; } tnsMakeScaleMatrix44d(trans,LA_COLOR3(use_delta)); tnsMatrix44d t1,t2,t3; tnsMakeTranslationMatrix44d(t1,LA_COLOR3(td->TCenter)); tnsInverse44d(t2,t1); tnsMatrix44d final; tnsMultiply44d(t3,t1,trans); tnsMultiply44d(t1,t3,t2); if(!td->UseLocal) tnsMultiply44d(final,td->obmatinv,t1); else tnsMultiply44d(final,t1,td->obmatinv); for(int i=0;inext;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert)); tnsApplyTransform43d(to->mv->p, final, to->p); } tnsInvalidateMeshBatch(td->mo); tnsMMeshCalculateNormal(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;inext;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; } if(td->CanvasDeltaMode) tnsGlobalMatrixChangedForDelta(to->o, 0); else tnsGlobalMatrixChanged(to->o, 0); if(td->UseLocal){ if(td->CanvasDeltaMode) tnsRotateObjectDelta(to->o,LA_COLOR3(use_forward),angle); else tnsRotateObjectLocal(to->o,LA_COLOR3(use_forward),angle); }else{ if(td->CanvasDeltaMode) tnsRotateObjectGlobalForDelta(to->o,LA_COLOR3(use_forward),angle,LA_COLOR3(td->TCenter)); else 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->UseLocal?td->TLCenter:td->TCenter)); tnsInverse44d(t2,t1); tnsMatrix44d final; tnsMultiply44d(t3,t1,trans); tnsMultiply44d(t1,t3,t2); if(!td->UseLocal) tnsMultiply44d(final,td->obmatinv,t1); else tnsMultiply44d(final,t1,td->obmatinv); for(int i=0;inext;i++){ MTOrigMVert* to=arrElement(td->Originals, i, sizeof(MTOrigMVert)); tnsApplyTransform43d(to->mv->p, final, to->p); } tnsInvalidateMeshBatch(td->mo); tnsMMeshCalculateNormal(td->mo); } } void la_CancelTransformObjects(MTransformData* td){ if(!td->mo){ for(int i=0;inext;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)); if(td->CanvasDeltaMode) tnsGlobalMatrixChangedForDelta(to->o, 1); else tnsGlobalMatrixChanged(to->o, 1); } }else{ for(int i=0;inext;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;inext;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 "); } } void la_RestoreTransform(tnsObject* ob, int restore_type, int restore_delta){ if(restore_delta){ if(restore_type==LA_TRANSFORM_RESTORE_LOC){ tnsVectorSet3(ob->DLocation,0,0,0); } elif(restore_type==LA_TRANSFORM_RESTORE_ROT){ tnsVectorSet3(ob->DRotation,0,0,0); } elif(restore_type==LA_TRANSFORM_RESTORE_SCA){ ob->DScale=1; } tnsDeltaTransformValueChanged(ob); }else{ if(restore_type==LA_TRANSFORM_RESTORE_LOC){ tnsVectorSet3(ob->Location,0,0,0); } elif(restore_type==LA_TRANSFORM_RESTORE_ROT){ tnsVectorSet3(ob->Rotation,0,0,0); } elif(restore_type==LA_TRANSFORM_RESTORE_SCA){ ob->Scale=1; } tnsSelfTransformValueChanged(ob); } } int la_InitTransform(laOperator* a, laEvent* e, int mode, int restore_type, int restore_delta){ 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, ex->DeltaMode); 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 && restore_type){ for(int i=0;inext;i++){ MTOrigObject* ob=arrElement(td->Originals, i, sizeof(MTOrigObject)); la_RestoreTransform(ob->o,restore_type,restore_delta); } la_RecordTransformDifferences(td); laNotifyUsers("tns.world"); la_FreeTransformData(td); return 0; } } 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,0,0)) return LA_RUNNING; return LA_FINISHED_PASS; } int OPINV_Scale(laOperator *a, laEvent *e){ if(la_InitTransform(a, e, LA_TRANSFORM_MODE_SCALE,0,0)) return LA_RUNNING; return LA_FINISHED_PASS; } int OPINV_Rotate(laOperator *a, laEvent *e){ if(la_InitTransform(a, e, LA_TRANSFORM_MODE_ROTATE,0,0)) 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 OPINV_ClearTransformation(laOperator *a, laEvent *e){ laCanvasExtra* ex=a->This->EndInstance; int restore=0; char* channel=strGetArgumentString(a->ExtraInstructionsP,"channel"); if(channel){ if(channel[0]=='L' || channel[0]=='l'){ restore=LA_TRANSFORM_RESTORE_LOC; } if(channel[0]=='R' || channel[0]=='r'){ restore=LA_TRANSFORM_RESTORE_ROT; } if(channel[0]=='S' || channel[0]=='s'){ restore=LA_TRANSFORM_RESTORE_SCA; } }else{ laEnableOperatorPanel(a,a->This,e->x,e->y,150,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; } char* dt=strGetArgumentString(a->ExtraInstructionsP,"delta"); int delta=dt?strSame(dt,"true"):ex->DeltaMode; la_InitTransform(a,e,0,restore,delta); return LA_FINISHED; } void laui_ClearTransformation(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){ laColumn* c=laFirstColumn(uil), *cl,*cr; laSplitColumn(uil,c,0.6); cl=laLeftColumn(c,0); cr=laRightColumn(c,0); laShowItemFull(uil,cl,pp,"_this_M_clear_transformations",0,"channel=loc;text=Location",0,0); laShowItemFull(uil,cl,pp,"_this_M_clear_transformations",0,"channel=rot;text=Rotation",0,0); laShowItemFull(uil,cl,pp,"_this_M_clear_transformations",0,"channel=sca;text=Scale",0,0); laShowItemFull(uil,cr,pp,"_this_M_clear_transformations",0,"channel=loc;text=Delta;delta=true",0,0); laShowItemFull(uil,cr,pp,"_this_M_clear_transformations",0,"channel=rot;text=Delta;delta=true",0,0); laShowItemFull(uil,cr,pp,"_this_M_clear_transformations",0,"channel=sca;text=Delta;delta=true",0,0); } 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){ laListItemPointer* NextLip; if(root->Type!=TNS_OBJECT_ROOT){ if((root->Flags&TNS_OBJECT_FLAGS_SELECTED)&&root->InRoot==parent->InRoot){ if(Unparent) tnsUnparentObject(root, KeepTransform); elif(root!=parent) tnsParentObject(root, parent, KeepTransform); } } for(laListItemPointer* lip=root->ChildObjects.pFirst;lip;lip=NextLip){ NextLip=lip->pNext; tnsObject* o=lip->p; if(!o) continue; 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); } 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; nmf->mat=mf->mat; 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;inextv;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;inexte;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;inextf;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;inextf;i++){ tnsMMeshRemoveFaceOnly(mo, ee->df[i].omf); } for(int i=0;inexte;i++){ if(ee->de[i].IsBorder) continue; tnsMMeshRemoveEdgeFace(mo, ee->de[i].ome); } for(int i=0;inextv;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;inexte;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,0,0)) 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,0,0)) 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) return 0; 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){ tnsMMeshClearExtraFlags(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); ov1->flags|=TNS_MESH_FLAG_SELECTED;ov2->flags|=TNS_MESH_FLAG_SELECTED; mv->flags&=(~TNS_MESH_FLAG_SELECTED); tnsMMeshEnsureSelectionFromVerts(mo); 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=tnsMMeshVertsShareEdge(mv1,mv2); if(se->fl && se->fr) return 0; sf=se->fl?se->fl:se->fr; for(laListItemPointer* lip=mv1->elink.pFirst;lip;lip=lip->pNext){ tnsMEdge* oe=lip->p; if(oe->flags&TNS_MESH_FLAG_SELECTED) continue; if(M_SHOULD_USE_OE(oe,sf)){ if(!oe1)oe1=oe;else return 0; /* more than 1 empty edge connected */ } } 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}; int vcount=3; lstAppendPointer(&vl,ov1); lstAppendPointer(&vl,mv1); lstAppendPointer(&vl,mv2); if(ov2!=ov1){ lstAppendPointer(&vl,ov2); vcount=4; } tnsMFace* f=tnsMMeshMakeFaceN(mo, vcount, &vl, 0); tnsMMeshEnsureSelectionFromVerts(mo); 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(tnsMMeshVertsShareEdge(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(d2v); } if(d1Paired=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||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); if(success){ tnsMMeshCalculateNormal(mo); } 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; } #define LA_ADD_CTX_OBJECT 0 #define LA_ADD_CTX_MESH 1 STRUCTURE(laObjectAddData){ int Context; }; 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; laObjectAddData *ad=memAcquire(sizeof(laObjectAddData));a->CustomData=ad; char* str=strGetArgumentString(a->ExtraInstructionsP, "mode"); if((!mo) || mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){ ad->Context=LA_ADD_CTX_OBJECT; if(strSame(str,"PLANE")){ tnsDeselectAllObjects(root); no=tnsCreateMeshPlane(root, "Plane",0,0,0,1); no->Flags|=TNS_OBJECT_FLAGS_SELECTED; memAssignRef(root,&root->Active,no); ran=1; } elif(strSame(str,"INSTANCER")){ tnsDeselectAllObjects(root); no=tnsCreateInstancer(root, "Instancer",0,0,0); 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{ ad->Context=LA_ADD_CTX_MESH; if(strSame(str,"PLANE")){ tnsMMeshDeselectAll(mo); tnsAddMMeshPlane(mo, 1); 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); laUiItem* b=laOnConditionThat(uil,c,laEqual(laPropExpression(actinst,"context"),laIntExpression(LA_ADD_CTX_OBJECT)));{ laShowLabel(uil,c,"Empty",0,0)->Flags|=LA_TEXT_MONO|LA_UI_FLAGS_DISABLED; laShowItemFull(uil,c,pp,"_this_M_add",0,"mode=INSTANCER;text=Instancer",0,0); }laEndCondition(uil,b); laShowLabel(uil,c,"Primitives",0,0)->Flags|=LA_TEXT_MONO|LA_UI_FLAGS_DISABLED; 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; laListItemPointer* lip; for(lip=pending.pFirst;lip;lip=lip->pNext){ o=lip->p; o->Base.EditDuplicateTemp=0; if (o->Mode == TNS_MESH_EDIT_MODE) continue; if (no = tnsDuplicateMeshObject(o)){ o->Base.EditDuplicateTemp=no; no->Base.Flags |= TNS_OBJECT_FLAGS_SELECTED; o->Base.Flags &= (~TNS_OBJECT_FLAGS_SELECTED); if (mo == o) { memAssignRef(root, &root->Active, no); } ran++; } } for (lip = pending.pFirst;lip;lip=lip->pNext){ o=lip->p; no = o->Base.EditDuplicateTemp; tnsObject* NewParent=o->Base.ParentObject?o->Base.ParentObject->EditDuplicateTemp:0; if(NewParent){ tnsUnparentObject(no,1); tnsParentObject(no,NewParent,1); } } while(o=lstPopPointer(&pending)){ o->Base.EditDuplicateTemp=0; } if(ran){ laRecordAndPush(0,"tns.world","Merge mesh objects",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world"); if(la_InitTransform(a,e,LA_TRANSFORM_MODE_GRAB,0,0)) return LA_RUNNING; return LA_FINISHED; } return LA_FINISHED; } int OPINV_RecalculateNormals(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; } ran=tnsMMeshCalculateNormal(mo); if(ran){ tnsInvalidateMeshBatch(mo); laRecordAndPush(0,"tns.world","Recalculate Normals",TNS_HINT_GEOMETRY); laNotifyUsers("tns.world"); } return LA_FINISHED; } STRUCTURE(MKnifeElement){ laListItem Item; void* p; int Type; }; void la_KnifeUpdateToolBatch(MSelectExtra* se,tnsMeshObject* o){ if(o->ExtraBatch) tnsDeleteBatch(o->ExtraBatch); o->ExtraBatch=0; int count=lstCountElements(&se->KnifeElements); if((!count) && (!se->PendingElem)) return; float* points=calloc((count+1)*3,sizeof(real)); float* p=points; real tmp[3],trans[4]; for(MKnifeElement* ke=se->KnifeElements.pFirst;ke;ke=ke->Item.pNext){ if(ke->Type==TNS_MMESH_EDGE_BIT){ tnsMEdge* me=ke->p; tnsVectorSet3v(tmp,me->vl->p); tnsVectorAccum3d(tmp,me->vr->p); tnsVectorMultiSelf3d(tmp,0.5); tnsVectorSet3v(p,tmp); } else{ tnsMVert* mv=ke->p; tnsVectorSet3v(p,mv->p); } p+=3; } if(se->PendingElem){ if(se->PendingElemType==TNS_MMESH_EDGE_BIT){ tnsMEdge* me=se->PendingElem; tnsVectorSet3v(tmp,me->vl->p); tnsVectorAccum3d(tmp,me->vr->p); tnsVectorMultiSelf3d(tmp,0.5); tnsVectorSet3v(p,trans); } else{ tnsMVert* mv=se->PendingElem; tnsVectorSet3v(p,mv->p); } }elif(count){ tnsVectorSet3v(p,p-3); } uint32_t elem=count; tnsBatch* batch=tnsCreateBatch(count+1,3,points,0,0,0,0); tnsBatchCommand*c; c=tnsCreateCommand(batch, "hovering_point", 1, 3, GL_POINTS, &elem, 0); tnsCommandUseUniformColor(c,laAccentColor(LA_BT_SVERTEX)); tnsCommandUseWidth(c, 8); if(count){ c=tnsCreateCommand(batch, "edges", count+(se->IsLoop?0:1), 3, se->IsLoop?GL_LINE_LOOP:GL_LINE_STRIP, 0, 0); tnsCommandUseUniformColor(c,laAccentColor(LA_BT_NORMAL)); tnsCommandUseWidth(c, 2); c=tnsCreateCommand(batch, "points", count, 3, GL_POINTS, 0, 0); tnsCommandUseUniformColor(c,laAccentColor(LA_BT_NORMAL)); tnsCommandUseWidth(c, 6); } o->ExtraBatch=batch; free(points); } int la_KnifeIsDuplicated(MSelectExtra* se, void* ref){ for(MKnifeElement* ke=se->KnifeElements.pFirst;ke;ke=ke->Item.pNext){ if(ke->Type==TNS_MMESH_EDGE_BIT && ke->p==ref){ return 1; } } return 0; } void la_KnifeAppendCut(MSelectExtra* se){ MKnifeElement* ke=lstAppendPointerSized(&se->KnifeElements,se->PendingElem,sizeof(MKnifeElement)); ke->Type=se->PendingElemType; } int la_KnifeRegisterCuts(MSelectExtra* se, tnsMeshObject* mo, int TryClose){ if(!mo || mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE) return 0; if(!se->KnifeElements.pFirst) return 0; tnsMVert* lastv=0,*newv=0,*firstv=0; tnsMEdge* newme=0; int changed=0; tnsMFace* mf=0; for(MKnifeElement* ke=se->KnifeElements.pFirst;ke;ke=ke->Item.pNext){ if(ke->Type==TNS_MMESH_EDGE_BIT){ newv=tnsMMeshEdgeInsertVertAt(mo,ke->p,0.5,0,0,0); changed=1; } else{ newv=ke->p; } if(lastv){ if(tnsMMeshVertsShareFace(lastv,newv)) newme=tnsMMeshMakeEdge(mo, lastv, newv); changed=1; } lastv=newv; if(!firstv) firstv=newv; newv->flags|=TNS_MESH_FLAG_SELECTED; if(newme){ newme->flags|=TNS_MESH_FLAG_SELECTED; } } if(TryClose){ if((mf=tnsMMeshVertsShareFace(lastv,firstv))&&mf->looplen==6) newme=tnsMMeshMakeEdge(mo, lastv, firstv); changed=1; } if(changed){ tnsMMeshRefreshIndex(mo); tnsMMeshCalculateNormal(mo); } return changed; } void la_KnifeFinish(MSelectExtra* se, tnsMeshObject*o){ if(o->ExtraBatch) tnsDeleteBatch(o->ExtraBatch); o->ExtraBatch=0; while(lstPopPointer(&se->KnifeElements)); la_FreeSelectData(se->sd); memFree(se); } void la_KnifeRefreshLoopCuts(MSelectExtra* se, tnsObject* o,tnsMEdge* me){ if(!me) return; tnsMMeshClearExtraFlags(o); laListHandle lst={0}; lstAppendPointer(&lst,me); me->flags|=TNS_MESH_FLAG_PICKED; tnsMMeshExpandBandList(o, me, &lst); if(lst.pFirst==lst.pLast){ while(lstPopPointer(&lst)); return; } tnsMEdge* mme; while((mme=lstPopPointer(&lst))){ MKnifeElement* ke=lstAppendPointerSized(&se->KnifeElements,mme,sizeof(MKnifeElement)); ke->Type=TNS_MMESH_EDGE_BIT; } } void la_KnifeSortLoopCuts(MSelectExtra* se){ laListHandle lst={0};MKnifeElement* ke=se->KnifeElements.pFirst,*NextKe; if(!ke) return; lstRemoveItem(&se->KnifeElements,ke); lstAppendItem(&lst,ke); for(ke=se->KnifeElements.pFirst;ke;ke=NextKe){ NextKe=ke->Item.pNext; tnsMEdge* me=ke->p,*firste=((MKnifeElement*)lst.pFirst)->p,*laste=((MKnifeElement*)lst.pLast)->p; if(tnsMMeshEdgeShareFace(me,firste) && (!tnsMMeshEdgeShareVert(me,firste))){ lstRemoveItem(&se->KnifeElements,ke); lstPushItem(&lst,ke); ke=se->KnifeElements.pFirst; continue; } if(tnsMMeshEdgeShareFace(me,laste) && (!tnsMMeshEdgeShareVert(me,laste))){ lstRemoveItem(&se->KnifeElements,ke); lstAppendItem(&lst,ke); ke=se->KnifeElements.pFirst; continue; } } if(se->KnifeElements.pFirst){ /* something went wrong */ while(lstPopPointer(&se->KnifeElements)); while(lstPopPointer(&lst)); } se->KnifeElements.pFirst=lst.pFirst; se->KnifeElements.pLast=lst.pLast; } int OPINV_Knife(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; } MSelectExtra* se=memAcquire(sizeof(MSelectExtra)); MSelectData* sd=la_InitSelectData(ex->OffScr->pColor[0]->Width, ex->OffScr->pColor[0]->Height, c); a->CustomData=se; se->sd=sd; se->root=root; int SelectMode=LA_CANVAS_SELECT_MODE_KNIFE; if(strSame(strGetArgumentString(a->ExtraInstructionsP, "mode"), "loop_cut")){ se->IsLoop=1; SelectMode=LA_CANVAS_SELECT_MODE_EDGES; } la_PopulateSelectDataPrimitives(sd,mo,c,SelectMode,ex->SelectThrough); if(se->IsLoop){ strSafePrint(&a->RuntimeHint,"◧ Cut ◨ Cancel"); } else{ strSafePrint(&a->RuntimeHint,"◧ Place Cut ◨ Cancel ⮨ Confirm"); } return LA_RUNNING; } int OPMOD_Knife(laOperator *a, laEvent *e){ if(!a->This || !a->This->EndInstance || !a->CustomData){ 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; MSelectExtra* se=a->CustomData; tnsMeshObject* mo=root->Active; int changed=0; if(e->Type==LA_R_MOUSE_DOWN || (e->Type == LA_KEY_DOWN && e->key==LA_KEY_ESCAPE)){ la_KnifeFinish(se,mo); laNotifyUsers("tns.world"); return LA_FINISHED; } if(e->Type&LA_MOUSE_EVENT){ int elemtype,id=la_SelectGetClosest(se->sd, e->x-ui->L, e->y-ui->U, LA_RH,&elemtype)-1; void* p=la_SelectGetRef(se->sd,id,elemtype); if(se->IsLoop){ if(se->PendingElem!=p){ changed=1; while(lstPopPointer(&se->KnifeElements)); se->PendingElem=p; la_KnifeRefreshLoopCuts(se, mo, se->PendingElem); la_KnifeSortLoopCuts(se); } }else{ if(la_KnifeIsDuplicated(se,p)) p=0; if(se->PendingElem!=p){ changed=1; } se->PendingElem=p; se->PendingElemType=elemtype; if(e->Type==LA_L_MOUSE_DOWN && p){ la_KnifeAppendCut(se); changed=1; } } } if((e->Type==LA_KEY_DOWN && e->key==LA_KEY_ENTER) || (se->IsLoop && e->Type==LA_L_MOUSE_DOWN)){ if(la_KnifeRegisterCuts(se,mo,se->IsLoop)){ tnsMMeshEnsureSelection(mo,ex->SelectMode); tnsInvalidateMeshBatch(mo); laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world",se->IsLoop?"Loop Cut":"Knife Cut",TNS_HINT_GEOMETRY); } la_KnifeFinish(se,mo); return LA_FINISHED; } if(changed){ la_KnifeUpdateToolBatch(se,mo); laRedrawCurrentPanel(); } return LA_RUNNING; } #define LA_MERGE_MODE_CENTER 0 #define LA_MERGE_MODE_FIRST 1 #define LA_MERGE_MODE_LAST 2 int la_MergeGetCenter(tnsMeshObject* mo, int SelectMode, int MergeMode, real* center){ real pos[3]={0},tmp[3]={0}; int count=0; tnsMVert* usev=0; tnsMEdge* usee=0; if(MergeMode!=LA_MERGE_MODE_CENTER){ if(MergeMode==LA_MERGE_MODE_FIRST){ usev=mo->FirstSelectV; usee=mo->FirstSelectE; } elif(MergeMode==LA_MERGE_MODE_LAST){ usev=mo->LastSelectV; usee=mo->LastSelectE; } if(SelectMode==LA_CANVAS_SELECT_MODE_VERTS && usev){ tnsVectorCopy3d(usev->p,center); return 1; } elif(SelectMode==LA_CANVAS_SELECT_MODE_EDGES && usee){ tnsVectorAccum3d(pos,usee->vl->p);tnsVectorAccum3d(pos,usee->vr->p); tnsVectorMultiSelf3d(pos,0.5f); tnsVectorCopy3d(pos,center); return 1; } } for(tnsMVert* mv=mo->mv.pFirst;mv;mv=mv->Item.pNext){ if(!(mv->flags&TNS_MESH_FLAG_SELECTED)) continue; tnsVectorAccum3d(pos,mv->p); count++; } if(!count) return 0; tnsVectorMultiSelf3d(pos,1.0f/count); tnsVectorCopy3d(pos,center); return 1; } void la_MergeSelected(tnsMeshObject* mo, real* center, int* count_success, int* count_fail){ tnsMVert* fmv=0,*NextMv=0; int success=0,fail=0,has_success=0; do{ fail=0; has_success=0; for(tnsMVert* mv=mo->mv.pFirst;mv;mv=NextMv){ NextMv=mv->Item.pNext; if((!(mv->flags&TNS_MESH_FLAG_SELECTED))||mv==fmv) continue; if(!fmv){ fmv=mv; success++; continue; } if(tnsMMeshMergeVerts(mo,fmv,mv)){ success++; has_success=1; }else{ fail++; } } }while(has_success&&fail); if(count_success){ *count_success=success; } if(count_fail){ *count_fail=fail; } if(!fmv) return; tnsVectorCopy3d(center,fmv->p); tnsMMeshRefreshIndex(mo); } int OPINV_Merge(laOperator *a, laEvent *e){ if(!a->This || !a->This->EndInstance){ return 0; } laCanvasExtra* ex=a->This->EndInstance; tnsCamera*c=ex->ViewingCamera; laUiItem* ui=ex->ParentUi; tnsObject*root=ui?ui->PP.EndInstance:0; if(!root) return LA_CANCELED; tnsMeshObject* mo=root->Active; int ran=0; if(!mo || mo->Base.Type!=TNS_OBJECT_MESH || mo->Mode!=TNS_MESH_EDIT_MODE){ return LA_CANCELED; } char* arg=strGetArgumentString(a->ExtraInstructionsP, "towards"); if(arg){ int MergeMode=LA_MERGE_MODE_CENTER; real center[3]; if(strSame(arg,"FIRST")){ MergeMode=LA_MERGE_MODE_FIRST; } elif(strSame(arg,"LAST")){ MergeMode=LA_MERGE_MODE_LAST; } if(!(la_MergeGetCenter(mo,ex->SelectMode,MergeMode,center))){ laEnableMessagePanel(a, 0, "Can't perform merging:", "No vertices selected.", e->x, e->y, 0, e); return LA_FINISHED; } int success,fail; la_MergeSelected(mo,center,&success,&fail); //printf("succeeded: %d failed:%d\n",success,fail); if(fail){ char* msg[256]; sprintf(msg,"Succeeded: %d vertices, failed: %d vertices.",success,fail); laEnableMessagePanel(a, 0, "Merging partially succeeded.", msg, e->x, e->y, 0, e); } if(success){ tnsMMeshEnsureSelection(mo,ex->SelectMode); tnsInvalidateMeshBatch(mo); laNotifyUsers("tns.world"); laRecordAndPush(0,"tns.world","Merge vertices",TNS_HINT_GEOMETRY); } }else{ laEnableOperatorPanel(a,a->This,e->x,e->y,200,200,0,0,0,0,0,0,0,0,e); return LA_RUNNING; } return LA_FINISHED; } void laui_Merge(laUiList *uil, laPropPack *pp, laPropPack *actinst, laColumn *extracol, int context){ laColumn* c=laFirstColumn(uil); laShowItemFull(uil,c,pp,"_this_M_merge",0,"towards=CENTER;text=Center",0,0); laShowItemFull(uil,c,pp,"_this_M_merge",0,"towards=FIRST;text=First",0,0); laShowItemFull(uil,c,pp,"_this_M_merge",0,"towards=LAST;text=Last",0,0); } int OPINV_NewRootObject(laOperator *a, laEvent *e){ tnsCreateRootObject("Root"); laNotifyUsers("tns.world.root_objects"); laNotifyUsers("tns.world.active_root"); laRecordDifferences(0,"tns.world"); laPushDifferences("New root object",0); return LA_FINISHED; } int OPCHK_RemoveRootObjects(laPropPack *This, laStringSplitor *ss){ if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,0)!=TNS_PC_OBJECT_GENERIC) return 0; if(((tnsObject*)This->EndInstance)->Type!=TNS_OBJECT_ROOT) return 0; }else{ if(!T->World->ActiveRoot) return 0; } return 1; } int OPINV_RemoveRootObject(laOperator *a, laEvent *e){ tnsObject* root=0; if(a->This && a->This->EndInstance){ root=a->This->EndInstance; } else{ root=T->World->ActiveRoot; } laNotifyDetached(root,root->Item.pNext?root->Item.pNext:root->Item.pPrev); tnsDestroyRootObject(root); laNotifyUsers("tns.world.root_objects"); laNotifyUsers("tns.world.active_root"); laRecordDifferences(0,"tns.world"); laPushDifferences("Removed root object",0); return LA_FINISHED; } int OPINV_NewMaterial(laOperator *a, laEvent *e){ tnsMaterial* mat=tnsCreateMaterial("Material"); laNotifyUsers("tns.world.materials"); if(a->This && a->This->EndInstance && la_EnsureSubTarget(a->This->LastPs->p,a->This->EndInstance)==TNS_PC_OBJECT_MESH){ tnsMeshObject* mo=a->This->EndInstance; if(mo->CurrentMaterial){ memAssignRef(mo->CurrentMaterial,&mo->CurrentMaterial->Material,mat); tnsInvalidateMeshBatch(mo); laRecordInstanceDifferences(mo,"tns_mesh_object"); laNotifyInstanceUsers(mo); } } laDetachedTrySet("material",mat); laRecordDifferences(0,"tns.world.materials"); laPushDifferences("New material",0); return LA_FINISHED; } int OPCHK_RemoveMaterial(laPropPack *This, laStringSplitor *ss){ if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,0)==TNS_PC_MATERIAL) return 1; } return 0; } int OPINV_RemoveMaterial(laOperator *a, laEvent *e){ if(!a->This || !a->This->EndInstance){ return LA_FINISHED; } tnsMaterial* mat=a->This->EndInstance; laNotifyDetached(mat,mat->Item.pNext?mat->Item.pNext:mat->Item.pPrev); tnsRemoveMaterial(mat); laNotifyUsers("tns.world"); laRecordDifferences(0,"tns.world"); laPushDifferences("Removed material",0); return LA_FINISHED; } int OPCHK_NewMaterialSlot(laPropPack *This, laStringSplitor *ss){ if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,This->EndInstance)==TNS_PC_OBJECT_MESH) return 1; } return 0; } int OPINV_NewMaterialSlot(laOperator *a, laEvent *e){ if(!a->This || !a->This->EndInstance){ return LA_FINISHED; } tnsMeshObject* mo=a->This->EndInstance; tnsNewMaterialSlot(mo); laNotifyInstanceUsers(mo); laRecordDifferences(0,"tns.world.objects"); laPushDifferences("New material slot",0); return LA_FINISHED; } int OPCHK_RemoveMaterialSlot(laPropPack *This, laStringSplitor *ss){ if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,0)==TNS_PC_MATERIAL_SLOT) return 1; } return 0; } int OPINV_RemoveMaterialSlot(laOperator *a, laEvent *e){ if(!a->This || !a->This->EndInstance){ return LA_FINISHED; } tnsMaterialSlot* ms=a->This->EndInstance; laNotifyInstanceUsers(ms->Parent); tnsRemoveMaterialSlot(ms->Parent,ms); laRecordDifferences(0,"tns.world.objects"); laPushDifferences("Remove material slot",0); return LA_FINISHED; } int OPCHK_AssignMaterialSlot(laPropPack *This, laStringSplitor *ss){ if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,This->EndInstance)==TNS_PC_OBJECT_MESH){ tnsMeshObject* mo=This->EndInstance; if(mo->Mode==TNS_MESH_EDIT_MODE && mo->CurrentMaterial) return 1; } } return 0; } int OPINV_AssignMaterialSlot(laOperator *a, laEvent *e){ if(!a->This || !a->This->EndInstance){ return LA_FINISHED; } tnsMeshObject* mo=a->This->EndInstance; tnsMaterialSlot* ms=mo->CurrentMaterial; if(mo->Mode!=TNS_MESH_EDIT_MODE || !ms) return LA_FINISHED; tnsAssignMaterialSlot(mo,ms); tnsInvalidateMeshBatch(mo); laNotifyInstanceUsers(mo); laNotifyUsers("tns.world"); laRecordInstanceDifferences(mo,"tns_mesh_object"); laPushDifferences("Assign material slot",0); return LA_FINISHED; } int OPCHK_RefreshMaterialShader(laPropPack *This, laStringSplitor *ss){ if(This && This->EndInstance){ if(la_EnsureSubTarget(This->LastPs->p,This->EndInstance)==TNS_PC_MATERIAL){ return 1; } } return 0; } int OPINV_RefreshMaterialShader(laOperator *a, laEvent *e){ if(!a->This || !a->This->EndInstance){ return LA_FINISHED; } tnsMaterial* m=a->This->EndInstance; if(m->AsLibrary){ tnsRefreshMaterialLibraries(); return LA_FINISHED; } tnsEnsureMaterialShader(m,1); tns_InvalidateMeshWithMaterial(m); laNotifyUsers("tns.world"); return LA_FINISHED; } void la_RegisterModellingOperators(){ laPropContainer *pc; laProp *p; laOperatorType *at; laEnumProp *ep; laCreateOperatorType("M_new_root", "New Root", "Create a new root object", 0, 0, 0, OPINV_NewRootObject, 0, '+', 0); laCreateOperatorType("M_remove_root", "Remove Root", "Remove root object", OPCHK_RemoveRootObjects, 0, 0, OPINV_RemoveRootObject, 0 ,L'🗴', 0); laCreateOperatorType("M_new_material", "New Material", "Create a new material", 0, 0, 0, OPINV_NewMaterial, 0, '+', 0); laCreateOperatorType("M_remove_material", "Remove Material", "Remove a material", OPCHK_RemoveMaterial, 0, 0, OPINV_RemoveMaterial, 0 ,L'🗴', 0); laCreateOperatorType("M_new_material_slot", "New Material Slot", "Create a new material slot", OPCHK_NewMaterialSlot, 0, 0, OPINV_NewMaterialSlot, 0, '+', 0); laCreateOperatorType("M_remove_material_slot", "Remove Material Slot", "Remove a material slot", OPCHK_RemoveMaterialSlot, 0, 0, OPINV_RemoveMaterialSlot, 0 ,L'🗴', 0); laCreateOperatorType("M_assign_material_slot", "Assign Material Slot", "Assign faces to a material slot", OPCHK_AssignMaterialSlot, 0, 0, OPINV_AssignMaterialSlot, 0 ,L'🖌', 0); laCreateOperatorType("M_refresh_material_shader", "Refresh Material Shader", "Refresh material shader", OPCHK_RefreshMaterialShader, 0, 0, OPINV_RefreshMaterialShader, 0 ,L'🗘', 0); laCreateOperatorType("M_set_cursor", "Set Cursor", "Set cursor in the viewport", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_SetCursor, 0, 0, LA_EXTRA_TO_PANEL); 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_clear_transformations", "Clear Transformations", "Clear object transformations", 0, 0, 0, OPINV_ClearTransformation, OPMOD_FinishOnData, 0, 0); at->UiDefine=laui_ClearTransformation; 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_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; pc = laDefineOperatorProps(at, 2); p=laAddEnumProperty(pc,"context","Context","Context of adding",0,0,0,0,0,offsetof(laObjectAddData,Context),0,0,0,0,0,0,0,0,0,0); laAddEnumItemAs(p,"OBJECT","Object","Object context",LA_ADD_CTX_OBJECT,0); laAddEnumItemAs(p,"MESH","Mesh","Mesh context",LA_ADD_CTX_MESH,0); 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); laCreateOperatorType("M_recalculate_normals", "Recalculate Normals", "Recalculate normals", 0, 0, 0, OPINV_RecalculateNormals, 0, 0, 0); laCreateOperatorType("M_knife", "Knife", "Cut through edges", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Knife, OPMOD_Knife, 0, LA_EXTRA_TO_PANEL); at=laCreateOperatorType("M_merge", "Merge", "Merge vertices", OPCHK_ViewportAndSceneExists, 0, 0, OPINV_Merge, OPMOD_FinishOnData, 0, LA_EXTRA_TO_PANEL); at->UiDefine=laui_Merge; }