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:
authorBrecht Van Lommel <brechtvanlommel@pandora.be>2009-02-20 02:53:40 +0300
committerBrecht Van Lommel <brechtvanlommel@pandora.be>2009-02-20 02:53:40 +0300
commit8e41a21607231b733ef0f5469be90ca4715e9afa (patch)
treea82636932532377a9bb8cdebe63b1b5415f15a1f /source/blender/editors/sculpt_paint
parent2cb5af58a6cf8120275b37c063899b0234719da2 (diff)
2.5:
* Image painting back. 2d paint, 3d paint and projection, undo, pressure, repeating paint operations, etc should all work. Drawing cursor needs a bit of work, only gets shown when enabling texture paint mode now. * Move sculpt, image paint, and vertex/weight paint into a single sculpt_paint module. Doesn't make much difference now, but nice to have it together for better integration and consistency in the future.
Diffstat (limited to 'source/blender/editors/sculpt_paint')
-rw-r--r--source/blender/editors/sculpt_paint/Makefile57
-rw-r--r--source/blender/editors/sculpt_paint/SConscript11
-rw-r--r--source/blender/editors/sculpt_paint/paint_image.c5078
-rw-r--r--source/blender/editors/sculpt_paint/paint_intern.h62
-rw-r--r--source/blender/editors/sculpt_paint/paint_ops.c30
-rw-r--r--source/blender/editors/sculpt_paint/paint_utils.c191
-rw-r--r--source/blender/editors/sculpt_paint/paint_vertex.c1908
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c2176
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h67
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_stroke.c274
10 files changed, 9854 insertions, 0 deletions
diff --git a/source/blender/editors/sculpt_paint/Makefile b/source/blender/editors/sculpt_paint/Makefile
new file mode 100644
index 00000000000..e810f7efbe4
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/Makefile
@@ -0,0 +1,57 @@
+#
+# $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_sculpt_paint
+DIR = $(OCGDIR)/blender/$(LIBNAME)
+
+include nan_compile.mk
+
+CFLAGS += $(LEVEL_1_C_WARNINGS)
+
+CPPFLAGS += -I$(NAN_GLEW)/include
+CPPFLAGS += -I$(OPENGL_HEADERS)
+
+CPPFLAGS += -I$(NAN_BMFONT)/include
+CPPFLAGS += -I$(NAN_GUARDEDALLOC)/include
+CPPFLAGS += -I$(NAN_ELBEEM)/include
+
+CPPFLAGS += -I../../windowmanager
+CPPFLAGS += -I../../blenkernel
+CPPFLAGS += -I../../blenloader
+CPPFLAGS += -I../../blenlib
+CPPFLAGS += -I../../makesdna
+CPPFLAGS += -I../../makesrna
+CPPFLAGS += -I../../imbuf
+CPPFLAGS += -I../../gpu
+CPPFLAGS += -I../../render/extern/include
+
+# own include
+
+CPPFLAGS += -I../include
diff --git a/source/blender/editors/sculpt_paint/SConscript b/source/blender/editors/sculpt_paint/SConscript
new file mode 100644
index 00000000000..3e00453e049
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/SConscript
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+Import ('env')
+
+sources = env.Glob('*.c')
+
+incs = '../include ../../blenlib ../../blenkernel ../../makesdna ../../imbuf'
+incs += ' ../../windowmanager #/intern/guardedalloc #/extern/glew/include'
+incs += ' ../../render/extern/include #/intern/guardedalloc #intern/bmfont'
+incs += ' ../../gpu ../../makesrna'
+
+env.BlenderLib ( 'bf_editors_sculpt_paint', sources, Split(incs), [], libtype=['core'], priority=[40] )
diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c
new file mode 100644
index 00000000000..ee5aa1ee4a7
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/paint_image.c
@@ -0,0 +1,5078 @@
+/**
+ * $Id$
+ * imagepaint.c
+ *
+ * Functions to paint images in 2D and 3D.
+ *
+ * ***** 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: some of this file.
+ *
+ * Contributor(s): Jens Ole Wund (bjornmose), Campbell Barton (ideasman42)
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#include <float.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "MEM_guardedalloc.h"
+
+#ifdef WIN32
+#include "BLI_winstuff.h"
+#endif
+#include "BLI_arithb.h"
+#include "BLI_blenlib.h"
+#include "BLI_dynstr.h"
+#include "BLI_linklist.h"
+#include "BLI_memarena.h"
+#include "PIL_time.h"
+#include "BLI_threads.h"
+
+#include "IMB_imbuf.h"
+#include "IMB_imbuf_types.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_image_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_node_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+#include "DNA_userdef_types.h"
+#include "DNA_view3d_types.h"
+#include "DNA_windowmanager_types.h"
+
+#include "BKE_context.h"
+#include "BKE_brush.h"
+#include "BKE_global.h"
+#include "BKE_image.h"
+#include "BKE_main.h"
+#include "BKE_mesh.h"
+#include "BKE_node.h"
+#include "BKE_utildefines.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_report.h"
+#include "BKE_depsgraph.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "UI_interface.h"
+#include "UI_view2d.h"
+
+#include "ED_image.h"
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_view3d.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "GPU_draw.h"
+
+#include "paint_intern.h"
+
+/* Defines and Structs */
+
+#define IMAPAINT_CHAR_TO_FLOAT(c) ((c)/255.0f)
+
+#define IMAPAINT_FLOAT_RGB_TO_CHAR(c, f) { (c)[0]=FTOCHAR((f)[0]); (c)[1]=FTOCHAR((f)[1]); (c)[2]=FTOCHAR((f)[2]); }
+#define IMAPAINT_FLOAT_RGBA_TO_CHAR(c, f) { (c)[0]=FTOCHAR((f)[0]); (c)[1]=FTOCHAR((f)[1]); (c)[2]=FTOCHAR((f)[2]); (c)[3]=FTOCHAR((f)[3]); }
+
+#define IMAPAINT_CHAR_RGB_TO_FLOAT(f, c) { (f)[0]=IMAPAINT_CHAR_TO_FLOAT((c)[0]); (f)[1]=IMAPAINT_CHAR_TO_FLOAT((c)[1]); (f)[2]=IMAPAINT_CHAR_TO_FLOAT((c)[2]); }
+#define IMAPAINT_CHAR_RGBA_TO_FLOAT(f, c) { (f)[0]=IMAPAINT_CHAR_TO_FLOAT((c)[0]); (f)[1]=IMAPAINT_CHAR_TO_FLOAT((c)[1]); (f)[2]=IMAPAINT_CHAR_TO_FLOAT((c)[2]); (f)[3]=IMAPAINT_CHAR_TO_FLOAT((c)[3]); }
+#define IMAPAINT_FLOAT_RGB_COPY(a, b) VECCOPY(a, b)
+
+#define IMAPAINT_TILE_BITS 6
+#define IMAPAINT_TILE_SIZE (1 << IMAPAINT_TILE_BITS)
+#define IMAPAINT_TILE_NUMBER(size) (((size)+IMAPAINT_TILE_SIZE-1) >> IMAPAINT_TILE_BITS)
+
+#define MAXUNDONAME 64
+
+static void imapaint_image_update(SpaceImage *sima, Image *image, ImBuf *ibuf, short texpaint);
+
+
+typedef struct ImagePaintState {
+ SpaceImage *sima;
+ View2D *v2d;
+ Scene *scene;
+ bScreen *screen;
+
+ Brush *brush;
+ short tool, blend;
+ Image *image;
+ ImBuf *canvas;
+ ImBuf *clonecanvas;
+ short clonefreefloat;
+ char *warnpackedfile;
+ char *warnmultifile;
+
+ /* texture paint only */
+ Object *ob;
+ Mesh *me;
+ int faceindex;
+ float uv[2];
+} ImagePaintState;
+
+typedef struct ImagePaintPartialRedraw {
+ int x1, y1, x2, y2;
+ int enabled;
+} ImagePaintPartialRedraw;
+
+
+/* ProjectionPaint defines */
+
+/* approx the number of buckets to have under the brush,
+ * used with the brush size to set the ps->buckets_x and ps->buckets_y value.
+ *
+ * When 3 - a brush should have ~9 buckets under it at once
+ * ...this helps for threading while painting as well as
+ * avoiding initializing pixels that wont touch the brush */
+#define PROJ_BUCKET_BRUSH_DIV 4
+
+#define PROJ_BUCKET_RECT_MIN 4
+#define PROJ_BUCKET_RECT_MAX 256
+
+#define PROJ_BOUNDBOX_DIV 8
+#define PROJ_BOUNDBOX_SQUARED (PROJ_BOUNDBOX_DIV * PROJ_BOUNDBOX_DIV)
+
+//#define PROJ_DEBUG_PAINT 1
+//#define PROJ_DEBUG_NOSEAMBLEED 1
+//#define PROJ_DEBUG_PRINT_CLIP 1
+#define PROJ_DEBUG_WINCLIP 1
+
+/* projectFaceSeamFlags options */
+//#define PROJ_FACE_IGNORE 1<<0 /* When the face is hidden, backfacing or occluded */
+//#define PROJ_FACE_INIT 1<<1 /* When we have initialized the faces data */
+#define PROJ_FACE_SEAM1 1<<0 /* If this face has a seam on any of its edges */
+#define PROJ_FACE_SEAM2 1<<1
+#define PROJ_FACE_SEAM3 1<<2
+#define PROJ_FACE_SEAM4 1<<3
+
+#define PROJ_FACE_NOSEAM1 1<<4
+#define PROJ_FACE_NOSEAM2 1<<5
+#define PROJ_FACE_NOSEAM3 1<<6
+#define PROJ_FACE_NOSEAM4 1<<7
+
+/* a slightly scaled down face is used to get fake 3D location for edge pixels in the seams
+ * as this number approaches 1.0f the likelihood increases of float precision errors where
+ * it is occluded by an adjacent face */
+#define PROJ_FACE_SCALE_SEAM 0.99f
+
+#define PROJ_BUCKET_NULL 0
+#define PROJ_BUCKET_INIT 1<<0
+// #define PROJ_BUCKET_CLONE_INIT 1<<1
+
+/* used for testing doubles, if a point is on a line etc */
+#define PROJ_GEOM_TOLERANCE 0.0002f
+
+/* vert flags */
+#define PROJ_VERT_CULL 1
+
+#define PI_80_DEG ((M_PI_2 / 9) * 8)
+
+/* This is mainly a convenience struct used so we can keep an array of images we use
+ * Thir imbufs, etc, in 1 array, When using threads this array is copied for each thread
+ * because 'partRedrawRect' and 'touch' values would not be thread safe */
+typedef struct ProjPaintImage {
+ Image *ima;
+ ImBuf *ibuf;
+ ImagePaintPartialRedraw *partRedrawRect;
+ struct UndoTile **undoRect; /* only used to build undo tiles after painting */
+ int touch;
+} ProjPaintImage;
+
+/* Main projection painting struct passed to all projection painting functions */
+typedef struct ProjPaintState {
+ View3D *v3d;
+ RegionView3D *rv3d;
+ ARegion *ar;
+ Scene *scene;
+
+ Brush *brush;
+ short tool, blend;
+ Object *ob;
+ /* end similarities with ImagePaintState */
+
+ DerivedMesh *dm;
+ int dm_totface;
+ int dm_totvert;
+
+ MVert *dm_mvert;
+ MFace *dm_mface;
+ MTFace *dm_mtface;
+ MTFace *dm_mtface_clone; /* other UV layer, use for cloning between layers */
+ MTFace *dm_mtface_mask;
+
+ /* projection painting only */
+ MemArena *arena_mt[BLENDER_MAX_THREADS];/* for multithreading, the first item is sometimes used for non threaded cases too */
+ LinkNode **bucketRect; /* screen sized 2D array, each pixel has a linked list of ProjPixel's */
+ LinkNode **bucketFaces; /* bucketRect aligned array linkList of faces overlapping each bucket */
+ unsigned char *bucketFlags; /* store if the bucks have been initialized */
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ char *faceSeamFlags; /* store info about faces, if they are initialized etc*/
+ float (*faceSeamUVs)[4][2]; /* expanded UVs for faces to use as seams */
+ LinkNode **vertFaces; /* Only needed for when seam_bleed_px is enabled, use to find UV seams */
+ char *vertFlags; /* store options per vert, now only store if the vert is pointing away from the view */
+#endif
+ int buckets_x; /* The size of the bucket grid, the grid span's screenMin/screenMax so you can paint outsize the screen or with 2 brushes at once */
+ int buckets_y;
+
+ ProjPaintImage *projImages;
+
+ int image_tot; /* size of projectImages array */
+
+ float (*screenCoords)[4]; /* verts projected into floating point screen space */
+
+ float screenMin[2]; /* 2D bounds for mesh verts on the screen's plane (screenspace) */
+ float screenMax[2];
+ float screen_width; /* Calculated from screenMin & screenMax */
+ float screen_height;
+
+ /* options for projection painting */
+ int do_layer_clone;
+ int do_layer_mask;
+ int do_layer_mask_inv;
+
+ short do_occlude; /* Use raytraced occlusion? - ortherwise will paint right through to the back*/
+ short do_backfacecull; /* ignore faces with normals pointing away, skips a lot of raycasts if your normals are correctly flipped */
+ short do_mask_normal; /* mask out pixels based on their normals */
+ float normal_angle; /* what angle to mask at*/
+ float normal_angle_inner;
+ float normal_angle_range; /* difference between normal_angle and normal_angle_inner, for easy access */
+
+ short is_ortho;
+ short is_airbrush; /* only to avoid using (ps.brush->flag & BRUSH_AIRBRUSH) */
+ short is_texbrush; /* only to avoid running */
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ float seam_bleed_px;
+#endif
+ /* clone vars */
+ float cloneOffset[2];
+
+ float projectMat[4][4]; /* Projection matrix, use for getting screen coords */
+ float viewMat[4][4];
+ float viewDir[3]; /* View vector, use for do_backfacecull and for ray casting with an ortho viewport */
+ float viewPos[3]; /* View location in object relative 3D space, so can compare to verts */
+ float clipsta, clipend;
+
+ /* threads */
+ int thread_tot;
+ int bucketMin[2];
+ int bucketMax[2];
+ int context_bucket_x, context_bucket_y; /* must lock threads while accessing these */
+} ProjPaintState;
+
+typedef union pixelPointer
+{
+ float *f_pt; /* float buffer */
+ unsigned int *uint_pt; /* 2 ways to access a char buffer */
+ unsigned char *ch_pt;
+} PixelPointer;
+
+typedef union pixelStore
+{
+ unsigned char ch[4];
+ unsigned int uint;
+ float f[4];
+} PixelStore;
+
+typedef struct ProjPixel {
+ float projCoSS[2]; /* the floating point screen projection of this pixel */
+
+ /* Only used when the airbrush is disabled.
+ * Store the max mask value to avoid painting over an area with a lower opacity
+ * with an advantage that we can avoid touching the pixel at all, if the
+ * new mask value is lower then mask_max */
+ unsigned short mask_max;
+
+ /* for various reasons we may want to mask out painting onto this pixel */
+ unsigned short mask;
+
+ short x_px, y_px;
+
+ PixelStore origColor;
+ PixelStore newColor;
+ PixelPointer pixel;
+
+ short image_index; /* if anyone wants to paint onto more then 32768 images they can bite me */
+ unsigned char bb_cell_index;
+} ProjPixel;
+
+typedef struct ProjPixelClone {
+ struct ProjPixel __pp;
+ PixelStore clonepx;
+} ProjPixelClone;
+
+/* Finish projection painting structs */
+
+
+typedef struct UndoTile {
+ struct UndoTile *next, *prev;
+ ID id;
+ void *rect;
+ int x, y;
+} UndoTile;
+
+typedef struct UndoElem {
+ struct UndoElem *next, *prev;
+ char name[MAXUNDONAME];
+ unsigned long undosize;
+
+ ImBuf *ibuf;
+ ListBase tiles;
+} UndoElem;
+
+static ListBase undobase = {NULL, NULL};
+static UndoElem *curundo = NULL;
+static ImagePaintPartialRedraw imapaintpartial = {0, 0, 0, 0, 0};
+
+/* UNDO */
+
+/* internal functions */
+
+static void undo_copy_tile(UndoTile *tile, ImBuf *tmpibuf, ImBuf *ibuf, int restore)
+{
+ /* copy or swap contents of tile->rect and region in ibuf->rect */
+ IMB_rectcpy(tmpibuf, ibuf, 0, 0, tile->x*IMAPAINT_TILE_SIZE,
+ tile->y*IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE);
+
+ if(ibuf->rect_float) {
+ SWAP(void*, tmpibuf->rect_float, tile->rect);
+ } else {
+ SWAP(void*, tmpibuf->rect, tile->rect);
+ }
+
+ if(restore)
+ IMB_rectcpy(ibuf, tmpibuf, tile->x*IMAPAINT_TILE_SIZE,
+ tile->y*IMAPAINT_TILE_SIZE, 0, 0, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE);
+}
+
+static UndoTile *undo_init_tile(ID *id, ImBuf *ibuf, ImBuf **tmpibuf, int x_tile, int y_tile)
+{
+ UndoTile *tile;
+ int allocsize;
+
+ if (*tmpibuf==NULL)
+ *tmpibuf = IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, IB_rectfloat|IB_rect, 0);
+
+ tile= MEM_callocN(sizeof(UndoTile), "ImaUndoTile");
+ tile->id= *id;
+ tile->x= x_tile;
+ tile->y= y_tile;
+
+ allocsize= IMAPAINT_TILE_SIZE*IMAPAINT_TILE_SIZE*4;
+ allocsize *= (ibuf->rect_float)? sizeof(float): sizeof(char);
+ tile->rect= MEM_mapallocN(allocsize, "ImaUndoRect");
+
+ undo_copy_tile(tile, *tmpibuf, ibuf, 0);
+ curundo->undosize += allocsize;
+
+ BLI_addtail(&curundo->tiles, tile);
+
+ return tile;
+}
+
+static void undo_restore(UndoElem *undo)
+{
+ Image *ima = NULL;
+ ImBuf *ibuf, *tmpibuf;
+ UndoTile *tile;
+
+ if(!undo)
+ return;
+
+ tmpibuf= IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32,
+ IB_rectfloat|IB_rect, 0);
+
+ for(tile=undo->tiles.first; tile; tile=tile->next) {
+ /* find image based on name, pointer becomes invalid with global undo */
+ if(ima && strcmp(tile->id.name, ima->id.name)==0);
+ else {
+ for(ima=G.main->image.first; ima; ima=ima->id.next)
+ if(strcmp(tile->id.name, ima->id.name)==0)
+ break;
+ }
+
+ ibuf= BKE_image_get_ibuf(ima, NULL);
+
+ if (!ima || !ibuf || !(ibuf->rect || ibuf->rect_float))
+ continue;
+
+ undo_copy_tile(tile, tmpibuf, ibuf, 1);
+
+ GPU_free_image(ima); /* force OpenGL reload */
+ if(ibuf->rect_float)
+ imb_freerectImBuf(ibuf); /* force recreate of char rect */
+ }
+
+ IMB_freeImBuf(tmpibuf);
+}
+
+static void undo_free(UndoElem *undo)
+{
+ UndoTile *tile;
+
+ for(tile=undo->tiles.first; tile; tile=tile->next)
+ MEM_freeN(tile->rect);
+ BLI_freelistN(&undo->tiles);
+}
+
+static void undo_imagepaint_push_begin(char *name)
+{
+ UndoElem *uel;
+ int nr;
+
+ /* Undo push is split up in begin and end, the reason is that as painting
+ * happens more tiles are added to the list, and at the very end we know
+ * how much memory the undo used to remove old undo elements */
+
+ /* remove all undos after (also when curundo==NULL) */
+ while(undobase.last != curundo) {
+ uel= undobase.last;
+ undo_free(uel);
+ BLI_freelinkN(&undobase, uel);
+ }
+
+ /* make new */
+ curundo= uel= MEM_callocN(sizeof(UndoElem), "undo file");
+ BLI_addtail(&undobase, uel);
+
+ /* name can be a dynamic string */
+ strncpy(uel->name, name, MAXUNDONAME-1);
+
+ /* limit amount to the maximum amount*/
+ nr= 0;
+ uel= undobase.last;
+ while(uel) {
+ nr++;
+ if(nr==U.undosteps) break;
+ uel= uel->prev;
+ }
+ if(uel) {
+ while(undobase.first!=uel) {
+ UndoElem *first= undobase.first;
+ undo_free(first);
+ BLI_freelinkN(&undobase, first);
+ }
+ }
+}
+
+static void undo_imagepaint_push_end()
+{
+ UndoElem *uel;
+ unsigned long totmem, maxmem;
+
+ if(U.undomemory != 0) {
+ /* limit to maximum memory (afterwards, we can't know in advance) */
+ totmem= 0;
+ maxmem= ((unsigned long)U.undomemory)*1024*1024;
+
+ uel= undobase.last;
+ while(uel) {
+ totmem+= uel->undosize;
+ if(totmem>maxmem) break;
+ uel= uel->prev;
+ }
+
+ if(uel) {
+ while(undobase.first!=uel) {
+ UndoElem *first= undobase.first;
+ undo_free(first);
+ BLI_freelinkN(&undobase, first);
+ }
+ }
+ }
+}
+
+void undo_imagepaint_step(int step)
+{
+ UndoElem *undo;
+
+ if(step==1) {
+ if(curundo==NULL);
+ else {
+ if(G.f & G_DEBUG) printf("undo %s\n", curundo->name);
+ undo_restore(curundo);
+ curundo= curundo->prev;
+ }
+ }
+ else if(step==-1) {
+ if((curundo!=NULL && curundo->next==NULL) || undobase.first==NULL);
+ else {
+ undo= (curundo && curundo->next)? curundo->next: undobase.first;
+ undo_restore(undo);
+ curundo= undo;
+ if(G.f & G_DEBUG) printf("redo %s\n", undo->name);
+ }
+ }
+}
+
+void undo_imagepaint_clear(void)
+{
+ UndoElem *uel;
+
+ uel= undobase.first;
+ while(uel) {
+ undo_free(uel);
+ uel= uel->next;
+ }
+
+ BLI_freelistN(&undobase);
+ curundo= NULL;
+}
+
+/* fast projection bucket array lookup, use the safe version for bound checking */
+static int project_bucket_offset(const ProjPaintState *ps, const float projCoSS[2])
+{
+ /* If we were not dealing with screenspace 2D coords we could simple do...
+ * ps->bucketRect[x + (y*ps->buckets_y)] */
+
+ /* please explain?
+ * projCoSS[0] - ps->screenMin[0] : zero origin
+ * ... / ps->screen_width : range from 0.0 to 1.0
+ * ... * ps->buckets_x : use as a bucket index
+ *
+ * Second multiplication does similar but for vertical offset
+ */
+ return ( (int)(((projCoSS[0] - ps->screenMin[0]) / ps->screen_width) * ps->buckets_x)) +
+ ( ( (int)(((projCoSS[1] - ps->screenMin[1]) / ps->screen_height) * ps->buckets_y)) * ps->buckets_x);
+}
+
+static int project_bucket_offset_safe(const ProjPaintState *ps, const float projCoSS[2])
+{
+ int bucket_index = project_bucket_offset(ps, projCoSS);
+
+ if (bucket_index < 0 || bucket_index >= ps->buckets_x*ps->buckets_y) {
+ return -1;
+ }
+ else {
+ return bucket_index;
+ }
+}
+
+#define SIDE_OF_LINE(pa, pb, pp) ((pa[0]-pp[0])*(pb[1]-pp[1]))-((pb[0]-pp[0])*(pa[1]-pp[1]))
+
+static float AreaSignedF2Dfl(float *v1, float *v2, float *v3)
+{
+ return (float)(0.5f*((v1[0]-v2[0])*(v2[1]-v3[1]) +
+(v1[1]-v2[1])*(v3[0]-v2[0])));
+}
+
+static void BarycentricWeights2f(float pt[2], float v1[2], float v2[2], float v3[2], float w[3])
+{
+ float wtot_inv, wtot;
+
+ w[0] = AreaSignedF2Dfl(v2, v3, pt);
+ w[1] = AreaSignedF2Dfl(v3, v1, pt);
+ w[2] = AreaSignedF2Dfl(v1, v2, pt);
+ wtot = w[0]+w[1]+w[2];
+
+ if (wtot != 0.0f) {
+ wtot_inv = 1.0f/wtot;
+
+ w[0] = w[0]*wtot_inv;
+ w[1] = w[1]*wtot_inv;
+ w[2] = w[2]*wtot_inv;
+ }
+ else /* dummy values for zero area face */
+ w[0] = w[1] = w[2] = 1.0f/3.0f;
+}
+
+/* still use 2D X,Y space but this works for verts transformed by a perspective matrix, using their 4th component as a weight */
+static void BarycentricWeightsPersp2f(float pt[2], float v1[4], float v2[4], float v3[4], float w[3])
+{
+ float wtot_inv, wtot;
+
+ w[0] = AreaSignedF2Dfl(v2, v3, pt) / v1[3];
+ w[1] = AreaSignedF2Dfl(v3, v1, pt) / v2[3];
+ w[2] = AreaSignedF2Dfl(v1, v2, pt) / v3[3];
+ wtot = w[0]+w[1]+w[2];
+
+ if (wtot != 0.0f) {
+ wtot_inv = 1.0f/wtot;
+
+ w[0] = w[0]*wtot_inv;
+ w[1] = w[1]*wtot_inv;
+ w[2] = w[2]*wtot_inv;
+ }
+ else /* dummy values for zero area face */
+ w[0] = w[1] = w[2] = 1.0f/3.0f;
+}
+
+static void VecWeightf(float p[3], const float v1[3], const float v2[3], const float v3[3], const float w[3])
+{
+ p[0] = v1[0]*w[0] + v2[0]*w[1] + v3[0]*w[2];
+ p[1] = v1[1]*w[0] + v2[1]*w[1] + v3[1]*w[2];
+ p[2] = v1[2]*w[0] + v2[2]*w[1] + v3[2]*w[2];
+}
+
+static void Vec2Weightf(float p[2], const float v1[2], const float v2[2], const float v3[2], const float w[3])
+{
+ p[0] = v1[0]*w[0] + v2[0]*w[1] + v3[0]*w[2];
+ p[1] = v1[1]*w[0] + v2[1]*w[1] + v3[1]*w[2];
+}
+
+static float VecZDepthOrtho(float pt[2], float v1[3], float v2[3], float v3[3], float w[3])
+{
+ BarycentricWeights2f(pt, v1, v2, v3, w);
+ return (v1[2]*w[0]) + (v2[2]*w[1]) + (v3[2]*w[2]);
+}
+
+static float VecZDepthPersp(float pt[2], float v1[3], float v2[3], float v3[3], float w[3])
+{
+ BarycentricWeightsPersp2f(pt, v1, v2, v3, w);
+ return (v1[2]*w[0]) + (v2[2]*w[1]) + (v3[2]*w[2]);
+}
+
+
+/* Return the top-most face index that the screen space coord 'pt' touches (or -1) */
+static int project_paint_PickFace(const ProjPaintState *ps, float pt[2], float w[3], int *side)
+{
+ LinkNode *node;
+ float w_tmp[3];
+ float *v1, *v2, *v3, *v4;
+ int bucket_index;
+ int face_index;
+ int best_side = -1;
+ int best_face_index = -1;
+ float z_depth_best = FLT_MAX, z_depth;
+ MFace *mf;
+
+ bucket_index = project_bucket_offset_safe(ps, pt);
+ if (bucket_index==-1)
+ return -1;
+
+
+
+ /* we could return 0 for 1 face buckets, as long as this function assumes
+ * that the point its testing is only every originated from an existing face */
+
+ for (node= ps->bucketFaces[bucket_index]; node; node= node->next) {
+ face_index = GET_INT_FROM_POINTER(node->link);
+ mf= ps->dm_mface + face_index;
+
+ v1= ps->screenCoords[mf->v1];
+ v2= ps->screenCoords[mf->v2];
+ v3= ps->screenCoords[mf->v3];
+
+ if (IsectPT2Df(pt, v1, v2, v3)) {
+ if (ps->is_ortho) z_depth= VecZDepthOrtho(pt, v1, v2, v3, w_tmp);
+ else z_depth= VecZDepthPersp(pt, v1, v2, v3, w_tmp);
+
+ if (z_depth < z_depth_best) {
+ best_face_index = face_index;
+ best_side = 0;
+ z_depth_best = z_depth;
+ VECCOPY(w, w_tmp);
+ }
+ }
+ else if (mf->v4) {
+ v4= ps->screenCoords[mf->v4];
+
+ if (IsectPT2Df(pt, v1, v3, v4)) {
+ if (ps->is_ortho) z_depth= VecZDepthOrtho(pt, v1, v3, v4, w_tmp);
+ else z_depth= VecZDepthPersp(pt, v1, v3, v4, w_tmp);
+
+ if (z_depth < z_depth_best) {
+ best_face_index = face_index;
+ best_side= 1;
+ z_depth_best = z_depth;
+ VECCOPY(w, w_tmp);
+ }
+ }
+ }
+ }
+
+ *side = best_side;
+ return best_face_index; /* will be -1 or a valid face */
+}
+
+/* Converts a uv coord into a pixel location wrapping if the uv is outside 0-1 range */
+static void uvco_to_wrapped_pxco(float uv[2], int ibuf_x, int ibuf_y, float *x, float *y)
+{
+ /* use */
+ *x = (float)fmod(uv[0], 1.0f);
+ *y = (float)fmod(uv[1], 1.0f);
+
+ if (*x < 0.0f) *x += 1.0f;
+ if (*y < 0.0f) *y += 1.0f;
+
+ *x = *x * ibuf_x - 0.5f;
+ *y = *y * ibuf_y - 0.5f;
+}
+
+/* Set the top-most face color that the screen space coord 'pt' touches (or return 0 if none touch) */
+static int project_paint_PickColor(const ProjPaintState *ps, float pt[2], float *rgba_fp, unsigned char *rgba, const int interp)
+{
+ float w[3], uv[2];
+ int side;
+ int face_index;
+ MTFace *tf;
+ ImBuf *ibuf;
+ int xi, yi;
+
+
+ face_index = project_paint_PickFace(ps, pt, w, &side);
+
+ if (face_index == -1)
+ return 0;
+
+ tf = ps->dm_mtface + face_index;
+
+ if (side == 0) {
+ Vec2Weightf(uv, tf->uv[0], tf->uv[1], tf->uv[2], w);
+ }
+ else { /* QUAD */
+ Vec2Weightf(uv, tf->uv[0], tf->uv[2], tf->uv[3], w);
+ }
+
+ ibuf = BKE_image_get_ibuf((Image *)tf->tpage, NULL); /* TODO - this may be slow, the only way around it is to have an ibuf index per face */
+
+
+
+ if (interp) {
+ float x, y;
+ uvco_to_wrapped_pxco(uv, ibuf->x, ibuf->y, &x, &y);
+
+ if (ibuf->rect_float) {
+ if (rgba_fp) {
+ bilinear_interpolation_color(ibuf, NULL, rgba_fp, x, y);
+ }
+ else {
+ float rgba_tmp_f[4];
+ bilinear_interpolation_color(ibuf, NULL, rgba_tmp_f, x, y);
+ IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba, rgba_tmp_f);
+ }
+ }
+ else {
+ if (rgba) {
+ bilinear_interpolation_color(ibuf, rgba, NULL, x, y);
+ }
+ else {
+ unsigned char rgba_tmp[4];
+ bilinear_interpolation_color(ibuf, rgba_tmp, NULL, x, y);
+ IMAPAINT_CHAR_RGBA_TO_FLOAT(rgba_fp, rgba_tmp);
+ }
+ }
+ }
+ else {
+ xi = (uv[0]*ibuf->x) + 0.5f;
+ yi = (uv[1]*ibuf->y) + 0.5f;
+
+ //if (xi<0 || xi>=ibuf->x || yi<0 || yi>=ibuf->y) return 0;
+
+ /* wrap */
+ xi = ((int)(uv[0]*ibuf->x)) % ibuf->x;
+ if (xi<0) xi += ibuf->x;
+ yi = ((int)(uv[1]*ibuf->y)) % ibuf->y;
+ if (yi<0) yi += ibuf->y;
+
+
+ if (rgba) {
+ if (ibuf->rect_float) {
+ float *rgba_tmp_fp = ibuf->rect_float + (xi + yi * ibuf->x * 4);
+ IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba, rgba_tmp_fp);
+ }
+ else {
+ *((unsigned int *)rgba) = *(unsigned int *)(((char *)ibuf->rect) + ((xi + yi * ibuf->x) * 4));
+ }
+ }
+
+ if (rgba_fp) {
+ if (ibuf->rect_float) {
+ QUATCOPY(rgba_fp, ((float *)ibuf->rect_float + ((xi + yi * ibuf->x) * 4)));
+ }
+ else {
+ char *tmp_ch= ((char *)ibuf->rect) + ((xi + yi * ibuf->x) * 4);
+ IMAPAINT_CHAR_RGBA_TO_FLOAT(rgba_fp, tmp_ch);
+ }
+ }
+ }
+ return 1;
+}
+
+/* Check if 'pt' is infront of the 3 verts on the Z axis (used for screenspace occlusuion test)
+ * return...
+ * 0 : no occlusion
+ * -1 : no occlusion but 2D intersection is true (avoid testing the other half of a quad)
+ * 1 : occluded
+ 2 : occluded with w[3] weights set (need to know in some cases) */
+
+static int project_paint_occlude_ptv(float pt[3], float v1[3], float v2[3], float v3[3], float w[3], int is_ortho)
+{
+ /* if all are behind us, return false */
+ if(v1[2] > pt[2] && v2[2] > pt[2] && v3[2] > pt[2])
+ return 0;
+
+ /* do a 2D point in try intersection */
+ if (!IsectPT2Df(pt, v1, v2, v3))
+ return 0; /* we know there is */
+
+
+ /* From here on we know there IS an intersection */
+ /* if ALL of the verts are infront of us then we know it intersects ? */
+ if(v1[2] < pt[2] && v2[2] < pt[2] && v3[2] < pt[2]) {
+ return 1;
+ }
+ else {
+ /* we intersect? - find the exact depth at the point of intersection */
+ /* Is this point is occluded by another face? */
+ if (is_ortho) {
+ if (VecZDepthOrtho(pt, v1, v2, v3, w) < pt[2]) return 2;
+ }
+ else {
+ if (VecZDepthPersp(pt, v1, v2, v3, w) < pt[2]) return 2;
+ }
+ }
+ return -1;
+}
+
+
+static int project_paint_occlude_ptv_clip(
+ const ProjPaintState *ps, const MFace *mf,
+ float pt[3], float v1[3], float v2[3], float v3[3],
+ const int side )
+{
+ float w[3], wco[3];
+ int ret = project_paint_occlude_ptv(pt, v1, v2, v3, w, ps->is_ortho);
+
+ if (ret <= 0)
+ return ret;
+
+ if (ret==1) { /* weights not calculated */
+ if (ps->is_ortho) BarycentricWeights2f(pt, v1, v2, v3, w);
+ else BarycentricWeightsPersp2f(pt, v1, v2, v3, w);
+ }
+
+ /* Test if we're in the clipped area, */
+ if (side) VecWeightf(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v3].co, ps->dm_mvert[mf->v4].co, w);
+ else VecWeightf(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v2].co, ps->dm_mvert[mf->v3].co, w);
+
+ Mat4MulVecfl(ps->ob->obmat, wco);
+ if(!view3d_test_clipping(ps->rv3d, wco)) {
+ return 1;
+ }
+
+ return -1;
+}
+
+
+/* Check if a screenspace location is occluded by any other faces
+ * check, pixelScreenCo must be in screenspace, its Z-Depth only needs to be used for comparison
+ * and dosn't need to be correct in relation to X and Y coords (this is the case in perspective view) */
+static int project_bucket_point_occluded(const ProjPaintState *ps, LinkNode *bucketFace, const int orig_face, float pixelScreenCo[4])
+{
+ MFace *mf;
+ int face_index;
+ int isect_ret;
+ float w[3]; /* not needed when clipping */
+
+ /* we could return 0 for 1 face buckets, as long as this function assumes
+ * that the point its testing is only every originated from an existing face */
+
+ for (; bucketFace; bucketFace = bucketFace->next) {
+ face_index = GET_INT_FROM_POINTER(bucketFace->link);
+
+ if (orig_face != face_index) {
+ mf = ps->dm_mface + face_index;
+ if(ps->rv3d->rflag & RV3D_CLIPPING)
+ isect_ret = project_paint_occlude_ptv_clip(ps, mf, pixelScreenCo, ps->screenCoords[mf->v1], ps->screenCoords[mf->v2], ps->screenCoords[mf->v3], 0);
+ else
+ isect_ret = project_paint_occlude_ptv(pixelScreenCo, ps->screenCoords[mf->v1], ps->screenCoords[mf->v2], ps->screenCoords[mf->v3], w, ps->is_ortho);
+
+ /* Note, if isect_ret==-1 then we dont want to test the other side of the quad */
+ if (isect_ret==0 && mf->v4) {
+ if(ps->rv3d->rflag & RV3D_CLIPPING)
+ isect_ret = project_paint_occlude_ptv_clip(ps, mf, pixelScreenCo, ps->screenCoords[mf->v1], ps->screenCoords[mf->v3], ps->screenCoords[mf->v4], 1);
+ else
+ isect_ret = project_paint_occlude_ptv(pixelScreenCo, ps->screenCoords[mf->v1], ps->screenCoords[mf->v3], ps->screenCoords[mf->v4], w, ps->is_ortho);
+ }
+ if (isect_ret==1) {
+ /* TODO - we may want to cache the first hit,
+ * it is not possible to swap the face order in the list anymore */
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/* basic line intersection, could move to arithb.c, 2 points with a horiz line
+ * 1 for an intersection, 2 if the first point is aligned, 3 if the second point is aligned */
+#define ISECT_TRUE 1
+#define ISECT_TRUE_P1 2
+#define ISECT_TRUE_P2 3
+static int line_isect_y(const float p1[2], const float p2[2], const float y_level, float *x_isect)
+{
+ float y_diff;
+
+ if (y_level==p1[1]) { /* are we touching the first point? - no interpolation needed */
+ *x_isect = p1[0];
+ return ISECT_TRUE_P1;
+ }
+ if (y_level==p2[1]) { /* are we touching the second point? - no interpolation needed */
+ *x_isect = p2[0];
+ return ISECT_TRUE_P2;
+ }
+
+ y_diff= fabs(p1[1]-p2[1]); /* yuck, horizontal line, we cant do much here */
+
+ if (y_diff < 0.000001f) {
+ *x_isect = (p1[0]+p2[0]) * 0.5f;
+ return ISECT_TRUE;
+ }
+
+ if (p1[1] > y_level && p2[1] < y_level) {
+ *x_isect = (p2[0]*(p1[1]-y_level) + p1[0]*(y_level-p2[1])) / y_diff; /*(p1[1]-p2[1]);*/
+ return ISECT_TRUE;
+ }
+ else if (p1[1] < y_level && p2[1] > y_level) {
+ *x_isect = (p2[0]*(y_level-p1[1]) + p1[0]*(p2[1]-y_level)) / y_diff; /*(p2[1]-p1[1]);*/
+ return ISECT_TRUE;
+ }
+ else {
+ return 0;
+ }
+}
+
+static int line_isect_x(const float p1[2], const float p2[2], const float x_level, float *y_isect)
+{
+ float x_diff;
+
+ if (x_level==p1[0]) { /* are we touching the first point? - no interpolation needed */
+ *y_isect = p1[1];
+ return ISECT_TRUE_P1;
+ }
+ if (x_level==p2[0]) { /* are we touching the second point? - no interpolation needed */
+ *y_isect = p2[1];
+ return ISECT_TRUE_P2;
+ }
+
+ x_diff= fabs(p1[0]-p2[0]); /* yuck, horizontal line, we cant do much here */
+
+ if (x_diff < 0.000001) { /* yuck, vertical line, we cant do much here */
+ *y_isect = (p1[0]+p2[0]) * 0.5f;
+ return ISECT_TRUE;
+ }
+
+ if (p1[0] > x_level && p2[0] < x_level) {
+ *y_isect = (p2[1]*(p1[0]-x_level) + p1[1]*(x_level-p2[0])) / x_diff; /*(p1[0]-p2[0]);*/
+ return ISECT_TRUE;
+ }
+ else if (p1[0] < x_level && p2[0] > x_level) {
+ *y_isect = (p2[1]*(x_level-p1[0]) + p1[1]*(p2[0]-x_level)) / x_diff; /*(p2[0]-p1[0]);*/
+ return ISECT_TRUE;
+ }
+ else {
+ return 0;
+ }
+}
+
+/* simple func use for comparing UV locations to check if there are seams.
+ * Its possible this gives incorrect results, when the UVs for 1 face go into the next
+ * tile, but do not do this for the adjacent face, it could return a false positive.
+ * This is so unlikely that Id not worry about it. */
+static int cmp_uv(const float vec2a[2], const float vec2b[2])
+{
+ /* if the UV's are not between 0.0 and 1.0 */
+ float xa = (float)fmod(vec2a[0], 1.0f);
+ float ya = (float)fmod(vec2a[1], 1.0f);
+
+ float xb = (float)fmod(vec2b[0], 1.0f);
+ float yb = (float)fmod(vec2b[1], 1.0f);
+
+ if (xa < 0.0f) xa += 1.0f;
+ if (ya < 0.0f) ya += 1.0f;
+
+ if (xb < 0.0f) xb += 1.0f;
+ if (yb < 0.0f) yb += 1.0f;
+
+ return ((fabs(xa-xb) < PROJ_GEOM_TOLERANCE) && (fabs(ya-yb) < PROJ_GEOM_TOLERANCE)) ? 1:0;
+}
+
+
+/* set min_px and max_px to the image space bounds of the UV coords
+ * return zero if there is no area in the returned rectangle */
+static int pixel_bounds_uv(
+ const float uv1[2], const float uv2[2], const float uv3[2], const float uv4[2],
+ rcti *bounds_px,
+ const int ibuf_x, const int ibuf_y,
+ int is_quad
+) {
+ float min_uv[2], max_uv[2]; /* UV bounds */
+
+ INIT_MINMAX2(min_uv, max_uv);
+
+ DO_MINMAX2(uv1, min_uv, max_uv);
+ DO_MINMAX2(uv2, min_uv, max_uv);
+ DO_MINMAX2(uv3, min_uv, max_uv);
+ if (is_quad)
+ DO_MINMAX2(uv4, min_uv, max_uv);
+
+ bounds_px->xmin = (int)(ibuf_x * min_uv[0]);
+ bounds_px->ymin = (int)(ibuf_y * min_uv[1]);
+
+ bounds_px->xmax = (int)(ibuf_x * max_uv[0]) +1;
+ bounds_px->ymax = (int)(ibuf_y * max_uv[1]) +1;
+
+ /*printf("%d %d %d %d \n", min_px[0], min_px[1], max_px[0], max_px[1]);*/
+
+ /* face uses no UV area when quantized to pixels? */
+ return (bounds_px->xmin == bounds_px->xmax || bounds_px->ymin == bounds_px->ymax) ? 0 : 1;
+}
+
+static int pixel_bounds_array(float (* uv)[2], rcti *bounds_px, const int ibuf_x, const int ibuf_y, int tot)
+{
+ float min_uv[2], max_uv[2]; /* UV bounds */
+
+ if (tot==0) {
+ return 0;
+ }
+
+ INIT_MINMAX2(min_uv, max_uv);
+
+ while (tot--) {
+ DO_MINMAX2((*uv), min_uv, max_uv);
+ uv++;
+ }
+
+ bounds_px->xmin = (int)(ibuf_x * min_uv[0]);
+ bounds_px->ymin = (int)(ibuf_y * min_uv[1]);
+
+ bounds_px->xmax = (int)(ibuf_x * max_uv[0]) +1;
+ bounds_px->ymax = (int)(ibuf_y * max_uv[1]) +1;
+
+ /*printf("%d %d %d %d \n", min_px[0], min_px[1], max_px[0], max_px[1]);*/
+
+ /* face uses no UV area when quantized to pixels? */
+ return (bounds_px->xmin == bounds_px->xmax || bounds_px->ymin == bounds_px->ymax) ? 0 : 1;
+}
+
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+
+/* This function returns 1 if this face has a seam along the 2 face-vert indicies
+ * 'orig_i1_fidx' and 'orig_i2_fidx' */
+static int check_seam(const ProjPaintState *ps, const int orig_face, const int orig_i1_fidx, const int orig_i2_fidx, int *other_face, int *orig_fidx)
+{
+ LinkNode *node;
+ int face_index;
+ int i1, i2;
+ int i1_fidx = -1, i2_fidx = -1; /* index in face */
+ MFace *mf;
+ MTFace *tf;
+ const MFace *orig_mf = ps->dm_mface + orig_face;
+ const MTFace *orig_tf = ps->dm_mtface + orig_face;
+
+ /* vert indicies from face vert order indicies */
+ i1 = (*(&orig_mf->v1 + orig_i1_fidx));
+ i2 = (*(&orig_mf->v1 + orig_i2_fidx));
+
+ for (node = ps->vertFaces[i1]; node; node = node->next) {
+ face_index = GET_INT_FROM_POINTER(node->link);
+
+ if (face_index != orig_face) {
+ mf = ps->dm_mface + face_index;
+ /* could check if the 2 faces images match here,
+ * but then there wouldn't be a way to return the opposite face's info */
+
+
+ /* We need to know the order of the verts in the adjacent face
+ * set the i1_fidx and i2_fidx to (0,1,2,3) */
+ if (mf->v1==i1) i1_fidx = 0;
+ else if (mf->v2==i1) i1_fidx = 1;
+ else if (mf->v3==i1) i1_fidx = 2;
+ else if (mf->v4 && mf->v4==i1) i1_fidx = 3;
+
+ if (mf->v1==i2) i2_fidx = 0;
+ else if (mf->v2==i2) i2_fidx = 1;
+ else if (mf->v3==i2) i2_fidx = 2;
+ else if (mf->v4 && mf->v4==i2) i2_fidx = 3;
+
+ /* Only need to check if 'i2_fidx' is valid because we know i1_fidx is the same vert on both faces */
+ if (i2_fidx != -1) {
+ /* This IS an adjacent face!, now lets check if the UVs are ok */
+ tf = ps->dm_mtface + face_index;
+
+ /* set up the other face */
+ *other_face = face_index;
+ *orig_fidx = (i1_fidx < i2_fidx) ? i1_fidx : i2_fidx;
+
+ /* first test if they have the same image */
+ if ( (orig_tf->tpage == tf->tpage) &&
+ cmp_uv(orig_tf->uv[orig_i1_fidx], tf->uv[i1_fidx]) &&
+ cmp_uv(orig_tf->uv[orig_i2_fidx], tf->uv[i2_fidx]) )
+ {
+ // printf("SEAM (NONE)\n");
+ return 0;
+
+ }
+ else {
+ // printf("SEAM (UV GAP)\n");
+ return 1;
+ }
+ }
+ }
+ }
+ // printf("SEAM (NO FACE)\n");
+ *other_face = -1;
+ return 1;
+}
+
+/* TODO - move to arithb.c */
+/* Converts an angle to a length that can be used for maintaining an even margin around UV's */
+static float angleToLength(float angle)
+{
+ // already accounted for
+ if (angle < 0.000001f) {
+ return 1.0f;
+ }
+ else {
+ return fabs(1.0f / cos(angle * (M_PI/180.0f)));
+ }
+}
+
+/* Calculate outset UV's, this is not the same as simply scaling the UVs,
+ * since the outset coords are a margin that keep an even distance from the original UV's,
+ * note that the image aspect is taken into account */
+static void uv_image_outset(float (*orig_uv)[2], float (*outset_uv)[2], const float scaler, const int ibuf_x, const int ibuf_y, const int is_quad)
+{
+ float a1, a2, a3, a4=0.0f;
+ float puv[4][2]; /* pixelspace uv's */
+ float no1[2], no2[2], no3[2], no4[2]; /* normals */
+ float dir1[2], dir2[2], dir3[2], dir4[2];
+ float ibuf_x_inv = 1.0f / (float)ibuf_x;
+ float ibuf_y_inv = 1.0f / (float)ibuf_y;
+
+ /* make UV's in pixel space so we can */
+ puv[0][0] = orig_uv[0][0] * ibuf_x;
+ puv[0][1] = orig_uv[0][1] * ibuf_y;
+
+ puv[1][0] = orig_uv[1][0] * ibuf_x;
+ puv[1][1] = orig_uv[1][1] * ibuf_y;
+
+ puv[2][0] = orig_uv[2][0] * ibuf_x;
+ puv[2][1] = orig_uv[2][1] * ibuf_y;
+
+ if (is_quad) {
+ puv[3][0] = orig_uv[3][0] * ibuf_x;
+ puv[3][1] = orig_uv[3][1] * ibuf_y;
+ }
+
+ /* face edge directions */
+ Vec2Subf(dir1, puv[1], puv[0]);
+ Vec2Subf(dir2, puv[2], puv[1]);
+ Normalize2(dir1);
+ Normalize2(dir2);
+
+ if (is_quad) {
+ Vec2Subf(dir3, puv[3], puv[2]);
+ Vec2Subf(dir4, puv[0], puv[3]);
+ Normalize2(dir3);
+ Normalize2(dir4);
+ }
+ else {
+ Vec2Subf(dir3, puv[0], puv[2]);
+ Normalize2(dir3);
+ }
+
+ if (is_quad) {
+ a1 = angleToLength(NormalizedVecAngle2_2D(dir4, dir1));
+ a2 = angleToLength(NormalizedVecAngle2_2D(dir1, dir2));
+ a3 = angleToLength(NormalizedVecAngle2_2D(dir2, dir3));
+ a4 = angleToLength(NormalizedVecAngle2_2D(dir3, dir4));
+ }
+ else {
+ a1 = angleToLength(NormalizedVecAngle2_2D(dir3, dir1));
+ a2 = angleToLength(NormalizedVecAngle2_2D(dir1, dir2));
+ a3 = angleToLength(NormalizedVecAngle2_2D(dir2, dir3));
+ }
+
+ if (is_quad) {
+ Vec2Subf(no1, dir4, dir1);
+ Vec2Subf(no2, dir1, dir2);
+ Vec2Subf(no3, dir2, dir3);
+ Vec2Subf(no4, dir3, dir4);
+ Normalize2(no1);
+ Normalize2(no2);
+ Normalize2(no3);
+ Normalize2(no4);
+ Vec2Mulf(no1, a1*scaler);
+ Vec2Mulf(no2, a2*scaler);
+ Vec2Mulf(no3, a3*scaler);
+ Vec2Mulf(no4, a4*scaler);
+ Vec2Addf(outset_uv[0], puv[0], no1);
+ Vec2Addf(outset_uv[1], puv[1], no2);
+ Vec2Addf(outset_uv[2], puv[2], no3);
+ Vec2Addf(outset_uv[3], puv[3], no4);
+ outset_uv[0][0] *= ibuf_x_inv;
+ outset_uv[0][1] *= ibuf_y_inv;
+
+ outset_uv[1][0] *= ibuf_x_inv;
+ outset_uv[1][1] *= ibuf_y_inv;
+
+ outset_uv[2][0] *= ibuf_x_inv;
+ outset_uv[2][1] *= ibuf_y_inv;
+
+ outset_uv[3][0] *= ibuf_x_inv;
+ outset_uv[3][1] *= ibuf_y_inv;
+ }
+ else {
+ Vec2Subf(no1, dir3, dir1);
+ Vec2Subf(no2, dir1, dir2);
+ Vec2Subf(no3, dir2, dir3);
+ Normalize2(no1);
+ Normalize2(no2);
+ Normalize2(no3);
+ Vec2Mulf(no1, a1*scaler);
+ Vec2Mulf(no2, a2*scaler);
+ Vec2Mulf(no3, a3*scaler);
+ Vec2Addf(outset_uv[0], puv[0], no1);
+ Vec2Addf(outset_uv[1], puv[1], no2);
+ Vec2Addf(outset_uv[2], puv[2], no3);
+ outset_uv[0][0] *= ibuf_x_inv;
+ outset_uv[0][1] *= ibuf_y_inv;
+
+ outset_uv[1][0] *= ibuf_x_inv;
+ outset_uv[1][1] *= ibuf_y_inv;
+
+ outset_uv[2][0] *= ibuf_x_inv;
+ outset_uv[2][1] *= ibuf_y_inv;
+ }
+}
+
+/*
+ * Be tricky with flags, first 4 bits are PROJ_FACE_SEAM1 to 4, last 4 bits are PROJ_FACE_NOSEAM1 to 4
+ * 1<<i - where i is (0-3)
+ *
+ * If we're multithreadng, make sure threads are locked when this is called
+ */
+static void project_face_seams_init(const ProjPaintState *ps, const int face_index, const int is_quad)
+{
+ int other_face, other_fidx; /* vars for the other face, we also set its flag */
+ int fidx1 = is_quad ? 3 : 2;
+ int fidx2 = 0; /* next fidx in the face (0,1,2,3) -> (1,2,3,0) or (0,1,2) -> (1,2,0) for a tri */
+
+ do {
+ if ((ps->faceSeamFlags[face_index] & (1<<fidx1|16<<fidx1)) == 0) {
+ if (check_seam(ps, face_index, fidx1, fidx2, &other_face, &other_fidx)) {
+ ps->faceSeamFlags[face_index] |= 1<<fidx1;
+ if (other_face != -1)
+ ps->faceSeamFlags[other_face] |= 1<<other_fidx;
+ }
+ else {
+ ps->faceSeamFlags[face_index] |= 16<<fidx1;
+ if (other_face != -1)
+ ps->faceSeamFlags[other_face] |= 16<<other_fidx; /* second 4 bits for disabled */
+ }
+ }
+
+ fidx2 = fidx1;
+ } while (fidx1--);
+}
+#endif // PROJ_DEBUG_NOSEAMBLEED
+
+
+/* TODO - move to arithb.c */
+
+/* little sister we only need to know lambda */
+static float lambda_cp_line2(const float p[2], const float l1[2], const float l2[2])
+{
+ float h[2], u[2];
+
+ u[0] = l2[0] - l1[0];
+ u[1] = l2[1] - l1[1];
+
+ h[0] = p[0] - l1[0];
+ h[1] = p[1] - l1[1];
+
+ return(Inp2f(u, h)/Inp2f(u, u));
+}
+
+
+/* Converts a UV location to a 3D screenspace location
+ * Takes a 'uv' and 3 UV coords, and sets the values of pixelScreenCo
+ *
+ * This is used for finding a pixels location in screenspace for painting */
+static void screen_px_from_ortho(
+ float uv[2],
+ float v1co[3], float v2co[3], float v3co[3], /* Screenspace coords */
+ float uv1co[2], float uv2co[2], float uv3co[2],
+ float pixelScreenCo[4],
+ float w[3])
+{
+ BarycentricWeights2f(uv, uv1co, uv2co, uv3co, w);
+ VecWeightf(pixelScreenCo, v1co, v2co, v3co, w);
+}
+
+/* same as screen_px_from_ortho except we need to take into account
+ * the perspective W coord for each vert */
+static void screen_px_from_persp(
+ float uv[2],
+ float v1co[3], float v2co[3], float v3co[3], /* screenspace coords */
+ float uv1co[2], float uv2co[2], float uv3co[2],
+ float pixelScreenCo[4],
+ float w[3])
+{
+
+ float wtot_inv, wtot;
+ BarycentricWeights2f(uv, uv1co, uv2co, uv3co, w);
+
+ /* re-weight from the 4th coord of each screen vert */
+ w[0] *= v1co[3];
+ w[1] *= v2co[3];
+ w[2] *= v3co[3];
+
+ wtot = w[0]+w[1]+w[2];
+
+ if (wtot > 0.0f) {
+ wtot_inv = 1.0f / wtot;
+ w[0] *= wtot_inv;
+ w[1] *= wtot_inv;
+ w[2] *= wtot_inv;
+ }
+ else {
+ w[0] = w[1] = w[2] = 1.0/3.0; /* dummy values for zero area face */
+ }
+ /* done re-weighting */
+
+ VecWeightf(pixelScreenCo, v1co, v2co, v3co, w);
+}
+
+static void project_face_pixel(const MTFace *tf_other, ImBuf *ibuf_other, const float w[3], int side, unsigned char rgba_ub[4], float rgba_f[4])
+{
+ float *uvCo1, *uvCo2, *uvCo3;
+ float uv_other[2], x, y;
+
+ uvCo1 = (float *)tf_other->uv[0];
+ if (side==1) {
+ uvCo2 = (float *)tf_other->uv[2];
+ uvCo3 = (float *)tf_other->uv[3];
+ }
+ else {
+ uvCo2 = (float *)tf_other->uv[1];
+ uvCo3 = (float *)tf_other->uv[2];
+ }
+
+ Vec2Weightf(uv_other, uvCo1, uvCo2, uvCo3, w);
+
+ /* use */
+ uvco_to_wrapped_pxco(uv_other, ibuf_other->x, ibuf_other->y, &x, &y);
+
+
+ if (ibuf_other->rect_float) { /* from float to float */
+ bilinear_interpolation_color(ibuf_other, NULL, rgba_f, x, y);
+ }
+ else { /* from char to float */
+ bilinear_interpolation_color(ibuf_other, rgba_ub, NULL, x, y);
+ }
+
+}
+
+/* run this outside project_paint_uvpixel_init since pixels with mask 0 dont need init */
+float project_paint_uvpixel_mask(
+ const ProjPaintState *ps,
+ const int face_index,
+ const int side,
+ const float w[3])
+{
+ float mask;
+
+ /* Image Mask */
+ if (ps->do_layer_mask) {
+ /* another UV layers image is masking this one's */
+ ImBuf *ibuf_other;
+ const MTFace *tf_other = ps->dm_mtface_mask + face_index;
+
+ if (tf_other->tpage && (ibuf_other = BKE_image_get_ibuf((Image *)tf_other->tpage, NULL))) {
+ /* BKE_image_get_ibuf - TODO - this may be slow */
+ unsigned char rgba_ub[4];
+ float rgba_f[4];
+
+ project_face_pixel(tf_other, ibuf_other, w, side, rgba_ub, rgba_f);
+
+ if (ibuf_other->rect_float) { /* from float to float */
+ mask = ((rgba_f[0]+rgba_f[1]+rgba_f[2])/3.0f) * rgba_f[3];
+ }
+ else { /* from char to float */
+ mask = ((rgba_ub[0]+rgba_ub[1]+rgba_ub[2])/(256*3.0f)) * (rgba_ub[3]/256.0f);
+ }
+
+ if (!ps->do_layer_mask_inv) /* matching the gimps layer mask black/white rules, white==full opacity */
+ mask = (1.0f - mask);
+
+ if (mask == 0.0f) {
+ return 0.0f;
+ }
+ }
+ else {
+ return 0.0f;
+ }
+ } else {
+ mask = 1.0f;
+ }
+
+ /* calculate mask */
+ if (ps->do_mask_normal) {
+ MFace *mf = ps->dm_mface + face_index;
+ short *no1, *no2, *no3;
+ float no[3], angle;
+ no1 = ps->dm_mvert[mf->v1].no;
+ if (side==1) {
+ no2 = ps->dm_mvert[mf->v3].no;
+ no3 = ps->dm_mvert[mf->v4].no;
+ }
+ else {
+ no2 = ps->dm_mvert[mf->v2].no;
+ no3 = ps->dm_mvert[mf->v3].no;
+ }
+
+ no[0] = w[0]*no1[0] + w[1]*no2[0] + w[2]*no3[0];
+ no[1] = w[0]*no1[1] + w[1]*no2[1] + w[2]*no3[1];
+ no[2] = w[0]*no1[2] + w[1]*no2[2] + w[2]*no3[2];
+ Normalize(no);
+
+ /* now we can use the normal as a mask */
+ if (ps->is_ortho) {
+ angle = NormalizedVecAngle2((float *)ps->viewDir, no);
+ }
+ else {
+ /* Annoying but for the perspective view we need to get the pixels location in 3D space :/ */
+ float viewDirPersp[3];
+ float *co1, *co2, *co3;
+ co1 = ps->dm_mvert[mf->v1].co;
+ if (side==1) {
+ co2 = ps->dm_mvert[mf->v3].co;
+ co3 = ps->dm_mvert[mf->v4].co;
+ }
+ else {
+ co2 = ps->dm_mvert[mf->v2].co;
+ co3 = ps->dm_mvert[mf->v3].co;
+ }
+
+ /* Get the direction from the viewPoint to the pixel and normalize */
+ viewDirPersp[0] = (ps->viewPos[0] - (w[0]*co1[0] + w[1]*co2[0] + w[2]*co3[0]));
+ viewDirPersp[1] = (ps->viewPos[1] - (w[0]*co1[1] + w[1]*co2[1] + w[2]*co3[1]));
+ viewDirPersp[2] = (ps->viewPos[2] - (w[0]*co1[2] + w[1]*co2[2] + w[2]*co3[2]));
+ Normalize(viewDirPersp);
+
+ angle = NormalizedVecAngle2(viewDirPersp, no);
+ }
+
+ if (angle >= ps->normal_angle) {
+ return 0.0f; /* outsize the normal limit*/
+ }
+ else if (angle > ps->normal_angle_inner) {
+ mask *= (ps->normal_angle - angle) / ps->normal_angle_range;
+ } /* otherwise no mask normal is needed, were within the limit */
+ }
+
+ // This only works when the opacity dosnt change while painting, stylus pressure messes with this
+ // so dont use it.
+ // if (ps->is_airbrush==0) mask *= ps->brush->alpha;
+
+ return mask;
+}
+
+/* run this function when we know a bucket's, face's pixel can be initialized,
+ * return the ProjPixel which is added to 'ps->bucketRect[bucket_index]' */
+static ProjPixel *project_paint_uvpixel_init(
+ const ProjPaintState *ps,
+ MemArena *arena,
+ const ImBuf *ibuf,
+ short x_px, short y_px,
+ const float mask,
+ const int face_index,
+ const int image_index,
+ const float pixelScreenCo[4],
+ const int side,
+ const float w[3])
+{
+ ProjPixel *projPixel;
+ short size;
+
+ /* wrap pixel location */
+ x_px = x_px % ibuf->x;
+ if (x_px<0) x_px += ibuf->x;
+ y_px = y_px % ibuf->y;
+ if (y_px<0) y_px += ibuf->y;
+
+ if (ps->tool==PAINT_TOOL_CLONE) {
+ size = sizeof(ProjPixelClone);
+ }
+ else if (ps->tool==PAINT_TOOL_SMEAR) {
+ size = sizeof(ProjPixelClone);
+ }
+ else {
+ size = sizeof(ProjPixel);
+ }
+
+ projPixel = (ProjPixel *)BLI_memarena_alloc(arena, size);
+ //memset(projPixel, 0, size);
+
+ if (ibuf->rect_float) {
+ projPixel->pixel.f_pt = (float *)ibuf->rect_float + ((x_px + y_px * ibuf->x) * 4);
+ projPixel->origColor.f[0] = projPixel->newColor.f[0] = projPixel->pixel.f_pt[0];
+ projPixel->origColor.f[1] = projPixel->newColor.f[1] = projPixel->pixel.f_pt[1];
+ projPixel->origColor.f[2] = projPixel->newColor.f[2] = projPixel->pixel.f_pt[2];
+ projPixel->origColor.f[3] = projPixel->newColor.f[3] = projPixel->pixel.f_pt[3];
+ }
+ else {
+ projPixel->pixel.ch_pt = ((unsigned char *)ibuf->rect + ((x_px + y_px * ibuf->x) * 4));
+ projPixel->origColor.uint = projPixel->newColor.uint = *projPixel->pixel.uint_pt;
+ }
+
+ /* screenspace unclamped, we could keep its z and w values but dont need them at the moment */
+ VECCOPY2D(projPixel->projCoSS, pixelScreenCo);
+
+ projPixel->x_px = x_px;
+ projPixel->y_px = y_px;
+
+ projPixel->mask = (unsigned short)(mask * 65535);
+ projPixel->mask_max = 0;
+
+ /* which bounding box cell are we in?, needed for undo */
+ projPixel->bb_cell_index = ((int)(((float)x_px/(float)ibuf->x) * PROJ_BOUNDBOX_DIV)) + ((int)(((float)y_px/(float)ibuf->y) * PROJ_BOUNDBOX_DIV)) * PROJ_BOUNDBOX_DIV ;
+
+ /* done with view3d_project_float inline */
+ if (ps->tool==PAINT_TOOL_CLONE) {
+ if (ps->dm_mtface_clone) {
+ ImBuf *ibuf_other;
+ const MTFace *tf_other = ps->dm_mtface_clone + face_index;
+
+ if (tf_other->tpage && (ibuf_other = BKE_image_get_ibuf((Image *)tf_other->tpage, NULL))) {
+ /* BKE_image_get_ibuf - TODO - this may be slow */
+
+ if (ibuf->rect_float) {
+ if (ibuf_other->rect_float) { /* from float to float */
+ project_face_pixel(tf_other, ibuf_other, w, side, NULL, ((ProjPixelClone *)projPixel)->clonepx.f);
+ }
+ else { /* from char to float */
+ unsigned char rgba_ub[4];
+ project_face_pixel(tf_other, ibuf_other, w, side, rgba_ub, NULL);
+ IMAPAINT_CHAR_RGBA_TO_FLOAT(((ProjPixelClone *)projPixel)->clonepx.f, rgba_ub);
+ }
+ }
+ else {
+ if (ibuf_other->rect_float) { /* float to char */
+ float rgba[4];
+ project_face_pixel(tf_other, ibuf_other, w, side, NULL, rgba);
+ IMAPAINT_FLOAT_RGBA_TO_CHAR(((ProjPixelClone *)projPixel)->clonepx.ch, rgba)
+ }
+ else { /* char to char */
+ project_face_pixel(tf_other, ibuf_other, w, side, ((ProjPixelClone *)projPixel)->clonepx.ch, NULL);
+ }
+ }
+ }
+ else {
+ if (ibuf->rect_float) {
+ ((ProjPixelClone *)projPixel)->clonepx.f[3] = 0;
+ }
+ else {
+ ((ProjPixelClone *)projPixel)->clonepx.ch[3] = 0;
+ }
+ }
+
+ }
+ else {
+ float co[2];
+ Vec2Subf(co, projPixel->projCoSS, (float *)ps->cloneOffset);
+
+ /* no need to initialize the bucket, we're only checking buckets faces and for this
+ * the faces are alredy initialized in project_paint_delayed_face_init(...) */
+ if (ibuf->rect_float) {
+ if (!project_paint_PickColor(ps, co, ((ProjPixelClone *)projPixel)->clonepx.f, NULL, 1)) {
+ ((ProjPixelClone *)projPixel)->clonepx.f[3] = 0; /* zero alpha - ignore */
+ }
+ }
+ else {
+ if (!project_paint_PickColor(ps, co, NULL, ((ProjPixelClone *)projPixel)->clonepx.ch, 1)) {
+ ((ProjPixelClone *)projPixel)->clonepx.ch[3] = 0; /* zero alpha - ignore */
+ }
+ }
+ }
+ }
+
+#ifdef PROJ_DEBUG_PAINT
+ if (ibuf->rect_float) projPixel->pixel.f_pt[0] = 0;
+ else projPixel->pixel.ch_pt[0] = 0;
+#endif
+ projPixel->image_index = image_index;
+
+ return projPixel;
+}
+
+static int line_clip_rect2f(
+ rctf *rect,
+ const float l1[2], const float l2[2],
+ float l1_clip[2], float l2_clip[2])
+{
+ /* first account for horizontal, then vertical lines */
+ /* horiz */
+ if (fabs(l1[1]-l2[1]) < PROJ_GEOM_TOLERANCE) {
+ /* is the line out of range on its Y axis? */
+ if (l1[1] < rect->ymin || l1[1] > rect->ymax) {
+ return 0;
+ }
+ /* line is out of range on its X axis */
+ if ((l1[0] < rect->xmin && l2[0] < rect->xmin) || (l1[0] > rect->xmax && l2[0] > rect->xmax)) {
+ return 0;
+ }
+
+
+ if (fabs(l1[0]-l2[0]) < PROJ_GEOM_TOLERANCE) { /* this is a single point (or close to)*/
+ if (BLI_in_rctf(rect, l1[0], l1[1])) {
+ VECCOPY2D(l1_clip, l1);
+ VECCOPY2D(l2_clip, l2);
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ VECCOPY2D(l1_clip, l1);
+ VECCOPY2D(l2_clip, l2);
+ CLAMP(l1_clip[0], rect->xmin, rect->xmax);
+ CLAMP(l2_clip[0], rect->xmin, rect->xmax);
+ return 1;
+ }
+ else if (fabs(l1[0]-l2[0]) < PROJ_GEOM_TOLERANCE) {
+ /* is the line out of range on its X axis? */
+ if (l1[0] < rect->xmin || l1[0] > rect->xmax) {
+ return 0;
+ }
+
+ /* line is out of range on its Y axis */
+ if ((l1[1] < rect->ymin && l2[1] < rect->ymin) || (l1[1] > rect->ymax && l2[1] > rect->ymax)) {
+ return 0;
+ }
+
+ if (fabs(l1[1]-l2[1]) < PROJ_GEOM_TOLERANCE) { /* this is a single point (or close to)*/
+ if (BLI_in_rctf(rect, l1[0], l1[1])) {
+ VECCOPY2D(l1_clip, l1);
+ VECCOPY2D(l2_clip, l2);
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ VECCOPY2D(l1_clip, l1);
+ VECCOPY2D(l2_clip, l2);
+ CLAMP(l1_clip[1], rect->ymin, rect->ymax);
+ CLAMP(l2_clip[1], rect->ymin, rect->ymax);
+ return 1;
+ }
+ else {
+ float isect;
+ short ok1 = 0;
+ short ok2 = 0;
+
+ /* Done with vertical lines */
+
+ /* are either of the points inside the rectangle ? */
+ if (BLI_in_rctf(rect, l1[0], l1[1])) {
+ VECCOPY2D(l1_clip, l1);
+ ok1 = 1;
+ }
+
+ if (BLI_in_rctf(rect, l2[0], l2[1])) {
+ VECCOPY2D(l2_clip, l2);
+ ok2 = 1;
+ }
+
+ /* line inside rect */
+ if (ok1 && ok2) return 1;
+
+ /* top/bottom */
+ if (line_isect_y(l1, l2, rect->ymin, &isect) && (isect >= rect->xmin) && (isect <= rect->xmax)) {
+ if (l1[1] < l2[1]) { /* line 1 is outside */
+ l1_clip[0] = isect;
+ l1_clip[1] = rect->ymin;
+ ok1 = 1;
+ }
+ else {
+ l2_clip[0] = isect;
+ l2_clip[1] = rect->ymin;
+ ok2 = 2;
+ }
+ }
+
+ if (ok1 && ok2) return 1;
+
+ if (line_isect_y(l1, l2, rect->ymax, &isect) && (isect >= rect->xmin) && (isect <= rect->xmax)) {
+ if (l1[1] > l2[1]) { /* line 1 is outside */
+ l1_clip[0] = isect;
+ l1_clip[1] = rect->ymax;
+ ok1 = 1;
+ }
+ else {
+ l2_clip[0] = isect;
+ l2_clip[1] = rect->ymax;
+ ok2 = 2;
+ }
+ }
+
+ if (ok1 && ok2) return 1;
+
+ /* left/right */
+ if (line_isect_x(l1, l2, rect->xmin, &isect) && (isect >= rect->ymin) && (isect <= rect->ymax)) {
+ if (l1[0] < l2[0]) { /* line 1 is outside */
+ l1_clip[0] = rect->xmin;
+ l1_clip[1] = isect;
+ ok1 = 1;
+ }
+ else {
+ l2_clip[0] = rect->xmin;
+ l2_clip[1] = isect;
+ ok2 = 2;
+ }
+ }
+
+ if (ok1 && ok2) return 1;
+
+ if (line_isect_x(l1, l2, rect->xmax, &isect) && (isect >= rect->ymin) && (isect <= rect->ymax)) {
+ if (l1[0] > l2[0]) { /* line 1 is outside */
+ l1_clip[0] = rect->xmax;
+ l1_clip[1] = isect;
+ ok1 = 1;
+ }
+ else {
+ l2_clip[0] = rect->xmax;
+ l2_clip[1] = isect;
+ ok2 = 2;
+ }
+ }
+
+ if (ok1 && ok2) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+}
+
+
+
+/* scale the quad & tri about its center
+ * scaling by PROJ_FACE_SCALE_SEAM (0.99x) is used for getting fake UV pixel coords that are on the
+ * edge of the face but slightly inside it occlusion tests dont return hits on adjacent faces */
+static void scale_quad(float insetCos[4][3], float *origCos[4], const float inset)
+{
+ float cent[3];
+ cent[0] = (origCos[0][0] + origCos[1][0] + origCos[2][0] + origCos[3][0]) / 4.0f;
+ cent[1] = (origCos[0][1] + origCos[1][1] + origCos[2][1] + origCos[3][1]) / 4.0f;
+ cent[2] = (origCos[0][2] + origCos[1][2] + origCos[2][2] + origCos[3][2]) / 4.0f;
+
+ VecSubf(insetCos[0], origCos[0], cent);
+ VecSubf(insetCos[1], origCos[1], cent);
+ VecSubf(insetCos[2], origCos[2], cent);
+ VecSubf(insetCos[3], origCos[3], cent);
+
+ VecMulf(insetCos[0], inset);
+ VecMulf(insetCos[1], inset);
+ VecMulf(insetCos[2], inset);
+ VecMulf(insetCos[3], inset);
+
+ VecAddf(insetCos[0], insetCos[0], cent);
+ VecAddf(insetCos[1], insetCos[1], cent);
+ VecAddf(insetCos[2], insetCos[2], cent);
+ VecAddf(insetCos[3], insetCos[3], cent);
+}
+
+
+static void scale_tri(float insetCos[4][3], float *origCos[4], const float inset)
+{
+ float cent[3];
+ cent[0] = (origCos[0][0] + origCos[1][0] + origCos[2][0]) / 3.0f;
+ cent[1] = (origCos[0][1] + origCos[1][1] + origCos[2][1]) / 3.0f;
+ cent[2] = (origCos[0][2] + origCos[1][2] + origCos[2][2]) / 3.0f;
+
+ VecSubf(insetCos[0], origCos[0], cent);
+ VecSubf(insetCos[1], origCos[1], cent);
+ VecSubf(insetCos[2], origCos[2], cent);
+
+ VecMulf(insetCos[0], inset);
+ VecMulf(insetCos[1], inset);
+ VecMulf(insetCos[2], inset);
+
+ VecAddf(insetCos[0], insetCos[0], cent);
+ VecAddf(insetCos[1], insetCos[1], cent);
+ VecAddf(insetCos[2], insetCos[2], cent);
+}
+
+
+static float Vec2Lenf_nosqrt(const float *v1, const float *v2)
+{
+ float x, y;
+
+ x = v1[0]-v2[0];
+ y = v1[1]-v2[1];
+ return x*x+y*y;
+}
+
+static float Vec2Lenf_nosqrt_other(const float *v1, const float v2_1, const float v2_2)
+{
+ float x, y;
+
+ x = v1[0]-v2_1;
+ y = v1[1]-v2_2;
+ return x*x+y*y;
+}
+
+/* note, use a squared value so we can use Vec2Lenf_nosqrt
+ * be sure that you have done a bounds check first or this may fail */
+/* only give bucket_bounds as an arg because we need it elsewhere */
+static int project_bucket_isect_circle(const int bucket_x, const int bucket_y, const float cent[2], const float radius_squared, rctf *bucket_bounds)
+{
+
+ /* Would normally to a simple intersection test, however we know the bounds of these 2 alredy intersect
+ * so we only need to test if the center is inside the vertical or horizontal bounds on either axis,
+ * this is even less work then an intersection test
+ *
+ if (BLI_in_rctf(bucket_bounds, cent[0], cent[1]))
+ return 1;
+ */
+
+ if((bucket_bounds->xmin <= cent[0] && bucket_bounds->xmax >= cent[0]) || (bucket_bounds->ymin <= cent[1] && bucket_bounds->ymax >= cent[1]) ) {
+ return 1;
+ }
+
+ /* out of bounds left */
+ if (cent[0] < bucket_bounds->xmin) {
+ /* lower left out of radius test */
+ if (cent[1] < bucket_bounds->ymin) {
+ return (Vec2Lenf_nosqrt_other(cent, bucket_bounds->xmin, bucket_bounds->ymin) < radius_squared) ? 1 : 0;
+ }
+ /* top left test */
+ else if (cent[1] > bucket_bounds->ymax) {
+ return (Vec2Lenf_nosqrt_other(cent, bucket_bounds->xmin, bucket_bounds->ymax) < radius_squared) ? 1 : 0;
+ }
+ }
+ else if (cent[0] > bucket_bounds->xmax) {
+ /* lower right out of radius test */
+ if (cent[1] < bucket_bounds->ymin) {
+ return (Vec2Lenf_nosqrt_other(cent, bucket_bounds->xmax, bucket_bounds->ymin) < radius_squared) ? 1 : 0;
+ }
+ /* top right test */
+ else if (cent[1] > bucket_bounds->ymax) {
+ return (Vec2Lenf_nosqrt_other(cent, bucket_bounds->xmax, bucket_bounds->ymax) < radius_squared) ? 1 : 0;
+ }
+ }
+
+ return 0;
+}
+
+
+
+/* Note for rect_to_uvspace_ortho() and rect_to_uvspace_persp()
+ * in ortho view this function gives good results when bucket_bounds are outside the triangle
+ * however in some cases, perspective view will mess up with faces that have minimal screenspace area (viewed from the side)
+ *
+ * for this reason its not relyable in this case so we'll use the Simple Barycentric' funcs that only account for points inside the triangle.
+ * however switching back to this for ortho is always an option */
+
+static void rect_to_uvspace_ortho(
+ rctf *bucket_bounds,
+ float *v1coSS, float *v2coSS, float *v3coSS,
+ float *uv1co, float *uv2co, float *uv3co,
+ float bucket_bounds_uv[4][2],
+ const int flip)
+{
+ float uv[2];
+ float w[3];
+
+ /* get the UV space bounding box */
+ uv[0] = bucket_bounds->xmax;
+ uv[1] = bucket_bounds->ymin;
+ BarycentricWeights2f(uv, v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[flip?3:0], uv1co, uv2co, uv3co, w);
+
+ //uv[0] = bucket_bounds->xmax; // set above
+ uv[1] = bucket_bounds->ymax;
+ BarycentricWeights2f(uv, v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[flip?2:1], uv1co, uv2co, uv3co, w);
+
+ uv[0] = bucket_bounds->xmin;
+ //uv[1] = bucket_bounds->ymax; // set above
+ BarycentricWeights2f(uv, v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[flip?1:2], uv1co, uv2co, uv3co, w);
+
+ //uv[0] = bucket_bounds->xmin; // set above
+ uv[1] = bucket_bounds->ymin;
+ BarycentricWeights2f(uv, v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[flip?0:3], uv1co, uv2co, uv3co, w);
+}
+
+/* same as above but use BarycentricWeightsPersp2f */
+static void rect_to_uvspace_persp(
+ rctf *bucket_bounds,
+ float *v1coSS, float *v2coSS, float *v3coSS,
+ float *uv1co, float *uv2co, float *uv3co,
+ float bucket_bounds_uv[4][2],
+ const int flip
+ )
+{
+ float uv[2];
+ float w[3];
+
+ /* get the UV space bounding box */
+ uv[0] = bucket_bounds->xmax;
+ uv[1] = bucket_bounds->ymin;
+ BarycentricWeightsPersp2f(uv, v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[flip?3:0], uv1co, uv2co, uv3co, w);
+
+ //uv[0] = bucket_bounds->xmax; // set above
+ uv[1] = bucket_bounds->ymax;
+ BarycentricWeightsPersp2f(uv, v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[flip?2:1], uv1co, uv2co, uv3co, w);
+
+ uv[0] = bucket_bounds->xmin;
+ //uv[1] = bucket_bounds->ymax; // set above
+ BarycentricWeightsPersp2f(uv, v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[flip?1:2], uv1co, uv2co, uv3co, w);
+
+ //uv[0] = bucket_bounds->xmin; // set above
+ uv[1] = bucket_bounds->ymin;
+ BarycentricWeightsPersp2f(uv, v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[flip?0:3], uv1co, uv2co, uv3co, w);
+}
+
+/* This works as we need it to but we can save a few steps and not use it */
+
+#if 0
+static float angle_2d_clockwise(const float p1[2], const float p2[2], const float p3[2])
+{
+ float v1[2], v2[2];
+
+ v1[0] = p1[0]-p2[0]; v1[1] = p1[1]-p2[1];
+ v2[0] = p3[0]-p2[0]; v2[1] = p3[1]-p2[1];
+
+ return -atan2(v1[0]*v2[1] - v1[1]*v2[0], v1[0]*v2[0]+v1[1]*v2[1]);
+}
+#endif
+
+#define ISECT_1 (1)
+#define ISECT_2 (1<<1)
+#define ISECT_3 (1<<2)
+#define ISECT_4 (1<<3)
+#define ISECT_ALL3 ((1<<3)-1)
+#define ISECT_ALL4 ((1<<4)-1)
+
+/* limit must be a fraction over 1.0f */
+static int IsectPT2Df_limit(float pt[2], float v1[2], float v2[2], float v3[2], float limit)
+{
+ return ((AreaF2Dfl(pt,v1,v2) + AreaF2Dfl(pt,v2,v3) + AreaF2Dfl(pt,v3,v1)) / (AreaF2Dfl(v1,v2,v3))) < limit;
+}
+
+/* Clip the face by a bucket and set the uv-space bucket_bounds_uv
+ * so we have the clipped UV's to do pixel intersection tests with
+ * */
+static int float_z_sort_flip(const void *p1, const void *p2) {
+ return (((float *)p1)[2] < ((float *)p2)[2] ? 1:-1);
+}
+
+static int float_z_sort(const void *p1, const void *p2) {
+ return (((float *)p1)[2] < ((float *)p2)[2] ?-1:1);
+}
+
+static void project_bucket_clip_face(
+ const int is_ortho,
+ rctf *bucket_bounds,
+ float *v1coSS, float *v2coSS, float *v3coSS,
+ float *uv1co, float *uv2co, float *uv3co,
+ float bucket_bounds_uv[8][2],
+ int *tot)
+{
+ int inside_bucket_flag = 0;
+ int inside_face_flag = 0;
+ const int flip = ((SIDE_OF_LINE(v1coSS, v2coSS, v3coSS) > 0.0f) != (SIDE_OF_LINE(uv1co, uv2co, uv3co) > 0.0f));
+
+ float bucket_bounds_ss[4][2];
+ float w[3];
+
+ /* get the UV space bounding box */
+ inside_bucket_flag |= BLI_in_rctf(bucket_bounds, v1coSS[0], v1coSS[1]);
+ inside_bucket_flag |= BLI_in_rctf(bucket_bounds, v2coSS[0], v2coSS[1]) << 1;
+ inside_bucket_flag |= BLI_in_rctf(bucket_bounds, v3coSS[0], v3coSS[1]) << 2;
+
+ if (inside_bucket_flag == ISECT_ALL3) {
+ /* all screenspace points are inside the bucket bounding box, this means we dont need to clip and can simply return the UVs */
+ if (flip) { /* facing the back? */
+ VECCOPY2D(bucket_bounds_uv[0], uv3co);
+ VECCOPY2D(bucket_bounds_uv[1], uv2co);
+ VECCOPY2D(bucket_bounds_uv[2], uv1co);
+ }
+ else {
+ VECCOPY2D(bucket_bounds_uv[0], uv1co);
+ VECCOPY2D(bucket_bounds_uv[1], uv2co);
+ VECCOPY2D(bucket_bounds_uv[2], uv3co);
+ }
+
+ *tot = 3;
+ return;
+ }
+
+ /* get the UV space bounding box */
+ /* use IsectPT2Df_limit here so we catch points are are touching the tri edge (or a small fraction over) */
+ bucket_bounds_ss[0][0] = bucket_bounds->xmax;
+ bucket_bounds_ss[0][1] = bucket_bounds->ymin;
+ inside_face_flag |= (IsectPT2Df_limit(bucket_bounds_ss[0], v1coSS, v2coSS, v3coSS, 1+PROJ_GEOM_TOLERANCE) ? ISECT_1 : 0);
+
+ bucket_bounds_ss[1][0] = bucket_bounds->xmax;
+ bucket_bounds_ss[1][1] = bucket_bounds->ymax;
+ inside_face_flag |= (IsectPT2Df_limit(bucket_bounds_ss[1], v1coSS, v2coSS, v3coSS, 1+PROJ_GEOM_TOLERANCE) ? ISECT_2 : 0);
+
+ bucket_bounds_ss[2][0] = bucket_bounds->xmin;
+ bucket_bounds_ss[2][1] = bucket_bounds->ymax;
+ inside_face_flag |= (IsectPT2Df_limit(bucket_bounds_ss[2], v1coSS, v2coSS, v3coSS, 1+PROJ_GEOM_TOLERANCE) ? ISECT_3 : 0);
+
+ bucket_bounds_ss[3][0] = bucket_bounds->xmin;
+ bucket_bounds_ss[3][1] = bucket_bounds->ymin;
+ inside_face_flag |= (IsectPT2Df_limit(bucket_bounds_ss[3], v1coSS, v2coSS, v3coSS, 1+PROJ_GEOM_TOLERANCE) ? ISECT_4 : 0);
+
+ if (inside_face_flag == ISECT_ALL4) {
+ /* bucket is totally inside the screenspace face, we can safely use weights */
+
+ if (is_ortho) rect_to_uvspace_ortho(bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, bucket_bounds_uv, flip);
+ else rect_to_uvspace_persp(bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, bucket_bounds_uv, flip);
+
+ *tot = 4;
+ return;
+ }
+ else {
+ /* The Complicated Case!
+ *
+ * The 2 cases above are where the face is inside the bucket or the bucket is inside the face.
+ *
+ * we need to make a convex polyline from the intersection between the screenspace face
+ * and the bucket bounds.
+ *
+ * There are a number of ways this could be done, currently it just collects all intersecting verts,
+ * and line intersections, then sorts them clockwise, this is a lot easier then evaluating the geometry to
+ * do a correct clipping on both shapes. */
+
+
+ /* add a bunch of points, we know must make up the convex hull which is the clipped rect and triangle */
+
+
+
+ /* Maximum possible 6 intersections when using a rectangle and triangle */
+ float isectVCosSS[8][3]; /* The 3rd float is used to store angle for qsort(), NOT as a Z location */
+ float v1_clipSS[2], v2_clipSS[2];
+
+ /* calc center*/
+ float cent[2] = {0.0f, 0.0f};
+ /*float up[2] = {0.0f, 1.0f};*/
+ int i;
+ short doubles;
+
+ (*tot) = 0;
+
+ if (inside_face_flag & ISECT_1) { VECCOPY2D(isectVCosSS[*tot], bucket_bounds_ss[0]); (*tot)++; }
+ if (inside_face_flag & ISECT_2) { VECCOPY2D(isectVCosSS[*tot], bucket_bounds_ss[1]); (*tot)++; }
+ if (inside_face_flag & ISECT_3) { VECCOPY2D(isectVCosSS[*tot], bucket_bounds_ss[2]); (*tot)++; }
+ if (inside_face_flag & ISECT_4) { VECCOPY2D(isectVCosSS[*tot], bucket_bounds_ss[3]); (*tot)++; }
+
+ if (inside_bucket_flag & ISECT_1) { VECCOPY2D(isectVCosSS[*tot], v1coSS); (*tot)++; }
+ if (inside_bucket_flag & ISECT_2) { VECCOPY2D(isectVCosSS[*tot], v2coSS); (*tot)++; }
+ if (inside_bucket_flag & ISECT_3) { VECCOPY2D(isectVCosSS[*tot], v3coSS); (*tot)++; }
+
+ if ((inside_bucket_flag & (ISECT_1|ISECT_2)) != (ISECT_1|ISECT_2)) {
+ if (line_clip_rect2f(bucket_bounds, v1coSS, v2coSS, v1_clipSS, v2_clipSS)) {
+ if ((inside_bucket_flag & ISECT_1)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; }
+ if ((inside_bucket_flag & ISECT_2)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; }
+ }
+ }
+
+ if ((inside_bucket_flag & (ISECT_2|ISECT_3)) != (ISECT_2|ISECT_3)) {
+ if (line_clip_rect2f(bucket_bounds, v2coSS, v3coSS, v1_clipSS, v2_clipSS)) {
+ if ((inside_bucket_flag & ISECT_2)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; }
+ if ((inside_bucket_flag & ISECT_3)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; }
+ }
+ }
+
+ if ((inside_bucket_flag & (ISECT_3|ISECT_1)) != (ISECT_3|ISECT_1)) {
+ if (line_clip_rect2f(bucket_bounds, v3coSS, v1coSS, v1_clipSS, v2_clipSS)) {
+ if ((inside_bucket_flag & ISECT_3)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; }
+ if ((inside_bucket_flag & ISECT_1)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; }
+ }
+ }
+
+
+ if ((*tot) < 3) { /* no intersections to speak of */
+ *tot = 0;
+ }
+
+ /* now we have all points we need, collect their angles and sort them clockwise */
+
+ for(i=0; i<(*tot); i++) {
+ cent[0] += isectVCosSS[i][0];
+ cent[1] += isectVCosSS[i][1];
+ }
+ cent[0] = cent[0] / (float)(*tot);
+ cent[1] = cent[1] / (float)(*tot);
+
+
+
+ /* Collect angles for every point around the center point */
+
+
+#if 0 /* uses a few more cycles then the above loop */
+ for(i=0; i<(*tot); i++) {
+ isectVCosSS[i][2] = angle_2d_clockwise(up, cent, isectVCosSS[i]);
+ }
+#endif
+
+ v1_clipSS[0] = cent[0]; /* Abuse this var for the loop below */
+ v1_clipSS[1] = cent[1] + 1.0f;
+
+ for(i=0; i<(*tot); i++) {
+ v2_clipSS[0] = isectVCosSS[i][0] - cent[0];
+ v2_clipSS[1] = isectVCosSS[i][1] - cent[1];
+ isectVCosSS[i][2] = atan2(v1_clipSS[0]*v2_clipSS[1] - v1_clipSS[1]*v2_clipSS[0], v1_clipSS[0]*v2_clipSS[0]+v1_clipSS[1]*v2_clipSS[1]);
+ }
+
+ if (flip) qsort(isectVCosSS, *tot, sizeof(float)*3, float_z_sort_flip);
+ else qsort(isectVCosSS, *tot, sizeof(float)*3, float_z_sort);
+
+
+ /* remove doubles */
+ /* first/last check */
+ if (fabs(isectVCosSS[0][0]-isectVCosSS[(*tot)-1][0]) < PROJ_GEOM_TOLERANCE && fabs(isectVCosSS[0][1]-isectVCosSS[(*tot)-1][1]) < PROJ_GEOM_TOLERANCE) {
+ (*tot)--;
+ }
+
+ /* its possible there is only a few left after remove doubles */
+ if ((*tot) < 3) {
+ // printf("removed too many doubles A\n");
+ *tot = 0;
+ return;
+ }
+
+ doubles = TRUE;
+ while (doubles==TRUE) {
+ doubles = FALSE;
+ for(i=1; i<(*tot); i++) {
+ if (fabs(isectVCosSS[i-1][0]-isectVCosSS[i][0]) < PROJ_GEOM_TOLERANCE &&
+ fabs(isectVCosSS[i-1][1]-isectVCosSS[i][1]) < PROJ_GEOM_TOLERANCE)
+ {
+ int j;
+ for(j=i+1; j<(*tot); j++) {
+ isectVCosSS[j-1][0] = isectVCosSS[j][0];
+ isectVCosSS[j-1][1] = isectVCosSS[j][1];
+ }
+ doubles = TRUE; /* keep looking for more doubles */
+ (*tot)--;
+ }
+ }
+ }
+
+ /* its possible there is only a few left after remove doubles */
+ if ((*tot) < 3) {
+ // printf("removed too many doubles B\n");
+ *tot = 0;
+ return;
+ }
+
+
+ if (is_ortho) {
+ for(i=0; i<(*tot); i++) {
+ BarycentricWeights2f(isectVCosSS[i], v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[i], uv1co, uv2co, uv3co, w);
+ }
+ }
+ else {
+ for(i=0; i<(*tot); i++) {
+ BarycentricWeightsPersp2f(isectVCosSS[i], v1coSS, v2coSS, v3coSS, w);
+ Vec2Weightf(bucket_bounds_uv[i], uv1co, uv2co, uv3co, w);
+ }
+ }
+ }
+
+#ifdef PROJ_DEBUG_PRINT_CLIP
+ /* include this at the bottom of the above function to debug the output */
+
+ {
+ /* If there are ever any problems, */
+ float test_uv[4][2];
+ int i;
+ if (is_ortho) rect_to_uvspace_ortho(bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, test_uv, flip);
+ else rect_to_uvspace_persp(bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, test_uv, flip);
+ printf("( [(%f,%f), (%f,%f), (%f,%f), (%f,%f)], ", test_uv[0][0], test_uv[0][1], test_uv[1][0], test_uv[1][1], test_uv[2][0], test_uv[2][1], test_uv[3][0], test_uv[3][1]);
+
+ printf(" [(%f,%f), (%f,%f), (%f,%f)], ", uv1co[0], uv1co[1], uv2co[0], uv2co[1], uv3co[0], uv3co[1]);
+
+ printf("[");
+ for (i=0; i < (*tot); i++) {
+ printf("(%f, %f),", bucket_bounds_uv[i][0], bucket_bounds_uv[i][1]);
+ }
+ printf("]),\\\n");
+ }
+#endif
+}
+
+ /*
+# This script creates faces in a blender scene from printed data above.
+
+project_ls = [
+...(output from above block)...
+]
+
+from Blender import Scene, Mesh, Window, sys, Mathutils
+
+import bpy
+
+V = Mathutils.Vector
+
+def main():
+ sce = bpy.data.scenes.active
+
+ for item in project_ls:
+ bb = item[0]
+ uv = item[1]
+ poly = item[2]
+
+ me = bpy.data.meshes.new()
+ ob = sce.objects.new(me)
+
+ me.verts.extend([V(bb[0]).resize3D(), V(bb[1]).resize3D(), V(bb[2]).resize3D(), V(bb[3]).resize3D()])
+ me.faces.extend([(0,1,2,3),])
+ me.verts.extend([V(uv[0]).resize3D(), V(uv[1]).resize3D(), V(uv[2]).resize3D()])
+ me.faces.extend([(4,5,6),])
+
+ vs = [V(p).resize3D() for p in poly]
+ print len(vs)
+ l = len(me.verts)
+ me.verts.extend(vs)
+
+ i = l
+ while i < len(me.verts):
+ ii = i+1
+ if ii==len(me.verts):
+ ii = l
+ me.edges.extend([i, ii])
+ i+=1
+
+if __name__ == '__main__':
+ main()
+ */
+
+
+#undef ISECT_1
+#undef ISECT_2
+#undef ISECT_3
+#undef ISECT_4
+#undef ISECT_ALL3
+#undef ISECT_ALL4
+
+
+/* checks if pt is inside a convex 2D polyline, the polyline must be ordered rotating clockwise
+ * otherwise it would have to test for mixed (SIDE_OF_LINE > 0.0f) cases */
+int IsectPoly2Df(const float pt[2], float uv[][2], const int tot)
+{
+ int i;
+ if (SIDE_OF_LINE(uv[tot-1], uv[0], pt) < 0.0f)
+ return 0;
+
+ for (i=1; i<tot; i++) {
+ if (SIDE_OF_LINE(uv[i-1], uv[i], pt) < 0.0f)
+ return 0;
+
+ }
+
+ return 1;
+}
+
+/* One of the most important function for projectiopn painting, since it selects the pixels to be added into each bucket.
+ * initialize pixels from this face where it intersects with the bucket_index, optionally initialize pixels for removing seams */
+static void project_paint_face_init(const ProjPaintState *ps, const int thread_index, const int bucket_index, const int face_index, const int image_index, rctf *bucket_bounds, const ImBuf *ibuf)
+{
+ /* Projection vars, to get the 3D locations into screen space */
+ MemArena *arena = ps->arena_mt[thread_index];
+ LinkNode **bucketPixelNodes = ps->bucketRect + bucket_index;
+ LinkNode *bucketFaceNodes = ps->bucketFaces[bucket_index];
+
+ const MFace *mf = ps->dm_mface + face_index;
+ const MTFace *tf = ps->dm_mtface + face_index;
+
+ /* UV/pixel seeking data */
+ int x; /* Image X-Pixel */
+ int y;/* Image Y-Pixel */
+ float mask;
+ float uv[2]; /* Image floating point UV - same as x, y but from 0.0-1.0 */
+
+ int side;
+ float *v1coSS, *v2coSS, *v3coSS; /* vert co screen-space, these will be assigned to mf->v1,2,3 or mf->v1,3,4 */
+
+ float *vCo[4]; /* vertex screenspace coords */
+
+ float w[3], wco[3];
+
+ float *uv1co, *uv2co, *uv3co; /* for convenience only, these will be assigned to tf->uv[0],1,2 or tf->uv[0],2,3 */
+ float pixelScreenCo[4];
+
+ rcti bounds_px; /* ispace bounds */
+ /* vars for getting uvspace bounds */
+
+ float tf_uv_pxoffset[4][2]; /* bucket bounds in UV space so we can init pixels only for this face, */
+ float xhalfpx, yhalfpx;
+ const float ibuf_xf = ibuf->x, ibuf_yf = ibuf->y;
+
+ int has_x_isect = 0, has_isect = 0; /* for early loop exit */
+
+ int i1, i2, i3;
+
+ float uv_clip[8][2];
+ int uv_clip_tot;
+ const short is_ortho = ps->is_ortho;
+
+ vCo[0] = ps->dm_mvert[mf->v1].co;
+ vCo[1] = ps->dm_mvert[mf->v2].co;
+ vCo[2] = ps->dm_mvert[mf->v3].co;
+
+
+ /* Use tf_uv_pxoffset instead of tf->uv so we can offset the UV half a pixel
+ * this is done so we can avoid offseting all the pixels by 0.5 which causes
+ * problems when wrapping negative coords */
+ xhalfpx = 0.5f / ibuf_xf;
+ yhalfpx = 0.5f / ibuf_yf;
+
+ tf_uv_pxoffset[0][0] = tf->uv[0][0] - xhalfpx;
+ tf_uv_pxoffset[0][1] = tf->uv[0][1] - yhalfpx;
+
+ tf_uv_pxoffset[1][0] = tf->uv[1][0] - xhalfpx;
+ tf_uv_pxoffset[1][1] = tf->uv[1][1] - yhalfpx;
+
+ tf_uv_pxoffset[2][0] = tf->uv[2][0] - xhalfpx;
+ tf_uv_pxoffset[2][1] = tf->uv[2][1] - yhalfpx;
+
+ if (mf->v4) {
+ vCo[3] = ps->dm_mvert[ mf->v4 ].co;
+
+ tf_uv_pxoffset[3][0] = tf->uv[3][0] - xhalfpx;
+ tf_uv_pxoffset[3][1] = tf->uv[3][1] - yhalfpx;
+ side = 1;
+ }
+ else {
+ side = 0;
+ }
+
+ do {
+ if (side==1) {
+ i1=0; i2=2; i3=3;
+ }
+ else {
+ i1=0; i2=1; i3=2;
+ }
+
+ uv1co = tf_uv_pxoffset[i1]; // was tf->uv[i1];
+ uv2co = tf_uv_pxoffset[i2]; // was tf->uv[i2];
+ uv3co = tf_uv_pxoffset[i3]; // was tf->uv[i3];
+
+ v1coSS = ps->screenCoords[ (*(&mf->v1 + i1)) ];
+ v2coSS = ps->screenCoords[ (*(&mf->v1 + i2)) ];
+ v3coSS = ps->screenCoords[ (*(&mf->v1 + i3)) ];
+
+
+ /* This funtion gives is a concave polyline in UV space from the clipped quad and tri*/
+ project_bucket_clip_face(
+ is_ortho, bucket_bounds,
+ v1coSS, v2coSS, v3coSS,
+ uv1co, uv2co, uv3co,
+ uv_clip, &uv_clip_tot
+ );
+
+
+ /* sometimes this happens, better just allow for 8 intersectiosn even though there should be max 6 */
+ /*
+ if (uv_clip_tot>6) {
+ printf("this should never happen! %d\n", uv_clip_tot);
+ }*/
+
+
+ if (pixel_bounds_array(uv_clip, &bounds_px, ibuf->x, ibuf->y, uv_clip_tot)) {
+
+ /* clip face and */
+
+ has_isect = 0;
+ for (y = bounds_px.ymin; y < bounds_px.ymax; y++) {
+ //uv[1] = (((float)y) + 0.5f) / (float)ibuf->y;
+ uv[1] = (float)y / ibuf_yf; /* use pixel offset UV coords instead */
+
+ has_x_isect = 0;
+ for (x = bounds_px.xmin; x < bounds_px.xmax; x++) {
+ //uv[0] = (((float)x) + 0.5f) / ibuf->x;
+ uv[0] = (float)x / ibuf_xf; /* use pixel offset UV coords instead */
+
+ if (IsectPoly2Df(uv, uv_clip, uv_clip_tot)) {
+
+ has_x_isect = has_isect = 1;
+
+ if (is_ortho) screen_px_from_ortho(uv, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, pixelScreenCo, w);
+ else screen_px_from_persp(uv, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, pixelScreenCo, w);
+
+ /* a pitty we need to get the worldspace pixel location here */
+ if(ps->rv3d->rflag & RV3D_CLIPPING) {
+ VecWeightf(wco, ps->dm_mvert[ (*(&mf->v1 + i1)) ].co, ps->dm_mvert[ (*(&mf->v1 + i2)) ].co, ps->dm_mvert[ (*(&mf->v1 + i3)) ].co, w);
+ Mat4MulVecfl(ps->ob->obmat, wco);
+ if(view3d_test_clipping(ps->rv3d, wco)) {
+ continue; /* Watch out that no code below this needs to run */
+ }
+ }
+
+ /* Is this UV visible from the view? - raytrace */
+ /* project_paint_PickFace is less complex, use for testing */
+ //if (project_paint_PickFace(ps, pixelScreenCo, w, &side) == face_index) {
+ if (ps->do_occlude==0 || !project_bucket_point_occluded(ps, bucketFaceNodes, face_index, pixelScreenCo)) {
+
+ mask = project_paint_uvpixel_mask(ps, face_index, side, w);
+
+ if (mask > 0.0f) {
+ BLI_linklist_prepend_arena(
+ bucketPixelNodes,
+ project_paint_uvpixel_init(ps, arena, ibuf, x, y, mask, face_index, image_index, pixelScreenCo, side, w),
+ arena
+ );
+ }
+ }
+
+ }
+//#if 0
+ else if (has_x_isect) {
+ /* assuming the face is not a bow-tie - we know we cant intersect again on the X */
+ break;
+ }
+//#endif
+ }
+
+
+#if 0 /* TODO - investigate why this dosnt work sometimes! it should! */
+ /* no intersection for this entire row, after some intersection above means we can quit now */
+ if (has_x_isect==0 && has_isect) {
+ break;
+ }
+#endif
+ }
+ }
+ } while(side--);
+
+
+
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ if (ps->seam_bleed_px > 0.0f) {
+ int face_seam_flag;
+
+ if (ps->thread_tot > 1)
+ BLI_lock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
+
+ face_seam_flag = ps->faceSeamFlags[face_index];
+
+ /* are any of our edges un-initialized? */
+ if ((face_seam_flag & (PROJ_FACE_SEAM1|PROJ_FACE_NOSEAM1))==0 ||
+ (face_seam_flag & (PROJ_FACE_SEAM2|PROJ_FACE_NOSEAM2))==0 ||
+ (face_seam_flag & (PROJ_FACE_SEAM3|PROJ_FACE_NOSEAM3))==0 ||
+ (face_seam_flag & (PROJ_FACE_SEAM4|PROJ_FACE_NOSEAM4))==0
+ ) {
+ project_face_seams_init(ps, face_index, mf->v4);
+ face_seam_flag = ps->faceSeamFlags[face_index];
+ //printf("seams - %d %d %d %d\n", flag&PROJ_FACE_SEAM1, flag&PROJ_FACE_SEAM2, flag&PROJ_FACE_SEAM3, flag&PROJ_FACE_SEAM4);
+ }
+
+ if ((face_seam_flag & (PROJ_FACE_SEAM1|PROJ_FACE_SEAM2|PROJ_FACE_SEAM3|PROJ_FACE_SEAM4))==0) {
+
+ if (ps->thread_tot > 1)
+ BLI_unlock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
+
+ }
+ else {
+ /* we have a seam - deal with it! */
+
+ /* Now create new UV's for the seam face */
+ float (*outset_uv)[2] = ps->faceSeamUVs[face_index];
+ float insetCos[4][3]; /* inset face coords. NOTE!!! ScreenSace for ortho, Worldspace in prespective view */
+
+ float *uv_seam_quad[4];
+ float fac;
+ float *vCoSS[4]; /* vertex screenspace coords */
+
+ float bucket_clip_edges[2][2]; /* store the screenspace coords of the face, clipped by the bucket's screen aligned rectangle */
+ float edge_verts_inset_clip[2][3];
+ int fidx1, fidx2; /* face edge pairs - loop throuh these ((0,1), (1,2), (2,3), (3,0)) or ((0,1), (1,2), (2,0)) for a tri */
+
+ float seam_subsection[4][2];
+ float fac1, fac2, ftot;
+
+
+ if (outset_uv[0][0]==FLT_MAX) /* first time initialize */
+ uv_image_outset(tf_uv_pxoffset, outset_uv, ps->seam_bleed_px, ibuf->x, ibuf->y, mf->v4);
+
+ /* ps->faceSeamUVs cant be modified when threading, now this is done we can unlock */
+ if (ps->thread_tot > 1)
+ BLI_unlock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
+
+ vCoSS[0] = ps->screenCoords[mf->v1];
+ vCoSS[1] = ps->screenCoords[mf->v2];
+ vCoSS[2] = ps->screenCoords[mf->v3];
+ if (mf->v4)
+ vCoSS[3] = ps->screenCoords[ mf->v4 ];
+
+ /* PROJ_FACE_SCALE_SEAM must be slightly less then 1.0f */
+ if (is_ortho) {
+ if (mf->v4) scale_quad(insetCos, vCoSS, PROJ_FACE_SCALE_SEAM);
+ else scale_tri(insetCos, vCoSS, PROJ_FACE_SCALE_SEAM);
+ }
+ else {
+ if (mf->v4) scale_quad(insetCos, vCo, PROJ_FACE_SCALE_SEAM);
+ else scale_tri(insetCos, vCo, PROJ_FACE_SCALE_SEAM);
+ }
+
+ side = 0; /* for triangles this wont need to change */
+
+ for (fidx1 = 0; fidx1 < (mf->v4 ? 4 : 3); fidx1++) {
+ if (mf->v4) fidx2 = (fidx1==3) ? 0 : fidx1+1; /* next fidx in the face (0,1,2,3) -> (1,2,3,0) */
+ else fidx2 = (fidx1==2) ? 0 : fidx1+1; /* next fidx in the face (0,1,2) -> (1,2,0) */
+
+ if ( (face_seam_flag & (1<<fidx1)) && /* 1<<fidx1 -> PROJ_FACE_SEAM# */
+ line_clip_rect2f(bucket_bounds, vCoSS[fidx1], vCoSS[fidx2], bucket_clip_edges[0], bucket_clip_edges[1])
+ ) {
+
+ ftot = Vec2Lenf(vCoSS[fidx1], vCoSS[fidx2]); /* screenspace edge length */
+
+ if (ftot > 0.0f) { /* avoid div by zero */
+ if (mf->v4) {
+ if (fidx1==2 || fidx2==2) side= 1;
+ else side= 0;
+ }
+
+ fac1 = Vec2Lenf(vCoSS[fidx1], bucket_clip_edges[0]) / ftot;
+ fac2 = Vec2Lenf(vCoSS[fidx1], bucket_clip_edges[1]) / ftot;
+
+ uv_seam_quad[0] = tf_uv_pxoffset[fidx1];
+ uv_seam_quad[1] = tf_uv_pxoffset[fidx2];
+ uv_seam_quad[2] = outset_uv[fidx2];
+ uv_seam_quad[3] = outset_uv[fidx1];
+
+ Vec2Lerpf(seam_subsection[0], uv_seam_quad[0], uv_seam_quad[1], fac1);
+ Vec2Lerpf(seam_subsection[1], uv_seam_quad[0], uv_seam_quad[1], fac2);
+
+ Vec2Lerpf(seam_subsection[2], uv_seam_quad[3], uv_seam_quad[2], fac2);
+ Vec2Lerpf(seam_subsection[3], uv_seam_quad[3], uv_seam_quad[2], fac1);
+
+ /* if the bucket_clip_edges values Z values was kept we could avoid this
+ * Inset needs to be added so occlusion tests wont hit adjacent faces */
+ VecLerpf(edge_verts_inset_clip[0], insetCos[fidx1], insetCos[fidx2], fac1);
+ VecLerpf(edge_verts_inset_clip[1], insetCos[fidx1], insetCos[fidx2], fac2);
+
+
+ if (pixel_bounds_uv(seam_subsection[0], seam_subsection[1], seam_subsection[2], seam_subsection[3], &bounds_px, ibuf->x, ibuf->y, 1)) {
+ /* bounds between the seam rect and the uvspace bucket pixels */
+
+ has_isect = 0;
+ for (y = bounds_px.ymin; y < bounds_px.ymax; y++) {
+ // uv[1] = (((float)y) + 0.5f) / (float)ibuf->y;
+ uv[1] = (float)y / ibuf_yf; /* use offset uvs instead */
+
+ has_x_isect = 0;
+ for (x = bounds_px.xmin; x < bounds_px.xmax; x++) {
+ //uv[0] = (((float)x) + 0.5f) / (float)ibuf->x;
+ uv[0] = (float)x / ibuf_xf; /* use offset uvs instead */
+
+ /* test we're inside uvspace bucket and triangle bounds */
+ if (IsectPQ2Df(uv, seam_subsection[0], seam_subsection[1], seam_subsection[2], seam_subsection[3])) {
+
+ /* We need to find the closest point along the face edge,
+ * getting the screen_px_from_*** wont work because our actual location
+ * is not relevent, since we are outside the face, Use VecLerpf to find
+ * our location on the side of the face's UV */
+ /*
+ if (is_ortho) screen_px_from_ortho(ps, uv, v1co, v2co, v3co, uv1co, uv2co, uv3co, pixelScreenCo);
+ else screen_px_from_persp(ps, uv, v1co, v2co, v3co, uv1co, uv2co, uv3co, pixelScreenCo);
+ */
+
+ /* Since this is a seam we need to work out where on the line this pixel is */
+ //fac = lambda_cp_line2(uv, uv_seam_quad[0], uv_seam_quad[1]);
+
+ fac = lambda_cp_line2(uv, seam_subsection[0], seam_subsection[1]);
+ if (fac < 0.0f) { VECCOPY(pixelScreenCo, edge_verts_inset_clip[0]); }
+ else if (fac > 1.0f) { VECCOPY(pixelScreenCo, edge_verts_inset_clip[1]); }
+ else { VecLerpf(pixelScreenCo, edge_verts_inset_clip[0], edge_verts_inset_clip[1], fac); }
+
+ if (!is_ortho) {
+ pixelScreenCo[3] = 1.0f;
+ Mat4MulVec4fl((float(*)[4])ps->projectMat, pixelScreenCo); /* cast because of const */
+ pixelScreenCo[0] = (float)(ps->ar->winx/2.0f)+(ps->ar->winx/2.0f)*pixelScreenCo[0]/pixelScreenCo[3];
+ pixelScreenCo[1] = (float)(ps->ar->winy/2.0f)+(ps->ar->winy/2.0f)*pixelScreenCo[1]/pixelScreenCo[3];
+ pixelScreenCo[2] = pixelScreenCo[2]/pixelScreenCo[3]; /* Use the depth for bucket point occlusion */
+ }
+
+ if (ps->do_occlude==0 || !project_bucket_point_occluded(ps, bucketFaceNodes, face_index, pixelScreenCo)) {
+
+ /* Only bother calculating the weights if we intersect */
+ if (ps->do_mask_normal || ps->dm_mtface_clone) {
+ /* TODO, this is not QUITE correct since UV is not inside the UV's but good enough for seams */
+ if (side) {
+ BarycentricWeights2f(uv, tf_uv_pxoffset[0], tf_uv_pxoffset[2], tf_uv_pxoffset[3], w);
+ }
+ else {
+ BarycentricWeights2f(uv, tf_uv_pxoffset[0], tf_uv_pxoffset[1], tf_uv_pxoffset[2], w);
+ }
+
+ }
+
+ /* a pitty we need to get the worldspace pixel location here */
+ if(ps->rv3d->rflag & RV3D_CLIPPING) {
+ if (side) VecWeightf(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v3].co, ps->dm_mvert[mf->v4].co, w);
+ else VecWeightf(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v2].co, ps->dm_mvert[mf->v3].co, w);
+
+ Mat4MulVecfl(ps->ob->obmat, wco);
+ if(view3d_test_clipping(ps->rv3d, wco)) {
+ continue; /* Watch out that no code below this needs to run */
+ }
+ }
+
+ mask = project_paint_uvpixel_mask(ps, face_index, side, w);
+
+ if (mask > 0.0f) {
+ BLI_linklist_prepend_arena(
+ bucketPixelNodes,
+ project_paint_uvpixel_init(ps, arena, ibuf, x, y, mask, face_index, image_index, pixelScreenCo, side, w),
+ arena
+ );
+ }
+
+ }
+ }
+ else if (has_x_isect) {
+ /* assuming the face is not a bow-tie - we know we cant intersect again on the X */
+ break;
+ }
+ }
+
+#if 0 /* TODO - investigate why this dosnt work sometimes! it should! */
+ /* no intersection for this entire row, after some intersection above means we can quit now */
+ if (has_x_isect==0 && has_isect) {
+ break;
+ }
+#endif
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+#endif // PROJ_DEBUG_NOSEAMBLEED
+}
+
+
+/* takes floating point screenspace min/max and returns int min/max to be used as indicies for ps->bucketRect, ps->bucketFlags */
+static void project_paint_bucket_bounds(const ProjPaintState *ps, const float min[2], const float max[2], int bucketMin[2], int bucketMax[2])
+{
+ /* divide by bucketWidth & bucketHeight so the bounds are offset in bucket grid units */
+ bucketMin[0] = (int)(((float)(min[0] - ps->screenMin[0]) / ps->screen_width) * ps->buckets_x) + 0.5f; /* these offsets of 0.5 and 1.5 seem odd but they are correct */
+ bucketMin[1] = (int)(((float)(min[1] - ps->screenMin[1]) / ps->screen_height) * ps->buckets_y) + 0.5f;
+
+ bucketMax[0] = (int)(((float)(max[0] - ps->screenMin[0]) / ps->screen_width) * ps->buckets_x) + 1.5f;
+ bucketMax[1] = (int)(((float)(max[1] - ps->screenMin[1]) / ps->screen_height) * ps->buckets_y) + 1.5f;
+
+ /* incase the rect is outside the mesh 2d bounds */
+ CLAMP(bucketMin[0], 0, ps->buckets_x);
+ CLAMP(bucketMin[1], 0, ps->buckets_y);
+
+ CLAMP(bucketMax[0], 0, ps->buckets_x);
+ CLAMP(bucketMax[1], 0, ps->buckets_y);
+}
+
+/* set bucket_bounds to a screen space-aligned floating point bound-box */
+static void project_bucket_bounds(const ProjPaintState *ps, const int bucket_x, const int bucket_y, rctf *bucket_bounds)
+{
+ bucket_bounds->xmin = ps->screenMin[0]+((bucket_x)*(ps->screen_width / ps->buckets_x)); /* left */
+ bucket_bounds->xmax = ps->screenMin[0]+((bucket_x+1)*(ps->screen_width / ps->buckets_x)); /* right */
+
+ bucket_bounds->ymin = ps->screenMin[1]+((bucket_y)*(ps->screen_height / ps->buckets_y)); /* bottom */
+ bucket_bounds->ymax = ps->screenMin[1]+((bucket_y+1)*(ps->screen_height / ps->buckets_y)); /* top */
+}
+
+/* Fill this bucket with pixels from the faces that intersect it.
+ *
+ * have bucket_bounds as an argument so we don;t need to give bucket_x/y the rect function needs */
+static void project_bucket_init(const ProjPaintState *ps, const int thread_index, const int bucket_index, rctf *bucket_bounds)
+{
+ LinkNode *node;
+ int face_index, image_index;
+ ImBuf *ibuf = NULL;
+ MTFace *tf;
+
+ Image *tpage_last = NULL;
+
+
+ if (ps->image_tot==1) {
+ /* Simple loop, no context switching */
+ ibuf = ps->projImages[0].ibuf;
+
+ for (node = ps->bucketFaces[bucket_index]; node; node= node->next) {
+ project_paint_face_init(ps, thread_index, bucket_index, GET_INT_FROM_POINTER(node->link), 0, bucket_bounds, ibuf);
+ }
+ }
+ else {
+
+ /* More complicated loop, switch between images */
+ for (node = ps->bucketFaces[bucket_index]; node; node= node->next) {
+ face_index = GET_INT_FROM_POINTER(node->link);
+
+ /* Image context switching */
+ tf = ps->dm_mtface+face_index;
+ if (tpage_last != tf->tpage) {
+ tpage_last = tf->tpage;
+
+ image_index = -1; /* sanity check */
+
+ for (image_index=0; image_index < ps->image_tot; image_index++) {
+ if (ps->projImages[image_index].ima == tpage_last) {
+ ibuf = ps->projImages[image_index].ibuf;
+ break;
+ }
+ }
+ }
+ /* context switching done */
+
+ project_paint_face_init(ps, thread_index, bucket_index, face_index, image_index, bucket_bounds, ibuf);
+
+ }
+ }
+
+ ps->bucketFlags[bucket_index] |= PROJ_BUCKET_INIT;
+}
+
+
+/* We want to know if a bucket and a face overlap in screen-space
+ *
+ * Note, if this ever returns false positives its not that bad, since a face in the bounding area will have its pixels
+ * calculated when it might not be needed later, (at the moment at least)
+ * obviously it shouldn't have bugs though */
+
+static int project_bucket_face_isect(ProjPaintState *ps, float min[2], float max[2], int bucket_x, int bucket_y, int bucket_index, const MFace *mf)
+{
+ /* TODO - replace this with a tricker method that uses sideofline for all screenCoords's edges against the closest bucket corner */
+ rctf bucket_bounds;
+ float p1[2], p2[2], p3[2], p4[2];
+ float *v, *v1,*v2,*v3,*v4;
+ int fidx;
+
+ project_bucket_bounds(ps, bucket_x, bucket_y, &bucket_bounds);
+
+ /* Is one of the faces verts in the bucket bounds? */
+
+ fidx = mf->v4 ? 3:2;
+ do {
+ v = ps->screenCoords[ (*(&mf->v1 + fidx)) ];
+ if (BLI_in_rctf(&bucket_bounds, v[0], v[1])) {
+ return 1;
+ }
+ } while (fidx--);
+
+ v1 = ps->screenCoords[mf->v1];
+ v2 = ps->screenCoords[mf->v2];
+ v3 = ps->screenCoords[mf->v3];
+ if (mf->v4) {
+ v4 = ps->screenCoords[mf->v4];
+ }
+
+ p1[0] = bucket_bounds.xmin; p1[1] = bucket_bounds.ymin;
+ p2[0] = bucket_bounds.xmin; p2[1] = bucket_bounds.ymax;
+ p3[0] = bucket_bounds.xmax; p3[1] = bucket_bounds.ymax;
+ p4[0] = bucket_bounds.xmax; p4[1] = bucket_bounds.ymin;
+
+ if (mf->v4) {
+ if( IsectPQ2Df(p1, v1, v2, v3, v4) || IsectPQ2Df(p2, v1, v2, v3, v4) || IsectPQ2Df(p3, v1, v2, v3, v4) || IsectPQ2Df(p4, v1, v2, v3, v4) ||
+ /* we can avoid testing v3,v1 because another intersection MUST exist if this intersects */
+ (IsectLL2Df(p1, p2, v1, v2) || IsectLL2Df(p1, p2, v2, v3) || IsectLL2Df(p1, p2, v3, v4)) ||
+ (IsectLL2Df(p2, p3, v1, v2) || IsectLL2Df(p2, p3, v2, v3) || IsectLL2Df(p2, p3, v3, v4)) ||
+ (IsectLL2Df(p3, p4, v1, v2) || IsectLL2Df(p3, p4, v2, v3) || IsectLL2Df(p3, p4, v3, v4)) ||
+ (IsectLL2Df(p4, p1, v1, v2) || IsectLL2Df(p4, p1, v2, v3) || IsectLL2Df(p4, p1, v3, v4))
+ ) {
+ return 1;
+ }
+ }
+ else {
+ if( IsectPT2Df(p1, v1, v2, v3) || IsectPT2Df(p2, v1, v2, v3) || IsectPT2Df(p3, v1, v2, v3) || IsectPT2Df(p4, v1, v2, v3) ||
+ /* we can avoid testing v3,v1 because another intersection MUST exist if this intersects */
+ (IsectLL2Df(p1, p2, v1, v2) || IsectLL2Df(p1, p2, v2, v3)) ||
+ (IsectLL2Df(p2, p3, v1, v2) || IsectLL2Df(p2, p3, v2, v3)) ||
+ (IsectLL2Df(p3, p4, v1, v2) || IsectLL2Df(p3, p4, v2, v3)) ||
+ (IsectLL2Df(p4, p1, v1, v2) || IsectLL2Df(p4, p1, v2, v3))
+ ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Add faces to the bucket but dont initialize its pixels
+ * TODO - when painting occluded, sort the faces on their min-Z and only add faces that faces that are not occluded */
+static void project_paint_delayed_face_init(ProjPaintState *ps, const MFace *mf, const MTFace *tf, const int face_index)
+{
+ float min[2], max[2], *vCoSS;
+ int bucketMin[2], bucketMax[2]; /* for ps->bucketRect indexing */
+ int fidx, bucket_x, bucket_y, bucket_index;
+ int has_x_isect = -1, has_isect = 0; /* for early loop exit */
+ MemArena *arena = ps->arena_mt[0]; /* just use the first thread arena since threading has not started yet */
+
+ INIT_MINMAX2(min, max);
+
+ fidx = mf->v4 ? 3:2;
+ do {
+ vCoSS = ps->screenCoords[ *(&mf->v1 + fidx) ];
+ DO_MINMAX2(vCoSS, min, max);
+ } while (fidx--);
+
+ project_paint_bucket_bounds(ps, min, max, bucketMin, bucketMax);
+
+ for (bucket_y = bucketMin[1]; bucket_y < bucketMax[1]; bucket_y++) {
+ has_x_isect = 0;
+ for (bucket_x = bucketMin[0]; bucket_x < bucketMax[0]; bucket_x++) {
+
+ bucket_index = bucket_x + (bucket_y * ps->buckets_x);
+
+ if (project_bucket_face_isect(ps, min, max, bucket_x, bucket_y, bucket_index, mf)) {
+ BLI_linklist_prepend_arena(
+ &ps->bucketFaces[ bucket_index ],
+ SET_INT_IN_POINTER(face_index), /* cast to a pointer to shut up the compiler */
+ arena
+ );
+
+ has_x_isect = has_isect = 1;
+ }
+ else if (has_x_isect) {
+ /* assuming the face is not a bow-tie - we know we cant intersect again on the X */
+ break;
+ }
+ }
+
+ /* no intersection for this entire row, after some intersection above means we can quit now */
+ if (has_x_isect==0 && has_isect) {
+ break;
+ }
+ }
+
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ if (ps->seam_bleed_px > 0.0f) {
+ if (!mf->v4) {
+ ps->faceSeamFlags[face_index] |= PROJ_FACE_NOSEAM4; /* so this wont show up as an untagged edge */
+ }
+ **ps->faceSeamUVs[face_index] = FLT_MAX; /* set as uninitialized */
+ }
+#endif
+}
+
+/* run once per stroke before projection painting */
+static void project_paint_begin(ProjPaintState *ps)
+{
+ /* Viewport vars */
+ float mat[3][3];
+
+ float no[3];
+
+ float (*projScreenCo)[4]; /* Note, we could have 4D vectors are only needed for */
+ float projMargin;
+ /* Image Vars - keep track of images we have used */
+ LinkNode *image_LinkList = NULL;
+ LinkNode *node;
+
+ ProjPaintImage *projIma;
+ Image *tpage_last = NULL;
+
+ /* Face vars */
+ MFace *mf;
+ MTFace *tf;
+
+ int a, i; /* generic looping vars */
+ int image_index = -1, face_index;
+
+ MemArena *arena; /* at the moment this is just ps->arena_mt[0], but use this to show were not multithreading */
+
+ /* ---- end defines ---- */
+
+ /* paint onto the derived mesh */
+ ps->dm = mesh_get_derived_final(ps->scene, ps->ob, ps->v3d->customdata_mask);
+
+ if ( !CustomData_has_layer( &ps->dm->faceData, CD_MTFACE) ) {
+ ps->dm = NULL;
+ return;
+ }
+ ps->dm_mvert = ps->dm->getVertArray(ps->dm);
+ ps->dm_mface = ps->dm->getFaceArray(ps->dm);
+ ps->dm_mtface= ps->dm->getFaceDataArray(ps->dm, CD_MTFACE);
+
+ ps->dm_totvert = ps->dm->getNumVerts(ps->dm);
+ ps->dm_totface = ps->dm->getNumFaces(ps->dm);
+
+ /* use clone mtface? */
+
+
+ /* Note, use the original mesh for getting the clone and mask layer index
+ * this avoids re-generating the derived mesh just to get the new index */
+ if (ps->do_layer_clone) {
+ //int layer_num = CustomData_get_clone_layer(&ps->dm->faceData, CD_MTFACE);
+ int layer_num = CustomData_get_clone_layer(&((Mesh *)ps->ob->data)->fdata, CD_MTFACE);
+ if (layer_num != -1)
+ ps->dm_mtface_clone = CustomData_get_layer_n(&ps->dm->faceData, CD_MTFACE, layer_num);
+
+ if (ps->dm_mtface_clone==NULL || ps->dm_mtface_clone==ps->dm_mtface) {
+ ps->do_layer_clone = 0;
+ ps->dm_mtface_clone= NULL;
+ }
+ }
+
+ if (ps->do_layer_mask) {
+ //int layer_num = CustomData_get_mask_layer(&ps->dm->faceData, CD_MTFACE);
+ int layer_num = CustomData_get_mask_layer(&((Mesh *)ps->ob->data)->fdata, CD_MTFACE);
+ if (layer_num != -1)
+ ps->dm_mtface_mask = CustomData_get_layer_n(&ps->dm->faceData, CD_MTFACE, layer_num);
+
+ if (ps->dm_mtface_mask==NULL || ps->dm_mtface_mask==ps->dm_mtface) {
+ ps->do_layer_mask = 0;
+ ps->dm_mtface_mask = NULL;
+ }
+ }
+
+
+
+ ps->viewDir[0] = 0.0f;
+ ps->viewDir[1] = 0.0f;
+ ps->viewDir[2] = 1.0f;
+
+ view3d_get_object_project_mat(ps->rv3d, ps->ob, ps->projectMat, ps->viewMat);
+
+ /* viewDir - object relative */
+ Mat4Invert(ps->ob->imat, ps->ob->obmat);
+ Mat3CpyMat4(mat, ps->rv3d->viewinv);
+ Mat3MulVecfl(mat, ps->viewDir);
+ Mat3CpyMat4(mat, ps->ob->imat);
+ Mat3MulVecfl(mat, ps->viewDir);
+ Normalize(ps->viewDir);
+
+ /* viewPos - object relative */
+ VECCOPY(ps->viewPos, ps->rv3d->viewinv[3]);
+ Mat3CpyMat4(mat, ps->ob->imat);
+ Mat3MulVecfl(mat, ps->viewPos);
+ VecAddf(ps->viewPos, ps->viewPos, ps->ob->imat[3]);
+
+ { /* only use these for running 'get_view3d_viewplane' */
+ rctf viewplane;
+
+ ps->is_ortho = get_view3d_viewplane(ps->v3d, ps->rv3d, ps->ar->winx, ps->ar->winy, &viewplane, &ps->clipsta, &ps->clipend, NULL);
+
+ //printf("%f %f\n", ps->clipsta, ps->clipend);
+ if (ps->is_ortho) { /* only needed for ortho */
+ float fac = 2.0f / (ps->clipend - ps->clipsta);
+ ps->clipsta *= fac;
+ ps->clipend *= fac;
+ }
+ else {
+ /* TODO - can we even adjust for clip start/end? */
+ }
+
+ }
+
+ ps->is_airbrush = (ps->brush->flag & BRUSH_AIRBRUSH) ? 1 : 0;
+
+ ps->is_texbrush = (ps->brush->mtex[ps->brush->texact] && ps->brush->mtex[ps->brush->texact]->tex) ? 1 : 0;
+
+
+ /* calculate vert screen coords
+ * run this early so we can calculate the x/y resolution of our bucket rect */
+ INIT_MINMAX2(ps->screenMin, ps->screenMax);
+
+ ps->screenCoords = MEM_mallocN(sizeof(float) * ps->dm_totvert * 4, "ProjectPaint ScreenVerts");
+ projScreenCo = ps->screenCoords;
+
+ if (ps->is_ortho) {
+ for(a=0; a < ps->dm_totvert; a++, projScreenCo++) {
+ VECCOPY((*projScreenCo), ps->dm_mvert[a].co);
+ Mat4MulVecfl(ps->projectMat, (*projScreenCo));
+
+ /* screen space, not clamped */
+ (*projScreenCo)[0] = (float)(ps->ar->winx/2.0f)+(ps->ar->winx/2.0f)*(*projScreenCo)[0];
+ (*projScreenCo)[1] = (float)(ps->ar->winy/2.0f)+(ps->ar->winy/2.0f)*(*projScreenCo)[1];
+ DO_MINMAX2((*projScreenCo), ps->screenMin, ps->screenMax);
+ }
+ }
+ else {
+ for(a=0; a < ps->dm_totvert; a++, projScreenCo++) {
+ VECCOPY((*projScreenCo), ps->dm_mvert[a].co);
+ (*projScreenCo)[3] = 1.0f;
+
+ Mat4MulVec4fl(ps->projectMat, (*projScreenCo));
+
+
+ if ((*projScreenCo)[3] > ps->clipsta) {
+ /* screen space, not clamped */
+ (*projScreenCo)[0] = (float)(ps->ar->winx/2.0f)+(ps->ar->winx/2.0f)*(*projScreenCo)[0]/(*projScreenCo)[3];
+ (*projScreenCo)[1] = (float)(ps->ar->winy/2.0f)+(ps->ar->winy/2.0f)*(*projScreenCo)[1]/(*projScreenCo)[3];
+ (*projScreenCo)[2] = (*projScreenCo)[2]/(*projScreenCo)[3]; /* Use the depth for bucket point occlusion */
+ DO_MINMAX2((*projScreenCo), ps->screenMin, ps->screenMax);
+ }
+ else {
+ /* TODO - deal with cases where 1 side of a face goes behind the view ?
+ *
+ * After some research this is actually very tricky, only option is to
+ * clip the derived mesh before painting, which is a Pain */
+ (*projScreenCo)[0] = FLT_MAX;
+ }
+ }
+ }
+
+ /* If this border is not added we get artifacts for faces that
+ * have a parallel edge and at the bounds of the the 2D projected verts eg
+ * - a single screen aligned quad */
+ projMargin = (ps->screenMax[0] - ps->screenMin[0]) * 0.000001f;
+ ps->screenMax[0] += projMargin;
+ ps->screenMin[0] -= projMargin;
+ projMargin = (ps->screenMax[1] - ps->screenMin[1]) * 0.000001f;
+ ps->screenMax[1] += projMargin;
+ ps->screenMin[1] -= projMargin;
+
+#ifdef PROJ_DEBUG_WINCLIP
+ CLAMP(ps->screenMin[0], -ps->brush->size, ps->ar->winx + ps->brush->size);
+ CLAMP(ps->screenMax[0], -ps->brush->size, ps->ar->winx + ps->brush->size);
+
+ CLAMP(ps->screenMin[1], -ps->brush->size, ps->ar->winy + ps->brush->size);
+ CLAMP(ps->screenMax[1], -ps->brush->size, ps->ar->winy + ps->brush->size);
+#endif
+
+ /* only for convenience */
+ ps->screen_width = ps->screenMax[0] - ps->screenMin[0];
+ ps->screen_height = ps->screenMax[1] - ps->screenMin[1];
+
+ ps->buckets_x = (int)(ps->screen_width / (((float)ps->brush->size) / PROJ_BUCKET_BRUSH_DIV));
+ ps->buckets_y = (int)(ps->screen_height / (((float)ps->brush->size) / PROJ_BUCKET_BRUSH_DIV));
+
+ printf("\tscreenspace bucket division x:%d y:%d\n", ps->buckets_x, ps->buckets_y);
+
+ /* really high values could cause problems since it has to allocate a few
+ * (ps->buckets_x*ps->buckets_y) sized arrays */
+ CLAMP(ps->buckets_x, PROJ_BUCKET_RECT_MIN, PROJ_BUCKET_RECT_MAX);
+ CLAMP(ps->buckets_y, PROJ_BUCKET_RECT_MIN, PROJ_BUCKET_RECT_MAX);
+
+ ps->bucketRect = (LinkNode **)MEM_callocN(sizeof(LinkNode *) * ps->buckets_x * ps->buckets_y, "paint-bucketRect");
+ ps->bucketFaces= (LinkNode **)MEM_callocN(sizeof(LinkNode *) * ps->buckets_x * ps->buckets_y, "paint-bucketFaces");
+
+ ps->bucketFlags= (unsigned char *)MEM_callocN(sizeof(char) * ps->buckets_x * ps->buckets_y, "paint-bucketFaces");
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ if (ps->seam_bleed_px > 0.0f) {
+ ps->vertFaces= (LinkNode **)MEM_callocN(sizeof(LinkNode *) * ps->dm_totvert, "paint-vertFaces");
+ ps->faceSeamFlags = (char *)MEM_callocN(sizeof(char) * ps->dm_totface, "paint-faceSeamFlags");
+ ps->faceSeamUVs= MEM_mallocN(sizeof(float) * ps->dm_totface * 8, "paint-faceSeamUVs");
+ }
+#endif
+
+ /* Thread stuff
+ *
+ * very small brushes run a lot slower multithreaded since the advantage with
+ * threads is being able to fill in multiple buckets at once.
+ * Only use threads for bigger brushes. */
+
+ if (ps->scene->r.mode & R_FIXED_THREADS) {
+ ps->thread_tot = ps->scene->r.threads;
+ }
+ else {
+ ps->thread_tot = BLI_system_thread_count();
+ }
+ for (a=0; a<ps->thread_tot; a++) {
+ ps->arena_mt[a] = BLI_memarena_new(1<<16);
+ }
+
+ arena = ps->arena_mt[0];
+
+ if (ps->do_backfacecull && ps->do_mask_normal) {
+ MVert *v = ps->dm_mvert;
+ float viewDirPersp[3];
+
+ ps->vertFlags = MEM_callocN(sizeof(char) * ps->dm_totvert, "paint-vertFlags");
+
+ for(a=0; a < ps->dm_totvert; a++, v++) {
+ no[0] = (float)(v->no[0] / 32767.0f);
+ no[1] = (float)(v->no[1] / 32767.0f);
+ no[2] = (float)(v->no[2] / 32767.0f);
+
+ if (ps->is_ortho) {
+ if (NormalizedVecAngle2(ps->viewDir, no) >= ps->normal_angle) { /* 1 vert of this face is towards us */
+ ps->vertFlags[a] |= PROJ_VERT_CULL;
+ }
+ }
+ else {
+ VecSubf(viewDirPersp, ps->viewPos, v->co);
+ Normalize(viewDirPersp);
+ if (NormalizedVecAngle2(viewDirPersp, no) >= ps->normal_angle) { /* 1 vert of this face is towards us */
+ ps->vertFlags[a] |= PROJ_VERT_CULL;
+ }
+ }
+ }
+ }
+
+
+ for(face_index = 0, tf = ps->dm_mtface, mf = ps->dm_mface; face_index < ps->dm_totface; mf++, tf++, face_index++) {
+
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ /* add face user if we have bleed enabled, set the UV seam flags later */
+ /* annoying but we need to add all faces even ones we never use elsewhere */
+ if (ps->seam_bleed_px > 0.0f) {
+ BLI_linklist_prepend_arena(&ps->vertFaces[mf->v1], SET_INT_IN_POINTER(face_index), arena);
+ BLI_linklist_prepend_arena(&ps->vertFaces[mf->v2], SET_INT_IN_POINTER(face_index), arena);
+ BLI_linklist_prepend_arena(&ps->vertFaces[mf->v3], SET_INT_IN_POINTER(face_index), arena);
+ if (mf->v4) {
+ BLI_linklist_prepend_arena(&ps->vertFaces[ mf->v4 ], SET_INT_IN_POINTER(face_index), arena);
+ }
+ }
+#endif
+
+ if (tf->tpage && ((G.f & G_FACESELECT)==0 || mf->flag & ME_FACE_SEL)) {
+
+ float *v1coSS, *v2coSS, *v3coSS, *v4coSS;
+
+ v1coSS = ps->screenCoords[mf->v1];
+ v2coSS = ps->screenCoords[mf->v2];
+ v3coSS = ps->screenCoords[mf->v3];
+ if (mf->v4) {
+ v4coSS = ps->screenCoords[mf->v4];
+ }
+
+
+ if (!ps->is_ortho) {
+ if ( v1coSS[0]==FLT_MAX ||
+ v2coSS[0]==FLT_MAX ||
+ v3coSS[0]==FLT_MAX ||
+ (mf->v4 && v4coSS[0]==FLT_MAX)
+ ) {
+ continue;
+ }
+ }
+
+#ifdef PROJ_DEBUG_WINCLIP
+ /* ignore faces outside the view */
+ if (
+ (v1coSS[0] < ps->screenMin[0] &&
+ v2coSS[0] < ps->screenMin[0] &&
+ v3coSS[0] < ps->screenMin[0] &&
+ (mf->v4 && v4coSS[0] < ps->screenMin[0])) ||
+
+ (v1coSS[0] > ps->screenMax[0] &&
+ v2coSS[0] > ps->screenMax[0] &&
+ v3coSS[0] > ps->screenMax[0] &&
+ (mf->v4 && v4coSS[0] > ps->screenMax[0])) ||
+
+ (v1coSS[1] < ps->screenMin[1] &&
+ v2coSS[1] < ps->screenMin[1] &&
+ v3coSS[1] < ps->screenMin[1] &&
+ (mf->v4 && v4coSS[1] < ps->screenMin[1])) ||
+
+ (v1coSS[1] > ps->screenMax[1] &&
+ v2coSS[1] > ps->screenMax[1] &&
+ v3coSS[1] > ps->screenMax[1] &&
+ (mf->v4 && v4coSS[1] > ps->screenMax[1]))
+ ) {
+ continue;
+ }
+
+#endif //PROJ_DEBUG_WINCLIP
+
+
+ if (ps->do_backfacecull) {
+ if (ps->do_mask_normal) {
+ /* Since we are interpolating the normals of faces, we want to make
+ * sure all the verts are pointing away from the view,
+ * not just the face */
+ if ( (ps->vertFlags[mf->v1] & PROJ_VERT_CULL) &&
+ (ps->vertFlags[mf->v2] & PROJ_VERT_CULL) &&
+ (ps->vertFlags[mf->v3] & PROJ_VERT_CULL) &&
+ (mf->v4==0 || ps->vertFlags[mf->v4] & PROJ_VERT_CULL)
+
+ ) {
+ continue;
+ }
+ }
+ else {
+ if (SIDE_OF_LINE(v1coSS, v2coSS, v3coSS) < 0.0f) {
+ continue;
+ }
+
+ }
+ }
+
+ if (tpage_last != tf->tpage) {
+
+ image_index = BLI_linklist_index(image_LinkList, tf->tpage);
+
+ if (image_index==-1 && BKE_image_get_ibuf((Image *)tf->tpage, NULL)) { /* MemArena dosnt have an append func */
+ BLI_linklist_append(&image_LinkList, tf->tpage);
+ image_index = ps->image_tot;
+ ps->image_tot++;
+ }
+
+ tpage_last = tf->tpage;
+ }
+
+ if (image_index != -1) {
+ /* Initialize the faces screen pixels */
+ /* Add this to a list to initialize later */
+ project_paint_delayed_face_init(ps, mf, tf, face_index);
+ }
+ }
+ }
+
+ /* build an array of images we use*/
+ projIma = ps->projImages = (ProjPaintImage *)BLI_memarena_alloc(arena, sizeof(ProjPaintImage) * ps->image_tot);
+
+ for (node= image_LinkList, i=0; node; node= node->next, i++, projIma++) {
+ projIma->ima = node->link;
+ // calloced - projIma->touch = 0;
+ projIma->ibuf = BKE_image_get_ibuf(projIma->ima, NULL);
+ projIma->partRedrawRect = BLI_memarena_alloc(arena, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED);
+ // calloced - memset(projIma->partRedrawRect, 0, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED);
+ }
+
+ /* we have built the array, discard the linked list */
+ BLI_linklist_free(image_LinkList, NULL);
+}
+
+static void project_paint_begin_clone(ProjPaintState *ps, int mouse[2])
+{
+ /* setup clone offset */
+ if (ps->tool == PAINT_TOOL_CLONE) {
+ float projCo[4];
+ float *curs= give_cursor(ps->scene, ps->v3d);
+ VECCOPY(projCo, curs);
+ Mat4MulVecfl(ps->ob->imat, projCo);
+
+ projCo[3] = 1.0f;
+ Mat4MulVec4fl(ps->projectMat, projCo);
+ ps->cloneOffset[0] = mouse[0] - ((float)(ps->ar->winx/2.0f)+(ps->ar->winx/2.0f)*projCo[0]/projCo[3]);
+ ps->cloneOffset[1] = mouse[1] - ((float)(ps->ar->winy/2.0f)+(ps->ar->winy/2.0f)*projCo[1]/projCo[3]);
+ }
+}
+
+static void project_paint_end(ProjPaintState *ps)
+{
+ int a;
+
+ /* build undo data from original pixel colors */
+ if(U.uiflag & USER_GLOBALUNDO) {
+ ProjPixel *projPixel;
+ ImBuf *tmpibuf = NULL, *tmpibuf_float = NULL;
+ LinkNode *pixel_node;
+ UndoTile *tile;
+ MemArena *arena = ps->arena_mt[0]; /* threaded arena re-used for non threaded case */
+
+ int bucket_tot = (ps->buckets_x * ps->buckets_y); /* we could get an X/Y but easier to loop through all possible buckets */
+ int bucket_index;
+ int tile_index;
+ int x_round, y_round;
+ int x_tile, y_tile;
+ int is_float = -1;
+
+ /* context */
+ ProjPaintImage *last_projIma;
+ int last_image_index = -1;
+ int last_tile_width;
+
+ for(a=0, last_projIma=ps->projImages; a < ps->image_tot; a++, last_projIma++) {
+ int size = sizeof(UndoTile **) * IMAPAINT_TILE_NUMBER(last_projIma->ibuf->x) * IMAPAINT_TILE_NUMBER(last_projIma->ibuf->y);
+ last_projIma->undoRect = (UndoTile **) BLI_memarena_alloc(arena, size);
+ memset(last_projIma->undoRect, 0, size);
+ }
+
+ for (bucket_index = 0; bucket_index < bucket_tot; bucket_index++) {
+ /* loop through all pixels */
+ for(pixel_node= ps->bucketRect[bucket_index]; pixel_node; pixel_node= pixel_node->next) {
+
+ /* ok we have a pixel, was it modified? */
+ projPixel = (ProjPixel *)pixel_node->link;
+
+ if (last_image_index != projPixel->image_index) {
+ /* set the context */
+ last_image_index = projPixel->image_index;
+ last_projIma = ps->projImages + last_image_index;
+ last_tile_width = IMAPAINT_TILE_NUMBER(last_projIma->ibuf->x);
+ is_float = last_projIma->ibuf->rect_float ? 1 : 0;
+ }
+
+
+ if ( (is_float == 0 && projPixel->origColor.uint != *projPixel->pixel.uint_pt) ||
+
+ (is_float == 1 &&
+ ( projPixel->origColor.f[0] != projPixel->pixel.f_pt[0] ||
+ projPixel->origColor.f[1] != projPixel->pixel.f_pt[1] ||
+ projPixel->origColor.f[2] != projPixel->pixel.f_pt[2] ||
+ projPixel->origColor.f[3] != projPixel->pixel.f_pt[3] ))
+ ) {
+
+ x_tile = projPixel->x_px >> IMAPAINT_TILE_BITS;
+ y_tile = projPixel->y_px >> IMAPAINT_TILE_BITS;
+
+ x_round = x_tile * IMAPAINT_TILE_SIZE;
+ y_round = y_tile * IMAPAINT_TILE_SIZE;
+
+ tile_index = x_tile + y_tile * last_tile_width;
+
+ if (last_projIma->undoRect[tile_index]==NULL) {
+ /* add the undo tile from the modified image, then write the original colors back into it */
+ tile = last_projIma->undoRect[tile_index] = undo_init_tile(&last_projIma->ima->id, last_projIma->ibuf, is_float ? (&tmpibuf_float):(&tmpibuf) , x_tile, y_tile);
+ }
+ else {
+ tile = last_projIma->undoRect[tile_index];
+ }
+
+ /* This is a BIT ODD, but overwrite the undo tiles image info with this pixels original color
+ * because allocating the tiles allong the way slows down painting */
+
+ if (is_float) {
+ float *rgba_fp = (float *)tile->rect + (((projPixel->x_px - x_round) + (projPixel->y_px - y_round) * IMAPAINT_TILE_SIZE)) * 4;
+ QUATCOPY(rgba_fp, projPixel->origColor.f);
+ }
+ else {
+ ((unsigned int *)tile->rect)[ (projPixel->x_px - x_round) + (projPixel->y_px - y_round) * IMAPAINT_TILE_SIZE ] = projPixel->origColor.uint;
+ }
+ }
+ }
+ }
+
+ if (tmpibuf) IMB_freeImBuf(tmpibuf);
+ if (tmpibuf_float) IMB_freeImBuf(tmpibuf_float);
+ }
+ /* done calculating undo data */
+
+ MEM_freeN(ps->screenCoords);
+ MEM_freeN(ps->bucketRect);
+ MEM_freeN(ps->bucketFaces);
+ MEM_freeN(ps->bucketFlags);
+
+ if (ps->seam_bleed_px > 0.0f) {
+ MEM_freeN(ps->vertFaces);
+ MEM_freeN(ps->faceSeamFlags);
+ MEM_freeN(ps->faceSeamUVs);
+ }
+
+ if (ps->vertFlags) MEM_freeN(ps->vertFlags);
+
+
+ for (a=0; a<ps->thread_tot; a++) {
+ BLI_memarena_free(ps->arena_mt[a]);
+ }
+
+ ps->dm->release(ps->dm);
+}
+
+/* 1= an undo, -1 is a redo. */
+static void partial_redraw_array_init(ImagePaintPartialRedraw *pr)
+{
+ int tot = PROJ_BOUNDBOX_SQUARED;
+ while (tot--) {
+ pr->x1 = 10000000;
+ pr->y1 = 10000000;
+
+ pr->x2 = -1;
+ pr->y2 = -1;
+
+ pr->enabled = 1;
+
+ pr++;
+ }
+}
+
+
+static int partial_redraw_array_merge(ImagePaintPartialRedraw *pr, ImagePaintPartialRedraw *pr_other, int tot)
+{
+ int touch;
+ while (tot--) {
+ pr->x1 = MIN2(pr->x1, pr_other->x1);
+ pr->y1 = MIN2(pr->y1, pr_other->y1);
+
+ pr->x2 = MAX2(pr->x2, pr_other->x2);
+ pr->y2 = MAX2(pr->y2, pr_other->y2);
+
+ if (pr->x2 != -1)
+ touch = 1;
+
+ pr++; pr_other++;
+ }
+
+ return touch;
+}
+
+/* Loop over all images on this mesh and update any we have touched */
+static int project_image_refresh_tagged(ProjPaintState *ps)
+{
+ ImagePaintPartialRedraw *pr;
+ ProjPaintImage *projIma;
+ int a,i;
+ int redraw = 0;
+
+
+ for (a=0, projIma=ps->projImages; a < ps->image_tot; a++, projIma++) {
+ if (projIma->touch) {
+ /* look over each bound cell */
+ for (i=0; i<PROJ_BOUNDBOX_SQUARED; i++) {
+ pr = &(projIma->partRedrawRect[i]);
+ if (pr->x2 != -1) { /* TODO - use 'enabled' ? */
+ imapaintpartial = *pr;
+ imapaint_image_update(NULL, projIma->ima, projIma->ibuf, 1); /*last 1 is for texpaint*/
+ redraw = 1;
+ }
+ }
+
+ projIma->touch = 0; /* clear for reuse */
+ }
+ }
+
+ return redraw;
+}
+
+/* run this per painting onto each mouse location */
+static int project_bucket_iter_init(ProjPaintState *ps, const float mval_f[2])
+{
+ float min_brush[2], max_brush[2];
+ float size_half = ((float)ps->brush->size) * 0.5f;
+
+ /* so we dont have a bucket bounds that is way too small to paint into */
+ // if (size_half < 1.0f) size_half = 1.0f; // this dosnt work yet :/
+
+ min_brush[0] = mval_f[0] - size_half;
+ min_brush[1] = mval_f[1] - size_half;
+
+ max_brush[0] = mval_f[0] + size_half;
+ max_brush[1] = mval_f[1] + size_half;
+
+ /* offset to make this a valid bucket index */
+ project_paint_bucket_bounds(ps, min_brush, max_brush, ps->bucketMin, ps->bucketMax);
+
+ /* mouse outside the model areas? */
+ if (ps->bucketMin[0]==ps->bucketMax[0] || ps->bucketMin[1]==ps->bucketMax[1]) {
+ return 0;
+ }
+
+ ps->context_bucket_x = ps->bucketMin[0];
+ ps->context_bucket_y = ps->bucketMin[1];
+ return 1;
+}
+
+static int project_bucket_iter_next(ProjPaintState *ps, int *bucket_index, rctf *bucket_bounds, const float mval[2])
+{
+ if (ps->thread_tot > 1)
+ BLI_lock_thread(LOCK_CUSTOM1);
+
+ //printf("%d %d \n", ps->context_bucket_x, ps->context_bucket_y);
+
+ for ( ; ps->context_bucket_y < ps->bucketMax[1]; ps->context_bucket_y++) {
+ for ( ; ps->context_bucket_x < ps->bucketMax[0]; ps->context_bucket_x++) {
+
+ /* use bucket_bounds for project_bucket_isect_circle and project_bucket_init*/
+ project_bucket_bounds(ps, ps->context_bucket_x, ps->context_bucket_y, bucket_bounds);
+
+ if (project_bucket_isect_circle(ps->context_bucket_x, ps->context_bucket_y, mval, ps->brush->size * ps->brush->size, bucket_bounds)) {
+ *bucket_index = ps->context_bucket_x + (ps->context_bucket_y * ps->buckets_x);
+ ps->context_bucket_x++;
+
+ if (ps->thread_tot > 1)
+ BLI_unlock_thread(LOCK_CUSTOM1);
+
+ return 1;
+ }
+ }
+ ps->context_bucket_x = ps->bucketMin[0];
+ }
+
+ if (ps->thread_tot > 1)
+ BLI_unlock_thread(LOCK_CUSTOM1);
+ return 0;
+}
+
+/* Each thread gets one of these, also used as an argument to pass to project_paint_op */
+typedef struct ProjectHandle {
+ /* args */
+ ProjPaintState *ps;
+ float prevmval[2];
+ float mval[2];
+
+ /* annoying but we need to have image bounds per thread, then merge into ps->projectPartialRedraws */
+ ProjPaintImage *projImages; /* array of partial redraws */
+
+ /* thread settings */
+ int thread_index;
+} ProjectHandle;
+
+static void blend_color_mix(unsigned char *cp, const unsigned char *cp1, const unsigned char *cp2, const int fac)
+{
+ /* this and other blending modes previously used >>8 instead of /255. both
+ are not equivalent (>>8 is /256), and the former results in rounding
+ errors that can turn colors black fast after repeated blending */
+ const int mfac= 255-fac;
+
+ cp[0]= (mfac*cp1[0]+fac*cp2[0])/255;
+ cp[1]= (mfac*cp1[1]+fac*cp2[1])/255;
+ cp[2]= (mfac*cp1[2]+fac*cp2[2])/255;
+}
+
+static void blend_color_mix_float(float *cp, const float *cp1, const float *cp2, const float fac)
+{
+ const float mfac= 1.0-fac;
+ cp[0]= mfac*cp1[0] + fac*cp2[0];
+ cp[1]= mfac*cp1[1] + fac*cp2[1];
+ cp[2]= mfac*cp1[2] + fac*cp2[2];
+}
+
+static void do_projectpaint_clone(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask)
+{
+ if (ps->is_airbrush==0 && mask < 1.0f) {
+ projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, ((ProjPixelClone*)projPixel)->clonepx.uint, (int)(alpha*255), ps->blend);
+ blend_color_mix(projPixel->pixel.ch_pt, projPixel->origColor.ch, projPixel->newColor.ch, (int)(mask*255));
+ }
+ else {
+ *projPixel->pixel.uint_pt = IMB_blend_color(*projPixel->pixel.uint_pt, ((ProjPixelClone*)projPixel)->clonepx.uint, (int)(alpha*mask*255), ps->blend);
+ }
+}
+
+static void do_projectpaint_clone_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask)
+{
+ if (ps->is_airbrush==0 && mask < 1.0f) {
+ IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, ((ProjPixelClone *)projPixel)->clonepx.f, alpha, ps->blend);
+ blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f, projPixel->newColor.f, mask);
+ }
+ else {
+ IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.f, alpha*mask, ps->blend);
+ }
+}
+
+/* do_projectpaint_smear*
+ *
+ * note, mask is used to modify the alpha here, this is not correct since it allows
+ * accumulation of color greater then 'projPixel->mask' however in the case of smear its not
+ * really that important to be correct as it is with clone and painting
+ */
+static void do_projectpaint_smear(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, MemArena *smearArena, LinkNode **smearPixels, float co[2])
+{
+ unsigned char rgba_ub[4];
+
+ if (project_paint_PickColor(ps, co, NULL, rgba_ub, 1)==0)
+ return;
+
+ ((ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*projPixel->pixel.uint_pt, *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend);
+ BLI_linklist_prepend_arena(smearPixels, (void *)projPixel, smearArena);
+}
+
+static void do_projectpaint_smear_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, MemArena *smearArena, LinkNode **smearPixels_f, float co[2])
+{
+ unsigned char rgba_ub[4];
+ unsigned char rgba_smear[4];
+
+ if (project_paint_PickColor(ps, co, NULL, rgba_ub, 1)==0)
+ return;
+
+ IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba_smear, projPixel->pixel.f_pt);
+ ((ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*((unsigned int *)rgba_smear), *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend);
+ BLI_linklist_prepend_arena(smearPixels_f, (void *)projPixel, smearArena);
+}
+
+static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask)
+{
+ unsigned char rgba_ub[4];
+
+ if (ps->is_texbrush) {
+ rgba_ub[0] = FTOCHAR(rgba[0] * ps->brush->rgb[0]);
+ rgba_ub[1] = FTOCHAR(rgba[1] * ps->brush->rgb[1]);
+ rgba_ub[2] = FTOCHAR(rgba[2] * ps->brush->rgb[2]);
+ rgba_ub[3] = FTOCHAR(rgba[3]);
+ }
+ else {
+ IMAPAINT_FLOAT_RGB_TO_CHAR(rgba_ub, ps->brush->rgb);
+ rgba_ub[3] = 255;
+ }
+
+ if (ps->is_airbrush==0 && mask < 1.0f) {
+ projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, *((unsigned int *)rgba_ub), (int)(alpha*255), ps->blend);
+ blend_color_mix(projPixel->pixel.ch_pt, projPixel->origColor.ch, projPixel->newColor.ch, (int)(mask*255));
+ }
+ else {
+ *projPixel->pixel.uint_pt = IMB_blend_color(*projPixel->pixel.uint_pt, *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend);
+ }
+}
+
+static void do_projectpaint_draw_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask) {
+ if (ps->is_texbrush) {
+ rgba[0] *= ps->brush->rgb[0];
+ rgba[1] *= ps->brush->rgb[1];
+ rgba[2] *= ps->brush->rgb[2];
+ }
+ else {
+ VECCOPY(rgba, ps->brush->rgb);
+ }
+
+ if (ps->is_airbrush==0 && mask < 1.0f) {
+ IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, rgba, alpha, ps->blend);
+ blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f, projPixel->newColor.f, mask);
+ }
+ else {
+ IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, rgba, alpha*mask, ps->blend);
+ }
+}
+
+
+
+/* run this for single and multithreaded painting */
+static void *do_projectpaint_thread(void *ph_v)
+{
+ /* First unpack args from the struct */
+ ProjPaintState *ps = ((ProjectHandle *)ph_v)->ps;
+ ProjPaintImage *projImages = ((ProjectHandle *)ph_v)->projImages;
+ const float *lastpos = ((ProjectHandle *)ph_v)->prevmval;
+ const float *pos = ((ProjectHandle *)ph_v)->mval;
+ const int thread_index = ((ProjectHandle *)ph_v)->thread_index;
+ /* Done with args from ProjectHandle */
+
+ LinkNode *node;
+ ProjPixel *projPixel;
+
+ int last_index = -1;
+ ProjPaintImage *last_projIma;
+ ImagePaintPartialRedraw *last_partial_redraw_cell;
+
+ float rgba[4], alpha, dist_nosqrt;
+
+ float brush_size_sqared;
+ float falloff;
+ int bucket_index;
+ int is_floatbuf = 0;
+ const short tool = ps->tool;
+ rctf bucket_bounds;
+
+ /* for smear only */
+ float pos_ofs[2];
+ float co[2];
+ float mask = 1.0f; /* airbrush wont use mask */
+ unsigned short mask_short;
+
+ LinkNode *smearPixels = NULL;
+ LinkNode *smearPixels_f = NULL;
+ MemArena *smearArena = NULL; /* mem arena for this brush projection only */
+
+
+ if (tool==PAINT_TOOL_SMEAR) {
+ pos_ofs[0] = pos[0] - lastpos[0];
+ pos_ofs[1] = pos[1] - lastpos[1];
+
+ smearArena = BLI_memarena_new(1<<16);
+ }
+
+ /* avoid a square root with every dist comparison */
+ brush_size_sqared = ps->brush->size * ps->brush->size;
+
+ /* printf("brush bounds %d %d %d %d\n", bucketMin[0], bucketMin[1], bucketMax[0], bucketMax[1]); */
+
+ while (project_bucket_iter_next(ps, &bucket_index, &bucket_bounds, pos)) {
+
+ /* Check this bucket and its faces are initialized */
+ if (ps->bucketFlags[bucket_index] == PROJ_BUCKET_NULL) {
+ /* No pixels initialized */
+ project_bucket_init(ps, thread_index, bucket_index, &bucket_bounds);
+ }
+
+ for (node = ps->bucketRect[bucket_index]; node; node = node->next) {
+
+ projPixel = (ProjPixel *)node->link;
+
+ /*dist = Vec2Lenf(projPixel->projCoSS, pos);*/ /* correct but uses a sqrt */
+ dist_nosqrt = Vec2Lenf_nosqrt(projPixel->projCoSS, pos);
+
+ /*if (dist < s->brush->size) {*/ /* correct but uses a sqrt */
+ if (dist_nosqrt < brush_size_sqared) {
+ falloff = brush_sample_falloff_noalpha(ps->brush, sqrt(dist_nosqrt));
+ if (falloff > 0.0f) {
+ if (ps->is_texbrush) {
+ brush_sample_tex(ps->brush, projPixel->projCoSS, rgba);
+ alpha = rgba[3];
+ } else {
+ alpha = 1.0f;
+ }
+
+ if (ps->is_airbrush) {
+ /* for an aurbrush there is no real mask, so just multiply the alpha by it */
+ alpha *= falloff * ps->brush->alpha;
+ mask = ((float)projPixel->mask)/65535.0f;
+ }
+ else {
+ /* This brush dosnt accumulate so add some curve to the brushes falloff */
+ falloff = 1.0f - falloff;
+ falloff = 1.0f - (falloff * falloff);
+
+ mask_short = projPixel->mask * (ps->brush->alpha * falloff);
+ if (mask_short > projPixel->mask_max) {
+ mask = ((float)mask_short)/65535.0f;
+ projPixel->mask_max = mask_short;
+ }
+ else {
+ /*mask = ((float)projPixel->mask_max)/65535.0f;*/
+
+ /* Go onto the next pixel */
+ continue;
+ }
+ }
+
+ if (alpha > 0.0f) {
+
+ if (last_index != projPixel->image_index) {
+ last_index = projPixel->image_index;
+ last_projIma = projImages + last_index;
+
+ last_projIma->touch = 1;
+ is_floatbuf = last_projIma->ibuf->rect_float ? 1 : 0;
+ }
+
+ last_partial_redraw_cell = last_projIma->partRedrawRect + projPixel->bb_cell_index;
+ last_partial_redraw_cell->x1 = MIN2(last_partial_redraw_cell->x1, projPixel->x_px);
+ last_partial_redraw_cell->y1 = MIN2(last_partial_redraw_cell->y1, projPixel->y_px);
+
+ last_partial_redraw_cell->x2 = MAX2(last_partial_redraw_cell->x2, projPixel->x_px+1);
+ last_partial_redraw_cell->y2 = MAX2(last_partial_redraw_cell->y2, projPixel->y_px+1);
+
+
+ switch(tool) {
+ case PAINT_TOOL_CLONE:
+ if (is_floatbuf) {
+ if (((ProjPixelClone *)projPixel)->clonepx.f[3]) {
+ do_projectpaint_clone_f(ps, projPixel, rgba, alpha, mask);
+ }
+ }
+ else {
+ if (((ProjPixelClone*)projPixel)->clonepx.ch[3]) {
+ do_projectpaint_clone(ps, projPixel, rgba, alpha, mask);
+ }
+ }
+ break;
+ case PAINT_TOOL_SMEAR:
+ Vec2Subf(co, projPixel->projCoSS, pos_ofs);
+
+ if (is_floatbuf) do_projectpaint_smear_f(ps, projPixel, rgba, alpha, mask, smearArena, &smearPixels_f, co);
+ else do_projectpaint_smear(ps, projPixel, rgba, alpha, mask, smearArena, &smearPixels, co);
+ break;
+ default:
+ if (is_floatbuf) do_projectpaint_draw_f(ps, projPixel, rgba, alpha, mask);
+ else do_projectpaint_draw(ps, projPixel, rgba, alpha, mask);
+ break;
+ }
+ }
+ /* done painting */
+ }
+ }
+ }
+ }
+
+
+ if (tool==PAINT_TOOL_SMEAR) {
+
+ for (node= smearPixels; node; node= node->next) { /* this wont run for a float image */
+ projPixel = node->link;
+ *projPixel->pixel.uint_pt = ((ProjPixelClone *)projPixel)->clonepx.uint;
+ }
+
+ for (node= smearPixels_f; node; node= node->next) { /* this wont run for a float image */
+ projPixel = node->link;
+ IMAPAINT_CHAR_RGBA_TO_FLOAT(projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.ch);
+ node = node->next;
+ }
+
+ BLI_memarena_free(smearArena);
+ }
+
+ return NULL;
+}
+
+static int project_paint_op(void *state, ImBuf *ibufb, float *lastpos, float *pos)
+{
+ /* First unpack args from the struct */
+ ProjPaintState *ps = (ProjPaintState *)state;
+ int touch_any = 0;
+
+ ProjectHandle handles[BLENDER_MAX_THREADS];
+ ListBase threads;
+ int a,i;
+
+ if (!project_bucket_iter_init(ps, pos)) {
+ return 0;
+ }
+
+ if (ps->thread_tot > 1)
+ BLI_init_threads(&threads, do_projectpaint_thread, ps->thread_tot);
+
+ /* get the threads running */
+ for(a=0; a < ps->thread_tot; a++) {
+
+ /* set defaults in handles */
+ //memset(&handles[a], 0, sizeof(BakeShade));
+
+ handles[a].ps = ps;
+ VECCOPY2D(handles[a].mval, pos);
+ VECCOPY2D(handles[a].prevmval, lastpos);
+
+ /* thread spesific */
+ handles[a].thread_index = a;
+
+ handles[a].projImages = (ProjPaintImage *)BLI_memarena_alloc(ps->arena_mt[a], ps->image_tot * sizeof(ProjPaintImage));
+
+ memcpy(handles[a].projImages, ps->projImages, ps->image_tot * sizeof(ProjPaintImage));
+
+ /* image bounds */
+ for (i=0; i< ps->image_tot; i++) {
+ handles[a].projImages[i].partRedrawRect = (ImagePaintPartialRedraw *)BLI_memarena_alloc(ps->arena_mt[a], sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED);
+ memcpy(handles[a].projImages[i].partRedrawRect, ps->projImages[i].partRedrawRect, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED);
+ }
+
+ if (ps->thread_tot > 1)
+ BLI_insert_thread(&threads, &handles[a]);
+ }
+
+ if (ps->thread_tot > 1) /* wait for everything to be done */
+ BLI_end_threads(&threads);
+ else
+ do_projectpaint_thread(&handles[0]);
+
+
+ /* move threaded bounds back into ps->projectPartialRedraws */
+ for(i=0; i < ps->image_tot; i++) {
+ int touch = 0;
+ for(a=0; a < ps->thread_tot; a++) {
+ touch |= partial_redraw_array_merge(ps->projImages[i].partRedrawRect, handles[a].projImages[i].partRedrawRect, PROJ_BOUNDBOX_SQUARED);
+ }
+
+ if (touch) {
+ ps->projImages[i].touch = 1;
+ touch_any = 1;
+ }
+ }
+
+ return touch_any;
+}
+
+
+static int project_paint_sub_stroke(ProjPaintState *ps, BrushPainter *painter, int *prevmval_i, int *mval_i, double time, float pressure)
+{
+
+ /* Use mouse coords as floats for projection painting */
+ float pos[2];
+
+ pos[0] = mval_i[0];
+ pos[1] = mval_i[1];
+
+ // we may want to use this later
+ // brush_painter_require_imbuf(painter, ((ibuf->rect_float)? 1: 0), 0, 0);
+
+ if (brush_painter_paint(painter, project_paint_op, pos, time, pressure, ps)) {
+ return 1;
+ }
+ else return 0;
+}
+
+
+static int project_paint_stroke(ProjPaintState *ps, BrushPainter *painter, int *prevmval_i, int *mval_i, double time, float pressure)
+{
+ int a, redraw;
+
+ for (a=0; a < ps->image_tot; a++)
+ partial_redraw_array_init(ps->projImages[a].partRedrawRect);
+
+ redraw= project_paint_sub_stroke(ps, painter, prevmval_i, mval_i, time, pressure);
+
+ if(project_image_refresh_tagged(ps))
+ return redraw;
+
+ return 0;
+}
+
+/* Imagepaint Partial Redraw & Dirty Region */
+
+static void imapaint_clear_partial_redraw()
+{
+ memset(&imapaintpartial, 0, sizeof(imapaintpartial));
+}
+
+static void imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w, int h)
+{
+ ImBuf *tmpibuf = NULL;
+ UndoTile *tile;
+ int srcx= 0, srcy= 0, origx;
+
+ IMB_rectclip(ibuf, NULL, &x, &y, &srcx, &srcy, &w, &h);
+
+ if (w == 0 || h == 0)
+ return;
+
+ if (!imapaintpartial.enabled) {
+ imapaintpartial.x1 = x;
+ imapaintpartial.y1 = y;
+ imapaintpartial.x2 = x+w;
+ imapaintpartial.y2 = y+h;
+ imapaintpartial.enabled = 1;
+ }
+ else {
+ imapaintpartial.x1 = MIN2(imapaintpartial.x1, x);
+ imapaintpartial.y1 = MIN2(imapaintpartial.y1, y);
+ imapaintpartial.x2 = MAX2(imapaintpartial.x2, x+w);
+ imapaintpartial.y2 = MAX2(imapaintpartial.y2, y+h);
+ }
+
+ w = ((x + w - 1) >> IMAPAINT_TILE_BITS);
+ h = ((y + h - 1) >> IMAPAINT_TILE_BITS);
+ origx = (x >> IMAPAINT_TILE_BITS);
+ y = (y >> IMAPAINT_TILE_BITS);
+
+ for (; y <= h; y++) {
+ for (x=origx; x <= w; x++) {
+ for(tile=curundo->tiles.first; tile; tile=tile->next)
+ if(tile->x == x && tile->y == y && strcmp(tile->id.name, ima->id.name)==0)
+ break;
+
+ if(!tile) {
+ undo_init_tile(&ima->id, ibuf, &tmpibuf, x, y);
+ }
+ }
+ }
+
+ ibuf->userflags |= IB_BITMAPDIRTY;
+
+ if (tmpibuf)
+ IMB_freeImBuf(tmpibuf);
+}
+
+static void imapaint_image_update(SpaceImage *sima, Image *image, ImBuf *ibuf, short texpaint)
+{
+ if(ibuf->rect_float)
+ /* TODO - should just update a portion from imapaintpartial! */
+ imb_freerectImBuf(ibuf); /* force recreate of char rect */
+ if(ibuf->mipmap[0])
+ imb_freemipmapImBuf(ibuf);
+
+ /* todo: should set_tpage create ->rect? */
+ if(texpaint || (sima && sima->lock)) {
+ int w = imapaintpartial.x2 - imapaintpartial.x1;
+ int h = imapaintpartial.y2 - imapaintpartial.y1;
+ GPU_paint_update_image(image, imapaintpartial.x1, imapaintpartial.y1, w, h);
+ }
+}
+
+/* Image Paint Operations */
+
+static void imapaint_ibuf_get_set_rgb(ImBuf *ibuf, int x, int y, short torus, short set, float *rgb)
+{
+ if (torus) {
+ x %= ibuf->x;
+ if (x < 0) x += ibuf->x;
+ y %= ibuf->y;
+ if (y < 0) y += ibuf->y;
+ }
+
+ if (ibuf->rect_float) {
+ float *rrgbf = ibuf->rect_float + (ibuf->x*y + x)*4;
+
+ if (set) {
+ IMAPAINT_FLOAT_RGB_COPY(rrgbf, rgb);
+ } else {
+ IMAPAINT_FLOAT_RGB_COPY(rgb, rrgbf);
+ }
+ }
+ else {
+ char *rrgb = (char*)ibuf->rect + (ibuf->x*y + x)*4;
+
+ if (set) {
+ IMAPAINT_FLOAT_RGB_TO_CHAR(rrgb, rgb)
+ } else {
+ IMAPAINT_CHAR_RGB_TO_FLOAT(rgb, rrgb)
+ }
+ }
+}
+
+static int imapaint_ibuf_add_if(ImBuf *ibuf, unsigned int x, unsigned int y, float *outrgb, short torus)
+{
+ float inrgb[3];
+
+ if ((x >= ibuf->x) || (y >= ibuf->y)) {
+ if (torus) imapaint_ibuf_get_set_rgb(ibuf, x, y, 1, 0, inrgb);
+ else return 0;
+ }
+ else imapaint_ibuf_get_set_rgb(ibuf, x, y, 0, 0, inrgb);
+
+ outrgb[0] += inrgb[0];
+ outrgb[1] += inrgb[1];
+ outrgb[2] += inrgb[2];
+
+ return 1;
+}
+
+static void imapaint_lift_soften(ImBuf *ibuf, ImBuf *ibufb, int *pos, short torus)
+{
+ int x, y, count, xi, yi, xo, yo;
+ int out_off[2], in_off[2], dim[2];
+ float outrgb[3];
+
+ dim[0] = ibufb->x;
+ dim[1] = ibufb->y;
+ in_off[0] = pos[0];
+ in_off[1] = pos[1];
+ out_off[0] = out_off[1] = 0;
+
+ if (!torus) {
+ IMB_rectclip(ibuf, ibufb, &in_off[0], &in_off[1], &out_off[0],
+ &out_off[1], &dim[0], &dim[1]);
+
+ if ((dim[0] == 0) || (dim[1] == 0))
+ return;
+ }
+
+ for (y=0; y < dim[1]; y++) {
+ for (x=0; x < dim[0]; x++) {
+ /* get input pixel */
+ xi = in_off[0] + x;
+ yi = in_off[1] + y;
+
+ count = 1;
+ imapaint_ibuf_get_set_rgb(ibuf, xi, yi, torus, 0, outrgb);
+
+ count += imapaint_ibuf_add_if(ibuf, xi-1, yi-1, outrgb, torus);
+ count += imapaint_ibuf_add_if(ibuf, xi-1, yi , outrgb, torus);
+ count += imapaint_ibuf_add_if(ibuf, xi-1, yi+1, outrgb, torus);
+
+ count += imapaint_ibuf_add_if(ibuf, xi , yi-1, outrgb, torus);
+ count += imapaint_ibuf_add_if(ibuf, xi , yi+1, outrgb, torus);
+
+ count += imapaint_ibuf_add_if(ibuf, xi+1, yi-1, outrgb, torus);
+ count += imapaint_ibuf_add_if(ibuf, xi+1, yi , outrgb, torus);
+ count += imapaint_ibuf_add_if(ibuf, xi+1, yi+1, outrgb, torus);
+
+ outrgb[0] /= count;
+ outrgb[1] /= count;
+ outrgb[2] /= count;
+
+ /* write into brush buffer */
+ xo = out_off[0] + x;
+ yo = out_off[1] + y;
+ imapaint_ibuf_get_set_rgb(ibufb, xo, yo, 0, 1, outrgb);
+ }
+ }
+}
+
+static void imapaint_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos)
+{
+ IMB_rectblend_torus(ibufb, ibuf, 0, 0, pos[0], pos[1],
+ ibufb->x, ibufb->y, IMB_BLEND_COPY_RGB);
+}
+
+static ImBuf *imapaint_lift_clone(ImBuf *ibuf, ImBuf *ibufb, int *pos)
+{
+ /* note: allocImbuf returns zero'd memory, so regions outside image will
+ have zero alpha, and hence not be blended onto the image */
+ int w=ibufb->x, h=ibufb->y, destx=0, desty=0, srcx=pos[0], srcy=pos[1];
+ ImBuf *clonebuf= IMB_allocImBuf(w, h, ibufb->depth, ibufb->flags, 0);
+
+ IMB_rectclip(clonebuf, ibuf, &destx, &desty, &srcx, &srcy, &w, &h);
+ IMB_rectblend(clonebuf, ibuf, destx, desty, srcx, srcy, w, h,
+ IMB_BLEND_COPY_RGB);
+ IMB_rectblend(clonebuf, ibufb, destx, desty, destx, desty, w, h,
+ IMB_BLEND_COPY_ALPHA);
+
+ return clonebuf;
+}
+
+static void imapaint_convert_brushco(ImBuf *ibufb, float *pos, int *ipos)
+{
+ ipos[0]= (int)(pos[0] - ibufb->x/2);
+ ipos[1]= (int)(pos[1] - ibufb->y/2);
+}
+
+/* dosnt run for projection painting
+ * only the old style painting in the 3d view */
+static int imapaint_paint_op(void *state, ImBuf *ibufb, float *lastpos, float *pos)
+{
+ ImagePaintState *s= ((ImagePaintState*)state);
+ ImBuf *clonebuf= NULL;
+ short torus= s->brush->flag & BRUSH_TORUS;
+ short blend= s->blend;
+ float *offset= s->brush->clone.offset;
+ float liftpos[2];
+ int bpos[2], blastpos[2], bliftpos[2];
+
+ imapaint_convert_brushco(ibufb, pos, bpos);
+
+ /* lift from canvas */
+ if(s->tool == PAINT_TOOL_SOFTEN) {
+ imapaint_lift_soften(s->canvas, ibufb, bpos, torus);
+ }
+ else if(s->tool == PAINT_TOOL_SMEAR) {
+ if (lastpos[0]==pos[0] && lastpos[1]==pos[1])
+ return 0;
+
+ imapaint_convert_brushco(ibufb, lastpos, blastpos);
+ imapaint_lift_smear(s->canvas, ibufb, blastpos);
+ }
+ else if(s->tool == PAINT_TOOL_CLONE && s->clonecanvas) {
+ liftpos[0]= pos[0] - offset[0]*s->canvas->x;
+ liftpos[1]= pos[1] - offset[1]*s->canvas->y;
+
+ imapaint_convert_brushco(ibufb, liftpos, bliftpos);
+ clonebuf= imapaint_lift_clone(s->clonecanvas, ibufb, bliftpos);
+ }
+
+ imapaint_dirty_region(s->image, s->canvas, bpos[0], bpos[1], ibufb->x, ibufb->y);
+
+ /* blend into canvas */
+ if(torus)
+ IMB_rectblend_torus(s->canvas, (clonebuf)? clonebuf: ibufb,
+ bpos[0], bpos[1], 0, 0, ibufb->x, ibufb->y, blend);
+ else
+ IMB_rectblend(s->canvas, (clonebuf)? clonebuf: ibufb,
+ bpos[0], bpos[1], 0, 0, ibufb->x, ibufb->y, blend);
+
+ if(clonebuf) IMB_freeImBuf(clonebuf);
+
+ return 1;
+}
+
+/* 3D TexturePaint */
+
+static int texpaint_break_stroke(float *prevuv, float *fwuv, float *bkuv, float *uv)
+{
+ float d1[2], d2[2];
+ float mismatch = Vec2Lenf(fwuv, uv);
+ float len1 = Vec2Lenf(prevuv, fwuv);
+ float len2 = Vec2Lenf(bkuv, uv);
+
+ Vec2Subf(d1, fwuv, prevuv);
+ Vec2Subf(d2, uv, bkuv);
+
+ return ((Inp2f(d1, d2) < 0.0f) || (mismatch > MAX2(len1, len2)*2));
+}
+
+/* ImagePaint Common */
+
+static int imapaint_canvas_set(ImagePaintState *s, Image *ima)
+{
+ ImBuf *ibuf= BKE_image_get_ibuf(ima, s->sima? &s->sima->iuser: NULL);
+
+ /* verify that we can paint and set canvas */
+ if(ima->packedfile && ima->rr) {
+ s->warnpackedfile = ima->id.name + 2;
+ return 0;
+ }
+ else if(ibuf && ibuf->channels!=4) {
+ s->warnmultifile = ima->id.name + 2;
+ return 0;
+ }
+ else if(!ima || !ibuf || !(ibuf->rect || ibuf->rect_float))
+ return 0;
+
+ s->image= ima;
+ s->canvas= ibuf;
+
+ /* set clone canvas */
+ if(s->tool == PAINT_TOOL_CLONE) {
+ ima= s->brush->clone.image;
+ ibuf= BKE_image_get_ibuf(ima, s->sima? &s->sima->iuser: NULL);
+
+ if(!ima || !ibuf || !(ibuf->rect || ibuf->rect_float))
+ return 0;
+
+ s->clonecanvas= ibuf;
+
+ if(s->canvas->rect_float && !s->clonecanvas->rect_float) {
+ /* temporarily add float rect for cloning */
+ IMB_float_from_rect(s->clonecanvas);
+ s->clonefreefloat= 1;
+ }
+ else if(!s->canvas->rect_float && !s->clonecanvas->rect)
+ IMB_rect_from_float(s->clonecanvas);
+ }
+
+ return 1;
+}
+
+static void imapaint_canvas_free(ImagePaintState *s)
+{
+ if (s->clonefreefloat)
+ imb_freerectfloatImBuf(s->clonecanvas);
+}
+
+static int imapaint_paint_sub_stroke(ImagePaintState *s, BrushPainter *painter, Image *image, short texpaint, float *uv, double time, int update, float pressure)
+{
+ ImBuf *ibuf= BKE_image_get_ibuf(image, s->sima? &s->sima->iuser: NULL);
+ float pos[2];
+
+ if(!ibuf)
+ return 0;
+
+ pos[0] = uv[0]*ibuf->x;
+ pos[1] = uv[1]*ibuf->y;
+
+ brush_painter_require_imbuf(painter, ((ibuf->rect_float)? 1: 0), 0, 0);
+
+ if (brush_painter_paint(painter, imapaint_paint_op, pos, time, pressure, s)) {
+ if (update)
+ imapaint_image_update(s->sima, image, ibuf, texpaint);
+ return 1;
+ }
+ else return 0;
+}
+
+static int imapaint_paint_stroke(ViewContext *vc, ImagePaintState *s, BrushPainter *painter, short texpaint, int *prevmval, int *mval, double time, float pressure)
+{
+ Image *newimage = NULL;
+ float fwuv[2], bkuv[2], newuv[2];
+ unsigned int newfaceindex;
+ int breakstroke = 0, redraw = 0;
+
+ if (texpaint) {
+ /* pick new face and image */
+ if ( imapaint_pick_face(vc, s->me, mval, &newfaceindex) &&
+ ((G.f & G_FACESELECT)==0 || (s->me->mface+newfaceindex)->flag & ME_FACE_SEL)
+ ) {
+ ImBuf *ibuf;
+
+ newimage = (Image*)((s->me->mtface+newfaceindex)->tpage);
+ ibuf= BKE_image_get_ibuf(newimage, s->sima? &s->sima->iuser: NULL);
+
+ if(ibuf && ibuf->rect)
+ imapaint_pick_uv(s->scene, s->ob, s->me, newfaceindex, mval, newuv);
+ else {
+ newimage = NULL;
+ newuv[0] = newuv[1] = 0.0f;
+ }
+ }
+ else
+ newuv[0] = newuv[1] = 0.0f;
+
+ /* see if stroke is broken, and if so finish painting in old position */
+ if (s->image) {
+ imapaint_pick_uv(s->scene, s->ob, s->me, s->faceindex, mval, fwuv);
+ imapaint_pick_uv(s->scene, s->ob, s->me, newfaceindex, prevmval, bkuv);
+
+ if (newimage == s->image)
+ breakstroke= texpaint_break_stroke(s->uv, fwuv, bkuv, newuv);
+ else
+ breakstroke= 1;
+ }
+ else
+ fwuv[0]= fwuv[1]= 0.0f;
+
+ if (breakstroke) {
+ imapaint_pick_uv(s->scene, s->ob, s->me, s->faceindex, mval, fwuv);
+ redraw |= imapaint_paint_sub_stroke(s, painter, s->image, texpaint,
+ fwuv, time, 1, pressure);
+ imapaint_clear_partial_redraw();
+ brush_painter_break_stroke(painter);
+ }
+
+ /* set new canvas */
+ if (newimage && (newimage != s->image))
+ if (!imapaint_canvas_set(s, newimage))
+ newimage = NULL;
+
+ /* paint in new image */
+ if (newimage) {
+ if (breakstroke)
+ redraw|= imapaint_paint_sub_stroke(s, painter, newimage,
+ texpaint, bkuv, time, 0, pressure);
+ redraw|= imapaint_paint_sub_stroke(s, painter, newimage, texpaint,
+ newuv, time, 1, pressure);
+ }
+
+ /* update state */
+ s->image = newimage;
+ s->faceindex = newfaceindex;
+ s->uv[0] = newuv[0];
+ s->uv[1] = newuv[1];
+ }
+ else {
+ UI_view2d_region_to_view(s->v2d, mval[0], mval[1], &newuv[0], &newuv[1]);
+ redraw |= imapaint_paint_sub_stroke(s, painter, s->image, texpaint, newuv,
+ time, 1, pressure);
+ }
+
+ if (redraw)
+ imapaint_clear_partial_redraw();
+
+ return redraw;
+}
+
+/************************ image paint poll ************************/
+
+static Brush *image_paint_brush(bContext *C)
+{
+ Scene *scene= CTX_data_scene(C);
+ ToolSettings *settings= scene->toolsettings;
+
+ return settings->imapaint.brush;
+}
+
+static int image_paint_poll(bContext *C)
+{
+ if(!image_paint_brush(C))
+ return 0;
+
+ if((G.f & G_TEXTUREPAINT) && CTX_wm_region_view3d(C)) {
+ return 1;
+ }
+ else {
+ ScrArea *sa= CTX_wm_area(C);
+ SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C);
+
+ if(sa && sa->spacetype==SPACE_IMAGE) {
+ ARegion *ar= CTX_wm_region(C);
+
+ if((sima->flag & SI_DRAWTOOL) && ar->regiontype==RGN_TYPE_WINDOW)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int image_paint_3d_poll(bContext *C)
+{
+ if(CTX_wm_region_view3d(C))
+ return image_paint_poll(C);
+
+ return 0;
+}
+
+static int image_paint_2d_clone_poll(bContext *C)
+{
+ Scene *scene= CTX_data_scene(C);
+ ToolSettings *settings= scene->toolsettings;
+ Brush *brush= image_paint_brush(C);
+
+ if(!CTX_wm_region_view3d(C) && image_paint_poll(C))
+ if(brush && (settings->imapaint.tool == PAINT_TOOL_CLONE))
+ if(brush->clone.image)
+ return 1;
+
+ return 0;
+}
+
+/************************ paint operator ************************/
+
+typedef enum PaintMode {
+ PAINT_MODE_2D,
+ PAINT_MODE_3D,
+ PAINT_MODE_3D_PROJECT
+} PaintMode;
+
+typedef struct PaintOperation {
+ PaintMode mode;
+
+ BrushPainter *painter;
+ ImagePaintState s;
+ ProjPaintState ps;
+
+ int first;
+ int prevmouse[2];
+ double starttime;
+
+ ViewContext vc;
+ wmTimer *timer;
+} PaintOperation;
+
+static void paint_redraw(bContext *C, ImagePaintState *s, int final)
+{
+ if(final) {
+ if(s->image)
+ GPU_free_image(s->image);
+
+ WM_event_add_notifier(C, NC_IMAGE|NA_EDITED, s->image);
+
+ // XXX node update
+#if 0
+ if(!s->sima && s->image) {
+ /* after paint, tag Image or RenderResult nodes changed */
+ if(s->scene->nodetree) {
+ imagepaint_composite_tags(s->scene->nodetree, image, &s->sima->iuser);
+ }
+ /* signal composite (hurmf, need an allqueue?) */
+ if(s->sima->lock) {
+ ScrArea *sa;
+ for(sa=s->screen->areabase.first; sa; sa= sa->next) {
+ if(sa->spacetype==SPACE_NODE) {
+ if(((SpaceNode *)sa->spacedata.first)->treetype==NTREE_COMPOSIT) {
+ addqueue(sa->win, UI_BUT_EVENT, B_NODE_TREE_EXEC);
+ break;
+ }
+ }
+ }
+ }
+ }
+#endif
+ }
+ else {
+ if(!s->sima || !s->sima->lock)
+ ED_region_tag_redraw(CTX_wm_region(C));
+ else
+ WM_event_add_notifier(C, NC_IMAGE|NA_EDITED, s->image);
+ }
+}
+
+static int paint_init(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ ToolSettings *settings= scene->toolsettings;
+ PaintOperation *pop;
+
+ pop= MEM_callocN(sizeof(PaintOperation), "PaintOperation");
+ pop->first= 1;
+ op->customdata= pop;
+
+ /* initialize from context */
+ if(CTX_wm_region_view3d(C)) {
+ pop->ps.v3d= CTX_wm_view3d(C);
+ pop->ps.rv3d= CTX_wm_region_view3d(C);
+ pop->mode= PAINT_MODE_3D;
+
+ if(!(settings->imapaint.flag & IMAGEPAINT_PROJECT_DISABLE))
+ pop->mode= PAINT_MODE_3D_PROJECT;
+ else
+ view3d_set_viewcontext(C, &pop->vc);
+ }
+ else {
+ pop->s.sima= (SpaceImage*)CTX_wm_space_data(C);
+ pop->s.v2d= &CTX_wm_region(C)->v2d;
+ }
+
+ pop->s.scene= scene;
+ pop->ps.scene= scene;
+ pop->s.screen= CTX_wm_screen(C);
+ pop->ps.ar= CTX_wm_region(C);
+
+ /* intialize brush */
+ pop->s.brush = settings->imapaint.brush;
+ pop->s.tool = settings->imapaint.tool;
+ if(pop->mode == PAINT_MODE_3D && (pop->s.tool == PAINT_TOOL_CLONE))
+ pop->s.tool = PAINT_TOOL_DRAW;
+ pop->s.blend = pop->s.brush->blend;
+
+ if(pop->mode == PAINT_MODE_3D_PROJECT) {
+ pop->ps.brush = pop->s.brush;
+ pop->ps.tool = pop->s.tool;
+ pop->ps.blend = pop->s.blend;
+ }
+
+ if(pop->mode != PAINT_MODE_2D) {
+ pop->ps.ob = pop->s.ob = OBACT;
+ if (!pop->s.ob || !(pop->s.ob->lay & pop->ps.v3d->lay)) return 0;
+ pop->s.me = get_mesh(pop->s.ob);
+ if (!pop->s.me) return 0;
+ }
+ else {
+ pop->s.image = pop->s.sima->image;
+
+ if(!imapaint_canvas_set(&pop->s, pop->s.sima->image)) {
+ if(pop->s.warnmultifile)
+ BKE_report(op->reports, RPT_WARNING, "Image requires 4 color channels to paint");
+ if(pop->s.warnpackedfile)
+ BKE_report(op->reports, RPT_WARNING, "Packed MultiLayer files cannot be painted");
+
+ return 0;
+ }
+ }
+
+ /* note, if we have no UVs on the derived mesh, then we must return here */
+ if(pop->mode == PAINT_MODE_3D_PROJECT) {
+ /* setup projection painting data */
+ pop->ps.do_backfacecull = (settings->imapaint.flag & IMAGEPAINT_PROJECT_BACKFACE) ? 0 : 1;
+ pop->ps.do_occlude = (settings->imapaint.flag & IMAGEPAINT_PROJECT_XRAY) ? 0 : 1;
+ pop->ps.do_mask_normal = (settings->imapaint.flag & IMAGEPAINT_PROJECT_FLAT) ? 0 : 1;;
+
+ if (pop->ps.tool == PAINT_TOOL_CLONE)
+ pop->ps.do_layer_clone = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_CLONE);
+
+ pop->ps.do_layer_mask = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_MASK) ? 1 : 0;
+ pop->ps.do_layer_mask_inv = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_MASK_INV) ? 1 : 0;
+
+
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ pop->ps.seam_bleed_px = settings->imapaint.seam_bleed; /* pixel num to bleed */
+#endif
+
+ if(pop->ps.do_mask_normal) {
+ pop->ps.normal_angle_inner = settings->imapaint.normal_angle;
+ pop->ps.normal_angle = (pop->ps.normal_angle_inner + 90.0f) * 0.5f;
+ }
+ else {
+ pop->ps.normal_angle_inner= pop->ps.normal_angle= settings->imapaint.normal_angle;
+ }
+
+ pop->ps.normal_angle_inner *= M_PI_2 / 90;
+ pop->ps.normal_angle *= M_PI_2 / 90;
+ pop->ps.normal_angle_range = pop->ps.normal_angle - pop->ps.normal_angle_inner;
+
+ if(pop->ps.normal_angle_range <= 0.0f)
+ pop->ps.do_mask_normal = 0; /* no need to do blending */
+
+ project_paint_begin(&pop->ps);
+
+ if(pop->ps.dm==NULL)
+ return 0;
+ }
+
+ settings->imapaint.flag |= IMAGEPAINT_DRAWING;
+ undo_imagepaint_push_begin("Image Paint");
+
+ /* create painter */
+ pop->painter= brush_painter_new(pop->s.brush);
+
+ return 1;
+}
+
+static void paint_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
+{
+ PaintOperation *pop= op->customdata;
+ float time;
+ float pressure;
+ int mouse[2], redraw;
+
+ RNA_int_get_array(itemptr, "mouse", mouse);
+ time= RNA_float_get(itemptr, "time");
+ pressure= RNA_float_get(itemptr, "pressure");
+
+ if(pop->first)
+ project_paint_begin_clone(&pop->ps, mouse);
+
+ if(pop->mode == PAINT_MODE_3D)
+ view3d_operator_needs_opengl(C);
+
+ if(pop->mode == PAINT_MODE_3D_PROJECT) {
+ redraw= project_paint_stroke(&pop->ps, pop->painter, pop->prevmouse, mouse, time, pressure);
+ pop->prevmouse[0]= mouse[0];
+ pop->prevmouse[1]= mouse[1];
+
+ }
+ else {
+ redraw= imapaint_paint_stroke(&pop->vc, &pop->s, pop->painter, pop->mode == PAINT_MODE_3D, pop->prevmouse, mouse, time, pressure);
+ pop->prevmouse[0]= mouse[0];
+ pop->prevmouse[1]= mouse[1];
+ }
+
+ if(redraw)
+ paint_redraw(C, &pop->s, 0);
+
+ pop->first= 0;
+}
+
+static void paint_exit(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ ToolSettings *settings= scene->toolsettings;
+ PaintOperation *pop= op->customdata;
+
+ if(pop->timer)
+ WM_event_remove_window_timer(CTX_wm_window(C), pop->timer);
+
+ settings->imapaint.flag &= ~IMAGEPAINT_DRAWING;
+ imapaint_canvas_free(&pop->s);
+ brush_painter_free(pop->painter);
+
+ if(pop->mode == PAINT_MODE_3D_PROJECT)
+ project_paint_end(&pop->ps);
+
+ paint_redraw(C, &pop->s, 1);
+ undo_imagepaint_push_end();
+
+ if(pop->s.warnmultifile)
+ BKE_reportf(op->reports, RPT_WARNING, "Image requires 4 color channels to paint: %s", pop->s.warnmultifile);
+ if(pop->s.warnpackedfile)
+ BKE_reportf(op->reports, RPT_WARNING, "Packed MultiLayer files cannot be painted: %s", pop->s.warnpackedfile);
+
+ MEM_freeN(pop);
+}
+
+static int paint_exec(bContext *C, wmOperator *op)
+{
+ if(!paint_init(C, op)) {
+ MEM_freeN(op->customdata);
+ return OPERATOR_CANCELLED;
+ }
+
+ RNA_BEGIN(op->ptr, itemptr, "stroke") {
+ paint_apply(C, op, &itemptr);
+ }
+ RNA_END;
+
+ paint_exit(C, op);
+
+ return OPERATOR_FINISHED;
+}
+
+static void paint_apply_event(bContext *C, wmOperator *op, wmEvent *event)
+{
+ ARegion *ar= CTX_wm_region(C);
+ PaintOperation *pop= op->customdata;
+ wmTabletData *wmtab;
+ PointerRNA itemptr;
+ float pressure;
+ double time;
+ int tablet, mouse[2];
+
+ // XXX +1 matches brush location better but
+ // still not exact, find out why and fix ..
+ mouse[0]= event->x - ar->winrct.xmin + 1;
+ mouse[1]= event->y - ar->winrct.ymin + 1;
+
+ time= PIL_check_seconds_timer();
+
+ tablet= 0;
+ pressure= 0;
+ pop->s.blend= pop->s.brush->blend;
+
+ if(event->custom == EVT_DATA_TABLET) {
+ wmtab= event->customdata;
+
+ tablet= (wmtab->Active != EVT_TABLET_NONE);
+ pressure= wmtab->Pressure;
+ if(wmtab->Active == EVT_TABLET_ERASER)
+ pop->s.blend= BRUSH_BLEND_ERASE_ALPHA;
+ }
+ else
+ pressure= 1.0f;
+
+ if(pop->first) {
+ pop->prevmouse[0]= mouse[0];
+ pop->prevmouse[1]= mouse[1];
+ pop->starttime= time;
+
+ /* special exception here for too high pressure values on first touch in
+ windows for some tablets, then we just skip first touch .. */
+ if ((pop->s.brush->flag & (BRUSH_ALPHA_PRESSURE|BRUSH_SIZE_PRESSURE|
+ BRUSH_SPACING_PRESSURE|BRUSH_RAD_PRESSURE)) && tablet && (pressure >= 0.99f))
+ return;
+ }
+
+ /* fill in stroke */
+ RNA_collection_add(op->ptr, "stroke", &itemptr);
+
+ RNA_int_set_array(&itemptr, "mouse", mouse);
+ RNA_float_set(&itemptr, "time", (float)(time - pop->starttime));
+ RNA_float_set(&itemptr, "pressure", pressure);
+
+ /* apply */
+ paint_apply(C, op, &itemptr);
+}
+
+static int paint_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ PaintOperation *pop;
+
+ if(!paint_init(C, op)) {
+ MEM_freeN(op->customdata);
+ return OPERATOR_CANCELLED;
+ }
+
+ paint_apply_event(C, op, event);
+
+ pop= op->customdata;
+ WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);
+
+ if(pop->s.brush->flag & BRUSH_AIRBRUSH)
+ pop->timer= WM_event_add_window_timer(CTX_wm_window(C), TIMER, 0.01f);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int paint_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+ PaintOperation *pop= op->customdata;
+
+ switch(event->type) {
+ case LEFTMOUSE:
+ case MIDDLEMOUSE:
+ case RIGHTMOUSE: // XXX hardcoded
+ paint_exit(C, op);
+ return OPERATOR_FINISHED;
+ case MOUSEMOVE:
+ paint_apply_event(C, op, event);
+ break;
+ case TIMER:
+ if(event->customdata == pop->timer)
+ paint_apply_event(C, op, event);
+ break;
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int paint_cancel(bContext *C, wmOperator *op)
+{
+ paint_exit(C, op);
+
+ return OPERATOR_CANCELLED;
+}
+
+void PAINT_OT_image_paint(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Image Paint";
+ ot->idname= "PAINT_OT_image_paint";
+
+ /* api callbacks */
+ ot->exec= paint_exec;
+ ot->invoke= paint_invoke;
+ ot->modal= paint_modal;
+ ot->cancel= paint_cancel;
+ ot->poll= image_paint_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+}
+
+/************************ grab clone operator ************************/
+
+typedef struct GrabClone {
+ float startoffset[2];
+ int startx, starty;
+} GrabClone;
+
+static void grab_clone_apply(bContext *C, wmOperator *op)
+{
+ Brush *brush= image_paint_brush(C);
+ float delta[2];
+
+ RNA_float_get_array(op->ptr, "delta", delta);
+ brush->clone.offset[0] += delta[0];
+ brush->clone.offset[1] += delta[1];
+
+ ED_region_tag_redraw(CTX_wm_region(C));
+}
+
+static int grab_clone_exec(bContext *C, wmOperator *op)
+{
+ grab_clone_apply(C, op);
+
+ return OPERATOR_FINISHED;
+}
+
+static int grab_clone_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ Brush *brush= image_paint_brush(C);
+ GrabClone *cmv;
+
+ cmv= MEM_callocN(sizeof(GrabClone), "GrabClone");
+ cmv->startoffset[0]= brush->clone.offset[0];
+ cmv->startoffset[1]= brush->clone.offset[1];
+ cmv->startx= event->x;
+ cmv->starty= event->y;
+ op->customdata= cmv;
+
+ WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int grab_clone_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+ Brush *brush= image_paint_brush(C);
+ ARegion *ar= CTX_wm_region(C);
+ GrabClone *cmv= op->customdata;
+ float startfx, startfy, fx, fy, delta[2];
+ int xmin= ar->winrct.xmin, ymin= ar->winrct.ymin;
+
+ switch(event->type) {
+ case LEFTMOUSE:
+ case MIDDLEMOUSE:
+ case RIGHTMOUSE: // XXX hardcoded
+ MEM_freeN(op->customdata);
+ return OPERATOR_FINISHED;
+ case MOUSEMOVE:
+ /* mouse moved, so move the clone image */
+ UI_view2d_region_to_view(&ar->v2d, cmv->startx - xmin, cmv->starty - ymin, &startfx, &startfy);
+ UI_view2d_region_to_view(&ar->v2d, event->x - xmin, event->y - ymin, &fx, &fy);
+
+ delta[0]= fx - startfx;
+ delta[1]= fy - startfy;
+ RNA_float_set_array(op->ptr, "delta", delta);
+
+ brush->clone.offset[0]= cmv->startoffset[0];
+ brush->clone.offset[1]= cmv->startoffset[1];
+
+ grab_clone_apply(C, op);
+ break;
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int grab_clone_cancel(bContext *C, wmOperator *op)
+{
+ MEM_freeN(op->customdata);
+ return OPERATOR_CANCELLED;
+}
+
+void PAINT_OT_grab_clone(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Grab Clone";
+ ot->idname= "PAINT_OT_grab_clone";
+
+ /* api callbacks */
+ ot->exec= grab_clone_exec;
+ ot->invoke= grab_clone_invoke;
+ ot->modal= grab_clone_modal;
+ ot->cancel= grab_clone_cancel;
+ ot->poll= image_paint_2d_clone_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_float_vector(ot->srna, "delta", 2, NULL, -FLT_MAX, FLT_MAX, "Delta", "Delta offset of clone image in 0.0..1.0 coordinates.", -1.0f, 1.0f);
+}
+
+/******************** sample color operator ********************/
+
+static int sample_color_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ Brush *brush= image_paint_brush(C);
+ ARegion *ar= CTX_wm_region(C);
+ int location[2];
+
+ RNA_int_get_array(op->ptr, "location", location);
+ paint_sample_color(scene, ar, location[0], location[1]);
+
+ WM_event_add_notifier(C, NC_BRUSH|NA_EDITED, brush);
+
+ return OPERATOR_FINISHED;
+}
+
+static int sample_color_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ ARegion *ar= CTX_wm_region(C);
+ int location[2];
+
+ location[0]= event->x - ar->winrct.xmin;
+ location[1]= event->y - ar->winrct.ymin;
+ RNA_int_set_array(op->ptr, "location", location);
+
+ return sample_color_exec(C, op);
+}
+
+void PAINT_OT_sample_color(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Sample Color";
+ ot->idname= "PAINT_OT_sample_color";
+
+ /* api callbacks */
+ ot->exec= sample_color_exec;
+ ot->invoke= sample_color_invoke;
+ ot->poll= image_paint_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_int_vector(ot->srna, "location", 2, NULL, 0, INT_MAX, "Location", "Cursor location in region coordinates.", 0, 16384);
+}
+
+/******************** set clone cursor operator ********************/
+
+static int set_clone_cursor_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ View3D *v3d= CTX_wm_view3d(C);
+ float *cursor= give_cursor(scene, v3d);
+
+ RNA_float_get_array(op->ptr, "location", cursor);
+
+ ED_area_tag_redraw(CTX_wm_area(C));
+
+ return OPERATOR_FINISHED;
+}
+
+static int set_clone_cursor_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ Scene *scene= CTX_data_scene(C);
+ View3D *v3d= CTX_wm_view3d(C);
+ ARegion *ar= CTX_wm_region(C);
+ float location[3];
+ short mval[2];
+
+ mval[0]= event->x - ar->winrct.xmin;
+ mval[1]= event->y - ar->winrct.ymin;
+
+ view3d_operator_needs_opengl(C);
+
+ if(!view_autodist(scene, ar, v3d, mval, location))
+ return OPERATOR_CANCELLED;
+
+ RNA_float_set_array(op->ptr, "location", location);
+
+ return set_clone_cursor_exec(C, op);
+}
+
+void PAINT_OT_set_clone_cursor(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Set Clone Cursor";
+ ot->idname= "PAINT_OT_set_clone_cursor";
+
+ /* api callbacks */
+ ot->exec= set_clone_cursor_exec;
+ ot->invoke= set_clone_cursor_invoke;
+ ot->poll= image_paint_3d_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_float_vector(ot->srna, "location", 3, NULL, -FLT_MAX, FLT_MAX, "Location", "Cursor location in world space coordinates.", -10000.0f, 10000.0f);
+}
+
+/************************ cursor drawing *******************************/
+
+static void brush_drawcursor(bContext *C, int x, int y, void *customdata)
+{
+ Brush *brush= image_paint_brush(C);
+ RegionView3D *rv3d= CTX_wm_region_view3d(C);
+
+ if(brush) {
+ glPushMatrix();
+
+ glTranslatef((float)x, (float)y, 0.0f);
+
+ if(!rv3d) {
+ SpaceImage *sima= (SpaceImage*)CTX_wm_space_data(C);
+ ARegion *ar= CTX_wm_region(C);
+ float zoomx, zoomy;
+
+ ED_space_image_zoom(sima, ar, &zoomx, &zoomy);
+ glScalef(zoomx, zoomy, 1.0f);
+ }
+
+ glColor4ub(255, 255, 255, 128);
+ glEnable( GL_LINE_SMOOTH );
+ glEnable(GL_BLEND);
+ glutil_draw_lined_arc(0.0, M_PI*2.0, brush->size*0.5f, 40);
+ glDisable(GL_BLEND);
+ glDisable( GL_LINE_SMOOTH );
+
+ glPopMatrix();
+ }
+}
+
+static void toggle_paint_cursor(bContext *C, int enable)
+{
+ ToolSettings *settings= CTX_data_scene(C)->toolsettings;
+
+ if(settings->imapaint.paintcursor && !enable) {
+ WM_paint_cursor_end(CTX_wm_manager(C), settings->imapaint.paintcursor);
+ settings->imapaint.paintcursor = NULL;
+ }
+ else if(enable)
+ settings->imapaint.paintcursor= WM_paint_cursor_activate(CTX_wm_manager(C), image_paint_poll, brush_drawcursor, NULL);
+}
+
+/******************** texture paint toggle operator ********************/
+
+static int texture_paint_toggle_poll(bContext *C)
+{
+ if(CTX_data_edit_object(C))
+ return 0;
+ if(CTX_data_active_object(C)==NULL)
+ return 0;
+
+ return 1;
+}
+
+static int texture_paint_toggle_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene= CTX_data_scene(C);
+ Object *ob= CTX_data_active_object(C);
+ Mesh *me= 0;
+
+ if(ob==NULL)
+ return OPERATOR_CANCELLED;
+
+ if (object_data_is_libdata(ob)) {
+ BKE_report(op->reports, RPT_ERROR, "Can't edit external libdata.");
+ return OPERATOR_CANCELLED;
+ }
+
+ me= get_mesh(ob);
+
+ if(!(G.f & G_TEXTUREPAINT) && !me) {
+ BKE_report(op->reports, RPT_ERROR, "Can only enter texture paint mode for mesh objects.");
+ return OPERATOR_CANCELLED;
+ }
+
+ if(G.f & G_TEXTUREPAINT) {
+ G.f &= ~G_TEXTUREPAINT;
+ GPU_paint_set_mipmap(1);
+ toggle_paint_cursor(C, 0);
+ }
+ else {
+ G.f |= G_TEXTUREPAINT;
+
+ if(me->mtface==NULL)
+ me->mtface= CustomData_add_layer(&me->fdata, CD_MTFACE, CD_DEFAULT,
+ NULL, me->totface);
+
+ brush_check_exists(&scene->toolsettings->imapaint.brush);
+ GPU_paint_set_mipmap(0);
+ toggle_paint_cursor(C, 1);
+ }
+
+ DAG_object_flush_update(scene, ob, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_SCENE|ND_MODE, scene);
+
+ return OPERATOR_FINISHED;
+}
+
+void PAINT_OT_texture_paint_toggle(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Texture Paint Toggle";
+ ot->idname= "PAINT_OT_texture_paint_toggle";
+
+ /* api callbacks */
+ ot->exec= texture_paint_toggle_exec;
+ ot->poll= texture_paint_toggle_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h
new file mode 100644
index 00000000000..a5ac3264eee
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/paint_intern.h
@@ -0,0 +1,62 @@
+/**
+ * $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_PAINT_INTERN_H
+#define ED_PAINT_INTERN_H
+
+struct Scene;
+struct Object;
+struct Mesh;
+struct ViewContext;
+struct wmOperatorType;
+struct ARegion;
+
+/* paint_vertex.c */
+void PAINT_OT_weight_paint_toggle(struct wmOperatorType *ot);
+void PAINT_OT_weight_paint_radial_control(struct wmOperatorType *ot);
+void PAINT_OT_weight_paint(struct wmOperatorType *ot);
+
+void PAINT_OT_vertex_paint_radial_control(struct wmOperatorType *ot);
+void PAINT_OT_vertex_paint_toggle(struct wmOperatorType *ot);
+void PAINT_OT_vertex_paint(struct wmOperatorType *ot);
+
+/* paint_image.c */
+void PAINT_OT_image_paint(struct wmOperatorType *ot);
+void PAINT_OT_grab_clone(struct wmOperatorType *ot);
+void PAINT_OT_sample_color(struct wmOperatorType *ot);
+void PAINT_OT_set_clone_cursor(struct wmOperatorType *ot);
+void PAINT_OT_texture_paint_toggle(struct wmOperatorType *ot);
+
+/* paint_utils.c */
+int imapaint_pick_face(struct ViewContext *vc, struct Mesh *me, int *mval, unsigned int *index);
+void imapaint_pick_uv(struct Scene *scene, struct Object *ob, struct Mesh *mesh, unsigned int faceindex, int *xy, float *uv);
+
+void paint_sample_color(struct Scene *scene, struct ARegion *ar, int x, int y);
+
+#endif /* ED_PAINT_INTERN_H */
+
diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c
new file mode 100644
index 00000000000..0918e8c9b94
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/paint_ops.c
@@ -0,0 +1,30 @@
+
+#include "ED_sculpt.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "paint_intern.h"
+
+/**************************** registration **********************************/
+
+void ED_operatortypes_paint(void)
+{
+ /* image */
+ WM_operatortype_append(PAINT_OT_texture_paint_toggle);
+ WM_operatortype_append(PAINT_OT_image_paint);
+ WM_operatortype_append(PAINT_OT_sample_color);
+ WM_operatortype_append(PAINT_OT_grab_clone);
+ WM_operatortype_append(PAINT_OT_set_clone_cursor);
+
+ /* weight */
+ WM_operatortype_append(PAINT_OT_weight_paint_toggle);
+ WM_operatortype_append(PAINT_OT_weight_paint_radial_control);
+ WM_operatortype_append(PAINT_OT_weight_paint);
+
+ /* vertex */
+ WM_operatortype_append(PAINT_OT_vertex_paint_radial_control);
+ WM_operatortype_append(PAINT_OT_vertex_paint_toggle);
+ WM_operatortype_append(PAINT_OT_vertex_paint);
+}
+
diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c
new file mode 100644
index 00000000000..e0ac3c94109
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/paint_utils.c
@@ -0,0 +1,191 @@
+
+#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_view3d_types.h"
+
+#include "BLI_arithb.h"
+
+#include "BKE_DerivedMesh.h"
+#include "BKE_global.h"
+#include "BKE_utildefines.h"
+
+#include "BIF_gl.h"
+
+#include "ED_view3d.h"
+
+#include "paint_intern.h"
+
+/* 3D Paint */
+
+static void imapaint_project(Object *ob, float *model, float *proj, float *co, float *pco)
+{
+ VECCOPY(pco, co);
+ pco[3]= 1.0f;
+
+ Mat4MulVecfl(ob->obmat, pco);
+ Mat4MulVecfl((float(*)[4])model, pco);
+ Mat4MulVec4fl((float(*)[4])proj, pco);
+}
+
+static void imapaint_tri_weights(Object *ob, float *v1, float *v2, float *v3, float *co, float *w)
+{
+ float pv1[4], pv2[4], pv3[4], h[3], divw;
+ float model[16], proj[16], wmat[3][3], invwmat[3][3];
+ GLint view[4];
+
+ /* compute barycentric coordinates */
+
+ /* get the needed opengl matrices */
+ glGetIntegerv(GL_VIEWPORT, view);
+ glGetFloatv(GL_MODELVIEW_MATRIX, model);
+ glGetFloatv(GL_PROJECTION_MATRIX, proj);
+ view[0] = view[1] = 0;
+
+ /* project the verts */
+ imapaint_project(ob, model, proj, v1, pv1);
+ imapaint_project(ob, model, proj, v2, pv2);
+ imapaint_project(ob, model, proj, v3, pv3);
+
+ /* do inverse view mapping, see gluProject man page */
+ h[0]= (co[0] - view[0])*2.0f/view[2] - 1;
+ h[1]= (co[1] - view[1])*2.0f/view[3] - 1;
+ h[2]= 1.0f;
+
+ /* solve for(w1,w2,w3)/perspdiv in:
+ h*perspdiv = Project*Model*(w1*v1 + w2*v2 + w3*v3) */
+
+ wmat[0][0]= pv1[0]; wmat[1][0]= pv2[0]; wmat[2][0]= pv3[0];
+ wmat[0][1]= pv1[1]; wmat[1][1]= pv2[1]; wmat[2][1]= pv3[1];
+ wmat[0][2]= pv1[3]; wmat[1][2]= pv2[3]; wmat[2][2]= pv3[3];
+
+ Mat3Inv(invwmat, wmat);
+ Mat3MulVecfl(invwmat, h);
+
+ VECCOPY(w, h);
+
+ /* w is still divided by perspdiv, make it sum to one */
+ divw= w[0] + w[1] + w[2];
+ if(divw != 0.0f)
+ VecMulf(w, 1.0f/divw);
+}
+
+/* compute uv coordinates of mouse in face */
+void imapaint_pick_uv(Scene *scene, Object *ob, Mesh *mesh, unsigned int faceindex, int *xy, float *uv)
+{
+ DerivedMesh *dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH);
+ int *index = dm->getFaceDataArray(dm, CD_ORIGINDEX);
+ MTFace *tface = dm->getFaceDataArray(dm, CD_MTFACE), *tf;
+ int numfaces = dm->getNumFaces(dm), a;
+ float p[2], w[3], absw, minabsw;
+ MFace mf;
+ MVert mv[4];
+
+ minabsw = 1e10;
+ uv[0] = uv[1] = 0.0;
+
+ /* test all faces in the derivedmesh with the original index of the picked face */
+ for(a = 0; a < numfaces; a++) {
+ if(index[a] == faceindex) {
+ dm->getFace(dm, a, &mf);
+
+ dm->getVert(dm, mf.v1, &mv[0]);
+ dm->getVert(dm, mf.v2, &mv[1]);
+ dm->getVert(dm, mf.v3, &mv[2]);
+ if(mf.v4)
+ dm->getVert(dm, mf.v4, &mv[3]);
+
+ tf= &tface[a];
+
+ p[0]= xy[0];
+ p[1]= xy[1];
+
+ if(mf.v4) {
+ /* the triangle with the largest absolute values is the one
+ with the most negative weights */
+ imapaint_tri_weights(ob, mv[0].co, mv[1].co, mv[3].co, p, w);
+ absw= fabs(w[0]) + fabs(w[1]) + fabs(w[2]);
+ if(absw < minabsw) {
+ uv[0]= tf->uv[0][0]*w[0] + tf->uv[1][0]*w[1] + tf->uv[3][0]*w[2];
+ uv[1]= tf->uv[0][1]*w[0] + tf->uv[1][1]*w[1] + tf->uv[3][1]*w[2];
+ minabsw = absw;
+ }
+
+ imapaint_tri_weights(ob, mv[1].co, mv[2].co, mv[3].co, p, w);
+ absw= fabs(w[0]) + fabs(w[1]) + fabs(w[2]);
+ if(absw < minabsw) {
+ uv[0]= tf->uv[1][0]*w[0] + tf->uv[2][0]*w[1] + tf->uv[3][0]*w[2];
+ uv[1]= tf->uv[1][1]*w[0] + tf->uv[2][1]*w[1] + tf->uv[3][1]*w[2];
+ minabsw = absw;
+ }
+ }
+ else {
+ imapaint_tri_weights(ob, mv[0].co, mv[1].co, mv[2].co, p, w);
+ absw= fabs(w[0]) + fabs(w[1]) + fabs(w[2]);
+ if(absw < minabsw) {
+ uv[0]= tf->uv[0][0]*w[0] + tf->uv[1][0]*w[1] + tf->uv[2][0]*w[2];
+ uv[1]= tf->uv[0][1]*w[0] + tf->uv[1][1]*w[1] + tf->uv[2][1]*w[2];
+ minabsw = absw;
+ }
+ }
+ }
+ }
+
+ dm->release(dm);
+}
+
+///* returns 0 if not found, otherwise 1 */
+int imapaint_pick_face(ViewContext *vc, Mesh *me, int *mval, unsigned int *index)
+{
+ if(!me || me->totface==0)
+ return 0;
+
+ /* sample only on the exact position */
+ *index = view3d_sample_backbuf(vc, mval[0], mval[1]);
+
+ if((*index)<=0 || (*index)>(unsigned int)me->totface)
+ return 0;
+
+ (*index)--;
+
+ return 1;
+}
+
+/* used for both 3d view and image window */
+void paint_sample_color(Scene *scene, ARegion *ar, int x, int y) /* frontbuf */
+{
+ VPaint *vp= scene->toolsettings->vpaint;
+ unsigned int col;
+ char *cp;
+
+ CLAMP(x, 0, ar->winx);
+ CLAMP(y, 0, ar->winy);
+
+ glReadBuffer(GL_FRONT);
+ glReadPixels(x+ar->winrct.xmin, y+ar->winrct.ymin, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &col);
+ glReadBuffer(GL_BACK);
+
+ cp = (char *)&col;
+
+ if(G.f & (G_VERTEXPAINT|G_WEIGHTPAINT)) {
+ vp->r= cp[0]/255.0f;
+ vp->g= cp[1]/255.0f;
+ vp->b= cp[2]/255.0f;
+ }
+ else {
+ Brush *brush= scene->toolsettings->imapaint.brush;
+
+ if(brush) {
+ brush->rgb[0]= cp[0]/255.0f;
+ brush->rgb[1]= cp[1]/255.0f;
+ brush->rgb[2]= cp[2]/255.0f;
+
+ }
+ }
+}
+
diff --git a/source/blender/editors/sculpt_paint/paint_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.c
new file mode 100644
index 00000000000..1623c7feef1
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/paint_vertex.c
@@ -0,0 +1,1908 @@
+/**
+ * $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 <math.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "MEM_guardedalloc.h"
+
+#include "IMB_imbuf.h"
+#include "IMB_imbuf_types.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_arithb.h"
+#include "MTC_matrixops.h"
+
+#include "DNA_action_types.h"
+#include "DNA_armature_types.h"
+#include "DNA_brush_types.h"
+#include "DNA_cloth_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_types.h"
+#include "DNA_object_force.h"
+#include "DNA_particle_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_view3d_types.h"
+#include "DNA_userdef_types.h"
+
+#include "RNA_access.h"
+
+#include "BKE_armature.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_cloth.h"
+#include "BKE_context.h"
+#include "BKE_customdata.h"
+#include "BKE_depsgraph.h"
+#include "BKE_deform.h"
+#include "BKE_displist.h"
+#include "BKE_global.h"
+#include "BKE_mesh.h"
+#include "BKE_modifier.h"
+#include "BKE_multires.h"
+#include "BKE_object.h"
+#include "BKE_utildefines.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "ED_mesh.h"
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_util.h"
+#include "ED_view3d.h"
+
+ /* vp->mode */
+#define VP_MIX 0
+#define VP_ADD 1
+#define VP_SUB 2
+#define VP_MUL 3
+#define VP_BLUR 4
+#define VP_LIGHTEN 5
+#define VP_DARKEN 6
+
+#define MAXINDEX 512000
+
+/* XXX */
+static void error() {}
+
+/* polling - retrieve whether cursor should be set or operator should be done */
+
+static int vp_poll(bContext *C)
+{
+ if(G.f & G_VERTEXPAINT) {
+ ScrArea *sa= CTX_wm_area(C);
+ if(sa->spacetype==SPACE_VIEW3D) {
+ ARegion *ar= CTX_wm_region(C);
+ if(ar->regiontype==RGN_TYPE_WINDOW)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int wp_poll(bContext *C)
+{
+ if(G.f & G_WEIGHTPAINT) {
+ ScrArea *sa= CTX_wm_area(C);
+ if(sa->spacetype==SPACE_VIEW3D) {
+ ARegion *ar= CTX_wm_region(C);
+ if(ar->regiontype==RGN_TYPE_WINDOW)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/* Cursors */
+static void vp_drawcursor(bContext *C, int x, int y, void *customdata)
+{
+ ToolSettings *ts= CTX_data_tool_settings(C);
+
+ glTranslatef((float)x, (float)y, 0.0f);
+
+ glColor4ub(255, 255, 255, 128);
+ glEnable( GL_LINE_SMOOTH );
+ glEnable(GL_BLEND);
+ glutil_draw_lined_arc(0.0, M_PI*2.0, ts->vpaint->size, 40);
+ glDisable(GL_BLEND);
+ glDisable( GL_LINE_SMOOTH );
+
+ glTranslatef((float)-x, (float)-y, 0.0f);
+}
+
+static void wp_drawcursor(bContext *C, int x, int y, void *customdata)
+{
+ ToolSettings *ts= CTX_data_tool_settings(C);
+
+ glTranslatef((float)x, (float)y, 0.0f);
+
+ glColor4ub(200, 200, 255, 128);
+ glEnable( GL_LINE_SMOOTH );
+ glEnable(GL_BLEND);
+ glutil_draw_lined_arc(0.0, M_PI*2.0, ts->wpaint->size, 40);
+ glDisable(GL_BLEND);
+ glDisable( GL_LINE_SMOOTH );
+
+ glTranslatef((float)-x, (float)-y, 0.0f);
+}
+
+static void toggle_paint_cursor(bContext *C, int wpaint)
+{
+ ToolSettings *ts = CTX_data_scene(C)->toolsettings;
+ VPaint *vp = wpaint ? ts->wpaint : ts->vpaint;
+
+ if(vp->paintcursor) {
+ WM_paint_cursor_end(CTX_wm_manager(C), vp->paintcursor);
+ vp->paintcursor = NULL;
+ }
+ else {
+ vp->paintcursor = wpaint ?
+ WM_paint_cursor_activate(CTX_wm_manager(C), wp_poll, wp_drawcursor, NULL) :
+ WM_paint_cursor_activate(CTX_wm_manager(C), vp_poll, vp_drawcursor, NULL);
+ }
+}
+
+static VPaint *new_vpaint(int wpaint)
+{
+ VPaint *vp= MEM_callocN(sizeof(VPaint), "VPaint");
+
+ vp->r= 1.0f;
+ vp->g= 1.0f;
+ vp->b= 1.0f;
+ vp->a= 0.2f;
+ vp->size= 25.0f;
+ vp->gamma= vp->mul= 1.0f;
+
+ vp->flag= VP_AREA+VP_SOFT+VP_SPRAY;
+
+ if(wpaint) {
+ vp->weight= 1.0f;
+ vp->a= 1.0f;
+ vp->flag= VP_AREA+VP_SOFT;
+ }
+ return vp;
+}
+
+static int *get_indexarray(void)
+{
+ return MEM_mallocN(sizeof(int)*MAXINDEX + 2, "vertexpaint");
+}
+
+
+/* in contradiction to cpack drawing colors, the MCOL colors (vpaint colors) are per byte!
+ so not endian sensitive. Mcol = ABGR!!! so be cautious with cpack calls */
+
+unsigned int rgba_to_mcol(float r, float g, float b, float a)
+{
+ int ir, ig, ib, ia;
+ unsigned int col;
+ char *cp;
+
+ ir= floor(255.0*r);
+ if(ir<0) ir= 0; else if(ir>255) ir= 255;
+ ig= floor(255.0*g);
+ if(ig<0) ig= 0; else if(ig>255) ig= 255;
+ ib= floor(255.0*b);
+ if(ib<0) ib= 0; else if(ib>255) ib= 255;
+ ia= floor(255.0*a);
+ if(ia<0) ia= 0; else if(ia>255) ia= 255;
+
+ cp= (char *)&col;
+ cp[0]= ia;
+ cp[1]= ib;
+ cp[2]= ig;
+ cp[3]= ir;
+
+ return col;
+
+}
+
+static unsigned int vpaint_get_current_col(VPaint *vp)
+{
+ return rgba_to_mcol(vp->r, vp->g, vp->b, 1.0f);
+}
+
+void do_shared_vertexcol(Mesh *me)
+{
+ /* if no mcol: do not do */
+ /* if tface: only the involved faces, otherwise all */
+ MFace *mface;
+ MTFace *tface;
+ int a;
+ short *scolmain, *scol;
+ char *mcol;
+
+ if(me->mcol==0 || me->totvert==0 || me->totface==0) return;
+
+ scolmain= MEM_callocN(4*sizeof(short)*me->totvert, "colmain");
+
+ tface= me->mtface;
+ mface= me->mface;
+ mcol= (char *)me->mcol;
+ for(a=me->totface; a>0; a--, mface++, mcol+=16) {
+ if((tface && tface->mode & TF_SHAREDCOL) || (G.f & G_FACESELECT)==0) {
+ scol= scolmain+4*mface->v1;
+ scol[0]++; scol[1]+= mcol[1]; scol[2]+= mcol[2]; scol[3]+= mcol[3];
+ scol= scolmain+4*mface->v2;
+ scol[0]++; scol[1]+= mcol[5]; scol[2]+= mcol[6]; scol[3]+= mcol[7];
+ scol= scolmain+4*mface->v3;
+ scol[0]++; scol[1]+= mcol[9]; scol[2]+= mcol[10]; scol[3]+= mcol[11];
+ if(mface->v4) {
+ scol= scolmain+4*mface->v4;
+ scol[0]++; scol[1]+= mcol[13]; scol[2]+= mcol[14]; scol[3]+= mcol[15];
+ }
+ }
+ if(tface) tface++;
+ }
+
+ a= me->totvert;
+ scol= scolmain;
+ while(a--) {
+ if(scol[0]>1) {
+ scol[1]/= scol[0];
+ scol[2]/= scol[0];
+ scol[3]/= scol[0];
+ }
+ scol+= 4;
+ }
+
+ tface= me->mtface;
+ mface= me->mface;
+ mcol= (char *)me->mcol;
+ for(a=me->totface; a>0; a--, mface++, mcol+=16) {
+ if((tface && tface->mode & TF_SHAREDCOL) || (G.f & G_FACESELECT)==0) {
+ scol= scolmain+4*mface->v1;
+ mcol[1]= scol[1]; mcol[2]= scol[2]; mcol[3]= scol[3];
+ scol= scolmain+4*mface->v2;
+ mcol[5]= scol[1]; mcol[6]= scol[2]; mcol[7]= scol[3];
+ scol= scolmain+4*mface->v3;
+ mcol[9]= scol[1]; mcol[10]= scol[2]; mcol[11]= scol[3];
+ if(mface->v4) {
+ scol= scolmain+4*mface->v4;
+ mcol[13]= scol[1]; mcol[14]= scol[2]; mcol[15]= scol[3];
+ }
+ }
+ if(tface) tface++;
+ }
+
+ MEM_freeN(scolmain);
+}
+
+void make_vertexcol(Scene *scene, int shade) /* single ob */
+{
+ Object *ob;
+ Mesh *me;
+
+ if(scene->obedit) {
+ error("Unable to perform function in Edit Mode");
+ return;
+ }
+
+ ob= OBACT;
+ if(!ob || ob->id.lib) return;
+ me= get_mesh(ob);
+ if(me==0) return;
+
+ /* copies from shadedisplist to mcol */
+ if(!me->mcol) {
+ CustomData_add_layer(&me->fdata, CD_MCOL, CD_CALLOC, NULL, me->totface);
+ mesh_update_customdata_pointers(me);
+ }
+
+ if(shade)
+ shadeMeshMCol(scene, ob, me);
+ else
+ memset(me->mcol, 255, 4*sizeof(MCol)*me->totface);
+
+// XXX if (me->mr) multires_load_cols(me);
+
+ DAG_object_flush_update(scene, ob, OB_RECALC_DATA);
+
+}
+
+static void copy_vpaint_prev(VPaint *vp, unsigned int *mcol, int tot)
+{
+ if(vp->vpaint_prev) {
+ MEM_freeN(vp->vpaint_prev);
+ vp->vpaint_prev= NULL;
+ }
+ vp->tot= tot;
+
+ if(mcol==NULL || tot==0) return;
+
+ vp->vpaint_prev= MEM_mallocN(4*sizeof(int)*tot, "vpaint_prev");
+ memcpy(vp->vpaint_prev, mcol, 4*sizeof(int)*tot);
+
+}
+
+static void copy_wpaint_prev (VPaint *wp, MDeformVert *dverts, int dcount)
+{
+ if (wp->wpaint_prev) {
+ free_dverts(wp->wpaint_prev, wp->tot);
+ wp->wpaint_prev= NULL;
+ }
+
+ if(dverts && dcount) {
+
+ wp->wpaint_prev = MEM_mallocN (sizeof(MDeformVert)*dcount, "wpaint prev");
+ wp->tot = dcount;
+ copy_dverts (wp->wpaint_prev, dverts, dcount);
+ }
+}
+
+
+void clear_vpaint(Scene *scene)
+{
+ Mesh *me;
+ Object *ob;
+ unsigned int *to, paintcol;
+ int a;
+
+ if((G.f & G_VERTEXPAINT)==0) return;
+
+ ob= OBACT;
+ me= get_mesh(ob);
+ if(!ob || ob->id.lib) return;
+
+ if(me==0 || me->mcol==0 || me->totface==0) return;
+
+ paintcol= vpaint_get_current_col(scene->toolsettings->vpaint);
+
+ to= (unsigned int *)me->mcol;
+ a= 4*me->totface;
+ while(a--) {
+ *to= paintcol;
+ to++;
+ }
+ DAG_object_flush_update(scene, ob, OB_RECALC_DATA);
+
+}
+
+void clear_vpaint_selectedfaces(Scene *scene)
+{
+ Mesh *me;
+ MFace *mf;
+ Object *ob;
+ unsigned int paintcol, *mcol;
+ int i;
+
+ ob= OBACT;
+ me= get_mesh(ob);
+ if(me==0 || me->totface==0) return;
+
+ if(!me->mcol)
+ make_vertexcol(scene, 0);
+
+ paintcol= vpaint_get_current_col(scene->toolsettings->vpaint);
+
+ mf = me->mface;
+ mcol = (unsigned int*)me->mcol;
+ for (i = 0; i < me->totface; i++, mf++, mcol+=4) {
+ if (mf->flag & ME_FACE_SEL) {
+ mcol[0] = paintcol;
+ mcol[1] = paintcol;
+ mcol[2] = paintcol;
+ mcol[3] = paintcol;
+ }
+ }
+
+ DAG_object_flush_update(scene, ob, OB_RECALC_DATA);
+}
+
+
+/* fills in the selected faces with the current weight and vertex group */
+void clear_wpaint_selectedfaces(Scene *scene)
+{
+ VPaint *wp= scene->toolsettings->wpaint;
+ float paintweight= wp->weight;
+ Mesh *me;
+ MFace *mface;
+ Object *ob;
+ MDeformWeight *dw, *uw;
+ int *indexar;
+ int index, vgroup;
+ unsigned int faceverts[5]={0,0,0,0,0};
+ unsigned char i;
+ int vgroup_mirror= -1;
+
+ ob= OBACT;
+ me= ob->data;
+ if(me==0 || me->totface==0 || me->dvert==0 || !me->mface) return;
+
+ indexar= get_indexarray();
+ for(index=0, mface=me->mface; index<me->totface; index++, mface++) {
+ if((mface->flag & ME_FACE_SEL)==0)
+ indexar[index]= 0;
+ else
+ indexar[index]= index+1;
+ }
+
+ vgroup= ob->actdef-1;
+
+ /* directly copied from weight_paint, should probaby split into a seperate function */
+ /* if mirror painting, find the other group */
+ if(wp->flag & VP_MIRROR_X) {
+ bDeformGroup *defgroup= BLI_findlink(&ob->defbase, ob->actdef-1);
+ if(defgroup) {
+ bDeformGroup *curdef;
+ int actdef= 0;
+ char name[32];
+
+ BLI_strncpy(name, defgroup->name, 32);
+ bone_flip_name(name, 0); /* 0 = don't strip off number extensions */
+
+ for (curdef = ob->defbase.first; curdef; curdef=curdef->next, actdef++)
+ if (!strcmp(curdef->name, name))
+ break;
+ if(curdef==NULL) {
+ int olddef= ob->actdef; /* tsk, add_defgroup sets the active defgroup */
+ curdef= add_defgroup_name (ob, name);
+ ob->actdef= olddef;
+ }
+
+ if(curdef && curdef!=defgroup)
+ vgroup_mirror= actdef;
+ }
+ }
+ /* end copy from weight_paint*/
+
+ copy_wpaint_prev(wp, me->dvert, me->totvert);
+
+ for(index=0; index<me->totface; index++) {
+ if(indexar[index] && indexar[index]<=me->totface) {
+ mface= me->mface + (indexar[index]-1);
+ /* just so we can loop through the verts */
+ faceverts[0]= mface->v1;
+ faceverts[1]= mface->v2;
+ faceverts[2]= mface->v3;
+ faceverts[3]= mface->v4;
+ for (i=0; i<3 || faceverts[i]; i++) {
+ if(!((me->dvert+faceverts[i])->flag)) {
+ dw= verify_defweight(me->dvert+faceverts[i], vgroup);
+ if(dw) {
+ uw= verify_defweight(wp->wpaint_prev+faceverts[i], vgroup);
+ uw->weight= dw->weight; /* set the undo weight */
+ dw->weight= paintweight;
+
+ if(wp->flag & VP_MIRROR_X) { /* x mirror painting */
+ int j= mesh_get_x_mirror_vert(ob, faceverts[i]);
+ if(j>=0) {
+ /* copy, not paint again */
+ if(vgroup_mirror != -1) {
+ dw= verify_defweight(me->dvert+j, vgroup_mirror);
+ uw= verify_defweight(wp->wpaint_prev+j, vgroup_mirror);
+ } else {
+ dw= verify_defweight(me->dvert+j, vgroup);
+ uw= verify_defweight(wp->wpaint_prev+j, vgroup);
+ }
+ uw->weight= dw->weight; /* set the undo weight */
+ dw->weight= paintweight;
+ }
+ }
+ }
+ (me->dvert+faceverts[i])->flag= 1;
+ }
+ }
+ }
+ }
+
+ index=0;
+ while (index<me->totvert) {
+ (me->dvert+index)->flag= 0;
+ index++;
+ }
+
+ MEM_freeN(indexar);
+ copy_wpaint_prev(wp, NULL, 0);
+
+ DAG_object_flush_update(scene, ob, OB_RECALC_DATA);
+}
+
+
+void vpaint_dogamma(Scene *scene)
+{
+ VPaint *vp= scene->toolsettings->vpaint;
+ Mesh *me;
+ Object *ob;
+ float igam, fac;
+ int a, temp;
+ unsigned char *cp, gamtab[256];
+
+ if((G.f & G_VERTEXPAINT)==0) return;
+
+ ob= OBACT;
+ me= get_mesh(ob);
+ if(me==0 || me->mcol==0 || me->totface==0) return;
+
+ igam= 1.0/vp->gamma;
+ for(a=0; a<256; a++) {
+
+ fac= ((float)a)/255.0;
+ fac= vp->mul*pow( fac, igam);
+
+ temp= 255.9*fac;
+
+ if(temp<=0) gamtab[a]= 0;
+ else if(temp>=255) gamtab[a]= 255;
+ else gamtab[a]= temp;
+ }
+
+ a= 4*me->totface;
+ cp= (unsigned char *)me->mcol;
+ while(a--) {
+
+ cp[1]= gamtab[ cp[1] ];
+ cp[2]= gamtab[ cp[2] ];
+ cp[3]= gamtab[ cp[3] ];
+
+ cp+= 4;
+ }
+}
+
+static unsigned int mcol_blend(unsigned int col1, unsigned int col2, int fac)
+{
+ char *cp1, *cp2, *cp;
+ int mfac;
+ unsigned int col=0;
+
+ if(fac==0) return col1;
+ if(fac>=255) return col2;
+
+ mfac= 255-fac;
+
+ cp1= (char *)&col1;
+ cp2= (char *)&col2;
+ cp= (char *)&col;
+
+ cp[0]= 255;
+ cp[1]= (mfac*cp1[1]+fac*cp2[1])/255;
+ cp[2]= (mfac*cp1[2]+fac*cp2[2])/255;
+ cp[3]= (mfac*cp1[3]+fac*cp2[3])/255;
+
+ return col;
+}
+
+static unsigned int mcol_add(unsigned int col1, unsigned int col2, int fac)
+{
+ char *cp1, *cp2, *cp;
+ int temp;
+ unsigned int col=0;
+
+ if(fac==0) return col1;
+
+ cp1= (char *)&col1;
+ cp2= (char *)&col2;
+ cp= (char *)&col;
+
+ cp[0]= 255;
+ temp= cp1[1] + ((fac*cp2[1])/255);
+ if(temp>254) cp[1]= 255; else cp[1]= temp;
+ temp= cp1[2] + ((fac*cp2[2])/255);
+ if(temp>254) cp[2]= 255; else cp[2]= temp;
+ temp= cp1[3] + ((fac*cp2[3])/255);
+ if(temp>254) cp[3]= 255; else cp[3]= temp;
+
+ return col;
+}
+
+static unsigned int mcol_sub(unsigned int col1, unsigned int col2, int fac)
+{
+ char *cp1, *cp2, *cp;
+ int temp;
+ unsigned int col=0;
+
+ if(fac==0) return col1;
+
+ cp1= (char *)&col1;
+ cp2= (char *)&col2;
+ cp= (char *)&col;
+
+ cp[0]= 255;
+ temp= cp1[1] - ((fac*cp2[1])/255);
+ if(temp<0) cp[1]= 0; else cp[1]= temp;
+ temp= cp1[2] - ((fac*cp2[2])/255);
+ if(temp<0) cp[2]= 0; else cp[2]= temp;
+ temp= cp1[3] - ((fac*cp2[3])/255);
+ if(temp<0) cp[3]= 0; else cp[3]= temp;
+
+ return col;
+}
+
+static unsigned int mcol_mul(unsigned int col1, unsigned int col2, int fac)
+{
+ char *cp1, *cp2, *cp;
+ int mfac;
+ unsigned int col=0;
+
+ if(fac==0) return col1;
+
+ mfac= 255-fac;
+
+ cp1= (char *)&col1;
+ cp2= (char *)&col2;
+ cp= (char *)&col;
+
+ /* first mul, then blend the fac */
+ cp[0]= 255;
+ cp[1]= (mfac*cp1[1] + fac*((cp2[1]*cp1[1])/255) )/255;
+ cp[2]= (mfac*cp1[2] + fac*((cp2[2]*cp1[2])/255) )/255;
+ cp[3]= (mfac*cp1[3] + fac*((cp2[3]*cp1[3])/255) )/255;
+
+
+ return col;
+}
+
+static unsigned int mcol_lighten(unsigned int col1, unsigned int col2, int fac)
+{
+ char *cp1, *cp2, *cp;
+ int mfac;
+ unsigned int col=0;
+
+ if(fac==0) return col1;
+ if(fac>=255) return col2;
+
+ mfac= 255-fac;
+
+ cp1= (char *)&col1;
+ cp2= (char *)&col2;
+ cp= (char *)&col;
+
+ /* See if are lighter, if so mix, else dont do anything.
+ if the paint col is darker then the original, then ignore */
+ if (cp1[1]+cp1[2]+cp1[3] > cp2[1]+cp2[2]+cp2[3])
+ return col1;
+
+ cp[0]= 255;
+ cp[1]= (mfac*cp1[1]+fac*cp2[1])/255;
+ cp[2]= (mfac*cp1[2]+fac*cp2[2])/255;
+ cp[3]= (mfac*cp1[3]+fac*cp2[3])/255;
+
+ return col;
+}
+
+static unsigned int mcol_darken(unsigned int col1, unsigned int col2, int fac)
+{
+ char *cp1, *cp2, *cp;
+ int mfac;
+ unsigned int col=0;
+
+ if(fac==0) return col1;
+ if(fac>=255) return col2;
+
+ mfac= 255-fac;
+
+ cp1= (char *)&col1;
+ cp2= (char *)&col2;
+ cp= (char *)&col;
+
+ /* See if were darker, if so mix, else dont do anything.
+ if the paint col is brighter then the original, then ignore */
+ if (cp1[1]+cp1[2]+cp1[3] < cp2[1]+cp2[2]+cp2[3])
+ return col1;
+
+ cp[0]= 255;
+ cp[1]= (mfac*cp1[1]+fac*cp2[1])/255;
+ cp[2]= (mfac*cp1[2]+fac*cp2[2])/255;
+ cp[3]= (mfac*cp1[3]+fac*cp2[3])/255;
+ return col;
+}
+
+static void vpaint_blend(VPaint *vp, unsigned int *col, unsigned int *colorig, unsigned int paintcol, int alpha)
+{
+
+ if(vp->mode==VP_MIX || vp->mode==VP_BLUR) *col= mcol_blend( *col, paintcol, alpha);
+ else if(vp->mode==VP_ADD) *col= mcol_add( *col, paintcol, alpha);
+ else if(vp->mode==VP_SUB) *col= mcol_sub( *col, paintcol, alpha);
+ else if(vp->mode==VP_MUL) *col= mcol_mul( *col, paintcol, alpha);
+ else if(vp->mode==VP_LIGHTEN) *col= mcol_lighten( *col, paintcol, alpha);
+ else if(vp->mode==VP_DARKEN) *col= mcol_darken( *col, paintcol, alpha);
+
+ /* if no spray, clip color adding with colorig & orig alpha */
+ if((vp->flag & VP_SPRAY)==0) {
+ unsigned int testcol=0, a;
+ char *cp, *ct, *co;
+
+ alpha= (int)(255.0*vp->a);
+
+ if(vp->mode==VP_MIX || vp->mode==VP_BLUR) testcol= mcol_blend( *colorig, paintcol, alpha);
+ else if(vp->mode==VP_ADD) testcol= mcol_add( *colorig, paintcol, alpha);
+ else if(vp->mode==VP_SUB) testcol= mcol_sub( *colorig, paintcol, alpha);
+ else if(vp->mode==VP_MUL) testcol= mcol_mul( *colorig, paintcol, alpha);
+ else if(vp->mode==VP_LIGHTEN) testcol= mcol_lighten( *colorig, paintcol, alpha);
+ else if(vp->mode==VP_DARKEN) testcol= mcol_darken( *colorig, paintcol, alpha);
+
+ cp= (char *)col;
+ ct= (char *)&testcol;
+ co= (char *)colorig;
+
+ for(a=0; a<4; a++) {
+ if( ct[a]<co[a] ) {
+ if( cp[a]<ct[a] ) cp[a]= ct[a];
+ else if( cp[a]>co[a] ) cp[a]= co[a];
+ }
+ else {
+ if( cp[a]<co[a] ) cp[a]= co[a];
+ else if( cp[a]>ct[a] ) cp[a]= ct[a];
+ }
+ }
+ }
+}
+
+
+static int sample_backbuf_area(ViewContext *vc, int *indexar, int totface, int x, int y, float size)
+{
+ struct ImBuf *ibuf;
+ int a, tot=0, index;
+
+ if(totface+4>=MAXINDEX) return 0;
+
+ if(size>64.0) size= 64.0;
+
+ ibuf= view3d_read_backbuf(vc, x-size, y-size, x+size, y+size);
+ if(ibuf) {
+ unsigned int *rt= ibuf->rect;
+
+ memset(indexar, 0, sizeof(int)*totface+4); /* plus 2! first element is total, +2 was giving valgrind errors, +4 seems ok */
+
+ size= ibuf->x*ibuf->y;
+ while(size--) {
+
+ if(*rt) {
+ index= WM_framebuffer_to_index(*rt);
+ if(index>0 && index<=totface)
+ indexar[index] = 1;
+ }
+
+ rt++;
+ }
+
+ for(a=1; a<=totface; a++) {
+ if(indexar[a]) indexar[tot++]= a;
+ }
+
+ IMB_freeImBuf(ibuf);
+ }
+
+ return tot;
+}
+
+static int calc_vp_alpha_dl(VPaint *vp, ViewContext *vc, float vpimat[][3], float *vert_nor, short *mval)
+{
+ float fac, dx, dy;
+ int alpha;
+ short vertco[2];
+
+ if(vp->flag & VP_SOFT) {
+ project_short_noclip(vc->ar, vert_nor, vertco);
+ dx= mval[0]-vertco[0];
+ dy= mval[1]-vertco[1];
+
+ fac= sqrt(dx*dx + dy*dy);
+ if(fac > vp->size) return 0;
+ if(vp->flag & VP_HARD)
+ alpha= 255;
+ else
+ alpha= 255.0*vp->a*(1.0-fac/vp->size);
+ }
+ else {
+ alpha= 255.0*vp->a;
+ }
+
+ if(vp->flag & VP_NORMALS) {
+ float *no= vert_nor+3;
+
+ /* transpose ! */
+ fac= vpimat[2][0]*no[0]+vpimat[2][1]*no[1]+vpimat[2][2]*no[2];
+ if(fac>0.0) {
+ dx= vpimat[0][0]*no[0]+vpimat[0][1]*no[1]+vpimat[0][2]*no[2];
+ dy= vpimat[1][0]*no[0]+vpimat[1][1]*no[1]+vpimat[1][2]*no[2];
+
+ alpha*= fac/sqrt(dx*dx + dy*dy + fac*fac);
+ }
+ else return 0;
+ }
+
+ return alpha;
+}
+
+static void wpaint_blend(VPaint *wp, MDeformWeight *dw, MDeformWeight *uw, float alpha, float paintval)
+{
+
+ if(dw==NULL || uw==NULL) return;
+
+ if(wp->mode==VP_MIX || wp->mode==VP_BLUR)
+ dw->weight = paintval*alpha + dw->weight*(1.0-alpha);
+ else if(wp->mode==VP_ADD)
+ dw->weight += paintval*alpha;
+ else if(wp->mode==VP_SUB)
+ dw->weight -= paintval*alpha;
+ else if(wp->mode==VP_MUL)
+ /* first mul, then blend the fac */
+ dw->weight = ((1.0-alpha) + alpha*paintval)*dw->weight;
+ else if(wp->mode==VP_LIGHTEN) {
+ if (dw->weight < paintval)
+ dw->weight = paintval*alpha + dw->weight*(1.0-alpha);
+ } else if(wp->mode==VP_DARKEN) {
+ if (dw->weight > paintval)
+ dw->weight = paintval*alpha + dw->weight*(1.0-alpha);
+ }
+ CLAMP(dw->weight, 0.0f, 1.0f);
+
+ /* if no spray, clip result with orig weight & orig alpha */
+ if((wp->flag & VP_SPRAY)==0) {
+ float testw=0.0f;
+
+ alpha= wp->a;
+ if(wp->mode==VP_MIX || wp->mode==VP_BLUR)
+ testw = paintval*alpha + uw->weight*(1.0-alpha);
+ else if(wp->mode==VP_ADD)
+ testw = uw->weight + paintval*alpha;
+ else if(wp->mode==VP_SUB)
+ testw = uw->weight - paintval*alpha;
+ else if(wp->mode==VP_MUL)
+ /* first mul, then blend the fac */
+ testw = ((1.0-alpha) + alpha*paintval)*uw->weight;
+ else if(wp->mode==VP_LIGHTEN) {
+ if (uw->weight < paintval)
+ testw = paintval*alpha + uw->weight*(1.0-alpha);
+ else
+ testw = uw->weight;
+ } else if(wp->mode==VP_DARKEN) {
+ if (uw->weight > paintval)
+ testw = paintval*alpha + uw->weight*(1.0-alpha);
+ else
+ testw = uw->weight;
+ }
+ CLAMP(testw, 0.0f, 1.0f);
+
+ if( testw<uw->weight ) {
+ if(dw->weight < testw) dw->weight= testw;
+ else if(dw->weight > uw->weight) dw->weight= uw->weight;
+ }
+ else {
+ if(dw->weight > testw) dw->weight= testw;
+ else if(dw->weight < uw->weight) dw->weight= uw->weight;
+ }
+ }
+
+}
+
+/* ----------------------------------------------------- */
+
+/* used for 3d view, on active object, assumes me->dvert exists */
+/* if mode==1: */
+/* samples cursor location, and gives menu with vertex groups to activate */
+/* else */
+/* sets wp->weight to the closest weight value to vertex */
+/* note: we cant sample frontbuf, weight colors are interpolated too unpredictable */
+void sample_wpaint(Scene *scene, ARegion *ar, View3D *v3d, int mode)
+{
+ ViewContext vc;
+ VPaint *wp= scene->toolsettings->wpaint;
+ Object *ob= OBACT;
+ Mesh *me= get_mesh(ob);
+ int index;
+ short mval[2], sco[2];
+
+ if (!me) return;
+
+// getmouseco_areawin(mval);
+ index= view3d_sample_backbuf(&vc, mval[0], mval[1]);
+
+ if(index && index<=me->totface) {
+ MFace *mface;
+
+ mface= ((MFace *)me->mface) + index-1;
+
+ if(mode==1) { /* sampe which groups are in here */
+ MDeformVert *dv;
+ int a, totgroup;
+
+ totgroup= BLI_countlist(&ob->defbase);
+ if(totgroup) {
+ int totmenu=0;
+ int *groups=MEM_callocN(totgroup*sizeof(int), "groups");
+
+ dv= me->dvert+mface->v1;
+ for(a=0; a<dv->totweight; a++) {
+ if (dv->dw[a].def_nr<totgroup)
+ groups[dv->dw[a].def_nr]= 1;
+ }
+ dv= me->dvert+mface->v2;
+ for(a=0; a<dv->totweight; a++) {
+ if (dv->dw[a].def_nr<totgroup)
+ groups[dv->dw[a].def_nr]= 1;
+ }
+ dv= me->dvert+mface->v3;
+ for(a=0; a<dv->totweight; a++) {
+ if (dv->dw[a].def_nr<totgroup)
+ groups[dv->dw[a].def_nr]= 1;
+ }
+ if(mface->v4) {
+ dv= me->dvert+mface->v4;
+ for(a=0; a<dv->totweight; a++) {
+ if (dv->dw[a].def_nr<totgroup)
+ groups[dv->dw[a].def_nr]= 1;
+ }
+ }
+ for(a=0; a<totgroup; a++)
+ if(groups[a]) totmenu++;
+
+ if(totmenu==0) {
+ //notice("No Vertex Group Selected");
+ }
+ else {
+ bDeformGroup *dg;
+ short val;
+ char item[40], *str= MEM_mallocN(40*totmenu+40, "menu");
+
+ strcpy(str, "Vertex Groups %t");
+ for(a=0, dg=ob->defbase.first; dg && a<totgroup; a++, dg= dg->next) {
+ if(groups[a]) {
+ sprintf(item, "|%s %%x%d", dg->name, a);
+ strcat(str, item);
+ }
+ }
+
+ val= 0; // XXX pupmenu(str);
+ if(val>=0) {
+ ob->actdef= val+1;
+ DAG_object_flush_update(scene, ob, OB_RECALC_DATA);
+ }
+ MEM_freeN(str);
+ }
+ MEM_freeN(groups);
+ }
+// else notice("No Vertex Groups in Object");
+ }
+ else {
+ DerivedMesh *dm;
+ MDeformWeight *dw;
+ float w1, w2, w3, w4, co[3], fac;
+
+ dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH);
+ if(dm->getVertCo==NULL) {
+ //notice("Not supported yet");
+ }
+ else {
+ /* calc 3 or 4 corner weights */
+ dm->getVertCo(dm, mface->v1, co);
+ project_short_noclip(ar, co, sco);
+ w1= ((mval[0]-sco[0])*(mval[0]-sco[0]) + (mval[1]-sco[1])*(mval[1]-sco[1]));
+
+ dm->getVertCo(dm, mface->v2, co);
+ project_short_noclip(ar, co, sco);
+ w2= ((mval[0]-sco[0])*(mval[0]-sco[0]) + (mval[1]-sco[1])*(mval[1]-sco[1]));
+
+ dm->getVertCo(dm, mface->v3, co);
+ project_short_noclip(ar, co, sco);
+ w3= ((mval[0]-sco[0])*(mval[0]-sco[0]) + (mval[1]-sco[1])*(mval[1]-sco[1]));
+
+ if(mface->v4) {
+ dm->getVertCo(dm, mface->v4, co);
+ project_short_noclip(ar, co, sco);
+ w4= ((mval[0]-sco[0])*(mval[0]-sco[0]) + (mval[1]-sco[1])*(mval[1]-sco[1]));
+ }
+ else w4= 1.0e10;
+
+ fac= MIN4(w1, w2, w3, w4);
+ if(w1==fac) {
+ dw= get_defweight(me->dvert+mface->v1, ob->actdef-1);
+ if(dw) wp->weight= dw->weight; else wp->weight= 0.0f;
+ }
+ else if(w2==fac) {
+ dw= get_defweight(me->dvert+mface->v2, ob->actdef-1);
+ if(dw) wp->weight= dw->weight; else wp->weight= 0.0f;
+ }
+ else if(w3==fac) {
+ dw= get_defweight(me->dvert+mface->v3, ob->actdef-1);
+ if(dw) wp->weight= dw->weight; else wp->weight= 0.0f;
+ }
+ else if(w4==fac) {
+ if(mface->v4) {
+ dw= get_defweight(me->dvert+mface->v4, ob->actdef-1);
+ if(dw) wp->weight= dw->weight; else wp->weight= 0.0f;
+ }
+ }
+ }
+ dm->release(dm);
+ }
+
+ }
+
+}
+
+static void do_weight_paint_vertex(VPaint *wp, Object *ob, int index, int alpha, float paintweight, int vgroup_mirror)
+{
+ Mesh *me= ob->data;
+ MDeformWeight *dw, *uw;
+ int vgroup= ob->actdef-1;
+
+ if(wp->flag & VP_ONLYVGROUP) {
+ dw= get_defweight(me->dvert+index, vgroup);
+ uw= get_defweight(wp->wpaint_prev+index, vgroup);
+ }
+ else {
+ dw= verify_defweight(me->dvert+index, vgroup);
+ uw= verify_defweight(wp->wpaint_prev+index, vgroup);
+ }
+ if(dw==NULL || uw==NULL)
+ return;
+
+ wpaint_blend(wp, dw, uw, (float)alpha/255.0, paintweight);
+
+ if(wp->flag & VP_MIRROR_X) { /* x mirror painting */
+ int j= mesh_get_x_mirror_vert(ob, index);
+ if(j>=0) {
+ /* copy, not paint again */
+ if(vgroup_mirror != -1)
+ uw= verify_defweight(me->dvert+j, vgroup_mirror);
+ else
+ uw= verify_defweight(me->dvert+j, vgroup);
+
+ uw->weight= dw->weight;
+ }
+ }
+}
+
+
+/* *************** set wpaint operator ****************** */
+
+static int set_wpaint(bContext *C, wmOperator *op) /* toggle */
+{
+ Object *ob= CTX_data_active_object(C);
+ Scene *scene= CTX_data_scene(C);
+ VPaint *wp= scene->toolsettings->wpaint;
+ Mesh *me;
+
+ me= get_mesh(ob);
+ if(ob->id.lib || me==NULL) return OPERATOR_CANCELLED;
+
+ if(me && me->totface>=MAXINDEX) {
+ error("Maximum number of faces: %d", MAXINDEX-1);
+ G.f &= ~G_WEIGHTPAINT;
+ return OPERATOR_CANCELLED;
+ }
+
+ if(G.f & G_WEIGHTPAINT) G.f &= ~G_WEIGHTPAINT;
+ else G.f |= G_WEIGHTPAINT;
+
+
+ /* Weightpaint works by overriding colors in mesh,
+ * so need to make sure we recalc on enter and
+ * exit (exit needs doing regardless because we
+ * should redeform).
+ */
+ DAG_object_flush_update(scene, ob, OB_RECALC_DATA);
+
+ if(G.f & G_WEIGHTPAINT) {
+ Object *par;
+
+ if(wp==NULL)
+ wp= scene->toolsettings->wpaint= new_vpaint(1);
+
+ toggle_paint_cursor(C, 1);
+
+ mesh_octree_table(ob, NULL, NULL, 's');
+
+ /* verify if active weight group is also active bone */
+ par= modifiers_isDeformedByArmature(ob);
+ if(par && (par->flag & OB_POSEMODE)) {
+ bPoseChannel *pchan;
+ for(pchan= par->pose->chanbase.first; pchan; pchan= pchan->next)
+ if(pchan->bone->flag & BONE_ACTIVE)
+ break;
+ if(pchan)
+ vertexgroup_select_by_name(ob, pchan->name);
+ }
+ }
+ else {
+ if(wp)
+ toggle_paint_cursor(C, 1);
+
+ mesh_octree_table(ob, NULL, NULL, 'e');
+ }
+
+ WM_event_add_notifier(C, NC_SCENE|ND_MODE, scene);
+
+ return OPERATOR_FINISHED;
+}
+
+/* for switching to/from mode */
+static int paint_poll_test(bContext *C)
+{
+ if(ED_operator_view3d_active(C)==0)
+ return 0;
+ if(CTX_data_edit_object(C))
+ return 0;
+ if(CTX_data_active_object(C)==NULL)
+ return 0;
+ return 1;
+}
+
+void PAINT_OT_weight_paint_toggle(wmOperatorType *ot)
+{
+
+ /* identifiers */
+ ot->name= "Weight Paint Mode";
+ ot->idname= "PAINT_OT_weight_paint_toggle";
+
+ /* api callbacks */
+ ot->exec= set_wpaint;
+ ot->poll= paint_poll_test;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+}
+
+/* ************ paint radial controls *************/
+
+void paint_radial_control_invoke(wmOperator *op, VPaint *vp)
+{
+ int mode = RNA_int_get(op->ptr, "mode");
+ float original_value;
+
+ if(mode == WM_RADIALCONTROL_SIZE)
+ original_value = vp->size;
+ else if(mode == WM_RADIALCONTROL_STRENGTH)
+ original_value = vp->a;
+
+ RNA_float_set(op->ptr, "initial_value", original_value);
+}
+
+static int paint_radial_control_exec(wmOperator *op, VPaint *vp)
+{
+ int mode = RNA_int_get(op->ptr, "mode");
+ float new_value = RNA_float_get(op->ptr, "new_value");
+
+ if(mode == WM_RADIALCONTROL_SIZE)
+ vp->size = new_value;
+ else if(mode == WM_RADIALCONTROL_STRENGTH)
+ vp->a = new_value;
+
+ return OPERATOR_FINISHED;
+}
+
+static int vpaint_radial_control_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ toggle_paint_cursor(C, 0);
+ paint_radial_control_invoke(op, CTX_data_scene(C)->toolsettings->vpaint);
+ return WM_radial_control_invoke(C, op, event);
+}
+
+static int vpaint_radial_control_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+ int ret = WM_radial_control_modal(C, op, event);
+ if(ret != OPERATOR_RUNNING_MODAL)
+ toggle_paint_cursor(C, 0);
+ return ret;
+}
+
+static int vpaint_radial_control_exec(bContext *C, wmOperator *op)
+{
+ int ret = paint_radial_control_exec(op, CTX_data_scene(C)->toolsettings->vpaint);
+ char str[256];
+ WM_radial_control_string(op, str, 256);
+ return ret;
+}
+
+static int wpaint_radial_control_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ toggle_paint_cursor(C, 1);
+ paint_radial_control_invoke(op, CTX_data_scene(C)->toolsettings->wpaint);
+ return WM_radial_control_invoke(C, op, event);
+}
+
+static int wpaint_radial_control_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+ int ret = WM_radial_control_modal(C, op, event);
+ if(ret != OPERATOR_RUNNING_MODAL)
+ toggle_paint_cursor(C, 1);
+ return ret;
+}
+
+static int wpaint_radial_control_exec(bContext *C, wmOperator *op)
+{
+ int ret = paint_radial_control_exec(op, CTX_data_scene(C)->toolsettings->wpaint);
+ char str[256];
+ WM_radial_control_string(op, str, 256);
+ return ret;
+}
+
+void PAINT_OT_weight_paint_radial_control(wmOperatorType *ot)
+{
+ WM_OT_radial_control_partial(ot);
+
+ ot->name= "Weight Paint Radial Control";
+ ot->idname= "PAINT_OT_weight_paint_radial_control";
+
+ ot->invoke= wpaint_radial_control_invoke;
+ ot->modal= wpaint_radial_control_modal;
+ ot->exec= wpaint_radial_control_exec;
+ ot->poll= wp_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+void PAINT_OT_vertex_paint_radial_control(wmOperatorType *ot)
+{
+ WM_OT_radial_control_partial(ot);
+
+ ot->name= "Vertex Paint Radial Control";
+ ot->idname= "PAINT_OT_vertex_paint_radial_control";
+
+ ot->invoke= vpaint_radial_control_invoke;
+ ot->modal= vpaint_radial_control_modal;
+ ot->exec= vpaint_radial_control_exec;
+ ot->poll= vp_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/* ************ weight paint operator ********** */
+
+struct WPaintData {
+ ViewContext vc;
+ int *indexar;
+ int vgroup_mirror;
+ float *vertexcosnos;
+ float wpimat[3][3];
+};
+
+static void wpaint_exit(bContext *C, wmOperator *op)
+{
+ ToolSettings *ts= CTX_data_tool_settings(C);
+ Object *ob= CTX_data_active_object(C);
+ struct WPaintData *wpd= op->customdata;
+
+ if(wpd->vertexcosnos)
+ MEM_freeN(wpd->vertexcosnos);
+ MEM_freeN(wpd->indexar);
+
+ /* frees prev buffer */
+ copy_wpaint_prev(ts->wpaint, NULL, 0);
+
+ /* and particles too */
+ if(ob->particlesystem.first) {
+ ParticleSystem *psys;
+ int i;
+
+ for(psys= ob->particlesystem.first; psys; psys= psys->next) {
+ for(i=0; i<PSYS_TOT_VG; i++) {
+ if(psys->vgroup[i]==ob->actdef) {
+ psys->recalc |= PSYS_RECALC_HAIR;
+ break;
+ }
+ }
+ }
+ }
+
+ DAG_object_flush_update(CTX_data_scene(C), ob, OB_RECALC_DATA);
+
+ MEM_freeN(wpd);
+ op->customdata= NULL;
+}
+
+
+static int wpaint_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+ ToolSettings *ts= CTX_data_tool_settings(C);
+ VPaint *wp= ts->wpaint;
+
+ switch(event->type) {
+ case LEFTMOUSE:
+ if(event->val==0) { /* release */
+ wpaint_exit(C, op);
+ return OPERATOR_FINISHED;
+ }
+ /* pass on, first press gets painted too */
+
+ case MOUSEMOVE:
+ {
+ struct WPaintData *wpd= op->customdata;
+ ViewContext *vc= &wpd->vc;
+ Object *ob= vc->obact;
+ Mesh *me= ob->data;
+ float mat[4][4];
+ float paintweight= wp->weight;
+ int *indexar= wpd->indexar;
+ int totindex, index, alpha, totw;
+ short mval[2];
+
+ view3d_operator_needs_opengl(C);
+
+ /* load projection matrix */
+ wmMultMatrix(ob->obmat);
+ wmGetSingleMatrix(mat);
+ wmLoadMatrix(wpd->vc.rv3d->viewmat);
+
+ MTC_Mat4SwapMat4(wpd->vc.rv3d->persmat, mat);
+
+ mval[0]= event->x - vc->ar->winrct.xmin;
+ mval[1]= event->y - vc->ar->winrct.ymin;
+
+ /* which faces are involved */
+ if(wp->flag & VP_AREA) {
+ totindex= sample_backbuf_area(vc, indexar, me->totface, mval[0], mval[1], wp->size);
+ }
+ else {
+ indexar[0]= view3d_sample_backbuf(vc, mval[0], mval[1]);
+ if(indexar[0]) totindex= 1;
+ else totindex= 0;
+ }
+
+ if(wp->flag & VP_COLINDEX) {
+ for(index=0; index<totindex; index++) {
+ if(indexar[index] && indexar[index]<=me->totface) {
+ MFace *mface= ((MFace *)me->mface) + (indexar[index]-1);
+
+ if(mface->mat_nr!=ob->actcol-1) {
+ indexar[index]= 0;
+ }
+ }
+ }
+ }
+
+ if((G.f & G_FACESELECT) && me->mface) {
+ for(index=0; index<totindex; index++) {
+ if(indexar[index] && indexar[index]<=me->totface) {
+ MFace *mface= ((MFace *)me->mface) + (indexar[index]-1);
+
+ if((mface->flag & ME_FACE_SEL)==0) {
+ indexar[index]= 0;
+ }
+ }
+ }
+ }
+
+ /* make sure each vertex gets treated only once */
+ /* and calculate filter weight */
+ totw= 0;
+ if(wp->mode==VP_BLUR)
+ paintweight= 0.0f;
+ else
+ paintweight= wp->weight;
+
+ for(index=0; index<totindex; index++) {
+ if(indexar[index] && indexar[index]<=me->totface) {
+ MFace *mface= me->mface + (indexar[index]-1);
+
+ (me->dvert+mface->v1)->flag= 1;
+ (me->dvert+mface->v2)->flag= 1;
+ (me->dvert+mface->v3)->flag= 1;
+ if(mface->v4) (me->dvert+mface->v4)->flag= 1;
+
+ if(wp->mode==VP_BLUR) {
+ MDeformWeight *dw, *(*dw_func)(MDeformVert *, int) = verify_defweight;
+
+ if(wp->flag & VP_ONLYVGROUP)
+ dw_func= get_defweight;
+
+ dw= dw_func(me->dvert+mface->v1, ob->actdef-1);
+ if(dw) {paintweight+= dw->weight; totw++;}
+ dw= dw_func(me->dvert+mface->v2, ob->actdef-1);
+ if(dw) {paintweight+= dw->weight; totw++;}
+ dw= dw_func(me->dvert+mface->v3, ob->actdef-1);
+ if(dw) {paintweight+= dw->weight; totw++;}
+ if(mface->v4) {
+ dw= dw_func(me->dvert+mface->v4, ob->actdef-1);
+ if(dw) {paintweight+= dw->weight; totw++;}
+ }
+ }
+ }
+ }
+
+ if(wp->mode==VP_BLUR)
+ paintweight/= (float)totw;
+
+ for(index=0; index<totindex; index++) {
+
+ if(indexar[index] && indexar[index]<=me->totface) {
+ MFace *mface= me->mface + (indexar[index]-1);
+
+ if((me->dvert+mface->v1)->flag) {
+ alpha= calc_vp_alpha_dl(wp, vc, wpd->wpimat, wpd->vertexcosnos+6*mface->v1, mval);
+ if(alpha) {
+ do_weight_paint_vertex(wp, ob, mface->v1, alpha, paintweight, wpd->vgroup_mirror);
+ }
+ (me->dvert+mface->v1)->flag= 0;
+ }
+
+ if((me->dvert+mface->v2)->flag) {
+ alpha= calc_vp_alpha_dl(wp, vc, wpd->wpimat, wpd->vertexcosnos+6*mface->v2, mval);
+ if(alpha) {
+ do_weight_paint_vertex(wp, ob, mface->v2, alpha, paintweight, wpd->vgroup_mirror);
+ }
+ (me->dvert+mface->v2)->flag= 0;
+ }
+
+ if((me->dvert+mface->v3)->flag) {
+ alpha= calc_vp_alpha_dl(wp, vc, wpd->wpimat, wpd->vertexcosnos+6*mface->v3, mval);
+ if(alpha) {
+ do_weight_paint_vertex(wp, ob, mface->v3, alpha, paintweight, wpd->vgroup_mirror);
+ }
+ (me->dvert+mface->v3)->flag= 0;
+ }
+
+ if((me->dvert+mface->v4)->flag) {
+ if(mface->v4) {
+ alpha= calc_vp_alpha_dl(wp, vc, wpd->wpimat, wpd->vertexcosnos+6*mface->v4, mval);
+ if(alpha) {
+ do_weight_paint_vertex(wp, ob, mface->v4, alpha, paintweight, wpd->vgroup_mirror);
+ }
+ (me->dvert+mface->v4)->flag= 0;
+ }
+ }
+ }
+ }
+
+ MTC_Mat4SwapMat4(vc->rv3d->persmat, mat);
+
+ DAG_object_flush_update(vc->scene, ob, OB_RECALC_DATA);
+ ED_region_tag_redraw(vc->ar);
+ }
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int wpaint_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ Scene *scene= CTX_data_scene(C);
+ ToolSettings *ts= CTX_data_tool_settings(C);
+ VPaint *wp= ts->wpaint;
+ Object *ob= CTX_data_active_object(C);
+ struct WPaintData *wpd;
+ Mesh *me;
+ float mat[4][4], imat[4][4];
+
+ if(scene->obedit) return OPERATOR_CANCELLED;
+ // XXX if(multires_level1_test()) return;
+
+ me= get_mesh(ob);
+ if(me==NULL || me->totface==0) return OPERATOR_CANCELLED;
+
+ /* if nothing was added yet, we make dverts and a vertex deform group */
+ if (!me->dvert)
+ create_dverts(&me->id);
+
+ /* make customdata storage */
+ op->customdata= wpd= MEM_callocN(sizeof(struct WPaintData), "WPaintData");
+ view3d_set_viewcontext(C, &wpd->vc);
+ wpd->vgroup_mirror= -1;
+
+ // if(qual & LR_CTRLKEY) {
+ // sample_wpaint(scene, ar, v3d, 0);
+ // return;
+ // }
+ // if(qual & LR_SHIFTKEY) {
+ // sample_wpaint(scene, ar, v3d, 1);
+ // return;
+ // }
+
+ /* ALLOCATIONS! no return after this line */
+ /* painting on subsurfs should give correct points too, this returns me->totvert amount */
+ wpd->vertexcosnos= mesh_get_mapped_verts_nors(scene, ob);
+ wpd->indexar= get_indexarray();
+ copy_wpaint_prev(wp, me->dvert, me->totvert);
+
+ /* this happens on a Bone select, when no vgroup existed yet */
+ if(ob->actdef<=0) {
+ Object *modob;
+ if((modob = modifiers_isDeformedByArmature(ob))) {
+ bPoseChannel *pchan;
+ for(pchan= modob->pose->chanbase.first; pchan; pchan= pchan->next)
+ if(pchan->bone->flag & SELECT)
+ break;
+ if(pchan) {
+ bDeformGroup *dg= get_named_vertexgroup(ob, pchan->name);
+ if(dg==NULL)
+ dg= add_defgroup_name(ob, pchan->name); /* sets actdef */
+ else
+ ob->actdef= get_defgroup_num(ob, dg);
+ }
+ }
+ }
+ if(ob->defbase.first==NULL) {
+ add_defgroup(ob);
+ }
+
+ // if(ob->lay & v3d->lay); else error("Active object is not in this layer");
+
+ /* imat for normals */
+ Mat4MulMat4(mat, ob->obmat, wpd->vc.rv3d->viewmat);
+ Mat4Invert(imat, mat);
+ Mat3CpyMat4(wpd->wpimat, imat);
+
+ /* if mirror painting, find the other group */
+ if(wp->flag & VP_MIRROR_X) {
+ bDeformGroup *defgroup= BLI_findlink(&ob->defbase, ob->actdef-1);
+ if(defgroup) {
+ bDeformGroup *curdef;
+ int actdef= 0;
+ char name[32];
+
+ BLI_strncpy(name, defgroup->name, 32);
+ bone_flip_name(name, 0); /* 0 = don't strip off number extensions */
+
+ for (curdef = ob->defbase.first; curdef; curdef=curdef->next, actdef++)
+ if (!strcmp(curdef->name, name))
+ break;
+ if(curdef==NULL) {
+ int olddef= ob->actdef; /* tsk, add_defgroup sets the active defgroup */
+ curdef= add_defgroup_name (ob, name);
+ ob->actdef= olddef;
+ }
+
+ if(curdef && curdef!=defgroup)
+ wpd->vgroup_mirror= actdef;
+ }
+ }
+
+ /* do paint once for click only paint */
+ wpaint_modal(C, op, event);
+
+ /* add modal handler */
+ WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+void PAINT_OT_weight_paint(wmOperatorType *ot)
+{
+
+ /* identifiers */
+ ot->name= "Weight Paint";
+ ot->idname= "PAINT_OT_weight_paint";
+
+ /* api callbacks */
+ ot->invoke= wpaint_invoke;
+ ot->modal= wpaint_modal;
+ /* ot->exec= vpaint_exec; <-- needs stroke property */
+ ot->poll= wp_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+}
+
+/* ************ set / clear vertex paint mode ********** */
+
+
+static int set_vpaint(bContext *C, wmOperator *op) /* toggle */
+{
+ Object *ob= CTX_data_active_object(C);
+ Scene *scene= CTX_data_scene(C);
+ VPaint *vp= scene->toolsettings->vpaint;
+ Mesh *me;
+
+ me= get_mesh(ob);
+
+ if(me==NULL || object_data_is_libdata(ob)) {
+ G.f &= ~G_VERTEXPAINT;
+ return OPERATOR_FINISHED;
+ }
+
+ if(me && me->totface>=MAXINDEX) {
+ error("Maximum number of faces: %d", MAXINDEX-1);
+ G.f &= ~G_VERTEXPAINT;
+ return OPERATOR_FINISHED;
+ }
+
+ if(me && me->mcol==NULL) make_vertexcol(scene, 0);
+
+ /* toggle: end vpaint */
+ if(G.f & G_VERTEXPAINT) {
+
+ G.f &= ~G_VERTEXPAINT;
+
+ if(vp) {
+ toggle_paint_cursor(C, 0);
+ vp->paintcursor= NULL;
+ }
+ }
+ else {
+
+ G.f |= G_VERTEXPAINT;
+ /* Turn off weight painting */
+ if (G.f & G_WEIGHTPAINT)
+ set_wpaint(C, op);
+
+ if(vp==NULL)
+ vp= scene->toolsettings->vpaint= new_vpaint(0);
+
+ toggle_paint_cursor(C, 0);
+ }
+
+ if (me)
+ /* update modifier stack for mapping requirements */
+ DAG_object_flush_update(scene, ob, OB_RECALC_DATA);
+
+ WM_event_add_notifier(C, NC_SCENE|ND_MODE, scene);
+
+ return OPERATOR_FINISHED;
+}
+
+void PAINT_OT_vertex_paint_toggle(wmOperatorType *ot)
+{
+
+ /* identifiers */
+ ot->name= "Vertex Paint Mode";
+ ot->idname= "PAINT_OT_vertex_paint_toggle";
+
+ /* api callbacks */
+ ot->exec= set_vpaint;
+ ot->poll= paint_poll_test;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+}
+
+
+
+/* ********************** vertex paint operator ******************* */
+
+/* Implementation notes:
+
+Operator->invoke()
+ - validate context (add mcol)
+ - create customdata storage
+ - call paint once (mouse click)
+ - add modal handler
+
+Operator->modal()
+ - for every mousemove, apply vertex paint
+ - exit on mouse release, free customdata
+ (return OPERATOR_FINISHED also removes handler and operator)
+
+For future:
+ - implement a stroke event (or mousemove with past positons)
+ - revise whether op->customdata should be added in object, in set_vpaint
+
+*/
+
+struct VPaintData {
+ ViewContext vc;
+ unsigned int paintcol;
+ int *indexar;
+ float *vertexcosnos;
+ float vpimat[3][3];
+};
+
+static void vpaint_exit(bContext *C, wmOperator *op)
+{
+ ToolSettings *ts= CTX_data_tool_settings(C);
+ struct VPaintData *vpd= op->customdata;
+
+ if(vpd->vertexcosnos)
+ MEM_freeN(vpd->vertexcosnos);
+ MEM_freeN(vpd->indexar);
+
+ /* frees prev buffer */
+ copy_vpaint_prev(ts->vpaint, NULL, 0);
+
+ MEM_freeN(vpd);
+ op->customdata= NULL;
+}
+
+static int vpaint_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+ ToolSettings *ts= CTX_data_tool_settings(C);
+ VPaint *vp= ts->vpaint;
+
+ switch(event->type) {
+ case LEFTMOUSE:
+ if(event->val==0) { /* release */
+ vpaint_exit(C, op);
+ return OPERATOR_FINISHED;
+ }
+ /* pass on, first press gets painted too */
+
+ case MOUSEMOVE:
+ {
+ struct VPaintData *vpd= op->customdata;
+ ViewContext *vc= &vpd->vc;
+ Object *ob= vc->obact;
+ Mesh *me= ob->data;
+ float mat[4][4];
+ int *indexar= vpd->indexar;
+ int totindex, index;
+ short mval[2];
+
+ view3d_operator_needs_opengl(C);
+
+ /* load projection matrix */
+ wmMultMatrix(ob->obmat);
+ wmGetSingleMatrix(mat);
+ wmLoadMatrix(vc->rv3d->viewmat);
+
+ mval[0]= event->x - vc->ar->winrct.xmin;
+ mval[1]= event->y - vc->ar->winrct.ymin;
+
+ /* which faces are involved */
+ if(vp->flag & VP_AREA) {
+ totindex= sample_backbuf_area(vc, indexar, me->totface, mval[0], mval[1], vp->size);
+ }
+ else {
+ indexar[0]= view3d_sample_backbuf(vc, mval[0], mval[1]);
+ if(indexar[0]) totindex= 1;
+ else totindex= 0;
+ }
+
+ MTC_Mat4SwapMat4(vc->rv3d->persmat, mat);
+
+ if(vp->flag & VP_COLINDEX) {
+ for(index=0; index<totindex; index++) {
+ if(indexar[index] && indexar[index]<=me->totface) {
+ MFace *mface= ((MFace *)me->mface) + (indexar[index]-1);
+
+ if(mface->mat_nr!=ob->actcol-1) {
+ indexar[index]= 0;
+ }
+ }
+ }
+ }
+ if((G.f & G_FACESELECT) && me->mface) {
+ for(index=0; index<totindex; index++) {
+ if(indexar[index] && indexar[index]<=me->totface) {
+ MFace *mface= ((MFace *)me->mface) + (indexar[index]-1);
+
+ if((mface->flag & ME_FACE_SEL)==0)
+ indexar[index]= 0;
+ }
+ }
+ }
+
+ for(index=0; index<totindex; index++) {
+
+ if(indexar[index] && indexar[index]<=me->totface) {
+ MFace *mface= ((MFace *)me->mface) + (indexar[index]-1);
+ unsigned int *mcol= ( (unsigned int *)me->mcol) + 4*(indexar[index]-1);
+ unsigned int *mcolorig= ( (unsigned int *)vp->vpaint_prev) + 4*(indexar[index]-1);
+ int alpha;
+
+ if(vp->mode==VP_BLUR) {
+ unsigned int fcol1= mcol_blend( mcol[0], mcol[1], 128);
+ if(mface->v4) {
+ unsigned int fcol2= mcol_blend( mcol[2], mcol[3], 128);
+ vpd->paintcol= mcol_blend( fcol1, fcol2, 128);
+ }
+ else {
+ vpd->paintcol= mcol_blend( mcol[2], fcol1, 170);
+ }
+
+ }
+
+ alpha= calc_vp_alpha_dl(vp, vc, vpd->vpimat, vpd->vertexcosnos+6*mface->v1, mval);
+ if(alpha) vpaint_blend(vp, mcol, mcolorig, vpd->paintcol, alpha);
+
+ alpha= calc_vp_alpha_dl(vp, vc, vpd->vpimat, vpd->vertexcosnos+6*mface->v2, mval);
+ if(alpha) vpaint_blend(vp, mcol+1, mcolorig+1, vpd->paintcol, alpha);
+
+ alpha= calc_vp_alpha_dl(vp, vc, vpd->vpimat, vpd->vertexcosnos+6*mface->v3, mval);
+ if(alpha) vpaint_blend(vp, mcol+2, mcolorig+2, vpd->paintcol, alpha);
+
+ if(mface->v4) {
+ alpha= calc_vp_alpha_dl(vp, vc, vpd->vpimat, vpd->vertexcosnos+6*mface->v4, mval);
+ if(alpha) vpaint_blend(vp, mcol+3, mcolorig+3, vpd->paintcol, alpha);
+ }
+ }
+ }
+
+ MTC_Mat4SwapMat4(vc->rv3d->persmat, mat);
+
+ do_shared_vertexcol(me);
+
+ ED_region_tag_redraw(vc->ar);
+
+ DAG_object_flush_update(vc->scene, ob, OB_RECALC_DATA);
+ }
+ break;
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int vpaint_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ ToolSettings *ts= CTX_data_tool_settings(C);
+ VPaint *vp= ts->vpaint;
+ struct VPaintData *vpd;
+ Object *ob= CTX_data_active_object(C);
+ Mesh *me;
+ float mat[4][4], imat[4][4];
+
+ /* context checks could be a poll() */
+ me= get_mesh(ob);
+ if(me==NULL || me->totface==0) return OPERATOR_CANCELLED;
+
+ if(me->mcol==NULL) make_vertexcol(CTX_data_scene(C), 0);
+ if(me->mcol==NULL) return OPERATOR_CANCELLED;
+
+ /* make customdata storage */
+ op->customdata= vpd= MEM_callocN(sizeof(struct VPaintData), "VPaintData");
+ view3d_set_viewcontext(C, &vpd->vc);
+
+ vpd->vertexcosnos= mesh_get_mapped_verts_nors(vpd->vc.scene, ob);
+ vpd->indexar= get_indexarray();
+ vpd->paintcol= vpaint_get_current_col(vp);
+
+ /* for filtering */
+ copy_vpaint_prev(vp, (unsigned int *)me->mcol, me->totface);
+
+ /* some old cruft to sort out later */
+ Mat4MulMat4(mat, ob->obmat, vpd->vc.rv3d->viewmat);
+ Mat4Invert(imat, mat);
+ Mat3CpyMat4(vpd->vpimat, imat);
+
+ /* do paint once for click only paint */
+ vpaint_modal(C, op, event);
+
+ /* add modal handler */
+ WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+void PAINT_OT_vertex_paint(wmOperatorType *ot)
+{
+
+ /* identifiers */
+ ot->name= "Vertex Paint";
+ ot->idname= "PAINT_OT_vertex_paint";
+
+ /* api callbacks */
+ ot->invoke= vpaint_invoke;
+ ot->modal= vpaint_modal;
+ /* ot->exec= vpaint_exec; <-- needs stroke property */
+ ot->poll= vp_poll;
+
+ /* flags */
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+}
+
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
new file mode 100644
index 00000000000..f3d29989bb8
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -0,0 +1,2176 @@
+/*
+ * $Id: sculptmode.c 18309 2009-01-04 07:47:11Z nicholasbishop $
+ *
+ * ***** 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) 2006 by Nicholas Bishop
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ * Implements the Sculpt Mode tools
+ *
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_arithb.h"
+#include "BLI_blenlib.h"
+#include "BLI_dynstr.h"
+
+#include "DNA_armature_types.h"
+#include "DNA_brush_types.h"
+#include "DNA_image_types.h"
+#include "DNA_key_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_texture_types.h"
+#include "DNA_view3d_types.h"
+#include "DNA_userdef_types.h"
+#include "DNA_color_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_context.h"
+#include "BKE_customdata.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_depsgraph.h"
+#include "BKE_global.h"
+#include "BKE_image.h"
+#include "BKE_key.h"
+#include "BKE_library.h"
+#include "BKE_main.h"
+#include "BKE_mesh.h"
+#include "BKE_modifier.h"
+#include "BKE_multires.h"
+#include "BKE_sculpt.h"
+#include "BKE_texture.h"
+#include "BKE_utildefines.h"
+#include "BKE_colortools.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+#include "ED_screen.h"
+#include "ED_sculpt.h"
+#include "ED_space_api.h"
+#include "ED_util.h"
+#include "ED_view3d.h"
+#include "sculpt_intern.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "IMB_imbuf_types.h"
+
+#include "RE_render_ext.h"
+#include "RE_shader_ext.h" /*for multitex_ext*/
+
+#include "GPU_draw.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Number of vertices to average in order to determine the flatten distance */
+#define FLATTEN_SAMPLE_SIZE 10
+
+/* ===== STRUCTS =====
+ *
+ */
+
+/* ActiveData stores an Index into the mvert array of Mesh, plus Fade, which
+ stores how far the vertex is from the brush center, scaled to the range [0,1]. */
+typedef struct ActiveData {
+ struct ActiveData *next, *prev;
+ unsigned int Index;
+ float Fade;
+ float dist;
+} ActiveData;
+
+typedef enum StrokeFlags {
+ CLIP_X = 1,
+ CLIP_Y = 2,
+ CLIP_Z = 4
+} StrokeFlags;
+
+/* Cache stroke properties. Used because
+ RNA property lookup isn't particularly fast.
+
+ For descriptions of these settings, check the operator properties.
+*/
+typedef struct StrokeCache {
+ /* Invariants */
+ float radius;
+ float scale[3];
+ int flag;
+ float clip_tolerance[3];
+ int initial_mouse[2];
+ float depth;
+
+ /* Variants */
+ float true_location[3];
+ float location[3];
+ float flip;
+ float pressure;
+ int mouse[2];
+
+ /* The rest is temporary storage that isn't saved as a property */
+
+ int first_time; /* Beginning of stroke may do some things special */
+
+ ViewContext vc;
+ bglMats *mats;
+
+ float *layer_disps; /* Displacements for each vertex */
+ float (*mesh_store)[3]; /* Copy of the mesh vertices' locations */
+ short (*orig_norms)[3]; /* Copy of the mesh vertices' normals */
+ float rotation; /* Texture rotation (radians) for anchored and rake modes */
+ int pixel_radius, previous_pixel_radius;
+ ListBase grab_active_verts[8]; /* The same list of verts is used throught grab stroke */
+ float grab_delta[3], grab_delta_symmetry[3];
+ float old_grab_location[3];
+ int symmetry; /* Symmetry index between 0 and 7 */
+ float view_normal[3], view_normal_symmetry[3];
+ int last_dot[2]; /* Last location of stroke application */
+ int last_rake[2]; /* Last location of updating rake rotation */
+} StrokeCache;
+
+typedef struct RectNode {
+ struct RectNode *next, *prev;
+ rcti r;
+} RectNode;
+
+/* Used to store to 2D screen coordinates of each vertex in the mesh. */
+typedef struct ProjVert {
+ short co[2];
+
+ /* Used to mark whether a vertex is inside a rough bounding box
+ containing the brush. */
+ char inside;
+} ProjVert;
+
+/* ===== OPENGL =====
+ *
+ * Simple functions to get data from the GL
+ */
+
+/* Uses window coordinates (x,y) and depth component z to find a point in
+ modelspace */
+static void unproject(bglMats *mats, float out[3], const short x, const short y, const float z)
+{
+ double ux, uy, uz;
+
+ gluUnProject(x,y,z, mats->modelview, mats->projection,
+ (GLint *)mats->viewport, &ux, &uy, &uz );
+ out[0] = ux;
+ out[1] = uy;
+ out[2] = uz;
+}
+
+/* Convert a point in model coordinates to 2D screen coordinates. */
+static void projectf(bglMats *mats, const float v[3], float p[2])
+{
+ double ux, uy, uz;
+
+ gluProject(v[0],v[1],v[2], mats->modelview, mats->projection,
+ (GLint *)mats->viewport, &ux, &uy, &uz);
+ p[0]= ux;
+ p[1]= uy;
+}
+
+static void project(bglMats *mats, const float v[3], short p[2])
+{
+ float f[2];
+ projectf(mats, v, f);
+
+ p[0]= f[0];
+ p[1]= f[1];
+}
+
+/* ===== Sculpting =====
+ *
+ */
+
+/* Return modified brush size. Uses current tablet pressure (if available) to
+ shrink the brush. Skipped for grab brush because only the first mouse down
+ size is used, which is small if the user has just touched the pen to the
+ tablet */
+static char brush_size(Sculpt *sd)
+{
+ float size= sd->brush->size;
+
+ if((sd->brush->sculpt_tool != SCULPT_TOOL_GRAB) && (sd->brush->flag & BRUSH_SIZE_PRESSURE))
+ size *= sd->session->cache->pressure;
+
+ return size;
+}
+
+/* Return modified brush strength. Includes the direction of the brush, positive
+ values pull vertices, negative values push. Uses tablet pressure and a
+ special multiplier found experimentally to scale the strength factor. */
+static float brush_strength(Sculpt *sd, StrokeCache *cache)
+{
+ float dir= sd->brush->flag & BRUSH_DIR_IN ? -1 : 1;
+ float pressure= 1;
+ float flip= cache->flip ? -1:1;
+ float anchored = sd->brush->flag & BRUSH_ANCHORED ? 25 : 1;
+
+ if(sd->brush->flag & BRUSH_ALPHA_PRESSURE)
+ pressure *= cache->pressure;
+
+ switch(sd->brush->sculpt_tool){
+ case SCULPT_TOOL_DRAW:
+ case SCULPT_TOOL_LAYER:
+ return sd->brush->alpha / 50.0f * dir * pressure * flip * anchored; /*XXX: not sure why? multiplied by G.vd->grid */;
+ case SCULPT_TOOL_SMOOTH:
+ return sd->brush->alpha / .5 * pressure * anchored;
+ case SCULPT_TOOL_PINCH:
+ return sd->brush->alpha / 10.0f * dir * pressure * flip * anchored;
+ case SCULPT_TOOL_GRAB:
+ return 1;
+ case SCULPT_TOOL_INFLATE:
+ return sd->brush->alpha / 50.0f * dir * pressure * flip * anchored;
+ case SCULPT_TOOL_FLATTEN:
+ return sd->brush->alpha / 5.0f * pressure * anchored;
+ default:
+ return 0;
+ }
+}
+
+/* For clipping against a mirror modifier */
+static void sculpt_clip(StrokeCache *cache, float *co, const float val[3])
+{
+ int i;
+ for(i=0; i<3; ++i) {
+ if((cache->flag & (CLIP_X << i)) && (fabs(co[i]) <= cache->clip_tolerance[i]))
+ co[i]= 0.0f;
+ else
+ co[i]= val[i];
+ }
+}
+
+static void sculpt_axislock(Sculpt *sd, float *co)
+{
+ if(sd->flags == (SCULPT_LOCK_X|SCULPT_LOCK_Y|SCULPT_LOCK_Z))
+ return;
+
+ if(sd->session->cache->vc.v3d->twmode == V3D_MANIP_LOCAL) {
+ float mat[3][3], imat[3][3];
+ Mat3CpyMat4(mat, sd->session->cache->vc.obact->obmat);
+ Mat3Inv(imat, mat);
+ Mat3MulVecfl(mat, co);
+ if (sd->flags & SCULPT_LOCK_X) co[0] = 0.0;
+ if (sd->flags & SCULPT_LOCK_Y) co[1] = 0.0;
+ if (sd->flags & SCULPT_LOCK_Z) co[2] = 0.0;
+ Mat3MulVecfl(imat, co);
+ } else {
+ if (sd->flags & SCULPT_LOCK_X) co[0] = 0.0;
+ if (sd->flags & SCULPT_LOCK_Y) co[1] = 0.0;
+ if (sd->flags & SCULPT_LOCK_Z) co[2] = 0.0;
+ }
+}
+
+static void add_norm_if(float view_vec[3], float out[3], float out_flip[3], const short no[3])
+{
+ float fno[3] = {no[0], no[1], no[2]};
+
+ Normalize(fno);
+
+ if((Inpf(view_vec, fno)) > 0) {
+ VecAddf(out, out, fno);
+ } else {
+ VecAddf(out_flip, out_flip, fno); /* out_flip is used when out is {0,0,0} */
+ }
+}
+
+/* Currently only for the draw brush; finds average normal for all active
+ vertices */
+static void calc_area_normal(Sculpt *sd, float out[3], const ListBase* active_verts)
+{
+ StrokeCache *cache = sd->session->cache;
+ ActiveData *node = active_verts->first;
+ const int view = 0; /* XXX: should probably be a flag, not number: sd->brush_type==SCULPT_TOOL_DRAW ? sculptmode_brush()->view : 0; */
+ float out_flip[3];
+ float *out_dir = cache->view_normal_symmetry;
+
+ out[0]=out[1]=out[2] = out_flip[0]=out_flip[1]=out_flip[2] = 0;
+
+ if(sd->brush->flag & BRUSH_ANCHORED) {
+ for(; node; node = node->next)
+ add_norm_if(out_dir, out, out_flip, cache->orig_norms[node->Index]);
+ }
+ else {
+ for(; node; node = node->next)
+ add_norm_if(out_dir, out, out_flip, sd->session->mvert[node->Index].no);
+ }
+
+ if (out[0]==0.0 && out[1]==0.0 && out[2]==0.0) {
+ VECCOPY(out, out_flip);
+ }
+
+ Normalize(out);
+
+ if(out_dir) {
+ out[0] = out_dir[0] * view + out[0] * (10-view);
+ out[1] = out_dir[1] * view + out[1] * (10-view);
+ out[2] = out_dir[2] * view + out[2] * (10-view);
+ }
+
+ Normalize(out);
+}
+
+static void do_draw_brush(Sculpt *sd, SculptSession *ss, const ListBase* active_verts)
+{
+ float area_normal[3];
+ ActiveData *node= active_verts->first;
+
+ calc_area_normal(sd, area_normal, active_verts);
+
+ sculpt_axislock(sd, area_normal);
+
+ while(node){
+ float *co= ss->mvert[node->Index].co;
+
+ const float val[3]= {co[0]+area_normal[0]*node->Fade*ss->cache->scale[0],
+ co[1]+area_normal[1]*node->Fade*ss->cache->scale[1],
+ co[2]+area_normal[2]*node->Fade*ss->cache->scale[2]};
+
+ sculpt_clip(ss->cache, co, val);
+
+ node= node->next;
+ }
+}
+
+/* For the smooth brush, uses the neighboring vertices around vert to calculate
+ a smoothed location for vert. Skips corner vertices (used by only one
+ polygon.) */
+static void neighbor_average(SculptSession *ss, float avg[3], const int vert)
+{
+ int i, skip= -1, total=0;
+ IndexNode *node= ss->fmap[vert].first;
+ char ncount= BLI_countlist(&ss->fmap[vert]);
+ MFace *f;
+
+ avg[0] = avg[1] = avg[2] = 0;
+
+ /* Don't modify corner vertices */
+ if(ncount==1) {
+ VecCopyf(avg, ss->mvert[vert].co);
+ return;
+ }
+
+ while(node){
+ f= &ss->mface[node->index];
+
+ if(f->v4) {
+ skip= (f->v1==vert?2:
+ f->v2==vert?3:
+ f->v3==vert?0:
+ f->v4==vert?1:-1);
+ }
+
+ for(i=0; i<(f->v4?4:3); ++i) {
+ if(i != skip && (ncount!=2 || BLI_countlist(&ss->fmap[(&f->v1)[i]]) <= 2)) {
+ VecAddf(avg, avg, ss->mvert[(&f->v1)[i]].co);
+ ++total;
+ }
+ }
+
+ node= node->next;
+ }
+
+ if(total>0)
+ VecMulf(avg, 1.0f / total);
+ else
+ VecCopyf(avg, ss->mvert[vert].co);
+}
+
+static void do_smooth_brush(SculptSession *ss, const ListBase* active_verts)
+{
+ ActiveData *node= active_verts->first;
+
+ while(node){
+ float *co= ss->mvert[node->Index].co;
+ float avg[3], val[3];
+
+ neighbor_average(ss, avg, node->Index);
+ val[0] = co[0]+(avg[0]-co[0])*node->Fade;
+ val[1] = co[1]+(avg[1]-co[1])*node->Fade;
+ val[2] = co[2]+(avg[2]-co[2])*node->Fade;
+
+ sculpt_clip(ss->cache, co, val);
+ node= node->next;
+ }
+}
+
+static void do_pinch_brush(SculptSession *ss, const ListBase* active_verts)
+{
+ ActiveData *node= active_verts->first;
+
+ while(node) {
+ float *co= ss->mvert[node->Index].co;
+ const float val[3]= {co[0]+(ss->cache->location[0]-co[0])*node->Fade,
+ co[1]+(ss->cache->location[1]-co[1])*node->Fade,
+ co[2]+(ss->cache->location[2]-co[2])*node->Fade};
+ sculpt_clip(ss->cache, co, val);
+ node= node->next;
+ }
+}
+
+static void do_grab_brush(Sculpt *sd, SculptSession *ss)
+{
+ ActiveData *node= ss->cache->grab_active_verts[ss->cache->symmetry].first;
+ float add[3];
+ float grab_delta[3];
+
+ VecCopyf(grab_delta, ss->cache->grab_delta_symmetry);
+ sculpt_axislock(sd, grab_delta);
+
+ while(node) {
+ float *co= ss->mvert[node->Index].co;
+
+ VecCopyf(add, grab_delta);
+ VecMulf(add, node->Fade);
+ VecAddf(add, add, co);
+ sculpt_clip(ss->cache, co, add);
+
+ node= node->next;
+ }
+
+}
+
+static void do_layer_brush(Sculpt *sd, SculptSession *ss, const ListBase *active_verts)
+{
+ float area_normal[3];
+ ActiveData *node= active_verts->first;
+ const float bstr= brush_strength(sd, ss->cache);
+
+ calc_area_normal(sd, area_normal, active_verts);
+
+ while(node){
+ float *disp= &ss->cache->layer_disps[node->Index];
+
+ if((bstr > 0 && *disp < bstr) ||
+ (bstr < 0 && *disp > bstr)) {
+ float *co= ss->mvert[node->Index].co;
+
+ *disp+= node->Fade;
+
+ if(bstr < 0) {
+ if(*disp < bstr)
+ *disp = bstr;
+ } else {
+ if(*disp > bstr)
+ *disp = bstr;
+ }
+
+ {
+ const float val[3]= {ss->cache->mesh_store[node->Index][0]+area_normal[0] * *disp*ss->cache->scale[0],
+ ss->cache->mesh_store[node->Index][1]+area_normal[1] * *disp*ss->cache->scale[1],
+ ss->cache->mesh_store[node->Index][2]+area_normal[2] * *disp*ss->cache->scale[2]};
+ sculpt_clip(ss->cache, co, val);
+ }
+ }
+
+ node= node->next;
+ }
+}
+
+static void do_inflate_brush(SculptSession *ss, const ListBase *active_verts)
+{
+ ActiveData *node= active_verts->first;
+ float add[3];
+
+ while(node) {
+ float *co= ss->mvert[node->Index].co;
+ short *no= ss->mvert[node->Index].no;
+
+ add[0]= no[0]/ 32767.0f;
+ add[1]= no[1]/ 32767.0f;
+ add[2]= no[2]/ 32767.0f;
+ VecMulf(add, node->Fade);
+ add[0]*= ss->cache->scale[0];
+ add[1]*= ss->cache->scale[1];
+ add[2]*= ss->cache->scale[2];
+ VecAddf(add, add, co);
+
+ sculpt_clip(ss->cache, co, add);
+
+ node= node->next;
+ }
+}
+
+static void calc_flatten_center(SculptSession *ss, ActiveData *node, float co[3])
+{
+ ActiveData *outer[FLATTEN_SAMPLE_SIZE];
+ int i;
+
+ for(i = 0; i < FLATTEN_SAMPLE_SIZE; ++i)
+ outer[i] = node;
+
+ for(; node; node = node->next) {
+ for(i = 0; i < FLATTEN_SAMPLE_SIZE; ++i) {
+ if(node->dist > outer[i]->dist) {
+ outer[i] = node;
+ break;
+ }
+ }
+ }
+
+ co[0] = co[1] = co[2] = 0.0f;
+ for(i = 0; i < FLATTEN_SAMPLE_SIZE; ++i)
+ VecAddf(co, co, ss->mvert[outer[i]->Index].co);
+ VecMulf(co, 1.0f / FLATTEN_SAMPLE_SIZE);
+}
+
+static void do_flatten_brush(Sculpt *sd, SculptSession *ss, const ListBase *active_verts)
+{
+ ActiveData *node= active_verts->first;
+ /* area_normal and cntr define the plane towards which vertices are squashed */
+ float area_normal[3];
+ float cntr[3];
+
+ calc_area_normal(sd, area_normal, active_verts);
+ calc_flatten_center(ss, node, cntr);
+
+ while(node){
+ float *co= ss->mvert[node->Index].co;
+ float p1[3], sub1[3], sub2[3], intr[3], val[3];
+
+ /* Find the intersection between squash-plane and vertex (along the area normal) */
+ VecSubf(p1, co, area_normal);
+ VecSubf(sub1, cntr, p1);
+ VecSubf(sub2, co, p1);
+ VecSubf(intr, co, p1);
+ VecMulf(intr, Inpf(area_normal, sub1) / Inpf(area_normal, sub2));
+ VecAddf(intr, intr, p1);
+
+ VecSubf(val, intr, co);
+ VecMulf(val, node->Fade);
+ VecAddf(val, val, co);
+
+ sculpt_clip(ss->cache, co, val);
+
+ node= node->next;
+ }
+}
+
+
+/* Uses symm to selectively flip any axis of a coordinate. */
+static void flip_coord(float out[3], float in[3], const char symm)
+{
+ if(symm & SCULPT_SYMM_X)
+ out[0]= -in[0];
+ else
+ out[0]= in[0];
+ if(symm & SCULPT_SYMM_Y)
+ out[1]= -in[1];
+ else
+ out[1]= in[1];
+ if(symm & SCULPT_SYMM_Z)
+ out[2]= -in[2];
+ else
+ out[2]= in[2];
+}
+
+/* Get a pixel from the texcache at (px, py) */
+static unsigned char get_texcache_pixel(const SculptSession *ss, int px, int py)
+{
+ unsigned *p;
+ p = ss->texcache + py * ss->texcache_side + px;
+ return ((unsigned char*)(p))[0];
+}
+
+static float get_texcache_pixel_bilinear(const SculptSession *ss, float u, float v)
+{
+ int x, y, x2, y2;
+ const int tc_max = ss->texcache_side - 1;
+ float urat, vrat, uopp;
+
+ if(u < 0) u = 0;
+ else if(u >= ss->texcache_side) u = tc_max;
+ if(v < 0) v = 0;
+ else if(v >= ss->texcache_side) v = tc_max;
+
+ x = floor(u);
+ y = floor(v);
+ x2 = x + 1;
+ y2 = y + 1;
+
+ if(x2 > ss->texcache_side) x2 = tc_max;
+ if(y2 > ss->texcache_side) y2 = tc_max;
+
+ urat = u - x;
+ vrat = v - y;
+ uopp = 1 - urat;
+
+ return ((get_texcache_pixel(ss, x, y) * uopp +
+ get_texcache_pixel(ss, x2, y) * urat) * (1 - vrat) +
+ (get_texcache_pixel(ss, x, y2) * uopp +
+ get_texcache_pixel(ss, x2, y2) * urat) * vrat) / 255.0;
+}
+
+/* Return a multiplier for brush strength on a particular vertex. */
+static float tex_strength(Sculpt *sd, float *point, const float len)
+{
+ SculptSession *ss= sd->session;
+ Brush *br = sd->brush;
+ float avg= 1;
+
+ if(br->texact==-1 || !br->mtex[br->texact])
+ avg= 1;
+ else if(br->tex_mode==BRUSH_TEX_3D) {
+ /* Get strength by feeding the vertex location directly
+ into a texture */
+ float jnk;
+ const float factor= 0.01;
+ MTex mtex;
+ memset(&mtex,0,sizeof(MTex));
+ mtex.tex= br->mtex[br->texact]->tex;
+ mtex.projx= 1;
+ mtex.projy= 2;
+ mtex.projz= 3;
+ VecCopyf(mtex.size, br->mtex[br->texact]->size);
+ VecMulf(mtex.size, factor);
+ if(!sd->texsep)
+ mtex.size[1]= mtex.size[2]= mtex.size[0];
+
+ externtex(&mtex,point,&avg,&jnk,&jnk,&jnk,&jnk);
+ }
+ else if(ss->texcache) {
+ const float bsize= ss->cache->pixel_radius * 2;
+ const float rot= sd->brush->rot + ss->cache->rotation;
+ int px, py;
+ float flip[3], point_2d[2];
+
+ /* If the active area is being applied for symmetry, flip it
+ across the symmetry axis in order to project it. This insures
+ that the brush texture will be oriented correctly. */
+ VecCopyf(flip, point);
+ flip_coord(flip, flip, ss->cache->symmetry);
+ projectf(ss->cache->mats, flip, point_2d);
+
+ /* For Tile and Drag modes, get the 2D screen coordinates of the
+ and scale them up or down to the texture size. */
+ if(br->tex_mode==BRUSH_TEX_TILE) {
+ const int sx= (const int)br->mtex[br->texact]->size[0];
+ const int sy= (const int)sd->texsep ? br->mtex[br->texact]->size[1] : sx;
+
+ float fx= point_2d[0];
+ float fy= point_2d[1];
+
+ float angle= atan2(fy, fx) - rot;
+ float flen= sqrtf(fx*fx + fy*fy);
+
+ if(rot<0.001 && rot>-0.001) {
+ px= point_2d[0];
+ py= point_2d[1];
+ } else {
+ px= flen * cos(angle) + 2000;
+ py= flen * sin(angle) + 2000;
+ }
+ if(sx != 1)
+ px %= sx-1;
+ if(sy != 1)
+ py %= sy-1;
+ avg= get_texcache_pixel_bilinear(ss, ss->texcache_side*px/sx, ss->texcache_side*py/sy);
+ } else {
+ float fx= (point_2d[0] - ss->cache->mouse[0]) / bsize;
+ float fy= (point_2d[1] - ss->cache->mouse[1]) / bsize;
+
+ float angle= atan2(fy, fx) - rot;
+ float flen= sqrtf(fx*fx + fy*fy);
+
+ fx = flen * cos(angle) + 0.5;
+ fy = flen * sin(angle) + 0.5;
+
+ avg= get_texcache_pixel_bilinear(ss, fx * ss->texcache_side, fy * ss->texcache_side);
+ }
+ }
+
+ avg*= brush_curve_strength(sd->brush, len, ss->cache->radius); /* Falloff curve */
+
+ return avg;
+}
+
+/* Mark area around the brush as damaged. projverts are marked if they are
+ inside the area and the damaged rectangle in 2D screen coordinates is
+ added to damaged_rects. */
+static void sculpt_add_damaged_rect(SculptSession *ss)
+{
+ short p[2];
+ RectNode *rn= MEM_mallocN(sizeof(RectNode),"RectNode");
+ const float radius = MAX2(ss->cache->pixel_radius, ss->cache->previous_pixel_radius);
+ unsigned i;
+
+ /* Find center */
+ project(ss->cache->mats, ss->cache->location, p);
+ rn->r.xmin= p[0] - radius;
+ rn->r.ymin= p[1] - radius;
+ rn->r.xmax= p[0] + radius;
+ rn->r.ymax= p[1] + radius;
+
+ BLI_addtail(&ss->damaged_rects, rn);
+
+ /* Update insides */
+ for(i=0; i<ss->totvert; ++i) {
+ if(!ss->projverts[i].inside) {
+ if(ss->projverts[i].co[0] > rn->r.xmin && ss->projverts[i].co[1] > rn->r.ymin &&
+ ss->projverts[i].co[0] < rn->r.xmax && ss->projverts[i].co[1] < rn->r.ymax) {
+ ss->projverts[i].inside= 1;
+ }
+ }
+ // XXX: remember to fix this!
+ // temporary pass
+ ss->projverts[i].inside = 1;
+ }
+}
+
+/* Clears the depth buffer in each modified area. */
+#if 0
+static void sculpt_clear_damaged_areas(SculptSession *ss)
+{
+ RectNode *rn= NULL;
+
+ for(rn = ss->damaged_rects.first; rn; rn = rn->next) {
+ rcti clp = rn->r;
+ rcti *win = NULL; /*XXX: &curarea->winrct; */
+
+ clp.xmin += win->xmin;
+ clp.xmax += win->xmin;
+ clp.ymin += win->ymin;
+ clp.ymax += win->ymin;
+
+ if(clp.xmin < win->xmax && clp.xmax > win->xmin &&
+ clp.ymin < win->ymax && clp.ymax > win->ymin) {
+ if(clp.xmin < win->xmin) clp.xmin = win->xmin;
+ if(clp.ymin < win->ymin) clp.ymin = win->ymin;
+ if(clp.xmax > win->xmax) clp.xmax = win->xmax;
+ if(clp.ymax > win->ymax) clp.ymax = win->ymax;
+
+ glScissor(clp.xmin + 1, clp.ymin + 1,
+ clp.xmax - clp.xmin - 2,
+ clp.ymax - clp.ymin - 2);
+ }
+
+ glClear(GL_DEPTH_BUFFER_BIT);
+ }
+}
+#endif
+static void do_brush_action(Sculpt *sd, StrokeCache *cache)
+{
+ SculptSession *ss = sd->session;
+ float av_dist;
+ ListBase active_verts={0,0};
+ ListBase *grab_active_verts = &ss->cache->grab_active_verts[ss->cache->symmetry];
+ ActiveData *adata= 0;
+ float *vert;
+ Mesh *me= NULL; /*XXX: get_mesh(OBACT); */
+ const float bstrength= brush_strength(sd, cache);
+ KeyBlock *keyblock= NULL; /*XXX: ob_get_keyblock(OBACT); */
+ Brush *b = sd->brush;
+ int i;
+
+ sculpt_add_damaged_rect(ss);
+
+ /* Build a list of all vertices that are potentially within the brush's
+ area of influence. Only do this once for the grab brush. */
+ if((b->sculpt_tool != SCULPT_TOOL_GRAB) || cache->first_time) {
+ for(i=0; i<ss->totvert; ++i) {
+ /* Projverts.inside provides a rough bounding box */
+ if(ss->multires || ss->projverts[i].inside) {
+ //vert= ss->vertexcosnos ? &ss->vertexcosnos[i*6] : a->verts[i].co;
+ vert= ss->mvert[i].co;
+ av_dist= VecLenf(ss->cache->location, vert);
+ if(av_dist < cache->radius) {
+ adata= (ActiveData*)MEM_mallocN(sizeof(ActiveData), "ActiveData");
+
+ adata->Index = i;
+ /* Fade is used to store the final strength at which the brush
+ should modify a particular vertex. */
+ adata->Fade= tex_strength(sd, vert, av_dist) * bstrength;
+ adata->dist = av_dist;
+
+ if(b->sculpt_tool == SCULPT_TOOL_GRAB && cache->first_time)
+ BLI_addtail(grab_active_verts, adata);
+ else
+ BLI_addtail(&active_verts, adata);
+ }
+ }
+ }
+ }
+
+ /* Only act if some verts are inside the brush area */
+ if(active_verts.first || (b->sculpt_tool == SCULPT_TOOL_GRAB && grab_active_verts->first)) {
+ /* Apply one type of brush action */
+ switch(b->sculpt_tool){
+ case SCULPT_TOOL_DRAW:
+ do_draw_brush(sd, ss, &active_verts);
+ break;
+ case SCULPT_TOOL_SMOOTH:
+ do_smooth_brush(ss, &active_verts);
+ break;
+ case SCULPT_TOOL_PINCH:
+ do_pinch_brush(ss, &active_verts);
+ break;
+ case SCULPT_TOOL_INFLATE:
+ do_inflate_brush(ss, &active_verts);
+ break;
+ case SCULPT_TOOL_GRAB:
+ do_grab_brush(sd, ss);
+ break;
+ case SCULPT_TOOL_LAYER:
+ do_layer_brush(sd, ss, &active_verts);
+ break;
+ case SCULPT_TOOL_FLATTEN:
+ do_flatten_brush(sd, ss, &active_verts);
+ break;
+ }
+
+ /* Copy the modified vertices from mesh to the active key */
+ if(keyblock && !ss->multires) {
+ float *co= keyblock->data;
+ if(co) {
+ if(b->sculpt_tool == SCULPT_TOOL_GRAB)
+ adata = grab_active_verts->first;
+ else
+ adata = active_verts.first;
+
+ for(; adata; adata= adata->next)
+ if(adata->Index < keyblock->totelem)
+ VecCopyf(&co[adata->Index*3], me->mvert[adata->Index].co);
+ }
+ }
+
+ if(ss->vertexcosnos && !ss->multires)
+ BLI_freelistN(&active_verts);
+ else {
+ if(b->sculpt_tool != SCULPT_TOOL_GRAB)
+ addlisttolist(&ss->damaged_verts, &active_verts);
+ }
+ }
+}
+
+/* Flip all the editdata across the axis/axes specified by symm. Used to
+ calculate multiple modifications to the mesh when symmetry is enabled. */
+static void calc_brushdata_symm(StrokeCache *cache, const char symm)
+{
+ flip_coord(cache->location, cache->true_location, symm);
+ flip_coord(cache->view_normal_symmetry, cache->view_normal, symm);
+ flip_coord(cache->grab_delta_symmetry, cache->grab_delta, symm);
+ cache->symmetry= symm;
+}
+
+static void do_symmetrical_brush_actions(Sculpt *sd, StrokeCache *cache)
+{
+ const char symm = sd->flags & 7;
+ int i;
+
+ /* Brush spacing: only apply dot if next dot is far enough away */
+ if((sd->brush->flag & BRUSH_SPACE) && !(sd->brush->flag & BRUSH_ANCHORED) && !cache->first_time) {
+ int dx = cache->last_dot[0] - cache->mouse[0];
+ int dy = cache->last_dot[1] - cache->mouse[1];
+ if(sqrt(dx*dx+dy*dy) < sd->brush->spacing)
+ return;
+ }
+ memcpy(cache->last_dot, cache->mouse, sizeof(int) * 2);
+
+ VecCopyf(cache->location, cache->true_location);
+ VecCopyf(cache->grab_delta_symmetry, cache->grab_delta);
+ cache->symmetry = 0;
+ do_brush_action(sd, cache);
+
+ for(i = 1; i <= symm; ++i) {
+ if(symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5))) {
+ calc_brushdata_symm(cache, i);
+ do_brush_action(sd, cache);
+ }
+ }
+
+ cache->first_time = 0;
+}
+
+static void add_face_normal(vec3f *norm, MVert *mvert, const MFace* face, float *fn)
+{
+ vec3f c= {mvert[face->v1].co[0],mvert[face->v1].co[1],mvert[face->v1].co[2]};
+ vec3f b= {mvert[face->v2].co[0],mvert[face->v2].co[1],mvert[face->v2].co[2]};
+ vec3f a= {mvert[face->v3].co[0],mvert[face->v3].co[1],mvert[face->v3].co[2]};
+ vec3f s1, s2;
+ float final[3];
+
+ VecSubf(&s1.x,&a.x,&b.x);
+ VecSubf(&s2.x,&c.x,&b.x);
+
+ final[0] = s1.y * s2.z - s1.z * s2.y;
+ final[1] = s1.z * s2.x - s1.x * s2.z;
+ final[2] = s1.x * s2.y - s1.y * s2.x;
+
+ if(fn)
+ VecCopyf(fn, final);
+
+ norm->x+= final[0];
+ norm->y+= final[1];
+ norm->z+= final[2];
+}
+
+static void update_damaged_vert(SculptSession *ss, ListBase *lb)
+{
+ ActiveData *vert;
+
+ for(vert= lb->first; vert; vert= vert->next) {
+ vec3f norm= {0,0,0};
+ IndexNode *face= ss->fmap[vert->Index].first;
+
+ while(face){
+ float *fn = NULL;
+ if(ss->face_normals)
+ fn = &ss->face_normals[face->index*3];
+ add_face_normal(&norm, ss->mvert, &ss->mface[face->index], fn);
+ face= face->next;
+ }
+ Normalize(&norm.x);
+
+ ss->mvert[vert->Index].no[0]=norm.x*32767;
+ ss->mvert[vert->Index].no[1]=norm.y*32767;
+ ss->mvert[vert->Index].no[2]=norm.z*32767;
+ }
+}
+
+static void calc_damaged_verts(SculptSession *ss)
+{
+ int i;
+
+ for(i=0; i<8; ++i)
+ update_damaged_vert(ss, &ss->cache->grab_active_verts[i]);
+ update_damaged_vert(ss, &ss->damaged_verts);
+ BLI_freelistN(&ss->damaged_verts);
+ ss->damaged_verts.first = ss->damaged_verts.last = NULL;
+}
+
+#if 0
+static void projverts_clear_inside(SculptSession *ss)
+{
+ int i;
+ for(i = 0; i < ss->totvert; ++i)
+ ss->projverts[i].inside = 0;
+}
+#endif
+
+static void sculpt_update_tex(Sculpt *sd)
+{
+ SculptSession *ss= sd->session;
+
+ if(ss->texcache) {
+ MEM_freeN(ss->texcache);
+ ss->texcache= NULL;
+ }
+
+ /* Need to allocate a bigger buffer for bigger brush size */
+ ss->texcache_side = sd->brush->size * 2;
+ if(!ss->texcache || ss->texcache_side > ss->texcache_actual) {
+ ss->texcache = brush_gen_texture_cache(sd->brush, sd->brush->size);
+ ss->texcache_actual = ss->texcache_side;
+ }
+}
+
+void sculptmode_selectbrush_menu(void)
+{
+ /* XXX: I guess menus belong elsewhere too?
+
+ Sculpt *sd= sculpt_data();
+ int val;
+
+ pupmenu_set_active(sd->brush_type);
+
+ val= pupmenu("Select Brush%t|Draw|Smooth|Pinch|Inflate|Grab|Layer|Flatten");
+
+ if(val>0) {
+ sd->brush_type= val;
+
+ allqueue(REDRAWVIEW3D, 1);
+ allqueue(REDRAWBUTSEDIT, 1);
+ }*/
+}
+
+static void sculptmode_update_all_projverts(SculptSession *ss)
+{
+ unsigned i;
+
+ if(!ss->projverts)
+ ss->projverts = MEM_mallocN(sizeof(ProjVert)*ss->totvert,"ProjVerts");
+
+ for(i=0; i<ss->totvert; ++i) {
+ project(ss->cache->mats, ss->vertexcosnos ? &ss->vertexcosnos[i * 6] : ss->mvert[i].co,
+ ss->projverts[i].co);
+ ss->projverts[i].inside= 0;
+ }
+}
+
+/* Checks whether full update mode (slower) needs to be used to work with modifiers */
+char sculpt_modifiers_active(Object *ob)
+{
+ ModifierData *md;
+
+ for(md= modifiers_getVirtualModifierList(ob); md; md= md->next) {
+ if(md->mode & eModifierMode_Realtime && md->type != eModifierType_Multires)
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Sculpt mode handles multires differently from regular meshes, but only if
+ it's the last modifier on the stack and it is not on the first level */
+static struct MultiresModifierData *sculpt_multires_active(Object *ob)
+{
+ ModifierData *md;
+
+ for(md= modifiers_getVirtualModifierList(ob); md; md= md->next) {
+ if(md->type == eModifierType_Multires && !md->next) {
+ MultiresModifierData *mmd = (MultiresModifierData*)md;
+ if(mmd->lvl != 1)
+ return mmd;
+ }
+ }
+
+ return NULL;
+}
+
+static void sculpt_update_mesh_elements(bContext *C)
+{
+ SculptSession *ss = CTX_data_tool_settings(C)->sculpt->session;
+ Object *ob = CTX_data_active_object(C);
+
+ if((ss->multires = sculpt_multires_active(ob))) {
+ DerivedMesh *dm = mesh_get_derived_final(CTX_data_scene(C), ob, CD_MASK_BAREMESH);
+ ss->totvert = dm->getNumVerts(dm);
+ ss->totface = dm->getNumFaces(dm);
+ ss->mvert = dm->getVertDataArray(dm, CD_MVERT);
+ ss->mface = dm->getFaceDataArray(dm, CD_MFACE);
+ ss->face_normals = dm->getFaceDataArray(dm, CD_NORMAL);
+ }
+ else {
+ Mesh *me = get_mesh(ob);
+ ss->totvert = me->totvert;
+ ss->totface = me->totface;
+ ss->mvert = me->mvert;
+ ss->mface = me->mface;
+ ss->face_normals = NULL;
+ }
+
+ if(ss->totvert != ss->fmap_size) {
+ if(ss->fmap) MEM_freeN(ss->fmap);
+ if(ss->fmap_mem) MEM_freeN(ss->fmap_mem);
+ create_vert_face_map(&ss->fmap, &ss->fmap_mem, ss->mface, ss->totvert, ss->totface);
+ ss->fmap_size = ss->totvert;
+ }
+}
+
+/* XXX: lots of drawing code (partial redraw), has to go elsewhere */
+#if 0
+void sculptmode_draw_wires(SculptSession *ss, int only_damaged)
+{
+ Mesh *me = get_mesh(OBACT);
+ int i;
+
+ bglPolygonOffset(1.0);
+ glDepthMask(0);
+ BIF_ThemeColor((OBACT==OBACT)?TH_ACTIVE:TH_SELECT);
+
+ for(i=0; i<me->totedge; i++) {
+ MEdge *med= &me->medge[i];
+
+ if((!only_damaged || (ss->projverts[med->v1].inside || ss->projverts[med->v2].inside)) &&
+ (med->flag & ME_EDGEDRAW)) {
+ glDrawElements(GL_LINES, 2, GL_UNSIGNED_INT, &med->v1);
+ }
+ }
+
+ glDepthMask(1);
+ bglPolygonOffset(0.0);
+}
+
+void sculptmode_draw_mesh(int only_damaged)
+{
+ int i, j, dt, drawCurrentMat = 1, matnr= -1;
+ SculptSession *ss = sculpt_session();
+
+ sculpt_update_mesh_elements(ss, OBACT);
+
+ persp(PERSP_VIEW);
+ mymultmatrix(OBACT->obmat);
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_LIGHTING);
+ /* XXX: GPU_set_object_materials(G.scene, OBACT, 0, NULL); */
+ glEnable(GL_CULL_FACE);
+
+ glShadeModel(GL_SMOOTH);
+
+ glVertexPointer(3, GL_FLOAT, sizeof(MVert), &cache->mvert[0].co);
+ glNormalPointer(GL_SHORT, sizeof(MVert), &cache->mvert[0].no);
+
+ dt= MIN2(G.vd->drawtype, OBACT->dt);
+ if(dt==OB_WIRE)
+ glColorMask(0,0,0,0);
+
+ for(i=0; i<ss->totface; ++i) {
+ MFace *f= &ss->mface[i];
+ char inside= 0;
+ int new_matnr= f->mat_nr + 1;
+
+ if(new_matnr != matnr)
+ drawCurrentMat= GPU_enable_material(matnr = new_matnr, NULL);
+
+ /* If only_damaged!=0, only draw faces that are partially
+ inside the area(s) modified by the brush */
+ if(only_damaged) {
+ for(j=0; j<(f->v4?4:3); ++j) {
+ if(ss->projverts[*((&f->v1)+j)].inside) {
+ inside= 1;
+ break;
+ }
+ }
+ }
+ else
+ inside= 1;
+
+ if(inside && drawCurrentMat)
+ glDrawElements(f->v4?GL_QUADS:GL_TRIANGLES, f->v4?4:3, GL_UNSIGNED_INT, &f->v1);
+ }
+
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_LIGHTING);
+ glColorMask(1,1,1,1);
+
+ if(dt==OB_WIRE || (OBACT->dtx & OB_DRAWWIRE))
+ sculptmode_draw_wires(ss, only_damaged);
+
+ glDisable(GL_DEPTH_TEST);
+}
+#endif
+
+static int sculpt_poll(bContext *C)
+{
+ return G.f & G_SCULPTMODE && CTX_wm_area(C)->spacetype == SPACE_VIEW3D &&
+ CTX_wm_region(C)->regiontype == RGN_TYPE_WINDOW;
+}
+
+/*** Sculpt Cursor ***/
+static void draw_paint_cursor(bContext *C, int x, int y, void *customdata)
+{
+ Sculpt *sd= CTX_data_tool_settings(C)->sculpt;
+
+ glTranslatef((float)x, (float)y, 0.0f);
+
+ glColor4ub(255, 100, 100, 128);
+ glEnable( GL_LINE_SMOOTH );
+ glEnable(GL_BLEND);
+ glutil_draw_lined_arc(0.0, M_PI*2.0, sd->brush->size, 40);
+ glDisable(GL_BLEND);
+ glDisable( GL_LINE_SMOOTH );
+
+ glTranslatef((float)-x, (float)-y, 0.0f);
+}
+
+static void toggle_paint_cursor(bContext *C)
+{
+ Sculpt *s = CTX_data_scene(C)->toolsettings->sculpt;
+
+ if(s->session->cursor) {
+ WM_paint_cursor_end(CTX_wm_manager(C), s->session->cursor);
+ s->session->cursor = NULL;
+ }
+ else {
+ s->session->cursor =
+ WM_paint_cursor_activate(CTX_wm_manager(C), sculpt_poll, draw_paint_cursor, NULL);
+ }
+}
+
+static void sculpt_undo_push(bContext *C, Sculpt *sd)
+{
+ switch(sd->brush->sculpt_tool) {
+ case SCULPT_TOOL_DRAW:
+ ED_undo_push(C, "Draw Brush"); break;
+ case SCULPT_TOOL_SMOOTH:
+ ED_undo_push(C, "Smooth Brush"); break;
+ case SCULPT_TOOL_PINCH:
+ ED_undo_push(C, "Pinch Brush"); break;
+ case SCULPT_TOOL_INFLATE:
+ ED_undo_push(C, "Inflate Brush"); break;
+ case SCULPT_TOOL_GRAB:
+ ED_undo_push(C, "Grab Brush"); break;
+ case SCULPT_TOOL_LAYER:
+ ED_undo_push(C, "Layer Brush"); break;
+ case SCULPT_TOOL_FLATTEN:
+ ED_undo_push(C, "Flatten Brush"); break;
+ default:
+ ED_undo_push(C, "Sculpting"); break;
+ }
+}
+
+static int sculpt_brush_curve_preset_exec(bContext *C, wmOperator *op)
+{
+ brush_curve_preset(CTX_data_scene(C)->toolsettings->sculpt->brush, RNA_enum_get(op->ptr, "mode"));
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_brush_curve_preset(wmOperatorType *ot)
+{
+ static EnumPropertyItem prop_mode_items[] = {
+ {BRUSH_PRESET_SHARP, "SHARP", "Sharp Curve", ""},
+ {BRUSH_PRESET_SMOOTH, "SMOOTH", "Smooth Curve", ""},
+ {BRUSH_PRESET_MAX, "MAX", "Max Curve", ""},
+ {0, NULL, NULL, NULL}};
+
+ ot->name= "Preset";
+ ot->idname= "SCULPT_OT_brush_curve_preset";
+
+ ot->exec= sculpt_brush_curve_preset_exec;
+ ot->poll= sculpt_poll;
+
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+
+ RNA_def_enum(ot->srna, "mode", prop_mode_items, BRUSH_PRESET_SHARP, "Mode", "");
+}
+
+/**** Radial control ****/
+static int sculpt_radial_control_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ toggle_paint_cursor(C);
+ brush_radial_control_invoke(op, CTX_data_scene(C)->toolsettings->sculpt->brush);
+ return WM_radial_control_invoke(C, op, event);
+}
+
+static int sculpt_radial_control_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+ int ret = WM_radial_control_modal(C, op, event);
+ if(ret != OPERATOR_RUNNING_MODAL)
+ toggle_paint_cursor(C);
+ return ret;
+}
+
+static int sculpt_radial_control_exec(bContext *C, wmOperator *op)
+{
+ int ret = brush_radial_control_exec(op, CTX_data_scene(C)->toolsettings->sculpt->brush);
+ char str[256];
+ WM_radial_control_string(op, str, 256);
+
+ return ret;
+}
+
+static void SCULPT_OT_radial_control(wmOperatorType *ot)
+{
+ WM_OT_radial_control_partial(ot);
+
+ ot->name= "Sculpt Radial Control";
+ ot->idname= "SCULPT_OT_radial_control";
+
+ ot->invoke= sculpt_radial_control_invoke;
+ ot->modal= sculpt_radial_control_modal;
+ ot->exec= sculpt_radial_control_exec;
+ ot->poll= sculpt_poll;
+
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+/**** Operator for applying a stroke (various attributes including mouse path)
+ using the current brush. ****/
+
+static void sculpt_load_mats(bglMats *mats, ViewContext *vc)
+{
+ float cpy[4][4];
+ int i, j;
+
+ Mat4MulMat4(cpy, vc->rv3d->viewmat, vc->obact->obmat);
+
+ for(i = 0; i < 4; ++i) {
+ for(j = 0; j < 4; ++j) {
+ mats->projection[i*4+j] = vc->rv3d->winmat[i][j];
+ mats->modelview[i*4+j] = cpy[i][j];
+ }
+ }
+
+ mats->viewport[0] = vc->ar->winrct.xmin;
+ mats->viewport[1] = vc->ar->winrct.ymin;
+ mats->viewport[2] = vc->ar->winx;
+ mats->viewport[3] = vc->ar->winy;
+}
+
+static float unproject_brush_radius(SculptSession *ss, float offset)
+{
+ float brush_edge[3];
+
+ /* In anchored mode, brush size changes with mouse loc, otherwise it's fixed using the brush radius */
+ unproject(ss->cache->mats, brush_edge, ss->cache->initial_mouse[0] + offset,
+ ss->cache->initial_mouse[1], ss->cache->depth);
+
+ return VecLenf(ss->cache->true_location, brush_edge);
+}
+
+static void sculpt_cache_free(StrokeCache *cache)
+{
+ if(cache->layer_disps)
+ MEM_freeN(cache->layer_disps);
+ if(cache->mesh_store)
+ MEM_freeN(cache->mesh_store);
+ if(cache->orig_norms)
+ MEM_freeN(cache->orig_norms);
+ if(cache->mats)
+ MEM_freeN(cache->mats);
+ MEM_freeN(cache);
+}
+
+/* Initialize the stroke cache invariants from operator properties */
+static void sculpt_update_cache_invariants(Sculpt *sd, bContext *C, wmOperator *op)
+{
+ StrokeCache *cache = MEM_callocN(sizeof(StrokeCache), "stroke cache");
+ int i;
+
+ sd->session->cache = cache;
+
+ RNA_float_get_array(op->ptr, "scale", cache->scale);
+ cache->flag = RNA_int_get(op->ptr, "flag");
+ RNA_float_get_array(op->ptr, "clip_tolerance", cache->clip_tolerance);
+ RNA_int_get_array(op->ptr, "initial_mouse", cache->initial_mouse);
+ cache->depth = RNA_float_get(op->ptr, "depth");
+
+ /* Truly temporary data that isn't stored in properties */
+
+ view3d_set_viewcontext(C, &cache->vc);
+
+ cache->mats = MEM_callocN(sizeof(bglMats), "sculpt bglMats");
+ sculpt_load_mats(cache->mats, &cache->vc);
+
+ sculpt_update_mesh_elements(C);
+
+ /* Make copies of the mesh vertex locations and normals for some tools */
+ if(sd->brush->sculpt_tool == SCULPT_TOOL_LAYER || (sd->brush->flag & BRUSH_ANCHORED)) {
+ cache->layer_disps = MEM_callocN(sizeof(float) * sd->session->totvert, "layer brush displacements");
+ cache->mesh_store= MEM_mallocN(sizeof(float) * 3 * sd->session->totvert, "sculpt mesh vertices copy");
+ for(i = 0; i < sd->session->totvert; ++i)
+ VecCopyf(cache->mesh_store[i], sd->session->mvert[i].co);
+
+ if(sd->brush->flag & BRUSH_ANCHORED) {
+ cache->orig_norms= MEM_mallocN(sizeof(short) * 3 * sd->session->totvert, "Sculpt orig norm");
+ for(i = 0; i < sd->session->totvert; ++i) {
+ cache->orig_norms[i][0] = sd->session->mvert[i].no[0];
+ cache->orig_norms[i][1] = sd->session->mvert[i].no[1];
+ cache->orig_norms[i][2] = sd->session->mvert[i].no[2];
+ }
+ }
+ }
+
+ unproject(cache->mats, cache->true_location, cache->initial_mouse[0], cache->initial_mouse[1], cache->depth);
+ cache->radius = unproject_brush_radius(sd->session, brush_size(sd));
+ cache->rotation = 0;
+ cache->first_time = 1;
+}
+
+/* Initialize the stroke cache variants from operator properties */
+static void sculpt_update_cache_variants(Sculpt *sd, PointerRNA *ptr)
+{
+ StrokeCache *cache = sd->session->cache;
+ float grab_location[3];
+ int dx, dy;
+
+ if(!(sd->brush->flag & BRUSH_ANCHORED))
+ RNA_float_get_array(ptr, "location", cache->true_location);
+ cache->flip = RNA_boolean_get(ptr, "flip");
+ RNA_int_get_array(ptr, "mouse", cache->mouse);
+
+ /* Truly temporary data that isn't stored in properties */
+
+ cache->previous_pixel_radius = cache->pixel_radius;
+ cache->pixel_radius = brush_size(sd);
+
+ if(sd->brush->flag & BRUSH_ANCHORED) {
+ dx = cache->mouse[0] - cache->initial_mouse[0];
+ dy = cache->mouse[1] - cache->initial_mouse[1];
+ cache->pixel_radius = sqrt(dx*dx + dy*dy);
+ cache->radius = unproject_brush_radius(sd->session, cache->pixel_radius);
+ cache->rotation = atan2(dy, dx);
+ }
+ else if(sd->brush->flag & BRUSH_RAKE) {
+ int update;
+
+ dx = cache->last_rake[0] - cache->mouse[0];
+ dy = cache->last_rake[1] - cache->mouse[1];
+
+ update = dx*dx + dy*dy > 100;
+
+ /* To prevent jitter, only update the angle if the mouse has moved over 10 pixels */
+ if(update && !cache->first_time)
+ cache->rotation = M_PI_2 + atan2(dy, dx);
+
+ if(update || cache->first_time) {
+ cache->last_rake[0] = cache->mouse[0];
+ cache->last_rake[1] = cache->mouse[1];
+ }
+ }
+
+ /* Find the grab delta */
+ if(sd->brush->sculpt_tool == SCULPT_TOOL_GRAB) {
+ unproject(cache->mats, grab_location, cache->mouse[0], cache->mouse[1], cache->depth);
+ if(!cache->first_time)
+ VecSubf(cache->grab_delta, grab_location, cache->old_grab_location);
+ VecCopyf(cache->old_grab_location, grab_location);
+ }
+}
+
+/* Initialize stroke operator properties */
+static void sculpt_brush_stroke_init_properties(bContext *C, wmOperator *op, wmEvent *event, SculptSession *ss)
+{
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ Object *ob= CTX_data_active_object(C);
+ ModifierData *md;
+ ViewContext vc;
+ float scale[3], clip_tolerance[3] = {0,0,0};
+ int mouse[2], flag = 0;
+
+ /* Set scaling adjustment */
+ scale[0] = 1.0f / ob->size[0];
+ scale[1] = 1.0f / ob->size[1];
+ scale[2] = 1.0f / ob->size[2];
+ RNA_float_set_array(op->ptr, "scale", scale);
+
+ /* Initialize mirror modifier clipping */
+ for(md= ob->modifiers.first; md; md= md->next) {
+ if(md->type==eModifierType_Mirror && (md->mode & eModifierMode_Realtime)) {
+ const MirrorModifierData *mmd = (MirrorModifierData*) md;
+
+ /* Mark each axis that needs clipping along with its tolerance */
+ if(mmd->flag & MOD_MIR_CLIPPING) {
+ flag |= CLIP_X << mmd->axis;
+ if(mmd->tolerance > clip_tolerance[mmd->axis])
+ clip_tolerance[mmd->axis] = mmd->tolerance;
+ }
+ }
+ }
+ RNA_int_set(op->ptr, "flag", flag);
+ RNA_float_set_array(op->ptr, "clip_tolerance", clip_tolerance);
+
+ /* Initial mouse location */
+ mouse[0] = event->x;
+ mouse[1] = event->y;
+ RNA_int_set_array(op->ptr, "initial_mouse", mouse);
+
+ /* Initial screen depth under the mouse */
+ view3d_set_viewcontext(C, &vc);
+ RNA_float_set(op->ptr, "depth", read_cached_depth(&vc, event->x, event->y));
+
+ sculpt_update_cache_invariants(sd, C, op);
+}
+
+static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+
+ view3d_operator_needs_opengl(C);
+ sculpt_brush_stroke_init_properties(C, op, event, sd->session);
+
+ sculptmode_update_all_projverts(sd->session);
+
+ /* TODO: Shouldn't really have to do this at the start of every
+ stroke, but sculpt would need some sort of notification when
+ changes are made to the texture. */
+ sculpt_update_tex(sd);
+
+ /* add modal handler */
+ WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void sculpt_restore_mesh(Sculpt *sd)
+{
+ StrokeCache *cache = sd->session->cache;
+ int i;
+
+ /* Restore the mesh before continuing with anchored stroke */
+ if((sd->brush->flag & BRUSH_ANCHORED) && cache->mesh_store) {
+ for(i = 0; i < sd->session->totvert; ++i) {
+ VecCopyf(sd->session->mvert[i].co, cache->mesh_store[i]);
+ sd->session->mvert[i].no[0] = cache->orig_norms[i][0];
+ sd->session->mvert[i].no[1] = cache->orig_norms[i][1];
+ sd->session->mvert[i].no[2] = cache->orig_norms[i][2];
+ }
+ }
+}
+
+static void sculpt_post_stroke_free(SculptSession *ss)
+{
+ BLI_freelistN(&ss->damaged_rects);
+ BLI_freelistN(&ss->damaged_verts);
+}
+
+static void sculpt_flush_update(bContext *C)
+{
+ Sculpt *s = CTX_data_tool_settings(C)->sculpt;
+ ARegion *ar = CTX_wm_region(C);
+ MultiresModifierData *mmd = s->session->multires;
+
+ calc_damaged_verts(s->session);
+
+ if(mmd) {
+ if(mmd->undo_verts && mmd->undo_verts != s->session->mvert)
+ MEM_freeN(mmd->undo_verts);
+
+ mmd->undo_verts = s->session->mvert;
+ mmd->undo_verts_tot = s->session->totvert;
+ }
+
+ ED_region_tag_redraw(ar);
+}
+
+static int sculpt_brush_stroke_modal(bContext *C, wmOperator *op, wmEvent *event)
+{
+ PointerRNA itemptr;
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ float center[3];
+ int mouse[2] = {event->x, event->y};
+
+ sculpt_update_mesh_elements(C);
+
+ unproject(sd->session->cache->mats, center, event->x, event->y,
+ read_cached_depth(&sd->session->cache->vc, event->x, event->y));
+
+ /* Add to stroke */
+ RNA_collection_add(op->ptr, "stroke", &itemptr);
+ RNA_float_set_array(&itemptr, "location", center);
+ RNA_int_set_array(&itemptr, "mouse", mouse);
+ RNA_boolean_set(&itemptr, "flip", event->shift);
+ sculpt_update_cache_variants(sd, &itemptr);
+
+ sculpt_restore_mesh(sd);
+ do_symmetrical_brush_actions(CTX_data_tool_settings(C)->sculpt, sd->session->cache);
+
+ sculpt_flush_update(C);
+ sculpt_post_stroke_free(sd->session);
+
+ /* Finished */
+ if(event->type == LEFTMOUSE && event->val == 0) {
+ request_depth_update(sd->session->cache->vc.rv3d);
+
+ sculpt_cache_free(sd->session->cache);
+
+ sculpt_undo_push(C, sd);
+
+ return OPERATOR_FINISHED;
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op)
+{
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+
+ view3d_operator_needs_opengl(C);
+ sculpt_update_cache_invariants(sd, C, op);
+ sculptmode_update_all_projverts(sd->session);
+ sculpt_update_tex(sd);
+
+ RNA_BEGIN(op->ptr, itemptr, "stroke") {
+ sculpt_update_cache_variants(sd, &itemptr);
+
+ sculpt_restore_mesh(sd);
+ do_symmetrical_brush_actions(sd, sd->session->cache);
+
+ sculpt_post_stroke_free(sd->session);
+ }
+ RNA_END;
+
+ sculpt_flush_update(C);
+ sculpt_cache_free(sd->session->cache);
+
+ sculpt_undo_push(C, sd);
+
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_brush_stroke(wmOperatorType *ot)
+{
+ ot->flag |= OPTYPE_REGISTER;
+
+ /* identifiers */
+ ot->name= "Sculpt Mode";
+ ot->idname= "SCULPT_OT_brush_stroke";
+
+ /* api callbacks */
+ ot->invoke= sculpt_brush_stroke_invoke;
+ ot->modal= sculpt_brush_stroke_modal;
+ ot->exec= sculpt_brush_stroke_exec;
+ ot->poll= sculpt_poll;
+
+ /* flags (sculpt does own undo? (ton) */
+ ot->flag= OPTYPE_REGISTER;
+
+ /* properties */
+ RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+
+ /* If the object has a scaling factor, brushes also need to be scaled
+ to work as expected. */
+ RNA_def_float_vector(ot->srna, "scale", 3, NULL, 0.0f, FLT_MAX, "Scale", "", 0.0f, 1000.0f);
+
+ RNA_def_int(ot->srna, "flag", 0, 0, INT_MAX, "flag", "", 0, INT_MAX);
+
+ /* For mirror modifiers */
+ RNA_def_float_vector(ot->srna, "clip_tolerance", 3, NULL, 0.0f, FLT_MAX, "clip_tolerance", "", 0.0f, 1000.0f);
+
+ /* The initial 2D location of the mouse */
+ RNA_def_int_vector(ot->srna, "initial_mouse", 2, NULL, INT_MIN, INT_MAX, "initial_mouse", "", INT_MIN, INT_MAX);
+
+ /* The initial screen depth of the mouse */
+ RNA_def_float(ot->srna, "depth", 0.0f, 0.0f, FLT_MAX, "depth", "", 0.0f, FLT_MAX);
+}
+
+/**** Toggle operator for turning sculpt mode on or off ****/
+
+static int sculpt_toggle_mode(bContext *C, wmOperator *op)
+{
+ ToolSettings *ts = CTX_data_tool_settings(C);
+
+ if(G.f & G_SCULPTMODE) {
+ multires_force_update(CTX_data_active_object(C));
+
+ /* Leave sculptmode */
+ G.f &= ~G_SCULPTMODE;
+
+ toggle_paint_cursor(C);
+
+ sculptsession_free(ts->sculpt);
+ }
+ else {
+ MTex *mtex; // XXX: temporary
+
+ /* Enter sculptmode */
+
+ G.f |= G_SCULPTMODE;
+
+ /* Create persistent sculpt mode data */
+ if(!ts->sculpt)
+ ts->sculpt = MEM_callocN(sizeof(Sculpt), "sculpt mode data");
+
+ /* Create sculpt mode session data */
+ if(ts->sculpt->session)
+ MEM_freeN(ts->sculpt->session);
+ ts->sculpt->session = MEM_callocN(sizeof(SculptSession), "sculpt session");
+
+ toggle_paint_cursor(C);
+
+
+
+ /* XXX: testing */
+ /* Needed for testing, if there's no brush then create one */
+ ts->sculpt->brush = add_brush("test brush");
+ /* Also for testing, set the brush texture to the first available one */
+ if(G.main->tex.first) {
+ Tex *tex = G.main->tex.first;
+ if(tex->type) {
+ mtex = MEM_callocN(sizeof(MTex), "test mtex");
+ ts->sculpt->brush->texact = 0;
+ ts->sculpt->brush->mtex[0] = mtex;
+ mtex->tex = tex;
+ mtex->size[0] = mtex->size[1] = mtex->size[2] = 50;
+ }
+ }
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_sculptmode_toggle(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name= "Sculpt Mode";
+ ot->idname= "SCULPT_OT_sculptmode_toggle";
+
+ /* api callbacks */
+ ot->exec= sculpt_toggle_mode;
+ ot->poll= ED_operator_object_active;
+
+ ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
+}
+
+void ED_operatortypes_sculpt()
+{
+ WM_operatortype_append(SCULPT_OT_radial_control);
+ WM_operatortype_append(SCULPT_OT_brush_stroke);
+ WM_operatortype_append(SCULPT_OT_sculptmode_toggle);
+ WM_operatortype_append(SCULPT_OT_brush_curve_preset);
+}
+
+void sculpt(Sculpt *sd)
+{
+#if 0
+ SculptSession *ss= sd->session;
+ Object *ob= NULL; /*XXX */
+ Mesh *me;
+ MultiresModifierData *mmd = NULL;
+ /* lastSigMouse is for the rake, to store the last place the mouse movement was significant */
+ short mouse[2], mvalo[2], lastSigMouse[2],firsttime=1, mousebut;
+ short modifier_calculations= 0;
+ BrushAction *a = MEM_callocN(sizeof(BrushAction), "brush action");
+ short spacing= 32000;
+ int scissor_box[4];
+ float offsetRot;
+ int smooth_stroke = 0, i;
+ int anchored, rake = 0 /* XXX: rake = ? */;
+
+ /* XXX: checking that sculpting is allowed
+ if(!(G.f & G_SCULPTMODE) || G.obedit || !ob || ob->id.lib || !get_mesh(ob) || (get_mesh(ob)->totface == 0))
+ return;
+ if(!(ob->lay & G.vd->lay))
+ error("Active object is not in this layer");
+ if(ob_get_keyblock(ob)) {
+ if(!(ob->shapeflag & OB_SHAPE_LOCK)) {
+ error("Cannot sculpt on unlocked shape key");
+ return;
+ }
+ }*/
+
+ anchored = sd->brush->flag & BRUSH_ANCHORED;
+ smooth_stroke = (sd->flags & SCULPT_INPUT_SMOOTH) && (sd->brush->sculpt_tool != SCULPT_TOOL_GRAB) && !anchored;
+
+ if(smooth_stroke)
+ sculpt_stroke_new(256);
+
+ ss->damaged_rects.first = ss->damaged_rects.last = NULL;
+ ss->damaged_verts.first = ss->damaged_verts.last = NULL;
+ ss->vertexcosnos = NULL;
+
+ mmd = sculpt_multires_active(ob);
+
+ /* Check that vertex users are up-to-date */
+ if(ob != active_ob || !ss->vertex_users || ss->vertex_users_size != cache->totvert) {
+ sculpt_vertexusers_free(ss);
+ calc_vertex_users(ss);
+ if(ss->projverts)
+ MEM_freeN(ss->projverts);
+ ss->projverts = NULL;
+ active_ob= ob;
+ }
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_NORMAL_ARRAY);
+
+ /*XXX:
+ persp(PERSP_VIEW);
+ getmouseco_areawin(mvalo);*/
+
+ /* Init texture
+ FIXME: Shouldn't be doing this every time! */
+ if(sd->tex_mode!=SCULPTREPT_3D)
+ sculptmode_update_tex(sd);
+
+ /*XXX: getmouseco_areawin(mouse); */
+ mvalo[0]= mouse[0];
+ mvalo[1]= mouse[1];
+ lastSigMouse[0]=mouse[0];
+ lastSigMouse[1]=mouse[1];
+ mousebut = 0; /* XXX: L_MOUSE; */
+
+ /* If modifier_calculations is true, then extra time must be spent
+ updating the mesh. This takes a *lot* longer, so it's worth
+ skipping if the modifier stack is empty. */
+ modifier_calculations= sculpt_modifiers_active(ob);
+
+ if(modifier_calculations)
+ ss->vertexcosnos= mesh_get_mapped_verts_nors(NULL, ob); /* XXX: scene = ? */
+ sculptmode_update_all_projverts(ss);
+
+ /* Capture original copy */
+ if(sd->flags & SCULPT_DRAW_FAST)
+ glAccum(GL_LOAD, 1);
+
+ /* Get original scissor box */
+ glGetIntegerv(GL_SCISSOR_BOX, scissor_box);
+
+ /* For raking, get the original angle*/
+ offsetRot=sculpt_tex_angle(sd);
+
+ me = get_mesh(ob);
+
+ while (/*XXX:get_mbut() & mousebut*/0) {
+ /* XXX: getmouseco_areawin(mouse); */
+ /* If rake, and the mouse has moved over 10 pixels (euclidean) (prevents jitter) then get the new angle */
+ if (rake && (pow(lastSigMouse[0]-mouse[0],2)+pow(lastSigMouse[1]-mouse[1],2))>100){
+ /*Nasty looking, but just orig + new angle really*/
+ set_tex_angle(sd, offsetRot+180.+to_deg(atan2((float)(mouse[1]-lastSigMouse[1]),(float)(mouse[0]-lastSigMouse[0]))));
+ lastSigMouse[0]=mouse[0];
+ lastSigMouse[1]=mouse[1];
+ }
+
+ if(firsttime || mouse[0]!=mvalo[0] || mouse[1]!=mvalo[1] ||
+ sd->brush->flag & BRUSH_AIRBRUSH) {
+ a->firsttime = firsttime;
+ firsttime= 0;
+
+ if(smooth_stroke)
+ sculpt_stroke_add_point(ss->stroke, mouse[0], mouse[1]);
+
+ spacing+= sqrt(pow(mvalo[0]-mouse[0],2)+pow(mvalo[1]-mouse[1],2));
+
+ if(modifier_calculations && !ss->vertexcosnos)
+ ss->vertexcosnos= mesh_get_mapped_verts_nors(NULL, ob); /*XXX scene = ? */
+
+ if(sd->brush->sculpt_tool != SCULPT_TOOL_GRAB) {
+ if(anchored) {
+ /* Restore the mesh before continuing with anchored stroke */
+ /*if(a->mesh_store) {
+ for(i = 0; i < cache->totvert; ++i) {
+ VecCopyf(cache->mvert[i].co, &a->mesh_store[i].x);
+ cache->mvert[i].no[0] = a->orig_norms[i][0];
+ cache->mvert[i].no[1] = a->orig_norms[i][1];
+ cache->mvert[i].no[2] = a->orig_norms[i][2];
+ }
+ }*/
+
+ //do_symmetrical_brush_actions(sd, a, mouse, NULL);
+ }
+ else {
+ if(smooth_stroke) {
+ sculpt_stroke_apply(sd, ss->stroke);
+ }
+ else if(sd->spacing==0 || spacing>sd->spacing) {
+ //do_symmetrical_brush_actions(sd, a, mouse, NULL);
+ spacing= 0;
+ }
+ }
+ }
+ else {
+ //do_symmetrical_brush_actions(sd, a, mouse, mvalo);
+ //unproject(ss, sd->pivot, mouse[0], mouse[1], a->depth);
+ }
+
+ if((!ss->multires && modifier_calculations) || ob_get_keyblock(ob)) {
+ /* XXX: DAG_object_flush_update(G.scene, ob, OB_RECALC_DATA); */ }
+
+ if(modifier_calculations || sd->brush->sculpt_tool == SCULPT_TOOL_GRAB || !(sd->flags & SCULPT_DRAW_FAST)) {
+ calc_damaged_verts(ss, a);
+ /*XXX: scrarea_do_windraw(curarea);
+ screen_swapbuffers(); */
+ } else { /* Optimized drawing */
+ calc_damaged_verts(ss, a);
+
+ /* Draw the stored image to the screen */
+ glAccum(GL_RETURN, 1);
+
+ sculpt_clear_damaged_areas(ss);
+
+ /* Draw all the polygons that are inside the modified area(s) */
+ glScissor(scissor_box[0], scissor_box[1], scissor_box[2], scissor_box[3]);
+ /* XXX: sculptmode_draw_mesh(1); */
+ glAccum(GL_LOAD, 1);
+
+ projverts_clear_inside(ss);
+
+ /* XXX: persp(PERSP_WIN); */
+ glDisable(GL_DEPTH_TEST);
+
+ /* Draw cursor */
+ if(sd->flags & SCULPT_TOOL_DRAW)
+ fdrawXORcirc((float)mouse[0],(float)mouse[1],sd->brush->size);
+ /* XXX: if(smooth_stroke)
+ sculpt_stroke_draw();
+
+ myswapbuffers(); */
+ }
+
+ BLI_freelistN(&ss->damaged_rects);
+ ss->damaged_rects.first = ss->damaged_rects.last = NULL;
+
+ mvalo[0]= mouse[0];
+ mvalo[1]= mouse[1];
+
+ if(ss->vertexcosnos) {
+ MEM_freeN(ss->vertexcosnos);
+ ss->vertexcosnos= NULL;
+ }
+
+ }
+ else { /*XXX:BIF_wait_for_statechange();*/ }
+ }
+
+ /* Set the rotation of the brush back to what it was before any rake */
+ set_tex_angle(sd, offsetRot);
+
+ if(smooth_stroke) {
+ sculpt_stroke_apply_all(sd, ss->stroke);
+ calc_damaged_verts(ss, a);
+ BLI_freelistN(&ss->damaged_rects);
+ }
+
+ //if(a->layer_disps) MEM_freeN(a->layer_disps);
+ //if(a->mesh_store) MEM_freeN(a->mesh_store);
+ //if(a->orig_norms) MEM_freeN(a->orig_norms);
+ for(i=0; i<8; ++i)
+ BLI_freelistN(&a->grab_active_verts[i]);
+ MEM_freeN(a);
+ sculpt_stroke_free(ss->stroke);
+ ss->stroke = NULL;
+
+ if(mmd) {
+ if(mmd->undo_verts && mmd->undo_verts != cache->mvert)
+ MEM_freeN(mmd->undo_verts);
+
+ mmd->undo_verts = cache->mvert;
+ mmd->undo_verts_tot = cache->totvert;
+ }
+
+ //sculpt_undo_push(sd);
+
+ /* XXX: if(G.vd->depths) G.vd->depths->damaged= 1;
+ allqueue(REDRAWVIEW3D, 0); */
+#endif
+}
+
+/* Partial Mesh Visibility */
+
+/* XXX: Partial vis. always was a mess, have to figure something out */
+#if 0
+/* mode: 0=hide outside selection, 1=hide inside selection */
+static void sculptmode_do_pmv(Object *ob, rcti *hb_2d, int mode)
+{
+ Mesh *me= get_mesh(ob);
+ float hidebox[6][3];
+ vec3f plane_normals[4];
+ float plane_ds[4];
+ unsigned i, j;
+ unsigned ndx_show, ndx_hide;
+ MVert *nve;
+ unsigned face_cnt_show= 0, face_ndx_show= 0;
+ unsigned edge_cnt_show= 0, edge_ndx_show= 0;
+ unsigned *old_map= NULL;
+ const unsigned SHOW= 0, HIDE=1;
+
+ /* Convert hide box from 2D to 3D */
+ unproject(hidebox[0], hb_2d->xmin, hb_2d->ymax, 1);
+ unproject(hidebox[1], hb_2d->xmax, hb_2d->ymax, 1);
+ unproject(hidebox[2], hb_2d->xmax, hb_2d->ymin, 1);
+ unproject(hidebox[3], hb_2d->xmin, hb_2d->ymin, 1);
+ unproject(hidebox[4], hb_2d->xmin, hb_2d->ymax, 0);
+ unproject(hidebox[5], hb_2d->xmax, hb_2d->ymin, 0);
+
+ /* Calculate normals for each side of hide box */
+ CalcNormFloat(hidebox[0], hidebox[1], hidebox[4], &plane_normals[0].x);
+ CalcNormFloat(hidebox[1], hidebox[2], hidebox[5], &plane_normals[1].x);
+ CalcNormFloat(hidebox[2], hidebox[3], hidebox[5], &plane_normals[2].x);
+ CalcNormFloat(hidebox[3], hidebox[0], hidebox[4], &plane_normals[3].x);
+
+ /* Calculate D for each side of hide box */
+ for(i= 0; i<4; ++i)
+ plane_ds[i]= hidebox[i][0]*plane_normals[i].x + hidebox[i][1]*plane_normals[i].y +
+ hidebox[i][2]*plane_normals[i].z;
+
+ /* Add partial visibility to mesh */
+ if(!me->pv) {
+ me->pv= MEM_callocN(sizeof(PartialVisibility),"PartialVisibility");
+ } else {
+ old_map= MEM_callocN(sizeof(unsigned)*me->pv->totvert,"PMV oldmap");
+ for(i=0; i<me->pv->totvert; ++i) {
+ old_map[i]= me->pv->vert_map[i]<me->totvert?0:1;
+ }
+ mesh_pmv_revert(ob, me);
+ }
+
+ /* Kill sculpt data */
+ active_ob= NULL;
+
+ /* Initalize map with which verts are to be hidden */
+ me->pv->vert_map= MEM_mallocN(sizeof(unsigned)*me->totvert, "PMV vertmap");
+ me->pv->totvert= me->totvert;
+ me->totvert= 0;
+ for(i=0; i<me->pv->totvert; ++i) {
+ me->pv->vert_map[i]= mode ? HIDE:SHOW;
+ for(j=0; j<4; ++j) {
+ if(me->mvert[i].co[0] * plane_normals[j].x +
+ me->mvert[i].co[1] * plane_normals[j].y +
+ me->mvert[i].co[2] * plane_normals[j].z < plane_ds[j] ) {
+ me->pv->vert_map[i]= mode ? SHOW:HIDE; /* Vert is outside the hide box */
+ break;
+ }
+ }
+ if(old_map && old_map[i]) me->pv->vert_map[i]= 1;
+ if(!me->pv->vert_map[i]) ++me->totvert;
+
+ }
+ if(old_map) MEM_freeN(old_map);
+
+ /* Find out how many faces to show */
+ for(i=0; i<me->totface; ++i) {
+ if(!me->pv->vert_map[me->mface[i].v1] &&
+ !me->pv->vert_map[me->mface[i].v2] &&
+ !me->pv->vert_map[me->mface[i].v3]) {
+ if(me->mface[i].v4) {
+ if(!me->pv->vert_map[me->mface[i].v4])
+ ++face_cnt_show;
+ }
+ else ++face_cnt_show;
+ }
+ }
+ /* Find out how many edges to show */
+ for(i=0; i<me->totedge; ++i) {
+ if(!me->pv->vert_map[me->medge[i].v1] &&
+ !me->pv->vert_map[me->medge[i].v2])
+ ++edge_cnt_show;
+ }
+
+ /* Create new vert array and reset each vert's map with map[old]=new index */
+ nve= MEM_mallocN(sizeof(MVert)*me->pv->totvert, "PMV verts");
+ ndx_show= 0; ndx_hide= me->totvert;
+ for(i=0; i<me->pv->totvert; ++i) {
+ if(me->pv->vert_map[i]) {
+ me->pv->vert_map[i]= ndx_hide;
+ nve[me->pv->vert_map[i]]= me->mvert[i];
+ ++ndx_hide;
+ } else {
+ me->pv->vert_map[i]= ndx_show;
+ nve[me->pv->vert_map[i]]= me->mvert[i];
+ ++ndx_show;
+ }
+ }
+ CustomData_free_layer_active(&me->vdata, CD_MVERT, me->pv->totvert);
+ me->mvert= CustomData_add_layer(&me->vdata, CD_MVERT, CD_ASSIGN, nve, me->totvert);
+
+ /* Create new face array */
+ me->pv->old_faces= me->mface;
+ me->pv->totface= me->totface;
+ me->mface= MEM_mallocN(sizeof(MFace)*face_cnt_show, "PMV faces");
+ for(i=0; i<me->totface; ++i) {
+ MFace *pr_f= &me->pv->old_faces[i];
+ char show= 0;
+
+ if(me->pv->vert_map[pr_f->v1] < me->totvert &&
+ me->pv->vert_map[pr_f->v2] < me->totvert &&
+ me->pv->vert_map[pr_f->v3] < me->totvert) {
+ if(pr_f->v4) {
+ if(me->pv->vert_map[pr_f->v4] < me->totvert)
+ show= 1;
+ }
+ else show= 1;
+ }
+
+ if(show) {
+ MFace *cr_f= &me->mface[face_ndx_show];
+ *cr_f= *pr_f;
+ cr_f->v1= me->pv->vert_map[pr_f->v1];
+ cr_f->v2= me->pv->vert_map[pr_f->v2];
+ cr_f->v3= me->pv->vert_map[pr_f->v3];
+ cr_f->v4= pr_f->v4 ? me->pv->vert_map[pr_f->v4] : 0;
+ test_index_face(cr_f,NULL,0,pr_f->v4?4:3);
+ ++face_ndx_show;
+ }
+ }
+ me->totface= face_cnt_show;
+ CustomData_set_layer(&me->fdata, CD_MFACE, me->mface);
+
+ /* Create new edge array */
+ me->pv->old_edges= me->medge;
+ me->pv->totedge= me->totedge;
+ me->medge= MEM_mallocN(sizeof(MEdge)*edge_cnt_show, "PMV edges");
+ me->pv->edge_map= MEM_mallocN(sizeof(int)*me->pv->totedge,"PMV edgemap");
+ for(i=0; i<me->totedge; ++i) {
+ if(me->pv->vert_map[me->pv->old_edges[i].v1] < me->totvert &&
+ me->pv->vert_map[me->pv->old_edges[i].v2] < me->totvert) {
+ MEdge *cr_e= &me->medge[edge_ndx_show];
+ me->pv->edge_map[i]= edge_ndx_show;
+ *cr_e= me->pv->old_edges[i];
+ cr_e->v1= me->pv->vert_map[me->pv->old_edges[i].v1];
+ cr_e->v2= me->pv->vert_map[me->pv->old_edges[i].v2];
+ ++edge_ndx_show;
+ }
+ else me->pv->edge_map[i]= -1;
+ }
+ me->totedge= edge_cnt_show;
+ CustomData_set_layer(&me->edata, CD_MEDGE, me->medge);
+
+ /* XXX: DAG_object_flush_update(G.scene, OBACT, OB_RECALC_DATA); */
+}
+
+static rcti sculptmode_pmv_box()
+{
+ /*XXX: short down[2], mouse[2];
+ rcti ret;
+
+ getmouseco_areawin(down);
+
+ while((get_mbut()&L_MOUSE) || (get_mbut()&R_MOUSE)) {
+ getmouseco_areawin(mouse);
+
+ scrarea_do_windraw(curarea);
+
+ persp(PERSP_WIN);
+ glLineWidth(2);
+ setlinestyle(2);
+ sdrawXORline(down[0],down[1],mouse[0],down[1]);
+ sdrawXORline(mouse[0],down[1],mouse[0],mouse[1]);
+ sdrawXORline(mouse[0],mouse[1],down[0],mouse[1]);
+ sdrawXORline(down[0],mouse[1],down[0],down[1]);
+ setlinestyle(0);
+ glLineWidth(1);
+ persp(PERSP_VIEW);
+
+ screen_swapbuffers();
+ backdrawview3d(0);
+ }
+
+ ret.xmin= down[0]<mouse[0]?down[0]:mouse[0];
+ ret.ymin= down[1]<mouse[1]?down[1]:mouse[1];
+ ret.xmax= down[0]>mouse[0]?down[0]:mouse[0];
+ ret.ymax= down[1]>mouse[1]?down[1]:mouse[1];
+ return ret;*/
+}
+
+void sculptmode_pmv(int mode)
+{
+ Object *ob= NULL; /*XXX: OBACT; */
+ rcti hb_2d;
+
+ if(ob_get_key(ob)) {
+ error("Cannot hide mesh with shape keys enabled");
+ return;
+ }
+
+ hb_2d= sculptmode_pmv_box(); /* Get 2D hide box */
+
+ sculptmode_correct_state();
+
+ waitcursor(1);
+
+ if(hb_2d.xmax-hb_2d.xmin > 3 && hb_2d.ymax-hb_2d.ymin > 3) {
+ init_sculptmatrices();
+
+ sculptmode_do_pmv(ob,&hb_2d,mode);
+ }
+ else mesh_pmv_off(ob, get_mesh(ob));
+
+ /*XXX: scrarea_do_windraw(curarea); */
+
+ waitcursor(0);
+}
+#endif
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
new file mode 100644
index 00000000000..112da5b4f0f
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -0,0 +1,67 @@
+/*
+ * $Id: BDR_sculptmode.h 13396 2008-01-25 04:17:38Z nicholasbishop $
+ *
+ * ***** 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) 2006 by Nicholas Bishop
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef BDR_SCULPTMODE_H
+#define BDR_SCULPTMODE_H
+
+#include "DNA_listBase.h"
+#include "DNA_vec_types.h"
+#include "BKE_sculpt.h"
+
+struct Brush;
+struct Mesh;
+struct Object;
+struct Scene;
+struct Sculpt;
+struct SculptStroke;
+
+/* Interface */
+void sculptmode_selectbrush_menu(void);
+void sculptmode_draw_mesh(int);
+void sculpt_paint_brush(char clear);
+void sculpt_stroke_draw(struct SculptStroke *);
+void sculpt_radialcontrol_start(int mode);
+
+struct Brush *sculptmode_brush(void);
+//void do_symmetrical_brush_actions(struct Sculpt *sd, struct wmOperator *wm, struct BrushAction *a, short *, short *);
+
+char sculpt_modifiers_active(struct Object *ob);
+void sculpt(Sculpt *sd);
+
+/* Stroke */
+struct SculptStroke *sculpt_stroke_new(const int max);
+void sculpt_stroke_free(struct SculptStroke *);
+void sculpt_stroke_add_point(struct SculptStroke *, const short x, const short y);
+void sculpt_stroke_apply(struct Sculpt *sd, struct SculptStroke *);
+void sculpt_stroke_apply_all(struct Sculpt *sd, struct SculptStroke *);
+
+/* Partial Mesh Visibility */
+void sculptmode_pmv(int mode);
+
+#endif
diff --git a/source/blender/editors/sculpt_paint/sculpt_stroke.c b/source/blender/editors/sculpt_paint/sculpt_stroke.c
new file mode 100644
index 00000000000..554ff580358
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_stroke.c
@@ -0,0 +1,274 @@
+/*
+ * $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) 2007 by Nicholas Bishop
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ * Storage and manipulation of sculptmode brush strokes.
+ *
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_listBase.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_sculpt.h"
+#include "BLI_blenlib.h"
+#include "BIF_gl.h"
+
+#include "sculpt_intern.h"
+
+#include <math.h>
+
+/* Temporary storage of input stroke control points */
+typedef struct StrokePoint {
+ struct StrokePoint *next, *prev;
+ short x, y;
+} StrokePoint;
+typedef struct SculptStroke {
+ short (*loc)[2];
+ int max;
+ int index;
+ float length;
+ ListBase final;
+ StrokePoint *final_mem;
+ float offset;
+} SculptStroke;
+
+SculptStroke *sculpt_stroke_new(const int max)
+{
+ SculptStroke *stroke = MEM_callocN(sizeof(SculptStroke), "SculptStroke");
+ stroke->loc = MEM_callocN(sizeof(short) * 4 * max, "SculptStroke.loc");
+ stroke->max = max;
+ stroke->index = -1;
+ return stroke;
+}
+
+void sculpt_stroke_free(SculptStroke *stroke)
+{
+ if(stroke) {
+ if(stroke->loc) MEM_freeN(stroke->loc);
+ if(stroke->final_mem) MEM_freeN(stroke->final_mem);
+
+ MEM_freeN(stroke);
+ }
+}
+
+void sculpt_stroke_add_point(SculptStroke *stroke, const short x, const short y)
+{
+ const int next = stroke->index + 1;
+
+ if(stroke->index == -1) {
+ stroke->loc[0][0] = x;
+ stroke->loc[0][1] = y;
+ stroke->index = 0;
+ }
+ else if(next < stroke->max) {
+ const int dx = x - stroke->loc[stroke->index][0];
+ const int dy = y - stroke->loc[stroke->index][1];
+ stroke->loc[next][0] = x;
+ stroke->loc[next][1] = y;
+ stroke->length += sqrt(dx*dx + dy*dy);
+ stroke->index = next;
+ }
+}
+
+static void sculpt_stroke_smooth(SculptStroke *stroke)
+{
+ /* Apply smoothing (exclude the first and last points)*/
+ StrokePoint *p = stroke->final.first;
+ if(p && p->next && p->next->next) {
+ for(p = p->next->next; p && p->next && p->next->next; p = p->next) {
+ p->x = p->prev->prev->x*0.1 + p->prev->x*0.2 + p->x*0.4 + p->next->x*0.2 + p->next->next->x*0.1;
+ p->y = p->prev->prev->y*0.1 + p->prev->y*0.2 + p->y*0.4 + p->next->y*0.2 + p->next->next->y*0.1;
+ }
+ }
+}
+
+static void sculpt_stroke_create_final(SculptStroke *stroke)
+{
+ if(stroke) {
+ StrokePoint *p, *pnext;
+ int i;
+
+ /* Copy loc into final */
+ if(stroke->final_mem)
+ MEM_freeN(stroke->final_mem);
+ stroke->final_mem = MEM_callocN(sizeof(StrokePoint) * (stroke->index + 1) * 2, "SculptStroke.final");
+ stroke->final.first = stroke->final.last = NULL;
+ for(i = 0; i <= stroke->index; ++i) {
+ p = &stroke->final_mem[i];
+ p->x = stroke->loc[i][0];
+ p->y = stroke->loc[i][1];
+ BLI_addtail(&stroke->final, p);
+ }
+
+ /* Remove shortest edges */
+ if(stroke->final.first) {
+ for(p = ((StrokePoint*)stroke->final.first)->next; p && p->next; p = pnext) {
+ const int dx = p->x - p->prev->x;
+ const int dy = p->y - p->prev->y;
+ const float len = sqrt(dx*dx + dy*dy);
+ pnext = p->next;
+ if(len < 10) {
+ BLI_remlink(&stroke->final, p);
+ }
+ }
+ }
+
+ sculpt_stroke_smooth(stroke);
+
+ /* Subdivide edges */
+ for(p = stroke->final.first; p && p->next; p = pnext) {
+ StrokePoint *np = &stroke->final_mem[i++];
+
+ pnext = p->next;
+ np->x = (p->x + p->next->x) / 2;
+ np->y = (p->y + p->next->y) / 2;
+ BLI_insertlink(&stroke->final, p, np);
+ }
+
+ sculpt_stroke_smooth(stroke);
+ }
+}
+
+static float sculpt_stroke_seglen(StrokePoint *p1, StrokePoint *p2)
+{
+ int dx = p2->x - p1->x;
+ int dy = p2->y - p1->y;
+ return sqrt(dx*dx + dy*dy);
+}
+
+static float sculpt_stroke_final_length(SculptStroke *stroke)
+{
+ StrokePoint *p;
+ float len = 0;
+ for(p = stroke->final.first; p && p->next; ++p)
+ len += sculpt_stroke_seglen(p, p->next);
+ return len;
+}
+
+/* If partial is nonzero, cuts off apply after that length has been processed */
+static StrokePoint *sculpt_stroke_apply_generic(Sculpt *sd, SculptStroke *stroke, const int partial)
+{
+ const int sdspace = 0; //XXX: sd->spacing;
+ const short spacing = sdspace > 0 ? sdspace : 2;
+ const int dots = sculpt_stroke_final_length(stroke) / spacing;
+ int i;
+ StrokePoint *p = stroke->final.first;
+ float startloc = stroke->offset;
+
+ for(i = 0; i < dots && p && p->next; ++i) {
+ const float dotloc = spacing * i;
+ short co[2];
+ float len = sculpt_stroke_seglen(p, p->next);
+ float u, v;
+
+ /* Find edge containing dot */
+ while(dotloc > startloc + len && p && p->next && p->next->next) {
+ p = p->next;
+ startloc += len;
+ len = sculpt_stroke_seglen(p, p->next);
+ }
+
+ if(!p || !p->next || dotloc > startloc + len)
+ break;
+
+ if(partial && startloc > partial) {
+ /* Calculate offset for next stroke segment */
+ stroke->offset = startloc + len - dotloc;
+ break;
+ }
+
+ u = (dotloc - startloc) / len;
+ v = 1 - u;
+
+ co[0] = p->x*v + p->next->x*u;
+ co[1] = p->y*v + p->next->y*u;
+
+ //do_symmetrical_brush_actions(sd, a, co, NULL);
+ }
+
+ return p ? p->next : NULL;
+}
+
+void sculpt_stroke_apply(Sculpt *sd, SculptStroke *stroke)
+{
+ /* TODO: make these values user-modifiable? */
+ const int partial_len = 100;
+ const int min_len = 200;
+
+ if(stroke) {
+ sculpt_stroke_create_final(stroke);
+
+ if(sculpt_stroke_final_length(stroke) > min_len) {
+ StrokePoint *p = sculpt_stroke_apply_generic(sd, stroke, partial_len);
+
+ /* Replace remaining values in stroke->loc with remaining stroke->final values */
+ stroke->index = -1;
+ stroke->length = 0;
+ for(; p; p = p->next) {
+ ++stroke->index;
+ stroke->loc[stroke->index][0] = p->x;
+ stroke->loc[stroke->index][1] = p->y;
+ if(p->next) {
+ stroke->length += sculpt_stroke_seglen(p, p->next);
+ }
+ }
+ }
+ }
+}
+
+void sculpt_stroke_apply_all(Sculpt *sd, SculptStroke *stroke)
+{
+ sculpt_stroke_create_final(stroke);
+
+ if(stroke) {
+ sculpt_stroke_apply_generic(sd, stroke, 0);
+ }
+}
+
+/* XXX: drawing goes elsewhere */
+void sculpt_stroke_draw(SculptStroke *stroke)
+{
+ if(stroke) {
+ StrokePoint *p;
+
+ /* Draws the original stroke */
+ /*glColor3f(1, 0, 0);
+ glBegin(GL_LINE_STRIP);
+ for(i = 0; i <= stroke->index; ++i)
+ glVertex2s(stroke->loc[i][0], stroke->loc[i][1]);
+ glEnd();*/
+
+ /* Draws the smoothed stroke */
+ glColor3f(0, 1, 0);
+ glBegin(GL_LINE_STRIP);
+ for(p = stroke->final.first; p; p = p->next)
+ glVertex2s(p->x, p->y);
+ glEnd();
+ }
+}