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 | |
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')
36 files changed, 5493 insertions, 283 deletions
diff --git a/source/blender/editors/Makefile b/source/blender/editors/Makefile index 312227ba4e9..8a819195fbd 100644 --- a/source/blender/editors/Makefile +++ b/source/blender/editors/Makefile @@ -29,6 +29,6 @@ # Bounces make to subdirectories. SOURCEDIR = source/blender/editors -DIRS = armature mesh animation object sculpt datafiles transform screen curve gpencil physics preview uvedit space_outliner space_time space_view3d interface util space_api space_graph space_image space_node space_buttons space_info space_file space_sound space_action space_nla space_script space_text space_sequencer +DIRS = armature mesh animation object sculpt_paint datafiles transform screen curve gpencil physics preview uvedit space_outliner space_time space_view3d interface util space_api space_graph space_image space_node space_buttons space_info space_file space_sound space_action space_nla space_script space_text space_sequencer include nan_subdirs.mk diff --git a/source/blender/editors/SConscript b/source/blender/editors/SConscript index 8346092976e..a99d21b19a4 100644 --- a/source/blender/editors/SConscript +++ b/source/blender/editors/SConscript @@ -31,5 +31,5 @@ SConscript(['datafiles/SConscript', 'space_sequencer/SConscript', 'transform/SConscript', 'screen/SConscript', - 'sculpt/SConscript', + 'sculpt_paint/SConscript', 'uvedit/SConscript']) diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index d93f0546524..0433bf3f235 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -99,6 +99,7 @@ void ED_keymap_screen(struct wmWindowManager *wm); int ED_operator_screenactive(struct bContext *C); int ED_operator_screen_mainwinactive(struct bContext *C); int ED_operator_areaactive(struct bContext *C); +int ED_operator_regionactive(struct bContext *C); int ED_operator_scene_editable(struct bContext *C); diff --git a/source/blender/editors/include/ED_sculpt.h b/source/blender/editors/include/ED_sculpt.h index f42bb9b46a1..c6a8881a0c6 100644 --- a/source/blender/editors/include/ED_sculpt.h +++ b/source/blender/editors/include/ED_sculpt.h @@ -31,7 +31,15 @@ struct bContext; struct wmWindowManager; +/* sculpt.c */ void ED_operatortypes_sculpt(void); -void ED_keymap_sculpt(wmWindowManager *wm); +void ED_keymap_sculpt(struct wmWindowManager *wm); + +/* paint_ops.c */ +void ED_operatortypes_paint(void); + +/* paint_image.c */ +void undo_imagepaint_step(int step); +void undo_imagepaint_clear(void); #endif diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index f26f6b7460d..37e9c657d27 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -112,6 +112,8 @@ unsigned int view3d_sample_backbuf_rect(struct ViewContext *vc, short mval[2], i void *handle, unsigned int (*indextest)(void *handle, unsigned int index)); unsigned int view3d_sample_backbuf(struct ViewContext *vc, int x, int y); +int view_autodist(struct Scene *scene, struct ARegion *ar, struct View3D *v3d, short *mval, float mouse_worldloc[3]); + /* select */ #define MAXPICKBUF 10000 short view3d_opengl_select(struct ViewContext *vc, unsigned int *buffer, unsigned int bufsize, rcti *input); @@ -119,6 +121,7 @@ short view3d_opengl_select(struct ViewContext *vc, unsigned int *buffer, unsigne void view3d_set_viewcontext(struct bContext *C, struct ViewContext *vc); void view3d_operator_needs_opengl(const struct bContext *C); void view3d_get_view_aligned_coordinate(struct ViewContext *vc, float *fp, short mval[2]); +void view3d_get_object_project_mat(struct RegionView3D *v3d, struct Object *ob, float pmat[4][4], float vmat[4][4]);; /* XXX should move to arithb.c */ int edge_inside_circle(short centx, short centy, short rad, short x1, short y1, short x2, short y2); @@ -126,6 +129,5 @@ int edge_inside_circle(short centx, short centy, short rad, short x1, short y1, /* modes */ void ED_view3d_exit_paint_modes(struct bContext *C); - #endif /* ED_VIEW3D_H */ diff --git a/source/blender/editors/mesh/editface.c b/source/blender/editors/mesh/editface.c index ab6ec464a9b..a6c5e5beccf 100644 --- a/source/blender/editors/mesh/editface.c +++ b/source/blender/editors/mesh/editface.c @@ -144,16 +144,6 @@ MTFace *EM_get_active_mtface(EditMesh *em, EditFace **act_efa, MCol **mcol, int return NULL; } -static void make_tfaces(Object *ob) -{ - Mesh *me= ob->data; - - if(!me->mtface) { - me->mtface= CustomData_add_layer(&me->fdata, CD_MTFACE, CD_DEFAULT, - NULL, me->totface); - } -} - void reveal_tface(Scene *scene) { Mesh *me; @@ -734,155 +724,4 @@ void face_borderselect(Scene *scene, ARegion *ar) #endif } -/* Texture Paint */ - -void set_texturepaint(Scene *scene) /* toggle */ -{ - Object *ob = OBACT; - Mesh *me = 0; - - if(ob==NULL) return; - - if (object_data_is_libdata(ob)) { -// XXX error_libdata(); - return; - } - - me= get_mesh(ob); - - if(me) - DAG_object_flush_update(scene, ob, OB_RECALC_DATA); - - if(G.f & G_TEXTUREPAINT) { - G.f &= ~G_TEXTUREPAINT; - GPU_paint_set_mipmap(1); - } - else if (me) { - G.f |= G_TEXTUREPAINT; - - if(me->mtface==NULL) - make_tfaces(ob); - - brush_check_exists(&scene->toolsettings->imapaint.brush); - GPU_paint_set_mipmap(0); - } - -} - -static void texpaint_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 texpaint_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 */ - texpaint_project(ob, model, proj, v1, pv1); - texpaint_project(ob, model, proj, v2, pv2); - texpaint_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 texpaint_pick_uv(Scene *scene, Object *ob, Mesh *mesh, unsigned int faceindex, short *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; - -// XXX persp(PERSP_VIEW); - - /* 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 */ - texpaint_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; - } - - texpaint_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 { - texpaint_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); -} diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index e67e2d6e7a6..49acfed859c 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -73,6 +73,14 @@ /* ************** Exported Poll tests ********************** */ +int ED_operator_regionactive(bContext *C) +{ + if(CTX_wm_window(C)==NULL) return 0; + if(CTX_wm_screen(C)==NULL) return 0; + if(CTX_wm_region(C)==NULL) return 0; + return 1; +} + int ED_operator_areaactive(bContext *C) { if(CTX_wm_window(C)==NULL) return 0; diff --git a/source/blender/editors/sculpt/Makefile b/source/blender/editors/sculpt_paint/Makefile index 6b5c8baf27a..e810f7efbe4 100644 --- a/source/blender/editors/sculpt/Makefile +++ b/source/blender/editors/sculpt_paint/Makefile @@ -28,7 +28,7 @@ # # Makes module object directory and bounces make to subdirectories. -LIBNAME = ed_sculpt +LIBNAME = ed_sculpt_paint DIR = $(OCGDIR)/blender/$(LIBNAME) include nan_compile.mk diff --git a/source/blender/editors/sculpt/SConscript b/source/blender/editors/sculpt_paint/SConscript index 430563323de..3e00453e049 100644 --- a/source/blender/editors/sculpt/SConscript +++ b/source/blender/editors/sculpt_paint/SConscript @@ -8,4 +8,4 @@ incs += ' ../../windowmanager #/intern/guardedalloc #/extern/glew/include' incs += ' ../../render/extern/include #/intern/guardedalloc #intern/bmfont' incs += ' ../../gpu ../../makesrna' -env.BlenderLib ( 'bf_editors_sculpt', sources, Split(incs), [], libtype=['core'], priority=[40] )
\ No newline at end of file +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/space_view3d/vpaint.c b/source/blender/editors/sculpt_paint/paint_vertex.c index 1136518d424..1623c7feef1 100644 --- a/source/blender/editors/space_view3d/vpaint.c +++ b/source/blender/editors/sculpt_paint/paint_vertex.c @@ -90,8 +90,6 @@ #include "ED_util.h" #include "ED_view3d.h" -#include "view3d_intern.h" - /* vp->mode */ #define VP_MIX 0 #define VP_ADD 1 @@ -581,45 +579,6 @@ void vpaint_dogamma(Scene *scene) } } -/* used for both 3d view and image window */ -void sample_vpaint(Scene *scene, ARegion *ar) /* frontbuf */ -{ - VPaint *vp= scene->toolsettings->vpaint; - unsigned int col; - int x, y; - short mval[2]; - char *cp; - -// getmouseco_areawin(mval); - x= mval[0]; y= mval[1]; - - if(x<0 || y<0) return; - if(x>=ar->winx || y>=ar->winy) return; - - glReadBuffer(GL_FRONT); - glReadPixels(x, y, 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; - - } - } - -} - static unsigned int mcol_blend(unsigned int col1, unsigned int col2, int fac) { char *cp1, *cp2, *cp; @@ -1201,12 +1160,12 @@ static int paint_poll_test(bContext *C) return 1; } -void VIEW3D_OT_wpaint_toggle(wmOperatorType *ot) +void PAINT_OT_weight_paint_toggle(wmOperatorType *ot) { /* identifiers */ ot->name= "Weight Paint Mode"; - ot->idname= "VIEW3D_OT_wpaint_toggle"; + ot->idname= "PAINT_OT_weight_paint_toggle"; /* api callbacks */ ot->exec= set_wpaint; @@ -1222,7 +1181,7 @@ void VIEW3D_OT_wpaint_toggle(wmOperatorType *ot) void paint_radial_control_invoke(wmOperator *op, VPaint *vp) { int mode = RNA_int_get(op->ptr, "mode"); - float original_value= 1.0f; + float original_value; if(mode == WM_RADIALCONTROL_SIZE) original_value = vp->size; @@ -1291,12 +1250,12 @@ static int wpaint_radial_control_exec(bContext *C, wmOperator *op) return ret; } -void VIEW3D_OT_wpaint_radial_control(wmOperatorType *ot) +void PAINT_OT_weight_paint_radial_control(wmOperatorType *ot) { WM_OT_radial_control_partial(ot); ot->name= "Weight Paint Radial Control"; - ot->idname= "VIEW3D_OT_wpaint_radial_control"; + ot->idname= "PAINT_OT_weight_paint_radial_control"; ot->invoke= wpaint_radial_control_invoke; ot->modal= wpaint_radial_control_modal; @@ -1307,12 +1266,12 @@ void VIEW3D_OT_wpaint_radial_control(wmOperatorType *ot) ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO; } -void VIEW3D_OT_vpaint_radial_control(wmOperatorType *ot) +void PAINT_OT_vertex_paint_radial_control(wmOperatorType *ot) { WM_OT_radial_control_partial(ot); ot->name= "Vertex Paint Radial Control"; - ot->idname= "VIEW3D_OT_vpaint_radial_control"; + ot->idname= "PAINT_OT_vertex_paint_radial_control"; ot->invoke= vpaint_radial_control_invoke; ot->modal= vpaint_radial_control_modal; @@ -1391,6 +1350,7 @@ static int wpaint_modal(bContext *C, wmOperator *op, wmEvent *event) float paintweight= wp->weight; int *indexar= wpd->indexar; int totindex, index, alpha, totw; + short mval[2]; view3d_operator_needs_opengl(C); @@ -1401,12 +1361,15 @@ static int wpaint_modal(bContext *C, wmOperator *op, wmEvent *event) 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, event->mval[0], event->mval[1], wp->size); + totindex= sample_backbuf_area(vc, indexar, me->totface, mval[0], mval[1], wp->size); } else { - indexar[0]= view3d_sample_backbuf(vc, event->mval[0], event->mval[1]); + indexar[0]= view3d_sample_backbuf(vc, mval[0], mval[1]); if(indexar[0]) totindex= 1; else totindex= 0; } @@ -1481,7 +1444,7 @@ static int wpaint_modal(bContext *C, wmOperator *op, wmEvent *event) 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, event->mval); + 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); } @@ -1489,7 +1452,7 @@ static int wpaint_modal(bContext *C, wmOperator *op, wmEvent *event) } if((me->dvert+mface->v2)->flag) { - alpha= calc_vp_alpha_dl(wp, vc, wpd->wpimat, wpd->vertexcosnos+6*mface->v2, event->mval); + 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); } @@ -1497,7 +1460,7 @@ static int wpaint_modal(bContext *C, wmOperator *op, wmEvent *event) } if((me->dvert+mface->v3)->flag) { - alpha= calc_vp_alpha_dl(wp, vc, wpd->wpimat, wpd->vertexcosnos+6*mface->v3, event->mval); + 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); } @@ -1506,7 +1469,7 @@ static int wpaint_modal(bContext *C, wmOperator *op, wmEvent *event) if((me->dvert+mface->v4)->flag) { if(mface->v4) { - alpha= calc_vp_alpha_dl(wp, vc, wpd->wpimat, wpd->vertexcosnos+6*mface->v4, event->mval); + 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); } @@ -1628,12 +1591,12 @@ static int wpaint_invoke(bContext *C, wmOperator *op, wmEvent *event) return OPERATOR_RUNNING_MODAL; } -void VIEW3D_OT_wpaint(wmOperatorType *ot) +void PAINT_OT_weight_paint(wmOperatorType *ot) { /* identifiers */ ot->name= "Weight Paint"; - ot->idname= "VIEW3D_OT_wpaint"; + ot->idname= "PAINT_OT_weight_paint"; /* api callbacks */ ot->invoke= wpaint_invoke; @@ -1646,7 +1609,6 @@ void VIEW3D_OT_wpaint(wmOperatorType *ot) } - /* ************ set / clear vertex paint mode ********** */ @@ -1704,12 +1666,12 @@ static int set_vpaint(bContext *C, wmOperator *op) /* toggle */ return OPERATOR_FINISHED; } -void VIEW3D_OT_vpaint_toggle(wmOperatorType *ot) +void PAINT_OT_vertex_paint_toggle(wmOperatorType *ot) { /* identifiers */ ot->name= "Vertex Paint Mode"; - ot->idname= "VIEW3D_OT_vpaint_toggle"; + ot->idname= "PAINT_OT_vertex_paint_toggle"; /* api callbacks */ ot->exec= set_vpaint; @@ -1789,6 +1751,7 @@ static int vpaint_modal(bContext *C, wmOperator *op, wmEvent *event) float mat[4][4]; int *indexar= vpd->indexar; int totindex, index; + short mval[2]; view3d_operator_needs_opengl(C); @@ -1797,12 +1760,15 @@ static int vpaint_modal(bContext *C, wmOperator *op, wmEvent *event) 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, event->mval[0], event->mval[1], vp->size); + totindex= sample_backbuf_area(vc, indexar, me->totface, mval[0], mval[1], vp->size); } else { - indexar[0]= view3d_sample_backbuf(vc, event->mval[0], event->mval[1]); + indexar[0]= view3d_sample_backbuf(vc, mval[0], mval[1]); if(indexar[0]) totindex= 1; else totindex= 0; } @@ -1851,17 +1817,17 @@ static int vpaint_modal(bContext *C, wmOperator *op, wmEvent *event) } - alpha= calc_vp_alpha_dl(vp, vc, vpd->vpimat, vpd->vertexcosnos+6*mface->v1, event->mval); + 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, event->mval); + 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, event->mval); + 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, event->mval); + 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); } } @@ -1922,12 +1888,12 @@ static int vpaint_invoke(bContext *C, wmOperator *op, wmEvent *event) return OPERATOR_RUNNING_MODAL; } -void VIEW3D_OT_vpaint(wmOperatorType *ot) +void PAINT_OT_vertex_paint(wmOperatorType *ot) { /* identifiers */ ot->name= "Vertex Paint"; - ot->idname= "VIEW3D_OT_vpaint"; + ot->idname= "PAINT_OT_vertex_paint"; /* api callbacks */ ot->invoke= vpaint_invoke; @@ -1940,4 +1906,3 @@ void VIEW3D_OT_vpaint(wmOperatorType *ot) } - diff --git a/source/blender/editors/sculpt/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index f3d29989bb8..f3d29989bb8 100644 --- a/source/blender/editors/sculpt/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c diff --git a/source/blender/editors/sculpt/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 112da5b4f0f..112da5b4f0f 100644 --- a/source/blender/editors/sculpt/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h diff --git a/source/blender/editors/sculpt/stroke.c b/source/blender/editors/sculpt_paint/sculpt_stroke.c index 554ff580358..554ff580358 100644 --- a/source/blender/editors/sculpt/stroke.c +++ b/source/blender/editors/sculpt_paint/sculpt_stroke.c diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index 86d29840be6..cae8afb2c44 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -82,6 +82,7 @@ void ED_spacetypes_init(void) ED_operatortypes_mesh(); ED_operatortypes_sculpt(); ED_operatortypes_uvedit(); + ED_operatortypes_paint(); ED_operatortypes_curve(); ED_operatortypes_armature(); ED_marker_operatortypes(); diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c index b088f5920ed..912c2f86423 100644 --- a/source/blender/editors/space_image/space_image.c +++ b/source/blender/editors/space_image/space_image.c @@ -189,6 +189,10 @@ void image_keymap(struct wmWindowManager *wm) WM_keymap_add_item(keymap, "IMAGE_OT_reload", RKEY, KM_PRESS, KM_ALT, 0); WM_keymap_add_item(keymap, "IMAGE_OT_save", SKEY, KM_PRESS, KM_ALT, 0); + WM_keymap_add_item(keymap, "PAINT_OT_image_paint", ACTIONMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "PAINT_OT_grab_clone", SELECTMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "PAINT_OT_sample_color", SELECTMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "IMAGE_OT_sample", ACTIONMOUSE, KM_PRESS, 0, 0); RNA_enum_set(WM_keymap_add_item(keymap, "IMAGE_OT_set_curves_point", ACTIONMOUSE, KM_PRESS, KM_CTRL, 0)->ptr, "point", 0); RNA_enum_set(WM_keymap_add_item(keymap, "IMAGE_OT_set_curves_point", ACTIONMOUSE, KM_PRESS, KM_SHIFT, 0)->ptr, "point", 1); @@ -353,6 +357,10 @@ static void image_main_area_init(wmWindowManager *wm, ARegion *ar) // image space manages own v2d // UI_view2d_region_reinit(&ar->v2d, V2D_COMMONVIEW_STANDARD, ar->winx, ar->winy); + + /* image paint polls for mode */ + keymap= WM_keymap_listbase(wm, "ImagePaint", SPACE_IMAGE, 0); + WM_event_add_keymap_handler_bb(&ar->handlers, keymap, &ar->v2d.mask, &ar->winrct); /* own keymap */ keymap= WM_keymap_listbase(wm, "Image", SPACE_IMAGE, 0); diff --git a/source/blender/editors/space_view3d/drawmesh.c b/source/blender/editors/space_view3d/drawmesh.c index 92c8b8f0b69..5eb7caf50f1 100644 --- a/source/blender/editors/space_view3d/drawmesh.c +++ b/source/blender/editors/space_view3d/drawmesh.c @@ -478,7 +478,7 @@ void draw_mesh_text(Scene *scene, Object *ob, int glsl) return; /* don't draw when editing */ - if(me->edit_mesh) + if(ob == scene->obedit) return; else if(ob==OBACT) if(FACESEL_PAINT_TEST) @@ -558,7 +558,7 @@ void draw_mesh_textured(Scene *scene, View3D *v3d, RegionView3D *rv3d, Object *o /* draw the textured mesh */ draw_textured_begin(scene, v3d, rv3d, ob); - if(me->edit_mesh) { + if(ob == scene->obedit) { dm->drawMappedFacesTex(dm, draw_em_tf_mapped__set_draw, me->edit_mesh); } else if(faceselect) { if(G.f & G_WEIGHTPAINT) @@ -566,8 +566,9 @@ void draw_mesh_textured(Scene *scene, View3D *v3d, RegionView3D *rv3d, Object *o else dm->drawMappedFacesTex(dm, draw_tface_mapped__set_draw, me); } - else + else { dm->drawFacesTex(dm, draw_tface__set_draw); + } /* draw game engine text hack */ if(get_ob_property(ob, "Text")) @@ -576,7 +577,7 @@ void draw_mesh_textured(Scene *scene, View3D *v3d, RegionView3D *rv3d, Object *o draw_textured_end(); /* draw edges and selected faces over textured mesh */ - if(!me->edit_mesh && faceselect) + if(!(ob == scene->obedit) && faceselect) draw_tfaces3D(rv3d, ob, me, dm); /* reset from negative scale correction */ diff --git a/source/blender/editors/space_view3d/drawobject.c b/source/blender/editors/space_view3d/drawobject.c index 85edf6cf5fb..7df8f3a75db 100644 --- a/source/blender/editors/space_view3d/drawobject.c +++ b/source/blender/editors/space_view3d/drawobject.c @@ -5360,9 +5360,10 @@ void draw_object_backbufsel(Scene *scene, View3D *v3d, RegionView3D *rv3d, Objec switch( ob->type) { case OB_MESH: { - Mesh *me= ob->data; - EditMesh *em= me->edit_mesh; - if(em) { + if(ob == scene->obedit) { + Mesh *me= ob->data; + EditMesh *em= me->edit_mesh; + DerivedMesh *dm = editmesh_get_derived_cage(scene, ob, em, CD_MASK_BAREMESH); EM_init_index_arrays(em, 1, 1, 1); @@ -5415,7 +5416,7 @@ static void draw_object_mesh_instance(Scene *scene, View3D *v3d, RegionView3D *r DerivedMesh *dm=NULL, *edm=NULL; int glsl; - if(me->edit_mesh) + if(ob == scene->obedit) edm= editmesh_get_derived_base(ob, me->edit_mesh); else dm = mesh_get_derived_final(scene, ob, CD_MASK_BAREMESH); diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index 36ab867f2ae..d3935b49658 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -272,6 +272,9 @@ static void view3d_main_area_init(wmWindowManager *wm, ARegion *ar) /* modal ops keymaps */ view3d_modal_keymaps(wm, ar, rv3d->lastmode); + /* operator poll checks for modes */ + keymap= WM_keymap_listbase(wm, "ImagePaint", 0, 0); + WM_event_add_keymap_handler(&ar->handlers, keymap); } /* type callback, not region itself */ @@ -368,6 +371,11 @@ static void view3d_main_area_listener(ARegion *ar, wmNotifier *wmn) ED_region_tag_redraw(ar); break; } + case NC_IMAGE: + /* this could be more fine grained checks if we had + * more context than just the region */ + ED_region_tag_redraw(ar); + break; } } diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index ba774677420..706ce8c9d40 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -1081,7 +1081,7 @@ void backdrawview3d(Scene *scene, ARegion *ar, View3D *v3d) #endif if(G.f & G_VERTEXPAINT || G.f & G_WEIGHTPAINT); - else if((G.f & G_TEXTUREPAINT) && scene->toolsettings && (scene->toolsettings->imapaint.flag & IMAGEPAINT_PROJECT_DISABLE)==0); + else if((G.f & G_TEXTUREPAINT) && scene->toolsettings && (scene->toolsettings->imapaint.flag & IMAGEPAINT_PROJECT_DISABLE)); else if(scene->obedit && v3d->drawtype>OB_WIRE && (v3d->flag & V3D_ZBUF_SELECT)); else { v3d->flag &= ~V3D_NEEDBACKBUFDRAW; diff --git a/source/blender/editors/space_view3d/view3d_header.c b/source/blender/editors/space_view3d/view3d_header.c index f0b8587ba56..f30d2f2231f 100644 --- a/source/blender/editors/space_view3d/view3d_header.c +++ b/source/blender/editors/space_view3d/view3d_header.c @@ -137,10 +137,12 @@ static int retopo_mesh_paint_check() {return 0;} /* well... in this file a lot of view mode manipulation happens, so let's have it defined here */ void ED_view3d_exit_paint_modes(bContext *C) { + if(G.f & G_TEXTUREPAINT) + WM_operator_name_call(C, "PAINT_OT_texture_paint_toggle", WM_OP_EXEC_REGION_WIN, NULL); if(G.f & G_VERTEXPAINT) - WM_operator_name_call(C, "VIEW3D_OT_vpaint_toggle", WM_OP_EXEC_REGION_WIN, NULL); + WM_operator_name_call(C, "PAINT_OT_vertex_paint_toggle", WM_OP_EXEC_REGION_WIN, NULL); else if(G.f & G_WEIGHTPAINT) - WM_operator_name_call(C, "VIEW3D_OT_wpaint_toggle", WM_OP_EXEC_REGION_WIN, NULL); + WM_operator_name_call(C, "PAINT_OT_weight_paint_toggle", WM_OP_EXEC_REGION_WIN, NULL); if(G.f & G_SCULPTMODE) WM_operator_name_call(C, "SCULPT_OT_sculptmode_toggle", WM_OP_EXEC_REGION_WIN, NULL); @@ -5019,7 +5021,7 @@ static void do_view3d_header_buttons(bContext *C, void *arg, int event) ED_view3d_exit_paint_modes(C); if(obedit) ED_object_exit_editmode(C, EM_FREEDATA|EM_FREEUNDO|EM_WAITCURSOR); /* exit editmode and undo */ - WM_operator_name_call(C, "VIEW3D_OT_vpaint_toggle", WM_OP_EXEC_REGION_WIN, NULL); + WM_operator_name_call(C, "PAINT_OT_vertex_paint_toggle", WM_OP_EXEC_REGION_WIN, NULL); } } else if (v3d->modeselect == V3D_TEXTUREPAINTMODE_SEL) { @@ -5027,8 +5029,8 @@ static void do_view3d_header_buttons(bContext *C, void *arg, int event) v3d->flag &= ~V3D_MODE; ED_view3d_exit_paint_modes(C); if(obedit) ED_object_exit_editmode(C, EM_FREEDATA|EM_FREEUNDO|EM_WAITCURSOR); /* exit editmode and undo */ - -// XXX set_texturepaint(); + + WM_operator_name_call(C, "PAINT_OT_texture_paint_toggle", WM_OP_EXEC_REGION_WIN, NULL); } } else if (v3d->modeselect == V3D_WEIGHTPAINTMODE_SEL) { @@ -5038,7 +5040,7 @@ static void do_view3d_header_buttons(bContext *C, void *arg, int event) if(obedit) ED_object_exit_editmode(C, EM_FREEDATA|EM_FREEUNDO|EM_WAITCURSOR); /* exit editmode and undo */ - WM_operator_name_call(C, "VIEW3D_OT_wpaint_toggle", WM_OP_EXEC_REGION_WIN, NULL); + WM_operator_name_call(C, "PAINT_OT_weight_paint_toggle", WM_OP_EXEC_REGION_WIN, NULL); } } else if (v3d->modeselect == V3D_POSEMODE_SEL) { diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h index bf393a966c9..123f8674033 100644 --- a/source/blender/editors/space_view3d/view3d_intern.h +++ b/source/blender/editors/space_view3d/view3d_intern.h @@ -112,14 +112,6 @@ void VIEW3D_OT_circle_select(struct wmOperatorType *ot); void VIEW3D_OT_borderselect(struct wmOperatorType *ot); void VIEW3D_OT_lasso_select(struct wmOperatorType *ot); -/* vpaint.c */ -void VIEW3D_OT_vpaint_radial_control(struct wmOperatorType *ot); -void VIEW3D_OT_wpaint_radial_control(struct wmOperatorType *ot); -void VIEW3D_OT_vpaint_toggle(struct wmOperatorType *ot); -void VIEW3D_OT_vpaint(struct wmOperatorType *ot); -void VIEW3D_OT_wpaint_toggle(struct wmOperatorType *ot); -void VIEW3D_OT_wpaint(struct wmOperatorType *ot); - /* view3d_view.c */ void VIEW3D_OT_smoothview(struct wmOperatorType *ot); void VIEW3D_OT_setcameratoview(struct wmOperatorType *ot); @@ -130,6 +122,8 @@ int boundbox_clip(RegionView3D *rv3d, float obmat[][4], struct BoundBox *bb); void view3d_project_short_clip(struct ARegion *ar, float *vec, short *adr, float projmat[4][4], float wmat[4][4]); void view3d_project_short_noclip(struct ARegion *ar, float *vec, short *adr, float mat[4][4]); +void view3d_project_float(struct ARegion *a, float *vec, float *adr, float mat[4][4]); + void centerview(struct ARegion *ar, View3D *v3d); void smooth_view(struct bContext *C, Object *, Object *, float *ofs, float *quat, float *dist, float *lens); diff --git a/source/blender/editors/space_view3d/view3d_ops.c b/source/blender/editors/space_view3d/view3d_ops.c index 645dadcd275..f18b3c64cf8 100644 --- a/source/blender/editors/space_view3d/view3d_ops.c +++ b/source/blender/editors/space_view3d/view3d_ops.c @@ -83,12 +83,6 @@ void view3d_operatortypes(void) WM_operatortype_append(VIEW3D_OT_lasso_select); WM_operatortype_append(VIEW3D_OT_setcameratoview); WM_operatortype_append(VIEW3D_OT_drawtype); - WM_operatortype_append(VIEW3D_OT_vpaint_radial_control); - WM_operatortype_append(VIEW3D_OT_wpaint_radial_control); - WM_operatortype_append(VIEW3D_OT_vpaint_toggle); - WM_operatortype_append(VIEW3D_OT_wpaint_toggle); - WM_operatortype_append(VIEW3D_OT_vpaint); - WM_operatortype_append(VIEW3D_OT_wpaint); WM_operatortype_append(VIEW3D_OT_editmesh_face_toolbox); WM_operatortype_append(VIEW3D_OT_properties); WM_operatortype_append(VIEW3D_OT_localview); @@ -109,8 +103,8 @@ void view3d_keymap(wmWindowManager *wm) ListBase *keymap= WM_keymap_listbase(wm, "View3D Generic", SPACE_VIEW3D, 0); wmKeymapItem *km; - WM_keymap_add_item(keymap, "VIEW3D_OT_vpaint_toggle", VKEY, KM_PRESS, 0, 0); - WM_keymap_add_item(keymap, "VIEW3D_OT_wpaint_toggle", TABKEY, KM_PRESS, KM_CTRL, 0); + WM_keymap_add_item(keymap, "PAINT_OT_vertex_paint_toggle", VKEY, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "PAINT_OT_weight_paint_toggle", TABKEY, KM_PRESS, KM_CTRL, 0); WM_keymap_add_item(keymap, "VIEW3D_OT_properties", NKEY, KM_PRESS, 0, 0); @@ -118,8 +112,12 @@ void view3d_keymap(wmWindowManager *wm) keymap= WM_keymap_listbase(wm, "View3D", SPACE_VIEW3D, 0); /* paint poll checks mode */ - WM_keymap_verify_item(keymap, "VIEW3D_OT_vpaint", LEFTMOUSE, KM_PRESS, 0, 0); - WM_keymap_verify_item(keymap, "VIEW3D_OT_wpaint", LEFTMOUSE, KM_PRESS, 0, 0); + WM_keymap_verify_item(keymap, "PAINT_OT_vertex_paint", LEFTMOUSE, KM_PRESS, 0, 0); + WM_keymap_verify_item(keymap, "PAINT_OT_weight_paint", LEFTMOUSE, KM_PRESS, 0, 0); + + WM_keymap_add_item(keymap, "PAINT_OT_image_paint", LEFTMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "PAINT_OT_sample_color", RIGHTMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "PAINT_OT_set_clone_cursor", LEFTMOUSE, KM_PRESS, KM_CTRL, 0); WM_keymap_add_item(keymap, "SCULPT_OT_brush_stroke", LEFTMOUSE, KM_PRESS, 0, 0); WM_keymap_add_item(keymap, "SCULPT_OT_brush_stroke", LEFTMOUSE, KM_PRESS, KM_SHIFT, 0); @@ -196,10 +194,10 @@ void view3d_keymap(wmWindowManager *wm) RNA_enum_set(WM_keymap_add_item(keymap, "SCULPT_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0)->ptr, "mode", WM_RADIALCONTROL_STRENGTH); RNA_enum_set(WM_keymap_add_item(keymap, "SCULPT_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0)->ptr, "mode", WM_RADIALCONTROL_ANGLE); - RNA_enum_set(WM_keymap_add_item(keymap, "VIEW3D_OT_vpaint_radial_control", FKEY, KM_PRESS, 0, 0)->ptr, "mode", WM_RADIALCONTROL_SIZE); - RNA_enum_set(WM_keymap_add_item(keymap, "VIEW3D_OT_wpaint_radial_control", FKEY, KM_PRESS, 0, 0)->ptr, "mode", WM_RADIALCONTROL_SIZE); - RNA_enum_set(WM_keymap_add_item(keymap, "VIEW3D_OT_vpaint_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0)->ptr, "mode", WM_RADIALCONTROL_STRENGTH); - RNA_enum_set(WM_keymap_add_item(keymap, "VIEW3D_OT_wpaint_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0)->ptr, "mode", WM_RADIALCONTROL_STRENGTH); + RNA_enum_set(WM_keymap_add_item(keymap, "PAINT_OT_vertex_paint_radial_control", FKEY, KM_PRESS, 0, 0)->ptr, "mode", WM_RADIALCONTROL_SIZE); + RNA_enum_set(WM_keymap_add_item(keymap, "PAINT_OT_weight_paint_radial_control", FKEY, KM_PRESS, 0, 0)->ptr, "mode", WM_RADIALCONTROL_SIZE); + RNA_enum_set(WM_keymap_add_item(keymap, "PAINT_OT_vertex_paint_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0)->ptr, "mode", WM_RADIALCONTROL_STRENGTH); + RNA_enum_set(WM_keymap_add_item(keymap, "PAINT_OT_weight_paint_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0)->ptr, "mode", WM_RADIALCONTROL_STRENGTH); /* TODO - this is just while we have no way to load a text datablock */ RNA_string_set(WM_keymap_add_item(keymap, "SCRIPT_OT_run_pyfile", PKEY, KM_PRESS, 0, 0)->ptr, "filename", "test.py"); diff --git a/source/blender/editors/util/ed_util.c b/source/blender/editors/util/ed_util.c index ae75c9d967b..9dca9350754 100644 --- a/source/blender/editors/util/ed_util.c +++ b/source/blender/editors/util/ed_util.c @@ -45,6 +45,7 @@ #include "ED_armature.h" #include "ED_mesh.h" +#include "ED_sculpt.h" #include "ED_util.h" #include "UI_text.h" @@ -57,6 +58,7 @@ void ED_editors_exit(bContext *C) /* frees all editmode undos */ undo_editmode_clear(); + undo_imagepaint_clear(); /* global in meshtools... */ mesh_octree_table(ob, NULL, NULL, 'e'); diff --git a/source/blender/editors/util/undo.c b/source/blender/editors/util/undo.c index 15dba3fc951..0584644c027 100644 --- a/source/blender/editors/util/undo.c +++ b/source/blender/editors/util/undo.c @@ -60,6 +60,7 @@ #include "ED_mesh.h" #include "ED_object.h" #include "ED_screen.h" +#include "ED_sculpt.h" #include "ED_util.h" #include "WM_api.h" @@ -72,7 +73,6 @@ /* ********* XXX **************** */ static void undo_push_mball() {} -static void undo_imagepaint_step() {} static void sound_initialize_sounds() {} /* ********* XXX **************** */ diff --git a/source/blender/editors/uvedit/Makefile b/source/blender/editors/uvedit/Makefile index bd19bfdacb2..b8a8f0bc8af 100644 --- a/source/blender/editors/uvedit/Makefile +++ b/source/blender/editors/uvedit/Makefile @@ -45,6 +45,7 @@ CPPFLAGS += -I../../blenlib CPPFLAGS += -I../../makesdna CPPFLAGS += -I../../makesrna CPPFLAGS += -I../../imbuf +CPPFLAGS += -I../../gpu CPPFLAGS += -I$(NAN_GUARDEDALLOC)/include CPPFLAGS += -I$(NAN_OPENNL)/include diff --git a/source/blender/editors/uvedit/SConscript b/source/blender/editors/uvedit/SConscript index 29989a5b439..b472b89d23d 100644 --- a/source/blender/editors/uvedit/SConscript +++ b/source/blender/editors/uvedit/SConscript @@ -5,6 +5,6 @@ sources = env.Glob('*.c') incs = '../include ../../blenlib ../../blenkernel ../../makesdna ../../imbuf' incs += ' ../../windowmanager #/intern/guardedalloc #/extern/glew/include' -incs += ' ../../makesrna #/intern/opennl/extern' +incs += ' ../../makesrna #/intern/opennl/extern ../../gpu' env.BlenderLib ( 'bf_editors_uvedit', sources, Split(incs), [], libtype=['core'], priority=[45] ) diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 359573a141c..88520a12e75 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -51,7 +51,7 @@ typedef struct Brush { struct BrushClone clone; - struct CurveMapping *curve; /* falloff curve */ + struct CurveMapping *curve; /* falloff curve */ struct MTex *mtex[18]; /* MAX_MTEX */ short flag, blend; /* general purpose flag, blend mode */ @@ -63,7 +63,7 @@ typedef struct Brush { float rgb[3]; /* color */ float alpha; /* opacity */ - float rot; /* rotation in radians */ + float rot; /* rotation in radians */ short texact; /* active texture */ char sculpt_tool; /* active tool */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index e48300917d5..ce3d627863e 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -354,6 +354,8 @@ typedef struct ImagePaintSettings { /* for projection painting only */ short seam_bleed,normal_angle; + + void *paintcursor; /* wm handle */ } ImagePaintSettings; typedef struct ParticleBrushData { diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index 6a582b1569e..33af7945878 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -445,7 +445,7 @@ static char *rna_def_property_set_func(FILE *f, StructRNA *srna, PropertyRNA *pr fprintf(f, " else data->%s &= ~(%d<<%d);\n", dp->dnaname, dp->booleanbit, i); } else { - fprintf(f, " (&data->%s)[%d]= %s\n", dp->dnaname, i, (dp->booleannegative)? "!": ""); + fprintf(f, " (&data->%s)[%d]= %s", dp->dnaname, i, (dp->booleannegative)? "!": ""); rna_clamp_value(f, prop, 1, i); } } @@ -462,7 +462,7 @@ static char *rna_def_property_set_func(FILE *f, StructRNA *srna, PropertyRNA *pr fprintf(f, " data->%s[%d]= FTOCHAR(values[%d]);\n", dp->dnaname, i, i); } else { - fprintf(f, " data->%s[%d]= %s\n", dp->dnaname, i, (dp->booleannegative)? "!": ""); + fprintf(f, " data->%s[%d]= %s", dp->dnaname, i, (dp->booleannegative)? "!": ""); rna_clamp_value(f, prop, 1, i); } } diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 69e2cba5331..d21c79fbe52 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -241,10 +241,14 @@ static void rna_def_operator_stroke_element(BlenderRNA *brna) RNA_def_property_array(prop, 2); RNA_def_property_ui_text(prop, "Mouse", ""); - /*prop= RNA_def_property(srna, "pressure", PROP_FLOAT, PROP_NONE); + prop= RNA_def_property(srna, "pressure", PROP_FLOAT, PROP_NONE); RNA_def_property_flag(prop, PROP_IDPROPERTY); - RNA_def_property_range(prop, 0, 1); - RNA_def_property_ui_text(prop, "Pressure", "");*/ + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text(prop, "Pressure", "Tablet pressure."); + + prop= RNA_def_property(srna, "time", PROP_FLOAT, PROP_UNSIGNED); + RNA_def_property_flag(prop, PROP_IDPROPERTY); + RNA_def_property_ui_text(prop, "Time", ""); prop= RNA_def_property(srna, "flip", PROP_BOOLEAN, PROP_NONE); RNA_def_property_flag(prop, PROP_IDPROPERTY); @@ -252,8 +256,6 @@ static void rna_def_operator_stroke_element(BlenderRNA *brna) /* XXX: Tool (this will be for pressing a modifier key for a different brush, e.g. switching to a Smooth brush in the middle of the stroke */ - - /* XXX: Time (should be useful for airbrush mode) */ } void RNA_def_brush(BlenderRNA *brna) diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 4dc62985f53..2f24e180ae6 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -155,6 +155,7 @@ typedef struct wmNotifier { #define NC_LAMP (8<<24) #define NC_GROUP (9<<24) #define NC_IMAGE (10<<24) +#define NC_BRUSH (11<<24) /* data type, 256 entries is enough, it can overlap */ #define NOTE_DATA 0x00FF0000 diff --git a/source/blender/windowmanager/wm_event_types.h b/source/blender/windowmanager/wm_event_types.h index b3fb17055e1..9b9af1844b6 100644 --- a/source/blender/windowmanager/wm_event_types.h +++ b/source/blender/windowmanager/wm_event_types.h @@ -40,6 +40,11 @@ #define EVT_DATA_GESTURE 2 #define EVT_DATA_TIMER 3 +/* tablet active */ +#define EVT_TABLET_NONE 0 +#define EVT_TABLET_STYLUS 1 +#define EVT_TABLET_ERASER 2 + #define MOUSEX 0x004 #define MOUSEY 0x005 |