diff options
author | Brecht Van Lommel <brechtvanlommel@pandora.be> | 2009-02-20 02:53:40 +0300 |
---|---|---|
committer | Brecht Van Lommel <brechtvanlommel@pandora.be> | 2009-02-20 02:53:40 +0300 |
commit | 8e41a21607231b733ef0f5469be90ca4715e9afa (patch) | |
tree | a82636932532377a9bb8cdebe63b1b5415f15a1f /source/blender/editors/sculpt_paint | |
parent | 2cb5af58a6cf8120275b37c063899b0234719da2 (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/Makefile | 57 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/SConscript | 11 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_image.c | 5078 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_intern.h | 62 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_ops.c | 30 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_utils.c | 191 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_vertex.c | 1908 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 2176 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_intern.h | 67 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_stroke.c | 274 |
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(); + } +} |