/** * $Id$ * * ***** BEGIN GPL/BL DUAL LICENSE BLOCK ***** * * 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 2 * of the License, or (at your option) any later version. The Blender * Foundation also sells licenses for use in proprietary software under * the Blender License. See http://www.blender.org/BL/ for information * about this. * * 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, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. * All rights reserved. * * The Original Code is: all of this file. * * Contributor(s): none yet. * * ***** END GPL/BL DUAL LICENSE BLOCK ***** */ #include #include #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_arithb.h" #include "MTC_matrixops.h" #include "IMB_imbuf_types.h" #include "IMB_imbuf.h" #include "DNA_image_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_space_types.h" #include "DNA_screen_types.h" #include "DNA_scene_types.h" #include "DNA_view3d_types.h" #include "DNA_userdef_types.h" #include "BKE_utildefines.h" #include "BKE_depsgraph.h" #include "BKE_displist.h" #include "BKE_global.h" #include "BKE_mesh.h" #include "BKE_texture.h" #include "BKE_object.h" #include "BSE_view.h" #include "BSE_edit.h" #include "BSE_drawview.h" /* for backdrawview3d */ #include "BIF_editsima.h" #include "BIF_interface.h" #include "BIF_mywindow.h" #include "BIF_toolbox.h" #include "BIF_screen.h" #include "BIF_gl.h" #include "BIF_graphics.h" #include "BIF_space.h" /* for allqueue */ #include "BDR_drawmesh.h" #include "BDR_editface.h" #include "BDR_vpaint.h" #include "BDR_editface.h" #include "BDR_vpaint.h" #include "mydevice.h" #include "blendef.h" #include "butspace.h" #include "../img/IMG_Api.h" #include "BSE_trans_types.h" #include "BDR_unwrapper.h" TFace *lasttface=NULL; static void uv_calc_center_vector(float *result, Object *ob, Mesh *me) { float min[3], max[3], *cursx; int a; TFace *tface; MFace *mface; switch (G.vd->around) { case V3D_CENTRE: /* bounding box center */ min[0]= min[1]= min[2]= 1e20f; max[0]= max[1]= max[2]= -1e20f; tface= me->tface; mface= me->mface; for(a=0; atotface; a++, mface++, tface++) { if(tface->flag & TF_SELECT) { DO_MINMAX((me->mvert+mface->v1)->co, min, max); DO_MINMAX((me->mvert+mface->v2)->co, min, max); DO_MINMAX((me->mvert+mface->v3)->co, min, max); if(mface->v4) DO_MINMAX((me->mvert+mface->v4)->co, min, max); } } VecMidf(result, min, max); break; case V3D_CURSOR: /*cursor center*/ cursx= give_cursor(); /* shift to objects world */ result[0]= cursx[0]-ob->obmat[3][0]; result[1]= cursx[1]-ob->obmat[3][1]; result[2]= cursx[2]-ob->obmat[3][2]; break; case V3D_LOCAL: /*object center*/ case V3D_CENTROID: /* multiple objects centers, only one object here*/ default: result[0]= result[1]= result[2]= 0.0; break; } } static void uv_calc_map_matrix(float result[][4], Object *ob, float upangledeg, float sideangledeg, float radius) { float rotup[4][4], rotside[4][4], viewmatrix[4][4], rotobj[4][4]; float sideangle= 0.0, upangle= 0.0; int k; /* get rotation of the current view matrix */ Mat4CpyMat4(viewmatrix,G.vd->viewmat); /* but shifting */ for( k= 0; k< 4; k++) viewmatrix[3][k] =0.0; /* get rotation of the current object matrix */ Mat4CpyMat4(rotobj,ob->obmat); /* but shifting */ for( k= 0; k< 4; k++) rotobj[3][k] =0.0; Mat4Clr(*rotup); Mat4Clr(*rotside); /* compensate front/side.. against opengl x,y,z world definition */ /* this is "kanonen gegen spatzen", a few plus minus 1 will do here */ /* i wanted to keep the reason here, so we're rotating*/ sideangle= M_PI * (sideangledeg + 180.0) /180.0; rotside[0][0]= (float)cos(sideangle); rotside[0][1]= -(float)sin(sideangle); rotside[1][0]= (float)sin(sideangle); rotside[1][1]= (float)cos(sideangle); rotside[2][2]= 1.0f; upangle= M_PI * upangledeg /180.0; rotup[1][1]= (float)cos(upangle)/radius; rotup[1][2]= -(float)sin(upangle)/radius; rotup[2][1]= (float)sin(upangle)/radius; rotup[2][2]= (float)cos(upangle)/radius; rotup[0][0]= (float)1.0/radius; /* calculate transforms*/ Mat4MulSerie(result,rotup,rotside,viewmatrix,rotobj,NULL,NULL,NULL,NULL); } static void uv_calc_shift_project(float *target, float *shift, float rotmat[][4], int projectionmode, float *source, float *min, float *max) { float pv[3]; VecSubf(pv, source, shift); Mat4MulVecfl(rotmat, pv); switch(projectionmode) { case B_UVAUTO_CYLINDER: tubemap(pv[0], pv[1], pv[2], &target[0],&target[1]); /* split line is always zero */ if (target[0] >= 1.0f) target[0] -= 1.0f; break; case B_UVAUTO_SPHERE: spheremap(pv[0], pv[1], pv[2], &target[0],&target[1]); /* split line is always zero */ if (target[0] >= 1.0f) target[0] -= 1.0f; break; case 3: /* ortho special case for BOUNDS */ target[0] = -pv[0]; target[1] = pv[2]; break; case 4: { /* very special case for FROM WINDOW */ float pv4[4], dx, dy, x= 0.0, y= 0.0; dx= G.vd->area->winx; dy= G.vd->area->winy; VecCopyf(pv4, source); pv4[3] = 1.0; /* rotmat is the object matrix in this case */ Mat4MulVec4fl(rotmat,pv4); /* almost project_short */ Mat4MulVec4fl(G.vd->persmat,pv4); if (fabs(pv4[3]) > 0.00001) { /* avoid division by zero */ target[0] = dx/2.0 + (dx/2.0)*pv4[0]/pv4[3]; target[1] = dy/2.0 + (dy/2.0)*pv4[1]/pv4[3]; } else { /* scaling is lost but give a valid result */ target[0] = dx/2.0 + (dx/2.0)*pv4[0]; target[1] = dy/2.0 + (dy/2.0)*pv4[1]; } /* G.vd->persmat seems to do this funky scaling */ if(dx > dy) { y= (dx-dy)/2.0; dy = dx; } else { x= (dy-dx)/2.0; dx = dy; } target[0]= (x + target[0])/dx; target[1]= (y + target[1])/dy; } break; default: target[0] = 0.0; target[1] = 1.0; } /* we know the values here and may need min_max later */ /* max requests independand from min; not fastest but safest */ if(min) { min[0] = MIN2(target[0], min[0]); min[1] = MIN2(target[1], min[1]); } if(max) { max[0] = MAX2(target[0], max[0]); max[1] = MAX2(target[1], max[1]); } } void calculate_uv_map(unsigned short mapmode) { Mesh *me; TFace *tface; MFace *mface; Object *ob; float dx, dy, rotatematrix[4][4], radius= 1.0, min[3], cent[3], max[3]; float fac= 1.0, upangledeg= 0.0, sideangledeg= 90.0; int i, b, mi, a, n; if(G.scene->toolsettings->uvcalc_mapdir==1) { upangledeg= 90.0; sideangledeg= 0.0; } else { upangledeg= 0.0; if(G.scene->toolsettings->uvcalc_mapalign==1) sideangledeg= 0.0; else sideangledeg= 90.0; } me= get_mesh(ob=OBACT); if(me==0 || me->tface==0) return; if(me->totface==0) return; switch(mapmode) { case B_UVAUTO_BOUNDS1: case B_UVAUTO_BOUNDS2: case B_UVAUTO_BOUNDS4: case B_UVAUTO_BOUNDS8: switch(mapmode) { case B_UVAUTO_BOUNDS2: fac = 0.5; break; case B_UVAUTO_BOUNDS4: fac = 0.25; break; case B_UVAUTO_BOUNDS8: fac = 0.125; break; } min[0]= min[1]= 1.0; max[0]= max[1]= 0.0; uv_calc_center_vector(cent, ob, me); uv_calc_map_matrix(rotatematrix, ob, upangledeg, sideangledeg, 1.0f); tface= me->tface; mface= me->mface; for(a=0; atotface; a++, mface++, tface++) { if(tface->flag & TF_SELECT) { uv_calc_shift_project(tface->uv[0],cent,rotatematrix,3,(me->mvert+mface->v1)->co,min,max); uv_calc_shift_project(tface->uv[1],cent,rotatematrix,3,(me->mvert+mface->v2)->co,min,max); uv_calc_shift_project(tface->uv[2],cent,rotatematrix,3,(me->mvert+mface->v3)->co,min,max); if(mface->v4) uv_calc_shift_project(tface->uv[3],cent,rotatematrix,3,(me->mvert+mface->v4)->co,min,max); } } /* rescale UV to be in 0..1,1/2,1/4,1/8 */ dx= (max[0]-min[0]); dy= (max[1]-min[1]); tface= me->tface; mface= me->mface; for(a=0; atotface; a++, mface++, tface++) { if(tface->flag & TF_SELECT) { if(mface->v4) b= 3; else b= 2; for(; b>=0; b--) { tface->uv[b][0]= ((tface->uv[b][0]-min[0])*fac)/dx; tface->uv[b][1]= 1.0-fac+((tface->uv[b][1]-min[1])*fac)/dy; } } } break; case B_UVAUTO_WINDOW: cent[0] = cent[1] = cent[2] = 0.0; Mat4CpyMat4(rotatematrix,ob->obmat); tface= me->tface; mface= me->mface; for(a=0; atotface; a++, mface++, tface++) { if(tface->flag & TF_SELECT) { uv_calc_shift_project(tface->uv[0],cent,rotatematrix,4,(me->mvert+mface->v1)->co,NULL,NULL); uv_calc_shift_project(tface->uv[1],cent,rotatematrix,4,(me->mvert+mface->v2)->co,NULL,NULL); uv_calc_shift_project(tface->uv[2],cent,rotatematrix,4,(me->mvert+mface->v3)->co,NULL,NULL); if(mface->v4) uv_calc_shift_project(tface->uv[3],cent,rotatematrix,4,(me->mvert+mface->v4)->co,NULL,NULL); } } break; case B_UVAUTO_STD8: case B_UVAUTO_STD4: case B_UVAUTO_STD2: case B_UVAUTO_STD1: switch(mapmode) { case B_UVAUTO_STD8: fac = 0.125; break; case B_UVAUTO_STD4: fac = 0.25; break; case B_UVAUTO_STD2: fac = 0.5; break; } tface= me->tface; for(a=0; atotface; a++, tface++) if(tface->flag & TF_SELECT) default_uv(tface->uv, fac); break; case B_UVAUTO_CYLINDER: case B_UVAUTO_SPHERE: uv_calc_center_vector(cent, ob, me); if(mapmode==B_UVAUTO_CYLINDER) radius = G.scene->toolsettings->uvcalc_radius; /* be compatible to the "old" sphere/cylinder mode */ if (G.scene->toolsettings->uvcalc_mapdir== 2) Mat4One(rotatematrix); else uv_calc_map_matrix(rotatematrix,ob,upangledeg,sideangledeg,radius); tface= me->tface; mface= me->mface; for(a=0; atotface; a++, mface++, tface++) { if(tface->flag & TF_SELECT) { uv_calc_shift_project(tface->uv[0],cent,rotatematrix,mapmode,(me->mvert+mface->v1)->co,NULL,NULL); uv_calc_shift_project(tface->uv[1],cent,rotatematrix,mapmode,(me->mvert+mface->v2)->co,NULL,NULL); uv_calc_shift_project(tface->uv[2],cent,rotatematrix,mapmode,(me->mvert+mface->v3)->co,NULL,NULL); n = 3; if(mface->v4) { uv_calc_shift_project(tface->uv[3],cent,rotatematrix,mapmode,(me->mvert+mface->v4)->co,NULL,NULL); n=4; } mi = 0; for (i = 1; i < n; i++) if (tface->uv[i][0] > tface->uv[mi][0]) mi = i; for (i = 0; i < n; i++) { if (i != mi) { dx = tface->uv[mi][0] - tface->uv[i][0]; if (dx > 0.5) tface->uv[i][0] += 1.0; } } } } break; case B_UVAUTO_CUBE: { /* choose x,y,z axis for projetion depending on the largest normal */ /* component, but clusters all together around the center of map */ float no[3]; short cox, coy; float *loc= ob->obmat[3]; MVert *mv= me->mvert; float cubesize = G.scene->toolsettings->uvcalc_cubesize; tface= me->tface; mface= me->mface; for(a=0; atotface; a++, mface++, tface++) { if(tface->flag & TF_SELECT) { CalcNormFloat((mv+mface->v1)->co, (mv+mface->v2)->co, (mv+mface->v3)->co, no); no[0]= fabs(no[0]); no[1]= fabs(no[1]); no[2]= fabs(no[2]); cox=0; coy= 1; if(no[2]>=no[0] && no[2]>=no[1]); else if(no[1]>=no[0] && no[1]>=no[2]) coy= 2; else { cox= 1; coy= 2; } tface->uv[0][0]= 0.5+0.5*cubesize*(loc[cox] + (mv+mface->v1)->co[cox]); tface->uv[0][1]= 0.5+0.5*cubesize*(loc[coy] + (mv+mface->v1)->co[coy]); dx = floor(tface->uv[0][0]); dy = floor(tface->uv[0][1]); tface->uv[0][0] -= dx; tface->uv[0][1] -= dy; tface->uv[1][0]= 0.5+0.5*cubesize*(loc[cox] + (mv+mface->v2)->co[cox]); tface->uv[1][1]= 0.5+0.5*cubesize*(loc[coy] + (mv+mface->v2)->co[coy]); tface->uv[1][0] -= dx; tface->uv[1][1] -= dy; tface->uv[2][0]= 0.5+0.5*cubesize*(loc[cox] + (mv+mface->v3)->co[cox]); tface->uv[2][1]= 0.5+0.5*cubesize*(loc[coy] + (mv+mface->v3)->co[coy]); tface->uv[2][0] -= dx; tface->uv[2][1] -= dy; if(mface->v4) { tface->uv[3][0]= 0.5+0.5*cubesize*(loc[cox] + (mv+mface->v4)->co[cox]); tface->uv[3][1]= 0.5+0.5*cubesize*(loc[coy] + (mv+mface->v4)->co[coy]); tface->uv[3][0] -= dx; tface->uv[3][1] -= dy; } } } } break; default: return; } /* end switch mapmode */ /* clipping and wrapping */ if(G.sima && G.sima->flag & SI_CLIP_UV) { tface= me->tface; mface= me->mface; for(a=0; atotface; a++, mface++, tface++) { if(!(tface->flag & TF_SELECT)) continue; dx= dy= 0; if(mface->v4) b= 3; else b= 2; for(; b>=0; b--) { while(tface->uv[b][0] + dx < 0.0) dx+= 0.5; while(tface->uv[b][0] + dx > 1.0) dx-= 0.5; while(tface->uv[b][1] + dy < 0.0) dy+= 0.5; while(tface->uv[b][1] + dy > 1.0) dy-= 0.5; } if(mface->v4) b= 3; else b= 2; for(; b>=0; b--) { tface->uv[b][0]+= dx; CLAMP(tface->uv[b][0], 0.0, 1.0); tface->uv[b][1]+= dy; CLAMP(tface->uv[b][1], 0.0, 1.0); } } } BIF_undo_push("UV calculation"); object_uvs_changed(OBACT); allqueue(REDRAWVIEW3D, 0); allqueue(REDRAWIMAGE, 0); } void set_lasttface() { Mesh *me; TFace *tface; int a; lasttface= 0; if(OBACT==NULL || OBACT->type!=OB_MESH) return; me= get_mesh(OBACT); if(me==0 || me->tface==0) return; tface= me->tface; a= me->totface; while(a--) { if(tface->flag & TF_ACTIVE) { lasttface= tface; return; } tface++; } tface= me->tface; a= me->totface; while(a--) { if(tface->flag & TF_SELECT) { lasttface= tface; return; } tface++; } tface= me->tface; a= me->totface; while(a--) { if((tface->flag & TF_HIDE)==0) { lasttface= tface; return; } tface++; } } void default_uv(float uv[][2], float size) { int dy; if(size>1.0) size= 1.0; dy= 1.0-size; uv[0][0]= 0; uv[0][1]= size+dy; uv[1][0]= 0; uv[1][1]= dy; uv[2][0]= size; uv[2][1]= dy; uv[3][0]= size; uv[3][1]= size+dy; } void default_tface(TFace *tface) { default_uv(tface->uv, 1.0); tface->col[0]= tface->col[1]= tface->col[2]= tface->col[3]= vpaint_get_current_col(); tface->mode= TF_TEX; tface->mode= 0; tface->flag= TF_SELECT; tface->tpage= 0; tface->mode |= TF_DYNAMIC; } void make_tfaces(Mesh *me) { TFace *tface; int a; a= me->totface; if(a==0) return; tface= me->tface= MEM_callocN(a*sizeof(TFace), "tface"); while(a--) { default_tface(tface); tface++; } if(me->mcol) { mcol_to_tface(me, 1); } } void reveal_tface() { Mesh *me; TFace *tface; int a; me= get_mesh(OBACT); if(me==0 || me->tface==0 || me->totface==0) return; tface= me->tface; a= me->totface; while(a--) { if(tface->flag & TF_HIDE) { tface->flag |= TF_SELECT; tface->flag -= TF_HIDE; } tface++; } BIF_undo_push("Reveal UV face"); object_tface_flags_changed(OBACT, 0); } void hide_tface() { Mesh *me; TFace *tface; int a; me= get_mesh(OBACT); if(me==0 || me->tface==0 || me->totface==0) return; if(G.qual & LR_ALTKEY) { reveal_tface(); return; } tface= me->tface; a= me->totface; while(a--) { if(tface->flag & TF_HIDE); else { if(G.qual & LR_SHIFTKEY) { if( (tface->flag & TF_SELECT)==0) tface->flag |= TF_HIDE; } else { if( (tface->flag & TF_SELECT)) tface->flag |= TF_HIDE; } } if(tface->flag & TF_HIDE) tface->flag &= ~TF_SELECT; tface++; } BIF_undo_push("Hide UV face"); object_tface_flags_changed(OBACT, 0); } void select_linked_tfaces(int mode) { Object *ob; Mesh *me; short mval[2]; unsigned int index=0; ob = OBACT; me = get_mesh(ob); if(me==0 || me->tface==0 || me->totface==0) return; if (mode==0 || mode==1) { if (!(ob->lay & G.vd->lay)) error("The active object is not in this layer"); getmouseco_areawin(mval); if (!face_pick(me, mval[0], mval[1], &index)) return; } select_linked_tfaces_with_seams(mode, me, index); } void deselectall_tface() { Mesh *me; TFace *tface; int a, sel; me= get_mesh(OBACT); if(me==0 || me->tface==0) return; tface= me->tface; a= me->totface; sel= 0; while(a--) { if(tface->flag & TF_HIDE); else if(tface->flag & TF_SELECT) sel= 1; tface++; } tface= me->tface; a= me->totface; while(a--) { if(tface->flag & TF_HIDE); else { if(sel) tface->flag &= ~TF_SELECT; else tface->flag |= TF_SELECT; } tface++; } BIF_undo_push("(De)select all UV face"); object_tface_flags_changed(OBACT, 0); } void selectswap_tface(void) { Mesh *me; TFace *tface; int a; me= get_mesh(OBACT); if(me==0 || me->tface==0) return; tface= me->tface; a= me->totface; while(a--) { if(tface->flag & TF_HIDE); else { if(tface->flag & TF_SELECT) tface->flag &= ~TF_SELECT; else tface->flag |= TF_SELECT; } tface++; } BIF_undo_push("Select inverse UV face"); object_tface_flags_changed(OBACT, 0); } void rotate_uv_tface() { Mesh *me; TFace *tface; MFace *mface; short mode; int a; me= get_mesh(OBACT); if(me==0 || me->tface==0) return; mode= pupmenu("Rotate %t|UV Co-ordinates %x1|Vertex Colors %x2"); if(mode<1) return; tface= me->tface; mface= me->mface; a= me->totface; while(a--) { if(tface->flag & TF_SELECT) { if(mode==1) { float u1= tface->uv[0][0]; float v1= tface->uv[0][1]; tface->uv[0][0]= tface->uv[1][0]; tface->uv[0][1]= tface->uv[1][1]; tface->uv[1][0]= tface->uv[2][0]; tface->uv[1][1]= tface->uv[2][1]; if(mface->v4) { tface->uv[2][0]= tface->uv[3][0]; tface->uv[2][1]= tface->uv[3][1]; tface->uv[3][0]= u1; tface->uv[3][1]= v1; } else { tface->uv[2][0]= u1; tface->uv[2][1]= v1; } } else if(mode==2) { unsigned int tcol= tface->col[0]; tface->col[0]= tface->col[1]; tface->col[1]= tface->col[2]; if(mface->v4) { tface->col[2]= tface->col[3]; tface->col[3]= tcol; } else { tface->col[2]= tcol; } } } tface++; mface++; } BIF_undo_push("Rotate UV face"); object_uvs_changed(OBACT); } void mirror_uv_tface() { Mesh *me; TFace *tface; MFace *mface; short mode; int a; me= get_mesh(OBACT); if(me==0 || me->tface==0) return; mode= pupmenu("Mirror %t|UV Co-ordinates %x1|Vertex Colors %x2"); if(mode<1) return; tface= me->tface; mface= me->mface; a= me->totface; while(a--) { if(tface->flag & TF_SELECT) { if(mode==1) { float u1= tface->uv[0][0]; float v1= tface->uv[0][1]; if(mface->v4) { tface->uv[0][0]= tface->uv[3][0]; tface->uv[0][1]= tface->uv[3][1]; tface->uv[3][0]= u1; tface->uv[3][1]= v1; u1= tface->uv[1][0]; v1= tface->uv[1][1]; tface->uv[1][0]= tface->uv[2][0]; tface->uv[1][1]= tface->uv[2][1]; tface->uv[2][0]= u1; tface->uv[2][1]= v1; } else { tface->uv[0][0]= tface->uv[2][0]; tface->uv[0][1]= tface->uv[2][1]; tface->uv[2][0]= u1; tface->uv[2][1]= v1; } } else if(mode==2) { unsigned int tcol= tface->col[0]; if(mface->v4) { tface->col[0]= tface->col[3]; tface->col[3]= tcol; tcol = tface->col[1]; tface->col[1]= tface->col[2]; tface->col[2]= tcol; } else { tface->col[0]= tface->col[2]; tface->col[2]= tcol; } } } tface++; mface++; } BIF_undo_push("Mirror UV face"); object_uvs_changed(OBACT); } void minmax_tface(float *min, float *max) { Object *ob; Mesh *me; MFace *mf; TFace *tf; MVert *mv; int a; float vec[3], bmat[3][3]; ob = OBACT; if (ob==0) return; me= get_mesh(ob); if(me==0 || me->tface==0) return; Mat3CpyMat4(bmat, ob->obmat); mv= me->mvert; mf= me->mface; tf= me->tface; for (a=me->totface; a>0; a--, mf++, tf++) { if (tf->flag & TF_HIDE || !(tf->flag & TF_SELECT)) continue; VECCOPY(vec, (mv+mf->v1)->co); Mat3MulVecfl(bmat, vec); VecAddf(vec, vec, ob->obmat[3]); DO_MINMAX(vec, min, max); VECCOPY(vec, (mv+mf->v2)->co); Mat3MulVecfl(bmat, vec); VecAddf(vec, vec, ob->obmat[3]); DO_MINMAX(vec, min, max); VECCOPY(vec, (mv+mf->v3)->co); Mat3MulVecfl(bmat, vec); VecAddf(vec, vec, ob->obmat[3]); DO_MINMAX(vec, min, max); if (mf->v4) { VECCOPY(vec, (mv+mf->v4)->co); Mat3MulVecfl(bmat, vec); VecAddf(vec, vec, ob->obmat[3]); DO_MINMAX(vec, min, max); } } } /** * Returns the face under the give position in screen coordinates. * Code extracted from face_select routine. * Question: why is all of the backbuffer drawn? * We're only interested in one pixel! * @author Maarten Gribnau * @param me the mesh with the faces to be picked * @param x the x-coordinate to pick at * @param y the y-coordinate to pick at * @param index the index of the face * @return 1 if found, 0 if none found */ int face_pick(Mesh *me, short x, short y, unsigned int *index) { unsigned int col; if (me==0 || me->tface==0) return 0; /* Have OpenGL draw in the back buffer with color coded face indices */ if (curarea->win_swap==WIN_EQUAL) { G.vd->flag |= V3D_NEEDBACKBUFDRAW; } if (G.vd->flag & V3D_NEEDBACKBUFDRAW) { backdrawview3d(0); persp(PERSP_VIEW); } /* Read the pixel under the cursor */ #ifdef __APPLE__ glReadBuffer(GL_AUX0); #endif glReadPixels(x+curarea->winrct.xmin, y+curarea->winrct.ymin, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &col); glReadBuffer(GL_BACK); /* Unbelievable! */ if (G.order==B_ENDIAN) { SWITCH_INT(col); } /* Convert the color back to a face index */ *index = framebuffer_to_index(col); if (col==0 || (*index)<=0 || (*index)>(unsigned) me->totface) return 0; (*index)--; return 1; } void face_select() { Object *ob; Mesh *me; TFace *tface, *tsel; MFace *msel; short mval[2]; unsigned int a, index; /* Get the face under the cursor */ ob = OBACT; if (!(ob->lay & G.vd->lay)) { error("The active object is not in this layer"); } me = get_mesh(ob); getmouseco_areawin(mval); if (!face_pick(me, mval[0], mval[1], &index)) return; tsel= (((TFace*)me->tface)+index); msel= (((MFace*)me->mface)+index); if (tsel->flag & TF_HIDE) return; /* clear flags */ tface = me->tface; a = me->totface; while (a--) { if (G.qual & LR_SHIFTKEY) tface->flag &= ~TF_ACTIVE; else tface->flag &= ~(TF_ACTIVE+TF_SELECT); tface++; } tsel->flag |= TF_ACTIVE; if (G.qual & LR_SHIFTKEY) { if (tsel->flag & TF_SELECT) tsel->flag &= ~TF_SELECT; else tsel->flag |= TF_SELECT; } else tsel->flag |= TF_SELECT; lasttface = tsel; /* image window redraw */ BIF_undo_push("Select UV face"); object_tface_flags_changed(OBACT, 1); } void face_borderselect() { Mesh *me; TFace *tface; rcti rect; struct ImBuf *ibuf; unsigned int *rt; int a, sx, sy, index, val; char *selar; me= get_mesh(OBACT); if(me==0 || me->tface==0) return; if(me->totface==0) return; val= get_border(&rect, 3); /* why readbuffer here? shouldn't be necessary (maybe a flush or so) */ glReadBuffer(GL_BACK); #ifdef __APPLE__ glReadBuffer(GL_AUX0); /* apple only */ #endif if(val) { selar= MEM_callocN(me->totface+1, "selar"); sx= (rect.xmax-rect.xmin+1); sy= (rect.ymax-rect.ymin+1); if(sx*sy<=0) return; ibuf = IMB_allocImBuf(sx,sy,32,0,0); rt = ibuf->rect; glReadPixels(rect.xmin+curarea->winrct.xmin, rect.ymin+curarea->winrct.ymin, sx, sy, GL_RGBA, GL_UNSIGNED_BYTE, ibuf->rect); if(G.order==B_ENDIAN) IMB_convert_rgba_to_abgr(ibuf); a= sx*sy; while(a--) { if(*rt) { index= framebuffer_to_index(*rt); if(index<=me->totface) selar[index]= 1; } rt++; } tface= me->tface; for(a=1; a<=me->totface; a++, tface++) { if(selar[a]) { if(tface->flag & TF_HIDE); else { if(val==LEFTMOUSE) tface->flag |= TF_SELECT; else tface->flag &= ~TF_SELECT; } } } IMB_freeImBuf(ibuf); MEM_freeN(selar); BIF_undo_push("Border Select UV face"); object_tface_flags_changed(OBACT, 0); } #ifdef __APPLE__ glReadBuffer(GL_BACK); #endif } /* Pupmenu codes: */ #define UV_CUBE_MAPPING 2 #define UV_CYL_MAPPING 3 #define UV_SPHERE_MAPPING 4 #define UV_BOUNDS8_MAPPING 68 #define UV_BOUNDS4_MAPPING 65 #define UV_BOUNDS2_MAPPING 66 #define UV_BOUNDS1_MAPPING 67 #define UV_STD8_MAPPING 131 #define UV_STD4_MAPPING 130 #define UV_STD2_MAPPING 129 #define UV_STD1_MAPPING 128 #define UV_WINDOW_MAPPING 5 #define UV_UNWRAP_MAPPING 6 #define UV_CYL_EX 32 #define UV_SPHERE_EX 34 /* Some macro tricks to make pupmenu construction look nicer :-) Sorry, just did it for fun. */ #define _STR(x) " " #x #define STRING(x) _STR(x) #define MENUSTRING(string, code) string " %x" STRING(code) #define MENUTITLE(string) string " %t|" void uv_autocalc_tface() { short mode; mode= pupmenu(MENUTITLE("UV Calculation") MENUSTRING("Cube", UV_CUBE_MAPPING) "|" MENUSTRING("Cylinder", UV_CYL_MAPPING) "|" MENUSTRING("Sphere", UV_SPHERE_MAPPING) "|" MENUSTRING("Unwrap", UV_UNWRAP_MAPPING) "|" MENUSTRING("Bounds to 1/8", UV_BOUNDS8_MAPPING) "|" MENUSTRING("Bounds to 1/4", UV_BOUNDS4_MAPPING) "|" MENUSTRING("Bounds to 1/2", UV_BOUNDS2_MAPPING) "|" MENUSTRING("Bounds to 1/1", UV_BOUNDS1_MAPPING) "|" MENUSTRING("Standard 1/8", UV_STD8_MAPPING) "|" MENUSTRING("Standard 1/4", UV_STD4_MAPPING) "|" MENUSTRING("Standard 1/2", UV_STD2_MAPPING) "|" MENUSTRING("Standard 1/1", UV_STD1_MAPPING) "|" MENUSTRING("From Window", UV_WINDOW_MAPPING) ); switch(mode) { case UV_CUBE_MAPPING: calculate_uv_map(B_UVAUTO_CUBE); break; case UV_CYL_MAPPING: calculate_uv_map(B_UVAUTO_CYLINDER); break; case UV_SPHERE_MAPPING: calculate_uv_map(B_UVAUTO_SPHERE); break; case UV_BOUNDS8_MAPPING: calculate_uv_map(B_UVAUTO_BOUNDS8); break; case UV_BOUNDS4_MAPPING: calculate_uv_map(B_UVAUTO_BOUNDS4); break; case UV_BOUNDS2_MAPPING: calculate_uv_map(B_UVAUTO_BOUNDS2); break; case UV_BOUNDS1_MAPPING: calculate_uv_map(B_UVAUTO_BOUNDS1); break; case UV_STD8_MAPPING: calculate_uv_map(B_UVAUTO_STD8); break; case UV_STD4_MAPPING: calculate_uv_map(B_UVAUTO_STD4); break; case UV_STD2_MAPPING: calculate_uv_map(B_UVAUTO_STD2); break; case UV_STD1_MAPPING: calculate_uv_map(B_UVAUTO_STD1); break; case UV_WINDOW_MAPPING: calculate_uv_map(B_UVAUTO_WINDOW); break; case UV_UNWRAP_MAPPING: unwrap_lscm(); break; } } void set_faceselect() /* toggle */ { Object *ob = OBACT; Mesh *me = 0; scrarea_queue_headredraw(curarea); if(ob==NULL || ob->id.lib) return; if(G.f & G_FACESELECT) G.f &= ~G_FACESELECT; else { if (ob && ob->type == OB_MESH) G.f |= G_FACESELECT; } allqueue(REDRAWVIEW3D, 0); allqueue(REDRAWBUTSEDIT, 0); allqueue(REDRAWIMAGE, 0); ob= OBACT; me= get_mesh(ob); if(me && me->tface==NULL) make_tfaces(me); if(G.f & G_FACESELECT) { setcursor_space(SPACE_VIEW3D, CURSOR_FACESEL); if(me) { set_lasttface(); set_seamtface(); /* set TF_SEAM flags in tface */ } BIF_undo_push("Set UV Faceselect"); } else if((G.f & (G_WEIGHTPAINT|G_VERTEXPAINT|G_TEXTUREPAINT))==0) { if(me) { reveal_tface(); DAG_object_flush_update(G.scene, ob, OB_RECALC_DATA); } setcursor_space(SPACE_VIEW3D, CURSOR_STD); BIF_undo_push("End UV Faceselect"); } countall(); } /** * Get the view ray through the screen point. * Uses the OpenGL settings of the active view port. * The coordinates should be given in viewport coordinates. * @author Maarten Gribnau * @param x the x-coordinate of the screen point. * @param y the y-coordinate of the screen point. * @param org origin of the view ray. * @param dir direction of the view ray. */ static void get_pick_ray(short x, short y, float org[3], float dir[3]) { double mvmatrix[16]; double projmatrix[16]; GLint viewport[4]; double px, py, pz; float l; /* Get the matrices needed for gluUnProject */ glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix); glGetDoublev(GL_PROJECTION_MATRIX, projmatrix); /* Set up viewport so that gluUnProject will give correct values */ viewport[0] = 0; viewport[1] = 0; /* printf("viewport = (%4d, %4d, %4d, %4d)\n", viewport[0], viewport[1], viewport[2], viewport[3]); */ /* printf("cursor = (%4d, %4d)\n", x, y); */ gluUnProject((GLdouble) x, (GLdouble) y, 0.0, mvmatrix, projmatrix, viewport, &px, &py, &pz); org[0] = (float)px; org[1] = (float)py; org[2] = (float)pz; /* printf("world point at z=0.0 is (%f, %f, %f)\n", org[0], org[1], org[2]); */ gluUnProject((GLdouble) x, (GLdouble) y, 1.0, mvmatrix, projmatrix, viewport, &px, &py, &pz); /* printf("world point at z=1.0 is (%f, %f, %f)\n", px, py, pz); */ dir[0] = ((float)px) - org[0]; dir[1] = ((float)py) - org[1]; dir[2] = ((float)pz) - org[2]; l = (float)sqrt(dir[0]*dir[0] + dir[1]*dir[1] + dir[2]*dir[2]); if (!l) return; l = 1. / l; dir[0] *= l; dir[1] *= l; dir[2] *= l; /* printf("ray org. is (%f, %f, %f)\n", org[0], org[1], org[2]); */ /* printf("ray dir. is (%f, %f, %f)\n", dir[0], dir[1], dir[2]); */ } static int triangle_ray_intersect(float tv0[3], float tv1[3], float tv2[3], float org[3], float dir[3], float uv[2]) { float v1v0[3]; float v2v0[3]; float n[3], an[3]; float t, d, l; float p[3]; double u0, v0, u1, v1, u2, v2, uvtemp; unsigned int iu, iv; /* Calculate normal of the plane (cross, normalize) * Could really use moto here... */ v1v0[0] = tv1[0] - tv0[0]; v1v0[1] = tv1[1] - tv0[1]; v1v0[2] = tv1[2] - tv0[2]; v2v0[0] = tv2[0] - tv0[0]; v2v0[1] = tv2[1] - tv0[1]; v2v0[2] = tv2[2] - tv0[2]; n[0] = (v1v0[1] * v2v0[2]) - (v1v0[2] * v2v0[1]); n[1] = (v1v0[2] * v2v0[0]) - (v1v0[0] * v2v0[2]); n[2] = (v1v0[0] * v2v0[1]) - (v1v0[1] * v2v0[0]); l = sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]); if (!l) return 0; l = 1. / l; n[0] *= l; n[1] *= l; n[2] *= l; /* Calculate intersection point */ t = n[0]*dir[0] + n[1]*dir[1] + n[2]*dir[2]; if (fabs(t) < 1.0e-6) return 0; d = -(n[0]*tv0[0] + n[1]*tv0[1] + n[2]*tv0[2]); t = -(((n[0]*org[0] + n[1]*org[1] + n[2]*org[2]) + d) / t); if (t < 0) return 0; p[0] = org[0] + dir[0]*t; p[1] = org[1] + dir[1]*t; p[2] = org[2] + dir[2]*t; /*printf("intersection at (%f, %f, %f)\n", p[0], p[1], p[2]);*/ /* Calculate the largest component of the normal */ an[0] = fabs(n[0]); an[1] = fabs(n[1]); an[2] = fabs(n[2]); if ((an[0] > an[1]) && (an[0] > an[2])) { iu = 1; iv = 2; } else if ((an[1] > an[0]) && (an[1] > an[2])) { iu = 2; iv = 0; } else { iu = 0; iv = 1; } /* printf("iu, iv = (%d, %d)\n", iu, iv); */ /* Calculate (u,v) */ u0 = p[iu] - tv0[iu]; v0 = p[iv] - tv0[iv]; u1 = tv1[iu] - tv0[iu]; v1 = tv1[iv] - tv0[iv]; u2 = tv2[iu] - tv0[iu]; v2 = tv2[iv] - tv0[iv]; /* printf("u0, v0, u1, v1, u2, v2 = (%f, %f, %f, %f, %f, %f)\n", u0, v0, u1, v1, u2, v2); */ /* These calculations should be in double precision. * On windows we get inpredictable results in single precision */ if (u1 == 0) { uvtemp = u0/u2; uv[1] = (float)uvtemp; /* if ((uv[1] >= 0.) && (uv[1] <= 1.)) { */ uv[0] = (float)((v0 - uvtemp*v2) / v1); /* } */ } else { uvtemp = (v0*u1 - u0*v1)/(v2*u1-u2*v1); uv[1] = (float)uvtemp; /* if ((uv[1] >= 0) && (uv[1] <= 1)) { */ uv[0] = (float)((u0 - uvtemp*u2) / u1); /* } */ } /* printf("uv[0], uv[1] = (%f, %f)\n", uv[0], uv[1]); */ return ((uv[0] >= 0) && (uv[1] >= 0) && ((uv[0]+uv[1]) <= 1)) ? 2 : 1; } /** * Returns the vertex (local) coordinates of a face. * No bounds checking! * @author Maarten Gribnau * @param mesh the mesh with the face. * @param face the face. * @param v1 vertex 1 coordinates. * @param v2 vertex 2 coordinates. * @param v3 vertex 3 coordinates. * @param v4 vertex 4 coordinates. * @return number of vertices of this face */ static int face_get_vertex_coordinates(Mesh* mesh, TFace* face, float v1[3], float v2[3], float v3[3], float v4[3]) { int num_vertices; MVert *mv; MFace *mf = (MFace *) (((MFace *)mesh->mface) + (face - (TFace *) mesh->tface)); num_vertices = mf->v4 == 0 ? 3 : 4; mv = mesh->mvert + mf->v1; v1[0] = mv->co[0]; v1[1] = mv->co[1]; v1[2] = mv->co[2]; mv = mesh->mvert + mf->v2; v2[0] = mv->co[0]; v2[1] = mv->co[1]; v2[2] = mv->co[2]; mv = mesh->mvert + mf->v3; v3[0] = mv->co[0]; v3[1] = mv->co[1]; v3[2] = mv->co[2]; if (num_vertices == 4) { mv = mesh->mvert + mf->v4; v4[0] = mv->co[0]; v4[1] = mv->co[1]; v4[2] = mv->co[2]; } return num_vertices; } /** * Finds texture coordinates from face edge interpolation values. * @author Maarten Gribnau * @param face the face. * @param v1 vertex 1 index. * @param v2 vertex 2 index. * @param v3 vertex 3 index. * @param a interpolation value of edge v2-v1. * @param b interpolation value of edge v3-v1. * @param u (u,v) coordinate. * @param v (u,v) coordinate. */ static void face_get_uv(TFace* face, int v1, int v2, int v3, float a, float b, float* u, float* v) { float uv01[2], uv21[2]; /* Pin a,b inside [0,1] range */ #if 0 a = (float)fmod(a, 1.); b = (float)fmod(b, 1.); #else if (a < 0.f) a = 0.f; else if (a > 1.f) a = 1.f; if (b < 0.f) b = 0.f; else if (b > 1.f) b = 1.f; #endif /* Convert to texture coordinates */ uv01[0] = face->uv[v2][0] - face->uv[v1][0]; uv01[1] = face->uv[v2][1] - face->uv[v1][1]; uv21[0] = face->uv[v3][0] - face->uv[v1][0]; uv21[1] = face->uv[v3][1] - face->uv[v1][1]; uv01[0] *= a; uv01[1] *= a; uv21[0] *= b; uv21[1] *= b; *u = face->uv[v1][0] + (uv01[0] + uv21[0]); *v = face->uv[v1][1] + (uv01[1] + uv21[1]); } /** * Get the (u,v) coordinates on a face from a point in screen coordinates. * The coordinates should be given in viewport coordinates. * @author Maarten Gribnau * @param object the object with the mesh * @param mesh the mesh with the face to be picked. * @param face the face to be picked. * @param x the x-coordinate to pick at. * @param y the y-coordinate to pick at. * @param u the u-coordinate calculated. * @param v the v-coordinate calculated. * @return intersection result: * 0 == no intersection, (u,v) invalid * 1 == intersection, (u,v) valid */ static int face_pick_uv(Object* object, Mesh* mesh, TFace* face, short x, short y, float* u, float* v) { float org[3], dir[3]; float ab[2]; float v1[3], v2[3], v3[3], v4[3]; int result; int num_verts; /* Get a view ray to intersect with the face */ get_pick_ray(x, y, org, dir); /* Convert local vertex coordinates to world */ num_verts = face_get_vertex_coordinates(mesh, face, v1, v2, v3, v4); /* Convert local vertex coordinates to world */ Mat4MulVecfl(object->obmat, v1); Mat4MulVecfl(object->obmat, v2); Mat4MulVecfl(object->obmat, v3); if (num_verts > 3) { Mat4MulVecfl(object->obmat, v4); } /* Get (u,v) values (local face coordinates) of intersection point * If face is a quad, there are two triangles to check. */ result = triangle_ray_intersect(v2, v1, v3, org, dir, ab); if ( (num_verts == 3) || ((num_verts == 4) && (result > 1)) ) { /* Face is a triangle or a quad with a hit on the first triangle */ face_get_uv(face, 1, 0, 2, ab[0], ab[1], u, v); /* printf("triangle 1, texture (u,v)=(%f, %f)\n", *u, *v); */ } else { /* Face is a quad and no intersection with first triangle */ result = triangle_ray_intersect(v4, v3, v1, org, dir, ab); face_get_uv(face, 3, 2, 0, ab[0], ab[1], u, v); /* printf("triangle 2, texture (u,v)=(%f, %f)\n", *u, *v); */ } return result > 0; } /** * First attempt at drawing in the texture of a face. * @author Maarten Gribnau */ void face_draw() { Object *ob; Mesh *me; TFace *face, *face_old = 0; short xy[2], xy_old[2]; //int a, index; Image *img=NULL, *img_old = NULL; IMG_BrushPtr brush; IMG_CanvasPtr canvas = 0; unsigned int rowBytes, face_index; char *warn_packed_file = 0; float uv[2], uv_old[2]; extern VPaint Gvp; short mousebut; ob = OBACT; if (!ob) { error("No active object"); return; } if (!(ob->lay & G.vd->lay)) { error("The active object is not in this layer"); return; } me = get_mesh(ob); if (!me) { error("The active object does not have a mesh obData"); return; } brush = IMG_BrushCreate(Gvp.size, Gvp.size, &Gvp.r); if (!brush) { error("Can't create brush"); return; } if (U.flag & USER_LMOUSESELECT) mousebut = R_MOUSE; else mousebut = L_MOUSE; persp(PERSP_VIEW); getmouseco_areawin(xy_old); while (get_mbut() & mousebut) { getmouseco_areawin(xy); /* Check if cursor has moved */ if ((xy[0] != xy_old[0]) || (xy[1] != xy_old[1])) { /* Get face to draw on */ if (!face_pick(me, xy[0], xy[1], &face_index)) face = NULL; else face = (((TFace*)me->tface)+face_index); /* Check if this is another face. */ if (face != face_old) { /* The active face changed, check the texture */ if (face) { img = face->tpage; } else { img = 0; } if (img != img_old) { /* Faces have different textures. Finish drawing in the old face. */ if (face_old && canvas) { face_pick_uv(ob, me, face_old, xy[0], xy[1], &uv[0], &uv[1]); IMG_CanvasDrawLineUV(canvas, brush, uv_old[0], uv_old[1], uv[0], uv[1]); img_old->ibuf->userflags |= IB_BITMAPDIRTY; /* Delete old canvas */ IMG_CanvasDispose(canvas); canvas = 0; } /* Create new canvas and start drawing in the new face. */ if (img) { if (img->ibuf && img->packedfile == 0) { /* MAART: skipx is not set most of the times. Make a guess. */ rowBytes = img->ibuf->skipx ? img->ibuf->skipx : img->ibuf->x * 4; canvas = IMG_CanvasCreateFromPtr(img->ibuf->rect, img->ibuf->x, img->ibuf->y, rowBytes); if (canvas) { face_pick_uv(ob, me, face, xy_old[0], xy_old[1], &uv_old[0], &uv_old[1]); face_pick_uv(ob, me, face, xy[0], xy[1], &uv[0], &uv[1]); IMG_CanvasDrawLineUV(canvas, brush, uv_old[0], uv_old[1], uv[0], uv[1]); img->ibuf->userflags |= IB_BITMAPDIRTY; } } else { /* TODO: should issue warning that no texture is assigned */ if (img->packedfile) { warn_packed_file = img->id.name + 2; img = 0; } } } } else { /* Face changed and faces have the same texture. */ if (canvas) { /* Finish drawing in the old face. */ if (face_old) { face_pick_uv(ob, me, face_old, xy[0], xy[1], &uv[0], &uv[1]); IMG_CanvasDrawLineUV(canvas, brush, uv_old[0], uv_old[1], uv[0], uv[1]); img_old->ibuf->userflags |= IB_BITMAPDIRTY; } /* Start drawing in the new face. */ if (face) { face_pick_uv(ob, me, face, xy_old[0], xy_old[1], &uv_old[0], &uv_old[1]); face_pick_uv(ob, me, face, xy[0], xy[1], &uv[0], &uv[1]); IMG_CanvasDrawLineUV(canvas, brush, uv_old[0], uv_old[1], uv[0], uv[1]); img->ibuf->userflags |= IB_BITMAPDIRTY; } } } } else { /* Same face, continue drawing */ if (face && canvas) { /* Get the new (u,v) coordinates */ face_pick_uv(ob, me, face, xy[0], xy[1], &uv[0], &uv[1]); IMG_CanvasDrawLineUV(canvas, brush, uv_old[0], uv_old[1], uv[0], uv[1]); img->ibuf->userflags |= IB_BITMAPDIRTY; } } if (face && img) { /* Make OpenGL aware of a change in the texture */ free_realtime_image(img); /* Redraw the view */ scrarea_do_windraw(curarea); screen_swapbuffers(); } xy_old[0] = xy[0]; xy_old[1] = xy[1]; uv_old[0] = uv[0]; uv_old[1] = uv[1]; face_old = face; img_old = img; } } IMG_BrushDispose(brush); if (canvas) { IMG_CanvasDispose(canvas); canvas = 0; } if (warn_packed_file) { error("Painting in packed images is not supported: %s", warn_packed_file); } persp(PERSP_WIN); BIF_undo_push("UV face draw"); allqueue(REDRAWVIEW3D, 0); allqueue(REDRAWIMAGE, 0); allqueue(REDRAWHEADERS, 0); } /* Selects all faces which have the same uv-texture as the active face * @author Roel Spruit * @return Void * Errors: - Active object not in this layer * - No active face or active face has no UV-texture */ void get_same_uv(void) { Object *ob; Mesh *me; TFace *tface; short a, foundtex=0; Image *ima; char uvname[160]; ob = OBACT; if (!(ob->lay & G.vd->lay)) { error("The active object is not in this layer"); return; } me = get_mesh(ob); /* Search for the active face with a UV-Texture */ tface = me->tface; a = me->totface; while (a--) { if(tface->flag & TF_ACTIVE){ ima=tface->tpage; if(ima && ima->name){ strcpy(uvname,ima->name); a=0; foundtex=1; } } tface++; } if(!foundtex) { error("No active face, or active face has no UV texture"); return; } /* select everything with the same texture */ tface = me->tface; a = me->totface; while (a--) { ima=tface->tpage; if(ima && ima->name){ if(!strcmp(ima->name, uvname)){ tface->flag |= TF_SELECT; } else tface->flag &= ~TF_SELECT; } else tface->flag &= ~TF_SELECT; tface++; } /* image window redraw */ BIF_undo_push("Get same UV"); object_tface_flags_changed(OBACT, 0); }