Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/uvedit')
-rw-r--r--source/blender/editors/uvedit/Makefile55
-rw-r--r--source/blender/editors/uvedit/SConscript10
-rw-r--r--source/blender/editors/uvedit/uvedit_draw.c856
-rw-r--r--source/blender/editors/uvedit/uvedit_intern.h81
-rw-r--r--source/blender/editors/uvedit/uvedit_ops.c3147
-rw-r--r--source/blender/editors/uvedit/uvedit_parametrizer.c4509
-rw-r--r--source/blender/editors/uvedit/uvedit_parametrizer.h97
-rw-r--r--source/blender/editors/uvedit/uvedit_unwrap_ops.c1347
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;
+}
+