diff options
Diffstat (limited to 'source/blender/editors/uvedit')
-rw-r--r-- | source/blender/editors/uvedit/Makefile | 55 | ||||
-rw-r--r-- | source/blender/editors/uvedit/SConscript | 10 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_draw.c | 856 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_intern.h | 81 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_ops.c | 3147 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_parametrizer.c | 4509 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_parametrizer.h | 97 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_unwrap_ops.c | 1347 |
8 files changed, 10102 insertions, 0 deletions
diff --git a/source/blender/editors/uvedit/Makefile b/source/blender/editors/uvedit/Makefile new file mode 100644 index 00000000000..b8a8f0bc8af --- /dev/null +++ b/source/blender/editors/uvedit/Makefile @@ -0,0 +1,55 @@ +# +# $Id: Makefile 14 2002-10-13 15:57:19Z hans $ +# +# ***** BEGIN GPL 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. +# +# 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) 2007 Blender Foundation +# All rights reserved. +# +# The Original Code is: all of this file. +# +# Contributor(s): none yet. +# +# ***** END GPL LICENSE BLOCK ***** +# +# Makes module object directory and bounces make to subdirectories. + +LIBNAME = ed_uvedit +DIR = $(OCGDIR)/blender/$(LIBNAME) + +include nan_compile.mk + +CFLAGS += $(LEVEL_1_C_WARNINGS) + +CPPFLAGS += -I$(NAN_GLEW)/include +CPPFLAGS += -I$(OPENGL_HEADERS) + +# not very neat.... +CPPFLAGS += -I../../windowmanager +CPPFLAGS += -I../../blenkernel +CPPFLAGS += -I../../blenlib +CPPFLAGS += -I../../makesdna +CPPFLAGS += -I../../makesrna +CPPFLAGS += -I../../imbuf +CPPFLAGS += -I../../gpu +CPPFLAGS += -I$(NAN_GUARDEDALLOC)/include +CPPFLAGS += -I$(NAN_OPENNL)/include + +# own include + +CPPFLAGS += -I../include + diff --git a/source/blender/editors/uvedit/SConscript b/source/blender/editors/uvedit/SConscript new file mode 100644 index 00000000000..b472b89d23d --- /dev/null +++ b/source/blender/editors/uvedit/SConscript @@ -0,0 +1,10 @@ +#!/usr/bin/python +Import ('env') + +sources = env.Glob('*.c') + +incs = '../include ../../blenlib ../../blenkernel ../../makesdna ../../imbuf' +incs += ' ../../windowmanager #/intern/guardedalloc #/extern/glew/include' +incs += ' ../../makesrna #/intern/opennl/extern ../../gpu' + +env.BlenderLib ( 'bf_editors_uvedit', sources, Split(incs), [], libtype=['core'], priority=[45] ) diff --git a/source/blender/editors/uvedit/uvedit_draw.c b/source/blender/editors/uvedit/uvedit_draw.c new file mode 100644 index 00000000000..aded5a4cff9 --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_draw.c @@ -0,0 +1,856 @@ +/** + * $Id$ + * + * ***** BEGIN GPL 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. + * + * 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. + * + * Contributor(s): Blender Foundation, 2002-2009 + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <float.h> +#include <math.h> +#include <stdlib.h> + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_customdata.h" +#include "BKE_DerivedMesh.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +#include "BKE_utildefines.h" + +#include "BLI_arithb.h" +#include "BLI_editVert.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "ED_image.h" +#include "ED_mesh.h" + +#include "UI_resources.h" + +#include "uvedit_intern.h" + +static void drawcursor_sima(SpaceImage *sima, ARegion *ar) +{ + View2D *v2d= &ar->v2d; + float zoomx, zoomy, w, h; + int width, height; + + ED_space_image_size(sima, &width, &height); + ED_space_image_zoom(sima, ar, &zoomx, &zoomy); + + w= zoomx*width/256.0f; + h= zoomy*height/256.0f; + + cpack(0xFFFFFF); + glTranslatef(v2d->cursor[0], v2d->cursor[1], 0.0f); + fdrawline(-0.05/w, 0, 0, 0.05/h); + fdrawline(0, 0.05/h, 0.05/w, 0); + fdrawline(0.05/w, 0, 0, -0.05/h); + fdrawline(0, -0.05/h, -0.05/w, 0); + + setlinestyle(4); + cpack(0xFF); + fdrawline(-0.05/w, 0, 0, 0.05/h); + fdrawline(0, 0.05/h, 0.05/w, 0); + fdrawline(0.05/w, 0, 0, -0.05/h); + fdrawline(0, -0.05/h, -0.05/w, 0); + + + setlinestyle(0); + cpack(0x0); + fdrawline(-0.020/w, 0, -0.1/w, 0); + fdrawline(0.1/w, 0, .020/w, 0); + fdrawline(0, -0.020/h, 0, -0.1/h); + fdrawline(0, 0.1/h, 0, 0.020/h); + + setlinestyle(1); + cpack(0xFFFFFF); + fdrawline(-0.020/w, 0, -0.1/w, 0); + fdrawline(0.1/w, 0, .020/w, 0); + fdrawline(0, -0.020/h, 0, -0.1/h); + fdrawline(0, 0.1/h, 0, 0.020/h); + + glTranslatef(-v2d->cursor[0], -v2d->cursor[1], 0.0f); + setlinestyle(0); +} + +static int draw_uvs_face_check(Scene *scene) +{ + /* checks if we are selecting only faces */ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + if(scene->selectmode == SCE_SELECT_FACE) + return 2; + else if(scene->selectmode & SCE_SELECT_FACE) + return 1; + else + return 0; + } + else + return (scene->toolsettings->uv_selectmode == UV_SELECT_FACE); +} + +static void draw_uvs_shadow(SpaceImage *sima, Object *obedit) +{ + EditMesh *em; + EditFace *efa; + TFace *tf; + + em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + + /* draws the grey mesh when painting */ + glColor3ub(112, 112, 112); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + glBegin(GL_LINE_LOOP); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + } + + BKE_mesh_end_editmesh(obedit->data, em); +} + +static int draw_uvs_dm_shadow(DerivedMesh *dm) +{ + /* draw shadow mesh - this is the mesh with the modifier applied */ + + if(dm && dm->drawUVEdges && CustomData_has_layer(&dm->faceData, CD_MTFACE)) { + glColor3ub(112, 112, 112); + dm->drawUVEdges(dm); + return 1; + } + + return 0; +} + +static void draw_uvs_stretch(SpaceImage *sima, Scene *scene, EditMesh *em, MTFace *activetf) +{ + EditFace *efa; + MTFace *tf; + Image *ima= sima->image; + float aspx, aspy, col[4], tf_uv[4][2]; + + ED_space_image_uv_aspect(sima, &aspx, &aspy); + + switch(sima->dt_uvstretch) { + case SI_UVDT_STRETCH_AREA: + { + float totarea=0.0f, totuvarea=0.0f, areadiff, uvarea, area; + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + uv_copy_aspect(tf->uv, tf_uv, aspx, aspy); + + totarea += EM_face_area(efa); + //totuvarea += tf_area(tf, efa->v4!=0); + totuvarea += uv_area(tf_uv, efa->v4!=0); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + efa->tmp.p = tf; + } + else { + if(tf == activetf) + activetf= NULL; + efa->tmp.p = NULL; + } + } + + if(totarea < FLT_EPSILON || totuvarea < FLT_EPSILON) { + col[0] = 1.0; + col[1] = col[2] = 0.0; + glColor3fv(col); + for(efa= em->faces.first; efa; efa= efa->next) { + if((tf=(MTFace *)efa->tmp.p)) { + glBegin(efa->v4?GL_QUADS:GL_TRIANGLES); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + } + } + } + else { + for(efa= em->faces.first; efa; efa= efa->next) { + if((tf=(MTFace *)efa->tmp.p)) { + area = EM_face_area(efa) / totarea; + uv_copy_aspect(tf->uv, tf_uv, aspx, aspy); + //uvarea = tf_area(tf, efa->v4!=0) / totuvarea; + uvarea = uv_area(tf_uv, efa->v4!=0) / totuvarea; + + if(area < FLT_EPSILON || uvarea < FLT_EPSILON) + areadiff = 1.0; + else if(area>uvarea) + areadiff = 1.0-(uvarea/area); + else + areadiff = 1.0-(area/uvarea); + + weight_to_rgb(areadiff, col, col+1, col+2); + glColor3fv(col); + + glBegin(efa->v4?GL_QUADS:GL_TRIANGLES); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + } + } + } + break; + } + case SI_UVDT_STRETCH_ANGLE: + { + float uvang1,uvang2,uvang3,uvang4; + float ang1,ang2,ang3,ang4; + float av1[3], av2[3], av3[3], av4[3]; /* use for 2d and 3d angle vectors */ + float a; + + col[3] = 0.5; /* hard coded alpha, not that nice */ + + glShadeModel(GL_SMOOTH); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + efa->tmp.p = tf; + uv_copy_aspect(tf->uv, tf_uv, aspx, aspy); + if(efa->v4) { + +#if 0 /* Simple but slow, better reuse normalized vectors */ + uvang1 = VecAngle3_2D(tf_uv[3], tf_uv[0], tf_uv[1]); + ang1 = VecAngle3(efa->v4->co, efa->v1->co, efa->v2->co); + + uvang2 = VecAngle3_2D(tf_uv[0], tf_uv[1], tf_uv[2]); + ang2 = VecAngle3(efa->v1->co, efa->v2->co, efa->v3->co); + + uvang3 = VecAngle3_2D(tf_uv[1], tf_uv[2], tf_uv[3]); + ang3 = VecAngle3(efa->v2->co, efa->v3->co, efa->v4->co); + + uvang4 = VecAngle3_2D(tf_uv[2], tf_uv[3], tf_uv[0]); + ang4 = VecAngle3(efa->v3->co, efa->v4->co, efa->v1->co); +#endif + + /* uv angles */ + VECSUB2D(av1, tf_uv[3], tf_uv[0]); Normalize2(av1); + VECSUB2D(av2, tf_uv[0], tf_uv[1]); Normalize2(av2); + VECSUB2D(av3, tf_uv[1], tf_uv[2]); Normalize2(av3); + VECSUB2D(av4, tf_uv[2], tf_uv[3]); Normalize2(av4); + + /* This is the correct angle however we are only comparing angles + * uvang1 = 90-((NormalizedVecAngle2_2D(av1, av2) * 180.0/M_PI)-90);*/ + uvang1 = NormalizedVecAngle2_2D(av1, av2)*180.0/M_PI; + uvang2 = NormalizedVecAngle2_2D(av2, av3)*180.0/M_PI; + uvang3 = NormalizedVecAngle2_2D(av3, av4)*180.0/M_PI; + uvang4 = NormalizedVecAngle2_2D(av4, av1)*180.0/M_PI; + + /* 3d angles */ + VECSUB(av1, efa->v4->co, efa->v1->co); Normalize(av1); + VECSUB(av2, efa->v1->co, efa->v2->co); Normalize(av2); + VECSUB(av3, efa->v2->co, efa->v3->co); Normalize(av3); + VECSUB(av4, efa->v3->co, efa->v4->co); Normalize(av4); + + /* This is the correct angle however we are only comparing angles + * ang1 = 90-((NormalizedVecAngle2(av1, av2) * 180.0/M_PI)-90);*/ + ang1 = NormalizedVecAngle2(av1, av2)*180.0/M_PI; + ang2 = NormalizedVecAngle2(av2, av3)*180.0/M_PI; + ang3 = NormalizedVecAngle2(av3, av4)*180.0/M_PI; + ang4 = NormalizedVecAngle2(av4, av1)*180.0/M_PI; + + glBegin(GL_QUADS); + + /* This simple makes the angles display worse then they really are ;) + * 1.0-pow((1.0-a), 2) */ + + a = fabs(uvang1-ang1)/180.0; + weight_to_rgb(1.0-pow((1.0-a), 2), col, col+1, col+2); + glColor3fv(col); + glVertex2fv(tf->uv[0]); + a = fabs(uvang2-ang2)/180.0; + weight_to_rgb(1.0-pow((1.0-a), 2), col, col+1, col+2); + glColor3fv(col); + glVertex2fv(tf->uv[1]); + a = fabs(uvang3-ang3)/180.0; + weight_to_rgb(1.0-pow((1.0-a), 2), col, col+1, col+2); + glColor3fv(col); + glVertex2fv(tf->uv[2]); + a = fabs(uvang4-ang4)/180.0; + weight_to_rgb(1.0-pow((1.0-a), 2), col, col+1, col+2); + glColor3fv(col); + glVertex2fv(tf->uv[3]); + + } + else { +#if 0 /* Simple but slow, better reuse normalized vectors */ + uvang1 = VecAngle3_2D(tf_uv[2], tf_uv[0], tf_uv[1]); + ang1 = VecAngle3(efa->v3->co, efa->v1->co, efa->v2->co); + + uvang2 = VecAngle3_2D(tf_uv[0], tf_uv[1], tf_uv[2]); + ang2 = VecAngle3(efa->v1->co, efa->v2->co, efa->v3->co); + + uvang3 = 180-(uvang1+uvang2); + ang3 = 180-(ang1+ang2); +#endif + + /* uv angles */ + VECSUB2D(av1, tf_uv[2], tf_uv[0]); Normalize2(av1); + VECSUB2D(av2, tf_uv[0], tf_uv[1]); Normalize2(av2); + VECSUB2D(av3, tf_uv[1], tf_uv[2]); Normalize2(av3); + + /* This is the correct angle however we are only comparing angles + * uvang1 = 90-((NormalizedVecAngle2_2D(av1, av2) * 180.0/M_PI)-90); */ + uvang1 = NormalizedVecAngle2_2D(av1, av2)*180.0/M_PI; + uvang2 = NormalizedVecAngle2_2D(av2, av3)*180.0/M_PI; + uvang3 = NormalizedVecAngle2_2D(av3, av1)*180.0/M_PI; + + /* 3d angles */ + VECSUB(av1, efa->v3->co, efa->v1->co); Normalize(av1); + VECSUB(av2, efa->v1->co, efa->v2->co); Normalize(av2); + VECSUB(av3, efa->v2->co, efa->v3->co); Normalize(av3); + /* This is the correct angle however we are only comparing angles + * ang1 = 90-((NormalizedVecAngle2(av1, av2) * 180.0/M_PI)-90); */ + ang1 = NormalizedVecAngle2(av1, av2)*180.0/M_PI; + ang2 = NormalizedVecAngle2(av2, av3)*180.0/M_PI; + ang3 = NormalizedVecAngle2(av3, av1)*180.0/M_PI; + + /* This simple makes the angles display worse then they really are ;) + * 1.0-pow((1.0-a), 2) */ + + glBegin(GL_TRIANGLES); + a = fabs(uvang1-ang1)/180.0; + weight_to_rgb(1.0-pow((1.0-a), 2), col, col+1, col+2); + glColor3fv(col); + glVertex2fv(tf->uv[0]); + a = fabs(uvang2-ang2)/180.0; + weight_to_rgb(1.0-pow((1.0-a), 2), col, col+1, col+2); + glColor3fv(col); + glVertex2fv(tf->uv[1]); + a = fabs(uvang3-ang3)/180.0; + weight_to_rgb(1.0-pow((1.0-a), 2), col, col+1, col+2); + glColor3fv(col); + glVertex2fv(tf->uv[2]); + } + glEnd(); + } + else { + if(tf == activetf) + activetf= NULL; + efa->tmp.p = NULL; + } + } + + glShadeModel(GL_FLAT); + break; + } + } +} + +static void draw_uvs_other(SpaceImage *sima, Scene *scene, Object *obedit, MTFace *activetf) +{ + Base *base; + Image *curimage; + + curimage= (activetf)? activetf->tpage: NULL; + + glColor3ub(96, 96, 96); + + for(base=scene->base.first; base; base=base->next) { + Object *ob= base->object; + + if(!(base->flag & SELECT)) continue; + if(!(base->lay & scene->lay)) continue; + if(ob->restrictflag & OB_RESTRICT_VIEW) continue; + + if((ob->type==OB_MESH) && (ob!=obedit)) { + Mesh *me= ob->data; + + if(me->mtface) { + MFace *mface= me->mface; + MTFace *tface= me->mtface; + int a; + + for(a=me->totface; a>0; a--, tface++, mface++) { + if(tface->tpage == curimage) { + glBegin(GL_LINE_LOOP); + glVertex2fv(tface->uv[0]); + glVertex2fv(tface->uv[1]); + glVertex2fv(tface->uv[2]); + if(mface->v4) glVertex2fv(tface->uv[3]); + glEnd(); + } + } + } + } + } +} + +/* draws uv's in the image space */ +static void draw_uvs(SpaceImage *sima, Scene *scene, Object *obedit) +{ + ToolSettings *settings; + Mesh *me= obedit->data; + EditMesh *em; + EditFace *efa, *efa_act; + MTFace *tf, *activetf = NULL; + DerivedMesh *finaldm, *cagedm; + char col1[4], col2[4]; + float pointsize; + int drawfaces, interpedges, lastsel, sel; + Image *ima= sima->image; + + em= BKE_mesh_get_editmesh(me); + activetf= EM_get_active_mtface(em, &efa_act, NULL, 0); /* will be set to NULL if hidden */ + + settings= scene->toolsettings; + + drawfaces= draw_uvs_face_check(scene); + if(settings->uv_flag & UV_SYNC_SELECTION) + interpedges= (scene->selectmode & SCE_SELECT_VERTEX); + else + interpedges= (settings->uv_selectmode == UV_SELECT_VERTEX); + + /* draw other uvs */ + if(sima->flag & SI_DRAW_OTHER) + draw_uvs_other(sima, scene, obedit, activetf); + + /* 1. draw shadow mesh */ + + if(sima->flag & SI_DRAWSHADOW) { + /* first try existing derivedmesh */ + if(!draw_uvs_dm_shadow(em->derivedFinal)) { + /* create one if it does not exist */ + cagedm = editmesh_get_derived_cage_and_final(scene, obedit, em, &finaldm, CD_MASK_BAREMESH|CD_MASK_MTFACE); + + /* when sync selection is enabled, all faces are drawn (except for hidden) + * so if cage is the same as the final, theres no point in drawing this */ + if(!((settings->uv_flag & UV_SYNC_SELECTION) && (cagedm == finaldm))) + draw_uvs_dm_shadow(finaldm); + + /* release derivedmesh again */ + if(cagedm != finaldm) cagedm->release(cagedm); + finaldm->release(finaldm); + } + } + + /* 2. draw colored faces */ + + if(sima->flag & SI_DRAW_STRETCH) { + draw_uvs_stretch(sima, scene, em, activetf); + } + else if(me->drawflag & ME_DRAWFACES) { + /* draw transparent faces */ + UI_GetThemeColor4ubv(TH_FACE, col1); + UI_GetThemeColor4ubv(TH_FACE_SELECT, col2); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + efa->tmp.p = tf; + if(tf==activetf) continue; /* important the temp pointer is set above */ + + if(uvedit_face_selected(scene, efa, tf)) + glColor4ubv((GLubyte *)col2); + else + glColor4ubv((GLubyte *)col1); + + glBegin(efa->v4?GL_QUADS:GL_TRIANGLES); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + } + else { + if(tf == activetf) + activetf= NULL; + efa->tmp.p = NULL; + } + } + glDisable(GL_BLEND); + } + else { + /* would be nice to do this within a draw loop but most below are optional, so it would involve too many checks */ + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + efa->tmp.p = tf; + } + else { + if(tf == activetf) + activetf= NULL; + efa->tmp.p = NULL; + } + } + + } + + /* 3. draw active face stippled */ + + if(activetf) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + UI_ThemeColor4(TH_EDITMESH_ACTIVE); + + glEnable(GL_POLYGON_STIPPLE); + glPolygonStipple(stipple_quarttone); + + glBegin(efa_act->v4? GL_QUADS: GL_TRIANGLES); + glVertex2fv(activetf->uv[0]); + glVertex2fv(activetf->uv[1]); + glVertex2fv(activetf->uv[2]); + if(efa_act->v4) glVertex2fv(activetf->uv[3]); + glEnd(); + + glDisable(GL_POLYGON_STIPPLE); + glDisable(GL_BLEND); + } + + /* 4. draw edges */ + + if(sima->flag & SI_SMOOTH_UV) { + glEnable(GL_LINE_SMOOTH); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + switch(sima->dt_uv) { + case SI_UVDT_DASH: + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + cpack(0x111111); + + glBegin(GL_LINE_LOOP); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + + setlinestyle(2); + cpack(0x909090); + + glBegin(GL_LINE_STRIP); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glEnd(); + + glBegin(GL_LINE_STRIP); + glVertex2fv(tf->uv[0]); + if(efa->v4) glVertex2fv(tf->uv[3]); + else glVertex2fv(tf->uv[2]); + glEnd(); + + glBegin(GL_LINE_STRIP); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + + setlinestyle(0); + } + } + break; + case SI_UVDT_BLACK: /* black/white */ + case SI_UVDT_WHITE: + cpack((sima->dt_uv==SI_UVDT_WHITE) ? 0xFFFFFF : 0x0); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + glBegin(GL_LINE_LOOP); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + } + } + break; + case SI_UVDT_OUTLINE: + glLineWidth(3); + cpack(0x0); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + glBegin(GL_LINE_LOOP); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + } + } + + glLineWidth(1); + col2[0] = col2[1] = col2[2] = 192; col2[3] = 255; + glColor4ubv((unsigned char *)col2); + + if(me->drawflag & ME_DRAWEDGES) { + UI_GetThemeColor4ubv(TH_VERTEX_SELECT, col1); + lastsel = sel = 0; + + if(interpedges) { + glShadeModel(GL_SMOOTH); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + glBegin(GL_LINE_LOOP); + sel = (uvedit_uv_selected(scene, efa, tf, 0)? 1 : 0); + if(sel != lastsel) { glColor4ubv(sel ? (GLubyte *)col1 : (GLubyte *)col2); lastsel = sel; } + glVertex2fv(tf->uv[0]); + + sel = uvedit_uv_selected(scene, efa, tf, 1)? 1 : 0; + if(sel != lastsel) { glColor4ubv(sel ? (GLubyte *)col1 : (GLubyte *)col2); lastsel = sel; } + glVertex2fv(tf->uv[1]); + + sel = uvedit_uv_selected(scene, efa, tf, 2)? 1 : 0; + if(sel != lastsel) { glColor4ubv(sel ? (GLubyte *)col1 : (GLubyte *)col2); lastsel = sel; } + glVertex2fv(tf->uv[2]); + + if(efa->v4) { + sel = uvedit_uv_selected(scene, efa, tf, 3)? 1 : 0; + if(sel != lastsel) { glColor4ubv(sel ? (GLubyte *)col1 : (GLubyte *)col2); lastsel = sel; } + glVertex2fv(tf->uv[3]); + } + + glEnd(); + } + } + + glShadeModel(GL_FLAT); + } + else { + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + glBegin(GL_LINES); + sel = (uvedit_edge_selected(scene, efa, tf, 0)? 1 : 0); + if(sel != lastsel) { glColor4ubv(sel ? (GLubyte *)col1 : (GLubyte *)col2); lastsel = sel; } + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + + sel = uvedit_edge_selected(scene, efa, tf, 1)? 1 : 0; + if(sel != lastsel) { glColor4ubv(sel ? (GLubyte *)col1 : (GLubyte *)col2); lastsel = sel; } + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + + sel = uvedit_edge_selected(scene, efa, tf, 2)? 1 : 0; + if(sel != lastsel) { glColor4ubv(sel ? (GLubyte *)col1 : (GLubyte *)col2); lastsel = sel; } + glVertex2fv(tf->uv[2]); + + if(efa->v4) { + glVertex2fv(tf->uv[3]); + + sel = uvedit_edge_selected(scene, efa, tf, 3)? 1 : 0; + if(sel != lastsel) { glColor4ubv(sel ? (GLubyte *)col1 : (GLubyte *)col2); lastsel = sel; } + glVertex2fv(tf->uv[3]); + } + + glVertex2fv(tf->uv[0]); + + glEnd(); + } + } + } + } + else { + /* no nice edges */ + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + glBegin(GL_LINE_LOOP); + glVertex2fv(tf->uv[0]); + glVertex2fv(tf->uv[1]); + glVertex2fv(tf->uv[2]); + if(efa->v4) glVertex2fv(tf->uv[3]); + glEnd(); + } + } + } + + break; + } + + if(sima->flag & SI_SMOOTH_UV) { + glDisable(GL_LINE_SMOOTH); + glDisable(GL_BLEND); + } + + /* 5. draw face centers */ + + if(drawfaces) { + float cent[2]; + + pointsize = UI_GetThemeValuef(TH_FACEDOT_SIZE); + glPointSize(pointsize); // TODO - drawobject.c changes this value after - Investigate! + + /* unselected faces */ + UI_ThemeColor(TH_WIRE); + + bglBegin(GL_POINTS); + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf && !uvedit_face_selected(scene, efa, tf)) { + uv_center(tf->uv, cent, efa->v4 != NULL); + bglVertex2fv(cent); + } + } + bglEnd(); + + /* selected faces */ + UI_ThemeColor(TH_FACE_DOT); + + bglBegin(GL_POINTS); + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf && uvedit_face_selected(scene, efa, tf)) { + uv_center(tf->uv, cent, efa->v4 != NULL); + bglVertex2fv(cent); + } + } + bglEnd(); + } + + /* 6. draw uv vertices */ + + if(drawfaces != 2) { /* 2 means Mesh Face Mode */ + /* unselected uvs */ + UI_ThemeColor(TH_VERTEX); + pointsize = UI_GetThemeValuef(TH_VERTEX_SIZE); + glPointSize(pointsize); + + bglBegin(GL_POINTS); + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + if(!uvedit_uv_selected(scene, efa, tf, 0)) + bglVertex2fv(tf->uv[0]); + if(!uvedit_uv_selected(scene, efa, tf, 1)) + bglVertex2fv(tf->uv[1]); + if(!uvedit_uv_selected(scene, efa, tf, 2)) + bglVertex2fv(tf->uv[2]); + if(efa->v4 && !uvedit_uv_selected(scene, efa, tf, 3)) + bglVertex2fv(tf->uv[3]); + } + } + bglEnd(); + + /* pinned uvs */ + /* give odd pointsizes odd pin pointsizes */ + glPointSize(pointsize*2 + (((int)pointsize % 2)? (-1): 0)); + cpack(0xFF); + + bglBegin(GL_POINTS); + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + if(tf->unwrap & TF_PIN1) + bglVertex2fv(tf->uv[0]); + if(tf->unwrap & TF_PIN2) + bglVertex2fv(tf->uv[1]); + if(tf->unwrap & TF_PIN3) + bglVertex2fv(tf->uv[2]); + if(efa->v4 && (tf->unwrap & TF_PIN4)) + bglVertex2fv(tf->uv[3]); + } + } + bglEnd(); + + /* selected uvs */ + UI_ThemeColor(TH_VERTEX_SELECT); + glPointSize(pointsize); + + bglBegin(GL_POINTS); + for(efa= em->faces.first; efa; efa= efa->next) { + tf= (MTFace *)efa->tmp.p; /* visible faces cached */ + + if(tf) { + if(uvedit_uv_selected(scene, efa, tf, 0)) + bglVertex2fv(tf->uv[0]); + if(uvedit_uv_selected(scene, efa, tf, 1)) + bglVertex2fv(tf->uv[1]); + if(uvedit_uv_selected(scene, efa, tf, 2)) + bglVertex2fv(tf->uv[2]); + if(efa->v4 && uvedit_uv_selected(scene, efa, tf, 3)) + bglVertex2fv(tf->uv[3]); + } + } + bglEnd(); + } + + glPointSize(1.0); + BKE_mesh_end_editmesh(obedit->data, em); +} + +void draw_uvedit_main(SpaceImage *sima, ARegion *ar, Scene *scene, Object *obedit) +{ + int show_uvedit, show_uvshadow; + + show_uvedit= ED_space_image_show_uvedit(sima, obedit); + show_uvshadow= ED_space_image_show_uvshadow(sima, obedit); + + if(show_uvedit || show_uvshadow) { + /* this is basically the same object_handle_update as in the 3d view, + * here we have to do it as well for the object we are editing if we + * are displaying the final result */ + if(obedit && (sima->flag & SI_DRAWSHADOW)) + object_handle_update(scene, obedit); + + if(show_uvshadow) + draw_uvs_shadow(sima, obedit); + else + draw_uvs(sima, scene, obedit); + + if(show_uvedit) + drawcursor_sima(sima, ar); + } +} + diff --git a/source/blender/editors/uvedit/uvedit_intern.h b/source/blender/editors/uvedit/uvedit_intern.h new file mode 100644 index 00000000000..cca357c8685 --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_intern.h @@ -0,0 +1,81 @@ +/** + * $Id: + * + * ***** BEGIN GPL 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. + * + * 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) 2008 Blender Foundation. + * All rights reserved. + * + * + * Contributor(s): Blender Foundation + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#ifndef ED_UVEDIT_INTERN_H +#define ED_UVEDIT_INTERN_H + +struct SpaceImage; +struct EditFace; +struct MTFace; +struct Scene; +struct Image; +struct Object; +struct wmOperatorType; + +#define UV_SELECT_ALL 1 +#define UV_SELECT_PINNED 2 + +/* id can be from 0 to 3 */ +#define TF_PIN_MASK(id) (TF_PIN1 << id) +#define TF_SEL_MASK(id) (TF_SEL1 << id) + +/* visibility and selection */ +int uvedit_face_visible_nolocal(struct Scene *scene, struct EditFace *efa); +int uvedit_face_visible(struct Scene *scene, struct Image *ima, struct EditFace *efa, struct MTFace *tf); + +int uvedit_face_selected(struct Scene *scene, struct EditFace *efa, struct MTFace *tf); +void uvedit_face_select(struct Scene *scene, struct EditFace *efa, struct MTFace *tf); +void uvedit_face_deselect(struct Scene *scene, struct EditFace *efa, struct MTFace *tf); + +int uvedit_edge_selected(struct Scene *scene, struct EditFace *efa, struct MTFace *tf, int i); +void uvedit_edge_select(struct Scene *scene, struct EditFace *efa, struct MTFace *tf, int i); +void uvedit_edge_deselect(struct Scene *scene, struct EditFace *efa, struct MTFace *tf, int i); + +int uvedit_uv_selected(struct Scene *scene, struct EditFace *efa, struct MTFace *tf, int i); +void uvedit_uv_select(struct Scene *scene, struct EditFace *efa, struct MTFace *tf, int i); +void uvedit_uv_deselect(struct Scene *scene, struct EditFace *efa, struct MTFace *tf, int i); + +/* geometric utilities */ +void uv_center(float uv[][2], float cent[2], int quad); +float uv_area(float uv[][2], int quad); +void uv_copy_aspect(float uv_orig[][2], float uv[][2], float aspx, float aspy); + +/* operators */ +void UV_OT_average_islands_scale(struct wmOperatorType *ot); +void UV_OT_cube_project(struct wmOperatorType *ot); +void UV_OT_cylinder_project(struct wmOperatorType *ot); +void UV_OT_from_view(struct wmOperatorType *ot); +void UV_OT_mapping_menu(struct wmOperatorType *ot); +void UV_OT_minimize_stretch(struct wmOperatorType *ot); +void UV_OT_pack_islands(struct wmOperatorType *ot); +void UV_OT_reset(struct wmOperatorType *ot); +void UV_OT_sphere_project(struct wmOperatorType *ot); +void UV_OT_unwrap(struct wmOperatorType *ot); + +#endif /* ED_UVEDIT_INTERN_H */ + diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c new file mode 100644 index 00000000000..0b82efda7ab --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_ops.c @@ -0,0 +1,3147 @@ +/** + * $Id$ + * + * ***** BEGIN GPL 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. + * + * 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 LICENSE BLOCK ***** + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_object_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_space_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_windowmanager_types.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" +#include "BLI_editVert.h" + +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_depsgraph.h" +#include "BKE_image.h" +#include "BKE_library.h" +#include "BKE_mesh.h" +#include "BKE_report.h" +#include "BKE_utildefines.h" + +#include "BIF_transform.h" + +#include "ED_image.h" +#include "ED_mesh.h" +#include "ED_screen.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_types.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_view2d.h" + +#include "uvedit_intern.h" + +/************************* state testing ************************/ + +int ED_uvedit_test(Object *obedit) +{ + EditMesh *em; + int ret; + + if(obedit->type != OB_MESH) + return 0; + + em = BKE_mesh_get_editmesh(obedit->data); + ret = EM_texFaceCheck(em); + BKE_mesh_end_editmesh(obedit->data, em); + + return ret; +} + +/************************* assign image ************************/ + +void ED_uvedit_assign_image(Scene *scene, Object *obedit, Image *ima, Image *previma) +{ + EditMesh *em; + EditFace *efa; + MTFace *tf; + int update= 0; + + /* skip assigning these procedural images... */ + if(ima && (ima->type==IMA_TYPE_R_RESULT || ima->type==IMA_TYPE_COMPOSITE)) + return; + + /* verify we have a mesh we can work with */ + if(!obedit || (obedit->type != OB_MESH)) + return; + + em= BKE_mesh_get_editmesh(((Mesh*)obedit->data)); + if(!em || !em->faces.first) { + BKE_mesh_end_editmesh(obedit->data, em); + return; + } + + /* ensure we have a uv layer */ + if(!CustomData_has_layer(&em->fdata, CD_MTFACE)) { + EM_add_data_layer(em, &em->fdata, CD_MTFACE); + update= 1; + } + + /* now assign to all visible faces */ + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, previma, efa, tf)) { + if(ima) { + tf->tpage= ima; + tf->mode |= TF_TEX; + + if(ima->tpageflag & IMA_TILES) tf->mode |= TF_TILES; + else tf->mode &= ~TF_TILES; + + if(ima->id.us==0) id_us_plus(&ima->id); + else id_lib_extern(&ima->id); + } + else { + tf->tpage= NULL; + tf->mode &= ~TF_TEX; + } + + update = 1; + } + } + + /* and update depdency graph */ + if(update) + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + + BKE_mesh_end_editmesh(obedit->data, em); +} + +/* dotile - 1, set the tile flag (from the space image) + * 2, set the tile index for the faces. */ +void ED_uvedit_set_tile(bContext *C, Scene *scene, Object *obedit, Image *ima, int curtile, int dotile) +{ + EditMesh *em; + EditFace *efa; + MTFace *tf; + + /* verify if we have something to do */ + if(!ima || !ED_uvedit_test(obedit)) + return; + + /* skip assigning these procedural images... */ + if(ima->type==IMA_TYPE_R_RESULT || ima->type==IMA_TYPE_COMPOSITE) + return; + + em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(efa->h==0 && efa->f & SELECT) { + if(dotile==1) { + /* set tile flag */ + if(ima->tpageflag & IMA_TILES) + tf->mode |= TF_TILES; + else + tf->mode &= ~TF_TILES; + } + else if(dotile==2) + tf->tile= curtile; /* set tile index */ + } + } + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + BKE_mesh_end_editmesh(obedit->data, em); +} + +/*********************** space conversion *********************/ + +static void uvedit_pixel_to_float(SpaceImage *sima, float *dist, float pixeldist) +{ + int width, height; + + ED_space_image_size(sima, &width, &height); + + dist[0]= pixeldist/width; + dist[1]= pixeldist/height; +} + +/*************** visibility and selection utilities **************/ + +int uvedit_face_visible_nolocal(Scene *scene, EditFace *efa) +{ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) + return (efa->h==0); + else + return (efa->h==0 && (efa->f & SELECT)); +} + +int uvedit_face_visible(Scene *scene, Image *ima, EditFace *efa, MTFace *tf) +{ + if(scene->toolsettings->uv_flag & UV_SHOW_SAME_IMAGE) + return (tf->tpage==ima)? uvedit_face_visible_nolocal(scene, efa): 0; + else + return uvedit_face_visible_nolocal(scene, efa); +} + +int uvedit_face_selected(Scene *scene, EditFace *efa, MTFace *tf) +{ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) + return (efa->f & SELECT); + else + return (!(~tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3)) &&(!efa->v4 || tf->flag & TF_SEL4)); +} + +void uvedit_face_select(Scene *scene, EditFace *efa, MTFace *tf) +{ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) + EM_select_face(efa, 1); + else + tf->flag |= (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); +} + +void uvedit_face_deselect(Scene *scene, EditFace *efa, MTFace *tf) +{ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) + EM_select_face(efa, 0); + else + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); +} + +int uvedit_edge_selected(Scene *scene, EditFace *efa, MTFace *tf, int i) +{ + int nvert= (efa->v4)? 4: 3; + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + if(scene->selectmode == SCE_SELECT_FACE) + return (efa->f & SELECT); + else if(scene->selectmode == SCE_SELECT_EDGE) + return (*(&efa->e1 + i))->f & SELECT; + else + return (((efa->v1 + i)->f & SELECT) && ((efa->v1 + (i+1)%nvert)->f & SELECT)); + } + else + return (tf->flag & TF_SEL_MASK(i)) && (tf->flag & TF_SEL_MASK((i+1)%nvert)); +} + +void uvedit_edge_select(Scene *scene, EditFace *efa, MTFace *tf, int i) +{ + int nvert= (efa->v4)? 4: 3; + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + if(scene->selectmode == SCE_SELECT_FACE) + EM_select_face(efa, 1); + else if(scene->selectmode == SCE_SELECT_EDGE) + EM_select_edge((*(&efa->e1 + i)), 1); + else { + (efa->v1 + i)->f |= SELECT; + (efa->v1 + (i+1)%nvert)->f |= SELECT; + } + } + else + tf->flag |= TF_SEL_MASK(i)|TF_SEL_MASK((i+1)%nvert); +} + +void uvedit_edge_deselect(Scene *scene, EditFace *efa, MTFace *tf, int i) +{ + int nvert= (efa->v4)? 4: 3; + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + if(scene->selectmode == SCE_SELECT_FACE) + EM_select_face(efa, 0); + else if(scene->selectmode == SCE_SELECT_EDGE) + EM_select_edge((*(&efa->e1 + i)), 0); + else { + (efa->v1 + i)->f &= ~SELECT; + (efa->v1 + (i+1)%nvert)->f &= ~SELECT; + } + } + else + tf->flag &= ~(TF_SEL_MASK(i)|TF_SEL_MASK((i+1)%nvert)); +} + +int uvedit_uv_selected(Scene *scene, EditFace *efa, MTFace *tf, int i) +{ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + if(scene->selectmode == SCE_SELECT_FACE) + return (efa->f & SELECT); + else + return (*(&efa->v1 + i))->f & SELECT; + } + else + return tf->flag & TF_SEL_MASK(i); +} + +void uvedit_uv_select(Scene *scene, EditFace *efa, MTFace *tf, int i) +{ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + if(scene->selectmode == SCE_SELECT_FACE) + EM_select_face(efa, 1); + else + (*(&efa->v1 + i))->f |= SELECT; + } + else + tf->flag |= TF_SEL_MASK(i); +} + +void uvedit_uv_deselect(Scene *scene, EditFace *efa, MTFace *tf, int i) +{ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + if(scene->selectmode == SCE_SELECT_FACE) + EM_select_face(efa, 0); + else + (*(&efa->v1 + i))->f &= ~SELECT; + } + else + tf->flag &= ~TF_SEL_MASK(i); +} + +/*********************** geometric utilities ***********************/ + +void uv_center(float uv[][2], float cent[2], int quad) +{ + if(quad) { + cent[0] = (uv[0][0] + uv[1][0] + uv[2][0] + uv[3][0]) / 4.0; + cent[1] = (uv[0][1] + uv[1][1] + uv[2][1] + uv[3][1]) / 4.0; + } + else { + cent[0] = (uv[0][0] + uv[1][0] + uv[2][0]) / 3.0; + cent[1] = (uv[0][1] + uv[1][1] + uv[2][1]) / 3.0; + } +} + +float uv_area(float uv[][2], int quad) +{ + if(quad) + return AreaF2Dfl(uv[0], uv[1], uv[2]) + AreaF2Dfl(uv[0], uv[2], uv[3]); + else + return AreaF2Dfl(uv[0], uv[1], uv[2]); +} + +void uv_copy_aspect(float uv_orig[][2], float uv[][2], float aspx, float aspy) +{ + uv[0][0] = uv_orig[0][0]*aspx; + uv[0][1] = uv_orig[0][1]*aspy; + + uv[1][0] = uv_orig[1][0]*aspx; + uv[1][1] = uv_orig[1][1]*aspy; + + uv[2][0] = uv_orig[2][0]*aspx; + uv[2][1] = uv_orig[2][1]*aspy; + + uv[3][0] = uv_orig[3][0]*aspx; + uv[3][1] = uv_orig[3][1]*aspy; +} + +int ED_uvedit_minmax(Scene *scene, Image *ima, Object *obedit, float *min, float *max) +{ + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + int sel; + + INIT_MINMAX2(min, max); + + sel= 0; + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(uvedit_uv_selected(scene, efa, tf, 0)) { DO_MINMAX2(tf->uv[0], min, max); sel = 1; } + if(uvedit_uv_selected(scene, efa, tf, 1)) { DO_MINMAX2(tf->uv[1], min, max); sel = 1; } + if(uvedit_uv_selected(scene, efa, tf, 2)) { DO_MINMAX2(tf->uv[2], min, max); sel = 1; } + if(efa->v4 && (uvedit_uv_selected(scene, efa, tf, 3))) { DO_MINMAX2(tf->uv[3], min, max); sel = 1; } + } + } + + BKE_mesh_end_editmesh(obedit->data, em); + return sel; +} + +int uvedit_center(Scene *scene, Image *ima, Object *obedit, float *cent, int mode) +{ + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + float min[2], max[2]; + int change= 0; + + if(mode==0) { + if(ED_uvedit_minmax(scene, ima, obedit, min, max)) + change = 1; + } + else if(mode==1) { + INIT_MINMAX2(min, max); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(uvedit_uv_selected(scene, efa, tf, 0)) { DO_MINMAX2(tf->uv[0], min, max); change= 1;} + if(uvedit_uv_selected(scene, efa, tf, 1)) { DO_MINMAX2(tf->uv[1], min, max); change= 1;} + if(uvedit_uv_selected(scene, efa, tf, 2)) { DO_MINMAX2(tf->uv[2], min, max); change= 1;} + if(efa->v4 && (uvedit_uv_selected(scene, efa, tf, 3))) { DO_MINMAX2(tf->uv[3], min, max); change= 1;} + } + } + } + + if(change) { + cent[0]= (min[0]+max[0])/2.0; + cent[1]= (min[1]+max[1])/2.0; + + BKE_mesh_end_editmesh(obedit->data, em); + return 1; + } + + BKE_mesh_end_editmesh(obedit->data, em); + return 0; +} + +/************************** find nearest ****************************/ + +typedef struct NearestHit { + EditFace *efa; + MTFace *tf; + + int vert, uv; + int edge, vert2; +} NearestHit; + +static void find_nearest_uv_edge(Scene *scene, Image *ima, EditMesh *em, float co[2], NearestHit *hit) +{ + MTFace *tf; + EditFace *efa; + EditVert *eve; + float mindist, dist; + int i, nverts; + + mindist= 1e10f; + memset(hit, 0, sizeof(*hit)); + + for(i=0, eve=em->verts.first; eve; eve=eve->next, i++) + eve->tmp.l = i; + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + nverts= efa->v4? 4: 3; + + for(i=0; i<nverts; i++) { + dist= PdistVL2Dfl(co, tf->uv[i], tf->uv[(i+1)%nverts]); + + if(dist < mindist) { + hit->tf= tf; + hit->efa= efa; + hit->edge= i; + mindist= dist; + + hit->vert= (*(&efa->v1 + i))->tmp.l; + hit->vert2= (*(&efa->v1 + ((i+1)%nverts)))->tmp.l; + } + } + } + } +} + +static void find_nearest_uv_face(Scene *scene, Image *ima, EditMesh *em, float co[2], NearestHit *hit) +{ + MTFace *tf; + EditFace *efa; + float mindist, dist, cent[2]; + int i, nverts; + + mindist= 1e10f; + memset(hit, 0, sizeof(*hit)); + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + nverts= efa->v4? 4: 3; + cent[0]= cent[1]= 0.0f; + + for(i=0; i<nverts; i++) { + cent[0] += tf->uv[i][0]; + cent[1] += tf->uv[i][1]; + } + + cent[0] /= nverts; + cent[1] /= nverts; + dist= fabs(co[0]- cent[0]) + fabs(co[1]- cent[1]); + + if(dist < mindist) { + hit->tf= tf; + hit->efa= efa; + mindist= dist; + } + } + } +} + +static int nearest_uv_between(MTFace *tf, int nverts, int id, float co[2], float uv[2]) +{ + float m[3], v1[3], v2[3], c1, c2; + int id1, id2; + + id1= (id+nverts-1)%nverts; + id2= (id+nverts+1)%nverts; + + m[0]= co[0]-uv[0]; + m[1]= co[1]-uv[1]; + Vec2Subf(v1, tf->uv[id1], tf->uv[id]); + Vec2Subf(v2, tf->uv[id2], tf->uv[id]); + + /* m and v2 on same side of v-v1? */ + c1= v1[0]*m[1] - v1[1]*m[0]; + c2= v1[0]*v2[1] - v1[1]*v2[0]; + + if(c1*c2 < 0.0f) + return 0; + + /* m and v1 on same side of v-v2? */ + c1= v2[0]*m[1] - v2[1]*m[0]; + c2= v2[0]*v1[1] - v2[1]*v1[0]; + + return (c1*c2 >= 0.0f); +} + +static void find_nearest_uv_vert(Scene *scene, Image *ima, EditMesh *em, float co[2], float penalty[2], NearestHit *hit) +{ + EditFace *efa; + EditVert *eve; + MTFace *tf; + float mindist, dist; + int i, nverts; + + mindist= 1e10f; + memset(hit, 0, sizeof(*hit)); + + for(i=0, eve=em->verts.first; eve; eve=eve->next, i++) + eve->tmp.l = i; + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + nverts= efa->v4? 4: 3; + + for(i=0; i<nverts; i++) { + if(penalty && uvedit_uv_selected(scene, efa, tf, i)) + dist= fabs(co[0]-tf->uv[i][0])+penalty[0] + fabs(co[1]-tf->uv[i][1])+penalty[1]; + else + dist= fabs(co[0]-tf->uv[i][0]) + fabs(co[1]-tf->uv[i][1]); + + if(dist<=mindist) { + if(dist==mindist) + if(!nearest_uv_between(tf, nverts, i, co, tf->uv[i])) + continue; + + mindist= dist; + + hit->uv= i; + hit->tf= tf; + hit->efa= efa; + + hit->vert= (*(&efa->v1 + i))->tmp.l; + } + } + } + } +} + +int ED_uvedit_nearest_uv(Scene *scene, Object *obedit, Image *ima, float co[2], float uv[2]) +{ + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + float mindist, dist; + int i, nverts, found= 0; + + mindist= 1e10f; + uv[0]= co[0]; + uv[1]= co[1]; + + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + nverts= efa->v4? 4: 3; + + for(i=0; i<nverts; i++) { + dist= fabs(co[0]-tf->uv[i][0]) + fabs(co[1]-tf->uv[i][1]); + + if(dist<=mindist) { + mindist= dist; + + uv[0]= tf->uv[i][0]; + uv[1]= tf->uv[i][1]; + found= 1; + } + } + } + } + + BKE_mesh_end_editmesh(obedit->data, em); + return found; +} + +/*********************** loop select ***********************/ + +static void uv_vertex_loop_flag(UvMapVert *first) +{ + UvMapVert *iterv; + int count= 0; + + for(iterv=first; iterv; iterv=iterv->next) { + if(iterv->separate && iterv!=first) + break; + + count++; + } + + if(count < 5) + first->flag= 1; +} + +static UvMapVert *uv_vertex_map_get(UvVertMap *vmap, EditFace *efa, int a) +{ + UvMapVert *iterv, *first; + + first= EM_get_uv_map_vert(vmap, (*(&efa->v1 + a))->tmp.l); + + for(iterv=first; iterv; iterv=iterv->next) { + if(iterv->separate) + first= iterv; + if(iterv->f == efa->tmp.l) + return first; + } + + return NULL; +} + +static int uv_edge_tag_faces(UvMapVert *first1, UvMapVert *first2, int *totface) +{ + UvMapVert *iterv1, *iterv2; + EditFace *efa; + int tot = 0; + + /* count number of faces this edge has */ + for(iterv1=first1; iterv1; iterv1=iterv1->next) { + if(iterv1->separate && iterv1 != first1) + break; + + for(iterv2=first2; iterv2; iterv2=iterv2->next) { + if(iterv2->separate && iterv2 != first2) + break; + + if(iterv1->f == iterv2->f) { + /* if face already tagged, don't do this edge */ + efa= EM_get_face_for_index(iterv1->f); + if(efa->f1) + return 0; + + tot++; + break; + } + } + } + + if(*totface == 0) /* start edge */ + *totface= tot; + else if(tot != *totface) /* check for same number of faces as start edge */ + return 0; + + /* tag the faces */ + for(iterv1=first1; iterv1; iterv1=iterv1->next) { + if(iterv1->separate && iterv1 != first1) + break; + + for(iterv2=first2; iterv2; iterv2=iterv2->next) { + if(iterv2->separate && iterv2 != first2) + break; + + if(iterv1->f == iterv2->f) { + efa= EM_get_face_for_index(iterv1->f); + efa->f1= 1; + break; + } + } + } + + return 1; +} + +static int select_edgeloop(Scene *scene, Image *ima, EditMesh *em, NearestHit *hit, float limit[2], int extend) +{ + EditVert *eve; + EditFace *efa; + MTFace *tf; + UvVertMap *vmap; + UvMapVert *iterv1, *iterv2; + int a, count, looking, nverts, starttotf, select; + + /* setup */ + EM_init_index_arrays(em, 0, 0, 1); + vmap= EM_make_uv_vert_map(em, 0, 0, limit); + + for(count=0, eve=em->verts.first; eve; count++, eve= eve->next) + eve->tmp.l = count; + + for(count=0, efa= em->faces.first; efa; count++, efa= efa->next) { + if(!extend) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + uvedit_face_deselect(scene, efa, tf); + } + + efa->tmp.l= count; + efa->f1= 0; + } + + /* set flags for first face and verts */ + nverts= (hit->efa->v4)? 4: 3; + iterv1= uv_vertex_map_get(vmap, hit->efa, hit->edge); + iterv2= uv_vertex_map_get(vmap, hit->efa, (hit->edge+1)%nverts); + uv_vertex_loop_flag(iterv1); + uv_vertex_loop_flag(iterv2); + + starttotf= 0; + uv_edge_tag_faces(iterv1, iterv2, &starttotf); + + /* sorry, first edge isnt even ok */ + if(iterv1->flag==0 && iterv2->flag==0) looking= 0; + else looking= 1; + + /* iterate */ + while(looking) { + looking= 0; + + /* find correct valence edges which are not tagged yet, but connect to tagged one */ + for(efa= em->faces.first; efa; efa=efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(!efa->f1 && uvedit_face_visible(scene, ima, efa, tf)) { + nverts= (efa->v4)? 4: 3; + for(a=0; a<nverts; a++) { + /* check face not hidden and not tagged */ + iterv1= uv_vertex_map_get(vmap, efa, a); + iterv2= uv_vertex_map_get(vmap, efa, (a+1)%nverts); + + /* check if vertex is tagged and has right valence */ + if(iterv1->flag || iterv2->flag) { + if(uv_edge_tag_faces(iterv1, iterv2, &starttotf)) { + looking= 1; + efa->f1= 1; + + uv_vertex_loop_flag(iterv1); + uv_vertex_loop_flag(iterv2); + break; + } + } + } + } + } + } + + /* do the actual select/deselect */ + nverts= (hit->efa->v4)? 4: 3; + iterv1= uv_vertex_map_get(vmap, hit->efa, hit->edge); + iterv2= uv_vertex_map_get(vmap, hit->efa, (hit->edge+1)%nverts); + iterv1->flag= 1; + iterv2->flag= 1; + + if(extend) { + tf= CustomData_em_get(&em->fdata, hit->efa->data, CD_MTFACE); + + if(uvedit_uv_selected(scene, hit->efa, tf, hit->edge) && uvedit_uv_selected(scene, hit->efa, tf, hit->edge)) + select= 0; + else + select= 1; + } + else + select= 1; + + for(efa= em->faces.first; efa; efa=efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + nverts= (efa->v4)? 4: 3; + for(a=0; a<nverts; a++) { + iterv1= uv_vertex_map_get(vmap, efa, a); + + if(iterv1->flag) { + if(select) uvedit_uv_select(scene, efa, tf, a); + else uvedit_uv_deselect(scene, efa, tf, a); + } + } + } + + /* cleanup */ + EM_free_uv_vert_map(vmap); + EM_free_index_arrays(); + + return (select)? 1: -1; +} + +/*********************** linked select ***********************/ + +static void select_linked(Scene *scene, Image *ima, EditMesh *em, float limit[2], NearestHit *hit, int extend) +{ + EditFace *efa; + MTFace *tf; + UvVertMap *vmap; + UvMapVert *vlist, *iterv, *startv; + int a, i, nverts, j, stacksize= 0, *stack; + char *flag; + + vmap= EM_make_uv_vert_map(em, 1, 1, limit); + if(vmap == NULL) + return; + + stack= MEM_mallocN(sizeof(*stack)* BLI_countlist(&em->faces), "UvLinkStack"); + flag= MEM_callocN(sizeof(*flag)*BLI_countlist(&em->faces), "UvLinkFlag"); + + if(!hit) { + for(a=0, efa= em->faces.first; efa; efa= efa->next, a++) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4)) { + stack[stacksize]= a; + stacksize++; + flag[a]= 1; + } + } + } + } + else { + for(a=0, efa= em->faces.first; efa; efa= efa->next, a++) { + if(efa == hit->efa) { + stack[stacksize]= a; + stacksize++; + flag[a]= 1; + break; + } + } + } + + while(stacksize > 0) { + stacksize--; + a= stack[stacksize]; + + for(j=0, efa= em->faces.first; efa; efa= efa->next, j++) + if(j==a) + break; + + nverts= efa->v4? 4: 3; + + for(i=0; i<nverts; i++) { + /* make_uv_vert_map_EM sets verts tmp.l to the indicies */ + vlist= EM_get_uv_map_vert(vmap, (*(&efa->v1 + i))->tmp.l); + + startv= vlist; + + for(iterv=vlist; iterv; iterv=iterv->next) { + if(iterv->separate) + startv= iterv; + if(iterv->f == a) + break; + } + + for(iterv=startv; iterv; iterv=iterv->next) { + if((startv != iterv) && (iterv->separate)) + break; + else if(!flag[iterv->f]) { + flag[iterv->f]= 1; + stack[stacksize]= iterv->f;; + stacksize++; + } + } + } + } + + if(!extend || hit) { + for(a=0, efa= em->faces.first; efa; efa= efa->next, a++) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(flag[a]) + tf->flag |= (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + else + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + } + else if(extend && hit) { + for(a=0, efa= em->faces.first; efa; efa= efa->next, a++) { + if(flag[a]) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(efa->v4) { + if((tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4))) + break; + } + else if(tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3)) + break; + } + } + + if(efa) { + for(a=0, efa= em->faces.first; efa; efa= efa->next, a++) { + if(flag[a]) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + } + } + else { + for(a=0, efa= em->faces.first; efa; efa= efa->next, a++) { + if(flag[a]) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + tf->flag |= (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + } + } + } + + MEM_freeN(stack); + MEM_freeN(flag); + EM_free_uv_vert_map(vmap); +} + +/* ******************** mirror operator **************** */ + +static int mirror_exec(bContext *C, wmOperator *op) +{ + float mat[3][3]; + int axis; + + Mat3One(mat); + axis= RNA_enum_get(op->ptr, "axis"); + + if(axis == 'x') { + /* XXX initTransform(TFM_MIRROR, CTX_NO_PET|CTX_AUTOCONFIRM); + BIF_setSingleAxisConstraint(mat[0], " on X axis"); + Transform(); */ + } + else { + /* XXX initTransform(TFM_MIRROR, CTX_NO_PET|CTX_AUTOCONFIRM); + BIF_setSingleAxisConstraint(mat[1], " on Y axis"); + Transform(); */ + } + + return OPERATOR_FINISHED; +} + +void UV_OT_mirror(wmOperatorType *ot) +{ + static EnumPropertyItem axis_items[] = { + {'x', "MIRROR_X", "Mirror X", "Mirror UVs over X axis."}, + {'y', "MIRROR_Y", "Mirror Y", "Mirror UVs over Y axis."}, + {0, NULL, NULL, NULL}}; + + /* identifiers */ + ot->name= "Mirror"; + ot->idname= "UV_OT_mirror"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= mirror_exec; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_enum(ot->srna, "axis", axis_items, 'x', "Axis", "Axis to mirror UV locations over."); +} + +/* ******************** align operator **************** */ + +static void weld_align_uv(bContext *C, int tool) +{ + Scene *scene; + Object *obedit; + Image *ima; + EditMesh *em; + EditFace *efa; + MTFace *tf; + float cent[2], min[2], max[2]; + + scene= CTX_data_scene(C); + obedit= CTX_data_edit_object(C); + em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ima= CTX_data_edit_image(C); + + INIT_MINMAX2(min, max); + + if(tool == 'a') { + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(uvedit_uv_selected(scene, efa, tf, 0)) + DO_MINMAX2(tf->uv[0], min, max) + if(uvedit_uv_selected(scene, efa, tf, 1)) + DO_MINMAX2(tf->uv[1], min, max) + if(uvedit_uv_selected(scene, efa, tf, 2)) + DO_MINMAX2(tf->uv[2], min, max) + if(efa->v4 && uvedit_uv_selected(scene, efa, tf, 3)) + DO_MINMAX2(tf->uv[3], min, max) + } + } + + tool= (max[0]-min[0] >= max[1]-min[1])? 'y': 'x'; + } + + uvedit_center(scene, ima, obedit, cent, 0); + + if(tool == 'x' || tool == 'w') { + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(uvedit_uv_selected(scene, efa, tf, 0)) + tf->uv[0][0]= cent[0]; + if(uvedit_uv_selected(scene, efa, tf, 1)) + tf->uv[1][0]= cent[0]; + if(uvedit_uv_selected(scene, efa, tf, 2)) + tf->uv[2][0]= cent[0]; + if(efa->v4 && uvedit_uv_selected(scene, efa, tf, 3)) + tf->uv[3][0]= cent[0]; + } + } + } + + if(tool == 'y' || tool == 'w') { + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(uvedit_uv_selected(scene, efa, tf, 0)) + tf->uv[0][1]= cent[1]; + if(uvedit_uv_selected(scene, efa, tf, 1)) + tf->uv[1][1]= cent[1]; + if(uvedit_uv_selected(scene, efa, tf, 2)) + tf->uv[2][1]= cent[1]; + if(efa->v4 && uvedit_uv_selected(scene, efa, tf, 3)) + tf->uv[3][1]= cent[1]; + } + } + } + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); +} + +static int align_exec(bContext *C, wmOperator *op) +{ + weld_align_uv(C, RNA_enum_get(op->ptr, "axis")); + + return OPERATOR_FINISHED; +} + +void UV_OT_align(wmOperatorType *ot) +{ + static EnumPropertyItem axis_items[] = { + {'a', "ALIGN_AUTO", "Align Auto", "Automatically choose the axis on which there is most alignment already."}, + {'x', "ALIGN_X", "Align X", "Align UVs on X axis."}, + {'y', "ALIGN_Y", "Align Y", "Align UVs on Y axis."}, + {0, NULL, NULL, NULL}}; + + /* identifiers */ + ot->name= "Align"; + ot->idname= "UV_OT_align"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= align_exec; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_enum(ot->srna, "axis", axis_items, 'a', "Axis", "Axis to align UV locations on."); +} + +/* ******************** weld operator **************** */ + +static int weld_exec(bContext *C, wmOperator *op) +{ + weld_align_uv(C, 'w'); + + return OPERATOR_FINISHED; +} + +void UV_OT_weld(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Weld"; + ot->idname= "UV_OT_weld"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= weld_exec; + ot->poll= ED_operator_uvedit; +} + +/* ******************** stitch operator **************** */ + +/* just for averaging UVs */ +typedef struct UVVertAverage { + float uv[2]; + int count; +} UVVertAverage; + +static int stitch_exec(bContext *C, wmOperator *op) +{ + SpaceImage *sima; + Scene *scene; + Object *obedit; + EditMesh *em; + EditFace *efa; + EditVert *eve; + Image *ima; + MTFace *tf; + + sima= (SpaceImage*)CTX_wm_space_data(C); + scene= CTX_data_scene(C); + obedit= CTX_data_edit_object(C); + em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ima= CTX_data_edit_image(C); + + if(RNA_boolean_get(op->ptr, "use_limit")) { + UvVertMap *vmap; + UvMapVert *vlist, *iterv; + float newuv[2], limit[2], pixels; + int a, vtot; + + pixels= RNA_float_get(op->ptr, "limit"); + uvedit_pixel_to_float(sima, limit, pixels); + + EM_init_index_arrays(em, 0, 0, 1); + vmap= EM_make_uv_vert_map(em, 1, 0, limit); + + if(vmap == NULL) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + for(a=0, eve= em->verts.first; eve; a++, eve= eve->next) { + vlist= EM_get_uv_map_vert(vmap, a); + + while(vlist) { + newuv[0]= 0; newuv[1]= 0; + vtot= 0; + + for(iterv=vlist; iterv; iterv=iterv->next) { + if((iterv != vlist) && iterv->separate) + break; + + efa = EM_get_face_for_index(iterv->f); + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_uv_selected(scene, efa, tf, iterv->tfindex)) { + newuv[0] += tf->uv[iterv->tfindex][0]; + newuv[1] += tf->uv[iterv->tfindex][1]; + vtot++; + } + } + + if(vtot > 1) { + newuv[0] /= vtot; newuv[1] /= vtot; + + for(iterv=vlist; iterv; iterv=iterv->next) { + if((iterv != vlist) && iterv->separate) + break; + + efa = EM_get_face_for_index(iterv->f); + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_uv_selected(scene, efa, tf, iterv->tfindex)) { + tf->uv[iterv->tfindex][0]= newuv[0]; + tf->uv[iterv->tfindex][1]= newuv[1]; + } + } + } + + vlist= iterv; + } + } + + EM_free_uv_vert_map(vmap); + EM_free_index_arrays(); + } + else { + UVVertAverage *uv_average, *uvav; + int count; + + // index and count verts + for(count=0, eve=em->verts.first; eve; count++, eve= eve->next) + eve->tmp.l = count; + + uv_average= MEM_callocN(sizeof(UVVertAverage)*count, "Stitch"); + + // gather uv averages per vert + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(uvedit_uv_selected(scene, efa, tf, 0)) { + uvav = uv_average + efa->v1->tmp.l; + uvav->count++; + uvav->uv[0] += tf->uv[0][0]; + uvav->uv[1] += tf->uv[0][1]; + } + + if(uvedit_uv_selected(scene, efa, tf, 1)) { + uvav = uv_average + efa->v2->tmp.l; + uvav->count++; + uvav->uv[0] += tf->uv[1][0]; + uvav->uv[1] += tf->uv[1][1]; + } + + if(uvedit_uv_selected(scene, efa, tf, 2)) { + uvav = uv_average + efa->v3->tmp.l; + uvav->count++; + uvav->uv[0] += tf->uv[2][0]; + uvav->uv[1] += tf->uv[2][1]; + } + + if(efa->v4 && uvedit_uv_selected(scene, efa, tf, 3)) { + uvav = uv_average + efa->v4->tmp.l; + uvav->count++; + uvav->uv[0] += tf->uv[3][0]; + uvav->uv[1] += tf->uv[3][1]; + } + } + } + + // apply uv welding + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(uvedit_uv_selected(scene, efa, tf, 0)) { + uvav = uv_average + efa->v1->tmp.l; + tf->uv[0][0] = uvav->uv[0]/uvav->count; + tf->uv[0][1] = uvav->uv[1]/uvav->count; + } + + if(uvedit_uv_selected(scene, efa, tf, 1)) { + uvav = uv_average + efa->v2->tmp.l; + tf->uv[1][0] = uvav->uv[0]/uvav->count; + tf->uv[1][1] = uvav->uv[1]/uvav->count; + } + + if(uvedit_uv_selected(scene, efa, tf, 2)) { + uvav = uv_average + efa->v3->tmp.l; + tf->uv[2][0] = uvav->uv[0]/uvav->count; + tf->uv[2][1] = uvav->uv[1]/uvav->count; + } + + if(efa->v4 && uvedit_uv_selected(scene, efa, tf, 3)) { + uvav = uv_average + efa->v4->tmp.l; + tf->uv[3][0] = uvav->uv[0]/uvav->count; + tf->uv[3][1] = uvav->uv[1]/uvav->count; + } + } + } + + MEM_freeN(uv_average); + } + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_stitch(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Stitch"; + ot->idname= "UV_OT_stitch"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= stitch_exec; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_boolean(ot->srna, "use_limit", 1, "Use Limit", "Stitch UVs within a specified limit distance."); + RNA_def_float(ot->srna, "limit", 20.0, 0.0f, FLT_MAX, "Limit", "Limit distance in image pixels.", -FLT_MAX, FLT_MAX); +} + +/* ******************** (de)select all operator **************** */ + +static int select_inverse_exec(bContext *C, wmOperator *op) +{ + Scene *scene; + Object *obedit; + EditMesh *em; + EditFace *efa; + Image *ima; + MTFace *tf; + + scene= CTX_data_scene(C); + obedit= CTX_data_edit_object(C); + em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ima= CTX_data_edit_image(C); + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + EM_select_swap(em); + } + else { + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + tf->flag ^= TF_SEL1; + tf->flag ^= TF_SEL2; + tf->flag ^= TF_SEL3; + if(efa->v4) tf->flag ^= TF_SEL4; + } + } + } + + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_select_invert(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Select Invert"; + ot->idname= "UV_OT_select_invert"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= select_inverse_exec; + ot->poll= ED_operator_uvedit; +} + +/* ******************** (de)select all operator **************** */ + +static int de_select_all_exec(bContext *C, wmOperator *op) +{ + Scene *scene; + Object *obedit; + EditMesh *em; + EditFace *efa; + Image *ima; + MTFace *tf; + int sel; + + scene= CTX_data_scene(C); + obedit= CTX_data_edit_object(C); + em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ima= CTX_data_edit_image(C); + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + EM_toggle_select_all(em); + } + else { + sel= 0; + + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(tf->flag & (TF_SEL1+TF_SEL2+TF_SEL3+TF_SEL4)) { + sel= 1; + break; + } + } + } + + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(efa->v4) { + if(sel) tf->flag &= ~(TF_SEL1+TF_SEL2+TF_SEL3+TF_SEL4); + else tf->flag |= (TF_SEL1+TF_SEL2+TF_SEL3+TF_SEL4); + } + else { + if(sel) tf->flag &= ~(TF_SEL1+TF_SEL2+TF_SEL3+TF_SEL4); + else tf->flag |= (TF_SEL1+TF_SEL2+TF_SEL3); + } + } + } + } + + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_select_all_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Select or Deselect All"; + ot->idname= "UV_OT_select_all_toggle"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= de_select_all_exec; + ot->poll= ED_operator_uvedit; +} + +/* ******************** mouse select operator **************** */ + +static int sticky_select(float *limit, int hitv[4], int v, float *hituv[4], float *uv, int sticky) +{ + int i; + + /* this function test if some vertex needs to selected + * in addition to the existing ones due to sticky select */ + if(sticky == SI_STICKY_DISABLE) + return 0; + + for(i=0; i<4; i++) { + if(hitv[i] == v) { + if(sticky == SI_STICKY_LOC) { + if(fabs(hituv[i][0]-uv[0]) < limit[0] && fabs(hituv[i][1]-uv[1]) < limit[1]) + return 1; + } + else if(sticky == SI_STICKY_VERTEX) + return 1; + } + } + + return 0; +} + +static int mouse_select(bContext *C, float co[2], int extend, int loop) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Image *ima= CTX_data_edit_image(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + NearestHit hit; + int a, i, select = 1, selectmode, sticky, sync, hitv[4], nvert; + int flush = 0; /* 0 == dont flush, 1 == sel, -1 == desel; only use when selection sync is enabled */ + float limit[2], *hituv[4], penalty[2]; + + uvedit_pixel_to_float(sima, limit, 0.05f); + uvedit_pixel_to_float(sima, penalty, 5.0f); + + /* retrieve operation mode */ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + sync= 1; + + if(scene->selectmode & SCE_SELECT_FACE) + selectmode= UV_SELECT_FACE; + else if(scene->selectmode & SCE_SELECT_EDGE) + selectmode= UV_SELECT_EDGE; + else + selectmode= UV_SELECT_VERTEX; + + sticky= SI_STICKY_DISABLE; + } + else { + sync= 0; + selectmode= scene->toolsettings->uv_selectmode; + sticky= sima->sticky; + } + + /* find nearest element */ + if(loop) { + /* find edge */ + find_nearest_uv_edge(scene, ima, em, co, &hit); + if(hit.efa == NULL) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + } + else if(selectmode == UV_SELECT_VERTEX) { + /* find vertex */ + find_nearest_uv_vert(scene, ima, em, co, penalty, &hit); + if(hit.efa == NULL) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + /* mark 1 vertex as being hit */ + for(i=0; i<4; i++) + hitv[i]= 0xFFFFFFFF; + + hitv[hit.uv]= hit.vert; + hituv[hit.uv]= hit.tf->uv[hit.uv]; + } + else if(selectmode == UV_SELECT_EDGE) { + /* find edge */ + find_nearest_uv_edge(scene, ima, em, co, &hit); + if(hit.efa == NULL) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + /* mark 2 edge vertices as being hit */ + for(i=0; i<4; i++) + hitv[i]= 0xFFFFFFFF; + + nvert= (hit.efa->v4)? 4: 3; + + hitv[hit.edge]= hit.vert; + hitv[(hit.edge+1)%nvert]= hit.vert2; + hituv[hit.edge]= hit.tf->uv[hit.edge]; + hituv[(hit.edge+1)%nvert]= hit.tf->uv[(hit.edge+1)%nvert]; + } + else if(selectmode == UV_SELECT_FACE) { + /* find face */ + find_nearest_uv_face(scene, ima, em, co, &hit); + if(hit.efa == NULL) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + /* make active */ + EM_set_actFace(em, hit.efa); + + /* mark all face vertices as being hit */ + for(i=0; i<4; i++) + hituv[i]= hit.tf->uv[i]; + + hitv[0]= hit.efa->v1->tmp.l; + hitv[1]= hit.efa->v2->tmp.l; + hitv[2]= hit.efa->v3->tmp.l; + + if(hit.efa->v4) hitv[3]= hit.efa->v4->tmp.l; + else hitv[3]= 0xFFFFFFFF; + } + else if(selectmode == UV_SELECT_ISLAND) { + find_nearest_uv_vert(scene, ima, em, co, NULL, &hit); + + if(hit.efa==NULL) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + } + else { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + /* do selection */ + if(loop) { + flush= select_edgeloop(scene, ima, em, &hit, limit, extend); + } + else if(selectmode == UV_SELECT_ISLAND) { + select_linked(scene, ima, em, limit, &hit, extend); + } + else if(extend) { + if(selectmode == UV_SELECT_VERTEX) { + /* (de)select uv vertex */ + if(uvedit_uv_selected(scene, hit.efa, hit.tf, hit.uv)) { + uvedit_uv_deselect(scene, hit.efa, hit.tf, hit.uv); + select= 0; + } + else { + uvedit_uv_select(scene, hit.efa, hit.tf, hit.uv); + select= 1; + } + flush = 1; + } + else if(selectmode == UV_SELECT_EDGE) { + /* (de)select edge */ + if(uvedit_edge_selected(scene, hit.efa, hit.tf, hit.edge)) { + uvedit_edge_deselect(scene, hit.efa, hit.tf, hit.edge); + select= 0; + } + else { + uvedit_edge_select(scene, hit.efa, hit.tf, hit.edge); + select= 1; + } + flush = 1; + } + else if(selectmode == UV_SELECT_FACE) { + /* (de)select face */ + if(uvedit_face_selected(scene, hit.efa, hit.tf)) { + uvedit_face_deselect(scene, hit.efa, hit.tf); + select= 0; + } + else { + uvedit_face_select(scene, hit.efa, hit.tf); + select= 1; + } + flush = -1; + } + + /* (de)select sticky uv nodes */ + if(sticky != SI_STICKY_DISABLE) { + EditVert *ev; + + for(a=0, ev=em->verts.first; ev; ev = ev->next, a++) + ev->tmp.l = a; + + /* deselect */ + if(select==0) { + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(sticky_select(limit, hitv, efa->v1->tmp.l, hituv, tf->uv[0], sticky)) + uvedit_uv_deselect(scene, efa, tf, 0); + if(sticky_select(limit, hitv, efa->v2->tmp.l, hituv, tf->uv[1], sticky)) + uvedit_uv_deselect(scene, efa, tf, 1); + if(sticky_select(limit, hitv, efa->v3->tmp.l, hituv, tf->uv[2], sticky)) + uvedit_uv_deselect(scene, efa, tf, 2); + if(efa->v4) + if(sticky_select(limit, hitv, efa->v4->tmp.l, hituv, tf->uv[3], sticky)) + uvedit_uv_deselect(scene, efa, tf, 3); + } + } + flush = -1; + } + /* select */ + else { + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(sticky_select(limit, hitv, efa->v1->tmp.l, hituv, tf->uv[0], sticky)) + uvedit_uv_select(scene, efa, tf, 0); + if(sticky_select(limit, hitv, efa->v2->tmp.l, hituv, tf->uv[1], sticky)) + uvedit_uv_select(scene, efa, tf, 1); + if(sticky_select(limit, hitv, efa->v3->tmp.l, hituv, tf->uv[2], sticky)) + uvedit_uv_select(scene, efa, tf, 2); + if(efa->v4) + if(sticky_select(limit, hitv, efa->v4->tmp.l, hituv, tf->uv[3], sticky)) + uvedit_uv_select(scene, efa, tf, 3); + } + } + + flush = 1; + } + } + } + else { + /* deselect all */ + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + uvedit_face_deselect(scene, efa, tf); + } + + if(selectmode == UV_SELECT_VERTEX) { + /* select vertex */ + uvedit_uv_select(scene, hit.efa, hit.tf, hit.uv); + flush= 1; + } + else if(selectmode == UV_SELECT_EDGE) { + /* select edge */ + uvedit_edge_select(scene, hit.efa, hit.tf, hit.edge); + flush= 1; + } + else if(selectmode == UV_SELECT_FACE) { + /* select face */ + uvedit_face_select(scene, hit.efa, hit.tf); + } + + /* select sticky uvs */ + if(sticky != SI_STICKY_DISABLE) { + for(efa= em->faces.first; efa; efa= efa->next) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(sticky == SI_STICKY_DISABLE) continue; + + if(sticky_select(limit, hitv, efa->v1->tmp.l, hituv, tf->uv[0], sticky)) + uvedit_uv_select(scene, efa, tf, 0); + if(sticky_select(limit, hitv, efa->v2->tmp.l, hituv, tf->uv[1], sticky)) + uvedit_uv_select(scene, efa, tf, 1); + if(sticky_select(limit, hitv, efa->v3->tmp.l, hituv, tf->uv[2], sticky)) + uvedit_uv_select(scene, efa, tf, 2); + if(efa->v4) + if(sticky_select(limit, hitv, efa->v4->tmp.l, hituv, tf->uv[3], sticky)) + uvedit_uv_select(scene, efa, tf, 3); + + flush= 1; + } + } + } + } + + if(sync) { + /* flush for mesh selection */ + if(scene->selectmode != SCE_SELECT_FACE) { + if(flush==1) EM_select_flush(em); + else if(flush==-1) EM_deselect_flush(em); + } + } + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_PASS_THROUGH|OPERATOR_FINISHED; +} + +static int select_exec(bContext *C, wmOperator *op) +{ + float co[2]; + int extend, loop; + + RNA_float_get_array(op->ptr, "location", co); + extend= RNA_boolean_get(op->ptr, "extend"); + loop= 0; + + return mouse_select(C, co, extend, loop); +} + +static int select_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + ARegion *ar= CTX_wm_region(C); + float co[2]; + int x, y; + + x= event->x - ar->winrct.xmin; + y= event->y - ar->winrct.ymin; + + UI_view2d_region_to_view(&ar->v2d, x, y, &co[0], &co[1]); + RNA_float_set_array(op->ptr, "location", co); + + return select_exec(C, op); +} + +void UV_OT_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Select"; + ot->idname= "UV_OT_select"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= select_exec; + ot->invoke= select_invoke; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_boolean(ot->srna, "extend", 0, + "Extend", "Extend selection rather than clearing the existing selection."); + RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, + "Location", "Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds.", -100.0f, 100.0f); +} + +/* ******************** loop select operator **************** */ + +static int select_loop_exec(bContext *C, wmOperator *op) +{ + float co[2]; + int extend, loop; + + RNA_float_get_array(op->ptr, "location", co); + extend= RNA_boolean_get(op->ptr, "extend"); + loop= 1; + + return mouse_select(C, co, extend, loop); +} + +static int select_loop_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + ARegion *ar= CTX_wm_region(C); + float co[2]; + int x, y; + + x= event->x - ar->winrct.xmin; + y= event->y - ar->winrct.ymin; + + UI_view2d_region_to_view(&ar->v2d, x, y, &co[0], &co[1]); + RNA_float_set_array(op->ptr, "location", co); + + return select_loop_exec(C, op); +} + +void UV_OT_select_loop(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Loop Select"; + ot->idname= "UV_OT_select_loop"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= select_loop_exec; + ot->invoke= select_loop_invoke; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_boolean(ot->srna, "extend", 0, + "Extend", "Extend selection rather than clearing the existing selection."); + RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, + "Location", "Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds.", -100.0f, 100.0f); +} + +/* ******************** linked select operator **************** */ + +static int select_linked_exec(bContext *C, wmOperator *op) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Image *ima= CTX_data_edit_image(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + float limit[2]; + int extend; + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + BKE_report(op->reports, RPT_ERROR, "Can't select linked when sync selection is enabled."); + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + extend= RNA_boolean_get(op->ptr, "extend"); + uvedit_pixel_to_float(sima, limit, 0.05f); + select_linked(scene, ima, em, limit, NULL, extend); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_select_linked(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Select Linked"; + ot->idname= "UV_OT_select_linked"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= select_linked_exec; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_boolean(ot->srna, "extend", 0, + "Extend", "Extend selection rather than clearing the existing selection."); +} + +/* ******************** unlink selection operator **************** */ + +static int unlink_selection_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Image *ima= CTX_data_edit_image(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + BKE_report(op->reports, RPT_ERROR, "Can't unlink selection when sync selection is enabled."); + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tf)) { + if(efa->v4) { + if(~tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4)) + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + else { + if(~tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3)) + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3); + } + } + } + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_unlink_selection(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Unlink Selection"; + ot->idname= "UV_OT_unlink_selection"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= unlink_selection_exec; + ot->poll= ED_operator_uvedit; +} + +/* ******************** border select operator **************** */ + +/* This function sets the selection on tagged faces, need because settings the + * selection a face is done in a number of places but it also needs to respect + * the sticky modes for the UV verts, so dealing with the sticky modes is best + * done in a seperate function. + * + * De-selects faces that have been tagged on efa->tmp.l. */ + +static void uv_faces_do_sticky(bContext *C, SpaceImage *sima, Scene *scene, Object *obedit, short select) +{ + /* Selecting UV Faces with some modes requires us to change + * the selection in other faces (depending on the sticky mode). + * + * This only needs to be done when the Mesh is not used for + * selection (so for sticky modes, vertex or location based). */ + + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + int nverts, i; + + if((scene->toolsettings->uv_flag & UV_SYNC_SELECTION)==0 && sima->sticky == SI_STICKY_VERTEX) { + /* Tag all verts as untouched, then touch the ones that have a face center + * in the loop and select all MTFace UV's that use a touched vert. */ + EditVert *eve; + + for(eve= em->verts.first; eve; eve= eve->next) + eve->tmp.l = 0; + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->tmp.l) { + if(efa->v4) + efa->v1->tmp.l= efa->v2->tmp.l= efa->v3->tmp.l= efa->v4->tmp.l=1; + else + efa->v1->tmp.l= efa->v2->tmp.l= efa->v3->tmp.l= 1; + } + } + + /* now select tagged verts */ + for(efa= em->faces.first; efa; efa= efa->next) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + nverts= efa->v4? 4: 3; + for(i=0; i<nverts; i++) { + if((*(&efa->v1 + i))->tmp.l) { + if(select) + uvedit_uv_select(scene, efa, tf, i); + else + uvedit_uv_deselect(scene, efa, tf, i); + } + } + } + } + else if((scene->toolsettings->uv_flag & UV_SYNC_SELECTION)==0 && sima->sticky == SI_STICKY_LOC) { + EditFace *efa_vlist; + MTFace *tf_vlist; + UvMapVert *start_vlist=NULL, *vlist_iter; + struct UvVertMap *vmap; + float limit[2]; + int efa_index; + //EditVert *eve; /* removed vert counting for now */ + //int a; + + uvedit_pixel_to_float(sima, limit, 0.05); + + EM_init_index_arrays(em, 0, 0, 1); + vmap= EM_make_uv_vert_map(em, 0, 0, limit); + + /* verts are numbered above in make_uv_vert_map_EM, make sure this stays true! */ + /*for(a=0, eve= em->verts.first; eve; a++, eve= eve->next) + eve->tmp.l = a; */ + + if(vmap == NULL) { + BKE_mesh_end_editmesh(obedit->data, em); + return; + } + + for(efa_index=0, efa= em->faces.first; efa; efa_index++, efa= efa->next) { + if(efa->tmp.l) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + nverts= efa->v4? 4: 3; + + for(i=0; i<nverts; i++) { + if(select) + uvedit_uv_select(scene, efa, tf, i); + else + uvedit_uv_deselect(scene, efa, tf, i); + + vlist_iter= EM_get_uv_map_vert(vmap, (*(&efa->v1 + i))->tmp.l); + + while (vlist_iter) { + if(vlist_iter->separate) + start_vlist = vlist_iter; + + if(efa_index == vlist_iter->f) + break; + + vlist_iter = vlist_iter->next; + } + + vlist_iter = start_vlist; + while (vlist_iter) { + + if(vlist_iter != start_vlist && vlist_iter->separate) + break; + + if(efa_index != vlist_iter->f) { + efa_vlist = EM_get_face_for_index(vlist_iter->f); + tf_vlist = CustomData_em_get(&em->fdata, efa_vlist->data, CD_MTFACE); + + if(select) + uvedit_uv_select(scene, efa_vlist, tf_vlist, vlist_iter->tfindex); + else + uvedit_uv_deselect(scene, efa_vlist, tf_vlist, vlist_iter->tfindex); + } + vlist_iter = vlist_iter->next; + } + } + } + } + EM_free_index_arrays(); + EM_free_uv_vert_map(vmap); + + } + else { /* SI_STICKY_DISABLE or scene->toolsettings->uv_flag & UV_SYNC_SELECTION */ + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->tmp.l) { + tf = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(select) + uvedit_face_select(scene, efa, tf); + else + uvedit_face_deselect(scene, efa, tf); + } + } + } + BKE_mesh_end_editmesh(obedit->data, em); +} + +static int border_select_exec(bContext *C, wmOperator *op) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Image *ima= CTX_data_edit_image(C); + ARegion *ar= CTX_wm_region(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tface; + rcti rect; + rctf rectf; + int change, pinned, select, faces; + + /* get rectangle from operator */ + rect.xmin= RNA_int_get(op->ptr, "xmin"); + rect.ymin= RNA_int_get(op->ptr, "ymin"); + rect.xmax= RNA_int_get(op->ptr, "xmax"); + rect.ymax= RNA_int_get(op->ptr, "ymax"); + + UI_view2d_region_to_view(&ar->v2d, rect.xmin, rect.ymin, &rectf.xmin, &rectf.ymin); + UI_view2d_region_to_view(&ar->v2d, rect.xmax, rect.ymax, &rectf.xmax, &rectf.ymax); + + /* figure out what to select/deselect */ + select= (RNA_int_get(op->ptr, "event_type") == LEFTMOUSE); // XXX hardcoded + pinned= RNA_boolean_get(op->ptr, "pinned"); + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) + faces= (scene->selectmode == SCE_SELECT_FACE); + else + faces= (scene->toolsettings->uv_selectmode == UV_SELECT_FACE); + + /* do actual selection */ + if(faces && !pinned) { + /* handle face selection mode */ + float cent[2]; + + change= 0; + + for(efa= em->faces.first; efa; efa= efa->next) { + /* assume not touched */ + efa->tmp.l = 0; + tface= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(uvedit_face_visible(scene, ima, efa, tface)) { + uv_center(tface->uv, cent, efa->v4 != NULL); + if(BLI_in_rctf(&rectf, cent[0], cent[1])) { + efa->tmp.l = change = 1; + } + } + } + + /* (de)selects all tagged faces and deals with sticky modes */ + if(change) + uv_faces_do_sticky(C, sima, scene, obedit, select); + } + else { + /* other selection modes */ + change= 1; + + for(efa= em->faces.first; efa; efa= efa->next) { + tface= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(uvedit_face_visible(scene, ima, efa, tface)) { + if(!pinned || (scene->toolsettings->uv_flag & UV_SYNC_SELECTION) ) { + /* UV_SYNC_SELECTION - can't do pinned selection */ + if(BLI_in_rctf(&rectf, tface->uv[0][0], tface->uv[0][1])) { + if(select) uvedit_uv_select(scene, efa, tface, 0); + else uvedit_uv_deselect(scene, efa, tface, 0); + } + if(BLI_in_rctf(&rectf, tface->uv[1][0], tface->uv[1][1])) { + if(select) uvedit_uv_select(scene, efa, tface, 1); + else uvedit_uv_deselect(scene, efa, tface, 1); + } + if(BLI_in_rctf(&rectf, tface->uv[2][0], tface->uv[2][1])) { + if(select) uvedit_uv_select(scene, efa, tface, 2); + else uvedit_uv_deselect(scene, efa, tface, 2); + } + if(efa->v4 && BLI_in_rctf(&rectf, tface->uv[3][0], tface->uv[3][1])) { + if(select) uvedit_uv_select(scene, efa, tface, 3); + else uvedit_uv_deselect(scene, efa, tface, 3); + } + } + else if(pinned) { + if((tface->unwrap & TF_PIN1) && + BLI_in_rctf(&rectf, tface->uv[0][0], tface->uv[0][1])) { + + if(select) uvedit_uv_select(scene, efa, tface, 0); + else uvedit_uv_deselect(scene, efa, tface, 0); + } + if((tface->unwrap & TF_PIN2) && + BLI_in_rctf(&rectf, tface->uv[1][0], tface->uv[1][1])) { + + if(select) uvedit_uv_select(scene, efa, tface, 1); + else uvedit_uv_deselect(scene, efa, tface, 1); + } + if((tface->unwrap & TF_PIN3) && + BLI_in_rctf(&rectf, tface->uv[2][0], tface->uv[2][1])) { + + if(select) uvedit_uv_select(scene, efa, tface, 2); + else uvedit_uv_deselect(scene, efa, tface, 2); + } + if((efa->v4) && (tface->unwrap & TF_PIN4) && BLI_in_rctf(&rectf, tface->uv[3][0], tface->uv[3][1])) { + if(select) uvedit_uv_select(scene, efa, tface, 3); + else uvedit_uv_deselect(scene, efa, tface, 3); + } + } + } + } + } + + if(change) { + /* make sure newly selected vert selection is updated*/ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + if(scene->selectmode != SCE_SELECT_FACE) { + if(select) EM_select_flush(em); + else EM_deselect_flush(em); + } + } + + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; + } + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; +} + +void UV_OT_select_border(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Border Select"; + ot->idname= "UV_OT_select_border"; + + /* api callbacks */ + ot->invoke= WM_border_select_invoke; + ot->exec= border_select_exec; + ot->modal= WM_border_select_modal; + ot->poll= ED_operator_uvedit; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "pinned", 0, "Pinned", "Border select pinned UVs only."); + + RNA_def_int(ot->srna, "event_type", 0, INT_MIN, INT_MAX, "Event Type", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "xmin", 0, INT_MIN, INT_MAX, "X Min", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "xmax", 0, INT_MIN, INT_MAX, "X Max", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "ymin", 0, INT_MIN, INT_MAX, "Y Min", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "ymax", 0, INT_MIN, INT_MAX, "Y Max", "", INT_MIN, INT_MAX); +} + +/* ******************** circle select operator **************** */ + +static void select_uv_inside_ellipse(SpaceImage *sima, Scene *scene, int select, EditFace *efa, MTFace *tface, int index, float *offset, float *ell, int select_index) +{ + /* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */ + float x, y, r2, *uv; + + uv= tface->uv[index]; + + x= (uv[0] - offset[0])*ell[0]; + y= (uv[1] - offset[1])*ell[1]; + + r2 = x*x + y*y; + if(r2 < 1.0) { + if(select) uvedit_uv_select(scene, efa, tface, select_index); + else uvedit_uv_deselect(scene, efa, tface, select_index); + } +} + +int circle_select_exec(bContext *C, wmOperator *op) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ARegion *ar= CTX_wm_region(C); + EditFace *efa; + MTFace *tface; + int x, y, radius, width, height, select; + float zoomx, zoomy, offset[2], ellipse[2]; + + /* get operator properties */ + select= (RNA_int_get(op->ptr, "event_type") == LEFTMOUSE); // XXX hardcoded + x= RNA_int_get(op->ptr, "x"); + y= RNA_int_get(op->ptr, "y"); + radius= RNA_int_get(op->ptr, "radius"); + + /* compute ellipse size and location, not a circle since we deal + * with non square image. ellipse is normalized, r = 1.0. */ + ED_space_image_size(sima, &width, &height); + ED_space_image_zoom(sima, ar, &zoomx, &zoomy); + + ellipse[0]= width*zoomx/radius; + ellipse[1]= height*zoomy/radius; + + UI_view2d_region_to_view(&ar->v2d, x, y, &offset[0], &offset[1]); + + /* do selection */ + for(efa= em->faces.first; efa; efa= efa->next) { + tface= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + select_uv_inside_ellipse(sima, scene, select, efa, tface, 0, offset, ellipse, 0); + select_uv_inside_ellipse(sima, scene, select, efa, tface, 1, offset, ellipse, 1); + select_uv_inside_ellipse(sima, scene, select, efa, tface, 2, offset, ellipse, 2); + if(efa->v4) + select_uv_inside_ellipse(sima, scene, select, efa, tface, 3, offset, ellipse, 3); + } + + if(select) EM_select_flush(em); + else EM_deselect_flush(em); + + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_circle_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Circle Select"; + ot->idname= "UV_OT_circle_select"; + + /* api callbacks */ + ot->invoke= WM_gesture_circle_invoke; + ot->modal= WM_gesture_circle_modal; + ot->exec= circle_select_exec; + ot->poll= ED_operator_uvedit; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "radius", 0, INT_MIN, INT_MAX, "Radius", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "event_type", 0, INT_MIN, INT_MAX, "Event Type", "", INT_MIN, INT_MAX); +} + +/* ******************** snap cursor operator **************** */ + +static void snap_uv_to_pixel(float *uvco, float w, float h) +{ + uvco[0] = ((float)((int)((uvco[0]*w) + 0.5f)))/w; + uvco[1] = ((float)((int)((uvco[1]*h) + 0.5f)))/h; +} + +static void snap_cursor_to_pixels(SpaceImage *sima, View2D *v2d) +{ + int width= 0, height= 0; + + ED_space_image_size(sima, &width, &height); + snap_uv_to_pixel(v2d->cursor, width, height); +} + +static int snap_cursor_to_selection(Scene *scene, Image *ima, Object *obedit, View2D *v2d) +{ + return uvedit_center(scene, ima, obedit, v2d->cursor, 0); +} + +static int snap_cursor_exec(bContext *C, wmOperator *op) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Image *ima= CTX_data_edit_image(C); + ARegion *ar= CTX_wm_region(C); + int change= 0; + + switch(RNA_boolean_get(op->ptr, "target")) { + case 0: + snap_cursor_to_pixels(sima, &ar->v2d); + change= 1; + break; + case 1: + change= snap_cursor_to_selection(scene, ima, obedit, &ar->v2d); + break; + } + + if(!change) + return OPERATOR_CANCELLED; + + ED_region_tag_redraw(ar); + + return OPERATOR_FINISHED; +} + +void UV_OT_snap_cursor(wmOperatorType *ot) +{ + static EnumPropertyItem target_items[] = { + {0, "PIXELS", "Pixels", ""}, + {1, "SELECTION", "Selection", ""}, + {0, NULL, NULL, NULL}}; + + /* identifiers */ + ot->name= "Snap Cursor"; + ot->idname= "UV_OT_snap_cursor"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= snap_cursor_exec; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_enum(ot->srna, "target", target_items, 0, "Target", "Target to snap the selected UV's to."); +} + +/* ******************** snap selection operator **************** */ + +static int snap_uvs_to_cursor(Scene *scene, Image *ima, Object *obedit, View2D *v2d) +{ + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tface; + short change= 0; + + for(efa= em->faces.first; efa; efa= efa->next) { + tface= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(uvedit_face_visible(scene, ima, efa, tface)) { + if(uvedit_uv_selected(scene, efa, tface, 0)) VECCOPY2D(tface->uv[0], v2d->cursor); + if(uvedit_uv_selected(scene, efa, tface, 1)) VECCOPY2D(tface->uv[1], v2d->cursor); + if(uvedit_uv_selected(scene, efa, tface, 2)) VECCOPY2D(tface->uv[2], v2d->cursor); + if(efa->v4) + if(uvedit_uv_selected(scene, efa, tface, 3)) VECCOPY2D(tface->uv[3], v2d->cursor); + + change= 1; + } + } + + BKE_mesh_end_editmesh(obedit->data, em); + return change; +} + +static int snap_uvs_to_adjacent_unselected(Scene *scene, Image *ima, Object *obedit) +{ + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + EditVert *eve; + MTFace *tface; + short change = 0; + int count = 0; + float *coords; + short *usercount, users; + + /* set all verts to -1 : an unused index*/ + for(eve= em->verts.first; eve; eve= eve->next) + eve->tmp.l=-1; + + /* index every vert that has a selected UV using it, but only once so as to + * get unique indicies and to count how much to malloc */ + for(efa= em->faces.first; efa; efa= efa->next) { + tface= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tface)) { + if(uvedit_uv_selected(scene, efa, tface, 0) && efa->v1->tmp.l==-1) efa->v1->tmp.l= count++; + if(uvedit_uv_selected(scene, efa, tface, 1) && efa->v2->tmp.l==-1) efa->v2->tmp.l= count++; + if(uvedit_uv_selected(scene, efa, tface, 2) && efa->v3->tmp.l==-1) efa->v3->tmp.l= count++; + if(efa->v4) + if(uvedit_uv_selected(scene, efa, tface, 3) && efa->v4->tmp.l==-1) efa->v4->tmp.l= count++; + + change = 1; + + /* optional speedup */ + efa->tmp.p = tface; + } + else + efa->tmp.p = NULL; + } + + coords = MEM_callocN(sizeof(float)*count*2, "snap to adjacent coords"); + usercount = MEM_callocN(sizeof(short)*count, "snap to adjacent counts"); + + /* add all UV coords from visible, unselected UV coords as well as counting them to average later */ + for(efa= em->faces.first; efa; efa= efa->next) { + if((tface=(MTFace *)efa->tmp.p)) { + /* is this an unselected UV we can snap to? */ + if(efa->v1->tmp.l >= 0 && (!uvedit_uv_selected(scene, efa, tface, 0))) { + coords[efa->v1->tmp.l*2] += tface->uv[0][0]; + coords[(efa->v1->tmp.l*2)+1] += tface->uv[0][1]; + usercount[efa->v1->tmp.l]++; + change = 1; + } + if(efa->v2->tmp.l >= 0 && (!uvedit_uv_selected(scene, efa, tface, 1))) { + coords[efa->v2->tmp.l*2] += tface->uv[1][0]; + coords[(efa->v2->tmp.l*2)+1] += tface->uv[1][1]; + usercount[efa->v2->tmp.l]++; + change = 1; + } + if(efa->v3->tmp.l >= 0 && (!uvedit_uv_selected(scene, efa, tface, 2))) { + coords[efa->v3->tmp.l*2] += tface->uv[2][0]; + coords[(efa->v3->tmp.l*2)+1] += tface->uv[2][1]; + usercount[efa->v3->tmp.l]++; + change = 1; + } + + if(efa->v4) { + if(efa->v4->tmp.l >= 0 && (!uvedit_uv_selected(scene, efa, tface, 3))) { + coords[efa->v4->tmp.l*2] += tface->uv[3][0]; + coords[(efa->v4->tmp.l*2)+1] += tface->uv[3][1]; + usercount[efa->v4->tmp.l]++; + change = 1; + } + } + } + } + + /* no other verts selected, bail out */ + if(!change) { + MEM_freeN(coords); + MEM_freeN(usercount); + BKE_mesh_end_editmesh(obedit->data, em); + return change; + } + + /* copy the averaged unselected UVs back to the selected UVs */ + for(efa= em->faces.first; efa; efa= efa->next) { + if((tface=(MTFace *)efa->tmp.p)) { + + if( uvedit_uv_selected(scene, efa, tface, 0) && + efa->v1->tmp.l >= 0 && + (users = usercount[efa->v1->tmp.l]) + ) { + tface->uv[0][0] = coords[efa->v1->tmp.l*2] / users; + tface->uv[0][1] = coords[(efa->v1->tmp.l*2)+1] / users; + } + + if( uvedit_uv_selected(scene, efa, tface, 1) && + efa->v2->tmp.l >= 0 && + (users = usercount[efa->v2->tmp.l]) + ) { + tface->uv[1][0] = coords[efa->v2->tmp.l*2] / users; + tface->uv[1][1] = coords[(efa->v2->tmp.l*2)+1] / users; + } + + if( uvedit_uv_selected(scene, efa, tface, 2) && + efa->v3->tmp.l >= 0 && + (users = usercount[efa->v3->tmp.l]) + ) { + tface->uv[2][0] = coords[efa->v3->tmp.l*2] / users; + tface->uv[2][1] = coords[(efa->v3->tmp.l*2)+1] / users; + } + + if(efa->v4) { + if( uvedit_uv_selected(scene, efa, tface, 3) && + efa->v4->tmp.l >= 0 && + (users = usercount[efa->v4->tmp.l]) + ) { + tface->uv[3][0] = coords[efa->v4->tmp.l*2] / users; + tface->uv[3][1] = coords[(efa->v4->tmp.l*2)+1] / users; + } + } + } + } + + MEM_freeN(coords); + MEM_freeN(usercount); + + BKE_mesh_end_editmesh(obedit->data, em); + return change; +} + +static int snap_uvs_to_pixels(SpaceImage *sima, Scene *scene, Object *obedit) +{ + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + Image *ima= sima->image; + EditFace *efa; + MTFace *tface; + int width= 0, height= 0; + float w, h; + short change = 0; + + ED_space_image_size(sima, &width, &height); + w = (float)width; + h = (float)height; + + for(efa= em->faces.first; efa; efa= efa->next) { + tface= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(uvedit_face_visible(scene, ima, efa, tface)) { + if(uvedit_uv_selected(scene, efa, tface, 0)) snap_uv_to_pixel(tface->uv[0], w, h); + if(uvedit_uv_selected(scene, efa, tface, 1)) snap_uv_to_pixel(tface->uv[1], w, h); + if(uvedit_uv_selected(scene, efa, tface, 2)) snap_uv_to_pixel(tface->uv[2], w, h); + if(efa->v4) + if(uvedit_uv_selected(scene, efa, tface, 3)) snap_uv_to_pixel(tface->uv[3], w, h); + + change = 1; + } + } + + BKE_mesh_end_editmesh(obedit->data, em); + return change; +} + +static int snap_selection_exec(bContext *C, wmOperator *op) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Image *ima= CTX_data_edit_image(C); + ARegion *ar= CTX_wm_region(C); + int change= 0; + + switch(RNA_boolean_get(op->ptr, "target")) { + case 0: + change= snap_uvs_to_pixels(sima, scene, obedit); + break; + case 1: + change= snap_uvs_to_cursor(scene, ima, obedit, &ar->v2d); + break; + case 2: + change= snap_uvs_to_adjacent_unselected(scene, ima, obedit); + break; + } + + if(!change) + return OPERATOR_CANCELLED; + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + return OPERATOR_FINISHED; +} + +void UV_OT_snap_selection(wmOperatorType *ot) +{ + static EnumPropertyItem target_items[] = { + {0, "PIXELS", "Pixels", ""}, + {1, "CURSOR", "Cursor", ""}, + {2, "ADJACENT_UNSELECTED", "Adjacent Unselected", ""}, + {0, NULL, NULL, NULL}}; + + /* identifiers */ + ot->name= "Snap Selection"; + ot->idname= "UV_OT_snap_selection"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= snap_selection_exec; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_enum(ot->srna, "target", target_items, 0, "Target", "Target to snap the selected UV's to."); +} + +/* ******************** pin operator **************** */ + +static int pin_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Image *ima= CTX_data_edit_image(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tface; + int clear= RNA_boolean_get(op->ptr, "clear"); + + for(efa= em->faces.first; efa; efa= efa->next) { + tface = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tface)) { + if(!clear) { + if(uvedit_uv_selected(scene, efa, tface, 0)) tface->unwrap |= TF_PIN1; + if(uvedit_uv_selected(scene, efa, tface, 1)) tface->unwrap |= TF_PIN2; + if(uvedit_uv_selected(scene, efa, tface, 2)) tface->unwrap |= TF_PIN3; + if(efa->v4) + if(uvedit_uv_selected(scene, efa, tface, 3)) tface->unwrap |= TF_PIN4; + } + else { + if(uvedit_uv_selected(scene, efa, tface, 0)) tface->unwrap &= ~TF_PIN1; + if(uvedit_uv_selected(scene, efa, tface, 1)) tface->unwrap &= ~TF_PIN2; + if(uvedit_uv_selected(scene, efa, tface, 2)) tface->unwrap &= ~TF_PIN3; + if(efa->v4) + if(uvedit_uv_selected(scene, efa, tface, 3)) tface->unwrap &= ~TF_PIN4; + } + } + } + + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_pin(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Pin"; + ot->idname= "UV_OT_pin"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= pin_exec; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_boolean(ot->srna, "clear", 0, "Clear", "Clear pinning for the selection instead of setting it."); +} + +/******************* select pinned operator ***************/ + +static int select_pinned_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + Image *ima= CTX_data_edit_image(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tface; + + for(efa= em->faces.first; efa; efa= efa->next) { + tface = CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(uvedit_face_visible(scene, ima, efa, tface)) { + if(tface->unwrap & TF_PIN1) uvedit_uv_select(scene, efa, tface, 0); + if(tface->unwrap & TF_PIN2) uvedit_uv_select(scene, efa, tface, 1); + if(tface->unwrap & TF_PIN3) uvedit_uv_select(scene, efa, tface, 2); + if(efa->v4) { + if(tface->unwrap & TF_PIN4) uvedit_uv_select(scene, efa, tface, 3); + } + } + } + + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_select_pinned(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Selected Pinned"; + ot->idname= "UV_OT_select_pinned"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= select_pinned_exec; + ot->poll= ED_operator_uvedit; +} + +/********************** hide operator *********************/ + +static int hide_exec(bContext *C, wmOperator *op) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + int swap= RNA_boolean_get(op->ptr, "unselected"); + + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + EM_hide_mesh(em, swap); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; + } + + if(swap) { + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + if(sima->flag & SI_SELACTFACE) { + /* Pretend face mode */ + if(( (efa->v4==NULL && + ( tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3)) == (TF_SEL1|TF_SEL2|TF_SEL3) ) || + ( tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4)) == (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4) ) == 0) { + + if(em->selectmode == SCE_SELECT_FACE) { + efa->f &= ~SELECT; + /* must re-select after */ + efa->e1->f &= ~SELECT; + efa->e2->f &= ~SELECT; + efa->e3->f &= ~SELECT; + if(efa->e4) efa->e4->f &= ~SELECT; + } + else + EM_select_face(efa, 0); + } + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + else if(em->selectmode == SCE_SELECT_FACE) { + if((tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3))==0) { + if(!efa->v4) + EM_select_face(efa, 0); + else if(!(tf->flag & TF_SEL4)) + EM_select_face(efa, 0); + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + } + else { + /* EM_deselect_flush will deselect the face */ + if((tf->flag & TF_SEL1)==0) efa->v1->f &= ~SELECT; + if((tf->flag & TF_SEL2)==0) efa->v2->f &= ~SELECT; + if((tf->flag & TF_SEL3)==0) efa->v3->f &= ~SELECT; + if((efa->v4) && (tf->flag & TF_SEL4)==0) efa->v4->f &= ~SELECT; + + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + } + } + } + else { + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(sima->flag & SI_SELACTFACE) { + if( (efa->v4==NULL && + ( tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3)) == (TF_SEL1|TF_SEL2|TF_SEL3) ) || + ( tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4)) == (TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4) ) { + + if(em->selectmode == SCE_SELECT_FACE) { + efa->f &= ~SELECT; + /* must re-select after */ + efa->e1->f &= ~SELECT; + efa->e2->f &= ~SELECT; + efa->e3->f &= ~SELECT; + if(efa->e4) efa->e4->f &= ~SELECT; + } + else + EM_select_face(efa, 0); + } + + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + else if(em->selectmode == SCE_SELECT_FACE) { + if(tf->flag & (TF_SEL1|TF_SEL2|TF_SEL3)) + EM_select_face(efa, 0); + else if(efa->v4 && tf->flag & TF_SEL4) + EM_select_face(efa, 0); + + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + else { + /* EM_deselect_flush will deselect the face */ + if(tf->flag & TF_SEL1) efa->v1->f &= ~SELECT; + if(tf->flag & TF_SEL2) efa->v2->f &= ~SELECT; + if(tf->flag & TF_SEL3) efa->v3->f &= ~SELECT; + if((efa->v4) && tf->flag & TF_SEL4) efa->v4->f &= ~SELECT; + + tf->flag &= ~(TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4); + } + } + } + } + + /*deselects too many but ok for now*/ + if(em->selectmode & (SCE_SELECT_EDGE|SCE_SELECT_VERTEX)) + EM_deselect_flush(em); + + if(em->selectmode==SCE_SELECT_FACE) { + /* de-selected all edges from faces that were de-selected. + * now make sure all faces that are selected also have selected edges */ + for(efa= em->faces.first; efa; efa= efa->next) + if(efa->f & SELECT) + EM_select_face(efa, 1); + } + + EM_validate_selections(em); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_hide(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Hide Selected"; + ot->idname= "UV_OT_hide"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= hide_exec; + ot->poll= ED_operator_uvedit; + + /* props */ + RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected."); +} + +/****************** reveal operator ******************/ + +static int reveal_exec(bContext *C, wmOperator *op) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + + /* call the mesh function if we are in mesh sync sel */ + if(scene->toolsettings->uv_flag & UV_SYNC_SELECTION) { + EM_reveal_mesh(em); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; + } + + if(sima->flag & SI_SELACTFACE) { + if(em->selectmode == SCE_SELECT_FACE) { + for(efa= em->faces.first; efa; efa= efa->next) { + if(!(efa->h) && !(efa->f & SELECT)) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + EM_select_face(efa, 1); + tf->flag |= TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4; + } + } + } + else { + /* enable adjacent faces to have disconnected UV selections if sticky is disabled */ + if(sima->sticky == SI_STICKY_DISABLE) { + for(efa= em->faces.first; efa; efa= efa->next) { + if(!(efa->h) && !(efa->f & SELECT)) { + /* All verts must be unselected for the face to be selected in the UV view */ + if((efa->v1->f&SELECT)==0 && (efa->v2->f&SELECT)==0 && (efa->v3->f&SELECT)==0 && (efa->v4==0 || (efa->v4->f&SELECT)==0)) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + tf->flag |= TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4; + /* Cant use EM_select_face here because it unselects the verts + * and we cant tell if the face was totally unselected or not */ + /*EM_select_face(efa, 1); + * + * See Loop with EM_select_face() below... */ + efa->f |= SELECT; + } + } + } + } + else { + for(efa= em->faces.first; efa; efa= efa->next) { + if(!(efa->h) && !(efa->f & SELECT)) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if((efa->v1->f & SELECT)==0) {tf->flag |= TF_SEL1;} + if((efa->v2->f & SELECT)==0) {tf->flag |= TF_SEL2;} + if((efa->v3->f & SELECT)==0) {tf->flag |= TF_SEL3;} + if((efa->v4 && (efa->v4->f & SELECT)==0)) {tf->flag |= TF_SEL4;} + + efa->f |= SELECT; + } + } + } + + /* Select all edges and verts now */ + for(efa= em->faces.first; efa; efa= efa->next) + /* we only selected the face flags, and didnt changes edges or verts, fix this now */ + if(!(efa->h) && (efa->f & SELECT)) + EM_select_face(efa, 1); + + EM_select_flush(em); + } + } + else if(em->selectmode == SCE_SELECT_FACE) { + for(efa= em->faces.first; efa; efa= efa->next) { + if(!(efa->h) && !(efa->f & SELECT)) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + efa->f |= SELECT; + tf->flag |= TF_SEL1|TF_SEL2|TF_SEL3|TF_SEL4; + } + } + + /* Select all edges and verts now */ + for(efa= em->faces.first; efa; efa= efa->next) + /* we only selected the face flags, and didnt changes edges or verts, fix this now */ + if(!(efa->h) && (efa->f & SELECT)) + EM_select_face(efa, 1); + } + else { + for(efa= em->faces.first; efa; efa= efa->next) { + if(!(efa->h) && !(efa->f & SELECT)) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if((efa->v1->f & SELECT)==0) {tf->flag |= TF_SEL1;} + if((efa->v2->f & SELECT)==0) {tf->flag |= TF_SEL2;} + if((efa->v3->f & SELECT)==0) {tf->flag |= TF_SEL3;} + if((efa->v4 && (efa->v4->f & SELECT)==0)) {tf->flag |= TF_SEL4;} + + efa->f |= SELECT; + } + } + + /* Select all edges and verts now */ + for(efa= em->faces.first; efa; efa= efa->next) + /* we only selected the face flags, and didnt changes edges or verts, fix this now */ + if(!(efa->h) && (efa->f & SELECT)) + EM_select_face(efa, 1); + } + + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_reveal(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Reveal Hidden"; + ot->idname= "UV_OT_reveal"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= reveal_exec; + ot->poll= ED_operator_uvedit; +} + +/******************** set 3d cursor operator ********************/ + +static int set_2d_cursor_exec(bContext *C, wmOperator *op) +{ + ARegion *ar= CTX_wm_region(C); + float location[2]; + + RNA_float_get_array(op->ptr, "location", location); + ar->v2d.cursor[0]= location[0]; + ar->v2d.cursor[1]= location[1]; + + ED_area_tag_redraw(CTX_wm_area(C)); + + return OPERATOR_FINISHED; +} + +static int set_2d_cursor_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + ARegion *ar= CTX_wm_region(C); + int x, y; + float location[2]; + + x= event->x - ar->winrct.xmin; + y= event->y - ar->winrct.ymin; + UI_view2d_region_to_view(&ar->v2d, x, y, &location[0], &location[1]); + RNA_float_set_array(op->ptr, "location", location); + + return set_2d_cursor_exec(C, op); +} + +void UV_OT_cursor_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Set 3D Cursor"; + ot->idname= "UV_OT_cursor_set"; + + /* api callbacks */ + ot->exec= set_2d_cursor_exec; + ot->invoke= set_2d_cursor_invoke; + ot->poll= ED_operator_uvedit; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Cursor location in 0.0-1.0 coordinates.", -10.0f, 10.0f); +} + +/********************** set tile operator **********************/ + +static int set_tile_exec(bContext *C, wmOperator *op) +{ + Image *ima= CTX_data_edit_image(C); + int tile[2]; + + if(!ima || !(ima->tpageflag & IMA_TILES)) + return OPERATOR_CANCELLED; + + RNA_int_get_array(op->ptr, "tile", tile); + ED_uvedit_set_tile(C, CTX_data_scene(C), CTX_data_edit_object(C), ima, tile[0] + ima->xrep*tile[1], 1); + + ED_area_tag_redraw(CTX_wm_area(C)); + + return OPERATOR_FINISHED; +} + +static int set_tile_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C); + Image *ima= CTX_data_edit_image(C); + ARegion *ar= CTX_wm_region(C); + float fx, fy; + int x, y, tile[2]; + + if(!ima || !(ima->tpageflag & IMA_TILES)) + return OPERATOR_CANCELLED; + + x= event->x - ar->winrct.xmin; + y= event->y - ar->winrct.ymin; + UI_view2d_region_to_view(&ar->v2d, x, y, &fx, &fy); + + if(fx>=0.0 && fy>=0.0 && fx<1.0 && fy<1.0) { + fx= fx*ima->xrep; + fy= fy*ima->yrep; + + tile[0]= fx; + tile[1]= fy; + + sima->curtile= tile[1]*ima->xrep + tile[0]; + RNA_int_set_array(op->ptr, "tile", tile); + } + + return set_tile_exec(C, op); +} + +void UV_OT_tile_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Set Tile"; + ot->idname= "UV_OT_tile_set"; + + /* api callbacks */ + ot->exec= set_tile_exec; + ot->invoke= set_tile_invoke; + ot->poll= ED_operator_uvedit; + + /* flags */ + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* properties */ + RNA_def_int_vector(ot->srna, "tile", 2, NULL, 0, INT_MAX, "Tile", "Tile coordinate.", 0, 10); +} + +/* ************************** registration **********************************/ + +void ED_operatortypes_uvedit(void) +{ + WM_operatortype_append(UV_OT_select_all_toggle); + WM_operatortype_append(UV_OT_select_invert); + WM_operatortype_append(UV_OT_select); + WM_operatortype_append(UV_OT_select_loop); + WM_operatortype_append(UV_OT_select_linked); + WM_operatortype_append(UV_OT_unlink_selection); + WM_operatortype_append(UV_OT_select_pinned); + WM_operatortype_append(UV_OT_select_border); + WM_operatortype_append(UV_OT_circle_select); + + WM_operatortype_append(UV_OT_snap_cursor); + WM_operatortype_append(UV_OT_snap_selection); + + WM_operatortype_append(UV_OT_align); + WM_operatortype_append(UV_OT_mirror); + WM_operatortype_append(UV_OT_stitch); + WM_operatortype_append(UV_OT_weld); + WM_operatortype_append(UV_OT_pin); + + WM_operatortype_append(UV_OT_average_islands_scale); + WM_operatortype_append(UV_OT_cube_project); + WM_operatortype_append(UV_OT_cylinder_project); + WM_operatortype_append(UV_OT_from_view); + WM_operatortype_append(UV_OT_mapping_menu); + WM_operatortype_append(UV_OT_minimize_stretch); + WM_operatortype_append(UV_OT_pack_islands); + WM_operatortype_append(UV_OT_reset); + WM_operatortype_append(UV_OT_sphere_project); + WM_operatortype_append(UV_OT_unwrap); + + WM_operatortype_append(UV_OT_reveal); + WM_operatortype_append(UV_OT_hide); + + WM_operatortype_append(UV_OT_cursor_set); + WM_operatortype_append(UV_OT_tile_set); +} + +void ED_keymap_uvedit(wmWindowManager *wm) +{ + ListBase *keymap= WM_keymap_listbase(wm, "UVEdit", 0, 0); + + /* pick selection */ + WM_keymap_add_item(keymap, "UV_OT_select", SELECTMOUSE, KM_PRESS, 0, 0); + RNA_boolean_set(WM_keymap_add_item(keymap, "UV_OT_select", SELECTMOUSE, KM_PRESS, KM_SHIFT, 0)->ptr, "extend", 1); + WM_keymap_add_item(keymap, "UV_OT_select_loop", SELECTMOUSE, KM_PRESS, KM_ALT, 0); + RNA_boolean_set(WM_keymap_add_item(keymap, "UV_OT_select_loop", SELECTMOUSE, KM_PRESS, KM_SHIFT|KM_ALT, 0)->ptr, "extend", 1); + + /* border/circle selection */ + WM_keymap_add_item(keymap, "UV_OT_select_border", BKEY, KM_PRESS, 0, 0); + RNA_boolean_set(WM_keymap_add_item(keymap, "UV_OT_select_border", BKEY, KM_PRESS, KM_SHIFT, 0)->ptr, "pinned", 1); + WM_keymap_add_item(keymap, "UV_OT_circle_select", CKEY, KM_PRESS, 0, 0); + + /* selection manipulation */ + WM_keymap_add_item(keymap, "UV_OT_select_linked", LKEY, KM_PRESS, KM_CTRL, 0); + WM_keymap_add_item(keymap, "UV_OT_unlink_selection", LKEY, KM_PRESS, KM_ALT, 0); + WM_keymap_add_item(keymap, "UV_OT_select_all_toggle", AKEY, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "UV_OT_select_invert", IKEY, KM_PRESS, KM_CTRL, 0); + WM_keymap_add_item(keymap, "UV_OT_select_pinned", PKEY, KM_PRESS, KM_SHIFT, 0); + + /* uv operations */ + WM_keymap_add_item(keymap, "UV_OT_stitch", VKEY, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "UV_OT_pin", PKEY, KM_PRESS, 0, 0); + RNA_boolean_set(WM_keymap_add_item(keymap, "UV_OT_pin", PKEY, KM_PRESS, KM_ALT, 0)->ptr, "clear", 1); + + /* unwrap */ + WM_keymap_add_item(keymap, "UV_OT_unwrap", EKEY, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "UV_OT_minimize_stretch", VKEY, KM_PRESS, KM_CTRL, 0); + WM_keymap_add_item(keymap, "UV_OT_pack_islands", PKEY, KM_PRESS, KM_CTRL, 0); + WM_keymap_add_item(keymap, "UV_OT_average_islands_scale", AKEY, KM_PRESS, KM_CTRL, 0); + + /* hide */ + WM_keymap_add_item(keymap, "UV_OT_hide", HKEY, KM_PRESS, 0, 0); + RNA_boolean_set(WM_keymap_add_item(keymap, "UV_OT_hide", HKEY, KM_PRESS, KM_SHIFT, 0)->ptr, "unselected", 1); + WM_keymap_add_item(keymap, "UV_OT_reveal", HKEY, KM_PRESS, KM_ALT, 0); + + /* cursor */ + WM_keymap_add_item(keymap, "UV_OT_cursor_set", ACTIONMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "UV_OT_tile_set", ACTIONMOUSE, KM_PRESS, KM_SHIFT, 0); + + transform_keymap_for_space(wm, keymap, SPACE_IMAGE); +} + diff --git a/source/blender/editors/uvedit/uvedit_parametrizer.c b/source/blender/editors/uvedit/uvedit_parametrizer.c new file mode 100644 index 00000000000..20f74085e52 --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_parametrizer.c @@ -0,0 +1,4509 @@ + +#include "MEM_guardedalloc.h" + +#include "BLI_memarena.h" +#include "BLI_arithb.h" +#include "BLI_rand.h" +#include "BLI_heap.h" +#include "BLI_boxpack2d.h" + +#include "BKE_utildefines.h" + +#include "ONL_opennl.h" + +#include "uvedit_intern.h" +#include "uvedit_parametrizer.h" + +#include <math.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "BLO_sys_types.h" // for intptr_t support + +#if defined(_WIN32) +#define M_PI 3.14159265358979323846 +#endif + +/* Utils */ + +#if 0 + #define param_assert(condition); + #define param_warning(message); + #define param_test_equals_ptr(condition); + #define param_test_equals_int(condition); +#else + #define param_assert(condition) \ + if (!(condition)) \ + { /*printf("Assertion %s:%d\n", __FILE__, __LINE__); abort();*/ } + #define param_warning(message) \ + { /*printf("Warning %s:%d: %s\n", __FILE__, __LINE__, message);*/ } + #define param_test_equals_ptr(str, a, b) \ + if (a != b) \ + { /*printf("Equals %s => %p != %p\n", str, a, b);*/ }; + #define param_test_equals_int(str, a, b) \ + if (a != b) \ + { /*printf("Equals %s => %d != %d\n", str, a, b);*/ }; +#endif + +typedef enum PBool { + P_TRUE = 1, + P_FALSE = 0 +} PBool; + +/* Special Purpose Hash */ + +typedef intptr_t PHashKey; + +typedef struct PHashLink { + struct PHashLink *next; + PHashKey key; +} PHashLink; + +typedef struct PHash { + PHashLink **list; + PHashLink **buckets; + int size, cursize, cursize_id; +} PHash; + + + +struct PVert; +struct PEdge; +struct PFace; +struct PChart; +struct PHandle; + +/* Simplices */ + +typedef struct PVert { + struct PVert *nextlink; + + union PVertUnion { + PHashKey key; /* construct */ + int id; /* abf/lscm matrix index */ + float distortion; /* area smoothing */ + HeapNode *heaplink; /* edge collapsing */ + } u; + + struct PEdge *edge; + float *co; + float uv[2]; + unsigned char flag; + +} PVert; + +typedef struct PEdge { + struct PEdge *nextlink; + + union PEdgeUnion { + PHashKey key; /* construct */ + int id; /* abf matrix index */ + HeapNode *heaplink; /* fill holes */ + struct PEdge *nextcollapse; /* simplification */ + } u; + + struct PVert *vert; + struct PEdge *pair; + struct PEdge *next; + struct PFace *face; + float *orig_uv, old_uv[2]; + unsigned short flag; + +} PEdge; + +typedef struct PFace { + struct PFace *nextlink; + + union PFaceUnion { + PHashKey key; /* construct */ + int chart; /* construct splitting*/ + float area3d; /* stretch */ + int id; /* abf matrix index */ + } u; + + struct PEdge *edge; + unsigned char flag; + +} PFace; + +enum PVertFlag { + PVERT_PIN = 1, + PVERT_SELECT = 2, + PVERT_INTERIOR = 4, + PVERT_COLLAPSE = 8, + PVERT_SPLIT = 16 +}; + +enum PEdgeFlag { + PEDGE_SEAM = 1, + PEDGE_VERTEX_SPLIT = 2, + PEDGE_PIN = 4, + PEDGE_SELECT = 8, + PEDGE_DONE = 16, + PEDGE_FILLED = 32, + PEDGE_COLLAPSE = 64, + PEDGE_COLLAPSE_EDGE = 128, + PEDGE_COLLAPSE_PAIR = 256 +}; + +/* for flipping faces */ +#define PEDGE_VERTEX_FLAGS (PEDGE_PIN) + +enum PFaceFlag { + PFACE_CONNECTED = 1, + PFACE_FILLED = 2, + PFACE_COLLAPSE = 4 +}; + +/* Chart */ + +typedef struct PChart { + PVert *verts; + PEdge *edges; + PFace *faces; + int nverts, nedges, nfaces; + + PVert *collapsed_verts; + PEdge *collapsed_edges; + PFace *collapsed_faces; + + union PChartUnion { + struct PChartLscm { + NLContext context; + float *abf_alpha; + PVert *pin1, *pin2; + } lscm; + struct PChartPack { + float rescale, area; + float size[2], trans[2]; + } pack; + } u; + + unsigned char flag; + struct PHandle *handle; +} PChart; + +enum PChartFlag { + PCHART_NOPACK = 1 +}; + +enum PHandleState { + PHANDLE_STATE_ALLOCATED, + PHANDLE_STATE_CONSTRUCTED, + PHANDLE_STATE_LSCM, + PHANDLE_STATE_STRETCH +}; + +typedef struct PHandle { + enum PHandleState state; + MemArena *arena; + + PChart *construction_chart; + PHash *hash_verts; + PHash *hash_edges; + PHash *hash_faces; + + PChart **charts; + int ncharts; + + float aspx, aspy; + + RNG *rng; + float blend; +} PHandle; + + +/* PHash + - special purpose hash that keeps all its elements in a single linked list. + - after construction, this hash is thrown away, and the list remains. + - removing elements is not possible efficiently. +*/ + +static int PHashSizes[] = { + 1, 3, 5, 11, 17, 37, 67, 131, 257, 521, 1031, 2053, 4099, 8209, + 16411, 32771, 65537, 131101, 262147, 524309, 1048583, 2097169, + 4194319, 8388617, 16777259, 33554467, 67108879, 134217757, 268435459 +}; + +#define PHASH_hash(ph, item) (((uintptr_t) (item))%((unsigned int) (ph)->cursize)) +#define PHASH_edge(v1, v2) ((v1)^(v2)) + +static PHash *phash_new(PHashLink **list, int sizehint) +{ + PHash *ph = (PHash*)MEM_callocN(sizeof(PHash), "PHash"); + ph->size = 0; + ph->cursize_id = 0; + ph->list = list; + + while (PHashSizes[ph->cursize_id] < sizehint) + ph->cursize_id++; + + ph->cursize = PHashSizes[ph->cursize_id]; + ph->buckets = (PHashLink**)MEM_callocN(ph->cursize*sizeof(*ph->buckets), "PHashBuckets"); + + return ph; +} + +static void phash_delete(PHash *ph) +{ + MEM_freeN(ph->buckets); + MEM_freeN(ph); +} + +static int phash_size(PHash *ph) +{ + return ph->size; +} + +static void phash_insert(PHash *ph, PHashLink *link) +{ + int size = ph->cursize; + int hash = PHASH_hash(ph, link->key); + PHashLink *lookup = ph->buckets[hash]; + + if (lookup == NULL) { + /* insert in front of the list */ + ph->buckets[hash] = link; + link->next = *(ph->list); + *(ph->list) = link; + } + else { + /* insert after existing element */ + link->next = lookup->next; + lookup->next = link; + } + + ph->size++; + + if (ph->size > (size*3)) { + PHashLink *next = NULL, *first = *(ph->list); + + ph->cursize = PHashSizes[++ph->cursize_id]; + MEM_freeN(ph->buckets); + ph->buckets = (PHashLink**)MEM_callocN(ph->cursize*sizeof(*ph->buckets), "PHashBuckets"); + ph->size = 0; + *(ph->list) = NULL; + + for (link = first; link; link = next) { + next = link->next; + phash_insert(ph, link); + } + } +} + +static PHashLink *phash_lookup(PHash *ph, PHashKey key) +{ + PHashLink *link; + int hash = PHASH_hash(ph, key); + + for (link = ph->buckets[hash]; link; link = link->next) + if (link->key == key) + return link; + else if (PHASH_hash(ph, link->key) != hash) + return NULL; + + return link; +} + +static PHashLink *phash_next(PHash *ph, PHashKey key, PHashLink *link) +{ + int hash = PHASH_hash(ph, key); + + for (link = link->next; link; link = link->next) + if (link->key == key) + return link; + else if (PHASH_hash(ph, link->key) != hash) + return NULL; + + return link; +} + +/* Geometry */ + +static float p_vec_angle_cos(float *v1, float *v2, float *v3) +{ + float d1[3], d2[3]; + + d1[0] = v1[0] - v2[0]; + d1[1] = v1[1] - v2[1]; + d1[2] = v1[2] - v2[2]; + + d2[0] = v3[0] - v2[0]; + d2[1] = v3[1] - v2[1]; + d2[2] = v3[2] - v2[2]; + + Normalize(d1); + Normalize(d2); + + return d1[0]*d2[0] + d1[1]*d2[1] + d1[2]*d2[2]; +} + +static float p_vec_angle(float *v1, float *v2, float *v3) +{ + float dot = p_vec_angle_cos(v1, v2, v3); + + if (dot <= -1.0f) + return (float)M_PI; + else if (dot >= 1.0f) + return 0.0f; + else + return (float)acos(dot); +} + +static float p_vec2_angle(float *v1, float *v2, float *v3) +{ + float u1[3], u2[3], u3[3]; + + u1[0] = v1[0]; u1[1] = v1[1]; u1[2] = 0.0f; + u2[0] = v2[0]; u2[1] = v2[1]; u2[2] = 0.0f; + u3[0] = v3[0]; u3[1] = v3[1]; u3[2] = 0.0f; + + return p_vec_angle(u1, u2, u3); +} + +static void p_triangle_angles(float *v1, float *v2, float *v3, float *a1, float *a2, float *a3) +{ + *a1 = p_vec_angle(v3, v1, v2); + *a2 = p_vec_angle(v1, v2, v3); + *a3 = M_PI - *a2 - *a1; +} + +static void p_face_angles(PFace *f, float *a1, float *a2, float *a3) +{ + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; + + p_triangle_angles(v1->co, v2->co, v3->co, a1, a2, a3); +} + +static float p_face_area(PFace *f) +{ + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; + + return AreaT3Dfl(v1->co, v2->co, v3->co); +} + +static float p_area_signed(float *v1, float *v2, float *v3) +{ + return 0.5f*(((v2[0] - v1[0])*(v3[1] - v1[1])) - + ((v3[0] - v1[0])*(v2[1] - v1[1]))); +} + +static float p_face_uv_area_signed(PFace *f) +{ + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; + + return 0.5f*(((v2->uv[0] - v1->uv[0])*(v3->uv[1] - v1->uv[1])) - + ((v3->uv[0] - v1->uv[0])*(v2->uv[1] - v1->uv[1]))); +} + +static float p_edge_length(PEdge *e) +{ + PVert *v1 = e->vert, *v2 = e->next->vert; + float d[3]; + + d[0] = v2->co[0] - v1->co[0]; + d[1] = v2->co[1] - v1->co[1]; + d[2] = v2->co[2] - v1->co[2]; + + return sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]); +} + +static float p_edge_uv_length(PEdge *e) +{ + PVert *v1 = e->vert, *v2 = e->next->vert; + float d[3]; + + d[0] = v2->uv[0] - v1->uv[0]; + d[1] = v2->uv[1] - v1->uv[1]; + + return sqrt(d[0]*d[0] + d[1]*d[1]); +} + +static void p_chart_uv_bbox(PChart *chart, float *minv, float *maxv) +{ + PVert *v; + + INIT_MINMAX2(minv, maxv); + + for (v=chart->verts; v; v=v->nextlink) { + DO_MINMAX2(v->uv, minv, maxv); + } +} + +static void p_chart_uv_scale(PChart *chart, float scale) +{ + PVert *v; + + for (v=chart->verts; v; v=v->nextlink) { + v->uv[0] *= scale; + v->uv[1] *= scale; + } +} + +static void p_chart_uv_scale_xy(PChart *chart, float x, float y) +{ + PVert *v; + + for (v=chart->verts; v; v=v->nextlink) { + v->uv[0] *= x; + v->uv[1] *= y; + } +} + +static void p_chart_uv_translate(PChart *chart, float trans[2]) +{ + PVert *v; + + for (v=chart->verts; v; v=v->nextlink) { + v->uv[0] += trans[0]; + v->uv[1] += trans[1]; + } +} + +static PBool p_intersect_line_2d_dir(float *v1, float *dir1, float *v2, float *dir2, float *isect) +{ + float lmbda, div; + + div= dir2[0]*dir1[1] - dir2[1]*dir1[0]; + + if (div == 0.0f) + return P_FALSE; + + lmbda= ((v1[1]-v2[1])*dir1[0]-(v1[0]-v2[0])*dir1[1])/div; + isect[0] = v1[0] + lmbda*dir2[0]; + isect[1] = v1[1] + lmbda*dir2[1]; + + return P_TRUE; +} + +#if 0 +static PBool p_intersect_line_2d(float *v1, float *v2, float *v3, float *v4, float *isect) +{ + float dir1[2], dir2[2]; + + dir1[0] = v4[0] - v3[0]; + dir1[1] = v4[1] - v3[1]; + + dir2[0] = v2[0] - v1[0]; + dir2[1] = v2[1] - v1[1]; + + if (!p_intersect_line_2d_dir(v1, dir1, v2, dir2, isect)) { + /* parallel - should never happen in theory for polygon kernel, but + let's give a point nearby in case things go wrong */ + isect[0] = (v1[0] + v2[0])*0.5f; + isect[1] = (v1[1] + v2[1])*0.5f; + return P_FALSE; + } + + return P_TRUE; +} +#endif + +/* Topological Utilities */ + +static PEdge *p_wheel_edge_next(PEdge *e) +{ + return e->next->next->pair; +} + +static PEdge *p_wheel_edge_prev(PEdge *e) +{ + return (e->pair)? e->pair->next: NULL; +} + +static PEdge *p_boundary_edge_next(PEdge *e) +{ + return e->next->vert->edge; +} + +static PEdge *p_boundary_edge_prev(PEdge *e) +{ + PEdge *we = e, *last; + + do { + last = we; + we = p_wheel_edge_next(we); + } while (we && (we != e)); + + return last->next->next; +} + +static PBool p_vert_interior(PVert *v) +{ + return (v->edge->pair != NULL); +} + +static void p_face_flip(PFace *f) +{ + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; + int f1 = e1->flag, f2 = e2->flag, f3 = e3->flag; + + e1->vert = v2; + e1->next = e3; + e1->flag = (f1 & ~PEDGE_VERTEX_FLAGS) | (f2 & PEDGE_VERTEX_FLAGS); + + e2->vert = v3; + e2->next = e1; + e2->flag = (f2 & ~PEDGE_VERTEX_FLAGS) | (f3 & PEDGE_VERTEX_FLAGS); + + e3->vert = v1; + e3->next = e2; + e3->flag = (f3 & ~PEDGE_VERTEX_FLAGS) | (f1 & PEDGE_VERTEX_FLAGS); +} + +#if 0 +static void p_chart_topological_sanity_check(PChart *chart) +{ + PVert *v; + PEdge *e; + + for (v=chart->verts; v; v=v->nextlink) + param_test_equals_ptr("v->edge->vert", v, v->edge->vert); + + for (e=chart->edges; e; e=e->nextlink) { + if (e->pair) { + param_test_equals_ptr("e->pair->pair", e, e->pair->pair); + param_test_equals_ptr("pair->vert", e->vert, e->pair->next->vert); + param_test_equals_ptr("pair->next->vert", e->next->vert, e->pair->vert); + } + } +} +#endif + +/* Loading / Flushing */ + +static void p_vert_load_pin_select_uvs(PHandle *handle, PVert *v) +{ + PEdge *e; + int nedges = 0, npins = 0; + float pinuv[2]; + + v->uv[0] = v->uv[1] = 0.0f; + pinuv[0] = pinuv[1] = 0.0f; + e = v->edge; + do { + if (e->orig_uv) { + if (e->flag & PEDGE_SELECT) + v->flag |= PVERT_SELECT; + + if (e->flag & PEDGE_PIN) { + pinuv[0] += e->orig_uv[0]*handle->aspx; + pinuv[1] += e->orig_uv[1]*handle->aspy; + npins++; + } + else { + v->uv[0] += e->orig_uv[0]*handle->aspx; + v->uv[1] += e->orig_uv[1]*handle->aspy; + } + + nedges++; + } + + e = p_wheel_edge_next(e); + } while (e && e != (v->edge)); + + if (npins > 0) { + v->uv[0] = pinuv[0]/npins; + v->uv[1] = pinuv[1]/npins; + v->flag |= PVERT_PIN; + } + else if (nedges > 0) { + v->uv[0] /= nedges; + v->uv[1] /= nedges; + } +} + +static void p_flush_uvs(PHandle *handle, PChart *chart) +{ + PEdge *e; + + for (e=chart->edges; e; e=e->nextlink) { + if (e->orig_uv) { + e->orig_uv[0] = e->vert->uv[0]/handle->aspx; + e->orig_uv[1] = e->vert->uv[1]/handle->aspy; + } + } +} + +static void p_flush_uvs_blend(PHandle *handle, PChart *chart, float blend) +{ + PEdge *e; + float invblend = 1.0f - blend; + + for (e=chart->edges; e; e=e->nextlink) { + if (e->orig_uv) { + e->orig_uv[0] = blend*e->old_uv[0] + invblend*e->vert->uv[0]/handle->aspx; + e->orig_uv[1] = blend*e->old_uv[1] + invblend*e->vert->uv[1]/handle->aspy; + } + } +} + +static void p_face_backup_uvs(PFace *f) +{ + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + + if (e1->orig_uv && e2->orig_uv && e3->orig_uv) { + e1->old_uv[0] = e1->orig_uv[0]; + e1->old_uv[1] = e1->orig_uv[1]; + e2->old_uv[0] = e2->orig_uv[0]; + e2->old_uv[1] = e2->orig_uv[1]; + e3->old_uv[0] = e3->orig_uv[0]; + e3->old_uv[1] = e3->orig_uv[1]; + } +} + +static void p_face_restore_uvs(PFace *f) +{ + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + + if (e1->orig_uv && e2->orig_uv && e3->orig_uv) { + e1->orig_uv[0] = e1->old_uv[0]; + e1->orig_uv[1] = e1->old_uv[1]; + e2->orig_uv[0] = e2->old_uv[0]; + e2->orig_uv[1] = e2->old_uv[1]; + e3->orig_uv[0] = e3->old_uv[0]; + e3->orig_uv[1] = e3->old_uv[1]; + } +} + +/* Construction (use only during construction, relies on u.key being set */ + +static PVert *p_vert_add(PHandle *handle, PHashKey key, float *co, PEdge *e) +{ + PVert *v = (PVert*)BLI_memarena_alloc(handle->arena, sizeof *v); + v->co = co; + v->u.key = key; + v->edge = e; + v->flag = 0; + + phash_insert(handle->hash_verts, (PHashLink*)v); + + return v; +} + +static PVert *p_vert_lookup(PHandle *handle, PHashKey key, float *co, PEdge *e) +{ + PVert *v = (PVert*)phash_lookup(handle->hash_verts, key); + + if (v) + return v; + else + return p_vert_add(handle, key, co, e); +} + +static PVert *p_vert_copy(PChart *chart, PVert *v) +{ + PVert *nv = (PVert*)BLI_memarena_alloc(chart->handle->arena, sizeof *nv); + + nv->co = v->co; + nv->uv[0] = v->uv[0]; + nv->uv[1] = v->uv[1]; + nv->u.key = v->u.key; + nv->edge = v->edge; + nv->flag = v->flag; + + return nv; +} + +static PEdge *p_edge_lookup(PHandle *handle, PHashKey *vkeys) +{ + PHashKey key = PHASH_edge(vkeys[0], vkeys[1]); + PEdge *e = (PEdge*)phash_lookup(handle->hash_edges, key); + + while (e) { + if ((e->vert->u.key == vkeys[0]) && (e->next->vert->u.key == vkeys[1])) + return e; + else if ((e->vert->u.key == vkeys[1]) && (e->next->vert->u.key == vkeys[0])) + return e; + + e = (PEdge*)phash_next(handle->hash_edges, key, (PHashLink*)e); + } + + return NULL; +} + +static PBool p_face_exists(PHandle *handle, PHashKey *vkeys, int i1, int i2, int i3) +{ + PHashKey key = PHASH_edge(vkeys[i1], vkeys[i2]); + PEdge *e = (PEdge*)phash_lookup(handle->hash_edges, key); + + while (e) { + if ((e->vert->u.key == vkeys[i1]) && (e->next->vert->u.key == vkeys[i2])) { + if (e->next->next->vert->u.key == vkeys[i3]) + return P_TRUE; + } + else if ((e->vert->u.key == vkeys[i2]) && (e->next->vert->u.key == vkeys[i1])) { + if (e->next->next->vert->u.key == vkeys[i3]) + return P_TRUE; + } + + e = (PEdge*)phash_next(handle->hash_edges, key, (PHashLink*)e); + } + + return P_FALSE; +} + +static PChart *p_chart_new(PHandle *handle) +{ + PChart *chart = (PChart*)MEM_callocN(sizeof*chart, "PChart"); + chart->handle = handle; + + return chart; +} + +static void p_chart_delete(PChart *chart) +{ + /* the actual links are free by memarena */ + MEM_freeN(chart); +} + +static PBool p_edge_implicit_seam(PEdge *e, PEdge *ep) +{ + float *uv1, *uv2, *uvp1, *uvp2; + float limit[2]; + + limit[0] = 0.00001; + limit[1] = 0.00001; + + uv1 = e->orig_uv; + uv2 = e->next->orig_uv; + + if (e->vert->u.key == ep->vert->u.key) { + uvp1 = ep->orig_uv; + uvp2 = ep->next->orig_uv; + } + else { + uvp1 = ep->next->orig_uv; + uvp2 = ep->orig_uv; + } + + if((fabs(uv1[0]-uvp1[0]) > limit[0]) || (fabs(uv1[1]-uvp1[1]) > limit[1])) { + e->flag |= PEDGE_SEAM; + ep->flag |= PEDGE_SEAM; + return P_TRUE; + } + if((fabs(uv2[0]-uvp2[0]) > limit[0]) || (fabs(uv2[1]-uvp2[1]) > limit[1])) { + e->flag |= PEDGE_SEAM; + ep->flag |= PEDGE_SEAM; + return P_TRUE; + } + + return P_FALSE; +} + +static PBool p_edge_has_pair(PHandle *handle, PEdge *e, PEdge **pair, PBool impl) +{ + PHashKey key; + PEdge *pe; + PVert *v1, *v2; + PHashKey key1 = e->vert->u.key; + PHashKey key2 = e->next->vert->u.key; + + if (e->flag & PEDGE_SEAM) + return P_FALSE; + + key = PHASH_edge(key1, key2); + pe = (PEdge*)phash_lookup(handle->hash_edges, key); + *pair = NULL; + + while (pe) { + if (pe != e) { + v1 = pe->vert; + v2 = pe->next->vert; + + if (((v1->u.key == key1) && (v2->u.key == key2)) || + ((v1->u.key == key2) && (v2->u.key == key1))) { + + /* don't connect seams and t-junctions */ + if ((pe->flag & PEDGE_SEAM) || *pair || + (impl && p_edge_implicit_seam(e, pe))) { + *pair = NULL; + return P_FALSE; + } + + *pair = pe; + } + } + + pe = (PEdge*)phash_next(handle->hash_edges, key, (PHashLink*)pe); + } + + if (*pair && (e->vert == (*pair)->vert)) { + if ((*pair)->next->pair || (*pair)->next->next->pair) { + /* non unfoldable, maybe mobius ring or klein bottle */ + *pair = NULL; + return P_FALSE; + } + } + + return (*pair != NULL); +} + +static PBool p_edge_connect_pair(PHandle *handle, PEdge *e, PEdge ***stack, PBool impl) +{ + PEdge *pair = NULL; + + if(!e->pair && p_edge_has_pair(handle, e, &pair, impl)) { + if (e->vert == pair->vert) + p_face_flip(pair->face); + + e->pair = pair; + pair->pair = e; + + if (!(pair->face->flag & PFACE_CONNECTED)) { + **stack = pair; + (*stack)++; + } + } + + return (e->pair != NULL); +} + +static int p_connect_pairs(PHandle *handle, PBool impl) +{ + PEdge **stackbase = MEM_mallocN(sizeof*stackbase*phash_size(handle->hash_faces), "Pstackbase"); + PEdge **stack = stackbase; + PFace *f, *first; + PEdge *e, *e1, *e2; + PChart *chart = handle->construction_chart; + int ncharts = 0; + + /* connect pairs, count edges, set vertex-edge pointer to a pairless edge */ + for (first=chart->faces; first; first=first->nextlink) { + if (first->flag & PFACE_CONNECTED) + continue; + + *stack = first->edge; + stack++; + + while (stack != stackbase) { + stack--; + e = *stack; + e1 = e->next; + e2 = e1->next; + + f = e->face; + f->flag |= PFACE_CONNECTED; + + /* assign verts to charts so we can sort them later */ + f->u.chart = ncharts; + + if (!p_edge_connect_pair(handle, e, &stack, impl)) + e->vert->edge = e; + if (!p_edge_connect_pair(handle, e1, &stack, impl)) + e1->vert->edge = e1; + if (!p_edge_connect_pair(handle, e2, &stack, impl)) + e2->vert->edge = e2; + } + + ncharts++; + } + + MEM_freeN(stackbase); + + return ncharts; +} + +static void p_split_vert(PChart *chart, PEdge *e) +{ + PEdge *we, *lastwe = NULL; + PVert *v = e->vert; + PBool copy = P_TRUE; + + if (e->flag & PEDGE_VERTEX_SPLIT) + return; + + /* rewind to start */ + lastwe = e; + for (we = p_wheel_edge_prev(e); we && (we != e); we = p_wheel_edge_prev(we)) + lastwe = we; + + /* go over all edges in wheel */ + for (we = lastwe; we; we = p_wheel_edge_next(we)) { + if (we->flag & PEDGE_VERTEX_SPLIT) + break; + + we->flag |= PEDGE_VERTEX_SPLIT; + + if (we == v->edge) { + /* found it, no need to copy */ + copy = P_FALSE; + v->nextlink = chart->verts; + chart->verts = v; + chart->nverts++; + } + } + + if (copy) { + /* not found, copying */ + v->flag |= PVERT_SPLIT; + v = p_vert_copy(chart, v); + v->flag |= PVERT_SPLIT; + + v->nextlink = chart->verts; + chart->verts = v; + chart->nverts++; + + v->edge = lastwe; + + we = lastwe; + do { + we->vert = v; + we = p_wheel_edge_next(we); + } while (we && (we != lastwe)); + } +} + +static PChart **p_split_charts(PHandle *handle, PChart *chart, int ncharts) +{ + PChart **charts = MEM_mallocN(sizeof*charts * ncharts, "PCharts"), *nchart; + PFace *f, *nextf; + int i; + + for (i = 0; i < ncharts; i++) + charts[i] = p_chart_new(handle); + + f = chart->faces; + while (f) { + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + nextf = f->nextlink; + + nchart = charts[f->u.chart]; + + f->nextlink = nchart->faces; + nchart->faces = f; + e1->nextlink = nchart->edges; + nchart->edges = e1; + e2->nextlink = nchart->edges; + nchart->edges = e2; + e3->nextlink = nchart->edges; + nchart->edges = e3; + + nchart->nfaces++; + nchart->nedges += 3; + + p_split_vert(nchart, e1); + p_split_vert(nchart, e2); + p_split_vert(nchart, e3); + + f = nextf; + } + + return charts; +} + +static PFace *p_face_add(PHandle *handle) +{ + PFace *f; + PEdge *e1, *e2, *e3; + + /* allocate */ + f = (PFace*)BLI_memarena_alloc(handle->arena, sizeof *f); + f->flag=0; // init ! + + e1 = (PEdge*)BLI_memarena_alloc(handle->arena, sizeof *e1); + e2 = (PEdge*)BLI_memarena_alloc(handle->arena, sizeof *e2); + e3 = (PEdge*)BLI_memarena_alloc(handle->arena, sizeof *e3); + + /* set up edges */ + f->edge = e1; + e1->face = e2->face = e3->face = f; + + e1->next = e2; + e2->next = e3; + e3->next = e1; + + e1->pair = NULL; + e2->pair = NULL; + e3->pair = NULL; + + e1->flag =0; + e2->flag =0; + e3->flag =0; + + return f; +} + +static PFace *p_face_add_construct(PHandle *handle, ParamKey key, ParamKey *vkeys, + float *co[3], float *uv[3], int i1, int i2, int i3, + ParamBool *pin, ParamBool *select) +{ + PFace *f = p_face_add(handle); + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + + e1->vert = p_vert_lookup(handle, vkeys[i1], co[i1], e1); + e2->vert = p_vert_lookup(handle, vkeys[i2], co[i2], e2); + e3->vert = p_vert_lookup(handle, vkeys[i3], co[i3], e3); + + e1->orig_uv = uv[i1]; + e2->orig_uv = uv[i2]; + e3->orig_uv = uv[i3]; + + if (pin) { + if (pin[i1]) e1->flag |= PEDGE_PIN; + if (pin[i2]) e2->flag |= PEDGE_PIN; + if (pin[i3]) e3->flag |= PEDGE_PIN; + } + + if (select) { + if (select[i1]) e1->flag |= PEDGE_SELECT; + if (select[i2]) e2->flag |= PEDGE_SELECT; + if (select[i3]) e3->flag |= PEDGE_SELECT; + } + + /* insert into hash */ + f->u.key = key; + phash_insert(handle->hash_faces, (PHashLink*)f); + + e1->u.key = PHASH_edge(vkeys[i1], vkeys[i2]); + e2->u.key = PHASH_edge(vkeys[i2], vkeys[i3]); + e3->u.key = PHASH_edge(vkeys[i3], vkeys[i1]); + + phash_insert(handle->hash_edges, (PHashLink*)e1); + phash_insert(handle->hash_edges, (PHashLink*)e2); + phash_insert(handle->hash_edges, (PHashLink*)e3); + + return f; +} + +static PFace *p_face_add_fill(PChart *chart, PVert *v1, PVert *v2, PVert *v3) +{ + PFace *f = p_face_add(chart->handle); + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + + e1->vert = v1; + e2->vert = v2; + e3->vert = v3; + + e1->orig_uv = e2->orig_uv = e3->orig_uv = NULL; + + f->nextlink = chart->faces; + chart->faces = f; + e1->nextlink = chart->edges; + chart->edges = e1; + e2->nextlink = chart->edges; + chart->edges = e2; + e3->nextlink = chart->edges; + chart->edges = e3; + + chart->nfaces++; + chart->nedges += 3; + + return f; +} + +static PBool p_quad_split_direction(PHandle *handle, float **co, PHashKey *vkeys) +{ + float fac= VecLenf(co[0], co[2]) - VecLenf(co[1], co[3]); + PBool dir = (fac <= 0.0f); + + /* the face exists check is there because of a special case: when + two quads share three vertices, they can each be split into two + triangles, resulting in two identical triangles. for example in + suzanne's nose. */ + if (dir) { + if (p_face_exists(handle,vkeys,0,1,2) || p_face_exists(handle,vkeys,0,2,3)) + return !dir; + } + else { + if (p_face_exists(handle,vkeys,0,1,3) || p_face_exists(handle,vkeys,1,2,3)) + return !dir; + } + + return dir; +} + +/* Construction: boundary filling */ + +static void p_chart_boundaries(PChart *chart, int *nboundaries, PEdge **outer) +{ + PEdge *e, *be; + float len, maxlen = -1.0; + + if (nboundaries) + *nboundaries = 0; + if (outer) + *outer = NULL; + + for (e=chart->edges; e; e=e->nextlink) { + if (e->pair || (e->flag & PEDGE_DONE)) + continue; + + if (nboundaries) + (*nboundaries)++; + + len = 0.0f; + + be = e; + do { + be->flag |= PEDGE_DONE; + len += p_edge_length(be); + be = be->next->vert->edge; + } while(be != e); + + if (outer && (len > maxlen)) { + *outer = e; + maxlen = len; + } + } + + for (e=chart->edges; e; e=e->nextlink) + e->flag &= ~PEDGE_DONE; +} + +static float p_edge_boundary_angle(PEdge *e) +{ + PEdge *we; + PVert *v, *v1, *v2; + float angle; + int n = 0; + + v = e->vert; + + /* concave angle check -- could be better */ + angle = M_PI; + + we = v->edge; + do { + v1 = we->next->vert; + v2 = we->next->next->vert; + angle -= p_vec_angle(v1->co, v->co, v2->co); + + we = we->next->next->pair; + n++; + } while (we && (we != v->edge)); + + return angle; +} + +static void p_chart_fill_boundary(PChart *chart, PEdge *be, int nedges) +{ + PEdge *e, *e1, *e2; + + PFace *f; + struct Heap *heap = BLI_heap_new(); + float angle; + + e = be; + do { + angle = p_edge_boundary_angle(e); + e->u.heaplink = BLI_heap_insert(heap, angle, e); + + e = p_boundary_edge_next(e); + } while(e != be); + + if (nedges == 2) { + /* no real boundary, but an isolated seam */ + e = be->next->vert->edge; + e->pair = be; + be->pair = e; + + BLI_heap_remove(heap, e->u.heaplink); + BLI_heap_remove(heap, be->u.heaplink); + } + else { + while (nedges > 2) { + PEdge *ne, *ne1, *ne2; + + e = (PEdge*)BLI_heap_popmin(heap); + + e1 = p_boundary_edge_prev(e); + e2 = p_boundary_edge_next(e); + + BLI_heap_remove(heap, e1->u.heaplink); + BLI_heap_remove(heap, e2->u.heaplink); + e->u.heaplink = e1->u.heaplink = e2->u.heaplink = NULL; + + e->flag |= PEDGE_FILLED; + e1->flag |= PEDGE_FILLED; + + + + + + f = p_face_add_fill(chart, e->vert, e1->vert, e2->vert); + f->flag |= PFACE_FILLED; + + ne = f->edge->next->next; + ne1 = f->edge; + ne2 = f->edge->next; + + ne->flag = ne1->flag = ne2->flag = PEDGE_FILLED; + + e->pair = ne; + ne->pair = e; + e1->pair = ne1; + ne1->pair = e1; + + ne->vert = e2->vert; + ne1->vert = e->vert; + ne2->vert = e1->vert; + + if (nedges == 3) { + e2->pair = ne2; + ne2->pair = e2; + } + else { + ne2->vert->edge = ne2; + + ne2->u.heaplink = BLI_heap_insert(heap, p_edge_boundary_angle(ne2), ne2); + e2->u.heaplink = BLI_heap_insert(heap, p_edge_boundary_angle(e2), e2); + } + + nedges--; + } + } + + BLI_heap_free(heap, NULL); +} + +static void p_chart_fill_boundaries(PChart *chart, PEdge *outer) +{ + PEdge *e, *be; /* *enext - as yet unused */ + int nedges; + + for (e=chart->edges; e; e=e->nextlink) { + /* enext = e->nextlink; - as yet unused */ + + if (e->pair || (e->flag & PEDGE_FILLED)) + continue; + + nedges = 0; + be = e; + do { + be->flag |= PEDGE_FILLED; + be = be->next->vert->edge; + nedges++; + } while(be != e); + + if (e != outer) + p_chart_fill_boundary(chart, e, nedges); + } +} + +#if 0 +/* Polygon kernel for inserting uv's non overlapping */ + +static int p_polygon_point_in(float *cp1, float *cp2, float *p) +{ + if ((cp1[0] == p[0]) && (cp1[1] == p[1])) + return 2; + else if ((cp2[0] == p[0]) && (cp2[1] == p[1])) + return 3; + else + return (p_area_signed(cp1, cp2, p) >= 0.0f); +} + +static void p_polygon_kernel_clip(float (*oldpoints)[2], int noldpoints, float (*newpoints)[2], int *nnewpoints, float *cp1, float *cp2) +{ + float *p2, *p1, isect[2]; + int i, p2in, p1in; + + p1 = oldpoints[noldpoints-1]; + p1in = p_polygon_point_in(cp1, cp2, p1); + *nnewpoints = 0; + + for (i = 0; i < noldpoints; i++) { + p2 = oldpoints[i]; + p2in = p_polygon_point_in(cp1, cp2, p2); + + if ((p2in >= 2) || (p1in && p2in)) { + newpoints[*nnewpoints][0] = p2[0]; + newpoints[*nnewpoints][1] = p2[1]; + (*nnewpoints)++; + } + else if (p1in && !p2in) { + if (p1in != 3) { + p_intersect_line_2d(p1, p2, cp1, cp2, isect); + newpoints[*nnewpoints][0] = isect[0]; + newpoints[*nnewpoints][1] = isect[1]; + (*nnewpoints)++; + } + } + else if (!p1in && p2in) { + p_intersect_line_2d(p1, p2, cp1, cp2, isect); + newpoints[*nnewpoints][0] = isect[0]; + newpoints[*nnewpoints][1] = isect[1]; + (*nnewpoints)++; + + newpoints[*nnewpoints][0] = p2[0]; + newpoints[*nnewpoints][1] = p2[1]; + (*nnewpoints)++; + } + + p1in = p2in; + p1 = p2; + } +} + +static void p_polygon_kernel_center(float (*points)[2], int npoints, float *center) +{ + int i, size, nnewpoints = npoints; + float (*oldpoints)[2], (*newpoints)[2], *p1, *p2; + + size = npoints*3; + oldpoints = MEM_mallocN(sizeof(float)*2*size, "PPolygonOldPoints"); + newpoints = MEM_mallocN(sizeof(float)*2*size, "PPolygonNewPoints"); + + memcpy(oldpoints, points, sizeof(float)*2*npoints); + + for (i = 0; i < npoints; i++) { + p1 = points[i]; + p2 = points[(i+1)%npoints]; + p_polygon_kernel_clip(oldpoints, nnewpoints, newpoints, &nnewpoints, p1, p2); + + if (nnewpoints == 0) { + /* degenerate case, use center of original polygon */ + memcpy(oldpoints, points, sizeof(float)*2*npoints); + nnewpoints = npoints; + break; + } + else if (nnewpoints == 1) { + /* degenerate case, use remaining point */ + center[0] = newpoints[0][0]; + center[1] = newpoints[0][1]; + + MEM_freeN(oldpoints); + MEM_freeN(newpoints); + + return; + } + + if (nnewpoints*2 > size) { + size *= 2; + free(oldpoints); + oldpoints = malloc(sizeof(float)*2*size); + memcpy(oldpoints, newpoints, sizeof(float)*2*nnewpoints); + free(newpoints); + newpoints = malloc(sizeof(float)*2*size); + } + else { + float (*sw_points)[2] = oldpoints; + oldpoints = newpoints; + newpoints = sw_points; + } + } + + center[0] = center[1] = 0.0f; + + for (i = 0; i < nnewpoints; i++) { + center[0] += oldpoints[i][0]; + center[1] += oldpoints[i][1]; + } + + center[0] /= nnewpoints; + center[1] /= nnewpoints; + + MEM_freeN(oldpoints); + MEM_freeN(newpoints); +} +#endif + +#if 0 +/* Edge Collapser */ + +int NCOLLAPSE = 1; +int NCOLLAPSEX = 0; + +static float p_vert_cotan(float *v1, float *v2, float *v3) +{ + float a[3], b[3], c[3], clen; + + VecSubf(a, v2, v1); + VecSubf(b, v3, v1); + Crossf(c, a, b); + + clen = VecLength(c); + + if (clen == 0.0f) + return 0.0f; + + return Inpf(a, b)/clen; +} + +static PBool p_vert_flipped_wheel_triangle(PVert *v) +{ + PEdge *e = v->edge; + + do { + if (p_face_uv_area_signed(e->face) < 0.0f) + return P_TRUE; + + e = p_wheel_edge_next(e); + } while (e && (e != v->edge)); + + return P_FALSE; +} + +static PBool p_vert_map_harmonic_weights(PVert *v) +{ + float weightsum, positionsum[2], olduv[2]; + + weightsum = 0.0f; + positionsum[0] = positionsum[1] = 0.0f; + + if (p_vert_interior(v)) { + PEdge *e = v->edge; + + do { + float t1, t2, weight; + PVert *v1, *v2; + + v1 = e->next->vert; + v2 = e->next->next->vert; + t1 = p_vert_cotan(v2->co, e->vert->co, v1->co); + + v1 = e->pair->next->vert; + v2 = e->pair->next->next->vert; + t2 = p_vert_cotan(v2->co, e->pair->vert->co, v1->co); + + weight = 0.5f*(t1 + t2); + weightsum += weight; + positionsum[0] += weight*e->pair->vert->uv[0]; + positionsum[1] += weight*e->pair->vert->uv[1]; + + e = p_wheel_edge_next(e); + } while (e && (e != v->edge)); + } + else { + PEdge *e = v->edge; + + do { + float t1, t2; + PVert *v1, *v2; + + v2 = e->next->vert; + v1 = e->next->next->vert; + + t1 = p_vert_cotan(v1->co, v->co, v2->co); + t2 = p_vert_cotan(v2->co, v->co, v1->co); + + weightsum += t1 + t2; + positionsum[0] += (v2->uv[1] - v1->uv[1]) + (t1*v2->uv[0] + t2*v1->uv[0]); + positionsum[1] += (v1->uv[0] - v2->uv[0]) + (t1*v2->uv[1] + t2*v1->uv[1]); + + e = p_wheel_edge_next(e); + } while (e && (e != v->edge)); + } + + if (weightsum != 0.0f) { + weightsum = 1.0f/weightsum; + positionsum[0] *= weightsum; + positionsum[1] *= weightsum; + } + + olduv[0] = v->uv[0]; + olduv[1] = v->uv[1]; + v->uv[0] = positionsum[0]; + v->uv[1] = positionsum[1]; + + if (p_vert_flipped_wheel_triangle(v)) { + v->uv[0] = olduv[0]; + v->uv[1] = olduv[1]; + + return P_FALSE; + } + + return P_TRUE; +} + +static void p_vert_harmonic_insert(PVert *v) +{ + PEdge *e; + + if (!p_vert_map_harmonic_weights(v)) { + /* do polygon kernel center insertion: this is quite slow, but should + only be needed for 0.01 % of verts or so, when insert with harmonic + weights fails */ + + int npoints = 0, i; + float (*points)[2]; + + e = v->edge; + do { + npoints++; + e = p_wheel_edge_next(e); + } while (e && (e != v->edge)); + + if (e == NULL) + npoints++; + + points = MEM_mallocN(sizeof(float)*2*npoints, "PHarmonicPoints"); + + e = v->edge; + i = 0; + do { + PEdge *nexte = p_wheel_edge_next(e); + + points[i][0] = e->next->vert->uv[0]; + points[i][1] = e->next->vert->uv[1]; + + if (nexte == NULL) { + i++; + points[i][0] = e->next->next->vert->uv[0]; + points[i][1] = e->next->next->vert->uv[1]; + break; + } + + e = nexte; + i++; + } while (e != v->edge); + + p_polygon_kernel_center(points, npoints, v->uv); + + MEM_freeN(points); + } + + e = v->edge; + do { + if (!(e->next->vert->flag & PVERT_PIN)) + p_vert_map_harmonic_weights(e->next->vert); + e = p_wheel_edge_next(e); + } while (e && (e != v->edge)); + + p_vert_map_harmonic_weights(v); +} + +static void p_vert_fix_edge_pointer(PVert *v) +{ + PEdge *start = v->edge; + + /* set v->edge pointer to the edge with no pair, if there is one */ + while (v->edge->pair) { + v->edge = p_wheel_edge_prev(v->edge); + + if (v->edge == start) + break; + } +} + +static void p_collapsing_verts(PEdge *edge, PEdge *pair, PVert **newv, PVert **keepv) +{ + /* the two vertices that are involved in the collapse */ + if (edge) { + *newv = edge->vert; + *keepv = edge->next->vert; + } + else { + *newv = pair->next->vert; + *keepv = pair->vert; + } +} + +static void p_collapse_edge(PEdge *edge, PEdge *pair) +{ + PVert *oldv, *keepv; + PEdge *e; + + p_collapsing_verts(edge, pair, &oldv, &keepv); + + /* change e->vert pointers from old vertex to the target vertex */ + e = oldv->edge; + do { + if ((e != edge) && !(pair && pair->next == e)) + e->vert = keepv; + + e = p_wheel_edge_next(e); + } while (e && (e != oldv->edge)); + + /* set keepv->edge pointer */ + if ((edge && (keepv->edge == edge->next)) || (keepv->edge == pair)) { + if (edge && edge->next->pair) + keepv->edge = edge->next->pair->next; + else if (pair && pair->next->next->pair) + keepv->edge = pair->next->next->pair; + else if (edge && edge->next->next->pair) + keepv->edge = edge->next->next->pair; + else + keepv->edge = pair->next->pair->next; + } + + /* update pairs and v->edge pointers */ + if (edge) { + PEdge *e1 = edge->next, *e2 = e1->next; + + if (e1->pair) + e1->pair->pair = e2->pair; + + if (e2->pair) { + e2->pair->pair = e1->pair; + e2->vert->edge = p_wheel_edge_prev(e2); + } + else + e2->vert->edge = p_wheel_edge_next(e2); + + p_vert_fix_edge_pointer(e2->vert); + } + + if (pair) { + PEdge *e1 = pair->next, *e2 = e1->next; + + if (e1->pair) + e1->pair->pair = e2->pair; + + if (e2->pair) { + e2->pair->pair = e1->pair; + e2->vert->edge = p_wheel_edge_prev(e2); + } + else + e2->vert->edge = p_wheel_edge_next(e2); + + p_vert_fix_edge_pointer(e2->vert); + } + + p_vert_fix_edge_pointer(keepv); + + /* mark for move to collapsed list later */ + oldv->flag |= PVERT_COLLAPSE; + + if (edge) { + PFace *f = edge->face; + PEdge *e1 = edge->next, *e2 = e1->next; + + f->flag |= PFACE_COLLAPSE; + edge->flag |= PEDGE_COLLAPSE; + e1->flag |= PEDGE_COLLAPSE; + e2->flag |= PEDGE_COLLAPSE; + } + + if (pair) { + PFace *f = pair->face; + PEdge *e1 = pair->next, *e2 = e1->next; + + f->flag |= PFACE_COLLAPSE; + pair->flag |= PEDGE_COLLAPSE; + e1->flag |= PEDGE_COLLAPSE; + e2->flag |= PEDGE_COLLAPSE; + } +} + +static void p_split_vertex(PEdge *edge, PEdge *pair) +{ + PVert *newv, *keepv; + PEdge *e; + + p_collapsing_verts(edge, pair, &newv, &keepv); + + /* update edge pairs */ + if (edge) { + PEdge *e1 = edge->next, *e2 = e1->next; + + if (e1->pair) + e1->pair->pair = e1; + if (e2->pair) + e2->pair->pair = e2; + + e2->vert->edge = e2; + p_vert_fix_edge_pointer(e2->vert); + keepv->edge = e1; + } + + if (pair) { + PEdge *e1 = pair->next, *e2 = e1->next; + + if (e1->pair) + e1->pair->pair = e1; + if (e2->pair) + e2->pair->pair = e2; + + e2->vert->edge = e2; + p_vert_fix_edge_pointer(e2->vert); + keepv->edge = pair; + } + + p_vert_fix_edge_pointer(keepv); + + /* set e->vert pointers to restored vertex */ + e = newv->edge; + do { + e->vert = newv; + e = p_wheel_edge_next(e); + } while (e && (e != newv->edge)); +} + +static PBool p_collapse_allowed_topologic(PEdge *edge, PEdge *pair) +{ + PVert *oldv, *keepv; + + p_collapsing_verts(edge, pair, &oldv, &keepv); + + /* boundary edges */ + if (!edge || !pair) { + /* avoid collapsing chart into an edge */ + if (edge && !edge->next->pair && !edge->next->next->pair) + return P_FALSE; + else if (pair && !pair->next->pair && !pair->next->next->pair) + return P_FALSE; + } + /* avoid merging two boundaries (oldv and keepv are on the 'other side' of + the chart) */ + else if (!p_vert_interior(oldv) && !p_vert_interior(keepv)) + return P_FALSE; + + return P_TRUE; +} + +static PBool p_collapse_normal_flipped(float *v1, float *v2, float *vold, float *vnew) +{ + float nold[3], nnew[3], sub1[3], sub2[3]; + + VecSubf(sub1, vold, v1); + VecSubf(sub2, vold, v2); + Crossf(nold, sub1, sub2); + + VecSubf(sub1, vnew, v1); + VecSubf(sub2, vnew, v2); + Crossf(nnew, sub1, sub2); + + return (Inpf(nold, nnew) <= 0.0f); +} + +static PBool p_collapse_allowed_geometric(PEdge *edge, PEdge *pair) +{ + PVert *oldv, *keepv; + PEdge *e; + float angulardefect, angle; + + p_collapsing_verts(edge, pair, &oldv, &keepv); + + angulardefect = 2*M_PI; + + e = oldv->edge; + do { + float a[3], b[3], minangle, maxangle; + PEdge *e1 = e->next, *e2 = e1->next; + PVert *v1 = e1->vert, *v2 = e2->vert; + int i; + + angle = p_vec_angle(v1->co, oldv->co, v2->co); + angulardefect -= angle; + + /* skip collapsing faces */ + if (v1 == keepv || v2 == keepv) { + e = p_wheel_edge_next(e); + continue; + } + + if (p_collapse_normal_flipped(v1->co, v2->co, oldv->co, keepv->co)) + return P_FALSE; + + a[0] = angle; + a[1] = p_vec_angle(v2->co, v1->co, oldv->co); + a[2] = M_PI - a[0] - a[1]; + + b[0] = p_vec_angle(v1->co, keepv->co, v2->co); + b[1] = p_vec_angle(v2->co, v1->co, keepv->co); + b[2] = M_PI - b[0] - b[1]; + + /* abf criterion 1: avoid sharp and obtuse angles */ + minangle = 15.0f*M_PI/180.0f; + maxangle = M_PI - minangle; + + for (i = 0; i < 3; i++) { + if ((b[i] < a[i]) && (b[i] < minangle)) + return P_FALSE; + else if ((b[i] > a[i]) && (b[i] > maxangle)) + return P_FALSE; + } + + e = p_wheel_edge_next(e); + } while (e && (e != oldv->edge)); + + if (p_vert_interior(oldv)) { + /* hlscm criterion: angular defect smaller than threshold */ + if (fabs(angulardefect) > (M_PI*30.0/180.0)) + return P_FALSE; + } + else { + PVert *v1 = p_boundary_edge_next(oldv->edge)->vert; + PVert *v2 = p_boundary_edge_prev(oldv->edge)->vert; + + /* abf++ criterion 2: avoid collapsing verts inwards */ + if (p_vert_interior(keepv)) + return P_FALSE; + + /* don't collapse significant boundary changes */ + angle = p_vec_angle(v1->co, oldv->co, v2->co); + if (angle < (M_PI*160.0/180.0)) + return P_FALSE; + } + + return P_TRUE; +} + +static PBool p_collapse_allowed(PEdge *edge, PEdge *pair) +{ + PVert *oldv, *keepv; + + p_collapsing_verts(edge, pair, &oldv, &keepv); + + if (oldv->flag & PVERT_PIN) + return P_FALSE; + + return (p_collapse_allowed_topologic(edge, pair) && + p_collapse_allowed_geometric(edge, pair)); +} + +static float p_collapse_cost(PEdge *edge, PEdge *pair) +{ + /* based on volume and boundary optimization from: + "Fast and Memory Efficient Polygonal Simplification" P. Lindstrom, G. Turk */ + + PVert *oldv, *keepv; + PEdge *e; + PFace *oldf1, *oldf2; + float volumecost = 0.0f, areacost = 0.0f, edgevec[3], cost, weight, elen; + float shapecost = 0.0f; + float shapeold = 0.0f, shapenew = 0.0f; + int nshapeold = 0, nshapenew = 0; + + p_collapsing_verts(edge, pair, &oldv, &keepv); + oldf1 = (edge)? edge->face: NULL; + oldf2 = (pair)? pair->face: NULL; + + VecSubf(edgevec, keepv->co, oldv->co); + + e = oldv->edge; + do { + float a1, a2, a3; + float *co1 = e->next->vert->co; + float *co2 = e->next->next->vert->co; + + if ((e->face != oldf1) && (e->face != oldf2)) { + float tetrav2[3], tetrav3[3], c[3]; + + /* tetrahedron volume = (1/3!)*|a.(b x c)| */ + VecSubf(tetrav2, co1, oldv->co); + VecSubf(tetrav3, co2, oldv->co); + Crossf(c, tetrav2, tetrav3); + + volumecost += fabs(Inpf(edgevec, c)/6.0f); +#if 0 + shapecost += Inpf(co1, keepv->co); + + if (p_wheel_edge_next(e) == NULL) + shapecost += Inpf(co2, keepv->co); +#endif + + p_triangle_angles(oldv->co, co1, co2, &a1, &a2, &a3); + a1 = a1 - M_PI/3.0; + a2 = a2 - M_PI/3.0; + a3 = a3 - M_PI/3.0; + shapeold = (a1*a1 + a2*a2 + a3*a3)/((M_PI/2)*(M_PI/2)); + + nshapeold++; + } + else { + p_triangle_angles(keepv->co, co1, co2, &a1, &a2, &a3); + a1 = a1 - M_PI/3.0; + a2 = a2 - M_PI/3.0; + a3 = a3 - M_PI/3.0; + shapenew = (a1*a1 + a2*a2 + a3*a3)/((M_PI/2)*(M_PI/2)); + + nshapenew++; + } + + e = p_wheel_edge_next(e); + } while (e && (e != oldv->edge)); + + if (!p_vert_interior(oldv)) { + PVert *v1 = p_boundary_edge_prev(oldv->edge)->vert; + PVert *v2 = p_boundary_edge_next(oldv->edge)->vert; + + areacost = AreaT3Dfl(oldv->co, v1->co, v2->co); + } + + elen = VecLength(edgevec); + weight = 1.0f; /* 0.2f */ + cost = weight*volumecost*volumecost + elen*elen*areacost*areacost; +#if 0 + cost += shapecost; +#else + shapeold /= nshapeold; + shapenew /= nshapenew; + shapecost = (shapeold + 0.00001)/(shapenew + 0.00001); + + cost *= shapecost; +#endif + + return cost; +} + +static void p_collapse_cost_vertex(PVert *vert, float *mincost, PEdge **mine) +{ + PEdge *e, *enext, *pair; + + *mine = NULL; + *mincost = 0.0f; + e = vert->edge; + do { + if (p_collapse_allowed(e, e->pair)) { + float cost = p_collapse_cost(e, e->pair); + + if ((*mine == NULL) || (cost < *mincost)) { + *mincost = cost; + *mine = e; + } + } + + enext = p_wheel_edge_next(e); + + if (enext == NULL) { + /* the other boundary edge, where we only have the pair halfedge */ + pair = e->next->next; + + if (p_collapse_allowed(NULL, pair)) { + float cost = p_collapse_cost(NULL, pair); + + if ((*mine == NULL) || (cost < *mincost)) { + *mincost = cost; + *mine = pair; + } + } + + break; + } + + e = enext; + } while (e != vert->edge); +} + +static void p_chart_post_collapse_flush(PChart *chart, PEdge *collapsed) +{ + /* move to collapsed_ */ + + PVert *v, *nextv = NULL, *verts = chart->verts; + PEdge *e, *nexte = NULL, *edges = chart->edges, *laste = NULL; + PFace *f, *nextf = NULL, *faces = chart->faces; + + chart->verts = chart->collapsed_verts = NULL; + chart->edges = chart->collapsed_edges = NULL; + chart->faces = chart->collapsed_faces = NULL; + + chart->nverts = chart->nedges = chart->nfaces = 0; + + for (v=verts; v; v=nextv) { + nextv = v->nextlink; + + if (v->flag & PVERT_COLLAPSE) { + v->nextlink = chart->collapsed_verts; + chart->collapsed_verts = v; + } + else { + v->nextlink = chart->verts; + chart->verts = v; + chart->nverts++; + } + } + + for (e=edges; e; e=nexte) { + nexte = e->nextlink; + + if (!collapsed || !(e->flag & PEDGE_COLLAPSE_EDGE)) { + if (e->flag & PEDGE_COLLAPSE) { + e->nextlink = chart->collapsed_edges; + chart->collapsed_edges = e; + } + else { + e->nextlink = chart->edges; + chart->edges = e; + chart->nedges++; + } + } + } + + /* these are added last so they can be popped of in the right order + for splitting */ + for (e=collapsed; e; e=e->nextlink) { + e->nextlink = e->u.nextcollapse; + laste = e; + } + if (laste) { + laste->nextlink = chart->collapsed_edges; + chart->collapsed_edges = collapsed; + } + + for (f=faces; f; f=nextf) { + nextf = f->nextlink; + + if (f->flag & PFACE_COLLAPSE) { + f->nextlink = chart->collapsed_faces; + chart->collapsed_faces = f; + } + else { + f->nextlink = chart->faces; + chart->faces = f; + chart->nfaces++; + } + } +} + +static void p_chart_post_split_flush(PChart *chart) +{ + /* move from collapsed_ */ + + PVert *v, *nextv = NULL; + PEdge *e, *nexte = NULL; + PFace *f, *nextf = NULL; + + for (v=chart->collapsed_verts; v; v=nextv) { + nextv = v->nextlink; + v->nextlink = chart->verts; + chart->verts = v; + chart->nverts++; + } + + for (e=chart->collapsed_edges; e; e=nexte) { + nexte = e->nextlink; + e->nextlink = chart->edges; + chart->edges = e; + chart->nedges++; + } + + for (f=chart->collapsed_faces; f; f=nextf) { + nextf = f->nextlink; + f->nextlink = chart->faces; + chart->faces = f; + chart->nfaces++; + } + + chart->collapsed_verts = NULL; + chart->collapsed_edges = NULL; + chart->collapsed_faces = NULL; +} + +static void p_chart_simplify_compute(PChart *chart) +{ + /* Computes a list of edge collapses / vertex splits. The collapsed + simplices go in the chart->collapsed_* lists, The original and + collapsed may then be view as stacks, where the next collapse/split + is at the top of the respective lists. */ + + Heap *heap = BLI_heap_new(); + PVert *v, **wheelverts; + PEdge *collapsededges = NULL, *e; + int nwheelverts, i, ncollapsed = 0; + + wheelverts = MEM_mallocN(sizeof(PVert*)*chart->nverts, "PChartWheelVerts"); + + /* insert all potential collapses into heap */ + for (v=chart->verts; v; v=v->nextlink) { + float cost; + PEdge *e = NULL; + + p_collapse_cost_vertex(v, &cost, &e); + + if (e) + v->u.heaplink = BLI_heap_insert(heap, cost, e); + else + v->u.heaplink = NULL; + } + + for (e=chart->edges; e; e=e->nextlink) + e->u.nextcollapse = NULL; + + /* pop edge collapse out of heap one by one */ + while (!BLI_heap_empty(heap)) { + if (ncollapsed == NCOLLAPSE) + break; + + HeapNode *link = BLI_heap_top(heap); + PEdge *edge = (PEdge*)BLI_heap_popmin(heap), *pair = edge->pair; + PVert *oldv, *keepv; + PEdge *wheele, *nexte; + + /* remember the edges we collapsed */ + edge->u.nextcollapse = collapsededges; + collapsededges = edge; + + if (edge->vert->u.heaplink != link) { + edge->flag |= (PEDGE_COLLAPSE_EDGE|PEDGE_COLLAPSE_PAIR); + edge->next->vert->u.heaplink = NULL; + SWAP(PEdge*, edge, pair); + } + else { + edge->flag |= PEDGE_COLLAPSE_EDGE; + edge->vert->u.heaplink = NULL; + } + + p_collapsing_verts(edge, pair, &oldv, &keepv); + + /* gather all wheel verts and remember them before collapse */ + nwheelverts = 0; + wheele = oldv->edge; + + do { + wheelverts[nwheelverts++] = wheele->next->vert; + nexte = p_wheel_edge_next(wheele); + + if (nexte == NULL) + wheelverts[nwheelverts++] = wheele->next->next->vert; + + wheele = nexte; + } while (wheele && (wheele != oldv->edge)); + + /* collapse */ + p_collapse_edge(edge, pair); + + for (i = 0; i < nwheelverts; i++) { + float cost; + PEdge *collapse = NULL; + + v = wheelverts[i]; + + if (v->u.heaplink) { + BLI_heap_remove(heap, v->u.heaplink); + v->u.heaplink = NULL; + } + + p_collapse_cost_vertex(v, &cost, &collapse); + + if (collapse) + v->u.heaplink = BLI_heap_insert(heap, cost, collapse); + } + + ncollapsed++; + } + + MEM_freeN(wheelverts); + BLI_heap_free(heap, NULL); + + p_chart_post_collapse_flush(chart, collapsededges); +} + +static void p_chart_complexify(PChart *chart) +{ + PEdge *e, *pair, *edge; + PVert *newv, *keepv; + int x = 0; + + for (e=chart->collapsed_edges; e; e=e->nextlink) { + if (!(e->flag & PEDGE_COLLAPSE_EDGE)) + break; + + edge = e; + pair = e->pair; + + if (edge->flag & PEDGE_COLLAPSE_PAIR) { + SWAP(PEdge*, edge, pair); + } + + p_split_vertex(edge, pair); + p_collapsing_verts(edge, pair, &newv, &keepv); + + if (x >= NCOLLAPSEX) { + newv->uv[0] = keepv->uv[0]; + newv->uv[1] = keepv->uv[1]; + } + else { + p_vert_harmonic_insert(newv); + x++; + } + } + + p_chart_post_split_flush(chart); +} + +#if 0 +static void p_chart_simplify(PChart *chart) +{ + /* Not implemented, needs proper reordering in split_flush. */ +} +#endif +#endif + +/* ABF */ + +#define ABF_MAX_ITER 20 + +typedef struct PAbfSystem { + int ninterior, nfaces, nangles; + float *alpha, *beta, *sine, *cosine, *weight; + float *bAlpha, *bTriangle, *bInterior; + float *lambdaTriangle, *lambdaPlanar, *lambdaLength; + float (*J2dt)[3], *bstar, *dstar; + float minangle, maxangle; +} PAbfSystem; + +static void p_abf_setup_system(PAbfSystem *sys) +{ + int i; + + sys->alpha = (float*)MEM_mallocN(sizeof(float)*sys->nangles, "ABFalpha"); + sys->beta = (float*)MEM_mallocN(sizeof(float)*sys->nangles, "ABFbeta"); + sys->sine = (float*)MEM_mallocN(sizeof(float)*sys->nangles, "ABFsine"); + sys->cosine = (float*)MEM_mallocN(sizeof(float)*sys->nangles, "ABFcosine"); + sys->weight = (float*)MEM_mallocN(sizeof(float)*sys->nangles, "ABFweight"); + + sys->bAlpha = (float*)MEM_mallocN(sizeof(float)*sys->nangles, "ABFbalpha"); + sys->bTriangle = (float*)MEM_mallocN(sizeof(float)*sys->nfaces, "ABFbtriangle"); + sys->bInterior = (float*)MEM_mallocN(sizeof(float)*2*sys->ninterior, "ABFbinterior"); + + sys->lambdaTriangle = (float*)MEM_callocN(sizeof(float)*sys->nfaces, "ABFlambdatri"); + sys->lambdaPlanar = (float*)MEM_callocN(sizeof(float)*sys->ninterior, "ABFlamdaplane"); + sys->lambdaLength = (float*)MEM_mallocN(sizeof(float)*sys->ninterior, "ABFlambdalen"); + + sys->J2dt = MEM_mallocN(sizeof(float)*sys->nangles*3, "ABFj2dt"); + sys->bstar = (float*)MEM_mallocN(sizeof(float)*sys->nfaces, "ABFbstar"); + sys->dstar = (float*)MEM_mallocN(sizeof(float)*sys->nfaces, "ABFdstar"); + + for (i = 0; i < sys->ninterior; i++) + sys->lambdaLength[i] = 1.0; + + sys->minangle = 7.5f*M_PI/180.0f; + sys->maxangle = M_PI - sys->minangle; +} + +static void p_abf_free_system(PAbfSystem *sys) +{ + MEM_freeN(sys->alpha); + MEM_freeN(sys->beta); + MEM_freeN(sys->sine); + MEM_freeN(sys->cosine); + MEM_freeN(sys->weight); + MEM_freeN(sys->bAlpha); + MEM_freeN(sys->bTriangle); + MEM_freeN(sys->bInterior); + MEM_freeN(sys->lambdaTriangle); + MEM_freeN(sys->lambdaPlanar); + MEM_freeN(sys->lambdaLength); + MEM_freeN(sys->J2dt); + MEM_freeN(sys->bstar); + MEM_freeN(sys->dstar); +} + +static void p_abf_compute_sines(PAbfSystem *sys) +{ + int i; + float *sine = sys->sine, *cosine = sys->cosine, *alpha = sys->alpha; + + for (i = 0; i < sys->nangles; i++, sine++, cosine++, alpha++) { + *sine = sin(*alpha); + *cosine = cos(*alpha); + } +} + +static float p_abf_compute_sin_product(PAbfSystem *sys, PVert *v, int aid) +{ + PEdge *e, *e1, *e2; + float sin1, sin2; + + sin1 = sin2 = 1.0; + + e = v->edge; + do { + e1 = e->next; + e2 = e->next->next; + + if (aid == e1->u.id) { + /* we are computing a derivative for this angle, + so we use cos and drop the other part */ + sin1 *= sys->cosine[e1->u.id]; + sin2 = 0.0; + } + else + sin1 *= sys->sine[e1->u.id]; + + if (aid == e2->u.id) { + /* see above */ + sin1 = 0.0; + sin2 *= sys->cosine[e2->u.id]; + } + else + sin2 *= sys->sine[e2->u.id]; + + e = e->next->next->pair; + } while (e && (e != v->edge)); + + return (sin1 - sin2); +} + +static float p_abf_compute_grad_alpha(PAbfSystem *sys, PFace *f, PEdge *e) +{ + PVert *v = e->vert, *v1 = e->next->vert, *v2 = e->next->next->vert; + float deriv; + + deriv = (sys->alpha[e->u.id] - sys->beta[e->u.id])*sys->weight[e->u.id]; + deriv += sys->lambdaTriangle[f->u.id]; + + if (v->flag & PVERT_INTERIOR) { + deriv += sys->lambdaPlanar[v->u.id]; + } + + if (v1->flag & PVERT_INTERIOR) { + float product = p_abf_compute_sin_product(sys, v1, e->u.id); + deriv += sys->lambdaLength[v1->u.id]*product; + } + + if (v2->flag & PVERT_INTERIOR) { + float product = p_abf_compute_sin_product(sys, v2, e->u.id); + deriv += sys->lambdaLength[v2->u.id]*product; + } + + return deriv; +} + +static float p_abf_compute_gradient(PAbfSystem *sys, PChart *chart) +{ + PFace *f; + PEdge *e; + PVert *v; + float norm = 0.0; + + for (f=chart->faces; f; f=f->nextlink) { + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + float gtriangle, galpha1, galpha2, galpha3; + + galpha1 = p_abf_compute_grad_alpha(sys, f, e1); + galpha2 = p_abf_compute_grad_alpha(sys, f, e2); + galpha3 = p_abf_compute_grad_alpha(sys, f, e3); + + sys->bAlpha[e1->u.id] = -galpha1; + sys->bAlpha[e2->u.id] = -galpha2; + sys->bAlpha[e3->u.id] = -galpha3; + + norm += galpha1*galpha1 + galpha2*galpha2 + galpha3*galpha3; + + gtriangle = sys->alpha[e1->u.id] + sys->alpha[e2->u.id] + sys->alpha[e3->u.id] - M_PI; + sys->bTriangle[f->u.id] = -gtriangle; + norm += gtriangle*gtriangle; + } + + for (v=chart->verts; v; v=v->nextlink) { + if (v->flag & PVERT_INTERIOR) { + float gplanar = -2*M_PI, glength; + + e = v->edge; + do { + gplanar += sys->alpha[e->u.id]; + e = e->next->next->pair; + } while (e && (e != v->edge)); + + sys->bInterior[v->u.id] = -gplanar; + norm += gplanar*gplanar; + + glength = p_abf_compute_sin_product(sys, v, -1); + sys->bInterior[sys->ninterior + v->u.id] = -glength; + norm += glength*glength; + } + } + + return norm; +} + +static PBool p_abf_matrix_invert(PAbfSystem *sys, PChart *chart) +{ + PFace *f; + PEdge *e; + int i, j, ninterior = sys->ninterior, nvar = 2*sys->ninterior; + PBool success; + + nlNewContext(); + nlSolverParameteri(NL_NB_VARIABLES, nvar); + + nlBegin(NL_SYSTEM); + + nlBegin(NL_MATRIX); + + for (i = 0; i < nvar; i++) + nlRightHandSideAdd(0, i, sys->bInterior[i]); + + for (f=chart->faces; f; f=f->nextlink) { + float wi1, wi2, wi3, b, si, beta[3], j2[3][3], W[3][3]; + float row1[6], row2[6], row3[6]; + int vid[6]; + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; + + wi1 = 1.0/sys->weight[e1->u.id]; + wi2 = 1.0/sys->weight[e2->u.id]; + wi3 = 1.0/sys->weight[e3->u.id]; + + /* bstar1 = (J1*dInv*bAlpha - bTriangle) */ + b = sys->bAlpha[e1->u.id]*wi1; + b += sys->bAlpha[e2->u.id]*wi2; + b += sys->bAlpha[e3->u.id]*wi3; + b -= sys->bTriangle[f->u.id]; + + /* si = J1*d*J1t */ + si = 1.0/(wi1 + wi2 + wi3); + + /* J1t*si*bstar1 - bAlpha */ + beta[0] = b*si - sys->bAlpha[e1->u.id]; + beta[1] = b*si - sys->bAlpha[e2->u.id]; + beta[2] = b*si - sys->bAlpha[e3->u.id]; + + /* use this later for computing other lambda's */ + sys->bstar[f->u.id] = b; + sys->dstar[f->u.id] = si; + + /* set matrix */ + W[0][0] = si - sys->weight[e1->u.id]; W[0][1] = si; W[0][2] = si; + W[1][0] = si; W[1][1] = si - sys->weight[e2->u.id]; W[1][2] = si; + W[2][0] = si; W[2][1] = si; W[2][2] = si - sys->weight[e3->u.id]; + + vid[0] = vid[1] = vid[2] = vid[3] = vid[4] = vid[5] = -1; + + if (v1->flag & PVERT_INTERIOR) { + vid[0] = v1->u.id; + vid[3] = ninterior + v1->u.id; + + sys->J2dt[e1->u.id][0] = j2[0][0] = 1.0*wi1; + sys->J2dt[e2->u.id][0] = j2[1][0] = p_abf_compute_sin_product(sys, v1, e2->u.id)*wi2; + sys->J2dt[e3->u.id][0] = j2[2][0] = p_abf_compute_sin_product(sys, v1, e3->u.id)*wi3; + + nlRightHandSideAdd(0, v1->u.id, j2[0][0]*beta[0]); + nlRightHandSideAdd(0, ninterior + v1->u.id, j2[1][0]*beta[1] + j2[2][0]*beta[2]); + + row1[0] = j2[0][0]*W[0][0]; + row2[0] = j2[0][0]*W[1][0]; + row3[0] = j2[0][0]*W[2][0]; + + row1[3] = j2[1][0]*W[0][1] + j2[2][0]*W[0][2]; + row2[3] = j2[1][0]*W[1][1] + j2[2][0]*W[1][2]; + row3[3] = j2[1][0]*W[2][1] + j2[2][0]*W[2][2]; + } + + if (v2->flag & PVERT_INTERIOR) { + vid[1] = v2->u.id; + vid[4] = ninterior + v2->u.id; + + sys->J2dt[e1->u.id][1] = j2[0][1] = p_abf_compute_sin_product(sys, v2, e1->u.id)*wi1; + sys->J2dt[e2->u.id][1] = j2[1][1] = 1.0*wi2; + sys->J2dt[e3->u.id][1] = j2[2][1] = p_abf_compute_sin_product(sys, v2, e3->u.id)*wi3; + + nlRightHandSideAdd(0, v2->u.id, j2[1][1]*beta[1]); + nlRightHandSideAdd(0, ninterior + v2->u.id, j2[0][1]*beta[0] + j2[2][1]*beta[2]); + + row1[1] = j2[1][1]*W[0][1]; + row2[1] = j2[1][1]*W[1][1]; + row3[1] = j2[1][1]*W[2][1]; + + row1[4] = j2[0][1]*W[0][0] + j2[2][1]*W[0][2]; + row2[4] = j2[0][1]*W[1][0] + j2[2][1]*W[1][2]; + row3[4] = j2[0][1]*W[2][0] + j2[2][1]*W[2][2]; + } + + if (v3->flag & PVERT_INTERIOR) { + vid[2] = v3->u.id; + vid[5] = ninterior + v3->u.id; + + sys->J2dt[e1->u.id][2] = j2[0][2] = p_abf_compute_sin_product(sys, v3, e1->u.id)*wi1; + sys->J2dt[e2->u.id][2] = j2[1][2] = p_abf_compute_sin_product(sys, v3, e2->u.id)*wi2; + sys->J2dt[e3->u.id][2] = j2[2][2] = 1.0*wi3; + + nlRightHandSideAdd(0, v3->u.id, j2[2][2]*beta[2]); + nlRightHandSideAdd(0, ninterior + v3->u.id, j2[0][2]*beta[0] + j2[1][2]*beta[1]); + + row1[2] = j2[2][2]*W[0][2]; + row2[2] = j2[2][2]*W[1][2]; + row3[2] = j2[2][2]*W[2][2]; + + row1[5] = j2[0][2]*W[0][0] + j2[1][2]*W[0][1]; + row2[5] = j2[0][2]*W[1][0] + j2[1][2]*W[1][1]; + row3[5] = j2[0][2]*W[2][0] + j2[1][2]*W[2][1]; + } + + for (i = 0; i < 3; i++) { + int r = vid[i]; + + if (r == -1) + continue; + + for (j = 0; j < 6; j++) { + int c = vid[j]; + + if (c == -1) + continue; + + if (i == 0) + nlMatrixAdd(r, c, j2[0][i]*row1[j]); + else + nlMatrixAdd(r + ninterior, c, j2[0][i]*row1[j]); + + if (i == 1) + nlMatrixAdd(r, c, j2[1][i]*row2[j]); + else + nlMatrixAdd(r + ninterior, c, j2[1][i]*row2[j]); + + + if (i == 2) + nlMatrixAdd(r, c, j2[2][i]*row3[j]); + else + nlMatrixAdd(r + ninterior, c, j2[2][i]*row3[j]); + } + } + } + + nlEnd(NL_MATRIX); + + nlEnd(NL_SYSTEM); + + success = nlSolve(); + + if (success) { + for (f=chart->faces; f; f=f->nextlink) { + float dlambda1, pre[3], dalpha; + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; + + pre[0] = pre[1] = pre[2] = 0.0; + + if (v1->flag & PVERT_INTERIOR) { + float x = nlGetVariable(0, v1->u.id); + float x2 = nlGetVariable(0, ninterior + v1->u.id); + pre[0] += sys->J2dt[e1->u.id][0]*x; + pre[1] += sys->J2dt[e2->u.id][0]*x2; + pre[2] += sys->J2dt[e3->u.id][0]*x2; + } + + if (v2->flag & PVERT_INTERIOR) { + float x = nlGetVariable(0, v2->u.id); + float x2 = nlGetVariable(0, ninterior + v2->u.id); + pre[0] += sys->J2dt[e1->u.id][1]*x2; + pre[1] += sys->J2dt[e2->u.id][1]*x; + pre[2] += sys->J2dt[e3->u.id][1]*x2; + } + + if (v3->flag & PVERT_INTERIOR) { + float x = nlGetVariable(0, v3->u.id); + float x2 = nlGetVariable(0, ninterior + v3->u.id); + pre[0] += sys->J2dt[e1->u.id][2]*x2; + pre[1] += sys->J2dt[e2->u.id][2]*x2; + pre[2] += sys->J2dt[e3->u.id][2]*x; + } + + dlambda1 = pre[0] + pre[1] + pre[2]; + dlambda1 = sys->dstar[f->u.id]*(sys->bstar[f->u.id] - dlambda1); + + sys->lambdaTriangle[f->u.id] += dlambda1; + + dalpha = (sys->bAlpha[e1->u.id] - dlambda1); + sys->alpha[e1->u.id] += dalpha/sys->weight[e1->u.id] - pre[0]; + + dalpha = (sys->bAlpha[e2->u.id] - dlambda1); + sys->alpha[e2->u.id] += dalpha/sys->weight[e2->u.id] - pre[1]; + + dalpha = (sys->bAlpha[e3->u.id] - dlambda1); + sys->alpha[e3->u.id] += dalpha/sys->weight[e3->u.id] - pre[2]; + + /* clamp */ + e = f->edge; + do { + if (sys->alpha[e->u.id] > M_PI) + sys->alpha[e->u.id] = M_PI; + else if (sys->alpha[e->u.id] < 0.0f) + sys->alpha[e->u.id] = 0.0f; + } while (e != f->edge); + } + + for (i = 0; i < ninterior; i++) { + sys->lambdaPlanar[i] += nlGetVariable(0, i); + sys->lambdaLength[i] += nlGetVariable(0, ninterior + i); + } + } + + nlDeleteContext(nlGetCurrent()); + + return success; +} + +static PBool p_chart_abf_solve(PChart *chart) +{ + PVert *v; + PFace *f; + PEdge *e, *e1, *e2, *e3; + PAbfSystem sys; + int i; + float lastnorm, limit = (chart->nfaces > 100)? 1.0f: 0.001f; + + /* setup id's */ + sys.ninterior = sys.nfaces = sys.nangles = 0; + + for (v=chart->verts; v; v=v->nextlink) { + if (p_vert_interior(v)) { + v->flag |= PVERT_INTERIOR; + v->u.id = sys.ninterior++; + } + else + v->flag &= ~PVERT_INTERIOR; + } + + for (f=chart->faces; f; f=f->nextlink) { + e1 = f->edge; e2 = e1->next; e3 = e2->next; + f->u.id = sys.nfaces++; + + /* angle id's are conveniently stored in half edges */ + e1->u.id = sys.nangles++; + e2->u.id = sys.nangles++; + e3->u.id = sys.nangles++; + } + + p_abf_setup_system(&sys); + + /* compute initial angles */ + for (f=chart->faces; f; f=f->nextlink) { + float a1, a2, a3; + + e1 = f->edge; e2 = e1->next; e3 = e2->next; + p_face_angles(f, &a1, &a2, &a3); + + if (a1 < sys.minangle) + a1 = sys.minangle; + else if (a1 > sys.maxangle) + a1 = sys.maxangle; + if (a2 < sys.minangle) + a2 = sys.minangle; + else if (a2 > sys.maxangle) + a2 = sys.maxangle; + if (a3 < sys.minangle) + a3 = sys.minangle; + else if (a3 > sys.maxangle) + a3 = sys.maxangle; + + sys.alpha[e1->u.id] = sys.beta[e1->u.id] = a1; + sys.alpha[e2->u.id] = sys.beta[e2->u.id] = a2; + sys.alpha[e3->u.id] = sys.beta[e3->u.id] = a3; + + sys.weight[e1->u.id] = 2.0/(a1*a1); + sys.weight[e2->u.id] = 2.0/(a2*a2); + sys.weight[e3->u.id] = 2.0/(a3*a3); + } + + for (v=chart->verts; v; v=v->nextlink) { + if (v->flag & PVERT_INTERIOR) { + float anglesum = 0.0, scale; + + e = v->edge; + do { + anglesum += sys.beta[e->u.id]; + e = e->next->next->pair; + } while (e && (e != v->edge)); + + scale = (anglesum == 0.0f)? 0.0f: 2*M_PI/anglesum; + + e = v->edge; + do { + sys.beta[e->u.id] = sys.alpha[e->u.id] = sys.beta[e->u.id]*scale; + e = e->next->next->pair; + } while (e && (e != v->edge)); + } + } + + if (sys.ninterior > 0) { + p_abf_compute_sines(&sys); + + /* iteration */ + lastnorm = 1e10; + + for (i = 0; i < ABF_MAX_ITER; i++) { + float norm = p_abf_compute_gradient(&sys, chart); + + lastnorm = norm; + + if (norm < limit) + break; + + if (!p_abf_matrix_invert(&sys, chart)) { + param_warning("ABF failed to invert matrix."); + p_abf_free_system(&sys); + return P_FALSE; + } + + p_abf_compute_sines(&sys); + } + + if (i == ABF_MAX_ITER) { + param_warning("ABF maximum iterations reached."); + p_abf_free_system(&sys); + return P_FALSE; + } + } + + chart->u.lscm.abf_alpha = MEM_dupallocN(sys.alpha); + p_abf_free_system(&sys); + + return P_TRUE; +} + +/* Least Squares Conformal Maps */ + +static void p_chart_pin_positions(PChart *chart, PVert **pin1, PVert **pin2) +{ + if (pin1 == pin2) { + /* degenerate case */ + PFace *f = chart->faces; + *pin1 = f->edge->vert; + *pin2 = f->edge->next->vert; + + (*pin1)->uv[0] = 0.0f; + (*pin1)->uv[1] = 0.5f; + (*pin2)->uv[0] = 1.0f; + (*pin2)->uv[1] = 0.5f; + } + else { + int diru, dirv, dirx, diry; + float sub[3]; + + VecSubf(sub, (*pin1)->co, (*pin2)->co); + sub[0] = fabs(sub[0]); + sub[1] = fabs(sub[1]); + sub[2] = fabs(sub[2]); + + if ((sub[0] > sub[1]) && (sub[0] > sub[2])) { + dirx = 0; + diry = (sub[1] > sub[2])? 1: 2; + } + else if ((sub[1] > sub[0]) && (sub[1] > sub[2])) { + dirx = 1; + diry = (sub[0] > sub[2])? 0: 2; + } + else { + dirx = 2; + diry = (sub[0] > sub[1])? 0: 1; + } + + if (dirx == 2) { + diru = 1; + dirv = 0; + } + else { + diru = 0; + dirv = 1; + } + + (*pin1)->uv[diru] = (*pin1)->co[dirx]; + (*pin1)->uv[dirv] = (*pin1)->co[diry]; + (*pin2)->uv[diru] = (*pin2)->co[dirx]; + (*pin2)->uv[dirv] = (*pin2)->co[diry]; + } +} + +static PBool p_chart_symmetry_pins(PChart *chart, PEdge *outer, PVert **pin1, PVert **pin2) +{ + PEdge *be, *lastbe = NULL, *maxe1 = NULL, *maxe2 = NULL, *be1, *be2; + PEdge *cure = NULL, *firste1 = NULL, *firste2 = NULL, *nextbe; + float maxlen = 0.0f, curlen = 0.0f, totlen = 0.0f, firstlen = 0.0f; + float len1, len2; + + /* find longest series of verts split in the chart itself, these are + marked during construction */ + be = outer; + lastbe = p_boundary_edge_prev(be); + do { + float len = p_edge_length(be); + totlen += len; + + nextbe = p_boundary_edge_next(be); + + if ((be->vert->flag & PVERT_SPLIT) || + (lastbe->vert->flag & nextbe->vert->flag & PVERT_SPLIT)) { + if (!cure) { + if (be == outer) + firste1 = be; + cure = be; + } + else + curlen += p_edge_length(lastbe); + } + else if (cure) { + if (curlen > maxlen) { + maxlen = curlen; + maxe1 = cure; + maxe2 = lastbe; + } + + if (firste1 == cure) { + firstlen = curlen; + firste2 = lastbe; + } + + curlen = 0.0f; + cure = NULL; + } + + lastbe = be; + be = nextbe; + } while(be != outer); + + /* make sure we also count a series of splits over the starting point */ + if (cure && (cure != outer)) { + firstlen += curlen + p_edge_length(be); + + if (firstlen > maxlen) { + maxlen = firstlen; + maxe1 = cure; + maxe2 = firste2; + } + } + + if (!maxe1 || !maxe2 || (maxlen < 0.5f*totlen)) + return P_FALSE; + + /* find pin1 in the split vertices */ + be1 = maxe1; + be2 = maxe2; + len1 = 0.0f; + len2 = 0.0f; + + do { + if (len1 < len2) { + len1 += p_edge_length(be1); + be1 = p_boundary_edge_next(be1); + } + else { + be2 = p_boundary_edge_prev(be2); + len2 += p_edge_length(be2); + } + } while (be1 != be2); + + *pin1 = be1->vert; + + /* find pin2 outside the split vertices */ + be1 = maxe1; + be2 = maxe2; + len1 = 0.0f; + len2 = 0.0f; + + do { + if (len1 < len2) { + be1 = p_boundary_edge_prev(be1); + len1 += p_edge_length(be1); + } + else { + len2 += p_edge_length(be2); + be2 = p_boundary_edge_next(be2); + } + } while (be1 != be2); + + *pin2 = be1->vert; + + p_chart_pin_positions(chart, pin1, pin2); + + return P_TRUE; +} + +static void p_chart_extrema_verts(PChart *chart, PVert **pin1, PVert **pin2) +{ + float minv[3], maxv[3], dirlen; + PVert *v, *minvert[3], *maxvert[3]; + int i, dir; + + /* find minimum and maximum verts over x/y/z axes */ + minv[0] = minv[1] = minv[2] = 1e20; + maxv[0] = maxv[1] = maxv[2] = -1e20; + + minvert[0] = minvert[1] = minvert[2] = NULL; + maxvert[0] = maxvert[1] = maxvert[2] = NULL; + + for (v = chart->verts; v; v=v->nextlink) { + for (i = 0; i < 3; i++) { + if (v->co[i] < minv[i]) { + minv[i] = v->co[i]; + minvert[i] = v; + } + if (v->co[i] > maxv[i]) { + maxv[i] = v->co[i]; + maxvert[i] = v; + } + } + } + + /* find axes with longest distance */ + dir = 0; + dirlen = -1.0; + + for (i = 0; i < 3; i++) { + if (maxv[i] - minv[i] > dirlen) { + dir = i; + dirlen = maxv[i] - minv[i]; + } + } + + *pin1 = minvert[dir]; + *pin2 = maxvert[dir]; + + p_chart_pin_positions(chart, pin1, pin2); +} + +static void p_chart_lscm_load_solution(PChart *chart) +{ + PVert *v; + + for (v=chart->verts; v; v=v->nextlink) { + v->uv[0] = nlGetVariable(0, 2*v->u.id); + v->uv[1] = nlGetVariable(0, 2*v->u.id + 1); + } +} + +static void p_chart_lscm_begin(PChart *chart, PBool live, PBool abf) +{ + PVert *v, *pin1, *pin2; + PBool select = P_FALSE, deselect = P_FALSE; + int npins = 0, id = 0; + + /* give vertices matrix indices and count pins */ + for (v=chart->verts; v; v=v->nextlink) { + if (v->flag & PVERT_PIN) { + npins++; + if (v->flag & PVERT_SELECT) + select = P_TRUE; + } + + if (!(v->flag & PVERT_SELECT)) + deselect = P_TRUE; + } + + if ((live && (!select || !deselect)) || (npins == 1)) { + chart->u.lscm.context = NULL; + } + else { +#if 0 + p_chart_simplify_compute(chart); + p_chart_topological_sanity_check(chart); +#endif + + if (abf) { + if (!p_chart_abf_solve(chart)) + param_warning("ABF solving failed: falling back to LSCM.\n"); + } + + if (npins <= 1) { + /* not enough pins, lets find some ourself */ + PEdge *outer; + + p_chart_boundaries(chart, NULL, &outer); + + if (!p_chart_symmetry_pins(chart, outer, &pin1, &pin2)) + p_chart_extrema_verts(chart, &pin1, &pin2); + + chart->u.lscm.pin1 = pin1; + chart->u.lscm.pin2 = pin2; + } + else { + chart->flag |= PCHART_NOPACK; + } + + for (v=chart->verts; v; v=v->nextlink) + v->u.id = id++; + + nlNewContext(); + nlSolverParameteri(NL_NB_VARIABLES, 2*chart->nverts); + nlSolverParameteri(NL_NB_ROWS, 2*chart->nfaces); + nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE); + + chart->u.lscm.context = nlGetCurrent(); + } +} + +static PBool p_chart_lscm_solve(PHandle *handle, PChart *chart) +{ + PVert *v, *pin1 = chart->u.lscm.pin1, *pin2 = chart->u.lscm.pin2; + PFace *f; + float *alpha = chart->u.lscm.abf_alpha; + int row; + + nlMakeCurrent(chart->u.lscm.context); + + nlBegin(NL_SYSTEM); + +#if 0 + /* TODO: make loading pins work for simplify/complexify. */ +#endif + + for (v=chart->verts; v; v=v->nextlink) + if (v->flag & PVERT_PIN) + p_vert_load_pin_select_uvs(handle, v); /* reload for live */ + + if (chart->u.lscm.pin1) { + nlLockVariable(2*pin1->u.id); + nlLockVariable(2*pin1->u.id + 1); + nlLockVariable(2*pin2->u.id); + nlLockVariable(2*pin2->u.id + 1); + + nlSetVariable(0, 2*pin1->u.id, pin1->uv[0]); + nlSetVariable(0, 2*pin1->u.id + 1, pin1->uv[1]); + nlSetVariable(0, 2*pin2->u.id, pin2->uv[0]); + nlSetVariable(0, 2*pin2->u.id + 1, pin2->uv[1]); + } + else { + /* set and lock the pins */ + for (v=chart->verts; v; v=v->nextlink) { + if (v->flag & PVERT_PIN) { + nlLockVariable(2*v->u.id); + nlLockVariable(2*v->u.id + 1); + + nlSetVariable(0, 2*v->u.id, v->uv[0]); + nlSetVariable(0, 2*v->u.id + 1, v->uv[1]); + } + } + } + + /* construct matrix */ + + nlBegin(NL_MATRIX); + + row = 0; + for (f=chart->faces; f; f=f->nextlink) { + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; + float a1, a2, a3, ratio, cosine, sine; + float sina1, sina2, sina3, sinmax; + + if (alpha) { + /* use abf angles if passed on */ + a1 = *(alpha++); + a2 = *(alpha++); + a3 = *(alpha++); + } + else + p_face_angles(f, &a1, &a2, &a3); + + sina1 = sin(a1); + sina2 = sin(a2); + sina3 = sin(a3); + + sinmax = MAX3(sina1, sina2, sina3); + + /* shift vertices to find most stable order */ + if (sina3 != sinmax) { + SHIFT3(PVert*, v1, v2, v3); + SHIFT3(float, a1, a2, a3); + SHIFT3(float, sina1, sina2, sina3); + + if (sina2 == sinmax) { + SHIFT3(PVert*, v1, v2, v3); + SHIFT3(float, a1, a2, a3); + SHIFT3(float, sina1, sina2, sina3); + } + } + + /* angle based lscm formulation */ + ratio = (sina3 == 0.0f)? 1.0f: sina2/sina3; + cosine = cos(a1)*ratio; + sine = sina1*ratio; + +#if 0 + nlBegin(NL_ROW); + nlCoefficient(2*v1->u.id, cosine - 1.0); + nlCoefficient(2*v1->u.id+1, -sine); + nlCoefficient(2*v2->u.id, -cosine); + nlCoefficient(2*v2->u.id+1, sine); + nlCoefficient(2*v3->u.id, 1.0); + nlEnd(NL_ROW); + + nlBegin(NL_ROW); + nlCoefficient(2*v1->u.id, sine); + nlCoefficient(2*v1->u.id+1, cosine - 1.0); + nlCoefficient(2*v2->u.id, -sine); + nlCoefficient(2*v2->u.id+1, -cosine); + nlCoefficient(2*v3->u.id+1, 1.0); + nlEnd(NL_ROW); +#else + nlMatrixAdd(row, 2*v1->u.id, cosine - 1.0); + nlMatrixAdd(row, 2*v1->u.id+1, -sine); + nlMatrixAdd(row, 2*v2->u.id, -cosine); + nlMatrixAdd(row, 2*v2->u.id+1, sine); + nlMatrixAdd(row, 2*v3->u.id, 1.0); + row++; + + nlMatrixAdd(row, 2*v1->u.id, sine); + nlMatrixAdd(row, 2*v1->u.id+1, cosine - 1.0); + nlMatrixAdd(row, 2*v2->u.id, -sine); + nlMatrixAdd(row, 2*v2->u.id+1, -cosine); + nlMatrixAdd(row, 2*v3->u.id+1, 1.0); + row++; +#endif + } + + nlEnd(NL_MATRIX); + + nlEnd(NL_SYSTEM); + + if (nlSolveAdvanced(NULL, NL_TRUE)) { + p_chart_lscm_load_solution(chart); + return P_TRUE; + } + else { + for (v=chart->verts; v; v=v->nextlink) { + v->uv[0] = 0.0f; + v->uv[1] = 0.0f; + } + } + + return P_FALSE; +} + +static void p_chart_lscm_end(PChart *chart) +{ + if (chart->u.lscm.context) + nlDeleteContext(chart->u.lscm.context); + + if (chart->u.lscm.abf_alpha) { + MEM_freeN(chart->u.lscm.abf_alpha); + chart->u.lscm.abf_alpha = NULL; + } + + chart->u.lscm.context = NULL; + chart->u.lscm.pin1 = NULL; + chart->u.lscm.pin2 = NULL; +} + +/* Stretch */ + +#define P_STRETCH_ITER 20 + +static void p_stretch_pin_boundary(PChart *chart) +{ + PVert *v; + + for(v=chart->verts; v; v=v->nextlink) + if (v->edge->pair == NULL) + v->flag |= PVERT_PIN; + else + v->flag &= ~PVERT_PIN; +} + +static float p_face_stretch(PFace *f) +{ + float T, w, tmp[3]; + float Ps[3], Pt[3]; + float a, c, area; + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + PVert *v1 = e1->vert, *v2 = e2->vert, *v3 = e3->vert; + + area = p_face_uv_area_signed(f); + + if (area <= 0.0f) /* flipped face -> infinite stretch */ + return 1e10f; + + w= 1.0f/(2.0f*area); + + /* compute derivatives */ + VecCopyf(Ps, v1->co); + VecMulf(Ps, (v2->uv[1] - v3->uv[1])); + + VecCopyf(tmp, v2->co); + VecMulf(tmp, (v3->uv[1] - v1->uv[1])); + VecAddf(Ps, Ps, tmp); + + VecCopyf(tmp, v3->co); + VecMulf(tmp, (v1->uv[1] - v2->uv[1])); + VecAddf(Ps, Ps, tmp); + + VecMulf(Ps, w); + + VecCopyf(Pt, v1->co); + VecMulf(Pt, (v3->uv[0] - v2->uv[0])); + + VecCopyf(tmp, v2->co); + VecMulf(tmp, (v1->uv[0] - v3->uv[0])); + VecAddf(Pt, Pt, tmp); + + VecCopyf(tmp, v3->co); + VecMulf(tmp, (v2->uv[0] - v1->uv[0])); + VecAddf(Pt, Pt, tmp); + + VecMulf(Pt, w); + + /* Sander Tensor */ + a= Inpf(Ps, Ps); + c= Inpf(Pt, Pt); + + T = sqrt(0.5f*(a + c)); + if (f->flag & PFACE_FILLED) + T *= 0.2; + + return T; +} + +static float p_stretch_compute_vertex(PVert *v) +{ + PEdge *e = v->edge; + float sum = 0.0f; + + do { + sum += p_face_stretch(e->face); + e = p_wheel_edge_next(e); + } while (e && e != (v->edge)); + + return sum; +} + +static void p_chart_stretch_minimize(PChart *chart, RNG *rng) +{ + PVert *v; + PEdge *e; + int j, nedges; + float orig_stretch, low, stretch_low, high, stretch_high, mid, stretch; + float orig_uv[2], dir[2], random_angle, trusted_radius; + + for(v=chart->verts; v; v=v->nextlink) { + if((v->flag & PVERT_PIN) || !(v->flag & PVERT_SELECT)) + continue; + + orig_stretch = p_stretch_compute_vertex(v); + orig_uv[0] = v->uv[0]; + orig_uv[1] = v->uv[1]; + + /* move vertex in a random direction */ + trusted_radius = 0.0f; + nedges = 0; + e = v->edge; + + do { + trusted_radius += p_edge_uv_length(e); + nedges++; + + e = p_wheel_edge_next(e); + } while (e && e != (v->edge)); + + trusted_radius /= 2 * nedges; + + random_angle = rng_getFloat(rng) * 2.0 * M_PI; + dir[0] = trusted_radius * cos(random_angle); + dir[1] = trusted_radius * sin(random_angle); + + /* calculate old and new stretch */ + low = 0; + stretch_low = orig_stretch; + + Vec2Addf(v->uv, orig_uv, dir); + high = 1; + stretch = stretch_high = p_stretch_compute_vertex(v); + + /* binary search for lowest stretch position */ + for (j = 0; j < P_STRETCH_ITER; j++) { + mid = 0.5 * (low + high); + v->uv[0]= orig_uv[0] + mid*dir[0]; + v->uv[1]= orig_uv[1] + mid*dir[1]; + stretch = p_stretch_compute_vertex(v); + + if (stretch_low < stretch_high) { + high = mid; + stretch_high = stretch; + } + else { + low = mid; + stretch_low = stretch; + } + } + + /* no luck, stretch has increased, reset to old values */ + if(stretch >= orig_stretch) + Vec2Copyf(v->uv, orig_uv); + } +} + +/* Minimum area enclosing rectangle for packing */ + +static int p_compare_geometric_uv(const void *a, const void *b) +{ + PVert *v1 = *(PVert**)a; + PVert *v2 = *(PVert**)b; + + if (v1->uv[0] < v2->uv[0]) + return -1; + else if (v1->uv[0] == v2->uv[0]) { + if (v1->uv[1] < v2->uv[1]) + return -1; + else if (v1->uv[1] == v2->uv[1]) + return 0; + else + return 1; + } + else + return 1; +} + +static PBool p_chart_convex_hull(PChart *chart, PVert ***verts, int *nverts, int *right) +{ + /* Graham algorithm, taken from: + * http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/117225 */ + + PEdge *be, *e; + int npoints = 0, i, ulen, llen; + PVert **U, **L, **points, **p; + + p_chart_boundaries(chart, NULL, &be); + + if (!be) + return P_FALSE; + + e = be; + do { + npoints++; + e = p_boundary_edge_next(e); + } while(e != be); + + p = points = (PVert**)MEM_mallocN(sizeof(PVert*)*npoints*2, "PCHullpoints"); + U = (PVert**)MEM_mallocN(sizeof(PVert*)*npoints, "PCHullU"); + L = (PVert**)MEM_mallocN(sizeof(PVert*)*npoints, "PCHullL"); + + e = be; + do { + *p = e->vert; + p++; + e = p_boundary_edge_next(e); + } while(e != be); + + qsort(points, npoints, sizeof(PVert*), p_compare_geometric_uv); + + ulen = llen = 0; + for (p=points, i = 0; i < npoints; i++, p++) { + while ((ulen > 1) && (p_area_signed(U[ulen-2]->uv, (*p)->uv, U[ulen-1]->uv) <= 0)) + ulen--; + while ((llen > 1) && (p_area_signed(L[llen-2]->uv, (*p)->uv, L[llen-1]->uv) >= 0)) + llen--; + + U[ulen] = *p; + ulen++; + L[llen] = *p; + llen++; + } + + npoints = 0; + for (p=points, i = 0; i < ulen; i++, p++, npoints++) + *p = U[i]; + + /* the first and last point in L are left out, since they are also in U */ + for (i = llen-2; i > 0; i--, p++, npoints++) + *p = L[i]; + + *verts = points; + *nverts = npoints; + *right = ulen - 1; + + MEM_freeN(U); + MEM_freeN(L); + + return P_TRUE; +} + +static float p_rectangle_area(float *p1, float *dir, float *p2, float *p3, float *p4) +{ + /* given 4 points on the rectangle edges and the direction of on edge, + compute the area of the rectangle */ + + float orthodir[2], corner1[2], corner2[2], corner3[2]; + + orthodir[0] = dir[1]; + orthodir[1] = -dir[0]; + + if (!p_intersect_line_2d_dir(p1, dir, p2, orthodir, corner1)) + return 1e10; + + if (!p_intersect_line_2d_dir(p1, dir, p4, orthodir, corner2)) + return 1e10; + + if (!p_intersect_line_2d_dir(p3, dir, p4, orthodir, corner3)) + return 1e10; + + return Vec2Lenf(corner1, corner2)*Vec2Lenf(corner2, corner3); +} + +static float p_chart_minimum_area_angle(PChart *chart) +{ + /* minimum area enclosing rectangle with rotating calipers, info: + * http://cgm.cs.mcgill.ca/~orm/maer.html */ + + float rotated, minarea, minangle, area, len; + float *angles, miny, maxy, v[2], a[4], mina; + int npoints, right, mini, maxi, i, idx[4], nextidx; + PVert **points, *p1, *p2, *p3, *p4, *p1n; + + /* compute convex hull */ + if (!p_chart_convex_hull(chart, &points, &npoints, &right)) + return 0.0; + + /* find left/top/right/bottom points, and compute angle for each point */ + angles = MEM_mallocN(sizeof(float)*npoints, "PMinAreaAngles"); + + mini = maxi = 0; + miny = 1e10; + maxy = -1e10; + + for (i = 0; i < npoints; i++) { + p1 = (i == 0)? points[npoints-1]: points[i-1]; + p2 = points[i]; + p3 = (i == npoints-1)? points[0]: points[i+1]; + + angles[i] = M_PI - p_vec2_angle(p1->uv, p2->uv, p3->uv); + + if (points[i]->uv[1] < miny) { + miny = points[i]->uv[1]; + mini = i; + } + if (points[i]->uv[1] > maxy) { + maxy = points[i]->uv[1]; + maxi = i; + } + } + + /* left, top, right, bottom */ + idx[0] = 0; + idx[1] = maxi; + idx[2] = right; + idx[3] = mini; + + v[0] = points[idx[0]]->uv[0]; + v[1] = points[idx[0]]->uv[1] + 1.0f; + a[0] = p_vec2_angle(points[(idx[0]+1)%npoints]->uv, points[idx[0]]->uv, v); + + v[0] = points[idx[1]]->uv[0] + 1.0f; + v[1] = points[idx[1]]->uv[1]; + a[1] = p_vec2_angle(points[(idx[1]+1)%npoints]->uv, points[idx[1]]->uv, v); + + v[0] = points[idx[2]]->uv[0]; + v[1] = points[idx[2]]->uv[1] - 1.0f; + a[2] = p_vec2_angle(points[(idx[2]+1)%npoints]->uv, points[idx[2]]->uv, v); + + v[0] = points[idx[3]]->uv[0] - 1.0f; + v[1] = points[idx[3]]->uv[1]; + a[3] = p_vec2_angle(points[(idx[3]+1)%npoints]->uv, points[idx[3]]->uv, v); + + /* 4 rotating calipers */ + + rotated = 0.0; + minarea = 1e10; + minangle = 0.0; + + while (rotated <= M_PI/2) { /* INVESTIGATE: how far to rotate? */ + /* rotate with the smallest angle */ + mini = 0; + mina = 1e10; + + for (i = 0; i < 4; i++) + if (a[i] < mina) { + mina = a[i]; + mini = i; + } + + rotated += mina; + nextidx = (idx[mini]+1)%npoints; + + a[mini] = angles[nextidx]; + a[(mini+1)%4] = a[(mini+1)%4] - mina; + a[(mini+2)%4] = a[(mini+2)%4] - mina; + a[(mini+3)%4] = a[(mini+3)%4] - mina; + + /* compute area */ + p1 = points[idx[mini]]; + p1n = points[nextidx]; + p2 = points[idx[(mini+1)%4]]; + p3 = points[idx[(mini+2)%4]]; + p4 = points[idx[(mini+3)%4]]; + + len = Vec2Lenf(p1->uv, p1n->uv); + + if (len > 0.0f) { + len = 1.0/len; + v[0] = (p1n->uv[0] - p1->uv[0])*len; + v[1] = (p1n->uv[1] - p1->uv[1])*len; + + area = p_rectangle_area(p1->uv, v, p2->uv, p3->uv, p4->uv); + + /* remember smallest area */ + if (area < minarea) { + minarea = area; + minangle = rotated; + } + } + + idx[mini] = nextidx; + } + + /* try keeping rotation as small as possible */ + if (minangle > M_PI/4) + minangle -= M_PI/2; + + MEM_freeN(angles); + MEM_freeN(points); + + return minangle; +} + +static void p_chart_rotate_minimum_area(PChart *chart) +{ + float angle = p_chart_minimum_area_angle(chart); + float sine = sin(angle); + float cosine = cos(angle); + PVert *v; + + for (v = chart->verts; v; v=v->nextlink) { + float oldu = v->uv[0], oldv = v->uv[1]; + v->uv[0] = cosine*oldu - sine*oldv; + v->uv[1] = sine*oldu + cosine*oldv; + } +} + +/* Area Smoothing */ + +/* 2d bsp tree for inverse mapping - that's a bit silly */ + +typedef struct SmoothTriangle { + float co1[2], co2[2], co3[2]; + float oco1[2], oco2[2], oco3[2]; +} SmoothTriangle; + +typedef struct SmoothNode { + struct SmoothNode *c1, *c2; + SmoothTriangle **tri; + float split; + int axis, ntri; +} SmoothNode; + +static void p_barycentric_2d(float *v1, float *v2, float *v3, float *p, float *b) +{ + float a[2], c[2], h[2], div; + + a[0] = v2[0] - v1[0]; + a[1] = v2[1] - v1[1]; + c[0] = v3[0] - v1[0]; + c[1] = v3[1] - v1[1]; + + div = a[0]*c[1] - a[1]*c[0]; + + if (div == 0.0f) { + b[0] = 1.0f/3.0f; + b[1] = 1.0f/3.0f; + b[2] = 1.0f/3.0f; + } + else { + h[0] = p[0] - v1[0]; + h[1] = p[1] - v1[1]; + + div = 1.0f/div; + + b[1] = (h[0]*c[1] - h[1]*c[0])*div; + b[2] = (a[0]*h[1] - a[1]*h[0])*div; + b[0] = 1.0 - b[1] - b[2]; + } +} + +static PBool p_triangle_inside(SmoothTriangle *t, float *co) +{ + float b[3]; + + p_barycentric_2d(t->co1, t->co2, t->co3, co, b); + + if ((b[0] >= 0.0) && (b[1] >= 0.0) && (b[2] >= 0.0f)) { + co[0] = t->oco1[0]*b[0] + t->oco2[0]*b[1] + t->oco3[0]*b[2]; + co[1] = t->oco1[1]*b[0] + t->oco2[1]*b[1] + t->oco3[1]*b[2]; + return P_TRUE; + } + + return P_FALSE; +} + +static SmoothNode *p_node_new(MemArena *arena, SmoothTriangle **tri, int ntri, float *bmin, float *bmax, int depth) +{ + SmoothNode *node = BLI_memarena_alloc(arena, sizeof *node); + int axis, i, t1size = 0, t2size = 0; + float split, mi, mx; + SmoothTriangle **t1, **t2, *t; + + node->tri = tri; + node->ntri = ntri; + + if (ntri <= 10 || depth >= 15) + return node; + + t1 = MEM_mallocN(sizeof(SmoothTriangle)*ntri, "PNodeTri1"); + t2 = MEM_mallocN(sizeof(SmoothTriangle)*ntri, "PNodeTri1"); + + axis = (bmax[0] - bmin[0] > bmax[1] - bmin[1])? 0: 1; + split = 0.5f*(bmin[axis] + bmax[axis]); + + for (i = 0; i < ntri; i++) { + t = tri[i]; + + if ((t->co1[axis] <= split) || (t->co2[axis] <= split) || (t->co3[axis] <= split)) { + t1[t1size] = t; + t1size++; + } + if ((t->co1[axis] >= split) || (t->co2[axis] >= split) || (t->co3[axis] >= split)) { + t2[t2size] = t; + t2size++; + } + } + + if ((t1size == t2size) && (t1size == ntri)) { + MEM_freeN(t1); + MEM_freeN(t2); + return node; + } + + node->tri = NULL; + node->ntri = 0; + MEM_freeN(tri); + + node->axis = axis; + node->split = split; + + mi = bmin[axis]; + mx = bmax[axis]; + bmax[axis] = split; + node->c1 = p_node_new(arena, t1, t1size, bmin, bmax, depth+1); + + bmin[axis] = bmax[axis]; + bmax[axis] = mx; + node->c2 = p_node_new(arena, t2, t2size, bmin, bmax, depth+1); + + return node; +} + +static void p_node_delete(SmoothNode *node) +{ + if (node->c1) + p_node_delete(node->c1); + if (node->c2) + p_node_delete(node->c2); + if (node->tri) + MEM_freeN(node->tri); +} + +static PBool p_node_intersect(SmoothNode *node, float *co) +{ + int i; + + if (node->tri) { + for (i = 0; i < node->ntri; i++) + if (p_triangle_inside(node->tri[i], co)) + return P_TRUE; + + return P_FALSE; + } + else { + if (co[node->axis] < node->split) + return p_node_intersect(node->c1, co); + else + return p_node_intersect(node->c2, co); + } + +} + +/* smooothing */ + +static int p_compare_float(const void *a, const void *b) +{ + if (*((float*)a) < *((float*)b)) + return -1; + else if (*((float*)a) == *((float*)b)) + return 0; + else + return 1; +} + +static float p_smooth_median_edge_length(PChart *chart) +{ + PEdge *e; + float *lengths = MEM_mallocN(sizeof(chart->edges)*chart->nedges, "PMedianLength"); + float median; + int i; + + /* ok, so i'm lazy */ + for (i=0, e=chart->edges; e; e=e->nextlink, i++) + lengths[i] = p_edge_length(e); + + qsort(lengths, i, sizeof(float), p_compare_float); + + median = lengths[i/2]; + MEM_freeN(lengths); + + return median; +} + +static float p_smooth_distortion(PEdge *e, float avg2d, float avg3d) +{ + float len2d = p_edge_uv_length(e)*avg3d; + float len3d = p_edge_length(e)*avg2d; + + return (len3d == 0.0f)? 0.0f: len2d/len3d; +} + +static void p_smooth(PChart *chart) +{ + PEdge *e; + PVert *v; + PFace *f; + int j, it2, maxiter2, it; + int nedges = chart->nedges, nwheel, gridx, gridy; + int edgesx, edgesy, nsize, esize, i, x, y, maxiter, totiter; + float minv[2], maxv[2], median, invmedian, distortion, avglen2d, avglen3d; + float center[2], dx, dy, *nodes, dlimit, d, *oldnodesx, *oldnodesy; + float *nodesx, *nodesy, *hedges, *vedges, climit, moved, padding; + SmoothTriangle *triangles, *t, *t2, **tri, **trip; + SmoothNode *root; + MemArena *arena; + + if (nedges == 0) + return; + + p_chart_uv_bbox(chart, minv, maxv); + median = p_smooth_median_edge_length(chart)*0.10f; + + if (median == 0.0) + return; + + invmedian = 1.0/median; + + /* compute edge distortion */ + distortion = 0.0; + avglen2d = avglen3d = 0.0; + + for (e=chart->edges; e; e=e->nextlink) { + avglen2d += p_edge_uv_length(e); + avglen3d += p_edge_length(e); + } + + avglen2d /= nedges; + avglen3d /= nedges; + + for (v=chart->verts; v; v=v->nextlink) { + v->u.distortion = 0.0; + nwheel = 0; + + e = v->edge; + do { + v->u.distortion += p_smooth_distortion(e, avglen2d, avglen3d); + nwheel++; + + e = e->next->next->pair; + } while(e && (e != v->edge)); + + v->u.distortion /= nwheel; + } + + /* need to do excessive grid size checking still */ + center[0] = 0.5f*(minv[0] + maxv[0]); + center[1] = 0.5f*(minv[1] + maxv[1]); + + dx = 0.5f*(maxv[0] - minv[0]); + dy = 0.5f*(maxv[1] - minv[1]); + + padding = 0.15f; + dx += padding*dx + 2.0f*median; + dy += padding*dy + 2.0f*median; + + gridx = (int)(dx*invmedian); + gridy = (int)(dy*invmedian); + + minv[0] = center[0] - median*gridx; + minv[1] = center[1] - median*gridy; + maxv[0] = center[0] + median*gridx; + maxv[1] = center[1] + median*gridy; + + /* create grid */ + gridx = gridx*2 + 1; + gridy = gridy*2 + 1; + + if ((gridx <= 2) || (gridy <= 2)) + return; + + edgesx = gridx-1; + edgesy = gridy-1; + nsize = gridx*gridy; + esize = edgesx*edgesy; + + nodes = MEM_mallocN(sizeof(float)*nsize, "PSmoothNodes"); + nodesx = MEM_mallocN(sizeof(float)*nsize, "PSmoothNodesX"); + nodesy = MEM_mallocN(sizeof(float)*nsize, "PSmoothNodesY"); + oldnodesx = MEM_mallocN(sizeof(float)*nsize, "PSmoothOldNodesX"); + oldnodesy = MEM_mallocN(sizeof(float)*nsize, "PSmoothOldNodesY"); + hedges = MEM_mallocN(sizeof(float)*esize, "PSmoothHEdges"); + vedges = MEM_mallocN(sizeof(float)*esize, "PSmoothVEdges"); + + if (!nodes || !nodesx || !nodesy || !oldnodesx || !oldnodesy || !hedges || !vedges) { + if (nodes) MEM_freeN(nodes); + if (nodesx) MEM_freeN(nodesx); + if (nodesy) MEM_freeN(nodesy); + if (oldnodesx) MEM_freeN(oldnodesx); + if (oldnodesy) MEM_freeN(oldnodesy); + if (hedges) MEM_freeN(hedges); + if (vedges) MEM_freeN(vedges); + + // printf("Not enough memory for area smoothing grid."); + return; + } + + for (x = 0; x < gridx; x++) { + for (y = 0; y < gridy; y++) { + i = x + y*gridx; + + nodesx[i] = minv[0] + median*x; + nodesy[i] = minv[1] + median*y; + + nodes[i] = 1.0f; + } + } + + /* embed in grid */ + for (f=chart->faces; f; f=f->nextlink) { + PEdge *e1 = f->edge, *e2 = e1->next, *e3 = e2->next; + float fmin[2], fmax[2]; + int bx1, by1, bx2, by2; + + INIT_MINMAX2(fmin, fmax); + + DO_MINMAX2(e1->vert->uv, fmin, fmax); + DO_MINMAX2(e2->vert->uv, fmin, fmax); + DO_MINMAX2(e3->vert->uv, fmin, fmax); + + bx1 = (int)((fmin[0] - minv[0])*invmedian); + by1 = (int)((fmin[1] - minv[1])*invmedian); + bx2 = (int)((fmax[0] - minv[0])*invmedian + 2); + by2 = (int)((fmax[1] - minv[1])*invmedian + 2); + + for (x = bx1; x < bx2; x++) { + for (y = by1; y < by2; y++) { + float p[2], b[3]; + + i = x + y*gridx; + + p[0] = nodesx[i]; + p[1] = nodesy[i]; + + p_barycentric_2d(e1->vert->uv, e2->vert->uv, e3->vert->uv, p, b); + + if ((b[0] > 0.0) && (b[1] > 0.0) && (b[2] > 0.0)) { + nodes[i] = e1->vert->u.distortion*b[0]; + nodes[i] += e2->vert->u.distortion*b[1]; + nodes[i] += e3->vert->u.distortion*b[2]; + } + } + } + } + + /* smooth the grid */ + maxiter = 10; + totiter = 0; + climit = 0.00001f*nsize; + + for (it = 0; it < maxiter; it++) { + moved = 0.0f; + + for (x = 0; x < edgesx; x++) { + for (y = 0; y < edgesy; y++) { + i = x + y*gridx; + j = x + y*edgesx; + + hedges[j] = (nodes[i] + nodes[i+1])*0.5f; + vedges[j] = (nodes[i] + nodes[i+gridx])*0.5f; + + /* we do *inverse* mapping */ + hedges[j] = 1.0f/hedges[j]; + vedges[j] = 1.0f/vedges[j]; + } + } + + maxiter2 = 50; + dlimit = 0.0001f; + + for (it2 = 0; it2 < maxiter2; it2++) { + d = 0.0f; + totiter += 1; + + memcpy(oldnodesx, nodesx, sizeof(float)*nsize); + memcpy(oldnodesy, nodesy, sizeof(float)*nsize); + + for (x=1; x < gridx-1; x++) { + for (y=1; y < gridy-1; y++) { + float p[2], oldp[2], sum1, sum2, diff[2], length; + + i = x + gridx*y; + j = x + edgesx*y; + + oldp[0] = oldnodesx[i]; + oldp[1] = oldnodesy[i]; + + sum1 = hedges[j-1]*oldnodesx[i-1]; + sum1 += hedges[j]*oldnodesx[i+1]; + sum1 += vedges[j-edgesx]*oldnodesx[i-gridx]; + sum1 += vedges[j]*oldnodesx[i+gridx]; + + sum2 = hedges[j-1]; + sum2 += hedges[j]; + sum2 += vedges[j-edgesx]; + sum2 += vedges[j]; + + nodesx[i] = sum1/sum2; + + sum1 = hedges[j-1]*oldnodesy[i-1]; + sum1 += hedges[j]*oldnodesy[i+1]; + sum1 += vedges[j-edgesx]*oldnodesy[i-gridx]; + sum1 += vedges[j]*oldnodesy[i+gridx]; + + nodesy[i] = sum1/sum2; + + p[0] = nodesx[i]; + p[1] = nodesy[i]; + + diff[0] = p[0] - oldp[0]; + diff[1] = p[1] - oldp[1]; + + length = sqrt(diff[0]*diff[0] + diff[1]*diff[1]); + d = MAX2(d, length); + moved += length; + } + } + + if (d < dlimit) + break; + } + + if (moved < climit) + break; + } + + MEM_freeN(oldnodesx); + MEM_freeN(oldnodesy); + MEM_freeN(hedges); + MEM_freeN(vedges); + + /* create bsp */ + t = triangles = MEM_mallocN(sizeof(SmoothTriangle)*esize*2, "PSmoothTris"); + trip = tri = MEM_mallocN(sizeof(SmoothTriangle*)*esize*2, "PSmoothTriP"); + + if (!triangles || !tri) { + MEM_freeN(nodes); + MEM_freeN(nodesx); + MEM_freeN(nodesy); + + if (triangles) MEM_freeN(triangles); + if (tri) MEM_freeN(tri); + + // printf("Not enough memory for area smoothing grid."); + return; + } + + for (x = 0; x < edgesx; x++) { + for (y = 0; y < edgesy; y++) { + i = x + y*gridx; + + t->co1[0] = nodesx[i]; + t->co1[1] = nodesy[i]; + + t->co2[0] = nodesx[i+1]; + t->co2[1] = nodesy[i+1]; + + t->co3[0] = nodesx[i+gridx]; + t->co3[1] = nodesy[i+gridx]; + + t->oco1[0] = minv[0] + x*median; + t->oco1[1] = minv[1] + y*median; + + t->oco2[0] = minv[0] + (x+1)*median; + t->oco2[1] = minv[1] + y*median; + + t->oco3[0] = minv[0] + x*median; + t->oco3[1] = minv[1] + (y+1)*median; + + t2 = t+1; + + t2->co1[0] = nodesx[i+gridx+1]; + t2->co1[1] = nodesy[i+gridx+1]; + + t2->oco1[0] = minv[0] + (x+1)*median; + t2->oco1[1] = minv[1] + (y+1)*median; + + t2->co2[0] = t->co2[0]; t2->co2[1] = t->co2[1]; + t2->oco2[0] = t->oco2[0]; t2->oco2[1] = t->oco2[1]; + + t2->co3[0] = t->co3[0]; t2->co3[1] = t->co3[1]; + t2->oco3[0] = t->oco3[0]; t2->oco3[1] = t->oco3[1]; + + *trip = t; trip++; t++; + *trip = t; trip++; t++; + } + } + + MEM_freeN(nodes); + MEM_freeN(nodesx); + MEM_freeN(nodesy); + + arena = BLI_memarena_new(1<<16); + root = p_node_new(arena, tri, esize*2, minv, maxv, 0); + + for (v=chart->verts; v; v=v->nextlink) + if (!p_node_intersect(root, v->uv)) + param_warning("area smoothing error: couldn't find mapping triangle\n"); + + p_node_delete(root); + BLI_memarena_free(arena); + + MEM_freeN(triangles); +} + +/* Exported */ + +ParamHandle *param_construct_begin() +{ + PHandle *handle = MEM_callocN(sizeof*handle, "PHandle"); + handle->construction_chart = p_chart_new(handle); + handle->state = PHANDLE_STATE_ALLOCATED; + handle->arena = BLI_memarena_new((1<<16)); + handle->aspx = 1.0f; + handle->aspy = 1.0f; + + handle->hash_verts = phash_new((PHashLink**)&handle->construction_chart->verts, 1); + handle->hash_edges = phash_new((PHashLink**)&handle->construction_chart->edges, 1); + handle->hash_faces = phash_new((PHashLink**)&handle->construction_chart->faces, 1); + + return (ParamHandle*)handle; +} + +void param_aspect_ratio(ParamHandle *handle, float aspx, float aspy) +{ + PHandle *phandle = (PHandle*)handle; + + phandle->aspx = aspx; + phandle->aspy = aspy; +} + +void param_delete(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + int i; + + param_assert((phandle->state == PHANDLE_STATE_ALLOCATED) || + (phandle->state == PHANDLE_STATE_CONSTRUCTED)); + + for (i = 0; i < phandle->ncharts; i++) + p_chart_delete(phandle->charts[i]); + + if (phandle->charts) + MEM_freeN(phandle->charts); + + if (phandle->construction_chart) { + p_chart_delete(phandle->construction_chart); + + phash_delete(phandle->hash_verts); + phash_delete(phandle->hash_edges); + phash_delete(phandle->hash_faces); + } + + BLI_memarena_free(phandle->arena); + MEM_freeN(phandle); +} + +void param_face_add(ParamHandle *handle, ParamKey key, int nverts, + ParamKey *vkeys, float **co, float **uv, + ParamBool *pin, ParamBool *select) +{ + PHandle *phandle = (PHandle*)handle; + + param_assert(phash_lookup(phandle->hash_faces, key) == NULL); + param_assert(phandle->state == PHANDLE_STATE_ALLOCATED); + param_assert((nverts == 3) || (nverts == 4)); + + if (nverts == 4) { + if (p_quad_split_direction(phandle, co, vkeys)) { + p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select); + p_face_add_construct(phandle, key, vkeys, co, uv, 0, 2, 3, pin, select); + } + else { + p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 3, pin, select); + p_face_add_construct(phandle, key, vkeys, co, uv, 1, 2, 3, pin, select); + } + } + else + p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select); +} + +void param_edge_set_seam(ParamHandle *handle, ParamKey *vkeys) +{ + PHandle *phandle = (PHandle*)handle; + PEdge *e; + + param_assert(phandle->state == PHANDLE_STATE_ALLOCATED); + + e = p_edge_lookup(phandle, vkeys); + if (e) + e->flag |= PEDGE_SEAM; +} + +void param_construct_end(ParamHandle *handle, ParamBool fill, ParamBool impl) +{ + PHandle *phandle = (PHandle*)handle; + PChart *chart = phandle->construction_chart; + int i, j, nboundaries = 0; + PEdge *outer; + + param_assert(phandle->state == PHANDLE_STATE_ALLOCATED); + + phandle->ncharts = p_connect_pairs(phandle, impl); + phandle->charts = p_split_charts(phandle, chart, phandle->ncharts); + + p_chart_delete(phandle->construction_chart); + phandle->construction_chart = NULL; + + phash_delete(phandle->hash_verts); + phash_delete(phandle->hash_edges); + phash_delete(phandle->hash_faces); + phandle->hash_verts = phandle->hash_edges = phandle->hash_faces = NULL; + + for (i = j = 0; i < phandle->ncharts; i++) { + PVert *v; + chart = phandle->charts[i]; + + p_chart_boundaries(chart, &nboundaries, &outer); + + if (!impl && nboundaries == 0) { + p_chart_delete(chart); + continue; + } + + phandle->charts[j] = chart; + j++; + + if (fill && (nboundaries > 1)) + p_chart_fill_boundaries(chart, outer); + + for (v=chart->verts; v; v=v->nextlink) + p_vert_load_pin_select_uvs(handle, v); + } + + phandle->ncharts = j; + + phandle->state = PHANDLE_STATE_CONSTRUCTED; +} + +void param_lscm_begin(ParamHandle *handle, ParamBool live, ParamBool abf) +{ + PHandle *phandle = (PHandle*)handle; + PFace *f; + int i; + + param_assert(phandle->state == PHANDLE_STATE_CONSTRUCTED); + phandle->state = PHANDLE_STATE_LSCM; + + for (i = 0; i < phandle->ncharts; i++) { + for (f=phandle->charts[i]->faces; f; f=f->nextlink) + p_face_backup_uvs(f); + p_chart_lscm_begin(phandle->charts[i], live, abf); + } +} + +void param_lscm_solve(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + PChart *chart; + int i; + PBool result; + + param_assert(phandle->state == PHANDLE_STATE_LSCM); + + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + + if (chart->u.lscm.context) { + result = p_chart_lscm_solve(phandle, chart); + + if (result && !(chart->flag & PCHART_NOPACK)) + p_chart_rotate_minimum_area(chart); + + if (!result || (chart->u.lscm.pin1)) + p_chart_lscm_end(chart); + } + } +} + +void param_lscm_end(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + int i; + + param_assert(phandle->state == PHANDLE_STATE_LSCM); + + for (i = 0; i < phandle->ncharts; i++) { + p_chart_lscm_end(phandle->charts[i]); +#if 0 + p_chart_complexify(phandle->charts[i]); +#endif + } + + phandle->state = PHANDLE_STATE_CONSTRUCTED; +} + +void param_stretch_begin(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + PChart *chart; + PVert *v; + PFace *f; + int i; + + param_assert(phandle->state == PHANDLE_STATE_CONSTRUCTED); + phandle->state = PHANDLE_STATE_STRETCH; + + phandle->rng = rng_new(31415926); + phandle->blend = 0.0f; + + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + + for (v=chart->verts; v; v=v->nextlink) + v->flag &= ~PVERT_PIN; /* don't use user-defined pins */ + + p_stretch_pin_boundary(chart); + + for (f=chart->faces; f; f=f->nextlink) { + p_face_backup_uvs(f); + f->u.area3d = p_face_area(f); + } + } +} + +void param_stretch_blend(ParamHandle *handle, float blend) +{ + PHandle *phandle = (PHandle*)handle; + + param_assert(phandle->state == PHANDLE_STATE_STRETCH); + phandle->blend = blend; +} + +void param_stretch_iter(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + PChart *chart; + int i; + + param_assert(phandle->state == PHANDLE_STATE_STRETCH); + + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + p_chart_stretch_minimize(chart, phandle->rng); + } +} + +void param_stretch_end(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + + param_assert(phandle->state == PHANDLE_STATE_STRETCH); + phandle->state = PHANDLE_STATE_CONSTRUCTED; + + rng_free(phandle->rng); + phandle->rng = NULL; +} + +void param_smooth_area(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + int i; + + param_assert(phandle->state == PHANDLE_STATE_CONSTRUCTED); + + for (i = 0; i < phandle->ncharts; i++) { + PChart *chart = phandle->charts[i]; + PVert *v; + + for (v=chart->verts; v; v=v->nextlink) + v->flag &= ~PVERT_PIN; + + p_smooth(chart); + } +} + +void param_pack(ParamHandle *handle, float margin) +{ + /* box packing variables */ + boxPack *boxarray, *box; + float tot_width, tot_height, scale; + + PChart *chart; + int i, unpacked=0; + float trans[2]; + double area= 0.0; + + PHandle *phandle = (PHandle*)handle; + + if (phandle->ncharts == 0) + return; + + if(phandle->aspx != phandle->aspy) + param_scale(handle, 1.0f/phandle->aspx, 1.0f/phandle->aspy); + + /* we may not use all these boxes */ + boxarray = MEM_mallocN( phandle->ncharts*sizeof(boxPack), "boxPack box"); + + + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + + if (chart->flag & PCHART_NOPACK) { + unpacked++; + continue; + } + + box = boxarray+(i-unpacked); + + p_chart_uv_bbox(chart, trans, chart->u.pack.size); + + trans[0] = -trans[0]; + trans[1] = -trans[1]; + + p_chart_uv_translate(chart, trans); + + box->w = chart->u.pack.size[0] + trans[0]; + box->h = chart->u.pack.size[1] + trans[1]; + box->index = i; /* warning this index skips PCHART_NOPACK boxes */ + + if(margin>0.0f) + area += sqrt(box->w*box->h); + } + + if(margin>0.0f) { + /* multiply the margin by the area to give pradictable results not dependant on UV scale, + * ...Without using the area running pack multiple times also gives a bad feedback loop. + * multiply by 0.1 so the margin value from the UI can be from 0.0 to 1.0 but not give a massive margin */ + margin = (margin*(float)area) * 0.1; + unpacked= 0; + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + + if (chart->flag & PCHART_NOPACK) { + unpacked++; + continue; + } + + box = boxarray+(i-unpacked); + trans[0] = margin; + trans[1] = margin; + p_chart_uv_translate(chart, trans); + box->w += margin*2; + box->h += margin*2; + } + } + + boxPack2D(boxarray, phandle->ncharts-unpacked, &tot_width, &tot_height); + + if (tot_height>tot_width) + scale = 1.0/tot_height; + else + scale = 1.0/tot_width; + + for (i = 0; i < phandle->ncharts-unpacked; i++) { + box = boxarray+i; + trans[0] = box->x; + trans[1] = box->y; + + chart = phandle->charts[box->index]; + p_chart_uv_translate(chart, trans); + p_chart_uv_scale(chart, scale); + } + MEM_freeN(boxarray); + + if(phandle->aspx != phandle->aspy) + param_scale(handle, phandle->aspx, phandle->aspy); +} + +void param_average(ParamHandle *handle) +{ + PChart *chart; + int i; + float tot_uvarea = 0.0f, tot_facearea = 0.0f; + float tot_fac, fac; + float minv[2], maxv[2], trans[2]; + PHandle *phandle = (PHandle*)handle; + + if (phandle->ncharts == 0) + return; + + for (i = 0; i < phandle->ncharts; i++) { + PFace *f; + chart = phandle->charts[i]; + + chart->u.pack.area = 0.0f; /* 3d area */ + chart->u.pack.rescale = 0.0f; /* UV area, abusing rescale for tmp storage, oh well :/ */ + + for (f=chart->faces; f; f=f->nextlink) { + chart->u.pack.area += p_face_area(f); + chart->u.pack.rescale += fabs(p_face_uv_area_signed(f)); + } + + tot_facearea += chart->u.pack.area; + tot_uvarea += chart->u.pack.rescale; + } + + if (tot_facearea == tot_uvarea || tot_facearea==0.0f || tot_uvarea==0.0f) { + /* nothing to do */ + return; + } + + tot_fac = tot_facearea/tot_uvarea; + + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + if (chart->u.pack.area != 0.0f && chart->u.pack.rescale != 0.0f) { + fac = chart->u.pack.area / chart->u.pack.rescale; + + /* Get the island center */ + p_chart_uv_bbox(chart, minv, maxv); + trans[0] = (minv[0] + maxv[0]) /-2.0f; + trans[1] = (minv[1] + maxv[1]) /-2.0f; + + /* Move center to 0,0 */ + p_chart_uv_translate(chart, trans); + p_chart_uv_scale(chart, sqrt(fac / tot_fac)); + + /* Move to original center */ + trans[0] = -trans[0]; + trans[1] = -trans[1]; + p_chart_uv_translate(chart, trans); + } + } +} + +void param_scale(ParamHandle *handle, float x, float y) +{ + PHandle *phandle = (PHandle*)handle; + PChart *chart; + int i; + + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + p_chart_uv_scale_xy(chart, x, y); + } +} + +void param_flush(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + PChart *chart; + int i; + + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + + if ((phandle->state == PHANDLE_STATE_LSCM) && !chart->u.lscm.context) + continue; + + if (phandle->blend == 0.0f) + p_flush_uvs(phandle, chart); + else + p_flush_uvs_blend(phandle, chart, phandle->blend); + } +} + +void param_flush_restore(ParamHandle *handle) +{ + PHandle *phandle = (PHandle*)handle; + PChart *chart; + PFace *f; + int i; + + for (i = 0; i < phandle->ncharts; i++) { + chart = phandle->charts[i]; + + for (f=chart->faces; f; f=f->nextlink) + p_face_restore_uvs(f); + } +} + diff --git a/source/blender/editors/uvedit/uvedit_parametrizer.h b/source/blender/editors/uvedit/uvedit_parametrizer.h new file mode 100644 index 00000000000..f1454ee3865 --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_parametrizer.h @@ -0,0 +1,97 @@ + +#ifndef __PARAMETRIZER_H__ +#define __PARAMETRIZER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "BLO_sys_types.h" // for intptr_t support + +typedef void ParamHandle; /* handle to a set of charts */ +typedef intptr_t ParamKey; /* (hash) key for identifying verts and faces */ +typedef enum ParamBool { + PARAM_TRUE = 1, + PARAM_FALSE = 0 +} ParamBool; + +/* Chart construction: + ------------------- + - faces and seams may only be added between construct_{begin|end} + - the pointers to co and uv are stored, rather than being copied + - vertices are implicitly created + - in construct_end the mesh will be split up according to the seams + - the resulting charts must be: + - manifold, connected, open (at least one boundary loop) + - output will be written to the uv pointers +*/ + +ParamHandle *param_construct_begin(); + +void param_aspect_ratio(ParamHandle *handle, float aspx, float aspy); + +void param_face_add(ParamHandle *handle, + ParamKey key, + int nverts, + ParamKey *vkeys, + float **co, + float **uv, + ParamBool *pin, + ParamBool *select); + +void param_edge_set_seam(ParamHandle *handle, + ParamKey *vkeys); + +void param_construct_end(ParamHandle *handle, ParamBool fill, ParamBool impl); +void param_delete(ParamHandle *chart); + +/* Least Squares Conformal Maps: + ----------------------------- + - charts with less than two pinned vertices are assigned 2 pins + - lscm is divided in three steps: + - begin: compute matrix and it's factorization (expensive) + - solve using pinned coordinates (cheap) + - end: clean up + - uv coordinates are allowed to change within begin/end, for + quick re-solving +*/ + +void param_lscm_begin(ParamHandle *handle, ParamBool live, ParamBool abf); +void param_lscm_solve(ParamHandle *handle); +void param_lscm_end(ParamHandle *handle); + +/* Stretch */ + +void param_stretch_begin(ParamHandle *handle); +void param_stretch_blend(ParamHandle *handle, float blend); +void param_stretch_iter(ParamHandle *handle); +void param_stretch_end(ParamHandle *handle); + +/* Area Smooth */ + +void param_smooth_area(ParamHandle *handle); + +/* Packing */ + +void param_pack(ParamHandle *handle, float margin); + +/* Average area for all charts */ + +void param_average(ParamHandle *handle); + +/* Simple x,y scale */ + +void param_scale(ParamHandle *handle, float x, float y); + +/* Flushing */ + +void param_flush(ParamHandle *handle); +void param_flush_restore(ParamHandle *handle); + + +#ifdef __cplusplus +} +#endif + +#endif /*__PARAMETRIZER_H__*/ + diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c new file mode 100644 index 00000000000..27d0c68ec36 --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c @@ -0,0 +1,1347 @@ +/** + * $Id$ + * + * ***** BEGIN GPL 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. + * + * 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 LICENSE BLOCK ***** + */ + +#include <string.h> +#include <stdlib.h> +#include <math.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_depsgraph.h" +#include "BKE_global.h" +#include "BKE_mesh.h" +#include "BKE_utildefines.h" + +#include "BLI_arithb.h" +#include "BLI_edgehash.h" +#include "BLI_editVert.h" + +#include "PIL_time.h" + +#include "ED_image.h" +#include "ED_mesh.h" +#include "ED_screen.h" +#include "ED_uvedit.h" +#include "ED_view3d.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "uvedit_intern.h" +#include "uvedit_parametrizer.h" + +static int ED_uvedit_ensure_uvs(bContext *C, Scene *scene, Object *obedit) +{ + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + + if(ED_uvedit_test(obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return 1; + } + + if(em && em->faces.first) + EM_add_data_layer(em, &em->fdata, CD_MTFACE); + + if(!ED_uvedit_test(obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return 0; + } + + // XXX this image is not in context in 3d view .. only + // way to get would be to find the first image window? + ED_uvedit_assign_image(scene, obedit, CTX_data_edit_image(C), NULL); + + /* select new UV's */ + for(efa=em->faces.first; efa; efa=efa->next) { + tf= (MTFace *)CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + uvedit_face_select(scene, efa, tf); + } + + BKE_mesh_end_editmesh(obedit->data, em); + return 1; +} + +/****************** Parametrizer Conversion ***************/ + +ParamHandle *construct_param_handle(Scene *scene, EditMesh *em, short implicit, short fill, short sel, short correct_aspect) +{ + ParamHandle *handle; + EditFace *efa; + EditEdge *eed; + EditVert *ev; + MTFace *tf; + int a; + + handle = param_construct_begin(); + + if(correct_aspect) { + efa = EM_get_actFace(em, 1); + + if(efa) { + MTFace *tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + float aspx, aspy; + + ED_image_uv_aspect(tf->tpage, &aspx, &aspy); + + if(aspx!=aspy) + param_aspect_ratio(handle, aspx, aspy); + } + } + + /* we need the vert indicies */ + for(ev= em->verts.first, a=0; ev; ev= ev->next, a++) + ev->tmp.l = a; + + for(efa= em->faces.first; efa; efa= efa->next) { + ParamKey key, vkeys[4]; + ParamBool pin[4], select[4]; + float *co[4]; + float *uv[4]; + int nverts; + + if((efa->h) || (sel && (efa->f & SELECT)==0)) + continue; + + tf= (MTFace *)CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + if(implicit && + !( uvedit_uv_selected(scene, efa, tf, 0) || + uvedit_uv_selected(scene, efa, tf, 1) || + uvedit_uv_selected(scene, efa, tf, 2) || + (efa->v4 && uvedit_uv_selected(scene, efa, tf, 3)) ) + ) { + continue; + } + + key = (ParamKey)efa; + vkeys[0] = (ParamKey)efa->v1->tmp.l; + vkeys[1] = (ParamKey)efa->v2->tmp.l; + vkeys[2] = (ParamKey)efa->v3->tmp.l; + + co[0] = efa->v1->co; + co[1] = efa->v2->co; + co[2] = efa->v3->co; + + uv[0] = tf->uv[0]; + uv[1] = tf->uv[1]; + uv[2] = tf->uv[2]; + + pin[0] = ((tf->unwrap & TF_PIN1) != 0); + pin[1] = ((tf->unwrap & TF_PIN2) != 0); + pin[2] = ((tf->unwrap & TF_PIN3) != 0); + + select[0] = ((uvedit_uv_selected(scene, efa, tf, 0)) != 0); + select[1] = ((uvedit_uv_selected(scene, efa, tf, 1)) != 0); + select[2] = ((uvedit_uv_selected(scene, efa, tf, 2)) != 0); + + if(efa->v4) { + vkeys[3] = (ParamKey)efa->v4->tmp.l; + co[3] = efa->v4->co; + uv[3] = tf->uv[3]; + pin[3] = ((tf->unwrap & TF_PIN4) != 0); + select[3] = (uvedit_uv_selected(scene, efa, tf, 3) != 0); + nverts = 4; + } + else + nverts = 3; + + param_face_add(handle, key, nverts, vkeys, co, uv, pin, select); + } + + if(!implicit) { + for(eed= em->edges.first; eed; eed= eed->next) { + if(eed->seam) { + ParamKey vkeys[2]; + vkeys[0] = (ParamKey)eed->v1->tmp.l; + vkeys[1] = (ParamKey)eed->v2->tmp.l; + param_edge_set_seam(handle, vkeys); + } + } + } + + param_construct_end(handle, fill, implicit); + + return handle; +} + +/* ******************** Minimize Stretch operator **************** */ + +typedef struct MinStretch { + Scene *scene; + Object *obedit; + EditMesh *em; + ParamHandle *handle; + float blend; + double lasttime; + int i, iterations; + wmTimer *timer; +} MinStretch; + +static void minimize_stretch_init(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + MinStretch *ms; + int fill_holes= RNA_boolean_get(op->ptr, "fill_holes"); + + ms= MEM_callocN(sizeof(MinStretch), "MinStretch"); + ms->scene= scene; + ms->obedit= obedit; + ms->em= em; + ms->blend= RNA_float_get(op->ptr, "blend"); + ms->iterations= RNA_int_get(op->ptr, "iterations"); + ms->handle= construct_param_handle(scene, em, 1, fill_holes, 1, 1); + ms->lasttime= PIL_check_seconds_timer(); + + param_stretch_begin(ms->handle); + if(ms->blend != 0.0f) + param_stretch_blend(ms->handle, ms->blend); + + op->customdata= ms; +} + +static void minimize_stretch_iteration(bContext *C, wmOperator *op, int interactive) +{ + MinStretch *ms= op->customdata; + ScrArea *sa= CTX_wm_area(C); + + param_stretch_blend(ms->handle, ms->blend); + param_stretch_iter(ms->handle); + + if(interactive && (PIL_check_seconds_timer() - ms->lasttime > 0.5)) { + char str[100]; + + param_flush(ms->handle); + + if(sa) { + sprintf(str, "Minimize Stretch. Blend %.2f.", ms->blend); + ED_area_headerprint(sa, str); + } + + ms->lasttime = PIL_check_seconds_timer(); + + DAG_object_flush_update(ms->scene, ms->obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, ms->obedit); + } +} + +static void minimize_stretch_exit(bContext *C, wmOperator *op, int cancel) +{ + MinStretch *ms= op->customdata; + ScrArea *sa= CTX_wm_area(C); + + if(sa) + ED_area_headerprint(sa, NULL); + if(ms->timer) + WM_event_remove_window_timer(CTX_wm_window(C), ms->timer); + + if(cancel) + param_flush_restore(ms->handle); + else + param_flush(ms->handle); + + param_stretch_end(ms->handle); + param_delete(ms->handle); + + DAG_object_flush_update(ms->scene, ms->obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, ms->obedit); + + MEM_freeN(ms); + op->customdata= NULL; +} + +static int minimize_stretch_exec(bContext *C, wmOperator *op) +{ + int i, iterations; + + minimize_stretch_init(C, op); + + iterations= RNA_int_get(op->ptr, "iterations"); + for(i=0; i<iterations; i++) + minimize_stretch_iteration(C, op, 0); + minimize_stretch_exit(C, op, 0); + + return OPERATOR_FINISHED; +} + +static int minimize_stretch_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + MinStretch *ms; + + minimize_stretch_init(C, op); + minimize_stretch_iteration(C, op, 1); + + ms= op->customdata; + WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op); + ms->timer= WM_event_add_window_timer(CTX_wm_window(C), TIMER, 0.01f); + + return OPERATOR_RUNNING_MODAL; +} + +static int minimize_stretch_modal(bContext *C, wmOperator *op, wmEvent *event) +{ + MinStretch *ms= op->customdata; + + switch(event->type) { + case ESCKEY: + case RIGHTMOUSE: + minimize_stretch_exit(C, op, 1); + return OPERATOR_CANCELLED; + case RETKEY: + case PADENTER: + case LEFTMOUSE: + minimize_stretch_exit(C, op, 0); + return OPERATOR_FINISHED; + case PADPLUSKEY: + case WHEELUPMOUSE: + if(ms->blend < 0.95f) { + ms->blend += 0.1f; + ms->lasttime= 0.0f; + RNA_float_set(op->ptr, "blend", ms->blend); + minimize_stretch_iteration(C, op, 1); + } + break; + case PADMINUS: + case WHEELDOWNMOUSE: + if(ms->blend > 0.05f) { + ms->blend -= 0.1f; + ms->lasttime= 0.0f; + RNA_float_set(op->ptr, "blend", ms->blend); + minimize_stretch_iteration(C, op, 1); + } + break; + case TIMER: + if(ms->timer == event->customdata) { + double start= PIL_check_seconds_timer(); + + do { + minimize_stretch_iteration(C, op, 1); + } while(PIL_check_seconds_timer() - start < 0.01); + } + break; + } + + if(ms->iterations && ms->i >= ms->iterations) { + minimize_stretch_exit(C, op, 0); + return OPERATOR_FINISHED; + } + + return OPERATOR_RUNNING_MODAL; +} + +static int minimize_stretch_cancel(bContext *C, wmOperator *op) +{ + minimize_stretch_exit(C, op, 1); + + return OPERATOR_CANCELLED; +} + +void UV_OT_minimize_stretch(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Minimize Stretch"; + ot->idname= "UV_OT_minimize_stretch"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= minimize_stretch_exec; + ot->invoke= minimize_stretch_invoke; + ot->modal= minimize_stretch_modal; + ot->cancel= minimize_stretch_cancel; + ot->poll= ED_operator_uvedit; + + /* properties */ + RNA_def_boolean(ot->srna, "fill_holes", 1, "Fill Holes", "Virtual fill holes in mesh before unwrapping, to better avoid overlaps and preserve symmetry."); + RNA_def_float_percentage(ot->srna, "blend", 0.0f, 0.0f, 1.0f, "Blend", "Blend factor between stretch minimized and original.", 0.0f, 1.0f); + RNA_def_int(ot->srna, "iterations", 0, 0, INT_MAX, "Iterations", "Number of iterations to run, 0 is unlimited when run interactively.", 0, 100); +} + +/* ******************** Pack Islands operator **************** */ + +static int pack_islands_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ParamHandle *handle; + + handle = construct_param_handle(scene, em, 1, 0, 1, 1); + param_pack(handle, scene->toolsettings->uvcalc_margin); + param_flush(handle); + param_delete(handle); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_pack_islands(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Pack Islands"; + ot->idname= "UV_OT_pack_islands"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= pack_islands_exec; + ot->poll= ED_operator_uvedit; +} + +/* ******************** Average Islands Scale operator **************** */ + +static int average_islands_scale_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ParamHandle *handle; + + handle= construct_param_handle(scene, em, 1, 0, 1, 1); + param_average(handle); + param_flush(handle); + param_delete(handle); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_average_islands_scale(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Average Islands Scale"; + ot->idname= "UV_OT_average_islands_scale"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= average_islands_scale_exec; + ot->poll= ED_operator_uvedit; +} + +/**************** Live Unwrap *****************/ + +static ParamHandle *liveHandle = NULL; + +void ED_uvedit_live_unwrap_begin(Scene *scene, Object *obedit) +{ + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + short abf = scene->toolsettings->unwrapper == 1; + short fillholes = scene->toolsettings->uvcalc_flag & UVCALC_FILLHOLES; + + if(!ED_uvedit_test(obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return; + } + + liveHandle = construct_param_handle(scene, em, 0, fillholes, 1, 1); + + param_lscm_begin(liveHandle, PARAM_TRUE, abf); + BKE_mesh_end_editmesh(obedit->data, em); +} + +void ED_uvedit_live_unwrap_re_solve(void) +{ + if(liveHandle) { + param_lscm_solve(liveHandle); + param_flush(liveHandle); + } +} + +void ED_uvedit_live_unwrap_end(short cancel) +{ + if(liveHandle) { + param_lscm_end(liveHandle); + if(cancel) + param_flush_restore(liveHandle); + param_delete(liveHandle); + liveHandle = NULL; + } +} + +/*************** UV Map Common Transforms *****************/ + +#define VIEW_ON_EQUATOR 0 +#define VIEW_ON_POLES 1 +#define ALIGN_TO_OBJECT 2 + +#define POLAR_ZX 0 +#define POLAR_ZY 1 + +static void uv_map_transform_center(Scene *scene, View3D *v3d, float *result, Object *ob, EditMesh *em) +{ + EditFace *efa; + float min[3], max[3], *cursx; + int around= (v3d)? v3d->around: V3D_CENTER; + + /* only operates on the edit object - this is all that's needed now */ + + switch(around) { + case V3D_CENTER: /* bounding box center */ + min[0]= min[1]= min[2]= 1e20f; + max[0]= max[1]= max[2]= -1e20f; + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + DO_MINMAX(efa->v1->co, min, max); + DO_MINMAX(efa->v2->co, min, max); + DO_MINMAX(efa->v3->co, min, max); + if(efa->v4) DO_MINMAX(efa->v4->co, min, max); + } + } + VecMidf(result, min, max); + break; + + case V3D_CURSOR: /*cursor center*/ + cursx= give_cursor(scene, v3d); + /* 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_map_rotation_matrix(float result[][4], RegionView3D *rv3d, Object *ob, float upangledeg, float sideangledeg, float radius) +{ + float rotup[4][4], rotside[4][4], viewmatrix[4][4], rotobj[4][4]; + float sideangle= 0.0f, upangle= 0.0f; + int k; + + /* get rotation of the current view matrix */ + if(rv3d) + Mat4CpyMat4(viewmatrix, rv3d->viewmat); + else + Mat4One(viewmatrix); + + /* but shifting */ + for(k=0; k<4; k++) + viewmatrix[3][k] =0.0f; + + /* get rotation of the current object matrix */ + Mat4CpyMat4(rotobj,ob->obmat); + + /* but shifting */ + for(k=0; k<4; k++) + rotobj[3][k] =0.0f; + + 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= (float)M_PI*(sideangledeg + 180.0f)/180.0f; + 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= (float)M_PI*upangledeg/180.0f; + 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.0f/radius; + + /* calculate transforms*/ + Mat4MulSerie(result, rotup, rotside, viewmatrix, rotobj, NULL, NULL, NULL, NULL); +} + +static void uv_map_transform(bContext *C, wmOperator *op, float center[3], float rotmat[4][4]) +{ + /* context checks are messy here, making it work in both 3d view and uv editor */ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + View3D *v3d= CTX_wm_view3d(C); + RegionView3D *rv3d= CTX_wm_region_view3d(C); + /* common operator properties */ + int align= RNA_enum_get(op->ptr, "align"); + int direction= RNA_enum_get(op->ptr, "direction"); + float radius= RNA_struct_find_property(op->ptr, "radius")? RNA_float_get(op->ptr, "radius"): 1.0f; + float upangledeg, sideangledeg; + + uv_map_transform_center(scene, v3d, center, obedit, em); + + if(direction == VIEW_ON_EQUATOR) { + upangledeg= 90.0f; + sideangledeg= 0.0f; + } + else { + upangledeg= 0.0f; + if(align == POLAR_ZY) sideangledeg= 0.0f; + else sideangledeg= 90.0f; + } + + /* be compatible to the "old" sphere/cylinder mode */ + if(direction == ALIGN_TO_OBJECT) + Mat4One(rotmat); + else + uv_map_rotation_matrix(rotmat, rv3d, obedit, upangledeg, sideangledeg, radius); + + BKE_mesh_end_editmesh(obedit->data, em); +} + +static void uv_transform_properties(wmOperatorType *ot, int radius) +{ + static EnumPropertyItem direction_items[]= { + {VIEW_ON_EQUATOR, "VIEW_ON_EQUATOR", "View on Equator", "3D view is on the equator."}, + {VIEW_ON_POLES, "VIEW_ON_POLES", "View on Poles", "3D view is on the poles."}, + {ALIGN_TO_OBJECT, "ALIGN_TO_OBJECT", "Align to Object", "Align according to object transform."}, + {0, NULL, NULL, NULL} + }; + static EnumPropertyItem align_items[]= { + {POLAR_ZX, "POLAR_ZX", "Polar ZX", "Polar 0 is X."}, + {POLAR_ZY, "POLAR_ZY", "Polar ZY", "Polar 0 is Y."}, + {0, NULL, NULL, NULL} + }; + + RNA_def_enum(ot->srna, "direction", direction_items, VIEW_ON_EQUATOR, "Direction", "Direction of the sphere or cylinder."); + RNA_def_enum(ot->srna, "align", align_items, VIEW_ON_EQUATOR, "Align", "How to determine rotation around the pole."); + if(radius) + RNA_def_float(ot->srna, "radius", 1.0f, 0.0f, FLT_MAX, "Radius", "Radius of the sphere or cylinder.", 0.0001f, 100.0f); +} + +static void correct_uv_aspect(EditMesh *em) +{ + EditFace *efa= EM_get_actFace(em, 1); + MTFace *tf; + float scale, aspx= 1.0f, aspy=1.0f; + + if(efa) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + ED_image_uv_aspect(tf->tpage, &aspx, &aspy); + } + + if(aspx == aspy) + return; + + if(aspx > aspy) { + scale= aspy/aspx; + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + tf->uv[0][0]= ((tf->uv[0][0]-0.5)*scale)+0.5; + tf->uv[1][0]= ((tf->uv[1][0]-0.5)*scale)+0.5; + tf->uv[2][0]= ((tf->uv[2][0]-0.5)*scale)+0.5; + if(efa->v4) + tf->uv[3][0]= ((tf->uv[3][0]-0.5)*scale)+0.5; + } + } + } + else { + scale= aspx/aspy; + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + tf->uv[0][1]= ((tf->uv[0][1]-0.5)*scale)+0.5; + tf->uv[1][1]= ((tf->uv[1][1]-0.5)*scale)+0.5; + tf->uv[2][1]= ((tf->uv[2][1]-0.5)*scale)+0.5; + if(efa->v4) + tf->uv[3][1]= ((tf->uv[3][1]-0.5)*scale)+0.5; + } + } + } +} + +/******************** Map Clip & Correct ******************/ + +static void uv_map_clip_correct_properties(wmOperatorType *ot) +{ + RNA_def_boolean(ot->srna, "correct_aspect", 1, "Correct Aspect", "Map UV's taking image aspect ratio into account."); + RNA_def_boolean(ot->srna, "clip_to_bounds", 0, "Clip to Bounds", "Clip UV coordinates to bounds after unwrapping."); + RNA_def_boolean(ot->srna, "scale_to_bounds", 0, "Scale to Bounds", "Scale UV coordinates to bounds after unwrapping."); +} + +static void uv_map_clip_correct(EditMesh *em, wmOperator *op) +{ + EditFace *efa; + MTFace *tf; + float dx, dy, min[2], max[2]; + int b, nverts; + int correct_aspect= RNA_boolean_get(op->ptr, "correct_aspect"); + int clip_to_bounds= RNA_boolean_get(op->ptr, "clip_to_bounds"); + int scale_to_bounds= RNA_boolean_get(op->ptr, "scale_to_bounds"); + + /* correct for image aspect ratio */ + if(correct_aspect) + correct_uv_aspect(em); + + if(scale_to_bounds) { + INIT_MINMAX2(min, max); + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + DO_MINMAX2(tf->uv[0], min, max); + DO_MINMAX2(tf->uv[1], min, max); + DO_MINMAX2(tf->uv[2], min, max); + + if(efa->v4) + DO_MINMAX2(tf->uv[3], min, max); + } + } + + /* rescale UV to be in 1/1 */ + dx= (max[0]-min[0]); + dy= (max[1]-min[1]); + + if(dx > 0.0f) + dx= 1.0f/dx; + if(dy > 0.0f) + dy= 1.0f/dy; + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + nverts= (efa->v4)? 4: 3; + + for(b=0; b<nverts; b++) { + tf->uv[b][0]= (tf->uv[b][0]-min[0])*dx; + tf->uv[b][1]= (tf->uv[b][1]-min[1])*dy; + } + } + } + } + else if(clip_to_bounds) { + /* clipping and wrapping */ + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + nverts= (efa->v4)? 4: 3; + + for(b=0; b<nverts; b++) { + CLAMP(tf->uv[b][0], 0.0, 1.0); + CLAMP(tf->uv[b][1], 0.0, 1.0); + } + } + } + } +} + +/* ******************** Unwrap operator **************** */ + +static int unwrap_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ParamHandle *handle; + int method = RNA_enum_get(op->ptr, "method"); + int fill_holes = RNA_boolean_get(op->ptr, "fill_holes"); + int correct_aspect = RNA_boolean_get(op->ptr, "correct_aspect"); + + /* add uvs if they don't exist yet */ + if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + handle= construct_param_handle(scene, em, 0, fill_holes, 0, correct_aspect); + + param_lscm_begin(handle, PARAM_FALSE, method == 0); + param_lscm_solve(handle); + param_lscm_end(handle); + + param_pack(handle, scene->toolsettings->uvcalc_margin); + + param_flush(handle); + + param_delete(handle); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_unwrap(wmOperatorType *ot) +{ + static EnumPropertyItem method_items[] = { + {0, "ANGLE_BASED", "Angle Based", ""}, + {1, "CONFORMAL", "Conformal", ""}, + {0, NULL, NULL, NULL}}; + + /* identifiers */ + ot->name= "Unwrap"; + ot->idname= "UV_OT_unwrap"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= unwrap_exec; + ot->poll= ED_operator_uvmap; + + /* properties */ + RNA_def_enum(ot->srna, "method", method_items, 0, "Method", "Unwrapping method. Angle Based usually gives better results than Conformal, while being somewhat slower."); + RNA_def_boolean(ot->srna, "fill_holes", 1, "Fill Holes", "Virtual fill holes in mesh before unwrapping, to better avoid overlaps and preserve symmetry."); + RNA_def_boolean(ot->srna, "correct_aspect", 1, "Correct Aspect", "Map UV's taking image aspect ratio into account."); +} + +/**************** Project From View operator **************/ + +static void uv_from_view_bounds(float target[2], float source[3], float rotmat[4][4]) +{ + float pv[3]; + + Mat4MulVecfl(rotmat, pv); + + /* ortho projection */ + target[0] = -pv[0]; + target[1] = pv[2]; +} + +static void uv_from_view(ARegion *ar, float target[2], float source[3], float rotmat[4][4]) +{ + RegionView3D *rv3d= ar->regiondata; + float pv[3], pv4[4], dx, dy, x= 0.0, y= 0.0; + + Mat4MulVecfl(rotmat, pv); + + dx= ar->winx; + dy= ar->winy; + + VecCopyf(pv4, source); + pv4[3]= 1.0; + + /* rotmat is the object matrix in this case */ + Mat4MulVec4fl(rotmat, pv4); + + /* almost project_short */ + Mat4MulVec4fl(rv3d->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]; + } + + /* v3d->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; +} + +static int from_view_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + ARegion *ar= CTX_wm_region(C); + EditFace *efa; + MTFace *tf; + float rotmat[4][4]; + + /* add uvs if they don't exist yet */ + if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + if(RNA_boolean_get(op->ptr, "orthographic")) { + uv_map_rotation_matrix(rotmat, ar->regiondata, obedit, 90.0f, 0.0f, 1.0f); + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + uv_from_view_bounds(tf->uv[0], efa->v1->co, rotmat); + uv_from_view_bounds(tf->uv[1], efa->v2->co, rotmat); + uv_from_view_bounds(tf->uv[2], efa->v3->co, rotmat); + if(efa->v4) + uv_from_view_bounds(tf->uv[3], efa->v4->co, rotmat); + } + } + } + else { + Mat4CpyMat4(rotmat, obedit->obmat); + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + uv_from_view(ar, tf->uv[0], efa->v1->co, rotmat); + uv_from_view(ar, tf->uv[1], efa->v2->co, rotmat); + uv_from_view(ar, tf->uv[2], efa->v3->co, rotmat); + if(efa->v4) + uv_from_view(ar, tf->uv[3], efa->v4->co, rotmat); + } + } + } + + uv_map_clip_correct(em, op); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +static int from_view_poll(bContext *C) +{ + RegionView3D *rv3d= CTX_wm_region_view3d(C); + + if(!ED_operator_uvmap(C)) + return 0; + + return (rv3d != NULL); +} + +void UV_OT_from_view(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Project From View"; + ot->idname= "UV_OT_project_from_view"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= from_view_exec; + ot->poll= from_view_poll; + + /* properties */ + RNA_def_boolean(ot->srna, "orthographic", 0, "Orthographic", "Use orthographic projection."); + uv_map_clip_correct_properties(ot); +} + +/********************** Reset operator ********************/ + +static int reset_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + + /* add uvs if they don't exist yet */ + if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + tf->uv[0][0]= 0.0f; + tf->uv[0][1]= 0.0f; + + tf->uv[1][0]= 1.0f; + tf->uv[1][1]= 0.0f; + + tf->uv[2][0]= 1.0f; + tf->uv[2][1]= 1.0f; + + tf->uv[3][0]= 0.0f; + tf->uv[3][1]= 1.0f; + } + } + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_reset(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Reset"; + ot->idname= "UV_OT_reset"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= reset_exec; + ot->poll= ED_operator_uvmap; +} + +/****************** Sphere Project operator ***************/ + +static void uv_sphere_project(float target[2], float source[3], float center[3], float rotmat[4][4]) +{ + float pv[3]; + + VecSubf(pv, source, center); + Mat4MulVecfl(rotmat, pv); + + 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; +} + +static void uv_map_mirror(EditFace *efa, MTFace *tf) +{ + float dx; + int nverts, i, mi; + + nverts= (efa->v4)? 4: 3; + + mi = 0; + for(i=1; i<nverts; i++) + if(tf->uv[i][0] > tf->uv[mi][0]) + mi = i; + + for(i=0; i<nverts; i++) { + if(i != mi) { + dx = tf->uv[mi][0] - tf->uv[i][0]; + if(dx > 0.5) tf->uv[i][0] += 1.0; + } + } +} + +static int sphere_project_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + float center[3], rotmat[4][4]; + + /* add uvs if they don't exist yet */ + if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + uv_map_transform(C, op, center, rotmat); + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + uv_sphere_project(tf->uv[0], efa->v1->co, center, rotmat); + uv_sphere_project(tf->uv[1], efa->v2->co, center, rotmat); + uv_sphere_project(tf->uv[2], efa->v3->co, center, rotmat); + if(efa->v4) + uv_sphere_project(tf->uv[3], efa->v4->co, center, rotmat); + + uv_map_mirror(efa, tf); + } + } + + uv_map_clip_correct(em, op); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_sphere_project(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Sphere Projection"; + ot->idname= "UV_OT_sphere_project"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= sphere_project_exec; + ot->poll= ED_operator_uvmap; + + /* properties */ + uv_transform_properties(ot, 0); + uv_map_clip_correct_properties(ot); +} + +/***************** Cylinder Project operator **************/ + +static void uv_cylinder_project(float target[2], float source[3], float center[3], float rotmat[4][4]) +{ + float pv[3]; + + VecSubf(pv, source, center); + Mat4MulVecfl(rotmat, pv); + + 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; +} + +static int cylinder_project_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + float center[3], rotmat[4][4]; + + /* add uvs if they don't exist yet */ + if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + uv_map_transform(C, op, center, rotmat); + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + + uv_cylinder_project(tf->uv[0], efa->v1->co, center, rotmat); + uv_cylinder_project(tf->uv[1], efa->v2->co, center, rotmat); + uv_cylinder_project(tf->uv[2], efa->v3->co, center, rotmat); + if(efa->v4) + uv_cylinder_project(tf->uv[3], efa->v4->co, center, rotmat); + + uv_map_mirror(efa, tf); + } + } + + uv_map_clip_correct(em, op); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_cylinder_project(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Cylinder Projection"; + ot->idname= "UV_OT_cylinder_project"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= cylinder_project_exec; + ot->poll= ED_operator_uvmap; + + /* properties */ + uv_transform_properties(ot, 1); + uv_map_clip_correct_properties(ot); +} + +/******************* Cube Project operator ****************/ + +static int cube_project_exec(bContext *C, wmOperator *op) +{ + Scene *scene= CTX_data_scene(C); + Object *obedit= CTX_data_edit_object(C); + EditMesh *em= BKE_mesh_get_editmesh((Mesh*)obedit->data); + EditFace *efa; + MTFace *tf; + float no[3], cube_size, *loc, dx, dy; + int cox, coy; + + /* add uvs if they don't exist yet */ + if(!ED_uvedit_ensure_uvs(C, scene, obedit)) { + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_CANCELLED; + } + + loc= obedit->obmat[3]; + cube_size= RNA_float_get(op->ptr, "cube_size"); + + /* choose x,y,z axis for projection depending on the largest normal + * component, but clusters all together around the center of map. */ + + for(efa= em->faces.first; efa; efa= efa->next) { + if(efa->f & SELECT) { + tf= CustomData_em_get(&em->fdata, efa->data, CD_MTFACE); + CalcNormFloat(efa->v1->co, efa->v2->co, efa->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; } + + tf->uv[0][0]= 0.5+0.5*cube_size*(loc[cox] + efa->v1->co[cox]); + tf->uv[0][1]= 0.5+0.5*cube_size*(loc[coy] + efa->v1->co[coy]); + dx = floor(tf->uv[0][0]); + dy = floor(tf->uv[0][1]); + tf->uv[0][0] -= dx; + tf->uv[0][1] -= dy; + tf->uv[1][0]= 0.5+0.5*cube_size*(loc[cox] + efa->v2->co[cox]); + tf->uv[1][1]= 0.5+0.5*cube_size*(loc[coy] + efa->v2->co[coy]); + tf->uv[1][0] -= dx; + tf->uv[1][1] -= dy; + tf->uv[2][0]= 0.5+0.5*cube_size*(loc[cox] + efa->v3->co[cox]); + tf->uv[2][1]= 0.5+0.5*cube_size*(loc[coy] + efa->v3->co[coy]); + tf->uv[2][0] -= dx; + tf->uv[2][1] -= dy; + + if(efa->v4) { + tf->uv[3][0]= 0.5+0.5*cube_size*(loc[cox] + efa->v4->co[cox]); + tf->uv[3][1]= 0.5+0.5*cube_size*(loc[coy] + efa->v4->co[coy]); + tf->uv[3][0] -= dx; + tf->uv[3][1] -= dy; + } + } + } + + uv_map_clip_correct(em, op); + + DAG_object_flush_update(scene, obedit, OB_RECALC_DATA); + WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit); + + BKE_mesh_end_editmesh(obedit->data, em); + return OPERATOR_FINISHED; +} + +void UV_OT_cube_project(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Cube Projection"; + ot->idname= "UV_OT_cube_project"; + ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; + + /* api callbacks */ + ot->exec= cube_project_exec; + ot->poll= ED_operator_uvmap; + + /* properties */ + RNA_def_float(ot->srna, "cube_size", 1.0f, 0.0f, FLT_MAX, "Cube Size", "Size of the cube to project on.", 0.001f, 100.0f); + uv_map_clip_correct_properties(ot); +} + +/******************* Mapping Menu operator ****************/ + +static int mapping_menu_invoke(bContext *C, wmOperator *op, wmEvent *event) +{ + uiPopupMenu *pup; + uiLayout *layout; + + pup= uiPupMenuBegin(C, "UV Mapping", 0); + layout= uiPupMenuLayout(pup); + + uiItemO(layout, NULL, 0, "UV_OT_unwrap"); + uiItemS(layout); + uiItemO(layout, NULL, 0, "UV_OT_cube_project"); + uiItemO(layout, NULL, 0, "UV_OT_cylinder_project"); + uiItemO(layout, NULL, 0, "UV_OT_sphere_project"); + uiItemO(layout, NULL, 0, "UV_OT_project_from_view"); + uiItemS(layout); + uiItemO(layout, NULL, 0, "UV_OT_reset"); + + uiPupMenuEnd(C, pup); + + /* XXX python */ +#ifndef DISABLE_PYTHON +#if 0 + /* note that we account for the 10 previous entries with i+10: */ + for(pym = BPyMenuTable[PYMENU_UVCALCULATION]; pym; pym = pym->next, i++) { + + if(!has_pymenu) { + strcat(uvmenu, "|%l"); + has_pymenu = 1; + } + + strcat(uvmenu, "|"); + strcat(uvmenu, pym->name); + strcat(uvmenu, " %x"); + sprintf(menu_number, "%d", i+10); + strcat(uvmenu, menu_number); + } +#endif +#endif + +#ifndef DISABLE_PYTHON +#if 0 + mode= pupmenu(uvmenu); + if(mode >= 10) { + BPY_menu_do_python(PYMENU_UVCALCULATION, mode - 10); + return; + } +#endif +#endif + + return OPERATOR_CANCELLED; +} + +void UV_OT_mapping_menu(wmOperatorType *ot) +{ + /* identifiers */ + ot->name= "Mapping Menu"; + ot->idname= "UV_OT_mapping_menu"; + + /* api callbacks */ + ot->invoke= mapping_menu_invoke; + ot->poll= ED_operator_uvmap; +} + |